diff options
104 files changed, 5552 insertions, 3215 deletions
diff --git a/.ci/common/build.sh b/.ci/common/build.sh index 5cf34935d6..44087513ee 100644 --- a/.ci/common/build.sh +++ b/.ci/common/build.sh @@ -37,7 +37,7 @@ build_deps() { cd "${TRAVIS_BUILD_DIR}" } -build_nvim() { +prepare_build() { if [[ -n "${CLANG_SANITIZER}" ]]; then CMAKE_FLAGS="${CMAKE_FLAGS} -DCLANG_${CLANG_SANITIZER}=ON" fi @@ -47,9 +47,11 @@ build_nvim() { mkdir -p "${BUILD_DIR}" cd "${BUILD_DIR}" - echo "Configuring with '${CMAKE_FLAGS}'." - cmake ${CMAKE_FLAGS} "${TRAVIS_BUILD_DIR}" + echo "Configuring with '${CMAKE_FLAGS} $@'." + cmake ${CMAKE_FLAGS} "$@" "${TRAVIS_BUILD_DIR}" +} +build_nvim() { echo "Building nvim." if ! ${MAKE_CMD} nvim; then exit 1 diff --git a/.ci/common/test.sh b/.ci/common/test.sh index 714dfd297b..b28e46a4df 100644 --- a/.ci/common/test.sh +++ b/.ci/common/test.sh @@ -1,21 +1,46 @@ -check_core_dumps() { - sleep 2 +print_core() { + local app="$1" + local core="$2" + if test "$app" = quiet ; then + echo "Found core $core" + return 0 + fi + echo "======= Core file $core =======" + if [[ "${TRAVIS_OS_NAME}" == osx ]]; then + lldb -Q -o "bt all" -f "${app}" -c "${core}" + else + gdb -n -batch -ex 'thread apply all bt full' "${app}" -c "${core}" + fi +} +check_core_dumps() { + local del= + if test "$1" = "--delete" ; then + del=1 + shift + fi + local app="${1:-${BUILD_DIR}/bin/nvim}" if [[ "${TRAVIS_OS_NAME}" == osx ]]; then local cores="$(find /cores/ -type f -print)" - local dbg_cmd="lldb -Q -o bt -f ${BUILD_DIR}/bin/nvim -c" else - # FIXME (fwalch): Will trigger if a file named core.* exists outside of $DEPS_BUILD_DIR. - local cores="$(find ./ -type f -not -path "*${DEPS_BUILD_DIR}*" -name 'core.*' -print)" - local dbg_cmd="gdb -n -batch -ex bt ${BUILD_DIR}/bin/nvim" + local cores="$(find ./ -type f -name 'core.*' -print)" fi if [ -z "${cores}" ]; then return fi + local core for core in $cores; do - ${dbg_cmd} "${core}" + if test "$del" = "1" ; then + print_core "$app" "$core" >&2 + rm "$core" + else + print_core "$app" "$core" + fi done + if test "$app" = quiet ; then + return 0 + fi exit 1 } @@ -49,28 +74,39 @@ asan_check() { } run_unittests() { - ${MAKE_CMD} -C "${BUILD_DIR}" unittest + ulimit -c unlimited + if ! ${MAKE_CMD} -C "${BUILD_DIR}" unittest ; then + check_core_dumps "$(which luajit)" + exit 1 + fi + check_core_dumps "$(which luajit)" } run_functionaltests() { + ulimit -c unlimited if ! ${MAKE_CMD} -C "${BUILD_DIR}" ${FUNCTIONALTEST}; then asan_check "${LOG_DIR}" valgrind_check "${LOG_DIR}" + check_core_dumps exit 1 fi asan_check "${LOG_DIR}" valgrind_check "${LOG_DIR}" + check_core_dumps } run_oldtests() { + ulimit -c unlimited if ! make -C "${TRAVIS_BUILD_DIR}/src/nvim/testdir"; then reset asan_check "${LOG_DIR}" valgrind_check "${LOG_DIR}" + check_core_dumps exit 1 fi asan_check "${LOG_DIR}" valgrind_check "${LOG_DIR}" + check_core_dumps } install_nvim() { diff --git a/.ci/run_tests.sh b/.ci/run_tests.sh index d542a2f121..6347ac15d4 100755 --- a/.ci/run_tests.sh +++ b/.ci/run_tests.sh @@ -7,6 +7,9 @@ CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${CI_DIR}/common/build.sh" source "${CI_DIR}/common/test.sh" +check_core_dumps --delete quiet + +prepare_build build_nvim if [ "$CLANG_SANITIZER" != "TSAN" ]; then diff --git a/.travis.yml b/.travis.yml index 0baf3feb74..d28fc9d7f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,7 @@ env: -DCMAKE_INSTALL_PREFIX:PATH=$INSTALL_PREFIX -DBUSTED_OUTPUT_TYPE=gtest -DDEPS_PREFIX=$DEPS_BUILD_DIR/usr - -DMIN_LOG_LEVEL=0" + -DMIN_LOG_LEVEL=2" - DEPS_CMAKE_FLAGS="-DDEPS_DOWNLOAD_DIR:PATH=$DEPS_DOWNLOAD_DIR" # Additional CMake flags for 32-bit builds. - CMAKE_FLAGS_32BIT="-DCMAKE_SYSTEM_LIBRARY_PATH=/lib32:/usr/lib32:/usr/local/lib32 @@ -98,6 +98,7 @@ addons: packages: - autoconf - automake + - apport - build-essential - clang-3.8 - cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d2d0ec97c..2fd5209396 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -439,11 +439,11 @@ install_helper( # MIN_LOG_LEVEL for log.h if(DEFINED MIN_LOG_LEVEL) if(NOT MIN_LOG_LEVEL MATCHES "^[0-3]$") - message(FATAL_ERROR "MIN_LOG_LEVEL must be a number DEBUG (0), INFO (1), WARNING (2) or ERROR (3)") + message(FATAL_ERROR "MIN_LOG_LEVEL must be a number 0-3") endif() - message(STATUS "Log level set to ${MIN_LOG_LEVEL}") + message(STATUS "MIN_LOG_LEVEL set to ${MIN_LOG_LEVEL}") else() - message(STATUS "Log level not specified, defaulting to INFO(1)") + message(STATUS "MIN_LOG_LEVEL not specified, defaulting to INFO(1)") endif() # Go down the tree. @@ -1,4 +1,4 @@ -[](https://neovim.io) +[](https://neovim.io) [Wiki](https://github.com/neovim/neovim/wiki) | [Documentation](https://neovim.io/doc) | @@ -18,14 +18,28 @@ Neovim is a project that seeks to aggressively refactor Vim in order to: - Simplify maintenance and encourage [contributions](CONTRIBUTING.md) - Split the work between multiple developers -- Enable advanced [external UIs] without modifications to the core -- Improve extensibility with a new [plugin architecture](https://github.com/neovim/neovim/wiki/Plugin-UI-architecture) +- Enable [advanced UIs] without modifications to the core +- Maximize [extensibility](https://github.com/neovim/neovim/wiki/Plugin-UI-architecture) -For more details, see -[the wiki](https://github.com/neovim/neovim/wiki/Introduction)! +See [the wiki](https://github.com/neovim/neovim/wiki/Introduction) and [Roadmap] +for more information. [](https://waffle.io/neovim/neovim/metrics) +Install from source +------------------- + + make CMAKE_BUILD_TYPE=RelWithDebInfo + sudo make install + +See [the wiki](https://github.com/neovim/neovim/wiki/Building-Neovim) for details. + +Install from package +-------------------- + +Packages are in [Homebrew], [Debian], [Ubuntu], [Fedora], [Arch Linux], and +[more](https://github.com/neovim/neovim/wiki/Installing-Neovim). + What's been done so far ----------------------- @@ -34,24 +48,13 @@ What's been done so far - Asynchronous [job control](https://github.com/neovim/neovim/pull/2247) - [Shared data (shada)](https://github.com/neovim/neovim/pull/2506) among multiple editor instances - [XDG base directories](https://github.com/neovim/neovim/pull/3470) support -- [libuv](https://github.com/libuv/libuv/)-based platform/OS/"mch" layer +- [libuv](https://github.com/libuv/libuv/)-based platform/OS layer - [Pushdown automaton](https://github.com/neovim/neovim/pull/3413) input model - 1000s of new tests - Legacy tests converted to Lua tests See [`:help nvim-features`][nvim-features] for a comprehensive list. -What's being worked on now --------------------------- - -See the [Roadmap]. - -How do I get it? ----------------- - -There is a Debian package, homebrew formula, PKGBUILD for Arch Linux, RPM, and -more. See [the wiki](https://github.com/neovim/neovim/wiki/Installing-Neovim)! - License ------- @@ -81,6 +84,11 @@ See `LICENSE` for details. [license-commit]: https://github.com/neovim/neovim/commit/b17d9691a24099c9210289f16afb1a498a89d803 [nvim-features]: https://neovim.io/doc/user/vim_diff.html#nvim-features [Roadmap]: https://neovim.io/roadmap/ -[external UIs]: https://github.com/neovim/neovim/wiki/Related-projects#gui-projects +[advanced UIs]: https://github.com/neovim/neovim/wiki/Related-projects#gui-projects +[Homebrew]: https://github.com/neovim/homebrew-neovim#installation +[Debian]: https://packages.debian.org/testing/neovim +[Ubuntu]: http://packages.ubuntu.com/search?keywords=neovim +[Fedora]: https://admin.fedoraproject.org/pkgdb/package/rpms/neovim +[Arch Linux]: https://www.archlinux.org/packages/?q=neovim <!-- vim: set tw=80: --> diff --git a/appveyor.yml b/appveyor.yml index eb3064c3b1..ed5e06e3ee 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,6 +5,9 @@ configuration: install: [] build_script: - call .ci\build.bat +cache: +- C:\msys64\var\cache\pacman\pkg -> .ci\build.bat +- .deps -> third-party/CMakeLists.txt artifacts: - path: build/Neovim.zip - path: build/bin/nvim.exe diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index bc6b2c074c..57dd508f96 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -221,20 +221,26 @@ endfunction function! s:check_python(version) abort call health#report_start('Python ' . a:version . ' provider') - let python_bin_name = 'python'.(a:version == 2 ? '' : '3') + let pyname = 'python'.(a:version == 2 ? '' : '3') let pyenv = resolve(exepath('pyenv')) let pyenv_root = exists('$PYENV_ROOT') ? resolve($PYENV_ROOT) : 'n' let venv = exists('$VIRTUAL_ENV') ? resolve($VIRTUAL_ENV) : '' - let host_prog_var = python_bin_name.'_host_prog' + let host_prog_var = pyname.'_host_prog' + let loaded_var = 'g:loaded_'.pyname.'_provider' let python_bin = '' let python_multiple = [] + if exists(loaded_var) && !exists('*provider#'.pyname.'#Call') + call health#report_info('Disabled. '.loaded_var.'='.eval(loaded_var)) + return + endif + if exists('g:'.host_prog_var) call health#report_info(printf('Using: g:%s = "%s"', host_prog_var, get(g:, host_prog_var))) endif - let [python_bin_name, pythonx_errs] = provider#pythonx#Detect(a:version) - if empty(python_bin_name) + let [pyname, pythonx_errs] = provider#pythonx#Detect(a:version) + if empty(pyname) call health#report_warn('No Python interpreter was found with the neovim ' \ . 'module. Using the first available for diagnostics.') if !empty(pythonx_errs) @@ -242,21 +248,21 @@ function! s:check_python(version) abort endif endif - if !empty(python_bin_name) + if !empty(pyname) if exists('g:'.host_prog_var) - let python_bin = exepath(python_bin_name) + let python_bin = exepath(pyname) endif - let python_bin_name = fnamemodify(python_bin_name, ':t') + let pyname = fnamemodify(pyname, ':t') endif if !empty(pythonx_errs) call health#report_error('Python provider error', pythonx_errs) endif - if !empty(python_bin_name) && empty(python_bin) && empty(pythonx_errs) + if !empty(pyname) && empty(python_bin) && empty(pythonx_errs) if !exists('g:'.host_prog_var) call health#report_info(printf('`g:%s` is not set. Searching for ' - \ . '%s in the environment.', host_prog_var, python_bin_name)) + \ . '%s in the environment.', host_prog_var, pyname)) endif if !empty(pyenv) @@ -269,19 +275,19 @@ function! s:check_python(version) abort call health#report_ok(printf('pyenv found: "%s"', pyenv)) endif - let python_bin = s:trim(s:system([pyenv, 'which', python_bin_name], '', 1)) + let python_bin = s:trim(s:system([pyenv, 'which', pyname], '', 1)) if empty(python_bin) - call health#report_warn(printf('pyenv could not find %s.', python_bin_name)) + call health#report_warn(printf('pyenv could not find %s.', pyname)) endif endif if empty(python_bin) - let python_bin = exepath(python_bin_name) + let python_bin = exepath(pyname) if exists('$PATH') for path in split($PATH, has('win32') ? ';' : ':') - let path_bin = path.'/'.python_bin_name + let path_bin = path.'/'.pyname if path_bin != python_bin && index(python_multiple, path_bin) == -1 \ && executable(path_bin) call add(python_multiple, path_bin) @@ -292,7 +298,7 @@ function! s:check_python(version) abort " This is worth noting since the user may install something " that changes $PATH, like homebrew. call health#report_info(printf('Multiple %s executables found. ' - \ . 'Set `g:%s` to avoid surprises.', python_bin_name, host_prog_var)) + \ . 'Set `g:%s` to avoid surprises.', pyname, host_prog_var)) endif if python_bin =~# '\<shims\>' @@ -333,9 +339,9 @@ function! s:check_python(version) abort endif endif - if empty(python_bin) && !empty(python_bin_name) + if empty(python_bin) && !empty(pyname) " An error message should have already printed. - call health#report_error(printf('`%s` was not found.', python_bin_name)) + call health#report_error(printf('`%s` was not found.', pyname)) elseif !empty(python_bin) && !s:check_bin(python_bin) let python_bin = '' endif @@ -349,7 +355,7 @@ function! s:check_python(version) abort if $VIRTUAL_ENV != pyenv_prefix let virtualenv_inactive = 1 endif - elseif !empty(python_bin_name) && exepath(python_bin_name) !~# '^'.$VIRTUAL_ENV.'/' + elseif !empty(pyname) && exepath(pyname) !~# '^'.$VIRTUAL_ENV.'/' let virtualenv_inactive = 1 endif endif @@ -381,9 +387,9 @@ function! s:check_python(version) abort call health#report_info('Python'.a:version.' version: ' . pyversion) if s:is_bad_response(status) - call health#report_info(printf('%s-neovim version: %s (%s)', python_bin_name, current, status)) + call health#report_info(printf('%s-neovim version: %s (%s)', pyname, current, status)) else - call health#report_info(printf('%s-neovim version: %s', python_bin_name, current)) + call health#report_info(printf('%s-neovim version: %s', pyname, current)) endif if s:is_bad_response(current) @@ -397,10 +403,10 @@ function! s:check_python(version) abort call health#report_error('HTTP request failed: '.latest) elseif s:is_bad_response(status) call health#report_warn(printf('Latest %s-neovim is NOT installed: %s', - \ python_bin_name, latest)) + \ pyname, latest)) elseif !s:is_bad_response(current) call health#report_ok(printf('Latest %s-neovim is installed: %s', - \ python_bin_name, latest)) + \ pyname, latest)) endif endif @@ -409,6 +415,12 @@ endfunction function! s:check_ruby() abort call health#report_start('Ruby provider') + let loaded_var = 'g:loaded_ruby_provider' + if exists(loaded_var) && !exists('*provider#ruby#Call') + call health#report_info('Disabled. '.loaded_var.'='.eval(loaded_var)) + return + endif + if !executable('ruby') || !executable('gem') call health#report_warn( \ "`ruby` and `gem` must be in $PATH.", @@ -451,7 +463,7 @@ function! s:check_ruby() abort \ current_gem, latest_gem), \ ['Run in shell: gem update neovim']) else - call health#report_ok('Gem "neovim" is up-to-date: '. current_gem) + call health#report_ok('Latest "neovim" gem is installed: '. current_gem) endif endfunction diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index a14221a656..1105601a0e 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -112,9 +112,10 @@ You will not get an error if you try to change the type of a variable. 1.2 Function references ~ *Funcref* *E695* *E718* -A Funcref variable is obtained with the |function()| function. It can be used -in an expression in the place of a function name, before the parenthesis -around the arguments, to invoke the function it refers to. Example: > +A Funcref variable is obtained with the |function()| function or created with +the lambda expression |expr-lambda|. It can be used in an expression in the +place of a function name, before the parenthesis around the arguments, to +invoke the function it refers to. Example: > :let Fn = function("MyFunc") :echo Fn() @@ -667,6 +668,7 @@ Expression syntax summary, from least to most significant: @r contents of register 'r' function(expr1, ...) function call func{ti}on(expr1, ...) function call with curly braces + {args -> expr1} lambda expression ".." indicates that the operations in this level can be concatenated. @@ -1174,6 +1176,62 @@ function(expr1, ...) function call See below |functions|. +lambda expression *expr-lambda* *lambda* +----------------- +{args -> expr1} lambda expression + +A lambda expression creates a new unnamed function which returns the result of +evaluating |expr1|. Lambda expressions are differ from |user-functions| in +the following ways: + +1. The body of the lambda expression is an |expr1| and not a sequence of |Ex| + commands. +2. The prefix "a:" should not be used for arguments. E.g.: > + :let F = {arg1, arg2 -> arg1 - arg2} + :echo F(5, 2) +< 3 + +The arguments are optional. Example: > + :let F = {-> 'error function'} + :echo F() +< error function + *closure* +Lambda expressions can access outer scope variables and arguments. This is +often called a closure. Example where "i" a and "a:arg" are used in a lambda +while they exists in the function scope. They remain valid even after the +function returns: > + :function Foo(arg) + : let i = 3 + : return {x -> x + i - a:arg} + :endfunction + :let Bar = Foo(4) + :echo Bar(6) +< 5 +See also |:func-closure|. Lambda and closure support can be checked with: > + if has('lambda') + +Examples for using a lambda expression with |sort()|, |map()| and |filter()|: > + :echo map([1, 2, 3], {idx, val -> val + 1}) +< [2, 3, 4] > + :echo sort([3,7,2,1,4], {a, b -> a - b}) +< [1, 2, 3, 4, 7] + +The lambda expression is also useful for Channel, Job and timer: > + :let timer = timer_start(500, + \ {-> execute("echo 'Handler called'", "")}, + \ {'repeat': 3}) +< Handler called + Handler called + Handler called + +Note how execute() is used to execute an Ex command. That's ugly though. + + +Lambda expressions have internal names like '<lambda>42'. If you get an error +for a lambda expression, you can find what it is with the following command: > + :function {'<lambda>42'} +See also: |numbered-function| + ============================================================================== 3. Internal variable *internal-variables* *E461* @@ -1481,7 +1539,7 @@ v:false Special value used to put "false" in JSON and msgpack. See |json_encode()|. This value is converted to "v:false" when used as a String (e.g. in |expr5| with string concatenation operator) and to zero when used as a Number (e.g. in |expr5| - or |expr7| when used with numeric operators). + or |expr7| when used with numeric operators). Read-only. *v:fcs_reason* *fcs_reason-variable* v:fcs_reason The reason why the |FileChangedShell| event was triggered. @@ -1631,7 +1689,7 @@ v:null Special value used to put "null" in JSON and NIL in msgpack. See |json_encode()|. This value is converted to "v:null" when used as a String (e.g. in |expr5| with string concatenation operator) and to zero when used as a Number (e.g. in |expr5| - or |expr7| when used with numeric operators). + or |expr7| when used with numeric operators). Read-only. *v:oldfiles* *oldfiles-variable* v:oldfiles List of file names that is loaded from the |shada| file on @@ -1791,6 +1849,9 @@ v:termresponse The escape sequence returned by the terminal for the |t_RV| always 95 or bigger). Pc is always zero. {only when compiled with |+termresponse| feature} + *v:testing* *testing-variable* +v:testing Must be set before using `test_garbagecollect_now()`. + *v:this_session* *this_session-variable* v:this_session Full filename of the last loaded or saved session file. See |:mksession|. It is allowed to set this variable. When no @@ -1814,7 +1875,7 @@ v:true Special value used to put "true" in JSON and msgpack. See |json_encode()|. This value is converted to "v:true" when used as a String (e.g. in |expr5| with string concatenation operator) and to one when used as a Number (e.g. in |expr5| or - |expr7| when used with numeric operators). + |expr7| when used with numeric operators). Read-only. *v:val* *val-variable* v:val Value of the current item of a |List| or |Dictionary|. Only @@ -1954,8 +2015,10 @@ foldlevel({lnum}) Number fold level at {lnum} foldtext() String line displayed for closed fold foldtextresult({lnum}) String text for closed fold at {lnum} foreground() Number bring the Vim window to the foreground -function({name} [, {arglist}] [, {dict}]) +funcref({name} [, {arglist}] [, {dict}]) Funcref reference to function {name} +function({name} [, {arglist}] [, {dict}]) + Funcref named reference to function {name} garbagecollect([{atexit}]) none free memory, breaking cyclic references get({list}, {idx} [, {def}]) any get item {idx} from {list} or {def} get({dict}, {key} [, {def}]) any get item {key} from {dict} or {def} @@ -2215,6 +2278,7 @@ tagfiles() List tags files used tan({expr}) Float tangent of {expr} tanh({expr}) Float hyperbolic tangent of {expr} tempname() String name for a temporary file +test_garbagecollect_now() none free memory right now for testing timer_start({time}, {callback} [, {options}]) Number create a timer timer_stop({timer}) none stop a timer @@ -3369,31 +3433,47 @@ filewritable({file}) *filewritable()* directory, and we can write to it, the result is 2. -filter({expr}, {string}) *filter()* - {expr} must be a |List| or a |Dictionary|. - For each item in {expr} evaluate {string} and when the result +filter({expr1}, {expr2}) *filter()* + {expr1} must be a |List| or a |Dictionary|. + For each item in {expr1} evaluate {expr2} and when the result is zero remove the item from the |List| or |Dictionary|. - Inside {string} |v:val| has the value of the current item. + {expr2} must be a |string| or |Funcref|. + + if {expr2} is a |string|, inside {expr2} |v:val| has the value + of the current item. For a |Dictionary| |v:key| has the key + of the current item. For a |Dictionary| |v:key| has the key of the current item. Examples: > - :call filter(mylist, 'v:val !~ "OLD"') + call filter(mylist, 'v:val !~ "OLD"') < Removes the items where "OLD" appears. > - :call filter(mydict, 'v:key >= 8') + call filter(mydict, 'v:key >= 8') < Removes the items with a key below 8. > - :call filter(var, 0) + call filter(var, 0) < Removes all the items, thus clears the |List| or |Dictionary|. - Note that {string} is the result of expression and is then + Note that {expr2} is the result of expression and is then used as an expression again. Often it is good to use a |literal-string| to avoid having to double backslashes. + If {expr2} is a |Funcref| it must take two arguments: + 1. the key or the index of the current item. + 2. the value of the current item. + The function must return TRUE if the item should be kept. + Example that keeps the odd items of a list: > + func Odd(idx, val) + return a:idx % 2 == 1 + endfunc + call filter(mylist, function('Odd')) +< The operation is done in-place. If you want a |List| or |Dictionary| to remain unmodified make a copy first: > :let l = filter(copy(mylist), 'v:val =~ "KEEP"') -< Returns {expr}, the |List| or |Dictionary| that was filtered. - When an error is encountered while evaluating {string} no - further items in {expr} are processed. +< Returns {expr1}, the |List| or |Dictionary| that was filtered. + When an error is encountered while evaluating {expr2} no + further items in {expr1} are processed. When {expr2} is a + Funcref errors inside a function are ignored, unless it was + defined with the "abort" flag. finddir({name}[, {path}[, {count}]]) *finddir()* @@ -3545,12 +3625,31 @@ foreground() Move the Vim window to the foreground. Useful when sent from |remote_foreground()| instead. {only in the Win32 GUI and console version} + *funcref()* +funcref({name} [, {arglist}] [, {dict}]) + Just like |function()|, but the returned Funcref will lookup + the function by reference, not by name. This matters when the + function {name} is redefined later. + + Unlike |function()|, {name} must be an existing user function. + Also for autoloaded functions. {name} cannot be a builtin + function. *function()* *E700* *E922* *E923* function({name} [, {arglist}] [, {dict}]) Return a |Funcref| variable that refers to function {name}. {name} can be a user defined function or an internal function. + {name} can also be a Funcref or a partial. When it is a + partial the dict stored in it will be used and the {dict} + argument is not allowed. E.g.: > + let FuncWithArg = function(dict.Func, [arg]) + let Broken = function(dict.Func, [arg], dict) +< + When using the Funcref the function will be found by {name}, + also when it was redefined later. Use |funcref()| to keep the + same function. + When {arglist} or {dict} is present this creates a partial. That mans the argument list and/or the dictionary is stored in the Funcref and will be used when the Funcref is called. @@ -3589,18 +3688,25 @@ function({name} [, {arglist}] [, {dict}]) garbagecollect([{atexit}]) *garbagecollect()* Cleanup unused |Lists| and |Dictionaries| that have circular - references. There is hardly ever a need to invoke this - function, as it is automatically done when Vim runs out of - memory or is waiting for the user to press a key after - 'updatetime'. Items without circular references are always - freed when they become unused. + references. + + There is hardly ever a need to invoke this function, as it is + automatically done when Vim runs out of memory or is waiting + for the user to press a key after 'updatetime'. Items without + circular references are always freed when they become unused. This is useful if you have deleted a very big |List| and/or |Dictionary| with circular references in a script that runs for a long time. + When the optional {atexit} argument is one, garbage collection will also be done when exiting Vim, if it wasn't done before. This is useful when checking for memory leaks. + The garbage collection is not done immediately but only when + it's safe to perform. This is when waiting for the user to + type a character. To force garbage collection immediately use + |test_garbagecollect_now()|. + get({list}, {idx} [, {default}]) *get()* Get item {idx} from |List| {list}. When this item is not available return {default}. Return zero when {default} is @@ -4937,29 +5043,43 @@ log10({expr}) *log10()* < -2.0 -map({expr}, {string}) *map()* - {expr} must be a |List| or a |Dictionary|. - Replace each item in {expr} with the result of evaluating - {string}. - Inside {string} |v:val| has the value of the current item. - For a |Dictionary| |v:key| has the key of the current item - and for a |List| |v:key| has the index of the current item. +map({expr1}, {expr2}) *map()* + {expr1} must be a |List| or a |Dictionary|. + Replace each item in {expr1} with the result of evaluating + {expr2}. {expr2} must be a |string| or |Funcref|. + + If {expr2} is a |string|, inside {expr2} |v:val| has the value + of the current item. For a |Dictionary| |v:key| has the key + of the current item and for a |List| |v:key| has the index of + the current item. Example: > :call map(mylist, '"> " . v:val . " <"') < This puts "> " before and " <" after each item in "mylist". - Note that {string} is the result of an expression and is then + Note that {expr2} is the result of an expression and is then used as an expression again. Often it is good to use a |literal-string| to avoid having to double backslashes. You still have to double ' quotes + If {expr2} is a |Funcref| it is called with two arguments: + 1. The key or the index of the current item. + 2. the value of the current item. + The function must return the new value of the item. Example + that changes each value by "key-value": > + func KeyValue(key, val) + return a:key . '-' . a:val + endfunc + call map(myDict, function('KeyValue')) +< The operation is done in-place. If you want a |List| or |Dictionary| to remain unmodified make a copy first: > :let tlist = map(copy(mylist), ' v:val . "\t"') -< Returns {expr}, the |List| or |Dictionary| that was filtered. - When an error is encountered while evaluating {string} no - further items in {expr} are processed. +< Returns {expr1}, the |List| or |Dictionary| that was filtered. + When an error is encountered while evaluating {expr2} no + further items in {expr1} are processed. When {expr2} is a + Funcref errors inside a function are ignored, unless it was + defined with the "abort" flag. maparg({name}[, {mode} [, {abbr} [, {dict}]]]) *maparg()* @@ -6003,6 +6123,7 @@ screenrow() *screenrow()* The result is a Number, which is the current screen row of the cursor. The top line has number one. This function is mainly used for testing. + Alternatively you can use |winline()|. Note: Same restrictions as with |screencol()|. @@ -7029,6 +7150,14 @@ substitute({expr}, {pat}, {sub}, {flags}) *substitute()* :echo substitute(s, '%\(\x\x\)', \ '\=nr2char("0x" . submatch(1))', 'g') +< When {sub} is a Funcref that function is called, with one + optional argument. Example: > + :echo substitute(s, '%\(\x\x\)', SubNr, 'g') +< The optional argument is a list which contains the whole + matched string and up to nine submatches,like what + |submatch()| returns. Example: > + :echo substitute(s, '\(\x\x\)', {m -> '0x' . m[1]}, 'g') + synID({lnum}, {col}, {trans}) *synID()* The result is a Number, which is the syntax ID at the position {lnum} and {col} in the current window. @@ -7288,6 +7417,12 @@ termopen({cmd}[, {opts}]) {Nvim} *termopen()* See |terminal-emulator| for more information. +test_garbagecollect_now() *test_garbagecollect_now()* + Like garbagecollect(), but executed right away. This must + only be called directly to avoid any structure to exist + internally, and |v:testing| must have been set before calling + any function. + tan({expr}) *tan()* Return the tangent of {expr}, measured in radians, as a |Float| in the range [-inf, inf]. @@ -7379,16 +7514,18 @@ trunc({expr}) *trunc()* < 4.0 type({expr}) *type()* - The result is a Number, depending on the type of {expr}: - Number: 0 - String: 1 - Funcref: 2 - List: 3 - Dictionary: 4 - Float: 5 + The result is a Number representing the type of {expr}. + Instead of using the number directly, it is better to use the + v:t_ variable that has the value: + Number: 0 (|v:t_number|) + String: 1 (|v:t_string|) + Funcref: 2 (|v:t_func|) + List: 3 (|v:t_list|) + Dictionary: 4 (|v:t_dict|) + Float: 5 (|v:t_float|) Boolean: 6 (|v:true| and |v:false|) Null: 7 (|v:null|) - To avoid the magic numbers it should be used this way: > + For backward compatibility, this method can be used: > :if type(myvar) == type(0) :if type(myvar) == type("") :if type(myvar) == type(function("tr")) @@ -7399,6 +7536,8 @@ type({expr}) *type()* < In place of checking for |v:null| type it is better to check for |v:null| directly as it is the only value of this type: > :if myvar is v:null +< To check if the v:t_ variables exist use this: > + :if exists('v:t_number') undofile({name}) *undofile()* Return the name of the undo file that would be used for a file @@ -7806,6 +7945,7 @@ insert_expand Compiled with support for CTRL-X expansion commands in Insert mode. jumplist Compiled with |jumplist| support. keymap Compiled with 'keymap' support. +lambda Compiled with |lambda| support. langmap Compiled with 'langmap' support. libcall Compiled with |libcall()| support. linebreak Compiled with 'linebreak', 'breakat', 'showbreak' and @@ -7957,7 +8097,7 @@ last defined. Example: > See |:verbose-cmd| for more information. *E124* *E125* *E853* *E884* -:fu[nction][!] {name}([arguments]) [range] [abort] [dict] +:fu[nction][!] {name}([arguments]) [range] [abort] [dict] [closure] Define a new function by the name {name}. The name must be made of alphanumeric characters and '_', and must start with a capital or "s:" (see above). Note @@ -8000,6 +8140,28 @@ See |:verbose-cmd| for more information. be invoked through an entry in a |Dictionary|. The local variable "self" will then be set to the dictionary. See |Dictionary-function|. + *:func-closure* *E932* + When the [closure] argument is added, the function + can access variables and arguments from the outer + scope. This is usually called a closure. In this + example Bar() uses "x" from the scope of Foo(). It + remains referenced even after Foo() returns: > + :function! Foo() + : let x = 0 + : function! Bar() closure + : let x += 1 + : return x + : endfunction + : return function('Bar') + :endfunction + + :let F = Foo() + :echo F() +< 1 > + :echo F() +< 2 > + :echo F() +< 3 *function-search-undo* The last used search pattern and the redo command "." @@ -8011,7 +8173,7 @@ See |:verbose-cmd| for more information. :endf[unction] The end of a function definition. Must be on a line by its own, without other commands. - *:delf* *:delfunction* *E130* *E131* + *:delf* *:delfunction* *E130* *E131* *E933* :delf[unction] {name} Delete function {name}. {name} can also be a |Dictionary| entry that is a |Funcref|: > @@ -8047,10 +8209,10 @@ can be 0). "a:000" is set to a |List| that contains these arguments. Note that "a:1" is the same as "a:000[0]". *E742* The a: scope and the variables in it cannot be changed, they are fixed. -However, if a |List| or |Dictionary| is used, you can change their contents. -Thus you can pass a |List| to a function and have the function add an item to -it. If you want to make sure the function cannot change a |List| or -|Dictionary| use |:lockvar|. +However, if a composite type is used, such as |List| or |Dictionary| , you can +change their contents. Thus you can pass a |List| to a function and have the +function add an item to it. If you want to make sure the function cannot +change a |List| or |Dictionary| use |:lockvar|. When not using "...", the number of arguments in a function call must be equal to the number of named arguments. When using "...", the number of arguments @@ -8062,9 +8224,8 @@ until the matching |:endfunction|. It is allowed to define another function inside a function body. *local-variables* -Inside a function variables can be used. These are local variables, which -will disappear when the function returns. Global variables need to be -accessed with "g:". +Inside a function local variables can be used. These will disappear when the +function returns. Global variables need to be accessed with "g:". Example: > :function Table(title, ...) diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index a82e17c857..2d3eaafe63 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -370,9 +370,6 @@ CTRL-G U don't break undo with next left/right cursor *i_CTRL-G_U* within same the line) ----------------------------------------------------------------------- -Note: If the cursor keys take you out of Insert mode, check the 'noesckeys' -option. - The CTRL-O command sometimes has a side effect: If the cursor was beyond the end of the line, it will be put on the last character in the line. In mappings it's often better to use <Esc> (first put an "x" in the text, <Esc> diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt index 8f7dc0dbf0..0954dcb5a7 100644 --- a/runtime/doc/nvim_terminal_emulator.txt +++ b/runtime/doc/nvim_terminal_emulator.txt @@ -4,28 +4,19 @@ NVIM REFERENCE MANUAL by Thiago de Arruda -Embedded terminal emulator *terminal-emulator* +Terminal emulator *terminal-emulator* -1. Introduction |terminal-emulator-intro| -2. Spawning |terminal-emulator-spawning| -3. Input |terminal-emulator-input| -4. Configuration |terminal-emulator-configuration| -5. Status Variables |terminal-emulator-status| +Nvim embeds a VT220/xterm terminal emulator based on libvterm. The terminal is +presented as a special buffer type, asynchronously updated from the virtual +terminal as data is received from the program connected to it. -============================================================================== -1. Introduction *terminal-emulator-intro* - -Nvim offers a mostly complete VT220/xterm terminal emulator. The terminal is -presented as a special buffer type, asynchronously updated to mirror the -virtual terminal display as data is received from the program connected to it. -For most purposes, terminal buffers behave a lot like normal buffers with -'nomodifiable' set. - -The implementation is powered by libvterm, a powerful abstract terminal -emulation library. http://www.leonerd.org.uk/code/libvterm/ +Terminal buffers behave mostly like normal 'nomodifiable' buffers, except: +- Plugins can set 'modifiable' to modify text, but lines cannot be deleted. +- 'scrollback' controls how many off-screen lines are kept. +- Terminal output is followed if the cursor is on the last line. ============================================================================== -2. Spawning *terminal-emulator-spawning* +Spawning *terminal-emulator-spawning* There are 3 ways to create a terminal buffer: @@ -40,34 +31,27 @@ There are 3 ways to create a terminal buffer: Note: The "term://" pattern is handled by a BufReadCmd handler, so the |autocmd-nested| modifier is required to use it in an autocmd. > autocmd VimEnter * nested split term://sh -< This is only mentioned for reference; you should use the |:terminal| - command instead. +< This is only mentioned for reference; use |:terminal| instead. When the terminal spawns the program, the buffer will start to mirror the -terminal display and change its name to `term://$CWD//$PID:$COMMAND`. -Note that |:mksession| will "save" the terminal buffers by restarting all -programs when the session is restored. +terminal display and change its name to `term://{cwd}//{pid}:{cmd}`. +The "term://..." scheme enables |:mksession| to "restore" a terminal buffer by +restarting the {cmd} when the session is loaded. ============================================================================== -3. Input *terminal-emulator-input* - -Sending input is possible by entering terminal mode, which is achieved by -pressing any key that would enter insert mode in a normal buffer (|i| or |a| -for example). The |:terminal| ex command will automatically enter terminal -mode once it's spawned. While in terminal mode, Nvim will forward all keys to -the underlying program. The only exception is the <C-\><C-n> key combo, -which will exit back to normal mode. - -Terminal mode has its own namespace for mappings, which is accessed with the -"t" prefix. It's possible to use terminal mappings to customize interaction -with the terminal. For example, here's how to map <Esc> to exit terminal mode: -> +Input *terminal-emulator-input* + +To send input, enter terminal mode using any command that would enter "insert +mode" in a normal buffer, such as |i| or |:startinsert|. In this mode all keys +except <C-\><C-N> are sent to the underlying program. Use <C-\><C-N> to return +to normal mode. |CTRL-\_CTRL-N| + +Terminal mode has its own |:tnoremap| namespace for mappings, this can be used +to automate any terminal interaction. To map <Esc> to exit terminal mode: > :tnoremap <Esc> <C-\><C-n> < -Navigating to other windows is only possible by exiting to normal mode, which -can be cumbersome with <C-\><C-n> keys. To improve the navigation experience, -you could use the following mappings: -> +Navigating to other windows is only possible in normal mode. For convenience, +you could use these mappings: > :tnoremap <A-h> <C-\><C-n><C-w>h :tnoremap <A-j> <C-\><C-n><C-w>j :tnoremap <A-k> <C-\><C-n><C-w>k @@ -77,11 +61,9 @@ you could use the following mappings: :nnoremap <A-k> <C-w>k :nnoremap <A-l> <C-w>l < -This configuration allows using `Alt+{h,j,k,l}` to navigate between windows no -matter if they are displaying a normal buffer or a terminal buffer in terminal -mode. +Then you can use `Alt+{h,j,k,l}` to navigate between windows from any mode. -Mouse input is also fully supported, and has the following behavior: +Mouse input is supported, and has the following behavior: - If the program has enabled mouse events, the corresponding events will be forwarded to the program. @@ -93,28 +75,23 @@ Mouse input is also fully supported, and has the following behavior: the terminal wont lose focus and the hovered window will be scrolled. ============================================================================== -4. Configuration *terminal-emulator-configuration* +Configuration *terminal-emulator-configuration* + +Options: 'scrollback' +Events: |TermOpen|, |TermClose| +Highlight groups: |hl-TermCursor|, |hl-TermCursorNC| -Terminal buffers can be customized through the following global/buffer-local -variables (set via the |TermOpen| autocmd): +Terminal colors can be customized with these variables: -- `{g,b}:terminal_scrollback_buffer_size`: Scrollback buffer size, between 1 - and 100000 inclusive. The default is 1000. - `{g,b}:terminal_color_$NUM`: The terminal color palette, where `$NUM` is the color index, between 0 and 255 inclusive. This setting only affects UIs with RGB capabilities; for normal terminals the color index is simply forwarded. -The configuration variables are only processed when the terminal starts, which -is why it needs to be done with the |TermOpen| autocmd or setting global -variables before the terminal is started. - -There is also a corresponding |TermClose| event. - -The terminal cursor can be highlighted via |hl-TermCursor| and -|hl-TermCursorNC|. +The `{g,b}:terminal_color_$NUM` variables are processed only when the terminal +starts (after |TermOpen|). ============================================================================== -5. Status Variables *terminal-emulator-status* +Status Variables *terminal-emulator-status* Terminal buffers maintain some information about the terminal in buffer-local variables: @@ -127,11 +104,8 @@ variables: - *b:terminal_job_pid* The PID of the top-level process running in the terminal. -These variables will have a value by the time the TermOpen autocmd runs, and -will continue to have a value for the lifetime of the terminal buffer, making -them suitable for use in 'statusline'. For example, to show the terminal title -as the status line: -> +These variables are initialized before TermOpen, so you can use them in +a local 'statusline'. Example: > :autocmd TermOpen * setlocal statusline=%{b:term_title} < ============================================================================== diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 97d56af369..25dca5fb51 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -2209,18 +2209,6 @@ A jump table for the options with a short description can be found at |Q_op|. Scanf-like description of the format for the lines in the error file (see |errorformat|). - *'esckeys'* *'ek'* *'noesckeys'* *'noek'* -'esckeys' 'ek' boolean (Vim default: on, Vi default: off) - global - Function keys that start with an <Esc> are recognized in Insert - mode. When this option is off, the cursor and function keys cannot be - used in Insert mode if they start with an <Esc>. The advantage of - this is that the single <Esc> is recognized immediately, instead of - after one second. Instead of resetting this option, you might want to - try changing the values for 'timeoutlen' and 'ttimeoutlen'. Note that - when 'esckeys' is off, you can still map anything, but the cursor keys - won't work by default. - *'eventignore'* *'ei'* 'eventignore' 'ei' string (default "") global @@ -4961,6 +4949,16 @@ A jump table for the options with a short description can be found at |Q_op|. be used as the new value for 'scroll'. Reset to half the window height with ":set scroll=0". + *'scrollback'* *'scbk'* +'scrollback' 'scbk' number (default: 1000 + in normal buffers: -1) + local to buffer + Maximum number of lines kept beyond the visible screen. Lines at the + top are deleted if new lines exceed this limit. + Only in |terminal-emulator| buffers. 'buftype' + -1 means "unlimited" for normal buffers, 100000 otherwise. + Minimum is 1. + *'scrollbind'* *'scb'* *'noscrollbind'* *'noscb'* 'scrollbind' 'scb' boolean (default off) local to window @@ -6285,6 +6283,9 @@ A jump table for the options with a short description can be found at |Q_op|. By default, tag searches are case-sensitive. Case is ignored when 'ignorecase' is set and 'tagcase' is "followic", or when 'tagcase' is "ignore". + Also when 'tagcase' is "followscs" and 'smartcase' is set, or + 'tagcase' is "smart", and the pattern contains only lowercase + characters. When 'tagbsearch' is off, tags searching is slower when a full match exists, but faster when no full match exists. Tags in unsorted tags @@ -6302,8 +6303,10 @@ A jump table for the options with a short description can be found at |Q_op|. This option specifies how case is handled when searching the tags file: followic Follow the 'ignorecase' option + followscs Follow the 'smartcase' and 'ignorecase' options ignore Ignore case match Match case + smart Ignore case unless an upper case letter is used *'taglength'* *'tl'* 'taglength' 'tl' number (default 0) diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt index 3cd53e3e50..ac4d7361f4 100644 --- a/runtime/doc/provider.txt +++ b/runtime/doc/provider.txt @@ -49,7 +49,8 @@ Note: The `--upgrade` flag ensures you have the latest version even if PYTHON PROVIDER CONFIGURATION ~ *g:python_host_prog* *g:python3_host_prog* -Program to use for evaluating Python code. Setting this makes startup faster. > +Program to use for evaluating Python code. Setting this makes startup faster. +Also useful for working with virtualenvs. > let g:python_host_prog = '/path/to/python' let g:python3_host_prog = '/path/to/python3' < @@ -61,6 +62,23 @@ To disable Python 2 support: > To disable Python 3 support: > let g:loaded_python3_provider = 1 +PYTHON VIRTUALENVS ~ + +If you plan to use per-project virtualenvs often, you should assign +a virtualenv for Neovim and hard-code the interpreter path via +|g:python_host_prog| (or |g:python3_host_prog|) so that the "neovim" python +package is not required for each Environment. Example using pyenv: > + pyenv install 3.4.4 + pyenv virtualenv 3.4.4 py3neovim + pyenv activate py3neovim + pip install neovim + pyenv which python # Note the path + +The last command reports the interpreter path. Add it to your init.vim: > + let g:python3_host_prog = '/full/path/to/py3neovim/bin/python' + +More information: +https://github.com/zchee/deoplete-jedi/wiki/Setting-up-Python-for-Neovim ============================================================================== Ruby integration *provider-ruby* @@ -80,7 +98,6 @@ RUBY PROVIDER CONFIGURATION ~ To disable Ruby support: > let g:loaded_ruby_provider = 1 - ============================================================================== Clipboard integration *provider-clipboard* *clipboard* @@ -93,7 +110,7 @@ are found in your `$PATH`. - xclip - xsel (newer alternative to xclip) - - pbcopy/pbpaste (Mac OS X) + - pbcopy/pbpaste (macOS) - lemonade (for SSH) https://github.com/pocke/lemonade - doitclient (for SSH) http://www.chiark.greenend.org.uk/~sgtatham/doit/ diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index a7644fab84..2e2bec7637 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -678,7 +678,6 @@ Short explanation of each option: *option-list* 'errorbells' 'eb' ring the bell for error messages 'errorfile' 'ef' name of the errorfile for the QuickFix mode 'errorformat' 'efm' description of the lines in the error file -'esckeys' 'ek' recognize function keys in Insert mode 'eventignore' 'ei' autocommand events that are ignored 'expandtab' 'et' use spaces when <Tab> is inserted 'exrc' 'ex' read .nvimrc and .exrc in the current directory diff --git a/runtime/doc/tagsrch.txt b/runtime/doc/tagsrch.txt index 75d820d072..047abb42cc 100644 --- a/runtime/doc/tagsrch.txt +++ b/runtime/doc/tagsrch.txt @@ -83,14 +83,23 @@ Note that when the current file changes, the priority list is mostly not changed, to avoid confusion when using ":tnext". It is changed when using ":tag {ident}". -The ignore-case matches are not found for a ":tag" command when the -'ignorecase' option is off and 'tagcase' is "followic" or when 'tagcase' is -"match". They are found when a pattern is used (starting with a "/") and for -":tselect", also when 'ignorecase' is off and 'tagcase' is "followic" or when -'tagcase' is "match". Note that using ignore-case tag searching disables -binary searching in the tags file, which causes a slowdown. This can be -avoided by fold-case sorting the tag file. See the 'tagbsearch' option for an -explanation. +The ignore-case matches are not found for a ":tag" command when: +- the 'ignorecase' option is off and 'tagcase' is "followic" +- 'tagcase' is "match" +- 'tagcase' is "smart" and the pattern contains an upper case character. +- 'tagcase' is "followscs" and 'smartcase' option is on and the pattern + contains an upper case character. + +The gnore-case matches are found when: +- a pattern is used (starting with a "/") +- for ":tselect" +- when 'tagcase' is "followic" and 'ignorecase' is off +- when 'tagcase' is "match" +- when 'tagcase' is "followscs" and the 'smartcase' option is off + +Note that using ignore-case tag searching disables binary searching in the +tags file, which causes a slowdown. This can be avoided by fold-case sorting +the tag file. See the 'tagbsearch' option for an explanation. ============================================================================== 2. Tag stack *tag-stack* *tagstack* *E425* @@ -420,13 +429,18 @@ file "tags". It can also be used to access a common tags file. The next file in the list is not used when: - A matching static tag for the current buffer has been found. - A matching global tag has been found. -This also depends on whether case is ignored. Case is ignored when -'ignorecase' is set and 'tagcase' is "followic", or when 'tagcase' is -"ignore". If case is not ignored, and the tags file only has a match without -matching case, the next tags file is searched for a match with matching case. -If no tag with matching case is found, the first match without matching case -is used. If case is ignored, and a matching global tag with or without -matching case is found, this one is used, no further tags files are searched. +This also depends on whether case is ignored. Case is ignored when: +- 'tagcase' is "followic" and 'ignorecase' is set +- 'tagcase' is "ignore" +- 'tagcase' is "smart" and and the pattern only contains lower case + characters. +- 'tagcase' is "followscs" and 'smartcase' is set and and the pattern only + contains lower case characters. +If case is not ignored, and the tags file only has a match without matching +case, the next tags file is searched for a match with matching case. If no +tag with matching case is found, the first match without matching case is +used. If case is ignored, and a matching global tag with or without matching +case is found, this one is used, no further tags files are searched. When a tag file name starts with "./", the '.' is replaced with the path of the current file. This makes it possible to use a tags file in the directory diff --git a/runtime/doc/term.txt b/runtime/doc/term.txt index 08ffee7a2f..137d3a06db 100644 --- a/runtime/doc/term.txt +++ b/runtime/doc/term.txt @@ -70,9 +70,7 @@ for a next character to arrive. If it does not arrive within one second a single <Esc> is assumed. On very slow systems this may fail, causing cursor keys not to work sometimes. If you discover this problem reset the 'timeout' option. Vim will wait for the next character to arrive after an <Esc>. If -you want to enter a single <Esc> you must type it twice. Resetting the -'esckeys' option avoids this problem in Insert mode, but you lose the -possibility to use cursor and function keys in Insert mode. +you want to enter a single <Esc> you must type it twice. Some terminals have confusing codes for the cursor keys. The televideo 925 is such a terminal. It sends a CTRL-H for cursor-left. This would make it diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index 7d08a6f32a..9a2472e394 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -207,23 +207,15 @@ g8 Print the hex values of the bytes used in the :sh[ell] Removed. |vim-differences| {Nvim} *:terminal* *:te* -:te[rminal][!] {cmd} Spawns {cmd} using the current value of 'shell' and - 'shellcmdflag' in a new terminal buffer. This is - equivalent to: > - +:te[rminal][!] {cmd} Execute {cmd} with 'shell' in a |terminal-emulator| + buffer. Equivalent to: > :enew :call termopen('{cmd}') :startinsert < - If no {cmd} is given, 'shellcmdflag' will not be sent - to |termopen()|. - - Like |:enew|, it will fail if the current buffer is - modified, but can be forced with "!". See |termopen()| - and |terminal-emulator|. + See |jobstart()|. - To switch to terminal mode automatically: -> + To enter terminal mode automatically: > autocmd BufEnter term://* startinsert < *:!cmd* *:!* *E34* diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index eeb5e85036..bcc5d03ff0 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -238,6 +238,8 @@ newly allocated memory all over the place) and fail on types which cannot be coerced to strings. See |id()| for more details, currently it uses `printf("%p", {expr})` internally. +|c_CTRL-R| pasting a non-special register into |cmdline| omits the last <CR>. + ============================================================================== 5. Missing legacy features *nvim-features-missing* *if_lua* *if_perl* *if_mzscheme* *if_tcl* @@ -298,6 +300,7 @@ Other options: 'antialias' 'cpoptions' ("g", "w", "H", "*", "-", "j", and all POSIX flags were removed) 'encoding' ("utf-8" is always used) + 'esckeys' 'guioptions' "t" flag was removed *'guipty'* (Nvim uses pipes and PTYs consistently on all platforms.) *'imactivatefunc'* *'imaf'* diff --git a/runtime/optwin.vim b/runtime/optwin.vim index a7b94d73d4..64726937a0 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -524,8 +524,6 @@ endif call <SID>Header("terminal") -call append("$", "esckeys\trecognize keys that start with <Esc> in Insert mode") -call <SID>BinOptionG("ek", &ek) call append("$", "scrolljump\tminimal number of lines to scroll at a time") call append("$", " \tset sj=" . &sj) if has("gui") || has("msdos") || has("win32") diff --git a/scripts/genex_cmds.lua b/scripts/genex_cmds.lua index b1d34fbffd..cb566d46ca 100644 --- a/scripts/genex_cmds.lua +++ b/scripts/genex_cmds.lua @@ -66,7 +66,7 @@ for i, cmd in ipairs(defs) do defsfile:write(string.format([[ [%s] = { .cmd_name = (char_u *) "%s", - .cmd_func = &%s, + .cmd_func = (ex_func_T)&%s, .cmd_argt = %uL, .cmd_addr_type = %i }]], enumname, cmd.command, cmd.func, cmd.flags, cmd.addr_type)) diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index ad0317aa4b..75915372a3 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -136,7 +136,7 @@ preprocess_patch() { 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/\S*\<\%('${na_src}'\)@norm! d/\v(^diff)|%$
' +w +q "$file" # Remove channel.txt, netbeans.txt, os_*.txt, todo.txt, version*.txt, tags - local na_doc='channel\.txt\|netbeans\.txt\|os_\w\+\.txt\|todo\.txt\|version\d\.txt\|tags' + local na_doc='channel\.txt\|netbeans\.txt\|os_\w\+\.txt\|todo\.txt\|version\d\.txt\|tags$' 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/runtime/doc/\%('${na_doc}'\)@norm! d/\v(^diff)|%$
' +w +q "$file" # Remove some testdir/Make_*.mak files diff --git a/src/.valgrind.supp b/src/.valgrind.supp index 8b630fcaaf..cce22bd632 100644 --- a/src/.valgrind.supp +++ b/src/.valgrind.supp @@ -10,7 +10,7 @@ Memcheck:Leak fun:malloc fun:uv_spawn - fun:pipe_process_spawn + fun:libuv_process_spawn fun:process_spawn fun:job_start } diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 5a658691ce..b19a951d5b 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -299,25 +299,6 @@ if(WIN32) install(DIRECTORY ${PROJECT_BINARY_DIR}/windows_runtime_deps/ DESTINATION ${CMAKE_INSTALL_BINDIR}) - foreach(BIN win32yank.exe) - unset(BIN_PATH CACHE) - find_program(BIN_PATH ${BIN}) - if(NOT BIN_PATH) - message(FATAL_ERROR "Unable to find external dependency ${BIN}") - endif() - - add_custom_target(external_${BIN} - COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/windows_runtime_deps - COMMAND ${CMAKE_COMMAND} - "-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}" - -DBINARY="${BIN_PATH}" - -DDST=${PROJECT_BINARY_DIR}/windows_runtime_deps - -P ${PROJECT_SOURCE_DIR}/cmake/WindowsDllCopy.cmake - COMMAND ${CMAKE_COMMAND} -E copy ${BIN_PATH} ${PROJECT_BINARY_DIR}/windows_runtime_deps/ - COMMENT "${BIN_PATH}") - add_dependencies(nvim_runtime_deps "external_${BIN}") - endforeach() - add_custom_target(nvim_dll_deps DEPENDS nvim COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/windows_runtime_deps COMMAND ${CMAKE_COMMAND} @@ -326,6 +307,34 @@ if(WIN32) -DDST=${PROJECT_BINARY_DIR}/windows_runtime_deps -P ${PROJECT_SOURCE_DIR}/cmake/WindowsDllCopy.cmake) add_dependencies(nvim_runtime_deps nvim_dll_deps) + + add_custom_target(external_blobs + COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/windows_runtime_deps/platforms + + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/ca-bundle.crt" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/curl.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/diff.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/tee.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/tidy.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/win32yank.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/D3Dcompiler_47.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/libEGL.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/libgcc_s_dw2-1.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/libGLESV2.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/libstdc++-6.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/libwinpthread-1.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/nvim-qt.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/Qt5Core.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/Qt5Gui.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/Qt5Network.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/Qt5Svg.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/Qt5Widgets.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/platforms/qwindows.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/platforms/ + ) + + add_dependencies(nvim_runtime_deps external_blobs) endif() if(CLANG_ASAN_UBSAN) diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 6f3f654bdc..ba4d005e9a 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -380,7 +380,7 @@ static inline void typval_encode_list_start(EncodedData *const edata, kv_push(edata->stack, ARRAY_OBJ(((Array) { .capacity = len, .size = 0, - .items = xmalloc(len * sizeof(*((Object *)NULL)->data.array.items)), + .items = xmalloc(len * sizeof(*((Object)OBJECT_INIT).data.array.items)), }))); } @@ -422,7 +422,8 @@ static inline void typval_encode_dict_start(EncodedData *const edata, kv_push(edata->stack, DICTIONARY_OBJ(((Dictionary) { .capacity = len, .size = 0, - .items = xmalloc(len * sizeof(*((Object *)NULL)->data.dictionary.items)), + .items = xmalloc(len * sizeof( + *((Object)OBJECT_INIT).data.dictionary.items)), }))); } diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 1732ee0bae..fd0f98c017 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -221,11 +221,10 @@ Object nvim_call_function(String fname, Array args, Error *err) // Call the function typval_T rettv; int dummy; - int r = call_func((char_u *) fname.data, (int) fname.size, - &rettv, (int) args.size, vim_args, + int r = call_func((char_u *)fname.data, (int)fname.size, + &rettv, (int)args.size, vim_args, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, - true, - NULL, NULL); + true, NULL, NULL); if (r == FAIL) { api_set_error(err, Exception, _("Error calling function.")); } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 73d06de964..ccdab16ca1 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -31,14 +31,12 @@ typedef struct { #include "nvim/hashtab.h" // for dict_T #include "nvim/eval_defs.h" -// for proftime_T -#include "nvim/profile.h" // for String #include "nvim/api/private/defs.h" // for Map(K, V) #include "nvim/map.h" -#define MODIFIABLE(buf) (!buf->terminal && buf->b_p_ma) +#define MODIFIABLE(buf) (buf->b_p_ma) /* * Flags for w_valid. @@ -90,36 +88,25 @@ typedef struct { typedef struct window_S win_T; typedef struct wininfo_S wininfo_T; typedef struct frame_S frame_T; -typedef int scid_T; /* script ID */ // for struct memline (it needs memfile_T) #include "nvim/memline_defs.h" - // for struct memfile, bhdr_T, blocknr_T... (it needs buf_T) #include "nvim/memfile_defs.h" -/* - * This is here because regexp_defs.h needs win_T and buf_T. regprog_T is - * used below. - */ +// for regprog_T. Needs win_T and buf_T. #include "nvim/regexp_defs.h" - -// for synstate_T (needs reg_extmatch_T, win_T and buf_T) +// for synstate_T (needs reg_extmatch_T, win_T, buf_T) #include "nvim/syntax_defs.h" - // for signlist_T #include "nvim/sign_defs.h" - // for bufhl_*_T #include "nvim/bufhl_defs.h" typedef Map(linenr_T, bufhl_vec_T) bufhl_info_T; -// for FileID -#include "nvim/os/fs_defs.h" - -// for Terminal -#include "nvim/terminal.h" +#include "nvim/os/fs_defs.h" // for FileID +#include "nvim/terminal.h" // for Terminal /* * The taggy struct is used to store the information about a :tag command. @@ -664,6 +651,7 @@ struct file_buffer { char_u *b_p_qe; ///< 'quoteescape' int b_p_ro; ///< 'readonly' long b_p_sw; ///< 'shiftwidth' + long b_p_scbk; ///< 'scrollback' int b_p_si; ///< 'smartindent' long b_p_sts; ///< 'softtabstop' long b_p_sts_nopaste; ///< b_p_sts saved for paste mode diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 4d150c3230..e33e002787 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1373,17 +1373,17 @@ void getvcols(win_T *wp, pos_T *pos1, pos_T *pos2, colnr_T *left, /// skipwhite: skip over ' ' and '\t'. /// -/// @param q +/// @param[in] q String to skip in. /// /// @return Pointer to character after the skipped whitespace. -char_u* skipwhite(char_u *q) +char_u *skipwhite(const char_u *q) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - char_u *p = q; + const char_u *p = q; while (ascii_iswhite(*p)) { - // skip to next non-white p++; } - return p; + return (char_u *)p; } /// skip over digits diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 53540d21d4..ecc794fb14 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -801,8 +801,8 @@ static int insert_handle_key(InsertState *s) goto normalchar; // insert CTRL-Z as normal char } do_cmdline_cmd("stop"); - s->c = Ctrl_O; - // FALLTHROUGH + ui_cursor_shape(); // may need to update cursor shape + break; case Ctrl_O: // execute one command if (ctrl_x_mode == CTRL_X_OMNI) { @@ -1859,8 +1859,9 @@ static bool check_compl_option(bool dict_opt) : (*curbuf->b_p_tsr == NUL && *p_tsr == NUL)) { ctrl_x_mode = 0; edit_submode = NULL; - msg_attr(dict_opt ? (char_u *)_("'dictionary' option is empty") - : (char_u *)_("'thesaurus' option is empty"), hl_attr(HLF_E)); + msg_attr((dict_opt + ? _("'dictionary' option is empty") + : _("'thesaurus' option is empty")), hl_attr(HLF_E)); if (emsg_silent == 0) { vim_beep(BO_COMPL); setcursor(); @@ -3000,6 +3001,9 @@ static void ins_compl_addleader(int c) { int cc; + if (stop_arrow() == FAIL) { + return; + } if (has_mbyte && (cc = (*mb_char2len)(c)) > 1) { char_u buf[MB_MAXBYTES + 1]; @@ -4801,9 +4805,9 @@ static int ins_complete(int c, bool enable_pum) if (!shortmess(SHM_COMPLETIONMENU)) { if (edit_submode_extra != NULL) { if (!p_smd) { - msg_attr(edit_submode_extra, - edit_submode_highl < HLF_COUNT - ? hl_attr(edit_submode_highl) : 0); + msg_attr((const char *)edit_submode_extra, + (edit_submode_highl < HLF_COUNT + ? hl_attr(edit_submode_highl) : 0)); } } else { msg_clr_cmdline(); // necessary for "noshowmode" diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 61ed815391..a582c56208 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -201,22 +201,26 @@ static garray_T ga_scripts = {0, 0, sizeof(scriptvar_T *), 4, NULL}; static int echo_attr = 0; /* attributes used for ":echo" */ -/* Values for trans_function_name() argument: */ -#define TFN_INT 1 /* internal function name OK */ -#define TFN_QUIET 2 /* no error messages */ -#define TFN_NO_AUTOLOAD 4 /* do not use script autoloading */ - -/* Values for get_lval() flags argument: */ -#define GLV_QUIET TFN_QUIET /* no error messages */ -#define GLV_NO_AUTOLOAD TFN_NO_AUTOLOAD /* do not use script autoloading */ - -/* function flags */ -#define FC_ABORT 1 /* abort function on error */ -#define FC_RANGE 2 /* function accepts range */ -#define FC_DICT 4 /* Dict function, uses "self" */ - -/* The names of packages that once were loaded are remembered. */ -static garray_T ga_loaded = {0, 0, sizeof(char_u *), 4, NULL}; +// Values for trans_function_name() argument: +#define TFN_INT 1 // internal function name OK +#define TFN_QUIET 2 // no error messages +#define TFN_NO_AUTOLOAD 4 // do not use script autoloading +#define TFN_NO_DEREF 8 // do not dereference a Funcref + +// Values for get_lval() flags argument: +#define GLV_QUIET TFN_QUIET // no error messages +#define GLV_NO_AUTOLOAD TFN_NO_AUTOLOAD // do not use script autoloading + +// function flags +#define FC_ABORT 0x01 // abort function on error +#define FC_RANGE 0x02 // function accepts range +#define FC_DICT 0x04 // Dict function, uses "self" +#define FC_CLOSURE 0x08 // closure, uses outer scope variables +#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0 +#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0 + +// The names of packages that once were loaded are remembered. +static garray_T ga_loaded = { 0, 0, sizeof(char_u *), 4, NULL }; // List heads for garbage collection. Although there can be a reference loop // from partial to dict to partial, we don't need to keep track of the partial, @@ -224,38 +228,11 @@ static garray_T ga_loaded = {0, 0, sizeof(char_u *), 4, NULL}; static dict_T *first_dict = NULL; // list of all dicts static list_T *first_list = NULL; // list of all lists +#define FLEN_FIXED 40 + #define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] #define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j] -#define VAR_SHORT_LEN 20 /* short variable name length */ -#define FIXVAR_CNT 12 /* number of fixed variables */ - -/* structure to hold info for a function that is currently being executed. */ -typedef struct funccall_S funccall_T; - -struct funccall_S { - ufunc_T *func; /* function being called */ - int linenr; /* next line to be executed */ - int returned; /* ":return" used */ - struct /* fixed variables for arguments */ - { - dictitem_T var; /* variable (without room for name) */ - char_u room[VAR_SHORT_LEN]; /* room for the name */ - } fixvar[FIXVAR_CNT]; - dict_T l_vars; /* l: local function variables */ - dictitem_T l_vars_var; /* variable for l: scope */ - dict_T l_avars; /* a: argument variables */ - dictitem_T l_avars_var; /* variable for a: scope */ - list_T l_varlist; /* list for a:000 */ - listitem_T l_listitems[MAX_FUNC_ARGS]; /* listitems for a:000 */ - typval_T *rettv; /* return value */ - linenr_T breakpoint; /* next line with breakpoint or zero */ - int dbg_tick; /* debug_tick when breakpoint was set */ - int level; /* top nesting level of executed function */ - proftime_T prof_child; /* time spent in a child */ - funccall_T *caller; /* calling function or NULL */ -}; - /* * Info used by a ":for" loop. */ @@ -267,15 +244,6 @@ typedef struct { } forinfo_T; /* - * Struct 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; - -/* * enum used by var_flavour() */ typedef enum { @@ -390,6 +358,7 @@ static struct vimvar { VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO), VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO), VV(VV_VIM_DID_ENTER, "vim_did_enter", VAR_NUMBER, VV_RO), + VV(VV_TESTING, "testing", VAR_NUMBER, 0), VV(VV_TYPE_NUMBER, "t_number", VAR_NUMBER, VV_RO), VV(VV_TYPE_STRING, "t_string", VAR_NUMBER, VV_RO), VV(VV_TYPE_FUNC, "t_func", VAR_NUMBER, VV_RO), @@ -649,12 +618,11 @@ void eval_clear(void) xfree(SCRIPT_SV(i)); ga_clear(&ga_scripts); - /* unreferenced lists and dicts */ - (void)garbage_collect(); + // unreferenced lists and dicts + (void)garbage_collect(false); - /* functions */ + // functions free_all_functions(); - hash_clear(&func_hashtab); } #endif @@ -1230,8 +1198,8 @@ int call_vim_function( ++sandbox; } - rettv->v_type = VAR_UNKNOWN; /* clear_tv() uses this */ - ret = call_func(func, (int)STRLEN(func), rettv, argc, argvars, + rettv->v_type = VAR_UNKNOWN; // clear_tv() uses this + ret = call_func(func, (int)STRLEN(func), rettv, argc, argvars, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &doesrange, true, NULL, NULL); if (safe) { @@ -1455,13 +1423,13 @@ void ex_let(exarg_T *eap) if (*expr != '=' && !(vim_strchr((char_u *)"+-.", *expr) != NULL && expr[1] == '=')) { // ":let" without "=": list variables - if (*arg == '[') + if (*arg == '[') { EMSG(_(e_invarg)); - else if (!ends_excmd(*arg)) - /* ":let var1 var2" */ - arg = list_arg_vars(eap, arg, &first); - else if (!eap->skip) { - /* ":let" */ + } else if (!ends_excmd(*arg)) { + // ":let var1 var2" + arg = (char_u *)list_arg_vars(eap, (const char *)arg, &first); + } else if (!eap->skip) { + // ":let" list_glob_vars(&first); list_buf_vars(&first); list_win_vars(&first); @@ -1645,7 +1613,8 @@ static char_u *skip_var_one(char_u *arg) * List variables for hashtab "ht" with prefix "prefix". * If "empty" is TRUE also list NULL strings as empty strings. */ -static void list_hashtable_vars(hashtab_T *ht, char_u *prefix, int empty, int *first) +static void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty, + int *first) { hashitem_T *hi; dictitem_T *di; @@ -1654,11 +1623,12 @@ static void list_hashtable_vars(hashtab_T *ht, char_u *prefix, int empty, int *f todo = (int)ht->ht_used; for (hi = ht->ht_array; todo > 0 && !got_int; ++hi) { if (!HASHITEM_EMPTY(hi)) { - --todo; + todo--; di = HI2DI(hi); if (empty || di->di_tv.v_type != VAR_STRING - || di->di_tv.vval.v_string != NULL) + || di->di_tv.vval.v_string != NULL) { list_one_var(di, prefix, first); + } } } } @@ -1668,7 +1638,7 @@ static void list_hashtable_vars(hashtab_T *ht, char_u *prefix, int empty, int *f */ static void list_glob_vars(int *first) { - list_hashtable_vars(&globvarht, (char_u *)"", TRUE, first); + list_hashtable_vars(&globvarht, "", true, first); } /* @@ -1676,14 +1646,13 @@ static void list_glob_vars(int *first) */ static void list_buf_vars(int *first) { - char_u numbuf[NUMBUFLEN]; + char numbuf[NUMBUFLEN]; - list_hashtable_vars(&curbuf->b_vars->dv_hashtab, (char_u *)"b:", - TRUE, first); + list_hashtable_vars(&curbuf->b_vars->dv_hashtab, "b:", true, first); - sprintf((char *)numbuf, "%" PRId64, (int64_t)curbuf->b_changedtick); - list_one_var_a((char_u *)"b:", (char_u *)"changedtick", VAR_NUMBER, - numbuf, first); + snprintf(numbuf, sizeof(numbuf), "%d", curbuf->b_changedtick); + list_one_var_a("b:", "changedtick", sizeof("changedtick") - 1, VAR_NUMBER, + numbuf, first); } /* @@ -1691,8 +1660,7 @@ static void list_buf_vars(int *first) */ static void list_win_vars(int *first) { - list_hashtable_vars(&curwin->w_vars->dv_hashtab, - (char_u *)"w:", TRUE, first); + list_hashtable_vars(&curwin->w_vars->dv_hashtab, "w:", true, first); } /* @@ -1700,8 +1668,7 @@ static void list_win_vars(int *first) */ static void list_tab_vars(int *first) { - list_hashtable_vars(&curtab->tp_vars->dv_hashtab, - (char_u *)"t:", TRUE, first); + list_hashtable_vars(&curtab->tp_vars->dv_hashtab, "t:", true, first); } /* @@ -1709,7 +1676,7 @@ static void list_tab_vars(int *first) */ static void list_vim_vars(int *first) { - list_hashtable_vars(&vimvarht, (char_u *)"v:", FALSE, first); + list_hashtable_vars(&vimvarht, "v:", false, first); } /* @@ -1717,9 +1684,9 @@ static void list_vim_vars(int *first) */ static void list_script_vars(int *first) { - if (current_SID > 0 && current_SID <= ga_scripts.ga_len) - list_hashtable_vars(&SCRIPT_VARS(current_SID), - (char_u *)"s:", FALSE, first); + if (current_SID > 0 && current_SID <= ga_scripts.ga_len) { + list_hashtable_vars(&SCRIPT_VARS(current_SID), "s:", false, first); + } } /* @@ -1727,36 +1694,37 @@ static void list_script_vars(int *first) */ static void list_func_vars(int *first) { - if (current_funccal != NULL) - list_hashtable_vars(¤t_funccal->l_vars.dv_hashtab, - (char_u *)"l:", FALSE, first); + if (current_funccal != NULL) { + list_hashtable_vars(¤t_funccal->l_vars.dv_hashtab, "l:", false, + first); + } } /* * List variables in "arg". */ -static char_u *list_arg_vars(exarg_T *eap, char_u *arg, int *first) +static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first) { int error = FALSE; int len; - char_u *name; - char_u *name_start; - char_u *arg_subsc; - char_u *tofree; + const char *name; + const char *name_start; typval_T tv; while (!ends_excmd(*arg) && !got_int) { if (error || eap->skip) { - arg = find_name_end(arg, NULL, NULL, FNE_INCL_BR | FNE_CHECK_START); + arg = (const char *)find_name_end((char_u *)arg, NULL, NULL, + FNE_INCL_BR | FNE_CHECK_START); if (!ascii_iswhite(*arg) && !ends_excmd(*arg)) { emsg_severe = TRUE; EMSG(_(e_trailing)); break; } } else { - /* get_name_len() takes care of expanding curly braces */ + // get_name_len() takes care of expanding curly braces name_start = name = arg; - len = get_name_len(&arg, &tofree, TRUE, TRUE); + char *tofree; + len = get_name_len(&arg, &tofree, true, true); if (len <= 0) { /* This is mainly to keep test 49 working: when expanding * curly braces fails overrule the exception error message. */ @@ -1770,14 +1738,15 @@ static char_u *list_arg_vars(exarg_T *eap, char_u *arg, int *first) if (tofree != NULL) { name = tofree; } - if (get_var_tv(name, len, &tv, NULL, true, false) == FAIL) { + if (get_var_tv((const char *)name, len, &tv, NULL, true, false) + == FAIL) { error = true; } else { // handle d.key, l[idx], f(expr) - arg_subsc = arg; - if (handle_subscript(&arg, &tv, TRUE, TRUE) == FAIL) - error = TRUE; - else { + const char *const arg_subsc = arg; + if (handle_subscript(&arg, &tv, true, true) == FAIL) { + error = true; + } else { if (arg == arg_subsc && len == 2 && name[1] == ':') { switch (*name) { case 'g': list_glob_vars(first); break; @@ -1791,17 +1760,15 @@ static char_u *list_arg_vars(exarg_T *eap, char_u *arg, int *first) EMSG2(_("E738: Can't list variables for %s"), name); } } else { - int c; - - char_u *s = (char_u *) encode_tv2echo(&tv, NULL); - c = *arg; - *arg = NUL; - list_one_var_a((char_u *)"", - arg == arg_subsc ? name : name_start, - tv.v_type, - s == NULL ? (char_u *)"" : s, - first); - *arg = c; + char *const s = encode_tv2echo(&tv, NULL); + const char *const used_name = (arg == arg_subsc + ? name + : name_start); + const ptrdiff_t name_size = (used_name == tofree + ? (ptrdiff_t)strlen(used_name) + : (arg - used_name)); + list_one_var_a("", used_name, name_size, + tv.v_type, s == NULL ? "" : s, first); xfree(s); } clear_tv(&tv); @@ -1812,7 +1779,7 @@ static char_u *list_arg_vars(exarg_T *eap, char_u *arg, int *first) xfree(tofree); } - arg = skipwhite(arg); + arg = (const char *)skipwhite((const char_u *)arg); } return arg; @@ -1832,9 +1799,7 @@ ex_let_one ( char_u *op /* "+", "-", "." or NULL*/ ) { - int c1; char_u *name; - char_u *p; char_u *arg_end = NULL; int len; int opt_flags; @@ -1848,18 +1813,18 @@ ex_let_one ( ++arg; name = arg; len = get_env_len(&arg); - if (len == 0) + if (len == 0) { EMSG2(_(e_invarg2), name - 1); - else { - if (op != NULL && (*op == '+' || *op == '-')) + } else { + if (op != NULL && (*op == '+' || *op == '-')) { EMSG2(_(e_letwrong), op); - else if (endchars != NULL - && vim_strchr(endchars, *skipwhite(arg)) == NULL) + } else if (endchars != NULL + && vim_strchr(endchars, *skipwhite(arg)) == NULL) { EMSG(_(e_letunexp)); - else if (!check_secure()) { - c1 = name[len]; + } else if (!check_secure()) { + const char_u c1 = name[len]; name[len] = NUL; - p = get_tv_string_chk(tv); + char_u *p = get_tv_string_chk(tv); if (p != NULL && op != NULL && *op == '.') { char *s = vim_getenv((char *)name); @@ -1883,43 +1848,41 @@ ex_let_one ( xfree(tofree); } } - } - /* - * ":let &option = expr": Set option value. - * ":let &l:option = expr": Set local option value. - * ":let &g:option = expr": Set global option value. - */ - else if (*arg == '&') { - /* Find the end of the name. */ - p = find_option_end(&arg, &opt_flags); - if (p == NULL || (endchars != NULL - && vim_strchr(endchars, *skipwhite(p)) == NULL)) + // ":let &option = expr": Set option value. + // ":let &l:option = expr": Set local option value. + // ":let &g:option = expr": Set global option value. + } else if (*arg == '&') { + // Find the end of the name. + char *const p = (char *)find_option_end((const char **)&arg, &opt_flags); + if (p == NULL + || (endchars != NULL + && vim_strchr(endchars, *skipwhite((const char_u *)p)) == NULL)) { EMSG(_(e_letunexp)); - else { + } else { long n; int opt_type; long numval; - char_u *stringval = NULL; - char_u *s; + char_u *stringval = NULL; + char_u *s; - c1 = *p; + const char c1 = *p; *p = NUL; n = get_tv_number(tv); s = get_tv_string_chk(tv); /* != NULL if number or string */ if (s != NULL && op != NULL && *op != '=') { - opt_type = get_option_value(arg, &numval, - &stringval, opt_flags); + opt_type = get_option_value(arg, &numval, &stringval, opt_flags); if ((opt_type == 1 && *op == '.') - || (opt_type == 0 && *op != '.')) + || (opt_type == 0 && *op != '.')) { EMSG2(_(e_letwrong), op); - else { - if (opt_type == 1) { /* number */ - if (*op == '+') + } else { + if (opt_type == 1) { // number + if (*op == '+') { n = numval + n; - else + } else { n = numval - n; - } else if (opt_type == 0 && stringval != NULL) { /* string */ + } + } else if (opt_type == 0 && stringval != NULL) { // string s = concat_str(stringval, s); xfree(stringval); stringval = s; @@ -1928,7 +1891,7 @@ ex_let_one ( } if (s != NULL) { set_option_value(arg, n, s, opt_flags); - arg_end = p; + arg_end = (char_u *)p; } *p = c1; xfree(stringval); @@ -1948,7 +1911,7 @@ ex_let_one ( char_u *ptofree = NULL; char_u *s; - p = get_tv_string_chk(tv); + char_u *p = get_tv_string_chk(tv); if (p != NULL && op != NULL && *op == '.') { s = get_reg_contents(*arg == '@' ? '"' : *arg, kGRegExprSrc); if (s != NULL) { @@ -1970,7 +1933,7 @@ ex_let_one ( else if (eval_isnamec1(*arg) || *arg == '{') { lval_T lv; - p = get_lval(arg, tv, &lv, FALSE, FALSE, 0, FNE_CHECK_START); + char_u *const p = get_lval(arg, tv, &lv, false, false, 0, FNE_CHECK_START); if (p != NULL && lv.ll_name != NULL) { if (endchars != NULL && vim_strchr(endchars, *skipwhite(p)) == NULL) EMSG(_(e_letunexp)); @@ -2080,9 +2043,11 @@ get_lval ( cc = *p; *p = NUL; - v = find_var(lp->ll_name, &ht, flags & GLV_NO_AUTOLOAD); - if (v == NULL && !quiet) + v = find_var((const char *)lp->ll_name, STRLEN(lp->ll_name), &ht, + flags & GLV_NO_AUTOLOAD); + if (v == NULL && !quiet) { EMSG2(_(e_undefvar), lp->ll_name); + } *p = cc; if (v == NULL) return NULL; @@ -2342,7 +2307,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int copy, ch // handle +=, -= and .= di = NULL; - if (get_var_tv(lp->ll_name, (int)STRLEN(lp->ll_name), + if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name), &tv, &di, true, false) == OK) { if ((di == NULL || (!var_check_ro(di->di_flags, lp->ll_name, false) @@ -2818,7 +2783,7 @@ void ex_call(exarg_T *eap) // contents. For VAR_PARTIAL get its partial, unless we already have one // from trans_function_name(). len = (int)STRLEN(tofree); - name = deref_func_name(tofree, &len, + name = deref_func_name((const char *)tofree, &len, partial != NULL ? NULL : &partial, false); /* Skip white space to allow ":call func ()". Not good, but required for @@ -2856,9 +2821,10 @@ void ex_call(exarg_T *eap) break; } - /* Handle a function returning a Funcref, Dictionary or List. */ - if (handle_subscript(&arg, &rettv, !eap->skip, TRUE) == FAIL) { - failed = TRUE; + // Handle a function returning a Funcref, Dictionary or List. + if (handle_subscript((const char **)&arg, &rettv, !eap->skip, true) + == FAIL) { + failed = true; break; } @@ -3047,7 +3013,8 @@ int do_unlet(char_u *name, int forceit) dict_T *d; dictitem_T *di; dict_T *dict; - ht = find_var_ht_dict(name, &varname, &dict); + ht = find_var_ht_dict((const char *)name, STRLEN(name), + (const char **)&varname, &dict); if (ht != NULL && *varname != NUL) { if (ht == &globvarht) { @@ -3058,7 +3025,7 @@ int do_unlet(char_u *name, int forceit) } else if (ht == &compat_hashtab) { d = &vimvardict; } else { - di = find_var_in_ht(ht, *name, (char_u *)"", false); + di = find_var_in_ht(ht, *name, "", 0, false); d = di->di_tv.vval.v_dict; } if (d == NULL) { @@ -3066,7 +3033,10 @@ int do_unlet(char_u *name, int forceit) return FAIL; } hi = hash_find(ht, varname); - if (!HASHITEM_EMPTY(hi)) { + if (HASHITEM_EMPTY(hi)) { + hi = find_hi_in_scoped_ht((const char *)name, &ht); + } + if (hi != NULL && !HASHITEM_EMPTY(hi)) { di = HI2DI(hi); if (var_check_fixed(di->di_flags, name, false) || var_check_ro(di->di_flags, name, false) @@ -3118,18 +3088,19 @@ static int do_lock_var(lval_T *lp, char_u *name_end, int deep, int lock) cc = *name_end; *name_end = NUL; - /* Normal name or expanded name. */ - if (check_changedtick(lp->ll_name)) + // Normal name or expanded name. + if (check_changedtick(lp->ll_name)) { ret = FAIL; - else { - di = find_var(lp->ll_name, NULL, TRUE); - if (di == NULL) + } else { + di = find_var((const char *)lp->ll_name, STRLEN(lp->ll_name), NULL, true); + if (di == NULL) { ret = FAIL; - else { - if (lock) + } else { + if (lock) { di->di_flags |= DI_FLAGS_LOCK; - else + } else { di->di_flags &= ~DI_FLAGS_LOCK; + } item_lock(&di->di_tv, deep, lock); } } @@ -4301,14 +4272,19 @@ static int eval7( case '[': ret = get_list_tv(arg, rettv, evaluate); break; + // Lambda: {arg, arg -> expr} // Dictionary: {key: val, key: val} - case '{': ret = get_dict_tv(arg, rettv, evaluate); + case '{': ret = get_lambda_tv(arg, rettv, evaluate); + if (ret == NOTDONE) { + ret = get_dict_tv(arg, rettv, evaluate); + } break; // Option value: &name - case '&': ret = get_option_tv(arg, rettv, evaluate); + case '&': { + ret = get_option_tv((const char **)arg, rettv, evaluate); break; - + } // Environment variable: $VAR. case '$': ret = get_env_tv(arg, rettv, evaluate); break; @@ -4344,7 +4320,7 @@ static int eval7( // Must be a variable or function name. // Can also be a curly-braces kind of name: {expr}. s = *arg; - len = get_name_len(arg, &alias, evaluate, true); + len = get_name_len((const char **)arg, (char **)&alias, evaluate, true); if (alias != NULL) { s = alias; } @@ -4354,9 +4330,14 @@ static int eval7( } else { if (**arg == '(') { // recursive! partial_T *partial; + + if (!evaluate) { + check_vars((const char *)s, len); + } + // If "s" is the name of a variable of type VAR_FUNC // use its contents. - s = deref_func_name(s, &len, &partial, !evaluate); + s = deref_func_name((const char *)s, &len, &partial, !evaluate); // Invoke the function. ret = get_func_tv(s, len, rettv, arg, @@ -4381,8 +4362,9 @@ static int eval7( ret = FAIL; } } else if (evaluate) { - ret = get_var_tv(s, len, rettv, NULL, true, false); + ret = get_var_tv((const char *)s, len, rettv, NULL, true, false); } else { + check_vars((const char *)s, len); ret = OK; } } @@ -4394,7 +4376,7 @@ static int eval7( // Handle following '[', '(' and '.' for expr[expr], expr.name, // expr(expr). if (ret == OK) { - ret = handle_subscript(arg, rettv, evaluate, true); + ret = handle_subscript((const char **)arg, rettv, evaluate, true); } // Apply logical NOT and unary '-', from right to left, ignore '+'. @@ -4695,35 +4677,32 @@ eval_index ( return OK; } -/* - * Get an option value. - * "arg" points to the '&' or '+' before the option name. - * "arg" is advanced to character after the option name. - * Return OK or FAIL. - */ -static int -get_option_tv ( - char_u **arg, - typval_T *rettv, /* when NULL, only check if option exists */ - int evaluate -) +/// Get an option value +/// +/// @param[in,out] arg Points to the '&' or '+' before the option name. Is +/// advanced to the character after the option name. +/// @param[out] rettv Location where result is saved. +/// @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) + FUNC_ATTR_NONNULL_ARG(1) { - char_u *option_end; long numval; char_u *stringval; int opt_type; int c; - int working = (**arg == '+'); /* has("+option") */ + bool working = (**arg == '+'); // has("+option") int ret = OK; int opt_flags; - /* - * Isolate the option name and find its value. - */ - option_end = find_option_end(arg, &opt_flags); + // Isolate the option name and find its value. + char *option_end = (char *)find_option_end(arg, &opt_flags); if (option_end == NULL) { - if (rettv != NULL) + if (rettv != NULL) { EMSG2(_("E112: Option name missing: %s"), *arg); + } return FAIL; } @@ -4734,8 +4713,8 @@ get_option_tv ( c = *option_end; *option_end = NUL; - opt_type = get_option_value(*arg, &numval, - rettv == NULL ? NULL : &stringval, opt_flags); + opt_type = get_option_value((char_u *)(*arg), &numval, + rettv == NULL ? NULL : &stringval, opt_flags); if (opt_type == -3) { /* invalid name */ if (rettv != NULL) @@ -4939,6 +4918,15 @@ static int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate) return OK; } +/// @return the function name of the partial. +char_u *partial_name(partial_T *pt) +{ + if (pt->pt_name != NULL) { + return pt->pt_name; + } + return pt->pt_func->uf_name; +} + static void partial_free(partial_T *pt) { for (int i = 0; i < pt->pt_argc; i++) { @@ -4946,8 +4934,12 @@ static void partial_free(partial_T *pt) } xfree(pt->pt_argv); dict_unref(pt->pt_dict); - func_unref(pt->pt_name); - xfree(pt->pt_name); + if (pt->pt_name != NULL) { + func_unref(pt->pt_name); + xfree(pt->pt_name); + } else { + func_ptr_unref(pt->pt_func); + } xfree(pt); } @@ -5211,12 +5203,12 @@ static bool func_equal( // empty and NULL function name considered the same s1 = tv1->v_type == VAR_FUNC ? tv1->vval.v_string - : tv1->vval.v_partial->pt_name; + : partial_name(tv1->vval.v_partial); if (s1 != NULL && *s1 == NUL) { s1 = NULL; } s2 = tv2->v_type == VAR_FUNC ? tv2->vval.v_string - : tv2->vval.v_partial->pt_name; + : partial_name(tv2->vval.v_partial); if (s2 != NULL && *s2 == NUL) { s2 = NULL; } @@ -5285,7 +5277,8 @@ tv_equal ( return TRUE; } - // For VAR_FUNC and VAR_PARTIAL only compare the function name. + // For VAR_FUNC and VAR_PARTIAL compare the function name, bound dict and + // arguments. if ((tv1->v_type == VAR_FUNC || (tv1->v_type == VAR_PARTIAL && tv1->vval.v_partial != NULL)) && (tv2->v_type == VAR_FUNC @@ -5814,6 +5807,9 @@ int get_copyID(void) return current_copyID; } +// Used by get_func_tv() +static garray_T funcargs = GA_EMPTY_INIT_VALUE; + /* * Garbage collection for lists and dictionaries. * @@ -5836,19 +5832,22 @@ int get_copyID(void) /// Do garbage collection for lists and dicts. /// +/// @param testing true if called from test_garbagecollect_now(). /// @returns true if some memory was freed. -bool garbage_collect(void) +bool garbage_collect(bool testing) { bool abort = false; #define ABORTING(func) abort = abort || func - // Only do this once. - want_garbage_collect = false; - may_garbage_collect = false; - garbage_collect_at_exit = false; + if (!testing) { + // Only do this once. + want_garbage_collect = false; + may_garbage_collect = false; + garbage_collect_at_exit = false; + } - // We advance by two because we add one for items referenced through - // previous_funccal. + // We advance by two (COPYID_INC) because we add one for items referenced + // through previous_funccal. const int copyID = get_copyID(); // 1. Go through all accessible variables and mark all lists and dicts @@ -5858,6 +5857,7 @@ bool garbage_collect(void) // referenced through previous_funccal. This must be first, because if // the item is referenced elsewhere the funccal must not be freed. for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) { + fc->fc_copyID = copyID + 1; ABORTING(set_ref_in_ht)(&fc->l_vars.dv_hashtab, copyID + 1, NULL); ABORTING(set_ref_in_ht)(&fc->l_avars.dv_hashtab, copyID + 1, NULL); } @@ -5933,10 +5933,14 @@ bool garbage_collect(void) // function-local variables for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) { + fc->fc_copyID = copyID; ABORTING(set_ref_in_ht)(&fc->l_vars.dv_hashtab, copyID, NULL); ABORTING(set_ref_in_ht)(&fc->l_avars.dv_hashtab, copyID, NULL); } + // named functions (matters for closures) + ABORTING(set_ref_in_functions(copyID)); + // Jobs { TerminalJobData *data; @@ -5955,6 +5959,12 @@ bool garbage_collect(void) }) } + // function call arguments, if v:testing is set. + for (int i = 0; i < funcargs.ga_len; i++) { + ABORTING(set_ref_in_item)(((typval_T **)funcargs.ga_data)[i], + copyID, NULL, NULL); + } + // v: vars ABORTING(set_ref_in_ht)(&vimvarht, copyID, NULL); @@ -6009,7 +6019,7 @@ bool garbage_collect(void) if (did_free_funccal) { // When a funccal was freed some more items might be garbage // collected, so run again. - (void)garbage_collect(); + (void)garbage_collect(testing); } } else if (p_verbose > 0) { verb_msg((char_u *)_( @@ -6238,6 +6248,7 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, // A partial does not have a copyID, because it cannot contain itself. if (pt != NULL) { + abort = set_ref_in_func(pt->pt_name, pt->pt_func, copyID); if (pt->pt_dict != NULL) { typval_T dtv; @@ -6254,6 +6265,8 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, break; } case VAR_FUNC: + abort = set_ref_in_func(tv->vval.v_string, NULL, copyID); + break; case VAR_UNKNOWN: case VAR_SPECIAL: case VAR_FLOAT: @@ -6265,6 +6278,29 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, return abort; } +/// Set "copyID" in all functions available by name. +bool set_ref_in_functions(int copyID) +{ + int todo; + hashitem_T *hi = NULL; + bool abort = false; + ufunc_T *fp; + + todo = (int)func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) { + if (!HASHITEM_EMPTY(hi)) { + todo--; + fp = HI2UF(hi); + if (!func_name_refcount(fp->uf_name)) { + abort = abort || set_ref_in_func(NULL, fp, copyID); + } + } + } + return abort; +} + + + /// Mark all lists and dicts referenced in given mark /// /// @returns true if setting references failed somehow. @@ -6311,9 +6347,20 @@ static inline bool set_ref_dict(dict_T *dict, int copyID) return false; } -/* - * Allocate an empty header for a dictionary. - */ +static bool set_ref_in_funccal(funccall_T *fc, int copyID) +{ + bool abort = false; + + if (fc->fc_copyID != copyID) { + fc->fc_copyID = copyID; + abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); + abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL); + abort = abort || set_ref_in_func(NULL, fc->func, copyID); + } + return abort; +} + +/// Allocate an empty header for a dictionary. dict_T *dict_alloc(void) FUNC_ATTR_NONNULL_RET { dict_T *d = xmalloc(sizeof(dict_T)); @@ -6858,6 +6905,232 @@ failret: return OK; } +/// Get function arguments. +static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, + int *varargs, bool skip) +{ + bool mustend = false; + char_u *arg = *argp; + char_u *p = arg; + int c; + int i; + + if (newargs != NULL) { + ga_init(newargs, (int)sizeof(char_u *), 3); + } + + if (varargs != NULL) { + *varargs = false; + } + + // Isolate the arguments: "arg1, arg2, ...)" + while (*p != endchar) { + if (p[0] == '.' && p[1] == '.' && p[2] == '.') { + if (varargs != NULL) { + *varargs = true; + } + p += 3; + mustend = true; + } else { + arg = p; + while (ASCII_ISALNUM(*p) || *p == '_') { + p++; + } + if (arg == p || isdigit(*arg) + || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0) + || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) { + if (!skip) { + EMSG2(_("E125: Illegal argument: %s"), arg); + } + break; + } + if (newargs != NULL) { + ga_grow(newargs, 1); + c = *p; + *p = NUL; + arg = vim_strsave(arg); + if (arg == NULL) { + *p = c; + goto err_ret; + } + + // Check for duplicate argument name. + for (i = 0; i < newargs->ga_len; i++) { + if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0) { + EMSG2(_("E853: Duplicate argument name: %s"), arg); + xfree(arg); + goto err_ret; + } + } + ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg; + newargs->ga_len++; + + *p = c; + } + if (*p == ',') { + p++; + } else { + mustend = true; + } + } + p = skipwhite(p); + if (mustend && *p != endchar) { + if (!skip) { + EMSG2(_(e_invarg2), *argp); + } + break; + } + } + if (*p != endchar) { + goto err_ret; + } + p++; // skip "endchar" + + *argp = p; + return OK; + +err_ret: + if (newargs != NULL) { + ga_clear_strings(newargs); + } + return FAIL; +} + +/// Register function "fp" as using "current_funccal" as its scope. +static void register_closure(ufunc_T *fp) +{ + if (fp->uf_scoped == current_funccal) { + // no change + return; + } + funccal_unref(fp->uf_scoped, fp, false); + fp->uf_scoped = current_funccal; + current_funccal->fc_refcount++; + ga_grow(¤t_funccal->fc_funcs, 1); + ((ufunc_T **)current_funccal->fc_funcs.ga_data) + [current_funccal->fc_funcs.ga_len++] = fp; +} + +/// Parse a lambda expression and get a Funcref from "*arg". +/// +/// @return OK or FAIL. Returns NOTDONE for dict or {expr}. +static int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) +{ + garray_T newargs = GA_EMPTY_INIT_VALUE; + garray_T *pnewargs; + ufunc_T *fp = NULL; + int varargs; + int ret; + char_u *start = skipwhite(*arg + 1); + char_u *s, *e; + static int lambda_no = 0; + int *old_eval_lavars = eval_lavars_used; + int eval_lavars = false; + + // First, check if this is a lambda expression. "->" must exists. + ret = get_function_args(&start, '-', NULL, NULL, true); + if (ret == FAIL || *start != '>') { + return NOTDONE; + } + + // Parse the arguments again. + if (evaluate) { + pnewargs = &newargs; + } else { + pnewargs = NULL; + } + *arg = skipwhite(*arg + 1); + ret = get_function_args(arg, '-', pnewargs, &varargs, false); + if (ret == FAIL || **arg != '>') { + goto errret; + } + + // Set up a flag for checking local variables and arguments. + if (evaluate) { + eval_lavars_used = &eval_lavars; + } + + // Get the start and the end of the expression. + *arg = skipwhite(*arg + 1); + s = *arg; + ret = skip_expr(arg); + if (ret == FAIL) { + goto errret; + } + e = *arg; + *arg = skipwhite(*arg); + if (**arg != '}') { + goto errret; + } + (*arg)++; + + if (evaluate) { + int len, flags = 0; + char_u *p; + char_u name[20]; + partial_T *pt; + garray_T newlines; + + lambda_no++; + snprintf((char *)name, sizeof(name), "<lambda>%d", lambda_no); + + fp = (ufunc_T *)xcalloc(1, sizeof(ufunc_T) + STRLEN(name)); + pt = (partial_T *)xcalloc(1, sizeof(partial_T)); + if (pt == NULL) { + xfree(fp); + goto errret; + } + + ga_init(&newlines, (int)sizeof(char_u *), 1); + ga_grow(&newlines, 1); + + // Add "return " before the expression. + len = 7 + e - s + 1; + p = (char_u *)xmalloc(len); + ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; + STRCPY(p, "return "); + STRLCPY(p + 7, s, e - s + 1); + + fp->uf_refcount = 1; + STRCPY(fp->uf_name, name); + hash_add(&func_hashtab, UF2HIKEY(fp)); + fp->uf_args = newargs; + fp->uf_lines = newlines; + if (current_funccal != NULL && eval_lavars) { + flags |= FC_CLOSURE; + register_closure(fp); + } else { + fp->uf_scoped = NULL; + } + + fp->uf_tml_count = NULL; + fp->uf_tml_total = NULL; + fp->uf_tml_self = NULL; + fp->uf_profiling = false; + if (prof_def_func()) { + func_do_profile(fp); + } + fp->uf_varargs = true; + fp->uf_flags = flags; + fp->uf_calls = 0; + fp->uf_script_ID = current_SID; + + pt->pt_func = fp; + pt->pt_refcount = 1; + rettv->vval.v_partial = pt; + rettv->v_type = VAR_PARTIAL; + } + + eval_lavars_used = old_eval_lavars; + return OK; + +errret: + ga_clear_strings(&newargs); + xfree(fp); + eval_lavars_used = old_eval_lavars; + return FAIL; +} + /// Convert the string to a floating point number /// /// This uses strtod(). setlocale(LC_NUMERIC, "C") has been used earlier to @@ -6980,9 +7253,6 @@ char_u *get_expr_name(expand_T *xp, int idx) return get_user_var_name(xp, ++intidx); } - - - /// Find internal function in hash functions /// /// @param[in] name Name of the function. @@ -6995,49 +7265,55 @@ static VimLFuncDef *find_internal_func(const char *const name) return find_internal_func_gperf(name, len); } -/// Check if "name" is a variable of type VAR_FUNC. If so, return the function -/// name it contains, otherwise return "name". -/// If "partialp" is not NULL, and "name" is of type VAR_PARTIAL also set -/// "partialp". -static char_u *deref_func_name( - char_u *name, int *lenp, - partial_T **partialp, bool no_autoload -) +/// Return name of the function corresponding to `name` +/// +/// If `name` points to variable that is either a function or partial then +/// corresponding function name is returned. Otherwise it returns `name` itself. +/// +/// @param[in] name Function name to check. +/// @param[in,out] lenp Location where length of the returned name is stored. +/// Must be set to the length of the `name` argument. +/// @param[out] partialp Location where partial will be stored if found +/// function appears to be a partial. May be NULL if this +/// is not needed. +/// @param[in] no_autoload If true, do not source autoload scripts if function +/// was not found. +/// +/// @return name of the function. +static char_u *deref_func_name(const char *name, int *lenp, + partial_T **const partialp, bool no_autoload) + FUNC_ATTR_NONNULL_ARG(1, 2) { - dictitem_T *v; - int cc; if (partialp != NULL) { *partialp = NULL; } - cc = name[*lenp]; - name[*lenp] = NUL; - v = find_var(name, NULL, no_autoload); - name[*lenp] = cc; + dictitem_T *const v = find_var(name, (size_t)(*lenp), NULL, no_autoload); if (v != NULL && v->di_tv.v_type == VAR_FUNC) { - if (v->di_tv.vval.v_string == NULL) { + if (v->di_tv.vval.v_string == NULL) { // just in case *lenp = 0; - return (char_u *)""; /* just in case */ + return (char_u *)""; } *lenp = (int)STRLEN(v->di_tv.vval.v_string); return v->di_tv.vval.v_string; } if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) { - partial_T *pt = v->di_tv.vval.v_partial; + partial_T *const pt = v->di_tv.vval.v_partial; - if (pt == NULL) { + if (pt == NULL) { // just in case *lenp = 0; - return (char_u *)""; // just in case + return (char_u *)""; } if (partialp != NULL) { *partialp = pt; } - *lenp = (int)STRLEN(pt->pt_name); - return pt->pt_name; + char_u *s = partial_name(pt); + *lenp = (int)STRLEN(s); + return s; } - return name; + return (char_u *)name; } /* @@ -7086,9 +7362,24 @@ get_func_tv ( ret = FAIL; if (ret == OK) { - ret = call_func(name, len, rettv, argcount, argvars, + int i = 0; + + if (get_vim_var_nr(VV_TESTING)) { + // Prepare for calling garbagecollect_for_testing(), need to know + // what variables are used on the call stack. + if (funcargs.ga_itemsize == 0) { + ga_init(&funcargs, (int)sizeof(typval_T *), 50); + } + for (i = 0; i < argcount; i++) { + ga_grow(&funcargs, 1); + ((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i]; + } + } + ret = call_func(name, len, rettv, argcount, argvars, NULL, firstline, lastline, doesrange, evaluate, partial, selfdict); + + funcargs.ga_len -= i; } else if (!aborting()) { if (argcount == MAX_FUNC_ARGS) { emsg_funcname(N_("E740: Too many arguments for function %s"), name); @@ -7104,33 +7395,49 @@ get_func_tv ( return ret; } -#define ERROR_UNKNOWN 0 -#define ERROR_TOOMANY 1 -#define ERROR_TOOFEW 2 -#define ERROR_SCRIPT 3 -#define ERROR_DICT 4 -#define ERROR_NONE 5 -#define ERROR_OTHER 6 -#define ERROR_BOTH 7 +typedef enum { + ERROR_UNKNOWN = 0, + ERROR_TOOMANY, + ERROR_TOOFEW, + ERROR_SCRIPT, + ERROR_DICT, + ERROR_NONE, + ERROR_OTHER, + ERROR_BOTH, + ERROR_DELETED, +} FnameTransError; + #define FLEN_FIXED 40 -/// In a script change <SID>name() and s:name() to K_SNR 123_name(). -/// Change <SNR>123_name() to K_SNR 123_name(). -/// Use "fname_buf[FLEN_FIXED + 1]" when it fits, otherwise allocate memory -/// (slow). -static char_u * -fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error) { - int llen; +/// In a script transform script-local names into actually used names +/// +/// Transforms "<SID>" and "s:" prefixes to `K_SNR {N}` (e.g. K_SNR "123") and +/// "<SNR>" prefix to `K_SNR`. Uses `fname_buf` buffer that is supposed to have +/// #FLEN_FIXED + 1 length when it fits, otherwise it allocates memory. +/// +/// @param[in] name Name to transform. +/// @param fname_buf Buffer to save resulting function name to, if it fits. +/// Must have at least #FLEN_FIXED + 1 length. +/// @param[out] tofree Location where pointer to an allocated memory is saved +/// in case result does not fit into fname_buf. +/// @param[out] error Location where error type is saved, @see +/// FnameTransError. +/// +/// @return transformed name: either `fname_buf` or a pointer to an allocated +/// memory. +static char_u *fname_trans_sid(const char_u *const name, + char_u *const fname_buf, + char_u **const tofree, int *const error) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ char_u *fname; - int i; - - llen = eval_fname_script(name); + const int llen = eval_fname_script((const char *)name); if (llen > 0) { fname_buf[0] = K_SPECIAL; fname_buf[1] = KS_EXTRA; fname_buf[2] = (int)KE_SNR; - i = 3; - if (eval_fname_sid(name)) { // "<SID>" or "s:" + int i = 3; + if (eval_fname_sid((const char *)name)) { // "<SID>" or "s:" if (current_SID <= 0) { *error = ERROR_SCRIPT; } else { @@ -7153,13 +7460,49 @@ fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error) { } } } else { - fname = name; + fname = (char_u *)name; } return fname; } +/// Mark all lists and dicts referenced through function "name" with "copyID". +/// "list_stack" is used to add lists to be marked. Can be NULL. +/// "ht_stack" is used to add hashtabs to be marked. Can be NULL. +/// +/// @return true if setting references failed somehow. +bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) +{ + ufunc_T *fp = fp_in; + funccall_T *fc; + int error = ERROR_NONE; + char_u fname_buf[FLEN_FIXED + 1]; + char_u *tofree = NULL; + char_u *fname; + bool abort = false; + if (name == NULL && fp_in == NULL) { + return false; + } + + if (fp_in == NULL) { + fname = fname_trans_sid(name, fname_buf, &tofree, &error); + fp = find_func(fname); + } + if (fp != NULL) { + for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped) { + abort = abort || set_ref_in_funccal(fc, copyID); + } + } + xfree(tofree); + return abort; +} + /// Call a function with its resolved parameters +/// +/// "argv_func", when not NULL, can be used to fill in arguments only when the +/// invoked function uses them. It is called like this: +/// new_argcount = argv_func(current_argcount, argv, called_func_argcount) +/// /// Return FAIL when the function can't be called, OK otherwise. /// Also returns OK when an error was encountered while executing the function. int @@ -7170,6 +7513,7 @@ call_func( int argcount_in, // number of "argvars" typval_T *argvars_in, // vars for arguments, must have "argcount" // PLUS ONE elements! + ArgvFunc argv_func, // function to fill in argvars linenr_T firstline, // first line of range linenr_T lastline, // last line of range int *doesrange, // return: function handled range @@ -7236,44 +7580,53 @@ call_func( rettv->vval.v_number = 0; error = ERROR_UNKNOWN; - if (!builtin_function(rfname, -1)) { - /* - * User defined function. - */ - fp = find_func(rfname); + if (!builtin_function((const char *)rfname, -1)) { + // User defined function. + if (partial != NULL && partial->pt_func != NULL) { + fp = partial->pt_func; + } else { + fp = find_func(rfname); + } - /* Trigger FuncUndefined event, may load the function. */ + // Trigger FuncUndefined event, may load the function. if (fp == NULL && apply_autocmds(EVENT_FUNCUNDEFINED, rfname, rfname, TRUE, NULL) && !aborting()) { /* executed an autocommand, search for the function again */ fp = find_func(rfname); } - /* Try loading a package. */ - if (fp == NULL && script_autoload(rfname, TRUE) && !aborting()) { - /* loaded a package, search for the function again */ + // Try loading a package. + if (fp == NULL && script_autoload((const char *)rfname, STRLEN(rfname), + true) && !aborting()) { + // Loaded a package, search for the function again. fp = find_func(rfname); } - if (fp != NULL) { - if (fp->uf_flags & FC_RANGE) - *doesrange = TRUE; - if (argcount < fp->uf_args.ga_len) + if (fp != NULL && (fp->uf_flags & FC_DELETED)) { + error = ERROR_DELETED; + } else if (fp != NULL) { + if (argv_func != NULL) { + argcount = argv_func(argcount, argvars, fp->uf_args.ga_len); + } + if (fp->uf_flags & FC_RANGE) { + *doesrange = true; + } + if (argcount < fp->uf_args.ga_len) { error = ERROR_TOOFEW; - else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) + } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) { error = ERROR_TOOMANY; - else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) + } else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) { error = ERROR_DICT; - else { + } else { // Call the user function. call_user_func(fp, argcount, argvars, rettv, firstline, lastline, - (fp->uf_flags & FC_DICT) ? selfdict : NULL); + (fp->uf_flags & FC_DICT) ? selfdict : NULL); error = ERROR_NONE; } } } else { // Find the function name in the table, call its implementation. - VimLFuncDef *const fdef = find_internal_func((char *)fname); + VimLFuncDef *const fdef = find_internal_func((const char *)fname); if (fdef != NULL) { if (argcount < fdef->min_argc) { error = ERROR_TOOFEW; @@ -7310,6 +7663,9 @@ call_func( case ERROR_UNKNOWN: emsg_funcname(N_("E117: Unknown function: %s"), name); break; + case ERROR_DELETED: + emsg_funcname(N_("E933: Function was deleted: %s"), name); + break; case ERROR_TOOMANY: emsg_funcname(e_toomanyarg, name); break; @@ -7953,17 +8309,17 @@ static buf_T *get_buf_tv(typval_T *tv, int curtab_only) */ static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - buf_T *buf; - - (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ - ++emsg_off; - buf = get_buf_tv(&argvars[0], FALSE); rettv->v_type = VAR_STRING; - if (buf != NULL && buf->b_fname != NULL) - rettv->vval.v_string = vim_strsave(buf->b_fname); - else - rettv->vval.v_string = NULL; - --emsg_off; + rettv->vval.v_string = NULL; + if (!tv_check_str_or_nr(&argvars[0])) { + return; + } + emsg_off++; + const buf_T *const buf = get_buf_tv(&argvars[0], false); + emsg_off--; + if (buf != NULL && buf->b_fname != NULL) { + rettv->vval.v_string = (char_u *)xstrdup((char *)buf->b_fname); + } } /* @@ -7971,36 +8327,36 @@ static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - buf_T *buf; - int error = FALSE; - char_u *name; + int error = false; + char_u *name; - (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ - ++emsg_off; - buf = get_buf_tv(&argvars[0], FALSE); - --emsg_off; + rettv->vval.v_number = -1; + if (!tv_check_str_or_nr(&argvars[0])) { + return; + } + emsg_off++; + const buf_T *buf = get_buf_tv(&argvars[0], false); + emsg_off--; - /* If the buffer isn't found and the second argument is not zero create a - * new buffer. */ + // If the buffer isn't found and the second argument is not zero create a + // new buffer. if (buf == NULL && argvars[1].v_type != VAR_UNKNOWN && get_tv_number_chk(&argvars[1], &error) != 0 && !error && (name = get_tv_string_chk(&argvars[0])) != NULL - && !error) + && !error) { buf = buflist_new(name, NULL, (linenr_T)1, 0); + } - if (buf != NULL) + if (buf != NULL) { rettv->vval.v_number = buf->b_fnum; - else - rettv->vval.v_number = -1; + } } static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr) { - int error = false; - (void)get_tv_number_chk(&argvars[0], &error); // issue errmsg if type error - if (error) { // the argument has an invalid type + if (!tv_check_str_or_nr(&argvars[0])) { rettv->vval.v_number = -1; return; } @@ -8044,14 +8400,13 @@ static void f_bufwinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_byte2line(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - long boff = 0; - - boff = get_tv_number(&argvars[0]) - 1; /* boff gets -1 on type error */ - if (boff < 0) + long boff = get_tv_number(&argvars[0]) - 1; + if (boff < 0) { rettv->vval.v_number = -1; - else - rettv->vval.v_number = ml_find_line_or_offset(curbuf, - (linenr_T)0, &boff); + } else { + rettv->vval.v_number = (varnumber_T)ml_find_line_or_offset(curbuf, 0, + &boff); + } } static void byteidx(typval_T *argvars, typval_T *rettv, int comp) @@ -8116,7 +8471,7 @@ int func_call(char_u *name, typval_T *args, partial_T *partial, } if (item == NULL) { - r = call_func(name, (int)STRLEN(name), rettv, argc, argv, + r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, true, partial, selfdict); } @@ -8146,7 +8501,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) func = argvars[0].vval.v_string; } else if (argvars[0].v_type == VAR_PARTIAL) { partial = argvars[0].vval.v_partial; - func = partial->pt_name; + func = partial_name(partial); } else { func = get_tv_string(&argvars[0]); } @@ -8579,11 +8934,6 @@ static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - if (argvars[2].v_type != VAR_FUNC && argvars[2].v_type != VAR_STRING) { - EMSG2(e_invarg2, "funcref"); - return; - } - char *key_pattern = (char *)get_tv_string_chk(argvars + 1); assert(key_pattern); const size_t key_len = STRLEN(argvars[1].vval.v_string); @@ -8595,6 +8945,7 @@ static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) Callback callback; if (!callback_from_typval(&callback, &argvars[2])) { + EMSG2(e_invarg2, "funcref"); return; } @@ -8915,53 +9266,55 @@ static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - char_u *p; - char_u *name; - int n = FALSE; + int n = false; int len = 0; - p = get_tv_string(&argvars[0]); - if (*p == '$') { /* environment variable */ - /* first try "normal" environment variables (fast) */ - if (os_getenv((char *)(p + 1)) != NULL) - n = TRUE; - else { - /* try expanding things like $VIM and ${HOME} */ - p = expand_env_save(p); - if (p != NULL && *p != '$') - n = TRUE; + char *p = (char *)get_tv_string(&argvars[0]); + if (*p == '$') { // Environment variable. + // First try "normal" environment variables (fast). + if (os_getenv(p + 1) != NULL) { + n = true; + } else { + // Try expanding things like $VIM and ${HOME}. + p = (char *)expand_env_save((char_u *)p); + if (p != NULL && *p != '$') { + n = true; + } xfree(p); } - } else if (*p == '&' || *p == '+') { /* option */ - n = (get_option_tv(&p, NULL, TRUE) == OK); - if (*skipwhite(p) != NUL) - n = FALSE; /* trailing garbage */ - } else if (*p == '*') { /* internal or user defined function */ - n = function_exists(p + 1); + } else if (*p == '&' || *p == '+') { // Option. + n = (get_option_tv((const char **)&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] == '#') + if (p[1] == '#') { n = autocmd_supported(p + 2); - else + } else { n = au_exists(p + 1); - } else { /* internal variable */ - char_u *tofree; + } + } else { // Internal variable. typval_T tv; - /* get_name_len() takes care of expanding curly braces */ - name = p; - len = get_name_len(&p, &tofree, TRUE, FALSE); + // 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) + // Handle d.key, l[idx], f(expr). + n = (handle_subscript((const char **)&p, &tv, true, false) == OK); + if (n) { clear_tv(&tv); + } } } if (*p != NUL) @@ -9279,8 +9632,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) */ static void filter_map(typval_T *argvars, typval_T *rettv, int map) { - char_u buf[NUMBUFLEN]; - char_u *expr; + typval_T *expr; listitem_T *li, *nli; list_T *l = NULL; dictitem_T *di; @@ -9312,16 +9664,15 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) return; } - expr = get_tv_string_buf_chk(&argvars[1], buf); - /* On type errors, the preceding call has already displayed an error - * message. Avoid a misleading error message for an empty string that - * was not passed as argument. */ - if (expr != NULL) { + expr = &argvars[1]; + // On type errors, the preceding call has already displayed an error + // message. Avoid a misleading error message for an empty string that + // was not passed as argument. + if (expr->v_type != VAR_UNKNOWN) { prepare_vimvar(VV_VAL, &save_val); - expr = skipwhite(expr); - /* We reset "did_emsg" to be able to detect whether an error - * occurred during evaluation of the expression. */ + // We reset "did_emsg" to be able to detect whether an error + // occurred during evaluation of the expression. save_did_emsg = did_emsg; did_emsg = FALSE; @@ -9385,20 +9736,46 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) copy_tv(&argvars[0], rettv); } -static int filter_map_one(typval_T *tv, char_u *expr, int map, int *remp) +static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) { typval_T rettv; + typval_T argv[3]; + char_u buf[NUMBUFLEN]; char_u *s; int retval = FAIL; + int dummy; copy_tv(tv, &vimvars[VV_VAL].vv_tv); - s = expr; - if (eval1(&s, &rettv, TRUE) == FAIL) - goto theend; - if (*s != NUL) { /* check for trailing chars after expr */ - EMSG2(_(e_invexpr2), s); - clear_tv(&rettv); - goto theend; + argv[0] = vimvars[VV_KEY].vv_tv; + argv[1] = vimvars[VV_VAL].vv_tv; + if (expr->v_type == VAR_FUNC) { + s = expr->vval.v_string; + if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL, + 0L, 0L, &dummy, true, NULL, NULL) == FAIL) { + goto theend; + } + } else if (expr->v_type == VAR_PARTIAL) { + partial_T *partial = expr->vval.v_partial; + + s = partial_name(partial); + if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL, + 0L, 0L, &dummy, true, partial, NULL) == FAIL) { + goto theend; + } + } else { + s = get_tv_string_buf_chk(expr, buf); + if (s == NULL) { + goto theend; + } + s = skipwhite(s); + if (eval1(&s, &rettv, true) == FAIL) { + goto theend; + } + + if (*s != NUL) { // check for trailing chars after expr + EMSG2(_(e_invexpr2), s); + goto theend; + } } if (map) { /* map(): replace the list item value */ @@ -9657,15 +10034,14 @@ static void f_foreground(typval_T *argvars, typval_T *rettv, FunPtr fptr) { } -/* - * "function()" function - */ -static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void common_function(typval_T *argvars, typval_T *rettv, + bool is_funcref, FunPtr fptr) { char_u *s; char_u *name; bool use_string = false; partial_T *arg_pt = NULL; + char_u *trans_name = NULL; if (argvars[0].v_type == VAR_FUNC) { // function(MyFunc, [arg], dict) @@ -9674,18 +10050,29 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) && argvars[0].vval.v_partial != NULL) { // function(dict.MyFunc, [arg]) arg_pt = argvars[0].vval.v_partial; - s = arg_pt->pt_name; + s = partial_name(arg_pt); } else { // function('MyFunc', [arg], dict) s = get_tv_string(&argvars[0]); use_string = true; } - if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s))) { - EMSG2(_(e_invarg2), s); - } else if (use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL - && !function_exists(s)) { + if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref) { + name = s; + trans_name = trans_function_name(&name, false, + TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD + | TFN_NO_DEREF, NULL, NULL); + if (*name != NUL) { + s = NULL; + } + } + if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s)) + || (is_funcref && trans_name == NULL)) { + EMSG2(_(e_invarg2), use_string ? get_tv_string(&argvars[0]) : s); // Don't check an autoload name for existence here. + } else if (trans_name != NULL + && (is_funcref ? find_func(trans_name) == NULL + : !translated_function_exists((const char *)trans_name))) { EMSG2(_("E700: Unknown function: %s"), s); } else { int dict_idx = 0; @@ -9726,7 +10113,7 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[dict_idx].v_type != VAR_DICT) { EMSG(_("E922: expected a dict")); xfree(name); - return; + goto theend; } if (argvars[dict_idx].vval.v_dict == NULL) { dict_idx = 0; @@ -9737,7 +10124,7 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG(_("E923: Second argument of function() must be " "a list or a dict")); xfree(name); - return; + goto theend; } list = argvars[arg_idx].vval.v_list; if (list == NULL || list->lv_len == 0) { @@ -9745,7 +10132,7 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } - if (dict_idx > 0 || arg_idx > 0 || arg_pt != NULL) { + if (dict_idx > 0 || arg_idx > 0 || arg_pt != NULL || is_funcref) { partial_T *const pt = xcalloc(1, sizeof(*pt)); // result is a VAR_PARTIAL @@ -9758,18 +10145,17 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (pt->pt_argv == NULL) { xfree(pt); xfree(name); - return; - } else { - int i = 0; - for (; i < arg_len; i++) { - copy_tv(&arg_pt->pt_argv[i], &pt->pt_argv[i]); - } - if (lv_len > 0) { - for (listitem_T *li = list->lv_first; - li != NULL; - li = li->li_next) { - copy_tv(&li->li_tv, &pt->pt_argv[i++]); - } + goto theend; + } + int i = 0; + for (; i < arg_len; i++) { + copy_tv(&arg_pt->pt_argv[i], &pt->pt_argv[i]); + } + if (lv_len > 0) { + for (listitem_T *li = list->lv_first; + li != NULL; + li = li->li_next) { + copy_tv(&li->li_tv, &pt->pt_argv[i++]); } } } @@ -9791,8 +10177,18 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) } pt->pt_refcount = 1; - pt->pt_name = name; - func_ref(pt->pt_name); + if (arg_pt != NULL && arg_pt->pt_func != NULL) { + pt->pt_func = arg_pt->pt_func; + func_ptr_ref(pt->pt_func); + xfree(name); + } else if (is_funcref) { + pt->pt_func = find_func(trans_name); + func_ptr_ref(pt->pt_func); + xfree(name); + } else { + pt->pt_name = name; + func_ref(name); + } rettv->v_type = VAR_PARTIAL; rettv->vval.v_partial = pt; @@ -9803,6 +10199,18 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) func_ref(name); } } +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 @@ -9856,11 +10264,18 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (pt != NULL) { char_u *what = get_tv_string(&argvars[1]); + char_u *n; if (STRCMP(what, "func") == 0 || STRCMP(what, "name") == 0) { rettv->v_type = (*what == 'f' ? VAR_FUNC : VAR_STRING); - if (pt->pt_name != NULL) { - rettv->vval.v_string = vim_strsave(pt->pt_name); + n = partial_name(pt); + if (n == NULL) { + rettv->vval.v_string = NULL; + } else { + rettv->vval.v_string = vim_strsave(n); + if (rettv->v_type == VAR_FUNC) { + func_ref(rettv->vval.v_string); + } } } else if (STRCMP(what, "dict") == 0) { rettv->v_type = VAR_DICT; @@ -10053,20 +10468,22 @@ static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) { linenr_T lnum; linenr_T end; - buf_T *buf; + buf_T *buf = NULL; - (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ - ++emsg_off; - buf = get_buf_tv(&argvars[0], FALSE); - --emsg_off; + if (tv_check_str_or_nr(&argvars[0])) { + emsg_off++; + buf = get_buf_tv(&argvars[0], false); + emsg_off--; + } lnum = get_tv_lnum_buf(&argvars[1], buf); - if (argvars[2].v_type == VAR_UNKNOWN) + if (argvars[2].v_type == VAR_UNKNOWN) { end = lnum; - else + } else { end = get_tv_lnum_buf(&argvars[2], buf); + } - get_buffer_lines(buf, lnum, end, TRUE, rettv); + get_buffer_lines(buf, lnum, end, true, rettv); } /* @@ -10074,26 +10491,25 @@ static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - buf_T *buf; - buf_T *save_curbuf; - char_u *varname; - dictitem_T *v; - int done = FALSE; - - (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ - varname = get_tv_string_chk(&argvars[1]); - ++emsg_off; - buf = get_buf_tv(&argvars[0], FALSE); + 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 = (const char *)get_tv_string_chk(&argvars[1]); + emsg_off++; + buf_T *const buf = get_buf_tv(&argvars[0], false); + if (buf != NULL && varname != NULL) { - /* set curbuf to be our buf, temporarily */ - save_curbuf = curbuf; + // set curbuf to be our buf, temporarily + buf_T *const save_curbuf = curbuf; curbuf = buf; - if (*varname == '&') { // buffer-local-option + if (*varname == '&') { // buffer-local-option if (varname[1] == NUL) { // get all buffer-local options in a dict dict_T *opts = get_winbuf_options(true); @@ -10113,25 +10529,26 @@ static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = curbuf->b_changedtick; done = true; } else { - /* Look up the variable. */ - /* Let getbufvar({nr}, "") return the "b:" dictionary. */ - v = find_var_in_ht(&curbuf->b_vars->dv_hashtab, - 'b', varname, FALSE); + // 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) { copy_tv(&v->di_tv, rettv); - done = TRUE; + done = true; } } - /* restore previous notion of curbuf */ + // restore previous notion of curbuf curbuf = save_curbuf; } + emsg_off--; - if (!done && argvars[2].v_type != VAR_UNKNOWN) - /* use the default value */ +f_getbufvar_end: + if (!done && argvars[2].v_type != VAR_UNKNOWN) { + // use the default value copy_tv(&argvars[2], rettv); - - --emsg_off; + } } /* @@ -10899,13 +11316,12 @@ static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) win_T *oldcurwin; tabpage_T *tp, *oldtabpage; dictitem_T *v; - char_u *varname; bool done = false; rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; - varname = get_tv_string_chk(&argvars[1]); + const char *const varname = (const char *)get_tv_string_chk(&argvars[1]); tp = find_tabpage((int)get_tv_number_chk(&argvars[0], NULL)); if (tp != NULL && varname != NULL) { // Set tp to be our tabpage, temporarily. Also set the window to the @@ -10914,7 +11330,8 @@ static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (switch_win(&oldcurwin, &oldtabpage, window, tp, true) == OK) { // look up the variable // Let gettabvar({nr}, "") return the "t:" dictionary. - v = find_var_in_ht(&tp->tp_vars->dv_hashtab, 't', varname, FALSE); + v = find_var_in_ht(&tp->tp_vars->dv_hashtab, 't', + varname, strlen(varname), false); if (v != NULL) { copy_tv(&v->di_tv, rettv); done = true; @@ -11093,7 +11510,6 @@ getwinvar ( ) { win_T *win, *oldcurwin; - char_u *varname; dictitem_T *v; tabpage_T *tp = NULL; tabpage_T *oldtabpage = NULL; @@ -11104,12 +11520,13 @@ getwinvar ( else tp = curtab; win = find_win_by_nr(&argvars[off], tp); - varname = get_tv_string_chk(&argvars[off + 1]); - ++emsg_off; + const char *varname = (const char *)get_tv_string_chk( + &argvars[off + 1]); rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; + emsg_off++; if (win != NULL && varname != NULL) { // Set curwin to be our win, temporarily. Also set the tabpage, // otherwise the window is not valid. Only do this when needed, @@ -11135,7 +11552,8 @@ getwinvar ( } else { // Look up the variable. // Let getwinvar({nr}, "") return the "w:" dictionary. - v = find_var_in_ht(&win->w_vars->dv_hashtab, 'w', varname, FALSE); + v = find_var_in_ht(&win->w_vars->dv_hashtab, 'w', varname, + strlen(varname), false); if (v != NULL) { copy_tv(&v->di_tv, rettv); done = true; @@ -11148,12 +11566,12 @@ getwinvar ( restore_win(oldcurwin, oldtabpage, true); } } + emsg_off--; - if (!done && argvars[off + 2].v_type != VAR_UNKNOWN) - /* use the default return value */ + if (!done && argvars[off + 2].v_type != VAR_UNKNOWN) { + // use the default return value copy_tv(&argvars[off + 2], rettv); - - --emsg_off; + } } /* @@ -11318,6 +11736,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) "insert_expand", "jumplist", "keymap", + "lambda", "langmap", "libcall", "linebreak", @@ -11422,6 +11841,10 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) #endif } 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 } } @@ -11818,8 +12241,8 @@ static void get_user_input(typval_T *argvars, typval_T *rettv, int inputdialog) *p = NUL; msg_start(); msg_clr_eos(); - msg_puts_attr(prompt, echo_attr); - msg_didout = FALSE; + msg_puts_attr((const char *)prompt, echo_attr); + msg_didout = false; msg_starthere(); *p = c; } @@ -11911,7 +12334,7 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) msg_clr_eos(); for (li = argvars[0].vval.v_list->lv_first; li != NULL; li = li->li_next) { - msg_puts(get_tv_string(&li->li_tv)); + msg_puts((const char *)get_tv_string(&li->li_tv)); msg_putchar('\n'); } @@ -12036,10 +12459,11 @@ static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG(_(e_trailing)); else { if (lv.ll_tv == NULL) { - if (check_changedtick(lv.ll_name)) - rettv->vval.v_number = 1; /* always locked */ - else { - di = find_var(lv.ll_name, NULL, TRUE); + if (check_changedtick(lv.ll_name)) { + rettv->vval.v_number = 1; // Always locked. + } else { + di = find_var((const char *)lv.ll_name, STRLEN(lv.ll_name), NULL, + true); if (di != NULL) { /* Consider a variable locked when: * 1. the variable itself is locked @@ -12880,9 +13304,9 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) rettv_dict_alloc(rettv); if (rhs != NULL) { // Return a dictionary. - char_u *lhs = str2special_save(mp->m_keys, TRUE); - char_u *mapmode = map_mode_to_chars(mp->m_mode); - dict_T *dict = rettv->vval.v_dict; + char_u *lhs = str2special_save(mp->m_keys, true); + char *const mapmode = map_mode_to_chars(mp->m_mode); + dict_T *dict = rettv->vval.v_dict; dict_add_nr_str(dict, "lhs", 0L, lhs); dict_add_nr_str(dict, "rhs", 0L, mp->m_orig_str); @@ -12892,7 +13316,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) dict_add_nr_str(dict, "sid", (long)mp->m_script_ID, NULL); dict_add_nr_str(dict, "buffer", (long)buffer_local, NULL); dict_add_nr_str(dict, "nowait", mp->m_nowait ? 1L : 0L, NULL); - dict_add_nr_str(dict, "mode", 0L, mapmode); + dict_add_nr_str(dict, "mode", 0L, (char_u *)mapmode); xfree(lhs); xfree(mapmode); @@ -15109,44 +15533,45 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - buf_T *buf; - char_u *varname, *bufvarname; - typval_T *varp; char_u nbuf[NUMBUFLEN]; - if (check_restricted() || check_secure()) + if (check_restricted() + || check_secure() + || !tv_check_str_or_nr(&argvars[0])) { return; - (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ - varname = get_tv_string_chk(&argvars[1]); - buf = get_buf_tv(&argvars[0], FALSE); - varp = &argvars[2]; + } + const char *varname = (const char *)get_tv_string_chk(&argvars[1]); + buf_T *const buf = get_buf_tv(&argvars[0], false); + typval_T *varp = &argvars[2]; if (buf != NULL && varname != NULL && varp != NULL) { if (*varname == '&') { long numval; - char_u *strval; + char_u *strval; int error = false; - aco_save_T aco; + aco_save_T aco; // set curbuf to be our buf, temporarily aucmd_prepbuf(&aco, buf); - ++varname; + varname++; numval = get_tv_number_chk(varp, &error); strval = get_tv_string_buf_chk(varp, nbuf); - if (!error && strval != NULL) - set_option_value(varname, numval, strval, OPT_LOCAL); + if (!error && strval != NULL) { + set_option_value((char_u *)varname, numval, strval, OPT_LOCAL); + } // reset notion of buffer aucmd_restbuf(&aco); } else { buf_T *save_curbuf = curbuf; - bufvarname = xmalloc(STRLEN(varname) + 3); + const size_t varname_len = STRLEN(varname); + char_u *const bufvarname = xmalloc(STRLEN(varname) + 3); curbuf = buf; - STRCPY(bufvarname, "b:"); - STRCPY(bufvarname + 2, varname); - set_var(bufvarname, varp, TRUE); + memcpy(bufvarname, "b:", 2); + memcpy(bufvarname + 2, varname, varname_len + 1); + set_var(bufvarname, varp, true); xfree(bufvarname); curbuf = save_curbuf; } @@ -15897,8 +16322,9 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) if (partial == NULL) { func_name = sortinfo->item_compare_func; } else { - func_name = partial->pt_name; + func_name = 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. copy_tv(&si1->item->li_tv, &argv[0]); @@ -15907,7 +16333,7 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) rettv.v_type = VAR_UNKNOWN; // clear_tv() uses this res = call_func(func_name, (int)STRLEN(func_name), - &rettv, 2, argv, 0L, 0L, &dummy, true, + &rettv, 2, argv, NULL, 0L, 0L, &dummy, true, partial, sortinfo->item_compare_selfdict); clear_tv(&argv[0]); clear_tv(&argv[1]); @@ -16715,12 +17141,16 @@ static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + if (no < 0 || no >= NSUBEXP) { + EMSGN(_("E935: invalid submatch number: %d"), no); + return; + } int retList = 0; if (argvars[1].v_type != VAR_UNKNOWN) { retList = get_tv_number_chk(&argvars[1], &error); if (error) { - return; + return; } } @@ -16744,19 +17174,26 @@ static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr) char_u *str = get_tv_string_chk(&argvars[0]); char_u *pat = get_tv_string_buf_chk(&argvars[1], patbuf); - char_u *sub = get_tv_string_buf_chk(&argvars[2], subbuf); + char_u *sub = NULL; + typval_T *expr = NULL; char_u *flg = get_tv_string_buf_chk(&argvars[3], flagsbuf); + if (argvars[2].v_type == VAR_FUNC || argvars[2].v_type == VAR_PARTIAL) { + expr = &argvars[2]; + } else { + sub = get_tv_string_buf_chk(&argvars[2], subbuf); + } + rettv->v_type = VAR_STRING; - if (str == NULL || pat == NULL || sub == NULL || flg == NULL) + if (str == NULL || pat == NULL || (sub == NULL && expr == NULL) + || flg == NULL) { rettv->vval.v_string = NULL; - else - rettv->vval.v_string = do_string_sub(str, pat, sub, flg); + } else { + rettv->vval.v_string = do_string_sub(str, pat, sub, expr, flg); + } } -/* - * "synID(lnum, col, trans)" function - */ +/// "synID(lnum, col, trans)" function static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int id = 0; @@ -16821,8 +17258,8 @@ static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) p = highlight_has_attr(id, HL_ITALIC, modec); break; - case 'n': /* name */ - p = get_highlight_name(NULL, id - 1); + case 'n': // name + p = (char_u *)get_highlight_name(NULL, id - 1); break; case 'r': /* reverse */ @@ -17307,6 +17744,15 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } +// "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); +} + static bool callback_from_typval(Callback *callback, typval_T *arg) { if (arg->v_type == VAR_PARTIAL && arg->vval.v_partial != NULL) { @@ -17385,7 +17831,7 @@ static bool callback_call(Callback *callback, int argcount_in, case kCallbackPartial: partial = callback->data.partial; - name = partial->pt_name; + name = partial_name(partial); break; case kCallbackNone: @@ -17398,7 +17844,7 @@ static bool callback_call(Callback *callback, int argcount_in, int dummy; return call_func(name, (int)STRLEN(name), rettv, argcount_in, argvars_in, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, + NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, true, partial, NULL); } @@ -18078,6 +18524,34 @@ write_list_error: return false; } +/// Initializes a static list with 10 items. +void init_static_list(staticList10_T *sl) +{ + list_T *l = &sl->sl_list; + + memset(sl, 0, sizeof(staticList10_T)); + l->lv_first = &sl->sl_items[0]; + l->lv_last = &sl->sl_items[9]; + l->lv_refcount = DO_NOT_FREE_CNT; + l->lv_lock = VAR_FIXED; + sl->sl_list.lv_len = 10; + + for (int i = 0; i < 10; i++) { + listitem_T *li = &sl->sl_items[i]; + + if (i == 0) { + li->li_prev = NULL; + } else { + li->li_prev = li - 1; + } + if (i == 9) { + li->li_next = NULL; + } else { + li->li_next = li + 1; + } + } +} + /// Saves a typval_T as a string. /// /// For lists, replaces NLs with NUL and separates items with NLs. @@ -18400,11 +18874,12 @@ static int get_env_len(char_u **arg) // Get the length of the name of a function or internal variable. // "arg" is advanced to the first non-white character after the name. // Return 0 if something is wrong. -static int get_id_len(char_u **arg) { - char_u *p; +static int get_id_len(const char **const arg) +{ int len; // Find the end of the name. + const char *p; for (p = *arg; eval_isnamec(*p); p++) { if (*p == ':') { // "s:" is start of "s:var", but "n:" is not and can be used in @@ -18421,7 +18896,7 @@ static int get_id_len(char_u **arg) { } len = (int)(p - *arg); - *arg = skipwhite(p); + *arg = (const char *)skipwhite((const char_u *)p); return len; } @@ -18435,18 +18910,20 @@ static int get_id_len(char_u **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(char_u **arg, char_u **alias, int evaluate, int verbose) +static int get_name_len(const char **const arg, + char **alias, + int evaluate, + int verbose) { int len; - char_u *p; char_u *expr_start; char_u *expr_end; *alias = NULL; /* default to no alias */ - if ((*arg)[0] == K_SPECIAL && (*arg)[1] == KS_EXTRA - && (*arg)[2] == (int)KE_SNR) { - /* hard coded <SNR>, already translated */ + if ((*arg)[0] == (char)K_SPECIAL && (*arg)[1] == (char)KS_EXTRA + && (*arg)[2] == (char)KE_SNR) { + // Hard coded <SNR>, already translated. *arg += 3; return get_id_len(arg) + 3; } @@ -18459,14 +18936,14 @@ static int get_name_len(char_u **arg, char_u **alias, int evaluate, int verbose) /* * Find the end of the name; check for {} construction. */ - p = find_name_end(*arg, &expr_start, &expr_end, - len > 0 ? 0 : FNE_CHECK_START); + const char *p = (const char *)find_name_end((char_u *)(*arg), + &expr_start, + &expr_end, + len > 0 ? 0 : FNE_CHECK_START); if (expr_start != NULL) { - char_u *temp_string; - if (!evaluate) { len += (int)(p - *arg); - *arg = skipwhite(p); + *arg = (const char *)skipwhite((const char_u *)p); return len; } @@ -18474,11 +18951,13 @@ static int get_name_len(char_u **arg, char_u **alias, int evaluate, int verbose) * Include any <SID> etc in the expanded string: * Thus the -len here. */ - temp_string = make_expanded_name(*arg - len, expr_start, expr_end, p); - if (temp_string == NULL) + char_u *temp_string = make_expanded_name((char_u *)(*arg) - len, expr_start, + expr_end, (char_u *)p); + if (temp_string == NULL) { return -1; - *alias = temp_string; - *arg = skipwhite(p); + } + *alias = (char *)temp_string; + *arg = (const char *)skipwhite((const char_u *)p); return (int)STRLEN(temp_string); } @@ -18892,9 +19371,8 @@ 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 ( - char_u *name, +static int get_var_tv( + const char *name, int len, // length of "name" typval_T *rettv, // NULL when only checking existence dictitem_T **dip, // non-NULL when typval's dict item is needed @@ -18906,25 +19384,16 @@ get_var_tv ( typval_T *tv = NULL; typval_T atv; dictitem_T *v; - int cc; - /* truncate the name, so that we can use strcmp() */ - cc = name[len]; - name[len] = NUL; - - /* - * Check for "b:changedtick". - */ - if (STRCMP(name, "b:changedtick") == 0) { + // Check for "b:changedtick". + if (sizeof("b:changedtick") - 1 == len + && STRNCMP(name, "b:changedtick", len) == 0) { atv.v_type = VAR_NUMBER; atv.vval.v_number = curbuf->b_changedtick; tv = &atv; - } - /* - * Check for user-defined variables. - */ - else { - v = find_var(name, NULL, no_autoload); + } else { + // Check for user-defined variables. + v = find_var(name, (size_t)len, NULL, no_autoload); if (v != NULL) { tv = &v->di_tv; if (dip != NULL) { @@ -18934,25 +19403,41 @@ get_var_tv ( } if (tv == NULL) { - if (rettv != NULL && verbose) - EMSG2(_(e_undefvar), name); + if (rettv != NULL && verbose) { + emsgf(_("E121: Undefined variable: %.*s"), len, name); + } ret = FAIL; - } else if (rettv != NULL) + } else if (rettv != NULL) { copy_tv(tv, rettv); - - name[len] = cc; + } return ret; } -/* - * 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 -handle_subscript ( - char_u **arg, +/// Check if variable "name[len]" is a local variable or an argument. +/// If so, "*eval_lavars_used" is set to TRUE. +static void check_vars(const char *name, size_t len) +{ + if (eval_lavars_used == NULL) { + return; + } + + const char *varname; + hashtab_T *ht = find_var_ht(name, len, &varname); + + if (ht == get_funccal_local_ht() || ht == get_funccal_args_ht()) { + if (find_var(name, len, NULL, true) != NULL) { + *eval_lavars_used = true; + } + } +} + +/// 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 +handle_subscript( + const char **const arg, typval_T *rettv, int evaluate, /* do more than finding the end */ int verbose /* give error messages */ @@ -18980,14 +19465,14 @@ handle_subscript ( // Invoke the function. Recursive! if (functv.v_type == VAR_PARTIAL) { pt = functv.vval.v_partial; - s = pt->pt_name; + s = partial_name(pt); } else { s = functv.vval.v_string; } } else { s = (char_u *)""; } - ret = get_func_tv(s, (int)STRLEN(s), rettv, arg, + ret = get_func_tv(s, (int)STRLEN(s), rettv, (char_u **)arg, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &len, evaluate, pt, selfdict); @@ -19014,7 +19499,7 @@ handle_subscript ( ++selfdict->dv_refcount; } else selfdict = NULL; - if (eval_index(arg, rettv, evaluate, verbose) == FAIL) { + if (eval_index((char_u **)arg, rettv, evaluate, verbose) == FAIL) { clear_tv(rettv); ret = FAIL; } @@ -19040,19 +19525,23 @@ static void set_selfdict(typval_T *rettv, dict_T *selfdict) && rettv->vval.v_partial->pt_dict != NULL) { return; } - char_u *fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING - ? rettv->vval.v_string - : rettv->vval.v_partial->pt_name; + char_u *fname; char_u *tofree = NULL; ufunc_T *fp; char_u fname_buf[FLEN_FIXED + 1]; int error; - // Translate "s:func" to the stored function name. - fname = fname_trans_sid(fname, fname_buf, &tofree, &error); - - fp = find_func(fname); - xfree(tofree); + if (rettv->v_type == VAR_PARTIAL && rettv->vval.v_partial->pt_func != NULL) { + fp = rettv->vval.v_partial->pt_func; + } else { + fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING + ? rettv->vval.v_string + : rettv->vval.v_partial->pt_name; + // Translate "s:func" to the stored function name. + fname = fname_trans_sid(fname, fname_buf, &tofree, &error); + fp = find_func(fname); + xfree(tofree); + } // Turn "dict.Func" into a partial for "Func" with "dict". if (fp != NULL && (fp->uf_flags & FC_DICT)) { @@ -19073,8 +19562,13 @@ static void set_selfdict(typval_T *rettv, dict_T *selfdict) // Partial: copy the function name, use selfdict and copy // args. Can't take over name or args, the partial might // be referenced elsewhere. - pt->pt_name = vim_strsave(ret_pt->pt_name); - func_ref(pt->pt_name); + if (ret_pt->pt_name != NULL) { + pt->pt_name = vim_strsave(ret_pt->pt_name); + func_ref(pt->pt_name); + } else { + pt->pt_func = ret_pt->pt_func; + func_ptr_ref(pt->pt_func); + } if (ret_pt->pt_argc > 0) { size_t arg_size = sizeof(typval_T) * ret_pt->pt_argc; pt->pt_argv = (typval_T *)xmalloc(arg_size); @@ -19378,6 +19872,53 @@ static void init_tv(typval_T *varp) memset(varp, 0, sizeof(typval_T)); } +/// Check that given value is a number or string +/// +/// Error messages are compatible with get_tv_number() previously used for the +/// same purpose in buf*() functions. Special values are not accepted (previous +/// behaviour: silently fail to find buffer). +/// +/// @param[in] tv Value to check. +/// +/// @return true if everything is OK, false otherwise. +bool tv_check_str_or_nr(const typval_T *const tv) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE +{ + switch (tv->v_type) { + case VAR_NUMBER: + case VAR_STRING: { + return true; + } + case VAR_FLOAT: { + EMSG(_("E805: Expected a Number or a String, Float found")); + return false; + } + case VAR_PARTIAL: + case VAR_FUNC: { + EMSG(_("E703: Expected a Number or a String, Funcref found")); + return false; + } + case VAR_LIST: { + EMSG(_("E745: Expected a Number or a String, List found")); + return false; + } + case VAR_DICT: { + EMSG(_("E728: Expected a Number or a String, Dictionary found")); + return false; + } + case VAR_SPECIAL: { + EMSG(_("E5300: Expected a Number or a String")); + return false; + } + case VAR_UNKNOWN: { + EMSG2(_(e_intern2), "tv_check_str_or_nr(UNKNOWN)"); + return false; + } + } + assert(false); + return false; +} + /* * Get the number value of a variable. * If it is a String variable, uses vim_str2nr(). @@ -19541,7 +20082,7 @@ char_u *get_tv_string_chk(const typval_T *varp) return get_tv_string_buf_chk(varp, mybuf); } -static char_u *get_tv_string_buf_chk(const typval_T *varp, char_u *buf) +char_u *get_tv_string_buf_chk(const typval_T *varp, char_u *buf) FUNC_ATTR_NONNULL_ALL { switch (varp->v_type) { @@ -19582,58 +20123,82 @@ static char_u *get_tv_string_buf_chk(const typval_T *varp, char_u *buf) * When "htp" is not NULL we are writing to the variable, set "htp" to the * hashtab_T used. */ -static dictitem_T *find_var(char_u *name, hashtab_T **htp, int no_autoload) +static dictitem_T *find_var(const char *const name, const size_t name_len, + hashtab_T **htp, int no_autoload) { - char_u *varname; - hashtab_T *ht; - - ht = find_var_ht(name, &varname); - if (htp != NULL) + const char *varname; + hashtab_T *ht = find_var_ht(name, name_len, &varname); + if (htp != NULL) { *htp = ht; - if (ht == NULL) + } + if (ht == NULL) { return NULL; - return find_var_in_ht(ht, *name, varname, no_autoload || htp != NULL); + } + dictitem_T *ret = find_var_in_ht(ht, *name, + varname, name_len - (size_t)(varname - name), + no_autoload || htp != NULL); + if (ret != NULL) { + return ret; + } + + // Search in parent scope for lambda + return find_var_in_scoped_ht(name, name_len, no_autoload || htp != NULL); } -/// Find variable "varname" in hashtab "ht" with name "htname". -/// Returns NULL if not found. -static dictitem_T *find_var_in_ht(hashtab_T *ht, int htname, - char_u *varname, bool no_autoload) +/// Find variable in hashtab +/// +/// @param[in] ht Hashtab to find variable in. +/// @param[in] htname Hashtab name (first character). +/// @param[in] varname Variable name. +/// @param[in] varname_len Variable name length. +/// @param[in] no_autoload If true then autoload scripts will not be sourced +/// if autoload variable was not found. +/// +/// @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) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { hashitem_T *hi; - if (*varname == NUL) { - /* Must be something like "s:", otherwise "ht" would be NULL. */ + if (varname_len == 0) { + // Must be something like "s:", otherwise "ht" would be NULL. switch (htname) { - case 's': return &SCRIPT_SV(current_SID)->sv_var; - case 'g': return &globvars_var; - case 'v': return &vimvars_var; - case 'b': return &curbuf->b_bufvar; - case 'w': return &curwin->w_winvar; - case 't': return &curtab->tp_winvar; - case 'l': return current_funccal == NULL - ? NULL : ¤t_funccal->l_vars_var; - case 'a': return current_funccal == NULL - ? NULL : ¤t_funccal->l_avars_var; + case 's': return (dictitem_T *)&SCRIPT_SV(current_SID)->sv_var; + case 'g': return (dictitem_T *)&globvars_var; + case 'v': return (dictitem_T *)&vimvars_var; + case 'b': return (dictitem_T *)&curbuf->b_bufvar; + case 'w': return (dictitem_T *)&curwin->w_winvar; + case 't': return (dictitem_T *)&curtab->tp_winvar; + case 'l': return (current_funccal == NULL + ? NULL : (dictitem_T *)¤t_funccal->l_vars_var); + case 'a': return (current_funccal == NULL + ? NULL : (dictitem_T *)¤t_funccal->l_avars_var); } return NULL; } - hi = hash_find(ht, varname); + hi = hash_find_len(ht, varname, varname_len); if (HASHITEM_EMPTY(hi)) { - /* For global variables we may try auto-loading the script. If it - * worked find the variable again. Don't auto-load a script if it was - * loaded already, otherwise it would be loaded every time when - * checking if a function name is a Funcref variable. */ + // For global variables we may try auto-loading the script. If it + // worked find the variable again. Don't auto-load a script if it was + // loaded already, otherwise it would be loaded every time when + // checking if a function name is a Funcref variable. if (ht == &globvarht && !no_autoload) { - /* Note: script_autoload() may make "hi" invalid. It must either - * be obtained again or not used. */ - if (!script_autoload(varname, FALSE) || aborting()) + // Note: script_autoload() may make "hi" invalid. It must either + // be obtained again or not used. + if (!script_autoload(varname, varname_len, false) || aborting()) { return NULL; - hi = hash_find(ht, varname); + } + hi = hash_find_len(ht, varname, varname_len); } - if (HASHITEM_EMPTY(hi)) + if (HASHITEM_EMPTY(hi)) { return NULL; + } } return HI2DI(hi); } @@ -19657,17 +20222,45 @@ static funccall_T *get_funccal(void) return funccal; } -// Find the dict and hashtable used for a variable name. Set "varname" to the -// start of name without ':'. -static hashtab_T *find_var_ht_dict(char_u *name, uint8_t **varname, dict_T **d) +/// Return the hashtable used for argument in the current funccal. +/// Return NULL if there is no current funccal. +static hashtab_T *get_funccal_args_ht(void) { - hashitem_T *hi; + if (current_funccal == NULL) { + return NULL; + } + return &get_funccal()->l_avars.dv_hashtab; +} + +/// Return the hashtable used for local variables in the current funccal. +/// Return NULL if there is no current funccal. +static hashtab_T *get_funccal_local_ht(void) +{ + if (current_funccal == NULL) { + return NULL; + } + return &get_funccal()->l_vars.dv_hashtab; +} + +/// Find the dict and hashtable used for a variable +/// +/// @param[in] name Variable name, possibly with scope prefix. +/// @param[in] name_len Variable name length. +/// @param[out] varname Will be set to the start of the name without scope +/// prefix. +/// @param[out] d Scope dictionary. +/// +/// @return Scope hashtab, NULL if name is not valid. +static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, + const char **varname, dict_T **d) +{ + hashitem_T *hi; *d = NULL; - if (name[0] == NUL) { + if (name_len == 0) { return NULL; } - if (name[1] != ':') { + if (name_len == 1 || (name_len >= 2 && name[1] != ':')) { // name has implicit scope if (name[0] == ':' || name[0] == AUTOLOAD_CHAR) { // The name must not start with a colon or #. @@ -19676,7 +20269,7 @@ static hashtab_T *find_var_ht_dict(char_u *name, uint8_t **varname, dict_T **d) *varname = name; // "version" is "v:version" in all scopes - hi = hash_find(&compat_hashtab, name); + hi = hash_find_len(&compat_hashtab, name, name_len); if (!HASHITEM_EMPTY(hi)) { return &compat_hashtab; } @@ -19692,26 +20285,27 @@ static hashtab_T *find_var_ht_dict(char_u *name, uint8_t **varname, dict_T **d) *varname = name + 2; if (*name == 'g') { // global variable *d = &globvardict; - } else if (vim_strchr(name + 2, ':') != NULL - || vim_strchr(name + 2, AUTOLOAD_CHAR) != NULL) { + } else if (name_len > 2 + && (memchr(name + 2, ':', name_len - 2) != NULL + || memchr(name + 2, AUTOLOAD_CHAR, name_len - 2) != NULL)) { // There must be no ':' or '#' in the rest of the name if g: was not used return NULL; } - if (*name == 'b') { // buffer variable + if (*name == 'b') { // buffer variable *d = curbuf->b_vars; - } else if (*name == 'w') { // window variable + } else if (*name == 'w') { // window variable *d = curwin->w_vars; - } else if (*name == 't') { // tab page variable + } else if (*name == 't') { // tab page variable *d = curtab->tp_vars; - } else if (*name == 'v') { // v: variable + } else if (*name == 'v') { // v: variable *d = &vimvardict; } else if (*name == 'a' && current_funccal != NULL) { // function argument *d = &get_funccal()->l_avars; } else if (*name == 'l' && current_funccal != NULL) { // local variable *d = &get_funccal()->l_vars; - } else if (*name == 's' // script variable - && current_SID > 0 && current_SID <= ga_scripts.ga_len) { + } else if (*name == 's' // script variable + && current_SID > 0 && current_SID <= ga_scripts.ga_len) { *d = &SCRIPT_SV(current_SID)->sv_dict; } @@ -19719,13 +20313,19 @@ end: return *d ? &(*d)->dv_hashtab : NULL; } -// Find the hashtab used for a variable name. -// Return NULL if the name is not valid. -// Set "varname" to the start of name without ':'. -static hashtab_T *find_var_ht(uint8_t *name, uint8_t **varname) +/// Find the hashtable used for a variable +/// +/// @param[in] name Variable name, possibly with scope prefix. +/// @param[in] name_len Variable name length. +/// @param[out] varname Will be set to the start of the name without scope +/// prefix. +/// +/// @return Scope hashtab, NULL if name is not valid. +static hashtab_T *find_var_ht(const char *name, const size_t name_len, + const char **varname) { dict_T *d; - return find_var_ht_dict(name, varname, &d); + return find_var_ht_dict(name, name_len, varname, &d); } /* @@ -19733,13 +20333,14 @@ static hashtab_T *find_var_ht(uint8_t *name, uint8_t **varname) * Note: see get_tv_string() for how long the pointer remains valid. * Returns NULL when it doesn't exist. */ -char_u *get_var_value(char_u *name) +char_u *get_var_value(const char *const name) { dictitem_T *v; - v = find_var(name, NULL, FALSE); - if (v == NULL) + v = find_var(name, strlen(name), NULL, false); + if (v == NULL) { return NULL; + } return get_tv_string(&v->di_tv); } @@ -19860,28 +20461,27 @@ static void delete_var(hashtab_T *ht, hashitem_T *hi) /* * List the value of one internal variable. */ -static void list_one_var(dictitem_T *v, char_u *prefix, int *first) +static void list_one_var(dictitem_T *v, const char *prefix, int *first) { - char_u *s = (char_u *) encode_tv2echo(&v->di_tv, NULL); - list_one_var_a(prefix, v->di_key, v->di_tv.v_type, - s == NULL ? (char_u *)"" : s, first); + char *const s = encode_tv2echo(&v->di_tv, NULL); + list_one_var_a(prefix, (const char *)v->di_key, STRLEN(v->di_key), + v->di_tv.v_type, (s == NULL ? "" : s), first); xfree(s); } -static void -list_one_var_a ( - char_u *prefix, - char_u *name, - int type, - char_u *string, - int *first /* when TRUE clear rest of screen and set to FALSE */ -) +/// @param[in] name_len Length of the name. May be -1, in this case strlen() +/// will be used. +/// @param[in,out] first When true clear rest of screen and set to false. +static void list_one_var_a(const char *prefix, const char *name, + const ptrdiff_t name_len, const int type, + const char *string, int *first) { - /* don't use msg() or msg_attr() to avoid overwriting "v:statusmsg" */ + // don't use msg() or msg_attr() to avoid overwriting "v:statusmsg" msg_start(); msg_puts(prefix); - if (name != NULL) /* "a:" vars don't have a name stored */ - msg_puts(name); + if (name != NULL) { // "a:" vars don't have a name stored + msg_puts_attr_len(name, name_len, 0); + } msg_putchar(' '); msg_advance(22); if (type == VAR_NUMBER) { @@ -19899,10 +20499,10 @@ list_one_var_a ( } else msg_putchar(' '); - msg_outtrans(string); + msg_outtrans((char_u *)string); if (type == VAR_FUNC || type == VAR_PARTIAL) { - msg_puts((char_u *)"()"); + msg_puts("()"); } if (*first) { msg_clr_eos(); @@ -19923,12 +20523,14 @@ set_var ( ) { dictitem_T *v; - char_u *varname; hashtab_T *ht; typval_T oldtv; dict_T *dict; - ht = find_var_ht_dict(name, &varname, &dict); + const size_t name_len = STRLEN(name); + char_u *varname; + ht = find_var_ht_dict((const char *)name, name_len, (const char **)&varname, + &dict); bool watched = is_watched(dict); if (watched) { @@ -19939,7 +20541,14 @@ set_var ( EMSG2(_(e_illvar), name); return; } - v = find_var_in_ht(ht, 0, varname, TRUE); + v = find_var_in_ht(ht, 0, + (const char *)varname, name_len - (size_t)(varname - name), + true); + + // Search in parent scope which is possible to reference from lambda + if (v == NULL) { + v = find_var_in_scoped_ht((const char *)name, name_len, true); + } if ((tv->v_type == VAR_FUNC || tv->v_type == VAR_PARTIAL) && var_check_func_name(name, v == NULL)) { @@ -20066,15 +20675,15 @@ var_check_func_name ( EMSG2(_("E704: Funcref variable name must start with a capital: %s"), name); return TRUE; } - /* Don't allow hiding a function. When "v" is not NULL we might be - * assigning another function to the same var, the type is checked - * below. */ - if (new_var && function_exists(name)) { + // Don't allow hiding a function. When "v" is not NULL we might be + // assigning another function to the same var, the type is checked + // below. + if (new_var && function_exists((const char *)name, false)) { EMSG2(_("E705: Variable name conflicts with existing function: %s"), - name); - return TRUE; + name); + return true; } - return FALSE; + return false; } /* @@ -20269,7 +20878,6 @@ void ex_echo(exarg_T *eap) { char_u *arg = eap->arg; typval_T rettv; - char_u *p; bool needclr = true; bool atstart = true; @@ -20280,19 +20888,20 @@ void ex_echo(exarg_T *eap) * still need to be cleared. E.g., "echo 22,44". */ need_clr_eos = needclr; - p = arg; - if (eval1(&arg, &rettv, !eap->skip) == FAIL) { - /* - * Report the invalid expression unless the expression evaluation - * has been cancelled due to an aborting error, an interrupt, or an - * exception. - */ - if (!aborting()) - EMSG2(_(e_invexpr2), p); - need_clr_eos = FALSE; - break; + { + char_u *p = arg; + if (eval1(&arg, &rettv, !eap->skip) == FAIL) { + // Report the invalid expression unless the expression evaluation + // has been cancelled due to an aborting error, an interrupt, or an + // exception. + if (!aborting()) { + EMSG2(_(e_invexpr2), p); + } + need_clr_eos = false; + break; + } + need_clr_eos = false; } - need_clr_eos = FALSE; if (!eap->skip) { if (atstart) { @@ -20306,9 +20915,11 @@ void ex_echo(exarg_T *eap) msg_sb_eol(); msg_start(); } - } else if (eap->cmdidx == CMD_echo) - msg_puts_attr((char_u *)" ", echo_attr); - char_u *tofree = p = (char_u *) encode_tv2echo(&rettv, NULL); + } else if (eap->cmdidx == CMD_echo) { + msg_puts_attr(" ", echo_attr); + } + char *tofree = encode_tv2echo(&rettv, NULL); + const char *p = tofree; if (p != NULL) { for (; *p != NUL && !got_int; ++p) { if (*p == '\n' || *p == '\r' || *p == TAB) { @@ -20317,15 +20928,16 @@ void ex_echo(exarg_T *eap) msg_clr_eos(); needclr = false; } - msg_putchar_attr(*p, echo_attr); + msg_putchar_attr((uint8_t)(*p), echo_attr); } else { if (has_mbyte) { - int i = (*mb_ptr2len)(p); + int i = (*mb_ptr2len)((const char_u *)p); - (void)msg_outtrans_len_attr(p, i, echo_attr); + (void)msg_outtrans_len_attr((char_u *)p, i, echo_attr); p += i - 1; - } else - (void)msg_outtrans_len_attr(p, 1, echo_attr); + } else { + (void)msg_outtrans_len_attr((char_u *)p, 1, echo_attr); + } } } } @@ -20439,9 +21051,9 @@ void ex_execute(exarg_T *eap) * Returns NULL when no option name found. Otherwise pointer to the char * after the option name. */ -static char_u *find_option_end(char_u **arg, int *opt_flags) +static const char *find_option_end(const char **const arg, int *const opt_flags) { - char_u *p = *arg; + const char *p = *arg; ++p; if (*p == 'g' && p[1] == ':') { @@ -20480,10 +21092,10 @@ void ex_function(exarg_T *eap) char_u *line_arg = NULL; garray_T newargs; garray_T newlines; - int varargs = FALSE; - int mustend = FALSE; + int varargs = false; int flags = 0; ufunc_T *fp; + bool overwrite = false; int indent; int nesting; char_u *skip_until = NULL; @@ -20506,8 +21118,9 @@ void ex_function(exarg_T *eap) if (!HASHITEM_EMPTY(hi)) { --todo; fp = HI2UF(hi); - if (!isdigit(*fp->uf_name)) - list_func_head(fp, FALSE); + if (!func_name_refcount(fp->uf_name)) { + list_func_head(fp, false); + } } } } @@ -20616,7 +21229,7 @@ void ex_function(exarg_T *eap) } if (!got_int) { msg_putchar('\n'); - msg_puts((char_u *)" endfunction"); + msg_puts(" endfunction"); } } else emsg_funcname(N_("E123: Undefined function: %s"), name); @@ -20664,59 +21277,11 @@ void ex_function(exarg_T *eap) EMSG(_("E862: Cannot use g: here")); } - /* - * Isolate the arguments: "arg1, arg2, ...)" - */ - while (*p != ')') { - if (p[0] == '.' && p[1] == '.' && p[2] == '.') { - varargs = TRUE; - p += 3; - mustend = TRUE; - } else { - arg = p; - while (ASCII_ISALNUM(*p) || *p == '_') - ++p; - if (arg == p || isdigit(*arg) - || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0) - || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) { - if (!eap->skip) - EMSG2(_("E125: Illegal argument: %s"), arg); - break; - } - ga_grow(&newargs, 1); - c = *p; - *p = NUL; - arg = vim_strsave(arg); - - /* Check for duplicate argument name. */ - for (int i = 0; i < newargs.ga_len; ++i) - if (STRCMP(((char_u **)(newargs.ga_data))[i], arg) == 0) { - EMSG2(_("E853: Duplicate argument name: %s"), arg); - xfree(arg); - goto erret; - } - - ((char_u **)(newargs.ga_data))[newargs.ga_len] = arg; - *p = c; - newargs.ga_len++; - if (*p == ',') - ++p; - else - mustend = TRUE; - } - p = skipwhite(p); - if (mustend && *p != ')') { - if (!eap->skip) - EMSG2(_(e_invarg2), eap->arg); - break; - } + if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) { + goto errret_2; } - if (*p != ')') { - goto erret; - } - ++p; // skip the ')' - /* find extra arguments "range", "dict" and "abort" */ + // find extra arguments "range", "dict", "abort" and "closure" for (;; ) { p = skipwhite(p); if (STRNCMP(p, "range", 5) == 0) { @@ -20728,8 +21293,18 @@ void ex_function(exarg_T *eap) } else if (STRNCMP(p, "abort", 5) == 0) { flags |= FC_ABORT; p += 5; - } else + } else if (STRNCMP(p, "closure", 7) == 0) { + flags |= FC_CLOSURE; + p += 7; + if (current_funccal == NULL) { + emsg_funcname(N_ + ("E932 Closure function should not be at top level: %s"), + name == NULL ? (char_u *)"" : name); + goto erret; + } + } else { break; + } } /* When there is a line break use what follows for the function body. @@ -20831,7 +21406,7 @@ void ex_function(exarg_T *eap) if (*p == '!') { p = skipwhite(p + 1); } - p += eval_fname_script(p); + p += eval_fname_script((const char *)p); xfree(trans_function_name(&p, true, 0, NULL, NULL)); if (*skipwhite(p) == '(') { nesting++; @@ -20906,7 +21481,7 @@ void ex_function(exarg_T *eap) * If there are no errors, add the function */ if (fudi.fd_dict == NULL) { - v = find_var(name, &ht, FALSE); + v = find_var((const char *)name, STRLEN(name), &ht, false); if (v != NULL && v->di_tv.v_type == VAR_FUNC) { emsg_funcname(N_("E707: Function name conflicts with variable: %s"), name); @@ -20919,16 +21494,25 @@ void ex_function(exarg_T *eap) emsg_funcname(e_funcexts, name); goto erret; } - if (fp->uf_refcount > 1 || fp->uf_calls > 0) { + if (fp->uf_calls > 0) { emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"), name); goto erret; } - /* redefine existing function */ - ga_clear_strings(&(fp->uf_args)); - ga_clear_strings(&(fp->uf_lines)); - xfree(name); - name = NULL; + if (fp->uf_refcount > 1) { + // This function is referenced somewhere, don't redefine it but + // create a new one. + (fp->uf_refcount)--; + fp->uf_flags |= FC_REMOVED; + fp = NULL; + overwrite = true; + } else { + // redefine existing function + ga_clear_strings(&(fp->uf_args)); + ga_clear_strings(&(fp->uf_lines)); + xfree(name); + name = NULL; + } } } else { char numbuf[20]; @@ -20963,7 +21547,7 @@ void ex_function(exarg_T *eap) /* Check that the autoload name matches the script name. */ int j = FAIL; if (sourcing_name != NULL) { - scriptname = autoload_name(name); + scriptname = (char_u *)autoload_name((const char *)name, STRLEN(name)); p = vim_strchr(scriptname, '/'); plen = (int)STRLEN(p); slen = (int)STRLEN(sourcing_name); @@ -20980,7 +21564,7 @@ void ex_function(exarg_T *eap) } } - fp = xmalloc(sizeof(ufunc_T) + STRLEN(name)); + fp = xcalloc(1, sizeof(ufunc_T) + STRLEN(name)); if (fudi.fd_dict != NULL) { if (fudi.fd_di == NULL) { @@ -21004,14 +21588,22 @@ void ex_function(exarg_T *eap) /* insert the new function in the function list */ STRCPY(fp->uf_name, name); - if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { - xfree(fp); - goto erret; + if (overwrite) { + hi = hash_find(&func_hashtab, name); + hi->hi_key = UF2HIKEY(fp); + } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { + xfree(fp); + goto erret; } + fp->uf_refcount = 1; } - fp->uf_refcount = 1; fp->uf_args = newargs; fp->uf_lines = newlines; + if ((flags & FC_CLOSURE) != 0) { + register_closure(fp); + } else { + fp->uf_scoped = NULL; + } fp->uf_tml_count = NULL; fp->uf_tml_total = NULL; fp->uf_tml_self = NULL; @@ -21026,6 +21618,7 @@ void ex_function(exarg_T *eap) erret: ga_clear_strings(&newargs); +errret_2: ga_clear_strings(&newlines); ret_free: xfree(skip_until); @@ -21035,18 +21628,18 @@ ret_free: need_wait_return |= saved_wait_return; } -/* - * Get a function name, translating "<SID>" and "<SNR>". - * Also handles a Funcref in a List or Dictionary. - * Returns the function name in allocated memory, or NULL for failure. - * flags: - * TFN_INT: internal function name OK - * TFN_QUIET: be quiet - * TFN_NO_AUTOLOAD: do not use script autoloading - * Advances "pp" to just after the function name (if no error). - */ -static char_u * -trans_function_name ( +/// Get a function name, translating "<SID>" and "<SNR>". +/// Also handles a Funcref in a List or Dictionary. +/// flags: +/// TFN_INT: internal function name OK +/// TFN_QUIET: be quiet +/// TFN_NO_AUTOLOAD: do not use script autoloading +/// TFN_NO_DEREF: do not dereference a Funcref +/// Advances "pp" to just after the function name (if no error). +/// +/// @return the function name in allocated memory, or NULL for failure. +char_u * +trans_function_name( char_u **pp, int skip, /* only find the end, don't evaluate */ int flags, @@ -21071,15 +21664,16 @@ trans_function_name ( if ((*pp)[0] == K_SPECIAL && (*pp)[1] == KS_EXTRA && (*pp)[2] == (int)KE_SNR) { *pp += 3; - len = get_id_len(pp) + 3; - return vim_strnsave(start, len); + len = get_id_len((const char **)pp) + 3; + return (char_u *)xmemdupz(start, len); } /* A name starting with "<SID>" or "<SNR>" is local to a script. But * don't skip over "s:", get_lval() needs it for "s:dict.func". */ - lead = eval_fname_script(start); - if (lead > 2) + lead = eval_fname_script((const char *)start); + if (lead > 2) { start += lead; + } /* Note that TFN_ flags use the same values as GLV_ flags. */ end = get_lval(start, NULL, &lv, FALSE, skip, flags, @@ -21115,7 +21709,7 @@ trans_function_name ( *pp = end; } else if (lv.ll_tv->v_type == VAR_PARTIAL && lv.ll_tv->vval.v_partial != NULL) { - name = vim_strsave(lv.ll_tv->vval.v_partial->pt_name); + name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial)); *pp = end; if (partial != NULL) { *partial = lv.ll_tv->vval.v_partial; @@ -21142,14 +21736,15 @@ trans_function_name ( /* Check if the name is a Funcref. If so, use the value. */ if (lv.ll_exp_name != NULL) { len = (int)STRLEN(lv.ll_exp_name); - name = deref_func_name(lv.ll_exp_name, &len, partial, + name = deref_func_name((const char *)lv.ll_exp_name, &len, partial, flags & TFN_NO_AUTOLOAD); if (name == lv.ll_exp_name) { name = NULL; } - } else { + } else if (!(flags & TFN_NO_DEREF)) { len = (int)(end - *pp); - name = deref_func_name(*pp, &len, partial, flags & TFN_NO_AUTOLOAD); + name = deref_func_name((const char *)(*pp), &len, partial, + flags & TFN_NO_AUTOLOAD); if (name == *pp) { name = NULL; } @@ -21194,9 +21789,9 @@ trans_function_name ( lead = 0; /* do nothing */ else if (lead > 0) { lead = 3; - if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) - || eval_fname_sid(*pp)) { - /* It's "s:" or "<SID>" */ + if ((lv.ll_exp_name != NULL && eval_fname_sid((const char *)lv.ll_exp_name)) + || eval_fname_sid((const char *)(*pp))) { + // It's "s:" or "<SID>". if (current_SID <= 0) { EMSG(_(e_usingsid)); goto theend; @@ -21204,13 +21799,14 @@ trans_function_name ( sprintf((char *)sid_buf, "%" PRId64 "_", (int64_t)current_SID); lead += (int)STRLEN(sid_buf); } - } else if (!(flags & TFN_INT) && builtin_function(lv.ll_name, len)) { + } else if (!(flags & TFN_INT) + && builtin_function((const char *)lv.ll_name, len)) { EMSG2(_("E128: Function name must start with a capital or \"s:\": %s"), start); goto theend; } - if (!skip && !(flags & TFN_QUIET)) { + if (!skip && !(flags & TFN_QUIET) && !(flags & TFN_NO_DEREF)) { char_u *cp = vim_strchr(lv.ll_name, ':'); if (cp != NULL && cp < end) { @@ -21241,12 +21837,13 @@ theend: * Return 2 if "p" starts with "s:". * Return 0 otherwise. */ -static int eval_fname_script(char_u *p) +static int eval_fname_script(const char *const p) { // Use mb_stricmp() because in Turkish comparing the "I" may not work with // the standard library function. - if (p[0] == '<' && (mb_strnicmp(p + 1, (char_u *)"SID>", 4) == 0 - || mb_strnicmp(p + 1, (char_u *)"SNR>", 4) == 0)) { + if (p[0] == '<' + && (mb_strnicmp((char_u *)p + 1, (char_u *)"SID>", 4) == 0 + || mb_strnicmp((char_u *)p + 1, (char_u *)"SNR>", 4) == 0)) { return 5; } if (p[0] == 's' && p[1] == ':') { @@ -21255,13 +21852,19 @@ static int eval_fname_script(char_u *p) return 0; } -/* - * Return TRUE if "p" starts with "<SID>" or "s:". - * Only works if eval_fname_script() returned non-zero for "p"! - */ -static int eval_fname_sid(char_u *p) +/// Check whether function name starts with <SID> or s: +/// +/// Only works for names previously checked by eval_fname_script(), if it +/// returned non-zero. +/// +/// @param[in] name Name to check. +/// +/// @return true if it starts with <SID> or s:, false otherwise. +static inline bool eval_fname_sid(const char *const name) + FUNC_ATTR_PURE FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ALL { - return *p == 's' || TOUPPER_ASC(p[2]) == 'I'; + return *name == 's' || TOUPPER_ASC(name[2]) == 'I'; } /* @@ -21275,38 +21878,45 @@ static void list_func_head(ufunc_T *fp, int indent) MSG_PUTS("function "); if (fp->uf_name[0] == K_SPECIAL) { MSG_PUTS_ATTR("<SNR>", hl_attr(HLF_8)); - msg_puts(fp->uf_name + 3); - } else - msg_puts(fp->uf_name); + msg_puts((const char *)fp->uf_name + 3); + } else { + msg_puts((const char *)fp->uf_name); + } msg_putchar('('); int j; - for (j = 0; j < fp->uf_args.ga_len; ++j) { - if (j) - MSG_PUTS(", "); - msg_puts(FUNCARG(fp, j)); + for (j = 0; j < fp->uf_args.ga_len; j++) { + if (j) { + msg_puts(", "); + } + msg_puts((const char *)FUNCARG(fp, j)); } if (fp->uf_varargs) { - if (j) - MSG_PUTS(", "); - MSG_PUTS("..."); + if (j) { + msg_puts(", "); + } + msg_puts("..."); } msg_putchar(')'); - if (fp->uf_flags & FC_ABORT) - MSG_PUTS(" abort"); - if (fp->uf_flags & FC_RANGE) - MSG_PUTS(" range"); - if (fp->uf_flags & FC_DICT) - MSG_PUTS(" dict"); + if (fp->uf_flags & FC_ABORT) { + msg_puts(" abort"); + } + if (fp->uf_flags & FC_RANGE) { + msg_puts(" range"); + } + if (fp->uf_flags & FC_DICT) { + msg_puts(" dict"); + } + if (fp->uf_flags & FC_CLOSURE) { + msg_puts(" closure"); + } msg_clr_eos(); if (p_verbose > 0) last_set_msg(fp->uf_script_ID); } -/* - * Find a function by name, return pointer to it in ufuncs. - * Return NULL for unknown function. - */ -static ufunc_T *find_func(char_u *name) +/// Find a function by name, return pointer to it in ufuncs. +/// @return NULL for unknown function. +static ufunc_T *find_func(const char_u *name) { hashitem_T *hi; @@ -21320,61 +21930,117 @@ static ufunc_T *find_func(char_u *name) void free_all_functions(void) { hashitem_T *hi; + ufunc_T *fp; + uint64_t skipped = 0; + uint64_t todo = 1; + uint64_t used; + + // First clear what the functions contain. Since this may lower the + // reference count of a function, it may also free a function and change + // the hash table. Restart if that happens. + while (todo > 0) { + todo = func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + // Only free functions that are not refcounted, those are + // supposed to be freed when no longer referenced. + fp = HI2UF(hi); + if (func_name_refcount(fp->uf_name)) { + skipped++; + } else { + used = func_hashtab.ht_used; + func_clear(fp, true); + if (used != func_hashtab.ht_used) { + skipped = 0; + break; + } + } + todo--; + } + } + } - /* Need to start all over every time, because func_free() may change the - * hash table. */ - while (func_hashtab.ht_used > 0) - for (hi = func_hashtab.ht_array;; ++hi) + // Now actually free the functions. Need to start all over every time, + // because func_free() may change the hash table. + skipped = 0; + while (func_hashtab.ht_used > skipped) { + todo = func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0; hi++) { if (!HASHITEM_EMPTY(hi)) { - func_free(HI2UF(hi)); - break; + todo--; + // Only free functions that are not refcounted, those are + // supposed to be freed when no longer referenced. + fp = HI2UF(hi); + if (func_name_refcount(fp->uf_name)) { + skipped++; + } else { + func_free(fp); + skipped = 0; + break; + } } + } + } + if (skipped == 0) { + hash_clear(&func_hashtab); + } } #endif -int translated_function_exists(char_u *name) +bool translated_function_exists(const char *name) { if (builtin_function(name, -1)) { return find_internal_func((char *)name) != NULL; } - return find_func(name) != NULL; + return find_func((const char_u *)name) != NULL; } -/* - * Return TRUE if a function "name" exists. - */ -static int function_exists(char_u *name) +/// Check whether function with the given name exists +/// +/// @param[in] name Function name. +/// @param[in] no_deref Whether to dereference a Funcref. +/// +/// @return True if it exists, false otherwise. +static bool function_exists(const char *const name, bool no_deref) { - char_u *nm = name; - char_u *p; - int n = FALSE; + char_u *nm = (char_u *)name; + bool n = false; + int flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD; - p = trans_function_name(&nm, false, TFN_INT|TFN_QUIET|TFN_NO_AUTOLOAD, - NULL, NULL); + if (no_deref) { + flag |= TFN_NO_DEREF; + } + char *const p = (char *)trans_function_name(&nm, false, flag, NULL, NULL); nm = skipwhite(nm); /* Only accept "funcname", "funcname ", "funcname (..." and * "funcname(...", not "funcname!...". */ - if (p != NULL && (*nm == NUL || *nm == '(')) + if (p != NULL && (*nm == NUL || *nm == '(')) { n = translated_function_exists(p); + } xfree(p); return n; } -/// Return TRUE if "name" looks like a builtin function name: starts with a +/// Checks if a builtin function with the given name exists. +/// +/// @param[in] name name of the builtin function to check. +/// @param[in] len length of "name", or -1 for NUL terminated. +/// +/// @return true if "name" looks like a builtin function name: starts with a /// lower case letter and doesn't contain AUTOLOAD_CHAR. -/// "len" is the length of "name", or -1 for NUL terminated. -static bool builtin_function(char_u *name, int len) +static bool builtin_function(const char *name, int len) { if (!ASCII_ISLOWER(name[0])) { - return FALSE; + return false; } - char_u *p = vim_strchr(name, AUTOLOAD_CHAR); + const char *p = (len == -1 + ? strchr(name, AUTOLOAD_CHAR) + : memchr(name, AUTOLOAD_CHAR, (size_t)len)); - return p == NULL - || (len > 0 && p > name + len); + return p == NULL; } /* @@ -21540,44 +22206,45 @@ static int prof_self_cmp(const void *s1, const void *s2) } -/* - * If "name" has a package name try autoloading the script for it. - * Return TRUE if a package was loaded. - */ -static int -script_autoload ( - char_u *name, - int reload /* load script again when already loaded */ -) +/// If name has a package name try autoloading the script for it +/// +/// @param[in] name Variable/function name. +/// @param[in] name_len Name length. +/// @param[in] reload If true, load script again when already loaded. +/// +/// @return true if a package was loaded. +static bool script_autoload(const char *const name, const size_t name_len, + const bool reload) { - char_u *p; - char_u *scriptname, *tofree; - int ret = FALSE; - int i; - - /* If there is no '#' after name[0] there is no package name. */ - p = vim_strchr(name, AUTOLOAD_CHAR); - if (p == NULL || p == name) - return FALSE; + // If there is no '#' after name[0] there is no package name. + const char *p = memchr(name, AUTOLOAD_CHAR, name_len); + if (p == NULL || p == name) { + return false; + } - tofree = scriptname = autoload_name(name); + bool ret = false; + char *tofree = autoload_name(name, name_len); + char *scriptname = tofree; - /* Find the name in the list of previously loaded package names. Skip - * "autoload/", it's always the same. */ - for (i = 0; i < ga_loaded.ga_len; ++i) - if (STRCMP(((char_u **)ga_loaded.ga_data)[i] + 9, scriptname + 9) == 0) + // Find the name in the list of previously loaded package names. Skip + // "autoload/", it's always the same. + int i = 0; + for (; i < ga_loaded.ga_len; i++) { + if (STRCMP(((char **)ga_loaded.ga_data)[i] + 9, scriptname + 9) == 0) { break; - if (!reload && i < ga_loaded.ga_len) - ret = FALSE; /* was loaded already */ - else { - /* Remember the name if it wasn't loaded already. */ + } + } + if (!reload && i < ga_loaded.ga_len) { + ret = false; // Was loaded already. + } else { + // Remember the name if it wasn't loaded already. if (i == ga_loaded.ga_len) { - GA_APPEND(char_u *, &ga_loaded, scriptname); + GA_APPEND(char *, &ga_loaded, scriptname); tofree = NULL; } // Try loading the package from $VIMRUNTIME/autoload/<name>.vim - if (source_runtime(scriptname, 0) == OK) { + if (source_runtime((char_u *)scriptname, 0) == OK) { ret = true; } } @@ -21586,21 +22253,29 @@ script_autoload ( return ret; } -/* - * Return the autoload script name for a function or variable name. - */ -static char_u *autoload_name(char_u *name) +/// Return the autoload script name for a function or variable name +/// +/// @param[in] name Variable/function name. +/// @param[in] name_len Name length. +/// +/// @return [allocated] autoload script name. +static char *autoload_name(const char *const name, const size_t name_len) + FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT { - /* Get the script file name: replace '#' with '/', append ".vim". */ - char_u *scriptname = xmalloc(STRLEN(name) + 14); - STRCPY(scriptname, "autoload/"); - STRCAT(scriptname, name); - *vim_strrchr(scriptname, AUTOLOAD_CHAR) = NUL; - STRCAT(scriptname, ".vim"); - - char_u *p; - while ((p = vim_strchr(scriptname, AUTOLOAD_CHAR)) != NULL) - *p = '/'; + // Get the script file name: replace '#' with '/', append ".vim". + char *const scriptname = xmalloc(name_len + sizeof("autoload/.vim")); + memcpy(scriptname, "autoload/", sizeof("autoload/") - 1); + memcpy(scriptname + sizeof("autoload/") - 1, name, name_len); + size_t auchar_idx = 0; + for (size_t i = sizeof("autoload/") - 1; + i - sizeof("autoload/") + 1 < name_len; + i++) { + if (scriptname[i] == AUTOLOAD_CHAR) { + scriptname[i] = '/'; + auchar_idx = i; + } + } + memcpy(scriptname + auchar_idx, ".vim", sizeof(".vim")); return scriptname; } @@ -21628,8 +22303,10 @@ char_u *get_user_func_name(expand_T *xp, int idx) ++hi; fp = HI2UF(hi); - if (fp->uf_flags & FC_DICT) - return (char_u *)""; /* don't show dict functions */ + if ((fp->uf_flags & FC_DICT) + || STRNCMP(fp->uf_name, "<lambda>", 8) == 0) { + return (char_u *)""; // don't show dict and lambda functions + } if (STRLEN(fp->uf_name) + 4 >= IOSIZE) return fp->uf_name; /* prevents overflow */ @@ -21660,9 +22337,18 @@ static void cat_func_name(char_u *buf, ufunc_T *fp) STRCPY(buf, fp->uf_name); } -/* - * ":delfunction {name}" - */ +/// There are two kinds of function names: +/// 1. ordinary names, function defined with :function +/// 2. numbered functions and lambdas +/// For the first we only count the name stored in func_hashtab as a reference, +/// using function() does not count as a reference, because the function is +/// looked up by name. +static bool func_name_refcount(char_u *name) +{ + return isdigit(*name) || *name == '<'; +} + +/// ":delfunction {name}" void ex_delfunction(exarg_T *eap) { ufunc_T *fp = NULL; @@ -21712,98 +22398,167 @@ void ex_delfunction(exarg_T *eap) /* Delete the dict item that refers to the function, it will * invoke func_unref() and possibly delete the function. */ dictitem_remove(fudi.fd_dict, fudi.fd_di); - } else - func_free(fp); + } else { + // A normal function (not a numbered function or lambda) has a + // refcount of 1 for the entry in the hashtable. When deleting + // it and the refcount is more than one, it should be kept. + // A numbered function or lambda should be kept if the refcount is + // one or more. + if (fp->uf_refcount > (func_name_refcount(fp->uf_name) ? 0 : 1)) { + // Function is still referenced somewhere. Don't free it but + // do remove it from the hashtable. + if (func_remove(fp)) { + fp->uf_refcount--; + } + fp->uf_flags |= FC_DELETED; + } else { + func_clear_free(fp, false); + } + } } } -/* - * Free a function and remove it from the list of functions. - */ -static void func_free(ufunc_T *fp) +/// Remove the function from the function hashtable. If the function was +/// deleted while it still has references this was already done. +/// +/// @return true if the entry was deleted, false if it wasn't found. +static bool func_remove(ufunc_T *fp) { - hashitem_T *hi; + hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp)); + + if (!HASHITEM_EMPTY(hi)) { + hash_remove(&func_hashtab, hi); + return true; + } - /* clear this function */ + return false; +} + +/// Free all things that a function contains. Does not free the function +/// itself, use func_free() for that. +/// +/// param[in] force When true, we are exiting. +static void func_clear(ufunc_T *fp, bool force) +{ + if (fp->uf_cleared) { + return; + } + fp->uf_cleared = true; + + // clear this function ga_clear_strings(&(fp->uf_args)); ga_clear_strings(&(fp->uf_lines)); xfree(fp->uf_tml_count); xfree(fp->uf_tml_total); xfree(fp->uf_tml_self); + funccal_unref(fp->uf_scoped, fp, force); +} - /* remove the function from the function hashtable */ - hi = hash_find(&func_hashtab, UF2HIKEY(fp)); - if (HASHITEM_EMPTY(hi)) - EMSG2(_(e_intern2), "func_free()"); - else - hash_remove(&func_hashtab, hi); - +/// Free a function and remove it from the list of functions. Does not free +/// what a function contains, call func_clear() first. +/// +/// param[in] fp The function to free. +static void func_free(ufunc_T *fp) +{ + // only remove it when not done already, otherwise we would remove a newer + // version of the function + if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) { + func_remove(fp); + } xfree(fp); } +/// Free all things that a function contains and free the function itself. +/// +/// param[in] force When true, we are exiting. +static void func_clear_free(ufunc_T *fp, bool force) +{ + func_clear(fp, force); + func_free(fp); +} + /* * Unreference a Function: decrement the reference count and free it when it - * becomes zero. Only for numbered functions. + * becomes zero. */ void func_unref(char_u *name) { - ufunc_T *fp; + ufunc_T *fp = NULL; - if (name != NULL && isdigit(*name)) { - fp = find_func(name); - if (fp == NULL) { + if (name == NULL || !func_name_refcount(name)) { + return; + } + + fp = find_func(name); + if (fp == NULL && isdigit(*name)) { #ifdef EXITFREE - if (!entered_free_all_mem) { - EMSG2(_(e_intern2), "func_unref()"); - } + if (!entered_free_all_mem) { + EMSG2(_(e_intern2), "func_unref()"); + abort(); + } #else EMSG2(_(e_intern2), "func_unref()"); + abort(); #endif - } else { - user_func_unref(fp); + } + if (fp != NULL && --fp->uf_refcount <= 0) { + // Only delete it when it's not being used. Otherwise it's done + // when "uf_calls" becomes zero. + if (fp->uf_calls == 0) { + func_clear_free(fp, false); } } } -static void user_func_unref(ufunc_T *fp) +/// Unreference a Function: decrement the reference count and free it when it +/// becomes zero. +void func_ptr_unref(ufunc_T *fp) { - if (--fp->uf_refcount <= 0) { - // Only delete it when it's not being used. Otherwise it's done + if (fp != NULL && --fp->uf_refcount <= 0) { + // Only delete it when it's not being used. Otherwise it's done // when "uf_calls" becomes zero. if (fp->uf_calls == 0) { - func_free(fp); + func_clear_free(fp, false); } } } -/* - * Count a reference to a Function. - */ +/// Count a reference to a Function. void func_ref(char_u *name) { ufunc_T *fp; - if (name != NULL && isdigit(*name)) { - fp = find_func(name); - if (fp == NULL) - EMSG2(_(e_intern2), "func_ref()"); - else - ++fp->uf_refcount; + if (name == NULL || !func_name_refcount(name)) { + return; + } + fp = find_func(name); + if (fp != NULL) { + (fp->uf_refcount)++; + } else if (isdigit(*name)) { + // Only give an error for a numbered function. + // Fail silently, when named or lambda function isn't found. + EMSG2(_(e_intern2), "func_ref()"); } } -/* - * Call a user function. - */ -static void -call_user_func ( - ufunc_T *fp, /* pointer to function */ - int argcount, /* nr of args */ - typval_T *argvars, /* arguments */ - typval_T *rettv, /* return value */ - linenr_T firstline, /* first line of range */ - linenr_T lastline, /* last line of range */ - dict_T *selfdict /* Dictionary for "self" */ +/// Count a reference to a Function. +void func_ptr_ref(ufunc_T *fp) +{ + if (fp != NULL) { + (fp->uf_refcount)++; + } +} + +/// Call a user function. +static void +call_user_func( + ufunc_T *fp, // pointer to function + int argcount, // nr of args + typval_T *argvars, // arguments + typval_T *rettv, // return value + linenr_T firstline, // first line of range + linenr_T lastline, // last line of range + dict_T *selfdict // Dictionary for "self" ) { char_u *save_sourcing_name; @@ -21815,6 +22570,7 @@ call_user_func ( dictitem_T *v; int fixvar_idx = 0; /* index in fixvar[] */ int ai; + bool islambda = false; char_u numbuf[NUMBUFLEN]; char_u *name; proftime_T wait_start; @@ -21852,14 +22608,21 @@ call_user_func ( fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0); fc->dbg_tick = debug_tick; - /* - * Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables - * with names up to VAR_SHORT_LEN long. This avoids having to alloc/free - * each argument variable and saves a lot of time. - */ - /* - * Init l: variables. - */ + // Set up fields for closure. + fc->fc_refcount = 0; + fc->fc_copyID = 0; + ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1); + func_ptr_ref(fp); + + if (STRNCMP(fp->uf_name, "<lambda>", 8) == 0) { + islambda = true; + } + + // Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables + // with names up to VAR_SHORT_LEN long. This avoids having to alloc/free + // each argument variable and saves a lot of time. + // + // Init l: variables. init_var_dict(&fc->l_vars, &fc->l_vars_var, VAR_DEF_SCOPE); if (selfdict != NULL) { /* Set l:self to "selfdict". Use "name" to avoid a warning from @@ -21901,23 +22664,26 @@ call_user_func ( fc->l_varlist.lv_refcount = DO_NOT_FREE_CNT; fc->l_varlist.lv_lock = VAR_FIXED; - /* - * Set a:firstline to "firstline" and a:lastline to "lastline". - * Set a:name to named arguments. - * Set a:N to the "..." arguments. - */ + // Set a:firstline to "firstline" and a:lastline to "lastline". + // Set a:name to named arguments. + // Set a:N to the "..." arguments. add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "firstline", - (varnumber_T)firstline); + (varnumber_T)firstline); add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "lastline", - (varnumber_T)lastline); - for (int i = 0; i < argcount; ++i) { + (varnumber_T)lastline); + for (int i = 0; i < argcount; i++) { + bool addlocal = false; + ai = i - fp->uf_args.ga_len; - if (ai < 0) - /* named argument a:name */ + if (ai < 0) { + // named argument a:name name = FUNCARG(fp, i); - else { - /* "..." argument a:1, a:2, etc. */ - sprintf((char *)numbuf, "%d", ai + 1); + if (islambda) { + addlocal = true; + } + } else { + // "..." argument a:1, a:2, etc. + snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1); name = numbuf; } if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) { @@ -21928,13 +22694,21 @@ call_user_func ( v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC; } STRCPY(v->di_key, name); - hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v)); - /* Note: the values are copied directly to avoid alloc/free. - * "argvars" must have VAR_FIXED for v_lock. */ + // Note: the values are copied directly to avoid alloc/free. + // "argvars" must have VAR_FIXED for v_lock. v->di_tv = argvars[i]; v->di_tv.v_lock = VAR_FIXED; + if (addlocal) { + // Named arguments can be accessed without the "a:" prefix in lambda + // expressions. Add to the l: dict. + copy_tv(&v->di_tv, &v->di_tv); + hash_add(&fc->l_vars.dv_hashtab, DI2HIKEY(v)); + } else { + hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v)); + } + if (ai >= 0 && ai < MAX_FUNC_ARGS) { list_append(&fc->l_varlist, &fc->l_listitems[ai]); fc->l_listitems[ai].li_tv = argvars[i]; @@ -21974,24 +22748,24 @@ call_user_func ( smsg(_("calling %s"), sourcing_name); if (p_verbose >= 14) { - char_u buf[MSG_BUF_LEN]; - - msg_puts((char_u *)"("); - for (int i = 0; i < argcount; ++i) { + msg_puts("("); + for (int i = 0; i < argcount; i++) { if (i > 0) { - msg_puts((char_u *)", "); + msg_puts(", "); } if (argvars[i].v_type == VAR_NUMBER) { msg_outnum((long)argvars[i].vval.v_number); } else { // Do not want errors such as E724 here. emsg_off++; - char_u *s = (char_u *) encode_tv2string(&argvars[i], NULL); - char_u *tofree = s; + char *tofree = encode_tv2string(&argvars[i], NULL); + char *s = tofree; emsg_off--; if (s != NULL) { - if (vim_strsize(s) > MSG_BUF_CLEN) { - trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN); + if (vim_strsize((char_u *)s) > MSG_BUF_CLEN) { + char buf[MSG_BUF_LEN]; + trunc_string((char_u *)s, (char_u *)buf, MSG_BUF_CLEN, + sizeof(buf)); s = buf; } msg_puts(s); @@ -21999,9 +22773,9 @@ call_user_func ( } } } - msg_puts((char_u *)")"); + msg_puts(")"); } - msg_puts((char_u *)"\n"); /* don't overwrite this either */ + msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); --no_wait_return; @@ -22092,7 +22866,7 @@ call_user_func ( xfree(tofree); } } - msg_puts((char_u *)"\n"); /* don't overwrite this either */ + msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); --no_wait_return; @@ -22110,7 +22884,7 @@ call_user_func ( verbose_enter_scroll(); smsg(_("continuing in %s"), sourcing_name); - msg_puts((char_u *)"\n"); /* don't overwrite this either */ + msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); --no_wait_return; @@ -22120,20 +22894,21 @@ call_user_func ( current_funccal = fc->caller; --depth; - /* If the a:000 list and the l: and a: dicts are not referenced we can - * free the funccall_T and what's in it. */ + // If the a:000 list and the l: and a: dicts are not referenced and there + // is no closure using it, we can free the funccall_T and what's in it. if (fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT - && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT) { - free_funccal(fc, FALSE); + && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT + && fc->fc_refcount <= 0) { + free_funccal(fc, false); } else { hashitem_T *hi; listitem_T *li; int todo; - /* "fc" is still in use. This can happen when returning "a:000" or - * assigning "l:" to a global variable. - * Link "fc" in the list for garbage collection later. */ + // "fc" is still in use. This can happen when returning "a:000", + // assigning "l:" to a global variable or defining a closure. + // Link "fc" in the list for garbage collection later. fc->caller = previous_funccal; previous_funccal = fc; @@ -22152,9 +22927,9 @@ call_user_func ( copy_tv(&li->li_tv, &li->li_tv); } - if (--fp->uf_calls <= 0 && isdigit(*fp->uf_name) && fp->uf_refcount <= 0) { + if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) { // Function was unreferenced while being used, free it now. - func_free(fp); + func_clear_free(fp, false); } // restore search patterns and redo buffer if (did_save_redo) { @@ -22163,15 +22938,46 @@ call_user_func ( restore_search_patterns(); } -/* - * Return TRUE if items in "fc" do not have "copyID". That means they are not - * referenced from anywhere that is in use. - */ +/// Unreference "fc": decrement the reference count and free it when it +/// becomes zero. "fp" is detached from "fc". +/// +/// @param[in] force When true, we are exiting. +static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) +{ + funccall_T **pfc; + int i; + + if (fc == NULL) { + return; + } + + if (--fc->fc_refcount <= 0 && (force || ( + fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT + && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT + && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT))) { + for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) { + if (fc == *pfc) { + *pfc = fc->caller; + free_funccal(fc, true); + return; + } + } + } + for (i = 0; i < fc->fc_funcs.ga_len; i++) { + if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) { + ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL; + } + } +} + +/// @return true if items in "fc" do not have "copyID". That means they are not +/// referenced from anywhere that is in use. static int can_free_funccal(funccall_T *fc, int copyID) { return fc->l_varlist.lv_copyID != copyID && fc->l_vars.dv_copyID != copyID - && fc->l_avars.dv_copyID != copyID; + && fc->l_avars.dv_copyID != copyID + && fc->fc_copyID != copyID; } /* @@ -22185,18 +22991,34 @@ free_funccal ( { listitem_T *li; - /* The a: variables typevals may not have been allocated, only free the - * allocated variables. */ + for (int i = 0; i < fc->fc_funcs.ga_len; i++) { + ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; + + // When garbage collecting a funccall_T may be freed before the + // function that references it, clear its uf_scoped field. + // The function may have been redefined and point to another + // funccal_T, don't clear it then. + if (fp != NULL && fp->uf_scoped == fc) { + fp->uf_scoped = NULL; + } + } + ga_clear(&fc->fc_funcs); + + // The a: variables typevals may not have been allocated, only free the + // allocated variables. vars_clear_ext(&fc->l_avars.dv_hashtab, free_val); /* free all l: variables */ vars_clear(&fc->l_vars.dv_hashtab); - /* Free the a:000 variables if they were allocated. */ - if (free_val) - for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next) + // Free the a:000 variables if they were allocated. + if (free_val) { + for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next) { clear_tv(&li->li_tv); + } + } + func_ptr_unref(fc->func); xfree(fc); } @@ -22515,6 +23337,72 @@ static var_flavour_T var_flavour(char_u *varname) } } +/// Search hashitem in parent scope. +hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht) +{ + if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) { + return NULL; + } + + funccall_T *old_current_funccal = current_funccal; + hashitem_T *hi = NULL; + const size_t namelen = strlen(name); + const char *varname; + + // Search in parent scope which is possible to reference from lambda + current_funccal = current_funccal->func->uf_scoped; + while (current_funccal != NULL) { + hashtab_T *ht = find_var_ht(name, namelen, &varname); + if (ht != NULL && *varname != NUL) { + hi = hash_find_len(ht, varname, namelen - (varname - name)); + if (!HASHITEM_EMPTY(hi)) { + *pht = ht; + break; + } + } + if (current_funccal == current_funccal->func->uf_scoped) { + break; + } + current_funccal = current_funccal->func->uf_scoped; + } + current_funccal = old_current_funccal; + + return hi; +} + +/// Search variable in parent scope. +dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, + int no_autoload) +{ + if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) { + return NULL; + } + + dictitem_T *v = NULL; + funccall_T *old_current_funccal = current_funccal; + const char *varname; + + // Search in parent scope which is possible to reference from lambda + current_funccal = current_funccal->func->uf_scoped; + while (current_funccal) { + hashtab_T *ht = find_var_ht(name, namelen, &varname); + if (ht != NULL && *varname != NUL) { + v = find_var_in_ht(ht, *name, varname, + namelen - (size_t)(varname - name), no_autoload); + if (v != NULL) { + break; + } + } + if (current_funccal == current_funccal->func->uf_scoped) { + break; + } + current_funccal = current_funccal->func->uf_scoped; + } + current_funccal = old_current_funccal; + + return v; +} + /// Iterate over global variables /// /// @warning No modifications to global variable dictionary must be performed @@ -22914,7 +23802,7 @@ repeat: sub = vim_strnsave(s, (int)(p - s)); str = vim_strnsave(*fnamep, *fnamelen); *usedlen = (size_t)(p + 1 - src); - s = do_string_sub(str, pat, sub, flags); + s = do_string_sub(str, pat, sub, NULL, flags); *fnamep = s; *fnamelen = STRLEN(s); xfree(*bufp); @@ -22950,12 +23838,12 @@ repeat: return valid; } -/* - * Perform a substitution on "str" with pattern "pat" and substitute "sub". - * "flags" can be "g" to do a global substitute. - * Returns an allocated string, NULL for error. - */ -char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *flags) +/// Perform a substitution on "str" with pattern "pat" and substitute "sub". +/// When "sub" is NULL "expr" is used, must be a VAR_FUNC or VAR_PARTIAL. +/// "flags" can be "g" to do a global substitute. +/// Returns an allocated string, NULL for error. +char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, + typval_T *expr, char_u *flags) { int sublen; regmatch_T regmatch; @@ -22993,23 +23881,21 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *flags) zero_width = regmatch.startp[0]; } - /* - * Get some space for a temporary buffer to do the substitution - * into. It will contain: - * - The text up to where the match is. - * - The substituted text. - * - The text after the match. - */ - sublen = vim_regsub(®match, sub, tail, FALSE, TRUE, FALSE); + // Get some space for a temporary buffer to do the substitution + // into. It will contain: + // - The text up to where the match is. + // - The substituted text. + // - The text after the match. + sublen = vim_regsub(®match, sub, expr, tail, false, true, false); ga_grow(&ga, (int)((end - tail) + sublen - (regmatch.endp[0] - regmatch.startp[0]))); /* copy the text up to where the match is */ int i = (int)(regmatch.startp[0] - tail); memmove((char_u *)ga.ga_data + ga.ga_len, tail, (size_t)i); - /* add the substituted text */ - (void)vim_regsub(®match, sub, (char_u *)ga.ga_data - + ga.ga_len + i, TRUE, TRUE, FALSE); + // add the substituted text + (void)vim_regsub(®match, sub, expr, (char_u *)ga.ga_data + + ga.ga_len + i, true, true, false); ga.ga_len += i + sublen - 1; tail = regmatch.endp[0]; if (*tail == NUL) @@ -23026,11 +23912,12 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *flags) char_u *ret = vim_strsave(ga.ga_data == NULL ? str : (char_u *)ga.ga_data); ga_clear(&ga); - if (p_cpo == empty_option) + if (p_cpo == empty_option) { p_cpo = save_cpo; - else - /* Darn, evaluating {sub} expression changed the value. */ + } else { + // Darn, evaluating {sub} expression or {expr} changed the value. free_string_option(save_cpo); + } return ret; } @@ -23396,6 +24283,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) &rettv, 2, argvars, + NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, @@ -23416,10 +24304,12 @@ bool eval_has_provider(char *name) { #define check_provider(name) \ if (has_##name == -1) { \ - has_##name = !!find_func((uint8_t *)"provider#" #name "#Call"); \ + has_##name = !!find_func((char_u *)"provider#" #name "#Call"); \ if (!has_##name) { \ - script_autoload((uint8_t *)"provider#" #name "#Call", false); \ - has_##name = !!find_func((uint8_t *)"provider#" #name "#Call"); \ + script_autoload("provider#" #name "#Call", \ + sizeof("provider#" #name "#Call") - 1, \ + false); \ + has_##name = !!find_func((char_u *)"provider#" #name "#Call"); \ } \ } diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 630e309442..57fee5c5a2 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -12,39 +12,6 @@ // All user-defined functions are found in this hashtable. extern hashtab_T func_hashtab; - -// Structure to hold info for a user function. -typedef struct ufunc ufunc_T; - -struct ufunc { - int uf_varargs; ///< variable nr of arguments - int uf_flags; - int uf_calls; ///< nr of active calls - garray_T uf_args; ///< arguments - garray_T uf_lines; ///< function lines - int uf_profiling; ///< true when func is being profiled - // Profiling the function as a whole. - int uf_tm_count; ///< nr of calls - proftime_T uf_tm_total; ///< time spent in function + children - proftime_T uf_tm_self; ///< time spent in function itself - proftime_T uf_tm_children; ///< time spent in children this call - // Profiling the function per line. - int *uf_tml_count; ///< nr of times line was executed - proftime_T *uf_tml_total; ///< time spent in a line + children - proftime_T *uf_tml_self; ///< time spent in a line itself - proftime_T uf_tml_start; ///< start time for current line - proftime_T uf_tml_children; ///< time spent in children for this line - proftime_T uf_tml_wait; ///< start wait time for current line - int uf_tml_idx; ///< index of line being timed; -1 if none - int uf_tml_execed; ///< line being timed was executed - scid_T uf_script_ID; ///< ID of script where function was defined, - // used for s: variables - int uf_refcount; ///< for numbered function: reference count - char_u uf_name[1]; ///< name of function (actually longer); can - // start with <SNR>123_ (<SNR> is K_SPECIAL - // KS_EXTRA KE_SNR) -}; - // From user function to hashitem and back. EXTERN ufunc_T dumuf; #define UF2HIKEY(fp) ((fp)->uf_name) @@ -127,6 +94,7 @@ typedef enum { VV__NULL_LIST, // List with NULL value. For test purposes only. VV__NULL_DICT, // Dictionary with NULL value. For test purposes only. VV_VIM_DID_ENTER, + VV_TESTING, VV_TYPE_NUMBER, VV_TYPE_STRING, VV_TYPE_FUNC, @@ -156,8 +124,8 @@ extern const list_T *eval_msgpack_type_lists[LAST_MSGPACK_TYPE + 1]; #undef LAST_MSGPACK_TYPE -/// Maximum number of function arguments -#define MAX_FUNC_ARGS 20 +typedef int (*ArgvFunc)(int current_argcount, typval_T *argv, + int called_func_argcount); #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval.h.generated.h" diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 964b061e95..b0bf417207 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -103,6 +103,7 @@ return { foldtext={}, foldtextresult={args=1}, foreground={}, + funcref={args={1, 3}}, ['function']={args={1, 3}}, garbagecollect={args={0, 1}}, get={args={2, 3}}, @@ -303,6 +304,7 @@ return { tanh={args=1, func="float_op_wrapper", data="&tanh"}, tempname={}, termopen={args={1, 2}}, + test_garbagecollect_now={}, timer_start={args={2,3}}, timer_stop={args=1}, tolower={args=1}, diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 34f88cbc98..4ff5589887 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -344,7 +344,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( case VAR_PARTIAL: { partial_T *const pt = tv->vval.v_partial; (void)pt; - TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : pt->pt_name)); + TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : partial_name(pt))); _mp_push(*mpstack, ((MPConvStackVal) { .type = kMPConvPartial, .tv = tv, diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h index 616c89671b..a61ddb7605 100644 --- a/src/nvim/eval_defs.h +++ b/src/nvim/eval_defs.h @@ -6,6 +6,9 @@ #include "nvim/hashtab.h" #include "nvim/lib/queue.h" +#include "nvim/garray.h" // for garray_T +#include "nvim/profile.h" // for proftime_T +#include "nvim/pos.h" // for linenr_T typedef int varnumber_T; typedef double float_T; @@ -104,15 +107,19 @@ struct listvar_S { list_T *lv_used_prev; /* previous list in used lists list */ }; -/* - * Structure to hold an item of a Dictionary. - * Also used for a variable. - * The key is copied into "di_key" to avoid an extra alloc/free for it. - */ +// Static list with 10 items. Use init_static_list() to initialize. +typedef struct { + list_T sl_list; // must be first + listitem_T sl_items[10]; +} staticList10_T; + +// Structure to hold an item of a Dictionary. +// Also used for a variable. +// The key is copied into "di_key" to avoid an extra alloc/free for it. struct dictitem_S { - typval_T di_tv; /* type and value of the variable */ - char_u di_flags; /* flags (only used for variable) */ - char_u di_key[1]; /* key (actually longer!) */ + typval_T di_tv; ///< type and value of the variable + char_u di_flags; ///< flags (only used for variable) + char_u di_key[1]; ///< key (actually longer!) }; typedef struct dictitem_S dictitem_T; @@ -147,9 +154,88 @@ struct dictvar_S { QUEUE watchers; ///< Dictionary key watchers set by user code. }; +typedef int scid_T; // script ID +typedef struct funccall_S funccall_T; + +// Structure to hold info for a user function. +typedef struct ufunc ufunc_T; + +struct ufunc { + int uf_varargs; ///< variable nr of arguments + int uf_flags; + int uf_calls; ///< nr of active calls + bool uf_cleared; ///< func_clear() was already called + garray_T uf_args; ///< arguments + garray_T uf_lines; ///< function lines + int uf_profiling; ///< true when func is being profiled + // Profiling the function as a whole. + int uf_tm_count; ///< nr of calls + proftime_T uf_tm_total; ///< time spent in function + children + proftime_T uf_tm_self; ///< time spent in function itself + proftime_T uf_tm_children; ///< time spent in children this call + // Profiling the function per line. + int *uf_tml_count; ///< nr of times line was executed + proftime_T *uf_tml_total; ///< time spent in a line + children + proftime_T *uf_tml_self; ///< time spent in a line itself + proftime_T uf_tml_start; ///< start time for current line + proftime_T uf_tml_children; ///< time spent in children for this line + proftime_T uf_tml_wait; ///< start wait time for current line + int uf_tml_idx; ///< index of line being timed; -1 if none + int uf_tml_execed; ///< line being timed was executed + scid_T uf_script_ID; ///< ID of script where function was defined, + // used for s: variables + int uf_refcount; ///< reference count, see func_name_refcount() + funccall_T *uf_scoped; ///< l: local variables for closure + char_u uf_name[1]; ///< name of function (actually longer); can + // start with <SNR>123_ (<SNR> is K_SPECIAL + // KS_EXTRA KE_SNR) +}; + +/// Maximum number of function arguments +#define MAX_FUNC_ARGS 20 +#define VAR_SHORT_LEN 20 // short variable name length +#define FIXVAR_CNT 12 // number of fixed variables + +// structure to hold info for a function that is currently being executed. +struct funccall_S { + ufunc_T *func; ///< function being called + int linenr; ///< next line to be executed + int returned; ///< ":return" used + struct { ///< fixed variables for arguments + dictitem_T var; ///< variable (without room for name) + char_u room[VAR_SHORT_LEN]; ///< room for the name + } fixvar[FIXVAR_CNT]; + dict_T l_vars; ///< l: local function variables + dictitem_T l_vars_var; ///< variable for l: scope + dict_T l_avars; ///< a: argument variables + dictitem_T l_avars_var; ///< variable for a: scope + list_T l_varlist; ///< list for a:000 + listitem_T l_listitems[MAX_FUNC_ARGS]; ///< listitems for a:000 + typval_T *rettv; ///< return value + linenr_T breakpoint; ///< next line with breakpoint or zero + int dbg_tick; ///< debug_tick when breakpoint was set + int level; ///< top nesting level of executed function + proftime_T prof_child; ///< time spent in a child + funccall_T *caller; ///< calling function or NULL + int fc_refcount; ///< number of user functions that reference + // this funccal + int fc_copyID; ///< for garbage collection + garray_T fc_funcs; ///< list of ufunc_T* which keep a reference + // to "func" +}; + +// structure used by trans_function_name() +typedef struct { + dict_T *fd_dict; ///< Dictionary used. + char_u *fd_newkey; ///< New key in "dict" in allocated memory. + dictitem_T *fd_di; ///< Dictionary item used. +} funcdict_T; + struct partial_S { int pt_refcount; ///< Reference count. - char_u *pt_name; ///< Function name. + char_u *pt_name; ///< Function name; when NULL use pt_func->name. + ufunc_T *pt_func; ///< Function pointer; when NULL lookup function + ///< with pt_name. bool pt_auto; ///< when true the partial was created for using ///< dict.member in handle_subscript(). int pt_argc; ///< Number of arguments. diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 2297d14cb6..0fda9a8ae6 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -1475,12 +1475,12 @@ void append_redir(char *const buf, const size_t buflen, void print_line_no_prefix(linenr_T lnum, int use_number, int list) { - char_u numbuf[30]; + char numbuf[30]; if (curwin->w_p_nu || use_number) { - vim_snprintf((char *)numbuf, sizeof(numbuf), - "%*ld ", number_width(curwin), (long)lnum); - msg_puts_attr(numbuf, hl_attr(HLF_N)); /* Highlight line nrs */ + vim_snprintf(numbuf, sizeof(numbuf), "%*" PRIdLINENR " ", + number_width(curwin), lnum); + msg_puts_attr(numbuf, hl_attr(HLF_N)); // Highlight line nrs. } msg_prt_line(ml_get(lnum), list); } @@ -5721,33 +5721,33 @@ void ex_sign(exarg_T *eap) */ static void sign_list_defined(sign_T *sp) { - char_u *p; - smsg("sign %s", sp->sn_name); if (sp->sn_icon != NULL) { - MSG_PUTS(" icon="); + msg_puts(" icon="); msg_outtrans(sp->sn_icon); - MSG_PUTS(_(" (not supported)")); + msg_puts(_(" (not supported)")); } if (sp->sn_text != NULL) { - MSG_PUTS(" text="); + msg_puts(" text="); msg_outtrans(sp->sn_text); } if (sp->sn_line_hl > 0) { - MSG_PUTS(" linehl="); - p = get_highlight_name(NULL, sp->sn_line_hl - 1); - if (p == NULL) - MSG_PUTS("NONE"); - else + msg_puts(" linehl="); + const char *const p = get_highlight_name(NULL, sp->sn_line_hl - 1); + if (p == NULL) { + msg_puts("NONE"); + } else { msg_puts(p); + } } if (sp->sn_text_hl > 0) { - MSG_PUTS(" texthl="); - p = get_highlight_name(NULL, sp->sn_text_hl - 1); - if (p == NULL) - MSG_PUTS("NONE"); - else + msg_puts(" texthl="); + const char *const p = get_highlight_name(NULL, sp->sn_text_hl - 1); + if (p == NULL) { + msg_puts("NONE"); + } else { msg_puts(p); + } } } diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index b4e57bc916..048323b137 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -2250,7 +2250,7 @@ void ex_compiler(exarg_T *eap) // plugin will then skip the settings. Afterwards set // "b:current_compiler" and restore "current_compiler". // Explicitly prepend "g:" to make it work in a function. - old_cur_comp = get_var_value((char_u *)"g:current_compiler"); + old_cur_comp = get_var_value("g:current_compiler"); if (old_cur_comp != NULL) { old_cur_comp = vim_strsave(old_cur_comp); } @@ -2268,7 +2268,7 @@ void ex_compiler(exarg_T *eap) do_cmdline_cmd(":delcommand CompilerSet"); // Set "b:current_compiler" from "current_compiler". - p = get_var_value((char_u *)"g:current_compiler"); + p = get_var_value("g:current_compiler"); if (p != NULL) { set_internal_string_var((char_u *)"b:current_compiler", p); } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index c5625d7882..03f943b1b1 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -583,10 +583,10 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, ++no_wait_return; verbose_enter_scroll(); - smsg(_("line %" PRId64 ": %s"), - (int64_t)sourcing_lnum, cmdline_copy); - if (msg_silent == 0) - msg_puts((char_u *)"\n"); /* don't overwrite this */ + smsg(_("line %" PRIdLINENR ": %s"), sourcing_lnum, cmdline_copy); + if (msg_silent == 0) { + msg_puts("\n"); // don't overwrite this either + } verbose_leave_scroll(); --no_wait_return; @@ -1815,9 +1815,9 @@ static char_u * do_one_cmd(char_u **cmdlinep, errormsg = (char_u *)_("E493: Backwards range given"); goto doend; } - if (ask_yesno((char_u *) - _("Backwards range given, OK to swap"), FALSE) != 'y') + if (ask_yesno(_("Backwards range given, OK to swap"), false) != 'y') { goto doend; + } } lnum = ea.line1; ea.line1 = ea.line2; @@ -2582,27 +2582,29 @@ int modifier_len(char_u *cmd) * Return 2 if there is an exact match. * Return 3 if there is an ambiguous match. */ -int cmd_exists(char_u *name) +int cmd_exists(const char *const name) { exarg_T ea; - int full = FALSE; - int i; - int j; char_u *p; - /* Check command modifiers. */ - for (i = 0; i < (int)ARRAY_SIZE(cmdmods); ++i) { - for (j = 0; name[j] != NUL; ++j) - if (name[j] != cmdmods[i].name[j]) + // Check command modifiers. + for (int i = 0; i < (int)ARRAY_SIZE(cmdmods); i++) { + int j; + for (j = 0; name[j] != NUL; j++) { + if (name[j] != (char)cmdmods[i].name[j]) { break; - if (name[j] == NUL && j >= cmdmods[i].minlen) + } + } + if (name[j] == NUL && j >= cmdmods[i].minlen) { return cmdmods[i].name[j] == NUL ? 2 : 1; + } } /* Check built-in commands and user defined commands. * For ":2match" and ":3match" we need to skip the number. */ - ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name; + ea.cmd = (char_u *)((*name == '2' || *name == '3') ? name + 1 : name); ea.cmdidx = (cmdidx_T)0; + int full = false; p = find_command(&ea, &full); if (p == NULL) return 3; @@ -3940,8 +3942,6 @@ int expand_filename(exarg_T *eap, char_u **cmdlinep, char_u **errormsgp) * Don't do this for: * - replacement that already has been escaped: "##" * - shell commands (may have to use quotes instead). - * - non-unix systems when there is a single argument (spaces don't - * separate arguments then). */ if (!eap->usefilter && !escaped @@ -3952,9 +3952,7 @@ int expand_filename(exarg_T *eap, char_u **cmdlinep, char_u **errormsgp) && eap->cmdidx != CMD_lgrep && eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd -#ifndef UNIX && !(eap->argt & NOSPC) -#endif ) { char_u *l; #ifdef BACKSLASH_IN_FILENAME @@ -4016,28 +4014,6 @@ int expand_filename(exarg_T *eap, char_u **cmdlinep, char_u **errormsgp) } } - // Replace any other wildcards, remove backslashes. -#ifdef UNIX - /* - * Only for Unix we check for more than one file name. - * For other systems spaces are considered to be part - * of the file name. - * Only check here if there is no wildcard, otherwise - * ExpandOne() will check for errors. This allows - * ":e `ls ve*.c`" on Unix. - */ - if (!has_wildcards) - for (p = eap->arg; *p; ++p) { - /* skip escaped characters */ - if (p[1] && (*p == '\\' || *p == Ctrl_V)) - ++p; - else if (ascii_iswhite(*p)) { - *errormsgp = (char_u *)_("E172: Only one file name allowed"); - return FAIL; - } - } -#endif - /* * Halve the number of backslashes (this is Vi compatible). * For Unix, when wildcards are expanded, this is @@ -8246,9 +8222,9 @@ eval_vars ( char_u *resultbuf = NULL; size_t resultlen; buf_T *buf; - int valid = VALID_HEAD + VALID_PATH; /* assume valid result */ - int skip_mod = FALSE; - char_u strbuf[30]; + int valid = VALID_HEAD | VALID_PATH; // Assume valid result. + int skip_mod = false; + char strbuf[30]; *errormsg = NULL; if (escaped != NULL) @@ -8380,8 +8356,8 @@ eval_vars ( "E496: no autocommand buffer number to substitute for \"<abuf>\""); return NULL; } - sprintf((char *)strbuf, "%d", autocmd_bufnr); - result = strbuf; + snprintf(strbuf, sizeof(strbuf), "%d", autocmd_bufnr); + result = (char_u *)strbuf; break; case SPEC_AMATCH: /* match name for autocommand */ @@ -8406,8 +8382,8 @@ eval_vars ( *errormsg = (char_u *)_("E842: no line number to use for \"<slnum>\""); return NULL; } - sprintf((char *)strbuf, "%" PRId64, (int64_t)sourcing_lnum); - result = strbuf; + snprintf(strbuf, sizeof(strbuf), "%" PRIdLINENR, sourcing_lnum); + result = (char_u *)strbuf; break; default: // should not happen diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index f518fa0d66..4bb6f97035 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -482,7 +482,7 @@ static int throw_exception(void *value, int type, char_u *cmdname) msg_scroll = TRUE; /* always scroll up, don't overwrite */ smsg(_("Exception thrown: %s"), excp->value); - msg_puts((char_u *)"\n"); /* don't overwrite this either */ + msg_puts("\n"); // don't overwrite this either if (debug_break_level > 0 || *p_vfile == NUL) cmdline_row = msg_row; @@ -532,15 +532,17 @@ static void discard_exception(except_T *excp, int was_finished) smsg(was_finished ? _("Exception finished: %s") : _("Exception discarded: %s"), excp->value); - msg_puts((char_u *)"\n"); /* don't overwrite this either */ - if (debug_break_level > 0 || *p_vfile == NUL) + msg_puts("\n"); // don't overwrite this either + if (debug_break_level > 0 || *p_vfile == NUL) { cmdline_row = msg_row; - --no_wait_return; - if (debug_break_level > 0) + } + no_wait_return--; + if (debug_break_level > 0) { msg_silent = save_msg_silent; - else + } else { verbose_leave(); - STRCPY(IObuff, saved_IObuff); + } + xstrlcpy((char *)IObuff, (const char *)saved_IObuff, IOSIZE); xfree(saved_IObuff); } if (excp->type != ET_INTERRUPT) @@ -595,7 +597,7 @@ static void catch_exception(except_T *excp) msg_scroll = TRUE; /* always scroll up, don't overwrite */ smsg(_("Exception caught: %s"), excp->value); - msg_puts((char_u *)"\n"); /* don't overwrite this either */ + msg_puts("\n"); // don't overwrite this either if (debug_break_level > 0 || *p_vfile == NUL) cmdline_row = msg_row; @@ -714,7 +716,7 @@ static void report_pending(int action, int pending, void *value) ++no_wait_return; msg_scroll = TRUE; /* always scroll up, don't overwrite */ smsg(mesg, s); - msg_puts((char_u *)"\n"); /* don't overwrite this either */ + msg_puts("\n"); // don't overwrite this either cmdline_row = msg_row; --no_wait_return; if (debug_break_level > 0) diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 2555b64dfd..d99c8d02f7 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1879,7 +1879,7 @@ getexmodeline ( vcol = indent; while (indent >= 8) { ga_append(&line_ga, TAB); - msg_puts((char_u *)" "); + msg_puts(" "); indent -= 8; } while (indent-- > 0) { @@ -2608,7 +2608,7 @@ static void redrawcmdprompt(void) if (ccline.cmdfirstc != NUL) msg_putchar(ccline.cmdfirstc); if (ccline.cmdprompt != NULL) { - msg_puts_attr(ccline.cmdprompt, ccline.cmdattr); + msg_puts_attr((const char *)ccline.cmdprompt, ccline.cmdattr); ccline.cmdindent = msg_col + (msg_row - cmdline_row) * Columns; /* do the reverse of set_cmdspos() */ if (ccline.cmdfirstc != NUL) @@ -3317,7 +3317,7 @@ static int showmatches(expand_T *xp, int wildmenu) msg_outtrans_attr(files_found[k], hl_attr(HLF_D)); p = files_found[k] + STRLEN(files_found[k]) + 1; msg_advance(maxlen + 1); - msg_puts(p); + msg_puts((const char *)p); msg_advance(maxlen + 3); msg_puts_long_attr(p + 2, hl_attr(HLF_D)); break; @@ -3759,6 +3759,8 @@ static void cleanup_help_tags(int num_file, char_u **file) } } +typedef char_u *(*ExpandFunc)(expand_T *, int); + /* * Do the expansion based on xp->xp_context and "pat". */ @@ -3898,39 +3900,38 @@ ExpandFromContext ( else { static struct expgen { int context; - char_u *((*func)(expand_T *, int)); + ExpandFunc func; int ic; int escaped; - } tab[] = - { - {EXPAND_COMMANDS, get_command_name, FALSE, TRUE}, - {EXPAND_BEHAVE, get_behave_arg, TRUE, TRUE}, - {EXPAND_HISTORY, get_history_arg, TRUE, TRUE}, - {EXPAND_USER_COMMANDS, get_user_commands, FALSE, TRUE}, - {EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, FALSE, TRUE}, - {EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, FALSE, TRUE}, - {EXPAND_USER_NARGS, get_user_cmd_nargs, FALSE, TRUE}, - {EXPAND_USER_COMPLETE, get_user_cmd_complete, FALSE, TRUE}, - {EXPAND_USER_VARS, get_user_var_name, FALSE, TRUE}, - {EXPAND_FUNCTIONS, get_function_name, FALSE, TRUE}, - {EXPAND_USER_FUNC, get_user_func_name, FALSE, TRUE}, - {EXPAND_EXPRESSION, get_expr_name, FALSE, TRUE}, - {EXPAND_MENUS, get_menu_name, FALSE, TRUE}, - {EXPAND_MENUNAMES, get_menu_names, FALSE, TRUE}, - {EXPAND_SYNTAX, get_syntax_name, TRUE, TRUE}, - {EXPAND_SYNTIME, get_syntime_arg, TRUE, TRUE}, - {EXPAND_HIGHLIGHT, get_highlight_name, TRUE, TRUE}, - {EXPAND_EVENTS, get_event_name, TRUE, TRUE}, - {EXPAND_AUGROUP, get_augroup_name, TRUE, TRUE}, - {EXPAND_CSCOPE, get_cscope_name, TRUE, TRUE}, - {EXPAND_SIGN, get_sign_name, TRUE, TRUE}, - {EXPAND_PROFILE, get_profile_name, TRUE, TRUE}, + } tab[] = { + { EXPAND_COMMANDS, get_command_name, false, true }, + { EXPAND_BEHAVE, get_behave_arg, true, true }, + { EXPAND_HISTORY, get_history_arg, true, true }, + { EXPAND_USER_COMMANDS, get_user_commands, false, true }, + { EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, false, true }, + { EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, false, true }, + { EXPAND_USER_NARGS, get_user_cmd_nargs, false, true }, + { EXPAND_USER_COMPLETE, get_user_cmd_complete, false, true }, + { EXPAND_USER_VARS, get_user_var_name, false, true }, + { EXPAND_FUNCTIONS, get_function_name, false, true }, + { EXPAND_USER_FUNC, get_user_func_name, false, true }, + { EXPAND_EXPRESSION, get_expr_name, false, true }, + { EXPAND_MENUS, get_menu_name, false, true }, + { EXPAND_MENUNAMES, get_menu_names, false, true }, + { EXPAND_SYNTAX, get_syntax_name, true, true }, + { EXPAND_SYNTIME, get_syntime_arg, true, true }, + { EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, true }, + { EXPAND_EVENTS, get_event_name, true, true }, + { EXPAND_AUGROUP, get_augroup_name, true, true }, + { EXPAND_CSCOPE, get_cscope_name, true, true }, + { EXPAND_SIGN, get_sign_name, true, true }, + { EXPAND_PROFILE, get_profile_name, true, true }, #ifdef HAVE_WORKING_LIBINTL - {EXPAND_LANGUAGE, get_lang_arg, TRUE, FALSE}, - {EXPAND_LOCALES, get_locales, TRUE, FALSE}, + { EXPAND_LANGUAGE, get_lang_arg, true, false }, + { EXPAND_LOCALES, get_locales, true, false }, #endif - {EXPAND_ENV_VARS, get_env_name, TRUE, TRUE}, - {EXPAND_USER, get_users, TRUE, FALSE}, + { EXPAND_ENV_VARS, get_env_name, true, true }, + { EXPAND_USER, get_users, true, false }, }; int i; diff --git a/src/nvim/farsi.c b/src/nvim/farsi.c index eb22ad1428..678e6d2e14 100644 --- a/src/nvim/farsi.c +++ b/src/nvim/farsi.c @@ -1593,7 +1593,7 @@ void conv_to_pvim(void) // Assume the screen has been messed up: clear it and redraw. redraw_later(CLEAR); - MSG_ATTR(farsi_text_1, hl_attr(HLF_S)); + MSG_ATTR((const char *)farsi_text_1, hl_attr(HLF_S)); } /// Convert the Farsi VIM into Farsi 3342 standard. @@ -1614,7 +1614,7 @@ void conv_to_pstd(void) // Assume the screen has been messed up: clear it and redraw. redraw_later(CLEAR); - MSG_ATTR(farsi_text_2, hl_attr(HLF_S)); + msg_attr((const char *)farsi_text_2, hl_attr(HLF_S)); } /// left-right swap the characters in buf[len]. diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index d733ba311a..79a39c6503 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -636,9 +636,8 @@ char_u *vim_findfile(void *search_ctx_arg) if (p_verbose >= 5) { verbose_enter_scroll(); smsg("Already Searched: %s (%s)", - stackp->ffs_fix_path, stackp->ffs_wc_path); - /* don't overwrite this either */ - msg_puts((char_u *)"\n"); + stackp->ffs_fix_path, stackp->ffs_wc_path); + msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); } #endif @@ -650,8 +649,7 @@ char_u *vim_findfile(void *search_ctx_arg) verbose_enter_scroll(); smsg("Searching: %s (%s)", stackp->ffs_fix_path, stackp->ffs_wc_path); - /* don't overwrite this either */ - msg_puts((char_u *)"\n"); + msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); } #endif @@ -809,10 +807,8 @@ char_u *vim_findfile(void *search_ctx_arg) ) == FAIL) { if (p_verbose >= 5) { verbose_enter_scroll(); - smsg("Already: %s", - file_path); - /* don't overwrite this either */ - msg_puts((char_u *)"\n"); + smsg("Already: %s", file_path); + msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); } continue; @@ -837,8 +833,7 @@ char_u *vim_findfile(void *search_ctx_arg) if (p_verbose >= 5) { verbose_enter_scroll(); smsg("HIT: %s", file_path); - /* don't overwrite this either */ - msg_puts((char_u *)"\n"); + msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); } #endif @@ -999,10 +994,8 @@ static ff_visited_list_hdr_T *ff_get_visited_list(char_u *filename, ff_visited_l #ifdef FF_VERBOSE if (p_verbose >= 5) { verbose_enter_scroll(); - smsg("ff_get_visited_list: FOUND list for %s", - filename); - /* don't overwrite this either */ - msg_puts((char_u *)"\n"); + smsg("ff_get_visited_list: FOUND list for %s", filename); + msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); } #endif @@ -1016,8 +1009,7 @@ static ff_visited_list_hdr_T *ff_get_visited_list(char_u *filename, ff_visited_l if (p_verbose >= 5) { verbose_enter_scroll(); smsg("ff_get_visited_list: new list for %s", filename); - /* don't overwrite this either */ - msg_puts((char_u *)"\n"); + msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); } #endif diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index cdb912ca94..4ea5121a91 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -3895,15 +3895,15 @@ static int check_mtime(buf_T *buf, FileInfo *file_info) if (buf->b_mtime_read != 0 && time_differs(file_info->stat.st_mtim.tv_sec, buf->b_mtime_read)) { - msg_scroll = TRUE; /* don't overwrite messages here */ - msg_silent = 0; /* must give this prompt */ - /* don't use emsg() here, don't want to flush the buffers */ - MSG_ATTR(_("WARNING: The file has been changed since reading it!!!"), - hl_attr(HLF_E)); - if (ask_yesno((char_u *)_("Do you really want to write to it"), - TRUE) == 'n') + msg_scroll = true; // Don't overwrite messages here. + msg_silent = 0; // Must give this prompt. + // Don't use emsg() here, don't want to flush the buffers. + msg_attr(_("WARNING: The file has been changed since reading it!!!"), + hl_attr(HLF_E)); + if (ask_yesno(_("Do you really want to write to it"), true) == 'n') { return FAIL; - msg_scroll = FALSE; /* always overwrite the file message now */ + } + msg_scroll = false; // Always overwrite the file message now. } return OK; } @@ -4778,8 +4778,8 @@ check_timestamps ( --no_wait_return; need_check_timestamps = FALSE; if (need_wait_return && didit == 2) { - /* make sure msg isn't overwritten */ - msg_puts((char_u *)"\n"); + // make sure msg isn't overwritten + msg_puts("\n"); ui_flush(); } } @@ -5006,10 +5006,9 @@ buf_check_timestamp ( } else { if (!autocmd_busy) { msg_start(); - msg_puts_attr((char_u *) tbuf, hl_attr(HLF_E) + MSG_HIST); + msg_puts_attr(tbuf, hl_attr(HLF_E) + MSG_HIST); if (*mesg2 != NUL) { - msg_puts_attr((char_u *)mesg2, - hl_attr(HLF_W) + MSG_HIST); + msg_puts_attr(mesg2, hl_attr(HLF_W) + MSG_HIST); } msg_clr_eos(); (void)msg_end(); @@ -5385,13 +5384,9 @@ char_u *vim_tempname(void) static AutoPatCmd *active_apc_list = NULL; /* stack of active autocommands */ -/* - * augroups stores a list of autocmd group names. - */ -static garray_T augroups = {0, 0, sizeof(char_u *), 10, NULL}; -#define AUGROUP_NAME(i) (((char_u **)augroups.ga_data)[i]) -// use get_deleted_augroup() to get this -static char_u *deleted_augroup = NULL; +/// List of autocmd group names +static garray_T augroups = { 0, 0, sizeof(char_u *), 10, NULL }; +#define AUGROUP_NAME(i) (((char **)augroups.ga_data)[i]) /* * The ID of the current group. Group 0 is the default one. @@ -5406,10 +5401,14 @@ static event_T last_event; static int last_group; static int autocmd_blocked = 0; /* block all autocmds */ -static char_u *get_deleted_augroup(void) +// use get_deleted_augroup() to get this +static const char *deleted_augroup = NULL; + +static inline const char *get_deleted_augroup(void) + FUNC_ATTR_ALWAYS_INLINE { if (deleted_augroup == NULL) { - deleted_augroup = (char_u *)_("--Deleted--"); + deleted_augroup = _("--Deleted--"); } return deleted_augroup; } @@ -5438,7 +5437,7 @@ static void show_autocmd(AutoPat *ap, event_T event) } else { msg_puts_attr(AUGROUP_NAME(ap->group), hl_attr(HLF_T)); } - msg_puts((char_u *)" "); + msg_puts(" "); } msg_puts_attr(event_nr2name(event), hl_attr(HLF_T)); last_event = event; @@ -5567,7 +5566,7 @@ void aubuflocal_remove(buf_T *buf) if (p_verbose >= 6) { verbose_enter(); smsg(_("auto-removing autocommand: %s <buffer=%d>"), - event_nr2name(event), buf->b_fnum); + event_nr2name(event), buf->b_fnum); verbose_leave(); } } @@ -5590,9 +5589,10 @@ static int au_new_group(char_u *name) ga_grow(&augroups, 1); } - AUGROUP_NAME(i) = vim_strsave(name); - if (i == augroups.ga_len) - ++augroups.ga_len; + AUGROUP_NAME(i) = xstrdup((char *)name); + if (i == augroups.ga_len) { + augroups.ga_len++; + } } return i; @@ -5626,7 +5626,7 @@ static void au_del_group(char_u *name) } xfree(AUGROUP_NAME(i)); if (in_use) { - AUGROUP_NAME(i) = get_deleted_augroup(); + AUGROUP_NAME(i) = (char *)get_deleted_augroup(); } else { AUGROUP_NAME(i) = NULL; } @@ -5680,7 +5680,7 @@ void do_augroup(char_u *arg, int del_group) for (int i = 0; i < augroups.ga_len; ++i) { if (AUGROUP_NAME(i) != NULL) { msg_puts(AUGROUP_NAME(i)); - msg_puts((char_u *)" "); + msg_puts(" "); } } msg_clr_eos(); @@ -5691,23 +5691,19 @@ void do_augroup(char_u *arg, int del_group) #if defined(EXITFREE) void free_all_autocmds(void) { - int i; - char_u *s; - for (current_augroup = -1; current_augroup < augroups.ga_len; current_augroup++) { do_autocmd((char_u *)"", true); } - for (i = 0; i < augroups.ga_len; i++) { - s = ((char_u **)(augroups.ga_data))[i]; - if (s != get_deleted_augroup()) { - xfree(s); - } + for (int i = 0; i < augroups.ga_len; i++) { + char *const s = ((char **)(augroups.ga_data))[i]; + if ((const char *)s != get_deleted_augroup()) { + xfree(s); + } } ga_clear(&augroups); } - #endif /* @@ -5715,13 +5711,13 @@ void free_all_autocmds(void) * Return NUM_EVENTS if the event name was not found. * Return a pointer to the next event name in "end". */ -static event_T event_name2nr(char_u *start, char_u **end) +static event_T event_name2nr(const char_u *start, char_u **end) { - char_u *p; + const char_u *p; int i; int len; - // the event name ends with end of line, '|', a blank or a comma */ + // the event name ends with end of line, '|', a blank or a comma for (p = start; *p && !ascii_iswhite(*p) && *p != ',' && *p != '|'; p++) { } for (i = 0; event_names[i].name != NULL; i++) { @@ -5730,25 +5726,32 @@ static event_T event_name2nr(char_u *start, char_u **end) break; } } - if (*p == ',') - ++p; - *end = p; - if (event_names[i].name == NULL) + if (*p == ',') { + p++; + } + *end = (char_u *)p; + if (event_names[i].name == NULL) { return NUM_EVENTS; + } return event_names[i].event; } -/* - * Return the name for event "event". - */ -static char_u *event_nr2name(event_T event) +/// Return the name for event +/// +/// @param[in] event Event to return name for. +/// +/// @return Event name, static string. Returns "Unknown" for unknown events. +static const char *event_nr2name(event_T event) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_CONST { int i; - for (i = 0; event_names[i].name != NULL; ++i) - if (event_names[i].event == event) - return (char_u *)event_names[i].name; - return (char_u *)"Unknown"; + for (i = 0; event_names[i].name != NULL; i++) { + if (event_names[i].event == event) { + return event_names[i].name; + } + } + return "Unknown"; } /* @@ -7013,7 +7016,6 @@ auto_next_pat ( { AutoPat *ap; AutoCmd *cp; - char_u *name; char *s; xfree(sourcing_name); @@ -7032,11 +7034,13 @@ auto_next_pat ( ? match_file_pat(NULL, &ap->reg_prog, apc->fname, apc->sfname, apc->tail, ap->allow_dirs) : ap->buflocal_nr == apc->arg_bufnr) { - name = event_nr2name(apc->event); + const char *const name = event_nr2name(apc->event); s = _("%s Auto commands for \"%s\""); - sourcing_name = xmalloc(STRLEN(s) + STRLEN(name) + ap->patlen + 1); - sprintf((char *)sourcing_name, s, - (char *)name, (char *)ap->pat); + const size_t sourcing_name_len = (STRLEN(s) + strlen(name) + ap->patlen + + 1); + sourcing_name = xmalloc(sourcing_name_len); + snprintf((char *)sourcing_name, sourcing_name_len, s, name, + (char *)ap->pat); if (p_verbose >= 8) { verbose_enter(); smsg(_("Executing %s"), sourcing_name); @@ -7102,7 +7106,7 @@ char_u *getnextac(int c, void *cookie, int indent) if (p_verbose >= 9) { verbose_enter_scroll(); smsg(_("autocommand %s"), ac->cmd); - msg_puts((char_u *)"\n"); /* don't overwrite this either */ + msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); } retval = vim_strsave(ac->cmd); @@ -7168,15 +7172,17 @@ bool has_autocmd(event_T event, char_u *sfname, buf_T *buf) */ char_u *get_augroup_name(expand_T *xp, int idx) { - if (idx == augroups.ga_len) /* add "END" add the end */ + if (idx == augroups.ga_len) { // add "END" add the end return (char_u *)"END"; - if (idx >= augroups.ga_len) /* end of list */ + } + if (idx >= augroups.ga_len) { // end of list return NULL; + } if (AUGROUP_NAME(idx) == NULL || AUGROUP_NAME(idx) == get_deleted_augroup()) { // skip deleted entries return (char_u *)""; } - return AUGROUP_NAME(idx); // return a name + return (char_u *)AUGROUP_NAME(idx); } static int include_groups = FALSE; @@ -7238,18 +7244,22 @@ char_u *get_event_name(expand_T *xp, int idx) || AUGROUP_NAME(idx) == get_deleted_augroup()) { return (char_u *)""; // skip deleted entries } - return AUGROUP_NAME(idx); // return a name + return (char_u *)AUGROUP_NAME(idx); } return (char_u *)event_names[idx - augroups.ga_len].name; } -/// Return true if autocmd "event" is supported. -bool autocmd_supported(char_u *event) +/// Check whether given autocommand is supported +/// +/// @param[in] event Event to check. +/// +/// @return True if it is, false otherwise. +bool autocmd_supported(const char *const event) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { char_u *p; - - return event_name2nr(event, &p) != NUM_EVENTS; + return event_name2nr((const char_u *)event, &p) != NUM_EVENTS; } /// Return true if an autocommand is defined for a group, event and @@ -7264,26 +7274,24 @@ bool autocmd_supported(char_u *event) /// exists("#Event#pat") /// /// @param arg autocommand string -bool au_exists(const char_u *arg) FUNC_ATTR_WARN_UNUSED_RESULT +bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT { - char_u *arg_save; - char_u *pattern = NULL; - char_u *event_name; - char_u *p; event_T event; AutoPat *ap; buf_T *buflocal_buf = NULL; int group; bool retval = false; - /* Make a copy so that we can change the '#' chars to a NUL. */ - arg_save = vim_strsave(arg); - p = vim_strchr(arg_save, '#'); - if (p != NULL) + // Make a copy so that we can change the '#' chars to a NUL. + char *const arg_save = xstrdup(arg); + char *p = strchr(arg_save, '#'); + if (p != NULL) { *p++ = NUL; + } - /* First, look for an autocmd group name */ - group = au_find_group(arg_save); + // First, look for an autocmd group name. + group = au_find_group((char_u *)arg_save); + char *event_name; if (group == AUGROUP_ERROR) { /* Didn't match a group name, assume the first argument is an event. */ group = AUGROUP_ALL; @@ -7295,17 +7303,18 @@ bool au_exists(const char_u *arg) FUNC_ATTR_WARN_UNUSED_RESULT goto theend; } - /* Must be "Group#Event" or "Group#Event#pat". */ + // Must be "Group#Event" or "Group#Event#pat". event_name = p; - p = vim_strchr(event_name, '#'); - if (p != NULL) - *p++ = NUL; /* "Group#Event#pat" */ + p = strchr(event_name, '#'); + if (p != NULL) { + *p++ = NUL; // "Group#Event#pat" + } } - pattern = p; /* "pattern" is NULL when there is no pattern */ + char *pattern = p; // "pattern" is NULL when there is no pattern. - /* find the index (enum) for the event name */ - event = event_name2nr(event_name, &p); + // Find the index (enum) for the event name. + event = event_name2nr((char_u *)event_name, (char_u **)&p); /* return FALSE if the event name is not recognized */ if (event == NUM_EVENTS) @@ -7331,7 +7340,7 @@ bool au_exists(const char_u *arg) FUNC_ATTR_WARN_UNUSED_RESULT && (group == AUGROUP_ALL || ap->group == group) && (pattern == NULL || (buflocal_buf == NULL - ? fnamecmp(ap->pat, pattern) == 0 + ? fnamecmp(ap->pat, (char_u *)pattern) == 0 : ap->buflocal_nr == buflocal_buf->b_fnum))) { retval = true; break; diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index fccbd69dbf..b7c6fd41f2 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1327,8 +1327,9 @@ int using_script(void) void before_blocking(void) { updatescript(0); - if (may_garbage_collect) - garbage_collect(); + if (may_garbage_collect) { + garbage_collect(false); + } } /* @@ -1366,10 +1367,11 @@ int vgetc(void) char_u buf[MB_MAXBYTES + 1]; int i; - /* Do garbage collection when garbagecollect() was called previously and - * we are now at the toplevel. */ - if (may_garbage_collect && want_garbage_collect) - garbage_collect(); + // Do garbage collection when garbagecollect() was called previously and + // we are now at the toplevel. + if (may_garbage_collect && want_garbage_collect) { + garbage_collect(false); + } /* * If a character was put back with vungetc, it was already processed. @@ -1911,17 +1913,13 @@ static int vgetorpeek(int advance) if ((mp == NULL || max_mlen >= mp_match_len) && keylen != KEYLEN_PART_MAP) { - /* - * When no matching mapping found or found a - * non-matching mapping that matches at least what the - * matching mapping matched: - * Check if we have a terminal code, when: - * mapping is allowed, - * keys have not been mapped, - * and not an ESC sequence, not in insert mode or - * p_ek is on, - * and when not timed out, - */ + // When no matching mapping found or found a non-matching mapping + // that matches at least what the matching mapping matched: + // Check if we have a terminal code, when: + // mapping is allowed, + // keys have not been mapped, + // and not an ESC sequence, not in insert mode, + // and when not timed out. if ((no_mapping == 0 || allow_keys != 0) && (typebuf.tb_maplen == 0 || (p_remap && typebuf.tb_noremap[ @@ -3163,11 +3161,11 @@ map_clear_int ( } } -/* - * Return characters to represent the map mode in an allocated string. - * Returns NULL when out of memory. - */ -char_u *map_mode_to_chars(int mode) +/// Return characters to represent the map mode in an allocated string +/// +/// @return [allocated] NUL-terminated string with characters. +char *map_mode_to_chars(int mode) + FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_RET { garray_T mapmode; @@ -3200,7 +3198,7 @@ char_u *map_mode_to_chars(int mode) } ga_append(&mapmode, NUL); - return (char_u *)mapmode.ga_data; + return (char *)mapmode.ga_data; } static void @@ -3209,8 +3207,7 @@ showmap ( int local /* TRUE for buffer-local map */ ) { - int len = 1; - char_u *mapchars; + size_t len = 1; if (msg_didout || msg_silent != 0) { msg_putchar('\n'); @@ -3218,29 +3215,30 @@ showmap ( return; } - mapchars = map_mode_to_chars(mp->m_mode); - if (mapchars != NULL) { + { + char *const mapchars = map_mode_to_chars(mp->m_mode); msg_puts(mapchars); - len = (int)STRLEN(mapchars); + len = strlen(mapchars); xfree(mapchars); } while (++len <= 3) msg_putchar(' '); - /* Display the LHS. Get length of what we write. */ - len = msg_outtrans_special(mp->m_keys, TRUE); + // Display the LHS. Get length of what we write. + len = (size_t)msg_outtrans_special(mp->m_keys, true); do { msg_putchar(' '); /* padd with blanks */ ++len; } while (len < 12); - if (mp->m_noremap == REMAP_NONE) - msg_puts_attr((char_u *)"*", hl_attr(HLF_8)); - else if (mp->m_noremap == REMAP_SCRIPT) - msg_puts_attr((char_u *)"&", hl_attr(HLF_8)); - else + if (mp->m_noremap == REMAP_NONE) { + msg_puts_attr("*", hl_attr(HLF_8)); + } else if (mp->m_noremap == REMAP_SCRIPT) { + msg_puts_attr("&", hl_attr(HLF_8)); + } else { msg_putchar(' '); + } if (local) msg_putchar('@'); @@ -3249,11 +3247,11 @@ showmap ( /* Use FALSE below if we only want things like <Up> to show up as such on * the rhs, and not M-x etc, TRUE gets both -- webb */ - if (*mp->m_str == NUL) - msg_puts_attr((char_u *)"<Nop>", hl_attr(HLF_8)); - else { - /* Remove escaping of CSI, because "m_str" is in a format to be used - * as typeahead. */ + if (*mp->m_str == NUL) { + msg_puts_attr("<Nop>", hl_attr(HLF_8)); + } else { + // Remove escaping of CSI, because "m_str" is in a format to be used + // as typeahead. char_u *s = vim_strsave(mp->m_str); vim_unescape_csi(s); msg_outtrans_special(s, FALSE); diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 4e20be3fca..ba4f6e2b3b 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -882,8 +882,13 @@ EXTERN int swap_exists_did_quit INIT(= FALSE); EXTERN char_u IObuff[IOSIZE]; ///< Buffer for sprintf, I/O, etc. EXTERN char_u NameBuff[MAXPATHL]; ///< Buffer for expanding file names EXTERN char_u msg_buf[MSG_BUF_LEN]; ///< Small buffer for messages -EXTERN char os_buf[MAX(MAXPATHL, IOSIZE)]; ///< Buffer for the os/ layer - +EXTERN char os_buf[ ///< Buffer for the os/ layer +#if MAXPATHL > IOSIZE +MAXPATHL +#else +IOSIZE +#endif +]; /* When non-zero, postpone redrawing. */ EXTERN int RedrawingDisabled INIT(= 0); @@ -1230,6 +1235,9 @@ EXTERN char *ignoredp; EXTERN bool in_free_unref_items INIT(= false); +// Used for checking if local variables or arguments used in a lambda. +EXTERN int *eval_lavars_used INIT(= NULL); + // If a msgpack-rpc channel should be started over stdin/stdout EXTERN bool embedded_mode INIT(= false); diff --git a/src/nvim/hashtab.c b/src/nvim/hashtab.c index 376f33e23e..b14bb01c0e 100644 --- a/src/nvim/hashtab.c +++ b/src/nvim/hashtab.c @@ -82,22 +82,42 @@ void hash_clear_all(hashtab_T *ht, unsigned int off) /// used for that key. /// WARNING: Returned pointer becomes invalid as soon as the hash table /// is changed in any way. -hashitem_T* hash_find(hashtab_T *ht, char_u *key) +hashitem_T *hash_find(hashtab_T *ht, const char_u *key) { - return hash_lookup(ht, key, hash_hash(key)); + return hash_lookup(ht, (const char *)key, STRLEN(key), hash_hash(key)); +} + +/// Like hash_find, but key is not NUL-terminated +/// +/// @param[in] ht Hashtab to look in. +/// @param[in] key Key of the looked-for item. Must not be NULL. +/// @param[in] len Key length. +/// +/// @return Pointer to the hash item corresponding to the given key. +/// If not found, then return pointer to the empty item that would be +/// used for that key. +/// +/// @warning Returned pointer becomes invalid as soon as the hash table +/// is changed in any way. +hashitem_T *hash_find_len(hashtab_T *ht, const char *key, const size_t len) +{ + return hash_lookup(ht, key, len, hash_hash_len(key, len)); } /// Like hash_find(), but caller computes "hash". /// -/// @param key The key of the looked-for item. Must not be NULL. -/// @param hash The precomputed hash for the key. +/// @param[in] key The key of the looked-for item. Must not be NULL. +/// @param[in] key_len Key length. +/// @param[in] hash The precomputed hash for the key. /// /// @return Pointer to the hashitem corresponding to the given key. /// If not found, then return pointer to the empty item that would be /// used for that key. /// WARNING: Returned pointer becomes invalid as soon as the hash table /// is changed in any way. -hashitem_T* hash_lookup(hashtab_T *ht, char_u *key, hash_T hash) +hashitem_T *hash_lookup(hashtab_T *const ht, + const char *const key, const size_t key_len, + const hash_T hash) { #ifdef HT_DEBUG hash_count_lookup++; @@ -117,7 +137,9 @@ hashitem_T* hash_lookup(hashtab_T *ht, char_u *key, hash_T hash) hashitem_T *freeitem = NULL; if (hi->hi_key == HI_KEY_REMOVED) { freeitem = hi; - } else if ((hi->hi_hash == hash) && (STRCMP(hi->hi_key, key) == 0)) { + } else if ((hi->hi_hash == hash) + && (STRNCMP(hi->hi_key, key, key_len) == 0) + && hi->hi_key[key_len] == NUL) { return hi; } @@ -142,7 +164,8 @@ hashitem_T* hash_lookup(hashtab_T *ht, char_u *key, hash_T hash) if ((hi->hi_hash == hash) && (hi->hi_key != HI_KEY_REMOVED) - && (STRCMP(hi->hi_key, key) == 0)) { + && (STRNCMP(hi->hi_key, key, key_len) == 0) + && hi->hi_key[key_len] == NUL) { return hi; } @@ -179,7 +202,7 @@ void hash_debug_results(void) int hash_add(hashtab_T *ht, char_u *key) { hash_T hash = hash_hash(key); - hashitem_T *hi = hash_lookup(ht, key, hash); + hashitem_T *hi = hash_lookup(ht, (const char *)key, STRLEN(key), hash); if (!HASHITEM_EMPTY(hi)) { EMSG2(_(e_intern2), "hash_add()"); return FAIL; @@ -359,13 +382,16 @@ static void hash_may_resize(hashtab_T *ht, size_t minitems) ht->ht_filled = ht->ht_used; } +#define HASH_CYCLE_BODY(hash, p) \ + hash = hash * 101 + *p++ + /// Get the hash number for a key. /// /// If you think you know a better hash function: Compile with HT_DEBUG set and /// run a script that uses hashtables a lot. Vim will then print statistics /// when exiting. Try that with the current hash algorithm and yours. The /// lower the percentage the better. -hash_T hash_hash(char_u *key) +hash_T hash_hash(const char_u *key) { hash_T hash = *key; @@ -375,14 +401,43 @@ hash_T hash_hash(char_u *key) // A simplistic algorithm that appears to do very well. // Suggested by George Reilly. - char_u *p = key + 1; + const uint8_t *p = key + 1; while (*p != NUL) { - hash = hash * 101 + *p++; + HASH_CYCLE_BODY(hash, p); } return hash; } +/// Get the hash number for a key that is not a NUL-terminated string +/// +/// @warning Function does not check whether key contains NUL. But you will not +/// be able to get hash entry in this case. +/// +/// @param[in] key Key. +/// @param[in] len Key length. +/// +/// @return Key hash. +hash_T hash_hash_len(const char *key, const size_t len) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (len == 0) { + return 0; + } + + hash_T hash = *(uint8_t *)key; + const uint8_t *end = (uint8_t *)key + len; + + const uint8_t *p = (const uint8_t *)key + 1; + while (p < end) { + HASH_CYCLE_BODY(hash, p); + } + + return hash; +} + +#undef HASH_CYCLE_BODY + /// Function to get HI_KEY_REMOVED value /// /// Used for testing because luajit ffi does not allow getting addresses of diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 9d50058257..a05ac5f877 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -2008,8 +2008,9 @@ static int cs_reset(exarg_T *eap) xfree(pplist); xfree(fllist); - if (p_csverbose) - MSG_ATTR(_("All cscope databases reset"), hl_attr(HLF_R) | MSG_HIST); + if (p_csverbose) { + msg_attr(_("All cscope databases reset"), hl_attr(HLF_R) | MSG_HIST); + } return CSCOPE_SUCCESS; } /* cs_reset */ diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index 94bbaf4239..72b0d0aa40 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -843,10 +843,10 @@ char_u *replace_termcodes(const char_u *from, const size_t from_len, // If "mapleader" or "maplocalleader" isn't set use a backslash. if (end - src >= 7 && STRNICMP(src, "<Leader>", 8) == 0) { len = 8; - p = get_var_value((char_u *)"g:mapleader"); + p = get_var_value("g:mapleader"); } else if (end - src >= 12 && STRNICMP(src, "<LocalLeader>", 13) == 0) { len = 13; - p = get_var_value((char_u *)"g:maplocalleader"); + p = get_var_value("g:maplocalleader"); } else { len = 0; p = NULL; diff --git a/src/nvim/main.c b/src/nvim/main.c index 88c1990786..fab968836c 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -624,8 +624,9 @@ void getout(int exitval) iconv_end(); #endif cs_end(); - if (garbage_collect_at_exit) - garbage_collect(); + if (garbage_collect_at_exit) { + garbage_collect(false); + } mch_exit(exitval); } diff --git a/src/nvim/memline.c b/src/nvim/memline.c index b8891f6560..91dab16e27 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1410,8 +1410,8 @@ recover_names ( for (int i = 0; i < num_files; ++i) { /* print the swap file name */ msg_outnum((long)++file_count); - MSG_PUTS(". "); - msg_puts(path_tail(files[i])); + msg_puts(". "); + msg_puts((const char *)path_tail(files[i])); msg_putchar('\n'); (void)swapfile_info(files[i]); } diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 7c0eee64dd..529978e3f0 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -755,10 +755,11 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth) else msg_putchar(' '); MSG_PUTS(" "); - if (*menu->strings[bit] == NUL) - msg_puts_attr((char_u *)"<Nop>", hl_attr(HLF_8)); - else - msg_outtrans_special(menu->strings[bit], FALSE); + if (*menu->strings[bit] == NUL) { + msg_puts_attr("<Nop>", hl_attr(HLF_8)); + } else { + msg_outtrans_special(menu->strings[bit], false); + } } } else { if (menu == NULL) { diff --git a/src/nvim/message.c b/src/nvim/message.c index ad1c63eeac..5427ae7793 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -7,7 +7,6 @@ #include <stdbool.h> #include <stdarg.h> #include <string.h> -#include <math.h> #include "nvim/vim.h" #include "nvim/ascii.h" @@ -129,9 +128,9 @@ int verb_msg(char_u *s) return n; } -int msg_attr(char_u *s, int attr) FUNC_ATTR_NONNULL_ARG(1) +int msg_attr(const char *s, const int attr) FUNC_ATTR_NONNULL_ARG(1) { - return msg_attr_keep(s, attr, FALSE); + return msg_attr_keep((char_u *)s, attr, false); } int @@ -165,8 +164,9 @@ msg_attr_keep ( || (*s != '<' && last_msg_hist != NULL && last_msg_hist->msg != NULL - && STRCMP(s, last_msg_hist->msg))) - add_msg_hist(s, -1, attr); + && STRCMP(s, last_msg_hist->msg))) { + add_msg_hist((const char *)s, -1, attr); + } /* When displaying keep_msg, don't let msg_start() free it, caller must do * that. */ @@ -348,7 +348,7 @@ int smsg_attr(int attr, char *s, ...) va_start(arglist, s); vim_vsnprintf((char *)IObuff, IOSIZE, s, arglist, NULL); va_end(arglist); - return msg_attr(IObuff, attr); + return msg_attr((const char *)IObuff, attr); } /* @@ -382,39 +382,40 @@ static int other_sourcing_name(void) return FALSE; } -/// Get the message about the source, as used for an error message. -/// Returns an allocated string with room for one more character. -/// Returns NULL when no message is to be given. -static char_u *get_emsg_source(void) +/// Get the message about the source, as used for an error message +/// +/// @return [allocated] String with room for one more character. NULL when no +/// message is to be given. +static char *get_emsg_source(void) + FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT { if (sourcing_name != NULL && other_sourcing_name()) { - char_u *p = (char_u *)_("Error detected while processing %s:"); - size_t len = STRLEN(sourcing_name) + STRLEN(p) + 1; - char_u *buf = xmalloc(len); - snprintf((char *)buf, len, (char *)p, sourcing_name); + const char *const p = _("Error detected while processing %s:"); + const size_t buf_len = STRLEN(sourcing_name) + strlen(p) + 1; + char *const buf = xmalloc(buf_len); + snprintf(buf, buf_len, p, sourcing_name); return buf; } return NULL; } -/* - * Get the message about the source lnum, as used for an error message. - * Returns an allocated string with room for one more character. - * Returns NULL when no message is to be given. - */ -static char_u *get_emsg_lnum(void) +/// Get the message about the source lnum, as used for an error message. +/// +/// @return [allocated] String with room for one more character. NULL when no +/// message is to be given. +static char *get_emsg_lnum(void) + FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT { - char_u *Buf, *p; - - /* lnum is 0 when executing a command from the command line - * argument, we don't want a line number then */ + // lnum is 0 when executing a command from the command line + // argument, we don't want a line number then if (sourcing_name != NULL && (other_sourcing_name() || sourcing_lnum != last_sourcing_lnum) && sourcing_lnum != 0) { - p = (char_u *)_("line %4ld:"); - Buf = xmalloc(STRLEN(p) + 20); - sprintf((char *)Buf, (char *)p, (long)sourcing_lnum); - return Buf; + const char *const p = _("line %4ld:"); + const size_t buf_len = 20 + strlen(p); + char *const buf = xmalloc(buf_len); + snprintf(buf, buf_len, p, (long)sourcing_lnum); + return buf; } return NULL; } @@ -426,10 +427,8 @@ static char_u *get_emsg_lnum(void) */ void msg_source(int attr) { - char_u *p; - - ++no_wait_return; - p = get_emsg_source(); + no_wait_return++; + char *p = get_emsg_source(); if (p != NULL) { msg_attr(p, attr); xfree(p); @@ -476,10 +475,10 @@ int emsg_not_now(void) * * return TRUE if wait_return not called */ -int emsg(char_u *s) +int emsg(const char_u *s_) { + const char *s = (const char *)s_; int attr; - char_u *p; int ignore = false; int severe; @@ -506,7 +505,7 @@ int emsg(char_u *s) * when the message should be ignored completely (used for the * interrupt message). */ - if (cause_errthrow(s, severe, &ignore) == true) { + if (cause_errthrow((char_u *)s, severe, &ignore) == true) { if (!ignore) { did_emsg = true; } @@ -514,7 +513,7 @@ int emsg(char_u *s) } // set "v:errmsg", also when using ":silent! cmd" - set_vim_var_string(VV_ERRMSG, (char *) s, -1); + set_vim_var_string(VV_ERRMSG, s, -1); /* * When using ":silent! cmd" ignore error messages. @@ -523,19 +522,21 @@ int emsg(char_u *s) if (emsg_silent != 0) { if (!emsg_noredir) { msg_start(); - p = get_emsg_source(); + char *p = get_emsg_source(); if (p != NULL) { - STRCAT(p, "\n"); - redir_write(p, STRLEN(p)); + const size_t p_len = strlen(p); + p[p_len] = '\n'; + redir_write(p, p_len + 1); xfree(p); } p = get_emsg_lnum(); if (p != NULL) { - STRCAT(p, "\n"); - redir_write(p, STRLEN(p)); + const size_t p_len = strlen(p); + p[p_len] = '\n'; + redir_write(p, p_len + 1); xfree(p); } - redir_write(s, STRLEN(s)); + redir_write(s, strlen(s)); } return true; } @@ -570,10 +571,8 @@ int emsg(char_u *s) */ msg_source(attr); - /* - * Display the error message itself. - */ - msg_nowait = FALSE; /* wait for this msg */ + // Display the error message itself. + msg_nowait = false; // Wait for this msg. return msg_attr(s, attr); } @@ -625,14 +624,14 @@ char_u *msg_trunc_attr(char_u *s, int force, int attr) { int n; - /* Add message to history before truncating */ - add_msg_hist(s, -1, attr); + // Add message to history before truncating. + add_msg_hist((const char *)s, -1, attr); s = msg_may_trunc(force, s); - msg_hist_off = TRUE; - n = msg_attr(s, attr); - msg_hist_off = FALSE; + msg_hist_off = true; + n = msg_attr((const char *)s, attr); + msg_hist_off = false; if (n) return s; @@ -671,12 +670,8 @@ char_u *msg_may_trunc(int force, char_u *s) return s; } -static void -add_msg_hist ( - char_u *s, - int len, /* -1 for undetermined length */ - int attr -) +/// @param[in] len Length of s or -1. +static void add_msg_hist(const char *s, int len, int attr) { if (msg_hist_off || msg_silent != 0) return; @@ -694,9 +689,10 @@ add_msg_hist ( ++s; --len; } - while (len > 0 && s[len - 1] == '\n') - --len; - p->msg = vim_strnsave(s, len); + while (len > 0 && s[len - 1] == '\n') { + len--; + } + p->msg = (char_u *)xmemdupz(s, (size_t)len); p->next = NULL; p->attr = attr; if (last_msg_hist != NULL) @@ -729,11 +725,11 @@ int delete_first_msg(void) return OK; } -/* - * ":messages" command. - */ -void ex_messages(exarg_T *eap) +/// :messages command implementation +void ex_messages(void *const eap_p) + FUNC_ATTR_NONNULL_ALL { + exarg_T *eap = (exarg_T *)eap_p; struct msg_hist *p; int c = 0; @@ -771,7 +767,7 @@ void ex_messages(exarg_T *eap) // Display what was not skipped. for (; p != NULL && !got_int; p = p->next) { if (p->msg != NULL) { - msg_attr(p->msg, p->attr); + msg_attr((const char *)p->msg, p->attr); } } @@ -1055,9 +1051,10 @@ void msg_start(void) msg_didout = FALSE; /* no output on current line yet */ } - /* when redirecting, may need to start a new line. */ - if (!did_return) - redir_write((char_u *)"\n", -1); + // When redirecting, may need to start a new line. + if (!did_return) { + redir_write("\n", 1); + } } /* @@ -1076,24 +1073,24 @@ void msg_putchar(int c) void msg_putchar_attr(int c, int attr) { - char_u buf[MB_MAXBYTES + 1]; + char buf[MB_MAXBYTES + 1]; if (IS_SPECIAL(c)) { - buf[0] = K_SPECIAL; - buf[1] = K_SECOND(c); - buf[2] = K_THIRD(c); + buf[0] = (char)K_SPECIAL; + buf[1] = (char)K_SECOND(c); + buf[2] = (char)K_THIRD(c); buf[3] = NUL; } else { - buf[(*mb_char2bytes)(c, buf)] = NUL; + buf[(*mb_char2bytes)(c, (char_u *)buf)] = NUL; } msg_puts_attr(buf, attr); } void msg_outnum(long n) { - char_u buf[20]; + char buf[20]; - sprintf((char *)buf, "%" PRId64, (int64_t)n); + snprintf(buf, sizeof(buf), "%ld", n); msg_puts(buf); } @@ -1149,16 +1146,16 @@ char_u *msg_outtrans_one(char_u *p, int attr) msg_outtrans_len_attr(p, l, attr); return p + l; } - msg_puts_attr(transchar_byte(*p), attr); + msg_puts_attr((const char *)transchar_byte(*p), attr); return p + 1; } int msg_outtrans_len_attr(char_u *msgstr, int len, int attr) { int retval = 0; - char_u *str = msgstr; - char_u *plain_start = msgstr; - char_u *s; + const char *str = (const char *)msgstr; + const char *plain_start = (const char *)msgstr; + char_u *s; int mb_l; int c; @@ -1168,36 +1165,39 @@ int msg_outtrans_len_attr(char_u *msgstr, int len, int attr) attr &= ~MSG_HIST; } - /* If the string starts with a composing character first draw a space on - * which the composing char can be drawn. */ - if (enc_utf8 && utf_iscomposing(utf_ptr2char(msgstr))) - msg_puts_attr((char_u *)" ", attr); + // If the string starts with a composing character first draw a space on + // which the composing char can be drawn. + if (enc_utf8 && utf_iscomposing(utf_ptr2char(msgstr))) { + msg_puts_attr(" ", attr); + } /* * Go over the string. Special characters are translated and printed. * Normal characters are printed several at a time. */ while (--len >= 0) { - if (enc_utf8) - /* Don't include composing chars after the end. */ - mb_l = utfc_ptr2len_len(str, len + 1); - else if (has_mbyte) - mb_l = (*mb_ptr2len)(str); - else + if (enc_utf8) { + // Don't include composing chars after the end. + mb_l = utfc_ptr2len_len((char_u *)str, len + 1); + } else if (has_mbyte) { + mb_l = (*mb_ptr2len)((char_u *)str); + } else { mb_l = 1; + } if (has_mbyte && mb_l > 1) { - c = (*mb_ptr2char)(str); - if (vim_isprintc(c)) - /* printable multi-byte char: count the cells. */ - retval += (*mb_ptr2cells)(str); - else { - /* unprintable multi-byte char: print the printable chars so - * far and the translation of the unprintable char. */ - if (str > plain_start) - msg_puts_attr_len(plain_start, (int)(str - plain_start), - attr); + c = (*mb_ptr2char)((char_u *)str); + if (vim_isprintc(c)) { + // Printable multi-byte char: count the cells. + retval += (*mb_ptr2cells)((char_u *)str); + } else { + // Unprintable multi-byte char: print the printable chars so + // far and the translation of the unprintable char. + if (str > plain_start) { + msg_puts_attr_len(plain_start, str - plain_start, attr); + } plain_start = str + mb_l; - msg_puts_attr(transchar(c), attr == 0 ? hl_attr(HLF_8) : attr); + msg_puts_attr((const char *)transchar(c), + (attr == 0 ? hl_attr(HLF_8) : attr)); retval += char2cells(c); } len -= mb_l - 1; @@ -1205,23 +1205,25 @@ int msg_outtrans_len_attr(char_u *msgstr, int len, int attr) } else { s = transchar_byte(*str); if (s[1] != NUL) { - /* unprintable char: print the printable chars so far and the - * translation of the unprintable char. */ - if (str > plain_start) - msg_puts_attr_len(plain_start, (int)(str - plain_start), - attr); + // Unprintable char: print the printable chars so far and the + // translation of the unprintable char. + if (str > plain_start) { + msg_puts_attr_len(plain_start, str - plain_start, attr); + } plain_start = str + 1; - msg_puts_attr(s, attr == 0 ? hl_attr(HLF_8) : attr); + msg_puts_attr((const char *)s, attr == 0 ? hl_attr(HLF_8) : attr); retval += (int)STRLEN(s); - } else - ++retval; - ++str; + } else { + retval++; + } + str++; } } - if (str > plain_start) - /* print the printable chars at the end */ - msg_puts_attr_len(plain_start, (int)(str - plain_start), attr); + if (str > plain_start) { + // Print the printable chars at the end. + msg_puts_attr_len(plain_start, str - plain_start, attr); + } return retval; } @@ -1264,23 +1266,23 @@ msg_outtrans_special ( { char_u *str = strstart; int retval = 0; - char_u *string; int attr; - int len; attr = hl_attr(HLF_8); while (*str != NUL) { - /* Leading and trailing spaces need to be displayed in <> form. */ + const char *string; + // Leading and trailing spaces need to be displayed in <> form. if ((str == strstart || str[1] == NUL) && *str == ' ') { - string = (char_u *)"<Space>"; - ++str; - } else - string = str2special(&str, from); - len = vim_strsize(string); - /* Highlight special keys */ - msg_puts_attr(string, len > 1 - && (*mb_ptr2len)(string) <= 1 - ? attr : 0); + string = "<Space>"; + str++; + } else { + string = (const char *)str2special((char_u **)&str, from); + } + const int len = vim_strsize((char_u *)string); + // Highlight special keys + msg_puts_attr(string, (len > 1 + && (*mb_ptr2len)((char_u *)string) <= 1 + ? attr : 0)); retval += len; } return retval; @@ -1404,7 +1406,6 @@ void msg_prt_line(char_u *s, int list) int attr = 0; char_u *trail = NULL; int l; - char_u buf[MB_MAXBYTES + 1]; if (curwin->w_p_list) list = TRUE; @@ -1430,10 +1431,11 @@ void msg_prt_line(char_u *s, int list) c = *p_extra++; } else if (has_mbyte && (l = (*mb_ptr2len)(s)) > 1) { col += (*mb_ptr2cells)(s); + char buf[MB_MAXBYTES + 1]; if (lcs_nbsp != NUL && list && (mb_ptr2char(s) == 160 || mb_ptr2char(s) == 0x202f)) { - mb_char2bytes(lcs_nbsp, buf); - buf[(*mb_ptr2len)(buf)] = NUL; + mb_char2bytes(lcs_nbsp, (char_u *)buf); + buf[(*mb_ptr2len)((char_u *)buf)] = NUL; } else { memmove(buf, s, (size_t)l); buf[l] = NUL; @@ -1530,12 +1532,12 @@ static char_u *screen_puts_mbyte(char_u *s, int l, int attr) * Output a string to the screen at position msg_row, msg_col. * Update msg_row and msg_col for the next message. */ -void msg_puts(char_u *s) +void msg_puts(const char *s) { msg_puts_attr(s, 0); } -void msg_puts_title(char_u *s) +void msg_puts_title(const char *s) { msg_puts_attr(s, hl_attr(HLF_T)); } @@ -1559,7 +1561,7 @@ void msg_puts_long_len_attr(char_u *longstr, int len, int attr) if (len > room && room >= 20) { slen = (room - 3) / 2; msg_outtrans_len_attr(longstr, slen, attr); - msg_puts_attr((char_u *)"...", hl_attr(HLF_8)); + msg_puts_attr("...", hl_attr(HLF_8)); } msg_outtrans_len_attr(longstr + len - slen, slen, attr); } @@ -1567,7 +1569,7 @@ void msg_puts_long_len_attr(char_u *longstr, int len, int attr) /* * Basic function for writing a message with highlight attributes. */ -void msg_puts_attr(char_u *s, int attr) +void msg_puts_attr(const char *const s, const int attr) { msg_puts_attr_len(s, -1, attr); } @@ -1575,7 +1577,7 @@ void msg_puts_attr(char_u *s, int attr) /// Like msg_puts_attr(), but with a maximum length "maxlen" (in bytes). /// When "maxlen" is -1 there is no maximum length. /// When "maxlen" is >= 0 the message is not put in the history. -static void msg_puts_attr_len(char_u *str, int maxlen, int attr) +void msg_puts_attr_len(const char *str, const ptrdiff_t maxlen, int attr) { // If redirection is on, also write to the redirection file. redir_write(str, maxlen); @@ -1606,9 +1608,9 @@ static void msg_puts_attr_len(char_u *str, int maxlen, int attr) // different, e.g. for Win32 console) or we just don't know where the // cursor is. if (msg_use_printf()) { - msg_puts_printf((char *)str, maxlen); + msg_puts_printf(str, maxlen); } else { - msg_puts_display(str, maxlen, attr, false); + msg_puts_display((const char_u *)str, maxlen, attr, false); } } @@ -1616,14 +1618,15 @@ static void msg_puts_attr_len(char_u *str, int maxlen, int attr) * The display part of msg_puts_attr_len(). * May be called recursively to display scroll-back text. */ -static void msg_puts_display(char_u *str, int maxlen, int attr, int recurse) +static void msg_puts_display(const char_u *str, int maxlen, int attr, + int recurse) { - char_u *s = str; - char_u *t_s = str; /* string from "t_s" to "s" is still todo */ - int t_col = 0; /* screen cells todo, 0 when "t_s" not used */ + const char_u *s = str; + const char_u *t_s = str; // String from "t_s" to "s" is still todo. + int t_col = 0; // Screen cells todo, 0 when "t_s" not used. int l; int cw; - char_u *sb_str = str; + const char_u *sb_str = str; int sb_col = msg_col; int wrap; int did_last_char; @@ -1665,26 +1668,27 @@ static void msg_puts_display(char_u *str, int maxlen, int attr, int recurse) if (msg_col >= Columns) /* can happen after screen resize */ msg_col = Columns - 1; - /* Display char in last column before showing more-prompt. */ - if (*s >= ' ' - && !cmdmsg_rl - ) { + // Display char in last column before showing more-prompt. + if (*s >= ' ' && !cmdmsg_rl) { if (has_mbyte) { if (enc_utf8 && maxlen >= 0) /* avoid including composing chars after the end */ l = utfc_ptr2len_len(s, (int)((str + maxlen) - s)); else l = (*mb_ptr2len)(s); - s = screen_puts_mbyte(s, l, attr); - } else + s = screen_puts_mbyte((char_u *)s, l, attr); + } else { msg_screen_putchar(*s++, attr); - did_last_char = TRUE; - } else - did_last_char = FALSE; + } + did_last_char = true; + } else { + did_last_char = false; + } - if (p_more) - /* store text for scrolling back */ - store_sb_text(&sb_str, s, attr, &sb_col, TRUE); + if (p_more) { + // Store text for scrolling back. + store_sb_text((char_u **)&sb_str, (char_u *)s, attr, &sb_col, true); + } inc_msg_scrolled(); need_wait_return = TRUE; /* may need wait_return in main() */ @@ -1720,13 +1724,15 @@ static void msg_puts_display(char_u *str, int maxlen, int attr, int recurse) && msg_col + t_col >= Columns - 1) ; if (t_col > 0 && (wrap || *s == '\r' || *s == '\b' - || *s == '\t' || *s == BELL)) - /* output any postponed text */ + || *s == '\t' || *s == BELL)) { + // Output any postponed text. t_puts(&t_col, t_s, s, attr); + } - if (wrap && p_more && !recurse) - /* store text for scrolling back */ - store_sb_text(&sb_str, s, attr, &sb_col, TRUE); + if (wrap && p_more && !recurse) { + // Store text for scrolling back. + store_sb_text((char_u **)&sb_str, (char_u *)s, attr, &sb_col, true); + } if (*s == '\n') { /* go to next line */ msg_didout = FALSE; /* remember that line is empty */ @@ -1764,7 +1770,7 @@ static void msg_puts_display(char_u *str, int maxlen, int attr, int recurse) // characters and draw them all at once later. if (cmdmsg_rl || (cw > 1 && msg_col + t_col >= Columns - 1)) { if (l > 1) { - s = screen_puts_mbyte(s, l, attr) - 1; + s = screen_puts_mbyte((char_u *)s, l, attr) - 1; } else { msg_screen_putchar(*s, attr); } @@ -1779,11 +1785,13 @@ static void msg_puts_display(char_u *str, int maxlen, int attr, int recurse) ++s; } - /* output any postponed text */ - if (t_col > 0) + // Output any postponed text. + if (t_col > 0) { t_puts(&t_col, t_s, s, attr); - if (p_more && !recurse) - store_sb_text(&sb_str, s, attr, &sb_col, FALSE); + } + if (p_more && !recurse) { + store_sb_text((char_u **)&sb_str, (char_u *)s, attr, &sb_col, false); + } msg_check(); } @@ -1959,11 +1967,11 @@ static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp) /* * Output any postponed text for msg_puts_attr_len(). */ -static void t_puts(int *t_col, char_u *t_s, char_u *s, int attr) +static void t_puts(int *t_col, const char_u *t_s, const char_u *s, int attr) { - /* output postponed text */ - msg_didout = TRUE; /* remember that line is not empty */ - screen_puts_len(t_s, (int)(s - t_s), msg_row, msg_col, attr); + // Output postponed text. + msg_didout = true; // Remember that line is not empty. + screen_puts_len((char_u *)t_s, (int)(s - t_s), msg_row, msg_col, attr); msg_col += *t_col; *t_col = 0; /* If the string starts with a composing character don't increment the @@ -1984,13 +1992,13 @@ int msg_use_printf(void) } /// Print a message when there is no valid screen. -static void msg_puts_printf(char *str, int maxlen) +static void msg_puts_printf(const char *str, const ptrdiff_t maxlen) { - char *s = str; + const char *s = str; char buf[4]; char *p; - while (*s != NUL && (maxlen < 0 || (int)(s - str) < maxlen)) { + while (*s != NUL && (maxlen < 0 || s - str < maxlen)) { if (!(silent_mode && p_verbose == 0)) { // NL --> CR NL translation (for Unix, not for "--version") p = &buf[0]; @@ -2447,9 +2455,9 @@ void msg_check(void) * May write a string to the redirection file. * When "maxlen" is -1 write the whole string, otherwise up to "maxlen" bytes. */ -static void redir_write(char_u *str, int maxlen) +static void redir_write(const char *const str, const ptrdiff_t maxlen) { - char_u *s = str; + const char_u *s = (char_u *)str; static int cur_col = 0; if (maxlen == 0) { @@ -2493,23 +2501,28 @@ static void redir_write(char_u *str, int maxlen) write_reg_contents(redir_reg, s, len, true); } if (redir_vname) { - var_redir_str(s, maxlen); + var_redir_str((char_u *)s, maxlen); } - /* Write and adjust the current column. */ - while (*s != NUL && (maxlen < 0 || (int)(s - str) < maxlen)) { - if (!redir_reg && !redir_vname && !capture_ga) - if (redir_fd != NULL) + // Write and adjust the current column. + while (*s != NUL + && (maxlen < 0 || (int)(s - (const char_u *)str) < maxlen)) { + if (!redir_reg && !redir_vname && !capture_ga) { + if (redir_fd != NULL) { putc(*s, redir_fd); - if (verbose_fd != NULL) + } + } + if (verbose_fd != NULL) { putc(*s, verbose_fd); - if (*s == '\r' || *s == '\n') + } + if (*s == '\r' || *s == '\n') { cur_col = 0; - else if (*s == '\t') + } else if (*s == '\t') { cur_col += (8 - cur_col % 8); - else - ++cur_col; - ++s; + } else { + cur_col++; + } + s++; } if (msg_silent != 0) /* should update msg_col */ @@ -2615,14 +2628,16 @@ void give_warning(char_u *message, bool hl) FUNC_ATTR_NONNULL_ARG(1) set_vim_var_string(VV_WARNINGMSG, (char *) message, -1); xfree(keep_msg); keep_msg = NULL; - if (hl) + if (hl) { keep_msg_attr = hl_attr(HLF_W); - else + } else { keep_msg_attr = 0; - if (msg_attr(message, keep_msg_attr) && msg_scrolled == 0) + } + if (msg_attr((const char *)message, keep_msg_attr) && msg_scrolled == 0) { set_keep_msg(message, keep_msg_attr); - msg_didout = FALSE; /* overwrite this message */ - msg_nowait = TRUE; /* don't wait for this message */ + } + msg_didout = false; // Overwrite this message. + msg_nowait = true; // Don't wait for this message. msg_col = 0; --no_wait_return; @@ -2958,11 +2973,12 @@ static void copy_hotkeys_and_msg(const char_u *message, char_u *buttons, */ void display_confirm_msg(void) { - /* avoid that 'q' at the more prompt truncates the message here */ - ++confirm_msg_used; - if (confirm_msg != NULL) - msg_puts_attr(confirm_msg, hl_attr(HLF_M)); - --confirm_msg_used; + // Avoid that 'q' at the more prompt truncates the message here. + confirm_msg_used++; + if (confirm_msg != NULL) { + msg_puts_attr((const char *)confirm_msg, hl_attr(HLF_M)); + } + confirm_msg_used--; } int vim_dialog_yesno(int type, char_u *title, char_u *message, int dflt) @@ -3001,834 +3017,3 @@ int vim_dialog_yesnoallcancel(int type, char_u *title, char_u *message, int dflt } return VIM_CANCEL; } - - - -static char *e_printf = N_("E766: Insufficient arguments for printf()"); - -/* - * Get number argument from "idxp" entry in "tvs". First entry is 1. - */ -static long tv_nr(typval_T *tvs, int *idxp) -{ - int idx = *idxp - 1; - long n = 0; - int err = FALSE; - - if (tvs[idx].v_type == VAR_UNKNOWN) - EMSG(_(e_printf)); - else { - ++*idxp; - n = get_tv_number_chk(&tvs[idx], &err); - if (err) - n = 0; - } - return n; -} - -/* - * Get string argument from "idxp" entry in "tvs". First entry is 1. - * Returns NULL for an error. - */ -static char *tv_str(typval_T *tvs, int *idxp) -{ - int idx = *idxp - 1; - char *s = NULL; - - if (tvs[idx].v_type == VAR_UNKNOWN) - EMSG(_(e_printf)); - else { - ++*idxp; - s = (char *)get_tv_string_chk(&tvs[idx]); - } - return s; -} - -/// Get pointer argument from the next entry in tvs -/// -/// First entry is 1. Returns NULL for an error. -/// -/// @param[in] tvs List of typval_T values. -/// @param[in,out] idxp Pointer to the index of the current value. -/// -/// @return Pointer stored in typval_T or NULL. -static const void *tv_ptr(const typval_T *const tvs, int *const idxp) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ -#define OFF(attr) offsetof(union typval_vval_union, attr) - STATIC_ASSERT( - OFF(v_string) == OFF(v_list) - && OFF(v_string) == OFF(v_dict) - && OFF(v_string) == OFF(v_partial) - && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_list) - && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_dict) - && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_partial), - "Strings, dictionaries, lists and partials are expected to be pointers, " - "so that all three of them can be accessed via v_string"); -#undef OFF - const int idx = *idxp - 1; - if (tvs[idx].v_type == VAR_UNKNOWN) { - EMSG(_(e_printf)); - return NULL; - } else { - (*idxp)++; - return tvs[idx].vval.v_string; - } -} - -/* - * Get float argument from "idxp" entry in "tvs". First entry is 1. - */ -static double tv_float(typval_T *tvs, int *idxp) -{ - int idx = *idxp - 1; - double f = 0; - - if (tvs[idx].v_type == VAR_UNKNOWN) - EMSG(_(e_printf)); - else { - ++*idxp; - if (tvs[idx].v_type == VAR_FLOAT) - f = tvs[idx].vval.v_float; - else if (tvs[idx].v_type == VAR_NUMBER) - f = tvs[idx].vval.v_number; - else - EMSG(_("E807: Expected Float argument for printf()")); - } - return f; -} - -/* - * This code was included to provide a portable vsnprintf() and snprintf(). - * Some systems may provide their own, but we always use this one for - * consistency. - * - * This code is based on snprintf.c - a portable implementation of snprintf - * by Mark Martinec <mark.martinec@ijs.si>, Version 2.2, 2000-10-06. - * Included with permission. It was heavily modified to fit in Vim. - * The original code, including useful comments, can be found here: - * http://www.ijs.si/software/snprintf/ - * - * This snprintf() only supports the following conversion specifiers: - * s, c, b, B, d, u, o, x, X, p (and synonyms: i, D, U, O - see below) - * with flags: '-', '+', ' ', '0' and '#'. - * An asterisk is supported for field width as well as precision. - * - * Limited support for floating point was added: 'f', 'e', 'E', 'g', 'G'. - * - * Length modifiers 'h' (short int) and 'l' (long int) are supported. - * 'll' (long long int) is not supported. - * - * The locale is not used, the string is used as a byte string. This is only - * relevant for double-byte encodings where the second byte may be '%'. - * - * It is permitted for "str_m" to be zero, and it is permitted to specify NULL - * pointer for resulting string argument if "str_m" is zero (as per ISO C99). - * - * The return value is the number of characters which would be generated - * for the given input, excluding the trailing NUL. If this value - * is greater or equal to "str_m", not all characters from the result - * have been stored in str, output bytes beyond the ("str_m"-1) -th character - * are discarded. If "str_m" is greater than zero it is guaranteed - * the resulting string will be NUL-terminated. - */ - -/* - * When va_list is not supported we only define vim_snprintf(). - * - * vim_vsnprintf() can be invoked with either "va_list" or a list of - * "typval_T". When the latter is not used it must be NULL. - */ - -/* Like vim_vsnprintf() but append to the string. */ -int vim_snprintf_add(char *str, size_t str_m, char *fmt, ...) -{ - va_list ap; - int str_l; - size_t len = STRLEN(str); - size_t space; - - if (str_m <= len) - space = 0; - else - space = str_m - len; - va_start(ap, fmt); - str_l = vim_vsnprintf(str + len, space, fmt, ap, NULL); - va_end(ap); - return str_l; -} - -int vim_snprintf(char *str, size_t str_m, const char *fmt, ...) -{ - va_list ap; - int str_l; - - va_start(ap, fmt); - str_l = vim_vsnprintf(str, str_m, fmt, ap, NULL); - va_end(ap); - return str_l; -} - -int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap, - typval_T *tvs) -{ - size_t str_l = 0; - bool str_avail = str_l < str_m; - const char *p = fmt; - int arg_idx = 1; - - if (!p) { - p = ""; - } - while (*p) { - if (*p != '%') { - // copy up to the next '%' or NUL without any changes - size_t n = (size_t)(xstrchrnul(p + 1, '%') - p); - if (str_avail) { - size_t avail = str_m - str_l; - memmove(str + str_l, p, MIN(n, avail)); - str_avail = n < avail; - } - p += n; - assert(n <= SIZE_MAX - str_l); - str_l += n; - } else { - size_t min_field_width = 0, precision = 0; - int zero_padding = 0, precision_specified = 0, justify_left = 0; - int alternate_form = 0, force_sign = 0; - - // if both ' ' and '+' flags appear, ' ' flag should be ignored - int space_for_positive = 1; - - // allowed values: \0, h, l, 2 (for ll), z, L - char length_modifier = '\0'; - - // temporary buffer for simple numeric->string conversion -# define TMP_LEN 350 // 1e308 seems reasonable as the maximum printable - char tmp[TMP_LEN]; - - // string address in case of string argument - const char *str_arg; - - // natural field width of arg without padding and sign - size_t str_arg_l; - - // unsigned char argument value (only defined for c conversion); - // standard explicitly states the char argument for the c - // conversion is unsigned - unsigned char uchar_arg; - - // number of zeros to be inserted for numeric conversions as - // required by the precision or minimal field width - size_t number_of_zeros_to_pad = 0; - - // index into tmp where zero padding is to be inserted - size_t zero_padding_insertion_ind = 0; - - // current conversion specifier character - char fmt_spec = '\0'; - - str_arg = NULL; - p++; // skip '%' - - // parse flags - while (*p == '0' || *p == '-' || *p == '+' || *p == ' ' - || *p == '#' || *p == '\'') { - switch (*p) { - case '0': zero_padding = 1; break; - case '-': justify_left = 1; break; - // if both '0' and '-' flags appear, '0' should be ignored - case '+': force_sign = 1; space_for_positive = 0; break; - case ' ': force_sign = 1; break; - // if both ' ' and '+' flags appear, ' ' should be ignored - case '#': alternate_form = 1; break; - case '\'': break; - } - p++; - } - - // parse field width - if (*p == '*') { - p++; - int j = tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, int); - if (j >= 0) { - min_field_width = j; - } else { - min_field_width = -j; - justify_left = 1; - } - } else if (ascii_isdigit((int)(*p))) { - // size_t could be wider than unsigned int; make sure we treat - // argument like common implementations do - unsigned int uj = *p++ - '0'; - - while (ascii_isdigit((int)(*p))) { - uj = 10 * uj + (unsigned int)(*p++ - '0'); - } - min_field_width = uj; - } - - // parse precision - if (*p == '.') { - p++; - precision_specified = 1; - if (*p == '*') { - int j = tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, int); - p++; - if (j >= 0) { - precision = j; - } else { - precision_specified = 0; - precision = 0; - } - } else if (ascii_isdigit((int)(*p))) { - // size_t could be wider than unsigned int; make sure we - // treat argument like common implementations do - unsigned int uj = *p++ - '0'; - - while (ascii_isdigit((int)(*p))) { - uj = 10 * uj + (unsigned int)(*p++ - '0'); - } - precision = uj; - } - } - - // parse 'h', 'l', 'll' and 'z' length modifiers - if (*p == 'h' || *p == 'l' || *p == 'z') { - length_modifier = *p; - p++; - if (length_modifier == 'l' && *p == 'l') { // ll, encoded as 2 - length_modifier = '2'; - p++; - } - } - - fmt_spec = *p; - - // common synonyms - switch (fmt_spec) { - case 'i': fmt_spec = 'd'; break; - case 'D': fmt_spec = 'd'; length_modifier = 'l'; break; - case 'U': fmt_spec = 'u'; length_modifier = 'l'; break; - case 'O': fmt_spec = 'o'; length_modifier = 'l'; break; - case 'F': fmt_spec = 'f'; break; - default: break; - } - - // get parameter value, do initial processing - switch (fmt_spec) { - // '%' and 'c' behave similar to 's' regarding flags and field widths - case '%': case 'c': case 's': case 'S': - str_arg_l = 1; - switch (fmt_spec) { - case '%': - str_arg = p; - break; - - case 'c': { - int j = tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, int); - // standard demands unsigned char - uchar_arg = (unsigned char)j; - str_arg = (char *)&uchar_arg; - break; - } - - case 's': - case 'S': - str_arg = tvs ? tv_str(tvs, &arg_idx) : va_arg(ap, char *); - if (!str_arg) { - str_arg = "[NULL]"; - str_arg_l = 6; - } else if (!precision_specified) { - // make sure not to address string beyond the specified precision - str_arg_l = strlen(str_arg); - } else if (precision == 0) { - // truncate string if necessary as requested by precision - str_arg_l = 0; - } else { - // memchr on HP does not like n > 2^31 - // TODO(elmart): check if this still holds / is relevant - str_arg_l = (size_t)((char *)xmemscan(str_arg, - NUL, - MIN(precision, 0x7fffffff)) - - str_arg); - } - if (fmt_spec == 'S') { - if (min_field_width != 0) - min_field_width += strlen(str_arg) - - mb_string2cells((char_u *) str_arg); - if (precision) { - char_u *p1 = (char_u *)str_arg; - for (size_t i = 0; i < precision && *p1; i++) { - p1 += mb_ptr2len(p1); - } - str_arg_l = precision = p1 - (char_u *)str_arg; - } - } - break; - - default: - break; - } - break; - - case 'd': - case 'u': - case 'b': case 'B': - case 'o': - case 'x': case 'X': - case 'p': { - // u, b, B, o, x, X and p conversion specifiers imply - // the value is unsigned; d implies a signed value - - // 0 if numeric argument is zero (or if pointer is NULL for 'p'), - // +1 if greater than zero (or non NULL for 'p'), - // -1 if negative (unsigned argument is never negative) - int arg_sign = 0; - - // only defined for length modifier h, or for no length modifiers - int int_arg = 0; - unsigned int uint_arg = 0; - - // only defined for length modifier l - long int long_arg = 0; - unsigned long int ulong_arg = 0; - - // only defined for length modifier ll - long long int long_long_arg = 0; - unsigned long long int ulong_long_arg = 0; - - // only defined for length modifier z - size_t size_t_arg = 0; - - // only defined for p conversion - const void *ptr_arg = NULL; - - if (fmt_spec == 'p') { - length_modifier = '\0'; - ptr_arg = tvs ? tv_ptr(tvs, &arg_idx) : va_arg(ap, void *); - if (ptr_arg) { - arg_sign = 1; - } - } else if (fmt_spec == 'd') { - // signed - switch (length_modifier) { - case '\0': - case 'h': - // char and short arguments are passed as int - int_arg = tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, int); - if (int_arg > 0) { - arg_sign = 1; - } else if (int_arg < 0) { - arg_sign = -1; - } - break; - case 'l': - long_arg = tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, long int); - if (long_arg > 0) { - arg_sign = 1; - } else if (long_arg < 0) { - arg_sign = -1; - } - break; - case '2': - long_long_arg = tvs ? tv_nr(tvs, &arg_idx) - : va_arg(ap, long long int); // NOLINT (runtime/int) - if (long_long_arg > 0) { - arg_sign = 1; - } else if (long_long_arg < 0) { - arg_sign = -1; - } - break; - } - } else { - // unsigned - switch (length_modifier) { - case '\0': - case 'h': - uint_arg = tvs ? (unsigned)tv_nr(tvs, &arg_idx) - : va_arg(ap, unsigned int); - if (uint_arg != 0) { arg_sign = 1; } - break; - case 'l': - ulong_arg = tvs ? (unsigned long)tv_nr(tvs, &arg_idx) - : va_arg(ap, unsigned long int); - if (ulong_arg != 0) { arg_sign = 1; } - break; - case '2': - ulong_long_arg = tvs - ? (unsigned long long)tv_nr(tvs, &arg_idx) // NOLINT (runtime/int) - : va_arg(ap, unsigned long long int); // NOLINT (runtime/int) - if (ulong_long_arg) { arg_sign = 1; } - break; - case 'z': - size_t_arg = tvs ? (size_t)tv_nr(tvs, &arg_idx) - : va_arg(ap, size_t); - if (size_t_arg) { arg_sign = 1; } - break; - } - } - - str_arg = tmp; - str_arg_l = 0; - - // For d, i, u, o, x, and X conversions, if precision is specified, - // '0' flag should be ignored. This is so with Solaris 2.6, Digital UNIX - // 4.0, HPUX 10, Linux, FreeBSD, NetBSD; but not with Perl. - if (precision_specified) { - zero_padding = 0; - } - - if (fmt_spec == 'd') { - if (force_sign && arg_sign >= 0) { - tmp[str_arg_l++] = space_for_positive ? ' ' : '+'; - } - // leave negative numbers for sprintf to handle, to - // avoid handling tricky cases like (short int)-32768 - } else if (alternate_form) { - if (arg_sign != 0 && (fmt_spec == 'x' || fmt_spec == 'X' - || fmt_spec == 'b' || fmt_spec == 'B')) { - tmp[str_arg_l++] = '0'; - tmp[str_arg_l++] = fmt_spec; - } - // alternate form should have no effect for p * conversion, but ... - } - - zero_padding_insertion_ind = str_arg_l; - if (!precision_specified) { - precision = 1; // default precision is 1 - } - if (precision == 0 && arg_sign == 0) { - // when zero value is formatted with an explicit precision 0, - // resulting formatted string is empty (d, i, u, b, B, o, x, X, p) - } else { - char f[5]; - int f_l = 0; - - // construct a simple format string for sprintf - f[f_l++] = '%'; - if (!length_modifier) { - } else if (length_modifier == '2') { - f[f_l++] = 'l'; - f[f_l++] = 'l'; - } else - f[f_l++] = length_modifier; - f[f_l++] = fmt_spec; - f[f_l++] = '\0'; - - if (fmt_spec == 'p') - str_arg_l += sprintf(tmp + str_arg_l, f, ptr_arg); - else if (fmt_spec == 'd') { - // signed - switch (length_modifier) { - case '\0': - case 'h': str_arg_l += sprintf(tmp + str_arg_l, f, int_arg); - break; - case 'l': str_arg_l += sprintf(tmp + str_arg_l, f, long_arg); - break; - case '2': str_arg_l += sprintf(tmp + str_arg_l, f, long_long_arg); - break; - } - } else if (fmt_spec == 'b' || fmt_spec == 'B') { - // binary - size_t bits = 0; - switch (length_modifier) { - case '\0': - case 'h': for (bits = sizeof(unsigned) * 8; bits > 0; bits--) { - if ((uint_arg >> (bits - 1)) & 0x1) { break; } } - - while (bits > 0) { - tmp[str_arg_l++] = - ((uint_arg >> --bits) & 0x1) ? '1' : '0'; } - break; - case 'l': for (bits = sizeof(unsigned long) * 8; bits > 0; bits--) { - if ((ulong_arg >> (bits - 1)) & 0x1) { break; } } - - while (bits > 0) { - tmp[str_arg_l++] = - ((ulong_arg >> --bits) & 0x1) ? '1' : '0'; } - break; - case '2': for (bits = sizeof(unsigned long long) * 8; // NOLINT (runtime/int) - bits > 0; bits--) { - if ((ulong_long_arg >> (bits - 1)) & 0x1) { break; } } - - while (bits > 0) { - tmp[str_arg_l++] = - ((ulong_long_arg >> --bits) & 0x1) ? '1' : '0'; } - break; - case 'z': for (bits = sizeof(size_t) * 8; bits > 0; bits--) { - if ((size_t_arg >> (bits - 1)) & 0x1) { break; } } - - while (bits > 0) { - tmp[str_arg_l++] = - ((size_t_arg >> --bits) & 0x1) ? '1' : '0'; } - break; - } - } else { - // unsigned - switch (length_modifier) { - case '\0': - case 'h': str_arg_l += sprintf(tmp + str_arg_l, f, uint_arg); - break; - case 'l': str_arg_l += sprintf(tmp + str_arg_l, f, ulong_arg); - break; - case '2': str_arg_l += sprintf(tmp + str_arg_l, f, ulong_long_arg); - break; - case 'z': str_arg_l += sprintf(tmp + str_arg_l, f, size_t_arg); - break; - } - } - - // include the optional minus sign and possible "0x" in the region - // before the zero padding insertion point - if (zero_padding_insertion_ind < str_arg_l - && tmp[zero_padding_insertion_ind] == '-') - zero_padding_insertion_ind++; - if (zero_padding_insertion_ind + 1 < str_arg_l - && tmp[zero_padding_insertion_ind] == '0' - && (tmp[zero_padding_insertion_ind + 1] == 'x' - || tmp[zero_padding_insertion_ind + 1] == 'X' - || tmp[zero_padding_insertion_ind + 1] == 'b' - || tmp[zero_padding_insertion_ind + 1] == 'B')) - zero_padding_insertion_ind += 2; - } - - { - size_t num_of_digits = str_arg_l - zero_padding_insertion_ind; - - if (alternate_form && fmt_spec == 'o' - // unless zero is already the first character - && !(zero_padding_insertion_ind < str_arg_l - && tmp[zero_padding_insertion_ind] == '0')) { - // assure leading zero for alternate-form octal numbers - if (!precision_specified - || precision < num_of_digits + 1) { - // precision is increased to force the first character to be zero, - // except if a zero value is formatted with an explicit precision - // of zero - precision = num_of_digits + 1; - } - } - // zero padding to specified precision? - if (num_of_digits < precision) - number_of_zeros_to_pad = precision - num_of_digits; - } - // zero padding to specified minimal field width? - if (!justify_left && zero_padding) { - int n = (int)(min_field_width - (str_arg_l - + number_of_zeros_to_pad)); - if (n > 0) - number_of_zeros_to_pad += n; - } - break; - } - - case 'f': - case 'e': - case 'E': - case 'g': - case 'G': - { - // floating point - char format[40]; - int l; - int remove_trailing_zeroes = false; - - double f = tvs ? tv_float(tvs, &arg_idx) : va_arg(ap, double); - double abs_f = f < 0 ? -f : f; - - if (fmt_spec == 'g' || fmt_spec == 'G') { - // can't use %g directly, cause it prints "1.0" as "1" - if ((abs_f >= 0.001 && abs_f < 10000000.0) || abs_f == 0.0) - fmt_spec = 'f'; - else - fmt_spec = fmt_spec == 'g' ? 'e' : 'E'; - remove_trailing_zeroes = true; - } - - if (fmt_spec == 'f' && abs_f > 1.0e307) { - // avoid a buffer overflow - strcpy(tmp, "inf"); - str_arg_l = 3; - } else { - format[0] = '%'; - l = 1; - if (precision_specified) { - size_t max_prec = TMP_LEN - 10; - - // make sure we don't get more digits than we have room for - if (fmt_spec == 'f' && abs_f > 1.0) - max_prec -= (size_t)log10(abs_f); - if (precision > max_prec) - precision = max_prec; - l += sprintf(format + 1, ".%d", (int)precision); - } - format[l] = fmt_spec; - format[l + 1] = NUL; - str_arg_l = sprintf(tmp, format, f); - - if (remove_trailing_zeroes) { - int i; - char *tp; - - // using %g or %G: remove superfluous zeroes - if (fmt_spec == 'f') - tp = tmp + str_arg_l - 1; - else { - tp = (char *)vim_strchr((char_u *)tmp, - fmt_spec == 'e' ? 'e' : 'E'); - if (tp) { - // remove superfluous '+' and leading zeroes from exponent - if (tp[1] == '+') { - // change "1.0e+07" to "1.0e07" - STRMOVE(tp + 1, tp + 2); - --str_arg_l; - } - i = (tp[1] == '-') ? 2 : 1; - while (tp[i] == '0') { - // change "1.0e07" to "1.0e7" - STRMOVE(tp + i, tp + i + 1); - --str_arg_l; - } - --tp; - } - } - - if (tp != NULL && !precision_specified) - // remove trailing zeroes, but keep the one just after a dot - while (tp > tmp + 2 && *tp == '0' && tp[-1] != '.') { - STRMOVE(tp, tp + 1); - --tp; - --str_arg_l; - } - } else { - // be consistent: some printf("%e") use 1.0e+12 and some 1.0e+012; - // remove one zero in the last case - char *tp = (char *)vim_strchr((char_u *)tmp, - fmt_spec == 'e' ? 'e' : 'E'); - if (tp && (tp[1] == '+' || tp[1] == '-') && tp[2] == '0' - && ascii_isdigit(tp[3]) && ascii_isdigit(tp[4])) { - STRMOVE(tp + 2, tp + 3); - --str_arg_l; - } - } - } - str_arg = tmp; - break; - } - - default: - // unrecognized conversion specifier, keep format string as-is - zero_padding = 0; // turn zero padding off for non-numeric conversion - justify_left = 1; - min_field_width = 0; // reset flags - - // discard the unrecognized conversion, just keep - // the unrecognized conversion character - str_arg = p; - str_arg_l = 0; - if (*p) - str_arg_l++; // include invalid conversion specifier - // unchanged if not at end-of-string - break; - } - - if (*p) - p++; // step over the just processed conversion specifier - - // insert padding to the left as requested by min_field_width; - // this does not include the zero padding in case of numerical conversions - if (!justify_left) { - assert(str_arg_l <= SIZE_MAX - number_of_zeros_to_pad); - if (min_field_width > str_arg_l + number_of_zeros_to_pad) { - // left padding with blank or zero - size_t pn = min_field_width - (str_arg_l + number_of_zeros_to_pad); - if (str_avail) { - size_t avail = str_m - str_l; - memset(str + str_l, zero_padding ? '0' : ' ', MIN(pn, avail)); - str_avail = pn < avail; - } - assert(pn <= SIZE_MAX - str_l); - str_l += pn; - } - } - - // zero padding as requested by the precision or by the minimal - // field width for numeric conversions required? - if (number_of_zeros_to_pad == 0) { - // will not copy first part of numeric right now, - // force it to be copied later in its entirety - zero_padding_insertion_ind = 0; - } else { - // insert first part of numerics (sign or '0x') before zero padding - if (zero_padding_insertion_ind > 0) { - size_t zn = zero_padding_insertion_ind; - if (str_avail) { - size_t avail = str_m - str_l; - memmove(str + str_l, str_arg, MIN(zn, avail)); - str_avail = zn < avail; - } - assert(zn <= SIZE_MAX - str_l); - str_l += zn; - } - - // insert zero padding as requested by precision or min field width - if (number_of_zeros_to_pad > 0) { - size_t zn = number_of_zeros_to_pad; - if (str_avail) { - size_t avail = str_m - str_l; - memset(str + str_l, '0', MIN(zn, avail)); - str_avail = zn < avail; - } - assert(zn <= SIZE_MAX - str_l); - str_l += zn; - } - } - - // insert formatted string - // (or as-is conversion specifier for unknown conversions) - if (str_arg_l > zero_padding_insertion_ind) { - size_t sn = str_arg_l - zero_padding_insertion_ind; - if (str_avail) { - size_t avail = str_m - str_l; - memmove(str + str_l, - str_arg + zero_padding_insertion_ind, - MIN(sn, avail)); - str_avail = sn < avail; - } - assert(sn <= SIZE_MAX - str_l); - str_l += sn; - } - - // insert right padding - if (justify_left) { - assert(str_arg_l <= SIZE_MAX - number_of_zeros_to_pad); - if (min_field_width > str_arg_l + number_of_zeros_to_pad) { - // right blank padding to the field width - size_t pn = min_field_width - (str_arg_l + number_of_zeros_to_pad); - if (str_avail) { - size_t avail = str_m - str_l; - memset(str + str_l, ' ', MIN(pn, avail)); - str_avail = pn < avail; - } - assert(pn <= SIZE_MAX - str_l); - str_l += pn; - } - } - } - } - - if (str_m > 0) { - // make sure the string is nul-terminated even at the expense of - // overwriting the last character (shouldn't happen, but just in case) - str[str_l <= str_m - 1 ? str_l : str_m - 1] = '\0'; - } - - if (tvs && tvs[arg_idx - 1].v_type != VAR_UNKNOWN) - EMSG(_("E767: Too many arguments to printf()")); - - // return the number of characters formatted (excluding trailing nul - // character); that is, the number of characters that would have been - // written to the buffer if it were large enough. - return (int)str_l; -} diff --git a/src/nvim/message.h b/src/nvim/message.h index d3a16fff93..904a9d3ca1 100644 --- a/src/nvim/message.h +++ b/src/nvim/message.h @@ -3,8 +3,9 @@ #include <stdbool.h> #include <stdarg.h> -#include "nvim/eval_defs.h" // for typval_T -#include "nvim/ex_cmds_defs.h" // for exarg_T +#include <stddef.h> + +#include "nvim/types.h" /* * Types of dialogs passed to do_dialog(). @@ -29,7 +30,7 @@ #define MSG(s) msg((char_u *)(s)) /// Show message highlighted according to the attr -#define MSG_ATTR(s, attr) msg_attr((char_u *)(s), (attr)) +#define MSG_ATTR(s, attr) msg_attr((const char *)(s), (attr)) /// Display error message /// @@ -49,13 +50,13 @@ #define EMSGU(s, n) emsgf((const char *) (s), (uint64_t)(n)) /// Display message at the recorded position -#define MSG_PUTS(s) msg_puts((char_u *)(s)) +#define MSG_PUTS(s) msg_puts((const char *)(s)) /// Display message at the recorded position, highlighted -#define MSG_PUTS_ATTR(s, a) msg_puts_attr((char_u *)(s), (a)) +#define MSG_PUTS_ATTR(s, a) msg_puts_attr((const char *)(s), (a)) /// Like #MSG_PUTS, but highlight like title -#define MSG_PUTS_TITLE(s) msg_puts_title((char_u *)(s)) +#define MSG_PUTS_TITLE(s) msg_puts_title((const char *)(s)) /// Like #MSG_PUTS, but if middle part of too long messages it will be replaced #define MSG_PUTS_LONG(s) msg_puts_long_attr((char_u *)(s), 0) diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index ba26381e23..db34159f24 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -2223,7 +2223,7 @@ change_warning ( * * return the 'y' or 'n' */ -int ask_yesno(char_u *str, int direct) +int ask_yesno(const char *str, bool direct) { int r = ' '; int save_State = State; @@ -2545,7 +2545,7 @@ void vim_beep(unsigned val) * function give the user a hint where the beep comes from. */ if (vim_strchr(p_debug, 'e') != NULL) { msg_source(hl_attr(HLF_W)); - msg_attr((char_u *)_("Beep!"), hl_attr(HLF_W)); + msg_attr(_("Beep!"), hl_attr(HLF_W)); } } } diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 98636263b9..71db7506ad 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -321,14 +321,15 @@ static void parse_msgpack(Stream *stream, RBuffer *rbuf, size_t c, void *data, if (eof) { close_channel(channel); - call_set_error(channel, "Channel was closed by the client"); + char buf[256]; + snprintf(buf, sizeof(buf), "channel %" PRIu64 " was closed by the client", + channel->id); + call_set_error(channel, buf); goto end; } size_t count = rbuffer_size(rbuf); - DLOG("Feeding the msgpack parser with %u bytes of data from Stream(%p)", - count, - stream); + DLOG("parsing %u bytes of msgpack data from Stream(%p)", count, stream); // Feed the unpacker with data msgpack_unpacker_reserve_buffer(channel->unpacker, count); @@ -350,11 +351,9 @@ static void parse_msgpack(Stream *stream, RBuffer *rbuf, size_t c, void *data, complete_call(&unpacked.data, channel); } else { char buf[256]; - snprintf(buf, - sizeof(buf), - "Channel %" PRIu64 " returned a response that doesn't have " - "a matching request id. Ensure the client is properly " - "synchronized", + snprintf(buf, sizeof(buf), + "channel %" PRIu64 " sent a response without a matching " + "request id. Ensure the client is properly synchronized", channel->id); call_set_error(channel, buf); } @@ -406,7 +405,7 @@ static void handle_request(Channel *channel, msgpack_object *request) &out_buffer))) { char buf[256]; snprintf(buf, sizeof(buf), - "Channel %" PRIu64 " sent an invalid message, closed.", + "channel %" PRIu64 " sent an invalid message, closed.", channel->id); call_set_error(channel, buf); } @@ -716,7 +715,7 @@ static void complete_call(msgpack_object *obj, Channel *channel) static void call_set_error(Channel *channel, char *msg) { - ELOG("msgpack-rpc: %s", msg); + ELOG("RPC: %s", msg); for (size_t i = 0; i < kv_size(channel->call_stack); i++) { ChannelCallFrame *frame = kv_A(channel->call_stack, i); frame->returned = true; @@ -789,10 +788,10 @@ static void decref(Channel *channel) } #if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL -#define REQ "[request] " -#define RES "[response] " -#define NOT "[notification] " -#define ERR "[error] " +#define REQ "[request] " +#define RES "[response] " +#define NOT "[notify] " +#define ERR "[error] " // Cannot define array with negative offsets, so this one is needed to be added // to MSGPACK_UNPACK_\* values. @@ -810,7 +809,7 @@ static void log_server_msg(uint64_t channel_id, { msgpack_unpacked unpacked; msgpack_unpacked_init(&unpacked); - DLOGN("[msgpack-rpc] nvim -> client(%" PRIu64 ") ", channel_id); + DLOGN("RPC ->ch %" PRIu64 ": ", channel_id); const msgpack_unpack_return result = msgpack_unpack_next(&unpacked, packed->data, packed->size, NULL); switch (result) { @@ -847,7 +846,7 @@ static void log_client_msg(uint64_t channel_id, bool is_request, msgpack_object msg) { - DLOGN("[msgpack-rpc] client(%" PRIu64 ") -> nvim ", channel_id); + DLOGN("RPC <-ch %" PRIu64 ": ", channel_id); log_lock(); FILE *f = open_log_file(); fprintf(f, is_request ? REQ : RES); diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 5d7a8faeba..dbd8e153a8 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -617,7 +617,7 @@ static void normal_redraw_mode_message(NormalState *s) update_screen(0); // now reset it, otherwise it's put in the history again keep_msg = kmsg; - msg_attr(kmsg, keep_msg_attr); + msg_attr((const char *)kmsg, keep_msg_attr); xfree(kmsg); } setcursor(); @@ -1276,7 +1276,7 @@ static void normal_redraw(NormalState *s) // msg_attr_keep() will set keep_msg to NULL, must free the string here. // Don't reset keep_msg, msg_attr_keep() uses it to check for duplicates. char *p = (char *)keep_msg; - msg_attr((uint8_t *)p, keep_msg_attr); + msg_attr(p, keep_msg_attr); xfree(p); } @@ -2460,11 +2460,11 @@ do_mouse ( }; typval_T rettv; int doesrange; - (void) call_func((char_u *) tab_page_click_defs[mouse_col].func, - (int) strlen(tab_page_click_defs[mouse_col].func), - &rettv, ARRAY_SIZE(argv), argv, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &doesrange, true, NULL, NULL); + (void)call_func((char_u *)tab_page_click_defs[mouse_col].func, + (int)strlen(tab_page_click_defs[mouse_col].func), + &rettv, ARRAY_SIZE(argv), argv, NULL, + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &doesrange, true, NULL, NULL); clear_tv(&rettv); break; } @@ -2696,8 +2696,6 @@ do_mouse ( else if (((mod_mask & MOD_MASK_CTRL) || (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) && bt_quickfix(curbuf)) { - if (State & INSERT) - stuffcharReadbuff(Ctrl_O); if (curwin->w_llist_ref == NULL) { // quickfix window do_cmdline_cmd(".cc"); } else { // location list window @@ -7420,27 +7418,23 @@ static void nv_esc(cmdarg_T *cap) restart_edit = 'a'; } -/* - * Handle "A", "a", "I", "i" and <Insert> commands. - */ +/// Handle "A", "a", "I", "i" and <Insert> commands. static void nv_edit(cmdarg_T *cap) { - /* <Insert> is equal to "i" */ - if (cap->cmdchar == K_INS || cap->cmdchar == K_KINS) + // <Insert> is equal to "i" + if (cap->cmdchar == K_INS || cap->cmdchar == K_KINS) { cap->cmdchar = 'i'; + } - /* in Visual mode "A" and "I" are an operator */ - if (VIsual_active && (cap->cmdchar == 'A' || cap->cmdchar == 'I')) + // in Visual mode "A" and "I" are an operator + if (VIsual_active && (cap->cmdchar == 'A' || cap->cmdchar == 'I')) { v_visop(cap); - - /* in Visual mode and after an operator "a" and "i" are for text objects */ - else if ((cap->cmdchar == 'a' || cap->cmdchar == 'i') - && (cap->oap->op_type != OP_NOP - || VIsual_active - )) { + // in Visual mode and after an operator "a" and "i" are for text objects + } else if ((cap->cmdchar == 'a' || cap->cmdchar == 'i') + && (cap->oap->op_type != OP_NOP || VIsual_active)) { nv_object(cap); - } else if (!curbuf->b_p_ma && !p_im) { - /* Only give this error when 'insertmode' is off. */ + } else if (!curbuf->b_p_ma && !p_im && !curbuf->terminal) { + // Only give this error when 'insertmode' is off. EMSG(_(e_modifiable)); clearop(cap->oap); } else if (!checkclearopq(cap->oap)) { diff --git a/src/nvim/ops.c b/src/nvim/ops.c index d58c8700ca..8bfda3c193 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1260,7 +1260,7 @@ int get_spec_reg( /// Paste a yank register into the command line. /// Only for non-special registers. -/// Used by CTRL-R command in command-line mode +/// Used by CTRL-R in command-line mode. /// insert_reg() can't be used here, because special characters from the /// register contents will be interpreted as commands. /// @@ -1278,9 +1278,8 @@ bool cmdline_paste_reg(int regname, bool literally, bool remcr) for (size_t i = 0; i < reg->y_size; i++) { cmdline_paste_str(reg->y_array[i], literally); - // Insert ^M between lines and after last line if type is kMTLineWise. - // Don't do this when "remcr" is true. - if ((reg->y_type == kMTLineWise || i < reg->y_size - 1) && !remcr) { + // Insert ^M between lines, unless `remcr` is true. + if (i < reg->y_size - 1 && !remcr) { cmdline_paste_str((char_u *)"\r", literally); } diff --git a/src/nvim/option.c b/src/nvim/option.c index a218323dac..8990b59f57 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1,24 +1,18 @@ -/* - * Code to handle user-settable options. This is all pretty much table- - * driven. Checklist for adding a new option: - * - Put it in the options array below (copy an existing entry). - * - For a global option: Add a variable for it in option_defs.h. - * - For a buffer or window local option: - * - Add a PV_XX entry to the enum below. - * - Add a variable to the window or buffer struct in buffer_defs.h. - * - For a window option, add some code to copy_winopt(). - * - For a buffer option, add some code to buf_copy_options(). - * - For a buffer string option, add code to check_buf_options(). - * - If it's a numeric option, add any necessary bounds checks to do_set(). - * - If it's a list of flags, add some code in do_set(), search for WW_ALL. - * - When adding an option with expansion (P_EXPAND), but with a different - * default for Vi and Vim (no P_VI_DEF), add some code at VIMEXP. - * - Add documentation! One line in doc/help.txt, full description in - * options.txt, and any other related places. - * - Add an entry in runtime/optwin.vim. - * When making changes: - * - Adjust the help for the option in doc/option.txt. - */ +// User-settable options. Checklist for adding a new option: +// - Put it in options.lua +// - For a global option: Add a variable for it in option_defs.h. +// - For a buffer or window local option: +// - Add a BV_XX or WV_XX entry to option_defs.h +// - Add a variable to the window or buffer struct in buffer_defs.h. +// - For a window option, add some code to copy_winopt(). +// - For a buffer option, add some code to buf_copy_options(). +// - For a buffer string option, add code to check_buf_options(). +// - If it's a numeric option, add any necessary bounds checks to do_set(). +// - If it's a list of flags, add some code in do_set(), search for WW_ALL. +// - When adding an option with expansion (P_EXPAND), but with a different +// default for Vi and Vim (no P_VI_DEF), add some code at VIMEXP. +// - Add documentation! doc/options.txt, and any other related places. +// - Add an entry in runtime/optwin.vim. #define IN_OPTION_C #include <assert.h> @@ -161,6 +155,7 @@ static long p_ts; static long p_tw; static int p_udf; static long p_wm; +static long p_scbk; static char_u *p_keymap; /* Saved values for when 'bin' is set. */ @@ -2569,12 +2564,11 @@ did_set_string_option ( init_highlight(FALSE, FALSE); - if (dark != (*p_bg == 'd') - && get_var_value((char_u *)"g:colors_name") != NULL) { - /* The color scheme must have set 'background' back to another - * value, that's not what we want here. Disable the color - * scheme and set the colors again. */ - do_unlet((char_u *)"g:colors_name", TRUE); + if (dark != (*p_bg == 'd') && get_var_value("g:colors_name") != NULL) { + // The color scheme must have set 'background' back to another + // value, that's not what we want here. Disable the color + // scheme and set the colors again. + do_unlet((char_u *)"g:colors_name", true); free_string_option(p_bg); p_bg = vim_strsave((char_u *)(dark ? "dark" : "light")); check_string_option(&p_bg); @@ -3886,7 +3880,7 @@ set_bool_option ( "W17: Arabic requires UTF-8, do ':set encoding=utf-8'"); msg_source(hl_attr(HLF_W)); - MSG_ATTR(_(w_arabic), hl_attr(HLF_W)); + msg_attr(_(w_arabic), hl_attr(HLF_W)); set_vim_var_string(VV_WARNINGMSG, _(w_arabic), -1); } @@ -4202,7 +4196,19 @@ set_num_option ( FOR_ALL_TAB_WINDOWS(tp, wp) { check_colorcolumn(wp); } - + } else if (pp == &curbuf->b_p_scbk) { + // 'scrollback' + if (!curbuf->terminal) { + errmsg = e_invarg; + curbuf->b_p_scbk = -1; + } else { + if (curbuf->b_p_scbk < -1 || curbuf->b_p_scbk > 100000) { + errmsg = e_invarg; + curbuf->b_p_scbk = 1000; + } + // Force the scrollback to take effect. + terminal_resize(curbuf->terminal, UINT16_MAX, UINT16_MAX); + } } /* @@ -5427,6 +5433,7 @@ static char_u *get_varp(vimoption_T *p) case PV_PI: return (char_u *)&(curbuf->b_p_pi); case PV_QE: return (char_u *)&(curbuf->b_p_qe); case PV_RO: return (char_u *)&(curbuf->b_p_ro); + case PV_SCBK: return (char_u *)&(curbuf->b_p_scbk); case PV_SI: return (char_u *)&(curbuf->b_p_si); case PV_STS: return (char_u *)&(curbuf->b_p_sts); case PV_SUA: return (char_u *)&(curbuf->b_p_sua); @@ -5637,6 +5644,7 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_ai = p_ai; buf->b_p_ai_nopaste = p_ai_nopaste; buf->b_p_sw = p_sw; + buf->b_p_scbk = -1; buf->b_p_tw = p_tw; buf->b_p_tw_nopaste = p_tw_nopaste; buf->b_p_tw_nobin = p_tw_nobin; diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 32021da922..9c6393e014 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -411,7 +411,6 @@ EXTERN char_u *p_efm; // 'errorformat' EXTERN char_u *p_gefm; // 'grepformat' EXTERN char_u *p_gp; // 'grepprg' EXTERN char_u *p_ei; // 'eventignore' -EXTERN int p_ek; // 'esckeys' EXTERN int p_exrc; // 'exrc' EXTERN char_u *p_fencs; // 'fileencodings' EXTERN char_u *p_ffs; // 'fileformats' @@ -602,11 +601,14 @@ EXTERN int p_tbs; ///< 'tagbsearch' EXTERN char_u *p_tc; ///< 'tagcase' EXTERN unsigned tc_flags; ///< flags from 'tagcase' #ifdef IN_OPTION_C -static char *(p_tc_values[]) = { "followic", "ignore", "match", NULL }; +static char *(p_tc_values[]) = + { "followic", "ignore", "match", "followscs", "smart", NULL }; #endif #define TC_FOLLOWIC 0x01 #define TC_IGNORE 0x02 #define TC_MATCH 0x04 +#define TC_FOLLOWSCS 0x08 +#define TC_SMART 0x10 EXTERN long p_tl; ///< 'taglength' EXTERN int p_tr; ///< 'tagrelative' EXTERN char_u *p_tags; ///< 'tags' @@ -737,6 +739,7 @@ enum { , BV_PI , BV_QE , BV_RO + , BV_SCBK , BV_SI , BV_SMC , BV_SYN diff --git a/src/nvim/options.lua b/src/nvim/options.lua index fd68d1cde0..e12860c0cc 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -711,13 +711,6 @@ return { defaults={if_true={vi=macros('DFLT_EFM')}} }, { - full_name='esckeys', abbreviation='ek', - type='bool', scope={'global'}, - vim=true, - varname='p_ek', - defaults={if_true={vi=false, vim=true}} - }, - { full_name='eventignore', abbreviation='ei', type='string', list='onecomma', scope={'global'}, deny_duplicates=true, @@ -1920,6 +1913,14 @@ return { defaults={if_true={vi=12}} }, { + full_name='scrollback', abbreviation='scbk', + type='number', scope={'buffer'}, + vi_def=true, + varname='p_scbk', + redraw={'current_buffer'}, + defaults={if_true={vi=-1}} + }, + { full_name='scrollbind', abbreviation='scb', type='bool', scope={'window'}, vi_def=true, diff --git a/src/nvim/pos.h b/src/nvim/pos.h index 864f3fe866..966255e6a4 100644 --- a/src/nvim/pos.h +++ b/src/nvim/pos.h @@ -2,6 +2,8 @@ #define NVIM_POS_H typedef long linenr_T; // line number type +/// Format used to print values which have linenr_T type +#define PRIdLINENR "ld" /// Column number type typedef int colnr_T; diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index c40403e3f6..0da98713df 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2068,21 +2068,20 @@ void qf_list(exarg_T *eap) if (qfp->qf_lnum == 0) { IObuff[0] = NUL; } else if (qfp->qf_col == 0) { - vim_snprintf((char *)IObuff, IOSIZE, ":%" PRId64, - (int64_t)qfp->qf_lnum); + vim_snprintf((char *)IObuff, IOSIZE, ":%" PRIdLINENR, qfp->qf_lnum); } else { - vim_snprintf((char *)IObuff, IOSIZE, ":%" PRId64 " col %d", - (int64_t)qfp->qf_lnum, qfp->qf_col); + vim_snprintf((char *)IObuff, IOSIZE, ":%" PRIdLINENR " col %d", + qfp->qf_lnum, qfp->qf_col); } vim_snprintf((char *)IObuff + STRLEN(IObuff), IOSIZE, "%s:", (char *)qf_types(qfp->qf_type, qfp->qf_nr)); - msg_puts_attr(IObuff, hl_attr(HLF_N)); + msg_puts_attr((const char *)IObuff, hl_attr(HLF_N)); if (qfp->qf_pattern != NULL) { qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE); - STRCAT(IObuff, ":"); - msg_puts(IObuff); + xstrlcat((char *)IObuff, ":", IOSIZE); + msg_puts((const char *)IObuff); } - msg_puts((char_u *)" "); + msg_puts(" "); /* Remove newlines and leading whitespace from the text. For an * unrecognized line keep the indent, the compiler may mark a word diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 6613d284d7..1cd334abcd 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -6442,32 +6442,72 @@ static linenr_T submatch_firstlnum; static linenr_T submatch_maxline; static int submatch_line_lbr; -/* - * vim_regsub() - perform substitutions after a vim_regexec() or - * vim_regexec_multi() match. - * - * If "copy" is TRUE really copy into "dest". - * If "copy" is FALSE nothing is copied, this is just to find out the length - * of the result. - * - * If "backslash" is TRUE, a backslash will be removed later, need to double - * them to keep them, and insert a backslash before a CR to avoid it being - * replaced with a line break later. - * - * Note: The matched text must not change between the call of - * vim_regexec()/vim_regexec_multi() and vim_regsub()! It would make the back - * references invalid! - * - * Returns the size of the replacement, including terminating NUL. - */ -int vim_regsub(regmatch_T *rmp, char_u *source, char_u *dest, int copy, int magic, int backslash) +/// Put the submatches in "argv[0]" which is a list passed into call_func() by +/// vim_regsub_both(). +static int fill_submatch_list(int argc, typval_T *argv, int argcount) +{ + listitem_T *li; + int i; + char_u *s; + + if (argcount == 0) { + // called function doesn't take an argument + return 0; + } + + // Relies on sl_list to be the first item in staticList10_T. + init_static_list((staticList10_T *)(argv->vval.v_list)); + + // There are always 10 list items in staticList10_T. + li = argv->vval.v_list->lv_first; + for (i = 0; i < 10; i++) { + s = submatch_match->startp[i]; + if (s == NULL || submatch_match->endp[i] == NULL) { + s = NULL; + } else { + s = vim_strnsave(s, (int)(submatch_match->endp[i] - s)); + } + li->li_tv.v_type = VAR_STRING; + li->li_tv.vval.v_string = s; + li = li->li_next; + } + return 1; +} + +static void clear_submatch_list(staticList10_T *sl) +{ + int i; + + for (i = 0; i < 10; i++) { + xfree(sl->sl_items[i].li_tv.vval.v_string); + } +} + +/// vim_regsub() - perform substitutions after a vim_regexec() or +/// vim_regexec_multi() match. +/// +/// If "copy" is TRUE really copy into "dest". +/// If "copy" is FALSE nothing is copied, this is just to find out the length +/// of the result. +/// +/// If "backslash" is TRUE, a backslash will be removed later, need to double +/// them to keep them, and insert a backslash before a CR to avoid it being +/// replaced with a line break later. +/// +/// Note: The matched text must not change between the call of +/// vim_regexec()/vim_regexec_multi() and vim_regsub()! It would make the back +/// references invalid! +/// +/// Returns the size of the replacement, including terminating NUL. +int vim_regsub(regmatch_T *rmp, char_u *source, typval_T *expr, char_u *dest, + int copy, int magic, int backslash) { reg_match = rmp; reg_mmatch = NULL; reg_maxline = 0; reg_buf = curbuf; - reg_line_lbr = TRUE; - return vim_regsub_both(source, dest, copy, magic, backslash); + reg_line_lbr = true; + return vim_regsub_both(source, expr, dest, copy, magic, backslash); } int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *dest, int copy, int magic, int backslash) @@ -6477,11 +6517,12 @@ int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *de reg_buf = curbuf; /* always works on the current buffer! */ reg_firstlnum = lnum; reg_maxline = curbuf->b_ml.ml_line_count - lnum; - reg_line_lbr = FALSE; - return vim_regsub_both(source, dest, copy, magic, backslash); + reg_line_lbr = false; + return vim_regsub_both(source, NULL, dest, copy, magic, backslash); } -static int vim_regsub_both(char_u *source, char_u *dest, int copy, int magic, int backslash) +static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, + int copy, int magic, int backslash) { char_u *src; char_u *dst; @@ -6495,8 +6536,8 @@ static int vim_regsub_both(char_u *source, char_u *dest, int copy, int magic, in int len = 0; /* init for GCC */ static char_u *eval_result = NULL; - /* Be paranoid... */ - if (source == NULL || dest == NULL) { + // Be paranoid... + if ((source == NULL && expr == NULL) || dest == NULL) { EMSG(_(e_null)); return 0; } @@ -6505,16 +6546,13 @@ static int vim_regsub_both(char_u *source, char_u *dest, int copy, int magic, in src = source; dst = dest; - /* - * When the substitute part starts with "\=" evaluate it as an expression. - */ - if (source[0] == '\\' && source[1] == '=' - && !can_f_submatch /* can't do this recursively */ - ) { - /* To make sure that the length doesn't change between checking the - * length and copying the string, and to speed up things, the - * resulting string is saved from the call with "copy" == FALSE to the - * call with "copy" == TRUE. */ + // When the substitute part starts with "\=" evaluate it as an expression. + if (expr != NULL || (source[0] == '\\' && source[1] == '=' + && !can_f_submatch)) { // can't do this recursively + // To make sure that the length doesn't change between checking the + // length and copying the string, and to speed up things, the + // resulting string is saved from the call with "copy" == FALSE to the + // call with "copy" == TRUE. if (copy) { if (eval_result != NULL) { STRCPY(dest, eval_result); @@ -6525,6 +6563,7 @@ static int vim_regsub_both(char_u *source, char_u *dest, int copy, int magic, in } else { win_T *save_reg_win; int save_ireg_ic; + bool prev_can_f_submatch = can_f_submatch; xfree(eval_result); @@ -6539,9 +6578,50 @@ static int vim_regsub_both(char_u *source, char_u *dest, int copy, int magic, in submatch_line_lbr = reg_line_lbr; save_reg_win = reg_win; save_ireg_ic = ireg_ic; - can_f_submatch = TRUE; + can_f_submatch = true; + + if (expr != NULL) { + typval_T argv[2]; + int dummy; + char_u buf[NUMBUFLEN]; + typval_T rettv; + staticList10_T matchList; + + rettv.v_type = VAR_STRING; + rettv.vval.v_string = NULL; + if (prev_can_f_submatch) { + // can't do this recursively + } else { + argv[0].v_type = VAR_LIST; + argv[0].vval.v_list = &matchList.sl_list; + matchList.sl_list.lv_len = 0; + if (expr->v_type == VAR_FUNC) { + s = expr->vval.v_string; + call_func(s, (int)STRLEN(s), &rettv, 1, argv, + fill_submatch_list, 0L, 0L, &dummy, + true, NULL, NULL); + } else if (expr->v_type == VAR_PARTIAL) { + partial_T *partial = expr->vval.v_partial; + + s = partial_name(partial); + call_func(s, (int)STRLEN(s), &rettv, 1, argv, + fill_submatch_list, 0L, 0L, &dummy, + true, partial, NULL); + } + if (matchList.sl_list.lv_len > 0) { + // fill_submatch_list() was called. + clear_submatch_list(&matchList); + } + } + eval_result = get_tv_string_buf_chk(&rettv, buf); + if (eval_result != NULL) { + eval_result = vim_strsave(eval_result); + } + clear_tv(&rettv); + } else { + eval_result = eval_to_string(source + 2, NULL, true); + } - eval_result = eval_to_string(source + 2, NULL, TRUE); if (eval_result != NULL) { int had_backslash = FALSE; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 3823fb2a23..f981fcb875 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -6719,17 +6719,19 @@ int showmode(void) if (edit_submode_pre != NULL) length -= vim_strsize(edit_submode_pre); if (length - vim_strsize(edit_submode) > 0) { - if (edit_submode_pre != NULL) - msg_puts_attr(edit_submode_pre, attr); - msg_puts_attr(edit_submode, attr); + if (edit_submode_pre != NULL) { + msg_puts_attr((const char *)edit_submode_pre, attr); + } + msg_puts_attr((const char *)edit_submode, attr); } if (edit_submode_extra != NULL) { - MSG_PUTS_ATTR(" ", attr); /* add a space in between */ - if ((int)edit_submode_highl < (int)HLF_COUNT) + MSG_PUTS_ATTR(" ", attr); // Add a space in between. + if ((int)edit_submode_highl < (int)HLF_COUNT) { sub_attr = hl_attr(edit_submode_highl); - else + } else { sub_attr = attr; - msg_puts_attr(edit_submode_extra, sub_attr); + } + msg_puts_attr((const char *)edit_submode_extra, sub_attr); } } } else { @@ -7111,8 +7113,9 @@ void showruler(int always) } if ((*p_stl != NUL || *curwin->w_p_stl != NUL) && curwin->w_status_height) { redraw_custom_statusline(curwin); - } else + } else { win_redr_ruler(curwin, always); + } if (need_maketitle || (p_icon && (stl_syntax & STL_IN_ICON)) diff --git a/src/nvim/search.c b/src/nvim/search.c index 2a087276e7..ba6c4e6548 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -308,13 +308,19 @@ void free_search_patterns(void) */ int ignorecase(char_u *pat) { - int ic = p_ic; + return ignorecase_opt(pat, p_ic, p_scs); +} - if (ic && !no_smartcase && p_scs +/// As ignorecase() put pass the "ic" and "scs" flags. +int ignorecase_opt(char_u *pat, int ic_in, int scs) +{ + int ic = ic_in; + if (ic && !no_smartcase && scs && !(ctrl_x_mode && curbuf->b_p_inf) - ) + ) { ic = !pat_has_uppercase(pat); - no_smartcase = FALSE; + } + no_smartcase = false; return ic; } @@ -4656,12 +4662,12 @@ static void show_pat_in_path(char_u *line, int type, int did_show, int action, F *(p + 1) = NUL; } if (action == ACTION_SHOW_ALL) { - sprintf((char *)IObuff, "%3ld: ", count); /* show match nr */ - msg_puts(IObuff); - sprintf((char *)IObuff, "%4ld", *lnum); /* show line nr */ - /* Highlight line numbers */ - msg_puts_attr(IObuff, hl_attr(HLF_N)); - MSG_PUTS(" "); + snprintf((char *)IObuff, IOSIZE, "%3ld: ", count); // Show match nr. + msg_puts((const char *)IObuff); + snprintf((char *)IObuff, IOSIZE, "%4ld", *lnum); // Show line nr. + // Highlight line numbers. + msg_puts_attr((const char *)IObuff, hl_attr(HLF_N)); + msg_puts(" "); } msg_prt_line(line, FALSE); ui_flush(); /* show one line at a time */ diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 1ace39344d..dea09cd633 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -3094,10 +3094,11 @@ count_common_word ( } hash = hash_hash(p); - hi = hash_lookup(&lp->sl_wordcount, p, hash); + const size_t p_len = STRLEN(p); + hi = hash_lookup(&lp->sl_wordcount, (const char *)p, p_len, hash); if (HASHITEM_EMPTY(hi)) { - wc = xmalloc(sizeof(wordcount_T) + STRLEN(p)); - STRCPY(wc->wc_word, p); + wc = xmalloc(sizeof(wordcount_T) + p_len); + memcpy(wc->wc_word, p, p_len + 1); wc->wc_count = count; hash_add_item(&lp->sl_wordcount, hi, wc->wc_word, hash); } else { @@ -5513,7 +5514,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) } hash = hash_hash(dw); - hi = hash_lookup(&ht, dw, hash); + hi = hash_lookup(&ht, (const char *)dw, STRLEN(dw), hash); if (!HASHITEM_EMPTY(hi)) { if (p_verbose > 0) smsg(_("Duplicate word in %s line %d: %s"), @@ -6343,7 +6344,7 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int if (spin->si_verbose) { msg_start(); - msg_puts((char_u *)_(msg_compressing)); + msg_puts(_(msg_compressing)); msg_clr_eos(); msg_didout = FALSE; msg_col = 0; @@ -6519,7 +6520,8 @@ node_compress ( // Try to find an identical child. hash = hash_hash(child->wn_u1.hashkey); - hi = hash_lookup(ht, child->wn_u1.hashkey, hash); + hi = hash_lookup(ht, (const char *)child->wn_u1.hashkey, + STRLEN(child->wn_u1.hashkey), hash); if (!HASHITEM_EMPTY(hi)) { // There are children we encountered before with a hash value // identical to the current child. Now check if there is one @@ -8515,7 +8517,7 @@ void spell_suggest(int count) vim_snprintf((char *)IObuff, IOSIZE, ":ot \"%.*s\" egnahC", sug.su_badlen, sug.su_badptr); } - msg_puts(IObuff); + msg_puts((const char *)IObuff); msg_clr_eos(); msg_putchar('\n'); @@ -8531,18 +8533,19 @@ void spell_suggest(int count) sug.su_badptr + stp->st_orglen, sug.su_badlen - stp->st_orglen + 1); vim_snprintf((char *)IObuff, IOSIZE, "%2d", i + 1); - if (cmdmsg_rl) + if (cmdmsg_rl) { rl_mirror(IObuff); - msg_puts(IObuff); + } + msg_puts((const char *)IObuff); vim_snprintf((char *)IObuff, IOSIZE, " \"%s\"", wcopy); - msg_puts(IObuff); + msg_puts((const char *)IObuff); // The word may replace more than "su_badlen". if (sug.su_badlen < stp->st_orglen) { vim_snprintf((char *)IObuff, IOSIZE, _(" < \"%.*s\""), - stp->st_orglen, sug.su_badptr); - msg_puts(IObuff); + stp->st_orglen, sug.su_badptr); + msg_puts((const char *)IObuff); } if (p_verbose > 0) { @@ -8558,7 +8561,7 @@ void spell_suggest(int count) // Mirror the numbers, but keep the leading space. rl_mirror(IObuff + 1); msg_advance(30); - msg_puts(IObuff); + msg_puts((const char *)IObuff); } msg_putchar('\n'); } @@ -11182,11 +11185,13 @@ add_sound_suggest ( // the words that have a better score than before. Use a hashtable to // remember the words that have been done. hash = hash_hash(goodword); - hi = hash_lookup(&slang->sl_sounddone, goodword, hash); + const size_t goodword_len = STRLEN(goodword); + hi = hash_lookup(&slang->sl_sounddone, (const char *)goodword, goodword_len, + hash); if (HASHITEM_EMPTY(hi)) { - sft = xmalloc(sizeof(sftword_T) + STRLEN(goodword)); + sft = xmalloc(sizeof(sftword_T) + goodword_len); sft->sft_score = score; - STRCPY(sft->sft_word, goodword); + memcpy(sft->sft_word, goodword, goodword_len + 1); hash_add_item(&slang->sl_sounddone, hi, sft->sft_word, hash); } else { sft = HI2SFT(hi); @@ -11453,10 +11458,10 @@ static void set_map_str(slang_T *lp, char_u *map) mb_char2bytes(headc, b + cl + 1); b[cl + 1 + headcl] = NUL; hash = hash_hash(b); - hi = hash_lookup(&lp->sl_map_hash, b, hash); - if (HASHITEM_EMPTY(hi)) + hi = hash_lookup(&lp->sl_map_hash, (const char *)b, STRLEN(b), hash); + if (HASHITEM_EMPTY(hi)) { hash_add_item(&lp->sl_map_hash, hi, b, hash); - else { + } else { // This should have been checked when generating the .spl // file. EMSG(_("E783: duplicate char in MAP entry")); @@ -11661,9 +11666,10 @@ static void add_banned(suginfo_T *su, char_u *word) hashitem_T *hi; hash = hash_hash(word); - hi = hash_lookup(&su->su_banned, word, hash); + const size_t word_len = STRLEN(word); + hi = hash_lookup(&su->su_banned, (const char *)word, word_len, hash); if (HASHITEM_EMPTY(hi)) { - s = vim_strsave(word); + s = xmemdupz(word, word_len); hash_add_item(&su->su_banned, hi, s, hash); } } @@ -12995,19 +13001,17 @@ pop: // ":spellinfo" void ex_spellinfo(exarg_T *eap) { - langp_T *lp; - char_u *p; - - if (no_spell_checking(curwin)) + if (no_spell_checking(curwin)) { return; + } msg_start(); - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len && !got_int; ++lpi) { - lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); - msg_puts((char_u *)"file: "); - msg_puts(lp->lp_slang->sl_fname); + for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len && !got_int; lpi++) { + langp_T *const lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); + msg_puts("file: "); + msg_puts((const char *)lp->lp_slang->sl_fname); msg_putchar('\n'); - p = lp->lp_slang->sl_info; + const char *const p = (const char *)lp->lp_slang->sl_info; if (p != NULL) { msg_puts(p); msg_putchar('\n'); diff --git a/src/nvim/strings.c b/src/nvim/strings.c index b38d4f8a58..f7218cc267 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -1,7 +1,11 @@ #include <inttypes.h> +#include <stdarg.h> #include <stdbool.h> #include <string.h> +#include <math.h> +#include <assert.h> +#include "nvim/assert.h" #include "nvim/vim.h" #include "nvim/ascii.h" #include "nvim/strings.h" @@ -541,3 +545,847 @@ char_u *concat_str(const char_u *restrict str1, const char_u *restrict str2) return dest; } + +static const char *const e_printf = + N_("E766: Insufficient arguments for printf()"); + +/// Get number argument from idxp entry in tvs +/// +/// Will give an error message for VimL entry with invalid type or for +/// insufficient entries. +/// +/// @param[in] tvs List of VimL values. List is terminated by VAR_UNKNOWN +/// value. +/// @param[in,out] idxp Index in a list. Will be incremented. Indexing starts +/// at 1. +/// +/// @return Number value or 0 in case of error. +static varnumber_T tv_nr(typval_T *tvs, int *idxp) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + int idx = *idxp - 1; + varnumber_T n = 0; + + if (tvs[idx].v_type == VAR_UNKNOWN) { + EMSG(_(e_printf)); + } else { + (*idxp)++; + int err = false; + n = (varnumber_T)get_tv_number_chk(&tvs[idx], &err); + if (err) { + n = 0; + } + } + return n; +} + +/// Get string argument from idxp entry in tvs +/// +/// Will give an error message for VimL entry with invalid type or for +/// insufficient entries. +/// +/// @param[in] tvs List of VimL values. List is terminated by VAR_UNKNOWN +/// value. +/// @param[in,out] idxp Index in a list. Will be incremented. +/// +/// @return String value or NULL in case of error. +static char *tv_str(typval_T *tvs, int *idxp) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + int idx = *idxp - 1; + char *s = NULL; + + if (tvs[idx].v_type == VAR_UNKNOWN) { + EMSG(_(e_printf)); + } else { + (*idxp)++; + s = (char *)get_tv_string_chk(&tvs[idx]); + } + return s; +} + +/// Get pointer argument from the next entry in tvs +/// +/// Will give an error message for VimL entry with invalid type or for +/// insufficient entries. +/// +/// @param[in] tvs List of typval_T values. +/// @param[in,out] idxp Pointer to the index of the current value. +/// +/// @return Pointer stored in typval_T or NULL. +static const void *tv_ptr(const typval_T *const tvs, int *const idxp) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ +#define OFF(attr) offsetof(union typval_vval_union, attr) + STATIC_ASSERT( + OFF(v_string) == OFF(v_list) + && OFF(v_string) == OFF(v_dict) + && OFF(v_string) == OFF(v_partial) + && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_list) + && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_dict) + && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_partial), + "Strings, dictionaries, lists and partials are expected to be pointers, " + "so that all three of them can be accessed via v_string"); +#undef OFF + const int idx = *idxp - 1; + if (tvs[idx].v_type == VAR_UNKNOWN) { + EMSG(_(e_printf)); + return NULL; + } else { + (*idxp)++; + return tvs[idx].vval.v_string; + } +} + +/// Get float argument from idxp entry in tvs +/// +/// Will give an error message for VimL entry with invalid type or for +/// insufficient entries. +/// +/// @param[in] tvs List of VimL values. List is terminated by VAR_UNKNOWN +/// value. +/// @param[in,out] idxp Index in a list. Will be incremented. +/// +/// @return Floating-point value or zero in case of error. +static float_T tv_float(typval_T *const tvs, int *const idxp) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + int idx = *idxp - 1; + float_T f = 0; + + if (tvs[idx].v_type == VAR_UNKNOWN) { + EMSG(_(e_printf)); + } else { + (*idxp)++; + if (tvs[idx].v_type == VAR_FLOAT) { + f = tvs[idx].vval.v_float; + } else if (tvs[idx].v_type == VAR_NUMBER) { + f = tvs[idx].vval.v_number; + } else { + EMSG(_("E807: Expected Float argument for printf()")); + } + } + return f; +} + +// This code was included to provide a portable vsnprintf() and snprintf(). +// Some systems may provide their own, but we always use this one for +// consistency. +// +// This code is based on snprintf.c - a portable implementation of snprintf +// by Mark Martinec <mark.martinec@ijs.si>, Version 2.2, 2000-10-06. +// Included with permission. It was heavily modified to fit in Vim. +// The original code, including useful comments, can be found here: +// +// http://www.ijs.si/software/snprintf/ +// +// This snprintf() only supports the following conversion specifiers: +// s, c, b, B, d, u, o, x, X, p (and synonyms: i, D, U, O - see below) +// with flags: '-', '+', ' ', '0' and '#'. +// An asterisk is supported for field width as well as precision. +// +// Limited support for floating point was added: 'f', 'e', 'E', 'g', 'G'. +// +// Length modifiers 'h' (short int), 'l' (long int) and "ll" (long long int) are +// supported. +// +// The locale is not used, the string is used as a byte string. This is only +// relevant for double-byte encodings where the second byte may be '%'. +// +// It is permitted for "str_m" to be zero, and it is permitted to specify NULL +// pointer for resulting string argument if "str_m" is zero (as per ISO C99). +// +// The return value is the number of characters which would be generated +// for the given input, excluding the trailing NUL. If this value +// is greater or equal to "str_m", not all characters from the result +// have been stored in str, output bytes beyond the ("str_m"-1) -th character +// are discarded. If "str_m" is greater than zero it is guaranteed +// the resulting string will be NUL-terminated. + +// vim_vsnprintf() can be invoked with either "va_list" or a list of +// "typval_T". When the latter is not used it must be NULL. + +/// Append a formatted value to the string +/// +/// @see vim_vsnprintf(). +int vim_snprintf_add(char *str, size_t str_m, char *fmt, ...) +{ + const size_t len = strlen(str); + size_t space; + + if (str_m <= len) { + space = 0; + } else { + space = str_m - len; + } + va_list ap; + va_start(ap, fmt); + const int str_l = vim_vsnprintf(str + len, space, fmt, ap, NULL); + va_end(ap); + return str_l; +} + +/// Write formatted value to the string +/// +/// @param[out] str String to write to. +/// @param[in] str_m String length. +/// @param[in] fmt String format. +/// +/// @return Number of bytes excluding NUL byte that would be written to the +/// string if str_m was greater or equal to the return value. +int vim_snprintf(char *str, size_t str_m, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + const int str_l = vim_vsnprintf(str, str_m, fmt, ap, NULL); + va_end(ap); + return str_l; +} + + +/// Write formatted value to the string +/// +/// @param[out] str String to write to. +/// @param[in] str_m String length. +/// @param[in] fmt String format. +/// @param[in] ap Values that should be formatted. Ignored if tvs is not NULL. +/// @param[in] tvs Values that should be formatted, for printf() VimL +/// function. Must be NULL in other cases. +/// +/// @return Number of bytes excluding NUL byte that would be written to the +/// string if str_m was greater or equal to the return value. +int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap, + typval_T *const tvs) +{ + size_t str_l = 0; + bool str_avail = str_l < str_m; + const char *p = fmt; + int arg_idx = 1; + + if (!p) { + p = ""; + } + while (*p) { + if (*p != '%') { + // copy up to the next '%' or NUL without any changes + size_t n = (size_t)(xstrchrnul(p + 1, '%') - p); + if (str_avail) { + size_t avail = str_m - str_l; + memmove(str + str_l, p, MIN(n, avail)); + str_avail = n < avail; + } + p += n; + assert(n <= SIZE_MAX - str_l); + str_l += n; + } else { + size_t min_field_width = 0, precision = 0; + int zero_padding = 0, precision_specified = 0, justify_left = 0; + int alternate_form = 0, force_sign = 0; + + // if both ' ' and '+' flags appear, ' ' flag should be ignored + int space_for_positive = 1; + + // allowed values: \0, h, l, 2 (for ll), z, L + char length_modifier = '\0'; + + // temporary buffer for simple numeric->string conversion +# define TMP_LEN 350 // 1e308 seems reasonable as the maximum printable + char tmp[TMP_LEN]; + + // string address in case of string argument + const char *str_arg = NULL; + + // natural field width of arg without padding and sign + size_t str_arg_l; + + // unsigned char argument value (only defined for c conversion); + // standard explicitly states the char argument for the c + // conversion is unsigned + unsigned char uchar_arg; + + // number of zeros to be inserted for numeric conversions as + // required by the precision or minimal field width + size_t number_of_zeros_to_pad = 0; + + // index into tmp where zero padding is to be inserted + size_t zero_padding_insertion_ind = 0; + + // current conversion specifier character + char fmt_spec = '\0'; + + p++; // skip '%' + + // parse flags + while (true) { + switch (*p) { + case '0': zero_padding = 1; p++; continue; + case '-': justify_left = 1; p++; continue; + // if both '0' and '-' flags appear, '0' should be ignored + case '+': force_sign = 1; space_for_positive = 0; p++; continue; + case ' ': force_sign = 1; p++; continue; + // if both ' ' and '+' flags appear, ' ' should be ignored + case '#': alternate_form = 1; p++; continue; + case '\'': p++; continue; + default: break; + } + break; + } + + // parse field width + if (*p == '*') { + p++; + const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int); + if (j >= 0) { + min_field_width = (size_t)j; + } else { + min_field_width = (size_t)-j; + justify_left = 1; + } + } else if (ascii_isdigit((int)(*p))) { + // size_t could be wider than unsigned int; make sure we treat + // argument like common implementations do + unsigned int uj = (unsigned)(*p++ - '0'); + + while (ascii_isdigit((int)(*p))) { + uj = 10 * uj + (unsigned int)(*p++ - '0'); + } + min_field_width = uj; + } + + // parse precision + if (*p == '.') { + p++; + precision_specified = 1; + if (*p == '*') { + const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int); + p++; + if (j >= 0) { + precision = (size_t)j; + } else { + precision_specified = 0; + precision = 0; + } + } else if (ascii_isdigit((int)(*p))) { + // size_t could be wider than unsigned int; make sure we + // treat argument like common implementations do + unsigned int uj = (unsigned)(*p++ - '0'); + + while (ascii_isdigit((int)(*p))) { + uj = 10 * uj + (unsigned int)(*p++ - '0'); + } + precision = uj; + } + } + + // parse 'h', 'l', 'll' and 'z' length modifiers + if (*p == 'h' || *p == 'l' || *p == 'z') { + length_modifier = *p; + p++; + if (length_modifier == 'l' && *p == 'l') { // ll, encoded as 2 + length_modifier = '2'; + p++; + } + } + + fmt_spec = *p; + + // common synonyms + switch (fmt_spec) { + case 'i': fmt_spec = 'd'; break; + case 'D': fmt_spec = 'd'; length_modifier = 'l'; break; + case 'U': fmt_spec = 'u'; length_modifier = 'l'; break; + case 'O': fmt_spec = 'o'; length_modifier = 'l'; break; + case 'F': fmt_spec = 'f'; break; + default: break; + } + + // get parameter value, do initial processing + switch (fmt_spec) { + // '%' and 'c' behave similar to 's' regarding flags and field widths + case '%': case 'c': case 's': case 'S': + str_arg_l = 1; + switch (fmt_spec) { + case '%': + str_arg = p; + break; + + case 'c': { + const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int); + // standard demands unsigned char + uchar_arg = (unsigned char)j; + str_arg = (char *)&uchar_arg; + break; + } + + case 's': + case 'S': + str_arg = tvs ? tv_str(tvs, &arg_idx) : va_arg(ap, char *); + if (!str_arg) { + str_arg = "[NULL]"; + str_arg_l = 6; + } else if (!precision_specified) { + // make sure not to address string beyond the specified + // precision + str_arg_l = strlen(str_arg); + } else if (precision == 0) { + // truncate string if necessary as requested by precision + str_arg_l = 0; + } else { + // memchr on HP does not like n > 2^31 + // TODO(elmart): check if this still holds / is relevant + str_arg_l = (size_t)((char *)xmemscan(str_arg, + NUL, + MIN(precision, + 0x7fffffff)) + - str_arg); + } + if (fmt_spec == 'S') { + if (min_field_width != 0) { + min_field_width += (strlen(str_arg) + - mb_string2cells((char_u *)str_arg)); + } + if (precision) { + const char *p1 = str_arg; + for (size_t i = 0; i < precision && *p1; i++) { + p1 += mb_ptr2len((const char_u *)p1); + } + str_arg_l = precision = (size_t)(p1 - str_arg); + } + } + break; + + default: + break; + } + break; + + case 'd': + case 'u': + case 'b': case 'B': + case 'o': + case 'x': case 'X': + case 'p': { + // u, b, B, o, x, X and p conversion specifiers imply + // the value is unsigned; d implies a signed value + + // 0 if numeric argument is zero (or if pointer is NULL for 'p'), + // +1 if greater than zero (or non NULL for 'p'), + // -1 if negative (unsigned argument is never negative) + int arg_sign = 0; + + intmax_t arg = 0; + uintmax_t uarg = 0; + + // only defined for p conversion + const void *ptr_arg = NULL; + + if (fmt_spec == 'p') { + length_modifier = '\0'; + ptr_arg = tvs ? tv_ptr(tvs, &arg_idx) : va_arg(ap, void *); + if (ptr_arg) { + arg_sign = 1; + } + } else if (fmt_spec == 'd') { + // signed + switch (length_modifier) { + case '\0': + case 'h': { + // char and short arguments are passed as int + arg = (tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int)); + break; + } + case 'l': { + arg = (tvs ? (long)tv_nr(tvs, &arg_idx) : va_arg(ap, long)); + break; + } + case '2': { + arg = ( + tvs + ? (long long)tv_nr(tvs, &arg_idx) // NOLINT (runtime/int) + : va_arg(ap, long long)); // NOLINT (runtime/int) + break; + } + case 'z': { + arg = (tvs + ? (ptrdiff_t)tv_nr(tvs, &arg_idx) + : va_arg(ap, ptrdiff_t)); + break; + } + } + if (arg > 0) { + arg_sign = 1; + } else if (arg < 0) { + arg_sign = -1; + } + } else { + // unsigned + switch (length_modifier) { + case '\0': + case 'h': { + uarg = (tvs + ? (unsigned)tv_nr(tvs, &arg_idx) + : va_arg(ap, unsigned)); + break; + } + case 'l': { + uarg = (tvs + ? (unsigned long)tv_nr(tvs, &arg_idx) + : va_arg(ap, unsigned long)); + break; + } + case '2': { + uarg = (uintmax_t)(unsigned long long)( // NOLINT (runtime/int) + tvs + ? ((unsigned long long) // NOLINT (runtime/int) + tv_nr(tvs, &arg_idx)) + : va_arg(ap, unsigned long long)); // NOLINT (runtime/int) + break; + } + case 'z': { + uarg = (tvs + ? (size_t)tv_nr(tvs, &arg_idx) + : va_arg(ap, size_t)); + break; + } + } + arg_sign = (uarg != 0); + } + + str_arg = tmp; + str_arg_l = 0; + + // For d, i, u, o, x, and X conversions, if precision is specified, + // '0' flag should be ignored. This is so with Solaris 2.6, Digital + // UNIX 4.0, HPUX 10, Linux, FreeBSD, NetBSD; but not with Perl. + if (precision_specified) { + zero_padding = 0; + } + + if (fmt_spec == 'd') { + if (force_sign && arg_sign >= 0) { + tmp[str_arg_l++] = space_for_positive ? ' ' : '+'; + } + // leave negative numbers for snprintf to handle, to + // avoid handling tricky cases like (short int)-32768 + } else if (alternate_form) { + if (arg_sign != 0 && (fmt_spec == 'x' || fmt_spec == 'X' + || fmt_spec == 'b' || fmt_spec == 'B')) { + tmp[str_arg_l++] = '0'; + tmp[str_arg_l++] = fmt_spec; + } + // alternate form should have no effect for p * conversion, but ... + } + + zero_padding_insertion_ind = str_arg_l; + if (!precision_specified) { + precision = 1; // default precision is 1 + } + if (precision == 0 && arg_sign == 0) { + // when zero value is formatted with an explicit precision 0, + // resulting formatted string is empty (d, i, u, b, B, o, x, X, p) + } else { + switch (fmt_spec) { + case 'p': { // pointer + str_arg_l += (size_t)snprintf(tmp + str_arg_l, + sizeof(tmp) - str_arg_l, + "%p", ptr_arg); + break; + } + case 'd': { // signed + str_arg_l += (size_t)snprintf(tmp + str_arg_l, + sizeof(tmp) - str_arg_l, + "%" PRIdMAX, arg); + break; + } + case 'b': case 'B': { // binary + size_t bits = 0; + for (bits = sizeof(uintmax_t) * 8; bits > 0; bits--) { + if ((uarg >> (bits - 1)) & 0x1) { + break; + } + } + + while (bits > 0) { + tmp[str_arg_l++] = ((uarg >> --bits) & 0x1) ? '1' : '0'; + } + break; + } + default: { // unsigned + // construct a simple format string for snprintf + char f[] = "%" PRIuMAX; + f[sizeof("%" PRIuMAX) - 1 - 1] = fmt_spec; + assert(PRIuMAX[sizeof(PRIuMAX) - 1 - 1] == 'u'); + str_arg_l += (size_t)snprintf(tmp + str_arg_l, + sizeof(tmp) - str_arg_l, + f, uarg); + break; + } + assert(str_arg_l < sizeof(tmp)); + } + + // include the optional minus sign and possible "0x" in the region + // before the zero padding insertion point + if (zero_padding_insertion_ind < str_arg_l + && tmp[zero_padding_insertion_ind] == '-') { + zero_padding_insertion_ind++; + } + if (zero_padding_insertion_ind + 1 < str_arg_l + && tmp[zero_padding_insertion_ind] == '0' + && (tmp[zero_padding_insertion_ind + 1] == 'x' + || tmp[zero_padding_insertion_ind + 1] == 'X' + || tmp[zero_padding_insertion_ind + 1] == 'b' + || tmp[zero_padding_insertion_ind + 1] == 'B')) { + zero_padding_insertion_ind += 2; + } + } + + { + size_t num_of_digits = str_arg_l - zero_padding_insertion_ind; + + if (alternate_form && fmt_spec == 'o' + // unless zero is already the first character + && !(zero_padding_insertion_ind < str_arg_l + && tmp[zero_padding_insertion_ind] == '0')) { + // assure leading zero for alternate-form octal numbers + if (!precision_specified + || precision < num_of_digits + 1) { + // precision is increased to force the first character to be + // zero, except if a zero value is formatted with an explicit + // precision of zero + precision = num_of_digits + 1; + } + } + // zero padding to specified precision? + if (num_of_digits < precision) { + number_of_zeros_to_pad = precision - num_of_digits; + } + } + // zero padding to specified minimal field width? + if (!justify_left && zero_padding) { + const int n = (int)(min_field_width - (str_arg_l + + number_of_zeros_to_pad)); + if (n > 0) { + number_of_zeros_to_pad += (size_t)n; + } + } + break; + } + + case 'f': + case 'e': + case 'E': + case 'g': + case 'G': + { + // floating point + char format[40]; + int remove_trailing_zeroes = false; + + double f = tvs ? tv_float(tvs, &arg_idx) : va_arg(ap, double); + double abs_f = f < 0 ? -f : f; + + if (fmt_spec == 'g' || fmt_spec == 'G') { + // can't use %g directly, cause it prints "1.0" as "1" + if ((abs_f >= 0.001 && abs_f < 10000000.0) || abs_f == 0.0) { + fmt_spec = 'f'; + } else { + fmt_spec = fmt_spec == 'g' ? 'e' : 'E'; + } + remove_trailing_zeroes = true; + } + + if (fmt_spec == 'f' && abs_f > 1.0e307) { + // avoid a buffer overflow + memmove(tmp, "inf", sizeof("inf")); + str_arg_l = sizeof("inf") - 1; + } else { + format[0] = '%'; + int l = 1; + if (precision_specified) { + size_t max_prec = TMP_LEN - 10; + + // make sure we don't get more digits than we have room for + if (fmt_spec == 'f' && abs_f > 1.0) { + max_prec -= (size_t)log10(abs_f); + } + if (precision > max_prec) { + precision = max_prec; + } + l += snprintf(format + 1, sizeof(format) - 1, ".%d", + (int)precision); + } + format[l] = fmt_spec; + format[l + 1] = NUL; + assert(l + 1 < (int)sizeof(format)); + str_arg_l = (size_t)snprintf(tmp, sizeof(tmp), format, f); + assert(str_arg_l < sizeof(tmp)); + + if (remove_trailing_zeroes) { + int i; + char *tp; + + // using %g or %G: remove superfluous zeroes + if (fmt_spec == 'f') { + tp = tmp + str_arg_l - 1; + } else { + tp = (char *)vim_strchr((char_u *)tmp, + fmt_spec == 'e' ? 'e' : 'E'); + if (tp) { + // remove superfluous '+' and leading zeroes from exponent + if (tp[1] == '+') { + // change "1.0e+07" to "1.0e07" + STRMOVE(tp + 1, tp + 2); + str_arg_l--; + } + i = (tp[1] == '-') ? 2 : 1; + while (tp[i] == '0') { + // change "1.0e07" to "1.0e7" + STRMOVE(tp + i, tp + i + 1); + str_arg_l--; + } + tp--; + } + } + + if (tp != NULL && !precision_specified) { + // remove trailing zeroes, but keep the one just after a dot + while (tp > tmp + 2 && *tp == '0' && tp[-1] != '.') { + STRMOVE(tp, tp + 1); + tp--; + str_arg_l--; + } + } + } else { + // Be consistent: some printf("%e") use 1.0e+12 and some + // 1.0e+012; remove one zero in the last case. + char *tp = (char *)vim_strchr((char_u *)tmp, + fmt_spec == 'e' ? 'e' : 'E'); + if (tp && (tp[1] == '+' || tp[1] == '-') && tp[2] == '0' + && ascii_isdigit(tp[3]) && ascii_isdigit(tp[4])) { + STRMOVE(tp + 2, tp + 3); + str_arg_l--; + } + } + } + str_arg = tmp; + break; + } + + default: + // unrecognized conversion specifier, keep format string as-is + zero_padding = 0; // turn zero padding off for non-numeric conversion + justify_left = 1; + min_field_width = 0; // reset flags + + // discard the unrecognized conversion, just keep + // the unrecognized conversion character + str_arg = p; + str_arg_l = 0; + if (*p) { + str_arg_l++; // include invalid conversion specifier + } + // unchanged if not at end-of-string + break; + } + + if (*p) { + p++; // step over the just processed conversion specifier + } + + // insert padding to the left as requested by min_field_width; + // this does not include the zero padding in case of numerical conversions + if (!justify_left) { + assert(str_arg_l <= SIZE_MAX - number_of_zeros_to_pad); + if (min_field_width > str_arg_l + number_of_zeros_to_pad) { + // left padding with blank or zero + size_t pn = min_field_width - (str_arg_l + number_of_zeros_to_pad); + if (str_avail) { + size_t avail = str_m - str_l; + memset(str + str_l, zero_padding ? '0' : ' ', MIN(pn, avail)); + str_avail = pn < avail; + } + assert(pn <= SIZE_MAX - str_l); + str_l += pn; + } + } + + // zero padding as requested by the precision or by the minimal + // field width for numeric conversions required? + if (number_of_zeros_to_pad == 0) { + // will not copy first part of numeric right now, + // force it to be copied later in its entirety + zero_padding_insertion_ind = 0; + } else { + // insert first part of numerics (sign or '0x') before zero padding + if (zero_padding_insertion_ind > 0) { + size_t zn = zero_padding_insertion_ind; + if (str_avail) { + size_t avail = str_m - str_l; + memmove(str + str_l, str_arg, MIN(zn, avail)); + str_avail = zn < avail; + } + assert(zn <= SIZE_MAX - str_l); + str_l += zn; + } + + // insert zero padding as requested by precision or min field width + if (number_of_zeros_to_pad > 0) { + size_t zn = number_of_zeros_to_pad; + if (str_avail) { + size_t avail = str_m - str_l; + memset(str + str_l, '0', MIN(zn, avail)); + str_avail = zn < avail; + } + assert(zn <= SIZE_MAX - str_l); + str_l += zn; + } + } + + // insert formatted string + // (or as-is conversion specifier for unknown conversions) + if (str_arg_l > zero_padding_insertion_ind) { + size_t sn = str_arg_l - zero_padding_insertion_ind; + if (str_avail) { + size_t avail = str_m - str_l; + memmove(str + str_l, + str_arg + zero_padding_insertion_ind, + MIN(sn, avail)); + str_avail = sn < avail; + } + assert(sn <= SIZE_MAX - str_l); + str_l += sn; + } + + // insert right padding + if (justify_left) { + assert(str_arg_l <= SIZE_MAX - number_of_zeros_to_pad); + if (min_field_width > str_arg_l + number_of_zeros_to_pad) { + // right blank padding to the field width + size_t pn = min_field_width - (str_arg_l + number_of_zeros_to_pad); + if (str_avail) { + size_t avail = str_m - str_l; + memset(str + str_l, ' ', MIN(pn, avail)); + str_avail = pn < avail; + } + assert(pn <= SIZE_MAX - str_l); + str_l += pn; + } + } + } + } + + if (str_m > 0) { + // make sure the string is nul-terminated even at the expense of + // overwriting the last character (shouldn't happen, but just in case) + str[str_l <= str_m - 1 ? str_l : str_m - 1] = '\0'; + } + + if (tvs && tvs[arg_idx - 1].v_type != VAR_UNKNOWN) { + EMSG(_("E767: Too many arguments to printf()")); + } + + // return the number of characters formatted (excluding trailing nul + // character); that is, the number of characters that would have been + // written to the buffer if it were large enough. + return (int)str_l; +} diff --git a/src/nvim/strings.h b/src/nvim/strings.h index 3f0f0c8d6a..eb8b83c7d0 100644 --- a/src/nvim/strings.h +++ b/src/nvim/strings.h @@ -1,7 +1,12 @@ #ifndef NVIM_STRINGS_H #define NVIM_STRINGS_H +#include <stdarg.h> #include <stdbool.h> +#include <stddef.h> + +#include "nvim/types.h" +#include "nvim/eval_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "strings.h.generated.h" diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 37e5542dad..0a27d9dd92 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -3576,22 +3576,24 @@ syn_list_one ( } syn_list_flags(namelist1, spp->sp_flags, attr); - if (spp->sp_cont_list != NULL) - put_id_list((char_u *)"contains", spp->sp_cont_list, attr); + if (spp->sp_cont_list != NULL) { + put_id_list("contains", spp->sp_cont_list, attr); + } - if (spp->sp_syn.cont_in_list != NULL) - put_id_list((char_u *)"containedin", - spp->sp_syn.cont_in_list, attr); + if (spp->sp_syn.cont_in_list != NULL) { + put_id_list("containedin", spp->sp_syn.cont_in_list, attr); + } if (spp->sp_next_list != NULL) { - put_id_list((char_u *)"nextgroup", spp->sp_next_list, attr); + put_id_list("nextgroup", spp->sp_next_list, attr); syn_list_flags(namelist2, spp->sp_flags, attr); } if (spp->sp_flags & (HL_SYNC_HERE|HL_SYNC_THERE)) { - if (spp->sp_flags & HL_SYNC_HERE) - msg_puts_attr((char_u *)"grouphere", attr); - else - msg_puts_attr((char_u *)"groupthere", attr); + if (spp->sp_flags & HL_SYNC_HERE) { + msg_puts_attr("grouphere", attr); + } else { + msg_puts_attr("groupthere", attr); + } msg_putchar(' '); if (spp->sp_sync_idx >= 0) msg_outtrans(HL_TABLE()[SYN_ITEMS(curwin->w_s) @@ -3605,7 +3607,7 @@ syn_list_one ( /* list the link, if there is one */ if (HL_TABLE()[id - 1].sg_link && (did_header || link_only) && !got_int) { (void)syn_list_header(did_header, 999, id); - msg_puts_attr((char_u *)"links to", attr); + msg_puts_attr("links to", attr); msg_putchar(' '); msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name); } @@ -3617,7 +3619,7 @@ static void syn_list_flags(struct name_list *nlist, int flags, int attr) for (i = 0; nlist[i].flag != 0; ++i) if (flags & nlist[i].flag) { - msg_puts_attr((char_u *)nlist[i].name, attr); + msg_puts_attr(nlist[i].name, attr); msg_putchar(' '); } } @@ -3640,15 +3642,16 @@ static void syn_list_cluster(int id) msg_advance(endcol); if (SYN_CLSTR(curwin->w_s)[id].scl_list != NULL) { - put_id_list((char_u *)"cluster", SYN_CLSTR(curwin->w_s)[id].scl_list, - hl_attr(HLF_D)); + put_id_list("cluster", SYN_CLSTR(curwin->w_s)[id].scl_list, hl_attr(HLF_D)); } else { - msg_puts_attr((char_u *)"cluster", hl_attr(HLF_D)); - msg_puts((char_u *)"=NONE"); + msg_puts_attr("cluster", hl_attr(HLF_D)); + msg_puts("=NONE"); } } -static void put_id_list(char_u *name, short *list, int attr) +static void put_id_list(const char *name, + short *list, // NOLINT(runtime/int) + int attr) { short *p; @@ -3656,14 +3659,15 @@ static void put_id_list(char_u *name, short *list, int attr) msg_putchar('='); for (p = list; *p; ++p) { if (*p >= SYNID_ALLBUT && *p < SYNID_TOP) { - if (p[1]) - MSG_PUTS("ALLBUT"); - else - MSG_PUTS("ALL"); + if (p[1]) { + msg_puts("ALLBUT"); + } else { + msg_puts("ALL"); + } } else if (*p >= SYNID_TOP && *p < SYNID_CONTAINED) { - MSG_PUTS("TOP"); + msg_puts("TOP"); } else if (*p >= SYNID_CONTAINED && *p < SYNID_CLUSTER) { - MSG_PUTS("CONTAINED"); + msg_puts("CONTAINED"); } else if (*p >= SYNID_CLUSTER) { short scl_id = *p - SYNID_CLUSTER; @@ -3688,7 +3692,7 @@ static void put_pattern(char *s, int c, synpat_T *spp, int attr) /* May have to write "matchgroup=group" */ if (last_matchgroup != spp->sp_syn_match_id) { last_matchgroup = spp->sp_syn_match_id; - msg_puts_attr((char_u *)"matchgroup", attr); + msg_puts_attr("matchgroup", attr); msg_putchar('='); if (last_matchgroup == 0) msg_outtrans((char_u *)"NONE"); @@ -3697,8 +3701,8 @@ static void put_pattern(char *s, int c, synpat_T *spp, int attr) msg_putchar(' '); } - /* output the name of the pattern and an '=' or ' ' */ - msg_puts_attr((char_u *)s, attr); + // Output the name of the pattern and an '=' or ' '. + msg_puts_attr(s, attr); msg_putchar(c); /* output the pattern, in between a char that is not in the pattern */ @@ -3718,9 +3722,10 @@ static void put_pattern(char *s, int c, synpat_T *spp, int attr) if (!(spp->sp_off_flags & (mask + (mask << SPO_COUNT)))) { continue; } - if (!first) - msg_putchar(','); /* separate with commas */ - msg_puts((char_u *)spo_name_tab[i]); + if (!first) { + msg_putchar(','); // Separate with commas. + } + msg_puts(spo_name_tab[i]); n = spp->sp_offsets[i]; if (i != SPO_LC_OFF) { if (spp->sp_off_flags & mask) @@ -3792,32 +3797,31 @@ syn_list_keywords ( } did_header = TRUE; if (prev_contained != (kp->flags & HL_CONTAINED)) { - msg_puts_attr((char_u *)"contained", attr); + msg_puts_attr("contained", attr); msg_putchar(' '); prev_contained = (kp->flags & HL_CONTAINED); } if (kp->k_syn.cont_in_list != prev_cont_in_list) { - put_id_list((char_u *)"containedin", - kp->k_syn.cont_in_list, attr); + put_id_list("containedin", kp->k_syn.cont_in_list, attr); msg_putchar(' '); prev_cont_in_list = kp->k_syn.cont_in_list; } if (kp->next_list != prev_next_list) { - put_id_list((char_u *)"nextgroup", kp->next_list, attr); + put_id_list("nextgroup", kp->next_list, attr); msg_putchar(' '); prev_next_list = kp->next_list; if (kp->flags & HL_SKIPNL) { - msg_puts_attr((char_u *)"skipnl", attr); + msg_puts_attr("skipnl", attr); msg_putchar(' '); prev_skipnl = (kp->flags & HL_SKIPNL); } if (kp->flags & HL_SKIPWHITE) { - msg_puts_attr((char_u *)"skipwhite", attr); + msg_puts_attr("skipwhite", attr); msg_putchar(' '); prev_skipwhite = (kp->flags & HL_SKIPWHITE); } if (kp->flags & HL_SKIPEMPTY) { - msg_puts_attr((char_u *)"skipempty", attr); + msg_puts_attr("skipempty", attr); msg_putchar(' '); prev_skipempty = (kp->flags & HL_SKIPEMPTY); } @@ -3929,7 +3933,8 @@ static void add_keyword(char_u *name, hash_T hash = hash_hash(kp->keyword); hashtab_T *ht = (curwin->w_s->b_syn_ic) ? &curwin->w_s->b_keywtab_ic : &curwin->w_s->b_keywtab; - hashitem_T *hi = hash_lookup(ht, kp->keyword, hash); + hashitem_T *hi = hash_lookup(ht, (const char *)kp->keyword, + 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 @@ -5516,19 +5521,21 @@ void ex_ownsyntax(exarg_T *eap) clear_string_option(&curwin->w_s->b_syn_isk); } - /* save value of b:current_syntax */ - old_value = get_var_value((char_u *)"b:current_syntax"); - if (old_value != NULL) + // Save value of b:current_syntax. + old_value = get_var_value("b:current_syntax"); + if (old_value != NULL) { old_value = vim_strsave(old_value); + } /* Apply the "syntax" autocommand event, this finds and loads the syntax * file. */ apply_autocmds(EVENT_SYNTAX, eap->arg, curbuf->b_fname, TRUE, curbuf); - /* move value of b:current_syntax to w:current_syntax */ - new_value = get_var_value((char_u *)"b:current_syntax"); - if (new_value != NULL) + // Move value of b:current_syntax to w:current_syntax. + new_value = get_var_value("b:current_syntax"); + if (new_value != NULL) { set_internal_string_var((char_u *)"w:current_syntax", new_value); + } /* restore value of b:current_syntax */ if (old_value == NULL) @@ -5988,7 +5995,7 @@ init_highlight ( * Try finding the color scheme file. Used when a color file was loaded * and 'background' or 't_Co' is changed. */ - char_u *p = get_var_value((char_u *)"g:colors_name"); + char_u *p = get_var_value("g:colors_name"); if (p != NULL) { // Value of g:colors_name could be freed in load_colors() and make // p invalid, so copy it. @@ -6042,7 +6049,7 @@ init_highlight ( /* * If syntax highlighting is enabled load the highlighting for it. */ - if (get_var_value((char_u *)"g:syntax_on") != NULL) { + if (get_var_value("g:syntax_on") != NULL) { static int recursive = 0; if (recursive >= 5) { @@ -6871,8 +6878,8 @@ static void highlight_list_one(int id) if (sgp->sg_link && !got_int) { (void)syn_list_header(didh, 9999, id); - didh = TRUE; - msg_puts_attr((char_u *)"links to", hl_attr(HLF_D)); + didh = true; + msg_puts_attr("links to", hl_attr(HLF_D)); msg_putchar(' '); msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name); } @@ -7057,7 +7064,7 @@ syn_list_header ( /* Show "xxx" with the attributes. */ if (!did_header) { - msg_puts_attr((char_u *)"xxx", syn_id2attr(id)); + msg_puts_attr("xxx", syn_id2attr(id)); msg_putchar(' '); } @@ -7517,7 +7524,7 @@ static void highlight_list(void) static void highlight_list_two(int cnt, int attr) { - msg_puts_attr((char_u *)&("N \bI \b! \b"[cnt / 11]), attr); + msg_puts_attr(&("N \bI \b! \b"[cnt / 11]), attr); msg_clr_eos(); ui_flush(); os_delay(cnt == 99 ? 40L : (long)cnt * 50L, false); @@ -7528,22 +7535,25 @@ static void highlight_list_two(int cnt, int attr) * Function given to ExpandGeneric() to obtain the list of group names. * Also used for synIDattr() function. */ -char_u *get_highlight_name(expand_T *xp, int idx) -{ - //TODO: 'xp' is unused - if (idx == highlight_ga.ga_len && include_none != 0) - return (char_u *)"none"; - if (idx == highlight_ga.ga_len + include_none && include_default != 0) - return (char_u *)"default"; - if (idx == highlight_ga.ga_len + include_none + include_default - && include_link != 0) - return (char_u *)"link"; - if (idx == highlight_ga.ga_len + include_none + include_default + 1 - && include_link != 0) - return (char_u *)"clear"; - if (idx < 0 || idx >= highlight_ga.ga_len) +const char *get_highlight_name(expand_T *const xp, const int idx) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + // TODO(justinmk): 'xp' is unused + if (idx == highlight_ga.ga_len && include_none != 0) { + return "none"; + } else if (idx == highlight_ga.ga_len + include_none + && include_default != 0) { + return "default"; + } else if (idx == highlight_ga.ga_len + include_none + include_default + && include_link != 0) { + return "link"; + } else if (idx == highlight_ga.ga_len + include_none + include_default + 1 + && include_link != 0) { + return "clear"; + } else if (idx < 0 || idx >= highlight_ga.ga_len) { return NULL; - return HL_TABLE()[idx].sg_name; + } + return (const char *)HL_TABLE()[idx].sg_name; } color_name_table_T color_name_table[] = { diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 0b76e36a6b..cc5aac6094 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -540,10 +540,11 @@ do_tag ( } vim_snprintf((char *)IObuff + 1, IOSIZE - 1, "%2d %s ", i + 1, mt_names[matches[i][0] & MT_MASK]); - msg_puts(IObuff); - if (tagp.tagkind != NULL) + msg_puts((const char *)IObuff); + if (tagp.tagkind != NULL) { msg_outtrans_len(tagp.tagkind, - (int)(tagp.tagkind_end - tagp.tagkind)); + (int)(tagp.tagkind_end - tagp.tagkind)); + } msg_advance(13); msg_outtrans_len_attr(tagp.tagname, (int)(tagp.tagname_end - tagp.tagname), @@ -595,7 +596,7 @@ do_tag ( } p = msg_outtrans_one(p, attr); if (*p == TAB) { - msg_puts_attr((char_u *)" ", attr); + msg_puts_attr(" ", attr); break; } if (*p == ':') @@ -858,13 +859,15 @@ do_tag ( STRCAT(IObuff, _(" Using tag with different case!")); if ((num_matches > prev_num_matches || new_tag) && num_matches > 1) { - if (ic) - msg_attr(IObuff, hl_attr(HLF_W)); - else + if (ic) { + msg_attr((const char *)IObuff, hl_attr(HLF_W)); + } else { msg(IObuff); - msg_scroll = TRUE; /* don't overwrite this message */ - } else + } + msg_scroll = true; // Don't overwrite this message. + } else { give_warning(IObuff, ic); + } if (ic && !msg_scrolled && msg_silent == 0) { ui_flush(); os_delay(1000L, true); @@ -1161,6 +1164,12 @@ find_tags ( case TC_MATCH: p_ic = false; break; + case TC_FOLLOWSCS: + p_ic = ignorecase(pat); + break; + case TC_SMART: + p_ic = ignorecase_opt(pat, true, true); + break; default: assert(false); } diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index bd7b9fc58f..3fd2814070 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -1,18 +1,17 @@ -// VT220/xterm-like terminal emulator implementation for nvim. Powered by -// libvterm (http://www.leonerd.org.uk/code/libvterm/). +// VT220/xterm-like terminal emulator. +// Powered by libvterm http://www.leonerd.org.uk/code/libvterm // // libvterm is a pure C99 terminal emulation library with abstract input and // display. This means that the library needs to read data from the master fd // and feed VTerm instances, which will invoke user callbacks with screen // update instructions that must be mirrored to the real display. // -// Keys are pressed in VTerm instances by calling +// Keys are sent to VTerm instances by calling // vterm_keyboard_key/vterm_keyboard_unichar, which generates byte streams that // must be fed back to the master fd. // -// This implementation uses nvim buffers as the display mechanism for both -// the visible screen and the scrollback buffer. When focused, the window -// "pins" to the bottom of the buffer and mirrors libvterm screen state. +// Nvim buffers are used as the display mechanism for both the visible screen +// and the scrollback buffer. // // When a line becomes invisible due to a decrease in screen height or because // a line was pushed up during normal terminal output, we store the line @@ -23,18 +22,17 @@ // scrollback buffer, which is mirrored in the nvim buffer displaying lines // that were previously invisible. // -// The vterm->nvim synchronization is performed in intervals of 10 -// milliseconds. This is done to minimize screen updates when receiving large -// bursts of data. +// The vterm->nvim synchronization is performed in intervals of 10 milliseconds, +// to minimize screen updates when receiving large bursts of data. // // This module is decoupled from the processes that normally feed it data, so // it's possible to use it as a general purpose console buffer (possibly as a // log/display mechanism for nvim in the future) // -// Inspired by vimshell (http://www.wana.at/vimshell/) and -// Conque (https://code.google.com/p/conque/). Libvterm usage instructions (plus -// some extra code) were taken from -// pangoterm (http://www.leonerd.org.uk/code/pangoterm/) +// Inspired by: vimshell http://www.wana.at/vimshell +// Conque https://code.google.com/p/conque +// Some code from pangoterm http://www.leonerd.org.uk/code/pangoterm + #include <assert.h> #include <stdio.h> #include <stdint.h> @@ -87,10 +85,10 @@ typedef struct terminal_state { # include "terminal.c.generated.h" #endif -#define SCROLLBACK_BUFFER_DEFAULT_SIZE 1000 +#define SB_MAX 100000 // Maximum 'scrollback' value. + // Delay for refreshing the terminal buffer after receiving updates from -// libvterm. This is greatly improves performance when receiving large bursts -// of data. +// libvterm. Improves performance when receiving large bursts of data. #define REFRESH_DELAY 10 static TimeWatcher refresh_timer; @@ -102,27 +100,23 @@ typedef struct { } ScrollbackLine; struct terminal { - // options passed to terminal_open - TerminalOptions opts; - // libvterm structures + TerminalOptions opts; // options passed to terminal_open VTerm *vt; VTermScreen *vts; // buffer used to: // - convert VTermScreen cell arrays into utf8 strings // - receive data from libvterm as a result of key presses. char textbuf[0x1fff]; - // Scrollback buffer storage for libvterm. - // TODO(tarruda): Use a doubly-linked list - ScrollbackLine **sb_buffer; - // number of rows pushed to sb_buffer - size_t sb_current; - // sb_buffer size; - size_t sb_size; + + ScrollbackLine **sb_buffer; // Scrollback buffer storage for libvterm + size_t sb_current; // number of rows pushed to sb_buffer + size_t sb_size; // sb_buffer size // "virtual index" that points to the first sb_buffer row that we need to // push to the terminal buffer when refreshing the scrollback. When negative, // it actually points to entries that are no longer in sb_buffer (because the // window height has increased) and must be deleted from the terminal buffer int sb_pending; + // buf_T instance that acts as a "drawing surface" for libvterm // we can't store a direct reference to the buffer because the // refresh_timer_cb may be called after the buffer was freed, and there's @@ -130,20 +124,18 @@ struct terminal { handle_T buf_handle; // program exited bool closed, destroy; + // some vterm properties bool forward_mouse; - // invalid rows libvterm screen - int invalid_start, invalid_end; + int invalid_start, invalid_end; // invalid rows in libvterm screen struct { int row, col; bool visible; } cursor; - // which mouse button is pressed - int pressed_button; - // pending width/height - bool pending_resize; - // With a reference count of 0 the terminal can be freed. - size_t refcount; + int pressed_button; // which mouse button is pressed + bool pending_resize; // pending width/height + + size_t refcount; // reference count }; static VTermScreenCallbacks vterm_screen_callbacks = { @@ -174,7 +166,7 @@ void terminal_init(void) VTerm *vt = vterm_new(24, 80); VTermState *state = vterm_obtain_state(vt); - for (int color_index = 0; color_index < 256; color_index++) { + for (int color_index = 255; color_index >= 0; color_index--) { VTermColor color; // Some of the default 16 colors has the same color as the later // 240 colors. To avoid collisions, we will use the custom colors @@ -237,25 +229,22 @@ Terminal *terminal_open(TerminalOptions opts) rv->invalid_end = opts.height; refresh_screen(rv, curbuf); set_option_value((uint8_t *)"buftype", 0, (uint8_t *)"terminal", OPT_LOCAL); - // some sane settings for terminal buffers + + // Default settings for terminal buffers + curbuf->b_p_ma = false; // 'nomodifiable' + curbuf->b_p_ul = -1; // disable undo + curbuf->b_p_scbk = 1000; // 'scrollback' set_option_value((uint8_t *)"wrap", false, NULL, OPT_LOCAL); set_option_value((uint8_t *)"number", false, NULL, OPT_LOCAL); set_option_value((uint8_t *)"relativenumber", false, NULL, OPT_LOCAL); buf_set_term_title(curbuf, (char *)curbuf->b_ffname); RESET_BINDING(curwin); - // Apply TermOpen autocmds so the user can configure the terminal + + // Apply TermOpen autocmds _before_ configuring the scrollback buffer. apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, curbuf); - // Configure the scrollback buffer. Try to get the size from: - // - // - b:terminal_scrollback_buffer_size - // - g:terminal_scrollback_buffer_size - // - SCROLLBACK_BUFFER_DEFAULT_SIZE - // - // but limit to 100k. - int size = get_config_int("terminal_scrollback_buffer_size"); - rv->sb_size = size > 0 ? (size_t)size : SCROLLBACK_BUFFER_DEFAULT_SIZE; - rv->sb_size = MIN(rv->sb_size, 100000); + // Configure the scrollback buffer. + rv->sb_size = curbuf->b_p_scbk < 0 ? SB_MAX : (size_t)curbuf->b_p_scbk;; rv->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * rv->sb_size); if (!true_color) { @@ -334,22 +323,22 @@ void terminal_close(Terminal *term, char *msg) void terminal_resize(Terminal *term, uint16_t width, uint16_t height) { if (term->closed) { - // will be called after exited if two windows display the same terminal and - // one of the is closed as a consequence of pressing a key. + // If two windows display the same terminal and one is closed by keypress. return; } + bool force = width == UINT16_MAX || height == UINT16_MAX; int curwidth, curheight; vterm_get_size(term->vt, &curheight, &curwidth); - if (!width) { + if (force || !width) { width = (uint16_t)curwidth; } - if (!height) { + if (force || !height) { height = (uint16_t)curheight; } - if (curheight == height && curwidth == width) { + if (!force && curheight == height && curwidth == width) { return; } @@ -381,8 +370,7 @@ void terminal_enter(void) State = TERM_FOCUS; mapped_ctrl_c |= TERM_FOCUS; // Always map CTRL-C to avoid interrupt. RedrawingDisabled = false; - // go to the bottom when the terminal is focused - adjust_topline(s->term, buf, false); + adjust_topline(s->term, buf, 0); // scroll to end // erase the unfocused cursor invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1); showmode(); @@ -667,10 +655,15 @@ static int term_bell(void *data) return 1; } -// the scrollback push/pop handlers were copied almost verbatim from pangoterm +// Scrollback push handler (from pangoterm). static int term_sb_push(int cols, const VTermScreenCell *cells, void *data) { Terminal *term = data; + + if (!term->sb_size) { + return 0; + } + // copy vterm cells into sb_buffer size_t c = (size_t)cols; ScrollbackLine *sbrow = NULL; @@ -682,10 +675,12 @@ static int term_sb_push(int cols, const VTermScreenCell *cells, void *data) xfree(term->sb_buffer[term->sb_current - 1]); } + // Make room at the start by shifting to the right. memmove(term->sb_buffer + 1, term->sb_buffer, sizeof(term->sb_buffer[0]) * (term->sb_current - 1)); } else if (term->sb_current > 0) { + // Make room at the start by shifting to the right. memmove(term->sb_buffer + 1, term->sb_buffer, sizeof(term->sb_buffer[0]) * term->sb_current); } @@ -695,6 +690,7 @@ static int term_sb_push(int cols, const VTermScreenCell *cells, void *data) sbrow->cols = c; } + // New row is added at the start of the storage buffer. term->sb_buffer[0] = sbrow; if (term->sb_current < term->sb_size) { term->sb_current++; @@ -710,6 +706,11 @@ static int term_sb_push(int cols, const VTermScreenCell *cells, void *data) return 1; } +/// Scrollback pop handler (from pangoterm). +/// +/// @param cols +/// @param cells VTerm state to update. +/// @param data Terminal static int term_sb_pop(int cols, VTermScreenCell *cells, void *data) { Terminal *term = data; @@ -722,24 +723,24 @@ static int term_sb_pop(int cols, VTermScreenCell *cells, void *data) term->sb_pending--; } - // restore vterm state - size_t c = (size_t)cols; ScrollbackLine *sbrow = term->sb_buffer[0]; term->sb_current--; + // Forget the "popped" row by shifting the rest onto it. memmove(term->sb_buffer, term->sb_buffer + 1, sizeof(term->sb_buffer[0]) * (term->sb_current)); - size_t cols_to_copy = c; + size_t cols_to_copy = (size_t)cols; if (cols_to_copy > sbrow->cols) { cols_to_copy = sbrow->cols; } // copy to vterm state memcpy(cells, sbrow->cells, sizeof(cells[0]) * cols_to_copy); - for (size_t col = cols_to_copy; col < c; col++) { + for (size_t col = cols_to_copy; col < (size_t)cols; col++) { cells[col].chars[0] = 0; cells[col].width = 1; } + xfree(sbrow); pmap_put(ptr_t)(invalidated_terminals, term, NULL); @@ -885,7 +886,7 @@ static bool send_mouse_event(Terminal *term, int c) // terminal buffer refresh & misc {{{ -void fetch_row(Terminal *term, int row, int end_col) +static void fetch_row(Terminal *term, int row, int end_col) { int col = 0; size_t line_len = 0; @@ -958,28 +959,26 @@ static void refresh_terminal(Terminal *term) buf_T *buf = handle_get_buffer(term->buf_handle); bool valid = true; if (!buf || !(valid = buf_valid(buf))) { - // destroyed by `close_buffer`. Dont do anything else + // Destroyed by `close_buffer`. Do not do anything else. if (!valid) { term->buf_handle = 0; } return; } - bool pending_resize = term->pending_resize; + long ml_before = buf->b_ml.ml_line_count; WITH_BUFFER(buf, { refresh_size(term, buf); refresh_scrollback(term, buf); refresh_screen(term, buf); redraw_buf_later(buf, NOT_VALID); }); - adjust_topline(term, buf, pending_resize); + long ml_added = buf->b_ml.ml_line_count - ml_before; + adjust_topline(term, buf, ml_added); } -// libuv timer callback. This will enqueue on_refresh to be processed as an -// event. +// Calls refresh_terminal() on all invalidated_terminals. static void refresh_timer_cb(TimeWatcher *watcher, void *data) { - if (exiting) { - // bad things can happen if we redraw when exiting, and there's no need to - // update the buffer. + if (exiting) { // Cannot redraw (requires event loop) during teardown/exit. goto end; } Terminal *term; @@ -1010,7 +1009,37 @@ static void refresh_size(Terminal *term, buf_T *buf) term->opts.resize_cb((uint16_t)width, (uint16_t)height, term->opts.data); } -// Refresh the scrollback of a invalidated terminal +/// Adjusts scrollback storage after 'scrollback' option changed. +static void on_scrollback_option_changed(Terminal *term, buf_T *buf) +{ + const size_t scbk = curbuf->b_p_scbk < 0 + ? SB_MAX : (size_t)MAX(1, curbuf->b_p_scbk); + assert(term->sb_current < SIZE_MAX); + if (term->sb_pending > 0) { // Pending rows must be processed first. + abort(); + } + + // Delete lines exceeding the new 'scrollback' limit. + if (scbk < term->sb_current) { + size_t diff = term->sb_current - scbk; + for (size_t i = 0; i < diff; i++) { + ml_delete(1, false); + term->sb_current--; + xfree(term->sb_buffer[term->sb_current]); + } + deleted_lines(1, (long)diff); + } + + // Resize the scrollback storage. + size_t sb_region = sizeof(ScrollbackLine *) * scbk; + if (scbk != term->sb_size) { + term->sb_buffer = xrealloc(term->sb_buffer, sb_region); + } + + term->sb_size = scbk; +} + +// Refresh the scrollback of an invalidated terminal. static void refresh_scrollback(Terminal *term, buf_T *buf) { int width, height; @@ -1039,9 +1068,11 @@ static void refresh_scrollback(Terminal *term, buf_T *buf) ml_delete(buf->b_ml.ml_line_count, false); deleted_lines(buf->b_ml.ml_line_count, 1); } + + on_scrollback_option_changed(term, buf); } -// Refresh the screen(visible part of the buffer when the terminal is +// Refresh the screen (visible part of the buffer when the terminal is // focused) of a invalidated terminal static void refresh_screen(Terminal *term, buf_T *buf) { @@ -1050,8 +1081,7 @@ static void refresh_screen(Terminal *term, buf_T *buf) int height; int width; vterm_get_size(term->vt, &height, &width); - // It's possible that the terminal height decreased and `term->invalid_end` - // doesn't reflect it yet + // Terminal height may have decreased before `invalid_end` reflects it. term->invalid_end = MIN(term->invalid_end, height); for (int r = term->invalid_start, linenr = row_to_linenr(term, r); @@ -1096,14 +1126,6 @@ static void redraw(bool restore_cursor) update_screen(0); } - redraw_statuslines(); - - if (need_maketitle) { - maketitle(); - } - - showruler(false); - if (term && is_focused(term)) { curwin->w_wrow = term->cursor.row; curwin->w_wcol = term->cursor.col + win_col_off(curwin); @@ -1123,21 +1145,21 @@ static void redraw(bool restore_cursor) ui_flush(); } -static void adjust_topline(Terminal *term, buf_T *buf, bool force) +static void adjust_topline(Terminal *term, buf_T *buf, long added) { int height, width; vterm_get_size(term->vt, &height, &width); FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_buffer == buf) { - // for every window that displays a terminal, ensure the cursor is in a - // valid line - wp->w_cursor.lnum = MIN(wp->w_cursor.lnum, buf->b_ml.ml_line_count); - if (force || curbuf != buf || is_focused(term)) { - // if the terminal is not in the current window or if it's focused, - // adjust topline/cursor so the window will "follow" the terminal - // output - wp->w_cursor.lnum = buf->b_ml.ml_line_count; + linenr_T ml_end = buf->b_ml.ml_line_count; + bool following = ml_end == wp->w_cursor.lnum + added; // cursor at end? + if (following || (wp == curwin && is_focused(term))) { + // "Follow" the terminal output + wp->w_cursor.lnum = ml_end; set_topline(wp, MAX(wp->w_cursor.lnum - height + 1, 1)); + } else { + // Ensure valid cursor for each window displaying this terminal. + wp->w_cursor.lnum = MIN(wp->w_cursor.lnum, ml_end); } } } @@ -1180,17 +1202,6 @@ static char *get_config_string(char *key) return NULL; } -static int get_config_int(char *key) -{ - Object obj; - GET_CONFIG_VALUE(key, obj); - if (obj.type == kObjectTypeInteger) { - return (int)obj.data.integer; - } - api_free_object(obj); - return 0; -} - // }}} // vim: foldmethod=marker diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 721300c334..6f4e0fe49f 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -34,12 +34,14 @@ NEW_TESTS ?= \ test_cscope.res \ test_digraph.res \ test_diffmode.res \ + test_filter_map.res \ test_gn.res \ test_hardcopy.res \ test_help_tagjump.res \ test_history.res \ test_increment.res \ test_increment_dbcs.res \ + test_lambda.res \ test_langmap.res \ test_match.res \ test_matchadd_conceal.res \ diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 316aba968d..5299fec7c2 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -62,6 +62,12 @@ lang mess C " Always use forward slashes. set shellslash +" Make sure $HOME does not get read or written. +let $HOME = '/does/not/exist' + +" Prepare for calling garbagecollect_for_testing(). +let v:testing = 1 + " Align with vim defaults. set directory^=. set nohidden diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 60248bf430..04cc279619 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -9,8 +9,10 @@ source test_ex_undo.vim source test_expr.vim source test_expr_utf8.vim source test_feedkeys.vim +source test_filter_map.vim source test_goto.vim source test_jumps.vim +source test_lambda.vim source test_match.vim source test_matchadd_conceal_utf8.vim source test_menu.vim @@ -23,6 +25,7 @@ source test_statusline.vim source test_syn_attr.vim source test_tabline.vim source test_tabpage.vim +source test_tagcase.vim source test_tagjump.vim source test_unlet.vim source test_window_cmd.vim diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 21bb057fe1..40db227d97 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -85,6 +85,11 @@ func Test_getcompletion() let l = getcompletion('paint', 'function') call assert_equal([], l) + let Flambda = {-> 'hello'} + let l = getcompletion('', 'function') + let l = filter(l, {i, v -> v =~ 'lambda'}) + call assert_equal(0, len(l)) + let l = getcompletion('run', 'file') call assert_true(index(l, 'runtest.vim') >= 0) let l = getcompletion('walk', 'file') diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index 39dcacb55f..7ceef2834f 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -106,3 +106,68 @@ func Test_setmatches() call setmatches(set) call assert_equal(exp, getmatches()) endfunc + +func Test_substitute_expr() + let g:val = 'XXX' + call assert_equal('XXX', substitute('yyy', 'y*', '\=g:val', '')) + call assert_equal('XXX', substitute('yyy', 'y*', {-> g:val}, '')) + call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)', + \ '\=nr2char("0x" . submatch(1))', 'g')) + call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)', + \ {-> nr2char("0x" . submatch(1))}, 'g')) + + call assert_equal('231', substitute('123', '\(.\)\(.\)\(.\)', + \ {-> submatch(2) . submatch(3) . submatch(1)}, '')) + + func Recurse() + return substitute('yyy', 'y*', {-> g:val}, '') + endfunc + call assert_equal('--', substitute('xxx', 'x*', {-> '-' . Recurse() . '-'}, '')) +endfunc + +func Test_invalid_submatch() + " This was causing invalid memory access in Vim-7.4.2232 and older + call assert_fails("call substitute('x', '.', {-> submatch(10)}, '')", 'E935:') +endfunc + +func Test_substitute_expr_arg() + call assert_equal('123456789-123456789=', substitute('123456789', + \ '\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)', + \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) + + call assert_equal('123456-123456=789', substitute('123456789', + \ '\(.\)\(.\)\(.\)\(a*\)\(n*\)\(.\)\(.\)\(.\)\(x*\)', + \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) + + call assert_equal('123456789-123456789x=', substitute('123456789', + \ '\(.\)\(.\)\(.*\)', + \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . 'x' . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) + + call assert_fails("call substitute('xxx', '.', {m -> string(add(m, 'x'))}, '')", 'E742:') + call assert_fails("call substitute('xxx', '.', {m -> string(insert(m, 'x'))}, '')", 'E742:') + call assert_fails("call substitute('xxx', '.', {m -> string(extend(m, ['x']))}, '')", 'E742:') + call assert_fails("call substitute('xxx', '.', {m -> string(remove(m, 1))}, '')", 'E742:') +endfunc + +func Test_function_with_funcref() + let s:f = function('type') + let s:fref = function(s:f) + call assert_equal(v:t_string, s:fref('x')) + call assert_fails("call function('s:f')", 'E700:') +endfunc + +func Test_funcref() + func! One() + return 1 + endfunc + let OneByName = function('One') + let OneByRef = funcref('One') + func! One() + return 2 + endfunc + call assert_equal(2, OneByName()) + call assert_equal(1, OneByRef()) + let OneByRef = funcref('One') + call assert_equal(2, OneByRef()) + call assert_fails('echo funcref("{")', 'E475:') +endfunc diff --git a/src/nvim/testdir/test_filter_map.vim b/src/nvim/testdir/test_filter_map.vim new file mode 100644 index 0000000000..c8d64ce0a4 --- /dev/null +++ b/src/nvim/testdir/test_filter_map.vim @@ -0,0 +1,81 @@ +" Test filter() and map() + +" list with expression string +func Test_filter_map_list_expr_string() + " filter() + call assert_equal([2, 3, 4], filter([1, 2, 3, 4], 'v:val > 1')) + call assert_equal([3, 4], filter([1, 2, 3, 4], 'v:key > 1')) + call assert_equal([], filter([1, 2, 3, 4], 0)) + + " map() + call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], 'v:val * 2')) + call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], 'v:key * 2')) + call assert_equal([9, 9, 9, 9], map([1, 2, 3, 4], 9)) +endfunc + +" dict with expression string +func Test_filter_map_dict_expr_string() + let dict = {"foo": 1, "bar": 2, "baz": 3} + + " filter() + call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), 'v:val > 1')) + call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), 'v:key > "bar"')) + call assert_equal({}, filter(copy(dict), 0)) + + " map() + call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), 'v:val * 2')) + call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), 'v:key[0]')) + call assert_equal({"foo": 9, "bar": 9, "baz": 9}, map(copy(dict), 9)) +endfunc + +" list with funcref +func Test_filter_map_list_expr_funcref() + " filter() + func! s:filter1(index, val) abort + return a:val > 1 + endfunc + call assert_equal([2, 3, 4], filter([1, 2, 3, 4], function('s:filter1'))) + + func! s:filter2(index, val) abort + return a:index > 1 + endfunc + call assert_equal([3, 4], filter([1, 2, 3, 4], function('s:filter2'))) + + " map() + func! s:filter3(index, val) abort + return a:val * 2 + endfunc + call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], function('s:filter3'))) + + func! s:filter4(index, val) abort + return a:index * 2 + endfunc + call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], function('s:filter4'))) +endfunc + +" dict with funcref +func Test_filter_map_dict_expr_funcref() + let dict = {"foo": 1, "bar": 2, "baz": 3} + + " filter() + func! s:filter1(key, val) abort + return a:val > 1 + endfunc + call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), function('s:filter1'))) + + func! s:filter2(key, val) abort + return a:key > "bar" + endfunc + call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), function('s:filter2'))) + + " map() + func! s:filter3(key, val) abort + return a:val * 2 + endfunc + call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), function('s:filter3'))) + + func! s:filter4(key, val) abort + return a:key[0] + endfunc + call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4'))) +endfunc diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim new file mode 100644 index 0000000000..311cc6e2cb --- /dev/null +++ b/src/nvim/testdir/test_lambda.vim @@ -0,0 +1,287 @@ +" Test for lambda and closure + +function! Test_lambda_feature() + call assert_equal(1, has('lambda')) +endfunction + +function! Test_lambda_with_filter() + let s:x = 2 + call assert_equal([2, 3], filter([1, 2, 3], {i, v -> v >= s:x})) +endfunction + +function! Test_lambda_with_map() + let s:x = 1 + call assert_equal([2, 3, 4], map([1, 2, 3], {i, v -> v + s:x})) +endfunction + +function! Test_lambda_with_sort() + call assert_equal([1, 2, 3, 4, 7], sort([3,7,2,1,4], {a, b -> a - b})) +endfunction + +function! Test_lambda_with_timer() + if !has('timers') + return + endif + + let s:n = 0 + let s:timer_id = 0 + function! s:Foo() + "let n = 0 + let s:timer_id = timer_start(50, {-> execute("let s:n += 1 | echo s:n", "")}, {"repeat": -1}) + endfunction + + call s:Foo() + sleep 200ms + " do not collect lambda + call garbagecollect() + let m = s:n + sleep 200ms + call timer_stop(s:timer_id) + call assert_true(m > 1) + call assert_true(s:n > m + 1) + call assert_true(s:n < 9) +endfunction + +function! Test_lambda_with_partial() + let l:Cb = function({... -> ['zero', a:1, a:2, a:3]}, ['one', 'two']) + call assert_equal(['zero', 'one', 'two', 'three'], l:Cb('three')) +endfunction + +function Test_lambda_fails() + call assert_equal(3, {a, b -> a + b}(1, 2)) + call assert_fails('echo {a, a -> a + a}(1, 2)', 'E15:') + call assert_fails('echo {a, b -> a + b)}(1, 2)', 'E15:') +endfunc + +func Test_not_lambda() + let x = {'>' : 'foo'} + call assert_equal('foo', x['>']) +endfunc + +function! Test_lambda_capture_by_reference() + let v = 1 + let l:F = {x -> x + v} + let v = 2 + call assert_equal(12, l:F(10)) +endfunction + +function! Test_lambda_side_effect() + function! s:update_and_return(arr) + let a:arr[1] = 5 + return a:arr + endfunction + + function! s:foo(arr) + return {-> s:update_and_return(a:arr)} + endfunction + + let arr = [3,2,1] + call assert_equal([3, 5, 1], s:foo(arr)()) +endfunction + +function! Test_lambda_refer_local_variable_from_other_scope() + function! s:foo(X) + return a:X() " refer l:x in s:bar() + endfunction + + function! s:bar() + let x = 123 + return s:foo({-> x}) + endfunction + + call assert_equal(123, s:bar()) +endfunction + +function! Test_lambda_do_not_share_local_variable() + function! s:define_funcs() + let l:One = {-> split(execute("let a = 'abc' | echo a"))[0]} + let l:Two = {-> exists("a") ? a : "no"} + return [l:One, l:Two] + endfunction + + let l:F = s:define_funcs() + + call assert_equal('no', l:F[1]()) + call assert_equal('abc', l:F[0]()) + call assert_equal('no', l:F[1]()) +endfunction + +function! Test_lambda_closure_counter() + function! s:foo() + let x = 0 + return {-> [execute("let x += 1"), x][-1]} + endfunction + + let l:F = s:foo() + call garbagecollect() + call assert_equal(1, l:F()) + call assert_equal(2, l:F()) + call assert_equal(3, l:F()) + call assert_equal(4, l:F()) +endfunction + +function! Test_lambda_with_a_var() + function! s:foo() + let x = 2 + return {... -> a:000 + [x]} + endfunction + function! s:bar() + return s:foo()(1) + endfunction + + call assert_equal([1, 2], s:bar()) +endfunction + +function! Test_lambda_call_lambda_from_lambda() + function! s:foo(x) + let l:F1 = {-> {-> a:x}} + return {-> l:F1()} + endfunction + + let l:F = s:foo(1) + call assert_equal(1, l:F()()) +endfunction + +function! Test_lambda_delfunc() + function! s:gen() + let pl = l: + let l:Foo = {-> get(pl, "Foo", get(pl, "Bar", {-> 0}))} + let l:Bar = l:Foo + delfunction l:Foo + return l:Bar + endfunction + + let l:F = s:gen() + call assert_fails(':call l:F()', 'E933:') +endfunction + +function! Test_lambda_scope() + function! s:NewCounter() + let c = 0 + return {-> [execute('let c += 1'), c][-1]} + endfunction + + function! s:NewCounter2() + return {-> [execute('let c += 100'), c][-1]} + endfunction + + let l:C = s:NewCounter() + let l:D = s:NewCounter2() + + call assert_equal(1, l:C()) + call assert_fails(':call l:D()', 'E15:') " E121: then E15: + call assert_equal(2, l:C()) +endfunction + +function! Test_lambda_share_scope() + function! s:New() + let c = 0 + let l:Inc0 = {-> [execute('let c += 1'), c][-1]} + let l:Dec0 = {-> [execute('let c -= 1'), c][-1]} + return [l:Inc0, l:Dec0] + endfunction + + let [l:Inc, l:Dec] = s:New() + + call assert_equal(1, l:Inc()) + call assert_equal(2, l:Inc()) + call assert_equal(1, l:Dec()) +endfunction + +function! Test_lambda_circular_reference() + function! s:Foo() + let d = {} + let d.f = {-> d} + return d.f + endfunction + + call s:Foo() + call garbagecollect() + let i = 0 | while i < 10000 | call s:Foo() | let i+= 1 | endwhile + call garbagecollect() +endfunction + +function! Test_lambda_combination() + call assert_equal(2, {x -> {x -> x}}(1)(2)) + call assert_equal(10, {y -> {x -> x(y)(10)}({y -> y})}({z -> z})) + call assert_equal(5.0, {x -> {y -> x / y}}(10)(2.0)) + call assert_equal(6, {x -> {y -> {z -> x + y + z}}}(1)(2)(3)) + + call assert_equal(6, {x -> {f -> f(x)}}(3)({x -> x * 2})) + call assert_equal(6, {f -> {x -> f(x)}}({x -> x * 2})(3)) + + " Z combinator + let Z = {f -> {x -> f({y -> x(x)(y)})}({x -> f({y -> x(x)(y)})})} + let Fact = {f -> {x -> x == 0 ? 1 : x * f(x - 1)}} + call assert_equal(120, Z(Fact)(5)) +endfunction + +function! Test_closure_counter() + function! s:foo() + let x = 0 + function! s:bar() closure + let x += 1 + return x + endfunction + return function('s:bar') + endfunction + + let l:F = s:foo() + call garbagecollect() + call assert_equal(1, l:F()) + call assert_equal(2, l:F()) + call assert_equal(3, l:F()) + call assert_equal(4, l:F()) +endfunction + +function! Test_closure_unlet() + function! s:foo() + let x = 1 + function! s:bar() closure + unlet x + endfunction + call s:bar() + return l: + endfunction + + call assert_false(has_key(s:foo(), 'x')) + call garbagecollect() +endfunction + +function! LambdaFoo() + let x = 0 + function! LambdaBar() closure + let x += 1 + return x + endfunction + return function('LambdaBar') +endfunction + +func Test_closure_refcount() + let g:Count = LambdaFoo() + call test_garbagecollect_now() + call assert_equal(1, g:Count()) + let g:Count2 = LambdaFoo() + call test_garbagecollect_now() + call assert_equal(1, g:Count2()) + call assert_equal(2, g:Count()) + call assert_equal(3, g:Count2()) + + delfunc LambdaFoo + delfunc LambdaBar +endfunc + +" This test is causing a use-after-free on shutdown. +func Test_named_function_closure() + func! Afoo() + let x = 14 + func! s:Abar() closure + return x + endfunc + call assert_equal(14, s:Abar()) + endfunc + call Afoo() + call assert_equal(14, s:Abar()) + call garbagecollect() + call assert_equal(14, s:Abar()) +endfunc diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim index 3a6e162453..de5c26c2dd 100644 --- a/src/nvim/testdir/test_partial.vim +++ b/src/nvim/testdir/test_partial.vim @@ -14,6 +14,14 @@ func MySort(up, one, two) return a:one < a:two ? 1 : -1 endfunc +func MyMap(sub, index, val) + return a:val - a:sub +endfunc + +func MyFilter(threshold, index, val) + return a:val > a:threshold +endfunc + func Test_partial_args() let Cb = function('MyFunc', ["foo", "bar"]) @@ -36,6 +44,16 @@ func Test_partial_args() call assert_equal([1, 2, 3], sort([3, 1, 2], Sort)) let Sort = function('MySort', [0]) call assert_equal([3, 2, 1], sort([3, 1, 2], Sort)) + + let Map = function('MyMap', [2]) + call assert_equal([-1, 0, 1], map([1, 2, 3], Map)) + let Map = function('MyMap', [3]) + call assert_equal([-2, -1, 0], map([1, 2, 3], Map)) + + let Filter = function('MyFilter', [1]) + call assert_equal([2, 3], filter([1, 2, 3], Filter)) + let Filter = function('MyFilter', [2]) + call assert_equal([3], filter([1, 2, 3], Filter)) endfunc func MyDictFunc(arg1, arg2) dict @@ -59,6 +77,9 @@ func Test_partial_dict() call assert_equal("hello/xxx/yyy", Cb("xxx", "yyy")) call assert_fails('Cb("fff")', 'E492:') + let Cb = function('MyDictFunc', dict) + call assert_equal({"foo": "hello/foo/1", "bar": "hello/bar/2"}, map({"foo": 1, "bar": 2}, Cb)) + let dict = {"tr": function('tr', ['hello', 'h', 'H'])} call assert_equal("Hello", dict.tr()) endfunc diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index 50110dd622..edc6336aa7 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -482,4 +482,24 @@ func Test_completion_ctrl_e_without_autowrap() q! endfunc +func CompleteUndo() abort + call complete(1, g:months) + return '' +endfunc + +func Test_completion_can_undo() + inoremap <Right> <c-r>=CompleteUndo()<cr> + set completeopt+=noinsert,noselect + + new + call feedkeys("a\<Right>a\<Esc>", 'xt') + call assert_equal('a', getline(1)) + undo + call assert_equal('', getline(1)) + + bwipe! + set completeopt& + iunmap <Right> +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_tagcase.vim b/src/nvim/testdir/test_tagcase.vim new file mode 100644 index 0000000000..833cb9f990 --- /dev/null +++ b/src/nvim/testdir/test_tagcase.vim @@ -0,0 +1,73 @@ +" test 'tagcase' option + +func Test_tagcase() + call writefile(["Bar\tXtext\t3", "Foo\tXtext\t2", "foo\tXtext\t4"], 'Xtags') + set tags=Xtags + e Xtext + + for &ic in [0, 1] + for &scs in [0, 1] + for &g:tc in ["followic", "ignore", "match", "followscs", "smart"] + for &l:tc in ["", "followic", "ignore", "match", "followscs", "smart"] + let smart = 0 + if &l:tc != '' + let tc = &l:tc + else + let tc = &g:tc + endif + if tc == 'followic' + let ic = &ic + elseif tc == 'ignore' + let ic = 1 + elseif tc == 'followscs' + let ic = &ic + let smart = &scs + elseif tc == 'smart' + let ic = 1 + let smart = 1 + else + let ic = 0 + endif + if ic && smart + call assert_equal(['foo', 'Foo'], map(taglist("^foo$"), {i, v -> v.name})) + call assert_equal(['Foo'], map(taglist("^Foo$"), {i, v -> v.name})) + elseif ic + call assert_equal(['foo', 'Foo'], map(taglist("^foo$"), {i, v -> v.name})) + call assert_equal(['Foo', 'foo'], map(taglist("^Foo$"), {i, v -> v.name})) + else + call assert_equal(['foo'], map(taglist("^foo$"), {i, v -> v.name})) + call assert_equal(['Foo'], map(taglist("^Foo$"), {i, v -> v.name})) + endif + endfor + endfor + endfor + endfor + + call delete('Xtags') + set ic& + setg tc& + setl tc& + set scs& +endfunc + +func Test_set_tagcase() + " Verify default values. + set ic& + setg tc& + setl tc& + call assert_equal(0, &ic) + call assert_equal('followic', &g:tc) + call assert_equal('followic', &l:tc) + call assert_equal('followic', &tc) + + " Verify that the local setting accepts <empty> but that the global setting + " does not. The first of these (setting the local value to <empty>) should + " succeed; the other two should fail. + setl tc= + call assert_fails('setg tc=', 'E474:') + call assert_fails('set tc=', 'E474:') + + set ic& + setg tc& + setl tc& +endfunc diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index ed82e23be2..9fbbe8be92 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1014,7 +1014,7 @@ static const char *tui_tk_ti_getstr(const char *name, const char *value, } else if (strcmp(name, "key_dc") == 0) { ILOG("libtermkey:kdch1=%s", value); // Vim: "If <BS> and <DEL> are now the same, redefine <DEL>." - if (stty_erase != NULL && strcmp(stty_erase, value) == 0) { + if (stty_erase != NULL && value != NULL && strcmp(stty_erase, value) == 0) { return stty_erase[0] == DEL ? (char *)CTRL_H_STR : (char *)DEL_STR; } } diff --git a/src/nvim/ui.c b/src/nvim/ui.c index d3784b6cd3..8c72c28e56 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -258,7 +258,10 @@ void ui_detach_impl(UI *ui) shift_index++; } - if (--ui_count) { + if (--ui_count + // During teardown/exit the loop was already destroyed, cannot schedule. + // https://github.com/neovim/neovim/pull/5119#issuecomment-258667046 + && !exiting) { ui_schedule_refresh(); } } diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 4b267a1627..c95a795587 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -305,16 +305,12 @@ bool undo_allowed(void) return true; } -/* - * Get the undolevle value for the current buffer. - */ +/// Get the 'undolevels' value for the current buffer. static long get_undolevel(void) { - if (curbuf->terminal) { - return -1; - } - if (curbuf->b_p_ul == NO_LOCAL_UNDOLEVEL) + if (curbuf->b_p_ul == NO_LOCAL_UNDOLEVEL) { return p_ul; + } return curbuf->b_p_ul; } @@ -2512,13 +2508,14 @@ void ex_undolist(exarg_T *eap) sort_strings((char_u **)ga.ga_data, ga.ga_len); msg_start(); - msg_puts_attr((char_u *)_("number changes when saved"), - hl_attr(HLF_T)); - for (int i = 0; i < ga.ga_len && !got_int; ++i) { + msg_puts_attr(_("number changes when saved"), + hl_attr(HLF_T)); + for (int i = 0; i < ga.ga_len && !got_int; i++) { msg_putchar('\n'); - if (got_int) + if (got_int) { break; - msg_puts(((char_u **)ga.ga_data)[i]); + } + msg_puts(((const char **)ga.ga_data)[i]); } msg_end(); diff --git a/src/nvim/version.c b/src/nvim/version.c index 5e33597568..fe443ce01f 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -205,12 +205,12 @@ static int included_patches[] = { // 2238 NA 2237, // 2236, - // 2235, + 2235, // 2234 NA - // 2233, + 2233, // 2232 NA // 2231, - // 2230, + 2230, // 2229, // 2228, 2227, @@ -243,7 +243,7 @@ static int included_patches[] = { // 2200, // 2199 NA // 2198 NA - // 2197, + 2197, // 2196, // 2195 NA 2194, @@ -297,16 +297,16 @@ static int included_patches[] = { 2146, // 2145 NA // 2144, - // 2143, - // 2142, - // 2141, + 2143, + 2142, + 2141, // 2140 NA - // 2139, + 2139, // 2138 NA - // 2137, - // 2136, + 2137, + 2136, // 2135, - // 2134, + 2134, // 2133 NA // 2132 NA // 2131 NA @@ -319,9 +319,9 @@ static int included_patches[] = { 2124, 2123, // 2122 NA - // 2121, - // 2120, - // 2119, + 2121, + 2120, + 2119, // 2118 NA 2117, // 2116 NA @@ -344,13 +344,13 @@ static int included_patches[] = { 2099, // 2098, // 2097, - // 2096, + 2096, // 2095, // 2094 NA // 2093 NA // 2092 NA // 2091 NA - // 2090, + 2090, // 2089 NA 2088, 2087, @@ -364,11 +364,11 @@ static int included_patches[] = { // 2079 NA // 2078 NA 2077, - // 2076, + 2076, 2075, 2074, // 2073 NA - // 2072, + 2072, 2071, // 2070 NA // 2069, @@ -396,7 +396,7 @@ static int included_patches[] = { // 2047, // 2046, // 2045 NA - // 2044, + 2044, 2043, // 2042 NA // 2041 NA @@ -438,7 +438,7 @@ static int included_patches[] = { 2005, // 2004 NA // 2003 NA - // 2002, + 2002, // 2001 NA 2000, 1999, @@ -451,7 +451,7 @@ static int included_patches[] = { // 1992, // 1991, 1990, - // 1989, + 1989, // 1988 NA // 1987 NA // 1986, @@ -714,7 +714,7 @@ static int included_patches[] = { 1730, // 1729 NA 1728, - // 1727 NA + 1727, // 1726 NA // 1725 NA // 1724 NA @@ -2572,7 +2572,7 @@ static void list_features(void) int idx = (i / ncol) + (i % ncol) * nrow; if (idx < nfeat) { int last_col = (i + 1) % ncol == 0; - msg_puts((char_u *)features[idx]); + msg_puts(features[idx]); if (last_col) { if (msg_col > 0) { msg_putchar('\n'); diff --git a/src/nvim/window.c b/src/nvim/window.c index 28269e8889..73a60b2e04 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -4848,7 +4848,7 @@ void scroll_to_fraction(win_T *wp, int prev_height) if (wp->w_buffer->terminal) { terminal_resize(wp->w_buffer->terminal, 0, wp->w_height); - redraw_win_later(wp, CLEAR); + redraw_win_later(wp, NOT_VALID); } } @@ -4872,7 +4872,6 @@ void win_new_width(win_T *wp, int width) if (wp->w_height != 0) { terminal_resize(wp->w_buffer->terminal, wp->w_width, 0); } - redraw_win_later(wp, CLEAR); } } diff --git a/test/functional/cmdline/ctrl_r_spec.lua b/test/functional/cmdline/ctrl_r_spec.lua new file mode 100644 index 0000000000..d2dad23e98 --- /dev/null +++ b/test/functional/cmdline/ctrl_r_spec.lua @@ -0,0 +1,34 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear, insert, funcs, eq, feed = + helpers.clear, helpers.insert, helpers.funcs, helpers.eq, helpers.feed + +describe('cmdline CTRL-R', function() + before_each(clear) + + it('pasting non-special register inserts <CR> *between* lines', function() + insert([[ + line1abc + line2somemoretext + ]]) + -- Yank 2 lines linewise, then paste to cmdline. + feed([[<C-\><C-N>gg0yj:<C-R>0]]) + -- <CR> inserted between lines, NOT after the final line. + eq('line1abc\rline2somemoretext', funcs.getcmdline()) + + -- Yank 2 lines characterwise, then paste to cmdline. + feed([[<C-\><C-N>gg05lyvj:<C-R>0]]) + -- <CR> inserted between lines, NOT after the final line. + eq('abc\rline2', funcs.getcmdline()) + + -- Yank 1 line linewise, then paste to cmdline. + feed([[<C-\><C-N>ggyy:<C-R>0]]) + -- No <CR> inserted. + eq('line1abc', funcs.getcmdline()) + end) + + it('pasting special register inserts <CR>, <NL>', function() + feed([[:<C-R>="foo\nbar\rbaz"<CR>]]) + eq('foo\nbar\rbaz', funcs.getcmdline()) + end) +end) + diff --git a/test/functional/ex_getln/history_spec.lua b/test/functional/cmdline/history_spec.lua index 20f9cf06a2..20f9cf06a2 100644 --- a/test/functional/ex_getln/history_spec.lua +++ b/test/functional/cmdline/history_spec.lua diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 6e9633465f..b551adb8db 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -391,6 +391,27 @@ describe('jobs', function() eq({'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, next_msg()) end) + it('jobstart() works with closures', function() + source([[ + fun! MkFun() + let a1 = 'foo' + let a2 = 'bar' + return {id, data, event -> rpcnotify(g:channel, '1', a1, a2, Normalize(data), event)} + endfun + let g:job_opts = {'on_stdout': MkFun()} + call jobstart('echo "some text"', g:job_opts) + ]]) + eq({'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, next_msg()) + end) + + it('jobstart() works when closure passed directly to `jobstart`', function() + source([[ + let g:job_opts = {'on_stdout': {id, data, event -> rpcnotify(g:channel, '1', 'foo', 'bar', Normalize(data), event)}} + call jobstart('echo "some text"', g:job_opts) + ]]) + eq({'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, next_msg()) + end) + describe('jobwait', function() it('returns a list of status codes', function() source([[ diff --git a/test/functional/eval/has_spec.lua b/test/functional/eval/has_spec.lua index 97b3b0e620..78c4e08fde 100644 --- a/test/functional/eval/has_spec.lua +++ b/test/functional/eval/has_spec.lua @@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local eq = helpers.eq local clear = helpers.clear local funcs = helpers.funcs +local iswin = helpers.iswin describe('has()', function() before_each(clear) @@ -49,4 +50,11 @@ describe('has()', function() eq(1, funcs.has("nvim-00.001.05")) end) + it('"unnamedplus"', function() + if (not iswin()) and funcs.has("clipboard") == 1 then + eq(1, funcs.has("unnamedplus")) + else + eq(0, funcs.has("unnamedplus")) + end + end) end) diff --git a/test/functional/eval/let_spec.lua b/test/functional/eval/let_spec.lua new file mode 100644 index 0000000000..c3ab3cc367 --- /dev/null +++ b/test/functional/eval/let_spec.lua @@ -0,0 +1,22 @@ +local helpers = require('test.functional.helpers')(after_each) + +local eq = helpers.eq +local clear = helpers.clear +local meths = helpers.meths +local redir_exec = helpers.redir_exec + +before_each(clear) + +describe(':let command', function() + it('correctly lists variables with curly-braces', function() + meths.set_var('v', {0}) + eq('\nv [0]', redir_exec('let {"v"}')) + end) + + it('correctly lists variables with subscript', function() + meths.set_var('v', {0}) + eq('\nv[0] #0', redir_exec('let v[0]')) + eq('\ng:["v"][0] #0', redir_exec('let g:["v"][0]')) + eq('\n{"g:"}["v"][0] #0', redir_exec('let {"g:"}["v"][0]')) + end) +end) diff --git a/test/functional/ex_cmds/dict_notifications_spec.lua b/test/functional/ex_cmds/dict_notifications_spec.lua index 5e89986c0f..30753c34ac 100644 --- a/test/functional/ex_cmds/dict_notifications_spec.lua +++ b/test/functional/ex_cmds/dict_notifications_spec.lua @@ -254,23 +254,33 @@ describe('dictionary change notifications', function() command('call g:ReplaceWatcher2()') command('let g:key = "value"') eq({'notification', '2b', {'key', {old = 'v2', new = 'value'}}}, next_msg()) - end) it('does not crash when freeing a watched dictionary', function() source([[ - function! Watcher(dict, key, value) - echo a:key string(a:value) - endfunction + function! Watcher(dict, key, value) + echo a:key string(a:value) + endfunction - function! MakeWatch() - let d = {'foo': 'bar'} - call dictwatcheradd(d, 'foo', function('Watcher')) - endfunction + function! MakeWatch() + let d = {'foo': 'bar'} + call dictwatcheradd(d, 'foo', function('Watcher')) + endfunction ]]) command('call MakeWatch()') eq(2, eval('1+1')) -- Still alive? end) end) + + describe('with lambdas', function() + it('works correctly', function() + source([[ + let d = {'foo': 'baz'} + call dictwatcheradd(d, 'foo', {dict, key, value -> rpcnotify(g:channel, '2', key, value)}) + let d.foo = 'bar' + ]]) + eq({'notification', '2', {'foo', {old = 'baz', new = 'bar'}}}, next_msg()) + end) + end) end) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index eb6cc95f34..a894fa9328 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -9,6 +9,7 @@ local TcpStream = require('nvim.tcp_stream') local SocketStream = require('nvim.socket_stream') local ChildProcessStream = require('nvim.child_process_stream') +local check_cores = global_helpers.check_cores local check_logs = global_helpers.check_logs local neq = global_helpers.neq local eq = global_helpers.eq @@ -619,7 +620,10 @@ local M = { return function(after_each) if after_each then - after_each(check_logs) + after_each(function() + check_logs() + check_cores('build/bin/nvim') + end) end return M end diff --git a/test/functional/legacy/arglist_spec.lua b/test/functional/legacy/arglist_spec.lua index b86d3f0aea..f5e3522972 100644 --- a/test/functional/legacy/arglist_spec.lua +++ b/test/functional/legacy/arglist_spec.lua @@ -222,7 +222,6 @@ describe('argument list commands', function() execute('argedit a') eq({'a', 'b'}, eval('argv()')) eq('a', eval('expand("%:t")')) - assert_fails('argedit a b', 'E172:') execute('argedit c') eq({'a', 'c', 'b'}, eval('argv()')) execute('0argedit x') @@ -232,6 +231,9 @@ describe('argument list commands', function() execute('argedit! y') eq({'x', 'y', 'a', 'c', 'b'}, eval('argv()')) execute('%argd') + -- Nvim allows unescaped spaces in filename on all platforms. #6010 + execute('argedit a b') + eq({'a b'}, eval('argv()')) end) it('test for :argdelete command', function() diff --git a/test/functional/terminal/edit_spec.lua b/test/functional/terminal/edit_spec.lua index 8edcfa56b7..42a5c768bb 100644 --- a/test/functional/terminal/edit_spec.lua +++ b/test/functional/terminal/edit_spec.lua @@ -31,45 +31,41 @@ describe(':edit term://*', function() eq(termopen_runs[1], termopen_runs[1]:match('^term://.//%d+:$')) end) - it('runs TermOpen early enough to respect terminal_scrollback_buffer_size', function() + it("runs TermOpen early enough to set buffer-local 'scrollback'", function() local columns, lines = 20, 4 local scr = get_screen(columns, lines) local rep = 'a' meths.set_option('shellcmdflag', 'REP ' .. rep) - local rep_size = rep:byte() + local rep_size = rep:byte() -- 'a' => 97 local sb = 10 - local gsb = 20 - meths.set_var('terminal_scrollback_buffer_size', gsb) - command('autocmd TermOpen * :let b:terminal_scrollback_buffer_size = ' - .. tostring(sb)) + command('autocmd TermOpen * :setlocal scrollback='..tostring(sb) + ..'|call feedkeys("G", "n")') command('edit term://foobar') + local bufcontents = {} local winheight = curwinmeths.get_height() - -- I have no idea why there is + 4 needed. But otherwise it works fine with - -- different scrollbacks. - local shift = -4 - local buf_cont_start = rep_size - 1 - sb - winheight - shift - local bufline = function(i) return ('%d: foobar'):format(i) end + local buf_cont_start = rep_size - sb - winheight + 2 + local function bufline (i) + return ('%d: foobar'):format(i) + end for i = buf_cont_start,(rep_size - 1) do bufcontents[#bufcontents + 1] = bufline(i) end bufcontents[#bufcontents + 1] = '' bufcontents[#bufcontents + 1] = '[Process exited 0]' - -- Do not ask me why displayed screen is one line *before* buffer - -- contents: buffer starts with 87:, screen with 86:. + local exp_screen = '\n' - local did_cursor = false - for i = 0,(winheight - 1) do - local line = bufline(buf_cont_start + i - 1) + for i = 1,(winheight - 1) do + local line = bufcontents[#bufcontents - winheight + i] exp_screen = (exp_screen - .. (did_cursor and '' or '^') .. line .. (' '):rep(columns - #line) .. '|\n') - did_cursor = true end - exp_screen = exp_screen .. (' '):rep(columns) .. '|\n' + exp_screen = exp_screen..'^[Process exited 0] |\n' + + exp_screen = exp_screen..(' '):rep(columns)..'|\n' scr:expect(exp_screen) - eq(bufcontents, curbufmeths.get_lines(1, -1, true)) + eq(bufcontents, curbufmeths.get_lines(0, -1, true)) end) end) diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index 7c391db18c..7a9d2a9b36 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -20,26 +20,34 @@ describe(':terminal', function() source([[ echomsg "msg1" echomsg "msg2" + echomsg "msg3" ]]) -- Invoke a command that emits frequent terminal activity. execute([[terminal while true; do echo X; done]]) helpers.feed([[<C-\><C-N>]]) - screen:expect([[ - X | - X | - ^X | - | - ]]) + wait() helpers.sleep(10) -- Let some terminal activity happen. execute("messages") screen:expect([[ - X | msg1 | msg2 | + msg3 | Press ENTER or type command to continue^ | ]]) end) + it("in normal-mode :split does not move cursor", function() + execute([[terminal while true; do echo foo; sleep .1; done]]) + helpers.feed([[<C-\><C-N>M]]) -- move cursor away from last line + wait() + eq(3, eval("line('$')")) -- window height + eq(2, eval("line('.')")) -- cursor is in the middle + execute('vsplit') + eq(2, eval("line('.')")) -- cursor stays where we put it + execute('split') + eq(2, eval("line('.')")) -- cursor stays where we put it + end) + end) describe(':terminal (with fake shell)', function() diff --git a/test/functional/terminal/helpers.lua b/test/functional/terminal/helpers.lua index ae5e6d4b1f..934c01e3bf 100644 --- a/test/functional/terminal/helpers.lua +++ b/test/functional/terminal/helpers.lua @@ -1,7 +1,7 @@ local helpers = require('test.functional.helpers')(nil) local Screen = require('test.functional.ui.screen') local nvim_dir = helpers.nvim_dir -local execute, nvim, wait = helpers.execute, helpers.nvim, helpers.wait +local execute, nvim = helpers.execute, helpers.nvim local function feed_data(data) nvim('set_var', 'term_data', data) @@ -34,13 +34,15 @@ local function disable_mouse() feed_termcode('[?1002l') end local default_command = '["'..nvim_dir..'/tty-test'..'"]' -local function screen_setup(extra_height, command) +local function screen_setup(extra_rows, command, cols) + extra_rows = extra_rows and extra_rows or 0 + command = command and command or default_command + cols = cols and cols or 50 + nvim('command', 'highlight TermCursor cterm=reverse') nvim('command', 'highlight TermCursorNC ctermbg=11') - nvim('set_var', 'terminal_scrollback_buffer_size', 10) - if not extra_height then extra_height = 0 end - if not command then command = default_command end - local screen = Screen.new(50, 7 + extra_height) + + local screen = Screen.new(cols, 7 + extra_rows) screen:set_default_attr_ids({ [1] = {reverse = true}, -- focused cursor [2] = {background = 11}, -- unfocused cursor @@ -55,31 +57,42 @@ local function screen_setup(extra_height, command) }) screen:attach({rgb=false}) - -- tty-test puts the terminal into raw mode and echoes all input. tests are - -- done by feeding it with terminfo codes to control the display and - -- verifying output with screen:expect. - execute('enew | call termopen('..command..') | startinsert') + + execute('enew | call termopen('..command..')') + nvim('input', '<CR>') + local vim_errmsg = nvim('eval', 'v:errmsg') + if vim_errmsg and "" ~= vim_errmsg then + error(vim_errmsg) + end + + execute('setlocal scrollback=10') + execute('startinsert') + + -- tty-test puts the terminal into raw mode and echoes input. Tests work by + -- feeding termcodes to control the display and asserting by screen:expect. if command == default_command then - -- wait for "tty ready" to be printed before each test or the terminal may - -- still be in canonical mode(will echo characters for example) - -- - local empty_line = ' ' + -- Wait for "tty ready" to be printed before each test or the terminal may + -- still be in canonical mode (will echo characters for example). + local empty_line = (' '):rep(cols + 1) local expected = { - 'tty ready ', - '{1: } ', + 'tty ready'..(' '):rep(cols - 8), + '{1: }' ..(' '):rep(cols), empty_line, empty_line, empty_line, empty_line, } - for _ = 1, extra_height do + for _ = 1, extra_rows do table.insert(expected, empty_line) end - table.insert(expected, '{3:-- TERMINAL --} ') + table.insert(expected, '{3:-- TERMINAL --}' .. ((' '):rep(cols - 13))) screen:expect(table.concat(expected, '\n')) else - wait() + -- This eval also acts as a wait(). + if 0 == nvim('eval', "exists('b:terminal_job_id')") then + error("terminal job failed to start") + end end return screen end diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua index ecb0b2beb0..da7e1c36db 100644 --- a/test/functional/terminal/mouse_spec.lua +++ b/test/functional/terminal/mouse_spec.lua @@ -117,7 +117,7 @@ describe('terminal mouse', function() rows: 5, cols: 25 |rows: 5, cols: 25 | {2:^ } |{2: } | ========== ========== | - | + :vsp | ]]) feed(':enew | set number<cr>') screen:expect([[ diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index d60819af65..930d0cf58b 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -3,7 +3,11 @@ local helpers = require('test.functional.helpers')(after_each) local thelpers = require('test.functional.terminal.helpers') local clear, eq, curbuf = helpers.clear, helpers.eq, helpers.curbuf local feed, nvim_dir, execute = helpers.feed, helpers.nvim_dir, helpers.execute +local eval = helpers.eval +local command = helpers.command local wait = helpers.wait +local retry = helpers.retry +local curbufmeths = helpers.curbufmeths local feed_data = thelpers.feed_data if helpers.pending_win32(pending) then return end @@ -13,14 +17,14 @@ describe('terminal scrollback', function() before_each(function() clear() - screen = thelpers.screen_setup() + screen = thelpers.screen_setup(nil, nil, 30) end) after_each(function() screen:detach() end) - describe('when the limit is crossed', function() + describe('when the limit is exceeded', function() before_each(function() local lines = {} for i = 1, 30 do @@ -29,26 +33,26 @@ describe('terminal scrollback', function() table.insert(lines, '') feed_data(lines) screen:expect([[ - line26 | - line27 | - line28 | - line29 | - line30 | - {1: } | - {3:-- TERMINAL --} | + line26 | + line27 | + line28 | + line29 | + line30 | + {1: } | + {3:-- TERMINAL --} | ]]) end) it('will delete extra lines at the top', function() feed('<c-\\><c-n>gg') screen:expect([[ - ^line16 | - line17 | - line18 | - line19 | - line20 | - line21 | - | + ^line16 | + line17 | + line18 | + line19 | + line20 | + line21 | + | ]]) end) end) @@ -57,13 +61,13 @@ describe('terminal scrollback', function() before_each(function() feed_data({'line1', 'line2', 'line3', 'line4', ''}) screen:expect([[ - tty ready | - line1 | - line2 | - line3 | - line4 | - {1: } | - {3:-- TERMINAL --} | + tty ready | + line1 | + line2 | + line3 | + line4 | + {1: } | + {3:-- TERMINAL --} | ]]) end) @@ -72,13 +76,13 @@ describe('terminal scrollback', function() it('will hide the top line', function() screen:expect([[ - line1 | - line2 | - line3 | - line4 | - line5 | - {1: } | - {3:-- TERMINAL --} | + line1 | + line2 | + line3 | + line4 | + line5 | + {1: } | + {3:-- TERMINAL --} | ]]) eq(7, curbuf('line_count')) end) @@ -88,46 +92,46 @@ describe('terminal scrollback', function() it('will hide the top 4 lines', function() screen:expect([[ - line3 | - line4 | - line5 | - line6 | - line7 | - line8{1: } | - {3:-- TERMINAL --} | + line3 | + line4 | + line5 | + line6 | + line7 | + line8{1: } | + {3:-- TERMINAL --} | ]]) feed('<c-\\><c-n>6k') screen:expect([[ - ^line2 | - line3 | - line4 | - line5 | - line6 | - line7 | - | + ^line2 | + line3 | + line4 | + line5 | + line6 | + line7 | + | ]]) feed('gg') screen:expect([[ - ^tty ready | - line1 | - line2 | - line3 | - line4 | - line5 | - | + ^tty ready | + line1 | + line2 | + line3 | + line4 | + line5 | + | ]]) feed('G') screen:expect([[ - line3 | - line4 | - line5 | - line6 | - line7 | - ^line8{2: } | - | + line3 | + line4 | + line5 | + line6 | + line7 | + ^line8{2: } | + | ]]) end) end) @@ -138,12 +142,12 @@ describe('terminal scrollback', function() local function will_hide_top_line() screen:try_resize(screen._width, screen._height - 1) screen:expect([[ - line2 | - line3 | - line4 | - rows: 5, cols: 50 | - {1: } | - {3:-- TERMINAL --} | + line2 | + line3 | + line4 | + rows: 5, cols: 30 | + {1: } | + {3:-- TERMINAL --} | ]]) end @@ -157,18 +161,18 @@ describe('terminal scrollback', function() it('will hide the top 3 lines', function() screen:expect([[ - rows: 5, cols: 50 | - rows: 3, cols: 50 | - {1: } | - {3:-- TERMINAL --} | + rows: 5, cols: 30 | + rows: 3, cols: 30 | + {1: } | + {3:-- TERMINAL --} | ]]) eq(8, curbuf('line_count')) feed('<c-\\><c-n>3k') screen:expect([[ - ^line4 | - rows: 5, cols: 50 | - rows: 3, cols: 50 | - | + ^line4 | + rows: 5, cols: 30 | + rows: 3, cols: 30 | + | ]]) end) end) @@ -183,11 +187,11 @@ describe('terminal scrollback', function() local function will_delete_last_two_lines() screen:expect([[ - tty ready | - rows: 4, cols: 50 | - {1: } | - | - {3:-- TERMINAL --} | + tty ready | + rows: 4, cols: 30 | + {1: } | + | + {3:-- TERMINAL --} | ]]) eq(4, curbuf('line_count')) end @@ -202,25 +206,25 @@ describe('terminal scrollback', function() it('will delete the last line and hide the first', function() screen:expect([[ - rows: 4, cols: 50 | - rows: 3, cols: 50 | - {1: } | - {3:-- TERMINAL --} | + rows: 4, cols: 30 | + rows: 3, cols: 30 | + {1: } | + {3:-- TERMINAL --} | ]]) eq(4, curbuf('line_count')) feed('<c-\\><c-n>gg') screen:expect([[ - ^tty ready | - rows: 4, cols: 50 | - rows: 3, cols: 50 | - | + ^tty ready | + rows: 4, cols: 30 | + rows: 3, cols: 30 | + | ]]) feed('a') screen:expect([[ - rows: 4, cols: 50 | - rows: 3, cols: 50 | - {1: } | - {3:-- TERMINAL --} | + rows: 4, cols: 30 | + rows: 3, cols: 30 | + {1: } | + {3:-- TERMINAL --} | ]]) end) end) @@ -231,20 +235,20 @@ describe('terminal scrollback', function() before_each(function() feed_data({'line1', 'line2', 'line3', 'line4', ''}) screen:expect([[ - tty ready | - line1 | - line2 | - line3 | - line4 | - {1: } | - {3:-- TERMINAL --} | + tty ready | + line1 | + line2 | + line3 | + line4 | + {1: } | + {3:-- TERMINAL --} | ]]) screen:try_resize(screen._width, screen._height - 3) screen:expect([[ - line4 | - rows: 3, cols: 50 | - {1: } | - {3:-- TERMINAL --} | + line4 | + rows: 3, cols: 30 | + {1: } | + {3:-- TERMINAL --} | ]]) eq(7, curbuf('line_count')) end) @@ -253,11 +257,11 @@ describe('terminal scrollback', function() local function pop_then_push() screen:try_resize(screen._width, screen._height + 1) screen:expect([[ - line4 | - rows: 3, cols: 50 | - rows: 4, cols: 50 | - {1: } | - {3:-- TERMINAL --} | + line4 | + rows: 3, cols: 30 | + rows: 4, cols: 30 | + {1: } | + {3:-- TERMINAL --} | ]]) end @@ -272,26 +276,26 @@ describe('terminal scrollback', function() local function pop3_then_push1() screen:expect([[ - line2 | - line3 | - line4 | - rows: 3, cols: 50 | - rows: 4, cols: 50 | - rows: 7, cols: 50 | - {1: } | - {3:-- TERMINAL --} | + line2 | + line3 | + line4 | + rows: 3, cols: 30 | + rows: 4, cols: 30 | + rows: 7, cols: 30 | + {1: } | + {3:-- TERMINAL --} | ]]) eq(9, curbuf('line_count')) feed('<c-\\><c-n>gg') screen:expect([[ - ^tty ready | - line1 | - line2 | - line3 | - line4 | - rows: 3, cols: 50 | - rows: 4, cols: 50 | - | + ^tty ready | + line1 | + line2 | + line3 | + line4 | + rows: 3, cols: 30 | + rows: 4, cols: 30 | + | ]]) end @@ -306,18 +310,18 @@ describe('terminal scrollback', function() it('will show all lines and leave a blank one at the end', function() screen:expect([[ - tty ready | - line1 | - line2 | - line3 | - line4 | - rows: 3, cols: 50 | - rows: 4, cols: 50 | - rows: 7, cols: 50 | - rows: 11, cols: 50 | - {1: } | - | - {3:-- TERMINAL --} | + tty ready | + line1 | + line2 | + line3 | + line4 | + rows: 3, cols: 30 | + rows: 4, cols: 30 | + rows: 7, cols: 30 | + rows: 11, cols: 30 | + {1: } | + | + {3:-- TERMINAL --} | ]]) -- since there's an empty line after the cursor, the buffer line -- count equals the terminal screen height @@ -332,30 +336,115 @@ end) describe('terminal prints more lines than the screen height and exits', function() it('will push extra lines to scrollback', function() clear() - local screen = Screen.new(50, 7) + local screen = Screen.new(30, 7) screen:attach({rgb=false}) execute('call termopen(["'..nvim_dir..'/tty-test", "10"]) | startinsert') wait() screen:expect([[ - line6 | - line7 | - line8 | - line9 | - | - [Process exited 0] | - -- TERMINAL -- | + line6 | + line7 | + line8 | + line9 | + | + [Process exited 0] | + -- TERMINAL -- | ]]) feed('<cr>') -- closes the buffer correctly after pressing a key screen:expect([[ - ^ | - ~ | - ~ | - ~ | - ~ | - ~ | - | + ^ | + ~ | + ~ | + ~ | + ~ | + ~ | + | ]]) end) end) +describe("'scrollback' option", function() + before_each(function() + clear() + end) + + local function expect_lines(expected) + local actual = eval("line('$')") + if expected ~= actual then + error('expected: '..expected..', actual: '..tostring(actual)) + end + end + + it('set to 0 behaves as 1', function() + local screen = thelpers.screen_setup(nil, "['sh']", 30) + + curbufmeths.set_option('scrollback', 0) + feed_data('for i in $(seq 1 30); do echo "line$i"; done\n') + screen:expect('line30 ', nil, nil, nil, true) + retry(nil, nil, function() expect_lines(7) end) + + screen:detach() + end) + + it('deletes lines (only) if necessary', function() + local screen = thelpers.screen_setup(nil, "['sh']", 30) + + curbufmeths.set_option('scrollback', 200) + + -- Wait for prompt. + screen:expect('$', nil, nil, nil, true) + + wait() + feed_data('for i in $(seq 1 30); do echo "line$i"; done\n') + + screen:expect('line30 ', nil, nil, nil, true) + + retry(nil, nil, function() expect_lines(33) end) + curbufmeths.set_option('scrollback', 10) + wait() + retry(nil, nil, function() expect_lines(16) end) + curbufmeths.set_option('scrollback', 10000) + eq(16, eval("line('$')")) + -- Terminal job data is received asynchronously, may happen before the + -- 'scrollback' option is synchronized with the internal sb_buffer. + command('sleep 100m') + feed_data('for i in $(seq 1 40); do echo "line$i"; done\n') + + screen:expect('line40 ', nil, nil, nil, true) + + retry(nil, nil, function() expect_lines(58) end) + -- Verify off-screen state + eq('line35', eval("getline(line('w0') - 1)")) + eq('line26', eval("getline(line('w0') - 10)")) + + screen:detach() + end) + + it('defaults to 1000', function() + execute('terminal') + eq(1000, curbufmeths.get_option('scrollback')) + end) + + it('error if set to invalid values', function() + local status, rv = pcall(command, 'set scrollback=-2') + eq(false, status) -- assert failure + eq('E474:', string.match(rv, "E%d*:")) + + status, rv = pcall(command, 'set scrollback=100001') + eq(false, status) -- assert failure + eq('E474:', string.match(rv, "E%d*:")) + end) + + it('defaults to -1 on normal buffers', function() + execute('new') + eq(-1, curbufmeths.get_option('scrollback')) + end) + + it('error if set on a normal buffer', function() + command('new') + execute('set scrollback=42') + feed('<CR>') + eq('E474:', string.match(eval("v:errmsg"), "E%d*:")) + end) + +end) diff --git a/test/functional/terminal/window_split_tab_spec.lua b/test/functional/terminal/window_split_tab_spec.lua index 6951b84a69..d3386a641e 100644 --- a/test/functional/terminal/window_split_tab_spec.lua +++ b/test/functional/terminal/window_split_tab_spec.lua @@ -28,16 +28,16 @@ describe('terminal', function() feed('<c-\\><c-n>') execute('2split') screen:expect([[ - tty ready | - ^rows: 2, cols: 50 | + rows: 2, cols: 50 | + {2:^ } | ========== | - tty ready | rows: 2, cols: 50 | {2: } | {4:~ }| {4:~ }| + {4:~ }| ========== | - | + :2split | ]]) execute('wincmd p') screen:expect([[ @@ -54,14 +54,14 @@ describe('terminal', function() ]]) execute('wincmd p') screen:expect([[ - rows: 5, cols: 50 | - ^rows: 2, cols: 50 | + rows: 2, cols: 50 | + {2:^ } | ========== | - rows: 5, cols: 50 | rows: 2, cols: 50 | {2: } | {4:~ }| {4:~ }| + {4:~ }| ========== | :wincmd p | ]]) diff --git a/test/helpers.lua b/test/helpers.lua index 3f7a9c2b74..25ab80bb50 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -17,6 +17,34 @@ local ok = function(res) return assert.is_true(res) end +local function glob(initial_path, re, exc_re) + local paths_to_check = {initial_path} + local ret = {} + local checked_files = {} + while #paths_to_check > 0 do + local cur_path = paths_to_check[#paths_to_check] + paths_to_check[#paths_to_check] = nil + for e in lfs.dir(cur_path) do + local full_path = cur_path .. '/' .. e + local checked_path = full_path:sub(#initial_path + 1) + if ((not exc_re or not checked_path:match(exc_re)) + and e:sub(1, 1) ~= '.') then + local attrs = lfs.attributes(full_path) + local check_key = attrs.dev .. ':' .. tostring(attrs.ino) + if not checked_files[check_key] then + checked_files[check_key] = true + if attrs.mode == 'directory' then + paths_to_check[#paths_to_check + 1] = full_path + elseif not re or checked_path:match(re) then + ret[#ret + 1] = full_path + end + end + end + end + end + return ret +end + local function check_logs() local log_dir = os.getenv('LOG_DIR') local runtime_errors = 0 @@ -109,6 +137,81 @@ local function filter(filter_func, tab) return rettab end +local function hasenv(name) + local env = os.getenv(name) + if env and env ~= '' then + return env + end + return nil +end + +local tests_skipped = 0 + +local function check_cores(app) + app = app or 'build/bin/nvim' + local initial_path, re, exc_re + local gdb_db_cmd = 'gdb -n -batch -ex "thread apply all bt full" "$_NVIM_TEST_APP" -c "$_NVIM_TEST_CORE"' + local lldb_db_cmd = 'lldb -Q -o "bt all" -f "$_NVIM_TEST_APP" -c "$_NVIM_TEST_CORE"' + local random_skip = false + local db_cmd + if hasenv('NVIM_TEST_CORE_GLOB_DIRECTORY') then + initial_path = os.getenv('NVIM_TEST_CORE_GLOB_DIRECTORY') + re = os.getenv('NVIM_TEST_CORE_GLOB_RE') + exc_re = os.getenv('NVIM_TEST_CORE_EXC_RE') + db_cmd = os.getenv('NVIM_TEST_CORE_DB_CMD') or gdb_db_cmd + random_skip = os.getenv('NVIM_TEST_CORE_RANDOM_SKIP') + elseif os.getenv('TRAVIS_OS_NAME') == 'osx' then + initial_path = '/cores' + re = nil + exc_re = nil + db_cmd = lldb_db_cmd + else + initial_path = '.' + re = '/core[^/]*$' + exc_re = '^/%.deps$' + db_cmd = gdb_db_cmd + random_skip = true + end + -- Finding cores takes too much time on linux + if random_skip and math.random() < 0.9 then + tests_skipped = tests_skipped + 1 + return + end + local cores = glob(initial_path, re, exc_re) + local found_cores = 0 + local out = io.stdout + for _, core in ipairs(cores) do + local len = 80 - #core - #('Core file ') - 2 + local esigns = ('='):rep(len / 2) + out:write(('\n%s Core file %s %s\n'):format(esigns, core, esigns)) + out:flush() + local pipe = io.popen( + db_cmd:gsub('%$_NVIM_TEST_APP', app):gsub('%$_NVIM_TEST_CORE', core) + .. ' 2>&1', 'r') + if pipe then + local bt = pipe:read('*a') + if bt then + out:write(bt) + out:write('\n') + else + out:write('Failed to read from the pipe\n') + end + else + out:write('Failed to create pipe\n') + end + out:flush() + found_cores = found_cores + 1 + os.remove(core) + end + if found_cores ~= 0 then + out:write(('\nTests covered by this check: %u\n'):format(tests_skipped + 1)) + end + tests_skipped = 0 + if found_cores > 0 then + error("crash detected (see above)") + end +end + return { eq = eq, neq = neq, @@ -118,4 +221,7 @@ return { tmpname = tmpname, map = map, filter = filter, + glob = glob, + check_cores = check_cores, + hasenv = hasenv, } diff --git a/test/unit/preprocess.lua b/test/unit/preprocess.lua index 1c9b290462..4d819e5f23 100644 --- a/test/unit/preprocess.lua +++ b/test/unit/preprocess.lua @@ -185,6 +185,30 @@ local function repeated_call(...) return nil end +function Gcc:filter_standard_defines(defines) + if not self.standard_defines then + local pseudoheader_fname = 'tmp_empty_pseudoheader.h' + local pseudoheader_file = io.open(pseudoheader_fname, 'w') + pseudoheader_file:close() + local standard_defines = repeated_call(self.path, + self.preprocessor_extra_flags, + self.get_defines_extra_flags, + {pseudoheader_fname}) + os.remove(pseudoheader_fname) + self.standard_defines = {} + for line in standard_defines:gmatch('[^\n]+') do + self.standard_defines[line] = true + end + end + local ret = {} + for line in defines:gmatch('[^\n]+') do + if not self.standard_defines[line] then + ret[#ret + 1] = line + end + end + return table.concat(ret, "\n") +end + -- returns a stream representing a preprocessed form of the passed-in headers. -- Don't forget to close the stream by calling the close() method on it. function Gcc:preprocess(previous_defines, ...) @@ -201,6 +225,7 @@ function Gcc:preprocess(previous_defines, ...) local defines = repeated_call(self.path, self.preprocessor_extra_flags, self.get_defines_extra_flags, {pseudoheader_fname}) + defines = self:filter_standard_defines(defines) -- lfs = require("lfs") -- print("CWD: #{lfs.currentdir!}") diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index f0bc41fef6..1663843663 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -120,9 +120,19 @@ set(LUV_SHA256 86a199403856018cd8e5529c8527450c83664a3d36f52d5253cbe909ea6c5a06) set(GPERF_URL http://ftp.gnu.org/pub/gnu/gperf/gperf-3.0.4.tar.gz) set(GPERF_SHA256 767112a204407e62dbc3106647cf839ed544f3cf5d0f0523aaa2508623aad63e) +# 7za.exe cat.exe curl.exe ca-bundle.crt diff.exe tee.exe tidy.exe xxd.exe +set(WINTOOLS_URL https://github.com/neovim/deps/raw/5d23093c66d63a8777244ed84de727c26d3f7b79/opt/win32tools.zip) +set(WINTOOLS_SHA256 40c7d1fbed47d8b1cf3b3cada6bfe0e0df06d99beb48775b4db27972d3ceafc1) + +set(WINGUI_URL https://github.com/equalsraf/neovim-qt/releases/download/v0.2.4/neovim-qt.zip) +set(WINGUI_SHA256 95bbc852b69b12d0ef962a8410522010b453ba70f36ea379c548558d16abc2e6) + set(WIN32YANK_URL https://github.com/equalsraf/win32yank/releases/download/v0.0.2/win32yank.zip) set(WIN32YANK_SHA256 78869bf68565607cda1b6a3d549e2487d59d6f0f16f9b003e123c0086f90309d) +set(WINPTY_URL https://github.com/rprichard/winpty/releases/download/0.4.2/winpty-0.4.2-msvc2015.zip) +set(WINPTY_SHA256 b465f2584ff394b3fe27c01aa1dcfc679583c1ee951d0e83de3f859d8b8305b8) + if(USE_BUNDLED_UNIBILIUM) include(BuildUnibilium) endif() @@ -170,8 +180,33 @@ endif() include(GetBinaryDeps) if(WIN32) + GetBinaryDep(TARGET wintools + INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory . ${DEPS_INSTALL_DIR}/bin) + GetBinaryDep(TARGET win32yank INSTALL_COMMAND ${CMAKE_COMMAND} -E copy win32yank.exe ${DEPS_INSTALL_DIR}/bin) + + GetBinaryDep(TARGET wingui + INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory bin ${DEPS_INSTALL_DIR}/bin + COMMAND ${CMAKE_COMMAND} -E copy_directory share/nvim-qt/runtime ${DEPS_INSTALL_DIR}/share/nvim/runtime) + + include(TargetArch) + if("${TARGET_ARCH}" STREQUAL "X86_64") + set(TARGET_ARCH x64) + elseif(TARGET_ARCH STREQUAL "X86") + set(TARGET_ARCH ia32) + endif() + GetBinaryDep(TARGET winpty + INSTALL_COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/bin + COMMAND ${CMAKE_COMMAND} -DFROM_GLOB=${DEPS_BUILD_DIR}/src/winpty/${TARGET_ARCH}/bin/* + -DTO=${DEPS_INSTALL_DIR}/bin/ + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CopyFilesGlob.cmake + COMMAND ${CMAKE_COMMAND} -DFROM_GLOB=${DEPS_BUILD_DIR}/src/winpty/include/* + -DTO=${DEPS_INSTALL_DIR}/include/ + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CopyFilesGlob.cmake + COMMAND ${CMAKE_COMMAND} -DFROM_GLOB=${DEPS_BUILD_DIR}/src/winpty/${TARGET_ARCH}/lib/* + -DTO=${DEPS_INSTALL_DIR}/lib/ + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CopyFilesGlob.cmake) endif() add_custom_target(clean-shared-libraries diff --git a/third-party/cmake/BuildLibuv.cmake b/third-party/cmake/BuildLibuv.cmake index 5482f28557..06b527e5a2 100644 --- a/third-party/cmake/BuildLibuv.cmake +++ b/third-party/cmake/BuildLibuv.cmake @@ -75,16 +75,16 @@ elseif(WIN32 AND MSVC) message(FATAL_ERROR "Python2 is required to build libuv on windows, use -DPYTHON_EXECUTABLE to set a python interpreter") endif() - string(FIND ${CMAKE_GENERATOR} Win64 VS_WIN64) - if(VS_WIN64 EQUAL -1) - set(VS_ARCH x86) - else() - set(VS_ARCH x64) + include(TargetArch) + if("${TARGET_ARCH}" STREQUAL "X86_64") + set(TARGET_ARCH x64) + elseif(TARGET_ARCH STREQUAL "X86") + set(TARGET_ARCH x86) endif() string(TOLOWER ${CMAKE_BUILD_TYPE} LOWERCASE_BUILD_TYPE) set(UV_OUTPUT_DIR ${DEPS_BUILD_DIR}/src/libuv/${CMAKE_BUILD_TYPE}) BuildLibUv( - BUILD_COMMAND set PYTHON=${PYTHON_EXECUTABLE} COMMAND ${DEPS_BUILD_DIR}/src/libuv/vcbuild.bat shared ${LOWERCASE_BUILD_TYPE} ${VS_ARCH} + BUILD_COMMAND set PYTHON=${PYTHON_EXECUTABLE} COMMAND ${DEPS_BUILD_DIR}/src/libuv/vcbuild.bat shared ${LOWERCASE_BUILD_TYPE} ${TARGET_ARCH} INSTALL_COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/lib COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/bin COMMAND ${CMAKE_COMMAND} -E copy ${UV_OUTPUT_DIR}/libuv.lib ${DEPS_INSTALL_DIR}/lib diff --git a/third-party/cmake/GetBinaryDeps.cmake b/third-party/cmake/GetBinaryDeps.cmake index cdc8ac051e..1d98177924 100644 --- a/third-party/cmake/GetBinaryDeps.cmake +++ b/third-party/cmake/GetBinaryDeps.cmake @@ -21,7 +21,7 @@ function(GetBinaryDep) set(URL ${${URL_VARNAME}}) set(HASH ${${HASH_VARNAME}}) if(NOT URL OR NOT HASH ) - message(FATAL_ERROR "${URL_VARNAME} and ${HASH_VARNAME} must be set") + message(FATAL_ERROR "${URL_VARNAME} and ${HASH_VARNAME} must be set") endif() ExternalProject_Add(${_gettool_TARGET} @@ -41,6 +41,6 @@ function(GetBinaryDep) CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/bin - COMMAND "${_gettool_INSTALL_COMMAND}") + COMMAND "${_gettool_INSTALL_COMMAND}") list(APPEND THIRD_PARTY_DEPS ${__gettool_TARGET}) endfunction() diff --git a/third-party/cmake/TargetArch.cmake b/third-party/cmake/TargetArch.cmake new file mode 100644 index 0000000000..71ea44ec59 --- /dev/null +++ b/third-party/cmake/TargetArch.cmake @@ -0,0 +1,23 @@ +# Sets TARGET_ARCH to a normalized name (X86 or X86_64). +# See https://github.com/axr/solar-cmake/blob/master/TargetArch.cmake +include(CheckSymbolExists) + +# X86 +check_symbol_exists("_M_IX86" "" T_M_IX86) +check_symbol_exists("__i386__" "" T_I386) +if(T_M_IX86 OR T_I386) +set(TARGET_ARCH "X86") + return() +endif() + +# X86_64 +check_symbol_exists("_M_AMD64" "" T_M_AMD64) +check_symbol_exists("__x86_64__" "" T_X86_64) +check_symbol_exists("__amd64__" "" T_AMD64) + +if(T_M_AMD64 OR T_X86_64 OR T_AMD64) +set(TARGET_ARCH "X86_64") + return() +endif() + +message(FATAL_ERROR "Unknown target architecture") |