diff options
306 files changed, 28960 insertions, 21667 deletions
diff --git a/.travis.yml b/.travis.yml index 53faf29cc5..b920f70f45 100644 --- a/.travis.yml +++ b/.travis.yml @@ -77,7 +77,6 @@ addons: - gdb - gperf - language-pack-tr - - libc6-dev-i386 - libtool-bin - locales - ninja-build @@ -86,7 +85,7 @@ addons: - valgrind - xclip homebrew: - update: false + update: true casks: - powershell packages: @@ -160,6 +159,26 @@ jobs: # Minimum required CMake. - CMAKE_URL=https://cmake.org/files/v2.8/cmake-2.8.12-Linux-i386.sh - *common-job-env + - name: big-endian + os: linux + arch: s390x + compiler: gcc + env: + - FUNCTIONALTEST=functionaltest-lua + - CMAKE_FLAGS="$CMAKE_FLAGS -DPREFER_LUA=ON" + - DEPS_CMAKE_FLAGS="$DEPS_CMAKE_FLAGS -DUSE_BUNDLED_LUAJIT=OFF" + - *common-job-env + addons: + apt: + packages: + - *common-apt-packages + - gettext + - python-pip + - python3-pip + - python-setuptools + - python3-setuptools + - python-dev + - python3-dev - name: clang-tsan os: linux compiler: clang diff --git a/CMakeLists.txt b/CMakeLists.txt index dbe2bf4d10..a4e49ccfc5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -308,6 +308,11 @@ if(UNIX) endif() endif() +check_c_compiler_flag(-fno-common HAVE_FNO_COMMON) +if (HAVE_FNO_COMMON) + add_compile_options(-fno-common) +endif() + check_c_compiler_flag(-fdiagnostics-color=auto HAS_DIAG_COLOR_FLAG) if(HAS_DIAG_COLOR_FLAG) if(CMAKE_GENERATOR MATCHES "Ninja") @@ -369,13 +374,6 @@ include_directories(SYSTEM ${MSGPACK_INCLUDE_DIRS}) find_package(LibLUV 1.30.0 REQUIRED) include_directories(SYSTEM ${LIBLUV_INCLUDE_DIRS}) -find_package(UTF8PROC REQUIRED) -include_directories(SYSTEM ${UTF8PROC_INCLUDE_DIRS}) -if(WIN32) - add_definitions(-DUTF8PROC_STATIC) -endif() - - # Note: The test lib requires LuaJIT; it will be skipped if LuaJIT is missing. option(PREFER_LUA "Prefer Lua over LuaJIT in the nvim executable." OFF) @@ -455,17 +453,17 @@ if((CLANG_ASAN_UBSAN OR CLANG_MSAN OR CLANG_TSAN) AND NOT CMAKE_C_COMPILER_ID MA message(FATAL_ERROR "Sanitizers are only supported for Clang") endif() +if(ENABLE_LIBICONV) + find_package(Iconv REQUIRED) + include_directories(SYSTEM ${Iconv_INCLUDE_DIRS}) +endif() + if(ENABLE_LIBINTL) # LibIntl (not Intl) selects our FindLibIntl.cmake script. #8464 find_package(LibIntl REQUIRED) include_directories(SYSTEM ${LibIntl_INCLUDE_DIRS}) endif() -if(ENABLE_LIBICONV) - find_package(Iconv REQUIRED) - include_directories(SYSTEM ${Iconv_INCLUDE_DIRS}) -endif() - # Determine platform's threading library. Set CMAKE_THREAD_PREFER_PTHREAD # explicitly to indicate a strong preference for pthread. set(CMAKE_THREAD_PREFER_PTHREAD ON) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 750d853158..c7d8398bf0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ If you want to help but don't know where to start, here are some low-risk/isolated tasks: - [Merge a Vim patch]. -- Try a [complexity:low] issue. +- Try a [good first issue](../../labels/good%20first%20issue) or [complexity:low] issue. - Fix bugs found by [Clang](#clang-scan-build), [PVS](#pvs-studio) or [Coverity](#coverity). @@ -58,11 +58,15 @@ endif BUILD_CMD = $(BUILD_TOOL) -ifneq ($(VERBOSE),) - # Only need to handle Ninja here. Make will inherit the VERBOSE variable. - ifeq ($(BUILD_TYPE),Ninja) +# Only need to handle Ninja here. Make will inherit the VERBOSE variable, and the -j and -n flags. +ifeq ($(BUILD_TYPE),Ninja) + ifneq ($(VERBOSE),) BUILD_CMD += -v endif + BUILD_CMD += $(shell printf '%s' '$(MAKEFLAGS)' | grep -o -- '-j[0-9]\+') + ifeq (n,$(findstring n,$(firstword -$(MAKEFLAGS)))) + BUILD_CMD += -n + endif endif DEPS_CMAKE_FLAGS ?= diff --git a/ci/before_script.sh b/ci/before_script.sh index a0e87adb9e..1759dbe942 100755 --- a/ci/before_script.sh +++ b/ci/before_script.sh @@ -10,6 +10,12 @@ fi CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${CI_DIR}/common/build.sh" +# Enable ipv6 on Travis. ref: a39c8b7ce30d +if ! test "${TRAVIS_OS_NAME}" = osx ; then + echo "before_script.sh: enable ipv6" + sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=0 +fi + # Test some of the configuration variables. if [[ -n "${GCOV}" ]] && [[ ! $(type -P "${GCOV}") ]]; then echo "\$GCOV: '${GCOV}' is not executable." diff --git a/ci/build.ps1 b/ci/build.ps1 index 627dc69e88..36570be7ae 100644 --- a/ci/build.ps1 +++ b/ci/build.ps1 @@ -36,8 +36,7 @@ $scoop = (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh' Invoke-Expression $scoop } -scoop install diffutils perl -diff3 --version +scoop install perl perl --version cpanm.bat --version @@ -81,7 +80,7 @@ if ($compiler -eq 'MINGW') { # in MSYS2, but we cannot build inside the MSYS2 shell. $cmakeGenerator = 'Ninja' $cmakeGeneratorArgs = '-v' - $mingwPackages = @('ninja', 'cmake').ForEach({ + $mingwPackages = @('ninja', 'cmake', 'diffutils').ForEach({ "mingw-w64-$arch-$_" }) @@ -123,22 +122,6 @@ if (-not $NoTests) { npm.cmd install -g neovim Get-Command -CommandType Application neovim-node-host.cmd npm.cmd link neovim - - - $env:TREE_SITTER_DIR = $env:USERPROFILE + "\tree-sitter-build" - mkdir "$env:TREE_SITTER_DIR\bin" - - $xbits = if ($bits -eq '32') {'x86'} else {'x64'} - Invoke-WebRequest -UseBasicParsing -Uri "https://github.com/tree-sitter/tree-sitter/releases/download/0.15.9/tree-sitter-windows-$xbits.gz" -OutFile tree-sitter.exe.gz - C:\msys64\usr\bin\gzip -d tree-sitter.exe.gz - - Invoke-WebRequest -UseBasicParsing -Uri "https://codeload.github.com/tree-sitter/tree-sitter-c/zip/v0.15.2" -OutFile tree_sitter_c.zip - Expand-Archive .\tree_sitter_c.zip -DestinationPath . - cd tree-sitter-c-0.15.2 - ..\tree-sitter.exe test - if (-Not (Test-Path -PathType Leaf "$env:TREE_SITTER_DIR\bin\c.dll")) { - exit 1 - } } if ($compiler -eq 'MSVC') { @@ -173,6 +156,9 @@ if (-not $NoTests) { # Functional tests # The $LastExitCode from MSBuild can't be trusted $failed = $false + + # Run only this test file: + # $env:TEST_FILE = "test\functional\foo.lua" cmake --build . --config $cmakeBuildType --target functionaltest -- $cmakeGeneratorArgs 2>&1 | foreach { $failed = $failed -or $_ -match 'functional tests failed with error'; $_ } diff --git a/ci/common/build.sh b/ci/common/build.sh index 02e1110a15..0024f2cbd5 100644 --- a/ci/common/build.sh +++ b/ci/common/build.sh @@ -86,12 +86,3 @@ build_nvim() { cd "${TRAVIS_BUILD_DIR}" } - -macos_rvm_dance() { - # neovim-ruby gem requires a ruby newer than the macOS default. - source ~/.rvm/scripts/rvm - rvm get stable --auto-dotfiles - rvm reload - rvm use 2.2.5 - rvm use -} diff --git a/ci/install.sh b/ci/install.sh index b3ec9e7f65..a4dfc87a1b 100755 --- a/ci/install.sh +++ b/ci/install.sh @@ -14,9 +14,9 @@ fi # Use default CC to avoid compilation problems when installing Python modules. echo "Install neovim module for Python 3." -CC=cc python3 -m pip -q install --upgrade pynvim +CC=cc python3 -m pip -q install --user --upgrade pynvim echo "Install neovim module for Python 2." -CC=cc python2 -m pip -q install --upgrade pynvim +CC=cc python2 -m pip -q install --user --upgrade pynvim echo "Install neovim RubyGem." gem install --no-document --version ">= 0.8.0" neovim @@ -27,29 +27,5 @@ nvm use 10 npm install -g neovim npm link neovim -echo "Install tree-sitter npm package" - -# FIXME -# https://github.com/tree-sitter/tree-sitter/commit/e14e285a1087264a8c74a7c62fcaecc49db9d904 -# If queries added to tree-sitter-c, we can use latest tree-sitter-cli -npm install -g tree-sitter-cli@v0.15.9 - -echo "Install tree-sitter c parser" -curl "https://codeload.github.com/tree-sitter/tree-sitter-c/tar.gz/v0.15.2" -o tree_sitter_c.tar.gz -tar xf tree_sitter_c.tar.gz -cd tree-sitter-c-0.15.2 -export TREE_SITTER_DIR=$HOME/tree-sitter-build/ -mkdir -p "$TREE_SITTER_DIR/bin" - -if [[ "$BUILD_32BIT" != "ON" ]]; then - # builds c parser in $HOME/tree-sitter-build/bin/c.(so|dylib) - tree-sitter test -else - # no tree-sitter binary for 32bit linux, so fake it (no tree-sitter unit tests) - cd src/ - gcc -m32 -o "$TREE_SITTER_DIR/bin/c.so" -shared parser.c -I. -fi -test -f "$TREE_SITTER_DIR/bin/c.so" - sudo cpanm -n Neovim::Ext || cat "$HOME/.cpanm/build.log" perl -W -e 'use Neovim::Ext; print $Neovim::Ext::VERSION' diff --git a/ci/run_tests.sh b/ci/run_tests.sh index 97f380b7b9..d91ac5589e 100755 --- a/ci/run_tests.sh +++ b/ci/run_tests.sh @@ -19,7 +19,7 @@ exit_suite --continue source ~/.nvm/nvm.sh nvm use 10 -export TREE_SITTER_DIR=$HOME/tree-sitter-build/ + enter_suite tests diff --git a/cmake/FindLibIntl.cmake b/cmake/FindLibIntl.cmake index 5663098147..09eafb786a 100644 --- a/cmake/FindLibIntl.cmake +++ b/cmake/FindLibIntl.cmake @@ -38,6 +38,9 @@ endif() if (LibIntl_LIBRARY) list(APPEND CMAKE_REQUIRED_LIBRARIES "${LibIntl_LIBRARY}") endif() +if (MSVC) + list(APPEND CMAKE_REQUIRED_LIBRARIES ${ICONV_LIBRARY}) +endif() check_c_source_compiles(" #include <libintl.h> @@ -48,6 +51,9 @@ int main(int argc, char** argv) { bind_textdomain_codeset(\"foo\", \"bar\"); textdomain(\"foo\"); }" HAVE_WORKING_LIBINTL) +if (MSVC) + list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES ${ICONV_LIBRARY}) +endif() if (LibIntl_INCLUDE_DIR) list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${LibIntl_INCLUDE_DIR}") endif() diff --git a/cmake/FindUTF8PROC.cmake b/cmake/FindUTF8PROC.cmake deleted file mode 100644 index fdb462b779..0000000000 --- a/cmake/FindUTF8PROC.cmake +++ /dev/null @@ -1,16 +0,0 @@ -# - Try to find utf8proc -# Once done this will define -# UTF8PROC_FOUND - System has utf8proc -# UTF8PROC_INCLUDE_DIRS - The utf8proc include directories -# UTF8PROC_LIBRARIES - The libraries needed to use utf8proc - -include(LibFindMacros) - -set(UTF8PROC_NAMES utf8proc) -if(MSVC) - # "utf8proc_static" is used for MSVC (when built statically from third-party). - # https://github.com/JuliaStrings/utf8proc/commit/0975bf9b6. - list(APPEND UTF8PROC_NAMES utf8proc_static) -endif() -libfind_pkg_detect(UTF8PROC utf8proc FIND_PATH utf8proc.h FIND_LIBRARY ${UTF8PROC_NAMES}) -libfind_process(UTF8PROC REQUIRED) diff --git a/config/pathdef.c.in b/config/pathdef.c.in index 41950f5ac5..6a8a2b205a 100644 --- a/config/pathdef.c.in +++ b/config/pathdef.c.in @@ -3,5 +3,6 @@ #include "${PROJECT_SOURCE_DIR}/src/nvim/vim.h" char *default_vim_dir = "${CMAKE_INSTALL_FULL_DATAROOTDIR}/nvim"; char *default_vimruntime_dir = ""; +char *default_lib_dir = "${CMAKE_INSTALL_FULL_LIBDIR}/nvim"; char_u *compiled_user = (char_u *)"${USERNAME}"; char_u *compiled_sys = (char_u *)"${HOSTNAME}"; diff --git a/man/nvim.1 b/man/nvim.1 index bc11739747..9e7da629f7 100644 --- a/man/nvim.1 +++ b/man/nvim.1 @@ -378,7 +378,7 @@ See also System-global .Nm configuration file. -.It Pa /usr/local/share/nvim +.It Pa $VIM System-global .Nm runtime directory. diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index cc7d86d0c1..6d481e9f49 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -163,7 +163,7 @@ function! s:check_clipboard() abort endif endfunction -" Get the latest Neovim Python client (pynvim) version from PyPI. +" Get the latest Nvim Python client (pynvim) version from PyPI. function! s:latest_pypi_version() abort let pypi_version = 'unable to get pypi response' let pypi_response = s:download('https://pypi.python.org/pypi/pynvim/json') @@ -180,7 +180,7 @@ endfunction " Get version information using the specified interpreter. The interpreter is " used directly in case breaking changes were introduced since the last time -" Neovim's Python client was updated. +" Nvim's Python client was updated. " " Returns: [ " {python executable version}, @@ -224,7 +224,7 @@ function! s:version_info(python) abort \ 'print("{}.{}.{}{}".format(v.major, v.minor, v.patch, v.prerelease))'], \ '', 1, 1) if empty(nvim_version) - let nvim_version = 'unable to find neovim Python module version' + let nvim_version = 'unable to find pynvim module version' let base = fnamemodify(nvim_path, ':h') let metas = glob(base.'-*/METADATA', 1, 1) \ + glob(base.'-*/PKG-INFO', 1, 1) @@ -363,7 +363,7 @@ function! s:check_python(version) abort \ && !empty(pyenv_root) && resolve(python_exe) !~# '^'.pyenv_root.'/' call health#report_warn('pyenv is not set up optimally.', [ \ printf('Create a virtualenv specifically ' - \ . 'for Neovim using pyenv, and set `g:%s`. This will avoid ' + \ . 'for Nvim using pyenv, and set `g:%s`. This will avoid ' \ . 'the need to install the pynvim module in each ' \ . 'version/virtualenv.', host_prog_var) \ ]) @@ -377,7 +377,7 @@ function! s:check_python(version) abort if resolve(python_exe) !~# '^'.venv_root.'/' call health#report_warn('Your virtualenv is not set up optimally.', [ \ printf('Create a virtualenv specifically ' - \ . 'for Neovim and use `g:%s`. This will avoid ' + \ . 'for Nvim and use `g:%s`. This will avoid ' \ . 'the need to install the pynvim module in each ' \ . 'virtualenv.', host_prog_var) \ ]) @@ -392,18 +392,6 @@ function! s:check_python(version) abort let python_exe = '' endif - " Check if $VIRTUAL_ENV is valid. - if exists('$VIRTUAL_ENV') && !empty(python_exe) - if $VIRTUAL_ENV ==# matchstr(python_exe, '^\V'.$VIRTUAL_ENV) - call health#report_info('$VIRTUAL_ENV matches executable') - else - call health#report_warn( - \ '$VIRTUAL_ENV exists but appears to be inactive. ' - \ . 'This could lead to unexpected results.', - \ [ 'If you are using Zsh, see: http://vi.stackexchange.com/a/7654' ]) - endif - endif - " Diagnostic output call health#report_info('Executable: ' . (empty(python_exe) ? 'Not found' : python_exe)) if len(python_multiple) @@ -497,6 +485,76 @@ function! s:check_for_pyenv() abort return [pyenv_path, pyenv_root] endfunction +" Resolves Python executable path by invoking and checking `sys.executable`. +function! s:python_exepath(invocation) abort + return s:normalize_path(system(a:invocation + \ . ' -c "import sys; sys.stdout.write(sys.executable)"')) +endfunction + +" Checks that $VIRTUAL_ENV Python executables are found at front of $PATH in +" Nvim and subshells. +function! s:check_virtualenv() abort + call health#report_start('Python virtualenv') + if !exists('$VIRTUAL_ENV') + call health#report_ok('no $VIRTUAL_ENV') + return + endif + let errors = [] + " Keep hints as dict keys in order to discard duplicates. + let hints = {} + " The virtualenv should contain some Python executables, and those + " executables should be first both on Nvim's $PATH and the $PATH of + " subshells launched from Nvim. + let bin_dir = has('win32') ? '/Scripts' : '/bin' + let venv_bins = glob($VIRTUAL_ENV . bin_dir . '/python*', v:true, v:true) + " XXX: Remove irrelevant executables found in bin/. + let venv_bins = filter(venv_bins, 'v:val !~# "python-config"') + if len(venv_bins) + for venv_bin in venv_bins + let venv_bin = s:normalize_path(venv_bin) + let py_bin_basename = fnamemodify(venv_bin, ':t') + let nvim_py_bin = s:python_exepath(exepath(py_bin_basename)) + let subshell_py_bin = s:python_exepath(py_bin_basename) + if venv_bin !=# nvim_py_bin + call add(errors, '$PATH yields this '.py_bin_basename.' executable: '.nvim_py_bin) + let hint = '$PATH ambiguities arise if the virtualenv is not ' + \.'properly activated prior to launching Nvim. Close Nvim, activate the virtualenv, ' + \.'check that invoking Python from the command line launches the correct one, ' + \.'then relaunch Nvim.' + let hints[hint] = v:true + endif + if venv_bin !=# subshell_py_bin + call add(errors, '$PATH in subshells yields this ' + \.py_bin_basename . ' executable: '.subshell_py_bin) + let hint = '$PATH ambiguities in subshells typically are ' + \.'caused by your shell config overriding the $PATH previously set by the ' + \.'virtualenv. Either prevent them from doing so, or use this workaround: ' + \.'https://vi.stackexchange.com/a/7654' + let hints[hint] = v:true + endif + endfor + else + call add(errors, 'no Python executables found in the virtualenv '.bin_dir.' directory.') + endif + + if len(errors) + let msg = '$VIRTUAL_ENV is set to: '.$VIRTUAL_ENV + if len(venv_bins) + let msg .= "\nAnd its ".bin_dir.' directory contains: ' + \.join(map(venv_bins, "fnamemodify(v:val, ':t')"), ', ') + endif + let conj = "\nBut " + for error in errors + let msg .= conj.error + let conj = "\nAnd " + endfor + let msg .= "\nSo invoking Python may lead to unexpected results." + call health#report_warn(msg, keys(hints)) + else + call health#report_ok('$VIRTUAL_ENV provides :python, :python3, et al.') + endif +endfunction + function! s:check_ruby() abort call health#report_start('Ruby provider (optional)') @@ -531,7 +589,7 @@ function! s:check_ruby() abort \ 'Are you behind a firewall or proxy?']) return endif - let latest_gem = get(split(latest_gem, 'neovim (\|, \|)$' ), 1, 'not found') + let latest_gem = get(split(latest_gem, 'neovim (\|, \|)$' ), 0, 'not found') let current_gem_cmd = host .' --version' let current_gem = s:system(current_gem_cmd) @@ -567,7 +625,7 @@ function! s:check_node() abort let node_v = get(split(s:system('node -v'), "\n"), 0, '') call health#report_info('Node.js: '. node_v) if s:shell_error || s:version_cmp(node_v[1:], '6.0.0') < 0 - call health#report_warn('Neovim node.js host does not support '.node_v) + call health#report_warn('Nvim node.js host does not support '.node_v) " Skip further checks, they are nonsense if nodejs is too old. return endif @@ -582,7 +640,7 @@ function! s:check_node() abort \ 'Run in shell (if you use yarn): yarn global add neovim']) return endif - call health#report_info('Neovim node.js host: '. host) + call health#report_info('Nvim node.js host: '. host) let manager = executable('npm') ? 'npm' : 'yarn' let latest_npm_cmd = has('win32') ? @@ -637,7 +695,7 @@ function! s:check_perl() abort let perl_v = get(split(s:system(['perl', '-W', '-e', 'print $^V']), "\n"), 0, '') call health#report_info('Perl: '. perl_v) if s:shell_error - call health#report_warn('Neovim perl host does not support '.perl_v) + call health#report_warn('Nvim perl host does not support '.perl_v) " Skip further checks, they are nonsense if perl is too old. return endif @@ -648,17 +706,31 @@ function! s:check_perl() abort \ ['Run in shell: cpanm Neovim::Ext']) return endif - call health#report_info('Neovim perl host: '. host) + call health#report_info('Nvim perl host: '. host) - let latest_cpan_cmd = 'cpanm --info Neovim::Ext' + let latest_cpan_cmd = 'cpanm --info -q Neovim::Ext' let latest_cpan = s:system(latest_cpan_cmd) if s:shell_error || empty(latest_cpan) call health#report_error('Failed to run: '. latest_cpan_cmd, \ ["Make sure you're connected to the internet.", \ 'Are you behind a firewall or proxy?']) return + elseif latest_cpan[0] ==# '!' + let cpanm_errs = split(latest_cpan, '!') + if cpanm_errs[0] =~# "Can't write to " + call health#report_warn(cpanm_errs[0], cpanm_errs[1:-2]) + " Last line is the package info + let latest_cpan = cpanm_errs[-1] + else + call health#report_error('Unknown warning from command: ' . latest_cpan_cmd, cpanm_errs) + return + endif endif let latest_cpan = matchstr(latest_cpan, '\(\.\?\d\)\+') + if empty(latest_cpan) + call health#report_error('Cannot parse version number from cpanm output: ' . latest_cpan) + return + endif let current_cpan_cmd = [host, '-W', '-MNeovim::Ext', '-e', 'print $Neovim::Ext::VERSION'] let current_cpan = s:system(current_cpan_cmd) @@ -682,6 +754,7 @@ function! health#provider#check() abort call s:check_clipboard() call s:check_python(2) call s:check_python(3) + call s:check_virtualenv() call s:check_ruby() call s:check_node() call s:check_perl() diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index 5feab0ce70..122ae357bc 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -328,18 +328,24 @@ function! man#complete(arg_lead, cmd_line, cursor_pos) abort return s:complete(sect, sect, name) endfunction -function! s:get_paths(sect, name) abort +function! s:get_paths(sect, name, do_fallback) abort + " callers must try-catch this, as some `man` implementations don't support `s:find_arg` try let mandirs = join(split(s:system(['man', s:find_arg]), ':\|\n'), ',') + return globpath(mandirs,'man?/'.a:name.'*.'.a:sect.'*', 0, 1) catch - call s:error(v:exception) - return + if !a:do_fallback + throw v:exception + endif + + " fallback to a single path, with the page we're trying to find + let [l:sect, l:name, l:path] = s:verify_exists(a:sect, a:name) + return [l:path] endtry - return globpath(mandirs,'man?/'.a:name.'*.'.a:sect.'*', 0, 1) endfunction function! s:complete(sect, psect, name) abort - let pages = s:get_paths(a:sect, a:name) + let pages = s:get_paths(a:sect, a:name, v:false) " We remove duplicates in case the same manpage in different languages was found. return uniq(sort(map(pages, 's:format_candidate(v:val, a:psect)'), 'i')) endfunction @@ -387,7 +393,7 @@ endfunction function! man#goto_tag(pattern, flags, info) abort let [l:sect, l:name] = man#extract_sect_and_name_ref(a:pattern) - let l:paths = s:get_paths(l:sect, l:name) + let l:paths = s:get_paths(l:sect, l:name, v:true) let l:structured = [] for l:path in l:paths diff --git a/runtime/autoload/netrw.vim b/runtime/autoload/netrw.vim index fa86223d53..b69ad7187a 100644 --- a/runtime/autoload/netrw.vim +++ b/runtime/autoload/netrw.vim @@ -5460,6 +5460,11 @@ fun! netrw#CheckIfRemote(...) else let curfile= expand("%") endif + + " Ignore terminal buffers + if &buftype ==# 'terminal' + return 0 + endif " call Decho("curfile<".curfile.">") if curfile =~ '^\a\{3,}://' " call Dret("netrw#CheckIfRemote 1") diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim index c86f7d0c2f..a96a0a61b7 100644 --- a/runtime/autoload/provider/clipboard.vim +++ b/runtime/autoload/provider/clipboard.vim @@ -113,8 +113,13 @@ function! provider#clipboard#Executable() abort let s:paste['*'] = s:paste['+'] return 'doitclient' elseif executable('win32yank.exe') - let s:copy['+'] = 'win32yank.exe -i --crlf' - let s:paste['+'] = 'win32yank.exe -o --lf' + if has('wsl') && getftype(exepath('win32yank.exe')) == 'link' + let win32yank = resolve(exepath('win32yank.exe')) + else + let win32yank = 'win32yank.exe' + endif + let s:copy['+'] = win32yank.' -i --crlf' + let s:paste['+'] = win32yank.' -o --lf' let s:copy['*'] = s:copy['+'] let s:paste['*'] = s:paste['+'] return 'win32yank' diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 39b6c6417d..a2e0c56f85 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -2341,6 +2341,22 @@ nvim_ui_pum_set_height({height}) *nvim_ui_pum_set_height()* Parameters: ~ {height} Popupmenu height, must be greater than zero. + *nvim_ui_pum_set_bounds()* +nvim_ui_pum_set_bounds({width}, {height}, {row}, {col}) + + Tells Nvim the geometry of the popumenu, to align floating + windows with an external popup menu. Note that this method + is not to be confused with |nvim_ui_pum_set_height()|, which + sets the number of visible items in the popup menu, while + this function sets the bounding box of the popup menu, + including visual decorations such as boarders and sliders. + + Parameters: ~ + {width} Popupmenu width. + {height} Popupmenu height. + {row} Popupmenu row. + {height} Popupmenu height. + nvim_ui_set_option({name}, {value}) *nvim_ui_set_option()* TODO: Documentation diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 8006a45899..64ca7b6a45 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -358,7 +358,10 @@ Name triggered by ~ |MenuPopup| just before showing the popup menu |CompleteChanged| after popup menu changed, not fired on popup menu hide -|CompleteDone| after Insert mode completion is done +|CompleteDonePre| after Insert mode completion is done, before clearing + info +|CompleteDone| after Insert mode completion is done, after clearing + info |User| to be used in combination with ":doautocmd" |Signal| after Nvim receives a signal @@ -577,7 +580,8 @@ ColorSchemePre Before loading a color scheme. |:colorscheme| CompleteChanged *CompleteChanged* After each time the Insert mode completion menu changed. Not fired on popup menu hide, - use |CompleteDone| for that. + use |CompleteDonePre| or |CompleteDone| for + that. Sets these |v:event| keys: completed_item See |complete-items|. @@ -594,12 +598,22 @@ CompleteChanged *CompleteChanged* The size and position of the popup are also available by calling |pum_getpos()|. + *CompleteDonePre* +CompleteDonePre After Insert mode completion is done. Either + when something was completed or abandoning + completion. |ins-completion| + |complete_info()| can be used, the info is + cleared after triggering CompleteDonePre. + The |v:completed_item| variable contains + information about the completed item. + *CompleteDone* CompleteDone After Insert mode completion is done. Either when something was completed or abandoning completion. |ins-completion| - |complete_info()| can be used, the info is - cleared after triggering CompleteDone. + |complete_info()| cannot be used, the info is + cleared before triggering CompleteDone. Use + CompleteDonePre if you need it. |v:completed_item| gives the completed item. *CursorHold* diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 69b8b3cf39..6f13b34876 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2084,6 +2084,7 @@ ctxsize() Number return |context-stack| size cursor({lnum}, {col} [, {off}]) Number move cursor to {lnum}, {col}, {off} cursor({list}) Number move cursor to position in {list} +debugbreak({pid}) Number interrupt process being debugged deepcopy({expr} [, {noref}]) any make a full copy of {expr} delete({fname} [, {flags}]) Number delete the file or directory {fname} deletebufline({expr}, {first}[, {last}]) @@ -2109,6 +2110,7 @@ extend({expr1}, {expr2} [, {expr3}]) exp({expr}) Float exponential of {expr} expand({expr} [, {nosuf} [, {list}]]) any expand special keywords in {expr} +expandcmd({expr}) String expand {expr} like with `:edit` feedkeys({string} [, {mode}]) Number add key sequence to typeahead buffer filereadable({file}) Number |TRUE| if {file} is a readable file filewritable({file}) Number |TRUE| if {file} is a writable file @@ -2239,6 +2241,7 @@ libcallnr({lib}, {func}, {arg}) Number idem, but return a Number line({expr}) Number line nr of cursor, last line or mark line2byte({lnum}) Number byte count of line {lnum} lispindent({lnum}) Number Lisp indent for line {lnum} +list2str({list} [, {utf8}]) String turn numbers in {list} into a String localtime() Number current time log({expr}) Float natural logarithm (base e) of {expr} log10({expr}) Float logarithm of Float {expr} to base 10 @@ -2280,6 +2283,10 @@ pathshorten({expr}) String shorten directory names in a path pow({x}, {y}) Float {x} to the power of {y} prevnonblank({lnum}) Number line nr of non-blank line <= {lnum} printf({fmt}, {expr1}...) String format text +prompt_addtext({buf}, {expr}) none add text to a prompt buffer +prompt_setcallback({buf}, {expr}) none set prompt callback function +prompt_setinterrupt({buf}, {text}) none set prompt interrupt function +prompt_setprompt({buf}, {text}) none set prompt text pum_getpos() Dict position and size of pum if visible pumvisible() Number whether popup menu is visible pyeval({expr}) any evaluate |Python| expression @@ -2290,7 +2297,7 @@ range({expr} [, {max} [, {stride}]]) readdir({dir} [, {expr}]) List file names in {dir} selected by {expr} readfile({fname} [, {binary} [, {max}]]) List get list of lines from file {fname} -reg_executing() Number get the executing register name +reg_executing() String get the executing register name reg_recording() String get the recording register name reltime([{start} [, {end}]]) List get time value reltimefloat({time}) Float turn the time value into a Float @@ -2389,6 +2396,8 @@ sqrt({expr}) Float square root of {expr} stdioopen({dict}) Number open stdio in a headless instance. stdpath({what}) String/List returns the standard path(s) for {what} str2float({expr}) Float convert String to Float +str2list({expr} [, {utf8}]) List convert each character of {expr} to + ASCII/UTF8 value str2nr({expr} [, {base}]) Number convert String to Number strchars({expr} [, {skipcc}]) Number character length of the String {expr} strcharpart({str}, {start} [, {len}]) @@ -3637,6 +3646,11 @@ exp({expr}) *exp()* :echo exp(-1) < 0.367879 +debugbreak({pid}) *debugbreak()* + Specifically used to interrupt a program being debugged. It + will cause process {pid} to get a SIGTRAP. Behavior for other + processes is undefined. See |terminal-debugger|. + {Sends a SIGINT to a process {pid} other than MS-Windows} expand({expr} [, {nosuf} [, {list}]]) *expand()* Expand wildcards and the following special keywords in {expr}. @@ -3720,6 +3734,14 @@ expand({expr} [, {nosuf} [, {list}]]) *expand()* See |glob()| for finding existing files. See |system()| for getting the raw output of an external command. +expandcmd({expr}) *expandcmd()* + Expand special items in {expr} like what is done for an Ex + command such as `:edit`. This expands special keywords, like + with |expand()|, and environment variables, anywhere in + {expr}. Returns the expanded string. + Example: > + :echo expandcmd('make %<.o') +< extend({expr1}, {expr2} [, {expr3}]) *extend()* {expr1} and {expr2} must be both |Lists| or both |Dictionaries|. @@ -4142,6 +4164,9 @@ getbufinfo([{dict}]) changed TRUE if the buffer is modified. changedtick number of changes made to the buffer. hidden TRUE if the buffer is hidden. + lastused timestamp in seconds, like + |localtime()|, when the buffer was + last used. listed TRUE if the buffer is listed. lnum current line number in buffer. linecount number of lines in the buffer (only @@ -4541,7 +4566,7 @@ getline({lnum} [, {end}]) from the current buffer. Example: > getline(1) < When {lnum} is a String that doesn't start with a - digit, line() is called to translate the String into a Number. + digit, |line()| is called to translate the String into a Number. To get the line under the cursor: > getline(".") < When {lnum} is smaller than 1 or bigger than the number of @@ -5677,6 +5702,20 @@ lispindent({lnum}) *lispindent()* When {lnum} is invalid or Vim was not compiled the |+lispindent| feature, -1 is returned. +list2str({list} [, {utf8}]) *list2str()* + Convert each number in {list} to a character string can + concatenate them all. Examples: > + list2str([32]) returns " " + list2str([65, 66, 67]) returns "ABC" +< The same can be done (slowly) with: > + join(map(list, {nr, val -> nr2char(val)}), '') +< |str2list()| does the opposite. + + When {utf8} is omitted or zero, the current 'encoding' is used. + With {utf8} is 1, always return utf-8 characters. + With utf-8 composing characters work as expected: > + list2str([97, 769]) returns "aÌ" +< localtime() *localtime()* Return the current time, measured as seconds since 1st Jan 1970. See also |strftime()| and |getftime()|. @@ -6541,6 +6580,50 @@ printf({fmt}, {expr1} ...) *printf()* of "%" items. If there are not sufficient or too many arguments an error is given. Up to 18 arguments can be used. +prompt_setcallback({buf}, {expr}) *prompt_setcallback()* + Set prompt callback for buffer {buf} to {expr}. When {expr} + is an empty string the callback is removed. This has only + effect if {buf} has 'buftype' set to "prompt". + + The callback is invoked when pressing Enter. The current + buffer will always be the prompt buffer. A new line for a + prompt is added before invoking the callback, thus the prompt + for which the callback was invoked will be in the last but one + line. + If the callback wants to add text to the buffer, it must + insert it above the last line, since that is where the current + prompt is. This can also be done asynchronously. + The callback is invoked with one argument, which is the text + that was entered at the prompt. This can be an empty string + if the user only typed Enter. + Example: > + call prompt_setcallback(bufnr(''), function('s:TextEntered')) + func s:TextEntered(text) + if a:text == 'exit' || a:text == 'quit' + stopinsert + close + else + call append(line('$') - 1, 'Entered: "' . a:text . '"') + " Reset 'modified' to allow the buffer to be closed. + set nomodified + endif + endfunc + +prompt_setinterrupt({buf}, {expr}) *prompt_setinterrupt()* + Set a callback for buffer {buf} to {expr}. When {expr} is an + empty string the callback is removed. This has only effect if + {buf} has 'buftype' set to "prompt". + + This callback will be invoked when pressing CTRL-C in Insert + mode. Without setting a callback Vim will exit Insert mode, + as in any buffer. + +prompt_setprompt({buf}, {text}) *prompt_setprompt()* + Set prompt for buffer {buf} to {text}. You most likely want + {text} to end in a space. + The result is only visible if {buf} has 'buftype' set to + "prompt". Example: > + call prompt_setprompt(bufnr(''), 'command: ') pum_getpos() *pum_getpos()* If the popup menu (see |ins-completion-menu|) is not visible, @@ -6606,6 +6689,33 @@ range({expr} [, {max} [, {stride}]]) *range()* range(0) " [] range(2, 0) " error! < + *readdir()* +readdir({directory} [, {expr}]) + Return a list with file and directory names in {directory}. + + When {expr} is omitted all entries are included. + When {expr} is given, it is evaluated to check what to do: + If {expr} results in -1 then no further entries will + be handled. + If {expr} results in 0 then this entry will not be + added to the list. + If {expr} results in 1 then this entry will be added + to the list. + Each time {expr} is evaluated |v:val| is set to the entry name. + When {expr} is a function the name is passed as the argument. + For example, to get a list of files ending in ".txt": > + readdir(dirname, {n -> n =~ '.txt$'}) +< To skip hidden and backup files: > + readdir(dirname, {n -> n !~ '^\.\|\~$'}) + +< If you want to get a directory tree: > + function! s:tree(dir) + return {a:dir : map(readdir(a:dir), + \ {_, x -> isdirectory(x) ? + \ {x : s:tree(a:dir . '/' . x)} : x})} + endfunction + echo s:tree(".") +< *readfile()* readfile({fname} [, {binary} [, {max}]]) Read file {fname} and return a |List|, each line of the file @@ -6637,17 +6747,6 @@ readfile({fname} [, {binary} [, {max}]]) the result is an empty list. Also see |writefile()|. - *readdir()* -readdir({directory} [, {expr}]) - Return a list with file and directory names in {directory}. - You can also use |glob()| if you don't need to do complicated - things, such as limiting the number of matches. - - When {expr} is omitted all entries are included. - When {expr} is given, it is evaluated to check what to do: - If {expr} results in -1 then no further entries will - be handled. - reg_executing() *reg_executing()* Returns the single letter name of the register being executed. Returns an empty string when no register is being executed. @@ -7550,11 +7649,21 @@ settagstack({nr}, {dict} [, {action}]) *settagstack()* {nr} can be the window number or the |window-ID|. For a list of supported items in {dict}, refer to - |gettagstack()| + |gettagstack()|. "curidx" takes effect before changing the tag + stack. *E962* - If {action} is not present or is set to 'r', then the tag - stack is replaced. If {action} is set to 'a', then new entries - from {dict} are pushed onto the tag stack. + How the tag stack is modified depends on the {action} + argument: + - If {action} is not present or is set to 'r', then the tag + stack is replaced. + - If {action} is set to 'a', then new entries from {dict} are + pushed (added) onto the tag stack. + - If {action} is set to 't', then all the entries from the + current entry in the tag stack or "curidx" in {dict} are + removed and then new entries are pushed to the stack. + + The current index is set to one after the length of the tag + stack after the modification. Returns zero for success, -1 for failure. @@ -7728,7 +7837,7 @@ sign_getplaced([{expr} [, {dict}]]) *sign_getplaced()* priority sign priority The returned signs in a buffer are ordered by their line - number. + number and priority. Returns an empty list on failure or if there are no placed signs. @@ -8133,6 +8242,18 @@ str2float({expr}) *str2float()* |substitute()|: > let f = str2float(substitute(text, ',', '', 'g')) +str2list({expr} [, {utf8}]) *str2list()* + Return a list containing the number values which represent + each character in String {expr}. Examples: > + str2list(" ") returns [32] + str2list("ABC") returns [65, 66, 67] +< |list2str()| does the opposite. + + When {utf8} is omitted or zero, the current 'encoding' is used. + With {utf8} set to 1, always treat the String as utf-8 + characters. With utf-8 composing characters are handled + properly: > + str2list("aÌ") returns [97, 769] str2nr({expr} [, {base}]) *str2nr()* Convert string {expr} to a number. @@ -10277,8 +10398,8 @@ text... The parsing works slightly different from |:echo|, more like |:execute|. All the expressions are first evaluated and concatenated before echoing anything. - The expressions must evaluate to a Number or String, a - Dictionary or List causes an error. + If expressions does not evaluate to a Number or + String, string() is used to turn it into a string. Uses the highlighting set by the |:echohl| command. Example: > :echomsg "It's a Zizzer Zazzer Zuzz, as you can plainly see." @@ -10289,7 +10410,7 @@ text... message in the |message-history|. When used in a script or function the line number will be added. Spaces are placed between the arguments as with the - :echo command. When used inside a try conditional, + |:echomsg| command. When used inside a try conditional, the message is raised as an error exception instead (see |try-echoerr|). Example: > diff --git a/runtime/doc/filetype.txt b/runtime/doc/filetype.txt index c649688d99..1fce37c594 100644 --- a/runtime/doc/filetype.txt +++ b/runtime/doc/filetype.txt @@ -275,7 +275,7 @@ Note that the last one is the value of $VIMRUNTIME which has been expanded. Note that when using a plugin manager or |packages| many directories will be added to 'runtimepath'. These plugins each require their own directory, don't -put them directly in ~/.vim/plugin. +put them directly in ~/.config/nvim/plugin. What if it looks like your plugin is not being loaded? You can find out what happens when Vim starts up by using the |-V| argument: > diff --git a/runtime/doc/fold.txt b/runtime/doc/fold.txt index f2f6c70b0c..8e2cb2f728 100644 --- a/runtime/doc/fold.txt +++ b/runtime/doc/fold.txt @@ -527,8 +527,7 @@ FOLDCOLUMN *fold-foldcolumn* 'foldcolumn' is a number, which sets the width for a column on the side of the window to indicate folds. When it is zero, there is no foldcolumn. A normal -value is 4 or 5. The minimal useful value is 2, although 1 still provides -some information. The maximum is 12. +value is auto:9. The maximum is 9. An open fold is indicated with a column that has a '-' at the top and '|' characters below it. This column stops where the open fold stops. When folds diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index fce4d8628e..e53af5074b 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -1083,7 +1083,8 @@ items: empty when non-zero this match will be added even when it is an empty string user_data custom data which is associated with the item and - available in |v:completed_item| + available in |v:completed_item|; it can be any type; + defaults to an empty string All of these except "icase", "equal", "dup" and "empty" must be a string. If an item does not meet these requirements then an error message is given and diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 016a8be7e6..249136f32f 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -48,6 +48,8 @@ go-to-definition, hover, etc. Example config: > nnoremap <silent> <c-k> <cmd>lua vim.lsp.buf.signature_help()<CR> nnoremap <silent> 1gD <cmd>lua vim.lsp.buf.type_definition()<CR> nnoremap <silent> gr <cmd>lua vim.lsp.buf.references()<CR> + nnoremap <silent> g0 <cmd>lua vim.lsp.buf.document_symbol()<CR> + nnoremap <silent> gW <cmd>lua vim.lsp.buf.workspace_symbol()<CR> Nvim provides the |vim.lsp.omnifunc| 'omnifunc' handler which allows |i_CTRL-X_CTRL-O| to consume LSP completion. Example config (note the use of @@ -63,7 +65,7 @@ FAQ *lsp-faq* - Q: How to force-reload LSP? A: Stop all clients, then reload the buffer. > - :lua vim.lsp.stop_all_clients() + :lua vim.lsp.stop_client(vim.lsp.get_active_clients()) :edit - Q: Why isn't completion working? @@ -77,21 +79,6 @@ FAQ *lsp-faq* "after/ftplugin/python.vim". ================================================================================ -LSP HIGHLIGHT *lsp-highlight* - -When LSP is activated these highlight groups are defined: - - LspDiagnosticsError - LspDiagnosticsHint - LspDiagnosticsInformation - LspDiagnosticsUnderline - LspDiagnosticsUnderlineError - LspDiagnosticsUnderlineHint - LspDiagnosticsUnderlineInformation - LspDiagnosticsUnderlineWarning - LspDiagnosticsWarning - -================================================================================ LSP API *lsp-api* The `vim.lsp` Lua module is a framework for building LSP plugins. @@ -174,6 +161,33 @@ name: > vim.lsp.protocol.TextDocumentSyncKind[1] == "Full" ================================================================================ +LSP HIGHLIGHT *lsp-highlight* + + *hl-LspDiagnosticsError* +LspDiagnosticsError used for "Error" diagnostic virtual text + *hl-LspDiagnosticsErrorSign* +LspDiagnosticsErrorSign used for "Error" diagnostic signs in sign column + *hl-LspDiagnosticsWarning* +LspDiagnosticsWarning used for "Warning" diagnostic virtual text + *hl-LspDiagnosticsWarningSign* +LspDiagnosticsWarningSign used for "Warning" diagnostic signs in sign column + *hl-LspDiagnosticsInformation* +LspDiagnosticInformation used for "Information" diagnostic virtual text + *hl-LspDiagnosticsInformationSign* +LspDiagnosticInformationSign used for "Information" signs in sign column + *hl-LspDiagnosticsHint* +LspDiagnosticHint used for "Hint" diagnostic virtual text + *hl-LspDiagnosticsHintSign* +LspDiagnosticHintSign used for "Hint" diagnostic signs in sign column + *hl-LspReferenceText* +LspReferenceText used for highlighting "text" references + *hl-LspReferenceRead* +LspReferenceRead used for highlighting "read" references + *hl-LspReferenceWrite* +LspReferenceWrite used for highlighting "write" references + + +================================================================================ LSP EXAMPLE *lsp-extension-example* This example is for plugin authors or users who want a lot of control. If you @@ -290,6 +304,12 @@ The example will: < +============================================================================== +AUTOCOMMANDS *lsp-autocommands* + + *LspDiagnosticsChanged* +LspDiagnosticsChanged After receiving publishDiagnostics server response + ============================================================================== Lua module: vim.lsp *lsp-core* @@ -333,7 +353,7 @@ buf_notify({bufnr}, {method}, {params}) *vim.lsp.buf_notify()* {params} (string) Parameters to send to the server Return: ~ - nil + true if any client returns true; false otherwise *vim.lsp.buf_request()* buf_request({bufnr}, {method}, {params}, {callback}) @@ -543,7 +563,7 @@ start_client({config}) *vim.lsp.start_client()* {root_dir} (required, string) Directory where the LSP server will base its rootUri on initialization. - {cmd} (required, string or list treated like + {cmd} (required, list treated like |jobstart()|) Base command that initiates the LSP client. {cmd_cwd} (string, default=|getcwd()|) Directory @@ -720,6 +740,12 @@ declaration() *vim.lsp.buf.declaration()* definition() *vim.lsp.buf.definition()* TODO: Documentation +document_highlight() *vim.lsp.buf.document_highlight()* + TODO: Documentation + +document_symbol() *vim.lsp.buf.document_symbol()* + TODO: Documentation + formatting({options}) *vim.lsp.buf.formatting()* TODO: Documentation @@ -735,9 +761,6 @@ npcall({fn}, {...}) *vim.lsp.buf.npcall()* ok_or_nil({status}, {...}) *vim.lsp.buf.ok_or_nil()* TODO: Documentation -peek_definition() *vim.lsp.buf.peek_definition()* - TODO: Documentation - *vim.lsp.buf.range_formatting()* range_formatting({options}, {start_pos}, {end_pos}) TODO: Documentation @@ -751,12 +774,23 @@ rename({new_name}) *vim.lsp.buf.rename()* request({method}, {params}, {callback}) *vim.lsp.buf.request()* TODO: Documentation +server_ready() *vim.lsp.buf.server_ready()* + Sends a notification through all clients associated with current + buffer and returns `true` if server responds. + signature_help() *vim.lsp.buf.signature_help()* TODO: Documentation type_definition() *vim.lsp.buf.type_definition()* TODO: Documentation +workspace_symbol({query}) *vim.lsp.buf.workspace_symbol()* + Lists all symbols in the current workspace in the quickfix + window. The list is filtered against the optional argument + {query}; if the argument is omitted from the call, the user + is prompted to enter a string on the command line. An empty + string means no filtering is done. + ============================================================================== Lua module: vim.lsp.callbacks *lsp-callbacks* @@ -894,13 +928,60 @@ apply_text_edits({text_edits}, {bufnr}) apply_workspace_edit({workspace_edit}) TODO: Documentation + *vim.lsp.util.diagnostics_by_buf* +diagnostics_by_buf + A table containing diagnostics grouped by buf. + + {<bufnr>: {diagnostics}} + + {diagnostics} is an array of diagnostics. + + By default this is populated by the + `textDocument/publishDiagnostics` callback via + |vim.lsp.util.buf_diagnostics_save_positions|. + + It contains entries for active buffers. Once a buffer is + detached the entries for it are discarded. + buf_clear_diagnostics({bufnr}) *vim.lsp.util.buf_clear_diagnostics()* TODO: Documentation + + *vim.lsp.util.buf_diagnostics_count()* +buf_diagnostics_count({kind}) + Returns the number of diagnostics of given kind for current buffer. + Useful for showing diagnostics counts in statusline. eg: - *vim.lsp.util.buf_diagnostics_save_positions()* -buf_diagnostics_save_positions({bufnr}, {diagnostics}) +> + function! LspStatus() abort + let sl = '' + if luaeval('vim.lsp.buf.server_ready()') + let sl.='%#MyStatuslineLSP#E:' + let sl.='%#MyStatuslineLSPErrors#%{luaeval("vim.lsp.util.buf_diagnostics_count(\"Error\")")}' + let sl.='%#MyStatuslineLSP# W:' + let sl.='%#MyStatuslineLSPWarnings#%{luaeval("vim.lsp.util.buf_diagnostics_count(\"Warning\")")}' + else + let sl.='%#MyStatuslineLSPErrors#off' + endif + return sl + endfunction + let &l:statusline = '%#MyStatuslineLSP#LSP '.LspStatus() +< + + Parameters: ~ + {kind} Diagnostic severity kind: Error, Warning, Information or Hint. + +buf_clear_references({bufnr}) *vim.lsp.util.buf_clear_references()* TODO: Documentation + *vim.lsp.util.buf_diagnostics_save()* +buf_diagnostics_save_positions({bufnr}, {diagnostics}) + Stores the diagnostics into |vim.lsp.util.diagnostics_by_buf| + + Parameters: ~ + {bufr} bufnr for which the diagnostics are for. + {diagnostics} Diagnostics[] received from the + langauge server. + *vim.lsp.util.buf_diagnostics_underline()* buf_diagnostics_underline({bufnr}, {diagnostics}) TODO: Documentation @@ -909,6 +990,18 @@ buf_diagnostics_underline({bufnr}, {diagnostics}) buf_diagnostics_virtual_text({bufnr}, {diagnostics}) TODO: Documentation + *vim.lsp.util.buf_diagnostics_signs()* +buf_diagnostics_signs({bufnr}, {diagnostics}) + Place signs for each diagnostic in the sign column. + Sign characters can be customized with the following commands: +> +sign define LspDiagnosticsErrorSign text=E texthl=LspDiagnosticsError linehl= numhl= +sign define LspDiagnosticsWarningSign text=W texthl=LspDiagnosticsWarning linehl= numhl= +sign define LspDiagnosticsInformationSign text=I texthl=LspDiagnosticsInformation linehl= numhl= +sign define LspDiagnosticsHintSign text=H texthl=LspDiagnosticsHint linehl= numhl= +< + + character_offset({buf}, {row}, {col}) *vim.lsp.util.character_offset()* TODO: Documentation @@ -973,10 +1066,6 @@ npcall({fn}, {...}) *vim.lsp.util.npcall()* ok_or_nil({status}, {...}) *vim.lsp.util.ok_or_nil()* TODO: Documentation - *vim.lsp.util.open_floating_peek_preview()* -open_floating_peek_preview({bufnr}, {start}, {finish}, {opts}) - TODO: Documentation - *vim.lsp.util.open_floating_preview()* open_floating_preview({contents}, {filetype}, {opts}) TODO: Documentation diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index af1f4a8c1f..7f376cbbf0 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -494,10 +494,12 @@ VIM.TREESITTER *lua-treesitter* Nvim integrates the tree-sitter library for incremental parsing of buffers. Currently Nvim does not provide the tree-sitter parsers, instead these must -be built separately, for instance using the tree-sitter utility. -The parser is loaded into nvim using > +be built separately, for instance using the tree-sitter utility. The only +exception is a C parser being included in official builds for testing +purposes. Parsers are searched for as `parser/{lang}.*` in any 'runtimepath' +directory. A parser can also be loaded manually using a full path: > - vim.treesitter.add_language("/path/to/c_parser.so", "c") + vim.treesitter.require_language("python", "/path/to/python.so") <Create a parser for a buffer and a given language (if another plugin uses the same buffer/language combination, it will be safely reused). Use > @@ -691,6 +693,35 @@ identical identifiers, highlighting both as |hl-WarningMsg|: > (eq? @WarningMsg.left @WarningMsg.right)) ------------------------------------------------------------------------------ +VIM.REGEX *lua-regex* + +Vim regexes can be used directly from lua. Currently they only allow +matching within a single line. + +vim.regex({re}) *vim.regex()* + + Parse the regex {re} and return a regex object. 'magic' and + 'ignorecase' options are ignored, lua regexes always defaults to magic + and ignoring case. The behavior can be changed with flags in + the beginning of the string |/magic|. + +Regex objects support the following methods: + +regex:match_str({str}) *regex:match_str()* + Match the string against the regex. If the string should match the + regex precisely, surround the regex with `^` and `$`. + If the was a match, the byte indices for the beginning and end of + the match is returned. When there is no match, `nil` is returned. + As any integer is truth-y, `regex:match()` can be directly used + as a condition in an if-statement. + +regex:match_line({bufnr}, {line_idx}[, {start}, {end}]) *regex:match_line()* + Match line {line_idx} (zero-based) in buffer {bufnr}. If {start} and + {end} are supplied, match only this byte index range. Otherwise see + |regex:match_str()|. If {start} is used, then the returned byte + indices will be relative {start}. + +------------------------------------------------------------------------------ VIM *lua-builtin* vim.api.{func}({...}) *vim.api* @@ -877,7 +908,10 @@ deep_equal({a}, {b}) *vim.deep_equal()* deepcopy({orig}) *vim.deepcopy()* Returns a deep copy of the given object. Non-table objects are copied as in a typical Lua assignment, whereas table objects - are copied recursively. + are copied recursively. Functions are naively copied, so + functions in the copied table point to the same functions as + those in the input table. Userdata and threads are not copied + and will throw an error. Parameters: ~ {orig} Table to copy diff --git a/runtime/doc/mlang.txt b/runtime/doc/mlang.txt index 03c48b962d..2a10a7051d 100644 --- a/runtime/doc/mlang.txt +++ b/runtime/doc/mlang.txt @@ -185,8 +185,8 @@ you can do it without restarting Vim: > :source $VIMRUNTIME/menu.vim Each part of a menu path is translated separately. The result is that when -"Help" is translated to "Hilfe" and "Overview" to "Überblick" then -"Help.Overview" will be translated to "Hilfe.Überblick". +"Help" is translated to "Hilfe" and "Overview" to "Überblick" then +"Help.Overview" will be translated to "Hilfe.Überblick". ============================================================================== 3. Scripts *multilang-scripts* diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt index 1a5e6421e7..a96d118667 100644 --- a/runtime/doc/nvim_terminal_emulator.txt +++ b/runtime/doc/nvim_terminal_emulator.txt @@ -50,6 +50,13 @@ mode" in a normal buffer, such as |i| or |:startinsert|. In this mode all keys except <C-\><C-N> are sent to the underlying program. Use <C-\><C-N> to return to normal-mode. |CTRL-\_CTRL-N| +Terminal-mode forces these local options: + + 'nocursorline' + 'nocursorcolumn' + 'scrolloff' = 0 + 'sidescrolloff' = 0 + Terminal-mode has its own |:tnoremap| namespace for mappings, this can be used to automate any terminal interaction. @@ -307,6 +314,23 @@ Other commands ~ isn't one +Prompt mode ~ + *termdebug-prompt* +When on MS-Windows, gdb will run in a buffer with 'buftype' set to "prompt". +This works slightly differently: +- The gdb window will be in Insert mode while typing commands. Go to Normal + mode with <Esc>, then you can move around in the buffer, copy/paste, etc. + Go back to editing the gdb command with any command that starts Insert mode, + such as `a` or `i`. +- The program being debugged will run in a separate window. On MS-Windows + this is a new console window. On Unix, if the |+terminal| feature is + available a Terminal window will be opened to run the debugged program in. + + *termdebug_use_prompt* +Prompt mode can be used even when the |+terminal| feature is present with: > + let g:termdebug_use_prompt = 1 + + Communication ~ *termdebug-communication* There is another, hidden, buffer, which is used for Vim to communicate with diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 70af23ee29..8d4f76d3dd 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1093,6 +1093,8 @@ A jump table for the options with a short description can be found at |Q_op|. nowrite buffer will not be written quickfix list of errors |:cwindow| or locations |:lwindow| terminal |terminal-emulator| buffer + prompt buffer where only the last line can be edited, meant + to be used by a plugin, see |prompt-buffer| This option is used together with 'bufhidden' and 'swapfile' to specify special kinds of buffers. See |special-buffers|. @@ -2419,11 +2421,14 @@ A jump table for the options with a short description can be found at |Q_op|. automatically close when moving out of them. *'foldcolumn'* *'fdc'* -'foldcolumn' 'fdc' number (default 0) +'foldcolumn' 'fdc' string (default "0") local to window - When non-zero, a column with the specified width is shown at the side - of the window which indicates open and closed folds. The maximum - value is 12. + When and how to draw the foldcolumn. Valid values are: + "auto": resize to the maximum amount of folds to display. + "auto:[1-9]": resize to accommodate multiple folds up to the + selected level + 0: to disable foldcolumn + "[1-9]": to display a fixed number of columns See |folding|. *'foldenable'* *'fen'* *'nofoldenable'* *'nofen'* @@ -4895,13 +4900,17 @@ A jump table for the options with a short description can be found at |Q_op|. *'scrolloff'* *'so'* 'scrolloff' 'so' number (default 0) - global + global or local to window |global-local| Minimal number of screen lines to keep above and below the cursor. This will make some context visible around where you are working. If you set it to a very large value (999) the cursor line will always be in the middle of the window (except at the start or end of the file or when long lines wrap). - For scrolling horizontally see 'sidescrolloff'. + After using the local value, go back the global value with one of + these two: > + setlocal scrolloff< + setlocal scrolloff=-1 +< For scrolling horizontally see 'sidescrolloff'. *'scrollopt'* *'sbo'* 'scrollopt' 'sbo' string (default "ver,jump") @@ -5510,7 +5519,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'sidescrolloff'* *'siso'* 'sidescrolloff' 'siso' number (default 0) - global + global or local to window |global-local| The minimal number of screen columns to keep to the left and to the right of the cursor if 'nowrap' is set. Setting this option to a value greater than 0 while having |'sidescroll'| also at a non-zero @@ -5519,7 +5528,11 @@ A jump table for the options with a short description can be found at |Q_op|. to a large value (like 999) has the effect of keeping the cursor horizontally centered in the window, as long as one does not come too close to the beginning of the line. - + After using the local value, go back the global value with one of + these two: > + setlocal sidescrolloff< + setlocal sidescrolloff=-1 +< Example: Try this together with 'sidescroll' and 'listchars' as in the following example to never allow the cursor to move onto the "extends" character: > @@ -6503,9 +6516,11 @@ A jump table for the options with a short description can be found at |Q_op|. >= 1 When the shada file is read or written. >= 2 When a file is ":source"'ed. >= 3 UI info, terminal capabilities + >= 4 Shell commands. >= 5 Every searched tags file and include file. >= 8 Files for which a group of autocommands is executed. >= 9 Every executed autocommand. + >= 11 Finding items in a path >= 12 Every executed function. >= 13 When an exception is thrown, caught, finished, or discarded. >= 14 Anything pending in a ":finally" clause. @@ -6688,6 +6703,10 @@ A jump table for the options with a short description can be found at |Q_op|. While the menu is active these keys have special meanings: + CTRL-Y - accept the currently selected match and stop + completion. + CTRL-E - end completion, go back to what was there before + selecting a match. <Left> <Right> - select previous/next match (like CTRL-P/CTRL-N) <Down> - in filename/menu name completion: move into a subdirectory or submenu. @@ -6725,6 +6744,8 @@ A jump table for the options with a short description can be found at |Q_op|. complete first match. "list:longest" When more than one match, list all matches and complete till longest common string. + "list:lastused" When more than one buffer matches, sort buffers + by time last used (other than the current buffer). When there is only a single match, it is fully completed in all cases. Examples: > diff --git a/runtime/doc/sign.txt b/runtime/doc/sign.txt index 4e0d91dae0..e3ba4ba181 100644 --- a/runtime/doc/sign.txt +++ b/runtime/doc/sign.txt @@ -176,9 +176,9 @@ See |sign_place()| for the equivalent Vim script function. By default, the sign is assigned a default priority of 10. To assign a different priority value, use "priority={prio}" to - specify a value. The priority is used to determine the - highlight group used when multiple signs are placed on the - same line. + specify a value. The priority is used to determine the sign + that is displayed when multiple signs are placed on the same + line. Examples: > :sign place 5 line=3 name=sign1 file=a.py @@ -198,7 +198,9 @@ See |sign_place()| for the equivalent Vim script function. it (e.g., when the debugger has stopped at a breakpoint). The optional "group={group}" attribute can be used before - "file=" to select a sign in a particular group. + "file=" to select a sign in a particular group. The optional + "priority={prio}" attribute can be used to change the priority + of an existing sign. :sign place {id} name={name} [buffer={nr}] Same, but use buffer {nr}. If the buffer argument is not diff --git a/runtime/doc/spell.txt b/runtime/doc/spell.txt index 110c6ef221..b88e26cdff 100644 --- a/runtime/doc/spell.txt +++ b/runtime/doc/spell.txt @@ -504,7 +504,7 @@ then Vim will try to guess. Multiple {inname} arguments can be given to combine regions into one Vim spell file. Example: > - :mkspell ~/.vim/spell/en /tmp/en_US /tmp/en_CA /tmp/en_AU + :mkspell ~/.config/nvim/spell/en /tmp/en_US /tmp/en_CA /tmp/en_AU < This combines the English word lists for US, CA and AU into one en.spl file. Up to eight regions can be combined. *E754* *E755* @@ -888,9 +888,9 @@ when using "cp1250" on Unix. *spell-LOW* *spell-UPP* Three lines in the affix file are needed. Simplistic example: - FOL áëñ ~ - LOW áëñ ~ - UPP ÁËÑ ~ + FOL áëñ ~ + LOW áëñ ~ + UPP ÃËÑ ~ All three lines must have exactly the same number of characters. @@ -905,9 +905,9 @@ The "UPP" line specifies the characters with upper-case. That is, a character is upper-case where it's different from the character at the same position in "FOL". -An exception is made for the German sharp s ß. The upper-case version is +An exception is made for the German sharp s ß. The upper-case version is "SS". In the FOL/LOW/UPP lines it should be included, so that it's recognized -as a word character, but use the ß character in all three. +as a word character, but use the ß character in all three. ASCII characters should be omitted, Vim always handles these in the same way. When the encoding is UTF-8 no word characters need to be specified. @@ -1377,7 +1377,7 @@ suggestions would spend most time trying all kind of weird compound words. *spell-SYLLABLE* The SYLLABLE item defines characters or character sequences that are used to count the number of syllables in a word. Example: - SYLLABLE aáeéiíoóöõuúüûy/aa/au/ea/ee/ei/ie/oa/oe/oo/ou/uu/ui ~ + SYLLABLE aáeéiÃoóöõuúüûy/aa/au/ea/ee/ei/ie/oa/oe/oo/ou/uu/ui ~ Before the first slash is the set of characters that are counted for one syllable, also when repeated and mixed, until the next character that is not @@ -1458,8 +1458,8 @@ alike. This is mostly used for a letter with different accents. This is used to prefer suggestions with these letters substituted. Example: MAP 2 ~ - MAP eéëêè ~ - MAP uüùúû ~ + MAP eéëêè ~ + MAP uüùúû ~ The first line specifies the number of MAP lines following. Vim ignores the number, but the line must be there. diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index e3f0d593a7..af7d233619 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -184,12 +184,17 @@ argument. the 'modifiable' and 'write' options can be set to enable changes and writing. - *-Z* *restricted-mode* *E145* + *-Z* *restricted-mode* *E145* *E981* -Z Restricted mode. All commands that make use of an external shell are disabled. This includes suspending with CTRL-Z, - ":sh", filtering, the system() function, backtick expansion, - delete(), rename(), mkdir(), writefile(), libcall(), - jobstart(), etc. + ":sh", filtering, the system() function, backtick expansion + and libcall(). + Also disallowed are delete(), rename(), mkdir(), jobstart(), + etc. + Interfaces, such as Python, Ruby and Lua, are also disabled, + since they could be used to execute shell commands. + Note that the user may still find a loophole to execute a + shell command, it has only been made difficult. -e *-e* *-E* -E Start Nvim in Ex mode |gQ|. diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 30ccb699cd..57337aeac2 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -4647,8 +4647,8 @@ in their own color. ":colorscheme" in a color scheme script. To customize a colorscheme use another name, e.g. - "~/.vim/colors/mine.vim", and use `:runtime` to load - the original colorscheme: > + "~/.config/nvim/colors/mine.vim", and use `:runtime` to + load the original colorscheme: > runtime colors/evening.vim hi Statement ctermfg=Blue guifg=Blue diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index de54ce59b6..2817c1015b 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -161,7 +161,9 @@ the editor. `cursor_shape`: "block", "horizontal", "vertical" `cell_percentage`: Cell % occupied by the cursor. `blinkwait`, `blinkon`, `blinkoff`: See |cursor-blinking|. - `attr_id`: Cursor attribute id (defined by `hl_attr_define`) + `attr_id`: Cursor attribute id (defined by `hl_attr_define`). + When attr_id is 0, the background and foreground + colors should be swapped. `attr_id_lm`: Cursor attribute id for when 'langmap' is active. `short_name`: Mode code name, see 'guicursor'. `name`: Mode descriptive name. @@ -592,6 +594,12 @@ tabs. When |ext_messages| is active, no message grid is used, and this event will not be sent. +["win_viewport", grid, win, topline, botline, curline, curcol] + Indicates the range of buffer text displayed in the window, as well + as the cursor position in the buffer. All positions are zero-based. + `botline` is set to one more than the line count of the buffer, if + there are filler lines past the end. + ============================================================================== Popupmenu Events *ui-popupmenu* diff --git a/runtime/doc/usr_21.txt b/runtime/doc/usr_21.txt index 99a78e7b05..44d653a1a7 100644 --- a/runtime/doc/usr_21.txt +++ b/runtime/doc/usr_21.txt @@ -339,21 +339,6 @@ window, move the cursor to the filename and press "O". Double clicking with the mouse will also do this. -UNIX AND MS-WINDOWS - -Some people have to do work on MS-Windows systems one day and on Unix another -day. If you are one of them, consider adding "slash" and "unix" to -'sessionoptions'. The session files will then be written in a format that can -be used on both systems. This is the command to put in your |init.vim| file: -> - :set sessionoptions+=unix,slash - -Vim will use the Unix format then, because the MS-Windows Vim can read and -write Unix files, but Unix Vim can't read MS-Windows format session files. -Similarly, MS-Windows Vim understands file names with / to separate names, but -Unix Vim doesn't understand \. - - SESSIONS AND SHADA Sessions store many things, but not the position of marks, contents of diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index c770950a96..234f7801ab 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -578,8 +578,10 @@ used for. You can find an alphabetical list here: |functions|. Use CTRL-] on the function name to jump to detailed help on it. String manipulation: *string-functions* - nr2char() get a character by its ASCII value - char2nr() get ASCII value of a character + nr2char() get a character by its number value + list2str() get a character string from a list of numbers + char2nr() get number value of a character + str2list() get list of numbers from a string str2nr() convert a string to a Number str2float() convert a string to a Float printf() format a string according to % items @@ -607,6 +609,7 @@ String manipulation: *string-functions* strcharpart() get part of a string using char index strgetchar() get character from a string using char index expand() expand special keywords + expandcmd() expand a command like done for `:edit` iconv() convert text from one encoding to another byteidx() byte index of a character in a string byteidxcomp() like byteidx() but count composing characters diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 5835c7f314..376375e4ef 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -201,6 +201,7 @@ Options: 'guicursor' works in the terminal 'fillchars' flags: "msgsep" (see 'display'), "eob" for |hl-EndOfBuffer| marker, "foldopen", "foldsep", "foldclose" + 'foldcolumn' supports up to 9 dynamic/fixed columns 'inccommand' shows interactive results for |:substitute|-like commands 'listchars' local to window 'pumblend' pseudo-transparent popupmenu diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt index 977e0daef7..b5623f4ea4 100644 --- a/runtime/doc/windows.txt +++ b/runtime/doc/windows.txt @@ -1021,6 +1021,9 @@ list of buffers. |unlisted-buffer| x buffers with a read error % current buffer # alternate buffer + R terminal buffers with a running job + F terminal buffers with a finished job + t show time last used and sort buffers Combining flags means they are "and"ed together, e.g.: h+ hidden buffers which are modified a+ active buffers which are modified diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 656ee36484..0b5003dc44 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -1,7 +1,7 @@ " Vim support file to detect file types " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2019 Nov 26 +" Last Change: 2020 Apr 29 " Listen very carefully, I will say this only once if exists("did_load_filetypes") @@ -229,11 +229,14 @@ au BufNewFile,BufRead *.bl setf blank " Blkid cache file au BufNewFile,BufRead */etc/blkid.tab,*/etc/blkid.tab.old setf xml +" BSDL +au BufNewFile,BufRead *bsd,*.bsdl setf bsdl + " Bazel (http://bazel.io) autocmd BufRead,BufNewFile *.bzl,WORKSPACE,BUILD.bazel setf bzl if has("fname_case") " There is another check for BUILD further below. - autocmd BufRead,BufNewFile BUILD setf bzl + autocmd BufRead,BufNewFile BUILD setf bzl endif " C or lpc @@ -487,7 +490,7 @@ au BufNewFile,BufRead *.rul au BufNewFile,BufRead *.com call dist#ft#BindzoneCheck('dcl') " DOT -au BufNewFile,BufRead *.dot setf dot +au BufNewFile,BufRead *.dot,*.gv setf dot " Dylan - lid files au BufNewFile,BufRead *.lid setf dylanlid @@ -829,6 +832,9 @@ au BufNewFile,BufRead *.k setf kwt " Kivy au BufNewFile,BufRead *.kv setf kivy +" Kotlin +au BufNewFile,BufRead *.kt,*.ktm,*.kts setf kotlin + " KDE script au BufNewFile,BufRead *.ks setf kscript @@ -1123,6 +1129,9 @@ au BufNewFile,BufRead pf.conf setf pf " Pam conf au BufNewFile,BufRead */etc/pam.conf setf pamconf +" Pam environment +au BufNewFile,BufRead pam_env.conf,.pam_environment setf pamenv + " PApp au BufNewFile,BufRead *.papp,*.pxml,*.pxsl setf papp @@ -1636,9 +1645,13 @@ au BufNewFile,BufRead */etc/sysctl.conf,*/etc/sysctl.d/*.conf setf sysctl " Systemd unit files au BufNewFile,BufRead */systemd/*.{automount,mount,path,service,socket,swap,target,timer} setf systemd " Systemd overrides -au BufNewFile,BufRead /etc/systemd/system/*.d/*.conf setf systemd +au BufNewFile,BufRead */etc/systemd/system/*.d/*.conf setf systemd +au BufNewFile,BufRead */.config/systemd/user/*.d/*.conf setf systemd " Systemd temp files -au BufNewFile,BufRead /etc/systemd/system/*.d/.#* setf systemd +au BufNewFile,BufRead */etc/systemd/system/*.d/.#* setf systemd +au BufNewFile,BufRead */etc/systemd/system/.#* setf systemd +au BufNewFile,BufRead */.config/systemd/user/*.d/.#* setf systemd +au BufNewFile,BufRead */.config/systemd/user/.#* setf systemd " Synopsys Design Constraints au BufNewFile,BufRead *.sdc setf sdc @@ -1769,7 +1782,7 @@ au BufNewFile,BufRead *.va,*.vams setf verilogams au BufNewFile,BufRead *.sv,*.svh setf systemverilog " VHDL -au BufNewFile,BufRead *.hdl,*.vhd,*.vhdl,*.vbe,*.vst setf vhdl +au BufNewFile,BufRead *.hdl,*.vhd,*.vhdl,*.vbe,*.vst,*.vho setf vhdl " Vim script au BufNewFile,BufRead *.vim,*.vba,.exrc,_exrc setf vim diff --git a/runtime/indent/testdir/runtest.vim b/runtime/indent/testdir/runtest.vim index 9502c42f3e..945c2753e9 100644 --- a/runtime/indent/testdir/runtest.vim +++ b/runtime/indent/testdir/runtest.vim @@ -10,6 +10,7 @@ filetype indent on syn on set nowrapscan set report=9999 +set modeline au! SwapExists * call HandleSwapExists() func HandleSwapExists() diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index cfa208f21c..61da2130c8 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -101,7 +101,7 @@ local function for_each_buffer_client(bufnr, callback) for client_id in pairs(client_ids) do local client = active_clients[client_id] if client then - callback(client, client_id) + callback(client, client_id, bufnr) end end end @@ -121,13 +121,9 @@ local function validate_encoding(encoding) or error(string.format("Invalid offset encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'", encoding)) end -local function validate_command(input) +function lsp._cmd_parts(input) local cmd, cmd_args - if type(input) == 'string' then - -- Use a shell to execute the command if it is a string. - cmd = vim.api.nvim_get_option('shell') - cmd_args = {vim.api.nvim_get_option('shellcmdflag'), input} - elseif vim.tbl_islist(input) then + if vim.tbl_islist(input) then cmd = input[1] cmd_args = {} -- Don't mutate our input. @@ -138,7 +134,7 @@ local function validate_command(input) end end else - error("cmd type must be string or list.") + error("cmd type must be list.") end return cmd, cmd_args end @@ -158,7 +154,7 @@ local function validate_client_config(config) callbacks = { config.callbacks, "t", true }; capabilities = { config.capabilities, "t", true }; cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), "directory" }; - cmd_env = { config.cmd_env, "f", true }; + cmd_env = { config.cmd_env, "t", true }; name = { config.name, 's', true }; on_error = { config.on_error, "f", true }; on_exit = { config.on_exit, "f", true }; @@ -166,7 +162,7 @@ local function validate_client_config(config) before_init = { config.before_init, "f", true }; offset_encoding = { config.offset_encoding, "s", true }; } - local cmd, cmd_args = validate_command(config.cmd) + local cmd, cmd_args = lsp._cmd_parts(config.cmd) local offset_encoding = valid_encodings.UTF16 if config.offset_encoding then offset_encoding = validate_encoding(config.offset_encoding) @@ -202,6 +198,7 @@ local function text_document_did_open_handler(bufnr, client) } } client.notify('textDocument/didOpen', params) + util.buf_versions[bufnr] = params.textDocument.version end --- LSP client object. @@ -452,7 +449,7 @@ function lsp.start_client(config) -- The rootPath of the workspace. Is null if no folder is open. -- -- @deprecated in favour of rootUri. - rootPath = nil; + rootPath = config.root_dir; -- The rootUri of the workspace. Is null if no folder is open. If both -- `rootPath` and `rootUri` are set `rootUri` wins. rootUri = vim.uri_from_fname(config.root_dir); @@ -524,23 +521,25 @@ function lsp.start_client(config) end --- Checks capabilities before rpc.request-ing. - function client.request(method, params, callback) + function client.request(method, params, callback, bufnr) if not callback then callback = resolve_callback(method) or error("not found: request callback for client "..client.name) end - local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, callback) + local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, callback, bufnr) -- TODO keep these checks or just let it go anyway? if (not client.resolved_capabilities.hover and method == 'textDocument/hover') or (not client.resolved_capabilities.signature_help and method == 'textDocument/signatureHelp') or (not client.resolved_capabilities.goto_definition and method == 'textDocument/definition') or (not client.resolved_capabilities.implementation and method == 'textDocument/implementation') + or (not client.resolved_capabilities.document_symbol and method == 'textDocument/documentSymbol') + or (not client.resolved_capabilities.workspace_symbol and method == 'textDocument/workspaceSymbol') then - callback(unsupported_method(method), method, nil, client_id) + callback(unsupported_method(method), method, nil, client_id, bufnr) return end return rpc.request(method, params, function(err, result) - callback(err, method, result, client_id) + callback(err, method, result, client_id, bufnr) end) end @@ -617,6 +616,8 @@ do if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then return end + + util.buf_versions[bufnr] = changedtick -- Lazy initialize these because clients may not even need them. local incremental_changes = once(function(client) local size_index = encoding_index[client.offset_encoding] @@ -723,6 +724,7 @@ function lsp.buf_attach_client(bufnr, client_id) client.notify('textDocument/didClose', params) end end) + util.buf_versions[bufnr] = nil all_buffer_active_clients[bufnr] = nil end; -- TODO if we know all of the potential clients ahead of time, then we @@ -840,8 +842,8 @@ function lsp.buf_request(bufnr, method, params, callback) callback = { callback, 'f', true }; } local client_request_ids = {} - for_each_buffer_client(bufnr, function(client, client_id) - local request_success, request_id = client.request(method, params, callback) + for_each_buffer_client(bufnr, function(client, client_id, resolved_bufnr) + local request_success, request_id = client.request(method, params, callback, resolved_bufnr) -- This could only fail if the client shut down in the time since we looked -- it up and we did the request, which should be rare. @@ -897,21 +899,22 @@ function lsp.buf_request_sync(bufnr, method, params, timeout_ms) return request_results end ---- Sends a notification to all servers attached to the buffer. ---- ---@param bufnr (optional, number) Buffer handle, or 0 for current ---@param method (string) LSP method name ---@param params (string) Parameters to send to the server ---- ---@returns nil +--- Send a notification to a server +-- @param bufnr [number] (optional): The number of the buffer +-- @param method [string]: Name of the request method +-- @param params [string]: Arguments to send to the server +-- +-- @returns true if any client returns true; false otherwise function lsp.buf_notify(bufnr, method, params) validate { bufnr = { bufnr, 'n', true }; method = { method, 's' }; } - for_each_buffer_client(bufnr, function(client, _client_id) - client.rpc.notify(method, params) + local resp = false + for_each_buffer_client(bufnr, function(client, _client_id, _resolved_bufnr) + if client.rpc.notify(method, params) then resp = true end end) + return resp end --- Implements 'omnifunc' compatible LSP completion. @@ -949,12 +952,14 @@ function lsp.omnifunc(findstart, base) -- Get the start position of the current keyword local textMatch = vim.fn.match(line_to_cursor, '\\k*$') + local prefix = line_to_cursor:sub(textMatch+1) + local params = util.make_position_params() local items = {} lsp.buf_request(bufnr, 'textDocument/completion', params, function(err, _, result) if err or not result then return end - local matches = util.text_document_completion_list_to_complete_items(result) + local matches = util.text_document_completion_list_to_complete_items(result, prefix) -- TODO(ashkan): is this the best way to do this? vim.list_extend(items, matches) vim.fn.complete(textMatch+1, items) @@ -1013,5 +1018,18 @@ function lsp.get_log_path() return log.get_filename() end +-- Define the LspDiagnostics signs if they're not defined already. +do + local function define_default_sign(name, properties) + if vim.tbl_isempty(vim.fn.sign_getdefined(name)) then + vim.fn.sign_define(name, properties) + end + end + define_default_sign('LspDiagnosticsErrorSign', {text='E', texthl='LspDiagnosticsErrorSign', linehl='', numhl=''}) + define_default_sign('LspDiagnosticsWarningSign', {text='W', texthl='LspDiagnosticsWarningSign', linehl='', numhl=''}) + define_default_sign('LspDiagnosticsInformationSign', {text='I', texthl='LspDiagnosticsInformationSign', linehl='', numhl=''}) + define_default_sign('LspDiagnosticsHintSign', {text='H', texthl='LspDiagnosticsHintSign', linehl='', numhl=''}) +end + return lsp -- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index a6a05fb095..0b45951a56 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -23,17 +23,15 @@ local function request(method, params, callback) return vim.lsp.buf_request(0, method, params, callback) end -function M.hover() - local params = util.make_position_params() - request('textDocument/hover', params) +function M.server_ready() + return not not vim.lsp.buf_notify(0, "window/progress", {}) end -function M.peek_definition() +function M.hover() local params = util.make_position_params() - request('textDocument/peekDefinition', params) + request('textDocument/hover', params) end - function M.declaration() local params = util.make_position_params() request('textDocument/declaration', params) @@ -68,8 +66,9 @@ end function M.formatting(options) validate { options = {options, 't', true} } + local sts = vim.bo.softtabstop; options = vim.tbl_extend('keep', options or {}, { - tabSize = vim.bo.tabstop; + tabSize = (sts > 0 and sts) or (sts < 0 and vim.bo.shiftwidth) or vim.bo.tabstop; insertSpaces = vim.bo.expandtab; }) local params = { @@ -85,8 +84,9 @@ function M.range_formatting(options, start_pos, end_pos) start_pos = {start_pos, 't', true}; end_pos = {end_pos, 't', true}; } + local sts = vim.bo.softtabstop; options = vim.tbl_extend('keep', options or {}, { - tabSize = vim.bo.tabstop; + tabSize = (sts > 0 and sts) or (sts < 0 and vim.bo.shiftwidth) or vim.bo.tabstop; insertSpaces = vim.bo.expandtab; }) local A = list_extend({}, start_pos or api.nvim_buf_get_mark(0, '<')) @@ -132,5 +132,34 @@ function M.references(context) request('textDocument/references', params) end +function M.document_symbol() + local params = { textDocument = util.make_text_document_params() } + request('textDocument/documentSymbol', params) +end + +function M.workspace_symbol(query) + query = query or npcall(vfn.input, "Query: ") + local params = {query = query} + request('workspace/symbol', params) +end + +--- Send request to server to resolve document highlights for the +--- current text document position. This request can be associated +--- to key mapping or to events such as `CursorHold`, eg: +--- +--- <pre> +--- vim.api.nvim_command [[autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()]] +--- vim.api.nvim_command [[autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight()]] +--- vim.api.nvim_command [[autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()]] +--- </pre> +function M.document_highlight() + local params = util.make_position_params() + request('textDocument/documentHighlight', params) +end + +function M.clear_references() + util.buf_clear_references() +end + return M -- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua index 794140ee2e..09ca4b61e4 100644 --- a/runtime/lua/vim/lsp/callbacks.lua +++ b/runtime/lua/vim/lsp/callbacks.lua @@ -17,7 +17,11 @@ M['workspace/applyEdit'] = function(_, _, workspace_edit) if workspace_edit.label then print("Workspace edit", workspace_edit.label) end - util.apply_workspace_edit(workspace_edit.edit) + local status, result = pcall(util.apply_workspace_edit, workspace_edit.edit) + return { + applied = status; + failureReason = result; + } end M['textDocument/publishDiagnostics'] = function(_, _, result) @@ -29,18 +33,40 @@ M['textDocument/publishDiagnostics'] = function(_, _, result) return end util.buf_clear_diagnostics(bufnr) + + -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic + -- The diagnostic's severity. Can be omitted. If omitted it is up to the + -- client to interpret diagnostics as error, warning, info or hint. + -- TODO: Replace this with server-specific heuristics to infer severity. + for _, diagnostic in ipairs(result.diagnostics) do + if diagnostic.severity == nil then + diagnostic.severity = protocol.DiagnosticSeverity.Error + end + end + util.buf_diagnostics_save_positions(bufnr, result.diagnostics) util.buf_diagnostics_underline(bufnr, result.diagnostics) util.buf_diagnostics_virtual_text(bufnr, result.diagnostics) - -- util.set_loclist(result.diagnostics) + util.buf_diagnostics_signs(bufnr, result.diagnostics) + vim.api.nvim_command("doautocmd User LspDiagnosticsChanged") end M['textDocument/references'] = function(_, _, result) if not result then return end - util.set_qflist(result) + util.set_qflist(util.locations_to_items(result)) + api.nvim_command("copen") + api.nvim_command("wincmd p") +end + +local symbol_callback = function(_, _, result, _, bufnr) + if not result or vim.tbl_isempty(result) then return end + + util.set_qflist(util.symbols_to_items(result, bufnr)) api.nvim_command("copen") api.nvim_command("wincmd p") end +M['textDocument/documentSymbol'] = symbol_callback +M['workspace/symbol'] = symbol_callback M['textDocument/rename'] = function(_, _, result) if not result then return end @@ -63,8 +89,9 @@ M['textDocument/completion'] = function(_, _, result) local line = assert(api.nvim_buf_get_lines(0, row-1, row, false)[1]) local line_to_cursor = line:sub(col+1) local textMatch = vim.fn.match(line_to_cursor, '\\k*$') + local prefix = line_to_cursor:sub(textMatch+1) - local matches = util.text_document_completion_list_to_complete_items(result) + local matches = util.text_document_completion_list_to_complete_items(result, prefix) vim.fn.complete(textMatch+1, matches) end @@ -93,11 +120,20 @@ local function location_callback(_, method, result) local _ = log.info() and log.info(method, 'No location found') return nil end - util.jump_to_location(result[1]) - if #result > 1 then - util.set_qflist(result) - api.nvim_command("copen") - api.nvim_command("wincmd p") + + -- textDocument/definition can return Location or Location[] + -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition + + if vim.tbl_islist(result) then + util.jump_to_location(result[1]) + + if #result > 1 then + util.set_qflist(util.locations_to_items(result)) + api.nvim_command("copen") + api.nvim_command("wincmd p") + end + else + util.jump_to_location(result) end end @@ -106,72 +142,13 @@ M['textDocument/definition'] = location_callback M['textDocument/typeDefinition'] = location_callback M['textDocument/implementation'] = location_callback ---- Convert SignatureHelp response to preview contents. --- https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_signatureHelp -local function signature_help_to_preview_contents(input) - if not input.signatures then - return - end - --The active signature. If omitted or the value lies outside the range of - --`signatures` the value defaults to zero or is ignored if `signatures.length - --=== 0`. Whenever possible implementors should make an active decision about - --the active signature and shouldn't rely on a default value. - local contents = {} - local active_signature = input.activeSignature or 0 - -- If the activeSignature is not inside the valid range, then clip it. - if active_signature >= #input.signatures then - active_signature = 0 - end - local signature = input.signatures[active_signature + 1] - if not signature then - return - end - vim.list_extend(contents, vim.split(signature.label, '\n', true)) - if signature.documentation then - util.convert_input_to_markdown_lines(signature.documentation, contents) - end - if input.parameters then - local active_parameter = input.activeParameter or 0 - -- If the activeParameter is not inside the valid range, then clip it. - if active_parameter >= #input.parameters then - active_parameter = 0 - end - local parameter = signature.parameters and signature.parameters[active_parameter] - if parameter then - --[=[ - --Represents a parameter of a callable-signature. A parameter can - --have a label and a doc-comment. - interface ParameterInformation { - --The label of this parameter information. - -- - --Either a string or an inclusive start and exclusive end offsets within its containing - --signature label. (see SignatureInformation.label). The offsets are based on a UTF-16 - --string representation as `Position` and `Range` does. - -- - --*Note*: a label of type string should be a substring of its containing signature label. - --Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`. - label: string | [number, number]; - --The human-readable doc-comment of this parameter. Will be shown - --in the UI but can be omitted. - documentation?: string | MarkupContent; - } - --]=] - -- TODO highlight parameter - if parameter.documentation then - util.convert_input_to_markdown_lines(parameter.documentation, contents) - end - end - end - return contents -end - M['textDocument/signatureHelp'] = function(_, method, result) util.focusable_preview(method, function() if not (result and result.signatures and result.signatures[1]) then return { 'No signature available' } end -- TODO show popup when signatures is empty? - local lines = signature_help_to_preview_contents(result) + local lines = util.convert_signature_help_to_markdown_lines(result) lines = util.trim_empty_lines(lines) if vim.tbl_isempty(lines) then return { 'No signature available' } @@ -180,22 +157,33 @@ M['textDocument/signatureHelp'] = function(_, method, result) end) end -M['textDocument/peekDefinition'] = function(_, _, result, _) - if not (result and result[1]) then return end - local loc = result[1] - local bufnr = vim.uri_to_bufnr(loc.uri) or error("not found: "..tostring(loc.uri)) - local start = loc.range.start - local finish = loc.range["end"] - util.open_floating_peek_preview(bufnr, start, finish, { offset_x = 1 }) - local headbuf = util.open_floating_preview({"Peek:"}, nil, { - offset_y = -(finish.line - start.line); - width = finish.character - start.character + 2; - }) - -- TODO(ashkan) change highlight group? - api.nvim_buf_add_highlight(headbuf, -1, 'Keyword', 0, -1) +M['textDocument/documentHighlight'] = function(_, _, result, _) + if not result then return end + local bufnr = api.nvim_get_current_buf() + util.buf_highlight_references(bufnr, result) +end + +M['window/logMessage'] = function(_, _, result, client_id) + local message_type = result.type + local message = result.message + local client = vim.lsp.get_client_by_id(client_id) + local client_name = client and client.name or string.format("id=%d", client_id) + if not client then + err_message("LSP[", client_name, "] client has shut down after sending the message") + end + if message_type == protocol.MessageType.Error then + log.error(message) + elseif message_type == protocol.MessageType.Warning then + log.warn(message) + elseif message_type == protocol.MessageType.Info then + log.info(message) + else + log.debug(message) + end + return result end -local function log_message(_, _, result, client_id) +M['window/showMessage'] = function(_, _, result, client_id) local message_type = result.type local message = result.message local client = vim.lsp.get_client_by_id(client_id) @@ -212,9 +200,6 @@ local function log_message(_, _, result, client_id) return result end -M['window/showMessage'] = log_message -M['window/logMessage'] = log_message - -- Add boilerplate error validation and logging for all of these. for k, fn in pairs(M) do M[k] = function(err, method, params, client_id) diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index 974eaae38c..c0db5e5485 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -13,7 +13,6 @@ log.levels = { INFO = 2; WARN = 3; ERROR = 4; - -- FATAL = 4; } -- Default log level is warn. diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index f64b0b50e7..ee6e29bac0 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -644,6 +644,18 @@ function protocol.make_client_capabilities() -- TODO(tjdevries): Implement this contextSupport = false; }; + declaration = { + linkSupport = true; + }; + definition = { + linkSupport = true; + }; + implementation = { + linkSupport = true; + }; + typeDefinition = { + linkSupport = true; + }; hover = { dynamicRegistration = false; contentFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText }; @@ -663,21 +675,36 @@ function protocol.make_client_capabilities() documentHighlight = { dynamicRegistration = false }; - -- documentSymbol = { - -- dynamicRegistration = false; - -- symbolKind = { - -- valueSet = (function() - -- local res = {} - -- for k in pairs(protocol.SymbolKind) do - -- if type(k) == 'string' then table.insert(res, k) end - -- end - -- return res - -- end)(); - -- }; - -- hierarchicalDocumentSymbolSupport = false; - -- }; + documentSymbol = { + dynamicRegistration = false; + symbolKind = { + valueSet = (function() + local res = {} + for k in pairs(protocol.SymbolKind) do + if type(k) == 'number' then table.insert(res, k) end + end + return res + end)(); + }; + hierarchicalDocumentSymbolSupport = true; + }; + }; + workspace = { + symbol = { + dynamicRegistration = false; + symbolKind = { + valueSet = (function() + local res = {} + for k in pairs(protocol.SymbolKind) do + if type(k) == 'number' then table.insert(res, k) end + end + return res + end)(); + }; + hierarchicalWorkspaceSymbolSupport = true; + }; + applyEdit = true; }; - workspace = nil; experimental = nil; } end diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index e13b05610b..dad1dc11f1 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -33,38 +33,25 @@ local function convert_NIL(v) return v end --- If a dictionary is passed in, turn it into a list of string of "k=v" --- Accepts a table which can be composed of k=v strings or map-like --- specification, such as: --- --- ``` --- { --- "PRODUCTION=false"; --- "PATH=/usr/bin/"; --- PORT = 123; --- HOST = "0.0.0.0"; --- } --- ``` --- --- Non-string values will be cast with `tostring` -local function force_env_list(final_env) - if final_env then - local env = final_env - final_env = {} - for k,v in pairs(env) do - -- If it's passed in as a dict, then convert to list of "k=v" - if type(k) == "string" then - table.insert(final_env, k..'='..tostring(v)) - elseif type(v) == 'string' then - table.insert(final_env, v) - else - -- TODO is this right or should I exception here? - -- Try to coerce other values to string. - table.insert(final_env, tostring(v)) - end - end - return final_env +--- Merges current process env with the given env and returns the result as +--- a list of "k=v" strings. +--- +--- Example: +--- +--- { PRODUCTION="false", PATH="/usr/bin/", PORT=123, HOST="0.0.0.0", } +--- => { "PRODUCTION=false", "PATH=/usr/bin/", "PORT=123", "HOST=0.0.0.0", } +local function env_merge(env) + if env == nil then + return env end + -- Merge. + env = vim.tbl_extend('force', vim.fn.environ(), env) + local final_env = {} + for k,v in pairs(env) do + assert(type(k) == 'string', 'env must be a dict') + table.insert(final_env, k..'='..tostring(v)) + end + return final_env end local function format_message_with_content_length(encoded_message) @@ -153,14 +140,23 @@ local function format_rpc_error(err) validate { err = { err, 't' }; } - local code_name = assert(protocol.ErrorCodes[err.code], "err.code is invalid") - local message_parts = {"RPC", code_name} + + -- There is ErrorCodes in the LSP specification, + -- but in ResponseError.code it is not used and the actual type is number. + local code + if protocol.ErrorCodes[err.code] then + code = string.format("code_name = %s,", protocol.ErrorCodes[err.code]) + else + code = string.format("code_name = unknown, code = %s,", err.code) + end + + local message_parts = {"RPC[Error]", code} if err.message then - table.insert(message_parts, "message = ") + table.insert(message_parts, "message =") table.insert(message_parts, string.format("%q", err.message)) end if err.data then - table.insert(message_parts, "data = ") + table.insert(message_parts, "data =") table.insert(message_parts, vim.inspect(err.data)) end return table.concat(message_parts, ' ') @@ -262,7 +258,7 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para if spawn_params.cwd then assert(is_dir(spawn_params.cwd), "cwd must be a directory") end - spawn_params.env = force_env_list(extra_spawn_params.env) + spawn_params.env = env_merge(extra_spawn_params.env) end handle, pid = uv.spawn(cmd, spawn_params, onexit) end diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index df82e2d412..099a77099b 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -6,6 +6,31 @@ local list_extend = vim.list_extend local M = {} +--- Diagnostics received from the server via `textDocument/publishDiagnostics` +-- by buffer. +-- +-- {<bufnr>: {diagnostics}} +-- +-- This contains only entries for active buffers. Entries for detached buffers +-- are discarded. +-- +-- If you override the `textDocument/publishDiagnostic` callback, +-- this will be empty unless you call `buf_diagnostics_save_positions`. +-- +-- +-- Diagnostic is: +-- +-- { +-- range: Range +-- message: string +-- severity?: DiagnosticSeverity +-- code?: number | string +-- source?: string +-- tags?: DiagnosticTag[] +-- relatedInformation?: DiagnosticRelatedInformation[] +-- } +M.diagnostics_by_buf = {} + local split = vim.split local function split_lines(value) return split(value, '\n', true) @@ -71,16 +96,28 @@ end) function M.apply_text_edits(text_edits, bufnr) if not next(text_edits) then return end + if not api.nvim_buf_is_loaded(bufnr) then + vim.fn.bufload(bufnr) + end local start_line, finish_line = math.huge, -1 local cleaned = {} for i, e in ipairs(text_edits) do + -- adjust start and end column for UTF-16 encoding of non-ASCII characters + local start_row = e.range.start.line + local start_col = e.range.start.character + local start_bline = api.nvim_buf_get_lines(bufnr, start_row, start_row+1, true)[1] + start_col = vim.str_byteindex(start_bline, start_col) + local end_row = e.range["end"].line + local end_col = e.range["end"].character + local end_bline = api.nvim_buf_get_lines(bufnr, end_row, end_row+1, true)[1] + end_col = vim.str_byteindex(end_bline, end_col) start_line = math.min(e.range.start.line, start_line) finish_line = math.max(e.range["end"].line, finish_line) -- TODO(ashkan) sanity check ranges for overlap. table.insert(cleaned, { i = i; - A = {e.range.start.line; e.range.start.character}; - B = {e.range["end"].line; e.range["end"].character}; + A = {start_row; start_col}; + B = {end_row; end_col}; lines = vim.split(e.newText, '\n', true); }) end @@ -131,10 +168,12 @@ end function M.apply_text_document_edit(text_document_edit) local text_document = text_document_edit.textDocument local bufnr = vim.uri_to_bufnr(text_document.uri) - -- TODO(ashkan) check this is correct. - if api.nvim_buf_get_changedtick(bufnr) > text_document.version then - print("Buffer ", text_document.uri, " newer than edits.") - return + if text_document.version then + -- `VersionedTextDocumentIdentifier`s version may be null https://microsoft.github.io/language-server-protocol/specification#versionedTextDocumentIdentifier + if text_document.version ~= vim.NIL and M.buf_versions[bufnr] ~= nil and M.buf_versions[bufnr] > text_document.version then + print("Buffer ", text_document.uri, " newer than edits.") + return + end end M.apply_text_edits(text_document_edit.edits, bufnr) end @@ -145,15 +184,55 @@ function M.get_current_line_to_cursor() return line:sub(pos[2]+1) end +-- Sort by CompletionItem.sortText +-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion +local function sort_completion_items(items) + if items[1] and items[1].sortText then + table.sort(items, function(a, b) return a.sortText < b.sortText + end) + end +end + +-- Returns text that should be inserted when selecting completion item. The precedence is as follows: +-- textEdit.newText > insertText > label +-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion +local function get_completion_word(item) + if item.textEdit ~= nil and item.textEdit.newText ~= nil then + return item.textEdit.newText + elseif item.insertText ~= nil then + return item.insertText + end + return item.label +end + +-- Some lanuguage servers return complementary candidates whose prefixes do not match are also returned. +-- So we exclude completion candidates whose prefix does not match. +local function remove_unmatch_completion_items(items, prefix) + return vim.tbl_filter(function(item) + local word = get_completion_word(item) + return vim.startswith(word, prefix) + end, items) +end + +-- Acording to LSP spec, if the client set "completionItemKind.valueSet", +-- the client must handle it properly even if it receives a value outside the specification. +-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion +function M._get_completion_item_kind_name(completion_item_kind) + return protocol.CompletionItemKind[completion_item_kind] or "Unknown" +end + --- Getting vim complete-items with incomplete flag. -- @params CompletionItem[], CompletionList or nil (https://microsoft.github.io/language-server-protocol/specification#textDocument_completion) -- @return { matches = complete-items table, incomplete = boolean } -function M.text_document_completion_list_to_complete_items(result) +function M.text_document_completion_list_to_complete_items(result, prefix) local items = M.extract_completion_items(result) if vim.tbl_isempty(items) then return {} end + items = remove_unmatch_completion_items(items, prefix) + sort_completion_items(items) + local matches = {} for _, completion_item in ipairs(items) do @@ -169,16 +248,23 @@ function M.text_document_completion_list_to_complete_items(result) end end - local word = completion_item.insertText or completion_item.label + local word = get_completion_word(completion_item) table.insert(matches, { word = word, abbr = completion_item.label, - kind = protocol.CompletionItemKind[completion_item.kind] or '', + kind = M._get_completion_item_kind_name(completion_item.kind), menu = completion_item.detail or '', info = info, icase = 1, - dup = 0, + dup = 1, empty = 1, + user_data = { + nvim = { + lsp = { + completion_item = completion_item + } + } + }, }) end @@ -245,12 +331,71 @@ function M.convert_input_to_markdown_lines(input, contents) end end end - if contents[1] == '' or contents[1] == nil then + if (contents[1] == '' or contents[1] == nil) and #contents == 1 then return {} end return contents end +--- Convert SignatureHelp response to markdown lines. +-- https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_signatureHelp +function M.convert_signature_help_to_markdown_lines(signature_help) + if not signature_help.signatures then + return + end + --The active signature. If omitted or the value lies outside the range of + --`signatures` the value defaults to zero or is ignored if `signatures.length + --=== 0`. Whenever possible implementors should make an active decision about + --the active signature and shouldn't rely on a default value. + local contents = {} + local active_signature = signature_help.activeSignature or 0 + -- If the activeSignature is not inside the valid range, then clip it. + if active_signature >= #signature_help.signatures then + active_signature = 0 + end + local signature = signature_help.signatures[active_signature + 1] + if not signature then + return + end + vim.list_extend(contents, vim.split(signature.label, '\n', true)) + if signature.documentation then + M.convert_input_to_markdown_lines(signature.documentation, contents) + end + if signature_help.parameters then + local active_parameter = signature_help.activeParameter or 0 + -- If the activeParameter is not inside the valid range, then clip it. + if active_parameter >= #signature_help.parameters then + active_parameter = 0 + end + local parameter = signature.parameters and signature.parameters[active_parameter] + if parameter then + --[=[ + --Represents a parameter of a callable-signature. A parameter can + --have a label and a doc-comment. + interface ParameterInformation { + --The label of this parameter information. + -- + --Either a string or an inclusive start and exclusive end offsets within its containing + --signature label. (see SignatureInformation.label). The offsets are based on a UTF-16 + --string representation as `Position` and `Range` does. + -- + --*Note*: a label of type string should be a substring of its containing signature label. + --Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`. + label: string | [number, number]; + --The human-readable doc-comment of this parameter. Will be shown + --in the UI but can be omitted. + documentation?: string | MarkupContent; + } + --]=] + -- TODO highlight parameter + if parameter.documentation then + M.convert_input_help_to_markdown_lines(parameter.documentation, contents) + end + end + end + return contents +end + function M.make_floating_popup_options(width, height, opts) validate { opts = { opts, 't', true }; @@ -297,14 +442,24 @@ function M.make_floating_popup_options(width, height, opts) end function M.jump_to_location(location) - if location.uri == nil then return end - local bufnr = vim.uri_to_bufnr(location.uri) + -- location may be Location or LocationLink + local uri = location.uri or location.targetUri + if uri == nil then return end + local bufnr = vim.uri_to_bufnr(uri) -- Save position in jumplist vim.cmd "normal! m'" - -- TODO(ashkan) use tagfunc here to update tagstack. + + -- Push a new item into tagstack + local from = {vim.fn.bufnr('%'), vim.fn.line('.'), vim.fn.col('.'), 0} + local items = {{tagname=vim.fn.expand('<cword>'), from=from}} + vim.fn.settagstack(vim.fn.win_getid(), {items=items}, 't') + + --- Jump to new location (adjusting for UTF-16 encoding of characters) api.nvim_set_current_buf(bufnr) - local row = location.range.start.line - local col = location.range.start.character + api.nvim_buf_set_option(0, 'buflisted', true) + local range = location.range or location.targetSelectionRange + local row = range.start.line + local col = range.start.character local line = api.nvim_buf_get_lines(0, row, row+1, true)[1] col = vim.str_byteindex(line, col) api.nvim_win_set_cursor(0, {row + 1, col}) @@ -498,36 +653,10 @@ function M.open_floating_preview(contents, filetype, opts) end api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents) api.nvim_buf_set_option(floating_bufnr, 'modifiable', false) - -- TODO make InsertCharPre disappearing optional? - api.nvim_command("autocmd CursorMoved,BufHidden,InsertCharPre <buffer> ++once lua pcall(vim.api.nvim_win_close, "..floating_winnr..", true)") + M.close_preview_autocmd({"CursorMoved", "CursorMovedI", "BufHidden"}, floating_winnr) return floating_bufnr, floating_winnr end -local function validate_lsp_position(pos) - validate { pos = {pos, 't'} } - validate { - line = {pos.line, 'n'}; - character = {pos.character, 'n'}; - } - return true -end - -function M.open_floating_peek_preview(bufnr, start, finish, opts) - validate { - bufnr = {bufnr, 'n'}; - start = {start, validate_lsp_position, 'valid start Position'}; - finish = {finish, validate_lsp_position, 'valid finish Position'}; - opts = { opts, 't', true }; - } - local width = math.max(finish.character - start.character + 1, 1) - local height = math.max(finish.line - start.line + 1, 1) - local floating_winnr = api.nvim_open_win(bufnr, false, M.make_floating_popup_options(width, height, opts)) - api.nvim_win_set_cursor(floating_winnr, {start.line+1, start.character}) - api.nvim_command("autocmd CursorMoved * ++once lua pcall(vim.api.nvim_win_close, "..floating_winnr..", true)") - return floating_winnr -end - - local function highlight_range(bufnr, ns, hiname, start, finish) if start[1] == finish[1] then -- TODO care about encoding here since this is in byte index? @@ -542,10 +671,9 @@ local function highlight_range(bufnr, ns, hiname, start, finish) end do - local all_buffer_diagnostics = {} - local diagnostic_ns = api.nvim_create_namespace("vim_lsp_diagnostics") - + local reference_ns = api.nvim_create_namespace("vim_lsp_references") + local sign_ns = 'vim_lsp_signs' local underline_highlight_name = "LspDiagnosticsUnderline" vim.cmd(string.format("highlight default %s gui=underline cterm=underline", underline_highlight_name)) for kind, _ in pairs(protocol.DiagnosticSeverity) do @@ -573,12 +701,18 @@ do table.insert(cmd_parts, k.."="..v) end api.nvim_command(table.concat(cmd_parts, ' ')) + api.nvim_command('highlight link ' .. highlight_name .. 'Sign ' .. highlight_name) severity_highlights[severity] = highlight_name end function M.buf_clear_diagnostics(bufnr) validate { bufnr = {bufnr, 'n', true} } bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr + + -- clear sign group + vim.fn.sign_unplace(sign_ns, {buffer=bufnr}) + + -- clear virtual text namespace api.nvim_buf_clear_namespace(bufnr, diagnostic_ns, 0, -1) end @@ -593,13 +727,12 @@ do -- if #marks == 0 then -- return -- end - -- local buffer_diagnostics = all_buffer_diagnostics[bufnr] local lines = {"Diagnostics:"} local highlights = {{0, "Bold"}} - local buffer_diagnostics = all_buffer_diagnostics[bufnr] + local buffer_diagnostics = M.diagnostics_by_buf[bufnr] if not buffer_diagnostics then return end - local line_diagnostics = buffer_diagnostics[line] + local line_diagnostics = M.diagnostics_group_by_line(buffer_diagnostics)[line] if not line_diagnostics then return end for i, diagnostic in ipairs(line_diagnostics) do @@ -610,6 +743,7 @@ do -- TODO(ashkan) make format configurable? local prefix = string.format("%d. ", i) local hiname = severity_highlights[diagnostic.severity] + assert(hiname, 'unknown severity: ' .. tostring(diagnostic.severity)) local message_lines = split_lines(diagnostic.message) table.insert(lines, prefix..message_lines[1]) table.insert(highlights, {#prefix + 1, hiname}) @@ -627,6 +761,8 @@ do return popup_bufnr, winnr end + --- Saves the diagnostics (Diagnostic[]) into diagnostics_by_buf + -- function M.buf_diagnostics_save_positions(bufnr, diagnostics) validate { bufnr = {bufnr, 'n', true}; @@ -635,31 +771,17 @@ do if not diagnostics then return end bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr - if not all_buffer_diagnostics[bufnr] then + if not M.diagnostics_by_buf[bufnr] then -- Clean up our data when the buffer unloads. api.nvim_buf_attach(bufnr, false, { on_detach = function(b) - all_buffer_diagnostics[b] = nil + M.diagnostics_by_buf[b] = nil end }) end - all_buffer_diagnostics[bufnr] = {} - local buffer_diagnostics = all_buffer_diagnostics[bufnr] - - for _, diagnostic in ipairs(diagnostics) do - local start = diagnostic.range.start - -- local mark_id = api.nvim_buf_set_extmark(bufnr, diagnostic_ns, 0, start.line, 0, {}) - -- buffer_diagnostics[mark_id] = diagnostic - local line_diagnostics = buffer_diagnostics[start.line] - if not line_diagnostics then - line_diagnostics = {} - buffer_diagnostics[start.line] = line_diagnostics - end - table.insert(line_diagnostics, diagnostic) - end + M.diagnostics_by_buf[bufnr] = diagnostics end - function M.buf_diagnostics_underline(bufnr, diagnostics) for _, diagnostic in ipairs(diagnostics) do local start = diagnostic.range["start"] @@ -681,15 +803,46 @@ do end end - function M.buf_diagnostics_virtual_text(bufnr, diagnostics) - local buffer_line_diagnostics = all_buffer_diagnostics[bufnr] - if not buffer_line_diagnostics then - M.buf_diagnostics_save_positions(bufnr, diagnostics) + function M.buf_clear_references(bufnr) + validate { bufnr = {bufnr, 'n', true} } + api.nvim_buf_clear_namespace(bufnr, reference_ns, 0, -1) + end + + function M.buf_highlight_references(bufnr, references) + validate { bufnr = {bufnr, 'n', true} } + for _, reference in ipairs(references) do + local start_pos = {reference["range"]["start"]["line"], reference["range"]["start"]["character"]} + local end_pos = {reference["range"]["end"]["line"], reference["range"]["end"]["character"]} + local document_highlight_kind = { + [protocol.DocumentHighlightKind.Text] = "LspReferenceText"; + [protocol.DocumentHighlightKind.Read] = "LspReferenceRead"; + [protocol.DocumentHighlightKind.Write] = "LspReferenceWrite"; + } + local kind = reference["kind"] or protocol.DocumentHighlightKind.Text + highlight_range(bufnr, reference_ns, document_highlight_kind[kind], start_pos, end_pos) end - buffer_line_diagnostics = all_buffer_diagnostics[bufnr] - if not buffer_line_diagnostics then + end + + function M.diagnostics_group_by_line(diagnostics) + if not diagnostics then return end + local diagnostics_by_line = {} + for _, diagnostic in ipairs(diagnostics) do + local start = diagnostic.range.start + local line_diagnostics = diagnostics_by_line[start.line] + if not line_diagnostics then + line_diagnostics = {} + diagnostics_by_line[start.line] = line_diagnostics + end + table.insert(line_diagnostics, diagnostic) + end + return diagnostics_by_line + end + + function M.buf_diagnostics_virtual_text(bufnr, diagnostics) + if not diagnostics then return end + local buffer_line_diagnostics = M.diagnostics_group_by_line(diagnostics) for line, line_diags in pairs(buffer_line_diagnostics) do local virt_texts = {} for i = 1, #line_diags - 1 do @@ -701,10 +854,36 @@ do api.nvim_buf_set_virtual_text(bufnr, diagnostic_ns, line, virt_texts, {}) end end + + function M.buf_diagnostics_count(kind) + local bufnr = vim.api.nvim_get_current_buf() + local diagnostics = M.diagnostics_by_buf[bufnr] + if not diagnostics then return end + local count = 0 + for _, diagnostic in pairs(diagnostics) do + if protocol.DiagnosticSeverity[kind] == diagnostic.severity then + count = count + 1 + end + end + return count + end + + local diagnostic_severity_map = { + [protocol.DiagnosticSeverity.Error] = "LspDiagnosticsErrorSign"; + [protocol.DiagnosticSeverity.Warning] = "LspDiagnosticsWarningSign"; + [protocol.DiagnosticSeverity.Information] = "LspDiagnosticsInformationSign"; + [protocol.DiagnosticSeverity.Hint] = "LspDiagnosticsHintSign"; + } + + function M.buf_diagnostics_signs(bufnr, diagnostics) + for _, diagnostic in ipairs(diagnostics) do + vim.fn.sign_place(0, sign_ns, diagnostic_severity_map[diagnostic.severity], bufnr, {lnum=(diagnostic.range.start.line+1)}) + end + end end local position_sort = sort_by_key(function(v) - return {v.line, v.character} + return {v.start.line, v.start.character} end) -- Returns the items with the byte position calculated correctly and in sorted @@ -719,19 +898,25 @@ function M.locations_to_items(locations) end; }) for _, d in ipairs(locations) do - local start = d.range.start - local fname = assert(vim.uri_to_fname(d.uri)) - table.insert(grouped[fname], start) + -- locations may be Location or LocationLink + local uri = d.uri or d.targetUri + local fname = assert(vim.uri_to_fname(uri)) + local range = d.range or d.targetSelectionRange + table.insert(grouped[fname], {start = range.start}) end + + local keys = vim.tbl_keys(grouped) table.sort(keys) -- TODO(ashkan) I wish we could do this lazily. for _, fname in ipairs(keys) do local rows = grouped[fname] + table.sort(rows, position_sort) local i = 0 for line in io.lines(fname) do - for _, pos in ipairs(rows) do + for _, temp in ipairs(rows) do + local pos = temp.start local row = pos.line if i == row then local col @@ -754,23 +939,65 @@ function M.locations_to_items(locations) return items end --- locations is Location[] --- Only sets for the current window. -function M.set_loclist(locations) +function M.set_loclist(items) vim.fn.setloclist(0, {}, ' ', { title = 'Language Server'; - items = M.locations_to_items(locations); + items = items; }) end --- locations is Location[] -function M.set_qflist(locations) +function M.set_qflist(items) vim.fn.setqflist({}, ' ', { title = 'Language Server'; - items = M.locations_to_items(locations); + items = items; }) end +-- Acording to LSP spec, if the client set "symbolKind.valueSet", +-- the client must handle it properly even if it receives a value outside the specification. +-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol +function M._get_symbol_kind_name(symbol_kind) + return protocol.SymbolKind[symbol_kind] or "Unknown" +end + +--- Convert symbols to quickfix list items +--- +--@symbols DocumentSymbol[] or SymbolInformation[] +function M.symbols_to_items(symbols, bufnr) + local function _symbols_to_items(_symbols, _items, _bufnr) + for _, symbol in ipairs(_symbols) do + if symbol.location then -- SymbolInformation type + local range = symbol.location.range + local kind = M._get_symbol_kind_name(symbol.kind) + table.insert(_items, { + filename = vim.uri_to_fname(symbol.location.uri), + lnum = range.start.line + 1, + col = range.start.character + 1, + kind = kind, + text = '['..kind..'] '..symbol.name, + }) + elseif symbol.range then -- DocumentSymbole type + local kind = M._get_symbol_kind_name(symbol.kind) + table.insert(_items, { + -- bufnr = _bufnr, + filename = vim.api.nvim_buf_get_name(_bufnr), + lnum = symbol.range.start.line + 1, + col = symbol.range.start.character + 1, + kind = kind, + text = '['..kind..'] '..symbol.name + }) + if symbol.children then + for _, v in ipairs(_symbols_to_items(symbol.children, _items, _bufnr)) do + vim.list_extend(_items, v) + end + end + end + end + return _items + end + return _symbols_to_items(symbols, {}, bufnr) +end + -- Remove empty lines from the beginning and end. function M.trim_empty_lines(lines) local start = 1 @@ -823,11 +1050,15 @@ function M.make_position_params() local line = api.nvim_buf_get_lines(0, row, row+1, true)[1] col = str_utfindex(line, col) return { - textDocument = { uri = vim.uri_from_bufnr(0) }; + textDocument = M.make_text_document_params(); position = { line = row; character = col; } } end +function M.make_text_document_params() + return { uri = vim.uri_from_bufnr(0) } +end + -- @param buf buffer handle or 0 for current. -- @param row 0-indexed line -- @param col 0-indexed byte offset in line @@ -840,5 +1071,7 @@ function M.character_offset(buf, row, col) return str_utfindex(line, col) end +M.buf_versions = {} + return M -- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index ea1117a906..d18fcfaf95 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -8,6 +8,9 @@ local vim = vim or {} --- Returns a deep copy of the given object. Non-table objects are copied as --- in a typical Lua assignment, whereas table objects are copied recursively. +--- Functions are naively copied, so functions in the copied table point to the +--- same functions as those in the input table. Userdata and threads are not +--- copied and will throw an error. --- --@param orig Table to copy --@returns New table of copied keys and (nested) values. @@ -20,6 +23,11 @@ vim.deepcopy = (function() local deepcopy_funcs = { table = function(orig) local copy = {} + + if vim._empty_dict_mt ~= nil and getmetatable(orig) == vim._empty_dict_mt then + copy = vim.empty_dict() + end + for k, v in pairs(orig) do copy[vim.deepcopy(k)] = vim.deepcopy(v) end @@ -29,10 +37,16 @@ vim.deepcopy = (function() string = _id, ['nil'] = _id, boolean = _id, + ['function'] = _id, } return function(orig) - return deepcopy_funcs[type(orig)](orig) + local f = deepcopy_funcs[type(orig)] + if f then + return f(orig) + else + error("Cannot deepcopy object of type "..type(orig)) + end end end)() @@ -130,6 +144,36 @@ function vim.tbl_values(t) return values end +--- Apply a function to all values of a table. +--- +--@param func function or callable table +--@param t table +function vim.tbl_map(func, t) + vim.validate{func={func,'c'},t={t,'t'}} + + local rettab = {} + for k, v in pairs(t) do + rettab[k] = func(v) + end + return rettab +end + +--- Filter a table using a predicate function +--- +--@param func function or callable table +--@param t table +function vim.tbl_filter(func, t) + vim.validate{func={func,'c'},t={t,'t'}} + + local rettab = {} + for _, entry in pairs(t) do + if func(entry) then + table.insert(rettab, entry) + end + end + return rettab +end + --- Checks if a list-like (vector) table contains `value`. --- --@param t Table to check @@ -169,9 +213,19 @@ function vim.tbl_extend(behavior, ...) if (behavior ~= 'error' and behavior ~= 'keep' and behavior ~= 'force') then error('invalid "behavior": '..tostring(behavior)) end + + if select('#', ...) < 2 then + error('wrong number of arguments (given '..tostring(1 + select('#', ...))..', expected at least 3)') + end + local ret = {} + if vim._empty_dict_mt ~= nil and getmetatable(select(1, ...)) == vim._empty_dict_mt then + ret = vim.empty_dict() + end + for i = 1, select('#', ...) do local tbl = select(i, ...) + vim.validate{["after the second argument"] = {tbl,'t'}} if tbl then for k, v in pairs(tbl) do if behavior ~= 'force' and ret[k] ~= nil then @@ -311,6 +365,24 @@ function vim.tbl_islist(t) end end +--- Counts the number of non-nil values in table `t`. +--- +--- <pre> +--- vim.tbl_count({ a=1, b=2 }) => 2 +--- vim.tbl_count({ 1, 2 }) => 2 +--- </pre> +--- +--@see https://github.com/Tieske/Penlight/blob/master/lua/pl/tablex.lua +--@param Table +--@returns Number that is the number of the value in table +function vim.tbl_count(t) + vim.validate{t={t,'t'}} + + local count = 0 + for _ in pairs(t) do count = count + 1 end + return count +end + --- Trim whitespace (Lua pattern "%s") from both sides of a string. --- --@see https://www.lua.org/pil/20.2.html diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index aa8b8fcdd1..d3b78a7f73 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -31,8 +31,6 @@ function Parser:_on_lines(bufnr, _, start_row, old_stop_row, stop_row, old_byte_ end local M = { - add_language=vim._ts_add_language, - inspect_language=vim._ts_inspect_language, parse_query = vim._ts_parse_query, } @@ -45,12 +43,34 @@ setmetatable(M, { end }) -function M.create_parser(bufnr, ft, id) +function M.require_language(lang, path) + if vim._ts_has_language(lang) then + return true + end + if path == nil then + local fname = 'parser/' .. lang .. '.*' + local paths = a.nvim_get_runtime_file(fname, false) + if #paths == 0 then + -- TODO(bfredl): help tag? + error("no parser for '"..lang.."' language") + end + path = paths[1] + end + vim._ts_add_language(path, lang) +end + +function M.inspect_language(lang) + M.require_language(lang) + return vim._ts_inspect_language(lang) +end + +function M.create_parser(bufnr, lang, id) + M.require_language(lang) if bufnr == 0 then bufnr = a.nvim_get_current_buf() end - local self = setmetatable({bufnr=bufnr, lang=ft, valid=false}, Parser) - self._parser = vim._create_ts_parser(ft) + local self = setmetatable({bufnr=bufnr, lang=lang, valid=false}, Parser) + self._parser = vim._create_ts_parser(lang) self.change_cbs = {} self:parse() -- TODO(bfredl): use weakref to self, so that the parser is free'd is no plugin is @@ -93,11 +113,33 @@ end local Query = {} Query.__index = Query +local magic_prefixes = {['\\v']=true, ['\\m']=true, ['\\M']=true, ['\\V']=true} +local function check_magic(str) + if string.len(str) < 2 or magic_prefixes[string.sub(str,1,2)] then + return str + end + return '\\v'..str +end + function M.parse_query(lang, query) + M.require_language(lang) local self = setmetatable({}, Query) - self.query = vim._ts_parse_query(lang, query) + self.query = vim._ts_parse_query(lang, vim.fn.escape(query,'\\')) self.info = self.query:inspect() self.captures = self.info.captures + self.regexes = {} + for id,preds in pairs(self.info.patterns) do + local regexes = {} + for i, pred in ipairs(preds) do + if (pred[1] == "match?" and type(pred[2]) == "number" + and type(pred[3]) == "string") then + regexes[i] = vim.regex(check_magic(pred[3])) + end + end + if next(regexes) then + self.regexes[id] = regexes + end + end return self end @@ -110,8 +152,16 @@ local function get_node_text(node, bufnr) return string.sub(line, start_col+1, end_col) end -local function match_preds(match, preds, bufnr) - for _, pred in pairs(preds) do +function Query:match_preds(match, pattern, bufnr) + local preds = self.info.patterns[pattern] + if not preds then + return true + end + local regexes = self.regexes[pattern] + for i, pred in pairs(preds) do + -- Here we only want to return if a predicate DOES NOT match, and + -- continue on the other case. This way unknown predicates will not be considered, + -- which allows some testing and easier user extensibility (#12173). if pred[1] == "eq?" then local node = match[pred[2]] local node_text = get_node_text(node, bufnr) @@ -128,8 +178,18 @@ local function match_preds(match, preds, bufnr) if node_text ~= str or str == nil then return false end - else - return false + elseif pred[1] == "match?" then + if not regexes or not regexes[i] then + return false + end + local node = match[pred[2]] + local start_row, start_col, end_row, end_col = node:range() + if start_row ~= end_row then + return false + end + if not regexes[i]:match_line(bufnr, start_row, start_col, end_col) then + return false + end end end return true @@ -143,8 +203,7 @@ function Query:iter_captures(node, bufnr, start, stop) local function iter() local capture, captured_node, match = raw_iter() if match ~= nil then - local preds = self.info.patterns[match.pattern] - local active = match_preds(match, preds, bufnr) + local active = self:match_preds(match, match.pattern, bufnr) match.active = active if not active then return iter() -- tail call: try next match @@ -163,8 +222,7 @@ function Query:iter_matches(node, bufnr, start, stop) local function iter() local pattern, match = raw_iter() if match ~= nil then - local preds = self.info.patterns[pattern] - local active = (not preds) or match_preds(match, preds, bufnr) + local active = self:match_preds(match, pattern, bufnr) if not active then return iter() -- tail call: try next match end diff --git a/runtime/lua/vim/tshighlighter.lua b/runtime/lua/vim/tshighlighter.lua index 1544ecbf49..1440acf0d0 100644 --- a/runtime/lua/vim/tshighlighter.lua +++ b/runtime/lua/vim/tshighlighter.lua @@ -69,6 +69,8 @@ function TSHighlighter:set_query(query) end self.id_map[i] = hl end + + a.nvim__buf_redraw_range(self.buf, 0, a.nvim_buf_line_count(self.buf)) end function TSHighlighter:on_change(changes) @@ -85,7 +87,6 @@ end function TSHighlighter:on_window(_, _win, _buf, _topline, botline) self.iter = nil - self.active_nodes = {} self.nextrow = 0 self.botline = botline self.redraw_count = self.redraw_count + 1 @@ -107,34 +108,13 @@ function TSHighlighter:on_line(_, _win, buf, line) end local start_row, start_col, end_row, end_col = node:range() local hl = self.id_map[capture] - if hl > 0 and active then - if start_row == line and end_row == line then - a.nvim__put_attr(hl, start_col, end_col) - elseif end_row >= line then - -- TODO(bfredl): this is quite messy. Togheter with multiline bufhl we should support - -- luahl generating multiline highlights (and other kinds of annotations) - self.active_nodes[{hl=hl, start_row=start_row, start_col=start_col, end_row=end_row, end_col=end_col}] = true - end + if hl > 0 and active and end_row >= line then + a.nvim__put_attr(hl, start_row, start_col, end_row, end_col) end if start_row > line then self.nextrow = start_row end end - for node,_ in pairs(self.active_nodes) do - if node.start_row <= line and node.end_row >= line then - local start_col, end_col = node.start_col, node.end_col - if node.start_row < line then - start_col = 0 - end - if node.end_row > line then - end_col = 9000 - end - a.nvim__put_attr(node.hl, start_col, end_col) - end - if node.end_row <= line then - self.active_nodes[node] = nil - end - end self.line_count[line] = (self.line_count[line] or 0) + 1 --return tostring(self.line_count[line]) end diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua index 1065f84f4c..e28cc9e20f 100644 --- a/runtime/lua/vim/uri.lua +++ b/runtime/lua/vim/uri.lua @@ -66,10 +66,17 @@ local function uri_from_fname(path) end local function uri_from_bufnr(bufnr) - return uri_from_fname(vim.api.nvim_buf_get_name(bufnr)) + local fname = vim.api.nvim_buf_get_name(bufnr) + local scheme = fname:match("^([a-z]+)://.*") + if scheme then + return fname + else + return uri_from_fname(fname) + end end local function uri_to_fname(uri) + uri = uri_decode(uri) -- TODO improve this. if is_windows_file_uri(uri) then uri = uri:gsub('^file:///', '') @@ -77,12 +84,17 @@ local function uri_to_fname(uri) else uri = uri:gsub('^file://', '') end - return uri_decode(uri) + return uri end -- Return or create a buffer for a uri. local function uri_to_bufnr(uri) - return vim.fn.bufadd((uri_to_fname(uri))) + local scheme = assert(uri:match("^([a-z]+)://.*"), 'Uri must contain a scheme: ' .. uri) + if scheme == 'file' then + return vim.fn.bufadd(uri_to_fname(uri)) + else + return vim.fn.bufadd(uri) + end end return { diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim index 7a757ef7d6..aa2b69ad97 100644 --- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim +++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim @@ -37,7 +37,7 @@ " For neovim compatibility, the vim specific calls were replaced with neovim " specific calls: " term_start -> term_open -" term_sendkeys -> jobsend +" term_sendkeys -> chansend " term_getline -> getbufline " job_info && term_getjob -> using linux command ps to get the tty " balloon -> nvim floating window @@ -47,8 +47,6 @@ " https://github.com/autozimu/LanguageClient-neovim/blob/0ed9b69dca49c415390a8317b19149f97ae093fa/autoload/LanguageClient.vim#L304 " " Neovim terminal also works seamlessly on windows, which is why the ability -" to use the prompt buffer was removed. -" " Author: Bram Moolenaar " Copyright: Vim license applies, see ":help license" @@ -57,6 +55,12 @@ if exists(':Termdebug') finish endif +" The terminal feature does not work with gdb on win32. +if !has('win32') + let s:way = 'terminal' +else + let s:way = 'prompt' +endif let s:keepcpo = &cpo set cpo&vim @@ -138,7 +142,19 @@ func s:StartDebug_internal(dict) let s:vertical = 0 endif - call s:StartDebug_term(a:dict) + " Override using a terminal window by setting g:termdebug_use_prompt to 1. + let use_prompt = exists('g:termdebug_use_prompt') && g:termdebug_use_prompt + if !has('win32') && !use_prompt + let s:way = 'terminal' + else + let s:way = 'prompt' + endif + + if s:way == 'prompt' + call s:StartDebug_prompt(a:dict) + else + call s:StartDebug_term(a:dict) + endif endfunc " Use when debugger didn't start or ended. @@ -214,11 +230,11 @@ func s:StartDebug_term(dict) " Set arguments to be run if len(proc_args) - call jobsend(s:gdb_job_id, 'set args ' . join(proc_args) . "\r") + call chansend(s:gdb_job_id, 'set args ' . join(proc_args) . "\r") endif " Connect gdb to the communication pty, using the GDB/MI interface - call jobsend(s:gdb_job_id, 'new-ui mi ' . commpty . "\r") + call chansend(s:gdb_job_id, 'new-ui mi ' . commpty . "\r") " Wait for the response to show up, users may not notice the error and wonder " why the debugger doesn't work. @@ -275,6 +291,100 @@ func s:StartDebug_term(dict) call s:StartDebugCommon(a:dict) endfunc +func s:StartDebug_prompt(dict) + " Open a window with a prompt buffer to run gdb in. + if s:vertical + vertical new + else + new + endif + let s:gdbwin = win_getid(winnr()) + let s:promptbuf = bufnr('') + call prompt_setprompt(s:promptbuf, 'gdb> ') + set buftype=prompt + file gdb + call prompt_setcallback(s:promptbuf, function('s:PromptCallback')) + call prompt_setinterrupt(s:promptbuf, function('s:PromptInterrupt')) + + if s:vertical + " Assuming the source code window will get a signcolumn, use two more + " columns for that, thus one less for the terminal window. + exe (&columns / 2 - 1) . "wincmd |" + endif + + " Add -quiet to avoid the intro message causing a hit-enter prompt. + let gdb_args = get(a:dict, 'gdb_args', []) + let proc_args = get(a:dict, 'proc_args', []) + + let cmd = [g:termdebugger, '-quiet', '--interpreter=mi2'] + gdb_args + "call ch_log('executing "' . join(cmd) . '"') + + let s:gdbjob = jobstart(cmd, { + \ 'on_exit': function('s:EndPromptDebug'), + \ 'on_stdout': function('s:GdbOutCallback'), + \ }) + if s:gdbjob == 0 + echoerr 'invalid argument (or job table is full) while starting gdb job' + exe 'bwipe! ' . s:ptybuf + return + elseif s:gdbjob == -1 + echoerr 'Failed to start the gdb job' + call s:CloseBuffers() + return + endif + + " Interpret commands while the target is running. This should usualy only + " be exec-interrupt, since many commands don't work properly while the + " target is running. + call s:SendCommand('-gdb-set mi-async on') + " Older gdb uses a different command. + call s:SendCommand('-gdb-set target-async on') + + let s:ptybuf = 0 + if has('win32') + " MS-Windows: run in a new console window for maximum compatibility + call s:SendCommand('set new-console on') + else + " Unix: Run the debugged program in a terminal window. Open it below the + " gdb window. + execute 'new' + wincmd x | wincmd j + belowright let s:pty_job_id = termopen('tail -f /dev/null;#gdb program') + if s:pty_job_id == 0 + echoerr 'invalid argument (or job table is full) while opening terminal window' + return + elseif s:pty_job_id == -1 + echoerr 'Failed to open the program terminal window' + return + endif + let pty_job_info = nvim_get_chan_info(s:pty_job_id) + let s:ptybuf = pty_job_info['buffer'] + let pty = pty_job_info['pty'] + let s:ptywin = win_getid(winnr()) + call s:SendCommand('tty ' . pty) + + " Since GDB runs in a prompt window, the environment has not been set to + " match a terminal window, need to do that now. + call s:SendCommand('set env TERM = xterm-color') + call s:SendCommand('set env ROWS = ' . winheight(s:ptywin)) + call s:SendCommand('set env LINES = ' . winheight(s:ptywin)) + call s:SendCommand('set env COLUMNS = ' . winwidth(s:ptywin)) + call s:SendCommand('set env COLORS = ' . &t_Co) + call s:SendCommand('set env VIM_TERMINAL = ' . v:version) + endif + call s:SendCommand('set print pretty on') + call s:SendCommand('set breakpoint pending on') + " Disable pagination, it causes everything to stop at the gdb + call s:SendCommand('set pagination off') + + " Set arguments to be run + if len(proc_args) + call s:SendCommand('set args ' . join(proc_args)) + endif + + call s:StartDebugCommon(a:dict) + startinsert +endfunc func s:StartDebugCommon(dict) " Sign used to highlight the line where the program has stopped. @@ -316,23 +426,99 @@ endfunc " Send a command to gdb. "cmd" is the string without line terminator. func s:SendCommand(cmd) "call ch_log('sending to gdb: ' . a:cmd) - call jobsend(s:comm_job_id, a:cmd . "\r") + if s:way == 'prompt' + call chansend(s:gdbjob, a:cmd . "\n") + else + call chansend(s:comm_job_id, a:cmd . "\r") + endif endfunc " This is global so that a user can create their mappings with this. func TermDebugSendCommand(cmd) - let do_continue = 0 - if !s:stopped - let do_continue = 1 - call s:SendCommand('-exec-interrupt') - sleep 10m + if s:way == 'prompt' + call chansend(s:gdbjob, a:cmd . "\n") + else + let do_continue = 0 + if !s:stopped + let do_continue = 1 + if s:way == 'prompt' + " Need to send a signal to get the UI to listen. Strangely this is only + " needed once. + call jobstop(s:gdbjob) + else + call s:SendCommand('-exec-interrupt') + endif + sleep 10m + endif + call chansend(s:gdb_job_id, a:cmd . "\r") + if do_continue + Continue + endif endif - call jobsend(s:gdb_job_id, a:cmd . "\r") - if do_continue - Continue +endfunc + +" Function called when entering a line in the prompt buffer. +func s:PromptCallback(text) + call s:SendCommand(a:text) +endfunc + +" Function called when pressing CTRL-C in the prompt buffer and when placing a +" breakpoint. +func s:PromptInterrupt() + if s:pid == 0 + echoerr 'Cannot interrupt gdb, did not find a process ID' + else + "call ch_log('Interrupting gdb') + " Using job_stop(s:gdbjob, 'int') does not work. + call debugbreak(s:pid) endif endfunc +" Function called when gdb outputs text. +func s:GdbOutCallback(job_id, msgs, event) + "call ch_log('received from gdb: ' . a:text) + + " Drop the gdb prompt, we have our own. + " Drop status and echo'd commands. + call filter(a:msgs, { index, val -> + \ val !=# '(gdb)' && val !=# '^done' && val[0] !=# '&'}) + + let lines = [] + let index = 0 + + for msg in a:msgs + if msg =~ '^^error,msg=' + if exists('s:evalexpr') + \ && s:DecodeMessage(msg[11:]) + \ =~ 'A syntax error in expression, near\|No symbol .* in current context' + " Silently drop evaluation errors. + call remove(a:msgs, index) + unlet s:evalexpr + continue + endif + elseif msg[0] == '~' + call add(lines, s:DecodeMessage(msg[1:])) + call remove(a:msgs, index) + continue + endif + let index += 1 + endfor + + let curwinid = win_getid(winnr()) + call win_gotoid(s:gdbwin) + + " Add the output above the current prompt. + for line in lines + call append(line('$') - 1, line) + endfor + if !empty(lines) + set modified + endif + + call win_gotoid(curwinid) + call s:CommOutput(a:job_id, a:msgs, a:event) +endfunc + " Decode a message from gdb. quotedText starts with a ", return the text up " to the next ", unescaping characters. func s:DecodeMessage(quotedText) @@ -396,6 +582,19 @@ func s:EndDebugCommon() au! TermDebug endfunc +func s:EndPromptDebug(job_id, exit_code, event) + let curwinid = win_getid(winnr()) + call win_gotoid(s:gdbwin) + close + if curwinid != s:gdbwin + call win_gotoid(curwinid) + endif + + call s:EndDebugCommon() + unlet s:gdbwin + "call ch_log("Returning from EndPromptDebug()") +endfunc + func s:CommOutput(job_id, msgs, event) for msg in a:msgs @@ -436,7 +635,11 @@ func s:InstallCommands() command Stop call s:SendCommand('-exec-interrupt') " using -exec-continue results in CTRL-C in gdb window not working - command Continue call jobsend(s:gdb_job_id, "continue\r") + if s:way == 'prompt' + command Continue call s:SendCommand('continue') + else + command Continue call chansend(s:gdb_job_id, "continue\r") + endif command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>) command Gdb call win_gotoid(s:gdbwin) @@ -494,7 +697,11 @@ func s:SetBreakpoint() let do_continue = 0 if !s:stopped let do_continue = 1 - call s:SendCommand('-exec-interrupt') + if s:way == 'prompt' + call s:PromptInterrupt() + else + call s:SendCommand('-exec-interrupt') + endif sleep 10m endif " Use the fname:lnum format, older gdb can't handle --source. diff --git a/runtime/scripts.vim b/runtime/scripts.vim index a690431014..c552f0202f 100644 --- a/runtime/scripts.vim +++ b/runtime/scripts.vim @@ -376,6 +376,10 @@ else elseif s:line1 =~? '-\*-.*erlang.*-\*-' set ft=erlang + " YAML + elseif s:line1 =~# '^%YAML' + set ft=yaml + " CVS diff else let s:lnum = 1 diff --git a/runtime/syntax/tutor.vim b/runtime/syntax/tutor.vim index 6305eef734..83ca547fdd 100644 --- a/runtime/syntax/tutor.vim +++ b/runtime/syntax/tutor.vim @@ -33,16 +33,16 @@ syn keyword tutorMarks Todo Note Tip Excersise syn region tutorCodeblock matchgroup=Delimiter start=/^\~\{3}.*$/ end=/^\~\{3}/ -syn region tutorShell matchgroup=Delimiter start=/^\~\{3} sh\s*$/ end=/^\~\{3}/ keepend contains=@TUTORSHELL +syn region tutorShell matchgroup=Delimiter start=/^\~\{3} sh\s*$/ end=/^\~\{3}/ keepend contains=@TUTORSHELL concealends syn match tutorShellPrompt /\(^\s*\)\@<=[$#]/ contained containedin=tutorShell -syn region tutorInlineCode matchgroup=Delimiter start=/\\\@<!`/ end=/\\\@<!\(`{\@!\|`\s\)/ +syn region tutorInlineCode matchgroup=Delimiter start=/\\\@<!`/ end=/\\\@<!\(`{\@!\|`\s\)/ concealends -syn region tutorCommand matchgroup=Delimiter start=/^\~\{3} cmd\( :\)\?\s*$/ end=/^\~\{3}/ keepend contains=@VIM -syn region tutorInlineCommand matchgroup=Delimiter start=/\\\@<!`\(.*{vim}\)\@=/ end=/\\\@<!`\({vim}\)\@=/ nextgroup=tutorInlineType contains=@VIM +syn region tutorCommand matchgroup=Delimiter start=/^\~\{3} cmd\( :\)\?\s*$/ end=/^\~\{3}/ keepend contains=@VIM concealends +syn region tutorInlineCommand matchgroup=Delimiter start=/\\\@<!`\(.*`{vim}\)\@=/ end=/\\\@<!`\({vim}\)\@=/ nextgroup=tutorInlineType contains=@VIM concealends keepend -syn region tutorNormal matchgroup=Delimiter start=/^\~\{3} norm\(al\?\)\?\s*$/ end=/^\~\{3}/ contains=@VIMNORMAL -syn region tutorInlineNormal matchgroup=Delimiter start=/\\\@<!`\(\S*{normal}\)\@=/ end=/\\\@<!`\({normal}\)\@=/ nextgroup=tutorInlineType contains=@VIMNORMAL +syn region tutorNormal matchgroup=Delimiter start=/^\~\{3} norm\(al\?\)\?\s*$/ end=/^\~\{3}/ contains=@VIMNORMAL concealends +syn region tutorInlineNormal matchgroup=Delimiter start=/\\\@<!`\(\S*`{normal}\)\@=/ end=/\\\@<!`\({normal}\)\@=/ nextgroup=tutorInlineType contains=@VIMNORMAL concealends keepend syn match tutorInlineType /{\(normal\|vim\)}/ contained conceal diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index ecaddeacf6..a8b622d5c4 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -5,6 +5,12 @@ set -u # Use privileged mode, which e.g. skips using CDPATH. set -p +# Ensure that the user has a bash that supports -A +if [[ "${BASH_VERSINFO[0]}" -lt 4 ]]; then + echo "This script requires bash version 3 or later (you have ${BASH_VERSION})." >&2 + exit 1 +fi + readonly NVIM_SOURCE_DIR="${NVIM_SOURCE_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" readonly VIM_SOURCE_DIR_DEFAULT="${NVIM_SOURCE_DIR}/.vim-src" readonly VIM_SOURCE_DIR="${VIM_SOURCE_DIR:-${VIM_SOURCE_DIR_DEFAULT}}" @@ -674,9 +680,11 @@ review_pr() { echo echo "Downloading data for pull request #${pr}." - local pr_commit_urls=( - "$(curl -Ssf "https://api.github.com/repos/neovim/neovim/pulls/${pr}/commits" \ - | jq -r '.[].html_url')") + local -a pr_commit_urls + while IFS= read -r pr_commit_url; do + pr_commit_urls+=("$pr_commit_url") + done < <(curl -Ssf "https://api.github.com/repos/neovim/neovim/pulls/${pr}/commits" \ + | jq -r '.[].html_url') echo "Found ${#pr_commit_urls[@]} commit(s)." diff --git a/src/clint.py b/src/clint.py index 675b67ccef..8dc41fdb93 100755 --- a/src/clint.py +++ b/src/clint.py @@ -270,6 +270,8 @@ _line_length = 80 # This is set by --extensions flag. _valid_extensions = set(['c', 'h']) +_RE_COMMENTLINE = re.compile(r'^\s*//') + def ParseNolintSuppressions(filename, raw_line, linenum, error): """Updates the global list of error-suppressions. @@ -1358,7 +1360,9 @@ def CheckForOldStyleComments(filename, line, linenum, error): linenum: The number of the line to check. error: The function to call with any errors found. """ - if line.find('/*') >= 0 and line[-1] != '\\': + # hack: allow /* inside comment line. Could be extended to allow them inside + # any // comment. + if line.find('/*') >= 0 and line[-1] != '\\' and not _RE_COMMENTLINE.match(line): error(filename, linenum, 'readability/old_style_comment', 5, '/*-style comment found, it should be replaced with //-style. ' '/*-style comments are only allowed inside macros. ' @@ -1769,7 +1773,7 @@ def CheckSpacingForFunctionCall(filename, line, linenum, error): fncall = match.group(1) break - # Except in if/for/while/switch, there should never be space + # Except in if/for/while/switch/case, there should never be space # immediately inside parens (eg "f( 3, 4 )"). We make an exception # for nested parens ( (a+b) + c ). Likewise, there should never be # a space before a ( when it's a function argument. I assume it's a @@ -1783,7 +1787,7 @@ def CheckSpacingForFunctionCall(filename, line, linenum, error): # Note that we assume the contents of [] to be short enough that # they'll never need to wrap. if ( # Ignore control structures. - not Search(r'\b(if|for|while|switch|return|sizeof)\b', fncall) and + not Search(r'\b(if|for|while|switch|case|return|sizeof)\b', fncall) and # Ignore pointers/references to functions. not Search(r' \([^)]+\)\([^)]*(\)|,$)', fncall) and # Ignore pointers/references to arrays. diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 089dd537e9..982fab173f 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -23,6 +23,7 @@ endif() set(TOUCHES_DIR ${PROJECT_BINARY_DIR}/touches) set(GENERATOR_DIR ${CMAKE_CURRENT_LIST_DIR}/generators) set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto) +set(BINARY_LIB_DIR ${PROJECT_BINARY_DIR}/lib/nvim/) set(API_DISPATCH_GENERATOR ${GENERATOR_DIR}/gen_api_dispatch.lua) set(API_UI_EVENTS_GENERATOR ${GENERATOR_DIR}/gen_api_ui_events.lua) set(GENERATOR_C_GRAMMAR ${GENERATOR_DIR}/c_grammar.lua) @@ -149,6 +150,8 @@ set(CONV_SOURCES diff.c edit.c eval.c + eval/funcs.c + eval/userfunc.c ex_cmds.c ex_docmd.c fileio.c @@ -179,10 +182,10 @@ if(NOT MSVC) check_c_compiler_flag(-Wstatic-in-inline HAS_WSTATIC_IN_INLINE) if(HAS_WSTATIC_IN_INLINE) set_source_files_properties( - eval.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-static-in-inline -Wno-conversion") + eval/funcs.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-static-in-inline -Wno-conversion") else() set_source_files_properties( - eval.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion") + eval/funcs.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion") endif() # tree-sitter: inlined external project, we don't maintain it. #10124 @@ -218,7 +221,7 @@ set(gen_cflags ${gen_cflags} ${C_FLAGS_${build_type}_ARRAY} ${C_FLAGS_ARRAY}) function(get_preproc_output varname iname) if(MSVC) - set(${varname} /P /Fi${iname} PARENT_SCOPE) + set(${varname} /P /Fi${iname} /nologo PARENT_SCOPE) else() set(${varname} -E -o ${iname} PARENT_SCOPE) endif() @@ -546,6 +549,17 @@ else() endif() set_target_properties(nvim_runtime_deps PROPERTIES FOLDER deps) +file(MAKE_DIRECTORY ${BINARY_LIB_DIR}) + +# install treesitter parser if bundled +if(EXISTS ${DEPS_PREFIX}/lib/nvim/parser) + file(COPY ${DEPS_PREFIX}/lib/nvim/parser DESTINATION ${BINARY_LIB_DIR}) +endif() + +install(DIRECTORY ${BINARY_LIB_DIR} + DESTINATION ${CMAKE_INSTALL_LIBDIR}/nvim/ + USE_SOURCE_PERMISSIONS) + add_library( libnvim STATIC @@ -560,7 +574,6 @@ set_target_properties( libnvim PROPERTIES POSITION_INDEPENDENT_CODE ON - OUTPUT_NAME nvim ) set_property( TARGET libnvim diff --git a/src/nvim/README.md b/src/nvim/README.md index 3f7c05757a..d14ba85546 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -23,14 +23,18 @@ Logs Low-level log messages sink to `$NVIM_LOG_FILE`. -Use `LOG_CALLSTACK()` (Linux only) to log the current stacktrace. To log to an -alternate file (e.g. stderr) use `LOG_CALLSTACK_TO_FILE(FILE*)`. - UI events are logged at DEBUG level (`DEBUG_LOG_LEVEL`). rm -rf build/ make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0" +Use `LOG_CALLSTACK()` (Linux only) to log the current stacktrace. To log to an +alternate file (e.g. stderr) use `LOG_CALLSTACK_TO_FILE(FILE*)`. Requires +`-no-pie` ([ref](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=860394#15)): + + rm -rf build/ + make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0 -DCMAKE_C_FLAGS=-no-pie" + Many log messages have a shared prefix, such as "UI" or "RPC". Use the shell to filter the log, e.g. at DEBUG level you might want to exclude UI messages: diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index a666ed92da..b345dcaccd 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1123,7 +1123,7 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, } if (!ns_initialized((uint64_t)ns_id)) { - api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); + api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); return rv; } @@ -1190,7 +1190,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, } if (!ns_initialized((uint64_t)ns_id)) { - api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); + api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); return rv; } Integer limit = -1; @@ -1280,7 +1280,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, } if (!ns_initialized((uint64_t)ns_id)) { - api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); + api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); return 0; } @@ -1308,7 +1308,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, if (id >= 0) { id_num = (uint64_t)id; } else { - api_set_error(err, kErrorTypeValidation, _("Invalid mark id")); + api_set_error(err, kErrorTypeValidation, "Invalid mark id"); return 0; } @@ -1337,7 +1337,7 @@ Boolean nvim_buf_del_extmark(Buffer buffer, return false; } if (!ns_initialized((uint64_t)ns_id)) { - api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); + api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); return false; } @@ -1402,7 +1402,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, uint64_t ns_id = src2ns(&src_id); - if (!(0 <= line && line < buf->b_ml.ml_line_count)) { + if (!(line < buf->b_ml.ml_line_count)) { // safety check, we can't add marks outside the range return src_id; } @@ -1420,10 +1420,10 @@ Integer nvim_buf_add_highlight(Buffer buffer, end_line++; } - ns_id = extmark_add_decoration(buf, ns_id, hlg_id, - (int)line, (colnr_T)col_start, - end_line, (colnr_T)col_end, - VIRTTEXT_EMPTY); + extmark_add_decoration(buf, ns_id, hlg_id, + (int)line, (colnr_T)col_start, + end_line, (colnr_T)col_end, + VIRTTEXT_EMPTY); return src_id; } @@ -1655,7 +1655,7 @@ Integer nvim__buf_add_decoration(Buffer buffer, Integer ns_id, String hl_group, } if (!ns_initialized((uint64_t)ns_id)) { - api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); + api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); return 0; } diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index a1745ef777..a458762cc6 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1544,7 +1544,7 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int *col = MAXCOL; return true; } else if (id < 0) { - api_set_error(err, kErrorTypeValidation, _("Mark id must be positive")); + api_set_error(err, kErrorTypeValidation, "Mark id must be positive"); return false; } @@ -1554,7 +1554,7 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int *col = extmark.col; return true; } else { - api_set_error(err, kErrorTypeValidation, _("No mark with requested id")); + api_set_error(err, kErrorTypeValidation, "No mark with requested id"); return false; } @@ -1565,7 +1565,7 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int || pos.items[0].type != kObjectTypeInteger || pos.items[1].type != kObjectTypeInteger) { api_set_error(err, kErrorTypeValidation, - _("Position must have 2 integer elements")); + "Position must have 2 integer elements"); return false; } Integer pos_row = pos.items[0].data.integer; @@ -1575,7 +1575,7 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int return true; } else { api_set_error(err, kErrorTypeValidation, - _("Position must be a mark id Integer or position Array")); + "Position must be a mark id Integer or position Array"); return false; } } diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 048b937136..df3a263dcf 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -63,8 +63,8 @@ #define FIXED_TEMP_ARRAY(name, fixsize) \ Array name = ARRAY_DICT_INIT; \ Object name##__items[fixsize]; \ - args.size = fixsize; \ - args.items = name##__items; \ + name.size = fixsize; \ + name.items = name##__items; \ #define STATIC_CSTR_AS_STRING(s) ((String) {.data = s, .size = sizeof(s) - 1}) diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 75ee05761b..717713b948 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -109,7 +109,12 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, UI *ui = xcalloc(1, sizeof(UI)); ui->width = (int)width; ui->height = (int)height; - ui->pum_height = 0; + ui->pum_nlines = 0; + ui->pum_pos = false; + ui->pum_width = 0.0; + ui->pum_height = 0.0; + ui->pum_row = -1.0; + ui->pum_col = -1.0; ui->rgb = true; ui->override = false; ui->grid_resize = remote_ui_grid_resize; @@ -340,7 +345,56 @@ void nvim_ui_pum_set_height(uint64_t channel_id, Integer height, Error *err) "It must support the ext_popupmenu option"); return; } - ui->pum_height = (int)height; + + ui->pum_nlines = (int)height; +} + +/// Tells Nvim the geometry of the popumenu, to align floating windows with an +/// external popup menu. +/// +/// Note that this method is not to be confused with |nvim_ui_pum_set_height()|, +/// which sets the number of visible items in the popup menu, while this +/// function sets the bounding box of the popup menu, including visual +/// decorations such as boarders and sliders. Floats need not use the same font +/// size, nor be anchored to exact grid corners, so one can set floating-point +/// numbers to the popup menu geometry. +/// +/// @param channel_id +/// @param width Popupmenu width. +/// @param height Popupmenu height. +/// @param row Popupmenu row. +/// @param col Popupmenu height. +/// @param[out] err Error details, if any. +void nvim_ui_pum_set_bounds(uint64_t channel_id, Float width, Float height, + Float row, Float col, Error *err) + FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY +{ + if (!pmap_has(uint64_t)(connected_uis, channel_id)) { + api_set_error(err, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); + return; + } + + UI *ui = pmap_get(uint64_t)(connected_uis, channel_id); + if (!ui->ui_ext[kUIPopupmenu]) { + api_set_error(err, kErrorTypeValidation, + "UI must support the ext_popupmenu option"); + return; + } + + if (width <= 0) { + api_set_error(err, kErrorTypeValidation, "Expected width > 0"); + return; + } else if (height <= 0) { + api_set_error(err, kErrorTypeValidation, "Expected height > 0"); + return; + } + + ui->pum_row = (double)row; + ui->pum_col = (double)col; + ui->pum_width = (double)width; + ui->pum_height = (double)height; + ui->pum_pos = true; } /// Pushes data into UI.UIData, to be consumed later by remote_ui_flush(). diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 6677e248cf..ab31db39e9 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -115,6 +115,10 @@ void win_close(Integer grid) void msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char) FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL; +void win_viewport(Integer grid, Window win, Integer topline, + Integer botline, Integer curline, Integer curcol) + FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY; + void popupmenu_show(Array items, Integer selected, Integer row, Integer col, Integer grid) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 9c58ce853b..087ab37296 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -36,6 +36,7 @@ #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" +#include "nvim/eval/userfunc.h" #include "nvim/fileio.h" #include "nvim/ops.h" #include "nvim/option.h" @@ -253,7 +254,7 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi) if (execute) { int save_msg_scroll = msg_scroll; - /* Avoid a 1 second delay when the keys start Insert mode. */ + // Avoid a 1 second delay when the keys start Insert mode. msg_scroll = false; if (!dangerous) { ex_normal_busy++; @@ -703,6 +704,40 @@ ArrayOf(String) nvim_list_runtime_paths(void) return rv; } +/// Find files in runtime directories +/// +/// 'name' can contain wildcards. For example +/// nvim_get_runtime_file("colors/*.vim", true) will return all color +/// scheme files. +/// +/// It is not an error to not find any files. An empty array is returned then. +/// +/// @param name pattern of files to search for +/// @param all whether to return all matches or only the first +/// @return list of absolute paths to the found files +ArrayOf(String) nvim_get_runtime_file(String name, Boolean all) + FUNC_API_SINCE(7) +{ + Array rv = ARRAY_DICT_INIT; + if (!name.data) { + return rv; + } + int flags = DIP_START | (all ? DIP_ALL : 0); + do_in_runtimepath((char_u *)name.data, flags, find_runtime_cb, &rv); + return rv; +} + +static void find_runtime_cb(char_u *fname, void *cookie) +{ + Array *rv = (Array *)cookie; + ADD(*rv, STRING_OBJ(cstr_to_string((char *)fname))); +} + +String nvim__get_lib_dir(void) +{ + return cstr_as_string(get_lib_dir()); +} + /// Changes the global working directory. /// /// @param dir Directory path @@ -2564,7 +2599,8 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err) /// interface should probably be derived from a reformed /// bufhl/virttext interface with full support for multi-line /// ranges etc -void nvim__put_attr(Integer id, Integer c0, Integer c1) +void nvim__put_attr(Integer id, Integer start_row, Integer start_col, + Integer end_row, Integer end_col) FUNC_API_LUA_ONLY { if (!lua_attr_active) { @@ -2574,10 +2610,9 @@ void nvim__put_attr(Integer id, Integer c0, Integer c1) return; } int attr = syn_id2attr((int)id); - c0 = MAX(c0, 0); - c1 = MIN(c1, (Integer)lua_attr_bufsize); - for (Integer c = c0; c < c1; c++) { - lua_attr_buf[c] = (sattr_T)hl_combine_attr(lua_attr_buf[c], (int)attr); + if (attr == 0) { + return; } - return; + decorations_add_luahl_attr(attr, (int)start_row, (colnr_T)start_col, + (int)end_row, (colnr_T)end_col); } diff --git a/src/nvim/ascii.h b/src/nvim/ascii.h index ff6840d690..31423e79af 100644 --- a/src/nvim/ascii.h +++ b/src/nvim/ascii.h @@ -23,26 +23,28 @@ #define NL '\012' #define NL_STR "\012" #define FF '\014' -#define CAR '\015' /* CR is used by Mac OS X */ +#define CAR '\015' // CR is used by Mac OS X #define ESC '\033' #define ESC_STR "\033" #define DEL 0x7f #define DEL_STR "\177" #define CSI 0x9b // Control Sequence Introducer #define CSI_STR "\233" -#define DCS 0x90 /* Device Control String */ -#define STERM 0x9c /* String Terminator */ +#define DCS 0x90 // Device Control String +#define DCS_STR "\033P" +#define STERM 0x9c // String Terminator +#define STERM_STR "\033\\" #define POUND 0xA3 -#define Ctrl_chr(x) (TOUPPER_ASC(x) ^ 0x40) /* '?' -> DEL, '@' -> ^@, etc. */ +#define Ctrl_chr(x) (TOUPPER_ASC(x) ^ 0x40) // '?' -> DEL, '@' -> ^@, etc. #define Meta(x) ((x) | 0x80) #define CTRL_F_STR "\006" #define CTRL_H_STR "\010" #define CTRL_V_STR "\026" -#define Ctrl_AT 0 /* @ */ +#define Ctrl_AT 0 // @ #define Ctrl_A 1 #define Ctrl_B 2 #define Ctrl_C 3 @@ -69,16 +71,14 @@ #define Ctrl_X 24 #define Ctrl_Y 25 #define Ctrl_Z 26 -/* CTRL- [ Left Square Bracket == ESC*/ -#define Ctrl_BSL 28 /* \ BackSLash */ -#define Ctrl_RSB 29 /* ] Right Square Bracket */ -#define Ctrl_HAT 30 /* ^ */ +// CTRL- [ Left Square Bracket == ESC +#define Ctrl_BSL 28 // \ BackSLash +#define Ctrl_RSB 29 // ] Right Square Bracket +#define Ctrl_HAT 30 // ^ #define Ctrl__ 31 -/* - * Character that separates dir names in a path. - */ +// Character that separates dir names in a path. #ifdef BACKSLASH_IN_FILENAME # define PATHSEP psepc # define PATHSEPSTR pseps @@ -168,4 +168,4 @@ static inline bool ascii_isspace(int c) return (c >= 9 && c <= 13) || c == ' '; } -#endif /* NVIM_ASCII_H */ +#endif // NVIM_ASCII_H diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 96e170a9e1..4391d997a7 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -31,6 +31,7 @@ return { 'ColorSchemePre', -- before loading a colorscheme 'CompleteChanged', -- after popup menu changed 'CompleteDone', -- after finishing insert complete + 'CompleteDonePre', -- idem, before clearing info 'CursorHold', -- cursor in same position for a while 'CursorHoldI', -- idem, in Insert mode 'CursorMoved', -- cursor was moved diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 5083780719..7c8f93163a 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -763,6 +763,9 @@ static void free_buffer(buf_T *buf) unref_var_dict(buf->b_vars); aubuflocal_remove(buf); tv_dict_unref(buf->additional_data); + xfree(buf->b_prompt_text); + callback_free(&buf->b_prompt_callback); + callback_free(&buf->b_prompt_interrupt); clear_fmark(&buf->b_last_cursor); clear_fmark(&buf->b_last_insert); clear_fmark(&buf->b_last_change); @@ -1615,6 +1618,7 @@ void enter_buffer(buf_T *buf) if (!curbuf->b_help && curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) { (void)did_set_spelllang(curwin); } + curbuf->b_last_used = time(NULL); redraw_later(NOT_VALID); } @@ -1747,6 +1751,7 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags) apply_autocmds(EVENT_BUFWIPEOUT, NULL, NULL, false, curbuf); } if (aborting()) { // autocmds may abort script processing + xfree(ffname); return NULL; } if (buf == curbuf) { @@ -1876,6 +1881,10 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags) } } + buf->b_prompt_callback.type = kCallbackNone; + buf->b_prompt_interrupt.type = kCallbackNone; + buf->b_prompt_text = NULL; + return buf; } @@ -2242,6 +2251,23 @@ int buflist_findpat( return match; } +typedef struct { + buf_T *buf; + char_u *match; +} bufmatch_T; + +/// Compare functions for qsort() below, that compares b_last_used. +static int +buf_time_compare(const void *s1, const void *s2) +{ + buf_T *buf1 = *(buf_T **)s1; + buf_T *buf2 = *(buf_T **)s2; + + if (buf1->b_last_used == buf2->b_last_used) { + return 0; + } + return buf1->b_last_used > buf2->b_last_used ? -1 : 1; +} /* * Find all buffer names that match. @@ -2255,6 +2281,7 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) char_u *p; int attempt; char_u *patc; + bufmatch_T *matches = NULL; *num_file = 0; // return values in case of FAIL *file = NULL; @@ -2305,7 +2332,13 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) } else { p = vim_strsave(p); } - (*file)[count++] = p; + if (matches != NULL) { + matches[count].buf = buf; + matches[count].match = p; + count++; + } else { + (*file)[count++] = p; + } } } } @@ -2314,6 +2347,10 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) } if (round == 1) { *file = xmalloc((size_t)count * sizeof(**file)); + + if (options & WILD_BUFLASTUSED) { + matches = xmalloc((size_t)count * sizeof(*matches)); + } } } vim_regfree(regmatch.regprog); @@ -2326,6 +2363,25 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) xfree(patc); } + if (matches != NULL) { + if (count > 1) { + qsort(matches, (size_t)count, sizeof(bufmatch_T), buf_time_compare); + } + + // if the current buffer is first in the list, place it at the end + if (matches[0].buf == curbuf) { + for (int i = 1; i < count; i++) { + (*file)[i-1] = matches[i].match; + } + (*file)[count-1] = matches[0].match; + } else { + for (int i = 0; i < count; i++) { + (*file)[i] = matches[i].match; + } + } + xfree(matches); + } + *num_file = count; return count == 0 ? FAIL : OK; } @@ -2586,11 +2642,35 @@ linenr_T buflist_findlnum(buf_T *buf) // List all known file names (for :files and :buffers command). void buflist_list(exarg_T *eap) { - buf_T *buf; + buf_T *buf = firstbuf; int len; int i; - for (buf = firstbuf; buf != NULL && !got_int; buf = buf->b_next) { + garray_T buflist; + buf_T **buflist_data = NULL, **p; + + if (vim_strchr(eap->arg, 't')) { + ga_init(&buflist, sizeof(buf_T *), 50); + for (buf = firstbuf; buf != NULL; buf = buf->b_next) { + ga_grow(&buflist, 1); + ((buf_T **)buflist.ga_data)[buflist.ga_len++] = buf; + } + + qsort(buflist.ga_data, (size_t)buflist.ga_len, + sizeof(buf_T *), buf_time_compare); + + p = buflist_data = (buf_T **)buflist.ga_data; + buf = *p; + } + + for (; + buf != NULL && !got_int; + buf = buflist_data + ? (++p < buflist_data + buflist.ga_len ? *p : NULL) + : buf->b_next) { + const bool is_terminal = buf->terminal; + const bool job_running = buf->terminal && terminal_running(buf->terminal); + // skip unspecified buffers if ((!buf->b_p_bl && !eap->forceit && !strchr((char *)eap->arg, 'u')) || (strchr((char *)eap->arg, 'u') && buf->b_p_bl) @@ -2600,6 +2680,8 @@ void buflist_list(exarg_T *eap) && (buf->b_ml.ml_mfp == NULL || buf->b_nwindows == 0)) || (strchr((char *)eap->arg, 'h') && (buf->b_ml.ml_mfp == NULL || buf->b_nwindows != 0)) + || (strchr((char *)eap->arg, 'R') && (!is_terminal || !job_running)) + || (strchr((char *)eap->arg, 'F') && (!is_terminal || job_running)) || (strchr((char *)eap->arg, '-') && buf->b_p_ma) || (strchr((char *)eap->arg, '=') && !buf->b_p_ro) || (strchr((char *)eap->arg, 'x') && !(buf->b_flags & BF_READERR)) @@ -2646,13 +2728,22 @@ void buflist_list(exarg_T *eap) do { IObuff[len++] = ' '; } while (--i > 0 && len < IOSIZE - 18); - vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), - _("line %" PRId64), - buf == curbuf ? (int64_t)curwin->w_cursor.lnum - : (int64_t)buflist_findlnum(buf)); + if (vim_strchr(eap->arg, 't') && buf->b_last_used) { + add_time(IObuff + len, (size_t)(IOSIZE - len), buf->b_last_used); + } else { + vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), + _("line %" PRId64), + buf == curbuf ? (int64_t)curwin->w_cursor.lnum + : (int64_t)buflist_findlnum(buf)); + } + msg_outtrans(IObuff); line_breakcheck(); } + + if (buflist_data) { + ga_clear(&buflist); + } } /* @@ -3390,14 +3481,27 @@ int build_stl_str_hl( fillchar = '-'; } + // The cursor in windows other than the current one isn't always + // up-to-date, esp. because of autocommands and timers. + linenr_T lnum = wp->w_cursor.lnum; + if (lnum > wp->w_buffer->b_ml.ml_line_count) { + lnum = wp->w_buffer->b_ml.ml_line_count; + wp->w_cursor.lnum = lnum; + } + // Get line & check if empty (cursorpos will show "0-1"). - char_u *line_ptr = ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, false); + const char_u *line_ptr = ml_get_buf(wp->w_buffer, lnum, false); bool empty_line = (*line_ptr == NUL); // Get the byte value now, in case we need it below. This is more // efficient than making a copy of the line. int byteval; - if (wp->w_cursor.col > (colnr_T)STRLEN(line_ptr)) { + const size_t len = STRLEN(line_ptr); + if (wp->w_cursor.col > (colnr_T)len) { + // Line may have changed since checking the cursor column, or the lnum + // was adjusted above. + wp->w_cursor.col = (colnr_T)len; + wp->w_cursor.coladd = 0; byteval = 0; } else { byteval = utf_ptr2char(line_ptr + wp->w_cursor.col); @@ -3531,6 +3635,12 @@ int build_stl_str_hl( if (n == curitem && group_start_userhl == group_end_userhl) { out_p = t; group_len = 0; + // do not use the highlighting from the removed group + for (n = groupitems[groupdepth] + 1; n < curitem; n++) { + if (items[n].type == Highlight) { + items[n].type = Empty; + } + } } } @@ -4818,6 +4928,12 @@ do_arg_all( xfree(opened); } +// Return TRUE if "buf" is a prompt buffer. +int bt_prompt(buf_T *buf) +{ + return buf != NULL && buf->b_p_bt[0] == 'p'; +} + /* * Open a window for a number of buffers. */ @@ -5212,14 +5328,18 @@ bool bt_nofile(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f') - || buf->b_p_bt[0] == 'a' || buf->terminal); + || buf->b_p_bt[0] == 'a' + || buf->terminal + || buf->b_p_bt[0] == 'p'); } // Return true if "buf" is a "nowrite", "nofile" or "terminal" buffer. bool bt_dontwrite(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - return buf != NULL && (buf->b_p_bt[0] == 'n' || buf->terminal); + return buf != NULL && (buf->b_p_bt[0] == 'n' + || buf->terminal + || buf->b_p_bt[0] == 'p'); } bool bt_dontwrite_msg(const buf_T *const buf) @@ -5272,6 +5392,9 @@ char_u *buf_spname(buf_T *buf) if (buf->b_fname != NULL) { return buf->b_fname; } + if (bt_prompt(buf)) { + return (char_u *)_("[Prompt]"); + } return (char_u *)_("[Scratch]"); } if (buf->b_fname == NULL) { diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index a0379740b6..d696eedbb7 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -67,14 +67,14 @@ typedef struct { * off off w_botline not valid * on off not possible */ -#define VALID_WROW 0x01 /* w_wrow (window row) is valid */ -#define VALID_WCOL 0x02 /* w_wcol (window col) is valid */ -#define VALID_VIRTCOL 0x04 /* w_virtcol (file col) is valid */ -#define VALID_CHEIGHT 0x08 /* w_cline_height and w_cline_folded valid */ -#define VALID_CROW 0x10 /* w_cline_row is valid */ -#define VALID_BOTLINE 0x20 /* w_botine and w_empty_rows are valid */ -#define VALID_BOTLINE_AP 0x40 /* w_botine is approximated */ -#define VALID_TOPLINE 0x80 /* w_topline is valid (for cursor position) */ +#define VALID_WROW 0x01 // w_wrow (window row) is valid +#define VALID_WCOL 0x02 // w_wcol (window col) is valid +#define VALID_VIRTCOL 0x04 // w_virtcol (file col) is valid +#define VALID_CHEIGHT 0x08 // w_cline_height and w_cline_folded valid +#define VALID_CROW 0x10 // w_cline_row is valid +#define VALID_BOTLINE 0x20 // w_botine and w_empty_rows are valid +#define VALID_BOTLINE_AP 0x40 // w_botine is approximated +#define VALID_TOPLINE 0x80 // w_topline is valid (for cursor position) // flags for b_flags #define BF_RECOVERED 0x01 // buffer has been recovered @@ -92,7 +92,7 @@ typedef struct { #define BF_DUMMY 0x80 // dummy buffer, only used internally #define BF_PRESERVED 0x100 // ":preserve" was used -/* Mask to check for flags that prevent normal writing */ +// Mask to check for flags that prevent normal writing #define BF_WRITE_MASK (BF_NOTEDITED + BF_NEW + BF_READERR) typedef struct window_S win_T; @@ -160,90 +160,92 @@ typedef struct */ typedef struct { int wo_arab; -# define w_p_arab w_onebuf_opt.wo_arab /* 'arabic' */ +# define w_p_arab w_onebuf_opt.wo_arab // 'arabic' int wo_bri; # define w_p_bri w_onebuf_opt.wo_bri // 'breakindent' char_u *wo_briopt; -# define w_p_briopt w_onebuf_opt.wo_briopt /* 'breakindentopt' */ +# define w_p_briopt w_onebuf_opt.wo_briopt // 'breakindentopt' int wo_diff; -# define w_p_diff w_onebuf_opt.wo_diff /* 'diff' */ - long wo_fdc; -# define w_p_fdc w_onebuf_opt.wo_fdc /* 'foldcolumn' */ - int wo_fdc_save; -# define w_p_fdc_save w_onebuf_opt.wo_fdc_save /* 'foldenable' saved for diff mode */ +# define w_p_diff w_onebuf_opt.wo_diff // 'diff' + char_u *wo_fdc; +# define w_p_fdc w_onebuf_opt.wo_fdc // 'foldcolumn' + char_u *wo_fdc_save; +# define w_p_fdc_save w_onebuf_opt.wo_fdc_save // 'fdc' saved for diff mode int wo_fen; -# define w_p_fen w_onebuf_opt.wo_fen /* 'foldenable' */ +# define w_p_fen w_onebuf_opt.wo_fen // 'foldenable' int wo_fen_save; -# define w_p_fen_save w_onebuf_opt.wo_fen_save /* 'foldenable' saved for diff mode */ + // 'foldenable' saved for diff mode +# define w_p_fen_save w_onebuf_opt.wo_fen_save char_u *wo_fdi; -# define w_p_fdi w_onebuf_opt.wo_fdi /* 'foldignore' */ +# define w_p_fdi w_onebuf_opt.wo_fdi // 'foldignore' long wo_fdl; -# define w_p_fdl w_onebuf_opt.wo_fdl /* 'foldlevel' */ +# define w_p_fdl w_onebuf_opt.wo_fdl // 'foldlevel' int wo_fdl_save; -# define w_p_fdl_save w_onebuf_opt.wo_fdl_save /* 'foldlevel' state saved for diff mode */ + // 'foldlevel' state saved for diff mode +# define w_p_fdl_save w_onebuf_opt.wo_fdl_save char_u *wo_fdm; -# define w_p_fdm w_onebuf_opt.wo_fdm /* 'foldmethod' */ +# define w_p_fdm w_onebuf_opt.wo_fdm // 'foldmethod' char_u *wo_fdm_save; -# define w_p_fdm_save w_onebuf_opt.wo_fdm_save /* 'fdm' saved for diff mode */ +# define w_p_fdm_save w_onebuf_opt.wo_fdm_save // 'fdm' saved for diff mode long wo_fml; -# define w_p_fml w_onebuf_opt.wo_fml /* 'foldminlines' */ +# define w_p_fml w_onebuf_opt.wo_fml // 'foldminlines' long wo_fdn; -# define w_p_fdn w_onebuf_opt.wo_fdn /* 'foldnestmax' */ +# define w_p_fdn w_onebuf_opt.wo_fdn // 'foldnestmax' char_u *wo_fde; -# define w_p_fde w_onebuf_opt.wo_fde /* 'foldexpr' */ +# define w_p_fde w_onebuf_opt.wo_fde // 'foldexpr' char_u *wo_fdt; -# define w_p_fdt w_onebuf_opt.wo_fdt /* 'foldtext' */ +# define w_p_fdt w_onebuf_opt.wo_fdt // 'foldtext' char_u *wo_fmr; -# define w_p_fmr w_onebuf_opt.wo_fmr /* 'foldmarker' */ +# define w_p_fmr w_onebuf_opt.wo_fmr // 'foldmarker' int wo_lbr; -# define w_p_lbr w_onebuf_opt.wo_lbr /* 'linebreak' */ +# define w_p_lbr w_onebuf_opt.wo_lbr // 'linebreak' int wo_list; -#define w_p_list w_onebuf_opt.wo_list /* 'list' */ +#define w_p_list w_onebuf_opt.wo_list // 'list' int wo_nu; -#define w_p_nu w_onebuf_opt.wo_nu /* 'number' */ +#define w_p_nu w_onebuf_opt.wo_nu // 'number' int wo_rnu; -#define w_p_rnu w_onebuf_opt.wo_rnu /* 'relativenumber' */ +#define w_p_rnu w_onebuf_opt.wo_rnu // 'relativenumber' long wo_nuw; -# define w_p_nuw w_onebuf_opt.wo_nuw /* 'numberwidth' */ +# define w_p_nuw w_onebuf_opt.wo_nuw // 'numberwidth' int wo_wfh; -# define w_p_wfh w_onebuf_opt.wo_wfh /* 'winfixheight' */ +# define w_p_wfh w_onebuf_opt.wo_wfh // 'winfixheight' int wo_wfw; -# define w_p_wfw w_onebuf_opt.wo_wfw /* 'winfixwidth' */ +# define w_p_wfw w_onebuf_opt.wo_wfw // 'winfixwidth' int wo_pvw; -# define w_p_pvw w_onebuf_opt.wo_pvw /* 'previewwindow' */ +# define w_p_pvw w_onebuf_opt.wo_pvw // 'previewwindow' int wo_rl; -# define w_p_rl w_onebuf_opt.wo_rl /* 'rightleft' */ +# define w_p_rl w_onebuf_opt.wo_rl // 'rightleft' char_u *wo_rlc; -# define w_p_rlc w_onebuf_opt.wo_rlc /* 'rightleftcmd' */ +# define w_p_rlc w_onebuf_opt.wo_rlc // 'rightleftcmd' long wo_scr; -#define w_p_scr w_onebuf_opt.wo_scr /* 'scroll' */ +#define w_p_scr w_onebuf_opt.wo_scr // 'scroll' int wo_spell; -# define w_p_spell w_onebuf_opt.wo_spell /* 'spell' */ +# define w_p_spell w_onebuf_opt.wo_spell // 'spell' int wo_cuc; -# define w_p_cuc w_onebuf_opt.wo_cuc /* 'cursorcolumn' */ +# define w_p_cuc w_onebuf_opt.wo_cuc // 'cursorcolumn' int wo_cul; -# define w_p_cul w_onebuf_opt.wo_cul /* 'cursorline' */ +# define w_p_cul w_onebuf_opt.wo_cul // 'cursorline' char_u *wo_cc; -# define w_p_cc w_onebuf_opt.wo_cc /* 'colorcolumn' */ +# define w_p_cc w_onebuf_opt.wo_cc // 'colorcolumn' char_u *wo_stl; -#define w_p_stl w_onebuf_opt.wo_stl /* 'statusline' */ +#define w_p_stl w_onebuf_opt.wo_stl // 'statusline' int wo_scb; -# define w_p_scb w_onebuf_opt.wo_scb /* 'scrollbind' */ - int wo_diff_saved; /* options were saved for starting diff mode */ +# define w_p_scb w_onebuf_opt.wo_scb // 'scrollbind' + int wo_diff_saved; // options were saved for starting diff mode # define w_p_diff_saved w_onebuf_opt.wo_diff_saved - int wo_scb_save; /* 'scrollbind' saved for diff mode*/ + int wo_scb_save; // 'scrollbind' saved for diff mode # define w_p_scb_save w_onebuf_opt.wo_scb_save int wo_wrap; -#define w_p_wrap w_onebuf_opt.wo_wrap /* 'wrap' */ - int wo_wrap_save; /* 'wrap' state saved for diff mode*/ +#define w_p_wrap w_onebuf_opt.wo_wrap // 'wrap' + int wo_wrap_save; // 'wrap' state saved for diff mode # define w_p_wrap_save w_onebuf_opt.wo_wrap_save - char_u *wo_cocu; /* 'concealcursor' */ + char_u *wo_cocu; // 'concealcursor' # define w_p_cocu w_onebuf_opt.wo_cocu - long wo_cole; /* 'conceallevel' */ + long wo_cole; // 'conceallevel' # define w_p_cole w_onebuf_opt.wo_cole int wo_crb; -# define w_p_crb w_onebuf_opt.wo_crb /* 'cursorbind' */ - int wo_crb_save; /* 'cursorbind' state saved for diff mode*/ +# define w_p_crb w_onebuf_opt.wo_crb // 'cursorbind' + int wo_crb_save; // 'cursorbind' state saved for diff mode # define w_p_crb_save w_onebuf_opt.wo_crb_save char_u *wo_scl; # define w_p_scl w_onebuf_opt.wo_scl // 'signcolumn' @@ -271,14 +273,14 @@ typedef struct { * most-recently-used order. */ struct wininfo_S { - wininfo_T *wi_next; /* next entry or NULL for last entry */ - wininfo_T *wi_prev; /* previous entry or NULL for first entry */ - win_T *wi_win; /* pointer to window that did set wi_fpos */ - pos_T wi_fpos; /* last cursor position in the file */ - bool wi_optset; /* true when wi_opt has useful values */ - winopt_T wi_opt; /* local window options */ - bool wi_fold_manual; /* copy of w_fold_manual */ - garray_T wi_folds; /* clone of w_folds */ + wininfo_T *wi_next; // next entry or NULL for last entry + wininfo_T *wi_prev; // previous entry or NULL for first entry + win_T *wi_win; // pointer to window that did set wi_fpos + pos_T wi_fpos; // last cursor position in the file + bool wi_optset; // true when wi_opt has useful values + winopt_T wi_opt; // local window options + bool wi_fold_manual; // copy of w_fold_manual + garray_T wi_folds; // clone of w_folds }; /* @@ -288,8 +290,8 @@ struct wininfo_S { * TODO: move struct arglist to another header */ typedef struct arglist { - garray_T al_ga; /* growarray with the array of file names */ - int al_refcount; /* number of windows using this arglist */ + garray_T al_ga; // growarray with the array of file names + int al_refcount; // number of windows using this arglist int id; ///< id of this arglist } alist_T; @@ -301,8 +303,8 @@ typedef struct arglist { * TODO: move aentry_T to another header */ typedef struct argentry { - char_u *ae_fname; /* file name as specified */ - int ae_fnum; /* buffer number with expanded file name */ + char_u *ae_fname; // file name as specified + int ae_fnum; // buffer number with expanded file name } aentry_T; # define ALIST(win) (win)->w_alist @@ -318,21 +320,21 @@ typedef struct argentry { * Used for the typeahead buffer: typebuf. */ typedef struct { - char_u *tb_buf; /* buffer for typed characters */ - char_u *tb_noremap; /* mapping flags for characters in tb_buf[] */ - int tb_buflen; /* size of tb_buf[] */ - int tb_off; /* current position in tb_buf[] */ - int tb_len; /* number of valid bytes in tb_buf[] */ - int tb_maplen; /* nr of mapped bytes in tb_buf[] */ - int tb_silent; /* nr of silently mapped bytes in tb_buf[] */ - int tb_no_abbr_cnt; /* nr of bytes without abbrev. in tb_buf[] */ - int tb_change_cnt; /* nr of time tb_buf was changed; never zero */ + char_u *tb_buf; // buffer for typed characters + char_u *tb_noremap; // mapping flags for characters in tb_buf[] + int tb_buflen; // size of tb_buf[] + int tb_off; // current position in tb_buf[] + int tb_len; // number of valid bytes in tb_buf[] + int tb_maplen; // nr of mapped bytes in tb_buf[] + int tb_silent; // nr of silently mapped bytes in tb_buf[] + int tb_no_abbr_cnt; // nr of bytes without abbrev. in tb_buf[] + int tb_change_cnt; // nr of time tb_buf was changed; never zero } typebuf_T; -/* Struct to hold the saved typeahead for save_typeahead(). */ +// Struct to hold the saved typeahead for save_typeahead(). typedef struct { typebuf_T save_typebuf; - int typebuf_valid; /* TRUE when save_typebuf valid */ + bool typebuf_valid; // true when save_typebuf valid int old_char; int old_mod_mask; buffheader_T save_readbuf1; @@ -363,15 +365,15 @@ struct mapblock { */ struct stl_hlrec { char_u *start; - int userhl; /* 0: no HL, 1-9: User HL, < 0 for syn ID */ + int userhl; // 0: no HL, 1-9: User HL, < 0 for syn ID }; -/* values for b_syn_spell: what to do with toplevel text */ -#define SYNSPL_DEFAULT 0 /* spell check if @Spell not defined */ -#define SYNSPL_TOP 1 /* spell check toplevel text */ -#define SYNSPL_NOTOP 2 /* don't spell check toplevel text */ +// values for b_syn_spell: what to do with toplevel text +#define SYNSPL_DEFAULT 0 // spell check if @Spell not defined +#define SYNSPL_TOP 1 // spell check toplevel text +#define SYNSPL_NOTOP 2 // don't spell check toplevel text -/* avoid #ifdefs for when b_spell is not available */ +// avoid #ifdefs for when b_spell is not available # define B_SPELL(buf) ((buf)->b_spell) typedef struct qf_info_S qf_info_T; @@ -380,10 +382,10 @@ typedef struct qf_info_S qf_info_T; * Used for :syntime: timing of executing a syntax pattern. */ typedef struct { - proftime_T total; /* total time used */ - proftime_T slowest; /* time of slowest call */ - long count; /* nr of times used */ - long match; /* nr of times matched */ + proftime_T total; // total time used + proftime_T slowest; // time of slowest call + long count; // nr of times used + long match; // nr of times matched } syn_time_T; /* @@ -411,25 +413,23 @@ typedef struct { char_u *b_syn_linecont_pat; // line continuation pattern regprog_T *b_syn_linecont_prog; // line continuation program syn_time_T b_syn_linecont_time; - int b_syn_linecont_ic; /* ignore-case flag for above */ - int b_syn_topgrp; /* for ":syntax include" */ - int b_syn_conceal; /* auto-conceal for :syn cmds */ - int b_syn_folditems; /* number of patterns with the HL_FOLD - flag set */ - /* - * b_sst_array[] contains the state stack for a number of lines, for the - * start of that line (col == 0). This avoids having to recompute the - * syntax state too often. - * b_sst_array[] is allocated to hold the state for all displayed lines, - * and states for 1 out of about 20 other lines. - * b_sst_array pointer to an array of synstate_T - * b_sst_len number of entries in b_sst_array[] - * b_sst_first pointer to first used entry in b_sst_array[] or NULL - * b_sst_firstfree pointer to first free entry in b_sst_array[] or NULL - * b_sst_freecount number of free entries in b_sst_array[] - * b_sst_check_lnum entries after this lnum need to be checked for - * validity (MAXLNUM means no check needed) - */ + int b_syn_linecont_ic; // ignore-case flag for above + int b_syn_topgrp; // for ":syntax include" + int b_syn_conceal; // auto-conceal for :syn cmds + int b_syn_folditems; // number of patterns with the HL_FOLD + // flag set + // b_sst_array[] contains the state stack for a number of lines, for the + // start of that line (col == 0). This avoids having to recompute the + // syntax state too often. + // b_sst_array[] is allocated to hold the state for all displayed lines, + // and states for 1 out of about 20 other lines. + // b_sst_array pointer to an array of synstate_T + // b_sst_len number of entries in b_sst_array[] + // b_sst_first pointer to first used entry in b_sst_array[] or NULL + // b_sst_firstfree pointer to first free entry in b_sst_array[] or NULL + // b_sst_freecount number of free entries in b_sst_array[] + // b_sst_check_lnum entries after this lnum need to be checked for + // validity (MAXLNUM means no check needed) synstate_T *b_sst_array; int b_sst_len; synstate_T *b_sst_first; @@ -488,10 +488,10 @@ struct file_buffer { memline_T b_ml; // associated memline (also contains line count - buf_T *b_next; /* links in list of buffers */ + buf_T *b_next; // links in list of buffers buf_T *b_prev; - int b_nwindows; /* nr of windows open on this buffer */ + int b_nwindows; // nr of windows open on this buffer int b_flags; // various BF_ flags int b_locked; // Buffer is being closed or referenced, don't @@ -532,24 +532,25 @@ struct file_buffer { */ bool b_mod_set; /* true when there are changes since the last time the display was updated */ - linenr_T b_mod_top; /* topmost lnum that was changed */ - linenr_T b_mod_bot; /* lnum below last changed line, AFTER the - change */ - long b_mod_xlines; /* number of extra buffer lines inserted; - negative when lines were deleted */ - - wininfo_T *b_wininfo; /* list of last used info for each window */ - - long b_mtime; /* last change time of original file */ - long b_mtime_read; /* last change time when reading */ - uint64_t b_orig_size; /* size of original file in bytes */ - int b_orig_mode; /* mode of original file */ - - fmark_T b_namedm[NMARKS]; /* current named marks (mark.c) */ - - /* These variables are set when VIsual_active becomes FALSE */ + linenr_T b_mod_top; // topmost lnum that was changed + linenr_T b_mod_bot; // lnum below last changed line, AFTER the + // change + long b_mod_xlines; // number of extra buffer lines inserted; + // negative when lines were deleted + wininfo_T *b_wininfo; // list of last used info for each window + + long b_mtime; // last change time of original file + long b_mtime_read; // last change time when reading + uint64_t b_orig_size; // size of original file in bytes + int b_orig_mode; // mode of original file + time_t b_last_used; // time when the buffer was last used; used + // for viminfo + + fmark_T b_namedm[NMARKS]; // current named marks (mark.c) + + // These variables are set when VIsual_active becomes FALSE visualinfo_T b_visual; - int b_visual_mode_eval; /* b_visual.vi_mode for visualmode() */ + int b_visual_mode_eval; // b_visual.vi_mode for visualmode() fmark_T b_last_cursor; // cursor position when last unloading this // buffer @@ -560,8 +561,8 @@ struct file_buffer { * the changelist contains old change positions */ fmark_T b_changelist[JUMPLISTSIZE]; - int b_changelistlen; /* number of active entries */ - bool b_new_change; /* set by u_savecommon() */ + int b_changelistlen; // number of active entries + bool b_new_change; // set by u_savecommon() /* * Character table, only used in charset.c for 'iskeyword' @@ -572,9 +573,9 @@ struct file_buffer { // Table used for mappings local to a buffer. mapblock_T *(b_maphash[MAX_MAPHASH]); - /* First abbreviation local to a buffer. */ + // First abbreviation local to a buffer. mapblock_T *b_first_abbr; - /* User commands local to the buffer. */ + // User commands local to the buffer. garray_T b_ucmds; /* * start and end of an operator, also used for '[ and '] @@ -583,31 +584,31 @@ struct file_buffer { pos_T b_op_start_orig; // used for Insstart_orig pos_T b_op_end; - bool b_marks_read; /* Have we read ShaDa marks yet? */ + bool b_marks_read; // Have we read ShaDa marks yet? /* * The following only used in undo.c. */ - u_header_T *b_u_oldhead; /* pointer to oldest header */ - u_header_T *b_u_newhead; /* pointer to newest header; may not be valid - if b_u_curhead is not NULL */ - u_header_T *b_u_curhead; /* pointer to current header */ - int b_u_numhead; /* current number of headers */ - bool b_u_synced; /* entry lists are synced */ - long b_u_seq_last; /* last used undo sequence number */ - long b_u_save_nr_last; /* counter for last file write */ - long b_u_seq_cur; /* hu_seq of header below which we are now */ - time_t b_u_time_cur; /* uh_time of header below which we are now */ - long b_u_save_nr_cur; /* file write nr after which we are now */ + u_header_T *b_u_oldhead; // pointer to oldest header + u_header_T *b_u_newhead; // pointer to newest header; may not be valid + // if b_u_curhead is not NULL + u_header_T *b_u_curhead; // pointer to current header + int b_u_numhead; // current number of headers + bool b_u_synced; // entry lists are synced + long b_u_seq_last; // last used undo sequence number + long b_u_save_nr_last; // counter for last file write + long b_u_seq_cur; // hu_seq of header below which we are now + time_t b_u_time_cur; // uh_time of header below which we are now + long b_u_save_nr_cur; // file write nr after which we are now /* * variables for "U" command in undo.c */ - char_u *b_u_line_ptr; /* saved line for "U" command */ - linenr_T b_u_line_lnum; /* line number of line in u_line */ - colnr_T b_u_line_colnr; /* optional column number */ + char_u *b_u_line_ptr; // saved line for "U" command + linenr_T b_u_line_lnum; // line number of line in u_line + colnr_T b_u_line_colnr; // optional column number - bool b_scanned; /* ^N/^P have scanned this buffer */ + bool b_scanned; // ^N/^P have scanned this buffer // flags for use of ":lmap" and IM control long b_p_iminsert; // input mode for insert @@ -617,10 +618,10 @@ struct file_buffer { #define B_IMODE_LMAP 1 // Input via langmap # define B_IMODE_LAST 1 - short b_kmap_state; /* using "lmap" mappings */ -# define KEYMAP_INIT 1 /* 'keymap' was set, call keymap_init() */ -# define KEYMAP_LOADED 2 /* 'keymap' mappings have been loaded */ - garray_T b_kmap_ga; /* the keymap table */ + int16_t b_kmap_state; // using "lmap" mappings +# define KEYMAP_INIT 1 // 'keymap' was set, call keymap_init() +# define KEYMAP_LOADED 2 // 'keymap' mappings have been loaded + garray_T b_kmap_ga; // the keymap table /* * Options local to a buffer. @@ -720,9 +721,9 @@ struct file_buffer { int b_p_udf; ///< 'undofile' char_u *b_p_lw; ///< 'lispwords' local value - /* end of buffer options */ + // end of buffer options - /* values set from b_p_cino */ + // values set from b_p_cino int b_ind_level; int b_ind_open_imag; int b_ind_no_brace; @@ -763,11 +764,11 @@ struct file_buffer { linenr_T b_no_eol_lnum; /* non-zero lnum when last line of next binary * write should not have an end-of-line */ - int b_start_eol; /* last line had eol when it was read */ - int b_start_ffc; /* first char of 'ff' when edit started */ - char_u *b_start_fenc; /* 'fileencoding' when edit started or NULL */ - int b_bad_char; /* "++bad=" argument when edit started or 0 */ - int b_start_bomb; /* 'bomb' when it was read */ + int b_start_eol; // last line had eol when it was read + int b_start_ffc; // first char of 'ff' when edit started + char_u *b_start_fenc; // 'fileencoding' when edit started or NULL + int b_bad_char; // "++bad=" argument when edit started or 0 + int b_start_bomb; // 'bomb' when it was read ScopeDictDictItem b_bufvar; ///< Variable for "b:" Dictionary. dict_T *b_vars; ///< b: scope dictionary. @@ -791,6 +792,12 @@ struct file_buffer { // are not used! Use the B_SPELL macro to // access b_spell without #ifdef. + char_u *b_prompt_text; // set by prompt_setprompt() + Callback b_prompt_callback; // set by prompt_setcallback() + Callback b_prompt_interrupt; // set by prompt_setinterrupt() + int b_prompt_insert; // value for restart_edit when entering + // a prompt buffer window. + synblock_T b_s; // Info related to syntax highlighting. w_s // normally points to this, but some windows // may use a different synblock_T. @@ -860,8 +867,8 @@ struct file_buffer { typedef struct diffblock_S diff_T; struct diffblock_S { diff_T *df_next; - linenr_T df_lnum[DB_COUNT]; /* line number in buffer */ - linenr_T df_count[DB_COUNT]; /* nr of inserted/changed lines */ + linenr_T df_lnum[DB_COUNT]; // line number in buffer + linenr_T df_count[DB_COUNT]; // nr of inserted/changed lines }; #define SNAP_HELP_IDX 0 @@ -909,11 +916,11 @@ struct tabpage_S { * wl_lnum and wl_lastlnum are invalid too. */ typedef struct w_line { - linenr_T wl_lnum; /* buffer line number for logical line */ - uint16_t wl_size; /* height in screen lines */ - char wl_valid; /* TRUE values are valid for text in buffer */ - char wl_folded; /* TRUE when this is a range of folded lines */ - linenr_T wl_lastlnum; /* last buffer line number for logical line */ + linenr_T wl_lnum; // buffer line number for logical line + uint16_t wl_size; // height in screen lines + char wl_valid; // TRUE values are valid for text in buffer + char wl_folded; // TRUE when this is a range of folded lines + linenr_T wl_lastlnum; // last buffer line number for logical line } wline_T; /* @@ -936,9 +943,9 @@ struct frame_S { win_T *fr_win; // window that fills this frame }; -#define FR_LEAF 0 /* frame is a leaf */ -#define FR_ROW 1 /* frame with a row of windows */ -#define FR_COL 2 /* frame with a column of windows */ +#define FR_LEAF 0 // frame is a leaf +#define FR_ROW 1 // frame with a row of windows +#define FR_COL 2 // frame with a column of windows /* * Struct used for highlighting 'hlsearch' matches, matches defined by @@ -1055,6 +1062,48 @@ typedef struct pos_T w_cursor_corr; // corrected cursor position } pos_save_T; +/// Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode +/// \addtogroup MENU_INDEX +/// @{ +enum { + MENU_INDEX_INVALID = -1, + MENU_INDEX_NORMAL = 0, + MENU_INDEX_VISUAL = 1, + MENU_INDEX_SELECT = 2, + MENU_INDEX_OP_PENDING = 3, + MENU_INDEX_INSERT = 4, + MENU_INDEX_CMDLINE = 5, + MENU_INDEX_TIP = 6, + MENU_MODES = 7, +}; + +typedef struct VimMenu vimmenu_T; + +struct VimMenu { + int modes; ///< Which modes is this menu visible for + int enabled; ///< for which modes the menu is enabled + char_u *name; ///< Name of menu, possibly translated + char_u *dname; ///< Displayed Name ("name" without '&') + char_u *en_name; ///< "name" untranslated, NULL when + ///< was not translated + char_u *en_dname; ///< NULL when "dname" untranslated + int mnemonic; ///< mnemonic key (after '&') + char_u *actext; ///< accelerator text (after TAB) + long priority; ///< Menu order priority + char_u *strings[MENU_MODES]; ///< Mapped string for each mode + int noremap[MENU_MODES]; ///< A \ref REMAP_VALUES flag for each mode + bool silent[MENU_MODES]; ///< A silent flag for each mode + vimmenu_T *children; ///< Children of sub-menu + vimmenu_T *parent; ///< Parent of menu + vimmenu_T *next; ///< Next item in menu +}; + +typedef struct { + int wb_startcol; + int wb_endcol; + vimmenu_T *wb_menu; +} winbar_item_T; + /// Structure which contains all information that belongs to a window. /// /// All row numbers are relative to the start of the window, except w_winrow. @@ -1139,16 +1188,16 @@ struct window_S { top of the window */ char w_topline_was_set; /* flag set to TRUE when topline is set, e.g. by winrestview() */ - int w_topfill; /* number of filler lines above w_topline */ - int w_old_topfill; /* w_topfill at last redraw */ - bool w_botfill; /* true when filler lines are actually - below w_topline (at end of file) */ - bool w_old_botfill; /* w_botfill at last redraw */ - colnr_T w_leftcol; /* window column number of the left most - character in the window; used when - 'wrap' is off */ - colnr_T w_skipcol; /* starting column when a single line - doesn't fit in the window */ + int w_topfill; // number of filler lines above w_topline + int w_old_topfill; // w_topfill at last redraw + bool w_botfill; // true when filler lines are actually + // below w_topline (at end of file) + bool w_old_botfill; // w_botfill at last redraw + colnr_T w_leftcol; // window column number of the left most + // character in the window; used when + // 'wrap' is off + colnr_T w_skipcol; // starting column when a single line + // doesn't fit in the window // // Layout of the window in the screen. @@ -1156,7 +1205,7 @@ struct window_S { // int w_winrow; // first row of window in screen int w_height; // number of rows in window, excluding - // status/command line(s) + // status/command/winbar line(s) int w_status_height; // number of status lines (0 or 1) int w_wincol; // Leftmost column of window in screen. int w_width; // Width of window, excluding separation. @@ -1182,16 +1231,18 @@ struct window_S { int w_valid; pos_T w_valid_cursor; /* last known position of w_cursor, used to adjust w_valid */ - colnr_T w_valid_leftcol; /* last known w_leftcol */ + colnr_T w_valid_leftcol; // last known w_leftcol + + bool w_viewport_invalid; /* * w_cline_height is the number of physical lines taken by the buffer line * that the cursor is on. We use this to avoid extra calls to plines(). */ - int w_cline_height; /* current size of cursor line */ - bool w_cline_folded; /* cursor line is folded */ + int w_cline_height; // current size of cursor line + bool w_cline_folded; // cursor line is folded - int w_cline_row; /* starting row of the cursor line */ + int w_cline_row; // starting row of the cursor line colnr_T w_virtcol; // column number of the cursor in the // buffer line, as opposed to the column @@ -1205,7 +1256,7 @@ struct window_S { * This is related to positions in the window, not in the display or * buffer, thus w_wrow is relative to w_winrow. */ - int w_wrow, w_wcol; /* cursor position in window */ + int w_wrow, w_wcol; // cursor position in window linenr_T w_botline; // number of the line below the bottom of // the window @@ -1223,65 +1274,70 @@ struct window_S { * what is currently displayed. wl_valid is reset to indicated this. * This is used for efficient redrawing. */ - int w_lines_valid; /* number of valid entries */ + int w_lines_valid; // number of valid entries wline_T *w_lines; - garray_T w_folds; /* array of nested folds */ - bool w_fold_manual; /* when true: some folds are opened/closed - manually */ - bool w_foldinvalid; /* when true: folding needs to be - recomputed */ - int w_nrwidth; /* width of 'number' and 'relativenumber' - column being used */ + garray_T w_folds; // array of nested folds + bool w_fold_manual; // when true: some folds are opened/closed + // manually + bool w_foldinvalid; // when true: folding needs to be + // recomputed + int w_nrwidth; // width of 'number' and 'relativenumber' + // column being used /* * === end of cached values === */ - int w_redr_type; /* type of redraw to be performed on win */ - int w_upd_rows; /* number of window lines to update when - w_redr_type is REDRAW_TOP */ - linenr_T w_redraw_top; /* when != 0: first line needing redraw */ - linenr_T w_redraw_bot; /* when != 0: last line needing redraw */ - int w_redr_status; /* if TRUE status line must be redrawn */ + int w_redr_type; // type of redraw to be performed on win + int w_upd_rows; // number of window lines to update when + // w_redr_type is REDRAW_TOP + linenr_T w_redraw_top; // when != 0: first line needing redraw + linenr_T w_redraw_bot; // when != 0: last line needing redraw + int w_redr_status; // if TRUE status line must be redrawn - /* remember what is shown in the ruler for this window (if 'ruler' set) */ - pos_T w_ru_cursor; /* cursor position shown in ruler */ - colnr_T w_ru_virtcol; /* virtcol shown in ruler */ - linenr_T w_ru_topline; /* topline shown in ruler */ - linenr_T w_ru_line_count; /* line count used for ruler */ - int w_ru_topfill; /* topfill shown in ruler */ - char w_ru_empty; /* TRUE if ruler shows 0-1 (empty line) */ + // remember what is shown in the ruler for this window (if 'ruler' set) + pos_T w_ru_cursor; // cursor position shown in ruler + colnr_T w_ru_virtcol; // virtcol shown in ruler + linenr_T w_ru_topline; // topline shown in ruler + linenr_T w_ru_line_count; // line count used for ruler + int w_ru_topfill; // topfill shown in ruler + char w_ru_empty; // TRUE if ruler shows 0-1 (empty line) - int w_alt_fnum; /* alternate file (for # and CTRL-^) */ + int w_alt_fnum; // alternate file (for # and CTRL-^) - alist_T *w_alist; /* pointer to arglist for this window */ - int w_arg_idx; /* current index in argument list (can be - out of range!) */ - int w_arg_idx_invalid; /* editing another file than w_arg_idx */ + alist_T *w_alist; // pointer to arglist for this window + int w_arg_idx; // current index in argument list (can be + // out of range!) + int w_arg_idx_invalid; // editing another file than w_arg_idx char_u *w_localdir; /* absolute path of local directory or NULL */ - /* - * Options local to a window. - * They are local because they influence the layout of the window or - * depend on the window layout. - * There are two values: w_onebuf_opt is local to the buffer currently in - * this window, w_allbuf_opt is for all buffers in this window. - */ + vimmenu_T *w_winbar; // The root of the WinBar menu hierarchy. + winbar_item_T *w_winbar_items; // list of items in the WinBar + int w_winbar_height; // 1 if there is a window toolbar + + // Options local to a window. + // They are local because they influence the layout of the window or + // depend on the window layout. + // There are two values: w_onebuf_opt is local to the buffer currently in + // this window, w_allbuf_opt is for all buffers in this window. winopt_T w_onebuf_opt; winopt_T w_allbuf_opt; - /* A few options have local flags for P_INSECURE. */ - uint32_t w_p_stl_flags; /* flags for 'statusline' */ - uint32_t w_p_fde_flags; /* flags for 'foldexpr' */ - uint32_t w_p_fdt_flags; /* flags for 'foldtext' */ - int *w_p_cc_cols; /* array of columns to highlight or NULL */ - int w_p_brimin; /* minimum width for breakindent */ - int w_p_brishift; /* additional shift for breakindent */ - bool w_p_brisbr; /* sbr in 'briopt' */ + // A few options have local flags for P_INSECURE. + uint32_t w_p_stl_flags; // flags for 'statusline' + uint32_t w_p_fde_flags; // flags for 'foldexpr' + uint32_t w_p_fdt_flags; // flags for 'foldtext' + int *w_p_cc_cols; // array of columns to highlight or NULL + long w_p_siso; // 'sidescrolloff' local value + long w_p_so; // 'scrolloff' local value + + int w_briopt_min; // minimum width for breakindent + int w_briopt_shift; // additional shift for breakindent + bool w_briopt_sbr; // sbr in 'briopt' - /* transform a pointer to a "onebuf" option into a "allbuf" option */ + // transform a pointer to a "onebuf" option into a "allbuf" option #define GLOBAL_WO(p) ((char *)p + sizeof(winopt_T)) long w_scbind_pos; @@ -1294,20 +1350,20 @@ struct window_S { * a new line after setting the w_pcmark. If not, then we revert to * using the previous w_pcmark. */ - pos_T w_pcmark; /* previous context mark */ - pos_T w_prev_pcmark; /* previous w_pcmark */ + pos_T w_pcmark; // previous context mark + pos_T w_prev_pcmark; // previous w_pcmark /* * the jumplist contains old cursor positions */ xfmark_T w_jumplist[JUMPLISTSIZE]; - int w_jumplistlen; /* number of active entries */ - int w_jumplistidx; /* current position */ + int w_jumplistlen; // number of active entries + int w_jumplistidx; // current position - int w_changelistidx; /* current position in b_changelist */ + int w_changelistidx; // current position in b_changelist - matchitem_T *w_match_head; /* head of match list */ - int w_next_match_id; /* next match ID */ + matchitem_T *w_match_head; // head of match list + int w_next_match_id; // next match ID /* * the tagstack grows from 0 upwards: @@ -1315,9 +1371,9 @@ struct window_S { * entry 1: newer * entry 2: newest */ - taggy_T w_tagstack[TAGSTACKSIZE]; /* the tag stack */ - int w_tagstackidx; /* idx just below active entry */ - int w_tagstacklen; /* number of tags on stack */ + taggy_T w_tagstack[TAGSTACKSIZE]; // the tag stack + int w_tagstackidx; // idx just below active entry + int w_tagstacklen; // number of tags on stack ScreenGrid w_grid; // the grid specific to the window bool w_pos_changed; // true if window position changed @@ -1335,13 +1391,11 @@ struct window_S { linenr_T w_nrwidth_line_count; /* line count when ml_nrwidth_width * was computed. */ - int w_nrwidth_width; /* nr of chars to print line count. */ + int w_nrwidth_width; // nr of chars to print line count. - qf_info_T *w_llist; /* Location list for this window */ - /* - * Location list reference used in the location list window. - * In a non-location list window, w_llist_ref is NULL. - */ + qf_info_T *w_llist; // Location list for this window + // Location list reference used in the location list window. + // In a non-location list window, w_llist_ref is NULL. qf_info_T *w_llist_ref; }; diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c index 80780a3aa3..e6393bf02c 100644 --- a/src/nvim/buffer_updates.c +++ b/src/nvim/buffer_updates.c @@ -296,10 +296,7 @@ void buf_updates_send_splice(buf_T *buf, BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i); bool keep = true; if (cb.on_bytes != LUA_NOREF) { - Array args = ARRAY_DICT_INIT; - Object items[8]; - args.size = 8; - args.items = items; + FIXED_TEMP_ARRAY(args, 8); // the first argument is always the buffer handle args.items[0] = BUFFER_OBJ(buf->handle); diff --git a/src/nvim/change.c b/src/nvim/change.c index a341b8fce1..51afb40b40 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -361,8 +361,8 @@ void changed_bytes(linenr_T lnum, colnr_T col) /// insert/delete bytes at column /// -/// Like changed_bytes() but also adjust extmark for "added" bytes. -/// When "added" is negative text was deleted. +/// Like changed_bytes() but also adjust extmark for "new" bytes. +/// When "new" is negative text was deleted. static void inserted_bytes(linenr_T lnum, colnr_T col, int old, int new) { if (curbuf_splice_pending == 0) { diff --git a/src/nvim/channel.c b/src/nvim/channel.c index c66a0682e3..5eb29a7290 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -19,7 +19,6 @@ #include "nvim/ascii.h" static bool did_stdio = false; -PMap(uint64_t) *channels = NULL; /// next free id for a job or rpc channel /// 1 is reserved for stdio channel diff --git a/src/nvim/channel.h b/src/nvim/channel.h index c733e276be..9d26852ce5 100644 --- a/src/nvim/channel.h +++ b/src/nvim/channel.h @@ -85,7 +85,7 @@ struct Channel { bool callback_scheduled; }; -EXTERN PMap(uint64_t) *channels; +EXTERN PMap(uint64_t) *channels INIT(= NULL); #ifdef INCLUDE_GENERATED_DECLARATIONS # include "channel.h.generated.h" diff --git a/src/nvim/charset.c b/src/nvim/charset.c index e9140f8ec5..f9d5adbc12 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -8,7 +8,6 @@ #include <assert.h> #include <string.h> #include <wctype.h> -#include <wchar.h> // for towupper() and towlower() #include <inttypes.h> #include "nvim/vim.h" @@ -1571,6 +1570,7 @@ char_u* skiptohex(char_u *q) /// /// @return Pointer to the next whitespace or NUL character. char_u *skiptowhite(const char_u *p) + FUNC_ATTR_NONNULL_ALL { while (*p != ' ' && *p != '\t' && *p != NUL) { p++; diff --git a/src/nvim/cursor_shape.h b/src/nvim/cursor_shape.h index 2c466603f0..a23fa6836d 100644 --- a/src/nvim/cursor_shape.h +++ b/src/nvim/cursor_shape.h @@ -33,11 +33,11 @@ SHAPE_HOR = 1, ///< horizontal bar cursor SHAPE_VER = 2 ///< vertical bar cursor } CursorShape; -#define MSHAPE_NUMBERED 1000 /* offset for shapes identified by number */ -#define MSHAPE_HIDE 1 /* hide mouse pointer */ +#define MSHAPE_NUMBERED 1000 // offset for shapes identified by number +#define MSHAPE_HIDE 1 // hide mouse pointer -#define SHAPE_MOUSE 1 /* used for mouse pointer shape */ -#define SHAPE_CURSOR 2 /* used for text cursor shape */ +#define SHAPE_MOUSE 1 // used for mouse pointer shape +#define SHAPE_CURSOR 2 // used for text cursor shape typedef struct cursor_entry { char *full_name; ///< mode description diff --git a/src/nvim/diff.c b/src/nvim/diff.c index c31adc01fd..3de5fc49bd 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -648,8 +648,8 @@ void diff_redraw(bool dofold) foldUpdateAll(wp); } - /* A change may have made filler lines invalid, need to take care - * of that for other windows. */ + // A change may have made filler lines invalid, need to take care + // of that for other windows. int n = diff_check(wp, wp->w_topline); if (((wp != curwin) && (wp->w_topfill > 0)) || (n > 0)) { @@ -1385,11 +1385,18 @@ void diff_win_options(win_T *wp, int addbuf) curbuf = curwin->w_buffer; if (!wp->w_p_diff) { - wp->w_p_fdc_save = wp->w_p_fdc; wp->w_p_fen_save = wp->w_p_fen; wp->w_p_fdl_save = wp->w_p_fdl; + + if (wp->w_p_diff_saved) { + free_string_option(wp->w_p_fdc_save); + } + wp->w_p_fdc_save = vim_strsave(wp->w_p_fdc); } - wp->w_p_fdc = diff_foldcolumn; + xfree(wp->w_p_fdc); + wp->w_p_fdc = (char_u *)xstrdup("2"); + assert(diff_foldcolumn >= 0 && diff_foldcolumn <= 9); + snprintf((char *)wp->w_p_fdc, STRLEN(wp->w_p_fdc) + 1, "%d", diff_foldcolumn); wp->w_p_fen = true; wp->w_p_fdl = 0; foldUpdateAll(wp); @@ -1443,9 +1450,9 @@ void ex_diffoff(exarg_T *eap) wp->w_p_fdm = vim_strsave(*wp->w_p_fdm_save ? wp->w_p_fdm_save : (char_u *)"manual"); - if (wp->w_p_fdc == diff_foldcolumn) { - wp->w_p_fdc = wp->w_p_fdc_save; - } + free_string_option(wp->w_p_fdc); + wp->w_p_fdc = vim_strsave(wp->w_p_fdc_save); + if (wp->w_p_fdl == 0) { wp->w_p_fdl = wp->w_p_fdl_save; } @@ -2432,6 +2439,10 @@ void nv_diffgetput(bool put, size_t count) exarg_T ea; char buf[30]; + if (bt_prompt(curbuf)) { + vim_beep(BO_OPER); + return; + } if (count == 0) { ea.arg = (char_u *)""; } else { diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 68fa99484c..ea38221dc7 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -143,6 +143,7 @@ struct compl_S { compl_T *cp_prev; char_u *cp_str; // matched text char_u *(cp_text[CPT_COUNT]); // text for the menu + typval_T cp_user_data; char_u *cp_fname; // file containing the match, allocated when // cp_flags has CP_FREE_FNAME int cp_flags; // CP_ values @@ -184,7 +185,7 @@ static bool compl_used_match; // Selected one of the matches. static int compl_was_interrupted = FALSE; /* didn't finish finding completions. */ -static int compl_restarting = FALSE; /* don't insert match */ +static bool compl_restarting = false; // don't insert match // When the first completion is done "compl_started" is set. When it's // false the word to be completed must be located. @@ -197,7 +198,7 @@ static int compl_matches = 0; static char_u *compl_pattern = NULL; static int compl_direction = FORWARD; static int compl_shows_dir = FORWARD; -static int compl_pending = 0; /* > 1 for postponed CTRL-N */ +static int compl_pending = 0; // > 1 for postponed CTRL-N static pos_T compl_startpos; static colnr_T compl_col = 0; /* column where the text starts * that is being completed */ @@ -249,30 +250,30 @@ typedef struct insert_state { #define BACKSPACE_WORD_NOT_SPACE 3 #define BACKSPACE_LINE 4 -static size_t spell_bad_len = 0; /* length of located bad word */ +static size_t spell_bad_len = 0; // length of located bad word -static colnr_T Insstart_textlen; /* length of line when insert started */ -static colnr_T Insstart_blank_vcol; /* vcol for first inserted blank */ -static bool update_Insstart_orig = true; /* set Insstart_orig to Insstart */ +static colnr_T Insstart_textlen; // length of line when insert started +static colnr_T Insstart_blank_vcol; // vcol for first inserted blank +static bool update_Insstart_orig = true; // set Insstart_orig to Insstart -static char_u *last_insert = NULL; /* the text of the previous insert, - K_SPECIAL and CSI are escaped */ -static int last_insert_skip; /* nr of chars in front of previous insert */ -static int new_insert_skip; /* nr of chars in front of current insert */ -static int did_restart_edit; /* "restart_edit" when calling edit() */ +static char_u *last_insert = NULL; // the text of the previous insert, + // K_SPECIAL and CSI are escaped +static int last_insert_skip; // nr of chars in front of previous insert +static int new_insert_skip; // nr of chars in front of current insert +static int did_restart_edit; // "restart_edit" when calling edit() static bool can_cindent; // may do cindenting on this line -static int old_indent = 0; /* for ^^D command in insert mode */ +static int old_indent = 0; // for ^^D command in insert mode -static int revins_on; /* reverse insert mode on */ -static int revins_chars; /* how much to skip after edit */ -static int revins_legal; /* was the last char 'legal'? */ -static int revins_scol; /* start column of revins session */ +static int revins_on; // reverse insert mode on +static int revins_chars; // how much to skip after edit +static int revins_legal; // was the last char 'legal'? +static int revins_scol; // start column of revins session -static int ins_need_undo; /* call u_save() before inserting a - char. Set when edit() is called. - after that arrow_used is used. */ +static bool ins_need_undo; // call u_save() before inserting a + // char. Set when edit() is called. + // after that arrow_used is used. static bool did_add_space = false; // auto_format() added an extra space // under the cursor @@ -464,8 +465,8 @@ static void insert_enter(InsertState *s) change_warning(s->i == 0 ? 0 : s->i + 1); } - ui_cursor_shape(); /* may show different cursor shape */ - do_digraph(-1); /* clear digraphs */ + ui_cursor_shape(); // may show different cursor shape + do_digraph(-1); // clear digraphs // Get the current length of the redo buffer, those characters have to be // skipped if we want to get to the inserted characters. @@ -574,6 +575,10 @@ static int insert_check(VimState *state) foldCheckClose(); } + if (bt_prompt(curbuf)) { + init_prompt(s->cmdchar); + } + // If we inserted a character at the last position of the last line in the // window, scroll the window one line up. This avoids an extra redraw. This // is detected when the cursor column is smaller after inserting something. @@ -589,7 +594,7 @@ static int insert_check(VimState *state) if (curwin->w_wcol < s->mincol - curbuf->b_p_ts && curwin->w_wrow == curwin->w_winrow - + curwin->w_height_inner - 1 - p_so + + curwin->w_height_inner - 1 - get_scrolloff_value() && (curwin->w_cursor.lnum != curwin->w_topline || curwin->w_topfill > 0)) { if (curwin->w_topfill > 0) { @@ -817,6 +822,16 @@ static int insert_handle_key(InsertState *s) s->nomove = true; return 0; // exit insert mode } + if (s->c == Ctrl_C && bt_prompt(curbuf)) { + if (invoke_prompt_interrupt()) { + if (!bt_prompt(curbuf)) { + // buffer changed to a non-prompt buffer, get out of + // Insert mode + return 0; + } + break; + } + } // when 'insertmode' set, and not halfway through a mapping, don't leave // Insert mode @@ -1143,6 +1158,15 @@ check_pum: cmdwin_result = CAR; return 0; } + if (bt_prompt(curbuf)) { + invoke_prompt_callback(); + if (!bt_prompt(curbuf)) { + // buffer changed to a non-prompt buffer, get out of + // Insert mode + return 0; + } + break; + } if (!ins_eol(s->c) && !p_im) { return 0; // out of memory } @@ -1397,9 +1421,8 @@ bool edit(int cmdchar, bool startln, long count) * Only redraw when there are no characters available. This speeds up * inserting sequences of characters (e.g., for CTRL-R). */ -static void -ins_redraw ( - int ready /* not busy with something */ +static void ins_redraw( + bool ready // not busy with something ) { bool conceal_cursor_moved = false; @@ -1480,7 +1503,7 @@ ins_redraw ( } showruler(false); setcursor(); - emsg_on_display = FALSE; /* may remove error message now */ + emsg_on_display = false; // may remove error message now } /* @@ -1489,24 +1512,25 @@ ins_redraw ( static void ins_ctrl_v(void) { int c; - int did_putchar = FALSE; + bool did_putchar = false; - /* may need to redraw when no more chars available now */ - ins_redraw(FALSE); + // may need to redraw when no more chars available now + ins_redraw(false); if (redrawing() && !char_avail()) { - edit_putchar('^', TRUE); - did_putchar = TRUE; + edit_putchar('^', true); + did_putchar = true; } AppendToRedobuff(CTRL_V_STR); add_to_showcmd_c(Ctrl_V); c = get_literal(); - if (did_putchar) - /* when the line fits in 'columns' the '^' is at the start of the next - * line and will not removed by the redraw */ + if (did_putchar) { + // when the line fits in 'columns' the '^' is at the start of the next + // line and will not removed by the redraw edit_unputchar(); + } clear_showcmd(); insert_special(c, true, true); revins_chars++; @@ -1518,16 +1542,16 @@ static void ins_ctrl_v(void) * Used while handling CTRL-K, CTRL-V, etc. in Insert mode. */ static int pc_status; -#define PC_STATUS_UNSET 0 /* pc_bytes was not set */ -#define PC_STATUS_RIGHT 1 /* right halve of double-wide char */ -#define PC_STATUS_LEFT 2 /* left halve of double-wide char */ -#define PC_STATUS_SET 3 /* pc_bytes was filled */ -static char_u pc_bytes[MB_MAXBYTES + 1]; /* saved bytes */ +#define PC_STATUS_UNSET 0 // pc_bytes was not set +#define PC_STATUS_RIGHT 1 // right halve of double-wide char +#define PC_STATUS_LEFT 2 // left halve of double-wide char +#define PC_STATUS_SET 3 // pc_bytes was filled +static char_u pc_bytes[MB_MAXBYTES + 1]; // saved bytes static int pc_attr; static int pc_row; static int pc_col; -void edit_putchar(int c, int highlight) +void edit_putchar(int c, bool highlight) { int attr; @@ -1560,7 +1584,7 @@ void edit_putchar(int c, int highlight) } } - /* save the character to be able to put it back */ + // save the character to be able to put it back if (pc_status == PC_STATUS_UNSET) { grid_getbytes(&curwin->w_grid, pc_row, pc_col, pc_bytes, &pc_attr); pc_status = PC_STATUS_SET; @@ -1569,6 +1593,52 @@ void edit_putchar(int c, int highlight) } } +// Return the effective prompt for the current buffer. +char_u *prompt_text(void) +{ + if (curbuf->b_prompt_text == NULL) { + return (char_u *)"% "; + } + return curbuf->b_prompt_text; +} + +// Prepare for prompt mode: Make sure the last line has the prompt text. +// Move the cursor to this line. +static void init_prompt(int cmdchar_todo) +{ + char_u *prompt = prompt_text(); + char_u *text; + + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + text = get_cursor_line_ptr(); + if (STRNCMP(text, prompt, STRLEN(prompt)) != 0) { + // prompt is missing, insert it or append a line with it + if (*text == NUL) { + ml_replace(curbuf->b_ml.ml_line_count, prompt, true); + } else { + ml_append(curbuf->b_ml.ml_line_count, prompt, 0, false); + } + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + coladvance((colnr_T)MAXCOL); + changed_bytes(curbuf->b_ml.ml_line_count, 0); + } + if (cmdchar_todo == 'A') { + coladvance((colnr_T)MAXCOL); + } + if (cmdchar_todo == 'I' || curwin->w_cursor.col <= (int)STRLEN(prompt)) { + curwin->w_cursor.col = STRLEN(prompt); + } + // Make sure the cursor is in a valid position. + check_cursor(); +} + +// Return TRUE if the cursor is in the editable position of the prompt line. +int prompt_curpos_editable(void) +{ + return curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count + && curwin->w_cursor.col >= (int)STRLEN(prompt_text()); +} + /* * Undo the previous edit_putchar(). */ @@ -1637,29 +1707,29 @@ change_indent ( int type, int amount, int round, - int replaced, /* replaced character, put on replace stack */ - int call_changed_bytes /* call changed_bytes() */ + int replaced, // replaced character, put on replace stack + int call_changed_bytes // call changed_bytes() ) { int vcol; int last_vcol; - int insstart_less; /* reduction for Insstart.col */ + int insstart_less; // reduction for Insstart.col int new_cursor_col; int i; char_u *ptr; int save_p_list; int start_col; colnr_T vc; - colnr_T orig_col = 0; /* init for GCC */ - char_u *new_line, *orig_line = NULL; /* init for GCC */ + colnr_T orig_col = 0; // init for GCC + char_u *new_line, *orig_line = NULL; // init for GCC - /* VREPLACE mode needs to know what the line was like before changing */ + // VREPLACE mode needs to know what the line was like before changing if (State & VREPLACE_FLAG) { - orig_line = vim_strsave(get_cursor_line_ptr()); /* Deal with NULL below */ + orig_line = vim_strsave(get_cursor_line_ptr()); // Deal with NULL below orig_col = curwin->w_cursor.col; } - /* for the following tricks we don't want list mode */ + // for the following tricks we don't want list mode save_p_list = curwin->w_p_list; curwin->w_p_list = FALSE; vc = getvcol_nolist(&curwin->w_cursor); @@ -1672,7 +1742,7 @@ change_indent ( */ start_col = curwin->w_cursor.col; - /* determine offset from first non-blank */ + // determine offset from first non-blank new_cursor_col = curwin->w_cursor.col; beginline(BL_WHITE); new_cursor_col -= curwin->w_cursor.col; @@ -1686,8 +1756,9 @@ change_indent ( if (new_cursor_col < 0) vcol = get_indent() - vcol; - if (new_cursor_col > 0) /* can't fix replace stack */ + if (new_cursor_col > 0) { // can't fix replace stack start_col = -1; + } /* * Set the new indent. The cursor will be put on the first non-blank. @@ -1697,9 +1768,10 @@ change_indent ( else { int save_State = State; - /* Avoid being called recursively. */ - if (State & VREPLACE_FLAG) + // Avoid being called recursively. + if (State & VREPLACE_FLAG) { State = INSERT; + } shift_line(type == INDENT_DEC, round, 1, call_changed_bytes); State = save_State; } @@ -1802,8 +1874,8 @@ change_indent ( */ if (REPLACE_NORMAL(State) && start_col >= 0) { while (start_col > (int)curwin->w_cursor.col) { - replace_join(0); /* remove a NUL from the replace stack */ - --start_col; + replace_join(0); // remove a NUL from the replace stack + start_col--; } while (start_col < (int)curwin->w_cursor.col || replaced) { replace_push(NUL); @@ -1821,10 +1893,10 @@ change_indent ( * put it back again the way we wanted it. */ if (State & VREPLACE_FLAG) { - /* Save new line */ + // Save new line new_line = vim_strsave(get_cursor_line_ptr()); - /* We only put back the new line up to the cursor */ + // We only put back the new line up to the cursor new_line[curwin->w_cursor.col] = NUL; int new_col = curwin->w_cursor.col; @@ -1834,10 +1906,10 @@ change_indent ( curbuf_splice_pending++; - /* Backspace from cursor to start of line */ + // Backspace from cursor to start of line backspace_until_column(0); - /* Insert new stuff into line again */ + // Insert new stuff into line again ins_bytes(new_line); xfree(new_line); @@ -1863,10 +1935,11 @@ void truncate_spaces(char_u *line) { int i; - /* find start of trailing white space */ + // find start of trailing white space for (i = (int)STRLEN(line) - 1; i >= 0 && ascii_iswhite(line[i]); i--) { - if (State & REPLACE_FLAG) - replace_join(0); /* remove a NUL from the replace stack */ + if (State & REPLACE_FLAG) { + replace_join(0); // remove a NUL from the replace stack + } } line[i + 1] = NUL; } @@ -1935,7 +2008,7 @@ static void ins_ctrl_x(void) compl_cont_status |= CONT_INTRPT; else compl_cont_status = 0; - /* We're not sure which CTRL-X mode it will be yet */ + // We're not sure which CTRL-X mode it will be yet ctrl_x_mode = CTRL_X_NOT_DEFINED_YET; edit_submode = (char_u *)_(CTRL_X_MSG(ctrl_x_mode)); edit_submode_pre = NULL; @@ -2084,8 +2157,8 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname, { char_u *str = str_arg; int i, c; - int actual_len; /* Take multi-byte characters */ - int actual_compl_length; /* into account. */ + int actual_len; // Take multi-byte characters + int actual_compl_length; // into account. int min_len; bool has_lower = false; bool was_letter = false; @@ -2105,16 +2178,15 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname, } else actual_len = len; - /* Find actual length of original text. */ - if (has_mbyte) { + // Find actual length of original text. + { const char_u *p = compl_orig_text; actual_compl_length = 0; while (*p != NUL) { MB_PTR_ADV(p); actual_compl_length++; } - } else - actual_compl_length = compl_length; + } /* "actual_len" may be smaller than "actual_compl_length" when using * thesaurus, only use the minimum when comparing. */ @@ -2220,7 +2292,7 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname, flags |= CP_ICASE; } - return ins_compl_add(str, len, fname, NULL, false, dir, flags, false); + return ins_compl_add(str, len, fname, NULL, false, NULL, dir, flags, false); } /// Add a match to the list of matches @@ -2244,6 +2316,7 @@ static int ins_compl_add(char_u *const str, int len, char_u *const fname, char_u *const *const cptext, const bool cptext_allocated, + typval_T *user_data, const Direction cdir, int flags_arg, const bool adup) FUNC_ATTR_NONNULL_ARG(1) { @@ -2284,7 +2357,7 @@ static int ins_compl_add(char_u *const str, int len, } while (match != NULL && match != compl_first_match); } - /* Remove any popup menu before changing the list of matches. */ + // Remove any popup menu before changing the list of matches. ins_compl_del_pum(); /* @@ -2332,6 +2405,10 @@ static int ins_compl_add(char_u *const str, int len, } } + if (user_data != NULL) { + match->cp_user_data = *user_data; + } + /* * Link the new match structure in the list of matches. */ @@ -2340,16 +2417,18 @@ static int ins_compl_add(char_u *const str, int len, else if (dir == FORWARD) { match->cp_next = compl_curr_match->cp_next; match->cp_prev = compl_curr_match; - } else { /* BACKWARD */ + } else { // BACKWARD match->cp_next = compl_curr_match; match->cp_prev = compl_curr_match->cp_prev; } - if (match->cp_next) + if (match->cp_next) { match->cp_next->cp_prev = match; - if (match->cp_prev) + } + if (match->cp_prev) { match->cp_prev->cp_next = match; - else /* if there's nothing before, it is the first match */ + } else { // if there's nothing before, it is the first match compl_first_match = match; + } compl_curr_match = match; /* @@ -2390,7 +2469,7 @@ static void ins_compl_longest_match(compl_T *match) int had_match; if (compl_leader == NULL) { - /* First match, use it as a whole. */ + // First match, use it as a whole. compl_leader = vim_strsave(match->cp_str); had_match = (curwin->w_cursor.col > compl_col); ins_compl_delete(); @@ -2403,7 +2482,7 @@ static void ins_compl_longest_match(compl_T *match) ins_compl_delete(); compl_used_match = false; } else { - /* Reduce the text if this match differs from compl_leader. */ + // Reduce the text if this match differs from compl_leader. p = compl_leader; s = match->cp_str; while (*p != NUL) { @@ -2420,7 +2499,7 @@ static void ins_compl_longest_match(compl_T *match) } if (*p != NUL) { - /* Leader was shortened, need to change the inserted text. */ + // Leader was shortened, need to change the inserted text. *p = NUL; had_match = (curwin->w_cursor.col > compl_col); ins_compl_delete(); @@ -2448,7 +2527,7 @@ static void ins_compl_add_matches(int num_matches, char_u **matches, int icase) int dir = compl_direction; for (int i = 0; i < num_matches && add_r != FAIL; i++) { - if ((add_r = ins_compl_add(matches[i], -1, NULL, NULL, false, dir, + if ((add_r = ins_compl_add(matches[i], -1, NULL, NULL, false, NULL, dir, icase ? CP_ICASE : 0, false)) == OK) { // If dir was BACKWARD then honor it just once. dir = FORWARD; @@ -2470,7 +2549,7 @@ static int ins_compl_make_cyclic(void) * Find the end of the list. */ match = compl_first_match; - /* there's always an entry for the compl_orig_text, it doesn't count. */ + // there's always an entry for the compl_orig_text, it doesn't count. while (match->cp_next != NULL && match->cp_next != compl_first_match) { match = match->cp_next; ++count; @@ -2517,13 +2596,13 @@ void set_completion(colnr_T startcol, list_T *list) startcol = curwin->w_cursor.col; compl_col = startcol; compl_length = (int)curwin->w_cursor.col - (int)startcol; - /* compl_pattern doesn't need to be set */ + // compl_pattern doesn't need to be set compl_orig_text = vim_strnsave(get_cursor_line_ptr() + compl_col, compl_length); if (p_ic) { flags |= CP_ICASE; } - if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, 0, + if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0, flags, false) != OK) { return; } @@ -2650,10 +2729,10 @@ void ins_compl_show_pum(void) if (!pum_wanted() || !pum_enough_matches()) return; - /* Dirty hard-coded hack: remove any matchparen highlighting. */ + // Dirty hard-coded hack: remove any matchparen highlighting. do_cmdline_cmd("if exists('g:loaded_matchparen')|3match none|endif"); - /* Update the screen before drawing the popup menu over it. */ + // Update the screen before drawing the popup menu over it. update_screen(0); if (compl_match_array == NULL) { @@ -2744,17 +2823,19 @@ void ins_compl_show_pum(void) compl = compl->cp_next; } while (compl != NULL && compl != compl_first_match); - if (!shown_match_ok) /* no displayed match at all */ + if (!shown_match_ok) { // no displayed match at all cur = -1; + } } else { - /* popup menu already exists, only need to find the current item.*/ - for (i = 0; i < compl_match_arraysize; ++i) + // popup menu already exists, only need to find the current item. + for (i = 0; i < compl_match_arraysize; i++) { if (compl_match_array[i].pum_text == compl_shown_match->cp_str || compl_match_array[i].pum_text == compl_shown_match->cp_text[CPT_ABBR]) { cur = i; break; } + } } // In Replace mode when a $ is displayed at the end of the line only @@ -2774,8 +2855,8 @@ void ins_compl_show_pum(void) } } -#define DICT_FIRST (1) /* use just first element in "dict" */ -#define DICT_EXACT (2) /* "dict" is the exact name of a file */ +#define DICT_FIRST (1) // use just first element in "dict" +#define DICT_EXACT (2) // "dict" is the exact name of a file /* * Add any identifiers that match the given pattern in the list of dictionary @@ -2785,8 +2866,8 @@ static void ins_compl_dictionaries ( char_u *dict_start, char_u *pat, - int flags, /* DICT_FIRST and/or DICT_EXACT */ - int thesaurus /* Thesaurus completion */ + int flags, // DICT_FIRST and/or DICT_EXACT + int thesaurus // Thesaurus completion ) { char_u *dict = dict_start; @@ -2808,9 +2889,9 @@ ins_compl_dictionaries ( } buf = xmalloc(LSIZE); - regmatch.regprog = NULL; /* so that we can goto theend */ + regmatch.regprog = NULL; // so that we can goto theend - /* If 'infercase' is set, don't use 'smartcase' here */ + // If 'infercase' is set, don't use 'smartcase' here save_p_scs = p_scs; if (curbuf->b_p_inf) p_scs = FALSE; @@ -2833,10 +2914,10 @@ ins_compl_dictionaries ( goto theend; } - /* ignore case depends on 'ignorecase', 'smartcase' and "pat" */ + // ignore case depends on 'ignorecase', 'smartcase' and "pat" regmatch.rm_ic = ignorecase(pat); while (*dict != NUL && !got_int && !compl_interrupted) { - /* copy one dictionary file name into buf */ + // copy one dictionary file name into buf if (flags == DICT_EXACT) { count = 1; files = &dict; @@ -2861,7 +2942,7 @@ ins_compl_dictionaries ( else ptr = pat; spell_dump_compl(ptr, regmatch.rm_ic, &dir, 0); - } else if (count > 0) { /* avoid warning for using "files" uninit */ + } else if (count > 0) { // avoid warning for using "files" uninit ins_compl_files(count, files, thesaurus, flags, ®match, buf, &dir); if (flags != DICT_EXACT) @@ -2927,20 +3008,18 @@ static void ins_compl_files(int count, char_u **files, int thesaurus, int flags, break; wstart = ptr; - /* Find end of the word. */ - if (has_mbyte) - /* Japanese words may have characters in - * different classes, only separate words - * with single-byte non-word characters. */ - while (*ptr != NUL) { - int l = (*mb_ptr2len)(ptr); - - if (l < 2 && !vim_iswordc(*ptr)) - break; - ptr += l; + // Find end of the word. + // Japanese words may have characters in + // different classes, only separate words + // with single-byte non-word characters. + while (*ptr != NUL) { + const int l = utfc_ptr2len(ptr); + + if (l < 2 && !vim_iswordc(*ptr)) { + break; } - else - ptr = find_word_end(ptr); + ptr += l; + } // Add the word. Skip the regexp match. if (wstart != regmatch->startp[0]) { @@ -2949,15 +3028,17 @@ static void ins_compl_files(int count, char_u **files, int thesaurus, int flags, } } } - if (add_r == OK) - /* if dir was BACKWARD then honor it just once */ + if (add_r == OK) { + // if dir was BACKWARD then honor it just once *dir = FORWARD; - else if (add_r == FAIL) + } else if (add_r == FAIL) { break; - /* avoid expensive call to vim_regexec() when at end - * of line */ - if (*ptr == '\n' || got_int) + } + // avoid expensive call to vim_regexec() when at end + // of line + if (*ptr == '\n' || got_int) { break; + } } line_breakcheck(); ins_compl_check_keys(50, false); @@ -3045,6 +3126,7 @@ static void ins_compl_free(void) for (int i = 0; i < CPT_COUNT; i++) { xfree(match->cp_text[i]); } + tv_clear(&match->cp_user_data); xfree(match); } while (compl_curr_match != NULL && compl_curr_match != compl_first_match); compl_first_match = compl_curr_match = NULL; @@ -3140,8 +3222,11 @@ void get_complete_info(list_T *what_list, dict_T *retdict) (char *)EMPTY_IF_NULL(match->cp_text[CPT_KIND])); tv_dict_add_str(di, S_LEN("info"), (char *)EMPTY_IF_NULL(match->cp_text[CPT_INFO])); - tv_dict_add_str(di, S_LEN("user_data"), - (char *)EMPTY_IF_NULL(match->cp_text[CPT_USER_DATA])); + if (match->cp_user_data.v_type == VAR_UNKNOWN) { + tv_dict_add_str(di, S_LEN("user_data"), ""); + } else { + tv_dict_add_tv(di, S_LEN("user_data"), &match->cp_user_data); + } } match = match->cp_next; } while (match != NULL && match != compl_first_match); @@ -3204,9 +3289,10 @@ static int ins_compl_bs(void) xfree(compl_leader); compl_leader = vim_strnsave(line + compl_col, (int)(p - line) - compl_col); ins_compl_new_leader(); - if (compl_shown_match != NULL) - /* Make sure current match is not a hidden item. */ + if (compl_shown_match != NULL) { + // Make sure current match is not a hidden item. compl_curr_match = compl_shown_match; + } return NUL; } @@ -3250,7 +3336,7 @@ static void ins_compl_new_leader(void) compl_enter_selects = !compl_used_match; - /* Show the popup menu with a different set of matches. */ + // Show the popup menu with a different set of matches. ins_compl_show_pum(); /* Don't let Enter select the original text when there is no popup menu. @@ -3293,9 +3379,10 @@ static void ins_compl_addleader(int c) ins_char(c); } - /* If we didn't complete finding matches we must search again. */ - if (ins_compl_need_restart()) + // If we didn't complete finding matches we must search again. + if (ins_compl_need_restart()) { ins_compl_restart(); + } xfree(compl_leader); compl_leader = vim_strnsave(get_cursor_line_ptr() + compl_col, @@ -3351,9 +3438,9 @@ static void ins_compl_addfrommatch(void) compl_T *cp; assert(compl_shown_match != NULL); p = compl_shown_match->cp_str; - if ((int)STRLEN(p) <= len) { /* the match is too short */ - /* When still at the original match use the first entry that matches - * the leader. */ + if ((int)STRLEN(p) <= len) { // the match is too short + // When still at the original match use the first entry that matches + // the leader. if (compl_shown_match->cp_flags & CP_ORIGINAL_TEXT) { p = NULL; for (cp = compl_shown_match->cp_next; cp != NULL @@ -3393,14 +3480,14 @@ static bool ins_compl_prep(int c) if (c != Ctrl_R && vim_is_ctrl_x_key(c)) edit_submode_extra = NULL; - /* Ignore end of Select mode mapping and mouse scroll buttons. */ + // Ignore end of Select mode mapping and mouse scroll buttons. if (c == K_SELECT || c == K_MOUSEDOWN || c == K_MOUSEUP || c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_EVENT || c == K_COMMAND) { return retval; } - /* Set "compl_get_longest" when finding the first matches. */ + // Set "compl_get_longest" when finding the first matches. if (ctrl_x_mode == CTRL_X_NOT_DEFINED_YET || (ctrl_x_mode == CTRL_X_NORMAL && !compl_started)) { compl_get_longest = (strstr((char *)p_cot, "longest") != NULL); @@ -3433,7 +3520,7 @@ static bool ins_compl_prep(int c) ctrl_x_mode = CTRL_X_DICTIONARY; break; case Ctrl_R: - /* Simply allow ^R to happen without affecting ^X mode */ + // Simply allow ^R to happen without affecting ^X mode break; case Ctrl_T: ctrl_x_mode = CTRL_X_THESAURUS; @@ -3447,9 +3534,9 @@ static bool ins_compl_prep(int c) case 's': case Ctrl_S: ctrl_x_mode = CTRL_X_SPELL; - ++emsg_off; /* Avoid getting the E756 error twice. */ + emsg_off++; // Avoid getting the E756 error twice. spell_back_to_badword(); - --emsg_off; + emsg_off--; break; case Ctrl_RSB: ctrl_x_mode = CTRL_X_TAGS; @@ -3548,10 +3635,10 @@ static bool ins_compl_prep(int c) // When completing whole lines: fix indent for 'cindent'. // Otherwise, break line if it's too long. if (compl_cont_mode == CTRL_X_WHOLE_LINE) { - /* re-indent the current line */ + // re-indent the current line if (want_cindent) { do_c_expr_indent(); - want_cindent = FALSE; /* don't do it again */ + want_cindent = false; // don't do it again } } else { int prev_col = curwin->w_cursor.col; @@ -3594,17 +3681,11 @@ static bool ins_compl_prep(int c) auto_format(FALSE, TRUE); - { - const int new_mode = ctrl_x_mode; - - // Trigger the CompleteDone event to give scripts a chance to - // act upon the completion. Do this before clearing the info, - // and restore ctrl_x_mode, so that complete_info() can be - // used. - ctrl_x_mode = prev_mode; - ins_apply_autocmds(EVENT_COMPLETEDONE); - ctrl_x_mode = new_mode; - } + // Trigger the CompleteDonePre event to give scripts a chance to + // act upon the completion before clearing the info, and restore + // ctrl_x_mode, so that complete_info() can be used. + ctrl_x_mode = prev_mode; + ins_apply_autocmds(EVENT_COMPLETEDONEPRE); ins_compl_free(); compl_started = false; @@ -3630,6 +3711,9 @@ static bool ins_compl_prep(int c) */ if (want_cindent && in_cinkeys(KEY_COMPLETE, ' ', inindent(0))) do_c_expr_indent(); + // Trigger the CompleteDone event to give scripts a chance to act + // upon the end of completion. + ins_apply_autocmds(EVENT_COMPLETEDONE); } } else if (ctrl_x_mode == CTRL_X_LOCAL_MSG) /* Trigger the CompleteDone event to give scripts a chance to act @@ -3658,10 +3742,11 @@ static void ins_compl_fixRedoBufForLeader(char_u *ptr_arg) char_u *ptr = ptr_arg; if (ptr == NULL) { - if (compl_leader != NULL) + if (compl_leader != NULL) { ptr = compl_leader; - else - return; /* nothing to do */ + } else { + return; // nothing to do + } } if (compl_orig_text != NULL) { p = compl_orig_text; @@ -3690,9 +3775,10 @@ static buf_T *ins_compl_next_buf(buf_T *buf, int flag) { static win_T *wp; - if (flag == 'w') { /* just windows */ - if (buf == curbuf) /* first call for this flag/expansion */ + if (flag == 'w') { // just windows + if (buf == curbuf) { // first call for this flag/expansion wp = curwin; + } assert(wp); while ((wp = (wp->w_next != NULL ? wp->w_next : firstwin)) != curwin && wp->w_buffer->b_scanned) @@ -3770,7 +3856,7 @@ expand_by_function( EMSG(_(e_complwin)); goto theend; } - curwin->w_cursor = pos; /* restore the cursor position */ + curwin->w_cursor = pos; // restore the cursor position validate_cursor(); if (!equalpos(curwin->w_cursor, pos)) { EMSG(_(e_compldel)); @@ -3854,15 +3940,16 @@ int ins_compl_add_tv(typval_T *const tv, const Direction dir) bool empty = false; int flags = 0; char *(cptext[CPT_COUNT]); + typval_T user_data; + user_data.v_type = VAR_UNKNOWN; if (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL) { word = tv_dict_get_string(tv->vval.v_dict, "word", false); cptext[CPT_ABBR] = tv_dict_get_string(tv->vval.v_dict, "abbr", true); cptext[CPT_MENU] = tv_dict_get_string(tv->vval.v_dict, "menu", true); cptext[CPT_KIND] = tv_dict_get_string(tv->vval.v_dict, "kind", true); cptext[CPT_INFO] = tv_dict_get_string(tv->vval.v_dict, "info", true); - cptext[CPT_USER_DATA] = tv_dict_get_string(tv->vval.v_dict, - "user_data", true); + tv_dict_get_tv(tv->vval.v_dict, "user_data", &user_data); if (tv_dict_get_number(tv->vval.v_dict, "icase")) { flags |= CP_ICASE; @@ -3884,7 +3971,7 @@ int ins_compl_add_tv(typval_T *const tv, const Direction dir) return FAIL; } return ins_compl_add((char_u *)word, -1, NULL, - (char_u **)cptext, true, dir, flags, dup); + (char_u **)cptext, true, &user_data, dir, flags, dup); } // Get the next expansion(s), using "compl_pattern". @@ -4017,7 +4104,7 @@ static int ins_compl_get_exp(pos_T *ini) type = CTRL_X_PATH_DEFINES; else if (*e_cpt == ']' || *e_cpt == 't') { type = CTRL_X_TAGS; - vim_snprintf((char *)IObuff, IOSIZE, _("Scanning tags.")); + vim_snprintf((char *)IObuff, IOSIZE, "%s", _("Scanning tags.")); (void)msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R)); } else { type = -1; @@ -4374,9 +4461,11 @@ static dict_T *ins_compl_dict_alloc(compl_T *match) tv_dict_add_str( dict, S_LEN("info"), (const char *)EMPTY_IF_NULL(match->cp_text[CPT_INFO])); - tv_dict_add_str( - dict, S_LEN("user_data"), - (const char *)EMPTY_IF_NULL(match->cp_text[CPT_USER_DATA])); + if (match->cp_user_data.v_type == VAR_UNKNOWN) { + tv_dict_add_str(dict, S_LEN("user_data"), ""); + } else { + tv_dict_add_tv(dict, S_LEN("user_data"), &match->cp_user_data); + } return dict; } @@ -4408,8 +4497,7 @@ ins_compl_next ( int num_matches = -1; int todo = count; compl_T *found_compl = NULL; - int found_end = FALSE; - int advance; + bool found_end = false; const bool started = compl_started; /* When user complete function return -1 for findstart which is next @@ -4445,17 +4533,17 @@ ins_compl_next ( if (allow_get_expansion && insert_match && (!(compl_get_longest || compl_restarting) || compl_used_match)) - /* Delete old text to be replaced */ + // Delete old text to be replaced ins_compl_delete(); - /* When finding the longest common text we stick at the original text, - * don't let CTRL-N or CTRL-P move to the first match. */ - advance = count != 1 || !allow_get_expansion || !compl_get_longest; + // When finding the longest common text we stick at the original text, + // don't let CTRL-N or CTRL-P move to the first match. + bool advance = count != 1 || !allow_get_expansion || !compl_get_longest; - /* When restarting the search don't insert the first match either. */ + // When restarting the search don't insert the first match either. if (compl_restarting) { - advance = FALSE; - compl_restarting = FALSE; + advance = false; + compl_restarting = false; } /* Repeat this for when <PageUp> or <PageDown> is typed. But don't wrap @@ -4489,10 +4577,10 @@ ins_compl_next ( ++compl_pending; } - /* Find matches. */ + // Find matches. num_matches = ins_compl_get_exp(&compl_startpos); - /* handle any pending completions */ + // handle any pending completions while (compl_pending != 0 && compl_direction == compl_shows_dir && advance) { if (compl_pending > 0 && compl_shown_match->cp_next != NULL) { @@ -4505,7 +4593,7 @@ ins_compl_next ( } else break; } - found_end = FALSE; + found_end = false; } if ((compl_shown_match->cp_flags & CP_ORIGINAL_TEXT) == 0 && compl_leader != NULL @@ -4517,17 +4605,17 @@ ins_compl_next ( found_compl = compl_shown_match; } - /* Stop at the end of the list when we found a usable match. */ + // Stop at the end of the list when we found a usable match. if (found_end) { if (found_compl != NULL) { compl_shown_match = found_compl; break; } - todo = 1; /* use first usable match after wrapping around */ + todo = 1; // use first usable match after wrapping around } } - /* Insert the text of the new completion, or the compl_leader. */ + // Insert the text of the new completion, or the compl_leader. if (compl_no_insert && !started) { ins_bytes(compl_orig_text + ins_compl_len()); compl_used_match = false; @@ -4621,9 +4709,10 @@ void ins_compl_check_keys(int frequency, int in_compl_func) return; } - /* Only do this at regular intervals */ - if (++count < frequency) + // Only do this at regular intervals + if (++count < frequency) { return; + } count = 0; /* Check for a typed key. Do use mappings, otherwise vim_is_ctrl_x_key() @@ -4631,7 +4720,7 @@ void ins_compl_check_keys(int frequency, int in_compl_func) int c = vpeekc_any(); if (c != NUL) { if (vim_is_ctrl_x_key(c) && c != Ctrl_X && c != Ctrl_R) { - c = safe_vgetc(); /* Eat the character */ + c = safe_vgetc(); // Eat the character compl_shows_dir = ins_compl_key2dir(c); (void)ins_compl_next(false, ins_compl_key2count(c), c != K_UP && c != K_DOWN, in_compl_func); @@ -4700,8 +4789,9 @@ static int ins_compl_key2count(int c) if (ins_compl_pum_key(c) && c != K_UP && c != K_DOWN) { h = pum_get_height(); - if (h > 3) - h -= 2; /* keep some context */ + if (h > 3) { + h -= 2; // keep some context + } return h; } return 1; @@ -4739,8 +4829,8 @@ static bool ins_compl_use_match(int c) static int ins_complete(int c, bool enable_pum) { char_u *line; - int startcol = 0; /* column where searched text starts */ - colnr_T curs_col; /* cursor column */ + int startcol = 0; // column where searched text starts + colnr_T curs_col; // cursor column int n; int save_w_wrow; int save_w_leftcol; @@ -4752,7 +4842,7 @@ static int ins_complete(int c, bool enable_pum) insert_match = ins_compl_use_match(c); if (!compl_started) { - /* First time we hit ^N or ^P (in a row, I mean) */ + // First time we hit ^N or ^P (in a row, I mean) did_ai = false; did_si = false; @@ -4790,7 +4880,7 @@ static int ins_complete(int c, bool enable_pum) compl_col = (colnr_T)getwhitecols(line); compl_startpos.col = compl_col; compl_startpos.lnum = curwin->w_cursor.lnum; - compl_cont_status &= ~CONT_SOL; /* clear SOL if present */ + compl_cont_status &= ~CONT_SOL; // clear SOL if present } else { /* S_IPOS was set when we inserted a word that was at the * beginning of the line, which means that we'll go to SOL @@ -4822,7 +4912,7 @@ static int ins_complete(int c, bool enable_pum) } else compl_cont_status &= CONT_LOCAL; - if (!(compl_cont_status & CONT_ADDING)) { /* normal expansion */ + if (!(compl_cont_status & CONT_ADDING)) { // normal expansion compl_cont_mode = ctrl_x_mode; if (ctrl_x_mode != CTRL_X_NORMAL) { // Remove LOCAL if ctrl_x_mode != CTRL_X_NORMAL @@ -4851,7 +4941,7 @@ static int ins_complete(int c, bool enable_pum) } else if (compl_cont_status & CONT_ADDING) { char_u *prefix = (char_u *)"\\<"; - /* we need up to 2 extra chars for the prefix */ + // we need up to 2 extra chars for the prefix compl_pattern = xmalloc(quote_meta(NULL, line + compl_col, compl_length) + 2); if (!vim_iswordp(line + compl_col) @@ -4903,14 +4993,16 @@ static int ins_complete(int c, bool enable_pum) } else if (CTRL_X_MODE_LINE_OR_EVAL(ctrl_x_mode)) { compl_col = (colnr_T)getwhitecols(line); compl_length = (int)curs_col - (int)compl_col; - if (compl_length < 0) /* cursor in indent: empty pattern */ + if (compl_length < 0) { // cursor in indent: empty pattern compl_length = 0; - if (p_ic) + } + if (p_ic) { compl_pattern = str_foldcase(line + compl_col, compl_length, NULL, 0); - else + } else { compl_pattern = vim_strnsave(line + compl_col, compl_length); + } } else if (ctrl_x_mode == CTRL_X_FILES) { - /* Go back to just before the first filename character. */ + // Go back to just before the first filename character. if (startcol > 0) { char_u *p = line + startcol; @@ -4982,7 +5074,7 @@ static int ins_complete(int c, bool enable_pum) EMSG(_(e_complwin)); return FAIL; } - curwin->w_cursor = pos; /* restore the cursor position */ + curwin->w_cursor = pos; // restore the cursor position validate_cursor(); if (!equalpos(curwin->w_cursor, pos)) { EMSG(_(e_compldel)); @@ -5034,7 +5126,7 @@ static int ins_complete(int c, bool enable_pum) spell_expand_check_cap(compl_col); compl_length = (int)curs_col - compl_col; } - /* Need to obtain "line" again, it may have become invalid. */ + // Need to obtain "line" again, it may have become invalid. line = ml_get(curwin->w_cursor.lnum); compl_pattern = vim_strnsave(line + compl_col, compl_length); } else { @@ -5045,7 +5137,7 @@ static int ins_complete(int c, bool enable_pum) if (compl_cont_status & CONT_ADDING) { edit_submode_pre = (char_u *)_(" Adding"); if (CTRL_X_MODE_LINE_OR_EVAL(ctrl_x_mode)) { - /* Insert a new line, keep indentation but ignore 'comments' */ + // Insert a new line, keep indentation but ignore 'comments' char_u *old = curbuf->b_p_com; curbuf->b_p_com = (char_u *)""; @@ -5070,13 +5162,13 @@ static int ins_complete(int c, bool enable_pum) * the redo buffer. */ ins_compl_fixRedoBufForLeader(NULL); - /* Always add completion for the original text. */ + // Always add completion for the original text. xfree(compl_orig_text); compl_orig_text = vim_strnsave(line + compl_col, compl_length); if (p_ic) { flags |= CP_ICASE; } - if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, 0, + if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0, flags, false) != OK) { XFREE_CLEAR(compl_pattern); XFREE_CLEAR(compl_orig_text); @@ -5107,8 +5199,9 @@ static int ins_complete(int c, bool enable_pum) n = ins_compl_next(true, ins_compl_key2count(c), insert_match, false); - if (n > 1) /* all matches have been found */ + if (n > 1) { // all matches have been found compl_matches = n; + } compl_curr_match = compl_shown_match; compl_direction = compl_shows_dir; @@ -5119,7 +5212,7 @@ static int ins_complete(int c, bool enable_pum) got_int = FALSE; } - /* we found no match if the list has only the "compl_orig_text"-entry */ + // we found no match if the list has only the "compl_orig_text"-entry if (compl_first_match == compl_first_match->cp_next) { edit_submode_extra = (compl_cont_status & CONT_ADDING) && compl_length > 1 @@ -5155,7 +5248,7 @@ static int ins_complete(int c, bool enable_pum) edit_submode_extra = (char_u *)_("The only match"); edit_submode_highl = HLF_COUNT; } else { - /* Update completion sequence number when needed. */ + // Update completion sequence number when needed. if (compl_curr_match->cp_number == -1) { int number = 0; compl_T *match; @@ -5178,24 +5271,27 @@ static int ins_complete(int c, bool enable_pum) match != NULL && match->cp_number == -1; match = match->cp_next) match->cp_number = ++number; - } else { /* BACKWARD */ - /* search forwards (upwards) for the first valid (!= -1) - * number. This should normally succeed already at the - * first loop cycle, so it's fast! */ - for (match = compl_curr_match->cp_next; match != NULL - && match != compl_first_match; - match = match->cp_next) + } else { // BACKWARD + // search forwards (upwards) for the first valid (!= -1) + // number. This should normally succeed already at the + // first loop cycle, so it's fast! + for (match = compl_curr_match->cp_next; + match != NULL && match != compl_first_match; + match = match->cp_next) { if (match->cp_number != -1) { number = match->cp_number; break; } - if (match != NULL) - /* go down and assign all numbers which are not - * assigned yet */ - for (match = match->cp_prev; match - && match->cp_number == -1; - match = match->cp_prev) + } + if (match != NULL) { + // go down and assign all numbers which are not + // assigned yet + for (match = match->cp_prev; + match && match->cp_number == -1; + match = match->cp_prev) { match->cp_number = ++number; + } + } } } @@ -5254,7 +5350,7 @@ static int ins_complete(int c, bool enable_pum) */ static unsigned quote_meta(char_u *dest, char_u *src, int len) { - unsigned m = (unsigned)len + 1; /* one extra for the NUL */ + unsigned m = (unsigned)len + 1; // one extra for the NUL for (; --len >= 0; src++) { switch (*src) { @@ -5266,8 +5362,9 @@ static unsigned quote_meta(char_u *dest, char_u *src, int len) break; FALLTHROUGH; case '~': - if (!p_magic) /* quote these only if magic is set */ + if (!p_magic) { // quote these only if magic is set break; + } FALLTHROUGH; case '\\': if (ctrl_x_mode == CTRL_X_DICTIONARY @@ -5277,24 +5374,24 @@ static unsigned quote_meta(char_u *dest, char_u *src, int len) case '^': // currently it's not needed. case '$': m++; - if (dest != NULL) + if (dest != NULL) { *dest++ = '\\'; + } break; } - if (dest != NULL) + if (dest != NULL) { *dest++ = *src; - /* Copy remaining bytes of a multibyte character. */ - if (has_mbyte) { - int i, mb_len; - - mb_len = (*mb_ptr2len)(src) - 1; - if (mb_len > 0 && len >= mb_len) - for (i = 0; i < mb_len; ++i) { - --len; - ++src; - if (dest != NULL) - *dest++ = *src; + } + // Copy remaining bytes of a multibyte character. + const int mb_len = utfc_ptr2len(src) - 1; + if (mb_len > 0 && len >= mb_len) { + for (int i = 0; i < mb_len; i++) { + len--; + src++; + if (dest != NULL) { + *dest++ = *src; } + } } } if (dest != NULL) @@ -5321,7 +5418,7 @@ int get_literal(void) if (got_int) return Ctrl_C; - ++no_mapping; /* don't map the next key hits */ + no_mapping++; // don't map the next key hits cc = 0; i = 0; for (;; ) { @@ -5359,20 +5456,23 @@ int get_literal(void) if (cc > 255 && unicode == 0 ) - cc = 255; /* limit range to 0-255 */ + cc = 255; // limit range to 0-255 nc = 0; - if (hex) { /* hex: up to two chars */ - if (i >= 2) + if (hex) { // hex: up to two chars + if (i >= 2) { break; - } else if (unicode) { /* Unicode: up to four or eight chars */ - if ((unicode == 'u' && i >= 4) || (unicode == 'U' && i >= 8)) + } + } else if (unicode) { // Unicode: up to four or eight chars + if ((unicode == 'u' && i >= 4) || (unicode == 'U' && i >= 8)) { break; - } else if (i >= 3) /* decimal or octal: up to three chars */ + } + } else if (i >= 3) { // decimal or octal: up to three chars break; + } } - if (i == 0) { /* no number entered */ - if (nc == K_ZERO) { /* NUL is stored as NL */ + if (i == 0) { // no number entered + if (nc == K_ZERO) { // NUL is stored as NL cc = '\n'; nc = 0; } else { @@ -5388,7 +5488,7 @@ int get_literal(void) --no_mapping; if (nc) vungetc(nc); - got_int = FALSE; /* CTRL-C typed after CTRL-V is not an interrupt */ + got_int = false; // CTRL-C typed after CTRL-V is not an interrupt return cc; } @@ -5451,11 +5551,10 @@ static void insert_special(int c, int allow_modmask, int ctrlv) * INSCHAR_DO_COM - format comments * INSCHAR_COM_LIST - format comments with num list or 2nd line indent */ -void -insertchar ( - int c, /* character to insert or NUL */ - int flags, /* INSCHAR_FORMAT, etc. */ - int second_indent /* indent for second line if >= 0 */ +void insertchar( + int c, // character to insert or NUL + int flags, // INSCHAR_FORMAT, etc. + int second_indent // indent for second line if >= 0 ) { int textwidth; @@ -5491,27 +5590,27 @@ insertchar ( || ((!has_format_option(FO_INS_LONG) || Insstart_textlen <= (colnr_T)textwidth) && (!fo_ins_blank - || Insstart_blank_vcol <= (colnr_T)textwidth - )))))) { - /* Format with 'formatexpr' when it's set. Use internal formatting - * when 'formatexpr' isn't set or it returns non-zero. */ - int do_internal = TRUE; + || Insstart_blank_vcol <= (colnr_T)textwidth)))))) { + // Format with 'formatexpr' when it's set. Use internal formatting + // when 'formatexpr' isn't set or it returns non-zero. + bool do_internal = true; colnr_T virtcol = get_nolist_virtcol() + char2cells(c != NUL ? c : gchar_cursor()); if (*curbuf->b_p_fex != NUL && (flags & INSCHAR_NO_FEX) == 0 && (force_format || virtcol > (colnr_T)textwidth)) { do_internal = (fex_format(curwin->w_cursor.lnum, 1L, c) != 0); - /* It may be required to save for undo again, e.g. when setline() - * was called. */ - ins_need_undo = TRUE; + // It may be required to save for undo again, e.g. when setline() + // was called. + ins_need_undo = true; } if (do_internal) internal_format(textwidth, second_indent, flags, c == NUL, c); } - if (c == NUL) /* only formatting was wanted */ + if (c == NUL) { // only formatting was wanted return; + } // Check whether this character should end a comment. if (did_ai && c == end_comment_pending) { @@ -5615,8 +5714,8 @@ insertchar ( buf[i++] = c; } - do_digraph(-1); /* clear digraphs */ - do_digraph(buf[i-1]); /* may be the start of a digraph */ + do_digraph(-1); // clear digraphs + do_digraph(buf[i-1]); // may be the start of a digraph buf[i] = NUL; ins_str(buf); if (flags & INSCHAR_CTRLV) { @@ -5659,7 +5758,7 @@ internal_format ( int second_indent, int flags, int format_only, - int c /* character to be inserted (can be NUL) */ + int c // character to be inserted (can be NUL) ) { int cc; @@ -5670,7 +5769,7 @@ internal_format ( int fo_white_par = has_format_option(FO_WHITE_PAR); int first_line = TRUE; colnr_T leader_len; - int no_leader = FALSE; + bool no_leader = false; int do_comments = (flags & INSCHAR_DO_COM); int has_lbr = curwin->w_p_lbr; @@ -5695,10 +5794,10 @@ internal_format ( * Repeat breaking lines, until the current line is not too long. */ while (!got_int) { - int startcol; /* Cursor column at entry */ - int wantcol; /* column at textwidth border */ - int foundcol; /* column for start of spaces */ - int end_foundcol = 0; /* column for start of word */ + int startcol; // Cursor column at entry + int wantcol; // column at textwidth border + int foundcol; // column for start of spaces + int end_foundcol = 0; // column for start of word colnr_T len; colnr_T virtcol; int orig_col = 0; @@ -5711,33 +5810,37 @@ internal_format ( if (virtcol <= (colnr_T)textwidth) break; - if (no_leader) - do_comments = FALSE; - else if (!(flags & INSCHAR_FORMAT) - && has_format_option(FO_WRAP_COMS)) - do_comments = TRUE; + if (no_leader) { + do_comments = false; + } else if (!(flags & INSCHAR_FORMAT) + && has_format_option(FO_WRAP_COMS)) { + do_comments = true; + } - /* Don't break until after the comment leader */ - if (do_comments) - leader_len = get_leader_len(get_cursor_line_ptr(), NULL, FALSE, TRUE); - else + // Don't break until after the comment leader + if (do_comments) { + leader_len = get_leader_len(get_cursor_line_ptr(), NULL, false, true); + } else { leader_len = 0; + } - /* If the line doesn't start with a comment leader, then don't - * start one in a following broken line. Avoids that a %word - * moved to the start of the next line causes all following lines - * to start with %. */ - if (leader_len == 0) - no_leader = TRUE; + // If the line doesn't start with a comment leader, then don't + // start one in a following broken line. Avoids that a %word + // moved to the start of the next line causes all following lines + // to start with %. + if (leader_len == 0) { + no_leader = true; + } if (!(flags & INSCHAR_FORMAT) && leader_len == 0 - && !has_format_option(FO_WRAP)) - + && !has_format_option(FO_WRAP)) { break; - if ((startcol = curwin->w_cursor.col) == 0) + } + if ((startcol = curwin->w_cursor.col) == 0) { break; + } - /* find column of textwidth border */ + // find column of textwidth border coladvance((colnr_T)textwidth); wantcol = curwin->w_cursor.col; @@ -5757,7 +5860,7 @@ internal_format ( else cc = gchar_cursor(); if (WHITECHAR(cc)) { - /* remember position of blank just before text */ + // remember position of blank just before text end_col = curwin->w_cursor.col; // find start of sequence of blanks @@ -5788,18 +5891,21 @@ internal_format ( } if (has_format_option(FO_ONE_LETTER)) { - /* do not break after one-letter words */ - if (curwin->w_cursor.col == 0) - break; /* one-letter word at begin */ - /* do not break "#a b" when 'tw' is 2 */ - if (curwin->w_cursor.col <= leader_len) + // do not break after one-letter words + if (curwin->w_cursor.col == 0) { + break; // one-letter word at begin + } + // do not break "#a b" when 'tw' is 2 + if (curwin->w_cursor.col <= leader_len) { break; + } col = curwin->w_cursor.col; dec_cursor(); cc = gchar_cursor(); - if (WHITECHAR(cc)) - continue; /* one-letter, continue */ + if (WHITECHAR(cc)) { + continue; // one-letter, continue + } curwin->w_cursor.col = col; } @@ -5810,14 +5916,15 @@ internal_format ( if (curwin->w_cursor.col <= (colnr_T)wantcol) break; } else if (cc >= 0x100 && fo_multibyte) { - /* Break after or before a multi-byte character. */ + // Break after or before a multi-byte character. if (curwin->w_cursor.col != startcol) { - /* Don't break until after the comment leader */ - if (curwin->w_cursor.col < leader_len) + // Don't break until after the comment leader + if (curwin->w_cursor.col < leader_len) { break; + } col = curwin->w_cursor.col; inc_cursor(); - /* Don't change end_foundcol if already set. */ + // Don't change end_foundcol if already set. if (foundcol != curwin->w_cursor.col) { foundcol = curwin->w_cursor.col; end_foundcol = foundcol; @@ -5835,11 +5942,13 @@ internal_format ( dec_cursor(); cc = gchar_cursor(); - if (WHITECHAR(cc)) - continue; /* break with space */ - /* Don't break until after the comment leader */ - if (curwin->w_cursor.col < leader_len) + if (WHITECHAR(cc)) { + continue; // break with space + } + // Don't break until after the comment leader + if (curwin->w_cursor.col < leader_len) { break; + } curwin->w_cursor.col = col; @@ -5853,12 +5962,12 @@ internal_format ( dec_cursor(); } - if (foundcol == 0) { /* no spaces, cannot break line */ + if (foundcol == 0) { // no spaces, cannot break line curwin->w_cursor.col = startcol; break; } - /* Going to break the line, remove any "$" now. */ + // Going to break the line, remove any "$" now. undisplay_dollar(); /* @@ -5866,10 +5975,11 @@ internal_format ( * stack functions. VREPLACE does not use this, and backspaces * over the text instead. */ - if (State & VREPLACE_FLAG) - orig_col = startcol; /* Will start backspacing from here */ - else + if (State & VREPLACE_FLAG) { + orig_col = startcol; // Will start backspacing from here + } else { replace_offset = startcol - end_foundcol; + } /* * adjust startcol for spaces that will be deleted and @@ -5892,13 +6002,15 @@ internal_format ( curwin->w_cursor.col = orig_col; saved_text[startcol] = NUL; - /* Backspace over characters that will move to the next line */ - if (!fo_white_par) + // Backspace over characters that will move to the next line + if (!fo_white_par) { backspace_until_column(foundcol); + } } else { - /* put cursor after pos. to break line */ - if (!fo_white_par) + // put cursor after pos. to break line + if (!fo_white_par) { curwin->w_cursor.col = foundcol; + } } /* @@ -5916,32 +6028,29 @@ internal_format ( replace_offset = 0; if (first_line) { if (!(flags & INSCHAR_COM_LIST)) { - /* - * This section is for auto-wrap of numeric lists. When not - * in insert mode (i.e. format_lines()), the INSCHAR_COM_LIST - * flag will be set and open_line() will handle it (as seen - * above). The code here (and in get_number_indent()) will - * recognize comments if needed... - */ - if (second_indent < 0 && has_format_option(FO_Q_NUMBER)) - second_indent = - get_number_indent(curwin->w_cursor.lnum - 1); + // This section is for auto-wrap of numeric lists. When not + // in insert mode (i.e. format_lines()), the INSCHAR_COM_LIST + // flag will be set and open_line() will handle it (as seen + // above). The code here (and in get_number_indent()) will + // recognize comments if needed... + if (second_indent < 0 && has_format_option(FO_Q_NUMBER)) { + second_indent = get_number_indent(curwin->w_cursor.lnum - 1); + } if (second_indent >= 0) { - if (State & VREPLACE_FLAG) - change_indent(INDENT_SET, second_indent, - FALSE, NUL, TRUE); - else if (leader_len > 0 && second_indent - leader_len > 0) { - int i; + if (State & VREPLACE_FLAG) { + change_indent(INDENT_SET, second_indent, false, NUL, true); + } else if (leader_len > 0 && second_indent - leader_len > 0) { int padding = second_indent - leader_len; - /* We started at the first_line of a numbered list - * that has a comment. the open_line() function has - * inserted the proper comment leader and positioned - * the cursor at the end of the split line. Now we - * add the additional whitespace needed after the - * comment leader for the numbered list. */ - for (i = 0; i < padding; i++) + // We started at the first_line of a numbered list + // that has a comment. the open_line() function has + // inserted the proper comment leader and positioned + // the cursor at the end of the split line. Now we + // add the additional whitespace needed after the + // comment leader for the numbered list. + for (int i = 0; i < padding; i++) { ins_str((char_u *)" "); + } changed_bytes(curwin->w_cursor.lnum, leader_len); } else { (void)set_indent(second_indent, SIN_CHANGED); @@ -5979,8 +6088,9 @@ internal_format ( line_breakcheck(); } - if (save_char != NUL) /* put back space after cursor */ + if (save_char != NUL) { // put back space after cursor pchar_cursor(save_char); + } curwin->w_p_lbr = has_lbr; @@ -5997,10 +6107,9 @@ internal_format ( * The caller must have saved the cursor line for undo, following ones will be * saved here. */ -void -auto_format ( - int trailblank, /* when TRUE also format with trailing blank */ - int prev_line /* may start in previous line */ +void auto_format( + bool trailblank, // when true also format with trailing blank + bool prev_line // may start in previous line ) { pos_T pos; @@ -6019,11 +6128,11 @@ auto_format ( // may remove added space check_auto_format(false); - /* Don't format in Insert mode when the cursor is on a trailing blank, the - * user might insert normal text next. Also skip formatting when "1" is - * in 'formatoptions' and there is a single character before the cursor. - * Otherwise the line would be broken and when typing another non-white - * next they are not joined back together. */ + // Don't format in Insert mode when the cursor is on a trailing blank, the + // user might insert normal text next. Also skip formatting when "1" is + // in 'formatoptions' and there is a single character before the cursor. + // Otherwise the line would be broken and when typing another non-white + // next they are not joined back together. wasatend = (pos.col == (colnr_T)STRLEN(old)); if (*old != NUL && !trailblank && wasatend) { dec_cursor(); @@ -6066,16 +6175,16 @@ auto_format ( saved_cursor.lnum = 0; if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { - /* "cannot happen" */ + // "cannot happen" curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; coladvance((colnr_T)MAXCOL); } else check_cursor_col(); - /* Insert mode: If the cursor is now after the end of the line while it - * previously wasn't, the line was broken. Because of the rule above we - * need to add a space when 'w' is in 'formatoptions' to keep a paragraph - * formatted. */ + // Insert mode: If the cursor is now after the end of the line while it + // previously wasn't, the line was broken. Because of the rule above we + // need to add a space when 'w' is in 'formatoptions' to keep a paragraph + // formatted. if (!wasatend && has_format_option(FO_WHITE_PAR)) { new = get_cursor_line_ptr(); len = (colnr_T)STRLEN(new); @@ -6134,22 +6243,21 @@ static void check_auto_format( * if invalid value, use 0. * Set default to window width (maximum 79) for "gq" operator. */ -int -comp_textwidth ( - int ff /* force formatting (for "gq" command) */ +int comp_textwidth( + int ff // force formatting (for "gq" command) ) { int textwidth; textwidth = curbuf->b_p_tw; if (textwidth == 0 && curbuf->b_p_wm) { - /* The width is the window width minus 'wrapmargin' minus all the - * things that add to the margin. */ + // The width is the window width minus 'wrapmargin' minus all the + // things that add to the margin. textwidth = curwin->w_width_inner - curbuf->b_p_wm; if (cmdwin_type != 0) { textwidth -= 1; } - textwidth -= curwin->w_p_fdc; + textwidth -= win_fdccol_count(curwin); textwidth -= win_signcol_count(curwin); if (curwin->w_p_nu || curwin->w_p_rnu) @@ -6185,7 +6293,9 @@ static void redo_literal(int c) // start_arrow() is called when an arrow key is used in insert mode. // For undo/redo it resembles hitting the <ESC> key. -static void start_arrow(pos_T *end_insert_pos /* can be NULL */) +static void start_arrow( + pos_T *end_insert_pos // can be NULL +) { start_arrow_common(end_insert_pos, true); } @@ -6258,8 +6368,8 @@ int stop_arrow(void) Insstart_textlen = (colnr_T)linetabsize(get_cursor_line_ptr()); if (u_save_cursor() == OK) { - arrow_used = FALSE; - ins_need_undo = FALSE; + arrow_used = false; + ins_need_undo = false; } ai_col = 0; if (State & VREPLACE_FLAG) { @@ -6270,11 +6380,12 @@ int stop_arrow(void) AppendToRedobuff("1i"); // Pretend we start an insertion. new_insert_skip = 2; } else if (ins_need_undo) { - if (u_save_cursor() == OK) - ins_need_undo = FALSE; + if (u_save_cursor() == OK) { + ins_need_undo = false; + } } - /* Always open fold at the cursor line when inserting something. */ + // Always open fold at the cursor line when inserting something. foldOpenCursor(); return arrow_used || ins_need_undo ? FAIL : OK; @@ -6288,15 +6399,15 @@ int stop_arrow(void) static void stop_insert ( pos_T *end_insert_pos, - int esc, /* called by ins_esc() */ - int nomove /* <c-\><c-o>, don't move cursor */ + int esc, // called by ins_esc() + int nomove // <c-\><c-o>, don't move cursor ) { int cc; char_u *ptr; stop_redo_ins(); - replace_flush(); /* abandon replace stack */ + replace_flush(); // abandon replace stack /* * Save the inserted text for later redo with ^@ and CTRL-A. @@ -6313,16 +6424,16 @@ stop_insert ( xfree(ptr); if (!arrow_used && end_insert_pos != NULL) { - /* Auto-format now. It may seem strange to do this when stopping an - * insertion (or moving the cursor), but it's required when appending - * a line and having it end in a space. But only do it when something - * was actually inserted, otherwise undo won't work. */ + // Auto-format now. It may seem strange to do this when stopping an + // insertion (or moving the cursor), but it's required when appending + // a line and having it end in a space. But only do it when something + // was actually inserted, otherwise undo won't work. if (!ins_need_undo && has_format_option(FO_AUTO)) { pos_T tpos = curwin->w_cursor; - /* When the cursor is at the end of the line after a space the - * formatting will move it to the following word. Avoid that by - * moving the cursor onto the space. */ + // When the cursor is at the end of the line after a space the + // formatting will move it to the following word. Avoid that by + // moving the cursor onto the space. cc = 'x'; if (curwin->w_cursor.col > 0 && gchar_cursor() == NUL) { dec_cursor(); @@ -6348,11 +6459,11 @@ stop_insert ( // If a space was inserted for auto-formatting, remove it now. check_auto_format(true); - /* If we just did an auto-indent, remove the white space from the end - * of the line, and put the cursor back. - * Do this when ESC was used or moving the cursor up/down. - * Check for the old position still being valid, just in case the text - * got changed unexpectedly. */ + // If we just did an auto-indent, remove the white space from the end + // of the line, and put the cursor back. + // Do this when ESC was used or moving the cursor up/down. + // Check for the old position still being valid, just in case the text + // got changed unexpectedly. if (!nomove && did_ai && (esc || (vim_strchr(p_cpo, CPO_INDENT) == NULL && curwin->w_cursor.lnum != end_insert_pos->lnum)) @@ -6360,7 +6471,7 @@ stop_insert ( pos_T tpos = curwin->w_cursor; curwin->w_cursor = *end_insert_pos; - check_cursor_col(); /* make sure it is not past the line */ + check_cursor_col(); // make sure it is not past the line for (;; ) { if (gchar_cursor() == NUL && curwin->w_cursor.col > 0) --curwin->w_cursor.col; @@ -6372,10 +6483,10 @@ stop_insert ( break; // should not happen } } - if (curwin->w_cursor.lnum != tpos.lnum) + if (curwin->w_cursor.lnum != tpos.lnum) { curwin->w_cursor = tpos; - else { - /* reset tpos, could have been invalidated in the loop above */ + } else { + // reset tpos, could have been invalidated in the loop above tpos = curwin->w_cursor; tpos.col++; if (cc != NUL && gchar_pos(&tpos) == NUL) { @@ -6383,8 +6494,8 @@ stop_insert ( } } - /* <C-S-Right> may have started Visual mode, adjust the position for - * deleted characters. */ + // <C-S-Right> may have started Visual mode, adjust the position for + // deleted characters. if (VIsual_active && VIsual.lnum == curwin->w_cursor.lnum) { int len = (int)STRLEN(get_cursor_line_ptr()); @@ -6400,8 +6511,8 @@ stop_insert ( can_si = false; can_si_back = false; - /* Set '[ and '] to the inserted text. When end_insert_pos is NULL we are - * now in a different buffer. */ + // Set '[ and '] to the inserted text. When end_insert_pos is NULL we are + // now in a different buffer. if (end_insert_pos != NULL) { curbuf->b_op_start = Insstart; curbuf->b_op_start_orig = Insstart_orig; @@ -6420,9 +6531,10 @@ void set_last_insert(int c) xfree(last_insert); last_insert = xmalloc(MB_MAXBYTES * 3 + 5); s = last_insert; - /* Use the CTRL-V only when entering a special char */ - if (c < ' ' || c == DEL) + // Use the CTRL-V only when entering a special char + if (c < ' ' || c == DEL) { *s++ = Ctrl_V; + } s = add_char2buf(c, s); *s++ = ESC; *s++ = NUL; @@ -6508,7 +6620,7 @@ int oneright(void) if (virtual_active()) { pos_T prevpos = curwin->w_cursor; - /* Adjust for multi-wide char (excluding TAB) */ + // Adjust for multi-wide char (excluding TAB) ptr = get_cursor_pos_ptr(); coladvance(getviscol() + ((*ptr != TAB && vim_isprintc(utf_ptr2char(ptr))) ? ptr2cells(ptr) : 1)); @@ -6519,20 +6631,18 @@ int oneright(void) } ptr = get_cursor_pos_ptr(); - if (*ptr == NUL) - return FAIL; /* already at the very end */ + if (*ptr == NUL) { + return FAIL; // already at the very end + } - if (has_mbyte) - l = (*mb_ptr2len)(ptr); - else - l = 1; + l = utfc_ptr2len(ptr); - /* move "l" bytes right, but don't end up on the NUL, unless 'virtualedit' - * contains "onemore". */ + // move "l" bytes right, but don't end up on the NUL, unless 'virtualedit' + // contains "onemore". if (ptr[l] == NUL - && (ve_flags & VE_ONEMORE) == 0 - ) + && (ve_flags & VE_ONEMORE) == 0) { return FAIL; + } curwin->w_cursor.col += l; curwin->w_set_curswant = TRUE; @@ -6548,25 +6658,23 @@ int oneleft(void) if (v == 0) return FAIL; - /* We might get stuck on 'showbreak', skip over it. */ + // We might get stuck on 'showbreak', skip over it. width = 1; for (;; ) { coladvance(v - width); - /* getviscol() is slow, skip it when 'showbreak' is empty, - 'breakindent' is not set and there are no multi-byte - characters */ - if ((*p_sbr == NUL - && !curwin->w_p_bri - && !has_mbyte - ) || getviscol() < v) + // getviscol() is slow, skip it when 'showbreak' is empty, + // 'breakindent' is not set and there are no multi-byte + // characters + if (getviscol() < v) { break; - ++width; + } + width++; } if (curwin->w_cursor.coladd == 1) { char_u *ptr; - /* Adjust for multi-wide char (not a TAB) */ + // Adjust for multi-wide char (not a TAB) ptr = get_cursor_pos_ptr(); if (*ptr != TAB && vim_isprintc(utf_ptr2char(ptr)) && ptr2cells(ptr) > 1) { @@ -6584,17 +6692,16 @@ int oneleft(void) curwin->w_set_curswant = TRUE; --curwin->w_cursor.col; - /* if the character on the left of the current cursor is a multi-byte - * character, move to its first byte */ - if (has_mbyte) - mb_adjust_cursor(); + // if the character on the left of the current cursor is a multi-byte + // character, move to its first byte + mb_adjust_cursor(); return OK; } int cursor_up ( long n, - int upd_topline /* When TRUE: update topline */ + int upd_topline // When TRUE: update topline ) { linenr_T lnum; @@ -6612,19 +6719,21 @@ cursor_up ( /* * Count each sequence of folded lines as one logical line. */ - /* go to the start of the current fold */ + // go to the start of the current fold (void)hasFolding(lnum, &lnum, NULL); while (n--) { - /* move up one line */ - --lnum; - if (lnum <= 1) + // move up one line + lnum--; + if (lnum <= 1) { break; - /* If we entered a fold, move to the beginning, unless in - * Insert mode or when 'foldopen' contains "all": it will open - * in a moment. */ - if (n > 0 || !((State & INSERT) || (fdo_flags & FDO_ALL))) + } + // If we entered a fold, move to the beginning, unless in + // Insert mode or when 'foldopen' contains "all": it will open + // in a moment. + if (n > 0 || !((State & INSERT) || (fdo_flags & FDO_ALL))) { (void)hasFolding(lnum, &lnum, NULL); + } } if (lnum < 1) lnum = 1; @@ -6633,11 +6742,12 @@ cursor_up ( curwin->w_cursor.lnum = lnum; } - /* try to advance to the column we want to be at */ + // try to advance to the column we want to be at coladvance(curwin->w_curswant); - if (upd_topline) - update_topline(); /* make sure curwin->w_topline is valid */ + if (upd_topline) { + update_topline(); // make sure curwin->w_topline is valid + } return OK; } @@ -6648,14 +6758,14 @@ cursor_up ( int cursor_down ( long n, - int upd_topline /* When TRUE: update topline */ + int upd_topline // When TRUE: update topline ) { linenr_T lnum; if (n > 0) { lnum = curwin->w_cursor.lnum; - /* Move to last line of fold, will fail if it's the end-of-file. */ + // Move to last line of fold, will fail if it's the end-of-file. (void)hasFolding(lnum, NULL, &lnum); // This fails if the cursor is already in the last line. @@ -6667,7 +6777,7 @@ cursor_down ( else if (hasAnyFolding(curwin)) { linenr_T last; - /* count each sequence of folded lines as one logical line */ + // count each sequence of folded lines as one logical line while (n--) { if (hasFolding(lnum, NULL, &last)) lnum = last + 1; @@ -6683,11 +6793,12 @@ cursor_down ( curwin->w_cursor.lnum = lnum; } - /* try to advance to the column we want to be at */ + // try to advance to the column we want to be at coladvance(curwin->w_curswant); - if (upd_topline) - update_topline(); /* make sure curwin->w_topline is valid */ + if (upd_topline) { + update_topline(); // make sure curwin->w_topline is valid + } return OK; } @@ -6697,11 +6808,10 @@ cursor_down ( * Last_insert actually is a copy of the redo buffer, so we * first have to remove the command. */ -int -stuff_inserted ( - int c, /* Command character to be inserted */ - long count, /* Repeat this many times */ - int no_esc /* Don't add an ESC at the end */ +int stuff_inserted( + int c, // Command character to be inserted + long count, // Repeat this many times + int no_esc // Don't add an ESC at the end ) { char_u *esc_ptr; @@ -6715,18 +6825,18 @@ stuff_inserted ( return FAIL; } - /* may want to stuff the command character, to start Insert mode */ - if (c != NUL) + // may want to stuff the command character, to start Insert mode + if (c != NUL) { stuffcharReadbuff(c); + } if ((esc_ptr = STRRCHR(ptr, ESC)) != NULL) { // remove the ESC. *esc_ptr = NUL; } - /* when the last char is either "0" or "^" it will be quoted if no ESC - * comes after it OR if it will inserted more than once and "ptr" - * starts with ^D. -- Acevedo - */ + // when the last char is either "0" or "^" it will be quoted if no ESC + // comes after it OR if it will inserted more than once and "ptr" + // starts with ^D. -- Acevedo last_ptr = (esc_ptr ? esc_ptr : ptr + STRLEN(ptr)) - 1; if (last_ptr >= ptr && (*last_ptr == '0' || *last_ptr == '^') && (no_esc || (*ptr == Ctrl_D && count > 1))) { @@ -6747,12 +6857,14 @@ stuff_inserted ( if (last) *last_ptr = last; - if (esc_ptr != NULL) - *esc_ptr = ESC; /* put the ESC back */ + if (esc_ptr != NULL) { + *esc_ptr = ESC; // put the ESC back + } - /* may want to stuff a trailing ESC, to get out of Insert mode */ - if (!no_esc) + // may want to stuff a trailing ESC, to get out of Insert mode + if (!no_esc) { stuffcharReadbuff(ESC); + } return OK; } @@ -6777,8 +6889,9 @@ char_u *get_last_insert_save(void) return NULL; s = vim_strsave(last_insert + last_insert_skip); len = (int)STRLEN(s); - if (len > 0 && s[len - 1] == ESC) /* remove trailing ESC */ + if (len > 0 && s[len - 1] == ESC) { // remove trailing ESC s[len - 1] = NUL; + } return s; } @@ -6820,8 +6933,8 @@ static bool echeck_abbr(int c) */ static char_u *replace_stack = NULL; -static ssize_t replace_stack_nr = 0; /* next entry in replace stack */ -static ssize_t replace_stack_len = 0; /* max. number of entries */ +static ssize_t replace_stack_nr = 0; // next entry in replace stack +static ssize_t replace_stack_len = 0; // max. number of entries /// Push character that is replaced onto the the replace stack. /// @@ -6875,9 +6988,8 @@ static int replace_pop(void) * Join the top two items on the replace stack. This removes to "off"'th NUL * encountered. */ -static void -replace_join ( - int off /* offset for which NUL to remove */ +static void replace_join( + int off // offset for which NUL to remove ) { int i; @@ -6900,7 +7012,7 @@ static void replace_pop_ins(void) int cc; int oldState = State; - State = NORMAL; /* don't want REPLACE here */ + State = NORMAL; // don't want REPLACE here while ((cc = replace_pop()) > 0) { mb_replace_pop_ins(cc); dec_cursor(); @@ -6919,7 +7031,7 @@ static void mb_replace_pop_ins(int cc) int i; int c; - if (has_mbyte && (n = MB_BYTE2LEN(cc)) > 1) { + if ((n = MB_BYTE2LEN(cc)) > 1) { buf[0] = cc; for (i = 1; i < n; ++i) buf[i] = replace_pop(); @@ -6928,31 +7040,33 @@ static void mb_replace_pop_ins(int cc) ins_char(cc); } - if (enc_utf8) - /* Handle composing chars. */ - for (;; ) { - c = replace_pop(); - if (c == -1) /* stack empty */ - break; - if ((n = MB_BYTE2LEN(c)) == 1) { - /* Not a multi-byte char, put it back. */ - replace_push(c); - break; + // Handle composing chars. + for (;; ) { + c = replace_pop(); + if (c == -1) { // stack empty + break; + } + if ((n = MB_BYTE2LEN(c)) == 1) { + // Not a multi-byte char, put it back. + replace_push(c); + break; + } else { + buf[0] = c; + assert(n > 1); + for (i = 1; i < n; i++) { + buf[i] = replace_pop(); + } + if (utf_iscomposing(utf_ptr2char(buf))) { + ins_bytes_len(buf, n); } else { - buf[0] = c; - assert(n > 1); - for (i = 1; i < n; ++i) - buf[i] = replace_pop(); - if (utf_iscomposing(utf_ptr2char(buf))) - ins_bytes_len(buf, n); - else { - /* Not a composing char, put it back. */ - for (i = n - 1; i >= 0; --i) - replace_push(buf[i]); - break; + // Not a composing char, put it back. + for (i = n - 1; i >= 0; i--) { + replace_push(buf[i]); } + break; } } + } } /* @@ -6990,8 +7104,8 @@ static void replace_do_bs(int limit_col) cc = replace_pop(); if (cc > 0) { if (l_State & VREPLACE_FLAG) { - /* Get the number of screen cells used by the character we are - * going to delete. */ + // Get the number of screen cells used by the character we are + // going to delete. getvcol(curwin, &curwin->w_cursor, NULL, &start_vcol, NULL); orig_vcols = chartabsize(get_cursor_pos_ptr(), start_vcol); } @@ -7008,7 +7122,7 @@ static void replace_do_bs(int limit_col) replace_pop_ins(); if (l_State & VREPLACE_FLAG) { - /* Get the number of screen cells used by the inserted characters */ + // Get the number of screen cells used by the inserted characters p = get_cursor_pos_ptr(); ins_len = (int)STRLEN(p) - orig_len; vcol = start_vcol; @@ -7018,8 +7132,8 @@ static void replace_do_bs(int limit_col) } vcol -= start_vcol; - /* Delete spaces that were inserted after the cursor to keep the - * text aligned. */ + // Delete spaces that were inserted after the cursor to keep the + // text aligned. curwin->w_cursor.col += ins_len; while (vcol > orig_vcols && gchar_cursor() == ' ') { del_char(false); @@ -7028,7 +7142,7 @@ static void replace_do_bs(int limit_col) curwin->w_cursor.col -= ins_len; } - /* mark the buffer as changed and prepare for displaying */ + // mark the buffer as changed and prepare for displaying changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col); } else if (cc == 0) (void)del_char_after_col(limit_col); @@ -7096,10 +7210,11 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) return false; } - if (*curbuf->b_p_inde != NUL) - look = curbuf->b_p_indk; /* 'indentexpr' set: use 'indentkeys' */ - else - look = curbuf->b_p_cink; /* 'indentexpr' empty: use 'cinkeys' */ + if (*curbuf->b_p_inde != NUL) { + look = curbuf->b_p_indk; // 'indentexpr' set: use 'indentkeys' + } else { + look = curbuf->b_p_cink; // 'indentexpr' empty: use 'cinkeys' + } while (*look) { /* * Find out if we want to try a match with this key, depending on @@ -7298,10 +7413,12 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) */ int hkmap(int c) { - if (p_hkmapp) { /* phonetic mapping, by Ilya Dogolazky */ - enum {hALEF=0, BET, GIMEL, DALET, HEI, VAV, ZAIN, HET, TET, IUD, - KAFsofit, hKAF, LAMED, MEMsofit, MEM, NUNsofit, NUN, SAMEH, AIN, - PEIsofit, PEI, ZADIsofit, ZADI, KOF, RESH, hSHIN, TAV}; + if (p_hkmapp) { // phonetic mapping, by Ilya Dogolazky + enum { + hALEF = 0, BET, GIMEL, DALET, HEI, VAV, ZAIN, HET, TET, IUD, + KAFsofit, hKAF, LAMED, MEMsofit, MEM, NUNsofit, NUN, SAMEH, AIN, + PEIsofit, PEI, ZADIsofit, ZADI, KOF, RESH, hSHIN, TAV + }; static char_u map[26] = {(char_u)hALEF /*a*/, (char_u)BET /*b*/, (char_u)hKAF /*c*/, (char_u)DALET /*d*/, (char_u)-1 /*e*/, (char_u)PEIsofit /*f*/, @@ -7313,28 +7430,27 @@ int hkmap(int c) (char_u)VAV /*v*/, (char_u)hSHIN /*w*/, (char_u)-1 /*x*/, (char_u)AIN /*y*/, (char_u)ZADI /*z*/}; - if (c == 'N' || c == 'M' || c == 'P' || c == 'C' || c == 'Z') + if (c == 'N' || c == 'M' || c == 'P' || c == 'C' || c == 'Z') { return (int)(map[CharOrd(c)] - 1 + p_aleph); - /* '-1'='sofit' */ - else if (c == 'x') + } else if (c == 'x') { // '-1'='sofit' return 'X'; - else if (c == 'q') - return '\''; /* {geresh}={'} */ - else if (c == 246) - return ' '; /* \"o --> ' ' for a german keyboard */ - else if (c == 228) - return ' '; /* \"a --> ' ' -- / -- */ - else if (c == 252) - return ' '; /* \"u --> ' ' -- / -- */ - /* NOTE: islower() does not do the right thing for us on Linux so we - * do this the same was as 5.7 and previous, so it works correctly on - * all systems. Specifically, the e.g. Delete and Arrow keys are - * munged and won't work if e.g. searching for Hebrew text. - */ - else if (c >= 'a' && c <= 'z') + } else if (c == 'q') { + return '\''; // {geresh}={'} + } else if (c == 246) { + return ' '; // \"o --> ' ' for a german keyboard + } else if (c == 228) { + return ' '; // \"a --> ' ' -- / -- + } else if (c == 252) { + return ' '; // \"u --> ' ' -- / -- + } else if (c >= 'a' && c <= 'z') { + // NOTE: islower() does not do the right thing for us on Linux so we + // do this the same was as 5.7 and previous, so it works correctly on + // all systems. Specifically, the e.g. Delete and Arrow keys are + // munged and won't work if e.g. searching for Hebrew text. return (int)(map[CharOrdLow(c)] + p_aleph); - else + } else { return c; + } } else { switch (c) { case '`': return ';'; @@ -7343,7 +7459,7 @@ int hkmap(int c) case 'q': return '/'; case 'w': return '\''; - /* Hebrew letters - set offset from 'a' */ + // Hebrew letters - set offset from 'a' case ',': c = '{'; break; case '.': c = 'v'; break; case ';': c = 't'; break; @@ -7373,10 +7489,10 @@ static void ins_reg(void) */ pc_status = PC_STATUS_UNSET; if (redrawing() && !char_avail()) { - /* may need to redraw when no more chars available now */ - ins_redraw(FALSE); + // may need to redraw when no more chars available now + ins_redraw(false); - edit_putchar('"', TRUE); + edit_putchar('"', true); add_to_showcmd_c(Ctrl_R); } @@ -7389,7 +7505,7 @@ static void ins_reg(void) regname = plain_vgetc(); LANGMAP_ADJUST(regname, TRUE); if (regname == Ctrl_R || regname == Ctrl_O || regname == Ctrl_P) { - /* Get a third key for literal register insertion */ + // Get a third key for literal register insertion literally = regname; add_to_showcmd_c(literally); regname = plain_vgetc(); @@ -7397,9 +7513,9 @@ static void ins_reg(void) } --no_mapping; - /* Don't call u_sync() while typing the expression or giving an error - * message for it. Only call it explicitly. */ - ++no_u_sync; + // Don't call u_sync() while typing the expression or giving an error + // message for it. Only call it explicitly. + no_u_sync++; if (regname == '=') { pos_T curpos = curwin->w_cursor; @@ -7418,7 +7534,7 @@ static void ins_reg(void) need_redraw = true; // remove the '"' } else { if (literally == Ctrl_O || literally == Ctrl_P) { - /* Append the command to the redo buffer. */ + // Append the command to the redo buffer. AppendCharToRedobuff(Ctrl_R); AppendCharToRedobuff(literally); AppendCharToRedobuff(regname); @@ -7435,19 +7551,22 @@ static void ins_reg(void) need_redraw = true; } } - --no_u_sync; - if (u_sync_once == 1) - ins_need_undo = TRUE; + no_u_sync--; + if (u_sync_once == 1) { + ins_need_undo = true; + } u_sync_once = 0; clear_showcmd(); - /* If the inserted register is empty, we need to remove the '"' */ - if (need_redraw || stuff_empty()) + // If the inserted register is empty, we need to remove the '"' + if (need_redraw || stuff_empty()) { edit_unputchar(); + } - /* Disallow starting Visual mode here, would get a weird mode. */ - if (!vis_active && VIsual_active) + // Disallow starting Visual mode here, would get a weird mode. + if (!vis_active && VIsual_active) { end_visual_mode(); + } } /* @@ -7457,7 +7576,7 @@ static void ins_ctrl_g(void) { int c; - /* Right after CTRL-X the cursor will be after the ruler. */ + // Right after CTRL-X the cursor will be after the ruler. setcursor(); /* @@ -7468,24 +7587,25 @@ static void ins_ctrl_g(void) c = plain_vgetc(); --no_mapping; switch (c) { - /* CTRL-G k and CTRL-G <Up>: cursor up to Insstart.col */ + // CTRL-G k and CTRL-G <Up>: cursor up to Insstart.col case K_UP: case Ctrl_K: case 'k': ins_up(TRUE); break; - /* CTRL-G j and CTRL-G <Down>: cursor down to Insstart.col */ + // CTRL-G j and CTRL-G <Down>: cursor down to Insstart.col case K_DOWN: case Ctrl_J: case 'j': ins_down(TRUE); break; - /* CTRL-G u: start new undoable edit */ - case 'u': u_sync(TRUE); - ins_need_undo = TRUE; + // CTRL-G u: start new undoable edit + case 'u': + u_sync(true); + ins_need_undo = true; - /* Need to reset Insstart, esp. because a BS that joins - * a line to the previous one must save for undo. */ + // Need to reset Insstart, esp. because a BS that joins + // a line to the previous one must save for undo. update_Insstart_orig = false; Insstart = curwin->w_cursor; break; @@ -7497,7 +7617,7 @@ static void ins_ctrl_g(void) dont_sync_undo = kNone; break; - /* Unknown CTRL-G command, reserved for future expansion. */ + // Unknown CTRL-G command, reserved for future expansion. default: vim_beep(BO_CTRLG); } } @@ -7519,7 +7639,7 @@ static void ins_ctrl_hat(void) } set_iminsert_global(); showmode(); - /* Show/unshow value of 'keymap' in status lines. */ + // Show/unshow value of 'keymap' in status lines. status_redraw_curbuf(); } @@ -7560,10 +7680,11 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) *count = 0; } - if (--*count > 0) { /* repeat what was typed */ - /* Vi repeats the insert without replacing characters. */ - if (vim_strchr(p_cpo, CPO_REPLCNT) != NULL) + if (--*count > 0) { // repeat what was typed + // Vi repeats the insert without replacing characters. + if (vim_strchr(p_cpo, CPO_REPLCNT) != NULL) { State &= ~REPLACE_FLAG; + } (void)start_redo_ins(); if (cmdchar == 'r' || cmdchar == 'v') { @@ -7578,12 +7699,13 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) undisplay_dollar(); } - /* When an autoindent was removed, curswant stays after the - * indent */ - if (restart_edit == NUL && (colnr_T)temp == curwin->w_cursor.col) - curwin->w_set_curswant = TRUE; + // When an autoindent was removed, curswant stays after the + // indent + if (restart_edit == NUL && (colnr_T)temp == curwin->w_cursor.col) { + curwin->w_set_curswant = true; + } - /* Remember the last Insert position in the '^ mark. */ + // Remember the last Insert position in the '^ mark. if (!cmdmod.keepjumps) { RESET_FMARK(&curbuf->b_last_insert, curwin->w_cursor, curbuf->b_fnum); } @@ -7604,23 +7726,23 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) ) { if (curwin->w_cursor.coladd > 0 || ve_flags == VE_ALL) { oneleft(); - if (restart_edit != NUL) - ++curwin->w_cursor.coladd; + if (restart_edit != NUL) { + curwin->w_cursor.coladd++; + } } else { - --curwin->w_cursor.col; - /* Correct cursor for multi-byte character. */ - if (has_mbyte) - mb_adjust_cursor(); + curwin->w_cursor.col--; + // Correct cursor for multi-byte character. + mb_adjust_cursor(); } } State = NORMAL; - /* need to position cursor again (e.g. when on a TAB ) */ + // need to position cursor again (e.g. when on a TAB ) changed_cline_bef_curs(); setmouse(); - ui_cursor_shape(); /* may show different cursor shape */ + ui_cursor_shape(); // may show different cursor shape // When recording or for CTRL-O, need to display the new mode. // Otherwise remove the mode message. @@ -7716,7 +7838,7 @@ static void ins_insert(int replaceState) } AppendCharToRedobuff(K_INS); showmode(); - ui_cursor_shape(); /* may show different cursor shape */ + ui_cursor_shape(); // may show different cursor shape } /* @@ -7730,10 +7852,11 @@ static void ins_ctrl_o(void) restart_edit = 'R'; else restart_edit = 'I'; - if (virtual_active()) - ins_at_eol = FALSE; /* cursor always keeps its column */ - else + if (virtual_active()) { + ins_at_eol = false; // cursor always keeps its column + } else { ins_at_eol = (gchar_cursor() == NUL); + } } /* @@ -7815,11 +7938,12 @@ static void ins_bs_one(colnr_T *vcolp) dec_cursor(); getvcol(curwin, &curwin->w_cursor, vcolp, NULL, NULL); if (State & REPLACE_FLAG) { - /* Don't delete characters before the insert point when in - * Replace mode */ + // Don't delete characters before the insert point when in + // Replace mode if (curwin->w_cursor.lnum != Insstart.lnum - || curwin->w_cursor.col >= Insstart.col) + || curwin->w_cursor.col >= Insstart.col) { replace_do_bs(-1); + } } else { (void)del_char(false); } @@ -7838,13 +7962,13 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) { linenr_T lnum; int cc; - int temp = 0; /* init for GCC */ + int temp = 0; // init for GCC colnr_T save_col; colnr_T mincol; bool did_backspace = false; int in_indent; int oldState; - int cpc[MAX_MCO]; /* composing characters */ + int cpc[MAX_MCO]; // composing characters // can't delete anything in an empty file // can't backup past first character in buffer @@ -7908,23 +8032,22 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) * cc >= 0: NL was replaced, put original characters back */ cc = -1; - if (State & REPLACE_FLAG) - cc = replace_pop(); /* returns -1 if NL was inserted */ - /* - * In replace mode, in the line we started replacing, we only move the - * cursor. - */ + if (State & REPLACE_FLAG) { + cc = replace_pop(); // returns -1 if NL was inserted + } + // In replace mode, in the line we started replacing, we only move the + // cursor. if ((State & REPLACE_FLAG) && curwin->w_cursor.lnum <= lnum) { dec_cursor(); } else { if (!(State & VREPLACE_FLAG) || curwin->w_cursor.lnum > orig_line_count) { - temp = gchar_cursor(); /* remember current char */ - --curwin->w_cursor.lnum; + temp = gchar_cursor(); // remember current char + curwin->w_cursor.lnum--; - /* When "aw" is in 'formatoptions' we must delete the space at - * the end of the line, otherwise the line will be broken - * again when auto-formatting. */ + // When "aw" is in 'formatoptions' we must delete the space at + // the end of the line, otherwise the line will be broken + // again when auto-formatting. if (has_format_option(FO_AUTO) && has_format_option(FO_WHITE_PAR)) { char_u *ptr = ml_get_buf(curbuf, curwin->w_cursor.lnum, @@ -7965,20 +8088,19 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) curwin->w_cursor.col = save_col; cc = replace_pop(); } - /* restore the characters that NL replaced */ + // restore the characters that NL replaced replace_pop_ins(); State = oldState; } } did_ai = false; } else { - /* - * Delete character(s) before the cursor. - */ - if (revins_on) /* put cursor on last inserted char */ + // Delete character(s) before the cursor. + if (revins_on) { // put cursor on last inserted char dec_cursor(); + } mincol = 0; - /* keep indent */ + // keep indent if (mode == BACKSPACE_LINE && (curbuf->b_p_ai || cindent_on() @@ -8013,9 +8135,9 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) ts = get_sw_value(curbuf); else ts = get_sts_value(); - /* Compute the virtual column where we want to be. Since - * 'showbreak' may get in the way, need to get the last column of - * the previous character. */ + // Compute the virtual column where we want to be. Since + // 'showbreak' may get in the way, need to get the last column of + // the previous character. getvcol(curwin, &curwin->w_cursor, &vcol, NULL, NULL); start_vcol = vcol; dec_cursor(); @@ -8023,14 +8145,15 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) inc_cursor(); want_vcol = (want_vcol / ts) * ts; - /* delete characters until we are at or before want_vcol */ + // delete characters until we are at or before want_vcol while (vcol > want_vcol - && (cc = *(get_cursor_pos_ptr() - 1), ascii_iswhite(cc))) + && (cc = *(get_cursor_pos_ptr() - 1), ascii_iswhite(cc))) { ins_bs_one(&vcol); + } - /* insert extra spaces until we are at want_vcol */ + // insert extra spaces until we are at want_vcol while (vcol < want_vcol) { - /* Remember the first char we inserted */ + // Remember the first char we inserted if (curwin->w_cursor.lnum == Insstart_orig.lnum && curwin->w_cursor.col < Insstart_orig.col) { Insstart_orig.col = curwin->w_cursor.col; @@ -8046,18 +8169,16 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) getvcol(curwin, &curwin->w_cursor, &vcol, NULL, NULL); } - /* If we are now back where we started delete one character. Can - * happen when using 'sts' and 'linebreak'. */ - if (vcol >= start_vcol) + // If we are now back where we started delete one character. Can + // happen when using 'sts' and 'linebreak'. + if (vcol >= start_vcol) { ins_bs_one(&vcol); - - // Delete upto starting point, start of line or previous word. + } } else { - int cclass = 0, prev_cclass = 0; + // Delete upto starting point, start of line or previous word. + int prev_cclass = 0; - if (has_mbyte) { - cclass = mb_get_class(get_cursor_pos_ptr()); - } + int cclass = mb_get_class(get_cursor_pos_ptr()); do { if (!revins_on) { // put cursor on char to be deleted dec_cursor(); @@ -8125,21 +8246,22 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) // with. AppendCharToRedobuff(c); - /* If deleted before the insertion point, adjust it */ + // If deleted before the insertion point, adjust it if (curwin->w_cursor.lnum == Insstart_orig.lnum && curwin->w_cursor.col < Insstart_orig.col) { Insstart_orig.col = curwin->w_cursor.col; } - /* vi behaviour: the cursor moves backward but the character that - * was there remains visible - * Vim behaviour: the cursor moves backward and the character that - * was there is erased from the screen. - * We can emulate the vi behaviour by pretending there is a dollar - * displayed even when there isn't. - * --pkv Sun Jan 19 01:56:40 EST 2003 */ - if (vim_strchr(p_cpo, CPO_BACKSPACE) != NULL && dollar_vcol == -1) + // vi behaviour: the cursor moves backward but the character that + // was there remains visible + // Vim behaviour: the cursor moves backward and the character that + // was there is erased from the screen. + // We can emulate the vi behaviour by pretending there is a dollar + // displayed even when there isn't. + // --pkv Sun Jan 19 01:56:40 EST 2003 + if (vim_strchr(p_cpo, CPO_BACKSPACE) != NULL && dollar_vcol == -1) { dollar_vcol = curwin->w_virtcol; + } // When deleting a char the cursor line must never be in a closed fold. // E.g., when 'foldmethod' is indent and deleting the first non-white @@ -8164,10 +8286,14 @@ static void ins_mouse(int c) win_T *new_curwin = curwin; if (curwin != old_curwin && win_valid(old_curwin)) { - /* Mouse took us to another window. We need to go back to the - * previous one to stop insert there properly. */ + // Mouse took us to another window. We need to go back to the + // previous one to stop insert there properly. curwin = old_curwin; curbuf = curwin->w_buffer; + if (bt_prompt(curbuf)) { + // Restart Insert mode when re-entering the prompt buffer. + curbuf->b_prompt_insert = 'A'; + } } start_arrow(curwin == old_curwin ? &tpos : NULL); if (curwin != new_curwin && win_valid(new_curwin)) { @@ -8177,7 +8303,7 @@ static void ins_mouse(int c) can_cindent = true; } - /* redraw status lines (in case another window became active) */ + // redraw status lines (in case another window became active) redraw_statuslines(); } @@ -8200,7 +8326,7 @@ static void ins_mousescroll(int dir) if (curwin == old_curwin) undisplay_dollar(); - /* Don't scroll the window in which completion is being done. */ + // Don't scroll the window in which completion is being done. if (!pum_visible() || curwin != old_curwin ) { @@ -8242,9 +8368,10 @@ static void ins_left(void) if (!end_change) { AppendCharToRedobuff(K_LEFT); } - /* If exit reversed string, position is fixed */ - if (revins_scol != -1 && (int)curwin->w_cursor.col >= revins_scol) + // If exit reversed string, position is fixed + if (revins_scol != -1 && (int)curwin->w_cursor.col >= revins_scol) { revins_legal++; + } revins_chars++; } else if (vim_strchr(p_ww, '[') != NULL && curwin->w_cursor.lnum > 1) { // if 'whichwrap' set for cursor in insert mode may go to previous line. @@ -8337,14 +8464,13 @@ static void ins_right(void) revins_legal++; if (revins_chars) revins_chars--; - } - /* if 'whichwrap' set for cursor in insert mode, may move the - * cursor to the next line */ - else if (vim_strchr(p_ww, ']') != NULL - && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { + } else if (vim_strchr(p_ww, ']') != NULL + && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { + // if 'whichwrap' set for cursor in insert mode, may move the + // cursor to the next line start_arrow(&curwin->w_cursor); - curwin->w_set_curswant = TRUE; - ++curwin->w_cursor.lnum; + curwin->w_set_curswant = true; + curwin->w_cursor.lnum++; curwin->w_cursor.col = 0; } else { vim_beep(BO_CRSR); @@ -8373,9 +8499,8 @@ static void ins_s_right(void) dont_sync_undo = kFalse; } -static void -ins_up ( - int startcol /* when TRUE move to Insstart.col */ +static void ins_up( + bool startcol // when true move to Insstart.col ) { pos_T tpos; @@ -8405,7 +8530,7 @@ static void ins_pageup(void) undisplay_dollar(); if (mod_mask & MOD_MASK_CTRL) { - /* <C-PageUp>: tab page back */ + // <C-PageUp>: tab page back if (first_tabpage->tp_next != NULL) { start_arrow(&curwin->w_cursor); goto_tabpage(-1); @@ -8422,9 +8547,8 @@ static void ins_pageup(void) } } -static void -ins_down ( - int startcol /* when TRUE move to Insstart.col */ +static void ins_down( + bool startcol // when true move to Insstart.col ) { pos_T tpos; @@ -8454,7 +8578,7 @@ static void ins_pagedown(void) undisplay_dollar(); if (mod_mask & MOD_MASK_CTRL) { - /* <C-PageDown>: tab page forward */ + // <C-PageDown>: tab page forward if (first_tabpage->tp_next != NULL) { start_arrow(&curwin->w_cursor); goto_tabpage(0); @@ -8541,7 +8665,7 @@ static bool ins_tab(void) */ if (!curbuf->b_p_et && (get_sts_value() || (p_sta && ind))) { char_u *ptr; - char_u *saved_line = NULL; /* init for GCC */ + char_u *saved_line = NULL; // init for GCC pos_T pos; pos_T fpos; pos_T *cursor; @@ -8563,18 +8687,19 @@ static bool ins_tab(void) cursor = &curwin->w_cursor; } - /* When 'L' is not in 'cpoptions' a tab always takes up 'ts' spaces. */ - if (vim_strchr(p_cpo, CPO_LISTWM) == NULL) - curwin->w_p_list = FALSE; + // When 'L' is not in 'cpoptions' a tab always takes up 'ts' spaces. + if (vim_strchr(p_cpo, CPO_LISTWM) == NULL) { + curwin->w_p_list = false; + } - /* Find first white before the cursor */ + // Find first white before the cursor fpos = curwin->w_cursor; while (fpos.col > 0 && ascii_iswhite(ptr[-1])) { --fpos.col; --ptr; } - /* In Replace mode, don't change characters before the insert point. */ + // In Replace mode, don't change characters before the insert point. if ((State & REPLACE_FLAG) && fpos.lnum == Insstart.lnum && fpos.col < Insstart.col) { @@ -8582,12 +8707,12 @@ static bool ins_tab(void) fpos.col = Insstart.col; } - /* compute virtual column numbers of first white and cursor */ + // compute virtual column numbers of first white and cursor getvcol(curwin, &fpos, &vcol, NULL, NULL); getvcol(curwin, cursor, &want_vcol, NULL, NULL); - /* Use as many TABs as possible. Beware of 'breakindent', 'showbreak' - and 'linebreak' adding extra virtual columns. */ + // Use as many TABs as possible. Beware of 'breakindent', 'showbreak' + // and 'linebreak' adding extra virtual columns. while (ascii_iswhite(*ptr)) { i = lbr_chartabsize(NULL, (char_u *)"\t", vcol); if (vcol + i > want_vcol) @@ -8595,10 +8720,11 @@ static bool ins_tab(void) if (*ptr != TAB) { *ptr = TAB; if (change_col < 0) { - change_col = fpos.col; /* Column of first change */ - /* May have to adjust Insstart */ - if (fpos.lnum == Insstart.lnum && fpos.col < Insstart.col) + change_col = fpos.col; // Column of first change + // May have to adjust Insstart + if (fpos.lnum == Insstart.lnum && fpos.col < Insstart.col) { Insstart.col = fpos.col; + } } } ++fpos.col; @@ -8610,29 +8736,30 @@ static bool ins_tab(void) int repl_off = 0; char_u *line = ptr; - /* Skip over the spaces we need. */ + // Skip over the spaces we need. while (vcol < want_vcol && *ptr == ' ') { vcol += lbr_chartabsize(line, ptr, vcol); ++ptr; ++repl_off; } if (vcol > want_vcol) { - /* Must have a char with 'showbreak' just before it. */ - --ptr; - --repl_off; + // Must have a char with 'showbreak' just before it. + ptr--; + repl_off--; } fpos.col += repl_off; - /* Delete following spaces. */ + // Delete following spaces. i = cursor->col - fpos.col; if (i > 0) { STRMOVE(ptr, ptr + i); - /* correct replace stack. */ + // correct replace stack. if ((State & REPLACE_FLAG) - && !(State & VREPLACE_FLAG) - ) - for (temp = i; --temp >= 0; ) + && !(State & VREPLACE_FLAG)) { + for (temp = i; --temp >= 0; ) { replace_join(repl_off); + } + } } cursor->col -= i; @@ -8642,11 +8769,11 @@ static bool ins_tab(void) * spacing. */ if (State & VREPLACE_FLAG) { - /* Backspace from real cursor to change_col */ + // Backspace from real cursor to change_col backspace_until_column(change_col); - /* Insert each char in saved_line from changed_col to - * ptr-cursor */ + // Insert each char in saved_line from changed_col to + // ptr-cursor ins_bytes_len(saved_line + change_col, cursor->col - change_col); } @@ -8690,10 +8817,11 @@ static bool ins_eol(int c) * in open_line(). */ - /* Put cursor on NUL if on the last char and coladd is 1 (happens after - * CTRL-O). */ - if (virtual_active() && curwin->w_cursor.coladd > 0) + // Put cursor on NUL if on the last char and coladd is 1 (happens after + // CTRL-O). + if (virtual_active() && curwin->w_cursor.coladd > 0) { coladvance(getviscol()); + } // NL in reverse insert will always start in the end of current line. if (revins_on) { @@ -8721,15 +8849,15 @@ static int ins_digraph(void) { int c; int cc; - int did_putchar = FALSE; + bool did_putchar = false; pc_status = PC_STATUS_UNSET; if (redrawing() && !char_avail()) { - /* may need to redraw when no more chars available now */ - ins_redraw(FALSE); + // may need to redraw when no more chars available now + ins_redraw(false); - edit_putchar('?', TRUE); - did_putchar = TRUE; + edit_putchar('?', true); + did_putchar = true; add_to_showcmd_c(Ctrl_K); } @@ -8745,21 +8873,21 @@ static int ins_digraph(void) edit_unputchar(); } - if (IS_SPECIAL(c) || mod_mask) { /* special key */ + if (IS_SPECIAL(c) || mod_mask) { // special key clear_showcmd(); insert_special(c, TRUE, FALSE); return NUL; } if (c != ESC) { - did_putchar = FALSE; + did_putchar = false; if (redrawing() && !char_avail()) { - /* may need to redraw when no more chars available now */ - ins_redraw(FALSE); + // may need to redraw when no more chars available now + ins_redraw(false); if (char2cells(c) == 1) { - ins_redraw(FALSE); - edit_putchar(c, TRUE); - did_putchar = TRUE; + ins_redraw(false); + edit_putchar(c, true); + did_putchar = true; } add_to_showcmd_c(c); } @@ -8798,7 +8926,7 @@ int ins_copychar(linenr_T lnum) return NUL; } - /* try to advance to the cursor column */ + // try to advance to the cursor column temp = 0; line = ptr = ml_get(lnum); prev_ptr = ptr; @@ -8848,8 +8976,8 @@ static int ins_ctrl_ey(int tc) curbuf->b_p_tw = tw_save; revins_chars++; revins_legal++; - c = Ctrl_V; /* pretend CTRL-V is last character */ - auto_format(FALSE, TRUE); + c = Ctrl_V; // pretend CTRL-V is last character + auto_format(false, true); } } return c; @@ -8884,9 +9012,10 @@ static void ins_try_si(int c) */ ptr = ml_get(pos->lnum); i = pos->col; - if (i > 0) /* skip blanks before '{' */ - while (--i > 0 && ascii_iswhite(ptr[i])) - ; + if (i > 0) { // skip blanks before '{' + while (--i > 0 && ascii_iswhite(ptr[i])) { + } + } curwin->w_cursor.lnum = pos->lnum; curwin->w_cursor.col = i; if (ptr[i] == ')' && (pos = findmatch(NULL, '(')) != NULL) @@ -8909,9 +9038,10 @@ static void ins_try_si(int c) while (curwin->w_cursor.lnum > 1) { ptr = skipwhite(ml_get(--(curwin->w_cursor.lnum))); - /* ignore empty lines and lines starting with '#'. */ - if (*ptr != '#' && *ptr != NUL) + // ignore empty lines and lines starting with '#'. + if (*ptr != '#' && *ptr != NUL) { break; + } } if (get_indent() >= i) temp = FALSE; @@ -8926,14 +9056,15 @@ static void ins_try_si(int c) * set indent of '#' always to 0 */ if (curwin->w_cursor.col > 0 && can_si && c == '#') { - /* remember current indent for next line */ + // remember current indent for next line old_indent = get_indent(); (void)set_indent(0, SIN_CHANGED); } - /* Adjust ai_col, the char at this position can be deleted. */ - if (ai_col > curwin->w_cursor.col) + // Adjust ai_col, the char at this position can be deleted. + if (ai_col > curwin->w_cursor.col) { ai_col = curwin->w_cursor.col; + } } /* @@ -8998,7 +9129,8 @@ static int ins_apply_autocmds(event_T event) // If u_savesub() was called then we are not prepared to start // a new line. Call u_save() with no contents to fix that. - if (tick != buf_get_changedtick(curbuf)) { + // Except when leaving Insert mode. + if (event != EVENT_INSERTLEAVE && tick != buf_get_changedtick(curbuf)) { u_save(curwin->w_cursor.lnum, (linenr_T)(curwin->w_cursor.lnum + 1)); } diff --git a/src/nvim/edit.h b/src/nvim/edit.h index 92dab37a70..09f401ee82 100644 --- a/src/nvim/edit.h +++ b/src/nvim/edit.h @@ -10,8 +10,7 @@ #define CPT_MENU 1 // "menu" #define CPT_KIND 2 // "kind" #define CPT_INFO 3 // "info" -#define CPT_USER_DATA 4 // "user data" -#define CPT_COUNT 5 // Number of entries +#define CPT_COUNT 4 // Number of entries // values for cp_flags typedef enum { diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 55b18d5f4f..4a0876a952 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5,183 +5,64 @@ * eval.c: Expression evaluation. */ -#include <assert.h> -#include <float.h> -#include <inttypes.h> -#include <stdarg.h> -#include <string.h> -#include <stdlib.h> -#include <stdbool.h> #include <math.h> -#include <limits.h> -#include <msgpack.h> -#include "nvim/assert.h" -#include "nvim/vim.h" -#include "nvim/ascii.h" #ifdef HAVE_LOCALE_H # include <locale.h> #endif -#include "nvim/eval.h" + +#include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/channel.h" #include "nvim/charset.h" -#include "nvim/context.h" #include "nvim/cursor.h" -#include "nvim/diff.h" #include "nvim/edit.h" -#include "nvim/ex_cmds.h" +#include "nvim/eval/userfunc.h" +#include "nvim/eval.h" +#include "nvim/eval/encode.h" +#include "nvim/eval/executor.h" +#include "nvim/eval/gc.h" +#include "nvim/eval/typval.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" -#include "nvim/ex_eval.h" #include "nvim/ex_getln.h" +#include "nvim/ex_session.h" #include "nvim/fileio.h" -#include "nvim/os/fileio.h" -#include "nvim/func_attr.h" -#include "nvim/fold.h" #include "nvim/getchar.h" -#include "nvim/hashtab.h" -#include "nvim/iconv.h" -#include "nvim/if_cscope.h" -#include "nvim/indent_c.h" -#include "nvim/indent.h" +#include "nvim/lua/executor.h" #include "nvim/mark.h" -#include "nvim/math.h" -#include "nvim/mbyte.h" #include "nvim/memline.h" -#include "nvim/memory.h" -#include "nvim/menu.h" -#include "nvim/message.h" #include "nvim/misc1.h" -#include "nvim/keymap.h" -#include "nvim/map.h" -#include "nvim/file_search.h" -#include "nvim/garray.h" #include "nvim/move.h" -#include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" -#include "nvim/os_unix.h" +#include "nvim/os/input.h" +#include "nvim/os/os.h" +#include "nvim/os/shell.h" #include "nvim/path.h" -#include "nvim/popupmnu.h" -#include "nvim/profile.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/screen.h" #include "nvim/search.h" -#include "nvim/sha256.h" #include "nvim/sign.h" -#include "nvim/spell.h" -#include "nvim/state.h" -#include "nvim/strings.h" #include "nvim/syntax.h" -#include "nvim/tag.h" #include "nvim/ui.h" -#include "nvim/main.h" -#include "nvim/mouse.h" -#include "nvim/terminal.h" #include "nvim/undo.h" #include "nvim/version.h" #include "nvim/window.h" -#include "nvim/eval/encode.h" -#include "nvim/eval/decode.h" -#include "nvim/os/os.h" -#include "nvim/event/libuv_process.h" -#include "nvim/os/pty_process.h" -#include "nvim/event/rstream.h" -#include "nvim/event/wstream.h" -#include "nvim/event/time.h" -#include "nvim/os/time.h" -#include "nvim/msgpack_rpc/channel.h" -#include "nvim/msgpack_rpc/server.h" -#include "nvim/msgpack_rpc/helpers.h" -#include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" -#include "nvim/os/dl.h" -#include "nvim/os/input.h" -#include "nvim/event/loop.h" -#include "nvim/lib/kvec.h" -#include "nvim/lib/khash.h" -#include "nvim/lib/queue.h" -#include "nvim/lua/executor.h" -#include "nvim/eval/typval.h" -#include "nvim/eval/executor.h" -#include "nvim/eval/gc.h" -#include "nvim/macros.h" - -// TODO(ZyX-I): Remove DICT_MAXNEST, make users be non-recursive instead -#define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */ -// Character used as separator in autoload function/variable names. -#define AUTOLOAD_CHAR '#' +// TODO(ZyX-I): Remove DICT_MAXNEST, make users be non-recursive instead -/* - * Structure returned by get_lval() and used by set_var_lval(). - * For a plain name: - * "name" points to the variable name. - * "exp_name" is NULL. - * "tv" is NULL - * For a magic braces name: - * "name" points to the expanded variable name. - * "exp_name" is non-NULL, to be freed later. - * "tv" is NULL - * For an index in a list: - * "name" points to the (expanded) variable name. - * "exp_name" NULL or non-NULL, to be freed later. - * "tv" points to the (first) list item value - * "li" points to the (first) list item - * "range", "n1", "n2" and "empty2" indicate what items are used. - * For an existing Dict item: - * "name" points to the (expanded) variable name. - * "exp_name" NULL or non-NULL, to be freed later. - * "tv" points to the dict item value - * "newkey" is NULL - * For a non-existing Dict item: - * "name" points to the (expanded) variable name. - * "exp_name" NULL or non-NULL, to be freed later. - * "tv" points to the Dictionary typval_T - * "newkey" is the key for the new item. - */ -typedef struct lval_S { - const char *ll_name; ///< Start of variable name (can be NULL). - size_t ll_name_len; ///< Length of the .ll_name. - char *ll_exp_name; ///< NULL or expanded name in allocated memory. - typval_T *ll_tv; ///< Typeval of item being used. If "newkey" - ///< isn't NULL it's the Dict to which to add the item. - listitem_T *ll_li; ///< The list item or NULL. - list_T *ll_list; ///< The list or NULL. - int ll_range; ///< TRUE when a [i:j] range was used. - long ll_n1; ///< First index for list. - long ll_n2; ///< Second index for list range. - int ll_empty2; ///< Second index is empty: [i:]. - dict_T *ll_dict; ///< The Dictionary or NULL. - dictitem_T *ll_di; ///< The dictitem or NULL. - char_u *ll_newkey; ///< New key for Dict in allocated memory or NULL. -} lval_T; +#define DICT_MAXNEST 100 // maximum nesting of lists and dicts static char *e_letunexp = N_("E18: Unexpected characters in :let"); static char *e_missbrac = N_("E111: Missing ']'"); -static char *e_listarg = N_("E686: Argument of %s must be a List"); -static char *e_listdictarg = N_( - "E712: Argument of %s must be a List or Dictionary"); -static char *e_listreq = N_("E714: List required"); -static char *e_dictreq = N_("E715: Dictionary required"); -static char *e_stringreq = N_("E928: String required"); -static char *e_toomanyarg = N_("E118: Too many arguments for function: %s"); -static char *e_dictkey = N_("E716: Key not present in Dictionary: %s"); -static char *e_funcexts = N_( - "E122: Function %s already exists, add ! to replace it"); -static char *e_funcdict = N_("E717: Dictionary entry already exists"); -static char *e_funcref = N_("E718: Funcref required"); static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary"); -static char *e_nofunc = N_("E130: Unknown function: %s"); static char *e_illvar = N_("E461: Illegal variable name: %s"); static char *e_cannot_mod = N_("E995: Cannot modify existing variable"); -static const char *e_readonlyvar = N_( - "E46: Cannot change read-only variable \"%.*s\""); // TODO(ZyX-I): move to eval/executor static char *e_letwrong = N_("E734: Wrong variable type for %s="); @@ -200,10 +81,8 @@ static ScopeDictDictItem globvars_var; */ static hashtab_T compat_hashtab; -hashtab_T func_hashtab; - -// Used for checking if local variables or arguments used in a lambda. -static int *eval_lavars_used = NULL; +/// Used for checking if local variables or arguments used in a lambda. +bool *eval_lavars_used = NULL; /* * Array to hold the hashtab with variables local to each sourced script. @@ -218,98 +97,25 @@ static garray_T ga_scripts = {0, 0, sizeof(scriptvar_T *), 4, NULL}; #define SCRIPT_SV(id) (((scriptvar_T **)ga_scripts.ga_data)[(id) - 1]) #define SCRIPT_VARS(id) (SCRIPT_SV(id)->sv_dict.dv_hashtab) -static int echo_attr = 0; /* attributes used for ":echo" */ - -/// Describe data to return from find_some_match() -typedef enum { - kSomeMatch, ///< Data for match(). - kSomeMatchEnd, ///< Data for matchend(). - kSomeMatchList, ///< Data for matchlist(). - kSomeMatchStr, ///< Data for matchstr(). - kSomeMatchStrPos, ///< Data for matchstrpos(). -} SomeMatchType; - -/// trans_function_name() flags -typedef enum { - TFN_INT = 1, ///< May use internal function name - TFN_QUIET = 2, ///< Do not emit error messages. - TFN_NO_AUTOLOAD = 4, ///< Do not use script autoloading. - TFN_NO_DEREF = 8, ///< Do not dereference a Funcref. - TFN_READ_ONLY = 16, ///< Will not change the variable. -} TransFunctionNameFlags; - -/// get_lval() flags -typedef enum { - GLV_QUIET = TFN_QUIET, ///< Do not emit error messages. - GLV_NO_AUTOLOAD = TFN_NO_AUTOLOAD, ///< Do not use script autoloading. - GLV_READ_ONLY = TFN_READ_ONLY, ///< Indicates that caller will not change - ///< the value (prevents error message). -} GetLvalFlags; - -// flags used in uf_flags -#define FC_ABORT 0x01 // abort function on error -#define FC_RANGE 0x02 // function accepts range -#define FC_DICT 0x04 // Dict function, uses "self" -#define FC_CLOSURE 0x08 // closure, uses outer scope variables -#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0 -#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0 -#define FC_SANDBOX 0x40 // function defined in the sandbox +static int echo_attr = 0; // attributes used for ":echo" // The names of packages that once were loaded are remembered. static garray_T ga_loaded = { 0, 0, sizeof(char_u *), 4, NULL }; -#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] -#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j] - -/// Short variable name length -#define VAR_SHORT_LEN 20 -/// Number of fixed variables used for arguments -#define FIXVAR_CNT 12 - -struct funccall_S { - ufunc_T *func; ///< Function being called. - int linenr; ///< Next line to be executed. - int returned; ///< ":return" used. - /// Fixed variables for arguments. - TV_DICTITEM_STRUCT(VAR_SHORT_LEN + 1) fixvar[FIXVAR_CNT]; - dict_T l_vars; ///< l: local function variables. - ScopeDictDictItem l_vars_var; ///< Variable for l: scope. - dict_T l_avars; ///< a: argument variables. - ScopeDictDictItem l_avars_var; ///< Variable for a: scope. - list_T l_varlist; ///< List for a:000. - listitem_T l_listitems[MAX_FUNC_ARGS]; ///< List items for a:000. - typval_T *rettv; ///< Return value. - linenr_T breakpoint; ///< Next line with breakpoint or zero. - int dbg_tick; ///< Debug_tick when breakpoint was set. - int level; ///< Top nesting level of executed function. - proftime_T prof_child; ///< Time spent in a child. - funccall_T *caller; ///< Calling function or NULL. - int fc_refcount; ///< Number of user functions that reference this funccall. - int fc_copyID; ///< CopyID used for garbage collection. - garray_T fc_funcs; ///< List of ufunc_T* which keep a reference to "func". -}; - -///< Structure used by trans_function_name() -typedef struct { - dict_T *fd_dict; ///< Dictionary used. - char_u *fd_newkey; ///< New key in "dict" in allocated memory. - dictitem_T *fd_di; ///< Dictionary item used. -} funcdict_T; - /* * Info used by a ":for" loop. */ typedef struct { - int fi_semicolon; /* TRUE if ending in '; var]' */ - int fi_varcount; /* nr of variables in the list */ - listwatch_T fi_lw; /* keep an eye on the item used. */ - list_T *fi_list; /* list being used */ + int fi_semicolon; // TRUE if ending in '; var]' + int fi_varcount; // nr of variables in the list + listwatch_T fi_lw; // keep an eye on the item used. + list_T *fi_list; // list being used } forinfo_T; -/* values for vv_flags: */ -#define VV_COMPAT 1 /* compatible, also used without "v:" */ -#define VV_RO 2 /* read-only */ -#define VV_RO_SBX 4 /* read-only in the sandbox */ +// values for vv_flags: +#define VV_COMPAT 1 // compatible, also used without "v:" +#define VV_RO 2 // read-only +#define VV_RO_SBX 4 // read-only in the sandbox #define VV(idx, name, type, flags) \ [idx] = { \ @@ -426,7 +232,7 @@ static struct vimvar { }; #undef VV -/* shorthand */ +// shorthand #define vv_type vv_di.di_tv.v_type #define vv_nr vv_di.di_tv.vval.v_number #define vv_special vv_di.di_tv.vval.v_special @@ -445,71 +251,13 @@ static partial_T *vvlua_partial; /// v: hashtab #define vimvarht vimvardict.dv_hashtab -typedef struct { - TimeWatcher tw; - int timer_id; - int repeat_count; - int refcount; - int emsg_count; ///< Errors in a repeating timer. - long timeout; - bool stopped; - bool paused; - Callback callback; -} timer_T; - -typedef void (*FunPtr)(void); - -/// Prototype of C function that implements VimL function -typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, FunPtr data); - -/// Structure holding VimL function definition -typedef struct fst { - char *name; ///< Name of the function. - uint8_t min_argc; ///< Minimal number of arguments. - uint8_t max_argc; ///< Maximal number of arguments. - VimLFunc func; ///< Function implementation. - FunPtr data; ///< Userdata for function implementation. -} VimLFuncDef; - -KHASH_MAP_INIT_STR(functions, VimLFuncDef) - -/// Type of assert_* check being performed -typedef enum -{ - ASSERT_EQUAL, - ASSERT_NOTEQUAL, - ASSERT_MATCH, - ASSERT_NOTMATCH, - ASSERT_INRANGE, - ASSERT_OTHER, -} assert_type_T; - -/// Type for dict_list function -typedef enum { - kDictListKeys, ///< List dictionary keys. - kDictListValues, ///< List dictionary values. - kDictListItems, ///< List dictionary contents: [keys, values]. -} DictListType; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval.c.generated.h" #endif -#define FNE_INCL_BR 1 /* find_name_end(): include [] in name */ -#define FNE_CHECK_START 2 /* find_name_end(): check name starts with - valid character */ - static uint64_t last_timer_id = 1; static PMap(uint64_t) *timers = NULL; -/// Dummy va_list for passing to vim_snprintf -/// -/// Used because: -/// - passing a NULL pointer doesn't work when va_list isn't a pointer -/// - locally in the function results in a "used before set" warning -/// - using va_start() to initialize it gives "function with fixed args" error -static va_list dummy_ap; - static const char *const msgpack_type_names[] = { [kMPNil] = "nil", [kMPBoolean] = "boolean", @@ -576,7 +324,7 @@ void eval_init(void) init_var_dict(&vimvardict, &vimvars_var, VAR_SCOPE); vimvardict.dv_lock = VAR_FIXED; hash_init(&compat_hashtab); - hash_init(&func_hashtab); + func_init(); for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) { p = &vimvars[i]; @@ -589,12 +337,14 @@ void eval_init(void) else p->vv_di.di_flags = DI_FLAGS_FIX; - /* add to v: scope dict, unless the value is not always available */ - if (p->vv_type != VAR_UNKNOWN) + // add to v: scope dict, unless the value is not always available + if (p->vv_type != VAR_UNKNOWN) { hash_add(&vimvarht, p->vv_di.di_key); - if (p->vv_flags & VV_COMPAT) - /* add to compat scope dict */ + } + if (p->vv_flags & VV_COMPAT) { + // add to compat scope dict hash_add(&compat_hashtab, p->vv_di.di_key); + } } vimvars[VV_VERSION].vv_nr = VIM_VERSION_100; @@ -668,16 +418,16 @@ void eval_clear(void) } } hash_clear(&vimvarht); - hash_init(&vimvarht); /* garbage_collect() will access it */ + hash_init(&vimvarht); // garbage_collect() will access it hash_clear(&compat_hashtab); free_scriptnames(); free_locales(); - /* global variables */ + // global variables vars_clear(&globvarht); - /* autoloaded script names */ + // autoloaded script names ga_clear_strings(&ga_loaded); /* Script-local variables. First clear all the variables and in a second @@ -699,53 +449,6 @@ void eval_clear(void) #endif /* - * Return the name of the executed function. - */ -char_u *func_name(void *cookie) -{ - return ((funccall_T *)cookie)->func->uf_name; -} - -/* - * Return the address holding the next breakpoint line for a funccall cookie. - */ -linenr_T *func_breakpoint(void *cookie) -{ - return &((funccall_T *)cookie)->breakpoint; -} - -/* - * Return the address holding the debug tick for a funccall cookie. - */ -int *func_dbg_tick(void *cookie) -{ - return &((funccall_T *)cookie)->dbg_tick; -} - -/* - * Return the nesting level for a funccall cookie. - */ -int func_level(void *cookie) -{ - return ((funccall_T *)cookie)->level; -} - -/* pointer to funccal for currently active function */ -funccall_T *current_funccal = NULL; - -// Pointer to list of previously used funccal, still around because some -// item in it is still being used. -funccall_T *previous_funccal = NULL; - -/* - * Return TRUE when a function was ended by a ":return" command. - */ -int current_func_returned(void) -{ - return current_funccal->returned; -} - -/* * Set an internal variable to a string value. Creates the variable if it does * not already exist. */ @@ -771,25 +474,25 @@ static char_u *redir_varname = NULL; int var_redir_start( char_u *name, - int append /* append to an existing variable */ + int append // append to an existing variable ) { int save_emsg; int err; typval_T tv; - /* Catch a bad name early. */ + // Catch a bad name early. if (!eval_isnamec1(*name)) { EMSG(_(e_invarg)); return FAIL; } - /* Make a copy of the name, it is used in redir_lval until redir ends. */ + // Make a copy of the name, it is used in redir_lval until redir ends. redir_varname = vim_strsave(name); redir_lval = xcalloc(1, sizeof(lval_T)); - /* The output is stored in growarray "redir_ga" until redirection ends. */ + // The output is stored in growarray "redir_ga" until redirection ends. ga_init(&redir_ga, (int)sizeof(char), 500); // Parse the variable name (can be a dict or list entry). @@ -798,12 +501,13 @@ var_redir_start( if (redir_endp == NULL || redir_lval->ll_name == NULL || *redir_endp != NUL) { clear_lval(redir_lval); - if (redir_endp != NULL && *redir_endp != NUL) - /* Trailing characters are present after the variable name */ + if (redir_endp != NULL && *redir_endp != NUL) { + // Trailing characters are present after the variable name EMSG(_(e_trailing)); - else + } else { EMSG(_(e_invarg)); - redir_endp = NULL; /* don't store a value, only cleanup */ + } + redir_endp = NULL; // don't store a value, only cleanup var_redir_stop(); return FAIL; } @@ -823,7 +527,7 @@ var_redir_start( err = did_emsg; did_emsg |= save_emsg; if (err) { - redir_endp = NULL; /* don't store a value, only cleanup */ + redir_endp = NULL; // don't store a value, only cleanup var_redir_stop(); return FAIL; } @@ -847,10 +551,11 @@ void var_redir_str(char_u *value, int value_len) if (redir_lval == NULL) return; - if (value_len == -1) - len = (int)STRLEN(value); /* Append the entire string */ - else - len = value_len; /* Append only "value_len" characters */ + if (value_len == -1) { + len = (int)STRLEN(value); // Append the entire string + } else { + len = value_len; // Append only "value_len" characters + } ga_grow(&redir_ga, len); memmove((char *)redir_ga.ga_data + redir_ga.ga_len, value, len); @@ -866,9 +571,9 @@ void var_redir_stop(void) typval_T tv; if (redir_lval != NULL) { - /* If there was no error: assign the text to the variable. */ + // If there was no error: assign the text to the variable. if (redir_endp != NULL) { - ga_append(&redir_ga, NUL); /* Append the trailing NUL. */ + ga_append(&redir_ga, NUL); // Append the trailing NUL. tv.v_type = VAR_STRING; tv.vval.v_string = redir_ga.ga_data; // Call get_lval() again, if it's inside a Dict or List it may @@ -969,7 +674,7 @@ eval_to_bool( char_u *arg, bool *error, char_u **nextcmd, - int skip /* only parse, don't execute */ + int skip // only parse, don't execute ) { typval_T tv; @@ -1017,8 +722,8 @@ static int eval1_emsg(char_u **arg, typval_T *rettv, bool evaluate) return ret; } -static int eval_expr_typval(const typval_T *expr, typval_T *argv, - int argc, typval_T *rettv) +int eval_expr_typval(const typval_T *expr, typval_T *argv, + int argc, typval_T *rettv) FUNC_ATTR_NONNULL_ARG(1, 2, 4) { int dummy; @@ -1063,7 +768,7 @@ static int eval_expr_typval(const typval_T *expr, typval_T *argv, /// Like eval_to_bool() but using a typval_T instead of a string. /// Works for string, funcref and partial. -static bool eval_expr_to_bool(const typval_T *expr, bool *error) +bool eval_expr_to_bool(const typval_T *expr, bool *error) FUNC_ATTR_NONNULL_ARG(1, 2) { typval_T argv, rettv; @@ -1210,7 +915,7 @@ varnumber_T eval_to_number(char_u *expr) * Save the current typeval in "save_tv". * When not used yet add the variable to the v: hashtable. */ -static void prepare_vimvar(int idx, typval_T *save_tv) +void prepare_vimvar(int idx, typval_T *save_tv) { *save_tv = vimvars[idx].vv_tv; if (vimvars[idx].vv_type == VAR_UNKNOWN) @@ -1221,7 +926,7 @@ static void prepare_vimvar(int idx, typval_T *save_tv) * Restore v: variable "idx" to typeval "save_tv". * When no longer defined, remove the variable from the v: hashtable. */ -static void restore_vimvar(int idx, typval_T *save_tv) +void restore_vimvar(int idx, typval_T *save_tv) { hashitem_T *hi; @@ -1237,7 +942,7 @@ static void restore_vimvar(int idx, typval_T *save_tv) } /// If there is a window for "curbuf", make it the current window. -static void find_win_for_curbuf(void) +void find_win_for_curbuf(void) { for (wininfo_T *wip = curbuf->b_wininfo; wip != NULL; wip = wip->wi_next) { if (wip->wi_win != NULL) { @@ -1420,33 +1125,15 @@ void *call_func_retlist(const char_u *func, int argc, typval_T *argv) } /* - * Save the current function call pointer, and set it to NULL. - * Used when executing autocommands and for ":source". - */ -void *save_funccal(void) -{ - funccall_T *fc = current_funccal; - - current_funccal = NULL; - return (void *)fc; -} - -void restore_funccal(void *vfc) -{ - funccall_T *fc = (funccall_T *)vfc; - - current_funccal = fc; -} - -/* * Prepare profiling for entering a child or something else that is not * counted for the script/function itself. * Should always be called in pair with prof_child_exit(). */ -void prof_child_enter(proftime_T *tm /* place to store waittime */ - ) +void prof_child_enter( + proftime_T *tm // place to store waittime +) { - funccall_T *fc = current_funccal; + funccall_T *fc = get_current_funccal(); if (fc != NULL && fc->func->uf_profiling) { fc->prof_child = profile_start(); @@ -1459,10 +1146,11 @@ void prof_child_enter(proftime_T *tm /* place to store waittime */ * Take care of time spent in a child. * Should always be called after prof_child_enter(). */ -void prof_child_exit(proftime_T *tm /* where waittime was stored */ - ) +void prof_child_exit( + proftime_T *tm // where waittime was stored +) { - funccall_T *fc = current_funccal; + funccall_T *fc = get_current_funccal(); if (fc != NULL && fc->func->uf_profiling) { fc->prof_child = profile_end(fc->prof_child); @@ -1485,7 +1173,6 @@ int eval_foldexpr(char_u *arg, int *cp) { typval_T tv; varnumber_T retval; - char_u *s; int use_sandbox = was_set_insecurely((char_u *)"foldexpr", OPT_LOCAL); @@ -1494,20 +1181,21 @@ int eval_foldexpr(char_u *arg, int *cp) ++sandbox; ++textlock; *cp = NUL; - if (eval0(arg, &tv, NULL, TRUE) == FAIL) + if (eval0(arg, &tv, NULL, true) == FAIL) { retval = 0; - else { - /* If the result is a number, just return the number. */ - if (tv.v_type == VAR_NUMBER) + } else { + // If the result is a number, just return the number. + if (tv.v_type == VAR_NUMBER) { retval = tv.vval.v_number; - else if (tv.v_type != VAR_STRING || tv.vval.v_string == NULL) + } else if (tv.v_type != VAR_STRING || tv.vval.v_string == NULL) { retval = 0; - else { - /* If the result is a string, check if there is a non-digit before - * the number. */ - s = tv.vval.v_string; - if (!ascii_isdigit(*s) && *s != '-') + } else { + // If the result is a string, check if there is a non-digit before + // the number. + char_u *s = tv.vval.v_string; + if (!ascii_isdigit(*s) && *s != '-') { *cp = *s++; + } retval = atol((char *)s); } tv_clear(&tv); @@ -1847,10 +1535,10 @@ static const char_u *skip_var_list(const char_u *arg, int *var_count, const char_u *s; if (*arg == '[') { - /* "[var, var]": find the matching ']'. */ + // "[var, var]": find the matching ']'. p = arg; for (;; ) { - p = skipwhite(p + 1); /* skip whites after '[', ';' or ',' */ + p = skipwhite(p + 1); // skip whites after '[', ';' or ',' s = skip_var_one(p); if (s == p) { EMSG2(_(e_invarg2), p); @@ -1893,8 +1581,8 @@ static const char_u *skip_var_one(const char_u *arg) * List variables for hashtab "ht" with prefix "prefix". * If "empty" is TRUE also list NULL strings as empty strings. */ -static void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty, - int *first) +void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty, + int *first) { hashitem_T *hi; dictitem_T *di; @@ -1971,17 +1659,6 @@ static void list_script_vars(int *first) } /* - * List function variables, if there is a function. - */ -static void list_func_vars(int *first) -{ - if (current_funccal != NULL) { - list_hashtable_vars(¤t_funccal->l_vars.dv_hashtab, "l:", false, - first); - } -} - -/* * List variables in "arg". */ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first) @@ -2275,9 +1952,9 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, /// /// @return A pointer to just after the name, including indexes. Returns NULL /// for a parsing error, but it is still needed to free items in lp. -static char_u *get_lval(char_u *const name, typval_T *const rettv, - lval_T *const lp, const bool unlet, const bool skip, - const int flags, const int fne_flags) +char_u *get_lval(char_u *const name, typval_T *const rettv, + lval_T *const lp, const bool unlet, const bool skip, + const int flags, const int fne_flags) FUNC_ATTR_NONNULL_ARG(1, 3) { dictitem_T *v; @@ -2288,7 +1965,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, hashtab_T *ht; int quiet = flags & GLV_QUIET; - /* Clear everything in "lp". */ + // Clear everything in "lp". memset(lp, 0, sizeof(lval_T)); if (skip) { @@ -2306,7 +1983,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, (const char_u **)&expr_end, fne_flags); if (expr_start != NULL) { - /* Don't expand the name when we already know there is an error. */ + // Don't expand the name when we already know there is an error. if (unlet && !ascii_iswhite(*p) && !ends_excmd(*p) && *p != '[' && *p != '.') { EMSG(_(e_trailing)); @@ -2386,7 +2063,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } p = key + len; } else { - /* Get the index [expr] or the first index [expr: ]. */ + // Get the index [expr] or the first index [expr: ]. p = skipwhite(p + 1); if (*p == ':') { empty1 = true; @@ -2402,7 +2079,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } } - /* Optionally get the second index [ :expr]. */ + // Optionally get the second index [ :expr]. if (*p == ':') { if (lp->ll_tv->v_type == VAR_DICT) { if (!quiet) { @@ -2448,8 +2125,8 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, return NULL; } - /* Skip to past ']'. */ - ++p; + // Skip to past ']'. + p++; } if (lp->ll_tv->v_type == VAR_DICT) { @@ -2598,7 +2275,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, /* * Clear lval "lp" that was filled by get_lval(). */ -static void clear_lval(lval_T *lp) +void clear_lval(lval_T *lp) { xfree(lp->ll_exp_name); xfree(lp->ll_newkey); @@ -2875,7 +2552,7 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx) if (cmdidx == CMD_let || cmdidx == CMD_const) { xp->xp_context = EXPAND_USER_VARS; if (vim_strpbrk(arg, (char_u *)"\"'+-*/%.=!?~|&$([<>,#") == NULL) { - /* ":let var1 var2 ...": find last space. */ + // ":let var1 var2 ...": find last space. for (p = arg + STRLEN(arg); p >= arg; ) { xp->xp_pattern = p; MB_PTR_BACK(arg, p); @@ -2904,7 +2581,7 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx) } } else if (c == '$') { - /* environment variable */ + // environment variable xp->xp_context = EXPAND_ENV_VARS; } else if (c == '=') { got_eq = TRUE; @@ -2916,18 +2593,20 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx) } else if ((c == '<' || c == '#') && xp->xp_context == EXPAND_FUNCTIONS && vim_strchr(xp->xp_pattern, '(') == NULL) { - /* Function name can start with "<SNR>" and contain '#'. */ + // Function name can start with "<SNR>" and contain '#'. break; } else if (cmdidx != CMD_let || got_eq) { - if (c == '"') { /* string */ - while ((c = *++xp->xp_pattern) != NUL && c != '"') - if (c == '\\' && xp->xp_pattern[1] != NUL) - ++xp->xp_pattern; + if (c == '"') { // string + while ((c = *++xp->xp_pattern) != NUL && c != '"') { + if (c == '\\' && xp->xp_pattern[1] != NUL) { + xp->xp_pattern++; + } + } xp->xp_context = EXPAND_NOTHING; - } else if (c == '\'') { /* literal string */ - /* Trick: '' is like stopping and starting a literal string. */ - while ((c = *++xp->xp_pattern) != NUL && c != '\'') - /* skip */; + } else if (c == '\'') { // literal string + // Trick: '' is like stopping and starting a literal string. + while ((c = *++xp->xp_pattern) != NUL && c != '\'') { + } xp->xp_context = EXPAND_NOTHING; } else if (c == '|') { if (xp->xp_pattern[1] == '|') { @@ -2942,136 +2621,14 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx) * anyway. */ xp->xp_context = EXPAND_EXPRESSION; arg = xp->xp_pattern; - if (*arg != NUL) - while ((c = *++arg) != NUL && (c == ' ' || c == '\t')) - /* skip */; - } - xp->xp_pattern = arg; -} - -// TODO(ZyX-I): move to eval/ex_cmds - -/* - * ":1,25call func(arg1, arg2)" function call. - */ -void ex_call(exarg_T *eap) -{ - char_u *arg = eap->arg; - char_u *startarg; - char_u *name; - char_u *tofree; - int len; - typval_T rettv; - linenr_T lnum; - int doesrange; - bool failed = false; - funcdict_T fudi; - partial_T *partial = NULL; - - 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) { - tv_clear(&rettv); - } - emsg_skip--; - return; - } - - tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial); - if (fudi.fd_newkey != NULL) { - // Still need to give an error message for missing key. - EMSG2(_(e_dictkey), fudi.fd_newkey); - xfree(fudi.fd_newkey); - } - if (tofree == NULL) { - return; - } - - // Increase refcount on dictionary, it could get deleted when evaluating - // the arguments. - if (fudi.fd_dict != NULL) { - fudi.fd_dict->dv_refcount++; - } - - // If it is the name of a variable of type VAR_FUNC or VAR_PARTIAL use its - // contents. For VAR_PARTIAL get its partial, unless we already have one - // from trans_function_name(). - len = (int)STRLEN(tofree); - name = deref_func_name((const char *)tofree, &len, - partial != NULL ? NULL : &partial, false); - - // Skip white space to allow ":call func ()". Not good, but required for - // backward compatibility. - startarg = skipwhite(arg); - rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this. - - if (*startarg != '(') { - EMSG2(_("E107: Missing parentheses: %s"), eap->arg); - goto end; - } - - lnum = eap->line1; - for (; lnum <= eap->line2; lnum++) { - if (eap->addr_count > 0) { // -V560 - if (lnum > curbuf->b_ml.ml_line_count) { - // If the function deleted lines or switched to another buffer - // the line number may become invalid. - EMSG(_(e_invrange)); - break; + if (*arg != NUL) { + while ((c = *++arg) != NUL && (c == ' ' || c == '\t')) { } - curwin->w_cursor.lnum = lnum; - curwin->w_cursor.col = 0; - curwin->w_cursor.coladd = 0; - } - arg = startarg; - if (get_func_tv(name, (int)STRLEN(name), &rettv, &arg, - eap->line1, eap->line2, &doesrange, - true, partial, fudi.fd_dict) == FAIL) { - failed = true; - break; - } - - // Handle a function returning a Funcref, Dictionary or List. - if (handle_subscript((const char **)&arg, &rettv, true, true) - == FAIL) { - failed = true; - break; - } - - tv_clear(&rettv); - if (doesrange) { - break; - } - - // Stop when immediately aborting on error, or when an interrupt - // occurred or an exception was thrown but not caught. - // get_func_tv() returned OK, so that the check for trailing - // characters below is executed. - if (aborting()) { - break; } } - - if (!failed) { - // Check for trailing illegal characters and a following command. - if (!ends_excmd(*arg)) { - emsg_severe = TRUE; - EMSG(_(e_trailing)); - } else { - eap->nextcmd = check_nextcmd(arg); - } - } - -end: - tv_dict_unref(fudi.fd_dict); - xfree(tofree); + xp->xp_pattern = arg; } -// TODO(ZyX-I): move to eval/ex_cmds - /* * ":unlet[!] var1 ... " command. */ @@ -3260,22 +2817,22 @@ int do_unlet(const char *const name, const size_t name_len, const int forceit) hashtab_T *ht = find_var_ht_dict(name, name_len, &varname, &dict); if (ht != NULL && *varname != NUL) { - dict_T *d; - if (ht == &globvarht) { - d = &globvardict; - } else if (current_funccal != NULL - && ht == ¤t_funccal->l_vars.dv_hashtab) { - d = ¤t_funccal->l_vars; - } else if (ht == &compat_hashtab) { - d = &vimvardict; - } else { - dictitem_T *const di = find_var_in_ht(ht, *name, "", 0, false); - d = di->di_tv.vval.v_dict; - } + dict_T *d = get_current_funccal_dict(ht); if (d == NULL) { - internal_error("do_unlet()"); - return FAIL; + if (ht == &globvarht) { + d = &globvardict; + } else if (ht == &compat_hashtab) { + d = &vimvardict; + } else { + dictitem_T *const di = find_var_in_ht(ht, *name, "", 0, false); + d = di->di_tv.vval.v_dict; + } + if (d == NULL) { + internal_error("do_unlet()"); + return FAIL; + } } + hashitem_T *hi = hash_find(ht, (const char_u *)varname); if (HASHITEM_EMPTY(hi)) { hi = find_hi_in_scoped_ht((const char *)name, &ht); @@ -3354,7 +2911,7 @@ static int do_lock_var(lval_T *lp, char_u *const name_end, const int deep, } else if (lp->ll_range) { listitem_T *li = lp->ll_li; - /* (un)lock a range of List items. */ + // (un)lock a range of List items. while (li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { tv_item_lock(TV_LIST_ITEM_TV(li), deep, lock); li = TV_LIST_ITEM_NEXT(lp->ll_list, li); @@ -3404,7 +2961,7 @@ static char_u *cat_prefix_varname(int prefix, char_u *name) if (len > varnamebuflen) { xfree(varnamebuf); - len += 10; /* some additional space */ + len += 10; // some additional space varnamebuf = xmalloc(len); varnamebuflen = len; } @@ -3433,7 +2990,7 @@ char_u *get_user_var_name(expand_T *xp, int idx) tdone = 0; } - /* Global variables */ + // Global variables if (gdone < globvarht.ht_used) { if (gdone++ == 0) hi = globvarht.ht_array; @@ -3446,7 +3003,7 @@ char_u *get_user_var_name(expand_T *xp, int idx) return hi->hi_key; } - /* b: variables */ + // b: variables ht = &curbuf->b_vars->dv_hashtab; if (bdone < ht->ht_used) { if (bdone++ == 0) @@ -3458,7 +3015,7 @@ char_u *get_user_var_name(expand_T *xp, int idx) return cat_prefix_varname('b', hi->hi_key); } - /* w: variables */ + // w: variables ht = &curwin->w_vars->dv_hashtab; if (wdone < ht->ht_used) { if (wdone++ == 0) @@ -3470,7 +3027,7 @@ char_u *get_user_var_name(expand_T *xp, int idx) return cat_prefix_varname('w', hi->hi_key); } - /* t: variables */ + // t: variables ht = &curtab->tp_vars->dv_hashtab; if (tdone < ht->ht_used) { if (tdone++ == 0) @@ -3496,7 +3053,7 @@ char_u *get_user_var_name(expand_T *xp, int idx) /// Return TRUE if "pat" matches "text". /// Does not use 'cpo' and always uses 'magic'. -static int pattern_match(char_u *pat, char_u *text, int ic) +static int pattern_match(char_u *pat, char_u *text, bool ic) { int matches = 0; regmatch_T regmatch; @@ -3582,7 +3139,7 @@ int eval0(char_u *arg, typval_T *rettv, char_u **nextcmd, int evaluate) * * Return OK or FAIL. */ -static int eval1(char_u **arg, typval_T *rettv, int evaluate) +int eval1(char_u **arg, typval_T *rettv, int evaluate) { int result; typval_T var2; @@ -3611,8 +3168,9 @@ static int eval1(char_u **arg, typval_T *rettv, int evaluate) * Get the second variable. */ *arg = skipwhite(*arg + 1); - if (eval1(arg, rettv, evaluate && result) == FAIL) /* recursive! */ + if (eval1(arg, rettv, evaluate && result) == FAIL) { // recursive! return FAIL; + } /* * Check for the ":". @@ -3806,10 +3364,10 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) char_u *p; int i; exptype_T type = TYPE_UNKNOWN; - int type_is = FALSE; /* TRUE for "is" and "isnot" */ + bool type_is = false; // true for "is" and "isnot" int len = 2; varnumber_T n1, n2; - int ic; + bool ic; /* * Get the first variable. @@ -3847,7 +3405,7 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) } if (!isalnum(p[len]) && p[len] != '_') { type = len == 2 ? TYPE_EQUAL : TYPE_NEQUAL; - type_is = TRUE; + type_is = true; } } break; @@ -3857,23 +3415,18 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) * If there is a comparative operator, use it. */ if (type != TYPE_UNKNOWN) { - /* extra question mark appended: ignore case */ + // extra question mark appended: ignore case if (p[len] == '?') { - ic = TRUE; - ++len; - } - /* extra '#' appended: match case */ - else if (p[len] == '#') { - ic = FALSE; - ++len; - } - /* nothing appended: use 'ignorecase' */ - else + ic = true; + len++; + } else if (p[len] == '#') { // extra '#' appended: match case + ic = false; + len++; + } else { // nothing appended: use 'ignorecase' ic = p_ic; + } - /* - * Get the second variable. - */ + // Get the second variable. *arg = skipwhite(p + len); if (eval5(arg, &var2, evaluate) == FAIL) { tv_clear(rettv); @@ -4018,7 +3571,7 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) const char *const s1 = tv_get_string_buf(rettv, buf1); const char *const s2 = tv_get_string_buf(&var2, buf2); if (type != TYPE_MATCH && type != TYPE_NOMATCH) { - i = mb_strcmp_ic((bool)ic, s1, s2); + i = mb_strcmp_ic(ic, s1, s2); } else { i = 0; } @@ -4179,7 +3732,7 @@ static int eval5(char_u **arg, typval_T *rettv, int evaluate) } tv_clear(rettv); - /* If there is a float on either side the result is a float. */ + // If there is a float on either side the result is a float. if (rettv->v_type == VAR_FLOAT || var2.v_type == VAR_FLOAT) { if (op == '+') f1 = f1 + f2; @@ -4618,7 +4171,7 @@ eval_index( char_u **arg, typval_T *rettv, int evaluate, - int verbose /* give error messages */ + int verbose // give error messages ) { bool empty1 = false; @@ -4714,7 +4267,7 @@ eval_index( } } - /* Check for the ']'. */ + // Check for the ']'. if (**arg != ']') { if (verbose) { EMSG(_(e_missbrac)); @@ -4725,7 +4278,7 @@ eval_index( } return FAIL; } - *arg = skipwhite(*arg + 1); /* skip the ']' */ + *arg = skipwhite(*arg + 1); // skip the ']' } if (evaluate) { @@ -4886,8 +4439,8 @@ eval_index( /// @param[in] evaluate If not true, rettv is not populated. /// /// @return OK or FAIL. -static int get_option_tv(const char **const arg, typval_T *const rettv, - const bool evaluate) +int get_option_tv(const char **const arg, typval_T *const rettv, + const bool evaluate) FUNC_ATTR_NONNULL_ARG(1) { long numval; @@ -4917,28 +4470,29 @@ static int get_option_tv(const char **const arg, typval_T *const rettv, opt_type = get_option_value((char_u *)(*arg), &numval, rettv == NULL ? NULL : &stringval, opt_flags); - if (opt_type == -3) { /* invalid name */ - if (rettv != NULL) + if (opt_type == -3) { // invalid name + if (rettv != NULL) { EMSG2(_("E113: Unknown option: %s"), *arg); + } ret = FAIL; } else if (rettv != NULL) { - if (opt_type == -2) { /* hidden string option */ + if (opt_type == -2) { // hidden string option rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; - } else if (opt_type == -1) { /* hidden number option */ + } else if (opt_type == -1) { // hidden number option rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - } else if (opt_type == 1) { /* number option */ + } else if (opt_type == 1) { // number option rettv->v_type = VAR_NUMBER; rettv->vval.v_number = numval; - } else { /* string option */ + } else { // string option rettv->v_type = VAR_STRING; rettv->vval.v_string = stringval; } } else if (working && (opt_type == -2 || opt_type == -1)) ret = FAIL; - *option_end = c; /* put back for error messages */ + *option_end = c; // put back for error messages *arg = option_end; return ret; @@ -4972,7 +4526,7 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate) return FAIL; } - /* If only parsing, set *arg and return here */ + // If only parsing, set *arg and return here if (!evaluate) { *arg = p + 1; return OK; @@ -4996,9 +4550,9 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate) case 'r': *name++ = CAR; ++p; break; case 't': *name++ = TAB; ++p; break; - case 'X': /* hex: "\x1", "\x12" */ + case 'X': // hex: "\x1", "\x12" case 'x': - case 'u': /* Unicode: "\u0023" */ + case 'u': // Unicode: "\u0023" case 'U': if (ascii_isxdigit(p[1])) { int n, nr; @@ -5027,7 +4581,7 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate) } break; - /* octal: "\1", "\12", "\123" */ + // octal: "\1", "\12", "\123" case '0': case '1': case '2': @@ -5096,7 +4650,7 @@ static int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate) return FAIL; } - /* If only parsing return after setting "*arg" */ + // If only parsing return after setting "*arg" if (!evaluate) { *arg = p + 1; return OK; @@ -5279,9 +4833,6 @@ int get_copyID(void) return current_copyID; } -// Used by get_func_tv() -static garray_T funcargs = GA_EMPTY_INIT_VALUE; - /* * Garbage collection for lists and dictionaries. * @@ -5328,11 +4879,7 @@ bool garbage_collect(bool testing) // Don't free variables in the previous_funccal list unless they are only // referenced through previous_funccal. This must be first, because if // the item is referenced elsewhere the funccal must not be freed. - for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) { - fc->fc_copyID = copyID + 1; - ABORTING(set_ref_in_ht)(&fc->l_vars.dv_hashtab, copyID + 1, NULL); - ABORTING(set_ref_in_ht)(&fc->l_avars.dv_hashtab, copyID + 1, NULL); - } + ABORTING(set_ref_in_previous_funccal)(copyID); // script-local variables for (int i = 1; i <= ga_scripts.ga_len; ++i) { @@ -5355,6 +4902,10 @@ bool garbage_collect(bool testing) } // buffer ShaDa additional data ABORTING(set_ref_dict)(buf->additional_data, copyID); + + // buffer callback functions + set_ref_in_callback(&buf->b_prompt_callback, copyID, NULL, NULL); + set_ref_in_callback(&buf->b_prompt_interrupt, copyID, NULL, NULL); } FOR_ALL_TAB_WINDOWS(tp, wp) { @@ -5405,14 +4956,10 @@ bool garbage_collect(bool testing) ABORTING(set_ref_in_ht)(&globvarht, copyID, NULL); // function-local variables - for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) { - fc->fc_copyID = copyID; - ABORTING(set_ref_in_ht)(&fc->l_vars.dv_hashtab, copyID, NULL); - ABORTING(set_ref_in_ht)(&fc->l_avars.dv_hashtab, copyID, NULL); - } + ABORTING(set_ref_in_call_stack)(copyID); // named functions (matters for closures) - ABORTING(set_ref_in_functions(copyID)); + ABORTING(set_ref_in_functions)(copyID); // Channels { @@ -5433,10 +4980,7 @@ bool garbage_collect(bool testing) } // function call arguments, if v:testing is set. - for (int i = 0; i < funcargs.ga_len; i++) { - ABORTING(set_ref_in_item)(((typval_T **)funcargs.ga_data)[i], - copyID, NULL, NULL); - } + ABORTING(set_ref_in_func_args)(copyID); // v: vars ABORTING(set_ref_in_ht)(&vimvarht, copyID, NULL); @@ -5479,23 +5023,8 @@ bool garbage_collect(bool testing) did_free = free_unref_items(copyID); // 3. Check if any funccal can be freed now. - bool did_free_funccal = false; - for (funccall_T **pfc = &previous_funccal; *pfc != NULL;) { - if (can_free_funccal(*pfc, copyID)) { - funccall_T *fc = *pfc; - *pfc = fc->caller; - free_funccal(fc, true); - did_free = true; - did_free_funccal = true; - } else { - pfc = &(*pfc)->caller; - } - } - if (did_free_funccal) { - // When a funccal was freed some more items might be garbage - // collected, so run again. - (void)garbage_collect(testing); - } + // This may call us back recursively. + did_free = did_free || free_unref_funccal(copyID, testing); } else if (p_verbose > 0) { verb_msg(_( "Not enough memory to set references, garbage collection aborted!")); @@ -5750,27 +5279,6 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, return abort; } -/// Set "copyID" in all functions available by name. -bool set_ref_in_functions(int copyID) -{ - int todo; - hashitem_T *hi = NULL; - bool abort = false; - ufunc_T *fp; - - todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) { - if (!HASHITEM_EMPTY(hi)) { - todo--; - fp = HI2UF(hi); - if (!func_name_refcount(fp->uf_name)) { - abort = abort || set_ref_in_func(NULL, fp, copyID); - } - } - } - return abort; -} - /// Mark all lists and dicts referenced in given mark @@ -5819,19 +5327,6 @@ static inline bool set_ref_dict(dict_T *dict, int copyID) return false; } -static bool set_ref_in_funccal(funccall_T *fc, int copyID) -{ - bool abort = false; - - if (fc->fc_copyID != copyID) { - fc->fc_copyID = copyID; - abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); - abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL); - abort = abort || set_ref_in_func(NULL, fc->func, copyID); - } - return abort; -} - /* * Allocate a variable for a Dictionary and fill it from "*arg". * Return OK or FAIL. Returns NOTDONE for {expr}. @@ -5854,10 +5349,12 @@ static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate) * But {} is an empty Dictionary. */ if (*start != '}') { - if (eval1(&start, &tv, FALSE) == FAIL) /* recursive! */ + if (eval1(&start, &tv, false) == FAIL) { // recursive! return FAIL; - if (*start == '}') + } + if (*start == '}') { return NOTDONE; + } } if (evaluate) { @@ -5868,8 +5365,9 @@ static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate) *arg = skipwhite(*arg + 1); while (**arg != '}' && **arg != NUL) { - if (eval1(arg, &tvkey, evaluate) == FAIL) /* recursive! */ + if (eval1(arg, &tvkey, evaluate) == FAIL) { // recursive! goto failret; + } if (**arg != ':') { EMSG2(_("E720: Missing colon in Dictionary: %s"), *arg); tv_clear(&tvkey); @@ -5934,224 +5432,6 @@ failret: return OK; } -/// Get function arguments. -static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, - int *varargs, bool skip) -{ - bool mustend = false; - char_u *arg = *argp; - char_u *p = arg; - int c; - int i; - - if (newargs != NULL) { - ga_init(newargs, (int)sizeof(char_u *), 3); - } - - if (varargs != NULL) { - *varargs = false; - } - - // Isolate the arguments: "arg1, arg2, ...)" - while (*p != endchar) { - if (p[0] == '.' && p[1] == '.' && p[2] == '.') { - if (varargs != NULL) { - *varargs = true; - } - p += 3; - mustend = true; - } else { - arg = p; - while (ASCII_ISALNUM(*p) || *p == '_') { - p++; - } - if (arg == p || isdigit(*arg) - || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0) - || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) { - if (!skip) { - EMSG2(_("E125: Illegal argument: %s"), arg); - } - break; - } - if (newargs != NULL) { - ga_grow(newargs, 1); - c = *p; - *p = NUL; - arg = vim_strsave(arg); - - // Check for duplicate argument name. - for (i = 0; i < newargs->ga_len; i++) { - if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0) { - EMSG2(_("E853: Duplicate argument name: %s"), arg); - xfree(arg); - goto err_ret; - } - } - ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg; - newargs->ga_len++; - - *p = c; - } - if (*p == ',') { - p++; - } else { - mustend = true; - } - } - p = skipwhite(p); - if (mustend && *p != endchar) { - if (!skip) { - EMSG2(_(e_invarg2), *argp); - } - break; - } - } - if (*p != endchar) { - goto err_ret; - } - p++; // skip "endchar" - - *argp = p; - return OK; - -err_ret: - if (newargs != NULL) { - ga_clear_strings(newargs); - } - return FAIL; -} - -/// Register function "fp" as using "current_funccal" as its scope. -static void register_closure(ufunc_T *fp) -{ - if (fp->uf_scoped == current_funccal) { - // no change - return; - } - funccal_unref(fp->uf_scoped, fp, false); - fp->uf_scoped = current_funccal; - current_funccal->fc_refcount++; - ga_grow(¤t_funccal->fc_funcs, 1); - ((ufunc_T **)current_funccal->fc_funcs.ga_data) - [current_funccal->fc_funcs.ga_len++] = fp; -} - -/// Parse a lambda expression and get a Funcref from "*arg". -/// -/// @return OK or FAIL. Returns NOTDONE for dict or {expr}. -static int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) -{ - garray_T newargs = GA_EMPTY_INIT_VALUE; - garray_T *pnewargs; - ufunc_T *fp = NULL; - int varargs; - int ret; - char_u *start = skipwhite(*arg + 1); - char_u *s, *e; - static int lambda_no = 0; - int *old_eval_lavars = eval_lavars_used; - int eval_lavars = false; - - // First, check if this is a lambda expression. "->" must exists. - ret = get_function_args(&start, '-', NULL, NULL, true); - if (ret == FAIL || *start != '>') { - return NOTDONE; - } - - // Parse the arguments again. - if (evaluate) { - pnewargs = &newargs; - } else { - pnewargs = NULL; - } - *arg = skipwhite(*arg + 1); - ret = get_function_args(arg, '-', pnewargs, &varargs, false); - if (ret == FAIL || **arg != '>') { - goto errret; - } - - // Set up a flag for checking local variables and arguments. - if (evaluate) { - eval_lavars_used = &eval_lavars; - } - - // Get the start and the end of the expression. - *arg = skipwhite(*arg + 1); - s = *arg; - ret = skip_expr(arg); - if (ret == FAIL) { - goto errret; - } - e = *arg; - *arg = skipwhite(*arg); - if (**arg != '}') { - goto errret; - } - (*arg)++; - - if (evaluate) { - int len, flags = 0; - char_u *p; - char_u name[20]; - partial_T *pt; - garray_T newlines; - - lambda_no++; - snprintf((char *)name, sizeof(name), "<lambda>%d", lambda_no); - - fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); - pt = xcalloc(1, sizeof(partial_T)); - - ga_init(&newlines, (int)sizeof(char_u *), 1); - ga_grow(&newlines, 1); - - // Add "return " before the expression. - len = 7 + e - s + 1; - p = (char_u *)xmalloc(len); - ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; - STRCPY(p, "return "); - STRLCPY(p + 7, s, e - s + 1); - - fp->uf_refcount = 1; - STRCPY(fp->uf_name, name); - hash_add(&func_hashtab, UF2HIKEY(fp)); - fp->uf_args = newargs; - fp->uf_lines = newlines; - if (current_funccal != NULL && eval_lavars) { - flags |= FC_CLOSURE; - register_closure(fp); - } else { - fp->uf_scoped = NULL; - } - - if (prof_def_func()) { - func_do_profile(fp); - } - if (sandbox) { - flags |= FC_SANDBOX; - } - fp->uf_varargs = true; - fp->uf_flags = flags; - fp->uf_calls = 0; - fp->uf_script_ctx = current_sctx; - fp->uf_script_ctx.sc_lnum += sourcing_lnum - newlines.ga_len; - - pt->pt_func = fp; - pt->pt_refcount = 1; - rettv->vval.v_partial = pt; - rettv->v_type = VAR_PARTIAL; - } - - eval_lavars_used = old_eval_lavars; - return OK; - -errret: - ga_clear_strings(&newargs); - xfree(fp); - eval_lavars_used = old_eval_lavars; - return FAIL; -} - /// Convert the string to a floating point number /// /// This uses strtod(). setlocale(LC_NUMERIC, "C") has been used earlier to @@ -6226,728 +5506,9 @@ static int get_env_tv(char_u **arg, typval_T *rettv, int evaluate) return OK; } -#ifdef INCLUDE_GENERATED_DECLARATIONS - -#ifdef _MSC_VER -// This prevents MSVC from replacing the functions with intrinsics, -// and causing errors when trying to get their addresses in funcs.generated.h -#pragma function (ceil) -#pragma function (floor) -#endif - -PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES -# include "funcs.generated.h" -PRAGMA_DIAG_POP -#endif - -/* - * Function given to ExpandGeneric() to obtain the list of internal - * or user defined function names. - */ -char_u *get_function_name(expand_T *xp, int idx) -{ - static int intidx = -1; - char_u *name; - - if (idx == 0) - intidx = -1; - if (intidx < 0) { - name = get_user_func_name(xp, idx); - if (name != NULL) - return name; - } - while ( (size_t)++intidx < ARRAY_SIZE(functions) - && functions[intidx].name[0] == '\0') { - } - - if ((size_t)intidx >= ARRAY_SIZE(functions)) { - return NULL; - } - - const char *const key = functions[intidx].name; - const size_t key_len = strlen(key); - memcpy(IObuff, key, key_len); - IObuff[key_len] = '('; - if (functions[intidx].max_argc == 0) { - IObuff[key_len + 1] = ')'; - IObuff[key_len + 2] = NUL; - } else { - IObuff[key_len + 1] = NUL; - } - return IObuff; -} - -/* - * Function given to ExpandGeneric() to obtain the list of internal or - * user defined variable or function names. - */ -char_u *get_expr_name(expand_T *xp, int idx) -{ - static int intidx = -1; - char_u *name; - - if (idx == 0) - intidx = -1; - if (intidx < 0) { - name = get_function_name(xp, idx); - if (name != NULL) - return name; - } - return get_user_var_name(xp, ++intidx); -} - -/// Find internal function in hash functions -/// -/// @param[in] name Name of the function. -/// -/// Returns pointer to the function definition or NULL if not found. -static const VimLFuncDef *find_internal_func(const char *const name) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL -{ - size_t len = strlen(name); - return find_internal_func_gperf(name, len); -} - -/// Return name of the function corresponding to `name` -/// -/// If `name` points to variable that is either a function or partial then -/// corresponding function name is returned. Otherwise it returns `name` itself. -/// -/// @param[in] name Function name to check. -/// @param[in,out] lenp Location where length of the returned name is stored. -/// Must be set to the length of the `name` argument. -/// @param[out] partialp Location where partial will be stored if found -/// function appears to be a partial. May be NULL if this -/// is not needed. -/// @param[in] no_autoload If true, do not source autoload scripts if function -/// was not found. -/// -/// @return name of the function. -static char_u *deref_func_name(const char *name, int *lenp, - partial_T **const partialp, bool no_autoload) - FUNC_ATTR_NONNULL_ARG(1, 2) -{ - if (partialp != NULL) { - *partialp = NULL; - } - - dictitem_T *const v = find_var(name, (size_t)(*lenp), NULL, no_autoload); - if (v != NULL && v->di_tv.v_type == VAR_FUNC) { - if (v->di_tv.vval.v_string == NULL) { // just in case - *lenp = 0; - return (char_u *)""; - } - *lenp = (int)STRLEN(v->di_tv.vval.v_string); - return v->di_tv.vval.v_string; - } - - if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) { - partial_T *const pt = v->di_tv.vval.v_partial; - - if (pt == NULL) { // just in case - *lenp = 0; - return (char_u *)""; - } - if (partialp != NULL) { - *partialp = pt; - } - char_u *s = partial_name(pt); - *lenp = (int)STRLEN(s); - return s; - } - - return (char_u *)name; -} - -/* - * Allocate a variable for the result of a function. - * Return OK or FAIL. - */ -static int -get_func_tv( - const char_u *name, // name of the function - int len, // length of "name" - typval_T *rettv, - char_u **arg, // argument, pointing to the '(' - linenr_T firstline, // first line of range - linenr_T lastline, // last line of range - int *doesrange, // return: function handled range - int evaluate, - partial_T *partial, // for extra arguments - dict_T *selfdict // Dictionary for "self" -) -{ - char_u *argp; - int ret = OK; - typval_T argvars[MAX_FUNC_ARGS + 1]; /* vars for arguments */ - int argcount = 0; /* number of arguments found */ - - /* - * Get the arguments. - */ - argp = *arg; - while (argcount < MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { - argp = skipwhite(argp + 1); // skip the '(' or ',' - if (*argp == ')' || *argp == ',' || *argp == NUL) { - break; - } - if (eval1(&argp, &argvars[argcount], evaluate) == FAIL) { - ret = FAIL; - break; - } - ++argcount; - if (*argp != ',') - break; - } - if (*argp == ')') - ++argp; - else - ret = FAIL; - - if (ret == OK) { - int i = 0; - - if (get_vim_var_nr(VV_TESTING)) { - // Prepare for calling garbagecollect_for_testing(), need to know - // what variables are used on the call stack. - if (funcargs.ga_itemsize == 0) { - ga_init(&funcargs, (int)sizeof(typval_T *), 50); - } - for (i = 0; i < argcount; i++) { - ga_grow(&funcargs, 1); - ((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i]; - } - } - ret = call_func(name, len, rettv, argcount, argvars, NULL, - firstline, lastline, doesrange, evaluate, - partial, selfdict); - - funcargs.ga_len -= i; - } else if (!aborting()) { - if (argcount == MAX_FUNC_ARGS) { - emsg_funcname(N_("E740: Too many arguments for function %s"), name); - } else { - emsg_funcname(N_("E116: Invalid arguments for function %s"), name); - } - } - - while (--argcount >= 0) { - tv_clear(&argvars[argcount]); - } - - *arg = skipwhite(argp); - return ret; -} - -typedef enum { - ERROR_UNKNOWN = 0, - ERROR_TOOMANY, - ERROR_TOOFEW, - ERROR_SCRIPT, - ERROR_DICT, - ERROR_NONE, - ERROR_OTHER, - ERROR_BOTH, - ERROR_DELETED, -} FnameTransError; - -#define FLEN_FIXED 40 - -/// In a script transform script-local names into actually used names -/// -/// Transforms "<SID>" and "s:" prefixes to `K_SNR {N}` (e.g. K_SNR "123") and -/// "<SNR>" prefix to `K_SNR`. Uses `fname_buf` buffer that is supposed to have -/// #FLEN_FIXED + 1 length when it fits, otherwise it allocates memory. -/// -/// @param[in] name Name to transform. -/// @param fname_buf Buffer to save resulting function name to, if it fits. -/// Must have at least #FLEN_FIXED + 1 length. -/// @param[out] tofree Location where pointer to an allocated memory is saved -/// in case result does not fit into fname_buf. -/// @param[out] error Location where error type is saved, @see -/// FnameTransError. -/// -/// @return transformed name: either `fname_buf` or a pointer to an allocated -/// memory. -static char_u *fname_trans_sid(const char_u *const name, - char_u *const fname_buf, - char_u **const tofree, int *const error) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ - char_u *fname; - const int llen = eval_fname_script((const char *)name); - if (llen > 0) { - fname_buf[0] = K_SPECIAL; - fname_buf[1] = KS_EXTRA; - fname_buf[2] = (int)KE_SNR; - int i = 3; - if (eval_fname_sid((const char *)name)) { // "<SID>" or "s:" - if (current_sctx.sc_sid <= 0) { - *error = ERROR_SCRIPT; - } else { - snprintf((char *)fname_buf + 3, FLEN_FIXED + 1, "%" PRId64 "_", - (int64_t)current_sctx.sc_sid); - i = (int)STRLEN(fname_buf); - } - } - if (i + STRLEN(name + llen) < FLEN_FIXED) { - STRCPY(fname_buf + i, name + llen); - fname = fname_buf; - } else { - fname = xmalloc(i + STRLEN(name + llen) + 1); - *tofree = fname; - memmove(fname, fname_buf, (size_t)i); - STRCPY(fname + i, name + llen); - } - } else { - fname = (char_u *)name; - } - - return fname; -} - -/// Mark all lists and dicts referenced through function "name" with "copyID". -/// "list_stack" is used to add lists to be marked. Can be NULL. -/// "ht_stack" is used to add hashtabs to be marked. Can be NULL. -/// -/// @return true if setting references failed somehow. -bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) -{ - ufunc_T *fp = fp_in; - funccall_T *fc; - int error = ERROR_NONE; - char_u fname_buf[FLEN_FIXED + 1]; - char_u *tofree = NULL; - char_u *fname; - bool abort = false; - if (name == NULL && fp_in == NULL) { - return false; - } - - if (fp_in == NULL) { - fname = fname_trans_sid(name, fname_buf, &tofree, &error); - fp = find_func(fname); - } - if (fp != NULL) { - for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped) { - abort = abort || set_ref_in_funccal(fc, copyID); - } - } - xfree(tofree); - return abort; -} - -/// Call a function with its resolved parameters -/// -/// "argv_func", when not NULL, can be used to fill in arguments only when the -/// invoked function uses them. It is called like this: -/// new_argcount = argv_func(current_argcount, argv, called_func_argcount) -/// -/// @return FAIL if function cannot be called, else OK (even if an error -/// occurred while executing the function! Set `msg_list` to capture -/// the error, see do_cmdline()). -int -call_func( - const char_u *funcname, // name of the function - int len, // length of "name" - typval_T *rettv, // [out] value goes here - int argcount_in, // number of "argvars" - typval_T *argvars_in, // vars for arguments, must have "argcount" - // PLUS ONE elements! - ArgvFunc argv_func, // function to fill in argvars - linenr_T firstline, // first line of range - linenr_T lastline, // last line of range - int *doesrange, // [out] function handled range - bool evaluate, - partial_T *partial, // optional, can be NULL - dict_T *selfdict_in // Dictionary for "self" -) - FUNC_ATTR_NONNULL_ARG(1, 3, 5, 9) -{ - int ret = FAIL; - int error = ERROR_NONE; - ufunc_T *fp; - char_u fname_buf[FLEN_FIXED + 1]; - char_u *tofree = NULL; - char_u *fname; - char_u *name; - int argcount = argcount_in; - typval_T *argvars = argvars_in; - dict_T *selfdict = selfdict_in; - typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" is not NULL - int argv_clear = 0; - - // Initialize rettv so that it is safe for caller to invoke clear_tv(rettv) - // even when call_func() returns FAIL. - rettv->v_type = VAR_UNKNOWN; - - // Make a copy of the name, if it comes from a funcref variable it could - // be changed or deleted in the called function. - name = vim_strnsave(funcname, len); - - fname = fname_trans_sid(name, fname_buf, &tofree, &error); - - *doesrange = false; - - if (partial != NULL) { - // When the function has a partial with a dict and there is a dict - // argument, use the dict argument. That is backwards compatible. - // When the dict was bound explicitly use the one from the partial. - if (partial->pt_dict != NULL - && (selfdict_in == NULL || !partial->pt_auto)) { - selfdict = partial->pt_dict; - } - if (error == ERROR_NONE && partial->pt_argc > 0) { - for (argv_clear = 0; argv_clear < partial->pt_argc; argv_clear++) { - tv_copy(&partial->pt_argv[argv_clear], &argv[argv_clear]); - } - for (int i = 0; i < argcount_in; i++) { - argv[i + argv_clear] = argvars_in[i]; - } - argvars = argv; - argcount = partial->pt_argc + argcount_in; - } - } - - if (error == ERROR_NONE && evaluate) { - char_u *rfname = fname; - - /* Ignore "g:" before a function name. */ - if (fname[0] == 'g' && fname[1] == ':') { - rfname = fname + 2; - } - - rettv->v_type = VAR_NUMBER; /* default rettv is number zero */ - rettv->vval.v_number = 0; - error = ERROR_UNKNOWN; - - if (partial == vvlua_partial) { - if (len > 0) { - error = ERROR_NONE; - executor_call_lua((const char *)funcname, len, - argvars, argcount, rettv); - } - } else if (!builtin_function((const char *)rfname, -1)) { - // User defined function. - if (partial != NULL && partial->pt_func != NULL) { - fp = partial->pt_func; - } else { - fp = find_func(rfname); - } - - // Trigger FuncUndefined event, may load the function. - if (fp == NULL - && apply_autocmds(EVENT_FUNCUNDEFINED, rfname, rfname, TRUE, NULL) - && !aborting()) { - /* executed an autocommand, search for the function again */ - fp = find_func(rfname); - } - // Try loading a package. - if (fp == NULL && script_autoload((const char *)rfname, STRLEN(rfname), - true) && !aborting()) { - // Loaded a package, search for the function again. - fp = find_func(rfname); - } - - if (fp != NULL && (fp->uf_flags & FC_DELETED)) { - error = ERROR_DELETED; - } else if (fp != NULL) { - if (argv_func != NULL) { - argcount = argv_func(argcount, argvars, fp->uf_args.ga_len); - } - if (fp->uf_flags & FC_RANGE) { - *doesrange = true; - } - if (argcount < fp->uf_args.ga_len) { - error = ERROR_TOOFEW; - } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) { - error = ERROR_TOOMANY; - } else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) { - error = ERROR_DICT; - } else { - // Call the user function. - call_user_func(fp, argcount, argvars, rettv, firstline, lastline, - (fp->uf_flags & FC_DICT) ? selfdict : NULL); - error = ERROR_NONE; - } - } - } else { - // Find the function name in the table, call its implementation. - const VimLFuncDef *const fdef = find_internal_func((const char *)fname); - if (fdef != NULL) { - if (argcount < fdef->min_argc) { - error = ERROR_TOOFEW; - } else if (argcount > fdef->max_argc) { - error = ERROR_TOOMANY; - } else { - argvars[argcount].v_type = VAR_UNKNOWN; - fdef->func(argvars, rettv, fdef->data); - error = ERROR_NONE; - } - } - } - /* - * The function call (or "FuncUndefined" autocommand sequence) might - * have been aborted by an error, an interrupt, or an explicitly thrown - * exception that has not been caught so far. This situation can be - * tested for by calling aborting(). For an error in an internal - * function or for the "E132" error in call_user_func(), however, the - * throw point at which the "force_abort" flag (temporarily reset by - * emsg()) is normally updated has not been reached yet. We need to - * update that flag first to make aborting() reliable. - */ - update_force_abort(); - } - if (error == ERROR_NONE) - ret = OK; - - /* - * Report an error unless the argument evaluation or function call has been - * cancelled due to an aborting error, an interrupt, or an exception. - */ - if (!aborting()) { - switch (error) { - case ERROR_UNKNOWN: - emsg_funcname(N_("E117: Unknown function: %s"), name); - break; - case ERROR_DELETED: - emsg_funcname(N_("E933: Function was deleted: %s"), name); - break; - case ERROR_TOOMANY: - emsg_funcname(e_toomanyarg, name); - break; - case ERROR_TOOFEW: - emsg_funcname(N_("E119: Not enough arguments for function: %s"), - name); - break; - case ERROR_SCRIPT: - emsg_funcname(N_("E120: Using <SID> not in a script context: %s"), - name); - break; - case ERROR_DICT: - emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"), - name); - break; - } - } - - while (argv_clear > 0) { - tv_clear(&argv[--argv_clear]); - } - xfree(tofree); - xfree(name); - - return ret; -} - -/// Give an error message with a function name. Handle <SNR> things. -/// -/// @param ermsg must be passed without translation (use N_() instead of _()). -/// @param name function name -static void emsg_funcname(char *ermsg, const char_u *name) -{ - char_u *p; - - if (*name == K_SPECIAL) { - p = concat_str((char_u *)"<SNR>", name + 3); - } else { - p = (char_u *)name; - } - - EMSG2(_(ermsg), p); - - if (p != name) { - xfree(p); - } -} - -/* - * Return TRUE for a non-zero Number and a non-empty String. - */ -static int non_zero_arg(typval_T *argvars) -{ - return ((argvars[0].v_type == VAR_NUMBER - && argvars[0].vval.v_number != 0) - || (argvars[0].v_type == VAR_SPECIAL - && argvars[0].vval.v_special == kSpecialVarTrue) - || (argvars[0].v_type == VAR_STRING - && argvars[0].vval.v_string != NULL - && *argvars[0].vval.v_string != NUL)); -} - -/********************************************* - * Implementation of the built-in functions - */ - - -// Apply a floating point C function on a typval with one float_T. -// -// Some versions of glibc on i386 have an optimization that makes it harder to -// call math functions indirectly from inside an inlined function, causing -// compile-time errors. Avoid `inline` in that case. #3072 -static void float_op_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - float_T f; - float_T (*function)(float_T) = (float_T (*)(float_T))fptr; - - rettv->v_type = VAR_FLOAT; - if (tv_get_float_chk(argvars, &f)) { - rettv->vval.v_float = function(f); - } else { - rettv->vval.v_float = 0.0; - } -} - -static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (check_restricted() || check_secure()) { - return; - } - - ApiDispatchWrapper fn = (ApiDispatchWrapper)fptr; - - Array args = ARRAY_DICT_INIT; - - for (typval_T *tv = argvars; tv->v_type != VAR_UNKNOWN; tv++) { - ADD(args, vim_to_object(tv)); - } - - Error err = ERROR_INIT; - Object result = fn(VIML_INTERNAL_CALL, args, &err); - - if (ERROR_SET(&err)) { - emsgf_multiline((const char *)e_api_error, err.msg); - goto end; - } - - if (!object_to_vim(result, rettv, &err)) { - EMSG2(_("Error converting the call result: %s"), err.msg); - } - -end: - api_free_array(args); - api_free_object(result); - api_clear_error(&err); -} - -/* - * "abs(expr)" function - */ -static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type == VAR_FLOAT) { - float_op_wrapper(argvars, rettv, (FunPtr)&fabs); - } else { - varnumber_T n; - bool error = false; - - n = tv_get_number_chk(&argvars[0], &error); - if (error) { - rettv->vval.v_number = -1; - } else if (n > 0) { - rettv->vval.v_number = n; - } else { - rettv->vval.v_number = -n; - } - } -} - -/* - * "add(list, item)" function - */ -static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = 1; // Default: failed. - if (argvars[0].v_type == VAR_LIST) { - list_T *const l = argvars[0].vval.v_list; - if (!tv_check_lock(tv_list_locked(l), N_("add() argument"), TV_TRANSLATE)) { - tv_list_append_tv(l, &argvars[1]); - tv_copy(&argvars[0], rettv); - } - } else { - EMSG(_(e_listreq)); - } -} - -/* - * "and(expr, expr)" function - */ -static void f_and(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) - & tv_get_number_chk(&argvars[1], NULL); -} - - -/// "api_info()" function -static void f_api_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - Dictionary metadata = api_metadata(); - (void)object_to_vim(DICTIONARY_OBJ(metadata), rettv, NULL); - api_free_dictionary(metadata); -} - -// "append(lnum, string/list)" function -static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const linenr_T lnum = tv_get_lnum(&argvars[0]); - - set_buffer_lines(curbuf, lnum, true, &argvars[1], rettv); -} - -// "appendbufline(buf, lnum, string/list)" function -static void f_appendbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *const buf = tv_get_buf(&argvars[0], false); - if (buf == NULL) { - rettv->vval.v_number = 1; // FAIL - } else { - const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); - set_buffer_lines(buf, lnum, true, &argvars[2], rettv); - } -} - -static void f_argc(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type == VAR_UNKNOWN) { - // use the current window - rettv->vval.v_number = ARGCOUNT; - } else if (argvars[0].v_type == VAR_NUMBER - && tv_get_number(&argvars[0]) == -1) { - // use the global argument list - rettv->vval.v_number = GARGCOUNT; - } else { - // use the argument list of the specified window - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp != NULL) { - rettv->vval.v_number = WARGCOUNT(wp); - } else { - rettv->vval.v_number = -1; - } - } -} - -/* - * "argidx()" function - */ -static void f_argidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = curwin->w_arg_idx; -} - -/// "arglistid" function -static void f_arglistid(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; - win_T *wp = find_tabwin(&argvars[0], &argvars[1]); - if (wp != NULL) { - rettv->vval.v_number = wp->w_alist->id; - } -} - /// Get the argument list for a given window -static void get_arglist_as_rettv(aentry_T *arglist, int argcount, - typval_T *rettv) +void get_arglist_as_rettv(aentry_T *arglist, int argcount, + typval_T *rettv) { tv_list_alloc_ret(rettv, argcount); if (arglist != NULL) { @@ -6958,46 +5519,8 @@ static void get_arglist_as_rettv(aentry_T *arglist, int argcount, } } -/* - * "argv(nr)" function - */ -static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - aentry_T *arglist = NULL; - int argcount = -1; - - if (argvars[0].v_type != VAR_UNKNOWN) { - if (argvars[1].v_type == VAR_UNKNOWN) { - arglist = ARGLIST; - argcount = ARGCOUNT; - } else if (argvars[1].v_type == VAR_NUMBER - && tv_get_number(&argvars[1]) == -1) { - arglist = GARGLIST; - argcount = GARGCOUNT; - } else { - win_T *wp = find_win_by_nr_or_id(&argvars[1]); - if (wp != NULL) { - // Use the argument list of the specified window - arglist = WARGLIST(wp); - argcount = WARGCOUNT(wp); - } - } - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - int idx = tv_get_number_chk(&argvars[0], NULL); - if (arglist != NULL && idx >= 0 && idx < argcount) { - rettv->vval.v_string = (char_u *)xstrdup( - (const char *)alist_name(&arglist[idx])); - } else if (idx == -1) { - get_arglist_as_rettv(arglist, argcount, rettv); - } - } else { - get_arglist_as_rettv(ARGLIST, ARGCOUNT, rettv); - } -} - // Prepare "gap" for an assert error and add the sourcing position. -static void prepare_assert_error(garray_T *gap) +void prepare_assert_error(garray_T *gap) { char buf[NUMBUFLEN]; @@ -7051,9 +5574,9 @@ static void ga_concat_esc(garray_T *gap, char_u *str) } // Fill "gap" with information about an assert error. -static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, - char_u *exp_str, typval_T *exp_tv, - typval_T *got_tv, assert_type_T atype) +void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, + char_u *exp_str, typval_T *exp_tv, + typval_T *got_tv, assert_type_T atype) { char_u *tofree; @@ -7095,7 +5618,7 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, } // Add an assert error to v:errors. -static void assert_error(garray_T *gap) +void assert_error(garray_T *gap) { struct vimvar *vp = &vimvars[VV_ERRORS]; @@ -7107,7 +5630,7 @@ static void assert_error(garray_T *gap) (const char *)gap->ga_data, (ptrdiff_t)gap->ga_len); } -static int assert_equal_common(typval_T *argvars, assert_type_T atype) +int assert_equal_common(typval_T *argvars, assert_type_T atype) FUNC_ATTR_NONNULL_ALL { garray_T ga; @@ -7124,7 +5647,7 @@ static int assert_equal_common(typval_T *argvars, assert_type_T atype) return 0; } -static int assert_equalfile(typval_T *argvars) +int assert_equalfile(typval_T *argvars) FUNC_ATTR_NONNULL_ALL { char buf1[NUMBUFLEN]; @@ -7178,120 +5701,7 @@ static int assert_equalfile(typval_T *argvars) return 0; } -static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const cmd = tv_get_string_chk(&argvars[0]); - garray_T ga; - int ret = 0; - - called_vim_beep = false; - suppress_errthrow = true; - emsg_silent = false; - do_cmdline_cmd(cmd); - if (!called_vim_beep) { - prepare_assert_error(&ga); - ga_concat(&ga, (const char_u *)"command did not beep: "); - ga_concat(&ga, (const char_u *)cmd); - assert_error(&ga); - ga_clear(&ga); - ret = 1; - } - - suppress_errthrow = false; - emsg_on_display = false; - rettv->vval.v_number = ret; -} - -// "assert_equal(expected, actual[, msg])" function -static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL); -} - -// "assert_equalfile(fname-one, fname-two)" function -static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_equalfile(argvars); -} - -// "assert_notequal(expected, actual[, msg])" function -static void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL); -} - -/// "assert_report(msg) -static void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - garray_T ga; - - prepare_assert_error(&ga); - ga_concat(&ga, (const char_u *)tv_get_string(&argvars[0])); - assert_error(&ga); - ga_clear(&ga); - rettv->vval.v_number = 1; -} - -/// "assert_exception(string[, msg])" function -static void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_exception(argvars); -} - -/// "assert_fails(cmd [, error [, msg]])" function -static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const cmd = tv_get_string_chk(&argvars[0]); - garray_T ga; - int ret = 0; - int save_trylevel = trylevel; - - // trylevel must be zero for a ":throw" command to be considered failed - trylevel = 0; - called_emsg = false; - suppress_errthrow = true; - emsg_silent = true; - - do_cmdline_cmd(cmd); - if (!called_emsg) { - prepare_assert_error(&ga); - ga_concat(&ga, (const char_u *)"command did not fail: "); - if (argvars[1].v_type != VAR_UNKNOWN - && argvars[2].v_type != VAR_UNKNOWN) { - char *const tofree = encode_tv2echo(&argvars[2], NULL); - ga_concat(&ga, (char_u *)tofree); - xfree(tofree); - } else { - ga_concat(&ga, (const char_u *)cmd); - } - assert_error(&ga); - ga_clear(&ga); - ret = 1; - } else if (argvars[1].v_type != VAR_UNKNOWN) { - char buf[NUMBUFLEN]; - const char *const error = tv_get_string_buf_chk(&argvars[1], buf); - - if (error == NULL - || strstr((char *)vimvars[VV_ERRMSG].vv_str, error) == NULL) { - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[2], NULL, &argvars[1], - &vimvars[VV_ERRMSG].vv_tv, ASSERT_OTHER); - assert_error(&ga); - ga_clear(&ga); - ret = 1; - } - } - - trylevel = save_trylevel; - called_emsg = false; - suppress_errthrow = false; - emsg_silent = false; - emsg_on_display = false; - set_vim_var_string(VV_ERRMSG, NULL, 0); - rettv->vval.v_number = ret; -} - -static int assert_inrange(typval_T *argvars) +int assert_inrange(typval_T *argvars) FUNC_ATTR_NONNULL_ALL { bool error = false; @@ -7320,7 +5730,7 @@ static int assert_inrange(typval_T *argvars) } // Common for assert_true() and assert_false(). -static int assert_bool(typval_T *argvars, bool is_true) +int assert_bool(typval_T *argvars, bool is_true) FUNC_ATTR_NONNULL_ALL { bool error = false; @@ -7345,7 +5755,7 @@ static int assert_bool(typval_T *argvars, bool is_true) return 0; } -static int assert_exception(typval_T *argvars) +int assert_exception(typval_T *argvars) FUNC_ATTR_NONNULL_ALL { garray_T ga; @@ -7369,13 +5779,60 @@ static int assert_exception(typval_T *argvars) return 0; } -// "assert_false(actual[, msg])" function -static void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr) +int assert_fails(typval_T *argvars) + FUNC_ATTR_NONNULL_ALL { - rettv->vval.v_number = assert_bool(argvars, false); + const char *const cmd = tv_get_string_chk(&argvars[0]); + garray_T ga; + int ret = 0; + int save_trylevel = trylevel; + + // trylevel must be zero for a ":throw" command to be considered failed + trylevel = 0; + called_emsg = false; + suppress_errthrow = true; + emsg_silent = true; + + do_cmdline_cmd(cmd); + if (!called_emsg) { + prepare_assert_error(&ga); + ga_concat(&ga, (const char_u *)"command did not fail: "); + if (argvars[1].v_type != VAR_UNKNOWN + && argvars[2].v_type != VAR_UNKNOWN) { + char *const tofree = encode_tv2echo(&argvars[2], NULL); + ga_concat(&ga, (char_u *)tofree); + xfree(tofree); + } else { + ga_concat(&ga, (const char_u *)cmd); + } + assert_error(&ga); + ga_clear(&ga); + ret = 1; + } else if (argvars[1].v_type != VAR_UNKNOWN) { + char buf[NUMBUFLEN]; + const char *const error = tv_get_string_buf_chk(&argvars[1], buf); + + if (error == NULL + || strstr((char *)vimvars[VV_ERRMSG].vv_str, error) == NULL) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[2], NULL, &argvars[1], + &vimvars[VV_ERRMSG].vv_tv, ASSERT_OTHER); + assert_error(&ga); + ga_clear(&ga); + ret = 1; + } + } + + trylevel = save_trylevel; + called_emsg = false; + suppress_errthrow = false; + emsg_silent = false; + emsg_on_display = false; + set_vim_var_string(VV_ERRMSG, NULL, 0); + return ret; } -static int assert_match_common(typval_T *argvars, assert_type_T atype) +int assert_match_common(typval_T *argvars, assert_type_T atype) FUNC_ATTR_NONNULL_ALL { char buf1[NUMBUFLEN]; @@ -7397,1523 +5854,6 @@ static int assert_match_common(typval_T *argvars, assert_type_T atype) return 0; } -/// "assert_inrange(lower, upper[, msg])" function -static void f_assert_inrange(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_inrange(argvars); -} - -/// "assert_match(pattern, actual[, msg])" function -static void f_assert_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH); -} - -/// "assert_notmatch(pattern, actual[, msg])" function -static void f_assert_notmatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH); -} - -// "assert_true(actual[, msg])" function -static void f_assert_true(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_bool(argvars, true); -} - -/* - * "atan2()" function - */ -static void f_atan2(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - float_T fx; - float_T fy; - - rettv->v_type = VAR_FLOAT; - if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) { - rettv->vval.v_float = atan2(fx, fy); - } else { - rettv->vval.v_float = 0.0; - } -} - -/* - * "browse(save, title, initdir, default)" function - */ -static void f_browse(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_string = NULL; - rettv->v_type = VAR_STRING; -} - -/* - * "browsedir(title, initdir)" function - */ -static void f_browsedir(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - f_browse(argvars, rettv, NULL); -} - - -/* - * Find a buffer by number or exact name. - */ -static buf_T *find_buffer(typval_T *avar) -{ - buf_T *buf = NULL; - - if (avar->v_type == VAR_NUMBER) - buf = buflist_findnr((int)avar->vval.v_number); - else if (avar->v_type == VAR_STRING && avar->vval.v_string != NULL) { - buf = buflist_findname_exp(avar->vval.v_string); - if (buf == NULL) { - /* No full path name match, try a match with a URL or a "nofile" - * buffer, these don't use the full path. */ - FOR_ALL_BUFFERS(bp) { - if (bp->b_fname != NULL - && (path_with_url((char *)bp->b_fname) - || bt_nofile(bp) - ) - && STRCMP(bp->b_fname, avar->vval.v_string) == 0) { - buf = bp; - break; - } - } - } - } - return buf; -} - -// "bufadd(expr)" function -static void f_bufadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *name = (char_u *)tv_get_string(&argvars[0]); - - rettv->vval.v_number = buflist_add(*name == NUL ? NULL : name, 0); -} - -/* - * "bufexists(expr)" function - */ -static void f_bufexists(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = (find_buffer(&argvars[0]) != NULL); -} - -/* - * "buflisted(expr)" function - */ -static void f_buflisted(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *buf; - - buf = find_buffer(&argvars[0]); - rettv->vval.v_number = (buf != NULL && buf->b_p_bl); -} - -// "bufload(expr)" function -static void f_bufload(typval_T *argvars, typval_T *unused, FunPtr fptr) -{ - buf_T *buf = get_buf_arg(&argvars[0]); - - if (buf != NULL && buf->b_ml.ml_mfp == NULL) { - aco_save_T aco; - - aucmd_prepbuf(&aco, buf); - swap_exists_action = SEA_NONE; - open_buffer(false, NULL, 0); - aucmd_restbuf(&aco); - } -} - -/* - * "bufloaded(expr)" function - */ -static void f_bufloaded(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *buf; - - buf = find_buffer(&argvars[0]); - rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL); -} - - -/* - * Get buffer by number or pattern. - */ -static buf_T *tv_get_buf(typval_T *tv, int curtab_only) -{ - char_u *name = tv->vval.v_string; - int save_magic; - char_u *save_cpo; - buf_T *buf; - - if (tv->v_type == VAR_NUMBER) - return buflist_findnr((int)tv->vval.v_number); - if (tv->v_type != VAR_STRING) - return NULL; - if (name == NULL || *name == NUL) - return curbuf; - if (name[0] == '$' && name[1] == NUL) - return lastbuf; - - // Ignore 'magic' and 'cpoptions' here to make scripts portable - save_magic = p_magic; - p_magic = TRUE; - save_cpo = p_cpo; - p_cpo = (char_u *)""; - - buf = buflist_findnr(buflist_findpat(name, name + STRLEN(name), - TRUE, FALSE, curtab_only)); - - p_magic = save_magic; - p_cpo = save_cpo; - - // If not found, try expanding the name, like done for bufexists(). - if (buf == NULL) { - buf = find_buffer(tv); - } - - return buf; -} - -/// Get the buffer from "arg" and give an error and return NULL if it is not -/// valid. -static buf_T * get_buf_arg(typval_T *arg) -{ - buf_T *buf; - - emsg_off++; - buf = tv_get_buf(arg, false); - emsg_off--; - if (buf == NULL) { - EMSG2(_("E158: Invalid buffer name: %s"), tv_get_string(arg)); - } - return buf; -} - -/* - * "bufname(expr)" function - */ -static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const buf_T *buf; - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (argvars[0].v_type == VAR_UNKNOWN) { - buf = curbuf; - } else { - if (!tv_check_str_or_nr(&argvars[0])) { - return; - } - emsg_off++; - buf = tv_get_buf(&argvars[0], false); - emsg_off--; - } - if (buf != NULL && buf->b_fname != NULL) { - rettv->vval.v_string = (char_u *)xstrdup((char *)buf->b_fname); - } -} - -/* - * "bufnr(expr)" function - */ -static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const buf_T *buf; - bool error = false; - - rettv->vval.v_number = -1; - - if (argvars[0].v_type == VAR_UNKNOWN) { - buf = curbuf; - } else { - if (!tv_check_str_or_nr(&argvars[0])) { - return; - } - emsg_off++; - buf = tv_get_buf(&argvars[0], false); - emsg_off--; - } - - // If the buffer isn't found and the second argument is not zero create a - // new buffer. - const char *name; - if (buf == NULL - && argvars[1].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[1], &error) != 0 - && !error - && (name = tv_get_string_chk(&argvars[0])) != NULL) { - buf = buflist_new((char_u *)name, NULL, 1, 0); - } - - if (buf != NULL) { - rettv->vval.v_number = buf->b_fnum; - } -} - -static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr) -{ - if (!tv_check_str_or_nr(&argvars[0])) { - rettv->vval.v_number = -1; - return; - } - - emsg_off++; - buf_T *buf = tv_get_buf(&argvars[0], true); - if (buf == NULL) { // no need to search if buffer was not found - rettv->vval.v_number = -1; - goto end; - } - - int winnr = 0; - int winid; - bool found_buf = false; - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - winnr++; - if (wp->w_buffer == buf) { - found_buf = true; - winid = wp->handle; - break; - } - } - rettv->vval.v_number = (found_buf ? (get_nr ? winnr : winid) : -1); -end: - emsg_off--; -} - -/// "bufwinid(nr)" function -static void f_bufwinid(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - buf_win_common(argvars, rettv, false); -} - -/// "bufwinnr(nr)" function -static void f_bufwinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_win_common(argvars, rettv, true); -} - -/* - * "byte2line(byte)" function - */ -static void f_byte2line(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - long boff = tv_get_number(&argvars[0]) - 1; - if (boff < 0) { - rettv->vval.v_number = -1; - } else { - rettv->vval.v_number = (varnumber_T)ml_find_line_or_offset(curbuf, 0, - &boff, false); - } -} - -static void byteidx(typval_T *argvars, typval_T *rettv, int comp) -{ - const char *const str = tv_get_string_chk(&argvars[0]); - varnumber_T idx = tv_get_number_chk(&argvars[1], NULL); - rettv->vval.v_number = -1; - if (str == NULL || idx < 0) { - return; - } - - const char *t = str; - for (; idx > 0; idx--) { - if (*t == NUL) { // EOL reached. - return; - } - if (enc_utf8 && comp) { - t += utf_ptr2len((const char_u *)t); - } else { - t += (*mb_ptr2len)((const char_u *)t); - } - } - rettv->vval.v_number = (varnumber_T)(t - str); -} - -/* - * "byteidx()" function - */ -static void f_byteidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - byteidx(argvars, rettv, FALSE); -} - -/* - * "byteidxcomp()" function - */ -static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - byteidx(argvars, rettv, TRUE); -} - -int func_call(char_u *name, typval_T *args, partial_T *partial, - dict_T *selfdict, typval_T *rettv) -{ - typval_T argv[MAX_FUNC_ARGS + 1]; - int argc = 0; - int dummy; - int r = 0; - - TV_LIST_ITER(args->vval.v_list, item, { - if (argc == MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { - EMSG(_("E699: Too many arguments")); - goto func_call_skip_call; - } - // Make a copy of each argument. This is needed to be able to set - // v_lock to VAR_FIXED in the copy without changing the original list. - tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]); - }); - - r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &dummy, true, partial, selfdict); - -func_call_skip_call: - // Free the arguments. - while (argc > 0) { - tv_clear(&argv[--argc]); - } - - return r; -} - -/// "call(func, arglist [, dict])" function -static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[1].v_type != VAR_LIST) { - EMSG(_(e_listreq)); - return; - } - if (argvars[1].vval.v_list == NULL) { - return; - } - - char_u *func; - partial_T *partial = NULL; - dict_T *selfdict = NULL; - if (argvars[0].v_type == VAR_FUNC) { - func = argvars[0].vval.v_string; - } else if (argvars[0].v_type == VAR_PARTIAL) { - partial = argvars[0].vval.v_partial; - func = partial_name(partial); - } else { - func = (char_u *)tv_get_string(&argvars[0]); - } - if (*func == NUL) { - return; // type error or empty name - } - - if (argvars[2].v_type != VAR_UNKNOWN) { - if (argvars[2].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - selfdict = argvars[2].vval.v_dict; - } - - func_call(func, &argvars[1], partial, selfdict, rettv); -} - -/* - * "changenr()" function - */ -static void f_changenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = curbuf->b_u_seq_cur; -} - -// "chanclose(id[, stream])" function -static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_NUMBER || (argvars[1].v_type != VAR_STRING - && argvars[1].v_type != VAR_UNKNOWN)) { - EMSG(_(e_invarg)); - return; - } - - ChannelPart part = kChannelPartAll; - if (argvars[1].v_type == VAR_STRING) { - char *stream = (char *)argvars[1].vval.v_string; - if (!strcmp(stream, "stdin")) { - part = kChannelPartStdin; - } else if (!strcmp(stream, "stdout")) { - part = kChannelPartStdout; - } else if (!strcmp(stream, "stderr")) { - part = kChannelPartStderr; - } else if (!strcmp(stream, "rpc")) { - part = kChannelPartRpc; - } else { - EMSG2(_("Invalid channel stream \"%s\""), stream); - return; - } - } - const char *error; - rettv->vval.v_number = channel_close(argvars[0].vval.v_number, part, &error); - if (!rettv->vval.v_number) { - EMSG(error); - } -} - -// "chansend(id, data)" function -static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type == VAR_UNKNOWN) { - // First argument is the channel id and second is the data to write - EMSG(_(e_invarg)); - return; - } - - ptrdiff_t input_len = 0; - char *input = save_tv_as_string(&argvars[1], &input_len, false); - if (!input) { - // Either the error has been handled by save_tv_as_string(), - // or there is no input to send. - return; - } - uint64_t id = argvars[0].vval.v_number; - const char *error = NULL; - rettv->vval.v_number = channel_send(id, input, input_len, &error); - if (error) { - EMSG(error); - } -} - -/* - * "char2nr(string)" function - */ -static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[1].v_type != VAR_UNKNOWN) { - if (!tv_check_num(&argvars[1])) { - return; - } - } - - rettv->vval.v_number = utf_ptr2char( - (const char_u *)tv_get_string(&argvars[0])); -} - -/* - * "cindent(lnum)" function - */ -static void f_cindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - pos_T pos; - linenr_T lnum; - - pos = curwin->w_cursor; - lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - curwin->w_cursor.lnum = lnum; - rettv->vval.v_number = get_c_indent(); - curwin->w_cursor = pos; - } else - rettv->vval.v_number = -1; -} - -/* - * "clearmatches()" function - */ -static void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - clear_matches(curwin); -} - -/* - * "col(string)" function - */ -static void f_col(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - colnr_T col = 0; - pos_T *fp; - int fnum = curbuf->b_fnum; - - fp = var2fpos(&argvars[0], FALSE, &fnum); - if (fp != NULL && fnum == curbuf->b_fnum) { - if (fp->col == MAXCOL) { - /* '> can be MAXCOL, get the length of the line then */ - if (fp->lnum <= curbuf->b_ml.ml_line_count) - col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1; - else - col = MAXCOL; - } else { - col = fp->col + 1; - /* col(".") when the cursor is on the NUL at the end of the line - * because of "coladd" can be seen as an extra column. */ - if (virtual_active() && fp == &curwin->w_cursor) { - char_u *p = get_cursor_pos_ptr(); - - if (curwin->w_cursor.coladd >= (colnr_T)chartabsize(p, - curwin->w_virtcol - curwin->w_cursor.coladd)) { - int l; - - if (*p != NUL && p[(l = (*mb_ptr2len)(p))] == NUL) - col += l; - } - } - } - } - rettv->vval.v_number = col; -} - -/* - * "complete()" function - */ -static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if ((State & INSERT) == 0) { - EMSG(_("E785: complete() can only be used in Insert mode")); - return; - } - - /* Check for undo allowed here, because if something was already inserted - * the line was already saved for undo and this check isn't done. */ - if (!undo_allowed()) - return; - - if (argvars[1].v_type != VAR_LIST) { - EMSG(_(e_invarg)); - return; - } - - const colnr_T startcol = tv_get_number_chk(&argvars[0], NULL); - if (startcol <= 0) { - return; - } - - set_completion(startcol - 1, argvars[1].vval.v_list); -} - -/* - * "complete_add()" function - */ -static void f_complete_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0); -} - -/* - * "complete_check()" function - */ -static void f_complete_check(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int saved = RedrawingDisabled; - - RedrawingDisabled = 0; - ins_compl_check_keys(0, true); - rettv->vval.v_number = compl_interrupted; - RedrawingDisabled = saved; -} - -// "complete_info()" function -static void f_complete_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_dict_alloc_ret(rettv); - - list_T *what_list = NULL; - - if (argvars[0].v_type != VAR_UNKNOWN) { - if (argvars[0].v_type != VAR_LIST) { - EMSG(_(e_listreq)); - return; - } - what_list = argvars[0].vval.v_list; - } - get_complete_info(what_list, rettv->vval.v_dict); -} - -/* - * "confirm(message, buttons[, default [, type]])" function - */ -static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char buf[NUMBUFLEN]; - char buf2[NUMBUFLEN]; - const char *message; - const char *buttons = NULL; - int def = 1; - int type = VIM_GENERIC; - const char *typestr; - bool error = false; - - message = tv_get_string_chk(&argvars[0]); - if (message == NULL) { - error = true; - } - if (argvars[1].v_type != VAR_UNKNOWN) { - buttons = tv_get_string_buf_chk(&argvars[1], buf); - if (buttons == NULL) { - error = true; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - def = tv_get_number_chk(&argvars[2], &error); - if (argvars[3].v_type != VAR_UNKNOWN) { - typestr = tv_get_string_buf_chk(&argvars[3], buf2); - if (typestr == NULL) { - error = true; - } else { - switch (TOUPPER_ASC(*typestr)) { - case 'E': type = VIM_ERROR; break; - case 'Q': type = VIM_QUESTION; break; - case 'I': type = VIM_INFO; break; - case 'W': type = VIM_WARNING; break; - case 'G': type = VIM_GENERIC; break; - } - } - } - } - } - - if (buttons == NULL || *buttons == NUL) { - buttons = _("&Ok"); - } - - if (!error) { - rettv->vval.v_number = do_dialog( - type, NULL, (char_u *)message, (char_u *)buttons, def, NULL, false); - } -} - -/* - * "copy()" function - */ -static void f_copy(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - var_item_copy(NULL, &argvars[0], rettv, false, 0); -} - -/* - * "count()" function - */ -static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - long n = 0; - int ic = 0; - bool error = false; - - if (argvars[2].v_type != VAR_UNKNOWN) { - ic = tv_get_number_chk(&argvars[2], &error); - } - - if (argvars[0].v_type == VAR_STRING) { - const char_u *expr = (char_u *)tv_get_string_chk(&argvars[1]); - const char_u *p = argvars[0].vval.v_string; - - if (!error && expr != NULL && *expr != NUL && p != NULL) { - if (ic) { - const size_t len = STRLEN(expr); - - while (*p != NUL) { - if (mb_strnicmp(p, expr, len) == 0) { - n++; - p += len; - } else { - MB_PTR_ADV(p); - } - } - } else { - char_u *next; - while ((next = (char_u *)strstr((char *)p, (char *)expr)) != NULL) { - n++; - p = next + STRLEN(expr); - } - } - } - } else if (argvars[0].v_type == VAR_LIST) { - listitem_T *li; - list_T *l; - long idx; - - if ((l = argvars[0].vval.v_list) != NULL) { - li = tv_list_first(l); - if (argvars[2].v_type != VAR_UNKNOWN) { - if (argvars[3].v_type != VAR_UNKNOWN) { - idx = tv_get_number_chk(&argvars[3], &error); - if (!error) { - li = tv_list_find(l, idx); - if (li == NULL) { - EMSGN(_(e_listidx), idx); - } - } - } - if (error) - li = NULL; - } - - for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { - if (tv_equal(TV_LIST_ITEM_TV(li), &argvars[1], ic, false)) { - n++; - } - } - } - } else if (argvars[0].v_type == VAR_DICT) { - int todo; - dict_T *d; - hashitem_T *hi; - - if ((d = argvars[0].vval.v_dict) != NULL) { - if (argvars[2].v_type != VAR_UNKNOWN) { - if (argvars[3].v_type != VAR_UNKNOWN) { - EMSG(_(e_invarg)); - } - } - - todo = error ? 0 : (int)d->dv_hashtab.ht_used; - for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - todo--; - if (tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &argvars[1], ic, false)) { - n++; - } - } - } - } - } else { - EMSG2(_(e_listdictarg), "count()"); - } - rettv->vval.v_number = n; -} - -/* - * "cscope_connection([{num} , {dbpath} [, {prepend}]])" function - * - * Checks the existence of a cscope connection. - */ -static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int num = 0; - const char *dbpath = NULL; - const char *prepend = NULL; - char buf[NUMBUFLEN]; - - if (argvars[0].v_type != VAR_UNKNOWN - && argvars[1].v_type != VAR_UNKNOWN) { - num = (int)tv_get_number(&argvars[0]); - dbpath = tv_get_string(&argvars[1]); - if (argvars[2].v_type != VAR_UNKNOWN) { - prepend = tv_get_string_buf(&argvars[2], buf); - } - } - - rettv->vval.v_number = cs_connection(num, (char_u *)dbpath, - (char_u *)prepend); -} - -/// "ctxget([{index}])" function -static void f_ctxget(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - size_t index = 0; - if (argvars[0].v_type == VAR_NUMBER) { - index = argvars[0].vval.v_number; - } else if (argvars[0].v_type != VAR_UNKNOWN) { - EMSG2(_(e_invarg2), "expected nothing or a Number as an argument"); - return; - } - - Context *ctx = ctx_get(index); - if (ctx == NULL) { - EMSG3(_(e_invargNval), "index", "out of bounds"); - return; - } - - Dictionary ctx_dict = ctx_to_dict(ctx); - Error err = ERROR_INIT; - object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err); - api_free_dictionary(ctx_dict); - api_clear_error(&err); -} - -/// "ctxpop()" function -static void f_ctxpop(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (!ctx_restore(NULL, kCtxAll)) { - EMSG(_("Context stack is empty")); - } -} - -/// "ctxpush([{types}])" function -static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int types = kCtxAll; - if (argvars[0].v_type == VAR_LIST) { - types = 0; - TV_LIST_ITER(argvars[0].vval.v_list, li, { - typval_T *tv_li = TV_LIST_ITEM_TV(li); - if (tv_li->v_type == VAR_STRING) { - if (strequal((char *)tv_li->vval.v_string, "regs")) { - types |= kCtxRegs; - } else if (strequal((char *)tv_li->vval.v_string, "jumps")) { - types |= kCtxJumps; - } else if (strequal((char *)tv_li->vval.v_string, "bufs")) { - types |= kCtxBufs; - } else if (strequal((char *)tv_li->vval.v_string, "gvars")) { - types |= kCtxGVars; - } else if (strequal((char *)tv_li->vval.v_string, "sfuncs")) { - types |= kCtxSFuncs; - } else if (strequal((char *)tv_li->vval.v_string, "funcs")) { - types |= kCtxFuncs; - } - } - }); - } else if (argvars[0].v_type != VAR_UNKNOWN) { - EMSG2(_(e_invarg2), "expected nothing or a List as an argument"); - return; - } - ctx_save(NULL, types); -} - -/// "ctxset({context}[, {index}])" function -static void f_ctxset(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type != VAR_DICT) { - EMSG2(_(e_invarg2), "expected dictionary as first argument"); - return; - } - - size_t index = 0; - if (argvars[1].v_type == VAR_NUMBER) { - index = argvars[1].vval.v_number; - } else if (argvars[1].v_type != VAR_UNKNOWN) { - EMSG2(_(e_invarg2), "expected nothing or a Number as second argument"); - return; - } - - Context *ctx = ctx_get(index); - if (ctx == NULL) { - EMSG3(_(e_invargNval), "index", "out of bounds"); - return; - } - - int save_did_emsg = did_emsg; - did_emsg = false; - - Dictionary dict = vim_to_object(&argvars[0]).data.dictionary; - Context tmp = CONTEXT_INIT; - ctx_from_dict(dict, &tmp); - - if (did_emsg) { - ctx_free(&tmp); - } else { - ctx_free(ctx); - *ctx = tmp; - } - - api_free_dictionary(dict); - did_emsg = save_did_emsg; -} - -/// "ctxsize()" function -static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = ctx_size(); -} - -/// "cursor(lnum, col)" function, or -/// "cursor(list)" -/// -/// Moves the cursor to the specified line and column. -/// -/// @returns 0 when the position could be set, -1 otherwise. -static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - long line, col; - long coladd = 0; - bool set_curswant = true; - - rettv->vval.v_number = -1; - if (argvars[1].v_type == VAR_UNKNOWN) { - pos_T pos; - colnr_T curswant = -1; - - if (list2fpos(argvars, &pos, NULL, &curswant) == FAIL) { - EMSG(_(e_invarg)); - return; - } - - line = pos.lnum; - col = pos.col; - coladd = pos.coladd; - if (curswant >= 0) { - curwin->w_curswant = curswant - 1; - set_curswant = false; - } - } else { - line = tv_get_lnum(argvars); - col = (long)tv_get_number_chk(&argvars[1], NULL); - if (argvars[2].v_type != VAR_UNKNOWN) { - coladd = (long)tv_get_number_chk(&argvars[2], NULL); - } - } - if (line < 0 || col < 0 - || coladd < 0) { - return; // type error; errmsg already given - } - if (line > 0) { - curwin->w_cursor.lnum = line; - } - if (col > 0) { - curwin->w_cursor.col = col - 1; - } - curwin->w_cursor.coladd = coladd; - - // Make sure the cursor is in a valid position. - check_cursor(); - // Correct cursor for multi-byte character. - if (has_mbyte) { - mb_adjust_cursor(); - } - - curwin->w_set_curswant = set_curswant; - rettv->vval.v_number = 0; -} - -/* - * "deepcopy()" function - */ -static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int noref = 0; - - if (argvars[1].v_type != VAR_UNKNOWN) { - noref = tv_get_number_chk(&argvars[1], NULL); - } - if (noref < 0 || noref > 1) { - EMSG(_(e_invarg)); - } else { - var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0 - ? get_copyID() - : 0)); - } -} - -// "delete()" function -static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; - if (check_restricted() || check_secure()) { - return; - } - - const char *const name = tv_get_string(&argvars[0]); - if (*name == NUL) { - EMSG(_(e_invarg)); - return; - } - - char nbuf[NUMBUFLEN]; - const char *flags; - if (argvars[1].v_type != VAR_UNKNOWN) { - flags = tv_get_string_buf(&argvars[1], nbuf); - } else { - flags = ""; - } - - if (*flags == NUL) { - // delete a file - rettv->vval.v_number = os_remove(name) == 0 ? 0 : -1; - } else if (strcmp(flags, "d") == 0) { - // delete an empty directory - rettv->vval.v_number = os_rmdir(name) == 0 ? 0 : -1; - } else if (strcmp(flags, "rf") == 0) { - // delete a directory recursively - rettv->vval.v_number = delete_recursive(name); - } else { - emsgf(_(e_invexpr2), flags); - } -} - -// dictwatcheradd(dict, key, funcref) function -static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_DICT) { - emsgf(_(e_invarg2), "dict"); - return; - } else if (argvars[0].vval.v_dict == NULL) { - const char *const arg_errmsg = _("dictwatcheradd() argument"); - const size_t arg_errmsg_len = strlen(arg_errmsg); - emsgf(_(e_readonlyvar), (int)arg_errmsg_len, arg_errmsg); - return; - } - - if (argvars[1].v_type != VAR_STRING && argvars[1].v_type != VAR_NUMBER) { - emsgf(_(e_invarg2), "key"); - return; - } - - const char *const key_pattern = tv_get_string_chk(argvars + 1); - if (key_pattern == NULL) { - return; - } - const size_t key_pattern_len = strlen(key_pattern); - - Callback callback; - if (!callback_from_typval(&callback, &argvars[2])) { - emsgf(_(e_invarg2), "funcref"); - return; - } - - tv_dict_watcher_add(argvars[0].vval.v_dict, key_pattern, key_pattern_len, - callback); -} - -// dictwatcherdel(dict, key, funcref) function -static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_DICT) { - emsgf(_(e_invarg2), "dict"); - return; - } - - if (argvars[2].v_type != VAR_FUNC && argvars[2].v_type != VAR_STRING) { - emsgf(_(e_invarg2), "funcref"); - return; - } - - const char *const key_pattern = tv_get_string_chk(argvars + 1); - if (key_pattern == NULL) { - return; - } - - Callback callback; - if (!callback_from_typval(&callback, &argvars[2])) { - return; - } - - if (!tv_dict_watcher_remove(argvars[0].vval.v_dict, key_pattern, - strlen(key_pattern), callback)) { - EMSG("Couldn't find a watcher matching key and callback"); - } - - callback_free(&callback); -} - -/// "deletebufline()" function -static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T last; - buf_T *curbuf_save = NULL; - win_T *curwin_save = NULL; - - buf_T *const buf = tv_get_buf(&argvars[0], false); - if (buf == NULL) { - rettv->vval.v_number = 1; // FAIL - return; - } - const bool is_curbuf = buf == curbuf; - - const linenr_T first = tv_get_lnum_buf(&argvars[1], buf); - if (argvars[2].v_type != VAR_UNKNOWN) { - last = tv_get_lnum_buf(&argvars[2], buf); - } else { - last = first; - } - - if (buf->b_ml.ml_mfp == NULL || first < 1 - || first > buf->b_ml.ml_line_count || last < first) { - rettv->vval.v_number = 1; // FAIL - return; - } - - if (!is_curbuf) { - curbuf_save = curbuf; - curwin_save = curwin; - curbuf = buf; - find_win_for_curbuf(); - } - if (last > curbuf->b_ml.ml_line_count) { - last = curbuf->b_ml.ml_line_count; - } - const long count = last - first + 1; - - // When coming here from Insert mode, sync undo, so that this can be - // undone separately from what was previously inserted. - if (u_sync_once == 2) { - u_sync_once = 1; // notify that u_sync() was called - u_sync(true); - } - - if (u_save(first - 1, last + 1) == FAIL) { - rettv->vval.v_number = 1; // FAIL - return; - } - - for (linenr_T lnum = first; lnum <= last; lnum++) { - ml_delete(first, true); - } - - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer == buf) { - if (wp->w_cursor.lnum > last) { - wp->w_cursor.lnum -= count; - } else if (wp->w_cursor.lnum> first) { - wp->w_cursor.lnum = first; - } - if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) { - wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count; - } - } - } - check_cursor_col(); - deleted_lines_mark(first, count); - - if (!is_curbuf) { - curbuf = curbuf_save; - curwin = curwin_save; - } -} - -/* - * "did_filetype()" function - */ -static void f_did_filetype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = did_filetype; -} - -/* - * "diff_filler()" function - */ -static void f_diff_filler(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = diff_check_fill(curwin, tv_get_lnum(argvars)); -} - -/* - * "diff_hlID()" function - */ -static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T lnum = tv_get_lnum(argvars); - static linenr_T prev_lnum = 0; - static int changedtick = 0; - static int fnum = 0; - static int change_start = 0; - static int change_end = 0; - static hlf_T hlID = (hlf_T)0; - int filler_lines; - int col; - - if (lnum < 0) /* ignore type error in {lnum} arg */ - lnum = 0; - if (lnum != prev_lnum - || changedtick != buf_get_changedtick(curbuf) - || fnum != curbuf->b_fnum) { - /* New line, buffer, change: need to get the values. */ - filler_lines = diff_check(curwin, lnum); - if (filler_lines < 0) { - if (filler_lines == -1) { - change_start = MAXCOL; - change_end = -1; - if (diff_find_change(curwin, lnum, &change_start, &change_end)) - hlID = HLF_ADD; /* added line */ - else - hlID = HLF_CHD; /* changed line */ - } else - hlID = HLF_ADD; /* added line */ - } else - hlID = (hlf_T)0; - prev_lnum = lnum; - changedtick = buf_get_changedtick(curbuf); - fnum = curbuf->b_fnum; - } - - if (hlID == HLF_CHD || hlID == HLF_TXD) { - col = tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}. - if (col >= change_start && col <= change_end) { - hlID = HLF_TXD; // Changed text. - } else { - hlID = HLF_CHD; // Changed line. - } - } - rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (int)(hlID + 1); -} - -/* - * "empty({expr})" function - */ -static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - bool n = true; - - switch (argvars[0].v_type) { - case VAR_STRING: - case VAR_FUNC: { - n = argvars[0].vval.v_string == NULL - || *argvars[0].vval.v_string == NUL; - break; - } - case VAR_PARTIAL: { - n = false; - break; - } - case VAR_NUMBER: { - n = argvars[0].vval.v_number == 0; - break; - } - case VAR_FLOAT: { - n = argvars[0].vval.v_float == 0.0; - break; - } - case VAR_LIST: { - n = (tv_list_len(argvars[0].vval.v_list) == 0); - break; - } - case VAR_DICT: { - n = (tv_dict_len(argvars[0].vval.v_dict) == 0); - break; - } - case VAR_SPECIAL: { - // Using switch to get warning if SpecialVarValue receives more values. - switch (argvars[0].vval.v_special) { - case kSpecialVarTrue: { - n = false; - break; - } - case kSpecialVarFalse: - case kSpecialVarNull: { - n = true; - break; - } - } - break; - } - case VAR_UNKNOWN: { - internal_error("f_empty(UNKNOWN)"); - break; - } - } - - rettv->vval.v_number = n; -} - -/// "environ()" function -static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_dict_alloc_ret(rettv); - - size_t env_size = os_get_fullenv_size(); - char **env = xmalloc(sizeof(*env) * (env_size + 1)); - env[env_size] = NULL; - - os_copy_fullenv(env, env_size); - - for (size_t i = 0; i < env_size; i++) { - const char * str = env[i]; - const char * const end = strchr(str + (str[0] == '=' ? 1 : 0), - '='); - assert(end != NULL); - ptrdiff_t len = end - str; - assert(len > 0); - const char * value = str + len + 1; - tv_dict_add_str(rettv->vval.v_dict, - str, len, - value); - } - os_free_fullenv(env); -} - -/* - * "escape({string}, {chars})" function - */ -static void f_escape(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char buf[NUMBUFLEN]; - - rettv->vval.v_string = vim_strsave_escaped( - (const char_u *)tv_get_string(&argvars[0]), - (const char_u *)tv_get_string_buf(&argvars[1], buf)); - rettv->v_type = VAR_STRING; -} - -/// "getenv()" function -static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *p = (char_u *)vim_getenv(tv_get_string(&argvars[0])); - - if (p == NULL) { - rettv->v_type = VAR_SPECIAL; - rettv->vval.v_special = kSpecialVarNull; - return; - } - rettv->vval.v_string = p; - rettv->v_type = VAR_STRING; -} - -/* - * "eval()" function - */ -static void f_eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *s = tv_get_string_chk(&argvars[0]); - if (s != NULL) { - s = (const char *)skipwhite((const char_u *)s); - } - - const char *const expr_start = s; - if (s == NULL || eval1((char_u **)&s, rettv, true) == FAIL) { - if (expr_start != NULL && !aborting()) { - EMSG2(_(e_invexpr2), expr_start); - } - need_clr_eos = FALSE; - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - } else if (*s != NUL) { - EMSG(_(e_trailing)); - } -} - -/* - * "eventhandler()" function - */ -static void f_eventhandler(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = vgetc_busy; -} - -/* - * "executable()" function - */ -static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *name = tv_get_string(&argvars[0]); - - // Check in $PATH and also check directly if there is a directory name - rettv->vval.v_number = os_can_exe(name, NULL, true); -} - -typedef struct { - const list_T *const l; - const listitem_T *li; -} GetListLineCookie; - -static char_u *get_list_line(int c, void *cookie, int indent, bool do_concat) -{ - GetListLineCookie *const p = (GetListLineCookie *)cookie; - - const listitem_T *const item = p->li; - if (item == NULL) { - return NULL; - } - char buf[NUMBUFLEN]; - const char *const s = tv_get_string_buf_chk(TV_LIST_ITEM_TV(item), buf); - p->li = TV_LIST_ITEM_NEXT(p->l, item); - return (char_u *)(s == NULL ? NULL : xstrdup(s)); -} - -// "execute(command)" function -static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const int save_msg_silent = msg_silent; - const int save_emsg_silent = emsg_silent; - const bool save_emsg_noredir = emsg_noredir; - const bool save_redir_off = redir_off; - garray_T *const save_capture_ga = capture_ga; - const int save_msg_col = msg_col; - bool echo_output = false; - - if (check_secure()) { - return; - } - - if (argvars[1].v_type != VAR_UNKNOWN) { - char buf[NUMBUFLEN]; - const char *const s = tv_get_string_buf_chk(&argvars[1], buf); - - if (s == NULL) { - return; - } - if (*s == NUL) { - echo_output = true; - } - if (strncmp(s, "silent", 6) == 0) { - msg_silent++; - } - if (strcmp(s, "silent!") == 0) { - emsg_silent = true; - emsg_noredir = true; - } - } else { - msg_silent++; - } - - garray_T capture_local; - ga_init(&capture_local, (int)sizeof(char), 80); - capture_ga = &capture_local; - redir_off = false; - if (!echo_output) { - msg_col = 0; // prevent leading spaces - } - - if (argvars[0].v_type != VAR_LIST) { - do_cmdline_cmd(tv_get_string(&argvars[0])); - } else if (argvars[0].vval.v_list != NULL) { - list_T *const list = argvars[0].vval.v_list; - tv_list_ref(list); - GetListLineCookie cookie = { - .l = list, - .li = tv_list_first(list), - }; - do_cmdline(NULL, get_list_line, (void *)&cookie, - DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT|DOCMD_KEYTYPED); - tv_list_unref(list); - } - msg_silent = save_msg_silent; - emsg_silent = save_emsg_silent; - emsg_noredir = save_emsg_noredir; - redir_off = save_redir_off; - // "silent reg" or "silent echo x" leaves msg_col somewhere in the line. - if (echo_output) { - // When not working silently: put it in column zero. A following - // "echon" will overwrite the message, unavoidably. - msg_col = 0; - } else { - // When working silently: Put it back where it was, since nothing - // should have been written. - msg_col = save_msg_col; - } - - ga_append(capture_ga, NUL); - rettv->v_type = VAR_STRING; - rettv->vval.v_string = capture_ga->ga_data; - - capture_ga = save_capture_ga; -} - -/// "exepath()" function -static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *arg = tv_get_string(&argvars[0]); - char *path = NULL; - - (void)os_can_exe(arg, &path, true); - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)path; -} - /// Find a window: When using a Window ID in any tab page, when using a number /// in the current tab page. win_T * find_win_by_nr_or_id(typval_T *vp) @@ -8928,329 +5868,9 @@ win_T * find_win_by_nr_or_id(typval_T *vp) } /* - * "exists()" function - */ -static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int n = false; - int len = 0; - - const char *p = tv_get_string(&argvars[0]); - if (*p == '$') { // Environment variable. - // First try "normal" environment variables (fast). - if (os_env_exists(p + 1)) { - n = true; - } else { - // Try expanding things like $VIM and ${HOME}. - char_u *const exp = expand_env_save((char_u *)p); - if (exp != NULL && *exp != '$') { - n = true; - } - xfree(exp); - } - } else if (*p == '&' || *p == '+') { // Option. - n = (get_option_tv(&p, NULL, true) == OK); - if (*skipwhite((const char_u *)p) != NUL) { - n = false; // Trailing garbage. - } - } else if (*p == '*') { // Internal or user defined function. - n = function_exists(p + 1, false); - } else if (*p == ':') { - n = cmd_exists(p + 1); - } else if (*p == '#') { - if (p[1] == '#') { - n = autocmd_supported(p + 2); - } else { - n = au_exists(p + 1); - } - } else { // Internal variable. - typval_T tv; - - // get_name_len() takes care of expanding curly braces - const char *name = p; - char *tofree; - len = get_name_len((const char **)&p, &tofree, true, false); - if (len > 0) { - if (tofree != NULL) { - name = tofree; - } - n = (get_var_tv(name, len, &tv, NULL, false, true) == OK); - if (n) { - // Handle d.key, l[idx], f(expr). - n = (handle_subscript(&p, &tv, true, false) == OK); - if (n) { - tv_clear(&tv); - } - } - } - if (*p != NUL) - n = FALSE; - - xfree(tofree); - } - - rettv->vval.v_number = n; -} - -/* - * "expand()" function - */ -static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - size_t len; - char_u *errormsg; - int options = WILD_SILENT|WILD_USE_NL|WILD_LIST_NOTFOUND; - expand_T xpc; - bool error = false; - char_u *result; - - rettv->v_type = VAR_STRING; - if (argvars[1].v_type != VAR_UNKNOWN - && argvars[2].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[2], &error) - && !error) { - tv_list_set_ret(rettv, NULL); - } - - const char *s = tv_get_string(&argvars[0]); - if (*s == '%' || *s == '#' || *s == '<') { - emsg_off++; - result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL); - emsg_off--; - if (rettv->v_type == VAR_LIST) { - tv_list_alloc_ret(rettv, (result != NULL)); - if (result != NULL) { - tv_list_append_string(rettv->vval.v_list, (const char *)result, -1); - } - } else - rettv->vval.v_string = result; - } else { - /* When the optional second argument is non-zero, don't remove matches - * for 'wildignore' and don't put matches for 'suffixes' at the end. */ - if (argvars[1].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[1], &error)) { - options |= WILD_KEEP_ALL; - } - if (!error) { - ExpandInit(&xpc); - xpc.xp_context = EXPAND_FILES; - if (p_wic) { - options += WILD_ICASE; - } - if (rettv->v_type == VAR_STRING) { - rettv->vval.v_string = ExpandOne(&xpc, (char_u *)s, NULL, options, - WILD_ALL); - } else { - ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL_KEEP); - tv_list_alloc_ret(rettv, xpc.xp_numfiles); - for (int i = 0; i < xpc.xp_numfiles; i++) { - tv_list_append_string(rettv->vval.v_list, - (const char *)xpc.xp_files[i], -1); - } - ExpandCleanup(&xpc); - } - } else { - rettv->vval.v_string = NULL; - } - } -} - - -/// "menu_get(path [, modes])" function -static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, kListLenMayKnow); - int modes = MENU_ALL_MODES; - if (argvars[1].v_type == VAR_STRING) { - const char_u *const strmodes = (char_u *)tv_get_string(&argvars[1]); - modes = get_menu_cmd_modes(strmodes, false, NULL, NULL); - } - menu_get((char_u *)tv_get_string(&argvars[0]), modes, rettv->vval.v_list); -} - -/* - * "extend(list, list [, idx])" function - * "extend(dict, dict [, action])" function - */ -static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const arg_errmsg = N_("extend() argument"); - - if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) { - long before; - bool error = false; - - list_T *const l1 = argvars[0].vval.v_list; - list_T *const l2 = argvars[1].vval.v_list; - if (!tv_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) { - listitem_T *item; - if (argvars[2].v_type != VAR_UNKNOWN) { - before = (long)tv_get_number_chk(&argvars[2], &error); - if (error) { - return; // Type error; errmsg already given. - } - - if (before == tv_list_len(l1)) { - item = NULL; - } else { - item = tv_list_find(l1, before); - if (item == NULL) { - EMSGN(_(e_listidx), before); - return; - } - } - } else { - item = NULL; - } - tv_list_extend(l1, l2, item); - - tv_copy(&argvars[0], rettv); - } - } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == - VAR_DICT) { - dict_T *const d1 = argvars[0].vval.v_dict; - dict_T *const d2 = argvars[1].vval.v_dict; - if (d1 == NULL) { - const bool locked = tv_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE); - (void)locked; - assert(locked == true); - } else if (d2 == NULL) { - // Do nothing - tv_copy(&argvars[0], rettv); - } else if (!tv_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) { - const char *action = "force"; - // Check the third argument. - if (argvars[2].v_type != VAR_UNKNOWN) { - const char *const av[] = { "keep", "force", "error" }; - - action = tv_get_string_chk(&argvars[2]); - if (action == NULL) { - return; // Type error; error message already given. - } - size_t i; - for (i = 0; i < ARRAY_SIZE(av); i++) { - if (strcmp(action, av[i]) == 0) { - break; - } - } - if (i == 3) { - EMSG2(_(e_invarg2), action); - return; - } - } - - tv_dict_extend(d1, d2, action); - - tv_copy(&argvars[0], rettv); - } - } else { - EMSG2(_(e_listdictarg), "extend()"); - } -} - -/* - * "feedkeys()" function - */ -static void f_feedkeys(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // This is not allowed in the sandbox. If the commands would still be - // executed in the sandbox it would be OK, but it probably happens later, - // when "sandbox" is no longer set. - if (check_secure()) { - return; - } - - const char *const keys = tv_get_string(&argvars[0]); - char nbuf[NUMBUFLEN]; - const char *flags = NULL; - if (argvars[1].v_type != VAR_UNKNOWN) { - flags = tv_get_string_buf(&argvars[1], nbuf); - } - - nvim_feedkeys(cstr_as_string((char *)keys), - cstr_as_string((char *)flags), true); -} - -/// "filereadable()" function -static void f_filereadable(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const p = tv_get_string(&argvars[0]); - rettv->vval.v_number = - (*p && !os_isdir((const char_u *)p) && os_file_is_readable(p)); -} - -/* - * Return 0 for not writable, 1 for writable file, 2 for a dir which we have - * rights to write into. - */ -static void f_filewritable(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *filename = tv_get_string(&argvars[0]); - rettv->vval.v_number = os_file_is_writable(filename); -} - - -static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) -{ - char_u *fresult = NULL; - char_u *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; - int count = 1; - bool first = true; - bool error = false; - - rettv->vval.v_string = NULL; - rettv->v_type = VAR_STRING; - - const char *fname = tv_get_string(&argvars[0]); - - char pathbuf[NUMBUFLEN]; - if (argvars[1].v_type != VAR_UNKNOWN) { - const char *p = tv_get_string_buf_chk(&argvars[1], pathbuf); - if (p == NULL) { - error = true; - } else { - if (*p != NUL) { - path = (char_u *)p; - } - - if (argvars[2].v_type != VAR_UNKNOWN) { - count = tv_get_number_chk(&argvars[2], &error); - } - } - } - - if (count < 0) { - tv_list_alloc_ret(rettv, kListLenUnknown); - } - - if (*fname != NUL && !error) { - do { - if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST) - xfree(fresult); - fresult = find_file_in_path_option(first ? (char_u *)fname : NULL, - first ? strlen(fname) : 0, - 0, first, path, - find_what, curbuf->b_ffname, - (find_what == FINDFILE_DIR - ? (char_u *)"" - : curbuf->b_p_sua)); - first = false; - - if (fresult != NULL && rettv->v_type == VAR_LIST) { - tv_list_append_string(rettv->vval.v_list, (const char *)fresult, -1); - } - } while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL); - } - - if (rettv->v_type == VAR_STRING) - rettv->vval.v_string = fresult; -} - - -/* * Implementation of map() and filter(). */ -static void filter_map(typval_T *argvars, typval_T *rettv, int map) +void filter_map(typval_T *argvars, typval_T *rettv, int map) { typval_T *expr; list_T *l = NULL; @@ -9400,248 +6020,8 @@ theend: return retval; } -/* - * "filter()" function - */ -static void f_filter(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - filter_map(argvars, rettv, FALSE); -} - -/* - * "finddir({fname}[, {path}[, {count}]])" function - */ -static void f_finddir(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - findfilendir(argvars, rettv, FINDFILE_DIR); -} - -/* - * "findfile({fname}[, {path}[, {count}]])" function - */ -static void f_findfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - findfilendir(argvars, rettv, FINDFILE_FILE); -} - -/* - * "float2nr({float})" function - */ -static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - float_T f; - - if (tv_get_float_chk(argvars, &f)) { - if (f <= -VARNUMBER_MAX + DBL_EPSILON) { - rettv->vval.v_number = -VARNUMBER_MAX; - } else if (f >= VARNUMBER_MAX - DBL_EPSILON) { - rettv->vval.v_number = VARNUMBER_MAX; - } else { - rettv->vval.v_number = (varnumber_T)f; - } - } -} - -/* - * "fmod()" function - */ -static void f_fmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - float_T fx; - float_T fy; - - rettv->v_type = VAR_FLOAT; - if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) { - rettv->vval.v_float = fmod(fx, fy); - } else { - rettv->vval.v_float = 0.0; - } -} - -/* - * "fnameescape({string})" function - */ -static void f_fnameescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_string = (char_u *)vim_strsave_fnameescape( - tv_get_string(&argvars[0]), false); - rettv->v_type = VAR_STRING; -} - -/* - * "fnamemodify({fname}, {mods})" function - */ -static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *fbuf = NULL; - size_t len; - char buf[NUMBUFLEN]; - const char *fname = tv_get_string_chk(&argvars[0]); - const char *const mods = tv_get_string_buf_chk(&argvars[1], buf); - if (fname == NULL || mods == NULL) { - fname = NULL; - } else { - len = strlen(fname); - size_t usedlen = 0; - (void)modify_fname((char_u *)mods, false, &usedlen, - (char_u **)&fname, &fbuf, &len); - } - - rettv->v_type = VAR_STRING; - if (fname == NULL) { - rettv->vval.v_string = NULL; - } else { - rettv->vval.v_string = (char_u *)xmemdupz(fname, len); - } - xfree(fbuf); -} - - -/* - * "foldclosed()" function - */ -static void foldclosed_both(typval_T *argvars, typval_T *rettv, int end) -{ - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - linenr_T first; - linenr_T last; - if (hasFoldingWin(curwin, lnum, &first, &last, false, NULL)) { - if (end) { - rettv->vval.v_number = (varnumber_T)last; - } else { - rettv->vval.v_number = (varnumber_T)first; - } - return; - } - } - rettv->vval.v_number = -1; -} - -/* - * "foldclosed()" function - */ -static void f_foldclosed(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - foldclosed_both(argvars, rettv, FALSE); -} - -/* - * "foldclosedend()" function - */ -static void f_foldclosedend(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - foldclosed_both(argvars, rettv, TRUE); -} - -/* - * "foldlevel()" function - */ -static void f_foldlevel(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - rettv->vval.v_number = foldLevel(lnum); - } -} - -/* - * "foldtext()" function - */ -static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T foldstart; - linenr_T foldend; - char_u *dashes; - linenr_T lnum; - char_u *s; - char_u *r; - int len; - char *txt; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - foldstart = (linenr_T)get_vim_var_nr(VV_FOLDSTART); - foldend = (linenr_T)get_vim_var_nr(VV_FOLDEND); - dashes = get_vim_var_str(VV_FOLDDASHES); - if (foldstart > 0 && foldend <= curbuf->b_ml.ml_line_count) { - // Find first non-empty line in the fold. - for (lnum = foldstart; lnum < foldend; lnum++) { - if (!linewhite(lnum)) { - break; - } - } - - /* Find interesting text in this line. */ - s = skipwhite(ml_get(lnum)); - /* skip C comment-start */ - if (s[0] == '/' && (s[1] == '*' || s[1] == '/')) { - s = skipwhite(s + 2); - if (*skipwhite(s) == NUL && lnum + 1 < foldend) { - s = skipwhite(ml_get(lnum + 1)); - if (*s == '*') - s = skipwhite(s + 1); - } - } - unsigned long count = (unsigned long)(foldend - foldstart + 1); - txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count); - r = xmalloc(STRLEN(txt) - + STRLEN(dashes) // for %s - + 20 // for %3ld - + STRLEN(s)); // concatenated - sprintf((char *)r, txt, dashes, count); - len = (int)STRLEN(r); - STRCAT(r, s); - /* remove 'foldmarker' and 'commentstring' */ - foldtext_cleanup(r + len); - rettv->vval.v_string = r; - } -} - -/* - * "foldtextresult(lnum)" function - */ -static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *text; - char_u buf[FOLD_TEXT_LEN]; - foldinfo_T foldinfo; - int fold_count; - static bool entered = false; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (entered) { - return; // reject recursive use - } - entered = true; - linenr_T lnum = tv_get_lnum(argvars); - // Treat illegal types and illegal string values for {lnum} the same. - if (lnum < 0) { - lnum = 0; - } - fold_count = foldedCount(curwin, lnum, &foldinfo); - if (fold_count > 0) { - text = get_foldtext(curwin, lnum, lnum + fold_count - 1, &foldinfo, buf); - if (text == buf) { - text = vim_strsave(text); - } - rettv->vval.v_string = text; - } - - entered = false; -} - -/* - * "foreground()" function - */ -static void f_foreground(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ -} - -static void common_function(typval_T *argvars, typval_T *rettv, - bool is_funcref, FunPtr fptr) +void common_function(typval_T *argvars, typval_T *rettv, + bool is_funcref, FunPtr fptr) { char_u *s; char_u *name; @@ -9735,6 +6115,10 @@ static void common_function(typval_T *argvars, typval_T *rettv, list = argvars[arg_idx].vval.v_list; if (tv_list_len(list) == 0) { arg_idx = 0; + } else if (tv_list_len(list) > MAX_FUNC_ARGS) { + emsg_funcname((char *)e_toomanyarg, name); + xfree(name); + goto theend; } } } @@ -9802,116 +6186,8 @@ theend: xfree(trans_name); } -static void f_funcref(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - common_function(argvars, rettv, true, fptr); -} - -static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - common_function(argvars, rettv, false, fptr); -} - -/// "garbagecollect()" function -static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // This is postponed until we are back at the toplevel, because we may be - // using Lists and Dicts internally. E.g.: ":echo [garbagecollect()]". - want_garbage_collect = true; - - if (argvars[0].v_type != VAR_UNKNOWN && tv_get_number(&argvars[0]) == 1) { - garbage_collect_at_exit = true; - } -} - -/* - * "get()" function - */ -static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - listitem_T *li; - list_T *l; - dictitem_T *di; - dict_T *d; - typval_T *tv = NULL; - bool what_is_dict = false; - - if (argvars[0].v_type == VAR_LIST) { - if ((l = argvars[0].vval.v_list) != NULL) { - bool error = false; - - li = tv_list_find(l, tv_get_number_chk(&argvars[1], &error)); - if (!error && li != NULL) { - tv = TV_LIST_ITEM_TV(li); - } - } - } else if (argvars[0].v_type == VAR_DICT) { - if ((d = argvars[0].vval.v_dict) != NULL) { - di = tv_dict_find(d, tv_get_string(&argvars[1]), -1); - if (di != NULL) { - tv = &di->di_tv; - } - } - } else if (tv_is_func(argvars[0])) { - partial_T *pt; - partial_T fref_pt; - - if (argvars[0].v_type == VAR_PARTIAL) { - pt = argvars[0].vval.v_partial; - } else { - memset(&fref_pt, 0, sizeof(fref_pt)); - fref_pt.pt_name = argvars[0].vval.v_string; - pt = &fref_pt; - } - - if (pt != NULL) { - const char *const what = tv_get_string(&argvars[1]); - - if (strcmp(what, "func") == 0 || strcmp(what, "name") == 0) { - rettv->v_type = (*what == 'f' ? VAR_FUNC : VAR_STRING); - const char *const n = (const char *)partial_name(pt); - assert(n != NULL); - rettv->vval.v_string = (char_u *)xstrdup(n); - if (rettv->v_type == VAR_FUNC) { - func_ref(rettv->vval.v_string); - } - } else if (strcmp(what, "dict") == 0) { - what_is_dict = true; - if (pt->pt_dict != NULL) { - tv_dict_set_ret(rettv, pt->pt_dict); - } - } else if (strcmp(what, "args") == 0) { - rettv->v_type = VAR_LIST; - if (tv_list_alloc_ret(rettv, pt->pt_argc) != NULL) { - for (int i = 0; i < pt->pt_argc; i++) { - tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]); - } - } - } else { - EMSG2(_(e_invarg2), what); - } - - // When {what} == "dict" and pt->pt_dict == NULL, evaluate the - // third argument - if (!what_is_dict) { - return; - } - } - } else { - EMSG2(_(e_listdictarg), "get()"); - } - - if (tv == NULL) { - if (argvars[2].v_type != VAR_UNKNOWN) { - tv_copy(&argvars[2], rettv); - } - } else { - tv_copy(tv, rettv); - } -} - /// Returns buffer options, variables and other attributes in a dictionary. -static dict_T *get_buffer_info(buf_T *buf) +dict_T *get_buffer_info(buf_T *buf) { dict_T *const dict = tv_dict_alloc(); @@ -9945,110 +6221,9 @@ static dict_T *get_buffer_info(buf_T *buf) tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf)); } - return dict; -} - -/// "getbufinfo()" function -static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *argbuf = NULL; - bool filtered = false; - bool sel_buflisted = false; - bool sel_bufloaded = false; - bool sel_bufmodified = false; - - tv_list_alloc_ret(rettv, kListLenMayKnow); - - // List of all the buffers or selected buffers - if (argvars[0].v_type == VAR_DICT) { - dict_T *sel_d = argvars[0].vval.v_dict; - - if (sel_d != NULL) { - dictitem_T *di; - - filtered = true; - - di = tv_dict_find(sel_d, S_LEN("buflisted")); - if (di != NULL && tv_get_number(&di->di_tv)) { - sel_buflisted = true; - } - - di = tv_dict_find(sel_d, S_LEN("bufloaded")); - if (di != NULL && tv_get_number(&di->di_tv)) { - sel_bufloaded = true; - } - di = tv_dict_find(sel_d, S_LEN("bufmodified")); - if (di != NULL && tv_get_number(&di->di_tv)) { - sel_bufmodified = true; - } - } - } else if (argvars[0].v_type != VAR_UNKNOWN) { - // Information about one buffer. Argument specifies the buffer - if (tv_check_num(&argvars[0])) { // issue errmsg if type error - emsg_off++; - argbuf = tv_get_buf(&argvars[0], false); - emsg_off--; - if (argbuf == NULL) { - return; - } - } - } - - // Return information about all the buffers or a specified buffer - FOR_ALL_BUFFERS(buf) { - if (argbuf != NULL && argbuf != buf) { - continue; - } - if (filtered && ((sel_bufloaded && buf->b_ml.ml_mfp == NULL) - || (sel_buflisted && !buf->b_p_bl) - || (sel_bufmodified && !buf->b_changed))) { - continue; - } - - dict_T *const d = get_buffer_info(buf); - tv_list_append_dict(rettv->vval.v_list, d); - if (argbuf != NULL) { - return; - } - } -} - -/* - * Get line or list of lines from buffer "buf" into "rettv". - * Return a range (from start to end) of lines in rettv from the specified - * buffer. - * If 'retlist' is TRUE, then the lines are returned as a Vim List. - */ -static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retlist, typval_T *rettv) -{ - rettv->v_type = (retlist ? VAR_LIST : VAR_STRING); - rettv->vval.v_string = NULL; - - if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0 || end < start) { - if (retlist) { - tv_list_alloc_ret(rettv, 0); - } - return; - } + tv_dict_add_nr(dict, S_LEN("lastused"), buf->b_last_used); - if (retlist) { - if (start < 1) { - start = 1; - } - if (end > buf->b_ml.ml_line_count) { - end = buf->b_ml.ml_line_count; - } - tv_list_alloc_ret(rettv, end - start + 1); - while (start <= end) { - tv_list_append_string(rettv->vval.v_list, - (const char *)ml_get_buf(buf, start++, false), -1); - } - } else { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = ((start >= 1 && start <= buf->b_ml.ml_line_count) - ? vim_strsave(ml_get_buf(buf, start, false)) - : NULL); - } + return dict; } /// Get the line number from VimL object @@ -10061,8 +6236,8 @@ static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retli /// be NULL, in this case "$" results in zero return. /// /// @return Line number or 0 in case of error. -static linenr_T tv_get_lnum_buf(const typval_T *const tv, - const buf_T *const buf) +linenr_T tv_get_lnum_buf(const typval_T *const tv, + const buf_T *const buf) FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT { if (tv->v_type == VAR_STRING @@ -10074,675 +6249,8 @@ static linenr_T tv_get_lnum_buf(const typval_T *const tv, return tv_get_number_chk(tv, NULL); } -/* - * "getbufline()" function - */ -static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *buf = NULL; - - if (tv_check_str_or_nr(&argvars[0])) { - emsg_off++; - buf = tv_get_buf(&argvars[0], false); - emsg_off--; - } - - const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); - const linenr_T end = (argvars[2].v_type == VAR_UNKNOWN - ? lnum - : tv_get_lnum_buf(&argvars[2], buf)); - - get_buffer_lines(buf, lnum, end, true, rettv); -} - -/* - * "getbufvar()" function - */ -static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - bool done = false; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - if (!tv_check_str_or_nr(&argvars[0])) { - goto f_getbufvar_end; - } - - const char *varname = tv_get_string_chk(&argvars[1]); - emsg_off++; - buf_T *const buf = tv_get_buf(&argvars[0], false); - - if (buf != NULL && varname != NULL) { - // set curbuf to be our buf, temporarily - buf_T *const save_curbuf = curbuf; - curbuf = buf; - - if (*varname == '&') { // buffer-local-option - if (varname[1] == NUL) { - // get all buffer-local options in a dict - dict_T *opts = get_winbuf_options(true); - - if (opts != NULL) { - tv_dict_set_ret(rettv, opts); - done = true; - } - } else if (get_option_tv(&varname, rettv, true) == OK) { - // buffer-local-option - done = true; - } - } else { - // Look up the variable. - // Let getbufvar({nr}, "") return the "b:" dictionary. - dictitem_T *const v = find_var_in_ht(&curbuf->b_vars->dv_hashtab, 'b', - varname, strlen(varname), false); - if (v != NULL) { - tv_copy(&v->di_tv, rettv); - done = true; - } - } - - // restore previous notion of curbuf - curbuf = save_curbuf; - } - emsg_off--; - -f_getbufvar_end: - if (!done && argvars[2].v_type != VAR_UNKNOWN) { - // use the default value - tv_copy(&argvars[2], rettv); - } -} - -// "getchangelist()" function -static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, 2); - vim_ignored = tv_get_number(&argvars[0]); // issue errmsg if type error - emsg_off++; - const buf_T *const buf = tv_get_buf(&argvars[0], false); - emsg_off--; - if (buf == NULL) { - return; - } - - list_T *const l = tv_list_alloc(buf->b_changelistlen); - tv_list_append_list(rettv->vval.v_list, l); - // The current window change list index tracks only the position in the - // current buffer change list. For other buffers, use the change list - // length as the current index. - tv_list_append_number(rettv->vval.v_list, - (buf == curwin->w_buffer) - ? curwin->w_changelistidx - : buf->b_changelistlen); - - for (int i = 0; i < buf->b_changelistlen; i++) { - if (buf->b_changelist[i].mark.lnum == 0) { - continue; - } - dict_T *const d = tv_dict_alloc(); - tv_list_append_dict(l, d); - tv_dict_add_nr(d, S_LEN("lnum"), buf->b_changelist[i].mark.lnum); - tv_dict_add_nr(d, S_LEN("col"), buf->b_changelist[i].mark.col); - tv_dict_add_nr(d, S_LEN("coladd"), buf->b_changelist[i].mark.coladd); - } -} - -/* - * "getchar()" function - */ -static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - varnumber_T n; - bool error = false; - - no_mapping++; - for (;; ) { - // Position the cursor. Needed after a message that ends in a space, - // or if event processing caused a redraw. - ui_cursor_goto(msg_row, msg_col); - - if (argvars[0].v_type == VAR_UNKNOWN) { - // getchar(): blocking wait. - if (!(char_avail() || using_script() || input_available())) { - (void)os_inchar(NULL, 0, -1, 0, main_loop.events); - if (!multiqueue_empty(main_loop.events)) { - multiqueue_process_events(main_loop.events); - continue; - } - } - n = safe_vgetc(); - } else if (tv_get_number_chk(&argvars[0], &error) == 1) { - // getchar(1): only check if char avail - n = vpeekc_any(); - } else if (error || vpeekc_any() == NUL) { - // illegal argument or getchar(0) and no char avail: return zero - n = 0; - } else { - // getchar(0) and char avail: return char - n = safe_vgetc(); - } - - if (n == K_IGNORE) { - continue; - } - break; - } - no_mapping--; - - vimvars[VV_MOUSE_WIN].vv_nr = 0; - vimvars[VV_MOUSE_WINID].vv_nr = 0; - vimvars[VV_MOUSE_LNUM].vv_nr = 0; - vimvars[VV_MOUSE_COL].vv_nr = 0; - - rettv->vval.v_number = n; - if (IS_SPECIAL(n) || mod_mask != 0) { - char_u temp[10]; /* modifier: 3, mbyte-char: 6, NUL: 1 */ - int i = 0; - - /* Turn a special key into three bytes, plus modifier. */ - if (mod_mask != 0) { - temp[i++] = K_SPECIAL; - temp[i++] = KS_MODIFIER; - temp[i++] = mod_mask; - } - if (IS_SPECIAL(n)) { - temp[i++] = K_SPECIAL; - temp[i++] = K_SECOND(n); - temp[i++] = K_THIRD(n); - } else { - i += utf_char2bytes(n, temp + i); - } - temp[i++] = NUL; - rettv->v_type = VAR_STRING; - rettv->vval.v_string = vim_strsave(temp); - - if (is_mouse_key(n)) { - int row = mouse_row; - int col = mouse_col; - int grid = mouse_grid; - win_T *win; - linenr_T lnum; - win_T *wp; - int winnr = 1; - - if (row >= 0 && col >= 0) { - /* Find the window at the mouse coordinates and compute the - * text position. */ - win = mouse_find_win(&grid, &row, &col); - if (win == NULL) { - return; - } - (void)mouse_comp_pos(win, &row, &col, &lnum); - for (wp = firstwin; wp != win; wp = wp->w_next) - ++winnr; - vimvars[VV_MOUSE_WIN].vv_nr = winnr; - vimvars[VV_MOUSE_WINID].vv_nr = wp->handle; - vimvars[VV_MOUSE_LNUM].vv_nr = lnum; - vimvars[VV_MOUSE_COL].vv_nr = col + 1; - } - } - } -} - -/* - * "getcharmod()" function - */ -static void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = mod_mask; -} - -/* - * "getcharsearch()" function - */ -static void f_getcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_dict_alloc_ret(rettv); - - dict_T *dict = rettv->vval.v_dict; - - tv_dict_add_str(dict, S_LEN("char"), last_csearch()); - tv_dict_add_nr(dict, S_LEN("forward"), last_csearch_forward()); - tv_dict_add_nr(dict, S_LEN("until"), last_csearch_until()); -} - -/* - * "getcmdline()" function - */ -static void f_getcmdline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = get_cmdline_str(); -} - -/* - * "getcmdpos()" function - */ -static void f_getcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = get_cmdline_pos() + 1; -} - -/* - * "getcmdtype()" function - */ -static void f_getcmdtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xmallocz(1); - rettv->vval.v_string[0] = get_cmdline_type(); -} - -/* - * "getcmdwintype()" function - */ -static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - rettv->vval.v_string = xmallocz(1); - rettv->vval.v_string[0] = cmdwin_type; -} - -// "getcompletion()" function -static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *pat; - expand_T xpc; - bool filtered = false; - int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH - | WILD_NO_BEEP; - - if (argvars[2].v_type != VAR_UNKNOWN) { - filtered = (bool)tv_get_number_chk(&argvars[2], NULL); - } - - if (p_wic) { - options |= WILD_ICASE; - } - - // For filtered results, 'wildignore' is used - if (!filtered) { - options |= WILD_KEEP_ALL; - } - - if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) { - EMSG(_(e_invarg)); - return; - } - - if (strcmp(tv_get_string(&argvars[1]), "cmdline") == 0) { - set_one_cmd_context(&xpc, tv_get_string(&argvars[0])); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - goto theend; - } - - ExpandInit(&xpc); - xpc.xp_pattern = (char_u *)tv_get_string(&argvars[0]); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - xpc.xp_context = cmdcomplete_str_to_type( - (char_u *)tv_get_string(&argvars[1])); - if (xpc.xp_context == EXPAND_NOTHING) { - EMSG2(_(e_invarg2), argvars[1].vval.v_string); - return; - } - - if (xpc.xp_context == EXPAND_MENUS) { - set_context_in_menu_cmd(&xpc, (char_u *)"menu", xpc.xp_pattern, false); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - } - - if (xpc.xp_context == EXPAND_CSCOPE) { - set_context_in_cscope_cmd(&xpc, (const char *)xpc.xp_pattern, CMD_cscope); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - } - - if (xpc.xp_context == EXPAND_SIGN) { - set_context_in_sign_cmd(&xpc, xpc.xp_pattern); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - } - -theend: - pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); - ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); - tv_list_alloc_ret(rettv, xpc.xp_numfiles); - - for (int i = 0; i < xpc.xp_numfiles; i++) { - tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], - -1); - } - xfree(pat); - ExpandCleanup(&xpc); -} - -/// `getcwd([{win}[, {tab}]])` function -/// -/// Every scope not specified implies the currently selected scope object. -/// -/// @pre The arguments must be of type number. -/// @pre There may not be more than two arguments. -/// @pre An argument may not be -1 if preceding arguments are not all -1. -/// -/// @post The return value will be a string. -static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // Possible scope of working directory to return. - CdScope scope = kCdScopeInvalid; - - // Numbers of the scope objects (window, tab) we want the working directory - // of. A `-1` means to skip this scope, a `0` means the current object. - int scope_number[] = { - [kCdScopeWindow] = 0, // Number of window to look at. - [kCdScopeTab ] = 0, // Number of tab to look at. - }; - - char_u *cwd = NULL; // Current working directory to print - char_u *from = NULL; // The original string to copy - - tabpage_T *tp = curtab; // The tabpage to look at. - win_T *win = curwin; // The window to look at. - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - // Pre-conditions and scope extraction together - for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) { - // If there is no argument there are no more scopes after it, break out. - if (argvars[i].v_type == VAR_UNKNOWN) { - break; - } - if (argvars[i].v_type != VAR_NUMBER) { - EMSG(_(e_invarg)); - return; - } - scope_number[i] = argvars[i].vval.v_number; - // It is an error for the scope number to be less than `-1`. - if (scope_number[i] < -1) { - EMSG(_(e_invarg)); - return; - } - // Use the narrowest scope the user requested - if (scope_number[i] >= 0 && scope == kCdScopeInvalid) { - // The scope is the current iteration step. - scope = i; - } else if (scope_number[i] < 0) { - scope = i + 1; - } - } - - // If the user didn't specify anything, default to window scope - if (scope == kCdScopeInvalid) { - scope = MIN_CD_SCOPE; - } - - // Find the tabpage by number - if (scope_number[kCdScopeTab] > 0) { - tp = find_tabpage(scope_number[kCdScopeTab]); - if (!tp) { - EMSG(_("E5000: Cannot find tab number.")); - return; - } - } - - // Find the window in `tp` by number, `NULL` if none. - if (scope_number[kCdScopeWindow] >= 0) { - if (scope_number[kCdScopeTab] < 0) { - EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); - return; - } - - if (scope_number[kCdScopeWindow] > 0) { - win = find_win_by_nr(&argvars[0], tp); - if (!win) { - EMSG(_("E5002: Cannot find window number.")); - return; - } - } - } - - cwd = xmalloc(MAXPATHL); - - switch (scope) { - case kCdScopeWindow: - assert(win); - from = win->w_localdir; - if (from) { - break; - } - FALLTHROUGH; - case kCdScopeTab: - assert(tp); - from = tp->tp_localdir; - if (from) { - break; - } - FALLTHROUGH; - case kCdScopeGlobal: - if (globaldir) { // `globaldir` is not always set. - from = globaldir; - } else if (os_dirname(cwd, MAXPATHL) == FAIL) { // Get the OS CWD. - from = (char_u *)""; // Return empty string on failure. - } - break; - case kCdScopeInvalid: // We should never get here - assert(false); - } - - if (from) { - xstrlcpy((char *)cwd, (char *)from, MAXPATHL); - } - - rettv->vval.v_string = vim_strsave(cwd); -#ifdef BACKSLASH_IN_FILENAME - slash_adjust(rettv->vval.v_string); -#endif - - xfree(cwd); -} - -/* - * "getfontname()" function - */ -static void f_getfontname(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; -} - -/* - * "getfperm({fname})" function - */ -static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char *perm = NULL; - char_u flags[] = "rwx"; - - const char *filename = tv_get_string(&argvars[0]); - int32_t file_perm = os_getperm(filename); - if (file_perm >= 0) { - perm = xstrdup("---------"); - for (int i = 0; i < 9; i++) { - if (file_perm & (1 << (8 - i))) { - perm[i] = flags[i % 3]; - } - } - } - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)perm; -} - -/* - * "getfsize({fname})" function - */ -static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *fname = tv_get_string(&argvars[0]); - - rettv->v_type = VAR_NUMBER; - - FileInfo file_info; - if (os_fileinfo(fname, &file_info)) { - uint64_t filesize = os_fileinfo_size(&file_info); - if (os_isdir((const char_u *)fname)) { - rettv->vval.v_number = 0; - } else { - rettv->vval.v_number = (varnumber_T)filesize; - - /* non-perfect check for overflow */ - if ((uint64_t)rettv->vval.v_number != filesize) { - rettv->vval.v_number = -2; - } - } - } else { - rettv->vval.v_number = -1; - } -} - -/* - * "getftime({fname})" function - */ -static void f_getftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *fname = tv_get_string(&argvars[0]); - - FileInfo file_info; - if (os_fileinfo(fname, &file_info)) { - rettv->vval.v_number = (varnumber_T)file_info.stat.st_mtim.tv_sec; - } else { - rettv->vval.v_number = -1; - } -} - -/* - * "getftype({fname})" function - */ -static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *type = NULL; - char *t; - - const char *fname = tv_get_string(&argvars[0]); - - rettv->v_type = VAR_STRING; - FileInfo file_info; - if (os_fileinfo_link(fname, &file_info)) { - uint64_t mode = file_info.stat.st_mode; -#ifdef S_ISREG - if (S_ISREG(mode)) - t = "file"; - else if (S_ISDIR(mode)) - t = "dir"; -# ifdef S_ISLNK - else if (S_ISLNK(mode)) - t = "link"; -# endif -# ifdef S_ISBLK - else if (S_ISBLK(mode)) - t = "bdev"; -# endif -# ifdef S_ISCHR - else if (S_ISCHR(mode)) - t = "cdev"; -# endif -# ifdef S_ISFIFO - else if (S_ISFIFO(mode)) - t = "fifo"; -# endif -# ifdef S_ISSOCK - else if (S_ISSOCK(mode)) - t = "socket"; -# endif - else - t = "other"; -#else -# ifdef S_IFMT - switch (mode & S_IFMT) { - case S_IFREG: t = "file"; break; - case S_IFDIR: t = "dir"; break; -# ifdef S_IFLNK - case S_IFLNK: t = "link"; break; -# endif -# ifdef S_IFBLK - case S_IFBLK: t = "bdev"; break; -# endif -# ifdef S_IFCHR - case S_IFCHR: t = "cdev"; break; -# endif -# ifdef S_IFIFO - case S_IFIFO: t = "fifo"; break; -# endif -# ifdef S_IFSOCK - case S_IFSOCK: t = "socket"; break; -# endif - default: t = "other"; - } -# else - if (os_isdir((const char_u *)fname)) { - t = "dir"; - } else { - t = "file"; - } -# endif -#endif - type = vim_strsave((char_u *)t); - } - rettv->vval.v_string = type; -} - -// "getjumplist()" function -static void f_getjumplist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, kListLenMayKnow); - win_T *const wp = find_tabwin(&argvars[0], &argvars[1]); - if (wp == NULL) { - return; - } - - cleanup_jumplist(wp, true); - - list_T *const l = tv_list_alloc(wp->w_jumplistlen); - tv_list_append_list(rettv->vval.v_list, l); - tv_list_append_number(rettv->vval.v_list, wp->w_jumplistidx); - - for (int i = 0; i < wp->w_jumplistlen; i++) { - if (wp->w_jumplist[i].fmark.mark.lnum == 0) { - continue; - } - dict_T *const d = tv_dict_alloc(); - tv_list_append_dict(l, d); - tv_dict_add_nr(d, S_LEN("lnum"), wp->w_jumplist[i].fmark.mark.lnum); - tv_dict_add_nr(d, S_LEN("col"), wp->w_jumplist[i].fmark.mark.col); - tv_dict_add_nr(d, S_LEN("coladd"), wp->w_jumplist[i].fmark.mark.coladd); - tv_dict_add_nr(d, S_LEN("bufnr"), wp->w_jumplist[i].fmark.fnum); - if (wp->w_jumplist[i].fname != NULL) { - tv_dict_add_str(d, S_LEN("filename"), (char *)wp->w_jumplist[i].fname); - } - } -} - -/* - * "getline(lnum, [end])" function - */ -static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T end; - bool retlist; - - const linenr_T lnum = tv_get_lnum(argvars); - if (argvars[1].v_type == VAR_UNKNOWN) { - end = lnum; - retlist = false; - } else { - end = tv_get_lnum(&argvars[1]); - retlist = true; - } - - get_buffer_lines(curbuf, lnum, end, retlist, rettv); -} - -static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, - typval_T *rettv) +void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, + typval_T *rettv) { if (what_arg->v_type == VAR_UNKNOWN) { tv_list_alloc_ret(rettv, kListLenMayKnow); @@ -10765,217 +6273,9 @@ static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, } } -/// "getloclist()" function -static void f_getloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - get_qf_loc_list(false, wp, &argvars[1], rettv); -} - -/* - * "getmatches()" function - */ -static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - matchitem_T *cur = curwin->w_match_head; - int i; - - tv_list_alloc_ret(rettv, kListLenMayKnow); - while (cur != NULL) { - dict_T *dict = tv_dict_alloc(); - if (cur->match.regprog == NULL) { - // match added with matchaddpos() - for (i = 0; i < MAXPOSMATCH; i++) { - llpos_T *llpos; - char buf[30]; // use 30 to avoid compiler warning - - llpos = &cur->pos.pos[i]; - if (llpos->lnum == 0) { - break; - } - list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0)); - tv_list_append_number(l, (varnumber_T)llpos->lnum); - if (llpos->col > 0) { - tv_list_append_number(l, (varnumber_T)llpos->col); - tv_list_append_number(l, (varnumber_T)llpos->len); - } - int len = snprintf(buf, sizeof(buf), "pos%d", i + 1); - assert((size_t)len < sizeof(buf)); - tv_dict_add_list(dict, buf, (size_t)len, l); - } - } else { - tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cur->pattern); - } - tv_dict_add_str(dict, S_LEN("group"), - (const char *)syn_id2name(cur->hlg_id)); - tv_dict_add_nr(dict, S_LEN("priority"), (varnumber_T)cur->priority); - tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->id); - - if (cur->conceal_char) { - char buf[MB_MAXBYTES + 1]; - - buf[utf_char2bytes((int)cur->conceal_char, (char_u *)buf)] = NUL; - tv_dict_add_str(dict, S_LEN("conceal"), buf); - } - - tv_list_append_dict(rettv->vval.v_list, dict); - cur = cur->next; - } -} - -/* - * "getpid()" function - */ -static void f_getpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = os_get_pid(); -} - -static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos) -{ - pos_T *fp; - int fnum = -1; - - if (getcurpos) { - fp = &curwin->w_cursor; - } else { - fp = var2fpos(&argvars[0], true, &fnum); - } - - list_T *const l = tv_list_alloc_ret(rettv, 4 + (!!getcurpos)); - tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0); - tv_list_append_number(l, ((fp != NULL) - ? (varnumber_T)fp->lnum - : (varnumber_T)0)); - tv_list_append_number( - l, ((fp != NULL) - ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1) - : (varnumber_T)0)); - tv_list_append_number( - l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0); - if (getcurpos) { - const int save_set_curswant = curwin->w_set_curswant; - const colnr_T save_curswant = curwin->w_curswant; - const colnr_T save_virtcol = curwin->w_virtcol; - - update_curswant(); - tv_list_append_number(l, (curwin->w_curswant == MAXCOL - ? (varnumber_T)MAXCOL - : (varnumber_T)curwin->w_curswant + 1)); - - // Do not change "curswant", as it is unexpected that a get - // function has a side effect. - if (save_set_curswant) { - curwin->w_set_curswant = save_set_curswant; - curwin->w_curswant = save_curswant; - curwin->w_virtcol = save_virtcol; - curwin->w_valid &= ~VALID_VIRTCOL; - } - } -} - -/* - * "getcurpos(string)" function - */ -static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - getpos_both(argvars, rettv, true); -} - -/* - * "getpos(string)" function - */ -static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - getpos_both(argvars, rettv, false); -} - -/// "getqflist()" functions -static void f_getqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - get_qf_loc_list(true, NULL, &argvars[0], rettv); -} - -/// "getreg()" function -static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *strregname; - int arg2 = false; - bool return_list = false; - bool error = false; - - if (argvars[0].v_type != VAR_UNKNOWN) { - strregname = tv_get_string_chk(&argvars[0]); - error = strregname == NULL; - if (argvars[1].v_type != VAR_UNKNOWN) { - arg2 = tv_get_number_chk(&argvars[1], &error); - if (!error && argvars[2].v_type != VAR_UNKNOWN) { - return_list = tv_get_number_chk(&argvars[2], &error); - } - } - } else { - strregname = (const char *)vimvars[VV_REG].vv_str; - } - - if (error) { - return; - } - - int regname = (uint8_t)(strregname == NULL ? '"' : *strregname); - if (regname == 0) { - regname = '"'; - } - - if (return_list) { - rettv->v_type = VAR_LIST; - rettv->vval.v_list = - get_reg_contents(regname, (arg2 ? kGRegExprSrc : 0) | kGRegList); - if (rettv->vval.v_list == NULL) { - rettv->vval.v_list = tv_list_alloc(0); - } - tv_list_ref(rettv->vval.v_list); - } else { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = get_reg_contents(regname, arg2 ? kGRegExprSrc : 0); - } -} - -/* - * "getregtype()" function - */ -static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *strregname; - - if (argvars[0].v_type != VAR_UNKNOWN) { - strregname = tv_get_string_chk(&argvars[0]); - if (strregname == NULL) { // Type error; errmsg already given. - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - return; - } - } else { - // Default to v:register. - strregname = (const char *)vimvars[VV_REG].vv_str; - } - - int regname = (uint8_t)(strregname == NULL ? '"' : *strregname); - if (regname == 0) { - regname = '"'; - } - - colnr_T reglen = 0; - char buf[NUMBUFLEN + 2]; - MotionType reg_type = get_reg_type(regname, ®len); - format_reg_type(reg_type, reglen, buf, ARRAY_SIZE(buf)); - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)xstrdup(buf); -} - /// Returns information (variables, options, etc.) about a tab page /// as a dictionary. -static dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) +dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) { dict_T *const dict = tv_dict_alloc(); @@ -10993,106 +6293,8 @@ static dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) return dict; } -/// "gettabinfo()" function -static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tabpage_T *tparg = NULL; - - tv_list_alloc_ret(rettv, (argvars[0].v_type == VAR_UNKNOWN - ? 1 - : kListLenMayKnow)); - - if (argvars[0].v_type != VAR_UNKNOWN) { - // Information about one tab page - tparg = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); - if (tparg == NULL) { - return; - } - } - - // Get information about a specific tab page or all tab pages - int tpnr = 0; - FOR_ALL_TABS(tp) { - tpnr++; - if (tparg != NULL && tp != tparg) { - continue; - } - dict_T *const d = get_tabpage_info(tp, tpnr); - tv_list_append_dict(rettv->vval.v_list, d); - if (tparg != NULL) { - return; - } - } -} - -/* - * "gettabvar()" function - */ -static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *oldcurwin; - tabpage_T *oldtabpage; - bool done = false; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - const char *const varname = tv_get_string_chk(&argvars[1]); - tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); - if (tp != NULL && varname != NULL) { - // Set tp to be our tabpage, temporarily. Also set the window to the - // first window in the tabpage, otherwise the window is not valid. - win_T *const window = tp == curtab || tp->tp_firstwin == NULL - ? firstwin - : tp->tp_firstwin; - if (switch_win(&oldcurwin, &oldtabpage, window, tp, true) == OK) { - // look up the variable - // Let gettabvar({nr}, "") return the "t:" dictionary. - const dictitem_T *const v = find_var_in_ht(&tp->tp_vars->dv_hashtab, 't', - varname, strlen(varname), - false); - if (v != NULL) { - tv_copy(&v->di_tv, rettv); - done = true; - } - } - - // restore previous notion of curwin - restore_win(oldcurwin, oldtabpage, true); - } - - if (!done && argvars[2].v_type != VAR_UNKNOWN) { - tv_copy(&argvars[2], rettv); - } -} - -/* - * "gettabwinvar()" function - */ -static void f_gettabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - getwinvar(argvars, rettv, 1); -} - -// "gettagstack()" function -static void f_gettagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = curwin; // default is current window - - tv_dict_alloc_ret(rettv); - - if (argvars[0].v_type != VAR_UNKNOWN) { - wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - return; - } - } - - get_tagstack(wp, rettv->vval.v_dict); -} - /// Returns information about a window as a dictionary. -static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) +dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) { dict_T *const dict = tv_dict_alloc(); @@ -11103,6 +6305,7 @@ static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) tv_dict_add_nr(dict, S_LEN("winrow"), wp->w_winrow + 1); tv_dict_add_nr(dict, S_LEN("topline"), wp->w_topline); tv_dict_add_nr(dict, S_LEN("botline"), wp->w_botline - 1); + tv_dict_add_nr(dict, S_LEN("winbar"), wp->w_winbar_height); tv_dict_add_nr(dict, S_LEN("width"), wp->w_width); tv_dict_add_nr(dict, S_LEN("bufnr"), wp->w_buffer->b_fnum); tv_dict_add_nr(dict, S_LEN("wincol"), wp->w_wincol + 1); @@ -11118,149 +6321,11 @@ static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) return dict; } -/// "getwininfo()" function -static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wparg = NULL; - - tv_list_alloc_ret(rettv, kListLenMayKnow); - - if (argvars[0].v_type != VAR_UNKNOWN) { - wparg = win_id2wp(argvars); - if (wparg == NULL) { - return; - } - } - - // Collect information about either all the windows across all the tab - // pages or one particular window. - int16_t tabnr = 0; - FOR_ALL_TABS(tp) { - tabnr++; - int16_t winnr = 0; - FOR_ALL_WINDOWS_IN_TAB(wp, tp) { - winnr++; - if (wparg != NULL && wp != wparg) { - continue; - } - dict_T *const d = get_win_info(wp, tabnr, winnr); - tv_list_append_dict(rettv->vval.v_list, d); - if (wparg != NULL) { - // found information about a specific window - return; - } - } - } -} - -// Dummy timer callback. Used by f_wait(). -static void dummy_timer_due_cb(TimeWatcher *tw, void *data) -{ -} - -// Dummy timer close callback. Used by f_wait(). -static void dummy_timer_close_cb(TimeWatcher *tw, void *data) -{ - xfree(tw); -} - -/// "wait(timeout, condition[, interval])" function -static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = -1; - - if (argvars[0].v_type != VAR_NUMBER) { - EMSG2(_(e_invargval), "1"); - return; - } - if ((argvars[2].v_type != VAR_NUMBER && argvars[2].v_type != VAR_UNKNOWN) - || (argvars[2].v_type == VAR_NUMBER && argvars[2].vval.v_number <= 0)) { - EMSG2(_(e_invargval), "3"); - return; - } - - int timeout = argvars[0].vval.v_number; - typval_T expr = argvars[1]; - int interval = argvars[2].v_type == VAR_NUMBER - ? argvars[2].vval.v_number - : 200; // Default. - TimeWatcher *tw = xmalloc(sizeof(TimeWatcher)); - - // Start dummy timer. - time_watcher_init(&main_loop, tw, NULL); - tw->events = main_loop.events; - tw->blockable = true; - time_watcher_start(tw, dummy_timer_due_cb, interval, interval); - - typval_T argv = TV_INITIAL_VALUE; - typval_T exprval = TV_INITIAL_VALUE; - bool error = false; - int save_called_emsg = called_emsg; - called_emsg = false; - - LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, timeout, - eval_expr_typval(&expr, &argv, 0, &exprval) != OK - || tv_get_number_chk(&exprval, &error) - || called_emsg || error || got_int); - - if (called_emsg || error) { - rettv->vval.v_number = -3; - } else if (got_int) { - got_int = false; - vgetc(); - rettv->vval.v_number = -2; - } else if (tv_get_number_chk(&exprval, &error)) { - rettv->vval.v_number = 0; - } - - called_emsg = save_called_emsg; - - // Stop dummy timer - time_watcher_stop(tw); - time_watcher_close(tw, dummy_timer_close_cb); -} - -// "win_screenpos()" function -static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, 2); - const win_T *const wp = find_win_by_nr_or_id(&argvars[0]); - tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_winrow + 1); - tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1); -} - -// "getwinpos({timeout})" function -static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, 2); - tv_list_append_number(rettv->vval.v_list, -1); - tv_list_append_number(rettv->vval.v_list, -1); -} - -/* - * "getwinposx()" function - */ -static void f_getwinposx(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; -} - -/* - * "getwinposy()" function - */ -static void f_getwinposy(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; -} - -/* - * Find window specified by "vp" in tabpage "tp". - */ -static win_T * -find_win_by_nr ( +// Find window specified by "vp" in tabpage "tp". +win_T * +find_win_by_nr( typval_T *vp, - tabpage_T *tp /* NULL for current tab page */ + tabpage_T *tp // NULL for current tab page ) { int nr = (int)tv_get_number_chk(vp, NULL); @@ -11291,7 +6356,7 @@ find_win_by_nr ( } /// Find window specified by "wvp" in tabpage "tvp". -static win_T *find_tabwin(typval_T *wvp, typval_T *tvp) +win_T *find_tabwin(typval_T *wvp, typval_T *tvp) { win_T *wp = NULL; tabpage_T *tp = NULL; @@ -11316,20 +6381,14 @@ static win_T *find_tabwin(typval_T *wvp, typval_T *tvp) return wp; } -/// "getwinvar()" function -static void f_getwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - getwinvar(argvars, rettv, 0); -} - /* * getwinvar() and gettabwinvar() */ -static void +void getwinvar( typval_T *argvars, typval_T *rettv, - int off /* 1 for gettabwinvar() */ + int off // 1 for gettabwinvar() ) { win_T *win, *oldcurwin; @@ -11396,655 +6455,15 @@ getwinvar( } /* - * "glob()" function - */ -static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int options = WILD_SILENT|WILD_USE_NL; - expand_T xpc; - bool error = false; - - /* When the optional second argument is non-zero, don't remove matches - * for 'wildignore' and don't put matches for 'suffixes' at the end. */ - rettv->v_type = VAR_STRING; - if (argvars[1].v_type != VAR_UNKNOWN) { - if (tv_get_number_chk(&argvars[1], &error)) { - options |= WILD_KEEP_ALL; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - if (tv_get_number_chk(&argvars[2], &error)) { - tv_list_set_ret(rettv, NULL); - } - if (argvars[3].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[3], &error)) { - options |= WILD_ALLLINKS; - } - } - } - if (!error) { - ExpandInit(&xpc); - xpc.xp_context = EXPAND_FILES; - if (p_wic) - options += WILD_ICASE; - if (rettv->v_type == VAR_STRING) { - rettv->vval.v_string = ExpandOne( - &xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, WILD_ALL); - } else { - ExpandOne(&xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, - WILD_ALL_KEEP); - tv_list_alloc_ret(rettv, xpc.xp_numfiles); - for (int i = 0; i < xpc.xp_numfiles; i++) { - tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], - -1); - } - ExpandCleanup(&xpc); - } - } else - rettv->vval.v_string = NULL; -} - -/// "globpath()" function -static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int flags = 0; // Flags for globpath. - bool error = false; - - // Return a string, or a list if the optional third argument is non-zero. - rettv->v_type = VAR_STRING; - - if (argvars[2].v_type != VAR_UNKNOWN) { - // When the optional second argument is non-zero, don't remove matches - // for 'wildignore' and don't put matches for 'suffixes' at the end. - if (tv_get_number_chk(&argvars[2], &error)) { - flags |= WILD_KEEP_ALL; - } - - if (argvars[3].v_type != VAR_UNKNOWN) { - if (tv_get_number_chk(&argvars[3], &error)) { - tv_list_set_ret(rettv, NULL); - } - if (argvars[4].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[4], &error)) { - flags |= WILD_ALLLINKS; - } - } - } - - char buf1[NUMBUFLEN]; - const char *const file = tv_get_string_buf_chk(&argvars[1], buf1); - if (file != NULL && !error) { - garray_T ga; - ga_init(&ga, (int)sizeof(char_u *), 10); - globpath((char_u *)tv_get_string(&argvars[0]), (char_u *)file, &ga, flags); - - if (rettv->v_type == VAR_STRING) { - rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n"); - } else { - tv_list_alloc_ret(rettv, ga.ga_len); - for (int i = 0; i < ga.ga_len; i++) { - tv_list_append_string(rettv->vval.v_list, - ((const char **)(ga.ga_data))[i], -1); - } - } - - ga_clear_strings(&ga); - } else { - rettv->vval.v_string = NULL; - } -} - -// "glob2regpat()" function -static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = ((pat == NULL) - ? NULL - : file_pat_to_reg_pat((char_u *)pat, NULL, NULL, - false)); -} - -/// "has()" function -static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - static const char *const has_list[] = { -#if defined(BSD) && !defined(__APPLE__) - "bsd", -#endif -#ifdef UNIX - "unix", -#endif -#if defined(WIN32) - "win32", -#endif -#if defined(WIN64) || defined(_WIN64) - "win64", -#endif - "fname_case", -#ifdef HAVE_ACL - "acl", -#endif - "autochdir", - "arabic", - "autocmd", - "browsefilter", - "byte_offset", - "cindent", - "cmdline_compl", - "cmdline_hist", - "comments", - "conceal", - "cscope", - "cursorbind", - "cursorshape", -#ifdef DEBUG - "debug", -#endif - "dialog_con", - "diff", - "digraphs", - "eval", /* always present, of course! */ - "ex_extra", - "extra_search", - "file_in_path", - "filterpipe", - "find_in_path", - "float", - "folding", -#if defined(UNIX) - "fork", -#endif - "gettext", -#if defined(HAVE_ICONV) - "iconv", -#endif - "insert_expand", - "jumplist", - "keymap", - "lambda", - "langmap", - "libcall", - "linebreak", - "lispindent", - "listcmds", - "localmap", -#ifdef __APPLE__ - "mac", - "macunix", - "osx", - "osxdarwin", -#endif - "menu", - "mksession", - "modify_fname", - "mouse", - "multi_byte", - "multi_lang", - "num64", - "packages", - "path_extra", - "persistent_undo", - "postscript", - "printer", - "profile", - "pythonx", - "reltime", - "quickfix", - "rightleft", - "scrollbind", - "showcmd", - "cmdline_info", - "shada", - "signs", - "smartindent", - "startuptime", - "statusline", - "spell", - "syntax", -#if !defined(UNIX) - "system", // TODO(SplinterOfChaos): This IS defined for UNIX! -#endif - "tablineat", - "tag_binary", - "termguicolors", - "termresponse", - "textobjects", - "timers", - "title", - "user-commands", /* was accidentally included in 5.4 */ - "user_commands", - "vertsplit", - "virtualedit", - "visual", - "visualextra", - "vreplace", - "wildignore", - "wildmenu", - "windows", - "winaltkeys", - "writebackup", -#if defined(HAVE_WSL) - "wsl", -#endif - "nvim", - }; - - bool n = false; - const char *const name = tv_get_string(&argvars[0]); - for (size_t i = 0; i < ARRAY_SIZE(has_list); i++) { - if (STRICMP(name, has_list[i]) == 0) { - n = true; - break; - } - } - - if (!n) { - if (STRNICMP(name, "patch", 5) == 0) { - if (name[5] == '-' - && strlen(name) >= 11 - && ascii_isdigit(name[6]) - && ascii_isdigit(name[8]) - && ascii_isdigit(name[10])) { - int major = atoi(name + 6); - int minor = atoi(name + 8); - - // Expect "patch-9.9.01234". - n = (major < VIM_VERSION_MAJOR - || (major == VIM_VERSION_MAJOR - && (minor < VIM_VERSION_MINOR - || (minor == VIM_VERSION_MINOR - && has_vim_patch(atoi(name + 10)))))); - } else { - n = has_vim_patch(atoi(name + 5)); - } - } else if (STRNICMP(name, "nvim-", 5) == 0) { - // Expect "nvim-x.y.z" - n = has_nvim_version(name + 5); - } else if (STRICMP(name, "vim_starting") == 0) { - n = (starting != 0); - } else if (STRICMP(name, "ttyin") == 0) { - n = stdin_isatty; - } else if (STRICMP(name, "ttyout") == 0) { - n = stdout_isatty; - } else if (STRICMP(name, "multi_byte_encoding") == 0) { - n = has_mbyte != 0; - } else if (STRICMP(name, "syntax_items") == 0) { - n = syntax_present(curwin); -#ifdef UNIX - } else if (STRICMP(name, "unnamedplus") == 0) { - n = eval_has_provider("clipboard"); -#endif - } - } - - if (!n && eval_has_provider(name)) { - n = true; - } - - rettv->vval.v_number = n; -} - -/* - * "has_key()" function - */ -static void f_has_key(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - if (argvars[0].vval.v_dict == NULL) - return; - - rettv->vval.v_number = tv_dict_find(argvars[0].vval.v_dict, - tv_get_string(&argvars[1]), - -1) != NULL; -} - -/// `haslocaldir([{win}[, {tab}]])` function -/// -/// Returns `1` if the scope object has a local directory, `0` otherwise. If a -/// scope object is not specified the current one is implied. This function -/// share a lot of code with `f_getcwd`. -/// -/// @pre The arguments must be of type number. -/// @pre There may not be more than two arguments. -/// @pre An argument may not be -1 if preceding arguments are not all -1. -/// -/// @post The return value will be either the number `1` or `0`. -static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // Possible scope of working directory to return. - CdScope scope = kCdScopeInvalid; - - // Numbers of the scope objects (window, tab) we want the working directory - // of. A `-1` means to skip this scope, a `0` means the current object. - int scope_number[] = { - [kCdScopeWindow] = 0, // Number of window to look at. - [kCdScopeTab ] = 0, // Number of tab to look at. - }; - - tabpage_T *tp = curtab; // The tabpage to look at. - win_T *win = curwin; // The window to look at. - - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - // Pre-conditions and scope extraction together - for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) { - if (argvars[i].v_type == VAR_UNKNOWN) { - break; - } - if (argvars[i].v_type != VAR_NUMBER) { - EMSG(_(e_invarg)); - return; - } - scope_number[i] = argvars[i].vval.v_number; - if (scope_number[i] < -1) { - EMSG(_(e_invarg)); - return; - } - // Use the narrowest scope the user requested - if (scope_number[i] >= 0 && scope == kCdScopeInvalid) { - // The scope is the current iteration step. - scope = i; - } else if (scope_number[i] < 0) { - scope = i + 1; - } - } - - // If the user didn't specify anything, default to window scope - if (scope == kCdScopeInvalid) { - scope = MIN_CD_SCOPE; - } - - // Find the tabpage by number - if (scope_number[kCdScopeTab] > 0) { - tp = find_tabpage(scope_number[kCdScopeTab]); - if (!tp) { - EMSG(_("E5000: Cannot find tab number.")); - return; - } - } - - // Find the window in `tp` by number, `NULL` if none. - if (scope_number[kCdScopeWindow] >= 0) { - if (scope_number[kCdScopeTab] < 0) { - EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); - return; - } - - if (scope_number[kCdScopeWindow] > 0) { - win = find_win_by_nr(&argvars[0], tp); - if (!win) { - EMSG(_("E5002: Cannot find window number.")); - return; - } - } - } - - switch (scope) { - case kCdScopeWindow: - assert(win); - rettv->vval.v_number = win->w_localdir ? 1 : 0; - break; - case kCdScopeTab: - assert(tp); - rettv->vval.v_number = tp->tp_localdir ? 1 : 0; - break; - case kCdScopeGlobal: - // The global scope never has a local directory - break; - case kCdScopeInvalid: - // We should never get here - assert(false); - } -} - -/* - * "hasmapto()" function - */ -static void f_hasmapto(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *mode; - const char *const name = tv_get_string(&argvars[0]); - bool abbr = false; - char buf[NUMBUFLEN]; - if (argvars[1].v_type == VAR_UNKNOWN) { - mode = "nvo"; - } else { - mode = tv_get_string_buf(&argvars[1], buf); - if (argvars[2].v_type != VAR_UNKNOWN) { - abbr = tv_get_number(&argvars[2]); - } - } - - if (map_to_exists(name, mode, abbr)) { - rettv->vval.v_number = true; - } else { - rettv->vval.v_number = false; - } -} - -/* - * "histadd()" function - */ -static void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - HistoryType histype; - - rettv->vval.v_number = false; - if (check_restricted() || check_secure()) { - return; - } - const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error - histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID; - if (histype != HIST_INVALID) { - char buf[NUMBUFLEN]; - str = tv_get_string_buf(&argvars[1], buf); - if (*str != NUL) { - init_history(); - add_to_history(histype, (char_u *)str, false, NUL); - rettv->vval.v_number = true; - return; - } - } -} - -/* - * "histdel()" function - */ -static void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int n; - const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error - if (str == NULL) { - n = 0; - } else if (argvars[1].v_type == VAR_UNKNOWN) { - // only one argument: clear entire history - n = clr_history(get_histtype(str, strlen(str), false)); - } else if (argvars[1].v_type == VAR_NUMBER) { - // index given: remove that entry - n = del_history_idx(get_histtype(str, strlen(str), false), - (int)tv_get_number(&argvars[1])); - } else { - // string given: remove all matching entries - char buf[NUMBUFLEN]; - n = del_history_entry(get_histtype(str, strlen(str), false), - (char_u *)tv_get_string_buf(&argvars[1], buf)); - } - rettv->vval.v_number = n; -} - -/* - * "histget()" function - */ -static void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - HistoryType type; - int idx; - - const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error - if (str == NULL) { - rettv->vval.v_string = NULL; - } else { - type = get_histtype(str, strlen(str), false); - if (argvars[1].v_type == VAR_UNKNOWN) { - idx = get_history_idx(type); - } else { - idx = (int)tv_get_number_chk(&argvars[1], NULL); - } - // -1 on type error - rettv->vval.v_string = vim_strsave(get_history_entry(type, idx)); - } - rettv->v_type = VAR_STRING; -} - -/* - * "histnr()" function - */ -static void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const history = tv_get_string_chk(&argvars[0]); - HistoryType i = history == NULL - ? HIST_INVALID - : get_histtype(history, strlen(history), false); - if (i != HIST_INVALID) { - i = get_history_idx(i); - } - rettv->vval.v_number = i; -} - -/* - * "highlightID(name)" function - */ -static void f_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = syn_name2id( - (const char_u *)tv_get_string(&argvars[0])); -} - -/* - * "highlight_exists()" function - */ -static void f_hlexists(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = highlight_exists( - (const char_u *)tv_get_string(&argvars[0])); -} - -/* - * "hostname()" function - */ -static void f_hostname(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char hostname[256]; - - os_get_hostname(hostname, 256); - rettv->v_type = VAR_STRING; - rettv->vval.v_string = vim_strsave((char_u *)hostname); -} - -/* - * iconv() function - */ -static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - vimconv_T vimconv; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - const char *const str = tv_get_string(&argvars[0]); - char buf1[NUMBUFLEN]; - char_u *const from = enc_canonize(enc_skip( - (char_u *)tv_get_string_buf(&argvars[1], buf1))); - char buf2[NUMBUFLEN]; - char_u *const to = enc_canonize(enc_skip( - (char_u *)tv_get_string_buf(&argvars[2], buf2))); - vimconv.vc_type = CONV_NONE; - convert_setup(&vimconv, from, to); - - // If the encodings are equal, no conversion needed. - if (vimconv.vc_type == CONV_NONE) { - rettv->vval.v_string = (char_u *)xstrdup(str); - } else { - rettv->vval.v_string = string_convert(&vimconv, (char_u *)str, NULL); - } - - convert_setup(&vimconv, NULL, NULL); - xfree(from); - xfree(to); -} - -/* - * "indent()" function - */ -static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - rettv->vval.v_number = get_indent_lnum(lnum); - } else { - rettv->vval.v_number = -1; - } -} - -/* - * "index()" function - */ -static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - long idx = 0; - bool ic = false; - - rettv->vval.v_number = -1; - if (argvars[0].v_type != VAR_LIST) { - EMSG(_(e_listreq)); - return; - } - list_T *const l = argvars[0].vval.v_list; - if (l != NULL) { - listitem_T *item = tv_list_first(l); - if (argvars[2].v_type != VAR_UNKNOWN) { - bool error = false; - - // Start at specified item. - idx = tv_list_uidx(l, tv_get_number_chk(&argvars[2], &error)); - if (error || idx == -1) { - item = NULL; - } else { - item = tv_list_find(l, idx); - assert(item != NULL); - } - if (argvars[3].v_type != VAR_UNKNOWN) { - ic = !!tv_get_number_chk(&argvars[3], &error); - if (error) { - item = NULL; - } - } - } - - for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) { - if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic, false)) { - rettv->vval.v_number = idx; - break; - } - } - } -} - -static int inputsecret_flag = 0; - -/* * This function is used by f_input() and f_inputdialog() functions. The third * argument to f_input() specifies the type of completion to use at the * prompt. The third argument to f_inputdialog() specifies the value to return * when the user cancels the prompt. */ void get_user_input(const typval_T *const argvars, - typval_T *const rettv, const bool inputdialog) + typval_T *const rettv, + const bool inputdialog, + const bool secret) FUNC_ATTR_NONNULL_ALL { rettv->v_type = VAR_STRING; @@ -12155,7 +6574,7 @@ void get_user_input(const typval_T *const argvars, const int save_ex_normal_busy = ex_normal_busy; ex_normal_busy = 0; rettv->vval.v_string = - (char_u *)getcmdline_prompt(inputsecret_flag ? NUL : '@', p, echo_attr, + (char_u *)getcmdline_prompt(secret ? NUL : '@', p, echo_attr, xp_type, xp_arg, input_callback); ex_normal_busy = save_ex_normal_busy; callback_free(&input_callback); @@ -12172,212 +6591,14 @@ void get_user_input(const typval_T *const argvars, cmd_silent = cmd_silent_save; } -/* - * "input()" function - * Also handles inputsecret() when inputsecret is set. - */ -static void f_input(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - get_user_input(argvars, rettv, FALSE); -} - -/* - * "inputdialog()" function - */ -static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - get_user_input(argvars, rettv, TRUE); -} - -/* - * "inputlist()" function - */ -static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int selected; - int mouse_used; - - if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "inputlist()"); - return; - } - - msg_start(); - msg_row = Rows - 1; /* for when 'cmdheight' > 1 */ - lines_left = Rows; /* avoid more prompt */ - msg_scroll = TRUE; - msg_clr_eos(); - - TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { - msg_puts(tv_get_string(TV_LIST_ITEM_TV(li))); - msg_putchar('\n'); - }); - - // Ask for choice. - selected = prompt_for_number(&mouse_used); - if (mouse_used) { - selected -= lines_left; - } - - rettv->vval.v_number = selected; -} - - -static garray_T ga_userinput = {0, 0, sizeof(tasave_T), 4, NULL}; - -/// "inputrestore()" function -static void f_inputrestore(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (!GA_EMPTY(&ga_userinput)) { - ga_userinput.ga_len--; - restore_typeahead((tasave_T *)(ga_userinput.ga_data) - + ga_userinput.ga_len); - // default return is zero == OK - } else if (p_verbose > 1) { - verb_msg(_("called inputrestore() more often than inputsave()")); - rettv->vval.v_number = 1; // Failed - } -} - -/// "inputsave()" function -static void f_inputsave(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // Add an entry to the stack of typeahead storage. - tasave_T *p = GA_APPEND_VIA_PTR(tasave_T, &ga_userinput); - save_typeahead(p); -} - -/// "inputsecret()" function -static void f_inputsecret(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - cmdline_star++; - inputsecret_flag++; - f_input(argvars, rettv, NULL); - cmdline_star--; - inputsecret_flag--; -} - -/* - * "insert()" function - */ -static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - list_T *l; - bool error = false; - - if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "insert()"); - } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - N_("insert() argument"), TV_TRANSLATE)) { - long before = 0; - if (argvars[2].v_type != VAR_UNKNOWN) { - before = tv_get_number_chk(&argvars[2], &error); - } - if (error) { - // type error; errmsg already given - return; - } - - listitem_T *item = NULL; - if (before != tv_list_len(l)) { - item = tv_list_find(l, before); - if (item == NULL) { - EMSGN(_(e_listidx), before); - l = NULL; - } - } - if (l != NULL) { - tv_list_insert_tv(l, &argvars[1], item); - tv_copy(&argvars[0], rettv); - } - } -} - -/* - * "invert(expr)" function - */ -static void f_invert(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL); -} - -/* - * "isdirectory()" function - */ -static void f_isdirectory(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = os_isdir((const char_u *)tv_get_string(&argvars[0])); -} - -/* - * "islocked()" function - */ -static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - lval_T lv; - dictitem_T *di; - - rettv->vval.v_number = -1; - const char_u *const end = get_lval((char_u *)tv_get_string(&argvars[0]), - NULL, - &lv, false, false, - GLV_NO_AUTOLOAD|GLV_READ_ONLY, - FNE_CHECK_START); - if (end != NULL && lv.ll_name != NULL) { - if (*end != NUL) { - EMSG(_(e_trailing)); - } else { - if (lv.ll_tv == NULL) { - di = find_var((const char *)lv.ll_name, lv.ll_name_len, NULL, true); - if (di != NULL) { - // Consider a variable locked when: - // 1. the variable itself is locked - // 2. the value of the variable is locked. - // 3. the List or Dict value is locked. - rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK) - || tv_islocked(&di->di_tv)); - } - } else if (lv.ll_range) { - EMSG(_("E786: Range not allowed")); - } else if (lv.ll_newkey != NULL) { - EMSG2(_(e_dictkey), lv.ll_newkey); - } else if (lv.ll_list != NULL) { - // List item. - rettv->vval.v_number = tv_islocked(TV_LIST_ITEM_TV(lv.ll_li)); - } else { - // Dictionary item. - rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv); - } - } - } - - clear_lval(&lv); -} - -// "isinf()" function -static void f_isinf(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type == VAR_FLOAT - && xisinf(argvars[0].vval.v_float)) { - rettv->vval.v_number = argvars[0].vval.v_float > 0.0 ? 1 : -1; - } -} - -// "isnan()" function -static void f_isnan(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = argvars[0].v_type == VAR_FLOAT - && xisnan(argvars[0].vval.v_float); -} - /// Turn a dictionary into a list /// /// @param[in] tv Dictionary to convert. Is checked for actually being /// a dictionary, will give an error if not. /// @param[out] rettv Location where result will be saved. /// @param[in] what What to save in rettv. -static void dict_list(typval_T *const tv, typval_T *const rettv, - const DictListType what) +void dict_list(typval_T *const tv, typval_T *const rettv, + const DictListType what) { if (tv->v_type != VAR_DICT) { EMSG(_(e_dictreq)); @@ -12425,82 +6646,6 @@ static void dict_list(typval_T *const tv, typval_T *const rettv, }); } -/// "id()" function -static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr) - FUNC_ATTR_NONNULL_ALL -{ - const int len = vim_vsnprintf_typval(NULL, 0, "%p", dummy_ap, argvars); - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xmalloc(len + 1); - vim_vsnprintf_typval((char *)rettv->vval.v_string, len + 1, "%p", - dummy_ap, argvars); -} - -/* - * "items(dict)" function - */ -static void f_items(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_list(argvars, rettv, 2); -} - -// "jobpid(id)" function -static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_NUMBER) { - EMSG(_(e_invarg)); - return; - } - - Channel *data = find_job(argvars[0].vval.v_number, true); - if (!data) { - return; - } - - Process *proc = (Process *)&data->stream.proc; - rettv->vval.v_number = proc->pid; -} - -// "jobresize(job, width, height)" function -static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type != VAR_NUMBER - || argvars[2].v_type != VAR_NUMBER) { - // job id, width, height - EMSG(_(e_invarg)); - return; - } - - - Channel *data = find_job(argvars[0].vval.v_number, true); - if (!data) { - return; - } - - if (data->stream.proc.type != kProcessTypePty) { - EMSG(_(e_channotpty)); - return; - } - - pty_process_resize(&data->stream.pty, argvars[1].vval.v_number, - argvars[2].vval.v_number); - rettv->vval.v_number = 1; -} - /// Builds a process argument vector from a VimL object (typval_T). /// /// @param[in] cmd_tv VimL object @@ -12510,7 +6655,7 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// @returns Result of `shell_build_argv()` if `cmd_tv` is a String. /// Else, string values of `cmd_tv` copied to a (char **) list with /// argv[0] resolved to full path ($PATHEXT-resolved on Windows). -static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) +char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) { if (cmd_tv->v_type == VAR_STRING) { // String => "shell semantics". const char *cmd_str = tv_get_string(cmd_tv); @@ -12569,568 +6714,6 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) return argv; } -// "jobstart()" function -static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - bool executable = true; - char **argv = tv_to_argv(&argvars[0], NULL, &executable); - char **env = NULL; - if (!argv) { - rettv->vval.v_number = executable ? 0 : -1; - return; // Did error message in tv_to_argv. - } - - if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) { - // Wrong argument types - EMSG2(_(e_invarg2), "expected dictionary"); - shell_free_argv(argv); - return; - } - - - dict_T *job_opts = NULL; - bool detach = false; - bool rpc = false; - bool pty = false; - bool clear_env = false; - CallbackReader on_stdout = CALLBACK_READER_INIT, - on_stderr = CALLBACK_READER_INIT; - Callback on_exit = CALLBACK_NONE; - char *cwd = NULL; - if (argvars[1].v_type == VAR_DICT) { - job_opts = argvars[1].vval.v_dict; - - detach = tv_dict_get_number(job_opts, "detach") != 0; - rpc = tv_dict_get_number(job_opts, "rpc") != 0; - pty = tv_dict_get_number(job_opts, "pty") != 0; - clear_env = tv_dict_get_number(job_opts, "clear_env") != 0; - if (pty && rpc) { - EMSG2(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set"); - shell_free_argv(argv); - return; - } - - char *new_cwd = tv_dict_get_string(job_opts, "cwd", false); - if (new_cwd && strlen(new_cwd) > 0) { - cwd = new_cwd; - // The new cwd must be a directory. - if (!os_isdir_executable((const char *)cwd)) { - EMSG2(_(e_invarg2), "expected valid directory"); - shell_free_argv(argv); - return; - } - } - dictitem_T *job_env = tv_dict_find(job_opts, S_LEN("env")); - if (job_env) { - if (job_env->di_tv.v_type != VAR_DICT) { - EMSG2(_(e_invarg2), "env"); - shell_free_argv(argv); - return; - } - - size_t custom_env_size = (size_t)tv_dict_len(job_env->di_tv.vval.v_dict); - size_t i = 0; - size_t env_size = 0; - - if (clear_env) { - // + 1 for last null entry - env = xmalloc((custom_env_size + 1) * sizeof(*env)); - env_size = 0; - } else { - env_size = os_get_fullenv_size(); - - env = xmalloc((custom_env_size + env_size + 1) * sizeof(*env)); - - os_copy_fullenv(env, env_size); - i = env_size; - } - assert(env); // env must be allocated at this point - - TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, { - const char *str = tv_get_string(&var->di_tv); - assert(str); - size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1; - env[i] = xmalloc(len); - snprintf(env[i], len, "%s=%s", (char *)var->di_key, str); - i++; - }); - - // must be null terminated - env[env_size + custom_env_size] = NULL; - } - - - if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) { - shell_free_argv(argv); - return; - } - } - - uint16_t width = 0, height = 0; - char *term_name = NULL; - - if (pty) { - width = (uint16_t)tv_dict_get_number(job_opts, "width"); - height = (uint16_t)tv_dict_get_number(job_opts, "height"); - term_name = tv_dict_get_string(job_opts, "TERM", true); - } - - Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty, - rpc, detach, cwd, width, height, - term_name, env, &rettv->vval.v_number); - if (chan) { - channel_create_event(chan, NULL); - } -} - -// "jobstop()" function -static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_NUMBER) { - // Only argument is the job id - EMSG(_(e_invarg)); - return; - } - - Channel *data = find_job(argvars[0].vval.v_number, false); - if (!data) { - return; - } - - const char *error = NULL; - if (data->is_rpc) { - // Ignore return code, but show error later. - (void)channel_close(data->id, kChannelPartRpc, &error); - } - process_stop((Process *)&data->stream.proc); - rettv->vval.v_number = 1; - if (error) { - EMSG(error); - } -} - -// "jobwait(ids[, timeout])" function -static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - if (argvars[0].v_type != VAR_LIST || (argvars[1].v_type != VAR_NUMBER - && argvars[1].v_type != VAR_UNKNOWN)) { - EMSG(_(e_invarg)); - return; - } - - ui_busy_start(); - list_T *args = argvars[0].vval.v_list; - Channel **jobs = xcalloc(tv_list_len(args), sizeof(*jobs)); - MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop); - - // Validate, prepare jobs for waiting. - int i = 0; - TV_LIST_ITER_CONST(args, arg, { - Channel *chan = NULL; - if (TV_LIST_ITEM_TV(arg)->v_type != VAR_NUMBER - || !(chan = find_job(TV_LIST_ITEM_TV(arg)->vval.v_number, false))) { - jobs[i] = NULL; // Invalid job. - } else { - jobs[i] = chan; - channel_incref(chan); - if (chan->stream.proc.status < 0) { - // Process any pending events on the job's queue before temporarily - // replacing it. - multiqueue_process_events(chan->events); - multiqueue_replace_parent(chan->events, waiting_jobs); - } - } - i++; - }); - - int remaining = -1; - uint64_t before = 0; - if (argvars[1].v_type == VAR_NUMBER && argvars[1].vval.v_number >= 0) { - remaining = argvars[1].vval.v_number; - before = os_hrtime(); - } - - for (i = 0; i < tv_list_len(args); i++) { - if (remaining == 0) { - break; // Timeout. - } - if (jobs[i] == NULL) { - continue; // Invalid job, will assign status=-3 below. - } - int status = process_wait(&jobs[i]->stream.proc, remaining, - waiting_jobs); - if (status < 0) { - break; // Interrupted (CTRL-C) or timeout, skip remaining jobs. - } - if (remaining > 0) { - uint64_t now = os_hrtime(); - remaining = MIN(0, remaining - (int)((now - before) / 1000000)); - before = now; - } - } - - list_T *const rv = tv_list_alloc(tv_list_len(args)); - - // For each job: - // * Restore its parent queue if the job is still alive. - // * Append its status to the output list, or: - // -3 for "invalid job id" - // -2 for "interrupted" (user hit CTRL-C) - // -1 for jobs that were skipped or timed out - for (i = 0; i < tv_list_len(args); i++) { - if (jobs[i] == NULL) { - tv_list_append_number(rv, -3); - continue; - } - multiqueue_process_events(jobs[i]->events); - multiqueue_replace_parent(jobs[i]->events, main_loop.events); - - tv_list_append_number(rv, jobs[i]->stream.proc.status); - channel_decref(jobs[i]); - } - - multiqueue_free(waiting_jobs); - xfree(jobs); - ui_busy_stop(); - tv_list_ref(rv); - rettv->v_type = VAR_LIST; - rettv->vval.v_list = rv; -} - -/* - * "join()" function - */ -static void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type != VAR_LIST) { - EMSG(_(e_listreq)); - return; - } - const char *const sep = (argvars[1].v_type == VAR_UNKNOWN - ? " " - : tv_get_string_chk(&argvars[1])); - - rettv->v_type = VAR_STRING; - - if (sep != NULL) { - garray_T ga; - ga_init(&ga, (int)sizeof(char), 80); - tv_list_join(&ga, argvars[0].vval.v_list, sep); - ga_append(&ga, NUL); - rettv->vval.v_string = (char_u *)ga.ga_data; - } else { - rettv->vval.v_string = NULL; - } -} - -/// json_decode() function -static void f_json_decode(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char numbuf[NUMBUFLEN]; - const char *s = NULL; - char *tofree = NULL; - size_t len; - if (argvars[0].v_type == VAR_LIST) { - if (!encode_vim_list_to_buf(argvars[0].vval.v_list, &len, &tofree)) { - EMSG(_("E474: Failed to convert list to string")); - return; - } - s = tofree; - if (s == NULL) { - assert(len == 0); - s = ""; - } - } else { - s = tv_get_string_buf_chk(&argvars[0], numbuf); - if (s) { - len = strlen(s); - } else { - return; - } - } - if (json_decode_string(s, len, rettv) == FAIL) { - emsgf(_("E474: Failed to parse %.*s"), (int)len, s); - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - } - assert(rettv->v_type != VAR_UNKNOWN); - xfree(tofree); -} - -/// json_encode() function -static void f_json_encode(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *) encode_tv2json(&argvars[0], NULL); -} - -/* - * "keys()" function - */ -static void f_keys(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_list(argvars, rettv, 0); -} - -/* - * "last_buffer_nr()" function. - */ -static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int n = 0; - - FOR_ALL_BUFFERS(buf) { - if (n < buf->b_fnum) { - n = buf->b_fnum; - } - } - - rettv->vval.v_number = n; -} - -/* - * "len()" function - */ -static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - switch (argvars[0].v_type) { - case VAR_STRING: - case VAR_NUMBER: { - rettv->vval.v_number = (varnumber_T)strlen( - tv_get_string(&argvars[0])); - break; - } - case VAR_LIST: { - rettv->vval.v_number = tv_list_len(argvars[0].vval.v_list); - break; - } - case VAR_DICT: { - rettv->vval.v_number = tv_dict_len(argvars[0].vval.v_dict); - break; - } - case VAR_UNKNOWN: - case VAR_SPECIAL: - case VAR_FLOAT: - case VAR_PARTIAL: - case VAR_FUNC: { - EMSG(_("E701: Invalid type for len()")); - break; - } - } -} - -static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type) -{ - rettv->v_type = out_type; - if (out_type != VAR_NUMBER) { - rettv->vval.v_string = NULL; - } - - if (check_restricted() || check_secure()) { - return; - } - - // The first two args (libname and funcname) must be strings - if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) { - return; - } - - const char *libname = (char *) argvars[0].vval.v_string; - const char *funcname = (char *) argvars[1].vval.v_string; - - VarType in_type = argvars[2].v_type; - - // input variables - char *str_in = (in_type == VAR_STRING) - ? (char *) argvars[2].vval.v_string : NULL; - int64_t int_in = argvars[2].vval.v_number; - - // output variables - char **str_out = (out_type == VAR_STRING) - ? (char **)&rettv->vval.v_string : NULL; - int int_out = 0; - - bool success = os_libcall(libname, funcname, - str_in, int_in, - str_out, &int_out); - - if (!success) { - EMSG2(_(e_libcall), funcname); - return; - } - - if (out_type == VAR_NUMBER) { - rettv->vval.v_number = (varnumber_T)int_out; - } -} - -/* - * "libcall()" function - */ -static void f_libcall(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - libcall_common(argvars, rettv, VAR_STRING); -} - -/* - * "libcallnr()" function - */ -static void f_libcallnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - libcall_common(argvars, rettv, VAR_NUMBER); -} - -/* - * "line(string)" function - */ -static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T lnum = 0; - pos_T *fp; - int fnum; - - fp = var2fpos(&argvars[0], TRUE, &fnum); - if (fp != NULL) - lnum = fp->lnum; - rettv->vval.v_number = lnum; -} - -/* - * "line2byte(lnum)" function - */ -static void f_line2byte(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) { - rettv->vval.v_number = -1; - } else { - rettv->vval.v_number = ml_find_line_or_offset(curbuf, lnum, NULL, false); - } - if (rettv->vval.v_number >= 0) { - rettv->vval.v_number++; - } -} - -/* - * "lispindent(lnum)" function - */ -static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const pos_T pos = curwin->w_cursor; - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - curwin->w_cursor.lnum = lnum; - rettv->vval.v_number = get_lisp_indent(); - curwin->w_cursor = pos; - } else { - rettv->vval.v_number = -1; - } -} - -/* - * "localtime()" function - */ -static void f_localtime(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = (varnumber_T)time(NULL); -} - - -static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) -{ - char_u *keys_buf = NULL; - char_u *rhs; - int mode; - int abbr = FALSE; - int get_dict = FALSE; - mapblock_T *mp; - int buffer_local; - - // Return empty string for failure. - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - char_u *keys = (char_u *)tv_get_string(&argvars[0]); - if (*keys == NUL) { - return; - } - - char buf[NUMBUFLEN]; - const char *which; - if (argvars[1].v_type != VAR_UNKNOWN) { - which = tv_get_string_buf_chk(&argvars[1], buf); - if (argvars[2].v_type != VAR_UNKNOWN) { - abbr = tv_get_number(&argvars[2]); - if (argvars[3].v_type != VAR_UNKNOWN) { - get_dict = tv_get_number(&argvars[3]); - } - } - } else { - which = ""; - } - if (which == NULL) { - return; - } - - mode = get_map_mode((char_u **)&which, 0); - - keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, true, - CPO_TO_CPO_FLAGS); - rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local); - xfree(keys_buf); - - if (!get_dict) { - // Return a string. - if (rhs != NULL) { - if (*rhs == NUL) { - rettv->vval.v_string = vim_strsave((char_u *)"<Nop>"); - } else { - rettv->vval.v_string = (char_u *)str2special_save( - (char *)rhs, false, false); - } - } - - } else { - tv_dict_alloc_ret(rettv); - if (rhs != NULL) { - // Return a dictionary. - mapblock_fill_dict(rettv->vval.v_dict, mp, buffer_local, true); - } - } -} - -/// luaeval() function implementation -static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) - FUNC_ATTR_NONNULL_ALL -{ - const char *const str = (const char *)tv_get_string_chk(&argvars[0]); - if (str == NULL) { - return; - } - - executor_eval_lua(cstr_as_string((char *)str), &argvars[1], rettv); -} - /// Fill a dictionary with all applicable maparg() like dictionaries /// /// @param dict The dictionary to be filled @@ -13176,260 +6759,8 @@ void mapblock_fill_dict(dict_T *const dict, tv_dict_add_allocated_str(dict, S_LEN("mode"), mapmode); } -/* - * "map()" function - */ -static void f_map(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - filter_map(argvars, rettv, TRUE); -} - -/* - * "maparg()" function - */ -static void f_maparg(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - get_maparg(argvars, rettv, TRUE); -} - -/* - * "mapcheck()" function - */ -static void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - get_maparg(argvars, rettv, FALSE); -} - - -static void find_some_match(typval_T *const argvars, typval_T *const rettv, - const SomeMatchType type) -{ - char_u *str = NULL; - long len = 0; - char_u *expr = NULL; - regmatch_T regmatch; - char_u *save_cpo; - long start = 0; - long nth = 1; - colnr_T startcol = 0; - bool match = false; - list_T *l = NULL; - listitem_T *li = NULL; - long idx = 0; - char_u *tofree = NULL; - - /* Make 'cpoptions' empty, the 'l' flag should not be used here. */ - save_cpo = p_cpo; - p_cpo = (char_u *)""; - - rettv->vval.v_number = -1; - switch (type) { - // matchlist(): return empty list when there are no matches. - case kSomeMatchList: { - tv_list_alloc_ret(rettv, kListLenMayKnow); - break; - } - // matchstrpos(): return ["", -1, -1, -1] - case kSomeMatchStrPos: { - tv_list_alloc_ret(rettv, 4); - tv_list_append_string(rettv->vval.v_list, "", 0); - tv_list_append_number(rettv->vval.v_list, -1); - tv_list_append_number(rettv->vval.v_list, -1); - tv_list_append_number(rettv->vval.v_list, -1); - break; - } - case kSomeMatchStr: { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - break; - } - case kSomeMatch: - case kSomeMatchEnd: { - // Do nothing: zero is default. - break; - } - } - - if (argvars[0].v_type == VAR_LIST) { - if ((l = argvars[0].vval.v_list) == NULL) { - goto theend; - } - li = tv_list_first(l); - } else { - expr = str = (char_u *)tv_get_string(&argvars[0]); - len = (long)STRLEN(str); - } - - char patbuf[NUMBUFLEN]; - const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf); - if (pat == NULL) { - goto theend; - } - - if (argvars[2].v_type != VAR_UNKNOWN) { - bool error = false; - - start = tv_get_number_chk(&argvars[2], &error); - if (error) { - goto theend; - } - if (l != NULL) { - idx = tv_list_uidx(l, start); - if (idx == -1) { - goto theend; - } - li = tv_list_find(l, idx); - } else { - if (start < 0) - start = 0; - if (start > len) - goto theend; - /* When "count" argument is there ignore matches before "start", - * otherwise skip part of the string. Differs when pattern is "^" - * or "\<". */ - if (argvars[3].v_type != VAR_UNKNOWN) - startcol = start; - else { - str += start; - len -= start; - } - } - - if (argvars[3].v_type != VAR_UNKNOWN) { - nth = tv_get_number_chk(&argvars[3], &error); - } - if (error) { - goto theend; - } - } - - regmatch.regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING); - if (regmatch.regprog != NULL) { - regmatch.rm_ic = p_ic; - - for (;; ) { - if (l != NULL) { - if (li == NULL) { - match = false; - break; - } - xfree(tofree); - tofree = expr = str = (char_u *)encode_tv2echo(TV_LIST_ITEM_TV(li), - NULL); - if (str == NULL) { - break; - } - } - - match = vim_regexec_nl(®match, str, (colnr_T)startcol); - - if (match && --nth <= 0) - break; - if (l == NULL && !match) - break; - - /* Advance to just after the match. */ - if (l != NULL) { - li = TV_LIST_ITEM_NEXT(l, li); - idx++; - } else { - startcol = (colnr_T)(regmatch.startp[0] - + (*mb_ptr2len)(regmatch.startp[0]) - str); - if (startcol > (colnr_T)len || str + startcol <= regmatch.startp[0]) { - match = false; - break; - } - } - } - - if (match) { - switch (type) { - case kSomeMatchStrPos: { - list_T *const ret_l = rettv->vval.v_list; - listitem_T *li1 = tv_list_first(ret_l); - listitem_T *li2 = TV_LIST_ITEM_NEXT(ret_l, li1); - listitem_T *li3 = TV_LIST_ITEM_NEXT(ret_l, li2); - listitem_T *li4 = TV_LIST_ITEM_NEXT(ret_l, li3); - xfree(TV_LIST_ITEM_TV(li1)->vval.v_string); - - const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]); - TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz( - (const char *)regmatch.startp[0], rd); - TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)( - regmatch.startp[0] - expr); - TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)( - regmatch.endp[0] - expr); - if (l != NULL) { - TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx; - } - break; - } - case kSomeMatchList: { - // Return list with matched string and submatches. - for (int i = 0; i < NSUBEXP; i++) { - if (regmatch.endp[i] == NULL) { - tv_list_append_string(rettv->vval.v_list, NULL, 0); - } else { - tv_list_append_string(rettv->vval.v_list, - (const char *)regmatch.startp[i], - (regmatch.endp[i] - regmatch.startp[i])); - } - } - break; - } - case kSomeMatchStr: { - // Return matched string. - if (l != NULL) { - tv_copy(TV_LIST_ITEM_TV(li), rettv); - } else { - rettv->vval.v_string = (char_u *)xmemdupz( - (const char *)regmatch.startp[0], - (size_t)(regmatch.endp[0] - regmatch.startp[0])); - } - break; - } - case kSomeMatch: - case kSomeMatchEnd: { - if (l != NULL) { - rettv->vval.v_number = idx; - } else { - if (type == kSomeMatch) { - rettv->vval.v_number = - (varnumber_T)(regmatch.startp[0] - str); - } else { - rettv->vval.v_number = - (varnumber_T)(regmatch.endp[0] - str); - } - rettv->vval.v_number += (varnumber_T)(str - expr); - } - break; - } - } - } - vim_regfree(regmatch.regprog); - } - -theend: - if (type == kSomeMatchStrPos && l == NULL && rettv->vval.v_list != NULL) { - // matchstrpos() without a list: drop the second item - list_T *const ret_l = rettv->vval.v_list; - tv_list_item_remove(ret_l, TV_LIST_ITEM_NEXT(ret_l, tv_list_first(ret_l))); - } - - xfree(tofree); - p_cpo = save_cpo; -} - -/* - * "match()" function - */ -static void f_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - find_some_match(argvars, rettv, kSomeMatch); -} - -static int matchadd_dict_arg(typval_T *tv, const char **conceal_char, - win_T **win) +int matchadd_dict_arg(typval_T *tv, const char **conceal_char, + win_T **win) { dictitem_T *di; @@ -13453,766 +6784,7 @@ static int matchadd_dict_arg(typval_T *tv, const char **conceal_char, return OK; } -/* - * "matchadd()" function - */ -static void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char grpbuf[NUMBUFLEN]; - char patbuf[NUMBUFLEN]; - const char *const grp = tv_get_string_buf_chk(&argvars[0], grpbuf); - const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf); - int prio = 10; - int id = -1; - bool error = false; - const char *conceal_char = NULL; - win_T *win = curwin; - - rettv->vval.v_number = -1; - - if (grp == NULL || pat == NULL) { - return; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - prio = tv_get_number_chk(&argvars[2], &error); - if (argvars[3].v_type != VAR_UNKNOWN) { - id = tv_get_number_chk(&argvars[3], &error); - if (argvars[4].v_type != VAR_UNKNOWN - && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) { - return; - } - } - } - if (error) { - return; - } - if (id >= 1 && id <= 3) { - EMSGN(_("E798: ID is reserved for \":match\": %" PRId64), id); - return; - } - - rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL, conceal_char); -} - -static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; - - char buf[NUMBUFLEN]; - const char *const group = tv_get_string_buf_chk(&argvars[0], buf); - if (group == NULL) { - return; - } - - if (argvars[1].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "matchaddpos()"); - return; - } - - list_T *l; - l = argvars[1].vval.v_list; - if (l == NULL) { - return; - } - - bool error = false; - int prio = 10; - int id = -1; - const char *conceal_char = NULL; - win_T *win = curwin; - - if (argvars[2].v_type != VAR_UNKNOWN) { - prio = tv_get_number_chk(&argvars[2], &error); - if (argvars[3].v_type != VAR_UNKNOWN) { - id = tv_get_number_chk(&argvars[3], &error); - if (argvars[4].v_type != VAR_UNKNOWN - && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) { - return; - } - } - } - if (error == true) { - return; - } - - // id == 3 is ok because matchaddpos() is supposed to substitute :3match - if (id == 1 || id == 2) { - EMSGN(_("E798: ID is reserved for \"match\": %" PRId64), id); - return; - } - - rettv->vval.v_number = match_add(win, group, NULL, prio, id, l, conceal_char); -} - -/* - * "matcharg()" function - */ -static void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const int id = tv_get_number(&argvars[0]); - - tv_list_alloc_ret(rettv, (id >= 1 && id <= 3 - ? 2 - : 0)); - - if (id >= 1 && id <= 3) { - matchitem_T *const m = (matchitem_T *)get_match(curwin, id); - - if (m != NULL) { - tv_list_append_string(rettv->vval.v_list, - (const char *)syn_id2name(m->hlg_id), -1); - tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1); - } else { - tv_list_append_string(rettv->vval.v_list, NULL, 0); - tv_list_append_string(rettv->vval.v_list, NULL, 0); - } - } -} - -/* - * "matchdelete()" function - */ -static void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = match_delete(curwin, - (int)tv_get_number(&argvars[0]), true); -} - -/* - * "matchend()" function - */ -static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - find_some_match(argvars, rettv, kSomeMatchEnd); -} - -/* - * "matchlist()" function - */ -static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - find_some_match(argvars, rettv, kSomeMatchList); -} - -/* - * "matchstr()" function - */ -static void f_matchstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - find_some_match(argvars, rettv, kSomeMatchStr); -} - -/// "matchstrpos()" function -static void f_matchstrpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - find_some_match(argvars, rettv, kSomeMatchStrPos); -} - -/// Get maximal/minimal number value in a list or dictionary -/// -/// @param[in] tv List or dictionary to work with. If it contains something -/// that is not an integer number (or cannot be coerced to -/// it) error is given. -/// @param[out] rettv Location where result will be saved. Only assigns -/// vval.v_number, type is not touched. Returns zero for -/// empty lists/dictionaries. -/// @param[in] domax Determines whether maximal or minimal value is desired. -static void max_min(const typval_T *const tv, typval_T *const rettv, - const bool domax) - FUNC_ATTR_NONNULL_ALL -{ - bool error = false; - - rettv->vval.v_number = 0; - varnumber_T n = (domax ? VARNUMBER_MIN : VARNUMBER_MAX); - if (tv->v_type == VAR_LIST) { - if (tv_list_len(tv->vval.v_list) == 0) { - return; - } - TV_LIST_ITER_CONST(tv->vval.v_list, li, { - const varnumber_T i = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error); - if (error) { - return; - } - if (domax ? i > n : i < n) { - n = i; - } - }); - } else if (tv->v_type == VAR_DICT) { - if (tv_dict_len(tv->vval.v_dict) == 0) { - return; - } - TV_DICT_ITER(tv->vval.v_dict, di, { - const varnumber_T i = tv_get_number_chk(&di->di_tv, &error); - if (error) { - return; - } - if (domax ? i > n : i < n) { - n = i; - } - }); - } else { - EMSG2(_(e_listdictarg), domax ? "max()" : "min()"); - return; - } - rettv->vval.v_number = n; -} - -/* - * "max()" function - */ -static void f_max(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - max_min(argvars, rettv, TRUE); -} - -/* - * "min()" function - */ -static void f_min(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - max_min(argvars, rettv, FALSE); -} - -/* - * "mkdir()" function - */ -static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int prot = 0755; // -V536 - - rettv->vval.v_number = FAIL; - if (check_restricted() || check_secure()) - return; - - char buf[NUMBUFLEN]; - const char *const dir = tv_get_string_buf(&argvars[0], buf); - if (*dir == NUL) { - return; - } - - if (*path_tail((char_u *)dir) == NUL) { - // Remove trailing slashes. - *path_tail_with_sep((char_u *)dir) = NUL; - } - - if (argvars[1].v_type != VAR_UNKNOWN) { - if (argvars[2].v_type != VAR_UNKNOWN) { - prot = tv_get_number_chk(&argvars[2], NULL); - if (prot == -1) { - return; - } - } - if (strcmp(tv_get_string(&argvars[1]), "p") == 0) { - char *failed_dir; - int ret = os_mkdir_recurse(dir, prot, &failed_dir); - if (ret != 0) { - EMSG3(_(e_mkdir), failed_dir, os_strerror(ret)); - xfree(failed_dir); - rettv->vval.v_number = FAIL; - return; - } else { - rettv->vval.v_number = OK; - return; - } - } - } - rettv->vval.v_number = vim_mkdir_emsg(dir, prot); -} - -/// "mode()" function -static void f_mode(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char *mode = get_mode(); - - // Clear out the minor mode when the argument is not a non-zero number or - // non-empty string. - if (!non_zero_arg(&argvars[0])) { - mode[1] = NUL; - } - - rettv->vval.v_string = (char_u *)mode; - rettv->v_type = VAR_STRING; -} - -/// "msgpackdump()" function -static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr) - FUNC_ATTR_NONNULL_ALL -{ - if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "msgpackdump()"); - return; - } - list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); - list_T *const list = argvars[0].vval.v_list; - msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write); - const char *const msg = _("msgpackdump() argument, index %i"); - // Assume that translation will not take more then 4 times more space - char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN]; - int idx = 0; - TV_LIST_ITER(list, li, { - vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx); - idx++; - if (encode_vim_to_msgpack(lpacker, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) { - break; - } - }); - msgpack_packer_free(lpacker); -} - -/// "msgpackparse" function -static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) - FUNC_ATTR_NONNULL_ALL -{ - if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "msgpackparse()"); - return; - } - list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); - const list_T *const list = argvars[0].vval.v_list; - if (tv_list_len(list) == 0) { - return; - } - if (TV_LIST_ITEM_TV(tv_list_first(list))->v_type != VAR_STRING) { - EMSG2(_(e_invarg2), "List item is not a string"); - return; - } - ListReaderState lrstate = encode_init_lrstate(list); - msgpack_unpacker *const unpacker = msgpack_unpacker_new(IOSIZE); - if (unpacker == NULL) { - EMSG(_(e_outofmem)); - return; - } - msgpack_unpacked unpacked; - msgpack_unpacked_init(&unpacked); - do { - if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) { - EMSG(_(e_outofmem)); - goto f_msgpackparse_exit; - } - size_t read_bytes; - const int rlret = encode_read_from_list( - &lrstate, msgpack_unpacker_buffer(unpacker), IOSIZE, &read_bytes); - if (rlret == FAIL) { - EMSG2(_(e_invarg2), "List item is not a string"); - goto f_msgpackparse_exit; - } - msgpack_unpacker_buffer_consumed(unpacker, read_bytes); - if (read_bytes == 0) { - break; - } - while (unpacker->off < unpacker->used) { - const msgpack_unpack_return result = msgpack_unpacker_next(unpacker, - &unpacked); - if (result == MSGPACK_UNPACK_PARSE_ERROR) { - EMSG2(_(e_invarg2), "Failed to parse msgpack string"); - goto f_msgpackparse_exit; - } - if (result == MSGPACK_UNPACK_NOMEM_ERROR) { - EMSG(_(e_outofmem)); - goto f_msgpackparse_exit; - } - if (result == MSGPACK_UNPACK_SUCCESS) { - typval_T tv = { .v_type = VAR_UNKNOWN }; - if (msgpack_to_vim(unpacked.data, &tv) == FAIL) { - EMSG2(_(e_invarg2), "Failed to convert msgpack string"); - goto f_msgpackparse_exit; - } - tv_list_append_owned_tv(ret_list, tv); - } - if (result == MSGPACK_UNPACK_CONTINUE) { - if (rlret == OK) { - EMSG2(_(e_invarg2), "Incomplete msgpack string"); - } - break; - } - } - if (rlret == OK) { - break; - } - } while (true); - -f_msgpackparse_exit: - msgpack_unpacked_destroy(&unpacked); - msgpack_unpacker_free(unpacker); - return; -} - -/* - * "nextnonblank()" function - */ -static void f_nextnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T lnum; - - for (lnum = tv_get_lnum(argvars);; lnum++) { - if (lnum < 0 || lnum > curbuf->b_ml.ml_line_count) { - lnum = 0; - break; - } - if (*skipwhite(ml_get(lnum)) != NUL) { - break; - } - } - rettv->vval.v_number = lnum; -} - -/* - * "nr2char()" function - */ -static void f_nr2char(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[1].v_type != VAR_UNKNOWN) { - if (!tv_check_num(&argvars[1])) { - return; - } - } - - bool error = false; - const varnumber_T num = tv_get_number_chk(&argvars[0], &error); - if (error) { - return; - } - if (num < 0) { - EMSG(_("E5070: Character number must not be less than zero")); - return; - } - if (num > INT_MAX) { - emsgf(_("E5071: Character number must not be greater than INT_MAX (%i)"), - INT_MAX); - return; - } - - char buf[MB_MAXBYTES]; - const int len = utf_char2bytes((int)num, (char_u *)buf); - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xmemdupz(buf, (size_t)len); -} - -/* - * "or(expr, expr)" function - */ -static void f_or(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) - | tv_get_number_chk(&argvars[1], NULL); -} - -/* - * "pathshorten()" function - */ -static void f_pathshorten(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - const char *const s = tv_get_string_chk(&argvars[0]); - if (!s) { - return; - } - rettv->vval.v_string = shorten_dir((char_u *)xstrdup(s)); -} - -/* - * "pow()" function - */ -static void f_pow(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - float_T fx; - float_T fy; - - rettv->v_type = VAR_FLOAT; - if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) { - rettv->vval.v_float = pow(fx, fy); - } else { - rettv->vval.v_float = 0.0; - } -} - -/* - * "prevnonblank()" function - */ -static void f_prevnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T lnum = tv_get_lnum(argvars); - if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) { - lnum = 0; - } else { - while (lnum >= 1 && *skipwhite(ml_get(lnum)) == NUL) { - lnum--; - } - } - rettv->vval.v_number = lnum; -} - -/* - * "printf()" function - */ -static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - { - int len; - int saved_did_emsg = did_emsg; - - // Get the required length, allocate the buffer and do it for real. - did_emsg = false; - char buf[NUMBUFLEN]; - const char *fmt = tv_get_string_buf(&argvars[0], buf); - len = vim_vsnprintf_typval(NULL, 0, fmt, dummy_ap, argvars + 1); - if (!did_emsg) { - char *s = xmalloc(len + 1); - rettv->vval.v_string = (char_u *)s; - (void)vim_vsnprintf_typval(s, len + 1, fmt, dummy_ap, argvars + 1); - } - did_emsg |= saved_did_emsg; - } -} - -// "pum_getpos()" function -static void f_pum_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_dict_alloc_ret(rettv); - pum_set_event_info(rettv->vval.v_dict); -} - -/* - * "pumvisible()" function - */ -static void f_pumvisible(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (pum_visible()) - rettv->vval.v_number = 1; -} - -/* - * "pyeval()" function - */ -static void f_pyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - script_host_eval("python", argvars, rettv); -} - -/* - * "py3eval()" function - */ -static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - script_host_eval("python3", argvars, rettv); -} - -// "pyxeval()" function -static void f_pyxeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - init_pyxversion(); - if (p_pyx == 2) { - f_pyeval(argvars, rettv, NULL); - } else { - f_py3eval(argvars, rettv, NULL); - } -} - -/* - * "range()" function - */ -static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - varnumber_T start; - varnumber_T end; - varnumber_T stride = 1; - varnumber_T i; - bool error = false; - - start = tv_get_number_chk(&argvars[0], &error); - if (argvars[1].v_type == VAR_UNKNOWN) { - end = start - 1; - start = 0; - } else { - end = tv_get_number_chk(&argvars[1], &error); - if (argvars[2].v_type != VAR_UNKNOWN) { - stride = tv_get_number_chk(&argvars[2], &error); - } - } - - if (error) { - return; // Type error; errmsg already given. - } - if (stride == 0) { - EMSG(_("E726: Stride is zero")); - } else if (stride > 0 ? end + 1 < start : end - 1 > start) { - EMSG(_("E727: Start past end")); - } else { - tv_list_alloc_ret(rettv, (end - start) / stride); - for (i = start; stride > 0 ? i <= end : i >= end; i += stride) { - tv_list_append_number(rettv->vval.v_list, (varnumber_T)i); - } - } -} - -/* - * "readfile()" function - */ -static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - bool binary = false; - FILE *fd; - char_u buf[(IOSIZE/256)*256]; /* rounded to avoid odd + 1 */ - int io_size = sizeof(buf); - int readlen; /* size of last fread() */ - char_u *prev = NULL; /* previously read bytes, if any */ - long prevlen = 0; /* length of data in prev */ - long prevsize = 0; /* size of prev buffer */ - long maxline = MAXLNUM; - - if (argvars[1].v_type != VAR_UNKNOWN) { - if (strcmp(tv_get_string(&argvars[1]), "b") == 0) { - binary = true; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - maxline = tv_get_number(&argvars[2]); - } - } - - list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown); - - // Always open the file in binary mode, library functions have a mind of - // their own about CR-LF conversion. - const char *const fname = tv_get_string(&argvars[0]); - if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) { - EMSG2(_(e_notopen), *fname == NUL ? _("<empty>") : fname); - return; - } - - while (maxline < 0 || tv_list_len(l) < maxline) { - readlen = (int)fread(buf, 1, io_size, fd); - - // This for loop processes what was read, but is also entered at end - // of file so that either: - // - an incomplete line gets written - // - a "binary" file gets an empty line at the end if it ends in a - // newline. - char_u *p; // Position in buf. - char_u *start; // Start of current line. - for (p = buf, start = buf; - p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); - p++) { - if (*p == '\n' || readlen <= 0) { - char_u *s = NULL; - size_t len = p - start; - - /* Finished a line. Remove CRs before NL. */ - if (readlen > 0 && !binary) { - while (len > 0 && start[len - 1] == '\r') - --len; - /* removal may cross back to the "prev" string */ - if (len == 0) - while (prevlen > 0 && prev[prevlen - 1] == '\r') - --prevlen; - } - if (prevlen == 0) { - assert(len < INT_MAX); - s = vim_strnsave(start, (int)len); - } else { - /* Change "prev" buffer to be the right size. This way - * the bytes are only copied once, and very long lines are - * allocated only once. */ - s = xrealloc(prev, prevlen + len + 1); - memcpy(s + prevlen, start, len); - s[prevlen + len] = NUL; - prev = NULL; /* the list will own the string */ - prevlen = prevsize = 0; - } - - tv_list_append_owned_tv(l, (typval_T) { - .v_type = VAR_STRING, - .v_lock = VAR_UNLOCKED, - .vval.v_string = s, - }); - - start = p + 1; // Step over newline. - if (maxline < 0) { - if (tv_list_len(l) > -maxline) { - assert(tv_list_len(l) == 1 + (-maxline)); - tv_list_item_remove(l, tv_list_first(l)); - } - } else if (tv_list_len(l) >= maxline) { - assert(tv_list_len(l) == maxline); - break; - } - if (readlen <= 0) { - break; - } - } else if (*p == NUL) { - *p = '\n'; - // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this - // when finding the BF and check the previous two bytes. - } else if (*p == 0xbf && !binary) { - // Find the two bytes before the 0xbf. If p is at buf, or buf + 1, - // these may be in the "prev" string. - char_u back1 = p >= buf + 1 ? p[-1] - : prevlen >= 1 ? prev[prevlen - 1] : NUL; - char_u back2 = p >= buf + 2 ? p[-2] - : p == buf + 1 && prevlen >= 1 ? prev[prevlen - 1] - : prevlen >= 2 ? prev[prevlen - 2] : NUL; - - if (back2 == 0xef && back1 == 0xbb) { - char_u *dest = p - 2; - - /* Usually a BOM is at the beginning of a file, and so at - * the beginning of a line; then we can just step over it. - */ - if (start == dest) - start = p + 1; - else { - /* have to shuffle buf to close gap */ - int adjust_prevlen = 0; - - if (dest < buf) { // -V782 - adjust_prevlen = (int)(buf - dest); // -V782 - // adjust_prevlen must be 1 or 2. - dest = buf; - } - if (readlen > p - buf + 1) - memmove(dest, p + 1, readlen - (p - buf) - 1); - readlen -= 3 - adjust_prevlen; - prevlen -= adjust_prevlen; - p = dest - 1; - } - } - } - } /* for */ - - if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) { - break; - } - if (start < p) { - /* There's part of a line in buf, store it in "prev". */ - if (p - start + prevlen >= prevsize) { - - /* A common use case is ordinary text files and "prev" gets a - * fragment of a line, so the first allocation is made - * small, to avoid repeatedly 'allocing' large and - * 'reallocing' small. */ - if (prevsize == 0) - prevsize = (long)(p - start); - else { - long grow50pc = (prevsize * 3) / 2; - long growmin = (long)((p - start) * 2 + prevlen); - prevsize = grow50pc > growmin ? grow50pc : growmin; - } - prev = xrealloc(prev, prevsize); - } - /* Add the line part to end of "prev". */ - memmove(prev + prevlen, start, p - start); - prevlen += (long)(p - start); - } - } /* while */ - - xfree(prev); - fclose(fd); -} - -static void return_register(int regname, typval_T *rettv) +void return_register(int regname, typval_T *rettv) { char_u buf[2] = { regname, 0 }; @@ -14220,810 +6792,7 @@ static void return_register(int regname, typval_T *rettv) rettv->vval.v_string = vim_strsave(buf); } -// "reg_executing()" function -static void f_reg_executing(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - return_register(reg_executing, rettv); -} - -// "reg_recording()" function -static void f_reg_recording(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - return_register(reg_recording, rettv); -} - -/// list2proftime - convert a List to proftime_T -/// -/// @param arg The input list, must be of type VAR_LIST and have -/// exactly 2 items -/// @param[out] tm The proftime_T representation of `arg` -/// @return OK In case of success, FAIL in case of error -static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL -{ - if (arg->v_type != VAR_LIST || tv_list_len(arg->vval.v_list) != 2) { - return FAIL; - } - - bool error = false; - varnumber_T n1 = tv_list_find_nr(arg->vval.v_list, 0L, &error); - varnumber_T n2 = tv_list_find_nr(arg->vval.v_list, 1L, &error); - if (error) { - return FAIL; - } - - // in f_reltime() we split up the 64-bit proftime_T into two 32-bit - // values, now we combine them again. - union { - struct { int32_t low, high; } split; - proftime_T prof; - } u = { .split.high = n1, .split.low = n2 }; - - *tm = u.prof; - - return OK; -} - -/// f_reltime - return an item that represents a time value -/// -/// @param[out] rettv Without an argument it returns the current time. With -/// one argument it returns the time passed since the argument. -/// With two arguments it returns the time passed between -/// the two arguments. -static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - proftime_T res; - proftime_T start; - - if (argvars[0].v_type == VAR_UNKNOWN) { - // no arguments: get current time. - res = profile_start(); - } else if (argvars[1].v_type == VAR_UNKNOWN) { - if (list2proftime(&argvars[0], &res) == FAIL) { - return; - } - res = profile_end(res); - } else { - // two arguments: compute the difference. - if (list2proftime(&argvars[0], &start) == FAIL - || list2proftime(&argvars[1], &res) == FAIL) { - return; - } - res = profile_sub(res, start); - } - - // we have to store the 64-bit proftime_T inside of a list of int's - // (varnumber_T is defined as int). For all our supported platforms, int's - // are at least 32-bits wide. So we'll use two 32-bit values to store it. - union { - struct { int32_t low, high; } split; - proftime_T prof; - } u = { .prof = res }; - - // statically assert that the union type conv will provide the correct - // results, if varnumber_T or proftime_T change, the union cast will need - // to be revised. - STATIC_ASSERT(sizeof(u.prof) == sizeof(u) && sizeof(u.split) == sizeof(u), - "type punning will produce incorrect results on this platform"); - - tv_list_alloc_ret(rettv, 2); - tv_list_append_number(rettv->vval.v_list, u.split.high); - tv_list_append_number(rettv->vval.v_list, u.split.low); -} - -/// "reltimestr()" function -static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr) - FUNC_ATTR_NONNULL_ALL -{ - proftime_T tm; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (list2proftime(&argvars[0], &tm) == OK) { - rettv->vval.v_string = (char_u *)xstrdup(profile_msg(tm)); - } -} - -/* - * "remove()" function - */ -static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - list_T *l; - listitem_T *item, *item2; - listitem_T *li; - long idx; - long end; - dict_T *d; - dictitem_T *di; - const char *const arg_errmsg = N_("remove() argument"); - - if (argvars[0].v_type == VAR_DICT) { - if (argvars[2].v_type != VAR_UNKNOWN) { - EMSG2(_(e_toomanyarg), "remove()"); - } else if ((d = argvars[0].vval.v_dict) != NULL - && !tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) { - const char *key = tv_get_string_chk(&argvars[1]); - if (key != NULL) { - di = tv_dict_find(d, key, -1); - if (di == NULL) { - EMSG2(_(e_dictkey), key); - } else if (!var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE) - && !var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) { - *rettv = di->di_tv; - di->di_tv = TV_INITIAL_VALUE; - tv_dict_item_remove(d, di); - if (tv_dict_is_watched(d)) { - tv_dict_watcher_notify(d, key, NULL, rettv); - } - } - } - } - } else if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listdictarg), "remove()"); - } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - arg_errmsg, TV_TRANSLATE)) { - bool error = false; - - idx = tv_get_number_chk(&argvars[1], &error); - if (error) { - // Type error: do nothing, errmsg already given. - } else if ((item = tv_list_find(l, idx)) == NULL) { - EMSGN(_(e_listidx), idx); - } else { - if (argvars[2].v_type == VAR_UNKNOWN) { - // Remove one item, return its value. - tv_list_drop_items(l, item, item); - *rettv = *TV_LIST_ITEM_TV(item); - xfree(item); - } else { - // Remove range of items, return list with values. - end = tv_get_number_chk(&argvars[2], &error); - if (error) { - // Type error: do nothing. - } else if ((item2 = tv_list_find(l, end)) == NULL) { - EMSGN(_(e_listidx), end); - } else { - int cnt = 0; - - for (li = item; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { - cnt++; - if (li == item2) { - break; - } - } - if (li == NULL) { // Didn't find "item2" after "item". - EMSG(_(e_invrange)); - } else { - tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv, cnt), - cnt); - } - } - } - } - } -} - -/* - * "rename({from}, {to})" function - */ -static void f_rename(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (check_restricted() || check_secure()) { - rettv->vval.v_number = -1; - } else { - char buf[NUMBUFLEN]; - rettv->vval.v_number = vim_rename( - (const char_u *)tv_get_string(&argvars[0]), - (const char_u *)tv_get_string_buf(&argvars[1], buf)); - } -} - -/* - * "repeat()" function - */ -static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - varnumber_T n = tv_get_number(&argvars[1]); - if (argvars[0].v_type == VAR_LIST) { - tv_list_alloc_ret(rettv, (n > 0) * n * tv_list_len(argvars[0].vval.v_list)); - while (n-- > 0) { - tv_list_extend(rettv->vval.v_list, argvars[0].vval.v_list, NULL); - } - } else { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (n <= 0) { - return; - } - - const char *const p = tv_get_string(&argvars[0]); - - const size_t slen = strlen(p); - if (slen == 0) { - return; - } - const size_t len = slen * n; - // Detect overflow. - if (len / n != slen) { - return; - } - - char *const r = xmallocz(len); - for (varnumber_T i = 0; i < n; i++) { - memmove(r + i * slen, p, slen); - } - - rettv->vval.v_string = (char_u *)r; - } -} - -/* - * "resolve()" function - */ -static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - const char *fname = tv_get_string(&argvars[0]); -#ifdef WIN32 - char *const v = os_resolve_shortcut(fname); - rettv->vval.v_string = (char_u *)(v == NULL ? xstrdup(fname) : v); -#else -# ifdef HAVE_READLINK - { - bool is_relative_to_current = false; - bool has_trailing_pathsep = false; - int limit = 100; - - char *p = xstrdup(fname); - - if (p[0] == '.' && (vim_ispathsep(p[1]) - || (p[1] == '.' && (vim_ispathsep(p[2]))))) { - is_relative_to_current = true; - } - - ptrdiff_t len = (ptrdiff_t)strlen(p); - if (len > 0 && after_pathsep(p, p + len)) { - has_trailing_pathsep = true; - p[len - 1] = NUL; // The trailing slash breaks readlink(). - } - - char *q = (char *)path_next_component(p); - char *remain = NULL; - if (*q != NUL) { - // Separate the first path component in "p", and keep the - // remainder (beginning with the path separator). - remain = xstrdup(q - 1); - q[-1] = NUL; - } - - char *const buf = xmallocz(MAXPATHL); - - char *cpy; - for (;; ) { - for (;; ) { - len = readlink(p, buf, MAXPATHL); - if (len <= 0) { - break; - } - buf[len] = NUL; - - if (limit-- == 0) { - xfree(p); - xfree(remain); - EMSG(_("E655: Too many symbolic links (cycle?)")); - rettv->vval.v_string = NULL; - xfree(buf); - return; - } - - // Ensure that the result will have a trailing path separator - // if the argument has one. */ - if (remain == NULL && has_trailing_pathsep) { - add_pathsep(buf); - } - - // Separate the first path component in the link value and - // concatenate the remainders. */ - q = (char *)path_next_component(vim_ispathsep(*buf) ? buf + 1 : buf); - if (*q != NUL) { - cpy = remain; - remain = (remain - ? (char *)concat_str((char_u *)q - 1, (char_u *)remain) - : xstrdup(q - 1)); - xfree(cpy); - q[-1] = NUL; - } - - q = (char *)path_tail((char_u *)p); - if (q > p && *q == NUL) { - // Ignore trailing path separator. - q[-1] = NUL; - q = (char *)path_tail((char_u *)p); - } - if (q > p && !path_is_absolute((const char_u *)buf)) { - // Symlink is relative to directory of argument. Replace the - // symlink with the resolved name in the same directory. - const size_t p_len = strlen(p); - const size_t buf_len = strlen(buf); - p = xrealloc(p, p_len + buf_len + 1); - memcpy(path_tail((char_u *)p), buf, buf_len + 1); - } else { - xfree(p); - p = xstrdup(buf); - } - } - - if (remain == NULL) { - break; - } - - // Append the first path component of "remain" to "p". - q = (char *)path_next_component(remain + 1); - len = q - remain - (*q != NUL); - const size_t p_len = strlen(p); - cpy = xmallocz(p_len + len); - memcpy(cpy, p, p_len + 1); - xstrlcat(cpy + p_len, remain, len + 1); - xfree(p); - p = cpy; - - // Shorten "remain". - if (*q != NUL) { - STRMOVE(remain, q - 1); - } else { - XFREE_CLEAR(remain); - } - } - - // If the result is a relative path name, make it explicitly relative to - // the current directory if and only if the argument had this form. - if (!vim_ispathsep(*p)) { - if (is_relative_to_current - && *p != NUL - && !(p[0] == '.' - && (p[1] == NUL - || vim_ispathsep(p[1]) - || (p[1] == '.' - && (p[2] == NUL - || vim_ispathsep(p[2])))))) { - // Prepend "./". - cpy = (char *)concat_str((const char_u *)"./", (const char_u *)p); - xfree(p); - p = cpy; - } else if (!is_relative_to_current) { - // Strip leading "./". - q = p; - while (q[0] == '.' && vim_ispathsep(q[1])) { - q += 2; - } - if (q > p) { - STRMOVE(p, p + 2); - } - } - } - - // Ensure that the result will have no trailing path separator - // if the argument had none. But keep "/" or "//". - if (!has_trailing_pathsep) { - q = p + strlen(p); - if (after_pathsep(p, q)) { - *path_tail_with_sep((char_u *)p) = NUL; - } - } - - rettv->vval.v_string = (char_u *)p; - xfree(buf); - } -# else - rettv->vval.v_string = (char_u *)xstrdup(p); -# endif -#endif - - simplify_filename(rettv->vval.v_string); -} - -/* - * "reverse({list})" function - */ -static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - list_T *l; - if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "reverse()"); - } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - N_("reverse() argument"), TV_TRANSLATE)) { - tv_list_reverse(l); - tv_list_set_ret(rettv, l); - } -} - -#define SP_NOMOVE 0x01 ///< don't move cursor -#define SP_REPEAT 0x02 ///< repeat to find outer pair -#define SP_RETCOUNT 0x04 ///< return matchcount -#define SP_SETPCMARK 0x08 ///< set previous context mark -#define SP_START 0x10 ///< accept match at start position -#define SP_SUBPAT 0x20 ///< return nr of matching sub-pattern -#define SP_END 0x40 ///< leave cursor at end of match -#define SP_COLUMN 0x80 ///< start at cursor column - -/* - * Get flags for a search function. - * Possibly sets "p_ws". - * Returns BACKWARD, FORWARD or zero (for an error). - */ -static int get_search_arg(typval_T *varp, int *flagsp) -{ - int dir = FORWARD; - int mask; - - if (varp->v_type != VAR_UNKNOWN) { - char nbuf[NUMBUFLEN]; - const char *flags = tv_get_string_buf_chk(varp, nbuf); - if (flags == NULL) { - return 0; // Type error; errmsg already given. - } - while (*flags != NUL) { - switch (*flags) { - case 'b': dir = BACKWARD; break; - case 'w': p_ws = true; break; - case 'W': p_ws = false; break; - default: { - mask = 0; - if (flagsp != NULL) { - switch (*flags) { - case 'c': mask = SP_START; break; - case 'e': mask = SP_END; break; - case 'm': mask = SP_RETCOUNT; break; - case 'n': mask = SP_NOMOVE; break; - case 'p': mask = SP_SUBPAT; break; - case 'r': mask = SP_REPEAT; break; - case 's': mask = SP_SETPCMARK; break; - case 'z': mask = SP_COLUMN; break; - } - } - if (mask == 0) { - emsgf(_(e_invarg2), flags); - dir = 0; - } else { - *flagsp |= mask; - } - } - } - if (dir == 0) { - break; - } - flags++; - } - } - return dir; -} - -// Shared by search() and searchpos() functions. -static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) -{ - int flags; - pos_T pos; - pos_T save_cursor; - bool save_p_ws = p_ws; - int dir; - int retval = 0; /* default: FAIL */ - long lnum_stop = 0; - proftime_T tm; - long time_limit = 0; - int options = SEARCH_KEEP; - int subpatnum; - searchit_arg_T sia; - - const char *const pat = tv_get_string(&argvars[0]); - dir = get_search_arg(&argvars[1], flagsp); // May set p_ws. - if (dir == 0) { - goto theend; - } - flags = *flagsp; - if (flags & SP_START) { - options |= SEARCH_START; - } - if (flags & SP_END) { - options |= SEARCH_END; - } - if (flags & SP_COLUMN) { - options |= SEARCH_COL; - } - - /* Optional arguments: line number to stop searching and timeout. */ - if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) { - lnum_stop = tv_get_number_chk(&argvars[2], NULL); - if (lnum_stop < 0) { - goto theend; - } - if (argvars[3].v_type != VAR_UNKNOWN) { - time_limit = tv_get_number_chk(&argvars[3], NULL); - if (time_limit < 0) { - goto theend; - } - } - } - - /* Set the time limit, if there is one. */ - tm = profile_setlimit(time_limit); - - /* - * This function does not accept SP_REPEAT and SP_RETCOUNT flags. - * Check to make sure only those flags are set. - * Also, Only the SP_NOMOVE or the SP_SETPCMARK flag can be set. Both - * flags cannot be set. Check for that condition also. - */ - if (((flags & (SP_REPEAT | SP_RETCOUNT)) != 0) - || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) { - EMSG2(_(e_invarg2), tv_get_string(&argvars[1])); - goto theend; - } - - pos = save_cursor = curwin->w_cursor; - memset(&sia, 0, sizeof(sia)); - sia.sa_stop_lnum = (linenr_T)lnum_stop; - sia.sa_tm = &tm; - subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1, - options, RE_SEARCH, &sia); - if (subpatnum != FAIL) { - if (flags & SP_SUBPAT) - retval = subpatnum; - else - retval = pos.lnum; - if (flags & SP_SETPCMARK) - setpcmark(); - curwin->w_cursor = pos; - if (match_pos != NULL) { - /* Store the match cursor position */ - match_pos->lnum = pos.lnum; - match_pos->col = pos.col + 1; - } - /* "/$" will put the cursor after the end of the line, may need to - * correct that here */ - check_cursor(); - } - - /* If 'n' flag is used: restore cursor position. */ - if (flags & SP_NOMOVE) - curwin->w_cursor = save_cursor; - else - curwin->w_set_curswant = TRUE; -theend: - p_ws = save_p_ws; - - return retval; -} - -// "rpcnotify()" function -static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number < 0) { - EMSG2(_(e_invarg2), "Channel id must be a positive integer"); - return; - } - - if (argvars[1].v_type != VAR_STRING) { - EMSG2(_(e_invarg2), "Event type must be a string"); - return; - } - - Array args = ARRAY_DICT_INIT; - - for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) { - ADD(args, vim_to_object(tv)); - } - - if (!rpc_send_event((uint64_t)argvars[0].vval.v_number, - tv_get_string(&argvars[1]), args)) { - EMSG2(_(e_invarg2), "Channel doesn't exist"); - return; - } - - rettv->vval.v_number = 1; -} - -// "rpcrequest()" function -static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - const int l_provider_call_nesting = provider_call_nesting; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number <= 0) { - EMSG2(_(e_invarg2), "Channel id must be a positive integer"); - return; - } - - if (argvars[1].v_type != VAR_STRING) { - EMSG2(_(e_invarg2), "Method name must be a string"); - return; - } - - Array args = ARRAY_DICT_INIT; - - for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) { - ADD(args, vim_to_object(tv)); - } - - sctx_T save_current_sctx; - uint8_t *save_sourcing_name, *save_autocmd_fname, *save_autocmd_match; - linenr_T save_sourcing_lnum; - int save_autocmd_bufnr; - void *save_funccalp; - - if (l_provider_call_nesting) { - // If this is called from a provider function, restore the scope - // information of the caller. - save_current_sctx = current_sctx; - save_sourcing_name = sourcing_name; - save_sourcing_lnum = sourcing_lnum; - save_autocmd_fname = autocmd_fname; - save_autocmd_match = autocmd_match; - save_autocmd_bufnr = autocmd_bufnr; - save_funccalp = save_funccal(); - - current_sctx = provider_caller_scope.script_ctx; - sourcing_name = provider_caller_scope.sourcing_name; - sourcing_lnum = provider_caller_scope.sourcing_lnum; - autocmd_fname = provider_caller_scope.autocmd_fname; - autocmd_match = provider_caller_scope.autocmd_match; - autocmd_bufnr = provider_caller_scope.autocmd_bufnr; - restore_funccal(provider_caller_scope.funccalp); - } - - - Error err = ERROR_INIT; - - uint64_t chan_id = (uint64_t)argvars[0].vval.v_number; - const char *method = tv_get_string(&argvars[1]); - - Object result = rpc_send_call(chan_id, method, args, &err); - - if (l_provider_call_nesting) { - current_sctx = save_current_sctx; - sourcing_name = save_sourcing_name; - sourcing_lnum = save_sourcing_lnum; - autocmd_fname = save_autocmd_fname; - autocmd_match = save_autocmd_match; - autocmd_bufnr = save_autocmd_bufnr; - restore_funccal(save_funccalp); - } - - if (ERROR_SET(&err)) { - const char *name = NULL; - Channel *chan = find_channel(chan_id); - if (chan) { - name = rpc_client_name(chan); - } - msg_ext_set_kind("rpc_error"); - if (name) { - emsgf_multiline("Error invoking '%s' on channel %"PRIu64" (%s):\n%s", - method, chan_id, name, err.msg); - } else { - emsgf_multiline("Error invoking '%s' on channel %"PRIu64":\n%s", - method, chan_id, err.msg); - } - - goto end; - } - - if (!object_to_vim(result, rettv, &err)) { - EMSG2(_("Error converting the call result: %s"), err.msg); - } - -end: - api_free_object(result); - api_clear_error(&err); -} - -// "rpcstart()" function (DEPRECATED) -static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_STRING - || (argvars[1].v_type != VAR_LIST && argvars[1].v_type != VAR_UNKNOWN)) { - // Wrong argument types - EMSG(_(e_invarg)); - return; - } - - list_T *args = NULL; - int argsl = 0; - if (argvars[1].v_type == VAR_LIST) { - args = argvars[1].vval.v_list; - argsl = tv_list_len(args); - // Assert that all list items are strings - int i = 0; - TV_LIST_ITER_CONST(args, arg, { - if (TV_LIST_ITEM_TV(arg)->v_type != VAR_STRING) { - emsgf(_("E5010: List item %d of the second argument is not a string"), - i); - return; - } - i++; - }); - } - - if (argvars[0].vval.v_string == NULL || argvars[0].vval.v_string[0] == NUL) { - EMSG(_(e_api_spawn_failed)); - return; - } - - // Allocate extra memory for the argument vector and the NULL pointer - int argvl = argsl + 2; - char **argv = xmalloc(sizeof(char_u *) * argvl); - - // Copy program name - argv[0] = xstrdup((char *)argvars[0].vval.v_string); - - int i = 1; - // Copy arguments to the vector - if (argsl > 0) { - TV_LIST_ITER_CONST(args, arg, { - argv[i++] = xstrdup(tv_get_string(TV_LIST_ITEM_TV(arg))); - }); - } - - // The last item of argv must be NULL - argv[i] = NULL; - - Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT, - CALLBACK_READER_INIT, CALLBACK_NONE, - false, true, false, NULL, 0, 0, NULL, NULL, - &rettv->vval.v_number); - if (chan) { - channel_create_event(chan, NULL); - } -} - -// "rpcstop()" function -static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_NUMBER) { - // Wrong argument types - EMSG(_(e_invarg)); - return; - } - - // if called with a job, stop it, else closes the channel - uint64_t id = argvars[0].vval.v_number; - if (find_job(id, false)) { - f_jobstop(argvars, rettv, NULL); - } else { - const char *error; - rettv->vval.v_number = channel_close(argvars[0].vval.v_number, - kChannelPartRpc, &error); - if (!rettv->vval.v_number) { - EMSG(error); - } - } -} - -static void screenchar_adjust_grid(ScreenGrid **grid, int *row, int *col) +void screenchar_adjust_grid(ScreenGrid **grid, int *row, int *col) { // TODO(bfredl): this is a hack for legacy tests which use screenchar() // to check printed messages on the screen (but not floats etc @@ -15038,501 +6807,9 @@ static void screenchar_adjust_grid(ScreenGrid **grid, int *row, int *col) } } -/* - * "screenattr()" function - */ -static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int c; - - int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1; - int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1; - if (row < 0 || row >= default_grid.Rows - || col < 0 || col >= default_grid.Columns) { - c = -1; - } else { - ScreenGrid *grid = &default_grid; - screenchar_adjust_grid(&grid, &row, &col); - c = grid->attrs[grid->line_offset[row] + col]; - } - rettv->vval.v_number = c; -} - -/* - * "screenchar()" function - */ -static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int c; - - int row = tv_get_number_chk(&argvars[0], NULL) - 1; - int col = tv_get_number_chk(&argvars[1], NULL) - 1; - if (row < 0 || row >= default_grid.Rows - || col < 0 || col >= default_grid.Columns) { - c = -1; - } else { - ScreenGrid *grid = &default_grid; - screenchar_adjust_grid(&grid, &row, &col); - c = utf_ptr2char(grid->chars[grid->line_offset[row] + col]); - } - rettv->vval.v_number = c; -} - -/* - * "screencol()" function - * - * First column is 1 to be consistent with virtcol(). - */ -static void f_screencol(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = ui_current_col() + 1; -} - -/// "screenpos({winid}, {lnum}, {col})" function -static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - pos_T pos; - int row = 0; - int scol = 0, ccol = 0, ecol = 0; - - tv_dict_alloc_ret(rettv); - dict_T *dict = rettv->vval.v_dict; - - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - return; - } - - pos.lnum = tv_get_number(&argvars[1]); - pos.col = tv_get_number(&argvars[2]) - 1; - pos.coladd = 0; - textpos2screenpos(wp, &pos, &row, &scol, &ccol, &ecol, false); - - tv_dict_add_nr(dict, S_LEN("row"), row); - tv_dict_add_nr(dict, S_LEN("col"), scol); - tv_dict_add_nr(dict, S_LEN("curscol"), ccol); - tv_dict_add_nr(dict, S_LEN("endcol"), ecol); -} - -/* - * "screenrow()" function - */ -static void f_screenrow(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = ui_current_row() + 1; -} - -/* - * "search()" function - */ -static void f_search(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int flags = 0; - - rettv->vval.v_number = search_cmn(argvars, NULL, &flags); -} - -/* - * "searchdecl()" function - */ -static void f_searchdecl(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int locally = 1; - int thisblock = 0; - bool error = false; - - rettv->vval.v_number = 1; /* default: FAIL */ - - const char *const name = tv_get_string_chk(&argvars[0]); - if (argvars[1].v_type != VAR_UNKNOWN) { - locally = tv_get_number_chk(&argvars[1], &error) == 0; - if (!error && argvars[2].v_type != VAR_UNKNOWN) { - thisblock = tv_get_number_chk(&argvars[2], &error) != 0; - } - } - if (!error && name != NULL) { - rettv->vval.v_number = find_decl((char_u *)name, strlen(name), locally, - thisblock, SEARCH_KEEP) == FAIL; - } -} - -/* - * Used by searchpair() and searchpairpos() - */ -static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) -{ - bool save_p_ws = p_ws; - int dir; - int flags = 0; - int retval = 0; // default: FAIL - long lnum_stop = 0; - long time_limit = 0; - - // Get the three pattern arguments: start, middle, end. Will result in an - // error if not a valid argument. - char nbuf1[NUMBUFLEN]; - char nbuf2[NUMBUFLEN]; - const char *spat = tv_get_string_chk(&argvars[0]); - const char *mpat = tv_get_string_buf_chk(&argvars[1], nbuf1); - const char *epat = tv_get_string_buf_chk(&argvars[2], nbuf2); - if (spat == NULL || mpat == NULL || epat == NULL) { - goto theend; // Type error. - } - - // Handle the optional fourth argument: flags. - dir = get_search_arg(&argvars[3], &flags); // may set p_ws. - if (dir == 0) { - goto theend; - } - - // Don't accept SP_END or SP_SUBPAT. - // Only one of the SP_NOMOVE or SP_SETPCMARK flags can be set. - if ((flags & (SP_END | SP_SUBPAT)) != 0 - || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) { - EMSG2(_(e_invarg2), tv_get_string(&argvars[3])); - goto theend; - } - - // Using 'r' implies 'W', otherwise it doesn't work. - if (flags & SP_REPEAT) { - p_ws = false; - } - - // Optional fifth argument: skip expression. - const typval_T *skip; - if (argvars[3].v_type == VAR_UNKNOWN - || argvars[4].v_type == VAR_UNKNOWN) { - skip = NULL; - } else { - skip = &argvars[4]; - if (skip->v_type != VAR_FUNC - && skip->v_type != VAR_PARTIAL - && skip->v_type != VAR_STRING) { - emsgf(_(e_invarg2), tv_get_string(&argvars[4])); - goto theend; // Type error. - } - if (argvars[5].v_type != VAR_UNKNOWN) { - lnum_stop = tv_get_number_chk(&argvars[5], NULL); - if (lnum_stop < 0) { - emsgf(_(e_invarg2), tv_get_string(&argvars[5])); - goto theend; - } - if (argvars[6].v_type != VAR_UNKNOWN) { - time_limit = tv_get_number_chk(&argvars[6], NULL); - if (time_limit < 0) { - emsgf(_(e_invarg2), tv_get_string(&argvars[6])); - goto theend; - } - } - } - } - - retval = do_searchpair( - (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, skip, - flags, match_pos, lnum_stop, time_limit); - -theend: - p_ws = save_p_ws; - - return retval; -} - -/* - * "searchpair()" function - */ -static void f_searchpair(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = searchpair_cmn(argvars, NULL); -} - -/* - * "searchpairpos()" function - */ -static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - pos_T match_pos; - int lnum = 0; - int col = 0; - - tv_list_alloc_ret(rettv, 2); - - if (searchpair_cmn(argvars, &match_pos) > 0) { - lnum = match_pos.lnum; - col = match_pos.col; - } - - tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum); - tv_list_append_number(rettv->vval.v_list, (varnumber_T)col); -} - -/* - * Search for a start/middle/end thing. - * Used by searchpair(), see its documentation for the details. - * Returns 0 or -1 for no match, - */ -long -do_searchpair( - char_u *spat, // start pattern - char_u *mpat, // middle pattern - char_u *epat, // end pattern - int dir, // BACKWARD or FORWARD - const typval_T *skip, // skip expression - int flags, // SP_SETPCMARK and other SP_ values - pos_T *match_pos, - linenr_T lnum_stop, // stop at this line if not zero - long time_limit // stop after this many msec -) -{ - char_u *save_cpo; - char_u *pat, *pat2 = NULL, *pat3 = NULL; - long retval = 0; - pos_T pos; - pos_T firstpos; - pos_T foundpos; - pos_T save_cursor; - pos_T save_pos; - int n; - int nest = 1; - bool use_skip = false; - int options = SEARCH_KEEP; - proftime_T tm; - size_t pat2_len; - size_t pat3_len; - - /* Make 'cpoptions' empty, the 'l' flag should not be used here. */ - save_cpo = p_cpo; - p_cpo = empty_option; - - /* Set the time limit, if there is one. */ - tm = profile_setlimit(time_limit); - - // Make two search patterns: start/end (pat2, for in nested pairs) and - // start/middle/end (pat3, for the top pair). - pat2_len = STRLEN(spat) + STRLEN(epat) + 17; - pat2 = xmalloc(pat2_len); - pat3_len = STRLEN(spat) + STRLEN(mpat) + STRLEN(epat) + 25; - pat3 = xmalloc(pat3_len); - snprintf((char *)pat2, pat2_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat); - if (*mpat == NUL) { - STRCPY(pat3, pat2); - } else { - snprintf((char *)pat3, pat3_len, - "\\m\\(%s\\m\\)\\|\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat, mpat); - } - if (flags & SP_START) { - options |= SEARCH_START; - } - - if (skip != NULL) { - // Empty string means to not use the skip expression. - if (skip->v_type == VAR_STRING || skip->v_type == VAR_FUNC) { - use_skip = skip->vval.v_string != NULL && *skip->vval.v_string != NUL; - } - } - - save_cursor = curwin->w_cursor; - pos = curwin->w_cursor; - clearpos(&firstpos); - clearpos(&foundpos); - pat = pat3; - for (;; ) { - searchit_arg_T sia; - memset(&sia, 0, sizeof(sia)); - sia.sa_stop_lnum = lnum_stop; - sia.sa_tm = &tm; - - n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L, - options, RE_SEARCH, &sia); - if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) { - // didn't find it or found the first match again: FAIL - break; - } - - if (firstpos.lnum == 0) - firstpos = pos; - if (equalpos(pos, foundpos)) { - /* Found the same position again. Can happen with a pattern that - * has "\zs" at the end and searching backwards. Advance one - * character and try again. */ - if (dir == BACKWARD) - decl(&pos); - else - incl(&pos); - } - foundpos = pos; - - /* clear the start flag to avoid getting stuck here */ - options &= ~SEARCH_START; - - // If the skip pattern matches, ignore this match. - if (use_skip) { - save_pos = curwin->w_cursor; - curwin->w_cursor = pos; - bool err = false; - const bool r = eval_expr_to_bool(skip, &err); - curwin->w_cursor = save_pos; - if (err) { - /* Evaluating {skip} caused an error, break here. */ - curwin->w_cursor = save_cursor; - retval = -1; - break; - } - if (r) - continue; - } - - if ((dir == BACKWARD && n == 3) || (dir == FORWARD && n == 2)) { - /* Found end when searching backwards or start when searching - * forward: nested pair. */ - ++nest; - pat = pat2; /* nested, don't search for middle */ - } else { - /* Found end when searching forward or start when searching - * backward: end of (nested) pair; or found middle in outer pair. */ - if (--nest == 1) - pat = pat3; /* outer level, search for middle */ - } - - if (nest == 0) { - /* Found the match: return matchcount or line number. */ - if (flags & SP_RETCOUNT) - ++retval; - else - retval = pos.lnum; - if (flags & SP_SETPCMARK) - setpcmark(); - curwin->w_cursor = pos; - if (!(flags & SP_REPEAT)) - break; - nest = 1; /* search for next unmatched */ - } - } - - if (match_pos != NULL) { - /* Store the match cursor position */ - match_pos->lnum = curwin->w_cursor.lnum; - match_pos->col = curwin->w_cursor.col + 1; - } - - /* If 'n' flag is used or search failed: restore cursor position. */ - if ((flags & SP_NOMOVE) || retval == 0) - curwin->w_cursor = save_cursor; - - xfree(pat2); - xfree(pat3); - if (p_cpo == empty_option) - p_cpo = save_cpo; - else - /* Darn, evaluating the {skip} expression changed the value. */ - free_string_option(save_cpo); - - return retval; -} - -/* - * "searchpos()" function - */ -static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - pos_T match_pos; - int flags = 0; - - const int n = search_cmn(argvars, &match_pos, &flags); - - tv_list_alloc_ret(rettv, 2 + (!!(flags & SP_SUBPAT))); - - const int lnum = (n > 0 ? match_pos.lnum : 0); - const int col = (n > 0 ? match_pos.col : 0); - - tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum); - tv_list_append_number(rettv->vval.v_list, (varnumber_T)col); - if (flags & SP_SUBPAT) { - tv_list_append_number(rettv->vval.v_list, (varnumber_T)n); - } -} - -/// "serverlist()" function -static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - size_t n; - char **addrs = server_address_list(&n); - - // Copy addrs into a linked list. - list_T *const l = tv_list_alloc_ret(rettv, n); - for (size_t i = 0; i < n; i++) { - tv_list_append_allocated_string(l, addrs[i]); - } - xfree(addrs); -} - -/// "serverstart()" function -static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; // Address of the new server - - if (check_restricted() || check_secure()) { - return; - } - - char *address; - // If the user supplied an address, use it, otherwise use a temp. - if (argvars[0].v_type != VAR_UNKNOWN) { - if (argvars[0].v_type != VAR_STRING) { - EMSG(_(e_invarg)); - return; - } else { - address = xstrdup(tv_get_string(argvars)); - } - } else { - address = server_address_new(); - } - - int result = server_start(address); - xfree(address); - - if (result != 0) { - EMSG2("Failed to start server: %s", - result > 0 ? "Unknown system error" : uv_strerror(result)); - return; - } - - // Since it's possible server_start adjusted the given {address} (e.g., - // "localhost:" will now have a port), return the final value to the user. - size_t n; - char **addrs = server_address_list(&n); - rettv->vval.v_string = (char_u *)addrs[n - 1]; - - n--; - for (size_t i = 0; i < n; i++) { - xfree(addrs[i]); - } - xfree(addrs); -} - -/// "serverstop()" function -static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_STRING) { - EMSG(_(e_invarg)); - return; - } - - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - if (argvars[0].vval.v_string) { - bool rv = server_stop((char *)argvars[0].vval.v_string); - rettv->vval.v_number = (rv ? 1 : 0); - } -} - /// Set line or list of lines in buffer "buf". -static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, - const typval_T *lines, typval_T *rettv) +void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, + const typval_T *lines, typval_T *rettv) FUNC_ATTR_NONNULL_ARG(4, 5) { linenr_T lnum = lnum_arg + (append ? 1 : 0); @@ -15646,632 +6923,11 @@ static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, } } -/// "setbufline()" function -static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T lnum; - buf_T *buf; - - buf = tv_get_buf(&argvars[0], false); - if (buf == NULL) { - rettv->vval.v_number = 1; // FAIL - } else { - lnum = tv_get_lnum_buf(&argvars[1], buf); - set_buffer_lines(buf, lnum, false, &argvars[2], rettv); - } -} - -/* - * "setbufvar()" function - */ -static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (check_restricted() - || check_secure() - || !tv_check_str_or_nr(&argvars[0])) { - return; - } - const char *varname = tv_get_string_chk(&argvars[1]); - buf_T *const buf = tv_get_buf(&argvars[0], false); - typval_T *varp = &argvars[2]; - - if (buf != NULL && varname != NULL) { - if (*varname == '&') { - long numval; - bool error = false; - aco_save_T aco; - - // set curbuf to be our buf, temporarily - aucmd_prepbuf(&aco, buf); - - varname++; - numval = tv_get_number_chk(varp, &error); - char nbuf[NUMBUFLEN]; - const char *const strval = tv_get_string_buf_chk(varp, nbuf); - if (!error && strval != NULL) { - set_option_value(varname, numval, strval, OPT_LOCAL); - } - - // reset notion of buffer - aucmd_restbuf(&aco); - } else { - buf_T *save_curbuf = curbuf; - - const size_t varname_len = STRLEN(varname); - char *const bufvarname = xmalloc(varname_len + 3); - curbuf = buf; - memcpy(bufvarname, "b:", 2); - memcpy(bufvarname + 2, varname, varname_len + 1); - set_var(bufvarname, varname_len + 2, varp, true); - xfree(bufvarname); - curbuf = save_curbuf; - } - } -} - -static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_T *d; - dictitem_T *di; - - if (argvars[0].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - - if ((d = argvars[0].vval.v_dict) != NULL) { - char_u *const csearch = (char_u *)tv_dict_get_string(d, "char", false); - if (csearch != NULL) { - if (enc_utf8) { - int pcc[MAX_MCO]; - int c = utfc_ptr2char(csearch, pcc); - set_last_csearch(c, csearch, utfc_ptr2len(csearch)); - } - else - set_last_csearch(PTR2CHAR(csearch), - csearch, utfc_ptr2len(csearch)); - } - - di = tv_dict_find(d, S_LEN("forward")); - if (di != NULL) { - set_csearch_direction(tv_get_number(&di->di_tv) ? FORWARD : BACKWARD); - } - - di = tv_dict_find(d, S_LEN("until")); - if (di != NULL) { - set_csearch_until(!!tv_get_number(&di->di_tv)); - } - } -} - -/* - * "setcmdpos()" function - */ -static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const int pos = (int)tv_get_number(&argvars[0]) - 1; - - if (pos >= 0) { - rettv->vval.v_number = set_cmdline_pos(pos); - } -} - -/// "setenv()" function -static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char namebuf[NUMBUFLEN]; - char valbuf[NUMBUFLEN]; - const char *name = tv_get_string_buf(&argvars[0], namebuf); - - if (argvars[1].v_type == VAR_SPECIAL - && argvars[1].vval.v_special == kSpecialVarNull) { - os_unsetenv(name); - } else { - os_setenv(name, tv_get_string_buf(&argvars[1], valbuf), 1); - } -} - -/// "setfperm({fname}, {mode})" function -static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = 0; - - const char *const fname = tv_get_string_chk(&argvars[0]); - if (fname == NULL) { - return; - } - - char modebuf[NUMBUFLEN]; - const char *const mode_str = tv_get_string_buf_chk(&argvars[1], modebuf); - if (mode_str == NULL) { - return; - } - if (strlen(mode_str) != 9) { - EMSG2(_(e_invarg2), mode_str); - return; - } - - int mask = 1; - int mode = 0; - for (int i = 8; i >= 0; i--) { - if (mode_str[i] != '-') { - mode |= mask; - } - mask = mask << 1; - } - rettv->vval.v_number = os_setperm(fname, mode) == OK; -} - -/* - * "setline()" function - */ -static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T lnum = tv_get_lnum(&argvars[0]); - set_buffer_lines(curbuf, lnum, false, &argvars[1], rettv); -} - -/// Create quickfix/location list from VimL values -/// -/// Used by `setqflist()` and `setloclist()` functions. Accepts invalid -/// list_arg, action_arg and what_arg arguments in which case errors out, -/// including VAR_UNKNOWN parameters. -/// -/// @param[in,out] wp Window to create location list for. May be NULL in -/// which case quickfix list will be created. -/// @param[in] list_arg Quickfix list contents. -/// @param[in] action_arg Action to perform: append to an existing list, -/// replace its content or create a new one. -/// @param[in] title_arg New list title. Defaults to caller function name. -/// @param[out] rettv Return value: 0 in case of success, -1 otherwise. -static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv) - FUNC_ATTR_NONNULL_ARG(2, 3) -{ - static char *e_invact = N_("E927: Invalid action: '%s'"); - const char *title = NULL; - int action = ' '; - static int recursive = 0; - rettv->vval.v_number = -1; - dict_T *d = NULL; - - typval_T *list_arg = &args[0]; - if (list_arg->v_type != VAR_LIST) { - EMSG(_(e_listreq)); - return; - } else if (recursive != 0) { - EMSG(_(e_au_recursive)); - return; - } - - typval_T *action_arg = &args[1]; - if (action_arg->v_type == VAR_UNKNOWN) { - // Option argument was not given. - goto skip_args; - } else if (action_arg->v_type != VAR_STRING) { - EMSG(_(e_stringreq)); - return; - } - const char *const act = tv_get_string_chk(action_arg); - if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f') - && act[1] == NUL) { - action = *act; - } else { - EMSG2(_(e_invact), act); - return; - } - - typval_T *title_arg = &args[2]; - if (title_arg->v_type == VAR_UNKNOWN) { - // Option argument was not given. - goto skip_args; - } else if (title_arg->v_type == VAR_STRING) { - title = tv_get_string_chk(title_arg); - if (!title) { - // Type error. Error already printed by tv_get_string_chk(). - return; - } - } else if (title_arg->v_type == VAR_DICT) { - d = title_arg->vval.v_dict; - } else { - EMSG(_(e_dictreq)); - return; - } - -skip_args: - if (!title) { - title = (wp ? ":setloclist()" : ":setqflist()"); - } - - recursive++; - list_T *const l = list_arg->vval.v_list; - if (set_errorlist(wp, l, action, (char_u *)title, d) == OK) { - rettv->vval.v_number = 0; - } - recursive--; -} - -/* - * "setloclist()" function - */ -static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *win; - - rettv->vval.v_number = -1; - - win = find_win_by_nr_or_id(&argvars[0]); - if (win != NULL) { - set_qf_ll_list(win, &argvars[1], rettv); - } -} - -/* - * "setmatches()" function - */ -static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_T *d; - list_T *s = NULL; - - rettv->vval.v_number = -1; - if (argvars[0].v_type != VAR_LIST) { - EMSG(_(e_listreq)); - return; - } - list_T *const l = argvars[0].vval.v_list; - // To some extent make sure that we are dealing with a list from - // "getmatches()". - int li_idx = 0; - TV_LIST_ITER_CONST(l, li, { - if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT - || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) { - emsgf(_("E474: List item %d is either not a dictionary " - "or an empty one"), li_idx); - return; - } - if (!(tv_dict_find(d, S_LEN("group")) != NULL - && (tv_dict_find(d, S_LEN("pattern")) != NULL - || tv_dict_find(d, S_LEN("pos1")) != NULL) - && tv_dict_find(d, S_LEN("priority")) != NULL - && tv_dict_find(d, S_LEN("id")) != NULL)) { - emsgf(_("E474: List item %d is missing one of the required keys"), - li_idx); - return; - } - li_idx++; - }); - - clear_matches(curwin); - bool match_add_failed = false; - TV_LIST_ITER_CONST(l, li, { - int i = 0; - - d = TV_LIST_ITEM_TV(li)->vval.v_dict; - dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); - if (di == NULL) { - if (s == NULL) { - s = tv_list_alloc(9); - } - - // match from matchaddpos() - for (i = 1; i < 9; i++) { - char buf[30]; // use 30 to avoid compiler warning - snprintf(buf, sizeof(buf), "pos%d", i); - dictitem_T *const pos_di = tv_dict_find(d, buf, -1); - if (pos_di != NULL) { - if (pos_di->di_tv.v_type != VAR_LIST) { - return; - } - - tv_list_append_tv(s, &pos_di->di_tv); - tv_list_ref(s); - } else { - break; - } - } - } - - // Note: there are three number buffers involved: - // - group_buf below. - // - numbuf in tv_dict_get_string(). - // - mybuf in tv_get_string(). - // - // If you change this code make sure that buffers will not get - // accidentally reused. - char group_buf[NUMBUFLEN]; - const char *const group = tv_dict_get_string_buf(d, "group", group_buf); - const int priority = (int)tv_dict_get_number(d, "priority"); - const int id = (int)tv_dict_get_number(d, "id"); - dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal")); - const char *const conceal = (conceal_di != NULL - ? tv_get_string(&conceal_di->di_tv) - : NULL); - if (i == 0) { - if (match_add(curwin, group, - tv_dict_get_string(d, "pattern", false), - priority, id, NULL, conceal) != id) { - match_add_failed = true; - } - } else { - if (match_add(curwin, group, NULL, priority, id, s, conceal) != id) { - match_add_failed = true; - } - tv_list_unref(s); - s = NULL; - } - }); - if (!match_add_failed) { - rettv->vval.v_number = 0; - } -} - -/* - * "setpos()" function - */ -static void f_setpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - pos_T pos; - int fnum; - colnr_T curswant = -1; - - rettv->vval.v_number = -1; - const char *const name = tv_get_string_chk(argvars); - if (name != NULL) { - if (list2fpos(&argvars[1], &pos, &fnum, &curswant) == OK) { - if (pos.col != MAXCOL && --pos.col < 0) { - pos.col = 0; - } - if (name[0] == '.' && name[1] == NUL) { - // set cursor; "fnum" is ignored - curwin->w_cursor = pos; - if (curswant >= 0) { - curwin->w_curswant = curswant - 1; - curwin->w_set_curswant = false; - } - check_cursor(); - rettv->vval.v_number = 0; - } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) { - // set mark - if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) { - rettv->vval.v_number = 0; - } - } else { - EMSG(_(e_invarg)); - } - } - } -} - -/* - * "setqflist()" function - */ -static void f_setqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - set_qf_ll_list(NULL, argvars, rettv); -} - -/* - * "setreg()" function - */ -static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int regname; - bool append = false; - MotionType yank_type; - long block_len; - - block_len = -1; - yank_type = kMTUnknown; - - rettv->vval.v_number = 1; // FAIL is default. - - const char *const strregname = tv_get_string_chk(argvars); - if (strregname == NULL) { - return; // Type error; errmsg already given. - } - regname = (uint8_t)(*strregname); - if (regname == 0 || regname == '@') { - regname = '"'; - } - - bool set_unnamed = false; - if (argvars[2].v_type != VAR_UNKNOWN) { - const char *stropt = tv_get_string_chk(&argvars[2]); - if (stropt == NULL) { - return; // Type error. - } - for (; *stropt != NUL; stropt++) { - switch (*stropt) { - case 'a': case 'A': { // append - append = true; - break; - } - case 'v': case 'c': { // character-wise selection - yank_type = kMTCharWise; - break; - } - case 'V': case 'l': { // line-wise selection - yank_type = kMTLineWise; - break; - } - case 'b': case Ctrl_V: { // block-wise selection - yank_type = kMTBlockWise; - if (ascii_isdigit(stropt[1])) { - stropt++; - block_len = getdigits_long((char_u **)&stropt, true, 0) - 1; - stropt--; - } - break; - } - case 'u': case '"': { // unnamed register - set_unnamed = true; - break; - } - } - } - } - - if (argvars[1].v_type == VAR_LIST) { - list_T *ll = argvars[1].vval.v_list; - // If the list is NULL handle like an empty list. - const int len = tv_list_len(ll); - - // First half: use for pointers to result lines; second half: use for - // pointers to allocated copies. - char **lstval = xmalloc(sizeof(char *) * ((len + 1) * 2)); - const char **curval = (const char **)lstval; - char **allocval = lstval + len + 2; - char **curallocval = allocval; - - TV_LIST_ITER_CONST(ll, li, { - char buf[NUMBUFLEN]; - *curval = tv_get_string_buf_chk(TV_LIST_ITEM_TV(li), buf); - if (*curval == NULL) { - goto free_lstval; - } - if (*curval == buf) { - // Need to make a copy, - // next tv_get_string_buf_chk() will overwrite the string. - *curallocval = xstrdup(*curval); - *curval = *curallocval; - curallocval++; - } - curval++; - }); - *curval++ = NULL; - - write_reg_contents_lst(regname, (char_u **)lstval, append, yank_type, - block_len); - -free_lstval: - while (curallocval > allocval) { - xfree(*--curallocval); - } - xfree(lstval); - } else { - const char *strval = tv_get_string_chk(&argvars[1]); - if (strval == NULL) { - return; - } - write_reg_contents_ex(regname, (const char_u *)strval, STRLEN(strval), - append, yank_type, block_len); - } - rettv->vval.v_number = 0; - - if (set_unnamed) { - // Discard the result. We already handle the error case. - if (op_reg_set_previous(regname)) { } - } -} - -/* - * "settabvar()" function - */ -static void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); - const char *const varname = tv_get_string_chk(&argvars[1]); - typval_T *const varp = &argvars[2]; - - if (varname != NULL && tp != NULL) { - tabpage_T *const save_curtab = curtab; - goto_tabpage_tp(tp, false, false); - - const size_t varname_len = strlen(varname); - char *const tabvarname = xmalloc(varname_len + 3); - memcpy(tabvarname, "t:", 2); - memcpy(tabvarname + 2, varname, varname_len + 1); - set_var(tabvarname, varname_len + 2, varp, true); - xfree(tabvarname); - - // Restore current tabpage. - if (valid_tabpage(save_curtab)) { - goto_tabpage_tp(save_curtab, false, false); - } - } -} - -/* - * "settabwinvar()" function - */ -static void f_settabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - setwinvar(argvars, rettv, 1); -} - -// "settagstack()" function -static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - static char *e_invact2 = N_("E962: Invalid action: '%s'"); - win_T *wp; - dict_T *d; - int action = 'r'; - - rettv->vval.v_number = -1; - - // first argument: window number or id - wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - return; - } - - // second argument: dict with items to set in the tag stack - if (argvars[1].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - d = argvars[1].vval.v_dict; - if (d == NULL) { - return; - } - - // third argument: action - 'a' for append and 'r' for replace. - // default is to replace the stack. - if (argvars[2].v_type == VAR_UNKNOWN) { - action = 'r'; - } else if (argvars[2].v_type == VAR_STRING) { - const char *actstr; - actstr = tv_get_string_chk(&argvars[2]); - if (actstr == NULL) { - return; - } - if ((*actstr == 'r' || *actstr == 'a') && actstr[1] == NUL) { - action = *actstr; - } else { - EMSG2(_(e_invact2), actstr); - return; - } - } else { - EMSG(_(e_stringreq)); - return; - } - - if (set_tagstack(wp, d, action) == OK) { - rettv->vval.v_number = 0; - } else { - EMSG(_(e_listreq)); - } -} - -/* - * "setwinvar()" function - */ -static void f_setwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - setwinvar(argvars, rettv, 0); -} - /* * "setwinvar()" and "settabwinvar()" functions */ -static void setwinvar(typval_T *argvars, typval_T *rettv, int off) +void setwinvar(typval_T *argvars, typval_T *rettv, int off) { if (check_secure()) { return; @@ -16319,990 +6975,8 @@ static void setwinvar(typval_T *argvars, typval_T *rettv, int off) } } -/// f_sha256 - sha256({string}) function -static void f_sha256(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *p = tv_get_string(&argvars[0]); - const char *hash = sha256_bytes((const uint8_t *)p, strlen(p) , NULL, 0); - - // make a copy of the hash (sha256_bytes returns a static buffer) - rettv->vval.v_string = (char_u *)xstrdup(hash); - rettv->v_type = VAR_STRING; -} - -/* - * "shellescape({string})" function - */ -static void f_shellescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const bool do_special = non_zero_arg(&argvars[1]); - - rettv->vval.v_string = vim_strsave_shellescape( - (const char_u *)tv_get_string(&argvars[0]), do_special, do_special); - rettv->v_type = VAR_STRING; -} - -/* - * shiftwidth() function - */ -static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = get_sw_value(curbuf); -} - -/// "sign_define()" function -static void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *name; - dict_T *dict; - char *icon = NULL; - char *linehl = NULL; - char *text = NULL; - char *texthl = NULL; - char *numhl = NULL; - - rettv->vval.v_number = -1; - - name = tv_get_string_chk(&argvars[0]); - if (name == NULL) { - return; - } - - if (argvars[1].v_type != VAR_UNKNOWN) { - if (argvars[1].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - - // sign attributes - dict = argvars[1].vval.v_dict; - if (tv_dict_find(dict, "icon", -1) != NULL) { - icon = tv_dict_get_string(dict, "icon", true); - } - if (tv_dict_find(dict, "linehl", -1) != NULL) { - linehl = tv_dict_get_string(dict, "linehl", true); - } - if (tv_dict_find(dict, "text", -1) != NULL) { - text = tv_dict_get_string(dict, "text", true); - } - if (tv_dict_find(dict, "texthl", -1) != NULL) { - texthl = tv_dict_get_string(dict, "texthl", true); - } - if (tv_dict_find(dict, "numhl", -1) != NULL) { - numhl = tv_dict_get_string(dict, "numhl", true); - } - } - - if (sign_define_by_name((char_u *)name, (char_u *)icon, (char_u *)linehl, - (char_u *)text, (char_u *)texthl, (char_u *)numhl) - == OK) { - rettv->vval.v_number = 0; - } - - xfree(icon); - xfree(linehl); - xfree(text); - xfree(texthl); -} - -/// "sign_getdefined()" function -static void f_sign_getdefined(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *name = NULL; - - tv_list_alloc_ret(rettv, 0); - - if (argvars[0].v_type != VAR_UNKNOWN) { - name = tv_get_string(&argvars[0]); - } - - sign_getlist((const char_u *)name, rettv->vval.v_list); -} - -/// "sign_getplaced()" function -static void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *buf = NULL; - dict_T *dict; - dictitem_T *di; - linenr_T lnum = 0; - int sign_id = 0; - const char *group = NULL; - bool notanum = false; - - tv_list_alloc_ret(rettv, 0); - - if (argvars[0].v_type != VAR_UNKNOWN) { - // get signs placed in the specified buffer - buf = get_buf_arg(&argvars[0]); - if (buf == NULL) { - return; - } - - if (argvars[1].v_type != VAR_UNKNOWN) { - if (argvars[1].v_type != VAR_DICT - || ((dict = argvars[1].vval.v_dict) == NULL)) { - EMSG(_(e_dictreq)); - return; - } - if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) { - // get signs placed at this line - lnum = (linenr_T)tv_get_number_chk(&di->di_tv, ¬anum); - if (notanum) { - return; - } - (void)lnum; - lnum = tv_get_lnum(&di->di_tv); - } - if ((di = tv_dict_find(dict, "id", -1)) != NULL) { - // get sign placed with this identifier - sign_id = (int)tv_get_number_chk(&di->di_tv, ¬anum); - if (notanum) { - return; - } - } - if ((di = tv_dict_find(dict, "group", -1)) != NULL) { - group = tv_get_string_chk(&di->di_tv); - if (group == NULL) { - return; - } - if (*group == '\0') { // empty string means global group - group = NULL; - } - } - } - } - - sign_get_placed(buf, lnum, sign_id, (const char_u *)group, - rettv->vval.v_list); -} - -/// "sign_jump()" function -static void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int sign_id; - char *sign_group = NULL; - buf_T *buf; - bool notanum = false; - - rettv->vval.v_number = -1; - - // Sign identifer - sign_id = (int)tv_get_number_chk(&argvars[0], ¬anum); - if (notanum) { - return; - } - if (sign_id <= 0) { - EMSG(_(e_invarg)); - return; - } - - // Sign group - const char * sign_group_chk = tv_get_string_chk(&argvars[1]); - if (sign_group_chk == NULL) { - return; - } - if (sign_group_chk[0] == '\0') { - sign_group = NULL; // global sign group - } else { - sign_group = xstrdup(sign_group_chk); - } - - // Buffer to place the sign - buf = get_buf_arg(&argvars[2]); - if (buf == NULL) { - goto cleanup; - } - - rettv->vval.v_number = sign_jump(sign_id, (char_u *)sign_group, buf); - -cleanup: - xfree(sign_group); -} - -/// "sign_place()" function -static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int sign_id; - char_u *group = NULL; - const char *sign_name; - buf_T *buf; - dict_T *dict; - dictitem_T *di; - linenr_T lnum = 0; - int prio = SIGN_DEF_PRIO; - bool notanum = false; - - rettv->vval.v_number = -1; - - // Sign identifer - sign_id = (int)tv_get_number_chk(&argvars[0], ¬anum); - if (notanum) { - return; - } - if (sign_id < 0) { - EMSG(_(e_invarg)); - return; - } - - // Sign group - const char *group_chk = tv_get_string_chk(&argvars[1]); - if (group_chk == NULL) { - return; - } - if (group_chk[0] == '\0') { - group = NULL; // global sign group - } else { - group = vim_strsave((const char_u *)group_chk); - } - - // Sign name - sign_name = tv_get_string_chk(&argvars[2]); - if (sign_name == NULL) { - goto cleanup; - } - - // Buffer to place the sign - buf = get_buf_arg(&argvars[3]); - if (buf == NULL) { - goto cleanup; - } - - if (argvars[4].v_type != VAR_UNKNOWN) { - if (argvars[4].v_type != VAR_DICT - || ((dict = argvars[4].vval.v_dict) == NULL)) { - EMSG(_(e_dictreq)); - goto cleanup; - } - - // Line number where the sign is to be placed - if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) { - lnum = (linenr_T)tv_get_number_chk(&di->di_tv, ¬anum); - if (notanum) { - goto cleanup; - } - (void)lnum; - lnum = tv_get_lnum(&di->di_tv); - } - if ((di = tv_dict_find(dict, "priority", -1)) != NULL) { - // Sign priority - prio = (int)tv_get_number_chk(&di->di_tv, ¬anum); - if (notanum) { - goto cleanup; - } - } - } - - if (sign_place(&sign_id, group, (const char_u *)sign_name, buf, lnum, prio) - == OK) { - rettv->vval.v_number = sign_id; - } - -cleanup: - xfree(group); -} - -/// "sign_undefine()" function -static void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *name; - - rettv->vval.v_number = -1; - - if (argvars[0].v_type == VAR_UNKNOWN) { - // Free all the signs - free_signs(); - rettv->vval.v_number = 0; - } else { - // Free only the specified sign - name = tv_get_string_chk(&argvars[0]); - if (name == NULL) { - return; - } - - if (sign_undefine_by_name((const char_u *)name) == OK) { - rettv->vval.v_number = 0; - } - } -} - -/// "sign_unplace()" function -static void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_T *dict; - dictitem_T *di; - int sign_id = 0; - buf_T *buf = NULL; - char_u *group = NULL; - - rettv->vval.v_number = -1; - - if (argvars[0].v_type != VAR_STRING) { - EMSG(_(e_invarg)); - return; - } - - const char *group_chk = tv_get_string(&argvars[0]); - if (group_chk[0] == '\0') { - group = NULL; // global sign group - } else { - group = vim_strsave((const char_u *)group_chk); - } - - if (argvars[1].v_type != VAR_UNKNOWN) { - if (argvars[1].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - goto cleanup; - } - dict = argvars[1].vval.v_dict; - - if ((di = tv_dict_find(dict, "buffer", -1)) != NULL) { - buf = get_buf_arg(&di->di_tv); - if (buf == NULL) { - goto cleanup; - } - } - if (tv_dict_find(dict, "id", -1) != NULL) { - sign_id = tv_dict_get_number(dict, "id"); - } - } - - if (buf == NULL) { - // Delete the sign in all the buffers - FOR_ALL_BUFFERS(cbuf) { - if (sign_unplace(sign_id, group, cbuf, 0) == OK) { - rettv->vval.v_number = 0; - } - } - } else { - if (sign_unplace(sign_id, group, buf, 0) == OK) { - rettv->vval.v_number = 0; - } - } - -cleanup: - xfree(group); -} - -/* - * "simplify()" function - */ -static void f_simplify(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const p = tv_get_string(&argvars[0]); - rettv->vval.v_string = (char_u *)xstrdup(p); - simplify_filename(rettv->vval.v_string); // Simplify in place. - rettv->v_type = VAR_STRING; -} - -/// "sockconnect()" function -static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) { - EMSG(_(e_invarg)); - return; - } - if (argvars[2].v_type != VAR_DICT && argvars[2].v_type != VAR_UNKNOWN) { - // Wrong argument types - EMSG2(_(e_invarg2), "expected dictionary"); - return; - } - - const char *mode = tv_get_string(&argvars[0]); - const char *address = tv_get_string(&argvars[1]); - - bool tcp; - if (strcmp(mode, "tcp") == 0) { - tcp = true; - } else if (strcmp(mode, "pipe") == 0) { - tcp = false; - } else { - EMSG2(_(e_invarg2), "invalid mode"); - return; - } - - bool rpc = false; - CallbackReader on_data = CALLBACK_READER_INIT; - if (argvars[2].v_type == VAR_DICT) { - dict_T *opts = argvars[2].vval.v_dict; - rpc = tv_dict_get_number(opts, "rpc") != 0; - - if (!tv_dict_get_callback(opts, S_LEN("on_data"), &on_data.cb)) { - return; - } - on_data.buffered = tv_dict_get_number(opts, "data_buffered"); - if (on_data.buffered && on_data.cb.type == kCallbackNone) { - on_data.self = opts; - } - } - - const char *error = NULL; - uint64_t id = channel_connect(tcp, address, rpc, on_data, 50, &error); - - if (error) { - EMSG2(_("connection failed: %s"), error); - } - - rettv->vval.v_number = (varnumber_T)id; - rettv->v_type = VAR_NUMBER; -} - -/// struct storing information about current sort -typedef struct { - int item_compare_ic; - bool item_compare_numeric; - bool item_compare_numbers; - bool item_compare_float; - const char *item_compare_func; - partial_T *item_compare_partial; - dict_T *item_compare_selfdict; - bool item_compare_func_err; -} sortinfo_T; -static sortinfo_T *sortinfo = NULL; - -#define ITEM_COMPARE_FAIL 999 - -/* - * Compare functions for f_sort() and f_uniq() below. - */ -static int item_compare(const void *s1, const void *s2, bool keep_zero) -{ - ListSortItem *const si1 = (ListSortItem *)s1; - ListSortItem *const si2 = (ListSortItem *)s2; - - typval_T *const tv1 = TV_LIST_ITEM_TV(si1->item); - typval_T *const tv2 = TV_LIST_ITEM_TV(si2->item); - - int res; - - if (sortinfo->item_compare_numbers) { - const varnumber_T v1 = tv_get_number(tv1); - const varnumber_T v2 = tv_get_number(tv2); - - res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1; - goto item_compare_end; - } - - if (sortinfo->item_compare_float) { - const float_T v1 = tv_get_float(tv1); - const float_T v2 = tv_get_float(tv2); - - res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1; - goto item_compare_end; - } - - char *tofree1 = NULL; - char *tofree2 = NULL; - char *p1; - char *p2; - - // encode_tv2string() puts quotes around a string and allocates memory. Don't - // do that for string variables. Use a single quote when comparing with - // a non-string to do what the docs promise. - if (tv1->v_type == VAR_STRING) { - if (tv2->v_type != VAR_STRING || sortinfo->item_compare_numeric) { - p1 = "'"; - } else { - p1 = (char *)tv1->vval.v_string; - } - } else { - tofree1 = p1 = encode_tv2string(tv1, NULL); - } - if (tv2->v_type == VAR_STRING) { - if (tv1->v_type != VAR_STRING || sortinfo->item_compare_numeric) { - p2 = "'"; - } else { - p2 = (char *)tv2->vval.v_string; - } - } else { - tofree2 = p2 = encode_tv2string(tv2, NULL); - } - if (p1 == NULL) { - p1 = ""; - } - if (p2 == NULL) { - p2 = ""; - } - if (!sortinfo->item_compare_numeric) { - if (sortinfo->item_compare_ic) { - res = STRICMP(p1, p2); - } else { - res = STRCMP(p1, p2); - } - } else { - double n1, n2; - n1 = strtod(p1, &p1); - n2 = strtod(p2, &p2); - res = n1 == n2 ? 0 : n1 > n2 ? 1 : -1; - } - - xfree(tofree1); - xfree(tofree2); - -item_compare_end: - // When the result would be zero, compare the item indexes. Makes the - // sort stable. - if (res == 0 && !keep_zero) { - // WARNING: When using uniq si1 and si2 are actually listitem_T **, no - // indexes are there. - res = si1->idx > si2->idx ? 1 : -1; - } - return res; -} - -static int item_compare_keeping_zero(const void *s1, const void *s2) -{ - return item_compare(s1, s2, true); -} - -static int item_compare_not_keeping_zero(const void *s1, const void *s2) -{ - return item_compare(s1, s2, false); -} - -static int item_compare2(const void *s1, const void *s2, bool keep_zero) -{ - ListSortItem *si1, *si2; - int res; - typval_T rettv; - typval_T argv[3]; - int dummy; - const char *func_name; - partial_T *partial = sortinfo->item_compare_partial; - - // shortcut after failure in previous call; compare all items equal - if (sortinfo->item_compare_func_err) { - return 0; - } - - si1 = (ListSortItem *)s1; - si2 = (ListSortItem *)s2; - - if (partial == NULL) { - func_name = sortinfo->item_compare_func; - } else { - func_name = (const char *)partial_name(partial); - } - - // Copy the values. This is needed to be able to set v_lock to VAR_FIXED - // in the copy without changing the original list items. - tv_copy(TV_LIST_ITEM_TV(si1->item), &argv[0]); - tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]); - - rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this - res = call_func((const char_u *)func_name, - (int)STRLEN(func_name), - &rettv, 2, argv, NULL, 0L, 0L, &dummy, true, - partial, sortinfo->item_compare_selfdict); - tv_clear(&argv[0]); - tv_clear(&argv[1]); - - if (res == FAIL) { - res = ITEM_COMPARE_FAIL; - } else { - res = tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err); - } - if (sortinfo->item_compare_func_err) { - res = ITEM_COMPARE_FAIL; // return value has wrong type - } - tv_clear(&rettv); - - // When the result would be zero, compare the pointers themselves. Makes - // the sort stable. - if (res == 0 && !keep_zero) { - // WARNING: When using uniq si1 and si2 are actually listitem_T **, no - // indexes are there. - res = si1->idx > si2->idx ? 1 : -1; - } - - return res; -} - -static int item_compare2_keeping_zero(const void *s1, const void *s2) -{ - return item_compare2(s1, s2, true); -} - -static int item_compare2_not_keeping_zero(const void *s1, const void *s2) -{ - return item_compare2(s1, s2, false); -} - -/* - * "sort({list})" function - */ -static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) -{ - ListSortItem *ptrs; - long len; - long i; - - // Pointer to current info struct used in compare function. Save and restore - // the current one for nested calls. - sortinfo_T info; - sortinfo_T *old_sortinfo = sortinfo; - sortinfo = &info; - - const char *const arg_errmsg = (sort - ? N_("sort() argument") - : N_("uniq() argument")); - - if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listarg), sort ? "sort()" : "uniq()"); - } else { - list_T *const l = argvars[0].vval.v_list; - if (tv_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { - goto theend; - } - tv_list_set_ret(rettv, l); - - len = tv_list_len(l); - if (len <= 1) { - goto theend; // short list sorts pretty quickly - } - - info.item_compare_ic = false; - info.item_compare_numeric = false; - info.item_compare_numbers = false; - info.item_compare_float = false; - info.item_compare_func = NULL; - info.item_compare_partial = NULL; - info.item_compare_selfdict = NULL; - - if (argvars[1].v_type != VAR_UNKNOWN) { - /* optional second argument: {func} */ - if (argvars[1].v_type == VAR_FUNC) { - info.item_compare_func = (const char *)argvars[1].vval.v_string; - } else if (argvars[1].v_type == VAR_PARTIAL) { - info.item_compare_partial = argvars[1].vval.v_partial; - } else { - bool error = false; - - i = tv_get_number_chk(&argvars[1], &error); - if (error) { - goto theend; // type error; errmsg already given - } - if (i == 1) { - info.item_compare_ic = true; - } else if (argvars[1].v_type != VAR_NUMBER) { - info.item_compare_func = tv_get_string(&argvars[1]); - } else if (i != 0) { - EMSG(_(e_invarg)); - goto theend; - } - if (info.item_compare_func != NULL) { - if (*info.item_compare_func == NUL) { - // empty string means default sort - info.item_compare_func = NULL; - } else if (strcmp(info.item_compare_func, "n") == 0) { - info.item_compare_func = NULL; - info.item_compare_numeric = true; - } else if (strcmp(info.item_compare_func, "N") == 0) { - info.item_compare_func = NULL; - info.item_compare_numbers = true; - } else if (strcmp(info.item_compare_func, "f") == 0) { - info.item_compare_func = NULL; - info.item_compare_float = true; - } else if (strcmp(info.item_compare_func, "i") == 0) { - info.item_compare_func = NULL; - info.item_compare_ic = true; - } - } - } - - if (argvars[2].v_type != VAR_UNKNOWN) { - // optional third argument: {dict} - if (argvars[2].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - goto theend; - } - info.item_compare_selfdict = argvars[2].vval.v_dict; - } - } - - // Make an array with each entry pointing to an item in the List. - ptrs = xmalloc((size_t)(len * sizeof(ListSortItem))); - - if (sort) { - info.item_compare_func_err = false; - tv_list_item_sort(l, ptrs, - ((info.item_compare_func == NULL - && info.item_compare_partial == NULL) - ? item_compare_not_keeping_zero - : item_compare2_not_keeping_zero), - &info.item_compare_func_err); - if (info.item_compare_func_err) { - EMSG(_("E702: Sort compare function failed")); - } - } else { - ListSorter item_compare_func_ptr; - - // f_uniq(): ptrs will be a stack of items to remove. - info.item_compare_func_err = false; - if (info.item_compare_func != NULL - || info.item_compare_partial != NULL) { - item_compare_func_ptr = item_compare2_keeping_zero; - } else { - item_compare_func_ptr = item_compare_keeping_zero; - } - - int idx = 0; - for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l)) - ; li != NULL;) { - listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); - if (item_compare_func_ptr(&prev_li, &li) == 0) { - if (info.item_compare_func_err) { // -V547 - EMSG(_("E882: Uniq compare function failed")); - break; - } - li = tv_list_item_remove(l, li); - } else { - idx++; - li = TV_LIST_ITEM_NEXT(l, li); - } - } - } - - xfree(ptrs); - } - -theend: - sortinfo = old_sortinfo; -} - -/// "sort"({list})" function -static void f_sort(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - do_sort_uniq(argvars, rettv, true); -} - -/// "stdioopen()" function -static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type != VAR_DICT) { - EMSG(_(e_invarg)); - return; - } - - - bool rpc = false; - CallbackReader on_stdin = CALLBACK_READER_INIT; - dict_T *opts = argvars[0].vval.v_dict; - rpc = tv_dict_get_number(opts, "rpc") != 0; - - if (!tv_dict_get_callback(opts, S_LEN("on_stdin"), &on_stdin.cb)) { - return; - } - on_stdin.buffered = tv_dict_get_number(opts, "stdin_buffered"); - if (on_stdin.buffered && on_stdin.cb.type == kCallbackNone) { - on_stdin.self = opts; - } - - const char *error; - uint64_t id = channel_from_stdio(rpc, on_stdin, &error); - if (!id) { - EMSG2(e_stdiochan2, error); - } - - - rettv->vval.v_number = (varnumber_T)id; - rettv->v_type = VAR_NUMBER; -} - -/// "uniq({list})" function -static void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - do_sort_uniq(argvars, rettv, false); -} - -// "reltimefloat()" function -static void f_reltimefloat(typval_T *argvars , typval_T *rettv, FunPtr fptr) - FUNC_ATTR_NONNULL_ALL -{ - proftime_T tm; - - rettv->v_type = VAR_FLOAT; - rettv->vval.v_float = 0; - if (list2proftime(&argvars[0], &tm) == OK) { - rettv->vval.v_float = (float_T)profile_signed(tm) / 1000000000.0; - } -} - -/* - * "soundfold({word})" function - */ -static void f_soundfold(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - const char *const s = tv_get_string(&argvars[0]); - rettv->vval.v_string = (char_u *)eval_soundfold(s); -} - -/* - * "spellbadword()" function - */ -static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *word = ""; - hlf_T attr = HLF_COUNT; - size_t len = 0; - - if (argvars[0].v_type == VAR_UNKNOWN) { - // Find the start and length of the badly spelled word. - len = spell_move_to(curwin, FORWARD, true, true, &attr); - if (len != 0) { - word = (char *)get_cursor_pos_ptr(); - curwin->w_set_curswant = true; - } - } else if (curwin->w_p_spell && *curbuf->b_s.b_p_spl != NUL) { - const char *str = tv_get_string_chk(&argvars[0]); - int capcol = -1; - - if (str != NULL) { - // Check the argument for spelling. - while (*str != NUL) { - len = spell_check(curwin, (char_u *)str, &attr, &capcol, false); - if (attr != HLF_COUNT) { - word = str; - break; - } - str += len; - capcol -= len; - len = 0; - } - } - } - - assert(len <= INT_MAX); - tv_list_alloc_ret(rettv, 2); - tv_list_append_string(rettv->vval.v_list, word, len); - tv_list_append_string(rettv->vval.v_list, - (attr == HLF_SPB ? "bad" - : attr == HLF_SPR ? "rare" - : attr == HLF_SPL ? "local" - : attr == HLF_SPC ? "caps" - : NULL), -1); -} - -/* - * "spellsuggest()" function - */ -static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - bool typeerr = false; - int maxcount; - garray_T ga = GA_EMPTY_INIT_VALUE; - bool need_capital = false; - - if (curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) { - const char *const str = tv_get_string(&argvars[0]); - if (argvars[1].v_type != VAR_UNKNOWN) { - maxcount = tv_get_number_chk(&argvars[1], &typeerr); - if (maxcount <= 0) { - goto f_spellsuggest_return; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - need_capital = tv_get_number_chk(&argvars[2], &typeerr); - if (typeerr) { - goto f_spellsuggest_return; - } - } - } else { - maxcount = 25; - } - - spell_suggest_list(&ga, (char_u *)str, maxcount, need_capital, false); - } - -f_spellsuggest_return: - tv_list_alloc_ret(rettv, (ptrdiff_t)ga.ga_len); - for (int i = 0; i < ga.ga_len; i++) { - char *const p = ((char **)ga.ga_data)[i]; - tv_list_append_allocated_string(rettv->vval.v_list, p); - } - ga_clear(&ga); -} - -static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *save_cpo; - int match; - colnr_T col = 0; - bool keepempty = false; - bool typeerr = false; - - /* Make 'cpoptions' empty, the 'l' flag should not be used here. */ - save_cpo = p_cpo; - p_cpo = (char_u *)""; - - const char *str = tv_get_string(&argvars[0]); - const char *pat = NULL; - char patbuf[NUMBUFLEN]; - if (argvars[1].v_type != VAR_UNKNOWN) { - pat = tv_get_string_buf_chk(&argvars[1], patbuf); - if (pat == NULL) { - typeerr = true; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - keepempty = (bool)tv_get_number_chk(&argvars[2], &typeerr); - } - } - if (pat == NULL || *pat == NUL) { - pat = "[\\x01- ]\\+"; - } - - tv_list_alloc_ret(rettv, kListLenMayKnow); - - if (typeerr) { - return; - } - - regmatch_T regmatch = { - .regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING), - .startp = { NULL }, - .endp = { NULL }, - .rm_ic = false, - }; - if (regmatch.regprog != NULL) { - while (*str != NUL || keepempty) { - if (*str == NUL) { - match = false; // Empty item at the end. - } else { - match = vim_regexec_nl(®match, (char_u *)str, col); - } - const char *end; - if (match) { - end = (const char *)regmatch.startp[0]; - } else { - end = str + strlen(str); - } - if (keepempty || end > str || (tv_list_len(rettv->vval.v_list) > 0 - && *str != NUL - && match - && end < (const char *)regmatch.endp[0])) { - tv_list_append_string(rettv->vval.v_list, str, end - str); - } - if (!match) { - break; - } - // Advance to just after the match. - if (regmatch.endp[0] > (char_u *)str) { - col = 0; - } else { - // Don't get stuck at the same match. - col = (*mb_ptr2len)(regmatch.endp[0]); - } - str = (const char *)regmatch.endp[0]; - } - - vim_regfree(regmatch.regprog); - } - - p_cpo = save_cpo; -} - /// "stdpath()" helper for list results -static void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) +void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) FUNC_ATTR_NONNULL_ALL { const void *iter = NULL; @@ -17328,691 +7002,6 @@ static void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) xfree(dirs); } -/// "stdpath(type)" function -static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - const char *const p = tv_get_string_chk(&argvars[0]); - if (p == NULL) { - return; // Type error; errmsg already given. - } - - if (strequal(p, "config")) { - rettv->vval.v_string = (char_u *)get_xdg_home(kXDGConfigHome); - } else if (strequal(p, "data")) { - rettv->vval.v_string = (char_u *)get_xdg_home(kXDGDataHome); - } else if (strequal(p, "cache")) { - rettv->vval.v_string = (char_u *)get_xdg_home(kXDGCacheHome); - } else if (strequal(p, "config_dirs")) { - get_xdg_var_list(kXDGConfigDirs, rettv); - } else if (strequal(p, "data_dirs")) { - get_xdg_var_list(kXDGDataDirs, rettv); - } else { - EMSG2(_("E6100: \"%s\" is not a valid stdpath"), p); - } -} - -/* - * "str2float()" function - */ -static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *p = skipwhite((const char_u *)tv_get_string(&argvars[0])); - bool isneg = (*p == '-'); - - if (*p == '+' || *p == '-') { - p = skipwhite(p + 1); - } - (void)string2float((char *)p, &rettv->vval.v_float); - if (isneg) { - rettv->vval.v_float *= -1; - } - rettv->v_type = VAR_FLOAT; -} - -// "str2nr()" function -static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int base = 10; - varnumber_T n; - int what; - - if (argvars[1].v_type != VAR_UNKNOWN) { - base = tv_get_number(&argvars[1]); - if (base != 2 && base != 8 && base != 10 && base != 16) { - EMSG(_(e_invarg)); - return; - } - } - - char_u *p = skipwhite((const char_u *)tv_get_string(&argvars[0])); - bool isneg = (*p == '-'); - if (*p == '+' || *p == '-') { - p = skipwhite(p + 1); - } - switch (base) { - case 2: { - what = STR2NR_BIN | STR2NR_FORCE; - break; - } - case 8: { - what = STR2NR_OCT | STR2NR_FORCE; - break; - } - case 16: { - what = STR2NR_HEX | STR2NR_FORCE; - break; - } - default: { - what = 0; - } - } - vim_str2nr(p, NULL, NULL, what, &n, NULL, 0); - if (isneg) { - rettv->vval.v_number = -n; - } else { - rettv->vval.v_number = n; - } -} - -/* - * "strftime({format}[, {time}])" function - */ -static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - time_t seconds; - - rettv->v_type = VAR_STRING; - - char *p = (char *)tv_get_string(&argvars[0]); - if (argvars[1].v_type == VAR_UNKNOWN) { - seconds = time(NULL); - } else { - seconds = (time_t)tv_get_number(&argvars[1]); - } - - struct tm curtime; - struct tm *curtime_ptr = os_localtime_r(&seconds, &curtime); - /* MSVC returns NULL for an invalid value of seconds. */ - if (curtime_ptr == NULL) - rettv->vval.v_string = vim_strsave((char_u *)_("(Invalid)")); - else { - vimconv_T conv; - char_u *enc; - - conv.vc_type = CONV_NONE; - enc = enc_locale(); - convert_setup(&conv, p_enc, enc); - if (conv.vc_type != CONV_NONE) { - p = (char *)string_convert(&conv, (char_u *)p, NULL); - } - char result_buf[256]; - if (p != NULL) { - (void)strftime(result_buf, sizeof(result_buf), p, curtime_ptr); - } else { - result_buf[0] = NUL; - } - - if (conv.vc_type != CONV_NONE) { - xfree(p); - } - convert_setup(&conv, enc, p_enc); - if (conv.vc_type != CONV_NONE) { - rettv->vval.v_string = string_convert(&conv, (char_u *)result_buf, NULL); - } else { - rettv->vval.v_string = (char_u *)xstrdup(result_buf); - } - - // Release conversion descriptors. - convert_setup(&conv, NULL, NULL); - xfree(enc); - } -} - -// "strgetchar()" function -static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; - - const char *const str = tv_get_string_chk(&argvars[0]); - if (str == NULL) { - return; - } - bool error = false; - varnumber_T charidx = tv_get_number_chk(&argvars[1], &error); - if (error) { - return; - } - - const size_t len = STRLEN(str); - size_t byteidx = 0; - - while (charidx >= 0 && byteidx < len) { - if (charidx == 0) { - rettv->vval.v_number = utf_ptr2char((const char_u *)str + byteidx); - break; - } - charidx--; - byteidx += MB_CPTR2LEN((const char_u *)str + byteidx); - } -} - -/* - * "stridx()" function - */ -static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; - - char buf[NUMBUFLEN]; - const char *const needle = tv_get_string_chk(&argvars[1]); - const char *haystack = tv_get_string_buf_chk(&argvars[0], buf); - const char *const haystack_start = haystack; - if (needle == NULL || haystack == NULL) { - return; // Type error; errmsg already given. - } - - if (argvars[2].v_type != VAR_UNKNOWN) { - bool error = false; - - const ptrdiff_t start_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], - &error); - if (error || start_idx >= (ptrdiff_t)strlen(haystack)) { - return; - } - if (start_idx >= 0) { - haystack += start_idx; - } - } - - const char *pos = strstr(haystack, needle); - if (pos != NULL) { - rettv->vval.v_number = (varnumber_T)(pos - haystack_start); - } -} - -/* - * "string()" function - */ -static void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *) encode_tv2string(&argvars[0], NULL); -} - -/* - * "strlen()" function - */ -static void f_strlen(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0])); -} - -/* - * "strchars()" function - */ -static void f_strchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *s = tv_get_string(&argvars[0]); - int skipcc = 0; - varnumber_T len = 0; - int (*func_mb_ptr2char_adv)(const char_u **pp); - - if (argvars[1].v_type != VAR_UNKNOWN) { - skipcc = tv_get_number_chk(&argvars[1], NULL); - } - if (skipcc < 0 || skipcc > 1) { - EMSG(_(e_invarg)); - } else { - func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv; - while (*s != NUL) { - func_mb_ptr2char_adv((const char_u **)&s); - len++; - } - rettv->vval.v_number = len; - } -} - -/* - * "strdisplaywidth()" function - */ -static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const s = tv_get_string(&argvars[0]); - int col = 0; - - if (argvars[1].v_type != VAR_UNKNOWN) { - col = tv_get_number(&argvars[1]); - } - - rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char_u *)s) - col); -} - -/* - * "strwidth()" function - */ -static void f_strwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const s = tv_get_string(&argvars[0]); - - rettv->vval.v_number = (varnumber_T)mb_string2cells((const char_u *)s); -} - -// "strcharpart()" function -static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const p = tv_get_string(&argvars[0]); - const size_t slen = STRLEN(p); - - int nbyte = 0; - bool error = false; - varnumber_T nchar = tv_get_number_chk(&argvars[1], &error); - if (!error) { - if (nchar > 0) { - while (nchar > 0 && (size_t)nbyte < slen) { - nbyte += MB_CPTR2LEN((const char_u *)p + nbyte); - nchar--; - } - } else { - nbyte = nchar; - } - } - int len = 0; - if (argvars[2].v_type != VAR_UNKNOWN) { - int charlen = tv_get_number(&argvars[2]); - while (charlen > 0 && nbyte + len < (int)slen) { - int off = nbyte + len; - - if (off < 0) { - len += 1; - } else { - len += (size_t)MB_CPTR2LEN((const char_u *)p + off); - } - charlen--; - } - } else { - len = slen - nbyte; // default: all bytes that are available. - } - - // Only return the overlap between the specified part and the actual - // string. - if (nbyte < 0) { - len += nbyte; - nbyte = 0; - } else if ((size_t)nbyte > slen) { - nbyte = slen; - } - if (len < 0) { - len = 0; - } else if (nbyte + len > (int)slen) { - len = slen - nbyte; - } - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)xstrndup(p + nbyte, (size_t)len); -} - -/* - * "strpart()" function - */ -static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - bool error = false; - - const char *const p = tv_get_string(&argvars[0]); - const size_t slen = strlen(p); - - varnumber_T n = tv_get_number_chk(&argvars[1], &error); - varnumber_T len; - if (error) { - len = 0; - } else if (argvars[2].v_type != VAR_UNKNOWN) { - len = tv_get_number(&argvars[2]); - } else { - len = slen - n; // Default len: all bytes that are available. - } - - // Only return the overlap between the specified part and the actual - // string. - if (n < 0) { - len += n; - n = 0; - } else if (n > (varnumber_T)slen) { - n = slen; - } - if (len < 0) { - len = 0; - } else if (n + len > (varnumber_T)slen) { - len = slen - n; - } - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)xmemdupz(p + n, (size_t)len); -} - -/* - * "strridx()" function - */ -static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char buf[NUMBUFLEN]; - const char *const needle = tv_get_string_chk(&argvars[1]); - const char *const haystack = tv_get_string_buf_chk(&argvars[0], buf); - - rettv->vval.v_number = -1; - if (needle == NULL || haystack == NULL) { - return; // Type error; errmsg already given. - } - - const size_t haystack_len = STRLEN(haystack); - ptrdiff_t end_idx; - if (argvars[2].v_type != VAR_UNKNOWN) { - // Third argument: upper limit for index. - end_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], NULL); - if (end_idx < 0) { - return; // Can never find a match. - } - } else { - end_idx = (ptrdiff_t)haystack_len; - } - - const char *lastmatch = NULL; - if (*needle == NUL) { - // Empty string matches past the end. - lastmatch = haystack + end_idx; - } else { - for (const char *rest = haystack; *rest != NUL; rest++) { - rest = strstr(rest, needle); - if (rest == NULL || rest > haystack + end_idx) { - break; - } - lastmatch = rest; - } - } - - if (lastmatch != NULL) { - rettv->vval.v_number = (varnumber_T)(lastmatch - haystack); - } -} - -/* - * "strtrans()" function - */ -static void f_strtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)transstr(tv_get_string(&argvars[0])); -} - -/* - * "submatch()" function - */ -static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - bool error = false; - int no = (int)tv_get_number_chk(&argvars[0], &error); - if (error) { - return; - } - - if (no < 0 || no >= NSUBEXP) { - emsgf(_("E935: invalid submatch number: %d"), no); - return; - } - int retList = 0; - - if (argvars[1].v_type != VAR_UNKNOWN) { - retList = tv_get_number_chk(&argvars[1], &error); - if (error) { - return; - } - } - - if (retList == 0) { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = reg_submatch(no); - } else { - rettv->v_type = VAR_LIST; - rettv->vval.v_list = reg_submatch_list(no); - } -} - -/* - * "substitute()" function - */ -static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char patbuf[NUMBUFLEN]; - char subbuf[NUMBUFLEN]; - char flagsbuf[NUMBUFLEN]; - - const char *const str = tv_get_string_chk(&argvars[0]); - const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf); - const char *sub = NULL; - const char *const flg = tv_get_string_buf_chk(&argvars[3], flagsbuf); - - typval_T *expr = NULL; - if (tv_is_func(argvars[2])) { - expr = &argvars[2]; - } else { - sub = tv_get_string_buf_chk(&argvars[2], subbuf); - } - - rettv->v_type = VAR_STRING; - if (str == NULL || pat == NULL || (sub == NULL && expr == NULL) - || flg == NULL) { - rettv->vval.v_string = NULL; - } else { - rettv->vval.v_string = do_string_sub((char_u *)str, (char_u *)pat, - (char_u *)sub, expr, (char_u *)flg); - } -} - -/// "swapinfo(swap_filename)" function -static void f_swapinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_dict_alloc_ret(rettv); - get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict); -} - -/// "swapname(expr)" function -static void f_swapname(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - buf_T *buf = tv_get_buf(&argvars[0], false); - if (buf == NULL - || buf->b_ml.ml_mfp == NULL - || buf->b_ml.ml_mfp->mf_fname == NULL) { - rettv->vval.v_string = NULL; - } else { - rettv->vval.v_string = vim_strsave(buf->b_ml.ml_mfp->mf_fname); - } -} - -/// "synID(lnum, col, trans)" function -static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // -1 on type error (both) - const linenr_T lnum = tv_get_lnum(argvars); - const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1; - - bool transerr = false; - const int trans = tv_get_number_chk(&argvars[2], &transerr); - - int id = 0; - if (!transerr && lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count - && col >= 0 && (size_t)col < STRLEN(ml_get(lnum))) { - id = syn_get_id(curwin, lnum, col, trans, NULL, false); - } - - rettv->vval.v_number = id; -} - -/* - * "synIDattr(id, what [, mode])" function - */ -static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const int id = (int)tv_get_number(&argvars[0]); - const char *const what = tv_get_string(&argvars[1]); - int modec; - if (argvars[2].v_type != VAR_UNKNOWN) { - char modebuf[NUMBUFLEN]; - const char *const mode = tv_get_string_buf(&argvars[2], modebuf); - modec = TOLOWER_ASC(mode[0]); - if (modec != 'c' && modec != 'g') { - modec = 0; // Replace invalid with current. - } - } else if (ui_rgb_attached()) { - modec = 'g'; - } else { - modec = 'c'; - } - - - const char *p = NULL; - switch (TOLOWER_ASC(what[0])) { - case 'b': { - if (TOLOWER_ASC(what[1]) == 'g') { // bg[#] - p = highlight_color(id, what, modec); - } else { // bold - p = highlight_has_attr(id, HL_BOLD, modec); - } - break; - } - case 'f': { // fg[#] or font - p = highlight_color(id, what, modec); - break; - } - case 'i': { - if (TOLOWER_ASC(what[1]) == 'n') { // inverse - p = highlight_has_attr(id, HL_INVERSE, modec); - } else { // italic - p = highlight_has_attr(id, HL_ITALIC, modec); - } - break; - } - case 'n': { // name - p = get_highlight_name_ext(NULL, id - 1, false); - break; - } - case 'r': { // reverse - p = highlight_has_attr(id, HL_INVERSE, modec); - break; - } - case 's': { - if (TOLOWER_ASC(what[1]) == 'p') { // sp[#] - p = highlight_color(id, what, modec); - } else if (TOLOWER_ASC(what[1]) == 't' - && TOLOWER_ASC(what[2]) == 'r') { // strikethrough - p = highlight_has_attr(id, HL_STRIKETHROUGH, modec); - } else { // standout - p = highlight_has_attr(id, HL_STANDOUT, modec); - } - break; - } - case 'u': { - if (STRLEN(what) <= 5 || TOLOWER_ASC(what[5]) != 'c') { // underline - p = highlight_has_attr(id, HL_UNDERLINE, modec); - } else { // undercurl - p = highlight_has_attr(id, HL_UNDERCURL, modec); - } - break; - } - } - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)(p == NULL ? p : xstrdup(p)); -} - -/* - * "synIDtrans(id)" function - */ -static void f_synIDtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int id = tv_get_number(&argvars[0]); - - if (id > 0) { - id = syn_get_final_id(id); - } else { - id = 0; - } - - rettv->vval.v_number = id; -} - -/* - * "synconcealed(lnum, col)" function - */ -static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int syntax_flags = 0; - int cchar; - int matchid = 0; - char_u str[NUMBUFLEN]; - - tv_list_set_ret(rettv, NULL); - - // -1 on type error (both) - const linenr_T lnum = tv_get_lnum(argvars); - const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1; - - memset(str, NUL, sizeof(str)); - - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0 - && (size_t)col <= STRLEN(ml_get(lnum)) && curwin->w_p_cole > 0) { - (void)syn_get_id(curwin, lnum, col, false, NULL, false); - syntax_flags = get_syntax_info(&matchid); - - // get the conceal character - if ((syntax_flags & HL_CONCEAL) && curwin->w_p_cole < 3) { - cchar = syn_get_sub_char(); - if (cchar == NUL && curwin->w_p_cole == 1) { - cchar = (curwin->w_p_lcs_chars.conceal == NUL) - ? ' ' - : curwin->w_p_lcs_chars.conceal; - } - if (cchar != NUL) { - utf_char2bytes(cchar, str); - } - } - } - - tv_list_alloc_ret(rettv, 3); - tv_list_append_number(rettv->vval.v_list, (syntax_flags & HL_CONCEAL) != 0); - // -1 to auto-determine strlen - tv_list_append_string(rettv->vval.v_list, (const char *)str, -1); - tv_list_append_number(rettv->vval.v_list, matchid); -} - -/* - * "synstack(lnum, col)" function - */ -static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_set_ret(rettv, NULL); - - // -1 on type error (both) - const linenr_T lnum = tv_get_lnum(argvars); - const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1; - - if (lnum >= 1 - && lnum <= curbuf->b_ml.ml_line_count - && col >= 0 - && (size_t)col <= STRLEN(ml_get(lnum))) { - tv_list_alloc_ret(rettv, kListLenMayKnow); - (void)syn_get_id(curwin, lnum, col, false, NULL, true); - - int id; - int i = 0; - while ((id = syn_get_stack_item(i++)) >= 0) { - tv_list_append_number(rettv->vval.v_list, id); - } - } -} - static list_T *string_to_list(const char *str, size_t len, const bool keepempty) { if (!keepempty && str[len - 1] == NL) { @@ -18024,8 +7013,8 @@ static list_T *string_to_list(const char *str, size_t len, const bool keepempty) } // os_system wrapper. Handles 'verbose', :profile, and v:shell_error. -static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, - bool retlist) +void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, + bool retlist) { proftime_T wait_time; bool profiling = do_profiling == PROF_YES; @@ -18123,324 +7112,19 @@ static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, } } -/// f_system - the VimL system() function -static void f_system(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - get_system_output_as_rettv(argvars, rettv, false); -} - -static void f_systemlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - get_system_output_as_rettv(argvars, rettv, true); -} - - -/* - * "tabpagebuflist()" function - */ -static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = NULL; - - if (argvars[0].v_type == VAR_UNKNOWN) { - wp = firstwin; - } else { - tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0])); - if (tp != NULL) { - wp = (tp == curtab) ? firstwin : tp->tp_firstwin; - } - } - if (wp != NULL) { - tv_list_alloc_ret(rettv, kListLenMayKnow); - while (wp != NULL) { - tv_list_append_number(rettv->vval.v_list, wp->w_buffer->b_fnum); - wp = wp->w_next; - } - } -} - -/* - * "tabpagenr()" function - */ -static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int nr = 1; - - if (argvars[0].v_type != VAR_UNKNOWN) { - const char *const arg = tv_get_string_chk(&argvars[0]); - nr = 0; - if (arg != NULL) { - if (strcmp(arg, "$") == 0) { - nr = tabpage_index(NULL) - 1; - } else if (strcmp(arg, "#") == 0) { - nr = valid_tabpage(lastused_tabpage) - ? tabpage_index(lastused_tabpage) - : nr; - } else { - EMSG2(_(e_invexpr2), arg); - } - } - } else { - nr = tabpage_index(curtab); - } - rettv->vval.v_number = nr; -} - - - -/* - * Common code for tabpagewinnr() and winnr(). - */ -static int get_winnr(tabpage_T *tp, typval_T *argvar) -{ - win_T *twin; - int nr = 1; - win_T *wp; - - twin = (tp == curtab) ? curwin : tp->tp_curwin; - if (argvar->v_type != VAR_UNKNOWN) { - bool invalid_arg = false; - const char *const arg = tv_get_string_chk(argvar); - if (arg == NULL) { - nr = 0; // Type error; errmsg already given. - } else if (strcmp(arg, "$") == 0) { - twin = (tp == curtab) ? lastwin : tp->tp_lastwin; - } else if (strcmp(arg, "#") == 0) { - twin = (tp == curtab) ? prevwin : tp->tp_prevwin; - if (twin == NULL) { - nr = 0; - } - } else { - // Extract the window count (if specified). e.g. winnr('3j') - char_u *endp; - long count = strtol((char *)arg, (char **)&endp, 10); - if (count <= 0) { - // if count is not specified, default to 1 - count = 1; - } - if (endp != NULL && *endp != '\0') { - if (strequal((char *)endp, "j")) { - twin = win_vert_neighbor(tp, twin, false, count); - } else if (strequal((char *)endp, "k")) { - twin = win_vert_neighbor(tp, twin, true, count); - } else if (strequal((char *)endp, "h")) { - twin = win_horz_neighbor(tp, twin, true, count); - } else if (strequal((char *)endp, "l")) { - twin = win_horz_neighbor(tp, twin, false, count); - } else { - invalid_arg = true; - } - } else { - invalid_arg = true; - } - } - - if (invalid_arg) { - EMSG2(_(e_invexpr2), arg); - nr = 0; - } - } - - if (nr > 0) - for (wp = (tp == curtab) ? firstwin : tp->tp_firstwin; - wp != twin; wp = wp->w_next) { - if (wp == NULL) { - /* didn't find it in this tabpage */ - nr = 0; - break; - } - ++nr; - } - return nr; -} - -/* - * "tabpagewinnr()" function - */ -static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int nr = 1; - tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0])); - if (tp == NULL) { - nr = 0; - } else { - nr = get_winnr(tp, &argvars[1]); - } - rettv->vval.v_number = nr; -} - -/* - * "tagfiles()" function - */ -static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char *fname; - tagname_T tn; - - tv_list_alloc_ret(rettv, kListLenUnknown); - fname = xmalloc(MAXPATHL); - - bool first = true; - while (get_tagfname(&tn, first, (char_u *)fname) == OK) { - tv_list_append_string(rettv->vval.v_list, fname, -1); - first = false; - } - - tagname_free(&tn); - xfree(fname); -} - -/* - * "taglist()" function - */ -static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const tag_pattern = tv_get_string(&argvars[0]); - - rettv->vval.v_number = false; - if (*tag_pattern == NUL) { - return; - } - - const char *fname = NULL; - if (argvars[1].v_type != VAR_UNKNOWN) { - fname = tv_get_string(&argvars[1]); - } - (void)get_tags(tv_list_alloc_ret(rettv, kListLenUnknown), - (char_u *)tag_pattern, (char_u *)fname); -} - -/* - * "tempname()" function - */ -static void f_tempname(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = vim_tempname(); -} - -// "termopen(cmd[, cwd])" function -static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (check_restricted() || check_secure()) { - return; - } - - if (curbuf->b_changed) { - EMSG(_("Can only call this function in an unmodified buffer")); - return; - } - - const char *cmd; - bool executable = true; - char **argv = tv_to_argv(&argvars[0], &cmd, &executable); - if (!argv) { - rettv->vval.v_number = executable ? 0 : -1; - return; // Did error message in tv_to_argv. - } - - if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) { - // Wrong argument type - EMSG2(_(e_invarg2), "expected dictionary"); - shell_free_argv(argv); - return; - } - - CallbackReader on_stdout = CALLBACK_READER_INIT, - on_stderr = CALLBACK_READER_INIT; - Callback on_exit = CALLBACK_NONE; - dict_T *job_opts = NULL; - const char *cwd = "."; - if (argvars[1].v_type == VAR_DICT) { - job_opts = argvars[1].vval.v_dict; - - const char *const new_cwd = tv_dict_get_string(job_opts, "cwd", false); - if (new_cwd && *new_cwd != NUL) { - cwd = new_cwd; - // The new cwd must be a directory. - if (!os_isdir_executable((const char *)cwd)) { - EMSG2(_(e_invarg2), "expected valid directory"); - shell_free_argv(argv); - return; - } - } - - if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) { - shell_free_argv(argv); - return; - } - } - - uint16_t term_width = MAX(0, curwin->w_width_inner - win_col_off(curwin)); - Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, - true, false, false, cwd, - term_width, curwin->w_height_inner, - xstrdup("xterm-256color"), NULL, - &rettv->vval.v_number); - if (rettv->vval.v_number <= 0) { - return; - } - - int pid = chan->stream.pty.process.pid; - - // "./…" => "/home/foo/…" - vim_FullName(cwd, (char *)NameBuff, sizeof(NameBuff), false); - // "/home/foo/…" => "~/…" - size_t len = home_replace(NULL, NameBuff, IObuff, sizeof(IObuff), true); - // Trim slash. - if (IObuff[len - 1] == '\\' || IObuff[len - 1] == '/') { - IObuff[len - 1] = '\0'; - } - - // Terminal URI: "term://$CWD//$PID:$CMD" - snprintf((char *)NameBuff, sizeof(NameBuff), "term://%s//%d:%s", - (char *)IObuff, pid, cmd); - // at this point the buffer has no terminal instance associated yet, so unset - // the 'swapfile' option to ensure no swap file will be created - curbuf->b_p_swf = false; - (void)setfname(curbuf, NameBuff, NULL, true); - // Save the job id and pid in b:terminal_job_{id,pid} - Error err = ERROR_INIT; - // deprecated: use 'channel' buffer option - dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"), - INTEGER_OBJ(chan->id), false, false, &err); - api_clear_error(&err); - dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_pid"), - INTEGER_OBJ(pid), false, false, &err); - api_clear_error(&err); - - channel_terminal_open(chan); - channel_create_event(chan, NULL); -} - -// "test_garbagecollect_now()" function -static void f_test_garbagecollect_now(typval_T *argvars, - typval_T *rettv, FunPtr fptr) -{ - // This is dangerous, any Lists and Dicts used internally may be freed - // while still in use. - garbage_collect(true); -} - -// "test_write_list_log()" function -static void f_test_write_list_log(typval_T *const argvars, - typval_T *const rettv, - FunPtr fptr) -{ - const char *const fname = tv_get_string_chk(&argvars[0]); - if (fname == NULL) { - return; - } - list_write_log(fname); -} - bool callback_from_typval(Callback *const callback, typval_T *const arg) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { + int r = OK; + if (arg->v_type == VAR_PARTIAL && arg->vval.v_partial != NULL) { callback->data.partial = arg->vval.v_partial; callback->data.partial->pt_refcount++; callback->type = kCallbackPartial; + } else if (arg->v_type == VAR_STRING + && arg->vval.v_string != NULL + && ascii_isdigit(*arg->vval.v_string)) { + r = FAIL; } else if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING) { char_u *name = arg->vval.v_string; func_ref(name); @@ -18449,6 +7133,10 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg) } else if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) { callback->type = kCallbackNone; } else { + r = FAIL; + } + + if (r == FAIL) { EMSG(_("E921: Invalid callback argument")); return false; } @@ -18526,7 +7214,12 @@ static bool set_ref_in_callback_reader(CallbackReader *reader, int copyID, return false; } -static void add_timer_info(typval_T *rettv, timer_T *timer) +timer_T *find_timer_by_nr(varnumber_T xx) +{ + return pmap_get(uint64_t)(timers, xx); +} + +void add_timer_info(typval_T *rettv, timer_T *timer) { list_T *list = rettv->vval.v_list; dict_T *dict = tv_dict_alloc(); @@ -18555,8 +7248,9 @@ static void add_timer_info(typval_T *rettv, timer_T *timer) } } -static void add_timer_info_all(typval_T *rettv) +void add_timer_info_all(typval_T *rettv) { + tv_list_alloc_ret(rettv, timers->table->n_occupied); timer_T *timer; map_foreach_value(timers, timer, { if (!timer->stopped) { @@ -18565,121 +7259,8 @@ static void add_timer_info_all(typval_T *rettv) }) } -/// "timer_info([timer])" function -static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, (argvars[0].v_type != VAR_UNKNOWN - ? 1 - : timers->table->n_occupied)); - if (argvars[0].v_type != VAR_UNKNOWN) { - if (argvars[0].v_type != VAR_NUMBER) { - EMSG(_(e_number_exp)); - return; - } - timer_T *timer = pmap_get(uint64_t)(timers, tv_get_number(&argvars[0])); - if (timer != NULL && !timer->stopped) { - add_timer_info(rettv, timer); - } - } else { - add_timer_info_all(rettv); - } -} - -/// "timer_pause(timer, paused)" function -static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr) -{ - if (argvars[0].v_type != VAR_NUMBER) { - EMSG(_(e_number_exp)); - return; - } - int paused = (bool)tv_get_number(&argvars[1]); - timer_T *timer = pmap_get(uint64_t)(timers, tv_get_number(&argvars[0])); - if (timer != NULL) { - if (!timer->paused && paused) { - time_watcher_stop(&timer->tw); - } else if (timer->paused && !paused) { - time_watcher_start(&timer->tw, timer_due_cb, timer->timeout, - timer->timeout); - } - timer->paused = paused; - } -} - -/// "timer_start(timeout, callback, opts)" function -static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const long timeout = tv_get_number(&argvars[0]); - timer_T *timer; - int repeat = 1; - dict_T *dict; - - rettv->vval.v_number = -1; - - if (argvars[2].v_type != VAR_UNKNOWN) { - if (argvars[2].v_type != VAR_DICT - || (dict = argvars[2].vval.v_dict) == NULL) { - EMSG2(_(e_invarg2), tv_get_string(&argvars[2])); - return; - } - dictitem_T *const di = tv_dict_find(dict, S_LEN("repeat")); - if (di != NULL) { - repeat = tv_get_number(&di->di_tv); - if (repeat == 0) { - repeat = 1; - } - } - } - - Callback callback; - if (!callback_from_typval(&callback, &argvars[1])) { - return; - } - - timer = xmalloc(sizeof *timer); - timer->refcount = 1; - timer->stopped = false; - timer->paused = false; - timer->emsg_count = 0; - timer->repeat_count = repeat; - timer->timeout = timeout; - timer->timer_id = last_timer_id++; - timer->callback = callback; - - time_watcher_init(&main_loop, &timer->tw, timer); - timer->tw.events = multiqueue_new_child(main_loop.events); - // if main loop is blocked, don't queue up multiple events - timer->tw.blockable = true; - time_watcher_start(&timer->tw, timer_due_cb, timeout, timeout); - - pmap_put(uint64_t)(timers, timer->timer_id, timer); - rettv->vval.v_number = timer->timer_id; -} - - -// "timer_stop(timerid)" function -static void f_timer_stop(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type != VAR_NUMBER) { - EMSG(_(e_number_exp)); - return; - } - - timer_T *timer = pmap_get(uint64_t)(timers, tv_get_number(&argvars[0])); - - if (timer == NULL) { - return; - } - - timer_stop(timer); -} - -static void f_timer_stopall(typval_T *argvars, typval_T *unused, FunPtr fptr) -{ - timer_stop_all(); -} - // invoked on the main loop -static void timer_due_cb(TimeWatcher *tw, void *data) +void timer_due_cb(TimeWatcher *tw, void *data) { timer_T *timer = (timer_T *)data; int save_did_emsg = did_emsg; @@ -18731,7 +7312,31 @@ static void timer_due_cb(TimeWatcher *tw, void *data) timer_decref(timer); } -static void timer_stop(timer_T *timer) +uint64_t timer_start(const long timeout, + const int repeat_count, + const Callback *const callback) +{ + timer_T *timer = xmalloc(sizeof *timer); + timer->refcount = 1; + timer->stopped = false; + timer->paused = false; + timer->emsg_count = 0; + timer->repeat_count = repeat_count; + timer->timeout = timeout; + timer->timer_id = last_timer_id++; + timer->callback = *callback; + + time_watcher_init(&main_loop, &timer->tw, timer); + timer->tw.events = multiqueue_new_child(main_loop.events); + // if main loop is blocked, don't queue up multiple events + timer->tw.blockable = true; + time_watcher_start(&timer->tw, timer_due_cb, timeout, timeout); + + pmap_put(uint64_t)(timers, timer->timer_id, timer); + return timer->timer_id; +} + +void timer_stop(timer_T *timer) { if (timer->stopped) { // avoid double free @@ -18760,7 +7365,7 @@ static void timer_decref(timer_T *timer) } } -static void timer_stop_all(void) +void timer_stop_all(void) { timer_T *timer; map_foreach_value(timers, timer, { @@ -18773,527 +7378,6 @@ void timer_teardown(void) timer_stop_all(); } -/* - * "tolower(string)" function - */ -static void f_tolower(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)strcase_save(tv_get_string(&argvars[0]), - false); -} - -/* - * "toupper(string)" function - */ -static void f_toupper(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)strcase_save(tv_get_string(&argvars[0]), - true); -} - -/* - * "tr(string, fromstr, tostr)" function - */ -static void f_tr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char buf[NUMBUFLEN]; - char buf2[NUMBUFLEN]; - - const char *in_str = tv_get_string(&argvars[0]); - const char *fromstr = tv_get_string_buf_chk(&argvars[1], buf); - const char *tostr = tv_get_string_buf_chk(&argvars[2], buf2); - - // Default return value: empty string. - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (fromstr == NULL || tostr == NULL) { - return; // Type error; errmsg already given. - } - garray_T ga; - ga_init(&ga, (int)sizeof(char), 80); - - if (!has_mbyte) { - // Not multi-byte: fromstr and tostr must be the same length. - if (strlen(fromstr) != strlen(tostr)) { - goto error; - } - } - - // fromstr and tostr have to contain the same number of chars. - bool first = true; - while (*in_str != NUL) { - if (has_mbyte) { - const char *cpstr = in_str; - const int inlen = (*mb_ptr2len)((const char_u *)in_str); - int cplen = inlen; - int idx = 0; - int fromlen; - for (const char *p = fromstr; *p != NUL; p += fromlen) { - fromlen = (*mb_ptr2len)((const char_u *)p); - if (fromlen == inlen && STRNCMP(in_str, p, inlen) == 0) { - int tolen; - for (p = tostr; *p != NUL; p += tolen) { - tolen = (*mb_ptr2len)((const char_u *)p); - if (idx-- == 0) { - cplen = tolen; - cpstr = (char *)p; - break; - } - } - if (*p == NUL) { // tostr is shorter than fromstr. - goto error; - } - break; - } - idx++; - } - - if (first && cpstr == in_str) { - // Check that fromstr and tostr have the same number of - // (multi-byte) characters. Done only once when a character - // of in_str doesn't appear in fromstr. - first = false; - int tolen; - for (const char *p = tostr; *p != NUL; p += tolen) { - tolen = (*mb_ptr2len)((const char_u *)p); - idx--; - } - if (idx != 0) { - goto error; - } - } - - ga_grow(&ga, cplen); - memmove((char *)ga.ga_data + ga.ga_len, cpstr, (size_t)cplen); - ga.ga_len += cplen; - - in_str += inlen; - } else { - // When not using multi-byte chars we can do it faster. - const char *const p = strchr(fromstr, *in_str); - if (p != NULL) { - ga_append(&ga, tostr[p - fromstr]); - } else { - ga_append(&ga, *in_str); - } - in_str++; - } - } - - // add a terminating NUL - ga_append(&ga, NUL); - - rettv->vval.v_string = ga.ga_data; - return; -error: - EMSG2(_(e_invarg2), fromstr); - ga_clear(&ga); - return; -} - -// "trim({expr})" function -static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char buf1[NUMBUFLEN]; - char buf2[NUMBUFLEN]; - const char_u *head = (const char_u *)tv_get_string_buf_chk(&argvars[0], buf1); - const char_u *mask = NULL; - const char_u *tail; - const char_u *prev; - const char_u *p; - int c1; - - rettv->v_type = VAR_STRING; - if (head == NULL) { - rettv->vval.v_string = NULL; - return; - } - - if (argvars[1].v_type == VAR_STRING) { - mask = (const char_u *)tv_get_string_buf_chk(&argvars[1], buf2); - } - - while (*head != NUL) { - c1 = PTR2CHAR(head); - if (mask == NULL) { - if (c1 > ' ' && c1 != 0xa0) { - break; - } - } else { - for (p = mask; *p != NUL; MB_PTR_ADV(p)) { - if (c1 == PTR2CHAR(p)) { - break; - } - } - if (*p == NUL) { - break; - } - } - MB_PTR_ADV(head); - } - - for (tail = head + STRLEN(head); tail > head; tail = prev) { - prev = tail; - MB_PTR_BACK(head, prev); - c1 = PTR2CHAR(prev); - if (mask == NULL) { - if (c1 > ' ' && c1 != 0xa0) { - break; - } - } else { - for (p = mask; *p != NUL; MB_PTR_ADV(p)) { - if (c1 == PTR2CHAR(p)) { - break; - } - } - if (*p == NUL) { - break; - } - } - } - rettv->vval.v_string = vim_strnsave(head, (int)(tail - head)); -} - -/* - * "type(expr)" function - */ -static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int n = -1; - - switch (argvars[0].v_type) { - case VAR_NUMBER: n = VAR_TYPE_NUMBER; break; - case VAR_STRING: n = VAR_TYPE_STRING; break; - case VAR_PARTIAL: - case VAR_FUNC: n = VAR_TYPE_FUNC; break; - case VAR_LIST: n = VAR_TYPE_LIST; break; - case VAR_DICT: n = VAR_TYPE_DICT; break; - case VAR_FLOAT: n = VAR_TYPE_FLOAT; break; - case VAR_SPECIAL: { - switch (argvars[0].vval.v_special) { - case kSpecialVarTrue: - case kSpecialVarFalse: { - n = VAR_TYPE_BOOL; - break; - } - case kSpecialVarNull: { - n = 7; - break; - } - } - break; - } - case VAR_UNKNOWN: { - internal_error("f_type(UNKNOWN)"); - break; - } - } - rettv->vval.v_number = n; -} - -/* - * "undofile(name)" function - */ -static void f_undofile(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - const char *const fname = tv_get_string(&argvars[0]); - - if (*fname == NUL) { - // If there is no file name there will be no undo file. - rettv->vval.v_string = NULL; - } else { - char *ffname = FullName_save(fname, true); - - if (ffname != NULL) { - rettv->vval.v_string = (char_u *)u_get_undo_file_name(ffname, false); - } - xfree(ffname); - } -} - -/* - * "undotree()" function - */ -static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_dict_alloc_ret(rettv); - - dict_T *dict = rettv->vval.v_dict; - - tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)curbuf->b_u_synced); - tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)curbuf->b_u_seq_last); - tv_dict_add_nr(dict, S_LEN("save_last"), - (varnumber_T)curbuf->b_u_save_nr_last); - tv_dict_add_nr(dict, S_LEN("seq_cur"), (varnumber_T)curbuf->b_u_seq_cur); - tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)curbuf->b_u_time_cur); - tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)curbuf->b_u_save_nr_cur); - - tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead)); -} - -/* - * "values(dict)" function - */ -static void f_values(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_list(argvars, rettv, 1); -} - -/* - * "virtcol(string)" function - */ -static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - colnr_T vcol = 0; - pos_T *fp; - int fnum = curbuf->b_fnum; - - fp = var2fpos(&argvars[0], FALSE, &fnum); - if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count - && fnum == curbuf->b_fnum) { - // Limit the column to a valid value, getvvcol() doesn't check. - if (fp->col < 0) { - fp->col = 0; - } else { - const size_t len = STRLEN(ml_get(fp->lnum)); - if (fp->col > (colnr_T)len) { - fp->col = (colnr_T)len; - } - } - getvvcol(curwin, fp, NULL, NULL, &vcol); - ++vcol; - } - - rettv->vval.v_number = vcol; -} - -/* - * "visualmode()" function - */ -static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u str[2]; - - rettv->v_type = VAR_STRING; - str[0] = curbuf->b_visual_mode_eval; - str[1] = NUL; - rettv->vval.v_string = vim_strsave(str); - - /* A non-zero number or non-empty string argument: reset mode. */ - if (non_zero_arg(&argvars[0])) - curbuf->b_visual_mode_eval = NUL; -} - -/* - * "wildmenumode()" function - */ -static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (wild_menu_showing || ((State & CMDLINE) && pum_visible())) { - rettv->vval.v_number = 1; - } -} - -/// "win_findbuf()" function -static void f_win_findbuf(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, kListLenMayKnow); - win_findbuf(argvars, rettv->vval.v_list); -} - -/// "win_getid()" function -static void f_win_getid(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = win_getid(argvars); -} - -/// "win_gotoid()" function -static void f_win_gotoid(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = win_gotoid(argvars); -} - -/// "win_id2tabwin()" function -static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_id2tabwin(argvars, rettv); -} - -/// "win_id2win()" function -static void f_win_id2win(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = win_id2win(argvars); -} - -/// "winbufnr(nr)" function -static void f_winbufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - rettv->vval.v_number = -1; - } else { - rettv->vval.v_number = wp->w_buffer->b_fnum; - } -} - -/* - * "wincol()" function - */ -static void f_wincol(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - validate_cursor(); - rettv->vval.v_number = curwin->w_wcol + 1; -} - -/// "winheight(nr)" function -static void f_winheight(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - rettv->vval.v_number = -1; - } else { - rettv->vval.v_number = wp->w_height; - } -} - -// "winlayout()" function -static void f_winlayout(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tabpage_T *tp; - - tv_list_alloc_ret(rettv, 2); - - if (argvars[0].v_type == VAR_UNKNOWN) { - tp = curtab; - } else { - tp = find_tabpage((int)tv_get_number(&argvars[0])); - if (tp == NULL) { - return; - } - } - - get_framelayout(tp->tp_topframe, rettv->vval.v_list, true); -} - -/* - * "winline()" function - */ -static void f_winline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - validate_cursor(); - rettv->vval.v_number = curwin->w_wrow + 1; -} - -/* - * "winnr()" function - */ -static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int nr = 1; - - nr = get_winnr(curtab, &argvars[0]); - rettv->vval.v_number = nr; -} - -/* - * "winrestcmd()" function - */ -static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int winnr = 1; - garray_T ga; - char_u buf[50]; - - ga_init(&ga, (int)sizeof(char), 70); - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - sprintf((char *)buf, "%dresize %d|", winnr, wp->w_height); - ga_concat(&ga, buf); - sprintf((char *)buf, "vert %dresize %d|", winnr, wp->w_width); - ga_concat(&ga, buf); - ++winnr; - } - ga_append(&ga, NUL); - - rettv->vval.v_string = ga.ga_data; - rettv->v_type = VAR_STRING; -} - -/* - * "winrestview()" function - */ -static void f_winrestview(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_T *dict; - - if (argvars[0].v_type != VAR_DICT - || (dict = argvars[0].vval.v_dict) == NULL) { - EMSG(_(e_invarg)); - } else { - dictitem_T *di; - if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) { - curwin->w_cursor.lnum = tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) { - curwin->w_cursor.col = tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) { - curwin->w_cursor.coladd = tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) { - curwin->w_curswant = tv_get_number(&di->di_tv); - curwin->w_set_curswant = false; - } - if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) { - set_topline(curwin, tv_get_number(&di->di_tv)); - } - if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) { - curwin->w_topfill = tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) { - curwin->w_leftcol = tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) { - curwin->w_skipcol = tv_get_number(&di->di_tv); - } - - check_cursor(); - win_new_height(curwin, curwin->w_height); - win_new_width(curwin, curwin->w_width); - changed_window_setting(); - - if (curwin->w_topline <= 0) - curwin->w_topline = 1; - if (curwin->w_topline > curbuf->b_ml.ml_line_count) - curwin->w_topline = curbuf->b_ml.ml_line_count; - check_topfill(curwin, true); - } -} - -/* - * "winsaveview()" function - */ -static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_T *dict; - - tv_dict_alloc_ret(rettv); - dict = rettv->vval.v_dict; - - tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)curwin->w_cursor.lnum); - tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)curwin->w_cursor.col); - tv_dict_add_nr(dict, S_LEN("coladd"), (varnumber_T)curwin->w_cursor.coladd); - update_curswant(); - tv_dict_add_nr(dict, S_LEN("curswant"), (varnumber_T)curwin->w_curswant); - - tv_dict_add_nr(dict, S_LEN("topline"), (varnumber_T)curwin->w_topline); - tv_dict_add_nr(dict, S_LEN("topfill"), (varnumber_T)curwin->w_topfill); - tv_dict_add_nr(dict, S_LEN("leftcol"), (varnumber_T)curwin->w_leftcol); - tv_dict_add_nr(dict, S_LEN("skipcol"), (varnumber_T)curwin->w_skipcol); -} - /// Write "list" of strings to file "fd". /// /// @param fp File to write to. @@ -19301,8 +7385,8 @@ static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// @param[in] binary Whether to write in binary mode. /// /// @return true in case of success, false otherwise. -static bool write_list(FileDescriptor *const fp, const list_T *const list, - const bool binary) +bool write_list(FileDescriptor *const fp, const list_T *const list, + const bool binary) FUNC_ATTR_NONNULL_ARG(1) { int error = 0; @@ -19360,7 +7444,7 @@ write_list_error: /// @param[in] endnl If true, the output will end in a newline (if a list). /// @returns an allocated string if `tv` represents a VimL string, list, or /// number; NULL otherwise. -static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) +char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { *len = 0; @@ -19439,101 +7523,6 @@ static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) return ret; } -/// "winwidth(nr)" function -static void f_winwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - rettv->vval.v_number = -1; - } else { - rettv->vval.v_number = wp->w_width; - } -} - -/// "wordcount()" function -static void f_wordcount(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_dict_alloc_ret(rettv); - cursor_pos_info(rettv->vval.v_dict); -} - -/// "writefile()" function -static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "writefile()"); - return; - } - const list_T *const list = argvars[0].vval.v_list; - TV_LIST_ITER_CONST(list, li, { - if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) { - return; - } - }); - - bool binary = false; - bool append = false; - bool do_fsync = !!p_fs; - if (argvars[2].v_type != VAR_UNKNOWN) { - const char *const flags = tv_get_string_chk(&argvars[2]); - if (flags == NULL) { - return; - } - for (const char *p = flags; *p; p++) { - switch (*p) { - case 'b': { binary = true; break; } - case 'a': { append = true; break; } - case 's': { do_fsync = true; break; } - case 'S': { do_fsync = false; break; } - default: { - // Using %s, p and not %c, *p to preserve multibyte characters - emsgf(_("E5060: Unknown flag: %s"), p); - return; - } - } - } - } - - char buf[NUMBUFLEN]; - const char *const fname = tv_get_string_buf_chk(&argvars[1], buf); - if (fname == NULL) { - return; - } - FileDescriptor fp; - int error; - if (*fname == NUL) { - EMSG(_("E482: Can't open file with an empty name")); - } else if ((error = file_open(&fp, fname, - ((append ? kFileAppend : kFileTruncate) - | kFileCreate), 0666)) != 0) { - emsgf(_("E482: Can't open file %s for writing: %s"), - fname, os_strerror(error)); - } else { - if (write_list(&fp, list, binary)) { - rettv->vval.v_number = 0; - } - if ((error = file_close(&fp, do_fsync)) != 0) { - emsgf(_("E80: Error when closing file %s: %s"), - fname, os_strerror(error)); - } - } -} -/* - * "xor(expr, expr)" function - */ -static void f_xor(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) - ^ tv_get_number_chk(&argvars[1], NULL); -} - - /// Translate a VimL object into a position /// /// Accepts VAR_LIST and VAR_STRING objects. Does not give an error for invalid @@ -19626,19 +7615,19 @@ pos_T *var2fpos(const typval_T *const tv, const int dollar_lnum, if (name[0] == 'w' && dollar_lnum) { pos.col = 0; - if (name[1] == '0') { /* "w0": first visible line */ + if (name[1] == '0') { // "w0": first visible line update_topline(); // In silent Ex mode topline is zero, but that's not a valid line // number; use one instead. pos.lnum = curwin->w_topline > 0 ? curwin->w_topline : 1; return &pos; - } else if (name[1] == '$') { /* "w$": last visible line */ + } else if (name[1] == '$') { // "w$": last visible line validate_botline(); // In silent Ex mode botline is zero, return zero then. pos.lnum = curwin->w_botline > 0 ? curwin->w_botline - 1 : 0; return &pos; } - } else if (name[0] == '$') { /* last column or line */ + } else if (name[0] == '$') { // last column or line if (dollar_lnum) { pos.lnum = curbuf->b_ml.ml_line_count; pos.col = 0; @@ -19735,7 +7724,7 @@ static int get_env_len(const char_u **arg) // Get the length of the name of a function or internal variable. // "arg" is advanced to the first non-white character after the name. // Return 0 if something is wrong. -static int get_id_len(const char **const arg) +int get_id_len(const char **const arg) { int len; @@ -19771,14 +7760,14 @@ static int get_id_len(const char **const arg) * If the name contains 'magic' {}'s, expand them and return the * expanded name in an allocated string via 'alias' - caller must free. */ -static int get_name_len(const char **const arg, - char **alias, - int evaluate, - int verbose) +int get_name_len(const char **const arg, + char **alias, + int evaluate, + int verbose) { int len; - *alias = NULL; /* default to no alias */ + *alias = NULL; // default to no alias if ((*arg)[0] == (char)K_SPECIAL && (*arg)[1] == (char)KS_EXTRA && (*arg)[2] == (char)KE_SNR) { @@ -19788,7 +7777,7 @@ static int get_name_len(const char **const arg, } len = eval_fname_script(*arg); if (len > 0) { - /* literal "<SID>", "s:" or "<SNR>" */ + // literal "<SID>", "s:" or "<SNR>" *arg += len; } @@ -19836,8 +7825,8 @@ static int get_name_len(const char **const arg, // "flags" can have FNE_INCL_BR and FNE_CHECK_START. // Return a pointer to just after the name. Equal to "arg" if there is no // valid name. -static const char_u *find_name_end(const char_u *arg, const char_u **expr_start, - const char_u **expr_end, int flags) +const char_u *find_name_end(const char_u *arg, const char_u **expr_start, + const char_u **expr_end, int flags) { int mb_nest = 0; int br_nest = 0; @@ -19951,7 +7940,7 @@ static char_u *make_expanded_name(const char_u *in_start, char_u *expr_start, } xfree(temp_result); - *in_end = c1; /* put char back for error messages */ + *in_end = c1; // put char back for error messages *expr_start = '{'; *expr_end = '}'; @@ -19960,7 +7949,7 @@ static char_u *make_expanded_name(const char_u *in_start, char_u *expr_start, (const char_u **)&expr_start, (const char_u **)&expr_end, 0); if (expr_start != NULL) { - /* Further expansion! */ + // Further expansion! temp_result = make_expanded_name(retval, expr_start, expr_end, temp_result); xfree(retval); @@ -19975,7 +7964,7 @@ static char_u *make_expanded_name(const char_u *in_start, char_u *expr_start, * Return TRUE if character "c" can be used in a variable or function name. * Does not include '{' or '}' for magic braces. */ -static int eval_isnamec(int c) +int eval_isnamec(int c) { return ASCII_ISALNUM(c) || c == '_' || c == ':' || c == AUTOLOAD_CHAR; } @@ -19984,7 +7973,7 @@ static int eval_isnamec(int c) * Return TRUE if character "c" can be used as the first character in a * variable or function name (excluding '{' and '}'). */ -static int eval_isnamec1(int c) +int eval_isnamec1(int c) { return ASCII_ISALPHA(c) || c == '_'; } @@ -20174,10 +8163,7 @@ char_u *v_throwpoint(char_u *oldval) */ char_u *set_cmdarg(exarg_T *eap, char_u *oldarg) { - char_u *oldval; - char_u *newval; - - oldval = vimvars[VV_CMDARG].vv_str; + char_u *oldval = vimvars[VV_CMDARG].vv_str; if (eap == NULL) { xfree(oldval); vimvars[VV_CMDARG].vv_str = oldarg; @@ -20193,14 +8179,18 @@ char_u *set_cmdarg(exarg_T *eap, char_u *oldarg) if (eap->read_edit) len += 7; - if (eap->force_ff != 0) - len += STRLEN(eap->cmd + eap->force_ff) + 6; - if (eap->force_enc != 0) + if (eap->force_ff != 0) { + len += 10; // " ++ff=unix" + } + if (eap->force_enc != 0) { len += STRLEN(eap->cmd + eap->force_enc) + 7; - if (eap->bad_char != 0) - len += 7 + 4; /* " ++bad=" + "keep" or "drop" */ + } + if (eap->bad_char != 0) { + len += 7 + 4; // " ++bad=" + "keep" or "drop" + } - newval = xmalloc(len + 1); + const size_t newval_len = len + 1; + char_u *newval = xmalloc(newval_len); if (eap->force_bin == FORCE_BIN) sprintf((char *)newval, " ++bin"); @@ -20212,18 +8202,23 @@ char_u *set_cmdarg(exarg_T *eap, char_u *oldarg) if (eap->read_edit) STRCAT(newval, " ++edit"); - if (eap->force_ff != 0) - sprintf((char *)newval + STRLEN(newval), " ++ff=%s", - eap->cmd + eap->force_ff); - if (eap->force_enc != 0) - sprintf((char *)newval + STRLEN(newval), " ++enc=%s", - eap->cmd + eap->force_enc); - if (eap->bad_char == BAD_KEEP) + if (eap->force_ff != 0) { + snprintf((char *)newval + STRLEN(newval), newval_len, " ++ff=%s", + eap->force_ff == 'u' ? "unix" : + eap->force_ff == 'd' ? "dos" : "mac"); + } + if (eap->force_enc != 0) { + snprintf((char *)newval + STRLEN(newval), newval_len, " ++enc=%s", + eap->cmd + eap->force_enc); + } + if (eap->bad_char == BAD_KEEP) { STRCPY(newval + STRLEN(newval), " ++bad=keep"); - else if (eap->bad_char == BAD_DROP) + } else if (eap->bad_char == BAD_DROP) { STRCPY(newval + STRLEN(newval), " ++bad=drop"); - else if (eap->bad_char != 0) - sprintf((char *)newval + STRLEN(newval), " ++bad=%c", eap->bad_char); + } else if (eap->bad_char != 0) { + snprintf((char *)newval + STRLEN(newval), newval_len, " ++bad=%c", + eap->bad_char); + } vimvars[VV_CMDARG].vv_str = newval; return oldval; } @@ -20232,7 +8227,7 @@ char_u *set_cmdarg(exarg_T *eap, char_u *oldarg) * Get the value of internal variable "name". * Return OK or FAIL. */ -static int get_var_tv( +int get_var_tv( const char *name, int len, // length of "name" typval_T *rettv, // NULL when only checking existence @@ -20266,7 +8261,7 @@ static int get_var_tv( } /// Check if variable "name[len]" is a local variable or an argument. -/// If so, "*eval_lavars_used" is set to TRUE. +/// If so, "*eval_lavars_used" is set to true. static void check_vars(const char *name, size_t len) { if (eval_lavars_used == NULL) { @@ -20284,13 +8279,19 @@ static void check_vars(const char *name, size_t len) } /// check if special v:lua value for calling lua functions +bool is_luafunc(partial_T *partial) +{ + return partial == vvlua_partial; +} + +/// check if special v:lua value for calling lua functions static bool tv_is_luafunc(typval_T *tv) { - return tv->v_type == VAR_PARTIAL && tv->vval.v_partial == vvlua_partial; + return tv->v_type == VAR_PARTIAL && is_luafunc(tv->vval.v_partial); } /// check the function name after "v:lua." -static int check_luafunc_name(const char *str, bool paren) +int check_luafunc_name(const char *str, bool paren) { const char *p = str; while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.') { @@ -20306,12 +8307,12 @@ static int check_luafunc_name(const char *str, bool paren) /// Handle expr[expr], expr[expr:expr] subscript and .name lookup. /// Also handle function call with Funcref variable: func(expr) /// Can all be combined: dict.func(expr)[idx]['func'](expr) -static int +int handle_subscript( const char **const arg, typval_T *rettv, - int evaluate, /* do more than finding the end */ - int verbose /* give error messages */ + int evaluate, // do more than finding the end + int verbose // give error messages ) { int ret = OK; @@ -20410,7 +8411,7 @@ handle_subscript( return ret; } -void set_selfdict(typval_T *rettv, dict_T *selfdict) +void set_selfdict(typval_T *const rettv, dict_T *const selfdict) { // Don't do this when "dict.Func" is already a partial that was bound // explicitly (pt_auto is false). @@ -20418,61 +8419,28 @@ void set_selfdict(typval_T *rettv, dict_T *selfdict) && rettv->vval.v_partial->pt_dict != NULL) { return; } - char_u *fname; - char_u *tofree = NULL; - ufunc_T *fp; - char_u fname_buf[FLEN_FIXED + 1]; - int error; + make_partial(selfdict, rettv); +} - if (rettv->v_type == VAR_PARTIAL && rettv->vval.v_partial->pt_func != NULL) { - fp = rettv->vval.v_partial->pt_func; - } else { - fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING - ? rettv->vval.v_string - : rettv->vval.v_partial->pt_name; - // Translate "s:func" to the stored function name. - fname = fname_trans_sid(fname, fname_buf, &tofree, &error); - fp = find_func(fname); - xfree(tofree); - } +// Turn a typeval into a string. Similar to tv_get_string_buf() but uses +// string() on Dict, List, etc. +static const char *tv_stringify(typval_T *varp, char *buf) + FUNC_ATTR_NONNULL_ALL +{ + if (varp->v_type == VAR_LIST + || varp->v_type == VAR_DICT + || varp->v_type == VAR_FUNC + || varp->v_type == VAR_PARTIAL + || varp->v_type == VAR_FLOAT) { + typval_T tmp; - // Turn "dict.Func" into a partial for "Func" with "dict". - if (fp != NULL && (fp->uf_flags & FC_DICT)) { - partial_T *pt = (partial_T *)xcalloc(1, sizeof(partial_T)); - pt->pt_refcount = 1; - pt->pt_dict = selfdict; - (selfdict->dv_refcount)++; - pt->pt_auto = true; - if (rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING) { - // Just a function: Take over the function name and use selfdict. - pt->pt_name = rettv->vval.v_string; - } else { - partial_T *ret_pt = rettv->vval.v_partial; - int i; - - // Partial: copy the function name, use selfdict and copy - // args. Can't take over name or args, the partial might - // be referenced elsewhere. - if (ret_pt->pt_name != NULL) { - pt->pt_name = vim_strsave(ret_pt->pt_name); - func_ref(pt->pt_name); - } else { - pt->pt_func = ret_pt->pt_func; - func_ptr_ref(pt->pt_func); - } - if (ret_pt->pt_argc > 0) { - size_t arg_size = sizeof(typval_T) * ret_pt->pt_argc; - pt->pt_argv = (typval_T *)xmalloc(arg_size); - pt->pt_argc = ret_pt->pt_argc; - for (i = 0; i < pt->pt_argc; i++) { - tv_copy(&ret_pt->pt_argv[i], &pt->pt_argv[i]); - } - } - partial_unref(ret_pt); - } - rettv->v_type = VAR_PARTIAL; - rettv->vval.v_partial = pt; + f_string(varp, &tmp, NULL); + const char *const res = tv_get_string_buf(&tmp, buf); + tv_clear(varp); + *varp = tmp; + return res; } + return tv_get_string_buf(varp, buf); } // Find variable "name" in the list of variables. @@ -20480,8 +8448,8 @@ void set_selfdict(typval_T *rettv, dict_T *selfdict) // Careful: "a:0" variables don't have a name. // When "htp" is not NULL we are writing to the variable, set "htp" to the // hashtab_T used. -static dictitem_T *find_var(const char *const name, const size_t name_len, - hashtab_T **htp, int no_autoload) +dictitem_T *find_var(const char *const name, const size_t name_len, + hashtab_T **htp, int no_autoload) { const char *varname; hashtab_T *const ht = find_var_ht(name, name_len, &varname); @@ -20503,7 +8471,8 @@ static dictitem_T *find_var(const char *const name, const size_t name_len, return find_var_in_scoped_ht(name, name_len, no_autoload || htp != NULL); } -/// Find variable in hashtab +/// Find variable in hashtab. +/// When "varname" is empty returns curwin/curtab/etc vars dictionary. /// /// @param[in] ht Hashtab to find variable in. /// @param[in] htname Hashtab name (first character). @@ -20514,11 +8483,11 @@ static dictitem_T *find_var(const char *const name, const size_t name_len, /// /// @return pointer to the dictionary item with the found variable or NULL if it /// was not found. -static dictitem_T *find_var_in_ht(hashtab_T *const ht, - int htname, - const char *const varname, - const size_t varname_len, - int no_autoload) +dictitem_T *find_var_in_ht(hashtab_T *const ht, + int htname, + const char *const varname, + const size_t varname_len, + int no_autoload) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { hashitem_T *hi; @@ -20532,10 +8501,8 @@ static dictitem_T *find_var_in_ht(hashtab_T *const ht, case 'b': return (dictitem_T *)&curbuf->b_bufvar; case 'w': return (dictitem_T *)&curwin->w_winvar; case 't': return (dictitem_T *)&curtab->tp_winvar; - case 'l': return (current_funccal == NULL - ? NULL : (dictitem_T *)¤t_funccal->l_vars_var); - case 'a': return (current_funccal == NULL - ? NULL : (dictitem_T *)&get_funccal()->l_avars_var); + case 'l': return get_funccal_local_var(); + case 'a': return get_funccal_args_var(); } return NULL; } @@ -20561,45 +8528,6 @@ static dictitem_T *find_var_in_ht(hashtab_T *const ht, return TV_DICT_HI2DI(hi); } -// Get function call environment based on backtrace debug level -static funccall_T *get_funccal(void) -{ - funccall_T *funccal = current_funccal; - if (debug_backtrace_level > 0) { - for (int i = 0; i < debug_backtrace_level; i++) { - funccall_T *temp_funccal = funccal->caller; - if (temp_funccal) { - funccal = temp_funccal; - } else { - // backtrace level overflow. reset to max - debug_backtrace_level = i; - } - } - } - - return funccal; -} - -/// Return the hashtable used for argument in the current funccal. -/// Return NULL if there is no current funccal. -static hashtab_T *get_funccal_args_ht(void) -{ - if (current_funccal == NULL) { - return NULL; - } - return &get_funccal()->l_avars.dv_hashtab; -} - -/// Return the hashtable used for local variables in the current funccal. -/// Return NULL if there is no current funccal. -static hashtab_T *get_funccal_local_ht(void) -{ - if (current_funccal == NULL) { - return NULL; - } - return &get_funccal()->l_vars.dv_hashtab; -} - /// Finds the dict (g:, l:, s:, …) and hashtable used for a variable. /// /// @param[in] name Variable name, possibly with scope prefix. @@ -20613,6 +8541,7 @@ static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, const char **varname, dict_T **d) { hashitem_T *hi; + funccall_T *funccal = get_funccal(); *d = NULL; if (name_len == 0) { @@ -20632,16 +8561,16 @@ static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, return &compat_hashtab; } - if (current_funccal == NULL) { + if (funccal == NULL) { // global variable *d = &globvardict; - } else { - *d = &get_funccal()->l_vars; // l: variable + } else { // l: variable + *d = &funccal->l_vars; } goto end; } *varname = name + 2; - if (*name == 'g') { // global variable + if (*name == 'g') { // global variable *d = &globvardict; } else if (name_len > 2 && (memchr(name + 2, ':', name_len - 2) != NULL @@ -20658,10 +8587,10 @@ static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, *d = curtab->tp_vars; } else if (*name == 'v') { // v: variable *d = &vimvardict; - } else if (*name == 'a' && current_funccal != NULL) { // function argument - *d = &get_funccal()->l_avars; - } else if (*name == 'l' && current_funccal != NULL) { // local variable - *d = &get_funccal()->l_vars; + } else if (*name == 'a' && funccal != NULL) { // function argument + *d = &funccal->l_avars; + } else if (*name == 'l' && funccal != NULL) { // local variable + *d = &funccal->l_vars; } else if (*name == 's' // script variable && current_sctx.sc_sid > 0 && current_sctx.sc_sid <= ga_scripts.ga_len) { @@ -20680,8 +8609,8 @@ end: /// prefix. /// /// @return Scope hashtab, NULL if name is not valid. -static hashtab_T *find_var_ht(const char *name, const size_t name_len, - const char **varname) +hashtab_T *find_var_ht(const char *name, const size_t name_len, + const char **varname) { dict_T *d; return find_var_ht_dict(name, name_len, varname, &d); @@ -20776,7 +8705,7 @@ void vars_clear(hashtab_T *ht) /* * Like vars_clear(), but only free the value if "free_val" is TRUE. */ -static void vars_clear_ext(hashtab_T *ht, int free_val) +void vars_clear_ext(hashtab_T *ht, int free_val) { int todo; hashitem_T *hi; @@ -20878,8 +8807,8 @@ static void list_one_var_a(const char *prefix, const char *name, /// @param[in] name_len Length of the variable name. /// @param tv Variable value. /// @param[in] copy True if value in tv is to be copied. -static void set_var(const char *name, const size_t name_len, typval_T *const tv, - const bool copy) +void set_var(const char *name, const size_t name_len, typval_T *const tv, + const bool copy) FUNC_ATTR_NONNULL_ALL { set_var_const(name, name_len, tv, copy, false); @@ -21048,7 +8977,7 @@ bool var_check_ro(const int flags, const char *name, { const char *error_message = NULL; if (flags & DI_FLAGS_RO) { - error_message = N_(e_readonlyvar); + error_message = _(e_readonlyvar); } else if ((flags & DI_FLAGS_RO_SBX) && sandbox) { error_message = N_("E794: Cannot set variable in the sandbox: \"%.*s\""); } @@ -21086,8 +9015,8 @@ bool var_check_ro(const int flags, const char *name, /// gettext. /// /// @return True if variable is fixed, false otherwise. -static bool var_check_fixed(const int flags, const char *name, - size_t name_len) +bool var_check_fixed(const int flags, const char *name, + size_t name_len) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { if (flags & DI_FLAGS_FIX) { @@ -21229,10 +9158,10 @@ int var_item_copy(const vimconv_T *const conv, case VAR_DICT: to->v_type = VAR_DICT; to->v_lock = 0; - if (from->vval.v_dict == NULL) + if (from->vval.v_dict == NULL) { to->vval.v_dict = NULL; - else if (copyID != 0 && from->vval.v_dict->dv_copyID == copyID) { - /* use the copy made earlier */ + } else if (copyID != 0 && from->vval.v_dict->dv_copyID == copyID) { + // use the copy made earlier to->vval.v_dict = from->vval.v_dict->dv_copydict; ++to->vval.v_dict->dv_refcount; } else { @@ -21330,13 +9259,7 @@ void ex_echo(exarg_T *eap) */ void ex_echohl(exarg_T *eap) { - int id; - - id = syn_name2id(eap->arg); - if (id == 0) - echo_attr = 0; - else - echo_attr = syn_id2attr(id); + echo_attr = syn_name2attr(eap->arg); } /* @@ -21365,7 +9288,10 @@ void ex_execute(exarg_T *eap) } if (!eap->skip) { - const char *const argstr = tv_get_string(&rettv); + char buf[NUMBUFLEN]; + const char *const argstr = eap->cmdidx == CMD_execute + ? tv_get_string_buf(&rettv, buf) + : tv_stringify(&rettv, buf); const size_t len = strlen(argstr); ga_grow(&ga, len + 2); if (!GA_EMPTY(&ga)) { @@ -21392,7 +9318,7 @@ void ex_execute(exarg_T *eap) MSG_ATTR(ga.ga_data, echo_attr); ui_flush(); } else if (eap->cmdidx == CMD_echoerr) { - /* We don't want to abort following commands, restore did_emsg. */ + // We don't want to abort following commands, restore did_emsg. save_did_emsg = did_emsg; msg_ext_set_kind("echoerr"); EMSG((char_u *)ga.ga_data); @@ -21447,1123 +9373,8 @@ static const char *find_option_end(const char **const arg, int *const opt_flags) return p; } -/* - * ":function" - */ -void ex_function(exarg_T *eap) -{ - char_u *theline; - char_u *line_to_free = NULL; - int c; - int saved_did_emsg; - int saved_wait_return = need_wait_return; - char_u *name = NULL; - char_u *p; - char_u *arg; - char_u *line_arg = NULL; - garray_T newargs; - garray_T newlines; - int varargs = false; - int flags = 0; - ufunc_T *fp; - bool overwrite = false; - int indent; - int nesting; - dictitem_T *v; - funcdict_T fudi; - static int func_nr = 0; /* number for nameless function */ - int paren; - hashtab_T *ht; - int todo; - hashitem_T *hi; - linenr_T sourcing_lnum_off; - linenr_T sourcing_lnum_top; - bool is_heredoc = false; - char_u *skip_until = NULL; - char_u *heredoc_trimmed = NULL; - bool show_block = false; - bool do_concat = true; - - /* - * ":function" without argument: list functions. - */ - if (ends_excmd(*eap->arg)) { - if (!eap->skip) { - todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - --todo; - fp = HI2UF(hi); - if (message_filtered(fp->uf_name)) { - continue; - } - if (!func_name_refcount(fp->uf_name)) { - list_func_head(fp, false, false); - } - } - } - } - eap->nextcmd = check_nextcmd(eap->arg); - return; - } - - /* - * ":function /pat": list functions matching pattern. - */ - if (*eap->arg == '/') { - p = skip_regexp(eap->arg + 1, '/', TRUE, NULL); - if (!eap->skip) { - regmatch_T regmatch; - - c = *p; - *p = NUL; - regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC); - *p = c; - if (regmatch.regprog != NULL) { - regmatch.rm_ic = p_ic; - - todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - --todo; - fp = HI2UF(hi); - if (!isdigit(*fp->uf_name) - && vim_regexec(®match, fp->uf_name, 0)) - list_func_head(fp, false, false); - } - } - vim_regfree(regmatch.regprog); - } - } - if (*p == '/') - ++p; - eap->nextcmd = check_nextcmd(p); - return; - } - - // Get the function name. There are these situations: - // func function name - // "name" == func, "fudi.fd_dict" == NULL - // dict.func new dictionary entry - // "name" == NULL, "fudi.fd_dict" set, - // "fudi.fd_di" == NULL, "fudi.fd_newkey" == func - // dict.func existing dict entry with a Funcref - // "name" == func, "fudi.fd_dict" set, - // "fudi.fd_di" set, "fudi.fd_newkey" == NULL - // dict.func existing dict entry that's not a Funcref - // "name" == NULL, "fudi.fd_dict" set, - // "fudi.fd_di" set, "fudi.fd_newkey" == NULL - // s:func script-local function name - // g:func global function name, same as "func" - p = eap->arg; - name = trans_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL); - paren = (vim_strchr(p, '(') != NULL); - if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { - /* - * Return on an invalid expression in braces, unless the expression - * evaluation has been cancelled due to an aborting error, an - * interrupt, or an exception. - */ - if (!aborting()) { - if (fudi.fd_newkey != NULL) { - EMSG2(_(e_dictkey), fudi.fd_newkey); - } - xfree(fudi.fd_newkey); - return; - } else - eap->skip = TRUE; - } - - /* An error in a function call during evaluation of an expression in magic - * braces should not cause the function not to be defined. */ - saved_did_emsg = did_emsg; - did_emsg = FALSE; - - // - // ":function func" with only function name: list function. - // If bang is given: - // - include "!" in function head - // - exclude line numbers from function body - // - if (!paren) { - if (!ends_excmd(*skipwhite(p))) { - EMSG(_(e_trailing)); - goto ret_free; - } - eap->nextcmd = check_nextcmd(p); - if (eap->nextcmd != NULL) - *p = NUL; - if (!eap->skip && !got_int) { - fp = find_func(name); - if (fp != NULL) { - list_func_head(fp, !eap->forceit, eap->forceit); - for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) { - if (FUNCLINE(fp, j) == NULL) { - continue; - } - msg_putchar('\n'); - if (!eap->forceit) { - msg_outnum((long)j + 1); - if (j < 9) { - msg_putchar(' '); - } - if (j < 99) { - msg_putchar(' '); - } - } - msg_prt_line(FUNCLINE(fp, j), false); - ui_flush(); // show a line at a time - os_breakcheck(); - } - if (!got_int) { - msg_putchar('\n'); - msg_puts(eap->forceit ? "endfunction" : " endfunction"); - } - } else - emsg_funcname(N_("E123: Undefined function: %s"), name); - } - goto ret_free; - } - - /* - * ":function name(arg1, arg2)" Define function. - */ - p = skipwhite(p); - if (*p != '(') { - if (!eap->skip) { - EMSG2(_("E124: Missing '(': %s"), eap->arg); - goto ret_free; - } - /* attempt to continue by skipping some text */ - if (vim_strchr(p, '(') != NULL) - p = vim_strchr(p, '('); - } - p = skipwhite(p + 1); - - ga_init(&newargs, (int)sizeof(char_u *), 3); - ga_init(&newlines, (int)sizeof(char_u *), 3); - - if (!eap->skip) { - /* Check the name of the function. Unless it's a dictionary function - * (that we are overwriting). */ - if (name != NULL) - arg = name; - else - arg = fudi.fd_newkey; - if (arg != NULL && (fudi.fd_di == NULL || !tv_is_func(fudi.fd_di->di_tv))) { - int j = (*arg == K_SPECIAL) ? 3 : 0; - while (arg[j] != NUL && (j == 0 ? eval_isnamec1(arg[j]) - : eval_isnamec(arg[j]))) - ++j; - if (arg[j] != NUL) - emsg_funcname((char *)e_invarg2, arg); - } - /* Disallow using the g: dict. */ - if (fudi.fd_dict != NULL && fudi.fd_dict->dv_scope == VAR_DEF_SCOPE) - EMSG(_("E862: Cannot use g: here")); - } - - if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) { - goto errret_2; - } - - if (KeyTyped && ui_has(kUICmdline)) { - show_block = true; - ui_ext_cmdline_block_append(0, (const char *)eap->cmd); - } - - // find extra arguments "range", "dict", "abort" and "closure" - for (;; ) { - p = skipwhite(p); - if (STRNCMP(p, "range", 5) == 0) { - flags |= FC_RANGE; - p += 5; - } else if (STRNCMP(p, "dict", 4) == 0) { - flags |= FC_DICT; - p += 4; - } else if (STRNCMP(p, "abort", 5) == 0) { - flags |= FC_ABORT; - p += 5; - } else if (STRNCMP(p, "closure", 7) == 0) { - flags |= FC_CLOSURE; - p += 7; - if (current_funccal == NULL) { - emsg_funcname(N_ - ("E932: Closure function should not be at top level: %s"), - name == NULL ? (char_u *)"" : name); - goto erret; - } - } else { - break; - } - } - - /* When there is a line break use what follows for the function body. - * Makes 'exe "func Test()\n...\nendfunc"' work. */ - if (*p == '\n') { - line_arg = p + 1; - } else if (*p != NUL && *p != '"' && !eap->skip && !did_emsg) { - EMSG(_(e_trailing)); - } - - /* - * Read the body of the function, until ":endfunction" is found. - */ - if (KeyTyped) { - /* Check if the function already exists, don't let the user type the - * whole function before telling him it doesn't work! For a script we - * need to skip the body to be able to find what follows. */ - if (!eap->skip && !eap->forceit) { - if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL) - EMSG(_(e_funcdict)); - else if (name != NULL && find_func(name) != NULL) - emsg_funcname(e_funcexts, name); - } - - if (!eap->skip && did_emsg) - goto erret; - - if (!ui_has(kUICmdline)) { - msg_putchar('\n'); // don't overwrite the function name - } - cmdline_row = msg_row; - } - - // Save the starting line number. - sourcing_lnum_top = sourcing_lnum; - - indent = 2; - nesting = 0; - for (;; ) { - if (KeyTyped) { - msg_scroll = true; - saved_wait_return = false; - } - need_wait_return = false; - - if (line_arg != NULL) { - /* Use eap->arg, split up in parts by line breaks. */ - theline = line_arg; - p = vim_strchr(theline, '\n'); - if (p == NULL) - line_arg += STRLEN(line_arg); - else { - *p = NUL; - line_arg = p + 1; - } - } else { - xfree(line_to_free); - if (eap->getline == NULL) { - theline = getcmdline(':', 0L, indent, do_concat); - } else { - theline = eap->getline(':', eap->cookie, indent, do_concat); - } - line_to_free = theline; - } - if (KeyTyped) { - lines_left = Rows - 1; - } - if (theline == NULL) { - EMSG(_("E126: Missing :endfunction")); - goto erret; - } - if (show_block) { - assert(indent >= 0); - ui_ext_cmdline_block_append((size_t)indent, (const char *)theline); - } - - // Detect line continuation: sourcing_lnum increased more than one. - sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie); - if (sourcing_lnum < sourcing_lnum_off) { - sourcing_lnum_off -= sourcing_lnum; - } else { - sourcing_lnum_off = 0; - } - - if (skip_until != NULL) { - // Don't check for ":endfunc" between - // * ":append" and "." - // * ":python <<EOF" and "EOF" - // * ":let {var-name} =<< [trim] {marker}" and "{marker}" - if (heredoc_trimmed == NULL - || (is_heredoc && skipwhite(theline) == theline) - || STRNCMP(theline, heredoc_trimmed, - STRLEN(heredoc_trimmed)) == 0) { - if (heredoc_trimmed == NULL) { - p = theline; - } else if (is_heredoc) { - p = skipwhite(theline) == theline - ? theline : theline + STRLEN(heredoc_trimmed); - } else { - p = theline + STRLEN(heredoc_trimmed); - } - if (STRCMP(p, skip_until) == 0) { - XFREE_CLEAR(skip_until); - XFREE_CLEAR(heredoc_trimmed); - do_concat = true; - is_heredoc = false; - } - } - } else { - /* skip ':' and blanks*/ - for (p = theline; ascii_iswhite(*p) || *p == ':'; ++p) - ; - - /* Check for "endfunction". */ - if (checkforcmd(&p, "endfunction", 4) && nesting-- == 0) { - if (*p == '!') { - p++; - } - char_u *nextcmd = NULL; - if (*p == '|') { - nextcmd = p + 1; - } else if (line_arg != NULL && *skipwhite(line_arg) != NUL) { - nextcmd = line_arg; - } else if (*p != NUL && *p != '"' && p_verbose > 0) { - give_warning2((char_u *)_("W22: Text found after :endfunction: %s"), - p, true); - } - if (nextcmd != NULL) { - // Another command follows. If the line came from "eap" we - // can simply point into it, otherwise we need to change - // "eap->cmdlinep". - eap->nextcmd = nextcmd; - if (line_to_free != NULL) { - xfree(*eap->cmdlinep); - *eap->cmdlinep = line_to_free; - line_to_free = NULL; - } - } - break; - } - - /* Increase indent inside "if", "while", "for" and "try", decrease - * at "end". */ - if (indent > 2 && STRNCMP(p, "end", 3) == 0) - indent -= 2; - else if (STRNCMP(p, "if", 2) == 0 - || STRNCMP(p, "wh", 2) == 0 - || STRNCMP(p, "for", 3) == 0 - || STRNCMP(p, "try", 3) == 0) - indent += 2; - - /* Check for defining a function inside this function. */ - if (checkforcmd(&p, "function", 2)) { - if (*p == '!') { - p = skipwhite(p + 1); - } - p += eval_fname_script((const char *)p); - xfree(trans_function_name(&p, true, 0, NULL, NULL)); - if (*skipwhite(p) == '(') { - nesting++; - indent += 2; - } - } - - // Check for ":append", ":change", ":insert". - p = skip_range(p, NULL); - if ((p[0] == 'a' && (!ASCII_ISALPHA(p[1]) || p[1] == 'p')) - || (p[0] == 'c' - && (!ASCII_ISALPHA(p[1]) - || (p[1] == 'h' && (!ASCII_ISALPHA(p[2]) - || (p[2] == 'a' - && (STRNCMP(&p[3], "nge", 3) != 0 - || !ASCII_ISALPHA(p[6]))))))) - || (p[0] == 'i' - && (!ASCII_ISALPHA(p[1]) || (p[1] == 'n' - && (!ASCII_ISALPHA(p[2]) - || (p[2] == 's')))))) { - skip_until = vim_strsave((char_u *)"."); - } - - // heredoc: Check for ":python <<EOF", ":lua <<EOF", etc. - arg = skipwhite(skiptowhite(p)); - if (arg[0] == '<' && arg[1] =='<' - && ((p[0] == 'p' && p[1] == 'y' - && (!ASCII_ISALNUM(p[2]) || p[2] == 't' - || ((p[2] == '3' || p[2] == 'x') - && !ASCII_ISALPHA(p[3])))) - || (p[0] == 'p' && p[1] == 'e' - && (!ASCII_ISALPHA(p[2]) || p[2] == 'r')) - || (p[0] == 't' && p[1] == 'c' - && (!ASCII_ISALPHA(p[2]) || p[2] == 'l')) - || (p[0] == 'l' && p[1] == 'u' && p[2] == 'a' - && !ASCII_ISALPHA(p[3])) - || (p[0] == 'r' && p[1] == 'u' && p[2] == 'b' - && (!ASCII_ISALPHA(p[3]) || p[3] == 'y')) - || (p[0] == 'm' && p[1] == 'z' - && (!ASCII_ISALPHA(p[2]) || p[2] == 's')) - )) { - /* ":python <<" continues until a dot, like ":append" */ - p = skipwhite(arg + 2); - if (*p == NUL) - skip_until = vim_strsave((char_u *)"."); - else - skip_until = vim_strsave(p); - } - - // Check for ":let v =<< [trim] EOF" - // and ":let [a, b] =<< [trim] EOF" - arg = skipwhite(skiptowhite(p)); - if (*arg == '[') { - arg = vim_strchr(arg, ']'); - } - if (arg != NULL) { - arg = skipwhite(skiptowhite(arg)); - if (arg[0] == '=' - && arg[1] == '<' - && arg[2] =='<' - && (p[0] == 'l' - && p[1] == 'e' - && (!ASCII_ISALNUM(p[2]) - || (p[2] == 't' && !ASCII_ISALNUM(p[3]))))) { - p = skipwhite(arg + 3); - if (STRNCMP(p, "trim", 4) == 0) { - // Ignore leading white space. - p = skipwhite(p + 4); - heredoc_trimmed = - vim_strnsave(theline, (int)(skipwhite(theline) - theline)); - } - skip_until = vim_strnsave(p, (int)(skiptowhite(p) - p)); - do_concat = false; - is_heredoc = true; - } - } - } - - /* Add the line to the function. */ - ga_grow(&newlines, 1 + sourcing_lnum_off); - - /* Copy the line to newly allocated memory. get_one_sourceline() - * allocates 250 bytes per line, this saves 80% on average. The cost - * is an extra alloc/free. */ - p = vim_strsave(theline); - ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; - - /* Add NULL lines for continuation lines, so that the line count is - * equal to the index in the growarray. */ - while (sourcing_lnum_off-- > 0) - ((char_u **)(newlines.ga_data))[newlines.ga_len++] = NULL; - - /* Check for end of eap->arg. */ - if (line_arg != NULL && *line_arg == NUL) - line_arg = NULL; - } - - /* Don't define the function when skipping commands or when an error was - * detected. */ - if (eap->skip || did_emsg) - goto erret; - - /* - * If there are no errors, add the function - */ - if (fudi.fd_dict == NULL) { - v = find_var((const char *)name, STRLEN(name), &ht, false); - if (v != NULL && v->di_tv.v_type == VAR_FUNC) { - emsg_funcname(N_("E707: Function name conflicts with variable: %s"), - name); - goto erret; - } - - fp = find_func(name); - if (fp != NULL) { - // Function can be replaced with "function!" and when sourcing the - // same script again, but only once. - if (!eap->forceit - && (fp->uf_script_ctx.sc_sid != current_sctx.sc_sid - || fp->uf_script_ctx.sc_seq == current_sctx.sc_seq)) { - emsg_funcname(e_funcexts, name); - goto erret; - } - if (fp->uf_calls > 0) { - emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"), - name); - goto erret; - } - if (fp->uf_refcount > 1) { - // This function is referenced somewhere, don't redefine it but - // create a new one. - (fp->uf_refcount)--; - fp->uf_flags |= FC_REMOVED; - fp = NULL; - overwrite = true; - } else { - // redefine existing function - XFREE_CLEAR(name); - func_clear_items(fp); - fp->uf_profiling = false; - fp->uf_prof_initialized = false; - } - } - } else { - char numbuf[20]; - - fp = NULL; - if (fudi.fd_newkey == NULL && !eap->forceit) { - EMSG(_(e_funcdict)); - goto erret; - } - if (fudi.fd_di == NULL) { - if (tv_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg, - TV_CSTRING)) { - // Can't add a function to a locked dictionary - goto erret; - } - } else if (tv_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg, - TV_CSTRING)) { - // Can't change an existing function if it is locked - goto erret; - } - - /* Give the function a sequential number. Can only be used with a - * Funcref! */ - xfree(name); - sprintf(numbuf, "%d", ++func_nr); - name = vim_strsave((char_u *)numbuf); - } - - if (fp == NULL) { - if (fudi.fd_dict == NULL && vim_strchr(name, AUTOLOAD_CHAR) != NULL) { - int slen, plen; - char_u *scriptname; - - /* Check that the autoload name matches the script name. */ - int j = FAIL; - if (sourcing_name != NULL) { - scriptname = (char_u *)autoload_name((const char *)name, STRLEN(name)); - p = vim_strchr(scriptname, '/'); - plen = (int)STRLEN(p); - slen = (int)STRLEN(sourcing_name); - if (slen > plen && fnamecmp(p, - sourcing_name + slen - plen) == 0) - j = OK; - xfree(scriptname); - } - if (j == FAIL) { - EMSG2(_( - "E746: Function name does not match script file name: %s"), - name); - goto erret; - } - } - - fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); - - if (fudi.fd_dict != NULL) { - if (fudi.fd_di == NULL) { - // Add new dict entry - fudi.fd_di = tv_dict_item_alloc((const char *)fudi.fd_newkey); - if (tv_dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) { - xfree(fudi.fd_di); - xfree(fp); - goto erret; - } - } else { - // Overwrite existing dict entry. - tv_clear(&fudi.fd_di->di_tv); - } - fudi.fd_di->di_tv.v_type = VAR_FUNC; - fudi.fd_di->di_tv.vval.v_string = vim_strsave(name); - - /* behave like "dict" was used */ - flags |= FC_DICT; - } - - /* insert the new function in the function list */ - STRCPY(fp->uf_name, name); - if (overwrite) { - hi = hash_find(&func_hashtab, name); - hi->hi_key = UF2HIKEY(fp); - } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { - xfree(fp); - goto erret; - } - fp->uf_refcount = 1; - } - fp->uf_args = newargs; - fp->uf_lines = newlines; - if ((flags & FC_CLOSURE) != 0) { - register_closure(fp); - } else { - fp->uf_scoped = NULL; - } - if (prof_def_func()) { - func_do_profile(fp); - } - fp->uf_varargs = varargs; - if (sandbox) { - flags |= FC_SANDBOX; - } - fp->uf_flags = flags; - fp->uf_calls = 0; - fp->uf_script_ctx = current_sctx; - fp->uf_script_ctx.sc_lnum += sourcing_lnum_top; - - goto ret_free; - -erret: - ga_clear_strings(&newargs); -errret_2: - ga_clear_strings(&newlines); -ret_free: - xfree(skip_until); - xfree(line_to_free); - xfree(fudi.fd_newkey); - xfree(name); - did_emsg |= saved_did_emsg; - need_wait_return |= saved_wait_return; - if (show_block) { - ui_ext_cmdline_block_leave(); - } -} // NOLINT(readability/fn_size) - -/// Get a function name, translating "<SID>" and "<SNR>". -/// Also handles a Funcref in a List or Dictionary. -/// flags: -/// TFN_INT: internal function name OK -/// TFN_QUIET: be quiet -/// TFN_NO_AUTOLOAD: do not use script autoloading -/// TFN_NO_DEREF: do not dereference a Funcref -/// Advances "pp" to just after the function name (if no error). -/// -/// @return the function name in allocated memory, or NULL for failure. -static char_u * -trans_function_name( - char_u **pp, - int skip, // only find the end, don't evaluate - int flags, - funcdict_T *fdp, // return: info about dictionary used - partial_T **partial // return: partial of a FuncRef -) -{ - char_u *name = NULL; - const char_u *start; - const char_u *end; - int lead; - int len; - lval_T lv; - - if (fdp != NULL) - memset(fdp, 0, sizeof(funcdict_T)); - start = *pp; - - /* Check for hard coded <SNR>: already translated function ID (from a user - * command). */ - if ((*pp)[0] == K_SPECIAL && (*pp)[1] == KS_EXTRA - && (*pp)[2] == (int)KE_SNR) { - *pp += 3; - len = get_id_len((const char **)pp) + 3; - return (char_u *)xmemdupz(start, len); - } - - /* A name starting with "<SID>" or "<SNR>" is local to a script. But - * don't skip over "s:", get_lval() needs it for "s:dict.func". */ - lead = eval_fname_script((const char *)start); - if (lead > 2) { - start += lead; - } - - // Note that TFN_ flags use the same values as GLV_ flags. - end = get_lval((char_u *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY, - lead > 2 ? 0 : FNE_CHECK_START); - if (end == start) { - if (!skip) - EMSG(_("E129: Function name required")); - goto theend; - } - if (end == NULL || (lv.ll_tv != NULL && (lead > 2 || lv.ll_range))) { - /* - * Report an invalid expression in braces, unless the expression - * evaluation has been cancelled due to an aborting error, an - * interrupt, or an exception. - */ - if (!aborting()) { - if (end != NULL) { - emsgf(_(e_invarg2), start); - } - } else { - *pp = (char_u *)find_name_end(start, NULL, NULL, FNE_INCL_BR); - } - goto theend; - } - - if (lv.ll_tv != NULL) { - if (fdp != NULL) { - fdp->fd_dict = lv.ll_dict; - fdp->fd_newkey = lv.ll_newkey; - lv.ll_newkey = NULL; - fdp->fd_di = lv.ll_di; - } - if (lv.ll_tv->v_type == VAR_FUNC && lv.ll_tv->vval.v_string != NULL) { - name = vim_strsave(lv.ll_tv->vval.v_string); - *pp = (char_u *)end; - } else if (lv.ll_tv->v_type == VAR_PARTIAL - && lv.ll_tv->vval.v_partial != NULL) { - if (lv.ll_tv->vval.v_partial == vvlua_partial && *end == '.') { - len = check_luafunc_name((const char *)end+1, true); - if (len == 0) { - EMSG2(e_invexpr2, "v:lua"); - goto theend; - } - name = xmallocz(len); - memcpy(name, end+1, len); - *pp = (char_u *)end+1+len; - } else { - name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial)); - *pp = (char_u *)end; - } - if (partial != NULL) { - *partial = lv.ll_tv->vval.v_partial; - } - } else { - if (!skip && !(flags & TFN_QUIET) && (fdp == NULL - || lv.ll_dict == NULL - || fdp->fd_newkey == NULL)) { - EMSG(_(e_funcref)); - } else { - *pp = (char_u *)end; - } - name = NULL; - } - goto theend; - } - - if (lv.ll_name == NULL) { - // Error found, but continue after the function name. - *pp = (char_u *)end; - goto theend; - } - - /* Check if the name is a Funcref. If so, use the value. */ - if (lv.ll_exp_name != NULL) { - len = (int)strlen(lv.ll_exp_name); - name = deref_func_name(lv.ll_exp_name, &len, partial, - flags & TFN_NO_AUTOLOAD); - if ((const char *)name == lv.ll_exp_name) { - name = NULL; - } - } else if (!(flags & TFN_NO_DEREF)) { - len = (int)(end - *pp); - name = deref_func_name((const char *)(*pp), &len, partial, - flags & TFN_NO_AUTOLOAD); - if (name == *pp) { - name = NULL; - } - } - if (name != NULL) { - name = vim_strsave(name); - *pp = (char_u *)end; - if (strncmp((char *)name, "<SNR>", 5) == 0) { - // Change "<SNR>" to the byte sequence. - name[0] = K_SPECIAL; - name[1] = KS_EXTRA; - name[2] = (int)KE_SNR; - memmove(name + 3, name + 5, strlen((char *)name + 5) + 1); - } - goto theend; - } - - if (lv.ll_exp_name != NULL) { - len = (int)strlen(lv.ll_exp_name); - if (lead <= 2 && lv.ll_name == lv.ll_exp_name - && lv.ll_name_len >= 2 && memcmp(lv.ll_name, "s:", 2) == 0) { - // When there was "s:" already or the name expanded to get a - // leading "s:" then remove it. - lv.ll_name += 2; - lv.ll_name_len -= 2; - len -= 2; - lead = 2; - } - } else { - // Skip over "s:" and "g:". - if (lead == 2 || (lv.ll_name[0] == 'g' && lv.ll_name[1] == ':')) { - lv.ll_name += 2; - lv.ll_name_len -= 2; - } - len = (int)((const char *)end - lv.ll_name); - } - - size_t sid_buf_len = 0; - char sid_buf[20]; - - // Copy the function name to allocated memory. - // Accept <SID>name() inside a script, translate into <SNR>123_name(). - // Accept <SNR>123_name() outside a script. - if (skip) { - lead = 0; // do nothing - } else if (lead > 0) { - lead = 3; - if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) - || eval_fname_sid((const char *)(*pp))) { - // It's "s:" or "<SID>". - if (current_sctx.sc_sid <= 0) { - EMSG(_(e_usingsid)); - goto theend; - } - sid_buf_len = snprintf(sid_buf, sizeof(sid_buf), - "%" PRIdSCID "_", current_sctx.sc_sid); - lead += sid_buf_len; - } - } else if (!(flags & TFN_INT) - && builtin_function(lv.ll_name, lv.ll_name_len)) { - EMSG2(_("E128: Function name must start with a capital or \"s:\": %s"), - start); - goto theend; - } - - if (!skip && !(flags & TFN_QUIET) && !(flags & TFN_NO_DEREF)) { - char_u *cp = xmemrchr(lv.ll_name, ':', lv.ll_name_len); - - if (cp != NULL && cp < end) { - EMSG2(_("E884: Function name cannot contain a colon: %s"), start); - goto theend; - } - } - - name = xmalloc(len + lead + 1); - if (lead > 0){ - name[0] = K_SPECIAL; - name[1] = KS_EXTRA; - name[2] = (int)KE_SNR; - if (sid_buf_len > 0) { // If it's "<SID>" - memcpy(name + 3, sid_buf, sid_buf_len); - } - } - memmove(name + lead, lv.ll_name, len); - name[lead + len] = NUL; - *pp = (char_u *)end; - -theend: - clear_lval(&lv); - return name; -} - -/* - * Return 5 if "p" starts with "<SID>" or "<SNR>" (ignoring case). - * Return 2 if "p" starts with "s:". - * Return 0 otherwise. - */ -static int eval_fname_script(const char *const p) -{ - // Use mb_strnicmp() because in Turkish comparing the "I" may not work with - // the standard library function. - if (p[0] == '<' - && (mb_strnicmp((char_u *)p + 1, (char_u *)"SID>", 4) == 0 - || mb_strnicmp((char_u *)p + 1, (char_u *)"SNR>", 4) == 0)) { - return 5; - } - if (p[0] == 's' && p[1] == ':') { - return 2; - } - return 0; -} - -/// Check whether function name starts with <SID> or s: -/// -/// @warning Only works for names previously checked by eval_fname_script(), if -/// it returned non-zero. -/// -/// @param[in] name Name to check. -/// -/// @return true if it starts with <SID> or s:, false otherwise. -static inline bool eval_fname_sid(const char *const name) - FUNC_ATTR_PURE FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT - FUNC_ATTR_NONNULL_ALL -{ - return *name == 's' || TOUPPER_ASC(name[2]) == 'I'; -} - -/// List the head of the function: "name(arg1, arg2)". -/// -/// @param[in] fp Function pointer. -/// @param[in] indent Indent line. -/// @param[in] force Include bang "!" (i.e.: "function!"). -static void list_func_head(ufunc_T *fp, int indent, bool force) -{ - msg_start(); - if (indent) - MSG_PUTS(" "); - MSG_PUTS(force ? "function! " : "function "); - if (fp->uf_name[0] == K_SPECIAL) { - MSG_PUTS_ATTR("<SNR>", HL_ATTR(HLF_8)); - msg_puts((const char *)fp->uf_name + 3); - } else { - msg_puts((const char *)fp->uf_name); - } - msg_putchar('('); - int j; - for (j = 0; j < fp->uf_args.ga_len; j++) { - if (j) { - msg_puts(", "); - } - msg_puts((const char *)FUNCARG(fp, j)); - } - if (fp->uf_varargs) { - if (j) { - msg_puts(", "); - } - msg_puts("..."); - } - msg_putchar(')'); - if (fp->uf_flags & FC_ABORT) { - msg_puts(" abort"); - } - if (fp->uf_flags & FC_RANGE) { - msg_puts(" range"); - } - if (fp->uf_flags & FC_DICT) { - msg_puts(" dict"); - } - if (fp->uf_flags & FC_CLOSURE) { - msg_puts(" closure"); - } - msg_clr_eos(); - if (p_verbose > 0) { - last_set_msg(fp->uf_script_ctx); - } -} - -/// Find a function by name, return pointer to it in ufuncs. -/// @return NULL for unknown function. -static ufunc_T *find_func(const char_u *name) -{ - hashitem_T *hi; - - hi = hash_find(&func_hashtab, name); - if (!HASHITEM_EMPTY(hi)) - return HI2UF(hi); - return NULL; -} - -#if defined(EXITFREE) -void free_all_functions(void) -{ - hashitem_T *hi; - ufunc_T *fp; - uint64_t skipped = 0; - uint64_t todo = 1; - uint64_t used; - - // Clean up the call stack. - while (current_funccal != NULL) { - tv_clear(current_funccal->rettv); - cleanup_function_call(current_funccal); - } - - // First clear what the functions contain. Since this may lower the - // reference count of a function, it may also free a function and change - // the hash table. Restart if that happens. - while (todo > 0) { - todo = func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0; hi++) { - if (!HASHITEM_EMPTY(hi)) { - // Only free functions that are not refcounted, those are - // supposed to be freed when no longer referenced. - fp = HI2UF(hi); - if (func_name_refcount(fp->uf_name)) { - skipped++; - } else { - used = func_hashtab.ht_used; - func_clear(fp, true); - if (used != func_hashtab.ht_used) { - skipped = 0; - break; - } - } - todo--; - } - } - } - - // Now actually free the functions. Need to start all over every time, - // because func_free() may change the hash table. - skipped = 0; - while (func_hashtab.ht_used > skipped) { - todo = func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0; hi++) { - if (!HASHITEM_EMPTY(hi)) { - todo--; - // Only free functions that are not refcounted, those are - // supposed to be freed when no longer referenced. - fp = HI2UF(hi); - if (func_name_refcount(fp->uf_name)) { - skipped++; - } else { - func_free(fp); - skipped = 0; - break; - } - } - } - } - if (skipped == 0) { - hash_clear(&func_hashtab); - } -} - -#endif - -bool translated_function_exists(const char *name) -{ - if (builtin_function(name, -1)) { - return find_internal_func((char *)name) != NULL; - } - return find_func((const char_u *)name) != NULL; -} - -/// Check whether function with the given name exists -/// -/// @param[in] name Function name. -/// @param[in] no_deref Whether to dereference a Funcref. -/// -/// @return True if it exists, false otherwise. -static bool function_exists(const char *const name, bool no_deref) -{ - const char_u *nm = (const char_u *)name; - bool n = false; - int flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD; - - if (no_deref) { - flag |= TFN_NO_DEREF; - } - char *const p = (char *)trans_function_name((char_u **)&nm, false, flag, NULL, - NULL); - nm = skipwhite(nm); - - /* Only accept "funcname", "funcname ", "funcname (..." and - * "funcname(...", not "funcname!...". */ - if (p != NULL && (*nm == NUL || *nm == '(')) { - n = translated_function_exists(p); - } - xfree(p); - return n; -} - -/// Checks if a builtin function with the given name exists. -/// -/// @param[in] name name of the builtin function to check. -/// @param[in] len length of "name", or -1 for NUL terminated. -/// -/// @return true if "name" looks like a builtin function name: starts with a -/// lower case letter and doesn't contain AUTOLOAD_CHAR. -static bool builtin_function(const char *name, int len) -{ - if (!ASCII_ISLOWER(name[0])) { - return false; - } - - const char *p = (len == -1 - ? strchr(name, AUTOLOAD_CHAR) - : memchr(name, AUTOLOAD_CHAR, (size_t)len)); - - return p == NULL; -} - -/* - * Start profiling function "fp". - */ -static void func_do_profile(ufunc_T *fp) +/// Start profiling function "fp". +void func_do_profile(ufunc_T *fp) { int len = fp->uf_lines.ga_len; @@ -22606,8 +9417,9 @@ void func_dump_profile(FILE *fd) int st_len = 0; todo = (int)func_hashtab.ht_used; - if (todo == 0) - return; /* nothing to dump */ + if (todo == 0) { + return; // nothing to dump + } sorttab = xmalloc(sizeof(ufunc_T *) * todo); @@ -22676,7 +9488,7 @@ prof_sort_list( ufunc_T **sorttab, int st_len, char *title, - int prefer_self /* when equal print only self time */ + int prefer_self // when equal print only self time ) { int i; @@ -22704,8 +9516,8 @@ static void prof_func_line( int count, proftime_T *total, proftime_T *self, - int prefer_self /* when equal print only self time */ - ) + int prefer_self // when equal print only self time +) { if (count > 0) { fprintf(fd, "%5d ", count); @@ -22741,6 +9553,33 @@ static int prof_self_cmp(const void *s1, const void *s2) return profile_cmp(p1->uf_tm_self, p2->uf_tm_self); } +/// Return the autoload script name for a function or variable name +/// Caller must make sure that "name" contains AUTOLOAD_CHAR. +/// +/// @param[in] name Variable/function name. +/// @param[in] name_len Name length. +/// +/// @return [allocated] autoload script name. +char *autoload_name(const char *const name, const size_t name_len) + FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT +{ + // Get the script file name: replace '#' with '/', append ".vim". + char *const scriptname = xmalloc(name_len + sizeof("autoload/.vim")); + memcpy(scriptname, "autoload/", sizeof("autoload/") - 1); + memcpy(scriptname + sizeof("autoload/") - 1, name, name_len); + size_t auchar_idx = 0; + for (size_t i = sizeof("autoload/") - 1; + i - sizeof("autoload/") + 1 < name_len; + i++) { + if (scriptname[i] == AUTOLOAD_CHAR) { + scriptname[i] = '/'; + auchar_idx = i; + } + } + memcpy(scriptname + auchar_idx, ".vim", sizeof(".vim")); + + return scriptname; +} /// If name has a package name try autoloading the script for it /// @@ -22749,8 +9588,8 @@ static int prof_self_cmp(const void *s1, const void *s2) /// @param[in] reload If true, load script again when already loaded. /// /// @return true if a package was loaded. -static bool script_autoload(const char *const name, const size_t name_len, - const bool reload) +bool script_autoload(const char *const name, const size_t name_len, + const bool reload) { // If there is no '#' after name[0] there is no package name. const char *p = memchr(name, AUTOLOAD_CHAR, name_len); @@ -22789,1027 +9628,6 @@ static bool script_autoload(const char *const name, const size_t name_len, return ret; } -/// Return the autoload script name for a function or variable name -/// Caller must make sure that "name" contains AUTOLOAD_CHAR. -/// -/// @param[in] name Variable/function name. -/// @param[in] name_len Name length. -/// -/// @return [allocated] autoload script name. -static char *autoload_name(const char *const name, const size_t name_len) - FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT -{ - // Get the script file name: replace '#' with '/', append ".vim". - char *const scriptname = xmalloc(name_len + sizeof("autoload/.vim")); - memcpy(scriptname, "autoload/", sizeof("autoload/") - 1); - memcpy(scriptname + sizeof("autoload/") - 1, name, name_len); - size_t auchar_idx = 0; - for (size_t i = sizeof("autoload/") - 1; - i - sizeof("autoload/") + 1 < name_len; - i++) { - if (scriptname[i] == AUTOLOAD_CHAR) { - scriptname[i] = '/'; - auchar_idx = i; - } - } - memcpy(scriptname + auchar_idx, ".vim", sizeof(".vim")); - - return scriptname; -} - - -/* - * Function given to ExpandGeneric() to obtain the list of user defined - * function names. - */ -char_u *get_user_func_name(expand_T *xp, int idx) -{ - static size_t done; - static hashitem_T *hi; - ufunc_T *fp; - - if (idx == 0) { - done = 0; - hi = func_hashtab.ht_array; - } - assert(hi); - if (done < func_hashtab.ht_used) { - if (done++ > 0) - ++hi; - while (HASHITEM_EMPTY(hi)) - ++hi; - fp = HI2UF(hi); - - if ((fp->uf_flags & FC_DICT) - || STRNCMP(fp->uf_name, "<lambda>", 8) == 0) { - return (char_u *)""; // don't show dict and lambda functions - } - - if (STRLEN(fp->uf_name) + 4 >= IOSIZE) { - return fp->uf_name; // Prevent overflow. - } - - cat_func_name(IObuff, fp); - if (xp->xp_context != EXPAND_USER_FUNC) { - STRCAT(IObuff, "("); - if (!fp->uf_varargs && GA_EMPTY(&fp->uf_args)) - STRCAT(IObuff, ")"); - } - return IObuff; - } - return NULL; -} - - -/* - * Copy the function name of "fp" to buffer "buf". - * "buf" must be able to hold the function name plus three bytes. - * Takes care of script-local function names. - */ -static void cat_func_name(char_u *buf, ufunc_T *fp) -{ - if (fp->uf_name[0] == K_SPECIAL) { - STRCPY(buf, "<SNR>"); - STRCAT(buf, fp->uf_name + 3); - } else - STRCPY(buf, fp->uf_name); -} - -/// There are two kinds of function names: -/// 1. ordinary names, function defined with :function -/// 2. numbered functions and lambdas -/// For the first we only count the name stored in func_hashtab as a reference, -/// using function() does not count as a reference, because the function is -/// looked up by name. -static bool func_name_refcount(char_u *name) -{ - return isdigit(*name) || *name == '<'; -} - -/// ":delfunction {name}" -void ex_delfunction(exarg_T *eap) -{ - ufunc_T *fp = NULL; - char_u *p; - char_u *name; - funcdict_T fudi; - - p = eap->arg; - name = trans_function_name(&p, eap->skip, 0, &fudi, NULL); - xfree(fudi.fd_newkey); - if (name == NULL) { - if (fudi.fd_dict != NULL && !eap->skip) - EMSG(_(e_funcref)); - return; - } - if (!ends_excmd(*skipwhite(p))) { - xfree(name); - EMSG(_(e_trailing)); - return; - } - eap->nextcmd = check_nextcmd(p); - if (eap->nextcmd != NULL) - *p = NUL; - - if (!eap->skip) - fp = find_func(name); - xfree(name); - - if (!eap->skip) { - if (fp == NULL) { - if (!eap->forceit) { - EMSG2(_(e_nofunc), eap->arg); - } - return; - } - if (fp->uf_calls > 0) { - EMSG2(_("E131: Cannot delete function %s: It is in use"), eap->arg); - return; - } - // check `uf_refcount > 2` because deleting a function should also reduce - // the reference count, and 1 is the initial refcount. - if (fp->uf_refcount > 2) { - EMSG2(_("Cannot delete function %s: It is being used internally"), - eap->arg); - return; - } - - if (fudi.fd_dict != NULL) { - // Delete the dict item that refers to the function, it will - // invoke func_unref() and possibly delete the function. - tv_dict_item_remove(fudi.fd_dict, fudi.fd_di); - } else { - // A normal function (not a numbered function or lambda) has a - // refcount of 1 for the entry in the hashtable. When deleting - // it and the refcount is more than one, it should be kept. - // A numbered function or lambda should be kept if the refcount is - // one or more. - if (fp->uf_refcount > (func_name_refcount(fp->uf_name) ? 0 : 1)) { - // Function is still referenced somewhere. Don't free it but - // do remove it from the hashtable. - if (func_remove(fp)) { - fp->uf_refcount--; - } - fp->uf_flags |= FC_DELETED; - } else { - func_clear_free(fp, false); - } - } - } -} - -/// Remove the function from the function hashtable. If the function was -/// deleted while it still has references this was already done. -/// -/// @return true if the entry was deleted, false if it wasn't found. -static bool func_remove(ufunc_T *fp) -{ - hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp)); - - if (!HASHITEM_EMPTY(hi)) { - hash_remove(&func_hashtab, hi); - return true; - } - - return false; -} - -static void func_clear_items(ufunc_T *fp) -{ - ga_clear_strings(&(fp->uf_args)); - ga_clear_strings(&(fp->uf_lines)); - - XFREE_CLEAR(fp->uf_tml_count); - XFREE_CLEAR(fp->uf_tml_total); - XFREE_CLEAR(fp->uf_tml_self); -} - -/// Free all things that a function contains. Does not free the function -/// itself, use func_free() for that. -/// -/// param[in] force When true, we are exiting. -static void func_clear(ufunc_T *fp, bool force) -{ - if (fp->uf_cleared) { - return; - } - fp->uf_cleared = true; - - // clear this function - func_clear_items(fp); - funccal_unref(fp->uf_scoped, fp, force); -} - -/// Free a function and remove it from the list of functions. Does not free -/// what a function contains, call func_clear() first. -/// -/// param[in] fp The function to free. -static void func_free(ufunc_T *fp) -{ - // only remove it when not done already, otherwise we would remove a newer - // version of the function - if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) { - func_remove(fp); - } - xfree(fp); -} - -/// Free all things that a function contains and free the function itself. -/// -/// param[in] force When true, we are exiting. -static void func_clear_free(ufunc_T *fp, bool force) -{ - func_clear(fp, force); - func_free(fp); -} - -/* - * Unreference a Function: decrement the reference count and free it when it - * becomes zero. - */ -void func_unref(char_u *name) -{ - ufunc_T *fp = NULL; - - if (name == NULL || !func_name_refcount(name)) { - return; - } - - fp = find_func(name); - if (fp == NULL && isdigit(*name)) { -#ifdef EXITFREE - if (!entered_free_all_mem) { - internal_error("func_unref()"); - abort(); - } -#else - internal_error("func_unref()"); - abort(); -#endif - } - func_ptr_unref(fp); -} - -/// Unreference a Function: decrement the reference count and free it when it -/// becomes zero. -/// Unreference user function, freeing it if needed -/// -/// Decrements the reference count and frees when it becomes zero. -/// -/// @param fp Function to unreference. -void func_ptr_unref(ufunc_T *fp) -{ - if (fp != NULL && --fp->uf_refcount <= 0) { - // Only delete it when it's not being used. Otherwise it's done - // when "uf_calls" becomes zero. - if (fp->uf_calls == 0) { - func_clear_free(fp, false); - } - } -} - -/// Count a reference to a Function. -void func_ref(char_u *name) -{ - ufunc_T *fp; - - if (name == NULL || !func_name_refcount(name)) { - return; - } - fp = find_func(name); - if (fp != NULL) { - (fp->uf_refcount)++; - } else if (isdigit(*name)) { - // Only give an error for a numbered function. - // Fail silently, when named or lambda function isn't found. - internal_error("func_ref()"); - } -} - -/// Count a reference to a Function. -void func_ptr_ref(ufunc_T *fp) -{ - if (fp != NULL) { - (fp->uf_refcount)++; - } -} - -/// Check whether funccall is still referenced outside -/// -/// It is supposed to be referenced if either it is referenced itself or if l:, -/// a: or a:000 are referenced as all these are statically allocated within -/// funccall structure. -static inline bool fc_referenced(const funccall_T *const fc) - FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT - FUNC_ATTR_NONNULL_ALL -{ - return ((fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated) - != DO_NOT_FREE_CNT) - || fc->l_vars.dv_refcount != DO_NOT_FREE_CNT - || fc->l_avars.dv_refcount != DO_NOT_FREE_CNT - || fc->fc_refcount > 0); -} - -/// Call a user function -/// -/// @param fp Function to call. -/// @param[in] argcount Number of arguments. -/// @param argvars Arguments. -/// @param[out] rettv Return value. -/// @param[in] firstline First line of range. -/// @param[in] lastline Last line of range. -/// @param selfdict Dictionary for "self" for dictionary functions. -void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, - typval_T *rettv, linenr_T firstline, linenr_T lastline, - dict_T *selfdict) - FUNC_ATTR_NONNULL_ARG(1, 3, 4) -{ - char_u *save_sourcing_name; - linenr_T save_sourcing_lnum; - bool using_sandbox = false; - funccall_T *fc; - int save_did_emsg; - static int depth = 0; - dictitem_T *v; - int fixvar_idx = 0; /* index in fixvar[] */ - int ai; - bool islambda = false; - char_u numbuf[NUMBUFLEN]; - char_u *name; - proftime_T wait_start; - proftime_T call_start; - int started_profiling = false; - bool did_save_redo = false; - save_redo_T save_redo; - - /* If depth of calling is getting too high, don't execute the function */ - if (depth >= p_mfd) { - EMSG(_("E132: Function call depth is higher than 'maxfuncdepth'")); - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = -1; - return; - } - ++depth; - // Save search patterns and redo buffer. - save_search_patterns(); - if (!ins_compl_active()) { - saveRedobuff(&save_redo); - did_save_redo = true; - } - ++fp->uf_calls; - // check for CTRL-C hit - line_breakcheck(); - // prepare the funccall_T structure - fc = xmalloc(sizeof(funccall_T)); - fc->caller = current_funccal; - current_funccal = fc; - fc->func = fp; - fc->rettv = rettv; - rettv->vval.v_number = 0; - fc->linenr = 0; - fc->returned = FALSE; - fc->level = ex_nesting_level; - /* Check if this function has a breakpoint. */ - fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0); - fc->dbg_tick = debug_tick; - - // Set up fields for closure. - fc->fc_refcount = 0; - fc->fc_copyID = 0; - ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1); - func_ptr_ref(fp); - - if (STRNCMP(fp->uf_name, "<lambda>", 8) == 0) { - islambda = true; - } - - // Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables - // with names up to VAR_SHORT_LEN long. This avoids having to alloc/free - // each argument variable and saves a lot of time. - // - // Init l: variables. - init_var_dict(&fc->l_vars, &fc->l_vars_var, VAR_DEF_SCOPE); - if (selfdict != NULL) { - // Set l:self to "selfdict". Use "name" to avoid a warning from - // some compiler that checks the destination size. - v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; -#ifndef __clang_analyzer__ - name = v->di_key; - STRCPY(name, "self"); -#endif - v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - tv_dict_add(&fc->l_vars, v); - v->di_tv.v_type = VAR_DICT; - v->di_tv.v_lock = 0; - v->di_tv.vval.v_dict = selfdict; - ++selfdict->dv_refcount; - } - - /* - * Init a: variables. - * Set a:0 to "argcount". - * Set a:000 to a list with room for the "..." arguments. - */ - init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE); - add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0", - (varnumber_T)(argcount - fp->uf_args.ga_len)); - fc->l_avars.dv_lock = VAR_FIXED; - // Use "name" to avoid a warning from some compiler that checks the - // destination size. - v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; -#ifndef __clang_analyzer__ - name = v->di_key; - STRCPY(name, "000"); -#endif - v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - tv_dict_add(&fc->l_avars, v); - v->di_tv.v_type = VAR_LIST; - v->di_tv.v_lock = VAR_FIXED; - v->di_tv.vval.v_list = &fc->l_varlist; - tv_list_init_static(&fc->l_varlist); - tv_list_set_lock(&fc->l_varlist, VAR_FIXED); - - // Set a:firstline to "firstline" and a:lastline to "lastline". - // Set a:name to named arguments. - // Set a:N to the "..." arguments. - add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], - "firstline", (varnumber_T)firstline); - add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], - "lastline", (varnumber_T)lastline); - for (int i = 0; i < argcount; i++) { - bool addlocal = false; - - ai = i - fp->uf_args.ga_len; - if (ai < 0) { - // named argument a:name - name = FUNCARG(fp, i); - if (islambda) { - addlocal = true; - } - } else { - // "..." argument a:1, a:2, etc. - snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1); - name = numbuf; - } - if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) { - v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; - v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - } else { - v = xmalloc(sizeof(dictitem_T) + STRLEN(name)); - v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC; - } - STRCPY(v->di_key, name); - - // Note: the values are copied directly to avoid alloc/free. - // "argvars" must have VAR_FIXED for v_lock. - v->di_tv = argvars[i]; - v->di_tv.v_lock = VAR_FIXED; - - if (addlocal) { - // Named arguments can be accessed without the "a:" prefix in lambda - // expressions. Add to the l: dict. - tv_copy(&v->di_tv, &v->di_tv); - tv_dict_add(&fc->l_vars, v); - } else { - tv_dict_add(&fc->l_avars, v); - } - - if (ai >= 0 && ai < MAX_FUNC_ARGS) { - tv_list_append(&fc->l_varlist, &fc->l_listitems[ai]); - *TV_LIST_ITEM_TV(&fc->l_listitems[ai]) = argvars[i]; - TV_LIST_ITEM_TV(&fc->l_listitems[ai])->v_lock = VAR_FIXED; - } - } - - /* Don't redraw while executing the function. */ - ++RedrawingDisabled; - save_sourcing_name = sourcing_name; - save_sourcing_lnum = sourcing_lnum; - sourcing_lnum = 1; - - if (fp->uf_flags & FC_SANDBOX) { - using_sandbox = true; - sandbox++; - } - - // need space for new sourcing_name: - // * save_sourcing_name - // * "["number"].." or "function " - // * "<SNR>" + fp->uf_name - 3 - // * terminating NUL - size_t len = (save_sourcing_name == NULL ? 0 : STRLEN(save_sourcing_name)) - + STRLEN(fp->uf_name) + 27; - sourcing_name = xmalloc(len); - { - if (save_sourcing_name != NULL - && STRNCMP(save_sourcing_name, "function ", 9) == 0) { - vim_snprintf((char *)sourcing_name, - len, - "%s[%" PRId64 "]..", - save_sourcing_name, - (int64_t)save_sourcing_lnum); - } else { - STRCPY(sourcing_name, "function "); - } - cat_func_name(sourcing_name + STRLEN(sourcing_name), fp); - - if (p_verbose >= 12) { - ++no_wait_return; - verbose_enter_scroll(); - - smsg(_("calling %s"), sourcing_name); - if (p_verbose >= 14) { - msg_puts("("); - for (int i = 0; i < argcount; i++) { - if (i > 0) { - msg_puts(", "); - } - if (argvars[i].v_type == VAR_NUMBER) { - msg_outnum((long)argvars[i].vval.v_number); - } else { - // Do not want errors such as E724 here. - emsg_off++; - char *tofree = encode_tv2string(&argvars[i], NULL); - emsg_off--; - if (tofree != NULL) { - char *s = tofree; - char buf[MSG_BUF_LEN]; - if (vim_strsize((char_u *)s) > MSG_BUF_CLEN) { - trunc_string((char_u *)s, (char_u *)buf, MSG_BUF_CLEN, - sizeof(buf)); - s = buf; - } - msg_puts(s); - xfree(tofree); - } - } - } - msg_puts(")"); - } - msg_puts("\n"); // don't overwrite this either - - verbose_leave_scroll(); - --no_wait_return; - } - } - - const bool do_profiling_yes = do_profiling == PROF_YES; - - bool func_not_yet_profiling_but_should = - do_profiling_yes - && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL); - - if (func_not_yet_profiling_but_should) { - started_profiling = true; - func_do_profile(fp); - } - - bool func_or_func_caller_profiling = - do_profiling_yes - && (fp->uf_profiling - || (fc->caller != NULL && fc->caller->func->uf_profiling)); - - if (func_or_func_caller_profiling) { - ++fp->uf_tm_count; - call_start = profile_start(); - fp->uf_tm_children = profile_zero(); - } - - if (do_profiling_yes) { - script_prof_save(&wait_start); - } - - const sctx_T save_current_sctx = current_sctx; - current_sctx = fp->uf_script_ctx; - save_did_emsg = did_emsg; - did_emsg = FALSE; - - /* call do_cmdline() to execute the lines */ - do_cmdline(NULL, get_func_line, (void *)fc, - DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); - - --RedrawingDisabled; - - // when the function was aborted because of an error, return -1 - if ((did_emsg - && (fp->uf_flags & FC_ABORT)) || rettv->v_type == VAR_UNKNOWN) { - tv_clear(rettv); - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = -1; - } - - if (func_or_func_caller_profiling) { - call_start = profile_end(call_start); - call_start = profile_sub_wait(wait_start, call_start); // -V614 - fp->uf_tm_total = profile_add(fp->uf_tm_total, call_start); - fp->uf_tm_self = profile_self(fp->uf_tm_self, call_start, - fp->uf_tm_children); - if (fc->caller != NULL && fc->caller->func->uf_profiling) { - fc->caller->func->uf_tm_children = - profile_add(fc->caller->func->uf_tm_children, call_start); - fc->caller->func->uf_tml_children = - profile_add(fc->caller->func->uf_tml_children, call_start); - } - if (started_profiling) { - // make a ":profdel func" stop profiling the function - fp->uf_profiling = false; - } - } - - /* when being verbose, mention the return value */ - if (p_verbose >= 12) { - ++no_wait_return; - verbose_enter_scroll(); - - if (aborting()) - smsg(_("%s aborted"), sourcing_name); - else if (fc->rettv->v_type == VAR_NUMBER) - smsg(_("%s returning #%" PRId64 ""), - sourcing_name, (int64_t)fc->rettv->vval.v_number); - else { - char_u buf[MSG_BUF_LEN]; - - // The value may be very long. Skip the middle part, so that we - // have some idea how it starts and ends. smsg() would always - // truncate it at the end. Don't want errors such as E724 here. - emsg_off++; - char_u *s = (char_u *) encode_tv2string(fc->rettv, NULL); - char_u *tofree = s; - emsg_off--; - if (s != NULL) { - if (vim_strsize(s) > MSG_BUF_CLEN) { - trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN); - s = buf; - } - smsg(_("%s returning %s"), sourcing_name, s); - xfree(tofree); - } - } - msg_puts("\n"); // don't overwrite this either - - verbose_leave_scroll(); - --no_wait_return; - } - - xfree(sourcing_name); - sourcing_name = save_sourcing_name; - sourcing_lnum = save_sourcing_lnum; - current_sctx = save_current_sctx; - if (do_profiling_yes) { - script_prof_restore(&wait_start); - } - if (using_sandbox) { - sandbox--; - } - - if (p_verbose >= 12 && sourcing_name != NULL) { - ++no_wait_return; - verbose_enter_scroll(); - - smsg(_("continuing in %s"), sourcing_name); - msg_puts("\n"); // don't overwrite this either - - verbose_leave_scroll(); - --no_wait_return; - } - - did_emsg |= save_did_emsg; - depth--; - - cleanup_function_call(fc); - - if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) { - // Function was unreferenced while being used, free it now. - func_clear_free(fp, false); - } - // restore search patterns and redo buffer - if (did_save_redo) { - restoreRedobuff(&save_redo); - } - restore_search_patterns(); -} - -/// Unreference "fc": decrement the reference count and free it when it -/// becomes zero. "fp" is detached from "fc". -/// -/// @param[in] force When true, we are exiting. -static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) -{ - funccall_T **pfc; - int i; - - if (fc == NULL) { - return; - } - - fc->fc_refcount--; - if (force ? fc->fc_refcount <= 0 : !fc_referenced(fc)) { - for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) { - if (fc == *pfc) { - *pfc = fc->caller; - free_funccal(fc, true); - return; - } - } - } - for (i = 0; i < fc->fc_funcs.ga_len; i++) { - if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) { - ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL; - } - } -} - -/// @return true if items in "fc" do not have "copyID". That means they are not -/// referenced from anywhere that is in use. -static int can_free_funccal(funccall_T *fc, int copyID) -{ - return fc->l_varlist.lv_copyID != copyID - && fc->l_vars.dv_copyID != copyID - && fc->l_avars.dv_copyID != copyID - && fc->fc_copyID != copyID; -} - -/* - * Free "fc" and what it contains. - */ -static void -free_funccal( - funccall_T *fc, - int free_val /* a: vars were allocated */ -) -{ - for (int i = 0; i < fc->fc_funcs.ga_len; i++) { - ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; - - // When garbage collecting a funccall_T may be freed before the - // function that references it, clear its uf_scoped field. - // The function may have been redefined and point to another - // funccal_T, don't clear it then. - if (fp != NULL && fp->uf_scoped == fc) { - fp->uf_scoped = NULL; - } - } - ga_clear(&fc->fc_funcs); - - // The a: variables typevals may not have been allocated, only free the - // allocated variables. - vars_clear_ext(&fc->l_avars.dv_hashtab, free_val); - - // Free all l: variables. - vars_clear(&fc->l_vars.dv_hashtab); - - // Free the a:000 variables if they were allocated. - if (free_val) { - TV_LIST_ITER(&fc->l_varlist, li, { - tv_clear(TV_LIST_ITEM_TV(li)); - }); - } - - func_ptr_unref(fc->func); - xfree(fc); -} - -/// Handle the last part of returning from a function: free the local hashtable. -/// Unless it is still in use by a closure. -static void cleanup_function_call(funccall_T *fc) -{ - current_funccal = fc->caller; - - // If the a:000 list and the l: and a: dicts are not referenced and there - // is no closure using it, we can free the funccall_T and what's in it. - if (!fc_referenced(fc)) { - free_funccal(fc, false); - } else { - // "fc" is still in use. This can happen when returning "a:000", - // assigning "l:" to a global variable or defining a closure. - // Link "fc" in the list for garbage collection later. - fc->caller = previous_funccal; - previous_funccal = fc; - - // Make a copy of the a: variables, since we didn't do that above. - TV_DICT_ITER(&fc->l_avars, di, { - tv_copy(&di->di_tv, &di->di_tv); - }); - - // Make a copy of the a:000 items, since we didn't do that above. - TV_LIST_ITER(&fc->l_varlist, li, { - tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li)); - }); - } -} - -/* - * Add a number variable "name" to dict "dp" with value "nr". - */ -static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr) -{ -#ifndef __clang_analyzer__ - STRCPY(v->di_key, name); -#endif - v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - tv_dict_add(dp, v); - v->di_tv.v_type = VAR_NUMBER; - v->di_tv.v_lock = VAR_FIXED; - v->di_tv.vval.v_number = nr; -} - -/* - * ":return [expr]" - */ -void ex_return(exarg_T *eap) -{ - char_u *arg = eap->arg; - typval_T rettv; - int returning = FALSE; - - if (current_funccal == NULL) { - EMSG(_("E133: :return not inside a function")); - return; - } - - if (eap->skip) - ++emsg_skip; - - eap->nextcmd = NULL; - if ((*arg != NUL && *arg != '|' && *arg != '\n') - && eval0(arg, &rettv, &eap->nextcmd, !eap->skip) != FAIL) { - if (!eap->skip) { - returning = do_return(eap, false, true, &rettv); - } else { - tv_clear(&rettv); - } - } else if (!eap->skip) { // It's safer to return also on error. - // In return statement, cause_abort should be force_abort. - update_force_abort(); - - // Return unless the expression evaluation has been cancelled due to an - // aborting error, an interrupt, or an exception. - if (!aborting()) { - returning = do_return(eap, false, true, NULL); - } - } - - /* When skipping or the return gets pending, advance to the next command - * in this line (!returning). Otherwise, ignore the rest of the line. - * Following lines will be ignored by get_func_line(). */ - if (returning) - eap->nextcmd = NULL; - else if (eap->nextcmd == NULL) /* no argument */ - eap->nextcmd = check_nextcmd(arg); - - if (eap->skip) - --emsg_skip; -} - -/* - * Return from a function. Possibly makes the return pending. Also called - * for a pending return at the ":endtry" or after returning from an extra - * do_cmdline(). "reanimate" is used in the latter case. "is_cmd" is set - * when called due to a ":return" command. "rettv" may point to a typval_T - * with the return rettv. Returns TRUE when the return can be carried out, - * FALSE when the return gets pending. - */ -int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) -{ - int idx; - cstack_T *const cstack = eap->cstack; - - if (reanimate) - /* Undo the return. */ - current_funccal->returned = FALSE; - - /* - * Cleanup (and inactivate) conditionals, but stop when a try conditional - * not in its finally clause (which then is to be executed next) is found. - * In this case, make the ":return" pending for execution at the ":endtry". - * Otherwise, return normally. - */ - idx = cleanup_conditionals(eap->cstack, 0, TRUE); - if (idx >= 0) { - cstack->cs_pending[idx] = CSTP_RETURN; - - if (!is_cmd && !reanimate) - /* A pending return again gets pending. "rettv" points to an - * allocated variable with the rettv of the original ":return"'s - * argument if present or is NULL else. */ - cstack->cs_rettv[idx] = rettv; - else { - /* When undoing a return in order to make it pending, get the stored - * return rettv. */ - if (reanimate) { - assert(current_funccal->rettv); - rettv = current_funccal->rettv; - } - - if (rettv != NULL) { - /* Store the value of the pending return. */ - cstack->cs_rettv[idx] = xcalloc(1, sizeof(typval_T)); - *(typval_T *)cstack->cs_rettv[idx] = *(typval_T *)rettv; - } else - cstack->cs_rettv[idx] = NULL; - - if (reanimate) { - /* The pending return value could be overwritten by a ":return" - * without argument in a finally clause; reset the default - * return value. */ - current_funccal->rettv->v_type = VAR_NUMBER; - current_funccal->rettv->vval.v_number = 0; - } - } - report_make_pending(CSTP_RETURN, rettv); - } else { - current_funccal->returned = TRUE; - - /* If the return is carried out now, store the return value. For - * a return immediately after reanimation, the value is already - * there. */ - if (!reanimate && rettv != NULL) { - tv_clear(current_funccal->rettv); - *current_funccal->rettv = *(typval_T *)rettv; - if (!is_cmd) - xfree(rettv); - } - } - - return idx < 0; -} - -/* - * Generate a return command for producing the value of "rettv". The result - * is an allocated string. Used by report_pending() for verbose messages. - */ -char_u *get_return_cmd(void *rettv) -{ - char_u *s = NULL; - char_u *tofree = NULL; - - if (rettv != NULL) { - tofree = s = (char_u *) encode_tv2echo((typval_T *) rettv, NULL); - } - if (s == NULL) { - s = (char_u *)""; - } - - STRCPY(IObuff, ":return "); - STRLCPY(IObuff + 8, s, IOSIZE - 8); - if (STRLEN(s) + 8 >= IOSIZE) - STRCPY(IObuff + IOSIZE - 4, "..."); - xfree(tofree); - return vim_strsave(IObuff); -} - -/* - * Get next function line. - * Called by do_cmdline() to get the next line. - * Returns allocated string, or NULL for end of function. - */ -char_u *get_func_line(int c, void *cookie, int indent, bool do_concat) -{ - funccall_T *fcp = (funccall_T *)cookie; - ufunc_T *fp = fcp->func; - char_u *retval; - garray_T *gap; /* growarray with function lines */ - - /* If breakpoints have been added/deleted need to check for it. */ - if (fcp->dbg_tick != debug_tick) { - fcp->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, - sourcing_lnum); - fcp->dbg_tick = debug_tick; - } - if (do_profiling == PROF_YES) - func_line_end(cookie); - - gap = &fp->uf_lines; - if (((fp->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try()) - || fcp->returned) - retval = NULL; - else { - /* Skip NULL lines (continuation lines). */ - while (fcp->linenr < gap->ga_len - && ((char_u **)(gap->ga_data))[fcp->linenr] == NULL) - ++fcp->linenr; - if (fcp->linenr >= gap->ga_len) - retval = NULL; - else { - retval = vim_strsave(((char_u **)(gap->ga_data))[fcp->linenr++]); - sourcing_lnum = fcp->linenr; - if (do_profiling == PROF_YES) - func_line_start(cookie); - } - } - - /* Did we encounter a breakpoint? */ - if (fcp->breakpoint != 0 && fcp->breakpoint <= sourcing_lnum) { - dbg_breakpoint(fp->uf_name, sourcing_lnum); - /* Find next breakpoint. */ - fcp->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, - sourcing_lnum); - fcp->dbg_tick = debug_tick; - } - - return retval; -} - /* * Called when starting to read a function line. * "sourcing_lnum" must be correct! @@ -23824,10 +9642,11 @@ void func_line_start(void *cookie) if (fp->uf_profiling && sourcing_lnum >= 1 && sourcing_lnum <= fp->uf_lines.ga_len) { fp->uf_tml_idx = sourcing_lnum - 1; - /* Skip continuation lines. */ - while (fp->uf_tml_idx > 0 && FUNCLINE(fp, fp->uf_tml_idx) == NULL) - --fp->uf_tml_idx; - fp->uf_tml_execed = FALSE; + // Skip continuation lines. + while (fp->uf_tml_idx > 0 && FUNCLINE(fp, fp->uf_tml_idx) == NULL) { + fp->uf_tml_idx--; + } + fp->uf_tml_execed = false; fp->uf_tml_start = profile_start(); fp->uf_tml_children = profile_zero(); fp->uf_tml_wait = profile_get_wait(); @@ -23869,28 +9688,6 @@ void func_line_end(void *cookie) } } -/* - * Return TRUE if the currently active function should be ended, because a - * return was encountered or an error occurred. Used inside a ":while". - */ -int func_has_ended(void *cookie) -{ - funccall_T *fcp = (funccall_T *)cookie; - - /* Ignore the "abort" flag if the abortion behavior has been changed due to - * an error inside a try conditional. */ - return ((fcp->func->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try()) - || fcp->returned; -} - -/* - * return TRUE if cookie indicates a function which "abort"s on errors. - */ -int func_has_abort(void *cookie) -{ - return ((funccall_T *)cookie)->func->uf_flags & FC_ABORT; -} - static var_flavour_T var_flavour(char_u *varname) { char_u *p = varname; @@ -23906,72 +9703,6 @@ static var_flavour_T var_flavour(char_u *varname) } } -/// Search hashitem in parent scope. -hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht) -{ - if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) { - return NULL; - } - - funccall_T *old_current_funccal = current_funccal; - hashitem_T *hi = NULL; - const size_t namelen = strlen(name); - const char *varname; - - // Search in parent scope which is possible to reference from lambda - current_funccal = current_funccal->func->uf_scoped; - while (current_funccal != NULL) { - hashtab_T *ht = find_var_ht(name, namelen, &varname); - if (ht != NULL && *varname != NUL) { - hi = hash_find_len(ht, varname, namelen - (varname - name)); - if (!HASHITEM_EMPTY(hi)) { - *pht = ht; - break; - } - } - if (current_funccal == current_funccal->func->uf_scoped) { - break; - } - current_funccal = current_funccal->func->uf_scoped; - } - current_funccal = old_current_funccal; - - return hi; -} - -/// Search variable in parent scope. -dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, - int no_autoload) -{ - if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) { - return NULL; - } - - dictitem_T *v = NULL; - funccall_T *old_current_funccal = current_funccal; - const char *varname; - - // Search in parent scope which is possible to reference from lambda - current_funccal = current_funccal->func->uf_scoped; - while (current_funccal) { - hashtab_T *ht = find_var_ht(name, namelen, &varname); - if (ht != NULL && *varname != NUL) { - v = find_var_in_ht(ht, *name, varname, - namelen - (size_t)(varname - name), no_autoload); - if (v != NULL) { - break; - } - } - if (current_funccal == current_funccal->func->uf_scoped) { - break; - } - current_funccal = current_funccal->func->uf_scoped; - } - current_funccal = old_current_funccal; - - return v; -} - /// Iterate over global variables /// /// @warning No modifications to global variable dictionary must be performed @@ -24016,10 +9747,9 @@ const void *var_shada_iter(const void *const iter, const char **const name, void var_set_global(const char *const name, typval_T vartv) { - funccall_T *const saved_current_funccal = current_funccal; - current_funccal = NULL; + funccall_T *const saved_funccal = (funccall_T *)save_funccal(); set_var(name, strlen(name), &vartv, false); - current_funccal = saved_current_funccal; + restore_funccal(saved_funccal); } int store_session_globals(FILE *fd) @@ -24138,14 +9868,14 @@ modify_fname( int has_fullname = 0; repeat: - /* ":p" - full path/file_name */ + // ":p" - full path/file_name if (src[*usedlen] == ':' && src[*usedlen + 1] == 'p') { has_fullname = 1; valid |= VALID_PATH; *usedlen += 2; - /* Expand "~/path" for all systems and "~user/path" for Unix */ + // Expand "~/path" for all systems and "~user/path" for Unix if ((*fnamep)[0] == '~' #if !defined(UNIX) && ((*fnamep)[1] == '/' @@ -24157,7 +9887,7 @@ repeat: && !(tilde_file && (*fnamep)[1] == NUL) ) { *fnamep = expand_env_save(*fnamep); - xfree(*bufp); /* free any allocated file name */ + xfree(*bufp); // free any allocated file name *bufp = *fnamep; if (*fnamep == NULL) return -1; @@ -24175,20 +9905,20 @@ repeat: } } - /* FullName_save() is slow, don't use it when not needed. */ + // FullName_save() is slow, don't use it when not needed. if (*p != NUL || !vim_isAbsName(*fnamep)) { - *fnamep = (char_u *)FullName_save((char *)*fnamep, *p != NUL); - xfree(*bufp); /* free any allocated file name */ + *fnamep = (char_u *)FullName_save((char *)(*fnamep), *p != NUL); + xfree(*bufp); // free any allocated file name *bufp = *fnamep; if (*fnamep == NULL) return -1; } - /* Append a path separator to a directory. */ + // Append a path separator to a directory. if (os_isdir(*fnamep)) { - /* Make room for one or two extra characters. */ + // Make room for one or two extra characters. *fnamep = vim_strnsave(*fnamep, STRLEN(*fnamep) + 2); - xfree(*bufp); /* free any allocated file name */ + xfree(*bufp); // free any allocated file name *bufp = *fnamep; if (*fnamep == NULL) return -1; @@ -24196,9 +9926,9 @@ repeat: } } - /* ":." - path relative to the current directory */ - /* ":~" - path relative to the home directory */ - /* ":8" - shortname path - postponed till after */ + // ":." - path relative to the current directory + // ":~" - path relative to the home directory + // ":8" - shortname path - postponed till after while (src[*usedlen] == ':' && ((c = src[*usedlen + 1]) == '.' || c == '~' || c == '8')) { *usedlen += 2; @@ -24206,7 +9936,7 @@ repeat: continue; } pbuf = NULL; - /* Need full path first (use expand_env() to remove a "~/") */ + // Need full path first (use expand_env() to remove a "~/") if (!has_fullname) { if (c == '.' && **fnamep == '~') p = pbuf = expand_env_save(*fnamep); @@ -24224,14 +9954,14 @@ repeat: if (s != NULL) { *fnamep = s; if (pbuf != NULL) { - xfree(*bufp); /* free any allocated file name */ + xfree(*bufp); // free any allocated file name *bufp = pbuf; pbuf = NULL; } } } else { - home_replace(NULL, p, dirname, MAXPATHL, TRUE); - /* Only replace it when it starts with '~' */ + home_replace(NULL, p, dirname, MAXPATHL, true); + // Only replace it when it starts with '~' if (*dirname == '~') { s = vim_strsave(dirname); *fnamep = s; @@ -24246,8 +9976,8 @@ repeat: tail = path_tail(*fnamep); *fnamelen = STRLEN(*fnamep); - /* ":h" - head, remove "/file_name", can be repeated */ - /* Don't remove the first "/" or "c:\" */ + // ":h" - head, remove "/file_name", can be repeated + // Don't remove the first "/" or "c:\" while (src[*usedlen] == ':' && src[*usedlen + 1] == 'h') { valid |= VALID_HEAD; *usedlen += 2; @@ -24257,7 +9987,7 @@ repeat: } *fnamelen = (size_t)(tail - *fnamep); if (*fnamelen == 0) { - /* Result is empty. Turn it into "." to make ":cd %:h" work. */ + // Result is empty. Turn it into "." to make ":cd %:h" work. xfree(*bufp); *bufp = *fnamep = tail = vim_strsave((char_u *)"."); *fnamelen = 1; @@ -24268,21 +9998,21 @@ repeat: } } - /* ":8" - shortname */ + // ":8" - shortname if (src[*usedlen] == ':' && src[*usedlen + 1] == '8') { *usedlen += 2; } - /* ":t" - tail, just the basename */ + // ":t" - tail, just the basename if (src[*usedlen] == ':' && src[*usedlen + 1] == 't') { *usedlen += 2; *fnamelen -= (size_t)(tail - *fnamep); *fnamep = tail; } - /* ":e" - extension, can be repeated */ - /* ":r" - root, without extension, can be repeated */ + // ":e" - extension, can be repeated + // ":r" - root, without extension, can be repeated while (src[*usedlen] == ':' && (src[*usedlen + 1] == 'e' || src[*usedlen + 1] == 'r')) { /* find a '.' in the tail: @@ -24334,8 +10064,8 @@ repeat: *usedlen += 2; } - /* ":s?pat?foo?" - substitute */ - /* ":gs?pat?foo?" - global substitute */ + // ":s?pat?foo?" - substitute + // ":gs?pat?foo?" - global substitute if (src[*usedlen] == ':' && (src[*usedlen + 1] == 's' || (src[*usedlen + 1] == 'g' && src[*usedlen + 2] == 's'))) { @@ -24355,12 +10085,12 @@ repeat: sep = *s++; if (sep) { - /* find end of pattern */ + // find end of pattern p = vim_strchr(s, sep); if (p != NULL) { pat = vim_strnsave(s, (int)(p - s)); s = p + 1; - /* find end of substitution */ + // find end of substitution p = vim_strchr(s, sep); if (p != NULL) { sub = vim_strnsave(s, (int)(p - s)); @@ -24377,9 +10107,10 @@ repeat: } xfree(pat); } - /* after using ":s", repeat all the modifiers */ - if (didit) + // after using ":s", repeat all the modifiers + if (didit) { goto repeat; + } } } @@ -24418,7 +10149,7 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *save_cpo; char_u *zero_width = NULL; - /* Make 'cpoptions' empty, so that the 'l' flag doesn't work here */ + // Make 'cpoptions' empty, so that the 'l' flag doesn't work here save_cpo = p_cpo; p_cpo = empty_option; @@ -24432,7 +10163,7 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, tail = str; end = str + STRLEN(str); while (vim_regexec_nl(®match, str, (colnr_T)(tail - str))) { - /* Skip empty match except for first match. */ + // Skip empty match except for first match. if (regmatch.startp[0] == regmatch.endp[0]) { if (zero_width == regmatch.startp[0]) { // avoid getting stuck on a match with an empty string @@ -24454,7 +10185,7 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, ga_grow(&ga, (int)((end - tail) + sublen - (regmatch.endp[0] - regmatch.startp[0]))); - /* copy the text up to where the match is */ + // copy the text up to where the match is int i = (int)(regmatch.startp[0] - tail); memmove((char_u *)ga.ga_data + ga.ga_len, tail, (size_t)i); // add the substituted text @@ -24489,10 +10220,10 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, /// common code for getting job callbacks for jobstart, termopen and rpcstart /// /// @return true/false on success/failure. -static inline bool common_job_callbacks(dict_T *vopts, - CallbackReader *on_stdout, - CallbackReader *on_stderr, - Callback *on_exit) +bool common_job_callbacks(dict_T *vopts, + CallbackReader *on_stdout, + CallbackReader *on_stderr, + Callback *on_exit) { if (tv_dict_get_callback(vopts, S_LEN("on_stdout"), &on_stdout->cb) &&tv_dict_get_callback(vopts, S_LEN("on_stderr"), &on_stderr->cb) @@ -24516,7 +10247,7 @@ static inline bool common_job_callbacks(dict_T *vopts, } -static Channel *find_job(uint64_t id, bool show_error) +Channel *find_job(uint64_t id, bool show_error) { Channel *data = find_channel(id); if (!data || data->streamtype != kChannelStreamProc @@ -24534,7 +10265,7 @@ static Channel *find_job(uint64_t id, bool show_error) } -static void script_host_eval(char *name, typval_T *argvars, typval_T *rettv) +void script_host_eval(char *name, typval_T *argvars, typval_T *rettv) { if (check_restricted() || check_secure()) { return; @@ -24711,3 +10442,51 @@ void ex_checkhealth(exarg_T *eap) xfree(buf); } + +void invoke_prompt_callback(void) +{ + typval_T rettv; + typval_T argv[2]; + char_u *text; + char_u *prompt; + linenr_T lnum = curbuf->b_ml.ml_line_count; + + // Add a new line for the prompt before invoking the callback, so that + // text can always be inserted above the last line. + ml_append(lnum, (char_u *)"", 0, false); + curwin->w_cursor.lnum = lnum + 1; + curwin->w_cursor.col = 0; + + if (curbuf->b_prompt_callback.type == kCallbackNone) { + return; + } + text = ml_get(lnum); + prompt = prompt_text(); + if (STRLEN(text) >= STRLEN(prompt)) { + text += STRLEN(prompt); + } + argv[0].v_type = VAR_STRING; + argv[0].vval.v_string = vim_strsave(text); + argv[1].v_type = VAR_UNKNOWN; + + callback_call(&curbuf->b_prompt_callback, 1, argv, &rettv); + tv_clear(&argv[0]); + tv_clear(&rettv); +} + +// Return true When the interrupt callback was invoked. +bool invoke_prompt_interrupt(void) +{ + typval_T rettv; + typval_T argv[1]; + + if (curbuf->b_prompt_interrupt.type == kCallbackNone) { + return false; + } + argv[0].v_type = VAR_UNKNOWN; + + got_int = false; // don't skip executing commands + callback_call(&curbuf->b_prompt_interrupt, 0, argv, &rettv); + tv_clear(&rettv); + return true; +} diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 2aa08e2074..ebc0eb0b1a 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -1,16 +1,13 @@ #ifndef NVIM_EVAL_H #define NVIM_EVAL_H -#include "nvim/hashtab.h" // For hashtab_T #include "nvim/buffer_defs.h" -#include "nvim/ex_cmds_defs.h" // For exarg_T -#include "nvim/eval/typval.h" -#include "nvim/profile.h" -#include "nvim/garray.h" -#include "nvim/event/rstream.h" -#include "nvim/event/wstream.h" #include "nvim/channel.h" -#include "nvim/os/stdpaths_defs.h" +#include "nvim/eval/funcs.h" // For FunPtr +#include "nvim/event/time.h" // For TimeWatcher +#include "nvim/ex_cmds_defs.h" // For exarg_T +#include "nvim/os/fileio.h" // For FileDescriptor +#include "nvim/os/stdpaths_defs.h" // For XDGVarType #define COPYID_INC 2 #define COPYID_MASK (~0x1) @@ -24,6 +21,50 @@ EXTERN ufunc_T dumuf; #define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name))) #define HI2UF(hi) HIKEY2UF((hi)->hi_key) +/* + * Structure returned by get_lval() and used by set_var_lval(). + * For a plain name: + * "name" points to the variable name. + * "exp_name" is NULL. + * "tv" is NULL + * For a magic braces name: + * "name" points to the expanded variable name. + * "exp_name" is non-NULL, to be freed later. + * "tv" is NULL + * For an index in a list: + * "name" points to the (expanded) variable name. + * "exp_name" NULL or non-NULL, to be freed later. + * "tv" points to the (first) list item value + * "li" points to the (first) list item + * "range", "n1", "n2" and "empty2" indicate what items are used. + * For an existing Dict item: + * "name" points to the (expanded) variable name. + * "exp_name" NULL or non-NULL, to be freed later. + * "tv" points to the dict item value + * "newkey" is NULL + * For a non-existing Dict item: + * "name" points to the (expanded) variable name. + * "exp_name" NULL or non-NULL, to be freed later. + * "tv" points to the Dictionary typval_T + * "newkey" is the key for the new item. + */ +typedef struct lval_S { + const char *ll_name; ///< Start of variable name (can be NULL). + size_t ll_name_len; ///< Length of the .ll_name. + char *ll_exp_name; ///< NULL or expanded name in allocated memory. + typval_T *ll_tv; ///< Typeval of item being used. If "newkey" + ///< isn't NULL it's the Dict to which to add the item. + listitem_T *ll_li; ///< The list item or NULL. + list_T *ll_list; ///< The list or NULL. + int ll_range; ///< TRUE when a [i:j] range was used. + long ll_n1; ///< First index for list. + long ll_n2; ///< Second index for list range. + int ll_empty2; ///< Second index is empty: [i:]. + dict_T *ll_dict; ///< The Dictionary or NULL. + dictitem_T *ll_di; ///< The dictitem or NULL. + char_u *ll_newkey; ///< New key for Dict in allocated memory or NULL. +} lval_T; + /// enum used by var_flavour() typedef enum { VAR_FLAVOUR_DEFAULT = 1, // doesn't start with uppercase @@ -139,8 +180,60 @@ extern const list_T *eval_msgpack_type_lists[LAST_MSGPACK_TYPE + 1]; #undef LAST_MSGPACK_TYPE -typedef int (*ArgvFunc)(int current_argcount, typval_T *argv, - int called_func_argcount); +/// trans_function_name() flags +typedef enum { + TFN_INT = 1, ///< May use internal function name + TFN_QUIET = 2, ///< Do not emit error messages. + TFN_NO_AUTOLOAD = 4, ///< Do not use script autoloading. + TFN_NO_DEREF = 8, ///< Do not dereference a Funcref. + TFN_READ_ONLY = 16, ///< Will not change the variable. +} TransFunctionNameFlags; + +/// get_lval() flags +typedef enum { + GLV_QUIET = TFN_QUIET, ///< Do not emit error messages. + GLV_NO_AUTOLOAD = TFN_NO_AUTOLOAD, ///< Do not use script autoloading. + GLV_READ_ONLY = TFN_READ_ONLY, ///< Indicates that caller will not change + ///< the value (prevents error message). +} GetLvalFlags; + +/// flags for find_name_end() +#define FNE_INCL_BR 1 /* find_name_end(): include [] in name */ +#define FNE_CHECK_START 2 /* find_name_end(): check name starts with + valid character */ + +typedef struct { + TimeWatcher tw; + int timer_id; + int repeat_count; + int refcount; + int emsg_count; ///< Errors in a repeating timer. + long timeout; + bool stopped; + bool paused; + Callback callback; +} timer_T; + +/// Type of assert_* check being performed +typedef enum +{ + ASSERT_EQUAL, + ASSERT_NOTEQUAL, + ASSERT_MATCH, + ASSERT_NOTMATCH, + ASSERT_INRANGE, + ASSERT_OTHER, +} assert_type_T; + +/// Type for dict_list function +typedef enum { + kDictListKeys, ///< List dictionary keys. + kDictListValues, ///< List dictionary values. + kDictListItems, ///< List dictionary contents: [keys, values]. +} DictListType; + +// Used for checking if local variables or arguments used in a lambda. +extern bool *eval_lavars_used; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval.h.generated.h" diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index efeac70816..65c4cfe553 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -82,6 +82,7 @@ return { ctxset={args={1, 2}}, ctxsize={}, cursor={args={1, 3}}, + debugbreak={args={1, 1}}, deepcopy={args={1, 2}}, delete={args={1,2}}, deletebufline={args={2,3}}, @@ -101,6 +102,7 @@ return { exists={args=1}, exp={args=1, func="float_op_wrapper", data="&exp"}, expand={args={1, 3}}, + expandcmd={args=1}, extend={args={2, 3}}, feedkeys={args={1, 2}}, file_readable={args=1, func='f_filereadable'}, -- obsolete @@ -213,6 +215,7 @@ return { line={args=1}, line2byte={args=1}, lispindent={args=1}, + list2str={args={1, 2}}, localtime={}, log={args=1, func="float_op_wrapper", data="&log"}, log10={args=1, func="float_op_wrapper", data="&log10"}, @@ -243,12 +246,16 @@ return { pow={args=2}, prevnonblank={args=1}, printf={args=varargs(1)}, + prompt_setcallback={args={2, 2}}, + prompt_setinterrupt={args={2, 2}}, + prompt_setprompt={args={2, 2}}, pum_getpos={}, pumvisible={}, py3eval={args=1}, pyeval={args=1}, pyxeval={args=1}, range={args={1, 3}}, + readdir={args={1, 2}}, readfile={args={1, 3}}, reg_executing={}, reg_recording={}, @@ -317,6 +324,7 @@ return { sqrt={args=1, func="float_op_wrapper", data="&sqrt"}, stdpath={args=1}, str2float={args=1}, + str2list={args={1, 2}}, str2nr={args={1, 2}}, strcharpart={args={2, 3}}, strchars={args={1,2}}, diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 6074e4ee69..138f638eb2 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -248,7 +248,7 @@ bool encode_vim_list_to_buf(const list_T *const list, size_t *const ret_len, /// @param[out] read_bytes Is set to amount of bytes read. /// /// @return OK when reading was finished, FAIL in case of error (i.e. list item -/// was not a string), NOTDONE if reading was successfull, but there are +/// was not a string), NOTDONE if reading was successful, but there are /// more bytes to read. int encode_read_from_list(ListReaderState *const state, char *const buf, const size_t nbuf, size_t *const read_bytes) diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c new file mode 100644 index 0000000000..c7df1d6753 --- /dev/null +++ b/src/nvim/eval/funcs.c @@ -0,0 +1,11192 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +#include <float.h> +#include <math.h> + +#include "nvim/api/private/helpers.h" +#include "nvim/api/vim.h" +#include "nvim/ascii.h" +#include "nvim/assert.h" +#include "nvim/buffer.h" +#include "nvim/change.h" +#include "nvim/channel.h" +#include "nvim/charset.h" +#include "nvim/context.h" +#include "nvim/cursor.h" +#include "nvim/diff.h" +#include "nvim/edit.h" +#include "nvim/eval.h" +#include "nvim/eval/decode.h" +#include "nvim/eval/encode.h" +#include "nvim/eval/executor.h" +#include "nvim/eval/funcs.h" +#include "nvim/eval/userfunc.h" +#include "nvim/ex_cmds2.h" +#include "nvim/ex_docmd.h" +#include "nvim/ex_getln.h" +#include "nvim/file_search.h" +#include "nvim/fileio.h" +#include "nvim/fold.h" +#include "nvim/if_cscope.h" +#include "nvim/indent.h" +#include "nvim/indent_c.h" +#include "nvim/lua/executor.h" +#include "nvim/mark.h" +#include "nvim/math.h" +#include "nvim/memline.h" +#include "nvim/misc1.h" +#include "nvim/mouse.h" +#include "nvim/move.h" +#include "nvim/msgpack_rpc/channel.h" +#include "nvim/msgpack_rpc/server.h" +#include "nvim/ops.h" +#include "nvim/option.h" +#include "nvim/os/dl.h" +#include "nvim/os/input.h" +#include "nvim/os/shell.h" +#include "nvim/path.h" +#include "nvim/popupmnu.h" +#include "nvim/quickfix.h" +#include "nvim/regexp.h" +#include "nvim/screen.h" +#include "nvim/search.h" +#include "nvim/sha256.h" +#include "nvim/sign.h" +#include "nvim/spell.h" +#include "nvim/state.h" +#include "nvim/syntax.h" +#include "nvim/tag.h" +#include "nvim/ui.h" +#include "nvim/undo.h" +#include "nvim/version.h" +#include "nvim/vim.h" + + +/// Describe data to return from find_some_match() +typedef enum { + kSomeMatch, ///< Data for match(). + kSomeMatchEnd, ///< Data for matchend(). + kSomeMatchList, ///< Data for matchlist(). + kSomeMatchStr, ///< Data for matchstr(). + kSomeMatchStrPos, ///< Data for matchstrpos(). +} SomeMatchType; + +KHASH_MAP_INIT_STR(functions, VimLFuncDef) + + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/funcs.c.generated.h" + +#ifdef _MSC_VER +// This prevents MSVC from replacing the functions with intrinsics, +// and causing errors when trying to get their addresses in funcs.generated.h +#pragma function(ceil) +#pragma function(floor) +#endif + +PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES +#include "funcs.generated.h" +PRAGMA_DIAG_POP +#endif + + +static char *e_listarg = N_("E686: Argument of %s must be a List"); +static char *e_stringreq = N_("E928: String required"); + +/// Dummy va_list for passing to vim_snprintf +/// +/// Used because: +/// - passing a NULL pointer doesn't work when va_list isn't a pointer +/// - locally in the function results in a "used before set" warning +/// - using va_start() to initialize it gives "function with fixed args" error +static va_list dummy_ap; + + +/// Function given to ExpandGeneric() to obtain the list of internal +/// or user defined function names. +char_u *get_function_name(expand_T *xp, int idx) +{ + static int intidx = -1; + char_u *name; + + if (idx == 0) + intidx = -1; + if (intidx < 0) { + name = get_user_func_name(xp, idx); + if (name != NULL) + return name; + } + while ((size_t)++intidx < ARRAY_SIZE(functions) + && functions[intidx].name[0] == '\0') { + } + + if ((size_t)intidx >= ARRAY_SIZE(functions)) { + return NULL; + } + + const char *const key = functions[intidx].name; + const size_t key_len = strlen(key); + memcpy(IObuff, key, key_len); + IObuff[key_len] = '('; + if (functions[intidx].max_argc == 0) { + IObuff[key_len + 1] = ')'; + IObuff[key_len + 2] = NUL; + } else { + IObuff[key_len + 1] = NUL; + } + return IObuff; +} + +/// Function given to ExpandGeneric() to obtain the list of internal or +/// user defined variable or function names. +char_u *get_expr_name(expand_T *xp, int idx) +{ + static int intidx = -1; + char_u *name; + + if (idx == 0) + intidx = -1; + if (intidx < 0) { + name = get_function_name(xp, idx); + if (name != NULL) + return name; + } + return get_user_var_name(xp, ++intidx); +} + +/// Find internal function in hash functions +/// +/// @param[in] name Name of the function. +/// +/// Returns pointer to the function definition or NULL if not found. +const VimLFuncDef *find_internal_func(const char *const name) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL +{ + size_t len = strlen(name); + return find_internal_func_gperf(name, len); +} + +/* + * Return TRUE for a non-zero Number and a non-empty String. + */ +static int non_zero_arg(typval_T *argvars) +{ + return ((argvars[0].v_type == VAR_NUMBER + && argvars[0].vval.v_number != 0) + || (argvars[0].v_type == VAR_SPECIAL + && argvars[0].vval.v_special == kSpecialVarTrue) + || (argvars[0].v_type == VAR_STRING + && argvars[0].vval.v_string != NULL + && *argvars[0].vval.v_string != NUL)); +} + +// Apply a floating point C function on a typval with one float_T. +// +// Some versions of glibc on i386 have an optimization that makes it harder to +// call math functions indirectly from inside an inlined function, causing +// compile-time errors. Avoid `inline` in that case. #3072 +static void float_op_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + float_T f; + float_T (*function)(float_T) = (float_T (*)(float_T))fptr; + + rettv->v_type = VAR_FLOAT; + if (tv_get_float_chk(argvars, &f)) { + rettv->vval.v_float = function(f); + } else { + rettv->vval.v_float = 0.0; + } +} + +static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (check_restricted() || check_secure()) { + return; + } + + ApiDispatchWrapper fn = (ApiDispatchWrapper)fptr; + + Array args = ARRAY_DICT_INIT; + + for (typval_T *tv = argvars; tv->v_type != VAR_UNKNOWN; tv++) { + ADD(args, vim_to_object(tv)); + } + + Error err = ERROR_INIT; + Object result = fn(VIML_INTERNAL_CALL, args, &err); + + if (ERROR_SET(&err)) { + emsgf_multiline((const char *)e_api_error, err.msg); + goto end; + } + + if (!object_to_vim(result, rettv, &err)) { + EMSG2(_("Error converting the call result: %s"), err.msg); + } + +end: + api_free_array(args); + api_free_object(result); + api_clear_error(&err); +} + +/* + * "abs(expr)" function + */ +static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type == VAR_FLOAT) { + float_op_wrapper(argvars, rettv, (FunPtr)&fabs); + } else { + varnumber_T n; + bool error = false; + + n = tv_get_number_chk(&argvars[0], &error); + if (error) { + rettv->vval.v_number = -1; + } else if (n > 0) { + rettv->vval.v_number = n; + } else { + rettv->vval.v_number = -n; + } + } +} + +/* + * "add(list, item)" function + */ +static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = 1; // Default: failed. + if (argvars[0].v_type == VAR_LIST) { + list_T *const l = argvars[0].vval.v_list; + if (!tv_check_lock(tv_list_locked(l), N_("add() argument"), TV_TRANSLATE)) { + tv_list_append_tv(l, &argvars[1]); + tv_copy(&argvars[0], rettv); + } + } else { + EMSG(_(e_listreq)); + } +} + +/* + * "and(expr, expr)" function + */ +static void f_and(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) + & tv_get_number_chk(&argvars[1], NULL); +} + + +/// "api_info()" function +static void f_api_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + Dictionary metadata = api_metadata(); + (void)object_to_vim(DICTIONARY_OBJ(metadata), rettv, NULL); + api_free_dictionary(metadata); +} + +// "append(lnum, string/list)" function +static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const linenr_T lnum = tv_get_lnum(&argvars[0]); + + set_buffer_lines(curbuf, lnum, true, &argvars[1], rettv); +} + +// "appendbufline(buf, lnum, string/list)" function +static void f_appendbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + buf_T *const buf = tv_get_buf(&argvars[0], false); + if (buf == NULL) { + rettv->vval.v_number = 1; // FAIL + } else { + const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); + set_buffer_lines(buf, lnum, true, &argvars[2], rettv); + } +} + +static void f_argc(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type == VAR_UNKNOWN) { + // use the current window + rettv->vval.v_number = ARGCOUNT; + } else if (argvars[0].v_type == VAR_NUMBER + && tv_get_number(&argvars[0]) == -1) { + // use the global argument list + rettv->vval.v_number = GARGCOUNT; + } else { + // use the argument list of the specified window + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp != NULL) { + rettv->vval.v_number = WARGCOUNT(wp); + } else { + rettv->vval.v_number = -1; + } + } +} + +/* + * "argidx()" function + */ +static void f_argidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = curwin->w_arg_idx; +} + +/// "arglistid" function +static void f_arglistid(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = -1; + win_T *wp = find_tabwin(&argvars[0], &argvars[1]); + if (wp != NULL) { + rettv->vval.v_number = wp->w_alist->id; + } +} + +/* + * "argv(nr)" function + */ +static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + aentry_T *arglist = NULL; + int argcount = -1; + + if (argvars[0].v_type != VAR_UNKNOWN) { + if (argvars[1].v_type == VAR_UNKNOWN) { + arglist = ARGLIST; + argcount = ARGCOUNT; + } else if (argvars[1].v_type == VAR_NUMBER + && tv_get_number(&argvars[1]) == -1) { + arglist = GARGLIST; + argcount = GARGCOUNT; + } else { + win_T *wp = find_win_by_nr_or_id(&argvars[1]); + if (wp != NULL) { + // Use the argument list of the specified window + arglist = WARGLIST(wp); + argcount = WARGCOUNT(wp); + } + } + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + int idx = tv_get_number_chk(&argvars[0], NULL); + if (arglist != NULL && idx >= 0 && idx < argcount) { + rettv->vval.v_string = (char_u *)xstrdup( + (const char *)alist_name(&arglist[idx])); + } else if (idx == -1) { + get_arglist_as_rettv(arglist, argcount, rettv); + } + } else { + get_arglist_as_rettv(ARGLIST, ARGCOUNT, rettv); + } +} + +static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const cmd = tv_get_string_chk(&argvars[0]); + garray_T ga; + int ret = 0; + + called_vim_beep = false; + suppress_errthrow = true; + emsg_silent = false; + do_cmdline_cmd(cmd); + if (!called_vim_beep) { + prepare_assert_error(&ga); + ga_concat(&ga, (const char_u *)"command did not beep: "); + ga_concat(&ga, (const char_u *)cmd); + assert_error(&ga); + ga_clear(&ga); + ret = 1; + } + + suppress_errthrow = false; + emsg_on_display = false; + rettv->vval.v_number = ret; +} + +// "assert_equal(expected, actual[, msg])" function +static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL); +} + +// "assert_equalfile(fname-one, fname-two)" function +static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_equalfile(argvars); +} + +// "assert_notequal(expected, actual[, msg])" function +static void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL); +} + +/// "assert_report(msg) +static void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + garray_T ga; + + prepare_assert_error(&ga); + ga_concat(&ga, (const char_u *)tv_get_string(&argvars[0])); + assert_error(&ga); + ga_clear(&ga); + rettv->vval.v_number = 1; +} + +/// "assert_exception(string[, msg])" function +static void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_exception(argvars); +} + +/// "assert_fails(cmd [, error [, msg]])" function +static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_fails(argvars); +} + +// "assert_false(actual[, msg])" function +static void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_bool(argvars, false); +} + +/// "assert_inrange(lower, upper[, msg])" function +static void f_assert_inrange(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_inrange(argvars); +} + +/// "assert_match(pattern, actual[, msg])" function +static void f_assert_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH); +} + +/// "assert_notmatch(pattern, actual[, msg])" function +static void f_assert_notmatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH); +} + +// "assert_true(actual[, msg])" function +static void f_assert_true(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_bool(argvars, true); +} + +/* + * "atan2()" function + */ +static void f_atan2(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + float_T fx; + float_T fy; + + rettv->v_type = VAR_FLOAT; + if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) { + rettv->vval.v_float = atan2(fx, fy); + } else { + rettv->vval.v_float = 0.0; + } +} + +/* + * "browse(save, title, initdir, default)" function + */ +static void f_browse(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_string = NULL; + rettv->v_type = VAR_STRING; +} + +/* + * "browsedir(title, initdir)" function + */ +static void f_browsedir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + f_browse(argvars, rettv, NULL); +} + + +/* + * Find a buffer by number or exact name. + */ +static buf_T *find_buffer(typval_T *avar) +{ + buf_T *buf = NULL; + + if (avar->v_type == VAR_NUMBER) + buf = buflist_findnr((int)avar->vval.v_number); + else if (avar->v_type == VAR_STRING && avar->vval.v_string != NULL) { + buf = buflist_findname_exp(avar->vval.v_string); + if (buf == NULL) { + /* No full path name match, try a match with a URL or a "nofile" + * buffer, these don't use the full path. */ + FOR_ALL_BUFFERS(bp) { + if (bp->b_fname != NULL + && (path_with_url((char *)bp->b_fname) + || bt_nofile(bp) + ) + && STRCMP(bp->b_fname, avar->vval.v_string) == 0) { + buf = bp; + break; + } + } + } + } + return buf; +} + +// "bufadd(expr)" function +static void f_bufadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char_u *name = (char_u *)tv_get_string(&argvars[0]); + + rettv->vval.v_number = buflist_add(*name == NUL ? NULL : name, 0); +} + +/* + * "bufexists(expr)" function + */ +static void f_bufexists(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = (find_buffer(&argvars[0]) != NULL); +} + +/* + * "buflisted(expr)" function + */ +static void f_buflisted(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + buf_T *buf; + + buf = find_buffer(&argvars[0]); + rettv->vval.v_number = (buf != NULL && buf->b_p_bl); +} + +// "bufload(expr)" function +static void f_bufload(typval_T *argvars, typval_T *unused, FunPtr fptr) +{ + buf_T *buf = get_buf_arg(&argvars[0]); + + if (buf != NULL && buf->b_ml.ml_mfp == NULL) { + aco_save_T aco; + + aucmd_prepbuf(&aco, buf); + swap_exists_action = SEA_NONE; + open_buffer(false, NULL, 0); + aucmd_restbuf(&aco); + } +} + +/* + * "bufloaded(expr)" function + */ +static void f_bufloaded(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + buf_T *buf; + + buf = find_buffer(&argvars[0]); + rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL); +} + +/* + * "bufname(expr)" function + */ +static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const buf_T *buf; + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (argvars[0].v_type == VAR_UNKNOWN) { + buf = curbuf; + } else { + if (!tv_check_str_or_nr(&argvars[0])) { + return; + } + emsg_off++; + buf = tv_get_buf(&argvars[0], false); + emsg_off--; + } + if (buf != NULL && buf->b_fname != NULL) { + rettv->vval.v_string = (char_u *)xstrdup((char *)buf->b_fname); + } +} + +/* + * "bufnr(expr)" function + */ +static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const buf_T *buf; + bool error = false; + + rettv->vval.v_number = -1; + + if (argvars[0].v_type == VAR_UNKNOWN) { + buf = curbuf; + } else { + if (!tv_check_str_or_nr(&argvars[0])) { + return; + } + emsg_off++; + buf = tv_get_buf(&argvars[0], false); + emsg_off--; + } + + // If the buffer isn't found and the second argument is not zero create a + // new buffer. + const char *name; + if (buf == NULL + && argvars[1].v_type != VAR_UNKNOWN + && tv_get_number_chk(&argvars[1], &error) != 0 + && !error + && (name = tv_get_string_chk(&argvars[0])) != NULL) { + buf = buflist_new((char_u *)name, NULL, 1, 0); + } + + if (buf != NULL) { + rettv->vval.v_number = buf->b_fnum; + } +} + +static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr) +{ + if (!tv_check_str_or_nr(&argvars[0])) { + rettv->vval.v_number = -1; + return; + } + + emsg_off++; + buf_T *buf = tv_get_buf(&argvars[0], true); + if (buf == NULL) { // no need to search if buffer was not found + rettv->vval.v_number = -1; + goto end; + } + + int winnr = 0; + int winid; + bool found_buf = false; + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + winnr++; + if (wp->w_buffer == buf) { + found_buf = true; + winid = wp->handle; + break; + } + } + rettv->vval.v_number = (found_buf ? (get_nr ? winnr : winid) : -1); +end: + emsg_off--; +} + +/// "bufwinid(nr)" function +static void f_bufwinid(typval_T *argvars, typval_T *rettv, FunPtr fptr) { + buf_win_common(argvars, rettv, false); +} + +/// "bufwinnr(nr)" function +static void f_bufwinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + buf_win_common(argvars, rettv, true); +} + +/* + * Get buffer by number or pattern. + */ +buf_T *tv_get_buf(typval_T *tv, int curtab_only) +{ + char_u *name = tv->vval.v_string; + int save_magic; + char_u *save_cpo; + buf_T *buf; + + if (tv->v_type == VAR_NUMBER) + return buflist_findnr((int)tv->vval.v_number); + if (tv->v_type != VAR_STRING) + return NULL; + if (name == NULL || *name == NUL) + return curbuf; + if (name[0] == '$' && name[1] == NUL) + return lastbuf; + + // Ignore 'magic' and 'cpoptions' here to make scripts portable + save_magic = p_magic; + p_magic = TRUE; + save_cpo = p_cpo; + p_cpo = (char_u *)""; + + buf = buflist_findnr(buflist_findpat(name, name + STRLEN(name), + TRUE, FALSE, curtab_only)); + + p_magic = save_magic; + p_cpo = save_cpo; + + // If not found, try expanding the name, like done for bufexists(). + if (buf == NULL) { + buf = find_buffer(tv); + } + + return buf; +} + +/// Get the buffer from "arg" and give an error and return NULL if it is not +/// valid. +buf_T * get_buf_arg(typval_T *arg) +{ + buf_T *buf; + + emsg_off++; + buf = tv_get_buf(arg, false); + emsg_off--; + if (buf == NULL) { + EMSG2(_("E158: Invalid buffer name: %s"), tv_get_string(arg)); + } + return buf; +} + +/* + * "byte2line(byte)" function + */ +static void f_byte2line(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + long boff = tv_get_number(&argvars[0]) - 1; + if (boff < 0) { + rettv->vval.v_number = -1; + } else { + rettv->vval.v_number = (varnumber_T)ml_find_line_or_offset(curbuf, 0, + &boff, false); + } +} + +static void byteidx(typval_T *argvars, typval_T *rettv, int comp) +{ + const char *const str = tv_get_string_chk(&argvars[0]); + varnumber_T idx = tv_get_number_chk(&argvars[1], NULL); + rettv->vval.v_number = -1; + if (str == NULL || idx < 0) { + return; + } + + const char *t = str; + for (; idx > 0; idx--) { + if (*t == NUL) { // EOL reached. + return; + } + if (enc_utf8 && comp) { + t += utf_ptr2len((const char_u *)t); + } else { + t += (*mb_ptr2len)((const char_u *)t); + } + } + rettv->vval.v_number = (varnumber_T)(t - str); +} + +/* + * "byteidx()" function + */ +static void f_byteidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + byteidx(argvars, rettv, FALSE); +} + +/* + * "byteidxcomp()" function + */ +static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + byteidx(argvars, rettv, TRUE); +} + +/// "call(func, arglist [, dict])" function +static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[1].v_type != VAR_LIST) { + EMSG(_(e_listreq)); + return; + } + if (argvars[1].vval.v_list == NULL) { + return; + } + + char_u *func; + partial_T *partial = NULL; + dict_T *selfdict = NULL; + if (argvars[0].v_type == VAR_FUNC) { + func = argvars[0].vval.v_string; + } else if (argvars[0].v_type == VAR_PARTIAL) { + partial = argvars[0].vval.v_partial; + func = partial_name(partial); + } else { + func = (char_u *)tv_get_string(&argvars[0]); + } + if (*func == NUL) { + return; // type error or empty name + } + + if (argvars[2].v_type != VAR_UNKNOWN) { + if (argvars[2].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; + } + selfdict = argvars[2].vval.v_dict; + } + + func_call(func, &argvars[1], partial, selfdict, rettv); +} + +/* + * "changenr()" function + */ +static void f_changenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = curbuf->b_u_seq_cur; +} + +// "chanclose(id[, stream])" function +static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_NUMBER || (argvars[1].v_type != VAR_STRING + && argvars[1].v_type != VAR_UNKNOWN)) { + EMSG(_(e_invarg)); + return; + } + + ChannelPart part = kChannelPartAll; + if (argvars[1].v_type == VAR_STRING) { + char *stream = (char *)argvars[1].vval.v_string; + if (!strcmp(stream, "stdin")) { + part = kChannelPartStdin; + } else if (!strcmp(stream, "stdout")) { + part = kChannelPartStdout; + } else if (!strcmp(stream, "stderr")) { + part = kChannelPartStderr; + } else if (!strcmp(stream, "rpc")) { + part = kChannelPartRpc; + } else { + EMSG2(_("Invalid channel stream \"%s\""), stream); + return; + } + } + const char *error; + rettv->vval.v_number = channel_close(argvars[0].vval.v_number, part, &error); + if (!rettv->vval.v_number) { + EMSG(error); + } +} + +// "chansend(id, data)" function +static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type == VAR_UNKNOWN) { + // First argument is the channel id and second is the data to write + EMSG(_(e_invarg)); + return; + } + + ptrdiff_t input_len = 0; + char *input = save_tv_as_string(&argvars[1], &input_len, false); + if (!input) { + // Either the error has been handled by save_tv_as_string(), + // or there is no input to send. + return; + } + uint64_t id = argvars[0].vval.v_number; + const char *error = NULL; + rettv->vval.v_number = channel_send(id, input, input_len, &error); + if (error) { + EMSG(error); + } +} + +/* + * "char2nr(string)" function + */ +static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[1].v_type != VAR_UNKNOWN) { + if (!tv_check_num(&argvars[1])) { + return; + } + } + + rettv->vval.v_number = utf_ptr2char( + (const char_u *)tv_get_string(&argvars[0])); +} + +/* + * "cindent(lnum)" function + */ +static void f_cindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + pos_T pos; + linenr_T lnum; + + pos = curwin->w_cursor; + lnum = tv_get_lnum(argvars); + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { + curwin->w_cursor.lnum = lnum; + rettv->vval.v_number = get_c_indent(); + curwin->w_cursor = pos; + } else + rettv->vval.v_number = -1; +} + +/* + * "clearmatches()" function + */ +static void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + clear_matches(curwin); +} + +/* + * "col(string)" function + */ +static void f_col(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + colnr_T col = 0; + pos_T *fp; + int fnum = curbuf->b_fnum; + + fp = var2fpos(&argvars[0], FALSE, &fnum); + if (fp != NULL && fnum == curbuf->b_fnum) { + if (fp->col == MAXCOL) { + // '> can be MAXCOL, get the length of the line then + if (fp->lnum <= curbuf->b_ml.ml_line_count) { + col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1; + } else { + col = MAXCOL; + } + } else { + col = fp->col + 1; + // col(".") when the cursor is on the NUL at the end of the line + // because of "coladd" can be seen as an extra column. + if (virtual_active() && fp == &curwin->w_cursor) { + char_u *p = get_cursor_pos_ptr(); + + if (curwin->w_cursor.coladd >= (colnr_T)chartabsize(p, + curwin->w_virtcol - curwin->w_cursor.coladd)) { + int l; + + if (*p != NUL && p[(l = (*mb_ptr2len)(p))] == NUL) + col += l; + } + } + } + } + rettv->vval.v_number = col; +} + +/* + * "complete()" function + */ +static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if ((State & INSERT) == 0) { + EMSG(_("E785: complete() can only be used in Insert mode")); + return; + } + + /* Check for undo allowed here, because if something was already inserted + * the line was already saved for undo and this check isn't done. */ + if (!undo_allowed()) + return; + + if (argvars[1].v_type != VAR_LIST) { + EMSG(_(e_invarg)); + return; + } + + const colnr_T startcol = tv_get_number_chk(&argvars[0], NULL); + if (startcol <= 0) { + return; + } + + set_completion(startcol - 1, argvars[1].vval.v_list); +} + +/* + * "complete_add()" function + */ +static void f_complete_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0); +} + +/* + * "complete_check()" function + */ +static void f_complete_check(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int saved = RedrawingDisabled; + + RedrawingDisabled = 0; + ins_compl_check_keys(0, true); + rettv->vval.v_number = compl_interrupted; + RedrawingDisabled = saved; +} + +// "complete_info()" function +static void f_complete_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_alloc_ret(rettv); + + list_T *what_list = NULL; + + if (argvars[0].v_type != VAR_UNKNOWN) { + if (argvars[0].v_type != VAR_LIST) { + EMSG(_(e_listreq)); + return; + } + what_list = argvars[0].vval.v_list; + } + get_complete_info(what_list, rettv->vval.v_dict); +} + +/* + * "confirm(message, buttons[, default [, type]])" function + */ +static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char buf[NUMBUFLEN]; + char buf2[NUMBUFLEN]; + const char *message; + const char *buttons = NULL; + int def = 1; + int type = VIM_GENERIC; + const char *typestr; + bool error = false; + + message = tv_get_string_chk(&argvars[0]); + if (message == NULL) { + error = true; + } + if (argvars[1].v_type != VAR_UNKNOWN) { + buttons = tv_get_string_buf_chk(&argvars[1], buf); + if (buttons == NULL) { + error = true; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + def = tv_get_number_chk(&argvars[2], &error); + if (argvars[3].v_type != VAR_UNKNOWN) { + typestr = tv_get_string_buf_chk(&argvars[3], buf2); + if (typestr == NULL) { + error = true; + } else { + switch (TOUPPER_ASC(*typestr)) { + case 'E': type = VIM_ERROR; break; + case 'Q': type = VIM_QUESTION; break; + case 'I': type = VIM_INFO; break; + case 'W': type = VIM_WARNING; break; + case 'G': type = VIM_GENERIC; break; + } + } + } + } + } + + if (buttons == NULL || *buttons == NUL) { + buttons = _("&Ok"); + } + + if (!error) { + rettv->vval.v_number = do_dialog( + type, NULL, (char_u *)message, (char_u *)buttons, def, NULL, false); + } +} + +/* + * "copy()" function + */ +static void f_copy(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + var_item_copy(NULL, &argvars[0], rettv, false, 0); +} + +/* + * "count()" function + */ +static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + long n = 0; + int ic = 0; + bool error = false; + + if (argvars[2].v_type != VAR_UNKNOWN) { + ic = tv_get_number_chk(&argvars[2], &error); + } + + if (argvars[0].v_type == VAR_STRING) { + const char_u *expr = (char_u *)tv_get_string_chk(&argvars[1]); + const char_u *p = argvars[0].vval.v_string; + + if (!error && expr != NULL && *expr != NUL && p != NULL) { + if (ic) { + const size_t len = STRLEN(expr); + + while (*p != NUL) { + if (mb_strnicmp(p, expr, len) == 0) { + n++; + p += len; + } else { + MB_PTR_ADV(p); + } + } + } else { + char_u *next; + while ((next = (char_u *)strstr((char *)p, (char *)expr)) != NULL) { + n++; + p = next + STRLEN(expr); + } + } + } + } else if (argvars[0].v_type == VAR_LIST) { + listitem_T *li; + list_T *l; + long idx; + + if ((l = argvars[0].vval.v_list) != NULL) { + li = tv_list_first(l); + if (argvars[2].v_type != VAR_UNKNOWN) { + if (argvars[3].v_type != VAR_UNKNOWN) { + idx = tv_get_number_chk(&argvars[3], &error); + if (!error) { + li = tv_list_find(l, idx); + if (li == NULL) { + EMSGN(_(e_listidx), idx); + } + } + } + if (error) + li = NULL; + } + + for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { + if (tv_equal(TV_LIST_ITEM_TV(li), &argvars[1], ic, false)) { + n++; + } + } + } + } else if (argvars[0].v_type == VAR_DICT) { + int todo; + dict_T *d; + hashitem_T *hi; + + if ((d = argvars[0].vval.v_dict) != NULL) { + if (argvars[2].v_type != VAR_UNKNOWN) { + if (argvars[3].v_type != VAR_UNKNOWN) { + EMSG(_(e_invarg)); + } + } + + todo = error ? 0 : (int)d->dv_hashtab.ht_used; + for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + todo--; + if (tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &argvars[1], ic, false)) { + n++; + } + } + } + } + } else { + EMSG2(_(e_listdictarg), "count()"); + } + rettv->vval.v_number = n; +} + +/* + * "cscope_connection([{num} , {dbpath} [, {prepend}]])" function + * + * Checks the existence of a cscope connection. + */ +static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int num = 0; + const char *dbpath = NULL; + const char *prepend = NULL; + char buf[NUMBUFLEN]; + + if (argvars[0].v_type != VAR_UNKNOWN + && argvars[1].v_type != VAR_UNKNOWN) { + num = (int)tv_get_number(&argvars[0]); + dbpath = tv_get_string(&argvars[1]); + if (argvars[2].v_type != VAR_UNKNOWN) { + prepend = tv_get_string_buf(&argvars[2], buf); + } + } + + rettv->vval.v_number = cs_connection(num, (char_u *)dbpath, + (char_u *)prepend); +} + +/// "ctxget([{index}])" function +static void f_ctxget(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + size_t index = 0; + if (argvars[0].v_type == VAR_NUMBER) { + index = argvars[0].vval.v_number; + } else if (argvars[0].v_type != VAR_UNKNOWN) { + EMSG2(_(e_invarg2), "expected nothing or a Number as an argument"); + return; + } + + Context *ctx = ctx_get(index); + if (ctx == NULL) { + EMSG3(_(e_invargNval), "index", "out of bounds"); + return; + } + + Dictionary ctx_dict = ctx_to_dict(ctx); + Error err = ERROR_INIT; + object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err); + api_free_dictionary(ctx_dict); + api_clear_error(&err); +} + +/// "ctxpop()" function +static void f_ctxpop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (!ctx_restore(NULL, kCtxAll)) { + EMSG(_("Context stack is empty")); + } +} + +/// "ctxpush([{types}])" function +static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int types = kCtxAll; + if (argvars[0].v_type == VAR_LIST) { + types = 0; + TV_LIST_ITER(argvars[0].vval.v_list, li, { + typval_T *tv_li = TV_LIST_ITEM_TV(li); + if (tv_li->v_type == VAR_STRING) { + if (strequal((char *)tv_li->vval.v_string, "regs")) { + types |= kCtxRegs; + } else if (strequal((char *)tv_li->vval.v_string, "jumps")) { + types |= kCtxJumps; + } else if (strequal((char *)tv_li->vval.v_string, "bufs")) { + types |= kCtxBufs; + } else if (strequal((char *)tv_li->vval.v_string, "gvars")) { + types |= kCtxGVars; + } else if (strequal((char *)tv_li->vval.v_string, "sfuncs")) { + types |= kCtxSFuncs; + } else if (strequal((char *)tv_li->vval.v_string, "funcs")) { + types |= kCtxFuncs; + } + } + }); + } else if (argvars[0].v_type != VAR_UNKNOWN) { + EMSG2(_(e_invarg2), "expected nothing or a List as an argument"); + return; + } + ctx_save(NULL, types); +} + +/// "ctxset({context}[, {index}])" function +static void f_ctxset(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_DICT) { + EMSG2(_(e_invarg2), "expected dictionary as first argument"); + return; + } + + size_t index = 0; + if (argvars[1].v_type == VAR_NUMBER) { + index = argvars[1].vval.v_number; + } else if (argvars[1].v_type != VAR_UNKNOWN) { + EMSG2(_(e_invarg2), "expected nothing or a Number as second argument"); + return; + } + + Context *ctx = ctx_get(index); + if (ctx == NULL) { + EMSG3(_(e_invargNval), "index", "out of bounds"); + return; + } + + int save_did_emsg = did_emsg; + did_emsg = false; + + Dictionary dict = vim_to_object(&argvars[0]).data.dictionary; + Context tmp = CONTEXT_INIT; + ctx_from_dict(dict, &tmp); + + if (did_emsg) { + ctx_free(&tmp); + } else { + ctx_free(ctx); + *ctx = tmp; + } + + api_free_dictionary(dict); + did_emsg = save_did_emsg; +} + +/// "ctxsize()" function +static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = ctx_size(); +} + +/// "cursor(lnum, col)" function, or +/// "cursor(list)" +/// +/// Moves the cursor to the specified line and column. +/// +/// @returns 0 when the position could be set, -1 otherwise. +static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + long line, col; + long coladd = 0; + bool set_curswant = true; + + rettv->vval.v_number = -1; + if (argvars[1].v_type == VAR_UNKNOWN) { + pos_T pos; + colnr_T curswant = -1; + + if (list2fpos(argvars, &pos, NULL, &curswant) == FAIL) { + EMSG(_(e_invarg)); + return; + } + + line = pos.lnum; + col = pos.col; + coladd = pos.coladd; + if (curswant >= 0) { + curwin->w_curswant = curswant - 1; + set_curswant = false; + } + } else { + line = tv_get_lnum(argvars); + col = (long)tv_get_number_chk(&argvars[1], NULL); + if (argvars[2].v_type != VAR_UNKNOWN) { + coladd = (long)tv_get_number_chk(&argvars[2], NULL); + } + } + if (line < 0 || col < 0 + || coladd < 0) { + return; // type error; errmsg already given + } + if (line > 0) { + curwin->w_cursor.lnum = line; + } + if (col > 0) { + curwin->w_cursor.col = col - 1; + } + curwin->w_cursor.coladd = coladd; + + // Make sure the cursor is in a valid position. + check_cursor(); + // Correct cursor for multi-byte character. + if (has_mbyte) { + mb_adjust_cursor(); + } + + curwin->w_set_curswant = set_curswant; + rettv->vval.v_number = 0; +} + +// "debugbreak()" function +static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int pid; + + rettv->vval.v_number = FAIL; + pid = (int)tv_get_number(&argvars[0]); + if (pid == 0) { + EMSG(_(e_invarg)); + } else { +#ifdef WIN32 + HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid); + + if (hProcess != NULL) { + DebugBreakProcess(hProcess); + CloseHandle(hProcess); + rettv->vval.v_number = OK; + } +#else + uv_kill(pid, SIGINT); +#endif + } +} + +// "deepcopy()" function +static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int noref = 0; + + if (argvars[1].v_type != VAR_UNKNOWN) { + noref = tv_get_number_chk(&argvars[1], NULL); + } + if (noref < 0 || noref > 1) { + EMSG(_(e_invarg)); + } else { + var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0 + ? get_copyID() + : 0)); + } +} + +// "delete()" function +static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = -1; + if (check_restricted() || check_secure()) { + return; + } + + const char *const name = tv_get_string(&argvars[0]); + if (*name == NUL) { + EMSG(_(e_invarg)); + return; + } + + char nbuf[NUMBUFLEN]; + const char *flags; + if (argvars[1].v_type != VAR_UNKNOWN) { + flags = tv_get_string_buf(&argvars[1], nbuf); + } else { + flags = ""; + } + + if (*flags == NUL) { + // delete a file + rettv->vval.v_number = os_remove(name) == 0 ? 0 : -1; + } else if (strcmp(flags, "d") == 0) { + // delete an empty directory + rettv->vval.v_number = os_rmdir(name) == 0 ? 0 : -1; + } else if (strcmp(flags, "rf") == 0) { + // delete a directory recursively + rettv->vval.v_number = delete_recursive(name); + } else { + emsgf(_(e_invexpr2), flags); + } +} + +// dictwatcheradd(dict, key, funcref) function +static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_DICT) { + emsgf(_(e_invarg2), "dict"); + return; + } else if (argvars[0].vval.v_dict == NULL) { + const char *const arg_errmsg = _("dictwatcheradd() argument"); + const size_t arg_errmsg_len = strlen(arg_errmsg); + emsgf(_(e_readonlyvar), (int)arg_errmsg_len, arg_errmsg); + return; + } + + if (argvars[1].v_type != VAR_STRING && argvars[1].v_type != VAR_NUMBER) { + emsgf(_(e_invarg2), "key"); + return; + } + + const char *const key_pattern = tv_get_string_chk(argvars + 1); + if (key_pattern == NULL) { + return; + } + const size_t key_pattern_len = strlen(key_pattern); + + Callback callback; + if (!callback_from_typval(&callback, &argvars[2])) { + emsgf(_(e_invarg2), "funcref"); + return; + } + + tv_dict_watcher_add(argvars[0].vval.v_dict, key_pattern, key_pattern_len, + callback); +} + +// dictwatcherdel(dict, key, funcref) function +static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_DICT) { + emsgf(_(e_invarg2), "dict"); + return; + } + + if (argvars[2].v_type != VAR_FUNC && argvars[2].v_type != VAR_STRING) { + emsgf(_(e_invarg2), "funcref"); + return; + } + + const char *const key_pattern = tv_get_string_chk(argvars + 1); + if (key_pattern == NULL) { + return; + } + + Callback callback; + if (!callback_from_typval(&callback, &argvars[2])) { + return; + } + + if (!tv_dict_watcher_remove(argvars[0].vval.v_dict, key_pattern, + strlen(key_pattern), callback)) { + EMSG("Couldn't find a watcher matching key and callback"); + } + + callback_free(&callback); +} + +/// "deletebufline()" function +static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + linenr_T last; + buf_T *curbuf_save = NULL; + win_T *curwin_save = NULL; + + buf_T *const buf = tv_get_buf(&argvars[0], false); + if (buf == NULL) { + rettv->vval.v_number = 1; // FAIL + return; + } + const bool is_curbuf = buf == curbuf; + + const linenr_T first = tv_get_lnum_buf(&argvars[1], buf); + if (argvars[2].v_type != VAR_UNKNOWN) { + last = tv_get_lnum_buf(&argvars[2], buf); + } else { + last = first; + } + + if (buf->b_ml.ml_mfp == NULL || first < 1 + || first > buf->b_ml.ml_line_count || last < first) { + rettv->vval.v_number = 1; // FAIL + return; + } + + if (!is_curbuf) { + curbuf_save = curbuf; + curwin_save = curwin; + curbuf = buf; + find_win_for_curbuf(); + } + if (last > curbuf->b_ml.ml_line_count) { + last = curbuf->b_ml.ml_line_count; + } + const long count = last - first + 1; + + // When coming here from Insert mode, sync undo, so that this can be + // undone separately from what was previously inserted. + if (u_sync_once == 2) { + u_sync_once = 1; // notify that u_sync() was called + u_sync(true); + } + + if (u_save(first - 1, last + 1) == FAIL) { + rettv->vval.v_number = 1; // FAIL + return; + } + + for (linenr_T lnum = first; lnum <= last; lnum++) { + ml_delete(first, true); + } + + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer == buf) { + if (wp->w_cursor.lnum > last) { + wp->w_cursor.lnum -= count; + } else if (wp->w_cursor.lnum> first) { + wp->w_cursor.lnum = first; + } + if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) { + wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count; + } + } + } + check_cursor_col(); + deleted_lines_mark(first, count); + + if (!is_curbuf) { + curbuf = curbuf_save; + curwin = curwin_save; + } +} + +/* + * "did_filetype()" function + */ +static void f_did_filetype(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = did_filetype; +} + +/* + * "diff_filler()" function + */ +static void f_diff_filler(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = diff_check_fill(curwin, tv_get_lnum(argvars)); +} + +/* + * "diff_hlID()" function + */ +static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + linenr_T lnum = tv_get_lnum(argvars); + static linenr_T prev_lnum = 0; + static int changedtick = 0; + static int fnum = 0; + static int change_start = 0; + static int change_end = 0; + static hlf_T hlID = (hlf_T)0; + int filler_lines; + int col; + + if (lnum < 0) { // ignore type error in {lnum} arg + lnum = 0; + } + if (lnum != prev_lnum + || changedtick != buf_get_changedtick(curbuf) + || fnum != curbuf->b_fnum) { + // New line, buffer, change: need to get the values. + filler_lines = diff_check(curwin, lnum); + if (filler_lines < 0) { + if (filler_lines == -1) { + change_start = MAXCOL; + change_end = -1; + if (diff_find_change(curwin, lnum, &change_start, &change_end)) { + hlID = HLF_ADD; // added line + } else { + hlID = HLF_CHD; // changed line + } + } else { + hlID = HLF_ADD; // added line + } + } else { + hlID = (hlf_T)0; + } + prev_lnum = lnum; + changedtick = buf_get_changedtick(curbuf); + fnum = curbuf->b_fnum; + } + + if (hlID == HLF_CHD || hlID == HLF_TXD) { + col = tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}. + if (col >= change_start && col <= change_end) { + hlID = HLF_TXD; // Changed text. + } else { + hlID = HLF_CHD; // Changed line. + } + } + rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (int)(hlID + 1); +} + +/* + * "empty({expr})" function + */ +static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + bool n = true; + + switch (argvars[0].v_type) { + case VAR_STRING: + case VAR_FUNC: { + n = argvars[0].vval.v_string == NULL + || *argvars[0].vval.v_string == NUL; + break; + } + case VAR_PARTIAL: { + n = false; + break; + } + case VAR_NUMBER: { + n = argvars[0].vval.v_number == 0; + break; + } + case VAR_FLOAT: { + n = argvars[0].vval.v_float == 0.0; + break; + } + case VAR_LIST: { + n = (tv_list_len(argvars[0].vval.v_list) == 0); + break; + } + case VAR_DICT: { + n = (tv_dict_len(argvars[0].vval.v_dict) == 0); + break; + } + case VAR_SPECIAL: { + // Using switch to get warning if SpecialVarValue receives more values. + switch (argvars[0].vval.v_special) { + case kSpecialVarTrue: { + n = false; + break; + } + case kSpecialVarFalse: + case kSpecialVarNull: { + n = true; + break; + } + } + break; + } + case VAR_UNKNOWN: { + internal_error("f_empty(UNKNOWN)"); + break; + } + } + + rettv->vval.v_number = n; +} + +/// "environ()" function +static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_alloc_ret(rettv); + + size_t env_size = os_get_fullenv_size(); + char **env = xmalloc(sizeof(*env) * (env_size + 1)); + env[env_size] = NULL; + + os_copy_fullenv(env, env_size); + + for (size_t i = 0; i < env_size; i++) { + const char * str = env[i]; + const char * const end = strchr(str + (str[0] == '=' ? 1 : 0), + '='); + assert(end != NULL); + ptrdiff_t len = end - str; + assert(len > 0); + const char * value = str + len + 1; + tv_dict_add_str(rettv->vval.v_dict, + str, len, + value); + } + os_free_fullenv(env); +} + +/* + * "escape({string}, {chars})" function + */ +static void f_escape(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char buf[NUMBUFLEN]; + + rettv->vval.v_string = vim_strsave_escaped( + (const char_u *)tv_get_string(&argvars[0]), + (const char_u *)tv_get_string_buf(&argvars[1], buf)); + rettv->v_type = VAR_STRING; +} + +/// "getenv()" function +static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char_u *p = (char_u *)vim_getenv(tv_get_string(&argvars[0])); + + if (p == NULL) { + rettv->v_type = VAR_SPECIAL; + rettv->vval.v_special = kSpecialVarNull; + return; + } + rettv->vval.v_string = p; + rettv->v_type = VAR_STRING; +} + +/* + * "eval()" function + */ +static void f_eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *s = tv_get_string_chk(&argvars[0]); + if (s != NULL) { + s = (const char *)skipwhite((const char_u *)s); + } + + const char *const expr_start = s; + if (s == NULL || eval1((char_u **)&s, rettv, true) == FAIL) { + if (expr_start != NULL && !aborting()) { + EMSG2(_(e_invexpr2), expr_start); + } + need_clr_eos = FALSE; + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + } else if (*s != NUL) { + EMSG(_(e_trailing)); + } +} + +/* + * "eventhandler()" function + */ +static void f_eventhandler(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = vgetc_busy; +} + +/* + * "executable()" function + */ +static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *name = tv_get_string(&argvars[0]); + + // Check in $PATH and also check directly if there is a directory name + rettv->vval.v_number = os_can_exe(name, NULL, true); +} + +typedef struct { + const list_T *const l; + const listitem_T *li; +} GetListLineCookie; + +static char_u *get_list_line(int c, void *cookie, int indent, bool do_concat) +{ + GetListLineCookie *const p = (GetListLineCookie *)cookie; + + const listitem_T *const item = p->li; + if (item == NULL) { + return NULL; + } + char buf[NUMBUFLEN]; + const char *const s = tv_get_string_buf_chk(TV_LIST_ITEM_TV(item), buf); + p->li = TV_LIST_ITEM_NEXT(p->l, item); + return (char_u *)(s == NULL ? NULL : xstrdup(s)); +} + +// "execute(command)" function +static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const int save_msg_silent = msg_silent; + const int save_emsg_silent = emsg_silent; + const bool save_emsg_noredir = emsg_noredir; + const bool save_redir_off = redir_off; + garray_T *const save_capture_ga = capture_ga; + const int save_msg_col = msg_col; + bool echo_output = false; + + if (check_secure()) { + return; + } + + if (argvars[1].v_type != VAR_UNKNOWN) { + char buf[NUMBUFLEN]; + const char *const s = tv_get_string_buf_chk(&argvars[1], buf); + + if (s == NULL) { + return; + } + if (*s == NUL) { + echo_output = true; + } + if (strncmp(s, "silent", 6) == 0) { + msg_silent++; + } + if (strcmp(s, "silent!") == 0) { + emsg_silent = true; + emsg_noredir = true; + } + } else { + msg_silent++; + } + + garray_T capture_local; + ga_init(&capture_local, (int)sizeof(char), 80); + capture_ga = &capture_local; + redir_off = false; + if (!echo_output) { + msg_col = 0; // prevent leading spaces + } + + if (argvars[0].v_type != VAR_LIST) { + do_cmdline_cmd(tv_get_string(&argvars[0])); + } else if (argvars[0].vval.v_list != NULL) { + list_T *const list = argvars[0].vval.v_list; + tv_list_ref(list); + GetListLineCookie cookie = { + .l = list, + .li = tv_list_first(list), + }; + do_cmdline(NULL, get_list_line, (void *)&cookie, + DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT|DOCMD_KEYTYPED); + tv_list_unref(list); + } + msg_silent = save_msg_silent; + emsg_silent = save_emsg_silent; + emsg_noredir = save_emsg_noredir; + redir_off = save_redir_off; + // "silent reg" or "silent echo x" leaves msg_col somewhere in the line. + if (echo_output) { + // When not working silently: put it in column zero. A following + // "echon" will overwrite the message, unavoidably. + msg_col = 0; + } else { + // When working silently: Put it back where it was, since nothing + // should have been written. + msg_col = save_msg_col; + } + + ga_append(capture_ga, NUL); + rettv->v_type = VAR_STRING; + rettv->vval.v_string = capture_ga->ga_data; + + capture_ga = save_capture_ga; +} + +/// "exepath()" function +static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *arg = tv_get_string(&argvars[0]); + char *path = NULL; + + (void)os_can_exe(arg, &path, true); + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)path; +} + +/* + * "exists()" function + */ +static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int n = false; + int len = 0; + + const char *p = tv_get_string(&argvars[0]); + if (*p == '$') { // Environment variable. + // First try "normal" environment variables (fast). + if (os_env_exists(p + 1)) { + n = true; + } else { + // Try expanding things like $VIM and ${HOME}. + char_u *const exp = expand_env_save((char_u *)p); + if (exp != NULL && *exp != '$') { + n = true; + } + xfree(exp); + } + } else if (*p == '&' || *p == '+') { // Option. + n = (get_option_tv(&p, NULL, true) == OK); + if (*skipwhite((const char_u *)p) != NUL) { + n = false; // Trailing garbage. + } + } else if (*p == '*') { // Internal or user defined function. + n = function_exists(p + 1, false); + } else if (*p == ':') { + n = cmd_exists(p + 1); + } else if (*p == '#') { + if (p[1] == '#') { + n = autocmd_supported(p + 2); + } else { + n = au_exists(p + 1); + } + } else { // Internal variable. + typval_T tv; + + // get_name_len() takes care of expanding curly braces + const char *name = p; + char *tofree; + len = get_name_len((const char **)&p, &tofree, true, false); + if (len > 0) { + if (tofree != NULL) { + name = tofree; + } + n = (get_var_tv(name, len, &tv, NULL, false, true) == OK); + if (n) { + // Handle d.key, l[idx], f(expr). + n = (handle_subscript(&p, &tv, true, false) == OK); + if (n) { + tv_clear(&tv); + } + } + } + if (*p != NUL) + n = FALSE; + + xfree(tofree); + } + + rettv->vval.v_number = n; +} + +/* + * "expand()" function + */ +static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + size_t len; + char_u *errormsg; + int options = WILD_SILENT|WILD_USE_NL|WILD_LIST_NOTFOUND; + expand_T xpc; + bool error = false; + char_u *result; + + rettv->v_type = VAR_STRING; + if (argvars[1].v_type != VAR_UNKNOWN + && argvars[2].v_type != VAR_UNKNOWN + && tv_get_number_chk(&argvars[2], &error) + && !error) { + tv_list_set_ret(rettv, NULL); + } + + const char *s = tv_get_string(&argvars[0]); + if (*s == '%' || *s == '#' || *s == '<') { + emsg_off++; + result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL); + emsg_off--; + if (rettv->v_type == VAR_LIST) { + tv_list_alloc_ret(rettv, (result != NULL)); + if (result != NULL) { + tv_list_append_string(rettv->vval.v_list, (const char *)result, -1); + } + XFREE_CLEAR(result); + } else { + rettv->vval.v_string = result; + } + } else { + // When the optional second argument is non-zero, don't remove matches + // for 'wildignore' and don't put matches for 'suffixes' at the end. + if (argvars[1].v_type != VAR_UNKNOWN + && tv_get_number_chk(&argvars[1], &error)) { + options |= WILD_KEEP_ALL; + } + if (!error) { + ExpandInit(&xpc); + xpc.xp_context = EXPAND_FILES; + if (p_wic) { + options += WILD_ICASE; + } + if (rettv->v_type == VAR_STRING) { + rettv->vval.v_string = ExpandOne(&xpc, (char_u *)s, NULL, options, + WILD_ALL); + } else { + ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); + for (int i = 0; i < xpc.xp_numfiles; i++) { + tv_list_append_string(rettv->vval.v_list, + (const char *)xpc.xp_files[i], -1); + } + ExpandCleanup(&xpc); + } + } else { + rettv->vval.v_string = NULL; + } + } +} + + +/// "menu_get(path [, modes])" function +static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_list_alloc_ret(rettv, kListLenMayKnow); + int modes = MENU_ALL_MODES; + if (argvars[1].v_type == VAR_STRING) { + const char_u *const strmodes = (char_u *)tv_get_string(&argvars[1]); + modes = get_menu_cmd_modes(strmodes, false, NULL, NULL); + } + menu_get((char_u *)tv_get_string(&argvars[0]), modes, rettv->vval.v_list); +} + +// "expandcmd()" function +// Expand all the special characters in a command string. +static void f_expandcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char_u *errormsg = NULL; + + rettv->v_type = VAR_STRING; + char_u *cmdstr = (char_u *)xstrdup(tv_get_string(&argvars[0])); + + exarg_T eap = { + .cmd = cmdstr, + .arg = cmdstr, + .usefilter = false, + .nextcmd = NULL, + .cmdidx = CMD_USER, + }; + eap.argt |= NOSPC; + + expand_filename(&eap, &cmdstr, &errormsg); + if (errormsg != NULL && *errormsg != NUL) { + EMSG(errormsg); + } + rettv->vval.v_string = cmdstr; +} + +/* + * "extend(list, list [, idx])" function + * "extend(dict, dict [, action])" function + */ +static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const arg_errmsg = N_("extend() argument"); + + if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) { + long before; + bool error = false; + + list_T *const l1 = argvars[0].vval.v_list; + list_T *const l2 = argvars[1].vval.v_list; + if (!tv_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) { + listitem_T *item; + if (argvars[2].v_type != VAR_UNKNOWN) { + before = (long)tv_get_number_chk(&argvars[2], &error); + if (error) { + return; // Type error; errmsg already given. + } + + if (before == tv_list_len(l1)) { + item = NULL; + } else { + item = tv_list_find(l1, before); + if (item == NULL) { + EMSGN(_(e_listidx), before); + return; + } + } + } else { + item = NULL; + } + tv_list_extend(l1, l2, item); + + tv_copy(&argvars[0], rettv); + } + } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == + VAR_DICT) { + dict_T *const d1 = argvars[0].vval.v_dict; + dict_T *const d2 = argvars[1].vval.v_dict; + if (d1 == NULL) { + const bool locked = tv_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE); + (void)locked; + assert(locked == true); + } else if (d2 == NULL) { + // Do nothing + tv_copy(&argvars[0], rettv); + } else if (!tv_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) { + const char *action = "force"; + // Check the third argument. + if (argvars[2].v_type != VAR_UNKNOWN) { + const char *const av[] = { "keep", "force", "error" }; + + action = tv_get_string_chk(&argvars[2]); + if (action == NULL) { + return; // Type error; error message already given. + } + size_t i; + for (i = 0; i < ARRAY_SIZE(av); i++) { + if (strcmp(action, av[i]) == 0) { + break; + } + } + if (i == 3) { + EMSG2(_(e_invarg2), action); + return; + } + } + + tv_dict_extend(d1, d2, action); + + tv_copy(&argvars[0], rettv); + } + } else { + EMSG2(_(e_listdictarg), "extend()"); + } +} + +/* + * "feedkeys()" function + */ +static void f_feedkeys(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + // This is not allowed in the sandbox. If the commands would still be + // executed in the sandbox it would be OK, but it probably happens later, + // when "sandbox" is no longer set. + if (check_secure()) { + return; + } + + const char *const keys = tv_get_string(&argvars[0]); + char nbuf[NUMBUFLEN]; + const char *flags = NULL; + if (argvars[1].v_type != VAR_UNKNOWN) { + flags = tv_get_string_buf(&argvars[1], nbuf); + } + + nvim_feedkeys(cstr_as_string((char *)keys), + cstr_as_string((char *)flags), true); +} + +/// "filereadable()" function +static void f_filereadable(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const p = tv_get_string(&argvars[0]); + rettv->vval.v_number = + (*p && !os_isdir((const char_u *)p) && os_file_is_readable(p)); +} + +/* + * Return 0 for not writable, 1 for writable file, 2 for a dir which we have + * rights to write into. + */ +static void f_filewritable(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *filename = tv_get_string(&argvars[0]); + rettv->vval.v_number = os_file_is_writable(filename); +} + + +static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) +{ + char_u *fresult = NULL; + char_u *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; + int count = 1; + bool first = true; + bool error = false; + + rettv->vval.v_string = NULL; + rettv->v_type = VAR_STRING; + + const char *fname = tv_get_string(&argvars[0]); + + char pathbuf[NUMBUFLEN]; + if (argvars[1].v_type != VAR_UNKNOWN) { + const char *p = tv_get_string_buf_chk(&argvars[1], pathbuf); + if (p == NULL) { + error = true; + } else { + if (*p != NUL) { + path = (char_u *)p; + } + + if (argvars[2].v_type != VAR_UNKNOWN) { + count = tv_get_number_chk(&argvars[2], &error); + } + } + } + + if (count < 0) { + tv_list_alloc_ret(rettv, kListLenUnknown); + } + + if (*fname != NUL && !error) { + do { + if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST) + xfree(fresult); + fresult = find_file_in_path_option(first ? (char_u *)fname : NULL, + first ? strlen(fname) : 0, + 0, first, path, + find_what, curbuf->b_ffname, + (find_what == FINDFILE_DIR + ? (char_u *)"" + : curbuf->b_p_sua)); + first = false; + + if (fresult != NULL && rettv->v_type == VAR_LIST) { + tv_list_append_string(rettv->vval.v_list, (const char *)fresult, -1); + } + } while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL); + } + + if (rettv->v_type == VAR_STRING) + rettv->vval.v_string = fresult; +} + + +/* + * "filter()" function + */ +static void f_filter(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + filter_map(argvars, rettv, FALSE); +} + +/* + * "finddir({fname}[, {path}[, {count}]])" function + */ +static void f_finddir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + findfilendir(argvars, rettv, FINDFILE_DIR); +} + +/* + * "findfile({fname}[, {path}[, {count}]])" function + */ +static void f_findfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + findfilendir(argvars, rettv, FINDFILE_FILE); +} + +/* + * "float2nr({float})" function + */ +static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + float_T f; + + if (tv_get_float_chk(argvars, &f)) { + if (f <= -VARNUMBER_MAX + DBL_EPSILON) { + rettv->vval.v_number = -VARNUMBER_MAX; + } else if (f >= VARNUMBER_MAX - DBL_EPSILON) { + rettv->vval.v_number = VARNUMBER_MAX; + } else { + rettv->vval.v_number = (varnumber_T)f; + } + } +} + +/* + * "fmod()" function + */ +static void f_fmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + float_T fx; + float_T fy; + + rettv->v_type = VAR_FLOAT; + if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) { + rettv->vval.v_float = fmod(fx, fy); + } else { + rettv->vval.v_float = 0.0; + } +} + +/* + * "fnameescape({string})" function + */ +static void f_fnameescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_string = (char_u *)vim_strsave_fnameescape( + tv_get_string(&argvars[0]), false); + rettv->v_type = VAR_STRING; +} + +/* + * "fnamemodify({fname}, {mods})" function + */ +static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char_u *fbuf = NULL; + size_t len; + char buf[NUMBUFLEN]; + const char *fname = tv_get_string_chk(&argvars[0]); + const char *const mods = tv_get_string_buf_chk(&argvars[1], buf); + if (fname == NULL || mods == NULL) { + fname = NULL; + } else { + len = strlen(fname); + size_t usedlen = 0; + (void)modify_fname((char_u *)mods, false, &usedlen, + (char_u **)&fname, &fbuf, &len); + } + + rettv->v_type = VAR_STRING; + if (fname == NULL) { + rettv->vval.v_string = NULL; + } else { + rettv->vval.v_string = (char_u *)xmemdupz(fname, len); + } + xfree(fbuf); +} + + +/* + * "foldclosed()" function + */ +static void foldclosed_both(typval_T *argvars, typval_T *rettv, int end) +{ + const linenr_T lnum = tv_get_lnum(argvars); + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { + linenr_T first; + linenr_T last; + if (hasFoldingWin(curwin, lnum, &first, &last, false, NULL)) { + if (end) { + rettv->vval.v_number = (varnumber_T)last; + } else { + rettv->vval.v_number = (varnumber_T)first; + } + return; + } + } + rettv->vval.v_number = -1; +} + +/* + * "foldclosed()" function + */ +static void f_foldclosed(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + foldclosed_both(argvars, rettv, FALSE); +} + +/* + * "foldclosedend()" function + */ +static void f_foldclosedend(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + foldclosed_both(argvars, rettv, TRUE); +} + +/* + * "foldlevel()" function + */ +static void f_foldlevel(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const linenr_T lnum = tv_get_lnum(argvars); + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { + rettv->vval.v_number = foldLevel(lnum); + } +} + +/* + * "foldtext()" function + */ +static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + linenr_T foldstart; + linenr_T foldend; + char_u *dashes; + linenr_T lnum; + char_u *s; + char_u *r; + int len; + char *txt; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + foldstart = (linenr_T)get_vim_var_nr(VV_FOLDSTART); + foldend = (linenr_T)get_vim_var_nr(VV_FOLDEND); + dashes = get_vim_var_str(VV_FOLDDASHES); + if (foldstart > 0 && foldend <= curbuf->b_ml.ml_line_count) { + // Find first non-empty line in the fold. + for (lnum = foldstart; lnum < foldend; lnum++) { + if (!linewhite(lnum)) { + break; + } + } + + // Find interesting text in this line. + s = skipwhite(ml_get(lnum)); + // skip C comment-start + if (s[0] == '/' && (s[1] == '*' || s[1] == '/')) { + s = skipwhite(s + 2); + if (*skipwhite(s) == NUL && lnum + 1 < foldend) { + s = skipwhite(ml_get(lnum + 1)); + if (*s == '*') + s = skipwhite(s + 1); + } + } + unsigned long count = (unsigned long)(foldend - foldstart + 1); + txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count); + r = xmalloc(STRLEN(txt) + + STRLEN(dashes) // for %s + + 20 // for %3ld + + STRLEN(s)); // concatenated + sprintf((char *)r, txt, dashes, count); + len = (int)STRLEN(r); + STRCAT(r, s); + // remove 'foldmarker' and 'commentstring' + foldtext_cleanup(r + len); + rettv->vval.v_string = r; + } +} + +/* + * "foldtextresult(lnum)" function + */ +static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char_u *text; + char_u buf[FOLD_TEXT_LEN]; + foldinfo_T foldinfo; + int fold_count; + static bool entered = false; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (entered) { + return; // reject recursive use + } + entered = true; + linenr_T lnum = tv_get_lnum(argvars); + // Treat illegal types and illegal string values for {lnum} the same. + if (lnum < 0) { + lnum = 0; + } + fold_count = foldedCount(curwin, lnum, &foldinfo); + if (fold_count > 0) { + text = get_foldtext(curwin, lnum, lnum + fold_count - 1, &foldinfo, buf); + if (text == buf) { + text = vim_strsave(text); + } + rettv->vval.v_string = text; + } + + entered = false; +} + +/* + * "foreground()" function + */ +static void f_foreground(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ +} + +static void f_funcref(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + common_function(argvars, rettv, true, fptr); +} + +static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + common_function(argvars, rettv, false, fptr); +} + +/// "garbagecollect()" function +static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + // This is postponed until we are back at the toplevel, because we may be + // using Lists and Dicts internally. E.g.: ":echo [garbagecollect()]". + want_garbage_collect = true; + + if (argvars[0].v_type != VAR_UNKNOWN && tv_get_number(&argvars[0]) == 1) { + garbage_collect_at_exit = true; + } +} + +/* + * "get()" function + */ +static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + listitem_T *li; + list_T *l; + dictitem_T *di; + dict_T *d; + typval_T *tv = NULL; + bool what_is_dict = false; + + if (argvars[0].v_type == VAR_LIST) { + if ((l = argvars[0].vval.v_list) != NULL) { + bool error = false; + + li = tv_list_find(l, tv_get_number_chk(&argvars[1], &error)); + if (!error && li != NULL) { + tv = TV_LIST_ITEM_TV(li); + } + } + } else if (argvars[0].v_type == VAR_DICT) { + if ((d = argvars[0].vval.v_dict) != NULL) { + di = tv_dict_find(d, tv_get_string(&argvars[1]), -1); + if (di != NULL) { + tv = &di->di_tv; + } + } + } else if (tv_is_func(argvars[0])) { + partial_T *pt; + partial_T fref_pt; + + if (argvars[0].v_type == VAR_PARTIAL) { + pt = argvars[0].vval.v_partial; + } else { + memset(&fref_pt, 0, sizeof(fref_pt)); + fref_pt.pt_name = argvars[0].vval.v_string; + pt = &fref_pt; + } + + if (pt != NULL) { + const char *const what = tv_get_string(&argvars[1]); + + if (strcmp(what, "func") == 0 || strcmp(what, "name") == 0) { + rettv->v_type = (*what == 'f' ? VAR_FUNC : VAR_STRING); + const char *const n = (const char *)partial_name(pt); + assert(n != NULL); + rettv->vval.v_string = (char_u *)xstrdup(n); + if (rettv->v_type == VAR_FUNC) { + func_ref(rettv->vval.v_string); + } + } else if (strcmp(what, "dict") == 0) { + what_is_dict = true; + if (pt->pt_dict != NULL) { + tv_dict_set_ret(rettv, pt->pt_dict); + } + } else if (strcmp(what, "args") == 0) { + rettv->v_type = VAR_LIST; + if (tv_list_alloc_ret(rettv, pt->pt_argc) != NULL) { + for (int i = 0; i < pt->pt_argc; i++) { + tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]); + } + } + } else { + EMSG2(_(e_invarg2), what); + } + + // When {what} == "dict" and pt->pt_dict == NULL, evaluate the + // third argument + if (!what_is_dict) { + return; + } + } + } else { + EMSG2(_(e_listdictarg), "get()"); + } + + if (tv == NULL) { + if (argvars[2].v_type != VAR_UNKNOWN) { + tv_copy(&argvars[2], rettv); + } + } else { + tv_copy(tv, rettv); + } +} + +/// "getbufinfo()" function +static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + buf_T *argbuf = NULL; + bool filtered = false; + bool sel_buflisted = false; + bool sel_bufloaded = false; + bool sel_bufmodified = false; + + tv_list_alloc_ret(rettv, kListLenMayKnow); + + // List of all the buffers or selected buffers + if (argvars[0].v_type == VAR_DICT) { + dict_T *sel_d = argvars[0].vval.v_dict; + + if (sel_d != NULL) { + dictitem_T *di; + + filtered = true; + + di = tv_dict_find(sel_d, S_LEN("buflisted")); + if (di != NULL && tv_get_number(&di->di_tv)) { + sel_buflisted = true; + } + + di = tv_dict_find(sel_d, S_LEN("bufloaded")); + if (di != NULL && tv_get_number(&di->di_tv)) { + sel_bufloaded = true; + } + di = tv_dict_find(sel_d, S_LEN("bufmodified")); + if (di != NULL && tv_get_number(&di->di_tv)) { + sel_bufmodified = true; + } + } + } else if (argvars[0].v_type != VAR_UNKNOWN) { + // Information about one buffer. Argument specifies the buffer + if (tv_check_num(&argvars[0])) { // issue errmsg if type error + emsg_off++; + argbuf = tv_get_buf(&argvars[0], false); + emsg_off--; + if (argbuf == NULL) { + return; + } + } + } + + // Return information about all the buffers or a specified buffer + FOR_ALL_BUFFERS(buf) { + if (argbuf != NULL && argbuf != buf) { + continue; + } + if (filtered && ((sel_bufloaded && buf->b_ml.ml_mfp == NULL) + || (sel_buflisted && !buf->b_p_bl) + || (sel_bufmodified && !buf->b_changed))) { + continue; + } + + dict_T *const d = get_buffer_info(buf); + tv_list_append_dict(rettv->vval.v_list, d); + if (argbuf != NULL) { + return; + } + } +} + +/* + * Get line or list of lines from buffer "buf" into "rettv". + * Return a range (from start to end) of lines in rettv from the specified + * buffer. + * If 'retlist' is TRUE, then the lines are returned as a Vim List. + */ +static void get_buffer_lines(buf_T *buf, + linenr_T start, + linenr_T end, + int retlist, + typval_T *rettv) +{ + rettv->v_type = (retlist ? VAR_LIST : VAR_STRING); + rettv->vval.v_string = NULL; + + if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0 || end < start) { + if (retlist) { + tv_list_alloc_ret(rettv, 0); + } + return; + } + + if (retlist) { + if (start < 1) { + start = 1; + } + if (end > buf->b_ml.ml_line_count) { + end = buf->b_ml.ml_line_count; + } + tv_list_alloc_ret(rettv, end - start + 1); + while (start <= end) { + tv_list_append_string(rettv->vval.v_list, + (const char *)ml_get_buf(buf, start++, false), -1); + } + } else { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = ((start >= 1 && start <= buf->b_ml.ml_line_count) + ? vim_strsave(ml_get_buf(buf, start, false)) + : NULL); + } +} + +/* + * "getbufline()" function + */ +static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + buf_T *buf = NULL; + + if (tv_check_str_or_nr(&argvars[0])) { + emsg_off++; + buf = tv_get_buf(&argvars[0], false); + emsg_off--; + } + + const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); + const linenr_T end = (argvars[2].v_type == VAR_UNKNOWN + ? lnum + : tv_get_lnum_buf(&argvars[2], buf)); + + get_buffer_lines(buf, lnum, end, true, rettv); +} + +/* + * "getbufvar()" function + */ +static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + bool done = false; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + if (!tv_check_str_or_nr(&argvars[0])) { + goto f_getbufvar_end; + } + + const char *varname = tv_get_string_chk(&argvars[1]); + emsg_off++; + buf_T *const buf = tv_get_buf(&argvars[0], false); + + if (buf != NULL && varname != NULL) { + if (*varname == '&') { // buffer-local-option + buf_T *const save_curbuf = curbuf; + + // set curbuf to be our buf, temporarily + curbuf = buf; + + if (varname[1] == NUL) { + // get all buffer-local options in a dict + dict_T *opts = get_winbuf_options(true); + + if (opts != NULL) { + tv_dict_set_ret(rettv, opts); + done = true; + } + } else if (get_option_tv(&varname, rettv, true) == OK) { + // buffer-local-option + done = true; + } + + // restore previous notion of curbuf + curbuf = save_curbuf; + } else { + // Look up the variable. + // Let getbufvar({nr}, "") return the "b:" dictionary. + dictitem_T *const v = *varname == NUL + ? (dictitem_T *)&buf->b_bufvar + : find_var_in_ht(&buf->b_vars->dv_hashtab, 'b', + varname, strlen(varname), false); + if (v != NULL) { + tv_copy(&v->di_tv, rettv); + done = true; + } + } + } + emsg_off--; + +f_getbufvar_end: + if (!done && argvars[2].v_type != VAR_UNKNOWN) { + // use the default value + tv_copy(&argvars[2], rettv); + } +} + +// "getchangelist()" function +static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_list_alloc_ret(rettv, 2); + vim_ignored = tv_get_number(&argvars[0]); // issue errmsg if type error + emsg_off++; + const buf_T *const buf = tv_get_buf(&argvars[0], false); + emsg_off--; + if (buf == NULL) { + return; + } + + list_T *const l = tv_list_alloc(buf->b_changelistlen); + tv_list_append_list(rettv->vval.v_list, l); + // The current window change list index tracks only the position in the + // current buffer change list. For other buffers, use the change list + // length as the current index. + tv_list_append_number(rettv->vval.v_list, + (buf == curwin->w_buffer) + ? curwin->w_changelistidx + : buf->b_changelistlen); + + for (int i = 0; i < buf->b_changelistlen; i++) { + if (buf->b_changelist[i].mark.lnum == 0) { + continue; + } + dict_T *const d = tv_dict_alloc(); + tv_list_append_dict(l, d); + tv_dict_add_nr(d, S_LEN("lnum"), buf->b_changelist[i].mark.lnum); + tv_dict_add_nr(d, S_LEN("col"), buf->b_changelist[i].mark.col); + tv_dict_add_nr(d, S_LEN("coladd"), buf->b_changelist[i].mark.coladd); + } +} + +/* + * "getchar()" function + */ +static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + varnumber_T n; + bool error = false; + + no_mapping++; + for (;; ) { + // Position the cursor. Needed after a message that ends in a space, + // or if event processing caused a redraw. + ui_cursor_goto(msg_row, msg_col); + + if (argvars[0].v_type == VAR_UNKNOWN) { + // getchar(): blocking wait. + if (!(char_avail() || using_script() || input_available())) { + (void)os_inchar(NULL, 0, -1, 0, main_loop.events); + if (!multiqueue_empty(main_loop.events)) { + multiqueue_process_events(main_loop.events); + continue; + } + } + n = safe_vgetc(); + } else if (tv_get_number_chk(&argvars[0], &error) == 1) { + // getchar(1): only check if char avail + n = vpeekc_any(); + } else if (error || vpeekc_any() == NUL) { + // illegal argument or getchar(0) and no char avail: return zero + n = 0; + } else { + // getchar(0) and char avail: return char + n = safe_vgetc(); + } + + if (n == K_IGNORE) { + continue; + } + break; + } + no_mapping--; + + set_vim_var_nr(VV_MOUSE_WIN, 0); + set_vim_var_nr(VV_MOUSE_WINID, 0); + set_vim_var_nr(VV_MOUSE_LNUM, 0); + set_vim_var_nr(VV_MOUSE_COL, 0); + + rettv->vval.v_number = n; + if (IS_SPECIAL(n) || mod_mask != 0) { + char_u temp[10]; // modifier: 3, mbyte-char: 6, NUL: 1 + int i = 0; + + // Turn a special key into three bytes, plus modifier. + if (mod_mask != 0) { + temp[i++] = K_SPECIAL; + temp[i++] = KS_MODIFIER; + temp[i++] = mod_mask; + } + if (IS_SPECIAL(n)) { + temp[i++] = K_SPECIAL; + temp[i++] = K_SECOND(n); + temp[i++] = K_THIRD(n); + } else { + i += utf_char2bytes(n, temp + i); + } + temp[i++] = NUL; + rettv->v_type = VAR_STRING; + rettv->vval.v_string = vim_strsave(temp); + + if (is_mouse_key(n)) { + int row = mouse_row; + int col = mouse_col; + int grid = mouse_grid; + win_T *win; + linenr_T lnum; + win_T *wp; + int winnr = 1; + + if (row >= 0 && col >= 0) { + /* Find the window at the mouse coordinates and compute the + * text position. */ + win = mouse_find_win(&grid, &row, &col); + if (win == NULL) { + return; + } + (void)mouse_comp_pos(win, &row, &col, &lnum); + for (wp = firstwin; wp != win; wp = wp->w_next) + ++winnr; + set_vim_var_nr(VV_MOUSE_WIN, winnr); + set_vim_var_nr(VV_MOUSE_WINID, wp->handle); + set_vim_var_nr(VV_MOUSE_LNUM, lnum); + set_vim_var_nr(VV_MOUSE_COL, col + 1); + } + } + } +} + +/* + * "getcharmod()" function + */ +static void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = mod_mask; +} + +/* + * "getcharsearch()" function + */ +static void f_getcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_alloc_ret(rettv); + + dict_T *dict = rettv->vval.v_dict; + + tv_dict_add_str(dict, S_LEN("char"), last_csearch()); + tv_dict_add_nr(dict, S_LEN("forward"), last_csearch_forward()); + tv_dict_add_nr(dict, S_LEN("until"), last_csearch_until()); +} + +/* + * "getcmdline()" function + */ +static void f_getcmdline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = get_cmdline_str(); +} + +/* + * "getcmdpos()" function + */ +static void f_getcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = get_cmdline_pos() + 1; +} + +/* + * "getcmdtype()" function + */ +static void f_getcmdtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = xmallocz(1); + rettv->vval.v_string[0] = get_cmdline_type(); +} + +/* + * "getcmdwintype()" function + */ +static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + rettv->vval.v_string = xmallocz(1); + rettv->vval.v_string[0] = cmdwin_type; +} + +// "getcompletion()" function +static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char_u *pat; + expand_T xpc; + bool filtered = false; + int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH + | WILD_NO_BEEP; + + if (argvars[2].v_type != VAR_UNKNOWN) { + filtered = (bool)tv_get_number_chk(&argvars[2], NULL); + } + + if (p_wic) { + options |= WILD_ICASE; + } + + // For filtered results, 'wildignore' is used + if (!filtered) { + options |= WILD_KEEP_ALL; + } + + if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) { + EMSG(_(e_invarg)); + return; + } + + if (strcmp(tv_get_string(&argvars[1]), "cmdline") == 0) { + set_one_cmd_context(&xpc, tv_get_string(&argvars[0])); + xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + goto theend; + } + + ExpandInit(&xpc); + xpc.xp_pattern = (char_u *)tv_get_string(&argvars[0]); + xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + xpc.xp_context = cmdcomplete_str_to_type( + (char_u *)tv_get_string(&argvars[1])); + if (xpc.xp_context == EXPAND_NOTHING) { + EMSG2(_(e_invarg2), argvars[1].vval.v_string); + return; + } + + if (xpc.xp_context == EXPAND_MENUS) { + set_context_in_menu_cmd(&xpc, (char_u *)"menu", xpc.xp_pattern, false); + xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + } + + if (xpc.xp_context == EXPAND_CSCOPE) { + set_context_in_cscope_cmd(&xpc, (const char *)xpc.xp_pattern, CMD_cscope); + xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + } + + if (xpc.xp_context == EXPAND_SIGN) { + set_context_in_sign_cmd(&xpc, xpc.xp_pattern); + xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + } + +theend: + pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); + ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); + + for (int i = 0; i < xpc.xp_numfiles; i++) { + tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], + -1); + } + xfree(pat); + ExpandCleanup(&xpc); +} + +/// `getcwd([{win}[, {tab}]])` function +/// +/// Every scope not specified implies the currently selected scope object. +/// +/// @pre The arguments must be of type number. +/// @pre There may not be more than two arguments. +/// @pre An argument may not be -1 if preceding arguments are not all -1. +/// +/// @post The return value will be a string. +static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + // Possible scope of working directory to return. + CdScope scope = kCdScopeInvalid; + + // Numbers of the scope objects (window, tab) we want the working directory + // of. A `-1` means to skip this scope, a `0` means the current object. + int scope_number[] = { + [kCdScopeWindow] = 0, // Number of window to look at. + [kCdScopeTab ] = 0, // Number of tab to look at. + }; + + char_u *cwd = NULL; // Current working directory to print + char_u *from = NULL; // The original string to copy + + tabpage_T *tp = curtab; // The tabpage to look at. + win_T *win = curwin; // The window to look at. + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + // Pre-conditions and scope extraction together + for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) { + // If there is no argument there are no more scopes after it, break out. + if (argvars[i].v_type == VAR_UNKNOWN) { + break; + } + if (argvars[i].v_type != VAR_NUMBER) { + EMSG(_(e_invarg)); + return; + } + scope_number[i] = argvars[i].vval.v_number; + // It is an error for the scope number to be less than `-1`. + if (scope_number[i] < -1) { + EMSG(_(e_invarg)); + return; + } + // Use the narrowest scope the user requested + if (scope_number[i] >= 0 && scope == kCdScopeInvalid) { + // The scope is the current iteration step. + scope = i; + } else if (scope_number[i] < 0) { + scope = i + 1; + } + } + + // If the user didn't specify anything, default to window scope + if (scope == kCdScopeInvalid) { + scope = MIN_CD_SCOPE; + } + + // Find the tabpage by number + if (scope_number[kCdScopeTab] > 0) { + tp = find_tabpage(scope_number[kCdScopeTab]); + if (!tp) { + EMSG(_("E5000: Cannot find tab number.")); + return; + } + } + + // Find the window in `tp` by number, `NULL` if none. + if (scope_number[kCdScopeWindow] >= 0) { + if (scope_number[kCdScopeTab] < 0) { + EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); + return; + } + + if (scope_number[kCdScopeWindow] > 0) { + win = find_win_by_nr(&argvars[0], tp); + if (!win) { + EMSG(_("E5002: Cannot find window number.")); + return; + } + } + } + + cwd = xmalloc(MAXPATHL); + + switch (scope) { + case kCdScopeWindow: + assert(win); + from = win->w_localdir; + if (from) { + break; + } + FALLTHROUGH; + case kCdScopeTab: + assert(tp); + from = tp->tp_localdir; + if (from) { + break; + } + FALLTHROUGH; + case kCdScopeGlobal: + if (globaldir) { // `globaldir` is not always set. + from = globaldir; + } else if (os_dirname(cwd, MAXPATHL) == FAIL) { // Get the OS CWD. + from = (char_u *)""; // Return empty string on failure. + } + break; + case kCdScopeInvalid: // We should never get here + assert(false); + } + + if (from) { + xstrlcpy((char *)cwd, (char *)from, MAXPATHL); + } + + rettv->vval.v_string = vim_strsave(cwd); +#ifdef BACKSLASH_IN_FILENAME + slash_adjust(rettv->vval.v_string); +#endif + + xfree(cwd); +} + +/* + * "getfontname()" function + */ +static void f_getfontname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; +} + +/* + * "getfperm({fname})" function + */ +static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char *perm = NULL; + char_u flags[] = "rwx"; + + const char *filename = tv_get_string(&argvars[0]); + int32_t file_perm = os_getperm(filename); + if (file_perm >= 0) { + perm = xstrdup("---------"); + for (int i = 0; i < 9; i++) { + if (file_perm & (1 << (8 - i))) { + perm[i] = flags[i % 3]; + } + } + } + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)perm; +} + +/* + * "getfsize({fname})" function + */ +static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *fname = tv_get_string(&argvars[0]); + + rettv->v_type = VAR_NUMBER; + + FileInfo file_info; + if (os_fileinfo(fname, &file_info)) { + uint64_t filesize = os_fileinfo_size(&file_info); + if (os_isdir((const char_u *)fname)) { + rettv->vval.v_number = 0; + } else { + rettv->vval.v_number = (varnumber_T)filesize; + + // non-perfect check for overflow + if ((uint64_t)rettv->vval.v_number != filesize) { + rettv->vval.v_number = -2; + } + } + } else { + rettv->vval.v_number = -1; + } +} + +/* + * "getftime({fname})" function + */ +static void f_getftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *fname = tv_get_string(&argvars[0]); + + FileInfo file_info; + if (os_fileinfo(fname, &file_info)) { + rettv->vval.v_number = (varnumber_T)file_info.stat.st_mtim.tv_sec; + } else { + rettv->vval.v_number = -1; + } +} + +/* + * "getftype({fname})" function + */ +static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char_u *type = NULL; + char *t; + + const char *fname = tv_get_string(&argvars[0]); + + rettv->v_type = VAR_STRING; + FileInfo file_info; + if (os_fileinfo_link(fname, &file_info)) { + uint64_t mode = file_info.stat.st_mode; +#ifdef S_ISREG + if (S_ISREG(mode)) + t = "file"; + else if (S_ISDIR(mode)) + t = "dir"; +# ifdef S_ISLNK + else if (S_ISLNK(mode)) + t = "link"; +# endif +# ifdef S_ISBLK + else if (S_ISBLK(mode)) + t = "bdev"; +# endif +# ifdef S_ISCHR + else if (S_ISCHR(mode)) + t = "cdev"; +# endif +# ifdef S_ISFIFO + else if (S_ISFIFO(mode)) + t = "fifo"; +# endif +# ifdef S_ISSOCK + else if (S_ISSOCK(mode)) + t = "socket"; +# endif + else + t = "other"; +#else +# ifdef S_IFMT + switch (mode & S_IFMT) { + case S_IFREG: t = "file"; break; + case S_IFDIR: t = "dir"; break; +# ifdef S_IFLNK + case S_IFLNK: t = "link"; break; +# endif +# ifdef S_IFBLK + case S_IFBLK: t = "bdev"; break; +# endif +# ifdef S_IFCHR + case S_IFCHR: t = "cdev"; break; +# endif +# ifdef S_IFIFO + case S_IFIFO: t = "fifo"; break; +# endif +# ifdef S_IFSOCK + case S_IFSOCK: t = "socket"; break; +# endif + default: t = "other"; + } +# else + if (os_isdir((const char_u *)fname)) { + t = "dir"; + } else { + t = "file"; + } +# endif +#endif + type = vim_strsave((char_u *)t); + } + rettv->vval.v_string = type; +} + +// "getjumplist()" function +static void f_getjumplist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_list_alloc_ret(rettv, kListLenMayKnow); + win_T *const wp = find_tabwin(&argvars[0], &argvars[1]); + if (wp == NULL) { + return; + } + + cleanup_jumplist(wp, true); + + list_T *const l = tv_list_alloc(wp->w_jumplistlen); + tv_list_append_list(rettv->vval.v_list, l); + tv_list_append_number(rettv->vval.v_list, wp->w_jumplistidx); + + for (int i = 0; i < wp->w_jumplistlen; i++) { + if (wp->w_jumplist[i].fmark.mark.lnum == 0) { + continue; + } + dict_T *const d = tv_dict_alloc(); + tv_list_append_dict(l, d); + tv_dict_add_nr(d, S_LEN("lnum"), wp->w_jumplist[i].fmark.mark.lnum); + tv_dict_add_nr(d, S_LEN("col"), wp->w_jumplist[i].fmark.mark.col); + tv_dict_add_nr(d, S_LEN("coladd"), wp->w_jumplist[i].fmark.mark.coladd); + tv_dict_add_nr(d, S_LEN("bufnr"), wp->w_jumplist[i].fmark.fnum); + if (wp->w_jumplist[i].fname != NULL) { + tv_dict_add_str(d, S_LEN("filename"), (char *)wp->w_jumplist[i].fname); + } + } +} + +/* + * "getline(lnum, [end])" function + */ +static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + linenr_T end; + bool retlist; + + const linenr_T lnum = tv_get_lnum(argvars); + if (argvars[1].v_type == VAR_UNKNOWN) { + end = lnum; + retlist = false; + } else { + end = tv_get_lnum(&argvars[1]); + retlist = true; + } + + get_buffer_lines(curbuf, lnum, end, retlist, rettv); +} + +/// "getloclist()" function +static void f_getloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + get_qf_loc_list(false, wp, &argvars[1], rettv); +} + +/* + * "getmatches()" function + */ +static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + matchitem_T *cur = curwin->w_match_head; + int i; + + tv_list_alloc_ret(rettv, kListLenMayKnow); + while (cur != NULL) { + dict_T *dict = tv_dict_alloc(); + if (cur->match.regprog == NULL) { + // match added with matchaddpos() + for (i = 0; i < MAXPOSMATCH; i++) { + llpos_T *llpos; + char buf[30]; // use 30 to avoid compiler warning + + llpos = &cur->pos.pos[i]; + if (llpos->lnum == 0) { + break; + } + list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0)); + tv_list_append_number(l, (varnumber_T)llpos->lnum); + if (llpos->col > 0) { + tv_list_append_number(l, (varnumber_T)llpos->col); + tv_list_append_number(l, (varnumber_T)llpos->len); + } + int len = snprintf(buf, sizeof(buf), "pos%d", i + 1); + assert((size_t)len < sizeof(buf)); + tv_dict_add_list(dict, buf, (size_t)len, l); + } + } else { + tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cur->pattern); + } + tv_dict_add_str(dict, S_LEN("group"), + (const char *)syn_id2name(cur->hlg_id)); + tv_dict_add_nr(dict, S_LEN("priority"), (varnumber_T)cur->priority); + tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->id); + + if (cur->conceal_char) { + char buf[MB_MAXBYTES + 1]; + + buf[utf_char2bytes((int)cur->conceal_char, (char_u *)buf)] = NUL; + tv_dict_add_str(dict, S_LEN("conceal"), buf); + } + + tv_list_append_dict(rettv->vval.v_list, dict); + cur = cur->next; + } +} + +/* + * "getpid()" function + */ +static void f_getpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = os_get_pid(); +} + +static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos) +{ + pos_T *fp; + int fnum = -1; + + if (getcurpos) { + fp = &curwin->w_cursor; + } else { + fp = var2fpos(&argvars[0], true, &fnum); + } + + list_T *const l = tv_list_alloc_ret(rettv, 4 + (!!getcurpos)); + tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0); + tv_list_append_number(l, ((fp != NULL) + ? (varnumber_T)fp->lnum + : (varnumber_T)0)); + tv_list_append_number( + l, ((fp != NULL) + ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1) + : (varnumber_T)0)); + tv_list_append_number( + l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0); + if (getcurpos) { + const int save_set_curswant = curwin->w_set_curswant; + const colnr_T save_curswant = curwin->w_curswant; + const colnr_T save_virtcol = curwin->w_virtcol; + + update_curswant(); + tv_list_append_number(l, (curwin->w_curswant == MAXCOL + ? (varnumber_T)MAXCOL + : (varnumber_T)curwin->w_curswant + 1)); + + // Do not change "curswant", as it is unexpected that a get + // function has a side effect. + if (save_set_curswant) { + curwin->w_set_curswant = save_set_curswant; + curwin->w_curswant = save_curswant; + curwin->w_virtcol = save_virtcol; + curwin->w_valid &= ~VALID_VIRTCOL; + } + } +} + +/* + * "getcurpos(string)" function + */ +static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + getpos_both(argvars, rettv, true); +} + +/* + * "getpos(string)" function + */ +static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + getpos_both(argvars, rettv, false); +} + +/// "getqflist()" functions +static void f_getqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + get_qf_loc_list(true, NULL, &argvars[0], rettv); +} + +/// "getreg()" function +static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *strregname; + int arg2 = false; + bool return_list = false; + bool error = false; + + if (argvars[0].v_type != VAR_UNKNOWN) { + strregname = tv_get_string_chk(&argvars[0]); + error = strregname == NULL; + if (argvars[1].v_type != VAR_UNKNOWN) { + arg2 = tv_get_number_chk(&argvars[1], &error); + if (!error && argvars[2].v_type != VAR_UNKNOWN) { + return_list = tv_get_number_chk(&argvars[2], &error); + } + } + } else { + strregname = _(get_vim_var_str(VV_REG)); + } + + if (error) { + return; + } + + int regname = (uint8_t)(strregname == NULL ? '"' : *strregname); + if (regname == 0) { + regname = '"'; + } + + if (return_list) { + rettv->v_type = VAR_LIST; + rettv->vval.v_list = + get_reg_contents(regname, (arg2 ? kGRegExprSrc : 0) | kGRegList); + if (rettv->vval.v_list == NULL) { + rettv->vval.v_list = tv_list_alloc(0); + } + tv_list_ref(rettv->vval.v_list); + } else { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = get_reg_contents(regname, arg2 ? kGRegExprSrc : 0); + } +} + +/* + * "getregtype()" function + */ +static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *strregname; + + if (argvars[0].v_type != VAR_UNKNOWN) { + strregname = tv_get_string_chk(&argvars[0]); + if (strregname == NULL) { // Type error; errmsg already given. + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + return; + } + } else { + // Default to v:register. + strregname = _(get_vim_var_str(VV_REG)); + } + + int regname = (uint8_t)(strregname == NULL ? '"' : *strregname); + if (regname == 0) { + regname = '"'; + } + + colnr_T reglen = 0; + char buf[NUMBUFLEN + 2]; + MotionType reg_type = get_reg_type(regname, ®len); + format_reg_type(reg_type, reglen, buf, ARRAY_SIZE(buf)); + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)xstrdup(buf); +} + +/// "gettabinfo()" function +static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tabpage_T *tparg = NULL; + + tv_list_alloc_ret(rettv, (argvars[0].v_type == VAR_UNKNOWN + ? 1 + : kListLenMayKnow)); + + if (argvars[0].v_type != VAR_UNKNOWN) { + // Information about one tab page + tparg = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); + if (tparg == NULL) { + return; + } + } + + // Get information about a specific tab page or all tab pages + int tpnr = 0; + FOR_ALL_TABS(tp) { + tpnr++; + if (tparg != NULL && tp != tparg) { + continue; + } + dict_T *const d = get_tabpage_info(tp, tpnr); + tv_list_append_dict(rettv->vval.v_list, d); + if (tparg != NULL) { + return; + } + } +} + +/* + * "gettabvar()" function + */ +static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *oldcurwin; + tabpage_T *oldtabpage; + bool done = false; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + const char *const varname = tv_get_string_chk(&argvars[1]); + tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); + if (tp != NULL && varname != NULL) { + // Set tp to be our tabpage, temporarily. Also set the window to the + // first window in the tabpage, otherwise the window is not valid. + win_T *const window = tp == curtab || tp->tp_firstwin == NULL + ? firstwin + : tp->tp_firstwin; + if (switch_win(&oldcurwin, &oldtabpage, window, tp, true) == OK) { + // look up the variable + // Let gettabvar({nr}, "") return the "t:" dictionary. + const dictitem_T *const v = find_var_in_ht(&tp->tp_vars->dv_hashtab, 't', + varname, strlen(varname), + false); + if (v != NULL) { + tv_copy(&v->di_tv, rettv); + done = true; + } + } + + // restore previous notion of curwin + restore_win(oldcurwin, oldtabpage, true); + } + + if (!done && argvars[2].v_type != VAR_UNKNOWN) { + tv_copy(&argvars[2], rettv); + } +} + +/* + * "gettabwinvar()" function + */ +static void f_gettabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + getwinvar(argvars, rettv, 1); +} + +// "gettagstack()" function +static void f_gettagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp = curwin; // default is current window + + tv_dict_alloc_ret(rettv); + + if (argvars[0].v_type != VAR_UNKNOWN) { + wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + return; + } + } + + get_tagstack(wp, rettv->vval.v_dict); +} + +/// "getwininfo()" function +static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wparg = NULL; + + tv_list_alloc_ret(rettv, kListLenMayKnow); + + if (argvars[0].v_type != VAR_UNKNOWN) { + wparg = win_id2wp(argvars); + if (wparg == NULL) { + return; + } + } + + // Collect information about either all the windows across all the tab + // pages or one particular window. + int16_t tabnr = 0; + FOR_ALL_TABS(tp) { + tabnr++; + int16_t winnr = 0; + FOR_ALL_WINDOWS_IN_TAB(wp, tp) { + winnr++; + if (wparg != NULL && wp != wparg) { + continue; + } + dict_T *const d = get_win_info(wp, tabnr, winnr); + tv_list_append_dict(rettv->vval.v_list, d); + if (wparg != NULL) { + // found information about a specific window + return; + } + } + } +} + +// Dummy timer callback. Used by f_wait(). +static void dummy_timer_due_cb(TimeWatcher *tw, void *data) +{ +} + +// Dummy timer close callback. Used by f_wait(). +static void dummy_timer_close_cb(TimeWatcher *tw, void *data) +{ + xfree(tw); +} + +/// "wait(timeout, condition[, interval])" function +static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = -1; + + if (argvars[0].v_type != VAR_NUMBER) { + EMSG2(_(e_invargval), "1"); + return; + } + if ((argvars[2].v_type != VAR_NUMBER && argvars[2].v_type != VAR_UNKNOWN) + || (argvars[2].v_type == VAR_NUMBER && argvars[2].vval.v_number <= 0)) { + EMSG2(_(e_invargval), "3"); + return; + } + + int timeout = argvars[0].vval.v_number; + typval_T expr = argvars[1]; + int interval = argvars[2].v_type == VAR_NUMBER + ? argvars[2].vval.v_number + : 200; // Default. + TimeWatcher *tw = xmalloc(sizeof(TimeWatcher)); + + // Start dummy timer. + time_watcher_init(&main_loop, tw, NULL); + tw->events = main_loop.events; + tw->blockable = true; + time_watcher_start(tw, dummy_timer_due_cb, interval, interval); + + typval_T argv = TV_INITIAL_VALUE; + typval_T exprval = TV_INITIAL_VALUE; + bool error = false; + int save_called_emsg = called_emsg; + called_emsg = false; + + LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, timeout, + eval_expr_typval(&expr, &argv, 0, &exprval) != OK + || tv_get_number_chk(&exprval, &error) + || called_emsg || error || got_int); + + if (called_emsg || error) { + rettv->vval.v_number = -3; + } else if (got_int) { + got_int = false; + vgetc(); + rettv->vval.v_number = -2; + } else if (tv_get_number_chk(&exprval, &error)) { + rettv->vval.v_number = 0; + } + + called_emsg = save_called_emsg; + + // Stop dummy timer + time_watcher_stop(tw); + time_watcher_close(tw, dummy_timer_close_cb); +} + +// "win_screenpos()" function +static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_list_alloc_ret(rettv, 2); + const win_T *const wp = find_win_by_nr_or_id(&argvars[0]); + tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_winrow + 1); + tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1); +} + +// "getwinpos({timeout})" function +static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_list_alloc_ret(rettv, 2); + tv_list_append_number(rettv->vval.v_list, -1); + tv_list_append_number(rettv->vval.v_list, -1); +} + +/* + * "getwinposx()" function + */ +static void f_getwinposx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = -1; +} + +/* + * "getwinposy()" function + */ +static void f_getwinposy(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = -1; +} + +/// "getwinvar()" function +static void f_getwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + getwinvar(argvars, rettv, 0); +} + +/* + * "glob()" function + */ +static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int options = WILD_SILENT|WILD_USE_NL; + expand_T xpc; + bool error = false; + + /* When the optional second argument is non-zero, don't remove matches + * for 'wildignore' and don't put matches for 'suffixes' at the end. */ + rettv->v_type = VAR_STRING; + if (argvars[1].v_type != VAR_UNKNOWN) { + if (tv_get_number_chk(&argvars[1], &error)) { + options |= WILD_KEEP_ALL; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + if (tv_get_number_chk(&argvars[2], &error)) { + tv_list_set_ret(rettv, NULL); + } + if (argvars[3].v_type != VAR_UNKNOWN + && tv_get_number_chk(&argvars[3], &error)) { + options |= WILD_ALLLINKS; + } + } + } + if (!error) { + ExpandInit(&xpc); + xpc.xp_context = EXPAND_FILES; + if (p_wic) + options += WILD_ICASE; + if (rettv->v_type == VAR_STRING) { + rettv->vval.v_string = ExpandOne( + &xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, WILD_ALL); + } else { + ExpandOne(&xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, + WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); + for (int i = 0; i < xpc.xp_numfiles; i++) { + tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], + -1); + } + ExpandCleanup(&xpc); + } + } else + rettv->vval.v_string = NULL; +} + +/// "globpath()" function +static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int flags = 0; // Flags for globpath. + bool error = false; + + // Return a string, or a list if the optional third argument is non-zero. + rettv->v_type = VAR_STRING; + + if (argvars[2].v_type != VAR_UNKNOWN) { + // When the optional second argument is non-zero, don't remove matches + // for 'wildignore' and don't put matches for 'suffixes' at the end. + if (tv_get_number_chk(&argvars[2], &error)) { + flags |= WILD_KEEP_ALL; + } + + if (argvars[3].v_type != VAR_UNKNOWN) { + if (tv_get_number_chk(&argvars[3], &error)) { + tv_list_set_ret(rettv, NULL); + } + if (argvars[4].v_type != VAR_UNKNOWN + && tv_get_number_chk(&argvars[4], &error)) { + flags |= WILD_ALLLINKS; + } + } + } + + char buf1[NUMBUFLEN]; + const char *const file = tv_get_string_buf_chk(&argvars[1], buf1); + if (file != NULL && !error) { + garray_T ga; + ga_init(&ga, (int)sizeof(char_u *), 10); + globpath((char_u *)tv_get_string(&argvars[0]), (char_u *)file, &ga, flags); + + if (rettv->v_type == VAR_STRING) { + rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n"); + } else { + tv_list_alloc_ret(rettv, ga.ga_len); + for (int i = 0; i < ga.ga_len; i++) { + tv_list_append_string(rettv->vval.v_list, + ((const char **)(ga.ga_data))[i], -1); + } + } + + ga_clear_strings(&ga); + } else { + rettv->vval.v_string = NULL; + } +} + +// "glob2regpat()" function +static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = ((pat == NULL) + ? NULL + : file_pat_to_reg_pat((char_u *)pat, NULL, NULL, + false)); +} + +/// "has()" function +static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + static const char *const has_list[] = { +#if defined(BSD) && !defined(__APPLE__) + "bsd", +#endif +#ifdef UNIX + "unix", +#endif +#if defined(WIN32) + "win32", +#endif +#if defined(WIN64) || defined(_WIN64) + "win64", +#endif + "fname_case", +#ifdef HAVE_ACL + "acl", +#endif + "autochdir", + "arabic", + "autocmd", + "browsefilter", + "byte_offset", + "cindent", + "cmdline_compl", + "cmdline_hist", + "comments", + "conceal", + "cscope", + "cursorbind", + "cursorshape", +#ifdef DEBUG + "debug", +#endif + "dialog_con", + "diff", + "digraphs", + "eval", // always present, of course! + "ex_extra", + "extra_search", + "file_in_path", + "filterpipe", + "find_in_path", + "float", + "folding", +#if defined(UNIX) + "fork", +#endif + "gettext", +#if defined(HAVE_ICONV) + "iconv", +#endif + "insert_expand", + "jumplist", + "keymap", + "lambda", + "langmap", + "libcall", + "linebreak", + "lispindent", + "listcmds", + "localmap", +#ifdef __APPLE__ + "mac", + "macunix", + "osx", + "osxdarwin", +#endif + "menu", + "mksession", + "modify_fname", + "mouse", + "multi_byte", + "multi_lang", + "num64", + "packages", + "path_extra", + "persistent_undo", + "postscript", + "printer", + "profile", + "pythonx", + "reltime", + "quickfix", + "rightleft", + "scrollbind", + "showcmd", + "cmdline_info", + "shada", + "signs", + "smartindent", + "startuptime", + "statusline", + "spell", + "syntax", +#if !defined(UNIX) + "system", // TODO(SplinterOfChaos): This IS defined for UNIX! +#endif + "tablineat", + "tag_binary", + "termguicolors", + "termresponse", + "textobjects", + "timers", + "title", + "user-commands", // was accidentally included in 5.4 + "user_commands", + "vertsplit", + "virtualedit", + "visual", + "visualextra", + "vreplace", + "wildignore", + "wildmenu", + "windows", + "winaltkeys", + "writebackup", +#if defined(HAVE_WSL) + "wsl", +#endif + "nvim", + }; + + bool n = false; + const char *const name = tv_get_string(&argvars[0]); + for (size_t i = 0; i < ARRAY_SIZE(has_list); i++) { + if (STRICMP(name, has_list[i]) == 0) { + n = true; + break; + } + } + + if (!n) { + if (STRNICMP(name, "patch", 5) == 0) { + if (name[5] == '-' + && strlen(name) >= 11 + && ascii_isdigit(name[6]) + && ascii_isdigit(name[8]) + && ascii_isdigit(name[10])) { + int major = atoi(name + 6); + int minor = atoi(name + 8); + + // Expect "patch-9.9.01234". + n = (major < VIM_VERSION_MAJOR + || (major == VIM_VERSION_MAJOR + && (minor < VIM_VERSION_MINOR + || (minor == VIM_VERSION_MINOR + && has_vim_patch(atoi(name + 10)))))); + } else { + n = has_vim_patch(atoi(name + 5)); + } + } else if (STRNICMP(name, "nvim-", 5) == 0) { + // Expect "nvim-x.y.z" + n = has_nvim_version(name + 5); + } else if (STRICMP(name, "vim_starting") == 0) { + n = (starting != 0); + } else if (STRICMP(name, "ttyin") == 0) { + n = stdin_isatty; + } else if (STRICMP(name, "ttyout") == 0) { + n = stdout_isatty; + } else if (STRICMP(name, "multi_byte_encoding") == 0) { + n = has_mbyte != 0; + } else if (STRICMP(name, "syntax_items") == 0) { + n = syntax_present(curwin); +#ifdef UNIX + } else if (STRICMP(name, "unnamedplus") == 0) { + n = eval_has_provider("clipboard"); +#endif + } + } + + if (!n && eval_has_provider(name)) { + n = true; + } + + rettv->vval.v_number = n; +} + +/* + * "has_key()" function + */ +static void f_has_key(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; + } + if (argvars[0].vval.v_dict == NULL) + return; + + rettv->vval.v_number = tv_dict_find(argvars[0].vval.v_dict, + tv_get_string(&argvars[1]), + -1) != NULL; +} + +/// `haslocaldir([{win}[, {tab}]])` function +/// +/// Returns `1` if the scope object has a local directory, `0` otherwise. If a +/// scope object is not specified the current one is implied. This function +/// share a lot of code with `f_getcwd`. +/// +/// @pre The arguments must be of type number. +/// @pre There may not be more than two arguments. +/// @pre An argument may not be -1 if preceding arguments are not all -1. +/// +/// @post The return value will be either the number `1` or `0`. +static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + // Possible scope of working directory to return. + CdScope scope = kCdScopeInvalid; + + // Numbers of the scope objects (window, tab) we want the working directory + // of. A `-1` means to skip this scope, a `0` means the current object. + int scope_number[] = { + [kCdScopeWindow] = 0, // Number of window to look at. + [kCdScopeTab ] = 0, // Number of tab to look at. + }; + + tabpage_T *tp = curtab; // The tabpage to look at. + win_T *win = curwin; // The window to look at. + + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + // Pre-conditions and scope extraction together + for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) { + if (argvars[i].v_type == VAR_UNKNOWN) { + break; + } + if (argvars[i].v_type != VAR_NUMBER) { + EMSG(_(e_invarg)); + return; + } + scope_number[i] = argvars[i].vval.v_number; + if (scope_number[i] < -1) { + EMSG(_(e_invarg)); + return; + } + // Use the narrowest scope the user requested + if (scope_number[i] >= 0 && scope == kCdScopeInvalid) { + // The scope is the current iteration step. + scope = i; + } else if (scope_number[i] < 0) { + scope = i + 1; + } + } + + // If the user didn't specify anything, default to window scope + if (scope == kCdScopeInvalid) { + scope = MIN_CD_SCOPE; + } + + // Find the tabpage by number + if (scope_number[kCdScopeTab] > 0) { + tp = find_tabpage(scope_number[kCdScopeTab]); + if (!tp) { + EMSG(_("E5000: Cannot find tab number.")); + return; + } + } + + // Find the window in `tp` by number, `NULL` if none. + if (scope_number[kCdScopeWindow] >= 0) { + if (scope_number[kCdScopeTab] < 0) { + EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); + return; + } + + if (scope_number[kCdScopeWindow] > 0) { + win = find_win_by_nr(&argvars[0], tp); + if (!win) { + EMSG(_("E5002: Cannot find window number.")); + return; + } + } + } + + switch (scope) { + case kCdScopeWindow: + assert(win); + rettv->vval.v_number = win->w_localdir ? 1 : 0; + break; + case kCdScopeTab: + assert(tp); + rettv->vval.v_number = tp->tp_localdir ? 1 : 0; + break; + case kCdScopeGlobal: + // The global scope never has a local directory + break; + case kCdScopeInvalid: + // We should never get here + assert(false); + } +} + +/* + * "hasmapto()" function + */ +static void f_hasmapto(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *mode; + const char *const name = tv_get_string(&argvars[0]); + bool abbr = false; + char buf[NUMBUFLEN]; + if (argvars[1].v_type == VAR_UNKNOWN) { + mode = "nvo"; + } else { + mode = tv_get_string_buf(&argvars[1], buf); + if (argvars[2].v_type != VAR_UNKNOWN) { + abbr = tv_get_number(&argvars[2]); + } + } + + if (map_to_exists(name, mode, abbr)) { + rettv->vval.v_number = true; + } else { + rettv->vval.v_number = false; + } +} + +/* + * "histadd()" function + */ +static void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + HistoryType histype; + + rettv->vval.v_number = false; + if (check_secure()) { + return; + } + const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error + histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID; + if (histype != HIST_INVALID) { + char buf[NUMBUFLEN]; + str = tv_get_string_buf(&argvars[1], buf); + if (*str != NUL) { + init_history(); + add_to_history(histype, (char_u *)str, false, NUL); + rettv->vval.v_number = true; + return; + } + } +} + +/* + * "histdel()" function + */ +static void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int n; + const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error + if (str == NULL) { + n = 0; + } else if (argvars[1].v_type == VAR_UNKNOWN) { + // only one argument: clear entire history + n = clr_history(get_histtype(str, strlen(str), false)); + } else if (argvars[1].v_type == VAR_NUMBER) { + // index given: remove that entry + n = del_history_idx(get_histtype(str, strlen(str), false), + (int)tv_get_number(&argvars[1])); + } else { + // string given: remove all matching entries + char buf[NUMBUFLEN]; + n = del_history_entry(get_histtype(str, strlen(str), false), + (char_u *)tv_get_string_buf(&argvars[1], buf)); + } + rettv->vval.v_number = n; +} + +/* + * "histget()" function + */ +static void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + HistoryType type; + int idx; + + const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error + if (str == NULL) { + rettv->vval.v_string = NULL; + } else { + type = get_histtype(str, strlen(str), false); + if (argvars[1].v_type == VAR_UNKNOWN) { + idx = get_history_idx(type); + } else { + idx = (int)tv_get_number_chk(&argvars[1], NULL); + } + // -1 on type error + rettv->vval.v_string = vim_strsave(get_history_entry(type, idx)); + } + rettv->v_type = VAR_STRING; +} + +/* + * "histnr()" function + */ +static void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const history = tv_get_string_chk(&argvars[0]); + HistoryType i = history == NULL + ? HIST_INVALID + : get_histtype(history, strlen(history), false); + if (i != HIST_INVALID) { + i = get_history_idx(i); + } + rettv->vval.v_number = i; +} + +/* + * "highlightID(name)" function + */ +static void f_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = syn_name2id( + (const char_u *)tv_get_string(&argvars[0])); +} + +/* + * "highlight_exists()" function + */ +static void f_hlexists(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = highlight_exists( + (const char_u *)tv_get_string(&argvars[0])); +} + +/* + * "hostname()" function + */ +static void f_hostname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char hostname[256]; + + os_get_hostname(hostname, 256); + rettv->v_type = VAR_STRING; + rettv->vval.v_string = vim_strsave((char_u *)hostname); +} + +/* + * iconv() function + */ +static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + vimconv_T vimconv; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + const char *const str = tv_get_string(&argvars[0]); + char buf1[NUMBUFLEN]; + char_u *const from = enc_canonize(enc_skip( + (char_u *)tv_get_string_buf(&argvars[1], buf1))); + char buf2[NUMBUFLEN]; + char_u *const to = enc_canonize(enc_skip( + (char_u *)tv_get_string_buf(&argvars[2], buf2))); + vimconv.vc_type = CONV_NONE; + convert_setup(&vimconv, from, to); + + // If the encodings are equal, no conversion needed. + if (vimconv.vc_type == CONV_NONE) { + rettv->vval.v_string = (char_u *)xstrdup(str); + } else { + rettv->vval.v_string = string_convert(&vimconv, (char_u *)str, NULL); + } + + convert_setup(&vimconv, NULL, NULL); + xfree(from); + xfree(to); +} + +/* + * "indent()" function + */ +static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const linenr_T lnum = tv_get_lnum(argvars); + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { + rettv->vval.v_number = get_indent_lnum(lnum); + } else { + rettv->vval.v_number = -1; + } +} + +/* + * "index()" function + */ +static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + long idx = 0; + bool ic = false; + + rettv->vval.v_number = -1; + if (argvars[0].v_type != VAR_LIST) { + EMSG(_(e_listreq)); + return; + } + list_T *const l = argvars[0].vval.v_list; + if (l != NULL) { + listitem_T *item = tv_list_first(l); + if (argvars[2].v_type != VAR_UNKNOWN) { + bool error = false; + + // Start at specified item. + idx = tv_list_uidx(l, tv_get_number_chk(&argvars[2], &error)); + if (error || idx == -1) { + item = NULL; + } else { + item = tv_list_find(l, idx); + assert(item != NULL); + } + if (argvars[3].v_type != VAR_UNKNOWN) { + ic = !!tv_get_number_chk(&argvars[3], &error); + if (error) { + item = NULL; + } + } + } + + for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) { + if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic, false)) { + rettv->vval.v_number = idx; + break; + } + } + } +} + +static bool inputsecret_flag = false; + +/* + * "input()" function + * Also handles inputsecret() when inputsecret is set. + */ +static void f_input(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + get_user_input(argvars, rettv, FALSE, inputsecret_flag); +} + +/* + * "inputdialog()" function + */ +static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + get_user_input(argvars, rettv, TRUE, inputsecret_flag); +} + +/* + * "inputlist()" function + */ +static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int selected; + int mouse_used; + + if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "inputlist()"); + return; + } + + msg_start(); + msg_row = Rows - 1; // for when 'cmdheight' > 1 + lines_left = Rows; // avoid more prompt + msg_scroll = true; + msg_clr_eos(); + + TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { + msg_puts(tv_get_string(TV_LIST_ITEM_TV(li))); + msg_putchar('\n'); + }); + + // Ask for choice. + selected = prompt_for_number(&mouse_used); + if (mouse_used) { + selected -= lines_left; + } + + rettv->vval.v_number = selected; +} + + +static garray_T ga_userinput = { 0, 0, sizeof(tasave_T), 4, NULL }; + +/// "inputrestore()" function +static void f_inputrestore(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (!GA_EMPTY(&ga_userinput)) { + ga_userinput.ga_len--; + restore_typeahead((tasave_T *)(ga_userinput.ga_data) + + ga_userinput.ga_len); + // default return is zero == OK + } else if (p_verbose > 1) { + verb_msg(_("called inputrestore() more often than inputsave()")); + rettv->vval.v_number = 1; // Failed + } +} + +/// "inputsave()" function +static void f_inputsave(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + // Add an entry to the stack of typeahead storage. + tasave_T *p = GA_APPEND_VIA_PTR(tasave_T, &ga_userinput); + save_typeahead(p); +} + +/// "inputsecret()" function +static void f_inputsecret(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + cmdline_star++; + inputsecret_flag = true; + f_input(argvars, rettv, NULL); + cmdline_star--; + inputsecret_flag = false; +} + +/* + * "insert()" function + */ +static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + list_T *l; + bool error = false; + + if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "insert()"); + } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + N_("insert() argument"), TV_TRANSLATE)) { + long before = 0; + if (argvars[2].v_type != VAR_UNKNOWN) { + before = tv_get_number_chk(&argvars[2], &error); + } + if (error) { + // type error; errmsg already given + return; + } + + listitem_T *item = NULL; + if (before != tv_list_len(l)) { + item = tv_list_find(l, before); + if (item == NULL) { + EMSGN(_(e_listidx), before); + l = NULL; + } + } + if (l != NULL) { + tv_list_insert_tv(l, &argvars[1], item); + tv_copy(&argvars[0], rettv); + } + } +} + +/* + * "invert(expr)" function + */ +static void f_invert(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL); +} + +/* + * "isdirectory()" function + */ +static void f_isdirectory(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = os_isdir((const char_u *)tv_get_string(&argvars[0])); +} + +/* + * "islocked()" function + */ +static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + lval_T lv; + dictitem_T *di; + + rettv->vval.v_number = -1; + const char_u *const end = get_lval((char_u *)tv_get_string(&argvars[0]), + NULL, + &lv, false, false, + GLV_NO_AUTOLOAD|GLV_READ_ONLY, + FNE_CHECK_START); + if (end != NULL && lv.ll_name != NULL) { + if (*end != NUL) { + EMSG(_(e_trailing)); + } else { + if (lv.ll_tv == NULL) { + di = find_var((const char *)lv.ll_name, lv.ll_name_len, NULL, true); + if (di != NULL) { + // Consider a variable locked when: + // 1. the variable itself is locked + // 2. the value of the variable is locked. + // 3. the List or Dict value is locked. + rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK) + || tv_islocked(&di->di_tv)); + } + } else if (lv.ll_range) { + EMSG(_("E786: Range not allowed")); + } else if (lv.ll_newkey != NULL) { + EMSG2(_(e_dictkey), lv.ll_newkey); + } else if (lv.ll_list != NULL) { + // List item. + rettv->vval.v_number = tv_islocked(TV_LIST_ITEM_TV(lv.ll_li)); + } else { + // Dictionary item. + rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv); + } + } + } + + clear_lval(&lv); +} + +// "isinf()" function +static void f_isinf(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type == VAR_FLOAT + && xisinf(argvars[0].vval.v_float)) { + rettv->vval.v_number = argvars[0].vval.v_float > 0.0 ? 1 : -1; + } +} + +// "isnan()" function +static void f_isnan(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = argvars[0].v_type == VAR_FLOAT + && xisnan(argvars[0].vval.v_float); +} + +/// "id()" function +static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr) + FUNC_ATTR_NONNULL_ALL +{ + const int len = vim_vsnprintf_typval(NULL, 0, "%p", dummy_ap, argvars); + rettv->v_type = VAR_STRING; + rettv->vval.v_string = xmalloc(len + 1); + vim_vsnprintf_typval((char *)rettv->vval.v_string, len + 1, "%p", + dummy_ap, argvars); +} + +/* + * "items(dict)" function + */ +static void f_items(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + dict_list(argvars, rettv, 2); +} + +// "jobpid(id)" function +static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_NUMBER) { + EMSG(_(e_invarg)); + return; + } + + Channel *data = find_job(argvars[0].vval.v_number, true); + if (!data) { + return; + } + + Process *proc = (Process *)&data->stream.proc; + rettv->vval.v_number = proc->pid; +} + +// "jobresize(job, width, height)" function +static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type != VAR_NUMBER + || argvars[2].v_type != VAR_NUMBER) { + // job id, width, height + EMSG(_(e_invarg)); + return; + } + + + Channel *data = find_job(argvars[0].vval.v_number, true); + if (!data) { + return; + } + + if (data->stream.proc.type != kProcessTypePty) { + EMSG(_(e_channotpty)); + return; + } + + pty_process_resize(&data->stream.pty, argvars[1].vval.v_number, + argvars[2].vval.v_number); + rettv->vval.v_number = 1; +} + +// "jobstart()" function +static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + if (check_restricted() || check_secure()) { + return; + } + + bool executable = true; + char **argv = tv_to_argv(&argvars[0], NULL, &executable); + char **env = NULL; + if (!argv) { + rettv->vval.v_number = executable ? 0 : -1; + return; // Did error message in tv_to_argv. + } + + if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) { + // Wrong argument types + EMSG2(_(e_invarg2), "expected dictionary"); + shell_free_argv(argv); + return; + } + + + dict_T *job_opts = NULL; + bool detach = false; + bool rpc = false; + bool pty = false; + bool clear_env = false; + CallbackReader on_stdout = CALLBACK_READER_INIT, + on_stderr = CALLBACK_READER_INIT; + Callback on_exit = CALLBACK_NONE; + char *cwd = NULL; + if (argvars[1].v_type == VAR_DICT) { + job_opts = argvars[1].vval.v_dict; + + detach = tv_dict_get_number(job_opts, "detach") != 0; + rpc = tv_dict_get_number(job_opts, "rpc") != 0; + pty = tv_dict_get_number(job_opts, "pty") != 0; + clear_env = tv_dict_get_number(job_opts, "clear_env") != 0; + if (pty && rpc) { + EMSG2(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set"); + shell_free_argv(argv); + return; + } + + char *new_cwd = tv_dict_get_string(job_opts, "cwd", false); + if (new_cwd && strlen(new_cwd) > 0) { + cwd = new_cwd; + // The new cwd must be a directory. + if (!os_isdir_executable((const char *)cwd)) { + EMSG2(_(e_invarg2), "expected valid directory"); + shell_free_argv(argv); + return; + } + } + dictitem_T *job_env = tv_dict_find(job_opts, S_LEN("env")); + if (job_env) { + if (job_env->di_tv.v_type != VAR_DICT) { + EMSG2(_(e_invarg2), "env"); + shell_free_argv(argv); + return; + } + + size_t custom_env_size = (size_t)tv_dict_len(job_env->di_tv.vval.v_dict); + size_t i = 0; + size_t env_size = 0; + + if (clear_env) { + // + 1 for last null entry + env = xmalloc((custom_env_size + 1) * sizeof(*env)); + env_size = 0; + } else { + env_size = os_get_fullenv_size(); + + env = xmalloc((custom_env_size + env_size + 1) * sizeof(*env)); + + os_copy_fullenv(env, env_size); + i = env_size; + } + assert(env); // env must be allocated at this point + + TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, { + const char *str = tv_get_string(&var->di_tv); + assert(str); + size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1; + env[i] = xmalloc(len); + snprintf(env[i], len, "%s=%s", (char *)var->di_key, str); + i++; + }); + + // must be null terminated + env[env_size + custom_env_size] = NULL; + } + + + if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) { + shell_free_argv(argv); + return; + } + } + + uint16_t width = 0, height = 0; + char *term_name = NULL; + + if (pty) { + width = (uint16_t)tv_dict_get_number(job_opts, "width"); + height = (uint16_t)tv_dict_get_number(job_opts, "height"); + term_name = tv_dict_get_string(job_opts, "TERM", true); + } + + Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty, + rpc, detach, cwd, width, height, + term_name, env, &rettv->vval.v_number); + if (chan) { + channel_create_event(chan, NULL); + } +} + +// "jobstop()" function +static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_NUMBER) { + // Only argument is the job id + EMSG(_(e_invarg)); + return; + } + + Channel *data = find_job(argvars[0].vval.v_number, false); + if (!data) { + return; + } + + const char *error = NULL; + if (data->is_rpc) { + // Ignore return code, but show error later. + (void)channel_close(data->id, kChannelPartRpc, &error); + } + process_stop((Process *)&data->stream.proc); + rettv->vval.v_number = 1; + if (error) { + EMSG(error); + } +} + +// "jobwait(ids[, timeout])" function +static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + if (check_restricted() || check_secure()) { + return; + } + if (argvars[0].v_type != VAR_LIST || (argvars[1].v_type != VAR_NUMBER + && argvars[1].v_type != VAR_UNKNOWN)) { + EMSG(_(e_invarg)); + return; + } + + ui_busy_start(); + list_T *args = argvars[0].vval.v_list; + Channel **jobs = xcalloc(tv_list_len(args), sizeof(*jobs)); + MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop); + + // Validate, prepare jobs for waiting. + int i = 0; + TV_LIST_ITER_CONST(args, arg, { + Channel *chan = NULL; + if (TV_LIST_ITEM_TV(arg)->v_type != VAR_NUMBER + || !(chan = find_job(TV_LIST_ITEM_TV(arg)->vval.v_number, false))) { + jobs[i] = NULL; // Invalid job. + } else { + jobs[i] = chan; + channel_incref(chan); + if (chan->stream.proc.status < 0) { + // Process any pending events on the job's queue before temporarily + // replacing it. + multiqueue_process_events(chan->events); + multiqueue_replace_parent(chan->events, waiting_jobs); + } + } + i++; + }); + + int remaining = -1; + uint64_t before = 0; + if (argvars[1].v_type == VAR_NUMBER && argvars[1].vval.v_number >= 0) { + remaining = argvars[1].vval.v_number; + before = os_hrtime(); + } + + for (i = 0; i < tv_list_len(args); i++) { + if (remaining == 0) { + break; // Timeout. + } + if (jobs[i] == NULL) { + continue; // Invalid job, will assign status=-3 below. + } + int status = process_wait(&jobs[i]->stream.proc, remaining, + waiting_jobs); + if (status < 0) { + break; // Interrupted (CTRL-C) or timeout, skip remaining jobs. + } + if (remaining > 0) { + uint64_t now = os_hrtime(); + remaining = MIN(0, remaining - (int)((now - before) / 1000000)); + before = now; + } + } + + list_T *const rv = tv_list_alloc(tv_list_len(args)); + + // For each job: + // * Restore its parent queue if the job is still alive. + // * Append its status to the output list, or: + // -3 for "invalid job id" + // -2 for "interrupted" (user hit CTRL-C) + // -1 for jobs that were skipped or timed out + for (i = 0; i < tv_list_len(args); i++) { + if (jobs[i] == NULL) { + tv_list_append_number(rv, -3); + continue; + } + multiqueue_process_events(jobs[i]->events); + multiqueue_replace_parent(jobs[i]->events, main_loop.events); + + tv_list_append_number(rv, jobs[i]->stream.proc.status); + channel_decref(jobs[i]); + } + + multiqueue_free(waiting_jobs); + xfree(jobs); + ui_busy_stop(); + tv_list_ref(rv); + rettv->v_type = VAR_LIST; + rettv->vval.v_list = rv; +} + +/* + * "join()" function + */ +static void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_LIST) { + EMSG(_(e_listreq)); + return; + } + const char *const sep = (argvars[1].v_type == VAR_UNKNOWN + ? " " + : tv_get_string_chk(&argvars[1])); + + rettv->v_type = VAR_STRING; + + if (sep != NULL) { + garray_T ga; + ga_init(&ga, (int)sizeof(char), 80); + tv_list_join(&ga, argvars[0].vval.v_list, sep); + ga_append(&ga, NUL); + rettv->vval.v_string = (char_u *)ga.ga_data; + } else { + rettv->vval.v_string = NULL; + } +} + +/// json_decode() function +static void f_json_decode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char numbuf[NUMBUFLEN]; + const char *s = NULL; + char *tofree = NULL; + size_t len; + if (argvars[0].v_type == VAR_LIST) { + if (!encode_vim_list_to_buf(argvars[0].vval.v_list, &len, &tofree)) { + EMSG(_("E474: Failed to convert list to string")); + return; + } + s = tofree; + if (s == NULL) { + assert(len == 0); + s = ""; + } + } else { + s = tv_get_string_buf_chk(&argvars[0], numbuf); + if (s) { + len = strlen(s); + } else { + return; + } + } + if (json_decode_string(s, len, rettv) == FAIL) { + emsgf(_("E474: Failed to parse %.*s"), (int)len, s); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + } + assert(rettv->v_type != VAR_UNKNOWN); + xfree(tofree); +} + +/// json_encode() function +static void f_json_encode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)encode_tv2json(&argvars[0], NULL); +} + +/* + * "keys()" function + */ +static void f_keys(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + dict_list(argvars, rettv, 0); +} + +/* + * "last_buffer_nr()" function. + */ +static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int n = 0; + + FOR_ALL_BUFFERS(buf) { + if (n < buf->b_fnum) { + n = buf->b_fnum; + } + } + + rettv->vval.v_number = n; +} + +/* + * "len()" function + */ +static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + switch (argvars[0].v_type) { + case VAR_STRING: + case VAR_NUMBER: { + rettv->vval.v_number = (varnumber_T)strlen( + tv_get_string(&argvars[0])); + break; + } + case VAR_LIST: { + rettv->vval.v_number = tv_list_len(argvars[0].vval.v_list); + break; + } + case VAR_DICT: { + rettv->vval.v_number = tv_dict_len(argvars[0].vval.v_dict); + break; + } + case VAR_UNKNOWN: + case VAR_SPECIAL: + case VAR_FLOAT: + case VAR_PARTIAL: + case VAR_FUNC: { + EMSG(_("E701: Invalid type for len()")); + break; + } + } +} + +static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type) +{ + rettv->v_type = out_type; + if (out_type != VAR_NUMBER) { + rettv->vval.v_string = NULL; + } + + if (check_restricted() || check_secure()) { + return; + } + + // The first two args (libname and funcname) must be strings + if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) { + return; + } + + const char *libname = (char *)argvars[0].vval.v_string; + const char *funcname = (char *)argvars[1].vval.v_string; + + VarType in_type = argvars[2].v_type; + + // input variables + char *str_in = (in_type == VAR_STRING) + ? (char *)argvars[2].vval.v_string : NULL; + int64_t int_in = argvars[2].vval.v_number; + + // output variables + char **str_out = (out_type == VAR_STRING) + ? (char **)&rettv->vval.v_string : NULL; + int int_out = 0; + + bool success = os_libcall(libname, funcname, + str_in, int_in, + str_out, &int_out); + + if (!success) { + EMSG2(_(e_libcall), funcname); + return; + } + + if (out_type == VAR_NUMBER) { + rettv->vval.v_number = (varnumber_T)int_out; + } +} + +/* + * "libcall()" function + */ +static void f_libcall(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + libcall_common(argvars, rettv, VAR_STRING); +} + +/* + * "libcallnr()" function + */ +static void f_libcallnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + libcall_common(argvars, rettv, VAR_NUMBER); +} + +/* + * "line(string)" function + */ +static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + linenr_T lnum = 0; + pos_T *fp; + int fnum; + + fp = var2fpos(&argvars[0], TRUE, &fnum); + if (fp != NULL) + lnum = fp->lnum; + rettv->vval.v_number = lnum; +} + +/* + * "line2byte(lnum)" function + */ +static void f_line2byte(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const linenr_T lnum = tv_get_lnum(argvars); + if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) { + rettv->vval.v_number = -1; + } else { + rettv->vval.v_number = ml_find_line_or_offset(curbuf, lnum, NULL, false); + } + if (rettv->vval.v_number >= 0) { + rettv->vval.v_number++; + } +} + +/* + * "lispindent(lnum)" function + */ +static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const pos_T pos = curwin->w_cursor; + const linenr_T lnum = tv_get_lnum(argvars); + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { + curwin->w_cursor.lnum = lnum; + rettv->vval.v_number = get_lisp_indent(); + curwin->w_cursor = pos; + } else { + rettv->vval.v_number = -1; + } +} + +// "list2str()" function +static void f_list2str(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + garray_T ga; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (argvars[0].v_type != VAR_LIST) { + EMSG(_(e_invarg)); + return; + } + + list_T *const l = argvars[0].vval.v_list; + if (l == NULL) { + return; // empty list results in empty string + } + + ga_init(&ga, 1, 80); + char_u buf[MB_MAXBYTES + 1]; + + TV_LIST_ITER_CONST(l, li, { + buf[utf_char2bytes(tv_get_number(TV_LIST_ITEM_TV(li)), buf)] = NUL; + ga_concat(&ga, buf); + }); + ga_append(&ga, NUL); + + rettv->vval.v_string = ga.ga_data; +} + +/* + * "localtime()" function + */ +static void f_localtime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = (varnumber_T)time(NULL); +} + + +static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) +{ + char_u *keys_buf = NULL; + char_u *rhs; + int mode; + int abbr = FALSE; + int get_dict = FALSE; + mapblock_T *mp; + int buffer_local; + + // Return empty string for failure. + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + char_u *keys = (char_u *)tv_get_string(&argvars[0]); + if (*keys == NUL) { + return; + } + + char buf[NUMBUFLEN]; + const char *which; + if (argvars[1].v_type != VAR_UNKNOWN) { + which = tv_get_string_buf_chk(&argvars[1], buf); + if (argvars[2].v_type != VAR_UNKNOWN) { + abbr = tv_get_number(&argvars[2]); + if (argvars[3].v_type != VAR_UNKNOWN) { + get_dict = tv_get_number(&argvars[3]); + } + } + } else { + which = ""; + } + if (which == NULL) { + return; + } + + mode = get_map_mode((char_u **)&which, 0); + + keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, true, + CPO_TO_CPO_FLAGS); + rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local); + xfree(keys_buf); + + if (!get_dict) { + // Return a string. + if (rhs != NULL) { + if (*rhs == NUL) { + rettv->vval.v_string = vim_strsave((char_u *)"<Nop>"); + } else { + rettv->vval.v_string = (char_u *)str2special_save( + (char *)rhs, false, false); + } + } + + } else { + tv_dict_alloc_ret(rettv); + if (rhs != NULL) { + // Return a dictionary. + mapblock_fill_dict(rettv->vval.v_dict, mp, buffer_local, true); + } + } +} + +/// luaeval() function implementation +static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) + FUNC_ATTR_NONNULL_ALL +{ + const char *const str = (const char *)tv_get_string_chk(&argvars[0]); + if (str == NULL) { + return; + } + + executor_eval_lua(cstr_as_string((char *)str), &argvars[1], rettv); +} + +/* + * "map()" function + */ +static void f_map(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + filter_map(argvars, rettv, TRUE); +} + +/* + * "maparg()" function + */ +static void f_maparg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + get_maparg(argvars, rettv, TRUE); +} + +/* + * "mapcheck()" function + */ +static void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + get_maparg(argvars, rettv, FALSE); +} + + +static void find_some_match(typval_T *const argvars, typval_T *const rettv, + const SomeMatchType type) +{ + char_u *str = NULL; + long len = 0; + char_u *expr = NULL; + regmatch_T regmatch; + char_u *save_cpo; + long start = 0; + long nth = 1; + colnr_T startcol = 0; + bool match = false; + list_T *l = NULL; + listitem_T *li = NULL; + long idx = 0; + char_u *tofree = NULL; + + // Make 'cpoptions' empty, the 'l' flag should not be used here. + save_cpo = p_cpo; + p_cpo = (char_u *)""; + + rettv->vval.v_number = -1; + switch (type) { + // matchlist(): return empty list when there are no matches. + case kSomeMatchList: { + tv_list_alloc_ret(rettv, kListLenMayKnow); + break; + } + // matchstrpos(): return ["", -1, -1, -1] + case kSomeMatchStrPos: { + tv_list_alloc_ret(rettv, 4); + tv_list_append_string(rettv->vval.v_list, "", 0); + tv_list_append_number(rettv->vval.v_list, -1); + tv_list_append_number(rettv->vval.v_list, -1); + tv_list_append_number(rettv->vval.v_list, -1); + break; + } + case kSomeMatchStr: { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + break; + } + case kSomeMatch: + case kSomeMatchEnd: { + // Do nothing: zero is default. + break; + } + } + + if (argvars[0].v_type == VAR_LIST) { + if ((l = argvars[0].vval.v_list) == NULL) { + goto theend; + } + li = tv_list_first(l); + } else { + expr = str = (char_u *)tv_get_string(&argvars[0]); + len = (long)STRLEN(str); + } + + char patbuf[NUMBUFLEN]; + const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf); + if (pat == NULL) { + goto theend; + } + + if (argvars[2].v_type != VAR_UNKNOWN) { + bool error = false; + + start = tv_get_number_chk(&argvars[2], &error); + if (error) { + goto theend; + } + if (l != NULL) { + idx = tv_list_uidx(l, start); + if (idx == -1) { + goto theend; + } + li = tv_list_find(l, idx); + } else { + if (start < 0) + start = 0; + if (start > len) + goto theend; + // When "count" argument is there ignore matches before "start", + // otherwise skip part of the string. Differs when pattern is "^" + // or "\<". + if (argvars[3].v_type != VAR_UNKNOWN) { + startcol = start; + } else { + str += start; + len -= start; + } + } + + if (argvars[3].v_type != VAR_UNKNOWN) { + nth = tv_get_number_chk(&argvars[3], &error); + } + if (error) { + goto theend; + } + } + + regmatch.regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING); + if (regmatch.regprog != NULL) { + regmatch.rm_ic = p_ic; + + for (;; ) { + if (l != NULL) { + if (li == NULL) { + match = false; + break; + } + xfree(tofree); + tofree = expr = str = (char_u *)encode_tv2echo(TV_LIST_ITEM_TV(li), + NULL); + if (str == NULL) { + break; + } + } + + match = vim_regexec_nl(®match, str, (colnr_T)startcol); + + if (match && --nth <= 0) + break; + if (l == NULL && !match) + break; + + // Advance to just after the match. + if (l != NULL) { + li = TV_LIST_ITEM_NEXT(l, li); + idx++; + } else { + startcol = (colnr_T)(regmatch.startp[0] + + (*mb_ptr2len)(regmatch.startp[0]) - str); + if (startcol > (colnr_T)len || str + startcol <= regmatch.startp[0]) { + match = false; + break; + } + } + } + + if (match) { + switch (type) { + case kSomeMatchStrPos: { + list_T *const ret_l = rettv->vval.v_list; + listitem_T *li1 = tv_list_first(ret_l); + listitem_T *li2 = TV_LIST_ITEM_NEXT(ret_l, li1); + listitem_T *li3 = TV_LIST_ITEM_NEXT(ret_l, li2); + listitem_T *li4 = TV_LIST_ITEM_NEXT(ret_l, li3); + xfree(TV_LIST_ITEM_TV(li1)->vval.v_string); + + const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]); + TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz( + (const char *)regmatch.startp[0], rd); + TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)( + regmatch.startp[0] - expr); + TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)( + regmatch.endp[0] - expr); + if (l != NULL) { + TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx; + } + break; + } + case kSomeMatchList: { + // Return list with matched string and submatches. + for (int i = 0; i < NSUBEXP; i++) { + if (regmatch.endp[i] == NULL) { + tv_list_append_string(rettv->vval.v_list, NULL, 0); + } else { + tv_list_append_string(rettv->vval.v_list, + (const char *)regmatch.startp[i], + (regmatch.endp[i] - regmatch.startp[i])); + } + } + break; + } + case kSomeMatchStr: { + // Return matched string. + if (l != NULL) { + tv_copy(TV_LIST_ITEM_TV(li), rettv); + } else { + rettv->vval.v_string = (char_u *)xmemdupz( + (const char *)regmatch.startp[0], + (size_t)(regmatch.endp[0] - regmatch.startp[0])); + } + break; + } + case kSomeMatch: + case kSomeMatchEnd: { + if (l != NULL) { + rettv->vval.v_number = idx; + } else { + if (type == kSomeMatch) { + rettv->vval.v_number = + (varnumber_T)(regmatch.startp[0] - str); + } else { + rettv->vval.v_number = + (varnumber_T)(regmatch.endp[0] - str); + } + rettv->vval.v_number += (varnumber_T)(str - expr); + } + break; + } + } + } + vim_regfree(regmatch.regprog); + } + +theend: + if (type == kSomeMatchStrPos && l == NULL && rettv->vval.v_list != NULL) { + // matchstrpos() without a list: drop the second item + list_T *const ret_l = rettv->vval.v_list; + tv_list_item_remove(ret_l, TV_LIST_ITEM_NEXT(ret_l, tv_list_first(ret_l))); + } + + xfree(tofree); + p_cpo = save_cpo; +} + +/* + * "match()" function + */ +static void f_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + find_some_match(argvars, rettv, kSomeMatch); +} + +/* + * "matchadd()" function + */ +static void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char grpbuf[NUMBUFLEN]; + char patbuf[NUMBUFLEN]; + // group + const char *const grp = tv_get_string_buf_chk(&argvars[0], grpbuf); + // pattern + const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf); + // default priority + int prio = 10; + int id = -1; + bool error = false; + const char *conceal_char = NULL; + win_T *win = curwin; + + rettv->vval.v_number = -1; + + if (grp == NULL || pat == NULL) { + return; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + prio = tv_get_number_chk(&argvars[2], &error); + if (argvars[3].v_type != VAR_UNKNOWN) { + id = tv_get_number_chk(&argvars[3], &error); + if (argvars[4].v_type != VAR_UNKNOWN + && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) { + return; + } + } + } + if (error) { + return; + } + if (id >= 1 && id <= 3) { + EMSGN(_("E798: ID is reserved for \":match\": %" PRId64), id); + return; + } + + rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL, conceal_char); +} + +static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = -1; + + char buf[NUMBUFLEN]; + const char *const group = tv_get_string_buf_chk(&argvars[0], buf); + if (group == NULL) { + return; + } + + if (argvars[1].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "matchaddpos()"); + return; + } + + list_T *l; + l = argvars[1].vval.v_list; + if (l == NULL) { + return; + } + + bool error = false; + int prio = 10; + int id = -1; + const char *conceal_char = NULL; + win_T *win = curwin; + + if (argvars[2].v_type != VAR_UNKNOWN) { + prio = tv_get_number_chk(&argvars[2], &error); + if (argvars[3].v_type != VAR_UNKNOWN) { + id = tv_get_number_chk(&argvars[3], &error); + if (argvars[4].v_type != VAR_UNKNOWN + && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) { + return; + } + } + } + if (error == true) { + return; + } + + // id == 3 is ok because matchaddpos() is supposed to substitute :3match + if (id == 1 || id == 2) { + EMSGN(_("E798: ID is reserved for \"match\": %" PRId64), id); + return; + } + + rettv->vval.v_number = match_add(win, group, NULL, prio, id, l, conceal_char); +} + +/* + * "matcharg()" function + */ +static void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const int id = tv_get_number(&argvars[0]); + + tv_list_alloc_ret(rettv, (id >= 1 && id <= 3 + ? 2 + : 0)); + + if (id >= 1 && id <= 3) { + matchitem_T *const m = (matchitem_T *)get_match(curwin, id); + + if (m != NULL) { + tv_list_append_string(rettv->vval.v_list, + (const char *)syn_id2name(m->hlg_id), -1); + tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1); + } else { + tv_list_append_string(rettv->vval.v_list, NULL, 0); + tv_list_append_string(rettv->vval.v_list, NULL, 0); + } + } +} + +/* + * "matchdelete()" function + */ +static void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = match_delete(curwin, + (int)tv_get_number(&argvars[0]), true); +} + +/* + * "matchend()" function + */ +static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + find_some_match(argvars, rettv, kSomeMatchEnd); +} + +/* + * "matchlist()" function + */ +static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + find_some_match(argvars, rettv, kSomeMatchList); +} + +/* + * "matchstr()" function + */ +static void f_matchstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + find_some_match(argvars, rettv, kSomeMatchStr); +} + +/// "matchstrpos()" function +static void f_matchstrpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + find_some_match(argvars, rettv, kSomeMatchStrPos); +} + +/// Get maximal/minimal number value in a list or dictionary +/// +/// @param[in] tv List or dictionary to work with. If it contains something +/// that is not an integer number (or cannot be coerced to +/// it) error is given. +/// @param[out] rettv Location where result will be saved. Only assigns +/// vval.v_number, type is not touched. Returns zero for +/// empty lists/dictionaries. +/// @param[in] domax Determines whether maximal or minimal value is desired. +static void max_min(const typval_T *const tv, typval_T *const rettv, + const bool domax) + FUNC_ATTR_NONNULL_ALL +{ + bool error = false; + + rettv->vval.v_number = 0; + varnumber_T n = (domax ? VARNUMBER_MIN : VARNUMBER_MAX); + if (tv->v_type == VAR_LIST) { + if (tv_list_len(tv->vval.v_list) == 0) { + return; + } + TV_LIST_ITER_CONST(tv->vval.v_list, li, { + const varnumber_T i = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error); + if (error) { + return; + } + if (domax ? i > n : i < n) { + n = i; + } + }); + } else if (tv->v_type == VAR_DICT) { + if (tv_dict_len(tv->vval.v_dict) == 0) { + return; + } + TV_DICT_ITER(tv->vval.v_dict, di, { + const varnumber_T i = tv_get_number_chk(&di->di_tv, &error); + if (error) { + return; + } + if (domax ? i > n : i < n) { + n = i; + } + }); + } else { + EMSG2(_(e_listdictarg), domax ? "max()" : "min()"); + return; + } + rettv->vval.v_number = n; +} + +/* + * "max()" function + */ +static void f_max(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + max_min(argvars, rettv, TRUE); +} + +/* + * "min()" function + */ +static void f_min(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + max_min(argvars, rettv, FALSE); +} + +/* + * "mkdir()" function + */ +static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int prot = 0755; // -V536 + + rettv->vval.v_number = FAIL; + if (check_restricted() || check_secure()) + return; + + char buf[NUMBUFLEN]; + const char *const dir = tv_get_string_buf(&argvars[0], buf); + if (*dir == NUL) { + return; + } + + if (*path_tail((char_u *)dir) == NUL) { + // Remove trailing slashes. + *path_tail_with_sep((char_u *)dir) = NUL; + } + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (argvars[2].v_type != VAR_UNKNOWN) { + prot = tv_get_number_chk(&argvars[2], NULL); + if (prot == -1) { + return; + } + } + if (strcmp(tv_get_string(&argvars[1]), "p") == 0) { + char *failed_dir; + int ret = os_mkdir_recurse(dir, prot, &failed_dir); + if (ret != 0) { + EMSG3(_(e_mkdir), failed_dir, os_strerror(ret)); + xfree(failed_dir); + rettv->vval.v_number = FAIL; + return; + } else { + rettv->vval.v_number = OK; + return; + } + } + } + rettv->vval.v_number = vim_mkdir_emsg(dir, prot); +} + +/// "mode()" function +static void f_mode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char *mode = get_mode(); + + // Clear out the minor mode when the argument is not a non-zero number or + // non-empty string. + if (!non_zero_arg(&argvars[0])) { + mode[1] = NUL; + } + + rettv->vval.v_string = (char_u *)mode; + rettv->v_type = VAR_STRING; +} + +/// "msgpackdump()" function +static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr) + FUNC_ATTR_NONNULL_ALL +{ + if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "msgpackdump()"); + return; + } + list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); + list_T *const list = argvars[0].vval.v_list; + msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write); + const char *const msg = _("msgpackdump() argument, index %i"); + // Assume that translation will not take more then 4 times more space + char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN]; + int idx = 0; + TV_LIST_ITER(list, li, { + vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx); + idx++; + if (encode_vim_to_msgpack(lpacker, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) { + break; + } + }); + msgpack_packer_free(lpacker); +} + +/// "msgpackparse" function +static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) + FUNC_ATTR_NONNULL_ALL +{ + if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "msgpackparse()"); + return; + } + list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); + const list_T *const list = argvars[0].vval.v_list; + if (tv_list_len(list) == 0) { + return; + } + if (TV_LIST_ITEM_TV(tv_list_first(list))->v_type != VAR_STRING) { + EMSG2(_(e_invarg2), "List item is not a string"); + return; + } + ListReaderState lrstate = encode_init_lrstate(list); + msgpack_unpacker *const unpacker = msgpack_unpacker_new(IOSIZE); + if (unpacker == NULL) { + EMSG(_(e_outofmem)); + return; + } + msgpack_unpacked unpacked; + msgpack_unpacked_init(&unpacked); + do { + if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) { + EMSG(_(e_outofmem)); + goto f_msgpackparse_exit; + } + size_t read_bytes; + const int rlret = encode_read_from_list( + &lrstate, msgpack_unpacker_buffer(unpacker), IOSIZE, &read_bytes); + if (rlret == FAIL) { + EMSG2(_(e_invarg2), "List item is not a string"); + goto f_msgpackparse_exit; + } + msgpack_unpacker_buffer_consumed(unpacker, read_bytes); + if (read_bytes == 0) { + break; + } + while (unpacker->off < unpacker->used) { + const msgpack_unpack_return result = msgpack_unpacker_next(unpacker, + &unpacked); + if (result == MSGPACK_UNPACK_PARSE_ERROR) { + EMSG2(_(e_invarg2), "Failed to parse msgpack string"); + goto f_msgpackparse_exit; + } + if (result == MSGPACK_UNPACK_NOMEM_ERROR) { + EMSG(_(e_outofmem)); + goto f_msgpackparse_exit; + } + if (result == MSGPACK_UNPACK_SUCCESS) { + typval_T tv = { .v_type = VAR_UNKNOWN }; + if (msgpack_to_vim(unpacked.data, &tv) == FAIL) { + EMSG2(_(e_invarg2), "Failed to convert msgpack string"); + goto f_msgpackparse_exit; + } + tv_list_append_owned_tv(ret_list, tv); + } + if (result == MSGPACK_UNPACK_CONTINUE) { + if (rlret == OK) { + EMSG2(_(e_invarg2), "Incomplete msgpack string"); + } + break; + } + } + if (rlret == OK) { + break; + } + } while (true); + +f_msgpackparse_exit: + msgpack_unpacked_destroy(&unpacked); + msgpack_unpacker_free(unpacker); + return; +} + +/* + * "nextnonblank()" function + */ +static void f_nextnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + linenr_T lnum; + + for (lnum = tv_get_lnum(argvars);; lnum++) { + if (lnum < 0 || lnum > curbuf->b_ml.ml_line_count) { + lnum = 0; + break; + } + if (*skipwhite(ml_get(lnum)) != NUL) { + break; + } + } + rettv->vval.v_number = lnum; +} + +/* + * "nr2char()" function + */ +static void f_nr2char(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[1].v_type != VAR_UNKNOWN) { + if (!tv_check_num(&argvars[1])) { + return; + } + } + + bool error = false; + const varnumber_T num = tv_get_number_chk(&argvars[0], &error); + if (error) { + return; + } + if (num < 0) { + EMSG(_("E5070: Character number must not be less than zero")); + return; + } + if (num > INT_MAX) { + emsgf(_("E5071: Character number must not be greater than INT_MAX (%i)"), + INT_MAX); + return; + } + + char buf[MB_MAXBYTES]; + const int len = utf_char2bytes((int)num, (char_u *)buf); + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = xmemdupz(buf, (size_t)len); +} + +/* + * "or(expr, expr)" function + */ +static void f_or(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) + | tv_get_number_chk(&argvars[1], NULL); +} + +/* + * "pathshorten()" function + */ +static void f_pathshorten(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + const char *const s = tv_get_string_chk(&argvars[0]); + if (!s) { + return; + } + rettv->vval.v_string = shorten_dir((char_u *)xstrdup(s)); +} + +/* + * "pow()" function + */ +static void f_pow(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + float_T fx; + float_T fy; + + rettv->v_type = VAR_FLOAT; + if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) { + rettv->vval.v_float = pow(fx, fy); + } else { + rettv->vval.v_float = 0.0; + } +} + +/* + * "prevnonblank()" function + */ +static void f_prevnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + linenr_T lnum = tv_get_lnum(argvars); + if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) { + lnum = 0; + } else { + while (lnum >= 1 && *skipwhite(ml_get(lnum)) == NUL) { + lnum--; + } + } + rettv->vval.v_number = lnum; +} + +/* + * "printf()" function + */ +static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + { + int len; + int saved_did_emsg = did_emsg; + + // Get the required length, allocate the buffer and do it for real. + did_emsg = false; + char buf[NUMBUFLEN]; + const char *fmt = tv_get_string_buf(&argvars[0], buf); + len = vim_vsnprintf_typval(NULL, 0, fmt, dummy_ap, argvars + 1); + if (!did_emsg) { + char *s = xmalloc(len + 1); + rettv->vval.v_string = (char_u *)s; + (void)vim_vsnprintf_typval(s, len + 1, fmt, dummy_ap, argvars + 1); + } + did_emsg |= saved_did_emsg; + } +} + +// "prompt_setcallback({buffer}, {callback})" function +static void f_prompt_setcallback(typval_T *argvars, + typval_T *rettv, FunPtr fptr) +{ + buf_T *buf; + Callback prompt_callback = { .type = kCallbackNone }; + + if (check_secure()) { + return; + } + buf = tv_get_buf(&argvars[0], false); + if (buf == NULL) { + return; + } + + if (argvars[1].v_type != VAR_STRING || *argvars[1].vval.v_string != NUL) { + if (!callback_from_typval(&prompt_callback, &argvars[1])) { + return; + } + } + + callback_free(&buf->b_prompt_callback); + buf->b_prompt_callback = prompt_callback; +} + +// "prompt_setinterrupt({buffer}, {callback})" function +static void f_prompt_setinterrupt(typval_T *argvars, + typval_T *rettv, FunPtr fptr) +{ + buf_T *buf; + Callback interrupt_callback = { .type = kCallbackNone }; + + if (check_secure()) { + return; + } + buf = tv_get_buf(&argvars[0], false); + if (buf == NULL) { + return; + } + + if (argvars[1].v_type != VAR_STRING || *argvars[1].vval.v_string != NUL) { + if (!callback_from_typval(&interrupt_callback, &argvars[1])) { + return; + } + } + + callback_free(&buf->b_prompt_interrupt); + buf->b_prompt_interrupt= interrupt_callback; +} + +// "prompt_setprompt({buffer}, {text})" function +static void f_prompt_setprompt(typval_T *argvars, + typval_T *rettv, FunPtr fptr) +{ + buf_T *buf; + const char_u *text; + + if (check_secure()) { + return; + } + buf = tv_get_buf(&argvars[0], false); + if (buf == NULL) { + return; + } + + text = (const char_u *)tv_get_string(&argvars[1]); + xfree(buf->b_prompt_text); + buf->b_prompt_text = vim_strsave(text); +} + +// "pum_getpos()" function +static void f_pum_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_alloc_ret(rettv); + pum_set_event_info(rettv->vval.v_dict); +} + +/* + * "pumvisible()" function + */ +static void f_pumvisible(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (pum_visible()) + rettv->vval.v_number = 1; +} + +/* + * "pyeval()" function + */ +static void f_pyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + script_host_eval("python", argvars, rettv); +} + +/* + * "py3eval()" function + */ +static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + script_host_eval("python3", argvars, rettv); +} + +// "pyxeval()" function +static void f_pyxeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + init_pyxversion(); + if (p_pyx == 2) { + f_pyeval(argvars, rettv, NULL); + } else { + f_py3eval(argvars, rettv, NULL); + } +} + +/* + * "range()" function + */ +static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + varnumber_T start; + varnumber_T end; + varnumber_T stride = 1; + varnumber_T i; + bool error = false; + + start = tv_get_number_chk(&argvars[0], &error); + if (argvars[1].v_type == VAR_UNKNOWN) { + end = start - 1; + start = 0; + } else { + end = tv_get_number_chk(&argvars[1], &error); + if (argvars[2].v_type != VAR_UNKNOWN) { + stride = tv_get_number_chk(&argvars[2], &error); + } + } + + if (error) { + return; // Type error; errmsg already given. + } + if (stride == 0) { + EMSG(_("E726: Stride is zero")); + } else if (stride > 0 ? end + 1 < start : end - 1 > start) { + EMSG(_("E727: Start past end")); + } else { + tv_list_alloc_ret(rettv, (end - start) / stride); + for (i = start; stride > 0 ? i <= end : i >= end; i += stride) { + tv_list_append_number(rettv->vval.v_list, (varnumber_T)i); + } + } +} + +// Evaluate "expr" for readdir(). +static varnumber_T readdir_checkitem(typval_T *expr, const char *name) +{ + typval_T save_val; + typval_T rettv; + typval_T argv[2]; + varnumber_T retval = 0; + bool error = false; + + prepare_vimvar(VV_VAL, &save_val); + set_vim_var_string(VV_VAL, name, -1); + argv[0].v_type = VAR_STRING; + argv[0].vval.v_string = (char_u *)name; + + if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) { + goto theend; + } + + retval = tv_get_number_chk(&rettv, &error); + if (error) { + retval = -1; + } + + tv_clear(&rettv); + +theend: + set_vim_var_string(VV_VAL, NULL, 0); + restore_vimvar(VV_VAL, &save_val); + return retval; +} + +// "readdir()" function +static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + typval_T *expr; + const char *path; + garray_T ga; + Directory dir; + + tv_list_alloc_ret(rettv, kListLenUnknown); + path = tv_get_string(&argvars[0]); + expr = &argvars[1]; + ga_init(&ga, (int)sizeof(char *), 20); + + if (!os_scandir(&dir, path)) { + smsg(_(e_notopen), path); + } else { + for (;;) { + bool ignore; + + path = os_scandir_next(&dir); + if (path == NULL) { + break; + } + + ignore = (path[0] == '.' + && (path[1] == NUL || (path[1] == '.' && path[2] == NUL))); + if (!ignore && expr->v_type != VAR_UNKNOWN) { + varnumber_T r = readdir_checkitem(expr, path); + + if (r < 0) { + break; + } + if (r == 0) { + ignore = true; + } + } + + if (!ignore) { + ga_grow(&ga, 1); + ((char **)ga.ga_data)[ga.ga_len++] = xstrdup(path); + } + } + + os_closedir(&dir); + } + + if (rettv->vval.v_list != NULL && ga.ga_len > 0) { + sort_strings((char_u **)ga.ga_data, ga.ga_len); + for (int i = 0; i < ga.ga_len; i++) { + path = ((const char **)ga.ga_data)[i]; + tv_list_append_string(rettv->vval.v_list, path, -1); + } + } + ga_clear_strings(&ga); +} + +/* + * "readfile()" function + */ +static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + bool binary = false; + FILE *fd; + char_u buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1 + int io_size = sizeof(buf); + int readlen; // size of last fread() + char_u *prev = NULL; // previously read bytes, if any + long prevlen = 0; // length of data in prev + long prevsize = 0; // size of prev buffer + long maxline = MAXLNUM; + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (strcmp(tv_get_string(&argvars[1]), "b") == 0) { + binary = true; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + maxline = tv_get_number(&argvars[2]); + } + } + + list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown); + + // Always open the file in binary mode, library functions have a mind of + // their own about CR-LF conversion. + const char *const fname = tv_get_string(&argvars[0]); + if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) { + EMSG2(_(e_notopen), *fname == NUL ? _("<empty>") : fname); + return; + } + + while (maxline < 0 || tv_list_len(l) < maxline) { + readlen = (int)fread(buf, 1, io_size, fd); + + // This for loop processes what was read, but is also entered at end + // of file so that either: + // - an incomplete line gets written + // - a "binary" file gets an empty line at the end if it ends in a + // newline. + char_u *p; // Position in buf. + char_u *start; // Start of current line. + for (p = buf, start = buf; + p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); + p++) { + if (*p == '\n' || readlen <= 0) { + char_u *s = NULL; + size_t len = p - start; + + // Finished a line. Remove CRs before NL. + if (readlen > 0 && !binary) { + while (len > 0 && start[len - 1] == '\r') { + len--; + } + // removal may cross back to the "prev" string + if (len == 0) { + while (prevlen > 0 && prev[prevlen - 1] == '\r') { + prevlen--; + } + } + } + if (prevlen == 0) { + assert(len < INT_MAX); + s = vim_strnsave(start, (int)len); + } else { + /* Change "prev" buffer to be the right size. This way + * the bytes are only copied once, and very long lines are + * allocated only once. */ + s = xrealloc(prev, prevlen + len + 1); + memcpy(s + prevlen, start, len); + s[prevlen + len] = NUL; + prev = NULL; // the list will own the string + prevlen = prevsize = 0; + } + + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = s, + }); + + start = p + 1; // Step over newline. + if (maxline < 0) { + if (tv_list_len(l) > -maxline) { + assert(tv_list_len(l) == 1 + (-maxline)); + tv_list_item_remove(l, tv_list_first(l)); + } + } else if (tv_list_len(l) >= maxline) { + assert(tv_list_len(l) == maxline); + break; + } + if (readlen <= 0) { + break; + } + } else if (*p == NUL) { + *p = '\n'; + // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this + // when finding the BF and check the previous two bytes. + } else if (*p == 0xbf && !binary) { + // Find the two bytes before the 0xbf. If p is at buf, or buf + 1, + // these may be in the "prev" string. + char_u back1 = p >= buf + 1 ? p[-1] + : prevlen >= 1 ? prev[prevlen - 1] : NUL; + char_u back2 = p >= buf + 2 ? p[-2] + : p == buf + 1 && prevlen >= 1 ? prev[prevlen - 1] + : prevlen >= 2 ? prev[prevlen - 2] : NUL; + + if (back2 == 0xef && back1 == 0xbb) { + char_u *dest = p - 2; + + // Usually a BOM is at the beginning of a file, and so at + // the beginning of a line; then we can just step over it. + if (start == dest) { + start = p + 1; + } else { + // have to shuffle buf to close gap + int adjust_prevlen = 0; + + if (dest < buf) { // -V782 + adjust_prevlen = (int)(buf - dest); // -V782 + // adjust_prevlen must be 1 or 2. + dest = buf; + } + if (readlen > p - buf + 1) + memmove(dest, p + 1, readlen - (p - buf) - 1); + readlen -= 3 - adjust_prevlen; + prevlen -= adjust_prevlen; + p = dest - 1; + } + } + } + } // for + + if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) { + break; + } + if (start < p) { + // There's part of a line in buf, store it in "prev". + if (p - start + prevlen >= prevsize) { + /* A common use case is ordinary text files and "prev" gets a + * fragment of a line, so the first allocation is made + * small, to avoid repeatedly 'allocing' large and + * 'reallocing' small. */ + if (prevsize == 0) + prevsize = (long)(p - start); + else { + long grow50pc = (prevsize * 3) / 2; + long growmin = (long)((p - start) * 2 + prevlen); + prevsize = grow50pc > growmin ? grow50pc : growmin; + } + prev = xrealloc(prev, prevsize); + } + // Add the line part to end of "prev". + memmove(prev + prevlen, start, p - start); + prevlen += (long)(p - start); + } + } // while + + xfree(prev); + fclose(fd); +} + +// "reg_executing()" function +static void f_reg_executing(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + return_register(reg_executing, rettv); +} + +// "reg_recording()" function +static void f_reg_recording(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + return_register(reg_recording, rettv); +} + +/// list2proftime - convert a List to proftime_T +/// +/// @param arg The input list, must be of type VAR_LIST and have +/// exactly 2 items +/// @param[out] tm The proftime_T representation of `arg` +/// @return OK In case of success, FAIL in case of error +static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL +{ + if (arg->v_type != VAR_LIST || tv_list_len(arg->vval.v_list) != 2) { + return FAIL; + } + + bool error = false; + varnumber_T n1 = tv_list_find_nr(arg->vval.v_list, 0L, &error); + varnumber_T n2 = tv_list_find_nr(arg->vval.v_list, 1L, &error); + if (error) { + return FAIL; + } + + // in f_reltime() we split up the 64-bit proftime_T into two 32-bit + // values, now we combine them again. + union { + struct { int32_t low, high; } split; + proftime_T prof; + } u = { .split.high = n1, .split.low = n2 }; + + *tm = u.prof; + + return OK; +} + +/// f_reltime - return an item that represents a time value +/// +/// @param[out] rettv Without an argument it returns the current time. With +/// one argument it returns the time passed since the argument. +/// With two arguments it returns the time passed between +/// the two arguments. +static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + proftime_T res; + proftime_T start; + + if (argvars[0].v_type == VAR_UNKNOWN) { + // no arguments: get current time. + res = profile_start(); + } else if (argvars[1].v_type == VAR_UNKNOWN) { + if (list2proftime(&argvars[0], &res) == FAIL) { + return; + } + res = profile_end(res); + } else { + // two arguments: compute the difference. + if (list2proftime(&argvars[0], &start) == FAIL + || list2proftime(&argvars[1], &res) == FAIL) { + return; + } + res = profile_sub(res, start); + } + + // we have to store the 64-bit proftime_T inside of a list of int's + // (varnumber_T is defined as int). For all our supported platforms, int's + // are at least 32-bits wide. So we'll use two 32-bit values to store it. + union { + struct { int32_t low, high; } split; + proftime_T prof; + } u = { .prof = res }; + + // statically assert that the union type conv will provide the correct + // results, if varnumber_T or proftime_T change, the union cast will need + // to be revised. + STATIC_ASSERT(sizeof(u.prof) == sizeof(u) && sizeof(u.split) == sizeof(u), + "type punning will produce incorrect results on this platform"); + + tv_list_alloc_ret(rettv, 2); + tv_list_append_number(rettv->vval.v_list, u.split.high); + tv_list_append_number(rettv->vval.v_list, u.split.low); +} + +/// "reltimestr()" function +static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr) + FUNC_ATTR_NONNULL_ALL +{ + proftime_T tm; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (list2proftime(&argvars[0], &tm) == OK) { + rettv->vval.v_string = (char_u *)xstrdup(profile_msg(tm)); + } +} + +/* + * "remove()" function + */ +static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + list_T *l; + listitem_T *item, *item2; + listitem_T *li; + long idx; + long end; + dict_T *d; + dictitem_T *di; + const char *const arg_errmsg = N_("remove() argument"); + + if (argvars[0].v_type == VAR_DICT) { + if (argvars[2].v_type != VAR_UNKNOWN) { + EMSG2(_(e_toomanyarg), "remove()"); + } else if ((d = argvars[0].vval.v_dict) != NULL + && !tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) { + const char *key = tv_get_string_chk(&argvars[1]); + if (key != NULL) { + di = tv_dict_find(d, key, -1); + if (di == NULL) { + EMSG2(_(e_dictkey), key); + } else if (!var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE) + && !var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) { + *rettv = di->di_tv; + di->di_tv = TV_INITIAL_VALUE; + tv_dict_item_remove(d, di); + if (tv_dict_is_watched(d)) { + tv_dict_watcher_notify(d, key, NULL, rettv); + } + } + } + } + } else if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listdictarg), "remove()"); + } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + arg_errmsg, TV_TRANSLATE)) { + bool error = false; + + idx = tv_get_number_chk(&argvars[1], &error); + if (error) { + // Type error: do nothing, errmsg already given. + } else if ((item = tv_list_find(l, idx)) == NULL) { + EMSGN(_(e_listidx), idx); + } else { + if (argvars[2].v_type == VAR_UNKNOWN) { + // Remove one item, return its value. + tv_list_drop_items(l, item, item); + *rettv = *TV_LIST_ITEM_TV(item); + xfree(item); + } else { + // Remove range of items, return list with values. + end = tv_get_number_chk(&argvars[2], &error); + if (error) { + // Type error: do nothing. + } else if ((item2 = tv_list_find(l, end)) == NULL) { + EMSGN(_(e_listidx), end); + } else { + int cnt = 0; + + for (li = item; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { + cnt++; + if (li == item2) { + break; + } + } + if (li == NULL) { // Didn't find "item2" after "item". + EMSG(_(e_invrange)); + } else { + tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv, cnt), + cnt); + } + } + } + } + } +} + +/* + * "rename({from}, {to})" function + */ +static void f_rename(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (check_restricted() || check_secure()) { + rettv->vval.v_number = -1; + } else { + char buf[NUMBUFLEN]; + rettv->vval.v_number = vim_rename( + (const char_u *)tv_get_string(&argvars[0]), + (const char_u *)tv_get_string_buf(&argvars[1], buf)); + } +} + +/* + * "repeat()" function + */ +static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + varnumber_T n = tv_get_number(&argvars[1]); + if (argvars[0].v_type == VAR_LIST) { + tv_list_alloc_ret(rettv, (n > 0) * n * tv_list_len(argvars[0].vval.v_list)); + while (n-- > 0) { + tv_list_extend(rettv->vval.v_list, argvars[0].vval.v_list, NULL); + } + } else { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (n <= 0) { + return; + } + + const char *const p = tv_get_string(&argvars[0]); + + const size_t slen = strlen(p); + if (slen == 0) { + return; + } + const size_t len = slen * n; + // Detect overflow. + if (len / n != slen) { + return; + } + + char *const r = xmallocz(len); + for (varnumber_T i = 0; i < n; i++) { + memmove(r + i * slen, p, slen); + } + + rettv->vval.v_string = (char_u *)r; + } +} + +/* + * "resolve()" function + */ +static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + const char *fname = tv_get_string(&argvars[0]); +#ifdef WIN32 + char *v = os_resolve_shortcut(fname); + if (v == NULL) { + if (os_is_reparse_point_include(fname)) { + v = os_realpath(fname, v); + } + } + rettv->vval.v_string = (char_u *)(v == NULL ? xstrdup(fname) : v); +#else +# ifdef HAVE_READLINK + { + bool is_relative_to_current = false; + bool has_trailing_pathsep = false; + int limit = 100; + + char *p = xstrdup(fname); + + if (p[0] == '.' && (vim_ispathsep(p[1]) + || (p[1] == '.' && (vim_ispathsep(p[2]))))) { + is_relative_to_current = true; + } + + ptrdiff_t len = (ptrdiff_t)strlen(p); + if (len > 0 && after_pathsep(p, p + len)) { + has_trailing_pathsep = true; + p[len - 1] = NUL; // The trailing slash breaks readlink(). + } + + char *q = (char *)path_next_component(p); + char *remain = NULL; + if (*q != NUL) { + // Separate the first path component in "p", and keep the + // remainder (beginning with the path separator). + remain = xstrdup(q - 1); + q[-1] = NUL; + } + + char *const buf = xmallocz(MAXPATHL); + + char *cpy; + for (;; ) { + for (;; ) { + len = readlink(p, buf, MAXPATHL); + if (len <= 0) { + break; + } + buf[len] = NUL; + + if (limit-- == 0) { + xfree(p); + xfree(remain); + EMSG(_("E655: Too many symbolic links (cycle?)")); + rettv->vval.v_string = NULL; + xfree(buf); + return; + } + + // Ensure that the result will have a trailing path separator + // if the argument has one. */ + if (remain == NULL && has_trailing_pathsep) { + add_pathsep(buf); + } + + // Separate the first path component in the link value and + // concatenate the remainders. */ + q = (char *)path_next_component(vim_ispathsep(*buf) ? buf + 1 : buf); + if (*q != NUL) { + cpy = remain; + remain = (remain + ? (char *)concat_str((char_u *)q - 1, (char_u *)remain) + : xstrdup(q - 1)); + xfree(cpy); + q[-1] = NUL; + } + + q = (char *)path_tail((char_u *)p); + if (q > p && *q == NUL) { + // Ignore trailing path separator. + q[-1] = NUL; + q = (char *)path_tail((char_u *)p); + } + if (q > p && !path_is_absolute((const char_u *)buf)) { + // Symlink is relative to directory of argument. Replace the + // symlink with the resolved name in the same directory. + const size_t p_len = strlen(p); + const size_t buf_len = strlen(buf); + p = xrealloc(p, p_len + buf_len + 1); + memcpy(path_tail((char_u *)p), buf, buf_len + 1); + } else { + xfree(p); + p = xstrdup(buf); + } + } + + if (remain == NULL) { + break; + } + + // Append the first path component of "remain" to "p". + q = (char *)path_next_component(remain + 1); + len = q - remain - (*q != NUL); + const size_t p_len = strlen(p); + cpy = xmallocz(p_len + len); + memcpy(cpy, p, p_len + 1); + xstrlcat(cpy + p_len, remain, len + 1); + xfree(p); + p = cpy; + + // Shorten "remain". + if (*q != NUL) { + STRMOVE(remain, q - 1); + } else { + XFREE_CLEAR(remain); + } + } + + // If the result is a relative path name, make it explicitly relative to + // the current directory if and only if the argument had this form. + if (!vim_ispathsep(*p)) { + if (is_relative_to_current + && *p != NUL + && !(p[0] == '.' + && (p[1] == NUL + || vim_ispathsep(p[1]) + || (p[1] == '.' + && (p[2] == NUL + || vim_ispathsep(p[2])))))) { + // Prepend "./". + cpy = (char *)concat_str((const char_u *)"./", (const char_u *)p); + xfree(p); + p = cpy; + } else if (!is_relative_to_current) { + // Strip leading "./". + q = p; + while (q[0] == '.' && vim_ispathsep(q[1])) { + q += 2; + } + if (q > p) { + STRMOVE(p, p + 2); + } + } + } + + // Ensure that the result will have no trailing path separator + // if the argument had none. But keep "/" or "//". + if (!has_trailing_pathsep) { + q = p + strlen(p); + if (after_pathsep(p, q)) { + *path_tail_with_sep((char_u *)p) = NUL; + } + } + + rettv->vval.v_string = (char_u *)p; + xfree(buf); + } +# else + rettv->vval.v_string = (char_u *)xstrdup(p); +# endif +#endif + + simplify_filename(rettv->vval.v_string); +} + +/* + * "reverse({list})" function + */ +static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + list_T *l; + if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "reverse()"); + } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + N_("reverse() argument"), TV_TRANSLATE)) { + tv_list_reverse(l); + tv_list_set_ret(rettv, l); + } +} + +#define SP_NOMOVE 0x01 ///< don't move cursor +#define SP_REPEAT 0x02 ///< repeat to find outer pair +#define SP_RETCOUNT 0x04 ///< return matchcount +#define SP_SETPCMARK 0x08 ///< set previous context mark +#define SP_START 0x10 ///< accept match at start position +#define SP_SUBPAT 0x20 ///< return nr of matching sub-pattern +#define SP_END 0x40 ///< leave cursor at end of match +#define SP_COLUMN 0x80 ///< start at cursor column + +/* + * Get flags for a search function. + * Possibly sets "p_ws". + * Returns BACKWARD, FORWARD or zero (for an error). + */ +static int get_search_arg(typval_T *varp, int *flagsp) +{ + int dir = FORWARD; + int mask; + + if (varp->v_type != VAR_UNKNOWN) { + char nbuf[NUMBUFLEN]; + const char *flags = tv_get_string_buf_chk(varp, nbuf); + if (flags == NULL) { + return 0; // Type error; errmsg already given. + } + while (*flags != NUL) { + switch (*flags) { + case 'b': dir = BACKWARD; break; + case 'w': p_ws = true; break; + case 'W': p_ws = false; break; + default: { + mask = 0; + if (flagsp != NULL) { + switch (*flags) { + case 'c': mask = SP_START; break; + case 'e': mask = SP_END; break; + case 'm': mask = SP_RETCOUNT; break; + case 'n': mask = SP_NOMOVE; break; + case 'p': mask = SP_SUBPAT; break; + case 'r': mask = SP_REPEAT; break; + case 's': mask = SP_SETPCMARK; break; + case 'z': mask = SP_COLUMN; break; + } + } + if (mask == 0) { + emsgf(_(e_invarg2), flags); + dir = 0; + } else { + *flagsp |= mask; + } + } + } + if (dir == 0) { + break; + } + flags++; + } + } + return dir; +} + +// Shared by search() and searchpos() functions. +static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) +{ + int flags; + pos_T pos; + pos_T save_cursor; + bool save_p_ws = p_ws; + int dir; + int retval = 0; // default: FAIL + long lnum_stop = 0; + proftime_T tm; + long time_limit = 0; + int options = SEARCH_KEEP; + int subpatnum; + searchit_arg_T sia; + + const char *const pat = tv_get_string(&argvars[0]); + dir = get_search_arg(&argvars[1], flagsp); // May set p_ws. + if (dir == 0) { + goto theend; + } + flags = *flagsp; + if (flags & SP_START) { + options |= SEARCH_START; + } + if (flags & SP_END) { + options |= SEARCH_END; + } + if (flags & SP_COLUMN) { + options |= SEARCH_COL; + } + + // Optional arguments: line number to stop searching and timeout. + if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) { + lnum_stop = tv_get_number_chk(&argvars[2], NULL); + if (lnum_stop < 0) { + goto theend; + } + if (argvars[3].v_type != VAR_UNKNOWN) { + time_limit = tv_get_number_chk(&argvars[3], NULL); + if (time_limit < 0) { + goto theend; + } + } + } + + // Set the time limit, if there is one. + tm = profile_setlimit(time_limit); + + /* + * This function does not accept SP_REPEAT and SP_RETCOUNT flags. + * Check to make sure only those flags are set. + * Also, Only the SP_NOMOVE or the SP_SETPCMARK flag can be set. Both + * flags cannot be set. Check for that condition also. + */ + if (((flags & (SP_REPEAT | SP_RETCOUNT)) != 0) + || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) { + EMSG2(_(e_invarg2), tv_get_string(&argvars[1])); + goto theend; + } + + pos = save_cursor = curwin->w_cursor; + memset(&sia, 0, sizeof(sia)); + sia.sa_stop_lnum = (linenr_T)lnum_stop; + sia.sa_tm = &tm; + subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1, + options, RE_SEARCH, &sia); + if (subpatnum != FAIL) { + if (flags & SP_SUBPAT) + retval = subpatnum; + else + retval = pos.lnum; + if (flags & SP_SETPCMARK) + setpcmark(); + curwin->w_cursor = pos; + if (match_pos != NULL) { + // Store the match cursor position + match_pos->lnum = pos.lnum; + match_pos->col = pos.col + 1; + } + // "/$" will put the cursor after the end of the line, may need to + // correct that here + check_cursor(); + } + + // If 'n' flag is used: restore cursor position. + if (flags & SP_NOMOVE) { + curwin->w_cursor = save_cursor; + } else { + curwin->w_set_curswant = true; + } +theend: + p_ws = save_p_ws; + + return retval; +} + +// "rpcnotify()" function +static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number < 0) { + EMSG2(_(e_invarg2), "Channel id must be a positive integer"); + return; + } + + if (argvars[1].v_type != VAR_STRING) { + EMSG2(_(e_invarg2), "Event type must be a string"); + return; + } + + Array args = ARRAY_DICT_INIT; + + for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) { + ADD(args, vim_to_object(tv)); + } + + if (!rpc_send_event((uint64_t)argvars[0].vval.v_number, + tv_get_string(&argvars[1]), args)) { + EMSG2(_(e_invarg2), "Channel doesn't exist"); + return; + } + + rettv->vval.v_number = 1; +} + +// "rpcrequest()" function +static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + const int l_provider_call_nesting = provider_call_nesting; + + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number <= 0) { + EMSG2(_(e_invarg2), "Channel id must be a positive integer"); + return; + } + + if (argvars[1].v_type != VAR_STRING) { + EMSG2(_(e_invarg2), "Method name must be a string"); + return; + } + + Array args = ARRAY_DICT_INIT; + + for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) { + ADD(args, vim_to_object(tv)); + } + + sctx_T save_current_sctx; + uint8_t *save_sourcing_name, *save_autocmd_fname, *save_autocmd_match; + linenr_T save_sourcing_lnum; + int save_autocmd_bufnr; + void *save_funccalp; + + if (l_provider_call_nesting) { + // If this is called from a provider function, restore the scope + // information of the caller. + save_current_sctx = current_sctx; + save_sourcing_name = sourcing_name; + save_sourcing_lnum = sourcing_lnum; + save_autocmd_fname = autocmd_fname; + save_autocmd_match = autocmd_match; + save_autocmd_bufnr = autocmd_bufnr; + save_funccalp = save_funccal(); + + current_sctx = provider_caller_scope.script_ctx; + sourcing_name = provider_caller_scope.sourcing_name; + sourcing_lnum = provider_caller_scope.sourcing_lnum; + autocmd_fname = provider_caller_scope.autocmd_fname; + autocmd_match = provider_caller_scope.autocmd_match; + autocmd_bufnr = provider_caller_scope.autocmd_bufnr; + restore_funccal(provider_caller_scope.funccalp); + } + + + Error err = ERROR_INIT; + + uint64_t chan_id = (uint64_t)argvars[0].vval.v_number; + const char *method = tv_get_string(&argvars[1]); + + Object result = rpc_send_call(chan_id, method, args, &err); + + if (l_provider_call_nesting) { + current_sctx = save_current_sctx; + sourcing_name = save_sourcing_name; + sourcing_lnum = save_sourcing_lnum; + autocmd_fname = save_autocmd_fname; + autocmd_match = save_autocmd_match; + autocmd_bufnr = save_autocmd_bufnr; + restore_funccal(save_funccalp); + } + + if (ERROR_SET(&err)) { + const char *name = NULL; + Channel *chan = find_channel(chan_id); + if (chan) { + name = rpc_client_name(chan); + } + msg_ext_set_kind("rpc_error"); + if (name) { + emsgf_multiline("Error invoking '%s' on channel %"PRIu64" (%s):\n%s", + method, chan_id, name, err.msg); + } else { + emsgf_multiline("Error invoking '%s' on channel %"PRIu64":\n%s", + method, chan_id, err.msg); + } + + goto end; + } + + if (!object_to_vim(result, rettv, &err)) { + EMSG2(_("Error converting the call result: %s"), err.msg); + } + +end: + api_free_object(result); + api_clear_error(&err); +} + +// "rpcstart()" function (DEPRECATED) +static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_STRING + || (argvars[1].v_type != VAR_LIST && argvars[1].v_type != VAR_UNKNOWN)) { + // Wrong argument types + EMSG(_(e_invarg)); + return; + } + + list_T *args = NULL; + int argsl = 0; + if (argvars[1].v_type == VAR_LIST) { + args = argvars[1].vval.v_list; + argsl = tv_list_len(args); + // Assert that all list items are strings + int i = 0; + TV_LIST_ITER_CONST(args, arg, { + if (TV_LIST_ITEM_TV(arg)->v_type != VAR_STRING) { + emsgf(_("E5010: List item %d of the second argument is not a string"), + i); + return; + } + i++; + }); + } + + if (argvars[0].vval.v_string == NULL || argvars[0].vval.v_string[0] == NUL) { + EMSG(_(e_api_spawn_failed)); + return; + } + + // Allocate extra memory for the argument vector and the NULL pointer + int argvl = argsl + 2; + char **argv = xmalloc(sizeof(char_u *) * argvl); + + // Copy program name + argv[0] = xstrdup((char *)argvars[0].vval.v_string); + + int i = 1; + // Copy arguments to the vector + if (argsl > 0) { + TV_LIST_ITER_CONST(args, arg, { + argv[i++] = xstrdup(tv_get_string(TV_LIST_ITEM_TV(arg))); + }); + } + + // The last item of argv must be NULL + argv[i] = NULL; + + Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT, + CALLBACK_READER_INIT, CALLBACK_NONE, + false, true, false, NULL, 0, 0, NULL, NULL, + &rettv->vval.v_number); + if (chan) { + channel_create_event(chan, NULL); + } +} + +// "rpcstop()" function +static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_NUMBER) { + // Wrong argument types + EMSG(_(e_invarg)); + return; + } + + // if called with a job, stop it, else closes the channel + uint64_t id = argvars[0].vval.v_number; + if (find_job(id, false)) { + f_jobstop(argvars, rettv, NULL); + } else { + const char *error; + rettv->vval.v_number = channel_close(argvars[0].vval.v_number, + kChannelPartRpc, &error); + if (!rettv->vval.v_number) { + EMSG(error); + } + } +} + +/* + * "screenattr()" function + */ +static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int c; + + int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1; + int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1; + if (row < 0 || row >= default_grid.Rows + || col < 0 || col >= default_grid.Columns) { + c = -1; + } else { + ScreenGrid *grid = &default_grid; + screenchar_adjust_grid(&grid, &row, &col); + c = grid->attrs[grid->line_offset[row] + col]; + } + rettv->vval.v_number = c; +} + +/* + * "screenchar()" function + */ +static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int c; + + int row = tv_get_number_chk(&argvars[0], NULL) - 1; + int col = tv_get_number_chk(&argvars[1], NULL) - 1; + if (row < 0 || row >= default_grid.Rows + || col < 0 || col >= default_grid.Columns) { + c = -1; + } else { + ScreenGrid *grid = &default_grid; + screenchar_adjust_grid(&grid, &row, &col); + c = utf_ptr2char(grid->chars[grid->line_offset[row] + col]); + } + rettv->vval.v_number = c; +} + +/* + * "screencol()" function + * + * First column is 1 to be consistent with virtcol(). + */ +static void f_screencol(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = ui_current_col() + 1; +} + +/// "screenpos({winid}, {lnum}, {col})" function +static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + pos_T pos; + int row = 0; + int scol = 0, ccol = 0, ecol = 0; + + tv_dict_alloc_ret(rettv); + dict_T *dict = rettv->vval.v_dict; + + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + return; + } + + pos.lnum = tv_get_number(&argvars[1]); + pos.col = tv_get_number(&argvars[2]) - 1; + pos.coladd = 0; + textpos2screenpos(wp, &pos, &row, &scol, &ccol, &ecol, false); + + tv_dict_add_nr(dict, S_LEN("row"), row); + tv_dict_add_nr(dict, S_LEN("col"), scol); + tv_dict_add_nr(dict, S_LEN("curscol"), ccol); + tv_dict_add_nr(dict, S_LEN("endcol"), ecol); +} + +/* + * "screenrow()" function + */ +static void f_screenrow(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = ui_current_row() + 1; +} + +/* + * "search()" function + */ +static void f_search(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int flags = 0; + + rettv->vval.v_number = search_cmn(argvars, NULL, &flags); +} + +/* + * "searchdecl()" function + */ +static void f_searchdecl(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int locally = 1; + int thisblock = 0; + bool error = false; + + rettv->vval.v_number = 1; // default: FAIL + + const char *const name = tv_get_string_chk(&argvars[0]); + if (argvars[1].v_type != VAR_UNKNOWN) { + locally = tv_get_number_chk(&argvars[1], &error) == 0; + if (!error && argvars[2].v_type != VAR_UNKNOWN) { + thisblock = tv_get_number_chk(&argvars[2], &error) != 0; + } + } + if (!error && name != NULL) { + rettv->vval.v_number = find_decl((char_u *)name, strlen(name), locally, + thisblock, SEARCH_KEEP) == FAIL; + } +} + +/* + * Used by searchpair() and searchpairpos() + */ +static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) +{ + bool save_p_ws = p_ws; + int dir; + int flags = 0; + int retval = 0; // default: FAIL + long lnum_stop = 0; + long time_limit = 0; + + // Get the three pattern arguments: start, middle, end. Will result in an + // error if not a valid argument. + char nbuf1[NUMBUFLEN]; + char nbuf2[NUMBUFLEN]; + const char *spat = tv_get_string_chk(&argvars[0]); + const char *mpat = tv_get_string_buf_chk(&argvars[1], nbuf1); + const char *epat = tv_get_string_buf_chk(&argvars[2], nbuf2); + if (spat == NULL || mpat == NULL || epat == NULL) { + goto theend; // Type error. + } + + // Handle the optional fourth argument: flags. + dir = get_search_arg(&argvars[3], &flags); // may set p_ws. + if (dir == 0) { + goto theend; + } + + // Don't accept SP_END or SP_SUBPAT. + // Only one of the SP_NOMOVE or SP_SETPCMARK flags can be set. + if ((flags & (SP_END | SP_SUBPAT)) != 0 + || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) { + EMSG2(_(e_invarg2), tv_get_string(&argvars[3])); + goto theend; + } + + // Using 'r' implies 'W', otherwise it doesn't work. + if (flags & SP_REPEAT) { + p_ws = false; + } + + // Optional fifth argument: skip expression. + const typval_T *skip; + if (argvars[3].v_type == VAR_UNKNOWN + || argvars[4].v_type == VAR_UNKNOWN) { + skip = NULL; + } else { + skip = &argvars[4]; + if (skip->v_type != VAR_FUNC + && skip->v_type != VAR_PARTIAL + && skip->v_type != VAR_STRING) { + emsgf(_(e_invarg2), tv_get_string(&argvars[4])); + goto theend; // Type error. + } + if (argvars[5].v_type != VAR_UNKNOWN) { + lnum_stop = tv_get_number_chk(&argvars[5], NULL); + if (lnum_stop < 0) { + emsgf(_(e_invarg2), tv_get_string(&argvars[5])); + goto theend; + } + if (argvars[6].v_type != VAR_UNKNOWN) { + time_limit = tv_get_number_chk(&argvars[6], NULL); + if (time_limit < 0) { + emsgf(_(e_invarg2), tv_get_string(&argvars[6])); + goto theend; + } + } + } + } + + retval = do_searchpair( + (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, skip, + flags, match_pos, lnum_stop, time_limit); + +theend: + p_ws = save_p_ws; + + return retval; +} + +/* + * "searchpair()" function + */ +static void f_searchpair(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = searchpair_cmn(argvars, NULL); +} + +/* + * "searchpairpos()" function + */ +static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + pos_T match_pos; + int lnum = 0; + int col = 0; + + tv_list_alloc_ret(rettv, 2); + + if (searchpair_cmn(argvars, &match_pos) > 0) { + lnum = match_pos.lnum; + col = match_pos.col; + } + + tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum); + tv_list_append_number(rettv->vval.v_list, (varnumber_T)col); +} + +/* + * Search for a start/middle/end thing. + * Used by searchpair(), see its documentation for the details. + * Returns 0 or -1 for no match, + */ +long +do_searchpair( + char_u *spat, // start pattern + char_u *mpat, // middle pattern + char_u *epat, // end pattern + int dir, // BACKWARD or FORWARD + const typval_T *skip, // skip expression + int flags, // SP_SETPCMARK and other SP_ values + pos_T *match_pos, + linenr_T lnum_stop, // stop at this line if not zero + long time_limit // stop after this many msec +) +{ + char_u *save_cpo; + char_u *pat, *pat2 = NULL, *pat3 = NULL; + long retval = 0; + pos_T pos; + pos_T firstpos; + pos_T foundpos; + pos_T save_cursor; + pos_T save_pos; + int n; + int nest = 1; + bool use_skip = false; + int options = SEARCH_KEEP; + proftime_T tm; + size_t pat2_len; + size_t pat3_len; + + // Make 'cpoptions' empty, the 'l' flag should not be used here. + save_cpo = p_cpo; + p_cpo = empty_option; + + // Set the time limit, if there is one. + tm = profile_setlimit(time_limit); + + // Make two search patterns: start/end (pat2, for in nested pairs) and + // start/middle/end (pat3, for the top pair). + pat2_len = STRLEN(spat) + STRLEN(epat) + 17; + pat2 = xmalloc(pat2_len); + pat3_len = STRLEN(spat) + STRLEN(mpat) + STRLEN(epat) + 25; + pat3 = xmalloc(pat3_len); + snprintf((char *)pat2, pat2_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat); + if (*mpat == NUL) { + STRCPY(pat3, pat2); + } else { + snprintf((char *)pat3, pat3_len, + "\\m\\(%s\\m\\)\\|\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat, mpat); + } + if (flags & SP_START) { + options |= SEARCH_START; + } + + if (skip != NULL) { + // Empty string means to not use the skip expression. + if (skip->v_type == VAR_STRING || skip->v_type == VAR_FUNC) { + use_skip = skip->vval.v_string != NULL && *skip->vval.v_string != NUL; + } + } + + save_cursor = curwin->w_cursor; + pos = curwin->w_cursor; + clearpos(&firstpos); + clearpos(&foundpos); + pat = pat3; + for (;; ) { + searchit_arg_T sia; + memset(&sia, 0, sizeof(sia)); + sia.sa_stop_lnum = lnum_stop; + sia.sa_tm = &tm; + + n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L, + options, RE_SEARCH, &sia); + if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) { + // didn't find it or found the first match again: FAIL + break; + } + + if (firstpos.lnum == 0) + firstpos = pos; + if (equalpos(pos, foundpos)) { + // Found the same position again. Can happen with a pattern that + // has "\zs" at the end and searching backwards. Advance one + // character and try again. + if (dir == BACKWARD) { + decl(&pos); + } else { + incl(&pos); + } + } + foundpos = pos; + + // clear the start flag to avoid getting stuck here + options &= ~SEARCH_START; + + // If the skip pattern matches, ignore this match. + if (use_skip) { + save_pos = curwin->w_cursor; + curwin->w_cursor = pos; + bool err = false; + const bool r = eval_expr_to_bool(skip, &err); + curwin->w_cursor = save_pos; + if (err) { + // Evaluating {skip} caused an error, break here. + curwin->w_cursor = save_cursor; + retval = -1; + break; + } + if (r) + continue; + } + + if ((dir == BACKWARD && n == 3) || (dir == FORWARD && n == 2)) { + // Found end when searching backwards or start when searching + // forward: nested pair. + nest++; + pat = pat2; // nested, don't search for middle + } else { + // Found end when searching forward or start when searching + // backward: end of (nested) pair; or found middle in outer pair. + if (--nest == 1) { + pat = pat3; // outer level, search for middle + } + } + + if (nest == 0) { + // Found the match: return matchcount or line number. + if (flags & SP_RETCOUNT) { + retval++; + } else { + retval = pos.lnum; + } + if (flags & SP_SETPCMARK) { + setpcmark(); + } + curwin->w_cursor = pos; + if (!(flags & SP_REPEAT)) + break; + nest = 1; // search for next unmatched + } + } + + if (match_pos != NULL) { + // Store the match cursor position + match_pos->lnum = curwin->w_cursor.lnum; + match_pos->col = curwin->w_cursor.col + 1; + } + + // If 'n' flag is used or search failed: restore cursor position. + if ((flags & SP_NOMOVE) || retval == 0) { + curwin->w_cursor = save_cursor; + } + + xfree(pat2); + xfree(pat3); + if (p_cpo == empty_option) { + p_cpo = save_cpo; + } else { + // Darn, evaluating the {skip} expression changed the value. + free_string_option(save_cpo); + } + + return retval; +} + +/* + * "searchpos()" function + */ +static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + pos_T match_pos; + int flags = 0; + + const int n = search_cmn(argvars, &match_pos, &flags); + + tv_list_alloc_ret(rettv, 2 + (!!(flags & SP_SUBPAT))); + + const int lnum = (n > 0 ? match_pos.lnum : 0); + const int col = (n > 0 ? match_pos.col : 0); + + tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum); + tv_list_append_number(rettv->vval.v_list, (varnumber_T)col); + if (flags & SP_SUBPAT) { + tv_list_append_number(rettv->vval.v_list, (varnumber_T)n); + } +} + +/// "serverlist()" function +static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + size_t n; + char **addrs = server_address_list(&n); + + // Copy addrs into a linked list. + list_T *const l = tv_list_alloc_ret(rettv, n); + for (size_t i = 0; i < n; i++) { + tv_list_append_allocated_string(l, addrs[i]); + } + xfree(addrs); +} + +/// "serverstart()" function +static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; // Address of the new server + + if (check_restricted() || check_secure()) { + return; + } + + char *address; + // If the user supplied an address, use it, otherwise use a temp. + if (argvars[0].v_type != VAR_UNKNOWN) { + if (argvars[0].v_type != VAR_STRING) { + EMSG(_(e_invarg)); + return; + } else { + address = xstrdup(tv_get_string(argvars)); + } + } else { + address = server_address_new(); + } + + int result = server_start(address); + xfree(address); + + if (result != 0) { + EMSG2("Failed to start server: %s", + result > 0 ? "Unknown system error" : uv_strerror(result)); + return; + } + + // Since it's possible server_start adjusted the given {address} (e.g., + // "localhost:" will now have a port), return the final value to the user. + size_t n; + char **addrs = server_address_list(&n); + rettv->vval.v_string = (char_u *)addrs[n - 1]; + + n--; + for (size_t i = 0; i < n; i++) { + xfree(addrs[i]); + } + xfree(addrs); +} + +/// "serverstop()" function +static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_STRING) { + EMSG(_(e_invarg)); + return; + } + + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + if (argvars[0].vval.v_string) { + bool rv = server_stop((char *)argvars[0].vval.v_string); + rettv->vval.v_number = (rv ? 1 : 0); + } +} + +/// "setbufline()" function +static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + linenr_T lnum; + buf_T *buf; + + buf = tv_get_buf(&argvars[0], false); + if (buf == NULL) { + rettv->vval.v_number = 1; // FAIL + } else { + lnum = tv_get_lnum_buf(&argvars[1], buf); + set_buffer_lines(buf, lnum, false, &argvars[2], rettv); + } +} + +/* + * "setbufvar()" function + */ +static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (check_secure() + || !tv_check_str_or_nr(&argvars[0])) { + return; + } + const char *varname = tv_get_string_chk(&argvars[1]); + buf_T *const buf = tv_get_buf(&argvars[0], false); + typval_T *varp = &argvars[2]; + + if (buf != NULL && varname != NULL) { + if (*varname == '&') { + long numval; + bool error = false; + aco_save_T aco; + + // set curbuf to be our buf, temporarily + aucmd_prepbuf(&aco, buf); + + varname++; + numval = tv_get_number_chk(varp, &error); + char nbuf[NUMBUFLEN]; + const char *const strval = tv_get_string_buf_chk(varp, nbuf); + if (!error && strval != NULL) { + set_option_value(varname, numval, strval, OPT_LOCAL); + } + + // reset notion of buffer + aucmd_restbuf(&aco); + } else { + const size_t varname_len = STRLEN(varname); + char *const bufvarname = xmalloc(varname_len + 3); + buf_T *const save_curbuf = curbuf; + curbuf = buf; + memcpy(bufvarname, "b:", 2); + memcpy(bufvarname + 2, varname, varname_len + 1); + set_var(bufvarname, varname_len + 2, varp, true); + xfree(bufvarname); + curbuf = save_curbuf; + } + } +} + +static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + dict_T *d; + dictitem_T *di; + + if (argvars[0].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; + } + + if ((d = argvars[0].vval.v_dict) != NULL) { + char_u *const csearch = (char_u *)tv_dict_get_string(d, "char", false); + if (csearch != NULL) { + if (enc_utf8) { + int pcc[MAX_MCO]; + int c = utfc_ptr2char(csearch, pcc); + set_last_csearch(c, csearch, utfc_ptr2len(csearch)); + } + else + set_last_csearch(PTR2CHAR(csearch), + csearch, utfc_ptr2len(csearch)); + } + + di = tv_dict_find(d, S_LEN("forward")); + if (di != NULL) { + set_csearch_direction(tv_get_number(&di->di_tv) ? FORWARD : BACKWARD); + } + + di = tv_dict_find(d, S_LEN("until")); + if (di != NULL) { + set_csearch_until(!!tv_get_number(&di->di_tv)); + } + } +} + +/* + * "setcmdpos()" function + */ +static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const int pos = (int)tv_get_number(&argvars[0]) - 1; + + if (pos >= 0) { + rettv->vval.v_number = set_cmdline_pos(pos); + } +} + +/// "setenv()" function +static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char namebuf[NUMBUFLEN]; + char valbuf[NUMBUFLEN]; + const char *name = tv_get_string_buf(&argvars[0], namebuf); + + if (argvars[1].v_type == VAR_SPECIAL + && argvars[1].vval.v_special == kSpecialVarNull) { + os_unsetenv(name); + } else { + os_setenv(name, tv_get_string_buf(&argvars[1], valbuf), 1); + } +} + +/// "setfperm({fname}, {mode})" function +static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = 0; + + const char *const fname = tv_get_string_chk(&argvars[0]); + if (fname == NULL) { + return; + } + + char modebuf[NUMBUFLEN]; + const char *const mode_str = tv_get_string_buf_chk(&argvars[1], modebuf); + if (mode_str == NULL) { + return; + } + if (strlen(mode_str) != 9) { + EMSG2(_(e_invarg2), mode_str); + return; + } + + int mask = 1; + int mode = 0; + for (int i = 8; i >= 0; i--) { + if (mode_str[i] != '-') { + mode |= mask; + } + mask = mask << 1; + } + rettv->vval.v_number = os_setperm(fname, mode) == OK; +} + +/* + * "setline()" function + */ +static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + linenr_T lnum = tv_get_lnum(&argvars[0]); + set_buffer_lines(curbuf, lnum, false, &argvars[1], rettv); +} + +/// Create quickfix/location list from VimL values +/// +/// Used by `setqflist()` and `setloclist()` functions. Accepts invalid +/// list_arg, action_arg and what_arg arguments in which case errors out, +/// including VAR_UNKNOWN parameters. +/// +/// @param[in,out] wp Window to create location list for. May be NULL in +/// which case quickfix list will be created. +/// @param[in] list_arg Quickfix list contents. +/// @param[in] action_arg Action to perform: append to an existing list, +/// replace its content or create a new one. +/// @param[in] title_arg New list title. Defaults to caller function name. +/// @param[out] rettv Return value: 0 in case of success, -1 otherwise. +static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv) + FUNC_ATTR_NONNULL_ARG(2, 3) +{ + static char *e_invact = N_("E927: Invalid action: '%s'"); + const char *title = NULL; + int action = ' '; + static int recursive = 0; + rettv->vval.v_number = -1; + dict_T *d = NULL; + + typval_T *list_arg = &args[0]; + if (list_arg->v_type != VAR_LIST) { + EMSG(_(e_listreq)); + return; + } else if (recursive != 0) { + EMSG(_(e_au_recursive)); + return; + } + + typval_T *action_arg = &args[1]; + if (action_arg->v_type == VAR_UNKNOWN) { + // Option argument was not given. + goto skip_args; + } else if (action_arg->v_type != VAR_STRING) { + EMSG(_(e_stringreq)); + return; + } + const char *const act = tv_get_string_chk(action_arg); + if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f') + && act[1] == NUL) { + action = *act; + } else { + EMSG2(_(e_invact), act); + return; + } + + typval_T *title_arg = &args[2]; + if (title_arg->v_type == VAR_UNKNOWN) { + // Option argument was not given. + goto skip_args; + } else if (title_arg->v_type == VAR_STRING) { + title = tv_get_string_chk(title_arg); + if (!title) { + // Type error. Error already printed by tv_get_string_chk(). + return; + } + } else if (title_arg->v_type == VAR_DICT) { + d = title_arg->vval.v_dict; + } else { + EMSG(_(e_dictreq)); + return; + } + +skip_args: + if (!title) { + title = (wp ? ":setloclist()" : ":setqflist()"); + } + + recursive++; + list_T *const l = list_arg->vval.v_list; + if (set_errorlist(wp, l, action, (char_u *)title, d) == OK) { + rettv->vval.v_number = 0; + } + recursive--; +} + +/* + * "setloclist()" function + */ +static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *win; + + rettv->vval.v_number = -1; + + win = find_win_by_nr_or_id(&argvars[0]); + if (win != NULL) { + set_qf_ll_list(win, &argvars[1], rettv); + } +} + +/* + * "setmatches()" function + */ +static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + dict_T *d; + list_T *s = NULL; + + rettv->vval.v_number = -1; + if (argvars[0].v_type != VAR_LIST) { + EMSG(_(e_listreq)); + return; + } + list_T *const l = argvars[0].vval.v_list; + // To some extent make sure that we are dealing with a list from + // "getmatches()". + int li_idx = 0; + TV_LIST_ITER_CONST(l, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT + || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) { + emsgf(_("E474: List item %d is either not a dictionary " + "or an empty one"), li_idx); + return; + } + if (!(tv_dict_find(d, S_LEN("group")) != NULL + && (tv_dict_find(d, S_LEN("pattern")) != NULL + || tv_dict_find(d, S_LEN("pos1")) != NULL) + && tv_dict_find(d, S_LEN("priority")) != NULL + && tv_dict_find(d, S_LEN("id")) != NULL)) { + emsgf(_("E474: List item %d is missing one of the required keys"), + li_idx); + return; + } + li_idx++; + }); + + clear_matches(curwin); + bool match_add_failed = false; + TV_LIST_ITER_CONST(l, li, { + int i = 0; + + d = TV_LIST_ITEM_TV(li)->vval.v_dict; + dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); + if (di == NULL) { + if (s == NULL) { + s = tv_list_alloc(9); + } + + // match from matchaddpos() + for (i = 1; i < 9; i++) { + char buf[30]; // use 30 to avoid compiler warning + snprintf(buf, sizeof(buf), "pos%d", i); + dictitem_T *const pos_di = tv_dict_find(d, buf, -1); + if (pos_di != NULL) { + if (pos_di->di_tv.v_type != VAR_LIST) { + return; + } + + tv_list_append_tv(s, &pos_di->di_tv); + tv_list_ref(s); + } else { + break; + } + } + } + + // Note: there are three number buffers involved: + // - group_buf below. + // - numbuf in tv_dict_get_string(). + // - mybuf in tv_get_string(). + // + // If you change this code make sure that buffers will not get + // accidentally reused. + char group_buf[NUMBUFLEN]; + const char *const group = tv_dict_get_string_buf(d, "group", group_buf); + const int priority = (int)tv_dict_get_number(d, "priority"); + const int id = (int)tv_dict_get_number(d, "id"); + dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal")); + const char *const conceal = (conceal_di != NULL + ? tv_get_string(&conceal_di->di_tv) + : NULL); + if (i == 0) { + if (match_add(curwin, group, + tv_dict_get_string(d, "pattern", false), + priority, id, NULL, conceal) != id) { + match_add_failed = true; + } + } else { + if (match_add(curwin, group, NULL, priority, id, s, conceal) != id) { + match_add_failed = true; + } + tv_list_unref(s); + s = NULL; + } + }); + if (!match_add_failed) { + rettv->vval.v_number = 0; + } +} + +/* + * "setpos()" function + */ +static void f_setpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + pos_T pos; + int fnum; + colnr_T curswant = -1; + + rettv->vval.v_number = -1; + const char *const name = tv_get_string_chk(argvars); + if (name != NULL) { + if (list2fpos(&argvars[1], &pos, &fnum, &curswant) == OK) { + if (pos.col != MAXCOL && --pos.col < 0) { + pos.col = 0; + } + if (name[0] == '.' && name[1] == NUL) { + // set cursor; "fnum" is ignored + curwin->w_cursor = pos; + if (curswant >= 0) { + curwin->w_curswant = curswant - 1; + curwin->w_set_curswant = false; + } + check_cursor(); + rettv->vval.v_number = 0; + } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) { + // set mark + if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) { + rettv->vval.v_number = 0; + } + } else { + EMSG(_(e_invarg)); + } + } + } +} + +/* + * "setqflist()" function + */ +static void f_setqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + set_qf_ll_list(NULL, argvars, rettv); +} + +/* + * "setreg()" function + */ +static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int regname; + bool append = false; + MotionType yank_type; + long block_len; + + block_len = -1; + yank_type = kMTUnknown; + + rettv->vval.v_number = 1; // FAIL is default. + + const char *const strregname = tv_get_string_chk(argvars); + if (strregname == NULL) { + return; // Type error; errmsg already given. + } + regname = (uint8_t)(*strregname); + if (regname == 0 || regname == '@') { + regname = '"'; + } + + bool set_unnamed = false; + if (argvars[2].v_type != VAR_UNKNOWN) { + const char *stropt = tv_get_string_chk(&argvars[2]); + if (stropt == NULL) { + return; // Type error. + } + for (; *stropt != NUL; stropt++) { + switch (*stropt) { + case 'a': case 'A': { // append + append = true; + break; + } + case 'v': case 'c': { // character-wise selection + yank_type = kMTCharWise; + break; + } + case 'V': case 'l': { // line-wise selection + yank_type = kMTLineWise; + break; + } + case 'b': case Ctrl_V: { // block-wise selection + yank_type = kMTBlockWise; + if (ascii_isdigit(stropt[1])) { + stropt++; + block_len = getdigits_long((char_u **)&stropt, true, 0) - 1; + stropt--; + } + break; + } + case 'u': case '"': { // unnamed register + set_unnamed = true; + break; + } + } + } + } + + if (argvars[1].v_type == VAR_LIST) { + list_T *ll = argvars[1].vval.v_list; + // If the list is NULL handle like an empty list. + const int len = tv_list_len(ll); + + // First half: use for pointers to result lines; second half: use for + // pointers to allocated copies. + char **lstval = xmalloc(sizeof(char *) * ((len + 1) * 2)); + const char **curval = (const char **)lstval; + char **allocval = lstval + len + 2; + char **curallocval = allocval; + + TV_LIST_ITER_CONST(ll, li, { + char buf[NUMBUFLEN]; + *curval = tv_get_string_buf_chk(TV_LIST_ITEM_TV(li), buf); + if (*curval == NULL) { + goto free_lstval; + } + if (*curval == buf) { + // Need to make a copy, + // next tv_get_string_buf_chk() will overwrite the string. + *curallocval = xstrdup(*curval); + *curval = *curallocval; + curallocval++; + } + curval++; + }); + *curval++ = NULL; + + write_reg_contents_lst(regname, (char_u **)lstval, append, yank_type, + block_len); + +free_lstval: + while (curallocval > allocval) { + xfree(*--curallocval); + } + xfree(lstval); + } else { + const char *strval = tv_get_string_chk(&argvars[1]); + if (strval == NULL) { + return; + } + write_reg_contents_ex(regname, (const char_u *)strval, STRLEN(strval), + append, yank_type, block_len); + } + rettv->vval.v_number = 0; + + if (set_unnamed) { + // Discard the result. We already handle the error case. + if (op_reg_set_previous(regname)) { } + } +} + +/* + * "settabvar()" function + */ +static void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = 0; + + if (check_secure()) { + return; + } + + tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); + const char *const varname = tv_get_string_chk(&argvars[1]); + typval_T *const varp = &argvars[2]; + + if (varname != NULL && tp != NULL) { + tabpage_T *const save_curtab = curtab; + goto_tabpage_tp(tp, false, false); + + const size_t varname_len = strlen(varname); + char *const tabvarname = xmalloc(varname_len + 3); + memcpy(tabvarname, "t:", 2); + memcpy(tabvarname + 2, varname, varname_len + 1); + set_var(tabvarname, varname_len + 2, varp, true); + xfree(tabvarname); + + // Restore current tabpage. + if (valid_tabpage(save_curtab)) { + goto_tabpage_tp(save_curtab, false, false); + } + } +} + +/* + * "settabwinvar()" function + */ +static void f_settabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + setwinvar(argvars, rettv, 1); +} + +// "settagstack()" function +static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + static char *e_invact2 = N_("E962: Invalid action: '%s'"); + win_T *wp; + dict_T *d; + int action = 'r'; + + rettv->vval.v_number = -1; + + // first argument: window number or id + wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + return; + } + + // second argument: dict with items to set in the tag stack + if (argvars[1].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; + } + d = argvars[1].vval.v_dict; + if (d == NULL) { + return; + } + + // third argument: action - 'a' for append and 'r' for replace. + // default is to replace the stack. + if (argvars[2].v_type == VAR_UNKNOWN) { + action = 'r'; + } else if (argvars[2].v_type == VAR_STRING) { + const char *actstr; + actstr = tv_get_string_chk(&argvars[2]); + if (actstr == NULL) { + return; + } + if ((*actstr == 'r' || *actstr == 'a' || *actstr == 't') + && actstr[1] == NUL) { + action = *actstr; + } else { + EMSG2(_(e_invact2), actstr); + return; + } + } else { + EMSG(_(e_stringreq)); + return; + } + + if (set_tagstack(wp, d, action) == OK) { + rettv->vval.v_number = 0; + } else { + EMSG(_(e_listreq)); + } +} + +/* + * "setwinvar()" function + */ +static void f_setwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + setwinvar(argvars, rettv, 0); +} + +/// f_sha256 - sha256({string}) function +static void f_sha256(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *p = tv_get_string(&argvars[0]); + const char *hash = sha256_bytes((const uint8_t *)p, strlen(p) , NULL, 0); + + // make a copy of the hash (sha256_bytes returns a static buffer) + rettv->vval.v_string = (char_u *)xstrdup(hash); + rettv->v_type = VAR_STRING; +} + +/* + * "shellescape({string})" function + */ +static void f_shellescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const bool do_special = non_zero_arg(&argvars[1]); + + rettv->vval.v_string = vim_strsave_shellescape( + (const char_u *)tv_get_string(&argvars[0]), do_special, do_special); + rettv->v_type = VAR_STRING; +} + +/* + * shiftwidth() function + */ +static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = get_sw_value(curbuf); +} + +/// "sign_define()" function +static void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *name; + dict_T *dict; + char *icon = NULL; + char *linehl = NULL; + char *text = NULL; + char *texthl = NULL; + char *numhl = NULL; + + rettv->vval.v_number = -1; + + name = tv_get_string_chk(&argvars[0]); + if (name == NULL) { + return; + } + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (argvars[1].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; + } + + // sign attributes + dict = argvars[1].vval.v_dict; + if (tv_dict_find(dict, "icon", -1) != NULL) { + icon = tv_dict_get_string(dict, "icon", true); + } + if (tv_dict_find(dict, "linehl", -1) != NULL) { + linehl = tv_dict_get_string(dict, "linehl", true); + } + if (tv_dict_find(dict, "text", -1) != NULL) { + text = tv_dict_get_string(dict, "text", true); + } + if (tv_dict_find(dict, "texthl", -1) != NULL) { + texthl = tv_dict_get_string(dict, "texthl", true); + } + if (tv_dict_find(dict, "numhl", -1) != NULL) { + numhl = tv_dict_get_string(dict, "numhl", true); + } + } + + if (sign_define_by_name((char_u *)name, (char_u *)icon, (char_u *)linehl, + (char_u *)text, (char_u *)texthl, (char_u *)numhl) + == OK) { + rettv->vval.v_number = 0; + } + + xfree(icon); + xfree(linehl); + xfree(text); + xfree(texthl); + xfree(numhl); +} + +/// "sign_getdefined()" function +static void f_sign_getdefined(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *name = NULL; + + tv_list_alloc_ret(rettv, 0); + + if (argvars[0].v_type != VAR_UNKNOWN) { + name = tv_get_string(&argvars[0]); + } + + sign_getlist((const char_u *)name, rettv->vval.v_list); +} + +/// "sign_getplaced()" function +static void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + buf_T *buf = NULL; + dict_T *dict; + dictitem_T *di; + linenr_T lnum = 0; + int sign_id = 0; + const char *group = NULL; + bool notanum = false; + + tv_list_alloc_ret(rettv, 0); + + if (argvars[0].v_type != VAR_UNKNOWN) { + // get signs placed in the specified buffer + buf = get_buf_arg(&argvars[0]); + if (buf == NULL) { + return; + } + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (argvars[1].v_type != VAR_DICT + || ((dict = argvars[1].vval.v_dict) == NULL)) { + EMSG(_(e_dictreq)); + return; + } + if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) { + // get signs placed at this line + lnum = (linenr_T)tv_get_number_chk(&di->di_tv, ¬anum); + if (notanum) { + return; + } + (void)lnum; + lnum = tv_get_lnum(&di->di_tv); + } + if ((di = tv_dict_find(dict, "id", -1)) != NULL) { + // get sign placed with this identifier + sign_id = (int)tv_get_number_chk(&di->di_tv, ¬anum); + if (notanum) { + return; + } + } + if ((di = tv_dict_find(dict, "group", -1)) != NULL) { + group = tv_get_string_chk(&di->di_tv); + if (group == NULL) { + return; + } + if (*group == '\0') { // empty string means global group + group = NULL; + } + } + } + } + + sign_get_placed(buf, lnum, sign_id, (const char_u *)group, + rettv->vval.v_list); +} + +/// "sign_jump()" function +static void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int sign_id; + char *sign_group = NULL; + buf_T *buf; + bool notanum = false; + + rettv->vval.v_number = -1; + + // Sign identifer + sign_id = (int)tv_get_number_chk(&argvars[0], ¬anum); + if (notanum) { + return; + } + if (sign_id <= 0) { + EMSG(_(e_invarg)); + return; + } + + // Sign group + const char * sign_group_chk = tv_get_string_chk(&argvars[1]); + if (sign_group_chk == NULL) { + return; + } + if (sign_group_chk[0] == '\0') { + sign_group = NULL; // global sign group + } else { + sign_group = xstrdup(sign_group_chk); + } + + // Buffer to place the sign + buf = get_buf_arg(&argvars[2]); + if (buf == NULL) { + goto cleanup; + } + + rettv->vval.v_number = sign_jump(sign_id, (char_u *)sign_group, buf); + +cleanup: + xfree(sign_group); +} + +/// "sign_place()" function +static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int sign_id; + char_u *group = NULL; + const char *sign_name; + buf_T *buf; + dict_T *dict; + dictitem_T *di; + linenr_T lnum = 0; + int prio = SIGN_DEF_PRIO; + bool notanum = false; + + rettv->vval.v_number = -1; + + // Sign identifer + sign_id = (int)tv_get_number_chk(&argvars[0], ¬anum); + if (notanum) { + return; + } + if (sign_id < 0) { + EMSG(_(e_invarg)); + return; + } + + // Sign group + const char *group_chk = tv_get_string_chk(&argvars[1]); + if (group_chk == NULL) { + return; + } + if (group_chk[0] == '\0') { + group = NULL; // global sign group + } else { + group = vim_strsave((const char_u *)group_chk); + } + + // Sign name + sign_name = tv_get_string_chk(&argvars[2]); + if (sign_name == NULL) { + goto cleanup; + } + + // Buffer to place the sign + buf = get_buf_arg(&argvars[3]); + if (buf == NULL) { + goto cleanup; + } + + if (argvars[4].v_type != VAR_UNKNOWN) { + if (argvars[4].v_type != VAR_DICT + || ((dict = argvars[4].vval.v_dict) == NULL)) { + EMSG(_(e_dictreq)); + goto cleanup; + } + + // Line number where the sign is to be placed + if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) { + lnum = (linenr_T)tv_get_number_chk(&di->di_tv, ¬anum); + if (notanum) { + goto cleanup; + } + (void)lnum; + lnum = tv_get_lnum(&di->di_tv); + } + if ((di = tv_dict_find(dict, "priority", -1)) != NULL) { + // Sign priority + prio = (int)tv_get_number_chk(&di->di_tv, ¬anum); + if (notanum) { + goto cleanup; + } + } + } + + if (sign_place(&sign_id, group, (const char_u *)sign_name, buf, lnum, prio) + == OK) { + rettv->vval.v_number = sign_id; + } + +cleanup: + xfree(group); +} + +/// "sign_undefine()" function +static void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *name; + + rettv->vval.v_number = -1; + + if (argvars[0].v_type == VAR_UNKNOWN) { + // Free all the signs + free_signs(); + rettv->vval.v_number = 0; + } else { + // Free only the specified sign + name = tv_get_string_chk(&argvars[0]); + if (name == NULL) { + return; + } + + if (sign_undefine_by_name((const char_u *)name) == OK) { + rettv->vval.v_number = 0; + } + } +} + +/// "sign_unplace()" function +static void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + dict_T *dict; + dictitem_T *di; + int sign_id = 0; + buf_T *buf = NULL; + char_u *group = NULL; + + rettv->vval.v_number = -1; + + if (argvars[0].v_type != VAR_STRING) { + EMSG(_(e_invarg)); + return; + } + + const char *group_chk = tv_get_string(&argvars[0]); + if (group_chk[0] == '\0') { + group = NULL; // global sign group + } else { + group = vim_strsave((const char_u *)group_chk); + } + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (argvars[1].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + goto cleanup; + } + dict = argvars[1].vval.v_dict; + + if ((di = tv_dict_find(dict, "buffer", -1)) != NULL) { + buf = get_buf_arg(&di->di_tv); + if (buf == NULL) { + goto cleanup; + } + } + if (tv_dict_find(dict, "id", -1) != NULL) { + sign_id = tv_dict_get_number(dict, "id"); + } + } + + if (buf == NULL) { + // Delete the sign in all the buffers + FOR_ALL_BUFFERS(cbuf) { + if (sign_unplace(sign_id, group, cbuf, 0) == OK) { + rettv->vval.v_number = 0; + } + } + } else { + if (sign_unplace(sign_id, group, buf, 0) == OK) { + rettv->vval.v_number = 0; + } + } + +cleanup: + xfree(group); +} + +/* + * "simplify()" function + */ +static void f_simplify(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const p = tv_get_string(&argvars[0]); + rettv->vval.v_string = (char_u *)xstrdup(p); + simplify_filename(rettv->vval.v_string); // Simplify in place. + rettv->v_type = VAR_STRING; +} + +/// "sockconnect()" function +static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) { + EMSG(_(e_invarg)); + return; + } + if (argvars[2].v_type != VAR_DICT && argvars[2].v_type != VAR_UNKNOWN) { + // Wrong argument types + EMSG2(_(e_invarg2), "expected dictionary"); + return; + } + + const char *mode = tv_get_string(&argvars[0]); + const char *address = tv_get_string(&argvars[1]); + + bool tcp; + if (strcmp(mode, "tcp") == 0) { + tcp = true; + } else if (strcmp(mode, "pipe") == 0) { + tcp = false; + } else { + EMSG2(_(e_invarg2), "invalid mode"); + return; + } + + bool rpc = false; + CallbackReader on_data = CALLBACK_READER_INIT; + if (argvars[2].v_type == VAR_DICT) { + dict_T *opts = argvars[2].vval.v_dict; + rpc = tv_dict_get_number(opts, "rpc") != 0; + + if (!tv_dict_get_callback(opts, S_LEN("on_data"), &on_data.cb)) { + return; + } + on_data.buffered = tv_dict_get_number(opts, "data_buffered"); + if (on_data.buffered && on_data.cb.type == kCallbackNone) { + on_data.self = opts; + } + } + + const char *error = NULL; + uint64_t id = channel_connect(tcp, address, rpc, on_data, 50, &error); + + if (error) { + EMSG2(_("connection failed: %s"), error); + } + + rettv->vval.v_number = (varnumber_T)id; + rettv->v_type = VAR_NUMBER; +} + +/// struct storing information about current sort +typedef struct { + int item_compare_ic; + bool item_compare_numeric; + bool item_compare_numbers; + bool item_compare_float; + const char *item_compare_func; + partial_T *item_compare_partial; + dict_T *item_compare_selfdict; + bool item_compare_func_err; +} sortinfo_T; +static sortinfo_T *sortinfo = NULL; + +#define ITEM_COMPARE_FAIL 999 + +/* + * Compare functions for f_sort() and f_uniq() below. + */ +static int item_compare(const void *s1, const void *s2, bool keep_zero) +{ + ListSortItem *const si1 = (ListSortItem *)s1; + ListSortItem *const si2 = (ListSortItem *)s2; + + typval_T *const tv1 = TV_LIST_ITEM_TV(si1->item); + typval_T *const tv2 = TV_LIST_ITEM_TV(si2->item); + + int res; + + if (sortinfo->item_compare_numbers) { + const varnumber_T v1 = tv_get_number(tv1); + const varnumber_T v2 = tv_get_number(tv2); + + res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1; + goto item_compare_end; + } + + if (sortinfo->item_compare_float) { + const float_T v1 = tv_get_float(tv1); + const float_T v2 = tv_get_float(tv2); + + res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1; + goto item_compare_end; + } + + char *tofree1 = NULL; + char *tofree2 = NULL; + char *p1; + char *p2; + + // encode_tv2string() puts quotes around a string and allocates memory. Don't + // do that for string variables. Use a single quote when comparing with + // a non-string to do what the docs promise. + if (tv1->v_type == VAR_STRING) { + if (tv2->v_type != VAR_STRING || sortinfo->item_compare_numeric) { + p1 = "'"; + } else { + p1 = (char *)tv1->vval.v_string; + } + } else { + tofree1 = p1 = encode_tv2string(tv1, NULL); + } + if (tv2->v_type == VAR_STRING) { + if (tv1->v_type != VAR_STRING || sortinfo->item_compare_numeric) { + p2 = "'"; + } else { + p2 = (char *)tv2->vval.v_string; + } + } else { + tofree2 = p2 = encode_tv2string(tv2, NULL); + } + if (p1 == NULL) { + p1 = ""; + } + if (p2 == NULL) { + p2 = ""; + } + if (!sortinfo->item_compare_numeric) { + if (sortinfo->item_compare_ic) { + res = STRICMP(p1, p2); + } else { + res = STRCMP(p1, p2); + } + } else { + double n1, n2; + n1 = strtod(p1, &p1); + n2 = strtod(p2, &p2); + res = n1 == n2 ? 0 : n1 > n2 ? 1 : -1; + } + + xfree(tofree1); + xfree(tofree2); + +item_compare_end: + // When the result would be zero, compare the item indexes. Makes the + // sort stable. + if (res == 0 && !keep_zero) { + // WARNING: When using uniq si1 and si2 are actually listitem_T **, no + // indexes are there. + res = si1->idx > si2->idx ? 1 : -1; + } + return res; +} + +static int item_compare_keeping_zero(const void *s1, const void *s2) +{ + return item_compare(s1, s2, true); +} + +static int item_compare_not_keeping_zero(const void *s1, const void *s2) +{ + return item_compare(s1, s2, false); +} + +static int item_compare2(const void *s1, const void *s2, bool keep_zero) +{ + ListSortItem *si1, *si2; + int res; + typval_T rettv; + typval_T argv[3]; + int dummy; + const char *func_name; + partial_T *partial = sortinfo->item_compare_partial; + + // shortcut after failure in previous call; compare all items equal + if (sortinfo->item_compare_func_err) { + return 0; + } + + si1 = (ListSortItem *)s1; + si2 = (ListSortItem *)s2; + + if (partial == NULL) { + func_name = sortinfo->item_compare_func; + } else { + func_name = (const char *)partial_name(partial); + } + + // Copy the values. This is needed to be able to set v_lock to VAR_FIXED + // in the copy without changing the original list items. + tv_copy(TV_LIST_ITEM_TV(si1->item), &argv[0]); + tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]); + + rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this + res = call_func((const char_u *)func_name, + (int)STRLEN(func_name), + &rettv, 2, argv, NULL, 0L, 0L, &dummy, true, + partial, sortinfo->item_compare_selfdict); + tv_clear(&argv[0]); + tv_clear(&argv[1]); + + if (res == FAIL) { + res = ITEM_COMPARE_FAIL; + } else { + res = tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err); + } + if (sortinfo->item_compare_func_err) { + res = ITEM_COMPARE_FAIL; // return value has wrong type + } + tv_clear(&rettv); + + // When the result would be zero, compare the pointers themselves. Makes + // the sort stable. + if (res == 0 && !keep_zero) { + // WARNING: When using uniq si1 and si2 are actually listitem_T **, no + // indexes are there. + res = si1->idx > si2->idx ? 1 : -1; + } + + return res; +} + +static int item_compare2_keeping_zero(const void *s1, const void *s2) +{ + return item_compare2(s1, s2, true); +} + +static int item_compare2_not_keeping_zero(const void *s1, const void *s2) +{ + return item_compare2(s1, s2, false); +} + +/* + * "sort({list})" function + */ +static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) +{ + ListSortItem *ptrs; + long len; + long i; + + // Pointer to current info struct used in compare function. Save and restore + // the current one for nested calls. + sortinfo_T info; + sortinfo_T *old_sortinfo = sortinfo; + sortinfo = &info; + + const char *const arg_errmsg = (sort + ? N_("sort() argument") + : N_("uniq() argument")); + + if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listarg), sort ? "sort()" : "uniq()"); + } else { + list_T *const l = argvars[0].vval.v_list; + if (tv_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { + goto theend; + } + tv_list_set_ret(rettv, l); + + len = tv_list_len(l); + if (len <= 1) { + goto theend; // short list sorts pretty quickly + } + + info.item_compare_ic = false; + info.item_compare_numeric = false; + info.item_compare_numbers = false; + info.item_compare_float = false; + info.item_compare_func = NULL; + info.item_compare_partial = NULL; + info.item_compare_selfdict = NULL; + + if (argvars[1].v_type != VAR_UNKNOWN) { + // optional second argument: {func} + if (argvars[1].v_type == VAR_FUNC) { + info.item_compare_func = (const char *)argvars[1].vval.v_string; + } else if (argvars[1].v_type == VAR_PARTIAL) { + info.item_compare_partial = argvars[1].vval.v_partial; + } else { + bool error = false; + + i = tv_get_number_chk(&argvars[1], &error); + if (error) { + goto theend; // type error; errmsg already given + } + if (i == 1) { + info.item_compare_ic = true; + } else if (argvars[1].v_type != VAR_NUMBER) { + info.item_compare_func = tv_get_string(&argvars[1]); + } else if (i != 0) { + EMSG(_(e_invarg)); + goto theend; + } + if (info.item_compare_func != NULL) { + if (*info.item_compare_func == NUL) { + // empty string means default sort + info.item_compare_func = NULL; + } else if (strcmp(info.item_compare_func, "n") == 0) { + info.item_compare_func = NULL; + info.item_compare_numeric = true; + } else if (strcmp(info.item_compare_func, "N") == 0) { + info.item_compare_func = NULL; + info.item_compare_numbers = true; + } else if (strcmp(info.item_compare_func, "f") == 0) { + info.item_compare_func = NULL; + info.item_compare_float = true; + } else if (strcmp(info.item_compare_func, "i") == 0) { + info.item_compare_func = NULL; + info.item_compare_ic = true; + } + } + } + + if (argvars[2].v_type != VAR_UNKNOWN) { + // optional third argument: {dict} + if (argvars[2].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + goto theend; + } + info.item_compare_selfdict = argvars[2].vval.v_dict; + } + } + + // Make an array with each entry pointing to an item in the List. + ptrs = xmalloc((size_t)(len * sizeof(ListSortItem))); + + if (sort) { + info.item_compare_func_err = false; + tv_list_item_sort(l, ptrs, + ((info.item_compare_func == NULL + && info.item_compare_partial == NULL) + ? item_compare_not_keeping_zero + : item_compare2_not_keeping_zero), + &info.item_compare_func_err); + if (info.item_compare_func_err) { + EMSG(_("E702: Sort compare function failed")); + } + } else { + ListSorter item_compare_func_ptr; + + // f_uniq(): ptrs will be a stack of items to remove. + info.item_compare_func_err = false; + if (info.item_compare_func != NULL + || info.item_compare_partial != NULL) { + item_compare_func_ptr = item_compare2_keeping_zero; + } else { + item_compare_func_ptr = item_compare_keeping_zero; + } + + int idx = 0; + for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l)) + ; li != NULL;) { + listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); + if (item_compare_func_ptr(&prev_li, &li) == 0) { + if (info.item_compare_func_err) { // -V547 + EMSG(_("E882: Uniq compare function failed")); + break; + } + li = tv_list_item_remove(l, li); + } else { + idx++; + li = TV_LIST_ITEM_NEXT(l, li); + } + } + } + + xfree(ptrs); + } + +theend: + sortinfo = old_sortinfo; +} + +/// "sort"({list})" function +static void f_sort(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + do_sort_uniq(argvars, rettv, true); +} + +/// "stdioopen()" function +static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_DICT) { + EMSG(_(e_invarg)); + return; + } + + + bool rpc = false; + CallbackReader on_stdin = CALLBACK_READER_INIT; + dict_T *opts = argvars[0].vval.v_dict; + rpc = tv_dict_get_number(opts, "rpc") != 0; + + if (!tv_dict_get_callback(opts, S_LEN("on_stdin"), &on_stdin.cb)) { + return; + } + on_stdin.buffered = tv_dict_get_number(opts, "stdin_buffered"); + if (on_stdin.buffered && on_stdin.cb.type == kCallbackNone) { + on_stdin.self = opts; + } + + const char *error; + uint64_t id = channel_from_stdio(rpc, on_stdin, &error); + if (!id) { + EMSG2(e_stdiochan2, error); + } + + + rettv->vval.v_number = (varnumber_T)id; + rettv->v_type = VAR_NUMBER; +} + +/// "uniq({list})" function +static void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + do_sort_uniq(argvars, rettv, false); +} + +// "reltimefloat()" function +static void f_reltimefloat(typval_T *argvars , typval_T *rettv, FunPtr fptr) + FUNC_ATTR_NONNULL_ALL +{ + proftime_T tm; + + rettv->v_type = VAR_FLOAT; + rettv->vval.v_float = 0; + if (list2proftime(&argvars[0], &tm) == OK) { + rettv->vval.v_float = (float_T)profile_signed(tm) / 1000000000.0; + } +} + +/* + * "soundfold({word})" function + */ +static void f_soundfold(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + const char *const s = tv_get_string(&argvars[0]); + rettv->vval.v_string = (char_u *)eval_soundfold(s); +} + +/* + * "spellbadword()" function + */ +static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *word = ""; + hlf_T attr = HLF_COUNT; + size_t len = 0; + + if (argvars[0].v_type == VAR_UNKNOWN) { + // Find the start and length of the badly spelled word. + len = spell_move_to(curwin, FORWARD, true, true, &attr); + if (len != 0) { + word = (char *)get_cursor_pos_ptr(); + curwin->w_set_curswant = true; + } + } else if (curwin->w_p_spell && *curbuf->b_s.b_p_spl != NUL) { + const char *str = tv_get_string_chk(&argvars[0]); + int capcol = -1; + + if (str != NULL) { + // Check the argument for spelling. + while (*str != NUL) { + len = spell_check(curwin, (char_u *)str, &attr, &capcol, false); + if (attr != HLF_COUNT) { + word = str; + break; + } + str += len; + capcol -= len; + len = 0; + } + } + } + + assert(len <= INT_MAX); + tv_list_alloc_ret(rettv, 2); + tv_list_append_string(rettv->vval.v_list, word, len); + tv_list_append_string(rettv->vval.v_list, + (attr == HLF_SPB ? "bad" + : attr == HLF_SPR ? "rare" + : attr == HLF_SPL ? "local" + : attr == HLF_SPC ? "caps" + : NULL), -1); +} + +/* + * "spellsuggest()" function + */ +static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + bool typeerr = false; + int maxcount; + garray_T ga = GA_EMPTY_INIT_VALUE; + bool need_capital = false; + + if (curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) { + const char *const str = tv_get_string(&argvars[0]); + if (argvars[1].v_type != VAR_UNKNOWN) { + maxcount = tv_get_number_chk(&argvars[1], &typeerr); + if (maxcount <= 0) { + goto f_spellsuggest_return; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + need_capital = tv_get_number_chk(&argvars[2], &typeerr); + if (typeerr) { + goto f_spellsuggest_return; + } + } + } else { + maxcount = 25; + } + + spell_suggest_list(&ga, (char_u *)str, maxcount, need_capital, false); + } + +f_spellsuggest_return: + tv_list_alloc_ret(rettv, (ptrdiff_t)ga.ga_len); + for (int i = 0; i < ga.ga_len; i++) { + char *const p = ((char **)ga.ga_data)[i]; + tv_list_append_allocated_string(rettv->vval.v_list, p); + } + ga_clear(&ga); +} + +static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char_u *save_cpo; + int match; + colnr_T col = 0; + bool keepempty = false; + bool typeerr = false; + + // Make 'cpoptions' empty, the 'l' flag should not be used here. + save_cpo = p_cpo; + p_cpo = (char_u *)""; + + const char *str = tv_get_string(&argvars[0]); + const char *pat = NULL; + char patbuf[NUMBUFLEN]; + if (argvars[1].v_type != VAR_UNKNOWN) { + pat = tv_get_string_buf_chk(&argvars[1], patbuf); + if (pat == NULL) { + typeerr = true; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + keepempty = (bool)tv_get_number_chk(&argvars[2], &typeerr); + } + } + if (pat == NULL || *pat == NUL) { + pat = "[\\x01- ]\\+"; + } + + tv_list_alloc_ret(rettv, kListLenMayKnow); + + if (typeerr) { + return; + } + + regmatch_T regmatch = { + .regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING), + .startp = { NULL }, + .endp = { NULL }, + .rm_ic = false, + }; + if (regmatch.regprog != NULL) { + while (*str != NUL || keepempty) { + if (*str == NUL) { + match = false; // Empty item at the end. + } else { + match = vim_regexec_nl(®match, (char_u *)str, col); + } + const char *end; + if (match) { + end = (const char *)regmatch.startp[0]; + } else { + end = str + strlen(str); + } + if (keepempty || end > str || (tv_list_len(rettv->vval.v_list) > 0 + && *str != NUL + && match + && end < (const char *)regmatch.endp[0])) { + tv_list_append_string(rettv->vval.v_list, str, end - str); + } + if (!match) { + break; + } + // Advance to just after the match. + if (regmatch.endp[0] > (char_u *)str) { + col = 0; + } else { + // Don't get stuck at the same match. + col = (*mb_ptr2len)(regmatch.endp[0]); + } + str = (const char *)regmatch.endp[0]; + } + + vim_regfree(regmatch.regprog); + } + + p_cpo = save_cpo; +} + +/// "stdpath(type)" function +static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + const char *const p = tv_get_string_chk(&argvars[0]); + if (p == NULL) { + return; // Type error; errmsg already given. + } + + if (strequal(p, "config")) { + rettv->vval.v_string = (char_u *)get_xdg_home(kXDGConfigHome); + } else if (strequal(p, "data")) { + rettv->vval.v_string = (char_u *)get_xdg_home(kXDGDataHome); + } else if (strequal(p, "cache")) { + rettv->vval.v_string = (char_u *)get_xdg_home(kXDGCacheHome); + } else if (strequal(p, "config_dirs")) { + get_xdg_var_list(kXDGConfigDirs, rettv); + } else if (strequal(p, "data_dirs")) { + get_xdg_var_list(kXDGDataDirs, rettv); + } else { + EMSG2(_("E6100: \"%s\" is not a valid stdpath"), p); + } +} + +/* + * "str2float()" function + */ +static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char_u *p = skipwhite((const char_u *)tv_get_string(&argvars[0])); + bool isneg = (*p == '-'); + + if (*p == '+' || *p == '-') { + p = skipwhite(p + 1); + } + (void)string2float((char *)p, &rettv->vval.v_float); + if (isneg) { + rettv->vval.v_float *= -1; + } + rettv->v_type = VAR_FLOAT; +} + +// "str2list()" function +static void f_str2list(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_list_alloc_ret(rettv, kListLenUnknown); + const char_u *p = (const char_u *)tv_get_string(&argvars[0]); + + for (; *p != NUL; p += utf_ptr2len(p)) { + tv_list_append_number(rettv->vval.v_list, utf_ptr2char(p)); + } +} + +// "str2nr()" function +static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int base = 10; + varnumber_T n; + int what; + + if (argvars[1].v_type != VAR_UNKNOWN) { + base = tv_get_number(&argvars[1]); + if (base != 2 && base != 8 && base != 10 && base != 16) { + EMSG(_(e_invarg)); + return; + } + } + + char_u *p = skipwhite((const char_u *)tv_get_string(&argvars[0])); + bool isneg = (*p == '-'); + if (*p == '+' || *p == '-') { + p = skipwhite(p + 1); + } + switch (base) { + case 2: { + what = STR2NR_BIN | STR2NR_FORCE; + break; + } + case 8: { + what = STR2NR_OCT | STR2NR_FORCE; + break; + } + case 16: { + what = STR2NR_HEX | STR2NR_FORCE; + break; + } + default: { + what = 0; + } + } + vim_str2nr(p, NULL, NULL, what, &n, NULL, 0); + if (isneg) { + rettv->vval.v_number = -n; + } else { + rettv->vval.v_number = n; + } +} + +/* + * "strftime({format}[, {time}])" function + */ +static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + time_t seconds; + + rettv->v_type = VAR_STRING; + + char *p = (char *)tv_get_string(&argvars[0]); + if (argvars[1].v_type == VAR_UNKNOWN) { + seconds = time(NULL); + } else { + seconds = (time_t)tv_get_number(&argvars[1]); + } + + struct tm curtime; + struct tm *curtime_ptr = os_localtime_r(&seconds, &curtime); + // MSVC returns NULL for an invalid value of seconds. + if (curtime_ptr == NULL) { + rettv->vval.v_string = vim_strsave((char_u *)_("(Invalid)")); + } else { + vimconv_T conv; + char_u *enc; + + conv.vc_type = CONV_NONE; + enc = enc_locale(); + convert_setup(&conv, p_enc, enc); + if (conv.vc_type != CONV_NONE) { + p = (char *)string_convert(&conv, (char_u *)p, NULL); + } + char result_buf[256]; + if (p != NULL) { + (void)strftime(result_buf, sizeof(result_buf), p, curtime_ptr); + } else { + result_buf[0] = NUL; + } + + if (conv.vc_type != CONV_NONE) { + xfree(p); + } + convert_setup(&conv, enc, p_enc); + if (conv.vc_type != CONV_NONE) { + rettv->vval.v_string = string_convert(&conv, (char_u *)result_buf, NULL); + } else { + rettv->vval.v_string = (char_u *)xstrdup(result_buf); + } + + // Release conversion descriptors. + convert_setup(&conv, NULL, NULL); + xfree(enc); + } +} + +// "strgetchar()" function +static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = -1; + + const char *const str = tv_get_string_chk(&argvars[0]); + if (str == NULL) { + return; + } + bool error = false; + varnumber_T charidx = tv_get_number_chk(&argvars[1], &error); + if (error) { + return; + } + + const size_t len = STRLEN(str); + size_t byteidx = 0; + + while (charidx >= 0 && byteidx < len) { + if (charidx == 0) { + rettv->vval.v_number = utf_ptr2char((const char_u *)str + byteidx); + break; + } + charidx--; + byteidx += MB_CPTR2LEN((const char_u *)str + byteidx); + } +} + +/* + * "stridx()" function + */ +static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = -1; + + char buf[NUMBUFLEN]; + const char *const needle = tv_get_string_chk(&argvars[1]); + const char *haystack = tv_get_string_buf_chk(&argvars[0], buf); + const char *const haystack_start = haystack; + if (needle == NULL || haystack == NULL) { + return; // Type error; errmsg already given. + } + + if (argvars[2].v_type != VAR_UNKNOWN) { + bool error = false; + + const ptrdiff_t start_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], + &error); + if (error || start_idx >= (ptrdiff_t)strlen(haystack)) { + return; + } + if (start_idx >= 0) { + haystack += start_idx; + } + } + + const char *pos = strstr(haystack, needle); + if (pos != NULL) { + rettv->vval.v_number = (varnumber_T)(pos - haystack_start); + } +} + +/* + * "string()" function + */ +void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)encode_tv2string(&argvars[0], NULL); +} + +/* + * "strlen()" function + */ +static void f_strlen(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0])); +} + +/* + * "strchars()" function + */ +static void f_strchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *s = tv_get_string(&argvars[0]); + int skipcc = 0; + varnumber_T len = 0; + int (*func_mb_ptr2char_adv)(const char_u **pp); + + if (argvars[1].v_type != VAR_UNKNOWN) { + skipcc = tv_get_number_chk(&argvars[1], NULL); + } + if (skipcc < 0 || skipcc > 1) { + EMSG(_(e_invarg)); + } else { + func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv; + while (*s != NUL) { + func_mb_ptr2char_adv((const char_u **)&s); + len++; + } + rettv->vval.v_number = len; + } +} + +/* + * "strdisplaywidth()" function + */ +static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const s = tv_get_string(&argvars[0]); + int col = 0; + + if (argvars[1].v_type != VAR_UNKNOWN) { + col = tv_get_number(&argvars[1]); + } + + rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char_u *)s) - col); +} + +/* + * "strwidth()" function + */ +static void f_strwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const s = tv_get_string(&argvars[0]); + + rettv->vval.v_number = (varnumber_T)mb_string2cells((const char_u *)s); +} + +// "strcharpart()" function +static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const p = tv_get_string(&argvars[0]); + const size_t slen = STRLEN(p); + + int nbyte = 0; + bool error = false; + varnumber_T nchar = tv_get_number_chk(&argvars[1], &error); + if (!error) { + if (nchar > 0) { + while (nchar > 0 && (size_t)nbyte < slen) { + nbyte += MB_CPTR2LEN((const char_u *)p + nbyte); + nchar--; + } + } else { + nbyte = nchar; + } + } + int len = 0; + if (argvars[2].v_type != VAR_UNKNOWN) { + int charlen = tv_get_number(&argvars[2]); + while (charlen > 0 && nbyte + len < (int)slen) { + int off = nbyte + len; + + if (off < 0) { + len += 1; + } else { + len += (size_t)MB_CPTR2LEN((const char_u *)p + off); + } + charlen--; + } + } else { + len = slen - nbyte; // default: all bytes that are available. + } + + // Only return the overlap between the specified part and the actual + // string. + if (nbyte < 0) { + len += nbyte; + nbyte = 0; + } else if ((size_t)nbyte > slen) { + nbyte = slen; + } + if (len < 0) { + len = 0; + } else if (nbyte + len > (int)slen) { + len = slen - nbyte; + } + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)xstrndup(p + nbyte, (size_t)len); +} + +/* + * "strpart()" function + */ +static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + bool error = false; + + const char *const p = tv_get_string(&argvars[0]); + const size_t slen = strlen(p); + + varnumber_T n = tv_get_number_chk(&argvars[1], &error); + varnumber_T len; + if (error) { + len = 0; + } else if (argvars[2].v_type != VAR_UNKNOWN) { + len = tv_get_number(&argvars[2]); + } else { + len = slen - n; // Default len: all bytes that are available. + } + + // Only return the overlap between the specified part and the actual + // string. + if (n < 0) { + len += n; + n = 0; + } else if (n > (varnumber_T)slen) { + n = slen; + } + if (len < 0) { + len = 0; + } else if (n + len > (varnumber_T)slen) { + len = slen - n; + } + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)xmemdupz(p + n, (size_t)len); +} + +/* + * "strridx()" function + */ +static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char buf[NUMBUFLEN]; + const char *const needle = tv_get_string_chk(&argvars[1]); + const char *const haystack = tv_get_string_buf_chk(&argvars[0], buf); + + rettv->vval.v_number = -1; + if (needle == NULL || haystack == NULL) { + return; // Type error; errmsg already given. + } + + const size_t haystack_len = STRLEN(haystack); + ptrdiff_t end_idx; + if (argvars[2].v_type != VAR_UNKNOWN) { + // Third argument: upper limit for index. + end_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], NULL); + if (end_idx < 0) { + return; // Can never find a match. + } + } else { + end_idx = (ptrdiff_t)haystack_len; + } + + const char *lastmatch = NULL; + if (*needle == NUL) { + // Empty string matches past the end. + lastmatch = haystack + end_idx; + } else { + for (const char *rest = haystack; *rest != NUL; rest++) { + rest = strstr(rest, needle); + if (rest == NULL || rest > haystack + end_idx) { + break; + } + lastmatch = rest; + } + } + + if (lastmatch != NULL) { + rettv->vval.v_number = (varnumber_T)(lastmatch - haystack); + } +} + +/* + * "strtrans()" function + */ +static void f_strtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)transstr(tv_get_string(&argvars[0])); +} + +/* + * "submatch()" function + */ +static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + bool error = false; + int no = (int)tv_get_number_chk(&argvars[0], &error); + if (error) { + return; + } + + if (no < 0 || no >= NSUBEXP) { + emsgf(_("E935: invalid submatch number: %d"), no); + return; + } + int retList = 0; + + if (argvars[1].v_type != VAR_UNKNOWN) { + retList = tv_get_number_chk(&argvars[1], &error); + if (error) { + return; + } + } + + if (retList == 0) { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = reg_submatch(no); + } else { + rettv->v_type = VAR_LIST; + rettv->vval.v_list = reg_submatch_list(no); + } +} + +/* + * "substitute()" function + */ +static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char patbuf[NUMBUFLEN]; + char subbuf[NUMBUFLEN]; + char flagsbuf[NUMBUFLEN]; + + const char *const str = tv_get_string_chk(&argvars[0]); + const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf); + const char *sub = NULL; + const char *const flg = tv_get_string_buf_chk(&argvars[3], flagsbuf); + + typval_T *expr = NULL; + if (tv_is_func(argvars[2])) { + expr = &argvars[2]; + } else { + sub = tv_get_string_buf_chk(&argvars[2], subbuf); + } + + rettv->v_type = VAR_STRING; + if (str == NULL || pat == NULL || (sub == NULL && expr == NULL) + || flg == NULL) { + rettv->vval.v_string = NULL; + } else { + rettv->vval.v_string = do_string_sub((char_u *)str, (char_u *)pat, + (char_u *)sub, expr, (char_u *)flg); + } +} + +/// "swapinfo(swap_filename)" function +static void f_swapinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_alloc_ret(rettv); + get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict); +} + +/// "swapname(expr)" function +static void f_swapname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + buf_T *buf = tv_get_buf(&argvars[0], false); + if (buf == NULL + || buf->b_ml.ml_mfp == NULL + || buf->b_ml.ml_mfp->mf_fname == NULL) { + rettv->vval.v_string = NULL; + } else { + rettv->vval.v_string = vim_strsave(buf->b_ml.ml_mfp->mf_fname); + } +} + +/// "synID(lnum, col, trans)" function +static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + // -1 on type error (both) + const linenr_T lnum = tv_get_lnum(argvars); + const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1; + + bool transerr = false; + const int trans = tv_get_number_chk(&argvars[2], &transerr); + + int id = 0; + if (!transerr && lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count + && col >= 0 && (size_t)col < STRLEN(ml_get(lnum))) { + id = syn_get_id(curwin, lnum, col, trans, NULL, false); + } + + rettv->vval.v_number = id; +} + +/* + * "synIDattr(id, what [, mode])" function + */ +static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const int id = (int)tv_get_number(&argvars[0]); + const char *const what = tv_get_string(&argvars[1]); + int modec; + if (argvars[2].v_type != VAR_UNKNOWN) { + char modebuf[NUMBUFLEN]; + const char *const mode = tv_get_string_buf(&argvars[2], modebuf); + modec = TOLOWER_ASC(mode[0]); + if (modec != 'c' && modec != 'g') { + modec = 0; // Replace invalid with current. + } + } else if (ui_rgb_attached()) { + modec = 'g'; + } else { + modec = 'c'; + } + + + const char *p = NULL; + switch (TOLOWER_ASC(what[0])) { + case 'b': { + if (TOLOWER_ASC(what[1]) == 'g') { // bg[#] + p = highlight_color(id, what, modec); + } else { // bold + p = highlight_has_attr(id, HL_BOLD, modec); + } + break; + } + case 'f': { // fg[#] or font + p = highlight_color(id, what, modec); + break; + } + case 'i': { + if (TOLOWER_ASC(what[1]) == 'n') { // inverse + p = highlight_has_attr(id, HL_INVERSE, modec); + } else { // italic + p = highlight_has_attr(id, HL_ITALIC, modec); + } + break; + } + case 'n': { // name + p = get_highlight_name_ext(NULL, id - 1, false); + break; + } + case 'r': { // reverse + p = highlight_has_attr(id, HL_INVERSE, modec); + break; + } + case 's': { + if (TOLOWER_ASC(what[1]) == 'p') { // sp[#] + p = highlight_color(id, what, modec); + } else if (TOLOWER_ASC(what[1]) == 't' + && TOLOWER_ASC(what[2]) == 'r') { // strikethrough + p = highlight_has_attr(id, HL_STRIKETHROUGH, modec); + } else { // standout + p = highlight_has_attr(id, HL_STANDOUT, modec); + } + break; + } + case 'u': { + if (STRLEN(what) <= 5 || TOLOWER_ASC(what[5]) != 'c') { // underline + p = highlight_has_attr(id, HL_UNDERLINE, modec); + } else { // undercurl + p = highlight_has_attr(id, HL_UNDERCURL, modec); + } + break; + } + } + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)(p == NULL ? p : xstrdup(p)); +} + +/* + * "synIDtrans(id)" function + */ +static void f_synIDtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int id = tv_get_number(&argvars[0]); + + if (id > 0) { + id = syn_get_final_id(id); + } else { + id = 0; + } + + rettv->vval.v_number = id; +} + +/* + * "synconcealed(lnum, col)" function + */ +static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int syntax_flags = 0; + int cchar; + int matchid = 0; + char_u str[NUMBUFLEN]; + + tv_list_set_ret(rettv, NULL); + + // -1 on type error (both) + const linenr_T lnum = tv_get_lnum(argvars); + const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1; + + memset(str, NUL, sizeof(str)); + + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0 + && (size_t)col <= STRLEN(ml_get(lnum)) && curwin->w_p_cole > 0) { + (void)syn_get_id(curwin, lnum, col, false, NULL, false); + syntax_flags = get_syntax_info(&matchid); + + // get the conceal character + if ((syntax_flags & HL_CONCEAL) && curwin->w_p_cole < 3) { + cchar = syn_get_sub_char(); + if (cchar == NUL && curwin->w_p_cole == 1) { + cchar = (curwin->w_p_lcs_chars.conceal == NUL) + ? ' ' + : curwin->w_p_lcs_chars.conceal; + } + if (cchar != NUL) { + utf_char2bytes(cchar, str); + } + } + } + + tv_list_alloc_ret(rettv, 3); + tv_list_append_number(rettv->vval.v_list, (syntax_flags & HL_CONCEAL) != 0); + // -1 to auto-determine strlen + tv_list_append_string(rettv->vval.v_list, (const char *)str, -1); + tv_list_append_number(rettv->vval.v_list, matchid); +} + +/* + * "synstack(lnum, col)" function + */ +static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_list_set_ret(rettv, NULL); + + // -1 on type error (both) + const linenr_T lnum = tv_get_lnum(argvars); + const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1; + + if (lnum >= 1 + && lnum <= curbuf->b_ml.ml_line_count + && col >= 0 + && (size_t)col <= STRLEN(ml_get(lnum))) { + tv_list_alloc_ret(rettv, kListLenMayKnow); + (void)syn_get_id(curwin, lnum, col, false, NULL, true); + + int id; + int i = 0; + while ((id = syn_get_stack_item(i++)) >= 0) { + tv_list_append_number(rettv->vval.v_list, id); + } + } +} + +/// f_system - the VimL system() function +static void f_system(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + get_system_output_as_rettv(argvars, rettv, false); +} + +static void f_systemlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + get_system_output_as_rettv(argvars, rettv, true); +} + + +/* + * "tabpagebuflist()" function + */ +static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp = NULL; + + if (argvars[0].v_type == VAR_UNKNOWN) { + wp = firstwin; + } else { + tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0])); + if (tp != NULL) { + wp = (tp == curtab) ? firstwin : tp->tp_firstwin; + } + } + if (wp != NULL) { + tv_list_alloc_ret(rettv, kListLenMayKnow); + while (wp != NULL) { + tv_list_append_number(rettv->vval.v_list, wp->w_buffer->b_fnum); + wp = wp->w_next; + } + } +} + +/* + * "tabpagenr()" function + */ +static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int nr = 1; + + if (argvars[0].v_type != VAR_UNKNOWN) { + const char *const arg = tv_get_string_chk(&argvars[0]); + nr = 0; + if (arg != NULL) { + if (strcmp(arg, "$") == 0) { + nr = tabpage_index(NULL) - 1; + } else if (strcmp(arg, "#") == 0) { + nr = valid_tabpage(lastused_tabpage) + ? tabpage_index(lastused_tabpage) + : nr; + } else { + EMSG2(_(e_invexpr2), arg); + } + } + } else { + nr = tabpage_index(curtab); + } + rettv->vval.v_number = nr; +} + + + +/* + * Common code for tabpagewinnr() and winnr(). + */ +static int get_winnr(tabpage_T *tp, typval_T *argvar) +{ + win_T *twin; + int nr = 1; + win_T *wp; + + twin = (tp == curtab) ? curwin : tp->tp_curwin; + if (argvar->v_type != VAR_UNKNOWN) { + bool invalid_arg = false; + const char *const arg = tv_get_string_chk(argvar); + if (arg == NULL) { + nr = 0; // Type error; errmsg already given. + } else if (strcmp(arg, "$") == 0) { + twin = (tp == curtab) ? lastwin : tp->tp_lastwin; + } else if (strcmp(arg, "#") == 0) { + twin = (tp == curtab) ? prevwin : tp->tp_prevwin; + if (twin == NULL) { + nr = 0; + } + } else { + // Extract the window count (if specified). e.g. winnr('3j') + char_u *endp; + long count = strtol((char *)arg, (char **)&endp, 10); + if (count <= 0) { + // if count is not specified, default to 1 + count = 1; + } + if (endp != NULL && *endp != '\0') { + if (strequal((char *)endp, "j")) { + twin = win_vert_neighbor(tp, twin, false, count); + } else if (strequal((char *)endp, "k")) { + twin = win_vert_neighbor(tp, twin, true, count); + } else if (strequal((char *)endp, "h")) { + twin = win_horz_neighbor(tp, twin, true, count); + } else if (strequal((char *)endp, "l")) { + twin = win_horz_neighbor(tp, twin, false, count); + } else { + invalid_arg = true; + } + } else { + invalid_arg = true; + } + } + + if (invalid_arg) { + EMSG2(_(e_invexpr2), arg); + nr = 0; + } + } + + if (nr > 0) + for (wp = (tp == curtab) ? firstwin : tp->tp_firstwin; + wp != twin; wp = wp->w_next) { + if (wp == NULL) { + // didn't find it in this tabpage + nr = 0; + break; + } + ++nr; + } + return nr; +} + +/* + * "tabpagewinnr()" function + */ +static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int nr = 1; + tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0])); + if (tp == NULL) { + nr = 0; + } else { + nr = get_winnr(tp, &argvars[1]); + } + rettv->vval.v_number = nr; +} + +/* + * "tagfiles()" function + */ +static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char *fname; + tagname_T tn; + + tv_list_alloc_ret(rettv, kListLenUnknown); + fname = xmalloc(MAXPATHL); + + bool first = true; + while (get_tagfname(&tn, first, (char_u *)fname) == OK) { + tv_list_append_string(rettv->vval.v_list, fname, -1); + first = false; + } + + tagname_free(&tn); + xfree(fname); +} + +/* + * "taglist()" function + */ +static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const tag_pattern = tv_get_string(&argvars[0]); + + rettv->vval.v_number = false; + if (*tag_pattern == NUL) { + return; + } + + const char *fname = NULL; + if (argvars[1].v_type != VAR_UNKNOWN) { + fname = tv_get_string(&argvars[1]); + } + (void)get_tags(tv_list_alloc_ret(rettv, kListLenUnknown), + (char_u *)tag_pattern, (char_u *)fname); +} + +/* + * "tempname()" function + */ +static void f_tempname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = vim_tempname(); +} + +// "termopen(cmd[, cwd])" function +static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (check_restricted() || check_secure()) { + return; + } + + if (curbuf->b_changed) { + EMSG(_("Can only call this function in an unmodified buffer")); + return; + } + + const char *cmd; + bool executable = true; + char **argv = tv_to_argv(&argvars[0], &cmd, &executable); + if (!argv) { + rettv->vval.v_number = executable ? 0 : -1; + return; // Did error message in tv_to_argv. + } + + if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) { + // Wrong argument type + EMSG2(_(e_invarg2), "expected dictionary"); + shell_free_argv(argv); + return; + } + + CallbackReader on_stdout = CALLBACK_READER_INIT, + on_stderr = CALLBACK_READER_INIT; + Callback on_exit = CALLBACK_NONE; + dict_T *job_opts = NULL; + const char *cwd = "."; + if (argvars[1].v_type == VAR_DICT) { + job_opts = argvars[1].vval.v_dict; + + const char *const new_cwd = tv_dict_get_string(job_opts, "cwd", false); + if (new_cwd && *new_cwd != NUL) { + cwd = new_cwd; + // The new cwd must be a directory. + if (!os_isdir_executable((const char *)cwd)) { + EMSG2(_(e_invarg2), "expected valid directory"); + shell_free_argv(argv); + return; + } + } + + if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) { + shell_free_argv(argv); + return; + } + } + + uint16_t term_width = MAX(0, curwin->w_width_inner - win_col_off(curwin)); + Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, + true, false, false, cwd, + term_width, curwin->w_height_inner, + xstrdup("xterm-256color"), NULL, + &rettv->vval.v_number); + if (rettv->vval.v_number <= 0) { + return; + } + + int pid = chan->stream.pty.process.pid; + + // "./…" => "/home/foo/…" + vim_FullName(cwd, (char *)NameBuff, sizeof(NameBuff), false); + // "/home/foo/…" => "~/…" + size_t len = home_replace(NULL, NameBuff, IObuff, sizeof(IObuff), true); + // Trim slash. + if (IObuff[len - 1] == '\\' || IObuff[len - 1] == '/') { + IObuff[len - 1] = '\0'; + } + + // Terminal URI: "term://$CWD//$PID:$CMD" + snprintf((char *)NameBuff, sizeof(NameBuff), "term://%s//%d:%s", + (char *)IObuff, pid, cmd); + // at this point the buffer has no terminal instance associated yet, so unset + // the 'swapfile' option to ensure no swap file will be created + curbuf->b_p_swf = false; + (void)setfname(curbuf, NameBuff, NULL, true); + // Save the job id and pid in b:terminal_job_{id,pid} + Error err = ERROR_INIT; + // deprecated: use 'channel' buffer option + dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"), + INTEGER_OBJ(chan->id), false, false, &err); + api_clear_error(&err); + dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_pid"), + INTEGER_OBJ(pid), false, false, &err); + api_clear_error(&err); + + channel_terminal_open(chan); + channel_create_event(chan, NULL); +} + +// "test_garbagecollect_now()" function +static void f_test_garbagecollect_now(typval_T *argvars, + typval_T *rettv, FunPtr fptr) +{ + // This is dangerous, any Lists and Dicts used internally may be freed + // while still in use. + garbage_collect(true); +} + +// "test_write_list_log()" function +static void f_test_write_list_log(typval_T *const argvars, + typval_T *const rettv, + FunPtr fptr) +{ + const char *const fname = tv_get_string_chk(&argvars[0]); + if (fname == NULL) { + return; + } + list_write_log(fname); +} + +/// "timer_info([timer])" function +static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_UNKNOWN) { + if (argvars[0].v_type != VAR_NUMBER) { + EMSG(_(e_number_exp)); + return; + } + tv_list_alloc_ret(rettv, 1); + timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0])); + if (timer != NULL && !timer->stopped) { + add_timer_info(rettv, timer); + } + } else { + add_timer_info_all(rettv); + } +} + +/// "timer_pause(timer, paused)" function +static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_NUMBER) { + EMSG(_(e_number_exp)); + return; + } + int paused = (bool)tv_get_number(&argvars[1]); + timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0])); + if (timer != NULL) { + if (!timer->paused && paused) { + time_watcher_stop(&timer->tw); + } else if (timer->paused && !paused) { + time_watcher_start(&timer->tw, timer_due_cb, timer->timeout, + timer->timeout); + } + timer->paused = paused; + } +} + +/// "timer_start(timeout, callback, opts)" function +static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int repeat = 1; + dict_T *dict; + + rettv->vval.v_number = -1; + + if (argvars[2].v_type != VAR_UNKNOWN) { + if (argvars[2].v_type != VAR_DICT + || (dict = argvars[2].vval.v_dict) == NULL) { + EMSG2(_(e_invarg2), tv_get_string(&argvars[2])); + return; + } + dictitem_T *const di = tv_dict_find(dict, S_LEN("repeat")); + if (di != NULL) { + repeat = tv_get_number(&di->di_tv); + if (repeat == 0) { + repeat = 1; + } + } + } + + Callback callback; + if (!callback_from_typval(&callback, &argvars[1])) { + return; + } + rettv->vval.v_number = + timer_start(tv_get_number(&argvars[0]), repeat, &callback); +} + + +// "timer_stop(timerid)" function +static void f_timer_stop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_NUMBER) { + EMSG(_(e_number_exp)); + return; + } + + timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0])); + if (timer == NULL) { + return; + } + + timer_stop(timer); +} + +static void f_timer_stopall(typval_T *argvars, typval_T *unused, FunPtr fptr) +{ + timer_stop_all(); +} + +/* + * "tolower(string)" function + */ +static void f_tolower(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)strcase_save(tv_get_string(&argvars[0]), + false); +} + +/* + * "toupper(string)" function + */ +static void f_toupper(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)strcase_save(tv_get_string(&argvars[0]), + true); +} + +/* + * "tr(string, fromstr, tostr)" function + */ +static void f_tr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char buf[NUMBUFLEN]; + char buf2[NUMBUFLEN]; + + const char *in_str = tv_get_string(&argvars[0]); + const char *fromstr = tv_get_string_buf_chk(&argvars[1], buf); + const char *tostr = tv_get_string_buf_chk(&argvars[2], buf2); + + // Default return value: empty string. + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (fromstr == NULL || tostr == NULL) { + return; // Type error; errmsg already given. + } + garray_T ga; + ga_init(&ga, (int)sizeof(char), 80); + + if (!has_mbyte) { + // Not multi-byte: fromstr and tostr must be the same length. + if (strlen(fromstr) != strlen(tostr)) { + goto error; + } + } + + // fromstr and tostr have to contain the same number of chars. + bool first = true; + while (*in_str != NUL) { + if (has_mbyte) { + const char *cpstr = in_str; + const int inlen = (*mb_ptr2len)((const char_u *)in_str); + int cplen = inlen; + int idx = 0; + int fromlen; + for (const char *p = fromstr; *p != NUL; p += fromlen) { + fromlen = (*mb_ptr2len)((const char_u *)p); + if (fromlen == inlen && STRNCMP(in_str, p, inlen) == 0) { + int tolen; + for (p = tostr; *p != NUL; p += tolen) { + tolen = (*mb_ptr2len)((const char_u *)p); + if (idx-- == 0) { + cplen = tolen; + cpstr = (char *)p; + break; + } + } + if (*p == NUL) { // tostr is shorter than fromstr. + goto error; + } + break; + } + idx++; + } + + if (first && cpstr == in_str) { + // Check that fromstr and tostr have the same number of + // (multi-byte) characters. Done only once when a character + // of in_str doesn't appear in fromstr. + first = false; + int tolen; + for (const char *p = tostr; *p != NUL; p += tolen) { + tolen = (*mb_ptr2len)((const char_u *)p); + idx--; + } + if (idx != 0) { + goto error; + } + } + + ga_grow(&ga, cplen); + memmove((char *)ga.ga_data + ga.ga_len, cpstr, (size_t)cplen); + ga.ga_len += cplen; + + in_str += inlen; + } else { + // When not using multi-byte chars we can do it faster. + const char *const p = strchr(fromstr, *in_str); + if (p != NULL) { + ga_append(&ga, tostr[p - fromstr]); + } else { + ga_append(&ga, *in_str); + } + in_str++; + } + } + + // add a terminating NUL + ga_append(&ga, NUL); + + rettv->vval.v_string = ga.ga_data; + return; +error: + EMSG2(_(e_invarg2), fromstr); + ga_clear(&ga); + return; +} + +// "trim({expr})" function +static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char buf1[NUMBUFLEN]; + char buf2[NUMBUFLEN]; + const char_u *head = (const char_u *)tv_get_string_buf_chk(&argvars[0], buf1); + const char_u *mask = NULL; + const char_u *tail; + const char_u *prev; + const char_u *p; + int c1; + + rettv->v_type = VAR_STRING; + if (head == NULL) { + rettv->vval.v_string = NULL; + return; + } + + if (argvars[1].v_type == VAR_STRING) { + mask = (const char_u *)tv_get_string_buf_chk(&argvars[1], buf2); + } + + while (*head != NUL) { + c1 = PTR2CHAR(head); + if (mask == NULL) { + if (c1 > ' ' && c1 != 0xa0) { + break; + } + } else { + for (p = mask; *p != NUL; MB_PTR_ADV(p)) { + if (c1 == PTR2CHAR(p)) { + break; + } + } + if (*p == NUL) { + break; + } + } + MB_PTR_ADV(head); + } + + for (tail = head + STRLEN(head); tail > head; tail = prev) { + prev = tail; + MB_PTR_BACK(head, prev); + c1 = PTR2CHAR(prev); + if (mask == NULL) { + if (c1 > ' ' && c1 != 0xa0) { + break; + } + } else { + for (p = mask; *p != NUL; MB_PTR_ADV(p)) { + if (c1 == PTR2CHAR(p)) { + break; + } + } + if (*p == NUL) { + break; + } + } + } + rettv->vval.v_string = vim_strnsave(head, (int)(tail - head)); +} + +/* + * "type(expr)" function + */ +static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int n = -1; + + switch (argvars[0].v_type) { + case VAR_NUMBER: n = VAR_TYPE_NUMBER; break; + case VAR_STRING: n = VAR_TYPE_STRING; break; + case VAR_PARTIAL: + case VAR_FUNC: n = VAR_TYPE_FUNC; break; + case VAR_LIST: n = VAR_TYPE_LIST; break; + case VAR_DICT: n = VAR_TYPE_DICT; break; + case VAR_FLOAT: n = VAR_TYPE_FLOAT; break; + case VAR_SPECIAL: { + switch (argvars[0].vval.v_special) { + case kSpecialVarTrue: + case kSpecialVarFalse: { + n = VAR_TYPE_BOOL; + break; + } + case kSpecialVarNull: { + n = 7; + break; + } + } + break; + } + case VAR_UNKNOWN: { + internal_error("f_type(UNKNOWN)"); + break; + } + } + rettv->vval.v_number = n; +} + +/* + * "undofile(name)" function + */ +static void f_undofile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + const char *const fname = tv_get_string(&argvars[0]); + + if (*fname == NUL) { + // If there is no file name there will be no undo file. + rettv->vval.v_string = NULL; + } else { + char *ffname = FullName_save(fname, true); + + if (ffname != NULL) { + rettv->vval.v_string = (char_u *)u_get_undo_file_name(ffname, false); + } + xfree(ffname); + } +} + +/* + * "undotree()" function + */ +static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_alloc_ret(rettv); + + dict_T *dict = rettv->vval.v_dict; + + tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)curbuf->b_u_synced); + tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)curbuf->b_u_seq_last); + tv_dict_add_nr(dict, S_LEN("save_last"), + (varnumber_T)curbuf->b_u_save_nr_last); + tv_dict_add_nr(dict, S_LEN("seq_cur"), (varnumber_T)curbuf->b_u_seq_cur); + tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)curbuf->b_u_time_cur); + tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)curbuf->b_u_save_nr_cur); + + tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead)); +} + +/* + * "values(dict)" function + */ +static void f_values(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + dict_list(argvars, rettv, 1); +} + +/* + * "virtcol(string)" function + */ +static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + colnr_T vcol = 0; + pos_T *fp; + int fnum = curbuf->b_fnum; + + fp = var2fpos(&argvars[0], FALSE, &fnum); + if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count + && fnum == curbuf->b_fnum) { + // Limit the column to a valid value, getvvcol() doesn't check. + if (fp->col < 0) { + fp->col = 0; + } else { + const size_t len = STRLEN(ml_get(fp->lnum)); + if (fp->col > (colnr_T)len) { + fp->col = (colnr_T)len; + } + } + getvvcol(curwin, fp, NULL, NULL, &vcol); + ++vcol; + } + + rettv->vval.v_number = vcol; +} + +/* + * "visualmode()" function + */ +static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char_u str[2]; + + rettv->v_type = VAR_STRING; + str[0] = curbuf->b_visual_mode_eval; + str[1] = NUL; + rettv->vval.v_string = vim_strsave(str); + + // A non-zero number or non-empty string argument: reset mode. + if (non_zero_arg(&argvars[0])) { + curbuf->b_visual_mode_eval = NUL; + } +} + +/* + * "wildmenumode()" function + */ +static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (wild_menu_showing || ((State & CMDLINE) && pum_visible())) { + rettv->vval.v_number = 1; + } +} + +/// "win_findbuf()" function +static void f_win_findbuf(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_list_alloc_ret(rettv, kListLenMayKnow); + win_findbuf(argvars, rettv->vval.v_list); +} + +/// "win_getid()" function +static void f_win_getid(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = win_getid(argvars); +} + +/// "win_gotoid()" function +static void f_win_gotoid(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = win_gotoid(argvars); +} + +/// "win_id2tabwin()" function +static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_id2tabwin(argvars, rettv); +} + +/// "win_id2win()" function +static void f_win_id2win(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = win_id2win(argvars); +} + +/// "winbufnr(nr)" function +static void f_winbufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + rettv->vval.v_number = -1; + } else { + rettv->vval.v_number = wp->w_buffer->b_fnum; + } +} + +/* + * "wincol()" function + */ +static void f_wincol(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + validate_cursor(); + rettv->vval.v_number = curwin->w_wcol + 1; +} + +/// "winheight(nr)" function +static void f_winheight(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + rettv->vval.v_number = -1; + } else { + rettv->vval.v_number = wp->w_height; + } +} + +// "winlayout()" function +static void f_winlayout(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tabpage_T *tp; + + tv_list_alloc_ret(rettv, 2); + + if (argvars[0].v_type == VAR_UNKNOWN) { + tp = curtab; + } else { + tp = find_tabpage((int)tv_get_number(&argvars[0])); + if (tp == NULL) { + return; + } + } + + get_framelayout(tp->tp_topframe, rettv->vval.v_list, true); +} + +/* + * "winline()" function + */ +static void f_winline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + validate_cursor(); + rettv->vval.v_number = curwin->w_wrow + 1; +} + +/* + * "winnr()" function + */ +static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int nr = 1; + + nr = get_winnr(curtab, &argvars[0]); + rettv->vval.v_number = nr; +} + +/* + * "winrestcmd()" function + */ +static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int winnr = 1; + garray_T ga; + char_u buf[50]; + + ga_init(&ga, (int)sizeof(char), 70); + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + sprintf((char *)buf, "%dresize %d|", winnr, wp->w_height); + ga_concat(&ga, buf); + sprintf((char *)buf, "vert %dresize %d|", winnr, wp->w_width); + ga_concat(&ga, buf); + ++winnr; + } + ga_append(&ga, NUL); + + rettv->vval.v_string = ga.ga_data; + rettv->v_type = VAR_STRING; +} + +/* + * "winrestview()" function + */ +static void f_winrestview(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + dict_T *dict; + + if (argvars[0].v_type != VAR_DICT + || (dict = argvars[0].vval.v_dict) == NULL) { + EMSG(_(e_invarg)); + } else { + dictitem_T *di; + if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) { + curwin->w_cursor.lnum = tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) { + curwin->w_cursor.col = tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) { + curwin->w_cursor.coladd = tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) { + curwin->w_curswant = tv_get_number(&di->di_tv); + curwin->w_set_curswant = false; + } + if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) { + set_topline(curwin, tv_get_number(&di->di_tv)); + } + if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) { + curwin->w_topfill = tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) { + curwin->w_leftcol = tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) { + curwin->w_skipcol = tv_get_number(&di->di_tv); + } + + check_cursor(); + win_new_height(curwin, curwin->w_height); + win_new_width(curwin, curwin->w_width); + changed_window_setting(); + + if (curwin->w_topline <= 0) + curwin->w_topline = 1; + if (curwin->w_topline > curbuf->b_ml.ml_line_count) + curwin->w_topline = curbuf->b_ml.ml_line_count; + check_topfill(curwin, true); + } +} + +/* + * "winsaveview()" function + */ +static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + dict_T *dict; + + tv_dict_alloc_ret(rettv); + dict = rettv->vval.v_dict; + + tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)curwin->w_cursor.lnum); + tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)curwin->w_cursor.col); + tv_dict_add_nr(dict, S_LEN("coladd"), (varnumber_T)curwin->w_cursor.coladd); + update_curswant(); + tv_dict_add_nr(dict, S_LEN("curswant"), (varnumber_T)curwin->w_curswant); + + tv_dict_add_nr(dict, S_LEN("topline"), (varnumber_T)curwin->w_topline); + tv_dict_add_nr(dict, S_LEN("topfill"), (varnumber_T)curwin->w_topfill); + tv_dict_add_nr(dict, S_LEN("leftcol"), (varnumber_T)curwin->w_leftcol); + tv_dict_add_nr(dict, S_LEN("skipcol"), (varnumber_T)curwin->w_skipcol); +} + +/// "winwidth(nr)" function +static void f_winwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + rettv->vval.v_number = -1; + } else { + rettv->vval.v_number = wp->w_width; + } +} + +/// "wordcount()" function +static void f_wordcount(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_alloc_ret(rettv); + cursor_pos_info(rettv->vval.v_dict); +} + +/// "writefile()" function +static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = -1; + + if (check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "writefile()"); + return; + } + const list_T *const list = argvars[0].vval.v_list; + TV_LIST_ITER_CONST(list, li, { + if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) { + return; + } + }); + + bool binary = false; + bool append = false; + bool do_fsync = !!p_fs; + if (argvars[2].v_type != VAR_UNKNOWN) { + const char *const flags = tv_get_string_chk(&argvars[2]); + if (flags == NULL) { + return; + } + for (const char *p = flags; *p; p++) { + switch (*p) { + case 'b': { binary = true; break; } + case 'a': { append = true; break; } + case 's': { do_fsync = true; break; } + case 'S': { do_fsync = false; break; } + default: { + // Using %s, p and not %c, *p to preserve multibyte characters + emsgf(_("E5060: Unknown flag: %s"), p); + return; + } + } + } + } + + char buf[NUMBUFLEN]; + const char *const fname = tv_get_string_buf_chk(&argvars[1], buf); + if (fname == NULL) { + return; + } + FileDescriptor fp; + int error; + if (*fname == NUL) { + EMSG(_("E482: Can't open file with an empty name")); + } else if ((error = file_open(&fp, fname, + ((append ? kFileAppend : kFileTruncate) + | kFileCreate), 0666)) != 0) { + emsgf(_("E482: Can't open file %s for writing: %s"), + fname, os_strerror(error)); + } else { + if (write_list(&fp, list, binary)) { + rettv->vval.v_number = 0; + } + if ((error = file_close(&fp, do_fsync)) != 0) { + emsgf(_("E80: Error when closing file %s: %s"), + fname, os_strerror(error)); + } + } +} +/* + * "xor(expr, expr)" function + */ +static void f_xor(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) + ^ tv_get_number_chk(&argvars[1], NULL); +} diff --git a/src/nvim/eval/funcs.h b/src/nvim/eval/funcs.h new file mode 100644 index 0000000000..a343290734 --- /dev/null +++ b/src/nvim/eval/funcs.h @@ -0,0 +1,24 @@ +#ifndef NVIM_EVAL_FUNCS_H +#define NVIM_EVAL_FUNCS_H + +#include "nvim/buffer_defs.h" +#include "nvim/eval/typval.h" + +typedef void (*FunPtr)(void); + +/// Prototype of C function that implements VimL function +typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, FunPtr data); + +/// Structure holding VimL function definition +typedef struct fst { + char *name; ///< Name of the function. + uint8_t min_argc; ///< Minimal number of arguments. + uint8_t max_argc; ///< Maximal number of arguments. + VimLFunc func; ///< Function implementation. + FunPtr data; ///< Userdata for function implementation. +} VimLFuncDef; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/funcs.h.generated.h" +#endif +#endif // NVIM_EVAL_FUNCS_H diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index da97eccc65..35130f6f40 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -15,6 +15,7 @@ #include "nvim/eval/encode.h" #include "nvim/eval/typval_encode.h" #include "nvim/eval.h" +#include "nvim/eval/userfunc.h" #include "nvim/types.h" #include "nvim/assert.h" #include "nvim/memory.h" @@ -1428,6 +1429,23 @@ dictitem_T *tv_dict_find(const dict_T *const d, const char *const key, return TV_DICT_HI2DI(hi); } +/// Get a typval item from a dictionary and copy it into "rettv". +/// +/// @param[in] d Dictionary to check. +/// @param[in] key Dictionary key. +/// @param[in] rettv Return value. +/// @return OK in case of success or FAIL if nothing was found. +int tv_dict_get_tv(dict_T *d, const char *const key, typval_T *rettv) +{ + dictitem_T *const di = tv_dict_find(d, key, -1); + if (di == NULL) { + return FAIL; + } + + tv_copy(&di->di_tv, rettv); + return OK; +} + /// Get a number item from a dictionary /// /// Returns 0 if the entry does not exist. @@ -1587,6 +1605,26 @@ int tv_dict_add_list(dict_T *const d, const char *const key, return OK; } +/// Add a typval entry to dictionary. +/// +/// @param[out] d Dictionary to add entry to. +/// @param[in] key Key to add. +/// @param[in] key_len Key length. +/// +/// @return FAIL if out of memory or key already exists. +int tv_dict_add_tv(dict_T *d, const char *key, const size_t key_len, + typval_T *tv) +{ + dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); + + tv_copy(tv, &item->di_tv); + if (tv_dict_add(d, item) == FAIL) { + tv_dict_item_free(item); + return FAIL; + } + return OK; +} + /// Add a dictionary entry to dictionary /// /// @param[out] d Dictionary to add entry to. @@ -1633,6 +1671,28 @@ int tv_dict_add_nr(dict_T *const d, const char *const key, return OK; } +/// Add a floating point number entry to dictionary +/// +/// @param[out] d Dictionary to add entry to. +/// @param[in] key Key to add. +/// @param[in] key_len Key length. +/// @param[in] nr Floating point number to add. +/// +/// @return OK in case of success, FAIL when key already exists. +int tv_dict_add_float(dict_T *const d, const char *const key, + const size_t key_len, const float_T nr) +{ + dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); + + item->di_tv.v_type = VAR_FLOAT; + item->di_tv.vval.v_float = nr; + if (tv_dict_add(d, item) == FAIL) { + tv_dict_item_free(item); + return FAIL; + } + return OK; +} + /// Add a special entry to dictionary /// /// @param[out] d Dictionary to add entry to. diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 008453b87f..4390db1b71 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -33,7 +33,7 @@ typedef double float_T; enum { DO_NOT_FREE_CNT = (INT_MAX / 2) }; /// Additional values for tv_list_alloc() len argument -enum { +enum ListLenSpecials { /// List length is not known in advance /// /// To be used when there is neither a way to know how many elements will be @@ -49,7 +49,7 @@ enum { /// /// To be used when it looks impractical to determine list length. kListLenMayKnow = -3, -} ListLenSpecials; +}; /// Maximal possible value of varnumber_T variable #define VARNUMBER_MAX INT64_MAX @@ -258,9 +258,39 @@ typedef struct { linenr_T sc_lnum; // line number } sctx_T; +/// Maximum number of function arguments +#define MAX_FUNC_ARGS 20 +/// Short variable name length +#define VAR_SHORT_LEN 20 +/// Number of fixed variables used for arguments +#define FIXVAR_CNT 12 + // Structure to hold info for a function that is currently being executed. typedef struct funccall_S funccall_T; +struct funccall_S { + ufunc_T *func; ///< Function being called. + int linenr; ///< Next line to be executed. + int returned; ///< ":return" used. + /// Fixed variables for arguments. + TV_DICTITEM_STRUCT(VAR_SHORT_LEN + 1) fixvar[FIXVAR_CNT]; + dict_T l_vars; ///< l: local function variables. + ScopeDictDictItem l_vars_var; ///< Variable for l: scope. + dict_T l_avars; ///< a: argument variables. + ScopeDictDictItem l_avars_var; ///< Variable for a: scope. + list_T l_varlist; ///< List for a:000. + listitem_T l_listitems[MAX_FUNC_ARGS]; ///< List items for a:000. + typval_T *rettv; ///< Return value. + linenr_T breakpoint; ///< Next line with breakpoint or zero. + int dbg_tick; ///< Debug_tick when breakpoint was set. + int level; ///< Top nesting level of executed function. + proftime_T prof_child; ///< Time spent in a child. + funccall_T *caller; ///< Calling function or NULL. + int fc_refcount; ///< Number of user functions that reference this funccall. + int fc_copyID; ///< CopyID used for garbage collection. + garray_T fc_funcs; ///< List of ufunc_T* which keep a reference to "func". +}; + /// Structure to hold info for a user function. struct ufunc { int uf_varargs; ///< variable nr of arguments @@ -293,9 +323,6 @@ struct ufunc { ///< (<SNR> is K_SPECIAL KS_EXTRA KE_SNR) }; -/// Maximum number of function arguments -#define MAX_FUNC_ARGS 20 - struct partial_S { int pt_refcount; ///< Reference count. char_u *pt_name; ///< Function name; when NULL use pt_func->name. diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 289c3ee99c..af21a6fbe3 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -173,7 +173,7 @@ /// @def TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK /// @brief Macros used to check special dictionary key /// -/// @param label Label for goto in case check was not successfull. +/// @param label Label for goto in case check was not successful. /// @param key typval_T key to check. /// @def TYPVAL_ENCODE_CONV_DICT_AFTER_KEY diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c new file mode 100644 index 0000000000..ae8557a8bc --- /dev/null +++ b/src/nvim/eval/userfunc.c @@ -0,0 +1,3385 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// User defined function support + +#include "nvim/ascii.h" +#include "nvim/charset.h" +#include "nvim/edit.h" +#include "nvim/eval.h" +#include "nvim/eval/encode.h" +#include "nvim/eval/userfunc.h" +#include "nvim/ex_cmds2.h" +#include "nvim/ex_docmd.h" +#include "nvim/ex_getln.h" +#include "nvim/ex_getln.h" +#include "nvim/fileio.h" +#include "nvim/getchar.h" +#include "nvim/globals.h" +#include "nvim/lua/executor.h" +#include "nvim/misc1.h" +#include "nvim/os/input.h" +#include "nvim/regexp.h" +#include "nvim/search.h" +#include "nvim/ui.h" +#include "nvim/vim.h" + +// flags used in uf_flags +#define FC_ABORT 0x01 // abort function on error +#define FC_RANGE 0x02 // function accepts range +#define FC_DICT 0x04 // Dict function, uses "self" +#define FC_CLOSURE 0x08 // closure, uses outer scope variables +#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0 +#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0 +#define FC_SANDBOX 0x40 // function defined in the sandbox + +#ifdef INCLUDE_GENERATED_DECLARATIONS +#include "eval/userfunc.c.generated.h" +#endif + +hashtab_T func_hashtab; + +// Used by get_func_tv() +static garray_T funcargs = GA_EMPTY_INIT_VALUE; + +// pointer to funccal for currently active function +funccall_T *current_funccal = NULL; + +// Pointer to list of previously used funccal, still around because some +// item in it is still being used. +funccall_T *previous_funccal = NULL; + +static char *e_funcexts = N_( + "E122: Function %s already exists, add ! to replace it"); +static char *e_funcdict = N_("E717: Dictionary entry already exists"); +static char *e_funcref = N_("E718: Funcref required"); +static char *e_nofunc = N_("E130: Unknown function: %s"); + +void func_init(void) +{ + hash_init(&func_hashtab); +} + +/// Get function arguments. +static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, + int *varargs, bool skip) +{ + bool mustend = false; + char_u *arg = *argp; + char_u *p = arg; + int c; + int i; + + if (newargs != NULL) { + ga_init(newargs, (int)sizeof(char_u *), 3); + } + + if (varargs != NULL) { + *varargs = false; + } + + // Isolate the arguments: "arg1, arg2, ...)" + while (*p != endchar) { + if (p[0] == '.' && p[1] == '.' && p[2] == '.') { + if (varargs != NULL) { + *varargs = true; + } + p += 3; + mustend = true; + } else { + arg = p; + while (ASCII_ISALNUM(*p) || *p == '_') { + p++; + } + if (arg == p || isdigit(*arg) + || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0) + || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) { + if (!skip) { + EMSG2(_("E125: Illegal argument: %s"), arg); + } + break; + } + if (newargs != NULL) { + ga_grow(newargs, 1); + c = *p; + *p = NUL; + arg = vim_strsave(arg); + + // Check for duplicate argument name. + for (i = 0; i < newargs->ga_len; i++) { + if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0) { + EMSG2(_("E853: Duplicate argument name: %s"), arg); + xfree(arg); + goto err_ret; + } + } + ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg; + newargs->ga_len++; + + *p = c; + } + if (*p == ',') { + p++; + } else { + mustend = true; + } + } + p = skipwhite(p); + if (mustend && *p != endchar) { + if (!skip) { + EMSG2(_(e_invarg2), *argp); + } + break; + } + } + if (*p != endchar) { + goto err_ret; + } + p++; // skip "endchar" + + *argp = p; + return OK; + +err_ret: + if (newargs != NULL) { + ga_clear_strings(newargs); + } + return FAIL; +} + +/// Register function "fp" as using "current_funccal" as its scope. +static void register_closure(ufunc_T *fp) +{ + if (fp->uf_scoped == current_funccal) { + // no change + return; + } + funccal_unref(fp->uf_scoped, fp, false); + fp->uf_scoped = current_funccal; + current_funccal->fc_refcount++; + ga_grow(¤t_funccal->fc_funcs, 1); + ((ufunc_T **)current_funccal->fc_funcs.ga_data) + [current_funccal->fc_funcs.ga_len++] = fp; +} + +/// 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_u **arg, typval_T *rettv, bool evaluate) +{ + garray_T newargs = GA_EMPTY_INIT_VALUE; + garray_T *pnewargs; + ufunc_T *fp = NULL; + int varargs; + int ret; + char_u *start = skipwhite(*arg + 1); + char_u *s, *e; + static int lambda_no = 0; + bool *old_eval_lavars = eval_lavars_used; + bool eval_lavars = false; + + // First, check if this is a lambda expression. "->" must exists. + ret = get_function_args(&start, '-', NULL, NULL, true); + if (ret == FAIL || *start != '>') { + return NOTDONE; + } + + // Parse the arguments again. + if (evaluate) { + pnewargs = &newargs; + } else { + pnewargs = NULL; + } + *arg = skipwhite(*arg + 1); + ret = get_function_args(arg, '-', pnewargs, &varargs, false); + if (ret == FAIL || **arg != '>') { + goto errret; + } + + // Set up a flag for checking local variables and arguments. + if (evaluate) { + eval_lavars_used = &eval_lavars; + } + + // Get the start and the end of the expression. + *arg = skipwhite(*arg + 1); + s = *arg; + ret = skip_expr(arg); + if (ret == FAIL) { + goto errret; + } + e = *arg; + *arg = skipwhite(*arg); + if (**arg != '}') { + goto errret; + } + (*arg)++; + + if (evaluate) { + int len, flags = 0; + char_u *p; + char_u name[20]; + partial_T *pt; + garray_T newlines; + + lambda_no++; + snprintf((char *)name, sizeof(name), "<lambda>%d", lambda_no); + + fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); + pt = xcalloc(1, sizeof(partial_T)); + + ga_init(&newlines, (int)sizeof(char_u *), 1); + ga_grow(&newlines, 1); + + // Add "return " before the expression. + len = 7 + e - s + 1; + p = (char_u *)xmalloc(len); + ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; + STRCPY(p, "return "); + STRLCPY(p + 7, s, e - s + 1); + + fp->uf_refcount = 1; + STRCPY(fp->uf_name, name); + hash_add(&func_hashtab, UF2HIKEY(fp)); + fp->uf_args = newargs; + fp->uf_lines = newlines; + if (current_funccal != NULL && eval_lavars) { + flags |= FC_CLOSURE; + register_closure(fp); + } else { + fp->uf_scoped = NULL; + } + + if (prof_def_func()) { + func_do_profile(fp); + } + if (sandbox) { + flags |= FC_SANDBOX; + } + fp->uf_varargs = true; + fp->uf_flags = flags; + fp->uf_calls = 0; + fp->uf_script_ctx = current_sctx; + fp->uf_script_ctx.sc_lnum += sourcing_lnum - newlines.ga_len; + + pt->pt_func = fp; + pt->pt_refcount = 1; + rettv->vval.v_partial = pt; + rettv->v_type = VAR_PARTIAL; + } + + eval_lavars_used = old_eval_lavars; + return OK; + +errret: + ga_clear_strings(&newargs); + xfree(fp); + eval_lavars_used = old_eval_lavars; + return FAIL; +} + +/// Return name of the function corresponding to `name` +/// +/// If `name` points to variable that is either a function or partial then +/// corresponding function name is returned. Otherwise it returns `name` itself. +/// +/// @param[in] name Function name to check. +/// @param[in,out] lenp Location where length of the returned name is stored. +/// Must be set to the length of the `name` argument. +/// @param[out] partialp Location where partial will be stored if found +/// function appears to be a partial. May be NULL if this +/// is not needed. +/// @param[in] no_autoload If true, do not source autoload scripts if function +/// was not found. +/// +/// @return name of the function. +char_u *deref_func_name(const char *name, int *lenp, + partial_T **const partialp, bool no_autoload) + FUNC_ATTR_NONNULL_ARG(1, 2) +{ + if (partialp != NULL) { + *partialp = NULL; + } + + dictitem_T *const v = find_var(name, (size_t)(*lenp), NULL, no_autoload); + if (v != NULL && v->di_tv.v_type == VAR_FUNC) { + if (v->di_tv.vval.v_string == NULL) { // just in case + *lenp = 0; + return (char_u *)""; + } + *lenp = (int)STRLEN(v->di_tv.vval.v_string); + return v->di_tv.vval.v_string; + } + + if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) { + partial_T *const pt = v->di_tv.vval.v_partial; + + if (pt == NULL) { // just in case + *lenp = 0; + return (char_u *)""; + } + if (partialp != NULL) { + *partialp = pt; + } + char_u *s = partial_name(pt); + *lenp = (int)STRLEN(s); + return s; + } + + return (char_u *)name; +} + +/// Give an error message with a function name. Handle <SNR> things. +/// +/// @param ermsg must be passed without translation (use N_() instead of _()). +/// @param name function name +void emsg_funcname(char *ermsg, const char_u *name) +{ + char_u *p; + + if (*name == K_SPECIAL) { + p = concat_str((char_u *)"<SNR>", name + 3); + } else { + p = (char_u *)name; + } + + EMSG2(_(ermsg), p); + + if (p != name) { + xfree(p); + } +} + +/* + * Allocate a variable for the result of a function. + * Return OK or FAIL. + */ +int +get_func_tv( + const char_u *name, // name of the function + int len, // length of "name" + typval_T *rettv, + char_u **arg, // argument, pointing to the '(' + linenr_T firstline, // first line of range + linenr_T lastline, // last line of range + int *doesrange, // return: function handled range + int evaluate, + partial_T *partial, // for extra arguments + dict_T *selfdict // Dictionary for "self" +) +{ + char_u *argp; + int ret = OK; + typval_T argvars[MAX_FUNC_ARGS + 1]; /* vars for arguments */ + int argcount = 0; /* number of arguments found */ + + /* + * Get the arguments. + */ + argp = *arg; + while (argcount < MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { + argp = skipwhite(argp + 1); // skip the '(' or ',' + if (*argp == ')' || *argp == ',' || *argp == NUL) { + break; + } + if (eval1(&argp, &argvars[argcount], evaluate) == FAIL) { + ret = FAIL; + break; + } + ++argcount; + if (*argp != ',') + break; + } + if (*argp == ')') + ++argp; + else + ret = FAIL; + + if (ret == OK) { + int i = 0; + + if (get_vim_var_nr(VV_TESTING)) { + // Prepare for calling garbagecollect_for_testing(), need to know + // what variables are used on the call stack. + if (funcargs.ga_itemsize == 0) { + ga_init(&funcargs, (int)sizeof(typval_T *), 50); + } + for (i = 0; i < argcount; i++) { + ga_grow(&funcargs, 1); + ((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i]; + } + } + ret = call_func(name, len, rettv, argcount, argvars, NULL, + firstline, lastline, doesrange, evaluate, + partial, selfdict); + + funcargs.ga_len -= i; + } else if (!aborting()) { + if (argcount == MAX_FUNC_ARGS) { + emsg_funcname(N_("E740: Too many arguments for function %s"), name); + } else { + emsg_funcname(N_("E116: Invalid arguments for function %s"), name); + } + } + + while (--argcount >= 0) { + tv_clear(&argvars[argcount]); + } + + *arg = skipwhite(argp); + return ret; +} + +#define FLEN_FIXED 40 + +/// Check whether function name starts with <SID> or s: +/// +/// @warning Only works for names previously checked by eval_fname_script(), if +/// it returned non-zero. +/// +/// @param[in] name Name to check. +/// +/// @return true if it starts with <SID> or s:, false otherwise. +static inline bool eval_fname_sid(const char *const name) + FUNC_ATTR_PURE FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ALL +{ + return *name == 's' || TOUPPER_ASC(name[2]) == 'I'; +} + +/// In a script transform script-local names into actually used names +/// +/// Transforms "<SID>" and "s:" prefixes to `K_SNR {N}` (e.g. K_SNR "123") and +/// "<SNR>" prefix to `K_SNR`. Uses `fname_buf` buffer that is supposed to have +/// #FLEN_FIXED + 1 length when it fits, otherwise it allocates memory. +/// +/// @param[in] name Name to transform. +/// @param fname_buf Buffer to save resulting function name to, if it fits. +/// Must have at least #FLEN_FIXED + 1 length. +/// @param[out] tofree Location where pointer to an allocated memory is saved +/// in case result does not fit into fname_buf. +/// @param[out] error Location where error type is saved, @see +/// FnameTransError. +/// +/// @return transformed name: either `fname_buf` or a pointer to an allocated +/// memory. +static char_u *fname_trans_sid(const char_u *const name, + char_u *const fname_buf, + char_u **const tofree, int *const error) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + char_u *fname; + const int llen = eval_fname_script((const char *)name); + if (llen > 0) { + fname_buf[0] = K_SPECIAL; + fname_buf[1] = KS_EXTRA; + fname_buf[2] = (int)KE_SNR; + int i = 3; + if (eval_fname_sid((const char *)name)) { // "<SID>" or "s:" + if (current_sctx.sc_sid <= 0) { + *error = ERROR_SCRIPT; + } else { + snprintf((char *)fname_buf + 3, FLEN_FIXED + 1, "%" PRId64 "_", + (int64_t)current_sctx.sc_sid); + i = (int)STRLEN(fname_buf); + } + } + if (i + STRLEN(name + llen) < FLEN_FIXED) { + STRCPY(fname_buf + i, name + llen); + fname = fname_buf; + } else { + fname = xmalloc(i + STRLEN(name + llen) + 1); + *tofree = fname; + memmove(fname, fname_buf, (size_t)i); + STRCPY(fname + i, name + llen); + } + } else { + fname = (char_u *)name; + } + + return fname; +} + +/// Find a function by name, return pointer to it in ufuncs. +/// @return NULL for unknown function. +ufunc_T *find_func(const char_u *name) +{ + hashitem_T *hi; + + hi = hash_find(&func_hashtab, name); + if (!HASHITEM_EMPTY(hi)) + return HI2UF(hi); + return NULL; +} + +/* + * Copy the function name of "fp" to buffer "buf". + * "buf" must be able to hold the function name plus three bytes. + * Takes care of script-local function names. + */ +static void cat_func_name(char_u *buf, ufunc_T *fp) +{ + if (fp->uf_name[0] == K_SPECIAL) { + STRCPY(buf, "<SNR>"); + STRCAT(buf, fp->uf_name + 3); + } else + STRCPY(buf, fp->uf_name); +} + +/* + * Add a number variable "name" to dict "dp" with value "nr". + */ +static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr) +{ +#ifndef __clang_analyzer__ + STRCPY(v->di_key, name); +#endif + v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + tv_dict_add(dp, v); + v->di_tv.v_type = VAR_NUMBER; + v->di_tv.v_lock = VAR_FIXED; + v->di_tv.vval.v_number = nr; +} + +/* + * Free "fc" and what it contains. + */ +static void +free_funccal( + funccall_T *fc, + int free_val // a: vars were allocated +) +{ + for (int i = 0; i < fc->fc_funcs.ga_len; i++) { + ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; + + // When garbage collecting a funccall_T may be freed before the + // function that references it, clear its uf_scoped field. + // The function may have been redefined and point to another + // funccal_T, don't clear it then. + if (fp != NULL && fp->uf_scoped == fc) { + fp->uf_scoped = NULL; + } + } + ga_clear(&fc->fc_funcs); + + // The a: variables typevals may not have been allocated, only free the + // allocated variables. + vars_clear_ext(&fc->l_avars.dv_hashtab, free_val); + + // Free all l: variables. + vars_clear(&fc->l_vars.dv_hashtab); + + // Free the a:000 variables if they were allocated. + if (free_val) { + TV_LIST_ITER(&fc->l_varlist, li, { + tv_clear(TV_LIST_ITEM_TV(li)); + }); + } + + func_ptr_unref(fc->func); + xfree(fc); +} + +/// Handle the last part of returning from a function: free the local hashtable. +/// Unless it is still in use by a closure. +static void cleanup_function_call(funccall_T *fc) +{ + current_funccal = fc->caller; + + // If the a:000 list and the l: and a: dicts are not referenced and there + // is no closure using it, we can free the funccall_T and what's in it. + if (!fc_referenced(fc)) { + free_funccal(fc, false); + } else { + static int made_copy = 0; + + // "fc" is still in use. This can happen when returning "a:000", + // assigning "l:" to a global variable or defining a closure. + // Link "fc" in the list for garbage collection later. + fc->caller = previous_funccal; + previous_funccal = fc; + + // Make a copy of the a: variables, since we didn't do that above. + TV_DICT_ITER(&fc->l_avars, di, { + tv_copy(&di->di_tv, &di->di_tv); + }); + + // Make a copy of the a:000 items, since we didn't do that above. + TV_LIST_ITER(&fc->l_varlist, li, { + tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li)); + }); + + if (++made_copy == 10000) { + // We have made a lot of copies. This can happen when + // repetitively calling a function that creates a reference to + // itself somehow. Call the garbage collector soon to avoid using + // too much memory. + made_copy = 0; + want_garbage_collect = true; + } + } +} + +/// Unreference "fc": decrement the reference count and free it when it +/// becomes zero. "fp" is detached from "fc". +/// +/// @param[in] force When true, we are exiting. +static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) +{ + funccall_T **pfc; + int i; + + if (fc == NULL) { + return; + } + + fc->fc_refcount--; + if (force ? fc->fc_refcount <= 0 : !fc_referenced(fc)) { + for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) { + if (fc == *pfc) { + *pfc = fc->caller; + free_funccal(fc, true); + return; + } + } + } + for (i = 0; i < fc->fc_funcs.ga_len; i++) { + if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) { + ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL; + } + } +} + +/// Remove the function from the function hashtable. If the function was +/// deleted while it still has references this was already done. +/// +/// @return true if the entry was deleted, false if it wasn't found. +static bool func_remove(ufunc_T *fp) +{ + hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp)); + + if (!HASHITEM_EMPTY(hi)) { + hash_remove(&func_hashtab, hi); + return true; + } + + return false; +} + +static void func_clear_items(ufunc_T *fp) +{ + ga_clear_strings(&(fp->uf_args)); + ga_clear_strings(&(fp->uf_lines)); + + XFREE_CLEAR(fp->uf_tml_count); + XFREE_CLEAR(fp->uf_tml_total); + XFREE_CLEAR(fp->uf_tml_self); +} + +/// Free all things that a function contains. Does not free the function +/// itself, use func_free() for that. +/// +/// param[in] force When true, we are exiting. +static void func_clear(ufunc_T *fp, bool force) +{ + if (fp->uf_cleared) { + return; + } + fp->uf_cleared = true; + + // clear this function + func_clear_items(fp); + funccal_unref(fp->uf_scoped, fp, force); +} + +/// Free a function and remove it from the list of functions. Does not free +/// what a function contains, call func_clear() first. +/// +/// param[in] fp The function to free. +static void func_free(ufunc_T *fp) +{ + // only remove it when not done already, otherwise we would remove a newer + // version of the function + if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) { + func_remove(fp); + } + xfree(fp); +} + +/// Free all things that a function contains and free the function itself. +/// +/// param[in] force When true, we are exiting. +static void func_clear_free(ufunc_T *fp, bool force) +{ + func_clear(fp, force); + func_free(fp); +} + +/// Call a user function +/// +/// @param fp Function to call. +/// @param[in] argcount Number of arguments. +/// @param argvars Arguments. +/// @param[out] rettv Return value. +/// @param[in] firstline First line of range. +/// @param[in] lastline Last line of range. +/// @param selfdict Dictionary for "self" for dictionary functions. +void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, + typval_T *rettv, linenr_T firstline, linenr_T lastline, + dict_T *selfdict) + FUNC_ATTR_NONNULL_ARG(1, 3, 4) +{ + char_u *save_sourcing_name; + linenr_T save_sourcing_lnum; + bool using_sandbox = false; + funccall_T *fc; + int save_did_emsg; + static int depth = 0; + dictitem_T *v; + int fixvar_idx = 0; // index in fixvar[] + int ai; + bool islambda = false; + char_u numbuf[NUMBUFLEN]; + char_u *name; + proftime_T wait_start; + proftime_T call_start; + int started_profiling = false; + bool did_save_redo = false; + save_redo_T save_redo; + + // If depth of calling is getting too high, don't execute the function + if (depth >= p_mfd) { + EMSG(_("E132: Function call depth is higher than 'maxfuncdepth'")); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = -1; + return; + } + ++depth; + // Save search patterns and redo buffer. + save_search_patterns(); + if (!ins_compl_active()) { + saveRedobuff(&save_redo); + did_save_redo = true; + } + ++fp->uf_calls; + // check for CTRL-C hit + line_breakcheck(); + // prepare the funccall_T structure + fc = xmalloc(sizeof(funccall_T)); + fc->caller = current_funccal; + current_funccal = fc; + fc->func = fp; + fc->rettv = rettv; + rettv->vval.v_number = 0; + fc->linenr = 0; + fc->returned = FALSE; + fc->level = ex_nesting_level; + // Check if this function has a breakpoint. + fc->breakpoint = dbg_find_breakpoint(false, fp->uf_name, (linenr_T)0); + fc->dbg_tick = debug_tick; + + // Set up fields for closure. + fc->fc_refcount = 0; + fc->fc_copyID = 0; + ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1); + func_ptr_ref(fp); + + if (STRNCMP(fp->uf_name, "<lambda>", 8) == 0) { + islambda = true; + } + + // Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables + // with names up to VAR_SHORT_LEN long. This avoids having to alloc/free + // each argument variable and saves a lot of time. + // + // Init l: variables. + init_var_dict(&fc->l_vars, &fc->l_vars_var, VAR_DEF_SCOPE); + if (selfdict != NULL) { + // Set l:self to "selfdict". Use "name" to avoid a warning from + // some compiler that checks the destination size. + v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; +#ifndef __clang_analyzer__ + name = v->di_key; + STRCPY(name, "self"); +#endif + v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + tv_dict_add(&fc->l_vars, v); + v->di_tv.v_type = VAR_DICT; + v->di_tv.v_lock = 0; + v->di_tv.vval.v_dict = selfdict; + ++selfdict->dv_refcount; + } + + /* + * Init a: variables. + * Set a:0 to "argcount". + * Set a:000 to a list with room for the "..." arguments. + */ + init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE); + add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0", + (varnumber_T)(argcount - fp->uf_args.ga_len)); + fc->l_avars.dv_lock = VAR_FIXED; + // Use "name" to avoid a warning from some compiler that checks the + // destination size. + v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; +#ifndef __clang_analyzer__ + name = v->di_key; + STRCPY(name, "000"); +#endif + v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + tv_dict_add(&fc->l_avars, v); + v->di_tv.v_type = VAR_LIST; + v->di_tv.v_lock = VAR_FIXED; + v->di_tv.vval.v_list = &fc->l_varlist; + tv_list_init_static(&fc->l_varlist); + tv_list_set_lock(&fc->l_varlist, VAR_FIXED); + + // Set a:firstline to "firstline" and a:lastline to "lastline". + // Set a:name to named arguments. + // Set a:N to the "..." arguments. + add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], + "firstline", (varnumber_T)firstline); + add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], + "lastline", (varnumber_T)lastline); + for (int i = 0; i < argcount; i++) { + bool addlocal = false; + + ai = i - fp->uf_args.ga_len; + if (ai < 0) { + // named argument a:name + name = FUNCARG(fp, i); + if (islambda) { + addlocal = true; + } + } else { + // "..." argument a:1, a:2, etc. + snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1); + name = numbuf; + } + if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) { + v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; + v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + } else { + v = xmalloc(sizeof(dictitem_T) + STRLEN(name)); + v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC; + } + STRCPY(v->di_key, name); + + // Note: the values are copied directly to avoid alloc/free. + // "argvars" must have VAR_FIXED for v_lock. + v->di_tv = argvars[i]; + v->di_tv.v_lock = VAR_FIXED; + + if (addlocal) { + // Named arguments can be accessed without the "a:" prefix in lambda + // expressions. Add to the l: dict. + tv_copy(&v->di_tv, &v->di_tv); + tv_dict_add(&fc->l_vars, v); + } else { + tv_dict_add(&fc->l_avars, v); + } + + if (ai >= 0 && ai < MAX_FUNC_ARGS) { + tv_list_append(&fc->l_varlist, &fc->l_listitems[ai]); + *TV_LIST_ITEM_TV(&fc->l_listitems[ai]) = argvars[i]; + TV_LIST_ITEM_TV(&fc->l_listitems[ai])->v_lock = VAR_FIXED; + } + } + + // Don't redraw while executing the function. + RedrawingDisabled++; + save_sourcing_name = sourcing_name; + save_sourcing_lnum = sourcing_lnum; + sourcing_lnum = 1; + + if (fp->uf_flags & FC_SANDBOX) { + using_sandbox = true; + sandbox++; + } + + // need space for new sourcing_name: + // * save_sourcing_name + // * "["number"].." or "function " + // * "<SNR>" + fp->uf_name - 3 + // * terminating NUL + size_t len = (save_sourcing_name == NULL ? 0 : STRLEN(save_sourcing_name)) + + STRLEN(fp->uf_name) + 27; + sourcing_name = xmalloc(len); + { + if (save_sourcing_name != NULL + && STRNCMP(save_sourcing_name, "function ", 9) == 0) { + vim_snprintf((char *)sourcing_name, + len, + "%s[%" PRId64 "]..", + save_sourcing_name, + (int64_t)save_sourcing_lnum); + } else { + STRCPY(sourcing_name, "function "); + } + cat_func_name(sourcing_name + STRLEN(sourcing_name), fp); + + if (p_verbose >= 12) { + ++no_wait_return; + verbose_enter_scroll(); + + smsg(_("calling %s"), sourcing_name); + if (p_verbose >= 14) { + msg_puts("("); + for (int i = 0; i < argcount; i++) { + if (i > 0) { + msg_puts(", "); + } + if (argvars[i].v_type == VAR_NUMBER) { + msg_outnum((long)argvars[i].vval.v_number); + } else { + // Do not want errors such as E724 here. + emsg_off++; + char *tofree = encode_tv2string(&argvars[i], NULL); + emsg_off--; + if (tofree != NULL) { + char *s = tofree; + char buf[MSG_BUF_LEN]; + if (vim_strsize((char_u *)s) > MSG_BUF_CLEN) { + trunc_string((char_u *)s, (char_u *)buf, MSG_BUF_CLEN, + sizeof(buf)); + s = buf; + } + msg_puts(s); + xfree(tofree); + } + } + } + msg_puts(")"); + } + msg_puts("\n"); // don't overwrite this either + + verbose_leave_scroll(); + --no_wait_return; + } + } + + const bool do_profiling_yes = do_profiling == PROF_YES; + + bool func_not_yet_profiling_but_should = + do_profiling_yes + && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL); + + if (func_not_yet_profiling_but_should) { + started_profiling = true; + func_do_profile(fp); + } + + bool func_or_func_caller_profiling = + do_profiling_yes + && (fp->uf_profiling + || (fc->caller != NULL && fc->caller->func->uf_profiling)); + + if (func_or_func_caller_profiling) { + ++fp->uf_tm_count; + call_start = profile_start(); + fp->uf_tm_children = profile_zero(); + } + + if (do_profiling_yes) { + script_prof_save(&wait_start); + } + + const sctx_T save_current_sctx = current_sctx; + current_sctx = fp->uf_script_ctx; + save_did_emsg = did_emsg; + did_emsg = FALSE; + + // call do_cmdline() to execute the lines + do_cmdline(NULL, get_func_line, (void *)fc, + DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); + + --RedrawingDisabled; + + // when the function was aborted because of an error, return -1 + if ((did_emsg + && (fp->uf_flags & FC_ABORT)) || rettv->v_type == VAR_UNKNOWN) { + tv_clear(rettv); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = -1; + } + + if (func_or_func_caller_profiling) { + call_start = profile_end(call_start); + call_start = profile_sub_wait(wait_start, call_start); // -V614 + fp->uf_tm_total = profile_add(fp->uf_tm_total, call_start); + fp->uf_tm_self = profile_self(fp->uf_tm_self, call_start, + fp->uf_tm_children); + if (fc->caller != NULL && fc->caller->func->uf_profiling) { + fc->caller->func->uf_tm_children = + profile_add(fc->caller->func->uf_tm_children, call_start); + fc->caller->func->uf_tml_children = + profile_add(fc->caller->func->uf_tml_children, call_start); + } + if (started_profiling) { + // make a ":profdel func" stop profiling the function + fp->uf_profiling = false; + } + } + + // when being verbose, mention the return value + if (p_verbose >= 12) { + ++no_wait_return; + verbose_enter_scroll(); + + if (aborting()) + smsg(_("%s aborted"), sourcing_name); + else if (fc->rettv->v_type == VAR_NUMBER) + smsg(_("%s returning #%" PRId64 ""), + sourcing_name, (int64_t)fc->rettv->vval.v_number); + else { + char_u buf[MSG_BUF_LEN]; + + // The value may be very long. Skip the middle part, so that we + // have some idea how it starts and ends. smsg() would always + // truncate it at the end. Don't want errors such as E724 here. + emsg_off++; + char_u *s = (char_u *) encode_tv2string(fc->rettv, NULL); + char_u *tofree = s; + emsg_off--; + if (s != NULL) { + if (vim_strsize(s) > MSG_BUF_CLEN) { + trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN); + s = buf; + } + smsg(_("%s returning %s"), sourcing_name, s); + xfree(tofree); + } + } + msg_puts("\n"); // don't overwrite this either + + verbose_leave_scroll(); + --no_wait_return; + } + + xfree(sourcing_name); + sourcing_name = save_sourcing_name; + sourcing_lnum = save_sourcing_lnum; + current_sctx = save_current_sctx; + if (do_profiling_yes) { + script_prof_restore(&wait_start); + } + if (using_sandbox) { + sandbox--; + } + + if (p_verbose >= 12 && sourcing_name != NULL) { + ++no_wait_return; + verbose_enter_scroll(); + + smsg(_("continuing in %s"), sourcing_name); + msg_puts("\n"); // don't overwrite this either + + verbose_leave_scroll(); + --no_wait_return; + } + + did_emsg |= save_did_emsg; + depth--; + + cleanup_function_call(fc); + + if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) { + // Function was unreferenced while being used, free it now. + func_clear_free(fp, false); + } + // restore search patterns and redo buffer + if (did_save_redo) { + restoreRedobuff(&save_redo); + } + restore_search_patterns(); +} + +/// There are two kinds of function names: +/// 1. ordinary names, function defined with :function +/// 2. numbered functions and lambdas +/// For the first we only count the name stored in func_hashtab as a reference, +/// using function() does not count as a reference, because the function is +/// looked up by name. +static bool func_name_refcount(char_u *name) +{ + return isdigit(*name) || *name == '<'; +} + +/* + * Save the current function call pointer, and set it to NULL. + * Used when executing autocommands and for ":source". + */ +void *save_funccal(void) +{ + funccall_T *fc = current_funccal; + + current_funccal = NULL; + return (void *)fc; +} + +void restore_funccal(void *vfc) +{ + current_funccal = (funccall_T *)vfc; +} + +funccall_T *get_current_funccal(void) +{ + return current_funccal; +} + +#if defined(EXITFREE) +void free_all_functions(void) +{ + hashitem_T *hi; + ufunc_T *fp; + uint64_t skipped = 0; + uint64_t todo = 1; + uint64_t used; + + // Clean up the call stack. + while (current_funccal != NULL) { + tv_clear(current_funccal->rettv); + cleanup_function_call(current_funccal); + } + + // First clear what the functions contain. Since this may lower the + // reference count of a function, it may also free a function and change + // the hash table. Restart if that happens. + while (todo > 0) { + todo = func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + // Only free functions that are not refcounted, those are + // supposed to be freed when no longer referenced. + fp = HI2UF(hi); + if (func_name_refcount(fp->uf_name)) { + skipped++; + } else { + used = func_hashtab.ht_used; + func_clear(fp, true); + if (used != func_hashtab.ht_used) { + skipped = 0; + break; + } + } + todo--; + } + } + } + + // Now actually free the functions. Need to start all over every time, + // because func_free() may change the hash table. + skipped = 0; + while (func_hashtab.ht_used > skipped) { + todo = func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + todo--; + // Only free functions that are not refcounted, those are + // supposed to be freed when no longer referenced. + fp = HI2UF(hi); + if (func_name_refcount(fp->uf_name)) { + skipped++; + } else { + func_free(fp); + skipped = 0; + break; + } + } + } + } + if (skipped == 0) { + hash_clear(&func_hashtab); + } +} + +#endif + +/// Checks if a builtin function with the given name exists. +/// +/// @param[in] name name of the builtin function to check. +/// @param[in] len length of "name", or -1 for NUL terminated. +/// +/// @return true if "name" looks like a builtin function name: starts with a +/// lower case letter and doesn't contain AUTOLOAD_CHAR. +static bool builtin_function(const char *name, int len) +{ + if (!ASCII_ISLOWER(name[0])) { + return false; + } + + const char *p = (len == -1 + ? strchr(name, AUTOLOAD_CHAR) + : memchr(name, AUTOLOAD_CHAR, (size_t)len)); + + return p == NULL; +} + +int func_call(char_u *name, typval_T *args, partial_T *partial, + dict_T *selfdict, typval_T *rettv) +{ + typval_T argv[MAX_FUNC_ARGS + 1]; + int argc = 0; + int dummy; + int r = 0; + + TV_LIST_ITER(args->vval.v_list, item, { + if (argc == MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { + EMSG(_("E699: Too many arguments")); + goto func_call_skip_call; + } + // Make a copy of each argument. This is needed to be able to set + // v_lock to VAR_FIXED in the copy without changing the original list. + tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]); + }); + + r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL, + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &dummy, true, partial, selfdict); + +func_call_skip_call: + // Free the arguments. + while (argc > 0) { + tv_clear(&argv[--argc]); + } + + return r; +} + +/// Call a function with its resolved parameters +/// +/// "argv_func", when not NULL, can be used to fill in arguments only when the +/// invoked function uses them. It is called like this: +/// new_argcount = argv_func(current_argcount, argv, called_func_argcount) +/// +/// @return FAIL if function cannot be called, else OK (even if an error +/// occurred while executing the function! Set `msg_list` to capture +/// the error, see do_cmdline()). +int +call_func( + const char_u *funcname, // name of the function + int len, // length of "name" + typval_T *rettv, // [out] value goes here + int argcount_in, // number of "argvars" + typval_T *argvars_in, // vars for arguments, must have "argcount" + // PLUS ONE elements! + ArgvFunc argv_func, // function to fill in argvars + linenr_T firstline, // first line of range + linenr_T lastline, // last line of range + int *doesrange, // [out] function handled range + bool evaluate, + partial_T *partial, // optional, can be NULL + dict_T *selfdict_in // Dictionary for "self" +) + FUNC_ATTR_NONNULL_ARG(1, 3, 5, 9) +{ + int ret = FAIL; + int error = ERROR_NONE; + ufunc_T *fp; + char_u fname_buf[FLEN_FIXED + 1]; + char_u *tofree = NULL; + char_u *fname; + char_u *name; + int argcount = argcount_in; + typval_T *argvars = argvars_in; + dict_T *selfdict = selfdict_in; + typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" is not NULL + int argv_clear = 0; + + // Initialize rettv so that it is safe for caller to invoke clear_tv(rettv) + // even when call_func() returns FAIL. + rettv->v_type = VAR_UNKNOWN; + + // Make a copy of the name, if it comes from a funcref variable it could + // be changed or deleted in the called function. + name = vim_strnsave(funcname, len); + + fname = fname_trans_sid(name, fname_buf, &tofree, &error); + + *doesrange = false; + + if (partial != NULL) { + // When the function has a partial with a dict and there is a dict + // argument, use the dict argument. That is backwards compatible. + // When the dict was bound explicitly use the one from the partial. + if (partial->pt_dict != NULL + && (selfdict_in == NULL || !partial->pt_auto)) { + selfdict = partial->pt_dict; + } + if (error == ERROR_NONE && partial->pt_argc > 0) { + for (argv_clear = 0; argv_clear < partial->pt_argc; argv_clear++) { + if (argv_clear + argcount_in >= MAX_FUNC_ARGS) { + error = ERROR_TOOMANY; + goto theend; + } + tv_copy(&partial->pt_argv[argv_clear], &argv[argv_clear]); + } + for (int i = 0; i < argcount_in; i++) { + argv[i + argv_clear] = argvars_in[i]; + } + argvars = argv; + argcount = partial->pt_argc + argcount_in; + } + } + + if (error == ERROR_NONE && evaluate) { + char_u *rfname = fname; + + // Ignore "g:" before a function name. + if (fname[0] == 'g' && fname[1] == ':') { + rfname = fname + 2; + } + + rettv->v_type = VAR_NUMBER; // default rettv is number zero + rettv->vval.v_number = 0; + error = ERROR_UNKNOWN; + + if (is_luafunc(partial)) { + if (len > 0) { + error = ERROR_NONE; + executor_call_lua((const char *)funcname, len, + argvars, argcount, rettv); + } + } else if (!builtin_function((const char *)rfname, -1)) { + // User defined function. + if (partial != NULL && partial->pt_func != NULL) { + fp = partial->pt_func; + } else { + fp = find_func(rfname); + } + + // Trigger FuncUndefined event, may load the function. + if (fp == NULL + && apply_autocmds(EVENT_FUNCUNDEFINED, rfname, rfname, TRUE, NULL) + && !aborting()) { + // executed an autocommand, search for the function again + fp = find_func(rfname); + } + // Try loading a package. + if (fp == NULL && script_autoload((const char *)rfname, STRLEN(rfname), + true) && !aborting()) { + // Loaded a package, search for the function again. + fp = find_func(rfname); + } + + if (fp != NULL && (fp->uf_flags & FC_DELETED)) { + error = ERROR_DELETED; + } else if (fp != NULL) { + if (argv_func != NULL) { + // postponed filling in the arguments, do it now + argcount = argv_func(argcount, argvars, argv_clear, + fp->uf_args.ga_len); + } + if (fp->uf_flags & FC_RANGE) { + *doesrange = true; + } + if (argcount < fp->uf_args.ga_len) { + error = ERROR_TOOFEW; + } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) { + error = ERROR_TOOMANY; + } else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) { + error = ERROR_DICT; + } else { + // Call the user function. + call_user_func(fp, argcount, argvars, rettv, firstline, lastline, + (fp->uf_flags & FC_DICT) ? selfdict : NULL); + error = ERROR_NONE; + } + } + } else { + // Find the function name in the table, call its implementation. + const VimLFuncDef *const fdef = find_internal_func((const char *)fname); + if (fdef != NULL) { + if (argcount < fdef->min_argc) { + error = ERROR_TOOFEW; + } else if (argcount > fdef->max_argc) { + error = ERROR_TOOMANY; + } else { + argvars[argcount].v_type = VAR_UNKNOWN; + fdef->func(argvars, rettv, fdef->data); + error = ERROR_NONE; + } + } + } + /* + * The function call (or "FuncUndefined" autocommand sequence) might + * have been aborted by an error, an interrupt, or an explicitly thrown + * exception that has not been caught so far. This situation can be + * tested for by calling aborting(). For an error in an internal + * function or for the "E132" error in call_user_func(), however, the + * throw point at which the "force_abort" flag (temporarily reset by + * emsg()) is normally updated has not been reached yet. We need to + * update that flag first to make aborting() reliable. + */ + update_force_abort(); + } + if (error == ERROR_NONE) + ret = OK; + +theend: + // Report an error unless the argument evaluation or function call has been + // cancelled due to an aborting error, an interrupt, or an exception. + if (!aborting()) { + switch (error) { + case ERROR_UNKNOWN: + emsg_funcname(N_("E117: Unknown function: %s"), name); + break; + case ERROR_DELETED: + emsg_funcname(N_("E933: Function was deleted: %s"), name); + break; + case ERROR_TOOMANY: + emsg_funcname(_(e_toomanyarg), name); + break; + case ERROR_TOOFEW: + emsg_funcname(N_("E119: Not enough arguments for function: %s"), + name); + break; + case ERROR_SCRIPT: + emsg_funcname(N_("E120: Using <SID> not in a script context: %s"), + name); + break; + case ERROR_DICT: + emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"), + name); + break; + } + } + + while (argv_clear > 0) { + tv_clear(&argv[--argv_clear]); + } + xfree(tofree); + xfree(name); + + return ret; +} + +/// List the head of the function: "name(arg1, arg2)". +/// +/// @param[in] fp Function pointer. +/// @param[in] indent Indent line. +/// @param[in] force Include bang "!" (i.e.: "function!"). +static void list_func_head(ufunc_T *fp, int indent, bool force) +{ + msg_start(); + if (indent) + MSG_PUTS(" "); + MSG_PUTS(force ? "function! " : "function "); + if (fp->uf_name[0] == K_SPECIAL) { + MSG_PUTS_ATTR("<SNR>", HL_ATTR(HLF_8)); + msg_puts((const char *)fp->uf_name + 3); + } else { + msg_puts((const char *)fp->uf_name); + } + msg_putchar('('); + int j; + for (j = 0; j < fp->uf_args.ga_len; j++) { + if (j) { + msg_puts(", "); + } + msg_puts((const char *)FUNCARG(fp, j)); + } + if (fp->uf_varargs) { + if (j) { + msg_puts(", "); + } + msg_puts("..."); + } + msg_putchar(')'); + if (fp->uf_flags & FC_ABORT) { + msg_puts(" abort"); + } + if (fp->uf_flags & FC_RANGE) { + msg_puts(" range"); + } + if (fp->uf_flags & FC_DICT) { + msg_puts(" dict"); + } + if (fp->uf_flags & FC_CLOSURE) { + msg_puts(" closure"); + } + msg_clr_eos(); + if (p_verbose > 0) { + last_set_msg(fp->uf_script_ctx); + } +} + +/// Get a function name, translating "<SID>" and "<SNR>". +/// Also handles a Funcref in a List or Dictionary. +/// flags: +/// TFN_INT: internal function name OK +/// TFN_QUIET: be quiet +/// TFN_NO_AUTOLOAD: do not use script autoloading +/// TFN_NO_DEREF: do not dereference a Funcref +/// Advances "pp" to just after the function name (if no error). +/// +/// @return the function name in allocated memory, or NULL for failure. +char_u * +trans_function_name( + char_u **pp, + bool skip, // only find the end, don't evaluate + int flags, + funcdict_T *fdp, // return: info about dictionary used + partial_T **partial // return: partial of a FuncRef +) + FUNC_ATTR_NONNULL_ARG(1) +{ + char_u *name = NULL; + const char_u *start; + const char_u *end; + int lead; + int len; + lval_T lv; + + if (fdp != NULL) + memset(fdp, 0, sizeof(funcdict_T)); + start = *pp; + + /* Check for hard coded <SNR>: already translated function ID (from a user + * command). */ + if ((*pp)[0] == K_SPECIAL && (*pp)[1] == KS_EXTRA + && (*pp)[2] == (int)KE_SNR) { + *pp += 3; + len = get_id_len((const char **)pp) + 3; + return (char_u *)xmemdupz(start, len); + } + + /* A name starting with "<SID>" or "<SNR>" is local to a script. But + * don't skip over "s:", get_lval() needs it for "s:dict.func". */ + lead = eval_fname_script((const char *)start); + if (lead > 2) { + start += lead; + } + + // Note that TFN_ flags use the same values as GLV_ flags. + end = get_lval((char_u *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY, + lead > 2 ? 0 : FNE_CHECK_START); + if (end == start) { + if (!skip) + EMSG(_("E129: Function name required")); + goto theend; + } + if (end == NULL || (lv.ll_tv != NULL && (lead > 2 || lv.ll_range))) { + /* + * Report an invalid expression in braces, unless the expression + * evaluation has been cancelled due to an aborting error, an + * interrupt, or an exception. + */ + if (!aborting()) { + if (end != NULL) { + emsgf(_(e_invarg2), start); + } + } else { + *pp = (char_u *)find_name_end(start, NULL, NULL, FNE_INCL_BR); + } + goto theend; + } + + if (lv.ll_tv != NULL) { + if (fdp != NULL) { + fdp->fd_dict = lv.ll_dict; + fdp->fd_newkey = lv.ll_newkey; + lv.ll_newkey = NULL; + fdp->fd_di = lv.ll_di; + } + if (lv.ll_tv->v_type == VAR_FUNC && lv.ll_tv->vval.v_string != NULL) { + name = vim_strsave(lv.ll_tv->vval.v_string); + *pp = (char_u *)end; + } else if (lv.ll_tv->v_type == VAR_PARTIAL + && lv.ll_tv->vval.v_partial != NULL) { + if (is_luafunc(lv.ll_tv->vval.v_partial) && *end == '.') { + len = check_luafunc_name((const char *)end+1, true); + if (len == 0) { + EMSG2(e_invexpr2, "v:lua"); + goto theend; + } + name = xmallocz(len); + memcpy(name, end+1, len); + *pp = (char_u *)end+1+len; + } else { + name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial)); + *pp = (char_u *)end; + } + if (partial != NULL) { + *partial = lv.ll_tv->vval.v_partial; + } + } else { + if (!skip && !(flags & TFN_QUIET) && (fdp == NULL + || lv.ll_dict == NULL + || fdp->fd_newkey == NULL)) { + EMSG(_(e_funcref)); + } else { + *pp = (char_u *)end; + } + name = NULL; + } + goto theend; + } + + if (lv.ll_name == NULL) { + // Error found, but continue after the function name. + *pp = (char_u *)end; + goto theend; + } + + /* Check if the name is a Funcref. If so, use the value. */ + if (lv.ll_exp_name != NULL) { + len = (int)strlen(lv.ll_exp_name); + name = deref_func_name(lv.ll_exp_name, &len, partial, + flags & TFN_NO_AUTOLOAD); + if ((const char *)name == lv.ll_exp_name) { + name = NULL; + } + } else if (!(flags & TFN_NO_DEREF)) { + len = (int)(end - *pp); + name = deref_func_name((const char *)(*pp), &len, partial, + flags & TFN_NO_AUTOLOAD); + if (name == *pp) { + name = NULL; + } + } + if (name != NULL) { + name = vim_strsave(name); + *pp = (char_u *)end; + if (strncmp((char *)name, "<SNR>", 5) == 0) { + // Change "<SNR>" to the byte sequence. + name[0] = K_SPECIAL; + name[1] = KS_EXTRA; + name[2] = (int)KE_SNR; + memmove(name + 3, name + 5, strlen((char *)name + 5) + 1); + } + goto theend; + } + + if (lv.ll_exp_name != NULL) { + len = (int)strlen(lv.ll_exp_name); + if (lead <= 2 && lv.ll_name == lv.ll_exp_name + && lv.ll_name_len >= 2 && memcmp(lv.ll_name, "s:", 2) == 0) { + // When there was "s:" already or the name expanded to get a + // leading "s:" then remove it. + lv.ll_name += 2; + lv.ll_name_len -= 2; + len -= 2; + lead = 2; + } + } else { + // Skip over "s:" and "g:". + if (lead == 2 || (lv.ll_name[0] == 'g' && lv.ll_name[1] == ':')) { + lv.ll_name += 2; + lv.ll_name_len -= 2; + } + len = (int)((const char *)end - lv.ll_name); + } + + size_t sid_buf_len = 0; + char sid_buf[20]; + + // Copy the function name to allocated memory. + // Accept <SID>name() inside a script, translate into <SNR>123_name(). + // Accept <SNR>123_name() outside a script. + if (skip) { + lead = 0; // do nothing + } else if (lead > 0) { + lead = 3; + if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) + || eval_fname_sid((const char *)(*pp))) { + // It's "s:" or "<SID>". + if (current_sctx.sc_sid <= 0) { + EMSG(_(e_usingsid)); + goto theend; + } + sid_buf_len = snprintf(sid_buf, sizeof(sid_buf), + "%" PRIdSCID "_", current_sctx.sc_sid); + lead += sid_buf_len; + } + } else if (!(flags & TFN_INT) + && builtin_function(lv.ll_name, lv.ll_name_len)) { + EMSG2(_("E128: Function name must start with a capital or \"s:\": %s"), + start); + goto theend; + } + + if (!skip && !(flags & TFN_QUIET) && !(flags & TFN_NO_DEREF)) { + char_u *cp = xmemrchr(lv.ll_name, ':', lv.ll_name_len); + + if (cp != NULL && cp < end) { + EMSG2(_("E884: Function name cannot contain a colon: %s"), start); + goto theend; + } + } + + name = xmalloc(len + lead + 1); + if (!skip && lead > 0) { + name[0] = K_SPECIAL; + name[1] = KS_EXTRA; + name[2] = (int)KE_SNR; + if (sid_buf_len > 0) { // If it's "<SID>" + memcpy(name + 3, sid_buf, sid_buf_len); + } + } + memmove(name + lead, lv.ll_name, len); + name[lead + len] = NUL; + *pp = (char_u *)end; + +theend: + clear_lval(&lv); + return name; +} + +/* + * ":function" + */ +void ex_function(exarg_T *eap) +{ + char_u *theline; + char_u *line_to_free = NULL; + int c; + int saved_did_emsg; + int saved_wait_return = need_wait_return; + char_u *name = NULL; + char_u *p; + char_u *arg; + char_u *line_arg = NULL; + garray_T newargs; + garray_T newlines; + int varargs = false; + int flags = 0; + ufunc_T *fp; + bool overwrite = false; + int indent; + int nesting; + dictitem_T *v; + funcdict_T fudi; + static int func_nr = 0; // number for nameless function + int paren; + hashtab_T *ht; + int todo; + hashitem_T *hi; + linenr_T sourcing_lnum_off; + linenr_T sourcing_lnum_top; + bool is_heredoc = false; + char_u *skip_until = NULL; + char_u *heredoc_trimmed = NULL; + bool show_block = false; + bool do_concat = true; + + /* + * ":function" without argument: list functions. + */ + if (ends_excmd(*eap->arg)) { + if (!eap->skip) { + todo = (int)func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + fp = HI2UF(hi); + if (message_filtered(fp->uf_name)) { + continue; + } + if (!func_name_refcount(fp->uf_name)) { + list_func_head(fp, false, false); + } + } + } + } + eap->nextcmd = check_nextcmd(eap->arg); + return; + } + + /* + * ":function /pat": list functions matching pattern. + */ + if (*eap->arg == '/') { + p = skip_regexp(eap->arg + 1, '/', TRUE, NULL); + if (!eap->skip) { + regmatch_T regmatch; + + c = *p; + *p = NUL; + regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC); + *p = c; + if (regmatch.regprog != NULL) { + regmatch.rm_ic = p_ic; + + todo = (int)func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + fp = HI2UF(hi); + if (!isdigit(*fp->uf_name) + && vim_regexec(®match, fp->uf_name, 0)) + list_func_head(fp, false, false); + } + } + vim_regfree(regmatch.regprog); + } + } + if (*p == '/') + ++p; + eap->nextcmd = check_nextcmd(p); + return; + } + + // Get the function name. There are these situations: + // func function name + // "name" == func, "fudi.fd_dict" == NULL + // dict.func new dictionary entry + // "name" == NULL, "fudi.fd_dict" set, + // "fudi.fd_di" == NULL, "fudi.fd_newkey" == func + // dict.func existing dict entry with a Funcref + // "name" == func, "fudi.fd_dict" set, + // "fudi.fd_di" set, "fudi.fd_newkey" == NULL + // dict.func existing dict entry that's not a Funcref + // "name" == NULL, "fudi.fd_dict" set, + // "fudi.fd_di" set, "fudi.fd_newkey" == NULL + // s:func script-local function name + // g:func global function name, same as "func" + p = eap->arg; + name = trans_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL); + paren = (vim_strchr(p, '(') != NULL); + if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { + /* + * Return on an invalid expression in braces, unless the expression + * evaluation has been cancelled due to an aborting error, an + * interrupt, or an exception. + */ + if (!aborting()) { + if (fudi.fd_newkey != NULL) { + EMSG2(_(e_dictkey), fudi.fd_newkey); + } + xfree(fudi.fd_newkey); + return; + } else + eap->skip = TRUE; + } + + /* An error in a function call during evaluation of an expression in magic + * braces should not cause the function not to be defined. */ + saved_did_emsg = did_emsg; + did_emsg = FALSE; + + // + // ":function func" with only function name: list function. + // If bang is given: + // - include "!" in function head + // - exclude line numbers from function body + // + if (!paren) { + if (!ends_excmd(*skipwhite(p))) { + EMSG(_(e_trailing)); + goto ret_free; + } + eap->nextcmd = check_nextcmd(p); + if (eap->nextcmd != NULL) + *p = NUL; + if (!eap->skip && !got_int) { + fp = find_func(name); + if (fp != NULL) { + list_func_head(fp, !eap->forceit, eap->forceit); + for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) { + if (FUNCLINE(fp, j) == NULL) { + continue; + } + msg_putchar('\n'); + if (!eap->forceit) { + msg_outnum((long)j + 1); + if (j < 9) { + msg_putchar(' '); + } + if (j < 99) { + msg_putchar(' '); + } + } + msg_prt_line(FUNCLINE(fp, j), false); + ui_flush(); // show a line at a time + os_breakcheck(); + } + if (!got_int) { + msg_putchar('\n'); + msg_puts(eap->forceit ? "endfunction" : " endfunction"); + } + } else + emsg_funcname(N_("E123: Undefined function: %s"), name); + } + goto ret_free; + } + + /* + * ":function name(arg1, arg2)" Define function. + */ + p = skipwhite(p); + if (*p != '(') { + if (!eap->skip) { + EMSG2(_("E124: Missing '(': %s"), eap->arg); + goto ret_free; + } + // attempt to continue by skipping some text + if (vim_strchr(p, '(') != NULL) { + p = vim_strchr(p, '('); + } + } + p = skipwhite(p + 1); + + ga_init(&newargs, (int)sizeof(char_u *), 3); + ga_init(&newlines, (int)sizeof(char_u *), 3); + + if (!eap->skip) { + /* Check the name of the function. Unless it's a dictionary function + * (that we are overwriting). */ + if (name != NULL) + arg = name; + else + arg = fudi.fd_newkey; + if (arg != NULL && (fudi.fd_di == NULL || !tv_is_func(fudi.fd_di->di_tv))) { + int j = (*arg == K_SPECIAL) ? 3 : 0; + while (arg[j] != NUL && (j == 0 ? eval_isnamec1(arg[j]) + : eval_isnamec(arg[j]))) + ++j; + if (arg[j] != NUL) + emsg_funcname((char *)e_invarg2, arg); + } + // Disallow using the g: dict. + if (fudi.fd_dict != NULL && fudi.fd_dict->dv_scope == VAR_DEF_SCOPE) { + EMSG(_("E862: Cannot use g: here")); + } + } + + if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) { + goto errret_2; + } + + if (KeyTyped && ui_has(kUICmdline)) { + show_block = true; + ui_ext_cmdline_block_append(0, (const char *)eap->cmd); + } + + // find extra arguments "range", "dict", "abort" and "closure" + for (;; ) { + p = skipwhite(p); + if (STRNCMP(p, "range", 5) == 0) { + flags |= FC_RANGE; + p += 5; + } else if (STRNCMP(p, "dict", 4) == 0) { + flags |= FC_DICT; + p += 4; + } else if (STRNCMP(p, "abort", 5) == 0) { + flags |= FC_ABORT; + p += 5; + } else if (STRNCMP(p, "closure", 7) == 0) { + flags |= FC_CLOSURE; + p += 7; + if (current_funccal == NULL) { + emsg_funcname(N_ + ("E932: Closure function should not be at top level: %s"), + name == NULL ? (char_u *)"" : name); + goto erret; + } + } else { + break; + } + } + + /* When there is a line break use what follows for the function body. + * Makes 'exe "func Test()\n...\nendfunc"' work. */ + if (*p == '\n') { + line_arg = p + 1; + } else if (*p != NUL && *p != '"' && !eap->skip && !did_emsg) { + EMSG(_(e_trailing)); + } + + /* + * Read the body of the function, until ":endfunction" is found. + */ + if (KeyTyped) { + /* Check if the function already exists, don't let the user type the + * whole function before telling him it doesn't work! For a script we + * need to skip the body to be able to find what follows. */ + if (!eap->skip && !eap->forceit) { + if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL) + EMSG(_(e_funcdict)); + else if (name != NULL && find_func(name) != NULL) + emsg_funcname(e_funcexts, name); + } + + if (!eap->skip && did_emsg) + goto erret; + + if (!ui_has(kUICmdline)) { + msg_putchar('\n'); // don't overwrite the function name + } + cmdline_row = msg_row; + } + + // Save the starting line number. + sourcing_lnum_top = sourcing_lnum; + + indent = 2; + nesting = 0; + for (;; ) { + if (KeyTyped) { + msg_scroll = true; + saved_wait_return = false; + } + need_wait_return = false; + + if (line_arg != NULL) { + // Use eap->arg, split up in parts by line breaks. + theline = line_arg; + p = vim_strchr(theline, '\n'); + if (p == NULL) + line_arg += STRLEN(line_arg); + else { + *p = NUL; + line_arg = p + 1; + } + } else { + xfree(line_to_free); + if (eap->getline == NULL) { + theline = getcmdline(':', 0L, indent, do_concat); + } else { + theline = eap->getline(':', eap->cookie, indent, do_concat); + } + line_to_free = theline; + } + if (KeyTyped) { + lines_left = Rows - 1; + } + if (theline == NULL) { + EMSG(_("E126: Missing :endfunction")); + goto erret; + } + if (show_block) { + assert(indent >= 0); + ui_ext_cmdline_block_append((size_t)indent, (const char *)theline); + } + + // Detect line continuation: sourcing_lnum increased more than one. + sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie); + if (sourcing_lnum < sourcing_lnum_off) { + sourcing_lnum_off -= sourcing_lnum; + } else { + sourcing_lnum_off = 0; + } + + if (skip_until != NULL) { + // Don't check for ":endfunc" between + // * ":append" and "." + // * ":python <<EOF" and "EOF" + // * ":let {var-name} =<< [trim] {marker}" and "{marker}" + if (heredoc_trimmed == NULL + || (is_heredoc && skipwhite(theline) == theline) + || STRNCMP(theline, heredoc_trimmed, + STRLEN(heredoc_trimmed)) == 0) { + if (heredoc_trimmed == NULL) { + p = theline; + } else if (is_heredoc) { + p = skipwhite(theline) == theline + ? theline : theline + STRLEN(heredoc_trimmed); + } else { + p = theline + STRLEN(heredoc_trimmed); + } + if (STRCMP(p, skip_until) == 0) { + XFREE_CLEAR(skip_until); + XFREE_CLEAR(heredoc_trimmed); + do_concat = true; + is_heredoc = false; + } + } + } else { + // skip ':' and blanks + for (p = theline; ascii_iswhite(*p) || *p == ':'; p++) { + } + + // Check for "endfunction". + if (checkforcmd(&p, "endfunction", 4) && nesting-- == 0) { + if (*p == '!') { + p++; + } + char_u *nextcmd = NULL; + if (*p == '|') { + nextcmd = p + 1; + } else if (line_arg != NULL && *skipwhite(line_arg) != NUL) { + nextcmd = line_arg; + } else if (*p != NUL && *p != '"' && p_verbose > 0) { + give_warning2((char_u *)_("W22: Text found after :endfunction: %s"), + p, true); + } + if (nextcmd != NULL) { + // Another command follows. If the line came from "eap" we + // can simply point into it, otherwise we need to change + // "eap->cmdlinep". + eap->nextcmd = nextcmd; + if (line_to_free != NULL) { + xfree(*eap->cmdlinep); + *eap->cmdlinep = line_to_free; + line_to_free = NULL; + } + } + break; + } + + /* Increase indent inside "if", "while", "for" and "try", decrease + * at "end". */ + if (indent > 2 && STRNCMP(p, "end", 3) == 0) + indent -= 2; + else if (STRNCMP(p, "if", 2) == 0 + || STRNCMP(p, "wh", 2) == 0 + || STRNCMP(p, "for", 3) == 0 + || STRNCMP(p, "try", 3) == 0) + indent += 2; + + // Check for defining a function inside this function. + if (checkforcmd(&p, "function", 2)) { + if (*p == '!') { + p = skipwhite(p + 1); + } + p += eval_fname_script((const char *)p); + xfree(trans_function_name(&p, true, 0, NULL, NULL)); + if (*skipwhite(p) == '(') { + nesting++; + indent += 2; + } + } + + // Check for ":append", ":change", ":insert". + p = skip_range(p, NULL); + if ((p[0] == 'a' && (!ASCII_ISALPHA(p[1]) || p[1] == 'p')) + || (p[0] == 'c' + && (!ASCII_ISALPHA(p[1]) + || (p[1] == 'h' && (!ASCII_ISALPHA(p[2]) + || (p[2] == 'a' + && (STRNCMP(&p[3], "nge", 3) != 0 + || !ASCII_ISALPHA(p[6]))))))) + || (p[0] == 'i' + && (!ASCII_ISALPHA(p[1]) || (p[1] == 'n' + && (!ASCII_ISALPHA(p[2]) + || (p[2] == 's')))))) { + skip_until = vim_strsave((char_u *)"."); + } + + // heredoc: Check for ":python <<EOF", ":lua <<EOF", etc. + arg = skipwhite(skiptowhite(p)); + if (arg[0] == '<' && arg[1] =='<' + && ((p[0] == 'p' && p[1] == 'y' + && (!ASCII_ISALNUM(p[2]) || p[2] == 't' + || ((p[2] == '3' || p[2] == 'x') + && !ASCII_ISALPHA(p[3])))) + || (p[0] == 'p' && p[1] == 'e' + && (!ASCII_ISALPHA(p[2]) || p[2] == 'r')) + || (p[0] == 't' && p[1] == 'c' + && (!ASCII_ISALPHA(p[2]) || p[2] == 'l')) + || (p[0] == 'l' && p[1] == 'u' && p[2] == 'a' + && !ASCII_ISALPHA(p[3])) + || (p[0] == 'r' && p[1] == 'u' && p[2] == 'b' + && (!ASCII_ISALPHA(p[3]) || p[3] == 'y')) + || (p[0] == 'm' && p[1] == 'z' + && (!ASCII_ISALPHA(p[2]) || p[2] == 's')))) { + // ":python <<" continues until a dot, like ":append" + p = skipwhite(arg + 2); + if (*p == NUL) + skip_until = vim_strsave((char_u *)"."); + else + skip_until = vim_strsave(p); + } + + // Check for ":let v =<< [trim] EOF" + // and ":let [a, b] =<< [trim] EOF" + arg = skipwhite(skiptowhite(p)); + if (*arg == '[') { + arg = vim_strchr(arg, ']'); + } + if (arg != NULL) { + arg = skipwhite(skiptowhite(arg)); + if (arg[0] == '=' + && arg[1] == '<' + && arg[2] =='<' + && (p[0] == 'l' + && p[1] == 'e' + && (!ASCII_ISALNUM(p[2]) + || (p[2] == 't' && !ASCII_ISALNUM(p[3]))))) { + p = skipwhite(arg + 3); + if (STRNCMP(p, "trim", 4) == 0) { + // Ignore leading white space. + p = skipwhite(p + 4); + heredoc_trimmed = + vim_strnsave(theline, (int)(skipwhite(theline) - theline)); + } + skip_until = vim_strnsave(p, (int)(skiptowhite(p) - p)); + do_concat = false; + is_heredoc = true; + } + } + } + + // Add the line to the function. + ga_grow(&newlines, 1 + sourcing_lnum_off); + + /* Copy the line to newly allocated memory. get_one_sourceline() + * allocates 250 bytes per line, this saves 80% on average. The cost + * is an extra alloc/free. */ + p = vim_strsave(theline); + ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; + + /* Add NULL lines for continuation lines, so that the line count is + * equal to the index in the growarray. */ + while (sourcing_lnum_off-- > 0) + ((char_u **)(newlines.ga_data))[newlines.ga_len++] = NULL; + + // Check for end of eap->arg. + if (line_arg != NULL && *line_arg == NUL) { + line_arg = NULL; + } + } + + /* Don't define the function when skipping commands or when an error was + * detected. */ + if (eap->skip || did_emsg) + goto erret; + + /* + * If there are no errors, add the function + */ + if (fudi.fd_dict == NULL) { + v = find_var((const char *)name, STRLEN(name), &ht, false); + if (v != NULL && v->di_tv.v_type == VAR_FUNC) { + emsg_funcname(N_("E707: Function name conflicts with variable: %s"), + name); + goto erret; + } + + fp = find_func(name); + if (fp != NULL) { + // Function can be replaced with "function!" and when sourcing the + // same script again, but only once. + if (!eap->forceit + && (fp->uf_script_ctx.sc_sid != current_sctx.sc_sid + || fp->uf_script_ctx.sc_seq == current_sctx.sc_seq)) { + emsg_funcname(e_funcexts, name); + goto erret; + } + if (fp->uf_calls > 0) { + emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"), + name); + goto erret; + } + if (fp->uf_refcount > 1) { + // This function is referenced somewhere, don't redefine it but + // create a new one. + (fp->uf_refcount)--; + fp->uf_flags |= FC_REMOVED; + fp = NULL; + overwrite = true; + } else { + // redefine existing function + XFREE_CLEAR(name); + func_clear_items(fp); + fp->uf_profiling = false; + fp->uf_prof_initialized = false; + } + } + } else { + char numbuf[20]; + + fp = NULL; + if (fudi.fd_newkey == NULL && !eap->forceit) { + EMSG(_(e_funcdict)); + goto erret; + } + if (fudi.fd_di == NULL) { + if (tv_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg, + TV_CSTRING)) { + // Can't add a function to a locked dictionary + goto erret; + } + } else if (tv_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg, + TV_CSTRING)) { + // Can't change an existing function if it is locked + goto erret; + } + + /* Give the function a sequential number. Can only be used with a + * Funcref! */ + xfree(name); + sprintf(numbuf, "%d", ++func_nr); + name = vim_strsave((char_u *)numbuf); + } + + if (fp == NULL) { + if (fudi.fd_dict == NULL && vim_strchr(name, AUTOLOAD_CHAR) != NULL) { + int slen, plen; + char_u *scriptname; + + // Check that the autoload name matches the script name. + int j = FAIL; + if (sourcing_name != NULL) { + scriptname = (char_u *)autoload_name((const char *)name, STRLEN(name)); + p = vim_strchr(scriptname, '/'); + plen = (int)STRLEN(p); + slen = (int)STRLEN(sourcing_name); + if (slen > plen && fnamecmp(p, + sourcing_name + slen - plen) == 0) + j = OK; + xfree(scriptname); + } + if (j == FAIL) { + EMSG2(_( + "E746: Function name does not match script file name: %s"), + name); + goto erret; + } + } + + fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); + + if (fudi.fd_dict != NULL) { + if (fudi.fd_di == NULL) { + // Add new dict entry + fudi.fd_di = tv_dict_item_alloc((const char *)fudi.fd_newkey); + if (tv_dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) { + xfree(fudi.fd_di); + xfree(fp); + goto erret; + } + } else { + // Overwrite existing dict entry. + tv_clear(&fudi.fd_di->di_tv); + } + fudi.fd_di->di_tv.v_type = VAR_FUNC; + fudi.fd_di->di_tv.vval.v_string = vim_strsave(name); + + // behave like "dict" was used + flags |= FC_DICT; + } + + // insert the new function in the function list + STRCPY(fp->uf_name, name); + if (overwrite) { + hi = hash_find(&func_hashtab, name); + hi->hi_key = UF2HIKEY(fp); + } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { + xfree(fp); + goto erret; + } + fp->uf_refcount = 1; + } + fp->uf_args = newargs; + fp->uf_lines = newlines; + if ((flags & FC_CLOSURE) != 0) { + register_closure(fp); + } else { + fp->uf_scoped = NULL; + } + if (prof_def_func()) { + func_do_profile(fp); + } + fp->uf_varargs = varargs; + if (sandbox) { + flags |= FC_SANDBOX; + } + fp->uf_flags = flags; + fp->uf_calls = 0; + fp->uf_script_ctx = current_sctx; + fp->uf_script_ctx.sc_lnum += sourcing_lnum_top; + + goto ret_free; + +erret: + ga_clear_strings(&newargs); +errret_2: + ga_clear_strings(&newlines); +ret_free: + xfree(skip_until); + xfree(line_to_free); + xfree(fudi.fd_newkey); + xfree(name); + did_emsg |= saved_did_emsg; + need_wait_return |= saved_wait_return; + if (show_block) { + ui_ext_cmdline_block_leave(); + } +} // NOLINT(readability/fn_size) + +/* + * Return 5 if "p" starts with "<SID>" or "<SNR>" (ignoring case). + * Return 2 if "p" starts with "s:". + * Return 0 otherwise. + */ +int eval_fname_script(const char *const p) +{ + // Use mb_strnicmp() because in Turkish comparing the "I" may not work with + // the standard library function. + if (p[0] == '<' + && (mb_strnicmp((char_u *)p + 1, (char_u *)"SID>", 4) == 0 + || mb_strnicmp((char_u *)p + 1, (char_u *)"SNR>", 4) == 0)) { + return 5; + } + if (p[0] == 's' && p[1] == ':') { + return 2; + } + return 0; +} + +bool translated_function_exists(const char *name) +{ + if (builtin_function(name, -1)) { + return find_internal_func((char *)name) != NULL; + } + return find_func((const char_u *)name) != NULL; +} + +/// Check whether function with the given name exists +/// +/// @param[in] name Function name. +/// @param[in] no_deref Whether to dereference a Funcref. +/// +/// @return True if it exists, false otherwise. +bool function_exists(const char *const name, bool no_deref) +{ + const char_u *nm = (const char_u *)name; + bool n = false; + int flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD; + + if (no_deref) { + flag |= TFN_NO_DEREF; + } + char *const p = (char *)trans_function_name((char_u **)&nm, false, flag, NULL, + NULL); + nm = skipwhite(nm); + + /* Only accept "funcname", "funcname ", "funcname (..." and + * "funcname(...", not "funcname!...". */ + if (p != NULL && (*nm == NUL || *nm == '(')) { + n = translated_function_exists(p); + } + xfree(p); + return n; +} + +/* + * Function given to ExpandGeneric() to obtain the list of user defined + * function names. + */ +char_u *get_user_func_name(expand_T *xp, int idx) +{ + static size_t done; + static hashitem_T *hi; + ufunc_T *fp; + + if (idx == 0) { + done = 0; + hi = func_hashtab.ht_array; + } + assert(hi); + if (done < func_hashtab.ht_used) { + if (done++ > 0) + ++hi; + while (HASHITEM_EMPTY(hi)) + ++hi; + fp = HI2UF(hi); + + if ((fp->uf_flags & FC_DICT) + || STRNCMP(fp->uf_name, "<lambda>", 8) == 0) { + return (char_u *)""; // don't show dict and lambda functions + } + + if (STRLEN(fp->uf_name) + 4 >= IOSIZE) { + return fp->uf_name; // Prevent overflow. + } + + cat_func_name(IObuff, fp); + if (xp->xp_context != EXPAND_USER_FUNC) { + STRCAT(IObuff, "("); + if (!fp->uf_varargs && GA_EMPTY(&fp->uf_args)) + STRCAT(IObuff, ")"); + } + return IObuff; + } + return NULL; +} + +/// ":delfunction {name}" +void ex_delfunction(exarg_T *eap) +{ + ufunc_T *fp = NULL; + char_u *p; + char_u *name; + funcdict_T fudi; + + p = eap->arg; + name = trans_function_name(&p, eap->skip, 0, &fudi, NULL); + xfree(fudi.fd_newkey); + if (name == NULL) { + if (fudi.fd_dict != NULL && !eap->skip) + EMSG(_(e_funcref)); + return; + } + if (!ends_excmd(*skipwhite(p))) { + xfree(name); + EMSG(_(e_trailing)); + return; + } + eap->nextcmd = check_nextcmd(p); + if (eap->nextcmd != NULL) + *p = NUL; + + if (!eap->skip) + fp = find_func(name); + xfree(name); + + if (!eap->skip) { + if (fp == NULL) { + if (!eap->forceit) { + EMSG2(_(e_nofunc), eap->arg); + } + return; + } + if (fp->uf_calls > 0) { + EMSG2(_("E131: Cannot delete function %s: It is in use"), eap->arg); + return; + } + // check `uf_refcount > 2` because deleting a function should also reduce + // the reference count, and 1 is the initial refcount. + if (fp->uf_refcount > 2) { + EMSG2(_("Cannot delete function %s: It is being used internally"), + eap->arg); + return; + } + + if (fudi.fd_dict != NULL) { + // Delete the dict item that refers to the function, it will + // invoke func_unref() and possibly delete the function. + tv_dict_item_remove(fudi.fd_dict, fudi.fd_di); + } else { + // A normal function (not a numbered function or lambda) has a + // refcount of 1 for the entry in the hashtable. When deleting + // it and the refcount is more than one, it should be kept. + // A numbered function or lambda should be kept if the refcount is + // one or more. + if (fp->uf_refcount > (func_name_refcount(fp->uf_name) ? 0 : 1)) { + // Function is still referenced somewhere. Don't free it but + // do remove it from the hashtable. + if (func_remove(fp)) { + fp->uf_refcount--; + } + fp->uf_flags |= FC_DELETED; + } else { + func_clear_free(fp, false); + } + } + } +} + +/* + * Unreference a Function: decrement the reference count and free it when it + * becomes zero. + */ +void func_unref(char_u *name) +{ + ufunc_T *fp = NULL; + + if (name == NULL || !func_name_refcount(name)) { + return; + } + + fp = find_func(name); + if (fp == NULL && isdigit(*name)) { +#ifdef EXITFREE + if (!entered_free_all_mem) { + internal_error("func_unref()"); + abort(); + } +#else + internal_error("func_unref()"); + abort(); +#endif + } + func_ptr_unref(fp); +} + +/// Unreference a Function: decrement the reference count and free it when it +/// becomes zero. +/// Unreference user function, freeing it if needed +/// +/// Decrements the reference count and frees when it becomes zero. +/// +/// @param fp Function to unreference. +void func_ptr_unref(ufunc_T *fp) +{ + if (fp != NULL && --fp->uf_refcount <= 0) { + // Only delete it when it's not being used. Otherwise it's done + // when "uf_calls" becomes zero. + if (fp->uf_calls == 0) { + func_clear_free(fp, false); + } + } +} + +/// Count a reference to a Function. +void func_ref(char_u *name) +{ + ufunc_T *fp; + + if (name == NULL || !func_name_refcount(name)) { + return; + } + fp = find_func(name); + if (fp != NULL) { + (fp->uf_refcount)++; + } else if (isdigit(*name)) { + // Only give an error for a numbered function. + // Fail silently, when named or lambda function isn't found. + internal_error("func_ref()"); + } +} + +/// Count a reference to a Function. +void func_ptr_ref(ufunc_T *fp) +{ + if (fp != NULL) { + (fp->uf_refcount)++; + } +} + +/// Check whether funccall is still referenced outside +/// +/// It is supposed to be referenced if either it is referenced itself or if l:, +/// a: or a:000 are referenced as all these are statically allocated within +/// funccall structure. +static inline bool fc_referenced(const funccall_T *const fc) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ALL +{ + return ((fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated) + != DO_NOT_FREE_CNT) + || fc->l_vars.dv_refcount != DO_NOT_FREE_CNT + || fc->l_avars.dv_refcount != DO_NOT_FREE_CNT + || fc->fc_refcount > 0); +} + +/// @return true if items in "fc" do not have "copyID". That means they are not +/// referenced from anywhere that is in use. +static int can_free_funccal(funccall_T *fc, int copyID) +{ + return fc->l_varlist.lv_copyID != copyID + && fc->l_vars.dv_copyID != copyID + && fc->l_avars.dv_copyID != copyID + && fc->fc_copyID != copyID; +} + +/* + * ":return [expr]" + */ +void ex_return(exarg_T *eap) +{ + char_u *arg = eap->arg; + typval_T rettv; + int returning = FALSE; + + if (current_funccal == NULL) { + EMSG(_("E133: :return not inside a function")); + return; + } + + if (eap->skip) + ++emsg_skip; + + eap->nextcmd = NULL; + if ((*arg != NUL && *arg != '|' && *arg != '\n') + && eval0(arg, &rettv, &eap->nextcmd, !eap->skip) != FAIL) { + if (!eap->skip) { + returning = do_return(eap, false, true, &rettv); + } else { + tv_clear(&rettv); + } + } else if (!eap->skip) { // It's safer to return also on error. + // In return statement, cause_abort should be force_abort. + update_force_abort(); + + // Return unless the expression evaluation has been cancelled due to an + // aborting error, an interrupt, or an exception. + if (!aborting()) { + returning = do_return(eap, false, true, NULL); + } + } + + /* When skipping or the return gets pending, advance to the next command + * in this line (!returning). Otherwise, ignore the rest of the line. + * Following lines will be ignored by get_func_line(). */ + if (returning) { + eap->nextcmd = NULL; + } else if (eap->nextcmd == NULL) { // no argument + eap->nextcmd = check_nextcmd(arg); + } + + if (eap->skip) + --emsg_skip; +} + +// TODO(ZyX-I): move to eval/ex_cmds + +/* + * ":1,25call func(arg1, arg2)" function call. + */ +void ex_call(exarg_T *eap) +{ + char_u *arg = eap->arg; + char_u *startarg; + char_u *name; + char_u *tofree; + int len; + typval_T rettv; + linenr_T lnum; + int doesrange; + bool failed = false; + funcdict_T fudi; + partial_T *partial = NULL; + + 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) { + tv_clear(&rettv); + } + emsg_skip--; + return; + } + + tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial); + if (fudi.fd_newkey != NULL) { + // Still need to give an error message for missing key. + EMSG2(_(e_dictkey), fudi.fd_newkey); + xfree(fudi.fd_newkey); + } + if (tofree == NULL) { + return; + } + + // Increase refcount on dictionary, it could get deleted when evaluating + // the arguments. + if (fudi.fd_dict != NULL) { + fudi.fd_dict->dv_refcount++; + } + + // If it is the name of a variable of type VAR_FUNC or VAR_PARTIAL use its + // contents. For VAR_PARTIAL get its partial, unless we already have one + // from trans_function_name(). + len = (int)STRLEN(tofree); + name = deref_func_name((const char *)tofree, &len, + partial != NULL ? NULL : &partial, false); + + // Skip white space to allow ":call func ()". Not good, but required for + // backward compatibility. + startarg = skipwhite(arg); + rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this. + + if (*startarg != '(') { + EMSG2(_("E107: Missing parentheses: %s"), eap->arg); + goto end; + } + + lnum = eap->line1; + for (; lnum <= eap->line2; lnum++) { + if (eap->addr_count > 0) { // -V560 + if (lnum > curbuf->b_ml.ml_line_count) { + // If the function deleted lines or switched to another buffer + // the line number may become invalid. + EMSG(_(e_invrange)); + break; + } + curwin->w_cursor.lnum = lnum; + curwin->w_cursor.col = 0; + curwin->w_cursor.coladd = 0; + } + arg = startarg; + if (get_func_tv(name, (int)STRLEN(name), &rettv, &arg, + eap->line1, eap->line2, &doesrange, + true, partial, fudi.fd_dict) == FAIL) { + failed = true; + break; + } + + // Handle a function returning a Funcref, Dictionary or List. + if (handle_subscript((const char **)&arg, &rettv, true, true) + == FAIL) { + failed = true; + break; + } + + tv_clear(&rettv); + if (doesrange) { + break; + } + + // Stop when immediately aborting on error, or when an interrupt + // occurred or an exception was thrown but not caught. + // get_func_tv() returned OK, so that the check for trailing + // characters below is executed. + if (aborting()) { + break; + } + } + + // When inside :try we need to check for following "| catch". + if (!failed || eap->cstack->cs_trylevel > 0) { + // Check for trailing illegal characters and a following command. + if (!ends_excmd(*arg)) { + emsg_severe = TRUE; + EMSG(_(e_trailing)); + } else { + eap->nextcmd = check_nextcmd(arg); + } + } + +end: + tv_dict_unref(fudi.fd_dict); + xfree(tofree); +} + +/* + * Return from a function. Possibly makes the return pending. Also called + * for a pending return at the ":endtry" or after returning from an extra + * do_cmdline(). "reanimate" is used in the latter case. "is_cmd" is set + * when called due to a ":return" command. "rettv" may point to a typval_T + * with the return rettv. Returns TRUE when the return can be carried out, + * FALSE when the return gets pending. + */ +int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) +{ + int idx; + cstack_T *const cstack = eap->cstack; + + if (reanimate) { + // Undo the return. + current_funccal->returned = false; + } + + /* + * Cleanup (and inactivate) conditionals, but stop when a try conditional + * not in its finally clause (which then is to be executed next) is found. + * In this case, make the ":return" pending for execution at the ":endtry". + * Otherwise, return normally. + */ + idx = cleanup_conditionals(eap->cstack, 0, TRUE); + if (idx >= 0) { + cstack->cs_pending[idx] = CSTP_RETURN; + + if (!is_cmd && !reanimate) + /* A pending return again gets pending. "rettv" points to an + * allocated variable with the rettv of the original ":return"'s + * argument if present or is NULL else. */ + cstack->cs_rettv[idx] = rettv; + else { + /* When undoing a return in order to make it pending, get the stored + * return rettv. */ + if (reanimate) { + assert(current_funccal->rettv); + rettv = current_funccal->rettv; + } + + if (rettv != NULL) { + // Store the value of the pending return. + cstack->cs_rettv[idx] = xcalloc(1, sizeof(typval_T)); + *(typval_T *)cstack->cs_rettv[idx] = *(typval_T *)rettv; + } else + cstack->cs_rettv[idx] = NULL; + + if (reanimate) { + /* The pending return value could be overwritten by a ":return" + * without argument in a finally clause; reset the default + * return value. */ + current_funccal->rettv->v_type = VAR_NUMBER; + current_funccal->rettv->vval.v_number = 0; + } + } + report_make_pending(CSTP_RETURN, rettv); + } else { + current_funccal->returned = TRUE; + + /* If the return is carried out now, store the return value. For + * a return immediately after reanimation, the value is already + * there. */ + if (!reanimate && rettv != NULL) { + tv_clear(current_funccal->rettv); + *current_funccal->rettv = *(typval_T *)rettv; + if (!is_cmd) + xfree(rettv); + } + } + + return idx < 0; +} + +/* + * Generate a return command for producing the value of "rettv". The result + * is an allocated string. Used by report_pending() for verbose messages. + */ +char_u *get_return_cmd(void *rettv) +{ + char_u *s = NULL; + char_u *tofree = NULL; + + if (rettv != NULL) { + tofree = s = (char_u *) encode_tv2echo((typval_T *) rettv, NULL); + } + if (s == NULL) { + s = (char_u *)""; + } + + STRCPY(IObuff, ":return "); + STRLCPY(IObuff + 8, s, IOSIZE - 8); + if (STRLEN(s) + 8 >= IOSIZE) + STRCPY(IObuff + IOSIZE - 4, "..."); + xfree(tofree); + return vim_strsave(IObuff); +} + +/* + * Get next function line. + * Called by do_cmdline() to get the next line. + * Returns allocated string, or NULL for end of function. + */ +char_u *get_func_line(int c, void *cookie, int indent, bool do_concat) +{ + funccall_T *fcp = (funccall_T *)cookie; + ufunc_T *fp = fcp->func; + char_u *retval; + garray_T *gap; // growarray with function lines + + // If breakpoints have been added/deleted need to check for it. + if (fcp->dbg_tick != debug_tick) { + fcp->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, + sourcing_lnum); + fcp->dbg_tick = debug_tick; + } + if (do_profiling == PROF_YES) + func_line_end(cookie); + + gap = &fp->uf_lines; + if (((fp->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try()) + || fcp->returned) { + retval = NULL; + } else { + // Skip NULL lines (continuation lines). + while (fcp->linenr < gap->ga_len + && ((char_u **)(gap->ga_data))[fcp->linenr] == NULL) { + fcp->linenr++; + } + if (fcp->linenr >= gap->ga_len) { + retval = NULL; + } else { + retval = vim_strsave(((char_u **)(gap->ga_data))[fcp->linenr++]); + sourcing_lnum = fcp->linenr; + if (do_profiling == PROF_YES) + func_line_start(cookie); + } + } + + // Did we encounter a breakpoint? + if (fcp->breakpoint != 0 && fcp->breakpoint <= sourcing_lnum) { + dbg_breakpoint(fp->uf_name, sourcing_lnum); + // Find next breakpoint. + fcp->breakpoint = dbg_find_breakpoint(false, fp->uf_name, + sourcing_lnum); + fcp->dbg_tick = debug_tick; + } + + return retval; +} + +/* + * Return TRUE if the currently active function should be ended, because a + * return was encountered or an error occurred. Used inside a ":while". + */ +int func_has_ended(void *cookie) +{ + funccall_T *fcp = (funccall_T *)cookie; + + /* Ignore the "abort" flag if the abortion behavior has been changed due to + * an error inside a try conditional. */ + return ((fcp->func->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try()) + || fcp->returned; +} + +/* + * return TRUE if cookie indicates a function which "abort"s on errors. + */ +int func_has_abort(void *cookie) +{ + return ((funccall_T *)cookie)->func->uf_flags & FC_ABORT; +} + +/// Turn "dict.Func" into a partial for "Func" bound to "dict". +/// Changes "rettv" in-place. +void make_partial(dict_T *const selfdict, typval_T *const rettv) +{ + char_u *fname; + char_u *tofree = NULL; + ufunc_T *fp; + char_u fname_buf[FLEN_FIXED + 1]; + int error; + + if (rettv->v_type == VAR_PARTIAL && rettv->vval.v_partial->pt_func != NULL) { + fp = rettv->vval.v_partial->pt_func; + } else { + fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING + ? rettv->vval.v_string + : rettv->vval.v_partial->pt_name; + // Translate "s:func" to the stored function name. + fname = fname_trans_sid(fname, fname_buf, &tofree, &error); + fp = find_func(fname); + xfree(tofree); + } + + // Turn "dict.Func" into a partial for "Func" with "dict". + if (fp != NULL && (fp->uf_flags & FC_DICT)) { + partial_T *pt = (partial_T *)xcalloc(1, sizeof(partial_T)); + pt->pt_refcount = 1; + pt->pt_dict = selfdict; + (selfdict->dv_refcount)++; + pt->pt_auto = true; + if (rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING) { + // Just a function: Take over the function name and use selfdict. + pt->pt_name = rettv->vval.v_string; + } else { + partial_T *ret_pt = rettv->vval.v_partial; + int i; + + // Partial: copy the function name, use selfdict and copy + // args. Can't take over name or args, the partial might + // be referenced elsewhere. + if (ret_pt->pt_name != NULL) { + pt->pt_name = vim_strsave(ret_pt->pt_name); + func_ref(pt->pt_name); + } else { + pt->pt_func = ret_pt->pt_func; + func_ptr_ref(pt->pt_func); + } + if (ret_pt->pt_argc > 0) { + size_t arg_size = sizeof(typval_T) * ret_pt->pt_argc; + pt->pt_argv = (typval_T *)xmalloc(arg_size); + pt->pt_argc = ret_pt->pt_argc; + for (i = 0; i < pt->pt_argc; i++) { + tv_copy(&ret_pt->pt_argv[i], &pt->pt_argv[i]); + } + } + partial_unref(ret_pt); + } + rettv->v_type = VAR_PARTIAL; + rettv->vval.v_partial = pt; + } +} + +/* + * Return the name of the executed function. + */ +char_u *func_name(void *cookie) +{ + return ((funccall_T *)cookie)->func->uf_name; +} + +/* + * Return the address holding the next breakpoint line for a funccall cookie. + */ +linenr_T *func_breakpoint(void *cookie) +{ + return &((funccall_T *)cookie)->breakpoint; +} + +/* + * Return the address holding the debug tick for a funccall cookie. + */ +int *func_dbg_tick(void *cookie) +{ + return &((funccall_T *)cookie)->dbg_tick; +} + +/* + * Return the nesting level for a funccall cookie. + */ +int func_level(void *cookie) +{ + return ((funccall_T *)cookie)->level; +} + +/* + * Return TRUE when a function was ended by a ":return" command. + */ +int current_func_returned(void) +{ + return current_funccal->returned; +} + +bool free_unref_funccal(int copyID, int testing) +{ + bool did_free = false; + bool did_free_funccal = false; + + for (funccall_T **pfc = &previous_funccal; *pfc != NULL;) { + if (can_free_funccal(*pfc, copyID)) { + funccall_T *fc = *pfc; + *pfc = fc->caller; + free_funccal(fc, true); + did_free = true; + did_free_funccal = true; + } else { + pfc = &(*pfc)->caller; + } + } + if (did_free_funccal) { + // When a funccal was freed some more items might be garbage + // collected, so run again. + (void)garbage_collect(testing); + } + return did_free; +} + +// Get function call environment based on backtrace debug level +funccall_T *get_funccal(void) +{ + funccall_T *funccal = current_funccal; + if (debug_backtrace_level > 0) { + for (int i = 0; i < debug_backtrace_level; i++) { + funccall_T *temp_funccal = funccal->caller; + if (temp_funccal) { + funccal = temp_funccal; + } else { + // backtrace level overflow. reset to max + debug_backtrace_level = i; + } + } + } + + return funccal; +} + +/// Return the hashtable used for local variables in the current funccal. +/// Return NULL if there is no current funccal. +hashtab_T *get_funccal_local_ht(void) +{ + if (current_funccal == NULL) { + return NULL; + } + return &get_funccal()->l_vars.dv_hashtab; +} + +/// Return the l: scope variable. +/// Return NULL if there is no current funccal. +dictitem_T *get_funccal_local_var(void) +{ + if (current_funccal == NULL) { + return NULL; + } + return (dictitem_T *)&get_funccal()->l_vars_var; +} + +/// Return the hashtable used for argument in the current funccal. +/// Return NULL if there is no current funccal. +hashtab_T *get_funccal_args_ht(void) +{ + if (current_funccal == NULL) { + return NULL; + } + return &get_funccal()->l_avars.dv_hashtab; +} + +/// Return the a: scope variable. +/// Return NULL if there is no current funccal. +dictitem_T *get_funccal_args_var(void) +{ + if (current_funccal == NULL) { + return NULL; + } + return (dictitem_T *)¤t_funccal->l_avars_var; +} + +/* + * List function variables, if there is a function. + */ +void list_func_vars(int *first) +{ + if (current_funccal != NULL) { + list_hashtable_vars(¤t_funccal->l_vars.dv_hashtab, "l:", false, + first); + } +} + +/// If "ht" is the hashtable for local variables in the current funccal, return +/// the dict that contains it. +/// Otherwise return NULL. +dict_T *get_current_funccal_dict(hashtab_T *ht) +{ + if (current_funccal != NULL && ht == ¤t_funccal->l_vars.dv_hashtab) { + return ¤t_funccal->l_vars; + } + return NULL; +} + +/// Search hashitem in parent scope. +hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht) +{ + if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) { + return NULL; + } + + funccall_T *old_current_funccal = current_funccal; + hashitem_T *hi = NULL; + const size_t namelen = strlen(name); + const char *varname; + + // Search in parent scope which is possible to reference from lambda + current_funccal = current_funccal->func->uf_scoped; + while (current_funccal != NULL) { + hashtab_T *ht = find_var_ht(name, namelen, &varname); + if (ht != NULL && *varname != NUL) { + hi = hash_find_len(ht, varname, namelen - (varname - name)); + if (!HASHITEM_EMPTY(hi)) { + *pht = ht; + break; + } + } + if (current_funccal == current_funccal->func->uf_scoped) { + break; + } + current_funccal = current_funccal->func->uf_scoped; + } + current_funccal = old_current_funccal; + + return hi; +} + +/// Search variable in parent scope. +dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, + int no_autoload) +{ + if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) { + return NULL; + } + + dictitem_T *v = NULL; + funccall_T *old_current_funccal = current_funccal; + const char *varname; + + // Search in parent scope which is possible to reference from lambda + current_funccal = current_funccal->func->uf_scoped; + while (current_funccal) { + hashtab_T *ht = find_var_ht(name, namelen, &varname); + if (ht != NULL && *varname != NUL) { + v = find_var_in_ht(ht, *name, varname, + namelen - (size_t)(varname - name), no_autoload); + if (v != NULL) { + break; + } + } + if (current_funccal == current_funccal->func->uf_scoped) { + break; + } + current_funccal = current_funccal->func->uf_scoped; + } + current_funccal = old_current_funccal; + + return v; +} + +/// Set "copyID + 1" in previous_funccal and callers. +bool set_ref_in_previous_funccal(int copyID) +{ + bool abort = false; + + for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) { + abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL); + abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL); + } + return abort; +} + +static bool set_ref_in_funccal(funccall_T *fc, int copyID) +{ + bool abort = false; + + if (fc->fc_copyID != copyID) { + fc->fc_copyID = copyID; + abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); + abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL); + abort = abort || set_ref_in_func(NULL, fc->func, copyID); + } + return abort; +} + +/// Set "copyID" in all local vars and arguments in the call stack. +bool set_ref_in_call_stack(int copyID) +{ + bool abort = false; + + for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) { + abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); + abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL); + } + return abort; +} + +/// Set "copyID" in all functions available by name. +bool set_ref_in_functions(int copyID) +{ + int todo; + hashitem_T *hi = NULL; + bool abort = false; + ufunc_T *fp; + + todo = (int)func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) { + if (!HASHITEM_EMPTY(hi)) { + todo--; + fp = HI2UF(hi); + if (!func_name_refcount(fp->uf_name)) { + abort = abort || set_ref_in_func(NULL, fp, copyID); + } + } + } + return abort; +} + +/// Set "copyID" in all function arguments. +bool set_ref_in_func_args(int copyID) +{ + bool abort = false; + + for (int i = 0; i < funcargs.ga_len; i++) { + abort = abort || set_ref_in_item(((typval_T **)funcargs.ga_data)[i], + copyID, NULL, NULL); + } + return abort; +} + +/// Mark all lists and dicts referenced through function "name" with "copyID". +/// "list_stack" is used to add lists to be marked. Can be NULL. +/// "ht_stack" is used to add hashtabs to be marked. Can be NULL. +/// +/// @return true if setting references failed somehow. +bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) +{ + ufunc_T *fp = fp_in; + funccall_T *fc; + int error = ERROR_NONE; + char_u fname_buf[FLEN_FIXED + 1]; + char_u *tofree = NULL; + char_u *fname; + bool abort = false; + if (name == NULL && fp_in == NULL) { + return false; + } + + if (fp_in == NULL) { + fname = fname_trans_sid(name, fname_buf, &tofree, &error); + fp = find_func(fname); + } + if (fp != NULL) { + for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped) { + abort = abort || set_ref_in_funccal(fc, copyID); + } + } + xfree(tofree); + return abort; +} diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h new file mode 100644 index 0000000000..ad8e071548 --- /dev/null +++ b/src/nvim/eval/userfunc.h @@ -0,0 +1,36 @@ +#ifndef NVIM_EVAL_USERFUNC_H +#define NVIM_EVAL_USERFUNC_H + +#include "nvim/eval/typval.h" +#include "nvim/ex_cmds_defs.h" + +///< Structure used by trans_function_name() +typedef struct { + dict_T *fd_dict; ///< Dictionary used. + char_u *fd_newkey; ///< New key in "dict" in allocated memory. + dictitem_T *fd_di; ///< Dictionary item used. +} funcdict_T; + +/// errors for when calling a function +typedef enum { + ERROR_UNKNOWN = 0, + ERROR_TOOMANY, + ERROR_TOOFEW, + ERROR_SCRIPT, + ERROR_DICT, + ERROR_NONE, + ERROR_OTHER, + ERROR_BOTH, + ERROR_DELETED, +} FnameTransError; + +typedef int (*ArgvFunc)(int current_argcount, typval_T *argv, int argskip, + int called_func_argcount); + +#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] +#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j] + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/userfunc.h.generated.h" +#endif +#endif // NVIM_EVAL_USERFUNC_H diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c index c2be472acd..e341513ae1 100644 --- a/src/nvim/event/loop.c +++ b/src/nvim/event/loop.c @@ -116,6 +116,20 @@ void loop_on_put(MultiQueue *queue, void *data) uv_stop(&loop->uv); } +#if !defined(EXITFREE) +static void loop_walk_cb(uv_handle_t *handle, void *arg) +{ + if (!uv_is_closing(handle)) { + uv_close(handle, NULL); + } +} +#endif + +/// Closes `loop` and its handles, and frees its structures. +/// +/// @param loop Loop to destroy +/// @param wait Wait briefly for handles to deref +/// /// @returns false if the loop could not be closed gracefully bool loop_close(Loop *loop, bool wait) { @@ -126,18 +140,34 @@ bool loop_close(Loop *loop, bool wait) uv_close((uv_handle_t *)&loop->poll_timer, timer_close_cb); uv_close((uv_handle_t *)&loop->async, NULL); uint64_t start = wait ? os_hrtime() : 0; + bool didstop = false; while (true) { - uv_run(&loop->uv, wait ? UV_RUN_DEFAULT : UV_RUN_NOWAIT); - if (!uv_loop_close(&loop->uv) || !wait) { + // Run the loop to tickle close-callbacks (which should then free memory). + // Use UV_RUN_NOWAIT to avoid a hang. #11820 + uv_run(&loop->uv, didstop ? UV_RUN_DEFAULT : UV_RUN_NOWAIT); + if ((uv_loop_close(&loop->uv) != UV_EBUSY) || !wait) { break; } - if (os_hrtime() - start >= 2 * 1000000000) { + uint64_t elapsed_s = (os_hrtime() - start) / 1000000000; // seconds + if (elapsed_s >= 2) { // Some libuv resource was not correctly deref'd. Log and bail. rv = false; ELOG("uv_loop_close() hang?"); log_uv_handles(&loop->uv); break; } +#if defined(EXITFREE) + (void)didstop; +#else + if (!didstop) { + // Loop won’t block for I/O after this. + uv_stop(&loop->uv); + // XXX: Close all (lua/luv!) handles. But loop_walk_cb() does not call + // resource-specific close-callbacks, so this leaks memory... + uv_walk(&loop->uv, loop_walk_cb, NULL); + didstop = true; + } +#endif } multiqueue_free(loop->fast_events); multiqueue_free(loop->thread_events); diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c index d1a53fa4b6..0e87f7c6c1 100644 --- a/src/nvim/event/stream.c +++ b/src/nvim/event/stream.c @@ -16,6 +16,11 @@ # include "event/stream.c.generated.h" #endif +// For compatbility with libuv < 1.19.0 (tested on 1.18.0) +#if UV_VERSION_MINOR < 19 +#define uv_stream_get_write_queue_size(stream) stream->write_queue_size +#endif + /// Sets the stream associated with `fd` to "blocking" mode. /// /// @return `0` on success, or libuv error code on failure. diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index bc6821f60f..8a0f2e634a 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -882,7 +882,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, kExtmarkNOOP); FOR_ALL_TAB_WINDOWS(tab, win) { if (win->w_buffer == curbuf) { - foldMoveRange(&win->w_folds, line1, line2, dest); + foldMoveRange(win, &win->w_folds, line1, line2, dest); } } curbuf->b_op_start.lnum = dest - num_lines + 1; @@ -891,7 +891,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, kExtmarkNOOP); FOR_ALL_TAB_WINDOWS(tab, win) { if (win->w_buffer == curbuf) { - foldMoveRange(&win->w_folds, dest + 1, line1 - 1, line2); + foldMoveRange(win, &win->w_folds, dest + 1, line1 - 1, line2); } } curbuf->b_op_start.lnum = dest + 1; @@ -2177,6 +2177,7 @@ int do_ecmd( int did_get_winopts = FALSE; int readfile_flags = 0; bool did_inc_redrawing_disabled = false; + long *so_ptr = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so; if (eap != NULL) command = eap->do_ecmd_cmd; @@ -2669,6 +2670,8 @@ int do_ecmd( msg_scrolled_ign = FALSE; } + curbuf->b_last_used = time(NULL); + if (command != NULL) do_cmdline(command, NULL, NULL, DOCMD_VERBOSE); @@ -2678,13 +2681,14 @@ int do_ecmd( RedrawingDisabled--; did_inc_redrawing_disabled = false; if (!skip_redraw) { - n = p_so; - if (topline == 0 && command == NULL) - p_so = 999; // force cursor to be vertically centered in the window + n = *so_ptr; + if (topline == 0 && command == NULL) { + *so_ptr = 999; // force cursor to be vertically centered in the window + } update_topline(); curwin->w_scbind_pos = curwin->w_topline; - p_so = n; - redraw_curbuf_later(NOT_VALID); /* redraw this buffer later */ + *so_ptr = n; + redraw_curbuf_later(NOT_VALID); // redraw this buffer later } if (p_im) @@ -3008,18 +3012,18 @@ void ex_z(exarg_T *eap) ex_no_reprint = true; } -/* - * Check if the restricted flag is set. - * If so, give an error message and return TRUE. - * Otherwise, return FALSE. - */ -int check_restricted(void) +// Check if the restricted flag is set. +// If so, give an error message and return true. +// Otherwise, return false. +bool check_restricted(void) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { if (restricted) { - EMSG(_("E145: Shell commands not allowed in restricted mode")); - return TRUE; + EMSG(_("E145: Shell commands and some functionality not allowed" + " in restricted mode")); + return true; } - return FALSE; + return false; } /* @@ -4482,8 +4486,9 @@ prepare_tagpreview ( curwin->w_p_wfh = TRUE; RESET_BINDING(curwin); /* don't take over 'scrollbind' and 'cursorbind' */ - curwin->w_p_diff = FALSE; /* no 'diff' */ - curwin->w_p_fdc = 0; /* no 'foldcolumn' */ + curwin->w_p_diff = false; // no 'diff' + set_string_option_direct((char_u *)"fdc", -1, // no 'foldcolumn' + (char_u *)"0", OPT_FREE, SID_NONE); return true; } } diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index f7aa8a994a..252af409c0 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -24,6 +24,7 @@ local SBOXOK = 0x80000 local CMDWIN = 0x100000 local MODIFY = 0x200000 local EXFLAGS = 0x400000 +local RESTRICT = 0x800000 local FILES = bit.bor(XFILE, EXTRA) local WORD1 = bit.bor(EXTRA, NOSPC) local FILE1 = bit.bor(FILES, NOSPC) @@ -1582,19 +1583,19 @@ return { }, { command='lua', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_lua', }, { command='luado', - flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_luado', }, { command='luafile', - flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_luafile', }, @@ -1924,13 +1925,13 @@ return { }, { command='perl', - flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, SBOXOK, CMDWIN), + flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, SBOXOK, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_script_ni', }, { command='perldo', - flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_ni', }, @@ -2056,67 +2057,67 @@ return { }, { command='python', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_python', }, { command='pydo', - flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_pydo', }, { command='pyfile', - flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_pyfile', }, { command='py3', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_python3', }, { command='py3do', - flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_pydo3', }, { command='python3', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_python3', }, { command='py3file', - flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_py3file', }, { command='pyx', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_pyx', }, { command='pyxdo', - flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_pyxdo', }, { command='pythonx', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_pyx', }, { command='pyxfile', - flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_pyxfile', }, @@ -2242,19 +2243,19 @@ return { }, { command='ruby', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_ruby', }, { command='rubydo', - flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_rubydo', }, { command='rubyfile', - flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT), addr_type=ADDR_LINES, func='ex_rubyfile', }, diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index a3d49c682e..9f4055af8d 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -20,7 +20,7 @@ #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" -#include "nvim/eval.h" +#include "nvim/eval/userfunc.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" @@ -1038,16 +1038,17 @@ static void profile_reset(void) if (!HASHITEM_EMPTY(hi)) { n--; ufunc_T *uf = HI2UF(hi); - if (uf->uf_profiling) { + if (uf->uf_prof_initialized) { uf->uf_profiling = 0; uf->uf_tm_count = 0; uf->uf_tm_total = profile_zero(); uf->uf_tm_self = profile_zero(); uf->uf_tm_children = profile_zero(); - XFREE_CLEAR(uf->uf_tml_count); - XFREE_CLEAR(uf->uf_tml_total); - XFREE_CLEAR(uf->uf_tml_self); + for (int i = 0; i < uf->uf_lines.ga_len; i++) { + uf->uf_tml_count[i] = 0; + uf->uf_tml_total[i] = uf->uf_tml_self[i] = 0; + } uf->uf_tml_start = profile_zero(); uf->uf_tml_children = profile_zero(); @@ -2394,7 +2395,7 @@ int do_in_path(char_u *path, char_u *name, int flags, char_u *rtp_copy = vim_strsave(path); char_u *buf = xmallocz(MAXPATHL); { - if (p_verbose > 1 && name != NULL) { + if (p_verbose > 10 && name != NULL) { verbose_enter(); smsg(_("Searching for \"%s\" in \"%s\""), (char *)name, (char *)path); @@ -2436,7 +2437,7 @@ int do_in_path(char_u *path, char_u *name, int flags, copy_option_part(&np, tail, (size_t)(MAXPATHL - (tail - buf)), "\t "); - if (p_verbose > 2) { + if (p_verbose > 10) { verbose_enter(); smsg(_("Searching for \"%s\""), buf); verbose_leave(); diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index 4a40cc54b4..1f0560ae48 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -36,35 +36,36 @@ // 4. Add documentation in ../doc/xxx.txt. Add a tag for both the short and // long name of the command. -#define RANGE 0x001 /* allow a linespecs */ -#define BANG 0x002 /* allow a ! after the command name */ -#define EXTRA 0x004 /* allow extra args after command name */ -#define XFILE 0x008 /* expand wildcards in extra part */ -#define NOSPC 0x010 /* no spaces allowed in the extra part */ -#define DFLALL 0x020 /* default file range is 1,$ */ -#define WHOLEFOLD 0x040 /* extend range to include whole fold also - when less than two numbers given */ -#define NEEDARG 0x080 /* argument required */ -#define TRLBAR 0x100 /* check for trailing vertical bar */ -#define REGSTR 0x200 /* allow "x for register designation */ -#define COUNT 0x400 /* allow count in argument, after command */ -#define NOTRLCOM 0x800 /* no trailing comment allowed */ -#define ZEROR 0x1000 /* zero line number allowed */ -#define USECTRLV 0x2000 /* do not remove CTRL-V from argument */ -#define NOTADR 0x4000 /* number before command is not an address */ -#define EDITCMD 0x8000 /* allow "+command" argument */ -#define BUFNAME 0x10000 /* accepts buffer name */ -#define BUFUNL 0x20000 /* accepts unlisted buffer too */ -#define ARGOPT 0x40000 /* allow "++opt=val" argument */ -#define SBOXOK 0x80000 /* allowed in the sandbox */ -#define CMDWIN 0x100000 /* allowed in cmdline window; when missing - * disallows editing another buffer when - * curbuf_lock is set */ -#define MODIFY 0x200000 /* forbidden in non-'modifiable' buffer */ -#define EXFLAGS 0x400000 /* allow flags after count in argument */ -#define FILES (XFILE | EXTRA) /* multiple extra files allowed */ -#define WORD1 (EXTRA | NOSPC) /* one extra word allowed */ -#define FILE1 (FILES | NOSPC) /* 1 file allowed, defaults to current file */ +#define RANGE 0x001 // allow a linespecs +#define BANG 0x002 // allow a ! after the command name +#define EXTRA 0x004 // allow extra args after command name +#define XFILE 0x008 // expand wildcards in extra part +#define NOSPC 0x010 // no spaces allowed in the extra part +#define DFLALL 0x020 // default file range is 1,$ +#define WHOLEFOLD 0x040 // extend range to include whole fold also + // when less than two numbers given +#define NEEDARG 0x080 // argument required +#define TRLBAR 0x100 // check for trailing vertical bar +#define REGSTR 0x200 // allow "x for register designation +#define COUNT 0x400 // allow count in argument, after command +#define NOTRLCOM 0x800 // no trailing comment allowed +#define ZEROR 0x1000 // zero line number allowed +#define USECTRLV 0x2000 // do not remove CTRL-V from argument +#define NOTADR 0x4000 // number before command is not an address +#define EDITCMD 0x8000 // allow "+command" argument +#define BUFNAME 0x10000 // accepts buffer name +#define BUFUNL 0x20000 // accepts unlisted buffer too +#define ARGOPT 0x40000 // allow "++opt=val" argument +#define SBOXOK 0x80000 // allowed in the sandbox +#define CMDWIN 0x100000 // allowed in cmdline window; when missing + // disallows editing another buffer when + // curbuf_lock is set +#define MODIFY 0x200000 // forbidden in non-'modifiable' buffer +#define EXFLAGS 0x400000 // allow flags after count in argument +#define RESTRICT 0x800000L // forbidden in restricted mode +#define FILES (XFILE | EXTRA) // multiple extra files allowed +#define WORD1 (EXTRA | NOSPC) // one extra word allowed +#define FILE1 (FILES | NOSPC) // 1 file allowed, defaults to current file // values for cmd_addr_type #define ADDR_LINES 0 @@ -160,7 +161,7 @@ struct exarg { int regname; ///< register name (NUL if none) int force_bin; ///< 0, FORCE_BIN or FORCE_NOBIN int read_edit; ///< ++edit argument - int force_ff; ///< ++ff= argument (index in cmd[]) + int force_ff; ///< ++ff= argument (first char of argument) int force_enc; ///< ++enc= argument (index in cmd[]) int bad_char; ///< BAD_KEEP, BAD_DROP or replacement byte int useridx; ///< user command index diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 02bee838d5..5bf6aa73c6 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1,9 +1,7 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * ex_docmd.c: functions for executing an Ex command line. - */ +// ex_docmd.c: functions for executing an Ex command line. #include <assert.h> #include <string.h> @@ -22,6 +20,7 @@ #include "nvim/digraph.h" #include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/userfunc.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_eval.h" @@ -40,6 +39,7 @@ #include "nvim/menu.h" #include "nvim/message.h" #include "nvim/misc1.h" +#include "nvim/ex_session.h" #include "nvim/keymap.h" #include "nvim/file_search.h" #include "nvim/garray.h" @@ -79,9 +79,6 @@ static int quitmore = 0; static bool ex_pressedreturn = false; -/// Whether ":lcd" or ":tcd" was produced for a session. -static int did_lcd; - typedef struct ucmd { char_u *uc_name; // The command name uint32_t uc_argt; // The argument type @@ -201,7 +198,7 @@ static void save_dbg_stuff(struct dbg_stuff *dsp) static void restore_dbg_stuff(struct dbg_stuff *dsp) { - suppress_errthrow = FALSE; + suppress_errthrow = false; trylevel = dsp->trylevel; force_abort = dsp->force_abort; caught_stack = dsp->caught_stack; @@ -398,8 +395,8 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, * Initialize "force_abort" and "suppress_errthrow" at the top level. */ if (!recursive) { - force_abort = FALSE; - suppress_errthrow = FALSE; + force_abort = false; + suppress_errthrow = false; } // If requested, store and reset the global values controlling the @@ -883,16 +880,14 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, xfree(sourcing_name); sourcing_name = saved_sourcing_name; sourcing_lnum = saved_sourcing_lnum; + } else if (got_int || (did_emsg && force_abort)) { + // On an interrupt or an aborting error not converted to an exception, + // disable the conversion of errors to exceptions. (Interrupts are not + // converted any more, here.) This enables also the interrupt message + // when force_abort is set and did_emsg unset in case of an interrupt + // from a finally clause after an error. + suppress_errthrow = true; } - /* - * On an interrupt or an aborting error not converted to an exception, - * disable the conversion of errors to exceptions. (Interrupts are not - * converted any more, here.) This enables also the interrupt message - * when force_abort is set and did_emsg unset in case of an interrupt - * from a finally clause after an error. - */ - else if (got_int || (did_emsg && force_abort)) - suppress_errthrow = TRUE; } // The current cstack will be freed when do_cmdline() returns. An uncaught @@ -1787,10 +1782,14 @@ static char_u * do_one_cmd(char_u **cmdlinep, if (!ea.skip) { if (sandbox != 0 && !(ea.argt & SBOXOK)) { - /* Command not allowed in sandbox. */ + // Command not allowed in sandbox. errormsg = (char_u *)_(e_sandbox); goto doend; } + if (restricted != 0 && (ea.argt & RESTRICT)) { + errormsg = (char_u *)_("E981: Command not allowed in restricted mode"); + goto doend; + } if (!MODIFIABLE(curbuf) && (ea.argt & MODIFY) // allow :put in terminals && (!curbuf->terminal || ea.cmdidx != CMD_put)) { @@ -2477,8 +2476,11 @@ int parse_cmd_address(exarg_T *eap, char_u **errormsg) if (*eap->cmd == ';') { if (!eap->skip) { curwin->w_cursor.lnum = eap->line2; - // don't leave the cursor on an illegal line or column - check_cursor(); + // Don't leave the cursor on an illegal line or column, but do + // accept zero as address, so 0;/PATTERN/ works correctly. + if (eap->line2 > 0) { + check_cursor(); + } } } else if (*eap->cmd != ',') { break; @@ -4377,7 +4379,7 @@ int expand_filename(exarg_T *eap, char_u **cmdlinep, char_u **errormsgp) if (has_wildcards) { expand_T xpc; - int options = WILD_LIST_NOTFOUND|WILD_ADD_SLASH; + int options = WILD_LIST_NOTFOUND | WILD_NOERROR | WILD_ADD_SLASH; ExpandInit(&xpc); xpc.xp_context = EXPAND_FILES; @@ -4542,6 +4544,21 @@ skip_cmd_arg ( return p; } +int get_bad_opt(const char_u *p, exarg_T *eap) + FUNC_ATTR_NONNULL_ALL +{ + if (STRICMP(p, "keep") == 0) { + eap->bad_char = BAD_KEEP; + } else if (STRICMP(p, "drop") == 0) { + eap->bad_char = BAD_DROP; + } else if (MB_BYTE2LEN(*p) == 1 && p[1] == NUL) { + eap->bad_char = *p; + } else { + return FAIL; + } + return OK; +} + /* * Get "++opt=arg" argument. * Return FAIL or OK. @@ -4600,8 +4617,10 @@ static int getargopt(exarg_T *eap) *arg = NUL; if (pp == &eap->force_ff) { - if (check_ff_value(eap->cmd + eap->force_ff) == FAIL) + if (check_ff_value(eap->cmd + eap->force_ff) == FAIL) { return FAIL; + } + eap->force_ff = eap->cmd[eap->force_ff]; } else if (pp == &eap->force_enc) { /* Make 'fileencoding' lower case. */ for (p = eap->cmd + eap->force_enc; *p != NUL; ++p) @@ -4609,15 +4628,9 @@ static int getargopt(exarg_T *eap) } else { /* Check ++bad= argument. Must be a single-byte character, "keep" or * "drop". */ - p = eap->cmd + bad_char_idx; - if (STRICMP(p, "keep") == 0) - eap->bad_char = BAD_KEEP; - else if (STRICMP(p, "drop") == 0) - eap->bad_char = BAD_DROP; - else if (MB_BYTE2LEN(*p) == 1 && p[1] == NUL) - eap->bad_char = *p; - else + if (get_bad_opt(eap->cmd + bad_char_idx, eap) == FAIL) { return FAIL; + } } return OK; @@ -5134,9 +5147,8 @@ static char *get_command_complete(int arg) static void uc_list(char_u *name, size_t name_len) { int i, j; - int found = FALSE; + bool found = false; ucmd_T *cmd; - int len; uint32_t a; // In cmdwin, the alternative buffer should be used. @@ -5155,62 +5167,96 @@ static void uc_list(char_u *name, size_t name_len) continue; } - /* Put out the title first time */ - if (!found) - MSG_PUTS_TITLE(_("\n Name Args Address Complete Definition")); - found = TRUE; + // Put out the title first time + if (!found) { + MSG_PUTS_TITLE(_("\n Name Args Address " + "Complete Definition")); + } + found = true; msg_putchar('\n'); if (got_int) break; - /* Special cases */ - msg_putchar(a & BANG ? '!' : ' '); - msg_putchar(a & REGSTR ? '"' : ' '); - msg_putchar(gap != &ucmds ? 'b' : ' '); - msg_putchar(' '); + // Special cases + int len = 4; + if (a & BANG) { + msg_putchar('!'); + len--; + } + if (a & REGSTR) { + msg_putchar('"'); + len--; + } + if (gap != &ucmds) { + msg_putchar('b'); + len--; + } + if (a & TRLBAR) { + msg_putchar('|'); + len--; + } + while (len-- > 0) { + msg_putchar(' '); + } msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D)); len = (int)STRLEN(cmd->uc_name) + 4; do { msg_putchar(' '); - ++len; - } while (len < 16); + len++; + } while (len < 22); + // "over" is how much longer the name is than the column width for + // the name, we'll try to align what comes after. + const int over = len - 22; len = 0; - /* Arguments */ + // Arguments switch (a & (EXTRA|NOSPC|NEEDARG)) { - case 0: IObuff[len++] = '0'; break; - case (EXTRA): IObuff[len++] = '*'; break; - case (EXTRA|NOSPC): IObuff[len++] = '?'; break; - case (EXTRA|NEEDARG): IObuff[len++] = '+'; break; - case (EXTRA|NOSPC|NEEDARG): IObuff[len++] = '1'; break; + case 0: + IObuff[len++] = '0'; + break; + case (EXTRA): + IObuff[len++] = '*'; + break; + case (EXTRA|NOSPC): + IObuff[len++] = '?'; + break; + case (EXTRA|NEEDARG): + IObuff[len++] = '+'; + break; + case (EXTRA|NOSPC|NEEDARG): + IObuff[len++] = '1'; + break; } do { IObuff[len++] = ' '; - } while (len < 5); + } while (len < 5 - over); - /* Range */ + // Address / Range if (a & (RANGE|COUNT)) { if (a & COUNT) { - /* -count=N */ - sprintf((char *)IObuff + len, "%" PRId64 "c", (int64_t)cmd->uc_def); + // -count=N + snprintf((char *)IObuff + len, IOSIZE, "%" PRId64 "c", + (int64_t)cmd->uc_def); len += (int)STRLEN(IObuff + len); - } else if (a & DFLALL) + } else if (a & DFLALL) { IObuff[len++] = '%'; - else if (cmd->uc_def >= 0) { - /* -range=N */ - sprintf((char *)IObuff + len, "%" PRId64 "", (int64_t)cmd->uc_def); + } else if (cmd->uc_def >= 0) { + // -range=N + snprintf((char *)IObuff + len, IOSIZE, "%" PRId64 "", + (int64_t)cmd->uc_def); len += (int)STRLEN(IObuff + len); - } else + } else { IObuff[len++] = '.'; + } } do { IObuff[len++] = ' '; - } while (len < 11); + } while (len < 9 - over); // Address Type for (j = 0; addr_type_complete[j].expand != -1; j++) { @@ -5224,7 +5270,7 @@ static void uc_list(char_u *name, size_t name_len) do { IObuff[len++] = ' '; - } while (len < 21); + } while (len < 13 - over); // Completion char *cmd_compl = get_command_complete(cmd->uc_compl); @@ -5235,12 +5281,13 @@ static void uc_list(char_u *name, size_t name_len) do { IObuff[len++] = ' '; - } while (len < 35); + } while (len < 24 - over); IObuff[len] = '\0'; msg_outtrans(IObuff); - msg_outtrans_special(cmd->uc_rep, false); + msg_outtrans_special(cmd->uc_rep, false, + name_len == 0 ? Columns - 46 : 0); if (p_verbose > 0) { last_set_msg(cmd->uc_script_ctx); } @@ -5428,9 +5475,8 @@ static void ex_command(exarg_T *eap) end = p; name_len = (int)(end - name); - /* If there is nothing after the name, and no attributes were specified, - * we are listing commands - */ + // If there is nothing after the name, and no attributes were specified, + // we are listing commands p = skipwhite(end); if (!has_attr && ends_excmd(*p)) { uc_list(name, end - name); @@ -7323,7 +7369,7 @@ static void ex_syncbind(exarg_T *eap) topline = curwin->w_topline; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_p_scb && wp->w_buffer) { - y = wp->w_buffer->b_ml.ml_line_count - p_so; + y = wp->w_buffer->b_ml.ml_line_count - get_scrolloff_value(); if (topline > y) { topline = y; } @@ -8085,167 +8131,6 @@ static void close_redir(void) } } -#ifdef USE_CRNL -# define MKSESSION_NL -static int mksession_nl = FALSE; /* use NL only in put_eol() */ -#endif - -/* - * ":mkexrc", ":mkvimrc", ":mkview" and ":mksession". - */ -static void ex_mkrc(exarg_T *eap) -{ - FILE *fd; - int failed = false; - int view_session = false; - int using_vdir = false; // using 'viewdir'? - char *viewFile = NULL; - unsigned *flagp; - - if (eap->cmdidx == CMD_mksession || eap->cmdidx == CMD_mkview) { - view_session = TRUE; - } - - /* Use the short file name until ":lcd" is used. We also don't use the - * short file name when 'acd' is set, that is checked later. */ - did_lcd = FALSE; - - char *fname; - // ":mkview" or ":mkview 9": generate file name with 'viewdir' - if (eap->cmdidx == CMD_mkview - && (*eap->arg == NUL - || (ascii_isdigit(*eap->arg) && eap->arg[1] == NUL))) { - eap->forceit = true; - fname = get_view_file(*eap->arg); - if (fname == NULL) { - return; - } - viewFile = fname; - using_vdir = true; - } else if (*eap->arg != NUL) { - fname = (char *) eap->arg; - } else if (eap->cmdidx == CMD_mkvimrc) { - fname = VIMRC_FILE; - } else if (eap->cmdidx == CMD_mksession) { - fname = SESSION_FILE; - } else { - fname = EXRC_FILE; - } - - /* When using 'viewdir' may have to create the directory. */ - if (using_vdir && !os_isdir(p_vdir)) { - vim_mkdir_emsg((const char *)p_vdir, 0755); - } - - fd = open_exfile((char_u *) fname, eap->forceit, WRITEBIN); - if (fd != NULL) { - if (eap->cmdidx == CMD_mkview) - flagp = &vop_flags; - else - flagp = &ssop_flags; - -#ifdef MKSESSION_NL - /* "unix" in 'sessionoptions': use NL line separator */ - if (view_session && (*flagp & SSOP_UNIX)) - mksession_nl = TRUE; -#endif - - /* Write the version command for :mkvimrc */ - if (eap->cmdidx == CMD_mkvimrc) - (void)put_line(fd, "version 6.0"); - - if (eap->cmdidx == CMD_mksession) { - if (put_line(fd, "let SessionLoad = 1") == FAIL) - failed = TRUE; - } - - if (!view_session - || (eap->cmdidx == CMD_mksession - && (*flagp & SSOP_OPTIONS))) - failed |= (makemap(fd, NULL) == FAIL - || makeset(fd, OPT_GLOBAL, FALSE) == FAIL); - - if (!failed && view_session) { - if (put_line(fd, - "let s:so_save = &so | let s:siso_save = &siso | set so=0 siso=0") - == FAIL) - failed = TRUE; - if (eap->cmdidx == CMD_mksession) { - char_u *dirnow; /* current directory */ - - dirnow = xmalloc(MAXPATHL); - /* - * Change to session file's dir. - */ - if (os_dirname(dirnow, MAXPATHL) == FAIL - || os_chdir((char *)dirnow) != 0) - *dirnow = NUL; - if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) { - if (vim_chdirfile((char_u *) fname) == OK) { - shorten_fnames(true); - } - } else if (*dirnow != NUL - && (ssop_flags & SSOP_CURDIR) && globaldir != NULL) { - if (os_chdir((char *)globaldir) == 0) - shorten_fnames(TRUE); - } - - failed |= (makeopens(fd, dirnow) == FAIL); - - /* restore original dir */ - if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR) - || ((ssop_flags & SSOP_CURDIR) && globaldir != - NULL))) { - if (os_chdir((char *)dirnow) != 0) - EMSG(_(e_prev_dir)); - shorten_fnames(TRUE); - /* restore original dir */ - if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR) - || ((ssop_flags & SSOP_CURDIR) && globaldir != - NULL))) { - if (os_chdir((char *)dirnow) != 0) - EMSG(_(e_prev_dir)); - shorten_fnames(TRUE); - } - } - xfree(dirnow); - } else { - failed |= (put_view(fd, curwin, !using_vdir, flagp, - -1) == FAIL); - } - if (put_line(fd, "let &so = s:so_save | let &siso = s:siso_save") - == FAIL) - failed = TRUE; - if (put_line(fd, "doautoall SessionLoadPost") == FAIL) - failed = TRUE; - if (eap->cmdidx == CMD_mksession) { - if (put_line(fd, "unlet SessionLoad") == FAIL) - failed = TRUE; - } - } - if (put_line(fd, "\" vim: set ft=vim :") == FAIL) - failed = TRUE; - - failed |= fclose(fd); - - if (failed) { - EMSG(_(e_write)); - } else if (eap->cmdidx == CMD_mksession) { - // successful session write - set v:this_session - char *const tbuf = xmalloc(MAXPATHL); - if (vim_FullName(fname, tbuf, MAXPATHL, false) == OK) { - set_vim_var_string(VV_THIS_SESSION, tbuf, -1); - } - xfree(tbuf); - } -#ifdef MKSESSION_NL - mksession_nl = FALSE; -#endif - } - - xfree(viewFile); -} - /// Try creating a directory, give error message on failure /// /// @param[in] name Directory to create. @@ -8328,6 +8213,57 @@ void update_topline_cursor(void) update_curswant(); } +// Save the current State and go to Normal mode. +// Return true if the typeahead could be saved. +bool save_current_state(save_state_T *sst) + FUNC_ATTR_NONNULL_ALL +{ + sst->save_msg_scroll = msg_scroll; + sst->save_restart_edit = restart_edit; + sst->save_msg_didout = msg_didout; + sst->save_State = State; + sst->save_insertmode = p_im; + sst->save_finish_op = finish_op; + sst->save_opcount = opcount; + sst->save_reg_executing = reg_executing; + + msg_scroll = false; // no msg scrolling in Normal mode + restart_edit = 0; // don't go to Insert mode + p_im = false; // don't use 'insertmode + + // Save the current typeahead. This is required to allow using ":normal" + // from an event handler and makes sure we don't hang when the argument + // ends with half a command. + save_typeahead(&sst->tabuf); + return sst->tabuf.typebuf_valid; +} + +void restore_current_state(save_state_T *sst) + FUNC_ATTR_NONNULL_ALL +{ + // Restore the previous typeahead. + restore_typeahead(&sst->tabuf); + + msg_scroll = sst->save_msg_scroll; + if (force_restart_edit) { + force_restart_edit = false; + } else { + // Some function (terminal_enter()) was aware of ex_normal and decided to + // override the value of restart_edit anyway. + restart_edit = sst->save_restart_edit; + } + p_im = sst->save_insertmode; + finish_op = sst->save_finish_op; + opcount = sst->save_opcount; + reg_executing = sst->save_reg_executing; + msg_didout |= sst->save_msg_didout; // don't reset msg_didout now + + // Restore the state (needed when called from a function executed for + // 'indentexpr'). Update the mouse and cursor, they may have changed. + State = sst->save_State; + ui_cursor_shape(); // may show different cursor shape +} + /* * ":normal[!] {commands}": Execute normal mode commands. */ @@ -8337,15 +8273,7 @@ static void ex_normal(exarg_T *eap) EMSG("Can't re-enter normal mode from terminal mode"); return; } - int save_msg_scroll = msg_scroll; - int save_restart_edit = restart_edit; - int save_msg_didout = msg_didout; - int save_State = State; - tasave_T tabuf; - int save_insertmode = p_im; - int save_finish_op = finish_op; - long save_opcount = opcount; - const int save_reg_executing = reg_executing; + save_state_T save_state; char_u *arg = NULL; int l; char_u *p; @@ -8358,11 +8286,6 @@ static void ex_normal(exarg_T *eap) EMSG(_("E192: Recursive use of :normal too deep")); return; } - ++ex_normal_busy; - - msg_scroll = FALSE; /* no msg scrolling in Normal mode */ - restart_edit = 0; /* don't go to Insert mode */ - p_im = FALSE; /* don't use 'insertmode' */ /* * vgetc() expects a CSI and K_SPECIAL to have been escaped. Don't do @@ -8396,19 +8319,11 @@ static void ex_normal(exarg_T *eap) } } - /* - * Save the current typeahead. This is required to allow using ":normal" - * from an event handler and makes sure we don't hang when the argument - * ends with half a command. - */ - save_typeahead(&tabuf); - // TODO(philix): after save_typeahead() this is always TRUE - if (tabuf.typebuf_valid) { - /* - * Repeat the :normal command for each line in the range. When no - * range given, execute it just once, without positioning the cursor - * first. - */ + ex_normal_busy++; + if (save_current_state(&save_state)) { + // Repeat the :normal command for each line in the range. When no + // range given, execute it just once, without positioning the cursor + // first. do { if (eap->addr_count != 0) { curwin->w_cursor.lnum = eap->line1++; @@ -8425,29 +8340,12 @@ static void ex_normal(exarg_T *eap) /* Might not return to the main loop when in an event handler. */ update_topline_cursor(); - /* Restore the previous typeahead. */ - restore_typeahead(&tabuf); + restore_current_state(&save_state); - --ex_normal_busy; - msg_scroll = save_msg_scroll; - if (force_restart_edit) { - force_restart_edit = false; - } else { - // Some function (terminal_enter()) was aware of ex_normal and decided to - // override the value of restart_edit anyway. - restart_edit = save_restart_edit; - } - p_im = save_insertmode; - finish_op = save_finish_op; - opcount = save_opcount; - reg_executing = save_reg_executing; - msg_didout |= save_msg_didout; // don't reset msg_didout now + ex_normal_busy--; - /* Restore the state (needed when called from a function executed for - * 'indentexpr'). Update the mouse and cursor, they may have changed. */ - State = save_State; setmouse(); - ui_cursor_shape(); /* may show different cursor shape */ + ui_cursor_shape(); // may show different cursor shape xfree(arg); } @@ -9118,864 +9016,6 @@ char_u *expand_sfile(char_u *arg) return result; } - -/* - * Write openfile commands for the current buffers to an .exrc file. - * Return FAIL on error, OK otherwise. - */ -static int -makeopens( - FILE *fd, - char_u *dirnow /* Current directory name */ -) -{ - int only_save_windows = TRUE; - int nr; - int restore_size = true; - win_T *wp; - char_u *sname; - win_T *edited_win = NULL; - int tabnr; - win_T *tab_firstwin; - frame_T *tab_topframe; - int cur_arg_idx = 0; - int next_arg_idx = 0; - - if (ssop_flags & SSOP_BUFFERS) - only_save_windows = FALSE; /* Save ALL buffers */ - - // Begin by setting v:this_session, and then other sessionable variables. - if (put_line(fd, "let v:this_session=expand(\"<sfile>:p\")") == FAIL) { - return FAIL; - } - if (ssop_flags & SSOP_GLOBALS) { - if (store_session_globals(fd) == FAIL) { - return FAIL; - } - } - - /* - * Close all windows but one. - */ - if (put_line(fd, "silent only") == FAIL) - return FAIL; - - /* - * Now a :cd command to the session directory or the current directory - */ - if (ssop_flags & SSOP_SESDIR) { - if (put_line(fd, "exe \"cd \" . escape(expand(\"<sfile>:p:h\"), ' ')") - == FAIL) - return FAIL; - } else if (ssop_flags & SSOP_CURDIR) { - sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow); - if (fputs("cd ", fd) < 0 - || ses_put_fname(fd, sname, &ssop_flags) == FAIL - || put_eol(fd) == FAIL) { - xfree(sname); - return FAIL; - } - xfree(sname); - } - - /* - * If there is an empty, unnamed buffer we will wipe it out later. - * Remember the buffer number. - */ - if (put_line(fd, - "if expand('%') == '' && !&modified && line('$') <= 1 && getline(1) == ''") - == - FAIL) - return FAIL; - if (put_line(fd, " let s:wipebuf = bufnr('%')") == FAIL) - return FAIL; - if (put_line(fd, "endif") == FAIL) - return FAIL; - - /* - * Now save the current files, current buffer first. - */ - if (put_line(fd, "set shortmess=aoO") == FAIL) - return FAIL; - - /* Now put the other buffers into the buffer list */ - FOR_ALL_BUFFERS(buf) { - if (!(only_save_windows && buf->b_nwindows == 0) - && !(buf->b_help && !(ssop_flags & SSOP_HELP)) - && buf->b_fname != NULL - && buf->b_p_bl) { - if (fprintf(fd, "badd +%" PRId64 " ", - buf->b_wininfo == NULL - ? (int64_t)1L - : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0 - || ses_fname(fd, buf, &ssop_flags, true) == FAIL) { - return FAIL; - } - } - } - - /* the global argument list */ - if (ses_arglist(fd, "argglobal", &global_alist.al_ga, - !(ssop_flags & SSOP_CURDIR), &ssop_flags) == FAIL) { - return FAIL; - } - - if (ssop_flags & SSOP_RESIZE) { - /* Note: after the restore we still check it worked!*/ - if (fprintf(fd, "set lines=%" PRId64 " columns=%" PRId64, - (int64_t)Rows, (int64_t)Columns) < 0 - || put_eol(fd) == FAIL) - return FAIL; - } - - int restore_stal = FALSE; - // When there are two or more tabpages and 'showtabline' is 1 the tabline - // will be displayed when creating the next tab. That resizes the windows - // in the first tab, which may cause problems. Set 'showtabline' to 2 - // temporarily to avoid that. - if (p_stal == 1 && first_tabpage->tp_next != NULL) { - if (put_line(fd, "set stal=2") == FAIL) { - return FAIL; - } - restore_stal = TRUE; - } - - /* - * May repeat putting Windows for each tab, when "tabpages" is in - * 'sessionoptions'. - * Don't use goto_tabpage(), it may change directory and trigger - * autocommands. - */ - tab_firstwin = firstwin; /* first window in tab page "tabnr" */ - tab_topframe = topframe; - for (tabnr = 1;; tabnr++) { - tabpage_T *tp = find_tabpage(tabnr); - if (tp == NULL) { - break; // done all tab pages - } - - int need_tabnew = false; - int cnr = 1; - - if ((ssop_flags & SSOP_TABPAGES)) { - if (tp == curtab) { - tab_firstwin = firstwin; - tab_topframe = topframe; - } else { - tab_firstwin = tp->tp_firstwin; - tab_topframe = tp->tp_topframe; - } - if (tabnr > 1) - need_tabnew = TRUE; - } - - /* - * Before creating the window layout, try loading one file. If this - * is aborted we don't end up with a number of useless windows. - * This may have side effects! (e.g., compressed or network file). - */ - for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { - if (ses_do_win(wp) - && wp->w_buffer->b_ffname != NULL - && !bt_help(wp->w_buffer) - && !bt_nofile(wp->w_buffer) - ) { - if (fputs(need_tabnew ? "tabedit " : "edit ", fd) < 0 - || ses_fname(fd, wp->w_buffer, &ssop_flags, true) == FAIL) { - return FAIL; - } - need_tabnew = false; - if (!wp->w_arg_idx_invalid) { - edited_win = wp; - } - break; - } - } - - /* If no file got edited create an empty tab page. */ - if (need_tabnew && put_line(fd, "tabnew") == FAIL) - return FAIL; - - /* - * Save current window layout. - */ - if (put_line(fd, "set splitbelow splitright") == FAIL) - return FAIL; - if (ses_win_rec(fd, tab_topframe) == FAIL) - return FAIL; - if (!p_sb && put_line(fd, "set nosplitbelow") == FAIL) - return FAIL; - if (!p_spr && put_line(fd, "set nosplitright") == FAIL) - return FAIL; - - /* - * Check if window sizes can be restored (no windows omitted). - * Remember the window number of the current window after restoring. - */ - nr = 0; - for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { - if (ses_do_win(wp)) - ++nr; - else - restore_size = FALSE; - if (curwin == wp) - cnr = nr; - } - - /* Go to the first window. */ - if (put_line(fd, "wincmd t") == FAIL) - return FAIL; - - // If more than one window, see if sizes can be restored. - // First set 'winheight' and 'winwidth' to 1 to avoid the windows being - // resized when moving between windows. - // Do this before restoring the view, so that the topline and the - // cursor can be set. This is done again below. - // winminheight and winminwidth need to be set to avoid an error if the - // user has set winheight or winwidth. - if (put_line(fd, "set winminheight=0") == FAIL - || put_line(fd, "set winheight=1") == FAIL - || put_line(fd, "set winminwidth=0") == FAIL - || put_line(fd, "set winwidth=1") == FAIL) { - return FAIL; - } - if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) { - return FAIL; - } - - /* - * Restore the view of the window (options, file, cursor, etc.). - */ - for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { - if (!ses_do_win(wp)) - continue; - if (put_view(fd, wp, wp != edited_win, &ssop_flags, - cur_arg_idx) == FAIL) - return FAIL; - if (nr > 1 && put_line(fd, "wincmd w") == FAIL) - return FAIL; - next_arg_idx = wp->w_arg_idx; - } - - /* The argument index in the first tab page is zero, need to set it in - * each window. For further tab pages it's the window where we do - * "tabedit". */ - cur_arg_idx = next_arg_idx; - - /* - * Restore cursor to the current window if it's not the first one. - */ - if (cnr > 1 && (fprintf(fd, "%dwincmd w", cnr) < 0 - || put_eol(fd) == FAIL)) - return FAIL; - - /* - * Restore window sizes again after jumping around in windows, because - * the current window has a minimum size while others may not. - */ - if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) - return FAIL; - - // Take care of tab-local working directories if applicable - if (tp->tp_localdir) { - if (fputs("if exists(':tcd') == 2 | tcd ", fd) < 0 - || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL - || fputs(" | endif", fd) < 0 - || put_eol(fd) == FAIL) { - return FAIL; - } - did_lcd = true; - } - - /* Don't continue in another tab page when doing only the current one - * or when at the last tab page. */ - if (!(ssop_flags & SSOP_TABPAGES)) - break; - } - - if (ssop_flags & SSOP_TABPAGES) { - if (fprintf(fd, "tabnext %d", tabpage_index(curtab)) < 0 - || put_eol(fd) == FAIL) - return FAIL; - } - if (restore_stal && put_line(fd, "set stal=1") == FAIL) { - return FAIL; - } - - /* - * Wipe out an empty unnamed buffer we started in. - */ - if (put_line(fd, "if exists('s:wipebuf') " - "&& getbufvar(s:wipebuf, '&buftype') isnot# 'terminal'") - == FAIL) - return FAIL; - if (put_line(fd, " silent exe 'bwipe ' . s:wipebuf") == FAIL) - return FAIL; - if (put_line(fd, "endif") == FAIL) - return FAIL; - if (put_line(fd, "unlet! s:wipebuf") == FAIL) - return FAIL; - - // Re-apply options. - if (fprintf(fd, "set winheight=%" PRId64 " winwidth=%" PRId64 - " winminheight=%" PRId64 " winminwidth=%" PRId64 - " shortmess=%s", - (int64_t)p_wh, - (int64_t)p_wiw, - (int64_t)p_wmh, - (int64_t)p_wmw, - p_shm) < 0 - || put_eol(fd) == FAIL) { - return FAIL; - } - - /* - * Lastly, execute the x.vim file if it exists. - */ - if (put_line(fd, "let s:sx = expand(\"<sfile>:p:r\").\"x.vim\"") == FAIL - || put_line(fd, "if file_readable(s:sx)") == FAIL - || put_line(fd, " exe \"source \" . fnameescape(s:sx)") == FAIL - || put_line(fd, "endif") == FAIL) - return FAIL; - - return OK; -} - -static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin) -{ - int n = 0; - win_T *wp; - - if (restore_size && (ssop_flags & SSOP_WINSIZE)) { - for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { - if (!ses_do_win(wp)) { - continue; - } - n++; - - // restore height when not full height - if (wp->w_height + wp->w_status_height < topframe->fr_height - && (fprintf(fd, - "exe '%dresize ' . ((&lines * %" PRId64 - " + %" PRId64 ") / %" PRId64 ")", - n, (int64_t)wp->w_height, - (int64_t)Rows / 2, (int64_t)Rows) < 0 - || put_eol(fd) == FAIL)) { - return FAIL; - } - - // restore width when not full width - if (wp->w_width < Columns - && (fprintf(fd, - "exe 'vert %dresize ' . ((&columns * %" PRId64 - " + %" PRId64 ") / %" PRId64 ")", - n, (int64_t)wp->w_width, (int64_t)Columns / 2, - (int64_t)Columns) < 0 - || put_eol(fd) == FAIL)) { - return FAIL; - } - } - } else { - // Just equalise window sizes - if (put_line(fd, "wincmd =") == FAIL) { - return FAIL; - } - } - return OK; -} - -/* - * Write commands to "fd" to recursively create windows for frame "fr", - * horizontally and vertically split. - * After the commands the last window in the frame is the current window. - * Returns FAIL when writing the commands to "fd" fails. - */ -static int ses_win_rec(FILE *fd, frame_T *fr) -{ - frame_T *frc; - int count = 0; - - if (fr->fr_layout != FR_LEAF) { - /* Find first frame that's not skipped and then create a window for - * each following one (first frame is already there). */ - frc = ses_skipframe(fr->fr_child); - if (frc != NULL) - while ((frc = ses_skipframe(frc->fr_next)) != NULL) { - /* Make window as big as possible so that we have lots of room - * to split. */ - if (put_line(fd, "wincmd _ | wincmd |") == FAIL - || put_line(fd, fr->fr_layout == FR_COL - ? "split" : "vsplit") == FAIL) - return FAIL; - ++count; - } - - /* Go back to the first window. */ - if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL - ? "%dwincmd k" : "%dwincmd h", count) < 0 - || put_eol(fd) == FAIL)) - return FAIL; - - /* Recursively create frames/windows in each window of this column or - * row. */ - frc = ses_skipframe(fr->fr_child); - while (frc != NULL) { - ses_win_rec(fd, frc); - frc = ses_skipframe(frc->fr_next); - /* Go to next window. */ - if (frc != NULL && put_line(fd, "wincmd w") == FAIL) - return FAIL; - } - } - return OK; -} - -/* - * Skip frames that don't contain windows we want to save in the Session. - * Returns NULL when there none. - */ -static frame_T *ses_skipframe(frame_T *fr) -{ - frame_T *frc; - - FOR_ALL_FRAMES(frc, fr) { - if (ses_do_frame(frc)) { - break; - } - } - return frc; -} - -// Return true if frame "fr" has a window somewhere that we want to save in -// the Session. -static bool ses_do_frame(const frame_T *fr) - FUNC_ATTR_NONNULL_ARG(1) -{ - const frame_T *frc; - - if (fr->fr_layout == FR_LEAF) { - return ses_do_win(fr->fr_win); - } - FOR_ALL_FRAMES(frc, fr->fr_child) { - if (ses_do_frame(frc)) { - return true; - } - } - return false; -} - -/// Return non-zero if window "wp" is to be stored in the Session. -static int ses_do_win(win_T *wp) -{ - if (wp->w_buffer->b_fname == NULL - // When 'buftype' is "nofile" can't restore the window contents. - || (!wp->w_buffer->terminal && bt_nofile(wp->w_buffer))) { - return ssop_flags & SSOP_BLANK; - } - if (bt_help(wp->w_buffer)) { - return ssop_flags & SSOP_HELP; - } - return true; -} - -static int put_view_curpos(FILE *fd, const win_T *wp, char *spaces) -{ - int r; - - if (wp->w_curswant == MAXCOL) { - r = fprintf(fd, "%snormal! $", spaces); - } else { - r = fprintf(fd, "%snormal! 0%d|", spaces, wp->w_virtcol + 1); - } - return r < 0 || put_eol(fd) == FAIL ? FAIL : OK; -} - -/* - * Write commands to "fd" to restore the view of a window. - * Caller must make sure 'scrolloff' is zero. - */ -static int -put_view( - FILE *fd, - win_T *wp, - int add_edit, /* add ":edit" command to view */ - unsigned *flagp, /* vop_flags or ssop_flags */ - int current_arg_idx /* current argument index of the window, use - * -1 if unknown */ -) -{ - win_T *save_curwin; - int f; - int do_cursor; - int did_next = FALSE; - - /* Always restore cursor position for ":mksession". For ":mkview" only - * when 'viewoptions' contains "cursor". */ - do_cursor = (flagp == &ssop_flags || *flagp & SSOP_CURSOR); - - /* - * Local argument list. - */ - if (wp->w_alist == &global_alist) { - if (put_line(fd, "argglobal") == FAIL) - return FAIL; - } else { - if (ses_arglist(fd, "arglocal", &wp->w_alist->al_ga, - flagp == &vop_flags - || !(*flagp & SSOP_CURDIR) - || wp->w_localdir != NULL, flagp) == FAIL) - return FAIL; - } - - /* Only when part of a session: restore the argument index. Some - * arguments may have been deleted, check if the index is valid. */ - if (wp->w_arg_idx != current_arg_idx && wp->w_arg_idx < WARGCOUNT(wp) - && flagp == &ssop_flags) { - if (fprintf(fd, "%" PRId64 "argu", (int64_t)wp->w_arg_idx + 1) < 0 - || put_eol(fd) == FAIL) { - return FAIL; - } - did_next = true; - } - - /* Edit the file. Skip this when ":next" already did it. */ - if (add_edit && (!did_next || wp->w_arg_idx_invalid)) { - /* - * Load the file. - */ - if (wp->w_buffer->b_ffname != NULL - && (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal) - ) { - // Editing a file in this buffer: use ":edit file". - // This may have side effects! (e.g., compressed or network file). - // - // Note, if a buffer for that file already exists, use :badd to - // edit that buffer, to not lose folding information (:edit resets - // folds in other buffers) - if (fputs("if bufexists(\"", fd) < 0 - || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL - || fputs("\") | buffer ", fd) < 0 - || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL - || fputs(" | else | edit ", fd) < 0 - || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL - || fputs(" | endif", fd) < 0 - || put_eol(fd) == FAIL) { - return FAIL; - } - } else { - // No file in this buffer, just make it empty. - if (put_line(fd, "enew") == FAIL) { - return FAIL; - } - if (wp->w_buffer->b_ffname != NULL) { - // The buffer does have a name, but it's not a file name. - if (fputs("file ", fd) < 0 - || ses_fname(fd, wp->w_buffer, flagp, true) == FAIL) { - return FAIL; - } - } - do_cursor = false; - } - } - - /* - * Local mappings and abbreviations. - */ - if ((*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) - && makemap(fd, wp->w_buffer) == FAIL) - return FAIL; - - /* - * Local options. Need to go to the window temporarily. - * Store only local values when using ":mkview" and when ":mksession" is - * used and 'sessionoptions' doesn't include "nvim/options". - * Some folding options are always stored when "folds" is included, - * otherwise the folds would not be restored correctly. - */ - save_curwin = curwin; - curwin = wp; - curbuf = curwin->w_buffer; - if (*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) - f = makeset(fd, OPT_LOCAL, - flagp == &vop_flags || !(*flagp & SSOP_OPTIONS)); - else if (*flagp & SSOP_FOLDS) - f = makefoldset(fd); - else - f = OK; - curwin = save_curwin; - curbuf = curwin->w_buffer; - if (f == FAIL) - return FAIL; - - /* - * Save Folds when 'buftype' is empty and for help files. - */ - if ((*flagp & SSOP_FOLDS) - && wp->w_buffer->b_ffname != NULL - && (bt_normal(wp->w_buffer) || bt_help(wp->w_buffer)) - ) { - if (put_folds(fd, wp) == FAIL) - return FAIL; - } - - /* - * Set the cursor after creating folds, since that moves the cursor. - */ - if (do_cursor) { - - /* Restore the cursor line in the file and relatively in the - * window. Don't use "G", it changes the jumplist. */ - if (fprintf(fd, - "let s:l = %" PRId64 " - ((%" PRId64 - " * winheight(0) + %" PRId64 ") / %" PRId64 ")", - (int64_t)wp->w_cursor.lnum, - (int64_t)(wp->w_cursor.lnum - wp->w_topline), - (int64_t)(wp->w_height_inner / 2), - (int64_t)wp->w_height_inner) < 0 - || put_eol(fd) == FAIL - || put_line(fd, "if s:l < 1 | let s:l = 1 | endif") == FAIL - || put_line(fd, "exe s:l") == FAIL - || put_line(fd, "normal! zt") == FAIL - || fprintf(fd, "%" PRId64, (int64_t)wp->w_cursor.lnum) < 0 - || put_eol(fd) == FAIL) - return FAIL; - /* Restore the cursor column and left offset when not wrapping. */ - if (wp->w_cursor.col == 0) { - if (put_line(fd, "normal! 0") == FAIL) - return FAIL; - } else { - if (!wp->w_p_wrap && wp->w_leftcol > 0 && wp->w_width > 0) { - if (fprintf(fd, - "let s:c = %" PRId64 " - ((%" PRId64 - " * winwidth(0) + %" PRId64 ") / %" PRId64 ")", - (int64_t)wp->w_virtcol + 1, - (int64_t)(wp->w_virtcol - wp->w_leftcol), - (int64_t)(wp->w_width / 2), - (int64_t)wp->w_width) < 0 - || put_eol(fd) == FAIL - || put_line(fd, "if s:c > 0") == FAIL - || fprintf(fd, " exe 'normal! ' . s:c . '|zs' . %" PRId64 " . '|'", - (int64_t)wp->w_virtcol + 1) < 0 - || put_eol(fd) == FAIL - || put_line(fd, "else") == FAIL - || put_view_curpos(fd, wp, " ") == FAIL - || put_line(fd, "endif") == FAIL) { - return FAIL; - } - } else if (put_view_curpos(fd, wp, "") == FAIL) { - return FAIL; - } - } - } - - // - // Local directory, if the current flag is not view options or the "curdir" - // option is included. - // - if (wp->w_localdir != NULL - && (flagp != &vop_flags || (*flagp & SSOP_CURDIR))) { - if (fputs("lcd ", fd) < 0 - || ses_put_fname(fd, wp->w_localdir, flagp) == FAIL - || put_eol(fd) == FAIL) { - return FAIL; - } - did_lcd = true; - } - - return OK; -} - -/* - * Write an argument list to the session file. - * Returns FAIL if writing fails. - */ -static int -ses_arglist( - FILE *fd, - char *cmd, - garray_T *gap, - int fullname, /* TRUE: use full path name */ - unsigned *flagp -) -{ - char_u *buf = NULL; - char_u *s; - - if (fputs(cmd, fd) < 0 || put_eol(fd) == FAIL) { - return FAIL; - } - if (put_line(fd, "%argdel") == FAIL) { - return FAIL; - } - for (int i = 0; i < gap->ga_len; ++i) { - /* NULL file names are skipped (only happens when out of memory). */ - s = alist_name(&((aentry_T *)gap->ga_data)[i]); - if (s != NULL) { - if (fullname) { - buf = xmalloc(MAXPATHL); - (void)vim_FullName((char *)s, (char *)buf, MAXPATHL, FALSE); - s = buf; - } - if (fputs("$argadd ", fd) < 0 || ses_put_fname(fd, s, flagp) == FAIL - || put_eol(fd) == FAIL) { - xfree(buf); - return FAIL; - } - xfree(buf); - } - } - return OK; -} - -/// Write a buffer name to the session file. -/// Also ends the line, if "add_eol" is true. -/// Returns FAIL if writing fails. -static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol) -{ - char_u *name; - - /* Use the short file name if the current directory is known at the time - * the session file will be sourced. - * Don't do this for ":mkview", we don't know the current directory. - * Don't do this after ":lcd", we don't keep track of what the current - * directory is. */ - if (buf->b_sfname != NULL - && flagp == &ssop_flags - && (ssop_flags & (SSOP_CURDIR | SSOP_SESDIR)) - && !p_acd - && !did_lcd) - name = buf->b_sfname; - else - name = buf->b_ffname; - if (ses_put_fname(fd, name, flagp) == FAIL - || (add_eol && put_eol(fd) == FAIL)) { - return FAIL; - } - return OK; -} - -/* - * Write a file name to the session file. - * Takes care of the "slash" option in 'sessionoptions' and escapes special - * characters. - * Returns FAIL if writing fails. - */ -static int ses_put_fname(FILE *fd, char_u *name, unsigned *flagp) -{ - char_u *p; - - char_u *sname = home_replace_save(NULL, name); - - if (*flagp & SSOP_SLASH) { - // change all backslashes to forward slashes - for (p = sname; *p != NUL; MB_PTR_ADV(p)) { - if (*p == '\\') { - *p = '/'; - } - } - } - - // Escape special characters. - p = (char_u *)vim_strsave_fnameescape((const char *)sname, false); - xfree(sname); - - /* write the result */ - bool retval = fputs((char *)p, fd) < 0 ? FAIL : OK; - xfree(p); - return retval; -} - -/* - * ":loadview [nr]" - */ -static void ex_loadview(exarg_T *eap) -{ - char *fname = get_view_file(*eap->arg); - if (fname != NULL) { - if (do_source((char_u *)fname, FALSE, DOSO_NONE) == FAIL) { - EMSG2(_(e_notopen), fname); - } - xfree(fname); - } -} - -/// Get the name of the view file for the current buffer. -static char *get_view_file(int c) -{ - if (curbuf->b_ffname == NULL) { - EMSG(_(e_noname)); - return NULL; - } - char *sname = (char *)home_replace_save(NULL, curbuf->b_ffname); - - // We want a file name without separators, because we're not going to make - // a directory. - // "normal" path separator -> "=+" - // "=" -> "==" - // ":" path separator -> "=-" - size_t len = 0; - for (char *p = sname; *p; p++) { - if (*p == '=' || vim_ispathsep(*p)) { - ++len; - } - } - char *retval = xmalloc(strlen(sname) + len + STRLEN(p_vdir) + 9); - STRCPY(retval, p_vdir); - add_pathsep(retval); - char *s = retval + strlen(retval); - for (char *p = sname; *p; p++) { - if (*p == '=') { - *s++ = '='; - *s++ = '='; - } else if (vim_ispathsep(*p)) { - *s++ = '='; -#if defined(BACKSLASH_IN_FILENAME) - if (*p == ':') - *s++ = '-'; - else -#endif - *s++ = '+'; - } else - *s++ = *p; - } - *s++ = '='; - *s++ = c; - strcpy(s, ".vim"); - - xfree(sname); - return retval; -} - - -/* - * Write end-of-line character(s) for ":mkexrc", ":mkvimrc" and ":mksession". - * Return FAIL for a write error. - */ -int put_eol(FILE *fd) -{ -#if defined(USE_CRNL) && defined(MKSESSION_NL) - if ((!mksession_nl && putc('\r', fd) < 0) || putc('\n', fd) < 0) { -#elif defined(USE_CRNL) - if (putc('\r', fd) < 0 || putc('\n', fd) < 0) { -#else - if (putc('\n', fd) < 0) { -#endif - return FAIL; - } - return OK; -} - -/* - * Write a line to "fd". - * Return FAIL for a write error. - */ -int put_line(FILE *fd, char *s) -{ - if (fputs(s, fd) < 0 || put_eol(fd) == FAIL) - return FAIL; - return OK; -} - /* * ":rshada" and ":wshada". */ @@ -10268,8 +9308,9 @@ static void ex_match(exarg_T *eap) static void ex_fold(exarg_T *eap) { - if (foldManualAllowed(TRUE)) - foldCreate(eap->line1, eap->line2); + if (foldManualAllowed(true)) { + foldCreate(curwin, eap->line1, eap->line2); + } } static void ex_foldopen(exarg_T *eap) diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h index cff350de08..f6bd2adcd5 100644 --- a/src/nvim/ex_docmd.h +++ b/src/nvim/ex_docmd.h @@ -20,6 +20,20 @@ #define EXMODE_NORMAL 1 #define EXMODE_VIM 2 +// Structure used to save the current state. Used when executing Normal mode +// commands while in any other mode. +typedef struct { + int save_msg_scroll; + int save_restart_edit; + int save_msg_didout; + int save_State; + int save_insertmode; + bool save_finish_op; + long save_opcount; + int save_reg_executing; + tasave_T tabuf; +} save_state_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_docmd.h.generated.h" #endif diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index f70a568e4a..81274fcf2a 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -16,7 +16,7 @@ #include "nvim/ex_eval.h" #include "nvim/charset.h" #include "nvim/eval.h" -#include "nvim/eval/typval.h" +#include "nvim/eval/userfunc.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/message.h" @@ -508,7 +508,7 @@ static int throw_exception(void *value, except_type_T type, char_u *cmdname) nomem: xfree(excp); - suppress_errthrow = TRUE; + suppress_errthrow = true; EMSG(_(e_outofmem)); fail: current_exception = NULL; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index e6b7bfaebf..b799e86ff7 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -23,6 +23,7 @@ #include "nvim/digraph.h" #include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/userfunc.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" @@ -621,6 +622,16 @@ static int command_line_execute(VimState *state, int key) s->c = Ctrl_N; } } + if (compl_match_array || s->did_wild_list) { + if (s->c == Ctrl_E) { + s->res = nextwild(&s->xpc, WILD_CANCEL, WILD_NO_BEEP, + s->firstc != '@'); + } else if (s->c == Ctrl_Y) { + s->res = nextwild(&s->xpc, WILD_APPLY, WILD_NO_BEEP, + s->firstc != '@'); + s->c = Ctrl_E; + } + } // Hitting CR after "emenu Name.": complete submenu if (s->xpc.xp_context == EXPAND_MENUNAMES && p_wmnu @@ -937,6 +948,10 @@ static int command_line_execute(VimState *state, int key) // - wildcard expansion is only done when the 'wildchar' key is really // typed, not when it comes from a macro if ((s->c == p_wc && !s->gotesc && KeyTyped) || s->c == p_wcm) { + int options = WILD_NO_BEEP; + if (wim_flags[s->wim_index] & WIM_BUFLASTUSED) { + options |= WILD_BUFLASTUSED; + } if (s->xpc.xp_numfiles > 0) { // typed p_wc at least twice // if 'wildmode' contains "list" may still need to list if (s->xpc.xp_numfiles > 1 @@ -950,11 +965,11 @@ static int command_line_execute(VimState *state, int key) } if (wim_flags[s->wim_index] & WIM_LONGEST) { - s->res = nextwild(&s->xpc, WILD_LONGEST, WILD_NO_BEEP, - s->firstc != '@'); + s->res = nextwild(&s->xpc, WILD_LONGEST, options, + s->firstc != '@'); } else if (wim_flags[s->wim_index] & WIM_FULL) { - s->res = nextwild(&s->xpc, WILD_NEXT, WILD_NO_BEEP, - s->firstc != '@'); + s->res = nextwild(&s->xpc, WILD_NEXT, options, + s->firstc != '@'); } else { s->res = OK; // don't insert 'wildchar' now } @@ -965,11 +980,11 @@ static int command_line_execute(VimState *state, int key) // if 'wildmode' first contains "longest", get longest // common part if (wim_flags[0] & WIM_LONGEST) { - s->res = nextwild(&s->xpc, WILD_LONGEST, WILD_NO_BEEP, - s->firstc != '@'); + s->res = nextwild(&s->xpc, WILD_LONGEST, options, + s->firstc != '@'); } else { - s->res = nextwild(&s->xpc, WILD_EXPAND_KEEP, WILD_NO_BEEP, - s->firstc != '@'); + s->res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options, + s->firstc != '@'); } // if interrupted while completing, behave like it failed @@ -1006,11 +1021,11 @@ static int command_line_execute(VimState *state, int key) s->did_wild_list = true; if (wim_flags[s->wim_index] & WIM_LONGEST) { - nextwild(&s->xpc, WILD_LONGEST, WILD_NO_BEEP, - s->firstc != '@'); + nextwild(&s->xpc, WILD_LONGEST, options, + s->firstc != '@'); } else if (wim_flags[s->wim_index] & WIM_FULL) { - nextwild(&s->xpc, WILD_NEXT, WILD_NO_BEEP, - s->firstc != '@'); + nextwild(&s->xpc, WILD_NEXT, options, + s->firstc != '@'); } } else { vim_beep(BO_WILD); @@ -2437,9 +2452,10 @@ redraw: /* make following messages go to the next line */ msg_didout = FALSE; msg_col = 0; - if (msg_row < Rows - 1) - ++msg_row; - emsg_on_display = FALSE; /* don't want os_delay() */ + if (msg_row < Rows - 1) { + msg_row++; + } + emsg_on_display = false; // don't want os_delay() if (got_int) ga_clear(&line_ga); @@ -2701,7 +2717,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) goto color_cmdline_error; } if (tv.v_type != VAR_LIST) { - PRINT_ERRMSG(_("E5400: Callback should return list")); + PRINT_ERRMSG("%s", _("E5400: Callback should return list")); goto color_cmdline_error; } if (tv.vval.v_list == NULL) { @@ -3040,7 +3056,7 @@ void cmdline_screen_cleared(void) } } -/// called by ui_flush, do what redraws neccessary to keep cmdline updated. +/// called by ui_flush, do what redraws necessary to keep cmdline updated. void cmdline_ui_flush(void) { if (!ui_has(kUICmdline)) { @@ -3790,6 +3806,12 @@ ExpandOne ( return NULL; } + if (mode == WILD_CANCEL) { + ss = vim_strsave(orig_save); + } else if (mode == WILD_APPLY) { + ss = vim_strsave(findex == -1 ? orig_save : xp->xp_files[findex]); + } + /* free old names */ if (xp->xp_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST) { FreeWild(xp->xp_numfiles, xp->xp_files); @@ -3801,7 +3823,7 @@ ExpandOne ( if (mode == WILD_FREE) /* only release file name */ return NULL; - if (xp->xp_numfiles == -1) { + if (xp->xp_numfiles == -1 && mode != WILD_APPLY && mode != WILD_CANCEL) { xfree(orig_save); orig_save = orig; orig_saved = TRUE; @@ -4693,6 +4715,9 @@ ExpandFromContext ( flags |= EW_KEEPALL; if (options & WILD_SILENT) flags |= EW_SILENT; + if (options & WILD_NOERROR) { + flags |= EW_NOERROR; + } if (options & WILD_ALLLINKS) { flags |= EW_ALLLINKS; } diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h index 99d5a7786d..dc4395e081 100644 --- a/src/nvim/ex_getln.h +++ b/src/nvim/ex_getln.h @@ -16,6 +16,8 @@ #define WILD_ALL 6 #define WILD_LONGEST 7 #define WILD_ALL_KEEP 8 +#define WILD_CANCEL 9 +#define WILD_APPLY 10 #define WILD_LIST_NOTFOUND 0x01 #define WILD_HOME_REPLACE 0x02 @@ -29,6 +31,7 @@ #define WILD_ALLLINKS 0x200 #define WILD_IGNORE_COMPLETESLASH 0x400 #define WILD_NOERROR 0x800 // sets EW_NOERROR +#define WILD_BUFLASTUSED 0x1000 /// Present history tables typedef enum { diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c new file mode 100644 index 0000000000..1b797c6168 --- /dev/null +++ b/src/nvim/ex_session.c @@ -0,0 +1,1047 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// Functions for creating a session file, i.e. implementing: +// :mkexrc +// :mkvimrc +// :mkview +// :mksession + +#include <assert.h> +#include <string.h> +#include <stdbool.h> +#include <stdlib.h> +#include <inttypes.h> + +#include "nvim/vim.h" +#include "nvim/globals.h" +#include "nvim/ascii.h" +#include "nvim/buffer.h" +#include "nvim/cursor.h" +#include "nvim/edit.h" +#include "nvim/eval.h" +#include "nvim/ex_cmds2.h" +#include "nvim/ex_docmd.h" +#include "nvim/ex_getln.h" +#include "nvim/ex_session.h" +#include "nvim/file_search.h" +#include "nvim/fileio.h" +#include "nvim/fold.h" +#include "nvim/getchar.h" +#include "nvim/keymap.h" +#include "nvim/misc1.h" +#include "nvim/move.h" +#include "nvim/option.h" +#include "nvim/os/input.h" +#include "nvim/os/os.h" +#include "nvim/os/time.h" +#include "nvim/path.h" +#include "nvim/window.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "ex_session.c.generated.h" +#endif + +/// Whether ":lcd" or ":tcd" was produced for a session. +static int did_lcd; + +#define PUTLINE_FAIL(s) \ + do { if (FAIL == put_line(fd, (s))) { return FAIL; } } while (0) + +static int put_view_curpos(FILE *fd, const win_T *wp, char *spaces) +{ + int r; + + if (wp->w_curswant == MAXCOL) { + r = fprintf(fd, "%snormal! $\n", spaces); + } else { + r = fprintf(fd, "%snormal! 0%d|\n", spaces, wp->w_virtcol + 1); + } + return r >= 0; +} + +static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin) +{ + int n = 0; + win_T *wp; + + if (restore_size && (ssop_flags & SSOP_WINSIZE)) { + for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { + if (!ses_do_win(wp)) { + continue; + } + n++; + + // restore height when not full height + if (wp->w_height + wp->w_status_height < topframe->fr_height + && (fprintf(fd, + "exe '%dresize ' . ((&lines * %" PRId64 + " + %" PRId64 ") / %" PRId64 ")\n", + n, (int64_t)wp->w_height, + (int64_t)Rows / 2, (int64_t)Rows) < 0)) { + return FAIL; + } + + // restore width when not full width + if (wp->w_width < Columns + && (fprintf(fd, + "exe 'vert %dresize ' . ((&columns * %" PRId64 + " + %" PRId64 ") / %" PRId64 ")\n", + n, (int64_t)wp->w_width, (int64_t)Columns / 2, + (int64_t)Columns) < 0)) { + return FAIL; + } + } + } else { + // Just equalize window sizes. + PUTLINE_FAIL("wincmd ="); + } + return OK; +} + +// Write commands to "fd" to recursively create windows for frame "fr", +// horizontally and vertically split. +// After the commands the last window in the frame is the current window. +// Returns FAIL when writing the commands to "fd" fails. +static int ses_win_rec(FILE *fd, frame_T *fr) +{ + frame_T *frc; + int count = 0; + + if (fr->fr_layout != FR_LEAF) { + // Find first frame that's not skipped and then create a window for + // each following one (first frame is already there). + frc = ses_skipframe(fr->fr_child); + if (frc != NULL) { + while ((frc = ses_skipframe(frc->fr_next)) != NULL) { + // Make window as big as possible so that we have lots of room + // to split. + if (fprintf(fd, "%s%s", + "wincmd _ | wincmd |\n", + (fr->fr_layout == FR_COL ? "split\n" : "vsplit\n")) < 0) { + return FAIL; + } + count++; + } + } + + // Go back to the first window. + if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL + ? "%dwincmd k\n" : "%dwincmd h\n", count) < 0)) { + return FAIL; + } + + // Recursively create frames/windows in each window of this column or row. + frc = ses_skipframe(fr->fr_child); + while (frc != NULL) { + ses_win_rec(fd, frc); + frc = ses_skipframe(frc->fr_next); + // Go to next window. + if (frc != NULL && put_line(fd, "wincmd w") == FAIL) { + return FAIL; + } + } + } + return OK; +} + +// Skip frames that don't contain windows we want to save in the Session. +// Returns NULL when there none. +static frame_T *ses_skipframe(frame_T *fr) +{ + frame_T *frc; + + FOR_ALL_FRAMES(frc, fr) { + if (ses_do_frame(frc)) { + break; + } + } + return frc; +} + +// Return true if frame "fr" has a window somewhere that we want to save in +// the Session. +static bool ses_do_frame(const frame_T *fr) + FUNC_ATTR_NONNULL_ARG(1) +{ + const frame_T *frc; + + if (fr->fr_layout == FR_LEAF) { + return ses_do_win(fr->fr_win); + } + FOR_ALL_FRAMES(frc, fr->fr_child) { + if (ses_do_frame(frc)) { + return true; + } + } + return false; +} + +/// Return non-zero if window "wp" is to be stored in the Session. +static int ses_do_win(win_T *wp) +{ + if (wp->w_buffer->b_fname == NULL + // When 'buftype' is "nofile" can't restore the window contents. + || (!wp->w_buffer->terminal && bt_nofile(wp->w_buffer))) { + return ssop_flags & SSOP_BLANK; + } + if (bt_help(wp->w_buffer)) { + return ssop_flags & SSOP_HELP; + } + return true; +} + +/// Writes an :argument list to the session file. +/// +/// @param fd +/// @param cmd +/// @param gap +/// @param fullname true: use full path name +/// @param flagp +/// +/// @returns FAIL if writing fails. +static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, + unsigned *flagp) +{ + char_u *buf = NULL; + char_u *s; + + if (fprintf(fd, "%s\n%s\n", cmd, "%argdel") < 0) { + return FAIL; + } + for (int i = 0; i < gap->ga_len; i++) { + // NULL file names are skipped (only happens when out of memory). + s = alist_name(&((aentry_T *)gap->ga_data)[i]); + if (s != NULL) { + if (fullname) { + buf = xmalloc(MAXPATHL); + (void)vim_FullName((char *)s, (char *)buf, MAXPATHL, false); + s = buf; + } + char *fname_esc = ses_escape_fname((char *)s, flagp); + if (fprintf(fd, "$argadd %s\n", fname_esc) < 0) { + xfree(fname_esc); + xfree(buf); + return FAIL; + } + xfree(fname_esc); + xfree(buf); + } + } + return OK; +} + +/// Gets the buffer name for `buf`. +static char *ses_get_fname(buf_T *buf, unsigned *flagp) +{ + // Use the short file name if the current directory is known at the time + // the session file will be sourced. + // Don't do this for ":mkview", we don't know the current directory. + // Don't do this after ":lcd", we don't keep track of what the current + // directory is. + if (buf->b_sfname != NULL + && flagp == &ssop_flags + && (ssop_flags & (SSOP_CURDIR | SSOP_SESDIR)) + && !p_acd + && !did_lcd) { + return (char *)buf->b_sfname; + } + return (char *)buf->b_ffname; +} + +/// Write a buffer name to the session file. +/// Also ends the line, if "add_eol" is true. +/// Returns FAIL if writing fails. +static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol) +{ + char *name = ses_get_fname(buf, flagp); + if (ses_put_fname(fd, (char_u *)name, flagp) == FAIL + || (add_eol && fprintf(fd, "\n") < 0)) { + return FAIL; + } + return OK; +} + +// Escapes a filename for session writing. +// Takes care of "slash" flag in 'sessionoptions' and escapes special +// characters. +// +// Returns allocated string or NULL. +static char *ses_escape_fname(char *name, unsigned *flagp) +{ + char *p; + char *sname = (char *)home_replace_save(NULL, (char_u *)name); + + // Always SSOP_SLASH: change all backslashes to forward slashes. + for (p = sname; *p != NUL; MB_PTR_ADV(p)) { + if (*p == '\\') { + *p = '/'; + } + } + + // Escape special characters. + p = vim_strsave_fnameescape(sname, false); + xfree(sname); + return p; +} + +// Write a file name to the session file. +// Takes care of the "slash" option in 'sessionoptions' and escapes special +// characters. +// Returns FAIL if writing fails. +static int ses_put_fname(FILE *fd, char_u *name, unsigned *flagp) +{ + char *p = ses_escape_fname((char *)name, flagp); + bool retval = fputs(p, fd) < 0 ? FAIL : OK; + xfree(p); + return retval; +} + +// Write commands to "fd" to restore the view of a window. +// Caller must make sure 'scrolloff' is zero. +static int put_view( + FILE *fd, + win_T *wp, + int add_edit, // add ":edit" command to view + unsigned *flagp, // vop_flags or ssop_flags + int current_arg_idx // current argument index of the window, use +) // -1 if unknown +{ + win_T *save_curwin; + int f; + int do_cursor; + int did_next = false; + + // Always restore cursor position for ":mksession". For ":mkview" only + // when 'viewoptions' contains "cursor". + do_cursor = (flagp == &ssop_flags || *flagp & SSOP_CURSOR); + + // + // Local argument list. + // + if (wp->w_alist == &global_alist) { + PUTLINE_FAIL("argglobal"); + } else { + if (ses_arglist(fd, "arglocal", &wp->w_alist->al_ga, + flagp == &vop_flags + || !(*flagp & SSOP_CURDIR) + || wp->w_localdir != NULL, flagp) == FAIL) { + return FAIL; + } + } + + // Only when part of a session: restore the argument index. Some + // arguments may have been deleted, check if the index is valid. + if (wp->w_arg_idx != current_arg_idx && wp->w_arg_idx < WARGCOUNT(wp) + && flagp == &ssop_flags) { + if (fprintf(fd, "%" PRId64 "argu\n", (int64_t)wp->w_arg_idx + 1) < 0) { + return FAIL; + } + did_next = true; + } + + // Edit the file. Skip this when ":next" already did it. + if (add_edit && (!did_next || wp->w_arg_idx_invalid)) { + char *fname_esc = + ses_escape_fname(ses_get_fname(wp->w_buffer, flagp), flagp); + // + // Load the file. + // + if (wp->w_buffer->b_ffname != NULL + && (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal) + ) { + // Editing a file in this buffer: use ":edit file". + // This may have side effects! (e.g., compressed or network file). + // + // Note, if a buffer for that file already exists, use :badd to + // edit that buffer, to not lose folding information (:edit resets + // folds in other buffers) + if (fprintf(fd, + "if bufexists(\"%s\") | buffer %s | else | edit %s | endif\n" + // Fixup :terminal buffer name. #7836 + "if &buftype ==# 'terminal'\n" + " silent file %s\n" + "endif\n", + fname_esc, + fname_esc, + fname_esc, + fname_esc) < 0) { + xfree(fname_esc); + return FAIL; + } + } else { + // No file in this buffer, just make it empty. + PUTLINE_FAIL("enew"); + if (wp->w_buffer->b_ffname != NULL) { + // The buffer does have a name, but it's not a file name. + if (fprintf(fd, "file %s\n", fname_esc) < 0) { + xfree(fname_esc); + return FAIL; + } + } + do_cursor = false; + } + xfree(fname_esc); + } + + // + // Local mappings and abbreviations. + // + if ((*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) + && makemap(fd, wp->w_buffer) == FAIL) { + return FAIL; + } + + // + // Local options. Need to go to the window temporarily. + // Store only local values when using ":mkview" and when ":mksession" is + // used and 'sessionoptions' doesn't include "nvim/options". + // Some folding options are always stored when "folds" is included, + // otherwise the folds would not be restored correctly. + // + save_curwin = curwin; + curwin = wp; + curbuf = curwin->w_buffer; + if (*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) { + f = makeset(fd, OPT_LOCAL, + flagp == &vop_flags || !(*flagp & SSOP_OPTIONS)); + } else if (*flagp & SSOP_FOLDS) { + f = makefoldset(fd); + } else { + f = OK; + } + curwin = save_curwin; + curbuf = curwin->w_buffer; + if (f == FAIL) { + return FAIL; + } + + // + // Save Folds when 'buftype' is empty and for help files. + // + if ((*flagp & SSOP_FOLDS) + && wp->w_buffer->b_ffname != NULL + && (bt_normal(wp->w_buffer) || bt_help(wp->w_buffer)) + ) { + if (put_folds(fd, wp) == FAIL) { + return FAIL; + } + } + + // + // Set the cursor after creating folds, since that moves the cursor. + // + if (do_cursor) { + // Restore the cursor line in the file and relatively in the + // window. Don't use "G", it changes the jumplist. + if (fprintf(fd, + "let s:l = %" PRId64 " - ((%" PRId64 + " * winheight(0) + %" PRId64 ") / %" PRId64 ")\n" + "if s:l < 1 | let s:l = 1 | endif\n" + "exe s:l\n" + "normal! zt\n" + "%" PRId64 "\n", + (int64_t)wp->w_cursor.lnum, + (int64_t)(wp->w_cursor.lnum - wp->w_topline), + (int64_t)(wp->w_height_inner / 2), + (int64_t)wp->w_height_inner, + (int64_t)wp->w_cursor.lnum) < 0) { + return FAIL; + } + // Restore the cursor column and left offset when not wrapping. + if (wp->w_cursor.col == 0) { + PUTLINE_FAIL("normal! 0"); + } else { + if (!wp->w_p_wrap && wp->w_leftcol > 0 && wp->w_width > 0) { + if (fprintf(fd, + "let s:c = %" PRId64 " - ((%" PRId64 + " * winwidth(0) + %" PRId64 ") / %" PRId64 ")\n" + "if s:c > 0\n" + " exe 'normal! ' . s:c . '|zs' . %" PRId64 " . '|'\n" + "else\n", + (int64_t)wp->w_virtcol + 1, + (int64_t)(wp->w_virtcol - wp->w_leftcol), + (int64_t)(wp->w_width / 2), + (int64_t)wp->w_width, + (int64_t)wp->w_virtcol + 1) < 0 + || put_view_curpos(fd, wp, " ") == FAIL + || put_line(fd, "endif") == FAIL) { + return FAIL; + } + } else if (put_view_curpos(fd, wp, "") == FAIL) { + return FAIL; + } + } + } + + // + // Local directory, if the current flag is not view options or the "curdir" + // option is included. + // + if (wp->w_localdir != NULL + && (flagp != &vop_flags || (*flagp & SSOP_CURDIR))) { + if (fputs("lcd ", fd) < 0 + || ses_put_fname(fd, wp->w_localdir, flagp) == FAIL + || fprintf(fd, "\n") < 0) { + return FAIL; + } + did_lcd = true; + } + + return OK; +} + +/// Writes commands for restoring the current buffers, for :mksession. +/// +/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled. +/// +/// @param dirnow Current directory name +/// @param fd File descriptor to write to +/// +/// @return FAIL on error, OK otherwise. +static int makeopens(FILE *fd, char_u *dirnow) +{ + int only_save_windows = true; + int nr; + int restore_size = true; + win_T *wp; + char_u *sname; + win_T *edited_win = NULL; + int tabnr; + win_T *tab_firstwin; + frame_T *tab_topframe; + int cur_arg_idx = 0; + int next_arg_idx = 0; + + if (ssop_flags & SSOP_BUFFERS) { + only_save_windows = false; // Save ALL buffers + } + + // Begin by setting v:this_session, and then other sessionable variables. + PUTLINE_FAIL("let v:this_session=expand(\"<sfile>:p\")"); + if (ssop_flags & SSOP_GLOBALS) { + if (store_session_globals(fd) == FAIL) { + return FAIL; + } + } + + // Close all windows but one. + PUTLINE_FAIL("silent only"); + + // + // Now a :cd command to the session directory or the current directory + // + if (ssop_flags & SSOP_SESDIR) { + PUTLINE_FAIL("exe \"cd \" . escape(expand(\"<sfile>:p:h\"), ' ')"); + } else if (ssop_flags & SSOP_CURDIR) { + sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow); + char *fname_esc = ses_escape_fname((char *)sname, &ssop_flags); + if (fprintf(fd, "cd %s\n", fname_esc) < 0) { + xfree(fname_esc); + xfree(sname); + return FAIL; + } + xfree(fname_esc); + xfree(sname); + } + + if (fprintf(fd, + "%s", + // If there is an empty, unnamed buffer we will wipe it out later. + // Remember the buffer number. + "if expand('%') == '' && !&modified && line('$') <= 1" + " && getline(1) == ''\n" + " let s:wipebuf = bufnr('%')\n" + "endif\n" + // Now save the current files, current buffer first. + "set shortmess=aoO\n") < 0) { + return FAIL; + } + + // Now put the other buffers into the buffer list. + FOR_ALL_BUFFERS(buf) { + if (!(only_save_windows && buf->b_nwindows == 0) + && !(buf->b_help && !(ssop_flags & SSOP_HELP)) + && buf->b_fname != NULL + && buf->b_p_bl) { + if (fprintf(fd, "badd +%" PRId64 " ", + buf->b_wininfo == NULL + ? (int64_t)1L + : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0 + || ses_fname(fd, buf, &ssop_flags, true) == FAIL) { + return FAIL; + } + } + } + + // the global argument list + if (ses_arglist(fd, "argglobal", &global_alist.al_ga, + !(ssop_flags & SSOP_CURDIR), &ssop_flags) == FAIL) { + return FAIL; + } + + if (ssop_flags & SSOP_RESIZE) { + // Note: after the restore we still check it worked! + if (fprintf(fd, "set lines=%" PRId64 " columns=%" PRId64 "\n", + (int64_t)Rows, (int64_t)Columns) < 0) { + return FAIL; + } + } + + int restore_stal = false; + // When there are two or more tabpages and 'showtabline' is 1 the tabline + // will be displayed when creating the next tab. That resizes the windows + // in the first tab, which may cause problems. Set 'showtabline' to 2 + // temporarily to avoid that. + if (p_stal == 1 && first_tabpage->tp_next != NULL) { + PUTLINE_FAIL("set stal=2"); + restore_stal = true; + } + + // + // For each tab: + // - Put windows for each tab, when "tabpages" is in 'sessionoptions'. + // - Don't use goto_tabpage(), it may change CWD and trigger autocommands. + // + tab_firstwin = firstwin; // First window in tab page "tabnr". + tab_topframe = topframe; + for (tabnr = 1;; tabnr++) { + tabpage_T *tp = find_tabpage(tabnr); + if (tp == NULL) { + break; // done all tab pages + } + + int need_tabnew = false; + int cnr = 1; + + if ((ssop_flags & SSOP_TABPAGES)) { + if (tp == curtab) { + tab_firstwin = firstwin; + tab_topframe = topframe; + } else { + tab_firstwin = tp->tp_firstwin; + tab_topframe = tp->tp_topframe; + } + if (tabnr > 1) { + need_tabnew = true; + } + } + + // + // Before creating the window layout, try loading one file. If this + // is aborted we don't end up with a number of useless windows. + // This may have side effects! (e.g., compressed or network file). + // + for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { + if (ses_do_win(wp) + && wp->w_buffer->b_ffname != NULL + && !bt_help(wp->w_buffer) + && !bt_nofile(wp->w_buffer) + ) { + if (fputs(need_tabnew ? "tabedit " : "edit ", fd) < 0 + || ses_fname(fd, wp->w_buffer, &ssop_flags, true) == FAIL) { + return FAIL; + } + need_tabnew = false; + if (!wp->w_arg_idx_invalid) { + edited_win = wp; + } + break; + } + } + + // If no file got edited create an empty tab page. + if (need_tabnew && put_line(fd, "tabnew") == FAIL) { + return FAIL; + } + + // + // Save current window layout. + // + PUTLINE_FAIL("set splitbelow splitright"); + if (ses_win_rec(fd, tab_topframe) == FAIL) { + return FAIL; + } + if (!p_sb && put_line(fd, "set nosplitbelow") == FAIL) { + return FAIL; + } + if (!p_spr && put_line(fd, "set nosplitright") == FAIL) { + return FAIL; + } + + // + // Check if window sizes can be restored (no windows omitted). + // Remember the window number of the current window after restoring. + // + nr = 0; + for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { + if (ses_do_win(wp)) { + nr++; + } else { + restore_size = false; + } + if (curwin == wp) { + cnr = nr; + } + } + + // Go to the first window. + PUTLINE_FAIL("wincmd t"); + + // If more than one window, see if sizes can be restored. + // First set 'winheight' and 'winwidth' to 1 to avoid the windows being + // resized when moving between windows. + // Do this before restoring the view, so that the topline and the + // cursor can be set. This is done again below. + // winminheight and winminwidth need to be set to avoid an error if the + // user has set winheight or winwidth. + if (fprintf(fd, + "set winminheight=0\n" + "set winheight=1\n" + "set winminwidth=0\n" + "set winwidth=1\n") < 0) { + return FAIL; + } + if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) { + return FAIL; + } + + // + // Restore the view of the window (options, file, cursor, etc.). + // + for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { + if (!ses_do_win(wp)) { + continue; + } + if (put_view(fd, wp, wp != edited_win, &ssop_flags, cur_arg_idx) + == FAIL) { + return FAIL; + } + if (nr > 1 && put_line(fd, "wincmd w") == FAIL) { + return FAIL; + } + next_arg_idx = wp->w_arg_idx; + } + + // The argument index in the first tab page is zero, need to set it in + // each window. For further tab pages it's the window where we do + // "tabedit". + cur_arg_idx = next_arg_idx; + + // + // Restore cursor to the current window if it's not the first one. + // + if (cnr > 1 && (fprintf(fd, "%dwincmd w\n", cnr) < 0)) { + return FAIL; + } + + // + // Restore window sizes again after jumping around in windows, because + // the current window has a minimum size while others may not. + // + if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) { + return FAIL; + } + + // Take care of tab-local working directories if applicable + if (tp->tp_localdir) { + if (fputs("if exists(':tcd') == 2 | tcd ", fd) < 0 + || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL + || fputs(" | endif\n", fd) < 0) { + return FAIL; + } + did_lcd = true; + } + + // Don't continue in another tab page when doing only the current one + // or when at the last tab page. + if (!(ssop_flags & SSOP_TABPAGES)) { + break; + } + } + + if (ssop_flags & SSOP_TABPAGES) { + if (fprintf(fd, "tabnext %d\n", tabpage_index(curtab)) < 0) { + return FAIL; + } + } + if (restore_stal && put_line(fd, "set stal=1") == FAIL) { + return FAIL; + } + + // + // Wipe out an empty unnamed buffer we started in. + // + if (fprintf(fd, "%s", + "if exists('s:wipebuf') " + "&& getbufvar(s:wipebuf, '&buftype') isnot# 'terminal'\n" + " silent exe 'bwipe ' . s:wipebuf\n" + "endif\n" + "unlet! s:wipebuf\n") < 0) { + return FAIL; + } + + // Re-apply options. + if (fprintf(fd, + "set winheight=%" PRId64 " winwidth=%" PRId64 + " winminheight=%" PRId64 " winminwidth=%" PRId64 + " shortmess=%s\n", + (int64_t)p_wh, + (int64_t)p_wiw, + (int64_t)p_wmh, + (int64_t)p_wmw, + p_shm) < 0) { + return FAIL; + } + + // + // Lastly, execute the x.vim file if it exists. + // + if (fprintf(fd, "%s", + "let s:sx = expand(\"<sfile>:p:r\").\"x.vim\"\n" + "if filereadable(s:sx)\n" + " exe \"source \" . fnameescape(s:sx)\n" + "endif\n") < 0) { + return FAIL; + } + + return OK; +} + +/// ":loadview [nr]" +void ex_loadview(exarg_T *eap) +{ + char *fname = get_view_file(*eap->arg); + if (fname != NULL) { + if (do_source((char_u *)fname, false, DOSO_NONE) == FAIL) { + EMSG2(_(e_notopen), fname); + } + xfree(fname); + } +} + +/// ":mkexrc", ":mkvimrc", ":mkview", ":mksession". +/// +/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled. +/// - SSOP_UNIX: line-endings are always LF +/// - SSOP_SLASH: filenames are always written with "/" slash +void ex_mkrc(exarg_T *eap) +{ + FILE *fd; + int failed = false; + int view_session = false; // :mkview, :mksession + int using_vdir = false; // using 'viewdir'? + char *viewFile = NULL; + unsigned *flagp; + + if (eap->cmdidx == CMD_mksession || eap->cmdidx == CMD_mkview) { + view_session = true; + } + + // Use the short file name until ":lcd" is used. We also don't use the + // short file name when 'acd' is set, that is checked later. + did_lcd = false; + + char *fname; + // ":mkview" or ":mkview 9": generate file name with 'viewdir' + if (eap->cmdidx == CMD_mkview + && (*eap->arg == NUL + || (ascii_isdigit(*eap->arg) && eap->arg[1] == NUL))) { + eap->forceit = true; + fname = get_view_file(*eap->arg); + if (fname == NULL) { + return; + } + viewFile = fname; + using_vdir = true; + } else if (*eap->arg != NUL) { + fname = (char *)eap->arg; + } else if (eap->cmdidx == CMD_mkvimrc) { + fname = VIMRC_FILE; + } else if (eap->cmdidx == CMD_mksession) { + fname = SESSION_FILE; + } else { + fname = EXRC_FILE; + } + + // When using 'viewdir' may have to create the directory. + if (using_vdir && !os_isdir(p_vdir)) { + vim_mkdir_emsg((const char *)p_vdir, 0755); + } + + fd = open_exfile((char_u *)fname, eap->forceit, WRITEBIN); + if (fd != NULL) { + if (eap->cmdidx == CMD_mkview) { + flagp = &vop_flags; + } else { + flagp = &ssop_flags; + } + + // Write the version command for :mkvimrc + if (eap->cmdidx == CMD_mkvimrc) { + (void)put_line(fd, "version 6.0"); + } + + if (eap->cmdidx == CMD_mksession) { + if (put_line(fd, "let SessionLoad = 1") == FAIL) { + failed = true; + } + } + + if (!view_session || (eap->cmdidx == CMD_mksession + && (*flagp & SSOP_OPTIONS))) { + failed |= (makemap(fd, NULL) == FAIL + || makeset(fd, OPT_GLOBAL, false) == FAIL); + } + + if (!failed && view_session) { + if (put_line(fd, + "let s:so_save = &so | let s:siso_save = &siso" + " | set so=0 siso=0") == FAIL) { + failed = true; + } + if (eap->cmdidx == CMD_mksession) { + char_u *dirnow; // current directory + + dirnow = xmalloc(MAXPATHL); + // + // Change to session file's dir. + // + if (os_dirname(dirnow, MAXPATHL) == FAIL + || os_chdir((char *)dirnow) != 0) { + *dirnow = NUL; + } + if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) { + if (vim_chdirfile((char_u *)fname) == OK) { + shorten_fnames(true); + } + } else if (*dirnow != NUL + && (ssop_flags & SSOP_CURDIR) && globaldir != NULL) { + if (os_chdir((char *)globaldir) == 0) { + shorten_fnames(true); + } + } + + failed |= (makeopens(fd, dirnow) == FAIL); + + // restore original dir + if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR) + || ((ssop_flags & SSOP_CURDIR) && globaldir != + NULL))) { + if (os_chdir((char *)dirnow) != 0) { + EMSG(_(e_prev_dir)); + } + shorten_fnames(true); + // restore original dir + if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR) + || ((ssop_flags & SSOP_CURDIR) && globaldir != + NULL))) { + if (os_chdir((char *)dirnow) != 0) { + EMSG(_(e_prev_dir)); + } + shorten_fnames(true); + } + } + xfree(dirnow); + } else { + failed |= (put_view(fd, curwin, !using_vdir, flagp, -1) == FAIL); + } + if (fprintf(fd, + "%s", + "let &so = s:so_save | let &siso = s:siso_save\n" + "doautoall SessionLoadPost\n") + < 0) { + failed = true; + } + if (eap->cmdidx == CMD_mksession) { + if (fprintf(fd, "unlet SessionLoad\n") < 0) { + failed = true; + } + } + } + if (put_line(fd, "\" vim: set ft=vim :") == FAIL) { + failed = true; + } + + failed |= fclose(fd); + + if (failed) { + EMSG(_(e_write)); + } else if (eap->cmdidx == CMD_mksession) { + // successful session write - set v:this_session + char *const tbuf = xmalloc(MAXPATHL); + if (vim_FullName(fname, tbuf, MAXPATHL, false) == OK) { + set_vim_var_string(VV_THIS_SESSION, tbuf, -1); + } + xfree(tbuf); + } + } + + xfree(viewFile); +} + +/// Get the name of the view file for the current buffer. +static char *get_view_file(int c) +{ + if (curbuf->b_ffname == NULL) { + EMSG(_(e_noname)); + return NULL; + } + char *sname = (char *)home_replace_save(NULL, curbuf->b_ffname); + + // We want a file name without separators, because we're not going to make + // a directory. + // "normal" path separator -> "=+" + // "=" -> "==" + // ":" path separator -> "=-" + size_t len = 0; + for (char *p = sname; *p; p++) { + if (*p == '=' || vim_ispathsep(*p)) { + len++; + } + } + char *retval = xmalloc(strlen(sname) + len + STRLEN(p_vdir) + 9); + STRCPY(retval, p_vdir); + add_pathsep(retval); + char *s = retval + strlen(retval); + for (char *p = sname; *p; p++) { + if (*p == '=') { + *s++ = '='; + *s++ = '='; + } else if (vim_ispathsep(*p)) { + *s++ = '='; +#if defined(BACKSLASH_IN_FILENAME) + *s++ = (*p == ':') ? '-' : '+'; +#else + *s++ = '+'; +#endif + } else { + *s++ = *p; + } + } + *s++ = '='; + assert(c >= CHAR_MIN && c <= CHAR_MAX); + *s++ = (char)c; + xstrlcpy(s, ".vim", 5); + + xfree(sname); + return retval; +} + +// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. +int put_eol(FILE *fd) +{ + if (putc('\n', fd) < 0) { + return FAIL; + } + return OK; +} + +// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. +int put_line(FILE *fd, char *s) +{ + if (fprintf(fd, "%s\n", s) < 0) { + return FAIL; + } + return OK; +} diff --git a/src/nvim/ex_session.h b/src/nvim/ex_session.h new file mode 100644 index 0000000000..8d3ea5b91a --- /dev/null +++ b/src/nvim/ex_session.h @@ -0,0 +1,13 @@ +#ifndef NVIM_EX_SESSION_H +#define NVIM_EX_SESSION_H + +#include <stdio.h> + +#include "nvim/ex_cmds_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "ex_session.h.generated.h" +#endif + +#endif // NVIM_EX_SESSION_H + diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index d60723c755..1457a1172d 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -86,7 +86,7 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id, extmark_del(buf, ns_id, id); } else { // TODO(bfredl): we need to do more if "revising" a decoration mark. - MarkTreeIter itr[1]; + MarkTreeIter itr[1] = { 0 }; old_pos = marktree_lookup(buf->b_marktree, old_mark, itr); assert(itr->node); if (old_pos.row == row && old_pos.col == col) { @@ -119,7 +119,7 @@ revised: static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col) { - MarkTreeIter itr[1]; + MarkTreeIter itr[1] = { 0 }; mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr); if (pos.row == -1) { return false; @@ -147,7 +147,7 @@ bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id) return false; } - MarkTreeIter itr[1]; + MarkTreeIter itr[1] = { 0 }; mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr); assert(pos.row >= 0); marktree_del_itr(buf->b_marktree, itr, false); @@ -207,7 +207,7 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, delete_set = map_new(uint64_t, uint64_t)(); } - MarkTreeIter itr[1]; + MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, l_row, l_col, itr); while (true) { mtmark_t mark = marktree_itr_current(itr); @@ -276,7 +276,7 @@ ExtmarkArray extmark_get(buf_T *buf, uint64_t ns_id, int64_t amount, bool reverse) { ExtmarkArray array = KV_INITIAL_VALUE; - MarkTreeIter itr[1]; + MarkTreeIter itr[1] = { 0 }; // Find all the marks marktree_itr_get_ext(buf->b_marktree, (mtpos_t){ l_row, l_col }, itr, reverse, false, NULL); @@ -396,7 +396,7 @@ void u_extmark_copy(buf_T *buf, ExtmarkUndoObject undo; - MarkTreeIter itr[1]; + MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, l_row, l_col, itr); while (true) { mtmark_t mark = marktree_itr_current(itr); @@ -578,6 +578,15 @@ void extmark_splice(buf_T *buf, } } +void extmark_splice_cols(buf_T *buf, + int start_row, colnr_T start_col, + colnr_T old_col, colnr_T new_col, + ExtmarkOp undo) +{ + extmark_splice(buf, start_row, start_col, + 0, old_col, + 0, new_col, undo); +} void extmark_move_region(buf_T *buf, int start_row, colnr_T start_col, @@ -699,21 +708,30 @@ void bufhl_add_hl_pos_offset(buf_T *buf, // TODO(bfredl): if decoration had blocky mode, we could avoid this loop for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) { + int end_off = 0; if (pos_start.lnum < lnum && lnum < pos_end.lnum) { - hl_start = offset-1; - hl_end = MAXCOL; + // TODO(bfredl): This is quite ad-hoc, but the space between |num| and + // text being highlighted is the indication of \n being part of the + // substituted text. But it would be more consistent to highlight + // a space _after_ the previous line instead (like highlight EOL list + // char) + hl_start = MAX(offset-1, 0); + end_off = 1; + hl_end = 0; } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) { hl_start = pos_start.col + offset; - hl_end = MAXCOL; + end_off = 1; + hl_end = 0; } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) { - hl_start = offset-1; + hl_start = MAX(offset-1, 0); hl_end = pos_end.col + offset; } else if (pos_start.lnum == lnum && pos_end.lnum == lnum) { hl_start = pos_start.col + offset; hl_end = pos_end.col + offset; } (void)extmark_add_decoration(buf, (uint64_t)src_id, hl_id, - (int)lnum-1, hl_start, (int)lnum-1, hl_end, + (int)lnum-1, hl_start, + (int)lnum-1+end_off, hl_end, VIRTTEXT_EMPTY); } } @@ -729,7 +747,7 @@ void clear_virttext(VirtText *text) VirtText *extmark_find_virttext(buf_T *buf, int row, uint64_t ns_id) { - MarkTreeIter itr[1]; + MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, row, 0, itr); while (true) { mtmark_t mark = marktree_itr_current(itr); @@ -747,17 +765,17 @@ VirtText *extmark_find_virttext(buf_T *buf, int row, uint64_t ns_id) return NULL; } - -bool extmark_decorations_reset(buf_T *buf, DecorationState *state) +bool decorations_redraw_reset(buf_T *buf, DecorationRedrawState *state) { state->row = -1; - return buf->b_extmark_index; + kv_size(state->active) = 0; + return buf->b_extmark_index || buf->b_luahl; } -bool extmark_decorations_start(buf_T *buf, int top_row, DecorationState *state) +bool decorations_redraw_start(buf_T *buf, int top_row, + DecorationRedrawState *state) { - kv_size(state->active) = 0; state->top_row = top_row; marktree_itr_get(buf->b_marktree, top_row, 0, state->itr); if (!state->itr->node) { @@ -780,7 +798,7 @@ bool extmark_decorations_start(buf_T *buf, int top_row, DecorationState *state) ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, start_id, false); if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row - && !kv_size(item->virt_text)) + && item && !kv_size(item->virt_text)) || ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) { goto next_mark; } @@ -808,17 +826,17 @@ next_mark: return true; // TODO(bfredl): check if available in the region } -bool extmark_decorations_line(buf_T *buf, int row, DecorationState *state) +bool decorations_redraw_line(buf_T *buf, int row, DecorationRedrawState *state) { if (state->row == -1) { - extmark_decorations_start(buf, row, state); + decorations_redraw_start(buf, row, state); } state->row = row; state->col_until = -1; return true; // TODO(bfredl): be more precise } -int extmark_decorations_col(buf_T *buf, int col, DecorationState *state) +int decorations_redraw_col(buf_T *buf, int col, DecorationRedrawState *state) { if (col <= state->col_until) { return state->current; @@ -845,7 +863,7 @@ int extmark_decorations_col(buf_T *buf, int col, DecorationState *state) if (endpos.row < mark.row || (endpos.row == mark.row && endpos.col <= mark.col)) { - if (!kv_size(item->virt_text)) { + if (item && !kv_size(item->virt_text)) { goto next_mark; } } @@ -897,9 +915,9 @@ next_mark: return attr; } -VirtText *extmark_decorations_virt_text(buf_T *buf, DecorationState *state) +VirtText *decorations_redraw_virt_text(buf_T *buf, DecorationRedrawState *state) { - extmark_decorations_col(buf, MAXCOL, state); + decorations_redraw_col(buf, MAXCOL, state); for (size_t i = 0; i < kv_size(state->active); i++) { HlRange item = kv_A(state->active, i); if (item.start_row == state->row && item.virt_text) { diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h index 829cbe0236..b5eb0db3b6 100644 --- a/src/nvim/extmark.h +++ b/src/nvim/extmark.h @@ -83,7 +83,7 @@ typedef struct { int col_until; int current; VirtText *virt_text; -} DecorationState; +} DecorationRedrawState; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 7a46957151..2335aba6dd 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -20,7 +20,7 @@ #include "nvim/cursor.h" #include "nvim/diff.h" #include "nvim/edit.h" -#include "nvim/eval.h" +#include "nvim/eval/userfunc.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" @@ -62,6 +62,11 @@ #define BUFSIZE 8192 /* size of normal write buffer */ #define SMBUFSIZE 256 /* size of emergency write buffer */ +// For compatibility with libuv < 1.20.0 (tested on 1.18.0) +#ifndef UV_FS_COPYFILE_FICLONE +#define UV_FS_COPYFILE_FICLONE 0 +#endif + // // The autocommands are stored in a list for each event. // Autocommands for the same pattern, that are consecutive, are joined @@ -407,11 +412,27 @@ readfile( if (newfile) { if (apply_autocmds_exarg(EVENT_BUFREADCMD, NULL, sfname, - FALSE, curbuf, eap)) - return aborting() ? FAIL : OK; + false, curbuf, eap)) { + int status = OK; + + if (aborting()) { + status = FAIL; + } + + // The BufReadCmd code usually uses ":read" to get the text and + // perhaps ":file" to change the buffer name. But we should + // consider this to work like ":edit", thus reset the + // BF_NOTEDITED flag. Then ":write" will work to overwrite the + // same file. + if (status == OK) { + curbuf->b_flags &= ~BF_NOTEDITED; + } + return status; + } } else if (apply_autocmds_exarg(EVENT_FILEREADCMD, sfname, sfname, - FALSE, NULL, eap)) + false, NULL, eap)) { return aborting() ? FAIL : OK; + } curbuf->b_op_start = pos; } @@ -2032,14 +2053,16 @@ readfile_linenr( * Fill "*eap" to force the 'fileencoding', 'fileformat' and 'binary to be * equal to the buffer "buf". Used for calling readfile(). */ -void prep_exarg(exarg_T *eap, buf_T *buf) +void prep_exarg(exarg_T *eap, const buf_T *buf) + FUNC_ATTR_NONNULL_ALL { - eap->cmd = xmalloc(STRLEN(buf->b_p_ff) + STRLEN(buf->b_p_fenc) + 15); + const size_t cmd_len = 15 + STRLEN(buf->b_p_fenc); + eap->cmd = xmalloc(cmd_len); - sprintf((char *)eap->cmd, "e ++ff=%s ++enc=%s", buf->b_p_ff, buf->b_p_fenc); - eap->force_enc = 14 + (int)STRLEN(buf->b_p_ff); + snprintf((char *)eap->cmd, cmd_len, "e ++enc=%s", buf->b_p_fenc); + eap->force_enc = 8; eap->bad_char = buf->b_bad_char; - eap->force_ff = 7; + eap->force_ff = *buf->b_p_ff; eap->force_bin = buf->b_p_bin ? FORCE_BIN : FORCE_NOBIN; eap->read_edit = FALSE; @@ -6587,7 +6610,7 @@ static int autocmd_nested = FALSE; /// Execute autocommands for "event" and file name "fname". /// -/// @param event event that occured +/// @param event event that occurred /// @param fname filename, NULL or empty means use actual file name /// @param fname_io filename to use for <afile> on cmdline /// @param force When true, ignore autocmd_busy @@ -6604,7 +6627,7 @@ bool apply_autocmds(event_T event, char_u *fname, char_u *fname_io, bool force, /// Like apply_autocmds(), but with extra "eap" argument. This takes care of /// setting v:filearg. /// -/// @param event event that occured +/// @param event event that occurred /// @param fname NULL or empty means use actual file name /// @param fname_io fname to use for <afile> on cmdline /// @param force When true, ignore autocmd_busy @@ -6624,7 +6647,7 @@ static bool apply_autocmds_exarg(event_T event, char_u *fname, char_u *fname_io, /// conditional, no autocommands are executed. If otherwise the autocommands /// cause the script to be aborted, retval is set to FAIL. /// -/// @param event event that occured +/// @param event event that occurred /// @param fname NULL or empty means use actual file name /// @param fname_io fname to use for <afile> on cmdline /// @param force When true, ignore autocmd_busy @@ -6684,7 +6707,7 @@ bool has_event(event_T event) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT /// Execute autocommands for "event" and file name "fname". /// -/// @param event event that occured +/// @param event event that occurred /// @param fname filename, NULL or empty means use actual file name /// @param fname_io filename to use for <afile> on cmdline, /// NULL means use `fname`. @@ -7197,8 +7220,8 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat) /// To account for buffer-local autocommands, function needs to know /// in which buffer the file will be opened. /// -/// @param event event that occured. -/// @param sfname filename the event occured in. +/// @param event event that occurred. +/// @param sfname filename the event occurred in. /// @param buf buffer the file is open in bool has_autocmd(event_T event, char_u *sfname, buf_T *buf) FUNC_ATTR_WARN_UNUSED_RESULT diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 0b14a6affb..61a85171e8 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -19,6 +19,7 @@ #include "nvim/diff.h" #include "nvim/eval.h" #include "nvim/ex_docmd.h" +#include "nvim/ex_session.h" #include "nvim/func_attr.h" #include "nvim/indent.h" #include "nvim/buffer_updates.h" @@ -538,12 +539,10 @@ int foldManualAllowed(int create) return FALSE; } -/* foldCreate() {{{2 */ -/* - * Create a fold from line "start" to line "end" (inclusive) in the current - * window. - */ -void foldCreate(linenr_T start, linenr_T end) +// foldCreate() {{{2 +/// Create a fold from line "start" to line "end" (inclusive) in the current +/// window. +void foldCreate(win_T *wp, linenr_T start, linenr_T end) { fold_T *fp; garray_T *gap; @@ -564,16 +563,16 @@ void foldCreate(linenr_T start, linenr_T end) end_rel = end; } - /* When 'foldmethod' is "marker" add markers, which creates the folds. */ - if (foldmethodIsMarker(curwin)) { - foldCreateMarkers(start, end); + // When 'foldmethod' is "marker" add markers, which creates the folds. + if (foldmethodIsMarker(wp)) { + foldCreateMarkers(wp, start, end); return; } - checkupdate(curwin); + checkupdate(wp); - /* Find the place to insert the new fold. */ - gap = &curwin->w_folds; + // Find the place to insert the new fold + gap = &wp->w_folds; for (;; ) { if (!foldFind(gap, start_rel, &fp)) break; @@ -583,12 +582,14 @@ void foldCreate(linenr_T start, linenr_T end) start_rel -= fp->fd_top; end_rel -= fp->fd_top; if (use_level || fp->fd_flags == FD_LEVEL) { - use_level = TRUE; - if (level >= curwin->w_p_fdl) - closed = TRUE; - } else if (fp->fd_flags == FD_CLOSED) - closed = TRUE; - ++level; + use_level = true; + if (level >= wp->w_p_fdl) { + closed = true; + } + } else if (fp->fd_flags == FD_CLOSED) { + closed = true; + } + level++; } else { /* This fold and new fold overlap: Insert here and move some folds * inside the new fold. */ @@ -641,26 +642,28 @@ void foldCreate(linenr_T start, linenr_T end) /* We want the new fold to be closed. If it would remain open because * of using 'foldlevel', need to adjust fd_flags of containing folds. */ - if (use_level && !closed && level < curwin->w_p_fdl) + if (use_level && !closed && level < wp->w_p_fdl) { closeFold(start, 1L); - if (!use_level) - curwin->w_fold_manual = true; + } + if (!use_level) { + wp->w_fold_manual = true; + } fp->fd_flags = FD_CLOSED; fp->fd_small = kNone; - /* redraw */ - changed_window_setting(); + // redraw + changed_window_setting_win(wp); } } -/* deleteFold() {{{2 */ -/* - * Delete a fold at line "start" in the current window. - * When "end" is not 0, delete all folds from "start" to "end". - * When "recursive" is TRUE delete recursively. - */ +// deleteFold() {{{2 +/// @param start delete all folds from start to end when not 0 +/// @param end delete all folds from start to end when not 0 +/// @param recursive delete recursively if true +/// @param had_visual true when Visual selection used void deleteFold( + win_T *const wp, const linenr_T start, const linenr_T end, const int recursive, @@ -677,11 +680,11 @@ void deleteFold( linenr_T first_lnum = MAXLNUM; linenr_T last_lnum = 0; - checkupdate(curwin); + checkupdate(wp); while (lnum <= end) { // Find the deepest fold for "start". - garray_T *gap = &curwin->w_folds; + garray_T *gap = &wp->w_folds; garray_T *found_ga = NULL; linenr_T lnum_off = 0; bool use_level = false; @@ -693,10 +696,11 @@ void deleteFold( found_fp = fp; found_off = lnum_off; - /* if "lnum" is folded, don't check nesting */ - if (check_closed(curwin, fp, &use_level, level, - &maybe_small, lnum_off)) + // if "lnum" is folded, don't check nesting + if (check_closed(wp, fp, &use_level, level, + &maybe_small, lnum_off)) { break; + } /* check nested folds */ gap = &fp->fd_nested; @@ -708,34 +712,41 @@ void deleteFold( } else { lnum = found_fp->fd_top + found_fp->fd_len + found_off; - if (foldmethodIsManual(curwin)) - deleteFoldEntry(found_ga, - (int)(found_fp - (fold_T *)found_ga->ga_data), recursive); - else { - if (first_lnum > found_fp->fd_top + found_off) + if (foldmethodIsManual(wp)) { + deleteFoldEntry(wp, found_ga, + (int)(found_fp - (fold_T *)found_ga->ga_data), + recursive); + } else { + if (first_lnum > found_fp->fd_top + found_off) { first_lnum = found_fp->fd_top + found_off; - if (last_lnum < lnum) + } + if (last_lnum < lnum) { last_lnum = lnum; - if (!did_one) - parseMarker(curwin); - deleteFoldMarkers(found_fp, recursive, found_off); + } + if (!did_one) { + parseMarker(wp); + } + deleteFoldMarkers(wp, found_fp, recursive, found_off); } did_one = true; - /* redraw window */ - changed_window_setting(); + // redraw window + changed_window_setting_win(wp); } } if (!did_one) { EMSG(_(e_nofold)); - /* Force a redraw to remove the Visual highlighting. */ - if (had_visual) - redraw_curbuf_later(INVERTED); - } else - /* Deleting markers may make cursor column invalid. */ - check_cursor_col(); + // Force a redraw to remove the Visual highlighting. + if (had_visual) { + redraw_buf_later(wp->w_buffer, INVERTED); + } + } else { + // Deleting markers may make cursor column invalid + check_cursor_col_win(wp); + } if (last_lnum > 0) { + // TODO(teto): pass the buffer changed_lines(first_lnum, (colnr_T)0, last_lnum, 0L, false); // send one nvim_buf_lines_event at the end @@ -744,7 +755,7 @@ void deleteFold( // the modification of the *first* line of the fold, but we send through a // notification that includes every line that was part of the fold int64_t num_changed = last_lnum - first_lnum; - buf_updates_send_changes(curbuf, first_lnum, num_changed, + buf_updates_send_changes(wp->w_buffer, first_lnum, num_changed, num_changed, true); } } @@ -1047,7 +1058,7 @@ void cloneFoldGrowArray(garray_T *from, garray_T *to) * the first fold below it (careful: it can be beyond the end of the array!). * Returns FALSE when there is no fold that contains "lnum". */ -static int foldFind(garray_T *gap, linenr_T lnum, fold_T **fpp) +static int foldFind(const garray_T *gap, linenr_T lnum, fold_T **fpp) { linenr_T low, high; fold_T *fp; @@ -1296,7 +1307,7 @@ static void foldOpenNested(fold_T *fpr) // Delete fold "idx" from growarray "gap". // When "recursive" is true also delete all the folds contained in it. // When "recursive" is false contained folds are moved one level up. -static void deleteFoldEntry(garray_T *const gap, const int idx, +static void deleteFoldEntry(win_T *const wp, garray_T *const gap, const int idx, const bool recursive) { fold_T *fp = (fold_T *)gap->ga_data + idx; @@ -1362,13 +1373,17 @@ void foldMarkAdjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, long line2 = line1 - amount_after - 1; /* If appending a line in Insert mode, it should be included in the fold * just above the line. */ - if ((State & INSERT) && amount == (linenr_T)1 && line2 == MAXLNUM) - --line1; - foldMarkAdjustRecurse(&wp->w_folds, line1, line2, amount, amount_after); + if ((State & INSERT) && amount == (linenr_T)1 && line2 == MAXLNUM) { + line1--; + } + foldMarkAdjustRecurse(wp, &wp->w_folds, line1, line2, amount, amount_after); } -/* foldMarkAdjustRecurse() {{{2 */ -static void foldMarkAdjustRecurse(garray_T *gap, linenr_T line1, linenr_T line2, long amount, long amount_after) +// foldMarkAdjustRecurse() {{{2 +static void foldMarkAdjustRecurse( + win_T *wp, garray_T *gap, + linenr_T line1, linenr_T line2, long amount, long amount_after +) { fold_T *fp; linenr_T last; @@ -1416,7 +1431,7 @@ static void foldMarkAdjustRecurse(garray_T *gap, linenr_T line1, linenr_T line2, // 4. fold completely contained in range if (amount == MAXLNUM) { // Deleting lines: delete the fold completely - deleteFoldEntry(gap, i, true); + deleteFoldEntry(wp, gap, i, true); i--; // adjust index for deletion fp--; } else { @@ -1424,9 +1439,9 @@ static void foldMarkAdjustRecurse(garray_T *gap, linenr_T line1, linenr_T line2, } } else { if (fp->fd_top < top) { - /* 2 or 3: need to correct nested folds too */ - foldMarkAdjustRecurse(&fp->fd_nested, line1 - fp->fd_top, - line2 - fp->fd_top, amount, amount_after); + // 2 or 3: need to correct nested folds too + foldMarkAdjustRecurse(wp, &fp->fd_nested, line1 - fp->fd_top, + line2 - fp->fd_top, amount, amount_after); if (last <= line2) { /* 2. fold contains line1, line2 is below fold */ if (amount == MAXLNUM) @@ -1441,13 +1456,13 @@ static void foldMarkAdjustRecurse(garray_T *gap, linenr_T line1, linenr_T line2, /* 5. fold is below line1 and contains line2; need to * correct nested folds too */ if (amount == MAXLNUM) { - foldMarkAdjustRecurse(&fp->fd_nested, line1 - fp->fd_top, + foldMarkAdjustRecurse(wp, &fp->fd_nested, line1 - fp->fd_top, line2 - fp->fd_top, amount, amount_after + (fp->fd_top - top)); fp->fd_len -= line2 - fp->fd_top + 1; fp->fd_top = line1; } else { - foldMarkAdjustRecurse(&fp->fd_nested, line1 - fp->fd_top, + foldMarkAdjustRecurse(wp, &fp->fd_nested, line1 - fp->fd_top, line2 - fp->fd_top, amount, amount_after - amount); fp->fd_len += amount_after - amount; @@ -1464,10 +1479,10 @@ static void foldMarkAdjustRecurse(garray_T *gap, linenr_T line1, linenr_T line2, * Get the lowest 'foldlevel' value that makes the deepest nested fold in the * current window open. */ -int getDeepestNesting(void) +int getDeepestNesting(win_T *wp) { - checkupdate(curwin); - return getDeepestNestingRecurse(&curwin->w_folds); + checkupdate(wp); + return getDeepestNestingRecurse(&wp->w_folds); } static int getDeepestNestingRecurse(garray_T *gap) @@ -1486,17 +1501,22 @@ static int getDeepestNestingRecurse(garray_T *gap) return maxlevel; } -/* check_closed() {{{2 */ -/* - * Check if a fold is closed and update the info needed to check nested folds. - */ +// check_closed() {{{2 +/// Check if a fold is closed and update the info needed to check nested folds. +/// +/// @param[in,out] use_levelp true: outer fold had FD_LEVEL +/// @param[in,out] fp fold to check +/// @param level folding depth +/// @param[out] maybe_smallp true: outer this had fd_small == kNone +/// @param lnum_off line number offset for fp->fd_top +/// @return true if fold is closed static bool check_closed( - win_T *const win, + win_T *const wp, fold_T *const fp, - bool *const use_levelp, // true: outer fold had FD_LEVEL - const int level, // folding depth - bool *const maybe_smallp, // true: outer this had fd_small == kNone - const linenr_T lnum_off // line number offset for fp->fd_top + bool *const use_levelp, + const int level, + bool *const maybe_smallp, + const linenr_T lnum_off ) { bool closed = false; @@ -1505,7 +1525,7 @@ static bool check_closed( * fold and all folds it contains depend on 'foldlevel'. */ if (*use_levelp || fp->fd_flags == FD_LEVEL) { *use_levelp = true; - if (level >= win->w_p_fdl) { + if (level >= wp->w_p_fdl) { closed = true; } } else if (fp->fd_flags == FD_CLOSED) { @@ -1520,7 +1540,7 @@ static bool check_closed( if (*maybe_smallp) { fp->fd_small = kNone; } - checkSmall(win, fp, lnum_off); + checkSmall(wp, fp, lnum_off); if (fp->fd_small == kTrue) { closed = false; } @@ -1528,10 +1548,9 @@ static bool check_closed( return closed; } -/* checkSmall() {{{2 */ -/* - * Update fd_small field of fold "fp". - */ +// checkSmall() {{{2 +/// Update fd_small field of fold "fp". +/// @param lnum_off offset for fp->fd_top static void checkSmall( win_T *const wp, @@ -1543,13 +1562,13 @@ checkSmall( // Mark any nested folds to maybe-small setSmallMaybe(&fp->fd_nested); - if (fp->fd_len > curwin->w_p_fml) { + if (fp->fd_len > wp->w_p_fml) { fp->fd_small = kFalse; } else { int count = 0; for (int n = 0; n < fp->fd_len; n++) { count += plines_win_nofold(wp, fp->fd_top + lnum_off + n); - if (count > curwin->w_p_fml) { + if (count > wp->w_p_fml) { fp->fd_small = kFalse; return; } @@ -1574,42 +1593,45 @@ static void setSmallMaybe(garray_T *gap) * Create a fold from line "start" to line "end" (inclusive) in the current * window by adding markers. */ -static void foldCreateMarkers(linenr_T start, linenr_T end) +static void foldCreateMarkers(win_T *wp, linenr_T start, linenr_T end) { - if (!MODIFIABLE(curbuf)) { + buf_T *buf = wp->w_buffer; + if (!MODIFIABLE(buf)) { EMSG(_(e_modifiable)); return; } - parseMarker(curwin); + parseMarker(wp); - foldAddMarker(start, curwin->w_p_fmr, foldstartmarkerlen); - foldAddMarker(end, foldendmarker, foldendmarkerlen); + foldAddMarker(buf, start, wp->w_p_fmr, foldstartmarkerlen); + foldAddMarker(buf, end, foldendmarker, foldendmarkerlen); /* Update both changes here, to avoid all folds after the start are * changed when the start marker is inserted and the end isn't. */ + // TODO(teto): pass the buffer changed_lines(start, (colnr_T)0, end, 0L, false); // Note: foldAddMarker() may not actually change start and/or end if // u_save() is unable to save the buffer line, but we send the // nvim_buf_lines_event anyway since it won't do any harm. int64_t num_changed = 1 + end - start; - buf_updates_send_changes(curbuf, start, num_changed, num_changed, true); + buf_updates_send_changes(buf, start, num_changed, num_changed, true); } /* foldAddMarker() {{{2 */ /* * Add "marker[markerlen]" in 'commentstring' to line "lnum". */ -static void foldAddMarker(linenr_T lnum, const char_u *marker, size_t markerlen) +static void foldAddMarker( + buf_T *buf, linenr_T lnum, const char_u *marker, size_t markerlen) { - char_u *cms = curbuf->b_p_cms; + char_u *cms = buf->b_p_cms; char_u *line; char_u *newline; - char_u *p = (char_u *)strstr((char *)curbuf->b_p_cms, "%s"); + char_u *p = (char_u *)strstr((char *)buf->b_p_cms, "%s"); bool line_is_comment = false; // Allocate a new line: old-line + 'cms'-start + marker + 'cms'-end - line = ml_get(lnum); + line = ml_get_buf(buf, lnum, false); size_t line_len = STRLEN(line); size_t added = 0; @@ -1628,11 +1650,10 @@ static void foldAddMarker(linenr_T lnum, const char_u *marker, size_t markerlen) STRCPY(newline + line_len + (p - cms) + markerlen, p + 2); added = markerlen + STRLEN(cms)-2; } - ml_replace(lnum, newline, false); + ml_replace_buf(buf, lnum, newline, false); if (added) { - extmark_splice(curbuf, (int)lnum-1, (int)line_len, - 0, 0, - 0, (int)added, kExtmarkUndo); + extmark_splice_cols(buf, (int)lnum-1, (int)line_len, + 0, (int)added, kExtmarkUndo); } } } @@ -1643,20 +1664,22 @@ static void foldAddMarker(linenr_T lnum, const char_u *marker, size_t markerlen) */ static void deleteFoldMarkers( + win_T *wp, fold_T *fp, int recursive, linenr_T lnum_off // offset for fp->fd_top ) { if (recursive) { - for (int i = 0; i < fp->fd_nested.ga_len; ++i) { - deleteFoldMarkers((fold_T *)fp->fd_nested.ga_data + i, TRUE, + for (int i = 0; i < fp->fd_nested.ga_len; i++) { + deleteFoldMarkers(wp, (fold_T *)fp->fd_nested.ga_data + i, true, lnum_off + fp->fd_top); } } - foldDelMarker(fp->fd_top + lnum_off, curwin->w_p_fmr, foldstartmarkerlen); - foldDelMarker(fp->fd_top + lnum_off + fp->fd_len - 1, foldendmarker, - foldendmarkerlen); + foldDelMarker(wp->w_buffer, fp->fd_top+lnum_off, wp->w_p_fmr, + foldstartmarkerlen); + foldDelMarker(wp->w_buffer, fp->fd_top + lnum_off + fp->fd_len - 1, + foldendmarker, foldendmarkerlen); } // foldDelMarker() {{{2 @@ -1665,18 +1688,20 @@ deleteFoldMarkers( // Delete 'commentstring' if it matches. // If the marker is not found, there is no error message. Could be a missing // close-marker. -static void foldDelMarker(linenr_T lnum, char_u *marker, size_t markerlen) +static void foldDelMarker( + buf_T *buf, linenr_T lnum, char_u *marker, size_t markerlen +) { char_u *newline; - char_u *cms = curbuf->b_p_cms; + char_u *cms = buf->b_p_cms; char_u *cms2; // end marker may be missing and fold extends below the last line - if (lnum > curbuf->b_ml.ml_line_count) { + if (lnum > buf->b_ml.ml_line_count) { return; } - char_u *line = ml_get(lnum); - for (char_u *p = line; *p != NUL; ++p) { + char_u *line = ml_get_buf(buf, lnum, false); + for (char_u *p = line; *p != NUL; p++) { if (STRNCMP(p, marker, markerlen) != 0) { continue; } @@ -1700,10 +1725,10 @@ static void foldDelMarker(linenr_T lnum, char_u *marker, size_t markerlen) assert(p >= line); memcpy(newline, line, (size_t)(p - line)); STRCPY(newline + (p - line), p + len); - ml_replace(lnum, newline, false); - extmark_splice(curbuf, (int)lnum-1, (int)(p - line), - 0, (int)len, - 0, 0, kExtmarkUndo); + ml_replace_buf(buf, lnum, newline, false); + extmark_splice_cols(buf, (int)lnum-1, (int)(p - line), + (int)len, + 0, kExtmarkUndo); } break; } @@ -1891,12 +1916,12 @@ void foldtext_cleanup(char_u *str) /* foldUpdateIEMS() {{{2 */ /* * Update the folding for window "wp", at least from lines "top" to "bot". - * Return TRUE if any folds did change. + * IEMS = "Indent Expr Marker Syntax" */ static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot) { fline_T fline; - void (*getlevel)(fline_T *); + LevelGetter getlevel = NULL; fold_T *fp; /* Avoid problems when being called recursively. */ @@ -2078,8 +2103,8 @@ static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot) } } - /* There can't be any folds from start until end now. */ - foldRemove(&wp->w_folds, start, end); + // There can't be any folds from start until end now. + foldRemove(wp, &wp->w_folds, start, end); /* If some fold changed, need to redraw and position cursor. */ if (fold_changed && wp->w_p_fen) @@ -2259,12 +2284,12 @@ static linenr_T foldUpdateIEMSRecurse( if (fp->fd_top > firstlnum) { // We will move the start of this fold up, hence we move all // nested folds (with relative line numbers) down. - foldMarkAdjustRecurse(&fp->fd_nested, + foldMarkAdjustRecurse(flp->wp, &fp->fd_nested, (linenr_T)0, (linenr_T)MAXLNUM, (long)(fp->fd_top - firstlnum), 0L); } else { // Will move fold down, move nested folds relatively up. - foldMarkAdjustRecurse(&fp->fd_nested, + foldMarkAdjustRecurse(flp->wp, &fp->fd_nested, (linenr_T)0, (long)(firstlnum - fp->fd_top - 1), (linenr_T)MAXLNUM, @@ -2294,10 +2319,10 @@ static linenr_T foldUpdateIEMSRecurse( breakstart = flp->lnum; breakend = flp->lnum; } - foldRemove(&fp->fd_nested, breakstart - fp->fd_top, + foldRemove(flp->wp, &fp->fd_nested, breakstart - fp->fd_top, breakend - fp->fd_top); i = (int)(fp - (fold_T *)gap->ga_data); - foldSplit(gap, i, breakstart, breakend - 1); + foldSplit(flp->wp->w_buffer, gap, i, breakstart, breakend - 1); fp = (fold_T *)gap->ga_data + i + 1; /* If using the "marker" or "syntax" method, we * need to continue until the end of the fold is @@ -2313,7 +2338,7 @@ static linenr_T foldUpdateIEMSRecurse( if (i != 0) { fp2 = fp - 1; if (fp2->fd_top + fp2->fd_len == fp->fd_top) { - foldMerge(fp2, gap, fp); + foldMerge(flp->wp, fp2, gap, fp); fp = fp2; } } @@ -2324,12 +2349,13 @@ static linenr_T foldUpdateIEMSRecurse( // A fold that starts at or after startlnum and stops // before the new fold must be deleted. Continue // looking for the next one. - deleteFoldEntry(gap, (int)(fp - (fold_T *)gap->ga_data), true); + deleteFoldEntry(flp->wp, gap, + (int)(fp - (fold_T *)gap->ga_data), true); } else { /* A fold has some lines above startlnum, truncate it * to stop just above startlnum. */ fp->fd_len = startlnum - fp->fd_top; - foldMarkAdjustRecurse(&fp->fd_nested, + foldMarkAdjustRecurse(flp->wp, &fp->fd_nested, (linenr_T)fp->fd_len, (linenr_T)MAXLNUM, (linenr_T)MAXLNUM, 0L); fold_changed = true; @@ -2458,8 +2484,8 @@ static linenr_T foldUpdateIEMSRecurse( // Delete contained folds from the end of the last one found until where // we stopped looking. - foldRemove(&fp->fd_nested, startlnum2 - fp->fd_top, - flp->lnum - 1 - fp->fd_top); + foldRemove(flp->wp, &fp->fd_nested, startlnum2 - fp->fd_top, + flp->lnum - 1 - fp->fd_top); if (lvl < level) { // End of fold found, update the length when it got shorter. @@ -2477,7 +2503,7 @@ static linenr_T foldUpdateIEMSRecurse( // indent or expr method: split fold to create a new one // below bot i = (int)(fp - (fold_T *)gap->ga_data); - foldSplit(gap, i, flp->lnum, bot); + foldSplit(flp->wp->w_buffer, gap, i, flp->lnum, bot); fp = (fold_T *)gap->ga_data + i; } } else { @@ -2495,23 +2521,23 @@ static linenr_T foldUpdateIEMSRecurse( break; if (fp2->fd_top + fp2->fd_len > flp->lnum) { if (fp2->fd_top < flp->lnum) { - /* Make fold that includes lnum start at lnum. */ - foldMarkAdjustRecurse(&fp2->fd_nested, - (linenr_T)0, (long)(flp->lnum - fp2->fd_top - 1), - (linenr_T)MAXLNUM, (long)(fp2->fd_top - flp->lnum)); + // Make fold that includes lnum start at lnum. + foldMarkAdjustRecurse(flp->wp, &fp2->fd_nested, + (linenr_T)0, (long)(flp->lnum - fp2->fd_top - 1), + (linenr_T)MAXLNUM, (long)(fp2->fd_top-flp->lnum)); fp2->fd_len -= flp->lnum - fp2->fd_top; fp2->fd_top = flp->lnum; fold_changed = true; } if (lvl >= level) { - /* merge new fold with existing fold that follows */ - foldMerge(fp, gap, fp2); + // merge new fold with existing fold that follows + foldMerge(flp->wp, fp, gap, fp2); } break; } fold_changed = true; - deleteFoldEntry(gap, (int)(fp2 - (fold_T *)gap->ga_data), true); + deleteFoldEntry(flp->wp, gap, (int)(fp2 - (fold_T *)gap->ga_data), true); } /* Need to redraw the lines we inspected, which might be further down than @@ -2547,8 +2573,10 @@ static void foldInsert(garray_T *gap, int i) * The caller must first have taken care of any nested folds from "top" to * "bot"! */ -static void foldSplit(garray_T *const gap, const int i, const linenr_T top, - const linenr_T bot) +static void foldSplit(buf_T *buf, garray_T *const gap, + const int i, const linenr_T top, + const linenr_T bot + ) { fold_T *fp2; @@ -2603,7 +2631,9 @@ static void foldSplit(garray_T *const gap, const int i, const linenr_T top, * 5: made to start below "bot". * 6: not changed */ -static void foldRemove(garray_T *gap, linenr_T top, linenr_T bot) +static void foldRemove( + win_T *const wp, garray_T *gap, linenr_T top, linenr_T bot +) { fold_T *fp = NULL; @@ -2615,10 +2645,11 @@ static void foldRemove(garray_T *gap, linenr_T top, linenr_T bot) // Find fold that includes top or a following one. if (foldFind(gap, top, &fp) && fp->fd_top < top) { // 2: or 3: need to delete nested folds - foldRemove(&fp->fd_nested, top - fp->fd_top, bot - fp->fd_top); + foldRemove(wp, &fp->fd_nested, top - fp->fd_top, bot - fp->fd_top); if (fp->fd_top + fp->fd_len - 1 > bot) { // 3: need to split it. - foldSplit(gap, (int)(fp - (fold_T *)gap->ga_data), top, bot); + foldSplit(wp->w_buffer, gap, + (int)(fp - (fold_T *)gap->ga_data), top, bot); } else { // 2: truncate fold at "top". fp->fd_len = top - fp->fd_top; @@ -2636,7 +2667,8 @@ static void foldRemove(garray_T *gap, linenr_T top, linenr_T bot) fold_changed = true; if (fp->fd_top + fp->fd_len - 1 > bot) { // 5: Make fold that includes bot start below bot. - foldMarkAdjustRecurse(&fp->fd_nested, + foldMarkAdjustRecurse( + wp, &fp->fd_nested, (linenr_T)0, (long)(bot - fp->fd_top), (linenr_T)MAXLNUM, (long)(fp->fd_top - bot - 1)); fp->fd_len -= bot - fp->fd_top + 1; @@ -2645,7 +2677,7 @@ static void foldRemove(garray_T *gap, linenr_T top, linenr_T bot) } // 4: Delete completely contained fold. - deleteFoldEntry(gap, (int)(fp - (fold_T *)gap->ga_data), true); + deleteFoldEntry(wp, gap, (int)(fp - (fold_T *)gap->ga_data), true); } } } @@ -2697,19 +2729,22 @@ static void foldReverseOrder( // 8. truncated below dest and shifted up. // 9. shifted up // 10. not changed -static void truncate_fold(fold_T *fp, linenr_T end) +static void truncate_fold(win_T *const wp, fold_T *fp, linenr_T end) { // I want to stop *at here*, foldRemove() stops *above* top end += 1; - foldRemove(&fp->fd_nested, end - fp->fd_top, MAXLNUM); + foldRemove(wp, &fp->fd_nested, end - fp->fd_top, MAXLNUM); fp->fd_len = end - fp->fd_top; } #define FOLD_END(fp) ((fp)->fd_top + (fp)->fd_len - 1) #define VALID_FOLD(fp, gap) ((fp) < ((fold_T *)(gap)->ga_data + (gap)->ga_len)) #define FOLD_INDEX(fp, gap) ((size_t)(fp - ((fold_T *)(gap)->ga_data))) -void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2, - const linenr_T dest) +void foldMoveRange( + win_T *const wp, garray_T *gap, + const linenr_T line1, const linenr_T line2, + const linenr_T dest +) { fold_T *fp; const linenr_T range_len = line2 - line1 + 1; @@ -2720,20 +2755,20 @@ void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2, if (FOLD_END(fp) > dest) { // Case 4 -- don't have to change this fold, but have to move nested // folds. - foldMoveRange(&fp->fd_nested, line1 - fp->fd_top, line2 - + foldMoveRange(wp, &fp->fd_nested, line1 - fp->fd_top, line2 - fp->fd_top, dest - fp->fd_top); return; } else if (FOLD_END(fp) > line2) { // Case 3 -- Remove nested folds between line1 and line2 & reduce the // length of fold by "range_len". // Folds after this one must be dealt with. - foldMarkAdjustRecurse(&fp->fd_nested, line1 - fp->fd_top, + foldMarkAdjustRecurse(wp, &fp->fd_nested, line1 - fp->fd_top, line2 - fp->fd_top, MAXLNUM, -range_len); fp->fd_len -= range_len; } else { // Case 2 -- truncate fold *above* line1. // Folds after this one must be dealt with. - truncate_fold(fp, line1 - 1); + truncate_fold(wp, fp, line1 - 1); } // Look at the next fold, and treat that one as if it were the first after // "line1" (because now it is). @@ -2751,13 +2786,13 @@ void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2, } if (VALID_FOLD(fp, gap) && fp->fd_top <= dest) { // Case 8. -- ensure truncated at dest, shift up - truncate_fold(fp, dest); + truncate_fold(wp, fp, dest); fp->fd_top -= range_len; } return; } else if (FOLD_END(fp) > dest) { // Case 7 -- remove nested folds and shrink - foldMarkAdjustRecurse(&fp->fd_nested, line2 + 1 - fp->fd_top, + foldMarkAdjustRecurse(wp, &fp->fd_nested, line2 + 1 - fp->fd_top, dest - fp->fd_top, MAXLNUM, -move_len); fp->fd_len -= move_len; fp->fd_top += move_len; @@ -2773,7 +2808,7 @@ void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2, // 5, or 6 if (FOLD_END(fp) > line2) { // 6, truncate before moving - truncate_fold(fp, line2); + truncate_fold(wp, fp, line2); } fp->fd_top += move_len; continue; @@ -2785,7 +2820,7 @@ void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2, } if (FOLD_END(fp) > dest) { - truncate_fold(fp, dest); + truncate_fold(wp, fp, dest); } fp->fd_top -= range_len; @@ -2817,7 +2852,7 @@ void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2, * The resulting fold is "fp1", nested folds are moved from "fp2" to "fp1". * Fold entry "fp2" in "gap" is deleted. */ -static void foldMerge(fold_T *fp1, garray_T *gap, fold_T *fp2) +static void foldMerge(win_T *const wp, fold_T *fp1, garray_T *gap, fold_T *fp2) { fold_T *fp3; fold_T *fp4; @@ -2827,8 +2862,9 @@ static void foldMerge(fold_T *fp1, garray_T *gap, fold_T *fp2) /* If the last nested fold in fp1 touches the first nested fold in fp2, * merge them recursively. */ - if (foldFind(gap1, fp1->fd_len - 1L, &fp3) && foldFind(gap2, 0L, &fp4)) - foldMerge(fp3, gap2, fp4); + if (foldFind(gap1, fp1->fd_len - 1L, &fp3) && foldFind(gap2, 0L, &fp4)) { + foldMerge(wp, fp3, gap2, fp4); + } /* Move nested folds in fp2 to the end of fp1. */ if (!GA_EMPTY(gap2)) { @@ -2843,7 +2879,7 @@ static void foldMerge(fold_T *fp1, garray_T *gap, fold_T *fp2) } fp1->fd_len += fp2->fd_len; - deleteFoldEntry(gap, (int)(fp2 - (fold_T *)gap->ga_data), true); + deleteFoldEntry(wp, gap, (int)(fp2 - (fold_T *)gap->ga_data), true); fold_changed = true; } diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index e861cfda35..6e80ad0e5c 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -237,6 +237,12 @@ for i = 1, #functions do (j - 1)..'].type == kObjectTypeInteger && args.items['..(j - 1)..'].data.integer >= 0) {') output:write('\n '..converted..' = (handle_T)args.items['..(j - 1)..'].data.integer;') end + if rt:match('^Float$') then + -- accept integers for Floats + output:write('\n } else if (args.items['.. + (j - 1)..'].type == kObjectTypeInteger) {') + output:write('\n '..converted..' = (Float)args.items['..(j - 1)..'].data.integer;') + end -- accept empty lua tables as empty dictionarys if rt:match('^Dictionary') then output:write('\n } else if (args.items['..(j - 1)..'].type == kObjectTypeArray && args.items['..(j - 1)..'].data.array.size == 0) {') --luacheck: ignore 631 diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 81666bf5d6..5ab5a7db1b 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -41,6 +41,7 @@ #include "nvim/option.h" #include "nvim/regexp.h" #include "nvim/screen.h" +#include "nvim/ex_session.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/ui.h" @@ -1095,26 +1096,40 @@ void del_typebuf(int len, int offset) * Write typed characters to script file. * If recording is on put the character in the recordbuffer. */ -static void gotchars(char_u *chars, size_t len) +static void gotchars(const char_u *chars, size_t len) + FUNC_ATTR_NONNULL_ALL { - char_u *s = chars; - int c; + const char_u *s = chars; + static char_u buf[4] = { 0 }; + static size_t buflen = 0; + size_t todo = len; - // remember how many chars were last recorded - if (reg_recording != 0) { - last_recorded_len += len; - } + while (todo--) { + buf[buflen++] = *s++; + + // When receiving a special key sequence, store it until we have all + // the bytes and we can decide what to do with it. + if (buflen == 1 && buf[0] == K_SPECIAL) { + continue; + } + if (buflen == 2) { + continue; + } - while (len--) { // Handle one byte at a time; no translation to be done. - c = *s++; - updatescript(c); + for (size_t i = 0; i < buflen; i++) { + updatescript(buf[i]); + } if (reg_recording != 0) { - char buf[2] = { (char)c, NUL }; - add_buff(&recordbuff, buf, 1L); + buf[buflen] = NUL; + add_buff(&recordbuff, (char *)buf, (ptrdiff_t)buflen); + // remember how many chars were last recorded + last_recorded_len += buflen; } + buflen = 0; } + may_sync_undo(); /* output "debug mode" message next time in debug mode */ @@ -1201,7 +1216,7 @@ void save_typeahead(tasave_T *tp) { tp->save_typebuf = typebuf; alloc_typebuf(); - tp->typebuf_valid = TRUE; + tp->typebuf_valid = true; tp->old_char = old_char; tp->old_mod_mask = old_mod_mask; old_char = -1; @@ -2480,12 +2495,11 @@ int inchar( return fix_input_buffer(buf, len); } -/* - * Fix typed characters for use by vgetc() and check_termcode(). - * buf[] must have room to triple the number of bytes! - * Returns the new length. - */ +// Fix typed characters for use by vgetc() and check_termcode(). +// "buf[]" must have room to triple the number of bytes! +// Returns the new length. int fix_input_buffer(char_u *buf, int len) + FUNC_ATTR_NONNULL_ALL { if (!using_script()) { // Should not escape K_SPECIAL/CSI reading input from the user because vim @@ -3347,7 +3361,7 @@ showmap ( msg_putchar(' '); // Display the LHS. Get length of what we write. - len = (size_t)msg_outtrans_special(mp->m_keys, true); + len = (size_t)msg_outtrans_special(mp->m_keys, true, 0); do { msg_putchar(' '); /* padd with blanks */ ++len; @@ -3375,7 +3389,7 @@ showmap ( // as typeahead. char_u *s = vim_strsave(mp->m_str); vim_unescape_csi(s); - msg_outtrans_special(s, FALSE); + msg_outtrans_special(s, false, 0); xfree(s); } if (p_verbose > 0) { diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h index 01f60ccf49..f0b52079aa 100644 --- a/src/nvim/getchar.h +++ b/src/nvim/getchar.h @@ -10,12 +10,12 @@ /// Values for "noremap" argument of ins_typebuf() /// /// Also used for map->m_noremap and menu->noremap[]. -enum { +enum RemapValues { REMAP_YES = 0, ///< Allow remapping. REMAP_NONE = -1, ///< No remapping. REMAP_SCRIPT = -2, ///< Remap script-local mappings only. REMAP_SKIP = -3, ///< No remapping for first char. -} RemapValues; +}; // Argument for flush_buffers(). typedef enum { diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 4741778c14..f102c3ddd8 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -120,28 +120,20 @@ typedef off_t off_T; # endif #endif -/* - * When vgetc() is called, it sets mod_mask to the set of modifiers that are - * held down based on the MOD_MASK_* symbols that are read first. - */ -EXTERN int mod_mask INIT(= 0x0); /* current key modifiers */ +// When vgetc() is called, it sets mod_mask to the set of modifiers that are +// held down based on the MOD_MASK_* symbols that are read first. +EXTERN int mod_mask INIT(= 0x0); // current key modifiers -// TODO(bfredl): for the final interface this should find a more suitable -// location. -EXTERN sattr_T *lua_attr_buf INIT(= NULL); -EXTERN size_t lua_attr_bufsize INIT(= 0); EXTERN bool lua_attr_active INIT(= false); -/* - * Cmdline_row is the row where the command line starts, just below the - * last window. - * When the cmdline gets longer than the available space the screen gets - * scrolled up. After a CTRL-D (show matches), after hitting ':' after - * "hit return", and for the :global command, the command line is - * temporarily moved. The old position is restored with the next call to - * update_screen(). - */ +// Cmdline_row is the row where the command line starts, just below the +// last window. +// When the cmdline gets longer than the available space the screen gets +// scrolled up. After a CTRL-D (show matches), after hitting ':' after +// "hit return", and for the :global command, the command line is +// temporarily moved. The old position is restored with the next call to +// update_screen(). EXTERN int cmdline_row; EXTERN int redraw_cmdline INIT(= false); // cmdline must be redrawn @@ -153,42 +145,38 @@ EXTERN int cmdline_was_last_drawn INIT(= false); // cmdline was last drawn EXTERN int exec_from_reg INIT(= false); // executing register -/* - * When '$' is included in 'cpoptions' option set: - * When a change command is given that deletes only part of a line, a dollar - * is put at the end of the changed text. dollar_vcol is set to the virtual - * column of this '$'. -1 is used to indicate no $ is being displayed. - */ +// When '$' is included in 'cpoptions' option set: +// When a change command is given that deletes only part of a line, a dollar +// is put at the end of the changed text. dollar_vcol is set to the virtual +// column of this '$'. -1 is used to indicate no $ is being displayed. EXTERN colnr_T dollar_vcol INIT(= -1); -/* - * Variables for Insert mode completion. - */ +// Variables for Insert mode completion. -/* Length in bytes of the text being completed (this is deleted to be replaced - * by the match.) */ +// Length in bytes of the text being completed (this is deleted to be replaced +// by the match.) EXTERN int compl_length INIT(= 0); -/* Set when character typed while looking for matches and it means we should - * stop looking for matches. */ -EXTERN int compl_interrupted INIT(= FALSE); +// Set when character typed while looking for matches and it means we should +// stop looking for matches. +EXTERN int compl_interrupted INIT(= false); // Set when doing something for completion that may call edit() recursively, // which is not allowed. Also used to disable folding during completion EXTERN int compl_busy INIT(= false); -/* List of flags for method of completion. */ +// List of flags for method of completion. EXTERN int compl_cont_status INIT(= 0); -# define CONT_ADDING 1 /* "normal" or "adding" expansion */ -# define CONT_INTRPT (2 + 4) /* a ^X interrupted the current expansion */ - /* it's set only iff N_ADDS is set */ -# define CONT_N_ADDS 4 /* next ^X<> will add-new or expand-current */ -# define CONT_S_IPOS 8 /* next ^X<> will set initial_pos? - * if so, word-wise-expansion will set SOL */ -# define CONT_SOL 16 /* pattern includes start of line, just for - * word-wise expansion, not set for ^X^L */ -# define CONT_LOCAL 32 /* for ctrl_x_mode 0, ^X^P/^X^N do a local - * expansion, (eg use complete=.) */ +# define CONT_ADDING 1 // "normal" or "adding" expansion +# define CONT_INTRPT (2 + 4) // a ^X interrupted the current expansion + // it's set only iff N_ADDS is set +# define CONT_N_ADDS 4 // next ^X<> will add-new or expand-current +# define CONT_S_IPOS 8 // next ^X<> will set initial_pos? + // if so, word-wise-expansion will set SOL +# define CONT_SOL 16 // pattern includes start of line, just for + // word-wise expansion, not set for ^X^L +# define CONT_LOCAL 32 // for ctrl_x_mode 0, ^X^P/^X^N do a local + // expansion, (eg use complete=.) // state for putting characters in the message area EXTERN int cmdmsg_rl INIT(= false); // cmdline is drawn right to left @@ -204,49 +192,49 @@ EXTERN bool msg_scrolled_ign INIT(= false); EXTERN bool msg_did_scroll INIT(= false); -EXTERN char_u *keep_msg INIT(= NULL); /* msg to be shown after redraw */ -EXTERN int keep_msg_attr INIT(= 0); /* highlight attr for keep_msg */ -EXTERN int keep_msg_more INIT(= FALSE); /* keep_msg was set by msgmore() */ -EXTERN int need_fileinfo INIT(= FALSE); /* do fileinfo() after redraw */ -EXTERN int msg_scroll INIT(= FALSE); /* msg_start() will scroll */ -EXTERN int msg_didout INIT(= FALSE); /* msg_outstr() was used in line */ -EXTERN int msg_didany INIT(= FALSE); /* msg_outstr() was used at all */ -EXTERN int msg_nowait INIT(= FALSE); /* don't wait for this msg */ -EXTERN int emsg_off INIT(= 0); /* don't display errors for now, - unless 'debug' is set. */ -EXTERN int info_message INIT(= FALSE); /* printing informative message */ -EXTERN int msg_hist_off INIT(= FALSE); /* don't add messages to history */ -EXTERN int need_clr_eos INIT(= FALSE); /* need to clear text before - displaying a message. */ -EXTERN int emsg_skip INIT(= 0); /* don't display errors for - expression that is skipped */ -EXTERN int emsg_severe INIT(= FALSE); /* use message of next of several - emsg() calls for throw */ -EXTERN int did_endif INIT(= FALSE); /* just had ":endif" */ -EXTERN dict_T vimvardict; /* Dictionary with v: variables */ -EXTERN dict_T globvardict; /* Dictionary with g: variables */ -EXTERN int did_emsg; /* set by emsg() when the message - is displayed or thrown */ +EXTERN char_u *keep_msg INIT(= NULL); // msg to be shown after redraw +EXTERN int keep_msg_attr INIT(= 0); // highlight attr for keep_msg +EXTERN int keep_msg_more INIT(= false); // keep_msg was set by msgmore() +EXTERN int need_fileinfo INIT(= false); // do fileinfo() after redraw +EXTERN int msg_scroll INIT(= false); // msg_start() will scroll +EXTERN int msg_didout INIT(= false); // msg_outstr() was used in line +EXTERN int msg_didany INIT(= false); // msg_outstr() was used at all +EXTERN int msg_nowait INIT(= false); // don't wait for this msg +EXTERN int emsg_off INIT(= 0); // don't display errors for now, + // unless 'debug' is set. +EXTERN int info_message INIT(= false); // printing informative message +EXTERN int msg_hist_off INIT(= false); // don't add messages to history +EXTERN int need_clr_eos INIT(= false); // need to clear text before + // displaying a message. +EXTERN int emsg_skip INIT(= 0); // don't display errors for + // expression that is skipped +EXTERN int emsg_severe INIT(= false); // use message of next of several + // emsg() calls for throw +EXTERN int did_endif INIT(= false); // just had ":endif" +EXTERN dict_T vimvardict; // Dictionary with v: variables +EXTERN dict_T globvardict; // Dictionary with g: variables +EXTERN int did_emsg; // set by emsg() when the message + // is displayed or thrown EXTERN bool called_vim_beep; // set if vim_beep() is called -EXTERN int did_emsg_syntax; /* did_emsg set because of a - syntax error */ -EXTERN int called_emsg; /* always set by emsg() */ -EXTERN int ex_exitval INIT(= 0); /* exit value for ex mode */ -EXTERN int emsg_on_display INIT(= FALSE); /* there is an error message */ -EXTERN int rc_did_emsg INIT(= FALSE); /* vim_regcomp() called emsg() */ - -EXTERN int no_wait_return INIT(= 0); /* don't wait for return for now */ -EXTERN int need_wait_return INIT(= 0); /* need to wait for return later */ -EXTERN int did_wait_return INIT(= FALSE); /* wait_return() was used and - nothing written since then */ -EXTERN int need_maketitle INIT(= TRUE); /* call maketitle() soon */ +EXTERN int did_emsg_syntax; // did_emsg set because of a + // syntax error +EXTERN int called_emsg; // always set by emsg() +EXTERN int ex_exitval INIT(= 0); // exit value for ex mode +EXTERN bool emsg_on_display INIT(= false); // there is an error message +EXTERN int rc_did_emsg INIT(= false); // vim_regcomp() called emsg() + +EXTERN int no_wait_return INIT(= 0); // don't wait for return for now +EXTERN int need_wait_return INIT(= 0); // need to wait for return later +EXTERN int did_wait_return INIT(= false); // wait_return() was used and + // nothing written since then +EXTERN int need_maketitle INIT(= true); // call maketitle() soon EXTERN int quit_more INIT(= false); // 'q' hit at "--more--" msg EXTERN int ex_keep_indent INIT(= false); // getexmodeline(): keep indent EXTERN int vgetc_busy INIT(= 0); // when inside vgetc() then > 0 -EXTERN int didset_vim INIT(= FALSE); /* did set $VIM ourselves */ -EXTERN int didset_vimruntime INIT(= FALSE); /* idem for $VIMRUNTIME */ +EXTERN int didset_vim INIT(= false); // did set $VIM ourselves +EXTERN int didset_vimruntime INIT(= false); // idem for $VIMRUNTIME /// Lines left before a "more" message. Ex mode needs to be able to reset this /// after you type something. @@ -254,8 +242,8 @@ EXTERN int lines_left INIT(= -1); // lines left for listing EXTERN int msg_no_more INIT(= false); // don't use more prompt, truncate // messages -EXTERN char_u *sourcing_name INIT( = NULL); /* name of error message source */ -EXTERN linenr_T sourcing_lnum INIT(= 0); /* line number of the source file */ +EXTERN char_u *sourcing_name INIT(= NULL); // name of error message source +EXTERN linenr_T sourcing_lnum INIT(= 0); // line number of the source file EXTERN int ex_nesting_level INIT(= 0); // nesting level EXTERN int debug_break_level INIT(= -1); // break below this level @@ -286,11 +274,11 @@ EXTERN int check_cstack INIT(= false); /// commands). EXTERN int trylevel INIT(= 0); -/// When "force_abort" is TRUE, always skip commands after an error message, +/// When "force_abort" is true, always skip commands after an error message, /// even after the outermost ":endif", ":endwhile" or ":endfor" or for a -/// function without the "abort" flag. It is set to TRUE when "trylevel" is +/// function without the "abort" flag. It is set to true when "trylevel" is /// non-zero (and ":silent!" was not used) or an exception is being thrown at -/// the time an error is detected. It is set to FALSE when "trylevel" gets +/// the time an error is detected. It is set to false when "trylevel" gets /// zero again and there was no error or interrupt or throw. EXTERN int force_abort INIT(= false); @@ -308,7 +296,7 @@ EXTERN struct msglist **msg_list INIT(= NULL); /// interrupt message or reporting an exception that is still uncaught at the /// top level (which has already been discarded then). Also used for the error /// message when no exception can be thrown. -EXTERN int suppress_errthrow INIT(= false); +EXTERN bool suppress_errthrow INIT(= false); /// The stack of all caught and not finished exceptions. The exception on the /// top of the stack is the one got by evaluation of v:exception. The complete @@ -361,41 +349,38 @@ EXTERN int provider_call_nesting INIT(= 0); EXTERN int t_colors INIT(= 256); // int value of T_CCO -/* - * When highlight_match is TRUE, highlight a match, starting at the cursor - * position. Search_match_lines is the number of lines after the match (0 for - * a match within one line), search_match_endcol the column number of the - * character just after the match in the last line. - */ -EXTERN int highlight_match INIT(= FALSE); /* show search match pos */ -EXTERN linenr_T search_match_lines; /* lines of of matched string */ -EXTERN colnr_T search_match_endcol; /* col nr of match end */ - -EXTERN int no_smartcase INIT(= FALSE); /* don't use 'smartcase' once */ - -EXTERN int need_check_timestamps INIT(= FALSE); /* need to check file - timestamps asap */ -EXTERN int did_check_timestamps INIT(= FALSE); /* did check timestamps - recently */ -EXTERN int no_check_timestamps INIT(= 0); /* Don't check timestamps */ - -EXTERN int autocmd_busy INIT(= FALSE); /* Is apply_autocmds() busy? */ -EXTERN int autocmd_no_enter INIT(= FALSE); /* *Enter autocmds disabled */ -EXTERN int autocmd_no_leave INIT(= FALSE); /* *Leave autocmds disabled */ -EXTERN int modified_was_set; /* did ":set modified" */ -EXTERN int did_filetype INIT(= FALSE); /* FileType event found */ -EXTERN int keep_filetype INIT(= FALSE); /* value for did_filetype when - starting to execute - autocommands */ +// When highlight_match is true, highlight a match, starting at the cursor +// position. Search_match_lines is the number of lines after the match (0 for +// a match within one line), search_match_endcol the column number of the +// character just after the match in the last line. +EXTERN int highlight_match INIT(= false); // show search match pos +EXTERN linenr_T search_match_lines; // lines of of matched string +EXTERN colnr_T search_match_endcol; // col nr of match end + +EXTERN int no_smartcase INIT(= false); // don't use 'smartcase' once + +EXTERN int need_check_timestamps INIT(= false); // need to check file + // timestamps asap +EXTERN int did_check_timestamps INIT(= false); // did check timestamps + // recently +EXTERN int no_check_timestamps INIT(= 0); // Don't check timestamps + +EXTERN int autocmd_busy INIT(= false); // Is apply_autocmds() busy? +EXTERN int autocmd_no_enter INIT(= false); // *Enter autocmds disabled +EXTERN int autocmd_no_leave INIT(= false); // *Leave autocmds disabled +EXTERN int modified_was_set; // did ":set modified" +EXTERN int did_filetype INIT(= false); // FileType event found +// value for did_filetype when starting to execute autocommands +EXTERN int keep_filetype INIT(= false); // When deleting the current buffer, another one must be loaded. // If we know which one is preferred, au_new_curbuf is set to it. EXTERN bufref_T au_new_curbuf INIT(= { NULL, 0, 0 }); -// When deleting a buffer/window and autocmd_busy is TRUE, do not free the +// When deleting a buffer/window and autocmd_busy is true, do not free the // buffer/window. but link it in the list starting with // au_pending_free_buf/ap_pending_free_win, using b_next/w_next. -// Free the buffer/window when autocmd_busy is being set to FALSE. +// Free the buffer/window when autocmd_busy is being set to false. EXTERN buf_T *au_pending_free_buf INIT(= NULL); EXTERN win_T *au_pending_free_win INIT(= NULL); @@ -403,31 +388,27 @@ EXTERN win_T *au_pending_free_win INIT(= NULL); EXTERN int mouse_grid; EXTERN int mouse_row; EXTERN int mouse_col; -EXTERN bool mouse_past_bottom INIT(= false); /* mouse below last line */ -EXTERN bool mouse_past_eol INIT(= false); /* mouse right of line */ -EXTERN int mouse_dragging INIT(= 0); /* extending Visual area with - mouse dragging */ +EXTERN bool mouse_past_bottom INIT(= false); // mouse below last line +EXTERN bool mouse_past_eol INIT(= false); // mouse right of line +EXTERN int mouse_dragging INIT(= 0); // extending Visual area with + // mouse dragging -/* The root of the menu hierarchy. */ +// The root of the menu hierarchy. EXTERN vimmenu_T *root_menu INIT(= NULL); -/* - * While defining the system menu, sys_menu is TRUE. This avoids - * overruling of menus that the user already defined. - */ -EXTERN int sys_menu INIT(= FALSE); - -/* While redrawing the screen this flag is set. It means the screen size - * ('lines' and 'rows') must not be changed. */ -EXTERN int updating_screen INIT(= FALSE); - -/* - * All windows are linked in a list. firstwin points to the first entry, - * lastwin to the last entry (can be the same as firstwin) and curwin to the - * currently active window. - */ -EXTERN win_T *firstwin; /* first window */ -EXTERN win_T *lastwin; /* last window */ -EXTERN win_T *prevwin INIT(= NULL); /* previous window */ +// While defining the system menu, sys_menu is true. This avoids +// overruling of menus that the user already defined. +EXTERN int sys_menu INIT(= false); + +// While redrawing the screen this flag is set. It means the screen size +// ('lines' and 'rows') must not be changed. +EXTERN int updating_screen INIT(= 0); + +// All windows are linked in a list. firstwin points to the first entry, +// lastwin to the last entry (can be the same as firstwin) and curwin to the +// currently active window. +EXTERN win_T *firstwin; // first window +EXTERN win_T *lastwin; // last window +EXTERN win_T *prevwin INIT(= NULL); // previous window # define ONE_WINDOW (firstwin == lastwin) # define FOR_ALL_FRAMES(frp, first_frame) \ for (frp = first_frame; frp != NULL; frp = frp->fr_next) // NOLINT @@ -443,33 +424,27 @@ EXTERN win_T *prevwin INIT(= NULL); /* previous window */ for (win_T *wp = ((tp) == curtab) \ ? firstwin : (tp)->tp_firstwin; wp != NULL; wp = wp->w_next) -EXTERN win_T *curwin; /* currently active window */ +EXTERN win_T *curwin; // currently active window -EXTERN win_T *aucmd_win; /* window used in aucmd_prepbuf() */ -EXTERN int aucmd_win_used INIT(= FALSE); /* aucmd_win is being used */ +EXTERN win_T *aucmd_win; // window used in aucmd_prepbuf() +EXTERN int aucmd_win_used INIT(= false); // aucmd_win is being used -/* - * The window layout is kept in a tree of frames. topframe points to the top - * of the tree. - */ -EXTERN frame_T *topframe; /* top of the window frame tree */ +// The window layout is kept in a tree of frames. topframe points to the top +// of the tree. +EXTERN frame_T *topframe; // top of the window frame tree -/* - * Tab pages are alternative topframes. "first_tabpage" points to the first - * one in the list, "curtab" is the current one. - */ +// Tab pages are alternative topframes. "first_tabpage" points to the first +// one in the list, "curtab" is the current one. EXTERN tabpage_T *first_tabpage; EXTERN tabpage_T *lastused_tabpage; EXTERN tabpage_T *curtab; -EXTERN int redraw_tabline INIT(= FALSE); /* need to redraw tabline */ +EXTERN int redraw_tabline INIT(= false); // need to redraw tabline // Iterates over all tabs in the tab list # define FOR_ALL_TABS(tp) for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) -/* - * All buffers are linked in a list. 'firstbuf' points to the first entry, - * 'lastbuf' to the last entry and 'curbuf' to the currently active buffer. - */ +// All buffers are linked in a list. 'firstbuf' points to the first entry, +// 'lastbuf' to the last entry and 'curbuf' to the currently active buffer. EXTERN buf_T *firstbuf INIT(= NULL); // first buffer EXTERN buf_T *lastbuf INIT(= NULL); // last buffer EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer @@ -485,23 +460,19 @@ EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer for (sign = buf->b_signlist; sign != NULL; sign = sign->next) // NOLINT -/* - * List of files being edited (global argument list). curwin->w_alist points - * to this when the window is using the global argument list. - */ -EXTERN alist_T global_alist; /* global argument list */ +// List of files being edited (global argument list). curwin->w_alist points +// to this when the window is using the global argument list. +EXTERN alist_T global_alist; // global argument list EXTERN int max_alist_id INIT(= 0); ///< the previous argument list id EXTERN bool arg_had_last INIT(= false); // accessed last file in // global_alist -EXTERN int ru_col; /* column for ruler */ -EXTERN int ru_wid; /* 'rulerfmt' width of ruler when non-zero */ -EXTERN int sc_col; /* column for shown command */ +EXTERN int ru_col; // column for ruler +EXTERN int ru_wid; // 'rulerfmt' width of ruler when non-zero +EXTERN int sc_col; // column for shown command -// // When starting or exiting some things are done differently (e.g. screen // updating). -// // First NO_SCREEN, then NO_BUFFERS, then 0 when startup finished. EXTERN int starting INIT(= NO_SCREEN); @@ -550,98 +521,78 @@ EXTERN int VIsual_select INIT(= false); EXTERN int VIsual_reselect; /// Type of Visual mode. EXTERN int VIsual_mode INIT(= 'v'); -/// TRUE when redoing Visual. +/// true when redoing Visual. EXTERN int redo_VIsual_busy INIT(= false); /// When pasting text with the middle mouse button in visual mode with /// restart_edit set, remember where it started so we can set Insstart. EXTERN pos_T where_paste_started; -/* - * This flag is used to make auto-indent work right on lines where only a - * <RETURN> or <ESC> is typed. It is set when an auto-indent is done, and - * reset when any other editing is done on the line. If an <ESC> or <RETURN> - * is received, and did_ai is TRUE, the line is truncated. - */ +// This flag is used to make auto-indent work right on lines where only a +// <RETURN> or <ESC> is typed. It is set when an auto-indent is done, and +// reset when any other editing is done on the line. If an <ESC> or <RETURN> +// is received, and did_ai is true, the line is truncated. EXTERN bool did_ai INIT(= false); -/* - * Column of first char after autoindent. 0 when no autoindent done. Used - * when 'backspace' is 0, to avoid backspacing over autoindent. - */ +// Column of first char after autoindent. 0 when no autoindent done. Used +// when 'backspace' is 0, to avoid backspacing over autoindent. EXTERN colnr_T ai_col INIT(= 0); -/* - * This is a character which will end a start-middle-end comment when typed as - * the first character on a new line. It is taken from the last character of - * the "end" comment leader when the COM_AUTO_END flag is given for that - * comment end in 'comments'. It is only valid when did_ai is TRUE. - */ +// This is a character which will end a start-middle-end comment when typed as +// the first character on a new line. It is taken from the last character of +// the "end" comment leader when the COM_AUTO_END flag is given for that +// comment end in 'comments'. It is only valid when did_ai is true. EXTERN int end_comment_pending INIT(= NUL); -/* - * This flag is set after a ":syncbind" to let the check_scrollbind() function - * know that it should not attempt to perform scrollbinding due to the scroll - * that was a result of the ":syncbind." (Otherwise, check_scrollbind() will - * undo some of the work done by ":syncbind.") -ralston - */ -EXTERN int did_syncbind INIT(= FALSE); - -/* - * This flag is set when a smart indent has been performed. When the next typed - * character is a '{' the inserted tab will be deleted again. - */ +// This flag is set after a ":syncbind" to let the check_scrollbind() function +// know that it should not attempt to perform scrollbinding due to the scroll +// that was a result of the ":syncbind." (Otherwise, check_scrollbind() will +// undo some of the work done by ":syncbind.") -ralston +EXTERN int did_syncbind INIT(= false); + +// This flag is set when a smart indent has been performed. When the next typed +// character is a '{' the inserted tab will be deleted again. EXTERN bool did_si INIT(= false); -/* - * This flag is set after an auto indent. If the next typed character is a '}' - * one indent will be removed. - */ +// This flag is set after an auto indent. If the next typed character is a '}' +// one indent will be removed. EXTERN bool can_si INIT(= false); -/* - * This flag is set after an "O" command. If the next typed character is a '{' - * one indent will be removed. - */ +// This flag is set after an "O" command. If the next typed character is a '{' +// one indent will be removed. EXTERN bool can_si_back INIT(= false); // w_cursor before formatting text. EXTERN pos_T saved_cursor INIT(= { 0, 0, 0 }); -/* - * Stuff for insert mode. - */ -EXTERN pos_T Insstart; /* This is where the latest - * insert/append mode started. */ +// Stuff for insert mode. +EXTERN pos_T Insstart; // This is where the latest + // insert/append mode started. // This is where the latest insert/append mode started. In contrast to // Insstart, this won't be reset by certain keys and is needed for // op_insert(), to detect correctly where inserting by the user started. EXTERN pos_T Insstart_orig; -/* - * Stuff for VREPLACE mode. - */ -EXTERN int orig_line_count INIT(= 0); /* Line count when "gR" started */ -EXTERN int vr_lines_changed INIT(= 0); /* #Lines changed by "gR" so far */ +// Stuff for VREPLACE mode. +EXTERN int orig_line_count INIT(= 0); // Line count when "gR" started +EXTERN int vr_lines_changed INIT(= 0); // #Lines changed by "gR" so far // increase around internal delete/replace EXTERN int inhibit_delete_count INIT(= 0); -/* - * These flags are set based upon 'fileencoding'. - * Note that "enc_utf8" is also set for "unicode", because the characters are - * internally stored as UTF-8 (to avoid trouble with NUL bytes). - */ -# define DBCS_JPN 932 /* japan */ -# define DBCS_JPNU 9932 /* euc-jp */ -# define DBCS_KOR 949 /* korea */ -# define DBCS_KORU 9949 /* euc-kr */ -# define DBCS_CHS 936 /* chinese */ -# define DBCS_CHSU 9936 /* euc-cn */ -# define DBCS_CHT 950 /* taiwan */ -# define DBCS_CHTU 9950 /* euc-tw */ -# define DBCS_2BYTE 1 /* 2byte- */ +// These flags are set based upon 'fileencoding'. +// Note that "enc_utf8" is also set for "unicode", because the characters are +// internally stored as UTF-8 (to avoid trouble with NUL bytes). +# define DBCS_JPN 932 // japan +# define DBCS_JPNU 9932 // euc-jp +# define DBCS_KOR 949 // korea +# define DBCS_KORU 9949 // euc-kr +# define DBCS_CHS 936 // chinese +# define DBCS_CHSU 9936 // euc-cn +# define DBCS_CHT 950 // taiwan +# define DBCS_CHTU 9950 // euc-tw +# define DBCS_2BYTE 1 // 2byte- # define DBCS_DEBUG -1 // mbyte flags that used to depend on 'encoding'. These are now deprecated, as @@ -682,40 +633,40 @@ EXTERN int u_sync_once INIT(= 0); // Call u_sync() once when evaluating EXTERN bool force_restart_edit INIT(= false); // force restart_edit after // ex_normal returns -EXTERN int restart_edit INIT(= 0); /* call edit when next cmd finished */ -EXTERN int arrow_used; /* Normally FALSE, set to TRUE after - * hitting cursor key in insert mode. - * Used by vgetorpeek() to decide when - * to call u_sync() */ -EXTERN int ins_at_eol INIT(= FALSE); /* put cursor after eol when - restarting edit after CTRL-O */ +EXTERN int restart_edit INIT(= 0); // call edit when next cmd finished +EXTERN int arrow_used; // Normally false, set to true after + // hitting cursor key in insert mode. + // Used by vgetorpeek() to decide when + // to call u_sync() +EXTERN bool ins_at_eol INIT(= false); // put cursor after eol when + // restarting edit after CTRL-O EXTERN char_u *edit_submode INIT(= NULL); // msg for CTRL-X submode EXTERN char_u *edit_submode_pre INIT(= NULL); // prepended to edit_submode EXTERN char_u *edit_submode_extra INIT(= NULL); // appended to edit_submode EXTERN hlf_T edit_submode_highl; // highl. method for extra info -EXTERN int no_abbr INIT(= TRUE); /* TRUE when no abbreviations loaded */ +EXTERN int no_abbr INIT(= true); // true when no abbreviations loaded EXTERN int mapped_ctrl_c INIT(= 0); // Modes where CTRL-C is mapped. -EXTERN cmdmod_T cmdmod; /* Ex command modifiers */ +EXTERN cmdmod_T cmdmod; // Ex command modifiers EXTERN int msg_silent INIT(= 0); // don't print messages EXTERN int emsg_silent INIT(= 0); // don't print error messages EXTERN bool emsg_noredir INIT(= false); // don't redirect error messages EXTERN bool cmd_silent INIT(= false); // don't echo the command line -/* Values for swap_exists_action: what to do when swap file already exists */ -#define SEA_NONE 0 /* don't use dialog */ -#define SEA_DIALOG 1 /* use dialog when possible */ -#define SEA_QUIT 2 /* quit editing the file */ -#define SEA_RECOVER 3 /* recover the file */ +// Values for swap_exists_action: what to do when swap file already exists +#define SEA_NONE 0 // don't use dialog +#define SEA_DIALOG 1 // use dialog when possible +#define SEA_QUIT 2 // quit editing the file +#define SEA_RECOVER 3 // recover the file EXTERN int swap_exists_action INIT(= SEA_NONE); -/* For dialog when swap file already - * exists. */ -EXTERN int swap_exists_did_quit INIT(= FALSE); -/* Selected "quit" at the dialog. */ +// For dialog when swap file already +// exists. +EXTERN int swap_exists_did_quit INIT(= false); +// Selected "quit" at the dialog. EXTERN char_u IObuff[IOSIZE]; ///< Buffer for sprintf, I/O, etc. EXTERN char_u NameBuff[MAXPATHL]; ///< Buffer for expanding file names @@ -728,11 +679,11 @@ IOSIZE #endif ]; -/* When non-zero, postpone redrawing. */ +// When non-zero, postpone redrawing. EXTERN int RedrawingDisabled INIT(= 0); -EXTERN int readonlymode INIT(= FALSE); /* Set to TRUE for "view" */ -EXTERN int recoverymode INIT(= FALSE); /* Set to TRUE for "-r" option */ +EXTERN int readonlymode INIT(= false); // Set to true for "view" +EXTERN int recoverymode INIT(= false); // Set to true for "-r" option // typeahead buffer EXTERN typebuf_T typebuf INIT(= { NULL, NULL, 0, 0, 0, 0, 0, 0, 0 }); @@ -742,7 +693,7 @@ EXTERN int ex_normal_lock INIT(= 0); // forbid use of ex_normal() EXTERN int ignore_script INIT(= false); // ignore script input EXTERN int stop_insert_mode; // for ":stopinsert" and 'insertmode' EXTERN bool KeyTyped; // true if user typed current char -EXTERN int KeyStuffed; // TRUE if current char from stuffbuf +EXTERN int KeyStuffed; // true if current char from stuffbuf EXTERN int maptick INIT(= 0); // tick for each non-mapped char EXTERN int must_redraw INIT(= 0); // type of redraw necessary @@ -758,15 +709,15 @@ EXTERN FILE *scriptout INIT(= NULL); ///< Stream to write script to. // volatile because it is used in a signal handler. EXTERN volatile int got_int INIT(= false); // set to true when interrupt // signal occurred -EXTERN int bangredo INIT(= FALSE); /* set to TRUE with ! command */ -EXTERN int searchcmdlen; /* length of previous search cmd */ -EXTERN int reg_do_extmatch INIT(= 0); /* Used when compiling regexp: - * REX_SET to allow \z\(...\), - * REX_USE to allow \z\1 et al. */ -EXTERN reg_extmatch_T *re_extmatch_in INIT(= NULL); /* Used by vim_regexec(): - * strings for \z\1...\z\9 */ -EXTERN reg_extmatch_T *re_extmatch_out INIT(= NULL); /* Set by vim_regexec() - * to store \z\(...\) matches */ +EXTERN int bangredo INIT(= false); // set to true with ! command +EXTERN int searchcmdlen; // length of previous search cmd +EXTERN int reg_do_extmatch INIT(= 0); // Used when compiling regexp: + // REX_SET to allow \z\(...\), + // REX_USE to allow \z\1 et al. +// Used by vim_regexec(): strings for \z\1...\z\9 +EXTERN reg_extmatch_T *re_extmatch_in INIT(= NULL); +// Set by vim_regexec() to store \z\(...\) matches +EXTERN reg_extmatch_T *re_extmatch_out INIT(= NULL); EXTERN int did_outofmem_msg INIT(= false); // set after out of memory msg @@ -785,11 +736,11 @@ EXTERN int autocmd_bufnr INIT(= 0); // fnum for <abuf> on cmdline EXTERN char_u *autocmd_match INIT(= NULL); // name for <amatch> on cmdline EXTERN int did_cursorhold INIT(= false); // set when CursorHold t'gerd -EXTERN int postponed_split INIT(= 0); /* for CTRL-W CTRL-] command */ -EXTERN int postponed_split_flags INIT(= 0); /* args for win_split() */ -EXTERN int postponed_split_tab INIT(= 0); /* cmdmod.tab */ -EXTERN int g_do_tagpreview INIT(= 0); /* for tag preview commands: - height of preview window */ +EXTERN int postponed_split INIT(= 0); // for CTRL-W CTRL-] command +EXTERN int postponed_split_flags INIT(= 0); // args for win_split() +EXTERN int postponed_split_tab INIT(= 0); // cmdmod.tab +EXTERN int g_do_tagpreview INIT(= 0); // for tag preview commands: + // height of preview window EXTERN int g_tag_at_cursor INIT(= false); // whether the tag command comes // from the command line (0) or was // invoked as a normal command (1) @@ -797,15 +748,13 @@ EXTERN int g_tag_at_cursor INIT(= false); // whether the tag command comes EXTERN int replace_offset INIT(= 0); // offset for replace_push() EXTERN char_u *escape_chars INIT(= (char_u *)" \t\\\"|"); -/* need backslash in cmd line */ +// need backslash in cmd line -EXTERN int keep_help_flag INIT(= FALSE); /* doing :ta from help file */ +EXTERN int keep_help_flag INIT(= false); // doing :ta from help file -/* - * When a string option is NULL (which only happens in out-of-memory - * situations), it is set to empty_option, to avoid having to check for NULL - * everywhere. - */ +// When a string option is NULL (which only happens in out-of-memory +// situations), it is set to empty_option, to avoid having to check for NULL +// everywhere. EXTERN char_u *empty_option INIT(= (char_u *)""); EXTERN int redir_off INIT(= false); // no redirection for a moment @@ -814,10 +763,10 @@ EXTERN int redir_reg INIT(= 0); // message redirection register EXTERN int redir_vname INIT(= 0); // message redirection variable EXTERN garray_T *capture_ga INIT(= NULL); // captured output for execute() -EXTERN char_u langmap_mapchar[256]; /* mapping for language keys */ +EXTERN char_u langmap_mapchar[256]; // mapping for language keys -EXTERN int save_p_ls INIT(= -1); /* Save 'laststatus' setting */ -EXTERN int save_p_wmh INIT(= -1); /* Save 'winminheight' setting */ +EXTERN int save_p_ls INIT(= -1); // Save 'laststatus' setting +EXTERN int save_p_wmh INIT(= -1); // Save 'winminheight' setting EXTERN int wild_menu_showing INIT(= 0); enum { WM_SHOWN = 1, ///< wildmenu showing @@ -826,25 +775,24 @@ enum { }; -/* - * Some file names are stored in pathdef.c, which is generated from the - * Makefile to make their value depend on the Makefile. - */ +// Some file names are stored in pathdef.c, which is generated from the +// Makefile to make their value depend on the Makefile. #ifdef HAVE_PATHDEF extern char *default_vim_dir; extern char *default_vimruntime_dir; +extern char *default_lib_dir; extern char_u *compiled_user; extern char_u *compiled_sys; #endif -/* When a window has a local directory, the absolute path of the global - * current directory is stored here (in allocated memory). If the current - * directory is not a local directory, globaldir is NULL. */ +// When a window has a local directory, the absolute path of the global +// current directory is stored here (in allocated memory). If the current +// directory is not a local directory, globaldir is NULL. EXTERN char_u *globaldir INIT(= NULL); -/* Whether 'keymodel' contains "stopsel" and "startsel". */ -EXTERN int km_stopsel INIT(= FALSE); -EXTERN int km_startsel INIT(= FALSE); +// Whether 'keymodel' contains "stopsel" and "startsel". +EXTERN int km_stopsel INIT(= false); +EXTERN int km_startsel INIT(= false); EXTERN int cedit_key INIT(= -1); ///< key value of 'cedit' option EXTERN int cmdwin_type INIT(= 0); ///< type of cmdline window or 0 @@ -853,18 +801,16 @@ EXTERN int cmdwin_level INIT(= 0); ///< cmdline recursion level EXTERN char_u no_lines_msg[] INIT(= N_("--No lines in buffer--")); -/* - * When ":global" is used to number of substitutions and changed lines is - * accumulated until it's finished. - * Also used for ":spellrepall". - */ -EXTERN long sub_nsubs; /* total number of substitutions */ -EXTERN linenr_T sub_nlines; /* total number of lines changed */ +// When ":global" is used to number of substitutions and changed lines is +// accumulated until it's finished. +// Also used for ":spellrepall". +EXTERN long sub_nsubs; // total number of substitutions +EXTERN linenr_T sub_nlines; // total number of lines changed -/* table to store parsed 'wildmode' */ +// table to store parsed 'wildmode' EXTERN char_u wim_flags[4]; -/* whether titlestring and iconstring contains statusline syntax */ +// whether titlestring and iconstring contains statusline syntax # define STL_IN_ICON 1 # define STL_IN_TITLE 2 EXTERN int stl_syntax INIT(= 0); @@ -872,7 +818,7 @@ EXTERN int stl_syntax INIT(= 0); // don't use 'hlsearch' temporarily EXTERN bool no_hlsearch INIT(= false); -/* Page number used for %N in 'pageheader' and 'guitablabel'. */ +// Page number used for %N in 'pageheader' and 'guitablabel'. EXTERN linenr_T printer_page_num; @@ -890,18 +836,16 @@ EXTERN char pseps[2] INIT(= { '\\', 0 }); // normal path separator string // kNone when no operator is being executed, kFalse otherwise. EXTERN TriState virtual_op INIT(= kNone); -/* Display tick, incremented for each call to update_screen() */ +// Display tick, incremented for each call to update_screen() EXTERN disptick_T display_tick INIT(= 0); -/* Line in which spell checking wasn't highlighted because it touched the - * cursor position in Insert mode. */ +// Line in which spell checking wasn't highlighted because it touched the +// cursor position in Insert mode. EXTERN linenr_T spell_redraw_lnum INIT(= 0); -/* - * The error messages that can be shared are included here. - * Excluded are errors that are only used once and debugging messages. - */ +// The error messages that can be shared are included here. +// Excluded are errors that are only used once and debugging messages. EXTERN char_u e_abort[] INIT(= N_("E470: Command aborted")); EXTERN char_u e_afterinit[] INIT(= N_( "E905: Cannot set this option after startup")); @@ -992,6 +936,14 @@ EXTERN char_u e_re_damg[] INIT(= N_("E43: Damaged match string")); EXTERN char_u e_re_corr[] INIT(= N_("E44: Corrupted regexp program")); EXTERN char_u e_readonly[] INIT(= N_( "E45: 'readonly' option is set (add ! to override)")); +EXTERN char_u e_readonlyvar[] INIT(= N_( + "E46: Cannot change read-only variable \"%.*s\"")); +EXTERN char_u e_dictreq[] INIT(= N_("E715: Dictionary required")); +EXTERN char_u e_toomanyarg[] INIT(= N_("E118: Too many arguments for function: %s")); +EXTERN char_u e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: %s")); +EXTERN char_u e_listreq[] INIT(= N_("E714: List required")); +EXTERN char_u e_listdictarg[] INIT(= N_( + "E712: Argument of %s must be a List or Dictionary")); EXTERN char_u e_readerrf[] INIT(= N_("E47: Error while reading errorfile")); EXTERN char_u e_sandbox[] INIT(= N_("E48: Not allowed in sandbox")); EXTERN char_u e_secure[] INIT(= N_("E523: Not allowed here")); @@ -1065,7 +1017,7 @@ EXTERN char line_msg[] INIT(= N_(" line ")); // For undo we need to know the lowest time possible. EXTERN time_t starttime; -EXTERN FILE *time_fd INIT(= NULL); /* where to write startup timing */ +EXTERN FILE *time_fd INIT(= NULL); // where to write startup timing // Some compilers warn for not using a return value, but in some situations we // can't do anything useful with the value. Assign to this variable to avoid @@ -1101,4 +1053,4 @@ typedef enum { #define MIN_CD_SCOPE kCdScopeWindow #define MAX_CD_SCOPE kCdScopeGlobal -#endif /* NVIM_GLOBALS_H */ +#endif // NVIM_GLOBALS_H diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h index 9e588d0387..c6687c8da9 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -18,7 +18,7 @@ typedef int16_t sattr_T; /// chars[] contains the UTF-8 text that is currently displayed on the grid. /// It is stored as a single block of cells. When redrawing a part of the grid, /// the new state can be compared with the existing state of the grid. This way -/// we can avoid sending bigger updates than neccessary to the Ul layer. +/// we can avoid sending bigger updates than necessary to the Ul layer. /// /// Screen cells are stored as NUL-terminated UTF-8 strings, and a cell can /// contain up to MAX_MCO composing characters after the base character. diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c index cb5c5338a1..f1f84e63be 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -162,21 +162,21 @@ static option_table_T mbfont_opts[OPT_MBFONT_NUM_OPTIONS] = * These values determine the print position on a page. */ typedef struct { - int lead_spaces; /* remaining spaces for a TAB */ - int print_pos; /* virtual column for computing TABs */ - colnr_T column; /* byte column */ - linenr_T file_line; /* line nr in the buffer */ - size_t bytes_printed; /* bytes printed so far */ - int ff; /* seen form feed character */ + int lead_spaces; // remaining spaces for a TAB + int print_pos; // virtual column for computing TABs + colnr_T column; // byte column + linenr_T file_line; // line nr in the buffer + size_t bytes_printed; // bytes printed so far + int ff; // seen form feed character } prt_pos_T; struct prt_mediasize_S { char *name; - double width; /* width and height in points for portrait */ + double width; // width and height in points for portrait double height; }; -/* PS font names, must be in Roman, Bold, Italic, Bold-Italic order */ +// PS font names, must be in Roman, Bold, Italic, Bold-Italic order struct prt_ps_font_S { int wx; int uline_offset; @@ -200,7 +200,7 @@ struct prt_ps_charset_S { int has_charset; }; -/* Collections of encodings and charsets for multi-byte printing */ +// Collections of encodings and charsets for multi-byte printing struct prt_ps_mbfont_S { int num_encodings; struct prt_ps_encoding_S *encodings; @@ -361,9 +361,10 @@ static uint32_t darken_rgb(uint32_t rgb) static uint32_t prt_get_term_color(int colorindex) { - /* TODO: Should check for xterm with 88 or 256 colors. */ - if (t_colors > 8) + // TODO(vim): Should check for xterm with 88 or 256 colors. + if (t_colors > 8) { return cterm_color_16[colorindex % 16]; + } return cterm_color_8[colorindex % 8]; } @@ -535,7 +536,7 @@ static void prt_header(prt_settings_T *const psettings, const int pagenum, p_header, use_sandbox, ' ', width, NULL, NULL); - /* Reset line numbers */ + // Reset line numbers curwin->w_cursor.lnum = tmp_lnum; curwin->w_topline = tmp_topline; curwin->w_botline = tmp_botline; @@ -602,7 +603,7 @@ void ex_hardcopy(exarg_T *eap) if (*eap->arg == '>') { char_u *errormsg = NULL; - /* Expand things like "%.ps". */ + // Expand things like "%.ps". if (expand_filename(eap, eap->cmdlinep, &errormsg) == FAIL) { if (errormsg != NULL) EMSG(errormsg); @@ -666,7 +667,7 @@ void ex_hardcopy(exarg_T *eap) goto print_fail_no_begin; } - /* Set colors and font to normal. */ + // Set colors and font to normal. curr_bg = 0xffffffff; curr_fg = 0xffffffff; curr_italic = kNone; @@ -691,8 +692,8 @@ void ex_hardcopy(exarg_T *eap) for (collated_copies = 0; collated_copies < settings.n_collated_copies; collated_copies++) { - prt_pos_T prtpos; /* current print position */ - prt_pos_T page_prtpos; /* print position at page start */ + prt_pos_T prtpos; // current print position + prt_pos_T page_prtpos; // print position at page start int side; memset(&page_prtpos, 0, sizeof(prt_pos_T)); @@ -700,7 +701,7 @@ void ex_hardcopy(exarg_T *eap) prtpos = page_prtpos; if (jobsplit && collated_copies > 0) { - /* Splitting jobs: Stop a previous job and start a new one. */ + // Splitting jobs: Stop a previous job and start a new one. mch_print_end(&settings); if (!mch_print_begin(&settings)) goto print_fail_no_begin; @@ -717,7 +718,7 @@ void ex_hardcopy(exarg_T *eap) for (uncollated_copies = 0; uncollated_copies < settings.n_uncollated_copies; uncollated_copies++) { - /* Set the print position to the start of this page. */ + // Set the print position to the start of this page. prtpos = page_prtpos; /* @@ -728,7 +729,7 @@ void ex_hardcopy(exarg_T *eap) * Print one page. */ - /* Check for interrupt character every page. */ + // Check for interrupt character every page. os_breakcheck(); if (got_int || settings.user_abort) goto print_fail; @@ -759,11 +760,12 @@ void ex_hardcopy(exarg_T *eap) prtpos.column = hardcopy_line(&settings, page_line, &prtpos); if (prtpos.column == 0) { - /* finished a file line */ + // finished a file line prtpos.bytes_printed += STRLEN(skipwhite(ml_get(prtpos.file_line))); - if (++prtpos.file_line > eap->line2) - break; /* reached the end */ + if (++prtpos.file_line > eap->line2) { + break; // reached the end + } } else if (prtpos.ff) { /* Line had a formfeed in it - start new page but * stay on the current line */ @@ -771,10 +773,12 @@ void ex_hardcopy(exarg_T *eap) } } - if (!mch_print_end_page()) + if (!mch_print_end_page()) { goto print_fail; - if (prtpos.file_line > eap->line2) - break; /* reached the end */ + } + if (prtpos.file_line > eap->line2) { + break; // reached the end + } } /* @@ -791,7 +795,7 @@ void ex_hardcopy(exarg_T *eap) if (settings.duplex && prtpos.file_line <= eap->line2) ++page_count; - /* Remember the position where the next page starts. */ + // Remember the position where the next page starts. page_prtpos = prtpos; } @@ -855,7 +859,7 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T id = syn_get_final_id(id); else id = 0; - /* Get the line again, a multi-line regexp may invalidate it. */ + // Get the line again, a multi-line regexp may invalidate it. line = ml_get(ppos->file_line); if (id != current_syn_id) { @@ -881,9 +885,10 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T if (need_break) break; } - /* Keep the TAB if we didn't finish it. */ - if (need_break && tab_spaces > 0) + // Keep the TAB if we didn't finish it. + if (need_break && tab_spaces > 0) { break; + } } else if (line[col] == FF && printer_opts[OPT_PRINT_FORMFEED].present && TOLOWER_ASC(printer_opts[OPT_PRINT_FORMFEED].string[0]) @@ -942,7 +947,7 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T * http://www.adobe.com */ -#define PRT_PS_DEFAULT_DPI (72) /* Default user space resolution */ +#define PRT_PS_DEFAULT_DPI (72) // Default user space resolution #define PRT_PS_DEFAULT_FONTSIZE (10) #define PRT_PS_DEFAULT_BUFFER_SIZE (80) @@ -951,20 +956,20 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T static struct prt_mediasize_S prt_mediasize[] = { - {"A4", 595.0, 842.0}, - {"letter", 612.0, 792.0}, - {"10x14", 720.0, 1008.0}, - {"A3", 842.0, 1191.0}, - {"A5", 420.0, 595.0}, - {"B4", 729.0, 1032.0}, - {"B5", 516.0, 729.0}, - {"executive", 522.0, 756.0}, - {"folio", 595.0, 935.0}, - {"ledger", 1224.0, 792.0}, /* Yes, it is wider than taller! */ - {"legal", 612.0, 1008.0}, - {"quarto", 610.0, 780.0}, - {"statement", 396.0, 612.0}, - {"tabloid", 792.0, 1224.0} + { "A4", 595.0, 842.0 }, + { "letter", 612.0, 792.0 }, + { "10x14", 720.0, 1008.0 }, + { "A3", 842.0, 1191.0 }, + { "A5", 420.0, 595.0 }, + { "B4", 729.0, 1032.0 }, + { "B5", 516.0, 729.0 }, + { "executive", 522.0, 756.0 }, + { "folio", 595.0, 935.0 }, + { "ledger", 1224.0, 792.0 }, // Yes, it is wider than taller! + { "legal", 612.0, 1008.0 }, + { "quarto", 610.0, 780.0 }, + { "statement", 396.0, 612.0 }, + { "tabloid", 792.0, 1224.0 } }; #define PRT_PS_FONT_ROMAN (0) @@ -972,7 +977,7 @@ static struct prt_mediasize_S prt_mediasize[] = #define PRT_PS_FONT_OBLIQUE (2) #define PRT_PS_FONT_BOLDOBLIQUE (3) -/* Standard font metrics for Courier family */ +// Standard font metrics for Courier family static struct prt_ps_font_S prt_ps_courier_font = { 600, @@ -981,7 +986,7 @@ static struct prt_ps_font_S prt_ps_courier_font = {"Courier", "Courier-Bold", "Courier-Oblique", "Courier-BoldOblique"} }; -/* Generic font metrics for multi-byte fonts */ +// Generic font metrics for multi-byte fonts static struct prt_ps_font_S prt_ps_mb_font = { 1000, @@ -990,9 +995,8 @@ static struct prt_ps_font_S prt_ps_mb_font = {NULL, NULL, NULL, NULL} }; -/* Pointer to current font set being used */ -static struct prt_ps_font_S* prt_ps_font; - +// Pointer to current font set being used +static struct prt_ps_font_S *prt_ps_font; #define CS_JIS_C_1978 (0x01) #define CS_JIS_X_1983 (0x02) @@ -1003,7 +1007,7 @@ static struct prt_ps_font_S* prt_ps_font; #define CS_KANJITALK6 (0x40) #define CS_KANJITALK7 (0x80) -/* Japanese encodings and charsets */ +// Japanese encodings and charsets static struct prt_ps_encoding_S j_encodings[] = { {"iso-2022-jp", NULL, (CS_JIS_C_1978|CS_JIS_X_1983|CS_JIS_X_1990| @@ -1035,7 +1039,7 @@ static struct prt_ps_charset_S j_charsets[] = #define CS_GBK (0x20) #define CS_SC_ISO10646 (0x40) -/* Simplified Chinese encodings and charsets */ +// Simplified Chinese encodings and charsets static struct prt_ps_encoding_S sc_encodings[] = { {"iso-2022", NULL, (CS_GB_2312_80|CS_GBT_12345_90)}, @@ -1071,7 +1075,7 @@ static struct prt_ps_charset_S sc_charsets[] = #define CS_DLHKS (0x800) #define CS_TC_ISO10646 (0x1000) -/* Traditional Chinese encodings and charsets */ +// Traditional Chinese encodings and charsets static struct prt_ps_encoding_S tc_encodings[] = { {"iso-2022", NULL, (CS_CNS_PLANE_1|CS_CNS_PLANE_2)}, @@ -1108,7 +1112,7 @@ static struct prt_ps_charset_S tc_charsets[] = #define CS_KR_X_1992_MS (0x04) #define CS_KR_ISO10646 (0x08) -/* Korean encodings and charsets */ +// Korean encodings and charsets static struct prt_ps_encoding_S k_encodings[] = { {"iso-2022-kr", NULL, CS_KR_X_1992}, @@ -1167,7 +1171,7 @@ static struct prt_ps_mbfont_S prt_ps_mbfonts[] = } }; -/* Types of PS resource file currently used */ +// Types of PS resource file currently used #define PRT_RESOURCE_TYPE_PROCSET (0) #define PRT_RESOURCE_TYPE_ENCODING (1) #define PRT_RESOURCE_TYPE_CMAP (2) @@ -1195,7 +1199,7 @@ static char *prt_resource_types[] = "cmap" }; -/* Strings to look for in a PS resource file */ +// Strings to look for in a PS resource file #define PRT_RESOURCE_HEADER "%!PS-Adobe-" #define PRT_RESOURCE_RESOURCE "Resource-" #define PRT_RESOURCE_PROCSET "ProcSet" @@ -1255,20 +1259,20 @@ static double prt_pos_y_moveto = 0.0; * Various control variables used to decide when and how to change the * PostScript graphics state. */ -static int prt_need_moveto; -static int prt_do_moveto; -static int prt_need_font; +static bool prt_need_moveto; +static bool prt_do_moveto; +static bool prt_need_font; static int prt_font; -static int prt_need_underline; +static bool prt_need_underline; static TriState prt_underline; static TriState prt_do_underline; -static int prt_need_fgcol; +static bool prt_need_fgcol; static uint32_t prt_fgcol; -static int prt_need_bgcol; -static int prt_do_bgcol; +static bool prt_need_bgcol; +static bool prt_do_bgcol; static uint32_t prt_bgcol; static uint32_t prt_new_bgcol; -static int prt_attribute_change; +static bool prt_attribute_change; static double prt_text_run; static int prt_page_num; static int prt_bufsiz; @@ -1296,8 +1300,8 @@ static int prt_out_mbyte; static int prt_custom_cmap; static char prt_cmap[80]; static int prt_use_courier; -static int prt_in_ascii; -static int prt_half_width; +static bool prt_in_ascii; +static bool prt_half_width; static char *prt_ascii_encoding; static char_u prt_hexchar[] = "0123456789abcdef"; @@ -1416,18 +1420,19 @@ static void prt_write_real(double val, int prec) int fraction; prt_real_bits(val, prec, &integer, &fraction); - /* Emit integer part */ - sprintf((char *)prt_line_buffer, "%d", integer); + // Emit integer part + snprintf((char *)prt_line_buffer, sizeof(prt_line_buffer), "%d", integer); prt_write_file(prt_line_buffer); - /* Only emit fraction if necessary */ + // Only emit fraction if necessary if (fraction != 0) { - /* Remove any trailing zeros */ + // Remove any trailing zeros while ((fraction % 10) == 0) { prec--; fraction /= 10; } - /* Emit fraction left padded with zeros */ - sprintf((char *)prt_line_buffer, ".%0*d", prec, fraction); + // Emit fraction left padded with zeros + snprintf((char *)prt_line_buffer, sizeof(prt_line_buffer), ".%0*d", + prec, fraction); prt_write_file(prt_line_buffer); } sprintf((char *)prt_line_buffer, " "); @@ -1447,13 +1452,13 @@ static void prt_def_var(char *name, double value, int prec) prt_write_file(prt_line_buffer); } -/* Convert size from font space to user space at current font scale */ +// Convert size from font space to user space at current font scale #define PRT_PS_FONT_TO_USER(scale, size) ((size) * ((scale)/1000.0)) static void prt_flush_buffer(void) { if (!GA_EMPTY(&prt_ps_buffer)) { - /* Any background color must be drawn first */ + // Any background color must be drawn first if (prt_do_bgcol && (prt_new_bgcol != PRCOLOR_WHITE)) { unsigned int r, g, b; @@ -1461,14 +1466,14 @@ static void prt_flush_buffer(void) prt_write_real(prt_pos_x_moveto, 2); prt_write_real(prt_pos_y_moveto, 2); prt_write_string("m\n"); - prt_do_moveto = FALSE; + prt_do_moveto = false; } - /* Size of rect of background color on which text is printed */ + // Size of rect of background color on which text is printed prt_write_real(prt_text_run, 2); prt_write_real(prt_line_height, 2); - /* Lastly add the color of the background */ + // Lastly add the color of the background r = (prt_new_bgcol & 0xff0000) >> 16; g = (prt_new_bgcol & 0xff00) >> 8; b = prt_new_bgcol & 0xff; @@ -1485,10 +1490,10 @@ static void prt_flush_buffer(void) prt_write_real(prt_pos_x_moveto, 2); prt_write_real(prt_pos_y_moveto, 2); prt_write_string("m\n"); - prt_do_moveto = FALSE; + prt_do_moveto = false; } - /* Underline length of text run */ + // Underline length of text run prt_write_real(prt_text_run, 2); prt_write_string("ul\n"); } @@ -1503,16 +1508,16 @@ static void prt_flush_buffer(void) prt_write_string(">"); else prt_write_string(")"); - /* Add a moveto if need be and use the appropriate show procedure */ + // Add a moveto if need be and use the appropriate show procedure if (prt_do_moveto) { prt_write_real(prt_pos_x_moveto, 2); prt_write_real(prt_pos_y_moveto, 2); - /* moveto and a show */ + // moveto and a show prt_write_string("ms\n"); - prt_do_moveto = FALSE; - } else /* Simple show */ + prt_do_moveto = false; + } else { // Simple show prt_write_string("s\n"); - + } ga_clear(&prt_ps_buffer); ga_init(&prt_ps_buffer, (int)sizeof(char), prt_bufsiz); } @@ -1536,7 +1541,7 @@ static int prt_find_resource(char *name, struct prt_ps_resource_S *resource) buffer = xmallocz(MAXPATHL); STRLCPY(resource->name, name, 64); - /* Look for named resource file in runtimepath */ + // Look for named resource file in runtimepath STRCPY(buffer, "print"); add_pathsep((char *)buffer); xstrlcat((char *)buffer, name, MAXPATHL); @@ -1548,7 +1553,7 @@ static int prt_find_resource(char *name, struct prt_ps_resource_S *resource) return retval; } -/* PS CR and LF characters have platform independent values */ +// PS CR and LF characters have platform independent values #define PSLF (0x0a) #define PSCR (0x0d) @@ -1558,7 +1563,7 @@ static int prt_resfile_next_line(void) { int idx; - /* Move to start of next line and then find end of line */ + // Move to start of next line and then find end of line idx = prt_resfile.line_end + 1; while (idx < prt_resfile.len) { if (prt_resfile.buffer[idx] != PSLF && prt_resfile.buffer[idx] != PSCR) @@ -1577,12 +1582,13 @@ static int prt_resfile_next_line(void) return idx < prt_resfile.len; } -static int prt_resfile_strncmp(int offset, char *string, int len) +static int prt_resfile_strncmp(int offset, const char *string, int len) + FUNC_ATTR_NONNULL_ALL { - /* Force not equal if string is longer than remainder of line */ - if (len > (prt_resfile.line_end - (prt_resfile.line_start + offset))) + // Force not equal if string is longer than remainder of line + if (len > (prt_resfile.line_end - (prt_resfile.line_start + offset))) { return 1; - + } return STRNCMP(&prt_resfile.buffer[prt_resfile.line_start + offset], string, len); } @@ -1615,178 +1621,182 @@ static int prt_resfile_skip_ws(int offset) /* prt_next_dsc() - returns detail on next DSC comment line found. Returns true * if a DSC comment is found, else false */ -static int prt_next_dsc(struct prt_dsc_line_S *p_dsc_line) +static bool prt_next_dsc(struct prt_dsc_line_S *p_dsc_line) + FUNC_ATTR_NONNULL_ALL { int comment; int offset; - /* Move to start of next line */ - if (!prt_resfile_next_line()) - return FALSE; - - /* DSC comments always start %% */ - if (prt_resfile_strncmp(0, "%%", 2) != 0) - return FALSE; - - /* Find type of DSC comment */ - for (comment = 0; comment < (int)ARRAY_SIZE(prt_dsc_table); comment++) + // Move to start of next line + if (!prt_resfile_next_line()) { + return false; + } + // DSC comments always start %% + if (prt_resfile_strncmp(0, "%%", 2) != 0) { + return false; + } + // Find type of DSC comment + for (comment = 0; comment < (int)ARRAY_SIZE(prt_dsc_table); comment++) { if (prt_resfile_strncmp(0, prt_dsc_table[comment].string, - prt_dsc_table[comment].len) == 0) + prt_dsc_table[comment].len) == 0) { break; - + } + } if (comment != ARRAY_SIZE(prt_dsc_table)) { - /* Return type of comment */ + // Return type of comment p_dsc_line->type = prt_dsc_table[comment].type; offset = prt_dsc_table[comment].len; } else { - /* Unrecognised DSC comment, skip to ws after comment leader */ + // Unrecognised DSC comment, skip to ws after comment leader p_dsc_line->type = PRT_DSC_MISC_TYPE; offset = prt_resfile_skip_nonws(0); - if (offset == -1) - return FALSE; + if (offset == -1) { + return false; + } } - /* Skip ws to comment value */ + // Skip ws to comment value offset = prt_resfile_skip_ws(offset); - if (offset == -1) - return FALSE; - + if (offset == -1) { + return false; + } p_dsc_line->string = &prt_resfile.buffer[prt_resfile.line_start + offset]; p_dsc_line->len = prt_resfile.line_end - (prt_resfile.line_start + offset); - return TRUE; + return true; } /* Improved hand crafted parser to get the type, title, and version number of a * PS resource file so the file details can be added to the DSC header comments. */ -static int prt_open_resource(struct prt_ps_resource_S *resource) +static bool prt_open_resource(struct prt_ps_resource_S *resource) + FUNC_ATTR_NONNULL_ALL { - int offset; - int seen_all; - int seen_title; - int seen_version; - FILE *fd_resource; struct prt_dsc_line_S dsc_line; - fd_resource = os_fopen((char *)resource->filename, READBIN); + FILE *fd_resource = os_fopen((char *)resource->filename, READBIN); if (fd_resource == NULL) { EMSG2(_("E624: Can't open file \"%s\""), resource->filename); - return FALSE; + return false; } memset(prt_resfile.buffer, NUL, PRT_FILE_BUFFER_LEN); - /* Parse first line to ensure valid resource file */ + // Parse first line to ensure valid resource file prt_resfile.len = (int)fread((char *)prt_resfile.buffer, sizeof(char_u), PRT_FILE_BUFFER_LEN, fd_resource); if (ferror(fd_resource)) { EMSG2(_("E457: Can't read PostScript resource file \"%s\""), resource->filename); fclose(fd_resource); - return FALSE; + return false; } fclose(fd_resource); prt_resfile.line_end = -1; prt_resfile.line_start = 0; - if (!prt_resfile_next_line()) - return FALSE; - - offset = 0; + if (!prt_resfile_next_line()) { + return false; + } + int offset = 0; if (prt_resfile_strncmp(offset, PRT_RESOURCE_HEADER, (int)STRLEN(PRT_RESOURCE_HEADER)) != 0) { EMSG2(_("E618: file \"%s\" is not a PostScript resource file"), - resource->filename); - return FALSE; + resource->filename); + return false; } - /* Skip over any version numbers and following ws */ + // Skip over any version numbers and following ws offset += (int)STRLEN(PRT_RESOURCE_HEADER); offset = prt_resfile_skip_nonws(offset); - if (offset == -1) - return FALSE; + if (offset == -1) { + return false; + } offset = prt_resfile_skip_ws(offset); - if (offset == -1) - return FALSE; - + if (offset == -1) { + return false; + } if (prt_resfile_strncmp(offset, PRT_RESOURCE_RESOURCE, (int)STRLEN(PRT_RESOURCE_RESOURCE)) != 0) { EMSG2(_("E619: file \"%s\" is not a supported PostScript resource file"), - resource->filename); - return FALSE; + resource->filename); + return false; } offset += (int)STRLEN(PRT_RESOURCE_RESOURCE); - /* Decide type of resource in the file */ + // Decide type of resource in the file if (prt_resfile_strncmp(offset, PRT_RESOURCE_PROCSET, - (int)STRLEN(PRT_RESOURCE_PROCSET)) == 0) + (int)STRLEN(PRT_RESOURCE_PROCSET)) == 0) { resource->type = PRT_RESOURCE_TYPE_PROCSET; - else if (prt_resfile_strncmp(offset, PRT_RESOURCE_ENCODING, - (int)STRLEN(PRT_RESOURCE_ENCODING)) == 0) + } else if (prt_resfile_strncmp(offset, PRT_RESOURCE_ENCODING, + (int)STRLEN(PRT_RESOURCE_ENCODING)) == 0) { resource->type = PRT_RESOURCE_TYPE_ENCODING; - else if (prt_resfile_strncmp(offset, PRT_RESOURCE_CMAP, - (int)STRLEN(PRT_RESOURCE_CMAP)) == 0) + } else if (prt_resfile_strncmp(offset, PRT_RESOURCE_CMAP, + (int)STRLEN(PRT_RESOURCE_CMAP)) == 0) { resource->type = PRT_RESOURCE_TYPE_CMAP; - else { + } else { EMSG2(_("E619: file \"%s\" is not a supported PostScript resource file"), - resource->filename); - return FALSE; + resource->filename); + return false; } - /* Look for title and version of resource */ + // Look for title and version of resource resource->title[0] = '\0'; resource->version[0] = '\0'; - seen_title = FALSE; - seen_version = FALSE; - seen_all = FALSE; + bool seen_title = false; + bool seen_version = false; + bool seen_all = false; while (!seen_all && prt_next_dsc(&dsc_line)) { switch (dsc_line.type) { case PRT_DSC_TITLE_TYPE: STRLCPY(resource->title, dsc_line.string, dsc_line.len + 1); - seen_title = TRUE; - if (seen_version) - seen_all = TRUE; + seen_title = true; + if (seen_version) { + seen_all = true; + } break; case PRT_DSC_VERSION_TYPE: STRLCPY(resource->version, dsc_line.string, dsc_line.len + 1); - seen_version = TRUE; - if (seen_title) - seen_all = TRUE; + seen_version = true; + if (seen_title) { + seen_all = true; + } break; case PRT_DSC_ENDCOMMENTS_TYPE: - /* Wont find title or resource after this comment, stop searching */ - seen_all = TRUE; + // Wont find title or resource after this comment, stop searching + seen_all = true; break; case PRT_DSC_MISC_TYPE: - /* Not interested in whatever comment this line had */ + // Not interested in whatever comment this line had break; } } if (!seen_title || !seen_version) { EMSG2(_("E619: file \"%s\" is not a supported PostScript resource file"), - resource->filename); - return FALSE; + resource->filename); + return false; } - return TRUE; + return true; } -static int prt_check_resource(struct prt_ps_resource_S *resource, char_u *version) +static bool prt_check_resource(const struct prt_ps_resource_S *resource, + const char_u *version) + FUNC_ATTR_NONNULL_ALL { - /* Version number m.n should match, the revision number does not matter */ + // Version number m.n should match, the revision number does not matter if (STRNCMP(resource->version, version, STRLEN(version))) { EMSG2(_("E621: \"%s\" resource file has wrong version"), - resource->name); - return FALSE; + resource->name); + return false; } - /* Other checks to be added as needed */ - return TRUE; + // Other checks to be added as needed + return true; } static void prt_dsc_start(void) @@ -1810,7 +1820,7 @@ static void prt_dsc_textline(char *comment, char *text) static void prt_dsc_text(char *comment, char *text) { - /* TODO - should scan 'text' for any chars needing escaping! */ + // TODO(vim): - should scan 'text' for any chars needing escaping! vim_snprintf((char *)prt_line_buffer, sizeof(prt_line_buffer), "%%%%%s: (%s)\n", comment, text); prt_write_file(prt_line_buffer); @@ -1834,9 +1844,8 @@ static void prt_dsc_ints(char *comment, int count, int *ints) prt_write_string("\n"); } -static void -prt_dsc_resources ( - char *comment, /* if NULL add to previous */ +static void prt_dsc_resources( + char *comment, // if NULL add to previous char *type, char *string ) @@ -1887,8 +1896,9 @@ static void prt_dsc_requirements(int duplex, int tumble, int collate, int color, prt_write_string(" color"); if (num_copies > 1) { prt_write_string(" numcopies("); - /* Note: no space wanted so don't use prt_write_int() */ - sprintf((char *)prt_line_buffer, "%d", num_copies); + // Note: no space wanted so don't use prt_write_int() + snprintf((char *)prt_line_buffer, sizeof(prt_line_buffer), "%d", + num_copies); prt_write_file(prt_line_buffer); prt_write_string(")"); } @@ -2038,13 +2048,13 @@ static int prt_get_lpp(void) prt_ps_font->bbox_min_y)) / 2); } - /* Get height for topmost line based on background rect offset. */ + // Get height for topmost line based on background rect offset. prt_first_line_height = prt_line_height + prt_bgcol_offset; - /* Calculate lpp */ + // Calculate lpp lpp = (int)((prt_top_margin - prt_bottom_margin) / prt_line_height); - /* Adjust top margin if there is a header */ + // Adjust top margin if there is a header prt_top_margin -= prt_line_height * prt_header_height(); return lpp - prt_header_height(); @@ -2057,7 +2067,7 @@ static int prt_match_encoding(char *p_encoding, struct prt_ps_mbfont_S *p_cmap, struct prt_ps_encoding_S *p_mbenc; *pp_mbenc = NULL; - /* Look for recognised encoding */ + // Look for recognised encoding enc_len = (int)STRLEN(p_encoding); p_mbenc = p_cmap->encodings; for (mbenc = 0; mbenc < p_cmap->num_encodings; mbenc++) { @@ -2076,9 +2086,10 @@ static int prt_match_charset(char *p_charset, struct prt_ps_mbfont_S *p_cmap, st int char_len; struct prt_ps_charset_S *p_mbchar; - /* Look for recognised character set, using default if one is not given */ - if (*p_charset == NUL) + // Look for recognised character set, using default if one is not given + if (*p_charset == NUL) { p_charset = p_cmap->defcs; + } char_len = (int)STRLEN(p_charset); p_mbchar = p_cmap->charsets; for (mbchar = 0; mbchar < p_cmap->num_charsets; mbchar++) { @@ -2133,7 +2144,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) break; } - /* Use first encoding matched if no charset matched */ + // Use first encoding matched if no charset matched if (p_mbenc_first != NULL && p_mbchar == NULL) { p_mbenc = p_mbenc_first; cmap = effective_cmap; @@ -2144,24 +2155,24 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) prt_out_mbyte = (p_mbenc != NULL); if (prt_out_mbyte) { - /* Build CMap name - will be same for all multi-byte fonts used */ + // Build CMap name - will be same for all multi-byte fonts used prt_cmap[0] = NUL; prt_custom_cmap = (p_mbchar == NULL); if (!prt_custom_cmap) { - /* Check encoding and character set are compatible */ + // Check encoding and character set are compatible if ((p_mbenc->needs_charset & p_mbchar->has_charset) == 0) { EMSG(_("E673: Incompatible multi-byte encoding and character set.")); return FALSE; } - /* Add charset name if not empty */ + // Add charset name if not empty if (p_mbchar->cmap_charset != NULL) { STRLCPY(prt_cmap, p_mbchar->cmap_charset, sizeof(prt_cmap) - 2); STRCAT(prt_cmap, "-"); } } else { - /* Add custom CMap character set name */ + // Add custom CMap character set name if (*p_pmcs == NUL) { EMSG(_("E674: printmbcharset cannot be empty with multi-byte encoding.")); return FALSE; @@ -2170,7 +2181,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) STRCAT(prt_cmap, "-"); } - /* CMap name ends with (optional) encoding name and -H for horizontal */ + // CMap name ends with (optional) encoding name and -H for horizontal if (p_mbenc->cmap_encoding != NULL && STRLEN(prt_cmap) + STRLEN(p_mbenc->cmap_encoding) + 3 < sizeof(prt_cmap)) { STRCAT(prt_cmap, p_mbenc->cmap_encoding); @@ -2183,7 +2194,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) return FALSE; } - /* Derive CID font names with fallbacks if not defined */ + // Derive CID font names with fallbacks if not defined prt_build_cid_fontname(PRT_PS_FONT_ROMAN, mbfont_opts[OPT_MBFONT_REGULAR].string, mbfont_opts[OPT_MBFONT_REGULAR].strlen); @@ -2288,9 +2299,10 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) psettings->chars_per_line = prt_get_cpl(); psettings->lines_per_page = prt_get_lpp(); - /* Catch margin settings that leave no space for output! */ - if (psettings->chars_per_line <= 0 || psettings->lines_per_page <= 0) + // Catch margin settings that leave no space for output! + if (psettings->chars_per_line <= 0 || psettings->lines_per_page <= 0) { return FAIL; + } /* * Sort out the number of copies to be printed. PS by default will do @@ -2329,10 +2341,10 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) prt_tumble = TRUE; } - /* For now user abort not supported */ + // For now user abort not supported psettings->user_abort = 0; - /* If the user didn't specify a file name, use a temp file. */ + // If the user didn't specify a file name, use a temp file. if (psettings->outfile == NULL) { prt_ps_file_name = vim_tempname(); if (prt_ps_file_name == NULL) { @@ -2360,12 +2372,12 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) prt_page_num = 0; - prt_attribute_change = FALSE; - prt_need_moveto = FALSE; - prt_need_font = FALSE; - prt_need_fgcol = FALSE; - prt_need_bgcol = FALSE; - prt_need_underline = FALSE; + prt_attribute_change = false; + prt_need_moveto = false; + prt_need_font = false; + prt_need_fgcol = false; + prt_need_bgcol = false; + prt_need_underline = false; prt_file_error = FALSE; @@ -2416,9 +2428,7 @@ static int prt_add_resource(struct prt_ps_resource_S *resource) int mch_print_begin(prt_settings_T *psettings) { - time_t now; int bbox[4]; - char *p_time; double left; double right; double top; @@ -2442,10 +2452,10 @@ int mch_print_begin(prt_settings_T *psettings) } prt_dsc_textline("For", buffer); prt_dsc_textline("Creator", longVersion); - /* Note: to ensure Clean8bit I don't think we can use LC_TIME */ - now = time(NULL); - p_time = ctime(&now); - /* Note: ctime() adds a \n so we have to remove it :-( */ + // Note: to ensure Clean8bit I don't think we can use LC_TIME + char ctime_buf[50]; + char *p_time = os_ctime(ctime_buf, sizeof(ctime_buf)); + // Note: os_ctime() adds a \n so we have to remove it :-( p = vim_strchr((char_u *)p_time, '\n'); if (p != NULL) *p = NUL; @@ -2483,14 +2493,15 @@ int mch_print_begin(prt_settings_T *psettings) + 0.5); } prt_dsc_ints("BoundingBox", 4, bbox); - /* The media width and height does not change with landscape printing! */ + // The media width and height does not change with landscape printing! prt_dsc_docmedia(prt_mediasize[prt_media].name, - prt_mediasize[prt_media].width, - prt_mediasize[prt_media].height, - (double)0, NULL, NULL); - /* Define fonts needed */ - if (!prt_out_mbyte || prt_use_courier) + prt_mediasize[prt_media].width, + prt_mediasize[prt_media].height, + (double)0, NULL, NULL); + // Define fonts needed + if (!prt_out_mbyte || prt_use_courier) { prt_dsc_font_resource("DocumentNeededResources", &prt_ps_courier_font); + } if (prt_out_mbyte) { prt_dsc_font_resource((prt_use_courier ? NULL : "DocumentNeededResources"), &prt_ps_mb_font); @@ -2498,7 +2509,7 @@ int mch_print_begin(prt_settings_T *psettings) prt_dsc_resources(NULL, "cmap", prt_cmap); } - /* Search for external resources VIM supplies */ + // Search for external resources VIM supplies if (!prt_find_resource("prolog", &res_prolog)) { EMSG(_("E456: Can't find PostScript resource file \"prolog.ps\"")); return FALSE; @@ -2508,7 +2519,7 @@ int mch_print_begin(prt_settings_T *psettings) if (!prt_check_resource(&res_prolog, PRT_PROLOG_VERSION)) return FALSE; if (prt_out_mbyte) { - /* Look for required version of multi-byte printing procset */ + // Look for required version of multi-byte printing procset if (!prt_find_resource("cidfont", &res_cidfont)) { EMSG(_("E456: Can't find PostScript resource file \"cidfont.ps\"")); return FALSE; @@ -2528,15 +2539,15 @@ int mch_print_begin(prt_settings_T *psettings) p_encoding = enc_skip(p_penc); if (*p_encoding == NUL || !prt_find_resource((char *)p_encoding, &res_encoding)) { - /* 'printencoding' not set or not supported - find alternate */ + // 'printencoding' not set or not supported - find alternate int props; p_encoding = enc_skip(p_enc); props = enc_canon_props(p_encoding); if (!(props & ENC_8BIT) || !prt_find_resource((char *)p_encoding, &res_encoding)) { - /* 8-bit 'encoding' is not supported */ - /* Use latin1 as default printing encoding */ + // 8-bit 'encoding' is not supported + // Use latin1 as default printing encoding p_encoding = (char_u *)"latin1"; if (!prt_find_resource((char *)p_encoding, &res_encoding)) { EMSG2(_("E456: Can't find PostScript resource file \"%s.ps\""), @@ -2554,7 +2565,7 @@ int mch_print_begin(prt_settings_T *psettings) if (*p_encoding == NUL) p_encoding = enc_skip(p_enc); if (prt_use_courier) { - /* Include ASCII range encoding vector */ + // Include ASCII range encoding vector if (!prt_find_resource(prt_ascii_encoding, &res_encoding)) { EMSG2(_("E456: Can't find PostScript resource file \"%s.ps\""), prt_ascii_encoding); @@ -2579,7 +2590,7 @@ int mch_print_begin(prt_settings_T *psettings) prt_do_conv = prt_conv.vc_type != CONV_NONE; if (prt_out_mbyte && prt_custom_cmap) { - /* Find user supplied CMap */ + // Find user supplied CMap if (!prt_find_resource(prt_cmap, &res_cmap)) { EMSG2(_("E456: Can't find PostScript resource file \"%s.ps\""), prt_cmap); @@ -2589,7 +2600,7 @@ int mch_print_begin(prt_settings_T *psettings) return FALSE; } - /* List resources supplied */ + // List resources supplied STRCPY(buffer, res_prolog.title); STRCAT(buffer, " "); STRCAT(buffer, res_prolog.version); @@ -2623,9 +2634,10 @@ int mch_print_begin(prt_settings_T *psettings) */ prt_dsc_noarg("BeginDefaults"); - /* List font resources most likely common to all pages */ - if (!prt_out_mbyte || prt_use_courier) + // List font resources most likely common to all pages + if (!prt_out_mbyte || prt_use_courier) { prt_dsc_font_resource("PageResources", &prt_ps_courier_font); + } if (prt_out_mbyte) { prt_dsc_font_resource((prt_use_courier ? NULL : "PageResources"), &prt_ps_mb_font); @@ -2633,7 +2645,7 @@ int mch_print_begin(prt_settings_T *psettings) prt_dsc_resources(NULL, "cmap", prt_cmap); } - /* Paper will be used for all pages */ + // Paper will be used for all pages prt_dsc_textline("PageMedia", prt_mediasize[prt_media].name); prt_dsc_noarg("EndDefaults"); @@ -2643,15 +2655,18 @@ int mch_print_begin(prt_settings_T *psettings) */ prt_dsc_noarg("BeginProlog"); - /* Add required procsets - NOTE: order is important! */ - if (!prt_add_resource(&res_prolog)) - return FALSE; + // Add required procsets - NOTE: order is important! + if (!prt_add_resource(&res_prolog)) { + return false; + } if (prt_out_mbyte) { - /* Add CID font procset, and any user supplied CMap */ - if (!prt_add_resource(&res_cidfont)) - return FALSE; - if (prt_custom_cmap && !prt_add_resource(&res_cmap)) - return FALSE; + // Add CID font procset, and any user supplied CMap + if (!prt_add_resource(&res_cidfont)) { + return false; + } + if (prt_custom_cmap && !prt_add_resource(&res_cmap)) { + return false; + } } if (!prt_out_mbyte || prt_use_courier) @@ -2667,7 +2682,7 @@ int mch_print_begin(prt_settings_T *psettings) */ prt_dsc_noarg("BeginSetup"); - /* Device setup - page size and number of uncollated copies */ + // Device setup - page size and number of uncollated copies prt_write_int((int)prt_mediasize[prt_media].width); prt_write_int((int)prt_mediasize[prt_media].height); prt_write_int(0); @@ -2680,7 +2695,7 @@ int mch_print_begin(prt_settings_T *psettings) prt_write_boolean(prt_collate); prt_write_string("c\n"); - /* Font resource inclusion and definition */ + // Font resource inclusion and definition if (!prt_out_mbyte || prt_use_courier) { /* When using Courier for ASCII range when printing multi-byte, need to * pick up ASCII encoding to use with it. */ @@ -2723,35 +2738,36 @@ int mch_print_begin(prt_settings_T *psettings) if (!prt_custom_cmap) prt_dsc_resources("IncludeResource", "cmap", prt_cmap); prt_def_cidfont("CF1", (int)prt_line_height, - prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLD]); - } else - /* Use ROMAN for BOLD */ + prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLD]); + } else { + // Use ROMAN for BOLD prt_dup_cidfont("CF0", "CF1"); - + } if (prt_ps_mb_font.ps_fontname[PRT_PS_FONT_OBLIQUE] != NULL) { prt_dsc_resources("IncludeResource", "font", prt_ps_mb_font.ps_fontname[PRT_PS_FONT_OBLIQUE]); if (!prt_custom_cmap) prt_dsc_resources("IncludeResource", "cmap", prt_cmap); prt_def_cidfont("CF2", (int)prt_line_height, - prt_ps_mb_font.ps_fontname[PRT_PS_FONT_OBLIQUE]); - } else - /* Use ROMAN for OBLIQUE */ + prt_ps_mb_font.ps_fontname[PRT_PS_FONT_OBLIQUE]); + } else { + // Use ROMAN for OBLIQUE prt_dup_cidfont("CF0", "CF2"); - + } if (prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE] != NULL) { prt_dsc_resources("IncludeResource", "font", prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE]); if (!prt_custom_cmap) prt_dsc_resources("IncludeResource", "cmap", prt_cmap); prt_def_cidfont("CF3", (int)prt_line_height, - prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE]); - } else - /* Use BOLD for BOLDOBLIQUE */ + prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE]); + } else { + // Use BOLD for BOLDOBLIQUE prt_dup_cidfont("CF1", "CF3"); + } } - /* Misc constant vars used for underlining and background rects */ + // Misc constant vars used for underlining and background rects prt_def_var("UO", PRT_PS_FONT_TO_USER(prt_line_height, prt_ps_font->uline_offset), 2); prt_def_var("UW", PRT_PS_FONT_TO_USER(prt_line_height, @@ -2760,7 +2776,7 @@ int mch_print_begin(prt_settings_T *psettings) prt_dsc_noarg("EndSetup"); - /* Fail if any problems writing out to the PS file */ + // Fail if any problems writing out to the PS file retval = !prt_file_error; return retval; @@ -2783,7 +2799,7 @@ void mch_print_end(prt_settings_T *psettings) if (!prt_file_error && psettings->outfile == NULL && !got_int && !psettings->user_abort) { - /* Close the file first. */ + // Close the file first. if (prt_ps_fd != NULL) { fclose(prt_ps_fd); prt_ps_fd = NULL; @@ -2834,7 +2850,7 @@ int mch_print_begin_page(char_u *str) prt_bgcol = PRCOLOR_WHITE; prt_font = PRT_PS_FONT_ROMAN; - /* Set up page transformation for landscape printing. */ + // Set up page transformation for landscape printing. if (!prt_portrait) { prt_write_int(-((int)prt_mediasize[prt_media].width)); prt_write_string("sl\n"); @@ -2842,7 +2858,7 @@ int mch_print_begin_page(char_u *str) prt_dsc_noarg("EndPageSetup"); - /* We have reset the font attributes, force setting them again. */ + // We have reset the font attributes, force setting them again. curr_bg = 0xffffffff; curr_fg = 0xffffffff; curr_bold = kNone; @@ -2868,9 +2884,9 @@ void mch_print_start_line(const bool margin, const int page_line) prt_pos_y = prt_top_margin - prt_first_line_height - page_line * prt_line_height; - prt_attribute_change = TRUE; - prt_need_moveto = TRUE; - prt_half_width = FALSE; + prt_attribute_change = true; + prt_need_moveto = true; + prt_half_width = false; } int mch_print_text_out(char_u *const textp, size_t len) @@ -2892,16 +2908,16 @@ int mch_print_text_out(char_u *const textp, size_t len) const bool in_ascii = (len == 1 && *p < 0x80); if (prt_in_ascii) { if (!in_ascii) { - /* No longer in ASCII range - need to switch font */ - prt_in_ascii = FALSE; - prt_need_font = TRUE; - prt_attribute_change = TRUE; + // No longer in ASCII range - need to switch font + prt_in_ascii = false; + prt_need_font = true; + prt_attribute_change = true; } } else if (in_ascii) { - /* Now in ASCII range - need to switch font */ - prt_in_ascii = TRUE; - prt_need_font = TRUE; - prt_attribute_change = TRUE; + // Now in ASCII range - need to switch font + prt_in_ascii = true; + prt_need_font = true; + prt_attribute_change = true; } } if (prt_out_mbyte) { @@ -2911,16 +2927,16 @@ int mch_print_text_out(char_u *const textp, size_t len) } if (prt_half_width) { if (!half_width) { - prt_half_width = FALSE; + prt_half_width = false; prt_pos_x += prt_char_width/4; - prt_need_moveto = TRUE; - prt_attribute_change = TRUE; + prt_need_moveto = true; + prt_attribute_change = true; } } else if (half_width) { - prt_half_width = TRUE; + prt_half_width = true; prt_pos_x += prt_char_width/4; - prt_need_moveto = TRUE; - prt_attribute_change = TRUE; + prt_need_moveto = true; + prt_attribute_change = true; } } @@ -2929,24 +2945,25 @@ int mch_print_text_out(char_u *const textp, size_t len) */ if (prt_attribute_change) { prt_flush_buffer(); - /* Reset count of number of chars that will be printed */ + // Reset count of number of chars that will be printed prt_text_run = 0; if (prt_need_moveto) { prt_pos_x_moveto = prt_pos_x; prt_pos_y_moveto = prt_pos_y; - prt_do_moveto = TRUE; + prt_do_moveto = true; - prt_need_moveto = FALSE; + prt_need_moveto = false; } if (prt_need_font) { - if (!prt_in_ascii) + if (!prt_in_ascii) { prt_write_string("CF"); - else + } else { prt_write_string("F"); + } prt_write_int(prt_font); prt_write_string("sf\n"); - prt_need_font = FALSE; + prt_need_font = false; } if (prt_need_fgcol) { unsigned int r, g, b; @@ -2962,22 +2979,24 @@ int mch_print_text_out(char_u *const textp, size_t len) prt_write_real(b / 255.0, 3); prt_write_string("r\n"); } - prt_need_fgcol = FALSE; + prt_need_fgcol = false; } if (prt_bgcol != PRCOLOR_WHITE) { prt_new_bgcol = prt_bgcol; - if (prt_need_bgcol) - prt_do_bgcol = TRUE; - } else - prt_do_bgcol = FALSE; - prt_need_bgcol = FALSE; + if (prt_need_bgcol) { + prt_do_bgcol = true; + } + } else { + prt_do_bgcol = false; + } + prt_need_bgcol = false; if (prt_need_underline) prt_do_underline = prt_underline; - prt_need_underline = FALSE; + prt_need_underline = false; - prt_attribute_change = FALSE; + prt_attribute_change = false; } if (prt_do_conv) { @@ -3062,29 +3081,29 @@ void mch_print_set_font(const TriState iBold, const TriState iItalic, if (font != prt_font) { prt_font = font; - prt_attribute_change = TRUE; - prt_need_font = TRUE; + prt_attribute_change = true; + prt_need_font = true; } if (prt_underline != iUnderline) { prt_underline = iUnderline; - prt_attribute_change = TRUE; - prt_need_underline = TRUE; + prt_attribute_change = true; + prt_need_underline = true; } } void mch_print_set_bg(uint32_t bgcol) { prt_bgcol = bgcol; - prt_attribute_change = TRUE; - prt_need_bgcol = TRUE; + prt_attribute_change = true; + prt_need_bgcol = true; } void mch_print_set_fg(uint32_t fgcol) { if (fgcol != prt_fgcol) { prt_fgcol = fgcol; - prt_attribute_change = TRUE; - prt_need_fgcol = TRUE; + prt_attribute_change = true; + prt_need_fgcol = true; } } diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 0f9984ec38..2af09f10cb 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -71,10 +71,10 @@ static void cs_usage_msg(csid_e x) static enum { - EXP_CSCOPE_SUBCMD, /* expand ":cscope" sub-commands */ - EXP_SCSCOPE_SUBCMD, /* expand ":scscope" sub-commands */ - EXP_CSCOPE_FIND, /* expand ":cscope find" arguments */ - EXP_CSCOPE_KILL /* expand ":cscope kill" arguments */ + EXP_CSCOPE_SUBCMD, // expand ":cscope" sub-commands + EXP_SCSCOPE_SUBCMD, // expand ":scscope" sub-commands + EXP_CSCOPE_FIND, // expand ":cscope find" arguments + EXP_CSCOPE_KILL // expand ":cscope kill" arguments } expand_what; /* @@ -87,13 +87,13 @@ char_u *get_cscope_name(expand_T *xp, int idx) switch (expand_what) { case EXP_CSCOPE_SUBCMD: - /* Complete with sub-commands of ":cscope": - * add, find, help, kill, reset, show */ + // Complete with sub-commands of ":cscope": + // add, find, help, kill, reset, show return (char_u *)cs_cmds[idx].name; case EXP_SCSCOPE_SUBCMD: { - /* Complete with sub-commands of ":scscope": same sub-commands as - * ":cscope" but skip commands which don't support split windows */ + // Complete with sub-commands of ":scscope": same sub-commands as + // ":cscope" but skip commands which don't support split windows int i; for (i = 0, current_idx = 0; cs_cmds[i].name != NULL; i++) if (cs_cmds[i].cansplit) @@ -118,10 +118,10 @@ char_u *get_cscope_name(expand_T *xp, int idx) { static char connection[5]; - /* ":cscope kill" accepts connection numbers or partial names of - * the pathname of the cscope database as argument. Only complete - * with connection numbers. -1 can also be used to kill all - * connections. */ + // ":cscope kill" accepts connection numbers or partial names of + // the pathname of the cscope database as argument. Only complete + // with connection numbers. -1 can also be used to kill all + // connections. size_t i; for (i = 0, current_idx = 0; i < csinfo_size; i++) { if (csinfo[i].fname == NULL) @@ -149,7 +149,7 @@ void set_context_in_cscope_cmd(expand_T *xp, const char *arg, cmdidx_T cmdidx) expand_what = ((cmdidx == CMD_scscope) ? EXP_SCSCOPE_SUBCMD : EXP_CSCOPE_SUBCMD); - /* (part of) subcommand already typed */ + // (part of) subcommand already typed if (*arg != NUL) { const char *p = (const char *)skiptowhite((const char_u *)arg); if (*p != NUL) { // Past first word. @@ -175,7 +175,7 @@ void set_context_in_cscope_cmd(expand_T *xp, const char *arg, cmdidx_T cmdidx) static void do_cscope_general( exarg_T *eap, - int make_split /* whether to split window */ + int make_split // whether to split window ) { cscmd_T *cmdp; @@ -276,17 +276,19 @@ void ex_cstag(exarg_T *eap) /// This simulates a vim_fgets(), but for cscope, returns the next line /// from the cscope output. should only be called from find_tags() /// -/// @return TRUE if eof, FALSE otherwise -int cs_fgets(char_u *buf, int size) +/// @return true if eof, FALSE otherwise +bool cs_fgets(char_u *buf, int size) + FUNC_ATTR_NONNULL_ALL { char *p; - if ((p = cs_manage_matches(NULL, NULL, 0, Get)) == NULL) + if ((p = cs_manage_matches(NULL, NULL, 0, Get)) == NULL) { return true; + } STRLCPY(buf, p, size); - return FALSE; -} /* cs_fgets */ + return false; +} /// Called only from do_tag(), when popping the tag stack. @@ -328,48 +330,53 @@ void cs_print_tags(void) * * Note: All string comparisons are case sensitive! */ -int cs_connection(int num, char_u *dbpath, char_u *ppath) +bool cs_connection(int num, char_u *dbpath, char_u *ppath) { - if (num < 0 || num > 4 || (num > 0 && !dbpath)) + if (num < 0 || num > 4 || (num > 0 && !dbpath)) { return false; + } for (size_t i = 0; i < csinfo_size; i++) { - if (!csinfo[i].fname) + if (!csinfo[i].fname) { continue; - - if (num == 0) - return TRUE; - + } + if (num == 0) { + return true; + } switch (num) { case 1: - if (strstr(csinfo[i].fname, (char *)dbpath)) - return TRUE; + if (strstr(csinfo[i].fname, (char *)dbpath)) { + return true; + } break; case 2: - if (strcmp(csinfo[i].fname, (char *)dbpath) == 0) - return TRUE; + if (strcmp(csinfo[i].fname, (char *)dbpath) == 0) { + return true; + } break; case 3: if (strstr(csinfo[i].fname, (char *)dbpath) && ((!ppath && !csinfo[i].ppath) || (ppath && csinfo[i].ppath - && strstr(csinfo[i].ppath, (char *)ppath)))) - return TRUE; + && strstr(csinfo[i].ppath, (char *)ppath)))) { + return true; + } break; case 4: if ((strcmp(csinfo[i].fname, (char *)dbpath) == 0) && ((!ppath && !csinfo[i].ppath) || (ppath && csinfo[i].ppath - && (strcmp(csinfo[i].ppath, (char *)ppath) == 0)))) - return TRUE; + && (strcmp(csinfo[i].ppath, (char *)ppath) == 0)))) { + return true; + } break; } } - return FALSE; -} /* cs_connection */ + return false; +} // cs_connection /* @@ -419,7 +426,7 @@ cs_add_common( size_t usedlen = 0; char_u *fbuf = NULL; - /* get the filename (arg1), expand it, and try to stat it */ + // get the filename (arg1), expand it, and try to stat it fname = xmalloc(MAXPATHL + 1); expand_env((char_u *)arg1, (char_u *)fname, MAXPATHL); @@ -451,7 +458,7 @@ staterr: } int i; - /* if filename is a directory, append the cscope database name to it */ + // if filename is a directory, append the cscope database name to it if ((file_info.stat.st_mode & S_IFMT) == S_IFDIR) { fname2 = (char *)xmalloc(strlen(CSCOPE_DBFILE) + strlen(fname) + 2); @@ -512,18 +519,18 @@ add_err: xfree(fname); xfree(ppath); return CSCOPE_FAILURE; -} /* cs_add_common */ +} static int cs_check_for_connections(void) { return cs_cnt_connections() > 0; -} /* cs_check_for_connections */ +} static int cs_check_for_tags(void) { return p_tags[0] != NUL && curbuf->b_p_tags != NULL; -} /* cs_check_for_tags */ +} /// Count the number of cscope connections. static size_t cs_cnt_connections(void) @@ -535,10 +542,10 @@ static size_t cs_cnt_connections(void) cnt++; } return cnt; -} /* cs_cnt_connections */ +} static void cs_reading_emsg( - size_t idx /* connection index */ + size_t idx // connection index ) { EMSGU(_("E262: error reading cscope connection %" PRIu64), idx); @@ -602,7 +609,7 @@ static int cs_cnt_matches(size_t idx) xfree(buf); return nlines; -} /* cs_cnt_matches */ +} /// Creates the actual cscope command query from what the user entered. @@ -646,8 +653,8 @@ static char *cs_create_cmd(char *csoption, char *pattern) return NULL; } - /* Skip white space before the patter, except for text and pattern search, - * they may want to use the leading white space. */ + // Skip white space before the patter, except for text and pattern search, + // they may want to use the leading white space. pat = pattern; if (search != 4 && search != 6) while (ascii_iswhite(*pat)) @@ -658,7 +665,7 @@ static char *cs_create_cmd(char *csoption, char *pattern) (void)sprintf(cmd, "%d%s", search, pat); return cmd; -} /* cs_create_cmd */ +} /// This piece of code was taken/adapted from nvi. do we need to add @@ -694,15 +701,18 @@ err_closing: case -1: (void)EMSG(_("E622: Could not fork for cscope")); goto err_closing; - case 0: /* child: run cscope. */ - if (dup2(to_cs[0], STDIN_FILENO) == -1) + case 0: // child: run cscope. + if (dup2(to_cs[0], STDIN_FILENO) == -1) { PERROR("cs_create_connection 1"); - if (dup2(from_cs[1], STDOUT_FILENO) == -1) + } + if (dup2(from_cs[1], STDOUT_FILENO) == -1) { PERROR("cs_create_connection 2"); - if (dup2(from_cs[1], STDERR_FILENO) == -1) + } + if (dup2(from_cs[1], STDERR_FILENO) == -1) { PERROR("cs_create_connection 3"); + } - /* close unused */ + // close unused (void)close(to_cs[1]); (void)close(from_cs[0]); #else @@ -735,14 +745,14 @@ err_closing: return CSCOPE_FAILURE; } #endif - /* expand the cscope exec for env var's */ + // expand the cscope exec for env var's prog = xmalloc(MAXPATHL + 1); expand_env(p_csprg, (char_u *)prog, MAXPATHL); - /* alloc space to hold the cscope command */ + // alloc space to hold the cscope command size_t len = strlen(prog) + strlen(csinfo[i].fname) + 32; if (csinfo[i].ppath) { - /* expand the prepend path for env var's */ + // expand the prepend path for env var's ppath = xmalloc(MAXPATHL + 1); expand_env((char_u *)csinfo[i].ppath, (char_u *)ppath, MAXPATHL); @@ -754,12 +764,12 @@ err_closing: cmd = xmalloc(len); - /* run the cscope command; is there execl for non-unix systems? */ + // run the cscope command; is there execl for non-unix systems? #if defined(UNIX) - (void)sprintf(cmd, "exec %s -dl -f %s", prog, csinfo[i].fname); + (void)snprintf(cmd, len, "exec %s -dl -f %s", prog, csinfo[i].fname); #else - /* WIN32 */ - (void)sprintf(cmd, "%s -dl -f %s", prog, csinfo[i].fname); + // WIN32 + (void)snprintf(cmd, len, "%s -dl -f %s", prog, csinfo[i].fname); #endif if (csinfo[i].ppath != NULL) { (void)strcat(cmd, " -P"); @@ -770,14 +780,14 @@ err_closing: (void)strcat(cmd, csinfo[i].flags); } # ifdef UNIX - /* on Win32 we still need prog */ + // on Win32 we still need prog xfree(prog); # endif xfree(ppath); #if defined(UNIX) # if defined(HAVE_SETSID) || defined(HAVE_SETPGID) - /* Change our process group to avoid cscope receiving SIGWINCH. */ + // Change our process group to avoid cscope receiving SIGWINCH. # if defined(HAVE_SETSID) (void)setsid(); # else @@ -789,18 +799,18 @@ err_closing: PERROR(_("cs_create_connection exec failed")); exit(127); - /* NOTREACHED */ - default: /* parent. */ - /* - * Save the file descriptors for later duplication, and - * reopen as streams. - */ - if ((csinfo[i].to_fp = fdopen(to_cs[1], "w")) == NULL) + // NOTREACHED + default: // parent. + // Save the file descriptors for later duplication, and + // reopen as streams. + if ((csinfo[i].to_fp = fdopen(to_cs[1], "w")) == NULL) { PERROR(_("cs_create_connection: fdopen for to_fp failed")); - if ((csinfo[i].fr_fp = fdopen(from_cs[0], "r")) == NULL) + } + if ((csinfo[i].fr_fp = fdopen(from_cs[0], "r")) == NULL) { PERROR(_("cs_create_connection: fdopen for fr_fp failed")); + } - /* close unused */ + // close unused (void)close(to_cs[0]); (void)close(from_cs[1]); @@ -808,11 +818,11 @@ err_closing: } #else - /* WIN32 */ - /* Create a new process to run cscope and use pipes to talk with it */ + // WIN32 + // Create a new process to run cscope and use pipes to talk with it GetStartupInfo(&si); si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; - si.wShowWindow = SW_HIDE; /* Hide child application window */ + si.wShowWindow = SW_HIDE; // Hide child application window si.hStdOutput = stdout_wr; si.hStdError = stdout_wr; si.hStdInput = stdin_rd; @@ -826,7 +836,7 @@ err_closing: (void)EMSG(_("E623: Could not spawn cscope process")); goto err_closing; } - /* else */ + // else csinfo[i].pid = pi.dwProcessId; csinfo[i].hProc = pi.hProcess; CloseHandle(pi.hThread); @@ -844,10 +854,10 @@ err_closing: CloseHandle(stdin_rd); CloseHandle(stdout_wr); -#endif /* !UNIX */ +#endif // !UNIX return CSCOPE_SUCCESS; -} /* cs_create_connection */ +} /// Query cscope using command line interface. Parse the output and use tselect @@ -882,9 +892,9 @@ static int cs_find(exarg_T *eap) if (NUL == eap->arg[i]) eap->arg[i] = ' '; - return cs_find_common(opt, pat, eap->forceit, TRUE, - eap->cmdidx == CMD_lcscope, *eap->cmdlinep); -} /* cs_find */ + return cs_find_common(opt, pat, eap->forceit, true, + eap->cmdidx == CMD_lcscope, *eap->cmdlinep); +} /// Common code for cscope find, shared by cs_find() and ex_cstag(). @@ -897,7 +907,7 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, char cmdletter; char *qfpos; - /* get cmd letter */ + // get cmd letter switch (opt[0]) { case '0': cmdletter = 's'; @@ -933,10 +943,10 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, qfpos = (char *)vim_strchr(p_csqf, cmdletter); if (qfpos != NULL) { qfpos++; - /* next symbol must be + or - */ + // next symbol must be + or - if (strchr(CSQF_FLAGS, *qfpos) == NULL) { char *nf = _("E469: invalid cscopequickfix flag %c for %c"); - /* strlen will be enough because we use chars */ + // strlen will be enough because we use chars char *buf = xmalloc(strlen(nf)); sprintf(buf, nf, *qfpos, *(qfpos-1)); @@ -954,23 +964,24 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, } } - /* create the actual command to send to cscope */ + // create the actual command to send to cscope cmd = cs_create_cmd(opt, pat); if (cmd == NULL) return FALSE; nummatches = xmalloc(sizeof(int) * csinfo_size); - /* Send query to all open connections, then count the total number - * of matches so we can alloc all in one swell foop. */ - for (size_t i = 0; i < csinfo_size; i++) + // Send query to all open connections, then count the total number + // of matches so we can alloc all in one swell foop. + for (size_t i = 0; i < csinfo_size; i++) { nummatches[i] = 0; + } totmatches = 0; for (size_t i = 0; i < csinfo_size; i++) { if (csinfo[i].fname == NULL || csinfo[i].to_fp == NULL) continue; - /* send cmd to cscope */ + // send cmd to cscope (void)fprintf(csinfo[i].to_fp, "%s\n", cmd); (void)fflush(csinfo[i].to_fp); @@ -1014,8 +1025,9 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, } else { cs_file_results(f, nummatches); fclose(f); - if (use_ll) /* Use location list */ + if (use_ll) { // Use location list wp = curwin; + } // '-' starts a new error list if (qf_init(wp, tmp, (char_u *)"%f%*\\t%l%*\\t%m", *qfpos == '-', cmdline, NULL) > 0) { @@ -1046,7 +1058,7 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, char **matches = NULL, **contexts = NULL; size_t matched = 0; - /* read output */ + // read output cs_fill_results((char *)pat, totmatches, nummatches, &matches, &contexts, &matched); xfree(nummatches); @@ -1057,8 +1069,7 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, return do_tag((char_u *)pat, DT_CSCOPE, 0, forceit, verbose); } - -} /* cs_find_common */ +} /// Print help. static int cs_help(exarg_T *eap) @@ -1070,9 +1081,10 @@ static int cs_help(exarg_T *eap) char *help = _(cmdp->help); int space_cnt = 30 - vim_strsize((char_u *)help); - /* Use %*s rather than %30s to ensure proper alignment in utf-8 */ - if (space_cnt < 0) + // Use %*s rather than %30s to ensure proper alignment in utf-8 + if (space_cnt < 0) { space_cnt = 0; + } (void)smsg(_("%-5s: %s%*s (Usage: %s)"), cmdp->name, help, space_cnt, " ", @@ -1094,7 +1106,7 @@ static int cs_help(exarg_T *eap) wait_return(TRUE); return CSCOPE_SUCCESS; -} /* cs_help */ +} static void clear_csinfo(size_t i) @@ -1124,7 +1136,7 @@ static int cs_insert_filelist(char *fname, char *ppath, char *flags, } if (csinfo[j].fname == NULL && !empty_found) { - i = j; /* remember first empty entry */ + i = j; // remember first empty entry empty_found = true; } } @@ -1132,13 +1144,13 @@ static int cs_insert_filelist(char *fname, char *ppath, char *flags, if (!empty_found) { i = csinfo_size; if (csinfo_size == 0) { - /* First time allocation: allocate only 1 connection. It should - * be enough for most users. If more is needed, csinfo will be - * reallocated. */ + // First time allocation: allocate only 1 connection. It should + // be enough for most users. If more is needed, csinfo will be + // reallocated. csinfo_size = 1; csinfo = xcalloc(1, sizeof(csinfo_T)); } else { - /* Reallocate space for more connections. */ + // Reallocate space for more connections. csinfo_size *= 2; csinfo = xrealloc(csinfo, sizeof(csinfo_T)*csinfo_size); } @@ -1165,7 +1177,7 @@ static int cs_insert_filelist(char *fname, char *ppath, char *flags, os_fileinfo_id(file_info, &(csinfo[i].file_id)); assert(i <= INT_MAX); return (int)i; -} /* cs_insert_filelist */ +} /// Find cscope command in command table. @@ -1178,7 +1190,7 @@ static cscmd_T * cs_lookup_cmd(exarg_T *eap) if (eap->arg == NULL) return NULL; - /* Store length of eap->arg before it gets modified by strtok(). */ + // Store length of eap->arg before it gets modified by strtok(). eap_arg_len = (int)STRLEN(eap->arg); if ((stok = strtok((char *)(eap->arg), (const char *)" ")) == NULL) @@ -1190,7 +1202,7 @@ static cscmd_T * cs_lookup_cmd(exarg_T *eap) return cmdp; } return NULL; -} /* cs_lookup_cmd */ +} /// Nuke em. @@ -1247,13 +1259,13 @@ static int cs_kill(exarg_T *eap) } return CSCOPE_SUCCESS; -} /* cs_kill */ +} /// Actually kills a specific cscope connection. static void cs_kill_execute( - size_t i, /* cscope table index */ - char *cname /* cscope database name */ + size_t i, // cscope table index + char *cname // cscope database name ) { if (p_csverbose) { @@ -1284,17 +1296,16 @@ static void cs_kill_execute( static char *cs_make_vim_style_matches(char *fname, char *slno, char *search, char *tagstr) { - /* vim style is ctags: - * - * <tagstr>\t<filename>\t<linenum_or_search>"\t<extra> - * - * but as mentioned above, we'll always use the line number and - * put the search pattern (if one exists) as "extra" - * - * buf is used as part of vim's method of handling tags, and - * (i think) vim frees it when you pop your tags and get replaced - * by new ones on the tag stack. - */ + // vim style is ctags: + // + // <tagstr>\t<filename>\t<linenum_or_search>"\t<extra> + // + // but as mentioned above, we'll always use the line number and + // put the search pattern (if one exists) as "extra" + // + // buf is used as part of vim's method of handling tags, and + // (i think) vim frees it when you pop your tags and get replaced + // by new ones on the tag stack. char *buf; size_t amt; @@ -1311,7 +1322,7 @@ static char *cs_make_vim_style_matches(char *fname, char *slno, char *search, } return buf; -} /* cs_make_vim_style_matches */ +} /// This is kind of hokey, but i don't see an easy way round this. @@ -1381,7 +1392,7 @@ static char *cs_manage_matches(char **matches, char **contexts, } return p; -} /* cs_manage_matches */ +} /// Parse cscope output. @@ -1408,7 +1419,7 @@ retry: return NULL; } - /* If the line's too long for the buffer, discard it. */ + // If the line's too long for the buffer, discard it. if ((p = strchr(buf, '\n')) == NULL) { while ((ch = getc(csinfo[cnumber].fr_fp)) != EOF && ch != '\n') ; @@ -1427,15 +1438,15 @@ retry: return NULL; if ((*linenumber = strtok(NULL, (const char *)" ")) == NULL) return NULL; - *search = *linenumber + strlen(*linenumber) + 1; /* +1 to skip \0 */ + *search = *linenumber + strlen(*linenumber) + 1; // +1 to skip \0 - /* --- nvi --- - * If the file is older than the cscope database, that is, - * the database was built since the file was last modified, - * or there wasn't a search string, use the line number. - */ - if (strcmp(*search, "<unknown>") == 0) + // --- nvi --- + // If the file is older than the cscope database, that is, + // the database was built since the file was last modified, + // or there wasn't a search string, use the line number. + if (strcmp(*search, "<unknown>") == 0) { *search = NULL; + } name = cs_resolve_file(cnumber, name); return name; @@ -1474,11 +1485,10 @@ static void cs_file_results(FILE *f, int *nummatches_a) xfree(context); xfree(fullname); - } /* for all matches */ + } // for all matches (void)cs_read_prompt(i); - - } /* for all cscope connections */ + } // for all cscope connections xfree(buf); } @@ -1539,10 +1549,10 @@ static void cs_fill_results(char *tagstr, size_t totmatches, int *nummatches_a, *cntxts_p = cntxts; xfree(buf); -} // cs_fill_results +} -/* get the requested path components */ +// get the requested path components static char *cs_pathcomponents(char *path) { if (p_cspc == 0) { @@ -1688,7 +1698,7 @@ static int cs_read_prompt(size_t i) static char *eprompt = "Press the RETURN key to continue:"; size_t epromptlen = strlen(eprompt); - /* compute maximum allowed len for Cscope error message */ + // compute maximum allowed len for Cscope error message assert(IOSIZE >= cs_emsg_len); size_t maxlen = IOSIZE - cs_emsg_len; @@ -1738,11 +1748,12 @@ static int cs_read_prompt(size_t i) } if (ch == EOF) { PERROR("cs_read_prompt EOF"); - if (buf != NULL && buf[0] != NUL) + if (buf != NULL && buf[0] != NUL) { (void)EMSG2(cs_emsg, buf); - else if (p_csverbose) - cs_reading_emsg(i); /* don't have additional information */ - cs_release_csp(i, TRUE); + } else if (p_csverbose) { + cs_reading_emsg(i); // don't have additional information + } + cs_release_csp(i, true); xfree(buf); return CSCOPE_FAILURE; } @@ -1753,9 +1764,10 @@ static int cs_read_prompt(size_t i) } } - if (ch == EOF) - continue; /* didn't find the prompt */ - break; /* did find the prompt */ + if (ch == EOF) { + continue; // didn't find the prompt + } + break; // did find the prompt } xfree(buf); @@ -1766,8 +1778,9 @@ static int cs_read_prompt(size_t i) /* * Used to catch and ignore SIGALRM below. */ -static void sig_handler(int s) { - /* do nothing */ +static void sig_handler(int s) +{ + // do nothing return; } @@ -1775,7 +1788,7 @@ static void sig_handler(int s) { /// Does the actual free'ing for the cs ptr with an optional flag of whether /// or not to free the filename. Called by cs_kill and cs_reset. -static void cs_release_csp(size_t i, int freefnpp) +static void cs_release_csp(size_t i, bool freefnpp) { // Trying to exit normally (not sure whether it is fit to Unix cscope) if (csinfo[i].to_fp != NULL) { @@ -1791,7 +1804,7 @@ static void cs_release_csp(size_t i, int freefnpp) # if defined(HAVE_SIGACTION) struct sigaction sa, old; - /* Use sigaction() to limit the waiting time to two seconds. */ + // Use sigaction() to limit the waiting time to two seconds. sigemptyset(&sa.sa_mask); sa.sa_handler = sig_handler; # ifdef SA_NODEFER @@ -1800,27 +1813,28 @@ static void cs_release_csp(size_t i, int freefnpp) sa.sa_flags = 0; # endif sigaction(SIGALRM, &sa, &old); - alarm(2); /* 2 sec timeout */ + alarm(2); // 2 sec timeout - /* Block until cscope exits or until timer expires */ + // Block until cscope exits or until timer expires pid = waitpid(csinfo[i].pid, &pstat, 0); waitpid_errno = errno; - /* cancel pending alarm if still there and restore signal */ + // cancel pending alarm if still there and restore signal alarm(0); sigaction(SIGALRM, &old, NULL); # else int waited; - /* Can't use sigaction(), loop for two seconds. First yield the CPU - * to give cscope a chance to exit quickly. */ + // Can't use sigaction(), loop for two seconds. First yield the CPU + // to give cscope a chance to exit quickly. sleep(0); for (waited = 0; waited < 40; ++waited) { pid = waitpid(csinfo[i].pid, &pstat, WNOHANG); waitpid_errno = errno; - if (pid != 0) - break; /* break unless the process is still running */ - os_delay(50L, false); /* sleep 50 ms */ + if (pid != 0) { + break; // break unless the process is still running + } + os_delay(50L, false); // sleep 50 ms } # endif /* @@ -1830,7 +1844,7 @@ static void cs_release_csp(size_t i, int freefnpp) */ if (pid < 0 && csinfo[i].pid > 1) { # ifdef ECHILD - int alive = TRUE; + bool alive = true; if (waitpid_errno == ECHILD) { /* @@ -1845,13 +1859,13 @@ static void cs_release_csp(size_t i, int freefnpp) int waited; sleep(0); - for (waited = 0; waited < 40; ++waited) { - /* Check whether cscope process is still alive */ + for (waited = 0; waited < 40; waited++) { + // Check whether cscope process is still alive if (kill(csinfo[i].pid, 0) != 0) { - alive = FALSE; /* cscope process no longer exists */ + alive = false; // cscope process no longer exists break; } - os_delay(50L, false); /* sleep 50ms */ + os_delay(50L, false); // sleep 50ms } } if (alive) @@ -1862,11 +1876,12 @@ static void cs_release_csp(size_t i, int freefnpp) } } } -#else /* !UNIX */ +#else // !UNIX if (csinfo[i].hProc != NULL) { - /* Give cscope a chance to exit normally */ - if (WaitForSingleObject(csinfo[i].hProc, 1000) == WAIT_TIMEOUT) + // Give cscope a chance to exit normally + if (WaitForSingleObject(csinfo[i].hProc, 1000) == WAIT_TIMEOUT) { TerminateProcess(csinfo[i].hProc, 0); + } CloseHandle(csinfo[i].hProc); } #endif @@ -1883,7 +1898,7 @@ static void cs_release_csp(size_t i, int freefnpp) } clear_csinfo(i); -} /* cs_release_csp */ +} /// Calls cs_kill on all cscope connections then reinits. @@ -1895,7 +1910,7 @@ static int cs_reset(exarg_T *eap) if (csinfo_size == 0) return CSCOPE_SUCCESS; - /* malloc our db and ppath list */ + // malloc our db and ppath list dblist = xmalloc(csinfo_size * sizeof(char *)); pplist = xmalloc(csinfo_size * sizeof(char *)); fllist = xmalloc(csinfo_size * sizeof(char *)); @@ -1908,15 +1923,14 @@ static int cs_reset(exarg_T *eap) cs_release_csp(i, FALSE); } - /* rebuild the cscope connection list */ + // rebuild the cscope connection list for (size_t i = 0; i < csinfo_size; i++) { if (dblist[i] != NULL) { cs_add_common(dblist[i], pplist[i], fllist[i]); if (p_csverbose) { - /* don't use smsg_attr() because we want to display the - * connection number in the same line as - * "Added cscope database..." - */ + // don't use smsg_attr() because we want to display the + // connection number in the same line as + // "Added cscope database..." snprintf(buf, ARRAY_SIZE(buf), " (#%zu)", i); MSG_PUTS_ATTR(buf, HL_ATTR(HLF_R)); } @@ -1933,7 +1947,7 @@ static int cs_reset(exarg_T *eap) msg_attr(_("All cscope databases reset"), HL_ATTR(HLF_R) | MSG_HIST); } return CSCOPE_SUCCESS; -} /* cs_reset */ +} /// Construct the full pathname to a file found in the cscope database. @@ -1954,11 +1968,11 @@ static char *cs_resolve_file(size_t i, char *name) * copied into the tag buffer used by Vim. */ size_t len = strlen(name) + 2; - if (csinfo[i].ppath != NULL) + if (csinfo[i].ppath != NULL) { len += strlen(csinfo[i].ppath); - else if (p_csre && csinfo[i].fname != NULL) { - /* If 'cscoperelative' is set and ppath is not set, use cscope.out - * path in path resolution. */ + } else if (p_csre && csinfo[i].fname != NULL) { + // If 'cscoperelative' is set and ppath is not set, use cscope.out + // path in path resolution. csdir = xmalloc(MAXPATHL); STRLCPY(csdir, csinfo[i].fname, path_tail((char_u *)csinfo[i].fname) @@ -1966,9 +1980,9 @@ static char *cs_resolve_file(size_t i, char *name) len += STRLEN(csdir); } - /* Note/example: this won't work if the cscope output already starts - * "../.." and the prefix path is also "../..". if something like this - * happens, you are screwed up and need to fix how you're using cscope. */ + // Note/example: this won't work if the cscope output already starts + // "../.." and the prefix path is also "../..". if something like this + // happens, you are screwed up and need to fix how you're using cscope. if (csinfo[i].ppath != NULL && (strncmp(name, csinfo[i].ppath, strlen(csinfo[i].ppath)) != 0) && (name[0] != '/') @@ -1976,9 +1990,9 @@ static char *cs_resolve_file(size_t i, char *name) fullname = xmalloc(len); (void)sprintf(fullname, "%s/%s", csinfo[i].ppath, name); } else if (csdir != NULL && csinfo[i].fname != NULL && *csdir != NUL) { - /* Check for csdir to be non empty to avoid empty path concatenated to - * cscope output. */ - fullname = concat_fnames((char *)csdir, name, TRUE); + // Check for csdir to be non empty to avoid empty path concatenated to + // cscope output. + fullname = concat_fnames((char *)csdir, name, true); } else { fullname = xstrdup(name); } @@ -2013,7 +2027,7 @@ static int cs_show(exarg_T *eap) wait_return(TRUE); return CSCOPE_SUCCESS; -} /* cs_show */ +} /// Only called when VIM exits to quit any cscope sessions. @@ -2025,4 +2039,4 @@ void cs_end(void) csinfo_size = 0; } -/* the end */ +// the end diff --git a/src/nvim/if_cscope_defs.h b/src/nvim/if_cscope_defs.h index fa18866840..d2d8b0fb62 100644 --- a/src/nvim/if_cscope_defs.h +++ b/src/nvim/if_cscope_defs.h @@ -1,19 +1,16 @@ #ifndef NVIM_IF_CSCOPE_DEFS_H #define NVIM_IF_CSCOPE_DEFS_H -/* - * CSCOPE support for Vim added by Andy Kahn <kahn@zk3.dec.com> - * Ported to Win32 by Sergey Khorev <sergey.khorev@gmail.com> - * - * The basic idea/structure of cscope for Vim was borrowed from Nvi. - * There might be a few lines of code that look similar to what Nvi - * has. If this is a problem and requires inclusion of the annoying - * BSD license, then sue me; I'm not worth much anyway. - */ - +// CSCOPE support for Vim added by Andy Kahn <kahn@zk3.dec.com> +// Ported to Win32 by Sergey Khorev <sergey.khorev@gmail.com> +// +// The basic idea/structure of cscope for Vim was borrowed from Nvi. +// There might be a few lines of code that look similar to what Nvi +// has. If this is a problem and requires inclusion of the annoying +// BSD license, then sue me; I'm not worth much anyway. #if defined(UNIX) -# include <sys/types.h> /* pid_t */ +# include <sys/types.h> // pid_t #endif #include "nvim/os/os_defs.h" @@ -33,13 +30,13 @@ typedef struct { int (*func)(exarg_T *eap); char * help; char * usage; - int cansplit; /* if supports splitting window */ + int cansplit; // if supports splitting window } cscmd_T; typedef struct csi { - char * fname; /* cscope db name */ - char * ppath; /* path to prepend (the -P option) */ - char * flags; /* additional cscope flags/options (e.g, -p2) */ + char * fname; // cscope db name + char * ppath; // path to prepend (the -P option) + char * flags; // additional cscope flags/options (e.g, -p2) #if defined(UNIX) pid_t pid; // PID of the connected cscope process #else @@ -51,8 +48,8 @@ typedef struct csi { #endif FileID file_id; - FILE * fr_fp; /* from cscope: FILE. */ - FILE * to_fp; /* to cscope: FILE. */ + FILE * fr_fp; // from cscope: FILE. + FILE * to_fp; // to cscope: FILE. } csinfo_T; typedef enum { Add, Find, Help, Kill, Reset, Show } csid_e; diff --git a/src/nvim/indent.c b/src/nvim/indent.c index f8018c039d..fb277b25fd 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -56,7 +56,8 @@ int get_indent_buf(buf_T *buf, linenr_T lnum) // Count the size (in window cells) of the indent in line "ptr", with // 'tabstop' at "ts". // If @param list is TRUE, count only screen size for tabs. -int get_indent_str(char_u *ptr, int ts, int list) +int get_indent_str(const char_u *ptr, int ts, int list) + FUNC_ATTR_NONNULL_ALL { int count = 0; @@ -375,12 +376,12 @@ int get_number_indent(linenr_T lnum) * parameters into account. Window must be specified, since it is not * necessarily always the current one. */ -int get_breakindent_win(win_T *wp, char_u *line) - FUNC_ATTR_NONNULL_ARG(1) +int get_breakindent_win(win_T *wp, const char_u *line) + FUNC_ATTR_NONNULL_ALL { static int prev_indent = 0; // Cached indent value. static long prev_ts = 0; // Cached tabstop value. - static char_u *prev_line = NULL; // cached pointer to line. + static const char_u *prev_line = NULL; // cached pointer to line. static varnumber_T prev_tick = 0; // Changedtick of cached value. int bri = 0; // window width minus window margin space, i.e. what rests for text @@ -389,7 +390,7 @@ int get_breakindent_win(win_T *wp, char_u *line) && (vim_strchr(p_cpo, CPO_NUMCOL) == NULL) ? number_width(wp) + 1 : 0); - /* used cached indent, unless pointer or 'tabstop' changed */ + // used cached indent, unless pointer or 'tabstop' changed if (prev_line != line || prev_ts != wp->w_buffer->b_p_ts || prev_tick != buf_get_changedtick(wp->w_buffer)) { prev_line = line; @@ -397,23 +398,24 @@ int get_breakindent_win(win_T *wp, char_u *line) prev_tick = buf_get_changedtick(wp->w_buffer); prev_indent = get_indent_str(line, (int)wp->w_buffer->b_p_ts, wp->w_p_list); } - bri = prev_indent + wp->w_p_brishift; + bri = prev_indent + wp->w_briopt_shift; - /* indent minus the length of the showbreak string */ - if (wp->w_p_brisbr) + // indent minus the length of the showbreak string + if (wp->w_briopt_sbr) { bri -= vim_strsize(p_sbr); - - /* Add offset for number column, if 'n' is in 'cpoptions' */ + } + // Add offset for number column, if 'n' is in 'cpoptions' bri += win_col_off2(wp); - /* never indent past left window margin */ - if (bri < 0) + // never indent past left window margin + if (bri < 0) { bri = 0; - /* always leave at least bri_min characters on the left, - * if text width is sufficient */ - else if (bri > eff_wwidth - wp->w_p_brimin) - bri = (eff_wwidth - wp->w_p_brimin < 0) - ? 0 : eff_wwidth - wp->w_p_brimin; + } else if (bri > eff_wwidth - wp->w_briopt_min) { + // always leave at least bri_min characters on the left, + // if text width is sufficient + bri = (eff_wwidth - wp->w_briopt_min < 0) + ? 0 : eff_wwidth - wp->w_briopt_min; + } return bri; } diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index 67a7e58ed7..bb443161ef 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -3372,11 +3372,9 @@ term_again: continue; } - /* - * Are we at the start of a cpp base class declaration or - * constructor initialization? - */ /* XXX */ - n = false; + // Are we at the start of a cpp base class declaration or + // constructor initialization? XXX + n = 0; if (curbuf->b_ind_cpp_baseclass != 0 && theline[0] != '{') { n = cin_is_cpp_baseclass(&cache_cpp_baseclass); l = get_cursor_line_ptr(); @@ -3409,7 +3407,6 @@ term_again: * } foo, * bar; */ - n = 0; if (cin_ends_in(l, (char_u *)",", NULL) || (*l != NUL && (n = l[STRLEN(l) - 1]) == '\\')) { /* take us back to opening paren */ diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h index cc02a6fb4f..ada9bc5780 100644 --- a/src/nvim/keymap.h +++ b/src/nvim/keymap.h @@ -13,7 +13,7 @@ * For MSDOS some keys produce codes larger than 0xff. They are split into two * chars, the first one is K_NUL. */ -#define K_NUL (0xce) /* for MSDOS: special key follows */ +#define K_NUL (0xce) // for MSDOS: special key follows /* * K_SPECIAL is the first byte of a special key code and is always followed by @@ -78,13 +78,13 @@ #define KS_SELECT 245 #define K_SELECT_STRING (char_u *)"\200\365X" -/* Used a termcap entry that produces a normal character. */ +// Used a termcap entry that produces a normal character. #define KS_KEY 242 -/* Used for click in a tab pages label. */ +// Used for click in a tab pages label. #define KS_TABLINE 240 -/* Used for menu in a tab pages line. */ +// Used for menu in a tab pages line. #define KS_TABMENU 239 /* @@ -240,8 +240,8 @@ enum key_extra { , KE_DROP = 95 // DnD data is available // , KE_CURSORHOLD = 96 // CursorHold event , KE_NOP = 97 // no-op: does nothing - , KE_FOCUSGAINED = 98 // focus gained - , KE_FOCUSLOST = 99 // focus lost + // , KE_FOCUSGAINED = 98 // focus gained + // , KE_FOCUSLOST = 99 // focus lost // , KE_MOUSEMOVE = 100 // mouse moved with no button down // , KE_CANCEL = 101 // return from vgetc , KE_EVENT = 102 // event @@ -274,13 +274,13 @@ enum key_extra { #define K_TAB TERMCAP2KEY(KS_EXTRA, KE_TAB) #define K_S_TAB TERMCAP2KEY('k', 'B') -/* extra set of function keys F1-F4, for vt100 compatible xterm */ +// extra set of function keys F1-F4, for vt100 compatible xterm #define K_XF1 TERMCAP2KEY(KS_EXTRA, KE_XF1) #define K_XF2 TERMCAP2KEY(KS_EXTRA, KE_XF2) #define K_XF3 TERMCAP2KEY(KS_EXTRA, KE_XF3) #define K_XF4 TERMCAP2KEY(KS_EXTRA, KE_XF4) -/* extra set of cursor keys for vt100 compatible xterm */ +// extra set of cursor keys for vt100 compatible xterm #define K_XUP TERMCAP2KEY(KS_EXTRA, KE_XUP) #define K_XDOWN TERMCAP2KEY(KS_EXTRA, KE_XDOWN) #define K_XLEFT TERMCAP2KEY(KS_EXTRA, KE_XLEFT) @@ -327,7 +327,7 @@ enum key_extra { #define K_F36 TERMCAP2KEY('F', 'Q') #define K_F37 TERMCAP2KEY('F', 'R') -/* extra set of shifted function keys F1-F4, for vt100 compatible xterm */ +// extra set of shifted function keys F1-F4, for vt100 compatible xterm #define K_S_XF1 TERMCAP2KEY(KS_EXTRA, KE_S_XF1) #define K_S_XF2 TERMCAP2KEY(KS_EXTRA, KE_S_XF2) #define K_S_XF3 TERMCAP2KEY(KS_EXTRA, KE_S_XF3) @@ -346,7 +346,7 @@ enum key_extra { #define K_S_F11 TERMCAP2KEY(KS_EXTRA, KE_S_F11) #define K_S_F12 TERMCAP2KEY(KS_EXTRA, KE_S_F12) -/* K_S_F13 to K_S_F37 are currently not used */ +// K_S_F13 to K_S_F37 are currently not used #define K_HELP TERMCAP2KEY('%', '1') #define K_UNDO TERMCAP2KEY('&', '8') @@ -443,8 +443,8 @@ enum key_extra { #define K_EVENT TERMCAP2KEY(KS_EXTRA, KE_EVENT) #define K_COMMAND TERMCAP2KEY(KS_EXTRA, KE_COMMAND) -/* Bits for modifier mask */ -/* 0x01 cannot be used, because the modifier must be 0x02 or higher */ +// Bits for modifier mask +// 0x01 cannot be used, because the modifier must be 0x02 or higher #define MOD_MASK_SHIFT 0x02 #define MOD_MASK_CTRL 0x04 #define MOD_MASK_ALT 0x08 // aka META diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 242d4e18d1..144646fca2 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -11,6 +11,7 @@ #include "nvim/func_attr.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/private/handle.h" #include "nvim/api/vim.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/vim.h" @@ -19,12 +20,14 @@ #include "nvim/message.h" #include "nvim/memline.h" #include "nvim/buffer_defs.h" +#include "nvim/regexp.h" #include "nvim/macros.h" #include "nvim/screen.h" #include "nvim/cursor.h" #include "nvim/undo.h" #include "nvim/ascii.h" #include "nvim/change.h" +#include "nvim/eval/userfunc.h" #ifdef WIN32 #include "nvim/os/os.h" @@ -244,6 +247,14 @@ static int nlua_schedule(lua_State *const lstate) return 0; } +static struct luaL_Reg regex_meta[] = { + { "__gc", regex_gc }, + { "__tostring", regex_tostring }, + { "match_str", regex_match_str }, + { "match_line", regex_match_line }, + { NULL, NULL } +}; + /// Initialize lua interpreter state /// /// Called by lua interpreter itself to initialize state. @@ -291,6 +302,15 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL // call lua_pushcfunction(lstate, &nlua_call); lua_setfield(lstate, -2, "call"); + // regex + lua_pushcfunction(lstate, &nlua_regex); + lua_setfield(lstate, -2, "regex"); + + luaL_newmetatable(lstate, "nvim_regex"); + luaL_register(lstate, NULL, regex_meta); + lua_pushvalue(lstate, -1); // [meta, meta] + lua_setfield(lstate, -2, "__index"); // [meta] + lua_pop(lstate, 1); // don't use metatable now // rpcrequest lua_pushcfunction(lstate, &nlua_rpcrequest); @@ -528,7 +548,7 @@ int nlua_debug(lua_State *lstate) for (;;) { lua_settop(lstate, 0); typval_T input; - get_user_input(input_args, &input, false); + get_user_input(input_args, &input, false, false); msg_putchar('\n'); // Avoid outputting on input line. if (input.v_type != VAR_STRING || input.vval.v_string == NULL @@ -1025,12 +1045,148 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, create_tslua_parser); lua_setfield(lstate, -2, "_create_ts_parser"); - lua_pushcfunction(lstate, tslua_register_lang); + lua_pushcfunction(lstate, tslua_add_language); lua_setfield(lstate, -2, "_ts_add_language"); + lua_pushcfunction(lstate, tslua_has_language); + lua_setfield(lstate, -2, "_ts_has_language"); + lua_pushcfunction(lstate, tslua_inspect_lang); lua_setfield(lstate, -2, "_ts_inspect_language"); lua_pushcfunction(lstate, ts_lua_parse_query); lua_setfield(lstate, -2, "_ts_parse_query"); } + +static int nlua_regex(lua_State *lstate) +{ + Error err = ERROR_INIT; + const char *text = luaL_checkstring(lstate, 1); + regprog_T *prog = NULL; + + TRY_WRAP({ + try_start(); + prog = vim_regcomp((char_u *)text, RE_AUTO | RE_MAGIC | RE_STRICT); + try_end(&err); + }); + + if (ERROR_SET(&err)) { + return luaL_error(lstate, "couldn't parse regex: %s", err.msg); + } + assert(prog); + + regprog_T **p = lua_newuserdata(lstate, sizeof(regprog_T *)); + *p = prog; + + lua_getfield(lstate, LUA_REGISTRYINDEX, "nvim_regex"); // [udata, meta] + lua_setmetatable(lstate, -2); // [udata] + return 1; +} + +static regprog_T **regex_check(lua_State *L) +{ + return luaL_checkudata(L, 1, "nvim_regex"); +} + + +static int regex_gc(lua_State *lstate) +{ + regprog_T **prog = regex_check(lstate); + vim_regfree(*prog); + return 0; +} + +static int regex_tostring(lua_State *lstate) +{ + lua_pushstring(lstate, "<regex>"); + return 1; +} + +static int regex_match(lua_State *lstate, regprog_T **prog, char_u *str) +{ + regmatch_T rm; + rm.regprog = *prog; + rm.rm_ic = false; + bool match = vim_regexec(&rm, str, 0); + *prog = rm.regprog; + + if (match) { + lua_pushinteger(lstate, (lua_Integer)(rm.startp[0]-str)); + lua_pushinteger(lstate, (lua_Integer)(rm.endp[0]-str)); + return 2; + } + return 0; +} + +static int regex_match_str(lua_State *lstate) +{ + regprog_T **prog = regex_check(lstate); + const char *str = luaL_checkstring(lstate, 2); + int nret = regex_match(lstate, prog, (char_u *)str); + + if (!*prog) { + return luaL_error(lstate, "regex: internal error"); + } + + return nret; +} + +static int regex_match_line(lua_State *lstate) +{ + regprog_T **prog = regex_check(lstate); + + int narg = lua_gettop(lstate); + if (narg < 3) { + return luaL_error(lstate, "not enough args"); + } + + long bufnr = luaL_checkinteger(lstate, 2); + long rownr = luaL_checkinteger(lstate, 3); + long start = 0, end = -1; + if (narg >= 4) { + start = luaL_checkinteger(lstate, 4); + } + if (narg >= 5) { + end = luaL_checkinteger(lstate, 5); + if (end < 0) { + return luaL_error(lstate, "invalid end"); + } + } + + buf_T *buf = bufnr ? handle_get_buffer((int)bufnr) : curbuf; + if (!buf || buf->b_ml.ml_mfp == NULL) { + return luaL_error(lstate, "invalid buffer"); + } + + if (rownr >= buf->b_ml.ml_line_count) { + return luaL_error(lstate, "invalid row"); + } + + char_u *line = ml_get_buf(buf, rownr+1, false); + size_t len = STRLEN(line); + + if (start < 0 || (size_t)start > len) { + return luaL_error(lstate, "invalid start"); + } + + char_u save = NUL; + if (end >= 0) { + if ((size_t)end > len || end < start) { + return luaL_error(lstate, "invalid end"); + } + save = line[end]; + line[end] = NUL; + } + + int nret = regex_match(lstate, prog, line+start); + + if (end >= 0) { + line[end] = save; + } + + if (!*prog) { + return luaL_error(lstate, "regex: internal error"); + } + + return nret; +} diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 874fabd89f..51d9549033 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -93,10 +93,7 @@ static PMap(cstr_t) *langs; static void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta) { if (luaL_newmetatable(L, tname)) { // [meta] - for (size_t i = 0; meta[i].name != NULL; i++) { - lua_pushcfunction(L, meta[i].func); // [meta, func] - lua_setfield(L, -2, meta[i].name); // [meta] - } + luaL_register(L, NULL, meta); lua_pushvalue(L, -1); // [meta, meta] lua_setfield(L, -2, "__index"); // [meta] @@ -119,7 +116,14 @@ void tslua_init(lua_State *L) build_meta(L, "treesitter_querycursor", querycursor_meta); } -int tslua_register_lang(lua_State *L) +int tslua_has_language(lua_State *L) +{ + const char *lang_name = luaL_checkstring(L, 1); + lua_pushboolean(L, pmap_has(cstr_t)(langs, lang_name)); + return 1; +} + +int tslua_add_language(lua_State *L) { if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { return luaL_error(L, "string expected"); @@ -267,17 +271,22 @@ static const char *input_cb(void *payload, uint32_t byte_index, } char_u *line = ml_get_buf(bp, position.row+1, false); size_t len = STRLEN(line); - size_t tocopy = MIN(len-position.column, BUFSIZE); - - memcpy(buf, line+position.column, tocopy); - // Translate embedded \n to NUL - memchrsub(buf, '\n', '\0', tocopy); - *bytes_read = (uint32_t)tocopy; - if (tocopy < BUFSIZE) { - // now add the final \n. If it didn't fit, input_cb will be called again - // on the same line with advanced column. - buf[tocopy] = '\n'; - (*bytes_read)++; + + if (position.column > len) { + *bytes_read = 0; + } else { + size_t tocopy = MIN(len-position.column, BUFSIZE); + + memcpy(buf, line+position.column, tocopy); + // Translate embedded \n to NUL + memchrsub(buf, '\n', '\0', tocopy); + *bytes_read = (uint32_t)tocopy; + if (tocopy < BUFSIZE) { + // now add the final \n. If it didn't fit, input_cb will be called again + // on the same line with advanced column. + buf[tocopy] = '\n'; + (*bytes_read)++; + } } return buf; #undef BUFSIZE diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 8ba550ea31..43a0a76b94 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -212,6 +212,18 @@ do vim.api.nvim_put(lines, 'c', true, true) -- XXX: Normal-mode: workaround bad cursor-placement after first chunk. vim.api.nvim_command('normal! a') + elseif phase < 2 and mode == 'R' then + local nchars = 0 + for _, line in ipairs(lines) do + nchars = nchars + line:len() + end + local row, col = unpack(vim.api.nvim_win_get_cursor(0)) + local bufline = vim.api.nvim_buf_get_lines(0, row-1, row, true)[1] + local firstline = lines[1] + firstline = bufline:sub(1, col)..firstline + lines[1] = firstline + lines[#lines] = lines[#lines]..bufline:sub(col + nchars + 1, bufline:len()) + vim.api.nvim_buf_set_lines(0, row-1, row, false, lines) else vim.api.nvim_put(lines, 'c', false, true) end @@ -320,9 +332,26 @@ do return pcall_ret(pcall(fn, ...)) end end + + vim.b = make_meta_accessor( + nil_wrap(function(v) return a.nvim_buf_get_var(0, v) end), + function(v, k) return a.nvim_buf_set_var(0, v, k) end, + function(v) return a.nvim_buf_del_var(0, v) end + ) + vim.w = make_meta_accessor( + nil_wrap(function(v) return a.nvim_win_get_var(0, v) end), + function(v, k) return a.nvim_win_set_var(0, v, k) end, + function(v) return a.nvim_win_del_var(0, v) end + ) + vim.t = make_meta_accessor( + nil_wrap(function(v) return a.nvim_tabpage_get_var(0, v) end), + function(v, k) return a.nvim_tabpage_set_var(0, v, k) end, + function(v) return a.nvim_tabpage_del_var(0, v) end + ) vim.g = make_meta_accessor(nil_wrap(a.nvim_get_var), a.nvim_set_var, a.nvim_del_var) vim.v = make_meta_accessor(nil_wrap(a.nvim_get_vvar), a.nvim_set_vvar) vim.o = make_meta_accessor(a.nvim_get_option, a.nvim_set_option) + local function getenv(k) local v = vim.fn.getenv(k) if v == vim.NIL then diff --git a/src/nvim/macros.h b/src/nvim/macros.h index f6c8c0a4a0..3df7fa768d 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -36,38 +36,34 @@ #define BUFEMPTY() (curbuf->b_ml.ml_line_count == 1 && *ml_get((linenr_T)1) == \ NUL) -/* - * toupper() and tolower() that use the current locale. - * Careful: Only call TOUPPER_LOC() and TOLOWER_LOC() with a character in the - * range 0 - 255. toupper()/tolower() on some systems can't handle others. - * Note: It is often better to use mb_tolower() and mb_toupper(), because many - * toupper() and tolower() implementations only work for ASCII. - */ +// toupper() and tolower() that use the current locale. +// Careful: Only call TOUPPER_LOC() and TOLOWER_LOC() with a character in the +// range 0 - 255. toupper()/tolower() on some systems can't handle others. +// Note: It is often better to use mb_tolower() and mb_toupper(), because many +// toupper() and tolower() implementations only work for ASCII. #define TOUPPER_LOC toupper #define TOLOWER_LOC tolower -/* toupper() and tolower() for ASCII only and ignore the current locale. */ +// toupper() and tolower() for ASCII only and ignore the current locale. # define TOUPPER_ASC(c) (((c) < 'a' || (c) > 'z') ? (c) : (c) - ('a' - 'A')) # define TOLOWER_ASC(c) (((c) < 'A' || (c) > 'Z') ? (c) : (c) + ('a' - 'A')) -/* Like isalpha() but reject non-ASCII characters. Can't be used with a - * special key (negative value). */ +// Like isalpha() but reject non-ASCII characters. Can't be used with a +// special key (negative value). # define ASCII_ISLOWER(c) ((unsigned)(c) >= 'a' && (unsigned)(c) <= 'z') # define ASCII_ISUPPER(c) ((unsigned)(c) >= 'A' && (unsigned)(c) <= 'Z') # define ASCII_ISALPHA(c) (ASCII_ISUPPER(c) || ASCII_ISLOWER(c)) # define ASCII_ISALNUM(c) (ASCII_ISALPHA(c) || ascii_isdigit(c)) -/* Returns empty string if it is NULL. */ +// Returns empty string if it is NULL. #define EMPTY_IF_NULL(x) ((x) ? (x) : (char_u *)"") -/* - * Adjust chars in a language according to 'langmap' option. - * NOTE that there is no noticeable overhead if 'langmap' is not set. - * When set the overhead for characters < 256 is small. - * Don't apply 'langmap' if the character comes from the Stuff buffer or from a - * mapping and the langnoremap option was set. - * The do-while is just to ignore a ';' after the macro. - */ +// Adjust chars in a language according to 'langmap' option. +// NOTE that there is no noticeable overhead if 'langmap' is not set. +// When set the overhead for characters < 256 is small. +// Don't apply 'langmap' if the character comes from the Stuff buffer or from a +// mapping and the langnoremap option was set. +// The do-while is just to ignore a ';' after the macro. # define LANGMAP_ADJUST(c, condition) \ do { \ if (*p_langmap \ @@ -83,12 +79,12 @@ } \ } while (0) -#define WRITEBIN "wb" /* no CR-LF translation */ +#define WRITEBIN "wb" // no CR-LF translation #define READBIN "rb" #define APPENDBIN "ab" -/* mch_open_rw(): invoke os_open() with third argument for user R/W. */ -#if defined(UNIX) /* open in rw------- mode */ +// mch_open_rw(): invoke os_open() with third argument for user R/W. +#if defined(UNIX) // open in rw------- mode # define mch_open_rw(n, f) os_open((n), (f), (mode_t)0600) #elif defined(WIN32) # define mch_open_rw(n, f) os_open((n), (f), S_IREAD | S_IWRITE) @@ -100,7 +96,7 @@ # define UTF_COMPOSINGLIKE(p1, p2) utf_composinglike((p1), (p2)) -/* Whether to draw the vertical bar on the right side of the cell. */ +// Whether to draw the vertical bar on the right side of the cell. # define CURSOR_BAR_RIGHT (curwin->w_p_rl && (!(State & CMDLINE) || cmdmsg_rl)) // MB_PTR_ADV(): advance a pointer to the next character, taking care of diff --git a/src/nvim/main.c b/src/nvim/main.c index a816221a9e..4a9f2371a2 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -10,6 +10,7 @@ #include <msgpack.h> #include "nvim/ascii.h" +#include "nvim/channel.h" #include "nvim/vim.h" #include "nvim/main.h" #include "nvim/aucmd.h" @@ -207,7 +208,7 @@ void early_init(void) // Allocate the first window and buffer. // Can't do anything without it, exit when it fails. if (!win_alloc_first()) { - mch_exit(0); + os_exit(0); } init_yank(); // init yank buffers @@ -293,8 +294,8 @@ int main(int argc, char **argv) if (params.diff_mode && params.window_count == -1) params.window_count = 0; /* open up to 3 windows */ - /* Don't redraw until much later. */ - ++RedrawingDisabled; + // Don't redraw until much later. + RedrawingDisabled++; setbuf(stdout, NULL); @@ -384,23 +385,17 @@ int main(int argc, char **argv) syn_maybe_on(); } - /* - * Read all the plugin files. - * Only when compiled with +eval, since most plugins need it. - */ + // Read all the plugin files. load_plugins(); // Decide about window layout for diff mode after reading vimrc. set_window_layout(¶ms); - /* - * Recovery mode without a file name: List swap files. - * This uses the 'dir' option, therefore it must be after the - * initializations. - */ + // Recovery mode without a file name: List swap files. + // Uses the 'dir' option, therefore it must be after the initializations. if (recoverymode && fname == NULL) { - recover_names(NULL, TRUE, 0, NULL); - mch_exit(0); + recover_names(NULL, true, 0, NULL); + os_exit(0); } // Set some option defaults after reading vimrc files. @@ -431,17 +426,15 @@ int main(int argc, char **argv) set_vim_var_list(VV_OLDFILES, tv_list_alloc(0)); } - /* - * "-q errorfile": Load the error file now. - * If the error file can't be read, exit before doing anything else. - */ + // "-q errorfile": Load the error file now. + // If the error file can't be read, exit before doing anything else. handle_quickfix(¶ms); - /* - * Start putting things on the screen. - * Scroll screen down before drawing over it - * Clear screen now, so file message will not be cleared. - */ + // + // Start putting things on the screen. + // Scroll screen down before drawing over it + // Clear screen now, so file message will not be cleared. + // starting = NO_BUFFERS; no_wait_return = false; if (!exmode_active) { @@ -473,27 +466,26 @@ int main(int argc, char **argv) no_wait_return = true; - /* - * Create the requested number of windows and edit buffers in them. - * Also does recovery if "recoverymode" set. - */ + // + // Create the requested number of windows and edit buffers in them. + // Also does recovery if "recoverymode" set. + // create_windows(¶ms); TIME_MSG("opening buffers"); - /* clear v:swapcommand */ + // Clear v:swapcommand set_vim_var_string(VV_SWAPCOMMAND, NULL, -1); - /* Ex starts at last line of the file */ - if (exmode_active) + // Ex starts at last line of the file. + if (exmode_active) { curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + } apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf); TIME_MSG("BufEnter autocommands"); setpcmark(); - /* - * When started with "-q errorfile" jump to first error now. - */ + // When started with "-q errorfile" jump to first error now. if (params.edit_type == EDIT_QF) { qf_jump(NULL, 0, 0, FALSE); TIME_MSG("jump to first error"); @@ -505,26 +497,23 @@ int main(int argc, char **argv) xfree(cwd); if (params.diff_mode) { - /* set options in each window for "nvim -d". */ + // set options in each window for "nvim -d". FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { diff_win_options(wp, TRUE); } } - /* - * Shorten any of the filenames, but only when absolute. - */ - shorten_fnames(FALSE); + // Shorten any of the filenames, but only when absolute. + shorten_fnames(false); - /* - * Need to jump to the tag before executing the '-c command'. - * Makes "vim -c '/return' -t main" work. - */ + // Need to jump to the tag before executing the '-c command'. + // Makes "vim -c '/return' -t main" work. handle_tag(params.tagname); - /* Execute any "+", "-c" and "-S" arguments. */ - if (params.n_commands > 0) + // Execute any "+", "-c" and "-S" arguments. + if (params.n_commands > 0) { exe_commands(¶ms); + } starting = 0; @@ -535,9 +524,10 @@ int main(int argc, char **argv) // 'autochdir' has been postponed. do_autochdir(); - /* start in insert mode */ - if (p_im) - need_start_insertmode = TRUE; + // start in insert mode + if (p_im) { + need_start_insertmode = true; + } set_vim_var_nr(VV_VIM_DID_ENTER, 1L); apply_autocmds(EVENT_VIMENTER, NULL, NULL, false, curbuf); @@ -553,18 +543,19 @@ int main(int argc, char **argv) // main loop. set_reg_var(get_default_register_name()); - /* When a startup script or session file setup for diff'ing and - * scrollbind, sync the scrollbind now. */ + // When a startup script or session file setup for diff'ing and + // scrollbind, sync the scrollbind now. if (curwin->w_p_diff && curwin->w_p_scb) { update_topline(); check_scrollbind((linenr_T)0, 0L); TIME_MSG("diff scrollbinding"); } - /* If ":startinsert" command used, stuff a dummy command to be able to - * call normal_cmd(), which will then start Insert mode. */ - if (restart_edit != 0) + // If ":startinsert" command used, stuff a dummy command to be able to + // call normal_cmd(), which will then start Insert mode. + if (restart_edit != 0) { stuffcharReadbuff(K_NOP); + } // WORKAROUND(mhi): #3023 if (cb_flags & CB_UNNAMEDMASK) { @@ -574,9 +565,7 @@ int main(int argc, char **argv) TIME_MSG("before starting main loop"); ILOG("starting main loop"); - /* - * Call the main command loop. This never returns. - */ + // Main loop: never returns. normal_enter(false, false); #if defined(WIN32) && !defined(MAKE_LIB) @@ -585,6 +574,31 @@ int main(int argc, char **argv) return 0; } +void os_exit(int r) + FUNC_ATTR_NORETURN +{ + exiting = true; + + ui_flush(); + ui_call_stop(); + ml_close_all(true); // remove all memfiles + + if (!event_teardown() && r == 0) { + r = 1; // Exit with error if main_loop did not teardown gracefully. + } + if (input_global_fd() >= 0) { + stream_set_blocking(input_global_fd(), true); // normalize stream (#2598) + } + + ILOG("Nvim exit: %d", r); + +#ifdef EXITFREE + free_all_mem(); +#endif + + exit(r); +} + /// Exit properly void getout(int exitval) FUNC_ATTR_NORETURN @@ -679,7 +693,7 @@ void getout(int exitval) garbage_collect(false); } - mch_exit(exitval); + os_exit(exitval); } /// Gets the integer value of a numeric command line argument if given, @@ -799,10 +813,10 @@ static void command_line_scan(mparm_T *parmp) // "--cmd <cmd>" execute cmd before vimrc if (STRICMP(argv[0] + argv_idx, "help") == 0) { usage(); - mch_exit(0); + os_exit(0); } else if (STRICMP(argv[0] + argv_idx, "version") == 0) { version(); - mch_exit(0); + os_exit(0); } else if (STRICMP(argv[0] + argv_idx, "api-info") == 0) { FileDescriptor fp; const int fof_ret = file_open_fd(&fp, STDOUT_FILENO, @@ -825,7 +839,7 @@ static void command_line_scan(mparm_T *parmp) if (ff_ret < 0) { msgpack_file_write_error(ff_ret); } - mch_exit(0); + os_exit(0); } else if (STRICMP(argv[0] + argv_idx, "headless") == 0) { headless_mode = true; } else if (STRICMP(argv[0] + argv_idx, "embed") == 0) { @@ -891,7 +905,7 @@ static void command_line_scan(mparm_T *parmp) case '?': // "-?" give help message (for MS-Windows) case 'h': { // "-h" give help message usage(); - mch_exit(0); + os_exit(0); } case 'H': { // "-H" start in Hebrew mode: rl + hkmap set. p_hkmap = true; @@ -988,7 +1002,7 @@ static void command_line_scan(mparm_T *parmp) } case 'v': { version(); - mch_exit(0); + os_exit(0); } case 'V': { // "-V{N}" Verbose level // default is 10: a little bit verbose @@ -1116,7 +1130,7 @@ scripterror: _("Attempt to open script file again: \"%s %s\"\n"), argv[-1], argv[0]); mch_errmsg((const char *)IObuff); - mch_exit(2); + os_exit(2); } int error; if (strequal(argv[0], "-")) { @@ -1135,7 +1149,7 @@ scripterror: _("Cannot open for reading: \"%s\": %s\n"), argv[0], os_strerror(error)); mch_errmsg((const char *)IObuff); - mch_exit(2); + os_exit(2); } save_typebuf(); break; @@ -1173,7 +1187,7 @@ scripterror: mch_errmsg(_("Cannot open for script output: \"")); mch_errmsg(argv[0]); mch_errmsg("\"\n"); - mch_exit(2); + os_exit(2); } break; } @@ -1380,7 +1394,7 @@ static void handle_quickfix(mparm_T *paramp) vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef); if (qf_init(NULL, p_ef, p_efm, true, IObuff, p_menc) < 0) { msg_putchar('\n'); - mch_exit(3); + os_exit(3); } TIME_MSG("reading errorfile"); } @@ -1943,7 +1957,7 @@ static void mainerr(const char *errstr, const char *str) mch_errmsg(prgname); mch_errmsg(" -h\"\n"); - mch_exit(1); + os_exit(1); } /// Prints version information for "nvim -v" or "nvim --version". diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c index 52e602cd94..6dd452b5af 100644 --- a/src/nvim/marktree.c +++ b/src/nvim/marktree.c @@ -197,7 +197,7 @@ static inline void split_node(MarkTree *b, mtnode_t *x, const int i) // x must not be a full node (even if there might be internal space) static inline void marktree_putp_aux(MarkTree *b, mtnode_t *x, mtkey_t k) { - int i = x->n - 1; + int i; if (x->level == 0) { i = marktree_getp_aux(x, k, 0); if (i != x->n - 1) { @@ -849,7 +849,8 @@ bool marktree_splice(MarkTree *b, bool same_line = old_extent.row == 0 && new_extent.row == 0; unrelative(start, &old_extent); unrelative(start, &new_extent); - MarkTreeIter itr[1], enditr[1]; + MarkTreeIter itr[1] = { 0 }; + MarkTreeIter enditr[1] = { 0 }; mtpos_t oldbase[MT_MAX_DEPTH]; @@ -905,7 +906,7 @@ continue_same_node: refkey(b, itr->node, itr->i); refkey(b, enditr->node, enditr->i); } else { - past_right = true; + past_right = true; // NOLINT break; } } @@ -1003,7 +1004,7 @@ void marktree_move_region(MarkTree *b, mtpos_t start = { start_row, start_col }, size = { extent_row, extent_col }; mtpos_t end = size; unrelative(start, &end); - MarkTreeIter itr[1]; + MarkTreeIter itr[1] = { 0 }; marktree_itr_get_ext(b, start, itr, false, true, NULL); kvec_t(mtkey_t) saved = KV_INITIAL_VALUE; while (itr->node) { diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 85e6697bfb..e67be60aa6 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -265,68 +265,70 @@ static struct { const char *name; int canon; } enc_alias_table[] = { - {"ansi", IDX_LATIN_1}, - {"iso-8859-1", IDX_LATIN_1}, - {"latin2", IDX_ISO_2}, - {"latin3", IDX_ISO_3}, - {"latin4", IDX_ISO_4}, - {"cyrillic", IDX_ISO_5}, - {"arabic", IDX_ISO_6}, - {"greek", IDX_ISO_7}, - {"hebrew", IDX_ISO_8}, - {"latin5", IDX_ISO_9}, - {"turkish", IDX_ISO_9}, /* ? */ - {"latin6", IDX_ISO_10}, - {"nordic", IDX_ISO_10}, /* ? */ - {"thai", IDX_ISO_11}, /* ? */ - {"latin7", IDX_ISO_13}, - {"latin8", IDX_ISO_14}, - {"latin9", IDX_ISO_15}, - {"utf8", IDX_UTF8}, - {"unicode", IDX_UCS2}, - {"ucs2", IDX_UCS2}, - {"ucs2be", IDX_UCS2}, - {"ucs-2be", IDX_UCS2}, - {"ucs2le", IDX_UCS2LE}, - {"utf16", IDX_UTF16}, - {"utf16be", IDX_UTF16}, - {"utf-16be", IDX_UTF16}, - {"utf16le", IDX_UTF16LE}, - {"ucs4", IDX_UCS4}, - {"ucs4be", IDX_UCS4}, - {"ucs-4be", IDX_UCS4}, - {"ucs4le", IDX_UCS4LE}, - {"utf32", IDX_UCS4}, - {"utf-32", IDX_UCS4}, - {"utf32be", IDX_UCS4}, - {"utf-32be", IDX_UCS4}, - {"utf32le", IDX_UCS4LE}, - {"utf-32le", IDX_UCS4LE}, - {"932", IDX_CP932}, - {"949", IDX_CP949}, - {"936", IDX_CP936}, - {"gbk", IDX_CP936}, - {"950", IDX_CP950}, - {"eucjp", IDX_EUC_JP}, - {"unix-jis", IDX_EUC_JP}, - {"ujis", IDX_EUC_JP}, - {"shift-jis", IDX_SJIS}, - {"pck", IDX_SJIS}, /* Sun: PCK */ - {"euckr", IDX_EUC_KR}, - {"5601", IDX_EUC_KR}, /* Sun: KS C 5601 */ - {"euccn", IDX_EUC_CN}, - {"gb2312", IDX_EUC_CN}, - {"euctw", IDX_EUC_TW}, - {"japan", IDX_EUC_JP}, - {"korea", IDX_EUC_KR}, - {"prc", IDX_EUC_CN}, - {"chinese", IDX_EUC_CN}, - {"taiwan", IDX_EUC_TW}, - {"cp950", IDX_BIG5}, - {"950", IDX_BIG5}, - {"mac", IDX_MACROMAN}, - {"mac-roman", IDX_MACROMAN}, - {NULL, 0} + { "ansi", IDX_LATIN_1 }, + { "iso-8859-1", IDX_LATIN_1 }, + { "latin2", IDX_ISO_2 }, + { "latin3", IDX_ISO_3 }, + { "latin4", IDX_ISO_4 }, + { "cyrillic", IDX_ISO_5 }, + { "arabic", IDX_ISO_6 }, + { "greek", IDX_ISO_7 }, + { "hebrew", IDX_ISO_8 }, + { "latin5", IDX_ISO_9 }, + { "turkish", IDX_ISO_9 }, // ? + { "latin6", IDX_ISO_10 }, + { "nordic", IDX_ISO_10 }, // ? + { "thai", IDX_ISO_11 }, // ? + { "latin7", IDX_ISO_13 }, + { "latin8", IDX_ISO_14 }, + { "latin9", IDX_ISO_15 }, + { "utf8", IDX_UTF8 }, + { "unicode", IDX_UCS2 }, + { "ucs2", IDX_UCS2 }, + { "ucs2be", IDX_UCS2 }, + { "ucs-2be", IDX_UCS2 }, + { "ucs2le", IDX_UCS2LE }, + { "utf16", IDX_UTF16 }, + { "utf16be", IDX_UTF16 }, + { "utf-16be", IDX_UTF16 }, + { "utf16le", IDX_UTF16LE }, + { "ucs4", IDX_UCS4 }, + { "ucs4be", IDX_UCS4 }, + { "ucs-4be", IDX_UCS4 }, + { "ucs4le", IDX_UCS4LE }, + { "utf32", IDX_UCS4 }, + { "utf-32", IDX_UCS4 }, + { "utf32be", IDX_UCS4 }, + { "utf-32be", IDX_UCS4 }, + { "utf32le", IDX_UCS4LE }, + { "utf-32le", IDX_UCS4LE }, + { "932", IDX_CP932 }, + { "949", IDX_CP949 }, + { "936", IDX_CP936 }, + { "gbk", IDX_CP936 }, + { "950", IDX_CP950 }, + { "eucjp", IDX_EUC_JP }, + { "unix-jis", IDX_EUC_JP }, + { "ujis", IDX_EUC_JP }, + { "shift-jis", IDX_SJIS }, + { "pck", IDX_SJIS }, // Sun: PCK + { "euckr", IDX_EUC_KR }, + { "5601", IDX_EUC_KR }, // Sun: KS C 5601 + { "euccn", IDX_EUC_CN }, + { "gb2312", IDX_EUC_CN }, + { "euctw", IDX_EUC_TW }, + { "japan", IDX_EUC_JP }, + { "korea", IDX_EUC_KR }, + { "prc", IDX_EUC_CN }, + { "zh-cn", IDX_EUC_CN }, + { "chinese", IDX_EUC_CN }, + { "zh-tw", IDX_EUC_TW }, + { "taiwan", IDX_EUC_TW }, + { "cp950", IDX_BIG5 }, + { "950", IDX_BIG5 }, + { "mac", IDX_MACROMAN }, + { "mac-roman", IDX_MACROMAN }, + { NULL, 0 } }; /* diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 922b684120..6e074b3249 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1504,16 +1504,15 @@ static time_t swapfile_info(char_u *fname) int fd; struct block0 b0; time_t x = (time_t)0; - char *p; #ifdef UNIX char uname[B0_UNAME_SIZE]; #endif - /* print the swap file date */ + // print the swap file date FileInfo file_info; if (os_fileinfo((char *)fname, &file_info)) { #ifdef UNIX - /* print name of owner of the file */ + // print name of owner of the file if (os_get_uname(file_info.stat.st_uid, uname, B0_UNAME_SIZE) == OK) { MSG_PUTS(_(" owned by: ")); msg_outtrans((char_u *)uname); @@ -1522,11 +1521,8 @@ static time_t swapfile_info(char_u *fname) #endif MSG_PUTS(_(" dated: ")); x = file_info.stat.st_mtim.tv_sec; - p = ctime(&x); // includes '\n' - if (p == NULL) - MSG_PUTS("(invalid)\n"); - else - MSG_PUTS(p); + char ctime_buf[50]; + MSG_PUTS(os_ctime_r(&x, ctime_buf, sizeof(ctime_buf))); } /* @@ -1863,7 +1859,10 @@ errorret: // Avoid giving this message for a recursive call, may happen // when the GUI redraws part of the text. recursive++; - IEMSGN(_("E316: ml_get: cannot find line %" PRId64), lnum); + get_trans_bufname(buf); + shorten_dir(NameBuff); + iemsgf(_("E316: ml_get: cannot find line %" PRId64 " in buffer %d %s"), + lnum, buf->b_fnum, NameBuff); recursive--; } goto errorret; @@ -2391,21 +2390,32 @@ static int ml_append_int( void ml_add_deleted_len(char_u *ptr, ssize_t len) { + ml_add_deleted_len_buf(curbuf, ptr, len); +} + +void ml_add_deleted_len_buf(buf_T *buf, char_u *ptr, ssize_t len) +{ if (inhibit_delete_count) { return; } if (len == -1) { len = STRLEN(ptr); } - curbuf->deleted_bytes += len+1; - if (curbuf->update_need_codepoints) { - mb_utflen(ptr, len, &curbuf->deleted_codepoints, - &curbuf->deleted_codeunits); - curbuf->deleted_codepoints++; // NL char - curbuf->deleted_codeunits++; + buf->deleted_bytes += len+1; + if (buf->update_need_codepoints) { + mb_utflen(ptr, len, &buf->deleted_codepoints, + &buf->deleted_codeunits); + buf->deleted_codepoints++; // NL char + buf->deleted_codeunits++; } } + +int ml_replace(linenr_T lnum, char_u *line, bool copy) +{ + return ml_replace_buf(curbuf, lnum, line, copy); +} + /* * Replace line lnum, with buffering, in current buffer. * @@ -2417,36 +2427,37 @@ void ml_add_deleted_len(char_u *ptr, ssize_t len) * * return FAIL for failure, OK otherwise */ -int ml_replace(linenr_T lnum, char_u *line, bool copy) +int ml_replace_buf(buf_T *buf, linenr_T lnum, char_u *line, bool copy) { if (line == NULL) /* just checking... */ return FAIL; - /* When starting up, we might still need to create the memfile */ - if (curbuf->b_ml.ml_mfp == NULL && open_buffer(FALSE, NULL, 0) == FAIL) + // When starting up, we might still need to create the memfile + if (buf->b_ml.ml_mfp == NULL && open_buffer(false, NULL, 0) == FAIL) { return FAIL; + } bool readlen = true; if (copy) { line = vim_strsave(line); } - if (curbuf->b_ml.ml_line_lnum != lnum) { // other line buffered - ml_flush_line(curbuf); // flush it - } else if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) { // same line allocated - ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, -1); + if (buf->b_ml.ml_line_lnum != lnum) { // other line buffered + ml_flush_line(buf); // flush it + } else if (buf->b_ml.ml_flags & ML_LINE_DIRTY) { // same line allocated + ml_add_deleted_len(buf->b_ml.ml_line_ptr, -1); readlen = false; // already added the length - xfree(curbuf->b_ml.ml_line_ptr); // free it + xfree(buf->b_ml.ml_line_ptr); // free it } - if (readlen && kv_size(curbuf->update_callbacks)) { - ml_add_deleted_len(ml_get_buf(curbuf, lnum, false), -1); + if (readlen && kv_size(buf->update_callbacks)) { + ml_add_deleted_len(ml_get_buf(buf, lnum, false), -1); } - curbuf->b_ml.ml_line_ptr = line; - curbuf->b_ml.ml_line_lnum = lnum; - curbuf->b_ml.ml_flags = (curbuf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY; + buf->b_ml.ml_line_ptr = line; + buf->b_ml.ml_line_lnum = lnum; + buf->b_ml.ml_flags = (buf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY; return OK; } @@ -3264,15 +3275,13 @@ attention_message ( ) { assert(buf->b_fname != NULL); - time_t x, sx; - char *p; ++no_wait_return; (void)EMSG(_("E325: ATTENTION")); MSG_PUTS(_("\nFound a swap file by the name \"")); msg_home_replace(fname); MSG_PUTS("\"\n"); - sx = swapfile_info(fname); + const time_t swap_mtime = swapfile_info(fname); MSG_PUTS(_("While opening file \"")); msg_outtrans(buf->b_fname); MSG_PUTS("\"\n"); @@ -3281,14 +3290,12 @@ attention_message ( MSG_PUTS(_(" CANNOT BE FOUND")); } else { MSG_PUTS(_(" dated: ")); - x = file_info.stat.st_mtim.tv_sec; - p = ctime(&x); // includes '\n' - if (p == NULL) - MSG_PUTS("(invalid)\n"); - else - MSG_PUTS(p); - if (sx != 0 && x > sx) + time_t x = file_info.stat.st_mtim.tv_sec; + char ctime_buf[50]; + MSG_PUTS(os_ctime_r(&x, ctime_buf, sizeof(ctime_buf))); + if (swap_mtime != 0 && x > swap_mtime) { MSG_PUTS(_(" NEWER than swap file!\n")); + } } /* Some of these messages are long to allow translation to * other languages. */ @@ -3994,8 +4001,8 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff) int ffdos = !no_ff && (get_fileformat(buf) == EOL_DOS); int extra = 0; - /* take care of cached line first */ - ml_flush_line(curbuf); + // take care of cached line first + ml_flush_line(buf); if (buf->b_ml.ml_usedchunks == -1 || buf->b_ml.ml_chunksize == NULL diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 18c4f1fff1..b060464383 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -27,6 +27,8 @@ #include "nvim/strings.h" #include "nvim/ui.h" #include "nvim/eval/typval.h" +#include "nvim/screen.h" +#include "nvim/window.h" #define MENUDEPTH 10 /* maximum depth of menus */ @@ -46,6 +48,24 @@ static char_u e_notsubmenu[] = N_( static char_u e_othermode[] = N_("E328: Menu only exists in another mode"); static char_u e_nomenu[] = N_("E329: No menu \"%s\""); +// Return true if "name" is a window toolbar menu name. +static bool menu_is_winbar(const char_u *const name) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + return (STRNCMP(name, "WinBar", 6) == 0); +} + +int winbar_height(const win_T *const wp) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + return wp->w_winbar != NULL && wp->w_winbar->children != NULL ? 1 : 0; +} + +static vimmenu_T **get_root_menu(const char_u *const name) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + return menu_is_winbar(name) ? &curwin->w_winbar : &root_menu; +} /// Do the :menu command and relatives. /// @param eap Ex command arguments @@ -170,6 +190,12 @@ ex_menu(exarg_T *eap) goto theend; } + vimmenu_T **root_menu_ptr = get_root_menu(menu_path); + if (root_menu_ptr == &curwin->w_winbar) { + // Assume the window toolbar menu will change. + redraw_later(NOT_VALID); + } + if (enable != kNone) { // Change sensitivity of the menu. // For the PopUp menu, remove a menu for each mode separately. @@ -182,11 +208,11 @@ ex_menu(exarg_T *eap) for (i = 0; i < MENU_INDEX_TIP; ++i) if (modes & (1 << i)) { p = popup_mode_name(menu_path, i); - menu_enable_recurse(root_menu, p, MENU_ALL_MODES, enable); + menu_enable_recurse(*root_menu_ptr, p, MENU_ALL_MODES, enable); xfree(p); } } - menu_enable_recurse(root_menu, menu_path, modes, enable); + menu_enable_recurse(*root_menu_ptr, menu_path, modes, enable); } else if (unmenu) { /* * Delete menu(s). @@ -201,13 +227,13 @@ ex_menu(exarg_T *eap) for (i = 0; i < MENU_INDEX_TIP; ++i) if (modes & (1 << i)) { p = popup_mode_name(menu_path, i); - remove_menu(&root_menu, p, MENU_ALL_MODES, TRUE); + remove_menu(root_menu_ptr, p, MENU_ALL_MODES, true); xfree(p); } } - /* Careful: remove_menu() changes menu_path */ - remove_menu(&root_menu, menu_path, modes, FALSE); + // Careful: remove_menu() changes menu_path + remove_menu(root_menu_ptr, menu_path, modes, false); } else { /* * Add menu(s). @@ -244,6 +270,19 @@ ex_menu(exarg_T *eap) xfree(map_buf); } + if (root_menu_ptr == &curwin->w_winbar) { + const int h = winbar_height(curwin); + + if (h != curwin->w_winbar_height) { + if (h == 0) { + curwin->w_height++; + } else if (curwin->w_height > 0) { + curwin->w_height--; + } + curwin->w_winbar_height = h; + } + } + ui_call_update_menu(); theend: @@ -266,7 +305,6 @@ add_menu_path( { char_u *path_name; int modes = menuarg->modes; - vimmenu_T **menup; vimmenu_T *menu = NULL; vimmenu_T *parent; vimmenu_T **lower_pri; @@ -285,7 +323,8 @@ add_menu_path( /* Make a copy so we can stuff around with it, since it could be const */ path_name = vim_strsave(menu_path); - menup = &root_menu; + vimmenu_T **root_menu_ptr = get_root_menu(menu_path); + vimmenu_T **menup = root_menu_ptr; parent = NULL; name = path_name; while (*name) { @@ -468,14 +507,16 @@ erret: /* Delete any empty submenu we added before discovering the error. Repeat * for higher levels. */ while (parent != NULL && parent->children == NULL) { - if (parent->parent == NULL) - menup = &root_menu; - else + if (parent->parent == NULL) { + menup = root_menu_ptr; + } else { menup = &parent->parent->children; - for (; *menup != NULL && *menup != parent; menup = &((*menup)->next)) - ; - if (*menup == NULL) /* safety check */ + } + for (; *menup != NULL && *menup != parent; menup = &((*menup)->next)) { + } + if (*menup == NULL) { // safety check break; + } parent = parent->parent; free_menu(menup); } @@ -620,6 +661,14 @@ remove_menu ( return OK; } +// Remove the WinBar menu from window "wp". +void remove_winbar(win_T *wp) + FUNC_ATTR_NONNULL_ALL +{ + remove_menu(&wp->w_winbar, (char_u *)"", MENU_ALL_MODES, true); + xfree(wp->w_winbar_items); +} + /* * Free the given menu structure and remove it from the linked list. */ @@ -740,7 +789,7 @@ static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) /// @return false if could not find path_name bool menu_get(char_u *const path_name, int modes, list_T *list) { - vimmenu_T *menu = find_menu(root_menu, path_name, modes); + vimmenu_T *menu = find_menu(*get_root_menu(path_name), path_name, modes); if (!menu) { return false; } @@ -802,10 +851,8 @@ static vimmenu_T *find_menu(vimmenu_T *menu, char_u *name, int modes) /// Show the mapping associated with a menu item or hierarchy in a sub-menu. static int show_menus(char_u *const path_name, int modes) { - vimmenu_T *menu; - // First, find the (sub)menu with the given name - menu = find_menu(root_menu, path_name, modes); + vimmenu_T *menu = find_menu(*get_root_menu(path_name), path_name, modes); if (!menu) { return FAIL; } @@ -868,7 +915,7 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth) if (*menu->strings[bit] == NUL) { msg_puts_attr("<Nop>", HL_ATTR(HLF_8)); } else { - msg_outtrans_special(menu->strings[bit], false); + msg_outtrans_special(menu->strings[bit], false, 0); } } } else { @@ -890,6 +937,7 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth) * Used when expanding menu names. */ static vimmenu_T *expand_menu = NULL; +static vimmenu_T *expand_menu_alt = NULL; static int expand_modes = 0x0; static int expand_emenu; /* TRUE for ":emenu" command */ @@ -940,14 +988,15 @@ char_u *set_context_in_menu_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forc // ":popup" only uses menues, not entries expand_menus = !((*cmd == 't' && cmd[1] == 'e') || *cmd == 'p'); expand_emenu = (*cmd == 'e'); - if (expand_menus && ascii_iswhite(*p)) - return NULL; /* TODO: check for next command? */ - if (*p == NUL) { /* Complete the menu name */ - /* - * With :unmenu, you only want to match menus for the appropriate mode. - * With :menu though you might want to add a menu with the same name as - * one in another mode, so match menus from other modes too. - */ + if (expand_menus && ascii_iswhite(*p)) { + return NULL; // TODO(vim): check for next command? + } + if (*p == NUL) { // Complete the menu name + bool try_alt_menu = true; + + // With :unmenu, you only want to match menus for the appropriate mode. + // With :menu though you might want to add a menu with the same name as + // one in another mode, so match menus from other modes too. expand_modes = get_menu_cmd_modes(cmd, forceit, NULL, &unmenu); if (!unmenu) expand_modes = MENU_ALL_MODES; @@ -976,6 +1025,10 @@ char_u *set_context_in_menu_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forc break; } menu = menu->next; + if (menu == NULL && try_alt_menu) { + menu = curwin->w_winbar; + try_alt_menu = false; + } } if (menu == NULL) { /* No menu found with the name we were looking for */ @@ -984,14 +1037,21 @@ char_u *set_context_in_menu_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forc } name = p; menu = menu->children; + try_alt_menu = false; } xfree(path_name); xp->xp_context = expand_menus ? EXPAND_MENUNAMES : EXPAND_MENUS; xp->xp_pattern = after_dot; expand_menu = menu; - } else /* We're in the mapping part */ + if (expand_menu == root_menu) { + expand_menu_alt = curwin->w_winbar; + } else { + expand_menu_alt = NULL; + } + } else { // We're in the mapping part xp->xp_context = EXPAND_NOTHING; + } return NULL; } @@ -1002,19 +1062,26 @@ char_u *set_context_in_menu_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forc char_u *get_menu_name(expand_T *xp, int idx) { static vimmenu_T *menu = NULL; + static bool did_alt_menu = false; char_u *str; static int should_advance = FALSE; if (idx == 0) { /* first call: start at first item */ menu = expand_menu; - should_advance = FALSE; + did_alt_menu = false; + should_advance = false; } /* Skip PopUp[nvoci]. */ while (menu != NULL && (menu_is_hidden(menu->dname) || menu_is_separator(menu->dname) - || menu->children == NULL)) + || menu->children == NULL)) { menu = menu->next; + if (menu == NULL && !did_alt_menu) { + menu = expand_menu_alt; + did_alt_menu = true; + } + } if (menu == NULL) /* at end of linked list */ return NULL; @@ -1030,9 +1097,14 @@ char_u *get_menu_name(expand_T *xp, int idx) else str = (char_u *)""; - if (should_advance) - /* Advance to next menu entry. */ + if (should_advance) { + // Advance to next menu entry. menu = menu->next; + if (menu == NULL && !did_alt_menu) { + menu = expand_menu_alt; + did_alt_menu = true; + } + } should_advance = !should_advance; @@ -1046,6 +1118,7 @@ char_u *get_menu_name(expand_T *xp, int idx) char_u *get_menu_names(expand_T *xp, int idx) { static vimmenu_T *menu = NULL; + static bool did_alt_menu = false; #define TBUFFER_LEN 256 static char_u tbuffer[TBUFFER_LEN]; /*hack*/ char_u *str; @@ -1053,16 +1126,21 @@ char_u *get_menu_names(expand_T *xp, int idx) if (idx == 0) { /* first call: start at first item */ menu = expand_menu; - should_advance = FALSE; + did_alt_menu = false; + should_advance = false; } /* Skip Browse-style entries, popup menus and separators. */ while (menu != NULL - && ( menu_is_hidden(menu->dname) - || (expand_emenu && menu_is_separator(menu->dname)) - || menu->dname[STRLEN(menu->dname) - 1] == '.' - )) + && (menu_is_hidden(menu->dname) + || (expand_emenu && menu_is_separator(menu->dname)) + || menu->dname[STRLEN(menu->dname) - 1] == '.')) { menu = menu->next; + if (menu == NULL && !did_alt_menu) { + menu = expand_menu_alt; + did_alt_menu = true; + } + } if (menu == NULL) /* at end of linked list */ return NULL; @@ -1092,9 +1170,14 @@ char_u *get_menu_names(expand_T *xp, int idx) } else str = (char_u *)""; - if (should_advance) - /* Advance to next menu entry. */ + if (should_advance) { + // Advance to next menu entry. menu = menu->next; + if (menu == NULL && !did_alt_menu) { + menu = expand_menu_alt; + did_alt_menu = true; + } + } should_advance = !should_advance; @@ -1279,29 +1362,27 @@ static char_u *menu_text(const char_u *str, int *mnemonic, char_u **actext) return text; } -/* - * Return TRUE if "name" can be a menu in the MenuBar. - */ -int menu_is_menubar(char_u *name) +// Return true if "name" can be a menu in the MenuBar. +bool menu_is_menubar(const char_u *const name) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { return !menu_is_popup(name) && !menu_is_toolbar(name) + && !menu_is_winbar(name) && *name != MNU_HIDDEN_CHAR; } -/* - * Return TRUE if "name" is a popup menu name. - */ -int menu_is_popup(char_u *name) +// Return true if "name" is a popup menu name. +bool menu_is_popup(const char_u *const name) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { return STRNCMP(name, "PopUp", 5) == 0; } -/* - * Return TRUE if "name" is a toolbar menu name. - */ -int menu_is_toolbar(char_u *name) +// Return true if "name" is a toolbar menu name. +bool menu_is_toolbar(const char_u *const name) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { return STRNCMP(name, "ToolBar", 7) == 0; } @@ -1325,53 +1406,15 @@ static int menu_is_hidden(char_u *name) || (menu_is_popup(name) && name[5] != NUL); } -/* - * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and - * execute it. - */ -void ex_emenu(exarg_T *eap) +// Execute "menu". Use by ":emenu" and the window toolbar. +// "eap" is NULL for the window toolbar. +static void execute_menu(const exarg_T *eap, vimmenu_T *menu) + FUNC_ATTR_NONNULL_ARG(2) { - vimmenu_T *menu; - char_u *name; - char_u *saved_name; - char_u *p; - int idx; - char_u *mode; - - saved_name = vim_strsave(eap->arg); - - menu = root_menu; - name = saved_name; - while (*name) { - /* Find in the menu hierarchy */ - p = menu_name_skip(name); + int idx = -1; + char_u *mode; - while (menu != NULL) { - if (menu_name_equal(name, menu)) { - if (*p == NUL && menu->children != NULL) { - EMSG(_("E333: Menu path must lead to a menu item")); - menu = NULL; - } else if (*p != NUL && menu->children == NULL) { - EMSG(_(e_notsubmenu)); - menu = NULL; - } - break; - } - menu = menu->next; - } - if (menu == NULL || *p == NUL) - break; - menu = menu->children; - name = p; - } - xfree(saved_name); - if (menu == NULL) { - EMSG2(_("E334: Menu not found: %s"), eap->arg); - return; - } - - /* Found the menu, so execute. - * Use the Insert mode entry when returning to Insert mode. */ + // Use the Insert mode entry when returning to Insert mode. if (((State & INSERT) || restart_edit) && !current_sctx.sc_sid) { mode = (char_u *)"Insert"; idx = MENU_INDEX_INSERT; @@ -1384,7 +1427,7 @@ void ex_emenu(exarg_T *eap) * is. Just execute the visual binding for the menu. */ mode = (char_u *)"Visual"; idx = MENU_INDEX_VISUAL; - } else if (eap->addr_count) { + } else if (eap != NULL && eap->addr_count) { pos_T tpos; mode = (char_u *)"Visual"; @@ -1422,9 +1465,13 @@ void ex_emenu(exarg_T *eap) /* Adjust the cursor to make sure it is in the correct pos * for exclusive mode */ - if (*p_sel == 'e' && gchar_cursor() != NUL) - ++curwin->w_cursor.col; - } else { + if (*p_sel == 'e' && gchar_cursor() != NUL) { + curwin->w_cursor.col++; + } + } + + // For the WinBar menu always use the Normal mode menu. + if (idx == -1 || eap == NULL) { mode = (char_u *)"Normal"; idx = MENU_INDEX_NORMAL; } @@ -1432,19 +1479,114 @@ void ex_emenu(exarg_T *eap) assert(idx != MENU_INDEX_INVALID); if (menu->strings[idx] != NULL) { // When executing a script or function execute the commands right now. + // Also for the window toolbar // Otherwise put them in the typeahead buffer. - if (current_sctx.sc_sid != 0) { - exec_normal_cmd(menu->strings[idx], menu->noremap[idx], - menu->silent[idx]); + if (eap == NULL || current_sctx.sc_sid != 0) { + save_state_T save_state; + + ex_normal_busy++; + if (save_current_state(&save_state)) { + exec_normal_cmd(menu->strings[idx], menu->noremap[idx], + menu->silent[idx]); + } + restore_current_state(&save_state); + ex_normal_busy--; } else { ins_typebuf(menu->strings[idx], menu->noremap[idx], 0, true, menu->silent[idx]); } - } else { + } else if (eap != NULL) { EMSG2(_("E335: Menu not defined for %s mode"), mode); } } +// Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and +// execute it. +void ex_emenu(exarg_T *eap) +{ + char_u *saved_name = vim_strsave(eap->arg); + vimmenu_T *menu = *get_root_menu(saved_name); + char_u *name = saved_name; + while (*name) { + // Find in the menu hierarchy + char_u *p = menu_name_skip(name); + + while (menu != NULL) { + if (menu_name_equal(name, menu)) { + if (*p == NUL && menu->children != NULL) { + EMSG(_("E333: Menu path must lead to a menu item")); + menu = NULL; + } else if (*p != NUL && menu->children == NULL) { + EMSG(_(e_notsubmenu)); + menu = NULL; + } + break; + } + menu = menu->next; + } + if (menu == NULL || *p == NUL) { + break; + } + menu = menu->children; + name = p; + } + xfree(saved_name); + if (menu == NULL) { + EMSG2(_("E334: Menu not found: %s"), eap->arg); + return; + } + + // Found the menu, so execute. + execute_menu(eap, menu); +} + +// Handle a click in the window toolbar of "wp" at column "col". +void winbar_click(win_T *wp, int col) + FUNC_ATTR_NONNULL_ALL +{ + if (wp->w_winbar_items == NULL) { + return; + } + for (int idx = 0; wp->w_winbar_items[idx].wb_menu != NULL; idx++) { + winbar_item_T *item = &wp->w_winbar_items[idx]; + + if (col >= item->wb_startcol && col <= item->wb_endcol) { + win_T *save_curwin = NULL; + const pos_T save_visual = VIsual; + const int save_visual_active = VIsual_active; + const int save_visual_select = VIsual_select; + const int save_visual_reselect = VIsual_reselect; + const int save_visual_mode = VIsual_mode; + + if (wp != curwin) { + // Clicking in the window toolbar of a not-current window. + // Make that window the current one and save Visual mode. + save_curwin = curwin; + VIsual_active = false; + curwin = wp; + curbuf = curwin->w_buffer; + check_cursor(); + } + + // Note: the command might close the current window. + execute_menu(NULL, item->wb_menu); + + if (save_curwin != NULL && win_valid(save_curwin)) { + curwin = save_curwin; + curbuf = curwin->w_buffer; + VIsual = save_visual; + VIsual_active = save_visual_active; + VIsual_select = save_visual_select; + VIsual_reselect = save_visual_reselect; + VIsual_mode = save_visual_mode; + } + if (!win_valid(wp)) { + break; + } + } + } +} + /* * Translation of menu names. Just a simple lookup table. */ diff --git a/src/nvim/menu.h b/src/nvim/menu.h index 5ff979f2bf..642d9aafac 100644 --- a/src/nvim/menu.h +++ b/src/nvim/menu.h @@ -6,18 +6,6 @@ #include "nvim/types.h" // for char_u and expand_T #include "nvim/ex_cmds_defs.h" // for exarg_T -/// Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode -/// \addtogroup MENU_INDEX -/// @{ -#define MENU_INDEX_INVALID -1 -#define MENU_INDEX_NORMAL 0 -#define MENU_INDEX_VISUAL 1 -#define MENU_INDEX_SELECT 2 -#define MENU_INDEX_OP_PENDING 3 -#define MENU_INDEX_INSERT 4 -#define MENU_INDEX_CMDLINE 5 -#define MENU_INDEX_TIP 6 -#define MENU_MODES 7 /// @} /// note MENU_INDEX_TIP is not a 'real' mode @@ -37,28 +25,6 @@ /// Start a menu name with this to not include it on the main menu bar #define MNU_HIDDEN_CHAR ']' -typedef struct VimMenu vimmenu_T; - -struct VimMenu { - int modes; ///< Which modes is this menu visible for - int enabled; ///< for which modes the menu is enabled - char_u *name; ///< Name of menu, possibly translated - char_u *dname; ///< Displayed Name ("name" without '&') - char_u *en_name; ///< "name" untranslated, NULL when - ///< was not translated - char_u *en_dname; ///< NULL when "dname" untranslated - int mnemonic; ///< mnemonic key (after '&') - char_u *actext; ///< accelerator text (after TAB) - long priority; ///< Menu order priority - char_u *strings[MENU_MODES]; ///< Mapped string for each mode - int noremap[MENU_MODES]; ///< A \ref REMAP_VALUES flag for each mode - bool silent[MENU_MODES]; ///< A silent flag for each mode - vimmenu_T *children; ///< Children of sub-menu - vimmenu_T *parent; ///< Parent of menu - vimmenu_T *next; ///< Next item in menu -}; - - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "menu.h.generated.h" #endif diff --git a/src/nvim/message.c b/src/nvim/message.c index 94729dfd2a..a12e665099 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1521,7 +1521,8 @@ void msg_make(char_u *arg) /// the character/string -- webb int msg_outtrans_special( const char_u *strstart, - int from ///< true for LHS of a mapping + bool from, ///< true for LHS of a mapping + int maxlen ///< screen columns, 0 for unlimeted ) { if (strstart == NULL) { @@ -1541,6 +1542,9 @@ int msg_outtrans_special( string = str2special((const char **)&str, from, false); } const int len = vim_strsize((char_u *)string); + if (maxlen > 0 && retval + len >= maxlen) { + break; + } // Highlight special keys msg_puts_attr(string, (len > 1 && (*mb_ptr2len)((char_u *)string) <= 1 diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index 8c19a2de66..e10770b6bd 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -73,7 +73,8 @@ static garray_T ga_users = GA_EMPTY_INIT_VALUE; * If "include_space" is set, include trailing whitespace while calculating the * length. */ -int get_leader_len(char_u *line, char_u **flags, int backward, int include_space) +int get_leader_len(char_u *line, char_u **flags, + bool backward, bool include_space) { int i, j; int result; @@ -1161,3 +1162,26 @@ int goto_im(void) { return p_im && stuff_empty() && typebuf_typed(); } + +/// Put the timestamp of an undo header in "buf[buflen]" in a nice format. +void add_time(char_u *buf, size_t buflen, time_t tt) +{ + struct tm curtime; + + if (time(NULL) - tt >= 100) { + os_localtime_r(&tt, &curtime); + if (time(NULL) - tt < (60L * 60L * 12L)) { + // within 12 hours + (void)strftime((char *)buf, buflen, "%H:%M:%S", &curtime); + } else { + // longer ago + (void)strftime((char *)buf, buflen, "%Y/%m/%d %H:%M:%S", &curtime); + } + } else { + int64_t seconds = time(NULL) - tt; + vim_snprintf((char *)buf, buflen, + NGETTEXT("%" PRId64 " second ago", + "%" PRId64 " seconds ago", (uint32_t)seconds), + seconds); + } +} diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index dab2e44890..fcd9ee4f75 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -60,6 +60,7 @@ int jump_to_mouse(int flags, { static int on_status_line = 0; // #lines below bottom of window static int on_sep_line = 0; // on separator right of window + static bool in_winbar = false; static int prev_row = -1; static int prev_col = -1; static win_T *dragwin = NULL; // window being dragged @@ -73,6 +74,7 @@ int jump_to_mouse(int flags, int col = mouse_col; int grid = mouse_grid; int mouse_char; + int fdc = 0; mouse_past_bottom = false; mouse_past_eol = false; @@ -92,10 +94,24 @@ int jump_to_mouse(int flags, retnomove: // before moving the cursor for a left click which is NOT in a status // line, stop Visual mode - if (on_status_line) + if (on_status_line) { return IN_STATUS_LINE; - if (on_sep_line) + } + if (on_sep_line) { return IN_SEP_LINE; + } + if (in_winbar) { + // A quick second click may arrive as a double-click, but we use it + // as a second click in the WinBar. + if ((mod_mask & MOD_MASK_MULTI_CLICK) && !(flags & MOUSE_RELEASED)) { + wp = mouse_find_win(&grid, &row, &col); + if (wp == NULL) { + return IN_UNKNOWN; + } + winbar_click(wp, col); + } + return IN_OTHER_WIN | MOUSE_WINBAR; + } if (flags & MOUSE_MAY_STOP_VIS) { end_visual_mode(); redraw_curbuf_later(INVERTED); // delete the inversion @@ -109,12 +125,12 @@ retnomove: if (flags & MOUSE_SETPOS) goto retnomove; // ugly goto... - // Remember the character under the mouse, it might be a '-' or '+' in the - // fold column. NB: only works for ASCII chars! + // Remember the character under the mouse, might be one of foldclose or + // foldopen fillchars in the fold column. if (row >= 0 && row < Rows && col >= 0 && col <= Columns && default_grid.chars != NULL) { - mouse_char = default_grid.chars[default_grid.line_offset[row] - + (unsigned)col][0]; + mouse_char = utf_ptr2char(default_grid.chars[default_grid.line_offset[row] + + (unsigned)col]); } else { mouse_char = ' '; } @@ -131,7 +147,18 @@ retnomove: if (wp == NULL) { return IN_UNKNOWN; } + fdc = win_fdccol_count(wp); dragwin = NULL; + + if (row == -1) { + // A click in the window toolbar does not enter another window or + // change Visual highlighting. + winbar_click(wp, col); + in_winbar = true; + return IN_OTHER_WIN | MOUSE_WINBAR; + } + in_winbar = false; + // winpos and height may change in win_enter()! if (grid == DEFAULT_GRID_HANDLE && row >= wp->w_height) { // In (or below) status line @@ -165,9 +192,8 @@ retnomove: || (!on_status_line && !on_sep_line && (wp->w_p_rl - ? col < wp->w_width_inner - wp->w_p_fdc - : col >= wp->w_p_fdc + (cmdwin_type == 0 && wp == curwin - ? 0 : 1)) + ? col < wp->w_width_inner - fdc + : col >= fdc + (cmdwin_type == 0 && wp == curwin ? 0 : 1)) && (flags & MOUSE_MAY_STOP_VIS)))) { end_visual_mode(); redraw_curbuf_later(INVERTED); // delete the inversion @@ -222,6 +248,9 @@ retnomove: did_drag |= count; } return IN_SEP_LINE; // Cursor didn't move + } else if (in_winbar) { + // After a click on the window toolbar don't start Visual mode. + return IN_OTHER_WIN | MOUSE_WINBAR; } else { // keep_window_focus must be true // before moving the cursor for a left click, stop Visual mode @@ -305,8 +334,8 @@ retnomove: } // Check for position outside of the fold column. - if (curwin->w_p_rl ? col < curwin->w_width_inner - curwin->w_p_fdc : - col >= curwin->w_p_fdc + (cmdwin_type == 0 ? 0 : 1)) { + if (curwin->w_p_rl ? col < curwin->w_width_inner - fdc : + col >= fdc + (cmdwin_type == 0 ? 0 : 1)) { mouse_char = ' '; } @@ -470,6 +499,7 @@ win_T *mouse_find_win(int *gridp, int *rowp, int *colp) // exist. FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp == fp->fr_win) { + *rowp -= wp->w_winbar_height; return wp; } } diff --git a/src/nvim/mouse.h b/src/nvim/mouse.h index 0149f7c7c0..6c5bc5dc0e 100644 --- a/src/nvim/mouse.h +++ b/src/nvim/mouse.h @@ -16,6 +16,7 @@ #define CURSOR_MOVED 0x100 #define MOUSE_FOLD_CLOSE 0x200 // clicked on '-' in fold column #define MOUSE_FOLD_OPEN 0x400 // clicked on '+' in fold column +#define MOUSE_WINBAR 0x800 // in window toolbar // flags for jump_to_mouse() #define MOUSE_FOCUS 0x01 // need to stay in this window diff --git a/src/nvim/move.c b/src/nvim/move.c index 3ae4f32a83..d4f82bc601 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -86,6 +86,7 @@ static void comp_botline(win_T *wp) /* wp->w_botline is the line that is just below the window */ wp->w_botline = lnum; wp->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; + wp->w_viewport_invalid = true; set_empty_rows(wp, done); @@ -142,7 +143,8 @@ void update_topline(void) int old_topfill; bool check_topline = false; bool check_botline = false; - long save_so = p_so; + long *so_ptr = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so; + long save_so = *so_ptr; // If there is no valid screen and when the window height is zero just use // the cursor line. @@ -150,6 +152,7 @@ void update_topline(void) curwin->w_topline = curwin->w_cursor.lnum; curwin->w_botline = curwin->w_topline; curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; + curwin->w_viewport_invalid = true; curwin->w_scbind_pos = 1; return; } @@ -158,9 +161,10 @@ void update_topline(void) if (curwin->w_valid & VALID_TOPLINE) return; - /* When dragging with the mouse, don't scroll that quickly */ - if (mouse_dragging > 0) - p_so = mouse_dragging - 1; + // When dragging with the mouse, don't scroll that quickly + if (mouse_dragging > 0) { + *so_ptr = mouse_dragging - 1; + } old_topline = curwin->w_topline; old_topfill = curwin->w_topfill; @@ -173,6 +177,7 @@ void update_topline(void) curwin->w_topline = 1; curwin->w_botline = 2; curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; + curwin->w_viewport_invalid = true; curwin->w_scbind_pos = 1; } /* @@ -206,15 +211,17 @@ void update_topline(void) * scrolled). */ n = 0; for (linenr_T lnum = curwin->w_cursor.lnum; - lnum < curwin->w_topline + p_so; ++lnum) { - ++n; - /* stop at end of file or when we know we are far off */ - if (lnum >= curbuf->b_ml.ml_line_count || n >= halfheight) + lnum < curwin->w_topline + *so_ptr; lnum++) { + n++; + // stop at end of file or when we know we are far off + if (lnum >= curbuf->b_ml.ml_line_count || n >= halfheight) { break; + } (void)hasFolding(lnum, NULL, &lnum); } - } else - n = curwin->w_topline + p_so - curwin->w_cursor.lnum; + } else { + n = curwin->w_topline + *so_ptr - curwin->w_cursor.lnum; + } /* If we weren't very close to begin with, we scroll to put the * cursor in the middle of the window. Otherwise put the cursor @@ -247,7 +254,7 @@ void update_topline(void) if (curwin->w_botline <= curbuf->b_ml.ml_line_count) { if (curwin->w_cursor.lnum < curwin->w_botline) { if (((long)curwin->w_cursor.lnum - >= (long)curwin->w_botline - p_so + >= (long)curwin->w_botline - *so_ptr || hasAnyFolding(curwin) )) { lineoff_T loff; @@ -266,13 +273,15 @@ void update_topline(void) && (loff.lnum + 1 < curwin->w_botline || loff.fill == 0) ) { n += loff.height; - if (n >= p_so) + if (n >= *so_ptr) { break; + } botline_forw(&loff); } - if (n >= p_so) - /* sufficient context, no need to scroll */ + if (n >= *so_ptr) { + // sufficient context, no need to scroll check_botline = false; + } } else { /* sufficient context, no need to scroll */ check_botline = false; @@ -285,7 +294,7 @@ void update_topline(void) * botline - p_so (approximation of how much will be * scrolled). */ for (linenr_T lnum = curwin->w_cursor.lnum; - lnum >= curwin->w_botline - p_so; lnum--) { + lnum >= curwin->w_botline - *so_ptr; lnum--) { line_count++; // stop at end of file or when we know we are far off if (lnum <= 0 || line_count > curwin->w_height_inner + 1) { @@ -295,7 +304,7 @@ void update_topline(void) } } else line_count = curwin->w_cursor.lnum - curwin->w_botline - + 1 + p_so; + + 1 + *so_ptr; if (line_count <= curwin->w_height_inner + 1) { scroll_cursor_bot(scrolljump_value(), false); } else { @@ -305,6 +314,7 @@ void update_topline(void) } } curwin->w_valid |= VALID_TOPLINE; + curwin->w_viewport_invalid = true; win_check_anchored_floats(curwin); /* @@ -324,7 +334,7 @@ void update_topline(void) validate_cursor(); } - p_so = save_so; + *so_ptr = save_so; } /* @@ -356,25 +366,28 @@ static int scrolljump_value(void) */ static bool check_top_offset(void) { - if (curwin->w_cursor.lnum < curwin->w_topline + p_so + long so = get_scrolloff_value(); + if (curwin->w_cursor.lnum < curwin->w_topline + so || hasAnyFolding(curwin) ) { lineoff_T loff; loff.lnum = curwin->w_cursor.lnum; loff.fill = 0; - int n = curwin->w_topfill; /* always have this context */ - /* Count the visible screen lines above the cursor line. */ - while (n < p_so) { + int n = curwin->w_topfill; // always have this context + // Count the visible screen lines above the cursor line. + while (n < so) { topline_back(&loff); - /* Stop when included a line above the window. */ + // Stop when included a line above the window. if (loff.lnum < curwin->w_topline || (loff.lnum == curwin->w_topline && loff.fill > 0) - ) + ) { break; + } n += loff.height; } - if (n < p_so) + if (n < so) { return true; + } } return false; } @@ -398,6 +411,7 @@ void check_cursor_moved(win_T *wp) |VALID_CHEIGHT|VALID_CROW|VALID_TOPLINE); wp->w_valid_cursor = wp->w_cursor; wp->w_valid_leftcol = wp->w_leftcol; + wp->w_viewport_invalid = true; } else if (wp->w_cursor.col != wp->w_valid_cursor.col || wp->w_leftcol != wp->w_valid_leftcol || wp->w_cursor.coladd != wp->w_valid_cursor.coladd @@ -406,6 +420,7 @@ void check_cursor_moved(win_T *wp) wp->w_valid_cursor.col = wp->w_cursor.col; wp->w_valid_leftcol = wp->w_leftcol; wp->w_valid_cursor.coladd = wp->w_cursor.coladd; + wp->w_viewport_invalid = true; } } @@ -674,7 +689,7 @@ int win_col_off(win_T *wp) { return ((wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) + 1 : 0) + (cmdwin_type == 0 || wp != curwin ? 0 : 1) - + (int)wp->w_p_fdc + + win_fdccol_count(wp) + (win_signcol_count(wp) * win_signcol_width(wp)); } @@ -714,6 +729,8 @@ void curs_columns( colnr_T startcol; colnr_T endcol; colnr_T prev_skipcol; + long so = get_scrolloff_value(); + long siso = get_sidescrolloff_value(); /* * First make sure that w_topline is valid (after moving the cursor). @@ -785,10 +802,10 @@ void curs_columns( * If we get closer to the edge than 'sidescrolloff', scroll a little * extra */ - assert(p_siso <= INT_MAX); - int off_left = startcol - curwin->w_leftcol - (int)p_siso; + assert(siso <= INT_MAX); + int off_left = startcol - curwin->w_leftcol - (int)siso; int off_right = - endcol - curwin->w_leftcol - curwin->w_width_inner + (int)p_siso + 1; + endcol - curwin->w_leftcol - curwin->w_width_inner + (int)siso + 1; if (off_left < 0 || off_right > 0) { int diff = (off_left < 0) ? -off_left: off_right; @@ -834,7 +851,7 @@ void curs_columns( int plines = 0; if ((curwin->w_wrow >= curwin->w_height_inner || ((prev_skipcol > 0 - || curwin->w_wrow + p_so >= curwin->w_height_inner) + || curwin->w_wrow + so >= curwin->w_height_inner) && (plines = plines_win_nofill(curwin, curwin->w_cursor.lnum, false)) - 1 >= curwin->w_height_inner)) @@ -850,17 +867,18 @@ void curs_columns( * 2: Less than "p_so" lines below * 3: both of them */ extra = 0; - if (curwin->w_skipcol + p_so * width > curwin->w_virtcol) + if (curwin->w_skipcol + so * width > curwin->w_virtcol) { extra = 1; - /* Compute last display line of the buffer line that we want at the - * bottom of the window. */ + } + // Compute last display line of the buffer line that we want at the + // bottom of the window. if (plines == 0) { plines = plines_win(curwin, curwin->w_cursor.lnum, false); } plines--; - if (plines > curwin->w_wrow + p_so) { - assert(p_so <= INT_MAX); - n = curwin->w_wrow + (int)p_so; + if (plines > curwin->w_wrow + so) { + assert(so <= INT_MAX); + n = curwin->w_wrow + (int)so; } else { n = plines; } @@ -868,7 +886,7 @@ void curs_columns( extra += 2; } - if (extra == 3 || plines < p_so * 2) { + if (extra == 3 || plines < so * 2) { // not enough room for 'scrolloff', put cursor in the middle n = curwin->w_virtcol / width; if (n > curwin->w_height_inner / 2) { @@ -882,9 +900,9 @@ void curs_columns( } curwin->w_skipcol = n * width; } else if (extra == 1) { - /* less then 'scrolloff' lines above, decrease skipcol */ - assert(p_so <= INT_MAX); - extra = (curwin->w_skipcol + (int)p_so * width - curwin->w_virtcol + // less then 'scrolloff' lines above, decrease skipcol + assert(so <= INT_MAX); + extra = (curwin->w_skipcol + (int)so * width - curwin->w_virtcol + width - 1) / width; if (extra > 0) { if ((colnr_T)(extra * width) > curwin->w_skipcol) @@ -1206,7 +1224,7 @@ void scrolldown_clamp(void) end_row += curwin->w_cline_height - 1 - curwin->w_virtcol / curwin->w_width_inner; } - if (end_row < curwin->w_height_inner - p_so) { + if (end_row < curwin->w_height_inner - get_scrolloff_value()) { if (can_fill) { ++curwin->w_topfill; check_topfill(curwin, true); @@ -1246,14 +1264,14 @@ void scrollup_clamp(void) validate_virtcol(); start_row -= curwin->w_virtcol / curwin->w_width_inner; } - if (start_row >= p_so) { - if (curwin->w_topfill > 0) - --curwin->w_topfill; - else { + if (start_row >= get_scrolloff_value()) { + if (curwin->w_topfill > 0) { + curwin->w_topfill--; + } else { (void)hasFolding(curwin->w_topline, NULL, &curwin->w_topline); - ++curwin->w_topline; + curwin->w_topline++; } - ++curwin->w_botline; /* approximate w_botline */ + curwin->w_botline++; // approximate w_botline curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE); } } @@ -1349,8 +1367,7 @@ void scroll_cursor_top(int min_scroll, int always) linenr_T old_topline = curwin->w_topline; linenr_T old_topfill = curwin->w_topfill; linenr_T new_topline; - assert(p_so <= INT_MAX); - int off = (int)p_so; + int off = (int)get_scrolloff_value(); if (mouse_dragging > 0) off = mouse_dragging - 1; @@ -1447,6 +1464,7 @@ void scroll_cursor_top(int min_scroll, int always) curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); curwin->w_valid |= VALID_TOPLINE; + curwin->w_viewport_invalid = true; } } @@ -1492,7 +1510,8 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) linenr_T old_botline = curwin->w_botline; int old_valid = curwin->w_valid; int old_empty_rows = curwin->w_empty_rows; - linenr_T cln = curwin->w_cursor.lnum; /* Cursor Line Number */ + linenr_T cln = curwin->w_cursor.lnum; // Cursor Line Number + long so = get_scrolloff_value(); if (set_topbot) { used = 0; @@ -1551,7 +1570,7 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) /* Stop when scrolled nothing or at least "min_scroll", found "extra" * context for 'scrolloff' and counted all lines below the window. */ if ((((scrolled <= 0 || scrolled >= min_scroll) - && extra >= (mouse_dragging > 0 ? mouse_dragging - 1 : p_so)) + && extra >= (mouse_dragging > 0 ? mouse_dragging - 1 : so)) || boff.lnum + 1 > curbuf->b_ml.ml_line_count) && loff.lnum <= curwin->w_botline && (loff.lnum < curwin->w_botline @@ -1589,7 +1608,7 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) if (used > curwin->w_height_inner) { break; } - if (extra < (mouse_dragging > 0 ? mouse_dragging - 1 : p_so) + if (extra < (mouse_dragging > 0 ? mouse_dragging - 1 : so) || scrolled < min_scroll) { extra += boff.height; if (boff.lnum >= curwin->w_botline @@ -1650,6 +1669,7 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) curwin->w_valid = old_valid; } curwin->w_valid |= VALID_TOPLINE; + curwin->w_viewport_invalid = true; } /// Recompute topline to put the cursor halfway across the window @@ -1724,9 +1744,8 @@ void cursor_correct(void) * How many lines we would like to have above/below the cursor depends on * whether the first/last line of the file is on screen. */ - assert(p_so <= INT_MAX); - int above_wanted = (int)p_so; - int below_wanted = (int)p_so; + int above_wanted = (int)get_scrolloff_value(); + int below_wanted = (int)get_scrolloff_value(); if (mouse_dragging > 0) { above_wanted = mouse_dragging - 1; below_wanted = mouse_dragging - 1; @@ -1807,6 +1826,7 @@ void cursor_correct(void) } } curwin->w_valid |= VALID_TOPLINE; + curwin->w_viewport_invalid = true; } @@ -1821,6 +1841,7 @@ int onepage(Direction dir, long count) int retval = OK; lineoff_T loff; linenr_T old_topline = curwin->w_topline; + long so = get_scrolloff_value(); if (curbuf->b_ml.ml_line_count == 1) { /* nothing to do */ beep_flush(); @@ -1836,7 +1857,7 @@ int onepage(Direction dir, long count) * last line. */ if (dir == FORWARD - ? ((curwin->w_topline >= curbuf->b_ml.ml_line_count - p_so) + ? ((curwin->w_topline >= curbuf->b_ml.ml_line_count - so) && curwin->w_botline > curbuf->b_ml.ml_line_count) : (curwin->w_topline == 1 && curwin->w_topfill == diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 0c874d7922..92ca29209e 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -564,7 +564,7 @@ void rpc_close(Channel *channel) static void exit_event(void **argv) { if (!exiting) { - mch_exit(0); + os_exit(0); } } diff --git a/src/nvim/msgpack_rpc/channel.h b/src/nvim/msgpack_rpc/channel.h index 9ff5abdc5f..90e1c7d48b 100644 --- a/src/nvim/msgpack_rpc/channel.h +++ b/src/nvim/msgpack_rpc/channel.h @@ -15,7 +15,7 @@ /// HACK: os/input.c drains this queue immediately before blocking for input. /// Events on this queue are async-safe, but they need the resolved state /// of os_inchar(), so they are processed "just-in-time". -MultiQueue *ch_before_blocking_events; +EXTERN MultiQueue *ch_before_blocking_events INIT(= NULL); #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c index 6168f097a7..062ea784ca 100644 --- a/src/nvim/msgpack_rpc/server.c +++ b/src/nvim/msgpack_rpc/server.c @@ -151,7 +151,7 @@ int server_start(const char *endpoint) result = socket_watcher_start(watcher, MAX_CONNECTIONS, connection_cb); if (result < 0) { - WLOG("Failed to start server: %s", uv_strerror(result)); + WLOG("Failed to start server: %s: %s", uv_strerror(result), watcher->addr); socket_watcher_close(watcher, free_server); return result; } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index dac4c8f527..87d687198d 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -24,7 +24,7 @@ #include "nvim/diff.h" #include "nvim/digraph.h" #include "nvim/edit.h" -#include "nvim/eval.h" +#include "nvim/eval/userfunc.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" @@ -615,7 +615,9 @@ static void normal_redraw_mode_message(NormalState *s) kmsg = keep_msg; keep_msg = NULL; - // showmode() will clear keep_msg, but we want to use it anyway + // Showmode() will clear keep_msg, but we want to use it anyway. + // First update w_topline. + setcursor(); update_screen(0); // now reset it, otherwise it's put in the history again keep_msg = kmsg; @@ -623,6 +625,7 @@ static void normal_redraw_mode_message(NormalState *s) xfree(kmsg); } setcursor(); + ui_cursor_shape(); // show different cursor shape ui_flush(); if (msg_scroll || emsg_on_display) { os_delay(1000L, true); // wait at least one second @@ -1257,6 +1260,8 @@ static void normal_redraw(NormalState *s) maketitle(); } + curbuf->b_last_used = time(NULL); + // Display message after redraw. If an external message is still visible, // it contains the kept message already. if (keep_msg != NULL && !msg_ext_is_visible()) { @@ -1964,8 +1969,8 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) break; case OP_FOLD: - VIsual_reselect = false; /* don't reselect now */ - foldCreate(oap->start.lnum, oap->end.lnum); + VIsual_reselect = false; // don't reselect now + foldCreate(curwin, oap->start.lnum, oap->end.lnum); break; case OP_FOLDOPEN: @@ -1983,9 +1988,9 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) case OP_FOLDDEL: case OP_FOLDDELREC: - VIsual_reselect = false; /* don't reselect now */ - deleteFold(oap->start.lnum, oap->end.lnum, - oap->op_type == OP_FOLDDELREC, oap->is_VIsual); + VIsual_reselect = false; // don't reselect now + deleteFold(curwin, oap->start.lnum, oap->end.lnum, + oap->op_type == OP_FOLDDELREC, oap->is_VIsual); break; case OP_NR_ADD: @@ -2556,7 +2561,14 @@ do_mouse ( * JUMP! */ jump_flags = jump_to_mouse(jump_flags, - oap == NULL ? NULL : &(oap->inclusive), which_button); + oap == NULL ? NULL : &(oap->inclusive), + which_button); + + // A click in the window toolbar has no side effects. + if (jump_flags & MOUSE_WINBAR) { + return false; + } + moved = (jump_flags & CURSOR_MOVED); in_status_line = (jump_flags & IN_STATUS_LINE); in_sep_line = (jump_flags & IN_SEP_LINE); @@ -2584,12 +2596,13 @@ do_mouse ( /* Set global flag that we are extending the Visual area with mouse * dragging; temporarily minimize 'scrolloff'. */ - if (VIsual_active && is_drag && p_so) { - /* In the very first line, allow scrolling one line */ - if (mouse_row == 0) + if (VIsual_active && is_drag && get_scrolloff_value()) { + // In the very first line, allow scrolling one line + if (mouse_row == 0) { mouse_dragging = 2; - else + } else { mouse_dragging = 1; + } } /* When dragging the mouse above the window, scroll down. */ @@ -3642,7 +3655,9 @@ static void nv_help(cmdarg_T *cap) */ static void nv_addsub(cmdarg_T *cap) { - if (!VIsual_active && cap->oap->op_type == OP_NOP) { + if (bt_prompt(curbuf) && !prompt_curpos_editable()) { + clearopbeep(cap->oap); + } else if (!VIsual_active && cap->oap->op_type == OP_NOP) { prep_redo_cmd(cap); cap->oap->op_type = cap->cmdchar == Ctrl_A ? OP_NR_ADD : OP_NR_SUB; op_addsub(cap->oap, cap->count1, cap->arg); @@ -4087,9 +4102,9 @@ void scroll_redraw(int up, long count) scrollup(count, true); else scrolldown(count, true); - if (p_so) { - /* Adjust the cursor position for 'scrolloff'. Mark w_topline as - * valid, otherwise the screen jumps back at the end of the file. */ + if (get_scrolloff_value()) { + // Adjust the cursor position for 'scrolloff'. Mark w_topline as + // valid, otherwise the screen jumps back at the end of the file. cursor_correct(); check_cursor_moved(curwin); curwin->w_valid |= VALID_TOPLINE; @@ -4118,6 +4133,7 @@ void scroll_redraw(int up, long count) } if (curwin->w_cursor.lnum != prev_lnum) coladvance(curwin->w_curswant); + curwin->w_viewport_invalid = true; redraw_later(VALID); } @@ -4133,8 +4149,8 @@ static void nv_zet(cmdarg_T *cap) int old_fen = curwin->w_p_fen; bool undo = false; - assert(p_siso <= INT_MAX); - int l_p_siso = (int)p_siso; + int l_p_siso = (int)get_sidescrolloff_value(); + assert(l_p_siso <= INT_MAX); if (ascii_isdigit(nchar)) { /* @@ -4338,11 +4354,12 @@ dozet: /* "zD": delete fold at cursor recursively */ case 'd': case 'D': if (foldManualAllowed(false)) { - if (VIsual_active) + if (VIsual_active) { nv_operator(cap); - else - deleteFold(curwin->w_cursor.lnum, - curwin->w_cursor.lnum, nchar == 'D', false); + } else { + deleteFold(curwin, curwin->w_cursor.lnum, + curwin->w_cursor.lnum, nchar == 'D', false); + } } break; @@ -4350,11 +4367,11 @@ dozet: case 'E': if (foldmethodIsManual(curwin)) { clearFolding(curwin); changed_window_setting(); - } else if (foldmethodIsMarker(curwin)) - deleteFold((linenr_T)1, curbuf->b_ml.ml_line_count, - true, false); - else + } else if (foldmethodIsMarker(curwin)) { + deleteFold(curwin, (linenr_T)1, curbuf->b_ml.ml_line_count, true, false); + } else { EMSG(_("E352: Cannot erase folds with current 'foldmethod'")); + } break; /* "zn": fold none: reset 'foldenable' */ @@ -4456,16 +4473,16 @@ dozet: case 'r': curwin->w_p_fdl += cap->count1; { - int d = getDeepestNesting(); + int d = getDeepestNesting(curwin); if (curwin->w_p_fdl >= d) { curwin->w_p_fdl = d; } } break; - /* "zR": open all folds */ - case 'R': curwin->w_p_fdl = getDeepestNesting(); - old_fdl = -1; /* force an update */ + case 'R': // "zR": open all folds + curwin->w_p_fdl = getDeepestNesting(curwin); + old_fdl = -1; // force an update break; case 'j': /* "zj" move to next fold downwards */ @@ -5239,6 +5256,13 @@ static void nv_down(cmdarg_T *cap) // In the cmdline window a <CR> executes the command. if (cmdwin_type != 0 && cap->cmdchar == CAR) { cmdwin_result = CAR; + } else if (bt_prompt(curbuf) && cap->cmdchar == CAR + && curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) { + // In a prompt buffer a <CR> in the last line invokes the callback. + invoke_prompt_callback(); + if (restart_edit == 0) { + restart_edit = 'a'; + } } else { cap->oap->motion_type = kMTLineWise; if (cursor_down(cap->count1, cap->oap->op_type == OP_NOP) == false) { @@ -5831,6 +5855,10 @@ static void nv_undo(cmdarg_T *cap) static void nv_kundo(cmdarg_T *cap) { if (!checkclearopq(cap->oap)) { + if (bt_prompt(curbuf)) { + clearopbeep(cap->oap); + return; + } u_undo((int)cap->count1); curwin->w_set_curswant = true; } @@ -5844,8 +5872,13 @@ static void nv_replace(cmdarg_T *cap) char_u *ptr; int had_ctrl_v; - if (checkclearop(cap->oap)) + if (checkclearop(cap->oap)) { + return; + } + if (bt_prompt(curbuf) && !prompt_curpos_editable()) { + clearopbeep(cap->oap); return; + } /* get another character */ if (cap->nchar == Ctrl_V) { @@ -6182,7 +6215,11 @@ static void v_visop(cmdarg_T *cap) */ static void nv_subst(cmdarg_T *cap) { - if (VIsual_active) { /* "vs" and "vS" are the same as "vc" */ + if (bt_prompt(curbuf) && !prompt_curpos_editable()) { + clearopbeep(cap->oap); + return; + } + if (VIsual_active) { // "vs" and "vS" are the same as "vc" if (cap->cmdchar == 'S') { VIsual_mode_orig = VIsual_mode; VIsual_mode = 'V'; @@ -7120,10 +7157,15 @@ static void nv_tilde(cmdarg_T *cap) { if (!p_to && !VIsual_active - && cap->oap->op_type != OP_TILDE) + && cap->oap->op_type != OP_TILDE) { + if (bt_prompt(curbuf) && !prompt_curpos_editable()) { + clearopbeep(cap->oap); + return; + } n_swapchar(cap); - else + } else { nv_operator(cap); + } } /* @@ -7136,6 +7178,12 @@ static void nv_operator(cmdarg_T *cap) op_type = get_op_type(cap->cmdchar, cap->nchar); + if (bt_prompt(curbuf) && op_is_change(op_type) + && !prompt_curpos_editable()) { + clearopbeep(cap->oap); + return; + } + if (op_type == cap->oap->op_type) /* double operator works on lines */ nv_lineop(cap); else if (!checkclearop(cap->oap)) { @@ -7796,8 +7844,11 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent) clearop(cap->oap); assert(cap->opcount >= 0); nv_diffgetput(true, (size_t)cap->opcount); - } else + } else { clearopbeep(cap->oap); + } + } else if (bt_prompt(curbuf) && !prompt_curpos_editable()) { + clearopbeep(cap->oap); } else { if (fix_indent) { dir = (cap->cmdchar == ']' && cap->nchar == 'p') @@ -7809,8 +7860,9 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent) ? BACKWARD : FORWARD; } prep_redo_cmd(cap); - if (cap->cmdchar == 'g') + if (cap->cmdchar == 'g') { flags |= PUT_CURSEND; + } if (VIsual_active) { /* Putting in Visual mode: The put text replaces the selected @@ -7916,10 +7968,14 @@ static void nv_open(cmdarg_T *cap) clearop(cap->oap); assert(cap->opcount >= 0); nv_diffgetput(false, (size_t)cap->opcount); - } else if (VIsual_active) /* switch start and end of visual */ + } else if (VIsual_active) { + // switch start and end of visual/ v_swap_corners(cap->cmdchar); - else + } else if (bt_prompt(curbuf)) { + clearopbeep(cap->oap); + } else { n_opencmd(cap); + } } // Calculate start/end virtual columns for operating in block mode. diff --git a/src/nvim/ops.c b/src/nvim/ops.c index db5c98ed78..a70224f98b 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -89,6 +89,10 @@ struct block_def { # include "ops.c.generated.h" #endif +// Flags for third item in "opchars". +#define OPF_LINES 1 // operator always works on lines +#define OPF_CHANGE 2 // operator changes text + /* * The names of operators. * IMPORTANT: Index must correspond with defines in vim.h!!! @@ -96,36 +100,36 @@ struct block_def { */ static char opchars[][3] = { - { NUL, NUL, false }, // OP_NOP - { 'd', NUL, false }, // OP_DELETE - { 'y', NUL, false }, // OP_YANK - { 'c', NUL, false }, // OP_CHANGE - { '<', NUL, true }, // OP_LSHIFT - { '>', NUL, true }, // OP_RSHIFT - { '!', NUL, true }, // OP_FILTER - { 'g', '~', false }, // OP_TILDE - { '=', NUL, true }, // OP_INDENT - { 'g', 'q', true }, // OP_FORMAT - { ':', NUL, true }, // OP_COLON - { 'g', 'U', false }, // OP_UPPER - { 'g', 'u', false }, // OP_LOWER - { 'J', NUL, true }, // DO_JOIN - { 'g', 'J', true }, // DO_JOIN_NS - { 'g', '?', false }, // OP_ROT13 - { 'r', NUL, false }, // OP_REPLACE - { 'I', NUL, false }, // OP_INSERT - { 'A', NUL, false }, // OP_APPEND - { 'z', 'f', true }, // OP_FOLD - { 'z', 'o', true }, // OP_FOLDOPEN - { 'z', 'O', true }, // OP_FOLDOPENREC - { 'z', 'c', true }, // OP_FOLDCLOSE - { 'z', 'C', true }, // OP_FOLDCLOSEREC - { 'z', 'd', true }, // OP_FOLDDEL - { 'z', 'D', true }, // OP_FOLDDELREC - { 'g', 'w', true }, // OP_FORMAT2 - { 'g', '@', false }, // OP_FUNCTION - { Ctrl_A, NUL, false }, // OP_NR_ADD - { Ctrl_X, NUL, false }, // OP_NR_SUB + { NUL, NUL, 0 }, // OP_NOP + { 'd', NUL, OPF_CHANGE }, // OP_DELETE + { 'y', NUL, 0 }, // OP_YANK + { 'c', NUL, OPF_CHANGE }, // OP_CHANGE + { '<', NUL, OPF_LINES | OPF_CHANGE }, // OP_LSHIFT + { '>', NUL, OPF_LINES | OPF_CHANGE }, // OP_RSHIFT + { '!', NUL, OPF_LINES | OPF_CHANGE }, // OP_FILTER + { 'g', '~', OPF_CHANGE }, // OP_TILDE + { '=', NUL, OPF_LINES | OPF_CHANGE }, // OP_INDENT + { 'g', 'q', OPF_LINES | OPF_CHANGE }, // OP_FORMAT + { ':', NUL, OPF_LINES }, // OP_COLON + { 'g', 'U', OPF_CHANGE }, // OP_UPPER + { 'g', 'u', OPF_CHANGE }, // OP_LOWER + { 'J', NUL, OPF_LINES | OPF_CHANGE }, // DO_JOIN + { 'g', 'J', OPF_LINES | OPF_CHANGE }, // DO_JOIN_NS + { 'g', '?', OPF_CHANGE }, // OP_ROT13 + { 'r', NUL, OPF_CHANGE }, // OP_REPLACE + { 'I', NUL, OPF_CHANGE }, // OP_INSERT + { 'A', NUL, OPF_CHANGE }, // OP_APPEND + { 'z', 'f', OPF_LINES }, // OP_FOLD + { 'z', 'o', OPF_LINES }, // OP_FOLDOPEN + { 'z', 'O', OPF_LINES }, // OP_FOLDOPENREC + { 'z', 'c', OPF_LINES }, // OP_FOLDCLOSE + { 'z', 'C', OPF_LINES }, // OP_FOLDCLOSEREC + { 'z', 'd', OPF_LINES }, // OP_FOLDDEL + { 'z', 'D', OPF_LINES }, // OP_FOLDDELREC + { 'g', 'w', OPF_LINES | OPF_CHANGE }, // OP_FORMAT2 + { 'g', '@', OPF_CHANGE }, // OP_FUNCTION + { Ctrl_A, NUL, OPF_CHANGE }, // OP_NR_ADD + { Ctrl_X, NUL, OPF_CHANGE }, // OP_NR_SUB }; /* @@ -169,7 +173,13 @@ int get_op_type(int char1, int char2) */ int op_on_lines(int op) { - return opchars[op][2]; + return opchars[op][2] & OPF_LINES; +} + +// Return TRUE if operator "op" changes text. +int op_is_change(int op) +{ + return opchars[op][2] & OPF_CHANGE; } /* @@ -221,8 +231,6 @@ void op_shift(oparg_T *oap, int curs_top, int amount) ++curwin->w_cursor.lnum; } - changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true); - if (oap->motion_type == kMTBlockWise) { curwin->w_cursor.lnum = oap->start.lnum; curwin->w_cursor.col = block_col; @@ -253,7 +261,7 @@ void op_shift(oparg_T *oap, int curs_top, int amount) sprintf((char *)IObuff, _("%" PRId64 " lines %sed %d times"), (int64_t)oap->line_count, s, amount); } - msg(IObuff); + msg_attr_keep(IObuff, 0, true, false); } /* @@ -262,8 +270,11 @@ void op_shift(oparg_T *oap, int curs_top, int amount) curbuf->b_op_start = oap->start; curbuf->b_op_end.lnum = oap->end.lnum; curbuf->b_op_end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum)); - if (curbuf->b_op_end.col > 0) - --curbuf->b_op_end.col; + if (curbuf->b_op_end.col > 0) { + curbuf->b_op_end.col--; + } + + changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true); } // Shift the current line one shiftwidth left (if left != 0) or right @@ -831,6 +842,15 @@ static bool is_append_register(int regname) return ASCII_ISUPPER(regname); } +/// @see get_yank_register +/// @returns true when register should be inserted literally +/// (selection or clipboard) +static inline bool is_literal_register(int regname) + FUNC_ATTR_CONST +{ + return regname == '*' || regname == '+'; +} + /// Returns a copy of contents in register `name` /// for use in do_put. Should be freed by caller. yankreg_T *copy_register(int name) @@ -1141,11 +1161,12 @@ static int put_in_typebuf( */ int insert_reg( int regname, - int literally /* insert literally, not as if typed */ + bool literally_arg // insert literally, not as if typed ) { int retval = OK; bool allocated; + const bool literally = literally_arg || is_literal_register(regname); /* * It is possible to get into an endless loop by having CTRL-R a in @@ -1315,12 +1336,14 @@ bool get_spec_reg( /// register contents will be interpreted as commands. /// /// @param regname Register name. -/// @param literally Insert text literally instead of "as typed". +/// @param literally_arg Insert text literally instead of "as typed". /// @param remcr When true, don't add CR characters. /// /// @returns FAIL for failure, OK otherwise -bool cmdline_paste_reg(int regname, bool literally, bool remcr) +bool cmdline_paste_reg(int regname, bool literally_arg, bool remcr) { + const bool literally = literally_arg || is_literal_register(regname); + yankreg_T *reg = get_yank_register(regname, YREG_PASTE); if (reg->y_array == NULL) return FAIL; @@ -2523,7 +2546,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) case kMTCharWise: { colnr_T startcol = 0, endcol = MAXCOL; - int is_oneChar = FALSE; + int is_oneChar = false; colnr_T cs, ce; p = ml_get(lnum); bd.startspaces = 0; @@ -2554,8 +2577,8 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) && utf_head_off(p, p + endcol) == 0)) { if (oap->start.lnum == oap->end.lnum && oap->start.col == oap->end.col) { - /* Special case: inside a single char */ - is_oneChar = TRUE; + // Special case: inside a single char + is_oneChar = true; bd.startspaces = oap->end.coladd - oap->start.coladd + oap->inclusive; endcol = startcol; @@ -4414,8 +4437,8 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bdp->textlen = 0; bdp->start_vcol = 0; bdp->end_vcol = 0; - bdp->is_short = FALSE; - bdp->is_oneChar = FALSE; + bdp->is_short = false; + bdp->is_oneChar = false; bdp->pre_whitesp = 0; bdp->pre_whitesp_c = 0; bdp->end_char_vcols = 0; @@ -4441,9 +4464,10 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bdp->start_char_vcols = incr; if (bdp->start_vcol < oap->start_vcol) { /* line too short */ bdp->end_vcol = bdp->start_vcol; - bdp->is_short = TRUE; - if (!is_del || oap->op_type == OP_APPEND) + bdp->is_short = true; + if (!is_del || oap->op_type == OP_APPEND) { bdp->endspaces = oap->end_vcol - oap->start_vcol + 1; + } } else { /* notice: this converts partly selected Multibyte characters to * spaces, too. */ @@ -4452,11 +4476,11 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bdp->startspaces = bdp->start_char_vcols - bdp->startspaces; pend = pstart; bdp->end_vcol = bdp->start_vcol; - if (bdp->end_vcol > oap->end_vcol) { /* it's all in one character */ - bdp->is_oneChar = TRUE; - if (oap->op_type == OP_INSERT) + if (bdp->end_vcol > oap->end_vcol) { // it's all in one character + bdp->is_oneChar = true; + if (oap->op_type == OP_INSERT) { bdp->endspaces = bdp->start_char_vcols - bdp->startspaces; - else if (oap->op_type == OP_APPEND) { + } else if (oap->op_type == OP_APPEND) { bdp->startspaces += oap->end_vcol - oap->start_vcol + 1; bdp->endspaces = bdp->start_char_vcols - bdp->startspaces; } else { @@ -4481,17 +4505,16 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, if (bdp->end_vcol <= oap->end_vcol && (!is_del || oap->op_type == OP_APPEND - || oap->op_type == OP_REPLACE)) { /* line too short */ - bdp->is_short = TRUE; - /* Alternative: include spaces to fill up the block. - * Disadvantage: can lead to trailing spaces when the line is - * short where the text is put */ - /* if (!is_del || oap->op_type == OP_APPEND) */ - if (oap->op_type == OP_APPEND || virtual_op) + || oap->op_type == OP_REPLACE)) { // line too short + bdp->is_short = true; + // Alternative: include spaces to fill up the block. + // Disadvantage: can lead to trailing spaces when the line is + // short where the text is put + // if (!is_del || oap->op_type == OP_APPEND) + if (oap->op_type == OP_APPEND || virtual_op) { bdp->endspaces = oap->end_vcol - bdp->end_vcol + oap->inclusive; - else - bdp->endspaces = 0; /* replace doesn't add characters */ + } } else if (bdp->end_vcol > oap->end_vcol) { bdp->endspaces = bdp->end_vcol - oap->end_vcol - 1; if (!is_del && bdp->endspaces) { diff --git a/src/nvim/option.c b/src/nvim/option.c index f03dcc2bf2..96f8e1529a 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -66,6 +66,7 @@ #include "nvim/path.h" #include "nvim/popupmnu.h" #include "nvim/regexp.h" +#include "nvim/ex_session.h" #include "nvim/screen.h" #include "nvim/spell.h" #include "nvim/spellfile.h" @@ -298,7 +299,8 @@ static char *(p_scbopt_values[]) = { "ver", "hor", "jump", NULL }; static char *(p_debug_values[]) = { "msg", "throw", "beep", NULL }; static char *(p_ead_values[]) = { "both", "ver", "hor", NULL }; static char *(p_buftype_values[]) = { "nofile", "nowrite", "quickfix", - "help", "acwrite", "terminal", NULL }; + "help", "acwrite", "terminal", + "prompt", NULL }; static char *(p_bufhidden_values[]) = { "hide", "unload", "delete", "wipe", NULL }; @@ -313,6 +315,9 @@ static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2", "auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9", "yes:1", "yes:2", "yes:3", "yes:4", "yes:5", "yes:6", "yes:7", "yes:8", "yes:9", NULL }; +static char *(p_fdc_values[]) = { "auto:1", "auto:2", + "auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", NULL }; /// All possible flags for 'shm'. static char_u SHM_ALL[] = { @@ -497,6 +502,24 @@ static inline char *add_dir(char *dest, const char *const dir, return dest; } +char *get_lib_dir(void) +{ + // TODO(bfredl): too fragile? Ideally default_lib_dir would be made empty + // in an appimage build + if (strlen(default_lib_dir) != 0 + && os_isdir((const char_u *)default_lib_dir)) { + return xstrdup(default_lib_dir); + } + + // Find library path relative to the nvim binary: ../lib/nvim/ + char exe_name[MAXPATHL]; + vim_get_prefix_from_exepath(exe_name); + if (append_path(exe_name, "lib" _PATHSEPSTR "nvim", MAXPATHL) == OK) { + return xstrdup(exe_name); + } + return NULL; +} + /// Sets &runtimepath to default value. /// /// Windows: Uses "…/nvim-data" for kXDGDataHome to avoid storing @@ -507,6 +530,7 @@ static void set_runtimepath_default(void) char *const data_home = stdpaths_get_xdg_var(kXDGDataHome); char *const config_home = stdpaths_get_xdg_var(kXDGConfigHome); char *const vimruntime = vim_getenv("VIMRUNTIME"); + char *const libdir = get_lib_dir(); char *const data_dirs = stdpaths_get_xdg_var(kXDGDataDirs); char *const config_dirs = stdpaths_get_xdg_var(kXDGConfigDirs); #define SITE_SIZE (sizeof("site") - 1) @@ -514,6 +538,7 @@ static void set_runtimepath_default(void) size_t data_len = 0; size_t config_len = 0; size_t vimruntime_len = 0; + size_t libdir_len = 0; if (data_home != NULL) { data_len = strlen(data_home); if (data_len != 0) { @@ -543,6 +568,12 @@ static void set_runtimepath_default(void) rtp_size += vimruntime_len + memcnt(vimruntime, ',', vimruntime_len) + 1; } } + if (libdir != NULL) { + libdir_len = strlen(libdir); + if (libdir_len != 0) { + rtp_size += libdir_len + memcnt(libdir, ',', libdir_len) + 1; + } + } rtp_size += compute_double_colon_len(data_dirs, NVIM_SIZE + 1 + SITE_SIZE + 1, AFTER_SIZE + 1); rtp_size += compute_double_colon_len(config_dirs, NVIM_SIZE + 1, @@ -561,6 +592,7 @@ static void set_runtimepath_default(void) true); rtp_cur = add_dir(rtp_cur, vimruntime, vimruntime_len, kXDGNone, NULL, 0, NULL, 0); + rtp_cur = add_dir(rtp_cur, libdir, libdir_len, kXDGNone, NULL, 0, NULL, 0); rtp_cur = add_colon_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, "after", AFTER_SIZE, false); rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome, @@ -582,15 +614,14 @@ static void set_runtimepath_default(void) xfree(data_home); xfree(config_home); xfree(vimruntime); + xfree(libdir); } #undef NVIM_SIZE -/* - * Initialize the options, first part. - * - * Called only once from main(), just after creating the first buffer. - */ +/// Initialize the options, first part. +/// +/// Called only once from main(), just after creating the first buffer. void set_init_1(void) { int opt_idx; @@ -835,10 +866,8 @@ void set_init_1(void) set_helplang_default(get_mess_lang()); } -/* - * Set an option to its default value. - * This does not take care of side effects! - */ +/// Set an option to its default value. +/// This does not take care of side effects! static void set_option_default( int opt_idx, @@ -871,11 +900,19 @@ set_option_default( if (options[opt_idx].indir == PV_SCROLL) { win_comp_scroll(curwin); } else { - *(long *)varp = (long)(intptr_t)options[opt_idx].def_val[dvi]; + long def_val = (long)options[opt_idx].def_val[dvi]; + if ((long *)varp == &curwin->w_p_so + || (long *)varp == &curwin->w_p_siso) { + // 'scrolloff' and 'sidescrolloff' local values have a + // different default value than the global default. + *(long *)varp = -1; + } else { + *(long *)varp = def_val; + } // May also set global value for local option. if (both) { *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = - *(long *)varp; + def_val; } } } else { // P_BOOL @@ -901,9 +938,7 @@ set_option_default( set_option_sctx_idx(opt_idx, opt_flags, current_sctx); } -/* - * Set all options (except terminal options) to their default value. - */ +/// Set all options (except terminal options) to their default value. static void set_options_default( int opt_flags // OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL @@ -946,10 +981,8 @@ static void set_string_default(const char *name, char *val, bool allocated) } } -/* - * Set the Vi-default value of a number option. - * Used for 'lines' and 'columns'. - */ +/// Set the Vi-default value of a number option. +/// Used for 'lines' and 'columns'. void set_number_default(char *name, long val) { int opt_idx; @@ -1076,10 +1109,8 @@ void set_init_3(void) set_title_defaults(); // 'title', 'icon' } -/* - * When 'helplang' is still at its default value, set it to "lang". - * Only the first two characters of "lang" are used. - */ +/// When 'helplang' is still at its default value, set it to "lang". +/// Only the first two characters of "lang" are used. void set_helplang_default(const char *lang) { if (lang == NULL) { @@ -1111,13 +1142,11 @@ void set_helplang_default(const char *lang) } -/* - * 'title' and 'icon' only default to true if they have not been set or reset - * in .vimrc and we can read the old value. - * When 'title' and 'icon' have been reset in .vimrc, we won't even check if - * they can be reset. This reduces startup time when using X on a remote - * machine. - */ +/// 'title' and 'icon' only default to true if they have not been set or reset +/// in .vimrc and we can read the old value. +/// When 'title' and 'icon' have been reset in .vimrc, we won't even check if +/// they can be reset. This reduces startup time when using X on a remote +/// machine. void set_title_defaults(void) { int idx1; @@ -1982,10 +2011,8 @@ static char_u *illegal_char(char_u *errbuf, size_t errbuflen, int c) return errbuf; } -/* - * Convert a key name or string into a key value. - * Used for 'wildchar' and 'cedit' options. - */ +/// Convert a key name or string into a key value. +/// Used for 'wildchar' and 'cedit' options. static int string_to_key(char_u *arg) { if (*arg == '<') { @@ -1997,10 +2024,8 @@ static int string_to_key(char_u *arg) return *arg; } -/* - * Check value of 'cedit' and set cedit_key. - * Returns NULL if value is OK, error message otherwise. - */ +/// Check value of 'cedit' and set cedit_key. +/// Returns NULL if value is OK, error message otherwise. static char_u *check_cedit(void) { int n; @@ -2084,13 +2109,11 @@ void set_options_bin( } } -/* - * Find the parameter represented by the given character (eg ', :, ", or /), - * and return its associated value in the 'shada' string. - * Only works for number parameters, not for 'r' or 'n'. - * If the parameter is not specified in the string or there is no following - * number, return -1. - */ +/// Find the parameter represented by the given character (eg ', :, ", or /), +/// and return its associated value in the 'shada' string. +/// Only works for number parameters, not for 'r' or 'n'. +/// If the parameter is not specified in the string or there is no following +/// number, return -1. int get_shada_parameter(int type) { char_u *p; @@ -2102,11 +2125,9 @@ int get_shada_parameter(int type) return -1; } -/* - * Find the parameter represented by the given character (eg ''', ':', '"', or - * '/') in the 'shada' option and return a pointer to the string after it. - * Return NULL if the parameter is not specified in the string. - */ +/// Find the parameter represented by the given character (eg ''', ':', '"', or +/// '/') in the 'shada' option and return a pointer to the string after it. +/// Return NULL if the parameter is not specified in the string. char_u *find_shada_parameter(int type) { char_u *p; @@ -2126,12 +2147,10 @@ char_u *find_shada_parameter(int type) return NULL; } -/* - * Expand environment variables for some string options. - * These string options cannot be indirect! - * If "val" is NULL expand the current value of the option. - * Return pointer to NameBuff, or NULL when not expanded. - */ +/// Expand environment variables for some string options. +/// These string options cannot be indirect! +/// If "val" is NULL expand the current value of the option. +/// Return pointer to NameBuff, or NULL when not expanded. static char_u *option_expand(int opt_idx, char_u *val) { // if option doesn't need expansion nothing to do @@ -2215,9 +2234,7 @@ static void didset_options2(void) check_opt_wim(); } -/* - * Check for string options that are NULL (normally only termcap options). - */ +/// Check for string options that are NULL (normally only termcap options). void check_options(void) { int opt_idx; @@ -2229,9 +2246,7 @@ void check_options(void) } } -/* - * Check string options in a buffer for NULL value. - */ +/// Check string options in a buffer for NULL value. void check_buf_options(buf_T *buf) { check_string_option(&buf->b_p_bh); @@ -2284,13 +2299,11 @@ void check_buf_options(buf_T *buf) check_string_option(&buf->b_p_menc); } -/* - * Free the string allocated for an option. - * Checks for the string being empty_option. This may happen if we're out of - * memory, vim_strsave() returned NULL, which was replaced by empty_option by - * check_options(). - * Does NOT check for P_ALLOCED flag! - */ +/// Free the string allocated for an option. +/// Checks for the string being empty_option. This may happen if we're out of +/// memory, vim_strsave() returned NULL, which was replaced by empty_option by +/// check_options(). +/// Does NOT check for P_ALLOCED flag! void free_string_option(char_u *p) { if (p != empty_option) { @@ -2328,10 +2341,8 @@ int was_set_insecurely(char_u *opt, int opt_flags) return -1; } -/* - * Get a pointer to the flags used for the P_INSECURE flag of option - * "opt_idx". For some local options a local flags field is used. - */ +/// Get a pointer to the flags used for the P_INSECURE flag of option +/// "opt_idx". For some local options a local flags field is used. static uint32_t *insecure_flag(int opt_idx, int opt_flags) { if (opt_flags & OPT_LOCAL) @@ -2349,9 +2360,7 @@ static uint32_t *insecure_flag(int opt_idx, int opt_flags) } -/* - * Redraw the window title and/or tab page text later. - */ +/// Redraw the window title and/or tab page text later. static void redraw_titles(void) { need_maketitle = true; @@ -2432,9 +2441,7 @@ set_string_option_direct( } } -/* - * Set global value for string option when it's a local option. - */ +/// Set global value for string option when it's a local option. static void set_string_option_global( int opt_idx, // option index @@ -2509,12 +2516,41 @@ static char *set_string_option(const int opt_idx, const char *const value, return r; } +/// Return true if "val" is a valid name: only consists of alphanumeric ASCII +/// characters or characters in "allowed". +static bool valid_name(const char_u *val, const char *allowed) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + for (const char_u *s = val; *s != NUL; s++) { + if (!ASCII_ISALNUM(*s) + && vim_strchr((const char_u *)allowed, *s) == NULL) { + return false; + } + } + return true; +} + /// Return true if "val" is a valid 'filetype' name. /// Also used for 'syntax' and 'keymap'. -static bool valid_filetype(char_u *val) +static bool valid_filetype(const char_u *val) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - for (char_u *s = val; *s != NUL; s++) { - if (!ASCII_ISALNUM(*s) && vim_strchr((char_u *)".-_", *s) == NULL) { + return valid_name(val, ".-_"); +} + +/// Return true if "val" is a valid 'spellang' value. +bool valid_spellang(const char_u *val) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return valid_name(val, ".-_,@"); +} + +/// Return true if "val" is a valid 'spellfile' value. +static bool valid_spellfile(const char_u *val) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + for (const char_u *s = val; *s != NUL; s++) { + if (!vim_isfilec(*s) && *s != ',') { return false; } } @@ -3032,7 +3068,14 @@ ambw_end: || varp == &(curwin->w_s->b_p_spf)) { // When 'spelllang' or 'spellfile' is set and there is a window for this // buffer in which 'spell' is set load the wordlists. - errmsg = did_set_spell_option(varp == &(curwin->w_s->b_p_spf)); + const bool is_spellfile = varp == &(curwin->w_s->b_p_spf); + + if ((is_spellfile && !valid_spellfile(*varp)) + || (!is_spellfile && !valid_spellang(*varp))) { + errmsg = e_invarg; + } else { + errmsg = did_set_spell_option(is_spellfile); + } } else if (varp == &(curwin->w_s->b_p_spc)) { // When 'spellcapcheck' is set compile the regexp program. errmsg = compile_cap_prog(curwin->w_s); @@ -3133,6 +3176,11 @@ ambw_end: if (check_opt_strings(*varp, p_scl_values, false) != OK) { errmsg = e_invarg; } + } else if (varp == &curwin->w_p_fdc || varp == &curwin->w_allbuf_opt.wo_fdc) { + // 'foldcolumn' + if (check_opt_strings(*varp, p_fdc_values, false) != OK) { + errmsg = e_invarg; + } } else if (varp == &p_pt) { // 'pastetoggle': translate key codes like in a mapping if (*p_pt) { @@ -3586,7 +3634,7 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set) } // first round: check for valid value, second round: assign values - for (round = 0; round <= set ? 1 : 0; round++) { + for (round = 0; round <= (set ? 1 : 0); round++) { if (round > 0) { // After checking that the value is valid: set defaults for (i = 0; i < entries; i++) { @@ -3660,10 +3708,8 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set) return NULL; // no error } -/* - * Check validity of options with the 'statusline' format. - * Return error message or NULL. - */ +/// Check validity of options with the 'statusline' format. +/// Return error message or NULL. char_u *check_stl_option(char_u *s) { int itemcnt = 0; @@ -3756,16 +3802,15 @@ static char_u *did_set_spell_option(bool is_spellfile) return errmsg; } -/* - * Set curbuf->b_cap_prog to the regexp program for 'spellcapcheck'. - * Return error message when failed, NULL when OK. - */ +/// Set curbuf->b_cap_prog to the regexp program for 'spellcapcheck'. +/// Return error message when failed, NULL when OK. static char_u *compile_cap_prog(synblock_T *synblock) + FUNC_ATTR_NONNULL_ALL { regprog_T *rp = synblock->b_cap_prog; char_u *re; - if (*synblock->b_p_spc == NUL) { + if (synblock->b_p_spc == NULL || *synblock->b_p_spc == NUL) { synblock->b_cap_prog = NULL; } else { // Prepend a ^ so that we only match at one column @@ -3805,7 +3850,8 @@ static bool parse_winhl_opt(win_T *wp) w_hl_id_normal = hl_id; } else { for (hlf = 0; hlf < (int)HLF_COUNT; hlf++) { - if (strncmp(hlf_names[hlf], p, nlen) == 0) { + if (strlen(hlf_names[hlf]) == nlen + && strncmp(hlf_names[hlf], p, nlen) == 0) { w_hl_ids[hlf] = hl_id; break; } @@ -4274,7 +4320,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } } else if (pp == &p_so) { if (value < 0 && full_screen) { - errmsg = e_scroll; + errmsg = e_positive; } } else if (pp == &p_siso) { if (value < 0 && full_screen) { @@ -4296,12 +4342,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (value < 0) { errmsg = e_positive; } - } else if (pp == &curwin->w_p_fdc || pp == &curwin->w_allbuf_opt.wo_fdc) { - if (value < 0) { - errmsg = e_positive; - } else if (value > 12) { - errmsg = e_invarg; - } } else if (pp == &curwin->w_p_cole || pp == &curwin->w_allbuf_opt.wo_cole) { if (value < 0) { errmsg = e_positive; @@ -4605,9 +4645,7 @@ static void trigger_optionsset_string(int opt_idx, int opt_flags, } } -/* - * Called after an option changed: check if something needs to be redrawn. - */ +/// Called after an option changed: check if something needs to be redrawn. static void check_redraw(uint32_t flags) { // Careful: P_RCLR and P_RALL are a combination of other P_ flags @@ -5062,10 +5100,8 @@ static int find_key_option(const char_u *arg, bool has_lt) return find_key_option_len(arg, STRLEN(arg), has_lt); } -/* - * if 'all' == 0: show changed options - * if 'all' == 1: show all normal options - */ +/// if 'all' == 0: show changed options +/// if 'all' == 1: show all normal options static void showoptions( int all, @@ -5214,10 +5250,8 @@ void ui_refresh_options(void) } } -/* - * showoneopt: show the value of one option - * must not be called with a hidden option! - */ +/// showoneopt: show the value of one option +/// must not be called with a hidden option! static void showoneopt( vimoption_T *p, @@ -5253,28 +5287,26 @@ showoneopt( info_message = false; } -/* - * Write modified options as ":set" commands to a file. - * - * There are three values for "opt_flags": - * OPT_GLOBAL: Write global option values and fresh values of - * buffer-local options (used for start of a session - * file). - * OPT_GLOBAL + OPT_LOCAL: Idem, add fresh values of window-local options for - * curwin (used for a vimrc file). - * OPT_LOCAL: Write buffer-local option values for curbuf, fresh - * and local values for window-local options of - * curwin. Local values are also written when at the - * default value, because a modeline or autocommand - * may have set them when doing ":edit file" and the - * user has set them back at the default or fresh - * value. - * When "local_only" is true, don't write fresh - * values, only local values (for ":mkview"). - * (fresh value = value used for a new buffer or window for a local option). - * - * Return FAIL on error, OK otherwise. - */ +/// Write modified options as ":set" commands to a file. +/// +/// There are three values for "opt_flags": +/// OPT_GLOBAL: Write global option values and fresh values of +/// buffer-local options (used for start of a session +/// file). +/// OPT_GLOBAL + OPT_LOCAL: Idem, add fresh values of window-local options for +/// curwin (used for a vimrc file). +/// OPT_LOCAL: Write buffer-local option values for curbuf, fresh +/// and local values for window-local options of +/// curwin. Local values are also written when at the +/// default value, because a modeline or autocommand +/// may have set them when doing ":edit file" and the +/// user has set them back at the default or fresh +/// value. +/// When "local_only" is true, don't write fresh +/// values, only local values (for ":mkview"). +/// (fresh value = value used for a new buffer or window for a local option). +/// +/// Return FAIL on error, OK otherwise. int makeset(FILE *fd, int opt_flags, int local_only) { vimoption_T *p; @@ -5370,8 +5402,9 @@ int makeset(FILE *fd, int opt_flags, int local_only) do_endif = true; } if (put_setstring(fd, cmd, p->fullname, (char_u **)varp, - (p->flags & P_EXPAND) != 0) == FAIL) + p->flags) == FAIL) { return FAIL; + } if (do_endif) { if (put_line(fd, "endif") == FAIL) { return FAIL; @@ -5385,18 +5418,16 @@ int makeset(FILE *fd, int opt_flags, int local_only) return OK; } -/* - * Generate set commands for the local fold options only. Used when - * 'sessionoptions' or 'viewoptions' contains "folds" but not "options". - */ +/// Generate set commands for the local fold options only. Used when +/// 'sessionoptions' or 'viewoptions' contains "folds" but not "options". int makefoldset(FILE *fd) { - if (put_setstring(fd, "setlocal", "fdm", &curwin->w_p_fdm, false) == FAIL - || put_setstring(fd, "setlocal", "fde", &curwin->w_p_fde, false) + if (put_setstring(fd, "setlocal", "fdm", &curwin->w_p_fdm, 0) == FAIL + || put_setstring(fd, "setlocal", "fde", &curwin->w_p_fde, 0) == FAIL - || put_setstring(fd, "setlocal", "fmr", &curwin->w_p_fmr, false) + || put_setstring(fd, "setlocal", "fmr", &curwin->w_p_fmr, 0) == FAIL - || put_setstring(fd, "setlocal", "fdi", &curwin->w_p_fdi, false) + || put_setstring(fd, "setlocal", "fdi", &curwin->w_p_fdi, 0) == FAIL || put_setnum(fd, "setlocal", "fdl", &curwin->w_p_fdl) == FAIL || put_setnum(fd, "setlocal", "fml", &curwin->w_p_fml) == FAIL @@ -5409,10 +5440,13 @@ int makefoldset(FILE *fd) return OK; } -static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, int expand) +static int put_setstring(FILE *fd, char *cmd, char *name, + char_u **valuep, uint64_t flags) { char_u *s; - char_u *buf; + char_u *buf = NULL; + char_u *part = NULL; + char_u *p; if (fprintf(fd, "%s %s=", cmd, name) < 0) { return FAIL; @@ -5430,9 +5464,46 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, int e return FAIL; } } - } else if (expand) { - buf = xmalloc(MAXPATHL); - home_replace(NULL, *valuep, buf, MAXPATHL, false); + } else if ((flags & P_EXPAND) != 0) { + size_t size = (size_t)STRLEN(*valuep) + 1; + + // replace home directory in the whole option value into "buf" + buf = xmalloc(size); + if (buf == NULL) { + goto fail; + } + home_replace(NULL, *valuep, buf, size, false); + + // If the option value is longer than MAXPATHL, we need to append + // earch comma separated part of the option sperately, so that it + // can be expanded when read back. + if (size >= MAXPATHL && (flags & P_COMMA) != 0 + && vim_strchr(*valuep, ',') != NULL) { + part = xmalloc(size); + if (part == NULL) { + goto fail; + } + + // write line break to clear the option, e.g. ':set rtp=' + if (put_eol(fd) == FAIL) { + goto fail; + } + p = buf; + while (*p != NUL) { + // for each comma seperated option part, append value to + // the option, :set rtp+=value + if (fprintf(fd, "%s %s+=", cmd, name) < 0) { + goto fail; + } + (void)copy_option_part(&p, part, size, ","); + if (put_escstr(fd, part, 2) == FAIL || put_eol(fd) == FAIL) { + goto fail; + } + } + xfree(buf); + xfree(part); + return OK; + } if (put_escstr(fd, buf, 2) == FAIL) { xfree(buf); return FAIL; @@ -5446,6 +5517,10 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, int e return FAIL; } return OK; +fail: + xfree(buf); + xfree(part); + return FAIL; } static int put_setnum(FILE *fd, char *cmd, char *name, long *valuep) @@ -5481,12 +5556,10 @@ static int put_setbool(FILE *fd, char *cmd, char *name, int value) return OK; } -/* - * Compute columns for ruler and shown command. 'sc_col' is also used to - * decide what the maximum length of a message on the status line can be. - * If there is a status line for the last window, 'sc_col' is independent - * of 'ru_col'. - */ +/// Compute columns for ruler and shown command. 'sc_col' is also used to +/// decide what the maximum length of a message on the status line can be. +/// If there is a status line for the last window, 'sc_col' is independent +/// of 'ru_col'. #define COL_RULER 17 // columns needed by standard ruler @@ -5565,6 +5638,12 @@ void unset_global_local_option(char *name, void *from) clear_string_option(&buf->b_p_tc); buf->b_tc_flags = 0; break; + case PV_SISO: + curwin->w_p_siso = -1; + break; + case PV_SO: + curwin->w_p_so = -1; + break; case PV_DEF: clear_string_option(&buf->b_p_def); break; @@ -5614,9 +5693,7 @@ void unset_global_local_option(char *name, void *from) } } -/* - * Get pointer to option variable, depending on local or global scope. - */ +/// Get pointer to option variable, depending on local or global scope. static char_u *get_varp_scope(vimoption_T *p, int opt_flags) { if ((opt_flags & OPT_GLOBAL) && p->indir != PV_NONE) { @@ -5637,6 +5714,8 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags) case PV_AR: return (char_u *)&(curbuf->b_p_ar); case PV_TAGS: return (char_u *)&(curbuf->b_p_tags); case PV_TC: return (char_u *)&(curbuf->b_p_tc); + case PV_SISO: return (char_u *)&(curwin->w_p_siso); + case PV_SO: return (char_u *)&(curwin->w_p_so); case PV_DEF: return (char_u *)&(curbuf->b_p_def); case PV_INC: return (char_u *)&(curbuf->b_p_inc); case PV_DICT: return (char_u *)&(curbuf->b_p_dict); @@ -5655,9 +5734,7 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags) return get_varp(p); } -/* - * Get pointer to option variable. - */ +/// Get pointer to option variable. static char_u *get_varp(vimoption_T *p) { // hidden option, always return NULL @@ -5681,6 +5758,10 @@ static char_u *get_varp(vimoption_T *p) ? (char_u *)&(curbuf->b_p_tags) : p->var; case PV_TC: return *curbuf->b_p_tc != NUL ? (char_u *)&(curbuf->b_p_tc) : p->var; + case PV_SISO: return curwin->w_p_siso >= 0 + ? (char_u *)&(curwin->w_p_siso) : p->var; + case PV_SO: return curwin->w_p_so >= 0 + ? (char_u *)&(curwin->w_p_so) : p->var; case PV_BKC: return *curbuf->b_p_bkc != NUL ? (char_u *)&(curbuf->b_p_bkc) : p->var; case PV_DEF: return *curbuf->b_p_def != NUL @@ -5815,9 +5896,7 @@ static char_u *get_varp(vimoption_T *p) return (char_u *)&(curbuf->b_p_wm); } -/* - * Get the value of 'equalprg', either the buffer-local one or the global one. - */ +/// Get the value of 'equalprg', either the buffer-local one or the global one. char_u *get_equalprg(void) { if (*curbuf->b_p_ep == NUL) { @@ -5826,22 +5905,18 @@ char_u *get_equalprg(void) return curbuf->b_p_ep; } -/* - * Copy options from one window to another. - * Used when splitting a window. - */ +/// Copy options from one window to another. +/// Used when splitting a window. void win_copy_options(win_T *wp_from, win_T *wp_to) { copy_winopt(&wp_from->w_onebuf_opt, &wp_to->w_onebuf_opt); copy_winopt(&wp_from->w_allbuf_opt, &wp_to->w_allbuf_opt); } -/* - * Copy the options from one winopt_T to another. - * Doesn't free the old option values in "to", use clear_winopt() for that. - * The 'scroll' option is not copied, because it depends on the window height. - * The 'previewwindow' option is reset, there can be only one preview window. - */ +/// Copy the options from one winopt_T to another. +/// Doesn't free the old option values in "to", use clear_winopt() for that. +/// The 'scroll' option is not copied, because it depends on the window height. +/// The 'previewwindow' option is reset, there can be only one preview window. void copy_winopt(winopt_T *from, winopt_T *to) { to->wo_arab = from->wo_arab; @@ -5869,8 +5944,9 @@ void copy_winopt(winopt_T *from, winopt_T *to) to->wo_diff_saved = from->wo_diff_saved; to->wo_cocu = vim_strsave(from->wo_cocu); to->wo_cole = from->wo_cole; - to->wo_fdc = from->wo_fdc; - to->wo_fdc_save = from->wo_fdc_save; + to->wo_fdc = vim_strsave(from->wo_fdc); + to->wo_fdc_save = from->wo_diff_saved + ? vim_strsave(from->wo_fdc_save) : empty_option; to->wo_fen = from->wo_fen; to->wo_fen_save = from->wo_fen_save; to->wo_fdi = vim_strsave(from->wo_fdi); @@ -5892,20 +5968,18 @@ void copy_winopt(winopt_T *from, winopt_T *to) check_winopt(to); // don't want NULL pointers } -/* - * Check string options in a window for a NULL value. - */ +/// Check string options in a window for a NULL value. void check_win_options(win_T *win) { check_winopt(&win->w_onebuf_opt); check_winopt(&win->w_allbuf_opt); } -/* - * Check for NULL pointers in a winopt_T and replace them with empty_option. - */ +/// Check for NULL pointers in a winopt_T and replace them with empty_option. static void check_winopt(winopt_T *wop) { + check_string_option(&wop->wo_fdc); + check_string_option(&wop->wo_fdc_save); check_string_option(&wop->wo_fdi); check_string_option(&wop->wo_fdm); check_string_option(&wop->wo_fdm_save); @@ -5923,11 +5997,11 @@ static void check_winopt(winopt_T *wop) check_string_option(&wop->wo_lcs); } -/* - * Free the allocated memory inside a winopt_T. - */ +/// Free the allocated memory inside a winopt_T. void clear_winopt(winopt_T *wop) { + clear_string_option(&wop->wo_fdc); + clear_string_option(&wop->wo_fdc_save); clear_string_option(&wop->wo_fdi); clear_string_option(&wop->wo_fdm); clear_string_option(&wop->wo_fdm_save); @@ -5956,15 +6030,13 @@ void didset_window_options(win_T *wp) } -/* - * Copy global option values to local options for one buffer. - * Used when creating a new buffer and sometimes when entering a buffer. - * flags: - * BCO_ENTER We will enter the buf buffer. - * BCO_ALWAYS Always copy the options, but only set b_p_initialized when - * appropriate. - * BCO_NOHELP Don't copy the values to a help buffer. - */ +/// Copy global option values to local options for one buffer. +/// Used when creating a new buffer and sometimes when entering a buffer. +/// flags: +/// BCO_ENTER We will enter the buf buffer. +/// BCO_ALWAYS Always copy the options, but only set b_p_initialized when +/// appropriate. +/// BCO_NOHELP Don't copy the values to a help buffer. void buf_copy_options(buf_T *buf, int flags) { int should_copy = true; @@ -6006,10 +6078,8 @@ void buf_copy_options(buf_T *buf, int flags) save_p_isk = buf->b_p_isk; buf->b_p_isk = NULL; } - /* - * Always free the allocated strings. - * If not already initialized, set 'readonly' and copy 'fileformat'. - */ + // Always free the allocated strings. If not already initialized, + // reset 'readonly' and copy 'fileformat'. if (!buf->b_p_initialized) { free_buf_options(buf, true); buf->b_p_ro = false; // don't copy readonly @@ -6161,9 +6231,7 @@ void buf_copy_options(buf_T *buf, int flags) } } -/* - * Reset the 'modifiable' option and its default value. - */ +/// Reset the 'modifiable' option and its default value. void reset_modifiable(void) { int opt_idx; @@ -6176,17 +6244,13 @@ void reset_modifiable(void) } } -/* - * Set the global value for 'iminsert' to the local value. - */ +/// Set the global value for 'iminsert' to the local value. void set_iminsert_global(void) { p_iminsert = curbuf->b_p_iminsert; } -/* - * Set the global value for 'imsearch' to the local value. - */ +/// Set the global value for 'imsearch' to the local value. void set_imsearch_global(void) { p_imsearch = curbuf->b_p_imsearch; @@ -6488,10 +6552,8 @@ void ExpandOldSetting(int *num_file, char_u ***file) *num_file = 1; } -/* - * Get the value for the numeric or string option *opp in a nice format into - * NameBuff[]. Must not be called with a hidden option! - */ +/// Get the value for the numeric or string option///opp in a nice format into +/// NameBuff[]. Must not be called with a hidden option! static void option_value2string( vimoption_T *opp, @@ -6544,21 +6606,18 @@ static int wc_use_keyname(char_u *varp, long *wcp) return false; } -/* - * Any character has an equivalent 'langmap' character. This is used for - * keyboards that have a special language mode that sends characters above - * 128 (although other characters can be translated too). The "to" field is a - * Vim command character. This avoids having to switch the keyboard back to - * ASCII mode when leaving Insert mode. - * - * langmap_mapchar[] maps any of 256 chars to an ASCII char used for Vim - * commands. - * langmap_mapga.ga_data is a sorted table of langmap_entry_T. - * This does the same as langmap_mapchar[] for characters >= 256. - */ -/* - * With multi-byte support use growarray for 'langmap' chars >= 256 - */ +/// Any character has an equivalent 'langmap' character. This is used for +/// keyboards that have a special language mode that sends characters above +/// 128 (although other characters can be translated too). The "to" field is a +/// Vim command character. This avoids having to switch the keyboard back to +/// ASCII mode when leaving Insert mode. +/// +/// langmap_mapchar[] maps any of 256 chars to an ASCII char used for Vim +/// commands. +/// langmap_mapga.ga_data is a sorted table of langmap_entry_T. +/// This does the same as langmap_mapchar[] for characters >= 256. +/// +/// With multi-byte support use growarray for 'langmap' chars >= 256 typedef struct { int from; int to; @@ -6566,10 +6625,8 @@ typedef struct { static garray_T langmap_mapga = GA_EMPTY_INIT_VALUE; -/* - * Search for an entry in "langmap_mapga" for "from". If found set the "to" - * field. If not found insert a new entry at the appropriate location. - */ +/// Search for an entry in "langmap_mapga" for "from". If found set the "to" +/// field. If not found insert a new entry at the appropriate location. static void langmap_set_entry(int from, int to) { langmap_entry_T *entries = (langmap_entry_T *)(langmap_mapga.ga_data); @@ -6604,9 +6661,7 @@ static void langmap_set_entry(int from, int to) entries[0].to = to; } -/* - * Apply 'langmap' to multi-byte character "c" and return the result. - */ +/// Apply 'langmap' to multi-byte character "c" and return the result. int langmap_adjust_mb(int c) { langmap_entry_T *entries = (langmap_entry_T *)(langmap_mapga.ga_data); @@ -6637,10 +6692,8 @@ static void langmap_init(void) ga_init(&langmap_mapga, sizeof(langmap_entry_T), 8); } -/* - * Called when langmap option is set; the language map can be - * changed at any time! - */ +/// Called when langmap option is set; the language map can be +/// changed at any time! static void langmap_set(void) { char_u *p; @@ -6743,9 +6796,7 @@ bool shortmess(int x) && vim_strchr((char_u *)SHM_ALL_ABBREVIATIONS, x) != NULL))); } -/* - * paste_option_changed() - Called after p_paste was set or reset. - */ +/// paste_option_changed() - Called after p_paste was set or reset. static void paste_option_changed(void) { static int old_p_paste = false; @@ -6892,9 +6943,7 @@ void reset_option_was_set(const char *name) } } -/* - * fill_breakat_flags() -- called when 'breakat' changes value. - */ +/// fill_breakat_flags() -- called when 'breakat' changes value. static void fill_breakat_flags(void) { char_u *p; @@ -6911,12 +6960,10 @@ static void fill_breakat_flags(void) } } -/* - * Check an option that can be a range of string values. - * - * Return OK for correct value, FAIL otherwise. - * Empty is always OK. - */ +/// Check an option that can be a range of string values. +/// +/// Return OK for correct value, FAIL otherwise. +/// Empty is always OK. static int check_opt_strings( char_u *val, char **values, @@ -6926,13 +6973,11 @@ static int check_opt_strings( return opt_strings_flags(val, values, NULL, list); } -/* - * Handle an option that can be a range of string values. - * Set a flag in "*flagp" for each string present. - * - * Return OK for correct value, FAIL otherwise. - * Empty is always OK. - */ +/// Handle an option that can be a range of string values. +/// Set a flag in "*flagp" for each string present. +/// +/// Return OK for correct value, FAIL otherwise. +/// Empty is always OK. static int opt_strings_flags( char_u *val, // new value char **values, // array of valid string values @@ -6965,9 +7010,7 @@ static int opt_strings_flags( return OK; } -/* - * Read the 'wildmode' option, fill wim_flags[]. - */ +/// Read the 'wildmode' option, fill wim_flags[]. static int check_opt_wim(void) { char_u new_wim_flags[4]; @@ -6990,6 +7033,8 @@ static int check_opt_wim(void) new_wim_flags[idx] |= WIM_FULL; } else if (i == 4 && STRNCMP(p, "list", 4) == 0) { new_wim_flags[idx] |= WIM_LIST; + } else if (i == 8 && STRNCMP(p, "lastused", 8) == 0) { + new_wim_flags[idx] |= WIM_BUFLASTUSED; } else { return FAIL; } @@ -7018,25 +7063,24 @@ static int check_opt_wim(void) return OK; } -/* - * Check if backspacing over something is allowed. - * The parameter what is one of the following: whatBS_INDENT, BS_EOL - * or BS_START - */ +/// Check if backspacing over something is allowed. +/// The parameter what is one of the following: whatBS_INDENT, BS_EOL +/// or BS_START bool can_bs(int what) { + if (what == BS_START && bt_prompt(curbuf)) { + return false; + } switch (*p_bs) { - case '2': return true; - case '1': return what != BS_START; - case '0': return false; + case '2': return true; + case '1': return what != BS_START; + case '0': return false; } return vim_strchr(p_bs, what) != NULL; } -/* - * Save the current values of 'fileformat' and 'fileencoding', so that we know - * the file must be considered changed when the value is different. - */ +/// Save the current values of 'fileformat' and 'fileencoding', so that we know +/// the file must be considered changed when the value is different. void save_file_ff(buf_T *buf) { buf->b_start_ffc = *buf->b_p_ff; @@ -7086,18 +7130,14 @@ bool file_ff_differs(buf_T *buf, bool ignore_empty) return STRCMP(buf->b_start_fenc, buf->b_p_fenc) != 0; } -/* - * return OK if "p" is a valid fileformat name, FAIL otherwise. - */ +/// return OK if "p" is a valid fileformat name, FAIL otherwise. int check_ff_value(char_u *p) { return check_opt_strings(p, p_ff_values, false); } -/* - * Return the effective shiftwidth value for current buffer, using the - * 'tabstop' value when 'shiftwidth' is zero. - */ +/// Return the effective shiftwidth value for current buffer, using the +/// 'tabstop' value when 'shiftwidth' is zero. int get_sw_value(buf_T *buf) { long result = buf->b_p_sw ? buf->b_p_sw : buf->b_p_ts; @@ -7105,8 +7145,8 @@ int get_sw_value(buf_T *buf) return (int)result; } -// Return the effective softtabstop value for the current buffer, -// using the effective shiftwidth value when 'softtabstop' is negative. +/// Return the effective softtabstop value for the current buffer, +/// using the effective shiftwidth value when 'softtabstop' is negative. int get_sts_value(void) { long result = curbuf->b_p_sts < 0 ? get_sw_value(curbuf) : curbuf->b_p_sts; @@ -7114,12 +7154,10 @@ int get_sts_value(void) return (int)result; } -/* - * Check matchpairs option for "*initc". - * If there is a match set "*initc" to the matching character and "*findc" to - * the opposite character. Set "*backwards" to the direction. - * When "switchit" is true swap the direction. - */ +/// Check matchpairs option for "*initc". +/// If there is a match set "*initc" to the matching character and "*findc" to +/// the opposite character. Set "*backwards" to the direction. +/// When "switchit" is true swap the direction. void find_mps_values(int *initc, int *findc, int *backwards, int switchit) { char_u *ptr = curbuf->b_p_mps; @@ -7191,9 +7229,9 @@ static bool briopt_check(win_T *wp) } } - wp->w_p_brishift = bri_shift; - wp->w_p_brimin = bri_min; - wp->w_p_brisbr = bri_sbr; + wp->w_briopt_shift = bri_shift; + wp->w_briopt_min = bri_min; + wp->w_briopt_sbr = bri_sbr; return true; } @@ -7224,12 +7262,13 @@ int get_fileformat(buf_T *buf) /// argument. /// /// @param eap can be NULL! -int get_fileformat_force(buf_T *buf, exarg_T *eap) +int get_fileformat_force(const buf_T *buf, const exarg_T *eap) + FUNC_ATTR_NONNULL_ARG(1) { int c; if (eap != NULL && eap->force_ff != 0) { - c = eap->cmd[eap->force_ff]; + c = eap->force_ff; } else { if ((eap != NULL && eap->force_bin != 0) ? (eap->force_bin == FORCE_BIN) : buf->b_p_bin) { @@ -7409,3 +7448,21 @@ dict_T *get_winbuf_options(const int bufopt) return d; } + +/// Return the effective 'scrolloff' value for the current window, using the +/// global value when appropriate. +long get_scrolloff_value(void) +{ + // Disallow scrolloff in terminal-mode. #11915 + if (State & TERM_FOCUS) { + return 0; + } + return curwin->w_p_so < 0 ? p_so : curwin->w_p_so; +} + +/// Return the effective 'sidescrolloff' value for the current window, using the +/// global value when appropriate. +long get_sidescrolloff_value(void) +{ + return curwin->w_p_siso < 0 ? p_siso : curwin->w_p_siso; +} diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index fcad6836bf..ecaa941082 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -277,6 +277,7 @@ enum { #define WIM_FULL 1 #define WIM_LONGEST 2 #define WIM_LIST 4 +#define WIM_BUFLASTUSED 8 // arguments for can_bs() #define BS_INDENT 'i' // "Indent" @@ -577,8 +578,8 @@ static char *(p_ssop_values[]) = { # define SSOP_HELP 0x040 # define SSOP_BLANK 0x080 # define SSOP_GLOBALS 0x100 -# define SSOP_SLASH 0x200 -# define SSOP_UNIX 0x400 +# define SSOP_SLASH 0x200 // Deprecated, always set. +# define SSOP_UNIX 0x400 // Deprecated, always set. # define SSOP_SESDIR 0x800 # define SSOP_CURDIR 0x1000 # define SSOP_FOLDS 0x2000 @@ -835,6 +836,8 @@ enum { , WV_RLC , WV_SCBIND , WV_SCROLL + , WV_SISO + , WV_SO , WV_SPELL , WV_CUC , WV_CUL diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 7d080b8d56..e7c1a3fe88 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -831,10 +831,11 @@ return { }, { full_name='foldcolumn', abbreviation='fdc', - type='number', scope={'window'}, + type='string', scope={'window'}, vi_def=true, + alloced=true, redraw={'current_window'}, - defaults={if_true={vi=false}} + defaults={if_true={vi="0"}} }, { full_name='foldenable', abbreviation='fen', @@ -1989,7 +1990,7 @@ return { }, { full_name='scrolloff', abbreviation='so', - type='number', scope={'global'}, + type='number', scope={'global', 'window'}, vi_def=true, vim=true, redraw={'all_windows'}, @@ -2228,10 +2229,10 @@ return { }, { full_name='sidescrolloff', abbreviation='siso', - type='number', scope={'global'}, + type='number', scope={'global', 'window'}, vi_def=true, vim=true, - redraw={'current_buffer'}, + redraw={'all_windows'}, varname='p_siso', defaults={if_true={vi=0}} }, @@ -2586,6 +2587,7 @@ return { type='bool', scope={'global'}, vi_def=true, vim=true, + redraw={'ui_option'}, varname='p_ttimeout', defaults={if_true={vi=true}} }, @@ -2593,6 +2595,7 @@ return { full_name='ttimeoutlen', abbreviation='ttm', type='number', scope={'global'}, vi_def=true, + redraw={'ui_option'}, varname='p_ttm', defaults={if_true={vi=50}} }, diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index ec266796a8..082ad58223 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -847,6 +847,20 @@ const void *vim_env_iter_rev(const char delim, } } + +/// @param[out] exe_name should be at least MAXPATHL in size +void vim_get_prefix_from_exepath(char *exe_name) +{ + // TODO(bfredl): param could have been written as "char exe_name[MAXPATHL]" + // but c_grammar.lua does not recognize it (yet). + xstrlcpy(exe_name, (char *)get_vim_var_str(VV_PROGPATH), + MAXPATHL * sizeof(*exe_name)); + char *path_end = (char *)path_tail_with_sep((char_u *)exe_name); + *path_end = '\0'; // remove the trailing "nvim.exe" + path_end = (char *)path_tail((char_u *)exe_name); + *path_end = '\0'; // remove the trailing "bin/" +} + /// Vim getenv() wrapper with special handling of $HOME, $VIM, $VIMRUNTIME, /// allowing the user to override the Nvim runtime directory at runtime. /// Result must be freed by the caller. @@ -902,12 +916,7 @@ char *vim_getenv(const char *name) char exe_name[MAXPATHL]; // Find runtime path relative to the nvim binary: ../share/nvim/runtime if (vim_path == NULL) { - xstrlcpy(exe_name, (char *)get_vim_var_str(VV_PROGPATH), - sizeof(exe_name)); - char *path_end = (char *)path_tail_with_sep((char_u *)exe_name); - *path_end = '\0'; // remove the trailing "nvim.exe" - path_end = (char *)path_tail((char_u *)exe_name); - *path_end = '\0'; // remove the trailing "bin/" + vim_get_prefix_from_exepath(exe_name); if (append_path( exe_name, "share" _PATHSEPSTR "nvim" _PATHSEPSTR "runtime" _PATHSEPSTR, diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index ae922e4040..873b611151 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -1147,6 +1147,30 @@ bool os_fileid_equal_fileinfo(const FileID *file_id, && file_id->device_id == file_info->stat.st_dev; } +/// Return the canonicalized absolute pathname. +/// +/// @param[in] name Filename to be canonicalized. +/// @param[out] buf Buffer to store the canonicalized values. A minimum length +// of MAXPATHL+1 is required. If it is NULL, memory is +// allocated. In that case, the caller should deallocate this +// buffer. +/// +/// @return pointer to the buf on success, or NULL. +char *os_realpath(const char *name, char *buf) + FUNC_ATTR_NONNULL_ARG(1) +{ + uv_fs_t request; + int result = uv_fs_realpath(&fs_loop, &request, name, NULL); + if (result == kLibuvSuccess) { + if (buf == NULL) { + buf = xmallocz(MAXPATHL); + } + xstrlcpy(buf, request.ptr, MAXPATHL + 1); + } + uv_fs_req_cleanup(&request); + return result == kLibuvSuccess ? buf : NULL; +} + #ifdef WIN32 # include <shlobj.h> @@ -1233,4 +1257,49 @@ shortcut_end: return rfname; } +#define is_path_sep(c) ((c) == L'\\' || (c) == L'/') +/// Returns true if the path contains a reparse point (junction or symbolic +/// link). Otherwise false in returned. +bool os_is_reparse_point_include(const char *path) +{ + wchar_t *p, *q, *utf16_path; + wchar_t buf[MAX_PATH]; + DWORD attr; + bool result = false; + + const int r = utf8_to_utf16(path, -1, &utf16_path); + if (r != 0) { + EMSG2("utf8_to_utf16 failed: %d", r); + return false; + } + + p = utf16_path; + if (isalpha(p[0]) && p[1] == L':' && is_path_sep(p[2])) { + p += 3; + } else if (is_path_sep(p[0]) && is_path_sep(p[1])) { + p += 2; + } + + while (*p != L'\0') { + q = wcspbrk(p, L"\\/"); + if (q == NULL) { + p = q = utf16_path + wcslen(utf16_path); + } else { + p = q + 1; + } + if (q - utf16_path >= MAX_PATH) { + break; + } + wcsncpy(buf, utf16_path, (size_t)(q - utf16_path)); + buf[q - utf16_path] = L'\0'; + attr = GetFileAttributesW(buf); + if (attr != INVALID_FILE_ATTRIBUTES + && (attr & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { + result = true; + break; + } + } + xfree(utf16_path); + return result; +} #endif diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index d1de18d5b3..6294d5e4e2 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -9,13 +9,17 @@ #include <uv.h> #include "nvim/ascii.h" +#include "nvim/fileio.h" #include "nvim/lib/kvec.h" #include "nvim/log.h" #include "nvim/event/loop.h" #include "nvim/event/libuv_process.h" #include "nvim/event/rstream.h" +#include "nvim/ex_cmds.h" +#include "nvim/misc1.h" #include "nvim/os/shell.h" #include "nvim/os/signal.h" +#include "nvim/path.h" #include "nvim/types.h" #include "nvim/main.h" #include "nvim/vim.h" @@ -32,6 +36,8 @@ #define NS_1_SECOND 1000000000U // 1 second, in nanoseconds #define OUT_DATA_THRESHOLD 1024 * 10U // 10KB, "a few screenfuls" of data. +#define SHELL_SPECIAL (char_u *)"\t \"&'$;<>()\\|" + typedef struct { char *data; size_t cap, len; @@ -41,6 +47,498 @@ typedef struct { # include "os/shell.c.generated.h" #endif +static void save_patterns(int num_pat, char_u **pat, int *num_file, + char_u ***file) +{ + *file = xmalloc((size_t)num_pat * sizeof(char_u *)); + for (int i = 0; i < num_pat; i++) { + char_u *s = vim_strsave(pat[i]); + // Be compatible with expand_filename(): halve the number of + // backslashes. + backslash_halve(s); + (*file)[i] = s; + } + *num_file = num_pat; +} + +static bool have_wildcard(int num, char_u **file) +{ + for (int i = 0; i < num; i++) { + if (path_has_wildcard(file[i])) { + return true; + } + } + return false; +} + +static bool have_dollars(int num, char_u **file) +{ + for (int i = 0; i < num; i++) { + if (vim_strchr(file[i], '$') != NULL) { + return true; + } + } + return false; +} + +/// Performs wildcard pattern matching using the shell. +/// +/// @param num_pat is the number of input patterns. +/// @param pat is an array of pointers to input patterns. +/// @param[out] num_file is pointer to number of matched file names. +/// Set to the number of pointers in *file. +/// @param[out] file is pointer to array of pointers to matched file names. +/// Memory pointed to by the initial value of *file will +/// not be freed. +/// Set to NULL if FAIL is returned. Otherwise points to +/// allocated memory. +/// @param flags is a combination of EW_* flags used in +/// expand_wildcards(). +/// If matching fails but EW_NOTFOUND is set in flags or +/// there are no wildcards, the patterns from pat are +/// copied into *file. +/// +/// @returns OK for success or FAIL for error. +int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, + char_u ***file, int flags) + FUNC_ATTR_NONNULL_ARG(3) + FUNC_ATTR_NONNULL_ARG(4) +{ + int i; + size_t len; + char_u *p; + bool dir; + char_u *extra_shell_arg = NULL; + ShellOpts shellopts = kShellOptExpand | kShellOptSilent; + int j; + char_u *tempname; + char_u *command; + FILE *fd; + char_u *buffer; +#define STYLE_ECHO 0 // use "echo", the default +#define STYLE_GLOB 1 // use "glob", for csh +#define STYLE_VIMGLOB 2 // use "vimglob", for Posix sh +#define STYLE_PRINT 3 // use "print -N", for zsh +#define STYLE_BT 4 // `cmd` expansion, execute the pattern directly + int shell_style = STYLE_ECHO; + int check_spaces; + static bool did_find_nul = false; + bool ampersent = false; + // vimglob() function to define for Posix shell + static char *sh_vimglob_func = + "vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >"; + + bool is_fish_shell = +#if defined(UNIX) + STRNCMP(invocation_path_tail(p_sh, NULL), "fish", 4) == 0; +#else + false; +#endif + + *num_file = 0; // default: no files found + *file = NULL; + + // If there are no wildcards, just copy the names to allocated memory. + // Saves a lot of time, because we don't have to start a new shell. + if (!have_wildcard(num_pat, pat)) { + save_patterns(num_pat, pat, num_file, file); + return OK; + } + + // Don't allow any shell command in the sandbox. + if (sandbox != 0 && check_secure()) { + return FAIL; + } + + // Don't allow the use of backticks in secure and restricted mode. + if (secure || restricted) { + for (i = 0; i < num_pat; i++) { + if (vim_strchr(pat[i], '`') != NULL + && (check_restricted() || check_secure())) { + return FAIL; + } + } + } + + // get a name for the temp file + if ((tempname = vim_tempname()) == NULL) { + EMSG(_(e_notmp)); + return FAIL; + } + + // Let the shell expand the patterns and write the result into the temp + // file. + // STYLE_BT: NL separated + // If expanding `cmd` execute it directly. + // STYLE_GLOB: NUL separated + // If we use *csh, "glob" will work better than "echo". + // STYLE_PRINT: NL or NUL separated + // If we use *zsh, "print -N" will work better than "glob". + // STYLE_VIMGLOB: NL separated + // If we use *sh*, we define "vimglob()". + // STYLE_ECHO: space separated. + // A shell we don't know, stay safe and use "echo". + if (num_pat == 1 && *pat[0] == '`' + && (len = STRLEN(pat[0])) > 2 + && *(pat[0] + len - 1) == '`') { + shell_style = STYLE_BT; + } else if ((len = STRLEN(p_sh)) >= 3) { + if (STRCMP(p_sh + len - 3, "csh") == 0) { + shell_style = STYLE_GLOB; + } else if (STRCMP(p_sh + len - 3, "zsh") == 0) { + shell_style = STYLE_PRINT; + } + } + if (shell_style == STYLE_ECHO + && strstr((char *)path_tail(p_sh), "sh") != NULL) { + shell_style = STYLE_VIMGLOB; + } + + // Compute the length of the command. We need 2 extra bytes: for the + // optional '&' and for the NUL. + // Worst case: "unset nonomatch; print -N >" plus two is 29 + len = STRLEN(tempname) + 29; + if (shell_style == STYLE_VIMGLOB) { + len += STRLEN(sh_vimglob_func); + } + + for (i = 0; i < num_pat; i++) { + // Count the length of the patterns in the same way as they are put in + // "command" below. + len++; // add space + for (j = 0; pat[i][j] != NUL; j++) { + if (vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) { + len++; // may add a backslash + } + len++; + } + } + + if (is_fish_shell) { + len += sizeof("egin;"" end") - 1; + } + + command = xmalloc(len); + + // Build the shell command: + // - Set $nonomatch depending on EW_NOTFOUND (hopefully the shell + // recognizes this). + // - Add the shell command to print the expanded names. + // - Add the temp file name. + // - Add the file name patterns. + if (shell_style == STYLE_BT) { + // change `command; command& ` to (command; command ) + if (is_fish_shell) { + STRCPY(command, "begin; "); + } else { + STRCPY(command, "("); + } + STRCAT(command, pat[0] + 1); // exclude first backtick + p = command + STRLEN(command) - 1; + if (is_fish_shell) { + *p-- = ';'; + STRCAT(command, " end"); + } else { + *p-- = ')'; // remove last backtick + } + while (p > command && ascii_iswhite(*p)) { + p--; + } + if (*p == '&') { // remove trailing '&' + ampersent = true; + *p = ' '; + } + STRCAT(command, ">"); + } else { + if (flags & EW_NOTFOUND) { + STRCPY(command, "set nonomatch; "); + } else { + STRCPY(command, "unset nonomatch; "); + } + if (shell_style == STYLE_GLOB) { + STRCAT(command, "glob >"); + } else if (shell_style == STYLE_PRINT) { + STRCAT(command, "print -N >"); + } else if (shell_style == STYLE_VIMGLOB) { + STRCAT(command, sh_vimglob_func); + } else { + STRCAT(command, "echo >"); + } + } + + STRCAT(command, tempname); + + if (shell_style != STYLE_BT) { + for (i = 0; i < num_pat; i++) { + // Put a backslash before special + // characters, except inside ``. + bool intick = false; + + p = command + STRLEN(command); + *p++ = ' '; + for (j = 0; pat[i][j] != NUL; j++) { + if (pat[i][j] == '`') { + intick = !intick; + } else if (pat[i][j] == '\\' && pat[i][j + 1] != NUL) { + // Remove a backslash, take char literally. But keep + // backslash inside backticks, before a special character + // and before a backtick. + if (intick + || vim_strchr(SHELL_SPECIAL, pat[i][j + 1]) != NULL + || pat[i][j + 1] == '`') { + *p++ = '\\'; + } + j++; + } else if (!intick + && ((flags & EW_KEEPDOLLAR) == 0 || pat[i][j] != '$') + && vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) { + // Put a backslash before a special character, but not + // when inside ``. And not for $var when EW_KEEPDOLLAR is + // set. + *p++ = '\\'; + } + + // Copy one character. + *p++ = pat[i][j]; + } + *p = NUL; + } + } + + if (flags & EW_SILENT) { + shellopts |= kShellOptHideMess; + } + + if (ampersent) { + STRCAT(command, "&"); // put the '&' after the redirection + } + + // Using zsh -G: If a pattern has no matches, it is just deleted from + // the argument list, otherwise zsh gives an error message and doesn't + // expand any other pattern. + if (shell_style == STYLE_PRINT) { + extra_shell_arg = (char_u *)"-G"; // Use zsh NULL_GLOB option + + // If we use -f then shell variables set in .cshrc won't get expanded. + // vi can do it, so we will too, but it is only necessary if there is a "$" + // in one of the patterns, otherwise we can still use the fast option. + } else if (shell_style == STYLE_GLOB && !have_dollars(num_pat, pat)) { + extra_shell_arg = (char_u *)"-f"; // Use csh fast option + } + + // execute the shell command + i = call_shell(command, shellopts, extra_shell_arg); + + // When running in the background, give it some time to create the temp + // file, but don't wait for it to finish. + if (ampersent) { + os_delay(10L, true); + } + + xfree(command); + + if (i) { // os_call_shell() failed + os_remove((char *)tempname); + xfree(tempname); + // With interactive completion, the error message is not printed. + if (!(flags & EW_SILENT)) { + msg_putchar('\n'); // clear bottom line quickly + cmdline_row = Rows - 1; // continue on last line + MSG(_(e_wildexpand)); + msg_start(); // don't overwrite this message + } + + // If a `cmd` expansion failed, don't list `cmd` as a match, even when + // EW_NOTFOUND is given + if (shell_style == STYLE_BT) { + return FAIL; + } + goto notfound; + } + + // read the names from the file into memory + fd = fopen((char *)tempname, READBIN); + if (fd == NULL) { + // Something went wrong, perhaps a file name with a special char. + if (!(flags & EW_SILENT)) { + MSG(_(e_wildexpand)); + msg_start(); // don't overwrite this message + } + xfree(tempname); + goto notfound; + } + int fseek_res = fseek(fd, 0L, SEEK_END); + if (fseek_res < 0) { + xfree(tempname); + fclose(fd); + return FAIL; + } + int64_t templen = ftell(fd); // get size of temp file + if (templen < 0) { + xfree(tempname); + fclose(fd); + return FAIL; + } +#if SIZEOF_LONG_LONG > SIZEOF_SIZE_T + assert(templen <= (long long)SIZE_MAX); // NOLINT(runtime/int) +#endif + len = (size_t)templen; + fseek(fd, 0L, SEEK_SET); + buffer = xmalloc(len + 1); + // fread() doesn't terminate buffer with NUL; + // appropriate termination (not always NUL) is done below. + size_t readlen = fread((char *)buffer, 1, len, fd); + fclose(fd); + os_remove((char *)tempname); + if (readlen != len) { + // unexpected read error + EMSG2(_(e_notread), tempname); + xfree(tempname); + xfree(buffer); + return FAIL; + } + xfree(tempname); + + // file names are separated with Space + if (shell_style == STYLE_ECHO) { + buffer[len] = '\n'; // make sure the buffer ends in NL + p = buffer; + for (i = 0; *p != '\n'; i++) { // count number of entries + while (*p != ' ' && *p != '\n') { + p++; + } + p = skipwhite(p); // skip to next entry + } + // file names are separated with NL + } else if (shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB) { + buffer[len] = NUL; // make sure the buffer ends in NUL + p = buffer; + for (i = 0; *p != NUL; i++) { // count number of entries + while (*p != '\n' && *p != NUL) { + p++; + } + if (*p != NUL) { + p++; + } + p = skipwhite(p); // skip leading white space + } + // file names are separated with NUL + } else { + // Some versions of zsh use spaces instead of NULs to separate + // results. Only do this when there is no NUL before the end of the + // buffer, otherwise we would never be able to use file names with + // embedded spaces when zsh does use NULs. + // When we found a NUL once, we know zsh is OK, set did_find_nul and + // don't check for spaces again. + check_spaces = false; + if (shell_style == STYLE_PRINT && !did_find_nul) { + // If there is a NUL, set did_find_nul, else set check_spaces + buffer[len] = NUL; + if (len && (int)STRLEN(buffer) < (int)len) { + did_find_nul = true; + } else { + check_spaces = true; + } + } + + // Make sure the buffer ends with a NUL. For STYLE_PRINT there + // already is one, for STYLE_GLOB it needs to be added. + if (len && buffer[len - 1] == NUL) { + len--; + } else { + buffer[len] = NUL; + } + for (p = buffer; p < buffer + len; p++) { + if (*p == NUL || (*p == ' ' && check_spaces)) { // count entry + i++; + *p = NUL; + } + } + if (len) { + i++; // count last entry + } + } + assert(buffer[len] == NUL || buffer[len] == '\n'); + + if (i == 0) { + // Can happen when using /bin/sh and typing ":e $NO_SUCH_VAR^I". + // /bin/sh will happily expand it to nothing rather than returning an + // error; and hey, it's good to check anyway -- webb. + xfree(buffer); + goto notfound; + } + *num_file = i; + *file = xmalloc(sizeof(char_u *) * (size_t)i); + + // Isolate the individual file names. + p = buffer; + for (i = 0; i < *num_file; i++) { + (*file)[i] = p; + // Space or NL separates + if (shell_style == STYLE_ECHO || shell_style == STYLE_BT + || shell_style == STYLE_VIMGLOB) { + while (!(shell_style == STYLE_ECHO && *p == ' ') + && *p != '\n' && *p != NUL) { + p++; + } + if (p == buffer + len) { // last entry + *p = NUL; + } else { + *p++ = NUL; + p = skipwhite(p); // skip to next entry + } + } else { // NUL separates + while (*p && p < buffer + len) { // skip entry + p++; + } + p++; // skip NUL + } + } + + // Move the file names to allocated memory. + for (j = 0, i = 0; i < *num_file; i++) { + // Require the files to exist. Helps when using /bin/sh + if (!(flags & EW_NOTFOUND) && !os_path_exists((*file)[i])) { + continue; + } + + // check if this entry should be included + dir = (os_isdir((*file)[i])); + if ((dir && !(flags & EW_DIR)) || (!dir && !(flags & EW_FILE))) { + continue; + } + + // Skip files that are not executable if we check for that. + if (!dir && (flags & EW_EXEC) + && !os_can_exe((char *)(*file)[i], NULL, !(flags & EW_SHELLCMD))) { + continue; + } + + p = xmalloc(STRLEN((*file)[i]) + 1 + dir); + STRCPY(p, (*file)[i]); + if (dir) { + add_pathsep((char *)p); // add '/' to a directory name + } + (*file)[j++] = p; + } + xfree(buffer); + *num_file = j; + + if (*num_file == 0) { // rejected all entries + XFREE_CLEAR(*file); + goto notfound; + } + + return OK; + +notfound: + if (flags & EW_NOTFOUND) { + save_patterns(num_pat, pat, num_file, file); + return OK; + } + return FAIL; +} + /// Builds the argument vector for running the user-configured 'shell' (p_sh) /// with an optional command prefixed by 'shellcmdflag' (p_shcf). E.g.: /// diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index ba6226ef9d..112de9fed8 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -50,22 +50,13 @@ void signal_init(void) signal_watcher_init(&main_loop, &shup, NULL); signal_watcher_init(&main_loop, &squit, NULL); signal_watcher_init(&main_loop, &sterm, NULL); -#ifdef SIGPIPE - signal_watcher_start(&spipe, on_signal, SIGPIPE); -#endif - signal_watcher_start(&shup, on_signal, SIGHUP); -#ifdef SIGQUIT - signal_watcher_start(&squit, on_signal, SIGQUIT); -#endif - signal_watcher_start(&sterm, on_signal, SIGTERM); #ifdef SIGPWR signal_watcher_init(&main_loop, &spwr, NULL); - signal_watcher_start(&spwr, on_signal, SIGPWR); #endif #ifdef SIGUSR1 signal_watcher_init(&main_loop, &susr1, NULL); - signal_watcher_start(&susr1, on_signal, SIGUSR1); #endif + signal_start(); } void signal_teardown(void) @@ -83,11 +74,33 @@ void signal_teardown(void) #endif } +void signal_start(void) +{ +#ifdef SIGPIPE + signal_watcher_start(&spipe, on_signal, SIGPIPE); +#endif + signal_watcher_start(&shup, on_signal, SIGHUP); +#ifdef SIGQUIT + signal_watcher_start(&squit, on_signal, SIGQUIT); +#endif + signal_watcher_start(&sterm, on_signal, SIGTERM); +#ifdef SIGPWR + signal_watcher_start(&spwr, on_signal, SIGPWR); +#endif +#ifdef SIGUSR1 + signal_watcher_start(&susr1, on_signal, SIGUSR1); +#endif +} + void signal_stop(void) { +#ifdef SIGPIPE signal_watcher_stop(&spipe); +#endif signal_watcher_stop(&shup); +#ifdef SIGQUIT signal_watcher_stop(&squit); +#endif signal_watcher_stop(&sterm); #ifdef SIGPWR signal_watcher_stop(&spwr); diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index 4dd0614fe2..346e40e02e 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -2,9 +2,6 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> -#include <stdint.h> -#include <stdbool.h> -#include <time.h> #include <limits.h> #include <uv.h> @@ -13,7 +10,7 @@ #include "nvim/os/time.h" #include "nvim/os/input.h" #include "nvim/event/loop.h" -#include "nvim/vim.h" +#include "nvim/os/os.h" #include "nvim/main.h" static uv_mutex_t delay_mutex; @@ -112,6 +109,10 @@ void os_microdelay(uint64_t us, bool ignoreinput) uv_mutex_unlock(&delay_mutex); } +// Cache of the current timezone name as retrieved from TZ, or an empty string +// where unset, up to 64 octets long including trailing null byte. +static char tz_cache[64]; + /// Portable version of POSIX localtime_r() /// /// @return NULL in case of error @@ -120,6 +121,19 @@ struct tm *os_localtime_r(const time_t *restrict clock, { #ifdef UNIX // POSIX provides localtime_r() as a thread-safe version of localtime(). + // + // Check to see if the environment variable TZ has changed since the last run. + // Call tzset(3) to update the global timezone variables if it has. + // POSIX standard doesn't require localtime_r() implementations to do that + // as it does with localtime(), and we don't want to call tzset() every time. + const char *tz = os_getenv("TZ"); + if (tz == NULL) { + tz = ""; + } + if (strncmp(tz_cache, tz, sizeof(tz_cache) - 1) != 0) { + tzset(); + xstrlcpy(tz_cache, tz, sizeof(tz_cache)); + } return localtime_r(clock, result); // NOLINT(runtime/threadsafe_fn) #else // Windows version of localtime() is thread-safe. @@ -144,6 +158,39 @@ struct tm *os_localtime(struct tm *result) FUNC_ATTR_NONNULL_ALL return os_localtime_r(&rawtime, result); } +/// Portable version of POSIX ctime_r() +/// +/// @param clock[in] +/// @param result[out] Pointer to a 'char' where the result should be placed +/// @param result_len length of result buffer +/// @return human-readable string of current local time +char *os_ctime_r(const time_t *restrict clock, char *restrict result, + size_t result_len) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET +{ + struct tm clock_local; + struct tm *clock_local_ptr = os_localtime_r(clock, &clock_local); + // MSVC returns NULL for an invalid value of seconds. + if (clock_local_ptr == NULL) { + snprintf(result, result_len, "%s\n", _("(Invalid)")); + } else { + strftime(result, result_len, "%a %b %d %H:%M:%S %Y\n", clock_local_ptr); + } + return result; +} + +/// Gets the current Unix timestamp and adjusts it to local time. +/// +/// @param result[out] Pointer to a 'char' where the result should be placed +/// @param result_len length of result buffer +/// @return human-readable string of current local time +char *os_ctime(char *result, size_t result_len) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET +{ + time_t rawtime = time(NULL); + return os_ctime_r(&rawtime, result, result_len); +} + /// Obtains the current Unix timestamp. /// /// @return Seconds since epoch. diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index 0f44df2188..be4bd9709b 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -1,13 +1,6 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * os_unix.c -- code for all flavors of Unix (BSD, SYSV, SVR4, POSIX, ...) - * - * A lot of this file was originally written by Juergen Weigert and later - * changed beyond recognition. - */ - #include <assert.h> #include <errno.h> #include <inttypes.h> @@ -79,527 +72,3 @@ void mch_free_acl(vim_acl_T aclent) return; } #endif - -void mch_exit(int r) - FUNC_ATTR_NORETURN -{ - exiting = true; - - ui_flush(); - ui_call_stop(); - ml_close_all(true); // remove all memfiles - - if (!event_teardown() && r == 0) { - r = 1; // Exit with error if main_loop did not teardown gracefully. - } - if (input_global_fd() >= 0) { - stream_set_blocking(input_global_fd(), true); // normalize stream (#2598) - } - - ILOG("Nvim exit: %d", r); - -#ifdef EXITFREE - free_all_mem(); -#endif - - exit(r); -} - -#define SHELL_SPECIAL (char_u *)"\t \"&'$;<>()\\|" - -/// Does wildcard pattern matching using the shell. -/// -/// @param num_pat is the number of input patterns. -/// @param pat is an array of pointers to input patterns. -/// @param[out] num_file is pointer to number of matched file names. -/// Set to the number of pointers in *file. -/// @param[out] file is pointer to array of pointers to matched file names. -/// Memory pointed to by the initial value of *file will -/// not be freed. -/// Set to NULL if FAIL is returned. Otherwise points to -/// allocated memory. -/// @param flags is a combination of EW_* flags used in -/// expand_wildcards(). -/// If matching fails but EW_NOTFOUND is set in flags or -/// there are no wildcards, the patterns from pat are -/// copied into *file. -/// -/// @returns OK for success or FAIL for error. -int mch_expand_wildcards(int num_pat, char_u **pat, int *num_file, - char_u ***file, int flags) FUNC_ATTR_NONNULL_ARG(3) - FUNC_ATTR_NONNULL_ARG(4) -{ - int i; - size_t len; - char_u *p; - bool dir; - char_u *extra_shell_arg = NULL; - ShellOpts shellopts = kShellOptExpand | kShellOptSilent; - int j; - char_u *tempname; - char_u *command; - FILE *fd; - char_u *buffer; -#define STYLE_ECHO 0 /* use "echo", the default */ -#define STYLE_GLOB 1 /* use "glob", for csh */ -#define STYLE_VIMGLOB 2 /* use "vimglob", for Posix sh */ -#define STYLE_PRINT 3 /* use "print -N", for zsh */ -#define STYLE_BT 4 /* `cmd` expansion, execute the pattern - * directly */ - int shell_style = STYLE_ECHO; - int check_spaces; - static bool did_find_nul = false; - bool ampersent = false; - // vimglob() function to define for Posix shell - static char *sh_vimglob_func = - "vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >"; - - bool is_fish_shell = -#if defined(UNIX) - STRNCMP(invocation_path_tail(p_sh, NULL), "fish", 4) == 0; -#else - false; -#endif - - *num_file = 0; // default: no files found - *file = NULL; - - // If there are no wildcards, just copy the names to allocated memory. - // Saves a lot of time, because we don't have to start a new shell. - if (!have_wildcard(num_pat, pat)) { - save_patterns(num_pat, pat, num_file, file); - return OK; - } - - // Don't allow any shell command in the sandbox. - if (sandbox != 0 && check_secure()) { - return FAIL; - } - - // Don't allow the use of backticks in secure and restricted mode. - if (secure || restricted) { - for (i = 0; i < num_pat; i++) { - if (vim_strchr(pat[i], '`') != NULL - && (check_restricted() || check_secure())) { - return FAIL; - } - } - } - - // get a name for the temp file - if ((tempname = vim_tempname()) == NULL) { - EMSG(_(e_notmp)); - return FAIL; - } - - // Let the shell expand the patterns and write the result into the temp - // file. - // STYLE_BT: NL separated - // If expanding `cmd` execute it directly. - // STYLE_GLOB: NUL separated - // If we use *csh, "glob" will work better than "echo". - // STYLE_PRINT: NL or NUL separated - // If we use *zsh, "print -N" will work better than "glob". - // STYLE_VIMGLOB: NL separated - // If we use *sh*, we define "vimglob()". - // STYLE_ECHO: space separated. - // A shell we don't know, stay safe and use "echo". - if (num_pat == 1 && *pat[0] == '`' - && (len = STRLEN(pat[0])) > 2 - && *(pat[0] + len - 1) == '`') { - shell_style = STYLE_BT; - } else if ((len = STRLEN(p_sh)) >= 3) { - if (STRCMP(p_sh + len - 3, "csh") == 0) { - shell_style = STYLE_GLOB; - } else if (STRCMP(p_sh + len - 3, "zsh") == 0) { - shell_style = STYLE_PRINT; - } - } - if (shell_style == STYLE_ECHO && strstr((char *)path_tail(p_sh), - "sh") != NULL) - shell_style = STYLE_VIMGLOB; - - // Compute the length of the command. We need 2 extra bytes: for the - // optional '&' and for the NUL. - // Worst case: "unset nonomatch; print -N >" plus two is 29 - len = STRLEN(tempname) + 29; - if (shell_style == STYLE_VIMGLOB) - len += STRLEN(sh_vimglob_func); - - for (i = 0; i < num_pat; i++) { - // Count the length of the patterns in the same way as they are put in - // "command" below. - len++; // add space - for (j = 0; pat[i][j] != NUL; j++) { - if (vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) { - len++; // may add a backslash - } - len++; - } - } - - if (is_fish_shell) { - len += sizeof("egin;"" end") - 1; - } - - command = xmalloc(len); - - // Build the shell command: - // - Set $nonomatch depending on EW_NOTFOUND (hopefully the shell - // recognizes this). - // - Add the shell command to print the expanded names. - // - Add the temp file name. - // - Add the file name patterns. - if (shell_style == STYLE_BT) { - // change `command; command& ` to (command; command ) - if (is_fish_shell) { - STRCPY(command, "begin; "); - } else { - STRCPY(command, "("); - } - STRCAT(command, pat[0] + 1); // exclude first backtick - p = command + STRLEN(command) - 1; - if (is_fish_shell) { - *p-- = ';'; - STRCAT(command, " end"); - } else { - *p-- = ')'; // remove last backtick - } - while (p > command && ascii_iswhite(*p)) { - p--; - } - if (*p == '&') { // remove trailing '&' - ampersent = true; - *p = ' '; - } - STRCAT(command, ">"); - } else { - if (flags & EW_NOTFOUND) - STRCPY(command, "set nonomatch; "); - else - STRCPY(command, "unset nonomatch; "); - if (shell_style == STYLE_GLOB) - STRCAT(command, "glob >"); - else if (shell_style == STYLE_PRINT) - STRCAT(command, "print -N >"); - else if (shell_style == STYLE_VIMGLOB) - STRCAT(command, sh_vimglob_func); - else - STRCAT(command, "echo >"); - } - - STRCAT(command, tempname); - - if (shell_style != STYLE_BT) { - for (i = 0; i < num_pat; i++) { - // Put a backslash before special - // characters, except inside ``. - bool intick = false; - - p = command + STRLEN(command); - *p++ = ' '; - for (j = 0; pat[i][j] != NUL; j++) { - if (pat[i][j] == '`') { - intick = !intick; - } else if (pat[i][j] == '\\' && pat[i][j + 1] != NUL) { - // Remove a backslash, take char literally. But keep - // backslash inside backticks, before a special character - // and before a backtick. - if (intick - || vim_strchr(SHELL_SPECIAL, pat[i][j + 1]) != NULL - || pat[i][j + 1] == '`') { - *p++ = '\\'; - } - j++; - } else if (!intick - && ((flags & EW_KEEPDOLLAR) == 0 || pat[i][j] != '$') - && vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) { - // Put a backslash before a special character, but not - // when inside ``. And not for $var when EW_KEEPDOLLAR is - // set. - *p++ = '\\'; - } - - // Copy one character. - *p++ = pat[i][j]; - } - *p = NUL; - } - } - - if (flags & EW_SILENT) { - shellopts |= kShellOptHideMess; - } - - if (ampersent) { - STRCAT(command, "&"); // put the '&' after the redirection - } - - // Using zsh -G: If a pattern has no matches, it is just deleted from - // the argument list, otherwise zsh gives an error message and doesn't - // expand any other pattern. - if (shell_style == STYLE_PRINT) { - extra_shell_arg = (char_u *)"-G"; // Use zsh NULL_GLOB option - - // If we use -f then shell variables set in .cshrc won't get expanded. - // vi can do it, so we will too, but it is only necessary if there is a "$" - // in one of the patterns, otherwise we can still use the fast option. - } else if (shell_style == STYLE_GLOB && !have_dollars(num_pat, pat)) { - extra_shell_arg = (char_u *)"-f"; // Use csh fast option - } - - // execute the shell command - i = call_shell( - command, - shellopts, - extra_shell_arg - ); - - // When running in the background, give it some time to create the temp - // file, but don't wait for it to finish. - if (ampersent) { - os_delay(10L, true); - } - - xfree(command); - - if (i) { // os_call_shell() failed - os_remove((char *)tempname); - xfree(tempname); - // With interactive completion, the error message is not printed. - if (!(flags & EW_SILENT)) { - msg_putchar('\n'); // clear bottom line quickly - cmdline_row = Rows - 1; // continue on last line - MSG(_(e_wildexpand)); - msg_start(); // don't overwrite this message - } - - // If a `cmd` expansion failed, don't list `cmd` as a match, even when - // EW_NOTFOUND is given - if (shell_style == STYLE_BT) { - return FAIL; - } - goto notfound; - } - - // read the names from the file into memory - fd = fopen((char *)tempname, READBIN); - if (fd == NULL) { - // Something went wrong, perhaps a file name with a special char. - if (!(flags & EW_SILENT)) { - MSG(_(e_wildexpand)); - msg_start(); // don't overwrite this message - } - xfree(tempname); - goto notfound; - } - int fseek_res = fseek(fd, 0L, SEEK_END); - if (fseek_res < 0) { - xfree(tempname); - fclose(fd); - return FAIL; - } - int64_t templen = ftell(fd); // get size of temp file - if (templen < 0) { - xfree(tempname); - fclose(fd); - return FAIL; - } -#if SIZEOF_LONG_LONG > SIZEOF_SIZE_T - assert(templen <= (long long)SIZE_MAX); -#endif - len = (size_t)templen; - fseek(fd, 0L, SEEK_SET); - buffer = xmalloc(len + 1); - // fread() doesn't terminate buffer with NUL; - // appropriate termination (not always NUL) is done below. - size_t readlen = fread((char *)buffer, 1, len, fd); - fclose(fd); - os_remove((char *)tempname); - if (readlen != len) { - // unexpected read error - EMSG2(_(e_notread), tempname); - xfree(tempname); - xfree(buffer); - return FAIL; - } - xfree(tempname); - - // file names are separated with Space - if (shell_style == STYLE_ECHO) { - buffer[len] = '\n'; // make sure the buffer ends in NL - p = buffer; - for (i = 0; *p != '\n'; i++) { // count number of entries - while (*p != ' ' && *p != '\n') { - p++; - } - p = skipwhite(p); // skip to next entry - } - // file names are separated with NL - } else if (shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB) { - buffer[len] = NUL; // make sure the buffer ends in NUL - p = buffer; - for (i = 0; *p != NUL; i++) { // count number of entries - while (*p != '\n' && *p != NUL) { - p++; - } - if (*p != NUL) { - p++; - } - p = skipwhite(p); // skip leading white space - } - // file names are separated with NUL - } else { - // Some versions of zsh use spaces instead of NULs to separate - // results. Only do this when there is no NUL before the end of the - // buffer, otherwise we would never be able to use file names with - // embedded spaces when zsh does use NULs. - // When we found a NUL once, we know zsh is OK, set did_find_nul and - // don't check for spaces again. - check_spaces = false; - if (shell_style == STYLE_PRINT && !did_find_nul) { - // If there is a NUL, set did_find_nul, else set check_spaces - buffer[len] = NUL; - if (len && (int)STRLEN(buffer) < (int)len) - did_find_nul = true; - else - check_spaces = true; - } - - // Make sure the buffer ends with a NUL. For STYLE_PRINT there - // already is one, for STYLE_GLOB it needs to be added. - if (len && buffer[len - 1] == NUL) { - len--; - } else { - buffer[len] = NUL; - } - i = 0; - for (p = buffer; p < buffer + len; p++) { - if (*p == NUL || (*p == ' ' && check_spaces)) { // count entry - i++; - *p = NUL; - } - } - if (len) { - i++; // count last entry - } - } - assert(buffer[len] == NUL || buffer[len] == '\n'); - - if (i == 0) { - // Can happen when using /bin/sh and typing ":e $NO_SUCH_VAR^I". - // /bin/sh will happily expand it to nothing rather than returning an - // error; and hey, it's good to check anyway -- webb. - xfree(buffer); - goto notfound; - } - *num_file = i; - *file = xmalloc(sizeof(char_u *) * (size_t)i); - - // Isolate the individual file names. - p = buffer; - for (i = 0; i < *num_file; ++i) { - (*file)[i] = p; - // Space or NL separates - if (shell_style == STYLE_ECHO || shell_style == STYLE_BT - || shell_style == STYLE_VIMGLOB) { - while (!(shell_style == STYLE_ECHO && *p == ' ') - && *p != '\n' && *p != NUL) { - p++; - } - if (p == buffer + len) { // last entry - *p = NUL; - } else { - *p++ = NUL; - p = skipwhite(p); // skip to next entry - } - } else { // NUL separates - while (*p && p < buffer + len) { // skip entry - p++; - } - p++; // skip NUL - } - } - - // Move the file names to allocated memory. - for (j = 0, i = 0; i < *num_file; i++) { - // Require the files to exist. Helps when using /bin/sh - if (!(flags & EW_NOTFOUND) && !os_path_exists((*file)[i])) { - continue; - } - - // check if this entry should be included - dir = (os_isdir((*file)[i])); - if ((dir && !(flags & EW_DIR)) || (!dir && !(flags & EW_FILE))) - continue; - - // Skip files that are not executable if we check for that. - if (!dir && (flags & EW_EXEC) - && !os_can_exe((char *)(*file)[i], NULL, !(flags & EW_SHELLCMD))) { - continue; - } - - p = xmalloc(STRLEN((*file)[i]) + 1 + dir); - STRCPY(p, (*file)[i]); - if (dir) { - add_pathsep((char *)p); // add '/' to a directory name - } - (*file)[j++] = p; - } - xfree(buffer); - *num_file = j; - - if (*num_file == 0) { // rejected all entries - XFREE_CLEAR(*file); - goto notfound; - } - - return OK; - -notfound: - if (flags & EW_NOTFOUND) { - save_patterns(num_pat, pat, num_file, file); - return OK; - } - return FAIL; - -} - - -static void save_patterns(int num_pat, char_u **pat, int *num_file, - char_u ***file) -{ - int i; - char_u *s; - - *file = xmalloc((size_t)num_pat * sizeof(char_u *)); - - for (i = 0; i < num_pat; i++) { - s = vim_strsave(pat[i]); - // Be compatible with expand_filename(): halve the number of - // backslashes. - backslash_halve(s); - (*file)[i] = s; - } - *num_file = num_pat; -} - -static bool have_wildcard(int num, char_u **file) -{ - int i; - - for (i = 0; i < num; i++) - if (path_has_wildcard(file[i])) - return true; - return false; -} - -static bool have_dollars(int num, char_u **file) -{ - int i; - - for (i = 0; i < num; i++) - if (vim_strchr(file[i], '$') != NULL) - return true; - return false; -} diff --git a/src/nvim/path.c b/src/nvim/path.c index a53870acb8..31318f6bea 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -1120,10 +1120,22 @@ static bool has_env_var(char_u *p) static bool has_special_wildchar(char_u *p) { for (; *p; MB_PTR_ADV(p)) { - // Allow for escaping - if (*p == '\\' && p[1] != NUL) { + // Disallow line break characters. + if (*p == '\r' || *p == '\n') { + break; + } + // Allow for escaping. + if (*p == '\\' && p[1] != NUL && p[1] != '\r' && p[1] != '\n') { p++; } else if (vim_strchr((char_u *)SPECIAL_WILDCHAR, *p) != NULL) { + // A { must be followed by a matching }. + if (*p == '{' && vim_strchr(p, '}') == NULL) { + continue; + } + // A quote and backtick must be followed by another one. + if ((*p == '`' || *p == '\'') && vim_strchr(p, *p) == NULL) { + continue; + } return true; } } @@ -1166,7 +1178,7 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, */ if (recursive) #ifdef SPECIAL_WILDCHAR - return mch_expand_wildcards(num_pat, pat, num_file, file, flags); + return os_expand_wildcards(num_pat, pat, num_file, file, flags); #else return FAIL; #endif @@ -1181,7 +1193,7 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, for (int i = 0; i < num_pat; i++) { if (has_special_wildchar(pat[i]) && !(vim_backtick(pat[i]) && pat[i][1] == '=')) { - return mch_expand_wildcards(num_pat, pat, num_file, file, flags); + return os_expand_wildcards(num_pat, pat, num_file, file, flags); } } #endif @@ -1221,8 +1233,8 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, else if (has_env_var(p) || *p == '~') { xfree(p); ga_clear_strings(&ga); - i = mch_expand_wildcards(num_pat, pat, num_file, file, - flags | EW_KEEPDOLLAR); + i = os_expand_wildcards(num_pat, pat, num_file, file, + flags | EW_KEEPDOLLAR); recursive = false; return i; } diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 4ba2a1032d..e06433892d 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -38,7 +38,8 @@ static int pum_width; // width of displayed pum items static int pum_base_width; // width of pum items base static int pum_kind_width; // width of pum items kind column static int pum_extra_width; // width of extra stuff -static int pum_scrollbar; // TRUE when scrollbar present +static int pum_scrollbar; // one when scrollbar present, else zero +static bool pum_rl; // true when popupmenu is drawn 'rightleft' static int pum_anchor_grid; // grid where position is defined static int pum_row; // top row of pum @@ -62,9 +63,12 @@ static void pum_compute_size(void) pum_kind_width = 0; pum_extra_width = 0; for (int i = 0; i < pum_size; i++) { - int w = vim_strsize(pum_array[i].pum_text); - if (pum_base_width < w) { - pum_base_width = w; + int w; + if (pum_array[i].pum_text != NULL) { + w = vim_strsize(pum_array[i].pum_text); + if (pum_base_width < w) { + pum_base_width = w; + } } if (pum_array[i].pum_kind != NULL) { w = vim_strsize(pum_array[i].pum_kind) + 1; @@ -110,6 +114,8 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, || (State == CMDLINE && ui_has(kUIWildmenu)); } + pum_rl = (curwin->w_p_rl && State != CMDLINE); + do { // Mark the pum as visible already here, // to avoid that must_redraw is set when 'cursorcolumn' is on. @@ -127,7 +133,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, } else { // anchor position: the start of the completed word pum_win_row = curwin->w_wrow; - if (curwin->w_p_rl) { + if (pum_rl) { cursor_col = curwin->w_width - curwin->w_wcol - 1; } else { cursor_col = curwin->w_wcol; @@ -270,16 +276,14 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, def_width = max_width; } - if ((((cursor_col < Columns - p_pw) - || (cursor_col < Columns - max_width)) - && !curwin->w_p_rl) - || (curwin->w_p_rl - && ((cursor_col > p_pw) || (cursor_col > max_width)))) { + if ((((cursor_col < Columns - p_pw) || (cursor_col < Columns - max_width)) + && !pum_rl) + || (pum_rl && ((cursor_col > p_pw) || (cursor_col > max_width)))) { // align pum with "cursor_col" pum_col = cursor_col; // start with the maximum space available - if (curwin->w_p_rl) { + if (pum_rl) { pum_width = pum_col - pum_scrollbar + 1; } else { assert(Columns - pum_col - pum_scrollbar >= INT_MIN @@ -297,19 +301,16 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, pum_width = (int)p_pw; } } - } else if (((cursor_col > p_pw || cursor_col > max_width) - && !curwin->w_p_rl) - || (curwin->w_p_rl - && (cursor_col < Columns - p_pw - || cursor_col < Columns - max_width))) { + } else if (((cursor_col > p_pw || cursor_col > max_width) && !pum_rl) + || (pum_rl && (cursor_col < Columns - p_pw + || cursor_col < Columns - max_width))) { // align pum edge with "cursor_col" - if (curwin->w_p_rl - && W_ENDCOL(curwin) < max_width + pum_scrollbar + 1) { + if (pum_rl && W_ENDCOL(curwin) < max_width + pum_scrollbar + 1) { pum_col = cursor_col + max_width + pum_scrollbar + 1; if (pum_col >= Columns) { pum_col = Columns - 1; } - } else if (!curwin->w_p_rl) { + } else if (!pum_rl) { if (curwin->w_wincol > Columns - max_width - pum_scrollbar && max_width <= p_pw) { // use full width to end of the screen @@ -320,7 +321,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, } } - if (curwin->w_p_rl) { + if (pum_rl) { pum_width = pum_col - pum_scrollbar + 1; } else { pum_width = Columns - pum_col - pum_scrollbar; @@ -328,7 +329,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, if (pum_width < p_pw) { pum_width = (int)p_pw; - if (curwin->w_p_rl) { + if (pum_rl) { if (pum_width > pum_col) { pum_width = pum_col; } @@ -346,7 +347,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, } } else if (Columns < def_width) { // not enough room, will use what we have - if (curwin->w_p_rl) { + if (pum_rl) { assert(Columns - 1 >= INT_MIN); pum_col = (int)(Columns - 1); } else { @@ -360,7 +361,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, max_width = (int)p_pw; } - if (curwin->w_p_rl) { + if (pum_rl) { pum_col = max_width - 1; } else { assert(Columns - max_width >= INT_MIN @@ -399,7 +400,7 @@ void pum_redraw(void) int grid_width = pum_width; int col_off = 0; bool extra_space = false; - if (curwin->w_p_rl) { + if (pum_rl) { col_off = pum_width; if (pum_col < curwin->w_wincol + curwin->w_width - 1) { grid_width += 1; @@ -460,7 +461,7 @@ void pum_redraw(void) // prepend a space if there is room if (extra_space) { - if (curwin->w_p_rl) { + if (pum_rl) { grid_putchar(&pum_grid, ' ', row, col_off + 1, attr); } else { grid_putchar(&pum_grid, ' ', row, col_off - 1, attr); @@ -507,7 +508,7 @@ void pum_redraw(void) st = (char_u *)transstr((const char *)s); *p = saved; - if (curwin->w_p_rl) { + if (pum_rl) { char_u *rt = reverse_text(st); char_u *rt_start = rt; int size = vim_strsize(rt); @@ -542,7 +543,7 @@ void pum_redraw(void) } // Display two spaces for a Tab. - if (curwin->w_p_rl) { + if (pum_rl) { grid_puts_len(&pum_grid, (char_u *)" ", 2, row, col - 1, attr); col -= 2; @@ -577,7 +578,7 @@ void pum_redraw(void) break; } - if (curwin->w_p_rl) { + if (pum_rl) { grid_fill(&pum_grid, row, row + 1, col_off - pum_base_width - n + 1, col + 1, ' ', ' ', attr); col = col_off - pum_base_width - n + 1; @@ -589,7 +590,7 @@ void pum_redraw(void) totwidth = pum_base_width + n; } - if (curwin->w_p_rl) { + if (pum_rl) { grid_fill(&pum_grid, row, row + 1, col_off - pum_width + 1, col + 1, ' ', ' ', attr); } else { @@ -598,7 +599,7 @@ void pum_redraw(void) } if (pum_scrollbar > 0) { - if (curwin->w_p_rl) { + if (pum_rl) { grid_putchar(&pum_grid, ' ', row, col_off - pum_width, i >= thumb_pos && i < thumb_pos + thumb_heigth ? attr_thumb : attr_scroll); @@ -910,10 +911,17 @@ void pum_set_event_info(dict_T *dict) if (!pum_visible()) { return; } - tv_dict_add_nr(dict, S_LEN("height"), pum_height); - tv_dict_add_nr(dict, S_LEN("width"), pum_width); - tv_dict_add_nr(dict, S_LEN("row"), pum_row); - tv_dict_add_nr(dict, S_LEN("col"), pum_col); + double w, h, r, c; + if (!ui_pum_get_pos(&w, &h, &r, &c)) { + w = (double)pum_width; + h = (double)pum_height; + r = (double)pum_row; + c = (double)pum_col; + } + tv_dict_add_float(dict, S_LEN("height"), h); + tv_dict_add_float(dict, S_LEN("width"), w); + tv_dict_add_float(dict, S_LEN("row"), r); + tv_dict_add_float(dict, S_LEN("col"), c); tv_dict_add_nr(dict, S_LEN("size"), pum_size); tv_dict_add_special(dict, S_LEN("scrollbar"), pum_scrollbar ? kSpecialVarTrue : kSpecialVarFalse); diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 71c6f06ac0..484168e798 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -3845,7 +3845,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last) *dirname = NUL; // Add one line for each error - if (old_last == NULL) { + if (old_last == NULL || old_last->qf_next == NULL) { qfp = qfl->qf_start; lnum = 0; } else { @@ -4775,10 +4775,10 @@ static void vgr_display_fname(char_u *fname) static buf_T *vgr_load_dummy_buf(char_u *fname, char_u *dirname_start, char_u *dirname_now) { - char_u *save_ei = NULL; - // Don't do Filetype autocommands to avoid loading syntax and // indent scripts, a great speed improvement. + char_u *save_ei = au_event_disable(",Filetype"); + long save_mls = p_mls; p_mls = 0; @@ -5134,6 +5134,7 @@ theend: // Restore current working directory to "dirname_start" if they differ, taking // into account whether it is set locally or globally. static void restore_start_dir(char_u *dirname_start) + FUNC_ATTR_NONNULL_ALL { char_u *dirname_now = xmalloc(MAXPATHL); @@ -5251,8 +5252,29 @@ load_dummy_buffer ( // directory to "dirname_start" prior to returning, if autocmds or the // 'autochdir' option have changed it. static void wipe_dummy_buffer(buf_T *buf, char_u *dirname_start) + FUNC_ATTR_NONNULL_ALL { - if (curbuf != buf) { // safety check + // If any autocommand opened a window on the dummy buffer, close that + // window. If we can't close them all then give up. + while (buf->b_nwindows > 0) { + bool did_one = false; + + if (firstwin->w_next != NULL) { + for (win_T *wp = firstwin; wp != NULL; wp = wp->w_next) { + if (wp->w_buffer == buf) { + if (win_close(wp, false) == OK) { + did_one = true; + } + break; + } + } + } + if (!did_one) { + return; + } + } + + if (curbuf != buf && buf->b_nwindows == 0) { // safety check cleanup_T cs; // Reset the error/interrupt/exception state here so that aborting() diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 90dc8ab90f..34553fcec4 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -56,6 +56,7 @@ #include "nvim/regexp.h" #include "nvim/charset.h" #include "nvim/eval.h" +#include "nvim/eval/userfunc.h" #include "nvim/ex_cmds2.h" #include "nvim/mark.h" #include "nvim/memline.h" @@ -6491,20 +6492,24 @@ typedef struct { static regsubmatch_T rsm; // can only be used when can_f_submatch is true -/// Put the submatches in "argv[0]" which is a list passed into call_func() by -/// vim_regsub_both(). -static int fill_submatch_list(int argc, typval_T *argv, int argcount) +/// Put the submatches in "argv[argskip]" which is a list passed into +/// call_func() by vim_regsub_both(). +static int fill_submatch_list(int argc FUNC_ATTR_UNUSED, typval_T *argv, + int argskip, int argcount) + FUNC_ATTR_NONNULL_ALL { - if (argcount == 0) { - // called function doesn't take an argument - return 0; + typval_T *listarg = argv + argskip; + + if (argcount == argskip) { + // called function doesn't take a submatches argument + return argskip; } // Relies on sl_list to be the first item in staticList10_T. - tv_list_init_static10((staticList10_T *)argv->vval.v_list); + tv_list_init_static10((staticList10_T *)listarg->vval.v_list); // There are always 10 list items in staticList10_T. - listitem_T *li = tv_list_first(argv->vval.v_list); + listitem_T *li = tv_list_first(listarg->vval.v_list); for (int i = 0; i < 10; i++) { char_u *s = rsm.sm_match->startp[i]; if (s == NULL || rsm.sm_match->endp[i] == NULL) { @@ -6516,7 +6521,7 @@ static int fill_submatch_list(int argc, typval_T *argv, int argcount) TV_LIST_ITEM_TV(li)->vval.v_string = s; li = TV_LIST_ITEM_NEXT(argv->vval.v_list, li); } - return 1; + return argskip + 1; } static void clear_submatch_list(staticList10_T *sl) @@ -6679,10 +6684,15 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, // fill_submatch_list() was called. clear_submatch_list(&matchList); } - char buf[NUMBUFLEN]; - eval_result = (char_u *)tv_get_string_buf_chk(&rettv, buf); - if (eval_result != NULL) { - eval_result = vim_strsave(eval_result); + if (rettv.v_type == VAR_UNKNOWN) { + // something failed, no need to report another error + eval_result = NULL; + } else { + char buf[NUMBUFLEN]; + eval_result = (char_u *)tv_get_string_buf_chk(&rettv, buf); + if (eval_result != NULL) { + eval_result = vim_strsave(eval_result); + } } tv_clear(&rettv); } else { diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h index 5e5b19b63f..116bfee91e 100644 --- a/src/nvim/regexp_defs.h +++ b/src/nvim/regexp_defs.h @@ -91,7 +91,7 @@ typedef struct { char_u *regmust; int regmlen; char_u reghasz; - char_u program[1]; /* actually longer.. */ + char_u program[1]; // actually longer.. } bt_regprog_T; // Structure representing a NFA state. @@ -102,7 +102,7 @@ struct nfa_state { nfa_state_T *out; nfa_state_T *out1; int id; - int lastlist[2]; /* 0: normal, 1: recursive */ + int lastlist[2]; // 0: normal, 1: recursive int val; }; @@ -116,19 +116,19 @@ typedef struct { unsigned re_engine; unsigned re_flags; ///< Second argument for vim_regcomp(). - nfa_state_T *start; /* points into state[] */ + nfa_state_T *start; // points into state[] - int reganch; /* pattern starts with ^ */ - int regstart; /* char at start of pattern */ - char_u *match_text; /* plain text to match with */ + int reganch; // pattern starts with ^ + int regstart; // char at start of pattern + char_u *match_text; // plain text to match with - int has_zend; /* pattern contains \ze */ - int has_backref; /* pattern contains \1 .. \9 */ + int has_zend; // pattern contains \ze + int has_backref; // pattern contains \1 .. \9 int reghasz; char_u *pattern; - int nsubexp; /* number of () */ + int nsubexp; // number of () int nstate; - nfa_state_T state[1]; /* actually longer.. */ + nfa_state_T state[1]; // actually longer.. } nfa_regprog_T; /* diff --git a/src/nvim/screen.c b/src/nvim/screen.c index ae38f657cd..9e958663aa 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -334,10 +334,10 @@ int update_screen(int type) } return FAIL; } + updating_screen = 1; - updating_screen = TRUE; - ++display_tick; /* let syntax code know we're in a next round of - * display updating */ + display_tick++; // let syntax code know we're in a next round of + // display updating // Tricky: vim code can reset msg_scrolled behind our back, so need // separate bookkeeping for now. @@ -370,6 +370,17 @@ int update_screen(int type) grid_clear_line(&default_grid, default_grid.line_offset[i], Columns, false); } + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_floating) { + continue; + } + if (W_ENDROW(wp) > valid) { + wp->w_redr_type = MAX(wp->w_redr_type, NOT_VALID); + } + if (W_ENDROW(wp) + wp->w_status_height > valid) { + wp->w_redr_status = true; + } + } } msg_grid_set_pos(Rows-p_ch, false); msg_grid_invalid = false; @@ -406,7 +417,7 @@ int update_screen(int type) need_wait_return = false; } - win_ui_flush_positions(); + win_ui_flush(); msg_ext_check_clear(); /* reset cmdline_row now (may have been changed temporarily) */ @@ -554,7 +565,7 @@ int update_screen(int type) wp->w_buffer->b_mod_set = false; } - updating_screen = FALSE; + updating_screen = 0; /* Clear or redraw the command line. Done last, because scrolling may * mess up the command line. */ @@ -623,9 +634,17 @@ bool win_cursorline_standout(const win_T *wp) || (wp->w_p_cole > 0 && (VIsual_active || !conceal_cursor_line(wp))); } -static DecorationState decorations; +static DecorationRedrawState decorations; bool decorations_active = false; +void decorations_add_luahl_attr(int attr_id, + int start_row, int start_col, + int end_row, int end_col) +{ + kv_push(decorations.active, + ((HlRange){ start_row, start_col, end_row, end_col, attr_id, NULL })); +} + /* * Update a single window. * @@ -1217,6 +1236,8 @@ static void win_update(win_T *wp) srow = 0; lnum = wp->w_topline; // first line shown in window + decorations_active = decorations_redraw_reset(buf, &decorations); + if (buf->b_luahl && buf->b_luahl_window != LUA_NOREF) { Error err = ERROR_INIT; FIXED_TEMP_ARRAY(args, 4); @@ -1237,7 +1258,6 @@ static void win_update(win_T *wp) } } - decorations_active = extmark_decorations_reset(buf, &decorations); for (;; ) { /* stop updating when reached the end of the window (check for _past_ @@ -1609,6 +1629,7 @@ static void win_update(win_T *wp) * changes are relevant). */ wp->w_valid |= VALID_BOTLINE; + wp->w_viewport_invalid = true; if (wp == curwin && wp->w_botline != old_botline && !recursive) { recursive = TRUE; curwin->w_valid &= ~VALID_TOPLINE; @@ -1628,7 +1649,7 @@ static void win_update(win_T *wp) /* restore got_int, unless CTRL-C was hit while redrawing */ if (!got_int) got_int = save_got_int; -} +} // NOLINT(readability/fn_size) /// Returns width of the signcolumn that should be used for the whole window /// @@ -1720,11 +1741,36 @@ static int advance_color_col(int vcol, int **color_cols) return **color_cols >= 0; } +// Returns the next grid column. +static int text_to_screenline(win_T *wp, char_u *text, int col, int off) + FUNC_ATTR_NONNULL_ALL +{ + int idx = wp->w_p_rl ? off : off + col; + LineState s = LINE_STATE(text); + + while (*s.p != NUL) { + // TODO(bfredl): cargo-culted from the old Vim code: + // if(col + cells > wp->w_width - (wp->w_p_rl ? col : 0)) { break; } + // This is obvious wrong. If Vim ever fixes this, solve for "cells" again + // in the correct condition. + const int maxcells = wp->w_grid.Columns - col - (wp->w_p_rl ? col : 0); + const int cells = line_putchar(&s, &linebuf_char[idx], maxcells, + wp->w_p_rl); + if (cells == -1) { + break; + } + col += cells; + idx += cells; + } + + return col; +} + // Compute the width of the foldcolumn. Based on 'foldcolumn' and how much // space is available for window "wp", minus "col". static int compute_foldcolumn(win_T *wp, int col) { - int fdc = wp->w_p_fdc; + int fdc = win_fdccol_count(wp); int wmw = wp == curwin && p_wmw == 0 ? 1 : p_wmw; int wwidth = wp->w_grid.Columns; @@ -1921,29 +1967,7 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T // 5. move the text to linebuf_char[off]. Fill up with "fold". // Right-left text is put in columns 0 - number-col, normal text is put // in columns number-col - window-width. - int idx; - - if (wp->w_p_rl) { - idx = off; - } else { - idx = off + col; - } - - LineState s = LINE_STATE(text); - - while (*s.p != NUL) { - // TODO(bfredl): cargo-culted from the old Vim code: - // if(col + cells > wp->w_width - (wp->w_p_rl ? col : 0)) { break; } - // This is obvious wrong. If Vim ever fixes this, solve for "cells" again - // in the correct condition. - int maxcells = wp->w_grid.Columns - col - (wp->w_p_rl ? col : 0); - int cells = line_putchar(&s, &linebuf_char[idx], maxcells, wp->w_p_rl); - if (cells == -1) { - break; - } - col += cells; - idx += cells; - } + col = text_to_screenline(wp, text, col, off); /* Fill the rest of the line with the fold filler */ if (wp->w_p_rl) @@ -2195,10 +2219,10 @@ win_line ( int n_skip = 0; /* nr of chars to skip for 'nowrap' */ - int fromcol = 0, tocol = 0; // start/end of inverting + int fromcol = -10; // start of inverting + int tocol = MAXCOL; // end of inverting int fromcol_prev = -2; // start of inverting after cursor - int noinvcur = false; // don't invert the cursor - pos_T *top, *bot; + bool noinvcur = false; // don't invert the cursor int lnum_in_visual_area = false; pos_T pos; long v; @@ -2300,6 +2324,8 @@ win_line ( char *luatext = NULL; + buf_T *buf = wp->w_buffer; + if (!number_only) { // To speed up the loop below, set extra_check when there is linebreak, // trailing white space and/or syntax processing to be done. @@ -2322,8 +2348,31 @@ win_line ( } if (decorations_active) { - has_decorations = extmark_decorations_line(wp->w_buffer, lnum-1, - &decorations); + if (buf->b_luahl && buf->b_luahl_line != LUA_NOREF) { + Error err = ERROR_INIT; + FIXED_TEMP_ARRAY(args, 3); + args.items[0] = WINDOW_OBJ(wp->handle); + args.items[1] = BUFFER_OBJ(buf->handle); + args.items[2] = INTEGER_OBJ(lnum-1); + lua_attr_active = true; + extra_check = true; + Object o = executor_exec_lua_cb(buf->b_luahl_line, "line", + args, true, &err); + lua_attr_active = false; + if (o.type == kObjectTypeString) { + // TODO(bfredl): this is a bit of a hack. A final API should use an + // "unified" interface where luahl can add both bufhl and virttext + luatext = o.data.string.data; + do_virttext = true; + } else if (ERROR_SET(&err)) { + ELOG("error in luahl line: %s", err.msg); + luatext = err.msg; + do_virttext = true; + } + } + + has_decorations = decorations_redraw_line(wp->w_buffer, lnum-1, + &decorations); if (has_decorations) { extra_check = true; } @@ -2371,27 +2420,28 @@ win_line ( capcol_lnum = 0; } - // - // handle visual active in this window - // - fromcol = -10; - tocol = MAXCOL; + // handle Visual active in this window if (VIsual_active && wp->w_buffer == curwin->w_buffer) { - // Visual is after curwin->w_cursor + pos_T *top, *bot; + if (ltoreq(curwin->w_cursor, VIsual)) { + // Visual is after curwin->w_cursor top = &curwin->w_cursor; bot = &VIsual; - } else { // Visual is before curwin->w_cursor + } else { + // Visual is before curwin->w_cursor top = &VIsual; bot = &curwin->w_cursor; } lnum_in_visual_area = (lnum >= top->lnum && lnum <= bot->lnum); - if (VIsual_mode == Ctrl_V) { // block mode + if (VIsual_mode == Ctrl_V) { + // block mode if (lnum_in_visual_area) { fromcol = wp->w_old_cursor_fcol; tocol = wp->w_old_cursor_lcol; } - } else { // non-block mode + } else { + // non-block mode if (lnum > top->lnum && lnum <= bot->lnum) { fromcol = 0; } else if (lnum == top->lnum) { @@ -2521,41 +2571,6 @@ win_line ( line = ml_get_buf(wp->w_buffer, lnum, FALSE); ptr = line; - buf_T *buf = wp->w_buffer; - if (buf->b_luahl && buf->b_luahl_line != LUA_NOREF) { - size_t size = STRLEN(line); - if (lua_attr_bufsize < size) { - xfree(lua_attr_buf); - lua_attr_buf = xcalloc(size, sizeof(*lua_attr_buf)); - lua_attr_bufsize = size; - } else if (lua_attr_buf) { - memset(lua_attr_buf, 0, size * sizeof(*lua_attr_buf)); - } - Error err = ERROR_INIT; - // TODO(bfredl): build a macro for the "static array" pattern - // in buf_updates_send_changes? - FIXED_TEMP_ARRAY(args, 3); - args.items[0] = WINDOW_OBJ(wp->handle); - args.items[1] = BUFFER_OBJ(buf->handle); - args.items[2] = INTEGER_OBJ(lnum-1); - lua_attr_active = true; - extra_check = true; - Object o = executor_exec_lua_cb(buf->b_luahl_line, "line", - args, true, &err); - lua_attr_active = false; - if (o.type == kObjectTypeString) { - // TODO(bfredl): this is a bit of a hack. A final API should use an - // "unified" interface where luahl can add both bufhl and virttext - luatext = o.data.string.data; - do_virttext = true; - } else if (ERROR_SET(&err)) { - ELOG("error in luahl line: %s", err.msg); - luatext = err.msg; - do_virttext = true; - api_clear_error(&err); - } - } - if (has_spell && !number_only) { // For checking first word with a capital skip white space. if (cap_col == 0) { @@ -2951,11 +2966,11 @@ win_line ( } } - if (wp->w_p_brisbr && draw_state == WL_BRI - 1 + if (wp->w_briopt_sbr && draw_state == WL_BRI - 1 && n_extra == 0 && *p_sbr != NUL) { // draw indent after showbreak value draw_state = WL_BRI; - } else if (wp->w_p_brisbr && draw_state == WL_SBR && n_extra == 0) { + } else if (wp->w_briopt_sbr && draw_state == WL_SBR && n_extra == 0) { // after the showbreak, draw the breakindent draw_state = WL_BRI - 1; } @@ -2979,7 +2994,7 @@ win_line ( c_final = NUL; n_extra = get_breakindent_win(wp, ml_get_buf(wp->w_buffer, lnum, false)); - if (wp->w_skipcol > 0 && wp->w_p_wrap) { + if (wp->w_skipcol > 0 && wp->w_p_wrap && wp->w_briopt_sbr) { need_showbreak = false; } // Correct end of highlighted area for 'breakindent', @@ -3149,8 +3164,8 @@ win_line ( shl->endcol += (*mb_ptr2len)(line + shl->endcol); } - /* Loop to check if the match starts at the - * current position */ + // Loop to check if the match starts at the + // current position continue; } } @@ -3527,8 +3542,8 @@ win_line ( } if (has_decorations && v > 0) { - int extmark_attr = extmark_decorations_col(wp->w_buffer, (colnr_T)v-1, - &decorations); + int extmark_attr = decorations_redraw_col(wp->w_buffer, (colnr_T)v-1, + &decorations); if (extmark_attr != 0) { if (!attr_pri) { char_attr = hl_combine_attr(char_attr, extmark_attr); @@ -3538,15 +3553,6 @@ win_line ( } } - // TODO(bfredl): luahl should reuse the "active decorations" buffer - if (buf->b_luahl && v > 0 && v < (long)lua_attr_bufsize+1) { - if (!attr_pri) { - char_attr = hl_combine_attr(char_attr, lua_attr_buf[v-1]); - } else { - char_attr = hl_combine_attr(lua_attr_buf[v-1], char_attr); - } - } - if (wp->w_buffer->terminal) { char_attr = hl_combine_attr(term_attrs[vcol], char_attr); } @@ -3646,8 +3652,9 @@ win_line ( tab_len += n_extra - tab_len; } - /* if n_extra > 0, it gives the number of chars to use for - * a tab, else we need to calculate the width for a tab */ + // if n_extra > 0, it gives the number of chars + // to use for a tab, else we need to calculate the width + // for a tab int len = (tab_len * mb_char2len(wp->w_p_lcs_chars.tab2)); if (n_extra > 0) { len += n_extra - tab_len; @@ -3659,10 +3666,16 @@ win_line ( xfree(p_extra_free); p_extra_free = p; for (i = 0; i < tab_len; i++) { - utf_char2bytes(wp->w_p_lcs_chars.tab2, p); - p += mb_char2len(wp->w_p_lcs_chars.tab2); - n_extra += mb_char2len(wp->w_p_lcs_chars.tab2) - - (saved_nextra > 0 ? 1: 0); + int lcs = wp->w_p_lcs_chars.tab2; + + // if tab3 is given, need to change the char + // for tab + if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) { + lcs = wp->w_p_lcs_chars.tab3; + } + utf_char2bytes(lcs, p); + p += mb_char2len(lcs); + n_extra += mb_char2len(lcs) - (saved_nextra > 0 ? 1 : 0); } p_extra = p_extra_free; @@ -4031,8 +4044,7 @@ win_line ( kv_push(virt_text, ((VirtTextChunk){ .text = luatext, .hl_id = 0 })); do_virttext = true; } else if (has_decorations) { - VirtText *vp = extmark_decorations_virt_text(wp->w_buffer, - &decorations); + VirtText *vp = decorations_redraw_virt_text(wp->w_buffer, &decorations); if (vp) { virt_text = *vp; do_virttext = true; @@ -5702,6 +5714,7 @@ void grid_puts_line_flush(bool set_cursor) static void start_search_hl(void) { if (p_hls && !no_hlsearch) { + end_search_hl(); // just in case it wasn't called before last_pat_prog(&search_hl.rm); // Set the time limit to 'redrawtime'. search_hl.tm = profile_setlimit(p_rdt); @@ -5724,12 +5737,11 @@ static void end_search_hl(void) * Init for calling prepare_search_hl(). */ static void init_search_hl(win_T *wp) + FUNC_ATTR_NONNULL_ALL { - matchitem_T *cur; - - /* Setup for match and 'hlsearch' highlighting. Disable any previous - * match */ - cur = wp->w_match_head; + // Setup for match and 'hlsearch' highlighting. Disable any previous + // match + matchitem_T *cur = wp->w_match_head; while (cur != NULL) { cur->hl.rm = cur->match; if (cur->hlg_id == 0) @@ -5739,7 +5751,7 @@ static void init_search_hl(win_T *wp) cur->hl.buf = wp->w_buffer; cur->hl.lnum = 0; cur->hl.first_lnum = 0; - /* Set the time limit to 'redrawtime'. */ + // Set the time limit to 'redrawtime'. cur->hl.tm = profile_setlimit(p_rdt); cur = cur->next; } @@ -5755,18 +5767,16 @@ static void init_search_hl(win_T *wp) * Advance to the match in window "wp" line "lnum" or past it. */ static void prepare_search_hl(win_T *wp, linenr_T lnum) + FUNC_ATTR_NONNULL_ALL { - matchitem_T *cur; /* points to the match list */ - match_T *shl; /* points to search_hl or a match */ - int shl_flag; /* flag to indicate whether search_hl - has been processed or not */ - int n; - - /* - * When using a multi-line pattern, start searching at the top - * of the window or just after a closed fold. - * Do this both for search_hl and the match list. - */ + matchitem_T *cur; // points to the match list + match_T *shl; // points to search_hl or a match + bool shl_flag; // flag to indicate whether search_hl + // has been processed or not + + // When using a multi-line pattern, start searching at the top + // of the window or just after a closed fold. + // Do this both for search_hl and the match list. cur = wp->w_match_head; shl_flag = false; while (cur != NULL || shl_flag == false) { @@ -5793,7 +5803,7 @@ static void prepare_search_hl(win_T *wp, linenr_T lnum) } bool pos_inprogress = true; // mark that a position match search is // in progress - n = 0; + int n = 0; while (shl->first_lnum < lnum && (shl->rm.regprog != NULL || (cur != NULL && pos_inprogress))) { next_search_hl(wp, shl, shl->first_lnum, (colnr_T)n, @@ -5831,6 +5841,7 @@ next_search_hl ( colnr_T mincol, /* minimal column for a match */ matchitem_T *cur /* to retrieve match positions if any */ ) + FUNC_ATTR_NONNULL_ARG(2) { linenr_T l; colnr_T matchcol; @@ -5838,11 +5849,10 @@ next_search_hl ( int save_called_emsg = called_emsg; if (shl->lnum != 0) { - /* Check for three situations: - * 1. If the "lnum" is below a previous match, start a new search. - * 2. If the previous match includes "mincol", use it. - * 3. Continue after the previous match. - */ + // Check for three situations: + // 1. If the "lnum" is below a previous match, start a new search. + // 2. If the previous match includes "mincol", use it. + // 3. Continue after the previous match. l = shl->lnum + shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum; if (lnum > l) shl->lnum = 0; @@ -5856,22 +5866,21 @@ next_search_hl ( */ called_emsg = FALSE; for (;; ) { - /* Stop searching after passing the time limit. */ + // Stop searching after passing the time limit. if (profile_passed_limit(shl->tm)) { shl->lnum = 0; /* no match found in time */ break; } - /* Three situations: - * 1. No useful previous match: search from start of line. - * 2. Not Vi compatible or empty match: continue at next character. - * Break the loop if this is beyond the end of the line. - * 3. Vi compatible searching: continue at end of previous match. - */ - if (shl->lnum == 0) + // Three situations: + // 1. No useful previous match: search from start of line. + // 2. Not Vi compatible or empty match: continue at next character. + // Break the loop if this is beyond the end of the line. + // 3. Vi compatible searching: continue at end of previous match. + if (shl->lnum == 0) { matchcol = 0; - else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL - || (shl->rm.endpos[0].lnum == 0 - && shl->rm.endpos[0].col <= shl->rm.startpos[0].col)) { + } else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL + || (shl->rm.endpos[0].lnum == 0 + && shl->rm.endpos[0].col <= shl->rm.startpos[0].col)) { char_u *ml; matchcol = shl->rm.startpos[0].col; @@ -5888,8 +5897,8 @@ next_search_hl ( shl->lnum = lnum; if (shl->rm.regprog != NULL) { - /* Remember whether shl->rm is using a copy of the regprog in - * cur->match. */ + // Remember whether shl->rm is using a copy of the regprog in + // cur->match. bool regprog_is_copy = (shl != &search_hl && cur != NULL && shl == &cur->hl @@ -5918,7 +5927,7 @@ next_search_hl ( nmatched = next_search_hl_pos(shl, lnum, &(cur->pos), matchcol); } if (nmatched == 0) { - shl->lnum = 0; /* no match found */ + shl->lnum = 0; // no match found break; } if (shl->rm.startpos[0].lnum > 0 @@ -5926,7 +5935,7 @@ next_search_hl ( || nmatched > 1 || shl->rm.endpos[0].col > mincol) { shl->lnum += shl->rm.startpos[0].lnum; - break; /* useful match found */ + break; // useful match found } // Restore called_emsg for assert_fails(). @@ -5943,6 +5952,7 @@ next_search_hl_pos( posmatch_T *posmatch, // match positions colnr_T mincol // minimal column for a match ) + FUNC_ATTR_NONNULL_ALL { int i; int found = -1; @@ -6097,9 +6107,10 @@ void check_for_delay(int check_msg_scroll) && emsg_silent == 0) { ui_flush(); os_delay(1000L, true); - emsg_on_display = FALSE; - if (check_msg_scroll) - msg_scroll = FALSE; + emsg_on_display = false; + if (check_msg_scroll) { + msg_scroll = false; + } } } diff --git a/src/nvim/search.c b/src/nvim/search.c index 3ee9777805..23086c629b 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -1192,6 +1192,7 @@ int do_search( len = STRLEN(p) + off_len + 3; } + xfree(msgbuf); msgbuf = xmalloc(len); { memset(msgbuf, ' ', len); @@ -2227,6 +2228,8 @@ showmatch( pos_T *lpos, save_cursor; pos_T mpos; colnr_T vcol; + long *so = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so; + long *siso = curwin->w_p_siso >= 0 ? &curwin->w_p_siso : &p_siso; long save_so; long save_siso; int save_state; @@ -2262,23 +2265,24 @@ showmatch( && vcol < curwin->w_leftcol + curwin->w_width_inner)) { mpos = *lpos; // save the pos, update_screen() may change it save_cursor = curwin->w_cursor; - save_so = p_so; - save_siso = p_siso; - /* Handle "$" in 'cpo': If the ')' is typed on top of the "$", - * stop displaying the "$". */ - if (dollar_vcol >= 0 && dollar_vcol == curwin->w_virtcol) + save_so = *so; + save_siso = *siso; + // Handle "$" in 'cpo': If the ')' is typed on top of the "$", + // stop displaying the "$". + if (dollar_vcol >= 0 && dollar_vcol == curwin->w_virtcol) { dollar_vcol = -1; - ++curwin->w_virtcol; /* do display ')' just before "$" */ - update_screen(VALID); /* show the new char first */ + } + curwin->w_virtcol++; // do display ')' just before "$" + update_screen(VALID); // show the new char first save_dollar_vcol = dollar_vcol; save_state = State; State = SHOWMATCH; - ui_cursor_shape(); /* may show different cursor shape */ - curwin->w_cursor = mpos; /* move to matching char */ - p_so = 0; /* don't use 'scrolloff' here */ - p_siso = 0; /* don't use 'sidescrolloff' here */ - showruler(FALSE); + ui_cursor_shape(); // may show different cursor shape + curwin->w_cursor = mpos; // move to matching char + *so = 0; // don't use 'scrolloff' here + *siso = 0; // don't use 'sidescrolloff' here + showruler(false); setcursor(); ui_flush(); /* Restore dollar_vcol(), because setcursor() may call curs_rows() @@ -2294,11 +2298,11 @@ showmatch( os_delay(p_mat * 100L, true); else if (!char_avail()) os_delay(p_mat * 100L, false); - curwin->w_cursor = save_cursor; /* restore cursor position */ - p_so = save_so; - p_siso = save_siso; + curwin->w_cursor = save_cursor; // restore cursor position + *so = save_so; + *siso = save_siso; State = save_state; - ui_cursor_shape(); /* may show different cursor shape */ + ui_cursor_shape(); // may show different cursor shape } } } diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 2306da94c6..19a14f340b 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -177,7 +177,7 @@ typedef enum { /// Possible results when reading ShaDa file typedef enum { - kSDReadStatusSuccess, ///< Reading was successfull. + kSDReadStatusSuccess, ///< Reading was successful. kSDReadStatusFinished, ///< Nothing more to read. kSDReadStatusReadError, ///< Failed to read from file. kSDReadStatusNotShaDa, ///< Input is most likely not a ShaDa file. @@ -186,11 +186,11 @@ typedef enum { /// Possible results of shada_write function. typedef enum { - kSDWriteSuccessfull, ///< Writing was successfull. - kSDWriteReadNotShada, ///< Writing was successfull, but when reading it + kSDWriteSuccessfull, ///< Writing was successful. + kSDWriteReadNotShada, ///< Writing was successful, but when reading it ///< attempted to read file that did not look like ///< a ShaDa file. - kSDWriteFailed, ///< Writing was not successfull (e.g. because there + kSDWriteFailed, ///< Writing was not successful (e.g. because there ///< was no space left on device). kSDWriteIgnError, ///< Writing resulted in a error which can be ignored ///< (e.g. when trying to dump a function reference or @@ -3005,7 +3005,7 @@ shada_write_exit: /// location is used. /// @param[in] nomerge If true then old file is ignored. /// -/// @return OK if writing was successfull, FAIL otherwise. +/// @return OK if writing was successful, FAIL otherwise. int shada_write_file(const char *const file, bool nomerge) { if (shada_disabled()) { @@ -3341,7 +3341,7 @@ static ShaDaReadResult fread_len(ShaDaReadDef *const sd_reader, /// @param[in] sd_reader Structure containing file reader definition. /// @param[out] result Location where result is saved. /// -/// @return kSDReadStatusSuccess if reading was successfull, +/// @return kSDReadStatusSuccess if reading was successful, /// kSDReadStatusNotShaDa if there were not enough bytes to read or /// kSDReadStatusReadError if reading failed for whatever reason. static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader, diff --git a/src/nvim/sign.c b/src/nvim/sign.c index 23dd447744..ab5d04d39b 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -198,7 +198,7 @@ static void insert_sign( // column for signs. if (buf->b_signlist == NULL) { redraw_buf_later(buf, NOT_VALID); - changed_cline_bef_curs(); + changed_line_abv_curs(); } // first sign in signlist @@ -265,6 +265,81 @@ dict_T * sign_get_info(signlist_T *sign) return d; } +// Sort the signs placed on the same line as "sign" by priority. Invoked after +// changing the priority of an already placed sign. Assumes the signs in the +// buffer are sorted by line number and priority. +static void sign_sort_by_prio_on_line(buf_T *buf, signlist_T *sign) + FUNC_ATTR_NONNULL_ALL +{ + // If there is only one sign in the buffer or only one sign on the line or + // the sign is already sorted by priority, then return. + if ((sign->prev == NULL + || sign->prev->lnum != sign->lnum + || sign->prev->priority > sign->priority) + && (sign->next == NULL + || sign->next->lnum != sign->lnum + || sign->next->priority < sign->priority)) { + return; + } + + // One or more signs on the same line as 'sign' + // Find a sign after which 'sign' should be inserted + + // First search backward for a sign with higher priority on the same line + signlist_T *p = sign; + while (p->prev != NULL + && p->prev->lnum == sign->lnum + && p->prev->priority <= sign->priority) { + p = p->prev; + } + if (p == sign) { + // Sign not found. Search forward for a sign with priority just before + // 'sign'. + p = sign->next; + while (p->next != NULL + && p->next->lnum == sign->lnum + && p->next->priority > sign->priority) { + p = p->next; + } + } + + // Remove 'sign' from the list + if (buf->b_signlist == sign) { + buf->b_signlist = sign->next; + } + if (sign->prev != NULL) { + sign->prev->next = sign->next; + } + if (sign->next != NULL) { + sign->next->prev = sign->prev; + } + sign->prev = NULL; + sign->next = NULL; + + // Re-insert 'sign' at the right place + if (p->priority <= sign->priority) { + // 'sign' has a higher priority and should be inserted before 'p' + sign->prev = p->prev; + sign->next = p; + p->prev = sign; + if (sign->prev != NULL) { + sign->prev->next = sign; + } + if (buf->b_signlist == p) { + buf->b_signlist = sign; + } + } else { + // 'sign' has a lower priority and should be inserted after 'p' + sign->prev = p; + sign->next = p->next; + p->next = sign; + if (sign->next != NULL) { + sign->next->prev = sign; + } + } +} + + /// Add the sign into the signlist. Find the right spot to do it though. void buf_addsign( buf_T *buf, // buffer to store sign in @@ -284,6 +359,8 @@ void buf_addsign( && sign_in_group(sign, groupname)) { // Update an existing sign sign->typenr = typenr; + sign->priority = prio; + sign_sort_by_prio_on_line(buf, sign); return; } else if (lnum < sign->lnum) { insert_sign_by_lnum_prio(buf, prev, id, groupname, prio, lnum, typenr); @@ -418,11 +495,11 @@ linenr_T buf_delsign( } } - // When deleted the last sign needs to redraw the windows to remove the - // sign column. + // When deleting the last sign the cursor position may change, because the + // sign columns no longer shows. And the 'signcolumn' may be hidden. if (buf->b_signlist == NULL) { redraw_buf_later(buf, NOT_VALID); - changed_cline_bef_curs(); + changed_line_abv_curs(); } return lnum; @@ -495,7 +572,7 @@ void buf_delete_signs(buf_T *buf, char_u *group) // When deleting the last sign need to redraw the windows to remove the // sign column. Not when curwin is NULL (this means we're exiting). if (buf->b_signlist != NULL && curwin != NULL) { - changed_cline_bef_curs(); + changed_line_abv_curs(); } lastp = &buf->b_signlist; @@ -754,6 +831,14 @@ int sign_define_by_name( } else { sp_prev->sn_next = sp; } + } else { + // Signs may already exist, a redraw is needed in windows with a + // non-empty sign list. + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer->b_signlist != NULL) { + redraw_buf_later(wp->w_buffer, NOT_VALID); + } + } } // set values for a defined sign. @@ -1531,10 +1616,44 @@ static enum EXP_SUBCMD, // expand :sign sub-commands EXP_DEFINE, // expand :sign define {name} args EXP_PLACE, // expand :sign place {id} args + EXP_LIST, // expand :sign place args EXP_UNPLACE, // expand :sign unplace" - EXP_SIGN_NAMES // expand with name of placed signs + EXP_SIGN_NAMES, // expand with name of placed signs + EXP_SIGN_GROUPS, // expand with name of placed sign groups } expand_what; +// Return the n'th sign name (used for command line completion) +static char_u *get_nth_sign_name(int idx) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + // Complete with name of signs already defined + int current_idx = 0; + for (sign_T *sp = first_sign; sp != NULL; sp = sp->sn_next) { + if (current_idx++ == idx) { + return sp->sn_name; + } + } + return NULL; +} + +// Return the n'th sign group name (used for command line completion) +static char_u *get_nth_sign_group_name(int idx) +{ + // Complete with name of sign groups already defined + int current_idx = 0; + int todo = (int)sg_table.ht_used; + for (hashitem_T *hi = sg_table.ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + todo--; + if (current_idx++ == idx) { + signgroup_T *const group = HI2SG(hi); + return group->sg_name; + } + } + } + return NULL; +} + /// Function given to ExpandGeneric() to obtain the sign command /// expansion. char_u * get_sign_name(expand_T *xp, int idx) @@ -1552,20 +1671,18 @@ char_u * get_sign_name(expand_T *xp, int idx) "buffer=", NULL }; return (char_u *)place_arg[idx]; } + case EXP_LIST: { + char *list_arg[] = { "group=", "file=", "buffer=", NULL }; + return (char_u *)list_arg[idx]; + } case EXP_UNPLACE: { char *unplace_arg[] = { "group=", "file=", "buffer=", NULL }; return (char_u *)unplace_arg[idx]; } - case EXP_SIGN_NAMES: { - // Complete with name of signs already defined - int current_idx = 0; - for (sign_T *sp = first_sign; sp != NULL; sp = sp->sn_next) { - if (current_idx++ == idx) { - return sp->sn_name; - } - } - } - return NULL; + case EXP_SIGN_NAMES: + return get_nth_sign_name(idx); + case EXP_SIGN_GROUPS: + return get_nth_sign_group_name(idx); default: return NULL; } @@ -1574,7 +1691,6 @@ char_u * get_sign_name(expand_T *xp, int idx) /// Handle command line completion for :sign command. void set_context_in_sign_cmd(expand_T *xp, char_u *arg) { - char_u *p; char_u *end_subcmd; char_u *last; int cmd_idx; @@ -1598,26 +1714,6 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) // | // begin_subcmd_args begin_subcmd_args = skipwhite(end_subcmd); - p = skiptowhite(begin_subcmd_args); - if (*p == NUL) { - // - // Expand first argument of subcmd when possible. - // For ":jump {id}" and ":unplace {id}", we could - // possibly expand the ids of all signs already placed. - // - xp->xp_pattern = begin_subcmd_args; - switch (cmd_idx) { - case SIGNCMD_LIST: - case SIGNCMD_UNDEFINE: - // :sign list <CTRL-D> - // :sign undefine <CTRL-D> - expand_what = EXP_SIGN_NAMES; - break; - default: - xp->xp_context = EXPAND_NOTHING; - } - return; - } // Expand last argument of subcmd. // @@ -1626,6 +1722,7 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) // p // Loop until reaching last argument. + char_u *p = begin_subcmd_args; do { p = skipwhite(p); last = p; @@ -1645,7 +1742,20 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) expand_what = EXP_DEFINE; break; case SIGNCMD_PLACE: - expand_what = EXP_PLACE; + // List placed signs + if (ascii_isdigit(*begin_subcmd_args)) { + // :sign place {id} {args}... + expand_what = EXP_PLACE; + } else { + // :sign place {args}... + expand_what = EXP_LIST; + } + break; + case SIGNCMD_LIST: + case SIGNCMD_UNDEFINE: + // :sign list <CTRL-D> + // :sign undefine <CTRL-D> + expand_what = EXP_SIGN_NAMES; break; case SIGNCMD_JUMP: case SIGNCMD_UNPLACE: @@ -1659,19 +1769,33 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) xp->xp_pattern = p + 1; switch (cmd_idx) { case SIGNCMD_DEFINE: - if (STRNCMP(last, "texthl", p - last) == 0 - || STRNCMP(last, "linehl", p - last) == 0 - || STRNCMP(last, "numhl", p - last) == 0) { + if (STRNCMP(last, "texthl", 6) == 0 + || STRNCMP(last, "linehl", 6) == 0 + || STRNCMP(last, "numhl", 5) == 0) { xp->xp_context = EXPAND_HIGHLIGHT; - } else if (STRNCMP(last, "icon", p - last) == 0) { + } else if (STRNCMP(last, "icon", 4) == 0) { xp->xp_context = EXPAND_FILES; } else { xp->xp_context = EXPAND_NOTHING; } break; case SIGNCMD_PLACE: - if (STRNCMP(last, "name", p - last) == 0) { + if (STRNCMP(last, "name", 4) == 0) { expand_what = EXP_SIGN_NAMES; + } else if (STRNCMP(last, "group", 5) == 0) { + expand_what = EXP_SIGN_GROUPS; + } else if (STRNCMP(last, "file", 4) == 0) { + xp->xp_context = EXPAND_BUFFERS; + } else { + xp->xp_context = EXPAND_NOTHING; + } + break; + case SIGNCMD_UNPLACE: + case SIGNCMD_JUMP: + if (STRNCMP(last, "group", 5) == 0) { + expand_what = EXP_SIGN_GROUPS; + } else if (STRNCMP(last, "file", 4) == 0) { + xp->xp_context = EXPAND_BUFFERS; } else { xp->xp_context = EXPAND_NOTHING; } diff --git a/src/nvim/spell.c b/src/nvim/spell.c index c75a53a777..180073ade1 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -848,7 +848,7 @@ static void find_word(matchinf_T *mip, int mode) mip->mi_compflags[mip->mi_complen] = ((unsigned)flags >> 24); mip->mi_compflags[mip->mi_complen + 1] = NUL; if (word_ends) { - char_u fword[MAXWLEN]; + char_u fword[MAXWLEN] = { 0 }; if (slang->sl_compsylmax < MAXWLEN) { // "fword" is only needed for checking syllables. @@ -1026,26 +1026,25 @@ match_checkcompoundpattern ( // Returns true if "flags" is a valid sequence of compound flags and "word" // does not have too many syllables. -static bool can_compound(slang_T *slang, char_u *word, char_u *flags) +static bool can_compound(slang_T *slang, const char_u *word, + const char_u *flags) + FUNC_ATTR_NONNULL_ALL { - char_u uflags[MAXWLEN * 2]; - int i; - char_u *p; + char_u uflags[MAXWLEN * 2] = { 0 }; - if (slang->sl_compprog == NULL) + if (slang->sl_compprog == NULL) { return false; - if (enc_utf8) { - // Need to convert the single byte flags to utf8 characters. - p = uflags; - for (i = 0; flags[i] != NUL; i++) { - p += utf_char2bytes(flags[i], p); - } - *p = NUL; - p = uflags; - } else - p = flags; - if (!vim_regexec_prog(&slang->sl_compprog, false, p, 0)) + } + // Need to convert the single byte flags to utf8 characters. + char_u *p = uflags; + for (int i = 0; flags[i] != NUL; i++) { + p += utf_char2bytes(flags[i], p); + } + *p = NUL; + p = uflags; + if (!vim_regexec_prog(&slang->sl_compprog, false, p, 0)) { return false; + } // Count the number of syllables. This may be slow, do it last. If there // are too many syllables AND the number of compound words is above @@ -2008,6 +2007,10 @@ char_u *did_set_spelllang(win_T *wp) region = NULL; len = (int)STRLEN(lang); + if (!valid_spellang(lang)) { + continue; + } + if (STRCMP(lang, "cjk") == 0) { wp->w_s->b_cjk = 1; continue; @@ -2824,9 +2827,6 @@ void spell_suggest(int count) smsg(_("Sorry, only %" PRId64 " suggestions"), (int64_t)sug.su_ga.ga_len); } else { - XFREE_CLEAR(repl_from); - XFREE_CLEAR(repl_to); - // When 'rightleft' is set the list is drawn right-left. cmdmsg_rl = curwin->w_p_rl; if (cmdmsg_rl) @@ -2906,6 +2906,9 @@ void spell_suggest(int count) if (selected > 0 && selected <= sug.su_ga.ga_len && u_save_cursor() == OK) { // Save the from and to text for :spellrepall. + XFREE_CLEAR(repl_from); + XFREE_CLEAR(repl_to); + stp = &SUG(sug.su_ga, selected - 1); if (sug.su_badlen > stp->st_orglen) { // Replacing less than "su_badlen", append the remainder to @@ -3603,7 +3606,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so { char_u tword[MAXWLEN]; // good word collected so far trystate_T stack[MAXWLEN]; - char_u preword[MAXWLEN * 3]; // word found with proper case; + char_u preword[MAXWLEN * 3] = { 0 }; // word found with proper case; // concatenation of prefix compound // words and split word. NUL terminated // when going deeper but not when coming @@ -4268,9 +4271,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // For changing a composing character adjust // the score from SCORE_SUBST to // SCORE_SUBCOMP. - if (enc_utf8 - && utf_iscomposing(utf_ptr2char(tword + sp->ts_twordlen - - sp->ts_tcharlen)) + if (utf_iscomposing(utf_ptr2char(tword + sp->ts_twordlen + - sp->ts_tcharlen)) && utf_iscomposing(utf_ptr2char(fword + sp->ts_fcharstart))) { sp->ts_score -= SCORE_SUBST - SCORE_SUBCOMP; @@ -5759,19 +5761,22 @@ cleanup_suggestions ( int maxscore, int keep // nr of suggestions to keep ) + FUNC_ATTR_NONNULL_ALL { suggest_T *stp = &SUG(*gap, 0); - // Sort the list. - qsort(gap->ga_data, (size_t)gap->ga_len, sizeof(suggest_T), sug_compare); + if (gap->ga_len > 0) { + // Sort the list. + qsort(gap->ga_data, (size_t)gap->ga_len, sizeof(suggest_T), sug_compare); - // Truncate the list to the number of suggestions that will be displayed. - if (gap->ga_len > keep) { - for (int i = keep; i < gap->ga_len; ++i) { - xfree(stp[i].st_word); + // Truncate the list to the number of suggestions that will be displayed. + if (gap->ga_len > keep) { + for (int i = keep; i < gap->ga_len; i++) { + xfree(stp[i].st_word); + } + gap->ga_len = keep; + return stp[keep - 1].st_score; } - gap->ga_len = keep; - return stp[keep - 1].st_score; } return maxscore; } @@ -5855,7 +5860,7 @@ static void spell_soundfold_sofo(slang_T *slang, char_u *inword, char_u *res) // 255, sl_sal the rest. for (s = inword; *s != NUL; ) { c = mb_cptr2char_adv((const char_u **)&s); - if (enc_utf8 ? utf_class(c) == 0 : ascii_iswhite(c)) { + if (utf_class(c) == 0) { c = ' '; } else if (c < 256) { c = slang->sl_sal_first[c]; @@ -5932,9 +5937,10 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) const char_u *t = s; c = mb_cptr2char_adv((const char_u **)&s); if (slang->sl_rem_accents) { - if (enc_utf8 ? utf_class(c) == 0 : ascii_iswhite(c)) { - if (did_white) + if (utf_class(c) == 0) { + if (did_white) { continue; + } c = ' '; did_white = true; } else { diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h index e83b21b219..034c580b3e 100644 --- a/src/nvim/spell_defs.h +++ b/src/nvim/spell_defs.h @@ -261,20 +261,15 @@ typedef struct trystate_S { // Use our own character-case definitions, because the current locale may // differ from what the .spl file uses. // These must not be called with negative number! -#include <wchar.h> // for towupper() and towlower() // Multi-byte implementation. For Unicode we can call utf_*(), but don't do // that for ASCII, because we don't want to use 'casemap' here. Otherwise use // the "w" library function for characters above 255. -#define SPELL_TOFOLD(c) (enc_utf8 && (c) >= 128 ? utf_fold(c) \ - : (c) < \ - 256 ? (int)spelltab.st_fold[c] : (int)towlower(c)) +#define SPELL_TOFOLD(c) ((c) >= 128 ? utf_fold(c) : (int)spelltab.st_fold[c]) -#define SPELL_TOUPPER(c) (enc_utf8 && (c) >= 128 ? mb_toupper(c) \ - : (c) < \ - 256 ? (int)spelltab.st_upper[c] : (int)towupper(c)) +#define SPELL_TOUPPER(c) ((c) >= 128 ? mb_toupper(c) \ + : (int)spelltab.st_upper[c]) -#define SPELL_ISUPPER(c) (enc_utf8 && (c) >= 128 ? mb_isupper(c) \ - : (c) < 256 ? spelltab.st_isu[c] : iswupper(c)) +#define SPELL_ISUPPER(c) ((c) >= 128 ? mb_isupper(c) : spelltab.st_isu[c]) // First language that is loaded, start of the linked list of loaded // languages. diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 14abfda9ff..f8c10d0258 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -304,9 +304,6 @@ static char *e_spell_trunc = N_("E758: Truncated spell file"); static char *e_afftrailing = N_("Trailing text in %s line %d: %s"); static char *e_affname = N_("Affix name too long in %s line %d: %s"); -static char *e_affform = N_("E761: Format error in affix file FOL, LOW or UPP"); -static char *e_affrange = N_( - "E762: Character in FOL, LOW or UPP is out of range"); static char *msg_compressing = N_("Compressing word tree..."); #define MAXLINELEN 500 // Maximum length in bytes of a line in a .aff @@ -1386,8 +1383,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len) // Inserting backslashes may double the length, "^\(\)$<Nul>" is 7 bytes. // Conversion to utf-8 may double the size. c = todo * 2 + 7; - if (enc_utf8) - c += todo * 2; + c += todo * 2; pat = xmalloc(c); // We also need a list of all flags that can appear at the start and one @@ -2624,19 +2620,6 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) spin->si_clear_chartab = false; } - // Don't write a word table for an ASCII file, so that we don't check - // for conflicts with a word table that matches 'encoding'. - // Don't write one for utf-8 either, we use utf_*() and - // mb_get_class(), the list of chars in the file will be incomplete. - if (!spin->si_ascii - && !enc_utf8 - ) { - if (fol == NULL || low == NULL || upp == NULL) - smsg(_("Missing FOL/LOW/UPP line in %s"), fname); - else - (void)set_spell_chartab(fol, low, upp); - } - xfree(fol); xfree(low); xfree(upp); @@ -5533,65 +5516,6 @@ static void init_spellfile(void) } } -// Set the spell character tables from strings in the affix file. -static int set_spell_chartab(char_u *fol, char_u *low, char_u *upp) -{ - // We build the new tables here first, so that we can compare with the - // previous one. - spelltab_T new_st; - char_u *pf = fol, *pl = low, *pu = upp; - int f, l, u; - - clear_spell_chartab(&new_st); - - while (*pf != NUL) { - if (*pl == NUL || *pu == NUL) { - EMSG(_(e_affform)); - return FAIL; - } - f = mb_ptr2char_adv((const char_u **)&pf); - l = mb_ptr2char_adv((const char_u **)&pl); - u = mb_ptr2char_adv((const char_u **)&pu); - // Every character that appears is a word character. - if (f < 256) - new_st.st_isw[f] = true; - if (l < 256) - new_st.st_isw[l] = true; - if (u < 256) - new_st.st_isw[u] = true; - - // if "LOW" and "FOL" are not the same the "LOW" char needs - // case-folding - if (l < 256 && l != f) { - if (f >= 256) { - EMSG(_(e_affrange)); - return FAIL; - } - new_st.st_fold[l] = f; - } - - // if "UPP" and "FOL" are not the same the "UPP" char needs - // case-folding, it's upper case and the "UPP" is the upper case of - // "FOL" . - if (u < 256 && u != f) { - if (f >= 256) { - EMSG(_(e_affrange)); - return FAIL; - } - new_st.st_fold[u] = f; - new_st.st_isu[u] = true; - new_st.st_upper[f] = u; - } - } - - if (*pl != NUL || *pu != NUL) { - EMSG(_(e_affform)); - return FAIL; - } - - return set_spell_finish(&new_st); -} - // Set the spell character tables from strings in the .spl file. static void set_spell_charflags ( diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index bcf133afda..ef4dfb3caa 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -3981,7 +3981,7 @@ static void add_keyword(char_u *const name, STRLEN(kp->keyword), hash); // even though it looks like only the kp->keyword member is - // being used here, vim uses some pointer trickery to get the orignal + // being used here, vim uses some pointer trickery to get the original // struct again later by using knowledge of the offset of the keyword // field in the struct. See the definition of the HI2KE macro. if (HASHITEM_EMPTY(hi)) { @@ -6400,7 +6400,7 @@ static int color_numbers_88[28] = { 0, 4, 2, 6, 75, 11, 78, 15, -1 }; // for xterm with 256 colors... static int color_numbers_256[28] = { 0, 4, 2, 6, - 1, 5, 130, 130, + 1, 5, 130, 3, 248, 248, 7, 7, 242, 242, 12, 81, 10, 121, diff --git a/src/nvim/tag.c b/src/nvim/tag.c index a412ed0276..81d1ef4c9f 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -1182,7 +1182,7 @@ static int find_tagfunc_tags( if (result == FAIL) { return FAIL; } - if (rettv.v_type == VAR_SPECIAL && rettv.vval.v_number == VV_NULL) { + if (rettv.v_type == VAR_SPECIAL && rettv.vval.v_special == kSpecialVarNull) { tv_clear(&rettv); return NOTDONE; } @@ -2540,7 +2540,9 @@ parse_match( } p += 2; // skip ";\"" if (*p++ == TAB) { - while (ASCII_ISALPHA(*p)) { + // Accept ASCII alphabetic kind characters and any multi-byte + // character. + while (ASCII_ISALPHA(*p) || utfc_ptr2len(p) > 1) { if (STRNCMP(p, "kind:", 5) == 0) { tagp->tagkind = p + 5; } else if (STRNCMP(p, "user_data:", 10) == 0) { @@ -2559,19 +2561,22 @@ parse_match( } if (pt == NULL) break; - p = pt + 1; + p = pt; + MB_PTR_ADV(p); } } } if (tagp->tagkind != NULL) { for (p = tagp->tagkind; - *p && *p != '\t' && *p != '\r' && *p != '\n'; ++p) - ; + *p && *p != '\t' && *p != '\r' && *p != '\n'; + MB_PTR_ADV(p)) { + } tagp->tagkind_end = p; } if (tagp->user_data != NULL) { for (p = tagp->user_data; - *p && *p != '\t' && *p != '\r' && *p != '\n'; p++) { + *p && *p != '\t' && *p != '\r' && *p != '\n'; + MB_PTR_ADV(p)) { } tagp->user_data_end = p; } @@ -3158,9 +3163,11 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) is_static = test_for_static(&tp); - /* Skip pseudo-tag lines. */ - if (STRNCMP(tp.tagname, "!_TAG_", 6) == 0) + // Skip pseudo-tag lines. + if (STRNCMP(tp.tagname, "!_TAG_", 6) == 0) { + xfree(matches[i]); continue; + } dict = tv_dict_alloc(); tv_list_append_dict(list, dict); @@ -3179,7 +3186,8 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) if (tp.command_end != NULL) { for (char_u *p = tp.command_end + 3; - *p != NUL && *p != '\n' && *p != '\r'; p++) { + *p != NUL && *p != '\n' && *p != '\r'; + MB_PTR_ADV(p)) { if (p == tp.tagkind || (p + 5 == tp.tagkind && STRNCMP(p, "kind:", 5) == 0)) { // skip "kind:<kind>" and "<kind>" @@ -3378,11 +3386,15 @@ static void tagstack_set_curidx(win_T *wp, int curidx) } // Set the tag stack entries of the specified window. -// 'action' is set to either 'a' for append or 'r' for replace. -int set_tagstack(win_T *wp, dict_T *d, int action) +// 'action' is set to one of: +// 'a' for append +// 'r' for replace +// 't' for truncate +int set_tagstack(win_T *wp, const dict_T *d, int action) + FUNC_ATTR_NONNULL_ARG(1) { dictitem_T *di; - list_T *l; + list_T *l = NULL; // not allowed to alter the tag stack entries from inside tagfunc if (tfu_in_use) { @@ -3395,16 +3407,30 @@ int set_tagstack(win_T *wp, dict_T *d, int action) return FAIL; } l = di->di_tv.vval.v_list; + } - if (action == 'r') { + if ((di = tv_dict_find(d, "curidx", -1)) != NULL) { + tagstack_set_curidx(wp, (int)tv_get_number(&di->di_tv) - 1); + } + if (action == 't') { // truncate the stack + taggy_T *const tagstack = wp->w_tagstack; + const int tagstackidx = wp->w_tagstackidx; + int tagstacklen = wp->w_tagstacklen; + // delete all the tag stack entries above the current entry + while (tagstackidx < tagstacklen) { + tagstack_clear_entry(&tagstack[--tagstacklen]); + } + wp->w_tagstacklen = tagstacklen; + } + + if (l != NULL) { + if (action == 'r') { // replace the stack tagstack_clear(wp); } tagstack_push_items(wp, l); - } - - if ((di = tv_dict_find(d, "curidx", -1)) != NULL) { - tagstack_set_curidx(wp, (int)tv_get_number(&di->di_tv) - 1); + // set the current index after the last entry + wp->w_tagstackidx = wp->w_tagstacklen; } return OK; diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index c5e756905a..a096b77ac6 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -343,12 +343,16 @@ void terminal_enter(void) RedrawingDisabled = false; // Disable these options in terminal-mode. They are nonsense because cursor is - // placed at end of buffer to "follow" output. + // placed at end of buffer to "follow" output. #11072 win_T *save_curwin = curwin; int save_w_p_cul = curwin->w_p_cul; int save_w_p_cuc = curwin->w_p_cuc; + long save_w_p_so = curwin->w_p_so; + long save_w_p_siso = curwin->w_p_siso; curwin->w_p_cul = false; curwin->w_p_cuc = false; + curwin->w_p_so = 0; + curwin->w_p_siso = 0; adjust_topline(s->term, buf, 0); // scroll to end // erase the unfocused cursor @@ -370,6 +374,8 @@ void terminal_enter(void) if (save_curwin == curwin) { // save_curwin may be invalid (window closed)! curwin->w_p_cul = save_w_p_cul; curwin->w_p_cuc = save_w_p_cuc; + curwin->w_p_so = save_w_p_so; + curwin->w_p_siso = save_w_p_siso; } // draw the unfocused cursor @@ -650,6 +656,11 @@ Buffer terminal_buf(const Terminal *term) return term->buf_handle; } +bool terminal_running(const Terminal *term) +{ + return !term->closed; +} + // }}} // libvterm callbacks {{{ @@ -992,8 +1003,9 @@ static void mouse_action(Terminal *term, int button, int row, int col, static bool send_mouse_event(Terminal *term, int c) { int row = mouse_row, col = mouse_col, grid = mouse_grid; + int offset; win_T *mouse_win = mouse_find_win(&grid, &row, &col); - if (mouse_win == NULL) { + if (mouse_win == NULL || (offset = win_col_off(mouse_win)) > col) { goto end; } @@ -1015,7 +1027,7 @@ static bool send_mouse_event(Terminal *term, int c) default: return false; } - mouse_action(term, button, row, col, drag, 0); + mouse_action(term, button, row, col - offset, drag, 0); size_t len = vterm_output_read(term->vt, term->textbuf, sizeof(term->textbuf)); terminal_send(term, term->textbuf, (size_t)len); diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index c36458930f..e52fd888bd 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -86,7 +86,7 @@ nongui: nolog $(FIXFF) $(SCRIPTS) newtests report @echo 'set $$_exitcode = -1\nrun\nif $$_exitcode != -1\n quit\nend' > .gdbinit report: - $(RUN_VIMTEST) $(NO_INITS) -u NONE -S summarize.vim messages + $(NVIM_PRG) -u NONE $(NO_INITS) -S summarize.vim messages @echo @echo 'Test results:' @cat test_result.log diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index fd49e48c2d..adf7463936 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -98,11 +98,6 @@ func GetAllocId(name) return lnum - top - 1 endfunc -func CanRunVimInTerminal() - " Nvim: always false, we use Lua screen-tests instead. - return 0 -endfunc - func RunTheTest(test) echo 'Executing ' . a:test diff --git a/src/nvim/testdir/screendump.vim b/src/nvim/testdir/screendump.vim index e69de29bb2..8afff1da91 100644 --- a/src/nvim/testdir/screendump.vim +++ b/src/nvim/testdir/screendump.vim @@ -0,0 +1,2 @@ +source shared.vim +source term_util.vim diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index 3875ffc056..b041fdedb1 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -1,10 +1,12 @@ " Functions shared by several tests. " Only load this script once. -if exists('*WaitFor') +if exists('*PythonProg') finish endif +source view_util.vim + " {Nvim} " Filepath captured from output may be truncated, like this: " /home/va...estdir/Xtest-tmpdir/nvimxbXN4i/10 @@ -328,17 +330,6 @@ func RunVimPiped(before, after, arguments, pipecmd) return 1 endfunc -" Get line "lnum" as displayed on the screen. -" Trailing white space is trimmed. -func! Screenline(lnum) - let chars = [] - for c in range(1, winwidth(0)) - call add(chars, nr2char(screenchar(a:lnum, c))) - endfor - let line = join(chars, '') - return matchstr(line, '^.\{-}\ze\s*$') -endfunc - func CanRunGui() return has('gui') && ($DISPLAY != "" || has('gui_running')) endfunc diff --git a/src/nvim/testdir/term_util.vim b/src/nvim/testdir/term_util.vim new file mode 100644 index 0000000000..3a838a3a1f --- /dev/null +++ b/src/nvim/testdir/term_util.vim @@ -0,0 +1,11 @@ +" Functions about terminal shared by several tests + +" Only load this script once. +if exists('*CanRunVimInTerminal') + finish +endif + +func CanRunVimInTerminal() + " Nvim: always false, we use Lua screen-tests instead. + return 0 +endfunc diff --git a/src/nvim/testdir/test49.vim b/src/nvim/testdir/test49.vim index fc79f57d2e..c86fdf25ab 100644 --- a/src/nvim/testdir/test49.vim +++ b/src/nvim/testdir/test49.vim @@ -1,6 +1,6 @@ " Vim script language tests " Author: Servatius Brandt <Servatius.Brandt@fujitsu-siemens.com> -" Last Change: 2019 May 24 +" Last Change: 2019 Oct 08 "------------------------------------------------------------------------------- " Test environment {{{1 @@ -456,7 +456,7 @@ function! ExtraVim(...) " messing up the user's viminfo file. let redirect = a:0 ? \ " -c 'au VimLeave * redir END' -c 'redir\\! >" . a:1 . "'" : "" - exec "!echo '" . debug_quits . "q' | $NVIM_PRG -u NONE -N -es" . redirect . + exec "!echo '" . debug_quits . "q' | " .. v:progpath .. " -u NONE -N -es" . redirect . \ " -c 'debuggreedy|set viminfo+=nviminfo'" . \ " -c 'let ExtraVimBegin = " . extra_begin . "'" . \ " -c 'let ExtraVimResult = \"" . resultfile . "\"'" . breakpoints . diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index e3547aea5b..954e5d875f 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -1072,6 +1072,40 @@ func Test_Cmd_Autocmds() enew! endfunc +func s:ReadFile() + setl noswapfile nomodified + let filename = resolve(expand("<afile>:p")) + execute 'read' fnameescape(filename) + 1d_ + exe 'file' fnameescape(filename) + setl buftype=acwrite +endfunc + +func s:WriteFile() + let filename = resolve(expand("<afile>:p")) + setl buftype= + noautocmd execute 'write' fnameescape(filename) + setl buftype=acwrite + setl nomodified +endfunc + +func Test_BufReadCmd() + autocmd BufReadCmd *.test call s:ReadFile() + autocmd BufWriteCmd *.test call s:WriteFile() + + call writefile(['one', 'two', 'three'], 'Xcmd.test') + edit Xcmd.test + call assert_match('Xcmd.test" line 1 of 3', execute('file')) + normal! Gofour + write + call assert_equal(['one', 'two', 'three', 'four'], readfile('Xcmd.test')) + + bwipe! + call delete('Xcmd.test') + au! BufReadCmd + au! BufWriteCmd +endfunc + func SetChangeMarks(start, end) exe a:start. 'mark [' exe a:end. 'mark ]' @@ -1786,3 +1820,46 @@ func Test_FileChangedShell_reload() bwipe! call delete('Xchanged') endfunc + +" Test for FileReadCmd autocmd +func Test_autocmd_FileReadCmd() + func ReadFileCmd() + call append(line('$'), "v:cmdarg = " .. v:cmdarg) + endfunc + augroup FileReadCmdTest + au! + au FileReadCmd Xtest call ReadFileCmd() + augroup END + + new + read ++bin Xtest + read ++nobin Xtest + read ++edit Xtest + read ++bad=keep Xtest + read ++bad=drop Xtest + read ++bad=- Xtest + read ++ff=unix Xtest + read ++ff=dos Xtest + read ++ff=mac Xtest + read ++enc=utf-8 Xtest + + call assert_equal(['', + \ 'v:cmdarg = ++bin', + \ 'v:cmdarg = ++nobin', + \ 'v:cmdarg = ++edit', + \ 'v:cmdarg = ++bad=keep', + \ 'v:cmdarg = ++bad=drop', + \ 'v:cmdarg = ++bad=-', + \ 'v:cmdarg = ++ff=unix', + \ 'v:cmdarg = ++ff=dos', + \ 'v:cmdarg = ++ff=mac', + \ 'v:cmdarg = ++enc=utf-8'], getline(1, '$')) + + close! + augroup FileReadCmdTest + au! + augroup END + delfunc ReadFileCmd +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim index 6d88f1dc5a..5675bf74dd 100644 --- a/src/nvim/testdir/test_breakindent.vim +++ b/src/nvim/testdir/test_breakindent.vim @@ -361,5 +361,15 @@ func Test_breakindent19_sbr_nextpage() \ "> aaaaaaaaaaaaaaaaaa", \ ] call s:compare_lines(expect, lines) + + setl breakindent briopt=min:18 sbr=> + norm! 5gj + let lines = s:screen_lines(1, 20) + let expect = [ + \ ">aaaaaaaaaaaaaaaaaaa", + \ ">aaaaaaaaaaaaaaaaaaa", + \ ">aaaaaaaaaaaaaaaaaaa", + \ ] + call s:compare_lines(expect, lines) call s:close_windows('set breakindent& briopt& sbr&') endfunc diff --git a/src/nvim/testdir/test_bufline.vim b/src/nvim/testdir/test_bufline.vim index a924ce0002..076f03fdd8 100644 --- a/src/nvim/testdir/test_bufline.vim +++ b/src/nvim/testdir/test_bufline.vim @@ -1,7 +1,7 @@ " Tests for setbufline(), getbufline(), appendbufline(), deletebufline() source shared.vim -" source screendump.vim +source screendump.vim func Test_setbufline_getbufline() new diff --git a/src/nvim/testdir/test_bufwintabinfo.vim b/src/nvim/testdir/test_bufwintabinfo.vim index 176d49d28e..cb7ab44798 100644 --- a/src/nvim/testdir/test_bufwintabinfo.vim +++ b/src/nvim/testdir/test_bufwintabinfo.vim @@ -149,3 +149,10 @@ func Test_getbufinfo_lines() edit Xfoo bw! endfunc + +function Test_getbufinfo_lastused() + new Xfoo + let info = getbufinfo('Xfoo')[0] + call assert_equal(has_key(info, 'lastused'), 1) + call assert_equal(type(info.lastused), type(0)) +endfunc diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 9c3c33a943..2c7d64f078 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -755,3 +755,62 @@ func Test_cmdwin_feedkeys() " This should not generate E488 call feedkeys("q:\<CR>", 'x') endfunc + +func Test_buffers_lastused() + " check that buffers are sorted by time when wildmode has lastused + edit bufc " oldest + + sleep 1200m + enew + edit bufa " middle + + sleep 1200m + enew + edit bufb " newest + + enew + + call assert_equal(['bufc', 'bufa', 'bufb'], + \ getcompletion('', 'buffer')) + + let save_wildmode = &wildmode + set wildmode=full:lastused + + let cap = "\<c-r>=execute('let X=getcmdline()')\<cr>" + call feedkeys(":b \<tab>" .. cap .. "\<esc>", 'xt') + call assert_equal('b bufb', X) + call feedkeys(":b \<tab>\<tab>" .. cap .. "\<esc>", 'xt') + call assert_equal('b bufa', X) + call feedkeys(":b \<tab>\<tab>\<tab>" .. cap .. "\<esc>", 'xt') + call assert_equal('b bufc', X) + enew + + sleep 1200m + edit other + call feedkeys(":b \<tab>" .. cap .. "\<esc>", 'xt') + call assert_equal('b bufb', X) + call feedkeys(":b \<tab>\<tab>" .. cap .. "\<esc>", 'xt') + call assert_equal('b bufa', X) + call feedkeys(":b \<tab>\<tab>\<tab>" .. cap .. "\<esc>", 'xt') + call assert_equal('b bufc', X) + enew + + let &wildmode = save_wildmode + + bwipeout bufa + bwipeout bufb + bwipeout bufc +endfunc + +" test that ";" works to find a match at the start of the first line +func Test_zero_line_search() + new + call setline(1, ["1, pattern", "2, ", "3, pattern"]) + call cursor(1,1) + 0;/pattern/d + call assert_equal(["2, ", "3, pattern"], getline(1,'$')) + q! +endfunc + + +" vim: shiftwidth=2 sts=2 expandtab " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim index 40d3cdbdae..6bb602717f 100644 --- a/src/nvim/testdir/test_compiler.vim +++ b/src/nvim/testdir/test_compiler.vim @@ -38,10 +38,11 @@ func Test_compiler() endfunc func Test_compiler_without_arg() - let a=split(execute('compiler')) - call assert_match('^.*runtime/compiler/ant.vim$', a[0]) - call assert_match('^.*runtime/compiler/bcc.vim$', a[1]) - call assert_match('^.*runtime/compiler/xmlwf.vim$', a[-1]) + let runtime = substitute($VIMRUNTIME, '\\', '/', 'g') + let a = split(execute('compiler')) + call assert_match(runtime .. '/compiler/ant.vim$', a[0]) + call assert_match(runtime .. '/compiler/bcc.vim$', a[1]) + call assert_match(runtime .. '/compiler/xmlwf.vim$', a[-1]) endfunc func Test_compiler_completion() diff --git a/src/nvim/testdir/test_debugger.vim b/src/nvim/testdir/test_debugger.vim index 130bcf8910..811717208e 100644 --- a/src/nvim/testdir/test_debugger.vim +++ b/src/nvim/testdir/test_debugger.vim @@ -1,7 +1,7 @@ " Tests for the Vim script debug commands source shared.vim -" source screendump.vim +source screendump.vim " Run a Vim debugger command " If the expected output argument is supplied, then check for it. diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 21e0271bda..42e18ed027 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -1,4 +1,6 @@ " Tests for diff mode +source shared.vim +source screendump.vim func Test_diff_fold_sync() enew! @@ -67,7 +69,7 @@ func Common_vert_split() set foldmethod=marker foldcolumn=4 call assert_equal(0, &diff) call assert_equal('marker', &foldmethod) - call assert_equal(4, &foldcolumn) + call assert_equal('4', &foldcolumn) call assert_equal(0, &scrollbind) call assert_equal(0, &cursorbind) call assert_equal(1, &wrap) @@ -76,7 +78,7 @@ func Common_vert_split() vert diffsplit Xtest2 call assert_equal(1, &diff) call assert_equal('diff', &foldmethod) - call assert_equal(2, &foldcolumn) + call assert_equal('2', &foldcolumn) call assert_equal(1, &scrollbind) call assert_equal(1, &cursorbind) call assert_equal(0, &wrap) @@ -142,7 +144,7 @@ func Common_vert_split() 1wincmd w call assert_equal(0, &diff) call assert_equal('marker', &foldmethod) - call assert_equal(4, &foldcolumn) + call assert_equal('4', &foldcolumn) call assert_equal(0, &scrollbind) call assert_equal(0, &cursorbind) call assert_equal(1, &wrap) @@ -150,7 +152,7 @@ func Common_vert_split() wincmd w call assert_equal(0, &diff) call assert_equal('marker', &foldmethod) - call assert_equal(4, &foldcolumn) + call assert_equal('4', &foldcolumn) call assert_equal(0, &scrollbind) call assert_equal(0, &cursorbind) call assert_equal(1, &wrap) @@ -158,7 +160,7 @@ func Common_vert_split() wincmd w call assert_equal(0, &diff) call assert_equal('marker', &foldmethod) - call assert_equal(4, &foldcolumn) + call assert_equal('4', &foldcolumn) call assert_equal(0, &scrollbind) call assert_equal(0, &cursorbind) call assert_equal(1, &wrap) diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim index 5da05e85b5..1792dcc00b 100644 --- a/src/nvim/testdir/test_digraph.vim +++ b/src/nvim/testdir/test_digraph.vim @@ -433,6 +433,18 @@ func Test_digraphs_output() call assert_equal('Z% Ж 1046', matchstr(out, '\C\<Z%\D*1046\>')) call assert_equal('u- Å« 363', matchstr(out, '\C\<u-\D*363\>')) call assert_equal('SH ^A 1', matchstr(out, '\C\<SH\D*1\>')) + call assert_notmatch('Latin supplement', out) + + let out_bang_without_custom = execute(':digraph!') + digraph lt 60 + let out_bang_with_custom = execute(':digraph!') + call assert_notmatch('lt', out_bang_without_custom) + call assert_match("^\n" + \ .. "NU ^@ 10 .*\n" + \ .. "Latin supplement\n" + \ .. "!I ¡ 161 .*\n" + \ .. ".*\n" + \ .. 'Custom\n.*\<lt < 60\>', out_bang_with_custom) bw! endfunc diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index 98fa9a3c47..12d5d9790e 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -1439,7 +1439,7 @@ func Test_edit_alt() call delete('XAltFile') endfunc -func Test_leave_insert_autocmd() +func Test_edit_InsertLeave() new au InsertLeave * let g:did_au = 1 let g:did_au = 0 @@ -1469,6 +1469,21 @@ func Test_leave_insert_autocmd() iunmap x endfunc +func Test_edit_InsertLeave_undo() + new XtestUndo + set undofile + au InsertLeave * wall + exe "normal ofoo\<Esc>" + call assert_equal(2, line('$')) + normal u + call assert_equal(1, line('$')) + + bwipe! + au! InsertLeave + call delete('XtestUndo') + set undofile& +endfunc + " Test for inserting characters using CTRL-V followed by a number. func Test_edit_special_chars() new diff --git a/src/nvim/testdir/test_escaped_glob.vim b/src/nvim/testdir/test_escaped_glob.vim index aad3a1e835..2bfd82c296 100644 --- a/src/nvim/testdir/test_escaped_glob.vim +++ b/src/nvim/testdir/test_escaped_glob.vim @@ -17,7 +17,7 @@ function Test_glob() " Setting 'shell' to an invalid name causes a memory leak. sandbox call assert_equal("", glob('Xxx\{')) sandbox call assert_equal("", glob('Xxx\$')) - w! Xxx{ + w! Xxx\{ " } to fix highlighting w! Xxx\$ sandbox call assert_equal("Xxx{", glob('Xxx\{')) diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim index f5ce979208..4a027c3864 100644 --- a/src/nvim/testdir/test_excmd.vim +++ b/src/nvim/testdir/test_excmd.vim @@ -8,3 +8,35 @@ func Test_ex_delete() .dl call assert_equal(['a', 'c'], getline(1, 2)) endfunc + +func Test_buffers_lastused() + edit bufc " oldest + + sleep 1200m + edit bufa " middle + + sleep 1200m + edit bufb " newest + + enew + + let ls = split(execute('buffers t', 'silent!'), '\n') + let bufs = [] + for line in ls + let bufs += [split(line, '"\s*')[1:2]] + endfor + + let names = [] + for buf in bufs + if buf[0] !=# '[No Name]' + let names += [buf[0]] + endif + endfor + + call assert_equal(['bufb', 'bufa', 'bufc'], names) + call assert_match('[0-2] seconds ago', bufs[1][1]) + + bwipeout bufa + bwipeout bufb + bwipeout bufc +endfunc diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index dd546dbf71..264d8b000f 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -475,6 +475,8 @@ func Test_funcref() let OneByRef = funcref('One') call assert_equal(2, OneByRef()) call assert_fails('echo funcref("{")', 'E475:') + let OneByRef = funcref("One", repeat(["foo"], 20)) + call assert_fails('let OneByRef = funcref("One", repeat(["foo"], 21))', 'E118:') endfunc func Test_empty_concatenate() diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 2334cc95a7..ace56a375f 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -79,6 +79,7 @@ let s:filename_checks = { \ 'bib': ['file.bib'], \ 'bindzone': ['named.root'], \ 'blank': ['file.bl'], + \ 'bsdl': ['file.bsd', 'file.bsdl'], \ 'bst': ['file.bst'], \ 'bzr': ['bzr_log.any'], \ 'c': ['enlightenment/file.cfg', 'file.qc', 'file.c'], @@ -139,7 +140,7 @@ let s:filename_checks = { \ 'dockerfile': ['Dockerfile', 'file.Dockerfile'], \ 'dosbatch': ['file.bat', 'file.sys'], \ 'dosini': ['.editorconfig', '/etc/yum.conf', 'file.ini'], - \ 'dot': ['file.dot'], + \ 'dot': ['file.dot', 'file.gv'], \ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe'], \ 'dsl': ['file.dsl'], \ 'dtd': ['file.dtd'], @@ -234,6 +235,7 @@ let s:filename_checks = { \ 'kconfig': ['Kconfig', 'Kconfig.debug'], \ 'kivy': ['file.kv'], \ 'kix': ['file.kix'], + \ 'kotlin': ['file.kt', 'file.ktm', 'file.kts'], \ 'kscript': ['file.ks'], \ 'kwt': ['file.k'], \ 'lace': ['file.ace', 'file.ACE'], @@ -319,6 +321,7 @@ let s:filename_checks = { \ 'openroad': ['file.or'], \ 'ora': ['file.ora'], \ 'pamconf': ['/etc/pam.conf'], + \ 'pamenv': ['/etc/security/pam_env.conf', '/home/user/.pam_environment'], \ 'papp': ['file.papp', 'file.pxml', 'file.pxsl'], \ 'pascal': ['file.pas', 'file.dpr'], \ 'passwd': ['any/etc/passwd', 'any/etc/passwd-', 'any/etc/passwd.edit', 'any/etc/shadow', 'any/etc/shadow-', 'any/etc/shadow.edit', 'any/var/backups/passwd.bak', 'any/var/backups/shadow.bak'], @@ -435,7 +438,7 @@ let s:filename_checks = { \ 'swiftgyb': ['file.swift.gyb'], \ 'sil': ['file.sil'], \ 'sysctl': ['/etc/sysctl.conf', '/etc/sysctl.d/file.conf'], - \ 'systemd': ['any/systemd/file.automount', 'any/systemd/file.mount', 'any/systemd/file.path', 'any/systemd/file.service', 'any/systemd/file.socket', 'any/systemd/file.swap', 'any/systemd/file.target', 'any/systemd/file.timer', '/etc/systemd/system/some.d/file.conf', '/etc/systemd/system/some.d/.#file'], + \ 'systemd': ['any/systemd/file.automount', 'any/systemd/file.mount', 'any/systemd/file.path', 'any/systemd/file.service', 'any/systemd/file.socket', 'any/systemd/file.swap', 'any/systemd/file.target', 'any/systemd/file.timer', '/etc/systemd/system/some.d/file.conf', '/etc/systemd/system/some.d/.#file', '/etc/systemd/system/.#otherfile', '/home/user/.config/systemd/user/some.d/mine.conf', '/home/user/.config/systemd/user/some.d/.#file', '/home/user/.config/systemd/user/.#otherfile'], \ 'systemverilog': ['file.sv', 'file.svh'], \ 'tags': ['tags'], \ 'tak': ['file.tak'], @@ -479,7 +482,7 @@ let s:filename_checks = { \ 'verilog': ['file.v'], \ 'verilogams': ['file.va', 'file.vams'], \ 'vgrindefs': ['vgrindefs'], - \ 'vhdl': ['file.hdl', 'file.vhd', 'file.vhdl', 'file.vbe', 'file.vst', 'file.vhdl_123'], + \ 'vhdl': ['file.hdl', 'file.vhd', 'file.vhdl', 'file.vbe', 'file.vst', 'file.vhdl_123', 'file.vho'], \ 'vim': ['file.vim', 'file.vba', '.exrc', '_exrc'], \ 'viminfo': ['.viminfo', '_viminfo'], \ 'vmasm': ['file.mar'], @@ -599,6 +602,7 @@ let s:script_checks = { \ 'haskell': [['#!/path/haskell']], \ 'cpp': [['// Standard iostream objects -*- C++ -*-'], \ ['// -*- C++ -*-']], + \ 'yaml': [['%YAML 1.2']], \ } func Test_script_detection() diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim index 56ed543d4b..692f6e4780 100644 --- a/src/nvim/testdir/test_fold.vim +++ b/src/nvim/testdir/test_fold.vim @@ -1,6 +1,7 @@ " Test for folding source view_util.vim +source screendump.vim func PrepIndent(arg) return [a:arg] + repeat(["\t".a:arg], 5) diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 7822507f86..51689db9c4 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -186,6 +186,32 @@ func Test_strftime() call assert_fails('call strftime([])', 'E730:') call assert_fails('call strftime("%Y", [])', 'E745:') + + " Check that the time changes after we change the timezone + " Save previous timezone value, if any + if exists('$TZ') + let tz = $TZ + endif + + " Force EST and then UTC, save the current hour (24-hour clock) for each + let $TZ = 'EST' | let est = strftime('%H') + let $TZ = 'UTC' | let utc = strftime('%H') + + " Those hours should be two bytes long, and should not be the same; if they + " are, a tzset(3) call may have failed somewhere + call assert_equal(strlen(est), 2) + call assert_equal(strlen(utc), 2) + " TODO: this fails on MS-Windows + if has('unix') + call assert_notequal(est, utc) + endif + + " If we cached a timezone value, put it back, otherwise clear it + if exists('tz') + let $TZ = tz + else + unlet $TZ + endif endfunc func Test_resolve() @@ -640,6 +666,16 @@ func Test_getbufvar() call assert_equal('iso-8859-2', getbufvar(bufnr('%'), '&fenc')) close + " Get the b: dict. + let b:testvar = 'one' + new + let b:testvar = 'two' + let thebuf = bufnr() + wincmd w + call assert_equal('two', getbufvar(thebuf, 'testvar')) + call assert_equal('two', getbufvar(thebuf, '').testvar) + bwipe! + set fileformats& endfunc @@ -1271,3 +1307,33 @@ func Test_bufadd_bufload() bwipe otherName call assert_equal(0, bufexists('someName')) endfunc + +func Test_readdir() + call mkdir('Xdir') + call writefile([], 'Xdir/foo.txt') + call writefile([], 'Xdir/bar.txt') + call mkdir('Xdir/dir') + + " All results + let files = readdir('Xdir') + call assert_equal(['bar.txt', 'dir', 'foo.txt'], sort(files)) + + " Only results containing "f" + let files = readdir('Xdir', { x -> stridx(x, 'f') !=- 1 }) + call assert_equal(['foo.txt'], sort(files)) + + " Only .txt files + let files = readdir('Xdir', { x -> x =~ '.txt$' }) + call assert_equal(['bar.txt', 'foo.txt'], sort(files)) + + " Only .txt files with string + let files = readdir('Xdir', 'v:val =~ ".txt$"') + call assert_equal(['bar.txt', 'foo.txt'], sort(files)) + + " Limit to 1 result. + let l = [] + let files = readdir('Xdir', {x -> len(add(l, x)) == 2 ? -1 : 1}) + call assert_equal(1, len(files)) + + call delete('Xdir', 'rf') +endfunc diff --git a/src/nvim/testdir/test_hardcopy.vim b/src/nvim/testdir/test_hardcopy.vim index ced13b107c..6125f9b993 100644 --- a/src/nvim/testdir/test_hardcopy.vim +++ b/src/nvim/testdir/test_hardcopy.vim @@ -1,39 +1,137 @@ " Test :hardcopy -func Test_printoptions_parsing() - " Only test that this doesn't throw an error. - set printoptions=left:5in,right:10pt,top:8mm,bottom:2pc - set printoptions=left:2in,top:30pt,right:16mm,bottom:3pc - set printoptions=header:3,syntax:y,number:7,wrap:n - set printoptions=duplex:short,collate:n,jobsplit:y,portrait:n - set printoptions=paper:10x14 - set printoptions=paper:A3 - set printoptions=paper:A4 - set printoptions=paper:A5 - set printoptions=paper:B4 - set printoptions=paper:B5 - set printoptions=paper:executive - set printoptions=paper:folio - set printoptions=paper:ledger - set printoptions=paper:legal - set printoptions=paper:letter - set printoptions=paper:quarto - set printoptions=paper:statement - set printoptions=paper:tabloid - set printoptions=formfeed:y - set printoptions= - set printoptions& +func Test_printoptions() + edit test_hardcopy.vim + syn on + + for opt in ['left:5in,right:10pt,top:8mm,bottom:2pc', + \ 'left:2in,top:30pt,right:16mm,bottom:3pc', + \ 'header:3,syntax:y,number:y,wrap:n', + \ 'header:3,syntax:n,number:y,wrap:y', + \ 'duplex:short,collate:n,jobsplit:y,portrait:n', + \ 'duplex:long,collate:y,jobsplit:n,portrait:y', + \ 'paper:10x14', + \ 'paper:A3', + \ 'paper:A4', + \ 'paper:A5', + \ 'paper:B4', + \ 'paper:B5', + \ 'paper:executive', + \ 'paper:folio', + \ 'paper:ledger', + \ 'paper:legal', + \ 'paper:letter', + \ 'paper:quarto', + \ 'paper:statement', + \ 'paper:tabloid', + \ 'formfeed:y', + \ ''] + exe 'set printoptions=' .. opt + if has('postscript') + hardcopy > Xhardcopy_printoptions + let lines = readfile('Xhardcopy_printoptions') + call assert_true(len(lines) > 20, opt) + call assert_true(lines[0] =~ 'PS-Adobe', opt) + call delete('Xhardcopy_printoptions') + endif + endfor call assert_fails('set printoptions=paper', 'E550:') call assert_fails('set printoptions=shredder:on', 'E551:') call assert_fails('set printoptions=left:no', 'E552:') + set printoptions& + bwipe endfunc -func Test_printmbfont_parsing() - " Only test that this doesn't throw an error. - set printmbfont=r:WadaMin-Regular,b:WadaMin-Bold,i:WadaMin-Italic,o:WadaMin-Bold-Italic,c:yes,a:no - set printmbfont= +func Test_printmbfont() + " Print a small help page which contains tabs to cover code that expands tabs to spaces. + help help + syn on + + for opt in [':WadaMin-Regular,b:WadaMin-Bold,i:WadaMin-Italic,o:WadaMin-Bold-Italic,c:yes,a:no', + \ ''] + exe 'set printmbfont=' .. opt + if has('postscript') + hardcopy > Xhardcopy_printmbfont + let lines = readfile('Xhardcopy_printmbfont') + call assert_true(len(lines) > 20, opt) + call assert_true(lines[0] =~ 'PS-Adobe', opt) + call delete('Xhardcopy_printmbfont') + endif + endfor set printmbfont& + bwipe +endfunc + +func Test_printexpr() + if !has('unix') + return + endif + + " Not a very useful printexpr value, but enough to test + " hardcopy with 'printexpr'. + function PrintFile(fname) + call writefile(['Test printexpr: ' .. v:cmdarg], + \ 'Xhardcopy_printexpr') + call delete(a:fname) + return 0 + endfunc + set printexpr=PrintFile(v:fname_in) + + help help + hardcopy dummy args + call assert_equal(['Test printexpr: dummy args'], + \ readfile('Xhardcopy_printexpr')) + call delete('Xhardcopy_printexpr') + + " Function return 1 to test print failure. + function PrintFails(fname) + call delete(a:fname) + return 1 + endfunc + set printexpr=PrintFails(v:fname_in) + call assert_fails('hardcopy', 'E365:') + + set printexpr& + bwipe +endfunc + +func Test_errors() + " FIXME: Windows fails differently than Unix. + if has('unix') + edit test_hardcopy.vim + call assert_fails('hardcopy >', 'E324:') + bwipe + endif +endfunc + +func Test_dark_background() + edit test_hardcopy.vim + syn on + + for bg in ['dark', 'light'] + exe 'set background=' .. bg + + if has('postscript') + hardcopy > Xhardcopy_dark_background + let lines = readfile('Xhardcopy_dark_background') + call assert_true(len(lines) > 20) + call assert_true(lines[0] =~ 'PS-Adobe') + call delete('Xhardcopy_dark_background') + endif + endfor + + set background& + bwipe +endfun + +func Test_empty_buffer() + " FIXME: Unclear why this fails on Windows. + if has('unix') + new + call assert_equal("\nNo text to be printed", execute('hardcopy')) + bwipe + endif endfunc func Test_printheader_parsing() @@ -46,22 +144,6 @@ func Test_printheader_parsing() set printheader& endfunc -" Test that :hardcopy produces a non-empty file. -" We don't check much of the contents. -func Test_with_syntax() - if has('postscript') - edit test_hardcopy.vim - set printoptions=syntax:y - syn on - hardcopy > Xhardcopy - let lines = readfile('Xhardcopy') - call assert_true(len(lines) > 20) - call assert_true(lines[0] =~ 'PS-Adobe') - call delete('Xhardcopy') - set printoptions& - endif -endfunc - func Test_fname_with_spaces() if !has('postscript') return @@ -86,4 +168,3 @@ func Test_illegal_byte() bwipe! call delete('Xpstest') endfunc - diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim index a320e8edc8..6aa187b17e 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -1,6 +1,7 @@ " Tests for ":highlight" and highlighting. source view_util.vim +source screendump.vim func Test_highlight() " basic test if ":highlight" doesn't crash diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index e6d427db05..1c275d5bd1 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -120,7 +120,7 @@ function! s:CompleteDone_CompleteFuncDict( findstart, base ) \ 'menu': 'extra text', \ 'info': 'words are cool', \ 'kind': 'W', - \ 'user_data': 'test' + \ 'user_data': ['one', 'two'] \ } \ ] \ } @@ -130,18 +130,20 @@ func s:CompleteDone_CheckCompletedItemNone() let s:called_completedone = 1 endfunc -function! s:CompleteDone_CheckCompletedItemDict() +func s:CompleteDone_CheckCompletedItemDict(pre) call assert_equal( 'aword', v:completed_item[ 'word' ] ) call assert_equal( 'wrd', v:completed_item[ 'abbr' ] ) call assert_equal( 'extra text', v:completed_item[ 'menu' ] ) call assert_equal( 'words are cool', v:completed_item[ 'info' ] ) call assert_equal( 'W', v:completed_item[ 'kind' ] ) - call assert_equal( 'test', v:completed_item[ 'user_data' ] ) + call assert_equal( ['one', 'two'], v:completed_item[ 'user_data' ] ) - call assert_equal('function', complete_info().mode) + if a:pre + call assert_equal('function', complete_info().mode) + endif let s:called_completedone = 1 -endfunction +endfunc func Test_CompleteDoneNone() throw 'skipped: Nvim does not support v:none' @@ -161,13 +163,14 @@ func Test_CompleteDoneNone() endfunc func Test_CompleteDoneDict() - au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemDict() + au CompleteDonePre * :call <SID>CompleteDone_CheckCompletedItemDict(1) + au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemDict(0) set completefunc=<SID>CompleteDone_CompleteFuncDict execute "normal a\<C-X>\<C-U>\<C-Y>" set completefunc& - call assert_equal('test', v:completed_item[ 'user_data' ]) + call assert_equal(['one', 'two'], v:completed_item[ 'user_data' ]) call assert_true(s:called_completedone) let s:called_completedone = 0 diff --git a/src/nvim/testdir/test_listchars.vim b/src/nvim/testdir/test_listchars.vim index 57cfaa298e..dcc588120c 100644 --- a/src/nvim/testdir/test_listchars.vim +++ b/src/nvim/testdir/test_listchars.vim @@ -58,6 +58,26 @@ func Test_listchars() call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) endfor + " tab with 3rd character and linebreak set + set listchars-=tab:<=> + set listchars+=tab:<·> + set linebreak + let expected = [ + \ '<······>aa<····>$', + \ '..bb<··>--$', + \ '...cccc>-$', + \ 'dd........ee--<>$', + \ '-$' + \ ] + redraw! + for i in range(1, 5) + call cursor(i, 1) + call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) + endfor + set nolinebreak + set listchars-=tab:<·> + set listchars+=tab:<=> + set listchars-=trail:- let expected = [ \ '<======>aa<====>$', diff --git a/src/nvim/testdir/test_maparg.vim b/src/nvim/testdir/test_maparg.vim index 0b941d51ec..5f73bd80ad 100644 --- a/src/nvim/testdir/test_maparg.vim +++ b/src/nvim/testdir/test_maparg.vim @@ -41,6 +41,11 @@ function Test_maparg() map abc y<S-char-114>y call assert_equal("yRy", maparg('abc')) + omap { w + let d = maparg('{', 'o', 0, 1) + call assert_equal(['{', 'w', 'o'], [d.lhs, d.rhs, d.mode]) + ounmap { + map abc <Nop> call assert_equal("<Nop>", maparg('abc')) unmap abc @@ -62,3 +67,5 @@ function Test_range_map() execute "normal a\uf040\<Esc>" call assert_equal("abcd", getline(1)) endfunction + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim index f14f292a92..82562339f6 100644 --- a/src/nvim/testdir/test_mapping.vim +++ b/src/nvim/testdir/test_mapping.vim @@ -390,3 +390,77 @@ func Test_motionforce_omap() delfunc Select delfunc GetCommand endfunc + +" Test for mapping errors +func Test_map_error() + call assert_fails('unmap', 'E474:') + call assert_fails("exe 'map ' .. repeat('a', 51) .. ' :ls'", 'E474:') + call assert_fails('unmap abc', 'E31:') + call assert_fails('unabbr abc', 'E24:') + call assert_equal('', maparg('')) + call assert_fails('echo maparg("abc", [])', 'E730:') + + " unique map + map ,w /[#&!]<CR> + call assert_fails("map <unique> ,w /[#&!]<CR>", 'E227:') + " unique buffer-local map + call assert_fails("map <buffer> <unique> ,w /[.,;]<CR>", 'E225:') + unmap ,w + + " unique abbreviation + abbr SP special + call assert_fails("abbr <unique> SP special", 'E226:') + " unique buffer-local map + call assert_fails("abbr <buffer> <unique> SP special", 'E224:') + unabbr SP + + call assert_fails('mapclear abc', 'E474:') + call assert_fails('abclear abc', 'E474:') +endfunc + +" Test for <special> key mapping +func Test_map_special() + throw 'skipped: Nvim does not support cpoptions flag "<"' + new + let old_cpo = &cpo + set cpo+=< + imap <F12> Blue + call feedkeys("i\<F12>", "x") + call assert_equal("<F12>", getline(1)) + call feedkeys("ddi<F12>", "x") + call assert_equal("Blue", getline(1)) + iunmap <F12> + imap <special> <F12> Green + call feedkeys("ddi\<F12>", "x") + call assert_equal("Green", getline(1)) + call feedkeys("ddi<F12>", "x") + call assert_equal("<F12>", getline(1)) + iunmap <special> <F12> + let &cpo = old_cpo + %bwipe! +endfunc + +" Test for hasmapto() +func Test_hasmapto() + call assert_equal(0, hasmapto('/^\k\+ (')) + call assert_equal(0, hasmapto('/^\k\+ (', 'n')) + nmap ,f /^\k\+ (<CR> + call assert_equal(1, hasmapto('/^\k\+ (')) + call assert_equal(1, hasmapto('/^\k\+ (', 'n')) + call assert_equal(0, hasmapto('/^\k\+ (', 'v')) + + call assert_equal(0, hasmapto('/^\k\+ (', 'n', 1)) +endfunc + +" Test for command-line completion of maps +func Test_mapcomplete() + call assert_equal(['<buffer>', '<expr>', '<nowait>', '<script>', + \ '<silent>', '<special>', '<unique>'], + \ getcompletion('', 'mapping')) + call assert_equal([], getcompletion(',d', 'mapping')) + + call feedkeys(":abbr! \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_match("abbr! \x01", @:) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim index 265dee66ce..7fbf04311d 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -1,4 +1,4 @@ -" Tests for :messages +" Tests for :messages, :echomsg, :echoerr function Test_messages() let oldmore = &more @@ -6,6 +6,9 @@ function Test_messages() set nomore " Avoid the "message maintainer" line. let $LANG = '' + let $LC_ALL = '' + let $LC_MESSAGES = '' + let $LC_COLLATE = '' let arr = map(range(10), '"hello" . v:val') for s in arr @@ -65,6 +68,35 @@ func Test_message_completion() call assert_equal('"message clear', @:) endfunc +func Test_echomsg() + call assert_equal("\nhello", execute(':echomsg "hello"')) + call assert_equal("\n", execute(':echomsg ""')) + call assert_equal("\n12345", execute(':echomsg 12345')) + call assert_equal("\n[]", execute(':echomsg []')) + call assert_equal("\n[1, 2, 3]", execute(':echomsg [1, 2, 3]')) + call assert_equal("\n{}", execute(':echomsg {}')) + call assert_equal("\n{'a': 1, 'b': 2}", execute(':echomsg {"a": 1, "b": 2}')) + if has('float') + call assert_equal("\n1.23", execute(':echomsg 1.23')) + endif + call assert_match("function('<lambda>\\d*')", execute(':echomsg {-> 1234}')) +endfunc + +func Test_echoerr() + throw 'skipped: Nvim does not support test_ignore_error()' + call test_ignore_error('IgNoRe') + call assert_equal("\nIgNoRe hello", execute(':echoerr "IgNoRe hello"')) + call assert_equal("\n12345 IgNoRe", execute(':echoerr 12345 "IgNoRe"')) + call assert_equal("\n[1, 2, 'IgNoRe']", execute(':echoerr [1, 2, "IgNoRe"]')) + call assert_equal("\n{'IgNoRe': 2, 'a': 1}", execute(':echoerr {"a": 1, "IgNoRe": 2}')) + if has('float') + call assert_equal("\n1.23 IgNoRe", execute(':echoerr 1.23 "IgNoRe"')) + endif + call test_ignore_error('<lambda>') + call assert_match("function('<lambda>\\d*')", execute(':echoerr {-> 1234}')) + call test_ignore_error('RESET') +endfunc + func Test_echospace() set noruler noshowcmd laststatus=1 call assert_equal(&columns - 1, v:echospace) diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index b7169444d1..9c9e04be07 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -2,10 +2,13 @@ scriptencoding latin1 -if !has('multi_byte') || !has('mksession') +if !has('mksession') finish endif +source shared.vim +source term_util.vim + func Test_mksession() tabnew let wrap_save = &wrap @@ -122,6 +125,34 @@ func Test_mksession_large_winheight() call delete('Xtest_mks_winheight.out') endfunc +func Test_mksession_rtp() + if has('win32') + " TODO: fix problem with backslashes + return + endif + new + set sessionoptions+=options + let _rtp=&rtp + " Make a real long (invalid) runtimepath value, + " that should exceed PATH_MAX (hopefully) + let newrtp=&rtp.',~'.repeat('/foobar', 1000) + let newrtp.=",".expand("$HOME")."/.vim" + let &rtp=newrtp + + " determine expected value + let expected=split(&rtp, ',') + let expected = map(expected, '"set runtimepath+=".v:val') + let expected = ['set runtimepath='] + expected + let expected = map(expected, {v,w -> substitute(w, $HOME, "~", "g")}) + + mksession! Xtest_mks.out + let &rtp=_rtp + let li = filter(readfile('Xtest_mks.out'), 'v:val =~# "runtimepath"') + call assert_equal(expected, li) + + call delete('Xtest_mks.out') +endfunc + " Verify that arglist is stored correctly to the session file. func Test_mksession_arglist() argdel * diff --git a/src/nvim/testdir/test_number.vim b/src/nvim/testdir/test_number.vim index 59debcea0d..3c9afc41d5 100644 --- a/src/nvim/testdir/test_number.vim +++ b/src/nvim/testdir/test_number.vim @@ -252,3 +252,14 @@ func Test_numberwidth_adjusted() call s:compare_lines(expect, lines) call s:close_windows() endfunc + +" This was causing a memcheck error +func Test_relativenumber_uninitialised() + new + set rnu + call setline(1, ["a", "b"]) + redraw + call feedkeys("j", 'xt') + redraw + bwipe! +endfunc diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 6fcc372591..41f1710faf 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -277,6 +277,21 @@ func Test_set_errors() call assert_fails('set t_foo=', 'E846:') endfunc +" Must be executed before other tests that set 'term'. +func Test_000_term_option_verbose() + if has('nvim') || has('gui_running') + return + endif + let verb_cm = execute('verbose set t_cm') + call assert_notmatch('Last set from', verb_cm) + + let term_save = &term + set term=ansi + let verb_cm = execute('verbose set t_cm') + call assert_match('Last set from.*test_options.vim', verb_cm) + let &term = term_save +endfunc + func Test_set_ttytype() " Nvim does not support 'ttytype'. if !has('nvim') && !has('gui_running') && has('unix') @@ -496,3 +511,46 @@ func Test_shortmess_F2() bwipe bwipe endfunc + +func Test_local_scrolloff() + set so=5 + set siso=7 + split + call assert_equal(5, &so) + setlocal so=3 + call assert_equal(3, &so) + wincmd w + call assert_equal(5, &so) + wincmd w + setlocal so< + call assert_equal(5, &so) + setlocal so=0 + call assert_equal(0, &so) + setlocal so=-1 + call assert_equal(5, &so) + + call assert_equal(7, &siso) + setlocal siso=3 + call assert_equal(3, &siso) + wincmd w + call assert_equal(7, &siso) + wincmd w + setlocal siso< + call assert_equal(7, &siso) + setlocal siso=0 + call assert_equal(0, &siso) + setlocal siso=-1 + call assert_equal(7, &siso) + + close + set so& + set siso& +endfunc + +func Test_visualbell() + set belloff= + set visualbell + call assert_beeps('normal 0h') + set novisualbell + set belloff=all +endfunc diff --git a/src/nvim/testdir/test_plus_arg_edit.vim b/src/nvim/testdir/test_plus_arg_edit.vim index f6d31e7626..e91a6e467a 100644 --- a/src/nvim/testdir/test_plus_arg_edit.vim +++ b/src/nvim/testdir/test_plus_arg_edit.vim @@ -8,3 +8,31 @@ function Test_edit() call delete('Xfile1') call delete('Xfile2') endfunction + +func Test_edit_bad() + if !has('multi_byte') + finish + endif + + " Test loading a utf8 file with bad utf8 sequences. + call writefile(["[\xff][\xc0][\xe2\x89\xf0][\xc2\xc2]"], "Xfile") + new + + " Without ++bad=..., the default behavior is like ++bad=? + e! ++enc=utf8 Xfile + call assert_equal('[?][?][???][??]', getline(1)) + + e! ++enc=utf8 ++bad=_ Xfile + call assert_equal('[_][_][___][__]', getline(1)) + + e! ++enc=utf8 ++bad=drop Xfile + call assert_equal('[][][][]', getline(1)) + + e! ++enc=utf8 ++bad=keep Xfile + call assert_equal("[\xff][\xc0][\xe2\x89\xf0][\xc2\xc2]", getline(1)) + + call assert_fails('e! ++enc=utf8 ++bad=foo Xfile', 'E474:') + + bw! + call delete('Xfile') +endfunc diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index e5696f4cbb..bb0ed6e00c 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -979,9 +979,9 @@ func Test_CompleteChanged() call cursor(4, 1) call feedkeys("Sf\<C-N>", 'tx') - call assert_equal({'completed_item': {}, 'width': 15, - \ 'height': 2, 'size': 2, - \ 'col': 0, 'row': 4, 'scrollbar': v:false}, g:event) + call assert_equal({'completed_item': {}, 'width': 15.0, + \ 'height': 2.0, 'size': 2, + \ 'col': 0.0, 'row': 4.0, 'scrollbar': v:false}, g:event) call feedkeys("a\<C-N>\<C-N>\<C-E>", 'tx') call assert_equal('foo', g:word) call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-E>", 'tx') @@ -1009,10 +1009,10 @@ func Test_pum_getpos() setlocal completefunc=UserDefinedComplete let d = { - \ 'height': 5, - \ 'width': 15, - \ 'row': 1, - \ 'col': 0, + \ 'height': 5.0, + \ 'width': 15.0, + \ 'row': 1.0, + \ 'col': 0.0, \ 'size': 5, \ 'scrollbar': v:false, \ } diff --git a/src/nvim/testdir/test_profile.vim b/src/nvim/testdir/test_profile.vim index f3eb88abf0..4b0097617e 100644 --- a/src/nvim/testdir/test_profile.vim +++ b/src/nvim/testdir/test_profile.vim @@ -16,6 +16,7 @@ func Test_profile_func() while l:count > 0 let l:count = l:count - 1 endwhile + sleep 1m endfunc func! Foo3() endfunc @@ -51,7 +52,7 @@ func Test_profile_func() " - Unlike Foo3(), Foo2() should not be deleted since there is a check " for v:profiling. " - Bar() is not reported since it does not match "profile func Foo*". - call assert_equal(30, len(lines)) + call assert_equal(31, len(lines)) call assert_equal('FUNCTION Foo1()', lines[0]) call assert_match('Defined:.*Xprofile_func.vim:3', lines[1]) @@ -71,17 +72,18 @@ func Test_profile_func() call assert_match('^\s*101\s\+.*\swhile l:count > 0$', lines[16]) call assert_match('^\s*100\s\+.*\s let l:count = l:count - 1$', lines[17]) call assert_match('^\s*101\s\+.*\sendwhile$', lines[18]) - call assert_equal('', lines[19]) - call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[20]) - call assert_equal('count total (s) self (s) function', lines[21]) - call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo2()$', lines[22]) - call assert_match('^\s*2\s\+\d\+\.\d\+\s\+Foo1()$', lines[23]) - call assert_equal('', lines[24]) - call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[25]) - call assert_equal('count total (s) self (s) function', lines[26]) - call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo2()$', lines[27]) - call assert_match('^\s*2\s\+\d\+\.\d\+\s\+Foo1()$', lines[28]) - call assert_equal('', lines[29]) + call assert_match('^\s*1\s\+.\+sleep 1m$', lines[19]) + call assert_equal('', lines[20]) + call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[21]) + call assert_equal('count total (s) self (s) function', lines[22]) + call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo2()$', lines[23]) + call assert_match('^\s*2\s\+\d\+\.\d\+\s\+Foo1()$', lines[24]) + call assert_equal('', lines[25]) + call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[26]) + call assert_equal('count total (s) self (s) function', lines[27]) + call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo2()$', lines[28]) + call assert_match('^\s*2\s\+\d\+\.\d\+\s\+Foo1()$', lines[29]) + call assert_equal('', lines[30]) call delete('Xprofile_func.vim') call delete('Xprofile_func.log') diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index d7b387c2c9..35555ca9d3 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1510,6 +1510,13 @@ func Test_setqflist_invalid_nr() call setqflist([], ' ', {'nr' : $XXX_DOES_NOT_EXIST}) endfunc +func Test_setqflist_user_sets_buftype() + call setqflist([{'text': 'foo'}, {'text': 'bar'}]) + set buftype=quickfix + call setqflist([], 'a') + enew +endfunc + func Test_quickfix_set_list_with_act() call XquickfixSetListWithAct('c') call XquickfixSetListWithAct('l') @@ -3311,6 +3318,14 @@ func Test_lvimgrep_crash() enew | only endfunc +func Test_lvimgrep_crash2() + au BufNewFile x sfind + call assert_fails('lvimgrep x x', 'E480:') + call assert_fails('lvimgrep x x x', 'E480:') + + au! BufNewFile +endfunc + " Test for the position of the quickfix and location list window func Test_qfwin_pos() " Open two windows diff --git a/src/nvim/testdir/test_quotestar.vim b/src/nvim/testdir/test_quotestar.vim index ce5a9ee827..77a5153a81 100644 --- a/src/nvim/testdir/test_quotestar.vim +++ b/src/nvim/testdir/test_quotestar.vim @@ -108,7 +108,8 @@ func Do_test_quotestar_for_x11() call remote_send(name, ":gui -f\<CR>") endif " Wait for the server in the GUI to be up and answering requests. - call WaitForAssert({-> assert_match("1", remote_expr(name, "has('gui_running')", "", 1))}) + " On some systems and with valgrind this can be very slow. + call WaitForAssert({-> assert_match("1", remote_expr(name, "has('gui_running')", "", 1))}, 10000) call remote_send(name, ":let @* = 'maybe'\<CR>") call WaitForAssert({-> assert_equal("maybe", remote_expr(name, "@*", "", 2))}) diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim index e06c7d6368..ecd0e8d56b 100644 --- a/src/nvim/testdir/test_regexp_utf8.vim +++ b/src/nvim/testdir/test_regexp_utf8.vim @@ -192,3 +192,144 @@ func Test_optmatch_toolong() set re=0 endfunc +" Test for regexp patterns with multi-byte support, using utf-8. +func Test_multibyte_chars() + " tl is a List of Lists with: + " 2: test auto/old/new 0: test auto/old 1: test auto/new + " regexp pattern + " text to test the pattern on + " expected match (optional) + " expected submatch 1 (optional) + " expected submatch 2 (optional) + " etc. + " When there is no match use only the first two items. + let tl = [] + + " Multi-byte character tests. These will fail unless vim is compiled + " with Multibyte (FEAT_MBYTE) or BIG/HUGE features. + call add(tl, [2, '[[:alpha:][=a=]]\+', '879 aiaãâaiuvna ', 'aiaãâaiuvna']) + call add(tl, [2, '[[=a=]]\+', 'ddaãâbcd', 'aãâ']) " equivalence classes + call add(tl, [2, '[^ม ]\+', 'มม oijasoifjos ifjoisj f osij j มมมมม abcd', 'oijasoifjos']) + call add(tl, [2, ' [^ ]\+', 'start มabcdม ', ' มabcdม']) + call add(tl, [2, '[ม[:alpha:][=a=]]\+', '879 aiaãมâมaiuvna ', 'aiaãมâมaiuvna']) + + " this is not a normal "i" but 0xec + call add(tl, [2, '\p\+', 'ìa', 'ìa']) + call add(tl, [2, '\p*', 'aã‚', 'aã‚']) + + " Test recognition of some character classes + call add(tl, [2, '\i\+', '&*¨xx ', 'xx']) + call add(tl, [2, '\f\+', '&*Ÿfname ', 'fname']) + + " Test composing character matching + call add(tl, [2, '.ม', 'xม่x yมy', 'yม']) + call add(tl, [2, '.ม่', 'xม่x yมy', 'xม่']) + call add(tl, [2, "\u05b9", " x\u05b9 ", "x\u05b9"]) + call add(tl, [2, ".\u05b9", " x\u05b9 ", "x\u05b9"]) + call add(tl, [2, "\u05b9\u05bb", " x\u05b9\u05bb ", "x\u05b9\u05bb"]) + call add(tl, [2, ".\u05b9\u05bb", " x\u05b9\u05bb ", "x\u05b9\u05bb"]) + call add(tl, [2, "\u05bb\u05b9", " x\u05b9\u05bb ", "x\u05b9\u05bb"]) + call add(tl, [2, ".\u05bb\u05b9", " x\u05b9\u05bb ", "x\u05b9\u05bb"]) + call add(tl, [2, "\u05b9", " y\u05bb x\u05b9 ", "x\u05b9"]) + call add(tl, [2, ".\u05b9", " y\u05bb x\u05b9 ", "x\u05b9"]) + call add(tl, [2, "\u05b9", " y\u05bb\u05b9 x\u05b9 ", "y\u05bb\u05b9"]) + call add(tl, [2, ".\u05b9", " y\u05bb\u05b9 x\u05b9 ", "y\u05bb\u05b9"]) + call add(tl, [1, "\u05b9\u05bb", " y\u05b9 x\u05b9\u05bb ", "x\u05b9\u05bb"]) + call add(tl, [2, ".\u05b9\u05bb", " y\u05bb x\u05b9\u05bb ", "x\u05b9\u05bb"]) + call add(tl, [2, "a", "ca\u0300t"]) + call add(tl, [2, "ca", "ca\u0300t"]) + call add(tl, [2, "a\u0300", "ca\u0300t", "a\u0300"]) + call add(tl, [2, 'a\%C', "ca\u0300t", "a\u0300"]) + call add(tl, [2, 'ca\%C', "ca\u0300t", "ca\u0300"]) + call add(tl, [2, 'ca\%Ct', "ca\u0300t", "ca\u0300t"]) + + " Test \Z + call add(tl, [2, 'ú\Z', 'x']) + call add(tl, [2, 'יהוה\Z', 'יהוה', 'יהוה']) + call add(tl, [2, 'יְהוָה\Z', 'יהוה', 'יהוה']) + call add(tl, [2, 'יהוה\Z', 'יְהוָה', 'יְהוָה']) + call add(tl, [2, 'יְהוָה\Z', 'יְהוָה', 'יְהוָה']) + call add(tl, [2, '×™Ö°\Z', 'וְיַ', '×™Ö·']) + call add(tl, [2, "×§\u200d\u05b9x\\Z", "x×§\u200d\u05b9xy", "×§\u200d\u05b9x"]) + call add(tl, [2, "×§\u200d\u05b9x\\Z", "x×§\u200dxy", "×§\u200dx"]) + call add(tl, [2, "×§\u200dx\\Z", "x×§\u200d\u05b9xy", "×§\u200d\u05b9x"]) + call add(tl, [2, "×§\u200dx\\Z", "x×§\u200dxy", "×§\u200dx"]) + call add(tl, [2, "\u05b9\\Z", "xyz"]) + call add(tl, [2, "\\Z\u05b9", "xyz"]) + call add(tl, [2, "\u05b9\\Z", "xy\u05b9z", "y\u05b9"]) + call add(tl, [2, "\\Z\u05b9", "xy\u05b9z", "y\u05b9"]) + call add(tl, [1, "\u05b9\\+\\Z", "xy\u05b9z\u05b9 ", "y\u05b9z\u05b9"]) + call add(tl, [1, "\\Z\u05b9\\+", "xy\u05b9z\u05b9 ", "y\u05b9z\u05b9"]) + + " Combining different tests and features + call add(tl, [2, '[^[=a=]]\+', 'ddaãâbcd', 'dd']) + + " Run the tests + for t in tl + let re = t[0] + let pat = t[1] + let text = t[2] + let matchidx = 3 + for engine in [0, 1, 2] + if engine == 2 && re == 0 || engine == 1 && re == 1 + continue + endif + let ®expengine = engine + try + let l = matchlist(text, pat) + catch + call assert_report('Error ' . engine . ': pat: \"' . pat . + \ '\", text: \"' . text . + \ '\", caused an exception: \"' . v:exception . '\"') + endtry + " check the match itself + if len(l) == 0 && len(t) > matchidx + call assert_report('Error ' . engine . ': pat: \"' . pat . + \ '\", text: \"' . text . + \ '\", did not match, expected: \"' . t[matchidx] . '\"') + elseif len(l) > 0 && len(t) == matchidx + call assert_report('Error ' . engine . ': pat: \"' . pat . + \ '\", text: \"' . text . '\", match: \"' . l[0] . + \ '\", expected no match') + elseif len(t) > matchidx && l[0] != t[matchidx] + call assert_report('Error ' . engine . ': pat: \"' . pat . + \ '\", text: \"' . text . '\", match: \"' . l[0] . + \ '\", expected: \"' . t[matchidx] . '\"') + else + " Test passed + endif + if len(l) > 0 + " check all the nine submatches + for i in range(1, 9) + if len(t) <= matchidx + i + let e = '' + else + let e = t[matchidx + i] + endif + if l[i] != e + call assert_report('Error ' . engine . ': pat: \"' . pat . + \ '\", text: \"' . text . '\", submatch ' . i . + \ ': \"' . l[i] . '\", expected: \"' . e . '\"') + endif + endfor + unlet i + endif + endfor + endfor + set regexpengine& +endfunc + +" check that 'ambiwidth' does not change the meaning of \p +func Test_ambiwidth() + set regexpengine=1 ambiwidth=single + call assert_equal(0, match("\u00EC", '\p')) + set regexpengine=1 ambiwidth=double + call assert_equal(0, match("\u00EC", '\p')) + set regexpengine=2 ambiwidth=single + call assert_equal(0, match("\u00EC", '\p')) + set regexpengine=2 ambiwidth=double + call assert_equal(0, match("\u00EC", '\p')) + set regexpengine& ambiwidth& +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_restricted.vim b/src/nvim/testdir/test_restricted.vim new file mode 100644 index 0000000000..a29f7c33d3 --- /dev/null +++ b/src/nvim/testdir/test_restricted.vim @@ -0,0 +1,103 @@ +" Test for "rvim" or "vim -Z" + +source shared.vim + +"if has('win32') && has('gui') +" " Win32 GUI shows a dialog instead of displaying the error in the last line. +" finish +"endif + +func Test_restricted() + call Run_restricted_test('!ls', 'E145:') +endfunc + +func Run_restricted_test(ex_cmd, error) + let cmd = GetVimCommand('Xrestricted') + if cmd == '' + return + endif + + " Use a VimEnter autocommand to avoid that the error message is displayed in + " a dialog with an OK button. + call writefile([ + \ "func Init()", + \ " silent! " . a:ex_cmd, + \ " call writefile([v:errmsg], 'Xrestrout')", + \ " qa!", + \ "endfunc", + \ "au VimEnter * call Init()", + \ ], 'Xrestricted') + call system(cmd . ' -Z') + call assert_match(a:error, join(readfile('Xrestrout'))) + + call delete('Xrestricted') + call delete('Xrestrout') +endfunc + +func Test_restricted_lua() + if !has('lua') + throw 'Skipped: Lua is not supported' + endif + call Run_restricted_test('lua print("Hello, Vim!")', 'E981:') + call Run_restricted_test('luado return "hello"', 'E981:') + call Run_restricted_test('luafile somefile', 'E981:') + call Run_restricted_test('call luaeval("expression")', 'E145:') +endfunc + +func Test_restricted_mzscheme() + if !has('mzscheme') + throw 'Skipped: MzScheme is not supported' + endif + call Run_restricted_test('mzscheme statement', 'E981:') + call Run_restricted_test('mzfile somefile', 'E981:') + call Run_restricted_test('call mzeval("expression")', 'E145:') +endfunc + +func Test_restricted_perl() + if !has('perl') + throw 'Skipped: Perl is not supported' + endif + " TODO: how to make Safe mode fail? + " call Run_restricted_test('perl system("ls")', 'E981:') + " call Run_restricted_test('perldo system("hello")', 'E981:') + " call Run_restricted_test('perlfile somefile', 'E981:') + " call Run_restricted_test('call perleval("system(\"ls\")")', 'E145:') +endfunc + +func Test_restricted_python() + if !has('python') + throw 'Skipped: Python is not supported' + endif + call Run_restricted_test('python print "hello"', 'E981:') + call Run_restricted_test('pydo return "hello"', 'E981:') + call Run_restricted_test('pyfile somefile', 'E981:') + call Run_restricted_test('call pyeval("expression")', 'E145:') +endfunc + +func Test_restricted_python3() + if !has('python3') + throw 'Skipped: Python3 is not supported' + endif + call Run_restricted_test('py3 print "hello"', 'E981:') + call Run_restricted_test('py3do return "hello"', 'E981:') + call Run_restricted_test('py3file somefile', 'E981:') + call Run_restricted_test('call py3eval("expression")', 'E145:') +endfunc + +func Test_restricted_ruby() + if !has('ruby') + throw 'Skipped: Ruby is not supported' + endif + call Run_restricted_test('ruby print "Hello"', 'E981:') + call Run_restricted_test('rubydo print "Hello"', 'E981:') + call Run_restricted_test('rubyfile somefile', 'E981:') +endfunc + +func Test_restricted_tcl() + if !has('tcl') + throw 'Skipped: Tcl is not supported' + endif + call Run_restricted_test('tcl puts "Hello"', 'E981:') + call Run_restricted_test('tcldo puts "Hello"', 'E981:') + call Run_restricted_test('tclfile somefile', 'E981:') +endfunc diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 5d99027ca5..8036dea29f 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -698,3 +698,9 @@ func Test_search_display_pattern() set norl endif endfunc + +func Test_search_special() + " this was causing illegal memory access and an endless loop + set t_PE= + exe "norm /\x80PS" +endfunc diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim index ef4b227215..8b1927e4f0 100644 --- a/src/nvim/testdir/test_signs.vim +++ b/src/nvim/testdir/test_signs.vim @@ -4,6 +4,8 @@ if !has('signs') finish endif +source screendump.vim + func Test_sign() new call setline(1, ['a', 'b', 'c', 'd']) @@ -210,13 +212,16 @@ func Test_sign_completion() call assert_equal('"sign define Sign linehl=SpellBad SpellCap ' . \ 'SpellLocal SpellRare', @:) - call writefile(['foo'], 'XsignOne') - call writefile(['bar'], 'XsignTwo') + call feedkeys(":sign define Sign texthl=Spell\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign define Sign texthl=SpellBad SpellCap ' . + \ 'SpellLocal SpellRare', @:) + + call writefile(repeat(["Sun is shining"], 30), "XsignOne") + call writefile(repeat(["Sky is blue"], 30), "XsignTwo") call feedkeys(":sign define Sign icon=Xsig\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"sign define Sign icon=XsignOne XsignTwo', @:) - call delete('XsignOne') - call delete('XsignTwo') + " Test for completion of arguments to ':sign undefine' call feedkeys(":sign undefine \<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"sign undefine Sign1 Sign2', @:) @@ -227,17 +232,70 @@ func Test_sign_completion() call feedkeys(":sign place 1 name=\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"sign place 1 name=Sign1 Sign2', @:) + edit XsignOne + sign place 1 name=Sign1 line=5 + sign place 1 name=Sign1 group=g1 line=10 + edit XsignTwo + sign place 1 name=Sign2 group=g2 line=15 + + " Test for completion of group= and file= arguments to ':sign place' + call feedkeys(":sign place 1 name=Sign1 file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign place 1 name=Sign1 file=XsignOne XsignTwo', @:) + call feedkeys(":sign place 1 name=Sign1 group=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign place 1 name=Sign1 group=g1 g2', @:) + + " Test for completion of arguments to 'sign place' without sign identifier + call feedkeys(":sign place \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign place buffer= file= group=', @:) + call feedkeys(":sign place file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign place file=XsignOne XsignTwo', @:) + call feedkeys(":sign place group=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign place group=g1 g2', @:) + call feedkeys(":sign place group=g1 file=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign place group=g1 file=XsignOne XsignTwo', @:) + + " Test for completion of arguments to ':sign unplace' call feedkeys(":sign unplace 1 \<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"sign unplace 1 buffer= file= group=', @:) - + call feedkeys(":sign unplace 1 file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign unplace 1 file=XsignOne XsignTwo', @:) + call feedkeys(":sign unplace 1 group=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign unplace 1 group=g1 g2', @:) + call feedkeys(":sign unplace 1 group=g2 file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign unplace 1 group=g2 file=XsignOne XsignTwo', @:) + + " Test for completion of arguments to ':sign list' call feedkeys(":sign list \<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"sign list Sign1 Sign2', @:) + " Test for completion of arguments to ':sign jump' call feedkeys(":sign jump 1 \<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"sign jump 1 buffer= file= group=', @:) + call feedkeys(":sign jump 1 file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign jump 1 file=XsignOne XsignTwo', @:) + call feedkeys(":sign jump 1 group=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign jump 1 group=g1 g2', @:) + + " Error cases + call feedkeys(":sign here\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign here', @:) + call feedkeys(":sign define Sign here=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"sign define Sign here=\<C-A>", @:) + call feedkeys(":sign place 1 here=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"sign place 1 here=\<C-A>", @:) + call feedkeys(":sign jump 1 here=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"sign jump 1 here=\<C-A>", @:) + call feedkeys(":sign here there\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"sign here there\<C-A>", @:) + call feedkeys(":sign here there=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"sign here there=\<C-A>", @:) + sign unplace * group=* sign undefine Sign1 sign undefine Sign2 + enew + call delete('XsignOne') + call delete('XsignTwo') endfunc func Test_sign_invalid_commands() @@ -1127,6 +1185,319 @@ func Test_sign_priority() \ 'priority' : 10}], \ s[0].signs) + call sign_unplace('*') + + " Three signs on different lines with changing priorities + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 11, 'priority' : 50}) + call sign_place(2, '', 'sign2', 'Xsign', + \ {'lnum' : 12, 'priority' : 60}) + call sign_place(3, '', 'sign3', 'Xsign', + \ {'lnum' : 13, 'priority' : 70}) + call sign_place(2, '', 'sign2', 'Xsign', + \ {'lnum' : 12, 'priority' : 40}) + call sign_place(3, '', 'sign3', 'Xsign', + \ {'lnum' : 13, 'priority' : 30}) + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 11, 'priority' : 50}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11, 'group' : '', + \ 'priority' : 50}, + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 12, 'group' : '', + \ 'priority' : 40}, + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 13, 'group' : '', + \ 'priority' : 30}], + \ s[0].signs) + + call sign_unplace('*') + + " Two signs on the same line with changing priorities + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 20}) + call sign_place(2, '', 'sign2', 'Xsign', + \ {'lnum' : 4, 'priority' : 30}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 30}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}], + \ s[0].signs) + " Change the priority of the last sign to highest + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 40}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 40}, + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 30}], + \ s[0].signs) + " Change the priority of the first sign to lowest + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 25}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 30}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 25}], + \ s[0].signs) + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 45}) + call sign_place(2, '', 'sign2', 'Xsign', + \ {'lnum' : 4, 'priority' : 55}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 55}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 45}], + \ s[0].signs) + + call sign_unplace('*') + + " Three signs on the same line with changing priorities + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 40}) + call sign_place(2, '', 'sign2', 'Xsign', + \ {'lnum' : 4, 'priority' : 30}) + call sign_place(3, '', 'sign3', 'Xsign', + \ {'lnum' : 4, 'priority' : 20}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 40}, + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 30}, + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}], + \ s[0].signs) + + " Change the priority of the middle sign to the highest + call sign_place(2, '', 'sign2', 'Xsign', + \ {'lnum' : 4, 'priority' : 50}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 50}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 40}, + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}], + \ s[0].signs) + + " Change the priority of the middle sign to the lowest + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 15}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 50}, + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 15}], + \ s[0].signs) + + " Change the priority of the last sign to the highest + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 55}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 55}, + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 50}, + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}], + \ s[0].signs) + + " Change the priority of the first sign to the lowest + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 15}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 50}, + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 15}], + \ s[0].signs) + + call sign_unplace('*') + + " Three signs on the same line with changing priorities along with other + " signs + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 2, 'priority' : 10}) + call sign_place(2, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 30}) + call sign_place(3, '', 'sign2', 'Xsign', + \ {'lnum' : 4, 'priority' : 20}) + call sign_place(4, '', 'sign3', 'Xsign', + \ {'lnum' : 4, 'priority' : 25}) + call sign_place(5, '', 'sign2', 'Xsign', + \ {'lnum' : 6, 'priority' : 80}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '', + \ 'priority' : 10}, + \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 30}, + \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 25}, + \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '', + \ 'priority' : 80}], + \ s[0].signs) + + " Change the priority of the first sign to lowest + call sign_place(2, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 15}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '', + \ 'priority' : 10}, + \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 25}, + \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 15}, + \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '', + \ 'priority' : 80}], + \ s[0].signs) + + " Change the priority of the last sign to highest + call sign_place(2, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 30}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '', + \ 'priority' : 10}, + \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 30}, + \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 25}, + \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '', + \ 'priority' : 80}], + \ s[0].signs) + + " Change the priority of the middle sign to lowest + call sign_place(4, '', 'sign3', 'Xsign', + \ {'lnum' : 4, 'priority' : 15}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '', + \ 'priority' : 10}, + \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 30}, + \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 15}, + \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '', + \ 'priority' : 80}], + \ s[0].signs) + + " Change the priority of the middle sign to highest + call sign_place(3, '', 'sign2', 'Xsign', + \ {'lnum' : 4, 'priority' : 35}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '', + \ 'priority' : 10}, + \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 35}, + \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 30}, + \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 15}, + \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '', + \ 'priority' : 80}], + \ s[0].signs) + + call sign_unplace('*') + + " Multiple signs with the same priority on the same line + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 20}) + call sign_place(2, '', 'sign2', 'Xsign', + \ {'lnum' : 4, 'priority' : 20}) + call sign_place(3, '', 'sign3', 'Xsign', + \ {'lnum' : 4, 'priority' : 20}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}], + \ s[0].signs) + " Place the last sign again with the same priority + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 20}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}], + \ s[0].signs) + " Place the first sign again with the same priority + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 4, 'priority' : 20}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}], + \ s[0].signs) + " Place the middle sign again with the same priority + call sign_place(3, '', 'sign3', 'Xsign', + \ {'lnum' : 4, 'priority' : 20}) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}, + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', + \ 'priority' : 20}], + \ s[0].signs) + + call sign_unplace('*') + + " Place multiple signs with same id on a line with different priority + call sign_place(1, '', 'sign1', 'Xsign', + \ {'lnum' : 5, 'priority' : 20}) + call sign_place(1, '', 'sign2', 'Xsign', + \ {'lnum' : 5, 'priority' : 10}) + let s = sign_getplaced('Xsign', {'lnum' : 5}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign2', 'lnum' : 5, 'group' : '', + \ 'priority' : 10}], + \ s[0].signs) + call sign_place(1, '', 'sign2', 'Xsign', + \ {'lnum' : 5, 'priority' : 5}) + let s = sign_getplaced('Xsign', {'lnum' : 5}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign2', 'lnum' : 5, 'group' : '', + \ 'priority' : 5}], + \ s[0].signs) + " Error case call assert_fails("call sign_place(1, 'g1', 'sign1', 'Xsign', \ [])", 'E715:') @@ -1339,3 +1710,35 @@ func Test_sign_jump_func() sign undefine sign1 enew! | only! endfunc + +" Test for correct cursor position after the sign column appears or disappears. +func Test_sign_cursor_position() + if !CanRunVimInTerminal() + throw 'Skipped: cannot make screendumps' + endif + + let lines =<< trim END + call setline(1, [repeat('x', 75), 'mmmm', 'yyyy']) + call cursor(2,1) + sign define s1 texthl=Search text==> + redraw + sign place 10 line=2 name=s1 + END + call writefile(lines, 'XtestSigncolumn') + let buf = RunVimInTerminal('-S XtestSigncolumn', {'rows': 6}) + call VerifyScreenDump(buf, 'Test_sign_cursor_1', {}) + + " Change the sign text + call term_sendkeys(buf, ":sign define s1 text=-)\<CR>") + call VerifyScreenDump(buf, 'Test_sign_cursor_2', {}) + + " update cursor position calculation + call term_sendkeys(buf, "lh") + call term_sendkeys(buf, ":sign unplace 10\<CR>") + call VerifyScreenDump(buf, 'Test_sign_cursor_3', {}) + + + " clean up + call StopVimInTerminal(buf) + call delete('XtestSigncolumn') +endfunc diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index e2016d7927..e5eaa01e92 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -130,20 +130,21 @@ endfunc func Test_spellinfo() throw 'skipped: Nvim does not support enc=latin1' new + let runtime = substitute($VIMRUNTIME, '\\', '/', 'g') set enc=latin1 spell spelllang=en - call assert_match("^\nfile: .*/runtime/spell/en.latin1.spl\n$", execute('spellinfo')) + call assert_match("^\nfile: " .. runtime .. "/spell/en.latin1.spl\n$", execute('spellinfo')) set enc=cp1250 spell spelllang=en - call assert_match("^\nfile: .*/runtime/spell/en.ascii.spl\n$", execute('spellinfo')) + call assert_match("^\nfile: " .. runtime .. "/spell/en.ascii.spl\n$", execute('spellinfo')) set enc=utf-8 spell spelllang=en - call assert_match("^\nfile: .*/runtime/spell/en.utf-8.spl\n$", execute('spellinfo')) + call assert_match("^\nfile: " .. runtime .. "/spell/en.utf-8.spl\n$", execute('spellinfo')) set enc=latin1 spell spelllang=en_us,en_nz call assert_match("^\n" . - \ "file: .*/runtime/spell/en.latin1.spl\n" . - \ "file: .*/runtime/spell/en.latin1.spl\n$", execute('spellinfo')) + \ "file: " .. runtime .. "/spell/en.latin1.spl\n" . + \ "file: " .. runtime .. "/spell/en.latin1.spl\n$", execute('spellinfo')) set spell spelllang= call assert_fails('spellinfo', 'E756:') @@ -151,6 +152,12 @@ func Test_spellinfo() set nospell spelllang=en call assert_fails('spellinfo', 'E756:') + call assert_fails('set spelllang=foo/bar', 'E474:') + call assert_fails('set spelllang=foo\ bar', 'E474:') + call assert_fails("set spelllang=foo\\\nbar", 'E474:') + call assert_fails("set spelllang=foo\\\rbar", 'E474:') + call assert_fails("set spelllang=foo+bar", 'E474:') + set enc& spell& spelllang& bwipe endfunc @@ -386,6 +393,11 @@ func Test_zz_sal_and_addition() call assert_equal("elekwint", SecondSpellWord()) endfunc +func Test_spellfile_value() + set spellfile=Xdir/Xtest.latin1.add + set spellfile=Xdir/Xtest.utf-8.add,Xtest_other.add +endfunc + func Test_region_error() messages clear call writefile(["/regions=usgbnz", "elequint/0"], "Xtest.latin1.add") diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index 15b55d35c1..f03c493275 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -271,7 +271,7 @@ func Test_V_arg() call assert_equal(" verbose=0\n", out) let out = system(GetVimCommand() . ' --clean -es -X -V2 -c "set verbose?" -cq') - " call assert_match("sourcing \"$VIMRUNTIME[\\/]defaults\.vim\"\r\nSearching for \"filetype\.vim\".*\n", out) + " call assert_match("sourcing \"$VIMRUNTIME[\\/]defaults\.vim\"\r\nline \\d\\+: sourcing \"[^\"]*runtime[\\/]filetype\.vim\".*\n", out) call assert_match(" verbose=2\n", out) let out = system(GetVimCommand() . ' --clean -es -X -V15 -c "set verbose?" -cq') diff --git a/src/nvim/testdir/test_startup_utf8.vim b/src/nvim/testdir/test_startup_utf8.vim index b24b0eb5cf..1b3d2184a0 100644 --- a/src/nvim/testdir/test_startup_utf8.vim +++ b/src/nvim/testdir/test_startup_utf8.vim @@ -1,7 +1,7 @@ " Tests for startup using utf-8. source shared.vim -" source screendump.vim +source screendump.vim func Test_read_stdin_utf8() let linesin = ['テスト', '€ÀÈÌÒÙ'] diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index 48ec777ffd..66b6e6c05c 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -369,3 +369,24 @@ func Test_statusline_visual() bwipe! x1 bwipe! x2 endfunc + +func Test_statusline_removed_group() + if !CanRunVimInTerminal() + throw 'Skipped: cannot make screendumps' + endif + + let lines =<< trim END + scriptencoding utf-8 + set laststatus=2 + let &statusline = '%#StatColorHi2#%(✓%#StatColorHi2#%) Q≡' + END + call writefile(lines, 'XTest_statusline') + + let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 10, 'cols': 50}) + call term_wait(buf, 100) + call VerifyScreenDump(buf, 'Test_statusline_1', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XTest_statusline') +endfunc diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index e94bd22cea..ff07d8eceb 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -613,6 +613,25 @@ func Test_sub_replace_10() call assert_equal('1aaa', substitute('123', '1\zs\|[23]', 'a', 'g')) endfunc +func SubReplacer(text, submatches) + return a:text .. a:submatches[0] .. a:text +endfunc +func SubReplacer20(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19, submatches) + return a:t3 .. a:submatches[0] .. a:t11 +endfunc + +func Test_substitute_partial() + call assert_equal('1foo2foo3', substitute('123', '2', function('SubReplacer', ['foo']), 'g')) + + " 19 arguments plus one is just OK + let Replacer = function('SubReplacer20', repeat(['foo'], 19)) + call assert_equal('1foo2foo3', substitute('123', '2', Replacer, 'g')) + + " 20 arguments plus one is too many + let Replacer = function('SubReplacer20', repeat(['foo'], 20)) + call assert_fails("call substitute('123', '2', Replacer, 'g')", 'E118') +endfunc + func Test_sub_cmd_9() new let input = ['1 aaa', '2 aaa', '3 aaa'] diff --git a/src/nvim/testdir/test_suspend.vim b/src/nvim/testdir/test_suspend.vim index ef5a96bd72..4b3bd5eadf 100644 --- a/src/nvim/testdir/test_suspend.vim +++ b/src/nvim/testdir/test_suspend.vim @@ -1,6 +1,7 @@ " Test :suspend source shared.vim +source term_util.vim func CheckSuspended(buf, fileExists) call WaitForAssert({-> assert_match('[$#] $', term_getline(a:buf, '.'))}) @@ -55,7 +56,7 @@ func Test_suspend() call term_wait(buf) " Wait until Vim actually exited and shell shows a prompt call WaitForAssert({-> assert_match('[$#] $', term_getline(buf, '.'))}) - call Stop_shell_in_terminal(buf) + call StopShellInTerminal(buf) exe buf . 'bwipe!' call delete('Xfoo') diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim index ce9f7adfef..55dff3d476 100644 --- a/src/nvim/testdir/test_tabpage.vim +++ b/src/nvim/testdir/test_tabpage.vim @@ -1,6 +1,6 @@ " Tests for tabpage -" source screendump.vim +source screendump.vim function Test_tabpage() bw! diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index fe98ef1ae2..5fd71d8bfc 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -340,6 +340,28 @@ func Test_getsettagstack() \ {'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 'a') call assert_equal('abc', gettagstack().items[19].tagname) + " truncate the tag stack + call settagstack(1, + \ {'curidx' : 9, + \ 'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 't') + let t = gettagstack() + call assert_equal(9, t.length) + call assert_equal(10, t.curidx) + + " truncate the tag stack without pushing any new items + call settagstack(1, {'curidx' : 5}, 't') + let t = gettagstack() + call assert_equal(4, t.length) + call assert_equal(5, t.curidx) + + " truncate an empty tag stack and push new items + call settagstack(1, {'items' : []}) + call settagstack(1, + \ {'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 't') + let t = gettagstack() + call assert_equal(1, t.length) + call assert_equal(2, t.curidx) + " Tag with multiple matches call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", \ "two\tXfile1\t1", diff --git a/src/nvim/testdir/test_taglist.vim b/src/nvim/testdir/test_taglist.vim index cb54ace695..d4ff42fd68 100644 --- a/src/nvim/testdir/test_taglist.vim +++ b/src/nvim/testdir/test_taglist.vim @@ -7,6 +7,7 @@ func Test_taglist() \ "BFoo\tXbar\t1", \ "BBar\tXbar\t2", \ "Kindly\tXbar\t3;\"\tv\tfile:", + \ "Lambda\tXbar\t3;\"\tλ\tfile:", \ "Command\tXbar\tcall cursor(3, 4)|;\"\td", \ ], 'Xtags') set tags=Xtags @@ -17,12 +18,16 @@ func Test_taglist() call assert_equal(['FFoo', 'BFoo'], map(taglist("Foo", "Xfoo"), {i, v -> v.name})) call assert_equal(['BFoo', 'FFoo'], map(taglist("Foo", "Xbar"), {i, v -> v.name})) - let kind = taglist("Kindly") - call assert_equal(1, len(kind)) - call assert_equal('v', kind[0]['kind']) - call assert_equal('3', kind[0]['cmd']) - call assert_equal(1, kind[0]['static']) - call assert_equal('Xbar', kind[0]['filename']) + let kindly = taglist("Kindly") + call assert_equal(1, len(kindly)) + call assert_equal('v', kindly[0]['kind']) + call assert_equal('3', kindly[0]['cmd']) + call assert_equal(1, kindly[0]['static']) + call assert_equal('Xbar', kindly[0]['filename']) + + let lambda = taglist("Lambda") + call assert_equal(1, len(lambda)) + call assert_equal('λ', lambda[0]['kind']) let cmd = taglist("Command") call assert_equal(1, len(cmd)) diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim index 13fb50b985..75673adf0a 100644 --- a/src/nvim/testdir/test_textformat.vim +++ b/src/nvim/testdir/test_textformat.vim @@ -489,3 +489,426 @@ func Test_format_list_auto() bwipe! set fo& ai& bs& endfunc + +" Test for formatting multi-byte text with 'fo=t' +func Test_tw_2_fo_t() + new + let t =<< trim END + { + XYZ + abc XYZ + } + END + call setline(1, t) + call cursor(2, 1) + + set tw=2 fo=t + let t =<< trim END + XYZ + abc XYZ + END + exe "normal gqgqjgqgq" + exe "normal o\n" . join(t, "\n") + + let expected =<< trim END + { + XYZ + abc + XYZ + + XYZ + abc + XYZ + } + END + call assert_equal(expected, getline(1, '$')) + + set tw& fo& + bwipe! +endfunc + +" Test for formatting multi-byte text with 'fo=tm' and 'tw=1' +func Test_tw_1_fo_tm() + new + let t =<< trim END + { + X + Xa + X a + XY + X ï¼¹ + } + END + call setline(1, t) + call cursor(2, 1) + + set tw=1 fo=tm + let t =<< trim END + X + Xa + X a + XY + X ï¼¹ + END + exe "normal gqgqjgqgqjgqgqjgqgqjgqgq" + exe "normal o\n" . join(t, "\n") + + let expected =<< trim END + { + X + X + a + X + a + X + ï¼¹ + X + ï¼¹ + + X + X + a + X + a + X + ï¼¹ + X + ï¼¹ + } + END + call assert_equal(expected, getline(1, '$')) + + set tw& fo& + bwipe! +endfunc + +" Test for formatting multi-byte text with 'fo=tm' and 'tw=2' +func Test_tw_2_fo_tm() + new + let t =<< trim END + { + X + Xa + X a + XY + X ï¼¹ + aX + abX + abcX + abX c + abXY + } + END + call setline(1, t) + call cursor(2, 1) + + set tw=2 fo=tm + let t =<< trim END + X + Xa + X a + XY + X ï¼¹ + aX + abX + abcX + abX c + abXY + END + exe "normal gqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgq" + exe "normal o\n" . join(t, "\n") + + let expected =<< trim END + { + X + X + a + X + a + X + ï¼¹ + X + ï¼¹ + a + X + ab + X + abc + X + ab + X + c + ab + X + ï¼¹ + + X + X + a + X + a + X + ï¼¹ + X + ï¼¹ + a + X + ab + X + abc + X + ab + X + c + ab + X + ï¼¹ + } + END + call assert_equal(expected, getline(1, '$')) + + set tw& fo& + bwipe! +endfunc + +" Test for formatting multi-byte text with 'fo=tm', 'tw=2' and 'autoindent'. +func Test_tw_2_fo_tm_ai() + new + let t =<< trim END + { + X + Xa + } + END + call setline(1, t) + call cursor(2, 1) + + set ai tw=2 fo=tm + let t =<< trim END + X + Xa + END + exe "normal gqgqjgqgq" + exe "normal o\n" . join(t, "\n") + + let expected =<< trim END + { + X + X + a + + X + X + a + } + END + call assert_equal(expected, getline(1, '$')) + + set tw& fo& ai& + bwipe! +endfunc + +" Test for formatting multi-byte text with 'fo=tm', 'tw=2' and 'noai'. +func Test_tw_2_fo_tm_noai() + new + let t =<< trim END + { + X + Xa + } + END + call setline(1, t) + call cursor(2, 1) + + set noai tw=2 fo=tm + exe "normal gqgqjgqgqo\n X\n Xa" + + let expected =<< trim END + { + X + X + a + + X + X + a + } + END + call assert_equal(expected, getline(1, '$')) + + set tw& fo& ai& + bwipe! +endfunc + +func Test_tw_2_fo_cqm_com() + new + let t =<< trim END + { + X + Xa + Xaï¼¹ + XY + XYZ + X ï¼¹ + X YZ + XX + XXa + XXY + } + END + call setline(1, t) + call cursor(2, 1) + + set tw=2 fo=cqm comments=n:X + exe "normal gqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgq" + let t =<< trim END + X + Xa + Xaï¼¹ + XY + XYZ + X ï¼¹ + X YZ + XX + XXa + XXY + END + exe "normal o\n" . join(t, "\n") + + let expected =<< trim END + { + X + Xa + Xa + XY + XY + XY + XZ + X ï¼¹ + X ï¼¹ + X Z + XX + XXa + XXY + + X + Xa + Xa + XY + XY + XY + XZ + X ï¼¹ + X ï¼¹ + X Z + XX + XXa + XXY + } + END + call assert_equal(expected, getline(1, '$')) + + set tw& fo& comments& + bwipe! +endfunc + +func Test_tw_2_fo_tm_replace() + new + let t =<< trim END + { + + } + END + call setline(1, t) + call cursor(2, 1) + + set tw=2 fo=tm + exe "normal RXa" + + let expected =<< trim END + { + X + a + } + END + call assert_equal(expected, getline(1, '$')) + + set tw& fo& + bwipe! +endfunc + +" Test for 'matchpairs' with multibyte chars +func Test_mps() + new + let t =<< trim END + { + ‘ two three ’ four + } + END + call setline(1, t) + call cursor(2, 1) + + exe "set mps+=\u2018:\u2019" + normal d% + + let expected =<< trim END + { + four + } + END + call assert_equal(expected, getline(1, '$')) + + set mps& + bwipe! +endfunc + +" Test for ra on multi-byte characters +func Test_ra_multibyte() + new + let t =<< trim END + ra test + ï½bbï½ + ï½ï½b + END + call setline(1, t) + call cursor(1, 1) + + normal jVjra + + let expected =<< trim END + ra test + aaaa + aaa + END + call assert_equal(expected, getline(1, '$')) + + bwipe! +endfunc + +" Test for 'whichwrap' with multi-byte character +func Test_whichwrap_multi_byte() + new + let t =<< trim END + á + x + END + call setline(1, t) + call cursor(2, 1) + + set whichwrap+=h + normal dh + set whichwrap& + + let expected =<< trim END + áx + END + call assert_equal(expected, getline(1, '$')) + + bwipe! +endfunc + +func Test_substitute() + call assert_equal('a1aï¼’a3a', substitute('123', '\zs', 'a', 'g')) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index 3043103270..cffd80ff4f 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -5,7 +5,7 @@ if !has('timers') endif source shared.vim -source screendump.vim +source term_util.vim source load.vim func MyHandler(timer) @@ -339,4 +339,8 @@ func Test_nocatch_garbage_collect() delfunc FeedChar endfunc +func Test_timer_invalid_callback() + call assert_fails('call timer_start(0, "0")', 'E921') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_user_func.vim b/src/nvim/testdir/test_user_func.vim index e7a3701386..67701ee3ca 100644 --- a/src/nvim/testdir/test_user_func.vim +++ b/src/nvim/testdir/test_user_func.vim @@ -94,3 +94,7 @@ func Test_user_func() unlet g:retval g:counter enew! endfunc + +func Test_failed_call_in_try() + try | call UnknownFunc() | catch | endtry +endfunc diff --git a/src/nvim/testdir/test_utf8.vim b/src/nvim/testdir/test_utf8.vim index b1f33f56dd..1b4ce4c4af 100644 --- a/src/nvim/testdir/test_utf8.vim +++ b/src/nvim/testdir/test_utf8.vim @@ -60,3 +60,46 @@ func Test_getvcol() call assert_equal(2, virtcol("'[")) call assert_equal(2, virtcol("']")) endfunc + +func Test_list2str_str2list_utf8() + " One Unicode codepoint + let s = "\u3042\u3044" + let l = [0x3042, 0x3044] + call assert_equal(l, str2list(s, 1)) + call assert_equal(s, list2str(l, 1)) + if &enc ==# 'utf-8' + call assert_equal(str2list(s), str2list(s, 1)) + call assert_equal(list2str(l), list2str(l, 1)) + endif + + " With composing characters + let s = "\u304b\u3099\u3044" + let l = [0x304b, 0x3099, 0x3044] + call assert_equal(l, str2list(s, 1)) + call assert_equal(s, list2str(l, 1)) + if &enc ==# 'utf-8' + call assert_equal(str2list(s), str2list(s, 1)) + call assert_equal(list2str(l), list2str(l, 1)) + endif + + " Null list is the same as an empty list + call assert_equal('', list2str([])) + " call assert_equal('', list2str(test_null_list())) +endfunc + +func Test_list2str_str2list_latin1() + " When 'encoding' is not multi-byte can still get utf-8 string. + " But we need to create the utf-8 string while 'encoding' is utf-8. + let s = "\u3042\u3044" + let l = [0x3042, 0x3044] + + let save_encoding = &encoding + " set encoding=latin1 + + let lres = str2list(s, 1) + let sres = list2str(l, 1) + + let &encoding = save_encoding + call assert_equal(l, lres) + call assert_equal(s, sres) +endfunc diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim index 6066d61af4..56031662a3 100644 --- a/src/nvim/testdir/test_writefile.vim +++ b/src/nvim/testdir/test_writefile.vim @@ -1,4 +1,4 @@ -" Tests for the writefile() function. +" Tests for the writefile() function and some :write commands. func Test_writefile() let f = tempname() @@ -16,6 +16,11 @@ func Test_writefile() call delete(f) endfunc +func Test_writefile_ignore_regexp_error() + write Xt[z-a]est.txt + call delete('Xt[z-a]est.txt') +endfunc + func Test_writefile_fails_gently() call assert_fails('call writefile(["test"], "Xfile", [])', 'E730:') call assert_false(filereadable("Xfile")) diff --git a/src/nvim/testdir/view_util.vim b/src/nvim/testdir/view_util.vim index 520f65c1e7..1def201a05 100644 --- a/src/nvim/testdir/view_util.vim +++ b/src/nvim/testdir/view_util.vim @@ -1,10 +1,21 @@ " Functions about view shared by several tests " Only load this script once. -if exists('*ScreenLines') +if exists('*Screenline') finish endif +" Get line "lnum" as displayed on the screen. +" Trailing white space is trimmed. +func Screenline(lnum) + let chars = [] + for c in range(1, winwidth(0)) + call add(chars, nr2char(screenchar(a:lnum, c))) + endfor + let line = join(chars, '') + return matchstr(line, '^.\{-}\ze\s*$') +endfunc + " ScreenLines(lnum, width) or " ScreenLines([start, end], width) function! ScreenLines(lnum, width) abort diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 951cb50c3c..bbee7e4712 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -31,6 +31,10 @@ void tinput_init(TermInput *input, Loop *loop) input->paste = 0; input->in_fd = STDIN_FILENO; input->waiting_for_bg_response = 0; + // The main thread is waiting for the UI thread to call CONTINUE, so it can + // safely access global variables. + input->ttimeout = (bool)p_ttimeout; + input->ttimeoutlen = p_ttm; input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE); uv_mutex_init(&input->key_buffer_mutex); uv_cond_init(&input->key_buffer_cond); @@ -285,21 +289,6 @@ static TermKeyResult tk_getkey(TermKey *tk, TermKeyKey *key, bool force) static void tinput_timer_cb(TimeWatcher *watcher, void *data); -static int get_key_code_timeout(void) -{ - Integer ms = -1; - // Check 'ttimeout' to determine if we should send ESC after 'ttimeoutlen'. - Error err = ERROR_INIT; - if (nvim_get_option(cstr_as_string("ttimeout"), &err).data.boolean) { - Object rv = nvim_get_option(cstr_as_string("ttimeoutlen"), &err); - if (!ERROR_SET(&err)) { - ms = rv.data.integer; - } - } - api_clear_error(&err); - return (int)ms; -} - static void tk_getkeys(TermInput *input, bool force) { TermKeyKey key; @@ -324,12 +313,11 @@ static void tk_getkeys(TermInput *input, bool force) // yet contain all the bytes required. `key` structure indicates what // termkey_getkey_force() would return. - int ms = get_key_code_timeout(); - - if (ms > 0) { + if (input->ttimeout && input->ttimeoutlen >= 0) { // Stop the current timer if already running time_watcher_stop(&input->timer_handle); - time_watcher_start(&input->timer_handle, tinput_timer_cb, (uint32_t)ms, 0); + time_watcher_start(&input->timer_handle, tinput_timer_cb, + (uint64_t)input->ttimeoutlen, 0); } else { tk_getkeys(input, true); } diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h index 77bd6fa132..b30546c815 100644 --- a/src/nvim/tui/input.h +++ b/src/nvim/tui/input.h @@ -12,7 +12,9 @@ typedef struct term_input { // Phases: -1=all 0=disabled 1=first-chunk 2=continue 3=last-chunk int8_t paste; bool waiting; + bool ttimeout; int8_t waiting_for_bg_response; + long ttimeoutlen; TermKey *tk; #if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 TermKey_Terminfo_Getstr_Hook *tk_ti_hook_fn; ///< libtermkey terminfo hook diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c index 03173afe07..ff2a357752 100644 --- a/src/nvim/tui/terminfo.c +++ b/src/nvim/tui/terminfo.c @@ -31,7 +31,10 @@ bool terminfo_is_term_family(const char *term, const char *family) return tlen >= flen && 0 == memcmp(term, family, flen) // Per commentary in terminfo, minus is the only valid suffix separator. - && ('\0' == term[flen] || '-' == term[flen]); + // The screen terminfo may have a terminal name like screen.xterm. By making + // the dot(.) a valid separator, such terminal names will also be the + // terminal family of the screen. + && ('\0' == term[flen] || '-' == term[flen] || '.' == term[flen]); } bool terminfo_is_bsd_console(const char *term) @@ -187,7 +190,7 @@ void terminfo_info_msg(const unibi_term *const ut) msg_printf_attr(0, " %-25s %-10s = ", unibi_name_str(i), unibi_short_name_str(i)); // Most of these strings will contain escape sequences. - msg_outtrans_special((char_u *)s, false); + msg_outtrans_special((char_u *)s, false, 0); msg_putchar('\n'); } } @@ -214,7 +217,7 @@ void terminfo_info_msg(const unibi_term *const ut) msg_puts("Extended string capabilities:\n"); for (size_t i = 0; i < unibi_count_ext_str(ut); i++) { msg_printf_attr(0, " %-25s = ", unibi_get_ext_str_name(ut, i)); - msg_outtrans_special((char_u *)unibi_get_ext_str(ut, i), false); + msg_outtrans_special((char_u *)unibi_get_ext_str(ut, i), false, 0); msg_putchar('\n'); } } diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index e9276db484..2c4d02812b 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -31,6 +31,7 @@ #include "nvim/event/signal.h" #include "nvim/os/input.h" #include "nvim/os/os.h" +#include "nvim/os/signal.h" #include "nvim/os/tty.h" #include "nvim/strings.h" #include "nvim/syntax.h" @@ -48,10 +49,15 @@ #define OUTBUF_SIZE 0xffff #define TOO_MANY_EVENTS 1000000 -#define STARTS_WITH(str, prefix) (strlen(str) >= (sizeof(prefix) - 1) \ - && 0 == memcmp((str), (prefix), sizeof(prefix) - 1)) -#define TMUX_WRAP(is_tmux, seq) ((is_tmux) \ - ? "\x1bPtmux;\x1b" seq "\x1b\\" : seq) +#define STARTS_WITH(str, prefix) \ + (strlen(str) >= (sizeof(prefix) - 1) && 0 == memcmp((str), (prefix), \ + sizeof(prefix) - 1)) +#define SCREEN_WRAP(is_screen, seq) ((is_screen) \ + ? DCS_STR seq STERM_STR : seq) +#define SCREEN_TMUX_WRAP(is_screen, is_tmux, seq) \ + ((is_screen) \ + ? DCS_STR seq STERM_STR : (is_tmux) \ + ? DCS_STR "tmux;\x1b" seq STERM_STR : seq) #define LINUXSET0C "\x1b[?0c" #define LINUXSET1C "\x1b[?1c" @@ -1099,6 +1105,7 @@ static void tui_grid_scroll(UI *ui, Integer g, Integer startrow, Integer endrow, set_scroll_region(ui, top, bot, left, right); } cursor_goto(ui, top, left); + update_attrs(ui, 0); if (rows > 0) { if (rows == 1) { @@ -1238,7 +1245,9 @@ static void suspend_event(void **argv) tui_terminal_stop(ui); data->cont_received = false; stream_set_blocking(input_global_fd(), true); // normalize stream (#2598) + signal_stop(); kill(0, SIGTSTP); + signal_start(); while (!data->cont_received) { // poll the event loop until SIGCONT is received loop_poll_events(data->loop, -1); @@ -1291,6 +1300,12 @@ static void tui_option_set(UI *ui, String name, Object value) data->print_attr_id = -1; invalidate(ui, 0, data->grid.height, 0, data->grid.width); } + if (strequal(name.data, "ttimeout")) { + data->input.ttimeout = value.data.boolean; + } + if (strequal(name.data, "ttimeoutlen")) { + data->input.ttimeoutlen = (long)value.data.integer; + } } static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol, @@ -1545,6 +1560,10 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, bool mate_pretending_xterm = xterm && colorterm && strstr(colorterm, "mate-terminal"); bool true_xterm = xterm && !!xterm_version && !bsdvt; + bool true_screen = screen && !os_getenv("TMUX"); + bool screen_host_linuxvt = + terminfo_is_term_family(true_screen && term[6] == '.' + ? term + 7 : NULL, "linux"); bool cygwin = terminfo_is_term_family(term, "cygwin"); char *fix_normal = (char *)unibi_get_str(ut, unibi_cursor_normal); @@ -1630,6 +1649,11 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, // per the screen manual; 2017-04 terminfo.src lacks these. unibi_set_if_empty(ut, unibi_to_status_line, "\x1b_"); unibi_set_if_empty(ut, unibi_from_status_line, "\x1b\\"); + // Fix an issue where smglr is inherited by TERM=screen.xterm. + if (unibi_get_str(ut, unibi_set_lr_margin)) { + ILOG("Disabling smglr with TERM=screen.xterm for screen."); + unibi_set_str(ut, unibi_set_lr_margin, NULL); + } } else if (tmux) { unibi_set_if_empty(ut, unibi_to_status_line, "\x1b_"); unibi_set_if_empty(ut, unibi_from_status_line, "\x1b\\"); @@ -1682,8 +1706,10 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, #define XTERM_SETAB_16 \ "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m" - data->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg", - "\x1b]11;?\x07"); + data->unibi_ext.get_bg = + (int)unibi_add_ext_str(ut, "ext.get_bg", + SCREEN_TMUX_WRAP(true_screen, + tmux, "\x1b]11;?\x07")); // Terminals with 256-colour SGR support despite what terminfo says. if (unibi_get_num(ut, unibi_max_colors) < 256) { @@ -1718,6 +1744,32 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, data->unibi_ext.set_cursor_style = unibi_find_ext_str(ut, "Ss"); } + // GNU Screen does not have Ss/Se. When terminfo has Ss/Se, it is wrapped with + // DCS because it is inherited from the host terminal. + if (true_screen) { + size_t len; + size_t dcs_st_len = strlen(DCS_STR) + strlen(STERM_STR); + if (-1 != data->unibi_ext.set_cursor_style) { + const char *orig_ss = + unibi_get_ext_str(data->ut, (size_t)data->unibi_ext.reset_cursor_style); + len = STRLEN(orig_ss) + dcs_st_len + 1; + char *ss = xmalloc(len); + snprintf(ss, len, "%s%s%s", DCS_STR, orig_ss, STERM_STR); + unibi_set_ext_str(data->ut, (size_t)data->unibi_ext.set_cursor_style, ss); + xfree(ss); + } + if (-1 != data->unibi_ext.reset_cursor_style) { + const char *orig_se = + unibi_get_ext_str(data->ut, (size_t)data->unibi_ext.reset_cursor_style); + len = strlen(orig_se) + dcs_st_len + 1; + char *se = xmalloc(len); + snprintf(se, len, "%s%s%s", DCS_STR, orig_se, STERM_STR); + unibi_set_ext_str(data->ut, + (size_t)data->unibi_ext.reset_cursor_style, se); + xfree(se); + } + } + // Dickey ncurses terminfo includes Ss/Se capabilities since 2011-07-14. So // adding them to terminal types, that have such control sequences but lack // the correct terminfo entries, is a fixup, not an augmentation. @@ -1733,7 +1785,12 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, || (konsolev >= 180770) // #9364 || tmux // per tmux manual page // https://lists.gnu.org/archive/html/screen-devel/2013-03/msg00000.html - || screen + || (true_screen + && (!screen_host_linuxvt + || (screen_host_linuxvt + && (xterm_version || (vte_version > 0) || colorterm)))) + // Since GNU Screen does not support DECSCUSR, DECSCUSR is wrapped + // in DCS and output to the host terminal. || st // #7641 || rxvt // per command.C // per analysis of VT100Terminal.m @@ -1746,58 +1803,72 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, || (linuxvt && (xterm_version || (vte_version > 0) || colorterm)))) { data->unibi_ext.set_cursor_style = - (int)unibi_add_ext_str(ut, "Ss", "\x1b[%p1%d q"); + (int)unibi_add_ext_str(ut, "Ss", + SCREEN_WRAP(true_screen, "\x1b[%p1%d q")); if (-1 == data->unibi_ext.reset_cursor_style) { data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", ""); } unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style, - "\x1b[ q"); - } else if (linuxvt) { + SCREEN_WRAP(true_screen, "\x1b[ q")); + } else if (linuxvt || screen_host_linuxvt) { // Linux uses an idiosyncratic escape code to set the cursor shape and // does not support DECSCUSR. // See http://linuxgazette.net/137/anonymous.html for more info - data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", - "\x1b[?" - "%?" - // The parameter passed to Ss is the DECSCUSR parameter, so the - // terminal capability has to translate into the Linux idiosyncratic - // parameter. - // - // linuxvt only supports block and underline. It is also only - // possible to have a steady block (no steady underline) - "%p1%{2}%<" "%t%{8}" // blink block - "%e%p1%{2}%=" "%t%{112}" // steady block - "%e%p1%{3}%=" "%t%{4}" // blink underline (set to half block) - "%e%p1%{4}%=" "%t%{4}" // steady underline - "%e%p1%{5}%=" "%t%{2}" // blink bar (set to underline) - "%e%p1%{6}%=" "%t%{2}" // steady bar - "%e%{0}" // anything else - "%;" "%dc"); + // + // Since gnu Screen does not have Ss/Se, if the host terminal is a linux + // console that does not support xterm extensions, it will wraps the + // linux-specific sequence in DCS and outputs it. + data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str( + ut, "Ss", + SCREEN_WRAP(true_screen, + "\x1b[?" + "%?" + // The parameter passed to Ss is the DECSCUSR parameter, + // so the + // terminal capability has to translate into the Linux + // idiosyncratic parameter. + // + // linuxvt only supports block and underline. It is also + // only possible to have a steady block (no steady + // underline) + "%p1%{2}%<" "%t%{8}" // blink block + "%e%p1%{2}%=" "%t%{112}" // steady block + "%e%p1%{3}%=" "%t%{4}" // blink underline (set to half + // block) + "%e%p1%{4}%=" "%t%{4}" // steady underline + "%e%p1%{5}%=" "%t%{2}" // blink bar (set to underline) + "%e%p1%{6}%=" "%t%{2}" // steady bar + "%e%{0}" // anything else + "%;" "%dc")); if (-1 == data->unibi_ext.reset_cursor_style) { data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", ""); } unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style, - "\x1b[?c"); + SCREEN_WRAP(true_screen, "\x1b[?c")); } else if (konsolev > 0 && konsolev < 180770) { // Konsole before version 18.07.70: set up a nonce profile. This has // side-effects on temporary font resizing. #6798 - data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", - TMUX_WRAP(tmux, "\x1b]50;CursorShape=%?" - "%p1%{3}%<" "%t%{0}" // block - "%e%p1%{5}%<" "%t%{2}" // underline - "%e%{1}" // everything else is bar - "%;%d;BlinkingCursorEnabled=%?" - "%p1%{1}%<" "%t%{1}" // Fortunately if we exclude zero as special, - "%e%p1%{1}%&" // in all other cases we can treat bit #0 as a flag. - "%;%d\x07")); + data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str( + ut, "Ss", + SCREEN_TMUX_WRAP(true_screen, tmux, + "\x1b]50;CursorShape=%?" + "%p1%{3}%<" "%t%{0}" // block + "%e%p1%{5}%<" "%t%{2}" // underline + "%e%{1}" // everything else is bar + "%;%d;BlinkingCursorEnabled=%?" + "%p1%{1}%<" "%t%{1}" // Fortunately if we exclude + // zero as special, + "%e%p1%{1}%&" // in all other c2ses we can treat bit + // #0 as a flag. + "%;%d\x07")); if (-1 == data->unibi_ext.reset_cursor_style) { data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", ""); } unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style, - "\x1b]50;\x07"); + SCREEN_TMUX_WRAP(true_screen, tmux, "\x1b]50;\x07")); } } } @@ -1829,6 +1900,10 @@ static void augment_terminfo(TUIData *data, const char *term, const char *xterm_version = os_getenv("XTERM_VERSION"); bool true_xterm = xterm && !!xterm_version && !bsdvt; + bool true_screen = screen && !os_getenv("TMUX"); + bool screen_host_rxvt = + terminfo_is_term_family(true_screen + && term[6] == '.' ? term + 7 : NULL, "rxvt"); // Only define this capability for terminal types that we know understand it. if (dtterm // originated this extension @@ -1895,8 +1970,8 @@ static void augment_terminfo(TUIData *data, const char *term, // all panes, which is not particularly desirable. A better approach // would use a tmux control sequence and an extra if(screen) test. data->unibi_ext.set_cursor_color = (int)unibi_add_ext_str( - ut, NULL, TMUX_WRAP(tmux, "\033]Pl%p1%06x\033\\")); - } else if ((xterm || rxvt || alacritty) + ut, NULL, SCREEN_TMUX_WRAP(true_screen, tmux, "\033]Pl%p1%06x\033\\")); + } else if ((xterm || rxvt || tmux || alacritty) && (vte_version == 0 || vte_version >= 3900)) { // Supported in urxvt, newer VTE. data->unibi_ext.set_cursor_color = (int)unibi_add_ext_str( @@ -1915,21 +1990,27 @@ static void augment_terminfo(TUIData *data, const char *term, /// Terminals usually ignore unrecognized private modes, and there is no /// known ambiguity with these. So we just set them unconditionally. + /// If the DECSET is not supported by GNU Screen, it is wrapped with DCS and + /// sent to the host terminal. data->unibi_ext.enable_lr_margin = (int)unibi_add_ext_str( ut, "ext.enable_lr_margin", "\x1b[?69h"); data->unibi_ext.disable_lr_margin = (int)unibi_add_ext_str( ut, "ext.disable_lr_margin", "\x1b[?69l"); data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str( - ut, "ext.enable_bpaste", "\x1b[?2004h"); + ut, "ext.enable_bpaste", SCREEN_WRAP(true_screen, "\x1b[?2004h")); data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str( - ut, "ext.disable_bpaste", "\x1b[?2004l"); + ut, "ext.disable_bpaste", SCREEN_WRAP(true_screen, "\x1b[?2004l")); // For urxvt send BOTH xterm and old urxvt sequences. #8695 data->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str( ut, "ext.enable_focus", - rxvt ? "\x1b[?1004h\x1b]777;focus;on\x7" : "\x1b[?1004h"); + (rxvt || screen_host_rxvt) + ? SCREEN_WRAP(true_screen, "\x1b[?1004h\x1b]777;focus;on\x7") + : SCREEN_WRAP(true_screen, "\x1b[?1004h")); data->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str( ut, "ext.disable_focus", - rxvt ? "\x1b[?1004l\x1b]777;focus;off\x7" : "\x1b[?1004l"); + (rxvt || screen_host_rxvt) + ? SCREEN_WRAP(true_screen, "\x1b[?1004l\x1b]777;focus;off\x7") + : SCREEN_WRAP(true_screen, "\x1b[?1004l")); data->unibi_ext.enable_mouse = (int)unibi_add_ext_str( ut, "ext.enable_mouse", "\x1b[?1002h\x1b[?1006h"); data->unibi_ext.disable_mouse = (int)unibi_add_ext_str( @@ -1961,7 +2042,23 @@ static void flush_buf(UI *ui) uv_buf_t *bufp = &bufs[0]; TUIData *data = ui->data; - if (data->bufpos <= 0 && data->busy == data->is_invisible) { + // The content of the output for each condition is shown in the following + // table. Therefore, if data->bufpos == 0 and N/A or invis + norm, there is + // no need to output it. + // + // | is_invisible | !is_invisible + // ------+-----------------+--------------+--------------- + // busy | want_invisible | N/A | invis + // | !want_invisible | N/A | invis + // ------+-----------------+--------------+--------------- + // !busy | want_invisible | N/A | invis + // | !want_invisible | norm | invis + norm + // ------+-----------------+--------------+--------------- + // + if (data->bufpos <= 0 + && ((data->is_invisible && data->busy) + || (data->is_invisible && !data->busy && data->want_invisible) + || (!data->is_invisible && !data->busy && !data->want_invisible))) { return; } @@ -1988,8 +2085,8 @@ static void flush_buf(UI *ui) bufp->base = data->norm; bufp->len = UV_BUF_LEN(data->normlen); bufp++; + data->is_invisible = false; } - data->is_invisible = false; } uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle), diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 0f841760d6..685da77b39 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -226,7 +226,7 @@ int ui_pum_get_height(void) { int pum_height = 0; for (size_t i = 1; i < ui_count; i++) { - int ui_pum_height = uis[i]->pum_height; + int ui_pum_height = uis[i]->pum_nlines; if (ui_pum_height) { pum_height = pum_height != 0 ? MIN(pum_height, ui_pum_height) : ui_pum_height; @@ -235,6 +235,21 @@ int ui_pum_get_height(void) return pum_height; } +bool ui_pum_get_pos(double *pwidth, double *pheight, double *prow, double *pcol) +{ + for (size_t i = 1; i < ui_count; i++) { + if (!uis[i]->pum_pos) { + continue; + } + *pwidth = uis[i]->pum_width; + *pheight = uis[i]->pum_height; + *prow = uis[i]->pum_row; + *pcol = uis[i]->pum_col; + return true; + } + return false; +} + static void ui_refresh_event(void **argv) { ui_refresh(); @@ -424,7 +439,7 @@ int ui_current_col(void) void ui_flush(void) { cmdline_ui_flush(); - win_ui_flush_positions(); + win_ui_flush(); msg_ext_ui_flush(); msg_scroll_flush(); diff --git a/src/nvim/ui.h b/src/nvim/ui.h index 8867b5ee24..d00243d35f 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -53,7 +53,12 @@ struct ui_t { bool ui_ext[kUIExtCount]; ///< Externalized UI capabilities. int width; int height; - int pum_height; + int pum_nlines; /// actual nr. lines shown in PUM + bool pum_pos; /// UI reports back pum position? + double pum_row; + double pum_col; + double pum_height; + double pum_width; void *data; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 1f74bada41..97018f6c02 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -2441,10 +2441,11 @@ static void u_undo_end( uhp = curbuf->b_u_newhead; } - if (uhp == NULL) + if (uhp == NULL) { *msgbuf = NUL; - else - u_add_time(msgbuf, sizeof(msgbuf), uhp->uh_time); + } else { + add_time(msgbuf, sizeof(msgbuf), uhp->uh_time); + } { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { @@ -2509,8 +2510,8 @@ void ex_undolist(exarg_T *eap) && uhp->uh_walk != mark) { vim_snprintf((char *)IObuff, IOSIZE, "%6ld %7d ", uhp->uh_seq, changes); - u_add_time(IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff), - uhp->uh_time); + add_time(IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff), + uhp->uh_time); if (uhp->uh_save_nr > 0) { while (STRLEN(IObuff) < 33) STRCAT(IObuff, " "); @@ -2575,30 +2576,6 @@ void ex_undolist(exarg_T *eap) } /* - * Put the timestamp of an undo header in "buf[buflen]" in a nice format. - */ -static void u_add_time(char_u *buf, size_t buflen, time_t tt) -{ - struct tm curtime; - - if (time(NULL) - tt >= 100) { - os_localtime_r(&tt, &curtime); - if (time(NULL) - tt < (60L * 60L * 12L)) - /* within 12 hours */ - (void)strftime((char *)buf, buflen, "%H:%M:%S", &curtime); - else - /* longer ago */ - (void)strftime((char *)buf, buflen, "%Y/%m/%d %H:%M:%S", &curtime); - } else { - int64_t seconds = time(NULL) - tt; - vim_snprintf((char *)buf, buflen, - NGETTEXT("%" PRId64 " second ago", - "%" PRId64 " seconds ago", (uint32_t)seconds), - seconds); - } -} - -/* * ":undojoin": continue adding to the last entry list */ void ex_undojoin(exarg_T *eap) @@ -2971,7 +2948,10 @@ static char_u *u_save_line(linenr_T lnum) bool bufIsChanged(buf_T *buf) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - return !bt_dontwrite(buf) && (buf->b_changed || file_ff_differs(buf, true)); + // In a "prompt" buffer we do respect 'modified', so that we can control + // closing the window by setting or resetting that option. + return (!bt_dontwrite(buf) || bt_prompt(buf)) + && (buf->b_changed || file_ff_differs(buf, true)); } // Return true if any buffer has changes. Also buffers that are not written. diff --git a/src/nvim/version.c b/src/nvim/version.c index c67fd9175f..e9f2b37e6b 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -129,13 +129,13 @@ static const int included_patches[] = { 1792, 1791, 1790, - // 1789, + 1789, 1788, 1787, 1786, 1785, 1784, - // 1783, + 1783, 1782, 1781, 1780, @@ -258,7 +258,7 @@ static const int included_patches[] = { 1663, 1662, 1661, - // 1660, + 1660, 1659, 1658, 1657, @@ -267,7 +267,7 @@ static const int included_patches[] = { 1654, 1653, 1652, - // 1651, + 1651, 1650, 1649, 1648, @@ -331,7 +331,7 @@ static const int included_patches[] = { 1590, // 1589, // 1588, - // 1587, + 1587, 1586, 1585, 1584, @@ -398,7 +398,7 @@ static const int included_patches[] = { 1523, 1522, 1521, - // 1520, + 1520, 1519, 1518, 1517, @@ -496,7 +496,7 @@ static const int included_patches[] = { 1425, 1424, 1423, - // 1422, + 1422, 1421, 1420, 1419, @@ -530,7 +530,7 @@ static const int included_patches[] = { 1391, 1390, 1389, - // 1388, + 1388, 1387, 1386, 1385, @@ -543,11 +543,11 @@ static const int included_patches[] = { 1378, 1377, 1376, - // 1375, + 1375, 1374, 1373, 1372, - // 1371, + 1371, 1370, 1369, 1368, @@ -560,11 +560,11 @@ static const int included_patches[] = { 1361, 1360, 1359, - // 1358, + 1358, 1357, 1356, - // 1355, - // 1354, + 1355, + 1354, 1353, 1352, 1351, @@ -573,7 +573,7 @@ static const int included_patches[] = { 1348, 1347, 1346, - // 1345, + 1345, 1344, 1343, 1342, @@ -584,7 +584,7 @@ static const int included_patches[] = { 1337, 1336, // 1335, - // 1334, + 1334, 1333, 1332, 1331, @@ -626,12 +626,12 @@ static const int included_patches[] = { 1295, 1294, 1293, - // 1292, + 1292, 1291, 1290, 1289, 1288, - // 1287, + 1287, 1286, 1285, 1284, @@ -776,10 +776,10 @@ static const int included_patches[] = { 1145, 1144, 1143, - // 1142, + 1142, 1141, 1140, - // 1139, + 1139, 1138, 1137, 1136, @@ -789,13 +789,13 @@ static const int included_patches[] = { 1132, 1131, 1130, - // 1129, + 1129, 1128, 1127, 1126, - // 1125, + 1125, 1124, - // 1123, + 1123, 1122, 1121, 1120, diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 51f143a3d7..c1eea1ab90 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -248,6 +248,9 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext() # define vim_strpbrk(s, cs) (char_u *)strpbrk((char *)(s), (char *)(cs)) +// Character used as separated in autoload function/variable names. +#define AUTOLOAD_CHAR '#' + #include "nvim/message.h" // Prefer using emsgf(), because perror() may send the output to the wrong @@ -257,6 +260,8 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext() #define SHOWCMD_COLS 10 // columns needed by shown command #define STL_MAX_ITEM 80 // max nr of %<flag> in statusline +#include "nvim/path.h" + /// Compare file names /// /// On some systems case in a file name does not matter, on others it does. diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index b4a0f57e99..b77b80a5f3 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -133,9 +133,6 @@ typedef enum { # include "viml/parser/expressions.c.generated.h" #endif -/// Character used as a separator in autoload function/variable names. -#define AUTOLOAD_CHAR '#' - /// Scale number by a given factor /// /// Used to apply exponent to a number. Idea taken from uClibc. diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 23e172da75..838a742271 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -326,7 +326,7 @@ struct expr_ast_node { } data; }; -enum { +enum ExprParserFlags { /// Allow multiple expressions in a row: e.g. for :echo /// /// Parser will still parse only one of them though. @@ -345,7 +345,7 @@ enum { // viml_expressions_parser.c, nvim_parse_expression() flags parsing // alongside with its documentation and flag sets in check_parsing() // function in expressions parser functional and unit tests. -} ExprParserFlags; +}; /// AST error definition typedef struct { diff --git a/src/nvim/window.c b/src/nvim/window.c index 8181883426..0fff93d984 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -618,7 +618,6 @@ void win_set_minimal_style(win_T *wp) wp->w_p_cuc = false; wp->w_p_spell = false; wp->w_p_list = false; - wp->w_p_fdc = 0; // Hide EOB region: use " " fillchar and cleared highlighting if (wp->w_p_fcs_chars.eob != ' ') { @@ -642,6 +641,12 @@ void win_set_minimal_style(win_T *wp) wp->w_p_scl = (char_u *)xstrdup("auto"); } + // foldcolumn: use 'auto' + if (wp->w_p_fdc[0] != '0') { + xfree(wp->w_p_fdc); + wp->w_p_fdc = (char_u *)xstrdup("0"); + } + // colorcolumn: cleared if (wp->w_p_cc != NULL && *wp->w_p_cc != NUL) { xfree(wp->w_p_cc); @@ -689,6 +694,21 @@ void win_check_anchored_floats(win_T *win) } } +/// Return the number of fold columns to display +int win_fdccol_count(win_T *wp) +{ + const char *fdc = (const char *)wp->w_p_fdc; + + // auto:<NUM> + if (strncmp(fdc, "auto:", 5) == 0) { + int needed_fdccols = getDeepestNesting(wp); + return MIN(fdc[5] - '0', needed_fdccols); + } else { + return fdc[0] - '0'; + } +} + + static void ui_ext_win_position(win_T *wp) { if (!wp->w_floating) { @@ -753,6 +773,21 @@ static void ui_ext_win_position(win_T *wp) } +void ui_ext_win_viewport(win_T *wp) +{ + if ((wp == curwin || ui_has(kUIMultigrid)) && wp->w_viewport_invalid) { + int botline = wp->w_botline; + if (botline == wp->w_buffer->b_ml.ml_line_count+1 + && wp->w_empty_rows == 0) { + // TODO(bfredl): The might be more cases to consider, like how does this + // interact with incomplete final line? Diff filler lines? + botline = wp->w_buffer->b_ml.ml_line_count; + } + ui_call_win_viewport(wp->w_grid.handle, wp->handle, wp->w_topline-1, + botline, wp->w_cursor.lnum-1, wp->w_cursor.col); + wp->w_viewport_invalid = false; + } +} static bool parse_float_anchor(String anchor, FloatAnchor *out) { @@ -1077,7 +1112,8 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) // add a status line when p_ls == 1 and splitting the first window if (one_nonfloat() && p_ls == 1 && oldwin->w_status_height == 0) { - if (oldwin->w_height <= p_wmh && new_in_layout) { + if ((oldwin->w_height + oldwin->w_winbar_height) <= p_wmh + && new_in_layout) { EMSG(_(e_noroom)); return FAIL; } @@ -1174,7 +1210,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) * height. */ // Current window requires at least 1 space. - wmh1 = (p_wmh == 0 ? 1 : p_wmh); + wmh1 = (p_wmh == 0 ? 1 : p_wmh) + curwin->w_winbar_height; needed = wmh1 + STATUS_HEIGHT; if (flags & WSP_ROOM) { needed += p_wh - wmh1; @@ -1372,12 +1408,12 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) if (flags & (WSP_TOP | WSP_BOT)) { /* set height and row of new window to full height */ wp->w_winrow = tabline_height(); - win_new_height(wp, curfrp->fr_height - (p_ls > 0)); + win_new_height(wp, curfrp->fr_height - (p_ls > 0) - wp->w_winbar_height); wp->w_status_height = (p_ls > 0); } else { /* height and row of new window is same as current window */ wp->w_winrow = oldwin->w_winrow; - win_new_height(wp, oldwin->w_height); + win_new_height(wp, oldwin->w_height + oldwin->w_winbar_height); wp->w_status_height = oldwin->w_status_height; } frp->fr_height = curfrp->fr_height; @@ -1424,7 +1460,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) * one row for the status line */ win_new_height(wp, new_size); if (flags & (WSP_TOP | WSP_BOT)) { - int new_fr_height = curfrp->fr_height - new_size; + int new_fr_height = curfrp->fr_height - new_size + wp->w_winbar_height; if (!((flags & WSP_BOT) && p_ls == 0)) { new_fr_height -= STATUS_HEIGHT; @@ -1437,8 +1473,9 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) wp->w_winrow = oldwin->w_winrow; wp->w_status_height = STATUS_HEIGHT; oldwin->w_winrow += wp->w_height + STATUS_HEIGHT; - } else { /* new window below current one */ - wp->w_winrow = oldwin->w_winrow + oldwin->w_height + STATUS_HEIGHT; + } else { // new window below current one + wp->w_winrow = oldwin->w_winrow + oldwin->w_height + + STATUS_HEIGHT + oldwin->w_winbar_height; wp->w_status_height = oldwin->w_status_height; if (!(flags & WSP_BOT)) { oldwin->w_status_height = STATUS_HEIGHT; @@ -1654,8 +1691,9 @@ make_windows ( maxcount = (curwin->w_width + curwin->w_vsep_width - (p_wiw - p_wmw)) / (p_wmw + 1); } else { - /* Each window needs at least 'winminheight' lines and a status line. */ - maxcount = (curwin->w_height + curwin->w_status_height + // Each window needs at least 'winminheight' lines and a status line. + maxcount = (curwin->w_height + curwin->w_winbar_height + + curwin->w_status_height - (p_wh - p_wmh)) / (p_wmh + STATUS_HEIGHT); } @@ -3109,15 +3147,18 @@ frame_new_height ( int wfh /* obey 'winfixheight' when there is a choice; may cause the height not to be set */ ) + FUNC_ATTR_NONNULL_ALL { frame_T *frp; int extra_lines; int h; if (topfrp->fr_win != NULL) { - /* Simple case: just one window. */ + // Simple case: just one window. win_new_height(topfrp->fr_win, - height - topfrp->fr_win->w_status_height); + height + - topfrp->fr_win->w_status_height + - topfrp->fr_win->w_winbar_height); } else if (topfrp->fr_layout == FR_ROW) { do { // All frames in this row get the same new height. @@ -3422,8 +3463,10 @@ static void frame_fix_width(win_T *wp) * Set frame height from the window it contains. */ static void frame_fix_height(win_T *wp) + FUNC_ATTR_NONNULL_ALL { - wp->w_frame->fr_height = wp->w_height + wp->w_status_height; + wp->w_frame->fr_height = + wp->w_height + wp->w_status_height + wp->w_winbar_height; } /* @@ -3440,14 +3483,18 @@ static int frame_minheight(frame_T *topfrp, win_T *next_curwin) int n; if (topfrp->fr_win != NULL) { - if (topfrp->fr_win == next_curwin) + if (topfrp->fr_win == next_curwin) { m = p_wh + topfrp->fr_win->w_status_height; - else { - /* window: minimal height of the window plus status line */ + } else { + // window: minimal height of the window plus status line m = p_wmh + topfrp->fr_win->w_status_height; - /* Current window is minimal one line high */ - if (p_wmh == 0 && topfrp->fr_win == curwin && next_curwin == NULL) - ++m; + if (topfrp->fr_win == curwin && next_curwin == NULL) { + // Current window is minimal one line high and WinBar is visible. + if (p_wmh == 0) { + m++; + } + m += curwin->w_winbar_height; + } } } else if (topfrp->fr_layout == FR_ROW) { /* get the minimal height from each frame in this row */ @@ -4668,6 +4715,11 @@ static win_T *win_alloc(win_T *after, int hidden) new_wp->w_scbind_pos = 1; new_wp->w_floating = 0; new_wp->w_float_config = FLOAT_CONFIG_INIT; + new_wp->w_viewport_invalid = true; + + // use global option for global-local options + new_wp->w_p_so = -1; + new_wp->w_p_siso = -1; /* We won't calculate w_fraction until resizing the window */ new_wp->w_fraction = 0; @@ -4742,6 +4794,7 @@ win_free ( qf_free_all(wp); + remove_winbar(wp); xfree(wp->w_p_cc_cols); @@ -5008,7 +5061,9 @@ static void frame_comp_pos(frame_T *topfrp, int *row, int *col) wp->w_redr_status = true; wp->w_pos_changed = true; } - *row += wp->w_height + wp->w_status_height; + // WinBar will not show if the window height is zero + const int h = wp->w_height + wp->w_winbar_height + wp->w_status_height; + *row += h > topfrp->fr_height ? topfrp->fr_height : h; *col += wp->w_width + wp->w_vsep_width; } else { startrow = *row; @@ -5041,12 +5096,15 @@ void win_setheight(int height) void win_setheight_win(int height, win_T *win) { if (win == curwin) { - /* Always keep current window at least one line high, even when - * 'winminheight' is zero. */ - if (height < p_wmh) + // Always keep current window at least one line high, even when + // 'winminheight' is zero. + if (height < p_wmh) { height = p_wmh; - if (height == 0) + } + if (height == 0) { height = 1; + } + height += curwin->w_winbar_height; } if (win->w_floating) { @@ -5143,7 +5201,7 @@ static void frame_setheight(frame_T *curfrp, int height) } else { win_T *wp = lastwin_nofloating(); room_cmdline = Rows - p_ch - (wp->w_winrow - + wp->w_height + + + wp->w_height + wp->w_winbar_height + wp->w_status_height); if (room_cmdline < 0) { room_cmdline = 0; @@ -5668,11 +5726,10 @@ void set_fraction(win_T *wp) } } -/* - * Set the height of a window. - * This takes care of the things inside the window, not what happens to the - * window position, the frame or to other windows. - */ +// Set the height of a window. +// "height" excludes any window toolbar. +// This takes care of the things inside the window, not what happens to the +// window position, the frame or to other windows. void win_new_height(win_T *wp, int height) { // Don't want a negative height. Happens when splitting a tiny window. @@ -5779,9 +5836,10 @@ void scroll_to_fraction(win_T *wp, int prev_height) } if (wp == curwin) { - if (p_so) + if (get_scrolloff_value()) { update_topline(); - curs_columns(FALSE); /* validate w_wrow */ + } + curs_columns(false); // validate w_wrow } if (prev_height > 0) { wp->w_prev_fraction_row = wp->w_wrow; @@ -6503,10 +6561,12 @@ void restore_buffer(bufref_T *save_curbuf) /// @param[in] id a desired ID 'id' can be specified /// (greater than or equal to 1). -1 must be specified if no /// particular ID is desired +/// @param[in] conceal_char pointer to conceal replacement char /// @return ID of added match, -1 on failure. int match_add(win_T *wp, const char *const grp, const char *const pat, int prio, int id, list_T *pos_list, const char *const conceal_char) + FUNC_ATTR_NONNULL_ARG(1, 2) { matchitem_T *cur; matchitem_T *prev; @@ -6543,7 +6603,7 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, return -1; } - /* Find available match ID. */ + // Find available match ID. while (id == -1) { cur = wp->w_match_head; while (cur != NULL && cur->id != wp->w_next_match_id) @@ -6553,7 +6613,7 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, wp->w_next_match_id++; } - /* Build new match. */ + // Build new match. m = xcalloc(1, sizeof(matchitem_T)); m->id = id; m->priority = prio; @@ -6661,9 +6721,9 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, rtype = VALID; } } - - /* Insert new match. The match list is in ascending order with regard to - * the match priorities. */ + + // Insert new match. The match list is in ascending order with regard to + // the match priorities. cur = wp->w_match_head; prev = cur; while (cur != NULL && prio >= cur->priority) { @@ -6967,7 +7027,7 @@ void get_framelayout(const frame_T *fr, list_T *l, bool outer) } } -void win_ui_flush_positions(void) +void win_ui_flush(void) { FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->w_pos_changed && wp->w_grid.chars != NULL) { @@ -6978,6 +7038,9 @@ void win_ui_flush_positions(void) } wp->w_pos_changed = false; } + if (tp == curtab) { + ui_ext_win_viewport(wp); + } } } diff --git a/src/nvim/xdiff/xdiff.h b/src/nvim/xdiff/xdiff.h index bc26fb64fd..8ff4a05bfb 100644 --- a/src/nvim/xdiff/xdiff.h +++ b/src/nvim/xdiff/xdiff.h @@ -25,9 +25,9 @@ #ifdef __cplusplus extern "C" { -#endif /* #ifdef __cplusplus */ +#endif // #ifdef __cplusplus -/* xpparm_t.flags */ +// xpparm_t.flags #define XDF_NEED_MINIMAL (1 << 0) #define XDF_IGNORE_WHITESPACE (1 << 1) @@ -48,22 +48,22 @@ extern "C" { #define XDF_INDENT_HEURISTIC (1 << 23) -/* xdemitconf_t.flags */ +// xdemitconf_t.flags #define XDL_EMIT_FUNCNAMES (1 << 0) #define XDL_EMIT_FUNCCONTEXT (1 << 2) -/* merge simplification levels */ +// merge simplification levels #define XDL_MERGE_MINIMAL 0 #define XDL_MERGE_EAGER 1 #define XDL_MERGE_ZEALOUS 2 #define XDL_MERGE_ZEALOUS_ALNUM 3 -/* merge favor modes */ +// merge favor modes #define XDL_MERGE_FAVOR_OURS 1 #define XDL_MERGE_FAVOR_THEIRS 2 #define XDL_MERGE_FAVOR_UNION 3 -/* merge output styles */ +// merge output styles #define XDL_MERGE_DIFF3 1 typedef struct s_mmfile { @@ -79,7 +79,7 @@ typedef struct s_mmbuffer { typedef struct s_xpparam { unsigned long flags; - /* See Documentation/diff-options.txt. */ + // See Documentation/diff-options.txt. char **anchors; size_t anchors_nr; } xpparam_t; @@ -126,9 +126,9 @@ typedef struct s_xmparam { int level; int favor; int style; - const char *ancestor; /* label for orig */ - const char *file1; /* label for mf1 */ - const char *file2; /* label for mf2 */ + const char *ancestor; // label for orig + const char *file1; // label for mf1 + const char *file2; // label for mf2 } xmparam_t; #define DEFAULT_CONFLICT_MARKER_SIZE 7 @@ -138,6 +138,6 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, #ifdef __cplusplus } -#endif /* #ifdef __cplusplus */ +#endif // #ifdef __cplusplus -#endif /* #if !defined(XDIFF_H) */ +#endif // #if !defined(XDIFF_H) diff --git a/src/nvim/xdiff/xdiffi.c b/src/nvim/xdiff/xdiffi.c index 96d5277027..3806903986 100644 --- a/src/nvim/xdiff/xdiffi.c +++ b/src/nvim/xdiff/xdiffi.c @@ -418,24 +418,24 @@ static int xget_indent(xrecord_t *rec) ret += 1; else if (c == '\t') ret += 8 - ret % 8; - /* ignore other whitespace characters */ + // ignore other whitespace characters if (ret >= MAX_INDENT) return MAX_INDENT; } - /* The line contains only whitespace. */ + // The line contains only whitespace. return -1; } /* - * If more than this number of consecutive blank rows are found, just return this - * value. This avoids requiring O(N^2) work for pathological cases, and also - * ensures that the output of score_split fits in an int. + * If more than this number of consecutive blank rows are found, just return + * this value. This avoids requiring O(N^2) work for pathological cases, and + * also ensures that the output of score_split fits in an int. */ #define MAX_BLANKS 20 -/* Characteristics measured about a hypothetical split position. */ +// Characteristics measured about a hypothetical split position. struct split_measurement { /* * Is the split at the end of the file (aside from any blank lines)? @@ -472,10 +472,10 @@ struct split_measurement { }; struct split_score { - /* The effective indent of this split (smaller is preferred). */ + // The effective indent of this split (smaller is preferred). int effective_indent; - /* Penalty for this split (smaller is preferred). */ + // Penalty for this split (smaller is preferred). int penalty; }; @@ -534,16 +534,16 @@ static void measure_split(const xdfile_t *xdf, long split, * integer math. */ -/* Penalty if there are no non-blank lines before the split */ +// Penalty if there are no non-blank lines before the split #define START_OF_FILE_PENALTY 1 -/* Penalty if there are no non-blank lines after the split */ +// Penalty if there are no non-blank lines after the split #define END_OF_FILE_PENALTY 21 -/* Multiplier for the number of blank lines around the split */ +// Multiplier for the number of blank lines around the split #define TOTAL_BLANK_WEIGHT (-30) -/* Multiplier for the number of blank lines after the split */ +// Multiplier for the number of blank lines after the split #define POST_BLANK_WEIGHT 6 /* @@ -610,7 +610,7 @@ static void score_add_split(const struct split_measurement *m, struct split_scor post_blank = (m->indent == -1) ? 1 + m->post_blank : 0; total_blank = m->pre_blank + post_blank; - /* Penalties based on nearby blank lines: */ + // Penalties based on nearby blank lines: s->penalty += TOTAL_BLANK_WEIGHT * total_blank; s->penalty += POST_BLANK_WEIGHT * post_blank; @@ -621,13 +621,13 @@ static void score_add_split(const struct split_measurement *m, struct split_scor any_blanks = (total_blank != 0); - /* Note that the effective indent is -1 at the end of the file: */ + // Note that the effective indent is -1 at the end of the file: s->effective_indent += indent; if (indent == -1) { - /* No additional adjustments needed. */ + // No additional adjustments needed. } else if (m->pre_indent == -1) { - /* No additional adjustments needed. */ + // No additional adjustments needed. } else if (indent > m->pre_indent) { /* * The line is indented more than its predecessor. @@ -669,7 +669,7 @@ static void score_add_split(const struct split_measurement *m, struct split_scor static int score_cmp(struct split_score *s1, struct split_score *s2) { - /* -1 if s1.effective_indent < s2->effective_indent, etc. */ + // -1 if s1.effective_indent < s2->effective_indent, etc. int cmp_indents = ((s1->effective_indent > s2->effective_indent) - (s1->effective_indent < s2->effective_indent)); @@ -809,7 +809,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { group_init(xdfo, &go); while (1) { - /* If the group is empty in the to-be-compacted file, skip it: */ + // If the group is empty in the to-be-compacted file, skip it: if (g.end == g.start) goto next; @@ -828,7 +828,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { */ end_matching_other = -1; - /* Shift the group backward as much as possible: */ + // Shift the group backward as much as possible: while (!group_slide_up(xdf, &g, flags)) if (group_previous(xdfo, &go)) xdl_bug("group sync broken sliding up"); @@ -842,7 +842,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { if (go.end > go.start) end_matching_other = g.end; - /* Now shift the group forward as far as possible: */ + // Now shift the group forward as far as possible: while (1) { if (group_slide_down(xdf, &g, flags)) break; @@ -863,7 +863,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { */ if (g.end == earliest_end) { - /* no shifting was possible */ + // no shifting was possible } else if (end_matching_other != -1) { /* * Move the possibly merged group of changes back to line @@ -921,7 +921,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { } next: - /* Move past the just-processed group: */ + // Move past the just-processed group: if (group_next(xdf, &g)) break; if (group_next(xdfo, &go)) diff --git a/src/nvim/xdiff/xdiffi.h b/src/nvim/xdiff/xdiffi.h index 8f1c7c8b04..467a1e85cd 100644 --- a/src/nvim/xdiff/xdiffi.h +++ b/src/nvim/xdiff/xdiffi.h @@ -61,4 +61,4 @@ int xdl_do_patience_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, int xdl_do_histogram_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdfenv_t *env); -#endif /* #if !defined(XDIFFI_H) */ +#endif // #if !defined(XDIFFI_H) diff --git a/src/nvim/xdiff/xemit.c b/src/nvim/xdiff/xemit.c index d8a6f1ed38..f1a45139cc 100644 --- a/src/nvim/xdiff/xemit.c +++ b/src/nvim/xdiff/xemit.c @@ -54,9 +54,9 @@ xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg) xdchange_t *xch, *xchp, *lxch; long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen; long max_ignorable = xecfg->ctxlen; - unsigned long ignored = 0; /* number of ignored blank lines */ + unsigned long ignored = 0; // number of ignored blank lines - /* remove ignorable changes that are too far before other changes */ + // remove ignorable changes that are too far before other changes for (xchp = *xscr; xchp && xchp->ignore; xchp = xchp->next) { xch = xchp->next; @@ -99,9 +99,9 @@ xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg) static long def_ff(const char *rec, long len, char *buf, long sz, void *priv UNUSED) { if (len > 0 && - (isalpha((unsigned char)*rec) || /* identifier? */ - *rec == '_' || /* also identifier? */ - *rec == '$')) { /* identifiers from VMS and other esoterico */ + (isalpha((unsigned char)*rec) || // identifier? + *rec == '_' || // also identifier? + *rec == '$')) { // identifiers from VMS and other esoterico if (len > sz) len = sz; while (0 < len && isspace((unsigned char)rec[len - 1])) @@ -197,7 +197,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) { long fs1, i1 = xch->i1; - /* Appended chunk? */ + // Appended chunk? if (i1 >= xe->xdf1.nrec) { long i2 = xch->i2; diff --git a/src/nvim/xdiff/xemit.h b/src/nvim/xdiff/xemit.h index 1b9887e670..3ce7e3dd50 100644 --- a/src/nvim/xdiff/xemit.h +++ b/src/nvim/xdiff/xemit.h @@ -33,4 +33,4 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, -#endif /* #if !defined(XEMIT_H) */ +#endif // #if !defined(XEMIT_H) diff --git a/src/nvim/xdiff/xhistogram.c b/src/nvim/xdiff/xhistogram.c index 3fb8974dd4..28cf8258e5 100644 --- a/src/nvim/xdiff/xhistogram.c +++ b/src/nvim/xdiff/xhistogram.c @@ -55,8 +55,8 @@ struct histindex { struct record { unsigned int ptr, cnt; struct record *next; - } **records, /* an occurrence */ - **line_map; /* map of line to record chain */ + } **records, // an occurrence + **line_map; // map of line to record chain chastore_t rcha; unsigned int *next_ptrs; unsigned int table_bits, @@ -128,7 +128,7 @@ static int scanA(struct histindex *index, int line1, int count1) */ NEXT_PTR(index, ptr) = rec->ptr; rec->ptr = ptr; - /* cap rec->cnt at MAX_CNT */ + // cap rec->cnt at MAX_CNT rec->cnt = XDL_MIN(MAX_CNT, rec->cnt + 1); LINE_MAP(index, ptr) = rec; goto continue_scan; @@ -154,7 +154,7 @@ static int scanA(struct histindex *index, int line1, int count1) LINE_MAP(index, ptr) = rec; continue_scan: - ; /* no op */ + ; // no op } return 0; @@ -266,7 +266,7 @@ static int find_lcs(xpparam_t const *xpp, xdfenv_t *env, index.records = NULL; index.line_map = NULL; - /* in case of early xdl_cha_free() */ + // in case of early xdl_cha_free() index.rcha.head = NULL; index.table_bits = xdl_hashbits(count1); @@ -288,7 +288,7 @@ static int find_lcs(xpparam_t const *xpp, xdfenv_t *env, goto cleanup; memset(index.next_ptrs, 0, sz); - /* lines / 4 + 1 comes from xprepare.c:xdl_prepare_ctx() */ + // lines / 4 + 1 comes from xprepare.c:xdl_prepare_ctx() if (xdl_cha_init(&index.rcha, sizeof(struct record), count1 / 4 + 1) < 0) goto cleanup; diff --git a/src/nvim/xdiff/xinclude.h b/src/nvim/xdiff/xinclude.h index 46b8608314..5a359d1431 100644 --- a/src/nvim/xdiff/xinclude.h +++ b/src/nvim/xdiff/xinclude.h @@ -20,13 +20,13 @@ * */ -/* defines HAVE_ATTRIBUTE_UNUSED */ +// defines HAVE_ATTRIBUTE_UNUSED #ifdef HAVE_CONFIG_H # include "../auto/config.h" #endif -/* Mark unused function arguments with UNUSED, so that gcc -Wunused-parameter - * can be used to check for mistakes. */ +// Mark unused function arguments with UNUSED, so that gcc -Wunused-parameter +// can be used to check for mistakes. #ifdef HAVE_ATTRIBUTE_UNUSED # define UNUSED __attribute__((unused)) #else @@ -58,4 +58,4 @@ #include "xemit.h" -#endif /* #if !defined(XINCLUDE_H) */ +#endif // #if !defined(XINCLUDE_H) diff --git a/src/nvim/xdiff/xmacros.h b/src/nvim/xdiff/xmacros.h index 2809a28ca9..1167ebbb05 100644 --- a/src/nvim/xdiff/xmacros.h +++ b/src/nvim/xdiff/xmacros.h @@ -51,4 +51,4 @@ do { \ } while (0) -#endif /* #if !defined(XMACROS_H) */ +#endif // #if !defined(XMACROS_H) diff --git a/src/nvim/xdiff/xpatience.c b/src/nvim/xdiff/xpatience.c index 2c65aac386..f6c84c67d8 100644 --- a/src/nvim/xdiff/xpatience.c +++ b/src/nvim/xdiff/xpatience.c @@ -69,7 +69,7 @@ struct hashmap { */ unsigned anchor : 1; } *entries, *first, *last; - /* were common records found? */ + // were common records found? unsigned long has_matches; mmfile_t *file1, *file2; xdfenv_t *env; @@ -86,7 +86,7 @@ static int is_anchor(xpparam_t const *xpp, const char *line) return 0; } -/* The argument "pass" is 1 for the first file, 2 for the second. */ +// The argument "pass" is 1 for the first file, 2 for the second. static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map, int pass) { @@ -155,7 +155,7 @@ static int fill_hashmap(mmfile_t *file1, mmfile_t *file2, result->xpp = xpp; result->env = env; - /* We know exactly how large we want the hash map */ + // We know exactly how large we want the hash map result->alloc = count1 * 2; result->entries = (struct entry *) xdl_malloc(result->alloc * sizeof(struct entry)); @@ -163,11 +163,11 @@ static int fill_hashmap(mmfile_t *file1, mmfile_t *file2, return -1; memset(result->entries, 0, result->alloc * sizeof(struct entry)); - /* First, fill with entries from the first file */ + // First, fill with entries from the first file while (count1--) insert_record(xpp, line1++, result, 1); - /* Then search for matches in the second file */ + // Then search for matches in the second file while (count2--) insert_record(xpp, line2++, result, 2); @@ -185,13 +185,13 @@ static int binary_search(struct entry **sequence, int longest, while (left + 1 < right) { int middle = left + (right - left) / 2; - /* by construction, no two entries can be equal */ + // by construction, no two entries can be equal if (sequence[middle]->line2 > entry->line2) right = middle; else left = middle; } - /* return the index in "sequence", _not_ the sequence length */ + // return the index in "sequence", _not_ the sequence length return left; } @@ -216,7 +216,7 @@ static struct entry *find_longest_common_sequence(struct hashmap *map) */ int anchor_i = -1; - /* Added to silence Coverity. */ + // Added to silence Coverity. if (sequence == NULL) return map->first; @@ -237,13 +237,13 @@ static struct entry *find_longest_common_sequence(struct hashmap *map) } } - /* No common unique lines were found */ + // No common unique lines were found if (!longest) { xdl_free(sequence); return NULL; } - /* Iterate starting at the last element, adjusting the "next" members */ + // Iterate starting at the last element, adjusting the "next" members entry = sequence[longest - 1]; entry->next = NULL; while (entry->previous) { @@ -273,7 +273,7 @@ static int walk_common_sequence(struct hashmap *map, struct entry *first, int next1, next2; for (;;) { - /* Try to grow the line ranges of common lines */ + // Try to grow the line ranges of common lines if (first) { next1 = first->line1; next2 = first->line2; @@ -292,7 +292,7 @@ static int walk_common_sequence(struct hashmap *map, struct entry *first, line2++; } - /* Recurse */ + // Recurse if (next1 > line1 || next2 > line2) { struct hashmap submap; @@ -343,7 +343,7 @@ static int patience_diff(mmfile_t *file1, mmfile_t *file2, struct entry *first; int result = 0; - /* trivial case: one side is empty */ + // trivial case: one side is empty if (!count1) { while(count2--) env->xdf2.rchg[line2++ - 1] = 1; @@ -359,7 +359,7 @@ static int patience_diff(mmfile_t *file1, mmfile_t *file2, line1, count1, line2, count2)) return -1; - /* are there any matching lines at all? */ + // are there any matching lines at all? if (!map.has_matches) { while(count1--) env->xdf1.rchg[line1++ - 1] = 1; @@ -387,7 +387,7 @@ int xdl_do_patience_diff(mmfile_t *file1, mmfile_t *file2, if (xdl_prepare_env(file1, file2, xpp, env) < 0) return -1; - /* environment is cleaned up in xdl_diff() */ + // environment is cleaned up in xdl_diff() return patience_diff(file1, file2, xpp, env, 1, env->xdf1.nrec, 1, env->xdf2.nrec); } diff --git a/src/nvim/xdiff/xprepare.h b/src/nvim/xdiff/xprepare.h index 947d9fc1bb..b67b3b25ab 100644 --- a/src/nvim/xdiff/xprepare.h +++ b/src/nvim/xdiff/xprepare.h @@ -31,4 +31,4 @@ void xdl_free_env(xdfenv_t *xe); -#endif /* #if !defined(XPREPARE_H) */ +#endif // #if !defined(XPREPARE_H) diff --git a/src/nvim/xdiff/xtypes.h b/src/nvim/xdiff/xtypes.h index 8442bd436e..026999c1bf 100644 --- a/src/nvim/xdiff/xtypes.h +++ b/src/nvim/xdiff/xtypes.h @@ -64,4 +64,4 @@ typedef struct s_xdfenv { -#endif /* #if !defined(XTYPES_H) */ +#endif // #if !defined(XTYPES_H) diff --git a/src/nvim/xdiff/xutils.c b/src/nvim/xdiff/xutils.c index 25a090fb73..e8c7d2f884 100644 --- a/src/nvim/xdiff/xutils.c +++ b/src/nvim/xdiff/xutils.c @@ -168,7 +168,7 @@ static int ends_with_optional_cr(const char *l, long s, long i) s--; if (s == i) return 1; - /* do not ignore CR at the end of an incomplete line */ + // do not ignore CR at the end of an incomplete line if (complete && s == i + 1 && l[i] == '\r') return 1; return 0; @@ -208,7 +208,7 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) } else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) { while (i1 < s1 && i2 < s2) { if (XDL_ISSPACE(l1[i1]) && XDL_ISSPACE(l2[i2])) { - /* Skip matching spaces and try again */ + // Skip matching spaces and try again while (i1 < s1 && XDL_ISSPACE(l1[i1])) i1++; while (i2 < s2 && XDL_ISSPACE(l2[i2])) @@ -224,7 +224,7 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) i2++; } } else if (flags & XDF_IGNORE_CR_AT_EOL) { - /* Find the first difference and see how the line ends */ + // Find the first difference and see how the line ends while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) { i1++; i2++; @@ -261,7 +261,7 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data, for (; ptr < top && *ptr != '\n'; ptr++) { if (cr_at_eol_only) { - /* do not ignore CR at the end of an incomplete line */ + // do not ignore CR at the end of an incomplete line if (*ptr == '\r' && (ptr + 1 < top && ptr[1] == '\n')) continue; @@ -274,7 +274,7 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data, ptr++; at_eol = (top <= ptr + 1 || ptr[1] == '\n'); if (flags & XDF_IGNORE_WHITESPACE) - ; /* already handled */ + ; // already handled else if (flags & XDF_IGNORE_WHITESPACE_CHANGE && !at_eol) { ha += (ha << 5); diff --git a/src/nvim/xdiff/xutils.h b/src/nvim/xdiff/xutils.h index fba7bae03c..0bebd93022 100644 --- a/src/nvim/xdiff/xutils.h +++ b/src/nvim/xdiff/xutils.h @@ -44,4 +44,4 @@ int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp, -#endif /* #if !defined(XUTILS_H) */ +#endif // #if !defined(XUTILS_H) diff --git a/src/tree_sitter/api.h b/src/tree_sitter/api.h index 40187e3db0..9d832e6ec4 100644 --- a/src/tree_sitter/api.h +++ b/src/tree_sitter/api.h @@ -174,8 +174,19 @@ const TSLanguage *ts_parser_language(const TSParser *self); * The second and third parameters specify the location and length of an array * of ranges. The parser does *not* take ownership of these ranges; it copies * the data, so it doesn't matter how these ranges are allocated. + * + * If `length` is zero, then the entire document will be parsed. Otherwise, + * the given ranges must be ordered from earliest to latest in the document, + * and they must not overlap. That is, the following must hold for all + * `i` < `length - 1`: + * + * ranges[i].end_byte <= ranges[i + 1].start_byte + * + * If this requirement is not satisfied, the operation will fail, the ranges + * will not be assigned, and this function will return `false`. On success, + * this function returns `true` */ -void ts_parser_set_included_ranges( +bool ts_parser_set_included_ranges( TSParser *self, const TSRange *ranges, uint32_t length @@ -325,14 +336,6 @@ TSLogger ts_parser_logger(const TSParser *self); */ void ts_parser_print_dot_graphs(TSParser *self, int file); -/** - * Set whether or not the parser should halt immediately upon detecting an - * error. This will generally result in a syntax tree with an error at the - * root, and one or more partial syntax trees within the error. This behavior - * may not be supported long-term. - */ -void ts_parser_halt_on_error(TSParser *self, bool halt); - /******************/ /* Section - Tree */ /******************/ @@ -732,13 +735,23 @@ const char *ts_query_string_value_for_id( ); /** - * Disable a certain capture within a query. This prevents the capture - * from being returned in matches, and also avoids any resource usage - * associated with recording the capture. + * Disable a certain capture within a query. + * + * This prevents the capture from being returned in matches, and also avoids + * any resource usage associated with recording the capture. Currently, there + * is no way to undo this. */ void ts_query_disable_capture(TSQuery *, const char *, uint32_t); /** + * Disable a certain pattern within a query. + * + * This prevents the pattern from matching and removes most of the overhead + * associated with the pattern. Currently, there is no way to undo this. + */ +void ts_query_disable_pattern(TSQuery *, uint32_t); + +/** * Create a new cursor for executing a given query. * * The cursor stores the state that is needed to iteratively search diff --git a/src/tree_sitter/array.h b/src/tree_sitter/array.h index bc77e687bf..26cb8448f1 100644 --- a/src/tree_sitter/array.h +++ b/src/tree_sitter/array.h @@ -126,12 +126,28 @@ static inline void array__splice(VoidArray *self, size_t element_size, array__reserve(self, element_size, new_size); char *contents = (char *)self->contents; - if (self->size > old_end) - memmove(contents + new_end * element_size, contents + old_end * element_size, - (self->size - old_end) * element_size); - if (new_count > 0) - memcpy((contents + index * element_size), elements, - new_count * element_size); + if (self->size > old_end) { + memmove( + contents + new_end * element_size, + contents + old_end * element_size, + (self->size - old_end) * element_size + ); + } + if (new_count > 0) { + if (elements) { + memcpy( + (contents + index * element_size), + elements, + new_count * element_size + ); + } else { + memset( + (contents + index * element_size), + 0, + new_count * element_size + ); + } + } self->size += new_count - old_count; } diff --git a/src/tree_sitter/atomic.h b/src/tree_sitter/atomic.h index 301ee36700..7bd0e850a9 100644 --- a/src/tree_sitter/atomic.h +++ b/src/tree_sitter/atomic.h @@ -12,11 +12,11 @@ static inline size_t atomic_load(const volatile size_t *p) { } static inline uint32_t atomic_inc(volatile uint32_t *p) { - return InterlockedIncrement(p); + return InterlockedIncrement((long volatile *)p); } static inline uint32_t atomic_dec(volatile uint32_t *p) { - return InterlockedDecrement(p); + return InterlockedDecrement((long volatile *)p); } #else diff --git a/src/tree_sitter/bits.h b/src/tree_sitter/bits.h index 3bec455dd1..ce7a715567 100644 --- a/src/tree_sitter/bits.h +++ b/src/tree_sitter/bits.h @@ -7,7 +7,7 @@ static inline uint32_t bitmask_for_index(uint16_t id) { return (1u << (31 - id)); } -#ifdef _WIN32 +#if defined _WIN32 && !defined __GNUC__ #include <intrin.h> diff --git a/src/tree_sitter/language.c b/src/tree_sitter/language.c index e240ef2a53..a396b4b0b6 100644 --- a/src/tree_sitter/language.c +++ b/src/tree_sitter/language.c @@ -72,8 +72,10 @@ const char *ts_language_symbol_name( return "ERROR"; } else if (symbol == ts_builtin_sym_error_repeat) { return "_ERROR"; - } else { + } else if (symbol < ts_language_symbol_count(self)) { return self->symbol_names[symbol]; + } else { + return NULL; } } @@ -119,7 +121,7 @@ const char *ts_language_field_name_for_id( TSFieldId id ) { uint32_t count = ts_language_field_count(self); - if (count) { + if (count && id <= count) { return self->field_names[id]; } else { return NULL; diff --git a/src/tree_sitter/language.h b/src/tree_sitter/language.h index d7e17c3d70..f908b4593a 100644 --- a/src/tree_sitter/language.h +++ b/src/tree_sitter/language.h @@ -29,10 +29,12 @@ static inline bool ts_language_is_symbol_external(const TSLanguage *self, TSSymb return 0 < symbol && symbol < self->external_token_count + 1; } -static inline const TSParseAction *ts_language_actions(const TSLanguage *self, - TSStateId state, - TSSymbol symbol, - uint32_t *count) { +static inline const TSParseAction *ts_language_actions( + const TSLanguage *self, + TSStateId state, + TSSymbol symbol, + uint32_t *count +) { TableEntry entry; ts_language_table_entry(self, state, symbol, &entry); *count = entry.action_count; @@ -90,8 +92,8 @@ static inline TSStateId ts_language_next_state(const TSLanguage *self, const TSParseAction *actions = ts_language_actions(self, state, symbol, &count); if (count > 0) { TSParseAction action = actions[count - 1]; - if (action.type == TSParseActionTypeShift || action.type == TSParseActionTypeRecover) { - return action.params.state; + if (action.type == TSParseActionTypeShift) { + return action.params.extra ? state : action.params.state; } } return 0; diff --git a/src/tree_sitter/lexer.c b/src/tree_sitter/lexer.c index e2ca851973..3f8a4c0ae8 100644 --- a/src/tree_sitter/lexer.c +++ b/src/tree_sitter/lexer.c @@ -355,7 +355,7 @@ void ts_lexer_mark_end(Lexer *self) { ts_lexer__mark_end(&self->data); } -void ts_lexer_set_included_ranges( +bool ts_lexer_set_included_ranges( Lexer *self, const TSRange *ranges, uint32_t count @@ -363,6 +363,16 @@ void ts_lexer_set_included_ranges( if (count == 0 || !ranges) { ranges = &DEFAULT_RANGE; count = 1; + } else { + uint32_t previous_byte = 0; + for (unsigned i = 0; i < count; i++) { + const TSRange *range = &ranges[i]; + if ( + range->start_byte < previous_byte || + range->end_byte < range->start_byte + ) return false; + previous_byte = range->end_byte; + } } size_t size = count * sizeof(TSRange); @@ -370,6 +380,7 @@ void ts_lexer_set_included_ranges( memcpy(self->included_ranges, ranges, size); self->included_range_count = count; ts_lexer_goto(self, self->current_position); + return true; } TSRange *ts_lexer_included_ranges(const Lexer *self, uint32_t *count) { diff --git a/src/tree_sitter/lexer.h b/src/tree_sitter/lexer.h index 8cd9c26706..5e39294529 100644 --- a/src/tree_sitter/lexer.h +++ b/src/tree_sitter/lexer.h @@ -38,7 +38,7 @@ void ts_lexer_start(Lexer *); void ts_lexer_finish(Lexer *, uint32_t *); void ts_lexer_advance_to_end(Lexer *); void ts_lexer_mark_end(Lexer *); -void ts_lexer_set_included_ranges(Lexer *self, const TSRange *ranges, uint32_t count); +bool ts_lexer_set_included_ranges(Lexer *self, const TSRange *ranges, uint32_t count); TSRange *ts_lexer_included_ranges(const Lexer *self, uint32_t *count); #ifdef __cplusplus diff --git a/src/tree_sitter/node.c b/src/tree_sitter/node.c index b03e2fc979..576f3ef38e 100644 --- a/src/tree_sitter/node.c +++ b/src/tree_sitter/node.c @@ -150,7 +150,9 @@ static inline TSNode ts_node__child( while (ts_node_child_iterator_next(&iterator, &child)) { if (ts_node__is_relevant(child, include_anonymous)) { if (index == child_index) { - ts_tree_set_cached_parent(self.tree, &child, &self); + if (ts_node__is_relevant(self, true)) { + ts_tree_set_cached_parent(self.tree, &child, &self); + } return child; } index++; diff --git a/src/tree_sitter/parser.c b/src/tree_sitter/parser.c index f381afccab..d4b227308b 100644 --- a/src/tree_sitter/parser.c +++ b/src/tree_sitter/parser.c @@ -71,7 +71,6 @@ struct TSParser { unsigned accept_count; unsigned operation_count; const volatile size_t *cancellation_flag; - bool halt_on_error; Subtree old_tree; TSRangeArray included_range_differences; unsigned included_range_difference_index; @@ -325,6 +324,12 @@ static bool ts_parser__can_reuse_first_leaf( TSStateId leaf_state = ts_subtree_leaf_parse_state(tree); TSLexMode leaf_lex_mode = self->language->lex_modes[leaf_state]; + // At the end of a non-terminal extra node, the lexer normally returns + // NULL, which indicates that the parser should look for a reduce action + // at symbol `0`. Avoid reusing tokens in this situation to ensure that + // the same thing happens when incrementally reparsing. + if (current_lex_mode.lex_state == (uint16_t)(-1)) return false; + // If the token was created in a state with the same set of lookaheads, it is reusable. if ( table_entry->action_count > 0 && @@ -594,6 +599,10 @@ static Subtree ts_parser__reuse_node( uint32_t byte_offset = reusable_node_byte_offset(&self->reusable_node); uint32_t end_byte_offset = byte_offset + ts_subtree_total_bytes(result); + // Do not reuse an EOF node if the included ranges array has changes + // later on in the file. + if (ts_subtree_is_eof(result)) end_byte_offset = UINT32_MAX; + if (byte_offset > position) { LOG("before_reusable_node symbol:%s", TREE_NAME(result)); break; @@ -1014,7 +1023,9 @@ static void ts_parser__handle_error( TSStateId state_after_missing_symbol = ts_language_next_state( self->language, state, missing_symbol ); - if (state_after_missing_symbol == 0) continue; + if (state_after_missing_symbol == 0 || state_after_missing_symbol == state) { + continue; + } if (ts_language_has_reduce_action( self->language, @@ -1067,46 +1078,6 @@ static void ts_parser__handle_error( LOG_STACK(); } -static void ts_parser__halt_parse(TSParser *self) { - LOG("halting_parse"); - LOG_STACK(); - - ts_lexer_advance_to_end(&self->lexer); - Length remaining_length = length_sub( - self->lexer.current_position, - ts_stack_position(self->stack, 0) - ); - - Subtree filler_node = ts_subtree_new_error( - &self->tree_pool, - 0, - length_zero(), - remaining_length, - remaining_length.bytes, - 0, - self->language - ); - ts_subtree_to_mut_unsafe(filler_node).ptr->visible = false; - ts_stack_push(self->stack, 0, filler_node, false, 0); - - SubtreeArray children = array_new(); - Subtree root_error = ts_subtree_new_error_node(&self->tree_pool, &children, false, self->language); - ts_stack_push(self->stack, 0, root_error, false, 0); - - Subtree eof = ts_subtree_new_leaf( - &self->tree_pool, - ts_builtin_sym_end, - length_zero(), - length_zero(), - 0, - 0, - false, - false, - self->language - ); - ts_parser__accept(self, 0, eof); -} - static bool ts_parser__recover_to_state( TSParser *self, StackVersion version, @@ -1644,8 +1615,8 @@ static unsigned ts_parser__condense_stack(TSParser *self) { static bool ts_parser_has_outstanding_parse(TSParser *self) { return ( - self->lexer.current_position.bytes > 0 || - ts_stack_state(self->stack, 0) != 1 + ts_stack_state(self->stack, 0) != 1 || + ts_stack_node_count_since_error(self->stack, 0) != 0 ); } @@ -1661,7 +1632,6 @@ TSParser *ts_parser_new(void) { self->finished_tree = NULL_SUBTREE; self->reusable_node = reusable_node_new(); self->dot_graph_file = NULL; - self->halt_on_error = false; self->cancellation_flag = NULL; self->timeout_duration = 0; self->end_clock = clock_null(); @@ -1741,10 +1711,6 @@ void ts_parser_print_dot_graphs(TSParser *self, int fd) { } } -void ts_parser_halt_on_error(TSParser *self, bool should_halt_on_error) { - self->halt_on_error = should_halt_on_error; -} - const size_t *ts_parser_cancellation_flag(const TSParser *self) { return (const size_t *)self->cancellation_flag; } @@ -1761,8 +1727,12 @@ void ts_parser_set_timeout_micros(TSParser *self, uint64_t timeout_micros) { self->timeout_duration = duration_from_micros(timeout_micros); } -void ts_parser_set_included_ranges(TSParser *self, const TSRange *ranges, uint32_t count) { - ts_lexer_set_included_ranges(&self->lexer, ranges, count); +bool ts_parser_set_included_ranges( + TSParser *self, + const TSRange *ranges, + uint32_t count +) { + return ts_lexer_set_included_ranges(&self->lexer, ranges, count); } const TSRange *ts_parser_included_ranges(const TSParser *self, uint32_t *count) { @@ -1858,9 +1828,6 @@ TSTree *ts_parser_parse( unsigned min_error_cost = ts_parser__condense_stack(self); if (self->finished_tree.ptr && ts_subtree_error_cost(self->finished_tree) < min_error_cost) { break; - } else if (self->halt_on_error && min_error_cost > 0) { - ts_parser__halt_parse(self); - break; } while (self->included_range_difference_index < self->included_range_differences.size) { diff --git a/src/tree_sitter/query.c b/src/tree_sitter/query.c index 2563325248..92a8006179 100644 --- a/src/tree_sitter/query.c +++ b/src/tree_sitter/query.c @@ -19,6 +19,8 @@ typedef struct { uint8_t next_size; } Stream; +#define MAX_STEP_CAPTURE_COUNT 4 + /* * QueryStep - A step in the process of matching a query. Each node within * a query S-expression maps to one of these steps. An entire pattern is @@ -33,13 +35,21 @@ typedef struct { * captured in this pattern. * - `depth` - The depth where this node occurs in the pattern. The root node * of the pattern has depth zero. + * - `repeat_step_index` - If this step is part of a repetition, the index of + * the beginning of the repetition. A `NONE` value means this step is not + * part of a repetition. */ typedef struct { TSSymbol symbol; TSFieldId field; - uint16_t capture_id; - uint16_t depth: 15; + uint16_t capture_ids[MAX_STEP_CAPTURE_COUNT]; + uint16_t repeat_step_index; + uint16_t depth: 11; bool contains_captures: 1; + bool is_pattern_start: 1; + bool is_immediate: 1; + bool is_last: 1; + bool is_repeated: 1; } QueryStep; /* @@ -62,10 +72,12 @@ typedef struct { } SymbolTable; /* - * PatternEntry - The set of steps needed to match a particular pattern, - * represented as a slice of a shared array. These entries are stored in a - * 'pattern map' - a sorted array that makes it possible to efficiently lookup - * patterns based on the symbol for their first step. + * PatternEntry - Information about the starting point for matching a + * particular pattern, consisting of the index of the pattern within the query, + * and the index of the patter's first step in the shared `steps` array. These + * entries are stored in a 'pattern map' - a sorted array that makes it + * possible to efficiently lookup patterns based on the symbol for their first + * step. */ typedef struct { uint16_t step_index; @@ -79,23 +91,27 @@ typedef struct { * represented as one of these states. */ typedef struct { + uint32_t id; uint16_t start_depth; uint16_t pattern_index; uint16_t step_index; - uint16_t capture_count; - uint16_t capture_list_id; uint16_t consumed_capture_count; - uint32_t id; + uint16_t repeat_match_count; + uint16_t step_index_on_failure; + uint8_t capture_list_id; + bool seeking_non_match; } QueryState; +typedef Array(TSQueryCapture) CaptureList; + /* * CaptureListPool - A collection of *lists* of captures. Each QueryState - * needs to maintain its own list of captures. They are all represented as - * slices of one shared array. The CaptureListPool keeps track of which - * parts of the shared array are currently in use by a QueryState. + * needs to maintain its own list of captures. To avoid repeated allocations, + * the reuses a fixed set of capture lists, and keeps track of which ones + * are currently in use. */ typedef struct { - Array(TSQueryCapture) list; + CaptureList list[32]; uint32_t usage_map; } CaptureListPool; @@ -113,7 +129,6 @@ struct TSQuery { Array(Slice) predicates_by_pattern; Array(uint32_t) start_bytes_by_pattern; const TSLanguage *language; - uint16_t max_capture_count; uint16_t wildcard_root_pattern_count; TSSymbol *symbol_map; }; @@ -140,6 +155,7 @@ static const TSQueryError PARENT_DONE = -1; static const uint8_t PATTERN_DONE_MARKER = UINT8_MAX; static const uint16_t NONE = UINT16_MAX; static const TSSymbol WILDCARD_SYMBOL = 0; +static const TSSymbol NAMED_WILDCARD_SYMBOL = UINT16_MAX - 1; static const uint16_t MAX_STATE_COUNT = 32; // #define LOG(...) fprintf(stderr, __VA_ARGS__) @@ -226,24 +242,25 @@ static void stream_scan_identifier(Stream *stream) { static CaptureListPool capture_list_pool_new(void) { return (CaptureListPool) { - .list = array_new(), .usage_map = UINT32_MAX, }; } -static void capture_list_pool_reset(CaptureListPool *self, uint16_t list_size) { +static void capture_list_pool_reset(CaptureListPool *self) { self->usage_map = UINT32_MAX; - uint32_t total_size = MAX_STATE_COUNT * list_size; - array_reserve(&self->list, total_size); - self->list.size = total_size; + for (unsigned i = 0; i < 32; i++) { + array_clear(&self->list[i]); + } } static void capture_list_pool_delete(CaptureListPool *self) { - array_delete(&self->list); + for (unsigned i = 0; i < 32; i++) { + array_delete(&self->list[i]); + } } -static TSQueryCapture *capture_list_pool_get(CaptureListPool *self, uint16_t id) { - return &self->list.contents[id * (self->list.size / MAX_STATE_COUNT)]; +static CaptureList *capture_list_pool_get(CaptureListPool *self, uint16_t id) { + return &self->list[id]; } static bool capture_list_pool_is_empty(const CaptureListPool *self) { @@ -262,6 +279,7 @@ static uint16_t capture_list_pool_acquire(CaptureListPool *self) { } static void capture_list_pool_release(CaptureListPool *self, uint16_t id) { + array_clear(&self->list[id]); self->usage_map |= bitmask_for_index(id); } @@ -324,6 +342,114 @@ static uint16_t symbol_table_insert_name( return self->slices.size - 1; } +static uint16_t symbol_table_insert_name_with_escapes( + SymbolTable *self, + const char *escaped_name, + uint32_t escaped_length +) { + Slice slice = { + .offset = self->characters.size, + .length = 0, + }; + array_grow_by(&self->characters, escaped_length + 1); + + // Copy the contents of the literal into the characters buffer, processing escape + // sequences like \n and \". This needs to be done before checking if the literal + // is already present, in order to do the string comparison. + bool is_escaped = false; + for (unsigned i = 0; i < escaped_length; i++) { + const char *src = &escaped_name[i]; + char *dest = &self->characters.contents[slice.offset + slice.length]; + if (is_escaped) { + switch (*src) { + case 'n': + *dest = '\n'; + break; + case 'r': + *dest = '\r'; + break; + case 't': + *dest = '\t'; + break; + case '0': + *dest = '\0'; + break; + default: + *dest = *src; + break; + } + is_escaped = false; + slice.length++; + } else { + if (*src == '\\') { + is_escaped = true; + } else { + *dest = *src; + slice.length++; + } + } + } + + // If the string is already present, remove the redundant content from the characters + // buffer and return the existing id. + int id = symbol_table_id_for_name(self, &self->characters.contents[slice.offset], slice.length); + if (id >= 0) { + self->characters.size -= (escaped_length + 1); + return id; + } + + self->characters.contents[slice.offset + slice.length] = 0; + array_push(&self->slices, slice); + return self->slices.size - 1; +} + +/************ + * QueryStep + ************/ + +static QueryStep query_step__new( + TSSymbol symbol, + uint16_t depth, + bool is_immediate +) { + return (QueryStep) { + .symbol = symbol, + .depth = depth, + .field = 0, + .capture_ids = {NONE, NONE, NONE, NONE}, + .contains_captures = false, + .is_repeated = false, + .is_last = false, + .is_pattern_start = false, + .is_immediate = is_immediate, + .repeat_step_index = NONE, + }; +} + +static void query_step__add_capture(QueryStep *self, uint16_t capture_id) { + for (unsigned i = 0; i < MAX_STEP_CAPTURE_COUNT; i++) { + if (self->capture_ids[i] == NONE) { + self->capture_ids[i] = capture_id; + break; + } + } +} + +static void query_step__remove_capture(QueryStep *self, uint16_t capture_id) { + for (unsigned i = 0; i < MAX_STEP_CAPTURE_COUNT; i++) { + if (self->capture_ids[i] == capture_id) { + self->capture_ids[i] = NONE; + while (i + 1 < MAX_STEP_CAPTURE_COUNT) { + if (self->capture_ids[i + 1] == NONE) break; + self->capture_ids[i] = self->capture_ids[i + 1]; + self->capture_ids[i + 1] = NONE; + i++; + } + break; + } + } +} + /********* * Query *********/ @@ -333,7 +459,7 @@ static uint16_t symbol_table_insert_name( // to quickly find the starting steps of all of the patterns whose root matches // that node. Each entry has two fields: a `pattern_index`, which identifies one // of the patterns in the query, and a `step_index`, which indicates the start -// offset of that pattern's steps pattern within the `steps` array. +// offset of that pattern's steps within the `steps` array. // // The entries are sorted by the patterns' root symbols, and lookups use a // binary search. This ensures that the cost of this initial lookup step @@ -399,14 +525,14 @@ static void ts_query__finalize_steps(TSQuery *self) { for (unsigned i = 0; i < self->steps.size; i++) { QueryStep *step = &self->steps.contents[i]; uint32_t depth = step->depth; - if (step->capture_id != NONE) { + if (step->capture_ids[0] != NONE) { step->contains_captures = true; } else { step->contains_captures = false; for (unsigned j = i + 1; j < self->steps.size; j++) { QueryStep *s = &self->steps.contents[j]; if (s->depth == PATTERN_DONE_MARKER || s->depth <= depth) break; - if (s->capture_id != NONE) step->contains_captures = true; + if (s->capture_ids[0] != NONE) step->contains_captures = true; } } } @@ -473,9 +599,22 @@ static TSQueryError ts_query__parse_predicate( stream_advance(stream); // Parse the string content + bool is_escaped = false; const char *string_content = stream->input; - while (stream->next != '"') { - if (stream->next == '\n' || !stream_advance(stream)) { + for (;;) { + if (is_escaped) { + is_escaped = false; + } else { + if (stream->next == '\\') { + is_escaped = true; + } else if (stream->next == '"') { + break; + } else if (stream->next == '\n') { + stream_reset(stream, string_content - 1); + return TSQueryErrorSyntax; + } + } + if (!stream_advance(stream)) { stream_reset(stream, string_content - 1); return TSQueryErrorSyntax; } @@ -483,7 +622,7 @@ static TSQueryError ts_query__parse_predicate( uint32_t length = stream->input - string_content; // Add a step for the node - uint16_t id = symbol_table_insert_name( + uint16_t id = symbol_table_insert_name_with_escapes( &self->predicate_values, string_content, length @@ -533,7 +672,8 @@ static TSQueryError ts_query__parse_pattern( TSQuery *self, Stream *stream, uint32_t depth, - uint32_t *capture_count + uint32_t *capture_count, + bool is_immediate ) { uint16_t starting_step_index = self->steps.size; @@ -552,7 +692,7 @@ static TSQueryError ts_query__parse_pattern( // Parse a nested list, which represents a pattern followed by // zero-or-more predicates. if (stream->next == '(' && depth == 0) { - TSQueryError e = ts_query__parse_pattern(self, stream, 0, capture_count); + TSQueryError e = ts_query__parse_pattern(self, stream, 0, capture_count, is_immediate); if (e) return e; // Parse the predicates. @@ -573,7 +713,7 @@ static TSQueryError ts_query__parse_pattern( // Parse the wildcard symbol if (stream->next == '*') { - symbol = WILDCARD_SYMBOL; + symbol = depth > 0 ? NAMED_WILDCARD_SYMBOL : WILDCARD_SYMBOL; stream_advance(stream); } @@ -597,24 +737,37 @@ static TSQueryError ts_query__parse_pattern( } // Add a step for the node. - array_push(&self->steps, ((QueryStep) { - .depth = depth, - .symbol = symbol, - .field = 0, - .capture_id = NONE, - .contains_captures = false, - })); + array_push(&self->steps, query_step__new(symbol, depth, is_immediate)); // Parse the child patterns stream_skip_whitespace(stream); + bool child_is_immediate = false; + uint16_t child_start_step_index = self->steps.size; for (;;) { - TSQueryError e = ts_query__parse_pattern(self, stream, depth + 1, capture_count); + if (stream->next == '.') { + child_is_immediate = true; + stream_advance(stream); + stream_skip_whitespace(stream); + } + + TSQueryError e = ts_query__parse_pattern( + self, + stream, + depth + 1, + capture_count, + child_is_immediate + ); if (e == PARENT_DONE) { + if (child_is_immediate) { + self->steps.contents[child_start_step_index].is_last = true; + } stream_advance(stream); break; } else if (e) { return e; } + + child_is_immediate = false; } } @@ -643,13 +796,7 @@ static TSQueryError ts_query__parse_pattern( stream_reset(stream, string_content); return TSQueryErrorNodeType; } - array_push(&self->steps, ((QueryStep) { - .depth = depth, - .symbol = symbol, - .field = 0, - .capture_id = NONE, - .contains_captures = false, - })); + array_push(&self->steps, query_step__new(symbol, depth, is_immediate)); if (stream->next != '"') return TSQueryErrorSyntax; stream_advance(stream); @@ -672,7 +819,13 @@ static TSQueryError ts_query__parse_pattern( // Parse the pattern uint32_t step_index = self->steps.size; - TSQueryError e = ts_query__parse_pattern(self, stream, depth, capture_count); + TSQueryError e = ts_query__parse_pattern( + self, + stream, + depth, + capture_count, + is_immediate + ); if (e == PARENT_DONE) return TSQueryErrorSyntax; if (e) return e; @@ -695,12 +848,7 @@ static TSQueryError ts_query__parse_pattern( stream_skip_whitespace(stream); // Add a step that matches any kind of node - array_push(&self->steps, ((QueryStep) { - .depth = depth, - .symbol = WILDCARD_SYMBOL, - .field = 0, - .contains_captures = false, - })); + array_push(&self->steps, query_step__new(WILDCARD_SYMBOL, depth, is_immediate)); } else { @@ -709,26 +857,43 @@ static TSQueryError ts_query__parse_pattern( stream_skip_whitespace(stream); - // Parse an '@'-prefixed capture pattern - if (stream->next == '@') { - stream_advance(stream); + // Parse suffixes modifiers for this pattern + for (;;) { + QueryStep *step = &self->steps.contents[starting_step_index]; - // Parse the capture name - if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax; - const char *capture_name = stream->input; - stream_scan_identifier(stream); - uint32_t length = stream->input - capture_name; + if (stream->next == '+') { + stream_advance(stream); + step->is_repeated = true; + array_back(&self->steps)->repeat_step_index = starting_step_index; + stream_skip_whitespace(stream); + } - // Add the capture id to the first step of the pattern - uint16_t capture_id = symbol_table_insert_name( - &self->captures, - capture_name, - length - ); - self->steps.contents[starting_step_index].capture_id = capture_id; - (*capture_count)++; + // Parse an '@'-prefixed capture pattern + else if (stream->next == '@') { + stream_advance(stream); - stream_skip_whitespace(stream); + // Parse the capture name + if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax; + const char *capture_name = stream->input; + stream_scan_identifier(stream); + uint32_t length = stream->input - capture_name; + + // Add the capture id to the first step of the pattern + uint16_t capture_id = symbol_table_insert_name( + &self->captures, + capture_name, + length + ); + query_step__add_capture(step, capture_id); + (*capture_count)++; + + stream_skip_whitespace(stream); + } + + // No more suffix modifiers + else { + break; + } } return 0; @@ -778,24 +943,22 @@ TSQuery *ts_query_new( .predicates_by_pattern = array_new(), .symbol_map = symbol_map, .wildcard_root_pattern_count = 0, - .max_capture_count = 0, .language = language, }; // Parse all of the S-expressions in the given string. Stream stream = stream_new(source, source_len); stream_skip_whitespace(&stream); - uint32_t start_step_index; while (stream.input < stream.end) { - start_step_index = self->steps.size; + uint32_t start_step_index = self->steps.size; uint32_t capture_count = 0; array_push(&self->start_bytes_by_pattern, stream.input - source); array_push(&self->predicates_by_pattern, ((Slice) { .offset = self->predicate_steps.size, .length = 0, })); - *error_type = ts_query__parse_pattern(self, &stream, 0, &capture_count); - array_push(&self->steps, ((QueryStep) { .depth = PATTERN_DONE_MARKER })); + *error_type = ts_query__parse_pattern(self, &stream, 0, &capture_count, false); + array_push(&self->steps, query_step__new(0, PATTERN_DONE_MARKER, false)); // If any pattern could not be parsed, then report the error information // and terminate. @@ -805,7 +968,19 @@ TSQuery *ts_query_new( return NULL; } + // If a pattern has a wildcard at its root, optimize the matching process + // by skipping matching the wildcard. + if ( + self->steps.contents[start_step_index].symbol == WILDCARD_SYMBOL + ) { + QueryStep *second_step = &self->steps.contents[start_step_index + 1]; + if (second_step->symbol != WILDCARD_SYMBOL && second_step->depth != PATTERN_DONE_MARKER) { + start_step_index += 1; + } + } + // Maintain a map that can look up patterns for a given root symbol. + self->steps.contents[start_step_index].is_pattern_start = true; ts_query__pattern_map_insert( self, self->steps.contents[start_step_index].symbol, @@ -814,13 +989,6 @@ TSQuery *ts_query_new( if (self->steps.contents[start_step_index].symbol == WILDCARD_SYMBOL) { self->wildcard_root_pattern_count++; } - - // Keep track of the maximum number of captures in pattern, because - // that numer determines how much space is needed to store each capture - // list. - if (capture_count > self->max_capture_count) { - self->max_capture_count = capture_count; - } } ts_query__finalize_steps(self); @@ -891,16 +1059,31 @@ void ts_query_disable_capture( const char *name, uint32_t length ) { + // Remove capture information for any pattern step that previously + // captured with the given name. int id = symbol_table_id_for_name(&self->captures, name, length); if (id != -1) { for (unsigned i = 0; i < self->steps.size; i++) { QueryStep *step = &self->steps.contents[i]; - if (step->capture_id == id) { - step->capture_id = NONE; - } + query_step__remove_capture(step, id); + } + ts_query__finalize_steps(self); + } +} + +void ts_query_disable_pattern( + TSQuery *self, + uint32_t pattern_index +) { + // Remove the given pattern from the pattern map. Its steps will still + // be in the `steps` array, but they will never be read. + for (unsigned i = 0; i < self->pattern_map.size; i++) { + PatternEntry *pattern = &self->pattern_map.contents[i]; + if (pattern->pattern_index == pattern_index) { + array_erase(&self->pattern_map, i); + i--; } } - ts_query__finalize_steps(self); } /*************** @@ -940,7 +1123,7 @@ void ts_query_cursor_exec( array_clear(&self->states); array_clear(&self->finished_states); ts_tree_cursor_reset(&self->cursor, node); - capture_list_pool_reset(&self->capture_list_pool, query->max_capture_count); + capture_list_pool_reset(&self->capture_list_pool); self->next_state_id = 0; self->depth = 0; self->ascending = false; @@ -984,12 +1167,12 @@ static bool ts_query_cursor__first_in_progress_capture( bool result = false; for (unsigned i = 0; i < self->states.size; i++) { const QueryState *state = &self->states.contents[i]; - if (state->capture_count > 0) { - const TSQueryCapture *captures = capture_list_pool_get( - &self->capture_list_pool, - state->capture_list_id - ); - uint32_t capture_byte = ts_node_start_byte(captures[0].node); + const CaptureList *captures = capture_list_pool_get( + &self->capture_list_pool, + state->capture_list_id + ); + if (captures->size > 0) { + uint32_t capture_byte = ts_node_start_byte(captures->contents[0].node); if ( !result || capture_byte < *byte_offset || @@ -1010,8 +1193,21 @@ static bool ts_query_cursor__first_in_progress_capture( static bool ts_query__cursor_add_state( TSQueryCursor *self, - const PatternEntry *slice + const PatternEntry *pattern ) { + QueryStep *step = &self->query->steps.contents[pattern->step_index]; + + // If this pattern begins with a repetition, then avoid creating + // new states after already matching the repetition one or more times. + // The query should only one match for the repetition - the one that + // started the earliest. + if (step->is_repeated) { + for (unsigned i = 0; i < self->states.size; i++) { + QueryState *state = &self->states.contents[i]; + if (state->step_index == pattern->step_index) return true; + } + } + uint32_t list_id = capture_list_pool_acquire(&self->capture_list_pool); // If there are no capture lists left in the pool, then terminate whichever @@ -1037,14 +1233,20 @@ static bool ts_query__cursor_add_state( } } - LOG(" start state. pattern:%u\n", slice->pattern_index); + LOG( + " start state. pattern:%u, step:%u\n", + pattern->pattern_index, + pattern->step_index + ); array_push(&self->states, ((QueryState) { .capture_list_id = list_id, - .step_index = slice->step_index, - .pattern_index = slice->pattern_index, - .start_depth = self->depth, - .capture_count = 0, + .step_index = pattern->step_index, + .pattern_index = pattern->pattern_index, + .start_depth = self->depth - step->depth, .consumed_capture_count = 0, + .repeat_match_count = 0, + .step_index_on_failure = NONE, + .seeking_non_match = false, })); return true; } @@ -1058,15 +1260,15 @@ static QueryState *ts_query__cursor_copy_state( array_push(&self->states, *state); QueryState *new_state = array_back(&self->states); new_state->capture_list_id = new_list_id; - TSQueryCapture *old_captures = capture_list_pool_get( + CaptureList *old_captures = capture_list_pool_get( &self->capture_list_pool, state->capture_list_id ); - TSQueryCapture *new_captures = capture_list_pool_get( + CaptureList *new_captures = capture_list_pool_get( &self->capture_list_pool, new_list_id ); - memcpy(new_captures, old_captures, state->capture_count * sizeof(TSQueryCapture)); + array_push_all(new_captures, old_captures); return new_state; } @@ -1113,15 +1315,16 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) { return self->finished_states.size > 0; } } else { - bool can_have_later_siblings; + bool has_later_siblings; bool can_have_later_siblings_with_this_field; TSFieldId field_id = ts_tree_cursor_current_status( &self->cursor, - &can_have_later_siblings, + &has_later_siblings, &can_have_later_siblings_with_this_field ); TSNode node = ts_tree_cursor_current_node(&self->cursor); TSSymbol symbol = ts_node_symbol(node); + bool is_named = ts_node_is_named(node); if (symbol != ts_builtin_sym_error && self->query->symbol_map) { symbol = self->query->symbol_map[symbol]; } @@ -1145,43 +1348,46 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) { ) return false; LOG( - "enter node. type:%s, field:%s, row:%u state_count:%u, finished_state_count:%u, can_have_later_siblings:%d, can_have_later_siblings_with_this_field:%d\n", + "enter node. " + "type:%s, field:%s, row:%u state_count:%u, " + "finished_state_count:%u, has_later_siblings:%d, " + "can_have_later_siblings_with_this_field:%d\n", ts_node_type(node), ts_language_field_name_for_id(self->query->language, field_id), ts_node_start_point(node).row, self->states.size, self->finished_states.size, - can_have_later_siblings, + has_later_siblings, can_have_later_siblings_with_this_field ); // Add new states for any patterns whose root node is a wildcard. for (unsigned i = 0; i < self->query->wildcard_root_pattern_count; i++) { - PatternEntry *slice = &self->query->pattern_map.contents[i]; - QueryStep *step = &self->query->steps.contents[slice->step_index]; + PatternEntry *pattern = &self->query->pattern_map.contents[i]; + QueryStep *step = &self->query->steps.contents[pattern->step_index]; // If this node matches the first step of the pattern, then add a new // state at the start of this pattern. if (step->field && field_id != step->field) continue; - if (!ts_query__cursor_add_state(self, slice)) break; + if (!ts_query__cursor_add_state(self, pattern)) break; } // Add new states for any patterns whose root node matches this node. unsigned i; if (ts_query__pattern_map_search(self->query, symbol, &i)) { - PatternEntry *slice = &self->query->pattern_map.contents[i]; - QueryStep *step = &self->query->steps.contents[slice->step_index]; + PatternEntry *pattern = &self->query->pattern_map.contents[i]; + QueryStep *step = &self->query->steps.contents[pattern->step_index]; do { // If this node matches the first step of the pattern, then add a new // state at the start of this pattern. if (step->field && field_id != step->field) continue; - if (!ts_query__cursor_add_state(self, slice)) break; + if (!ts_query__cursor_add_state(self, pattern)) break; // Advance to the next pattern whose root node matches this node. i++; if (i == self->query->pattern_map.size) break; - slice = &self->query->pattern_map.contents[i]; - step = &self->query->steps.contents[slice->step_index]; + pattern = &self->query->pattern_map.contents[i]; + step = &self->query->steps.contents[pattern->step_index]; } while (step->symbol == symbol); } @@ -1191,14 +1397,23 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) { QueryStep *step = &self->query->steps.contents[state->step_index]; // Check that the node matches all of the criteria for the next - // step of the pattern.if ( + // step of the pattern. if ((uint32_t)state->start_depth + (uint32_t)step->depth != self->depth) continue; // Determine if this node matches this step of the pattern, and also // if this node can have later siblings that match this step of the // pattern. - bool node_does_match = !step->symbol || step->symbol == symbol; - bool later_sibling_can_match = can_have_later_siblings; + bool node_does_match = + step->symbol == symbol || + step->symbol == WILDCARD_SYMBOL || + (step->symbol == NAMED_WILDCARD_SYMBOL && is_named); + bool later_sibling_can_match = has_later_siblings; + if (step->is_immediate && is_named) { + later_sibling_can_match = false; + } + if (step->is_last && has_later_siblings) { + node_does_match = false; + } if (step->field) { if (step->field == field_id) { if (!can_have_later_siblings_with_this_field) { @@ -1210,6 +1425,24 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) { } if (!node_does_match) { + // If this QueryState has processed a repeating sequence, and that repeating + // sequence has ended, move on to the *next* step of this state's pattern. + if ( + state->step_index_on_failure != NONE && + (!later_sibling_can_match || step->is_repeated) + ) { + LOG( + " finish repetition state. pattern:%u, step:%u\n", + state->pattern_index, + state->step_index + ); + state->step_index = state->step_index_on_failure; + state->step_index_on_failure = NONE; + state->repeat_match_count = 0; + i--; + continue; + } + if (!later_sibling_can_match) { LOG( " discard state. pattern:%u, step:%u\n", @@ -1224,9 +1457,17 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) { i--; n--; } + + state->seeking_non_match = false; continue; } + // The `seeking_non_match` flag indicates that a previous QueryState + // has already begun processing this repeating sequence, so that *this* + // QueryState should not begin matching until a separate repeating sequence + // is found. + if (state->seeking_non_match) continue; + // Some patterns can match their root node in multiple ways, // capturing different children. If this pattern step could match // later children within the same parent, then this query state @@ -1236,11 +1477,20 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) { // siblings. QueryState *next_state = state; if ( - step->depth > 0 && + !step->is_pattern_start && step->contains_captures && - later_sibling_can_match + later_sibling_can_match && + state->repeat_match_count == 0 ) { QueryState *copy = ts_query__cursor_copy_state(self, state); + + // The QueryState that matched this node has begun matching a repeating + // sequence. The QueryState that *skipped* this node should not start + // matching later elements of the same repeating sequence. + if (step->is_repeated) { + state->seeking_non_match = true; + } + if (copy) { LOG( " split state. pattern:%u, step:%u\n", @@ -1249,53 +1499,71 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) { ); next_state = copy; } else { - LOG(" canot split state.\n"); + LOG(" cannot split state.\n"); } } - LOG( - " advance state. pattern:%u, step:%u\n", - next_state->pattern_index, - next_state->step_index - ); - // If the current node is captured in this pattern, add it to the // capture list. - if (step->capture_id != NONE) { - LOG( - " capture node. pattern:%u, capture_id:%u\n", - next_state->pattern_index, - step->capture_id - ); - TSQueryCapture *capture_list = capture_list_pool_get( + for (unsigned j = 0; j < MAX_STEP_CAPTURE_COUNT; j++) { + uint16_t capture_id = step->capture_ids[j]; + if (step->capture_ids[j] == NONE) break; + CaptureList *capture_list = capture_list_pool_get( &self->capture_list_pool, next_state->capture_list_id ); - capture_list[next_state->capture_count++] = (TSQueryCapture) { + array_push(capture_list, ((TSQueryCapture) { node, - step->capture_id - }; + capture_id + })); + LOG( + " capture node. pattern:%u, capture_id:%u, capture_count:%u\n", + next_state->pattern_index, + capture_id, + capture_list->size + ); } - // If the pattern is now done, then remove it from the list of - // in-progress states, and add it to the list of finished states. - next_state->step_index++; - QueryStep *next_step = step + 1; - if (next_step->depth == PATTERN_DONE_MARKER) { - LOG(" finish pattern %u\n", next_state->pattern_index); + // If this is the end of a repetition, then jump back to the beginning + // of that repetition. + if (step->repeat_step_index != NONE) { + next_state->step_index_on_failure = next_state->step_index + 1; + next_state->step_index = step->repeat_step_index; + next_state->repeat_match_count++; + LOG( + " continue repeat. pattern:%u, match_count:%u\n", + next_state->pattern_index, + next_state->repeat_match_count + ); + } else { + next_state->step_index++; + LOG( + " advance state. pattern:%u, step:%u\n", + next_state->pattern_index, + next_state->step_index + ); - next_state->id = self->next_state_id++; - array_push(&self->finished_states, *next_state); - if (next_state == state) { - array_erase(&self->states, i); - i--; - n--; - } else { - self->states.size--; + QueryStep *next_step = step + 1; + + // If the pattern is now done, then remove it from the list of + // in-progress states, and add it to the list of finished states. + if (next_step->depth == PATTERN_DONE_MARKER) { + LOG(" finish pattern %u\n", next_state->pattern_index); + + next_state->id = self->next_state_id++; + array_push(&self->finished_states, *next_state); + if (next_state == state) { + array_erase(&self->states, i); + i--; + n--; + } else { + self->states.size--; + } } } } + // Continue descending if possible. if (ts_tree_cursor_goto_first_child(&self->cursor)) { self->depth++; @@ -1321,11 +1589,12 @@ bool ts_query_cursor_next_match( QueryState *state = &self->finished_states.contents[0]; match->id = state->id; match->pattern_index = state->pattern_index; - match->capture_count = state->capture_count; - match->captures = capture_list_pool_get( + CaptureList *captures = capture_list_pool_get( &self->capture_list_pool, state->capture_list_id ); + match->captures = captures->contents; + match->capture_count = captures->size; capture_list_pool_release(&self->capture_list_pool, state->capture_list_id); array_erase(&self->finished_states, 0); return true; @@ -1378,13 +1647,13 @@ bool ts_query_cursor_next_capture( uint32_t first_finished_pattern_index = first_unfinished_pattern_index; for (unsigned i = 0; i < self->finished_states.size; i++) { const QueryState *state = &self->finished_states.contents[i]; - if (state->capture_count > state->consumed_capture_count) { - const TSQueryCapture *captures = capture_list_pool_get( - &self->capture_list_pool, - state->capture_list_id - ); + CaptureList *captures = capture_list_pool_get( + &self->capture_list_pool, + state->capture_list_id + ); + if (captures->size > state->consumed_capture_count) { uint32_t capture_byte = ts_node_start_byte( - captures[state->consumed_capture_count].node + captures->contents[state->consumed_capture_count].node ); if ( capture_byte < first_finished_capture_byte || @@ -1416,11 +1685,12 @@ bool ts_query_cursor_next_capture( ]; match->id = state->id; match->pattern_index = state->pattern_index; - match->capture_count = state->capture_count; - match->captures = capture_list_pool_get( + CaptureList *captures = capture_list_pool_get( &self->capture_list_pool, state->capture_list_id ); + match->captures = captures->contents; + match->capture_count = captures->size; *capture_index = state->consumed_capture_count; state->consumed_capture_count++; return true; diff --git a/src/tree_sitter/stack.c b/src/tree_sitter/stack.c index 3e842c99c3..ade1577566 100644 --- a/src/tree_sitter/stack.c +++ b/src/tree_sitter/stack.c @@ -11,7 +11,7 @@ #define MAX_NODE_POOL_SIZE 50 #define MAX_ITERATOR_COUNT 64 -#ifdef _WIN32 +#if defined _WIN32 && !defined __GNUC__ #define inline __forceinline #else #define inline static inline __attribute__((always_inline)) diff --git a/src/tree_sitter/subtree.c b/src/tree_sitter/subtree.c index 30144fa175..b98f172339 100644 --- a/src/tree_sitter/subtree.c +++ b/src/tree_sitter/subtree.c @@ -322,12 +322,9 @@ void ts_subtree_balance(Subtree self, SubtreePool *pool, const TSLanguage *langu if (tree.ptr->repeat_depth > 0) { Subtree child1 = tree.ptr->children[0]; Subtree child2 = tree.ptr->children[tree.ptr->child_count - 1]; - if ( - ts_subtree_child_count(child1) > 0 && - ts_subtree_child_count(child2) > 0 && - child1.ptr->repeat_depth > child2.ptr->repeat_depth - ) { - unsigned n = child1.ptr->repeat_depth - child2.ptr->repeat_depth; + long repeat_delta = (long)ts_subtree_repeat_depth(child1) - (long)ts_subtree_repeat_depth(child2); + if (repeat_delta > 0) { + unsigned n = repeat_delta; for (unsigned i = n / 2; i > 0; i /= 2) { ts_subtree__compress(tree, i, language, &pool->tree_stack); n -= i; @@ -344,10 +341,6 @@ void ts_subtree_balance(Subtree self, SubtreePool *pool, const TSLanguage *langu } } -static inline uint32_t ts_subtree_repeat_depth(Subtree self) { - return ts_subtree_child_count(self) ? self.ptr->repeat_depth : 0; -} - void ts_subtree_set_children( MutableSubtree self, Subtree *children, uint32_t child_count, const TSLanguage *language ) { diff --git a/src/tree_sitter/subtree.h b/src/tree_sitter/subtree.h index 79ccd92390..18c48dcbd0 100644 --- a/src/tree_sitter/subtree.h +++ b/src/tree_sitter/subtree.h @@ -206,6 +206,10 @@ static inline uint32_t ts_subtree_child_count(Subtree self) { return self.data.is_inline ? 0 : self.ptr->child_count; } +static inline uint32_t ts_subtree_repeat_depth(Subtree self) { + return self.data.is_inline ? 0 : self.ptr->repeat_depth; +} + static inline uint32_t ts_subtree_node_count(Subtree self) { return (self.data.is_inline || self.ptr->child_count == 0) ? 1 : self.ptr->node_count; } diff --git a/src/tree_sitter/utf16.c b/src/tree_sitter/utf16.c deleted file mode 100644 index 3956c01cb9..0000000000 --- a/src/tree_sitter/utf16.c +++ /dev/null @@ -1,33 +0,0 @@ -#include "./utf16.h" - -utf8proc_ssize_t utf16_iterate( - const utf8proc_uint8_t *string, - utf8proc_ssize_t length, - utf8proc_int32_t *code_point -) { - if (length < 2) { - *code_point = -1; - return 0; - } - - uint16_t *units = (uint16_t *)string; - uint16_t unit = units[0]; - - if (unit < 0xd800 || unit >= 0xe000) { - *code_point = unit; - return 2; - } - - if (unit < 0xdc00) { - if (length >= 4) { - uint16_t next_unit = units[1]; - if (next_unit >= 0xdc00 && next_unit < 0xe000) { - *code_point = 0x10000 + ((unit - 0xd800) << 10) + (next_unit - 0xdc00); - return 4; - } - } - } - - *code_point = -1; - return 2; -} diff --git a/src/tree_sitter/utf16.h b/src/tree_sitter/utf16.h deleted file mode 100644 index 32fd05e6db..0000000000 --- a/src/tree_sitter/utf16.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef TREE_SITTER_UTF16_H_ -#define TREE_SITTER_UTF16_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <stdint.h> -#include <stdlib.h> -#include "utf8proc.h" - -// Analogous to utf8proc's utf8proc_iterate function. Reads one code point from -// the given UTF16 string and stores it in the location pointed to by `code_point`. -// Returns the number of bytes in `string` that were read. -utf8proc_ssize_t utf16_iterate(const utf8proc_uint8_t *, utf8proc_ssize_t, utf8proc_int32_t *); - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_UTF16_H_ diff --git a/test/busted/outputHandlers/TAP.lua b/test/busted/outputHandlers/TAP.lua index 8dc4ff55b6..5de48c0ad3 100644 --- a/test/busted/outputHandlers/TAP.lua +++ b/test/busted/outputHandlers/TAP.lua @@ -7,7 +7,7 @@ return function(options) local handler = require 'busted.outputHandlers.TAP'(options) local suiteEnd = function() - io.write(global_helpers.read_nvim_log()) + io.write(global_helpers.read_nvim_log(nil, true)) return nil, true end busted.subscribe({ 'suite', 'end' }, suiteEnd) diff --git a/test/busted/outputHandlers/nvim.lua b/test/busted/outputHandlers/nvim.lua index 8f3aad776e..5456e9ca98 100644 --- a/test/busted/outputHandlers/nvim.lua +++ b/test/busted/outputHandlers/nvim.lua @@ -196,7 +196,7 @@ return function(options) local tests = (testCount == 1 and 'test' or 'tests') local files = (fileCount == 1 and 'file' or 'files') io.write(globalTeardown) - io.write(global_helpers.read_nvim_log()) + io.write(global_helpers.read_nvim_log(nil, true)) io.write(suiteEndString:format(testCount, tests, fileCount, files, elapsedTime_ms)) io.write(getSummaryString()) io.flush() diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index d901a5e2eb..2e9d0f57ac 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -23,6 +23,7 @@ local pcall_err = helpers.pcall_err local format_string = helpers.format_string local intchar2lua = helpers.intchar2lua local mergedicts_copy = helpers.mergedicts_copy +local endswith = helpers.endswith describe('API', function() before_each(clear) @@ -570,6 +571,28 @@ describe('API', function() eq({0,7,1,0}, funcs.getpos('.')) eq(false, nvim('get_option', 'paste')) end) + it('Replace-mode', function() + -- Within single line + nvim('put', {'aabbccdd', 'eeffgghh', 'iijjkkll'}, "c", true, false) + command('normal l') + command('startreplace') + nvim('paste', '123456', true, -1) + expect([[ + a123456d + eeffgghh + iijjkkll]]) + command('%delete _') + -- Across lines + nvim('put', {'aabbccdd', 'eeffgghh', 'iijjkkll'}, "c", true, false) + command('normal l') + command('startreplace') + nvim('paste', '123\n456', true, -1) + expect([[ + a123 + 456d + eeffgghh + iijjkkll]]) + end) it('crlf=false does not break lines at CR, CRLF', function() nvim('paste', 'line 1\r\n\r\rline 2\nline 3\rline 4\r', false, -1) expect('line 1\r\n\r\rline 2\nline 3\rline 4\r') @@ -1853,4 +1876,27 @@ describe('API', function() command('silent! call nvim_create_buf(0, 1)') end) end) + + describe('nvim_get_runtime_file', function() + it('works', function() + eq({}, meths.get_runtime_file("bork.borkbork", false)) + eq({}, meths.get_runtime_file("bork.borkbork", true)) + eq(1, #meths.get_runtime_file("autoload/msgpack.vim", false)) + eq(1, #meths.get_runtime_file("autoload/msgpack.vim", true)) + local val = meths.get_runtime_file("autoload/remote/*.vim", true) + eq(2, #val) + local p = helpers.alter_slashes + if endswith(val[1], "define.vim") then + ok(endswith(val[1], p("autoload/remote/define.vim"))) + ok(endswith(val[2], p("autoload/remote/host.vim"))) + else + ok(endswith(val[1], p("autoload/remote/host.vim"))) + ok(endswith(val[2], p("autoload/remote/define.vim"))) + end + val = meths.get_runtime_file("autoload/remote/*.vim", false) + eq(1, #val) + ok(endswith(val[1], p("autoload/remote/define.vim")) + or endswith(val[1], p("autoload/remote/host.vim"))) + end) + end) end) diff --git a/test/functional/eval/ctx_functions_spec.lua b/test/functional/eval/ctx_functions_spec.lua index c81dad9645..f23adbc556 100644 --- a/test/functional/eval/ctx_functions_spec.lua +++ b/test/functional/eval/ctx_functions_spec.lua @@ -6,7 +6,7 @@ local command = helpers.command local eq = helpers.eq local eval = helpers.eval local feed = helpers.feed -local map = helpers.map +local map = helpers.tbl_map local nvim = helpers.nvim local parse_context = helpers.parse_context local redir_exec = helpers.redir_exec diff --git a/test/functional/ex_cmds/echo_spec.lua b/test/functional/ex_cmds/echo_spec.lua index 10c7230896..408ce52b8c 100644 --- a/test/functional/ex_cmds/echo_spec.lua +++ b/test/functional/ex_cmds/echo_spec.lua @@ -11,31 +11,57 @@ local dedent = helpers.dedent local command = helpers.command local exc_exec = helpers.exc_exec local redir_exec = helpers.redir_exec +local matches = helpers.matches + +describe(':echo :echon :echomsg :echoerr', function() + local fn_tbl = {'String', 'StringN', 'StringMsg', 'StringErr'} + local function assert_same_echo_dump(expected, input, use_eval) + for _,v in pairs(fn_tbl) do + eq(expected, use_eval and eval(v..'('..input..')') or funcs[v](input)) + end + end + local function assert_matches_echo_dump(expected, input, use_eval) + for _,v in pairs(fn_tbl) do + matches(expected, use_eval and eval(v..'('..input..')') or funcs[v](input)) + end + end -describe(':echo', function() before_each(function() clear() source([[ function String(s) return execute('echo a:s')[1:] endfunction + function StringMsg(s) + return execute('echomsg a:s')[1:] + endfunction + function StringN(s) + return execute('echon a:s') + endfunction + function StringErr(s) + try + execute 'echoerr a:s' + catch + return substitute(v:exception, '^Vim(echoerr):', '', '') + endtry + endfunction ]]) end) describe('used to represent floating-point values', function() it('dumps NaN values', function() - eq('str2float(\'nan\')', eval('String(str2float(\'nan\'))')) + assert_same_echo_dump("str2float('nan')", "str2float('nan')", true) end) it('dumps infinite values', function() - eq('str2float(\'inf\')', eval('String(str2float(\'inf\'))')) - eq('-str2float(\'inf\')', eval('String(str2float(\'-inf\'))')) + assert_same_echo_dump("str2float('inf')", "str2float('inf')", true) + assert_same_echo_dump("-str2float('inf')", "str2float('-inf')", true) end) it('dumps regular values', function() - eq('1.5', funcs.String(1.5)) - eq('1.56e-20', funcs.String(1.56000e-020)) - eq('0.0', eval('String(0.0)')) + assert_same_echo_dump('1.5', 1.5) + assert_same_echo_dump('1.56e-20', 1.56000e-020) + assert_same_echo_dump('0.0', '0.0', true) end) it('dumps special v: values', function() @@ -45,69 +71,81 @@ describe(':echo', function() eq('v:true', funcs.String(true)) eq('v:false', funcs.String(false)) eq('v:null', funcs.String(NIL)) + eq('true', eval('StringMsg(v:true)')) + eq('false', eval('StringMsg(v:false)')) + eq('null', eval('StringMsg(v:null)')) + eq('true', funcs.StringMsg(true)) + eq('false', funcs.StringMsg(false)) + eq('null', funcs.StringMsg(NIL)) + eq('true', eval('StringErr(v:true)')) + eq('false', eval('StringErr(v:false)')) + eq('null', eval('StringErr(v:null)')) + eq('true', funcs.StringErr(true)) + eq('false', funcs.StringErr(false)) + eq('null', funcs.StringErr(NIL)) end) it('dumps values with at most six digits after the decimal point', function() - eq('1.234568e-20', funcs.String(1.23456789123456789123456789e-020)) - eq('1.234568', funcs.String(1.23456789123456789123456789)) + assert_same_echo_dump('1.234568e-20', 1.23456789123456789123456789e-020) + assert_same_echo_dump('1.234568', 1.23456789123456789123456789) end) it('dumps values with at most seven digits before the decimal point', function() - eq('1234567.891235', funcs.String(1234567.89123456789123456789)) - eq('1.234568e7', funcs.String(12345678.9123456789123456789)) + assert_same_echo_dump('1234567.891235', 1234567.89123456789123456789) + assert_same_echo_dump('1.234568e7', 12345678.9123456789123456789) end) it('dumps negative values', function() - eq('-1.5', funcs.String(-1.5)) - eq('-1.56e-20', funcs.String(-1.56000e-020)) - eq('-1.234568e-20', funcs.String(-1.23456789123456789123456789e-020)) - eq('-1.234568', funcs.String(-1.23456789123456789123456789)) - eq('-1234567.891235', funcs.String(-1234567.89123456789123456789)) - eq('-1.234568e7', funcs.String(-12345678.9123456789123456789)) + assert_same_echo_dump('-1.5', -1.5) + assert_same_echo_dump('-1.56e-20', -1.56000e-020) + assert_same_echo_dump('-1.234568e-20', -1.23456789123456789123456789e-020) + assert_same_echo_dump('-1.234568', -1.23456789123456789123456789) + assert_same_echo_dump('-1234567.891235', -1234567.89123456789123456789) + assert_same_echo_dump('-1.234568e7', -12345678.9123456789123456789) end) end) describe('used to represent numbers', function() it('dumps regular values', function() - eq('0', funcs.String(0)) - eq('-1', funcs.String(-1)) - eq('1', funcs.String(1)) + assert_same_echo_dump('0', 0) + assert_same_echo_dump('-1', -1) + assert_same_echo_dump('1', 1) end) it('dumps large values', function() - eq('2147483647', funcs.String(2^31-1)) - eq('-2147483648', funcs.String(-2^31)) + assert_same_echo_dump('2147483647', 2^31-1) + assert_same_echo_dump('-2147483648', -2^31) end) end) describe('used to represent strings', function() it('dumps regular strings', function() - eq('test', funcs.String('test')) + assert_same_echo_dump('test', 'test') end) it('dumps empty strings', function() - eq('', funcs.String('')) + assert_same_echo_dump('', '') end) - it('dumps strings with \' inside', function() - eq('\'\'\'', funcs.String('\'\'\'')) - eq('a\'b\'\'', funcs.String('a\'b\'\'')) - eq('\'b\'\'d', funcs.String('\'b\'\'d')) - eq('a\'b\'c\'d', funcs.String('a\'b\'c\'d')) + it("dumps strings with ' inside", function() + assert_same_echo_dump("'''", "'''") + assert_same_echo_dump("a'b''", "a'b''") + assert_same_echo_dump("'b''d", "'b''d") + assert_same_echo_dump("a'b'c'd", "a'b'c'd") end) it('dumps NULL strings', function() - eq('', eval('String($XXX_UNEXISTENT_VAR_XXX)')) + assert_same_echo_dump('', '$XXX_UNEXISTENT_VAR_XXX', true) end) it('dumps NULL lists', function() - eq('[]', eval('String(v:_null_list)')) + assert_same_echo_dump('[]', 'v:_null_list', true) end) it('dumps NULL dictionaries', function() - eq('{}', eval('String(v:_null_dict)')) + assert_same_echo_dump('{}', 'v:_null_dict', true) end) end) @@ -129,15 +167,27 @@ describe(':echo', function() it('dumps references to built-in functions', function() eq('function', eval('String(function("function"))')) + eq("function('function')", eval('StringMsg(function("function"))')) + eq("function('function')", eval('StringErr(function("function"))')) end) it('dumps references to user functions', function() eq('Test1', eval('String(function("Test1"))')) eq('g:Test3', eval('String(function("g:Test3"))')) + eq("function('Test1')", eval("StringMsg(function('Test1'))")) + eq("function('g:Test3')", eval("StringMsg(function('g:Test3'))")) + eq("function('Test1')", eval("StringErr(function('Test1'))")) + eq("function('g:Test3')", eval("StringErr(function('g:Test3'))")) end) it('dumps references to script functions', function() eq('<SNR>2_Test2', eval('String(Test2_f)')) + eq("function('<SNR>2_Test2')", eval('StringMsg(Test2_f)')) + eq("function('<SNR>2_Test2')", eval('StringErr(Test2_f)')) + end) + + it('dump references to lambdas', function() + assert_matches_echo_dump("function%('<lambda>%d+'%)", '{-> 1234}', true) end) it('dumps partials with self referencing a partial', function() @@ -156,19 +206,23 @@ describe(':echo', function() end) it('dumps automatically created partials', function() - eq('function(\'<SNR>2_Test2\', {\'f\': function(\'<SNR>2_Test2\')})', - eval('String({"f": Test2_f}.f)')) - eq('function(\'<SNR>2_Test2\', [1], {\'f\': function(\'<SNR>2_Test2\', [1])})', - eval('String({"f": function(Test2_f, [1])}.f)')) + assert_same_echo_dump( + "function('<SNR>2_Test2', {'f': function('<SNR>2_Test2')})", + '{"f": Test2_f}.f', + true) + assert_same_echo_dump( + "function('<SNR>2_Test2', [1], {'f': function('<SNR>2_Test2', [1])})", + '{"f": function(Test2_f, [1])}.f', + true) end) it('dumps manually created partials', function() - eq('function(\'Test3\', [1, 2], {})', - eval('String(function("Test3", [1, 2], {}))')) - eq('function(\'Test3\', {})', - eval('String(function("Test3", {}))')) - eq('function(\'Test3\', [1, 2])', - eval('String(function("Test3", [1, 2]))')) + assert_same_echo_dump("function('Test3', [1, 2], {})", + "function('Test3', [1, 2], {})", true) + assert_same_echo_dump("function('Test3', [1, 2])", + "function('Test3', [1, 2])", true) + assert_same_echo_dump("function('Test3', {})", + "function('Test3', {})", true) end) it('does not crash or halt when dumping partials with reference cycles in self', @@ -225,15 +279,19 @@ describe(':echo', function() describe('used to represent lists', function() it('dumps empty list', function() - eq('[]', funcs.String({})) + assert_same_echo_dump('[]', {}) + end) + + it('dumps non-empty list', function() + assert_same_echo_dump('[1, 2]', {1,2}) end) it('dumps nested lists', function() - eq('[[[[[]]]]]', funcs.String({{{{{}}}}})) + assert_same_echo_dump('[[[[[]]]]]', {{{{{}}}}}) end) it('dumps nested non-empty lists', function() - eq('[1, [[3, [[5], 4]], 2]]', funcs.String({1, {{3, {{5}, 4}}, 2}})) + assert_same_echo_dump('[1, [[3, [[5], 4]], 2]]', {1, {{3, {{5}, 4}}, 2}}) end) it('does not error when dumping recursive lists', function() @@ -252,18 +310,18 @@ describe(':echo', function() describe('used to represent dictionaries', function() it('dumps empty dictionary', function() - eq('{}', eval('String({})')) + assert_same_echo_dump('{}', '{}', true) end) it('dumps list with two same empty dictionaries, also in partials', function() command('let d = {}') - eq('[{}, {}]', eval('String([d, d])')) + assert_same_echo_dump('[{}, {}]', '[d, d]', true) eq('[function(\'tr\', {}), {}]', eval('String([function("tr", d), d])')) eq('[{}, function(\'tr\', {})]', eval('String([d, function("tr", d)])')) end) it('dumps non-empty dictionary', function() - eq('{\'t\'\'est\': 1}', funcs.String({['t\'est']=1})) + assert_same_echo_dump("{'t''est': 1}", {["t'est"]=1}) end) it('does not error when dumping recursive dictionaries', function() @@ -297,11 +355,20 @@ describe(':echo', function() eq('<8e>', funcs.String(chr(0x8e))) eq('<c2>', funcs.String(('«'):sub(1, 1))) eq('«', funcs.String(('«'):sub(1, 2))) + + eq('<80>', funcs.StringMsg(chr(0x80))) + eq('<81>', funcs.StringMsg(chr(0x81))) + eq('<8e>', funcs.StringMsg(chr(0x8e))) + eq('<c2>', funcs.StringMsg(('«'):sub(1, 1))) + eq('«', funcs.StringMsg(('«'):sub(1, 2))) end) it('displays ASCII control characters using ^X notation', function() eq('^C', funcs.String(ctrl('c'))) eq('^A', funcs.String(ctrl('a'))) eq('^F', funcs.String(ctrl('f'))) + eq('^C', funcs.StringMsg(ctrl('c'))) + eq('^A', funcs.StringMsg(ctrl('a'))) + eq('^F', funcs.StringMsg(ctrl('f'))) end) it('prints CR, NL and tab as-is', function() eq('\n', funcs.String('\n')) @@ -311,11 +378,15 @@ describe(':echo', function() it('prints non-printable UTF-8 in <> notation', function() -- SINGLE SHIFT TWO, unicode control eq('<8e>', funcs.String(funcs.nr2char(0x8E))) + eq('<8e>', funcs.StringMsg(funcs.nr2char(0x8E))) -- Surrogate pair: U+1F0A0 PLAYING CARD BACK is represented in UTF-16 as -- 0xD83C 0xDCA0. This is not valid in UTF-8. eq('<d83c>', funcs.String(funcs.nr2char(0xD83C))) eq('<dca0>', funcs.String(funcs.nr2char(0xDCA0))) eq('<d83c><dca0>', funcs.String(funcs.nr2char(0xD83C) .. funcs.nr2char(0xDCA0))) + eq('<d83c>', funcs.StringMsg(funcs.nr2char(0xD83C))) + eq('<dca0>', funcs.StringMsg(funcs.nr2char(0xDCA0))) + eq('<d83c><dca0>', funcs.StringMsg(funcs.nr2char(0xD83C) .. funcs.nr2char(0xDCA0))) end) end) end) diff --git a/test/functional/ex_cmds/ls_spec.lua b/test/functional/ex_cmds/ls_spec.lua index f7bacd7386..9853084c47 100644 --- a/test/functional/ex_cmds/ls_spec.lua +++ b/test/functional/ex_cmds/ls_spec.lua @@ -31,6 +31,18 @@ describe(':ls', function() -- Terminal buffer [F]inished. eq('\n 3 %aF', string.match(ls_output, '\n *3....')) end) + + retry(nil, 5000, function() + local ls_output = eval('execute("ls R")') + -- Just the [R]unning terminal buffer. + eq('\n 2 #aR ', string.match(ls_output, '^\n *2 ... ')) + end) + + retry(nil, 5000, function() + local ls_output = eval('execute("ls F")') + -- Just the [F]inished terminal buffer. + eq('\n 3 %aF ', string.match(ls_output, '^\n *3 ... ')) + end) end) end) diff --git a/test/functional/ex_cmds/mksession_spec.lua b/test/functional/ex_cmds/mksession_spec.lua index 855f8105aa..949724bb53 100644 --- a/test/functional/ex_cmds/mksession_spec.lua +++ b/test/functional/ex_cmds/mksession_spec.lua @@ -26,6 +26,27 @@ describe(':mksession', function() rmdir(tab_dir) end) + it('restores same :terminal buf in splits', function() + -- If the same :terminal is displayed in multiple windows, :mksession + -- should restore it as such. + + -- Create two windows showing the same :terminal buffer. + command('terminal') + command('split') + command('terminal') + command('split') + command('mksession '..session_file) + + -- Create a new test instance of Nvim. + command('qall!') + clear() + -- Restore session. + command('source '..session_file) + + eq({3,3,2}, + {funcs.winbufnr(1), funcs.winbufnr(2), funcs.winbufnr(3)}) + end) + it('restores tab-local working directories', function() local tmpfile_base = file_prefix .. '-tmpfile' local cwd_dir = funcs.getcwd() @@ -75,7 +96,8 @@ describe(':mksession', function() it('restores CWD for :terminal buffers #11288', function() local cwd_dir = funcs.fnamemodify('.', ':p:~'):gsub([[[\/]*$]], '') - local session_path = cwd_dir..get_pathsep()..session_file + cwd_dir = cwd_dir:gsub([[\]], '/') -- :mksession always uses unix slashes. + local session_path = cwd_dir..'/'..session_file command('cd '..tab_dir) command('terminal echo $PWD') @@ -87,7 +109,7 @@ describe(':mksession', function() clear() command('silent source '..session_path) - local expected_cwd = cwd_dir..get_pathsep()..tab_dir + local expected_cwd = cwd_dir..'/'..tab_dir matches('^term://'..pesc(expected_cwd)..'//%d+:', funcs.expand('%')) command('qall!') end) diff --git a/test/functional/ex_cmds/profile_spec.lua b/test/functional/ex_cmds/profile_spec.lua index f185db192a..2b92f8d0de 100644 --- a/test/functional/ex_cmds/profile_spec.lua +++ b/test/functional/ex_cmds/profile_spec.lua @@ -6,6 +6,9 @@ local eval = helpers.eval local command = helpers.command local eq, neq = helpers.eq, helpers.neq local tempfile = helpers.tmpname() +local source = helpers.source +local matches = helpers.matches +local read_file = helpers.read_file -- tmpname() also creates the file on POSIX systems. Remove it again. -- We just need the name, ignoring any race conditions. @@ -32,20 +35,67 @@ describe(':profile', function() end end) - it('dump', function() - eq(0, eval('v:profiling')) - command('profile start ' .. tempfile) - eq(1, eval('v:profiling')) - assert_file_exists_not(tempfile) - command('profile dump') - assert_file_exists(tempfile) + describe('dump', function() + it('works', function() + eq(0, eval('v:profiling')) + command('profile start ' .. tempfile) + eq(1, eval('v:profiling')) + assert_file_exists_not(tempfile) + command('profile dump') + assert_file_exists(tempfile) + end) + + it('not resetting the profile', function() + source([[ + function! Test() + endfunction + ]]) + command('profile start ' .. tempfile) + assert_file_exists_not(tempfile) + command('profile func Test') + command('call Test()') + command('profile dump') + assert_file_exists(tempfile) + local profile = read_file(tempfile) + matches('Called 1 time', profile) + command('call Test()') + command('profile dump') + assert_file_exists(tempfile) + profile = read_file(tempfile) + matches('Called 2 time', profile) + command('profile stop') + end) end) - it('stop', function() - command('profile start ' .. tempfile) - assert_file_exists_not(tempfile) - command('profile stop') - assert_file_exists(tempfile) - eq(0, eval('v:profiling')) + describe('stop', function() + it('works', function() + command('profile start ' .. tempfile) + assert_file_exists_not(tempfile) + command('profile stop') + assert_file_exists(tempfile) + eq(0, eval('v:profiling')) + end) + + it('resetting the profile', function() + source([[ + function! Test() + endfunction + ]]) + command('profile start ' .. tempfile) + assert_file_exists_not(tempfile) + command('profile func Test') + command('call Test()') + command('profile stop') + assert_file_exists(tempfile) + local profile = read_file(tempfile) + matches('Called 1 time', profile) + command('profile start ' .. tempfile) + command('profile func Test') + command('call Test()') + command('profile stop') + assert_file_exists(tempfile) + profile = read_file(tempfile) + matches('Called 1 time', profile) + end) end) end) diff --git a/test/functional/fixtures/lsp-test-rpc-server.lua b/test/functional/fixtures/fake-lsp-server.lua index 798883ced0..dca7f35923 100644 --- a/test/functional/fixtures/lsp-test-rpc-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -1,23 +1,14 @@ local protocol = require 'vim.lsp.protocol' --- Internal utility methods. --- TODO replace with a better implementation. -local function json_encode(data) - local status, result = pcall(vim.fn.json_encode, data) - if status then - return result - else - return nil, result - end -end -local function json_decode(data) - local status, result = pcall(vim.fn.json_decode, data) - if status then - return result - else - return nil, result - end +-- Logs to $NVIM_LOG_FILE. +-- +-- TODO(justinmk): remove after https://github.com/neovim/neovim/pull/7062 +local function log(loglevel, area, msg) + vim.fn.writefile( + {string.format('%s %s: %s', loglevel, area, msg)}, + vim.env.NVIM_LOG_FILE, + 'a') end local function message_parts(sep, ...) @@ -49,16 +40,14 @@ local function format_message_with_content_length(encoded_message) } end --- Server utility methods. - local function read_message() local line = io.read("*l") local length = line:lower():match("content%-length:%s*(%d+)") - return assert(json_decode(io.read(2 + length):sub(2)), "read_message.json_decode") + return vim.fn.json_decode(io.read(2 + length):sub(2)) end local function send(payload) - io.stdout:write(format_message_with_content_length(json_encode(payload))) + io.stdout:write(format_message_with_content_length(vim.fn.json_encode(payload))) end local function respond(id, err, result) @@ -390,7 +379,7 @@ function tests.basic_check_buffer_open_and_change_incremental() } end -function tests.basic_check_buffer_open_and_change_incremental_editting() +function tests.basic_check_buffer_open_and_change_incremental_editing() skeleton { on_init = function(params) local expected_capabilities = protocol.make_client_capabilities() @@ -443,6 +432,7 @@ local kill_timer = vim.loop.new_timer() kill_timer:start(_G.TIMEOUT or 1e3, 0, function() kill_timer:stop() kill_timer:close() + log('ERROR', 'LSP', 'TIMEOUT') io.stderr:write("TIMEOUT") os.exit(100) end) @@ -453,7 +443,8 @@ local status, err = pcall(assert(tests[test_name], "Test not found")) kill_timer:stop() kill_timer:close() if not status then + log('ERROR', 'LSP', tostring(err)) io.stderr:write(err) - os.exit(1) + os.exit(101) end os.exit(0) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 3ffc6137d6..e8435cd3b7 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -15,9 +15,9 @@ local check_cores = global_helpers.check_cores local check_logs = global_helpers.check_logs local dedent = global_helpers.dedent local eq = global_helpers.eq -local filter = global_helpers.filter +local filter = global_helpers.tbl_filter local is_os = global_helpers.is_os -local map = global_helpers.map +local map = global_helpers.tbl_map local ok = global_helpers.ok local sleep = global_helpers.sleep local tbl_contains = global_helpers.tbl_contains @@ -794,7 +794,7 @@ function module.alter_slashes(obj) end return ret else - assert(false, 'Could only alter slashes for tables of strings and strings') + assert(false, 'expected string or table of strings, got '..type(obj)) end end diff --git a/test/functional/legacy/021_control_wi_spec.lua b/test/functional/legacy/021_control_wi_spec.lua index 87d9deed7a..94871433cd 100644 --- a/test/functional/legacy/021_control_wi_spec.lua +++ b/test/functional/legacy/021_control_wi_spec.lua @@ -18,7 +18,7 @@ describe('CTRL-W CTRL-I', function() start found wrong line test text]]) - -- Search for the second occurence of start and append to register + -- Search for the second occurrence of start and append to register feed_command('/start') feed('2[<C-i>') feed_command('yank A') diff --git a/test/functional/legacy/097_glob_path_spec.lua b/test/functional/legacy/097_glob_path_spec.lua index ccd93fed60..dd5a26ad3b 100644 --- a/test/functional/legacy/097_glob_path_spec.lua +++ b/test/functional/legacy/097_glob_path_spec.lua @@ -52,7 +52,7 @@ describe('glob() and globpath()', function() command([[$put =glob('Xxx\{')]]) command([[$put =glob('Xxx\$')]]) - command('silent w! Xxx{') + command('silent w! Xxx\\{') command([[w! Xxx\$]]) command([[$put =glob('Xxx\{')]]) command([[$put =glob('Xxx\$')]]) diff --git a/test/functional/legacy/eval_spec.lua b/test/functional/legacy/eval_spec.lua index c5d38d6d05..4198ea8bfe 100644 --- a/test/functional/legacy/eval_spec.lua +++ b/test/functional/legacy/eval_spec.lua @@ -360,7 +360,7 @@ describe('eval', function() abcD3b]]) -- From now on we delete the buffer contents after each expect() to make - -- the next expect() easier to write. This is neccessary because null + -- the next expect() easier to write. This is necessary because null -- bytes on a line by itself don't play well together with the dedent -- function used in expect(). command('%delete') @@ -416,7 +416,7 @@ describe('eval', function() ' abcD3b50') end) - -- The tests for setting lists with NLs are split into seperate it() blocks + -- The tests for setting lists with NLs are split into separate it() blocks -- to make the expect() calls easier to write. Otherwise the null byte can -- make trouble on a line on its own. it('setting lists with NLs with setreg(), part 1', function() diff --git a/test/functional/legacy/expand_spec.lua b/test/functional/legacy/expand_spec.lua index 1b735080f4..f238128b31 100644 --- a/test/functional/legacy/expand_spec.lua +++ b/test/functional/legacy/expand_spec.lua @@ -70,6 +70,40 @@ describe('expand file name', function() call assert_match('\~', expand('%:p')) bwipe! endfunc + + func Test_expandcmd() + let $FOO = 'Test' + call assert_equal('e x/Test/y', expandcmd('e x/$FOO/y')) + unlet $FOO + + new + edit Xfile1 + call assert_equal('e Xfile1', expandcmd('e %')) + edit Xfile2 + edit Xfile1 + call assert_equal('e Xfile2', expandcmd('e #')) + edit Xfile2 + edit Xfile3 + edit Xfile4 + let bnum = bufnr('Xfile2') + call assert_equal('e Xfile2', expandcmd('e #' . bnum)) + call setline('.', 'Vim!@#') + call assert_equal('e Vim', expandcmd('e <cword>')) + call assert_equal('e Vim!@#', expandcmd('e <cWORD>')) + enew! + edit Xfile.java + call assert_equal('e Xfile.py', expandcmd('e %:r.py')) + call assert_equal('make abc.java', expandcmd('make abc.%:e')) + call assert_equal('make Xabc.java', expandcmd('make %:s?file?abc?')) + edit a1a2a3.rb + call assert_equal('make b1b2b3.rb a1a2a3 Xfile.o', expandcmd('make %:gs?a?b? %< #<.o')) + + call assert_fails('call expandcmd("make <afile>")', 'E495:') + call assert_fails('call expandcmd("make <afile>")', 'E495:') + enew + call assert_fails('call expandcmd("make %")', 'E499:') + close + endfunc ]]) end) @@ -87,4 +121,9 @@ describe('expand file name', function() call('Test_expand_tilde_filename') expected_empty() end) + + it('works with expandcmd()', function() + call('Test_expandcmd') + expected_empty() + end) end) diff --git a/test/functional/legacy/prompt_buffer_spec.lua b/test/functional/legacy/prompt_buffer_spec.lua new file mode 100644 index 0000000000..513be807be --- /dev/null +++ b/test/functional/legacy/prompt_buffer_spec.lua @@ -0,0 +1,153 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local feed= helpers.feed +local source = helpers.source +local clear = helpers.clear +local feed_command = helpers.feed_command + +describe('prompt buffer', function() + local screen + + before_each(function() + clear() + screen = Screen.new(25, 10) + screen:attach() + source([[ + func TextEntered(text) + if a:text == "exit" + set nomodified + stopinsert + close + else + call append(line("$") - 1, 'Command: "' . a:text . '"') + set nomodfied + call timer_start(20, {id -> TimerFunc(a:text)}) + endif + endfunc + + func TimerFunc(text) + call append(line("$") - 1, 'Result: "' . a:text .'"') + endfunc + ]]) + feed_command("set noshowmode | set laststatus=0") + feed_command("call setline(1, 'other buffer')") + feed_command("new") + feed_command("set buftype=prompt") + feed_command("call prompt_setcallback(bufnr(''), function('TextEntered'))") + end) + + after_each(function() + screen:detach() + end) + + it('works', function() + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + [Prompt] | + other buffer | + ~ | + ~ | + ~ | + | + ]]) + feed("i") + feed("hello\n") + screen:expect([[ + % hello | + Command: "hello" | + Result: "hello" | + % ^ | + [Prompt] [+] | + other buffer | + ~ | + ~ | + ~ | + | + ]]) + feed("exit\n") + screen:expect([[ + ^other buffer | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + | + ]]) + end) + + it('editing', function() + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + [Prompt] | + other buffer | + ~ | + ~ | + ~ | + | + ]]) + feed("i") + feed("hello<BS><BS>") + screen:expect([[ + % hel^ | + ~ | + ~ | + ~ | + [Prompt] [+] | + other buffer | + ~ | + ~ | + ~ | + | + ]]) + feed("<Left><Left><Left><BS>-") + screen:expect([[ + % -^hel | + ~ | + ~ | + ~ | + [Prompt] [+] | + other buffer | + ~ | + ~ | + ~ | + | + ]]) + feed("<End>x") + screen:expect([[ + % -helx^ | + ~ | + ~ | + ~ | + [Prompt] [+] | + other buffer | + ~ | + ~ | + ~ | + | + ]]) + feed("<C-U>exit\n") + screen:expect([[ + ^other buffer | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + | + ]]) + end) + +end) diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index 990cb97fec..77f8189bb9 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -98,7 +98,7 @@ describe('lua: buffer event callbacks', function() command('undo') -- plugins can opt in to receive changedtick events, or choose - -- to only recieve actual changes. + -- to only receive actual changes. check_events({{ "test1", "lines", 1, tick, 3, 4, 5, 13 }, { "test2", "lines", 1, tick, 3, 4, 5, 13 }, { "test2", "changedtick", 1, tick+1 } }) @@ -111,7 +111,7 @@ describe('lua: buffer event callbacks', function() tick = tick + 1 -- plugins can opt in to receive changedtick events, or choose - -- to only recieve actual changes. + -- to only receive actual changes. check_events({{ "test1", "lines", 1, tick, 6, 7, 9, 16 }, { "test2", "lines", 1, tick, 6, 7, 9, 16 }}) @@ -203,4 +203,17 @@ describe('lua: buffer event callbacks', function() { "test1", "lines", 1, tick+1, 5, 6, 5, 27, 20, 20 }}, exec_lua("return get_events(...)" )) end) + it('has valid cursor position while shifting', function() + meths.buf_set_lines(0, 0, -1, true, {'line1'}) + exec_lua([[ + vim.api.nvim_buf_attach(0, false, { + on_lines = function() + vim.api.nvim_set_var('listener_cursor_line', vim.api.nvim_win_get_cursor(0)[1]) + end, + }) + ]]) + feed('>>') + eq(1, meths.get_var('listener_cursor_line')) + end) + end) diff --git a/test/functional/lua/treesitter_spec.lua b/test/functional/lua/treesitter_spec.lua index 76e9899d34..f93185d1f6 100644 --- a/test/functional/lua/treesitter_spec.lua +++ b/test/functional/lua/treesitter_spec.lua @@ -6,7 +6,6 @@ local clear = helpers.clear local eq = helpers.eq local insert = helpers.insert local exec_lua = helpers.exec_lua -local iswin = helpers.iswin local feed = helpers.feed local pcall_err = helpers.pcall_err local matches = helpers.matches @@ -16,37 +15,35 @@ before_each(clear) describe('treesitter API', function() -- error tests not requiring a parser library it('handles missing language', function() - eq('Error executing lua: .../treesitter.lua: no such language: borklang', + eq("Error executing lua: .../treesitter.lua: no parser for 'borklang' language", pcall_err(exec_lua, "parser = vim.treesitter.create_parser(0, 'borklang')")) -- actual message depends on platform - matches('Error executing lua: Failed to load parser: uv_dlopen: .+', - pcall_err(exec_lua, "parser = vim.treesitter.add_language('borkbork.so', 'borklang')")) + matches("Error executing lua: Failed to load parser: uv_dlopen: .+", + pcall_err(exec_lua, "parser = vim.treesitter.require_language('borklang', 'borkbork.so')")) - eq('Error executing lua: [string "<nvim>"]:1: no such language: borklang', + eq("Error executing lua: .../treesitter.lua: no parser for 'borklang' language", pcall_err(exec_lua, "parser = vim.treesitter.inspect_language('borklang')")) end) end) describe('treesitter API with C parser', function() - local ts_path = os.getenv("TREE_SITTER_DIR") - - -- The tests after this requires an actual parser - if ts_path == nil then - it("works", function() pending("TREE_SITTER_PATH not set, skipping treesitter parser tests") end) - return + local function check_parser() + local status, msg = unpack(exec_lua([[ return {pcall(vim.treesitter.require_language, 'c')} ]])) + if not status then + if helpers.isCI() then + error("treesitter C parser not found, required on CI: " .. msg) + else + pending('no C parser, skipping') + end + end + return status end - before_each(function() - local path = ts_path .. '/bin/c'..(iswin() and '.dll' or '.so') - exec_lua([[ - local path = ... - vim.treesitter.add_language(path,'c') - ]], path) - end) - it('parses buffer', function() + if not check_parser() then return end + insert([[ int main() { int x = 3; @@ -138,6 +135,8 @@ void ui_refresh(void) ]] it('support query and iter by capture', function() + if not check_parser() then return end + insert(test_text) local res = exec_lua([[ @@ -167,6 +166,8 @@ void ui_refresh(void) end) it('support query and iter by match', function() + if not check_parser() then return end + insert(test_text) local res = exec_lua([[ @@ -198,6 +199,8 @@ void ui_refresh(void) end) it('supports highlighting', function() + if not check_parser() then return end + local hl_text = [[ /// Schedule Lua callback on main loop's event queue static int nlua_schedule(lua_State *const lstate) @@ -242,6 +245,11 @@ static int nlua_schedule(lua_State *const lstate) (primitive_type) @type (sized_type_specifier) @type +; defaults to very magic syntax, for best compatibility +((identifier) @Identifier (match? @Identifier "^l(u)a_")) +; still support \M etc prefixes +((identifier) @Constant (match? @Constant "\M^\[A-Z_]\+$")) + ((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) (eq? @WarningMsg.left @WarningMsg.right)) (comment) @comment @@ -260,7 +268,7 @@ static int nlua_schedule(lua_State *const lstate) [8] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, [9] = {foreground = Screen.colors.Magenta, background = Screen.colors.Red}, [10] = {foreground = Screen.colors.Red, background = Screen.colors.Red}, - + [11] = {foreground = Screen.colors.Cyan4}, }) insert(hl_text) @@ -294,10 +302,10 @@ static int nlua_schedule(lua_State *const lstate) {2:/// Schedule Lua callback on main loop's event queue} | {3:static} {3:int} nlua_schedule(lua_State *{3:const} lstate) | { | - {4:if} (lua_type(lstate, {5:1}) != LUA_TFUNCTION | + {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | || {6:lstate} != {6:lstate}) { | - lua_pushliteral(lstate, {5:"vim.schedule: expected function"}); | - {4:return} lua_error(lstate); | + {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); | + {4:return} {11:lua_error}(lstate); | } | | {7:LuaRef} cb = nlua_ref(lstate, {5:1}); | @@ -316,10 +324,10 @@ static int nlua_schedule(lua_State *const lstate) {2:/// Schedule Lua callback on main loop's event queue} | {3:static} {3:int} nlua_schedule(lua_State *{3:const} lstate) | { | - {4:if} (lua_type(lstate, {5:1}) != LUA_TFUNCTION | + {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | || {6:lstate} != {6:lstate}) { | - lua_pushliteral(lstate, {5:"vim.schedule: expected function"}); | - {4:return} lua_error(lstate); | + {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); | + {4:return} {11:lua_error}(lstate); | {8:*^/} | } | | @@ -357,41 +365,43 @@ static int nlua_schedule(lua_State *const lstate) end) it('inspects language', function() - local keys, fields, symbols = unpack(exec_lua([[ - local lang = vim.treesitter.inspect_language('c') - local keys, symbols = {}, {} - for k,_ in pairs(lang) do - keys[k] = true - end + if not check_parser() then return end - -- symbols array can have "holes" and is thus not a valid msgpack array - -- but we don't care about the numbers here (checked in the parser test) - for _, v in pairs(lang.symbols) do - table.insert(symbols, v) - end - return {keys, lang.fields, symbols} - ]])) - - eq({fields=true, symbols=true}, keys) + local keys, fields, symbols = unpack(exec_lua([[ + local lang = vim.treesitter.inspect_language('c') + local keys, symbols = {}, {} + for k,_ in pairs(lang) do + keys[k] = true + end - local fset = {} - for _,f in pairs(fields) do - eq("string", type(f)) - fset[f] = true + -- symbols array can have "holes" and is thus not a valid msgpack array + -- but we don't care about the numbers here (checked in the parser test) + for _, v in pairs(lang.symbols) do + table.insert(symbols, v) end - eq(true, fset["directive"]) - eq(true, fset["initializer"]) - - local has_named, has_anonymous - for _,s in pairs(symbols) do - eq("string", type(s[1])) - eq("boolean", type(s[2])) - if s[1] == "for_statement" and s[2] == true then - has_named = true - elseif s[1] == "|=" and s[2] == false then - has_anonymous = true - end + return {keys, lang.fields, symbols} + ]])) + + eq({fields=true, symbols=true}, keys) + + local fset = {} + for _,f in pairs(fields) do + eq("string", type(f)) + fset[f] = true + end + eq(true, fset["directive"]) + eq(true, fset["initializer"]) + + local has_named, has_anonymous + for _,s in pairs(symbols) do + eq("string", type(s[1])) + eq("boolean", type(s[2])) + if s[1] == "for_statement" and s[2] == true then + has_named = true + elseif s[1] == "|=" and s[2] == false then + has_anonymous = true end - eq({true,true}, {has_named,has_anonymous}) + end + eq({true,true}, {has_named,has_anonymous}) end) end) diff --git a/test/functional/lua/uri_spec.lua b/test/functional/lua/uri_spec.lua index 19b1eb1f61..a3b8e685e1 100644 --- a/test/functional/lua/uri_spec.lua +++ b/test/functional/lua/uri_spec.lua @@ -85,6 +85,15 @@ describe('URI methods', function() eq('C:\\Foo\\Bar\\Baz.txt', exec_lua(test_case)) end) + it('file path includes only ascii charactors with encoded colon character', function() + local test_case = [[ + local uri = 'file:///C%3A/Foo/Bar/Baz.txt' + return vim.uri_to_fname(uri) + ]] + + eq('C:\\Foo\\Bar\\Baz.txt', exec_lua(test_case)) + end) + it('file path including white space', function() local test_case = [[ local uri = 'file:///C:/Foo%20/Bar/Baz.txt' @@ -104,4 +113,15 @@ describe('URI methods', function() end) end) end) + + describe('uri to bufnr', function() + it('uri_to_bufnr & uri_from_bufnr returns original uri for non-file uris', function() + local uri = 'jdt://contents/java.base/java.util/List.class?=sql/%5C/home%5C/user%5C/.jabba%5C/jdk%5C/openjdk%5C@1.14.0%5C/lib%5C/jrt-fs.jar%60java.base=/javadoc_location=/https:%5C/%5C/docs.oracle.com%5C/en%5C/java%5C/javase%5C/14%5C/docs%5C/api%5C/=/%3Cjava.util(List.class' + local test_case = string.format([[ + local uri = '%s' + return vim.uri_from_bufnr(vim.uri_to_bufnr(uri)) + ]], uri) + eq(uri, exec_lua(test_case)) + end) + end) end) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index e879f8b925..79d523b5c6 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -7,6 +7,7 @@ local meths = helpers.meths local command = helpers.command local clear = helpers.clear local eq = helpers.eq +local ok = helpers.ok local eval = helpers.eval local feed = helpers.feed local pcall_err = helpers.pcall_err @@ -310,18 +311,52 @@ describe('lua stdlib', function() end) it("vim.deepcopy", function() - local is_dc = exec_lua([[ + ok(exec_lua([[ local a = { x = { 1, 2 }, y = 5} local b = vim.deepcopy(a) - local count = 0 - for _ in pairs(b) do count = count + 1 end - - return b.x[1] == 1 and b.x[2] == 2 and b.y == 5 and count == 2 + return b.x[1] == 1 and b.x[2] == 2 and b.y == 5 and vim.tbl_count(b) == 2 and tostring(a) ~= tostring(b) - ]]) + ]])) + + ok(exec_lua([[ + local a = {} + local b = vim.deepcopy(a) + + return vim.tbl_islist(b) and vim.tbl_count(b) == 0 and tostring(a) ~= tostring(b) + ]])) + + ok(exec_lua([[ + local a = vim.empty_dict() + local b = vim.deepcopy(a) + + return not vim.tbl_islist(b) and vim.tbl_count(b) == 0 + ]])) + + ok(exec_lua([[ + local a = {x = vim.empty_dict(), y = {}} + local b = vim.deepcopy(a) + + return not vim.tbl_islist(b.x) and vim.tbl_islist(b.y) + and vim.tbl_count(b) == 2 + and tostring(a) ~= tostring(b) + ]])) - assert(is_dc) + ok(exec_lua([[ + local f1 = function() return 1 end + local f2 = function() return 2 end + local t1 = {f = f1} + local t2 = vim.deepcopy(t1) + t1.f = f2 + return t1.f() ~= t2.f() + ]])) + + eq('Error executing lua: .../shared.lua: Cannot deepcopy object of type thread', + pcall_err(exec_lua, [[ + local thread = coroutine.create(function () return 0 end) + local t = {thr = thread} + vim.deepcopy(t) + ]])) end) it('vim.pesc', function() @@ -353,6 +388,30 @@ describe('lua stdlib', function() end end) + it('vim.tbl_map', function() + eq({}, exec_lua([[ + return vim.tbl_map(function(v) return v * 2 end, {}) + ]])) + eq({2, 4, 6}, exec_lua([[ + return vim.tbl_map(function(v) return v * 2 end, {1, 2, 3}) + ]])) + eq({{i=2}, {i=4}, {i=6}}, exec_lua([[ + return vim.tbl_map(function(v) return { i = v.i * 2 } end, {{i=1}, {i=2}, {i=3}}) + ]])) + end) + + it('vim.tbl_filter', function() + eq({}, exec_lua([[ + return vim.tbl_filter(function(v) return (v % 2) == 0 end, {}) + ]])) + eq({2}, exec_lua([[ + return vim.tbl_filter(function(v) return (v % 2) == 0 end, {1, 2, 3}) + ]])) + eq({{i=2}}, exec_lua([[ + return vim.tbl_filter(function(v) return (v.i % 2) == 0 end, {{i=1}, {i=2}, {i=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())")) @@ -369,6 +428,88 @@ describe('lua stdlib', function() eq(false, exec_lua("return vim.tbl_isempty({a=1, b=2, c=3})")) end) + it('vim.tbl_extend', function() + ok(exec_lua([[ + local a = {x = 1} + local b = {y = 2} + local c = vim.tbl_extend("keep", a, b) + + return c.x == 1 and b.y == 2 and vim.tbl_count(c) == 2 + ]])) + + ok(exec_lua([[ + local a = {x = 1} + local b = {y = 2} + local c = {z = 3} + local d = vim.tbl_extend("keep", a, b, c) + + return d.x == 1 and d.y == 2 and d.z == 3 and vim.tbl_count(d) == 3 + ]])) + + ok(exec_lua([[ + local a = {x = 1} + local b = {x = 3} + local c = vim.tbl_extend("keep", a, b) + + return c.x == 1 and vim.tbl_count(c) == 1 + ]])) + + ok(exec_lua([[ + local a = {x = 1} + local b = {x = 3} + local c = vim.tbl_extend("force", a, b) + + return c.x == 3 and vim.tbl_count(c) == 1 + ]])) + + ok(exec_lua([[ + local a = vim.empty_dict() + local b = {} + local c = vim.tbl_extend("keep", a, b) + + return not vim.tbl_islist(c) and vim.tbl_count(c) == 0 + ]])) + + ok(exec_lua([[ + local a = {} + local b = vim.empty_dict() + local c = vim.tbl_extend("keep", a, b) + + return vim.tbl_islist(c) and vim.tbl_count(c) == 0 + ]])) + + eq('Error executing lua: .../shared.lua: invalid "behavior": nil', + pcall_err(exec_lua, [[ + return vim.tbl_extend() + ]]) + ) + + eq('Error executing lua: .../shared.lua: wrong number of arguments (given 1, expected at least 3)', + pcall_err(exec_lua, [[ + return vim.tbl_extend("keep") + ]]) + ) + + eq('Error executing lua: .../shared.lua: wrong number of arguments (given 2, expected at least 3)', + pcall_err(exec_lua, [[ + return vim.tbl_extend("keep", {}) + ]]) + ) + end) + + it('vim.tbl_count', function() + eq(0, exec_lua [[ return vim.tbl_count({}) ]]) + eq(0, exec_lua [[ return vim.tbl_count(vim.empty_dict()) ]]) + eq(0, exec_lua [[ return vim.tbl_count({nil}) ]]) + eq(0, exec_lua [[ return vim.tbl_count({a=nil}) ]]) + eq(1, exec_lua [[ return vim.tbl_count({1}) ]]) + eq(2, exec_lua [[ return vim.tbl_count({1, 2}) ]]) + eq(2, exec_lua [[ return vim.tbl_count({1, nil, 3}) ]]) + eq(1, exec_lua [[ return vim.tbl_count({a=1}) ]]) + eq(2, exec_lua [[ return vim.tbl_count({a=1, b=2}) ]]) + eq(2, exec_lua [[ return vim.tbl_count({a=1, b=nil, c=3}) ]]) + end) + it('vim.deep_equal', function() eq(true, exec_lua [[ return vim.deep_equal({a=1}, {a=1}) ]]) eq(true, exec_lua [[ return vim.deep_equal({a={b=1}}, {a={b=1}}) ]]) @@ -545,6 +686,8 @@ describe('lua stdlib', function() ]])) eq("{ {}, vim.empty_dict() }", exec_lua("return vim.inspect({{}, vim.empty_dict()})")) + eq('{}', exec_lua([[ return vim.fn.json_encode(vim.empty_dict()) ]])) + eq('{"a": {}, "b": []}', exec_lua([[ return vim.fn.json_encode({a=vim.empty_dict(), b={}}) ]])) end) it('vim.validate', function() @@ -626,10 +769,96 @@ describe('lua stdlib', function() exec_lua [[ vim.api.nvim_set_var("testing", "hi") vim.api.nvim_set_var("other", 123) + vim.api.nvim_set_var("to_delete", {hello="world"}) ]] + eq('hi', funcs.luaeval "vim.g.testing") eq(123, funcs.luaeval "vim.g.other") eq(NIL, funcs.luaeval "vim.g.nonexistant") + + eq({hello="world"}, funcs.luaeval "vim.g.to_delete") + exec_lua [[ + vim.g.to_delete = nil + ]] + eq(NIL, funcs.luaeval "vim.g.to_delete") + end) + + it('vim.b', function() + exec_lua [[ + vim.api.nvim_buf_set_var(0, "testing", "hi") + vim.api.nvim_buf_set_var(0, "other", 123) + vim.api.nvim_buf_set_var(0, "to_delete", {hello="world"}) + ]] + + eq('hi', funcs.luaeval "vim.b.testing") + eq(123, funcs.luaeval "vim.b.other") + eq(NIL, funcs.luaeval "vim.b.nonexistant") + + eq({hello="world"}, funcs.luaeval "vim.b.to_delete") + exec_lua [[ + vim.b.to_delete = nil + ]] + eq(NIL, funcs.luaeval "vim.b.to_delete") + + exec_lua [[ + vim.cmd "vnew" + ]] + + eq(NIL, funcs.luaeval "vim.b.testing") + eq(NIL, funcs.luaeval "vim.b.other") + eq(NIL, funcs.luaeval "vim.b.nonexistant") + end) + + it('vim.w', function() + exec_lua [[ + vim.api.nvim_win_set_var(0, "testing", "hi") + vim.api.nvim_win_set_var(0, "other", 123) + vim.api.nvim_win_set_var(0, "to_delete", {hello="world"}) + ]] + + eq('hi', funcs.luaeval "vim.w.testing") + eq(123, funcs.luaeval "vim.w.other") + eq(NIL, funcs.luaeval "vim.w.nonexistant") + + eq({hello="world"}, funcs.luaeval "vim.w.to_delete") + exec_lua [[ + vim.w.to_delete = nil + ]] + eq(NIL, funcs.luaeval "vim.w.to_delete") + + exec_lua [[ + vim.cmd "vnew" + ]] + + eq(NIL, funcs.luaeval "vim.w.testing") + eq(NIL, funcs.luaeval "vim.w.other") + eq(NIL, funcs.luaeval "vim.w.nonexistant") + end) + + it('vim.t', function() + exec_lua [[ + vim.api.nvim_tabpage_set_var(0, "testing", "hi") + vim.api.nvim_tabpage_set_var(0, "other", 123) + vim.api.nvim_tabpage_set_var(0, "to_delete", {hello="world"}) + ]] + + eq('hi', funcs.luaeval "vim.t.testing") + eq(123, funcs.luaeval "vim.t.other") + eq(NIL, funcs.luaeval "vim.t.nonexistant") + + eq({hello="world"}, funcs.luaeval "vim.t.to_delete") + exec_lua [[ + vim.t.to_delete = nil + ]] + eq(NIL, funcs.luaeval "vim.t.to_delete") + + exec_lua [[ + vim.cmd "tabnew" + ]] + + eq(NIL, funcs.luaeval "vim.t.testing") + eq(NIL, funcs.luaeval "vim.t.other") + eq(NIL, funcs.luaeval "vim.t.nonexistant") end) it('vim.env', function() @@ -700,4 +929,22 @@ describe('lua stdlib', function() eq('2', funcs.luaeval "BUF") eq(2, funcs.luaeval "#vim.api.nvim_list_bufs()") end) + + it('vim.regex', function() + exec_lua [[ + re1 = vim.regex"ab\\+c" + vim.cmd "set nomagic ignorecase" + re2 = vim.regex"xYz" + ]] + eq({}, exec_lua[[return {re1:match_str("x ac")}]]) + eq({3,7}, exec_lua[[return {re1:match_str("ac abbc")}]]) + + meths.buf_set_lines(0, 0, -1, true, {"yy", "abc abbc"}) + eq({}, exec_lua[[return {re1:match_line(0, 0)}]]) + eq({0,3}, exec_lua[[return {re1:match_line(0, 1)}]]) + eq({3,7}, exec_lua[[return {re1:match_line(0, 1, 1)}]]) + eq({3,7}, exec_lua[[return {re1:match_line(0, 1, 1, 8)}]]) + eq({}, exec_lua[[return {re1:match_line(0, 1, 1, 7)}]]) + eq({0,3}, exec_lua[[return {re1:match_line(0, 1, 0, 7)}]]) + end) end) diff --git a/test/functional/normal/put_spec.lua b/test/functional/normal/put_spec.lua index 357fafec44..26967ecbba 100644 --- a/test/functional/normal/put_spec.lua +++ b/test/functional/normal/put_spec.lua @@ -6,8 +6,8 @@ local insert = helpers.insert local feed = helpers.feed local expect = helpers.expect local eq = helpers.eq -local map = helpers.map -local filter = helpers.filter +local map = helpers.tbl_map +local filter = helpers.tbl_filter local feed_command = helpers.feed_command local curbuf_contents = helpers.curbuf_contents local funcs = helpers.funcs diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index 57e5077989..11ce26410d 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -293,6 +293,14 @@ describe('XDG-based defaults', function() -- TODO(jkeyes): tests below fail on win32 because of path separator. if helpers.pending_win32(pending) then return end + local function vimruntime_and_libdir() + local vimruntime = eval('$VIMRUNTIME') + -- libdir is hard to calculate reliably across various ci platforms + -- local libdir = string.gsub(vimruntime, "share/nvim/runtime$", "lib/nvim") + local libdir = meths._get_lib_dir() + return vimruntime, libdir + end + describe('with too long XDG variables', function() before_each(function() clear({env={ @@ -308,6 +316,8 @@ describe('XDG-based defaults', function() end) it('are correctly set', function() + local vimruntime, libdir = vimruntime_and_libdir() + eq((('/x'):rep(4096) .. '/nvim' .. ',' .. ('/a'):rep(2048) .. '/nvim' .. ',' .. ('/b'):rep(2048) .. '/nvim' @@ -316,7 +326,8 @@ describe('XDG-based defaults', function() .. ',' .. ('/A'):rep(2048) .. '/nvim/site' .. ',' .. ('/B'):rep(2048) .. '/nvim/site' .. (',' .. '/C/nvim/site'):rep(512) - .. ',' .. eval('$VIMRUNTIME') + .. ',' .. vimruntime + .. ',' .. libdir .. (',' .. '/C/nvim/site/after'):rep(512) .. ',' .. ('/B'):rep(2048) .. '/nvim/site/after' .. ',' .. ('/A'):rep(2048) .. '/nvim/site/after' @@ -339,7 +350,8 @@ describe('XDG-based defaults', function() .. ',' .. ('/A'):rep(2048) .. '/nvim/site' .. ',' .. ('/B'):rep(2048) .. '/nvim/site' .. (',' .. '/C/nvim/site'):rep(512) - .. ',' .. eval('$VIMRUNTIME') + .. ',' .. vimruntime + .. ',' .. libdir .. (',' .. '/C/nvim/site/after'):rep(512) .. ',' .. ('/B'):rep(2048) .. '/nvim/site/after' .. ',' .. ('/A'):rep(2048) .. '/nvim/site/after' @@ -368,11 +380,13 @@ describe('XDG-based defaults', function() end) it('are not expanded', function() + local vimruntime, libdir = vimruntime_and_libdir() eq(('$XDG_DATA_HOME/nvim' .. ',$XDG_DATA_DIRS/nvim' .. ',$XDG_CONFIG_HOME/nvim/site' .. ',$XDG_CONFIG_DIRS/nvim/site' - .. ',' .. eval('$VIMRUNTIME') + .. ',' .. vimruntime + .. ',' .. libdir .. ',$XDG_CONFIG_DIRS/nvim/site/after' .. ',$XDG_CONFIG_HOME/nvim/site/after' .. ',$XDG_DATA_DIRS/nvim/after' @@ -387,7 +401,8 @@ describe('XDG-based defaults', function() .. ',$XDG_DATA_DIRS/nvim' .. ',$XDG_CONFIG_HOME/nvim/site' .. ',$XDG_CONFIG_DIRS/nvim/site' - .. ',' .. eval('$VIMRUNTIME') + .. ',' .. vimruntime + .. ',' .. libdir .. ',$XDG_CONFIG_DIRS/nvim/site/after' .. ',$XDG_CONFIG_HOME/nvim/site/after' .. ',$XDG_DATA_DIRS/nvim/after' @@ -402,7 +417,8 @@ describe('XDG-based defaults', function() .. ',$XDG_DATA_DIRS/nvim' .. ',$XDG_CONFIG_HOME/nvim/site' .. ',$XDG_CONFIG_DIRS/nvim/site' - .. ',' .. eval('$VIMRUNTIME') + .. ',' .. vimruntime + .. ',' .. libdir .. ',$XDG_CONFIG_DIRS/nvim/site/after' .. ',$XDG_CONFIG_HOME/nvim/site/after' .. ',$XDG_DATA_DIRS/nvim/after' @@ -426,13 +442,15 @@ describe('XDG-based defaults', function() end) it('are escaped properly', function() + local vimruntime, libdir = vimruntime_and_libdir() eq(('\\, \\, \\,/nvim' .. ',\\,-\\,-\\,/nvim' .. ',-\\,-\\,-/nvim' .. ',\\,=\\,=\\,/nvim/site' .. ',\\,≡\\,≡\\,/nvim/site' .. ',≡\\,≡\\,≡/nvim/site' - .. ',' .. eval('$VIMRUNTIME') + .. ',' .. vimruntime + .. ',' .. libdir .. ',≡\\,≡\\,≡/nvim/site/after' .. ',\\,≡\\,≡\\,/nvim/site/after' .. ',\\,=\\,=\\,/nvim/site/after' @@ -451,7 +469,8 @@ describe('XDG-based defaults', function() .. ',\\,=\\,=\\,/nvim/site' .. ',\\,≡\\,≡\\,/nvim/site' .. ',≡\\,≡\\,≡/nvim/site' - .. ',' .. eval('$VIMRUNTIME') + .. ',' .. vimruntime + .. ',' .. libdir .. ',≡\\,≡\\,≡/nvim/site/after' .. ',\\,≡\\,≡\\,/nvim/site/after' .. ',\\,=\\,=\\,/nvim/site/after' diff --git a/test/functional/options/num_options_spec.lua b/test/functional/options/num_options_spec.lua index deda5c9118..4754c14f5b 100644 --- a/test/functional/options/num_options_spec.lua +++ b/test/functional/options/num_options_spec.lua @@ -65,14 +65,12 @@ describe(':set validation', function() should_succeed('regexpengine', 2) should_fail('report', -1, 'E487') should_succeed('report', 0) - should_fail('scrolloff', -1, 'E49') - should_fail('sidescrolloff', -1, 'E487') should_fail('sidescroll', -1, 'E487') should_fail('cmdwinheight', 0, 'E487') should_fail('updatetime', -1, 'E487') should_fail('foldlevel', -5, 'E487') - should_fail('foldcolumn', 13, 'E474') + should_fail('foldcolumn', '13', 'E474') should_fail('conceallevel', 4, 'E474') should_fail('numberwidth', 21, 'E474') should_fail('numberwidth', 0, 'E487') @@ -82,6 +80,22 @@ describe(':set validation', function() meths.set_option('window', -10) eq(23, meths.get_option('window')) eq('', eval("v:errmsg")) + + -- 'scrolloff' and 'sidescrolloff' can have a -1 value when + -- set for the current window, but not globally + feed_command('setglobal scrolloff=-1') + eq('E487', eval("v:errmsg"):match("E%d*")) + + feed_command('setglobal sidescrolloff=-1') + eq('E487', eval("v:errmsg"):match("E%d*")) + + feed_command('let v:errmsg=""') + + feed_command('setlocal scrolloff=-1') + eq('', eval("v:errmsg")) + + feed_command('setlocal sidescrolloff=-1') + eq('', eval("v:errmsg")) end) it('set wmh/wh wmw/wiw checks', function() diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 4829a33861..62dee7df90 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1,12 +1,13 @@ local helpers = require('test.functional.helpers')(after_each) +local assert_log = helpers.assert_log local clear = helpers.clear local buf_lines = helpers.buf_lines local dedent = helpers.dedent local exec_lua = helpers.exec_lua local eq = helpers.eq +local pesc = helpers.pesc local insert = helpers.insert -local iswin = helpers.iswin local retry = helpers.retry local NIL = helpers.NIL @@ -14,20 +15,27 @@ local NIL = helpers.NIL -- yield. local run, stop = helpers.run, helpers.stop +-- TODO(justinmk): hangs on Windows https://github.com/neovim/neovim/pull/11837 if helpers.pending_win32(pending) then return end -local lsp_test_rpc_server_file = "test/functional/fixtures/lsp-test-rpc-server.lua" -if iswin() then - lsp_test_rpc_server_file = lsp_test_rpc_server_file:gsub("/", "\\") -end +-- Fake LSP server. +local fake_lsp_code = 'test/functional/fixtures/fake-lsp-server.lua' +local fake_lsp_logfile = 'Xtest-fake-lsp.log' + +teardown(function() + os.remove(fake_lsp_logfile) +end) -local function test_rpc_server_setup(test_name, timeout_ms) +local function fake_lsp_server_setup(test_name, timeout_ms) exec_lua([=[ lsp = require('vim.lsp') - local test_name, fixture_filename, timeout = ... + local test_name, fixture_filename, logfile, timeout = ... TEST_RPC_CLIENT_ID = lsp.start_client { + cmd_env = { + NVIM_LOG_FILE = logfile; + }; cmd = { - vim.api.nvim_get_vvar("progpath"), '-Es', '-u', 'NONE', '--headless', + vim.v.progpath, '-Es', '-u', 'NONE', '--headless', "-c", string.format("lua TEST_NAME = %q", test_name), "-c", string.format("lua TIMEOUT = %d", timeout), "-c", "luafile "..fixture_filename, @@ -48,13 +56,13 @@ local function test_rpc_server_setup(test_name, timeout_ms) vim.rpcnotify(1, "exit", ...) end; } - ]=], test_name, lsp_test_rpc_server_file, timeout_ms or 1e3) + ]=], test_name, fake_lsp_code, fake_lsp_logfile, timeout_ms or 1e3) end local function test_rpc_server(config) if config.test_name then clear() - test_rpc_server_setup(config.test_name, config.timeout_ms or 1e3) + fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3) end local client = setmetatable({}, { __index = function(_, name) @@ -114,11 +122,14 @@ describe('LSP', function() local test_name = "basic_init" exec_lua([=[ lsp = require('vim.lsp') - local test_name, fixture_filename = ... + local test_name, fixture_filename, logfile = ... function test__start_client() return lsp.start_client { + cmd_env = { + NVIM_LOG_FILE = logfile; + }; cmd = { - vim.api.nvim_get_vvar("progpath"), '-Es', '-u', 'NONE', '--headless', + vim.v.progpath, '-Es', '-u', 'NONE', '--headless', "-c", string.format("lua TEST_NAME = %q", test_name), "-c", "luafile "..fixture_filename; }; @@ -126,7 +137,7 @@ describe('LSP', function() } end TEST_CLIENT1 = test__start_client() - ]=], test_name, lsp_test_rpc_server_file) + ]=], test_name, fake_lsp_code, fake_lsp_logfile) end) after_each(function() @@ -195,7 +206,8 @@ describe('LSP', function() end; -- If the program timed out, then code will be nil. on_exit = function(code, signal) - eq(0, code, "exit code") eq(0, signal, "exit signal") + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) end; -- Note that NIL must be used here. -- on_callback(err, method, result, client_id) @@ -216,7 +228,10 @@ describe('LSP', function() client.stop() end; on_exit = function(code, signal) - eq(1, code, "exit code") eq(0, signal, "exit signal") + eq(101, code, "exit code", fake_lsp_logfile) -- See fake-lsp-server.lua + eq(0, signal, "exit signal", fake_lsp_logfile) + assert_log(pesc([[assert_eq failed: left == "\"shutdown\"", right == "\"test\""]]), + fake_lsp_logfile) end; on_callback = function(...) eq(table.remove(expected_callbacks), {...}, "expected callback") @@ -226,7 +241,7 @@ describe('LSP', function() it('should succeed with manual shutdown', function() local expected_callbacks = { - {NIL, "shutdown", {}, 1}; + {NIL, "shutdown", {}, 1, NIL}; {NIL, "test", {}, 1}; } test_rpc_server { @@ -237,7 +252,8 @@ describe('LSP', function() client.notify('exit') end; on_exit = function(code, signal) - eq(0, code, "exit code") eq(0, signal, "exit signal") + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) end; on_callback = function(...) eq(table.remove(expected_callbacks), {...}, "expected callback") @@ -255,7 +271,8 @@ describe('LSP', function() client.stop() end; on_exit = function(code, signal) - eq(0, code, "exit code") eq(0, signal, "exit signal") + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) end; on_callback = function(...) eq(table.remove(expected_callbacks), {...}, "expected callback") @@ -294,7 +311,8 @@ describe('LSP', function() client.notify('finish') end; on_exit = function(code, signal) - eq(0, code, "exit code") eq(0, signal, "exit signal") + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) end; on_callback = function(err, method, params, client_id) eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback") @@ -336,7 +354,8 @@ describe('LSP', function() ]] end; on_exit = function(code, signal) - eq(0, code, "exit code") eq(0, signal, "exit signal") + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) end; on_callback = function(err, method, params, client_id) if method == 'start' then @@ -378,7 +397,8 @@ describe('LSP', function() ]] end; on_exit = function(code, signal) - eq(0, code, "exit code") eq(0, signal, "exit signal") + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) end; on_callback = function(err, method, params, client_id) if method == 'start' then @@ -420,7 +440,8 @@ describe('LSP', function() ]] end; on_exit = function(code, signal) - eq(0, code, "exit code") eq(0, signal, "exit signal") + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) end; on_callback = function(err, method, params, client_id) if method == 'start' then @@ -468,7 +489,8 @@ describe('LSP', function() ]] end; on_exit = function(code, signal) - eq(0, code, "exit code") eq(0, signal, "exit signal") + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) end; on_callback = function(err, method, params, client_id) if method == 'start' then @@ -516,7 +538,8 @@ describe('LSP', function() ]] end; on_exit = function(code, signal) - eq(0, code, "exit code") eq(0, signal, "exit signal") + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) end; on_callback = function(err, method, params, client_id) if method == 'start' then @@ -536,7 +559,7 @@ describe('LSP', function() end) -- TODO(askhan) we don't support full for now, so we can disable these tests. - pending('should check the body and didChange incremental normal mode editting', function() + pending('should check the body and didChange incremental normal mode editing', function() local expected_callbacks = { {NIL, "shutdown", {}, 1}; {NIL, "finish", {}, 1}; @@ -544,7 +567,7 @@ describe('LSP', function() } local client test_rpc_server { - test_name = "basic_check_buffer_open_and_change_incremental_editting"; + test_name = "basic_check_buffer_open_and_change_incremental_editing"; on_setup = function() exec_lua [[ BUFFER = vim.api.nvim_create_buf(false, true) @@ -564,7 +587,8 @@ describe('LSP', function() ]] end; on_exit = function(code, signal) - eq(0, code, "exit code") eq(0, signal, "exit signal") + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) end; on_callback = function(err, method, params, client_id) if method == 'start' then @@ -607,7 +631,8 @@ describe('LSP', function() ]] end; on_exit = function(code, signal) - eq(0, code, "exit code") eq(0, signal, "exit signal") + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) end; on_callback = function(err, method, params, client_id) if method == 'start' then @@ -657,7 +682,8 @@ describe('LSP', function() ]] end; on_exit = function(code, signal) - eq(0, code, "exit code") eq(0, signal, "exit signal") + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) end; on_callback = function(err, method, params, client_id) if method == 'start' then @@ -699,7 +725,8 @@ describe('LSP', function() client.stop(true) end; on_exit = function(code, signal) - eq(0, code, "exit code") eq(0, signal, "exit signal") + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) end; on_callback = function(err, method, params, client_id) eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback") @@ -727,27 +754,32 @@ describe('LSP', function() it('highlight groups', function() eq({'LspDiagnosticsError', + 'LspDiagnosticsErrorSign', 'LspDiagnosticsHint', + 'LspDiagnosticsHintSign', 'LspDiagnosticsInformation', + 'LspDiagnosticsInformationSign', 'LspDiagnosticsUnderline', 'LspDiagnosticsUnderlineError', 'LspDiagnosticsUnderlineHint', 'LspDiagnosticsUnderlineInformation', 'LspDiagnosticsUnderlineWarning', 'LspDiagnosticsWarning', + 'LspDiagnosticsWarningSign', }, exec_lua([[require'vim.lsp'; return vim.fn.getcompletion('Lsp', 'highlight')]])) end) - describe('apply_edits', function() + describe('apply_text_edits', function() before_each(function() insert(dedent([[ First line of text Second line of text Third line of text - Fourth line of text]])) + Fourth line of text + aÌŠ Ã¥ ɧ æ±‰è¯ â†¥ 🤦 🦄]])) end) - it('applies apply simple edits', function() + it('applies simple edits', function() local edits = { make_edit(0, 0, 0, 0, {"123"}); make_edit(1, 0, 1, 1, {"2"}); @@ -759,6 +791,7 @@ describe('LSP', function() '2econd line of text'; '3ird line of text'; 'Fourth line of text'; + 'aÌŠ Ã¥ ɧ æ±‰è¯ â†¥ 🤦 🦄'; }, buf_lines(1)) end) it('applies complex edits', function() @@ -782,7 +815,455 @@ describe('LSP', function() 'The next line of text'; 'another line of text'; 'before this!'; + 'aÌŠ Ã¥ ɧ æ±‰è¯ â†¥ 🤦 🦄'; + }, buf_lines(1)) + end) + it('applies non-ASCII characters edits', function() + local edits = { + make_edit(4, 3, 4, 4, {"ä"}); + } + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + eq({ + 'First line of text'; + 'Second line of text'; + 'Third line of text'; + 'Fourth line of text'; + 'aÌŠ ä ɧ æ±‰è¯ â†¥ 🤦 🦄'; }, buf_lines(1)) end) end) + + describe('apply_text_document_edit', function() + local target_bufnr + local text_document_edit = function(editVersion) + return { + edits = { + make_edit(0, 0, 0, 3, "First ↥ 🤦 🦄") + }, + textDocument = { + uri = "file://fake/uri"; + version = editVersion + } + } + end + before_each(function() + target_bufnr = exec_lua [[ + local bufnr = vim.uri_to_bufnr("file://fake/uri") + local lines = {"1st line of text", "2nd line of è¯text"} + vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) + return bufnr + ]] + end) + it('correctly goes ahead with the edit if all is normal', function() + exec_lua('vim.lsp.util.apply_text_document_edit(...)', text_document_edit(5)) + eq({ + 'First ↥ 🤦 🦄 line of text'; + '2nd line of è¯text'; + }, buf_lines(target_bufnr)) + end) + it('correctly goes ahead with the edit if the version is vim.NIL', function() + -- we get vim.NIL when we decode json null value. + local json = exec_lua[[ + return vim.fn.json_decode("{ \"a\": 1, \"b\": null }") + ]] + eq(json.b, exec_lua("return vim.NIL")) + + exec_lua('vim.lsp.util.apply_text_document_edit(...)', text_document_edit(exec_lua("return vim.NIL"))) + eq({ + 'First ↥ 🤦 🦄 line of text'; + '2nd line of è¯text'; + }, buf_lines(target_bufnr)) + end) + it('skips the edit if the version of the edit is behind the local buffer ', function() + local apply_edit_mocking_current_version = function(edit, versionedBuf) + exec_lua([[ + local args = {...} + local versionedBuf = args[2] + vim.lsp.util.buf_versions[versionedBuf.bufnr] = versionedBuf.currentVersion + vim.lsp.util.apply_text_document_edit(...) + ]], edit, versionedBuf) + end + + local baseText = { + '1st line of text'; + '2nd line of è¯text'; + } + + eq(baseText, buf_lines(target_bufnr)) + + -- Apply an edit for an old version, should skip + apply_edit_mocking_current_version(text_document_edit(2), {currentVersion=7; bufnr=target_bufnr}) + eq(baseText, buf_lines(target_bufnr)) -- no change + + -- Sanity check that next version to current does apply change + apply_edit_mocking_current_version(text_document_edit(8), {currentVersion=7; bufnr=target_bufnr}) + eq({ + 'First ↥ 🤦 🦄 line of text'; + '2nd line of è¯text'; + }, buf_lines(target_bufnr)) + end) + end) + describe('workspace_apply_edit', function() + it('workspace/applyEdit returns ApplyWorkspaceEditResponse', function() + local expected = { + applied = true; + failureReason = nil; + } + eq(expected, exec_lua [[ + local apply_edit = { + label = nil; + edit = {}; + } + return vim.lsp.callbacks['workspace/applyEdit'](nil, nil, apply_edit) + ]]) + end) + end) + describe('completion_list_to_complete_items', function() + -- Completion option precedence: + -- textEdit.newText > insertText > label + -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion + it('should choose right completion option', function () + local prefix = 'foo' + local completion_list = { + -- resolves into label + { label='foobar' }, + { label='foobar', textEdit={} }, + -- resolves into insertText + { label='foocar', insertText='foobar' }, + { label='foocar', insertText='foobar', textEdit={} }, + -- resolves into textEdit.newText + { label='foocar', insertText='foodar', textEdit={newText='foobar'} }, + { label='foocar', textEdit={newText='foobar'} } + } + local completion_list_items = {items=completion_list} + local expected = { + { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label = 'foobar' } } } } }, + { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foobar', textEdit={} } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foobar' } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foobar', textEdit={} } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar', textEdit={newText='foobar'} } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', textEdit={newText='foobar'} } } } } }, + } + + eq(expected, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], completion_list, prefix)) + eq(expected, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], completion_list_items, prefix)) + eq({}, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], {}, prefix)) + end) + end) + describe('buf_diagnostics_save_positions', function() + it('stores the diagnostics in diagnostics_by_buf', function () + local diagnostics = { + { range = {}; message = "diag1" }, + { range = {}; message = "diag2" }, + } + exec_lua([[ + vim.lsp.util.buf_diagnostics_save_positions(...)]], 0, diagnostics) + eq(1, exec_lua [[ return #vim.lsp.util.diagnostics_by_buf ]]) + eq(diagnostics, exec_lua [[ + for _, diagnostics in pairs(vim.lsp.util.diagnostics_by_buf) do + return diagnostics + end + ]]) + end) + end) + describe('lsp.util.show_line_diagnostics', function() + it('creates floating window and returns popup bufnr and winnr if current line contains diagnostics', function() + eq(3, exec_lua [[ + local buffer = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(buffer, 0, -1, false, { + "testing"; + "123"; + }) + local diagnostics = { + { + range = { + start = { line = 0; character = 1; }; + ["end"] = { line = 0; character = 3; }; + }; + severity = vim.lsp.protocol.DiagnosticSeverity.Error; + message = "Syntax error"; + }, + } + vim.api.nvim_win_set_buf(0, buffer) + vim.lsp.util.buf_diagnostics_save_positions(vim.fn.bufnr(buffer), diagnostics) + local popup_bufnr, winnr = vim.lsp.util.show_line_diagnostics() + return popup_bufnr + ]]) + end) + end) + describe('lsp.util.symbols_to_items', function() + describe('convert DocumentSymbol[] to items', function() + it('DocumentSymbol has children', function() + local expected = { + { + col = 1, + filename = '', + kind = 'File', + lnum = 2, + text = '[File] TestA' + }, + { + col = 1, + filename = '', + kind = 'Module', + lnum = 4, + text = '[Module] TestB' + }, + { + col = 1, + filename = '', + kind = 'Namespace', + lnum = 6, + text = '[Namespace] TestC' + } + } + eq(expected, exec_lua [[ + local doc_syms = { + { + deprecated = false, + detail = "A", + kind = 1, + name = "TestA", + range = { + start = { + character = 0, + line = 1 + }, + ["end"] = { + character = 0, + line = 2 + } + }, + selectionRange = { + start = { + character = 0, + line = 1 + }, + ["end"] = { + character = 4, + line = 1 + } + }, + children = { + { + children = {}, + deprecated = false, + detail = "B", + kind = 2, + name = "TestB", + range = { + start = { + character = 0, + line = 3 + }, + ["end"] = { + character = 0, + line = 4 + } + }, + selectionRange = { + start = { + character = 0, + line = 3 + }, + ["end"] = { + character = 4, + line = 3 + } + } + } + } + }, + { + deprecated = false, + detail = "C", + kind = 3, + name = "TestC", + range = { + start = { + character = 0, + line = 5 + }, + ["end"] = { + character = 0, + line = 6 + } + }, + selectionRange = { + start = { + character = 0, + line = 5 + }, + ["end"] = { + character = 4, + line = 5 + } + } + } + } + return vim.lsp.util.symbols_to_items(doc_syms, nil) + ]]) + end) + it('DocumentSymbol has no children', function() + local expected = { + { + col = 1, + filename = '', + kind = 'File', + lnum = 2, + text = '[File] TestA' + }, + { + col = 1, + filename = '', + kind = 'Namespace', + lnum = 6, + text = '[Namespace] TestC' + } + } + eq(expected, exec_lua [[ + local doc_syms = { + { + deprecated = false, + detail = "A", + kind = 1, + name = "TestA", + range = { + start = { + character = 0, + line = 1 + }, + ["end"] = { + character = 0, + line = 2 + } + }, + selectionRange = { + start = { + character = 0, + line = 1 + }, + ["end"] = { + character = 4, + line = 1 + } + }, + }, + { + deprecated = false, + detail = "C", + kind = 3, + name = "TestC", + range = { + start = { + character = 0, + line = 5 + }, + ["end"] = { + character = 0, + line = 6 + } + }, + selectionRange = { + start = { + character = 0, + line = 5 + }, + ["end"] = { + character = 4, + line = 5 + } + } + } + } + return vim.lsp.util.symbols_to_items(doc_syms, nil) + ]]) + end) + end) + describe('convert SymbolInformation[] to items', function() + local expected = { + { + col = 1, + filename = 'test_a', + kind = 'File', + lnum = 2, + text = '[File] TestA' + }, + { + col = 1, + filename = 'test_b', + kind = 'Module', + lnum = 4, + text = '[Module] TestB' + } + } + eq(expected, exec_lua [[ + local sym_info = { + { + deprecated = false, + kind = 1, + name = "TestA", + location = { + range = { + start = { + character = 0, + line = 1 + }, + ["end"] = { + character = 0, + line = 2 + } + }, + uri = "file://test_a" + }, + contanerName = "TestAContainer" + }, + { + deprecated = false, + kind = 2, + name = "TestB", + location = { + range = { + start = { + character = 0, + line = 3 + }, + ["end"] = { + character = 0, + line = 4 + } + }, + uri = "file://test_b" + }, + contanerName = "TestBContainer" + } + } + return vim.lsp.util.symbols_to_items(sym_info, nil) + ]]) + end) + end) + + describe('lsp.util._get_completion_item_kind_name', function() + describe('returns the name specified by protocol', function() + eq("Text", exec_lua("return vim.lsp.util._get_completion_item_kind_name(1)")) + eq("TypeParameter", exec_lua("return vim.lsp.util._get_completion_item_kind_name(25)")) + end) + describe('returns the name not specified by protocol', function() + eq("Unknown", exec_lua("return vim.lsp.util._get_completion_item_kind_name(nil)")) + eq("Unknown", exec_lua("return vim.lsp.util._get_completion_item_kind_name(vim.NIL)")) + eq("Unknown", exec_lua("return vim.lsp.util._get_completion_item_kind_name(1000)")) + end) + end) + + describe('lsp.util._get_symbol_kind_name', function() + describe('returns the name specified by protocol', function() + eq("File", exec_lua("return vim.lsp.util._get_symbol_kind_name(1)")) + eq("TypeParameter", exec_lua("return vim.lsp.util._get_symbol_kind_name(26)")) + end) + describe('returns the name not specified by protocol', function() + eq("Unknown", exec_lua("return vim.lsp.util._get_symbol_kind_name(nil)")) + eq("Unknown", exec_lua("return vim.lsp.util._get_symbol_kind_name(vim.NIL)")) + eq("Unknown", exec_lua("return vim.lsp.util._get_symbol_kind_name(1000)")) + end) + end) end) diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index d40baca871..f79aceaddf 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -17,6 +17,18 @@ describe(':terminal buffer', function() screen = thelpers.screen_setup() end) + it('terminal-mode forces various options', function() + feed([[<C-\><C-N>]]) + command('setlocal cursorline cursorcolumn scrolloff=4 sidescrolloff=7') + eq({ 1, 1, 4, 7 }, eval('[&l:cursorline, &l:cursorcolumn, &l:scrolloff, &l:sidescrolloff]')) + eq('n', eval('mode()')) + + -- Enter terminal-mode ("insert" mode in :terminal). + feed('i') + eq('t', eval('mode()')) + eq({ 0, 0, 0, 0 }, eval('[&l:cursorline, &l:cursorcolumn, &l:scrolloff, &l:sidescrolloff]')) + end) + describe('when a new file is edited', function() before_each(function() feed('<c-\\><c-n>:set bufhidden=wipe<cr>:enew<cr>') diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua index ee3db7ae97..0eb5901b3b 100644 --- a/test/functional/terminal/mouse_spec.lua +++ b/test/functional/terminal/mouse_spec.lua @@ -87,6 +87,36 @@ describe(':terminal mouse', function() {3:-- TERMINAL --} | ]]) end) + + it('will forward mouse clicks to the program with the correct even if set nu', function() + if helpers.pending_win32(pending) then return end + nvim('command', 'set number') + -- When the display area such as a number is clicked, it returns to the + -- normal mode. + feed('<LeftMouse><3,0>') + eq('n', eval('mode()')) + screen:expect([[ + {7: 11 }^line28 | + {7: 12 }line29 | + {7: 13 }line30 | + {7: 14 }mouse enabled | + {7: 15 }rows: 6, cols: 46 | + {7: 16 }{2: } | + | + ]]) + -- If click on the coordinate (0,1) of the region of the terminal + -- (i.e. the coordinate (4,1) of vim), 'CSI !"' is sent to the terminal. + feed('i<LeftMouse><4,1>') + screen:expect([[ + {7: 11 }line28 | + {7: 12 }line29 | + {7: 13 }line30 | + {7: 14 }mouse enabled | + {7: 15 }rows: 6, cols: 46 | + {7: 16 } !"{1: } | + {3:-- TERMINAL --} | + ]]) + end) end) describe('with a split window and other buffer', function() @@ -148,7 +178,7 @@ describe(':terminal mouse', function() end) it('wont lose focus if another window is scrolled', function() - feed('<ScrollWheelUp><0,0><ScrollWheelUp><0,0>') + feed('<ScrollWheelUp><4,0><ScrollWheelUp><4,0>') screen:expect([[ {7: 21 }line │line30 | {7: 22 }line │rows: 5, cols: 25 | @@ -158,7 +188,7 @@ describe(':terminal mouse', function() ========== ========== | {3:-- TERMINAL --} | ]]) - feed('<S-ScrollWheelDown><0,0>') + feed('<S-ScrollWheelDown><4,0>') screen:expect([[ {7: 26 }line │line30 | {7: 27 }line │rows: 5, cols: 25 | diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 7a5569c14b..639e311ae6 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -976,6 +976,28 @@ describe('floatwin', function() {2:~ }| ]], float_pos={ [5] = {{id = 1002}, "NE", 4, 0, 50, true} + }, win_viewport = { + [2] = { + topline = 0, + botline = 3, + curline = 0, + curcol = 3, + win = { id = 1000 } + }, + [4] = { + topline = 0, + botline = 3, + curline = 0, + curcol = 3, + win = { id = 1001 } + }, + [5] = { + topline = 0, + botline = 2, + curline = 0, + curcol = 0, + win = { id = 1002 } + } }} else screen:expect([[ diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua index 0b788e7afb..6ec45064da 100644 --- a/test/functional/ui/fold_spec.lua +++ b/test/functional/ui/fold_spec.lua @@ -96,8 +96,20 @@ describe("folded lines", function() {1: ~}| :set rightleft | ]]} - end) + feed_command("set norightleft") + meths.input_mouse('left', 'press', '', 0, 0, 1) + screen:expect{grid=[[ + {7:▾▸}{5:^+--- 5 lines: aa··························}| + {7:│ }ff | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :set norightleft | + ]]} + end) it("works with multibyte text", function() -- Currently the only allowed value of 'maxcombine' @@ -283,4 +295,64 @@ describe("folded lines", function() ]]) end) + + it("work with autoresize", function() + + funcs.setline(1, 'line 1') + funcs.setline(2, 'line 2') + funcs.setline(3, 'line 3') + funcs.setline(4, 'line 4') + + feed("zfj") + command("set foldcolumn=0") + screen:expect{grid=[[ + {5:^+-- 2 lines: line 1·························}| + line 3 | + line 4 | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + -- should adapt to the current nesting of folds (e.g., 1) + command("set foldcolumn=auto:1") + screen:expect{grid=[[ + {7:+}{5:^+-- 2 lines: line 1························}| + {7: }line 3 | + {7: }line 4 | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + -- fdc should not change with a new fold as the maximum is 1 + feed("zf3j") + + screen:expect{grid=[[ + {7:+}{5:^+-- 4 lines: line 1························}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + -- relax the maximum fdc thus fdc should expand to + -- accomodate the current number of folds + command("set foldcolumn=auto:4") + screen:expect{grid=[[ + {7:+ }{5:^+-- 4 lines: line 1·······················}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end) end) diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index d7791a3107..28e4e88326 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -1186,6 +1186,7 @@ describe("'winhighlight' highlight", function() [25] = {bold = true, foreground = Screen.colors.Green1}, [26] = {background = Screen.colors.Red}, [27] = {background = Screen.colors.DarkBlue, bold = true, foreground = Screen.colors.Green1}, + [28] = {bold = true, foreground = Screen.colors.Brown}, }) command("hi Background1 guibg=DarkBlue") command("hi Background2 guibg=DarkGreen") @@ -1598,4 +1599,45 @@ describe("'winhighlight' highlight", function() {21:-- }{22:match 1 of 3} | ]]) end) + + it('can override CursorLine and CursorLineNr', function() + -- CursorLine used to be parsed as CursorLineNr, because strncmp + command('set cursorline number') + command('split') + command('set winhl=CursorLine:Background1') + screen:expect{grid=[[ + {28: 1 }{1:^ }| + {0:~ }| + {0:~ }| + {3:[No Name] }| + {28: 1 }{18: }| + {0:~ }| + {4:[No Name] }| + | + ]]} + + command('set winhl=CursorLineNr:Background2,CursorLine:Background1') + screen:expect{grid=[[ + {5: 1 }{1:^ }| + {0:~ }| + {0:~ }| + {3:[No Name] }| + {28: 1 }{18: }| + {0:~ }| + {4:[No Name] }| + | + ]]} + + feed('<c-w>w') + screen:expect{grid=[[ + {5: 1 }{1: }| + {0:~ }| + {0:~ }| + {4:[No Name] }| + {28: 1 }{18:^ }| + {0:~ }| + {3:[No Name] }| + | + ]]} + end) end) diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index b841574643..afb0c9cfa6 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -18,6 +18,7 @@ local wait = helpers.wait local nvim = helpers.nvim local sleep = helpers.sleep local nvim_dir = helpers.nvim_dir +local assert_alive = helpers.assert_alive local default_text = [[ Inc substitution on @@ -84,6 +85,7 @@ local function common_setup(screen, inccommand, text) [14] = {foreground = Screen.colors.White, background = Screen.colors.Red}, [15] = {bold=true, foreground=Screen.colors.Blue}, [16] = {background=Screen.colors.Grey90}, -- cursorline + [17] = {foreground = Screen.colors.Blue1}, vis = {background=Screen.colors.LightGrey} }) end @@ -2291,6 +2293,76 @@ describe(":substitute", function() ]]) end) + it("inccommand=split, contraction of two subsequent NL chars", function() + -- luacheck: push ignore 611 + local text = [[ + AAA AA + + BBB BB + + CCC CC + +]] + -- luacheck: pop + + -- This used to crash, but more than 20 highlight entries are required + -- to reproduce it (so that the marktree has multiple nodes) + common_setup(screen, "split", string.rep(text,10)) + feed(":%s/\\n\\n/<c-v><c-m>/g") + screen:expect{grid=[[ + CCC CC | + AAA AA | + BBB BB | + CCC CC | + | + {11:[No Name] [+] }| + | 1| AAA AA | + | 2|{12: }BBB BB | + | 3|{12: }CCC CC | + | 4|{12: }AAA AA | + | 5|{12: }BBB BB | + | 6|{12: }CCC CC | + | 7|{12: }AAA AA | + {10:[Preview] }| + :%s/\n\n/{17:^M}/g^ | + ]]} + assert_alive() + end) + + it("inccommand=nosplit, contraction of two subsequent NL chars", function() + -- luacheck: push ignore 611 + local text = [[ + AAA AA + + BBB BB + + CCC CC + +]] + -- luacheck: pop + + common_setup(screen, "nosplit", string.rep(text,10)) + feed(":%s/\\n\\n/<c-v><c-m>/g") + screen:expect{grid=[[ + CCC CC | + AAA AA | + BBB BB | + CCC CC | + AAA AA | + BBB BB | + CCC CC | + AAA AA | + BBB BB | + CCC CC | + AAA AA | + BBB BB | + CCC CC | + | + :%s/\n\n/{17:^M}/g^ | + ]]} + assert_alive() + end) + it("inccommand=split, multibyte text", function() common_setup(screen, "split", multibyte_text) feed(":%s/£.*Ñ«/X¥¥") diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index dfc3d045e8..efc02db159 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -811,6 +811,8 @@ describe('ui/builtin messages', function() [5] = {foreground = Screen.colors.Blue1}, [6] = {bold = true, foreground = Screen.colors.Magenta}, [7] = {background = Screen.colors.Grey20}, + [8] = {reverse = true}, + [9] = {background = Screen.colors.LightRed} }) end) @@ -962,6 +964,91 @@ vimComment xxx match /\s"[^\-:.%#=*].*$/ms=s+1,lc=1 excludenl contains=@vim zbc | ]]} end) + + it('redraws NOT_VALID correctly after message', function() + -- edge case: only one window was set NOT_VALID. Orginal report + -- used :make, but fake it using one command to set the current + -- window NOT_VALID and another to show a long message. + command("set more") + feed(':new<cr><c-w><c-w>') + screen:expect{grid=[[ + | + {1:~ }| + {8:[No Name] }| + ^ | + {1:~ }| + {3:[No Name] }| + :new | + ]]} + + feed(':set colorcolumn=10 | digraphs<cr>') + screen:expect{grid=[[ + :set colorcolumn=10 | digraphs | + NU {5:^@} 10 SH {5:^A} 1 SX {5:^B} 2 EX {5:^C} 3 | + ET {5:^D} 4 EQ {5:^E} 5 AK {5:^F} 6 BL {5:^G} 7 | + BS {5:^H} 8 HT {5:^I} 9 LF {5:^@} 10 VT {5:^K} 11 | + FF {5:^L} 12 CR {5:^M} 13 SO {5:^N} 14 SI {5:^O} 15 | + DL {5:^P} 16 D1 {5:^Q} 17 D2 {5:^R} 18 D3 {5:^S} 19 | + {4:-- More --}^ | + ]]} + + feed('q') + screen:expect{grid=[[ + | + {1:~ }| + {8:[No Name] }| + ^ {9: } | + {1:~ }| + {3:[No Name] }| + | + ]]} + + -- edge case: just covers statusline + feed(':set colorcolumn=5 | lua error("x\\n\\nx")<cr>') + screen:expect{grid=[[ + | + {1:~ }| + {3: }| + {2:E5108: Error executing lua [string ":lua"]:1: x} | + | + {2:x} | + {4:Press ENTER or type command to continue}^ | + ]]} + + feed('<cr>') + screen:expect{grid=[[ + | + {1:~ }| + {8:[No Name] }| + ^ {9: } | + {1:~ }| + {3:[No Name] }| + | + ]]} + + -- edge case: just covers lowest window line + feed(':set colorcolumn=5 | lua error("x\\n\\n\\nx")<cr>') + screen:expect{grid=[[ + | + {3: }| + {2:E5108: Error executing lua [string ":lua"]:1: x} | + | + | + {2:x} | + {4:Press ENTER or type command to continue}^ | + ]]} + + feed('<cr>') + screen:expect{grid=[[ + | + {1:~ }| + {8:[No Name] }| + ^ {9: } | + {1:~ }| + {3:[No Name] }| + | + ]]} + end) end) describe('ui/ext_messages', function() diff --git a/test/functional/ui/multibyte_spec.lua b/test/functional/ui/multibyte_spec.lua index 8122cb08a3..e6a79feadc 100644 --- a/test/functional/ui/multibyte_spec.lua +++ b/test/functional/ui/multibyte_spec.lua @@ -123,6 +123,10 @@ describe('multibyte rendering: statusline', function() before_each(function() clear() screen = Screen.new(40, 4) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {bold = true, reverse = true}, + }) screen:attach() command('set laststatus=2') end) @@ -131,8 +135,8 @@ describe('multibyte rendering: statusline', function() command('set statusline=ä½ å¥½') screen:expect([[ ^ | - ~ | - ä½ å¥½ | + {1:~ }| + {2:ä½ å¥½ }| | ]]) end) @@ -140,8 +144,8 @@ describe('multibyte rendering: statusline', function() command('set statusline=abc') screen:expect([[ ^ | - ~ | - abc | + {1:~ }| + {2:abc }| | ]]) end) @@ -149,8 +153,8 @@ describe('multibyte rendering: statusline', function() command('set statusline=Ÿ') screen:expect([[ ^ | - ~ | - <9f> | + {1:~ }| + {2:<9f> }| | ]]) end) @@ -159,8 +163,8 @@ describe('multibyte rendering: statusline', function() -- o + U+1DF0 + U+20EF + U+0338 + U+20D0 + U+20E7 + U+20DD screen:expect([[ ^ | - ~ | - o̸⃯ᷰâƒâƒ§âƒ | + {1:~ }| + {2:o̸⃯ᷰâƒâƒ§âƒ }| | ]]) end) @@ -169,9 +173,19 @@ describe('multibyte rendering: statusline', function() -- U+9F + U+1DF0 + U+20EF + U+0338 + U+20D0 + U+20E7 + U+20DD screen:expect([[ ^ | - ~ | - <9f><1df0><20ef><0338><20d0><20e7><20dd>| + {1:~ }| + {2:<9f><1df0><20ef><0338><20d0><20e7><20dd>}| | ]]) end) + + it('hidden group %( %) does not cause invalid unicode', function() + command("let &statusline = '%#StatColorHi2#%(✓%#StatColorHi2#%) Q≡'") + screen:expect{grid=[[ + ^ | + {1:~ }| + {2: Q≡ }| + | + ]]} + end) end) diff --git a/test/functional/ui/multigrid_spec.lua b/test/functional/ui/multigrid_spec.lua index 01ffe80be3..e4d1187dea 100644 --- a/test/functional/ui/multigrid_spec.lua +++ b/test/functional/ui/multigrid_spec.lua @@ -1962,4 +1962,191 @@ describe('ext_multigrid', function() {1:~ }| ]]} end) + + it('has viewport information', function() + screen:try_resize(48, 8) + screen:expect{grid=[[ + ## grid 1 + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + {11:[No Name] }| + [3:------------------------------------------------]| + ## grid 2 + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ]], win_viewport={ + [2] = {win = { id = 1000 }, topline = 0, botline = 2, curline = 0, curcol = 0} + }} + insert([[ + Lorem ipsum dolor sit amet, consectetur + adipisicing elit, sed do eiusmod tempor + incididunt ut labore et dolore magna aliqua. + Ut enim ad minim veniam, quis nostrud + exercitation ullamco laboris nisi ut aliquip ex + ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum + dolore eu fugiat nulla pariatur. Excepteur sint + occaecat cupidatat non proident, sunt in culpa + qui officia deserunt mollit anim id est + laborum.]]) + + screen:expect{grid=[[ + ## grid 1 + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + {11:[No Name] [+] }| + [3:------------------------------------------------]| + ## grid 2 + ea commodo consequat. Duis aute irure dolor in | + reprehenderit in voluptate velit esse cillum | + dolore eu fugiat nulla pariatur. Excepteur sint | + occaecat cupidatat non proident, sunt in culpa | + qui officia deserunt mollit anim id est | + laborum^. | + ## grid 3 + | + ]], win_viewport={ + [2] = {win = {id = 1000}, topline = 5, botline = 11, curline = 10, curcol = 7}, + }} + + + feed('<c-u>') + screen:expect{grid=[[ + ## grid 1 + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + {11:[No Name] [+] }| + [3:------------------------------------------------]| + ## grid 2 + incididunt ut labore et dolore magna aliqua. | + Ut enim ad minim veniam, quis nostrud | + exercitation ullamco laboris nisi ut aliquip ex | + ea commodo consequat. Duis aute irure dolor in | + reprehenderit in voluptate velit esse cillum | + ^dolore eu fugiat nulla pariatur. Excepteur sint | + ## grid 3 + | + ]], win_viewport={ + [2] = {win = {id = 1000}, topline = 2, botline = 9, curline = 7, curcol = 0}, + }} + + command("split") + screen:expect{grid=[[ + ## grid 1 + [4:------------------------------------------------]| + [4:------------------------------------------------]| + [4:------------------------------------------------]| + {11:[No Name] [+] }| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + {12:[No Name] [+] }| + [3:------------------------------------------------]| + ## grid 2 + reprehenderit in voluptate velit esse cillum | + dolore eu fugiat nulla pariatur. Excepteur sint | + ## grid 3 + | + ## grid 4 + ea commodo consequat. Duis aute irure dolor in | + reprehenderit in voluptate velit esse cillum | + ^dolore eu fugiat nulla pariatur. Excepteur sint | + ]], win_viewport={ + [2] = {win = {id = 1000}, topline = 6, botline = 9, curline = 7, curcol = 0}, + [4] = {win = {id = 1001}, topline = 5, botline = 9, curline = 7, curcol = 0}, + }} + + feed("b") + screen:expect{grid=[[ + ## grid 1 + [4:------------------------------------------------]| + [4:------------------------------------------------]| + [4:------------------------------------------------]| + {11:[No Name] [+] }| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + {12:[No Name] [+] }| + [3:------------------------------------------------]| + ## grid 2 + reprehenderit in voluptate velit esse cillum | + dolore eu fugiat nulla pariatur. Excepteur sint | + ## grid 3 + | + ## grid 4 + ea commodo consequat. Duis aute irure dolor in | + reprehenderit in voluptate velit esse ^cillum | + dolore eu fugiat nulla pariatur. Excepteur sint | + ]], win_viewport={ + [2] = {win = {id = 1000}, topline = 6, botline = 9, curline = 7, curcol = 0}, + [4] = {win = {id = 1001}, topline = 5, botline = 9, curline = 6, curcol = 38}, + }} + + feed("2k") + screen:expect{grid=[[ + ## grid 1 + [4:------------------------------------------------]| + [4:------------------------------------------------]| + [4:------------------------------------------------]| + {11:[No Name] [+] }| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + {12:[No Name] [+] }| + [3:------------------------------------------------]| + ## grid 2 + reprehenderit in voluptate velit esse cillum | + dolore eu fugiat nulla pariatur. Excepteur sint | + ## grid 3 + | + ## grid 4 + exercitation ullamco laboris nisi ut a^liquip ex | + ea commodo consequat. Duis aute irure dolor in | + reprehenderit in voluptate velit esse cillum | + ]], win_viewport={ + [2] = {win = {id = 1000}, topline = 6, botline = 9, curline = 7, curcol = 0}, + [4] = {win = {id = 1001}, topline = 4, botline = 8, curline = 4, curcol = 38}, + }} + + -- handles non-current window + meths.win_set_cursor(1000, {1, 10}) + screen:expect{grid=[[ + ## grid 1 + [4:------------------------------------------------]| + [4:------------------------------------------------]| + [4:------------------------------------------------]| + {11:[No Name] [+] }| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + {12:[No Name] [+] }| + [3:------------------------------------------------]| + ## grid 2 + Lorem ipsum dolor sit amet, consectetur | + adipisicing elit, sed do eiusmod tempor | + ## grid 3 + | + ## grid 4 + exercitation ullamco laboris nisi ut a^liquip ex | + ea commodo consequat. Duis aute irure dolor in | + reprehenderit in voluptate velit esse cillum | + ]], win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 3, curline = 0, curcol = 10}, + [4] = {win = {id = 1001}, topline = 4, botline = 8, curline = 4, curcol = 38}, + }} + end) end) diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua index 581e196bbb..9646c3fdad 100644 --- a/test/functional/ui/options_spec.lua +++ b/test/functional/ui/options_spec.lua @@ -20,6 +20,8 @@ describe('UI receives option updates', function() pumblend=0, showtabline=1, termguicolors=false, + ttimeout=true, + ttimeoutlen=50, ext_cmdline=false, ext_popupmenu=false, ext_tabline=false, @@ -108,6 +110,18 @@ describe('UI receives option updates', function() eq(expected, screen.options) end) + command("set nottimeout") + expected.ttimeout = false + screen:expect(function() + eq(expected, screen.options) + end) + + command("set ttimeoutlen=100") + expected.ttimeoutlen = 100 + screen:expect(function() + eq(expected, screen.options) + end) + command("set all&") screen:expect(function() eq(defaults, screen.options) diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index b2ebf7af19..c1c5d1ce2e 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -8,7 +8,7 @@ local command = helpers.command local funcs = helpers.funcs local get_pathsep = helpers.get_pathsep local eq = helpers.eq -local matches = helpers.matches +local pcall_err = helpers.pcall_err describe('ui/ext_popupmenu', function() local screen @@ -382,7 +382,7 @@ describe('ui/ext_popupmenu', function() end describe('pum_set_height', function() - it('can be set pum height', function() + it('can set pum height', function() source_complete_month() local month_expected = { {'January', '', '', ''}, @@ -421,22 +421,79 @@ describe('ui/ext_popupmenu', function() end) it('an error occurs if set 0 or less', function() - local ok, err, _ - ok, _ = pcall(meths.ui_pum_set_height, 1) - eq(ok, true) - ok, err = pcall(meths.ui_pum_set_height, 0) - eq(ok, false) - matches('.*: Expected pum height > 0', err) + meths.ui_pum_set_height(1) + eq('Expected pum height > 0', + pcall_err(meths.ui_pum_set_height, 0)) end) it('an error occurs when ext_popupmenu is false', function() - local ok, err, _ - ok, _ = pcall(meths.ui_pum_set_height, 1) - eq(ok, true) + meths.ui_pum_set_height(1) screen:set_option('ext_popupmenu', false) - ok, err = pcall(meths.ui_pum_set_height, 1) - eq(ok, false) - matches('.*: It must support the ext_popupmenu option', err) + eq('It must support the ext_popupmenu option', + pcall_err(meths.ui_pum_set_height, 1)) + end) + end) + + describe('pum_set_bounds', function() + it('can set pum bounds', function() + source_complete_month() + local month_expected = { + {'January', '', '', ''}, + {'February', '', '', ''}, + {'March', '', '', ''}, + {'April', '', '', ''}, + {'May', '', '', ''}, + {'June', '', '', ''}, + {'July', '', '', ''}, + {'August', '', '', ''}, + {'September', '', '', ''}, + {'October', '', '', ''}, + {'November', '', '', ''}, + {'December', '', '', ''}, + } + local pum_height = 6 + feed('o<C-r>=TestCompleteMonth()<CR>') + meths.ui_pum_set_height(pum_height) + -- set bounds w h r c + meths.ui_pum_set_bounds(10.5, 5.2, 6.3, 7.4) + feed('<PageDown>') + -- pos becomes pum_height-2 because it is subtracting 2 to keep some + -- context in ins_compl_key2count() + screen:expect{grid=[[ + | + January^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]], popupmenu={ + items=month_expected, + pos=pum_height-2, + anchor={1,1,0}, + }} + end) + + it('no error occurs if row or col set less than 0', function() + meths.ui_pum_set_bounds(1.0, 1.0, 0.0, 1.5) + meths.ui_pum_set_bounds(1.0, 1.0, -1.0, 0.0) + meths.ui_pum_set_bounds(1.0, 1.0, 0.0, -1.0) + end) + + it('an error occurs if width or height set 0 or less', function() + meths.ui_pum_set_bounds(1.0, 1.0, 0.0, 1.5) + eq('Expected width > 0', + pcall_err(meths.ui_pum_set_bounds, 0.0, 1.0, 1.0, 0.0)) + eq('Expected height > 0', + pcall_err(meths.ui_pum_set_bounds, 1.0, -1.0, 1.0, 0.0)) + end) + + it('an error occurs when ext_popupmenu is false', function() + meths.ui_pum_set_bounds(1.0, 1.0, 0.0, 1.5) + screen:set_option('ext_popupmenu', false) + eq('UI must support the ext_popupmenu option', + pcall_err(meths.ui_pum_set_bounds, 1.0, 1.0, 0.0, 1.5)) end) end) @@ -1338,7 +1395,7 @@ describe('builtin popupmenu', function() end) it('with rightleft window', function() - command("set rl") + command("set rl wildoptions+=pum") feed('isome rightleft ') screen:expect([[ ^ tfelthgir emos| @@ -1435,6 +1492,55 @@ describe('builtin popupmenu', function() {1: ~}| {2:-- INSERT --} | ]]) + + -- not rightleft on the cmdline + feed('<esc>:sign ') + screen:expect{grid=[[ + drow tfelthgir emos| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + :sign ^ | + ]]} + + feed('<tab>') + screen:expect{grid=[[ + drow tfelthgir emos| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + {1: }{s: define }{1: ~}| + {1: }{n: jump }{1: ~}| + {1: }{n: list }{1: ~}| + {1: }{n: place }{1: ~}| + {1: }{n: undefine }{1: ~}| + {1: }{n: unplace }{1: ~}| + :sign define^ | + ]]} end) it('with multiline messages', function() diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 64f784afe3..bf979e89f4 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -158,6 +158,7 @@ function Screen.new(width, height) wildmenu_items = nil, wildmenu_selected = nil, win_position = {}, + win_viewport = {}, float_pos = {}, msg_grid = nil, msg_grid_pos = nil, @@ -254,7 +255,7 @@ end -- canonical order of ext keys, used to generate asserts local ext_keys = { 'popupmenu', 'cmdline', 'cmdline_block', 'wildmenu_items', 'wildmenu_pos', - 'messages', 'showmode', 'showcmd', 'ruler', 'float_pos', + 'messages', 'showmode', 'showcmd', 'ruler', 'float_pos', 'win_viewport' } -- Asserts that the screen state eventually matches an expected state. @@ -421,6 +422,9 @@ screen:redraw_debug() to show all intermediate screen states. ]]) if expected.mode ~= nil then extstate.mode = self.mode end + if expected.win_viewport == nil then + extstate.win_viewport = nil + end -- Convert assertion errors into invalid screen state descriptions. for _, k in ipairs(concat_tables(ext_keys, {'mode'})) do @@ -726,6 +730,7 @@ function Screen:_handle_grid_destroy(grid) self._grids[grid] = nil if self._options.ext_multigrid then self.win_position[grid] = nil + self.win_viewport[grid] = nil end end @@ -746,14 +751,24 @@ function Screen:_handle_grid_cursor_goto(grid, row, col) end function Screen:_handle_win_pos(grid, win, startrow, startcol, width, height) - self.win_position[grid] = { - win = win, - startrow = startrow, - startcol = startcol, - width = width, - height = height - } - self.float_pos[grid] = nil + self.win_position[grid] = { + win = win, + startrow = startrow, + startcol = startcol, + width = width, + height = height + } + self.float_pos[grid] = nil +end + +function Screen:_handle_win_viewport(grid, win, topline, botline, curline, curcol) + self.win_viewport[grid] = { + win = win, + topline = topline, + botline = botline, + curline = curline, + curcol = curcol + } end function Screen:_handle_win_float_pos(grid, ...) @@ -1130,6 +1145,8 @@ function Screen:_extstate_repr(attr_state) messages[i] = {kind=entry[1], content=self:_chunks_repr(entry[2], attr_state)} end + local win_viewport = (next(self.win_viewport) and self.win_viewport) or nil + return { popupmenu=self.popupmenu, cmdline=cmdline, @@ -1141,7 +1158,8 @@ function Screen:_extstate_repr(attr_state) showcmd=self:_chunks_repr(self.showcmd, attr_state), ruler=self:_chunks_repr(self.ruler, attr_state), msg_history=msg_history, - float_pos=self.float_pos + float_pos=self.float_pos, + win_viewport=win_viewport, } end @@ -1216,10 +1234,6 @@ function Screen:render(headers, attr_state, preview) return rv end -local remove_all_metatables = function(item, path) - if path[#path] ~= inspect.METATABLE then return item end -end - -- Returns the current screen state in the form of a screen:expect() -- keyword-args map. function Screen:get_snapshot(attrs, ignore) @@ -1269,6 +1283,26 @@ function Screen:get_snapshot(attrs, ignore) return kwargs, ext_state, attr_state end +local function fmt_ext_state(name, state) + if name == "win_viewport" then + local str = "{\n" + for k,v in pairs(state) do + str = (str.." ["..k.."] = {win = {id = "..v.win.id.."}, topline = " + ..v.topline..", botline = "..v.botline..", curline = "..v.curline + ..", curcol = "..v.curcol.."},\n") + end + return str .. "}" + else + -- TODO(bfredl): improve formatting of more states + local function remove_all_metatables(item, path) + if path[#path] ~= inspect.METATABLE then + return item + end + end + return inspect(state,{process=remove_all_metatables}) + end +end + function Screen:print_snapshot(attrs, ignore) local kwargs, ext_state, attr_state = self:get_snapshot(attrs, ignore) local attrstr = "" @@ -1291,9 +1325,8 @@ function Screen:print_snapshot(attrs, ignore) print(kwargs.grid) io.stdout:write( "]]"..attrstr) for _, k in ipairs(ext_keys) do - if ext_state[k] ~= nil then - -- TODO(bfredl): improve formatting - io.stdout:write(", "..k.."="..inspect(ext_state[k],{process=remove_all_metatables})) + if ext_state[k] ~= nil and not (k == "win_viewport" and not self.options.ext_multigrid) then + io.stdout:write(", "..k.."="..fmt_ext_state(k, ext_state[k])) end end print("}\n") diff --git a/test/functional/ui/wildmode_spec.lua b/test/functional/ui/wildmode_spec.lua index 56987d7bc2..99ebc4971e 100644 --- a/test/functional/ui/wildmode_spec.lua +++ b/test/functional/ui/wildmode_spec.lua @@ -16,6 +16,44 @@ describe("'wildmenu'", function() screen:attach() end) + it('C-E to cancel wildmenu completion restore original input', function() + feed(':sign <tab>') + screen:expect([[ + | + ~ | + ~ | + define jump list > | + :sign define^ | + ]]) + feed('<C-E>') + screen:expect([[ + | + ~ | + ~ | + ~ | + :sign ^ | + ]]) + end) + + it('C-Y to apply selection and end wildmenu completion', function() + feed(':sign <tab>') + screen:expect([[ + | + ~ | + ~ | + define jump list > | + :sign define^ | + ]]) + feed('<tab><C-Y>') + screen:expect([[ + | + ~ | + ~ | + ~ | + :sign jump^ | + ]]) + end) + it(':sign <tab> shows wildmenu completions', function() command('set wildmenu wildmode=full') feed(':sign <tab>') diff --git a/test/helpers.lua b/test/helpers.lua index 98f003f208..40b93d9935 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -55,17 +55,32 @@ local check_logs_useless_lines = { ['See README_MISSING_SYSCALL_OR_IOCTL for guidance']=3, } -function module.eq(expected, actual, context) - return assert.are.same(expected, actual, context) +--- Invokes `fn` and includes the tail of `logfile` in the error message if it +--- fails. +--- +--@param logfile Log file, defaults to $NVIM_LOG_FILE or '.nvimlog' +--@param fn Function to invoke +--@param ... Function arguments +local function dumplog(logfile, fn, ...) + -- module.validate({ + -- logfile={logfile,'s',true}, + -- fn={fn,'f',false}, + -- }) + local status, rv = pcall(fn, ...) + if status == false then + logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog' + local logtail = module.read_nvim_log(logfile) + error(string.format('%s\n%s', rv, logtail)) + end end -function module.neq(expected, actual, context) - return assert.are_not.same(expected, actual, context) +function module.eq(expected, actual, context, logfile) + return dumplog(logfile, assert.are.same, expected, actual, context) end -function module.ok(res, msg) - return assert.is_true(res, msg) +function module.neq(expected, actual, context, logfile) + return dumplog(logfile, assert.are_not.same, expected, actual, context) end -function module.near(actual, expected, tolerance) - return assert.is.near(actual, expected, tolerance) +function module.ok(res, msg, logfile) + return dumplog(logfile, assert.is_true, res, msg) end function module.matches(pat, actual) if nil ~= string.match(actual, pat) then @@ -74,6 +89,22 @@ function module.matches(pat, actual) error(string.format('Pattern does not match.\nPattern:\n%s\nActual:\n%s', pat, actual)) end +--- Asserts that `pat` matches one or more lines in the tail of $NVIM_LOG_FILE. +--- +--@param pat (string) Lua pattern to search for in the log file. +--@param logfile (string, default=$NVIM_LOG_FILE) full path to log file. +function module.assert_log(pat, logfile) + logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog' + local nrlines = 10 + local lines = module.read_file_list(logfile, -nrlines) or {} + for _,line in ipairs(lines) do + if line:match(pat) then return end + end + local logtail = module.read_nvim_log(logfile) + error(string.format('Pattern %q not found in log (last %d lines): %s:\n%s', + pat, nrlines, logfile, logtail)) +end + -- Invokes `fn` and returns the error string (may truncate full paths), or -- raises an error if `fn` succeeds. -- @@ -259,24 +290,6 @@ module.tmpname = (function() end) end)() -function module.map(func, tab) - local rettab = {} - for k, v in pairs(tab) do - rettab[k] = func(v) - end - return rettab -end - -function module.filter(filter_func, tab) - local rettab = {} - for _, entry in pairs(tab) do - if filter_func(entry) then - table.insert(rettab, entry) - end - end - return rettab -end - function module.hasenv(name) local env = os.getenv(name) if env and env ~= '' then @@ -737,10 +750,10 @@ function module.isCI(name) end --- Gets the contents of $NVIM_LOG_FILE for printing to the build log. +-- Gets the (tail) contents of `logfile`. -- Also moves the file to "${NVIM_LOG_FILE}.displayed" on CI environments. -function module.read_nvim_log() - local logfile = os.getenv('NVIM_LOG_FILE') or '.nvimlog' +function module.read_nvim_log(logfile, ci_rename) + logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog' local is_ci = module.isCI() local keep = is_ci and 999 or 10 local lines = module.read_file_list(logfile, -keep) or {} @@ -751,7 +764,7 @@ function module.read_nvim_log() log = log..line..'\n' end log = log..('-'):rep(78)..'\n' - if is_ci then + if is_ci and ci_rename then os.rename(logfile, logfile .. '.displayed') end return log diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index 4535d6a0b2..ea86ccbf1c 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -14,7 +14,7 @@ local cimport = helpers.cimport local to_cstr = helpers.to_cstr local alloc_log_new = helpers.alloc_log_new local concat_tables = helpers.concat_tables -local map = helpers.map +local map = helpers.tbl_map local a = eval_helpers.alloc_logging_helpers local int = eval_helpers.int @@ -2026,6 +2026,26 @@ describe('typval.c', function() alloc_log:check({}) end) end) + describe('float()', function() + itp('works', function() + local d = dict({test=10}) + alloc_log:clear() + eq({test=10}, dct2tbl(d)) + eq(OK, lib.tv_dict_add_float(d, 'testt', 3, 1.5)) + local dis = dict_items(d) + alloc_log:check({a.di(dis.tes, 'tes')}) + eq({test=10, tes=1.5}, dct2tbl(d)) + eq(FAIL, check_emsg(function() return lib.tv_dict_add_float(d, 'testt', 3, 1.5) end, + 'E685: Internal error: hash_add()')) + alloc_log:clear() + lib.emsg_skip = lib.emsg_skip + 1 + eq(FAIL, check_emsg(function() return lib.tv_dict_add_float(d, 'testt', 3, 1.5) end, + nil)) + lib.emsg_skip = lib.emsg_skip - 1 + alloc_log:clear_tmp_allocs() + alloc_log:check({}) + end) + end) describe('str()', function() itp('works', function() local d = dict({test=10}) diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index bacdc54416..a77a089763 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -13,7 +13,7 @@ local syscall = nil local check_cores = global_helpers.check_cores local dedent = global_helpers.dedent local neq = global_helpers.neq -local map = global_helpers.map +local map = global_helpers.tbl_map local eq = global_helpers.eq local trim = global_helpers.trim diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index af3ed0c5b3..153028bb2b 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -35,13 +35,13 @@ option(USE_BUNDLED_LIBTERMKEY "Use the bundled libtermkey." ${USE_BUNDLED}) option(USE_BUNDLED_LIBVTERM "Use the bundled libvterm." ${USE_BUNDLED}) option(USE_BUNDLED_LIBUV "Use the bundled libuv." ${USE_BUNDLED}) option(USE_BUNDLED_MSGPACK "Use the bundled msgpack." ${USE_BUNDLED}) -option(USE_BUNDLED_UTF8PROC "Use the bundled utf8proc." ${USE_BUNDLED}) option(USE_BUNDLED_LUAJIT "Use the bundled version of luajit." ${USE_BUNDLED}) option(USE_BUNDLED_LUAROCKS "Use the bundled version of luarocks." ${USE_BUNDLED}) option(USE_BUNDLED_LUV "Use the bundled version of luv." ${USE_BUNDLED}) #XXX(tarruda): Lua is only used for debugging the functional test client, no # build it unless explicitly requested option(USE_BUNDLED_LUA "Use the bundled version of lua." OFF) +option(USE_BUNDLED_TS_PARSERS "Use the bundled treesitter parsers." ${USE_BUNDLED}) if(USE_BUNDLED AND MSVC) option(USE_BUNDLED_GETTEXT "Use the bundled version of gettext." ON) @@ -190,14 +190,14 @@ set(WIN32YANK_X86_64_SHA256 33a747a92da60fb65e668edbf7661d3d902411a2d545fe9dc086 set(WINPTY_URL https://github.com/rprichard/winpty/releases/download/0.4.3/winpty-0.4.3-msvc2015.zip) set(WINPTY_SHA256 35a48ece2ff4acdcbc8299d4920de53eb86b1fb41e64d2fe5ae7898931bcee89) -set(GETTEXT_URL https://ftp.gnu.org/pub/gnu/gettext/gettext-0.19.8.1.tar.gz) -set(GETTEXT_SHA256 ff942af0e438ced4a8b0ea4b0b6e0d6d657157c5e2364de57baa279c1c125c43) +set(GETTEXT_URL https://ftp.gnu.org/pub/gnu/gettext/gettext-0.20.1.tar.gz) +set(GETTEXT_SHA256 66415634c6e8c3fa8b71362879ec7575e27da43da562c798a8a2f223e6e47f5c) set(LIBICONV_URL https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.15.tar.gz) set(LIBICONV_SHA256 ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178) -set(UTF8PROC_URL https://github.com/JuliaStrings/utf8proc/archive/v2.2.0.tar.gz) -set(UTF8PROC_SHA256 3f8fd1dbdb057ee5ba584a539d5cd1b3952141c0338557cb0bdf8cb9cfed5dbf) +set(TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/6002fcd.tar.gz) +set(TREESITTER_C_SHA256 46f8d44fa886d9ddb92571bb6fa8b175992c8758eca749cb1217464e512b6e97) if(USE_BUNDLED_UNIBILIUM) include(BuildUnibilium) @@ -250,8 +250,8 @@ if(USE_BUNDLED_LIBICONV) include(BuildLibiconv) endif() -if(USE_BUNDLED_UTF8PROC) - include(BuildUtf8proc) +if(USE_BUNDLED_TS_PARSERS) + include(BuildTreesitterParsers) endif() if(WIN32) @@ -287,13 +287,21 @@ if(WIN32) -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CopyFilesGlob.cmake) endif() -add_custom_target(clean-shared-libraries - COMMAND ${CMAKE_COMMAND} - -DREMOVE_FILE_GLOB=${DEPS_INSTALL_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}*${CMAKE_SHARED_LIBRARY_SUFFIX}* - -P ${PROJECT_SOURCE_DIR}/cmake/RemoveFiles.cmake - DEPENDS ${THIRD_PARTY_DEPS} -) +# clean-shared-libraries removes ${DEPS_INSTALL_DIR}/lib/nvim/parser/c.dll, +# resulting in MSVC build failure in CI. +if (MSVC) + set(ALL_DEPS ${THIRD_PARTY_DEPS}) +else() + add_custom_target(clean-shared-libraries + COMMAND ${CMAKE_COMMAND} + -DREMOVE_FILE_GLOB=${DEPS_INSTALL_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}*${CMAKE_SHARED_LIBRARY_SUFFIX}* + -P ${PROJECT_SOURCE_DIR}/cmake/RemoveFiles.cmake + DEPENDS ${THIRD_PARTY_DEPS} + ) + set(ALL_DEPS clean-shared-libraries) +endif() add_custom_target(third-party ALL COMMAND ${CMAKE_COMMAND} -E touch .third-party - DEPENDS clean-shared-libraries) + DEPENDS ${ALL_DEPS} +) diff --git a/third-party/cmake/BuildGettext.cmake b/third-party/cmake/BuildGettext.cmake index 45264167a5..9357456343 100644 --- a/third-party/cmake/BuildGettext.cmake +++ b/third-party/cmake/BuildGettext.cmake @@ -12,10 +12,6 @@ if(MSVC) -DTARGET=gettext -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake - PATCH_COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/gettext init - COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/gettext apply --ignore-whitespace - ${CMAKE_CURRENT_SOURCE_DIR}/patches/gettext-Fix-compilation-on-a-system-without-alloca.patch - ${CMAKE_CURRENT_SOURCE_DIR}/patches/gettext-Fix-building-with-MSVC.patch CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/cmake/GettextCMakeLists.txt ${DEPS_BUILD_DIR}/src/gettext/CMakeLists.txt diff --git a/third-party/cmake/BuildLuarocks.cmake b/third-party/cmake/BuildLuarocks.cmake index 4b1b94a46b..c5595bf840 100644 --- a/third-party/cmake/BuildLuarocks.cmake +++ b/third-party/cmake/BuildLuarocks.cmake @@ -217,10 +217,10 @@ if(USE_BUNDLED_BUSTED) endif() add_custom_target(luv DEPENDS ${ROCKS_DIR}/luv) - # nvim-client + # nvim-client: https://github.com/neovim/lua-client add_custom_command(OUTPUT ${ROCKS_DIR}/nvim-client COMMAND ${LUAROCKS_BINARY} - ARGS build nvim-client 0.2.0-1 ${LUAROCKS_BUILDARGS} + ARGS build nvim-client 0.2.2-1 ${LUAROCKS_BUILDARGS} DEPENDS luv) add_custom_target(nvim-client DEPENDS ${ROCKS_DIR}/nvim-client) diff --git a/third-party/cmake/BuildLuv.cmake b/third-party/cmake/BuildLuv.cmake index c2a2bbf083..ab3e2190ab 100644 --- a/third-party/cmake/BuildLuv.cmake +++ b/third-party/cmake/BuildLuv.cmake @@ -65,6 +65,7 @@ set(LUV_PATCH_COMMAND set(LUV_CONFIGURE_COMMAND_COMMON ${CMAKE_COMMAND} ${LUV_SRC_DIR} -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} -DLUA_BUILD_TYPE=System -DWITH_SHARED_LIBUV=ON @@ -87,7 +88,8 @@ endif() if(USE_BUNDLED_LIBUV) set(LUV_CONFIGURE_COMMAND_COMMON ${LUV_CONFIGURE_COMMAND_COMMON} - -DCMAKE_PREFIX_PATH=${DEPS_INSTALL_DIR}) + -DCMAKE_PREFIX_PATH=${DEPS_INSTALL_DIR} + -DLUA_COMPAT53_DIR=${DEPS_BUILD_DIR}/src/lua-compat-5.3) endif() if(MINGW AND CMAKE_CROSSCOMPILING) @@ -106,28 +108,23 @@ elseif(MSVC) # Same as Unix without fPIC "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1} ${LUV_INCLUDE_FLAGS}" # Make sure we use the same generator, otherwise we may - # accidentaly end up using different MSVC runtimes - -DCMAKE_GENERATOR=${CMAKE_GENERATOR} - # Use static runtime - -DCMAKE_C_FLAGS_DEBUG="-MTd" - -DCMAKE_C_FLAGS_RELEASE="-MT") + # accidentally end up using different MSVC runtimes + -DCMAKE_GENERATOR=${CMAKE_GENERATOR}) else() set(LUV_CONFIGURE_COMMAND ${LUV_CONFIGURE_COMMAND_COMMON} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1} ${LUV_INCLUDE_FLAGS} -fPIC") + if(CMAKE_GENERATOR MATCHES "Unix Makefiles" AND + (CMAKE_SYSTEM_NAME MATCHES ".*BSD" OR CMAKE_SYSTEM_NAME MATCHES "DragonFly")) + set(LUV_CONFIGURE_COMMAND ${LUV_CONFIGURE_COMMAND} -DCMAKE_MAKE_PROGRAM=gmake) + endif() endif() -if(CMAKE_GENERATOR MATCHES "Unix Makefiles" AND - (CMAKE_SYSTEM_NAME MATCHES ".*BSD" OR CMAKE_SYSTEM_NAME MATCHES "DragonFly")) - set(LUV_BUILD_COMMAND ${CMAKE_COMMAND} - "-DLUA_COMPAT53_DIR=${DEPS_BUILD_DIR}/src/lua-compat-5.3" - "-DCMAKE_MAKE_PROGRAM=gmake" --build .) -else() - set(LUV_BUILD_COMMAND ${CMAKE_COMMAND} - "-DLUA_COMPAT53_DIR=${DEPS_BUILD_DIR}/src/lua-compat-5.3" --build .) -endif() -set(LUV_INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install) +set(LUV_BUILD_COMMAND + ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE}) +set(LUV_INSTALL_COMMAND + ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}) BuildLuv(PATCH_COMMAND ${LUV_PATCH_COMMAND} CONFIGURE_COMMAND ${LUV_CONFIGURE_COMMAND} diff --git a/third-party/cmake/BuildMsgpack.cmake b/third-party/cmake/BuildMsgpack.cmake index 616b6e5f83..30af5f060b 100644 --- a/third-party/cmake/BuildMsgpack.cmake +++ b/third-party/cmake/BuildMsgpack.cmake @@ -68,7 +68,7 @@ elseif(MSVC) "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1}" -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} # Make sure we use the same generator, otherwise we may - # accidentaly end up using different MSVC runtimes + # accidentally end up using different MSVC runtimes -DCMAKE_GENERATOR=${CMAKE_GENERATOR}) endif() diff --git a/third-party/cmake/BuildTreesitterParsers.cmake b/third-party/cmake/BuildTreesitterParsers.cmake new file mode 100644 index 0000000000..5284a7fd62 --- /dev/null +++ b/third-party/cmake/BuildTreesitterParsers.cmake @@ -0,0 +1,27 @@ +ExternalProject_Add(treesitter-c +PREFIX ${DEPS_BUILD_DIR} +URL ${TREESITTER_C_URL} +DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/treesitter-c +DOWNLOAD_COMMAND ${CMAKE_COMMAND} + -DPREFIX=${DEPS_BUILD_DIR} + -DDOWNLOAD_DIR=${DEPS_DOWNLOAD_DIR}/treesitter-c + -DURL=${TREESITTER_C_URL} + -DEXPECTED_SHA256=${TREESITTER_C_SHA256} + -DTARGET=treesitter-c + -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake +PATCH_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/TreesitterParserCMakeLists.txt + ${DEPS_BUILD_DIR}/src/treesitter-c/CMakeLists.txt +CMAKE_ARGS + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} + # Pass toolchain + -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} + -DPARSERLANG=c + +BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} +INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}) diff --git a/third-party/cmake/BuildUtf8proc.cmake b/third-party/cmake/BuildUtf8proc.cmake deleted file mode 100644 index 7297913f87..0000000000 --- a/third-party/cmake/BuildUtf8proc.cmake +++ /dev/null @@ -1,68 +0,0 @@ -include(CMakeParseArguments) - -# BuildUtf8proc(CONFIGURE_COMMAND ... BUILD_COMMAND ... INSTALL_COMMAND ...) -# Reusable function to build utf8proc, wraps ExternalProject_Add. -# Failing to pass a command argument will result in no command being run -function(BuildUtf8proc) - cmake_parse_arguments(_utf8proc - "" - "" - "CONFIGURE_COMMAND;BUILD_COMMAND;INSTALL_COMMAND" - ${ARGN}) - - if(NOT _utf8proc_CONFIGURE_COMMAND AND NOT _utf8proc_BUILD_COMMAND - AND NOT _utf8proc_INSTALL_COMMAND) - message(FATAL_ERROR "Must pass at least one of CONFIGURE_COMMAND, BUILD_COMMAND, INSTALL_COMMAND") - endif() - - ExternalProject_Add(utf8proc - PREFIX ${DEPS_BUILD_DIR} - URL ${UTF8PROC_URL} - DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/utf8proc - DOWNLOAD_COMMAND ${CMAKE_COMMAND} - -DPREFIX=${DEPS_BUILD_DIR} - -DDOWNLOAD_DIR=${DEPS_DOWNLOAD_DIR}/utf8proc - -DURL=${UTF8PROC_URL} - -DEXPECTED_SHA256=${UTF8PROC_SHA256} - -DTARGET=utf8proc - -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} - -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake - CONFIGURE_COMMAND "${_utf8proc_CONFIGURE_COMMAND}" - BUILD_COMMAND "${_utf8proc_BUILD_COMMAND}" - INSTALL_COMMAND "${_utf8proc_INSTALL_COMMAND}") -endfunction() - -set(UTF8PROC_CONFIGURE_COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/utf8proc - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} - "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1} -fPIC" - -DCMAKE_GENERATOR=${CMAKE_GENERATOR}) - -set(UTF8PROC_BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE}) -set(UTF8PROC_INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}) - -if(MINGW AND CMAKE_CROSSCOMPILING) - get_filename_component(TOOLCHAIN ${CMAKE_TOOLCHAIN_FILE} REALPATH) - set(UTF8PROC_CONFIGURE_COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/utf8proc - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - # Pass toolchain - -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} - # Hack to avoid -rdynamic in Mingw - -DCMAKE_SHARED_LIBRARY_LINK_C_FLAGS="") -elseif(MSVC) - # Same as Unix without fPIC - set(UTF8PROC_CONFIGURE_COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/utf8proc - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1}" - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} - # Make sure we use the same generator, otherwise we may - # accidentaly end up using different MSVC runtimes - -DCMAKE_GENERATOR=${CMAKE_GENERATOR}) -endif() - -BuildUtf8proc(CONFIGURE_COMMAND ${UTF8PROC_CONFIGURE_COMMAND} - BUILD_COMMAND ${UTF8PROC_BUILD_COMMAND} - INSTALL_COMMAND ${UTF8PROC_INSTALL_COMMAND}) diff --git a/third-party/cmake/GettextCMakeLists.txt b/third-party/cmake/GettextCMakeLists.txt index 5a6253df3b..c3f78716d0 100644 --- a/third-party/cmake/GettextCMakeLists.txt +++ b/third-party/cmake/GettextCMakeLists.txt @@ -8,6 +8,10 @@ endmacro() file(READ gettext-runtime/config.h.in CONFIG_CONTENT) string(REPLACE "#undef HAVE_GETCWD" "#define HAVE_GETCWD 1" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef HAVE_LONG_LONG_INT" "#define HAVE_LONG_LONG_INT 1" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef HAVE_ICONV_H" "#define HAVE_ICONV_H 1" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef HAVE_ICONV" "#define HAVE_ICONV 1" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef ICONV_CONST" "#define ICONV_CONST const" CONFIG_CONTENT ${CONFIG_CONTENT}) string(REPLACE "#undef uintmax_t" " #if _WIN64 # define intmax_t long long @@ -24,6 +28,8 @@ set(HAVE_POSIX_PRINTF 0) set(HAVE_SNPRINTF 0) set(HAVE_ASPRINTF 0) set(HAVE_WPRINTF 0) +set(HAVE_NAMELESS_LOCALES 0) +set(HAVE_LONG_LONG_INT 1) configure_file(gettext-runtime/intl/libgnuintl.in.h ${CMAKE_CURRENT_BINARY_DIR}/gettext-runtime/intl/libgnuintl.h) @@ -45,16 +51,20 @@ add_definitions(-DLOCALEDIR=\"${LOCALDIR}\" set(libintl_SOURCES bindtextdom.c dcgettext.c dcigettext.c dcngettext.c dgettext.c dngettext.c - explodename.c finddomain.c gettext.c hash-string.c l10nflist.c langprefs.c - loadmsgcat.c localcharset.c localealias.c localename.c lock.c log.c ngettext.c + explodename.c finddomain.c gettext.c hash-string.c intl-compat.c l10nflist.c + langprefs.c loadmsgcat.c localcharset.c localealias.c localename-table.c + localename.c lock.c log.c ngettext.c osdep.c plural-exp.c plural.c printf.c relocatable.c setlocale.c textdomain.c - threadlib.c version.c) + threadlib.c version.c xsize.c) + PREFIX_LIST_ITEMS(libintl_SOURCES "gettext-runtime/intl/") add_library(libintl ${libintl_SOURCES}) +target_link_libraries(libintl ${LIBICONV_LIBRARIES}) set_property(TARGET libintl APPEND PROPERTY INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}/gettext-runtime - ${CMAKE_CURRENT_BINARY_DIR}/gettext-runtime/intl) + ${CMAKE_CURRENT_BINARY_DIR}/gettext-runtime/intl + ${LIBICONV_INCLUDE_DIRS}) set_property(TARGET libintl APPEND PROPERTY COMPILE_DEFINITIONS BUILDING_LIBINTL IN_LIBINTL @@ -68,16 +78,22 @@ set_property(TARGET libintl APPEND PROPERTY COMPILE_DEFINITIONS file(READ gettext-tools/config.h.in CONFIG_CONTENT) +string(REPLACE "#undef FLEXIBLE_ARRAY_MEMBER" "#define FLEXIBLE_ARRAY_MEMBER 1" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "__declspec (dllimport)" "" CONFIG_CONTENT ${CONFIG_CONTENT}) string(REPLACE "#undef ENDIANNESS" "#define ENDIANNESS 0" CONFIG_CONTENT ${CONFIG_CONTENT}) string(REPLACE "#undef GNULIB_FWRITEERROR" "#define GNULIB_FWRITEERROR 1" CONFIG_CONTENT ${CONFIG_CONTENT}) string(REPLACE "#undef HAVE_DECL_STRERROR_R" "#define HAVE_DECL_STRERROR_R 0" CONFIG_CONTENT ${CONFIG_CONTENT}) string(REPLACE "#undef HAVE_DUP2" "#define HAVE_DUP2 1" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef HAVE_ICONV_H" "#define HAVE_ICONV_H 1" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef HAVE_ICONV" "#define HAVE_ICONV 1" CONFIG_CONTENT ${CONFIG_CONTENT}) string(REPLACE "#undef HAVE_LIBUNISTRING" "#define HAVE_LIBUNISTRING 1" CONFIG_CONTENT ${CONFIG_CONTENT}) string(REPLACE "#undef HAVE_STDINT_H_WITH_UINTMAX" "#define HAVE_STDINT_H_WITH_UINTMAX 1" CONFIG_CONTENT ${CONFIG_CONTENT}) string(REPLACE "#undef HAVE_STDINT_H" "#define HAVE_STDINT_H 1" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef HAVE_LONG_LONG_INT" "#define HAVE_LONG_LONG_INT 1" CONFIG_CONTENT ${CONFIG_CONTENT}) string(REPLACE "#undef HAVE_STRING_H" "#define HAVE_STRING_H 1" CONFIG_CONTENT ${CONFIG_CONTENT}) string(REPLACE "#undef HAVE_SYS_TIMEB_H" "#define HAVE_SYS_TIMEB_H 1" CONFIG_CONTENT ${CONFIG_CONTENT}) string(REPLACE "#undef HAVE__FTIME" "#define HAVE__FTIME 1" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef HAVE_FLOAT_H" "#define HAVE_FLOAT_H 1" CONFIG_CONTENT ${CONFIG_CONTENT}) string(REPLACE "#undef ICONV_CONST" "#define ICONV_CONST const" CONFIG_CONTENT ${CONFIG_CONTENT}) string(REPLACE "#undef PACKAGE" "#define PACKAGE \"gettext\"\n#define gettext_VERSION" CONFIG_CONTENT ${CONFIG_CONTENT}) string(REPLACE "#undef VERSION" "#define VERSION \"\"" CONFIG_CONTENT ${CONFIG_CONTENT}) @@ -86,13 +102,14 @@ string(REPLACE "#undef pid_t" "#define pid_t int" CONFIG_CONTENT ${CONFIG_CONTEN string(REPLACE "#undef restrict" "#define restrict __restrict" CONFIG_CONTENT ${CONFIG_CONTENT}) string(REPLACE "#undef ssize_t" "#include <BaseTsd.h>\n#define ssize_t SSIZE_T" CONFIG_CONTENT ${CONFIG_CONTENT}) string(REPLACE "#undef uid_t" "#define uid_t int" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef HAVE_DECL___ARGV" "#define HAVE_DECL___ARGV 1" CONFIG_CONTENT ${CONFIG_CONTENT}) +set(CONFIG_CONTENT "${CONFIG_CONTENT}\n#define isatty libtextstyle_isatty") file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/gettext-tools/config.h ${CONFIG_CONTENT}) -set(libgettextsrc_COMMON_SOURCE +set(libgettextsrc_COMMON_SOURCE message.c po-error.c po-xerror.c read-catalog-abstract.c po-lex.c po-gram-gen.c po-charset.c read-po.c read-properties.c read-stringtable.c - open-catalog.c dir-list.c - str-list.c) + open-catalog.c dir-list.c str-list.c) set(libgettextsrc_FORMAT_SOURCE format.c format-invalid.h format-c.c format-c-parse.h format-sh.c @@ -104,11 +121,12 @@ set(libgettextsrc_FORMAT_SOURCE format-javascript.c) set(libgettextsrc_SOURCES - ${libgettextsrc_COMMON_SOURCE} read-catalog.c color.c write-catalog.c - write-properties.c write-stringtable.c write-po.c msgl-ascii.c msgl-iconv.c - msgl-equal.c msgl-cat.c msgl-header.c msgl-english.c msgl-check.c file-list.c - msgl-charset.c po-time.c plural-exp.c plural-eval.c plural-count.c - plural-table.c quote.h sentence.h sentence.c ${libgettextsrc_FORMAT_SOURCE} + ${libgettextsrc_COMMON_SOURCE} read-catalog.c + write-catalog.c write-properties.c write-stringtable.c write-po.c + msgl-ascii.c msgl-iconv.c msgl-equal.c msgl-cat.c msgl-header.c msgl-english.c + msgl-check.c file-list.c msgl-charset.c po-time.c plural-exp.c plural-eval.c + plural-table.c quote.h sentence.h sentence.c + ${libgettextsrc_FORMAT_SOURCE} read-desktop.c locating-rule.c its.c search-path.c) PREFIX_LIST_ITEMS(libgettextsrc_SOURCES "gettext-tools/src/") @@ -118,101 +136,141 @@ set(GLIBC_SOURCE gettimeofday.c getdtablesize.c fcntl.c dup-safer-flag.c cloexec.c fd-safer-flag.c fd-safer.c pipe2.c pipe2-safer.c spawn-pipe.c xmemdup0.c secure_getenv.c tmpdir.c tempname.c mkdtemp.c fnmatch.c clean-temp.c - gl_array_list.c tputs.c wait-process.c waitpid.c getdelim.c getline.c - sigprocmask.c sigaction.c addext.c argmatch.c backupfile.c basename.c - c-strcasecmp.c c-strncasecmp.c c-strstr.c closeout.c concat-filename.c - error-progname.c error.c exitfail.c file-ostream.c fstrcmp.c full-write.c - fwriteerror.c getopt.c getopt1.c hash.c libxml/buf.c localcharset.c malloca.c - mbchar.c mbslen.c mbsstr.c mbswidth.c obstack.c ostream.c html-ostream.c - fd-ostream.c styled-ostream.c progname.c html-styled-ostream.c printf-args.c - printf-parse.c propername.c quotearg.c rawmemchr.c safe-read.c safe-write.c - stpcpy.c stpncpy.c strchrnul.c striconveh.c striconveha.c strnlen1.c - term-ostream.c term-styled-ostream.c tparm.c trim.c gcd.c gl_linkedhash_list.c + wait-process.c waitpid.c getdelim.c getline.c sigprocmask.c sigaction.c + addext.c argmatch.c backupfile.c basename.c c-strcasecmp.c c-strncasecmp.c + c-strstr.c closeout.c concat-filename.c error-progname.c error.c exitfail.c + fstrcmp.c full-write.c fwriteerror.c getopt.c getopt1.c hash.c libxml/buf.c + localcharset.c malloca.c mbchar.c mbslen.c mbsstr.c mbswidth.c obstack.c + progname.c printf-args.c printf-parse.c propername.c quotearg.c rawmemchr.c + safe-read.c safe-write.c stpcpy.c stpncpy.c strchrnul.c striconv.c + striconveh.c striconveha.c strnlen1.c trim.c gcd.c gl_linkedhash_list.c uniconv/u8-conv-from-enc.c unictype/ctype_space.c unilbrk/lbrktables.c unilbrk/u8-possible-linebreaks.c unilbrk/u8-width-linebreaks.c unilbrk/ulc-common.c unilbrk/ulc-width-linebreaks.c unistr/u16-mbtouc-aux.c - unistr/u16-mbtouc.c unistr/u8-check.c unistr/u8-mblen.c unistr/u8-mbtouc-aux.c - unistr/u8-mbtouc-unsafe-aux.c unistr/u8-mbtouc-unsafe.c unistr/u8-mbtouc.c - unistr/u8-mbtoucr.c unistr/u8-prev.c unistr/u8-uctomb-aux.c unistr/u8-uctomb.c - uniwidth/width.c vasnprintf.c vasprintf.c wcwidth.c xasprintf.c - xconcat-filename.c xerror.c xmalloc.c xstrdup.c xvasprintf.c glib/ghash.c - glib/glist.c glib/gmessages.c glib/gprimes.c glib/gstrfuncs.c glib/gstring.c - libcroco/cr-additional-sel.c libcroco/cr-attr-sel.c libcroco/cr-cascade.c - libcroco/cr-declaration.c libcroco/cr-doc-handler.c libcroco/cr-enc-handler.c - libcroco/cr-fonts.c libcroco/cr-input.c libcroco/cr-num.c - libcroco/cr-om-parser.c libcroco/cr-parser.c libcroco/cr-parsing-location.c - libcroco/cr-prop-list.c libcroco/cr-pseudo.c libcroco/cr-rgb.c - libcroco/cr-sel-eng.c libcroco/cr-selector.c libcroco/cr-simple-sel.c - libcroco/cr-statement.c libcroco/cr-string.c libcroco/cr-style.c - libcroco/cr-stylesheet.c libcroco/cr-term.c libcroco/cr-tknzr.c - libcroco/cr-token.c libcroco/cr-utils.c libxml/DOCBparser.c - libxml/HTMLparser.c libxml/HTMLtree.c libxml/SAX.c libxml/SAX2.c libxml/c14n.c - libxml/catalog.c libxml/chvalid.c libxml/debugXML.c libxml/dict.c - libxml/encoding.c libxml/entities.c libxml/error.c libxml/globals.c - libxml/hash.c libxml/legacy.c libxml/list.c libxml/nanoftp.c libxml/nanohttp.c - libxml/parser.c libxml/parserInternals.c libxml/pattern.c libxml/relaxng.c - libxml/schematron.c libxml/threads.c libxml/tree.c libxml/trionan.c - libxml/uri.c libxml/valid.c libxml/xinclude.c libxml/xlink.c libxml/xmlIO.c - libxml/xmlmemory.c libxml/xmlmodule.c libxml/xmlreader.c libxml/xmlregexp.c - libxml/xmlsave.c libxml/xmlschemas.c libxml/xmlschemastypes.c - libxml/xmlstring.c libxml/xmlunicode.c libxml/xmlwriter.c libxml/xpath.c - libxml/xpointer.c fatal-signal.c copy-file.c) + unistr/u16-mbtouc.c unistr/u8-check.c unistr/u8-mblen.c + unistr/u8-mbtouc-aux.c unistr/u8-mbtouc-unsafe-aux.c + unistr/u8-mbtouc-unsafe.c unistr/u8-mbtouc.c unistr/u8-mbtoucr.c + unistr/u8-prev.c unistr/u8-uctomb-aux.c unistr/u8-uctomb.c uniwidth/width.c + vasnprintf.c vasprintf.c wcwidth.c xasprintf.c xconcat-filename.c xerror.c + xmalloc.c xstrdup.c xstriconv.c xstriconveh.c xvasprintf.c + libxml/DOCBparser.c libxml/HTMLparser.c libxml/HTMLtree.c libxml/SAX.c + libxml/SAX2.c libxml/c14n.c libxml/catalog.c libxml/chvalid.c + libxml/debugXML.c libxml/dict.c libxml/encoding.c libxml/entities.c + libxml/error.c libxml/globals.c libxml/hash.c libxml/legacy.c libxml/list.c + libxml/nanoftp.c libxml/nanohttp.c libxml/parser.c libxml/parserInternals.c + libxml/pattern.c libxml/relaxng.c libxml/schematron.c libxml/threads.c + libxml/tree.c libxml/trionan.c libxml/uri.c libxml/valid.c libxml/xinclude.c + libxml/xlink.c libxml/xmlIO.c libxml/xmlmemory.c libxml/xmlmodule.c + libxml/xmlreader.c libxml/xmlregexp.c libxml/xmlsave.c libxml/xmlschemas.c + libxml/xmlschemastypes.c libxml/xmlstring.c libxml/xmlunicode.c + libxml/xmlwriter.c libxml/xpath.c libxml/xpointer.c fatal-signal.c + copy-file.c read-file.c ftello.c utime.c gettime.c utimens.c) PREFIX_LIST_ITEMS(GLIBC_SOURCE "gettext-tools/gnulib-lib/") -set(libgettextsrc_SOURCES ${libgettextsrc_SOURCES} ${GLIBC_SOURCE}) - -set(HEADER_TEMPLATES_PATH "gettext-tools/gnulib-lib") -set(HEADER_TEMPLATES_ABS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${HEADER_TEMPLATES_PATH}") -file(GLOB_RECURSE HEADER_TEMPLATES "${HEADER_TEMPLATES_ABS_PATH}/*.in.h") -list(REMOVE_ITEM HEADER_TEMPLATES "${HEADER_TEMPLATES_ABS_PATH}/stdint.in.h") -list(REMOVE_ITEM HEADER_TEMPLATES "${HEADER_TEMPLATES_ABS_PATH}/wchar.in.h") -foreach(HEADER_TEMPLATE ${HEADER_TEMPLATES}) - file(READ ${HEADER_TEMPLATE} HEADER_CONTENT) - string(REPLACE "/* The definition of _GL_ARG_NONNULL is copied here. */" "#include \"arg-nonnull.h\"" HEADER_CONTENT "${HEADER_CONTENT}") - string(REPLACE "/* The definition of _GL_WARN_ON_USE is copied here. */" "#include \"warn-on-use.h\"" HEADER_CONTENT "${HEADER_CONTENT}") - string(REPLACE "/* The definitions of _GL_FUNCDECL_RPL etc. are copied here. */" "#include \"c++defs.h\"" HEADER_CONTENT "${HEADER_CONTENT}") - string(REPLACE "@GNULIB_LSTAT@" "1" HEADER_CONTENT "${HEADER_CONTENT}") - string(REPLACE "@GNULIB_MBSINIT@" "1" HEADER_CONTENT "${HEADER_CONTENT}") - string(REPLACE "@GNULIB_SIGACTION@" "1" HEADER_CONTENT "${HEADER_CONTENT}") - string(REPLACE "@GNULIB_SIGPROCMASK@" "1" HEADER_CONTENT "${HEADER_CONTENT}") - string(REPLACE "@GNULIB_STPCPY@" "1" HEADER_CONTENT "${HEADER_CONTENT}") - string(REPLACE "@GNULIB_STPNCPY@" "1" HEADER_CONTENT "${HEADER_CONTENT}") - string(REPLACE "@GNULIB_STRCHRNUL@" "1" HEADER_CONTENT "${HEADER_CONTENT}") - string(REPLACE "@HAVE_ISWCNTRL@" "1" HEADER_CONTENT "${HEADER_CONTENT}") - string(REPLACE "@HAVE_WCTYPE_T@" "1" HEADER_CONTENT "${HEADER_CONTENT}") - string(REPLACE "@PRAGMA_COLUMNS@" "" HEADER_CONTENT "${HEADER_CONTENT}") - - string(REGEX REPLACE "^${HEADER_TEMPLATES_ABS_PATH}/" "" HEADER_PATH "${HEADER_TEMPLATE}") - string(REPLACE ".in" "" HEADER_PATH ${HEADER_PATH}) - string(REPLACE "_" "/" HEADER_PATH ${HEADER_PATH}) - # find_file will create a cache entry for the variable - # SYSTEM_HEADER, so reset it before each call - set(SYSTEM_HEADER "SYSTEM_HEADER-NOTFOUND") - find_file(SYSTEM_HEADER ${HEADER_PATH} PATHS "${LIBICONV_INCLUDE_DIRS}") - if(SYSTEM_HEADER) - # Gnulib uses #include_next to extend system header files, - # but MSVC doesn't support it, so a regular include directive - # with a relative path is used instead - string(REGEX REPLACE ".*/(.*/${HEADER_PATH})" "../\\1" - INCLUDE_PATH "${SYSTEM_HEADER}") - string(REGEX REPLACE "@INCLUDE_NEXT[^@]*@ @NEXT_[^@\n]+@" - "include <${INCLUDE_PATH}>" HEADER_CONTENT "${HEADER_CONTENT}") - endif() - - # Default any remaining template variables to 0 - string(REGEX REPLACE "@[^@\n]+@" "0" HEADER_CONTENT "${HEADER_CONTENT}") - - file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${HEADER_TEMPLATES_PATH}/${HEADER_PATH}" "${HEADER_CONTENT}") -endforeach() +file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/gettext-tools/gnulib-lib/configmake.h "#define PKGDATADIR \"gettext\"") + +set(LIBGLIB_SOURCES + ghash.c glist.c gmessages.c gprimes.c gstrfuncs.c gstring.c) +PREFIX_LIST_ITEMS(LIBGLIB_SOURCES "libtextstyle/lib/glib/") + +set(LIBTEXTSTYLE_SOURCE + gl_array_list.h gl_array_list.c binary-io.h + binary-io.c c-ctype.h c-ctype.c c-strcase.h c-strcasecmp.c + c-strncasecmp.c concat-filename.c dirname-lgpl.c + basename-lgpl.c stripslash.c exitfail.c fatal-signal.h + fatal-signal.c fd-hook.c fd-ostream.c file-ostream.c + full-write.h full-write.c getprogname.h getprogname.c + gettext.h hash.h hash.c html-ostream.c html-styled-ostream.c + iconv-ostream.c gl_list.h gl_list.c math.c memory-ostream.c + minmax.h noop-styled-ostream.c ostream.c safe-read.c + safe-write.c sig-handler.c size_max.h styled-ostream.c + term-ostream.c term-style-control.c term-styled-ostream.c + unistd.c xalloc.h xmalloc.c xstrdup.c + xconcat-filename.c gl_xlist.h gl_xlist.c xsize.h xsize.c + xvasprintf.h xvasprintf.c xasprintf.c color.h color.c misc.h + misc.c version.c isatty.c fsync.c tparm.c tputs.c) +PREFIX_LIST_ITEMS(LIBTEXTSTYLE_SOURCE "libtextstyle/lib/") + +configure_file( + libtextstyle/lib/stdbool.mini.h + ${CMAKE_CURRENT_BINARY_DIR}/libtextstyle/lib/textstyle/stdbool.h + COPYONLY) + +set(LIBCROCO_SOURCES + cr-additional-sel.c cr-attr-sel.c cr-cascade.c cr-declaration.c + cr-doc-handler.c cr-enc-handler.c cr-fonts.c cr-input.c cr-num.c + cr-om-parser.c cr-parser.c cr-parsing-location.c cr-prop-list.c cr-pseudo.c + cr-rgb.c cr-sel-eng.c cr-selector.c cr-simple-sel.c cr-statement.c + cr-string.c cr-style.c cr-stylesheet.c cr-term.c cr-tknzr.c cr-token.c + cr-utils.c) +PREFIX_LIST_ITEMS(LIBCROCO_SOURCES "libtextstyle/lib/libcroco/") + +set(libgettextsrc_SOURCES + ${libgettextsrc_SOURCES} ${GLIBC_SOURCE} ${LIBGLIB_SOURCES} + ${LIBTEXTSTYLE_SOURCE} ${LIBCROCO_SOURCES}) + +macro(CONFIGURE_HEADER_FILES HEADER_TEMPLATES_PATH) + set(HEADER_TEMPLATES_ABS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${HEADER_TEMPLATES_PATH}") + file(GLOB_RECURSE HEADER_TEMPLATES "${HEADER_TEMPLATES_ABS_PATH}/*.in.h") + list(REMOVE_ITEM HEADER_TEMPLATES "${HEADER_TEMPLATES_ABS_PATH}/stdint.in.h") + list(REMOVE_ITEM HEADER_TEMPLATES "${HEADER_TEMPLATES_ABS_PATH}/wchar.in.h") + foreach(HEADER_TEMPLATE ${HEADER_TEMPLATES}) + file(READ ${HEADER_TEMPLATE} HEADER_CONTENT) + string(REPLACE "/* The definition of _GL_ARG_NONNULL is copied here. */" "#include \"arg-nonnull.h\"" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "/* The definition of _GL_WARN_ON_USE is copied here. */" "#include \"warn-on-use.h\"" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "/* The definitions of _GL_FUNCDECL_RPL etc. are copied here. */" "#include \"c++defs.h\"" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@GNULIB_LSTAT@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@GNULIB_MBSINIT@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@GNULIB_SIGACTION@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@GNULIB_SIGPROCMASK@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@GNULIB_STPCPY@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@GNULIB_STPNCPY@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@GNULIB_STRCHRNUL@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@HAVE_ISWCNTRL@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@HAVE_WCTYPE_T@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@HAVE_STRUCT_TIMEVAL@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@HAVE_WINSOCK2_H@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@DLL_VARIABLE@" "" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@HAVE_NEWLOCALE@" "0" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@PRAGMA_COLUMNS@" "" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "#if @GNULIB_UTIME@" "#if 1\n#define utime gl_utime" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@HAVE_UTIME@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@HAVE_LONG_LONG_INT@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + + string(REGEX REPLACE "^${HEADER_TEMPLATES_ABS_PATH}/" "" HEADER_PATH "${HEADER_TEMPLATE}") + string(REPLACE ".in" "" HEADER_PATH ${HEADER_PATH}) + string(REPLACE "_" "/" HEADER_PATH ${HEADER_PATH}) + # find_file will create a cache entry for the variable + # SYSTEM_HEADER, so reset it before each call + set(SYSTEM_HEADER "SYSTEM_HEADER-NOTFOUND") + find_file(SYSTEM_HEADER ${HEADER_PATH} PATHS "${LIBICONV_INCLUDE_DIRS}") + if(SYSTEM_HEADER) + # Gnulib uses #include_next to extend system header files, + # but MSVC doesn't support it, so a regular include directive + # with a relative path is used instead + string(REGEX REPLACE ".*/(.*/${HEADER_PATH})" "../\\1" + INCLUDE_PATH "${SYSTEM_HEADER}") + string(REGEX REPLACE "@INCLUDE_NEXT[^@]*@ @NEXT_[^@\n]+@" + "include <${INCLUDE_PATH}>" HEADER_CONTENT "${HEADER_CONTENT}") + endif() + + # Default any remaining template variables to 0 + string(REGEX REPLACE "@[^@\n]+@" "0" HEADER_CONTENT "${HEADER_CONTENT}") + + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${HEADER_TEMPLATES_PATH}/${HEADER_PATH}" "${HEADER_CONTENT}") + endforeach() +endmacro() + +CONFIGURE_HEADER_FILES("gettext-tools/gnulib-lib") +CONFIGURE_HEADER_FILES("libtextstyle/lib") add_library(libgettextsrc ${libgettextsrc_SOURCES}) target_link_libraries(libgettextsrc ${LIBICONV_LIBRARIES}) +set_property(TARGET libgettextsrc APPEND PROPERTY COMPILE_DEFINITIONS + LIBTEXTSTYLE_DLL_VARIABLE=) set(msgmerge_SOURCES - msgmerge.c - msgl-fsearch.c - lang-table.c - ) + msgmerge.c msgl-fsearch.c lang-table.c plural-count.c) PREFIX_LIST_ITEMS(msgmerge_SOURCES "gettext-tools/src/") add_executable(msgmerge ${msgmerge_SOURCES}) @@ -220,25 +278,21 @@ target_link_libraries(msgmerge libgettextsrc) add_dependencies(msgmerge libgettextsrc libintl) set(msgfmt_SOURCES - msgfmt.c - write-mo.c - write-java.c - write-csharp.c - write-resources.c - write-tcl.c - write-qt.c - write-desktop.c - write-xml.c) + msgfmt.c write-mo.c write-java.c write-csharp.c write-resources.c write-tcl.c + write-qt.c write-desktop.c write-xml.c + ../../gettext-runtime/intl/hash-string.c) PREFIX_LIST_ITEMS(msgfmt_SOURCES "gettext-tools/src/") -add_executable(msgfmt ${msgfmt_SOURCES} gettext-runtime/intl/hash-string.c) +add_executable(msgfmt ${msgfmt_SOURCES}) target_link_libraries(msgfmt libgettextsrc) add_dependencies(msgfmt libgettextsrc libintl) set(xgettext_SOURCES - xgettext.c x-c.c x-po.c x-sh.c x-python.c x-lisp.c x-elisp.c x-librep.c - x-scheme.c x-smalltalk.c x-java.c x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c - x-php.c x-rst.c x-lua.c x-javascript.c x-vala.c x-desktop.c) + xgettext.c xg-pos.c xg-encoding.c xg-mixed-string.c xg-arglist-context.c + xg-arglist-callshape.c xg-arglist-parser.c xg-message.c x-c.c x-po.c x-sh.c + x-python.c x-lisp.c x-elisp.c x-librep.c x-scheme.c x-smalltalk.c x-java.c + x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c x-php.c x-rst.c x-lua.c + x-javascript.c x-vala.c x-desktop.c) PREFIX_LIST_ITEMS(xgettext_SOURCES "gettext-tools/src/") add_executable(xgettext ${xgettext_SOURCES}) @@ -252,9 +306,13 @@ set_property(TARGET msgmerge msgfmt xgettext libgettextsrc APPEND PROPERTY ${CMAKE_CURRENT_SOURCE_DIR}/gettext-tools/gnulib-lib ${CMAKE_CURRENT_SOURCE_DIR}/gettext-tools/gnulib-lib/libcroco ${CMAKE_CURRENT_SOURCE_DIR}/build-aux/snippet + ${CMAKE_CURRENT_SOURCE_DIR}/libtextstyle/lib + ${CMAKE_CURRENT_SOURCE_DIR}/libtextstyle/lib/libcroco ${CMAKE_CURRENT_BINARY_DIR}/gettext-runtime/intl ${CMAKE_CURRENT_BINARY_DIR}/gettext-tools ${CMAKE_CURRENT_BINARY_DIR}/gettext-tools/gnulib-lib + ${CMAKE_CURRENT_BINARY_DIR}/libtextstyle/lib + ${CMAKE_CURRENT_BINARY_DIR}/libtextstyle/lib/textstyle ${LIBICONV_INCLUDE_DIRS}) include(GNUInstallDirs) diff --git a/third-party/cmake/TreesitterParserCMakeLists.txt b/third-party/cmake/TreesitterParserCMakeLists.txt new file mode 100644 index 0000000000..2808a9ee14 --- /dev/null +++ b/third-party/cmake/TreesitterParserCMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12) +# some parsers have c++ scanner, problem? +project(parser C) # CXX + +add_library(parser + MODULE + src/parser.c +) +set_target_properties( + parser + PROPERTIES + POSITION_INDEPENDENT_CODE ON + OUTPUT_NAME ${PARSERLANG} + PREFIX "" +) + +include_directories(src) + +install(TARGETS parser LIBRARY DESTINATION lib/nvim/parser) diff --git a/third-party/patches/gettext-Fix-building-with-MSVC.patch b/third-party/patches/gettext-Fix-building-with-MSVC.patch deleted file mode 100644 index d15901bce3..0000000000 --- a/third-party/patches/gettext-Fix-building-with-MSVC.patch +++ /dev/null @@ -1,50 +0,0 @@ -diff --git a/gettext-tools/config.h.in b/gettext-tools/config.h.in -index 6818a4d..9842a71 100644 ---- a/gettext-tools/config.h.in -+++ b/gettext-tools/config.h.in -@@ -3147,7 +3147,7 @@ - #define PAGE_WIDTH 79 - - /* On Windows, variables that may be in a DLL must be marked specially. */ --#if ((defined _MSC_VER && defined _DLL) || defined WOE32DLL) && !defined IN_RELOCWRAPPER -+#if ((defined _MSC_VER && defined DLL_IMPORT) || defined WOE32DLL) && !defined IN_RELOCWRAPPER - # define DLL_VARIABLE __declspec (dllimport) - #else - # define DLL_VARIABLE -diff --git a/gettext-tools/gnulib-lib/javaversion.c b/gettext-tools/gnulib-lib/javaversion.c -index d760c32..4867fda 100644 ---- a/gettext-tools/gnulib-lib/javaversion.c -+++ b/gettext-tools/gnulib-lib/javaversion.c -@@ -39,7 +39,7 @@ - #define _(str) gettext (str) - - /* Get PKGDATADIR. */ --#include "configmake.h" -+#define PKGDATADIR "" - - - struct locals -diff --git a/gettext-tools/libgettextpo/xalloc.h b/gettext-tools/libgettextpo/xalloc.h -index f4a329e..a38dcf1 100644 ---- a/gettext-tools/libgettextpo/xalloc.h -+++ b/gettext-tools/libgettextpo/xalloc.h -@@ -60,7 +60,7 @@ extern "C" { - in charge of honoring the three previous items. This is the - function to call when one wants the program to die because of a - memory allocation failure. */ --extern void xalloc_die (void) -+extern _Noreturn void xalloc_die (void) - #if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5)) && !__STRICT_ANSI__ - __attribute__ ((__noreturn__)) - #endif -diff --git a/gettext-tools/src/plural-exp.c b/gettext-tools/src/plural-exp.c -index d5b9deb..e2c6bc4 100644 ---- a/gettext-tools/src/plural-exp.c -+++ b/gettext-tools/src/plural-exp.c -@@ -17,5 +17,5 @@ - - /* Include the expression parsing code from libintl, with different function - names. */ --#include "../intl/pluralx.c" -+#include "../intl/plural.c" - #include "../../gettext-runtime/intl/plural-exp.c" diff --git a/third-party/patches/gettext-Fix-compilation-on-a-system-without-alloca.patch b/third-party/patches/gettext-Fix-compilation-on-a-system-without-alloca.patch deleted file mode 100644 index 5c472c470f..0000000000 --- a/third-party/patches/gettext-Fix-compilation-on-a-system-without-alloca.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 1d12aeb7334104f77070361492ff7cc8225503f5 Mon Sep 17 00:00:00 2001 -From: Daiki Ueno <ueno@gnu.org> -Date: Mon, 14 Nov 2016 13:27:58 +0100 -Subject: [PATCH] intl: Fix compilation on a system without alloca - -* gettext-runtime/intl/dcigettext.c (DCIGETTEXT): Fix typo 'tmp_dirname' --> 'resolved_dirname'. Reported by Egor Pugin in: -http://lists.gnu.org/archive/html/bug-gettext/2016-09/msg00008.html ---- - gettext-runtime/intl/dcigettext.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/gettext-runtime/intl/dcigettext.c b/gettext-runtime/intl/dcigettext.c -index 83bd77574..92f6fd685 100644 ---- a/gettext-runtime/intl/dcigettext.c -+++ b/gettext-runtime/intl/dcigettext.c -@@ -634,7 +634,7 @@ DCIGETTEXT (const char *domainname, const char *msgid1, const char *msgid2, - for (;;) - { - resolved_dirname = (char *) alloca (path_max + dirname_len); -- ADD_BLOCK (block_list, tmp_dirname); -+ ADD_BLOCK (block_list, resolved_dirname); - - __set_errno (0); - ret = getcwd (resolved_dirname, path_max); --- -2.16.1.windows.4 - |