aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml26
-rw-r--r--CMakeLists.txt15
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--appveyor.yml3
-rwxr-xr-xci/before_install.sh1
-rw-r--r--ci/build.ps145
-rwxr-xr-xci/install.sh29
-rwxr-xr-xci/run_tests.sh2
-rw-r--r--cmake/FindLibIntl.cmake6
-rw-r--r--config/pathdef.c.in1
-rw-r--r--man/nvim.12
-rw-r--r--runtime/autoload/health/provider.vim185
-rw-r--r--runtime/autoload/man.vim18
-rw-r--r--runtime/autoload/provider/perl.vim69
-rw-r--r--runtime/autoload/remote/host.vim4
-rw-r--r--runtime/doc/autocmd.txt22
-rw-r--r--runtime/doc/eval.txt125
-rw-r--r--runtime/doc/fold.txt3
-rw-r--r--runtime/doc/lsp.txt94
-rw-r--r--runtime/doc/lua.txt37
-rw-r--r--runtime/doc/mlang.txt4
-rw-r--r--runtime/doc/nvim_terminal_emulator.txt17
-rw-r--r--runtime/doc/options.txt29
-rw-r--r--runtime/doc/provider.txt25
-rw-r--r--runtime/doc/sign.txt10
-rw-r--r--runtime/doc/spell.txt16
-rw-r--r--runtime/doc/starting.txt13
-rw-r--r--runtime/doc/usr_21.txt15
-rw-r--r--runtime/doc/usr_41.txt7
-rw-r--r--runtime/doc/vim_diff.txt1
-rw-r--r--runtime/filetype.vim12
-rw-r--r--runtime/lua/vim/lsp.lua56
-rw-r--r--runtime/lua/vim/lsp/buf.lua33
-rw-r--r--runtime/lua/vim/lsp/callbacks.lua47
-rw-r--r--runtime/lua/vim/lsp/log.lua1
-rw-r--r--runtime/lua/vim/lsp/protocol.lua26
-rw-r--r--runtime/lua/vim/lsp/rpc.lua51
-rw-r--r--runtime/lua/vim/lsp/util.lua157
-rw-r--r--runtime/lua/vim/shared.lua65
-rw-r--r--runtime/lua/vim/treesitter.lua77
-rw-r--r--runtime/lua/vim/tshighlighter.lua28
-rw-r--r--runtime/pack/dist/opt/termdebug/plugin/termdebug.vim241
-rw-r--r--runtime/scripts.vim4
-rwxr-xr-xscripts/vim-patch.sh6
-rwxr-xr-xsrc/clint.py10
-rw-r--r--src/nvim/CMakeLists.txt17
-rw-r--r--src/nvim/README.md10
-rw-r--r--src/nvim/api/buffer.c22
-rw-r--r--src/nvim/api/private/helpers.c10
-rw-r--r--src/nvim/api/vim.c48
-rw-r--r--src/nvim/auevents.lua1
-rw-r--r--src/nvim/buffer.c114
-rw-r--r--src/nvim/buffer_defs.h38
-rw-r--r--src/nvim/buffer_updates.c5
-rw-r--r--src/nvim/change.c2
-rw-r--r--src/nvim/channel.c1
-rw-r--r--src/nvim/channel.h2
-rw-r--r--src/nvim/charset.c2
-rw-r--r--src/nvim/diff.c21
-rw-r--r--src/nvim/edit.c104
-rw-r--r--src/nvim/eval.c11527
-rw-r--r--src/nvim/eval.h116
-rw-r--r--src/nvim/eval.lua7
-rw-r--r--src/nvim/eval/encode.c2
-rw-r--r--src/nvim/eval/funcs.c11076
-rw-r--r--src/nvim/eval/funcs.h24
-rw-r--r--src/nvim/eval/typval.h4
-rw-r--r--src/nvim/eval/typval_encode.c.h2
-rw-r--r--src/nvim/event/loop.c36
-rw-r--r--src/nvim/ex_cmds.c37
-rw-r--r--src/nvim/ex_cmds.lua39
-rw-r--r--src/nvim/ex_cmds_defs.h61
-rw-r--r--src/nvim/ex_docmd.c1171
-rw-r--r--src/nvim/ex_getln.c13
-rw-r--r--src/nvim/ex_session.c1047
-rw-r--r--src/nvim/ex_session.h13
-rw-r--r--src/nvim/extmark.c (renamed from src/nvim/mark_extended.c)57
-rw-r--r--src/nvim/extmark.h (renamed from src/nvim/mark_extended.h)12
-rw-r--r--src/nvim/extmark_defs.h (renamed from src/nvim/mark_extended_defs.h)6
-rw-r--r--src/nvim/fileio.c24
-rw-r--r--src/nvim/fold.c9
-rw-r--r--src/nvim/getchar.c52
-rw-r--r--src/nvim/getchar.h4
-rw-r--r--src/nvim/globals.h550
-rw-r--r--src/nvim/grid_defs.h2
-rw-r--r--src/nvim/indent.c2
-rw-r--r--src/nvim/indent_c.c9
-rw-r--r--src/nvim/keymap.h4
-rw-r--r--src/nvim/lua/executor.c159
-rw-r--r--src/nvim/lua/treesitter.c14
-rw-r--r--src/nvim/main.c150
-rw-r--r--src/nvim/map.h2
-rw-r--r--src/nvim/mark.c2
-rw-r--r--src/nvim/mark.h2
-rw-r--r--src/nvim/marktree.c9
-rw-r--r--src/nvim/mbyte.c126
-rw-r--r--src/nvim/memline.c10
-rw-r--r--src/nvim/menu.c2
-rw-r--r--src/nvim/message.c6
-rw-r--r--src/nvim/mouse.c19
-rw-r--r--src/nvim/move.c133
-rw-r--r--src/nvim/msgpack_rpc/channel.c2
-rw-r--r--src/nvim/msgpack_rpc/channel.h2
-rw-r--r--src/nvim/msgpack_rpc/server.c2
-rw-r--r--src/nvim/normal.c262
-rw-r--r--src/nvim/ops.c184
-rw-r--r--src/nvim/option.c208
-rw-r--r--src/nvim/option_defs.h6
-rw-r--r--src/nvim/options.lua13
-rw-r--r--src/nvim/os/env.c21
-rw-r--r--src/nvim/os/shell.c500
-rw-r--r--src/nvim/os_unix.c531
-rw-r--r--src/nvim/path.c24
-rw-r--r--src/nvim/popupmnu.c52
-rw-r--r--src/nvim/quickfix.c6
-rw-r--r--src/nvim/screen.c198
-rw-r--r--src/nvim/search.c35
-rw-r--r--src/nvim/shada.c12
-rw-r--r--src/nvim/sign.c210
-rw-r--r--src/nvim/spell.c51
-rw-r--r--src/nvim/spell_defs.h13
-rw-r--r--src/nvim/spellfile.c78
-rw-r--r--src/nvim/syntax.c2
-rw-r--r--src/nvim/tag.c40
-rw-r--r--src/nvim/testdir/Makefile2
-rw-r--r--src/nvim/testdir/test49.vim4
-rw-r--r--src/nvim/testdir/test_breakindent.vim67
-rw-r--r--src/nvim/testdir/test_compiler.vim9
-rw-r--r--src/nvim/testdir/test_diffmode.vim10
-rw-r--r--src/nvim/testdir/test_escaped_glob.vim2
-rw-r--r--src/nvim/testdir/test_filetype.vim7
-rw-r--r--src/nvim/testdir/test_ins_complete.vim11
-rw-r--r--src/nvim/testdir/test_listchars.vim20
-rw-r--r--src/nvim/testdir/test_listlbr.vim31
-rw-r--r--src/nvim/testdir/test_messages.vim31
-rw-r--r--src/nvim/testdir/test_number.vim11
-rw-r--r--src/nvim/testdir/test_options.vim58
-rw-r--r--src/nvim/testdir/test_plus_arg_edit.vim28
-rw-r--r--src/nvim/testdir/test_quickfix.vim7
-rw-r--r--src/nvim/testdir/test_regexp_utf8.vim141
-rw-r--r--src/nvim/testdir/test_restricted.vim103
-rw-r--r--src/nvim/testdir/test_search.vim6
-rw-r--r--src/nvim/testdir/test_signs.vim413
-rw-r--r--src/nvim/testdir/test_spell.vim22
-rw-r--r--src/nvim/testdir/test_statusline.vim21
-rw-r--r--src/nvim/testdir/test_tagjump.vim22
-rw-r--r--src/nvim/testdir/test_textformat.vim423
-rw-r--r--src/nvim/testdir/test_timers.vim4
-rw-r--r--src/nvim/testdir/test_user_func.vim4
-rw-r--r--src/nvim/testdir/test_utf8.vim43
-rw-r--r--src/nvim/testdir/test_writefile.vim7
-rw-r--r--src/nvim/tui/input.c26
-rw-r--r--src/nvim/tui/input.h2
-rw-r--r--src/nvim/tui/terminfo.c4
-rw-r--r--src/nvim/tui/tui.c7
-rw-r--r--src/nvim/undo.c7
-rw-r--r--src/nvim/undo_defs.h2
-rw-r--r--src/nvim/version.c4
-rw-r--r--src/nvim/viml/parser/expressions.h4
-rw-r--r--src/nvim/window.c31
-rw-r--r--src/tree_sitter/api.h37
-rw-r--r--src/tree_sitter/array.h28
-rw-r--r--src/tree_sitter/atomic.h4
-rw-r--r--src/tree_sitter/bits.h2
-rw-r--r--src/tree_sitter/language.h14
-rw-r--r--src/tree_sitter/lexer.c13
-rw-r--r--src/tree_sitter/lexer.h2
-rw-r--r--src/tree_sitter/parser.c61
-rw-r--r--src/tree_sitter/query.c218
-rw-r--r--src/tree_sitter/stack.c2
-rw-r--r--src/tree_sitter/subtree.c13
-rw-r--r--src/tree_sitter/subtree.h4
-rw-r--r--test/busted/outputHandlers/TAP.lua2
-rw-r--r--test/busted/outputHandlers/nvim.lua2
-rw-r--r--test/functional/api/extmark_spec.lua (renamed from test/functional/api/mark_extended_spec.lua)1
-rw-r--r--test/functional/api/vim_spec.lua24
-rw-r--r--test/functional/autocmd/tabnewentered_spec.lua36
-rw-r--r--test/functional/core/job_spec.lua17
-rw-r--r--test/functional/eval/ctx_functions_spec.lua2
-rw-r--r--test/functional/ex_cmds/echo_spec.lua167
-rw-r--r--test/functional/ex_cmds/mksession_spec.lua45
-rw-r--r--test/functional/fixtures/fake-lsp-server.lua (renamed from test/functional/fixtures/lsp-test-rpc-server.lua)37
-rw-r--r--test/functional/helpers.lua8
-rw-r--r--test/functional/legacy/021_control_wi_spec.lua2
-rw-r--r--test/functional/legacy/097_glob_path_spec.lua2
-rw-r--r--test/functional/legacy/eval_spec.lua4
-rw-r--r--test/functional/legacy/expand_spec.lua39
-rw-r--r--test/functional/legacy/prompt_buffer_spec.lua153
-rw-r--r--test/functional/lua/buffer_updates_spec.lua17
-rw-r--r--test/functional/lua/treesitter_spec.lua126
-rw-r--r--test/functional/lua/vim_spec.lua159
-rw-r--r--test/functional/normal/put_spec.lua4
-rw-r--r--test/functional/options/defaults_spec.lua33
-rw-r--r--test/functional/options/num_options_spec.lua20
-rw-r--r--test/functional/plugin/lsp_spec.lua87
-rw-r--r--test/functional/provider/perl_spec.lua80
-rw-r--r--test/functional/terminal/edit_spec.lua6
-rw-r--r--test/functional/ui/fold_spec.lua74
-rw-r--r--test/functional/ui/highlight_spec.lua42
-rw-r--r--test/functional/ui/inccommand_spec.lua72
-rw-r--r--test/functional/ui/messages_spec.lua87
-rw-r--r--test/functional/ui/multibyte_spec.lua34
-rw-r--r--test/functional/ui/options_spec.lua14
-rw-r--r--test/functional/ui/popupmenu_spec.lua110
-rw-r--r--test/functional/ui/wildmode_spec.lua100
-rw-r--r--test/helpers.lua73
-rw-r--r--test/unit/eval/typval_spec.lua2
-rw-r--r--test/unit/helpers.lua2
-rw-r--r--third-party/CMakeLists.txt34
-rw-r--r--third-party/cmake/BuildGettext.cmake4
-rw-r--r--third-party/cmake/BuildLuarocks.cmake4
-rw-r--r--third-party/cmake/BuildLuv.cmake29
-rw-r--r--third-party/cmake/BuildMsgpack.cmake2
-rw-r--r--third-party/cmake/BuildTreesitterParsers.cmake27
-rw-r--r--third-party/cmake/BuildUtf8proc.cmake2
-rw-r--r--third-party/cmake/GettextCMakeLists.txt278
-rw-r--r--third-party/cmake/TreesitterParserCMakeLists.txt19
-rw-r--r--third-party/patches/gettext-Fix-building-with-MSVC.patch50
-rw-r--r--third-party/patches/gettext-Fix-compilation-on-a-system-without-alloca.patch28
219 files changed, 19938 insertions, 15243 deletions
diff --git a/.travis.yml b/.travis.yml
index 4bb5c00251..b920f70f45 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -71,12 +71,12 @@ addons:
- build-essential
- clang
- cmake
+ - cpanminus
- cscope
- gcc-multilib
- gdb
- gperf
- language-pack-tr
- - libc6-dev-i386
- libtool-bin
- locales
- ninja-build
@@ -85,12 +85,14 @@ addons:
- valgrind
- xclip
homebrew:
- update: false
+ update: true
casks:
- powershell
packages:
- ccache
+ - cpanminus
- ninja
+ - perl
jobs:
include:
@@ -157,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..74e161d989 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")
@@ -455,17 +460,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).
diff --git a/appveyor.yml b/appveyor.yml
index 7e2aef345b..01ca16f930 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -22,9 +22,6 @@ init:
#- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
matrix:
fast_finish: true
-install: []
-before_build:
-- ps: Install-Product node 10
build_script:
- powershell ci\build.ps1
after_build:
diff --git a/ci/before_install.sh b/ci/before_install.sh
index 5810bec71a..0303936bdd 100755
--- a/ci/before_install.sh
+++ b/ci/before_install.sh
@@ -44,7 +44,6 @@ fi
source ~/.nvm/nvm.sh
nvm install 10
-nvm use 10
if [[ -n "$CMAKE_URL" ]]; then
echo "Installing custom CMake: $CMAKE_URL"
diff --git a/ci/build.ps1 b/ci/build.ps1
index 524dabdf3e..36570be7ae 100644
--- a/ci/build.ps1
+++ b/ci/build.ps1
@@ -29,6 +29,29 @@ function exitIfFailed() {
}
}
+# https://github.com/lukesampson/scoop#installation
+$scoop = (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')
+& {
+ Set-StrictMode -Off
+ Invoke-Expression $scoop
+}
+
+scoop install perl
+perl --version
+cpanm.bat --version
+
+if (-not $NoTests) {
+ scoop install nodejs-lts
+ node --version
+ npm.cmd --version
+
+ cpanm.bat -n Neovim::Ext
+ if ($LastExitCode -ne 0) {
+ Get-Content -Path "$env:USERPROFILE\.cpanm\build.log"
+ }
+ perl -W -e 'use Neovim::Ext; print $Neovim::Ext::VERSION'; exitIfFailed
+}
+
if (-Not (Test-Path -PathType container $env:DEPS_BUILD_DIR)) {
write-host "cache dir not found: $($env:DEPS_BUILD_DIR)"
mkdir $env:DEPS_BUILD_DIR
@@ -57,7 +80,7 @@ if ($compiler -eq 'MINGW') {
# in MSYS2, but we cannot build inside the MSYS2 shell.
$cmakeGenerator = 'Ninja'
$cmakeGeneratorArgs = '-v'
- $mingwPackages = @('ninja', 'cmake', 'perl', 'diffutils').ForEach({
+ $mingwPackages = @('ninja', 'cmake', 'diffutils').ForEach({
"mingw-w64-$arch-$_"
})
@@ -99,23 +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') {
@@ -150,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/install.sh b/ci/install.sh
index 3364edfe70..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,26 +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/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 ad7a614ff5..86f9f060ff 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)')
@@ -566,8 +624,8 @@ function! s:check_node() abort
endif
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)
+ if s:shell_error || s:version_cmp(node_v[1:], '6.0.0') < 0
+ 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') ?
@@ -595,14 +653,12 @@ function! s:check_node() abort
\ 'Are you behind a firewall or proxy?'])
return
endif
- if !empty(latest_npm)
- try
- let pkg_data = json_decode(latest_npm)
- catch /E474/
- return 'error: '.latest_npm
- endtry
- let latest_npm = get(get(pkg_data, 'dist-tags', {}), 'latest', 'unable to parse')
- endif
+ try
+ let pkg_data = json_decode(latest_npm)
+ catch /E474/
+ return 'error: '.latest_npm
+ endtry
+ let latest_npm = get(get(pkg_data, 'dist-tags', {}), 'latest', 'unable to parse')
let current_npm_cmd = ['node', host, '--version']
let current_npm = s:system(current_npm_cmd)
@@ -623,10 +679,83 @@ function! s:check_node() abort
endif
endfunction
+function! s:check_perl() abort
+ call health#report_start('Perl provider (optional)')
+
+ if s:disabled_via_loaded_var('perl')
+ return
+ endif
+
+ if !executable('perl') || !executable('cpanm')
+ call health#report_warn(
+ \ '`perl` and `cpanm` must be in $PATH.',
+ \ ['Install Perl and cpanminus and verify that `perl` and `cpanm` commands work.'])
+ return
+ endif
+ 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('Nvim perl host does not support '.perl_v)
+ " Skip further checks, they are nonsense if perl is too old.
+ return
+ endif
+
+ let host = provider#perl#Detect()
+ if empty(host)
+ call health#report_warn('Missing "Neovim::Ext" cpan module.',
+ \ ['Run in shell: cpanm Neovim::Ext'])
+ return
+ endif
+ call health#report_info('Nvim perl host: '. host)
+
+ 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)
+ if s:shell_error
+ call health#report_error('Failed to run: '. string(current_cpan_cmd),
+ \ ['Report this issue with the output of: ', string(current_cpan_cmd)])
+ return
+ endif
+
+ if s:version_cmp(current_cpan, latest_cpan) == -1
+ call health#report_warn(
+ \ printf('Module "Neovim::Ext" is out-of-date. Installed: %s, latest: %s',
+ \ current_cpan, latest_cpan),
+ \ ['Run in shell: cpanm Neovim::Ext'])
+ else
+ call health#report_ok('Latest "Neovim::Ext" cpan module is installed: '. current_cpan)
+ endif
+endfunction
+
function! health#provider#check() abort
call s:check_clipboard()
call s:check_python(2)
call s:check_python(3)
+ call s:check_virtualenv()
call s:check_ruby()
call s:check_node()
+ call s:check_perl()
endfunction
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/provider/perl.vim b/runtime/autoload/provider/perl.vim
new file mode 100644
index 0000000000..36ca2bbf14
--- /dev/null
+++ b/runtime/autoload/provider/perl.vim
@@ -0,0 +1,69 @@
+if exists('s:loaded_perl_provider')
+ finish
+endif
+
+let s:loaded_perl_provider = 1
+
+function! provider#perl#Detect() abort
+ " use g:perl_host_prof if set or check if perl is on the path
+ let prog = exepath(get(g:, 'perl_host_prog', 'perl'))
+ if empty(prog)
+ return ''
+ endif
+
+ " if perl is available, make sure the required module is available
+ call system([prog, '-W', '-MNeovim::Ext', '-e', ''])
+ return v:shell_error ? '' : prog
+endfunction
+
+function! provider#perl#Prog() abort
+ return s:prog
+endfunction
+
+function! provider#perl#Require(host) abort
+ if s:err != ''
+ echoerr s:err
+ return
+ endif
+
+ let prog = provider#perl#Prog()
+ let args = [s:prog, '-e', 'use Neovim::Ext; start_host();']
+
+ " Collect registered perl plugins into args
+ let perl_plugins = remote#host#PluginsForHost(a:host.name)
+ for plugin in perl_plugins
+ call add(args, plugin.path)
+ endfor
+
+ return provider#Poll(args, a:host.orig_name, '$NVIM_PERL_LOG_FILE')
+endfunction
+
+function! provider#perl#Call(method, args) abort
+ if s:err != ''
+ echoerr s:err
+ return
+ endif
+
+ if !exists('s:host')
+ try
+ let s:host = remote#host#Require('perl')
+ catch
+ let s:err = v:exception
+ echohl WarningMsg
+ echomsg v:exception
+ echohl None
+ return
+ endtry
+ endif
+ return call('rpcrequest', insert(insert(a:args, 'perl_'.a:method), s:host))
+endfunction
+
+let s:err = ''
+let s:prog = provider#perl#Detect()
+let g:loaded_perl_provider = empty(s:prog) ? 1 : 2
+
+if g:loaded_perl_provider != 2
+ let s:err = 'Cannot find perl or the required perl module'
+endif
+
+call remote#host#RegisterPlugin('perl-provider', 'perl', [])
diff --git a/runtime/autoload/remote/host.vim b/runtime/autoload/remote/host.vim
index 1cf328e08d..c34ff4bee7 100644
--- a/runtime/autoload/remote/host.vim
+++ b/runtime/autoload/remote/host.vim
@@ -203,3 +203,7 @@ call remote#host#Register('ruby', '*.rb',
" nodejs
call remote#host#Register('node', '*',
\ function('provider#node#Require'))
+
+" perl
+call remote#host#Register('perl', '*',
+ \ function('provider#perl#Require'))
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 cd02449475..29c5c37bcd 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|.
@@ -4541,7 +4563,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
@@ -5502,6 +5524,9 @@ jobstop({id}) *jobstop()*
(if any) will be invoked.
See |job-control|.
+ Returns 1 for valid job id, 0 for invalid id, including jobs have
+ exited or stopped.
+
jobwait({jobs}[, {timeout}]) *jobwait()*
Waits for jobs and their |on_exit| handlers to complete.
@@ -5674,6 +5699,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()|.
@@ -6538,6 +6577,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,
@@ -7547,11 +7630,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.
@@ -7725,7 +7818,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.
@@ -8130,6 +8223,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.
@@ -10274,8 +10379,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."
@@ -10286,7 +10391,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/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/lsp.txt b/runtime/doc/lsp.txt
index 016a8be7e6..2f5427f6fc 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -48,6 +48,7 @@ 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>
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
@@ -77,21 +78,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 +160,25 @@ name: >
vim.lsp.protocol.TextDocumentSyncKind[1] == "Full"
================================================================================
+LSP HIGHLIGHT *lsp-highlight*
+
+ *hl-LspDiagnosticsError*
+LspDiagnosticsError used for "Error" diagnostic virtual text
+ *hl-LspDiagnosticsWarning*
+LspDiagnosticsWarning used for "Warning" diagnostic virtual text
+ *hl-LspDiagnosticsInformation*
+LspDiagnosticInformation used for "Information" diagnostic virtual text
+ *hl-LspDiagnosticsHint*
+LspDiagnosticHint used for "Hint" diagnostic virtual text
+ *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 +295,12 @@ The example will:
<
+==============================================================================
+AUTOCOMMANDS *lsp-autocommands*
+
+ *LspDiagnosticsChanged*
+LspDiagnosticsChanged After receiving publishDiagnostics server response
+
==============================================================================
Lua module: vim.lsp *lsp-core*
@@ -333,7 +344,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 +554,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 +731,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
@@ -751,6 +768,10 @@ 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
@@ -896,6 +917,33 @@ apply_workspace_edit({workspace_edit})
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:
+
+>
+ 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_positions()*
buf_diagnostics_save_positions({bufnr}, {diagnostics})
@@ -909,6 +957,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 options:
+>
+let g:LspDiagnosticsErrorSign = 'E'
+let g:LspDiagnosticsWarningSign = 'W'
+let g:LspDiagnosticsInformationSign = 'I'
+let g:LspDiagnosticsHintSign = 'H'
+<
+
+
character_offset({buf}, {row}, {col}) *vim.lsp.util.character_offset()*
TODO: Documentation
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index af1f4a8c1f..800f24b5c9 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*
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..55c5335a60 100644
--- a/runtime/doc/nvim_terminal_emulator.txt
+++ b/runtime/doc/nvim_terminal_emulator.txt
@@ -307,6 +307,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 12af911d6d..5fb80d75ce 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: >
diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt
index 46ff075cef..0a6cdc60e8 100644
--- a/runtime/doc/provider.txt
+++ b/runtime/doc/provider.txt
@@ -127,6 +127,31 @@ To use the RVM "system" Ruby installation: >
let g:ruby_host_prog = 'rvm system do neovim-ruby-host'
==============================================================================
+Perl integration *provider-perl*
+
+Nvim supports Perl |remote-plugin|s.
+https://github.com/jacquesg/p5-Neovim-Ext
+
+
+PERL QUICKSTART~
+
+To use perl remote-plugins with Nvim, install the "Neovim::Ext" cpan package: >
+ cpanm -n Neovim::Ext
+
+Run |:checkhealth| to see if your system is up-to-date.
+
+
+PERL PROVIDER CONFIGURATION~
+ *g:loaded_perl_provider*
+To disable Perl support: >
+ :let g:loaded_perl_provider = 0
+<
+ *g:perl_host_prog*
+Command to start the Perl executable. Must be set before any
+check for has("perl"). >
+ let g:perl_host_prog = '/path/to/perl'
+<
+==============================================================================
Node.js integration *provider-nodejs*
Nvim supports Node.js |remote-plugin|s.
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..1f1599f560 100644
--- a/runtime/doc/spell.txt
+++ b/runtime/doc/spell.txt
@@ -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/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/filetype.vim b/runtime/filetype.vim
index e66630259e..49e1e9909b 100644
--- a/runtime/filetype.vim
+++ b/runtime/filetype.vim
@@ -487,7 +487,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 +829,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
@@ -1623,6 +1626,13 @@ au BufNewFile,BufRead *.sml setf sml
" Sratus VOS command macro
au BufNewFile,BufRead *.cm setf voscm
+" Swift
+au BufNewFile,BufRead *.swift setf swift
+au BufNewFile,BufRead *.swift.gyb setf swiftgyb
+
+" Swift Intermediate Language
+au BufNewFile,BufRead *.sil setf sil
+
" Sysctl
au BufNewFile,BufRead */etc/sysctl.conf,*/etc/sysctl.d/*.conf setf sysctl
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index cfa208f21c..afff4d9900 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)
@@ -452,7 +448,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 +520,24 @@ 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')
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 +614,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]
@@ -840,8 +839,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 +896,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 +949,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)
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index a6a05fb095..fc9e10cb73 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -23,6 +23,10 @@ local function request(method, params, callback)
return vim.lsp.buf_request(0, method, params, callback)
end
+function M.server_ready()
+ return not not vim.lsp.buf_notify(0, "window/progress", {})
+end
+
function M.hover()
local params = util.make_position_params()
request('textDocument/hover', params)
@@ -68,8 +72,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 +90,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 +138,28 @@ 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
+
+--- 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..644c12f98c 100644
--- a/runtime/lua/vim/lsp/callbacks.lua
+++ b/runtime/lua/vim/lsp/callbacks.lua
@@ -32,12 +32,19 @@ M['textDocument/publishDiagnostics'] = function(_, _, result)
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))
+end
+
+M['textDocument/documentSymbol'] = 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
@@ -63,8 +70,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
@@ -95,7 +103,7 @@ local function location_callback(_, method, result)
end
util.jump_to_location(result[1])
if #result > 1 then
- util.set_qflist(result)
+ util.set_qflist(util.locations_to_items(result))
api.nvim_command("copen")
api.nvim_command("wincmd p")
end
@@ -195,7 +203,33 @@ M['textDocument/peekDefinition'] = function(_, _, result, _)
api.nvim_buf_add_highlight(headbuf, -1, 'Keyword', 0, -1)
end
-local function log_message(_, _, result, client_id)
+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
+
+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 +246,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..41e8119c8c 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -663,19 +663,19 @@ 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 = nil;
experimental = nil;
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index e13b05610b..74d73da31f 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)
@@ -262,7 +249,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..5dd010f2a4 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -88,6 +88,9 @@ function M.apply_text_edits(text_edits, bufnr)
-- Reverse sort the orders so we can apply them without interfering with
-- eachother. Also add i as a sort key to mimic a stable sort.
table.sort(cleaned, edit_sort_key)
+ if not api.nvim_buf_is_loaded(bufnr) then
+ vim.fn.bufload(bufnr)
+ end
local lines = api.nvim_buf_get_lines(bufnr, start_line, finish_line + 1, false)
local fix_eol = api.nvim_buf_get_option(bufnr, 'fixeol')
local set_eol = fix_eol and api.nvim_buf_line_count(bufnr) <= finish_line + 1
@@ -132,7 +135,7 @@ 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
+ if (M.buf_versions[bufnr] or 0) > text_document.version then
print("Buffer ", text_document.uri, " newer than edits.")
return
end
@@ -145,15 +148,36 @@ 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
+
+-- 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 = item.insertText or item.label
+ return vim.startswith(word, prefix)
+ end, items)
+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
@@ -177,7 +201,7 @@ function M.text_document_completion_list_to_complete_items(result)
menu = completion_item.detail or '',
info = info,
icase = 1,
- dup = 0,
+ dup = 1,
empty = 1,
})
end
@@ -245,7 +269,7 @@ 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
@@ -545,7 +569,8 @@ 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
@@ -579,6 +604,11 @@ do
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
@@ -659,7 +689,6 @@ do
end
end
-
function M.buf_diagnostics_underline(bufnr, diagnostics)
for _, diagnostic in ipairs(diagnostics) do
local start = diagnostic.range["start"]
@@ -681,6 +710,25 @@ do
end
end
+ 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";
+ }
+ highlight_range(bufnr, reference_ns, document_highlight_kind[reference["kind"]], start_pos, end_pos)
+ end
+ end
+
function M.buf_diagnostics_virtual_text(bufnr, diagnostics)
local buffer_line_diagnostics = all_buffer_diagnostics[bufnr]
if not buffer_line_diagnostics then
@@ -701,10 +749,38 @@ 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 buffer_line_diagnostics = all_buffer_diagnostics[bufnr]
+ if not buffer_line_diagnostics then return end
+ local count = 0
+ for _, line_diags in pairs(buffer_line_diagnostics) do
+ for _, diag in ipairs(line_diags) do
+ if protocol.DiagnosticSeverity[kind] == diag.severity then count = count + 1 end
+ end
+ end
+ return count
+ end
+ function M.buf_diagnostics_signs(bufnr, diagnostics)
+ vim.fn.sign_define('LspDiagnosticsErrorSign', {text=vim.g['LspDiagnosticsErrorSign'] or 'E', texthl='LspDiagnosticsError', linehl='', numhl=''})
+ vim.fn.sign_define('LspDiagnosticsWarningSign', {text=vim.g['LspDiagnosticsWarningSign'] or 'W', texthl='LspDiagnosticsWarning', linehl='', numhl=''})
+ vim.fn.sign_define('LspDiagnosticsInformationSign', {text=vim.g['LspDiagnosticsInformationSign'] or 'I', texthl='LspDiagnosticsInformation', linehl='', numhl=''})
+ vim.fn.sign_define('LspDiagnosticsHintSign', {text=vim.g['LspDiagnosticsHintSign'] or 'H', texthl='LspDiagnosticsHint', linehl='', numhl=''})
+
+ for _, diagnostic in ipairs(diagnostics) do
+ local diagnostic_severity_map = {
+ [protocol.DiagnosticSeverity.Error] = "LspDiagnosticsErrorSign";
+ [protocol.DiagnosticSeverity.Warning] = "LspDiagnosticsWarningSign";
+ [protocol.DiagnosticSeverity.Information] = "LspDiagnosticsInformationSign";
+ [protocol.DiagnosticSeverity.Hint] = "LspDiagnosticsHintSign";
+ }
+ 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
@@ -721,17 +797,21 @@ function M.locations_to_items(locations)
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)
+ table.insert(grouped[fname], {start = 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 +834,60 @@ 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
+--- 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 = protocol.SymbolKind[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 = protocol.SymbolKind[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 _, child in ipairs(symbol) do
+ for _, v in ipairs(_symbols_to_items(child, _items, _bufnr)) do
+ vim.list_extend(_items, v)
+ end
+ 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 +940,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 +961,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 a71e9878bb..1bf1c63fd7 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -20,6 +20,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
@@ -130,6 +135,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 +204,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 +356,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
@@ -321,7 +384,7 @@ function vim.trim(s)
return s:match('^%s*(.*%S)') or ''
end
---- Escapes magic chars in a Lua pattern string.
+--- Escapes magic chars in a Lua pattern.
---
--@see https://github.com/rxi/lume
--@param s String to escape
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index aa8b8fcdd1..8dacfa11cf 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.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,13 @@ 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
if pred[1] == "eq?" then
local node = match[pred[2]]
local node_text = get_node_text(node, bufnr)
@@ -128,6 +175,16 @@ local function match_preds(match, preds, bufnr)
if node_text ~= str or str == nil then
return false
end
+ 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
+ return regexes[i]:match_line(bufnr, start_row, start_col, end_col)
else
return false
end
@@ -143,8 +200,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 +219,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/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/scripts/vim-patch.sh b/scripts/vim-patch.sh
index ff78cb82f7..f9726e8a87 100755
--- a/scripts/vim-patch.sh
+++ b/scripts/vim-patch.sh
@@ -234,7 +234,8 @@ get_vimpatch() {
msg_ok "Saved patch to '${NVIM_SOURCE_DIR}/${patch_file}'."
}
-# shellcheck disable=SC2015 # "Note that A && B || C is not if-then-else."
+# shellcheck disable=SC2015
+# ^ "Note that A && B || C is not if-then-else."
stage_patch() {
get_vimpatch "$1"
local try_apply="${2:-}"
@@ -305,7 +306,8 @@ git_hub_pr() {
git hub pull new -m "$1"
}
-# shellcheck disable=SC2015 # "Note that A && B || C is not if-then-else."
+# shellcheck disable=SC2015
+# ^ "Note that A && B || C is not if-then-else."
submit_pr() {
require_executable git
local push_first
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..2bfa193a63 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,7 @@ set(CONV_SOURCES
diff.c
edit.c
eval.c
+ eval/funcs.c
ex_cmds.c
ex_docmd.c
fileio.c
@@ -179,10 +181,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
@@ -546,6 +548,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
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 3106011fe2..7578f0fbc5 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -26,7 +26,7 @@
#include "nvim/map_defs.h"
#include "nvim/map.h"
#include "nvim/mark.h"
-#include "nvim/mark_extended.h"
+#include "nvim/extmark.h"
#include "nvim/fileio.h"
#include "nvim/move.h"
#include "nvim/syntax.h"
@@ -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;
}
@@ -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 37e31e0807..a458762cc6 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -24,7 +24,7 @@
#include "nvim/eval/typval.h"
#include "nvim/map_defs.h"
#include "nvim/map.h"
-#include "nvim/mark_extended.h"
+#include "nvim/extmark.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/version.h"
@@ -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/vim.c b/src/nvim/api/vim.c
index baa0387fd8..cf18f6b19a 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -40,7 +40,7 @@
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/state.h"
-#include "nvim/mark_extended.h"
+#include "nvim/extmark.h"
#include "nvim/syntax.h"
#include "nvim/getchar.h"
#include "nvim/os/input.h"
@@ -703,6 +703,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 +2598,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 +2609,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/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 837fcb5cc1..c2573cf942 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -53,7 +53,7 @@
#include "nvim/indent_c.h"
#include "nvim/main.h"
#include "nvim/mark.h"
-#include "nvim/mark_extended.h"
+#include "nvim/extmark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -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);
@@ -1876,6 +1879,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;
}
@@ -3068,18 +3075,15 @@ void col_print(char_u *buf, size_t buflen, int col, int vcol)
}
}
-/*
- * put file name in title bar of window and in icon title
- */
-
static char_u *lasttitle = NULL;
static char_u *lasticon = NULL;
+
+// Put the title name in the title bar and icon of the window.
void maketitle(void)
{
- char_u *t_str = NULL;
- char_u *i_name;
- char_u *i_str = NULL;
+ char_u *title_str = NULL;
+ char_u *icon_str = NULL;
int maxlen = 0;
int len;
int mustset;
@@ -3093,7 +3097,7 @@ void maketitle(void)
need_maketitle = false;
if (!p_title && !p_icon && lasttitle == NULL && lasticon == NULL) {
- return;
+ return; // nothing to do
}
if (p_title) {
@@ -3114,14 +3118,14 @@ void maketitle(void)
build_stl_str_hl(curwin, (char_u *)buf, sizeof(buf),
p_titlestring, use_sandbox,
0, maxlen, NULL, NULL);
- t_str = (char_u *)buf;
+ title_str = (char_u *)buf;
if (called_emsg) {
set_string_option_direct((char_u *)"titlestring", -1, (char_u *)"",
OPT_FREE, SID_ERROR);
}
called_emsg |= save_called_emsg;
} else {
- t_str = p_titlestring;
+ title_str = p_titlestring;
}
} else {
// Format: "fname + (path) (1 of 2) - VIM".
@@ -3205,16 +3209,16 @@ void maketitle(void)
trunc_string((char_u *)buf, (char_u *)buf, maxlen, sizeof(buf));
}
}
- t_str = (char_u *)buf;
+ title_str = (char_u *)buf;
#undef SPACE_FOR_FNAME
#undef SPACE_FOR_DIR
#undef SPACE_FOR_ARGNR
}
}
- mustset = ti_change(t_str, &lasttitle);
+ mustset = value_change(title_str, &lasttitle);
if (p_icon) {
- i_str = (char_u *)buf;
+ icon_str = (char_u *)buf;
if (*p_iconstring != NUL) {
if (stl_syntax & STL_IN_ICON) {
int use_sandbox = false;
@@ -3222,37 +3226,40 @@ void maketitle(void)
use_sandbox = was_set_insecurely((char_u *)"iconstring", 0);
called_emsg = false;
- build_stl_str_hl(curwin, i_str, sizeof(buf),
- p_iconstring, use_sandbox,
- 0, 0, NULL, NULL);
- if (called_emsg)
+ build_stl_str_hl(curwin, icon_str, sizeof(buf),
+ p_iconstring, use_sandbox,
+ 0, 0, NULL, NULL);
+ if (called_emsg) {
set_string_option_direct((char_u *)"iconstring", -1,
- (char_u *)"", OPT_FREE, SID_ERROR);
+ (char_u *)"", OPT_FREE, SID_ERROR);
+ }
called_emsg |= save_called_emsg;
- } else
- i_str = p_iconstring;
+ } else {
+ icon_str = p_iconstring;
+ }
} else {
+ char_u *buf_p;
if (buf_spname(curbuf) != NULL) {
- i_name = buf_spname(curbuf);
+ buf_p = buf_spname(curbuf);
} else { // use file name only in icon
- i_name = path_tail(curbuf->b_ffname);
+ buf_p = path_tail(curbuf->b_ffname);
}
- *i_str = NUL;
+ *icon_str = NUL;
// Truncate name at 100 bytes.
- len = (int)STRLEN(i_name);
+ len = (int)STRLEN(buf_p);
if (len > 100) {
len -= 100;
if (has_mbyte) {
- len += (*mb_tail_off)(i_name, i_name + len) + 1;
+ len += (*mb_tail_off)(buf_p, buf_p + len) + 1;
}
- i_name += len;
+ buf_p += len;
}
- STRCPY(i_str, i_name);
- trans_characters(i_str, IOSIZE);
+ STRCPY(icon_str, buf_p);
+ trans_characters(icon_str, IOSIZE);
}
}
- mustset |= ti_change(i_str, &lasticon);
+ mustset |= value_change(icon_str, &lasticon);
if (mustset) {
resettitle();
@@ -3266,8 +3273,8 @@ void maketitle(void)
/// @param str desired title string
/// @param[in,out] last current title string
//
-/// @return true when "*last" changed.
-static bool ti_change(char_u *str, char_u **last)
+/// @return true if resettitle() is to be called.
+static bool value_change(char_u *str, char_u **last)
FUNC_ATTR_WARN_UNUSED_RESULT
{
if ((str == NULL) != (*last == NULL)
@@ -3275,10 +3282,11 @@ static bool ti_change(char_u *str, char_u **last)
xfree(*last);
if (str == NULL) {
*last = NULL;
+ resettitle();
} else {
*last = vim_strsave(str);
+ return true;
}
- return true;
}
return false;
}
@@ -3389,14 +3397,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);
@@ -3530,6 +3551,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;
+ }
+ }
}
}
@@ -4817,6 +4844,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.
*/
@@ -5211,14 +5244,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)
@@ -5271,6 +5308,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..3993f61a3d 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -166,11 +166,11 @@ typedef struct {
char_u *wo_briopt;
# 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' */
int wo_fen_save;
@@ -791,6 +791,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.
@@ -1272,16 +1278,18 @@ struct window_S {
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' */
-
- /* transform a pointer to a "onebuf" option into a "allbuf" option */
+ // 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'
+ long w_p_siso; // 'sidescrolloff' local value
+ long w_p_so; // 'scrolloff' local value
+
+ // transform a pointer to a "onebuf" option into a "allbuf" option
#define GLOBAL_WO(p) ((char *)p + sizeof(winopt_T))
long w_scbind_pos;
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 7eb6ea7328..a341b8fce1 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -17,7 +17,7 @@
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/mark.h"
-#include "nvim/mark_extended.h"
+#include "nvim/extmark.h"
#include "nvim/memline.h"
#include "nvim/misc1.h"
#include "nvim/move.h"
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/diff.c b/src/nvim/diff.c
index c31adc01fd..f8b7592d0b 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -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 0c183add16..c7867f22c5 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -28,7 +28,7 @@
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/main.h"
-#include "nvim/mark_extended.h"
+#include "nvim/extmark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -574,6 +574,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 +593,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 +821,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 +1157,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
}
@@ -1569,6 +1592,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().
*/
@@ -3594,17 +3663,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 +3693,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
@@ -4017,7 +4083,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;
@@ -6149,7 +6215,7 @@ comp_textwidth (
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)
@@ -8164,10 +8230,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)) {
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 797c61a482..ca0e078e4a 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -5,110 +5,52 @@
* 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.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
@@ -117,61 +59,9 @@
// Character used as separator in autoload function/variable names.
#define AUTOLOAD_CHAR '#'
-/*
- * 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;
-
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");
@@ -180,8 +70,6 @@ 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=");
@@ -220,32 +108,6 @@ static garray_T ga_scripts = {0, 0, sizeof(scriptvar_T *), 4, NULL};
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
@@ -289,13 +151,6 @@ struct funccall_S {
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.
*/
@@ -445,71 +300,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",
@@ -1017,8 +814,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 +860,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;
@@ -1237,7 +1034,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) {
@@ -2275,9 +2072,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;
@@ -2598,7 +2395,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);
@@ -3055,7 +2852,8 @@ void ex_call(exarg_T *eap)
}
}
- if (!failed) {
+ // 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;
@@ -3582,7 +3380,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;
@@ -4886,8 +4684,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;
@@ -5355,6 +5153,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) {
@@ -6226,88 +6028,6 @@ 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
@@ -6714,7 +6434,7 @@ call_func(
emsg_funcname(N_("E933: Function was deleted: %s"), name);
break;
case ERROR_TOOMANY:
- emsg_funcname(e_toomanyarg, name);
+ emsg_funcname(_(e_toomanyarg), name);
break;
case ERROR_TOOFEW:
emsg_funcname(N_("E119: Not enough arguments for function: %s"),
@@ -6761,193 +6481,9 @@ static void emsg_funcname(char *ermsg, const char_u *name)
}
}
-/*
- * 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 +6494,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 +6549,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 +6593,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 +6605,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 +6622,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 +6676,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 +6705,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 +6730,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 +6754,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,355 +6829,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)
{
@@ -7777,1143 +6860,6 @@ func_call_skip_call:
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 +6874,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 +7026,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;
@@ -9802,116 +7188,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();
@@ -9948,109 +7226,6 @@ static dict_T *get_buffer_info(buf_T *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;
- }
-
- 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);
- }
-}
-
/// Get the line number from VimL object
///
/// @note Unlike tv_get_lnum(), this one supports only "$" special string.
@@ -10061,8 +7236,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 +7249,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 +7273,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, &reglen);
- 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 +7293,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();
@@ -11118,147 +7320,9 @@ 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 */
)
@@ -11291,7 +7355,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,16 +7380,10 @@ 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,
@@ -11396,655 +7454,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 +7573,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 +7590,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 +7645,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 +7654,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 +7713,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, true);
- 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 +7758,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(&regmatch, 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 +7783,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 +7791,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 +7806,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 +7922,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 +7974,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, &notanum);
- 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, &notanum);
- 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], &notanum);
- 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], &notanum);
- 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, &notanum);
- 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, &notanum);
- 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(&regmatch, (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 +8001,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 +8012,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,315 +8111,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;
-
- char buf[1024];
- // format the title with the pid to conform with the term:// URI
- snprintf(buf, sizeof(buf), "term://%s//%d:%s", cwd, 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, (char_u *)buf, 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);
@@ -18440,6 +8132,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;
}
@@ -18517,7 +8213,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();
@@ -18546,8 +8247,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) {
@@ -18556,121 +8258,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;
@@ -18722,7 +8311,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
@@ -18751,7 +8364,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, {
@@ -18764,527 +8377,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.
@@ -19292,8 +8384,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;
@@ -19351,7 +8443,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;
@@ -19430,101 +8522,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
@@ -19762,10 +8759,10 @@ 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;
@@ -20165,10 +9162,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;
@@ -20184,14 +9178,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");
@@ -20203,18 +9201,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;
}
@@ -20223,7 +9226,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
@@ -20297,7 +9300,7 @@ 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,
@@ -20466,13 +9469,34 @@ void set_selfdict(typval_T *rettv, dict_T *selfdict)
}
}
+// 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;
+
+ 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.
// Return a pointer to it if found, NULL if not found.
// 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);
@@ -20505,11 +9529,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;
@@ -20869,8 +9893,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);
@@ -21039,7 +10063,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\"");
}
@@ -21077,8 +10101,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) {
@@ -21356,7 +10380,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)) {
@@ -22122,11 +11149,12 @@ ret_free:
static char_u *
trans_function_name(
char_u **pp,
- int skip, // only find the end, don't evaluate
+ 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;
@@ -22313,7 +11341,7 @@ trans_function_name(
}
name = xmalloc(len + lead + 1);
- if (lead > 0){
+ if (!skip && lead > 0) {
name[0] = K_SPECIAL;
name[1] = KS_EXTRA;
name[2] = (int)KE_SNR;
@@ -22509,7 +11537,7 @@ bool translated_function_exists(const char *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)
+bool function_exists(const char *const name, bool no_deref)
{
const char_u *nm = (const char_u *)name;
bool n = false;
@@ -24480,10 +13508,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)
@@ -24507,7 +13535,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
@@ -24525,7 +13553,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;
@@ -24611,6 +13639,7 @@ bool eval_has_provider(const char *feat)
&& !strequal(feat, "python_dynamic")
&& !strequal(feat, "python3_compiled")
&& !strequal(feat, "python3_dynamic")
+ && !strequal(feat, "perl")
&& !strequal(feat, "ruby")
&& !strequal(feat, "node")) {
// Avoid autoload for non-provider has() features.
@@ -24701,3 +13730,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..918823c311 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)
@@ -18,12 +15,63 @@
// All user-defined functions are found in this hashtable.
extern hashtab_T func_hashtab;
+///< 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;
+
// From user function to hashitem and back.
EXTERN ufunc_T dumuf;
#define UF2HIKEY(fp) ((fp)->uf_name)
#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
@@ -142,6 +190,58 @@ extern const list_T *eval_msgpack_type_lists[LAST_MSGPACK_TYPE + 1];
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;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval.h.generated.h"
#endif
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index efeac70816..d3e769a7ef 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,6 +246,9 @@ 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},
@@ -317,6 +323,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..6d67279e64
--- /dev/null
+++ b/src/nvim/eval/funcs.c
@@ -0,0 +1,11076 @@
+// 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/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);
+ }
+ } 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) {
+ // 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--;
+
+ 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, &reglen);
+ 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(&regmatch, 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];
+ 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;
+ }
+}
+
+// "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);
+ }
+ }
+}
+
+/*
+ * "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 *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);
+ }
+ }
+}
+
+/*
+ * "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 {
+ 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_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);
+}
+
+/// "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, &notanum);
+ 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, &notanum);
+ 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], &notanum);
+ 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], &notanum);
+ 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, &notanum);
+ 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, &notanum);
+ 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(&regmatch, (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.h b/src/nvim/eval/typval.h
index 008453b87f..5afdedff75 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
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/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/ex_cmds.c b/src/nvim/ex_cmds.c
index 53caaa6a67..1cd95f2ed9 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -40,7 +40,7 @@
#include "nvim/buffer_updates.h"
#include "nvim/main.h"
#include "nvim/mark.h"
-#include "nvim/mark_extended.h"
+#include "nvim/extmark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/message.h"
@@ -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;
@@ -2678,13 +2679,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 +3010,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 +4484,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_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 6bda62594e..4f8bf63ffe 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>
@@ -40,6 +38,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 +78,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
@@ -1787,10 +1783,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)) {
@@ -4377,7 +4377,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 +4542,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 +4615,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 +4626,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;
@@ -4651,6 +4662,8 @@ static int get_tabpage_arg(exarg_T *eap)
if (relative == 0) {
if (STRCMP(p, "$") == 0) {
tab_number = LAST_TAB_NR;
+ } else if (STRCMP(p, "#") == 0) {
+ tab_number = tabpage_index(lastused_tabpage);
} else if (p == p_save || *p_save == '-' || *p != NUL
|| tab_number > LAST_TAB_NR) {
// No numbers as argument.
@@ -5132,9 +5145,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.
@@ -5153,62 +5165,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++) {
@@ -5222,7 +5268,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);
@@ -5233,12 +5279,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);
}
@@ -5426,9 +5473,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);
@@ -7321,7 +7367,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;
}
@@ -8083,167 +8129,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.
@@ -9116,864 +9001,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".
*/
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 380dc43b50..0c28f2ae70 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -951,8 +951,10 @@ static int command_line_execute(VimState *state, int key)
// if 'wildmode' contains "list" may still need to list
if (s->xpc.xp_numfiles > 1
&& !s->did_wild_list
- && (wim_flags[s->wim_index] & WIM_LIST)) {
- (void)showmatches(&s->xpc, false);
+ && ((wim_flags[s->wim_index] & WIM_LIST)
+ || (p_wmnu && (wim_flags[s->wim_index] & WIM_FULL) != 0))) {
+ (void)showmatches(&s->xpc, p_wmnu
+ && ((wim_flags[s->wim_index] & WIM_LIST) == 0));
redrawcmd();
s->did_wild_list = true;
}
@@ -2709,7 +2711,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) {
@@ -3048,7 +3050,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)) {
@@ -4707,6 +4709,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_session.c b/src/nvim/ex_session.c
new file mode 100644
index 0000000000..b8f2927480
--- /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 file_readable(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/mark_extended.c b/src/nvim/extmark.c
index b60d847676..e77b556024 100644
--- a/src/nvim/mark_extended.c
+++ b/src/nvim/extmark.c
@@ -32,7 +32,7 @@
#include "nvim/api/vim.h"
#include "nvim/vim.h"
#include "nvim/charset.h"
-#include "nvim/mark_extended.h"
+#include "nvim/extmark.h"
#include "nvim/buffer_updates.h"
#include "nvim/memline.h"
#include "nvim/pos.h"
@@ -45,7 +45,7 @@
#include "nvim/highlight.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "mark_extended.c.generated.h"
+# include "extmark.c.generated.h"
#endif
static ExtmarkNs *buf_ns_ref(buf_T *buf, uint64_t ns_id, bool put) {
@@ -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);
@@ -699,21 +699,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 +738,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 +756,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) {
@@ -808,17 +817,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;
@@ -897,9 +906,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/mark_extended.h b/src/nvim/extmark.h
index f809148d9b..b5eb0db3b6 100644
--- a/src/nvim/mark_extended.h
+++ b/src/nvim/extmark.h
@@ -1,8 +1,8 @@
-#ifndef NVIM_MARK_EXTENDED_H
-#define NVIM_MARK_EXTENDED_H
+#ifndef NVIM_EXTMARK_H
+#define NVIM_EXTMARK_H
#include "nvim/buffer_defs.h"
-#include "nvim/mark_extended_defs.h"
+#include "nvim/extmark_defs.h"
#include "nvim/marktree.h"
EXTERN int extmark_splice_pending INIT(= 0);
@@ -83,11 +83,11 @@ typedef struct {
int col_until;
int current;
VirtText *virt_text;
-} DecorationState;
+} DecorationRedrawState;
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "mark_extended.h.generated.h"
+# include "extmark.h.generated.h"
#endif
-#endif // NVIM_MARK_EXTENDED_H
+#endif // NVIM_EXTMARK_H
diff --git a/src/nvim/mark_extended_defs.h b/src/nvim/extmark_defs.h
index 439f7f0b36..c927048981 100644
--- a/src/nvim/mark_extended_defs.h
+++ b/src/nvim/extmark_defs.h
@@ -1,5 +1,5 @@
-#ifndef NVIM_MARK_EXTENDED_DEFS_H
-#define NVIM_MARK_EXTENDED_DEFS_H
+#ifndef NVIM_EXTMARK_DEFS_H
+#define NVIM_EXTMARK_DEFS_H
#include "nvim/pos.h" // for colnr_T
#include "nvim/lib/kvec.h"
@@ -34,4 +34,4 @@ typedef enum {
kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable
} ExtmarkOp;
-#endif // NVIM_MARK_EXTENDED_DEFS_H
+#endif // NVIM_EXTMARK_DEFS_H
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 7a46957151..e5845e0749 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -2032,14 +2032,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 +6589,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 +6606,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 +6626,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 +6686,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 +7199,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 addfab8f08..c14a64fa38 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -19,10 +19,11 @@
#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"
-#include "nvim/mark_extended.h"
+#include "nvim/extmark.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -1464,10 +1465,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)
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 81666bf5d6..0debd39555 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 */
@@ -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..ccf704fe76 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 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 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);
@@ -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 int 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/indent.c b/src/nvim/indent.c
index 2c5fdd8ea6..f8018c039d 100644
--- a/src/nvim/indent.c
+++ b/src/nvim/indent.c
@@ -13,7 +13,7 @@
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/mark.h"
-#include "nvim/mark_extended.h"
+#include "nvim/extmark.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/misc1.h"
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..d3e887badc 100644
--- a/src/nvim/keymap.h
+++ b/src/nvim/keymap.h
@@ -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
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 242d4e18d1..9167ec5e54 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,6 +20,7 @@
#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"
@@ -244,6 +246,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 +301,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 +547,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 +1044,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..4753df7b87 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");
diff --git a/src/nvim/main.c b/src/nvim/main.c
index be279b449a..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);
@@ -339,8 +340,8 @@ int main(int argc, char **argv)
"matchstr(expand(\"<amatch>\"), "
"'\\c\\m" PROTO "\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), "
// capture the working directory
- "{'cwd': get(matchlist(expand(\"<amatch>\"), "
- "'\\c\\m" PROTO "\\(.\\{-}\\)//'), 1, '')})"
+ "{'cwd': expand(get(matchlist(expand(\"<amatch>\"), "
+ "'\\c\\m" PROTO "\\(.\\{-}\\)//'), 1, ''))})"
"|endif");
do_cmdline_cmd("augroup END");
#undef PROTO
@@ -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(&params);
- /*
- * 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(&params);
- /*
- * 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(&params);
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(&params);
+ }
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/map.h b/src/nvim/map.h
index 761938776d..0ad7865bf0 100644
--- a/src/nvim/map.h
+++ b/src/nvim/map.h
@@ -4,7 +4,7 @@
#include <stdbool.h>
#include "nvim/map_defs.h"
-#include "nvim/mark_extended_defs.h"
+#include "nvim/extmark_defs.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h"
#include "nvim/highlight_defs.h"
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index 4a7452493a..fa7c7d61c9 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -20,7 +20,7 @@
#include "nvim/ex_cmds.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
-#include "nvim/mark_extended.h"
+#include "nvim/extmark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
diff --git a/src/nvim/mark.h b/src/nvim/mark.h
index d8370c367a..b3d9b5d95a 100644
--- a/src/nvim/mark.h
+++ b/src/nvim/mark.h
@@ -6,7 +6,7 @@
#include "nvim/buffer_defs.h"
#include "nvim/func_attr.h"
#include "nvim/mark_defs.h"
-#include "nvim/mark_extended_defs.h"
+#include "nvim/extmark_defs.h"
#include "nvim/memory.h"
#include "nvim/pos.h"
#include "nvim/os/time.h"
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 e5ba17a0a7..fa7c39cc65 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -1802,9 +1802,10 @@ char_u *ml_get(linenr_T lnum)
/*
* Return pointer to position "pos".
*/
-char_u *ml_get_pos(pos_T *pos)
+char_u *ml_get_pos(const pos_T *pos)
+ FUNC_ATTR_NONNULL_ALL
{
- return ml_get_buf(curbuf, pos->lnum, FALSE) + pos->col;
+ return ml_get_buf(curbuf, pos->lnum, false) + pos->col;
}
/*
@@ -1862,7 +1863,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;
diff --git a/src/nvim/menu.c b/src/nvim/menu.c
index 18c4f1fff1..a3e0130465 100644
--- a/src/nvim/menu.c
+++ b/src/nvim/menu.c
@@ -868,7 +868,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 {
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/mouse.c b/src/nvim/mouse.c
index dab2e44890..32d8352d9b 100644
--- a/src/nvim/mouse.c
+++ b/src/nvim/mouse.c
@@ -73,6 +73,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;
@@ -109,12 +110,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,6 +132,7 @@ retnomove:
if (wp == NULL) {
return IN_UNKNOWN;
}
+ fdc = win_fdccol_count(wp);
dragwin = NULL;
// winpos and height may change in win_enter()!
if (grid == DEFAULT_GRID_HANDLE && row >= wp->w_height) {
@@ -165,9 +167,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
@@ -305,8 +306,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 = ' ';
}
diff --git a/src/nvim/move.c b/src/nvim/move.c
index efbc548620..e7ed98d4f0 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -142,7 +142,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.
@@ -158,9 +159,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;
@@ -206,15 +208,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 +251,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 +270,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 +291,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 +301,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 {
@@ -324,7 +330,7 @@ void update_topline(void)
validate_cursor();
}
- p_so = save_so;
+ *so_ptr = save_so;
}
/*
@@ -356,25 +362,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;
}
@@ -674,7 +683,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 +723,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 +796,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 +845,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 +861,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 +880,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 +894,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 +1218,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 +1258,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 +1361,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;
@@ -1492,7 +1503,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,15 +1563,14 @@ 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
|| loff.fill >= fill_below_window)
- )
+ ) {
break;
+ }
/* Add one line above */
topline_back(&loff);
@@ -1590,9 +1601,8 @@ 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) || scrolled < min_scroll) {
+ if (extra < (mouse_dragging > 0 ? mouse_dragging - 1 : so)
+ || scrolled < min_scroll) {
extra += boff.height;
if (boff.lnum >= curwin->w_botline
|| (boff.lnum + 1 == curwin->w_botline
@@ -1726,9 +1736,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;
@@ -1742,8 +1751,7 @@ void cursor_correct(void)
}
validate_botline();
if (curwin->w_botline == curbuf->b_ml.ml_line_count + 1
- && mouse_dragging == 0
- ) {
+ && mouse_dragging == 0) {
below_wanted = 0;
int max_off = (curwin->w_height_inner - 1) / 2;
if (above_wanted > max_off) {
@@ -1824,6 +1832,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();
@@ -1839,7 +1848,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 3aff3cef84..6c92b136da 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -760,7 +760,7 @@ static void normal_get_additional_char(NormalState *s)
// because if it's put back with vungetc() it's too late to apply
// mapping.
no_mapping--;
- while (enc_utf8 && lang && (s->c = vpeekc()) > 0
+ while (lang && (s->c = vpeekc()) > 0
&& (s->c >= 0x100 || MB_BYTE2LEN(vpeekc()) > 1)) {
s->c = plain_vgetc();
if (!utf_iscomposing(s->c)) {
@@ -1711,13 +1711,12 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
}
}
- /* Include the trailing byte of a multi-byte char. */
- if (has_mbyte && oap->inclusive) {
- int l;
-
- l = (*mb_ptr2len)(ml_get_pos(&oap->end));
- if (l > 1)
+ // Include the trailing byte of a multi-byte char.
+ if (oap->inclusive) {
+ const int l = utfc_ptr2len(ml_get_pos(&oap->end));
+ if (l > 1) {
oap->end.col += l - 1;
+ }
}
curwin->w_set_curswant = true;
@@ -1846,10 +1845,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
restart_edit = 0;
// Restore linebreak, so that when the user edits it looks as before.
- if (curwin->w_p_lbr != lbr_saved) {
- curwin->w_p_lbr = lbr_saved;
- get_op_vcol(oap, redo_VIsual_mode, false);
- }
+ curwin->w_p_lbr = lbr_saved;
// Reset finish_op now, don't want it set inside edit().
finish_op = false;
@@ -1935,10 +1931,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
restart_edit = 0;
// Restore linebreak, so that when the user edits it looks as before.
- if (curwin->w_p_lbr != lbr_saved) {
- curwin->w_p_lbr = lbr_saved;
- get_op_vcol(oap, redo_VIsual_mode, false);
- }
+ curwin->w_p_lbr = lbr_saved;
op_insert(oap, cap->count1);
@@ -1964,10 +1957,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
CancelRedo();
} else {
// Restore linebreak, so that when the user edits it looks as before.
- if (curwin->w_p_lbr != lbr_saved) {
- curwin->w_p_lbr = lbr_saved;
- get_op_vcol(oap, redo_VIsual_mode, false);
- }
+ curwin->w_p_lbr = lbr_saved;
op_replace(oap, cap->nchar);
}
@@ -2594,12 +2584,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. */
@@ -2906,17 +2897,17 @@ static void find_end_of_word(pos_T *pos)
*/
static int get_mouse_class(char_u *p)
{
- int c;
-
- if (has_mbyte && MB_BYTE2LEN(p[0]) > 1)
+ if (MB_BYTE2LEN(p[0]) > 1) {
return mb_get_class(p);
+ }
- c = *p;
- if (c == ' ' || c == '\t')
+ const int c = *p;
+ if (c == ' ' || c == '\t') {
return 0;
-
- if (vim_iswordc(c))
+ }
+ if (vim_iswordc(c)) {
return 2;
+ }
/*
* There are a few special cases where we want certain combinations of
@@ -3652,7 +3643,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);
@@ -4097,9 +4090,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;
@@ -4143,8 +4136,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)) {
/*
@@ -4466,16 +4459,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 */
@@ -4916,10 +4909,9 @@ static void nv_ident(cmdarg_T *cap)
*p++ = '\\';
/* When current byte is a part of multibyte character, copy all
* bytes of that character. */
- if (has_mbyte) {
- size_t len = (size_t)((*mb_ptr2len)(ptr) - 1);
- for (size_t i = 0; i < len && n > 0; ++i, --n)
- *p++ = *ptr++;
+ const size_t len = (size_t)(utfc_ptr2len(ptr) - 1);
+ for (size_t i = 0; i < len && n > 0; i++, n--) {
+ *p++ = *ptr++;
}
*p++ = *ptr++;
}
@@ -4930,11 +4922,11 @@ static void nv_ident(cmdarg_T *cap)
* Execute the command.
*/
if (cmdchar == '*' || cmdchar == '#') {
- if (!g_cmd && (
- has_mbyte ? vim_iswordp(mb_prevptr(get_cursor_line_ptr(), ptr)) :
- vim_iswordc(ptr[-1])))
+ if (!g_cmd
+ && vim_iswordp(mb_prevptr(get_cursor_line_ptr(), ptr))) {
STRCAT(buf, "\\>");
- /* put pattern in search history */
+ }
+ // put pattern in search history
init_history();
add_to_history(HIST_SEARCH, (char_u *)buf, true, NUL);
(void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0,
@@ -4977,9 +4969,8 @@ get_visual_text (
*pp = ml_get_pos(&VIsual);
*lenp = (size_t)curwin->w_cursor.col - (size_t)VIsual.col + 1;
}
- if (has_mbyte)
- /* Correct the length to include the whole last character. */
- *lenp += (size_t)((*mb_ptr2len)(*pp + (*lenp - 1)) - 1);
+ // Correct the length to include the whole last character.
+ *lenp += (size_t)(utfc_ptr2len(*pp + (*lenp - 1)) - 1);
}
reset_VIsual_and_resel();
return true;
@@ -5197,11 +5188,7 @@ static void nv_left(cmdarg_T *cap)
char_u *cp = get_cursor_pos_ptr();
if (*cp != NUL) {
- if (has_mbyte) {
- curwin->w_cursor.col += (*mb_ptr2len)(cp);
- } else {
- curwin->w_cursor.col++;
- }
+ curwin->w_cursor.col += utfc_ptr2len(cp);
}
cap->retval |= CA_NO_ADJ_OP_END;
}
@@ -5255,6 +5242,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) {
@@ -5847,6 +5841,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;
}
@@ -5859,10 +5857,14 @@ static void nv_replace(cmdarg_T *cap)
{
char_u *ptr;
int had_ctrl_v;
- long n;
- 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) {
@@ -5913,7 +5915,7 @@ static void nv_replace(cmdarg_T *cap)
/* Abort if not enough characters to replace. */
ptr = get_cursor_pos_ptr();
if (STRLEN(ptr) < (unsigned)cap->count1
- || (has_mbyte && mb_charlen(ptr) < cap->count1)
+ || (mb_charlen(ptr) < cap->count1)
) {
clearopbeep(cap->oap);
return;
@@ -5955,71 +5957,44 @@ static void nv_replace(cmdarg_T *cap)
NUL, 'r', NUL, had_ctrl_v, cap->nchar);
curbuf->b_op_start = curwin->w_cursor;
- if (has_mbyte) {
- int old_State = State;
-
- if (cap->ncharC1 != 0)
- AppendCharToRedobuff(cap->ncharC1);
- if (cap->ncharC2 != 0)
- AppendCharToRedobuff(cap->ncharC2);
-
- /* This is slow, but it handles replacing a single-byte with a
- * multi-byte and the other way around. Also handles adding
- * composing characters for utf-8. */
- for (n = cap->count1; n > 0; --n) {
- State = REPLACE;
- if (cap->nchar == Ctrl_E || cap->nchar == Ctrl_Y) {
- int c = ins_copychar(curwin->w_cursor.lnum
- + (cap->nchar == Ctrl_Y ? -1 : 1));
- if (c != NUL)
- ins_char(c);
- else
- /* will be decremented further down */
- ++curwin->w_cursor.col;
- } else
- ins_char(cap->nchar);
- State = old_State;
- if (cap->ncharC1 != 0)
- ins_char(cap->ncharC1);
- if (cap->ncharC2 != 0)
- ins_char(cap->ncharC2);
- }
- } else {
- /*
- * Replace the characters within one line.
- */
- for (n = cap->count1; n > 0; --n) {
- /*
- * Get ptr again, because u_save and/or showmatch() will have
- * released the line. At the same time we let know that the
- * line will be changed.
- */
- ptr = ml_get_buf(curbuf, curwin->w_cursor.lnum, true);
- if (cap->nchar == Ctrl_E || cap->nchar == Ctrl_Y) {
- int c = ins_copychar(curwin->w_cursor.lnum
- + (cap->nchar == Ctrl_Y ? -1 : 1));
- if (c != NUL) {
- assert(c >= 0 && c <= UCHAR_MAX);
- ptr[curwin->w_cursor.col] = (char_u)c;
- }
+ const int old_State = State;
+
+ if (cap->ncharC1 != 0) {
+ AppendCharToRedobuff(cap->ncharC1);
+ }
+ if (cap->ncharC2 != 0) {
+ AppendCharToRedobuff(cap->ncharC2);
+ }
+
+ // This is slow, but it handles replacing a single-byte with a
+ // multi-byte and the other way around. Also handles adding
+ // composing characters for utf-8.
+ for (long n = cap->count1; n > 0; n--) {
+ State = REPLACE;
+ if (cap->nchar == Ctrl_E || cap->nchar == Ctrl_Y) {
+ int c = ins_copychar(curwin->w_cursor.lnum
+ + (cap->nchar == Ctrl_Y ? -1 : 1));
+ if (c != NUL) {
+ ins_char(c);
} else {
- assert(cap->nchar >= 0 && cap->nchar <= UCHAR_MAX);
- ptr[curwin->w_cursor.col] = (char_u)cap->nchar;
+ // will be decremented further down
+ curwin->w_cursor.col++;
}
- if (p_sm && msg_silent == 0)
- showmatch(cap->nchar);
- ++curwin->w_cursor.col;
+ } else {
+ ins_char(cap->nchar);
+ }
+ State = old_State;
+ if (cap->ncharC1 != 0) {
+ ins_char(cap->ncharC1);
+ }
+ if (cap->ncharC2 != 0) {
+ ins_char(cap->ncharC2);
}
-
- /* mark the buffer as changed and prepare for displaying */
- changed_bytes(curwin->w_cursor.lnum,
- (colnr_T)(curwin->w_cursor.col - cap->count1));
}
--curwin->w_cursor.col; /* cursor on the last replaced char */
/* if the character on the left of the current cursor is a multi-byte
* character, move two characters left */
- if (has_mbyte)
- mb_adjust_cursor();
+ mb_adjust_cursor();
curbuf->b_op_end = curwin->w_cursor;
curwin->w_set_curswant = true;
set_last_insert(cap->nchar);
@@ -6226,7 +6201,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';
@@ -7164,10 +7143,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);
+ }
}
/*
@@ -7180,6 +7164,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)) {
@@ -7365,10 +7355,9 @@ static void adjust_cursor(oparg_T *oap)
&& (!VIsual_active || *p_sel == 'o')
&& !virtual_active() && (ve_flags & VE_ONEMORE) == 0
) {
- --curwin->w_cursor.col;
- /* prevent cursor from moving on the trail byte */
- if (has_mbyte)
- mb_adjust_cursor();
+ curwin->w_cursor.col--;
+ // prevent cursor from moving on the trail byte
+ mb_adjust_cursor();
oap->inclusive = true;
}
}
@@ -7395,10 +7384,7 @@ static void adjust_for_sel(cmdarg_T *cap)
{
if (VIsual_active && cap->oap->inclusive && *p_sel == 'e'
&& gchar_cursor() != NUL && lt(VIsual, curwin->w_cursor)) {
- if (has_mbyte)
- inc_cursor();
- else
- ++curwin->w_cursor.col;
+ inc_cursor();
cap->oap->inclusive = false;
}
}
@@ -7844,8 +7830,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')
@@ -7857,8 +7846,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
@@ -7964,10 +7954,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.
@@ -7988,9 +7982,7 @@ static void get_op_vcol(
oap->motion_type = kMTBlockWise;
// prevent from moving onto a trail byte
- if (has_mbyte) {
- mark_mb_adjustpos(curwin->w_buffer, &oap->end);
- }
+ mark_mb_adjustpos(curwin->w_buffer, &oap->end);
getvvcol(curwin, &(oap->start), &oap->start_vcol, NULL, &oap->end_vcol);
if (!redo_VIsual_busy) {
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index da2b81fd0a..5457400b76 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -31,7 +31,7 @@
#include "nvim/indent.h"
#include "nvim/log.h"
#include "nvim/mark.h"
-#include "nvim/mark_extended.h"
+#include "nvim/extmark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -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;
@@ -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
@@ -357,15 +368,11 @@ static void shift_block(oparg_T *oap, int amount)
colnr_T ws_vcol = bd.start_vcol - bd.pre_whitesp;
char_u * old_textstart = bd.textstart;
if (bd.startspaces) {
- if (has_mbyte) {
- if ((*mb_ptr2len)(bd.textstart) == 1) {
- bd.textstart++;
- } else {
- ws_vcol = 0;
- bd.startspaces = 0;
- }
- } else {
+ if (utfc_ptr2len(bd.textstart) == 1) {
bd.textstart++;
+ } else {
+ ws_vcol = 0;
+ bd.startspaces = 0;
}
}
for (; ascii_iswhite(*bd.textstart); ) {
@@ -835,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)
@@ -1145,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
@@ -1215,9 +1232,7 @@ static void stuffescaped(const char *arg, int literally)
/* stuff a single special character */
if (*arg != NUL) {
- const int c = (has_mbyte
- ? mb_cptr2char_adv((const char_u **)&arg)
- : (uint8_t)(*arg++));
+ const int c = mb_cptr2char_adv((const char_u **)&arg);
if (literally && ((c < ' ' && c != TAB) || c == DEL)) {
stuffcharReadbuff(Ctrl_V);
}
@@ -1321,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;
@@ -1389,8 +1406,7 @@ int op_delete(oparg_T *oap)
return FAIL;
}
- if (has_mbyte)
- mb_adjust_opend(oap);
+ mb_adjust_opend(oap);
/*
* Imitate the strange Vi behaviour: If the delete spans more than one
@@ -1736,8 +1752,7 @@ int op_replace(oparg_T *oap, int c)
c = NL;
}
- if (has_mbyte)
- mb_adjust_opend(oap);
+ mb_adjust_opend(oap);
if (u_save((linenr_T)(oap->start.lnum - 1),
(linenr_T)(oap->end.lnum + 1)) == FAIL)
@@ -2012,17 +2027,16 @@ void op_tilde(oparg_T *oap)
* Returns TRUE if some character was changed.
*/
static int swapchars(int op_type, pos_T *pos, int length)
+ FUNC_ATTR_NONNULL_ALL
{
- int todo;
int did_change = 0;
- for (todo = length; todo > 0; --todo) {
- if (has_mbyte) {
- int len = (*mb_ptr2len)(ml_get_pos(pos));
+ for (int todo = length; todo > 0; todo--) {
+ const int len = utfc_ptr2len(ml_get_pos(pos));
- /* we're counting bytes, not characters */
- if (len > 0)
- todo -= len - 1;
+ // we're counting bytes, not characters
+ if (len > 0) {
+ todo -= len - 1;
}
did_change |= swapchar(op_type, pos);
if (inc(pos) == -1) /* at end of file */
@@ -2532,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;
@@ -2563,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;
@@ -3052,7 +3066,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col);
// move to start of next multi-byte character
- curwin->w_cursor.col += (*mb_ptr2len)(get_cursor_pos_ptr());
+ curwin->w_cursor.col += utfc_ptr2len(get_cursor_pos_ptr());
col++;
} else {
getvcol(curwin, &curwin->w_cursor, &col, NULL, &endcol2);
@@ -3615,7 +3629,7 @@ dis_msg(
while (*p != NUL
&& !(*p == ESC && skip_esc && *(p + 1) == NUL)
&& (n -= ptr2cells(p)) >= 0) {
- if (has_mbyte && (l = (*mb_ptr2len)(p)) > 1) {
+ if ((l = utfc_ptr2len(p)) > 1) {
msg_outtrans_len(p, l);
p += l;
} else
@@ -4414,14 +4428,17 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum,
char_u *line;
char_u *prev_pstart;
char_u *prev_pend;
+ const int lbr_saved = curwin->w_p_lbr;
+ // Avoid a problem with unwanted linebreaks in block mode.
+ curwin->w_p_lbr = false;
bdp->startspaces = 0;
bdp->endspaces = 0;
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;
@@ -4447,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. */
@@ -4458,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 {
@@ -4487,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) {
@@ -4514,6 +4531,7 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum,
}
bdp->textcol = (colnr_T) (pstart - line);
bdp->textstart = pstart;
+ curwin->w_p_lbr = lbr_saved;
}
/// Handle the add/subtract operator.
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 2871a4b7de..43a4a9fdf9 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,6 +614,7 @@ static void set_runtimepath_default(void)
xfree(data_home);
xfree(config_home);
xfree(vimruntime);
+ xfree(libdir);
}
#undef NVIM_SIZE
@@ -871,11 +904,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
@@ -2021,13 +2062,10 @@ static char_u *check_cedit(void)
// maketitle() to create and display it.
// When switching the title or icon off, call ui_set_{icon,title}(NULL) to get
// the old value back.
-static void did_set_title(
- int icon // Did set icon instead of title
-)
+static void did_set_title(void)
{
if (starting != NO_SCREEN) {
maketitle();
- resettitle();
}
}
@@ -2512,12 +2550,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
+{
+ 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 (char_u *s = val; *s != NUL; s++) {
- if (!ASCII_ISALNUM(*s) && vim_strchr((char_u *)".-_", *s) == NULL) {
+ for (const char_u *s = val; *s != NUL; s++) {
+ if (!vim_isfilec(*s) && *s != ',') {
return false;
}
}
@@ -2986,7 +3053,7 @@ ambw_end:
} else {
stl_syntax &= ~flagval;
}
- did_set_title(varp == &p_iconstring);
+ did_set_title();
} else if (varp == &p_sel) { // 'selection'
if (*p_sel == NUL
@@ -3035,7 +3102,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);
@@ -3136,6 +3210,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) {
@@ -3808,7 +3887,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;
}
@@ -4025,9 +4105,9 @@ static char *set_bool_option(const int opt_idx, char_u *const varp,
(void)buf_init_chartab(curbuf, false); // ignore errors
} else if ((int *)varp == &p_title) {
// when 'title' changed, may need to change the title; same for 'icon'
- did_set_title(false);
+ did_set_title();
} else if ((int *)varp == &p_icon) {
- did_set_title(true);
+ did_set_title();
} else if ((int *)varp == &curbuf->b_changed) {
if (!value) {
save_file_ff(curbuf); // Buffer is unchanged
@@ -4277,7 +4357,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) {
@@ -4299,12 +4379,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;
@@ -5260,20 +5334,20 @@ showoneopt(
* 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: 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").
+ * 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.
@@ -5568,6 +5642,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;
@@ -5640,6 +5720,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);
@@ -5684,6 +5766,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
@@ -5872,8 +5958,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);
@@ -5909,6 +5996,8 @@ void check_win_options(win_T *win)
*/
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);
@@ -5931,6 +6020,8 @@ static void check_winopt(winopt_T *wop)
*/
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);
@@ -5963,10 +6054,10 @@ 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.
+ * 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)
{
@@ -6009,10 +6100,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
@@ -7028,10 +7117,13 @@ static int check_opt_wim(void)
*/
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;
}
@@ -7227,12 +7319,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) {
@@ -7412,3 +7505,18 @@ 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)
+{
+ 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..192e57a642 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -577,8 +577,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 +835,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/shell.c b/src/nvim/os/shell.c
index f4377b1457..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.:
///
@@ -423,7 +921,7 @@ static bool out_data_decide_throttle(size_t size)
pulse_msg[1] = (tick > 1) ? '.' : ' ';
pulse_msg[2] = (tick > 2) ? '.' : ' ';
if (visit == 1) {
- msg_puts("[...]\n");
+ msg_puts("...\n");
}
msg_putchar('\r'); // put cursor at start of line
msg_puts(pulse_msg);
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..da34d85c00 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
@@ -110,6 +111,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 +130,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 +273,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 +298,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 +318,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 +326,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 +344,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 +358,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 +397,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 +458,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 +505,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 +540,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 +575,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 +587,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 +596,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);
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 71c6f06ac0..c444326533 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;
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 047af8db75..2e2c6ca737 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -87,7 +87,7 @@
#include "nvim/highlight.h"
#include "nvim/main.h"
#include "nvim/mark.h"
-#include "nvim/mark_extended.h"
+#include "nvim/extmark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -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;
@@ -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_
@@ -1724,7 +1744,7 @@ static int advance_color_col(int vcol, int **color_cols)
// 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;
@@ -2130,11 +2150,11 @@ fill_foldcolumn(
if (closed) {
if (symbol != 0) {
- // rollback length
+ // rollback previous write
char_counter -= len;
+ memset(&p[char_counter], ' ', len);
}
- symbol = wp->w_p_fcs_chars.foldclosed;
- len = utf_char2bytes(symbol, &p[char_counter]);
+ len = utf_char2bytes(wp->w_p_fcs_chars.foldclosed, &p[char_counter]);
char_counter += len;
}
@@ -2195,9 +2215,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
+ bool noinvcur = false; // don't invert the cursor
pos_T *top, *bot;
int lnum_in_visual_area = false;
pos_T pos;
@@ -2243,7 +2264,7 @@ win_line (
int change_start = MAXCOL; // first col of changed area
int change_end = -1; // last col of changed area
colnr_T trailcol = MAXCOL; // start of trailing spaces
- int need_showbreak = false; // overlong line, skip first x chars
+ bool need_showbreak = false; // overlong line, skip first x chars
int line_attr = 0; // attribute for the whole line
int line_attr_lowprio = 0; // low-priority attribute for the line
matchitem_T *cur; // points to the match list
@@ -2300,6 +2321,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 +2345,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 +2417,26 @@ 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
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 +2566,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) {
@@ -2654,11 +2664,12 @@ win_line (
else if (fromcol >= 0 && fromcol < vcol)
fromcol = vcol;
- /* When w_skipcol is non-zero, first line needs 'showbreak' */
- if (wp->w_p_wrap)
- need_showbreak = TRUE;
- /* When spell checking a word we need to figure out the start of the
- * word and if it's badly spelled or not. */
+ // When w_skipcol is non-zero, first line needs 'showbreak'
+ if (wp->w_p_wrap) {
+ need_showbreak = true;
+ }
+ // When spell checking a word we need to figure out the start of the
+ // word and if it's badly spelled or not.
if (has_spell) {
size_t len;
colnr_T linecol = (colnr_T)(ptr - line);
@@ -2975,11 +2986,17 @@ win_line (
}
p_extra = NULL;
c_extra = ' ';
- n_extra = get_breakindent_win(wp, ml_get_buf(wp->w_buffer, lnum, FALSE));
- /* Correct end of highlighted area for 'breakindent',
- required wen 'linebreak' is also set. */
- if (tocol == vcol)
+ 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) {
+ need_showbreak = false;
+ }
+ // Correct end of highlighted area for 'breakindent',
+ // required wen 'linebreak' is also set.
+ if (tocol == vcol) {
tocol += n_extra;
+ }
}
}
@@ -3008,7 +3025,9 @@ win_line (
c_final = NUL;
n_extra = (int)STRLEN(p_sbr);
char_attr = win_hl_attr(wp, HLF_AT);
- need_showbreak = false;
+ if (wp->w_skipcol == 0 || !wp->w_p_wrap) {
+ need_showbreak = false;
+ }
vcol_sbr = vcol + MB_CHARLEN(p_sbr);
/* Correct end of highlighted area for 'showbreak',
* required when 'linebreak' is also set. */
@@ -3285,9 +3304,7 @@ win_line (
} else {
int c0;
- if (p_extra_free != NULL) {
- XFREE_CLEAR(p_extra_free);
- }
+ XFREE_CLEAR(p_extra_free);
// Get a character from the line itself.
c0 = c = *ptr;
@@ -3520,8 +3537,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);
@@ -3531,15 +3548,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);
}
@@ -3639,8 +3647,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;
@@ -3652,10 +3661,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;
@@ -4024,8 +4039,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;
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 3ee9777805..7d00785c74 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -2227,6 +2227,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 +2264,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 +2297,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..6cb8d01f51 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;
@@ -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;
@@ -5855,7 +5857,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 +5934,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..ddb9188371 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)) {
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index a412ed0276..57bb43c846 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -3158,9 +3158,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);
@@ -3378,11 +3380,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 +3401,30 @@ int set_tagstack(win_T *wp, dict_T *d, int action)
return FAIL;
}
l = di->di_tv.vval.v_list;
+ }
+
+ 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 (action == 'r') {
+ 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/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/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_breakindent.vim b/src/nvim/testdir/test_breakindent.vim
index 4b34420cab..6d88f1dc5a 100644
--- a/src/nvim/testdir/test_breakindent.vim
+++ b/src/nvim/testdir/test_breakindent.vim
@@ -296,3 +296,70 @@ function Test_breakindent16()
call s:compare_lines(expect, lines)
call s:close_windows()
endfunction
+
+func Test_breakindent17_vartabs()
+ if !has("vartabs")
+ return
+ endif
+ let s:input = ""
+ call s:test_windows('setl breakindent list listchars=tab:<-> showbreak=+++')
+ call setline(1, "\t" . repeat('a', 63))
+ vert resize 30
+ norm! 1gg$
+ redraw!
+ let lines = s:screen_lines(1, 30)
+ let expect = [
+ \ "<-->aaaaaaaaaaaaaaaaaaaaaaaaaa",
+ \ " +++aaaaaaaaaaaaaaaaaaaaaaa",
+ \ " +++aaaaaaaaaaaaaa ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set breakindent& list& listchars& showbreak&')
+endfunc
+
+func Test_breakindent18_vartabs()
+ if !has("vartabs")
+ return
+ endif
+ let s:input = ""
+ call s:test_windows('setl breakindent list listchars=tab:<->')
+ call setline(1, "\t" . repeat('a', 63))
+ vert resize 30
+ norm! 1gg$
+ redraw!
+ let lines = s:screen_lines(1, 30)
+ let expect = [
+ \ "<-->aaaaaaaaaaaaaaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaaaaaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaa ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set breakindent& list& listchars&')
+endfunc
+
+func Test_breakindent19_sbr_nextpage()
+ let s:input = ""
+ call s:test_windows('setl breakindent briopt=shift:2,sbr,min:18 sbr=>')
+ call setline(1, repeat('a', 200))
+ norm! 1gg
+ redraw!
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ "aaaaaaaaaaaaaaaaaaaa",
+ \ "> aaaaaaaaaaaaaaaaaa",
+ \ "> aaaaaaaaaaaaaaaaaa",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " Scroll down one screen line
+ setl scrolloff=5
+ norm! 5gj
+ redraw!
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ "> aaaaaaaaaaaaaaaaaa",
+ \ "> aaaaaaaaaaaaaaaaaa",
+ \ "> aaaaaaaaaaaaaaaaaa",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set breakindent& briopt& sbr&')
+endfunc
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_diffmode.vim b/src/nvim/testdir/test_diffmode.vim
index 21e0271bda..fed642e34b 100644
--- a/src/nvim/testdir/test_diffmode.vim
+++ b/src/nvim/testdir/test_diffmode.vim
@@ -67,7 +67,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 +76,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 +142,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 +150,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 +158,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_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_filetype.vim b/src/nvim/testdir/test_filetype.vim
index cc0037b4cf..9605348389 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -139,7 +139,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 +234,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'],
@@ -431,6 +432,9 @@ let s:filename_checks = {
\ 'sudoers': ['any/etc/sudoers', 'sudoers.tmp'],
\ 'svg': ['file.svg'],
\ 'svn': ['svn-commitfile.tmp'],
+ \ 'swift': ['file.swift'],
+ \ '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'],
\ 'systemverilog': ['file.sv', 'file.svh'],
@@ -596,6 +600,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_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim
index e6d427db05..f4e6b698c6 100644
--- a/src/nvim/testdir/test_ins_complete.vim
+++ b/src/nvim/testdir/test_ins_complete.vim
@@ -130,7 +130,7 @@ 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' ] )
@@ -138,10 +138,12 @@ function! s:CompleteDone_CheckCompletedItemDict()
call assert_equal( 'W', v:completed_item[ 'kind' ] )
call assert_equal( 'test', 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,7 +163,8 @@ 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>"
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_listlbr.vim b/src/nvim/testdir/test_listlbr.vim
index d28dbc444c..cdc5e4cc7c 100644
--- a/src/nvim/testdir/test_listlbr.vim
+++ b/src/nvim/testdir/test_listlbr.vim
@@ -103,6 +103,37 @@ func Test_linebreak_with_conceal()
call s:close_windows()
endfunc
+func Test_linebreak_with_visual_operations()
+ call s:test_windows()
+ let line = '1234567890 2234567890 3234567890'
+ call setline(1, line)
+
+ " yank
+ exec "norm! ^w\<C-V>ey"
+ call assert_equal('2234567890', @@)
+ exec "norm! w\<C-V>ey"
+ call assert_equal('3234567890', @@)
+
+ " increment / decrement
+ exec "norm! ^w\<C-V>\<C-A>w\<C-V>\<C-X>"
+ call assert_equal('1234567890 3234567890 2234567890', getline(1))
+
+ " replace
+ exec "norm! ^w\<C-V>3lraw\<C-V>3lrb"
+ call assert_equal('1234567890 aaaa567890 bbbb567890', getline(1))
+
+ " tilde
+ exec "norm! ^w\<C-V>2l~w\<C-V>2l~"
+ call assert_equal('1234567890 AAAa567890 BBBb567890', getline(1))
+
+ " delete and insert
+ exec "norm! ^w\<C-V>3lc2345\<Esc>w\<C-V>3lc3456\<Esc>"
+ call assert_equal('1234567890 2345567890 3456567890', getline(1))
+ call assert_equal('BBBb', @@)
+
+ call s:close_windows()
+endfunc
+
func Test_virtual_block()
call s:test_windows('setl sbr=+')
call setline(1, [
diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim
index 265dee66ce..aad21c002f 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
@@ -65,6 +65,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_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_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index d7b387c2c9..015f771435 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')
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 &regexpengine = 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_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_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_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 Y
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set tw=1 fo=tm
+ let t =<< trim END
+ X
+ Xa
+ X a
+ XY
+ X Y
+ 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 Y
+ 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 Y
+ 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
+ XaY
+ XY
+ XYZ
+ X Y
+ 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
+ XaY
+ XY
+ XYZ
+ X Y
+ 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 Y
+ X Y
+ X Z
+ XX
+ XXa
+ XXY
+
+ X
+ Xa
+ Xa
+ XY
+ XY
+ XY
+ XZ
+ X Y
+ X Y
+ 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('a1a2a3a', 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..40376a877e 100644
--- a/src/nvim/testdir/test_timers.vim
+++ b/src/nvim/testdir/test_timers.vim
@@ -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/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..7dfe7ab953 100644
--- a/src/nvim/tui/terminfo.c
+++ b/src/nvim/tui/terminfo.c
@@ -187,7 +187,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 +214,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..99abdd033b 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -1099,6 +1099,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) {
@@ -1291,6 +1292,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,
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index fda647106d..df92b2c036 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -93,7 +93,7 @@
#include "nvim/buffer_updates.h"
#include "nvim/pos.h" // MAXLNUM
#include "nvim/mark.h"
-#include "nvim/mark_extended.h"
+#include "nvim/extmark.h"
#include "nvim/memline.h"
#include "nvim/message.h"
#include "nvim/misc1.h"
@@ -2971,7 +2971,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/undo_defs.h b/src/nvim/undo_defs.h
index 0fa3b415ec..cc2c39a711 100644
--- a/src/nvim/undo_defs.h
+++ b/src/nvim/undo_defs.h
@@ -4,7 +4,7 @@
#include <time.h> // for time_t
#include "nvim/pos.h"
-#include "nvim/mark_extended_defs.h"
+#include "nvim/extmark_defs.h"
#include "nvim/mark_defs.h"
typedef struct u_header u_header_T;
diff --git a/src/nvim/version.c b/src/nvim/version.c
index c67fd9175f..6412d4caae 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -129,7 +129,7 @@ static const int included_patches[] = {
1792,
1791,
1790,
- // 1789,
+ 1789,
1788,
1787,
1786,
@@ -258,7 +258,7 @@ static const int included_patches[] = {
1663,
1662,
1661,
- // 1660,
+ 1660,
1659,
1658,
1657,
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..fb3f1e0c9f 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) {
@@ -4669,6 +4689,10 @@ static win_T *win_alloc(win_T *after, int hidden)
new_wp->w_floating = 0;
new_wp->w_float_config = FLOAT_CONFIG_INIT;
+ // 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;
new_wp->w_prev_fraction_row = -1;
@@ -5779,9 +5803,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;
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.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/parser.c b/src/tree_sitter/parser.c
index f381afccab..0fa0c4195b 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;
@@ -1014,7 +1013,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 +1068,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,
@@ -1661,7 +1622,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 +1701,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 +1717,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 +1818,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..053882cf68 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
@@ -37,9 +39,11 @@ typedef struct {
typedef struct {
TSSymbol symbol;
TSFieldId field;
- uint16_t capture_id;
- uint16_t depth: 15;
+ uint16_t capture_ids[MAX_STEP_CAPTURE_COUNT];
+ uint16_t depth: 13;
bool contains_captures: 1;
+ bool is_immediate: 1;
+ bool is_last: 1;
} QueryStep;
/*
@@ -62,10 +66,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;
@@ -140,6 +146,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__)
@@ -324,6 +331,49 @@ static uint16_t symbol_table_insert_name(
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_immediate = is_immediate,
+ };
+}
+
+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 +383,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 +449,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;
}
}
}
@@ -533,7 +583,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 +603,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 +624,7 @@ static TSQueryError ts_query__parse_pattern(
// Parse the wildcard symbol
if (stream->next == '*') {
- symbol = WILDCARD_SYMBOL;
+ symbol = NAMED_WILDCARD_SYMBOL;
stream_advance(stream);
}
@@ -597,24 +648,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 +707,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 +730,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 +759,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 {
@@ -710,7 +769,7 @@ static TSQueryError ts_query__parse_pattern(
stream_skip_whitespace(stream);
// Parse an '@'-prefixed capture pattern
- if (stream->next == '@') {
+ while (stream->next == '@') {
stream_advance(stream);
// Parse the capture name
@@ -725,7 +784,8 @@ static TSQueryError ts_query__parse_pattern(
capture_name,
length
);
- self->steps.contents[starting_step_index].capture_id = capture_id;
+ QueryStep *step = &self->steps.contents[starting_step_index];
+ query_step__add_capture(step, capture_id);
(*capture_count)++;
stream_skip_whitespace(stream);
@@ -794,8 +854,8 @@ TSQuery *ts_query_new(
.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.
@@ -891,16 +951,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);
}
/***************
@@ -1010,7 +1085,7 @@ static bool ts_query_cursor__first_in_progress_capture(
static bool ts_query__cursor_add_state(
TSQueryCursor *self,
- const PatternEntry *slice
+ const PatternEntry *pattern
) {
uint32_t list_id = capture_list_pool_acquire(&self->capture_list_pool);
@@ -1037,11 +1112,11 @@ static bool ts_query__cursor_add_state(
}
}
- LOG(" start state. pattern:%u\n", slice->pattern_index);
+ LOG(" start state. pattern:%u\n", pattern->pattern_index);
array_push(&self->states, ((QueryState) {
.capture_list_id = list_id,
- .step_index = slice->step_index,
- .pattern_index = slice->pattern_index,
+ .step_index = pattern->step_index,
+ .pattern_index = pattern->pattern_index,
.start_depth = self->depth,
.capture_count = 0,
.consumed_capture_count = 0,
@@ -1113,15 +1188,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 +1221,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 +1270,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) {
@@ -1261,11 +1349,13 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
// If the current node is captured in this pattern, add it to the
// capture list.
- if (step->capture_id != NONE) {
+ 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;
LOG(
" capture node. pattern:%u, capture_id:%u\n",
next_state->pattern_index,
- step->capture_id
+ capture_id
);
TSQueryCapture *capture_list = capture_list_pool_get(
&self->capture_list_pool,
@@ -1273,7 +1363,7 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
);
capture_list[next_state->capture_count++] = (TSQueryCapture) {
node,
- step->capture_id
+ capture_id
};
}
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/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/mark_extended_spec.lua b/test/functional/api/extmark_spec.lua
index 8aa8ed07c5..5bf3fa554f 100644
--- a/test/functional/api/mark_extended_spec.lua
+++ b/test/functional/api/extmark_spec.lua
@@ -548,7 +548,6 @@ describe('API/extmarks', function()
end)
it('deleting marks at end of line works', function()
- -- mark_extended.c/extmark_col_adjust_delete
set_extmark(ns, marks[1], 0, 4)
feed('$x')
check_undo_redo(ns, marks[1], 0, 4, 0, 4)
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index d901a5e2eb..fb3755cb8e 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)
@@ -1853,4 +1854,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/autocmd/tabnewentered_spec.lua b/test/functional/autocmd/tabnewentered_spec.lua
index 123dbd0824..dc2fd3e97d 100644
--- a/test/functional/autocmd/tabnewentered_spec.lua
+++ b/test/functional/autocmd/tabnewentered_spec.lua
@@ -97,6 +97,7 @@ describe('tabpage/previous', function()
switches_to_previous_after_new_tab_creation_at_end('g<Tab>'))
it('switches to previous via <C-W>g<Tab>. after new tab creation at end', switches_to_previous_after_new_tab_creation_at_end('<C-W>g<Tab>'))
it('switches to previous via <C-Tab>. after new tab creation at end', switches_to_previous_after_new_tab_creation_at_end('<C-Tab>'))
+ it('switches to previous via :tabn #<CR>. after new tab creation at end', switches_to_previous_after_new_tab_creation_at_end(':tabn #<CR>'))
local function switches_to_previous_after_new_tab_creation_in_middle(characters)
return function()
@@ -140,6 +141,8 @@ describe('tabpage/previous', function()
switches_to_previous_after_new_tab_creation_in_middle('<C-W>g<Tab>'))
it('switches to previous via <C-Tab> after new tab creation in middle',
switches_to_previous_after_new_tab_creation_in_middle('<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after new tab creation in middle',
+ switches_to_previous_after_new_tab_creation_in_middle(':tabn #<CR>'))
local function switches_to_previous_after_switching_to_next_tab(characters)
return function()
@@ -180,6 +183,8 @@ describe('tabpage/previous', function()
switches_to_previous_after_switching_to_next_tab('<C-W>g<Tab>'))
it('switches to previous via <C-Tab> after switching to next tab',
switches_to_previous_after_switching_to_next_tab('<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to next tab',
+ switches_to_previous_after_switching_to_next_tab(':tabn #<CR>'))
local function switches_to_previous_after_switching_to_last_tab(characters)
return function()
@@ -222,6 +227,8 @@ describe('tabpage/previous', function()
switches_to_previous_after_switching_to_last_tab('<C-W>g<Tab>'))
it('switches to previous after switching to last tab',
switches_to_previous_after_switching_to_last_tab('<C-Tab>'))
+ it('switches to previous after switching to last tab',
+ switches_to_previous_after_switching_to_last_tab(':tabn #<CR>'))
local function switches_to_previous_after_switching_to_previous_tab(characters)
return function()
@@ -262,6 +269,8 @@ describe('tabpage/previous', function()
switches_to_previous_after_switching_to_previous_tab('<C-W>g<Tab>'))
it('switches to previous via <C-Tab> after switching to previous tab',
switches_to_previous_after_switching_to_previous_tab('<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to previous tab',
+ switches_to_previous_after_switching_to_previous_tab(':tabn #<CR>'))
local function switches_to_previous_after_switching_to_first_tab(characters)
return function()
@@ -304,6 +313,8 @@ describe('tabpage/previous', function()
switches_to_previous_after_switching_to_first_tab('<C-W>g<Tab>'))
it('switches to previous via <C-Tab> after switching to first tab',
switches_to_previous_after_switching_to_first_tab('<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to first tab',
+ switches_to_previous_after_switching_to_first_tab(':tabn #<CR>'))
local function switches_to_previous_after_numbered_tab_switch(characters)
return function()
@@ -344,6 +355,8 @@ describe('tabpage/previous', function()
switches_to_previous_after_numbered_tab_switch('<C-W>g<Tab>'))
it('switches to previous via <C-Tab> after numbered tab switch',
switches_to_previous_after_numbered_tab_switch('<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after numbered tab switch',
+ switches_to_previous_after_numbered_tab_switch(':tabn #<CR>'))
local function switches_to_previous_after_switching_to_previous(characters1, characters2)
return function()
@@ -386,18 +399,32 @@ describe('tabpage/previous', function()
switches_to_previous_after_switching_to_previous('g<Tab>', '<C-W>g<Tab>'))
it('switches to previous via <C-Tab> after switching to previous via g<Tab>',
switches_to_previous_after_switching_to_previous('g<Tab>', '<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to previous via g<Tab>',
+ switches_to_previous_after_switching_to_previous('g<Tab>', ':tabn #<CR>'))
it('switches to previous via g<Tab> after switching to previous via <C-W>g<Tab>',
switches_to_previous_after_switching_to_previous('<C-W>g<Tab>', 'g<Tab>'))
it('switches to previous via <C-W>g<Tab> after switching to previous via <C-W>g<Tab>',
switches_to_previous_after_switching_to_previous('<C-W>g<Tab>', '<C-W>g<Tab>'))
it('switches to previous via <C-Tab> after switching to previous via <C-W>g<Tab>',
switches_to_previous_after_switching_to_previous('<C-W>g<Tab>', '<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to previous via <C-W>g<Tab>',
+ switches_to_previous_after_switching_to_previous('<C-W>g<Tab>', ':tabn #<CR>'))
it('switches to previous via g<Tab> after switching to previous via <C-Tab>',
switches_to_previous_after_switching_to_previous('<C-Tab>', 'g<Tab>'))
it('switches to previous via <C-W>g<Tab> after switching to previous via <C-Tab>',
switches_to_previous_after_switching_to_previous('<C-Tab>', '<C-W>g<Tab>'))
it('switches to previous via <C-Tab> after switching to previous via <C-Tab>',
switches_to_previous_after_switching_to_previous('<C-Tab>', '<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to previous via <C-Tab>',
+ switches_to_previous_after_switching_to_previous('<C-Tab>', ':tabn #<CR>'))
+ it('switches to previous via g<Tab> after switching to previous via :tabn #<CR>',
+ switches_to_previous_after_switching_to_previous(':tabn #<CR>', 'g<Tab>'))
+ it('switches to previous via <C-W>g<Tab> after switching to previous via :tabn #<CR>',
+ switches_to_previous_after_switching_to_previous(':tabn #<CR>', '<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab> after switching to previous via <C-Tab>',
+ switches_to_previous_after_switching_to_previous(':tabn #<CR>', '<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to previous via :tabn #<CR>',
+ switches_to_previous_after_switching_to_previous(':tabn #<CR>', ':tabn #<CR>'))
local function does_not_switch_to_previous_after_closing_current_tab(characters)
return function()
@@ -437,6 +464,8 @@ describe('tabpage/previous', function()
does_not_switch_to_previous_after_closing_current_tab('<C-W>g<Tab>'))
it('does not switch to previous via <C-Tab> after closing current tab',
does_not_switch_to_previous_after_closing_current_tab('<C-Tab>'))
+ it('does not switch to previous via :tabn #<CR> after closing current tab',
+ does_not_switch_to_previous_after_closing_current_tab(':tabn #<CR>'))
local function does_not_switch_to_previous_after_entering_operator_pending(characters)
return function()
@@ -480,6 +509,11 @@ describe('tabpage/previous', function()
-- does_not_switch_to_previous_after_entering_operator_pending('<C-W>g<Tab>'))
it('does not switch to previous via <C-Tab> after entering operator pending',
does_not_switch_to_previous_after_entering_operator_pending('<C-Tab>'))
+ -- NOTE: When in operator pending mode, pressing : leaves operator pending
+ -- mode and enters command mode, so :tabn #<CR> does in fact switch
+ -- tabs.
+ -- it('does not switch to previous via :tabn #<CR> after entering operator pending',
+ -- does_not_switch_to_previous_after_entering_operator_pending(':tabn #<CR>'))
local function cmdline_win_prevents_tab_switch(characters, completion_visible)
return function()
@@ -516,6 +550,8 @@ describe('tabpage/previous', function()
cmdline_win_prevents_tab_switch('<C-W>g<Tab>', 1))
it('cmdline-win prevents tab switch via <C-Tab>',
cmdline_win_prevents_tab_switch('<C-Tab>', 0))
+ it('cmdline-win prevents tab switch via :tabn #<CR>',
+ cmdline_win_prevents_tab_switch(':tabn #<CR>', 0))
it(':tabs indicates correct prevtab curwin', function()
-- Add three tabs for a total of four
diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua
index b63b868127..57e6f4fd63 100644
--- a/test/functional/core/job_spec.lua
+++ b/test/functional/core/job_spec.lua
@@ -306,16 +306,16 @@ describe('jobs', function()
end))
end)
- it('disallows jobsend/stop on a non-existent job', function()
+ it('disallows jobsend on a non-existent job', function()
eq(false, pcall(eval, "jobsend(-1, 'lol')"))
- eq(false, pcall(eval, "jobstop(-1)"))
+ eq(0, eval('jobstop(-1)'))
end)
- it('disallows jobstop twice on the same job', function()
+ it('jobstop twice on the stopped or exited job return 0', function()
nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)")
neq(0, eval('j'))
- eq(true, pcall(eval, "jobstop(j)"))
- eq(false, pcall(eval, "jobstop(j)"))
+ eq(1, eval("jobstop(j)"))
+ eq(0, eval("jobstop(j)"))
end)
it('will not leak memory if we leave a job running', function()
@@ -919,6 +919,13 @@ describe('jobs', function()
end)
end)
+ it('jobstop on same id before stopped', function()
+ nvim('command', 'let j = jobstart(["cat", "-"], g:job_opts)')
+ neq(0, eval('j'))
+
+ eq({1, 0}, eval('[jobstop(j), jobstop(j)]'))
+ end)
+
describe('running tty-test program', function()
if helpers.pending_win32(pending) then return end
local function next_chunk()
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/mksession_spec.lua b/test/functional/ex_cmds/mksession_spec.lua
index 0f7860740e..949724bb53 100644
--- a/test/functional/ex_cmds/mksession_spec.lua
+++ b/test/functional/ex_cmds/mksession_spec.lua
@@ -6,6 +6,8 @@ local command = helpers.command
local get_pathsep = helpers.get_pathsep
local eq = helpers.eq
local funcs = helpers.funcs
+local matches = helpers.matches
+local pesc = helpers.pesc
local rmdir = helpers.rmdir
local file_prefix = 'Xtest-functional-ex_cmds-mksession_spec'
@@ -24,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()
@@ -48,7 +71,7 @@ describe(':mksession', function()
eq(cwd_dir .. get_pathsep() .. tab_dir, funcs.getcwd())
end)
- it('restores buffers when using tab-local working directories', function()
+ it('restores buffers with tab-local CWD', function()
local tmpfile_base = file_prefix .. '-tmpfile'
local cwd_dir = funcs.getcwd()
local session_path = cwd_dir .. get_pathsep() .. session_file
@@ -70,4 +93,24 @@ describe(':mksession', function()
command('tabnext 2')
eq(cwd_dir .. get_pathsep() .. tmpfile_base .. '2', funcs.expand('%:p'))
end)
+
+ it('restores CWD for :terminal buffers #11288', function()
+ local cwd_dir = funcs.fnamemodify('.', ':p:~'):gsub([[[\/]*$]], '')
+ 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')
+ command('cd '..cwd_dir)
+ command('mksession '..session_path)
+ command('qall!')
+
+ -- Create a new test instance of Nvim.
+ clear()
+ command('silent source '..session_path)
+
+ local expected_cwd = cwd_dir..'/'..tab_dir
+ matches('^term://'..pesc(expected_cwd)..'//%d+:', funcs.expand('%'))
+ command('qall!')
+ 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 de61ff9cc8..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
@@ -768,7 +768,7 @@ function module.new_pipename()
end
function module.missing_provider(provider)
- if provider == 'ruby' or provider == 'node' then
+ if provider == 'ruby' or provider == 'node' or provider == 'perl' then
local prog = module.funcs['provider#' .. provider .. '#Detect']()
return prog == '' and (provider .. ' not detected') or false
elseif provider == 'python' or provider == 'python3' then
@@ -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/vim_spec.lua b/test/functional/lua/vim_spec.lua
index e879f8b925..5ce4bf2973 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,36 @@ 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)
- assert(is_dc)
+ return not vim.tbl_islist(b.x) and vim.tbl_islist(b.y)
+ and vim.tbl_count(b) == 2
+ and tostring(a) ~= tostring(b)
+ ]]))
end)
it('vim.pesc', function()
@@ -353,6 +372,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 +412,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 +670,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()
@@ -700,4 +827,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..369b826adf 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")
diff --git a/test/functional/provider/perl_spec.lua b/test/functional/provider/perl_spec.lua
new file mode 100644
index 0000000000..7b446e4ab3
--- /dev/null
+++ b/test/functional/provider/perl_spec.lua
@@ -0,0 +1,80 @@
+local helpers = require('test.functional.helpers')(after_each)
+local eq, clear = helpers.eq, helpers.clear
+local missing_provider = helpers.missing_provider
+local command = helpers.command
+local write_file = helpers.write_file
+local eval = helpers.eval
+local retry = helpers.retry
+
+do
+ clear()
+ local reason = missing_provider('perl')
+ if reason then
+ pending(string.format("Missing perl host, or perl version is too old (%s)", reason), function() end)
+ return
+ end
+end
+
+before_each(function()
+ clear()
+end)
+
+describe('perl host', function()
+ if helpers.pending_win32(pending) then return end
+ teardown(function ()
+ os.remove('Xtest-perl-hello.pl')
+ os.remove('Xtest-perl-hello-plugin.pl')
+ end)
+
+ it('works', function()
+ local fname = 'Xtest-perl-hello.pl'
+ write_file(fname, [[
+ package main;
+ use strict;
+ use warnings;
+ use Neovim::Ext;
+ use Neovim::Ext::MsgPack::RPC;
+
+ my $session = Neovim::Ext::MsgPack::RPC::socket_session($ENV{NVIM_LISTEN_ADDRESS});
+ my $nvim = Neovim::Ext::from_session($session);
+ $nvim->command('let g:job_out = "hello"');
+ 1;
+ ]])
+ command('let g:job_id = jobstart(["perl", "'..fname..'"])')
+ retry(nil, 3000, function() eq('hello', eval('g:job_out')) end)
+ end)
+
+ it('plugin works', function()
+ local fname = 'Xtest-perl-hello-plugin.pl'
+ write_file(fname, [[
+ package TestPlugin;
+ use strict;
+ use warnings;
+ use parent qw(Neovim::Ext::Plugin);
+
+ __PACKAGE__->register;
+
+ @{TestPlugin::commands} = ();
+ @{TestPlugin::specs} = ();
+ sub test_command :nvim_command('TestCommand')
+ {
+ my ($this) = @_;
+ $this->nvim->command('let g:job_out = "hello-plugin"');
+ }
+
+ package main;
+ use strict;
+ use warnings;
+ use Neovim::Ext;
+ use Neovim::Ext::MsgPack::RPC;
+
+ my $session = Neovim::Ext::MsgPack::RPC::socket_session($ENV{NVIM_LISTEN_ADDRESS});
+ my $nvim = Neovim::Ext::from_session($session);
+ my $plugin = TestPlugin->new($nvim);
+ $plugin->test_command();
+ 1;
+ ]])
+ command('let g:job_id = jobstart(["perl", "'..fname..'"])')
+ retry(nil, 3000, function() eq('hello-plugin', eval('g:job_out')) end)
+ end)
+end)
diff --git a/test/functional/terminal/edit_spec.lua b/test/functional/terminal/edit_spec.lua
index d213bae7b3..fabc5524ed 100644
--- a/test/functional/terminal/edit_spec.lua
+++ b/test/functional/terminal/edit_spec.lua
@@ -5,9 +5,12 @@ local curbufmeths = helpers.curbufmeths
local curwinmeths = helpers.curwinmeths
local nvim_dir = helpers.nvim_dir
local command = helpers.command
+local funcs = helpers.funcs
local meths = helpers.meths
local clear = helpers.clear
local eq = helpers.eq
+local matches = helpers.matches
+local pesc = helpers.pesc
describe(':edit term://*', function()
local get_screen = function(columns, lines)
@@ -28,7 +31,8 @@ describe(':edit term://*', function()
command('edit term://')
local termopen_runs = meths.get_var('termopen_runs')
eq(1, #termopen_runs)
- eq(termopen_runs[1], termopen_runs[1]:match('^term://.//%d+:$'))
+ local cwd = funcs.fnamemodify('.', ':p:~'):gsub([[[\/]*$]], '')
+ matches('^term://'..pesc(cwd)..'//%d+:$', termopen_runs[1])
end)
it("runs TermOpen early enough to set buffer-local 'scrollback'", function()
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/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 a0e5c3ca63..11c3f4123e 100644
--- a/test/functional/ui/popupmenu_spec.lua
+++ b/test/functional/ui/popupmenu_spec.lua
@@ -640,7 +640,7 @@ describe('builtin popupmenu', function()
})
end)
- it('works with preview-window above', function()
+ it('with preview-window above', function()
feed(':ped<CR><c-w>4+')
feed('iaa bb cc dd ee ff gg hh ii jj<cr>')
feed('<c-x><c-n>')
@@ -668,7 +668,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with preview-window below', function()
+ it('with preview-window below', function()
feed(':ped<CR><c-w>4+<c-w>r')
feed('iaa bb cc dd ee ff gg hh ii jj<cr>')
feed('<c-x><c-n>')
@@ -696,7 +696,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with preview-window above and tall and inverted', function()
+ it('with preview-window above and tall and inverted', function()
feed(':ped<CR><c-w>8+')
feed('iaa<cr>bb<cr>cc<cr>dd<cr>ee<cr>')
feed('ff<cr>gg<cr>hh<cr>ii<cr>jj<cr>')
@@ -726,7 +726,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with preview-window above and short and inverted', function()
+ it('with preview-window above and short and inverted', function()
feed(':ped<CR><c-w>4+')
feed('iaa<cr>bb<cr>cc<cr>dd<cr>ee<cr>')
feed('ff<cr>gg<cr>hh<cr>ii<cr>jj<cr>')
@@ -755,7 +755,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with preview-window below and inverted', function()
+ it('with preview-window below and inverted', function()
feed(':ped<CR><c-w>4+<c-w>r')
feed('iaa<cr>bb<cr>cc<cr>dd<cr>ee<cr>')
feed('ff<cr>gg<cr>hh<cr>ii<cr>jj<cr>')
@@ -784,7 +784,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with vsplits', function()
+ it('with vsplits', function()
insert('aaa aab aac\n')
feed(':vsplit<cr>')
screen:expect([[
@@ -859,7 +859,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with split and scroll', function()
+ it('with split and scroll', function()
screen:try_resize(60,14)
command("split")
command("set completeopt+=noinsert")
@@ -1293,7 +1293,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('behaves correcty with VimResized autocmd', function()
+ it('with VimResized autocmd', function()
feed('isome long prefix before the ')
command("set completeopt+=noinsert,noselect")
command("autocmd VimResized * redraw!")
@@ -1337,8 +1337,8 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with rightleft window', function()
- command("set rl")
+ it('with rightleft window', function()
+ command("set rl wildoptions+=pum")
feed('isome rightleft ')
screen:expect([[
^ tfelthgir emos|
@@ -1435,9 +1435,58 @@ 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('works with multiline messages', function()
+ it('with multiline messages', function()
screen:try_resize(40,8)
feed('ixx<cr>')
command('imap <f2> <cmd>echoerr "very"\\|echoerr "much"\\|echoerr "error"<cr>')
@@ -1514,7 +1563,7 @@ describe('builtin popupmenu', function()
]], unchanged=true}
end)
- it('works with kind, menu and abbr attributes', function()
+ it('with kind, menu and abbr attributes', function()
screen:try_resize(40,8)
feed('ixx ')
funcs.complete(4, {{word='wordey', kind= 'x', menu='extrainfo'}, 'thing', {word='secret', abbr='sneaky', menu='bar'}})
@@ -1566,7 +1615,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with wildoptions=pum', function()
+ it('wildoptions=pum', function()
screen:try_resize(32,10)
command('set wildmenu')
command('set wildoptions=pum')
@@ -1738,7 +1787,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with wildoptions=pum with scrolled mesages ', function()
+ it('wildoptions=pum with scrolled mesages ', function()
screen:try_resize(40,10)
command('set wildmenu')
command('set wildoptions=pum')
@@ -1786,6 +1835,39 @@ describe('builtin popupmenu', function()
]]}
end)
+ it('wildoptions=pum and wildmode=longest,full #11622', function()
+ screen:try_resize(30,8)
+ command('set wildmenu')
+ command('set wildoptions=pum')
+ command('set wildmode=longest,full')
+
+ feed(':sign u<tab>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :sign un^ |
+ ]]}
+ eq(0, funcs.wildmenumode())
+
+ feed('<tab>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{s: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign undefine^ |
+ ]]}
+ eq(1, funcs.wildmenumode())
+ end)
+
it("'pumblend' RGB-color", function()
screen:try_resize(60,14)
screen:set_default_attr_ids({
diff --git a/test/functional/ui/wildmode_spec.lua b/test/functional/ui/wildmode_spec.lua
index d655ab20d3..99ebc4971e 100644
--- a/test/functional/ui/wildmode_spec.lua
+++ b/test/functional/ui/wildmode_spec.lua
@@ -259,6 +259,106 @@ describe("'wildmenu'", function()
]])
end)
+ it('wildmode=longest,list', function()
+ -- Need more than 5 rows, else tabline is covered and will be redrawn.
+ screen:try_resize(25, 7)
+
+ command('set wildmenu wildmode=longest,list')
+
+ -- give wildmode-longest something to expand to
+ feed(':sign u<tab>')
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ :sign un^ |
+ ]])
+ feed('<tab>') -- trigger wildmode list
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ |
+ :sign un |
+ undefine unplace |
+ :sign un^ |
+ ]])
+ feed('<Esc>')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+
+ -- give wildmode-longest something it cannot expand, use list
+ feed(':sign un<tab>')
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ |
+ :sign un |
+ undefine unplace |
+ :sign un^ |
+ ]])
+ feed('<tab>')
+ screen:expect_unchanged()
+ feed('<Esc>')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ end)
+
+ it('wildmode=list,longest', function()
+ -- Need more than 5 rows, else tabline is covered and will be redrawn.
+ screen:try_resize(25, 7)
+
+ command('set wildmenu wildmode=list,longest')
+ feed(':sign u<tab>')
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ |
+ :sign u |
+ undefine unplace |
+ :sign u^ |
+ ]])
+ feed('<tab>') -- trigger wildmode longest
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ |
+ :sign u |
+ undefine unplace |
+ :sign un^ |
+ ]])
+ feed('<Esc>')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ end)
+
it('multiple <C-D> renders correctly', function()
screen:try_resize(25, 7)
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..1651eb9bcc 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
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..a321840fef 100644
--- a/third-party/CMakeLists.txt
+++ b/third-party/CMakeLists.txt
@@ -42,6 +42,7 @@ 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,8 +191,8 @@ 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)
@@ -199,6 +200,9 @@ set(LIBICONV_SHA256 ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc891
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)
endif()
@@ -254,6 +258,10 @@ if(USE_BUNDLED_UTF8PROC)
include(BuildUtf8proc)
endif()
+if(USE_BUNDLED_TS_PARSERS)
+ include(BuildTreesitterParsers)
+endif()
+
if(WIN32)
include(GetBinaryDeps)
@@ -287,13 +295,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
index 7297913f87..d5743d36e2 100644
--- a/third-party/cmake/BuildUtf8proc.cmake
+++ b/third-party/cmake/BuildUtf8proc.cmake
@@ -59,7 +59,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/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
-