diff options
249 files changed, 9800 insertions, 5021 deletions
diff --git a/.gitignore b/.gitignore index ddbcc9c60d..85b371b926 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Tools +.ropeproject/ + # Build/deps dir /build/ /cmake-build-debug/ @@ -17,13 +20,13 @@ tags /src/nvim/po/vim.pot /src/nvim/po/*.ck -# generated by tests with $NVIM_LOG_FILE set. +# Generated by tests with $NVIM_LOG_FILE set. /.nvimlog -# Files generated by scripts/vim-patch.sh +# Generated by scripts/vim-patch.sh /.vim-src/ -# Files generated by the tests +# Generated by old (Vim) tests. /src/nvim/testdir/del /src/nvim/testdir/test*.out /src/nvim/testdir/test*.res @@ -36,10 +39,10 @@ tags /src/nvim/testdir/valgrind.* /src/nvim/testdir/.gdbinit -# Folder generated by the unit tests +# Generated by unit tests. /test/includes/post/ -# generated by luacheck during `make testlint' +# Generated by luacheck during `make testlint' /test/.luacheckcache # local make targets diff --git a/.travis.yml b/.travis.yml index 451b89888d..b275a5262d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,6 +41,10 @@ env: # default target name for functional tests - FUNCTIONALTEST=functionaltest - CI_TARGET=tests + # Environment variables for ccache + - CCACHE_COMPRESS=1 + - CCACHE_SLOPPINESS=time_macros,file_macro + - CCACHE_BASEDIR="$TRAVIS_BUILD_DIR" jobs: include: @@ -60,7 +64,7 @@ jobs: - os: linux # Travis creates a cache per compiler. Set a different value here to # store 32-bit dependencies in a separate cache. - compiler: gcc -m32 + compiler: gcc env: BUILD_32BIT=ON - os: osx compiler: clang @@ -87,7 +91,6 @@ install: ci/install.sh before_script: ci/before_script.sh script: ci/script.sh before_cache: ci/before_cache.sh -after_success: ci/after_success.sh addons: apt: @@ -118,9 +121,11 @@ branches: cache: apt: true + ccache: true directories: - "$HOME/.cache/pip" - "$HOME/.cache/nvim-deps" + - "$HOME/.cache/nvim-deps-downloads" notifications: webhooks: diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b021ad6e7..47e873a845 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,8 +67,8 @@ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY # If not in a git repo (e.g., a tarball) these tokens define the complete # version string, else they are combined with the result of `git describe`. set(NVIM_VERSION_MAJOR 0) -set(NVIM_VERSION_MINOR 2) -set(NVIM_VERSION_PATCH 3) +set(NVIM_VERSION_MINOR 3) +set(NVIM_VERSION_PATCH 0) set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers # API level @@ -11,7 +11,7 @@ [](https://codecov.io/gh/neovim/neovim) [](https://scan.coverity.com/projects/2227) [](https://neovim.io/doc/reports/clang) -[](https://neovim.io/doc/reports/pvs) +[](https://neovim.io/doc/reports/pvs/PVS-studio.html.d) [](https://buildd.debian.org/neovim) [](https://github.com/neovim/neovim/releases/) @@ -34,11 +34,15 @@ Install from source make CMAKE_BUILD_TYPE=RelWithDebInfo sudo make install -To install to a non-default location, specify `CMAKE_INSTALL_PREFIX`: +To install to a non-default location, set `CMAKE_INSTALL_PREFIX`: make CMAKE_EXTRA_FLAGS="-DCMAKE_INSTALL_PREFIX=/full/path/" make install +To list all targets: + + cmake --build build --target help + See [the wiki](https://github.com/neovim/neovim/wiki/Building-Neovim) for details. Install from package diff --git a/appveyor.yml b/appveyor.yml index 957efc0e47..75ca74d0d5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,14 +3,13 @@ environment: APPVEYOR_CACHE_ENTRY_ZIP_ARGS: "-t7z -m0=lzma -mx=9" image: Visual Studio 2017 configuration: +- MSVC_64 +- MSVC_32 - MINGW_64 - MINGW_32 -- MSVC_64 -# - MSVC_32 - MINGW_64-gcov matrix: allow_failures: - - configuration: MSVC_64 - configuration: MINGW_64-gcov install: [] before_build: diff --git a/ci/before_cache.sh b/ci/before_cache.sh index 2340f4db74..c8a1fd5b71 100755 --- a/ci/before_cache.sh +++ b/ci/before_cache.sh @@ -11,10 +11,20 @@ source "${CI_DIR}/common/suite.sh" rm -rf "${HOME}/.cache/pip/log" rm -f "${HOME}/.cache/pip/selfcheck.json" +echo "before_cache.sh: cache size" +du -d 2 "${HOME}/.cache" | sort -n + +echo "before_cache.sh: ccache stats" +ccache -s 2>/dev/null || true + # Update the third-party dependency cache only if the build was successful. if ended_successfully; then rm -rf "${HOME}/.cache/nvim-deps" mv "${DEPS_BUILD_DIR}" "${HOME}/.cache/nvim-deps" + + rm -rf "${HOME}/.cache/nvim-deps-downloads" + mv "${DEPS_DOWNLOAD_DIR}" "${HOME}/.cache/nvim-deps-downloads" + touch "${CACHE_MARKER}" echo "Updated third-party dependencies (timestamp: $(_stat "${CACHE_MARKER}"))." fi diff --git a/ci/before_script.sh b/ci/before_script.sh index 445996a8df..49b4e068b5 100755 --- a/ci/before_script.sh +++ b/ci/before_script.sh @@ -20,6 +20,9 @@ if [[ -n "${LLVM_SYMBOLIZER}" ]] && [[ ! $(type -P "${LLVM_SYMBOLIZER}") ]]; the exit 1 fi +# Show ccache stats so we can compare in before_cache +ccache -s 2>/dev/null || true + if [[ "${TRAVIS_OS_NAME}" == osx ]]; then # Adds user to a dummy group. # That allows to test changing the group of the file by `os_fchown`. diff --git a/ci/build.ps1 b/ci/build.ps1 index 7075775bcd..8eb237ccd1 100644 --- a/ci/build.ps1 +++ b/ci/build.ps1 @@ -11,7 +11,11 @@ $depsCmakeVars = @{ $nvimCmakeVars = @{ CMAKE_BUILD_TYPE = $cmakeBuildType; BUSTED_OUTPUT_TYPE = 'nvim'; - GPERF_PRG = 'C:\msys64\usr\bin\gperf.exe'; +} + +# For pull requests, skip some build configurations to save time. +if ($env:APPVEYOR_PULL_REQUEST_HEAD_COMMIT -and $env:CONFIGURATION -match '^(MSVC_64|MINGW_32)$') { + exit 0 } function exitIfFailed() { @@ -39,12 +43,10 @@ if ($compiler -eq 'MINGW') { # Add MinGW to the PATH $env:PATH = "C:\msys64\mingw$bits\bin;$env:PATH" - # Remove the Git sh.exe from the PATH - $env:PATH = $env:PATH.Replace('C:\Program Files\Git\usr\bin', '') # Build third-party dependencies C:\msys64\usr\bin\bash -lc "pacman --verbose --noconfirm -Su" ; exitIfFailed - C:\msys64\usr\bin\bash -lc "pacman --verbose --noconfirm --needed -S mingw-w64-$arch-cmake mingw-w64-$arch-perl mingw-w64-$arch-diffutils mingw-w64-$arch-unibilium gperf" ; exitIfFailed + C:\msys64\usr\bin\bash -lc "pacman --verbose --noconfirm --needed -S mingw-w64-$arch-cmake mingw-w64-$arch-perl mingw-w64-$arch-diffutils mingw-w64-$arch-unibilium" ; exitIfFailed } elseif ($compiler -eq 'MSVC') { $cmakeGeneratorArgs = '/verbosity:normal' @@ -56,6 +58,9 @@ elseif ($compiler -eq 'MSVC') { } } +# Remove Git Unix utilities from the PATH +$env:PATH = $env:PATH.Replace('C:\Program Files\Git\usr\bin', '') + # Setup python (use AppVeyor system python) C:\Python27\python.exe -m pip install neovim ; exitIfFailed C:\Python35\python.exe -m pip install neovim ; exitIfFailed @@ -91,14 +96,25 @@ cmake --build . --config $cmakeBuildType -- $cmakeGeneratorArgs ; exitIfFailed bin\nvim --version ; exitIfFailed # Functional tests -cmake --build . --config $cmakeBuildType --target functionaltest -- $cmakeGeneratorArgs ; exitIfFailed +# The $LastExitCode from MSBuild can't be trusted +$failed = $false +# Temporarily turn off tracing to reduce log file output +Set-PSDebug -Off +cmake --build . --config $cmakeBuildType --target functionaltest -- $cmakeGeneratorArgs 2>&1 | + foreach { $failed = $failed -or + $_ -match 'Running functional tests failed with error'; $_ } +Set-PSDebug -Trace 1 +if ($failed) { + exit $LastExitCode +} + if ($uploadToCodecov) { C:\msys64\usr\bin\bash -lc "cd /c/projects/neovim; bash <(curl -s https://codecov.io/bash) -c -F functionaltest || echo 'codecov upload failed.'" } # Old tests -$env:PATH += ';C:\msys64\usr\bin' +$env:PATH = "C:\msys64\usr\bin;$env:PATH" & "C:\msys64\mingw$bits\bin\mingw32-make.exe" -C $(Convert-Path ..\src\nvim\testdir) VERBOSE=1 if ($uploadToCodecov) { diff --git a/ci/common/build.sh b/ci/common/build.sh index 5b719940ab..a3cf64d47a 100644 --- a/ci/common/build.sh +++ b/ci/common/build.sh @@ -26,13 +26,16 @@ build_deps() { DEPS_CMAKE_FLAGS="${DEPS_CMAKE_FLAGS} -DUSE_BUNDLED_LUA=ON" fi - rm -rf "${DEPS_BUILD_DIR}" mkdir -p "${DEPS_BUILD_DIR}" + mkdir -p "${DEPS_DOWNLOAD_DIR}" # Use cached dependencies if $CACHE_MARKER exists. - if test -f "${CACHE_MARKER}" && ! test "${CACHE_ENABLE}" = "false" ; then + if test "${CACHE_ENABLE}" = "false" ; then + export CCACHE_RECACHE=1 + elif test -f "${CACHE_MARKER}" ; then echo "Using third-party dependencies from Travis cache (last update: $(_stat "${CACHE_MARKER}"))." - cp -r "${HOME}/.cache/nvim-deps" "${DEPS_BUILD_DIR}" + cp -r "${HOME}/.cache/nvim-deps"/. "${DEPS_BUILD_DIR}" + cp -r "${HOME}/.cache/nvim-deps-downloads" "${DEPS_DOWNLOAD_DIR}" fi # Even if we're using cached dependencies, run CMake and make to diff --git a/ci/install.sh b/ci/install.sh index 053549d6db..50f3490b63 100755 --- a/ci/install.sh +++ b/ci/install.sh @@ -11,6 +11,8 @@ if [[ "${TRAVIS_OS_NAME}" == osx ]]; then brew install ninja brew install gettext brew reinstall -s libtool + brew install ccache + export PATH="/usr/local/opt/ccache/libexec:$PATH" fi echo "Install neovim module for Python 3." diff --git a/cmake/FindLibIntl.cmake b/cmake/FindLibIntl.cmake index ab4632cf45..f8442566a9 100644 --- a/cmake/FindLibIntl.cmake +++ b/cmake/FindLibIntl.cmake @@ -27,7 +27,7 @@ find_path(LibIntl_INCLUDE_DIR ) find_library(LibIntl_LIBRARY - NAMES intl libintl.a + NAMES intl libintl ) if (LibIntl_INCLUDE_DIR) diff --git a/config/pathdef.c.in b/config/pathdef.c.in index 4579fbe455..41950f5ac5 100644 --- a/config/pathdef.c.in +++ b/config/pathdef.c.in @@ -1,3 +1,5 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include "${PROJECT_SOURCE_DIR}/src/nvim/vim.h" char *default_vim_dir = "${CMAKE_INSTALL_FULL_DATAROOTDIR}/nvim"; char *default_vimruntime_dir = ""; diff --git a/contrib/local.mk.example b/contrib/local.mk.example index 23fe11622b..c347eb9e0d 100644 --- a/contrib/local.mk.example +++ b/contrib/local.mk.example @@ -36,7 +36,6 @@ # them. # # DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_BUSTED=OFF -# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_DEPS=OFF # DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_JEMALLOC=OFF # DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_LIBTERMKEY=OFF # DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_LIBUV=OFF @@ -45,6 +44,10 @@ # DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_LUAROCKS=OFF # DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_MSGPACK=OFF # DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_UNIBILIUM=OFF +# +# Or disable all bundled dependencies at once. +# +# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED=OFF # By default, bundled libraries are statically linked to nvim. # This has no effect for non-bundled deps, which are always dynamically linked. diff --git a/man/nvim.1 b/man/nvim.1 index d2a3ea5c43..12f342247e 100644 --- a/man/nvim.1 +++ b/man/nvim.1 @@ -25,7 +25,7 @@ To enter commands in type a colon .Pq Sq \&: which is also used in this manual to denote commands. -For more information, consult the on-line help system with the +For more information, consult the online help system with the .Ic :help command. .Bl -tag -width Fl @@ -329,6 +329,8 @@ Implies .Fl -headless . .It Fl -headless Do not start a user interface. +.It Fl -listen Ar address +Start RPC server on this pipe or TCP socket. .It Fl h , -help Print usage information and exit. .It Fl v , -version @@ -337,12 +339,12 @@ Print version information and exit. .Sh ENVIRONMENT .Bl -tag -width Fl .It Ev VIM -Used to locate various user files, such as the user's init.vim. +Used to locate various user files, such as init.vim. .It Ev VIMRUNTIME -Used to locate run time files, such as on-line documentation and +Used to locate runtime files, such as online documentation and syntax highlighting definitions. .It Ev XDG_CONFIG_HOME -Path to use for the user-local configuration directory, see +Path to the user-local configuration directory, see .Sx FILES . Defaults to .Pa ~/.config @@ -356,7 +358,7 @@ Defaults to .Pa ~/.local/share if not set. .It Ev VIMINIT -A string of Ex commands to be executed at startup. +Ex commands to be executed at startup. For example, the command to quit is .Ic :q , so to have @@ -375,41 +377,32 @@ command. .Sh FILES .Bl -tag -width "~/.config/nvim/init.vim" .It Pa ~/.config/nvim/init.vim -The user-local +User-local .Nm configuration file. -See -.Ev XDG_CONFIG_HOME -above. .It Pa ~/.config/nvim -The user-local +User-local .Nm configuration directory. -See -.Ev XDG_CONFIG_HOME -above. +See also +.Ev XDG_CONFIG_HOME . .It Pa $VIM/sysinit.vim -The system-global +System-global .Nm configuration file. .It Pa /usr/local/share/nvim -The system-global +System-global .Nm runtime directory. .El .Sh AUTHORS -.Nm -was started by -.An Thiago de Arruda , -with a lot of help from others. -.Pp +Nvim was started by +.An Thiago de Arruda . Most of Vim was written by .An -nosplit -.An Bram Moolenaar , -with a lot of help from others. +.An Bram Moolenaar . See .Ic :help credits . -.Pp Vim is based on Stevie, worked on by .An Tim Thompson , .An Tony Andrews , diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index b3907fdf37..0ae7c231af 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -42,6 +42,7 @@ foreach(PACKAGE ${PACKAGES}) nvim WORKING_DIRECTORY "${GENERATED_PACKAGE_DIR}/${PACKNAME}" ) + add_dependencies(${PACKNAME}-tags nvim_runtime_deps) add_custom_command(OUTPUT "${GENERATED_PACKAGE_DIR}/${PACKNAME}/doc/tags" DEPENDS @@ -80,6 +81,7 @@ add_custom_target(helptags nvim WORKING_DIRECTORY "${GENERATED_RUNTIME_DIR}" ) +add_dependencies(helptags nvim_runtime_deps) add_custom_command(OUTPUT ${GENERATED_HELP_TAGS} DEPENDS diff --git a/runtime/autoload/dist/ft.vim b/runtime/autoload/dist/ft.vim new file mode 100644 index 0000000000..2603c6822f --- /dev/null +++ b/runtime/autoload/dist/ft.vim @@ -0,0 +1,741 @@ +" Vim functions for file type detection +" +" Maintainer: Bram Moolenaar <Bram@vim.org> +" Last Change: 2017 Nov 11 + +" These functions are moved here from runtime/filetype.vim to make startup +" faster. + +" Line continuation is used here, remove 'C' from 'cpoptions' +let s:cpo_save = &cpo +set cpo&vim + +func dist#ft#Check_inp() + if getline(1) =~ '^\*' + setf abaqus + else + let n = 1 + if line("$") > 500 + let nmax = 500 + else + let nmax = line("$") + endif + while n <= nmax + if getline(n) =~? "^header surface data" + setf trasys + break + endif + let n = n + 1 + endwhile + endif +endfunc + +" This function checks for the kind of assembly that is wanted by the user, or +" can be detected from the first five lines of the file. +func dist#ft#FTasm() + " make sure b:asmsyntax exists + if !exists("b:asmsyntax") + let b:asmsyntax = "" + endif + + if b:asmsyntax == "" + call dist#ft#FTasmsyntax() + endif + + " if b:asmsyntax still isn't set, default to asmsyntax or GNU + if b:asmsyntax == "" + if exists("g:asmsyntax") + let b:asmsyntax = g:asmsyntax + else + let b:asmsyntax = "asm" + endif + endif + + exe "setf " . fnameescape(b:asmsyntax) +endfunc + +func dist#ft#FTasmsyntax() + " see if file contains any asmsyntax=foo overrides. If so, change + " b:asmsyntax appropriately + let head = " ".getline(1)." ".getline(2)." ".getline(3)." ".getline(4). + \" ".getline(5)." " + let match = matchstr(head, '\sasmsyntax=\zs[a-zA-Z0-9]\+\ze\s') + if match != '' + let b:asmsyntax = match + elseif ((head =~? '\.title') || (head =~? '\.ident') || (head =~? '\.macro') || (head =~? '\.subtitle') || (head =~? '\.library')) + let b:asmsyntax = "vmasm" + endif +endfunc + +" Check if one of the first five lines contains "VB_Name". In that case it is +" probably a Visual Basic file. Otherwise it's assumed to be "alt" filetype. +func dist#ft#FTVB(alt) + if getline(1).getline(2).getline(3).getline(4).getline(5) =~? 'VB_Name\|Begin VB\.\(Form\|MDIForm\|UserControl\)' + setf vb + else + exe "setf " . a:alt + endif +endfunc + +func dist#ft#FTbtm() + if exists("g:dosbatch_syntax_for_btm") && g:dosbatch_syntax_for_btm + setf dosbatch + else + setf btm + endif +endfunc + +func dist#ft#BindzoneCheck(default) + if getline(1).getline(2).getline(3).getline(4) =~ '^; <<>> DiG [0-9.]\+.* <<>>\|$ORIGIN\|$TTL\|IN\s\+SOA' + setf bindzone + elseif a:default != '' + exe 'setf ' . a:default + endif +endfunc + +func dist#ft#FTlpc() + if exists("g:lpc_syntax_for_c") + let lnum = 1 + while lnum <= 12 + if getline(lnum) =~# '^\(//\|inherit\|private\|protected\|nosave\|string\|object\|mapping\|mixed\)' + setf lpc + return + endif + let lnum = lnum + 1 + endwhile + endif + setf c +endfunc + +func dist#ft#FTheader() + if match(getline(1, min([line("$"), 200])), '^@\(interface\|end\|class\)') > -1 + if exists("g:c_syntax_for_h") + setf objc + else + setf objcpp + endif + elseif exists("g:c_syntax_for_h") + setf c + elseif exists("g:ch_syntax_for_h") + setf ch + else + setf cpp + endif +endfunc + +" This function checks if one of the first ten lines start with a '@'. In +" that case it is probably a change file. +" If the first line starts with # or ! it's probably a ch file. +" If a line has "main", "include", "//" ir "/*" it's probably ch. +" Otherwise CHILL is assumed. +func dist#ft#FTchange() + let lnum = 1 + while lnum <= 10 + if getline(lnum)[0] == '@' + setf change + return + endif + if lnum == 1 && (getline(1)[0] == '#' || getline(1)[0] == '!') + setf ch + return + endif + if getline(lnum) =~ "MODULE" + setf chill + return + endif + if getline(lnum) =~ 'main\s*(\|#\s*include\|//' + setf ch + return + endif + let lnum = lnum + 1 + endwhile + setf chill +endfunc + +func dist#ft#FTent() + " This function checks for valid cl syntax in the first five lines. + " Look for either an opening comment, '#', or a block start, '{". + " If not found, assume SGML. + let lnum = 1 + while lnum < 6 + let line = getline(lnum) + if line =~ '^\s*[#{]' + setf cl + return + elseif line !~ '^\s*$' + " Not a blank line, not a comment, and not a block start, + " so doesn't look like valid cl code. + break + endif + let lnum = lnum + 1 + endw + setf dtd +endfunc + +func dist#ft#EuphoriaCheck() + if exists('g:filetype_euphoria') + exe 'setf ' . g:filetype_euphoria + else + setf euphoria3 + endif +endfunc + +func dist#ft#DtraceCheck() + let lines = getline(1, min([line("$"), 100])) + if match(lines, '^module\>\|^import\>') > -1 + " D files often start with a module and/or import statement. + setf d + elseif match(lines, '^#!\S\+dtrace\|#pragma\s\+D\s\+option\|:\S\{-}:\S\{-}:') > -1 + setf dtrace + else + setf d + endif +endfunc + +func dist#ft#FTe() + if exists('g:filetype_euphoria') + exe 'setf ' . g:filetype_euphoria + else + let n = 1 + while n < 100 && n < line("$") + if getline(n) =~ "^\\s*\\(<'\\|'>\\)\\s*$" + setf specman + return + endif + let n = n + 1 + endwhile + setf eiffel + endif +endfunc + +" Distinguish between HTML, XHTML and Django +func dist#ft#FThtml() + let n = 1 + while n < 10 && n < line("$") + if getline(n) =~ '\<DTD\s\+XHTML\s' + setf xhtml + return + endif + if getline(n) =~ '{%\s*\(extends\|block\|load\)\>\|{#\s\+' + setf htmldjango + return + endif + let n = n + 1 + endwhile + setf html +endfunc + +" Distinguish between standard IDL and MS-IDL +func dist#ft#FTidl() + let n = 1 + while n < 50 && n < line("$") + if getline(n) =~ '^\s*import\s\+"\(unknwn\|objidl\)\.idl"' + setf msidl + return + endif + let n = n + 1 + endwhile + setf idl +endfunc + +" Distinguish between "default" and Cproto prototype file. */ +func dist#ft#ProtoCheck(default) + " Cproto files have a comment in the first line and a function prototype in + " the second line, it always ends in ";". Indent files may also have + " comments, thus we can't match comments to see the difference. + " IDL files can have a single ';' in the second line, require at least one + " chacter before the ';'. + if getline(2) =~ '.;$' + setf cpp + else + exe 'setf ' . a:default + endif +endfunc + +func dist#ft#FTm() + let n = 1 + let saw_comment = 0 " Whether we've seen a multiline comment leader. + while n < 100 + let line = getline(n) + if line =~ '^\s*/\*' + " /* ... */ is a comment in Objective C and Murphi, so we can't conclude + " it's either of them yet, but track this as a hint in case we don't see + " anything more definitive. + let saw_comment = 1 + endif + if line =~ '^\s*\(#\s*\(include\|import\)\>\|@import\>\|//\)' + setf objc + return + endif + if line =~ '^\s*%' + setf matlab + return + endif + if line =~ '^\s*(\*' + setf mma + return + endif + if line =~ '^\c\s*\(\(type\|var\)\>\|--\)' + setf murphi + return + endif + let n = n + 1 + endwhile + + if saw_comment + " We didn't see anything definitive, but this looks like either Objective C + " or Murphi based on the comment leader. Assume the former as it is more + " common. + setf objc + elseif exists("g:filetype_m") + " Use user specified default filetype for .m + exe "setf " . g:filetype_m + else + " Default is matlab + setf matlab + endif +endfunc + +func dist#ft#FTmms() + let n = 1 + while n < 10 + let line = getline(n) + if line =~ '^\s*\(%\|//\)' || line =~ '^\*' + setf mmix + return + endif + if line =~ '^\s*#' + setf make + return + endif + let n = n + 1 + endwhile + setf mmix +endfunc + +" This function checks if one of the first five lines start with a dot. In +" that case it is probably an nroff file: 'filetype' is set and 1 is returned. +func dist#ft#FTnroff() + if getline(1)[0] . getline(2)[0] . getline(3)[0] . getline(4)[0] . getline(5)[0] =~ '\.' + setf nroff + return 1 + endif + return 0 +endfunc + +func dist#ft#FTmm() + let n = 1 + while n < 10 + let line = getline(n) + if line =~ '^\s*\(#\s*\(include\|import\)\>\|@import\>\|/\*\)' + setf objcpp + return + endif + let n = n + 1 + endwhile + setf nroff +endfunc + +func dist#ft#FTpl() + if exists("g:filetype_pl") + exe "setf " . g:filetype_pl + else + " recognize Prolog by specific text in the first non-empty line + " require a blank after the '%' because Perl uses "%list" and "%translate" + let l = getline(nextnonblank(1)) + if l =~ '\<prolog\>' || l =~ '^\s*\(%\+\(\s\|$\)\|/\*\)' || l =~ ':-' + setf prolog + else + setf perl + endif + endif +endfunc + +func dist#ft#FTinc() + if exists("g:filetype_inc") + exe "setf " . g:filetype_inc + else + let lines = getline(1).getline(2).getline(3) + if lines =~? "perlscript" + setf aspperl + elseif lines =~ "<%" + setf aspvbs + elseif lines =~ "<?" + setf php + else + call dist#ft#FTasmsyntax() + if exists("b:asmsyntax") + exe "setf " . fnameescape(b:asmsyntax) + else + setf pov + endif + endif + endif +endfunc + +func dist#ft#FTprogress_cweb() + if exists("g:filetype_w") + exe "setf " . g:filetype_w + return + endif + if getline(1) =~ '&ANALYZE' || getline(3) =~ '&GLOBAL-DEFINE' + setf progress + else + setf cweb + endif +endfunc + +func dist#ft#FTprogress_asm() + if exists("g:filetype_i") + exe "setf " . g:filetype_i + return + endif + " This function checks for an assembly comment the first ten lines. + " If not found, assume Progress. + let lnum = 1 + while lnum <= 10 && lnum < line('$') + let line = getline(lnum) + if line =~ '^\s*;' || line =~ '^\*' + call dist#ft#FTasm() + return + elseif line !~ '^\s*$' || line =~ '^/\*' + " Not an empty line: Doesn't look like valid assembly code. + " Or it looks like a Progress /* comment + break + endif + let lnum = lnum + 1 + endw + setf progress +endfunc + +func dist#ft#FTprogress_pascal() + if exists("g:filetype_p") + exe "setf " . g:filetype_p + return + endif + " This function checks for valid Pascal syntax in the first ten lines. + " Look for either an opening comment or a program start. + " If not found, assume Progress. + let lnum = 1 + while lnum <= 10 && lnum < line('$') + let line = getline(lnum) + if line =~ '^\s*\(program\|unit\|procedure\|function\|const\|type\|var\)\>' + \ || line =~ '^\s*{' || line =~ '^\s*(\*' + setf pascal + return + elseif line !~ '^\s*$' || line =~ '^/\*' + " Not an empty line: Doesn't look like valid Pascal code. + " Or it looks like a Progress /* comment + break + endif + let lnum = lnum + 1 + endw + setf progress +endfunc + +func dist#ft#FTr() + let max = line("$") > 50 ? 50 : line("$") + + for n in range(1, max) + " Rebol is easy to recognize, check for that first + if getline(n) =~? '\<REBOL\>' + setf rebol + return + endif + endfor + + for n in range(1, max) + " R has # comments + if getline(n) =~ '^\s*#' + setf r + return + endif + " Rexx has /* comments */ + if getline(n) =~ '^\s*/\*' + setf rexx + return + endif + endfor + + " Nothing recognized, use user default or assume Rexx + if exists("g:filetype_r") + exe "setf " . g:filetype_r + else + " Rexx used to be the default, but R appears to be much more popular. + setf r + endif +endfunc + +func dist#ft#McSetf() + " Rely on the file to start with a comment. + " MS message text files use ';', Sendmail files use '#' or 'dnl' + for lnum in range(1, min([line("$"), 20])) + let line = getline(lnum) + if line =~ '^\s*\(#\|dnl\)' + setf m4 " Sendmail .mc file + return + elseif line =~ '^\s*;' + setf msmessages " MS Message text file + return + endif + endfor + setf m4 " Default: Sendmail .mc file +endfunc + +" Called from filetype.vim and scripts.vim. +func dist#ft#SetFileTypeSH(name) + if expand("<amatch>") =~ g:ft_ignore_pat + return + endif + if a:name =~ '\<csh\>' + " Some .sh scripts contain #!/bin/csh. + call dist#ft#SetFileTypeShell("csh") + return + elseif a:name =~ '\<tcsh\>' + " Some .sh scripts contain #!/bin/tcsh. + call dist#ft#SetFileTypeShell("tcsh") + return + elseif a:name =~ '\<zsh\>' + " Some .sh scripts contain #!/bin/zsh. + call dist#ft#SetFileTypeShell("zsh") + return + elseif a:name =~ '\<ksh\>' + let b:is_kornshell = 1 + if exists("b:is_bash") + unlet b:is_bash + endif + if exists("b:is_sh") + unlet b:is_sh + endif + elseif exists("g:bash_is_sh") || a:name =~ '\<bash\>' || a:name =~ '\<bash2\>' + let b:is_bash = 1 + if exists("b:is_kornshell") + unlet b:is_kornshell + endif + if exists("b:is_sh") + unlet b:is_sh + endif + elseif a:name =~ '\<sh\>' + let b:is_sh = 1 + if exists("b:is_kornshell") + unlet b:is_kornshell + endif + if exists("b:is_bash") + unlet b:is_bash + endif + endif + call dist#ft#SetFileTypeShell("sh") +endfunc + +" For shell-like file types, check for an "exec" command hidden in a comment, +" as used for Tcl. +" Also called from scripts.vim, thus can't be local to this script. +func dist#ft#SetFileTypeShell(name) + if expand("<amatch>") =~ g:ft_ignore_pat + return + endif + let l = 2 + while l < 20 && l < line("$") && getline(l) =~ '^\s*\(#\|$\)' + " Skip empty and comment lines. + let l = l + 1 + endwhile + if l < line("$") && getline(l) =~ '\s*exec\s' && getline(l - 1) =~ '^\s*#.*\\$' + " Found an "exec" line after a comment with continuation + let n = substitute(getline(l),'\s*exec\s\+\([^ ]*/\)\=', '', '') + if n =~ '\<tclsh\|\<wish' + setf tcl + return + endif + endif + exe "setf " . a:name +endfunc + +func dist#ft#CSH() + if exists("g:filetype_csh") + call dist#ft#SetFileTypeShell(g:filetype_csh) + elseif &shell =~ "tcsh" + call dist#ft#SetFileTypeShell("tcsh") + else + call dist#ft#SetFileTypeShell("csh") + endif +endfunc + +let s:ft_rules_udev_rules_pattern = '^\s*\cudev_rules\s*=\s*"\([^"]\{-1,}\)/*".*' +func dist#ft#FTRules() + let path = expand('<amatch>:p') + if path =~ '^/\(etc/udev/\%(rules\.d/\)\=.*\.rules\|lib/udev/\%(rules\.d/\)\=.*\.rules\)$' + setf udevrules + return + endif + if path =~ '^/etc/ufw/' + setf conf " Better than hog + return + endif + if path =~ '^/\(etc\|usr/share\)/polkit-1/rules\.d' + setf javascript + return + endif + try + let config_lines = readfile('/etc/udev/udev.conf') + catch /^Vim\%((\a\+)\)\=:E484/ + setf hog + return + endtry + let dir = expand('<amatch>:p:h') + for line in config_lines + if line =~ s:ft_rules_udev_rules_pattern + let udev_rules = substitute(line, s:ft_rules_udev_rules_pattern, '\1', "") + if dir == udev_rules + setf udevrules + endif + break + endif + endfor + setf hog +endfunc + +func dist#ft#SQL() + if exists("g:filetype_sql") + exe "setf " . g:filetype_sql + else + setf sql + endif +endfunc + +" If the file has an extension of 't' and is in a directory 't' or 'xt' then +" it is almost certainly a Perl test file. +" If the first line starts with '#' and contains 'perl' it's probably a Perl +" file. +" (Slow test) If a file contains a 'use' statement then it is almost certainly +" a Perl file. +func dist#ft#FTperl() + let dirname = expand("%:p:h:t") + if expand("%:e") == 't' && (dirname == 't' || dirname == 'xt') + setf perl + return 1 + endif + if getline(1)[0] == '#' && getline(1) =~ 'perl' + setf perl + return 1 + endif + if search('^use\s\s*\k', 'nc', 30) + setf perl + return 1 + endif + return 0 +endfunc + +" Choose context, plaintex, or tex (LaTeX) based on these rules: +" 1. Check the first line of the file for "%&<format>". +" 2. Check the first 1000 non-comment lines for LaTeX or ConTeXt keywords. +" 3. Default to "latex" or to g:tex_flavor, can be set in user's vimrc. +func dist#ft#FTtex() + let firstline = getline(1) + if firstline =~ '^%&\s*\a\+' + let format = tolower(matchstr(firstline, '\a\+')) + let format = substitute(format, 'pdf', '', '') + if format == 'tex' + let format = 'latex' + elseif format == 'plaintex' + let format = 'plain' + endif + elseif expand('%') =~ 'tex/context/.*/.*.tex' + let format = 'context' + else + " Default value, may be changed later: + let format = exists("g:tex_flavor") ? g:tex_flavor : 'plain' + " Save position, go to the top of the file, find first non-comment line. + let save_cursor = getpos('.') + call cursor(1,1) + let firstNC = search('^\s*[^[:space:]%]', 'c', 1000) + if firstNC " Check the next thousand lines for a LaTeX or ConTeXt keyword. + let lpat = 'documentclass\>\|usepackage\>\|begin{\|newcommand\>\|renewcommand\>' + let cpat = 'start\a\+\|setup\a\+\|usemodule\|enablemode\|enableregime\|setvariables\|useencoding\|usesymbols\|stelle\a\+\|verwende\a\+\|stel\a\+\|gebruik\a\+\|usa\a\+\|imposta\a\+\|regle\a\+\|utilisemodule\>' + let kwline = search('^\s*\\\%(' . lpat . '\)\|^\s*\\\(' . cpat . '\)', + \ 'cnp', firstNC + 1000) + if kwline == 1 " lpat matched + let format = 'latex' + elseif kwline == 2 " cpat matched + let format = 'context' + endif " If neither matched, keep default set above. + " let lline = search('^\s*\\\%(' . lpat . '\)', 'cn', firstNC + 1000) + " let cline = search('^\s*\\\%(' . cpat . '\)', 'cn', firstNC + 1000) + " if cline > 0 + " let format = 'context' + " endif + " if lline > 0 && (cline == 0 || cline > lline) + " let format = 'tex' + " endif + endif " firstNC + call setpos('.', save_cursor) + endif " firstline =~ '^%&\s*\a\+' + + " Translation from formats to file types. TODO: add AMSTeX, RevTex, others? + if format == 'plain' + setf plaintex + elseif format == 'context' + setf context + else " probably LaTeX + setf tex + endif + return +endfunc + +func dist#ft#FTxml() + let n = 1 + while n < 100 && n < line("$") + let line = getline(n) + " DocBook 4 or DocBook 5. + let is_docbook4 = line =~ '<!DOCTYPE.*DocBook' + let is_docbook5 = line =~ ' xmlns="http://docbook.org/ns/docbook"' + if is_docbook4 || is_docbook5 + let b:docbk_type = "xml" + if is_docbook5 + let b:docbk_ver = 5 + else + let b:docbk_ver = 4 + endif + setf docbk + return + endif + if line =~ 'xmlns:xbl="http://www.mozilla.org/xbl"' + setf xbl + return + endif + let n += 1 + endwhile + setf xml +endfunc + +func dist#ft#FTy() + let n = 1 + while n < 100 && n < line("$") + let line = getline(n) + if line =~ '^\s*%' + setf yacc + return + endif + if getline(n) =~ '^\s*\(#\|class\>\)' && getline(n) !~ '^\s*#\s*include' + setf racc + return + endif + let n = n + 1 + endwhile + setf yacc +endfunc + +func dist#ft#Redif() + let lnum = 1 + while lnum <= 5 && lnum < line('$') + if getline(lnum) =~ "^\ctemplate-type:" + setf redif + return + endif + let lnum = lnum + 1 + endwhile +endfunc + + +" Restore 'cpoptions' +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index 4adab1aa76..7067ff6c1d 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -51,7 +51,7 @@ function! s:shellify(cmd) abort return a:cmd endif return join(map(copy(a:cmd), - \'v:val =~# ''\m[\-.a-zA-Z_/]'' ? shellescape(v:val) : v:val'), ' ') + \'v:val =~# ''\m[^\-.a-zA-Z_/]'' ? shellescape(v:val) : v:val'), ' ') endfunction " Run a system command and timeout after 30 seconds. @@ -70,7 +70,8 @@ function! s:system(cmd, ...) abort let jobid = jobstart(a:cmd, opts) if jobid < 1 - call health#report_error(printf('Command error (job=%d): %s', jobid, s:shellify(a:cmd))) + call health#report_error(printf('Command error (job=%d): `%s` (in %s)', + \ jobid, s:shellify(a:cmd), string(getcwd()))) let s:shell_error = 1 return opts.output endif @@ -84,8 +85,8 @@ function! s:system(cmd, ...) abort call health#report_error(printf('Command timed out: %s', s:shellify(a:cmd))) call jobstop(jobid) elseif s:shell_error != 0 && !ignore_error - call health#report_error(printf("Command error (job=%d): %s\nOutput: %s", jobid, - \ s:shellify(a:cmd), opts.output)) + call health#report_error(printf("Command error (job=%d): `%s` (in %s)\nOutput: %s", + \ jobid, s:shellify(a:cmd), string(getcwd()), opts.output)) endif return opts.output @@ -185,7 +186,9 @@ function! s:version_info(python) abort endif let nvim_path = s:trim(s:system([ - \ a:python, '-c', 'import neovim; print(neovim.__file__)'])) + \ a:python, '-c', + \ 'import sys; sys.path.remove(""); ' . + \ 'import neovim; print(neovim.__file__)'])) if s:shell_error || empty(nvim_path) return [python_version, 'unable to load neovim Python module', pypi_version, \ nvim_path] diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index 4d43a4582b..f21e2ee4f3 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -19,7 +19,7 @@ function! s:init() abort let s:find_arg = '-l' endif " Check for -l support. - call s:get_page(s:get_path('', 'man')[0:-2]) + call s:get_page(s:get_path('', 'man')) catch /E145:/ " Ignore the error in restricted mode catch /command error .*/ @@ -213,14 +213,16 @@ endfunction function! s:get_path(sect, name) abort if empty(a:sect) - return s:system(['man', s:find_arg, a:name]) + " Some man implementations (OpenBSD) return all available paths from the + " search command, so we get() the first one. #8341 + return substitute(get(split(s:system(['man', s:find_arg, a:name])), 0, ''), '\n\+$', '', '') endif " '-s' flag handles: " - tokens like 'printf(echo)' " - sections starting with '-' " - 3pcap section (found on macOS) " - commas between sections (for section priority) - return s:system(['man', s:find_arg, s:section_arg, a:sect, a:name]) + return substitute(s:system(['man', s:find_arg, s:section_arg, a:sect, a:name]), '\n\+$', '', '') endfunction function! s:verify_exists(sect, name) abort @@ -233,13 +235,10 @@ function! s:verify_exists(sect, name) abort let path = s:get_path('', a:name) endtry endtry - " We need to extract the section from the path because sometimes - " the actual section of the manpage is more specific than the section - " we provided to `man`. Try ':Man 3 App::CLI'. - " Also on linux, it seems that the name is case insensitive. So if one does - " ':Man PRIntf', we still want the name of the buffer to be 'printf' or - " whatever the correct capitilization is. - let path = path[:len(path)-2] + " Extract the section from the path, because sometimes the actual section is + " more specific than what we provided to `man` (try `:Man 3 App::CLI`). + " Also on linux, name seems to be case-insensitive. So for `:Man PRIntf`, we + " still want the name of the buffer to be 'printf'. return s:extract_sect_and_name_path(path) + [path] endfunction diff --git a/runtime/autoload/msgpack.vim b/runtime/autoload/msgpack.vim index a10ac32469..09c99c940d 100644 --- a/runtime/autoload/msgpack.vim +++ b/runtime/autoload/msgpack.vim @@ -40,9 +40,10 @@ function s:msgpack_init_python() abort return s:msgpack_python_type endif let s:msgpack_python_initialized = 1 - for suf in ['', '3'] + for suf in (has('win32') ? ['3'] : ['', '3']) try execute 'python' . suf + \. "\n" \. "def shada_dict_strftime():\n" \. " import datetime\n" \. " import vim\n" @@ -60,12 +61,15 @@ function s:msgpack_init_python() abort \. " fmt = vim.eval('a:format')\n" \. " timestr = vim.eval('a:string')\n" \. " timestamp = datetime.datetime.strptime(timestr, fmt)\n" - \. " timestamp = int(timestamp.timestamp())\n" + \. " try:\n" + \. " timestamp = int(timestamp.timestamp())\n" + \. " except:\n" + \. " timestamp = int(timestamp.strftime('%s'))\n" \. " if timestamp > 2 ** 31:\n" - \. " tsabs = abs(timestamp)" + \. " tsabs = abs(timestamp)\n" \. " return ('{\"_TYPE\": v:msgpack_types.integer,'\n" \. " + '\"_VAL\": [{sign},{v1},{v2},{v3}]}').format(\n" - \. " sign=1 if timestamp >= 0 else -1,\n" + \. " sign=(1 if timestamp >= 0 else -1),\n" \. " v1=((tsabs >> 62) & 0x3),\n" \. " v2=((tsabs >> 31) & (2 ** 31 - 1)),\n" \. " v3=(tsabs & (2 ** 31 - 1)))\n" diff --git a/runtime/autoload/provider/node.vim b/runtime/autoload/provider/node.vim index ae6f84de72..39b5dc63b8 100644 --- a/runtime/autoload/provider/node.vim +++ b/runtime/autoload/provider/node.vim @@ -38,6 +38,9 @@ function! provider#node#can_inspect() abort endfunction function! provider#node#Detect() abort + if exists('g:node_host_prog') + return g:node_host_prog + endif let global_modules = get(split(system('npm root -g'), "\n"), 0, '') if v:shell_error || !isdirectory(global_modules) return '' diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 9a04bf2824..06a016eddb 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -272,10 +272,11 @@ Name triggered by ~ |GUIEnter| after starting the GUI successfully |GUIFailed| after starting the GUI failed |TermResponse| after the terminal response to |t_RV| is received - |QuitPre| when using `:quit`, before deciding whether to quit -|VimLeavePre| before exiting Vim, before writing the shada file -|VimLeave| before exiting Vim, after writing the shada file +|VimLeavePre| before exiting Nvim, before writing the shada file +|VimLeave| before exiting Nvim, after writing the shada file +|VimResume| after Nvim is resumed +|VimSuspend| before Nvim is suspended Various |DirChanged| after the |current-directory| was changed @@ -1009,6 +1010,10 @@ VimLeavePre Before exiting Vim, just before writing the VimResized After the Vim window was resized, thus 'lines' and/or 'columns' changed. Not when starting up though. + *VimResume* +VimResume After Nvim resumes from |suspend| state. + *VimSuspend* +VimSuspend Before Nvim enters |suspend| state. *WinEnter* WinEnter After entering another window. Not done for the first window, when Vim has just started. diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt index eb2bac6fce..dbac878a33 100644 --- a/runtime/doc/channel.txt +++ b/runtime/doc/channel.txt @@ -44,19 +44,23 @@ read over its stderr. Similarily, only bytes can be written to nvim's own stderr *channel-callback* *buffered* *E5210* *on_stdout* *on_stderr* *on_stdin* *on_data* -A callback function `on_{stream}` will be invoked with data read from the -channel. By default, the callback will be invoked immediately when data is -available, to facilitate interactive communication. The same callback will -then be invoked with empty data, to indicate that the stream reached EOF. -Alternatively the `{stream}_buffered` option can be set to invoke the callback -only when the underlying stream reaches EOF, and will then be passed in -complete output. This is helpful when only the complete output is useful, and -not partial data. Futhermore if `{stream}_buffered` is set but not a callback, -the data is saved in the options dict, with the stream name as key. For this -to work a new options dict must be used for each opened channel. If a script -uses a global `s:job_opts` dict, it can be copied with |copy()| before supplying -it to |jobstart()|. If a dict is reused, so that the dict key already is -occupied, error `E5210` will be raised. +It is possible to register callback functions when a channel receives data by +passing the `on_stdout`, `on_stderr`, `on_stdin` options on creation. Sockets +may specify an `on_data` callback instead. Each callback function will be +invoked with data read from the channel. By default, the callback will be +invoked immediately when data is available, to facilitate interactive +communication. The same callback will then be invoked with empty data, to +indicate that the stream reached EOF. Alternatively the `stdout_buffered`, +`stderr_buffered`, `stdin_buffered`, `data_buffered` options can be set to +invoke the corresponding callback only when the underlying stream reaches EOF. +The callback will then be passed complete output. This is helpful when only +the complete output is useful, and not partial data. Futhermore if the stream +is set to be buffered, but the callback is not set, the data is saved in the +options dict, with the stream name as key. For this to work a new options dict +must be used for each opened channel. If a script uses a global `s:job_opts` +dict, it can be copied with |copy()| before supplying it to |jobstart()|. If a +dict is reused, so that the dict key already is occupied, error `E5210` will +be raised. - The arguments passed to the callback function are: @@ -66,7 +70,7 @@ occupied, error `E5210` will be raised. Note that the items in this list do not directly correspond to actual lines in the output. See |channel-lines| 2: Stream name as a string, like `"stdout"`. This is to allow multiple - on_{event} handlers to be implemented by the same function. The available + stream handlers to be implemented by the same function. The available events depend on how the channel was opened and in what mode/protocol. *channel-lines* diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index ea61e847c7..03699b3dfb 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -22,6 +22,10 @@ Commands ~ *:wv* *:wviminfo* Deprecated alias to |:wshada| command. +Environment Variables ~ +*$NVIM_LISTEN_ADDRESS* Deprecated in favor of |--listen|. If both are given, + $NVIM_LISTEN_ADDRESS is ignored. + Events ~ *EncodingChanged* Never fired; 'encoding' is always "utf-8". *FileEncoding* Never fired; equivalent to |EncodingChanged|. diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 767fc133d8..fffdcef8d9 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1788,9 +1788,9 @@ v:scrollstart String describing the script or function that caused the hit-enter prompt. *v:servername* *servername-variable* - *$NVIM_LISTEN_ADDRESS* -v:servername Default Nvim server address. Equivalent to - |$NVIM_LISTEN_ADDRESS| on startup. |serverstop()| +v:servername Primary listen-address of the current Nvim instance, the first + item returned by |serverlist()|. Can be set by |--listen| or + |$NVIM_LISTEN_ADDRESS| at startup. |serverstart()| |serverstop()| Read-only. @@ -2286,6 +2286,7 @@ split({expr} [, {pat} [, {keepempty}]]) List make |List| from {pat} separated {expr} sqrt({expr}) Float square root of {expr} stdioopen({dict}) Number open stdio in a headless instance. +stdpath({what}) String/List returns the standard path(s) for {what} str2float({expr}) Float convert String to Float str2nr({expr} [, {base}]) Number convert String to Number strchars({expr} [, {skipcc}]) Number character length of the String {expr} @@ -4208,8 +4209,7 @@ getftype({fname}) *getftype()* getftype("/home") < Note that a type such as "link" will only be returned on systems that support it. On some systems only "dir" and - "file" are returned. On MS-Windows a symbolic link to a - directory returns "dir" instead of "link". + "file" are returned. *getline()* getline({lnum} [, {end}]) @@ -5033,10 +5033,9 @@ jobstart({cmd}[, {opts}]) *jobstart()* width : (pty only) Width of the terminal screen height : (pty only) Height of the terminal screen TERM : (pty only) $TERM environment variable - detach : (non-pty only) Detach the job process from the - nvim process. The process will not get killed - when nvim exits. If the process dies before - nvim exits, "on_exit" will still be invoked. + detach : (non-pty only) Detach the job process: it will + not be killed when Nvim exits. If the process + exits before Nvim, "on_exit" will be invoked. {opts} is passed as |self| dictionary to the callback; the caller may set other keys to pass application-specific data. @@ -6638,15 +6637,11 @@ server2client({clientid}, {string}) *server2client()* :echo server2client(expand("<client>"), "HELLO") < serverlist() *serverlist()* - Returns a list of available server names in a list. - When there are no servers an empty string is returned. + Returns a list of server addresses, or empty if all servers + were stopped. |serverstart()| |serverstop()| Example: > :echo serverlist() -< {Nvim} *--serverlist* - The Vim command-line option `--serverlist` was removed from - Nvim, but it can be imitated: > - nvim --cmd "echo serverlist()" --cmd "q" -< + serverstart([{address}]) *serverstart()* Opens a socket or named pipe at {address} and listens for |RPC| messages. Clients can send |API| commands to the address @@ -6674,13 +6669,9 @@ serverstart([{address}]) *serverstart()* < |$NVIM_LISTEN_ADDRESS| is set to {address} if not already set. - *--servername* - The Vim command-line option `--servername` can be imitated: > - nvim --cmd "let g:server_addr = serverstart('foo')" -< serverstop({address}) *serverstop()* - Closes the pipe or socket at {address}. Does nothing if - {address} is empty or invalid. + Closes the pipe or socket at {address}. + Returns TRUE if {address} is valid, else FALSE. If |$NVIM_LISTEN_ADDRESS| is stopped it is unset. If |v:servername| is stopped it is set to the next available address returned by |serverlist()|. @@ -7271,6 +7262,23 @@ stdioopen({opts}) *stdioopen()* - 0 on invalid arguments +stdpath({what}) *stdpath()* *E6100* + Returns the standard path(s) for {what}. + + These directories are the default locations for various files + used by Neovim. + + {what} Type Description ~ + cache String Cache directory. Useful for plugins + that need temporary files to work. + config String User configuration directory. The + |init.vim| is stored here. + config_dirs List Additional configuration directories. + data String User data directory. The |shada-file| + is stored here. + data_dirs List Additional data directories. + + str2float({expr}) *str2float()* Convert String {expr} to a Float. This mostly works the same as when using a floating point number in an expression, see diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index 9ae35bea52..1feff0723b 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -40,10 +40,11 @@ char action ~ *i_CTRL-[* *i_<Esc>* <Esc> or CTRL-[ End insert or Replace mode, go back to Normal mode. Finish abbreviation. - Note: If your <Esc> key is hard to hit on your keyboard, train - yourself to use CTRL-[. - If Esc doesn't work and you are using a Mac, try CTRL-Esc. - Or disable Listening under Accessibility preferences. + Note: If your <Esc> key is hard to hit, try CTRL-[ instead. + *i_META* *i_ALT* + |ALT| (|META|) acts like <Esc> if the chord is not mapped. + For example <A-x> acts like <Esc>x if <A-x> does not have an + insert-mode mapping. *i_CTRL-C* CTRL-C Quit insert mode, go back to Normal mode. Do not check for abbreviations. Does not trigger the |InsertLeave| autocommand diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index c64f8a1e0a..7633dbf352 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -232,8 +232,10 @@ For this reason the following is blocked: - Editing another buffer. - The |:normal| command. - Moving the cursor is allowed, but it is restored afterwards. +- If the cmdline is changed, the old text and cursor position are restored. If you want the mapping to do any of these let the returned characters do -that. +that. Alternatively use a |<Cmd>| mapping which doesn't have these +restrictions. You can use getchar(), it consumes typeahead if there is any. E.g., if you have these mappings: > @@ -272,6 +274,29 @@ again for using <expr>. This does work: > Using 0x80 as a single byte before other text does not work, it will be seen as a special key. + *<Cmd>* *:map-command* +A command mapping is a mapping that directly executes a command. Command +mappings are written by placing a command in between <Cmd> and <CR> in the +rhs of a mapping (in any mode): > + noremap <f3> <Cmd>echo mode(1)<cr> +< + *E5520* +The command must be complete and ended with a <CR>. If the command is +incomplete, an error is raised. |Command-line| mode is never entered. + +This is more flexible than using `:<c-u>` in visual and operator pending +mode, or `<c-o>:` in insert mode, as the commands are exectued directly in the +mode, and not normal mode. Also visual mode is not aborted. Commands can be +invoked directly in cmdline mode, which is not simple otherwise (a timer has +to be used). Unlike <expr> mappings, there are not any specific restrictions +what the command can do, except for what is normally possible to do in every +specific mode. The command should be executed the same way as if an +(unrestricted) |autocmd| was invoked or an async event event was processed. + +Note: In select mode, |:map| or |:vmap| command mappings will be executed in +visual mode. If a mapping is intended to work in select mode, it is +recomendend to map it using |:smap|, possibly in addition to the same mapping +with |:map| or |:xmap|. 1.3 MAPPING AND MODES *:map-modes* *mapmode-nvo* *mapmode-n* *mapmode-v* *mapmode-o* *mapmode-t* diff --git a/runtime/doc/msgpack_rpc.txt b/runtime/doc/msgpack_rpc.txt index 11fad105b5..01d4e10cea 100644 --- a/runtime/doc/msgpack_rpc.txt +++ b/runtime/doc/msgpack_rpc.txt @@ -70,9 +70,8 @@ An rpc socket is automatically created with each instance. The socket location is stored in |v:servername|. By default this is a named pipe with an automatically generated address. See |XXX|. -To make Nvim listen on a TCP/IP socket instead, set the - |$NVIM_LISTEN_ADDRESS| environment variable before starting Nvim: > - NVIM_LISTEN_ADDRESS=127.0.0.1:6666 nvim +To make Nvim listen on a TCP/IP socket instead, specify |--listen|: > + nvim --listen 127.0.0.1:6666 <Also, more sockets and named pipes can be listened on using |serverstart()|. Note that localhost TCP sockets are generally less secure than named pipes, diff --git a/runtime/doc/nvim.txt b/runtime/doc/nvim.txt index 2420227f6c..b8a481a016 100644 --- a/runtime/doc/nvim.txt +++ b/runtime/doc/nvim.txt @@ -20,15 +20,24 @@ differences from Vim. ============================================================================== Transitioning from Vim *nvim-from-vim* -To start the transition, create ~/.config/nvim/init.vim with these contents: +To start the transition, create init.vim in the correct directory for your +platform. + +For Linux, macOS and other Unixes, create the file at ~/.config/nvim/init.vim. + +For Windows, create the file at %LOCALAPPDATA%\nvim\init.vim. `%LOCALAPPDATA%` +usually expands to `C:\Users\<username>\AppData\Local`. + +Note: If your system sets `$XDG_CONFIG_HOME`, use that instead of `~/.config` +or `%LOCALAPPDATA%` in the paths above. Nvim follows the XDG |base-directories| +convention. + +Next, add these contents to the file: > set runtimepath^=~/.vim runtimepath+=~/.vim/after let &packpath = &runtimepath source ~/.vimrc < -Note: If your system sets `$XDG_CONFIG_HOME`, use that instead of `~/.config` -in the code above. Nvim follows the XDG |base-directories| convention. - See |provider-python| and |provider-clipboard| for additional software you might need to use some features. diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt index 94e7899b6a..8c6edc0fb8 100644 --- a/runtime/doc/nvim_terminal_emulator.txt +++ b/runtime/doc/nvim_terminal_emulator.txt @@ -96,7 +96,6 @@ global configuration. - 'list' is disabled - 'wrap' is disabled -- 'relativenumber' is disabled in |Terminal-mode| (and cannot be enabled) You can change the defaults with a TermOpen autocommand: > au TermOpen * setlocal list diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 9ef5ccb5c5..1f880eeb36 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -2036,6 +2036,11 @@ A jump table for the options with a short description can be found at |Q_op|. column of the last screen line. Overrules "lastline". uhex Show unprintable characters hexadecimal as <xx> instead of using ^C and ~C. + msgsep When showing messages longer than 'cmdheight' lines, + only scroll the message lines and not the entire + screen. This also shows a separator line filled with + chars determined by 'fillchars' option, and + highlighted with the |MsgSeparator| group. When neither "lastline" nor "truncate" is included, a last line that doesn't fit is replaced with "@" lines. @@ -2390,6 +2395,7 @@ A jump table for the options with a short description can be found at |Q_op|. vert:c '│' or '|' vertical separators |:vsplit| fold:c '·' or '-' filling 'foldtext' diff:c '-' deleted lines of the 'diff' option + msgsep:c ' ' message separator 'display' Any one that is omitted will fall back to the default. For "stl" and "stlnc" the space will be used when there is highlighting, '^' or '=' @@ -2684,17 +2690,19 @@ A jump table for the options with a short description can be found at |Q_op|. security reasons. *'fsync'* *'fs'* -'fsync' 'fs' boolean (default on) +'fsync' 'fs' boolean (default off) global - When on, the library function fsync() will be called after writing a - file. This will flush a file to disk, ensuring that it is safely - written even on filesystems which do metadata-only journaling. This - will force the harddrive to spin up on Linux systems running in laptop - mode, so it may be undesirable in some situations. Be warned that - turning this off increases the chances of data loss after a crash. + When on, the OS function fsync() will be called after saving a file + (|:write|, |writefile()|, …), |swap-file| and |shada-file|. This + flushes the file to disk, ensuring that it is safely written. + Slow on some systems: writing buffers, quitting Nvim, and other + operations may sometimes take a few seconds. - Currently applies only to writing the buffer with e.g. |:w| and - |writefile()|. + Files are ALWAYS flushed ('fsync' is ignored) when: + - |CursorHold| event is triggered + - |:preserve| is called + - system signals low battery life + - Nvim exits abnormally *'gdefault'* *'gd'* *'nogdefault'* *'nogd'* 'gdefault' 'gd' boolean (default off) @@ -3916,16 +3924,6 @@ A jump table for the options with a short description can be found at |Q_op|. because the 'w' is used before the next mapping is done. See also |key-mapping|. - *'maxmem'* *'mm'* -'maxmem' 'mm' number (default between 256 to 5120 (system - dependent) or half the amount of memory - available) - global - Maximum amount of memory (in Kbyte) to use for one buffer. When this - limit is reached allocating extra memory for a buffer will cause - other memory to be freed. The maximum usable value is about 2000000. - Use this to work without a limit. Also see 'maxmemtot'. - *'maxmempattern'* *'mmp'* 'maxmempattern' 'mmp' number (default 1000) global @@ -3939,20 +3937,6 @@ A jump table for the options with a short description can be found at |Q_op|. "\(.\)*" on a very long line. ".*" works much better. Vim may run out of memory before hitting the 'maxmempattern' limit. - *'maxmemtot'* *'mmt'* -'maxmemtot' 'mmt' number (default between 2048 and 10240 (system - dependent) or half the amount of memory - available) - global - Maximum amount of memory in Kbyte to use for all buffers together. - The maximum usable value is about 2000000 (2 Gbyte). Use this to work - without a limit. - On 64 bit machines higher values might work. But hey, do you really - need more than 2 Gbyte for text editing? Keep in mind that text is - stored in the swap file, one can edit files > 2 Gbyte anyway. We do - need the memory to store undo info. - Also see 'maxmem'. - *'menuitems'* *'mis'* 'menuitems' 'mis' number (default 25) global @@ -5160,10 +5144,10 @@ A jump table for the options with a short description can be found at |Q_op|. security reasons. *'shellcmdflag'* *'shcf'* -'shellcmdflag' 'shcf' string (default: "-c"; Windows: "/c") +'shellcmdflag' 'shcf' string (default: "-c"; Windows: "/s /c") global Flag passed to the shell to execute "!" and ":!" commands; e.g., - "bash.exe -c ls" or "cmd.exe /c dir". For Windows + `bash.exe -c ls` or `cmd.exe /s /c "dir"`. For Windows systems, the default is set according to the value of 'shell', to reduce the need to set this option by the user. On Unix it can have more than one flag. Each white space separated @@ -5284,7 +5268,7 @@ A jump table for the options with a short description can be found at |Q_op|. to execute most external commands with cmd.exe. *'shellxquote'* *'sxq'* -'shellxquote' 'sxq' string (default: "") +'shellxquote' 'sxq' string (default: "", Windows: "\"") global Quoting character(s), put around the command passed to the shell, for the "!" and ":!" commands. Includes the redirection. See @@ -6284,15 +6268,14 @@ A jump table for the options with a short description can be found at |Q_op|. *'timeoutlen'* *'tm'* 'timeoutlen' 'tm' number (default 1000) global - The time in milliseconds that is waited for a mapped sequence to - complete. + Time in milliseconds to wait for a mapped sequence to complete. *'ttimeoutlen'* *'ttm'* 'ttimeoutlen' 'ttm' number (default 50) global - The time in milliseconds that is waited for a key code - sequence to complete. Also used for CTRL-\ CTRL-N and CTRL-\ CTRL-G - when part of a command has been typed. + Time in milliseconds to wait for a key code sequence to complete. Also + used for CTRL-\ CTRL-N and CTRL-\ CTRL-G when part of a command has + been typed. *'title'* *'notitle'* 'title' boolean (default off) diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt index b806b08f95..4a76f1399e 100644 --- a/runtime/doc/provider.txt +++ b/runtime/doc/provider.txt @@ -33,20 +33,20 @@ To use Vim Python 2/3 plugins with Nvim: - For Python 2 plugins, make sure an interpreter for Python 2.6 or 2.7 is available in your `$PATH`, then install the `neovim` Python package systemwide: > - $ sudo pip2 install --upgrade neovim + sudo pip2 install --upgrade neovim < or for the current user: > - $ pip2 install --user --upgrade neovim + pip2 install --user --upgrade neovim < - For Python 3 plugins, make sure an interpreter for Python 3.3 or above is available in your `$PATH`, then install the `neovim` Python package systemwide: > - $ sudo pip3 install --upgrade neovim + sudo pip3 install --upgrade neovim < or for the current user: > - $ pip3 install --user --upgrade neovim + pip3 install --user --upgrade neovim < Note: The `--upgrade` flag ensures you have the latest version even if - a previous version was already installed. +a previous version was already installed. PYTHON PROVIDER CONFIGURATION ~ *g:python_host_prog* @@ -93,7 +93,7 @@ Run |:checkhealth| to see if your system is up-to-date. RUBY QUICKSTART ~ To use Vim Ruby plugins with Nvim, just install the latest `neovim` RubyGem: > - $ gem install neovim + gem install neovim RUBY PROVIDER CONFIGURATION ~ *g:loaded_ruby_provider* @@ -112,7 +112,30 @@ To use an absolute path (e.g. to an rbenv installation): > To use the RVM "system" Ruby installation: > let g:ruby_host_prog = 'rvm system do neovim-ruby-host' < +============================================================================== +Node.js integration *provider-nodejs* + +Nvim supports Node.js |remote-plugin|s. +https://github.com/neovim/node-client/ +https://nodejs.org/ +NODEJS QUICKSTART~ + +To use javascript remote-plugins with Nvim, install the `neovim` npm package: > + npm install -g neovim +< +NODEJS PROVIDER CONFIGURATION~ + *g:loaded_node_provider* +To disable Node support: > + :let g:loaded_node_provider = 1 +< + *g:node_host_prog* +Command to start the Node host. Setting this makes startup faster. + +By default, Nvim searches for `neovim-node-host` using "npm root -g", which +can be slow. To avoid this, set g:node_host_prog to an absolute path: > + let g:node_host_prog = '/usr/local/bin/neovim-node-host' +< ============================================================================== Clipboard integration *provider-clipboard* *clipboard* diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index a0ca17cc4a..7067c60d2f 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -768,9 +768,7 @@ Short explanation of each option: *option-list* 'maxcombine' 'mco' maximum nr of combining characters displayed 'maxfuncdepth' 'mfd' maximum recursive depth for user functions 'maxmapdepth' 'mmd' maximum recursive depth for mapping -'maxmem' 'mm' maximum memory (in Kbyte) used for one buffer 'maxmempattern' 'mmp' maximum memory (in Kbyte) used for pattern search -'maxmemtot' 'mmt' maximum memory (in Kbyte) used for all buffers 'menuitems' 'mis' maximum number of items in a menu 'mkspellmem' 'msm' memory used before |:mkspell| compresses the tree 'modeline' 'ml' recognize modelines at start or end of file diff --git a/runtime/doc/recover.txt b/runtime/doc/recover.txt index 0533157072..ffea514870 100644 --- a/runtime/doc/recover.txt +++ b/runtime/doc/recover.txt @@ -63,10 +63,8 @@ following ones in your vimrc: This is also very handy when editing files on floppy. Of course you will have to create that "tmp" directory for this to work! -For read-only files, a swap file is not used. Unless the file is big, causing -the amount of memory used to be higher than given with 'maxmem' or -'maxmemtot'. And when making a change to a read-only file, the swap file is -created anyway. +For read-only files, a swap file is not used right away. The swap file is +created only when making changes. The 'swapfile' option can be reset to avoid creating a swapfile. And the |:noswapfile| modifier can be used to not create a swapfile for a new buffer. diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index e9188ba641..db3eb757c0 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -355,6 +355,10 @@ argument. instead. See also |silent-mode|, which does start a (limited) UI. +--listen {addr} *--listen* + Start |RPC| server on pipe or TCP address {addr}. Sets the + primary listen address |v:servername| to {addr}. |serverstart()| + ============================================================================== 2. Initialization *initialization* *startup* @@ -683,25 +687,17 @@ CTRL-Z Suspend Vim, like ":stop". Command-line mode, the CTRL-Z is inserted as a normal character. In Visual mode Vim goes back to Normal mode. - Note: if CTRL-Z undoes a change see |mswin.vim|. - :sus[pend][!] or *:sus* *:suspend* *:st* *:stop* -:st[op][!] Suspend Vim. +:st[op][!] Suspend Vim. Vim will continue if you make it the + foreground job again. If the '!' is not given and 'autowrite' is set, every buffer with changes and a file name is written out. If the '!' is given or 'autowrite' is not set, changed buffers are not written, don't forget to bring Vim back to the foreground later! -In the GUI, suspending is implemented as iconising gvim. In Windows 95/NT, -gvim is minimized. - -On many Unix systems, it is possible to suspend Vim with CTRL-Z. This is only -possible in Normal and Visual mode (see next chapter, |vim-modes|). Vim will -continue if you make it the foreground job again. On other systems, CTRL-Z -will start a new shell. This is the same as the ":sh" command. Vim will -continue if you exit from the shell. +In the GUI, suspending is implementation-defined. In X-windows the selection is disowned when Vim suspends. this means you can't paste it in another application (since Vim is going to sleep an attempt @@ -1391,4 +1387,4 @@ RPC clients for debugging. $NVIM_LOG_FILE contains the log file path: > Usually the file is ~/.local/share/nvim/log unless that path is inaccessible or if $NVIM_LOG_FILE was set before |startup|. - vim:tw=78:ts=8:ft=help:norl: + vim:noet:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index ff9773b136..fa66d9d071 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -4909,6 +4909,8 @@ MatchParen The character under the cursor or just before it, if it *hl-ModeMsg* ModeMsg 'showmode' message (e.g., "-- INSERT --") + *hl-MsgSeparator* +MsgSeparator Separator for scrolled messages, `msgsep` flag of 'display' *hl-MoreMsg* MoreMsg |more-prompt| *hl-NonText* diff --git a/runtime/doc/usr_11.txt b/runtime/doc/usr_11.txt index 42009519df..42aa1d9100 100644 --- a/runtime/doc/usr_11.txt +++ b/runtime/doc/usr_11.txt @@ -294,8 +294,6 @@ If you really don't want to see this message, you can add the 'A' flag to the disk. 'updatetime' Timeout after which the swap file is flushed to disk. 'directory' List of directory names where to store the swap file. -'maxmem' Limit for memory usage before writing text to the swap file. -'maxmemtot' Same, but for all files in total. ============================================================================== diff --git a/runtime/doc/vi_diff.txt b/runtime/doc/vi_diff.txt index 917e0e6f80..139cd3749a 100644 --- a/runtime/doc/vi_diff.txt +++ b/runtime/doc/vi_diff.txt @@ -38,18 +38,6 @@ Information for undo and text in registers is kept in memory, thus when making undo levels and the text that can be kept in registers. Other things are also kept in memory: Command-line history, error messages for Quickfix mode, etc. -Memory usage limits -------------------- - -The option 'maxmem' ('mm') is used to set the maximum memory used for one -buffer (in kilobytes). 'maxmemtot' is used to set the maximum memory used for -all buffers (in kilobytes). The defaults depend on the system used. -These are not hard limits, but tell Vim when to move text into a swap file. -If you don't like Vim to swap to a file, set 'maxmem' and 'maxmemtot' to a -very large value. The swap file will then only be used for recovery. If you -don't want a swap file at all, set 'updatecount' to 0, or use the "-n" -argument when starting Vim. - ============================================================================== 2. The most interesting additions *vim-additions* diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 2d53f26028..04d78da45a 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -34,7 +34,7 @@ a complete and centralized reference of those differences. - 'complete' doesn't include "i" - 'cscopeverbose' is enabled - 'directory' defaults to ~/.local/share/nvim/swap// (|xdg|), auto-created -- 'display' defaults to "lastline" +- 'display' defaults to "lastline,msgsep" - 'fillchars' defaults (in effect) to "vert:│,fold:·" - 'formatoptions' defaults to "tcqj" - 'history' defaults to 10000 (the maximum) @@ -130,7 +130,9 @@ Some `CTRL-SHIFT-...` key chords are distinguished from `CTRL-...` variants Options: 'cpoptions' flags: |cpo-_| + 'display' flag `msgsep` to minimize scrolling when showing messages 'guicursor' works in the terminal + 'fillchars' flag `msgsep` (see 'display' above) 'inccommand' shows interactive results for |:substitute|-like commands 'scrollback' 'statusline' supports unlimited alignment sections @@ -164,6 +166,7 @@ Events: Highlight groups: |hl-NormalNC| highlights non-current windows + |hl-MsgSeparator| highlights separator for scrolled messages |hl-QuickFixLine| |hl-Substitute| |hl-TermCursor| @@ -405,6 +408,8 @@ Other options: *'imactivatekey'* *'imak'* *'imstatusfunc'* *'imsf'* *'macatsui'* + 'maxmem' Nvim delegates memory-management to the OS. + 'maxmemtot' Nvim delegates memory-management to the OS. *'restorescreen'* *'rs'* *'norestorescreen'* *'nors'* 'shelltype' *'shortname'* *'sn'* *'noshortname'* *'nosn'* diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 4e0f145c18..f8a29fa2b3 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -52,27 +52,7 @@ endfunc au BufNewFile,BufRead $VIMRUNTIME/doc/*.txt setf help " Abaqus or Trasys -au BufNewFile,BufRead *.inp call s:Check_inp() - -func! s:Check_inp() - if getline(1) =~ '^\*' - setf abaqus - else - let n = 1 - if line("$") > 500 - let nmax = 500 - else - let nmax = line("$") - endif - while n <= nmax - if getline(n) =~? "^header surface data" - setf trasys - break - endif - let n = n + 1 - endwhile - endif -endfunc +au BufNewFile,BufRead *.inp call dist#ft#Check_inp() " A-A-P recipe au BufNewFile,BufRead *.aap setf aap @@ -170,44 +150,7 @@ au BufNewFile,BufRead */boot/grub/menu.lst,*/boot/grub/grub.conf,*/etc/grub.conf " Assembly (all kinds) " *.lst is not pure assembly, it has two extra columns (address, byte codes) -au BufNewFile,BufRead *.asm,*.[sS],*.[aA],*.mac,*.lst call s:FTasm() - -" This function checks for the kind of assembly that is wanted by the user, or -" can be detected from the first five lines of the file. -func! s:FTasm() - " make sure b:asmsyntax exists - if !exists("b:asmsyntax") - let b:asmsyntax = "" - endif - - if b:asmsyntax == "" - call s:FTasmsyntax() - endif - - " if b:asmsyntax still isn't set, default to asmsyntax or GNU - if b:asmsyntax == "" - if exists("g:asmsyntax") - let b:asmsyntax = g:asmsyntax - else - let b:asmsyntax = "asm" - endif - endif - - exe "setf " . fnameescape(b:asmsyntax) -endfunc - -func! s:FTasmsyntax() - " see if file contains any asmsyntax=foo overrides. If so, change - " b:asmsyntax appropriately - let head = " ".getline(1)." ".getline(2)." ".getline(3)." ".getline(4). - \" ".getline(5)." " - let match = matchstr(head, '\sasmsyntax=\zs[a-zA-Z0-9]\+\ze\s') - if match != '' - let b:asmsyntax = match - elseif ((head =~? '\.title') || (head =~? '\.ident') || (head =~? '\.macro') || (head =~? '\.subtitle') || (head =~? '\.library')) - let b:asmsyntax = "vmasm" - endif -endfunc +au BufNewFile,BufRead *.asm,*.[sS],*.[aA],*.mac,*.lst call dist#ft#FTasm() " Macro (VAX) au BufNewFile,BufRead *.mar setf vmasm @@ -237,17 +180,7 @@ au BufNewFile,BufRead *.awk setf awk au BufNewFile,BufRead *.mch,*.ref,*.imp setf b " BASIC or Visual Basic -au BufNewFile,BufRead *.bas call s:FTVB("basic") - -" Check if one of the first five lines contains "VB_Name". In that case it is -" probably a Visual Basic file. Otherwise it's assumed to be "alt" filetype. -func! s:FTVB(alt) - if getline(1).getline(2).getline(3).getline(4).getline(5) =~? 'VB_Name\|Begin VB\.\(Form\|MDIForm\|UserControl\)' - setf vb - else - exe "setf " . a:alt - endif -endfunc +au BufNewFile,BufRead *.bas call dist#ft#FTVB("basic") " Visual Basic Script (close to Visual Basic) or Visual Basic .NET au BufNewFile,BufRead *.vb,*.vbs,*.dsm,*.ctl setf vb @@ -265,14 +198,7 @@ au BufNewFile,BufRead *.cmd \ if getline(1) =~ '^/\*' | setf rexx | else | setf dosbatch | endif " Batch file for 4DOS -au BufNewFile,BufRead *.btm call s:FTbtm() -func! s:FTbtm() - if exists("g:dosbatch_syntax_for_btm") && g:dosbatch_syntax_for_btm - setf dosbatch - else - setf btm - endif -endfunc +au BufNewFile,BufRead *.btm call dist#ft#FTbtm() " BC calculator au BufNewFile,BufRead *.bc setf bc @@ -292,15 +218,7 @@ au BufNewFile,BufRead named*.conf,rndc*.conf,rndc*.key setf named " BIND zone au BufNewFile,BufRead named.root setf bindzone -au BufNewFile,BufRead *.db call s:BindzoneCheck('') - -func! s:BindzoneCheck(default) - if getline(1).getline(2).getline(3).getline(4) =~ '^; <<>> DiG [0-9.]\+.* <<>>\|$ORIGIN\|$TTL\|IN\s\+SOA' - setf bindzone - elseif a:default != '' - exe 'setf ' . a:default - endif -endfunc +au BufNewFile,BufRead *.db call dist#ft#BindzoneCheck('') " Blank au BufNewFile,BufRead *.bl setf blank @@ -309,27 +227,14 @@ au BufNewFile,BufRead *.bl setf blank au BufNewFile,BufRead */etc/blkid.tab,*/etc/blkid.tab.old setf xml " Bazel (http://bazel.io) -autocmd BufRead,BufNewFile *.bzl,WORKSPACE setfiletype bzl +autocmd BufRead,BufNewFile *.bzl,WORKSPACE setf bzl if has("fname_case") - autocmd BufRead,BufNewFile BUILD setfiletype bzl + " There is another check for BUILD further below. + autocmd BufRead,BufNewFile BUILD setf bzl endif " C or lpc -au BufNewFile,BufRead *.c call s:FTlpc() - -func! s:FTlpc() - if exists("g:lpc_syntax_for_c") - let lnum = 1 - while lnum <= 12 - if getline(lnum) =~# '^\(//\|inherit\|private\|protected\|nosave\|string\|object\|mapping\|mixed\)' - setf lpc - return - endif - let lnum = lnum + 1 - endwhile - endif - setf c -endfunc +au BufNewFile,BufRead *.c call dist#ft#FTlpc() " Calendar au BufNewFile,BufRead calendar setf calendar @@ -383,23 +288,7 @@ endif " .h files can be C, Ch C++, ObjC or ObjC++. " Set c_syntax_for_h if you want C, ch_syntax_for_h if you want Ch. ObjC is " detected automatically. -au BufNewFile,BufRead *.h call s:FTheader() - -func! s:FTheader() - if match(getline(1, min([line("$"), 200])), '^@\(interface\|end\|class\)') > -1 - if exists("g:c_syntax_for_h") - setf objc - else - setf objcpp - endif - elseif exists("g:c_syntax_for_h") - setf c - elseif exists("g:ch_syntax_for_h") - setf ch - else - setf cpp - endif -endfunc +au BufNewFile,BufRead *.h call dist#ft#FTheader() " Ch (CHscript) au BufNewFile,BufRead *.chf setf ch @@ -433,36 +322,7 @@ au BufNewFile,BufRead NEWS au BufNewFile,BufRead *..ch setf chill " Changes for WEB and CWEB or CHILL -au BufNewFile,BufRead *.ch call s:FTchange() - -" This function checks if one of the first ten lines start with a '@'. In -" that case it is probably a change file. -" If the first line starts with # or ! it's probably a ch file. -" If a line has "main", "include", "//" ir "/*" it's probably ch. -" Otherwise CHILL is assumed. -func! s:FTchange() - let lnum = 1 - while lnum <= 10 - if getline(lnum)[0] == '@' - setf change - return - endif - if lnum == 1 && (getline(1)[0] == '#' || getline(1)[0] == '!') - setf ch - return - endif - if getline(lnum) =~ "MODULE" - setf chill - return - endif - if getline(lnum) =~ 'main\s*(\|#\s*include\|//' - setf ch - return - endif - let lnum = lnum + 1 - endwhile - setf chill -endfunc +au BufNewFile,BufRead *.ch call dist#ft#FTchange() " ChordPro au BufNewFile,BufRead *.chopro,*.crd,*.cho,*.crdpro,*.chordpro setf chordpro @@ -474,27 +334,7 @@ au BufNewFile,BufRead *.dcl,*.icl setf clean au BufNewFile,BufRead *.eni setf cl " Clever or dtd -au BufNewFile,BufRead *.ent call s:FTent() - -func! s:FTent() - " This function checks for valid cl syntax in the first five lines. - " Look for either an opening comment, '#', or a block start, '{". - " If not found, assume SGML. - let lnum = 1 - while lnum < 6 - let line = getline(lnum) - if line =~ '^\s*[#{]' - setf cl - return - elseif line !~ '^\s*$' - " Not a blank line, not a comment, and not a block start, - " so doesn't look like valid cl code. - break - endif - let lnum = lnum + 1 - endw - setf dtd -endfunc +au BufNewFile,BufRead *.ent call dist#ft#FTent() " Clipper (or FoxPro; could also be eviews) au BufNewFile,BufRead *.prg @@ -549,19 +389,11 @@ au BufNewFile,BufRead *enlightenment/*.cfg setf c au BufNewFile,BufRead *Eterm/*.cfg setf eterm " Euphoria 3 or 4 -au BufNewFile,BufRead *.eu,*.ew,*.ex,*.exu,*.exw call s:EuphoriaCheck() +au BufNewFile,BufRead *.eu,*.ew,*.ex,*.exu,*.exw call dist#ft#EuphoriaCheck() if has("fname_case") - au BufNewFile,BufRead *.EU,*.EW,*.EX,*.EXU,*.EXW call s:EuphoriaCheck() + au BufNewFile,BufRead *.EU,*.EW,*.EX,*.EXU,*.EXW call dist#ft#EuphoriaCheck() endif -func! s:EuphoriaCheck() - if exists('g:filetype_euphoria') - exe 'setf ' . g:filetype_euphoria - else - setf euphoria3 - endif -endfunc - " Lynx config files au BufNewFile,BufRead lynx.cfg setf lynx @@ -606,19 +438,7 @@ au BufNewFile,BufRead */etc/dnsmasq.conf setf dnsmasq au BufNewFile,BufRead *.desc setf desc " the D language or dtrace -au BufNewFile,BufRead *.d call s:DtraceCheck() - -func! s:DtraceCheck() - let lines = getline(1, min([line("$"), 100])) - if match(lines, '^module\>\|^import\>') > -1 - " D files often start with a module and/or import statement. - setf d - elseif match(lines, '^#!\S\+dtrace\|#pragma\s\+D\s\+option\|:\S\{-}:\S\{-}:') > -1 - setf dtrace - else - setf d - endif -endfunc +au BufNewFile,BufRead *.d call dist#ft#DtraceCheck() " Desktop files au BufNewFile,BufRead *.desktop,.directory setf desktop @@ -650,7 +470,7 @@ au BufNewFile,BufRead *.rul \ endif " DCL (Digital Command Language - vms) or DNS zone file -au BufNewFile,BufRead *.com call s:BindzoneCheck('dcl') +au BufNewFile,BufRead *.com call dist#ft#BindzoneCheck('dcl') " DOT au BufNewFile,BufRead *.dot setf dot @@ -698,27 +518,11 @@ au BufNewFile,BufRead .editorconfig setf dosini au BufNewFile,BufRead *.ecd setf ecd " Eiffel or Specman or Euphoria -au BufNewFile,BufRead *.e,*.E call s:FTe() +au BufNewFile,BufRead *.e,*.E call dist#ft#FTe() " Elinks configuration au BufNewFile,BufRead */etc/elinks.conf,*/.elinks/elinks.conf setf elinks -func! s:FTe() - if exists('g:filetype_euphoria') - exe 'setf ' . g:filetype_euphoria - else - let n = 1 - while n < 100 && n < line("$") - if getline(n) =~ "^\\s*\\(<'\\|'>\\)\\s*$" - setf specman - return - endif - let n = n + 1 - endwhile - setf eiffel - endif -endfunc - " ERicsson LANGuage; Yaws is erlang too au BufNewFile,BufRead *.erl,*.hrl,*.yaws setf erlang @@ -887,24 +691,7 @@ au BufNewFile,BufRead *.hex,*.h32 setf hex au BufNewFile,BufRead *.t.html setf tilde " HTML (.shtml and .stm for server side) -au BufNewFile,BufRead *.html,*.htm,*.shtml,*.stm call s:FThtml() - -" Distinguish between HTML, XHTML and Django -func! s:FThtml() - let n = 1 - while n < 10 && n < line("$") - if getline(n) =~ '\<DTD\s\+XHTML\s' - setf xhtml - return - endif - if getline(n) =~ '{%\s*\(extends\|block\|load\)\>\|{#\s\+' - setf htmldjango - return - endif - let n = n + 1 - endwhile - setf html -endfunc +au BufNewFile,BufRead *.html,*.htm,*.shtml,*.stm call dist#ft#FThtml() " HTML with Ruby - eRuby au BufNewFile,BufRead *.erb,*.rhtml setf eruby @@ -931,20 +718,7 @@ au BufNewFile,BufRead *.htt,*.htb setf httest au BufNewFile,BufRead *.icn setf icon " IDL (Interface Description Language) -au BufNewFile,BufRead *.idl call s:FTidl() - -" Distinguish between standard IDL and MS-IDL -func! s:FTidl() - let n = 1 - while n < 50 && n < line("$") - if getline(n) =~ '^\s*import\s\+"\(unknwn\|objidl\)\.idl"' - setf msidl - return - endif - let n = n + 1 - endwhile - setf idl -endfunc +au BufNewFile,BufRead *.idl call dist#ft#FTidl() " Microsoft IDL (Interface Description Language) Also *.idl " MOF = WMI (Windows Management Instrumentation) Managed Object Format @@ -955,25 +729,10 @@ au BufNewFile,BufRead */.icewm/menu setf icemenu " Indent profile (must come before IDL *.pro!) au BufNewFile,BufRead .indent.pro setf indent -au BufNewFile,BufRead indent.pro call s:ProtoCheck('indent') +au BufNewFile,BufRead indent.pro call dist#ft#ProtoCheck('indent') " IDL (Interactive Data Language) -au BufNewFile,BufRead *.pro call s:ProtoCheck('idlang') - -" Distinguish between "default" and Cproto prototype file. */ -func! s:ProtoCheck(default) - " Cproto files have a comment in the first line and a function prototype in - " the second line, it always ends in ";". Indent files may also have - " comments, thus we can't match comments to see the difference. - " IDL files can have a single ';' in the second line, require at least one - " chacter before the ';'. - if getline(2) =~ '.;$' - setf cpp - else - exe 'setf ' . a:default - endif -endfunc - +au BufNewFile,BufRead *.pro call dist#ft#ProtoCheck('idlang') " Indent RC au BufNewFile,BufRead indentrc setf indent @@ -1187,51 +946,7 @@ au BufNewFile,BufRead *.markdown,*.mdown,*.mkd,*.mkdn,*.mdwn,*.md setf markdown au BufNewFile,BufRead *.mason,*.mhtml,*.comp setf mason " Mathematica, Matlab, Murphi or Objective C -au BufNewFile,BufRead *.m call s:FTm() - -func! s:FTm() - let n = 1 - let saw_comment = 0 " Whether we've seen a multiline comment leader. - while n < 100 - let line = getline(n) - if line =~ '^\s*/\*' - " /* ... */ is a comment in Objective C and Murphi, so we can't conclude - " it's either of them yet, but track this as a hint in case we don't see - " anything more definitive. - let saw_comment = 1 - endif - if line =~ '^\s*\(#\s*\(include\|import\)\>\|@import\>\|//\)' - setf objc - return - endif - if line =~ '^\s*%' - setf matlab - return - endif - if line =~ '^\s*(\*' - setf mma - return - endif - if line =~ '^\c\s*\(\(type\|var\)\>\|--\)' - setf murphi - return - endif - let n = n + 1 - endwhile - - if saw_comment - " We didn't see anything definitive, but this looks like either Objective C - " or Murphi based on the comment leader. Assume the former as it is more - " common. - setf objc - elseif exists("g:filetype_m") - " Use user specified default filetype for .m - exe "setf " . g:filetype_m - else - " Default is matlab - setf matlab - endif -endfunc +au BufNewFile,BufRead *.m call dist#ft#FTm() " Mathematica notebook au BufNewFile,BufRead *.nb setf mma @@ -1261,29 +976,11 @@ au BufNewFile,BufRead *.mgl setf mgl au BufNewFile,BufRead *.mix,*.mixal setf mix " MMIX or VMS makefile -au BufNewFile,BufRead *.mms call s:FTmms() +au BufNewFile,BufRead *.mms call dist#ft#FTmms() " Symbian meta-makefile definition (MMP) au BufNewFile,BufRead *.mmp setf mmp -func! s:FTmms() - let n = 1 - while n < 10 - let line = getline(n) - if line =~ '^\s*\(%\|//\)' || line =~ '^\*' - setf mmix - return - endif - if line =~ '^\s*#' - setf make - return - endif - let n = n + 1 - endwhile - setf mmix -endfunc - - " Modsim III (or LambdaProlog) au BufNewFile,BufRead *.mod \ if getline(1) =~ '\<module\>' | @@ -1367,33 +1064,10 @@ au BufNewFile,BufRead *.me \ setf nroff | \ endif au BufNewFile,BufRead *.tr,*.nr,*.roff,*.tmac,*.mom setf nroff -au BufNewFile,BufRead *.[1-9] call s:FTnroff() - -" This function checks if one of the first five lines start with a dot. In -" that case it is probably an nroff file: 'filetype' is set and 1 is returned. -func! s:FTnroff() - if getline(1)[0] . getline(2)[0] . getline(3)[0] . getline(4)[0] . getline(5)[0] =~ '\.' - setf nroff - return 1 - endif - return 0 -endfunc +au BufNewFile,BufRead *.[1-9] call dist#ft#FTnroff() " Nroff or Objective C++ -au BufNewFile,BufRead *.mm call s:FTmm() - -func! s:FTmm() - let n = 1 - while n < 10 - let line = getline(n) - if line =~ '^\s*\(#\s*\(include\|import\)\>\|@import\>\|/\*\)' - setf objcpp - return - endif - let n = n + 1 - endwhile - setf nroff -endfunc +au BufNewFile,BufRead *.mm call dist#ft#FTmm() " Not Quite C au BufNewFile,BufRead *.nqc setf nqc @@ -1448,28 +1122,13 @@ au BufNewFile,BufRead *.pcmk setf pcmk " Perl if has("fname_case") - au BufNewFile,BufRead *.pl,*.PL call s:FTpl() + au BufNewFile,BufRead *.pl,*.PL call dist#ft#FTpl() else - au BufNewFile,BufRead *.pl call s:FTpl() + au BufNewFile,BufRead *.pl call dist#ft#FTpl() endif au BufNewFile,BufRead *.plx,*.al,*.psgi setf perl au BufNewFile,BufRead *.p6,*.pm6,*.pl6 setf perl6 -func! s:FTpl() - if exists("g:filetype_pl") - exe "setf " . g:filetype_pl - else - " recognize Prolog by specific text in the first non-empty line - " require a blank after the '%' because Perl uses "%list" and "%translate" - let l = getline(nextnonblank(1)) - if l =~ '\<prolog\>' || l =~ '^\s*\(%\+\(\s\|$\)\|/\*\)' || l =~ ':-' - setf prolog - else - setf perl - endif - endif -endfunc - " Perl, XPM or XPM2 au BufNewFile,BufRead *.pm \ if getline(1) =~ "XPM2" | @@ -1482,7 +1141,7 @@ au BufNewFile,BufRead *.pm " Perl POD au BufNewFile,BufRead *.pod setf pod -au BufNewFile,BufRead *.pod6 setf pod6 +au BufNewFile,BufRead *.pod6 setf pod6 " Php, php3, php4, etc. " Also Phtml (was used for PHP 2 in the past) @@ -1532,29 +1191,7 @@ au BufNewFile,BufRead *.pov setf pov au BufNewFile,BufRead .povrayrc setf povini " Povray, PHP or assembly -au BufNewFile,BufRead *.inc call s:FTinc() - -func! s:FTinc() - if exists("g:filetype_inc") - exe "setf " . g:filetype_inc - else - let lines = getline(1).getline(2).getline(3) - if lines =~? "perlscript" - setf aspperl - elseif lines =~ "<%" - setf aspvbs - elseif lines =~ "<?" - setf php - else - call s:FTasmsyntax() - if exists("b:asmsyntax") - exe "setf " . fnameescape(b:asmsyntax) - else - setf pov - endif - endif - endif -endfunc +au BufNewFile,BufRead *.inc call dist#ft#FTinc() " Printcap and Termcap au BufNewFile,BufRead *printcap @@ -1583,74 +1220,13 @@ au BufNewFile,BufRead *.action setf privoxy au BufNewFile,BufRead .procmail,.procmailrc setf procmail " Progress or CWEB -au BufNewFile,BufRead *.w call s:FTprogress_cweb() - -func! s:FTprogress_cweb() - if exists("g:filetype_w") - exe "setf " . g:filetype_w - return - endif - if getline(1) =~ '&ANALYZE' || getline(3) =~ '&GLOBAL-DEFINE' - setf progress - else - setf cweb - endif -endfunc +au BufNewFile,BufRead *.w call dist#ft#FTprogress_cweb() " Progress or assembly -au BufNewFile,BufRead *.i call s:FTprogress_asm() - -func! s:FTprogress_asm() - if exists("g:filetype_i") - exe "setf " . g:filetype_i - return - endif - " This function checks for an assembly comment the first ten lines. - " If not found, assume Progress. - let lnum = 1 - while lnum <= 10 && lnum < line('$') - let line = getline(lnum) - if line =~ '^\s*;' || line =~ '^\*' - call s:FTasm() - return - elseif line !~ '^\s*$' || line =~ '^/\*' - " Not an empty line: Doesn't look like valid assembly code. - " Or it looks like a Progress /* comment - break - endif - let lnum = lnum + 1 - endw - setf progress -endfunc +au BufNewFile,BufRead *.i call dist#ft#FTprogress_asm() " Progress or Pascal -au BufNewFile,BufRead *.p call s:FTprogress_pascal() - -func! s:FTprogress_pascal() - if exists("g:filetype_p") - exe "setf " . g:filetype_p - return - endif - " This function checks for valid Pascal syntax in the first ten lines. - " Look for either an opening comment or a program start. - " If not found, assume Progress. - let lnum = 1 - while lnum <= 10 && lnum < line('$') - let line = getline(lnum) - if line =~ '^\s*\(program\|unit\|procedure\|function\|const\|type\|var\)\>' - \ || line =~ '^\s*{' || line =~ '^\s*(\*' - setf pascal - return - elseif line !~ '^\s*$' || line =~ '^/\*' - " Not an empty line: Doesn't look like valid Pascal code. - " Or it looks like a Progress /* comment - break - endif - let lnum = lnum + 1 - endw - setf progress -endfunc - +au BufNewFile,BufRead *.p call dist#ft#FTprogress_pascal() " Software Distributor Product Specification File (POSIX 1387.2-1995) au BufNewFile,BufRead *.psf setf psf @@ -1736,40 +1312,7 @@ else endif " Rexx, Rebol or R -au BufNewFile,BufRead *.r,*.R call s:FTr() - -func! s:FTr() - let max = line("$") > 50 ? 50 : line("$") - - for n in range(1, max) - " Rebol is easy to recognize, check for that first - if getline(n) =~? '\<REBOL\>' - setf rebol - return - endif - endfor - - for n in range(1, max) - " R has # comments - if getline(n) =~ '^\s*#' - setf r - return - endif - " Rexx has /* comments */ - if getline(n) =~ '^\s*/\*' - setf rexx - return - endif - endfor - - " Nothing recognized, use user default or assume Rexx - if exists("g:filetype_r") - exe "setf " . g:filetype_r - else - " Rexx used to be the default, but R appears to be much more popular. - setf r - endif -endfunc +au BufNewFile,BufRead *.r,*.R call dist#ft#FTr() " Remind au BufNewFile,BufRead .reminders,*.remind,*.rem setf remind @@ -1865,23 +1408,7 @@ au BufNewFile,BufRead *.siv setf sieve au BufNewFile,BufRead sendmail.cf setf sm " Sendmail .mc files are actually m4. Could also be MS Message text file. -au BufNewFile,BufRead *.mc call s:McSetf() - -func! s:McSetf() - " Rely on the file to start with a comment. - " MS message text files use ';', Sendmail files use '#' or 'dnl' - for lnum in range(1, min([line("$"), 20])) - let line = getline(lnum) - if line =~ '^\s*\(#\|dnl\)' - setf m4 " Sendmail .mc file - return - elseif line =~ '^\s*;' - setf msmessages " MS Message text file - return - endif - endfor - setf m4 " Default: Sendmail .mc file -endfunc +au BufNewFile,BufRead *.mc call dist#ft#McSetf() " Services au BufNewFile,BufRead */etc/services setf services @@ -1922,101 +1449,23 @@ au BufNewFile,BufRead sgml.catalog* call s:StarSetf('catalog') " Shell scripts (sh, ksh, bash, bash2, csh); Allow .profile_foo etc. " Gentoo ebuilds and Arch Linux PKGBUILDs are actually bash scripts -au BufNewFile,BufRead .bashrc*,bashrc,bash.bashrc,.bash[_-]profile*,.bash[_-]logout*,.bash[_-]aliases*,*.bash,*/{,.}bash[_-]completion{,.d,.sh}{,/*},*.ebuild,*.eclass,PKGBUILD* call SetFileTypeSH("bash") -au BufNewFile,BufRead .kshrc*,*.ksh call SetFileTypeSH("ksh") -au BufNewFile,BufRead */etc/profile,.profile*,*.sh,*.env call SetFileTypeSH(getline(1)) +au BufNewFile,BufRead .bashrc*,bashrc,bash.bashrc,.bash[_-]profile*,.bash[_-]logout*,.bash[_-]aliases*,*.bash,*/{,.}bash[_-]completion{,.d,.sh}{,/*},*.ebuild,*.eclass,PKGBUILD* call dist#ft#SetFileTypeSH("bash") +au BufNewFile,BufRead .kshrc*,*.ksh call dist#ft#SetFileTypeSH("ksh") +au BufNewFile,BufRead */etc/profile,.profile*,*.sh,*.env call dist#ft#SetFileTypeSH(getline(1)) " Shell script (Arch Linux) or PHP file (Drupal) au BufNewFile,BufRead *.install \ if getline(1) =~ '<?php' | \ setf php | \ else | - \ call SetFileTypeSH("bash") | + \ call dist#ft#SetFileTypeSH("bash") | \ endif -" Also called from scripts.vim. -func! SetFileTypeSH(name) - if expand("<amatch>") =~ g:ft_ignore_pat - return - endif - if a:name =~ '\<csh\>' - " Some .sh scripts contain #!/bin/csh. - call SetFileTypeShell("csh") - return - elseif a:name =~ '\<tcsh\>' - " Some .sh scripts contain #!/bin/tcsh. - call SetFileTypeShell("tcsh") - return - elseif a:name =~ '\<zsh\>' - " Some .sh scripts contain #!/bin/zsh. - call SetFileTypeShell("zsh") - return - elseif a:name =~ '\<ksh\>' - let b:is_kornshell = 1 - if exists("b:is_bash") - unlet b:is_bash - endif - if exists("b:is_sh") - unlet b:is_sh - endif - elseif exists("g:bash_is_sh") || a:name =~ '\<bash\>' || a:name =~ '\<bash2\>' - let b:is_bash = 1 - if exists("b:is_kornshell") - unlet b:is_kornshell - endif - if exists("b:is_sh") - unlet b:is_sh - endif - elseif a:name =~ '\<sh\>' - let b:is_sh = 1 - if exists("b:is_kornshell") - unlet b:is_kornshell - endif - if exists("b:is_bash") - unlet b:is_bash - endif - endif - call SetFileTypeShell("sh") -endfunc - -" For shell-like file types, check for an "exec" command hidden in a comment, -" as used for Tcl. -" Also called from scripts.vim, thus can't be local to this script. -func! SetFileTypeShell(name) - if expand("<amatch>") =~ g:ft_ignore_pat - return - endif - let l = 2 - while l < 20 && l < line("$") && getline(l) =~ '^\s*\(#\|$\)' - " Skip empty and comment lines. - let l = l + 1 - endwhile - if l < line("$") && getline(l) =~ '\s*exec\s' && getline(l - 1) =~ '^\s*#.*\\$' - " Found an "exec" line after a comment with continuation - let n = substitute(getline(l),'\s*exec\s\+\([^ ]*/\)\=', '', '') - if n =~ '\<tclsh\|\<wish' - setf tcl - return - endif - endif - exe "setf " . a:name -endfunc - " tcsh scripts -au BufNewFile,BufRead .tcshrc*,*.tcsh,tcsh.tcshrc,tcsh.login call SetFileTypeShell("tcsh") +au BufNewFile,BufRead .tcshrc*,*.tcsh,tcsh.tcshrc,tcsh.login call dist#ft#SetFileTypeShell("tcsh") " csh scripts, but might also be tcsh scripts (on some systems csh is tcsh) -au BufNewFile,BufRead .login*,.cshrc*,csh.cshrc,csh.login,csh.logout,*.csh,.alias call s:CSH() - -func! s:CSH() - if exists("g:filetype_csh") - call SetFileTypeShell(g:filetype_csh) - elseif &shell =~ "tcsh" - call SetFileTypeShell("tcsh") - else - call SetFileTypeShell("csh") - endif -endfunc +au BufNewFile,BufRead .login*,.cshrc*,csh.cshrc,csh.login,csh.logout,*.csh,.alias call dist#ft#CSH() " Z-Shell script au BufNewFile,BufRead .zprofile,*/etc/zprofile,.zfbfmarks setf zsh @@ -2087,42 +1536,7 @@ au BufNewFile,BufRead *.mib,*.my setf mib " Snort Configuration au BufNewFile,BufRead *.hog,snort.conf,vision.conf setf hog -au BufNewFile,BufRead *.rules call s:FTRules() - -let s:ft_rules_udev_rules_pattern = '^\s*\cudev_rules\s*=\s*"\([^"]\{-1,}\)/*".*' -func! s:FTRules() - let path = expand('<amatch>:p') - if path =~ '^/\(etc/udev/\%(rules\.d/\)\=.*\.rules\|lib/udev/\%(rules\.d/\)\=.*\.rules\)$' - setf udevrules - return - endif - if path =~ '^/etc/ufw/' - setf conf " Better than hog - return - endif - if path =~ '^/\(etc\|usr/share\)/polkit-1/rules\.d' - setf javascript - return - endif - try - let config_lines = readfile('/etc/udev/udev.conf') - catch /^Vim\%((\a\+)\)\=:E484/ - setf hog - return - endtry - let dir = expand('<amatch>:p:h') - for line in config_lines - if line =~ s:ft_rules_udev_rules_pattern - let udev_rules = substitute(line, s:ft_rules_udev_rules_pattern, '\1', "") - if dir == udev_rules - setf udevrules - endif - break - endif - endfor - setf hog -endfunc - +au BufNewFile,BufRead *.rules call dist#ft#FTRules() " Spec (Linux RPM) au BufNewFile,BufRead *.spec setf spec @@ -2146,15 +1560,7 @@ au BufNewFile,BufRead squid.conf setf squid au BufNewFile,BufRead *.tyb,*.typ,*.tyc,*.pkb,*.pks setf sql " SQL -au BufNewFile,BufRead *.sql call s:SQL() - -func! s:SQL() - if exists("g:filetype_sql") - exe "setf " . g:filetype_sql - else - setf sql - endif -endfunc +au BufNewFile,BufRead *.sql call dist#ft#SQL() " SQLJ au BufNewFile,BufRead *.sqlj setf sqlj @@ -2201,32 +1607,9 @@ au BufNewFile,BufRead */etc/sudoers,sudoers.tmp setf sudoers " SVG (Scalable Vector Graphics) au BufNewFile,BufRead *.svg setf svg -" If the file has an extension of 't' and is in a directory 't' or 'xt' then -" it is almost certainly a Perl test file. -" If the first line starts with '#' and contains 'perl' it's probably a Perl -" file. -" (Slow test) If a file contains a 'use' statement then it is almost certainly -" a Perl file. -func! s:FTperl() - let dirname = expand("%:p:h:t") - if expand("%:e") == 't' && (dirname == 't' || dirname == 'xt') - setf perl - return 1 - endif - if getline(1)[0] == '#' && getline(1) =~ 'perl' - setf perl - return 1 - endif - if search('^use\s\s*\k', 'nc', 30) - setf perl - return 1 - endif - return 0 -endfunc - " Tads (or Nroff or Perl test file) au BufNewFile,BufRead *.t - \ if !s:FTnroff() && !s:FTperl() | setf tads | endif + \ if !dist#ft#FTnroff() && !dist#ft#FTperl() | setf tads | endif " Tags au BufNewFile,BufRead tags setf tags @@ -2255,63 +1638,7 @@ au BufNewFile,BufRead *.ti setf terminfo " TeX au BufNewFile,BufRead *.latex,*.sty,*.dtx,*.ltx,*.bbl setf tex -au BufNewFile,BufRead *.tex call s:FTtex() - -" Choose context, plaintex, or tex (LaTeX) based on these rules: -" 1. Check the first line of the file for "%&<format>". -" 2. Check the first 1000 non-comment lines for LaTeX or ConTeXt keywords. -" 3. Default to "latex" or to g:tex_flavor, can be set in user's vimrc. -func! s:FTtex() - let firstline = getline(1) - if firstline =~ '^%&\s*\a\+' - let format = tolower(matchstr(firstline, '\a\+')) - let format = substitute(format, 'pdf', '', '') - if format == 'tex' - let format = 'latex' - elseif format == 'plaintex' - let format = 'plain' - endif - elseif expand('%') =~ 'tex/context/.*/.*.tex' - let format = 'context' - else - " Default value, may be changed later: - let format = exists("g:tex_flavor") ? g:tex_flavor : 'plain' - " Save position, go to the top of the file, find first non-comment line. - let save_cursor = getpos('.') - call cursor(1,1) - let firstNC = search('^\s*[^[:space:]%]', 'c', 1000) - if firstNC " Check the next thousand lines for a LaTeX or ConTeXt keyword. - let lpat = 'documentclass\>\|usepackage\>\|begin{\|newcommand\>\|renewcommand\>' - let cpat = 'start\a\+\|setup\a\+\|usemodule\|enablemode\|enableregime\|setvariables\|useencoding\|usesymbols\|stelle\a\+\|verwende\a\+\|stel\a\+\|gebruik\a\+\|usa\a\+\|imposta\a\+\|regle\a\+\|utilisemodule\>' - let kwline = search('^\s*\\\%(' . lpat . '\)\|^\s*\\\(' . cpat . '\)', - \ 'cnp', firstNC + 1000) - if kwline == 1 " lpat matched - let format = 'latex' - elseif kwline == 2 " cpat matched - let format = 'context' - endif " If neither matched, keep default set above. - " let lline = search('^\s*\\\%(' . lpat . '\)', 'cn', firstNC + 1000) - " let cline = search('^\s*\\\%(' . cpat . '\)', 'cn', firstNC + 1000) - " if cline > 0 - " let format = 'context' - " endif - " if lline > 0 && (cline == 0 || cline > lline) - " let format = 'tex' - " endif - endif " firstNC - call setpos('.', save_cursor) - endif " firstline =~ '^%&\s*\a\+' - - " Translation from formats to file types. TODO: add AMSTeX, RevTex, others? - if format == 'plain' - setf plaintex - elseif format == 'context' - setf context - else " probably LaTeX - setf tex - endif - return -endfunc +au BufNewFile,BufRead *.tex call dist#ft#FTtex() " ConTeXt au BufNewFile,BufRead *.mkii,*.mkiv,*.mkvi setf context @@ -2412,7 +1739,7 @@ au BufRead,BufNewFile *.hw,*.module,*.pkg \ endif " Visual Basic (also uses *.bas) or FORM -au BufNewFile,BufRead *.frm call s:FTVB("form") +au BufNewFile,BufRead *.frm call dist#ft#FTVB("form") " SaxBasic is close to Visual Basic au BufNewFile,BufRead *.sba setf vb @@ -2502,36 +1829,10 @@ au BufNewFile,BufRead .Xdefaults,.Xpdefaults,.Xresources,xdm-config,*.ad setf xd " Xmath au BufNewFile,BufRead *.msc,*.msf setf xmath au BufNewFile,BufRead *.ms - \ if !s:FTnroff() | setf xmath | endif + \ if !dist#ft#FTnroff() | setf xmath | endif " XML specific variants: docbk and xbl -au BufNewFile,BufRead *.xml call s:FTxml() - -func! s:FTxml() - let n = 1 - while n < 100 && n < line("$") - let line = getline(n) - " DocBook 4 or DocBook 5. - let is_docbook4 = line =~ '<!DOCTYPE.*DocBook' - let is_docbook5 = line =~ ' xmlns="http://docbook.org/ns/docbook"' - if is_docbook4 || is_docbook5 - let b:docbk_type = "xml" - if is_docbook5 - let b:docbk_ver = 5 - else - let b:docbk_ver = 4 - endif - setf docbk - return - endif - if line =~ 'xmlns:xbl="http://www.mozilla.org/xbl"' - setf xbl - return - endif - let n += 1 - endwhile - setf xml -endfunc +au BufNewFile,BufRead *.xml call dist#ft#FTxml() " XMI (holding UML models) is also XML au BufNewFile,BufRead *.xmi setf xml @@ -2574,25 +1875,7 @@ au BufNewFile,BufRead *.xsl,*.xslt setf xslt au BufNewFile,BufRead *.yy,*.yxx,*.y++ setf yacc " Yacc or racc -au BufNewFile,BufRead *.y call s:FTy() - -func! s:FTy() - let n = 1 - while n < 100 && n < line("$") - let line = getline(n) - if line =~ '^\s*%' - setf yacc - return - endif - if getline(n) =~ '^\s*\(#\|class\>\)' && getline(n) !~ '^\s*#\s*include' - setf racc - return - endif - let n = n + 1 - endwhile - setf yacc -endfunc - +au BufNewFile,BufRead *.y call dist#ft#FTy() " Yaml au BufNewFile,BufRead *.yaml,*.yml setf yaml @@ -2608,9 +1891,9 @@ au BufNewFile,BufRead *.zut setf zimbutempl " Zope " dtml (zope dynamic template markup language), pt (zope page template), " cpt (zope form controller page template) -au BufNewFile,BufRead *.dtml,*.pt,*.cpt call s:FThtml() +au BufNewFile,BufRead *.dtml,*.pt,*.cpt call dist#ft#FThtml() " zsql (zope sql method) -au BufNewFile,BufRead *.zsql call s:SQL() +au BufNewFile,BufRead *.zsql call dist#ft#SQL() " Z80 assembler asz80 au BufNewFile,BufRead *.z8a setf z8a @@ -2652,6 +1935,11 @@ au BufNewFile,BufRead *asterisk*/*voicemail.conf* call s:StarSetf('asteriskvm') " Bazaar version control au BufNewFile,BufRead bzr_log.* setf bzr +" Bazel build file +if !has("fname_case") + au BufNewFile,BufRead BUILD setf bzl +endif + " BIND zone au BufNewFile,BufRead */named/db.*,*/bind/db.* call s:StarSetf('bindzone') @@ -2759,17 +2047,7 @@ au BufNewFile,BufRead *termcap* " ReDIF " Only used when the .rdf file was not detected to be XML. -au BufRead,BufNewFile *.rdf call s:Redif() -func! s:Redif() - let lnum = 1 - while lnum <= 5 && lnum < line('$') - if getline(lnum) =~ "^\ctemplate-type:" - setf redif - return - endif - let lnum = lnum + 1 - endwhile -endfunc +au BufRead,BufNewFile *.rdf call dist#ft#Redif() " Remind au BufNewFile,BufRead .reminders* call s:StarSetf('remind') diff --git a/runtime/optwin.vim b/runtime/optwin.vim index 13d0db0390..81eb3f11fd 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -996,10 +996,6 @@ call append("$", "updatecount\tnumber of characters typed to cause a swap file u call append("$", " \tset uc=" . &uc) call append("$", "updatetime\ttime in msec after which the swap file will be updated") call append("$", " \tset ut=" . &ut) -call append("$", "maxmem\tmaximum amount of memory in Kbyte used for one buffer") -call append("$", " \tset mm=" . &mm) -call append("$", "maxmemtot\tmaximum amount of memory in Kbyte used for all buffers") -call append("$", " \tset mmt=" . &mmt) call <SID>Header("command line editing") diff --git a/runtime/scripts.vim b/runtime/scripts.vim index 63bf9efcf9..18263e2842 100644 --- a/runtime/scripts.vim +++ b/runtime/scripts.vim @@ -1,11 +1,15 @@ " Vim support file to detect file types in scripts " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last change: 2017 Aug 27 +" Last change: 2017 Nov 11 " This file is called by an autocommand for every file that has just been " loaded into a buffer. It checks if the type of file can be recognized by " the file contents. The autocommand is in $VIMRUNTIME/filetype.vim. +" +" Note that the pattern matches are done with =~# to avoid the value of the +" 'ignorecase' option making a difference. Where case is to be ignored use +" =~? instead. Do not use =~ anywhere. " Only do the rest when the FileType autocommand has not been triggered yet. @@ -28,12 +32,12 @@ set cpo&vim let s:line1 = getline(1) -if s:line1 =~ "^#!" +if s:line1 =~# "^#!" " A script that starts with "#!". " Check for a line like "#!/usr/bin/env VAR=val bash". Turn it into " "#!/usr/bin/bash" to make matching easier. - if s:line1 =~ '^#!\s*\S*\<env\s' + if s:line1 =~# '^#!\s*\S*\<env\s' let s:line1 = substitute(s:line1, '\S\+=\S\+', '', 'g') let s:line1 = substitute(s:line1, '\<env\s\+', '', '') endif @@ -44,11 +48,11 @@ if s:line1 =~ "^#!" " "#!/usr/bin/env perl [path/args]" " If there is no path use the first word: "#!perl [path/args]". " Otherwise get the last word after a slash: "#!/usr/bin/perl [path/args]". - if s:line1 =~ '^#!\s*\a:[/\\]' + if s:line1 =~# '^#!\s*\a:[/\\]' let s:name = substitute(s:line1, '^#!.*[/\\]\(\i\+\).*', '\1', '') - elseif s:line1 =~ '^#!.*\<env\>' + elseif s:line1 =~# '^#!.*\<env\>' let s:name = substitute(s:line1, '^#!.*\<env\>\s\+\(\i\+\).*', '\1', '') - elseif s:line1 =~ '^#!\s*[^/\\ ]*\>\([^/\\]\|$\)' + elseif s:line1 =~# '^#!\s*[^/\\ ]*\>\([^/\\]\|$\)' let s:name = substitute(s:line1, '^#!\s*\([^/\\ ]*\>\).*', '\1', '') else let s:name = substitute(s:line1, '^#!\s*\S*[/\\]\(\i\+\).*', '\1', '') @@ -56,116 +60,116 @@ if s:line1 =~ "^#!" " tcl scripts may have #!/bin/sh in the first line and "exec wish" in the " third line. Suggested by Steven Atkinson. - if getline(3) =~ '^exec wish' + if getline(3) =~# '^exec wish' let s:name = 'wish' endif " Bourne-like shell scripts: bash bash2 ksh ksh93 sh - if s:name =~ '^\(bash\d*\|\|ksh\d*\|sh\)\>' - call SetFileTypeSH(s:line1) " defined in filetype.vim + if s:name =~# '^\(bash\d*\|\|ksh\d*\|sh\)\>' + call dist#ft#SetFileTypeSH(s:line1) " defined in filetype.vim " csh scripts - elseif s:name =~ '^csh\>' + elseif s:name =~# '^csh\>' if exists("g:filetype_csh") - call SetFileTypeShell(g:filetype_csh) + call dist#ft#SetFileTypeShell(g:filetype_csh) else - call SetFileTypeShell("csh") + call dist#ft#SetFileTypeShell("csh") endif " tcsh scripts - elseif s:name =~ '^tcsh\>' - call SetFileTypeShell("tcsh") + elseif s:name =~# '^tcsh\>' + call dist#ft#SetFileTypeShell("tcsh") " Z shell scripts - elseif s:name =~ '^zsh\>' + elseif s:name =~# '^zsh\>' set ft=zsh " TCL scripts - elseif s:name =~ '^\(tclsh\|wish\|expectk\|itclsh\|itkwish\)\>' + elseif s:name =~# '^\(tclsh\|wish\|expectk\|itclsh\|itkwish\)\>' set ft=tcl " Expect scripts - elseif s:name =~ '^expect\>' + elseif s:name =~# '^expect\>' set ft=expect " Gnuplot scripts - elseif s:name =~ '^gnuplot\>' + elseif s:name =~# '^gnuplot\>' set ft=gnuplot " Makefiles - elseif s:name =~ 'make\>' + elseif s:name =~# 'make\>' set ft=make " Lua - elseif s:name =~ 'lua' + elseif s:name =~# 'lua' set ft=lua " Perl 6 - elseif s:name =~ 'perl6' + elseif s:name =~# 'perl6' set ft=perl6 " Perl - elseif s:name =~ 'perl' + elseif s:name =~# 'perl' set ft=perl " PHP - elseif s:name =~ 'php' + elseif s:name =~# 'php' set ft=php " Python - elseif s:name =~ 'python' + elseif s:name =~# 'python' set ft=python " Groovy - elseif s:name =~ '^groovy\>' + elseif s:name =~# '^groovy\>' set ft=groovy " Ruby - elseif s:name =~ 'ruby' + elseif s:name =~# 'ruby' set ft=ruby " JavaScript - elseif s:name =~ 'node\(js\)\=\>' || s:name =~ 'rhino\>' + elseif s:name =~# 'node\(js\)\=\>' || s:name =~# 'rhino\>' set ft=javascript " BC calculator - elseif s:name =~ '^bc\>' + elseif s:name =~# '^bc\>' set ft=bc " sed - elseif s:name =~ 'sed\>' + elseif s:name =~# 'sed\>' set ft=sed " OCaml-scripts - elseif s:name =~ 'ocaml' + elseif s:name =~# 'ocaml' set ft=ocaml " Awk scripts - elseif s:name =~ 'awk\>' + elseif s:name =~# 'awk\>' set ft=awk " Website MetaLanguage - elseif s:name =~ 'wml' + elseif s:name =~# 'wml' set ft=wml " Scheme scripts - elseif s:name =~ 'scheme' + elseif s:name =~# 'scheme' set ft=scheme " CFEngine scripts - elseif s:name =~ 'cfengine' + elseif s:name =~# 'cfengine' set ft=cfengine " Erlang scripts - elseif s:name =~ 'escript' + elseif s:name =~# 'escript' set ft=erlang " Haskell - elseif s:name =~ 'haskell' + elseif s:name =~# 'haskell' set ft=haskell " Scala - elseif s:name =~ 'scala\>' + elseif s:name =~# 'scala\>' set ft=scala endif @@ -180,28 +184,28 @@ else let s:line5 = getline(5) " Bourne-like shell scripts: sh ksh bash bash2 - if s:line1 =~ '^:$' - call SetFileTypeSH(s:line1) " defined in filetype.vim + if s:line1 =~# '^:$' + call dist#ft#SetFileTypeSH(s:line1) " defined in filetype.vim " Z shell scripts - elseif s:line1 =~ '^#compdef\>' || s:line1 =~ '^#autoload\>' || - \ "\n".s:line1."\n".s:line2."\n".s:line3."\n".s:line4."\n".s:line5 =~ '\n\s*emulate\s\+\%(-[LR]\s\+\)\=[ckz]\=sh\>' + elseif s:line1 =~# '^#compdef\>' || s:line1 =~# '^#autoload\>' || + \ "\n".s:line1."\n".s:line2."\n".s:line3."\n".s:line4."\n".s:line5 =~# '\n\s*emulate\s\+\%(-[LR]\s\+\)\=[ckz]\=sh\>' set ft=zsh " ELM Mail files - elseif s:line1 =~ '^From \([a-zA-Z][a-zA-Z_0-9\.=-]*\(@[^ ]*\)\=\|-\) .* \(19\|20\)\d\d$' + elseif s:line1 =~# '^From \([a-zA-Z][a-zA-Z_0-9\.=-]*\(@[^ ]*\)\=\|-\) .* \(19\|20\)\d\d$' set ft=mail " Mason - elseif s:line1 =~ '^<[%&].*>' + elseif s:line1 =~# '^<[%&].*>' set ft=mason " Vim scripts (must have '" vim' as the first line to trigger this) - elseif s:line1 =~ '^" *[vV]im$' + elseif s:line1 =~# '^" *[vV]im$' set ft=vim " MOO - elseif s:line1 =~ '^\*\* LambdaMOO Database, Format Version \%([1-3]\>\)\@!\d\+ \*\*$' + elseif s:line1 =~# '^\*\* LambdaMOO Database, Format Version \%([1-3]\>\)\@!\d\+ \*\*$' set ft=moo " Diff file: @@ -215,40 +219,45 @@ else " - "=== ", "--- ", "+++ " (bzr diff, common case) " - "=== (removed|added|renamed|modified)" (bzr diff, alternative) " - "# HG changeset patch" in first line (Mercurial export format) - elseif s:line1 =~ '^\(diff\>\|Only in \|\d\+\(,\d\+\)\=[cda]\d\+\>\|# It was generated by makepatch \|Index:\s\+\f\+\r\=$\|===== \f\+ \d\+\.\d\+ vs edited\|==== //\f\+#\d\+\|# HG changeset patch\)' - \ || (s:line1 =~ '^--- ' && s:line2 =~ '^+++ ') - \ || (s:line1 =~ '^\* looking for ' && s:line2 =~ '^\* comparing to ') - \ || (s:line1 =~ '^\*\*\* ' && s:line2 =~ '^--- ') - \ || (s:line1 =~ '^=== ' && ((s:line2 =~ '^=\{66\}' && s:line3 =~ '^--- ' && s:line4 =~ '^+++') || (s:line2 =~ '^--- ' && s:line3 =~ '^+++ '))) - \ || (s:line1 =~ '^=== \(removed\|added\|renamed\|modified\)') + elseif s:line1 =~# '^\(diff\>\|Only in \|\d\+\(,\d\+\)\=[cda]\d\+\>\|# It was generated by makepatch \|Index:\s\+\f\+\r\=$\|===== \f\+ \d\+\.\d\+ vs edited\|==== //\f\+#\d\+\|# HG changeset patch\)' + \ || (s:line1 =~# '^--- ' && s:line2 =~# '^+++ ') + \ || (s:line1 =~# '^\* looking for ' && s:line2 =~# '^\* comparing to ') + \ || (s:line1 =~# '^\*\*\* ' && s:line2 =~# '^--- ') + \ || (s:line1 =~# '^=== ' && ((s:line2 =~# '^=\{66\}' && s:line3 =~# '^--- ' && s:line4 =~# '^+++') || (s:line2 =~# '^--- ' && s:line3 =~# '^+++ '))) + \ || (s:line1 =~# '^=== \(removed\|added\|renamed\|modified\)') set ft=diff " PostScript Files (must have %!PS as the first line, like a2ps output) - elseif s:line1 =~ '^%![ \t]*PS' + elseif s:line1 =~# '^%![ \t]*PS' set ft=postscr " M4 scripts: Guess there is a line that starts with "dnl". - elseif s:line1 =~ '^\s*dnl\>' - \ || s:line2 =~ '^\s*dnl\>' - \ || s:line3 =~ '^\s*dnl\>' - \ || s:line4 =~ '^\s*dnl\>' - \ || s:line5 =~ '^\s*dnl\>' + elseif s:line1 =~# '^\s*dnl\>' + \ || s:line2 =~# '^\s*dnl\>' + \ || s:line3 =~# '^\s*dnl\>' + \ || s:line4 =~# '^\s*dnl\>' + \ || s:line5 =~# '^\s*dnl\>' set ft=m4 + " AmigaDos scripts + elseif $TERM == "amiga" + \ && (s:line1 =~# "^;" || s:line1 =~? '^\.bra') + set ft=amiga + " SiCAD scripts (must have procn or procd as the first line to trigger this) elseif s:line1 =~? '^ *proc[nd] *$' set ft=sicad " Purify log files start with "**** Purify" - elseif s:line1 =~ '^\*\*\*\* Purify' + elseif s:line1 =~# '^\*\*\*\* Purify' set ft=purifylog " XML - elseif s:line1 =~ '<?\s*xml.*?>' + elseif s:line1 =~# '<?\s*xml.*?>' set ft=xml " XHTML (e.g.: PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN") - elseif s:line1 =~ '\<DTD\s\+XHTML\s' + elseif s:line1 =~# '\<DTD\s\+XHTML\s' set ft=xhtml " HTML (e.g.: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN") @@ -257,43 +266,43 @@ else set ft=html " PDF - elseif s:line1 =~ '^%PDF-' + elseif s:line1 =~# '^%PDF-' set ft=pdf " XXD output - elseif s:line1 =~ '^\x\{7}: \x\{2} \=\x\{2} \=\x\{2} \=\x\{2} ' + elseif s:line1 =~# '^\x\{7}: \x\{2} \=\x\{2} \=\x\{2} \=\x\{2} ' set ft=xxd " RCS/CVS log output - elseif s:line1 =~ '^RCS file:' || s:line2 =~ '^RCS file:' + elseif s:line1 =~# '^RCS file:' || s:line2 =~# '^RCS file:' set ft=rcslog " CVS commit - elseif s:line2 =~ '^CVS:' || getline("$") =~ '^CVS: ' + elseif s:line2 =~# '^CVS:' || getline("$") =~# '^CVS: ' set ft=cvs " Prescribe - elseif s:line1 =~ '^!R!' + elseif s:line1 =~# '^!R!' set ft=prescribe " Send-pr - elseif s:line1 =~ '^SEND-PR:' + elseif s:line1 =~# '^SEND-PR:' set ft=sendpr " SNNS files - elseif s:line1 =~ '^SNNS network definition file' + elseif s:line1 =~# '^SNNS network definition file' set ft=snnsnet - elseif s:line1 =~ '^SNNS pattern definition file' + elseif s:line1 =~# '^SNNS pattern definition file' set ft=snnspat - elseif s:line1 =~ '^SNNS result file' + elseif s:line1 =~# '^SNNS result file' set ft=snnsres " Virata - elseif s:line1 =~ '^%.\{-}[Vv]irata' - \ || s:line2 =~ '^%.\{-}[Vv]irata' - \ || s:line3 =~ '^%.\{-}[Vv]irata' - \ || s:line4 =~ '^%.\{-}[Vv]irata' - \ || s:line5 =~ '^%.\{-}[Vv]irata' + elseif s:line1 =~# '^%.\{-}[Vv]irata' + \ || s:line2 =~# '^%.\{-}[Vv]irata' + \ || s:line3 =~# '^%.\{-}[Vv]irata' + \ || s:line4 =~# '^%.\{-}[Vv]irata' + \ || s:line5 =~# '^%.\{-}[Vv]irata' set ft=virata " Strace @@ -301,17 +310,17 @@ else set ft=strace " VSE JCL - elseif s:line1 =~ '^\* $$ JOB\>' || s:line1 =~ '^// *JOB\>' + elseif s:line1 =~# '^\* $$ JOB\>' || s:line1 =~# '^// *JOB\>' set ft=vsejcl " TAK and SINDA - elseif s:line4 =~ 'K & K Associates' || s:line2 =~ 'TAK 2000' + elseif s:line4 =~# 'K & K Associates' || s:line2 =~# 'TAK 2000' set ft=takout - elseif s:line3 =~ 'S Y S T E M S I M P R O V E D ' + elseif s:line3 =~# 'S Y S T E M S I M P R O V E D ' set ft=sindaout - elseif getline(6) =~ 'Run Date: ' + elseif getline(6) =~# 'Run Date: ' set ft=takcmp - elseif getline(9) =~ 'Node File 1' + elseif getline(9) =~# 'Node File 1' set ft=sindacmp " DNS zone files @@ -319,34 +328,34 @@ else set ft=bindzone " BAAN - elseif s:line1 =~ '|\*\{1,80}' && s:line2 =~ 'VRC ' - \ || s:line2 =~ '|\*\{1,80}' && s:line3 =~ 'VRC ' + elseif s:line1 =~# '|\*\{1,80}' && s:line2 =~# 'VRC ' + \ || s:line2 =~# '|\*\{1,80}' && s:line3 =~# 'VRC ' set ft=baan " Valgrind - elseif s:line1 =~ '^==\d\+== valgrind' || s:line3 =~ '^==\d\+== Using valgrind' + elseif s:line1 =~# '^==\d\+== valgrind' || s:line3 =~# '^==\d\+== Using valgrind' set ft=valgrind " Go docs - elseif s:line1 =~ '^PACKAGE DOCUMENTATION$' + elseif s:line1 =~# '^PACKAGE DOCUMENTATION$' set ft=godoc " Renderman Interface Bytestream - elseif s:line1 =~ '^##RenderMan' + elseif s:line1 =~# '^##RenderMan' set ft=rib " Scheme scripts - elseif s:line1 =~ 'exec\s\+\S*scheme' || s:line2 =~ 'exec\s\+\S*scheme' + elseif s:line1 =~# 'exec\s\+\S*scheme' || s:line2 =~# 'exec\s\+\S*scheme' set ft=scheme " Git output - elseif s:line1 =~ '^\(commit\|tree\|object\) \x\{40\}\>\|^tag \S\+$' + elseif s:line1 =~# '^\(commit\|tree\|object\) \x\{40\}\>\|^tag \S\+$' set ft=git " Gprof (gnu profiler) elseif s:line1 == 'Flat profile:' \ && s:line2 == '' - \ && s:line3 =~ '^Each sample counts as .* seconds.$' + \ && s:line3 =~# '^Each sample counts as .* seconds.$' set ft=gprof " Erlang terms @@ -357,18 +366,18 @@ else " CVS diff else let s:lnum = 1 - while getline(s:lnum) =~ "^? " && s:lnum < line("$") + while getline(s:lnum) =~# "^? " && s:lnum < line("$") let s:lnum += 1 endwhile - if getline(s:lnum) =~ '^Index:\s\+\f\+$' + if getline(s:lnum) =~# '^Index:\s\+\f\+$' set ft=diff " locale input files: Formal Definitions of Cultural Conventions " filename must be like en_US, fr_FR@euro or en_US.UTF-8 - elseif expand("%") =~ '\a\a_\a\a\($\|[.@]\)\|i18n$\|POSIX$\|translit_' + elseif expand("%") =~# '\a\a_\a\a\($\|[.@]\)\|i18n$\|POSIX$\|translit_' let s:lnum = 1 while s:lnum < 100 && s:lnum < line("$") - if getline(s:lnum) =~ '^LC_\(IDENTIFICATION\|CTYPE\|COLLATE\|MONETARY\|NUMERIC\|TIME\|MESSAGES\|PAPER\|TELEPHONE\|MEASUREMENT\|NAME\|ADDRESS\)$' + if getline(s:lnum) =~# '^LC_\(IDENTIFICATION\|CTYPE\|COLLATE\|MONETARY\|NUMERIC\|TIME\|MESSAGES\|PAPER\|TELEPHONE\|MEASUREMENT\|NAME\|ADDRESS\)$' setf fdcc break endif diff --git a/runtime/syntax/dircolors.vim b/runtime/syntax/dircolors.vim index 1b598c39b5..3d7f63dc55 100644 --- a/runtime/syntax/dircolors.vim +++ b/runtime/syntax/dircolors.vim @@ -32,7 +32,7 @@ syntax match dircolorsEscape '\\[abefnrtv?_\\^#]' syntax match dircolorsEscape '\\[0-9]\{3}' syntax match dircolorsEscape '\\x[0-9a-f]\{3}' -if !has('gui_running') && &t_Co == '' +if !(has('gui_running') || &termguicolors) && &t_Co == '' syntax match dircolorsNumber '\<\d\+\>' highlight default link dircolorsNumber Number endif @@ -84,7 +84,7 @@ endfunction function! s:get_hi_str(color, place) abort if a:color >= 0 && a:color <= 255 - if has('gui_running') + if has('gui_running') || &termguicolors return ' gui' . a:place . '=' . s:termguicolors[a:color] elseif a:color <= 7 || &t_Co == 256 || &t_Co == 88 return ' cterm' . a:place . '=' . a:color @@ -169,7 +169,7 @@ function! s:preview_color(linenr) abort \ ' "\_s\zs' . colordef . '\ze\_s"' let hi_attrs_str = '' if !empty(hi_attrs) - if has('gui_running') + if has('gui_running') || &termguicolors let hi_attrs_str = ' gui=' . join(hi_attrs, ',') else let hi_attrs_str = ' cterm=' . join(hi_attrs, ',') @@ -199,11 +199,11 @@ endfunction let b:dc_next_index = 0 -if has('gui_running') +if has('gui_running') || &termguicolors call s:set_guicolors() endif -if has('gui_running') || &t_Co != '' +if has('gui_running') || &termguicolors || &t_Co != '' call s:reset_colors() autocmd CursorMoved,CursorMovedI <buffer> call s:preview_color('.') diff --git a/scripts/pvscheck.sh b/scripts/pvscheck.sh index 314966f6aa..11b672c515 100755 --- a/scripts/pvscheck.sh +++ b/scripts/pvscheck.sh @@ -21,13 +21,14 @@ help() { echo 'Usage:' echo ' pvscheck.sh [--pvs URL] [--deps] [--environment-cc]' echo ' [target-directory [branch]]' - echo ' pvscheck.sh [--pvs URL] [--recheck] [--environment-cc]' + echo ' pvscheck.sh [--pvs URL] [--recheck] [--environment-cc] [--update]' echo ' [target-directory]' echo ' pvscheck.sh [--pvs URL] --only-analyse [target-directory]' echo ' pvscheck.sh [--pvs URL] --pvs-install {target-directory}' echo ' pvscheck.sh --patch [--only-build]' echo echo ' --pvs: Fetch pvs-studio from URL.' + echo echo ' --pvs detect: Auto-detect latest version (by scraping viva64.com).' echo echo ' --deps: (for regular run) Use top-level Makefile and build deps.' @@ -47,6 +48,8 @@ help() { echo echo ' --recheck: run analysis on a prepared target directory.' echo + echo ' --update: when rechecking first do a pull.' + echo echo ' --only-analyse: run analysis on a prepared target directory ' echo ' without building Neovim.' echo @@ -302,8 +305,16 @@ create_compile_commands() {( # realpath is not available in Ubuntu trusty yet. realdir() {( local dir="$1" - cd "$dir" - printf '%s\n' "$PWD" + local add="" + while ! cd "$dir" 2>/dev/null ; do + add="${dir##*/}/$add" + local new_dir="${dir%/*}" + if test "$new_dir" = "$dir" ; then + return 1 + fi + dir="$new_dir" + done + printf '%s\n' "$PWD/$add" )} patch_sources() {( @@ -353,9 +364,12 @@ run_analysis() {( --file build/compile_commands.json \ --sourcetree-root . || true - plog-converter -t xml -o PVS-studio.xml PVS-studio.log - plog-converter -t errorfile -o PVS-studio.err PVS-studio.log - plog-converter -t tasklist -o PVS-studio.tsk PVS-studio.log + rm -rf PVS-studio.{xml,err,tsk,html.d} + local plog_args="PVS-studio.log --srcRoot . --excludedCodes V011" + plog-converter $plog_args --renderTypes xml --output PVS-studio.xml + plog-converter $plog_args --renderTypes errorfile --output PVS-studio.err + plog-converter $plog_args --renderTypes tasklist --output PVS-studio.tsk + plog-converter $plog_args --renderTypes fullhtml --output PVS-studio.html.d )} detect_url() { @@ -389,13 +403,24 @@ do_check() { install_pvs "$tgt" "$pvs_url" - do_recheck "$tgt" "$deps" "$environment_cc" + do_recheck "$tgt" "$deps" "$environment_cc" "" } do_recheck() { local tgt="$1" ; shift local deps="$1" ; shift local environment_cc="$1" ; shift + local update="$1" ; shift + + if test -n "$update" ; then + ( + cd "$tgt" + local branch="$(git rev-parse --abbrev-ref HEAD)" + git checkout --detach + git fetch -f origin "${branch}:${branch}" + git checkout -f "$branch" + ) + fi create_compile_commands "$tgt" "$deps" "$environment_cc" @@ -427,6 +452,7 @@ main() { pvs-install store_const \ deps store_const \ environment-cc store_const \ + update store_const \ -- \ 'modify realdir tgt "$PWD/../neovim-pvs"' \ 'store branch master' \ @@ -445,7 +471,7 @@ main() { elif test -n "$pvs_install" ; then install_pvs "$tgt" "$pvs_url" elif test -n "$recheck" ; then - do_recheck "$tgt" "$deps" "$environment_cc" + do_recheck "$tgt" "$deps" "$environment_cc" "$update" elif test -n "$only_analyse" ; then do_analysis "$tgt" else diff --git a/scripts/run-api-tests.exp b/scripts/run-api-tests.exp deleted file mode 100755 index 27c9c963e5..0000000000 --- a/scripts/run-api-tests.exp +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env expect - -if {$argc < 2} { - puts "Need commands for running the tests and for starting nvim" - exit 1 -} - -set timeout 60 -set run_tests [split [lindex $argv 0] " "] -set run_nvim [split [lindex $argv 1] " "] - -# don't echo to stdout -log_user 0 -# set NVIM_LISTEN_ADDRESS, so nvim will listen on a known socket -set env(NVIM_LISTEN_ADDRESS) "/tmp/nvim-[exec date +%s%N].sock" -# start nvim -spawn {*}$run_nvim -# save the job descriptor -set nvim_id $spawn_id -# Reset function that can be invoked by test runners to put nvim in a cleaner -# state -send { -:echo "read"."y" -} -# wait until nvim is ready -expect "ready" -# run tests -spawn {*}$run_tests -set tests_id $spawn_id -set status 1 -# listen for test output in the background -expect_background { - * { - # show test output to the user - send_user -- $expect_out(buffer) - } - eof { - # collect the exit status code - set spawn_id $tests_id - catch wait result - set status [lindex $result 3] - set spawn_id $nvim_id - # quit nvim - send ":qa!\r" - } -} -# switch back nvim and wait until it exits -set spawn_id $nvim_id -expect eof -exit $status diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index 21f11fbf1c..5182756d83 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -100,9 +100,9 @@ get_vim_sources() { commit_message() { if [[ -n "$vim_tag" ]]; then - printf '%s\n\n%s' "${vim_message}" "${vim_commit_url}" + printf '%s\n%s' "${vim_message}" "${vim_commit_url}" else - printf 'vim-patch:%s\n\n%s\n\n%s' "$vim_version" "$vim_message" "$vim_commit_url" + printf 'vim-patch:%s\n\n%s\n%s' "$vim_version" "$vim_message" "$vim_commit_url" fi } diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 0e174c3c61..2d803792c8 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -175,7 +175,7 @@ endif() get_directory_property(gen_cdefs COMPILE_DEFINITIONS) foreach(gen_cdef ${gen_cdefs} DO_NOT_DEFINE_EMPTY_ATTRIBUTES) - if(NOT "${gen_cdef}" MATCHES "INCLUDE_GENERATED_DECLARATIONS") + if(NOT ${gen_cdef} MATCHES "INCLUDE_GENERATED_DECLARATIONS") list(APPEND gen_cflags "-D${gen_cdef}") endif() endforeach() @@ -359,6 +359,10 @@ endforeach() # Our dependencies come first. +if (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + list(APPEND NVIM_LINK_LIBRARIES pthread c++abi) +endif() + if (LibIntl_FOUND) list(APPEND NVIM_LINK_LIBRARIES ${LibIntl_LIBRARY}) endif() @@ -437,6 +441,7 @@ if(WIN32) 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/winpty-agent.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/xxd.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/ @@ -455,6 +460,8 @@ if(WIN32) 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) +else() + add_custom_target(nvim_runtime_deps) # Stub target to avoid CMP0046. endif() add_library( diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index af723639c5..fa4ad27e60 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -24,6 +24,7 @@ #include "nvim/syntax.h" #include "nvim/window.h" #include "nvim/undo.h" +#include "nvim/ex_docmd.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/buffer.c.generated.h" @@ -186,12 +187,12 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, for (size_t i = 0; i < rv.size; i++) { int64_t lnum = start + (int64_t)i; - if (lnum > LONG_MAX) { + if (lnum >= MAXLNUM) { api_set_error(err, kErrorTypeValidation, "Line index is too high"); goto end; } - const char *bufstr = (char *) ml_get_buf(buf, (linenr_T) lnum, false); + const char *bufstr = (char *)ml_get_buf(buf, (linenr_T)lnum, false); Object str = STRING_OBJ(cstr_to_string(bufstr)); // Vim represents NULs as NLs, but this may confuse clients. @@ -360,7 +361,7 @@ void nvim_buf_set_lines(uint64_t channel_id, for (size_t i = 0; i < to_replace; i++) { int64_t lnum = start + (int64_t)i; - if (lnum > LONG_MAX) { + if (lnum >= MAXLNUM) { api_set_error(err, kErrorTypeValidation, "Index value is too high"); goto end; } @@ -378,7 +379,7 @@ void nvim_buf_set_lines(uint64_t channel_id, for (size_t i = to_replace; i < new_len; i++) { int64_t lnum = start + (int64_t)i - 1; - if (lnum > LONG_MAX) { + if (lnum >= MAXLNUM) { api_set_error(err, kErrorTypeValidation, "Index value is too high"); goto end; } @@ -458,16 +459,15 @@ Integer nvim_buf_get_changedtick(Buffer buffer, Error *err) return buf->b_changedtick; } -/// Gets a list of dictionaries describing buffer-local mappings. -/// The "buffer" key in the returned dictionary reflects the buffer -/// handle where the mapping is present. +/// Gets a list of buffer-local |mapping| definitions. /// /// @param mode Mode short-name ("n", "i", "v", ...) /// @param buffer Buffer handle /// @param[out] err Error details, if any -/// @returns Array of maparg()-like dictionaries describing mappings +/// @returns Array of maparg()-like dictionaries describing mappings. +/// The "buffer" key holds the associated buffer handle. ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err) - FUNC_API_SINCE(3) + FUNC_API_SINCE(3) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -478,6 +478,46 @@ ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err) return keymap_array(mode, buf); } +/// Gets a map of buffer-local |user-commands|. +/// +/// @param buffer Buffer handle. +/// @param opts Optional parameters. Currently not used. +/// @param[out] err Error details, if any. +/// +/// @returns Map of maps describing commands. +Dictionary nvim_buf_get_commands(Buffer buffer, Dictionary opts, Error *err) + FUNC_API_SINCE(4) +{ + bool global = (buffer == -1); + bool builtin = false; + + for (size_t i = 0; i < opts.size; i++) { + String k = opts.items[i].key; + Object v = opts.items[i].value; + if (!strequal("builtin", k.data)) { + api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); + return (Dictionary)ARRAY_DICT_INIT; + } + if (strequal("builtin", k.data)) { + builtin = v.data.boolean; + } + } + + if (global) { + if (builtin) { + api_set_error(err, kErrorTypeValidation, "builtin=true not implemented"); + return (Dictionary)ARRAY_DICT_INIT; + } + return commands_array(NULL); + } + + buf_T *buf = find_buffer_by_handle(buffer, err); + if (builtin || !buf) { + return (Dictionary)ARRAY_DICT_INIT; + } + return commands_array(buf); +} + /// Sets a buffer-scoped (b:) variable /// /// @param buffer Buffer handle @@ -581,7 +621,8 @@ Object nvim_buf_get_option(Buffer buffer, String name, Error *err) /// @param name Option name /// @param value Option value /// @param[out] err Error details, if any -void nvim_buf_set_option(Buffer buffer, String name, Object value, Error *err) +void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, + String name, Object value, Error *err) FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -590,7 +631,7 @@ void nvim_buf_set_option(Buffer buffer, String name, Object value, Error *err) return; } - set_option_to(buf, SREQ_BUF, name, value, err); + set_option_to(channel_id, buf, SREQ_BUF, name, value, err); } /// Gets the buffer number diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index 2144c80d6a..390b7e8363 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -47,7 +47,7 @@ typedef enum { /// Internal call from lua code #define LUA_INTERNAL_CALL (VIML_INTERNAL_CALL + 1) -static inline bool is_internal_call(uint64_t channel_id) +static inline bool is_internal_call(const uint64_t channel_id) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_CONST; /// Check whether call is internal diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 65c306d4b1..17ee3ed711 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -47,13 +47,15 @@ typedef struct { /// @param[out] tstate Location where try state should be saved. void try_enter(TryState *const tstate) { + // TODO(ZyX-I): Check whether try_enter()/try_leave() may use + // enter_cleanup()/leave_cleanup(). Or + // save_dbg_stuff()/restore_dbg_stuff(). *tstate = (TryState) { .current_exception = current_exception, .msg_list = (const struct msglist *const *)msg_list, .private_msg_list = NULL, .trylevel = trylevel, .got_int = got_int, - .did_throw = did_throw, .need_rethrow = need_rethrow, .did_emsg = did_emsg, }; @@ -61,7 +63,6 @@ void try_enter(TryState *const tstate) current_exception = NULL; trylevel = 1; got_int = false; - did_throw = false; need_rethrow = false; did_emsg = false; } @@ -82,7 +83,6 @@ bool try_leave(const TryState *const tstate, Error *const err) assert(trylevel == 0); assert(!need_rethrow); assert(!got_int); - assert(!did_throw); assert(!did_emsg); assert(msg_list == &tstate->private_msg_list); assert(*msg_list == NULL); @@ -91,7 +91,6 @@ bool try_leave(const TryState *const tstate, Error *const err) current_exception = tstate->current_exception; trylevel = tstate->trylevel; got_int = tstate->got_int; - did_throw = tstate->did_throw; need_rethrow = tstate->need_rethrow; did_emsg = tstate->did_emsg; return ret; @@ -121,13 +120,11 @@ bool try_end(Error *err) // try_enter/try_leave. trylevel--; - // Without this it stops processing all subsequent VimL commands and - // generates strange error messages if I e.g. try calling Test() in a - // cycle + // Set by emsg(), affects aborting(). See also enter_cleanup(). did_emsg = false; if (got_int) { - if (did_throw) { + if (current_exception) { // If we got an interrupt, discard the current exception discard_current_exception(); } @@ -146,7 +143,7 @@ bool try_end(Error *err) if (should_free) { xfree(msg); } - } else if (did_throw) { + } else if (current_exception) { api_set_error(err, kErrorTypeException, "%s", current_exception->value); discard_current_exception(); } @@ -327,7 +324,8 @@ Object get_option_from(void *from, int type, String name, Error *err) /// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF` /// @param name The option name /// @param[out] err Details of an error that may have occurred -void set_option_to(void *to, int type, String name, Object value, Error *err) +void set_option_to(uint64_t channel_id, void *to, int type, + String name, Object value, Error *err) { if (name.size == 0) { api_set_error(err, kErrorTypeValidation, "Empty option name"); @@ -364,7 +362,8 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) } } - int opt_flags = (type == SREQ_GLOBAL) ? OPT_GLOBAL : OPT_LOCAL; + int numval = 0; + char *stringval = NULL; if (flags & SOPT_BOOL) { if (value.type != kObjectTypeBoolean) { @@ -375,8 +374,7 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) return; } - bool val = value.data.boolean; - set_option_value_for(name.data, val, NULL, opt_flags, type, to, err); + numval = value.data.boolean; } else if (flags & SOPT_NUM) { if (value.type != kObjectTypeInteger) { api_set_error(err, @@ -394,8 +392,7 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) return; } - int val = (int) value.data.integer; - set_option_value_for(name.data, val, NULL, opt_flags, type, to, err); + numval = (int)value.data.integer; } else { if (value.type != kObjectTypeString) { api_set_error(err, @@ -405,9 +402,18 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) return; } - set_option_value_for(name.data, 0, value.data.string.data, - opt_flags, type, to, err); + stringval = (char *)value.data.string.data; } + + const scid_T save_current_SID = current_SID; + current_SID = channel_id == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; + current_channel_id = channel_id; + + const int opt_flags = (type == SREQ_GLOBAL) ? OPT_GLOBAL : OPT_LOCAL; + set_option_value_for(name.data, numval, stringval, + opt_flags, type, to, err); + + current_SID = save_current_SID; } #define TYPVAL_ENCODE_ALLOW_SPECIALS false @@ -567,7 +573,7 @@ static inline void typval_encode_dict_end(EncodedData *const edata) typval_encode_dict_end(edata) #define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ - TYPVAL_ENCODE_CONV_NIL() + TYPVAL_ENCODE_CONV_NIL(val) #define TYPVAL_ENCODE_SCOPE static #define TYPVAL_ENCODE_NAME object @@ -679,7 +685,7 @@ tabpage_T *find_tab_by_handle(Tabpage tabpage, Error *err) String cchar_to_string(char c) { char buf[] = { c, NUL }; - return (String) { + return (String){ .data = xmemdupz(buf, 1), .size = (c != NUL) ? 1 : 0 }; @@ -695,13 +701,13 @@ String cchar_to_string(char c) String cstr_to_string(const char *str) { if (str == NULL) { - return (String) STRING_INIT; + return (String)STRING_INIT; } size_t len = strlen(str); - return (String) { - .data = xmemdupz(str, len), - .size = len + return (String){ + .data = xmemdupz(str, len), + .size = len, }; } @@ -716,7 +722,7 @@ String cstr_to_string(const char *str) String cbuf_to_string(const char *buf, size_t size) FUNC_ATTR_NONNULL_ALL { - return (String) { + return (String){ .data = xmemdupz(buf, size), .size = size }; @@ -731,9 +737,9 @@ String cbuf_to_string(const char *buf, size_t size) String cstr_as_string(char *str) FUNC_ATTR_PURE { if (str == NULL) { - return (String) STRING_INIT; + return (String)STRING_INIT; } - return (String) { .data = str, .size = strlen(str) }; + return (String){ .data = str, .size = strlen(str) }; } /// Converts from type Object to a VimL value. @@ -1143,7 +1149,7 @@ void api_set_error(Error *err, ErrorType errType, const char *format, ...) /// /// @param mode The abbreviation for the mode /// @param buf The buffer to get the mapping array. NULL for global -/// @returns An array of maparg() like dictionaries describing mappings +/// @returns Array of maparg()-like dictionaries describing mappings ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf) { Array mappings = ARRAY_DICT_INIT; diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 87f334ac30..0634764f13 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -93,7 +93,6 @@ typedef struct { const struct msglist *const *msg_list; int trylevel; int got_int; - int did_throw; int need_rethrow; int did_emsg; } TryState; diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 4870c3fb8a..4cd2657561 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -56,7 +56,8 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(err, kErrorTypeException, "UI already attached for channel"); + api_set_error(err, kErrorTypeException, + "UI already attached to channel: %" PRId64, channel_id); return; } @@ -130,7 +131,8 @@ void nvim_ui_detach(uint64_t channel_id, Error *err) FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(err, kErrorTypeException, "UI is not attached for channel"); + api_set_error(err, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); return; } remote_ui_disconnect(channel_id); @@ -142,7 +144,8 @@ void nvim_ui_try_resize(uint64_t channel_id, Integer width, FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(err, kErrorTypeException, "UI is not attached for channel"); + api_set_error(err, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); return; } @@ -163,7 +166,8 @@ void nvim_ui_set_option(uint64_t channel_id, String name, FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(error, kErrorTypeException, "UI is not attached for channel"); + api_set_error(error, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); return; } UI *ui = pmap_get(uint64_t)(connected_uis, channel_id); @@ -209,7 +213,8 @@ static void ui_set_option(UI *ui, String name, Object value, Error *error) return; } - api_set_error(error, kErrorTypeValidation, "No such ui option"); + api_set_error(error, kErrorTypeValidation, "No such UI option: %s", + name.data); #undef UI_EXT_OPTION } diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index c599b0ce72..96d494460b 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -10,7 +10,7 @@ #include "nvim/func_attr.h" #include "nvim/ui.h" -void resize(Integer rows, Integer columns) +void resize(Integer width, Integer height) FUNC_API_SINCE(3); void clear(void) FUNC_API_SINCE(3); diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index dad67c5e4b..7a951d4e67 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -33,6 +33,7 @@ #include "nvim/syntax.h" #include "nvim/getchar.h" #include "nvim/os/input.h" +#include "nvim/os/process.h" #include "nvim/viml/parser/expressions.h" #include "nvim/viml/parser/parser.h" #include "nvim/ui.h" @@ -45,8 +46,7 @@ /// Executes an ex-command. /// -/// On parse error: forwards the Vim error; does not update v:errmsg. -/// On runtime error: forwards the Vim error; does not update v:errmsg. +/// On execution error: fails with VimL error, does not update v:errmsg. /// /// @param command Ex-command string /// @param[out] err Error details (Vim error), if any @@ -102,7 +102,8 @@ Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Error *err) } /// Passes input keys to Nvim. -/// On VimL error: Does not fail, but updates v:errmsg. +/// +/// On execution error: does not fail, but updates v:errmsg. /// /// @param keys to be typed /// @param mode mapping options @@ -168,7 +169,8 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi) } /// Passes keys to Nvim as raw user-input. -/// On VimL error: Does not fail, but updates v:errmsg. +/// +/// On execution error: does not fail, but updates v:errmsg. /// /// Unlike `nvim_feedkeys`, this uses a lower-level input buffer and the call /// is not deferred. This is the most reliable way to send real user input. @@ -212,8 +214,7 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, /// Executes an ex-command and returns its (non-error) output. /// Shell |:!| output is not captured. /// -/// On parse error: forwards the Vim error; does not update v:errmsg. -/// On runtime error: forwards the Vim error; does not update v:errmsg. +/// On execution error: fails with VimL error, does not update v:errmsg. /// /// @param command Ex-command string /// @param[out] err Error details (Vim error), if any @@ -238,15 +239,17 @@ String nvim_command_output(String command, Error *err) } if (capture_local.ga_len > 1) { - // redir always(?) prepends a newline; remove it. - char *s = capture_local.ga_data; - assert(s[0] == '\n'); - memmove(s, s + 1, (size_t)capture_local.ga_len); - s[capture_local.ga_len - 1] = '\0'; - return (String) { // Caller will free the memory. - .data = s, - .size = (size_t)(capture_local.ga_len - 1), + String s = (String){ + .data = capture_local.ga_data, + .size = (size_t)capture_local.ga_len, }; + // redir usually (except :echon) prepends a newline. + if (s.data[0] == '\n') { + memmove(s.data, s.data + 1, s.size); + s.data[s.size - 1] = '\0'; + s.size = s.size - 1; + } + return s; // Caller will free the memory. } theend: @@ -256,7 +259,8 @@ theend: /// Evaluates a VimL expression (:help expression). /// Dictionaries and Lists are recursively expanded. -/// On VimL error: Returns a generic error; v:errmsg is not updated. +/// +/// On execution error: fails with VimL error, does not update v:errmsg. /// /// @param expr VimL expression string /// @param[out] err Error details, if any @@ -264,41 +268,79 @@ theend: Object nvim_eval(String expr, Error *err) FUNC_API_SINCE(1) { + static int recursive = 0; // recursion depth Object rv = OBJECT_INIT; - // Evaluate the expression + + // `msg_list` controls the collection of abort-causing non-exception errors, + // which would otherwise be ignored. This pattern is from do_cmdline(). + struct msglist **saved_msg_list = msg_list; + struct msglist *private_msg_list; + msg_list = &private_msg_list; + private_msg_list = NULL; + + // Initialize `force_abort` and `suppress_errthrow` at the top level. + if (!recursive) { + force_abort = false; + suppress_errthrow = false; + current_exception = NULL; + // `did_emsg` is set by emsg(), which cancels execution. + did_emsg = false; + } + recursive++; try_start(); typval_T rettv; - if (eval0((char_u *)expr.data, &rettv, NULL, true) == FAIL) { - api_set_error(err, kErrorTypeException, "Failed to evaluate expression"); - } + int ok = eval0((char_u *)expr.data, &rettv, NULL, true); if (!try_end(err)) { - // No errors, convert the result - rv = vim_to_object(&rettv); + if (ok == FAIL) { + // Should never happen, try_end() should get the error. #8371 + api_set_error(err, kErrorTypeException, "Failed to evaluate expression"); + } else { + rv = vim_to_object(&rettv); + } } - // Free the Vim object tv_clear(&rettv); + msg_list = saved_msg_list; // Restore the exception context. + recursive--; return rv; } -/// Calls a VimL function with the given arguments +/// Execute lua code. Parameters (if any) are available as `...` inside the +/// chunk. The chunk can return a value. /// -/// On VimL error: Returns a generic error; v:errmsg is not updated. +/// Only statements are executed. To evaluate an expression, prefix it +/// with `return`: return my_function(...) /// -/// @param fname Function to call -/// @param args Function arguments packed in an Array +/// @param code lua code to execute +/// @param args Arguments to the code +/// @param[out] err Details of an error encountered while parsing +/// or executing the lua code. +/// +/// @return Return value of lua code if present or NIL. +Object nvim_execute_lua(String code, Array args, Error *err) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY +{ + return executor_exec_lua_api(code, args, err); +} + +/// Calls a VimL function. +/// +/// @param fn Function name +/// @param args Function arguments +/// @param self `self` dict, or NULL for non-dict functions /// @param[out] err Error details, if any /// @return Result of the function call -Object nvim_call_function(String fname, Array args, Error *err) - FUNC_API_SINCE(1) +static Object _call_function(String fn, Array args, dict_T *self, Error *err) { + static int recursive = 0; // recursion depth Object rv = OBJECT_INIT; + if (args.size > MAX_FUNC_ARGS) { api_set_error(err, kErrorTypeValidation, - "Function called with too many arguments."); + "Function called with too many arguments"); return rv; } @@ -311,21 +353,36 @@ Object nvim_call_function(String fname, Array args, Error *err) } } + // `msg_list` controls the collection of abort-causing non-exception errors, + // which would otherwise be ignored. This pattern is from do_cmdline(). + struct msglist **saved_msg_list = msg_list; + struct msglist *private_msg_list; + msg_list = &private_msg_list; + private_msg_list = NULL; + + // Initialize `force_abort` and `suppress_errthrow` at the top level. + if (!recursive) { + force_abort = false; + suppress_errthrow = false; + current_exception = NULL; + // `did_emsg` is set by emsg(), which cancels execution. + did_emsg = false; + } + recursive++; try_start(); - // 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, NULL, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, - true, NULL, NULL); - if (r == FAIL) { - api_set_error(err, kErrorTypeException, "Error calling function."); - } + // call_func() retval is deceptive, ignore it. Instead we set `msg_list` + // (see above) to capture abort-causing non-exception errors. + (void)call_func((char_u *)fn.data, (int)fn.size, &rettv, (int)args.size, + vim_args, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &dummy, true, NULL, self); if (!try_end(err)) { rv = vim_to_object(&rettv); } tv_clear(&rettv); + msg_list = saved_msg_list; // Restore the exception context. + recursive--; free_vim_args: while (i > 0) { @@ -335,22 +392,102 @@ free_vim_args: return rv; } -/// Execute lua code. Parameters (if any) are available as `...` inside the -/// chunk. The chunk can return a value. +/// Calls a VimL function with the given arguments. /// -/// Only statements are executed. To evaluate an expression, prefix it -/// with `return`: return my_function(...) +/// On execution error: fails with VimL error, does not update v:errmsg. /// -/// @param code lua code to execute -/// @param args Arguments to the code -/// @param[out] err Details of an error encountered while parsing -/// or executing the lua code. +/// @param fn Function to call +/// @param args Function arguments packed in an Array +/// @param[out] err Error details, if any +/// @return Result of the function call +Object nvim_call_function(String fn, Array args, Error *err) + FUNC_API_SINCE(1) +{ + return _call_function(fn, args, NULL, err); +} + +/// Calls a VimL |Dictionary-function| with the given arguments. /// -/// @return Return value of lua code if present or NIL. -Object nvim_execute_lua(String code, Array args, Error *err) - FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY +/// On execution error: fails with VimL error, does not update v:errmsg. +/// +/// @param dict Dictionary, or String evaluating to a VimL |self| dict +/// @param fn Name of the function defined on the VimL dict +/// @param args Function arguments packed in an Array +/// @param[out] err Error details, if any +/// @return Result of the function call +Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err) + FUNC_API_SINCE(4) { - return executor_exec_lua_api(code, args, err); + Object rv = OBJECT_INIT; + + typval_T rettv; + bool mustfree = false; + switch (dict.type) { + case kObjectTypeString: { + try_start(); + if (eval0((char_u *)dict.data.string.data, &rettv, NULL, true) == FAIL) { + api_set_error(err, kErrorTypeException, + "Failed to evaluate dict expression"); + } + if (try_end(err)) { + return rv; + } + // Evaluation of the string arg created a new dict or increased the + // refcount of a dict. Not necessary for a RPC dict. + mustfree = true; + break; + } + case kObjectTypeDictionary: { + if (!object_to_vim(dict, &rettv, err)) { + goto end; + } + break; + } + default: { + api_set_error(err, kErrorTypeValidation, + "dict argument type must be String or Dictionary"); + return rv; + } + } + dict_T *self_dict = rettv.vval.v_dict; + if (rettv.v_type != VAR_DICT || !self_dict) { + api_set_error(err, kErrorTypeValidation, "dict not found"); + goto end; + } + + if (fn.data && fn.size > 0 && dict.type != kObjectTypeDictionary) { + dictitem_T *const di = tv_dict_find(self_dict, fn.data, (ptrdiff_t)fn.size); + if (di == NULL) { + api_set_error(err, kErrorTypeValidation, "Not found: %s", fn.data); + goto end; + } + if (di->di_tv.v_type == VAR_PARTIAL) { + api_set_error(err, kErrorTypeValidation, + "partial function not supported"); + goto end; + } + if (di->di_tv.v_type != VAR_FUNC) { + api_set_error(err, kErrorTypeValidation, "Not a function: %s", fn.data); + goto end; + } + fn = (String) { + .data = (char *)di->di_tv.vval.v_string, + .size = strlen((char *)di->di_tv.vval.v_string), + }; + } + + if (!fn.data || fn.size < 1) { + api_set_error(err, kErrorTypeValidation, "Invalid (empty) function name"); + goto end; + } + + rv = _call_function(fn, args, self_dict, err); +end: + if (mustfree) { + tv_clear(&rettv); + } + + return rv; } /// Calculates the number of display cells occupied by `text`. @@ -546,10 +683,10 @@ Object nvim_get_option(String name, Error *err) /// @param name Option name /// @param value New option value /// @param[out] err Error details, if any -void nvim_set_option(String name, Object value, Error *err) +void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err) FUNC_API_SINCE(1) { - set_option_to(NULL, SREQ_GLOBAL, name, value, err); + set_option_to(channel_id, NULL, SREQ_GLOBAL, name, value, err); } /// Writes a message to the Vim output buffer. Does not append "\n", the @@ -811,17 +948,32 @@ Dictionary nvim_get_mode(void) return rv; } -/// Gets a list of dictionaries describing global (non-buffer) mappings. -/// The "buffer" key in the returned dictionary is always zero. +/// Gets a list of global (non-buffer-local) |mapping| definitions. /// /// @param mode Mode short-name ("n", "i", "v", ...) -/// @returns Array of maparg()-like dictionaries describing mappings +/// @returns Array of maparg()-like dictionaries describing mappings. +/// The "buffer" key is always zero. ArrayOf(Dictionary) nvim_get_keymap(String mode) - FUNC_API_SINCE(3) + FUNC_API_SINCE(3) { return keymap_array(mode, NULL); } +/// Gets a map of global (non-buffer-local) Ex commands. +/// +/// Currently only |user-commands| are supported, not builtin Ex commands. +/// +/// @param opts Optional parameters. Currently only supports +/// {"builtin":false} +/// @param[out] err Error details, if any. +/// +/// @returns Map of maps describing commands. +Dictionary nvim_get_commands(Dictionary opts, Error *err) + FUNC_API_SINCE(4) +{ + return nvim_buf_get_commands(-1, opts, err); +} + /// Returns a 2-tuple (Array), where item 0 is the current channel id and item /// 1 is the |api-metadata| map (Dictionary). /// @@ -838,27 +990,26 @@ Array nvim_get_api_info(uint64_t channel_id) return rv; } -/// Call many api methods atomically +/// Calls many API methods atomically. /// -/// This has two main usages: Firstly, to perform several requests from an -/// async context atomically, i.e. without processing requests from other rpc -/// clients or redrawing or allowing user interaction in between. Note that api -/// methods that could fire autocommands or do event processing still might do -/// so. For instance invoking the :sleep command might call timer callbacks. -/// Secondly, it can be used to reduce rpc overhead (roundtrips) when doing -/// many requests in sequence. +/// This has two main usages: +/// 1. To perform several requests from an async context atomically, i.e. +/// without interleaving redraws, RPC requests from other clients, or user +/// interactions (however API methods may trigger autocommands or event +/// processing which have such side-effects, e.g. |:sleep| may wake timers). +/// 2. To minimize RPC overhead (roundtrips) of a sequence of many requests. /// /// @param calls an array of calls, where each call is described by an array /// with two elements: the request name, and an array of arguments. /// @param[out] err Details of a validation error of the nvim_multi_request call -/// itself, i e malformatted `calls` parameter. Errors from called methods will +/// itself, i.e. malformed `calls` parameter. Errors from called methods will /// be indicated in the return value, see below. /// /// @return an array with two elements. The first is an array of return /// values. The second is NIL if all calls succeeded. If a call resulted in /// an error, it is a three-element array with the zero-based index of the call /// which resulted in an error, the error type and the error message. If an -/// error ocurred, the values from all preceding calls will still be returned. +/// error occurred, the values from all preceding calls will still be returned. Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { @@ -1197,7 +1348,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, .node_p = &node->next, .ret_node_p = cur_item.ret_node_p + 1, })); - } else if (node != NULL) { + } else { kv_drop(ast_conv_stack, 1); ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("type"), @@ -1470,6 +1621,17 @@ Float nvim__id_float(Float flt) return flt; } +/// Gets internal stats. +/// +/// @return Map of various internal stats. +Dictionary nvim__stats(void) +{ + Dictionary rv = ARRAY_DICT_INIT; + PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync)); + PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw)); + return rv; +} + /// Gets a list of dictionaries representing attached UIs. /// /// @return Array of UI dictionaries @@ -1478,3 +1640,86 @@ Array nvim_list_uis(void) { return ui_array(); } + +/// Gets the immediate children of process `pid`. +/// +/// @return Array of child process ids, empty if process not found. +Array nvim_get_proc_children(Integer pid, Error *err) + FUNC_API_SINCE(4) +{ + Array rvobj = ARRAY_DICT_INIT; + int *proc_list = NULL; + + if (pid <= 0 || pid > INT_MAX) { + api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid); + goto end; + } + + size_t proc_count; + int rv = os_proc_children((int)pid, &proc_list, &proc_count); + if (rv != 0) { + // syscall failed (possibly because of kernel options), try shelling out. + DLOG("fallback to vim._os_proc_children()"); + Array a = ARRAY_DICT_INIT; + ADD(a, INTEGER_OBJ(pid)); + String s = cstr_to_string("return vim._os_proc_children(select(1, ...))"); + Object o = nvim_execute_lua(s, a, err); + api_free_string(s); + api_free_array(a); + if (o.type == kObjectTypeArray) { + rvobj = o.data.array; + } else if (!ERROR_SET(err)) { + api_set_error(err, kErrorTypeException, + "Failed to get process children. pid=%" PRId64 " error=%d", + pid, rv); + } + goto end; + } + + for (size_t i = 0; i < proc_count; i++) { + ADD(rvobj, INTEGER_OBJ(proc_list[i])); + } + +end: + xfree(proc_list); + return rvobj; +} + +/// Gets info describing process `pid`. +/// +/// @return Map of process properties, or NIL if process not found. +Object nvim_get_proc(Integer pid, Error *err) + FUNC_API_SINCE(4) +{ + Object rvobj = OBJECT_INIT; + rvobj.data.dictionary = (Dictionary)ARRAY_DICT_INIT; + rvobj.type = kObjectTypeDictionary; + + if (pid <= 0 || pid > INT_MAX) { + api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid); + return NIL; + } +#ifdef WIN32 + rvobj.data.dictionary = os_proc_info((int)pid); + if (rvobj.data.dictionary.size == 0) { // Process not found. + return NIL; + } +#else + // Cross-platform process info APIs are miserable, so use `ps` instead. + Array a = ARRAY_DICT_INIT; + ADD(a, INTEGER_OBJ(pid)); + String s = cstr_to_string("return vim._os_proc_info(select(1, ...))"); + Object o = nvim_execute_lua(s, a, err); + api_free_string(s); + api_free_array(a); + if (o.type == kObjectTypeArray && o.data.array.size == 0) { + return NIL; // Process not found. + } else if (o.type == kObjectTypeDictionary) { + rvobj.data.dictionary = o.data.dictionary; + } else if (!ERROR_SET(err)) { + api_set_error(err, kErrorTypeException, + "Failed to get process info. pid=%" PRId64, pid); + } +#endif + return rvobj; +} diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 9bc91ef8fb..abfa0dc20b 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -309,7 +309,8 @@ Object nvim_win_get_option(Window window, String name, Error *err) /// @param name Option name /// @param value Option value /// @param[out] err Error details, if any -void nvim_win_set_option(Window window, String name, Object value, Error *err) +void nvim_win_set_option(uint64_t channel_id, Window window, + String name, Object value, Error *err) FUNC_API_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -318,7 +319,7 @@ void nvim_win_set_option(Window window, String name, Object value, Error *err) return; } - set_option_to(win, SREQ_WIN, name, value, err); + set_option_to(channel_id, win, SREQ_WIN, name, value, err); } /// Gets the window position in display cells. First position is zero. diff --git a/src/nvim/ascii.h b/src/nvim/ascii.h index 9ccb70764d..ff6840d690 100644 --- a/src/nvim/ascii.h +++ b/src/nvim/ascii.h @@ -146,7 +146,7 @@ static inline bool ascii_isxdigit(int c) /// Checks if `c` is an “identifier” character /// /// That is, whether it is alphanumeric character or underscore. -static inline bool ascii_isident(const int c) +static inline bool ascii_isident(int c) { return ASCII_ISALNUM(c) || c == '_'; } diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 7dfaf54ff0..1153314e76 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -92,6 +92,8 @@ return { 'VimLeave', -- before exiting Vim 'VimLeavePre', -- before exiting Vim and writing ShaDa file 'VimResized', -- after Vim window was resized + 'VimResume', -- after Nvim is resumed + 'VimSuspend', -- before Nvim is suspended 'WinNew', -- when entering a new window 'WinEnter', -- after entering a window 'WinLeave', -- before leaving a window diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 3958fb05e9..ba63822837 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -22,6 +22,7 @@ #include <stdbool.h> #include <string.h> #include <inttypes.h> +#include <assert.h> #include "nvim/api/private/handle.h" #include "nvim/api/private/helpers.h" @@ -2683,7 +2684,7 @@ void buflist_altfpos(win_T *win) } /// Check that "ffname" is not the same file as current file. -/// Fname must have a full path (expanded by path_get_absolute_path()). +/// Fname must have a full path (expanded by path_to_absolute()). /// /// @param ffname full path name to check bool otherfile(char_u *ffname) @@ -2693,7 +2694,7 @@ bool otherfile(char_u *ffname) } /// Check that "ffname" is not the same file as the file loaded in "buf". -/// Fname must have a full path (expanded by path_get_absolute_path()). +/// Fname must have a full path (expanded by path_to_absolute()). /// /// @param buf buffer to check /// @param ffname full path name to check @@ -2885,15 +2886,13 @@ static char_u *lasticon = NULL; void maketitle(void) { - char_u *p; char_u *t_str = NULL; char_u *i_name; char_u *i_str = NULL; int maxlen = 0; int len; int mustset; - char_u buf[IOSIZE]; - int off; + char buf[IOSIZE]; if (!redrawing()) { /* Postpone updating the title when 'lazyredraw' is set. */ @@ -2913,97 +2912,117 @@ void maketitle(void) } } - t_str = buf; if (*p_titlestring != NUL) { if (stl_syntax & STL_IN_TITLE) { int use_sandbox = FALSE; int save_called_emsg = called_emsg; use_sandbox = was_set_insecurely((char_u *)"titlestring", 0); - called_emsg = FALSE; - build_stl_str_hl(curwin, t_str, sizeof(buf), - p_titlestring, use_sandbox, - 0, maxlen, NULL, NULL); - if (called_emsg) - set_string_option_direct((char_u *)"titlestring", -1, - (char_u *)"", OPT_FREE, SID_ERROR); + called_emsg = false; + build_stl_str_hl(curwin, (char_u *)buf, sizeof(buf), + p_titlestring, use_sandbox, + 0, maxlen, NULL, NULL); + t_str = (char_u *)buf; + if (called_emsg) { + set_string_option_direct((char_u *)"titlestring", -1, (char_u *)"", + OPT_FREE, SID_ERROR); + } called_emsg |= save_called_emsg; - } else + } else { t_str = p_titlestring; + } } else { - /* format: "fname + (path) (1 of 2) - VIM" */ - -#define SPACE_FOR_FNAME (IOSIZE - 100) -#define SPACE_FOR_DIR (IOSIZE - 20) -#define SPACE_FOR_ARGNR (IOSIZE - 10) /* at least room for " - VIM" */ - if (curbuf->b_fname == NULL) - STRLCPY(buf, _("[No Name]"), SPACE_FOR_FNAME + 1); - else { - p = transstr(path_tail(curbuf->b_fname)); - STRLCPY(buf, p, SPACE_FOR_FNAME + 1); - xfree(p); + // Format: "fname + (path) (1 of 2) - VIM". + +#define SPACE_FOR_FNAME (sizeof(buf) - 100) +#define SPACE_FOR_DIR (sizeof(buf) - 20) +#define SPACE_FOR_ARGNR (sizeof(buf) - 10) // At least room for " - NVIM". + char *buf_p = buf; + if (curbuf->b_fname == NULL) { + const size_t size = xstrlcpy(buf_p, _("[No Name]"), + SPACE_FOR_FNAME + 1); + buf_p += MIN(size, SPACE_FOR_FNAME); + } else { + buf_p += transstr_buf((const char *)path_tail(curbuf->b_fname), + buf_p, SPACE_FOR_FNAME + 1); } switch (bufIsChanged(curbuf) - + (curbuf->b_p_ro * 2) - + (!MODIFIABLE(curbuf) * 4)) { - case 1: STRCAT(buf, " +"); break; - case 2: STRCAT(buf, " ="); break; - case 3: STRCAT(buf, " =+"); break; - case 4: - case 6: STRCAT(buf, " -"); break; - case 5: - case 7: STRCAT(buf, " -+"); break; + | (curbuf->b_p_ro << 1) + | (!MODIFIABLE(curbuf) << 2)) { + case 0: break; + case 1: buf_p = strappend(buf_p, " +"); break; + case 2: buf_p = strappend(buf_p, " ="); break; + case 3: buf_p = strappend(buf_p, " =+"); break; + case 4: + case 6: buf_p = strappend(buf_p, " -"); break; + case 5: + case 7: buf_p = strappend(buf_p, " -+"); break; + default: assert(false); } if (curbuf->b_fname != NULL) { - /* Get path of file, replace home dir with ~ */ - off = (int)STRLEN(buf); - buf[off++] = ' '; - buf[off++] = '('; - home_replace(curbuf, curbuf->b_ffname, - buf + off, (size_t)(SPACE_FOR_DIR - off), true); + // Get path of file, replace home dir with ~. + *buf_p++ = ' '; + *buf_p++ = '('; + home_replace(curbuf, curbuf->b_ffname, (char_u *)buf_p, + (SPACE_FOR_DIR - (size_t)(buf_p - buf)), true); #ifdef BACKSLASH_IN_FILENAME - /* avoid "c:/name" to be reduced to "c" */ - if (isalpha(buf[off]) && buf[off + 1] == ':') - off += 2; + // Avoid "c:/name" to be reduced to "c". + if (isalpha((uint8_t)buf_p) && *(buf_p + 1) == ':') { + buf_p += 2; + } #endif - /* remove the file name */ - p = path_tail_with_sep(buf + off); - if (p == buf + off) - /* must be a help buffer */ - STRLCPY(buf + off, _("help"), SPACE_FOR_DIR - off); - else + // Remove the file name. + char *p = (char *)path_tail_with_sep((char_u *)buf_p); + if (p == buf_p) { + // Must be a help buffer. + xstrlcpy(buf_p, _("help"), SPACE_FOR_DIR - (size_t)(buf_p - buf)); + } else { *p = NUL; + } - /* Translate unprintable chars and concatenate. Keep some - * room for the server name. When there is no room (very long - * file name) use (...). */ - if (off < SPACE_FOR_DIR) { - p = transstr(buf + off); - STRLCPY(buf + off, p, SPACE_FOR_DIR - off + 1); - xfree(p); + // Translate unprintable chars and concatenate. Keep some + // room for the server name. When there is no room (very long + // file name) use (...). + if ((size_t)(buf_p - buf) < SPACE_FOR_DIR) { + char *const tbuf = transstr(buf_p); + const size_t free_space = SPACE_FOR_DIR - (size_t)(buf_p - buf) + 1; + const size_t dir_len = xstrlcpy(buf_p, tbuf, free_space); + buf_p += MIN(dir_len, free_space - 1); + xfree(tbuf); } else { - STRLCPY(buf + off, "...", SPACE_FOR_ARGNR - off + 1); + const size_t free_space = SPACE_FOR_ARGNR - (size_t)(buf_p - buf) + 1; + const size_t dots_len = xstrlcpy(buf_p, "...", free_space); + buf_p += MIN(dots_len, free_space - 1); } - STRCAT(buf, ")"); + *buf_p++ = ')'; + *buf_p = NUL; + } else { + *buf_p = NUL; } - append_arg_number(curwin, buf, SPACE_FOR_ARGNR, FALSE); + append_arg_number(curwin, (char_u *)buf_p, + (int)(SPACE_FOR_ARGNR - (size_t)(buf_p - buf)), false); - STRCAT(buf, " - NVIM"); + xstrlcat(buf_p, " - NVIM", (sizeof(buf) - (size_t)(buf_p - buf))); if (maxlen > 0) { - /* make it shorter by removing a bit in the middle */ - if (vim_strsize(buf) > maxlen) - trunc_string(buf, buf, maxlen, IOSIZE); + // Make it shorter by removing a bit in the middle. + if (vim_strsize((char_u *)buf) > maxlen) { + trunc_string((char_u *)buf, (char_u *)buf, maxlen, sizeof(buf)); + } } + t_str = (char_u *)buf; +#undef SPACE_FOR_FNAME +#undef SPACE_FOR_DIR +#undef SPACE_FOR_ARGNR } } mustset = ti_change(t_str, &lasttitle); if (p_icon) { - i_str = buf; + i_str = (char_u *)buf; if (*p_iconstring != NUL) { if (stl_syntax & STL_IN_ICON) { int use_sandbox = FALSE; @@ -3091,7 +3110,6 @@ void free_titles(void) /// be used when printing numbers in the status line. typedef enum { kNumBaseDecimal = 10, - kNumBaseOctal = 8, kNumBaseHexadecimal = 16 } NumberBase; @@ -3889,9 +3907,7 @@ int build_stl_str_hl( // Note: The `*` means we take the width as one of the arguments *t++ = '*'; - *t++ = (char_u) (base == kNumBaseHexadecimal ? 'X' - : (base == kNumBaseOctal ? 'o' - : 'd')); + *t++ = (char_u)(base == kNumBaseHexadecimal ? 'X' : 'd'); *t = 0; // } @@ -5346,7 +5362,7 @@ void bufhl_clear_line_range(buf_T *buf, if (line > line_end) { break; } - if (line_start <= line && line <= line_end) { + if (line_start <= line) { BufhlLineStatus status = bufhl_clear_line(l, src_id, line); if (status != kBLSUnchanged) { if (line > last_changed) { diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 8de4286216..53edae58a5 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -237,7 +237,7 @@ typedef struct { char_u *wo_winhl; # define w_p_winhl w_onebuf_opt.wo_winhl // 'winhighlight' - int wo_scriptID[WV_COUNT]; /* SIDs for window-local options */ + LastSet wo_scriptID[WV_COUNT]; // SIDs for window-local options # define w_p_scriptID w_onebuf_opt.wo_scriptID } winopt_T; @@ -590,7 +590,7 @@ struct file_buffer { */ bool b_p_initialized; /* set when options initialized */ - int b_p_scriptID[BV_COUNT]; /* SIDs for buffer-local options */ + LastSet b_p_scriptID[BV_COUNT]; // SIDs for buffer-local options int b_p_ai; ///< 'autoindent' int b_p_ai_nopaste; ///< b_p_ai saved for paste mode diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 265d4d8b89..4e6ca8d278 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -180,10 +180,12 @@ static Channel *channel_alloc(ChannelStreamType type) } /// Not implemented, only logging for now -void channel_create_event(Channel *chan, char *ext_source) +void channel_create_event(Channel *chan, const char *ext_source) { #if MIN_LOG_LEVEL <= INFO_LOG_LEVEL - char *stream_desc, *mode_desc, *source; + const char *stream_desc; + const char *mode_desc; + const char *source; switch (chan->streamtype) { case kChannelStreamProc: @@ -223,8 +225,8 @@ void channel_create_event(Channel *chan, char *ext_source) // external events should be included. source = ext_source; } else { - eval_format_source_name_line((char *)IObuff, sizeof(IObuff)); - source = (char *)IObuff; + eval_fmt_source_name_line((char *)IObuff, sizeof(IObuff)); + source = (const char *)IObuff; } ILOG("new channel %" PRIu64 " (%s%s): %s", chan->id, stream_desc, @@ -235,15 +237,16 @@ void channel_create_event(Channel *chan, char *ext_source) #endif } -void channel_incref(Channel *channel) +void channel_incref(Channel *chan) { - channel->refcount++; + chan->refcount++; } -void channel_decref(Channel *channel) +void channel_decref(Channel *chan) { - if (!(--channel->refcount)) { - multiqueue_put(main_loop.fast_events, free_channel_event, 1, channel); + if (!(--chan->refcount)) { + // delay free, so that libuv is done with the handles + multiqueue_put(main_loop.events, free_channel_event, 1, chan); } } @@ -265,18 +268,18 @@ void callback_reader_start(CallbackReader *reader) static void free_channel_event(void **argv) { - Channel *channel = argv[0]; - if (channel->is_rpc) { - rpc_free(channel); + Channel *chan = argv[0]; + if (chan->is_rpc) { + rpc_free(chan); } - callback_reader_free(&channel->on_stdout); - callback_reader_free(&channel->on_stderr); - callback_free(&channel->on_exit); + callback_reader_free(&chan->on_stdout); + callback_reader_free(&chan->on_stderr); + callback_free(&chan->on_exit); - pmap_del(uint64_t)(channels, channel->id); - multiqueue_free(channel->events); - xfree(channel); + pmap_del(uint64_t)(channels, chan->id); + multiqueue_free(chan->events); + xfree(chan); } static void channel_destroy_early(Channel *chan) @@ -284,12 +287,15 @@ static void channel_destroy_early(Channel *chan) if ((chan->id != --next_chan_id)) { abort(); } + pmap_del(uint64_t)(channels, chan->id); + chan->id = 0; if ((--chan->refcount != 0)) { abort(); } - free_channel_event((void **)&chan); + // uv will keep a reference to handles until next loop tick, so delay free + multiqueue_put(main_loop.events, free_channel_event, 1, chan); } @@ -392,17 +398,22 @@ uint64_t channel_connect(bool tcp, const char *address, bool rpc, CallbackReader on_output, int timeout, const char **error) { + Channel *channel; + if (!tcp && rpc) { char *path = fix_fname(address); - if (server_owns_pipe_address(path)) { - // avoid deadlock - xfree(path); - return channel_create_internal_rpc(); - } + bool loopback = server_owns_pipe_address(path); xfree(path); + if (loopback) { + // Create a loopback channel. This avoids deadlock if nvim connects to + // its own named pipe. + channel = channel_alloc(kChannelStreamInternal); + rpc_start(channel); + goto end; + } } - Channel *channel = channel_alloc(kChannelStreamSocket); + channel = channel_alloc(kChannelStreamSocket); if (!socket_connect(&main_loop, &channel->stream.socket, tcp, address, timeout, error)) { channel_destroy_early(channel); @@ -422,7 +433,8 @@ uint64_t channel_connect(bool tcp, const char *address, rstream_start(&channel->stream.socket, on_socket_output, channel); } - channel_create_event(channel, NULL); +end: + channel_create_event(channel, address); return channel->id; } @@ -441,15 +453,6 @@ void channel_from_connection(SocketWatcher *watcher) channel_create_event(channel, watcher->addr); } -/// Creates a loopback channel. This is used to avoid deadlock -/// when an instance connects to its own named pipe. -static uint64_t channel_create_internal_rpc(void) -{ - Channel *channel = channel_alloc(kChannelStreamInternal); - rpc_start(channel); - return channel->id; -} - /// Creates an API channel from stdin/stdout. This is used when embedding /// Neovim uint64_t channel_from_stdio(bool rpc, CallbackReader on_output, @@ -655,9 +658,9 @@ static void channel_process_exit_cb(Process *proc, int status, void *data) terminal_close(chan->term, msg); } - // if status is -1 the process did not really exit, - // we just closed the handle onto a detached process - if (status >= 0) { + // If process did not exit, we only closed the handle of a detached process. + bool exited = (status >= 0); + if (exited) { process_channel_event(chan, &chan->on_exit, "exit", NULL, 0, status); } diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 980b4ed426..7d5f80c531 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -313,69 +313,112 @@ void trans_characters(char_u *buf, int bufsize) } } -/// Translate a string into allocated memory, replacing special chars with -/// printable chars. +/// Find length of a string capable of holding s with all specials replaced /// -/// @param s +/// Assumes replacing special characters with printable ones just like +/// strtrans() does. +/// +/// @param[in] s String to check. /// -/// @return translated string -char_u *transstr(char_u *s) FUNC_ATTR_NONNULL_RET +/// @return number of bytes needed to hold a translation of `s`, NUL byte not +/// included. +size_t transstr_len(const char *const s) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE { - char_u *res; - char_u *p; - int c; - size_t l; - char_u hexbuf[11]; - - // Compute the length of the result, taking account of unprintable - // multi-byte characters. + const char *p = s; size_t len = 0; - p = s; - while (*p != NUL) { - if ((l = (size_t)(*mb_ptr2len)(p)) > 1) { - c = (*mb_ptr2char)(p); - p += l; + while (*p) { + const size_t l = (size_t)utfc_ptr2len((const char_u *)p); + if (l > 1) { + int pcc[MAX_MCO + 2]; + pcc[0] = utfc_ptr2char((const char_u *)p, &pcc[1]); - if (vim_isprintc(c)) { + if (vim_isprintc(pcc[0])) { len += l; } else { - transchar_hex(hexbuf, c); - len += STRLEN(hexbuf); + for (size_t i = 0; i < ARRAY_SIZE(pcc); i++) { + char hexbuf[11]; + len += transchar_hex(hexbuf, pcc[i]); + } } + p += l; } else { - l = (size_t)byte2cells(*p++); - - if (l > 0) { - len += l; - } else { - // illegal byte sequence - len += 4; - } + const int b2c_l = byte2cells((uint8_t)(*p++)); + // Illegal byte sequence may occupy up to 4 characters. + len += (size_t)(b2c_l > 0 ? b2c_l : 4); } } - res = xmallocz(len); - - *res = NUL; - p = s; + return len; +} - while (*p != NUL) { - if ((l = (size_t)(*mb_ptr2len)(p)) > 1) { - c = (*mb_ptr2char)(p); +/// Replace special characters with printable ones +/// +/// @param[in] s String to replace characters from. +/// @param[out] buf Buffer to which result should be saved. +/// @param[in] len Buffer length. Resulting string may not occupy more then +/// len - 1 bytes (one for trailing NUL byte). +/// +/// @return length of the resulting string, without the NUL byte. +size_t transstr_buf(const char *const s, char *const buf, const size_t len) + FUNC_ATTR_NONNULL_ALL +{ + const char *p = s; + char *buf_p = buf; + char *const buf_e = buf_p + len - 1; + + while (*p != NUL && buf_p < buf_e) { + const size_t l = (size_t)utfc_ptr2len((const char_u *)p); + if (l > 1) { + if (buf_p + l >= buf_e) { + break; + } + int pcc[MAX_MCO + 2]; + pcc[0] = utfc_ptr2char((const char_u *)p, &pcc[1]); - if (vim_isprintc(c)) { - // append printable multi-byte char - STRNCAT(res, p, l); + if (vim_isprintc(pcc[0])) { + memmove(buf_p, p, l); + buf_p += l; } else { - transchar_hex(res + STRLEN(res), c); + for (size_t i = 0; i < ARRAY_SIZE(pcc); i++) { + char hexbuf[11]; + const size_t hexlen = transchar_hex(hexbuf, pcc[i]); + if (buf_p + hexlen >= buf_e) { + break; + } + memmove(buf_p, hexbuf, hexlen); + buf_p += hexlen; + } } p += l; } else { - STRCAT(res, transchar_byte(*p++)); + const char *const tb = (const char *)transchar_byte((uint8_t)(*p++)); + const size_t tb_len = strlen(tb); + memmove(buf_p, tb, tb_len); + buf_p += tb_len; } } + *buf_p = NUL; + assert(buf_p <= buf_e); + return (size_t)(buf_p - buf); +} - return res; +/// Copy string and replace special characters with printable characters +/// +/// Works like `strtrans()` does, used for that and in some other places. +/// +/// @param[in] s String to replace characters from. +/// +/// @return [allocated] translated string +char *transstr(const char *const s) + FUNC_ATTR_NONNULL_RET +{ + // Compute the length of the result, taking account of unprintable + // multi-byte characters. + const size_t len = transstr_len((const char *)s) + 1; + char *const buf = xmalloc(len); + transstr_buf(s, buf, len); + return buf; } /// Convert the string "str[orglen]" to do ignore-case comparing. @@ -474,14 +517,16 @@ char_u* str_foldcase(char_u *str, int orglen, char_u *buf, int buflen) // Does NOT work for multi-byte characters, c must be <= 255. // Also doesn't work for the first byte of a multi-byte, "c" must be a // character! -static char_u transchar_buf[7]; +static char_u transchar_buf[11]; -/// Translates a character +/// Translate a character into a printable one, leaving printable ASCII intact /// -/// @param c +/// All unicode characters are considered non-printable in this function. /// -/// @return translated character. -char_u* transchar(int c) +/// @param[in] c Character to translate. +/// +/// @return translated character into a static buffer. +char_u *transchar(int c) { int i = 0; if (IS_SPECIAL(c)) { @@ -494,23 +539,27 @@ char_u* transchar(int c) if ((!chartab_initialized && (((c >= ' ') && (c <= '~')) || (p_altkeymap && F_ischar(c)))) - || ((c < 256) && vim_isprintc_strict(c))) { + || ((c <= 0xFF) && vim_isprintc_strict(c))) { // printable character transchar_buf[i] = (char_u)c; transchar_buf[i + 1] = NUL; - } else { + } else if (c <= 0xFF) { transchar_nonprint(transchar_buf + i, c); + } else { + transchar_hex((char *)transchar_buf + i, c); } return transchar_buf; } -/// Like transchar(), but called with a byte instead of a character. Checks -/// for an illegal UTF-8 byte. +/// Like transchar(), but called with a byte instead of a character /// -/// @param c +/// Checks for an illegal UTF-8 byte. +/// +/// @param[in] c Byte to translate. /// /// @return pointer to translated character in transchar_buf. -char_u* transchar_byte(int c) +char_u *transchar_byte(const int c) + FUNC_ATTR_WARN_UNUSED_RESULT { if (c >= 0x80) { transchar_nonprint(transchar_buf, c); @@ -519,12 +568,14 @@ char_u* transchar_byte(int c) return transchar(c); } -/// Convert non-printable character to two or more printable characters in -/// "buf[]". "buf" needs to be able to hold five bytes. -/// Does NOT work for multi-byte characters, c must be <= 255. +/// Convert non-printable characters to 2..4 printable ones /// -/// @param buf -/// @param c +/// @warning Does not work for multi-byte characters, c must be <= 255. +/// +/// @param[out] buf Buffer to store result in, must be able to hold at least +/// 5 bytes (conversion result + NUL). +/// @param[in] c Character to convert. NUL is assumed to be NL according to +/// `:h NL-used-for-NUL`. void transchar_nonprint(char_u *buf, int c) { if (c == NL) { @@ -534,54 +585,63 @@ void transchar_nonprint(char_u *buf, int c) // we use CR in place of NL in this case c = NL; } + assert(c <= 0xff); - if (dy_flags & DY_UHEX) { + if (dy_flags & DY_UHEX || c > 0x7f) { // 'display' has "uhex" - transchar_hex(buf, c); - } else if (c <= 0x7f) { + transchar_hex((char *)buf, c); + } else { // 0x00 - 0x1f and 0x7f buf[0] = '^'; // DEL displayed as ^? buf[1] = (char_u)(c ^ 0x40); buf[2] = NUL; - } else { - transchar_hex(buf, c); } } -/// Convert a non-printable character to hex. +/// Convert a non-printable character to hex C string like "<FFFF>" /// -/// @param buf -/// @param c -void transchar_hex(char_u *buf, int c) +/// @param[out] buf Buffer to store result in. +/// @param[in] c Character to convert. +/// +/// @return Number of bytes stored in buffer, excluding trailing NUL byte. +size_t transchar_hex(char *const buf, const int c) + FUNC_ATTR_NONNULL_ALL { - int i = 0; + size_t i = 0; - buf[0] = '<'; + buf[i++] = '<'; if (c > 255) { - buf[++i] = (char_u)nr2hex((unsigned)c >> 12); - buf[++i] = (char_u)nr2hex((unsigned)c >> 8); - } - buf[++i] = (char_u)(nr2hex((unsigned)c >> 4)); - buf[++i] = (char_u)(nr2hex((unsigned)c)); - buf[++i] = '>'; - buf[++i] = NUL; + if (c > 255 * 256) { + buf[i++] = (char)nr2hex((unsigned)c >> 20); + buf[i++] = (char)nr2hex((unsigned)c >> 16); + } + buf[i++] = (char)nr2hex((unsigned)c >> 12); + buf[i++] = (char)nr2hex((unsigned)c >> 8); + } + buf[i++] = (char)(nr2hex((unsigned)c >> 4)); + buf[i++] = (char)(nr2hex((unsigned)c)); + buf[i++] = '>'; + buf[i] = NUL; + return i; } -/// Convert the lower 4 bits of byte "c" to its hex character. +/// Convert the lower 4 bits of byte "c" to its hex character +/// /// Lower case letters are used to avoid the confusion of <F1> being 0xf1 or /// function key 1. /// -/// @param c +/// @param[in] n Number to convert. /// /// @return the hex character. -static unsigned nr2hex(unsigned c) +static inline unsigned nr2hex(unsigned n) + FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT { - if ((c & 0xf) <= 9) { - return (c & 0xf) + '0'; + if ((n & 0xf) <= 9) { + return (n & 0xf) + '0'; } - return (c & 0xf) - 10 + 'a'; + return (n & 0xf) - 10 + 'a'; } /// Return number of display cells occupied by byte "b". @@ -863,7 +923,7 @@ bool vim_isprintc(int c) if (c >= 0x100) { return utf_printable(c); } - return c >= 0x100 || (c > 0 && (g_chartab[c] & CT_PRINT_CHAR)); + return c > 0 && (g_chartab[c] & CT_PRINT_CHAR); } /// Strict version of vim_isprintc(c), don't return true if "c" is the head @@ -1671,7 +1731,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, && !STRING_ENDED(ptr + 1) && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { pre = ptr[1]; - // Detect hexadecimal: 0x or 0X follwed by hex digit + // Detect hexadecimal: 0x or 0X followed by hex digit. if ((what & STR2NR_HEX) && !STRING_ENDED(ptr + 2) && (pre == 'X' || pre == 'x') @@ -1679,7 +1739,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, ptr += 2; goto vim_str2nr_hex; } - // Detect binary: 0b or 0B follwed by 0 or 1 + // Detect binary: 0b or 0B followed by 0 or 1. if ((what & STR2NR_BIN) && !STRING_ENDED(ptr + 2) && (pre == 'B' || pre == 'b') @@ -1687,7 +1747,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, ptr += 2; goto vim_str2nr_bin; } - // Detect octal number: zero followed by octal digits without '8' or '9' + // Detect octal number: zero followed by octal digits without '8' or '9'. pre = 0; if (!(what & STR2NR_OCT) || !('0' <= ptr[1] && ptr[1] <= '7')) { @@ -1718,32 +1778,21 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, ptr++; \ } \ } while (0) - switch (pre) { - case 'b': - case 'B': { vim_str2nr_bin: - PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0')); - break; - } - case '0': { + PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0')); + goto vim_str2nr_proceed; vim_str2nr_oct: - PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0')); - break; - } - case 0: { + PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0')); + goto vim_str2nr_proceed; vim_str2nr_dec: - PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0')); - break; - } - case 'x': - case 'X': { + PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0')); + goto vim_str2nr_proceed; vim_str2nr_hex: - PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr))); - break; - } - } + PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr))); + goto vim_str2nr_proceed; #undef PARSE_NUMBER +vim_str2nr_proceed: if (prep != NULL) { *prep = pre; } diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c index 97fc3a3ca3..b45e7002f7 100644 --- a/src/nvim/cursor_shape.c +++ b/src/nvim/cursor_shape.c @@ -102,11 +102,14 @@ char_u *parse_shape_opt(int what) } while (*modep != NUL) { colonp = vim_strchr(modep, ':'); - if (colonp == NULL) + commap = vim_strchr(modep, ','); + + if (colonp == NULL || (commap != NULL && commap < colonp)) { return (char_u *)N_("E545: Missing colon"); - if (colonp == modep) + } + if (colonp == modep) { return (char_u *)N_("E546: Illegal mode"); - commap = vim_strchr(modep, ','); + } // Repeat for all modes before the colon. // For the 'a' mode, we loop to handle all the modes. diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 0ee1c3815d..f9e40ed06f 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -859,9 +859,9 @@ void ex_diffpatch(exarg_T *eap) char_u *esc_name = NULL; #ifdef UNIX - char_u dirbuf[MAXPATHL]; - char_u *fullname = NULL; + char *fullname = NULL; #endif + // We need two temp file names. // Name of original temp file. char_u *tmp_orig = vim_tempname(); @@ -881,21 +881,17 @@ void ex_diffpatch(exarg_T *eap) #ifdef UNIX // Get the absolute path of the patchfile, changing directory below. - fullname = (char_u *)FullName_save((char *)eap->arg, false); -#endif - + fullname = FullName_save((char *)eap->arg, false); esc_name = vim_strsave_shellescape( -#ifdef UNIX - fullname != NULL ? fullname : + (fullname != NULL ? (char_u *)fullname : eap->arg), true, true); +#else + esc_name = vim_strsave_shellescape(eap->arg, true, true); #endif - eap->arg, true, true); - if (esc_name == NULL) { - goto theend; - } size_t buflen = STRLEN(tmp_orig) + STRLEN(esc_name) + STRLEN(tmp_new) + 16; buf = xmalloc(buflen); #ifdef UNIX + char_u dirbuf[MAXPATHL]; // Temporarily chdir to /tmp, to avoid patching files in the current // directory when the patch file contains more than one patch. When we // have our own temp dir use that instead, it will be cleaned up when we @@ -918,7 +914,7 @@ void ex_diffpatch(exarg_T *eap) // Use 'patchexpr' to generate the new file. #ifdef UNIX eval_patch((char *)tmp_orig, - (char *)(fullname != NULL ? fullname : eap->arg), + (fullname != NULL ? fullname : (char *)eap->arg), (char *)tmp_new); #else eval_patch((char *)tmp_orig, (char *)eap->arg, (char *)tmp_new); diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index e3d5ca07d1..6dbb0d05e0 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -1700,6 +1700,9 @@ static void printdigraph(digr_T *dp) *p++ = dp->char1; *p++ = dp->char2; *p++ = ' '; + *p = NUL; + msg_outtrans(buf); + p = buf; // add a space to draw a composing char on if (utf_iscomposing(dp->result)) { @@ -1707,6 +1710,9 @@ static void printdigraph(digr_T *dp) } p += (*mb_char2bytes)(dp->result, p); + *p = NUL; + msg_outtrans_attr(buf, hl_attr(HLF_8)); + p = buf; if (char2cells(dp->result) == 1) { *p++ = ' '; } diff --git a/src/nvim/edit.c b/src/nvim/edit.c index cf4328fe0a..a1987cf2d5 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -847,7 +847,7 @@ static int insert_handle_key(InsertState *s) case ' ': - if (mod_mask != 4) { + if (mod_mask != MOD_MASK_CTRL) { goto normalchar; } // FALLTHROUGH @@ -974,6 +974,10 @@ static int insert_handle_key(InsertState *s) multiqueue_process_events(main_loop.events); break; + case K_COMMAND: // some command + do_cmdline(NULL, getcmdkeycmd, NULL, 0); + break; + case K_HOME: // <Home> case K_KHOME: case K_S_HOME: @@ -1176,6 +1180,14 @@ static int insert_handle_key(InsertState *s) normalchar: // Insert a normal character. + + if (mod_mask == MOD_MASK_ALT || mod_mask == MOD_MASK_META) { + // Unmapped ALT/META chord behaves like ESC+c. #8213 + stuffcharReadbuff(ESC); + stuffcharReadbuff(s->c); + break; + } + if (!p_paste) { // Trigger InsertCharPre. char_u *str = do_insert_char_pre(s->c); @@ -1428,7 +1440,7 @@ static void ins_ctrl_v(void) * line and will not removed by the redraw */ edit_unputchar(); clear_showcmd(); - insert_special(c, FALSE, TRUE); + insert_special(c, true, true); revins_chars++; revins_legal++; } @@ -3611,6 +3623,9 @@ int ins_compl_add_tv(typval_T *const tv, const Direction dir) memset(cptext, 0, sizeof(cptext)); } if (word == NULL || (!aempty && *word == NUL)) { + for (size_t i = 0; i < CPT_COUNT; i++) { + xfree(cptext[i]); + } return FAIL; } return ins_compl_add((char_u *)word, -1, icase, NULL, @@ -5050,13 +5065,11 @@ static void insert_special(int c, int allow_modmask, int ctrlv) char_u *p; int len; - /* - * Special function key, translate into "<Key>". Up to the last '>' is - * inserted with ins_str(), so as not to replace characters in replace - * mode. - * Only use mod_mask for special keys, to avoid things like <S-Space>, - * unless 'allow_modmask' is TRUE. - */ + // Special function key, translate into "<Key>". Up to the last '>' is + // inserted with ins_str(), so as not to replace characters in replace + // mode. + // Only use mod_mask for special keys, to avoid things like <S-Space>, + // unless 'allow_modmask' is TRUE. if (mod_mask & MOD_MASK_CMD) { // Command-key never produces a normal key. allow_modmask = true; } @@ -5252,7 +5265,7 @@ insertchar ( // - need to check for abbreviation: A non-word char after a word-char while ((c = vpeekc()) != NUL && !ISSPECIAL(c) - && (!has_mbyte || MB_BYTE2LEN_CHECK(c) == 1) + && MB_BYTE2LEN(c) == 1 && i < INPUT_BUFLEN && !(p_fkmap && KeyTyped) // Farsi mode mapping moves cursor && (textwidth == 0 diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 4140eebdf6..a3540b3153 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2682,19 +2682,21 @@ void ex_call(exarg_T *eap) return; } - tofree = trans_function_name(&arg, eap->skip, TFN_INT, &fudi, &partial); + tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial); if (fudi.fd_newkey != NULL) { - /* Still need to give an error message for missing key. */ + // Still need to give an error message for missing key. EMSG2(_(e_dictkey), fudi.fd_newkey); xfree(fudi.fd_newkey); } - if (tofree == NULL) + if (tofree == NULL) { return; + } - /* Increase refcount on dictionary, it could get deleted when evaluating - * the arguments. */ - if (fudi.fd_dict != NULL) - ++fudi.fd_dict->dv_refcount; + // Increase refcount on dictionary, it could get deleted when evaluating + // the arguments. + if (fudi.fd_dict != NULL) { + fudi.fd_dict->dv_refcount++; + } // If it is the name of a variable of type VAR_FUNC or VAR_PARTIAL use its // contents. For VAR_PARTIAL get its partial, unless we already have one @@ -2703,8 +2705,8 @@ void ex_call(exarg_T *eap) name = deref_func_name((const char *)tofree, &len, partial != NULL ? NULL : &partial, false); - /* Skip white space to allow ":call func ()". Not good, but required for - * backward compatibility. */ + // Skip white space to allow ":call func ()". Not good, but required for + // backward compatibility. startarg = skipwhite(arg); rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this. @@ -2713,20 +2715,9 @@ void ex_call(exarg_T *eap) goto end; } - /* - * When skipping, evaluate the function once, to find the end of the - * arguments. - * When the function takes a range, this is discovered after the first - * call, and the loop is broken. - */ - if (eap->skip) { - emsg_skip++; - lnum = eap->line2; // Do it once, also with an invalid range. - } else { - lnum = eap->line1; - } + lnum = eap->line1; for (; lnum <= eap->line2; lnum++) { - if (!eap->skip && eap->addr_count > 0) { // -V560 + if (eap->addr_count > 0) { // -V560 curwin->w_cursor.lnum = lnum; curwin->w_cursor.col = 0; curwin->w_cursor.coladd = 0; @@ -2734,40 +2725,40 @@ void ex_call(exarg_T *eap) arg = startarg; if (get_func_tv(name, (int)STRLEN(name), &rettv, &arg, eap->line1, eap->line2, &doesrange, - !eap->skip, partial, fudi.fd_dict) == FAIL) { + true, partial, fudi.fd_dict) == FAIL) { failed = true; break; } // Handle a function returning a Funcref, Dictionary or List. - if (handle_subscript((const char **)&arg, &rettv, !eap->skip, true) + if (handle_subscript((const char **)&arg, &rettv, true, true) == FAIL) { failed = true; break; } tv_clear(&rettv); - if (doesrange || eap->skip) { // -V560 + if (doesrange) { break; } - /* Stop when immediately aborting on error, or when an interrupt - * occurred or an exception was thrown but not caught. - * get_func_tv() returned OK, so that the check for trailing - * characters below is executed. */ - if (aborting()) + // Stop when immediately aborting on error, or when an interrupt + // occurred or an exception was thrown but not caught. + // get_func_tv() returned OK, so that the check for trailing + // characters below is executed. + if (aborting()) { break; + } } - if (eap->skip) - --emsg_skip; if (!failed) { - /* Check for trailing illegal characters and a following command. */ + // Check for trailing illegal characters and a following command. if (!ends_excmd(*arg)) { emsg_severe = TRUE; EMSG(_(e_trailing)); - } else + } else { eap->nextcmd = check_nextcmd(arg); + } } end: @@ -5807,8 +5798,8 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) 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)); + fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); + pt = xcalloc(1, sizeof(partial_T)); ga_init(&newlines, (int)sizeof(char_u *), 1); ga_grow(&newlines, 1); @@ -6250,20 +6241,21 @@ bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) /// 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. +/// @return FAIL if function cannot be called, else OK (even if an error +/// occurred while executing the function! Set `msg_list` to capture +/// the error, see do_cmdline()). int call_func( const char_u *funcname, // name of the function int len, // length of "name" - typval_T *rettv, // return value goes here + typval_T *rettv, // [out] value goes here int argcount_in, // number of "argvars" typval_T *argvars_in, // vars for arguments, must have "argcount" // PLUS ONE elements! ArgvFunc argv_func, // function to fill in argvars linenr_T firstline, // first line of range linenr_T lastline, // last line of range - int *doesrange, // return: function handled range + int *doesrange, // [out] function handled range bool evaluate, partial_T *partial, // optional, can be NULL dict_T *selfdict_in // Dictionary for "self" @@ -6437,21 +6429,25 @@ call_func( return ret; } -/* - * Give an error message with a function name. Handle <SNR> things. - * "ermsg" is to be passed without translation, use N_() instead of _(). - */ +/// Give an error message with a function name. Handle <SNR> things. +/// +/// @param ermsg must be passed without translation (use N_() instead of _()). +/// @param name function name static void emsg_funcname(char *ermsg, char_u *name) { - char_u *p; + char_u *p; - if (*name == K_SPECIAL) + if (*name == K_SPECIAL) { p = concat_str((char_u *)"<SNR>", name + 3); - else + } else { p = name; + } + EMSG2(_(ermsg), p); - if (p != name) + + if (p != name) { xfree(p); + } } /* @@ -13496,7 +13492,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) q[-1] = NUL; q = (char *)path_tail((char_u *)p); } - if (q > p && !path_is_absolute_path((const char_u *)buf)) { + if (q > p && !path_is_absolute((const char_u *)buf)) { // Symlink is relative to directory of argument. Replace the // symlink with the resolved name in the same directory. const size_t p_len = strlen(p); @@ -13814,7 +13810,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) scid_T save_current_SID; uint8_t *save_sourcing_name, *save_autocmd_fname, *save_autocmd_match; linenr_T save_sourcing_lnum; - int save_autocmd_fname_full, save_autocmd_bufnr; + int save_autocmd_bufnr; void *save_funccalp; if (l_provider_call_nesting) { @@ -13825,16 +13821,14 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) save_sourcing_lnum = sourcing_lnum; save_autocmd_fname = autocmd_fname; save_autocmd_match = autocmd_match; - save_autocmd_fname_full = autocmd_fname_full; save_autocmd_bufnr = autocmd_bufnr; save_funccalp = save_funccal(); - // + current_SID = provider_caller_scope.SID; sourcing_name = provider_caller_scope.sourcing_name; sourcing_lnum = provider_caller_scope.sourcing_lnum; autocmd_fname = provider_caller_scope.autocmd_fname; autocmd_match = provider_caller_scope.autocmd_match; - autocmd_fname_full = provider_caller_scope.autocmd_fname_full; autocmd_bufnr = provider_caller_scope.autocmd_bufnr; restore_funccal(provider_caller_scope.funccalp); } @@ -13850,7 +13844,6 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) sourcing_lnum = save_sourcing_lnum; autocmd_fname = save_autocmd_fname; autocmd_match = save_autocmd_match; - autocmd_fname_full = save_autocmd_fname_full; autocmd_bufnr = save_autocmd_bufnr; restore_funccal(save_funccalp); } @@ -14406,8 +14399,11 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; if (argvars[0].vval.v_string) { - server_stop((char *) argvars[0].vval.v_string); + bool rv = server_stop((char *)argvars[0].vval.v_string); + rettv->vval.v_number = (rv ? 1 : 0); } } @@ -15456,7 +15452,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) ; li != NULL;) { listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); if (item_compare_func_ptr(&prev_li, &li) == 0) { - if (info.item_compare_func_err) { + if (info.item_compare_func_err) { // -V547 EMSG(_("E882: Uniq compare function failed")); break; } @@ -15630,7 +15626,6 @@ f_spellsuggest_return: static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - regmatch_T regmatch; char_u *save_cpo; int match; colnr_T col = 0; @@ -15663,9 +15658,13 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - regmatch.regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING); + regmatch_T regmatch = { + .regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING), + .startp = { NULL }, + .endp = { NULL }, + .rm_ic = false, + }; if (regmatch.regprog != NULL) { - regmatch.rm_ic = FALSE; while (*str != NUL || keepempty) { if (*str == NUL) { match = false; // Empty item at the end. @@ -15684,8 +15683,9 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) && end < (const char *)regmatch.endp[0])) { tv_list_append_string(rettv->vval.v_list, str, end - str); } - if (!match) + if (!match) { break; + } // Advance to just after the match. if (regmatch.endp[0] > (char_u *)str) { col = 0; @@ -15702,6 +15702,56 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) p_cpo = save_cpo; } +/// "stdpath()" helper for list results +static void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) + FUNC_ATTR_NONNULL_ALL +{ + const void *iter = NULL; + list_T *const list = tv_list_alloc(kListLenShouldKnow); + rettv->v_type = VAR_LIST; + rettv->vval.v_list = list; + tv_list_ref(list); + char *const dirs = stdpaths_get_xdg_var(xdg); + do { + size_t dir_len; + const char *dir; + iter = vim_env_iter(':', dirs, iter, &dir, &dir_len); + if (dir != NULL && dir_len > 0) { + char *dir_with_nvim = xmemdupz(dir, dir_len); + dir_with_nvim = concat_fnames_realloc(dir_with_nvim, "nvim", true); + tv_list_append_string(list, dir_with_nvim, strlen(dir_with_nvim)); + xfree(dir_with_nvim); + } + } while (iter != NULL); + xfree(dirs); +} + +/// "stdpath(type)" function +static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + const char *const p = tv_get_string_chk(&argvars[0]); + if (p == NULL) { + return; // Type error; errmsg already given. + } + + if (strcmp(p, "config") == 0) { + rettv->vval.v_string = (char_u *)get_xdg_home(kXDGConfigHome); + } else if (strcmp(p, "data") == 0) { + rettv->vval.v_string = (char_u *)get_xdg_home(kXDGDataHome); + } else if (strcmp(p, "cache") == 0) { + rettv->vval.v_string = (char_u *)get_xdg_home(kXDGCacheHome); + } else if (strcmp(p, "config_dirs") == 0) { + get_xdg_var_list(kXDGConfigDirs, rettv); + } else if (strcmp(p, "data_dirs") == 0) { + get_xdg_var_list(kXDGDataDirs, rettv); + } else { + EMSG2(_("E6100: \"%s\" is not a valid stdpath"), p); + } +} + /* * "str2float()" function */ @@ -16093,7 +16143,7 @@ static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_strtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; - rettv->vval.v_string = transstr((char_u *)tv_get_string(&argvars[0])); + rettv->vval.v_string = (char_u *)transstr(tv_get_string(&argvars[0])); } /* @@ -16864,6 +16914,12 @@ static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr) int paused = (bool)tv_get_number(&argvars[1]); timer_T *timer = pmap_get(uint64_t)(timers, tv_get_number(&argvars[0])); if (timer != NULL) { + if (!timer->paused && paused) { + time_watcher_stop(&timer->tw); + } else if (timer->paused && !paused) { + time_watcher_start(&timer->tw, timer_due_cb, timer->timeout, + timer->timeout); + } timer->paused = paused; } } @@ -16983,7 +17039,8 @@ static void timer_stop(timer_T *timer) time_watcher_close(&timer->tw, timer_close_cb); } -// invoked on next event loop tick, so queue is empty +// This will be run on the main loop after the last timer_due_cb, so at this +// point it is safe to free the callback. static void timer_close_cb(TimeWatcher *tw, void *data) { timer_T *timer = (timer_T *)data; @@ -18725,7 +18782,7 @@ static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, if (name_len == 0) { return NULL; } - if (name_len == 1 || (name_len >= 2 && name[1] != ':')) { + if (name_len == 1 || name[1] != ':') { // name has implicit scope if (name[0] == ':' || name[0] == AUTOLOAD_CHAR) { // The name must not start with a colon or #. @@ -20056,7 +20113,7 @@ void ex_function(exarg_T *eap) } } - fp = xcalloc(1, sizeof(ufunc_T) + STRLEN(name)); + fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); if (fudi.fd_dict != NULL) { if (fudi.fd_di == NULL) { @@ -20811,8 +20868,9 @@ char_u *get_user_func_name(expand_T *xp, int idx) return (char_u *)""; // don't show dict and lambda functions } - if (STRLEN(fp->uf_name) + 4 >= IOSIZE) - return fp->uf_name; /* prevents overflow */ + if (STRLEN(fp->uf_name) + 4 >= IOSIZE) { + return fp->uf_name; // Prevent overflow. + } cat_func_name(IObuff, fp); if (xp->xp_context != EXPAND_USER_FUNC) { @@ -22009,12 +22067,27 @@ int store_session_globals(FILE *fd) */ void last_set_msg(scid_T scriptID) { - if (scriptID != 0) { - char_u *p = home_replace_save(NULL, get_scriptname(scriptID)); + const LastSet last_set = (LastSet){ + .script_id = scriptID, + .channel_id = 0, + }; + option_last_set_msg(last_set); +} + +/// Displays where an option was last set. +/// +/// Should only be invoked when 'verbose' is non-zero. +void option_last_set_msg(LastSet last_set) +{ + if (last_set.script_id != 0) { + bool should_free; + char_u *p = get_scriptname(last_set, &should_free); verbose_enter(); MSG_PUTS(_("\n\tLast set from ")); MSG_PUTS(p); - xfree(p); + if (should_free) { + xfree(p); + } verbose_leave(); } } @@ -22448,7 +22521,6 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) .sourcing_lnum = sourcing_lnum, .autocmd_fname = autocmd_fname, .autocmd_match = autocmd_match, - .autocmd_fname_full = autocmd_fname_full, .autocmd_bufnr = autocmd_bufnr, .funccalp = save_funccal() }; @@ -22481,7 +22553,8 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) restore_funccal(provider_caller_scope.funccalp); provider_caller_scope = saved_provider_caller_scope; provider_call_nesting--; - + assert(provider_call_nesting >= 0); + return rettv; } @@ -22521,11 +22594,13 @@ bool eval_has_provider(const char *name) } /// Writes "<sourcing_name>:<sourcing_lnum>" to `buf[bufsize]`. -void eval_format_source_name_line(char *buf, size_t bufsize) +void eval_fmt_source_name_line(char *buf, size_t bufsize) { - snprintf(buf, bufsize, "%s:%" PRIdLINENR, - (sourcing_name ? sourcing_name : (char_u *)"?"), - (sourcing_name ? sourcing_lnum : 0)); + if (sourcing_name) { + snprintf(buf, bufsize, "%s:%" PRIdLINENR, sourcing_name, sourcing_lnum); + } else { + snprintf(buf, bufsize, "?"); + } } /// ":checkhealth [plugins]" diff --git a/src/nvim/eval.h b/src/nvim/eval.h index b798eae187..149dae688e 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -10,6 +10,7 @@ #include "nvim/event/rstream.h" #include "nvim/event/wstream.h" #include "nvim/channel.h" +#include "nvim/os/stdpaths_defs.h" #define COPYID_INC 2 #define COPYID_MASK (~0x1) diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index daa3b637a3..801d2cc468 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -280,6 +280,7 @@ return { spellsuggest={args={1, 3}}, split={args={1, 3}}, sqrt={args=1, func="float_op_wrapper", data="&sqrt"}, + stdpath={args=1}, str2float={args=1}, str2nr={args={1, 2}}, strcharpart={args={2, 3}}, diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index 17799b500c..4d75c7bda1 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -985,7 +985,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) break; } case MSGPACK_OBJECT_NEGATIVE_INTEGER: { - if (mobj.via.i64 >= VARNUMBER_MIN) { + if (mobj.via.i64 >= VARNUMBER_MIN) { // -V547 *rettv = (typval_T) { .v_type = VAR_NUMBER, .v_lock = VAR_UNLOCKED, diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index c8b550f902..7930653be0 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2825,7 +2825,7 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf) { switch (tv->v_type) { case VAR_NUMBER: { - snprintf(buf, NUMBUFLEN, "%" PRIdVARNUMBER, tv->vval.v_number); + snprintf(buf, NUMBUFLEN, "%" PRIdVARNUMBER, tv->vval.v_number); // -V576 return buf; } case VAR_STRING: { diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 40a1738d9e..33e2aa6b61 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -197,20 +197,11 @@ typedef struct { }, \ } -// 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!) -}; - -#define TV_DICTITEM_STRUCT(KEY_LEN) \ +#define TV_DICTITEM_STRUCT(...) \ struct { \ typval_T di_tv; /* Structure that holds scope dictionary itself. */ \ uint8_t di_flags; /* Flags. */ \ - char_u di_key[KEY_LEN]; /* Key value. */ \ + char_u di_key[__VA_ARGS__]; /* Key value. */ \ } /// Structure to hold a scope dictionary @@ -286,9 +277,8 @@ struct ufunc { ///< 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) + char_u uf_name[]; ///< Name of function; can start with <SNR>123_ + ///< (<SNR> is K_SPECIAL KS_EXTRA KE_SNR) }; /// Maximum number of function arguments diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index f2d0d7265f..4556ce8193 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -340,8 +340,9 @@ 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 : partial_name(pt))); - _mp_push(*mpstack, ((MPConvStackVal) { + TYPVAL_ENCODE_CONV_FUNC_START( // -V547 + tv, (pt == NULL ? NULL : partial_name(pt))); + _mp_push(*mpstack, ((MPConvStackVal) { // -V779 .type = kMPConvPartial, .tv = tv, .saved_copyID = copyID - 1, @@ -541,7 +542,8 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( } list_T *const val_list = val_di->di_tv.vval.v_list; if (val_list == NULL || tv_list_len(val_list) == 0) { - TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, TYPVAL_ENCODE_NODICT_VAR); + TYPVAL_ENCODE_CONV_EMPTY_DICT( // -V501 + tv, TYPVAL_ENCODE_NODICT_VAR); break; } TV_LIST_ITER_CONST(val_list, li, { diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index c101cb1bb9..ffe2db9b76 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -26,15 +26,18 @@ int libuv_process_spawn(LibuvProcess *uvproc) uvproc->uvopts.file = proc->argv[0]; uvproc->uvopts.args = proc->argv; uvproc->uvopts.flags = UV_PROCESS_WINDOWS_HIDE; - if (proc->detach) { - uvproc->uvopts.flags |= UV_PROCESS_DETACHED; - } #ifdef WIN32 // libuv collapses the argv to a CommandLineToArgvW()-style string. cmd.exe // expects a different syntax (must be prepared by the caller before now). if (os_shell_is_cmdexe(proc->argv[0])) { uvproc->uvopts.flags |= UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS; } + if (proc->detach) { + uvproc->uvopts.flags |= UV_PROCESS_DETACHED; + } +#else + // Always setsid() on unix-likes. #8107 + uvproc->uvopts.flags |= UV_PROCESS_DETACHED; #endif uvproc->uvopts.exit_cb = exit_cb; uvproc->uvopts.cwd = proc->cwd; @@ -48,12 +51,19 @@ int libuv_process_spawn(LibuvProcess *uvproc) if (!proc->in.closed) { uvproc->uvstdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; +#ifdef WIN32 + uvproc->uvstdio[0].flags |= UV_OVERLAPPED_PIPE; +#endif uvproc->uvstdio[0].data.stream = STRUCT_CAST(uv_stream_t, &proc->in.uv.pipe); } if (!proc->out.closed) { uvproc->uvstdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; +#ifdef WIN32 + // pipe must be readable for IOCP to work. + uvproc->uvstdio[1].flags |= UV_READABLE_PIPE | UV_OVERLAPPED_PIPE; +#endif uvproc->uvstdio[1].data.stream = STRUCT_CAST(uv_stream_t, &proc->out.uv.pipe); } diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c index d92464f17b..7998e0b8d0 100644 --- a/src/nvim/event/loop.c +++ b/src/nvim/event/loop.c @@ -21,7 +21,6 @@ void loop_init(Loop *loop, void *data) loop->recursive = 0; loop->uv.data = loop; loop->children = kl_init(WatcherPtr); - loop->children_stop_requests = 0; loop->events = multiqueue_new_parent(loop_on_put, loop); loop->fast_events = multiqueue_new_child(loop->events); loop->thread_events = multiqueue_new_parent(NULL, NULL); diff --git a/src/nvim/event/loop.h b/src/nvim/event/loop.h index d1a40d5cc9..f5dd23ac8b 100644 --- a/src/nvim/event/loop.h +++ b/src/nvim/event/loop.h @@ -37,7 +37,6 @@ typedef struct loop { // generic timer, used by loop_poll_events() uv_timer_t poll_timer; - size_t children_stop_requests; uv_async_t async; uv_mutex_t mutex; int recursive; @@ -53,6 +52,8 @@ typedef struct loop { } \ } while (0) +// -V:LOOP_PROCESS_EVENTS_UNTIL:547 + // Poll for events until a condition or timeout #define LOOP_PROCESS_EVENTS_UNTIL(loop, multiqueue, timeout, condition) \ do { \ diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index a06f5f4ff3..7a8a39dbcf 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -12,6 +12,7 @@ #include "nvim/event/wstream.h" #include "nvim/event/process.h" #include "nvim/event/libuv_process.h" +#include "nvim/os/process.h" #include "nvim/os/pty_process.h" #include "nvim/globals.h" #include "nvim/macros.h" @@ -22,7 +23,7 @@ #endif // Time for a process to exit cleanly before we send KILL. -// For pty processes SIGTERM is sent first (in case SIGHUP was not enough). +// For PTY processes SIGTERM is sent first (in case SIGHUP was not enough). #define KILL_TIMEOUT_MS 2000 static bool process_is_tearing_down = false; @@ -110,6 +111,7 @@ int process_spawn(Process *proc, bool in, bool out, bool err) proc->internal_close_cb = decref; proc->refcount++; kl_push(WatcherPtr, proc->loop->children, proc); + DLOG("new: pid=%d argv=[%s]", proc->pid, *proc->argv); return 0; } @@ -187,8 +189,7 @@ int process_wait(Process *proc, int ms, MultiQueue *events) } if (proc->refcount == 1) { - // Job exited, collect status and manually invoke close_cb to free the job - // resources + // Job exited, free its resources. decref(proc); if (events) { // the decref call created an exit event, process it now @@ -204,19 +205,19 @@ int process_wait(Process *proc, int ms, MultiQueue *events) /// Ask a process to terminate and eventually kill if it doesn't respond void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL { - if (proc->stopped_time) { + bool exited = (proc->status >= 0); + if (exited || proc->stopped_time) { return; } - proc->stopped_time = os_hrtime(); + switch (proc->type) { case kProcessTypeUv: // Close the process's stdin. If the process doesn't close its own // stdout/stderr, they will be closed when it exits(possibly due to being // terminated after a timeout) stream_may_close(&proc->in); - ILOG("Sending SIGTERM to pid %d", proc->pid); - uv_kill(proc->pid, SIGTERM); + os_proc_tree_kill(proc->pid, SIGTERM); break; case kProcessTypePty: // close all streams for pty processes to send SIGHUP to the process @@ -227,37 +228,32 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL abort(); } - Loop *loop = proc->loop; - if (!loop->children_stop_requests++) { - // When there's at least one stop request pending, start a timer that - // will periodically check if a signal should be send to the job. - ILOG("Starting job kill timer"); - uv_timer_start(&loop->children_kill_timer, children_kill_cb, - KILL_TIMEOUT_MS, KILL_TIMEOUT_MS); - } + // (Re)start timer to verify that stopped process(es) died. + uv_timer_start(&proc->loop->children_kill_timer, children_kill_cb, + KILL_TIMEOUT_MS, 0); } -/// Iterates the process list sending SIGTERM to stopped processes and SIGKILL -/// to those that didn't die from SIGTERM after a while(exit_timeout is 0). +/// Sends SIGKILL (or SIGTERM..SIGKILL for PTY jobs) to processes that did +/// not terminate after process_stop(). static void children_kill_cb(uv_timer_t *handle) { Loop *loop = handle->loop->data; - uint64_t now = os_hrtime(); kl_iter(WatcherPtr, loop->children, current) { Process *proc = (*current)->data; - if (!proc->stopped_time) { + bool exited = (proc->status >= 0); + if (exited || !proc->stopped_time) { continue; } - uint64_t elapsed = (now - proc->stopped_time) / 1000000 + 1; - - if (elapsed >= KILL_TIMEOUT_MS) { - int sig = proc->type == kProcessTypePty && elapsed < KILL_TIMEOUT_MS * 2 - ? SIGTERM - : SIGKILL; - ILOG("Sending %s to pid %d", sig == SIGTERM ? "SIGTERM" : "SIGKILL", - proc->pid); - uv_kill(proc->pid, sig); + uint64_t term_sent = UINT64_MAX == proc->stopped_time; + if (kProcessTypePty != proc->type || term_sent) { + os_proc_tree_kill(proc->pid, SIGKILL); + } else { + os_proc_tree_kill(proc->pid, SIGTERM); + proc->stopped_time = UINT64_MAX; // Flag: SIGTERM was sent. + // Restart timer. + uv_timer_start(&proc->loop->children_kill_timer, children_kill_cb, + KILL_TIMEOUT_MS, 0); } } } @@ -269,7 +265,7 @@ static void process_close_event(void **argv) if (proc->type == kProcessTypePty) { xfree(((PtyProcess *)proc)->term_name); } - if (proc->cb) { + if (proc->cb) { // "on_exit" for jobstart(). See channel_job_start(). proc->cb(proc, proc->status, proc->data); } } @@ -360,7 +356,7 @@ static void flush_stream(Process *proc, Stream *stream) } // Stream can be closed if it is empty. - if (num_bytes == stream->num_bytes) { + if (num_bytes == stream->num_bytes) { // -V547 if (stream->read_cb && !stream->did_eof) { // Stream callback could miss EOF handling if a child keeps the stream // open. But only send EOF if we haven't already. @@ -385,12 +381,8 @@ static void process_close_handles(void **argv) static void on_process_exit(Process *proc) { Loop *loop = proc->loop; - if (proc->stopped_time && loop->children_stop_requests - && !--loop->children_stop_requests) { - // Stop the timer if no more stop requests are pending - DLOG("Stopping process kill timer"); - uv_timer_stop(&loop->children_kill_timer); - } + ILOG("exited: pid=%d status=%d stoptime=%" PRIu64, proc->pid, proc->status, + proc->stopped_time); // Process has terminated, but there could still be data to be read from the // OS. We are still in the libuv loop, so we cannot call code that polls for diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h index 033ce3604b..ba2c2a6a11 100644 --- a/src/nvim/event/process.h +++ b/src/nvim/event/process.h @@ -19,8 +19,7 @@ struct process { Loop *loop; void *data; int pid, status, refcount; - // set to the hrtime of when process_stop was called for the process. - uint64_t stopped_time; + uint64_t stopped_time; // process_stop() timestamp const char *cwd; char **argv; Stream in, out, err; diff --git a/src/nvim/event/time.c b/src/nvim/event/time.c index 80289c27d1..b7e30e392b 100644 --- a/src/nvim/event/time.c +++ b/src/nvim/event/time.c @@ -61,10 +61,17 @@ static void time_watcher_cb(uv_timer_t *handle) CREATE_EVENT(watcher->events, time_event, 1, watcher); } +static void close_event(void **argv) +{ + TimeWatcher *watcher = argv[0]; + watcher->close_cb(watcher, watcher->data); +} + static void close_cb(uv_handle_t *handle) + FUNC_ATTR_NONNULL_ALL { TimeWatcher *watcher = handle->data; if (watcher->close_cb) { - watcher->close_cb(watcher, watcher->data); + CREATE_EVENT(watcher->events, close_event, 1, watcher); } } diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index c396b5891a..f575d58f05 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -109,82 +109,86 @@ typedef struct { # include "ex_cmds.c.generated.h" #endif -/* - * ":ascii" and "ga". - */ -void do_ascii(exarg_T *eap) +/// ":ascii" and "ga" implementation +void do_ascii(const exarg_T *const eap) { - int c; - int cval; - char buf1[20]; - char buf2[20]; - char_u buf3[7]; int cc[MAX_MCO]; - int ci = 0; - int len; - const bool l_enc_utf8 = enc_utf8; - - if (l_enc_utf8) - c = utfc_ptr2char(get_cursor_pos_ptr(), cc); - else - c = gchar_cursor(); + int c = utfc_ptr2char(get_cursor_pos_ptr(), cc); if (c == NUL) { MSG("NUL"); return; } - IObuff[0] = NUL; - if (!has_mbyte || (enc_dbcs != 0 && c < 0x100) || c < 0x80) { - if (c == NL) /* NUL is stored as NL */ + size_t iobuff_len = 0; + + int ci = 0; + if (c < 0x80) { + if (c == NL) { // NUL is stored as NL. c = NUL; - if (c == CAR && get_fileformat(curbuf) == EOL_MAC) - cval = NL; /* NL is stored as CR */ - else - cval = c; - if (vim_isprintc_strict(c) && (c < ' ' - || c > '~' - )) { + } + const int cval = (c == CAR && get_fileformat(curbuf) == EOL_MAC + ? NL // NL is stored as CR. + : c); + char buf1[20]; + if (vim_isprintc_strict(c) && (c < ' ' || c > '~')) { + char_u buf3[7]; transchar_nonprint(buf3, c); vim_snprintf(buf1, sizeof(buf1), " <%s>", (char *)buf3); - } else + } else { buf1[0] = NUL; - if (c >= 0x80) - vim_snprintf(buf2, sizeof(buf2), " <M-%s>", - (char *)transchar(c & 0x7f)); - else - buf2[0] = NUL; - vim_snprintf((char *)IObuff, IOSIZE, - _("<%s>%s%s %d, Hex %02x, Octal %03o"), - transchar(c), buf1, buf2, cval, cval, cval); - if (l_enc_utf8) - c = cc[ci++]; - else - c = 0; - } - - /* Repeat for combining characters. */ - while (has_mbyte && (c >= 0x100 || (l_enc_utf8 && c >= 0x80))) { - len = (int)STRLEN(IObuff); - /* This assumes every multi-byte char is printable... */ - if (len > 0) - IObuff[len++] = ' '; - IObuff[len++] = '<'; - if (l_enc_utf8 && utf_iscomposing(c) -# ifdef USE_GUI - && !gui.in_use -# endif - ) - IObuff[len++] = ' '; /* draw composing char on top of a space */ - len += (*mb_char2bytes)(c, IObuff + len); - vim_snprintf((char *)IObuff + len, IOSIZE - len, - c < 0x10000 ? _("> %d, Hex %04x, Octal %o") - : _("> %d, Hex %08x, Octal %o"), c, c, c); - if (ci == MAX_MCO) + } + char buf2[20]; + buf2[0] = NUL; + iobuff_len += ( + vim_snprintf((char *)IObuff + iobuff_len, sizeof(IObuff) - iobuff_len, + _("<%s>%s%s %d, Hex %02x, Octal %03o"), + transchar(c), buf1, buf2, cval, cval, cval)); + c = cc[ci++]; + } + +#define SPACE_FOR_DESC (1 + 1 + 1 + MB_MAXBYTES + 16 + 4 + 3 + 3 + 1) + // Space for description: + // - 1 byte for separator (starting from second entry) + // - 1 byte for "<" + // - 1 byte for space to draw composing character on (optional, but really + // mostly required) + // - up to MB_MAXBYTES bytes for character itself + // - 16 bytes for raw text ("> , Hex , Octal "). + // - at least 4 bytes for hexadecimal representation + // - at least 3 bytes for decimal representation + // - at least 3 bytes for octal representation + // - 1 byte for NUL + // + // Taking into account MAX_MCO and characters which need 8 bytes for + // hexadecimal representation, but not taking translation into account: + // resulting string will occupy less then 400 bytes (conservative estimate). + // + // Less then 1000 bytes if translation multiplies number of bytes needed for + // raw text by 6, so it should always fit into 1025 bytes reserved for IObuff. + + // Repeat for combining characters, also handle multiby here. + while (c >= 0x80 && iobuff_len < sizeof(IObuff) - SPACE_FOR_DESC) { + // This assumes every multi-byte char is printable... + if (iobuff_len > 0) { + IObuff[iobuff_len++] = ' '; + } + IObuff[iobuff_len++] = '<'; + if (utf_iscomposing(c)) { + IObuff[iobuff_len++] = ' '; // Draw composing char on top of a space. + } + iobuff_len += utf_char2bytes(c, IObuff + iobuff_len); + iobuff_len += ( + vim_snprintf((char *)IObuff + iobuff_len, sizeof(IObuff) - iobuff_len, + (c < 0x10000 + ? _("> %d, Hex %04x, Octal %o") + : _("> %d, Hex %08x, Octal %o")), c, c, c)); + if (ci == MAX_MCO) { break; - if (l_enc_utf8) - c = cc[ci++]; - else - c = 0; + } + c = cc[ci++]; + } + if (ci != MAX_MCO && c != 0) { + xstrlcpy((char *)IObuff + iobuff_len, " ...", sizeof(IObuff) - iobuff_len); } msg(IObuff); @@ -2018,13 +2022,13 @@ int getfile(int fnum, char_u *ffname, char_u *sfname, int setpm, linenr_T lnum, } if (other && !forceit && curbuf->b_nwindows == 1 && !buf_hide(curbuf) && curbufIsChanged() && autowrite(curbuf, forceit) == FAIL) { - if (p_confirm && p_write) - dialog_changed(curbuf, FALSE); + if (p_confirm && p_write) { + dialog_changed(curbuf, false); + } if (curbufIsChanged()) { - if (other) - --no_wait_return; + no_wait_return--; EMSG(_(e_nowrtmsg)); - retval = 2; /* file has been changed */ + retval = 2; // File has been changed. goto theend; } } diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 821c050c50..96d2102156 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -3055,24 +3055,32 @@ void scriptnames_slash_adjust(void) # endif /// Get a pointer to a script name. Used for ":verbose set". -char_u *get_scriptname(scid_T id) -{ - if (id == SID_MODELINE) { - return (char_u *)_("modeline"); - } - if (id == SID_CMDARG) { - return (char_u *)_("--cmd argument"); - } - if (id == SID_CARG) { - return (char_u *)_("-c argument"); - } - if (id == SID_ENV) { - return (char_u *)_("environment variable"); - } - if (id == SID_ERROR) { - return (char_u *)_("error handler"); +char_u *get_scriptname(LastSet last_set, bool *should_free) +{ + *should_free = false; + + switch (last_set.script_id) { + case SID_MODELINE: + return (char_u *)_("modeline"); + case SID_CMDARG: + return (char_u *)_("--cmd argument"); + case SID_CARG: + return (char_u *)_("-c argument"); + case SID_ENV: + return (char_u *)_("environment variable"); + case SID_ERROR: + return (char_u *)_("error handler"); + case SID_LUA: + return (char_u *)_("Lua"); + case SID_API_CLIENT: + vim_snprintf((char *)IObuff, IOSIZE, + _("API client (channel id %" PRIu64 ")"), + last_set.channel_id); + return IObuff; + default: + *should_free = true; + return home_replace_save(NULL, SCRIPT_ITEM(last_set.script_id).sn_name); } - return SCRIPT_ITEM(id).sn_name; } # if defined(EXITFREE) diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index e1d28b4a9c..52b810085c 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -73,6 +73,7 @@ #include "nvim/shada.h" #include "nvim/lua/executor.h" #include "nvim/globals.h" +#include "nvim/api/private/helpers.h" static int quitmore = 0; static int ex_pressedreturn = FALSE; @@ -133,7 +134,6 @@ struct dbg_stuff { char_u *vv_throwpoint; int did_emsg; int got_int; - int did_throw; int need_rethrow; int check_cstack; except_T *current_exception; @@ -165,12 +165,11 @@ static void save_dbg_stuff(struct dbg_stuff *dsp) dsp->vv_exception = v_exception(NULL); dsp->vv_throwpoint = v_throwpoint(NULL); - /* Necessary for debugging an inactive ":catch", ":finally", ":endtry" */ - dsp->did_emsg = did_emsg; did_emsg = FALSE; - dsp->got_int = got_int; got_int = FALSE; - dsp->did_throw = did_throw; did_throw = FALSE; - dsp->need_rethrow = need_rethrow; need_rethrow = FALSE; - dsp->check_cstack = check_cstack; check_cstack = FALSE; + // Necessary for debugging an inactive ":catch", ":finally", ":endtry". + dsp->did_emsg = did_emsg; did_emsg = false; + dsp->got_int = got_int; got_int = false; + dsp->need_rethrow = need_rethrow; need_rethrow = false; + dsp->check_cstack = check_cstack; check_cstack = false; dsp->current_exception = current_exception; current_exception = NULL; } @@ -184,7 +183,6 @@ static void restore_dbg_stuff(struct dbg_stuff *dsp) (void)v_throwpoint(dsp->vv_throwpoint); did_emsg = dsp->did_emsg; got_int = dsp->got_int; - did_throw = dsp->did_throw; need_rethrow = dsp->need_rethrow; check_cstack = dsp->check_cstack; current_exception = dsp->current_exception; @@ -400,16 +398,11 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, initial_trylevel = trylevel; - /* - * "did_throw" will be set to TRUE when an exception is being thrown. - */ - did_throw = FALSE; - /* - * "did_emsg" will be set to TRUE when emsg() is used, in which case we - * cancel the whole command line, and any if/endif or loop. - * If force_abort is set, we cancel everything. - */ - did_emsg = FALSE; + current_exception = NULL; + // "did_emsg" will be set to TRUE when emsg() is used, in which case we + // cancel the whole command line, and any if/endif or loop. + // If force_abort is set, we cancel everything. + did_emsg = false; /* * KeyTyped is only set when calling vgetc(). Reset it here when not @@ -611,6 +604,11 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, cmd_getline, cmd_cookie); recursive--; + // Ignore trailing '|'-separated commands in preview-mode ('inccommand'). + if (State & CMDPREVIEW) { + next_cmdline = NULL; + } + if (cmd_cookie == (void *)&cmd_loop_cookie) /* Use "current_line" from "cmd_loop_cookie", it may have been * incremented when defining a function. */ @@ -659,7 +657,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, * not to use a cs_line[] from an entry that isn't a ":while" * or ":for": It would make "current_line" invalid and can * cause a crash. */ - if (!did_emsg && !got_int && !did_throw + if (!did_emsg && !got_int && !current_exception && cstack.cs_idx >= 0 && (cstack.cs_flags[cstack.cs_idx] & (CSF_WHILE | CSF_FOR)) @@ -707,7 +705,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, } /* - * A ":finally" makes did_emsg, got_int, and did_throw pending for + * A ":finally" makes did_emsg, got_int and current_exception pending for * being restored at the ":endtry". Reset them here and set the * ACTIVE and FINALLY flags, so that the finally clause gets executed. * This includes the case where a missing ":endif", ":endwhile" or @@ -715,10 +713,11 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, */ if (cstack.cs_lflags & CSL_HAD_FINA) { cstack.cs_lflags &= ~CSL_HAD_FINA; - report_make_pending(cstack.cs_pending[cstack.cs_idx] - & (CSTP_ERROR | CSTP_INTERRUPT | CSTP_THROW), - did_throw ? (void *)current_exception : NULL); - did_emsg = got_int = did_throw = FALSE; + report_make_pending((cstack.cs_pending[cstack.cs_idx] + & (CSTP_ERROR | CSTP_INTERRUPT | CSTP_THROW)), + current_exception); + did_emsg = got_int = false; + current_exception = NULL; cstack.cs_flags[cstack.cs_idx] |= CSF_ACTIVE | CSF_FINALLY; } @@ -726,15 +725,14 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, * within this loop. */ trylevel = initial_trylevel + cstack.cs_trylevel; - /* - * If the outermost try conditional (across function calls and sourced - * files) is aborted because of an error, an interrupt, or an uncaught - * exception, cancel everything. If it is left normally, reset - * force_abort to get the non-EH compatible abortion behavior for - * the rest of the script. - */ - if (trylevel == 0 && !did_emsg && !got_int && !did_throw) - force_abort = FALSE; + // If the outermost try conditional (across function calls and sourced + // files) is aborted because of an error, an interrupt, or an uncaught + // exception, cancel everything. If it is left normally, reset + // force_abort to get the non-EH compatible abortion behavior for + // the rest of the script. + if (trylevel == 0 && !did_emsg && !got_int && !current_exception) { + force_abort = false; + } /* Convert an interrupt to an exception if appropriate. */ (void)do_intthrow(&cstack); @@ -749,11 +747,8 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, * - there is a command after '|', inside a :if, :while, :for or :try, or * looping for ":source" command or function call. */ - while (!((got_int - || (did_emsg && force_abort) || did_throw - ) - && cstack.cs_trylevel == 0 - ) + while (!((got_int || (did_emsg && force_abort) || current_exception) + && cstack.cs_trylevel == 0) && !(did_emsg /* Keep going when inside try/catch, so that the error can be * deal with, except when it is a syntax error, it may cause @@ -775,7 +770,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, * If a sourced file or executed function ran to its end, report the * unclosed conditional. */ - if (!got_int && !did_throw + if (!got_int && !current_exception && ((getline_equal(fgetline, cookie, getsourceline) && !source_finished(fgetline, cookie)) || (getline_equal(fgetline, cookie, get_func_line) @@ -815,17 +810,16 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, ? (char_u *)"endfunction" : (char_u *)NULL); if (trylevel == 0) { - /* - * When an exception is being thrown out of the outermost try - * conditional, discard the uncaught exception, disable the conversion - * of interrupts or errors to exceptions, and ensure that no more - * commands are executed. - */ - if (did_throw) { - void *p = NULL; - char_u *saved_sourcing_name; + // When an exception is being thrown out of the outermost try + // conditional, discard the uncaught exception, disable the conversion + // of interrupts or errors to exceptions, and ensure that no more + // commands are executed. + if (current_exception) { + void *p = NULL; + char_u *saved_sourcing_name; int saved_sourcing_lnum; - struct msglist *messages = NULL, *next; + struct msglist *messages = NULL; + struct msglist *next; /* * If the uncaught exception is a user exception, report it as an @@ -885,22 +879,22 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, suppress_errthrow = TRUE; } - /* - * The current cstack will be freed when do_cmdline() returns. An uncaught - * exception will have to be rethrown in the previous cstack. If a function - * has just returned or a script file was just finished and the previous - * cstack belongs to the same function or, respectively, script file, it - * will have to be checked for finally clauses to be executed due to the - * ":return" or ":finish". This is done in do_one_cmd(). - */ - if (did_throw) - need_rethrow = TRUE; + // The current cstack will be freed when do_cmdline() returns. An uncaught + // exception will have to be rethrown in the previous cstack. If a function + // has just returned or a script file was just finished and the previous + // cstack belongs to the same function or, respectively, script file, it + // will have to be checked for finally clauses to be executed due to the + // ":return" or ":finish". This is done in do_one_cmd(). + if (current_exception) { + need_rethrow = true; + } if ((getline_equal(fgetline, cookie, getsourceline) && ex_nesting_level > source_level(real_cookie)) || (getline_equal(fgetline, cookie, get_func_line) && ex_nesting_level > func_level(real_cookie) + 1)) { - if (!did_throw) - check_cstack = TRUE; + if (!current_exception) { + check_cstack = true; + } } else { /* When leaving a function, reduce nesting level. */ if (getline_equal(fgetline, cookie, get_func_line)) @@ -1480,10 +1474,11 @@ static char_u * do_one_cmd(char_u **cmdlinep, } char_u *after_modifier = ea.cmd; - ea.skip = did_emsg || got_int || did_throw || (cstack->cs_idx >= 0 - && !(cstack->cs_flags[cstack-> - cs_idx] - & CSF_ACTIVE)); + ea.skip = (did_emsg + || got_int + || current_exception + || (cstack->cs_idx >= 0 + && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE))); /* Count this line for profiling if ea.skip is FALSE. */ if (do_profiling == PROF_YES && !ea.skip) { @@ -1782,13 +1777,14 @@ static char_u * do_one_cmd(char_u **cmdlinep, )); - /* forced commands */ + // Forced commands. if (*p == '!' && ea.cmdidx != CMD_substitute && ea.cmdidx != CMD_smagic && ea.cmdidx != CMD_snomagic) { - ++p; - ea.forceit = TRUE; - } else - ea.forceit = FALSE; + p++; + ea.forceit = true; + } else { + ea.forceit = false; + } /* * 6. Parse arguments. @@ -4867,51 +4863,56 @@ static struct { * List of names for completion for ":command" with the EXPAND_ flag. * Must be alphabetical for completion. */ -static struct { - int expand; - char *name; -} command_complete[] = -{ - { EXPAND_AUGROUP, "augroup" }, - { EXPAND_BEHAVE, "behave" }, - { EXPAND_BUFFERS, "buffer" }, - { EXPAND_CHECKHEALTH, "checkhealth" }, - { EXPAND_COLORS, "color" }, - { EXPAND_COMMANDS, "command" }, - { EXPAND_COMPILER, "compiler" }, - { EXPAND_CSCOPE, "cscope" }, - { EXPAND_USER_DEFINED, "custom" }, - { EXPAND_USER_LIST, "customlist" }, - { EXPAND_DIRECTORIES, "dir" }, - { EXPAND_ENV_VARS, "environment" }, - { EXPAND_EVENTS, "event" }, - { EXPAND_EXPRESSION, "expression" }, - { EXPAND_FILES, "file" }, - { EXPAND_FILES_IN_PATH, "file_in_path" }, - { EXPAND_FILETYPE, "filetype" }, - { EXPAND_FUNCTIONS, "function" }, - { EXPAND_HELP, "help" }, - { EXPAND_HIGHLIGHT, "highlight" }, - { EXPAND_HISTORY, "history" }, +static const char *command_complete[] = +{ + [EXPAND_AUGROUP] = "augroup", + [EXPAND_BEHAVE] = "behave", + [EXPAND_BUFFERS] = "buffer", + [EXPAND_CHECKHEALTH] = "checkhealth", + [EXPAND_COLORS] = "color", + [EXPAND_COMMANDS] = "command", + [EXPAND_COMPILER] = "compiler", + [EXPAND_CSCOPE] = "cscope", + [EXPAND_USER_DEFINED] = "custom", + [EXPAND_USER_LIST] = "customlist", + [EXPAND_DIRECTORIES] = "dir", + [EXPAND_ENV_VARS] = "environment", + [EXPAND_EVENTS] = "event", + [EXPAND_EXPRESSION] = "expression", + [EXPAND_FILES] = "file", + [EXPAND_FILES_IN_PATH] = "file_in_path", + [EXPAND_FILETYPE] = "filetype", + [EXPAND_FUNCTIONS] = "function", + [EXPAND_HELP] = "help", + [EXPAND_HIGHLIGHT] = "highlight", + [EXPAND_HISTORY] = "history", #ifdef HAVE_WORKING_LIBINTL - { EXPAND_LOCALES, "locale" }, + [EXPAND_LOCALES] = "locale", #endif - { EXPAND_MAPPINGS, "mapping" }, - { EXPAND_MENUS, "menu" }, - { EXPAND_MESSAGES, "messages" }, - { EXPAND_OWNSYNTAX, "syntax" }, - { EXPAND_SYNTIME, "syntime" }, - { EXPAND_SETTINGS, "option" }, - { EXPAND_PACKADD, "packadd" }, - { EXPAND_SHELLCMD, "shellcmd" }, - { EXPAND_SIGN, "sign" }, - { EXPAND_TAGS, "tag" }, - { EXPAND_TAGS_LISTFILES, "tag_listfiles" }, - { EXPAND_USER, "user" }, - { EXPAND_USER_VARS, "var" }, - { 0, NULL } + [EXPAND_MAPPINGS] = "mapping", + [EXPAND_MENUS] = "menu", + [EXPAND_MESSAGES] = "messages", + [EXPAND_OWNSYNTAX] = "syntax", + [EXPAND_SYNTIME] = "syntime", + [EXPAND_SETTINGS] = "option", + [EXPAND_PACKADD] = "packadd", + [EXPAND_SHELLCMD] = "shellcmd", + [EXPAND_SIGN] = "sign", + [EXPAND_TAGS] = "tag", + [EXPAND_TAGS_LISTFILES] = "tag_listfiles", + [EXPAND_USER] = "user", + [EXPAND_USER_VARS] = "var", }; +static char *get_command_complete(int arg) +{ + if (arg >= (int)(ARRAY_SIZE(command_complete))) { + return NULL; + } else { + return (char *)command_complete[arg]; + } +} + static void uc_list(char_u *name, size_t name_len) { int i, j; @@ -5005,13 +5006,12 @@ static void uc_list(char_u *name, size_t name_len) IObuff[len++] = ' '; } while (len < 21); - /* Completion */ - for (j = 0; command_complete[j].expand != 0; ++j) - if (command_complete[j].expand == cmd->uc_compl) { - STRCPY(IObuff + len, command_complete[j].name); - len += (int)STRLEN(IObuff + len); - break; - } + // Completion + char *cmd_compl = get_command_complete(cmd->uc_compl); + if (cmd_compl != NULL) { + STRCPY(IObuff + len, get_command_complete(cmd->uc_compl)); + len += (int)STRLEN(IObuff + len); + } do { IObuff[len++] = ' '; @@ -5191,11 +5191,13 @@ static void ex_command(exarg_T *eap) p = skipwhite(end); } - /* Get the name (if any) and skip to the following argument */ + // Get the name (if any) and skip to the following argument. name = p; - if (ASCII_ISALPHA(*p)) - while (ASCII_ISALNUM(*p)) - ++p; + if (ASCII_ISALPHA(*p)) { + while (ASCII_ISALNUM(*p)) { + p++; + } + } if (!ends_excmd(*p) && !ascii_iswhite(*p)) { EMSG(_("E182: Invalid command name")); return; @@ -5213,8 +5215,7 @@ static void ex_command(exarg_T *eap) EMSG(_("E183: User defined commands must start with an uppercase letter")); return; } else if ((name_len == 1 && *name == 'X') - || (name_len <= 4 - && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0)) { + || (name_len <= 4 && STRNCMP(name, "Next", name_len) == 0)) { EMSG(_("E841: Reserved name, cannot be used for user defined command")); return; } else @@ -5678,22 +5679,21 @@ static void do_ucmd(exarg_T *eap) if (start != NULL) end = vim_strchr(start + 1, '>'); if (buf != NULL) { - for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp) - ; + for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ksp++) { + } if (*ksp == K_SPECIAL && (start == NULL || ksp < start || end == NULL) - && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER) - )) { - /* K_SPECIAL has been put in the buffer as K_SPECIAL - * KS_SPECIAL KE_FILLER, like for mappings, but - * do_cmdline() doesn't handle that, so convert it back. - * Also change K_SPECIAL KS_EXTRA KE_CSI into CSI. */ + && (ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)) { + // K_SPECIAL has been put in the buffer as K_SPECIAL + // KS_SPECIAL KE_FILLER, like for mappings, but + // do_cmdline() doesn't handle that, so convert it back. + // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI. len = ksp - p; if (len > 0) { memmove(q, p, len); q += len; } - *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI; + *q++ = K_SPECIAL; p = ksp + 3; continue; } @@ -5802,7 +5802,15 @@ char_u *get_user_cmd_nargs(expand_T *xp, int idx) */ char_u *get_user_cmd_complete(expand_T *xp, int idx) { - return (char_u *)command_complete[idx].name; + if (idx >= (int)ARRAY_SIZE(command_complete)) { + return NULL; + } + char *cmd_compl = get_command_complete(idx); + if (cmd_compl == NULL) { + return (char_u *)""; + } else { + return (char_u *)cmd_compl; + } } /* @@ -5862,20 +5870,23 @@ int parse_compl_arg(const char_u *value, int vallen, int *complp, } } - for (i = 0; command_complete[i].expand != 0; ++i) { - if ((int)STRLEN(command_complete[i].name) == valend - && STRNCMP(value, command_complete[i].name, valend) == 0) { - *complp = command_complete[i].expand; - if (command_complete[i].expand == EXPAND_BUFFERS) + for (i = 0; i < (int)ARRAY_SIZE(command_complete); i++) { + if (get_command_complete(i) == NULL) { + continue; + } + if ((int)STRLEN(command_complete[i]) == valend + && STRNCMP(value, command_complete[i], valend) == 0) { + *complp = i; + if (i == EXPAND_BUFFERS) { *argt |= BUFNAME; - else if (command_complete[i].expand == EXPAND_DIRECTORIES - || command_complete[i].expand == EXPAND_FILES) + } else if (i == EXPAND_DIRECTORIES || i == EXPAND_FILES) { *argt |= XFILE; + } break; } } - if (command_complete[i].expand == 0) { + if (i == (int)ARRAY_SIZE(command_complete)) { EMSG2(_("E180: Invalid complete value: %s"), value); return FAIL; } @@ -5899,9 +5910,13 @@ int parse_compl_arg(const char_u *value, int vallen, int *complp, int cmdcomplete_str_to_type(char_u *complete_str) { - for (int i = 0; command_complete[i].expand != 0; i++) { - if (STRCMP(complete_str, command_complete[i].name) == 0) { - return command_complete[i].expand; + for (int i = 0; i < (int)(ARRAY_SIZE(command_complete)); i++) { + char *cmd_compl = get_command_complete(i); + if (cmd_compl == NULL) { + continue; + } + if (STRCMP(complete_str, command_complete[i]) == 0) { + return i; } } @@ -6310,15 +6325,18 @@ static void ex_stop(exarg_T *eap) if (!eap->forceit) { autowrite_all(); } + apply_autocmds(EVENT_VIMSUSPEND, NULL, NULL, false, NULL); ui_cursor_goto((int)Rows - 1, 0); ui_linefeed(); ui_flush(); ui_call_suspend(); // call machine specific function + ui_flush(); maketitle(); resettitle(); // force updating the title redraw_later_clear(); ui_refresh(); // may have resized window + apply_autocmds(EVENT_VIMRESUME, NULL, NULL, false, NULL); } } @@ -6554,18 +6572,14 @@ void alist_slash_adjust(void) #endif -/* - * ":preserve". - */ +/// ":preserve". static void ex_preserve(exarg_T *eap) { curbuf->b_flags |= BF_PRESERVED; - ml_preserve(curbuf, TRUE); + ml_preserve(curbuf, true, true); } -/* - * ":recover". - */ +/// ":recover". static void ex_recover(exarg_T *eap) { /* Set recoverymode right away to avoid the ATTENTION prompt. */ @@ -6985,12 +6999,10 @@ do_exedit ( ex_no_reprint = TRUE; } -/* - * ":gui" and ":gvim" when there is no GUI. - */ +/// ":gui" and ":gvim" when there is no GUI. static void ex_nogui(exarg_T *eap) { - eap->errmsg = e_nogvim; + eap->errmsg = (char_u *)N_("E25: Nvim does not have a built-in GUI"); } @@ -8156,6 +8168,10 @@ static void ex_startinsert(exarg_T *eap) restart_edit = 'i'; curwin->w_curswant = 0; /* avoid MAXCOL */ } + + if (VIsual_active) { + showmode(); + } } /* @@ -8546,22 +8562,22 @@ eval_vars ( resultbuf = result; /* remember allocated string */ break; - case SPEC_AFILE: /* file name for autocommand */ - result = autocmd_fname; - if (result != NULL && !autocmd_fname_full) { - /* Still need to turn the fname into a full path. It is - * postponed to avoid a delay when <afile> is not used. */ - autocmd_fname_full = TRUE; - result = (char_u *)FullName_save((char *)autocmd_fname, FALSE); - xfree(autocmd_fname); - autocmd_fname = result; + case SPEC_AFILE: // file name for autocommand + if (autocmd_fname != NULL && !path_is_absolute(autocmd_fname)) { + // Still need to turn the fname into a full path. It was + // postponed to avoid a delay when <afile> is not used. + result = (char_u *)FullName_save((char *)autocmd_fname, false); + // Copy into `autocmd_fname`, don't reassign it. #8165 + xstrlcpy((char *)autocmd_fname, (char *)result, MAXPATHL); + xfree(result); } + result = autocmd_fname; if (result == NULL) { *errormsg = (char_u *)_( "E495: no autocommand file name to substitute for \"<afile>\""); return NULL; } - result = path_shorten_fname_if_possible(result); + result = path_try_shorten_fname(result); break; case SPEC_ABUF: /* buffer number for autocommand */ @@ -9952,3 +9968,82 @@ bool cmd_can_preview(char_u *cmd) return false; } + +/// Gets a map of maps describing user-commands defined for buffer `buf` or +/// defined globally if `buf` is NULL. +/// +/// @param buf Buffer to inspect, or NULL to get global commands. +/// +/// @return Map of maps describing commands +Dictionary commands_array(buf_T *buf) +{ + Dictionary rv = ARRAY_DICT_INIT; + Object obj = NIL; + char str[10]; + garray_T *gap = (buf == NULL) ? &ucmds : &buf->b_ucmds; + + for (int i = 0; i < gap->ga_len; i++) { + char arg[2] = { 0, 0 }; + Dictionary d = ARRAY_DICT_INIT; + ucmd_T *cmd = USER_CMD_GA(gap, i); + + PUT(d, "name", STRING_OBJ(cstr_to_string((char *)cmd->uc_name))); + PUT(d, "definition", STRING_OBJ(cstr_to_string((char *)cmd->uc_rep))); + PUT(d, "script_id", INTEGER_OBJ(cmd->uc_scriptID)); + PUT(d, "bang", BOOLEAN_OBJ(!!(cmd->uc_argt & BANG))); + PUT(d, "bar", BOOLEAN_OBJ(!!(cmd->uc_argt & TRLBAR))); + PUT(d, "register", BOOLEAN_OBJ(!!(cmd->uc_argt & REGSTR))); + + switch (cmd->uc_argt & (EXTRA|NOSPC|NEEDARG)) { + case 0: arg[0] = '0'; break; + case(EXTRA): arg[0] = '*'; break; + case(EXTRA|NOSPC): arg[0] = '?'; break; + case(EXTRA|NEEDARG): arg[0] = '+'; break; + case(EXTRA|NOSPC|NEEDARG): arg[0] = '1'; break; + } + PUT(d, "nargs", STRING_OBJ(cstr_to_string(arg))); + + char *cmd_compl = get_command_complete(cmd->uc_compl); + PUT(d, "complete", (cmd_compl == NULL + ? NIL : STRING_OBJ(cstr_to_string(cmd_compl)))); + PUT(d, "complete_arg", cmd->uc_compl_arg == NULL + ? NIL : STRING_OBJ(cstr_to_string((char *)cmd->uc_compl_arg))); + + obj = NIL; + if (cmd->uc_argt & COUNT) { + if (cmd->uc_def >= 0) { + snprintf(str, sizeof(str), "%" PRId64, (int64_t)cmd->uc_def); + obj = STRING_OBJ(cstr_to_string(str)); // -count=N + } else { + obj = STRING_OBJ(cstr_to_string("0")); // -count + } + } + PUT(d, "count", obj); + + obj = NIL; + if (cmd->uc_argt & RANGE) { + if (cmd->uc_argt & DFLALL) { + obj = STRING_OBJ(cstr_to_string("%")); // -range=% + } else if (cmd->uc_def >= 0) { + snprintf(str, sizeof(str), "%" PRId64, (int64_t)cmd->uc_def); + obj = STRING_OBJ(cstr_to_string(str)); // -range=N + } else { + obj = STRING_OBJ(cstr_to_string(".")); // -range + } + } + PUT(d, "range", obj); + + obj = NIL; + for (int j = 0; addr_type_complete[j].expand != -1; j++) { + if (addr_type_complete[j].expand != ADDR_LINES + && addr_type_complete[j].expand == cmd->uc_addr_type) { + obj = STRING_OBJ(cstr_to_string(addr_type_complete[j].name)); + break; + } + } + PUT(d, "addr", obj); + + PUT(rv, (char *)cmd->uc_name, DICTIONARY_OBJ(d)); + } + return rv; +} diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 4010a088c8..7f7851f078 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -28,39 +28,37 @@ #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_eval.c.generated.h" #endif -/* - * Exception handling terms: - * - * :try ":try" command \ - * ... try block | - * :catch RE ":catch" command | - * ... catch clause |- try conditional - * :finally ":finally" command | - * ... finally clause | - * :endtry ":endtry" command / - * - * The try conditional may have any number of catch clauses and at most one - * finally clause. A ":throw" command can be inside the try block, a catch - * clause, the finally clause, or in a function called or script sourced from - * there or even outside the try conditional. Try conditionals may be nested. - */ -/* - * Configuration whether an exception is thrown on error or interrupt. When - * the preprocessor macros below evaluate to FALSE, an error (did_emsg) or - * interrupt (got_int) under an active try conditional terminates the script - * after the non-active finally clauses of all active try conditionals have been - * executed. Otherwise, errors and/or interrupts are converted into catchable - * exceptions (did_throw additionally set), which terminate the script only if - * not caught. For user exceptions, only did_throw is set. (Note: got_int can - * be set asynchronously afterwards by a SIGINT, so did_throw && got_int is not - * a reliant test that the exception currently being thrown is an interrupt - * exception. Similarly, did_emsg can be set afterwards on an error in an - * (unskipped) conditional command inside an inactive conditional, so did_throw - * && did_emsg is not a reliant test that the exception currently being thrown - * is an error exception.) - The macros can be defined as expressions checking - * for a variable that is allowed to be changed during execution of a script. - */ +// Exception handling terms: +// +// :try ":try" command ─┐ +// ... try block │ +// :catch RE ":catch" command │ +// ... catch clause ├─ try conditional +// :finally ":finally" command │ +// ... finally clause │ +// :endtry ":endtry" command ─┘ +// +// The try conditional may have any number of catch clauses and at most one +// finally clause. A ":throw" command can be inside the try block, a catch +// clause, the finally clause, or in a function called or script sourced from +// there or even outside the try conditional. Try conditionals may be nested. + +// Configuration whether an exception is thrown on error or interrupt. When +// the preprocessor macros below evaluate to FALSE, an error (did_emsg) or +// interrupt (got_int) under an active try conditional terminates the script +// after the non-active finally clauses of all active try conditionals have been +// executed. Otherwise, errors and/or interrupts are converted into catchable +// exceptions, which terminate the script only if not caught. For user +// exceptions, only current_exception is set. (Note: got_int can be set +// asynchronously afterwards by a SIGINT, so current_exception && got_int is not +// a reliant test that the exception currently being thrown is an interrupt +// exception. Similarly, did_emsg can be set afterwards on an error in an +// (unskipped) conditional command inside an inactive conditional, so +// current_exception && did_emsg is not a reliant test that the exception +// currently being thrown is an error exception.) - The macros can be defined +// as expressions checking for a variable that is allowed to be changed during +// execution of a script. // Values used for the Vim release. #define THROW_ON_ERROR true @@ -68,6 +66,15 @@ #define THROW_ON_INTERRUPT true #define THROW_ON_INTERRUPT_TRUE +// Don't do something after an error, interrupt, or throw, or when +// there is a surrounding conditional and it was not active. +#define CHECK_SKIP \ + (did_emsg \ + || got_int \ + || current_exception \ + || (cstack->cs_idx > 0 \ + && !(cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE))) + #define discard_pending_return(p) tv_free((typval_T *)(p)) /* @@ -94,7 +101,7 @@ static int cause_abort = FALSE; */ int aborting(void) { - return (did_emsg && force_abort) || got_int || did_throw; + return (did_emsg && force_abort) || got_int || current_exception; } /* @@ -178,8 +185,9 @@ int cause_errthrow(char_u *mesg, int severe, int *ignore) * currently throwing an exception, do nothing. The message text will * then be stored to v:errmsg by emsg() without displaying it. */ - if (((trylevel == 0 && !cause_abort) || emsg_silent) && !did_throw) - return FALSE; + if (((trylevel == 0 && !cause_abort) || emsg_silent) && !current_exception) { + return false; + } /* * Ignore an interrupt message when inside a try conditional or when an @@ -208,12 +216,13 @@ int cause_errthrow(char_u *mesg, int severe, int *ignore) * exception currently being thrown to prevent it from being caught. Just * execute finally clauses and terminate. */ - if (did_throw) { - /* When discarding an interrupt exception, reset got_int to prevent the - * same interrupt being converted to an exception again and discarding - * the error exception we are about to throw here. */ - if (current_exception->type == ET_INTERRUPT) - got_int = FALSE; + if (current_exception) { + // When discarding an interrupt exception, reset got_int to prevent the + // same interrupt being converted to an exception again and discarding + // the error exception we are about to throw here. + if (current_exception->type == ET_INTERRUPT) { + got_int = false; + } discard_current_exception(); } @@ -333,45 +342,44 @@ void do_errthrow(struct condstack *cstack, char_u *cmdname) */ int do_intthrow(struct condstack *cstack) { - /* - * If no interrupt occurred or no try conditional is active and no exception - * is being thrown, do nothing (for compatibility of non-EH scripts). - */ - if (!got_int || (trylevel == 0 && !did_throw)) - return FALSE; + // If no interrupt occurred or no try conditional is active and no exception + // is being thrown, do nothing (for compatibility of non-EH scripts). + if (!got_int || (trylevel == 0 && !current_exception)) { + return false; + } -#ifdef THROW_TEST /* avoid warning for condition always true */ +#ifdef THROW_TEST // avoid warning for condition always true if (!THROW_ON_INTERRUPT) { - /* - * The interrupt aborts everything except for executing finally clauses. - * Discard any user or error or interrupt exception currently being - * thrown. - */ - if (did_throw) + // The interrupt aborts everything except for executing finally clauses. + // Discard any user or error or interrupt exception currently being + // thrown. + if (current_exception) { discard_current_exception(); - } else + } + } else { #endif - { - /* - * Throw an interrupt exception, so that everything will be aborted - * (except for executing finally clauses), until the interrupt exception - * is caught; if still uncaught at the top level, the script processing - * will be terminated then. - If an interrupt exception is already - * being thrown, do nothing. - * - */ - if (did_throw) { - if (current_exception->type == ET_INTERRUPT) - return FALSE; + // Throw an interrupt exception, so that everything will be aborted + // (except for executing finally clauses), until the interrupt exception + // is caught; if still uncaught at the top level, the script processing + // will be terminated then. - If an interrupt exception is already + // being thrown, do nothing. + + if (current_exception) { + if (current_exception->type == ET_INTERRUPT) { + return false; + } - /* An interrupt exception replaces any user or error exception. */ + // An interrupt exception replaces any user or error exception. discard_current_exception(); } - if (throw_exception("Vim:Interrupt", ET_INTERRUPT, NULL) != FAIL) + if (throw_exception("Vim:Interrupt", ET_INTERRUPT, NULL) != FAIL) { do_throw(cstack); + } +#ifdef THROW_TEST } +#endif - return TRUE; + return true; } // Get an exception message that is to be stored in current_exception->value. @@ -565,8 +573,7 @@ void discard_current_exception(void) // Note: all globals manipulated here should be saved/restored in // try_enter/try_leave. current_exception = NULL; - did_throw = FALSE; - need_rethrow = FALSE; + need_rethrow = false; } /* @@ -795,15 +802,7 @@ void ex_if(exarg_T *eap) ++cstack->cs_idx; cstack->cs_flags[cstack->cs_idx] = 0; - /* - * Don't do something after an error, interrupt, or throw, or when there - * is a surrounding conditional and it was not active. - */ - skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0 - && !(cstack->cs_flags[cstack-> - cs_idx - - 1] & - CSF_ACTIVE)); + skip = CHECK_SKIP; bool error; result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip); @@ -854,15 +853,7 @@ void ex_else(exarg_T *eap) int result; struct condstack *cstack = eap->cstack; - /* - * Don't do something after an error, interrupt, or throw, or when there is - * a surrounding conditional and it was not active. - */ - skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0 - && !(cstack->cs_flags[cstack-> - cs_idx - - 1] & - CSF_ACTIVE)); + skip = CHECK_SKIP; if (cstack->cs_idx < 0 || (cstack->cs_flags[cstack->cs_idx] @@ -952,15 +943,7 @@ void ex_while(exarg_T *eap) cstack->cs_flags[cstack->cs_idx] = eap->cmdidx == CMD_while ? CSF_WHILE : CSF_FOR; - /* - * Don't do something after an error, interrupt, or throw, or when - * there is a surrounding conditional and it was not active. - */ - skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0 - && !(cstack->cs_flags[cstack-> - cs_idx - - 1] & - CSF_ACTIVE)); + skip = CHECK_SKIP; if (eap->cmdidx == CMD_while) { /* * ":while bool-expr" @@ -1233,8 +1216,6 @@ void do_throw(struct condstack *cstack) cstack->cs_flags[idx] &= ~CSF_ACTIVE; cstack->cs_exception[idx] = current_exception; } - - did_throw = TRUE; } /* @@ -1253,15 +1234,7 @@ void ex_try(exarg_T *eap) cstack->cs_flags[cstack->cs_idx] = CSF_TRY; cstack->cs_pending[cstack->cs_idx] = CSTP_NONE; - /* - * Don't do something after an error, interrupt, or throw, or when there - * is a surrounding conditional and it was not active. - */ - skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0 - && !(cstack->cs_flags[cstack-> - cs_idx - - 1] & - CSF_ACTIVE)); + skip = CHECK_SKIP; if (!skip) { /* Set ACTIVE and TRUE. TRUE means that the corresponding ":catch" @@ -1353,8 +1326,9 @@ void ex_catch(exarg_T *eap) * corresponding try block never got active (because of an inactive * surrounding conditional or after an error or interrupt or throw). */ - if (!did_throw || !(cstack->cs_flags[idx] & CSF_TRUE)) - skip = TRUE; + if (!current_exception || !(cstack->cs_flags[idx] & CSF_TRUE)) { + skip = true; + } /* * Check for a match only if an exception is thrown but not caught by @@ -1413,10 +1387,10 @@ void ex_catch(exarg_T *eap) } if (caught) { - /* Make this ":catch" clause active and reset did_emsg, got_int, - * and did_throw. Put the exception on the caught stack. */ + /* Make this ":catch" clause active and reset did_emsg and got_int. + * Put the exception on the caught stack. */ cstack->cs_flags[idx] |= CSF_ACTIVE | CSF_CAUGHT; - did_emsg = got_int = did_throw = FALSE; + did_emsg = got_int = false; catch_exception((except_T *)cstack->cs_exception[idx]); /* It's mandatory that the current exception is stored in the cstack * so that it can be discarded at the next ":catch", ":finally", or @@ -1426,6 +1400,10 @@ void ex_catch(exarg_T *eap) if (cstack->cs_exception[cstack->cs_idx] != current_exception) { internal_error("ex_catch()"); } + // Discarding current_exceptions happens based on what is stored in + // cstack->cs_exception, *all* calls to discard_current_exception() are + // (and must be) guarded by current_exception check. + current_exception = NULL; } else { /* * If there is a preceding catch clause and it caught the exception, @@ -1484,7 +1462,7 @@ void ex_finally(exarg_T *eap) * interrupt or throw) or for a ":finally" without ":try" or a multiple * ":finally". After every other error (did_emsg or the conditional * errors detected above) or after an interrupt (got_int) or an - * exception (did_throw), the finally clause must be executed. + * exception (current_exception), the finally clause must be executed. */ skip = !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); @@ -1511,30 +1489,31 @@ void ex_finally(exarg_T *eap) cleanup_conditionals(cstack, CSF_TRY, FALSE); /* - * Make did_emsg, got_int, did_throw pending. If set, they overrule - * a pending ":continue", ":break", ":return", or ":finish". Then - * we have particularly to discard a pending return value (as done + * Make did_emsg, got_int, current_exception pending. If set, they + * overrule a pending ":continue", ":break", ":return", or ":finish". + * Then we have particularly to discard a pending return value (as done * by the call to cleanup_conditionals() above when did_emsg or * got_int is set). The pending values are restored by the * ":endtry", except if there is a new error, interrupt, exception, * ":continue", ":break", ":return", or ":finish" in the following * finally clause. A missing ":endwhile", ":endfor" or ":endif" - * detected here is treated as if did_emsg and did_throw had + * detected here is treated as if did_emsg and current_exception had * already been set, respectively in case that the error is not - * converted to an exception, did_throw had already been unset. + * converted to an exception, current_exception had already been unset. * We must not set did_emsg here since that would suppress the * error message. */ - if (pending == CSTP_ERROR || did_emsg || got_int || did_throw) { + if (pending == CSTP_ERROR || did_emsg || got_int || current_exception) { if (cstack->cs_pending[cstack->cs_idx] == CSTP_RETURN) { report_discard_pending(CSTP_RETURN, cstack->cs_rettv[cstack->cs_idx]); discard_pending_return(cstack->cs_rettv[cstack->cs_idx]); } - if (pending == CSTP_ERROR && !did_emsg) - pending |= (THROW_ON_ERROR) ? CSTP_THROW : 0; - else - pending |= did_throw ? CSTP_THROW : 0; + if (pending == CSTP_ERROR && !did_emsg) { + pending |= (THROW_ON_ERROR ? CSTP_THROW : 0); + } else { + pending |= (current_exception ? CSTP_THROW : 0); + } pending |= did_emsg ? CSTP_ERROR : 0; pending |= got_int ? CSTP_INTERRUPT : 0; assert(pending >= CHAR_MIN && pending <= CHAR_MAX); @@ -1547,14 +1526,15 @@ void ex_finally(exarg_T *eap) * exception. When emsg() is called for a missing ":endif" or * a missing ":endwhile"/":endfor" detected here, the * exception will be discarded. */ - if (did_throw && cstack->cs_exception[cstack->cs_idx] - != current_exception) + if (current_exception + && cstack->cs_exception[cstack->cs_idx] != current_exception) { internal_error("ex_finally()"); + } } /* * Set CSL_HAD_FINA, so do_cmdline() will reset did_emsg, - * got_int, and did_throw and make the finally clause active. + * got_int, and current_exception and make the finally clause active. * This will happen after emsg() has been called for a missing * ":endif" or a missing ":endwhile"/":endfor" detected here, so * that the following finally clause will be executed even then. @@ -1589,7 +1569,7 @@ void ex_endtry(exarg_T *eap) // made inactive by a ":continue", ":break", ":return", or ":finish" in // the finally clause. The latter case need not be tested since then // anything pending has already been discarded. - skip = (did_emsg || got_int || did_throw + skip = (did_emsg || got_int || current_exception || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE)); if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { @@ -1606,12 +1586,13 @@ void ex_endtry(exarg_T *eap) /* * If an exception is being thrown, discard it to prevent it from * being rethrown at the end of this function. It would be - * discarded by the error message, anyway. Resets did_throw. + * discarded by the error message, anyway. Resets current_exception. * This does not affect the script termination due to the error * since "trylevel" is decremented after emsg() has been called. */ - if (did_throw) + if (current_exception) { discard_current_exception(); + } } else { idx = cstack->cs_idx; @@ -1621,9 +1602,11 @@ void ex_endtry(exarg_T *eap) * a finally clause, we need to rethrow it after closing the try * conditional. */ - if (did_throw && (cstack->cs_flags[idx] & CSF_TRUE) - && !(cstack->cs_flags[idx] & CSF_FINALLY)) - rethrow = TRUE; + if (current_exception + && (cstack->cs_flags[idx] & CSF_TRUE) + && !(cstack->cs_flags[idx] & CSF_FINALLY)) { + rethrow = true; + } } /* If there was no finally clause, show the user when debugging or @@ -1644,11 +1627,12 @@ void ex_endtry(exarg_T *eap) if (got_int) { skip = TRUE; (void)do_intthrow(cstack); - /* The do_intthrow() call may have reset did_throw or - * cstack->cs_pending[idx].*/ - rethrow = FALSE; - if (did_throw && !(cstack->cs_flags[idx] & CSF_FINALLY)) - rethrow = TRUE; + // The do_intthrow() call may have reset current_exception or + // cstack->cs_pending[idx]. + rethrow = false; + if (current_exception && !(cstack->cs_flags[idx] & CSF_FINALLY)) { + rethrow = true; + } } } @@ -1710,26 +1694,30 @@ void ex_endtry(exarg_T *eap) do_finish(eap, FALSE); break; - /* When the finally clause was entered due to an error, - * interrupt or throw (as opposed to a ":continue", ":break", - * ":return", or ":finish"), restore the pending values of - * did_emsg, got_int, and did_throw. This is skipped, if there - * was a new error, interrupt, throw, ":continue", ":break", - * ":return", or ":finish". in the finally clause. */ + // When the finally clause was entered due to an error, + // interrupt or throw (as opposed to a ":continue", ":break", + // ":return", or ":finish"), restore the pending values of + // did_emsg, got_int, and current_exception. This is skipped, if there + // was a new error, interrupt, throw, ":continue", ":break", + // ":return", or ":finish". in the finally clause. default: - if (pending & CSTP_ERROR) - did_emsg = TRUE; - if (pending & CSTP_INTERRUPT) - got_int = TRUE; - if (pending & CSTP_THROW) - rethrow = TRUE; + if (pending & CSTP_ERROR) { + did_emsg = true; + } + if (pending & CSTP_INTERRUPT) { + got_int = true; + } + if (pending & CSTP_THROW) { + rethrow = true; + } break; } } - if (rethrow) - /* Rethrow the current exception (within this cstack). */ + if (rethrow) { + // Rethrow the current exception (within this cstack). do_throw(cstack); + } } } @@ -1759,33 +1747,34 @@ void enter_cleanup(cleanup_T *csp) int pending = CSTP_NONE; /* - * Postpone did_emsg, got_int, did_throw. The pending values will be + * Postpone did_emsg, got_int, current_exception. The pending values will be * restored by leave_cleanup() except if there was an aborting error, * interrupt, or uncaught exception after this function ends. */ - if (did_emsg || got_int || did_throw || need_rethrow) { - csp->pending = (did_emsg ? CSTP_ERROR : 0) - | (got_int ? CSTP_INTERRUPT : 0) - | (did_throw ? CSTP_THROW : 0) - | (need_rethrow ? CSTP_THROW : 0); - - /* If we are currently throwing an exception (did_throw), save it as - * well. On an error not yet converted to an exception, update - * "force_abort" and reset "cause_abort" (as do_errthrow() would do). - * This is needed for the do_cmdline() call that is going to be made - * for autocommand execution. We need not save *msg_list because - * there is an extra instance for every call of do_cmdline(), anyway. + if (did_emsg || got_int || current_exception || need_rethrow) { + csp->pending = (did_emsg ? CSTP_ERROR : 0) + | (got_int ? CSTP_INTERRUPT : 0) + | (current_exception ? CSTP_THROW : 0) + | (need_rethrow ? CSTP_THROW : 0); + + /* If we are currently throwing an exception, save it as well. On an error + * not yet converted to an exception, update "force_abort" and reset + * "cause_abort" (as do_errthrow() would do). This is needed for the + * do_cmdline() call that is going to be made for autocommand execution. We + * need not save *msg_list because there is an extra instance for every call + * of do_cmdline(), anyway. */ - if (did_throw || need_rethrow) + if (current_exception || need_rethrow) { csp->exception = current_exception; - else { + } else { csp->exception = NULL; if (did_emsg) { force_abort |= cause_abort; cause_abort = FALSE; } } - did_emsg = got_int = did_throw = need_rethrow = FALSE; + did_emsg = got_int = need_rethrow = false; + current_exception = NULL; /* Report if required by the 'verbose' option or when debugging. */ report_make_pending(pending, csp->exception); @@ -1857,19 +1846,20 @@ void leave_cleanup(cleanup_T *csp) force_abort = FALSE; } - /* - * Restore the pending values of did_emsg, got_int, and did_throw. - */ - if (pending & CSTP_ERROR) - did_emsg = TRUE; - if (pending & CSTP_INTERRUPT) - got_int = TRUE; - if (pending & CSTP_THROW) - need_rethrow = TRUE; /* did_throw will be set by do_one_cmd() */ + // Restore the pending values of did_emsg, got_int, and current_exception. + if (pending & CSTP_ERROR) { + did_emsg = true; + } + if (pending & CSTP_INTERRUPT) { + got_int = true; + } + if (pending & CSTP_THROW) { + need_rethrow = true; // current_exception will be set by do_one_cmd() + } - /* Report if required by the 'verbose' option or when debugging. */ - report_resume_pending(pending, - (pending & CSTP_THROW) ? (void *)current_exception : NULL); + // Report if required by the 'verbose' option or when debugging. + report_resume_pending( + pending, ((pending & CSTP_THROW) ? (void *)current_exception : NULL)); } } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index dd7504c05e..96388a2a9d 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -347,6 +347,13 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) redrawcmd(); } + // redraw the statusline for statuslines that display the current mode + // using the mode() function. + if (KeyTyped) { + curwin->w_redr_status = true; + redraw_statuslines(); + } + did_emsg = false; got_int = false; s->state.check = command_line_check; @@ -512,8 +519,12 @@ static int command_line_execute(VimState *state, int key) CommandLineState *s = (CommandLineState *)state; s->c = key; - if (s->c == K_EVENT) { - multiqueue_process_events(main_loop.events); + if (s->c == K_EVENT || s->c == K_COMMAND) { + if (s->c == K_EVENT) { + multiqueue_process_events(main_loop.events); + } else { + do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT); + } redrawcmdline(); return 1; } @@ -3586,40 +3597,33 @@ nextwild ( xp->xp_pattern_len = ccline.cmdpos - i; if (type == WILD_NEXT || type == WILD_PREV) { - /* - * Get next/previous match for a previous expanded pattern. - */ + // Get next/previous match for a previous expanded pattern. p2 = ExpandOne(xp, NULL, NULL, 0, type); } else { - /* - * Translate string into pattern and expand it. - */ - if ((p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, - xp->xp_context)) == NULL) - p2 = NULL; - else { - int use_options = options | - WILD_HOME_REPLACE|WILD_ADD_SLASH|WILD_SILENT; - if (escape) - use_options |= WILD_ESCAPE; - - if (p_wic) - use_options += WILD_ICASE; - p2 = ExpandOne(xp, p1, - vim_strnsave(&ccline.cmdbuff[i], xp->xp_pattern_len), - use_options, type); - xfree(p1); - /* longest match: make sure it is not shorter, happens with :help */ - if (p2 != NULL && type == WILD_LONGEST) { - for (j = 0; j < xp->xp_pattern_len; ++j) - if (ccline.cmdbuff[i + j] == '*' - || ccline.cmdbuff[i + j] == '?') - break; - if ((int)STRLEN(p2) < j) { - xfree(p2); - p2 = NULL; + // Translate string into pattern and expand it. + p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); + const int use_options = ( + options + | WILD_HOME_REPLACE + | WILD_ADD_SLASH + | WILD_SILENT + | (escape ? WILD_ESCAPE : 0) + | (p_wic ? WILD_ICASE : 0)); + p2 = ExpandOne(xp, p1, vim_strnsave(&ccline.cmdbuff[i], xp->xp_pattern_len), + use_options, type); + xfree(p1); + // Longest match: make sure it is not shorter, happens with :help. + if (p2 != NULL && type == WILD_LONGEST) { + for (j = 0; j < xp->xp_pattern_len; j++) { + if (ccline.cmdbuff[i + j] == '*' + || ccline.cmdbuff[i + j] == '?') { + break; } } + if ((int)STRLEN(p2) < j) { + xfree(p2); + p2 = NULL; + } } } @@ -4852,23 +4856,27 @@ void ExpandGeneric( // copy the matching names into allocated memory count = 0; - for (i = 0;; ++i) { + for (i = 0;; i++) { str = (*func)(xp, i); - if (str == NULL) // end of list + if (str == NULL) { // End of list. break; - if (*str == NUL) // skip empty strings + } + if (*str == NUL) { // Skip empty strings. continue; + } if (vim_regexec(regmatch, str, (colnr_T)0)) { - if (escaped) + if (escaped) { str = vim_strsave_escaped(str, (char_u *)" \t\\."); - else + } else { str = vim_strsave(str); + } (*file)[count++] = str; - if (func == get_menu_names && str != NULL) { - /* test for separator added by get_menu_names() */ + if (func == get_menu_names) { + // Test for separator added by get_menu_names(). str += STRLEN(str) - 1; - if (*str == '\001') + if (*str == '\001') { *str = '.'; + } } } } @@ -4923,13 +4931,14 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file, flags |= EW_FILE | EW_EXEC | EW_SHELLCMD; bool mustfree = false; // Track memory allocation for *path. - /* For an absolute name we don't use $PATH. */ - if (path_is_absolute_path(pat)) + // For an absolute name we don't use $PATH. + if (path_is_absolute(pat)) { path = (char_u *)" "; - else if ((pat[0] == '.' && (vim_ispathsep(pat[1]) - || (pat[1] == '.' && vim_ispathsep(pat[2]))))) + } else if (pat[0] == '.' && (vim_ispathsep(pat[1]) + || (pat[1] == '.' + && vim_ispathsep(pat[2])))) { path = (char_u *)"."; - else { + } else { path = (char_u *)vim_getenv("PATH"); if (path == NULL) { path = (char_u *)""; diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index c6f2166dab..5b17b58781 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -1541,26 +1541,30 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope) char buf[8]; switch (scope) { - case kCdScopeGlobal: - snprintf(buf, sizeof(buf), "global"); - break; - case kCdScopeTab: - snprintf(buf, sizeof(buf), "tab"); - break; - case kCdScopeWindow: - snprintf(buf, sizeof(buf), "window"); - break; - case kCdScopeInvalid: - // Should never happen. - assert(false); + case kCdScopeGlobal: { + snprintf(buf, sizeof(buf), "global"); + break; + } + case kCdScopeTab: { + snprintf(buf, sizeof(buf), "tab"); + break; + } + case kCdScopeWindow: { + snprintf(buf, sizeof(buf), "window"); + break; + } + case kCdScopeInvalid: { + // Should never happen. + assert(false); + } } - tv_dict_add_str(dict, S_LEN("scope"), buf); + tv_dict_add_str(dict, S_LEN("scope"), buf); // -V614 tv_dict_add_str(dict, S_LEN("cwd"), new_dir); tv_dict_set_keys_readonly(dict); apply_autocmds(EVENT_DIRCHANGED, (char_u *)buf, (char_u *)new_dir, false, - NULL); + curbuf); tv_dict_clear(dict); diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 91b0a695f1..efeee1ba2b 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -1,9 +1,7 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * fileio.c: read from and write to a file - */ +// fileio.c: read from and write to a file #include <assert.h> #include <errno.h> @@ -65,57 +63,62 @@ #define BUFSIZE 8192 /* size of normal write buffer */ #define SMBUFSIZE 256 /* size of emergency write buffer */ -/* - * The autocommands are stored in a list for each event. - * Autocommands for the same pattern, that are consecutive, are joined - * together, to avoid having to match the pattern too often. - * The result is an array of Autopat lists, which point to AutoCmd lists: - * - * first_autopat[0] --> Autopat.next --> Autopat.next --> NULL - * Autopat.cmds Autopat.cmds - * | | - * V V - * AutoCmd.next AutoCmd.next - * | | - * V V - * AutoCmd.next NULL - * | - * V - * NULL - * - * first_autopat[1] --> Autopat.next --> NULL - * Autopat.cmds - * | - * V - * AutoCmd.next - * | - * V - * NULL - * etc. - * - * The order of AutoCmds is important, this is the order in which they were - * defined and will have to be executed. - */ +// +// The autocommands are stored in a list for each event. +// Autocommands for the same pattern, that are consecutive, are joined +// together, to avoid having to match the pattern too often. +// The result is an array of Autopat lists, which point to AutoCmd lists: +// +// last_autopat[0] -----------------------------+ +// V +// first_autopat[0] --> Autopat.next --> Autopat.next --> NULL +// Autopat.cmds Autopat.cmds +// | | +// V V +// AutoCmd.next AutoCmd.next +// | | +// V V +// AutoCmd.next NULL +// | +// V +// NULL +// +// last_autopat[1] --------+ +// V +// first_autopat[1] --> Autopat.next --> NULL +// Autopat.cmds +// | +// V +// AutoCmd.next +// | +// V +// NULL +// etc. +// +// The order of AutoCmds is important, this is the order in which they were +// defined and will have to be executed. +// typedef struct AutoCmd { - char_u *cmd; /* The command to be executed (NULL - when command has been removed) */ - char nested; /* If autocommands nest here */ - char last; /* last command in list */ - scid_T scriptID; /* script ID where defined */ - struct AutoCmd *next; /* Next AutoCmd in list */ + char_u *cmd; // The command to be executed (NULL + // when command has been removed) + char nested; // If autocommands nest here + char last; // last command in list + scid_T scriptID; // script ID where defined + struct AutoCmd *next; // Next AutoCmd in list } AutoCmd; typedef struct AutoPat { - char_u *pat; /* pattern as typed (NULL when pattern - has been removed) */ - regprog_T *reg_prog; /* compiled regprog for pattern */ - AutoCmd *cmds; /* list of commands to do */ - struct AutoPat *next; /* next AutoPat in AutoPat list */ - int group; /* group ID */ - int patlen; /* strlen() of pat */ - int buflocal_nr; /* !=0 for buffer-local AutoPat */ - char allow_dirs; /* Pattern may match whole path */ - char last; /* last pattern for apply_autocmds() */ + struct AutoPat *next; // next AutoPat in AutoPat list; MUST + // be the first entry + char_u *pat; // pattern as typed (NULL when pattern + // has been removed) + regprog_T *reg_prog; // compiled regprog for pattern + AutoCmd *cmds; // list of commands to do + int group; // group ID + int patlen; // strlen() of pat + int buflocal_nr; // !=0 for buffer-local AutoPat + char allow_dirs; // Pattern may match whole path + char last; // last pattern for apply_autocmds() } AutoPat; /* @@ -226,6 +229,15 @@ void filemess(buf_T *buf, char_u *name, char_u *s, int attr) msg_scrolled_ign = FALSE; } +static AutoPat *last_autopat[NUM_EVENTS] = { + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; + /* * Read lines from file "fname" into the buffer after line "from". * @@ -286,8 +298,7 @@ readfile ( off_T filesize = 0; int skip_read = false; context_sha256_T sha_ctx; - int read_undo_file = FALSE; - int split = 0; /* number of split lines */ + int read_undo_file = false; linenr_T linecnt; int error = FALSE; /* errors encountered */ int ff_error = EOL_UNKNOWN; /* file format with errors */ @@ -1017,8 +1028,8 @@ retry: size = size / ICONV_MULT; /* worst case */ if (conv_restlen > 0) { - /* Insert unconverted bytes from previous line. */ - memmove(ptr, conv_rest, conv_restlen); + // Insert unconverted bytes from previous line. + memmove(ptr, conv_rest, conv_restlen); // -V614 ptr += conv_restlen; size -= conv_restlen; } @@ -1725,9 +1736,17 @@ failed: xfree(buffer); if (read_stdin) { - /* Use stderr for stdin, makes shell commands work. */ close(0); +#ifndef WIN32 + // On Unix, use stderr for stdin, makes shell commands work. ignored = dup(2); +#else + // On Windows, use the console input handle for stdin. + HANDLE conin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES)NULL, + OPEN_EXISTING, 0, (HANDLE)NULL); + ignored = _open_osfhandle(conin, _O_RDONLY); +#endif } if (tmpname != NULL) { @@ -1822,10 +1841,6 @@ failed: STRCAT(IObuff, _("[CR missing]")); c = TRUE; } - if (split) { - STRCAT(IObuff, _("[long lines split]")); - c = TRUE; - } if (notconverted) { STRCAT(IObuff, _("[NOT converted]")); c = TRUE; @@ -3048,7 +3063,7 @@ nobackup: */ if (reset_changed && !newfile && overwriting && !(exiting && backup != NULL)) { - ml_preserve(buf, FALSE); + ml_preserve(buf, false, !!p_fs); if (got_int) { SET_ERRMSG(_(e_interr)); goto restore_backup; @@ -3332,9 +3347,9 @@ restore_backup: *s++ = NL; } } - if (++len == bufsize && end) { + if (++len == bufsize) { if (buf_write_bytes(&write_info) == FAIL) { - end = 0; /* write error: break loop */ + end = 0; // Write error: break loop. break; } nchars += bufsize; @@ -3343,7 +3358,7 @@ restore_backup: os_breakcheck(); if (got_int) { - end = 0; /* Interrupted, break loop */ + end = 0; // Interrupted, break loop. break; } } @@ -4318,7 +4333,7 @@ void shorten_fnames(int force) && !path_with_url((char *)buf->b_fname) && (force || buf->b_sfname == NULL - || path_is_absolute_path(buf->b_sfname))) { + || path_is_absolute(buf->b_sfname))) { xfree(buf->b_sfname); buf->b_sfname = NULL; p = path_shorten_fname(buf->b_ffname, dirname); @@ -4440,7 +4455,7 @@ char *modname(const char *fname, const char *ext, bool prepend_dot) /// @param size size of the buffer /// @param fp file to read from /// -/// @return true for end-of-file. +/// @return true for EOF or error bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL { char *retval; @@ -4451,7 +4466,7 @@ bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL do { errno = 0; retval = fgets((char *)buf, size, fp); - } while (retval == NULL && errno == EINTR); + } while (retval == NULL && errno == EINTR && ferror(fp)); if (buf[size - 2] != NUL && buf[size - 2] != '\n') { char tbuf[200]; @@ -4463,12 +4478,12 @@ bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL tbuf[sizeof(tbuf) - 2] = NUL; errno = 0; retval = fgets((char *)tbuf, sizeof(tbuf), fp); - if (retval == NULL && errno != EINTR) { + if (retval == NULL && (feof(fp) || errno != EINTR)) { break; } } while (tbuf[sizeof(tbuf) - 2] != NUL && tbuf[sizeof(tbuf) - 2] != '\n'); } - return retval ? false : feof(fp); + return retval == NULL; } /// Read 2 bytes from "fd" and turn them into an int, MSB first. @@ -5528,6 +5543,15 @@ static void au_cleanup(void) /* remove the pattern if it has been marked for deletion */ if (ap->pat == NULL) { + if (ap->next == NULL) { + if (prev_ap == &(first_autopat[(int)event])) { + last_autopat[(int)event] = NULL; + } else { + // this depends on the "next" field being the first in + // the struct + last_autopat[(int)event] = (AutoPat *)prev_ap; + } + } *prev_ap = ap->next; vim_regfree(ap->reg_prog); xfree(ap); @@ -6120,10 +6144,13 @@ static int do_autocmd_event(event_T event, char_u *pat, int nested, char_u *cmd, patlen = (int)STRLEN(buflocal_pat); /* but not endpat */ } - /* - * Find AutoPat entries with this pattern. - */ - prev_ap = &first_autopat[(int)event]; + // Find AutoPat entries with this pattern. When adding a command it + // always goes at or after the last one, so start at the end. + if (!forceit && *cmd != NUL && last_autopat[(int)event] != NULL) { + prev_ap = &last_autopat[(int)event]; + } else { + prev_ap = &first_autopat[(int)event]; + } while ((ap = *prev_ap) != NULL) { if (ap->pat != NULL) { /* Accept a pattern when: @@ -6209,6 +6236,7 @@ static int do_autocmd_event(event_T event, char_u *pat, int nested, char_u *cmd, } ap->cmds = NULL; *prev_ap = ap; + last_autopat[(int)event] = ap; ap->next = NULL; if (group == AUGROUP_ALL) ap->group = current_augroup; @@ -6655,7 +6683,6 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, char_u *save_sourcing_name; linenr_T save_sourcing_lnum; char_u *save_autocmd_fname; - int save_autocmd_fname_full; int save_autocmd_bufnr; char_u *save_autocmd_match; int save_autocmd_busy; @@ -6728,7 +6755,6 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, * Save the autocmd_* variables and info about the current buffer. */ save_autocmd_fname = autocmd_fname; - save_autocmd_fname_full = autocmd_fname_full; save_autocmd_bufnr = autocmd_bufnr; save_autocmd_match = autocmd_match; save_autocmd_busy = autocmd_busy; @@ -6755,9 +6781,9 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, autocmd_fname = fname_io; } if (autocmd_fname != NULL) { - autocmd_fname = vim_strsave(autocmd_fname); + // Allocate MAXPATHL for when eval_vars() resolves the fullpath. + autocmd_fname = vim_strnsave(autocmd_fname, MAXPATHL); } - autocmd_fname_full = false; // call FullName_save() later /* * Set the buffer number to be used for <abuf>. @@ -6924,7 +6950,6 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, sourcing_lnum = save_sourcing_lnum; xfree(autocmd_fname); autocmd_fname = save_autocmd_fname; - autocmd_fname_full = save_autocmd_fname_full; autocmd_bufnr = save_autocmd_bufnr; autocmd_match = save_autocmd_match; current_SID = save_current_SID; diff --git a/src/nvim/fold.c b/src/nvim/fold.c index db88791967..ad9cd4d562 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -1775,7 +1775,7 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, break; } if (*p != NUL) { - p = transstr(text); + p = (char_u *)transstr((const char *)text); xfree(text); text = p; } diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index b01321e713..2ee1e5d4c5 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -324,6 +324,8 @@ end output = io.open(lua_c_bindings_outputf, 'wb') output:write([[ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <lua.h> #include <lualib.h> #include <lauxlib.h> diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index d1a28ae3b0..07a65c2611 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -48,8 +48,14 @@ #include "nvim/event/loop.h" #include "nvim/os/input.h" #include "nvim/os/os.h" +#include "nvim/os/fileio.h" #include "nvim/api/private/handle.h" + +/// Index in scriptin +static int curscript = 0; +FileDescriptor *scriptin[NSCRIPT] = { NULL }; + /* * These buffers are used for storing: * - stuffed characters: A command that is translated into another command. @@ -1243,10 +1249,13 @@ openscript ( ++curscript; /* use NameBuff for expanded name */ expand_env(name, NameBuff, MAXPATHL); - if ((scriptin[curscript] = mch_fopen((char *)NameBuff, READBIN)) == NULL) { - EMSG2(_(e_notopen), name); - if (curscript) - --curscript; + int error; + if ((scriptin[curscript] = file_open_new(&error, (char *)NameBuff, + kFileReadOnly, 0)) == NULL) { + emsgf(_(e_notopen_2), name, os_strerror(error)); + if (curscript) { + curscript--; + } return; } save_typebuf(); @@ -1296,7 +1305,7 @@ static void closescript(void) free_typebuf(); typebuf = saved_typebuf[curscript]; - fclose(scriptin[curscript]); + file_free(scriptin[curscript], false); scriptin[curscript] = NULL; if (curscript > 0) --curscript; @@ -1319,10 +1328,8 @@ int using_script(void) return scriptin[curscript] != NULL; } -/* - * This function is called just before doing a blocking wait. Thus after - * waiting 'updatetime' for a character to arrive. - */ +/// This function is called just before doing a blocking wait. Thus after +/// waiting 'updatetime' for a character to arrive. void before_blocking(void) { updatescript(0); @@ -1331,21 +1338,22 @@ void before_blocking(void) } } -/* - * updatescipt() is called when a character can be written into the script file - * or when we have waited some time for a character (c == 0) - * - * All the changed memfiles are synced if c == 0 or when the number of typed - * characters reaches 'updatecount' and 'updatecount' is non-zero. - */ -void updatescript(int c) +/// updatescript() is called when a character can be written to the script file +/// or when we have waited some time for a character (c == 0). +/// +/// All the changed memfiles are synced if c == 0 or when the number of typed +/// characters reaches 'updatecount' and 'updatecount' is non-zero. +static void updatescript(int c) { static int count = 0; - if (c && scriptout) + if (c && scriptout) { putc(c, scriptout); - if (c == 0 || (p_uc > 0 && ++count >= p_uc)) { - ml_sync_all(c == 0, TRUE); + } + bool idle = (c == 0); + if (idle || (p_uc > 0 && ++count >= p_uc)) { + ml_sync_all(idle, true, + (!!p_fs || idle)); // Always fsync at idle (CursorHold). count = 0; } } @@ -1577,7 +1585,7 @@ vungetc ( /* unget one character (can only be done once!) */ old_mouse_col = mouse_col; } -/// get a character: +/// Gets a character: /// 1. from the stuffbuffer /// This is used for abbreviated commands like "D" -> "d$". /// Also used to redo a command for ".". @@ -1595,7 +1603,7 @@ vungetc ( /* unget one character (can only be done once!) */ /// if "advance" is FALSE (vpeekc()): /// just look whether there is a character available. /// -/// When "no_mapping" is zero, checks for mappings in the current mode. +/// When `no_mapping` (global) is zero, checks for mappings in the current mode. /// Only returns one byte (of a multi-byte character). /// K_SPECIAL and CSI may be escaped, need to get two more bytes then. static int vgetorpeek(int advance) @@ -2346,9 +2354,8 @@ inchar ( int tb_change_cnt ) { - int len = 0; /* init for GCC */ - int retesc = FALSE; /* return ESC with gotint */ - int script_char; + int len = 0; // Init for GCC. + int retesc = false; // Return ESC with gotint. if (wait_time == -1L || wait_time > 100L) { // flush output before waiting @@ -2366,45 +2373,38 @@ inchar ( } undo_off = FALSE; /* restart undo now */ - /* - * Get a character from a script file if there is one. - * If interrupted: Stop reading script files, close them all. - */ - script_char = -1; - while (scriptin[curscript] != NULL && script_char < 0 - && !ignore_script - ) { - - - if (got_int || (script_char = getc(scriptin[curscript])) < 0) { - /* Reached EOF. - * Careful: closescript() frees typebuf.tb_buf[] and buf[] may - * point inside typebuf.tb_buf[]. Don't use buf[] after this! */ + // Get a character from a script file if there is one. + // If interrupted: Stop reading script files, close them all. + ptrdiff_t read_size = -1; + while (scriptin[curscript] != NULL && read_size <= 0 && !ignore_script) { + char script_char; + if (got_int + || (read_size = file_read(scriptin[curscript], &script_char, 1)) != 1) { + // Reached EOF or some error occurred. + // Careful: closescript() frees typebuf.tb_buf[] and buf[] may + // point inside typebuf.tb_buf[]. Don't use buf[] after this! closescript(); - /* - * When reading script file is interrupted, return an ESC to get - * back to normal mode. - * Otherwise return -1, because typebuf.tb_buf[] has changed. - */ - if (got_int) - retesc = TRUE; - else + // When reading script file is interrupted, return an ESC to get + // back to normal mode. + // Otherwise return -1, because typebuf.tb_buf[] has changed. + if (got_int) { + retesc = true; + } else { return -1; + } } else { buf[0] = (char_u)script_char; len = 1; } } - if (script_char < 0) { /* did not get a character from script */ - /* - * If we got an interrupt, skip all previously typed characters and - * return TRUE if quit reading script file. - * Stop reading typeahead when a single CTRL-C was read, - * fill_input_buf() returns this when not able to read from stdin. - * Don't use buf[] here, closescript() may have freed typebuf.tb_buf[] - * and buf may be pointing inside typebuf.tb_buf[]. - */ + if (read_size <= 0) { // Did not get a character from script. + // If we got an interrupt, skip all previously typed characters and + // return TRUE if quit reading script file. + // Stop reading typeahead when a single CTRL-C was read, + // fill_input_buf() returns this when not able to read from stdin. + // Don't use buf[] here, closescript() may have freed typebuf.tb_buf[] + // and buf may be pointing inside typebuf.tb_buf[]. if (got_int) { #define DUM_LEN MAXMAPLEN * 3 + 3 char_u dum[DUM_LEN + 1]; @@ -2417,21 +2417,18 @@ inchar ( return retesc; } - /* - * Always flush the output characters when getting input characters - * from the user. - */ + // Always flush the output characters when getting input characters + // from the user. ui_flush(); - /* - * Fill up to a third of the buffer, because each character may be - * tripled below. - */ + // Fill up to a third of the buffer, because each character may be + // tripled below. len = os_inchar(buf, maxlen / 3, (int)wait_time, tb_change_cnt); } - if (typebuf_changed(tb_change_cnt)) + if (typebuf_changed(tb_change_cnt)) { return 0; + } return fix_input_buffer(buf, len); } @@ -4257,3 +4254,70 @@ mapblock_T *get_maphash(int index, buf_T *buf) return (buf == NULL) ? maphash[index] : buf->b_maphash[index]; } + +/// Get command argument for <Cmd> key +char_u * getcmdkeycmd(int promptc, void *cookie, int indent) +{ + garray_T line_ga; + int c1 = -1, c2; + int cmod = 0; + bool aborted = false; + + ga_init(&line_ga, 1, 32); + + no_mapping++; + + got_int = false; + while (c1 != NUL && !aborted) { + ga_grow(&line_ga, 32); + + if (vgetorpeek(false) == NUL) { + // incomplete <Cmd> is an error, because there is not much the user + // could do in this state. + EMSG(e_cmdmap_err); + aborted = true; + break; + } + + // Get one character at a time. + c1 = vgetorpeek(true); + // Get two extra bytes for special keys + if (c1 == K_SPECIAL) { + c1 = vgetorpeek(true); // no mapping for these chars + c2 = vgetorpeek(true); + if (c1 == KS_MODIFIER) { + cmod = c2; + continue; + } + c1 = TO_SPECIAL(c1, c2); + } + + + if (got_int) { + aborted = true; + } else if (c1 == '\r' || c1 == '\n') { + c1 = NUL; // end the line + } else if (c1 == ESC) { + aborted = true; + } else if (c1 == K_COMMAND) { + // special case to give nicer error message + EMSG(e_cmdmap_repeated); + aborted = true; + } else if (IS_SPECIAL(c1)) { + EMSG2(e_cmdmap_key, get_special_key_name(c1, cmod)); + aborted = true; + } else { + ga_append(&line_ga, (char)c1); + } + + cmod = 0; + } + + no_mapping--; + + if (aborted) { + ga_clear(&line_ga); + } + + return (char_u *)line_ga.ga_data; +} diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h index e634273e0d..38a2e75663 100644 --- a/src/nvim/getchar.h +++ b/src/nvim/getchar.h @@ -1,24 +1,30 @@ #ifndef NVIM_GETCHAR_H #define NVIM_GETCHAR_H +#include "nvim/os/fileio.h" #include "nvim/types.h" #include "nvim/buffer_defs.h" #include "nvim/ex_cmds_defs.h" -/// Values for "noremap" argument of ins_typebuf(). Also used for -/// map->m_noremap and menu->noremap[]. -/// @addtogroup REMAP_VALUES -/// @{ -#define REMAP_YES 0 ///< allow remapping -#define REMAP_NONE -1 ///< no remapping -#define REMAP_SCRIPT -2 ///< remap script-local mappings only -#define REMAP_SKIP -3 ///< no remapping for first char -/// @} +/// Values for "noremap" argument of ins_typebuf() +/// +/// Also used for map->m_noremap and menu->noremap[]. +enum { + REMAP_YES = 0, ///< Allow remapping. + REMAP_NONE = -1, ///< No remapping. + REMAP_SCRIPT = -2, ///< Remap script-local mappings only. + REMAP_SKIP = -3, ///< No remapping for first char. +} RemapValues; #define KEYLEN_PART_KEY -1 /* keylen value for incomplete key-code */ #define KEYLEN_PART_MAP -2 /* keylen value for incomplete mapping */ #define KEYLEN_REMOVED 9999 /* keylen value for removed sequence */ +/// Maximum number of streams to read script from +enum { NSCRIPT = 15 }; + +/// Streams to read script from +extern FileDescriptor *scriptin[NSCRIPT]; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "getchar.h.generated.h" diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 1856384ffa..ddc58c2425 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -16,8 +16,6 @@ #define IOSIZE (1024+1) // file I/O and sprintf buffer size -#define MAX_MCO 6 // maximum value for 'maxcombine' - # define MSG_BUF_LEN 480 // length of buffer for small messages # define MSG_BUF_CLEN (MSG_BUF_LEN / 6) // cell length (worst case: utf-8 // takes 6 bytes for one cell) @@ -80,6 +78,11 @@ typedef enum { kTrue = 1, } TriState; +EXTERN struct nvim_stats_s { + int64_t fsync; + int64_t redraw; +} g_stats INIT(= { 0, 0 }); + /* Values for "starting" */ #define NO_SCREEN 2 /* no screen updating yet */ #define NO_BUFFERS 1 /* not all buffers loaded yet */ @@ -160,10 +163,6 @@ EXTERN u8char_T *ScreenLinesC[MAX_MCO]; /* composing characters */ EXTERN int Screen_mco INIT(= 0); /* value of p_mco used when allocating ScreenLinesC[] */ -/* Only used for euc-jp: Second byte of a character that starts with 0x8e. - * These are single-width. */ -EXTERN schar_T *ScreenLines2 INIT(= NULL); - EXTERN int screen_Rows INIT(= 0); /* actual size of ScreenLines[] */ EXTERN int screen_Columns INIT(= 0); /* actual size of ScreenLines[] */ @@ -317,17 +316,11 @@ EXTERN int do_profiling INIT(= PROF_NONE); /* PROF_ values */ /* * The exception currently being thrown. Used to pass an exception to * a different cstack. Also used for discarding an exception before it is - * caught or made pending. Only valid when did_throw is TRUE. + * caught or made pending. */ EXTERN except_T *current_exception; /* - * did_throw: An exception is being thrown. Reset when the exception is caught - * or as long as it is pending in a finally clause. - */ -EXTERN int did_throw INIT(= FALSE); - -/* * need_rethrow: set to TRUE when a throw that cannot be handled in do_cmdline() * must be propagated to the cstack of the previously called do_cmdline(). */ @@ -397,16 +390,20 @@ EXTERN int may_garbage_collect INIT(= FALSE); EXTERN int want_garbage_collect INIT(= FALSE); EXTERN int garbage_collect_at_exit INIT(= FALSE); -/* Special values for current_SID. */ -#define SID_MODELINE -1 /* when using a modeline */ -#define SID_CMDARG -2 /* for "--cmd" argument */ -#define SID_CARG -3 /* for "-c" argument */ -#define SID_ENV -4 /* for sourcing environment variable */ -#define SID_ERROR -5 /* option was reset because of an error */ -#define SID_NONE -6 /* don't set scriptID */ - -/* ID of script being sourced or was sourced to define the current function. */ +// Special values for current_SID. +#define SID_MODELINE -1 // when using a modeline +#define SID_CMDARG -2 // for "--cmd" argument +#define SID_CARG -3 // for "-c" argument +#define SID_ENV -4 // for sourcing environment variable +#define SID_ERROR -5 // option was reset because of an error +#define SID_NONE -6 // don't set scriptID +#define SID_LUA -7 // for Lua scripts/chunks +#define SID_API_CLIENT -8 // for API clients + +// ID of script being sourced or was sourced to define the current function. EXTERN scid_T current_SID INIT(= 0); +// ID of the current channel making a client API call +EXTERN uint64_t current_channel_id INIT(= 0); EXTERN bool did_source_packages INIT(= false); @@ -416,7 +413,7 @@ EXTERN struct caller_scope { scid_T SID; uint8_t *sourcing_name, *autocmd_fname, *autocmd_match; linenr_T sourcing_lnum; - int autocmd_fname_full, autocmd_bufnr; + int autocmd_bufnr; void *funccalp; } provider_caller_scope; EXTERN int provider_call_nesting INIT(= 0); @@ -547,10 +544,6 @@ EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer #define FOR_ALL_BUFFERS_BACKWARDS(buf) \ for (buf_T *buf = lastbuf; buf != NULL; buf = buf->b_prev) -/* Flag that is set when switching off 'swapfile'. It means that all blocks - * are to be loaded into memory. Shouldn't be global... */ -EXTERN int mf_dont_release INIT(= FALSE); /* don't release blocks */ - /* * List of files being edited (global argument list). curwin->w_alist points * to this when the window is using the global argument list. @@ -722,7 +715,7 @@ EXTERN int vr_lines_changed INIT(= 0); /* #Lines changed by "gR" so far */ // mbyte flags that used to depend on 'encoding'. These are now deprecated, as // 'encoding' is always "utf-8". Code that use them can be refactored to // remove dead code. -#define enc_dbcs false +#define enc_dbcs 0 #define enc_utf8 true #define has_mbyte true @@ -840,10 +833,7 @@ EXTERN int do_redraw INIT(= FALSE); /* extra redraw once */ EXTERN int need_highlight_changed INIT(= true); EXTERN char *used_shada_file INIT(= NULL); // name of the ShaDa file to use -#define NSCRIPT 15 -EXTERN FILE *scriptin[NSCRIPT]; /* streams to read script from */ -EXTERN int curscript INIT(= 0); /* index in scriptin[] */ -EXTERN FILE *scriptout INIT(= NULL); /* stream to write script to */ +EXTERN FILE *scriptout INIT(= NULL); ///< Stream to write script to. // volatile because it is used in a signal handler. EXTERN volatile int got_int INIT(= false); // set to true when interrupt @@ -872,7 +862,6 @@ EXTERN char_u *last_cmdline INIT(= NULL); // last command line (for ":) EXTERN char_u *repeat_cmdline INIT(= NULL); // command line for "." EXTERN char_u *new_last_cmdline INIT(= NULL); // new value for last_cmdline EXTERN char_u *autocmd_fname INIT(= NULL); // fname for <afile> on cmdline -EXTERN int autocmd_fname_full; // autocmd_fname is full path EXTERN int autocmd_bufnr INIT(= 0); // fnum for <abuf> on cmdline EXTERN char_u *autocmd_match INIT(= NULL); // name for <amatch> on cmdline EXTERN int did_cursorhold INIT(= false); // set when CursorHold t'gerd @@ -954,6 +943,7 @@ EXTERN int fill_stlnc INIT(= ' '); EXTERN int fill_vert INIT(= 9474); // │ EXTERN int fill_fold INIT(= 183); // · EXTERN int fill_diff INIT(= '-'); +EXTERN int fill_msgsep INIT(= ' '); /* Whether 'keymodel' contains "stopsel" and "startsel". */ EXTERN int km_stopsel INIT(= FALSE); @@ -1076,7 +1066,6 @@ EXTERN char_u e_nesting[] INIT(= N_("E22: Scripts nested too deep")); EXTERN char_u e_noalt[] INIT(= N_("E23: No alternate file")); EXTERN char_u e_noabbr[] INIT(= N_("E24: No such abbreviation")); EXTERN char_u e_nobang[] INIT(= N_("E477: No ! allowed")); -EXTERN char_u e_nogvim[] INIT(= N_("E25: Nvim does not have a built-in GUI")); EXTERN char_u e_nogroup[] INIT(= N_("E28: No such highlight group name: %s")); EXTERN char_u e_noinstext[] INIT(= N_("E29: No inserted text yet")); EXTERN char_u e_nolastcmd[] INIT(= N_("E30: No previous command line")); @@ -1092,6 +1081,7 @@ EXTERN char_u e_norange[] INIT(= N_("E481: No range allowed")); EXTERN char_u e_noroom[] INIT(= N_("E36: Not enough room")); EXTERN char_u e_notmp[] INIT(= N_("E483: Can't get temp file name")); EXTERN char_u e_notopen[] INIT(= N_("E484: Can't open file %s")); +EXTERN char_u e_notopen_2[] INIT(= N_("E484: Can't open file %s: %s")); EXTERN char_u e_notread[] INIT(= N_("E485: Can't read file %s")); EXTERN char_u e_nowrtmsg[] INIT(= N_( "E37: No write since last change (add ! to override)")); @@ -1155,6 +1145,12 @@ EXTERN char_u e_fnametoolong[] INIT(= N_("E856: Filename too long")); EXTERN char_u e_float_as_string[] INIT(= N_("E806: using Float as a String")); EXTERN char_u e_autocmd_err[] INIT(=N_( "E5500: autocmd has thrown an exception: %s")); +EXTERN char_u e_cmdmap_err[] INIT(=N_( + "E5520: <Cmd> mapping must end with <CR>")); +EXTERN char_u e_cmdmap_repeated[] INIT(=N_( + "E5521: <Cmd> mapping must end with <CR> before second <Cmd>")); +EXTERN char_u e_cmdmap_key[] INIT(=N_( + "E5522: <Cmd> mapping must not include %s key")); EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM")); diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 08157935f5..3518c8bdcc 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -87,6 +87,7 @@ typedef enum { , HLF_QFL // selected quickfix line , HLF_0 // Whitespace , HLF_INACTIVE // NormalNC: Normal text in non-current windows + , HLF_MSGSEP // message separator line , HLF_COUNT // MUST be the last one } hlf_T; @@ -137,7 +138,8 @@ EXTERN const char *hlf_names[] INIT(= { [HLF_MC] = "ColorColumn", [HLF_QFL] = "QuickFixLine", [HLF_0] = "Whitespace", - [HLF_INACTIVE] = "NormalNC" + [HLF_INACTIVE] = "NormalNC", + [HLF_MSGSEP] = "MsgSeparator", }); diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index b78b56562c..5d7bd26a2b 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -1001,8 +1001,8 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, return FALSE; } - if (qfpos != NULL && *qfpos != '0' && totmatches > 0) { - /* fill error list */ + if (qfpos != NULL && *qfpos != '0') { + // Fill error list. FILE *f; char_u *tmp = vim_tempname(); qf_info_T *qi = NULL; diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index 5e56e5f41b..628bfef221 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -285,6 +285,7 @@ static const struct key_name_entry { { K_SNR, "SNR" }, { K_PLUG, "Plug" }, { K_PASTE, "Paste" }, + { K_COMMAND, "Cmd" }, { 0, NULL } // NOTE: When adding a long name update MAX_KEY_NAME_LEN. }; diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h index 04fc93e29e..c64691e8ea 100644 --- a/src/nvim/keymap.h +++ b/src/nvim/keymap.h @@ -243,6 +243,7 @@ enum key_extra { , KE_EVENT // event , KE_PASTE // special key to toggle the 'paste' option. // sent only by UIs + , KE_COMMAND // special key to execute command in any mode }; /* @@ -431,6 +432,7 @@ enum key_extra { #define K_EVENT TERMCAP2KEY(KS_EXTRA, KE_EVENT) #define K_PASTE TERMCAP2KEY(KS_EXTRA, KE_PASTE) +#define K_COMMAND TERMCAP2KEY(KS_EXTRA, KE_COMMAND) /* Bits for modifier mask */ /* 0x01 cannot be used, because the modifier must be 0x02 or higher */ @@ -441,7 +443,7 @@ enum key_extra { #define MOD_MASK_2CLICK 0x20 // use MOD_MASK_MULTI_CLICK #define MOD_MASK_3CLICK 0x40 // use MOD_MASK_MULTI_CLICK #define MOD_MASK_4CLICK 0x60 // use MOD_MASK_MULTI_CLICK -#define MOD_MASK_CMD 0x80 // "super" key (OSX/Mac: command-key) +#define MOD_MASK_CMD 0x80 // "super" key (macOS: command-key) #define MOD_MASK_MULTI_CLICK (MOD_MASK_2CLICK|MOD_MASK_3CLICK| \ MOD_MASK_4CLICK) diff --git a/src/nvim/lib/kvec.h b/src/nvim/lib/kvec.h index ad56c9237b..6d54c7f78d 100644 --- a/src/nvim/lib/kvec.h +++ b/src/nvim/lib/kvec.h @@ -142,6 +142,8 @@ static inline void *_memcpy_free(void *const restrict dest, return dest; } +// -V:kvi_push:512 + /// Resize vector with preallocated array /// /// @note May not resize to an array smaller then init_array: if requested, diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 9e3063b164..5da6d2c0a0 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -423,7 +423,7 @@ nlua_pop_typval_table_processing_end: #define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING #define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \ - TYPVAL_ENCODE_CONV_NIL() + TYPVAL_ENCODE_CONV_NIL(tv) #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ do { \ diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index c7952520b0..e1bbb03d3f 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -1,3 +1,62 @@ +-- Internal-only until comments in #8107 are addressed. +-- Returns: +-- {errcode}, {output} +local function _system(cmd) + local out = vim.api.nvim_call_function('system', { cmd }) + local err = vim.api.nvim_get_vvar('shell_error') + return err, out +end + +-- Gets process info from the `ps` command. +-- Used by nvim_get_proc() as a fallback. +local function _os_proc_info(pid) + if pid == nil or pid <= 0 or type(pid) ~= 'number' then + error('invalid pid') + end + local cmd = { 'ps', '-p', pid, '-o', 'ucomm=', } + local err, name = _system(cmd) + if 1 == err and string.gsub(name, '%s*', '') == '' then + return {} -- Process not found. + elseif 0 ~= err then + local args_str = vim.api.nvim_call_function('string', { cmd }) + error('command failed: '..args_str) + end + local _, ppid = _system({ 'ps', '-p', pid, '-o', 'ppid=', }) + -- Remove trailing whitespace. + name = string.gsub(name, '%s+$', '') + ppid = string.gsub(ppid, '%s+$', '') + ppid = tonumber(ppid) == nil and -1 or tonumber(ppid) + return { + name = name, + pid = pid, + ppid = ppid, + } +end + +-- Gets process children from the `pgrep` command. +-- Used by nvim_get_proc_children() as a fallback. +local function _os_proc_children(ppid) + if ppid == nil or ppid <= 0 or type(ppid) ~= 'number' then + error('invalid ppid') + end + local cmd = { 'pgrep', '-P', ppid, } + local err, rv = _system(cmd) + if 1 == err and string.gsub(rv, '%s*', '') == '' then + return {} -- Process not found. + elseif 0 ~= err then + local args_str = vim.api.nvim_call_function('string', { cmd }) + error('command failed: '..args_str) + end + local children = {} + for s in string.gmatch(rv, '%S+') do + local i = tonumber(s) + if i ~= nil then + table.insert(children, i) + end + end + return children +end + -- TODO(ZyX-I): Create compatibility layer. --{{{1 package.path updater function -- Last inserted paths. Used to clear out items from package.[c]path when they @@ -58,7 +117,12 @@ local function _update_package_paths() end last_nvim_paths = cur_nvim_paths end ---{{{1 Module definition -return { + +local module = { _update_package_paths = _update_package_paths, + _os_proc_children = _os_proc_children, + _os_proc_info = _os_proc_info, + _system = _system, } + +return module diff --git a/src/nvim/macros.h b/src/nvim/macros.h index 61834c6499..348df2d9b6 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -136,19 +136,24 @@ # define RESET_BINDING(wp) (wp)->w_p_scb = FALSE; (wp)->w_p_crb = FALSE -/// Calculate the length of a C array. +/// Calculate the length of a C array /// /// This should be called with a real array. Calling this with a pointer is an -/// error. A mechanism to detect many (though not all) of those errors at compile -/// time is implemented. It works by the second division producing a division by -/// zero in those cases (-Wdiv-by-zero in GCC). -#define ARRAY_SIZE(arr) ((sizeof(arr)/sizeof((arr)[0])) / ((size_t)(!(sizeof(arr) % sizeof((arr)[0]))))) +/// error. A mechanism to detect many (though not all) of those errors at +/// compile time is implemented. It works by the second division producing +/// a division by zero in those cases (-Wdiv-by-zero in GCC). +#define ARRAY_SIZE(arr) \ + ((sizeof(arr)/sizeof((arr)[0])) \ + / ((size_t)(!(sizeof(arr) % sizeof((arr)[0]))))) + +/// Get last array entry +/// +/// This should be called with a real array. Calling this with a pointer is an +/// error. +#define ARRAY_LAST_ENTRY(arr) (arr)[ARRAY_SIZE(arr) - 1] // Duplicated in os/win_defs.h to avoid include-order sensitivity. -#if defined(WIN32) && defined(RGB) -# undef RGB -#endif -#define RGB(r, g, b) ((r << 16) | (g << 8) | b) +#define RGB_(r, g, b) ((r << 16) | (g << 8) | b) #define STR_(x) #x #define STR(x) STR_(x) diff --git a/src/nvim/main.c b/src/nvim/main.c index 4288d7f9d7..a4ed868af1 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -72,30 +72,30 @@ # include "nvim/os/pty_process_unix.h" #endif -/* Maximum number of commands from + or -c arguments. */ +// Maximum number of commands from + or -c arguments. #define MAX_ARG_CMDS 10 -/* values for "window_layout" */ -#define WIN_HOR 1 /* "-o" horizontally split windows */ -#define WIN_VER 2 /* "-O" vertically split windows */ -#define WIN_TABS 3 /* "-p" windows on tab pages */ +// values for "window_layout" +#define WIN_HOR 1 // "-o" horizontally split windows +#define WIN_VER 2 // "-O" vertically split windows +#define WIN_TABS 3 // "-p" windows on tab pages -/* Struct for various parameters passed between main() and other functions. */ +// Struct for various parameters passed between main() and other functions. typedef struct { int argc; char **argv; char *use_vimrc; // vimrc from -u argument - int n_commands; /* no. of commands from + or -c */ + int n_commands; // no. of commands from + or -c char *commands[MAX_ARG_CMDS]; // commands from + or -c arg - char_u cmds_tofree[MAX_ARG_CMDS]; /* commands that need free() */ - int n_pre_commands; /* no. of commands from --cmd */ + char_u cmds_tofree[MAX_ARG_CMDS]; // commands that need free() + int n_pre_commands; // no. of commands from --cmd char *pre_commands[MAX_ARG_CMDS]; // commands from --cmd argument - int edit_type; /* type of editing to do */ - char_u *tagname; /* tag from -t argument */ - char_u *use_ef; /* 'errorfile' from -q argument */ + int edit_type; // type of editing to do + char_u *tagname; // tag from -t argument + char_u *use_ef; // 'errorfile' from -q argument int want_full_screen; bool input_isatty; // stdin is a terminal @@ -103,13 +103,15 @@ typedef struct { bool err_isatty; // stderr is a terminal int no_swap_file; // "-n" argument used int use_debug_break_level; - int window_count; /* number of windows to use */ - int window_layout; /* 0, WIN_HOR, WIN_VER or WIN_TABS */ + int window_count; // number of windows to use + int window_layout; // 0, WIN_HOR, WIN_VER or WIN_TABS #if !defined(UNIX) - int literal; /* don't expand file names */ + int literal; // don't expand file names #endif - int diff_mode; /* start with 'diff' set */ + int diff_mode; // start with 'diff' set + + char *listen_addr; // --listen {address} } mparm_T; /* Values for edit_type. */ @@ -150,7 +152,6 @@ void event_init(void) signal_init(); // finish mspgack-rpc initialization channel_init(); - server_init(); terminal_init(); } @@ -241,9 +242,8 @@ int main(int argc, char **argv) char_u *cwd = NULL; // current workding dir on startup time_init(); - /* Many variables are in "params" so that we can pass them to invoked - * functions without a lot of arguments. "argc" and "argv" are also - * copied, so that they can be changed. */ + // Many variables are in `params` so that we can pass them around easily. + // `argc` and `argv` are also copied, so that they can be changed. init_params(¶ms, argc, argv); init_startuptime(¶ms); @@ -254,11 +254,10 @@ int main(int argc, char **argv) check_and_set_isatty(¶ms); event_init(); - /* - * Process the command line arguments. File names are put in the global - * argument list "global_alist". - */ + // Process the command line arguments. File names are put in the global + // argument list "global_alist". command_line_scan(¶ms); + server_init(params.listen_addr); if (GARGCOUNT > 0) { fname = get_fname(¶ms, cwd); @@ -726,9 +725,7 @@ static void init_locale(void) #endif -/* - * Scan the command line arguments. - */ +/// Scan the command line arguments. static void command_line_scan(mparm_T *parmp) { int argc = parmp->argc; @@ -790,7 +787,8 @@ static void command_line_scan(mparm_T *parmp) mch_exit(0); } else if (STRICMP(argv[0] + argv_idx, "api-info") == 0) { FileDescriptor fp; - const int fof_ret = file_open_fd(&fp, STDOUT_FILENO, true); + const int fof_ret = file_open_fd(&fp, STDOUT_FILENO, + kFileWriteOnly); msgpack_packer *p = msgpack_packer_new(&fp, msgpack_file_write); if (fof_ret != 0) { @@ -819,6 +817,9 @@ static void command_line_scan(mparm_T *parmp) if (!channel_from_stdio(true, CALLBACK_READER_INIT, &err)) { abort(); } + } else if (STRNICMP(argv[0] + argv_idx, "listen", 6) == 0) { + want_argument = true; + argv_idx += 6; } else if (STRNICMP(argv[0] + argv_idx, "literal", 7) == 0) { #if !defined(UNIX) parmp->literal = TRUE; @@ -864,10 +865,6 @@ static void command_line_scan(mparm_T *parmp) case 'f': /* "-f" GUI: run in foreground. */ break; - case 'g': /* "-g" start GUI */ - main_start_gui(); - break; - case 'F': { // "-F" start in Farsi mode: rl + fkmap set. p_fkmap = true; set_option_value("rl", 1L, NULL, 0); @@ -898,26 +895,17 @@ static void command_line_scan(mparm_T *parmp) p_write = FALSE; break; - case 'N': /* "-N" Nocompatible */ - /* No-op */ + case 'N': // "-N" Nocompatible + case 'X': // "-X" Do not connect to X server + // No-op break; case 'n': /* "-n" no swap file */ parmp->no_swap_file = TRUE; break; - case 'p': /* "-p[N]" open N tab pages */ -#ifdef TARGET_API_MAC_OSX - /* For some reason on MacOS X, an argument like: - -psn_0_10223617 is passed in when invoke from Finder - or with the 'open' command */ - if (argv[0][argv_idx] == 's') { - argv_idx = -1; /* bypass full -psn */ - main_start_gui(); - break; - } -#endif - /* default is 0: open window for each file */ + case 'p': // "-p[N]" open N tab pages + // default is 0: open window for each file parmp->window_count = get_number_arg(argv[0], &argv_idx, 0); parmp->window_layout = WIN_TABS; break; @@ -1030,15 +1018,12 @@ static void command_line_scan(mparm_T *parmp) mainerr(err_opt_unknown, argv[0]); } - /* - * Handle option arguments with argument. - */ + // Handle option arguments with argument. if (want_argument) { - /* - * Check for garbage immediately after the option letter. - */ - if (argv[0][argv_idx] != NUL) + // Check for garbage immediately after the option letter. + if (argv[0][argv_idx] != NUL) { mainerr(err_opt_garbage, argv[0]); + } --argc; if (argc < 1 && c != 'S') /* -S has an optional argument */ @@ -1077,13 +1062,17 @@ static void command_line_scan(mparm_T *parmp) break; case '-': - if (argv[-1][2] == 'c') { - /* "--cmd {command}" execute command */ - if (parmp->n_pre_commands >= MAX_ARG_CMDS) + if (strequal(argv[-1], "--cmd")) { + // "--cmd {command}" execute command + if (parmp->n_pre_commands >= MAX_ARG_CMDS) { mainerr(err_extra_cmd, NULL); + } parmp->pre_commands[parmp->n_pre_commands++] = argv[0]; + } else if (strequal(argv[-1], "--listen")) { + // "--listen {address}" + parmp->listen_addr = argv[0]; } - /* "--startuptime <file>" already handled */ + // "--startuptime <file>" already handled break; case 'q': /* "-q {errorfile}" QuickFix mode */ @@ -1097,17 +1086,36 @@ static void command_line_scan(mparm_T *parmp) case 's': /* "-s {scriptin}" read from script file */ if (scriptin[0] != NULL) { scripterror: - mch_errmsg(_("Attempt to open script file again: \"")); - mch_errmsg(argv[-1]); - mch_errmsg(" "); - mch_errmsg(argv[0]); - mch_errmsg("\"\n"); + vim_snprintf((char *)IObuff, IOSIZE, + _("Attempt to open script file again: \"%s %s\"\n"), + argv[-1], argv[0]); + mch_errmsg((const char *)IObuff); mch_exit(2); } - if ((scriptin[0] = mch_fopen(argv[0], READBIN)) == NULL) { - mch_errmsg(_("Cannot open for reading: \"")); - mch_errmsg(argv[0]); - mch_errmsg("\"\n"); + int error; + if (STRCMP(argv[0], "-") == 0) { + const int stdin_dup_fd = os_dup(STDIN_FILENO); +#ifdef WIN32 + // On Windows, replace the original stdin with the + // console input handle. + close(STDIN_FILENO); + const HANDLE conin_handle = + CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES)NULL, + OPEN_EXISTING, 0, (HANDLE)NULL); + const int conin_fd = _open_osfhandle(conin_handle, _O_RDONLY); + assert(conin_fd == STDIN_FILENO); +#endif + FileDescriptor *const stdin_dup = file_open_fd_new( + &error, stdin_dup_fd, kFileReadOnly|kFileNonBlocking); + assert(stdin_dup != NULL); + scriptin[0] = stdin_dup; + } else if ((scriptin[0] = file_open_new( + &error, argv[0], kFileReadOnly|kFileNonBlocking, 0)) == NULL) { + vim_snprintf((char *)IObuff, IOSIZE, + _("Cannot open for reading: \"%s\": %s\n"), + argv[0], os_strerror(error)); + mch_errmsg((const char *)IObuff); mch_exit(2); } save_typebuf(); @@ -1224,11 +1232,10 @@ static void init_params(mparm_T *paramp, int argc, char **argv) paramp->want_full_screen = true; paramp->use_debug_break_level = -1; paramp->window_count = -1; + paramp->listen_addr = NULL; } -/* - * Initialize global startuptime file if "--startuptime" passed as an argument. - */ +/// Initialize global startuptime file if "--startuptime" passed as an argument. static void init_startuptime(mparm_T *paramp) { for (int i = 1; i < paramp->argc; i++) { @@ -1834,17 +1841,6 @@ static void source_startup_scripts(const mparm_T *const parmp) TIME_MSG("sourcing vimrc file(s)"); } -/* - * Setup to start using the GUI. Exit with an error when not available. - */ -static void main_start_gui(void) -{ - mch_errmsg(_(e_nogvim)); - mch_errmsg("\n"); - mch_exit(2); -} - - /// Get an environment variable, and execute it as Ex commands. /// /// @param env environment variable to execute @@ -1968,6 +1964,7 @@ static void usage(void) mch_msg(_(" --api-info Write msgpack-encoded API metadata to stdout\n")); mch_msg(_(" --embed Use stdin/stdout as a msgpack-rpc channel\n")); mch_msg(_(" --headless Don't start a user interface\n")); + mch_msg(_(" --listen <address> Start RPC server at this address\n")); #if !defined(UNIX) mch_msg(_(" --literal Don't expand wildcards\n")); #endif diff --git a/src/nvim/map.h b/src/nvim/map.h index 047aa163ce..ac1239a548 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -8,6 +8,11 @@ #include "nvim/api/private/dispatch.h" #include "nvim/bufhl_defs.h" +#if defined(__NetBSD__) +# undef uint64_t +# define uint64_t uint64_t +#endif + #define MAP_DECLS(T, U) \ KHASH_DECLARE(T##_##U##_map, T, U) \ \ diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 008bce6df6..a52ab9f5d3 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -704,12 +704,14 @@ bool utf_composinglike(const char_u *p1, const char_u *p2) return arabic_combine(utf_ptr2char(p1), c2); } -/* - * Convert a UTF-8 byte string to a wide character. Also get up to MAX_MCO - * composing characters. - * - * @param [out] pcc: composing chars, last one is 0 - */ +/// Convert a UTF-8 string to a wide character +/// +/// Also gets up to #MAX_MCO composing characters. +/// +/// @param[out] pcc Location where to store composing characters. Must have +/// space at least for #MAX_MCO + 1 elements. +/// +/// @return leading character. int utfc_ptr2char(const char_u *p, int *pcc) { int len; @@ -1977,37 +1979,39 @@ char_u * enc_locale(void) return NULL; } - /* The most generic locale format is: - * language[_territory][.codeset][@modifier][+special][,[sponsor][_revision]] - * If there is a '.' remove the part before it. - * if there is something after the codeset, remove it. - * Make the name lowercase and replace '_' with '-'. - * Exception: "ja_JP.EUC" == "euc-jp", "zh_CN.EUC" = "euc-cn", - * "ko_KR.EUC" == "euc-kr" - */ + // The most generic locale format is: + // language[_territory][.codeset][@modifier][+special][,[sponsor][_revision]] + // If there is a '.' remove the part before it. + // if there is something after the codeset, remove it. + // Make the name lowercase and replace '_' with '-'. + // Exception: "ja_JP.EUC" == "euc-jp", "zh_CN.EUC" = "euc-cn", + // "ko_KR.EUC" == "euc-kr" const char *p = (char *)vim_strchr((char_u *)s, '.'); if (p != NULL) { if (p > s + 2 && !STRNICMP(p + 1, "EUC", 3) && !isalnum((int)p[4]) && p[4] != '-' && p[-3] == '_') { - /* copy "XY.EUC" to "euc-XY" to buf[10] */ - strcpy(buf + 10, "euc-"); - buf[14] = p[-2]; - buf[15] = p[-1]; - buf[16] = 0; - s = buf + 10; - } else - s = p + 1; - } - for (i = 0; i < (int)sizeof(buf) - 1 && s[i] != NUL; i++) { - if (s[i] == '_' || s[i] == '-') { - buf[i] = '-'; - } else if (isalnum((int)s[i])) { - buf[i] = TOLOWER_ASC(s[i]); + // Copy "XY.EUC" to "euc-XY" to buf[10]. + memmove(buf, "euc-", 4); + buf[4] = (ASCII_ISALNUM(p[-2]) ? TOLOWER_ASC(p[-2]) : 0); + buf[5] = (ASCII_ISALNUM(p[-1]) ? TOLOWER_ASC(p[-1]) : 0); + buf[6] = NUL; } else { - break; + s = p + 1; + goto enc_locale_copy_enc; + } + } else { +enc_locale_copy_enc: + for (i = 0; i < (int)sizeof(buf) - 1 && s[i] != NUL; i++) { + if (s[i] == '_' || s[i] == '-') { + buf[i] = '-'; + } else if (ASCII_ISALNUM((uint8_t)s[i])) { + buf[i] = TOLOWER_ASC(s[i]); + } else { + break; + } } + buf[i] = NUL; } - buf[i] = NUL; return enc_canonize((char_u *)buf); } diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h index a5ce1b0a15..dd8e44b3f9 100644 --- a/src/nvim/mbyte.h +++ b/src/nvim/mbyte.h @@ -19,6 +19,13 @@ #define MB_BYTE2LEN(b) utf8len_tab[b] #define MB_BYTE2LEN_CHECK(b) (((b) < 0 || (b) > 255) ? 1 : utf8len_tab[b]) +/// Maximum value for 'maxcombine' +/// +/// At most that number of composing characters may be attached to the leading +/// character by various `utfc_*` functions. Note that some functions do not +/// have this limit. +enum { MAX_MCO = 6 }; + // max length of an unicode char #define MB_MAXCHAR 6 diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c index 4eeba12b87..f6e03e2532 100644 --- a/src/nvim/memfile.c +++ b/src/nvim/memfile.c @@ -61,8 +61,6 @@ #define MEMFILE_PAGE_SIZE 4096 /// default page size -static size_t total_mem_used = 0; /// total memory used for memfiles - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "memfile.c.generated.h" #endif @@ -99,7 +97,6 @@ memfile_T *mf_open(char_u *fname, int flags) mfp->mf_used_first = NULL; // used list is empty mfp->mf_used_last = NULL; mfp->mf_dirty = false; - mfp->mf_used_count = 0; mf_hash_init(&mfp->mf_hash); mf_hash_init(&mfp->mf_trans); mfp->mf_page_size = MEMFILE_PAGE_SIZE; @@ -136,25 +133,6 @@ memfile_T *mf_open(char_u *fname, int flags) mfp->mf_neg_count = 0; mfp->mf_infile_count = mfp->mf_blocknr_max; - // Compute maximum number of pages ('maxmem' is in Kbytes): - // 'mammem' * 1Kbyte / page-size-in-bytes. - // Avoid overflow by first reducing page size as much as possible. - { - int shift = 10; - unsigned page_size = mfp->mf_page_size; - - while (shift > 0 && (page_size & 1) == 0) { - page_size /= 2; - --shift; - } - - assert(p_mm <= LONG_MAX >> shift); // check we don't overflow - assert((uintmax_t)(p_mm << shift) <= UINT_MAX); // check we can cast safely - mfp->mf_used_count_max = (unsigned)(p_mm << shift) / page_size; - if (mfp->mf_used_count_max < 10) - mfp->mf_used_count_max = 10; - } - return mfp; } @@ -198,7 +176,6 @@ void mf_close(memfile_T *mfp, bool del_file) // free entries in used list for (bhdr_T *hp = mfp->mf_used_first, *nextp; hp != NULL; hp = nextp) { - total_mem_used -= hp->bh_page_count * mfp->mf_page_size; nextp = hp->bh_next; mf_free_bhdr(hp); } @@ -223,12 +200,9 @@ void mf_close_file(buf_T *buf, bool getlines) if (getlines) { // get all blocks in memory by accessing all lines (clumsy!) - mf_dont_release = true; - for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; ++lnum) { + for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { (void)ml_get_buf(buf, lnum, false); } - mf_dont_release = false; - // TODO(elmart): should check if all blocks are really in core } if (close(mfp->mf_fd) < 0) { // close the file @@ -246,13 +220,6 @@ void mf_close_file(buf_T *buf, bool getlines) /// and the size it indicates differs from what was guessed. void mf_new_page_size(memfile_T *mfp, unsigned new_size) { - // Correct the memory used for block 0 to the new size, because it will be - // freed with that size later on. - if (new_size >= mfp->mf_page_size) { - total_mem_used += new_size - mfp->mf_page_size; - } else { - total_mem_used -= mfp->mf_page_size - new_size; - } mfp->mf_page_size = new_size; } @@ -262,10 +229,7 @@ void mf_new_page_size(memfile_T *mfp, unsigned new_size) /// @param page_count Desired number of pages. bhdr_T *mf_new(memfile_T *mfp, bool negative, unsigned page_count) { - // If we reached the maximum size for the used memory blocks, release one. - // If a bhdr_T is returned, use it and adjust the page_count if necessary. - // If no bhdr_T is returned, a new one will be created. - bhdr_T *hp = mf_release(mfp, page_count); // the block to be returned + bhdr_T *hp = NULL; // Decide on the number to use: // If there is a free block, use its number. @@ -273,34 +237,22 @@ bhdr_T *mf_new(memfile_T *mfp, bool negative, unsigned page_count) // a positive number. bhdr_T *freep = mfp->mf_free_first; // first free block if (!negative && freep != NULL && freep->bh_page_count >= page_count) { - // If the block in the free list has more pages, take only the number - // of pages needed and allocate a new bhdr_T with data. - // - // If the number of pages matches and mf_release() did not return a - // bhdr_T, use the bhdr_T from the free list and allocate the data. - // - // If the number of pages matches and mf_release() returned a bhdr_T, - // just use the number and free the bhdr_T from the free list if (freep->bh_page_count > page_count) { - if (hp == NULL) { - hp = mf_alloc_bhdr(mfp, page_count); - } + // If the block in the free list has more pages, take only the number + // of pages needed and allocate a new bhdr_T with data. + hp = mf_alloc_bhdr(mfp, page_count); hp->bh_bnum = freep->bh_bnum; freep->bh_bnum += page_count; freep->bh_page_count -= page_count; - } else if (hp == NULL) { // need to allocate memory for this block + } else { // need to allocate memory for this block + // If the number of pages matches use the bhdr_T from the free list and + // allocate the data. void *p = xmalloc(mfp->mf_page_size * page_count); hp = mf_rem_free(mfp); hp->bh_data = p; - } else { // use the number, remove entry from free list - freep = mf_rem_free(mfp); - hp->bh_bnum = freep->bh_bnum; - xfree(freep); } } else { // get a new number - if (hp == NULL) { - hp = mf_alloc_bhdr(mfp, page_count); - } + hp = mf_alloc_bhdr(mfp, page_count); if (negative) { hp->bh_bnum = mfp->mf_blocknr_min--; mfp->mf_neg_count++; @@ -341,13 +293,7 @@ bhdr_T *mf_get(memfile_T *mfp, blocknr_T nr, unsigned page_count) // could check here if the block is in the free list - // Check if we need to flush an existing block. - // If so, use that block. - // If not, allocate a new block. - hp = mf_release(mfp, page_count); - if (hp == NULL) { - hp = mf_alloc_bhdr(mfp, page_count); - } + hp = mf_alloc_bhdr(mfp, page_count); hp->bh_bnum = nr; hp->bh_flags = 0; @@ -514,8 +460,6 @@ static void mf_ins_used(memfile_T *mfp, bhdr_T *hp) } else { hp->bh_next->bh_prev = hp; } - mfp->mf_used_count += hp->bh_page_count; - total_mem_used += hp->bh_page_count * mfp->mf_page_size; } /// Remove block from memfile's used list. @@ -530,82 +474,6 @@ static void mf_rem_used(memfile_T *mfp, bhdr_T *hp) mfp->mf_used_first = hp->bh_next; else hp->bh_prev->bh_next = hp->bh_next; - - mfp->mf_used_count -= hp->bh_page_count; - total_mem_used -= hp->bh_page_count * mfp->mf_page_size; -} - -/// Try to release the least recently used block from the used list if the -/// number of used memory blocks gets too big. -/// -/// @return The block header, when release needed and possible. -/// Resulting block header includes memory block, so it can be -/// reused. Page count is checked to be right. -/// NULL, when release not needed, or not possible. -/// Not needed when number of blocks less than allowed maximum and -/// total memory used below 'maxmemtot'. -/// Not possible when: -/// - Called while closing file. -/// - Tried to create swap file but couldn't. -/// - All blocks are locked. -/// - Unlocked dirty block found, but flush failed. -static bhdr_T *mf_release(memfile_T *mfp, unsigned page_count) -{ - // don't release while in mf_close_file() - if (mf_dont_release) - return NULL; - - /// Need to release a block if the number of blocks for this memfile is - /// higher than the maximum one or total memory used is over 'maxmemtot'. - bool need_release = (mfp->mf_used_count >= mfp->mf_used_count_max - || (total_mem_used >> 10) >= (size_t)p_mmt); - - /// Try to create swap file if the amount of memory used is getting too high. - if (mfp->mf_fd < 0 && need_release && p_uc) { - // find for which buffer this memfile is - buf_T *buf = NULL; - FOR_ALL_BUFFERS(bp) { - if (bp->b_ml.ml_mfp == mfp) { - buf = bp; - break; - } - } - if (buf != NULL && buf->b_may_swap) { - ml_open_file(buf); - } - } - - /// Don't release a block if: - /// there is no file for this memfile - /// or - /// the number of blocks for this memfile is lower than the maximum - /// and - /// total memory used is not up to 'maxmemtot' - if (mfp->mf_fd < 0 || !need_release) - return NULL; - - bhdr_T *hp; - for (hp = mfp->mf_used_last; hp != NULL; hp = hp->bh_prev) - if (!(hp->bh_flags & BH_LOCKED)) - break; - if (hp == NULL) // not a single one that can be released - return NULL; - - // If the block is dirty, write it. - // If the write fails we don't free it. - if ((hp->bh_flags & BH_DIRTY) && mf_write(mfp, hp) == FAIL) - return NULL; - - mf_rem_used(mfp, hp); - mf_rem_hash(mfp, hp); - - /// Make sure page_count of bh_data is right. - if (hp->bh_page_count != page_count) { - xfree(hp->bh_data); - hp->bh_data = xmalloc(mfp->mf_page_size * page_count); - hp->bh_page_count = page_count; - } - return hp; } /// Release as many blocks as possible. diff --git a/src/nvim/memfile_defs.h b/src/nvim/memfile_defs.h index b3c2f3564c..2402d2147d 100644 --- a/src/nvim/memfile_defs.h +++ b/src/nvim/memfile_defs.h @@ -56,7 +56,6 @@ typedef struct mf_hashtab { /// /// The used list is a doubly linked list, most recently used block first. /// The blocks in the used list have a block of memory allocated. -/// mf_used_count is the number of pages in the used list. /// The hash lists are used to quickly find a block in the used list. /// The free list is a single linked list, not sorted. /// The blocks in the free list have no block of memory allocated and @@ -95,8 +94,6 @@ typedef struct memfile { bhdr_T *mf_free_first; /// first block header in free list bhdr_T *mf_used_first; /// mru block header in used list bhdr_T *mf_used_last; /// lru block header in used list - unsigned mf_used_count; /// number of pages in used list - unsigned mf_used_count_max; /// maximum number of pages in memory mf_hashtab_T mf_hash; /// hash lists mf_hashtab_T mf_trans; /// trans lists blocknr_T mf_blocknr_max; /// highest positive block number + 1 diff --git a/src/nvim/memline.c b/src/nvim/memline.c index c594782a1c..06de9fda67 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -843,8 +843,7 @@ void ml_recover(void) mfp = mf_open(fname_used, O_RDONLY); fname_used = p; if (mfp == NULL || mfp->mf_fd < 0) { - if (fname_used != NULL) - EMSG2(_("E306: Cannot open %s"), fname_used); + EMSG2(_("E306: Cannot open %s"), fname_used); goto theend; } buf->b_ml.ml_mfp = mfp; @@ -1297,18 +1296,14 @@ recover_names ( msg_putchar('\n'); } - /* - * Do the loop for every directory in 'directory'. - * First allocate some memory to put the directory name in. - */ + // Do the loop for every directory in 'directory'. + // First allocate some memory to put the directory name in. dir_name = xmalloc(STRLEN(p_dir) + 1); dirp = p_dir; - while (dir_name != NULL && *dirp) { - /* - * Isolate a directory name from *dirp and put it in dir_name (we know - * it is large enough, so use 31000 for length). - * Advance dirp to next directory name. - */ + while (*dirp) { + // Isolate a directory name from *dirp and put it in dir_name (we know + // it is large enough, so use 31000 for length). + // Advance dirp to next directory name. (void)copy_option_part(&dirp, dir_name, 31000, ","); if (dir_name[0] == '.' && dir_name[1] == NUL) { /* check current dir */ @@ -1330,7 +1325,7 @@ recover_names ( names[2] = (char_u *)concat_fnames((char *)dir_name, ".sw?", TRUE); num_names = 3; } else { - int len = STRLEN(dir_name); + int len = (int)STRLEN(dir_name); p = dir_name + len; if (after_pathsep((char *)dir_name, (char *)p) && len > 1 @@ -1593,7 +1588,7 @@ static int recov_file_names(char_u **names, char_u *path, int prepend_dot) * If 'check_char' is TRUE, stop syncing when character becomes available, but * always sync at least one block. */ -void ml_sync_all(int check_file, int check_char) +void ml_sync_all(int check_file, int check_char, bool do_fsync) { FOR_ALL_BUFFERS(buf) { if (buf->b_ml.ml_mfp == NULL || buf->b_ml.ml_mfp->mf_fname == NULL) @@ -1612,16 +1607,17 @@ void ml_sync_all(int check_file, int check_char) if (!os_fileinfo((char *)buf->b_ffname, &file_info) || file_info.stat.st_mtim.tv_sec != buf->b_mtime_read || os_fileinfo_size(&file_info) != buf->b_orig_size) { - ml_preserve(buf, FALSE); - did_check_timestamps = FALSE; - need_check_timestamps = TRUE; /* give message later */ + ml_preserve(buf, false, do_fsync); + did_check_timestamps = false; + need_check_timestamps = true; // give message later } } if (buf->b_ml.ml_mfp->mf_dirty) { (void)mf_sync(buf->b_ml.ml_mfp, (check_char ? MFS_STOP : 0) - | (bufIsChanged(buf) ? MFS_FLUSH : 0)); - if (check_char && os_char_avail()) /* character available now */ + | (do_fsync && bufIsChanged(buf) ? MFS_FLUSH : 0)); + if (check_char && os_char_avail()) { // character available now break; + } } } } @@ -1636,7 +1632,7 @@ void ml_sync_all(int check_file, int check_char) * * when message is TRUE the success of preserving is reported */ -void ml_preserve(buf_T *buf, int message) +void ml_preserve(buf_T *buf, int message, bool do_fsync) { bhdr_T *hp; linenr_T lnum; @@ -1654,9 +1650,9 @@ void ml_preserve(buf_T *buf, int message) * before. */ got_int = FALSE; - ml_flush_line(buf); /* flush buffered line */ - (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); /* flush locked block */ - status = mf_sync(mfp, MFS_ALL | MFS_FLUSH); + ml_flush_line(buf); // flush buffered line + (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); // flush locked block + status = mf_sync(mfp, MFS_ALL | (do_fsync ? MFS_FLUSH : 0)); /* stack is invalid after mf_sync(.., MFS_ALL) */ buf->b_ml.ml_stack_top = 0; @@ -1684,11 +1680,12 @@ void ml_preserve(buf_T *buf, int message) CHECK(buf->b_ml.ml_locked_low != lnum, "low != lnum"); lnum = buf->b_ml.ml_locked_high + 1; } - (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); /* flush locked block */ - /* sync the updated pointer blocks */ - if (mf_sync(mfp, MFS_ALL | MFS_FLUSH) == FAIL) + (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); // flush locked block + // sync the updated pointer blocks + if (mf_sync(mfp, MFS_ALL | (do_fsync ? MFS_FLUSH : 0)) == FAIL) { status = FAIL; - buf->b_ml.ml_stack_top = 0; /* stack is invalid now */ + } + buf->b_ml.ml_stack_top = 0; // stack is invalid now } theend: got_int |= got_int_save; @@ -1769,7 +1766,7 @@ errorret: * Don't use the last used line when 'swapfile' is reset, need to load all * blocks. */ - if (buf->b_ml.ml_line_lnum != lnum || mf_dont_release) { + if (buf->b_ml.ml_line_lnum != lnum) { ml_flush_line(buf); /* @@ -2770,9 +2767,8 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) if (buf->b_ml.ml_locked) { if (ML_SIMPLE(action) && buf->b_ml.ml_locked_low <= lnum - && buf->b_ml.ml_locked_high >= lnum - && !mf_dont_release) { - /* remember to update pointer blocks and stack later */ + && buf->b_ml.ml_locked_high >= lnum) { + // remember to update pointer blocks and stack later if (action == ML_INSERT) { ++(buf->b_ml.ml_locked_lineadd); ++(buf->b_ml.ml_locked_high); @@ -3020,20 +3016,17 @@ int resolve_symlink(const char_u *fname, char_u *buf) } buf[ret] = NUL; - /* - * Check whether the symlink is relative or absolute. - * If it's relative, build a new path based on the directory - * portion of the filename (if any) and the path the symlink - * points to. - */ - if (path_is_absolute_path(buf)) + // Check whether the symlink is relative or absolute. + // If it's relative, build a new path based on the directory + // portion of the filename (if any) and the path the symlink + // points to. + if (path_is_absolute(buf)) { STRCPY(tmp, buf); - else { - char_u *tail; - - tail = path_tail(tmp); - if (STRLEN(tail) + STRLEN(buf) >= MAXPATHL) + } else { + char_u *tail = path_tail(tmp); + if (STRLEN(tail) + STRLEN(buf) >= MAXPATHL) { return FAIL; + } STRCPY(tail, buf); } } @@ -3058,7 +3051,7 @@ char_u *makeswapname(char_u *fname, char_u *ffname, buf_T *buf, char_u *dir_name #ifdef HAVE_READLINK char_u fname_buf[MAXPATHL]; #endif - int len = STRLEN(dir_name); + int len = (int)STRLEN(dir_name); s = dir_name + len; if (after_pathsep((char *)dir_name, (char *)s) diff --git a/src/nvim/memory.c b/src/nvim/memory.c index a66ab6a3cc..bfc2f208dd 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -369,10 +369,12 @@ char *xstpncpy(char *restrict dst, const char *restrict src, size_t maxlen) /// string that fits in the buffer (unless, of course, the buffer size is /// zero). It does not pad out the result like strncpy() does. /// -/// @param dst Buffer to store the result -/// @param src String to be copied -/// @param dsize Size of `dst` -/// @return strlen(src). If retval >= dstsize, truncation occurs. +/// @param[out] dst Buffer to store the result. +/// @param[in] src String to be copied. +/// @param[in] dsize Size of `dst`. +/// +/// @return Length of `src`. May be greater than `dsize - 1`, which would mean +/// that string was truncated. size_t xstrlcpy(char *restrict dst, const char *restrict src, size_t dsize) FUNC_ATTR_NONNULL_ALL { @@ -394,11 +396,13 @@ size_t xstrlcpy(char *restrict dst, const char *restrict src, size_t dsize) /// @see vim_strcat from Vim. /// @see strlcat from OpenBSD. /// -/// @param dst Buffer to be appended-to. Must have a NUL byte. -/// @param src String to put at the end of `dst` -/// @param dsize Size of `dst` including NUL byte. Must be greater than 0. -/// @return strlen(src) + strlen(initial dst) -/// If retval >= dsize, truncation occurs. +/// @param[in,out] dst Buffer to be appended-to. Must have a NUL byte. +/// @param[in] src String to put at the end of `dst`. +/// @param[in] dsize Size of `dst` including NUL byte. Must be greater than 0. +/// +/// @return Length of the resulting string as if destination size was #SIZE_MAX. +/// May be greater than `dsize - 1`, which would mean that string was +/// truncated. size_t xstrlcat(char *const dst, const char *const src, const size_t dsize) FUNC_ATTR_NONNULL_ALL { diff --git a/src/nvim/message.c b/src/nvim/message.c index e522670a65..7ca82c2878 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -487,9 +487,6 @@ int emsg(const char_u *s_) } called_emsg = true; - if (emsg_silent == 0) { - ex_exitval = 1; - } // If "emsg_severe" is TRUE: When an error exception is to be thrown, // prefer this message over previous messages for the same command. @@ -540,6 +537,8 @@ int emsg(const char_u *s_) return true; } + ex_exitval = 1; + // Reset msg_silent, an error causes messages to be switched back on. msg_silent = 0; cmd_silent = FALSE; @@ -1383,9 +1382,6 @@ const char *str2special(const char **const sp, const bool replace_spaces, if (c == K_SPECIAL && str[1] != NUL && str[2] != NUL) { c = TO_SPECIAL((uint8_t)str[1], (uint8_t)str[2]); str += 2; - if (c == KS_ZERO) { // display <Nul> as ^@ or <Nul> - c = NUL; - } } if (IS_SPECIAL(c) || modifiers) { // Special key. special = true; @@ -1416,7 +1412,7 @@ const char *str2special(const char **const sp, const bool replace_spaces, || (replace_lt && c == '<')) { return (const char *)get_special_key_name(c, modifiers); } - buf[0] = c; + buf[0] = (char)c; buf[1] = NUL; return buf; } @@ -1876,13 +1872,29 @@ bool message_filtered(char_u *msg) return cmdmod.filter_force ? match : !match; } +/// including horizontal separator +int msg_scrollsize(void) +{ + return msg_scrolled + p_ch + 1; +} + /* * Scroll the screen up one line for displaying the next message line. */ static void msg_scroll_up(void) { - /* scrolling up always works */ - screen_del_lines(0, 0, 1, (int)Rows, NULL); + if (dy_flags & DY_MSGSEP) { + if (msg_scrolled == 0) { + screen_fill(Rows-p_ch-1, Rows-p_ch, 0, (int)Columns, + fill_msgsep, fill_msgsep, hl_attr(HLF_MSGSEP)); + } + int nscroll = MIN(msg_scrollsize()+1, Rows); + ui_call_set_scroll_region(Rows-nscroll, Rows-1, 0, Columns-1); + screen_del_lines(Rows-nscroll, 0, 1, nscroll, NULL); + ui_reset_scroll_region(); + } else { + screen_del_lines(0, 0, 1, (int)Rows, NULL); + } } /* @@ -2336,10 +2348,9 @@ static int do_more_prompt(int typed_char) * yet. When stderr can't be used, collect error messages until the GUI has * started and they can be displayed in a message box. */ -void mch_errmsg(char *str) +void mch_errmsg(const char *const str) + FUNC_ATTR_NONNULL_ALL { - int len; - #ifdef UNIX /* On Unix use stderr if it's a tty. * When not going to start the GUI also use stderr. @@ -2353,14 +2364,13 @@ void mch_errmsg(char *str) /* avoid a delay for a message that isn't there */ emsg_on_display = FALSE; - len = (int)STRLEN(str) + 1; + const size_t len = strlen(str) + 1; if (error_ga.ga_data == NULL) { ga_set_growsize(&error_ga, 80); error_ga.ga_itemsize = 1; } ga_grow(&error_ga, len); - memmove((char_u *)error_ga.ga_data + error_ga.ga_len, - (char_u *)str, len); + memmove(error_ga.ga_data + error_ga.ga_len, str, len); #ifdef UNIX /* remove CR characters, they are displayed */ { diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index b0232b6516..28455f0ba9 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -2643,7 +2643,7 @@ void preserve_exit(void) if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) { mch_errmsg((uint8_t *)"Vim: preserving files...\n"); ui_flush(); - ml_sync_all(false, false); // preserve all swap files + ml_sync_all(false, false, true); // preserve all swap files break; } } diff --git a/src/nvim/move.c b/src/nvim/move.c index 6548d351a6..cb13a9e207 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -672,7 +672,7 @@ int win_col_off(win_T *wp) return ((wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) + 1 : 0) + (cmdwin_type == 0 || wp != curwin ? 0 : 1) + (int)wp->w_p_fdc - + (signcolumn_on(wp) ? 2 : 0); + + (signcolumn_on(wp) ? win_signcol_width(wp) : 0); } int curwin_col_off(void) diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c index 9bf122f4db..e5d80aea1d 100644 --- a/src/nvim/msgpack_rpc/server.c +++ b/src/nvim/msgpack_rpc/server.c @@ -32,26 +32,27 @@ static garray_T watchers = GA_EMPTY_INIT_VALUE; #endif /// Initializes the module -bool server_init(void) +bool server_init(const char *listen_addr) { ga_init(&watchers, sizeof(SocketWatcher *), 1); - bool must_free = false; - const char *listen_address = os_getenv(LISTEN_ADDRESS_ENV_VAR); - if (listen_address == NULL) { - must_free = true; - listen_address = server_address_new(); - } + // $NVIM_LISTEN_ADDRESS + const char *env_addr = os_getenv(LISTEN_ADDRESS_ENV_VAR); + int rv = listen_addr == NULL ? 1 : server_start(listen_addr); - if (!listen_address) { - return false; + if (0 != rv) { + rv = env_addr == NULL ? 1 : server_start(env_addr); + if (0 != rv) { + listen_addr = server_address_new(); + if (listen_addr == NULL) { + return false; + } + rv = server_start(listen_addr); + xfree((char *)listen_addr); + } } - bool ok = (server_start(listen_address) == 0); - if (must_free) { - xfree((char *) listen_address); - } - return ok; + return rv == 0; } /// Teardown a single server @@ -120,8 +121,8 @@ bool server_owns_pipe_address(const char *path) /// @param endpoint Address of the server. Either a 'ip:[port]' string or an /// arbitrary identifier (trimmed to 256 bytes) for the Unix /// socket or named pipe. -/// @returns 0 on success, 1 on a regular error, and negative errno -/// on failure to bind or listen. +/// @returns 0: success, 1: validation error, 2: already listening, +/// -errno: failed to bind or listen. int server_start(const char *endpoint) { if (endpoint == NULL || endpoint[0] == '\0') { @@ -145,7 +146,7 @@ int server_start(const char *endpoint) uv_freeaddrinfo(watcher->uv.tcp.addrinfo); } socket_watcher_close(watcher, free_server); - return 1; + return 2; } } @@ -177,7 +178,7 @@ int server_start(const char *endpoint) /// Stops listening on the address specified by `endpoint`. /// /// @param endpoint Address of the server. -void server_stop(char *endpoint) +bool server_stop(char *endpoint) { SocketWatcher *watcher; bool watcher_found = false; @@ -196,8 +197,8 @@ void server_stop(char *endpoint) } if (!watcher_found) { - ELOG("Not listening on %s", addr); - return; + WLOG("Not listening on %s", addr); + return false; } // Unset $NVIM_LISTEN_ADDRESS if it is the stopped address. @@ -219,6 +220,8 @@ void server_stop(char *endpoint) if (STRCMP(addr, get_vim_var_str(VV_SEND_SERVER)) == 0) { set_vservername(&watchers); } + + return true; } /// Returns an allocated array of server addresses. diff --git a/src/nvim/normal.c b/src/nvim/normal.c index fbbc8248e8..e4310de5d8 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -345,6 +345,7 @@ static const struct nv_cmd { { K_F8, farsi_f8, 0, 0 }, { K_F9, farsi_f9, 0, 0 }, { K_EVENT, nv_event, NV_KEEPREG, 0 }, + { K_COMMAND, nv_colon, 0, 0 }, }; /* Number of commands in nv_cmds[]. */ @@ -1473,13 +1474,13 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) AppendToRedobuffLit(cap->searchbuf, -1); } AppendToRedobuff(NL_STR); - } else if (cap->cmdchar == ':') { - /* do_cmdline() has stored the first typed line in - * "repeat_cmdline". When several lines are typed repeating - * won't be possible. */ - if (repeat_cmdline == NULL) + } else if (cap->cmdchar == ':' || cap->cmdchar == K_COMMAND) { + // do_cmdline() has stored the first typed line in + // "repeat_cmdline". When several lines are typed repeating + // won't be possible. + if (repeat_cmdline == NULL) { ResetRedobuff(); - else { + } else { AppendToRedobuffLit(repeat_cmdline, -1); AppendToRedobuff(NL_STR); xfree(repeat_cmdline); @@ -4524,23 +4525,22 @@ static void nv_exmode(cmdarg_T *cap) } } -/* - * Handle a ":" command. - */ +/// Handle a ":" command and <Cmd>. static void nv_colon(cmdarg_T *cap) { int old_p_im; bool cmd_result; + bool is_cmdkey = cap->cmdchar == K_COMMAND; - if (VIsual_active) + if (VIsual_active && !is_cmdkey) { nv_operator(cap); - else { + } else { if (cap->oap->op_type != OP_NOP) { // Using ":" as a movement is characterwise exclusive. cap->oap->motion_type = kMTCharWise; cap->oap->inclusive = false; - } else if (cap->count0) { - /* translate "count:" into ":.,.+(count - 1)" */ + } else if (cap->count0 && !is_cmdkey) { + // translate "count:" into ":.,.+(count - 1)" stuffcharReadbuff('.'); if (cap->count0 > 1) { stuffReadbuff(",.+"); @@ -4554,9 +4554,9 @@ static void nv_colon(cmdarg_T *cap) old_p_im = p_im; - /* get a command line and execute it */ - cmd_result = do_cmdline(NULL, getexline, NULL, - cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0); + // get a command line and execute it + cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL, + cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0); /* If 'insertmode' changed, enter or exit Insert mode */ if (p_im != old_p_im) { diff --git a/src/nvim/ops.c b/src/nvim/ops.c index b421d81b7e..b39b139f9b 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -3568,7 +3568,7 @@ int do_join(size_t count, int *comments = NULL; int remove_comments = (use_formatoptions == TRUE) && has_format_option(FO_REMOVE_COMS); - bool prev_was_comment; + bool prev_was_comment = false; if (save_undo && u_save(curwin->w_cursor.lnum - 1, curwin->w_cursor.lnum + (linenr_T)count) == FAIL) { @@ -3592,17 +3592,16 @@ int do_join(size_t count, curwin->w_buffer->b_op_start.col = (colnr_T)STRLEN(curr); } if (remove_comments) { - /* We don't want to remove the comment leader if the - * previous line is not a comment. */ + // We don't want to remove the comment leader if the + // previous line is not a comment. if (t > 0 && prev_was_comment) { - - char_u *new_curr = skip_comment(curr, TRUE, insert_space, - &prev_was_comment); + char_u *new_curr = skip_comment(curr, true, insert_space, + &prev_was_comment); comments[t] = (int)(new_curr - curr); curr = new_curr; - } else - curr = skip_comment(curr, FALSE, insert_space, - &prev_was_comment); + } else { + curr = skip_comment(curr, false, insert_space, &prev_was_comment); + } } if (insert_space && t > 0) { @@ -3891,9 +3890,6 @@ fex_format ( // Make a copy, the option could be changed while calling it. fex = vim_strsave(curbuf->b_p_fex); - if (fex == NULL) { - return 0; - } // Evaluate the function. if (use_sandbox) { sandbox++; @@ -4610,9 +4606,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) } } curwin->w_cursor.col = col; - if (!did_change) { - startpos = curwin->w_cursor; - } + startpos = curwin->w_cursor; did_change = true; (void)del_char(false); ins_char(firstdigit); @@ -4687,9 +4681,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) // Delete the old number. curwin->w_cursor.col = col; - if (!did_change) { - startpos = curwin->w_cursor; - } + startpos = curwin->w_cursor; did_change = true; todel = length; c = gchar_cursor(); @@ -4716,9 +4708,6 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) // When there are many leading zeros it could be very long. // Allocate a bit too much. buf1 = xmalloc((size_t)length + NUMBUFLEN); - if (buf1 == NULL) { - goto theend; - } ptr = buf1; if (negative && (!visual || was_positive)) { *ptr++ = '-'; @@ -4754,7 +4743,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) vim_snprintf((char *)buf2, ARRAY_SIZE(buf2), "%" PRIu64, (uint64_t)n); } else if (pre == '0') { vim_snprintf((char *)buf2, ARRAY_SIZE(buf2), "%" PRIo64, (uint64_t)n); - } else if (pre && hexupper) { + } else if (hexupper) { vim_snprintf((char *)buf2, ARRAY_SIZE(buf2), "%" PRIX64, (uint64_t)n); } else { vim_snprintf((char *)buf2, ARRAY_SIZE(buf2), "%" PRIx64, (uint64_t)n); @@ -4775,18 +4764,16 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) ins_str(buf1); // insert the new number xfree(buf1); endpos = curwin->w_cursor; - if (did_change && curwin->w_cursor.col) { + if (curwin->w_cursor.col) { curwin->w_cursor.col--; } } - if (did_change) { - // set the '[ and '] marks - curbuf->b_op_start = startpos; - curbuf->b_op_end = endpos; - if (curbuf->b_op_end.col > 0) { - curbuf->b_op_end.col--; - } + // set the '[ and '] marks + curbuf->b_op_start = startpos; + curbuf->b_op_end = endpos; + if (curbuf->b_op_end.col > 0) { + curbuf->b_op_end.col--; } theend: @@ -5764,7 +5751,7 @@ static void set_clipboard(int name, yankreg_T *reg) list_T *args = tv_list_alloc(3); tv_list_append_list(args, lines); - tv_list_append_string(args, ®type, 1); + tv_list_append_string(args, ®type, 1); // -V614 tv_list_append_string(args, ((char[]) { (char)name }), 1); (void)eval_call_provider("clipboard", "set", args); diff --git a/src/nvim/option.c b/src/nvim/option.c index d6903c8db7..1da259e6b8 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -13,7 +13,8 @@ // add some code to didset_window_options(). // - 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 numeric option, add any necessary bounds checks to +// set_num_option(). // - 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. @@ -186,16 +187,16 @@ static long p_tw_nopaste; static long p_wm_nopaste; typedef struct vimoption { - char *fullname; /* full option name */ - char *shortname; /* permissible abbreviation */ - uint32_t flags; /* see below */ - char_u *var; /* global option: pointer to variable; - * window-local option: VAR_WIN; - * buffer-local option: global value */ - idopt_T indir; /* global option: PV_NONE; - * local option: indirect option index */ - char_u *def_val[2]; /* default values for variable (vi and vim) */ - scid_T scriptID; /* script in which the option was last set */ + char *fullname; // full option name + char *shortname; // permissible abbreviation + uint32_t flags; // see below + char_u *var; // global option: pointer to variable; + // window-local option: VAR_WIN; + // buffer-local option: global value + idopt_T indir; // global option: PV_NONE; + // local option: indirect option index + char_u *def_val[2]; // default values for variable (vi and vim) + LastSet last_set; // script in which the option was last set # define SCRIPTID_INIT , 0 } vimoption_T; @@ -618,27 +619,6 @@ void set_init_1(void) } } - /* - * 'maxmemtot' and 'maxmem' may have to be adjusted for available memory - */ - opt_idx = findoption("maxmemtot"); - if (opt_idx >= 0) { - { - /* Use half of amount of memory available to Vim. */ - /* If too much to fit in uintptr_t, get uintptr_t max */ - uint64_t available_kib = os_get_total_mem_kib(); - uintptr_t n = available_kib / 2 > UINTPTR_MAX - ? UINTPTR_MAX - : (uintptr_t)(available_kib /2); - options[opt_idx].def_val[VI_DEFAULT] = (char_u *)n; - opt_idx = findoption("maxmem"); - if (opt_idx >= 0) { - options[opt_idx].def_val[VI_DEFAULT] = (char_u *)n; - } - } - } - - { char_u *cdpath; char_u *buf; @@ -1365,15 +1345,16 @@ do_set ( if (opt_idx >= 0) { showoneopt(&options[opt_idx], opt_flags); if (p_verbose > 0) { - /* Mention where the option was last set. */ - if (varp == options[opt_idx].var) - last_set_msg(options[opt_idx].scriptID); - else if ((int)options[opt_idx].indir & PV_WIN) - last_set_msg(curwin->w_p_scriptID[ - (int)options[opt_idx].indir & PV_MASK]); - else if ((int)options[opt_idx].indir & PV_BUF) - last_set_msg(curbuf->b_p_scriptID[ - (int)options[opt_idx].indir & PV_MASK]); + // Mention where the option was last set. + if (varp == options[opt_idx].var) { + option_last_set_msg(options[opt_idx].last_set); + } else if ((int)options[opt_idx].indir & PV_WIN) { + option_last_set_msg(curwin->w_p_scriptID[ + (int)options[opt_idx].indir & PV_MASK]); + } else if ((int)options[opt_idx].indir & PV_BUF) { + option_last_set_msg(curbuf->b_p_scriptID[ + (int)options[opt_idx].indir & PV_MASK]); + } } } else { errmsg = (char_u *)N_("E846: Key code not set"); @@ -1467,8 +1448,7 @@ do_set ( goto skip; } } else if (*arg == '-' || ascii_isdigit(*arg)) { - // Allow negative (for 'undolevels'), octal and - // hex numbers. + // Allow negative, octal and hex numbers. vim_str2nr(arg, NULL, &i, STR2NR_ALL, &value, NULL, 0); if (arg[i] != NUL && !ascii_iswhite(arg[i])) { errmsg = e_invarg; @@ -3411,6 +3391,7 @@ static char_u *set_chars_option(char_u **varp) { &fill_vert, "vert" , 9474 }, // │ { &fill_fold, "fold" , 183 }, // · { &fill_diff, "diff" , '-' }, + { &fill_msgsep, "msgsep", ' ' }, }; static struct charstab lcstab[] = { { &lcs_eol, "eol", NUL }, @@ -3662,16 +3643,19 @@ static void set_option_scriptID_idx(int opt_idx, int opt_flags, int id) { int both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0; int indir = (int)options[opt_idx].indir; + const LastSet last_set = { id, current_channel_id }; - /* Remember where the option was set. For local options need to do that - * in the buffer or window structure. */ - if (both || (opt_flags & OPT_GLOBAL) || (indir & (PV_BUF|PV_WIN)) == 0) - options[opt_idx].scriptID = id; + // Remember where the option was set. For local options need to do that + // in the buffer or window structure. + if (both || (opt_flags & OPT_GLOBAL) || (indir & (PV_BUF|PV_WIN)) == 0) { + options[opt_idx].last_set = last_set; + } if (both || (opt_flags & OPT_LOCAL)) { - if (indir & PV_BUF) - curbuf->b_p_scriptID[indir & PV_MASK] = id; - else if (indir & PV_WIN) - curwin->w_p_scriptID[indir & PV_MASK] = id; + if (indir & PV_BUF) { + curbuf->b_p_scriptID[indir & PV_MASK] = last_set; + } else if (indir & PV_WIN) { + curwin->w_p_scriptID[indir & PV_MASK] = last_set; + } } } @@ -4089,238 +4073,251 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, return (char *)e_secure; } - *pp = value; - /* Remember where the option was set. */ - set_option_scriptID_idx(opt_idx, opt_flags, current_SID); - - if (curbuf->b_p_sw < 0) { - errmsg = e_positive; - curbuf->b_p_sw = curbuf->b_p_ts; + // Many number options assume their value is in the signed int range. + if (value < INT_MIN || value > INT_MAX) { + return (char *)e_invarg; } - /* - * Number options that need some action when changed - */ - if (pp == &p_wh || pp == &p_hh) { - if (p_wh < 1) { + // Options that need some validation. + if (pp == &p_wh) { + if (value < 1) { errmsg = e_positive; - p_wh = 1; - } - if (p_wmh > p_wh) { + } else if (p_wmh > value) { errmsg = e_winheight; - p_wh = p_wmh; } - if (p_hh < 0) { + } else if (pp == &p_hh) { + if (value < 0) { errmsg = e_positive; - p_hh = 0; - } - - /* Change window height NOW */ - if (!ONE_WINDOW) { - if (pp == &p_wh && curwin->w_height < p_wh) - win_setheight((int)p_wh); - if (pp == &p_hh && curbuf->b_help && curwin->w_height < p_hh) - win_setheight((int)p_hh); } - } - /* 'winminheight' */ - else if (pp == &p_wmh) { - if (p_wmh < 0) { + } else if (pp == &p_wmh) { + if (value < 0) { errmsg = e_positive; - p_wmh = 0; - } - if (p_wmh > p_wh) { + } else if (value > p_wh) { errmsg = e_winheight; - p_wmh = p_wh; } - win_setminheight(); } else if (pp == &p_wiw) { - if (p_wiw < 1) { + if (value < 1) { errmsg = e_positive; - p_wiw = 1; + } else if (p_wmw > value) { + errmsg = e_winwidth; } - if (p_wmw > p_wiw) { + } else if (pp == &p_wmw) { + if (value < 0) { + errmsg = e_positive; + } else if (value > p_wiw) { errmsg = e_winwidth; - p_wiw = p_wmw; } - - /* Change window width NOW */ - if (!ONE_WINDOW && curwin->w_width < p_wiw) - win_setwidth((int)p_wiw); - } - /* 'winminwidth' */ - else if (pp == &p_wmw) { - if (p_wmw < 0) { + } else if (pp == &p_mco) { + if (value > MAX_MCO) { + errmsg = e_invarg; + } else if (value < 0) { errmsg = e_positive; - p_wmw = 0; } - if (p_wmw > p_wiw) { - errmsg = e_winwidth; - p_wmw = p_wiw; + } else if (pp == &p_titlelen) { + if (value < 0) { + errmsg = e_positive; } - win_setminheight(); - } else if (pp == &p_ls) { - /* (re)set last window status line */ - last_status(false); - } - /* (re)set tab page line */ - else if (pp == &p_stal) { - shell_new_rows(); /* recompute window positions and heights */ - } - /* 'foldlevel' */ - else if (pp == &curwin->w_p_fdl) { - if (curwin->w_p_fdl < 0) - curwin->w_p_fdl = 0; - newFoldLevel(); - } - /* 'foldminlines' */ - else if (pp == &curwin->w_p_fml) { - foldUpdateAll(curwin); - } - /* 'foldnestmax' */ - else if (pp == &curwin->w_p_fdn) { - if (foldmethodIsSyntax(curwin) || foldmethodIsIndent(curwin)) - foldUpdateAll(curwin); - } - /* 'foldcolumn' */ - else if (pp == &curwin->w_p_fdc) { - if (curwin->w_p_fdc < 0) { + } else if (pp == &p_uc) { + if (value < 0) { errmsg = e_positive; - curwin->w_p_fdc = 0; - } else if (curwin->w_p_fdc > 12) { - errmsg = e_invarg; - curwin->w_p_fdc = 12; } - // 'shiftwidth' or 'tabstop' - } else if (pp == &curbuf->b_p_sw || pp == (long *)&curbuf->b_p_ts) { - if (foldmethodIsIndent(curwin)) { - foldUpdateAll(curwin); + } else if (pp == &p_ch) { + if (value < 1) { + errmsg = e_positive; } - // When 'shiftwidth' changes, or it's zero and 'tabstop' changes: - // parse 'cinoptions'. - if (pp == &curbuf->b_p_sw || curbuf->b_p_sw == 0) { - parse_cino(curbuf); + } else if (pp == &p_tm) { + if (value < 0) { + errmsg = e_positive; } - } - /* 'maxcombine' */ - else if (pp == &p_mco) { - if (p_mco > MAX_MCO) - p_mco = MAX_MCO; - else if (p_mco < 0) - p_mco = 0; - screenclear(); /* will re-allocate the screen */ - } else if (pp == &curbuf->b_p_iminsert) { - if (curbuf->b_p_iminsert < 0 || curbuf->b_p_iminsert > B_IMODE_LAST) { + } else if (pp == &p_hi) { + if (value < 0) { + errmsg = e_positive; + } else if (value > 10000) { errmsg = e_invarg; - curbuf->b_p_iminsert = B_IMODE_NONE; } - p_iminsert = curbuf->b_p_iminsert; - showmode(); - /* Show/unshow value of 'keymap' in status lines. */ - status_redraw_curbuf(); - } else if (pp == &p_window) { - if (p_window < 1) - p_window = 1; - else if (p_window >= Rows) - p_window = Rows - 1; - } else if (pp == &curbuf->b_p_imsearch) { - if (curbuf->b_p_imsearch < -1 || curbuf->b_p_imsearch > B_IMODE_LAST) { + } else if (pp == &p_re) { + if (value < 0 || value > 2) { errmsg = e_invarg; - curbuf->b_p_imsearch = B_IMODE_NONE; } - p_imsearch = curbuf->b_p_imsearch; - } else if (pp == &p_channel || pp == &curbuf->b_p_channel) { - errmsg = e_invarg; - *pp = old_value; - } - /* if 'titlelen' has changed, redraw the title */ - else if (pp == &p_titlelen) { - if (p_titlelen < 0) { + } else if (pp == &p_report) { + if (value < 0) { errmsg = e_positive; - p_titlelen = 85; } - if (starting != NO_SCREEN && old_value != p_titlelen) - need_maketitle = TRUE; - } - /* if p_ch changed value, change the command line height */ - else if (pp == &p_ch) { - if (p_ch < 1) { + } else if (pp == &p_so) { + if (value < 0 && full_screen) { + errmsg = e_scroll; + } + } else if (pp == &p_siso) { + if (value < 0 && full_screen) { errmsg = e_positive; - p_ch = 1; } - if (p_ch > Rows - min_rows() + 1) - p_ch = Rows - min_rows() + 1; - - /* Only compute the new window layout when startup has been - * completed. Otherwise the frame sizes may be wrong. */ - if (p_ch != old_value && full_screen - ) - command_height(); - } - /* when 'updatecount' changes from zero to non-zero, open swap files */ - else if (pp == &p_uc) { - if (p_uc < 0) { + } else if (pp == &p_cwh) { + if (value < 1) { errmsg = e_positive; - p_uc = 100; } - if (p_uc && !old_value) - ml_open_files(); - } else if (pp == &curwin->w_p_cole) { - if (curwin->w_p_cole < 0) { + } else if (pp == &p_ut) { + if (value < 0) { + errmsg = e_positive; + } + } else if (pp == &p_ss) { + if (value < 0) { + errmsg = e_positive; + } + } else if (pp == &curwin->w_p_fdl || pp == &curwin->w_allbuf_opt.wo_fdl) { + if (value < 0) { + errmsg = e_positive; + } + } else if (pp == &curwin->w_p_fdc || pp == &curwin->w_allbuf_opt.wo_fdc) { + if (value < 0) { + errmsg = e_positive; + } else if (value > 12) { + errmsg = e_invarg; + } + } else if (pp == &curwin->w_p_cole || pp == &curwin->w_allbuf_opt.wo_cole) { + if (value < 0) { errmsg = e_positive; - curwin->w_p_cole = 0; - } else if (curwin->w_p_cole > 3) { + } else if (value > 3) { errmsg = e_invarg; - curwin->w_p_cole = 3; - } - } - /* sync undo before 'undolevels' changes */ - else if (pp == &p_ul) { - /* use the old value, otherwise u_sync() may not work properly */ - p_ul = old_value; - u_sync(TRUE); - p_ul = value; - } else if (pp == &curbuf->b_p_ul) { - /* use the old value, otherwise u_sync() may not work properly */ - curbuf->b_p_ul = old_value; - u_sync(TRUE); - curbuf->b_p_ul = value; - } - /* 'numberwidth' must be positive */ - else if (pp == &curwin->w_p_nuw) { - if (curwin->w_p_nuw < 1) { + } + } else if (pp == &curwin->w_p_nuw || pp == &curwin->w_allbuf_opt.wo_nuw) { + if (value < 1) { errmsg = e_positive; - curwin->w_p_nuw = 1; + } else if (value > 10) { + errmsg = e_invarg; } - if (curwin->w_p_nuw > 10) { + } else if (pp == &curbuf->b_p_iminsert || pp == &p_iminsert) { + if (value < 0 || value > B_IMODE_LAST) { errmsg = e_invarg; - curwin->w_p_nuw = 10; } - curwin->w_nrwidth_line_count = 0; - } else if (pp == &curbuf->b_p_tw) { - if (curbuf->b_p_tw < 0) { + } else if (pp == &curbuf->b_p_imsearch || pp == &p_imsearch) { + if (value < -1 || value > B_IMODE_LAST) { + errmsg = e_invarg; + } + } else if (pp == &curbuf->b_p_channel || pp == &p_channel) { + errmsg = e_invarg; + } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { + if (value < -1 || value > SB_MAX + || (value != -1 && opt_flags == OPT_LOCAL && !curbuf->terminal)) { + errmsg = e_invarg; + } + } else if (pp == &curbuf->b_p_sw || pp == &p_sw) { + if (value < 0) { + errmsg = e_positive; + } + } else if (pp == &curbuf->b_p_ts || pp == &p_ts) { + if (value < 1) { errmsg = e_positive; - curbuf->b_p_tw = 0; } + } else if (pp == &curbuf->b_p_tw || pp == &p_tw) { + if (value < 0) { + errmsg = e_positive; + } + } + + // Don't change the value and return early if validation failed. + if (errmsg != NULL) { + return (char *)errmsg; + } + + *pp = value; + // Remember where the option was set. + set_option_scriptID_idx(opt_idx, opt_flags, current_SID); + + // For these options we want to fix some invalid values. + if (pp == &p_window) { + if (p_window < 1) { + p_window = Rows - 1; + } else if (p_window >= Rows) { + p_window = Rows - 1; + } + } else if (pp == &p_ch) { + if (p_ch > Rows - min_rows() + 1) { + p_ch = Rows - min_rows() + 1; + } + } + // Number options that need some action when changed + if (pp == &p_wh) { + if (!ONE_WINDOW && curwin->w_height < p_wh) { + win_setheight((int)p_wh); + } + } else if (pp == &p_hh) { + if (!ONE_WINDOW && curbuf->b_help && curwin->w_height < p_hh) { + win_setheight((int)p_hh); + } + } else if (pp == &p_wmh) { + win_setminheight(); + } else if (pp == &p_wiw) { + if (!ONE_WINDOW && curwin->w_width < p_wiw) { + win_setwidth((int)p_wiw); + } + } else if (pp == &p_ls) { + last_status(false); // (re)set last window status line. + } else if (pp == &p_stal) { + // (re)set tab page line + shell_new_rows(); // recompute window positions and heights + } else if (pp == &curwin->w_p_fdl) { + newFoldLevel(); + } else if (pp == &curwin->w_p_fml) { + foldUpdateAll(curwin); + } else if (pp == &curwin->w_p_fdn) { + if (foldmethodIsSyntax(curwin) || foldmethodIsIndent(curwin)) { + foldUpdateAll(curwin); + } + } else if (pp == &curbuf->b_p_sw || pp == &curbuf->b_p_ts) { + // 'shiftwidth' or 'tabstop' + if (foldmethodIsIndent(curwin)) { + foldUpdateAll(curwin); + } + // When 'shiftwidth' changes, or it's zero and 'tabstop' changes: + // parse 'cinoptions'. + if (pp == &curbuf->b_p_sw || curbuf->b_p_sw == 0) { + parse_cino(curbuf); + } + } else if (pp == &p_mco) { + screenclear(); // will re-allocate the screen + } else if (pp == &curbuf->b_p_iminsert) { + showmode(); + // Show/unshow value of 'keymap' in status lines. + status_redraw_curbuf(); + } else if (pp == &p_titlelen) { + // if 'titlelen' has changed, redraw the title + if (starting != NO_SCREEN && old_value != p_titlelen) { + need_maketitle = true; + } + } else if (pp == &p_ch) { + // if p_ch changed value, change the command line height + // Only compute the new window layout when startup has been + // completed. Otherwise the frame sizes may be wrong. + if (p_ch != old_value && full_screen) { + command_height(); + } + } else if (pp == &p_uc) { + // when 'updatecount' changes from zero to non-zero, open swap files + if (p_uc && !old_value) { + ml_open_files(); + } + } else if (pp == &p_ul || pp == &curbuf->b_p_ul) { + // sync undo before 'undolevels' changes + // use the old value, otherwise u_sync() may not work properly + *pp = old_value; + u_sync(true); + *pp = value; + } else if (pp == &curbuf->b_p_tw) { FOR_ALL_TAB_WINDOWS(tp, wp) { check_colorcolumn(wp); } } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { - // 'scrollback' - if (*pp < -1 || *pp > SB_MAX - || (*pp != -1 && opt_flags == OPT_LOCAL && !curbuf->terminal)) { - errmsg = e_invarg; - *pp = old_value; - } else if (curbuf->terminal) { + if (curbuf->terminal) { // Force the scrollback to take effect. terminal_resize(curbuf->terminal, UINT16_MAX, UINT16_MAX); } + } else if (pp == &curwin->w_p_nuw) { + curwin->w_nrwidth_line_count = 0; } - /* - * Check the bounds for numeric options here - */ + + // Check the (new) bounds for Rows and Columns here. if (Rows < min_rows() && full_screen) { if (errbuf != NULL) { vim_snprintf((char *)errbuf, errbuflen, @@ -4340,19 +4337,17 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, limit_screen_size(); - /* - * If the screen (shell) height has been changed, assume it is the - * physical screenheight. - */ + // If the screen (shell) height has been changed, assume it is the + // physical screenheight. if (old_Rows != Rows || old_Columns != Columns) { - /* Changing the screen size is not allowed while updating the screen. */ + // Changing the screen size is not allowed while updating the screen. if (updating_screen) { *pp = old_value; } else if (full_screen) { screen_resize((int)Columns, (int)Rows); } else { - /* Postpone the resizing; check the size and cmdline position for - * messages. */ + // Postpone the resizing; check the size and cmdline position for + // messages. check_shellsize(); if (cmdline_row > Rows - p_ch && Rows > p_ch) { assert(p_ch >= 0 && Rows - p_ch <= INT_MAX); @@ -4364,14 +4359,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } } - if (curbuf->b_p_ts <= 0) { - errmsg = e_positive; - curbuf->b_p_ts = 8; - } - if (p_tm < 0) { - errmsg = e_positive; - p_tm = 0; - } if ((curwin->w_p_scr <= 0 || (curwin->w_p_scr > curwin->w_height && curwin->w_height > 0)) @@ -4388,21 +4375,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, else /* curwin->w_p_scr > curwin->w_height */ curwin->w_p_scr = curwin->w_height; } - if (p_hi < 0) { - errmsg = e_positive; - p_hi = 0; - } else if (p_hi > 10000) { - errmsg = e_invarg; - p_hi = 10000; - } - if (p_re < 0 || p_re > 2) { - errmsg = e_invarg; - p_re = 0; - } - if (p_report < 0) { - errmsg = e_positive; - p_report = 1; - } if ((p_sj < -100 || p_sj >= Rows) && full_screen) { if (Rows != old_Rows) /* Rows changed, just adjust p_sj */ p_sj = Rows / 2; @@ -4411,30 +4383,11 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, p_sj = 1; } } - if (p_so < 0 && full_screen) { - errmsg = e_scroll; - p_so = 0; - } - if (p_siso < 0 && full_screen) { - errmsg = e_positive; - p_siso = 0; - } - if (p_cwh < 1) { - errmsg = e_positive; - p_cwh = 1; - } - if (p_ut < 0) { - errmsg = e_positive; - p_ut = 2000; - } - if (p_ss < 0) { - errmsg = e_positive; - p_ss = 0; - } - /* May set global value for local option. */ - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) + // May set global value for local option. + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = *pp; + } if (pp == &curbuf->b_p_scbk && !curbuf->terminal) { // Normal buffer: reset local 'scrollback' after updating the global value. @@ -5823,25 +5776,28 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_ro = FALSE; /* don't copy readonly */ buf->b_p_fenc = vim_strsave(p_fenc); switch (*p_ffs) { - case 'm': - buf->b_p_ff = vim_strsave((char_u *)FF_MAC); - break; - case 'd': - buf->b_p_ff = vim_strsave((char_u *)FF_DOS); - break; - case 'u': - buf->b_p_ff = vim_strsave((char_u *)FF_UNIX); - break; - default: - buf->b_p_ff = vim_strsave(p_ff); - } - if (buf->b_p_ff != NULL) { - buf->b_start_ffc = *buf->b_p_ff; + case 'm': { + buf->b_p_ff = vim_strsave((char_u *)FF_MAC); + break; + } + case 'd': { + buf->b_p_ff = vim_strsave((char_u *)FF_DOS); + break; + } + case 'u': { + buf->b_p_ff = vim_strsave((char_u *)FF_UNIX); + break; + } + default: { + buf->b_p_ff = vim_strsave(p_ff); + break; + } } buf->b_p_bh = empty_option; buf->b_p_bt = empty_option; - } else - free_buf_options(buf, FALSE); + } else { + free_buf_options(buf, false); + } buf->b_p_ai = p_ai; buf->b_p_ai_nopaste = p_ai_nopaste; @@ -6102,7 +6058,7 @@ set_context_in_set_cmd ( xp->xp_context = EXPAND_UNSUCCESSFUL; return; } - if (xp->xp_context != EXPAND_BOOL_SETTINGS && p[1] == NUL) { + if (p[1] == NUL) { xp->xp_context = EXPAND_OLD_SETTING; if (is_term_option) expand_option_idx = -1; @@ -6246,14 +6202,15 @@ void ExpandOldSetting(int *num_file, char_u ***file) } if (expand_option_idx >= 0) { - /* put string of option value in NameBuff */ + // Put string of option value in NameBuff. option_value2string(&options[expand_option_idx], expand_option_flags); var = NameBuff; - } else if (var == NULL) + } else { var = (char_u *)""; + } - /* A backslash is required before some characters. This is the reverse of - * what happens in do_set(). */ + // A backslash is required before some characters. This is the reverse of + // what happens in do_set(). char_u *buf = vim_strsave_escaped(var, escape_chars); #ifdef BACKSLASH_IN_FILENAME diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index e2e98f251e..f7dfa65053 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -3,6 +3,7 @@ #include "nvim/types.h" #include "nvim/macros.h" // For EXTERN +#include "eval/typval.h" // For scid_T // option_defs.h: definition of global variables for settable options @@ -394,11 +395,13 @@ EXTERN char_u *p_dir; /* 'directory' */ EXTERN char_u *p_dy; /* 'display' */ EXTERN unsigned dy_flags; #ifdef IN_OPTION_C -static char *(p_dy_values[]) = { "lastline", "truncate", "uhex", NULL }; +static char *(p_dy_values[]) = { "lastline", "truncate", "uhex", "msgsep", + NULL }; #endif #define DY_LASTLINE 0x001 #define DY_TRUNCATE 0x002 #define DY_UHEX 0x004 +#define DY_MSGSEP 0x008 EXTERN int p_ed; // 'edcompatible' EXTERN int p_emoji; // 'emoji' EXTERN char_u *p_ead; // 'eadirection' @@ -496,9 +499,7 @@ EXTERN long p_mat; // 'matchtime' EXTERN long p_mco; // 'maxcombine' EXTERN long p_mfd; // 'maxfuncdepth' EXTERN long p_mmd; // 'maxmapdepth' -EXTERN long p_mm; // 'maxmem' EXTERN long p_mmp; // 'maxmempattern' -EXTERN long p_mmt; // 'maxmemtot' EXTERN long p_mis; // 'menuitems' EXTERN char_u *p_msm; // 'mkspellmem' EXTERN long p_mls; // 'modelines' @@ -819,4 +820,10 @@ enum { #define SB_MAX 100000 // Maximum 'scrollback' value. +/// Stores an identifier of a script or channel that last set an option. +typedef struct { + scid_T script_id; /// Script ID or one of SID_* special values. + uint64_t channel_id; /// Only used when script_id is SID_API_CLIENT. +} LastSet; + #endif // NVIM_OPTION_DEFS_H diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 34ff810410..f1f559fff0 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -642,7 +642,7 @@ return { vim=true, redraw={'all_windows'}, varname='p_dy', - defaults={if_true={vi="", vim="lastline"}} + defaults={if_true={vi="", vim="lastline,msgsep"}} }, { full_name='eadirection', abbreviation='ead', @@ -976,7 +976,7 @@ return { secure=true, vi_def=true, varname='p_fs', - defaults={if_true={vi=true}} + defaults={if_true={vi=false}} }, { full_name='gdefault', abbreviation='gd', @@ -1512,13 +1512,6 @@ return { defaults={if_true={vi=1000}} }, { - full_name='maxmem', abbreviation='mm', - type='number', scope={'global'}, - vi_def=true, - varname='p_mm', - defaults={if_true={vi=macros('DFLT_MAXMEM')}} - }, - { full_name='maxmempattern', abbreviation='mmp', type='number', scope={'global'}, vi_def=true, @@ -1526,13 +1519,6 @@ return { defaults={if_true={vi=1000}} }, { - full_name='maxmemtot', abbreviation='mmt', - type='number', scope={'global'}, - vi_def=true, - varname='p_mmt', - defaults={if_true={vi=macros('DFLT_MAXMEMTOT')}} - }, - { full_name='menuitems', abbreviation='mis', type='number', scope={'global'}, vi_def=true, @@ -2048,7 +2034,7 @@ return { varname='p_shcf', defaults={ condition='WIN32', - if_true={vi="/c"}, + if_true={vi="/s /c"}, if_false={vi="-c"} } }, @@ -2104,7 +2090,11 @@ return { secure=true, vi_def=true, varname='p_sxq', - defaults={if_true={vi=""}} + defaults={ + condition='WIN32', + if_true={vi="\""}, + if_false={vi=""}, + } }, { full_name='shellxescape', abbreviation='sxe', diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 999fcd434a..7fb4a93b54 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -14,6 +14,7 @@ #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/path.h" +#include "nvim/macros.h" #include "nvim/strings.h" #include "nvim/eval.h" #include "nvim/ex_getln.h" @@ -176,7 +177,7 @@ void os_get_hostname(char *hostname, size_t size) /// - do os_dirname() to get the real name of that directory. /// This also works with mounts and links. /// Don't do this for Windows, it will change the "current dir" for a drive. -static char_u *homedir = NULL; +static char *homedir = NULL; void init_homedir(void) { @@ -220,7 +221,7 @@ void init_homedir(void) } } #endif - homedir = vim_strsave((char_u *)var); + homedir = xstrdup(var); } } @@ -357,7 +358,7 @@ void expand_env_esc(char_u *restrict srcp, } else if (src[1] == NUL // home directory || vim_ispathsep(src[1]) || vim_strchr((char_u *)" ,\t\n", src[1]) != NULL) { - var = homedir; + var = (char_u *)homedir; tail = src + 1; } else { // user directory #if defined(UNIX) @@ -719,108 +720,123 @@ char *vim_getenv(const char *name) /// Replace home directory by "~" in each space or comma separated file name in /// 'src'. +/// +/// Replace home directory with tilde in each file name +/// /// If anything fails (except when out of space) dst equals src. -/// @param buf When not NULL, check for help files -/// @param src Input file name -/// @param dst Where to put the result -/// @param dstlen Maximum length of the result -/// @param one If true, only replace one file name, including spaces and commas -/// in the file name -void home_replace(const buf_T *const buf, const char_u *src, - char_u *dst, size_t dstlen, bool one) +/// +/// @param[in] buf When not NULL, uses this buffer to check whether it is +/// a help file. If it is then path to file is removed +/// completely, `one` is ignored and assumed to be true. +/// @param[in] src Input file names. Assumed to be a space/comma separated +/// list unless `one` is true. +/// @param[out] dst Where to put the result. +/// @param[in] dstlen Destination length. +/// @param[in] one If true, assumes source is a single file name and not +/// a list of them. +/// +/// @return length of the string put into dst, does not include NUL byte. +size_t home_replace(const buf_T *const buf, const char_u *src, + char_u *const dst, size_t dstlen, const bool one) + FUNC_ATTR_NONNULL_ARG(3) { - size_t dirlen = 0, envlen = 0; - size_t len; + size_t dirlen = 0; + size_t envlen = 0; if (src == NULL) { *dst = NUL; - return; + return 0; } - /* - * If the file is a help file, remove the path completely. - */ if (buf != NULL && buf->b_help) { - xstrlcpy((char *)dst, (char *)path_tail(src), dstlen); - return; + const size_t dlen = xstrlcpy((char *)dst, (char *)path_tail(src), dstlen); + return MIN(dlen, dstlen - 1); } - /* - * We check both the value of the $HOME environment variable and the - * "real" home directory. - */ - if (homedir != NULL) - dirlen = STRLEN(homedir); + // We check both the value of the $HOME environment variable and the + // "real" home directory. + if (homedir != NULL) { + dirlen = strlen(homedir); + } - char_u *homedir_env = (char_u *)os_getenv("HOME"); + const char *const homedir_env = os_getenv("HOME"); + char *homedir_env_mod = (char *)homedir_env; bool must_free = false; - if (homedir_env != NULL && vim_strchr(homedir_env, '~') != NULL) { + if (homedir_env_mod != NULL && strchr(homedir_env_mod, '~') != NULL) { must_free = true; size_t usedlen = 0; - size_t flen = STRLEN(homedir_env); + size_t flen = strlen(homedir_env_mod); char_u *fbuf = NULL; - (void)modify_fname((char_u *)":p", &usedlen, &homedir_env, &fbuf, &flen); - flen = STRLEN(homedir_env); - if (flen > 0 && vim_ispathsep(homedir_env[flen - 1])) - /* Remove the trailing / that is added to a directory. */ - homedir_env[flen - 1] = NUL; + (void)modify_fname((char_u *)":p", &usedlen, (char_u **)&homedir_env_mod, + &fbuf, &flen); + flen = strlen(homedir_env_mod); + assert(homedir_env_mod != homedir_env); + if (vim_ispathsep(homedir_env_mod[flen - 1])) { + // Remove the trailing / that is added to a directory. + homedir_env_mod[flen - 1] = NUL; + } } - if (homedir_env != NULL) - envlen = STRLEN(homedir_env); + if (homedir_env_mod != NULL) { + envlen = strlen(homedir_env_mod); + } - if (!one) + if (!one) { src = skipwhite(src); + } + char *dst_p = (char *)dst; while (*src && dstlen > 0) { - /* - * Here we are at the beginning of a file name. - * First, check to see if the beginning of the file name matches - * $HOME or the "real" home directory. Check that there is a '/' - * after the match (so that if e.g. the file is "/home/pieter/bla", - * and the home directory is "/home/piet", the file does not end up - * as "~er/bla" (which would seem to indicate the file "bla" in user - * er's home directory)). - */ - char_u *p = homedir; - len = dirlen; - for (;; ) { - if ( len - && fnamencmp(src, p, len) == 0 - && (vim_ispathsep(src[len]) - || (!one && (src[len] == ',' || src[len] == ' ')) - || src[len] == NUL)) { + // Here we are at the beginning of a file name. + // First, check to see if the beginning of the file name matches + // $HOME or the "real" home directory. Check that there is a '/' + // after the match (so that if e.g. the file is "/home/pieter/bla", + // and the home directory is "/home/piet", the file does not end up + // as "~er/bla" (which would seem to indicate the file "bla" in user + // er's home directory)). + char *p = homedir; + size_t len = dirlen; + for (;;) { + if (len + && fnamencmp(src, (char_u *)p, len) == 0 + && (vim_ispathsep(src[len]) + || (!one && (src[len] == ',' || src[len] == ' ')) + || src[len] == NUL)) { src += len; - if (--dstlen > 0) - *dst++ = '~'; - - /* - * If it's just the home directory, add "/". - */ - if (!vim_ispathsep(src[0]) && --dstlen > 0) - *dst++ = '/'; + if (--dstlen > 0) { + *dst_p++ = '~'; + } + + // If it's just the home directory, add "/". + if (!vim_ispathsep(src[0]) && --dstlen > 0) { + *dst_p++ = '/'; + } break; } - if (p == homedir_env) + if (p == homedir_env_mod) { break; - p = homedir_env; + } + p = homedir_env_mod; len = envlen; } - /* if (!one) skip to separator: space or comma */ - while (*src && (one || (*src != ',' && *src != ' ')) && --dstlen > 0) - *dst++ = *src++; - /* skip separator */ - while ((*src == ' ' || *src == ',') && --dstlen > 0) - *dst++ = *src++; + // if (!one) skip to separator: space or comma. + while (*src && (one || (*src != ',' && *src != ' ')) && --dstlen > 0) { + *dst_p++ = (char)(*src++); + } + // Skip separator. + while ((*src == ' ' || *src == ',') && --dstlen > 0) { + *dst_p++ = (char)(*src++); + } } - /* if (dstlen == 0) out of space, what to do??? */ + // If (dstlen == 0) out of space, what to do??? - *dst = NUL; + *dst_p = NUL; if (must_free) { - xfree(homedir_env); + xfree(homedir_env_mod); } + return (size_t)(dst_p - (char *)dst); } /// Like home_replace, store the replaced string in allocated memory. @@ -886,7 +902,7 @@ bool os_setenv_append_path(const char *fname) // No prescribed maximum on unix. # define MAX_ENVPATHLEN INT_MAX #endif - if (!path_is_absolute_path((char_u *)fname)) { + if (!path_is_absolute((char_u *)fname)) { internal_error("os_setenv_append_path()"); return false; } diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index d294f9139b..ccf35fd57c 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -4,7 +4,7 @@ /// @file fileio.c /// /// Buffered reading/writing to a file. Unlike fileio.c this is not dealing with -/// Neovim stuctures for buffer, with autocommands, etc: just fopen/fread/fwrite +/// Nvim stuctures for buffer, with autocommands, etc: just fopen/fread/fwrite /// replacement. #include <assert.h> @@ -43,7 +43,7 @@ /// @param[in] mode Permissions for the newly created file (ignored if flags /// does not have kFileCreate\*). /// -/// @return Error code (@see os_strerror()) or 0. +/// @return Error code, or 0 on success. @see os_strerror() int file_open(FileDescriptor *const ret_fp, const char *const fname, const int flags, const int mode) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT @@ -83,7 +83,7 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname, if (fd < 0) { return fd; } - return file_open_fd(ret_fp, fd, (wr == kTrue)); + return file_open_fd(ret_fp, fd, flags); } /// Wrap file descriptor with FileDescriptor structure @@ -94,14 +94,23 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname, /// @param[out] ret_fp Address where information needed for reading from or /// writing to a file is saved /// @param[in] fd File descriptor to wrap. -/// @param[in] wr True if fd is opened for writing only, false if it is read -/// only. +/// @param[in] flags Flags, @see FileOpenFlags. Currently reading from and +/// writing to the file at once is not supported, so either +/// FILE_WRITE_ONLY or FILE_READ_ONLY is required. /// /// @return Error code (@see os_strerror()) or 0. Currently always returns 0. -int file_open_fd(FileDescriptor *const ret_fp, const int fd, const bool wr) +int file_open_fd(FileDescriptor *const ret_fp, const int fd, + const int flags) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - ret_fp->wr = wr; + ret_fp->wr = !!(flags & (kFileCreate + |kFileCreateOnly + |kFileTruncate + |kFileAppend + |kFileWriteOnly)); + ret_fp->non_blocking = !!(flags & kFileNonBlocking); + // Non-blocking writes not supported currently. + assert(!ret_fp->wr || !ret_fp->non_blocking); ret_fp->fd = fd; ret_fp->eof = false; ret_fp->rv = rbuffer_new(kRWBufferSize); @@ -115,8 +124,7 @@ int file_open_fd(FileDescriptor *const ret_fp, const int fd, const bool wr) /// Like file_open(), but allocate and return ret_fp /// -/// @param[out] error Error code, @see os_strerror(). Is set to zero on -/// success. +/// @param[out] error Error code, or 0 on success. @see os_strerror() /// @param[in] fname File name to open. /// @param[in] flags Flags, @see FileOpenFlags. /// @param[in] mode Permissions for the newly created file (ignored if flags @@ -137,18 +145,19 @@ FileDescriptor *file_open_new(int *const error, const char *const fname, /// Like file_open_fd(), but allocate and return ret_fp /// -/// @param[out] error Error code, @see os_strerror(). Is set to zero on -/// success. +/// @param[out] error Error code, or 0 on success. @see os_strerror() /// @param[in] fd File descriptor to wrap. -/// @param[in] wr True if fd is opened for writing only, false if it is read -/// only. +/// @param[in] flags Flags, @see FileOpenFlags. +/// @param[in] mode Permissions for the newly created file (ignored if flags +/// does not have FILE_CREATE\*). /// /// @return [allocated] Opened file or NULL in case of error. -FileDescriptor *file_open_fd_new(int *const error, const int fd, const bool wr) +FileDescriptor *file_open_fd_new(int *const error, const int fd, + const int flags) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT { FileDescriptor *const fp = xmalloc(sizeof(*fp)); - if ((*error = file_open_fd(fp, fd, wr)) != 0) { + if ((*error = file_open_fd(fp, fd, flags)) != 0) { xfree(fp); return NULL; } @@ -246,7 +255,8 @@ static void file_rb_write_full_cb(RBuffer *const rv, FileDescriptor *const fp) return; } const size_t read_bytes = rbuffer_read(rv, writebuf, kRWBufferSize); - const ptrdiff_t wres = os_write(fp->fd, writebuf, read_bytes); + const ptrdiff_t wres = os_write(fp->fd, writebuf, read_bytes, + fp->non_blocking); if (wres != (ptrdiff_t)read_bytes) { if (wres >= 0) { fp->_error = UV_EIO; @@ -272,6 +282,7 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf, char *buf = ret_buf; size_t read_remaining = size; RBuffer *const rv = fp->rv; + bool called_read = false; while (read_remaining) { const size_t rv_size = rbuffer_size(rv); if (rv_size > 0) { @@ -279,7 +290,9 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf, buf += rsize; read_remaining -= rsize; } - if (fp->eof) { + if (fp->eof + // Allow only at most one os_read[v] call. + || (called_read && fp->non_blocking)) { break; } if (read_remaining) { @@ -296,7 +309,7 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf, }; assert(write_count == kRWBufferSize); const ptrdiff_t r_ret = os_readv(fp->fd, &fp->eof, iov, - ARRAY_SIZE(iov)); + ARRAY_SIZE(iov), fp->non_blocking); if (r_ret > 0) { if (r_ret > (ptrdiff_t)read_remaining) { rbuffer_produced(rv, (size_t)(r_ret - (ptrdiff_t)read_remaining)); @@ -312,7 +325,8 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf, if (read_remaining >= kRWBufferSize) { // …otherwise leave RBuffer empty and populate only target buffer, // because filtering information through rbuffer will be more syscalls. - const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof, buf, read_remaining); + const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof, buf, read_remaining, + fp->non_blocking); if (r_ret >= 0) { read_remaining -= (size_t)r_ret; return (ptrdiff_t)(size - read_remaining); @@ -323,7 +337,7 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf, size_t write_count; const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof, rbuffer_write_ptr(rv, &write_count), - kRWBufferSize); + kRWBufferSize, fp->non_blocking); assert(write_count == kRWBufferSize); if (r_ret > 0) { rbuffer_produced(rv, (size_t)r_ret); @@ -332,6 +346,7 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf, } } #endif + called_read = true; } } return (ptrdiff_t)(size - read_remaining); diff --git a/src/nvim/os/fileio.h b/src/nvim/os/fileio.h index 0b55cc695f..7c53cd4f07 100644 --- a/src/nvim/os/fileio.h +++ b/src/nvim/os/fileio.h @@ -14,6 +14,7 @@ typedef struct { RBuffer *rv; ///< Read or write buffer. bool wr; ///< True if file is in write mode. bool eof; ///< True if end of file was encountered. + bool non_blocking; ///< True if EAGAIN should not restart syscalls. } FileDescriptor; /// file_open() flags @@ -32,6 +33,8 @@ typedef enum { ///< kFileCreateOnly. kFileAppend = 64, ///< Append to the file. Implies kFileWriteOnly. Cannot ///< be used with kFileCreateOnly. + kFileNonBlocking = 128, ///< Do not restart read() or write() syscall if + ///< EAGAIN was encountered. } FileOpenFlags; static inline bool file_eof(const FileDescriptor *const fp) diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index c0a97aeb34..5412c5daae 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -222,7 +222,7 @@ int os_exepath(char *buffer, size_t *size) bool os_can_exe(const char_u *name, char_u **abspath, bool use_path) FUNC_ATTR_NONNULL_ARG(1) { - bool no_path = !use_path || path_is_absolute_path(name); + bool no_path = !use_path || path_is_absolute(name); #ifndef WIN32 // If the filename is "qualified" (relative or absolute) do not check $PATH. no_path |= (name[0] == '.' @@ -244,7 +244,7 @@ bool os_can_exe(const char_u *name, char_u **abspath, bool use_path) #endif if (ok) { if (abspath != NULL) { - *abspath = save_absolute_path(name); + *abspath = save_abs_path(name); } return true; } @@ -357,7 +357,7 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath) #endif if (ok) { if (abspath != NULL) { // Caller asked for a copy of the path. - *abspath = save_absolute_path((char_u *)buf); + *abspath = save_abs_path((char_u *)buf); } rv = true; @@ -436,6 +436,29 @@ int os_close(const int fd) return r; } +/// Duplicate file descriptor +/// +/// @param[in] fd File descriptor to duplicate. +/// +/// @return New file descriptor or libuv error code (< 0). +int os_dup(const int fd) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + int ret; +os_dup_dup: + ret = dup(fd); + if (ret < 0) { + const int error = os_translate_sys_error(errno); + errno = 0; + if (error == UV_EINTR) { + goto os_dup_dup; + } else { + return error; + } + } + return ret; +} + /// Read from a file /// /// Handles EINTR and ENOMEM, but not other errors. @@ -445,10 +468,11 @@ int os_close(const int fd) /// to false. Initial value is ignored. /// @param[out] ret_buf Buffer to write to. May be NULL if size is zero. /// @param[in] size Amount of bytes to read. +/// @param[in] non_blocking Do not restart syscall if EAGAIN was encountered. /// /// @return Number of bytes read or libuv error code (< 0). -ptrdiff_t os_read(const int fd, bool *ret_eof, char *const ret_buf, - const size_t size) +ptrdiff_t os_read(const int fd, bool *const ret_eof, char *const ret_buf, + const size_t size, const bool non_blocking) FUNC_ATTR_WARN_UNUSED_RESULT { *ret_eof = false; @@ -468,7 +492,9 @@ ptrdiff_t os_read(const int fd, bool *ret_eof, char *const ret_buf, if (cur_read_bytes < 0) { const int error = os_translate_sys_error(errno); errno = 0; - if (error == UV_EINTR || error == UV_EAGAIN) { + if (non_blocking && error == UV_EAGAIN) { + break; + } else if (error == UV_EINTR || error == UV_EAGAIN) { continue; } else if (error == UV_ENOMEM && !did_try_to_free) { try_to_free_memory(); @@ -498,7 +524,11 @@ ptrdiff_t os_read(const int fd, bool *ret_eof, char *const ret_buf, /// may change, it is incorrect to use data it points to after /// os_readv(). /// @param[in] iov_size Number of buffers in iov. -ptrdiff_t os_readv(int fd, bool *ret_eof, struct iovec *iov, size_t iov_size) +/// @param[in] non_blocking Do not restart syscall if EAGAIN was encountered. +/// +/// @return Number of bytes read or libuv error code (< 0). +ptrdiff_t os_readv(const int fd, bool *const ret_eof, struct iovec *iov, + size_t iov_size, const bool non_blocking) FUNC_ATTR_NONNULL_ALL { *ret_eof = false; @@ -512,7 +542,7 @@ ptrdiff_t os_readv(int fd, bool *ret_eof, struct iovec *iov, size_t iov_size) } while (read_bytes < toread && iov_size && !*ret_eof) { ptrdiff_t cur_read_bytes = readv(fd, iov, (int)iov_size); - if (toread && cur_read_bytes == 0) { + if (cur_read_bytes == 0) { *ret_eof = true; } if (cur_read_bytes > 0) { @@ -531,7 +561,9 @@ ptrdiff_t os_readv(int fd, bool *ret_eof, struct iovec *iov, size_t iov_size) } else if (cur_read_bytes < 0) { const int error = os_translate_sys_error(errno); errno = 0; - if (error == UV_EINTR || error == UV_EAGAIN) { + if (non_blocking && error == UV_EAGAIN) { + break; + } else if (error == UV_EINTR || error == UV_EAGAIN) { continue; } else if (error == UV_ENOMEM && !did_try_to_free) { try_to_free_memory(); @@ -551,9 +583,11 @@ ptrdiff_t os_readv(int fd, bool *ret_eof, struct iovec *iov, size_t iov_size) /// @param[in] fd File descriptor to write to. /// @param[in] buf Data to write. May be NULL if size is zero. /// @param[in] size Amount of bytes to write. +/// @param[in] non_blocking Do not restart syscall if EAGAIN was encountered. /// /// @return Number of bytes written or libuv error code (< 0). -ptrdiff_t os_write(const int fd, const char *const buf, const size_t size) +ptrdiff_t os_write(const int fd, const char *const buf, const size_t size, + const bool non_blocking) FUNC_ATTR_WARN_UNUSED_RESULT { if (buf == NULL) { @@ -571,7 +605,9 @@ ptrdiff_t os_write(const int fd, const char *const buf, const size_t size) if (cur_written_bytes < 0) { const int error = os_translate_sys_error(errno); errno = 0; - if (error == UV_EINTR || error == UV_EAGAIN) { + if (non_blocking && error == UV_EAGAIN) { + break; + } else if (error == UV_EINTR || error == UV_EAGAIN) { continue; } else { return error; @@ -593,6 +629,7 @@ int os_fsync(int fd) { int r; RUN_UV_FS_FUNC(r, uv_fs_fsync, fd, NULL); + g_stats.fsync++; return r; } diff --git a/src/nvim/os/os_defs.h b/src/nvim/os/os_defs.h index f81785675e..c29af5c160 100644 --- a/src/nvim/os/os_defs.h +++ b/src/nvim/os/os_defs.h @@ -25,15 +25,6 @@ // Command-processing buffer. Use large buffers for all platforms. #define CMDBUFFSIZE 1024 -// Use up to 5 Mbyte for a buffer. -#ifndef DFLT_MAXMEM -# define DFLT_MAXMEM (5 * 1024) -#endif -// use up to 10 Mbyte for Vim. -#ifndef DFLT_MAXMEMTOT -# define DFLT_MAXMEMTOT (10 * 1024) -#endif - // Note: Some systems need both string.h and strings.h (Savage). However, // some systems can't handle both, only use string.h in that case. #include <string.h> diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c new file mode 100644 index 0000000000..a67e7487eb --- /dev/null +++ b/src/nvim/os/process.c @@ -0,0 +1,267 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +/// OS process functions +/// +/// psutil is a good reference for cross-platform syscall voodoo: +/// https://github.com/giampaolo/psutil/tree/master/psutil/arch + +#include <uv.h> // for HANDLE (win32) + +#ifdef WIN32 +# include <tlhelp32.h> // for CreateToolhelp32Snapshot +#endif + +#if defined(__FreeBSD__) // XXX: OpenBSD ? +# include <string.h> +# include <sys/types.h> +# include <sys/user.h> +#endif + +#if defined(__NetBSD__) || defined(__OpenBSD__) +# include <sys/param.h> +#endif + +#if defined(__APPLE__) || defined(BSD) +# include <sys/sysctl.h> +# include <pwd.h> +#endif + +#include "nvim/globals.h" +#include "nvim/log.h" +#include "nvim/os/process.h" +#include "nvim/os/os.h" +#include "nvim/os/os_defs.h" +#include "nvim/api/private/helpers.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/process.c.generated.h" +#endif + +#ifdef WIN32 +static bool os_proc_tree_kill_rec(HANDLE process, int sig) +{ + if (process == NULL) { + return false; + } + PROCESSENTRY32 pe; + DWORD pid = GetProcessId(process); + + if (pid != 0) { + HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (h != INVALID_HANDLE_VALUE) { + pe.dwSize = sizeof(PROCESSENTRY32); + if (!Process32First(h, &pe)) { + goto theend; + } + do { + if (pe.th32ParentProcessID == pid) { + HANDLE ph = OpenProcess(PROCESS_ALL_ACCESS, false, pe.th32ProcessID); + if (ph != NULL) { + os_proc_tree_kill_rec(ph, sig); + CloseHandle(ph); + } + } + } while (Process32Next(h, &pe)); + CloseHandle(h); + } + } + +theend: + return (bool)TerminateProcess(process, (unsigned int)sig); +} +/// Kills process `pid` and its descendants recursively. +bool os_proc_tree_kill(int pid, int sig) +{ + assert(sig >= 0); + assert(sig == SIGTERM || sig == SIGKILL); + if (pid > 0) { + ILOG("terminating process tree: %d", pid); + HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, false, (DWORD)pid); + return os_proc_tree_kill_rec(h, sig); + } else { + ELOG("invalid pid: %d", pid); + } + return false; +} +#else +/// Kills process group where `pid` is the process group leader. +bool os_proc_tree_kill(int pid, int sig) +{ + assert(sig == SIGTERM || sig == SIGKILL); + int pgid = getpgid(pid); + if (pgid > 0) { // Ignore error. Never kill self (pid=0). + if (pgid == pid) { + ILOG("sending %s to process group: -%d", + sig == SIGTERM ? "SIGTERM" : "SIGKILL", pgid); + int rv = uv_kill(-pgid, sig); + return rv == 0; + } else { + // Should never happen, because process_spawn() did setsid() in the child. + ELOG("pgid %d != pid %d", pgid, pid); + } + } else { + ELOG("getpgid(%d) returned %d", pid, pgid); + } + return false; +} +#endif + +/// Gets the process ids of the immediate children of process `ppid`. +/// +/// @param ppid Process to inspect. +/// @param[out,allocated] proc_list Child process ids. +/// @param[out] proc_count Number of child processes. +/// @return 0 on success, 1 if process not found, 2 on other error. +int os_proc_children(int ppid, int **proc_list, size_t *proc_count) +{ + if (ppid < 0) { + return 2; + } + + int *temp = NULL; + *proc_list = NULL; + *proc_count = 0; + +#ifdef WIN32 + PROCESSENTRY32 pe; + + // Snapshot of all processes. + HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (h == INVALID_HANDLE_VALUE) { + return 2; + } + + pe.dwSize = sizeof(PROCESSENTRY32); + // Get root process. + if (!Process32First(h, &pe)) { + CloseHandle(h); + return 2; + } + // Collect processes whose parent matches `ppid`. + do { + if (pe.th32ParentProcessID == (DWORD)ppid) { + temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp)); + temp[*proc_count] = (int)pe.th32ProcessID; + (*proc_count)++; + } + } while (Process32Next(h, &pe)); + CloseHandle(h); + +#elif defined(__APPLE__) || defined(BSD) +# if defined(__APPLE__) +# define KP_PID(o) o.kp_proc.p_pid +# define KP_PPID(o) o.kp_eproc.e_ppid +# elif defined(__FreeBSD__) +# define KP_PID(o) o.ki_pid +# define KP_PPID(o) o.ki_ppid +# else +# define KP_PID(o) o.p_pid +# define KP_PPID(o) o.p_ppid +# endif +# ifdef __NetBSD__ + static int name[] = { + CTL_KERN, KERN_PROC2, KERN_PROC_ALL, 0, (int)(sizeof(struct kinfo_proc2)), 0 + }; +# else + static int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; +# endif + + // Get total process count. + size_t len = 0; + int rv = sysctl(name, ARRAY_SIZE(name) - 1, NULL, &len, NULL, 0); + if (rv) { + return 2; + } + + // Get ALL processes. +# ifdef __NetBSD__ + struct kinfo_proc2 *p_list = xmalloc(len); +# else + struct kinfo_proc *p_list = xmalloc(len); +# endif + rv = sysctl(name, ARRAY_SIZE(name) - 1, p_list, &len, NULL, 0); + if (rv) { + xfree(p_list); + return 2; + } + + // Collect processes whose parent matches `ppid`. + bool exists = false; + size_t p_count = len / sizeof(*p_list); + for (size_t i = 0; i < p_count; i++) { + exists = exists || KP_PID(p_list[i]) == ppid; + if (KP_PPID(p_list[i]) == ppid) { + temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp)); + temp[*proc_count] = KP_PID(p_list[i]); + (*proc_count)++; + } + } + xfree(p_list); + if (!exists) { + return 1; // Process not found. + } + +#elif defined(__linux__) + char proc_p[256] = { 0 }; + // Collect processes whose parent matches `ppid`. + // Rationale: children are defined in thread with same ID of process. + snprintf(proc_p, sizeof(proc_p), "/proc/%d/task/%d/children", ppid, ppid); + FILE *fp = fopen(proc_p, "r"); + if (fp == NULL) { + return 2; // Process not found, or /proc/…/children not supported. + } + int match_pid; + while (fscanf(fp, "%d", &match_pid) > 0) { + temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp)); + temp[*proc_count] = match_pid; + (*proc_count)++; + } + fclose(fp); +#endif + + *proc_list = temp; + return 0; +} + +#ifdef WIN32 +/// Gets various properties of the process identified by `pid`. +/// +/// @param pid Process to inspect. +/// @return Map of process properties, empty on error. +Dictionary os_proc_info(int pid) +{ + Dictionary pinfo = ARRAY_DICT_INIT; + PROCESSENTRY32 pe; + + // Snapshot of all processes. This is used instead of: + // OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, …) + // to avoid ERROR_PARTIAL_COPY. https://stackoverflow.com/a/29942376 + HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (h == INVALID_HANDLE_VALUE) { + return pinfo; // Return empty. + } + + pe.dwSize = sizeof(PROCESSENTRY32); + // Get root process. + if (!Process32First(h, &pe)) { + CloseHandle(h); + return pinfo; // Return empty. + } + // Find the process. + do { + if (pe.th32ProcessID == (DWORD)pid) { + break; + } + } while (Process32Next(h, &pe)); + CloseHandle(h); + + if (pe.th32ProcessID == (DWORD)pid) { + PUT(pinfo, "pid", INTEGER_OBJ(pid)); + PUT(pinfo, "ppid", INTEGER_OBJ((int)pe.th32ParentProcessID)); + PUT(pinfo, "name", STRING_OBJ(cstr_to_string(pe.szExeFile))); + } + + return pinfo; +} +#endif diff --git a/src/nvim/os/process.h b/src/nvim/os/process.h new file mode 100644 index 0000000000..1722d56bd3 --- /dev/null +++ b/src/nvim/os/process.h @@ -0,0 +1,11 @@ +#ifndef NVIM_OS_PROCESS_H +#define NVIM_OS_PROCESS_H + +#include <stddef.h> +#include "nvim/api/private/defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/process.h.generated.h" +#endif + +#endif // NVIM_OS_PROCESS_H diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index 855ca2ae47..dfe2cfbb8d 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -145,8 +145,12 @@ void pty_process_teardown(Loop *loop) uv_signal_stop(&loop->children_watcher); } -static void init_child(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL +static void init_child(PtyProcess *ptyproc) + FUNC_ATTR_NONNULL_ALL { + // New session/process-group. #6530 + setsid(); + unsetenv("COLUMNS"); unsetenv("LINES"); unsetenv("TERMCAP"); diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index f650a51fe7..04f59d7522 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -136,7 +136,7 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args) xfree(input.data); if (output) { - (void)write_output(output, nread, true, true); + (void)write_output(output, nread, true); xfree(output); } @@ -388,10 +388,10 @@ static bool out_data_decide_throttle(size_t size) pulse_msg[1] = (tick == 0 || 1 == tick) ? ' ' : '.'; pulse_msg[2] = (tick == 0 || 1 == tick || 2 == tick) ? ' ' : '.'; if (visit == 1) { - screen_del_lines(0, 0, 1, (int)Rows, NULL); + msg_putchar('\n'); } - int lastrow = (int)Rows - 1; - screen_puts_len((char_u *)pulse_msg, ARRAY_SIZE(pulse_msg), lastrow, 0, 0); + msg_putchar('\r'); // put cursor at start of line + msg_puts(pulse_msg); ui_flush(); return true; } @@ -609,28 +609,20 @@ static void read_input(DynamicBuffer *buf) } } -static size_t write_output(char *output, size_t remaining, bool to_buffer, - bool eof) +static size_t write_output(char *output, size_t remaining, bool eof) { if (!output) { return 0; } - char replacement_NUL = to_buffer ? NL : 1; char *start = output; size_t off = 0; - int lastrow = (int)Rows - 1; while (off < remaining) { if (output[off] == NL) { // Insert the line - if (to_buffer) { - output[off] = NUL; - ml_append(curwin->w_cursor.lnum++, (char_u *)output, (int)off + 1, - false); - } else { - screen_del_lines(0, 0, 1, (int)Rows, NULL); - screen_puts_len((char_u *)output, (int)off, lastrow, 0, 0); - } + output[off] = NUL; + ml_append(curwin->w_cursor.lnum++, (char_u *)output, (int)off + 1, + false); size_t skip = off + 1; output += skip; remaining -= skip; @@ -640,24 +632,19 @@ static size_t write_output(char *output, size_t remaining, bool to_buffer, if (output[off] == NUL) { // Translate NUL to NL - output[off] = replacement_NUL; + output[off] = NL; } off++; } if (eof) { if (remaining) { - if (to_buffer) { - // append unfinished line - ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false); - // remember that the NL was missing - curbuf->b_no_eol_lnum = curwin->w_cursor.lnum; - } else { - screen_del_lines(0, 0, 1, (int)Rows, NULL); - screen_puts_len((char_u *)output, (int)remaining, lastrow, 0, 0); - } + // append unfinished line + ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false); + // remember that the NL was missing + curbuf->b_no_eol_lnum = curwin->w_cursor.lnum; output += remaining; - } else if (to_buffer) { + } else { curbuf->b_no_eol_lnum = 0; } } diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index 732be072e1..fc7f9cefd1 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -145,7 +145,7 @@ static void on_signal(SignalWatcher *handle, int signum, void *data) case SIGPWR: // Signal of a power failure(eg batteries low), flush the swap files to // be safe - ml_sync_all(false, false); + ml_sync_all(false, false, true); break; #endif #ifdef SIGPIPE diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c index a41fb7c621..866a005228 100644 --- a/src/nvim/os/stdpaths.c +++ b/src/nvim/os/stdpaths.c @@ -88,7 +88,7 @@ char *stdpaths_get_xdg_var(const XDGVarType idx) /// /// In WIN32 get_xdg_home(kXDGDataHome) returns `{xdg_directory}/nvim-data` to /// avoid storing configuration and data files in the same path. -static char *get_xdg_home(const XDGVarType idx) +char *get_xdg_home(const XDGVarType idx) FUNC_ATTR_WARN_UNUSED_RESULT { char *dir = stdpaths_get_xdg_var(idx); diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h index faee06304c..db93f016bf 100644 --- a/src/nvim/os/win_defs.h +++ b/src/nvim/os/win_defs.h @@ -32,11 +32,8 @@ // Windows defines a RGB macro that produces 0x00bbggrr color values for use // with GDI. Our macro is different, and we don't use GDI. -#if defined(RGB) -# undef RGB - // Duplicated from macros.h to avoid include-order sensitivity. -# define RGB(r, g, b) ((r << 16) | (g << 8) | b) -#endif +// Duplicated from macros.h to avoid include-order sensitivity. +#define RGB_(r, g, b) ((r << 16) | (g << 8) | b) #ifdef _MSC_VER # ifndef inline diff --git a/src/nvim/path.c b/src/nvim/path.c index 21ac064c30..4f3f7c0661 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -452,10 +452,10 @@ char *FullName_save(const char *fname, bool force) /// Saves the absolute path. /// @param name An absolute or relative path. /// @return The absolute path of `name`. -char_u *save_absolute_path(const char_u *name) +char_u *save_abs_path(const char_u *name) FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { - if (!path_is_absolute_path(name)) { + if (!path_is_absolute(name)) { return (char_u *)FullName_save((char *)name, true); } return vim_strsave((char_u *) name); @@ -673,11 +673,12 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, // Find all matching entries. char_u *name; scandir_next_with_dots(NULL); // initialize - while ((name = (char_u *) scandir_next_with_dots(&dir)) && name != NULL) { + while ((name = (char_u *)scandir_next_with_dots(&dir)) != NULL) { if ((name[0] != '.' || starts_with_dot || ((flags & EW_DODOT) - && name[1] != NUL && (name[1] != '.' || name[2] != NUL))) + && name[1] != NUL + && (name[1] != '.' || name[2] != NUL))) // -V557 && ((regmatch.regprog != NULL && vim_regexec(®match, name, 0)) || ((flags & EW_NOTWILD) && fnamencmp(path + (s - buf), name, e - s) == 0))) { @@ -814,7 +815,7 @@ static void expand_path_option(char_u *curdir, garray_T *gap) STRCPY(buf, curdir); // relative to current directory } else if (path_with_url((char *)buf)) { continue; // URL can't be used here - } else if (!path_is_absolute_path(buf)) { + } else if (!path_is_absolute(buf)) { // Expand relative path to their full path equivalent size_t len = STRLEN(curdir); if (len + STRLEN(buf) + 3 > MAXPATHL) { @@ -949,19 +950,17 @@ static void uniquefy_paths(garray_T *gap, char_u *pattern) } } - if (path_is_absolute_path(path)) { - /* - * Last resort: shorten relative to curdir if possible. - * 'possible' means: - * 1. It is under the current directory. - * 2. The result is actually shorter than the original. - * - * Before curdir After - * /foo/bar/file.txt /foo/bar ./file.txt - * c:\foo\bar\file.txt c:\foo\bar .\file.txt - * /file.txt / /file.txt - * c:\file.txt c:\ .\file.txt - */ + if (path_is_absolute(path)) { + // Last resort: shorten relative to curdir if possible. + // 'possible' means: + // 1. It is under the current directory. + // 2. The result is actually shorter than the original. + // + // Before curdir After + // /foo/bar/file.txt /foo/bar ./file.txt + // c:\foo\bar\file.txt c:\foo\bar .\file.txt + // /file.txt / /file.txt + // c:\file.txt c:\ .\file.txt short_name = path_shorten_fname(path, curdir); if (short_name != NULL && short_name > path + 1 ) { @@ -1221,7 +1220,7 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, */ if (path_has_exp_wildcard(p)) { if ((flags & EW_PATH) - && !path_is_absolute_path(p) + && !path_is_absolute(p) && !(p[0] == '.' && (vim_ispathsep(p[1]) || (p[1] == '.' && vim_ispathsep(p[2])))) @@ -1667,7 +1666,7 @@ int path_with_url(const char *fname) */ bool vim_isAbsName(char_u *name) { - return path_with_url((char *)name) != 0 || path_is_absolute_path(name); + return path_with_url((char *)name) != 0 || path_is_absolute(name); } /// Save absolute file name to "buf[len]". @@ -1701,7 +1700,7 @@ int vim_FullName(const char *fname, char *buf, size_t len, bool force) return OK; } - int rv = path_get_absolute_path((char_u *)fname, (char_u *)buf, len, force); + int rv = path_to_absolute((char_u *)fname, (char_u *)buf, len, force); if (rv == FAIL) { xstrlcpy(buf, fname, len); // something failed; use the filename } @@ -1910,7 +1909,7 @@ int pathcmp(const char *p, const char *q, int maxlen) /// - Pointer into `full_path` if shortened. /// - `full_path` unchanged if no shorter name is possible. /// - NULL if `full_path` is NULL. -char_u *path_shorten_fname_if_possible(char_u *full_path) +char_u *path_try_shorten_fname(char_u *full_path) { char_u *dirname = xmalloc(MAXPATHL); char_u *p = full_path; @@ -2191,8 +2190,8 @@ int append_path(char *path, const char *to_append, size_t max_len) /// @param force also expand when "fname" is already absolute. /// /// @return FAIL for failure, OK for success. -static int path_get_absolute_path(const char_u *fname, char_u *buf, - size_t len, int force) +static int path_to_absolute(const char_u *fname, char_u *buf, size_t len, + int force) { char_u *p; *buf = NUL; @@ -2201,7 +2200,7 @@ static int path_get_absolute_path(const char_u *fname, char_u *buf, char *end_of_path = (char *) fname; // expand it if forced or not an absolute path - if (force || !path_is_absolute_path(fname)) { + if (force || !path_is_absolute(fname)) { p = vim_strrchr(fname, '/'); #ifdef WIN32 if (p == NULL) { @@ -2234,10 +2233,10 @@ static int path_get_absolute_path(const char_u *fname, char_u *buf, return append_path((char *)buf, end_of_path, len); } -/// Check if the given file is absolute. +/// Check if file `fname` is a full (absolute) path. /// /// @return `TRUE` if "fname" is absolute. -int path_is_absolute_path(const char_u *fname) +int path_is_absolute(const char_u *fname) { #ifdef WIN32 // A name like "d:/foo" and "//server/share" is absolute @@ -2262,7 +2261,7 @@ void path_guess_exepath(const char *argv0, char *buf, size_t bufsize) { char *path = getenv("PATH"); - if (path == NULL || path_is_absolute_path((char_u *)argv0)) { + if (path == NULL || path_is_absolute((char_u *)argv0)) { xstrlcpy(buf, argv0, bufsize); } else if (argv0[0] == '.' || strchr(argv0, PATHSEP)) { // Relative to CWD. diff --git a/src/nvim/po/CMakeLists.txt b/src/nvim/po/CMakeLists.txt index 121f22129a..94cc63baea 100644 --- a/src/nvim/po/CMakeLists.txt +++ b/src/nvim/po/CMakeLists.txt @@ -2,10 +2,8 @@ find_package(Gettext) find_program(XGETTEXT_PRG xgettext) find_program(ICONV_PRG iconv) -if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) - set(ENV{OLD_PO_FILE_INPUT} yes) - set(ENV{OLD_PO_FILE_OUTPUT} yes) - +option(LANGUAGES "Localizations to build") +if(NOT LANGUAGES) set(LANGUAGES af ca @@ -31,6 +29,12 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) vi zh_CN.UTF-8 zh_TW.UTF-8) +endif() + +if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) + set(ENV{OLD_PO_FILE_INPUT} yes) + set(ENV{OLD_PO_FILE_OUTPUT} yes) + set(NVIM_RELATIVE_SOURCES) foreach(SRC ${NVIM_SOURCES} ${NVIM_HEADERS}) @@ -135,22 +139,30 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) endmacro() # Create some translations from others. - BuildPoIconv(ja utf-8 euc-jp) - BuildMo(ja.euc-jp) + if(";${LANGUAGES};" MATCHES ";ja;") + BuildPoIconv(ja utf-8 euc-jp) + BuildMo(ja.euc-jp) + endif() - BuildPoIconv(cs ISO-8859-2 cp1250) - BuildMo(cs.cp1250) + if(";${LANGUAGES};" MATCHES ";cs;") + BuildPoIconv(cs ISO-8859-2 cp1250) + BuildMo(cs.cp1250) + endif() - BuildPoIconv(sk ISO-8859-2 cp1250) - BuildMo(sk.cp1250) + if(";${LANGUAGES};" MATCHES ";sk;") + BuildPoIconv(sk ISO-8859-2 cp1250) + BuildMo(sk.cp1250) + endif() add_custom_target(update-po-nb COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/no.po ${CMAKE_CURRENT_SOURCE_DIR}/nb.po DEPENDS no.po) list(APPEND UPDATE_PO_TARGETS update-po-nb) - CheckPo(nb) - BuildMo(nb) + if(";${LANGUAGES};" MATCHES ";no;") + CheckPo(nb) + BuildMo(nb) + endif() foreach(LANGUAGE ${LANGUAGES}) set(poFile "${CMAKE_CURRENT_SOURCE_DIR}/${LANGUAGE}.po") diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 348daf028a..4c2fc6d906 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -387,7 +387,7 @@ void pum_redraw(void) char_u saved = *p; *p = NUL; - st = transstr(s); + st = (char_u *)transstr((const char *)s); *p = saved; if (curwin->w_p_rl) { diff --git a/src/nvim/pos.h b/src/nvim/pos.h index 966255e6a4..0a2afd5847 100644 --- a/src/nvim/pos.h +++ b/src/nvim/pos.h @@ -10,8 +10,10 @@ typedef int colnr_T; /// Format used to print values which have colnr_T type #define PRIdCOLNR "d" -#define MAXLNUM 0x7fffffff // maximum (invalid) line number -#define MAXCOL 0x7fffffff // maximum column number, 31 bits +/// Maximal (invalid) line number +enum { MAXLNUM = 0x7fffffff }; +/// Maximal column number, 31 bits +enum { MAXCOL = 0x7fffffff }; /* * position in file or buffer diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 1aeadcec4a..1c3cb5d6b2 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -924,7 +924,7 @@ restofline: if (qfprev == NULL) { return QF_FAIL; } - if (*fields->errmsg && !qi->qf_multiignore) { + if (*fields->errmsg) { size_t len = STRLEN(qfprev->qf_text); qfprev->qf_text = xrealloc(qfprev->qf_text, len + STRLEN(fields->errmsg) + 2); @@ -2062,7 +2062,7 @@ win_found: EMSG(_("E924: Current window was closed")); is_abort = true; opened_window = false; - } else if (old_qf_curlist != qi->qf_curlist + } else if (old_qf_curlist != qi->qf_curlist // -V560 || !is_qf_entry_present(qi, qf_ptr)) { if (qi == &ql_info) { EMSG(_("E925: Current quickfix was changed")); @@ -3029,7 +3029,7 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) /* * Return TRUE if "buf" is the quickfix buffer. */ -int bt_quickfix(buf_T *buf) +int bt_quickfix(const buf_T *const buf) { return buf != NULL && buf->b_p_bt[0] == 'q'; } @@ -3626,7 +3626,7 @@ void ex_vimgrep(exarg_T *eap) && eap->cmdidx != CMD_vimgrepadd && eap->cmdidx != CMD_lvimgrepadd) || qi->qf_curlist == qi->qf_listcount) { // make place for a new list - qf_new_list(qi, title != NULL ? title : *eap->cmdlinep); + qf_new_list(qi, title); } /* parse the list of arguments */ @@ -3649,8 +3649,8 @@ void ex_vimgrep(exarg_T *eap) cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start; seconds = (time_t)0; - for (fi = 0; fi < fcount && !got_int && tomatch > 0; ++fi) { - fname = path_shorten_fname_if_possible(fnames[fi]); + for (fi = 0; fi < fcount && !got_int && tomatch > 0; fi++) { + fname = path_try_shorten_fname(fnames[fi]); if (time(NULL) > seconds) { /* Display the file name every second or so, show the user we are * working on it. */ @@ -4205,11 +4205,9 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) { if (qi->qf_lists[qf_idx].qf_ctx != NULL) { di = tv_dict_item_alloc_len(S_LEN("context")); - if (di != NULL) { - tv_copy(qi->qf_lists[qf_idx].qf_ctx, &di->di_tv); - if (tv_dict_add(retdict, di) == FAIL) { - tv_dict_item_free(di); - } + tv_copy(qi->qf_lists[qf_idx].qf_ctx, &di->di_tv); + if (tv_dict_add(retdict, di) == FAIL) { + tv_dict_item_free(di); } } else { status = tv_dict_add_str(retdict, S_LEN("context"), ""); @@ -4740,15 +4738,7 @@ void ex_helpgrep(exarg_T *eap) regmatch.regprog = vim_regcomp(eap->arg, RE_MAGIC + RE_STRING); regmatch.rm_ic = FALSE; if (regmatch.regprog != NULL) { - vimconv_T vc; - - /* Help files are in utf-8 or latin1, convert lines when 'encoding' - * differs. */ - vc.vc_type = CONV_NONE; - if (!enc_utf8) - convert_setup(&vc, (char_u *)"utf-8", p_enc); - - /* create a new quickfix list */ + // Create a new quickfix list. qf_new_list(qi, *eap->cmdlinep); /* Go through all directories in 'runtimepath' */ @@ -4780,15 +4770,6 @@ void ex_helpgrep(exarg_T *eap) lnum = 1; while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) { char_u *line = IObuff; - /* Convert a line if 'encoding' is not utf-8 and - * the line contains a non-ASCII character. */ - if (vc.vc_type != CONV_NONE - && has_non_ascii(IObuff)) { - line = string_convert(&vc, IObuff, NULL); - if (line == NULL) - line = IObuff; - } - if (vim_regexec(®match, line, (colnr_T)0)) { int l = (int)STRLEN(line); @@ -4831,8 +4812,6 @@ void ex_helpgrep(exarg_T *eap) } vim_regfree(regmatch.regprog); - if (vc.vc_type != CONV_NONE) - convert_setup(&vc, NULL, NULL); qi->qf_lists[qi->qf_curlist].qf_nonevalid = FALSE; qi->qf_lists[qi->qf_curlist].qf_ptr = diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index c520ef5fb9..98fae858f6 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -4117,7 +4117,7 @@ skip_add: if (state->c == NFA_ZSTART) { subidx = 0; sub = &subs->norm; - } else if (state->c >= NFA_ZOPEN && state->c <= NFA_ZOPEN9) { + } else if (state->c >= NFA_ZOPEN && state->c <= NFA_ZOPEN9) { // -V560 subidx = state->c - NFA_ZOPEN; sub = &subs->synt; } else { @@ -4169,11 +4169,12 @@ skip_add: } subs = addstate(l, state->out, subs, pim, off_arg); - /* "subs" may have changed, need to set "sub" again */ - if (state->c >= NFA_ZOPEN && state->c <= NFA_ZOPEN9) + // "subs" may have changed, need to set "sub" again. + if (state->c >= NFA_ZOPEN && state->c <= NFA_ZOPEN9) { // -V560 sub = &subs->synt; - else + } else { sub = &subs->norm; + } if (save_in_use == -1) { if (REG_MULTI) { @@ -4217,7 +4218,7 @@ skip_add: if (state->c == NFA_ZEND) { subidx = 0; sub = &subs->norm; - } else if (state->c >= NFA_ZCLOSE && state->c <= NFA_ZCLOSE9) { + } else if (state->c >= NFA_ZCLOSE && state->c <= NFA_ZCLOSE9) { // -V560 subidx = state->c - NFA_ZCLOSE; sub = &subs->synt; } else { @@ -4250,11 +4251,12 @@ skip_add: } subs = addstate(l, state->out, subs, pim, off_arg); - /* "subs" may have changed, need to set "sub" again */ - if (state->c >= NFA_ZCLOSE && state->c <= NFA_ZCLOSE9) + // "subs" may have changed, need to set "sub" again. + if (state->c >= NFA_ZCLOSE && state->c <= NFA_ZCLOSE9) { // -V560 sub = &subs->synt; - else + } else { sub = &subs->norm; + } if (REG_MULTI) { sub->list.multi[subidx] = save_multipos; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index c13e68f8a1..f034ac33f1 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -24,8 +24,6 @@ * cells the next byte in ScreenLines[] is 0. * ScreenLinesC[][] contain up to 'maxcombine' composing characters * (drawn on top of the first character). There is 0 after the last one used. - * ScreenLines2[] is only used for euc-jp to store the second byte if the - * first byte is 0x8e (single-width character). * * The screen_*() functions write to the screen and handle updating * ScreenLines[]. @@ -300,13 +298,28 @@ void update_screen(int type) * if the screen was scrolled up when displaying a message, scroll it down */ if (msg_scrolled) { - clear_cmdline = TRUE; - if (msg_scrolled > Rows - 5) /* clearing is faster */ + clear_cmdline = true; + if (dy_flags & DY_MSGSEP) { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + int valid = Rows - msg_scrollsize(); + if (wp->w_winrow + wp->w_height > valid) { + wp->w_redr_type = NOT_VALID; + wp->w_lines_valid = 0; + } + if (wp->w_winrow + wp->w_height + wp->w_status_height > valid) { + wp->w_redr_status = true; + } + if (valid == 0) { + redraw_tabline = true; + } + } + } else if (msg_scrolled > Rows - 5) { // clearing is faster type = CLEAR; - else if (type != CLEAR) { - check_for_delay(FALSE); - if (screen_ins_lines(0, 0, msg_scrolled, (int)Rows, NULL) == FAIL) + } else if (type != CLEAR) { + check_for_delay(false); + if (screen_ins_lines(0, 0, msg_scrolled, (int)Rows, NULL) == FAIL) { type = CLEAR; + } FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_winrow < msg_scrolled) { if (wp->w_winrow + wp->w_height > msg_scrolled @@ -1582,6 +1595,20 @@ static void win_update(win_T *wp) got_int = save_got_int; } +/// Returns width of the signcolumn that should be used for the whole window +/// +/// @param wp window we want signcolumn width from +/// @return max width of signcolumn (cell unit) +/// +/// @note Returns a constant for now but hopefully we can improve neovim so that +/// the returned value width adapts to the maximum number of marks to draw +/// for the window +/// TODO(teto) +int win_signcol_width(win_T *wp) +{ + // 2 is vim default value + return 2; +} /* * Clear the rest of the window and mark the unused lines with "c1". use "c2" @@ -1609,7 +1636,7 @@ static void win_draw_end(win_T *wp, int c1, int c2, int row, int endrow, hlf_T h } if (signcolumn_on(wp)) { - int nn = n + 2; + int nn = n + win_signcol_width(wp); /* draw the sign column left of the fold column */ if (nn > wp->w_width) { @@ -1650,7 +1677,7 @@ static void win_draw_end(win_T *wp, int c1, int c2, int row, int endrow, hlf_T h } if (signcolumn_on(wp)) { - int nn = n + 2; + int nn = n + win_signcol_width(wp); /* draw the sign column after the fold column */ if (nn > wp->w_width) { @@ -1762,12 +1789,13 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T * text */ RL_MEMSET(col, win_hl_attr(wp, HLF_FL), wp->w_width - col); - // If signs are being displayed, add two spaces. + // If signs are being displayed, add spaces. if (signcolumn_on(wp)) { len = wp->w_width - col; if (len > 0) { - if (len > 2) { - len = 2; + int len_max = win_signcol_width(wp); + if (len > len_max) { + len = len_max; } copy_text_attr(off + col, (char_u *)" ", len, win_hl_attr(wp, HLF_FL)); @@ -1891,12 +1919,10 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T } if (cells > 1) ScreenLines[idx + 1] = 0; - } else if (enc_dbcs == DBCS_JPNU && *p == 0x8e) - /* double-byte single width character */ - ScreenLines2[idx] = p[1]; - else if (cells > 1) - /* double-width character */ + } else if (cells > 1) { + // Double-width character. ScreenLines[idx + 1] = p[1]; + } col += cells; idx += cells; p += c_len; @@ -2017,7 +2043,7 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T } screen_line(row + wp->w_winrow, wp->w_wincol, wp->w_width, - wp->w_width, false, wp); + wp->w_width, false, wp, 0); /* * Update w_cline_height and w_cline_folded if the cursor line was @@ -2117,7 +2143,6 @@ win_line ( bool nochange /* not updating for changed text */ ) { - int col = 0; // visual column on screen unsigned off; // offset in ScreenLines/ScreenAttrs int c = 0; // init for GCC long vcol = 0; // virtual column (for tabs) @@ -2438,10 +2463,6 @@ win_line ( line_attr = win_hl_attr(wp, HLF_QFL); } - if (wp->w_hl_attr_normal != 0) { - line_attr = hl_combine_attr(wp->w_hl_attr_normal, line_attr); - } - if (line_attr != 0) { area_highlighting = true; } @@ -2530,7 +2551,7 @@ win_line ( ptr = prev_ptr; // If the character fits on the screen, don't need to skip it. // Except for a TAB. - if (((*mb_ptr2cells)(ptr) >= c || *ptr == TAB) && col == 0) { + if (utf_ptr2cells(ptr) >= c || *ptr == TAB) { n_skip = v - vcol; } } @@ -2679,11 +2700,11 @@ win_line ( } off = (unsigned)(current_ScreenLine - ScreenLines); - col = 0; + int col = 0; // Visual column on screen. if (wp->w_p_rl) { - /* Rightleft window: process the text in the normal direction, but put - * it in current_ScreenLine[] from right to left. Start at the - * rightmost column of the window. */ + // Rightleft window: process the text in the normal direction, but put + // it in current_ScreenLine[] from right to left. Start at the + // rightmost column of the window. col = wp->w_width - 1; off += col; } @@ -2735,18 +2756,25 @@ win_line ( * buffer or when using Netbeans. */ if (signcolumn_on(wp)) { int text_sign; - /* Draw two cells with the sign value or blank. */ + // Draw cells with the sign value or blank. c_extra = ' '; char_attr = win_hl_attr(wp, HLF_SC); - n_extra = 2; + n_extra = win_signcol_width(wp); if (row == startrow + filler_lines && filler_todo <= 0) { text_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_TEXT); if (text_sign != 0) { p_extra = sign_get_text(text_sign); + int symbol_blen = (int)STRLEN(p_extra); if (p_extra != NULL) { c_extra = NUL; - n_extra = (int)STRLEN(p_extra); + // symbol(s) bytes + (filling spaces) (one byte each) + n_extra = symbol_blen + + (win_signcol_width(wp) - mb_string2cells(p_extra)); + memset(extra, ' ', sizeof(extra)); + STRNCPY(extra, p_extra, STRLEN(p_extra)); + p_extra = extra; + p_extra[n_extra] = NUL; } char_attr = sign_get_attr(text_sign, FALSE); } @@ -2891,7 +2919,8 @@ win_line ( && lnum == wp->w_cursor.lnum && vcol >= (long)wp->w_virtcol && filler_todo <= 0 ) { - screen_line(screen_row, wp->w_wincol, col, -wp->w_width, wp->w_p_rl, wp); + screen_line(screen_row, wp->w_wincol, col, -wp->w_width, wp->w_p_rl, wp, + wp->w_hl_attr_normal); // Pretend we have finished updating the window. Except when // 'cursorcolumn' is set. if (wp->w_p_cuc) { @@ -3178,9 +3207,9 @@ win_line ( || (mb_l > 1 && (!vim_isprintc(mb_c)))) { // Illegal UTF-8 byte: display as <xx>. // Non-BMP character : display as ? or fullwidth ?. - transchar_hex(extra, mb_c); - if (wp->w_p_rl) { // reverse - rl_mirror(extra); + transchar_hex((char *)extra, mb_c); + if (wp->w_p_rl) { // reverse + rl_mirror(extra); } p_extra = extra; @@ -3511,8 +3540,7 @@ win_line ( tab_len += vcol_off; } // boguscols before FIX_FOR_BOGUSCOLS macro from above. - if (wp->w_p_list && lcs_tab1 && old_boguscols > 0 - && n_extra > tab_len) { + if (lcs_tab1 && old_boguscols > 0 && n_extra > tab_len) { tab_len += n_extra - tab_len; } @@ -3984,7 +4012,8 @@ win_line ( col++; } } - screen_line(screen_row, wp->w_wincol, col, wp->w_width, wp->w_p_rl, wp); + screen_line(screen_row, wp->w_wincol, col, wp->w_width, wp->w_p_rl, wp, + wp->w_hl_attr_normal); row++; /* @@ -4057,24 +4086,19 @@ win_line ( --col; } ScreenLines[off] = c; - if (enc_dbcs == DBCS_JPNU) { - if ((mb_c & 0xff00) == 0x8e00) - ScreenLines[off] = 0x8e; - ScreenLines2[off] = mb_c & 0xff; - } else if (enc_utf8) { - if (mb_utf8) { - int i; - - ScreenLinesUC[off] = mb_c; - if ((c & 0xff) == 0) - ScreenLines[off] = 0x80; /* avoid storing zero */ - for (i = 0; i < Screen_mco; ++i) { - ScreenLinesC[i][off] = u8cc[i]; - if (u8cc[i] == 0) - break; + if (mb_utf8) { + ScreenLinesUC[off] = mb_c; + if ((c & 0xff) == 0) { + ScreenLines[off] = 0x80; // Avoid storing zero. + } + for (int i = 0; i < Screen_mco; i++) { + ScreenLinesC[i][off] = u8cc[i]; + if (u8cc[i] == 0) { + break; } - } else - ScreenLinesUC[off] = 0; + } + } else { + ScreenLinesUC[off] = 0; } if (multi_attr) { ScreenAttrs[off] = multi_attr; @@ -4207,7 +4231,7 @@ win_line ( || (n_extra != 0 && (c_extra != NUL || *p_extra != NUL))) ) { screen_line(screen_row, wp->w_wincol, col - boguscols, - wp->w_width, wp->w_p_rl, wp); + wp->w_width, wp->w_p_rl, wp, wp->w_hl_attr_normal); boguscols = 0; ++row; ++screen_row; @@ -4346,23 +4370,14 @@ static int comp_char_differs(int off_from, int off_to) static int char_needs_redraw(int off_from, int off_to, int cols) { return (cols > 0 - && ((ScreenLines[off_from] != ScreenLines[off_to] - || ScreenAttrs[off_from] != ScreenAttrs[off_to]) - - || (enc_dbcs != 0 - && MB_BYTE2LEN(ScreenLines[off_from]) > 1 - && (enc_dbcs == DBCS_JPNU && ScreenLines[off_from] == 0x8e - ? ScreenLines2[off_from] != ScreenLines2[off_to] - : (cols > 1 && ScreenLines[off_from + 1] - != ScreenLines[off_to + 1]))) - || (enc_utf8 - && (ScreenLinesUC[off_from] != ScreenLinesUC[off_to] - || (ScreenLinesUC[off_from] != 0 - && comp_char_differs(off_from, off_to)) - || ((*mb_off2cells)(off_from, off_from + cols) > 1 - && ScreenLines[off_from + 1] - != ScreenLines[off_to + 1]))) - || p_wd < 0)); + && (ScreenLines[off_from] != ScreenLines[off_to] + || ScreenAttrs[off_from] != ScreenAttrs[off_to] + || ScreenLinesUC[off_from] != ScreenLinesUC[off_to] + || (ScreenLinesUC[off_from] != 0 + && comp_char_differs(off_from, off_to)) + || (utf_off2cells(off_from, off_from + cols) > 1 + && ScreenLines[off_from + 1] != ScreenLines[off_to + 1]) + || p_wd < 0)); } /* @@ -4377,7 +4392,7 @@ static int char_needs_redraw(int off_from, int off_to, int cols) * When FALSE and "clear_width" > 0, clear columns "endcol" to "clear_width" */ static void screen_line(int row, int coloff, int endcol, - int clear_width, int rlflag, win_T *wp) + int clear_width, int rlflag, win_T *wp, int bg_attr) { unsigned off_from; unsigned off_to; @@ -4410,15 +4425,16 @@ static void screen_line(int row, int coloff, int endcol, /* Clear rest first, because it's left of the text. */ if (clear_width > 0) { while (col <= endcol && ScreenLines[off_to] == ' ' - && ScreenAttrs[off_to] == 0 + && ScreenAttrs[off_to] == bg_attr && (!enc_utf8 || ScreenLinesUC[off_to] == 0) ) { ++off_to; ++col; } - if (col <= endcol) - screen_fill(row, row + 1, col + coloff, - endcol + coloff + 1, ' ', ' ', 0); + if (col <= endcol) { + screen_fill(row, row + 1, col + coloff, endcol + coloff + 1, ' ', ' ', + bg_attr); + } } col = endcol + 1; off_to = LineOffset[row] + col + coloff; @@ -4426,6 +4442,13 @@ static void screen_line(int row, int coloff, int endcol, endcol = (clear_width > 0 ? clear_width : -clear_width); } + if (bg_attr) { + for (int c = col; c < endcol; c++) { + ScreenAttrs[off_from+c] = hl_combine_attr(bg_attr, + ScreenAttrs[off_from+c]); + } + } + redraw_next = char_needs_redraw(off_from, off_to, endcol - col); while (col < endcol) { @@ -4462,9 +4485,6 @@ static void screen_line(int row, int coloff, int endcol, ScreenLines[off_to + 2] = 0; redraw_next = TRUE; } - - if (enc_dbcs == DBCS_JPNU) - ScreenLines2[off_to] = ScreenLines2[off_from]; } /* When writing a single-width character over a double-width * character and at the end of the redrawn text, need to clear out @@ -4524,15 +4544,15 @@ static void screen_line(int row, int coloff, int endcol, /* blank out the rest of the line */ while (col < clear_width && ScreenLines[off_to] == ' ' - && ScreenAttrs[off_to] == 0 + && ScreenAttrs[off_to] == bg_attr && (!enc_utf8 || ScreenLinesUC[off_to] == 0) ) { ++off_to; ++col; } if (col < clear_width) { - screen_fill(row, row + 1, col + coloff, clear_width + coloff, - ' ', ' ', 0); + screen_fill(row, row + 1, col + coloff, clear_width + coloff, ' ', ' ', + bg_attr); off_to += clear_width - col; col = clear_width; } @@ -5197,8 +5217,8 @@ win_redr_custom ( xfree(stl); ewp->w_p_crb = p_crb_save; - /* Make all characters printable. */ - p = transstr(buf); + // Make all characters printable. + p = (char_u *)transstr((const char *)buf); len = STRLCPY(buf, p, sizeof(buf)); len = (size_t)len < sizeof(buf) ? len : (int)sizeof(buf) - 1; xfree(p); @@ -5290,15 +5310,8 @@ void screen_getbytes(int row, int col, char_u *bytes, int *attrp) bytes[0] = ScreenLines[off]; bytes[1] = NUL; - if (enc_utf8 && ScreenLinesUC[off] != 0) + if (ScreenLinesUC[off] != 0) { bytes[utfc_char2bytes(off, bytes)] = NUL; - else if (enc_dbcs == DBCS_JPNU && ScreenLines[off] == 0x8e) { - bytes[0] = ScreenLines[off]; - bytes[1] = ScreenLines2[off]; - bytes[2] = NUL; - } else if (enc_dbcs && MB_BYTE2LEN(bytes[0]) > 1) { - bytes[1] = ScreenLines[off + 1]; - bytes[2] = NUL; } } } @@ -5489,11 +5502,9 @@ void screen_puts_len(char_u *text, int textlen, int row, int col, int attr) ScreenLines[off + 1] = ptr[1]; ScreenAttrs[off + 1] = attr; screen_char_2(off, row, col); - } else if (l_enc_dbcs == DBCS_JPNU && c == 0x8e) { - ScreenLines2[off] = ptr[1]; - screen_char(off, row, col); - } else + } else { screen_char(off, row, col); + } } if (l_has_mbyte) { off += mbyte_cells; @@ -5866,7 +5877,7 @@ static void screen_char(unsigned off, int row, int col) ui_cursor_goto(row, col); ui_set_highlight(ScreenAttrs[off]); - if (enc_utf8 && ScreenLinesUC[off] != 0) { + if (ScreenLinesUC[off] != 0) { char_u buf[MB_MAXBYTES + 1]; // Convert UTF-8 character to bytes and write it. @@ -5874,10 +5885,6 @@ static void screen_char(unsigned off, int row, int col) ui_puts(buf); } else { ui_putc(ScreenLines[off]); - // double-byte character in single-width cell - if (enc_dbcs == DBCS_JPNU && ScreenLines[off] == 0x8e) { - ui_putc(ScreenLines2[off]); - } } } @@ -6069,40 +6076,25 @@ int screen_valid(int doclear) void screenalloc(bool doclear) { int new_row, old_row; - int outofmem = FALSE; int len; - schar_T *new_ScreenLines; - u8char_T *new_ScreenLinesUC = NULL; - u8char_T *new_ScreenLinesC[MAX_MCO]; - schar_T *new_ScreenLines2 = NULL; - int i; - sattr_T *new_ScreenAttrs; - unsigned *new_LineOffset; - char_u *new_LineWraps; - StlClickDefinition *new_tab_page_click_defs; static bool entered = false; // avoid recursiveness static bool done_outofmem_msg = false; int retry_count = 0; - const bool l_enc_utf8 = enc_utf8; - const int l_enc_dbcs = enc_dbcs; retry: - /* - * Allocation of the screen buffers is done only when the size changes and - * when Rows and Columns have been set and we have started doing full - * screen stuff. - */ + // Allocation of the screen buffers is done only when the size changes and + // when Rows and Columns have been set and we have started doing full + // screen stuff. if ((ScreenLines != NULL && Rows == screen_Rows && Columns == screen_Columns - && l_enc_utf8 == (ScreenLinesUC != NULL) - && (l_enc_dbcs == DBCS_JPNU) == (ScreenLines2 != NULL) - && p_mco == Screen_mco - ) + && ScreenLinesUC != NULL + && p_mco == Screen_mco) || Rows == 0 || Columns == 0 - || (!full_screen && ScreenLines == NULL)) + || (!full_screen && ScreenLines == NULL)) { return; + } /* * It's possible that we produce an out-of-memory message below, which @@ -6140,22 +6132,22 @@ retry: if (aucmd_win != NULL) win_free_lsize(aucmd_win); - new_ScreenLines = xmalloc((size_t)((Rows + 1) * Columns * sizeof(schar_T))); - memset(new_ScreenLinesC, 0, sizeof(u8char_T *) * MAX_MCO); - if (l_enc_utf8) { - new_ScreenLinesUC = xmalloc( - (size_t)((Rows + 1) * Columns * sizeof(u8char_T))); - for (i = 0; i < p_mco; ++i) - new_ScreenLinesC[i] = xcalloc((Rows + 1) * Columns, sizeof(u8char_T)); - } - if (l_enc_dbcs == DBCS_JPNU) - new_ScreenLines2 = xmalloc( - (size_t)((Rows + 1) * Columns * sizeof(schar_T))); - new_ScreenAttrs = xmalloc((size_t)((Rows + 1) * Columns * sizeof(sattr_T))); - new_LineOffset = xmalloc((size_t)(Rows * sizeof(unsigned))); - new_LineWraps = xmalloc((size_t)(Rows * sizeof(char_u))); - new_tab_page_click_defs = xcalloc( - (size_t) Columns, sizeof(*new_tab_page_click_defs)); + schar_T *new_ScreenLines = xmalloc( + (size_t)((Rows + 1) * Columns * sizeof(*new_ScreenLines))); + u8char_T *new_ScreenLinesC[MAX_MCO]; + memset(new_ScreenLinesC, 0, sizeof(new_ScreenLinesC)); + u8char_T *new_ScreenLinesUC = xmalloc( + (size_t)((Rows + 1) * Columns * sizeof(*new_ScreenLinesUC))); + for (int i = 0; i < p_mco; i++) { + new_ScreenLinesC[i] = xcalloc( + (size_t)((Rows + 1) * Columns), sizeof(new_ScreenLinesC[0][0])); + } + sattr_T *new_ScreenAttrs = xmalloc( + (size_t)((Rows + 1) * Columns * sizeof(*new_ScreenAttrs))); + unsigned *new_LineOffset = xmalloc((size_t)(Rows * sizeof(*new_LineOffset))); + char_u *new_LineWraps = xmalloc((size_t)(Rows * sizeof(*new_LineWraps))); + StlClickDefinition *new_tab_page_click_defs = xcalloc( + (size_t)Columns, sizeof(*new_tab_page_click_defs)); FOR_ALL_TAB_WINDOWS(tp, wp) { win_alloc_lines(wp); @@ -6164,23 +6156,20 @@ retry: win_alloc_lines(aucmd_win); } - for (i = 0; i < p_mco; ++i) - if (new_ScreenLinesC[i] == NULL) + int i; + for (i = 0; i < p_mco; i++) { + if (new_ScreenLinesC[i] == NULL) { break; - if (new_ScreenLines == NULL - || (new_ScreenLinesUC == NULL || i != p_mco) - || new_ScreenAttrs == NULL - || new_LineOffset == NULL - || new_LineWraps == NULL - || new_tab_page_click_defs == NULL - || outofmem) { + } + } + if (i != p_mco) { if (ScreenLines != NULL || !done_outofmem_msg) { - /* guess the size */ + // Guess the size. do_outofmem_msg((Rows + 1) * Columns); - /* Remember we did this to avoid getting outofmem messages over - * and over again. */ - done_outofmem_msg = TRUE; + // Remember we did this to avoid getting outofmem messages over + // and over again. + done_outofmem_msg = true; } xfree(new_ScreenLines); new_ScreenLines = NULL; @@ -6190,8 +6179,6 @@ retry: xfree(new_ScreenLinesC[i]); new_ScreenLinesC[i] = NULL; } - xfree(new_ScreenLines2); - new_ScreenLines2 = NULL; xfree(new_ScreenAttrs); new_ScreenAttrs = NULL; xfree(new_LineOffset); @@ -6214,53 +6201,42 @@ retry: * executing an external command, for the GUI). */ if (!doclear) { - (void)memset(new_ScreenLines + new_row * Columns, - ' ', (size_t)Columns * sizeof(schar_T)); - if (l_enc_utf8) { - (void)memset(new_ScreenLinesUC + new_row * Columns, - 0, (size_t)Columns * sizeof(u8char_T)); - for (i = 0; i < p_mco; ++i) - (void)memset(new_ScreenLinesC[i] - + new_row * Columns, - 0, (size_t)Columns * sizeof(u8char_T)); + memset(new_ScreenLines + new_row * Columns, + ' ', (size_t)Columns * sizeof(new_ScreenLines[0])); + memset(new_ScreenLinesUC + new_row * Columns, + 0, (size_t)Columns * sizeof(new_ScreenLinesUC[0])); + for (i = 0; i < p_mco; i++) { + memset(new_ScreenLinesC[i] + new_row * Columns, + 0, (size_t)Columns * sizeof(new_ScreenLinesC[0][0])); } - if (l_enc_dbcs == DBCS_JPNU) - (void)memset(new_ScreenLines2 + new_row * Columns, - 0, (size_t)Columns * sizeof(schar_T)); - (void)memset(new_ScreenAttrs + new_row * Columns, - 0, (size_t)Columns * sizeof(sattr_T)); + memset(new_ScreenAttrs + new_row * Columns, + 0, (size_t)Columns * sizeof(new_ScreenAttrs[0])); old_row = new_row + (screen_Rows - Rows); if (old_row >= 0 && ScreenLines != NULL) { if (screen_Columns < Columns) len = screen_Columns; else len = Columns; - /* When switching to utf-8 don't copy characters, they - * may be invalid now. Also when p_mco changes. */ - if (!(l_enc_utf8 && ScreenLinesUC == NULL) - && p_mco == Screen_mco) + // When switching to utf-8 don't copy characters, they + // may be invalid now. Also when p_mco changes. + if (ScreenLinesUC != NULL && p_mco == Screen_mco) { memmove(new_ScreenLines + new_LineOffset[new_row], - ScreenLines + LineOffset[old_row], - (size_t)len * sizeof(schar_T)); - if (l_enc_utf8 && ScreenLinesUC != NULL - && p_mco == Screen_mco) { - memmove(new_ScreenLinesUC + new_LineOffset[new_row], - ScreenLinesUC + LineOffset[old_row], - (size_t)len * sizeof(u8char_T)); - for (i = 0; i < p_mco; ++i) - memmove(new_ScreenLinesC[i] - + new_LineOffset[new_row], - ScreenLinesC[i] + LineOffset[old_row], - (size_t)len * sizeof(u8char_T)); + ScreenLines + LineOffset[old_row], + (size_t)len * sizeof(new_ScreenLines[0])); } - if (ScreenLines2 != NULL) { - memmove(new_ScreenLines2 + new_LineOffset[new_row], - ScreenLines2 + LineOffset[old_row], - (size_t)len * sizeof(schar_T)); + if (ScreenLinesUC != NULL && p_mco == Screen_mco) { + memmove(new_ScreenLinesUC + new_LineOffset[new_row], + ScreenLinesUC + LineOffset[old_row], + (size_t)len * sizeof(new_ScreenLinesUC[0])); + for (i = 0; i < p_mco; i++) { + memmove(new_ScreenLinesC[i] + new_LineOffset[new_row], + ScreenLinesC[i] + LineOffset[old_row], + (size_t)len * sizeof(new_ScreenLinesC[0][0])); + } } memmove(new_ScreenAttrs + new_LineOffset[new_row], ScreenAttrs + LineOffset[old_row], - (size_t)len * sizeof(sattr_T)); + (size_t)len * sizeof(new_ScreenAttrs[0])); } } } @@ -6275,7 +6251,6 @@ retry: for (i = 0; i < p_mco; ++i) ScreenLinesC[i] = new_ScreenLinesC[i]; Screen_mco = p_mco; - ScreenLines2 = new_ScreenLines2; ScreenAttrs = new_ScreenAttrs; LineOffset = new_LineOffset; LineWraps = new_LineWraps; @@ -6309,12 +6284,10 @@ retry: void free_screenlines(void) { - int i; - xfree(ScreenLinesUC); - for (i = 0; i < Screen_mco; ++i) + for (int i = 0; i < Screen_mco; i++) { xfree(ScreenLinesC[i]); - xfree(ScreenLines2); + } xfree(ScreenLines); xfree(ScreenAttrs); xfree(LineOffset); @@ -6398,25 +6371,21 @@ static void lineclear(unsigned off, int width) */ static void linecopy(int to, int from, win_T *wp) { - unsigned off_to = LineOffset[to] + wp->w_wincol; - unsigned off_from = LineOffset[from] + wp->w_wincol; + const unsigned off_to = LineOffset[to] + wp->w_wincol; + const unsigned off_from = LineOffset[from] + wp->w_wincol; memmove(ScreenLines + off_to, ScreenLines + off_from, - wp->w_width * sizeof(schar_T)); - if (enc_utf8) { - int i; + wp->w_width * sizeof(ScreenLines[0])); - memmove(ScreenLinesUC + off_to, ScreenLinesUC + off_from, - wp->w_width * sizeof(u8char_T)); - for (i = 0; i < p_mco; ++i) - memmove(ScreenLinesC[i] + off_to, ScreenLinesC[i] + off_from, - wp->w_width * sizeof(u8char_T)); + memmove(ScreenLinesUC + off_to, ScreenLinesUC + off_from, + wp->w_width * sizeof(ScreenLinesUC[0])); + for (int i = 0; i < p_mco; i++) { + memmove(ScreenLinesC[i] + off_to, ScreenLinesC[i] + off_from, + wp->w_width * sizeof(ScreenLinesC[0])); } - if (enc_dbcs == DBCS_JPNU) - memmove(ScreenLines2 + off_to, ScreenLines2 + off_from, - wp->w_width * sizeof(schar_T)); + memmove(ScreenAttrs + off_to, ScreenAttrs + off_from, - wp->w_width * sizeof(sattr_T)); + wp->w_width * sizeof(ScreenAttrs[0])); } /* @@ -6725,16 +6694,20 @@ int showmode(void) if (p_ri) MSG_PUTS_ATTR(_(" REVERSE"), attr); MSG_PUTS_ATTR(_(" INSERT"), attr); - } else if (restart_edit == 'I') + } else if (restart_edit == 'I' || restart_edit == 'i' + || restart_edit == 'a') { MSG_PUTS_ATTR(_(" (insert)"), attr); - else if (restart_edit == 'R') + } else if (restart_edit == 'R') { MSG_PUTS_ATTR(_(" (replace)"), attr); - else if (restart_edit == 'V') + } else if (restart_edit == 'V') { MSG_PUTS_ATTR(_(" (vreplace)"), attr); - if (p_hkmap) + } + if (p_hkmap) { MSG_PUTS_ATTR(_(" Hebrew"), attr); - if (p_fkmap) + } + if (p_fkmap) { MSG_PUTS_ATTR(farsi_text_5, attr); + } if (State & LANGMAP) { if (curwin->w_p_arab) { MSG_PUTS_ATTR(_(" Arabic"), attr); diff --git a/src/nvim/search.c b/src/nvim/search.c index 1943e2ca43..84782497a0 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -1493,38 +1493,34 @@ static int check_prevcol(char_u *linep, int col, int ch, int *prevcol) * Raw string start is found at linep[startpos.col - 1]. * Return true if the matching end can be found between startpos and endpos. */ -static int find_rawstring_end(char_u *linep, pos_T *startpos, pos_T *endpos) +static bool find_rawstring_end(char_u *linep, pos_T *startpos, pos_T *endpos) { char_u *p; char_u *delim_copy; size_t delim_len; linenr_T lnum; - int found = false; - for (p = linep + startpos->col + 1; *p && *p != '('; ++p) {} + for (p = linep + startpos->col + 1; *p && *p != '('; p++) {} delim_len = (p - linep) - startpos->col - 1; delim_copy = vim_strnsave(linep + startpos->col + 1, delim_len); - if (delim_copy == NULL) - return false; - for (lnum = startpos->lnum; lnum <= endpos->lnum; ++lnum) - { + bool found = false; + for (lnum = startpos->lnum; lnum <= endpos->lnum; lnum++) { char_u *line = ml_get(lnum); - for (p = line + (lnum == startpos->lnum - ? startpos->col + 1 : 0); *p; ++p) - { - if (lnum == endpos->lnum && (colnr_T)(p - line) >= endpos->col) + for (p = line + (lnum == startpos->lnum ? startpos->col + 1 : 0); *p; p++) { + if (lnum == endpos->lnum && (colnr_T)(p - line) >= endpos->col) { break; + } if (*p == ')' && p[delim_len + 1] == '"' - && STRNCMP(delim_copy, p + 1, delim_len) == 0) - { + && STRNCMP(delim_copy, p + 1, delim_len) == 0) { found = true; break; } } - if (found) + if (found) { break; + } } xfree(delim_copy); return found; @@ -3396,11 +3392,13 @@ again: goto again; } - if (do_include || r < 1) { - /* Include up to the '>'. */ - while (*get_cursor_pos_ptr() != '>') - if (inc_cursor() < 0) + if (do_include) { + // Include up to the '>'. + while (*get_cursor_pos_ptr() != '>') { + if (inc_cursor() < 0) { break; + } + } } else { char_u *c = get_cursor_pos_ptr(); // Exclude the '<' of the end tag. @@ -3944,15 +3942,15 @@ current_search ( if (VIsual_active) { orig_pos = pos = curwin->w_cursor; - /* make sure, searching further will extend the match */ - if (VIsual_active) { - if (forward) - incl(&pos); - else - decl(&pos); + // Searching further will extend the match. + if (forward) { + incl(&pos); + } else { + decl(&pos); } - } else + } else { orig_pos = pos = curwin->w_cursor; + } /* Is the pattern is zero-width? */ int one_char = is_one_char(spats[last_idx].pat, true); @@ -4017,23 +4015,22 @@ current_search ( VIsual = start_pos; curwin->w_cursor = pos; - VIsual_active = TRUE; + VIsual_active = true; VIsual_mode = 'v'; - if (VIsual_active) { - redraw_curbuf_later(INVERTED); /* update the inversion */ - if (*p_sel == 'e') { - /* Correction for exclusive selection depends on the direction. */ - if (forward && ltoreq(VIsual, curwin->w_cursor)) - inc_cursor(); - else if (!forward && ltoreq(curwin->w_cursor, VIsual)) - inc(&VIsual); + redraw_curbuf_later(INVERTED); // Update the inversion. + if (*p_sel == 'e') { + // Correction for exclusive selection depends on the direction. + if (forward && ltoreq(VIsual, curwin->w_cursor)) { + inc_cursor(); + } else if (!forward && ltoreq(curwin->w_cursor, VIsual)) { + inc(&VIsual); } - } - if (fdo_flags & FDO_SEARCH && KeyTyped) + if (fdo_flags & FDO_SEARCH && KeyTyped) { foldOpenCursor(); + } may_start_select('c'); setmouse(); diff --git a/src/nvim/shada.c b/src/nvim/shada.c index a1ce00f665..f4454504a4 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -16,12 +16,14 @@ #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/vim.h" +#include "nvim/pos.h" #include "nvim/ascii.h" #include "nvim/shada.h" #include "nvim/message.h" #include "nvim/globals.h" #include "nvim/memory.h" #include "nvim/mark.h" +#include "nvim/macros.h" #include "nvim/ops.h" #include "nvim/garray.h" #include "nvim/option.h" @@ -76,8 +78,8 @@ KHASH_SET_INIT_STR(strset) (vim_rename((char_u *)a, (char_u *)b)) #define mb_strnicmp(a, b, c) \ (mb_strnicmp((char_u *)a, (char_u *)b, c)) -#define path_shorten_fname_if_possible(b) \ - ((char *)path_shorten_fname_if_possible((char_u *)b)) +#define path_try_shorten_fname(b) \ + ((char *)path_try_shorten_fname((char_u *)b)) #define buflist_new(ffname, sfname, ...) \ (buflist_new((char_u *)ffname, (char_u *)sfname, __VA_ARGS__)) #define os_isdir(f) (os_isdir((char_u *) f)) @@ -375,7 +377,8 @@ KHASH_MAP_INIT_STR(file_marks, FileMarks) /// Before actually writing most of the data is read to this structure. typedef struct { HistoryMergerState hms[HIST_COUNT]; ///< Structures for history merging. - PossiblyFreedShadaEntry global_marks[NGLOBALMARKS]; ///< All global marks. + PossiblyFreedShadaEntry global_marks[NMARKS]; ///< Named global marks. + PossiblyFreedShadaEntry numbered_marks[EXTRA_MARKS]; ///< Numbered marks. PossiblyFreedShadaEntry registers[NUM_SAVED_REGISTERS]; ///< All registers. PossiblyFreedShadaEntry jumps[JUMPLISTSIZE]; ///< All dumped jumps. size_t jumps_size; ///< Number of jumps occupied. @@ -808,7 +811,7 @@ static int open_shada_file_for_reading(const char *const fname, /// Wrapper for closing file descriptors static void close_file(void *cookie) { - const int error = file_free(cookie, true); + const int error = file_free(cookie, !!p_fs); if (error != 0) { emsgf(_(SERR "System error while closing ShaDa file: %s"), os_strerror(error)); @@ -1397,7 +1400,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) } case kSDItemBufferList: { for (size_t i = 0; i < cur_entry.data.buffer_list.size; i++) { - char *const sfname = path_shorten_fname_if_possible( + char *const sfname = path_try_shorten_fname( cur_entry.data.buffer_list.buffers[i].fname); buf_T *const buf = buflist_new( cur_entry.data.buffer_list.buffers[i].fname, sfname, 0, @@ -2020,6 +2023,113 @@ shada_parse_msgpack_extra_bytes: return ret; } +/// Format shada entry for debugging purposes +/// +/// @param[in] entry ShaDa entry to format. +/// +/// @return string representing ShaDa entry in a static buffer. +static const char *shada_format_entry(const ShadaEntry entry) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_UNUSED FUNC_ATTR_NONNULL_RET +{ + static char ret[1024]; + ret[0] = 0; + vim_snprintf(S_LEN(ret), "[ ] ts=%" PRIu64 " "); + // ^ Space for `can_free_entry` + switch (entry.type) { + case kSDItemMissing: { + vim_snprintf_add(S_LEN(ret), "Missing"); + break; + } + case kSDItemHeader: { + vim_snprintf_add(S_LEN(ret), "Header { TODO }"); + break; + } + case kSDItemBufferList: { + vim_snprintf_add(S_LEN(ret), "BufferList { TODO }"); + break; + } + case kSDItemUnknown: { + vim_snprintf_add(S_LEN(ret), "Unknown { TODO }"); + break; + } + case kSDItemSearchPattern: { + vim_snprintf_add(S_LEN(ret), "SearchPattern { TODO }"); + break; + } + case kSDItemSubString: { + vim_snprintf_add(S_LEN(ret), "SubString { TODO }"); + break; + } + case kSDItemHistoryEntry: { + vim_snprintf_add(S_LEN(ret), "HistoryEntry { TODO }"); + break; + } + case kSDItemRegister: { + vim_snprintf_add(S_LEN(ret), "Register { TODO }"); + break; + } + case kSDItemVariable: { + vim_snprintf_add(S_LEN(ret), "Variable { TODO }"); + break; + } +#define FORMAT_MARK_ENTRY(entry_name, name_fmt, name_fmt_arg) \ + do { \ + typval_T ad_tv = { \ + .v_type = VAR_DICT, \ + .vval.v_dict = entry.data.filemark.additional_data \ + }; \ + size_t ad_len; \ + char *const ad = encode_tv2string(&ad_tv, &ad_len); \ + vim_snprintf_add( \ + S_LEN(ret), \ + entry_name " {" name_fmt " file=[%zu]\"%.512s\", " \ + "pos={l=%" PRIdLINENR ",c=%" PRIdCOLNR ",a=%" PRIdCOLNR "}, " \ + "ad={%p:[%zu]%.64s} }", \ + name_fmt_arg, \ + strlen(entry.data.filemark.fname), \ + entry.data.filemark.fname, \ + entry.data.filemark.mark.lnum, \ + entry.data.filemark.mark.col, \ + entry.data.filemark.mark.coladd, \ + entry.data.filemark.additional_data, \ + ad_len, \ + ad); \ + } while (0) + case kSDItemGlobalMark: { + FORMAT_MARK_ENTRY("GlobalMark", " name='%c',", entry.data.filemark.name); + break; + } + case kSDItemChange: { + FORMAT_MARK_ENTRY("Change", "%s", ""); + break; + } + case kSDItemLocalMark: { + FORMAT_MARK_ENTRY("LocalMark", " name='%c',", entry.data.filemark.name); + break; + } + case kSDItemJump: { + FORMAT_MARK_ENTRY("Jump", "%s", ""); + break; + } +#undef FORMAT_MARK_ENTRY + } + return ret; +} + +/// Format possibly freed shada entry for debugging purposes +/// +/// @param[in] entry ShaDa entry to format. +/// +/// @return string representing ShaDa entry in a static buffer. +static const char *shada_format_pfreed_entry( + const PossiblyFreedShadaEntry pfs_entry) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_UNUSED FUNC_ATTR_NONNULL_RET +{ + char *ret = (char *)shada_format_entry(pfs_entry.data); + ret[1] = (pfs_entry.can_free_entry ? 'T' : 'F'); + return ret; +} + /// Read and merge in ShaDa file, used when writing /// /// @param[in] sd_reader Structure containing file reader definition. @@ -2071,9 +2181,12 @@ static inline ShaDaWriteResult shada_read_when_writing( shada_free_shada_entry(&wms_entry->data); \ } \ } \ - wms_entry->can_free_entry = true; \ - wms_entry->data = (entry); \ + *wms_entry = pfs_entry; \ } while (0) + const PossiblyFreedShadaEntry pfs_entry = { + .can_free_entry = true, + .data = entry, + }; switch (entry.type) { case kSDItemMissing: { break; @@ -2125,13 +2238,49 @@ static inline ShaDaWriteResult shada_read_when_writing( break; } case kSDItemGlobalMark: { - const int idx = mark_global_index(entry.data.filemark.name); - if (idx < 0) { - ret = shada_pack_entry(packer, entry, 0); - shada_free_shada_entry(&entry); - break; + if (ascii_isdigit(entry.data.filemark.name)) { + bool processed_mark = false; + // Completely ignore numbered mark names, make a list sorted by + // timestamp. + for (size_t i = ARRAY_SIZE(wms->numbered_marks); i > 0; i--) { + ShadaEntry wms_entry = wms->numbered_marks[i - 1].data; + if (wms_entry.type != kSDItemGlobalMark) { + continue; + } + // Ignore duplicates. + if (wms_entry.timestamp == entry.timestamp + && (wms_entry.data.filemark.additional_data == NULL + && entry.data.filemark.additional_data == NULL) + && marks_equal(wms_entry.data.filemark.mark, + entry.data.filemark.mark) + && strcmp(wms_entry.data.filemark.fname, + entry.data.filemark.fname) == 0) { + shada_free_shada_entry(&entry); + processed_mark = true; + break; + } + if (wms_entry.timestamp >= entry.timestamp) { + processed_mark = true; + if (i < ARRAY_SIZE(wms->numbered_marks)) { + replace_numbered_mark(wms, i, pfs_entry); + } else { + shada_free_shada_entry(&entry); + } + break; + } + } + if (!processed_mark) { + replace_numbered_mark(wms, 0, pfs_entry); + } + } else { + const int idx = mark_global_index(entry.data.filemark.name); + if (idx < 0) { + ret = shada_pack_entry(packer, entry, 0); + shada_free_shada_entry(&entry); + break; + } + COMPARE_WITH_ENTRY(&wms->global_marks[idx], entry); } - COMPARE_WITH_ENTRY(&wms->global_marks[idx], entry); break; } case kSDItemChange: @@ -2175,8 +2324,7 @@ static inline ShaDaWriteResult shada_read_when_writing( shada_free_shada_entry(&wms_entry->data); } } - wms_entry->can_free_entry = true; - wms_entry->data = entry; + *wms_entry = pfs_entry; } } else { #define FREE_POSSIBLY_FREED_SHADA_ENTRY(entry) \ @@ -2216,6 +2364,20 @@ static inline ShaDaWriteResult shada_read_when_writing( return ret; } +/// Check whether buffer should be ignored +/// +/// @param[in] buf buf_T* to check. +/// @param[in] removable_bufs Cache of buffers ignored due to their location. +/// +/// @return true or false. +static inline bool ignore_buf(const buf_T *const buf, + khash_t(bufset) *const removable_bufs) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE +{ + return (buf->b_ffname == NULL || !buf->b_p_bl || bt_quickfix(buf) \ + || in_bufset(removable_bufs, buf)); +} + /// Get list of buffers to write to the shada file /// /// @param[in] removable_bufs Buffers which are ignored @@ -2227,11 +2389,9 @@ static inline ShadaEntry shada_get_buflist( { int max_bufs = get_shada_parameter('%'); size_t buf_count = 0; -#define IGNORE_BUF(buf)\ - (buf->b_ffname == NULL || !buf->b_p_bl || bt_quickfix(buf) \ - || in_bufset(removable_bufs, buf)) // NOLINT(whitespace/indent) FOR_ALL_BUFFERS(buf) { - if (!IGNORE_BUF(buf) && (max_bufs < 0 || buf_count < (size_t)max_bufs)) { + if (!ignore_buf(buf, removable_bufs) + && (max_bufs < 0 || buf_count < (size_t)max_bufs)) { buf_count++; } } @@ -2249,7 +2409,7 @@ static inline ShadaEntry shada_get_buflist( }; size_t i = 0; FOR_ALL_BUFFERS(buf) { - if (IGNORE_BUF(buf)) { + if (ignore_buf(buf, removable_bufs)) { continue; } if (i >= buf_count) { @@ -2263,7 +2423,6 @@ static inline ShadaEntry shada_get_buflist( i++; } -#undef IGNORE_BUF return buflist_entry; } @@ -2365,6 +2524,34 @@ static inline void shada_initialize_registers(WriteMergerState *const wms, } while (reg_iter != NULL); } +/// Replace numbered mark in WriteMergerState +/// +/// Frees the last mark, moves (including adjusting mark names) marks from idx +/// to the last-but-one one and saves the new mark at given index. +/// +/// @param[out] wms Merger state to adjust. +/// @param[in] idx Index at which new mark should be placed. +/// @param[in] entry New mark. +static inline void replace_numbered_mark(WriteMergerState *const wms, + const size_t idx, + const PossiblyFreedShadaEntry entry) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE +{ + if (ARRAY_LAST_ENTRY(wms->numbered_marks).can_free_entry) { + shada_free_shada_entry(&ARRAY_LAST_ENTRY(wms->numbered_marks).data); + } + for (size_t i = idx; i < ARRAY_SIZE(wms->numbered_marks) - 1; i++) { + if (wms->numbered_marks[i].data.type == kSDItemGlobalMark) { + wms->numbered_marks[i].data.data.filemark.name = (char)('0' + (int)i + 1); + } + } + memmove(wms->numbered_marks + idx + 1, wms->numbered_marks + idx, + sizeof(wms->numbered_marks[0]) + * (ARRAY_SIZE(wms->numbered_marks) - 1 - idx)); + wms->numbered_marks[idx] = entry; + wms->numbered_marks[idx].data.data.filemark.name = (char)('0' + (int)idx); +} + /// Write ShaDa file /// /// @param[in] sd_writer Structure containing file writer definition. @@ -2597,6 +2784,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, // Initialize global marks if (dump_global_marks) { const void *global_mark_iter = NULL; + size_t digit_mark_idx = 0; do { char name = NUL; xfmark_T fm; @@ -2619,7 +2807,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } fname = (const char *) buf->b_ffname; } - wms->global_marks[mark_global_index(name)] = (PossiblyFreedShadaEntry) { + const PossiblyFreedShadaEntry pf_entry = { .can_free_entry = false, .data = { .type = kSDItemGlobalMark, @@ -2629,11 +2817,16 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, .mark = fm.fmark.mark, .name = name, .additional_data = fm.fmark.additional_data, - .fname = (char *) fname, + .fname = (char *)fname, } } }, }; + if (ascii_isdigit(name)) { + replace_numbered_mark(wms, digit_mark_idx++, pf_entry); + } else { + wms->global_marks[mark_global_index(name)] = pf_entry; + } } while (global_mark_iter != NULL); } @@ -2715,6 +2908,26 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } } + // Update numbered marks: '0' should be replaced with the current position, + // '9' should be removed and all other marks shifted. + if (!ignore_buf(curbuf, &removable_bufs) && curwin->w_cursor.lnum != 0) { + replace_numbered_mark(wms, 0, (PossiblyFreedShadaEntry) { + .can_free_entry = false, + .data = { + .type = kSDItemGlobalMark, + .timestamp = os_time(), + .data = { + .filemark = { + .mark = curwin->w_cursor, + .name = '0', + .additional_data = NULL, + .fname = (char *)curbuf->b_ffname, + } + } + }, + }); + } + // Write the rest #define PACK_WMS_ARRAY(wms_array) \ do { \ @@ -2729,6 +2942,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } \ } while (0) PACK_WMS_ARRAY(wms->global_marks); + PACK_WMS_ARRAY(wms->numbered_marks); PACK_WMS_ARRAY(wms->registers); for (size_t i = 0; i < wms->jumps_size; i++) { if (shada_pack_pfreed_entry(packer, wms->jumps[i], max_kbyte) @@ -2823,6 +3037,7 @@ shada_write_exit: return ret; } +#undef IGNORE_BUF #undef PACK_STATIC_STR /// Write ShaDa file to a given location diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 34eb2fdf1b..84aeeda2bf 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -7368,16 +7368,24 @@ static void dump_word(slang_T *slang, char_u *word, char_u *pat, int *dir, int d if ((flags & (WF_BANNED | WF_RARE | WF_REGION)) || keepcap) { STRCPY(badword, p); STRCAT(badword, "/"); - if (keepcap) + if (keepcap) { STRCAT(badword, "="); - if (flags & WF_BANNED) + } + if (flags & WF_BANNED) { STRCAT(badword, "!"); - else if (flags & WF_RARE) + } else if (flags & WF_RARE) { STRCAT(badword, "?"); - if (flags & WF_REGION) - for (i = 0; i < 7; ++i) - if (flags & (0x10000 << i)) - sprintf((char *)badword + STRLEN(badword), "%d", i + 1); + } + if (flags & WF_REGION) { + for (i = 0; i < 7; i++) { + if (flags & (0x10000 << i)) { + const size_t badword_len = STRLEN(badword); + snprintf((char *)badword + badword_len, + sizeof(badword) - badword_len, + "%d", i + 1); + } + } + } p = badword; } diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index f5d5d408a1..dab9a2aacd 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -1644,7 +1644,7 @@ spell_read_tree ( if (len < 0) { return SP_TRUNCERROR; } - if ((size_t)len >= SIZE_MAX / sizeof(int)) { + if ((size_t)len >= SIZE_MAX / sizeof(int)) { // -V547 // Invalid length, multiply with sizeof(int) would overflow. return SP_FORMERROR; } @@ -1949,7 +1949,6 @@ static void spell_print_tree(wordnode_T *root) static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) { FILE *fd; - afffile_T *aff; char_u rline[MAXLINELEN]; char_u *line; char_u *pc = NULL; @@ -2006,11 +2005,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) do_mapline = GA_EMPTY(&spin->si_map); // Allocate and init the afffile_T structure. - aff = (afffile_T *)getroom(spin, sizeof(afffile_T), true); - if (aff == NULL) { - fclose(fd); - return NULL; - } + afffile_T *aff = getroom(spin, sizeof(*aff), true); hash_init(&aff->af_pref); hash_init(&aff->af_suff); hash_init(&aff->af_comp); @@ -2098,20 +2093,18 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) smsg(_("FLAG after using flags in %s line %d: %s"), fname, lnum, items[1]); } else if (spell_info_item(items[0]) && itemcnt > 1) { - p = (char_u *)getroom(spin, - (spin->si_info == NULL ? 0 : STRLEN(spin->si_info)) - + STRLEN(items[0]) - + STRLEN(items[1]) + 3, false); - if (p != NULL) { - if (spin->si_info != NULL) { - STRCPY(p, spin->si_info); - STRCAT(p, "\n"); - } - STRCAT(p, items[0]); - STRCAT(p, " "); - STRCAT(p, items[1]); - spin->si_info = p; + p = getroom(spin, + (spin->si_info == NULL ? 0 : STRLEN(spin->si_info)) + + STRLEN(items[0]) + + STRLEN(items[1]) + 3, false); + if (spin->si_info != NULL) { + STRCPY(p, spin->si_info); + STRCAT(p, "\n"); } + STRCAT(p, items[0]); + STRCAT(p, " "); + STRCAT(p, items[1]); + spin->si_info = p; } else if (is_aff_rule(items, itemcnt, "MIDWORD", 2) && midword == NULL) { midword = getroom_save(spin, items[1]); @@ -2291,14 +2284,12 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) fname, lnum, items[1]); } else { // New affix letter. - cur_aff = (affheader_T *)getroom(spin, - sizeof(affheader_T), true); - if (cur_aff == NULL) - break; + cur_aff = getroom(spin, sizeof(*cur_aff), true); cur_aff->ah_flag = affitem2flag(aff->af_flagtype, items[1], - fname, lnum); - if (cur_aff->ah_flag == 0 || STRLEN(items[1]) >= AH_KEY_LEN) + fname, lnum); + if (cur_aff->ah_flag == 0 || STRLEN(items[1]) >= AH_KEY_LEN) { break; + } if (cur_aff->ah_flag == aff->af_bad || cur_aff->ah_flag == aff->af_rare || cur_aff->ah_flag == aff->af_keepcase @@ -2306,11 +2297,12 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) || cur_aff->ah_flag == aff->af_circumfix || cur_aff->ah_flag == aff->af_nosuggest || cur_aff->ah_flag == aff->af_needcomp - || cur_aff->ah_flag == aff->af_comproot) + || cur_aff->ah_flag == aff->af_comproot) { smsg(_("Affix also used for " "BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST" "in %s line %d: %s"), - fname, lnum, items[1]); + fname, lnum, items[1]); + } STRCPY(cur_aff->ah_key, items[1]); hash_add(tp, cur_aff->ah_key); @@ -2372,11 +2364,8 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) smsg(_(e_afftrailing), fname, lnum, items[lasti]); // New item for an affix letter. - --aff_todo; - aff_entry = (affentry_T *)getroom(spin, - sizeof(affentry_T), true); - if (aff_entry == NULL) - break; + aff_todo--; + aff_entry = getroom(spin, sizeof(*aff_entry), true); if (STRCMP(items[2], "0") != 0) aff_entry->ae_chop = getroom_save(spin, items[2]); @@ -2848,12 +2837,10 @@ static void process_compflags(spellinfo_T *spin, afffile_T *aff, char_u *compfla // the existing ID. Otherwise add a new entry. STRLCPY(key, prevp, p - prevp + 1); hi = hash_find(&aff->af_comp, key); - if (!HASHITEM_EMPTY(hi)) + if (!HASHITEM_EMPTY(hi)) { id = HI2CI(hi)->ci_newID; - else { - ci = (compitem_T *)getroom(spin, sizeof(compitem_T), true); - if (ci == NULL) - break; + } else { + ci = getroom(spin, sizeof(compitem_T), true); STRCPY(ci->ci_key, key); ci->ci_flag = flag; // Avoid using a flag ID that has a special meaning in a @@ -3737,12 +3724,8 @@ static void *getroom(spellinfo_T *spin, size_t len, bool align) // Returns NULL when out of memory. static char_u *getroom_save(spellinfo_T *spin, char_u *s) { - char_u *sc; - - sc = (char_u *)getroom(spin, STRLEN(s) + 1, false); - if (sc != NULL) - STRCPY(sc, s); - return sc; + const size_t s_size = STRLEN(s) + 1; + return memcpy(getroom(spin, s_size, false), s, s_size); } @@ -3761,6 +3744,7 @@ static void free_blocks(sblock_T *bl) // Allocate the root of a word tree. // Returns NULL when out of memory. static wordnode_T *wordtree_alloc(spellinfo_T *spin) + FUNC_ATTR_NONNULL_RET { return (wordnode_T *)getroom(spin, sizeof(wordnode_T), true); } @@ -4794,8 +4778,6 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang) // We use si_foldroot for the soundfolded trie. spin->si_foldroot = wordtree_alloc(spin); - if (spin->si_foldroot == NULL) - return FAIL; // Let tree_add_word() know we're adding to the soundfolded tree spin->si_sugtree = true; @@ -5183,12 +5165,6 @@ mkspell ( spin.si_foldroot = wordtree_alloc(&spin); spin.si_keeproot = wordtree_alloc(&spin); spin.si_prefroot = wordtree_alloc(&spin); - if (spin.si_foldroot == NULL - || spin.si_keeproot == NULL - || spin.si_prefroot == NULL) { - free_blocks(spin.si_blocks); - goto theend; - } // When not producing a .add.spl file clear the character table when // we encounter one in the .aff file. This means we dump the current diff --git a/src/nvim/strings.c b/src/nvim/strings.c index e3f6a8cbf6..b3a0e4816b 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -1145,8 +1145,8 @@ int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap, f, uarg); break; } - assert(str_arg_l < sizeof(tmp)); } + assert(str_arg_l < sizeof(tmp)); // include the optional minus sign and possible "0x" in the region // before the zero padding insertion point @@ -1376,16 +1376,14 @@ int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap, } // 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; + 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 diff --git a/src/nvim/strings.h b/src/nvim/strings.h index 59b8701a3f..f2876c6307 100644 --- a/src/nvim/strings.h +++ b/src/nvim/strings.h @@ -3,10 +3,28 @@ #include <stdbool.h> #include <stdarg.h> +#include <string.h> #include "nvim/types.h" #include "nvim/eval/typval.h" +/// Append string to string and return pointer to the next byte +/// +/// Unlike strcat, this one does *not* add NUL byte and returns pointer to the +/// past of the added string. +/// +/// @param[out] dst String to append to. +/// @param[in] src String to append. +/// +/// @return pointer to the byte just past the appended byte. +static inline char *strappend(char *const dst, const char *const src) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_RET +{ + const size_t src_len = strlen(src); + return (char *)memmove(dst, src, src_len) + src_len; +} + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "strings.h.generated.h" #endif diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index e2476f9b0b..26de519f3c 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -451,9 +451,10 @@ void syntax_start(win_T *wp, linenr_T lnum) if (INVALID_STATE(¤t_state) && syn_block->b_sst_array != NULL) { /* Find last valid saved state before start_lnum. */ for (p = syn_block->b_sst_first; p != NULL; p = p->sst_next) { - if (p->sst_lnum > lnum) + if (p->sst_lnum > lnum) { break; - if (p->sst_lnum <= lnum && p->sst_change_lnum == 0) { + } + if (p->sst_change_lnum == 0) { last_valid = p; if (p->sst_lnum >= lnum - syn_block->b_syn_sync_minlines) last_min_valid = p; @@ -2825,9 +2826,10 @@ syn_add_end_off ( if (off > 0) { while (off-- > 0 && *p != NUL) mb_ptr_adv(p); - } else if (off < 0) { - while (off++ < 0 && base < p) + } else { + while (off++ < 0 && base < p) { mb_ptr_back(base, p); + } } col = (int)(p - base); } @@ -2870,11 +2872,13 @@ syn_add_start_off ( base = ml_get_buf(syn_buf, result->lnum, FALSE); p = base + col; if (off > 0) { - while (off-- && *p != NUL) + while (off-- && *p != NUL) { mb_ptr_adv(p); - } else if (off < 0) { - while (off++ && base < p) + } + } else { + while (off++ && base < p) { mb_ptr_back(base, p); + } } col = (int)(p - base); } @@ -4239,11 +4243,11 @@ static void syn_cmd_include(exarg_T *eap, int syncing) */ eap->argt |= (XFILE | NOSPC); separate_nextcmd(eap); - if (*eap->arg == '<' || *eap->arg == '$' || path_is_absolute_path(eap->arg)) { - /* For an absolute path, "$VIM/..." or "<sfile>.." we ":source" the - * file. Need to expand the file name first. In other cases - * ":runtime!" is used. */ - source = TRUE; + if (*eap->arg == '<' || *eap->arg == '$' || path_is_absolute(eap->arg)) { + // For an absolute path, "$VIM/..." or "<sfile>.." we ":source" the + // file. Need to expand the file name first. In other cases + // ":runtime!" is used. + source = true; if (expand_filename(eap, syn_cmdlinep, &errormsg) == FAIL) { if (errormsg != NULL) EMSG(errormsg); @@ -4549,20 +4553,21 @@ syn_cmd_region ( ++key_end; xfree(key); key = vim_strnsave_up(rest, (int)(key_end - rest)); - if (STRCMP(key, "MATCHGROUP") == 0) + if (STRCMP(key, "MATCHGROUP") == 0) { item = ITEM_MATCHGROUP; - else if (STRCMP(key, "START") == 0) + } else if (STRCMP(key, "START") == 0) { item = ITEM_START; - else if (STRCMP(key, "END") == 0) + } else if (STRCMP(key, "END") == 0) { item = ITEM_END; - else if (STRCMP(key, "SKIP") == 0) { - if (pat_ptrs[ITEM_SKIP] != NULL) { /* one skip pattern allowed */ - illegal = TRUE; + } else if (STRCMP(key, "SKIP") == 0) { + if (pat_ptrs[ITEM_SKIP] != NULL) { // One skip pattern allowed. + illegal = true; break; } item = ITEM_SKIP; - } else + } else { break; + } rest = skipwhite(key_end); if (*rest != '=') { rest = NULL; @@ -4598,21 +4603,23 @@ syn_cmd_region ( pat_ptrs[item] = ppp; ppp->pp_synp = xcalloc(1, sizeof(synpat_T)); - /* - * Get the syntax pattern and the following offset(s). - */ - /* Enable the appropriate \z specials. */ - if (item == ITEM_START) + // Get the syntax pattern and the following offset(s). + + // Enable the appropriate \z specials. + if (item == ITEM_START) { reg_do_extmatch = REX_SET; - else if (item == ITEM_SKIP || item == ITEM_END) + } else { + assert(item == ITEM_SKIP || item == ITEM_END); reg_do_extmatch = REX_USE; + } rest = get_syn_pattern(rest, ppp->pp_synp); reg_do_extmatch = 0; if (item == ITEM_END && vim_regcomp_had_eol() - && !(syn_opt_arg.flags & HL_EXCLUDENL)) + && !(syn_opt_arg.flags & HL_EXCLUDENL)) { ppp->pp_synp->sp_flags |= HL_HAS_EOL; + } ppp->pp_matchgroup_id = matchgroup_id; - ++pat_count; + pat_count++; } } xfree(key); @@ -5321,18 +5328,19 @@ get_id_list ( for (int i = highlight_ga.ga_len; --i >= 0; ) { if (vim_regexec(®match, HL_TABLE()[i].sg_name, (colnr_T)0)) { if (round == 2) { - /* Got more items than expected; can happen - * when adding items that match: - * "contains=a.*b,axb". - * Go back to first round */ + // Got more items than expected; can happen + // when adding items that match: + // "contains=a.*b,axb". + // Go back to first round. if (count >= total_count) { xfree(retval); round = 1; - } else - retval[count] = i + 1; + } else { + retval[count] = i + 1; // -V522 + } } - ++count; - id = -1; /* remember that we found one */ + count++; + id = -1; // Remember that we found one. } } vim_regfree(regmatch.regprog); @@ -5346,12 +5354,13 @@ get_id_list ( } if (id > 0) { if (round == 2) { - /* Got more items than expected, go back to first round */ + // Got more items than expected, go back to first round. if (count >= total_count) { xfree(retval); round = 1; - } else + } else { retval[count] = id; + } } ++count; } @@ -5721,13 +5730,9 @@ int syn_get_id( { // When the position is not after the current position and in the same // line of the same buffer, need to restart parsing. - if (wp->w_buffer != syn_buf - || lnum != current_lnum - || col < current_col) { + if (wp->w_buffer != syn_buf || lnum != current_lnum || col < current_col) { syntax_start(wp, lnum); - } else if (wp->w_buffer == syn_buf - && lnum == current_lnum - && col > current_col) { + } else if (col > current_col) { // next_match may not be correct when moving around, e.g. with the // "skip" expression in searchpair() next_match_idx = -1; @@ -5999,6 +6004,7 @@ static const char *highlight_init_both[] = { "default link QuickFixLine Search", "default link Substitute Search", "default link Whitespace NonText", + "default link MsgSeparator StatusLine", NULL }; @@ -7799,685 +7805,685 @@ const char *get_highlight_name_ext(expand_T *xp, int idx, int skip_cleared) color_name_table_T color_name_table[] = { // Colors from rgb.txt - { "AliceBlue", RGB(0xf0, 0xf8, 0xff) }, - { "AntiqueWhite", RGB(0xfa, 0xeb, 0xd7) }, - { "AntiqueWhite1", RGB(0xff, 0xef, 0xdb) }, - { "AntiqueWhite2", RGB(0xee, 0xdf, 0xcc) }, - { "AntiqueWhite3", RGB(0xcd, 0xc0, 0xb0) }, - { "AntiqueWhite4", RGB(0x8b, 0x83, 0x78) }, - { "Aqua", RGB(0x00, 0xff, 0xff) }, - { "Aquamarine", RGB(0x7f, 0xff, 0xd4) }, - { "Aquamarine1", RGB(0x7f, 0xff, 0xd4) }, - { "Aquamarine2", RGB(0x76, 0xee, 0xc6) }, - { "Aquamarine3", RGB(0x66, 0xcd, 0xaa) }, - { "Aquamarine4", RGB(0x45, 0x8b, 0x74) }, - { "Azure", RGB(0xf0, 0xff, 0xff) }, - { "Azure1", RGB(0xf0, 0xff, 0xff) }, - { "Azure2", RGB(0xe0, 0xee, 0xee) }, - { "Azure3", RGB(0xc1, 0xcd, 0xcd) }, - { "Azure4", RGB(0x83, 0x8b, 0x8b) }, - { "Beige", RGB(0xf5, 0xf5, 0xdc) }, - { "Bisque", RGB(0xff, 0xe4, 0xc4) }, - { "Bisque1", RGB(0xff, 0xe4, 0xc4) }, - { "Bisque2", RGB(0xee, 0xd5, 0xb7) }, - { "Bisque3", RGB(0xcd, 0xb7, 0x9e) }, - { "Bisque4", RGB(0x8b, 0x7d, 0x6b) }, - { "Black", RGB(0x00, 0x00, 0x00) }, - { "BlanchedAlmond", RGB(0xff, 0xeb, 0xcd) }, - { "Blue", RGB(0x00, 0x00, 0xff) }, - { "Blue1", RGB(0x0, 0x0, 0xff) }, - { "Blue2", RGB(0x0, 0x0, 0xee) }, - { "Blue3", RGB(0x0, 0x0, 0xcd) }, - { "Blue4", RGB(0x0, 0x0, 0x8b) }, - { "BlueViolet", RGB(0x8a, 0x2b, 0xe2) }, - { "Brown", RGB(0xa5, 0x2a, 0x2a) }, - { "Brown1", RGB(0xff, 0x40, 0x40) }, - { "Brown2", RGB(0xee, 0x3b, 0x3b) }, - { "Brown3", RGB(0xcd, 0x33, 0x33) }, - { "Brown4", RGB(0x8b, 0x23, 0x23) }, - { "BurlyWood", RGB(0xde, 0xb8, 0x87) }, - { "Burlywood1", RGB(0xff, 0xd3, 0x9b) }, - { "Burlywood2", RGB(0xee, 0xc5, 0x91) }, - { "Burlywood3", RGB(0xcd, 0xaa, 0x7d) }, - { "Burlywood4", RGB(0x8b, 0x73, 0x55) }, - { "CadetBlue", RGB(0x5f, 0x9e, 0xa0) }, - { "CadetBlue1", RGB(0x98, 0xf5, 0xff) }, - { "CadetBlue2", RGB(0x8e, 0xe5, 0xee) }, - { "CadetBlue3", RGB(0x7a, 0xc5, 0xcd) }, - { "CadetBlue4", RGB(0x53, 0x86, 0x8b) }, - { "ChartReuse", RGB(0x7f, 0xff, 0x00) }, - { "Chartreuse1", RGB(0x7f, 0xff, 0x0) }, - { "Chartreuse2", RGB(0x76, 0xee, 0x0) }, - { "Chartreuse3", RGB(0x66, 0xcd, 0x0) }, - { "Chartreuse4", RGB(0x45, 0x8b, 0x0) }, - { "Chocolate", RGB(0xd2, 0x69, 0x1e) }, - { "Chocolate1", RGB(0xff, 0x7f, 0x24) }, - { "Chocolate2", RGB(0xee, 0x76, 0x21) }, - { "Chocolate3", RGB(0xcd, 0x66, 0x1d) }, - { "Chocolate4", RGB(0x8b, 0x45, 0x13) }, - { "Coral", RGB(0xff, 0x7f, 0x50) }, - { "Coral1", RGB(0xff, 0x72, 0x56) }, - { "Coral2", RGB(0xee, 0x6a, 0x50) }, - { "Coral3", RGB(0xcd, 0x5b, 0x45) }, - { "Coral4", RGB(0x8b, 0x3e, 0x2f) }, - { "CornFlowerBlue", RGB(0x64, 0x95, 0xed) }, - { "Cornsilk", RGB(0xff, 0xf8, 0xdc) }, - { "Cornsilk1", RGB(0xff, 0xf8, 0xdc) }, - { "Cornsilk2", RGB(0xee, 0xe8, 0xcd) }, - { "Cornsilk3", RGB(0xcd, 0xc8, 0xb1) }, - { "Cornsilk4", RGB(0x8b, 0x88, 0x78) }, - { "Crimson", RGB(0xdc, 0x14, 0x3c) }, - { "Cyan", RGB(0x00, 0xff, 0xff) }, - { "Cyan1", RGB(0x0, 0xff, 0xff) }, - { "Cyan2", RGB(0x0, 0xee, 0xee) }, - { "Cyan3", RGB(0x0, 0xcd, 0xcd) }, - { "Cyan4", RGB(0x0, 0x8b, 0x8b) }, - { "DarkBlue", RGB(0x00, 0x00, 0x8b) }, - { "DarkCyan", RGB(0x00, 0x8b, 0x8b) }, - { "DarkGoldenRod", RGB(0xb8, 0x86, 0x0b) }, - { "DarkGoldenrod1", RGB(0xff, 0xb9, 0xf) }, - { "DarkGoldenrod2", RGB(0xee, 0xad, 0xe) }, - { "DarkGoldenrod3", RGB(0xcd, 0x95, 0xc) }, - { "DarkGoldenrod4", RGB(0x8b, 0x65, 0x8) }, - { "DarkGray", RGB(0xa9, 0xa9, 0xa9) }, - { "DarkGreen", RGB(0x00, 0x64, 0x00) }, - { "DarkGrey", RGB(0xa9, 0xa9, 0xa9) }, - { "DarkKhaki", RGB(0xbd, 0xb7, 0x6b) }, - { "DarkMagenta", RGB(0x8b, 0x00, 0x8b) }, - { "DarkOliveGreen", RGB(0x55, 0x6b, 0x2f) }, - { "DarkOliveGreen1", RGB(0xca, 0xff, 0x70) }, - { "DarkOliveGreen2", RGB(0xbc, 0xee, 0x68) }, - { "DarkOliveGreen3", RGB(0xa2, 0xcd, 0x5a) }, - { "DarkOliveGreen4", RGB(0x6e, 0x8b, 0x3d) }, - { "DarkOrange", RGB(0xff, 0x8c, 0x00) }, - { "DarkOrange1", RGB(0xff, 0x7f, 0x0) }, - { "DarkOrange2", RGB(0xee, 0x76, 0x0) }, - { "DarkOrange3", RGB(0xcd, 0x66, 0x0) }, - { "DarkOrange4", RGB(0x8b, 0x45, 0x0) }, - { "DarkOrchid", RGB(0x99, 0x32, 0xcc) }, - { "DarkOrchid1", RGB(0xbf, 0x3e, 0xff) }, - { "DarkOrchid2", RGB(0xb2, 0x3a, 0xee) }, - { "DarkOrchid3", RGB(0x9a, 0x32, 0xcd) }, - { "DarkOrchid4", RGB(0x68, 0x22, 0x8b) }, - { "DarkRed", RGB(0x8b, 0x00, 0x00) }, - { "DarkSalmon", RGB(0xe9, 0x96, 0x7a) }, - { "DarkSeaGreen", RGB(0x8f, 0xbc, 0x8f) }, - { "DarkSeaGreen1", RGB(0xc1, 0xff, 0xc1) }, - { "DarkSeaGreen2", RGB(0xb4, 0xee, 0xb4) }, - { "DarkSeaGreen3", RGB(0x9b, 0xcd, 0x9b) }, - { "DarkSeaGreen4", RGB(0x69, 0x8b, 0x69) }, - { "DarkSlateBlue", RGB(0x48, 0x3d, 0x8b) }, - { "DarkSlateGray", RGB(0x2f, 0x4f, 0x4f) }, - { "DarkSlateGray1", RGB(0x97, 0xff, 0xff) }, - { "DarkSlateGray2", RGB(0x8d, 0xee, 0xee) }, - { "DarkSlateGray3", RGB(0x79, 0xcd, 0xcd) }, - { "DarkSlateGray4", RGB(0x52, 0x8b, 0x8b) }, - { "DarkSlateGrey", RGB(0x2f, 0x4f, 0x4f) }, - { "DarkTurquoise", RGB(0x00, 0xce, 0xd1) }, - { "DarkViolet", RGB(0x94, 0x00, 0xd3) }, - { "DarkYellow", RGB(0xbb, 0xbb, 0x00) }, - { "DeepPink", RGB(0xff, 0x14, 0x93) }, - { "DeepPink1", RGB(0xff, 0x14, 0x93) }, - { "DeepPink2", RGB(0xee, 0x12, 0x89) }, - { "DeepPink3", RGB(0xcd, 0x10, 0x76) }, - { "DeepPink4", RGB(0x8b, 0xa, 0x50) }, - { "DeepSkyBlue", RGB(0x00, 0xbf, 0xff) }, - { "DeepSkyBlue1", RGB(0x0, 0xbf, 0xff) }, - { "DeepSkyBlue2", RGB(0x0, 0xb2, 0xee) }, - { "DeepSkyBlue3", RGB(0x0, 0x9a, 0xcd) }, - { "DeepSkyBlue4", RGB(0x0, 0x68, 0x8b) }, - { "DimGray", RGB(0x69, 0x69, 0x69) }, - { "DimGrey", RGB(0x69, 0x69, 0x69) }, - { "DodgerBlue", RGB(0x1e, 0x90, 0xff) }, - { "DodgerBlue1", RGB(0x1e, 0x90, 0xff) }, - { "DodgerBlue2", RGB(0x1c, 0x86, 0xee) }, - { "DodgerBlue3", RGB(0x18, 0x74, 0xcd) }, - { "DodgerBlue4", RGB(0x10, 0x4e, 0x8b) }, - { "Firebrick", RGB(0xb2, 0x22, 0x22) }, - { "Firebrick1", RGB(0xff, 0x30, 0x30) }, - { "Firebrick2", RGB(0xee, 0x2c, 0x2c) }, - { "Firebrick3", RGB(0xcd, 0x26, 0x26) }, - { "Firebrick4", RGB(0x8b, 0x1a, 0x1a) }, - { "FloralWhite", RGB(0xff, 0xfa, 0xf0) }, - { "ForestGreen", RGB(0x22, 0x8b, 0x22) }, - { "Fuchsia", RGB(0xff, 0x00, 0xff) }, - { "Gainsboro", RGB(0xdc, 0xdc, 0xdc) }, - { "GhostWhite", RGB(0xf8, 0xf8, 0xff) }, - { "Gold", RGB(0xff, 0xd7, 0x00) }, - { "Gold1", RGB(0xff, 0xd7, 0x0) }, - { "Gold2", RGB(0xee, 0xc9, 0x0) }, - { "Gold3", RGB(0xcd, 0xad, 0x0) }, - { "Gold4", RGB(0x8b, 0x75, 0x0) }, - { "GoldenRod", RGB(0xda, 0xa5, 0x20) }, - { "Goldenrod1", RGB(0xff, 0xc1, 0x25) }, - { "Goldenrod2", RGB(0xee, 0xb4, 0x22) }, - { "Goldenrod3", RGB(0xcd, 0x9b, 0x1d) }, - { "Goldenrod4", RGB(0x8b, 0x69, 0x14) }, - { "Gray", RGB(0x80, 0x80, 0x80) }, - { "Gray0", RGB(0x0, 0x0, 0x0) }, - { "Gray1", RGB(0x3, 0x3, 0x3) }, - { "Gray10", RGB(0x1a, 0x1a, 0x1a) }, - { "Gray100", RGB(0xff, 0xff, 0xff) }, - { "Gray11", RGB(0x1c, 0x1c, 0x1c) }, - { "Gray12", RGB(0x1f, 0x1f, 0x1f) }, - { "Gray13", RGB(0x21, 0x21, 0x21) }, - { "Gray14", RGB(0x24, 0x24, 0x24) }, - { "Gray15", RGB(0x26, 0x26, 0x26) }, - { "Gray16", RGB(0x29, 0x29, 0x29) }, - { "Gray17", RGB(0x2b, 0x2b, 0x2b) }, - { "Gray18", RGB(0x2e, 0x2e, 0x2e) }, - { "Gray19", RGB(0x30, 0x30, 0x30) }, - { "Gray2", RGB(0x5, 0x5, 0x5) }, - { "Gray20", RGB(0x33, 0x33, 0x33) }, - { "Gray21", RGB(0x36, 0x36, 0x36) }, - { "Gray22", RGB(0x38, 0x38, 0x38) }, - { "Gray23", RGB(0x3b, 0x3b, 0x3b) }, - { "Gray24", RGB(0x3d, 0x3d, 0x3d) }, - { "Gray25", RGB(0x40, 0x40, 0x40) }, - { "Gray26", RGB(0x42, 0x42, 0x42) }, - { "Gray27", RGB(0x45, 0x45, 0x45) }, - { "Gray28", RGB(0x47, 0x47, 0x47) }, - { "Gray29", RGB(0x4a, 0x4a, 0x4a) }, - { "Gray3", RGB(0x8, 0x8, 0x8) }, - { "Gray30", RGB(0x4d, 0x4d, 0x4d) }, - { "Gray31", RGB(0x4f, 0x4f, 0x4f) }, - { "Gray32", RGB(0x52, 0x52, 0x52) }, - { "Gray33", RGB(0x54, 0x54, 0x54) }, - { "Gray34", RGB(0x57, 0x57, 0x57) }, - { "Gray35", RGB(0x59, 0x59, 0x59) }, - { "Gray36", RGB(0x5c, 0x5c, 0x5c) }, - { "Gray37", RGB(0x5e, 0x5e, 0x5e) }, - { "Gray38", RGB(0x61, 0x61, 0x61) }, - { "Gray39", RGB(0x63, 0x63, 0x63) }, - { "Gray4", RGB(0xa, 0xa, 0xa) }, - { "Gray40", RGB(0x66, 0x66, 0x66) }, - { "Gray41", RGB(0x69, 0x69, 0x69) }, - { "Gray42", RGB(0x6b, 0x6b, 0x6b) }, - { "Gray43", RGB(0x6e, 0x6e, 0x6e) }, - { "Gray44", RGB(0x70, 0x70, 0x70) }, - { "Gray45", RGB(0x73, 0x73, 0x73) }, - { "Gray46", RGB(0x75, 0x75, 0x75) }, - { "Gray47", RGB(0x78, 0x78, 0x78) }, - { "Gray48", RGB(0x7a, 0x7a, 0x7a) }, - { "Gray49", RGB(0x7d, 0x7d, 0x7d) }, - { "Gray5", RGB(0xd, 0xd, 0xd) }, - { "Gray50", RGB(0x7f, 0x7f, 0x7f) }, - { "Gray51", RGB(0x82, 0x82, 0x82) }, - { "Gray52", RGB(0x85, 0x85, 0x85) }, - { "Gray53", RGB(0x87, 0x87, 0x87) }, - { "Gray54", RGB(0x8a, 0x8a, 0x8a) }, - { "Gray55", RGB(0x8c, 0x8c, 0x8c) }, - { "Gray56", RGB(0x8f, 0x8f, 0x8f) }, - { "Gray57", RGB(0x91, 0x91, 0x91) }, - { "Gray58", RGB(0x94, 0x94, 0x94) }, - { "Gray59", RGB(0x96, 0x96, 0x96) }, - { "Gray6", RGB(0xf, 0xf, 0xf) }, - { "Gray60", RGB(0x99, 0x99, 0x99) }, - { "Gray61", RGB(0x9c, 0x9c, 0x9c) }, - { "Gray62", RGB(0x9e, 0x9e, 0x9e) }, - { "Gray63", RGB(0xa1, 0xa1, 0xa1) }, - { "Gray64", RGB(0xa3, 0xa3, 0xa3) }, - { "Gray65", RGB(0xa6, 0xa6, 0xa6) }, - { "Gray66", RGB(0xa8, 0xa8, 0xa8) }, - { "Gray67", RGB(0xab, 0xab, 0xab) }, - { "Gray68", RGB(0xad, 0xad, 0xad) }, - { "Gray69", RGB(0xb0, 0xb0, 0xb0) }, - { "Gray7", RGB(0x12, 0x12, 0x12) }, - { "Gray70", RGB(0xb3, 0xb3, 0xb3) }, - { "Gray71", RGB(0xb5, 0xb5, 0xb5) }, - { "Gray72", RGB(0xb8, 0xb8, 0xb8) }, - { "Gray73", RGB(0xba, 0xba, 0xba) }, - { "Gray74", RGB(0xbd, 0xbd, 0xbd) }, - { "Gray75", RGB(0xbf, 0xbf, 0xbf) }, - { "Gray76", RGB(0xc2, 0xc2, 0xc2) }, - { "Gray77", RGB(0xc4, 0xc4, 0xc4) }, - { "Gray78", RGB(0xc7, 0xc7, 0xc7) }, - { "Gray79", RGB(0xc9, 0xc9, 0xc9) }, - { "Gray8", RGB(0x14, 0x14, 0x14) }, - { "Gray80", RGB(0xcc, 0xcc, 0xcc) }, - { "Gray81", RGB(0xcf, 0xcf, 0xcf) }, - { "Gray82", RGB(0xd1, 0xd1, 0xd1) }, - { "Gray83", RGB(0xd4, 0xd4, 0xd4) }, - { "Gray84", RGB(0xd6, 0xd6, 0xd6) }, - { "Gray85", RGB(0xd9, 0xd9, 0xd9) }, - { "Gray86", RGB(0xdb, 0xdb, 0xdb) }, - { "Gray87", RGB(0xde, 0xde, 0xde) }, - { "Gray88", RGB(0xe0, 0xe0, 0xe0) }, - { "Gray89", RGB(0xe3, 0xe3, 0xe3) }, - { "Gray9", RGB(0x17, 0x17, 0x17) }, - { "Gray90", RGB(0xe5, 0xe5, 0xe5) }, - { "Gray91", RGB(0xe8, 0xe8, 0xe8) }, - { "Gray92", RGB(0xeb, 0xeb, 0xeb) }, - { "Gray93", RGB(0xed, 0xed, 0xed) }, - { "Gray94", RGB(0xf0, 0xf0, 0xf0) }, - { "Gray95", RGB(0xf2, 0xf2, 0xf2) }, - { "Gray96", RGB(0xf5, 0xf5, 0xf5) }, - { "Gray97", RGB(0xf7, 0xf7, 0xf7) }, - { "Gray98", RGB(0xfa, 0xfa, 0xfa) }, - { "Gray99", RGB(0xfc, 0xfc, 0xfc) }, - { "Green", RGB(0x00, 0x80, 0x00) }, - { "Green1", RGB(0x0, 0xff, 0x0) }, - { "Green2", RGB(0x0, 0xee, 0x0) }, - { "Green3", RGB(0x0, 0xcd, 0x0) }, - { "Green4", RGB(0x0, 0x8b, 0x0) }, - { "GreenYellow", RGB(0xad, 0xff, 0x2f) }, - { "Grey", RGB(0x80, 0x80, 0x80) }, - { "Grey0", RGB(0x0, 0x0, 0x0) }, - { "Grey1", RGB(0x3, 0x3, 0x3) }, - { "Grey10", RGB(0x1a, 0x1a, 0x1a) }, - { "Grey100", RGB(0xff, 0xff, 0xff) }, - { "Grey11", RGB(0x1c, 0x1c, 0x1c) }, - { "Grey12", RGB(0x1f, 0x1f, 0x1f) }, - { "Grey13", RGB(0x21, 0x21, 0x21) }, - { "Grey14", RGB(0x24, 0x24, 0x24) }, - { "Grey15", RGB(0x26, 0x26, 0x26) }, - { "Grey16", RGB(0x29, 0x29, 0x29) }, - { "Grey17", RGB(0x2b, 0x2b, 0x2b) }, - { "Grey18", RGB(0x2e, 0x2e, 0x2e) }, - { "Grey19", RGB(0x30, 0x30, 0x30) }, - { "Grey2", RGB(0x5, 0x5, 0x5) }, - { "Grey20", RGB(0x33, 0x33, 0x33) }, - { "Grey21", RGB(0x36, 0x36, 0x36) }, - { "Grey22", RGB(0x38, 0x38, 0x38) }, - { "Grey23", RGB(0x3b, 0x3b, 0x3b) }, - { "Grey24", RGB(0x3d, 0x3d, 0x3d) }, - { "Grey25", RGB(0x40, 0x40, 0x40) }, - { "Grey26", RGB(0x42, 0x42, 0x42) }, - { "Grey27", RGB(0x45, 0x45, 0x45) }, - { "Grey28", RGB(0x47, 0x47, 0x47) }, - { "Grey29", RGB(0x4a, 0x4a, 0x4a) }, - { "Grey3", RGB(0x8, 0x8, 0x8) }, - { "Grey30", RGB(0x4d, 0x4d, 0x4d) }, - { "Grey31", RGB(0x4f, 0x4f, 0x4f) }, - { "Grey32", RGB(0x52, 0x52, 0x52) }, - { "Grey33", RGB(0x54, 0x54, 0x54) }, - { "Grey34", RGB(0x57, 0x57, 0x57) }, - { "Grey35", RGB(0x59, 0x59, 0x59) }, - { "Grey36", RGB(0x5c, 0x5c, 0x5c) }, - { "Grey37", RGB(0x5e, 0x5e, 0x5e) }, - { "Grey38", RGB(0x61, 0x61, 0x61) }, - { "Grey39", RGB(0x63, 0x63, 0x63) }, - { "Grey4", RGB(0xa, 0xa, 0xa) }, - { "Grey40", RGB(0x66, 0x66, 0x66) }, - { "Grey41", RGB(0x69, 0x69, 0x69) }, - { "Grey42", RGB(0x6b, 0x6b, 0x6b) }, - { "Grey43", RGB(0x6e, 0x6e, 0x6e) }, - { "Grey44", RGB(0x70, 0x70, 0x70) }, - { "Grey45", RGB(0x73, 0x73, 0x73) }, - { "Grey46", RGB(0x75, 0x75, 0x75) }, - { "Grey47", RGB(0x78, 0x78, 0x78) }, - { "Grey48", RGB(0x7a, 0x7a, 0x7a) }, - { "Grey49", RGB(0x7d, 0x7d, 0x7d) }, - { "Grey5", RGB(0xd, 0xd, 0xd) }, - { "Grey50", RGB(0x7f, 0x7f, 0x7f) }, - { "Grey51", RGB(0x82, 0x82, 0x82) }, - { "Grey52", RGB(0x85, 0x85, 0x85) }, - { "Grey53", RGB(0x87, 0x87, 0x87) }, - { "Grey54", RGB(0x8a, 0x8a, 0x8a) }, - { "Grey55", RGB(0x8c, 0x8c, 0x8c) }, - { "Grey56", RGB(0x8f, 0x8f, 0x8f) }, - { "Grey57", RGB(0x91, 0x91, 0x91) }, - { "Grey58", RGB(0x94, 0x94, 0x94) }, - { "Grey59", RGB(0x96, 0x96, 0x96) }, - { "Grey6", RGB(0xf, 0xf, 0xf) }, - { "Grey60", RGB(0x99, 0x99, 0x99) }, - { "Grey61", RGB(0x9c, 0x9c, 0x9c) }, - { "Grey62", RGB(0x9e, 0x9e, 0x9e) }, - { "Grey63", RGB(0xa1, 0xa1, 0xa1) }, - { "Grey64", RGB(0xa3, 0xa3, 0xa3) }, - { "Grey65", RGB(0xa6, 0xa6, 0xa6) }, - { "Grey66", RGB(0xa8, 0xa8, 0xa8) }, - { "Grey67", RGB(0xab, 0xab, 0xab) }, - { "Grey68", RGB(0xad, 0xad, 0xad) }, - { "Grey69", RGB(0xb0, 0xb0, 0xb0) }, - { "Grey7", RGB(0x12, 0x12, 0x12) }, - { "Grey70", RGB(0xb3, 0xb3, 0xb3) }, - { "Grey71", RGB(0xb5, 0xb5, 0xb5) }, - { "Grey72", RGB(0xb8, 0xb8, 0xb8) }, - { "Grey73", RGB(0xba, 0xba, 0xba) }, - { "Grey74", RGB(0xbd, 0xbd, 0xbd) }, - { "Grey75", RGB(0xbf, 0xbf, 0xbf) }, - { "Grey76", RGB(0xc2, 0xc2, 0xc2) }, - { "Grey77", RGB(0xc4, 0xc4, 0xc4) }, - { "Grey78", RGB(0xc7, 0xc7, 0xc7) }, - { "Grey79", RGB(0xc9, 0xc9, 0xc9) }, - { "Grey8", RGB(0x14, 0x14, 0x14) }, - { "Grey80", RGB(0xcc, 0xcc, 0xcc) }, - { "Grey81", RGB(0xcf, 0xcf, 0xcf) }, - { "Grey82", RGB(0xd1, 0xd1, 0xd1) }, - { "Grey83", RGB(0xd4, 0xd4, 0xd4) }, - { "Grey84", RGB(0xd6, 0xd6, 0xd6) }, - { "Grey85", RGB(0xd9, 0xd9, 0xd9) }, - { "Grey86", RGB(0xdb, 0xdb, 0xdb) }, - { "Grey87", RGB(0xde, 0xde, 0xde) }, - { "Grey88", RGB(0xe0, 0xe0, 0xe0) }, - { "Grey89", RGB(0xe3, 0xe3, 0xe3) }, - { "Grey9", RGB(0x17, 0x17, 0x17) }, - { "Grey90", RGB(0xe5, 0xe5, 0xe5) }, - { "Grey91", RGB(0xe8, 0xe8, 0xe8) }, - { "Grey92", RGB(0xeb, 0xeb, 0xeb) }, - { "Grey93", RGB(0xed, 0xed, 0xed) }, - { "Grey94", RGB(0xf0, 0xf0, 0xf0) }, - { "Grey95", RGB(0xf2, 0xf2, 0xf2) }, - { "Grey96", RGB(0xf5, 0xf5, 0xf5) }, - { "Grey97", RGB(0xf7, 0xf7, 0xf7) }, - { "Grey98", RGB(0xfa, 0xfa, 0xfa) }, - { "Grey99", RGB(0xfc, 0xfc, 0xfc) }, - { "Honeydew", RGB(0xf0, 0xff, 0xf0) }, - { "Honeydew1", RGB(0xf0, 0xff, 0xf0) }, - { "Honeydew2", RGB(0xe0, 0xee, 0xe0) }, - { "Honeydew3", RGB(0xc1, 0xcd, 0xc1) }, - { "Honeydew4", RGB(0x83, 0x8b, 0x83) }, - { "HotPink", RGB(0xff, 0x69, 0xb4) }, - { "HotPink1", RGB(0xff, 0x6e, 0xb4) }, - { "HotPink2", RGB(0xee, 0x6a, 0xa7) }, - { "HotPink3", RGB(0xcd, 0x60, 0x90) }, - { "HotPink4", RGB(0x8b, 0x3a, 0x62) }, - { "IndianRed", RGB(0xcd, 0x5c, 0x5c) }, - { "IndianRed1", RGB(0xff, 0x6a, 0x6a) }, - { "IndianRed2", RGB(0xee, 0x63, 0x63) }, - { "IndianRed3", RGB(0xcd, 0x55, 0x55) }, - { "IndianRed4", RGB(0x8b, 0x3a, 0x3a) }, - { "Indigo", RGB(0x4b, 0x00, 0x82) }, - { "Ivory", RGB(0xff, 0xff, 0xf0) }, - { "Ivory1", RGB(0xff, 0xff, 0xf0) }, - { "Ivory2", RGB(0xee, 0xee, 0xe0) }, - { "Ivory3", RGB(0xcd, 0xcd, 0xc1) }, - { "Ivory4", RGB(0x8b, 0x8b, 0x83) }, - { "Khaki", RGB(0xf0, 0xe6, 0x8c) }, - { "Khaki1", RGB(0xff, 0xf6, 0x8f) }, - { "Khaki2", RGB(0xee, 0xe6, 0x85) }, - { "Khaki3", RGB(0xcd, 0xc6, 0x73) }, - { "Khaki4", RGB(0x8b, 0x86, 0x4e) }, - { "Lavender", RGB(0xe6, 0xe6, 0xfa) }, - { "LavenderBlush", RGB(0xff, 0xf0, 0xf5) }, - { "LavenderBlush1", RGB(0xff, 0xf0, 0xf5) }, - { "LavenderBlush2", RGB(0xee, 0xe0, 0xe5) }, - { "LavenderBlush3", RGB(0xcd, 0xc1, 0xc5) }, - { "LavenderBlush4", RGB(0x8b, 0x83, 0x86) }, - { "LawnGreen", RGB(0x7c, 0xfc, 0x00) }, - { "LemonChiffon", RGB(0xff, 0xfa, 0xcd) }, - { "LemonChiffon1", RGB(0xff, 0xfa, 0xcd) }, - { "LemonChiffon2", RGB(0xee, 0xe9, 0xbf) }, - { "LemonChiffon3", RGB(0xcd, 0xc9, 0xa5) }, - { "LemonChiffon4", RGB(0x8b, 0x89, 0x70) }, - { "LightBlue", RGB(0xad, 0xd8, 0xe6) }, - { "LightBlue1", RGB(0xbf, 0xef, 0xff) }, - { "LightBlue2", RGB(0xb2, 0xdf, 0xee) }, - { "LightBlue3", RGB(0x9a, 0xc0, 0xcd) }, - { "LightBlue4", RGB(0x68, 0x83, 0x8b) }, - { "LightCoral", RGB(0xf0, 0x80, 0x80) }, - { "LightCyan", RGB(0xe0, 0xff, 0xff) }, - { "LightCyan1", RGB(0xe0, 0xff, 0xff) }, - { "LightCyan2", RGB(0xd1, 0xee, 0xee) }, - { "LightCyan3", RGB(0xb4, 0xcd, 0xcd) }, - { "LightCyan4", RGB(0x7a, 0x8b, 0x8b) }, - { "LightGoldenrod", RGB(0xee, 0xdd, 0x82) }, - { "LightGoldenrod1", RGB(0xff, 0xec, 0x8b) }, - { "LightGoldenrod2", RGB(0xee, 0xdc, 0x82) }, - { "LightGoldenrod3", RGB(0xcd, 0xbe, 0x70) }, - { "LightGoldenrod4", RGB(0x8b, 0x81, 0x4c) }, - { "LightGoldenRodYellow", RGB(0xfa, 0xfa, 0xd2) }, - { "LightGray", RGB(0xd3, 0xd3, 0xd3) }, - { "LightGreen", RGB(0x90, 0xee, 0x90) }, - { "LightGrey", RGB(0xd3, 0xd3, 0xd3) }, - { "LightMagenta", RGB(0xff, 0xbb, 0xff) }, - { "LightPink", RGB(0xff, 0xb6, 0xc1) }, - { "LightPink1", RGB(0xff, 0xae, 0xb9) }, - { "LightPink2", RGB(0xee, 0xa2, 0xad) }, - { "LightPink3", RGB(0xcd, 0x8c, 0x95) }, - { "LightPink4", RGB(0x8b, 0x5f, 0x65) }, - { "LightRed", RGB(0xff, 0xbb, 0xbb) }, - { "LightSalmon", RGB(0xff, 0xa0, 0x7a) }, - { "LightSalmon1", RGB(0xff, 0xa0, 0x7a) }, - { "LightSalmon2", RGB(0xee, 0x95, 0x72) }, - { "LightSalmon3", RGB(0xcd, 0x81, 0x62) }, - { "LightSalmon4", RGB(0x8b, 0x57, 0x42) }, - { "LightSeaGreen", RGB(0x20, 0xb2, 0xaa) }, - { "LightSkyBlue", RGB(0x87, 0xce, 0xfa) }, - { "LightSkyBlue1", RGB(0xb0, 0xe2, 0xff) }, - { "LightSkyBlue2", RGB(0xa4, 0xd3, 0xee) }, - { "LightSkyBlue3", RGB(0x8d, 0xb6, 0xcd) }, - { "LightSkyBlue4", RGB(0x60, 0x7b, 0x8b) }, - { "LightSlateBlue", RGB(0x84, 0x70, 0xff) }, - { "LightSlateGray", RGB(0x77, 0x88, 0x99) }, - { "LightSlateGrey", RGB(0x77, 0x88, 0x99) }, - { "LightSteelBlue", RGB(0xb0, 0xc4, 0xde) }, - { "LightSteelBlue1", RGB(0xca, 0xe1, 0xff) }, - { "LightSteelBlue2", RGB(0xbc, 0xd2, 0xee) }, - { "LightSteelBlue3", RGB(0xa2, 0xb5, 0xcd) }, - { "LightSteelBlue4", RGB(0x6e, 0x7b, 0x8b) }, - { "LightYellow", RGB(0xff, 0xff, 0xe0) }, - { "LightYellow1", RGB(0xff, 0xff, 0xe0) }, - { "LightYellow2", RGB(0xee, 0xee, 0xd1) }, - { "LightYellow3", RGB(0xcd, 0xcd, 0xb4) }, - { "LightYellow4", RGB(0x8b, 0x8b, 0x7a) }, - { "Lime", RGB(0x00, 0xff, 0x00) }, - { "LimeGreen", RGB(0x32, 0xcd, 0x32) }, - { "Linen", RGB(0xfa, 0xf0, 0xe6) }, - { "Magenta", RGB(0xff, 0x00, 0xff) }, - { "Magenta1", RGB(0xff, 0x0, 0xff) }, - { "Magenta2", RGB(0xee, 0x0, 0xee) }, - { "Magenta3", RGB(0xcd, 0x0, 0xcd) }, - { "Magenta4", RGB(0x8b, 0x0, 0x8b) }, - { "Maroon", RGB(0x80, 0x00, 0x00) }, - { "Maroon1", RGB(0xff, 0x34, 0xb3) }, - { "Maroon2", RGB(0xee, 0x30, 0xa7) }, - { "Maroon3", RGB(0xcd, 0x29, 0x90) }, - { "Maroon4", RGB(0x8b, 0x1c, 0x62) }, - { "MediumAquamarine", RGB(0x66, 0xcd, 0xaa) }, - { "MediumBlue", RGB(0x00, 0x00, 0xcd) }, - { "MediumOrchid", RGB(0xba, 0x55, 0xd3) }, - { "MediumOrchid1", RGB(0xe0, 0x66, 0xff) }, - { "MediumOrchid2", RGB(0xd1, 0x5f, 0xee) }, - { "MediumOrchid3", RGB(0xb4, 0x52, 0xcd) }, - { "MediumOrchid4", RGB(0x7a, 0x37, 0x8b) }, - { "MediumPurple", RGB(0x93, 0x70, 0xdb) }, - { "MediumPurple1", RGB(0xab, 0x82, 0xff) }, - { "MediumPurple2", RGB(0x9f, 0x79, 0xee) }, - { "MediumPurple3", RGB(0x89, 0x68, 0xcd) }, - { "MediumPurple4", RGB(0x5d, 0x47, 0x8b) }, - { "MediumSeaGreen", RGB(0x3c, 0xb3, 0x71) }, - { "MediumSlateBlue", RGB(0x7b, 0x68, 0xee) }, - { "MediumSpringGreen", RGB(0x00, 0xfa, 0x9a) }, - { "MediumTurquoise", RGB(0x48, 0xd1, 0xcc) }, - { "MediumVioletRed", RGB(0xc7, 0x15, 0x85) }, - { "MidnightBlue", RGB(0x19, 0x19, 0x70) }, - { "MintCream", RGB(0xf5, 0xff, 0xfa) }, - { "MistyRose", RGB(0xff, 0xe4, 0xe1) }, - { "MistyRose1", RGB(0xff, 0xe4, 0xe1) }, - { "MistyRose2", RGB(0xee, 0xd5, 0xd2) }, - { "MistyRose3", RGB(0xcd, 0xb7, 0xb5) }, - { "MistyRose4", RGB(0x8b, 0x7d, 0x7b) }, - { "Moccasin", RGB(0xff, 0xe4, 0xb5) }, - { "NavajoWhite", RGB(0xff, 0xde, 0xad) }, - { "NavajoWhite1", RGB(0xff, 0xde, 0xad) }, - { "NavajoWhite2", RGB(0xee, 0xcf, 0xa1) }, - { "NavajoWhite3", RGB(0xcd, 0xb3, 0x8b) }, - { "NavajoWhite4", RGB(0x8b, 0x79, 0x5e) }, - { "Navy", RGB(0x00, 0x00, 0x80) }, - { "NavyBlue", RGB(0x0, 0x0, 0x80) }, - { "OldLace", RGB(0xfd, 0xf5, 0xe6) }, - { "Olive", RGB(0x80, 0x80, 0x00) }, - { "OliveDrab", RGB(0x6b, 0x8e, 0x23) }, - { "OliveDrab1", RGB(0xc0, 0xff, 0x3e) }, - { "OliveDrab2", RGB(0xb3, 0xee, 0x3a) }, - { "OliveDrab3", RGB(0x9a, 0xcd, 0x32) }, - { "OliveDrab4", RGB(0x69, 0x8b, 0x22) }, - { "Orange", RGB(0xff, 0xa5, 0x00) }, - { "Orange1", RGB(0xff, 0xa5, 0x0) }, - { "Orange2", RGB(0xee, 0x9a, 0x0) }, - { "Orange3", RGB(0xcd, 0x85, 0x0) }, - { "Orange4", RGB(0x8b, 0x5a, 0x0) }, - { "OrangeRed", RGB(0xff, 0x45, 0x00) }, - { "OrangeRed1", RGB(0xff, 0x45, 0x0) }, - { "OrangeRed2", RGB(0xee, 0x40, 0x0) }, - { "OrangeRed3", RGB(0xcd, 0x37, 0x0) }, - { "OrangeRed4", RGB(0x8b, 0x25, 0x0) }, - { "Orchid", RGB(0xda, 0x70, 0xd6) }, - { "Orchid1", RGB(0xff, 0x83, 0xfa) }, - { "Orchid2", RGB(0xee, 0x7a, 0xe9) }, - { "Orchid3", RGB(0xcd, 0x69, 0xc9) }, - { "Orchid4", RGB(0x8b, 0x47, 0x89) }, - { "PaleGoldenRod", RGB(0xee, 0xe8, 0xaa) }, - { "PaleGreen", RGB(0x98, 0xfb, 0x98) }, - { "PaleGreen1", RGB(0x9a, 0xff, 0x9a) }, - { "PaleGreen2", RGB(0x90, 0xee, 0x90) }, - { "PaleGreen3", RGB(0x7c, 0xcd, 0x7c) }, - { "PaleGreen4", RGB(0x54, 0x8b, 0x54) }, - { "PaleTurquoise", RGB(0xaf, 0xee, 0xee) }, - { "PaleTurquoise1", RGB(0xbb, 0xff, 0xff) }, - { "PaleTurquoise2", RGB(0xae, 0xee, 0xee) }, - { "PaleTurquoise3", RGB(0x96, 0xcd, 0xcd) }, - { "PaleTurquoise4", RGB(0x66, 0x8b, 0x8b) }, - { "PaleVioletRed", RGB(0xdb, 0x70, 0x93) }, - { "PaleVioletRed1", RGB(0xff, 0x82, 0xab) }, - { "PaleVioletRed2", RGB(0xee, 0x79, 0x9f) }, - { "PaleVioletRed3", RGB(0xcd, 0x68, 0x89) }, - { "PaleVioletRed4", RGB(0x8b, 0x47, 0x5d) }, - { "PapayaWhip", RGB(0xff, 0xef, 0xd5) }, - { "PeachPuff", RGB(0xff, 0xda, 0xb9) }, - { "PeachPuff1", RGB(0xff, 0xda, 0xb9) }, - { "PeachPuff2", RGB(0xee, 0xcb, 0xad) }, - { "PeachPuff3", RGB(0xcd, 0xaf, 0x95) }, - { "PeachPuff4", RGB(0x8b, 0x77, 0x65) }, - { "Peru", RGB(0xcd, 0x85, 0x3f) }, - { "Pink", RGB(0xff, 0xc0, 0xcb) }, - { "Pink1", RGB(0xff, 0xb5, 0xc5) }, - { "Pink2", RGB(0xee, 0xa9, 0xb8) }, - { "Pink3", RGB(0xcd, 0x91, 0x9e) }, - { "Pink4", RGB(0x8b, 0x63, 0x6c) }, - { "Plum", RGB(0xdd, 0xa0, 0xdd) }, - { "Plum1", RGB(0xff, 0xbb, 0xff) }, - { "Plum2", RGB(0xee, 0xae, 0xee) }, - { "Plum3", RGB(0xcd, 0x96, 0xcd) }, - { "Plum4", RGB(0x8b, 0x66, 0x8b) }, - { "PowderBlue", RGB(0xb0, 0xe0, 0xe6) }, - { "Purple", RGB(0x80, 0x00, 0x80) }, - { "Purple1", RGB(0x9b, 0x30, 0xff) }, - { "Purple2", RGB(0x91, 0x2c, 0xee) }, - { "Purple3", RGB(0x7d, 0x26, 0xcd) }, - { "Purple4", RGB(0x55, 0x1a, 0x8b) }, - { "RebeccaPurple", RGB(0x66, 0x33, 0x99) }, - { "Red", RGB(0xff, 0x00, 0x00) }, - { "Red1", RGB(0xff, 0x0, 0x0) }, - { "Red2", RGB(0xee, 0x0, 0x0) }, - { "Red3", RGB(0xcd, 0x0, 0x0) }, - { "Red4", RGB(0x8b, 0x0, 0x0) }, - { "RosyBrown", RGB(0xbc, 0x8f, 0x8f) }, - { "RosyBrown1", RGB(0xff, 0xc1, 0xc1) }, - { "RosyBrown2", RGB(0xee, 0xb4, 0xb4) }, - { "RosyBrown3", RGB(0xcd, 0x9b, 0x9b) }, - { "RosyBrown4", RGB(0x8b, 0x69, 0x69) }, - { "RoyalBlue", RGB(0x41, 0x69, 0xe1) }, - { "RoyalBlue1", RGB(0x48, 0x76, 0xff) }, - { "RoyalBlue2", RGB(0x43, 0x6e, 0xee) }, - { "RoyalBlue3", RGB(0x3a, 0x5f, 0xcd) }, - { "RoyalBlue4", RGB(0x27, 0x40, 0x8b) }, - { "SaddleBrown", RGB(0x8b, 0x45, 0x13) }, - { "Salmon", RGB(0xfa, 0x80, 0x72) }, - { "Salmon1", RGB(0xff, 0x8c, 0x69) }, - { "Salmon2", RGB(0xee, 0x82, 0x62) }, - { "Salmon3", RGB(0xcd, 0x70, 0x54) }, - { "Salmon4", RGB(0x8b, 0x4c, 0x39) }, - { "SandyBrown", RGB(0xf4, 0xa4, 0x60) }, - { "SeaGreen", RGB(0x2e, 0x8b, 0x57) }, - { "SeaGreen1", RGB(0x54, 0xff, 0x9f) }, - { "SeaGreen2", RGB(0x4e, 0xee, 0x94) }, - { "SeaGreen3", RGB(0x43, 0xcd, 0x80) }, - { "SeaGreen4", RGB(0x2e, 0x8b, 0x57) }, - { "SeaShell", RGB(0xff, 0xf5, 0xee) }, - { "Seashell1", RGB(0xff, 0xf5, 0xee) }, - { "Seashell2", RGB(0xee, 0xe5, 0xde) }, - { "Seashell3", RGB(0xcd, 0xc5, 0xbf) }, - { "Seashell4", RGB(0x8b, 0x86, 0x82) }, - { "Sienna", RGB(0xa0, 0x52, 0x2d) }, - { "Sienna1", RGB(0xff, 0x82, 0x47) }, - { "Sienna2", RGB(0xee, 0x79, 0x42) }, - { "Sienna3", RGB(0xcd, 0x68, 0x39) }, - { "Sienna4", RGB(0x8b, 0x47, 0x26) }, - { "Silver", RGB(0xc0, 0xc0, 0xc0) }, - { "SkyBlue", RGB(0x87, 0xce, 0xeb) }, - { "SkyBlue1", RGB(0x87, 0xce, 0xff) }, - { "SkyBlue2", RGB(0x7e, 0xc0, 0xee) }, - { "SkyBlue3", RGB(0x6c, 0xa6, 0xcd) }, - { "SkyBlue4", RGB(0x4a, 0x70, 0x8b) }, - { "SlateBlue", RGB(0x6a, 0x5a, 0xcd) }, - { "SlateBlue1", RGB(0x83, 0x6f, 0xff) }, - { "SlateBlue2", RGB(0x7a, 0x67, 0xee) }, - { "SlateBlue3", RGB(0x69, 0x59, 0xcd) }, - { "SlateBlue4", RGB(0x47, 0x3c, 0x8b) }, - { "SlateGray", RGB(0x70, 0x80, 0x90) }, - { "SlateGray1", RGB(0xc6, 0xe2, 0xff) }, - { "SlateGray2", RGB(0xb9, 0xd3, 0xee) }, - { "SlateGray3", RGB(0x9f, 0xb6, 0xcd) }, - { "SlateGray4", RGB(0x6c, 0x7b, 0x8b) }, - { "SlateGrey", RGB(0x70, 0x80, 0x90) }, - { "Snow", RGB(0xff, 0xfa, 0xfa) }, - { "Snow1", RGB(0xff, 0xfa, 0xfa) }, - { "Snow2", RGB(0xee, 0xe9, 0xe9) }, - { "Snow3", RGB(0xcd, 0xc9, 0xc9) }, - { "Snow4", RGB(0x8b, 0x89, 0x89) }, - { "SpringGreen", RGB(0x00, 0xff, 0x7f) }, - { "SpringGreen1", RGB(0x0, 0xff, 0x7f) }, - { "SpringGreen2", RGB(0x0, 0xee, 0x76) }, - { "SpringGreen3", RGB(0x0, 0xcd, 0x66) }, - { "SpringGreen4", RGB(0x0, 0x8b, 0x45) }, - { "SteelBlue", RGB(0x46, 0x82, 0xb4) }, - { "SteelBlue1", RGB(0x63, 0xb8, 0xff) }, - { "SteelBlue2", RGB(0x5c, 0xac, 0xee) }, - { "SteelBlue3", RGB(0x4f, 0x94, 0xcd) }, - { "SteelBlue4", RGB(0x36, 0x64, 0x8b) }, - { "Tan", RGB(0xd2, 0xb4, 0x8c) }, - { "Tan1", RGB(0xff, 0xa5, 0x4f) }, - { "Tan2", RGB(0xee, 0x9a, 0x49) }, - { "Tan3", RGB(0xcd, 0x85, 0x3f) }, - { "Tan4", RGB(0x8b, 0x5a, 0x2b) }, - { "Teal", RGB(0x00, 0x80, 0x80) }, - { "Thistle", RGB(0xd8, 0xbf, 0xd8) }, - { "Thistle1", RGB(0xff, 0xe1, 0xff) }, - { "Thistle2", RGB(0xee, 0xd2, 0xee) }, - { "Thistle3", RGB(0xcd, 0xb5, 0xcd) }, - { "Thistle4", RGB(0x8b, 0x7b, 0x8b) }, - { "Tomato", RGB(0xff, 0x63, 0x47) }, - { "Tomato1", RGB(0xff, 0x63, 0x47) }, - { "Tomato2", RGB(0xee, 0x5c, 0x42) }, - { "Tomato3", RGB(0xcd, 0x4f, 0x39) }, - { "Tomato4", RGB(0x8b, 0x36, 0x26) }, - { "Turquoise", RGB(0x40, 0xe0, 0xd0) }, - { "Turquoise1", RGB(0x0, 0xf5, 0xff) }, - { "Turquoise2", RGB(0x0, 0xe5, 0xee) }, - { "Turquoise3", RGB(0x0, 0xc5, 0xcd) }, - { "Turquoise4", RGB(0x0, 0x86, 0x8b) }, - { "Violet", RGB(0xee, 0x82, 0xee) }, - { "VioletRed", RGB(0xd0, 0x20, 0x90) }, - { "VioletRed1", RGB(0xff, 0x3e, 0x96) }, - { "VioletRed2", RGB(0xee, 0x3a, 0x8c) }, - { "VioletRed3", RGB(0xcd, 0x32, 0x78) }, - { "VioletRed4", RGB(0x8b, 0x22, 0x52) }, - { "WebGray", RGB(0x80, 0x80, 0x80) }, - { "WebGreen", RGB(0x0, 0x80, 0x0) }, - { "WebGrey", RGB(0x80, 0x80, 0x80) }, - { "WebMaroon", RGB(0x80, 0x0, 0x0) }, - { "WebPurple", RGB(0x80, 0x0, 0x80) }, - { "Wheat", RGB(0xf5, 0xde, 0xb3) }, - { "Wheat1", RGB(0xff, 0xe7, 0xba) }, - { "Wheat2", RGB(0xee, 0xd8, 0xae) }, - { "Wheat3", RGB(0xcd, 0xba, 0x96) }, - { "Wheat4", RGB(0x8b, 0x7e, 0x66) }, - { "White", RGB(0xff, 0xff, 0xff) }, - { "WhiteSmoke", RGB(0xf5, 0xf5, 0xf5) }, - { "X11Gray", RGB(0xbe, 0xbe, 0xbe) }, - { "X11Green", RGB(0x0, 0xff, 0x0) }, - { "X11Grey", RGB(0xbe, 0xbe, 0xbe) }, - { "X11Maroon", RGB(0xb0, 0x30, 0x60) }, - { "X11Purple", RGB(0xa0, 0x20, 0xf0) }, - { "Yellow", RGB(0xff, 0xff, 0x00) }, - { "Yellow1", RGB(0xff, 0xff, 0x0) }, - { "Yellow2", RGB(0xee, 0xee, 0x0) }, - { "Yellow3", RGB(0xcd, 0xcd, 0x0) }, - { "Yellow4", RGB(0x8b, 0x8b, 0x0) }, - { "YellowGreen", RGB(0x9a, 0xcd, 0x32) }, + { "AliceBlue", RGB_(0xf0, 0xf8, 0xff) }, + { "AntiqueWhite", RGB_(0xfa, 0xeb, 0xd7) }, + { "AntiqueWhite1", RGB_(0xff, 0xef, 0xdb) }, + { "AntiqueWhite2", RGB_(0xee, 0xdf, 0xcc) }, + { "AntiqueWhite3", RGB_(0xcd, 0xc0, 0xb0) }, + { "AntiqueWhite4", RGB_(0x8b, 0x83, 0x78) }, + { "Aqua", RGB_(0x00, 0xff, 0xff) }, + { "Aquamarine", RGB_(0x7f, 0xff, 0xd4) }, + { "Aquamarine1", RGB_(0x7f, 0xff, 0xd4) }, + { "Aquamarine2", RGB_(0x76, 0xee, 0xc6) }, + { "Aquamarine3", RGB_(0x66, 0xcd, 0xaa) }, + { "Aquamarine4", RGB_(0x45, 0x8b, 0x74) }, + { "Azure", RGB_(0xf0, 0xff, 0xff) }, + { "Azure1", RGB_(0xf0, 0xff, 0xff) }, + { "Azure2", RGB_(0xe0, 0xee, 0xee) }, + { "Azure3", RGB_(0xc1, 0xcd, 0xcd) }, + { "Azure4", RGB_(0x83, 0x8b, 0x8b) }, + { "Beige", RGB_(0xf5, 0xf5, 0xdc) }, + { "Bisque", RGB_(0xff, 0xe4, 0xc4) }, + { "Bisque1", RGB_(0xff, 0xe4, 0xc4) }, + { "Bisque2", RGB_(0xee, 0xd5, 0xb7) }, + { "Bisque3", RGB_(0xcd, 0xb7, 0x9e) }, + { "Bisque4", RGB_(0x8b, 0x7d, 0x6b) }, + { "Black", RGB_(0x00, 0x00, 0x00) }, + { "BlanchedAlmond", RGB_(0xff, 0xeb, 0xcd) }, + { "Blue", RGB_(0x00, 0x00, 0xff) }, + { "Blue1", RGB_(0x0, 0x0, 0xff) }, + { "Blue2", RGB_(0x0, 0x0, 0xee) }, + { "Blue3", RGB_(0x0, 0x0, 0xcd) }, + { "Blue4", RGB_(0x0, 0x0, 0x8b) }, + { "BlueViolet", RGB_(0x8a, 0x2b, 0xe2) }, + { "Brown", RGB_(0xa5, 0x2a, 0x2a) }, + { "Brown1", RGB_(0xff, 0x40, 0x40) }, + { "Brown2", RGB_(0xee, 0x3b, 0x3b) }, + { "Brown3", RGB_(0xcd, 0x33, 0x33) }, + { "Brown4", RGB_(0x8b, 0x23, 0x23) }, + { "BurlyWood", RGB_(0xde, 0xb8, 0x87) }, + { "Burlywood1", RGB_(0xff, 0xd3, 0x9b) }, + { "Burlywood2", RGB_(0xee, 0xc5, 0x91) }, + { "Burlywood3", RGB_(0xcd, 0xaa, 0x7d) }, + { "Burlywood4", RGB_(0x8b, 0x73, 0x55) }, + { "CadetBlue", RGB_(0x5f, 0x9e, 0xa0) }, + { "CadetBlue1", RGB_(0x98, 0xf5, 0xff) }, + { "CadetBlue2", RGB_(0x8e, 0xe5, 0xee) }, + { "CadetBlue3", RGB_(0x7a, 0xc5, 0xcd) }, + { "CadetBlue4", RGB_(0x53, 0x86, 0x8b) }, + { "ChartReuse", RGB_(0x7f, 0xff, 0x00) }, + { "Chartreuse1", RGB_(0x7f, 0xff, 0x0) }, + { "Chartreuse2", RGB_(0x76, 0xee, 0x0) }, + { "Chartreuse3", RGB_(0x66, 0xcd, 0x0) }, + { "Chartreuse4", RGB_(0x45, 0x8b, 0x0) }, + { "Chocolate", RGB_(0xd2, 0x69, 0x1e) }, + { "Chocolate1", RGB_(0xff, 0x7f, 0x24) }, + { "Chocolate2", RGB_(0xee, 0x76, 0x21) }, + { "Chocolate3", RGB_(0xcd, 0x66, 0x1d) }, + { "Chocolate4", RGB_(0x8b, 0x45, 0x13) }, + { "Coral", RGB_(0xff, 0x7f, 0x50) }, + { "Coral1", RGB_(0xff, 0x72, 0x56) }, + { "Coral2", RGB_(0xee, 0x6a, 0x50) }, + { "Coral3", RGB_(0xcd, 0x5b, 0x45) }, + { "Coral4", RGB_(0x8b, 0x3e, 0x2f) }, + { "CornFlowerBlue", RGB_(0x64, 0x95, 0xed) }, + { "Cornsilk", RGB_(0xff, 0xf8, 0xdc) }, + { "Cornsilk1", RGB_(0xff, 0xf8, 0xdc) }, + { "Cornsilk2", RGB_(0xee, 0xe8, 0xcd) }, + { "Cornsilk3", RGB_(0xcd, 0xc8, 0xb1) }, + { "Cornsilk4", RGB_(0x8b, 0x88, 0x78) }, + { "Crimson", RGB_(0xdc, 0x14, 0x3c) }, + { "Cyan", RGB_(0x00, 0xff, 0xff) }, + { "Cyan1", RGB_(0x0, 0xff, 0xff) }, + { "Cyan2", RGB_(0x0, 0xee, 0xee) }, + { "Cyan3", RGB_(0x0, 0xcd, 0xcd) }, + { "Cyan4", RGB_(0x0, 0x8b, 0x8b) }, + { "DarkBlue", RGB_(0x00, 0x00, 0x8b) }, + { "DarkCyan", RGB_(0x00, 0x8b, 0x8b) }, + { "DarkGoldenRod", RGB_(0xb8, 0x86, 0x0b) }, + { "DarkGoldenrod1", RGB_(0xff, 0xb9, 0xf) }, + { "DarkGoldenrod2", RGB_(0xee, 0xad, 0xe) }, + { "DarkGoldenrod3", RGB_(0xcd, 0x95, 0xc) }, + { "DarkGoldenrod4", RGB_(0x8b, 0x65, 0x8) }, + { "DarkGray", RGB_(0xa9, 0xa9, 0xa9) }, + { "DarkGreen", RGB_(0x00, 0x64, 0x00) }, + { "DarkGrey", RGB_(0xa9, 0xa9, 0xa9) }, + { "DarkKhaki", RGB_(0xbd, 0xb7, 0x6b) }, + { "DarkMagenta", RGB_(0x8b, 0x00, 0x8b) }, + { "DarkOliveGreen", RGB_(0x55, 0x6b, 0x2f) }, + { "DarkOliveGreen1", RGB_(0xca, 0xff, 0x70) }, + { "DarkOliveGreen2", RGB_(0xbc, 0xee, 0x68) }, + { "DarkOliveGreen3", RGB_(0xa2, 0xcd, 0x5a) }, + { "DarkOliveGreen4", RGB_(0x6e, 0x8b, 0x3d) }, + { "DarkOrange", RGB_(0xff, 0x8c, 0x00) }, + { "DarkOrange1", RGB_(0xff, 0x7f, 0x0) }, + { "DarkOrange2", RGB_(0xee, 0x76, 0x0) }, + { "DarkOrange3", RGB_(0xcd, 0x66, 0x0) }, + { "DarkOrange4", RGB_(0x8b, 0x45, 0x0) }, + { "DarkOrchid", RGB_(0x99, 0x32, 0xcc) }, + { "DarkOrchid1", RGB_(0xbf, 0x3e, 0xff) }, + { "DarkOrchid2", RGB_(0xb2, 0x3a, 0xee) }, + { "DarkOrchid3", RGB_(0x9a, 0x32, 0xcd) }, + { "DarkOrchid4", RGB_(0x68, 0x22, 0x8b) }, + { "DarkRed", RGB_(0x8b, 0x00, 0x00) }, + { "DarkSalmon", RGB_(0xe9, 0x96, 0x7a) }, + { "DarkSeaGreen", RGB_(0x8f, 0xbc, 0x8f) }, + { "DarkSeaGreen1", RGB_(0xc1, 0xff, 0xc1) }, + { "DarkSeaGreen2", RGB_(0xb4, 0xee, 0xb4) }, + { "DarkSeaGreen3", RGB_(0x9b, 0xcd, 0x9b) }, + { "DarkSeaGreen4", RGB_(0x69, 0x8b, 0x69) }, + { "DarkSlateBlue", RGB_(0x48, 0x3d, 0x8b) }, + { "DarkSlateGray", RGB_(0x2f, 0x4f, 0x4f) }, + { "DarkSlateGray1", RGB_(0x97, 0xff, 0xff) }, + { "DarkSlateGray2", RGB_(0x8d, 0xee, 0xee) }, + { "DarkSlateGray3", RGB_(0x79, 0xcd, 0xcd) }, + { "DarkSlateGray4", RGB_(0x52, 0x8b, 0x8b) }, + { "DarkSlateGrey", RGB_(0x2f, 0x4f, 0x4f) }, + { "DarkTurquoise", RGB_(0x00, 0xce, 0xd1) }, + { "DarkViolet", RGB_(0x94, 0x00, 0xd3) }, + { "DarkYellow", RGB_(0xbb, 0xbb, 0x00) }, + { "DeepPink", RGB_(0xff, 0x14, 0x93) }, + { "DeepPink1", RGB_(0xff, 0x14, 0x93) }, + { "DeepPink2", RGB_(0xee, 0x12, 0x89) }, + { "DeepPink3", RGB_(0xcd, 0x10, 0x76) }, + { "DeepPink4", RGB_(0x8b, 0xa, 0x50) }, + { "DeepSkyBlue", RGB_(0x00, 0xbf, 0xff) }, + { "DeepSkyBlue1", RGB_(0x0, 0xbf, 0xff) }, + { "DeepSkyBlue2", RGB_(0x0, 0xb2, 0xee) }, + { "DeepSkyBlue3", RGB_(0x0, 0x9a, 0xcd) }, + { "DeepSkyBlue4", RGB_(0x0, 0x68, 0x8b) }, + { "DimGray", RGB_(0x69, 0x69, 0x69) }, + { "DimGrey", RGB_(0x69, 0x69, 0x69) }, + { "DodgerBlue", RGB_(0x1e, 0x90, 0xff) }, + { "DodgerBlue1", RGB_(0x1e, 0x90, 0xff) }, + { "DodgerBlue2", RGB_(0x1c, 0x86, 0xee) }, + { "DodgerBlue3", RGB_(0x18, 0x74, 0xcd) }, + { "DodgerBlue4", RGB_(0x10, 0x4e, 0x8b) }, + { "Firebrick", RGB_(0xb2, 0x22, 0x22) }, + { "Firebrick1", RGB_(0xff, 0x30, 0x30) }, + { "Firebrick2", RGB_(0xee, 0x2c, 0x2c) }, + { "Firebrick3", RGB_(0xcd, 0x26, 0x26) }, + { "Firebrick4", RGB_(0x8b, 0x1a, 0x1a) }, + { "FloralWhite", RGB_(0xff, 0xfa, 0xf0) }, + { "ForestGreen", RGB_(0x22, 0x8b, 0x22) }, + { "Fuchsia", RGB_(0xff, 0x00, 0xff) }, + { "Gainsboro", RGB_(0xdc, 0xdc, 0xdc) }, + { "GhostWhite", RGB_(0xf8, 0xf8, 0xff) }, + { "Gold", RGB_(0xff, 0xd7, 0x00) }, + { "Gold1", RGB_(0xff, 0xd7, 0x0) }, + { "Gold2", RGB_(0xee, 0xc9, 0x0) }, + { "Gold3", RGB_(0xcd, 0xad, 0x0) }, + { "Gold4", RGB_(0x8b, 0x75, 0x0) }, + { "GoldenRod", RGB_(0xda, 0xa5, 0x20) }, + { "Goldenrod1", RGB_(0xff, 0xc1, 0x25) }, + { "Goldenrod2", RGB_(0xee, 0xb4, 0x22) }, + { "Goldenrod3", RGB_(0xcd, 0x9b, 0x1d) }, + { "Goldenrod4", RGB_(0x8b, 0x69, 0x14) }, + { "Gray", RGB_(0x80, 0x80, 0x80) }, + { "Gray0", RGB_(0x0, 0x0, 0x0) }, + { "Gray1", RGB_(0x3, 0x3, 0x3) }, + { "Gray10", RGB_(0x1a, 0x1a, 0x1a) }, + { "Gray100", RGB_(0xff, 0xff, 0xff) }, + { "Gray11", RGB_(0x1c, 0x1c, 0x1c) }, + { "Gray12", RGB_(0x1f, 0x1f, 0x1f) }, + { "Gray13", RGB_(0x21, 0x21, 0x21) }, + { "Gray14", RGB_(0x24, 0x24, 0x24) }, + { "Gray15", RGB_(0x26, 0x26, 0x26) }, + { "Gray16", RGB_(0x29, 0x29, 0x29) }, + { "Gray17", RGB_(0x2b, 0x2b, 0x2b) }, + { "Gray18", RGB_(0x2e, 0x2e, 0x2e) }, + { "Gray19", RGB_(0x30, 0x30, 0x30) }, + { "Gray2", RGB_(0x5, 0x5, 0x5) }, + { "Gray20", RGB_(0x33, 0x33, 0x33) }, + { "Gray21", RGB_(0x36, 0x36, 0x36) }, + { "Gray22", RGB_(0x38, 0x38, 0x38) }, + { "Gray23", RGB_(0x3b, 0x3b, 0x3b) }, + { "Gray24", RGB_(0x3d, 0x3d, 0x3d) }, + { "Gray25", RGB_(0x40, 0x40, 0x40) }, + { "Gray26", RGB_(0x42, 0x42, 0x42) }, + { "Gray27", RGB_(0x45, 0x45, 0x45) }, + { "Gray28", RGB_(0x47, 0x47, 0x47) }, + { "Gray29", RGB_(0x4a, 0x4a, 0x4a) }, + { "Gray3", RGB_(0x8, 0x8, 0x8) }, + { "Gray30", RGB_(0x4d, 0x4d, 0x4d) }, + { "Gray31", RGB_(0x4f, 0x4f, 0x4f) }, + { "Gray32", RGB_(0x52, 0x52, 0x52) }, + { "Gray33", RGB_(0x54, 0x54, 0x54) }, + { "Gray34", RGB_(0x57, 0x57, 0x57) }, + { "Gray35", RGB_(0x59, 0x59, 0x59) }, + { "Gray36", RGB_(0x5c, 0x5c, 0x5c) }, + { "Gray37", RGB_(0x5e, 0x5e, 0x5e) }, + { "Gray38", RGB_(0x61, 0x61, 0x61) }, + { "Gray39", RGB_(0x63, 0x63, 0x63) }, + { "Gray4", RGB_(0xa, 0xa, 0xa) }, + { "Gray40", RGB_(0x66, 0x66, 0x66) }, + { "Gray41", RGB_(0x69, 0x69, 0x69) }, + { "Gray42", RGB_(0x6b, 0x6b, 0x6b) }, + { "Gray43", RGB_(0x6e, 0x6e, 0x6e) }, + { "Gray44", RGB_(0x70, 0x70, 0x70) }, + { "Gray45", RGB_(0x73, 0x73, 0x73) }, + { "Gray46", RGB_(0x75, 0x75, 0x75) }, + { "Gray47", RGB_(0x78, 0x78, 0x78) }, + { "Gray48", RGB_(0x7a, 0x7a, 0x7a) }, + { "Gray49", RGB_(0x7d, 0x7d, 0x7d) }, + { "Gray5", RGB_(0xd, 0xd, 0xd) }, + { "Gray50", RGB_(0x7f, 0x7f, 0x7f) }, + { "Gray51", RGB_(0x82, 0x82, 0x82) }, + { "Gray52", RGB_(0x85, 0x85, 0x85) }, + { "Gray53", RGB_(0x87, 0x87, 0x87) }, + { "Gray54", RGB_(0x8a, 0x8a, 0x8a) }, + { "Gray55", RGB_(0x8c, 0x8c, 0x8c) }, + { "Gray56", RGB_(0x8f, 0x8f, 0x8f) }, + { "Gray57", RGB_(0x91, 0x91, 0x91) }, + { "Gray58", RGB_(0x94, 0x94, 0x94) }, + { "Gray59", RGB_(0x96, 0x96, 0x96) }, + { "Gray6", RGB_(0xf, 0xf, 0xf) }, + { "Gray60", RGB_(0x99, 0x99, 0x99) }, + { "Gray61", RGB_(0x9c, 0x9c, 0x9c) }, + { "Gray62", RGB_(0x9e, 0x9e, 0x9e) }, + { "Gray63", RGB_(0xa1, 0xa1, 0xa1) }, + { "Gray64", RGB_(0xa3, 0xa3, 0xa3) }, + { "Gray65", RGB_(0xa6, 0xa6, 0xa6) }, + { "Gray66", RGB_(0xa8, 0xa8, 0xa8) }, + { "Gray67", RGB_(0xab, 0xab, 0xab) }, + { "Gray68", RGB_(0xad, 0xad, 0xad) }, + { "Gray69", RGB_(0xb0, 0xb0, 0xb0) }, + { "Gray7", RGB_(0x12, 0x12, 0x12) }, + { "Gray70", RGB_(0xb3, 0xb3, 0xb3) }, + { "Gray71", RGB_(0xb5, 0xb5, 0xb5) }, + { "Gray72", RGB_(0xb8, 0xb8, 0xb8) }, + { "Gray73", RGB_(0xba, 0xba, 0xba) }, + { "Gray74", RGB_(0xbd, 0xbd, 0xbd) }, + { "Gray75", RGB_(0xbf, 0xbf, 0xbf) }, + { "Gray76", RGB_(0xc2, 0xc2, 0xc2) }, + { "Gray77", RGB_(0xc4, 0xc4, 0xc4) }, + { "Gray78", RGB_(0xc7, 0xc7, 0xc7) }, + { "Gray79", RGB_(0xc9, 0xc9, 0xc9) }, + { "Gray8", RGB_(0x14, 0x14, 0x14) }, + { "Gray80", RGB_(0xcc, 0xcc, 0xcc) }, + { "Gray81", RGB_(0xcf, 0xcf, 0xcf) }, + { "Gray82", RGB_(0xd1, 0xd1, 0xd1) }, + { "Gray83", RGB_(0xd4, 0xd4, 0xd4) }, + { "Gray84", RGB_(0xd6, 0xd6, 0xd6) }, + { "Gray85", RGB_(0xd9, 0xd9, 0xd9) }, + { "Gray86", RGB_(0xdb, 0xdb, 0xdb) }, + { "Gray87", RGB_(0xde, 0xde, 0xde) }, + { "Gray88", RGB_(0xe0, 0xe0, 0xe0) }, + { "Gray89", RGB_(0xe3, 0xe3, 0xe3) }, + { "Gray9", RGB_(0x17, 0x17, 0x17) }, + { "Gray90", RGB_(0xe5, 0xe5, 0xe5) }, + { "Gray91", RGB_(0xe8, 0xe8, 0xe8) }, + { "Gray92", RGB_(0xeb, 0xeb, 0xeb) }, + { "Gray93", RGB_(0xed, 0xed, 0xed) }, + { "Gray94", RGB_(0xf0, 0xf0, 0xf0) }, + { "Gray95", RGB_(0xf2, 0xf2, 0xf2) }, + { "Gray96", RGB_(0xf5, 0xf5, 0xf5) }, + { "Gray97", RGB_(0xf7, 0xf7, 0xf7) }, + { "Gray98", RGB_(0xfa, 0xfa, 0xfa) }, + { "Gray99", RGB_(0xfc, 0xfc, 0xfc) }, + { "Green", RGB_(0x00, 0x80, 0x00) }, + { "Green1", RGB_(0x0, 0xff, 0x0) }, + { "Green2", RGB_(0x0, 0xee, 0x0) }, + { "Green3", RGB_(0x0, 0xcd, 0x0) }, + { "Green4", RGB_(0x0, 0x8b, 0x0) }, + { "GreenYellow", RGB_(0xad, 0xff, 0x2f) }, + { "Grey", RGB_(0x80, 0x80, 0x80) }, + { "Grey0", RGB_(0x0, 0x0, 0x0) }, + { "Grey1", RGB_(0x3, 0x3, 0x3) }, + { "Grey10", RGB_(0x1a, 0x1a, 0x1a) }, + { "Grey100", RGB_(0xff, 0xff, 0xff) }, + { "Grey11", RGB_(0x1c, 0x1c, 0x1c) }, + { "Grey12", RGB_(0x1f, 0x1f, 0x1f) }, + { "Grey13", RGB_(0x21, 0x21, 0x21) }, + { "Grey14", RGB_(0x24, 0x24, 0x24) }, + { "Grey15", RGB_(0x26, 0x26, 0x26) }, + { "Grey16", RGB_(0x29, 0x29, 0x29) }, + { "Grey17", RGB_(0x2b, 0x2b, 0x2b) }, + { "Grey18", RGB_(0x2e, 0x2e, 0x2e) }, + { "Grey19", RGB_(0x30, 0x30, 0x30) }, + { "Grey2", RGB_(0x5, 0x5, 0x5) }, + { "Grey20", RGB_(0x33, 0x33, 0x33) }, + { "Grey21", RGB_(0x36, 0x36, 0x36) }, + { "Grey22", RGB_(0x38, 0x38, 0x38) }, + { "Grey23", RGB_(0x3b, 0x3b, 0x3b) }, + { "Grey24", RGB_(0x3d, 0x3d, 0x3d) }, + { "Grey25", RGB_(0x40, 0x40, 0x40) }, + { "Grey26", RGB_(0x42, 0x42, 0x42) }, + { "Grey27", RGB_(0x45, 0x45, 0x45) }, + { "Grey28", RGB_(0x47, 0x47, 0x47) }, + { "Grey29", RGB_(0x4a, 0x4a, 0x4a) }, + { "Grey3", RGB_(0x8, 0x8, 0x8) }, + { "Grey30", RGB_(0x4d, 0x4d, 0x4d) }, + { "Grey31", RGB_(0x4f, 0x4f, 0x4f) }, + { "Grey32", RGB_(0x52, 0x52, 0x52) }, + { "Grey33", RGB_(0x54, 0x54, 0x54) }, + { "Grey34", RGB_(0x57, 0x57, 0x57) }, + { "Grey35", RGB_(0x59, 0x59, 0x59) }, + { "Grey36", RGB_(0x5c, 0x5c, 0x5c) }, + { "Grey37", RGB_(0x5e, 0x5e, 0x5e) }, + { "Grey38", RGB_(0x61, 0x61, 0x61) }, + { "Grey39", RGB_(0x63, 0x63, 0x63) }, + { "Grey4", RGB_(0xa, 0xa, 0xa) }, + { "Grey40", RGB_(0x66, 0x66, 0x66) }, + { "Grey41", RGB_(0x69, 0x69, 0x69) }, + { "Grey42", RGB_(0x6b, 0x6b, 0x6b) }, + { "Grey43", RGB_(0x6e, 0x6e, 0x6e) }, + { "Grey44", RGB_(0x70, 0x70, 0x70) }, + { "Grey45", RGB_(0x73, 0x73, 0x73) }, + { "Grey46", RGB_(0x75, 0x75, 0x75) }, + { "Grey47", RGB_(0x78, 0x78, 0x78) }, + { "Grey48", RGB_(0x7a, 0x7a, 0x7a) }, + { "Grey49", RGB_(0x7d, 0x7d, 0x7d) }, + { "Grey5", RGB_(0xd, 0xd, 0xd) }, + { "Grey50", RGB_(0x7f, 0x7f, 0x7f) }, + { "Grey51", RGB_(0x82, 0x82, 0x82) }, + { "Grey52", RGB_(0x85, 0x85, 0x85) }, + { "Grey53", RGB_(0x87, 0x87, 0x87) }, + { "Grey54", RGB_(0x8a, 0x8a, 0x8a) }, + { "Grey55", RGB_(0x8c, 0x8c, 0x8c) }, + { "Grey56", RGB_(0x8f, 0x8f, 0x8f) }, + { "Grey57", RGB_(0x91, 0x91, 0x91) }, + { "Grey58", RGB_(0x94, 0x94, 0x94) }, + { "Grey59", RGB_(0x96, 0x96, 0x96) }, + { "Grey6", RGB_(0xf, 0xf, 0xf) }, + { "Grey60", RGB_(0x99, 0x99, 0x99) }, + { "Grey61", RGB_(0x9c, 0x9c, 0x9c) }, + { "Grey62", RGB_(0x9e, 0x9e, 0x9e) }, + { "Grey63", RGB_(0xa1, 0xa1, 0xa1) }, + { "Grey64", RGB_(0xa3, 0xa3, 0xa3) }, + { "Grey65", RGB_(0xa6, 0xa6, 0xa6) }, + { "Grey66", RGB_(0xa8, 0xa8, 0xa8) }, + { "Grey67", RGB_(0xab, 0xab, 0xab) }, + { "Grey68", RGB_(0xad, 0xad, 0xad) }, + { "Grey69", RGB_(0xb0, 0xb0, 0xb0) }, + { "Grey7", RGB_(0x12, 0x12, 0x12) }, + { "Grey70", RGB_(0xb3, 0xb3, 0xb3) }, + { "Grey71", RGB_(0xb5, 0xb5, 0xb5) }, + { "Grey72", RGB_(0xb8, 0xb8, 0xb8) }, + { "Grey73", RGB_(0xba, 0xba, 0xba) }, + { "Grey74", RGB_(0xbd, 0xbd, 0xbd) }, + { "Grey75", RGB_(0xbf, 0xbf, 0xbf) }, + { "Grey76", RGB_(0xc2, 0xc2, 0xc2) }, + { "Grey77", RGB_(0xc4, 0xc4, 0xc4) }, + { "Grey78", RGB_(0xc7, 0xc7, 0xc7) }, + { "Grey79", RGB_(0xc9, 0xc9, 0xc9) }, + { "Grey8", RGB_(0x14, 0x14, 0x14) }, + { "Grey80", RGB_(0xcc, 0xcc, 0xcc) }, + { "Grey81", RGB_(0xcf, 0xcf, 0xcf) }, + { "Grey82", RGB_(0xd1, 0xd1, 0xd1) }, + { "Grey83", RGB_(0xd4, 0xd4, 0xd4) }, + { "Grey84", RGB_(0xd6, 0xd6, 0xd6) }, + { "Grey85", RGB_(0xd9, 0xd9, 0xd9) }, + { "Grey86", RGB_(0xdb, 0xdb, 0xdb) }, + { "Grey87", RGB_(0xde, 0xde, 0xde) }, + { "Grey88", RGB_(0xe0, 0xe0, 0xe0) }, + { "Grey89", RGB_(0xe3, 0xe3, 0xe3) }, + { "Grey9", RGB_(0x17, 0x17, 0x17) }, + { "Grey90", RGB_(0xe5, 0xe5, 0xe5) }, + { "Grey91", RGB_(0xe8, 0xe8, 0xe8) }, + { "Grey92", RGB_(0xeb, 0xeb, 0xeb) }, + { "Grey93", RGB_(0xed, 0xed, 0xed) }, + { "Grey94", RGB_(0xf0, 0xf0, 0xf0) }, + { "Grey95", RGB_(0xf2, 0xf2, 0xf2) }, + { "Grey96", RGB_(0xf5, 0xf5, 0xf5) }, + { "Grey97", RGB_(0xf7, 0xf7, 0xf7) }, + { "Grey98", RGB_(0xfa, 0xfa, 0xfa) }, + { "Grey99", RGB_(0xfc, 0xfc, 0xfc) }, + { "Honeydew", RGB_(0xf0, 0xff, 0xf0) }, + { "Honeydew1", RGB_(0xf0, 0xff, 0xf0) }, + { "Honeydew2", RGB_(0xe0, 0xee, 0xe0) }, + { "Honeydew3", RGB_(0xc1, 0xcd, 0xc1) }, + { "Honeydew4", RGB_(0x83, 0x8b, 0x83) }, + { "HotPink", RGB_(0xff, 0x69, 0xb4) }, + { "HotPink1", RGB_(0xff, 0x6e, 0xb4) }, + { "HotPink2", RGB_(0xee, 0x6a, 0xa7) }, + { "HotPink3", RGB_(0xcd, 0x60, 0x90) }, + { "HotPink4", RGB_(0x8b, 0x3a, 0x62) }, + { "IndianRed", RGB_(0xcd, 0x5c, 0x5c) }, + { "IndianRed1", RGB_(0xff, 0x6a, 0x6a) }, + { "IndianRed2", RGB_(0xee, 0x63, 0x63) }, + { "IndianRed3", RGB_(0xcd, 0x55, 0x55) }, + { "IndianRed4", RGB_(0x8b, 0x3a, 0x3a) }, + { "Indigo", RGB_(0x4b, 0x00, 0x82) }, + { "Ivory", RGB_(0xff, 0xff, 0xf0) }, + { "Ivory1", RGB_(0xff, 0xff, 0xf0) }, + { "Ivory2", RGB_(0xee, 0xee, 0xe0) }, + { "Ivory3", RGB_(0xcd, 0xcd, 0xc1) }, + { "Ivory4", RGB_(0x8b, 0x8b, 0x83) }, + { "Khaki", RGB_(0xf0, 0xe6, 0x8c) }, + { "Khaki1", RGB_(0xff, 0xf6, 0x8f) }, + { "Khaki2", RGB_(0xee, 0xe6, 0x85) }, + { "Khaki3", RGB_(0xcd, 0xc6, 0x73) }, + { "Khaki4", RGB_(0x8b, 0x86, 0x4e) }, + { "Lavender", RGB_(0xe6, 0xe6, 0xfa) }, + { "LavenderBlush", RGB_(0xff, 0xf0, 0xf5) }, + { "LavenderBlush1", RGB_(0xff, 0xf0, 0xf5) }, + { "LavenderBlush2", RGB_(0xee, 0xe0, 0xe5) }, + { "LavenderBlush3", RGB_(0xcd, 0xc1, 0xc5) }, + { "LavenderBlush4", RGB_(0x8b, 0x83, 0x86) }, + { "LawnGreen", RGB_(0x7c, 0xfc, 0x00) }, + { "LemonChiffon", RGB_(0xff, 0xfa, 0xcd) }, + { "LemonChiffon1", RGB_(0xff, 0xfa, 0xcd) }, + { "LemonChiffon2", RGB_(0xee, 0xe9, 0xbf) }, + { "LemonChiffon3", RGB_(0xcd, 0xc9, 0xa5) }, + { "LemonChiffon4", RGB_(0x8b, 0x89, 0x70) }, + { "LightBlue", RGB_(0xad, 0xd8, 0xe6) }, + { "LightBlue1", RGB_(0xbf, 0xef, 0xff) }, + { "LightBlue2", RGB_(0xb2, 0xdf, 0xee) }, + { "LightBlue3", RGB_(0x9a, 0xc0, 0xcd) }, + { "LightBlue4", RGB_(0x68, 0x83, 0x8b) }, + { "LightCoral", RGB_(0xf0, 0x80, 0x80) }, + { "LightCyan", RGB_(0xe0, 0xff, 0xff) }, + { "LightCyan1", RGB_(0xe0, 0xff, 0xff) }, + { "LightCyan2", RGB_(0xd1, 0xee, 0xee) }, + { "LightCyan3", RGB_(0xb4, 0xcd, 0xcd) }, + { "LightCyan4", RGB_(0x7a, 0x8b, 0x8b) }, + { "LightGoldenrod", RGB_(0xee, 0xdd, 0x82) }, + { "LightGoldenrod1", RGB_(0xff, 0xec, 0x8b) }, + { "LightGoldenrod2", RGB_(0xee, 0xdc, 0x82) }, + { "LightGoldenrod3", RGB_(0xcd, 0xbe, 0x70) }, + { "LightGoldenrod4", RGB_(0x8b, 0x81, 0x4c) }, + { "LightGoldenRodYellow", RGB_(0xfa, 0xfa, 0xd2) }, + { "LightGray", RGB_(0xd3, 0xd3, 0xd3) }, + { "LightGreen", RGB_(0x90, 0xee, 0x90) }, + { "LightGrey", RGB_(0xd3, 0xd3, 0xd3) }, + { "LightMagenta", RGB_(0xff, 0xbb, 0xff) }, + { "LightPink", RGB_(0xff, 0xb6, 0xc1) }, + { "LightPink1", RGB_(0xff, 0xae, 0xb9) }, + { "LightPink2", RGB_(0xee, 0xa2, 0xad) }, + { "LightPink3", RGB_(0xcd, 0x8c, 0x95) }, + { "LightPink4", RGB_(0x8b, 0x5f, 0x65) }, + { "LightRed", RGB_(0xff, 0xbb, 0xbb) }, + { "LightSalmon", RGB_(0xff, 0xa0, 0x7a) }, + { "LightSalmon1", RGB_(0xff, 0xa0, 0x7a) }, + { "LightSalmon2", RGB_(0xee, 0x95, 0x72) }, + { "LightSalmon3", RGB_(0xcd, 0x81, 0x62) }, + { "LightSalmon4", RGB_(0x8b, 0x57, 0x42) }, + { "LightSeaGreen", RGB_(0x20, 0xb2, 0xaa) }, + { "LightSkyBlue", RGB_(0x87, 0xce, 0xfa) }, + { "LightSkyBlue1", RGB_(0xb0, 0xe2, 0xff) }, + { "LightSkyBlue2", RGB_(0xa4, 0xd3, 0xee) }, + { "LightSkyBlue3", RGB_(0x8d, 0xb6, 0xcd) }, + { "LightSkyBlue4", RGB_(0x60, 0x7b, 0x8b) }, + { "LightSlateBlue", RGB_(0x84, 0x70, 0xff) }, + { "LightSlateGray", RGB_(0x77, 0x88, 0x99) }, + { "LightSlateGrey", RGB_(0x77, 0x88, 0x99) }, + { "LightSteelBlue", RGB_(0xb0, 0xc4, 0xde) }, + { "LightSteelBlue1", RGB_(0xca, 0xe1, 0xff) }, + { "LightSteelBlue2", RGB_(0xbc, 0xd2, 0xee) }, + { "LightSteelBlue3", RGB_(0xa2, 0xb5, 0xcd) }, + { "LightSteelBlue4", RGB_(0x6e, 0x7b, 0x8b) }, + { "LightYellow", RGB_(0xff, 0xff, 0xe0) }, + { "LightYellow1", RGB_(0xff, 0xff, 0xe0) }, + { "LightYellow2", RGB_(0xee, 0xee, 0xd1) }, + { "LightYellow3", RGB_(0xcd, 0xcd, 0xb4) }, + { "LightYellow4", RGB_(0x8b, 0x8b, 0x7a) }, + { "Lime", RGB_(0x00, 0xff, 0x00) }, + { "LimeGreen", RGB_(0x32, 0xcd, 0x32) }, + { "Linen", RGB_(0xfa, 0xf0, 0xe6) }, + { "Magenta", RGB_(0xff, 0x00, 0xff) }, + { "Magenta1", RGB_(0xff, 0x0, 0xff) }, + { "Magenta2", RGB_(0xee, 0x0, 0xee) }, + { "Magenta3", RGB_(0xcd, 0x0, 0xcd) }, + { "Magenta4", RGB_(0x8b, 0x0, 0x8b) }, + { "Maroon", RGB_(0x80, 0x00, 0x00) }, + { "Maroon1", RGB_(0xff, 0x34, 0xb3) }, + { "Maroon2", RGB_(0xee, 0x30, 0xa7) }, + { "Maroon3", RGB_(0xcd, 0x29, 0x90) }, + { "Maroon4", RGB_(0x8b, 0x1c, 0x62) }, + { "MediumAquamarine", RGB_(0x66, 0xcd, 0xaa) }, + { "MediumBlue", RGB_(0x00, 0x00, 0xcd) }, + { "MediumOrchid", RGB_(0xba, 0x55, 0xd3) }, + { "MediumOrchid1", RGB_(0xe0, 0x66, 0xff) }, + { "MediumOrchid2", RGB_(0xd1, 0x5f, 0xee) }, + { "MediumOrchid3", RGB_(0xb4, 0x52, 0xcd) }, + { "MediumOrchid4", RGB_(0x7a, 0x37, 0x8b) }, + { "MediumPurple", RGB_(0x93, 0x70, 0xdb) }, + { "MediumPurple1", RGB_(0xab, 0x82, 0xff) }, + { "MediumPurple2", RGB_(0x9f, 0x79, 0xee) }, + { "MediumPurple3", RGB_(0x89, 0x68, 0xcd) }, + { "MediumPurple4", RGB_(0x5d, 0x47, 0x8b) }, + { "MediumSeaGreen", RGB_(0x3c, 0xb3, 0x71) }, + { "MediumSlateBlue", RGB_(0x7b, 0x68, 0xee) }, + { "MediumSpringGreen", RGB_(0x00, 0xfa, 0x9a) }, + { "MediumTurquoise", RGB_(0x48, 0xd1, 0xcc) }, + { "MediumVioletRed", RGB_(0xc7, 0x15, 0x85) }, + { "MidnightBlue", RGB_(0x19, 0x19, 0x70) }, + { "MintCream", RGB_(0xf5, 0xff, 0xfa) }, + { "MistyRose", RGB_(0xff, 0xe4, 0xe1) }, + { "MistyRose1", RGB_(0xff, 0xe4, 0xe1) }, + { "MistyRose2", RGB_(0xee, 0xd5, 0xd2) }, + { "MistyRose3", RGB_(0xcd, 0xb7, 0xb5) }, + { "MistyRose4", RGB_(0x8b, 0x7d, 0x7b) }, + { "Moccasin", RGB_(0xff, 0xe4, 0xb5) }, + { "NavajoWhite", RGB_(0xff, 0xde, 0xad) }, + { "NavajoWhite1", RGB_(0xff, 0xde, 0xad) }, + { "NavajoWhite2", RGB_(0xee, 0xcf, 0xa1) }, + { "NavajoWhite3", RGB_(0xcd, 0xb3, 0x8b) }, + { "NavajoWhite4", RGB_(0x8b, 0x79, 0x5e) }, + { "Navy", RGB_(0x00, 0x00, 0x80) }, + { "NavyBlue", RGB_(0x0, 0x0, 0x80) }, + { "OldLace", RGB_(0xfd, 0xf5, 0xe6) }, + { "Olive", RGB_(0x80, 0x80, 0x00) }, + { "OliveDrab", RGB_(0x6b, 0x8e, 0x23) }, + { "OliveDrab1", RGB_(0xc0, 0xff, 0x3e) }, + { "OliveDrab2", RGB_(0xb3, 0xee, 0x3a) }, + { "OliveDrab3", RGB_(0x9a, 0xcd, 0x32) }, + { "OliveDrab4", RGB_(0x69, 0x8b, 0x22) }, + { "Orange", RGB_(0xff, 0xa5, 0x00) }, + { "Orange1", RGB_(0xff, 0xa5, 0x0) }, + { "Orange2", RGB_(0xee, 0x9a, 0x0) }, + { "Orange3", RGB_(0xcd, 0x85, 0x0) }, + { "Orange4", RGB_(0x8b, 0x5a, 0x0) }, + { "OrangeRed", RGB_(0xff, 0x45, 0x00) }, + { "OrangeRed1", RGB_(0xff, 0x45, 0x0) }, + { "OrangeRed2", RGB_(0xee, 0x40, 0x0) }, + { "OrangeRed3", RGB_(0xcd, 0x37, 0x0) }, + { "OrangeRed4", RGB_(0x8b, 0x25, 0x0) }, + { "Orchid", RGB_(0xda, 0x70, 0xd6) }, + { "Orchid1", RGB_(0xff, 0x83, 0xfa) }, + { "Orchid2", RGB_(0xee, 0x7a, 0xe9) }, + { "Orchid3", RGB_(0xcd, 0x69, 0xc9) }, + { "Orchid4", RGB_(0x8b, 0x47, 0x89) }, + { "PaleGoldenRod", RGB_(0xee, 0xe8, 0xaa) }, + { "PaleGreen", RGB_(0x98, 0xfb, 0x98) }, + { "PaleGreen1", RGB_(0x9a, 0xff, 0x9a) }, + { "PaleGreen2", RGB_(0x90, 0xee, 0x90) }, + { "PaleGreen3", RGB_(0x7c, 0xcd, 0x7c) }, + { "PaleGreen4", RGB_(0x54, 0x8b, 0x54) }, + { "PaleTurquoise", RGB_(0xaf, 0xee, 0xee) }, + { "PaleTurquoise1", RGB_(0xbb, 0xff, 0xff) }, + { "PaleTurquoise2", RGB_(0xae, 0xee, 0xee) }, + { "PaleTurquoise3", RGB_(0x96, 0xcd, 0xcd) }, + { "PaleTurquoise4", RGB_(0x66, 0x8b, 0x8b) }, + { "PaleVioletRed", RGB_(0xdb, 0x70, 0x93) }, + { "PaleVioletRed1", RGB_(0xff, 0x82, 0xab) }, + { "PaleVioletRed2", RGB_(0xee, 0x79, 0x9f) }, + { "PaleVioletRed3", RGB_(0xcd, 0x68, 0x89) }, + { "PaleVioletRed4", RGB_(0x8b, 0x47, 0x5d) }, + { "PapayaWhip", RGB_(0xff, 0xef, 0xd5) }, + { "PeachPuff", RGB_(0xff, 0xda, 0xb9) }, + { "PeachPuff1", RGB_(0xff, 0xda, 0xb9) }, + { "PeachPuff2", RGB_(0xee, 0xcb, 0xad) }, + { "PeachPuff3", RGB_(0xcd, 0xaf, 0x95) }, + { "PeachPuff4", RGB_(0x8b, 0x77, 0x65) }, + { "Peru", RGB_(0xcd, 0x85, 0x3f) }, + { "Pink", RGB_(0xff, 0xc0, 0xcb) }, + { "Pink1", RGB_(0xff, 0xb5, 0xc5) }, + { "Pink2", RGB_(0xee, 0xa9, 0xb8) }, + { "Pink3", RGB_(0xcd, 0x91, 0x9e) }, + { "Pink4", RGB_(0x8b, 0x63, 0x6c) }, + { "Plum", RGB_(0xdd, 0xa0, 0xdd) }, + { "Plum1", RGB_(0xff, 0xbb, 0xff) }, + { "Plum2", RGB_(0xee, 0xae, 0xee) }, + { "Plum3", RGB_(0xcd, 0x96, 0xcd) }, + { "Plum4", RGB_(0x8b, 0x66, 0x8b) }, + { "PowderBlue", RGB_(0xb0, 0xe0, 0xe6) }, + { "Purple", RGB_(0x80, 0x00, 0x80) }, + { "Purple1", RGB_(0x9b, 0x30, 0xff) }, + { "Purple2", RGB_(0x91, 0x2c, 0xee) }, + { "Purple3", RGB_(0x7d, 0x26, 0xcd) }, + { "Purple4", RGB_(0x55, 0x1a, 0x8b) }, + { "RebeccaPurple", RGB_(0x66, 0x33, 0x99) }, + { "Red", RGB_(0xff, 0x00, 0x00) }, + { "Red1", RGB_(0xff, 0x0, 0x0) }, + { "Red2", RGB_(0xee, 0x0, 0x0) }, + { "Red3", RGB_(0xcd, 0x0, 0x0) }, + { "Red4", RGB_(0x8b, 0x0, 0x0) }, + { "RosyBrown", RGB_(0xbc, 0x8f, 0x8f) }, + { "RosyBrown1", RGB_(0xff, 0xc1, 0xc1) }, + { "RosyBrown2", RGB_(0xee, 0xb4, 0xb4) }, + { "RosyBrown3", RGB_(0xcd, 0x9b, 0x9b) }, + { "RosyBrown4", RGB_(0x8b, 0x69, 0x69) }, + { "RoyalBlue", RGB_(0x41, 0x69, 0xe1) }, + { "RoyalBlue1", RGB_(0x48, 0x76, 0xff) }, + { "RoyalBlue2", RGB_(0x43, 0x6e, 0xee) }, + { "RoyalBlue3", RGB_(0x3a, 0x5f, 0xcd) }, + { "RoyalBlue4", RGB_(0x27, 0x40, 0x8b) }, + { "SaddleBrown", RGB_(0x8b, 0x45, 0x13) }, + { "Salmon", RGB_(0xfa, 0x80, 0x72) }, + { "Salmon1", RGB_(0xff, 0x8c, 0x69) }, + { "Salmon2", RGB_(0xee, 0x82, 0x62) }, + { "Salmon3", RGB_(0xcd, 0x70, 0x54) }, + { "Salmon4", RGB_(0x8b, 0x4c, 0x39) }, + { "SandyBrown", RGB_(0xf4, 0xa4, 0x60) }, + { "SeaGreen", RGB_(0x2e, 0x8b, 0x57) }, + { "SeaGreen1", RGB_(0x54, 0xff, 0x9f) }, + { "SeaGreen2", RGB_(0x4e, 0xee, 0x94) }, + { "SeaGreen3", RGB_(0x43, 0xcd, 0x80) }, + { "SeaGreen4", RGB_(0x2e, 0x8b, 0x57) }, + { "SeaShell", RGB_(0xff, 0xf5, 0xee) }, + { "Seashell1", RGB_(0xff, 0xf5, 0xee) }, + { "Seashell2", RGB_(0xee, 0xe5, 0xde) }, + { "Seashell3", RGB_(0xcd, 0xc5, 0xbf) }, + { "Seashell4", RGB_(0x8b, 0x86, 0x82) }, + { "Sienna", RGB_(0xa0, 0x52, 0x2d) }, + { "Sienna1", RGB_(0xff, 0x82, 0x47) }, + { "Sienna2", RGB_(0xee, 0x79, 0x42) }, + { "Sienna3", RGB_(0xcd, 0x68, 0x39) }, + { "Sienna4", RGB_(0x8b, 0x47, 0x26) }, + { "Silver", RGB_(0xc0, 0xc0, 0xc0) }, + { "SkyBlue", RGB_(0x87, 0xce, 0xeb) }, + { "SkyBlue1", RGB_(0x87, 0xce, 0xff) }, + { "SkyBlue2", RGB_(0x7e, 0xc0, 0xee) }, + { "SkyBlue3", RGB_(0x6c, 0xa6, 0xcd) }, + { "SkyBlue4", RGB_(0x4a, 0x70, 0x8b) }, + { "SlateBlue", RGB_(0x6a, 0x5a, 0xcd) }, + { "SlateBlue1", RGB_(0x83, 0x6f, 0xff) }, + { "SlateBlue2", RGB_(0x7a, 0x67, 0xee) }, + { "SlateBlue3", RGB_(0x69, 0x59, 0xcd) }, + { "SlateBlue4", RGB_(0x47, 0x3c, 0x8b) }, + { "SlateGray", RGB_(0x70, 0x80, 0x90) }, + { "SlateGray1", RGB_(0xc6, 0xe2, 0xff) }, + { "SlateGray2", RGB_(0xb9, 0xd3, 0xee) }, + { "SlateGray3", RGB_(0x9f, 0xb6, 0xcd) }, + { "SlateGray4", RGB_(0x6c, 0x7b, 0x8b) }, + { "SlateGrey", RGB_(0x70, 0x80, 0x90) }, + { "Snow", RGB_(0xff, 0xfa, 0xfa) }, + { "Snow1", RGB_(0xff, 0xfa, 0xfa) }, + { "Snow2", RGB_(0xee, 0xe9, 0xe9) }, + { "Snow3", RGB_(0xcd, 0xc9, 0xc9) }, + { "Snow4", RGB_(0x8b, 0x89, 0x89) }, + { "SpringGreen", RGB_(0x00, 0xff, 0x7f) }, + { "SpringGreen1", RGB_(0x0, 0xff, 0x7f) }, + { "SpringGreen2", RGB_(0x0, 0xee, 0x76) }, + { "SpringGreen3", RGB_(0x0, 0xcd, 0x66) }, + { "SpringGreen4", RGB_(0x0, 0x8b, 0x45) }, + { "SteelBlue", RGB_(0x46, 0x82, 0xb4) }, + { "SteelBlue1", RGB_(0x63, 0xb8, 0xff) }, + { "SteelBlue2", RGB_(0x5c, 0xac, 0xee) }, + { "SteelBlue3", RGB_(0x4f, 0x94, 0xcd) }, + { "SteelBlue4", RGB_(0x36, 0x64, 0x8b) }, + { "Tan", RGB_(0xd2, 0xb4, 0x8c) }, + { "Tan1", RGB_(0xff, 0xa5, 0x4f) }, + { "Tan2", RGB_(0xee, 0x9a, 0x49) }, + { "Tan3", RGB_(0xcd, 0x85, 0x3f) }, + { "Tan4", RGB_(0x8b, 0x5a, 0x2b) }, + { "Teal", RGB_(0x00, 0x80, 0x80) }, + { "Thistle", RGB_(0xd8, 0xbf, 0xd8) }, + { "Thistle1", RGB_(0xff, 0xe1, 0xff) }, + { "Thistle2", RGB_(0xee, 0xd2, 0xee) }, + { "Thistle3", RGB_(0xcd, 0xb5, 0xcd) }, + { "Thistle4", RGB_(0x8b, 0x7b, 0x8b) }, + { "Tomato", RGB_(0xff, 0x63, 0x47) }, + { "Tomato1", RGB_(0xff, 0x63, 0x47) }, + { "Tomato2", RGB_(0xee, 0x5c, 0x42) }, + { "Tomato3", RGB_(0xcd, 0x4f, 0x39) }, + { "Tomato4", RGB_(0x8b, 0x36, 0x26) }, + { "Turquoise", RGB_(0x40, 0xe0, 0xd0) }, + { "Turquoise1", RGB_(0x0, 0xf5, 0xff) }, + { "Turquoise2", RGB_(0x0, 0xe5, 0xee) }, + { "Turquoise3", RGB_(0x0, 0xc5, 0xcd) }, + { "Turquoise4", RGB_(0x0, 0x86, 0x8b) }, + { "Violet", RGB_(0xee, 0x82, 0xee) }, + { "VioletRed", RGB_(0xd0, 0x20, 0x90) }, + { "VioletRed1", RGB_(0xff, 0x3e, 0x96) }, + { "VioletRed2", RGB_(0xee, 0x3a, 0x8c) }, + { "VioletRed3", RGB_(0xcd, 0x32, 0x78) }, + { "VioletRed4", RGB_(0x8b, 0x22, 0x52) }, + { "WebGray", RGB_(0x80, 0x80, 0x80) }, + { "WebGreen", RGB_(0x0, 0x80, 0x0) }, + { "WebGrey", RGB_(0x80, 0x80, 0x80) }, + { "WebMaroon", RGB_(0x80, 0x0, 0x0) }, + { "WebPurple", RGB_(0x80, 0x0, 0x80) }, + { "Wheat", RGB_(0xf5, 0xde, 0xb3) }, + { "Wheat1", RGB_(0xff, 0xe7, 0xba) }, + { "Wheat2", RGB_(0xee, 0xd8, 0xae) }, + { "Wheat3", RGB_(0xcd, 0xba, 0x96) }, + { "Wheat4", RGB_(0x8b, 0x7e, 0x66) }, + { "White", RGB_(0xff, 0xff, 0xff) }, + { "WhiteSmoke", RGB_(0xf5, 0xf5, 0xf5) }, + { "X11Gray", RGB_(0xbe, 0xbe, 0xbe) }, + { "X11Green", RGB_(0x0, 0xff, 0x0) }, + { "X11Grey", RGB_(0xbe, 0xbe, 0xbe) }, + { "X11Maroon", RGB_(0xb0, 0x30, 0x60) }, + { "X11Purple", RGB_(0xa0, 0x20, 0xf0) }, + { "Yellow", RGB_(0xff, 0xff, 0x00) }, + { "Yellow1", RGB_(0xff, 0xff, 0x0) }, + { "Yellow2", RGB_(0xee, 0xee, 0x0) }, + { "Yellow3", RGB_(0xcd, 0xcd, 0x0) }, + { "Yellow4", RGB_(0x8b, 0x8b, 0x0) }, + { "YellowGreen", RGB_(0x9a, 0xcd, 0x32) }, { NULL, 0 }, }; diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index f3eb6883ce..f68bb2458d 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -180,13 +180,14 @@ void terminal_init(void) vterm_state_get_palette_color(state, color_index, &color); } map_put(int, int)(color_indexes, - RGB(color.red, color.green, color.blue), color_index + 1); + RGB_(color.red, color.green, color.blue), + color_index + 1); } VTermColor fg, bg; vterm_state_get_default_colors(state, &fg, &bg); - default_vt_fg = RGB(fg.red, fg.green, fg.blue); - default_vt_bg = RGB(bg.red, bg.green, bg.blue); + default_vt_fg = RGB_(fg.red, fg.green, fg.blue); + default_vt_bg = RGB_(bg.red, bg.green, bg.blue); default_vt_bg_rgb = bg; vterm_free(vt); } @@ -358,6 +359,15 @@ void terminal_resize(Terminal *term, uint16_t width, uint16_t height) return; } + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer && wp->w_buffer->terminal == term) { + const uint16_t win_width = + (uint16_t)(MAX(0, wp->w_width - win_col_off(wp))); + width = MAX(width, win_width); + height = (uint16_t)MAX(height, wp->w_height); + } + } + vterm_set_size(term->vt, height, width); vterm_screen_flush_damage(term->vts); term->pending_resize = true; @@ -386,10 +396,8 @@ void terminal_enter(void) win_T *save_curwin = curwin; int save_w_p_cul = curwin->w_p_cul; int save_w_p_cuc = curwin->w_p_cuc; - int save_w_p_rnu = curwin->w_p_rnu; curwin->w_p_cul = false; curwin->w_p_cuc = false; - curwin->w_p_rnu = false; adjust_topline(s->term, buf, 0); // scroll to end // erase the unfocused cursor @@ -407,7 +415,6 @@ void terminal_enter(void) if (save_curwin == curwin) { // save_curwin may be invalid (window closed)! curwin->w_p_cul = save_w_p_cul; curwin->w_p_cuc = save_w_p_cuc; - curwin->w_p_rnu = save_w_p_rnu; } // draw the unfocused cursor @@ -460,6 +467,10 @@ static int terminal_execute(VimState *state, int key) } break; + case K_COMMAND: + do_cmdline(NULL, getcmdkeycmd, NULL, 0); + break; + case Ctrl_N: if (s->got_bsl) { return 0; @@ -565,8 +576,8 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, VTermScreenCell cell; fetch_cell(term, row, col, &cell); // Get the rgb value set by libvterm. - int vt_fg = RGB(cell.fg.red, cell.fg.green, cell.fg.blue); - int vt_bg = RGB(cell.bg.red, cell.bg.green, cell.bg.blue); + int vt_fg = RGB_(cell.fg.red, cell.fg.green, cell.fg.blue); + int vt_bg = RGB_(cell.bg.red, cell.bg.green, cell.bg.blue); vt_fg = vt_fg != default_vt_fg ? vt_fg : - 1; vt_bg = vt_bg != default_vt_bg ? vt_bg : - 1; // Since libvterm does not expose the color index used by the program, we @@ -1083,7 +1094,6 @@ static void refresh_terminal(Terminal *term) refresh_size(term, buf); refresh_scrollback(term, buf); refresh_screen(term, buf); - redraw_buf_later(buf, NOT_VALID); }); long ml_added = buf->b_ml.ml_line_count - ml_before; adjust_topline(term, buf, ml_added); @@ -1093,6 +1103,7 @@ static void refresh_timer_cb(TimeWatcher *watcher, void *data) { refresh_pending = false; if (exiting // Cannot redraw (requires event loop) during teardown/exit. + || (State & CMDPREVIEW) // WM_LIST (^D) is not redrawn, unlike the normal wildmenu. So we must // skip redraws to keep it visible. || wild_menu_showing == WM_LIST) { diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 418463a0ad..4bfcbf8e79 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -7,7 +7,7 @@ ifeq ($(OS),Windows_NT) else NVIM_PRG ?= ../../../build/bin/nvim endif -SCRIPTSOURCE := ../../../runtime +ROOT := ../../.. export SHELL := sh export NVIM_PRG := $(NVIM_PRG) @@ -20,13 +20,13 @@ SCRIPTS_DEFAULT = \ test40.out \ test42.out \ test48.out \ - test49.out \ test52.out \ test64.out \ ifneq ($(OS),Windows_NT) SCRIPTS_DEFAULTS := $(SCRIPTS_DEFAULT) \ test17.out \ + test49.out \ endif @@ -134,7 +134,7 @@ ifdef USE_VALGRIND $(VALGRIND_TOOL) \ --suppressions=../../.valgrind.supp \ --error-exitcode=123 \ - --log-file=valgrind.\%p.$* \ + --log-file=valgrind-\%p.$* \ $(VGDB) \ --trace-children=yes else @@ -152,7 +152,8 @@ nongui: nolog $(SCRIPTS) newtests report gui: nolog $(SCRIPTS) $(SCRIPTS_GUI) newtests report .gdbinit: - echo 'set $$_exitcode = -1\nrun\nif $$_exitcode != -1\n quit\nend' > .gdbinit + @echo "[OLDTEST-PREP] Setting up .gdbinit" + @echo 'set $$_exitcode = -1\nrun\nif $$_exitcode != -1\n quit\nend' > .gdbinit report: @echo @@ -171,7 +172,7 @@ $(SCRIPTS) $(SCRIPTS_GUI): $(NVIM_PRG) test1.out RM_ON_RUN := test.out X* viminfo RM_ON_START := test.ok -RUN_VIM := VIMRUNTIME=$(SCRIPTSOURCE); export VIMRUNTIME; $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE -i viminfo --headless --noplugin -s dotest.in +RUN_VIM := $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE -i viminfo --headless --noplugin -s dotest.in clean: -rm -rf *.out \ @@ -179,6 +180,7 @@ clean: *.res \ *.rej \ *.orig \ + *.tlog \ test.log \ messages \ $(RM_ON_RUN) \ @@ -191,59 +193,32 @@ clean: del test1.out: .gdbinit test1.in - -rm -rf $*.failed $(RM_ON_RUN) $(RM_ON_START) wrongtermsize - mkdir -p $(TMPDIR) - $(RUN_VIM) $*.in - @/bin/sh -c "if test -e wrongtermsize; then \ - echo; \ - echo test1 FAILED - terminal size must be 80x24 or larger; \ - echo; exit 1; \ - elif diff test.out $*.ok; then \ - mv -f test.out $*.out; \ - else \ - echo; \ - echo test1 FAILED - Something basic is wrong; \ - echo; \ - exit 1; \ - fi" - -rm -rf X* viminfo + @echo "[OLDTEST-PREP] Running test1" + @rm -rf $*.failed $(RM_ON_RUN) $(RM_ON_START) wrongtermsize + @mkdir -p $(TMPDIR) + @/bin/sh runnvim.sh $(ROOT) $(NVIM_PRG) $* $(RUN_VIM) $*.in + @rm -f wrongtermsize + @rm -rf X* viminfo %.out: %.in .gdbinit - -rm -rf $*.failed test.ok $(RM_ON_RUN) - mkdir -p $(TMPDIR) - cp $*.ok test.ok - # Sleep a moment to avoid that the xterm title is messed up. - # 200 msec is sufficient, but only modern sleep supports a fraction of - # a second, fall back to a second if it fails. - @-/bin/sh -c "sleep .2 > /dev/null 2>&1 || sleep 1" - $(RUN_VIM) $*.in - - # Check if the test.out file matches test.ok. - @/bin/sh -c "if test -f test.out; then \ - if diff -u test.out $*.ok; then \ - mv -f test.out $*.out; \ - else \ - echo $* FAILED >> test.log; \ - mv -f test.out $*.failed; \ - fi; \ - else \ - echo $* NO OUTPUT >>test.log; \ - fi" - @/bin/sh -c "if test -f valgrind; then \ - mv -f valgrind valgrind.$*; \ - fi" - -rm -rf X* test.ok viminfo + @echo "[OLDESTTEST] Running" $* + @rm -rf $*.failed test.ok $(RM_ON_RUN) + @mkdir -p $(TMPDIR) + @cp $*.ok test.ok + @/bin/sh runnvim.sh --oldesttest $(ROOT) $(NVIM_PRG) $* $(RUN_VIM) $*.in + @rm -rf X* test.ok viminfo test49.out: test49.vim nolog: - -rm -f test.log messages + @echo "[OLDTEST-PREP] Removing test.log and messages" + @rm -f test.log messages # New style of tests uses Vim script with assert calls. These are easier # to write and a lot easier to read and debug. # Limitation: Only works with the +eval feature. -RUN_VIMTEST = VIMRUNTIME=$(SCRIPTSOURCE); export VIMRUNTIME; $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE --headless --noplugin +RUN_VIMTEST = $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE --headless --noplugin newtests: newtestssilent @/bin/sh -c "if test -f messages && grep -q 'FAILED' messages; then \ @@ -253,5 +228,6 @@ newtests: newtestssilent newtestssilent: $(NEW_TESTS) %.res: %.vim .gdbinit - mkdir -p $(TMPDIR) - $(RUN_VIMTEST) -u NONE -S runtest.vim $*.vim + @echo "[OLDTEST] Running" $* + @mkdir -p $(TMPDIR) + @/bin/sh runnvim.sh $(ROOT) $(NVIM_PRG) $* $(RUN_VIMTEST) -u NONE -S runtest.vim $*.vim diff --git a/src/nvim/testdir/runnvim.sh b/src/nvim/testdir/runnvim.sh new file mode 100755 index 0000000000..43556f3ad3 --- /dev/null +++ b/src/nvim/testdir/runnvim.sh @@ -0,0 +1,82 @@ +#!/bin/sh + +main() {( + local separator="================================================================================" + local oldesttest= + if test "$1" = "--oldesttest" ; then + shift + oldesttest=1 + fi + local root="$1" ; shift + local nvim_prg="$1" ; shift + local test_name="$1" ; shift + + local tlog="$test_name.tlog" + + export NVIM_TEST_ARGC=$# + local arg + local i=0 + for arg ; do + eval "export NVIM_TEST_ARG$i=\"\$arg\"" + i=$(( i+1 )) + done + + export CI_DIR="$root/ci" + export BUILD_DIR="$(dirname "$nvim_prg")/.." + export FAILED=0 + + . "$CI_DIR/common/suite.sh" + . "$CI_DIR/common/test.sh" + + export VIMRUNTIME="$root/runtime" + if ! "$nvim_prg" \ + -u NONE -i NONE \ + --headless \ + --cmd 'set shortmess+=I noswapfile noundofile nomore' \ + -S runnvim.vim \ + "$tlog" > "out-$tlog" 2> "err-$tlog" + then + fail "$test_name" F "Nvim exited with non-zero code" + fi + echo "Stdout of :terminal runner" >> "$tlog" + echo "$separator" >> "$tlog" + cat "out-$tlog" >> "$tlog" + echo "$separator" >> "$tlog" + echo "Stderr of :terminal runner" >> "$tlog" + echo "$separator" >> "$tlog" + cat "err-$tlog" >> "$tlog" + echo "$separator" >> "$tlog" + if test "$oldesttest" = 1 ; then + if ! diff -q test.out "$test_name.ok" > /dev/null 2>&1 ; then + if test -f test.out ; then + fail "$test_name" F "Oldest test .out file differs from .ok file" + echo "Diff between test.out and $test_name.ok" >> "$tlog" + echo "$separator" >> "$tlog" + diff -a test.out "$test_name.ok" >> "$tlog" + echo "$separator" >> "$tlog" + else + echo "No output in test.out" >> "$tlog" + fi + fi + fi + if test "$FAILED" = 1 ; then + travis_fold start "$NVIM_TEST_CURRENT_SUITE/$test_name" + fi + valgrind_check . + if test -n "$LOG_DIR" ; then + asan_check "$LOG_DIR" + fi + check_core_dumps + if test "$FAILED" = 1 ; then + cat "$tlog" + fi + rm -f "$tlog" + if test "$FAILED" = 1 ; then + travis_fold end "$NVIM_TEST_CURRENT_SUITE/$test_name" + fi + if test "$FAILED" = 1 ; then + echo "Test $test_name failed, see output above and summary for more details" >> test.log + fi +)} + +main "$@" diff --git a/src/nvim/testdir/runnvim.vim b/src/nvim/testdir/runnvim.vim new file mode 100644 index 0000000000..396a3a6477 --- /dev/null +++ b/src/nvim/testdir/runnvim.vim @@ -0,0 +1,55 @@ +let s:logger = {'d_events': []} +function s:logger.on_stdout(id, data, event) + call add(self.d_events, [a:event, a:data]) +endfunction +let s:logger.on_stderr = s:logger.on_stdout +function s:logger.on_exit(id, data, event) + call add(self.d_events, [a:event, ['']]) +endfunction + +function Main() + let argc = +$NVIM_TEST_ARGC + let args = [] + for i in range(argc) + call add(args, eval("$NVIM_TEST_ARG" . i)) + endfor + set lines=25 + set columns=80 + enew + let job = termopen(args, s:logger) + let results = jobwait([job], 5 * 60 * 1000) + " TODO(ZyX-I): Get colors + let screen = getline(1, '$') + bwipeout! + let stringified_events = map(s:logger.d_events, + \'v:val[0] . ": " . ' . + \'join(map(v:val[1], '. + \ '''substitute(v:val, '. + \ '"\\v\\C(\\p@!.|\\<)", '. + \ '"\\=printf(\"<%x>\", '. + \ 'char2nr(submatch(0)))", '. + \ '"")''), '. + \ '''\n'')') + call setline(1, [ + \ 'Job exited with code ' . results[0], + \ printf('Screen (%u lines)', len(screen)), + \ repeat('=', 80), + \] + screen + [ + \ repeat('=', 80), + \ printf('Events (%u lines):', len(stringified_events)), + \ repeat('=', 80), + \] + stringified_events + [ + \ repeat('=', 80), + \]) + write + if results[0] != 0 + if results[0] != -1 + call jobstop(job) + endif + cquit + else + qall + endif +endfunction + +call Main() diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 6a7b44a76f..5c98455909 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -61,7 +61,7 @@ set nomore lang mess C " Always use forward slashes. -set shellslash +" set shellslash " Prepare for calling test_garbagecollect_now(). let v:testing = 1 @@ -237,6 +237,7 @@ let s:flaky = [ \ 'Test_oneshot()', \ 'Test_out_cb()', \ 'Test_paused()', + \ 'Test_quoteplus()', \ 'Test_reltime()', \ 'Test_terminal_composing_unicode()', \ 'Test_terminal_redir_file()', diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index 7d6dd0c7ce..aac9fefef4 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -17,3 +17,11 @@ let &packpath = &rtp " Make sure $HOME does not get read or written. let $HOME = '/does/not/exist' + +" Use default shell on Windows to avoid segfault, caused by TUI +if has('win32') + let $SHELL = '' + let $TERM = '' + let &shell = empty($COMSPEC) ? exepath('cmd.exe') : $COMSPEC + set shellcmdflag=/s/c shellxquote=\" shellredir=>%s\ 2>&1 +endif diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 673246e1fb..be68e9ff9d 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -154,7 +154,7 @@ func Test_getcompletion() call assert_equal([], l) let l = getcompletion('', 'dir') - call assert_true(index(l, 'sautest/') >= 0) + call assert_true(index(l, expand('sautest/')) >= 0) let l = getcompletion('NoMatch', 'dir') call assert_equal([], l) @@ -246,7 +246,7 @@ func Test_getcompletion() " Command line completion tests let l = getcompletion('cd ', 'cmdline') - call assert_true(index(l, 'sautest/') >= 0) + call assert_true(index(l, expand('sautest/')) >= 0) let l = getcompletion('cd NoMatch', 'cmdline') call assert_equal([], l) let l = getcompletion('let v:n', 'cmdline') @@ -288,7 +288,7 @@ func Test_expand_star_star() call mkdir('a/b', 'p') call writefile(['asdfasdf'], 'a/b/fileXname') call feedkeys(":find **/fileXname\<Tab>\<CR>", 'xt') - call assert_equal('find a/b/fileXname', getreg(':')) + call assert_equal('find '.expand('a/b/fileXname'), getreg(':')) bwipe! call delete('a', 'rf') endfunc diff --git a/src/nvim/testdir/test_find_complete.vim b/src/nvim/testdir/test_find_complete.vim index 4732109ed0..1019246404 100644 --- a/src/nvim/testdir/test_find_complete.vim +++ b/src/nvim/testdir/test_find_complete.vim @@ -3,6 +3,8 @@ " Do all the tests in a separate window to avoid E211 when we recursively " delete the Xfind directory during cleanup func Test_find_complete() + let shellslash = &shellslash + set shellslash set belloff=all " On windows a stale "Xfind" directory may exist, remove it so that @@ -154,4 +156,5 @@ func Test_find_complete() exe 'cd ' . cwd call delete('Xfind', 'rf') set path& + let &shellslash = shellslash endfunc diff --git a/src/nvim/testdir/test_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim index 06c48d8e76..4d4a902031 100644 --- a/src/nvim/testdir/test_help_tagjump.vim +++ b/src/nvim/testdir/test_help_tagjump.vim @@ -30,7 +30,7 @@ func Test_help_tagjump() help sp?it call assert_equal("help", &filetype) - call assert_true(getline('.') =~ '\*:split\*') + call assert_true(getline('.') =~ '\*'.(has('win32') ? 'split()' : ':split').'\*') helpclose help :? diff --git a/src/nvim/testdir/test_makeencoding.vim b/src/nvim/testdir/test_makeencoding.vim index a3d5538a47..6e4c7af821 100644 --- a/src/nvim/testdir/test_makeencoding.vim +++ b/src/nvim/testdir/test_makeencoding.vim @@ -13,12 +13,19 @@ endif let s:script = 'test_makeencoding.py' -let s:message_tbl = { +if has('iconv') + let s:message_tbl = { \ 'utf-8': 'ÀÈÌÒÙ こんにちは 你好', \ 'latin1': 'ÀÈÌÒÙ', \ 'cp932': 'こんにちは', \ 'cp936': '你好', \} +else + let s:message_tbl = { + \ 'utf-8': 'ÀÈÌÒÙ こんにちは 你好', + \ 'latin1': 'ÀÈÌÒÙ', + \} +endif " Tests for :cgetfile and :lgetfile. diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 3c1b0050b5..f8c3161b40 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -158,6 +158,8 @@ func Test_set_completion() call assert_equal('"set fileencodings:ucs-bom,utf-8,default,latin1', @:) " Expand directories. + let shellslash = &shellslash + set shellslash call feedkeys(":set cdpath=./\<C-A>\<C-B>\"\<CR>", 'tx') call assert_match('./samples/ ', @:) call assert_notmatch('./small.vim ', @:) @@ -168,6 +170,7 @@ func Test_set_completion() call feedkeys(":set tags=./\\\\ dif\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"set tags=./\\ diff diffexpr diffopt', @:) + let &shellslash = shellslash endfunc func Test_set_errors() @@ -209,7 +212,14 @@ func Test_set_errors() call assert_fails('set statusline=%{', 'E540:') call assert_fails('set statusline=' . repeat("%p", 81), 'E541:') call assert_fails('set statusline=%(', 'E542:') - call assert_fails('set guicursor=x', 'E545:') + if has('cursorshape') + " This invalid value for 'guicursor' used to cause Vim to crash. + call assert_fails('set guicursor=i-ci,r-cr:h', 'E545:') + call assert_fails('set guicursor=i-ci', 'E545:') + call assert_fails('set guicursor=x', 'E545:') + call assert_fails('set guicursor=r-cr:horx', 'E548:') + call assert_fails('set guicursor=r-cr:hor0', 'E549:') + endif call assert_fails('set backupext=~ patchmode=~', 'E589:') call assert_fails('set winminheight=10 winheight=9', 'E591:') call assert_fails('set winminwidth=10 winwidth=9', 'E592:') diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index dd177fd633..85f93cf3da 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -850,17 +850,17 @@ func s:dir_stack_tests(cchar) let qf = g:Xgetlist() - call assert_equal('dir1/a/habits2.txt', bufname(qf[1].bufnr)) + call assert_equal(expand('dir1/a/habits2.txt'), bufname(qf[1].bufnr)) call assert_equal(1, qf[1].lnum) - call assert_equal('dir1/a/b/habits3.txt', bufname(qf[3].bufnr)) + call assert_equal(expand('dir1/a/b/habits3.txt'), bufname(qf[3].bufnr)) call assert_equal(2, qf[3].lnum) - call assert_equal('dir1/a/habits2.txt', bufname(qf[4].bufnr)) + call assert_equal(expand('dir1/a/habits2.txt'), bufname(qf[4].bufnr)) call assert_equal(7, qf[4].lnum) - call assert_equal('dir1/c/habits4.txt', bufname(qf[6].bufnr)) + call assert_equal(expand('dir1/c/habits4.txt'), bufname(qf[6].bufnr)) call assert_equal(3, qf[6].lnum) call assert_equal('habits1.txt', bufname(qf[9].bufnr)) call assert_equal(4, qf[9].lnum) - call assert_equal('dir2/habits5.txt', bufname(qf[11].bufnr)) + call assert_equal(expand('dir2/habits5.txt'), bufname(qf[11].bufnr)) call assert_equal(5, qf[11].lnum) let &efm=save_efm @@ -1065,7 +1065,7 @@ func Test_efm2() call assert_equal(8, len(l)) call assert_equal(89, l[4].lnum) call assert_equal(1, l[4].valid) - call assert_equal('unittests/dbfacadeTest.py', bufname(l[4].bufnr)) + call assert_equal(expand('unittests/dbfacadeTest.py'), bufname(l[4].bufnr)) " The following sequence of commands used to crash Vim set efm=%W%m @@ -1609,11 +1609,11 @@ func Test_two_windows() laddexpr 'one.txt:3:one one one' let loc_one = getloclist(one_id) - call assert_equal('Xone/a/one.txt', bufname(loc_one[1].bufnr)) + call assert_equal(expand('Xone/a/one.txt'), bufname(loc_one[1].bufnr)) call assert_equal(3, loc_one[1].lnum) let loc_two = getloclist(two_id) - call assert_equal('Xtwo/a/two.txt', bufname(loc_two[1].bufnr)) + call assert_equal(expand('Xtwo/a/two.txt'), bufname(loc_two[1].bufnr)) call assert_equal(5, loc_two[1].lnum) call win_gotoid(one_id) diff --git a/src/nvim/testdir/test_recover.vim b/src/nvim/testdir/test_recover.vim index 46d884a97c..beecb4cd0d 100644 --- a/src/nvim/testdir/test_recover.vim +++ b/src/nvim/testdir/test_recover.vim @@ -6,11 +6,6 @@ func Test_recover_root_dir() set dir=/ call assert_fails('recover', 'E305:') close! - - if has('win32') || filewritable('/') == 2 - " can write in / directory on MS-Windows - set dir=/notexist/ - endif call assert_fails('split Xtest', 'E303:') set dir& endfunc diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index 21f2363731..a2828b21d2 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -315,7 +315,7 @@ endfunc " Check using z= in new buffer (crash fixed by patch 7.4a.028). func Test_zeq_crash() new - set maxmem=512 spell + set spell call feedkeys('iasdz=:\"', 'tx') bwipe! diff --git a/src/nvim/testdir/test_stat.vim b/src/nvim/testdir/test_stat.vim index 1239fe9427..0a09130b0c 100644 --- a/src/nvim/testdir/test_stat.vim +++ b/src/nvim/testdir/test_stat.vim @@ -86,7 +86,7 @@ func Test_win32_symlink_dir() let res = system('dir C:\Users /a') if match(res, '\C<SYMLINKD> *All Users') >= 0 " Get the filetype of the symlink. - call assert_equal('dir', getftype('C:\Users\All Users')) + call assert_equal('link', getftype('C:\Users\All Users')) endif endif endfunc diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim index 0446bd9105..d3c0594c03 100644 --- a/src/nvim/testdir/test_system.vim +++ b/src/nvim/testdir/test_system.vim @@ -5,14 +5,12 @@ function! Test_System() return endif let out = system('echo 123') - " On Windows we may get a trailing space. - if out != "123 \n" - call assert_equal("123\n", out) - endif + call assert_equal("123\n", out) let out = systemlist('echo 123') - " On Windows we may get a trailing space and CR. - if out != ["123 \r"] + if &shell =~# 'cmd.exe$' + call assert_equal(["123\r"], out) + else call assert_equal(['123'], out) endif @@ -46,3 +44,47 @@ function! Test_System() call assert_fails('call system("wc -l", 99999)', 'E86:') endfunction + +function! Test_system_exmode() + if has('unix') " echo $? only works on Unix + let cmd = ' -es --headless -u NONE -c "source Xscript" +q; echo "result=$?"' + " Need to put this in a script, "catch" isn't found after an unknown + " function. + call writefile(['try', 'call doesnotexist()', 'catch', 'endtry'], 'Xscript') + let a = system(v:progpath . cmd) + call assert_match('result=0', a) + call assert_equal(0, v:shell_error) + endif + + " Error before try does set error flag. + call writefile(['call nosuchfunction()', 'try', 'call doesnotexist()', 'catch', 'endtry'], 'Xscript') + if has('unix') " echo $? only works on Unix + let a = system(v:progpath . cmd) + call assert_notequal('0', a[0]) + endif + + let cmd = ' -es --headless -u NONE -c "source Xscript" +q' + let a = system(v:progpath . cmd) + call assert_notequal(0, v:shell_error) + call delete('Xscript') + + if has('unix') " echo $? only works on Unix + let cmd = ' -es --headless -u NONE -c "call doesnotexist()" +q; echo $?' + let a = system(v:progpath. cmd) + call assert_notequal(0, a[0]) + endif + + let cmd = ' -es --headless -u NONE -c "call doesnotexist()" +q' + let a = system(v:progpath. cmd) + call assert_notequal(0, v:shell_error) + + if has('unix') " echo $? only works on Unix + let cmd = ' -es --headless -u NONE -c "call doesnotexist()|let a=1" +q; echo $?' + let a = system(v:progpath. cmd) + call assert_notequal(0, a[0]) + endif + + let cmd = ' -es --headless -u NONE -c "call doesnotexist()|let a=1" +q' + let a = system(v:progpath. cmd) + call assert_notequal(0, v:shell_error) +endfunc diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index d377062780..81ac2b6171 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -119,7 +119,7 @@ func Test_paused() let slept = WaitFor('g:val == 1') call assert_equal(1, g:val) if has('reltime') - call assert_inrange(0, 60, slept) + call assert_inrange(0, 100, slept) else call assert_inrange(0, 10, slept) endif diff --git a/src/nvim/testdir/test_unlet.vim b/src/nvim/testdir/test_unlet.vim index 96ba752d9f..3f06058d03 100644 --- a/src/nvim/testdir/test_unlet.vim +++ b/src/nvim/testdir/test_unlet.vim @@ -24,3 +24,7 @@ func Test_not_existing() call assert_true(v:exception =~ ':E108:') endtry endfunc + +func Test_unlet_fails() + call assert_fails('unlet v:["count"]', 'E46:') +endfunc diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index b04a6ce4f9..0362820687 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -170,11 +170,21 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key) char buf[64]; size_t len = 0; int button, row, col; + static int last_pressed_button = 0; TermKeyMouseEvent ev; termkey_interpret_mouse(input->tk, key, &ev, &button, &row, &col); - if (ev != TERMKEY_MOUSE_PRESS && ev != TERMKEY_MOUSE_DRAG - && ev != TERMKEY_MOUSE_RELEASE) { + if ((ev == TERMKEY_MOUSE_RELEASE || ev == TERMKEY_MOUSE_DRAG) + && button == 0) { + // Some terminals (like urxvt) don't report which button was released. + // libtermkey reports button 0 in this case. + // For drag and release, we can reasonably infer the button to be the last + // pressed one. + button = last_pressed_button; + } + + if (button == 0 || (ev != TERMKEY_MOUSE_PRESS && ev != TERMKEY_MOUSE_DRAG + && ev != TERMKEY_MOUSE_RELEASE)) { return; } @@ -210,6 +220,7 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key) "ScrollWheelDown"); } else { len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Mouse"); + last_pressed_button = button; } break; case TERMKEY_MOUSE_DRAG: diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index bfebe0442d..65957626cb 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -450,7 +450,8 @@ static void update_attrs(UI *ui, HlAttrs attrs) int attr = ui->rgb ? attrs.rgb_ae_attr : attrs.cterm_ae_attr; bool bold = attr & HL_BOLD; bool italic = attr & HL_ITALIC; - bool reverse = attr & (HL_INVERSE | HL_STANDOUT); + bool reverse = attr & HL_INVERSE; + bool standout = attr & HL_STANDOUT; bool underline = attr & (HL_UNDERLINE), undercurl = attr & (HL_UNDERCURL); if (unibi_get_str(data->ut, unibi_set_attributes)) { @@ -478,6 +479,9 @@ static void update_attrs(UI *ui, HlAttrs attrs) if (underline || undercurl) { unibi_out(ui, unibi_enter_underline_mode); } + if (standout) { + unibi_out(ui, unibi_enter_standout_mode); + } if (reverse) { unibi_out(ui, unibi_enter_reverse_mode); } @@ -601,8 +605,7 @@ static void cursor_goto(UI *ui, int row, int col) int n = col - grid->col; if (n <= (row == grid->row ? 4 : 2) && cheap_to_print(ui, grid->row, grid->col, n)) { - UGRID_FOREACH_CELL(grid, grid->row, grid->row, - grid->col, col - 1, { + UGRID_FOREACH_CELL(grid, grid->row, grid->row, grid->col, col - 1, { print_cell(ui, cell); }); } @@ -826,7 +829,7 @@ static void tui_cursor_goto(UI *ui, Integer row, Integer col) CursorShape tui_cursor_decode_shape(const char *shape_str) { - CursorShape shape = 0; + CursorShape shape; if (strequal(shape_str, "block")) { shape = SHAPE_BLOCK; } else if (strequal(shape_str, "vertical")) { @@ -834,7 +837,8 @@ CursorShape tui_cursor_decode_shape(const char *shape_str) } else if (strequal(shape_str, "horizontal")) { shape = SHAPE_HOR; } else { - EMSG2(_(e_invarg2), shape_str); + WLOG("Unknown shape value '%s'", shape_str); + shape = SHAPE_BLOCK; } return shape; } @@ -920,7 +924,6 @@ static void tui_set_mode(UI *ui, ModeShape mode) } TUIData *data = ui->data; cursorentry_T c = data->cursor_shapes[mode]; - int shape = c.shape; if (c.id != 0 && ui->rgb) { int attr = syn_id2attr(c.id); @@ -931,11 +934,12 @@ static void tui_set_mode(UI *ui, ModeShape mode) } } - switch (shape) { + int shape; + switch (c.shape) { + default: abort(); break; case SHAPE_BLOCK: shape = 1; break; case SHAPE_HOR: shape = 3; break; case SHAPE_VER: shape = 5; break; - default: WLOG("Unknown shape value %d", shape); break; } UNIBI_SET_NUM_VAR(data->params[0], shape + (int)(c.blinkon == 0)); unibi_out_ext(ui, data->unibi_ext.set_cursor_style); @@ -1766,7 +1770,8 @@ static void flush_buf(UI *ui) bufp++; } - if (!data->busy && data->is_invisible) { + if (!data->busy) { + assert(data->is_invisible); // not busy and the cursor is invisible. Write a "cursor normal" command // after writing the buffer. bufp->base = data->norm; diff --git a/src/nvim/ugrid.h b/src/nvim/ugrid.h index 60c9068eb1..035074846e 100644 --- a/src/nvim/ugrid.h +++ b/src/nvim/ugrid.h @@ -23,6 +23,8 @@ struct ugrid { UCell **cells; }; +// -V:UGRID_FOREACH_CELL:625 + #define UGRID_FOREACH_CELL(grid, top, bot, left, right, code) \ do { \ for (int row = top; row <= bot; row++) { \ diff --git a/src/nvim/ui.c b/src/nvim/ui.c index c70a02d960..42366fdb76 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -186,6 +186,10 @@ Dictionary hlattrs2dict(const HlAttrs *aep, bool use_rgb) PUT(hl, "bold", BOOLEAN_OBJ(true)); } + if (mask & HL_STANDOUT) { + PUT(hl, "standout", BOOLEAN_OBJ(true)); + } + if (mask & HL_UNDERLINE) { PUT(hl, "underline", BOOLEAN_OBJ(true)); } @@ -198,7 +202,7 @@ Dictionary hlattrs2dict(const HlAttrs *aep, bool use_rgb) PUT(hl, "italic", BOOLEAN_OBJ(true)); } - if (mask & (HL_INVERSE | HL_STANDOUT)) { + if (mask & HL_INVERSE) { PUT(hl, "reverse", BOOLEAN_OBJ(true)); } diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 56e0c0c454..a8bbeea035 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -116,7 +116,7 @@ static void ui_bridge_stop(UI *b) uv_mutex_lock(&bridge->mutex); stopped = bridge->stopped; uv_mutex_unlock(&bridge->mutex); - if (stopped) { + if (stopped) { // -V547 break; } loop_poll_events(&main_loop, 10); // Process one event. diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 35857510fc..e1ae4b4cc0 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -972,7 +972,7 @@ static u_entry_T *unserialize_uep(bufinfo_T * bi, bool *error, char_u **array = NULL; if (uep->ue_size > 0) { - if ((size_t)uep->ue_size < SIZE_MAX / sizeof(char_u *)) { + if ((size_t)uep->ue_size < SIZE_MAX / sizeof(char_u *)) { // -V547 array = xmalloc(sizeof(char_u *) * (size_t)uep->ue_size); memset(array, 0, sizeof(char_u *) * (size_t)uep->ue_size); } @@ -1404,7 +1404,7 @@ void u_read_undo(char *name, char_u *hash, char_u *orig_name) // sequence numbers of the headers. // When there are no headers uhp_table is NULL. if (num_head > 0) { - if ((size_t)num_head < SIZE_MAX / sizeof(*uhp_table)) { + if ((size_t)num_head < SIZE_MAX / sizeof(*uhp_table)) { // -V547 uhp_table = xmalloc((size_t)num_head * sizeof(*uhp_table)); } } diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 4196ecb9d2..1a7e55c11e 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -779,7 +779,8 @@ const char *viml_pexpr_repr_token(const ParserState *const pstate, eltkn_opt_scope_tab[token.data.opt.scope], (int)token.data.opt.len, token.data.opt.name) TKNARGS(kExprLexPlainIdentifier, "(scope=%s,autoload=%i)", - intchar2str(token.data.var.scope), (int)token.data.var.autoload) + intchar2str((int)token.data.var.scope), + (int)token.data.var.autoload) TKNARGS(kExprLexNumber, "(is_float=%i,base=%i,val=%lg)", (int)token.data.num.is_float, (int)token.data.num.base, @@ -2415,11 +2416,6 @@ viml_pexpr_parse_valid_colon: cur_token, _("E15: Expected value, got closing bracket: %.*s")); } - } else { - if (!kv_size(ast_stack)) { - new_top_node_p = top_node_p; - goto viml_pexpr_parse_bracket_closing_error; - } } do { new_top_node_p = kv_pop(ast_stack); @@ -2534,11 +2530,6 @@ viml_pexpr_parse_bracket_closing_error: cur_token, _("E15: Expected value, got closing figure brace: %.*s")); } - } else { - if (!kv_size(ast_stack)) { - new_top_node_p = top_node_p; - goto viml_pexpr_parse_figure_brace_closing_error; - } } do { new_top_node_p = kv_pop(ast_stack); diff --git a/src/nvim/window.c b/src/nvim/window.c index b4ef901d94..82fffe305c 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -4466,8 +4466,7 @@ static void frame_setwidth(frame_T *curfrp, int width) if (width <= room) break; if (run == 2 || curfrp->fr_height >= ROWS_AVAIL) { - if (width > room) - width = room; + width = room; break; } frame_setwidth(curfrp->fr_parent, width @@ -4807,7 +4806,7 @@ void win_new_height(win_T *wp, int height) // call win_new_height() recursively. validate_cursor(); } - if (wp->w_height != prev_height) { + if (wp->w_height != prev_height) { // -V547 return; // Recursive call already changed the size, bail out. } if (wp->w_wrow != wp->w_prev_fraction_row) { diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua new file mode 100644 index 0000000000..f3f9f93649 --- /dev/null +++ b/test/functional/api/command_spec.lua @@ -0,0 +1,80 @@ +local helpers = require('test.functional.helpers')(after_each) + +local NIL = helpers.NIL +local clear = helpers.clear +local command = helpers.command +local curbufmeths = helpers.curbufmeths +local eq = helpers.eq +local expect_err = helpers.expect_err +local meths = helpers.meths +local source = helpers.source + +describe('nvim_get_commands', function() + local cmd_dict = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='echo "Hello World"', name='Hello', nargs='1', range=NIL, register=false, script_id=0, } + local cmd_dict2 = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='pwd', name='Pwd', nargs='?', range=NIL, register=false, script_id=0, } + before_each(clear) + + it('gets empty list if no commands were defined', function() + eq({}, meths.get_commands({builtin=false})) + end) + + it('validates input', function() + expect_err('builtin=true not implemented', meths.get_commands, + {builtin=true}) + expect_err('unexpected key: foo', meths.get_commands, + {foo='blah'}) + end) + + it('gets global user-defined commands', function() + -- Define a command. + command('command -nargs=1 Hello echo "Hello World"') + eq({Hello=cmd_dict}, meths.get_commands({builtin=false})) + -- Define another command. + command('command -nargs=? Pwd pwd'); + eq({Hello=cmd_dict, Pwd=cmd_dict2}, meths.get_commands({builtin=false})) + -- Delete a command. + command('delcommand Pwd') + eq({Hello=cmd_dict}, meths.get_commands({builtin=false})) + end) + + it('gets buffer-local user-defined commands', function() + -- Define a buffer-local command. + command('command -buffer -nargs=1 Hello echo "Hello World"') + eq({Hello=cmd_dict}, curbufmeths.get_commands({builtin=false})) + -- Define another buffer-local command. + command('command -buffer -nargs=? Pwd pwd') + eq({Hello=cmd_dict, Pwd=cmd_dict2}, curbufmeths.get_commands({builtin=false})) + -- Delete a command. + command('delcommand Pwd') + eq({Hello=cmd_dict}, curbufmeths.get_commands({builtin=false})) + + -- {builtin=true} always returns empty for buffer-local case. + eq({}, curbufmeths.get_commands({builtin=true})) + end) + + it('gets various command attributes', function() + local cmd0 = { addr='arguments', bang=false, bar=false, complete='dir', complete_arg=NIL, count='10', definition='pwd <args>', name='TestCmd', nargs='0', range='10', register=false, script_id=0, } + local cmd1 = { addr=NIL, bang=false, bar=false, complete='custom', complete_arg='ListUsers', count=NIL, definition='!finger <args>', name='Finger', nargs='+', range=NIL, register=false, script_id=1, } + local cmd2 = { addr=NIL, bang=true, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253Q2_foo(<q-args>)', name='Cmd2', nargs='*', range=NIL, register=false, script_id=2, } + local cmd3 = { addr=NIL, bang=false, bar=true, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253Q3_ohyeah()', name='Cmd3', nargs='0', range=NIL, register=false, script_id=3, } + local cmd4 = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253Q4_just_great()', name='Cmd4', nargs='0', range=NIL, register=true, script_id=4, } + source([[ + command -complete=custom,ListUsers -nargs=+ Finger !finger <args> + ]]) + eq({Finger=cmd1}, meths.get_commands({builtin=false})) + command('command -complete=dir -addr=arguments -count=10 TestCmd pwd <args>') + eq({Finger=cmd1, TestCmd=cmd0}, meths.get_commands({builtin=false})) + + source([[ + command -bang -nargs=* Cmd2 call <SID>foo(<q-args>) + ]]) + source([[ + command -bar -nargs=0 Cmd3 call <SID>ohyeah() + ]]) + source([[ + command -register Cmd4 call <SID>just_great() + ]]) + -- TODO(justinmk): Order is stable but undefined. Sort before return? + eq({Cmd2=cmd2, Cmd3=cmd3, Cmd4=cmd4, Finger=cmd1, TestCmd=cmd0}, meths.get_commands({builtin=false})) + end) +end) diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index 2297a0760f..fed53a3dfd 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -99,5 +99,14 @@ describe('highlight api',function() eq(false, err) eq('Invalid highlight name: ', string.match(emsg, 'Invalid.*')) + + -- Test "standout" attribute. #8054 + eq({ underline = true, }, + meths.get_hl_by_name('cursorline', 0)); + command('hi CursorLine cterm=standout,underline term=standout,underline gui=standout,underline') + command('set cursorline') + eq({ underline = true, standout = true, }, + meths.get_hl_by_name('cursorline', 0)); + end) end) diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua index 3a10f9c60f..f52372bee3 100644 --- a/test/functional/api/keymap_spec.lua +++ b/test/functional/api/keymap_spec.lua @@ -11,7 +11,7 @@ local source = helpers.source local shallowcopy = global_helpers.shallowcopy -describe('get_keymap', function() +describe('nvim_get_keymap', function() before_each(clear) -- Basic mapping and table to be used to describe results diff --git a/test/functional/api/proc_spec.lua b/test/functional/api/proc_spec.lua new file mode 100644 index 0000000000..d99c26b6c2 --- /dev/null +++ b/test/functional/api/proc_spec.lua @@ -0,0 +1,81 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local eq = helpers.eq +local funcs = helpers.funcs +local iswin = helpers.iswin +local nvim_argv = helpers.nvim_argv +local ok = helpers.ok +local request = helpers.request +local retry = helpers.retry +local NIL = helpers.NIL + +describe('api', function() + before_each(clear) + + describe('nvim_get_proc_children', function() + it('returns child process ids', function() + local this_pid = funcs.getpid() + + local job1 = funcs.jobstart(nvim_argv) + retry(nil, nil, function() + eq(1, #request('nvim_get_proc_children', this_pid)) + end) + + local job2 = funcs.jobstart(nvim_argv) + retry(nil, nil, function() + eq(2, #request('nvim_get_proc_children', this_pid)) + end) + + funcs.jobstop(job1) + retry(nil, nil, function() + eq(1, #request('nvim_get_proc_children', this_pid)) + end) + + funcs.jobstop(job2) + retry(nil, nil, function() + eq(0, #request('nvim_get_proc_children', this_pid)) + end) + end) + + it('validates input', function() + local status, rv = pcall(request, "nvim_get_proc_children", -1) + eq(false, status) + eq("Invalid pid: -1", string.match(rv, "Invalid.*")) + + status, rv = pcall(request, "nvim_get_proc_children", 0) + eq(false, status) + eq("Invalid pid: 0", string.match(rv, "Invalid.*")) + + -- Assume PID 99999 does not exist. + status, rv = pcall(request, "nvim_get_proc_children", 99999) + eq(true, status) + eq({}, rv) + end) + end) + + describe('nvim_get_proc', function() + it('returns process info', function() + local pid = funcs.getpid() + local pinfo = request('nvim_get_proc', pid) + eq((iswin() and 'nvim.exe' or 'nvim'), pinfo.name) + ok(pinfo.pid == pid) + ok(type(pinfo.ppid) == 'number' and pinfo.ppid ~= pid) + end) + + it('validates input', function() + local status, rv = pcall(request, "nvim_get_proc", -1) + eq(false, status) + eq("Invalid pid: -1", string.match(rv, "Invalid.*")) + + status, rv = pcall(request, "nvim_get_proc", 0) + eq(false, status) + eq("Invalid pid: 0", string.match(rv, "Invalid.*")) + + -- Assume PID 99999 does not exist. + status, rv = pcall(request, "nvim_get_proc", 99999) + eq(true, status) + eq(NIL, rv) + end) + end) +end) diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua index 18229b54ff..e79a60fb10 100644 --- a/test/functional/api/server_requests_spec.lua +++ b/test/functional/api/server_requests_spec.lua @@ -11,6 +11,7 @@ local ok = helpers.ok local meths = helpers.meths local spawn, nvim_argv = helpers.spawn, helpers.nvim_argv local set_session = helpers.set_session +local expect_err = helpers.expect_err describe('server -> client', function() local cid @@ -221,9 +222,8 @@ describe('server -> client', function() end) it('returns an error if the request failed', function() - local status, err = pcall(eval, "rpcrequest(vim, 'does-not-exist')") - eq(false, status) - ok(nil ~= string.match(err, 'Failed to evaluate expression')) + expect_err('Vim:Invalid method name', + eval, "rpcrequest(vim, 'does-not-exist')") end) end) @@ -250,7 +250,7 @@ describe('server -> client', function() end) after_each(function() - funcs.jobstop(jobid) + pcall(funcs.jobstop, jobid) end) if helpers.pending_win32(pending) then return end @@ -261,7 +261,7 @@ describe('server -> client', function() eq({'notification', 'pong', {}}, next_msg()) eq("done!",funcs.rpcrequest(jobid, "write_stderr", "fluff\n")) eq({'notification', 'stderr', {0, {'fluff', ''}}}, next_msg()) - funcs.rpcrequest(jobid, "exit") + pcall(funcs.rpcrequest, jobid, "exit") eq({'notification', 'stderr', {0, {''}}}, next_msg()) eq({'notification', 'exit', {0, 0}}, next_msg()) end) @@ -308,8 +308,8 @@ describe('server -> client', function() it('via ipv4 address', function() local server = spawn(nvim_argv) set_session(server) - local address = funcs.serverstart("127.0.0.1:") - if #address == 0 then + local status, address = pcall(funcs.serverstart, "127.0.0.1:") + if not status then pending('no ipv4 stack', function() end) return end @@ -320,8 +320,8 @@ describe('server -> client', function() it('via ipv6 address', function() local server = spawn(nvim_argv) set_session(server) - local address = funcs.serverstart('::1:') - if #address == 0 then + local status, address = pcall(funcs.serverstart, '::1:') + if not status then pending('no ipv6 stack', function() end) return end diff --git a/test/functional/api/ui_spec.lua b/test/functional/api/ui_spec.lua new file mode 100644 index 0000000000..b028a50b02 --- /dev/null +++ b/test/functional/api/ui_spec.lua @@ -0,0 +1,37 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local eq = helpers.eq +local eval = helpers.eval +local expect_err = helpers.expect_err +local meths = helpers.meths +local request = helpers.request + +describe('nvim_ui_attach()', function() + before_each(function() + clear() + end) + it('handles very large width/height #2180', function() + local screen = Screen.new(999, 999) + screen:attach() + eq(999, eval('&lines')) + eq(999, eval('&columns')) + end) + it('invalid option returns error', function() + expect_err('No such UI option: foo', + meths.ui_attach, 80, 24, { foo={'foo'} }) + end) + it('validates channel arg', function() + expect_err('UI not attached to channel: 1', + request, 'nvim_ui_try_resize', 40, 10) + expect_err('UI not attached to channel: 1', + request, 'nvim_ui_set_option', 'rgb', true) + expect_err('UI not attached to channel: 1', + request, 'nvim_ui_detach') + + local screen = Screen.new() + screen:attach({rgb=false}) + expect_err('UI already attached to channel: 1', + request, 'nvim_ui_attach', 40, 10, { rgb=false }) + end) +end) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index bd56161a54..71fa6632c7 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -4,17 +4,20 @@ local global_helpers = require('test.helpers') local NIL = helpers.NIL local clear, nvim, eq, neq = helpers.clear, helpers.nvim, helpers.eq, helpers.neq +local command = helpers.command +local eval = helpers.eval +local funcs = helpers.funcs +local iswin = helpers.iswin +local meth_pcall = helpers.meth_pcall +local meths = helpers.meths local ok, nvim_async, feed = helpers.ok, helpers.nvim_async, helpers.feed local os_name = helpers.os_name -local meths = helpers.meths -local funcs = helpers.funcs local request = helpers.request -local meth_pcall = helpers.meth_pcall -local command = helpers.command -local iswin = helpers.iswin +local source = helpers.source -local intchar2lua = global_helpers.intchar2lua +local expect_err = global_helpers.expect_err local format_string = global_helpers.format_string +local intchar2lua = global_helpers.intchar2lua local mergedicts_copy = global_helpers.mergedicts_copy describe('api', function() @@ -38,20 +41,20 @@ describe('api', function() os.remove(fname) end) - it("parse error: fails (specific error), does NOT update v:errmsg", function() - -- Most API methods return generic errors (or no error) if a VimL - -- expression fails; nvim_command returns the VimL error details. + it('VimL validation error: fails with specific error', function() local status, rv = pcall(nvim, "command", "bogus_command") eq(false, status) -- nvim_command() failed. eq("E492:", string.match(rv, "E%d*:")) -- VimL error was returned. - eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + eq('', nvim('eval', 'v:errmsg')) -- v:errmsg was not updated. + eq('', eval('v:exception')) end) - it("runtime error: fails (specific error)", function() + it('VimL execution error: fails with specific error', function() local status, rv = pcall(nvim, "command_output", "buffer 23487") eq(false, status) -- nvim_command() failed. eq("E86: Buffer 23487 does not exist", string.match(rv, "E%d*:.*")) - eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + eq('', eval('v:errmsg')) -- v:errmsg was not updated. + eq('', eval('v:exception')) end) end) @@ -73,6 +76,8 @@ describe('api', function() it('captures command output', function() eq('this is\nspinal tap', nvim('command_output', [[echo "this is\nspinal tap"]])) + eq('no line ending!', + nvim('command_output', [[echon "no line ending!"]])) end) it('captures empty command output', function() @@ -105,21 +110,21 @@ describe('api', function() eq(':!echo foo\r\n\nfoo'..win_lf..'\n', nvim('command_output', [[!echo foo]])) end) - it("parse error: fails (specific error), does NOT update v:errmsg", function() + it('VimL validation error: fails with specific error', function() local status, rv = pcall(nvim, "command_output", "bogus commannnd") eq(false, status) -- nvim_command_output() failed. eq("E492: Not an editor command: bogus commannnd", string.match(rv, "E%d*:.*")) - eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + eq('', eval('v:errmsg')) -- v:errmsg was not updated. -- Verify NO hit-enter prompt. eq({mode='n', blocking=false}, nvim("get_mode")) end) - it("runtime error: fails (specific error)", function() + it('VimL execution error: fails with specific error', function() local status, rv = pcall(nvim, "command_output", "buffer 42") eq(false, status) -- nvim_command_output() failed. eq("E86: Buffer 42 does not exist", string.match(rv, "E%d*:.*")) - eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + eq('', eval('v:errmsg')) -- v:errmsg was not updated. -- Verify NO hit-enter prompt. eq({mode='n', blocking=false}, nvim("get_mode")) end) @@ -141,11 +146,10 @@ describe('api', function() eq(2, request("vim_eval", "1+1")) end) - it("VimL error: fails (generic error), does NOT update v:errmsg", function() - local status, rv = pcall(nvim, "eval", "bogus expression") - eq(false, status) -- nvim_eval() failed. - ok(nil ~= string.find(rv, "Failed to evaluate expression")) - eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + it("VimL error: returns error details, does NOT update v:errmsg", function() + expect_err('E121: Undefined variable: bogus', request, + 'nvim_eval', 'bogus expression') + eq('', eval('v:errmsg')) -- v:errmsg was not updated. end) end) @@ -157,11 +161,100 @@ describe('api', function() eq('foo', nvim('call_function', 'simplify', {'this/./is//redundant/../../../foo'})) end) - it("VimL error: fails (generic error), does NOT update v:errmsg", function() - local status, rv = pcall(nvim, "call_function", "bogus function", {"arg1"}) - eq(false, status) -- nvim_call_function() failed. - ok(nil ~= string.find(rv, "Error calling function")) - eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + it("VimL validation error: returns specific error, does NOT update v:errmsg", function() + expect_err('E117: Unknown function: bogus function', request, + 'nvim_call_function', 'bogus function', {'arg1'}) + expect_err('E119: Not enough arguments for function: atan', request, + 'nvim_call_function', 'atan', {}) + eq('', eval('v:exception')) + eq('', eval('v:errmsg')) -- v:errmsg was not updated. + end) + + it("VimL error: returns error details, does NOT update v:errmsg", function() + expect_err('E808: Number or Float required', request, + 'nvim_call_function', 'atan', {'foo'}) + expect_err('Invalid channel stream "xxx"', request, + 'nvim_call_function', 'chanclose', {999, 'xxx'}) + expect_err('E900: Invalid channel id', request, + 'nvim_call_function', 'chansend', {999, 'foo'}) + eq('', eval('v:exception')) + eq('', eval('v:errmsg')) -- v:errmsg was not updated. + end) + + it("VimL exception: returns exception details, does NOT update v:errmsg", function() + source([[ + function! Foo() abort + throw 'wtf' + endfunction + ]]) + expect_err('wtf', request, + 'nvim_call_function', 'Foo', {}) + eq('', eval('v:exception')) + eq('', eval('v:errmsg')) -- v:errmsg was not updated. + end) + + it('validates args', function() + local too_many_args = { 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x' } + source([[ + function! Foo(...) abort + echo a:000 + endfunction + ]]) + -- E740 + expect_err('Function called with too many arguments', request, + 'nvim_call_function', 'Foo', too_many_args) + end) + end) + + describe('nvim_call_dict_function', function() + it('invokes VimL dict function', function() + source([[ + function! F(name) dict + return self.greeting.', '.a:name.'!' + endfunction + let g:test_dict_fn = { 'greeting':'Hello', 'F':function('F') } + + let g:test_dict_fn2 = { 'greeting':'Hi' } + function g:test_dict_fn2.F2(name) + return self.greeting.', '.a:name.' ...' + endfunction + ]]) + + -- :help Dictionary-function + eq('Hello, World!', nvim('call_dict_function', 'g:test_dict_fn', 'F', {'World'})) + -- Funcref is sent as NIL over RPC. + eq({ greeting = 'Hello', F = NIL }, nvim('get_var', 'test_dict_fn')) + + -- :help numbered-function + eq('Hi, Moon ...', nvim('call_dict_function', 'g:test_dict_fn2', 'F2', {'Moon'})) + -- Funcref is sent as NIL over RPC. + eq({ greeting = 'Hi', F2 = NIL }, nvim('get_var', 'test_dict_fn2')) + + -- Function specified via RPC dict. + source('function! G() dict\n return "@".(self.result)."@"\nendfunction') + eq('@it works@', nvim('call_dict_function', { result = 'it works', G = 'G'}, 'G', {})) + end) + + it('validates args', function() + command('let g:d={"baz":"zub","meep":[]}') + expect_err('Not found: bogus', request, + 'nvim_call_dict_function', 'g:d', 'bogus', {1,2}) + expect_err('Not a function: baz', request, + 'nvim_call_dict_function', 'g:d', 'baz', {1,2}) + expect_err('Not a function: meep', request, + 'nvim_call_dict_function', 'g:d', 'meep', {1,2}) + expect_err('E117: Unknown function: f', request, + 'nvim_call_dict_function', { f = '' }, 'f', {1,2}) + expect_err('Not a function: f', request, + 'nvim_call_dict_function', "{ 'f': '' }", 'f', {1,2}) + expect_err('dict argument type must be String or Dictionary', request, + 'nvim_call_dict_function', 42, 'f', {1,2}) + expect_err('Failed to evaluate dict expression', request, + 'nvim_call_dict_function', 'foo', 'f', {1,2}) + expect_err('dict not found', request, + 'nvim_call_dict_function', '42', 'f', {1,2}) + expect_err('Invalid %(empty%) function name', request, + 'nvim_call_dict_function', "{ 'f': '' }", '', {1,2}) end) end) @@ -289,6 +382,21 @@ describe('api', function() eq(false, status) ok(err:match('Invalid option name') ~= nil) end) + + it('updates where the option was last set from', function() + nvim('set_option', 'equalalways', false) + local status, rv = pcall(nvim, 'command_output', + 'verbose set equalalways?') + eq(true, status) + ok(nil ~= string.find(rv, 'noequalalways\n'.. + '\tLast set from API client %(channel id %d+%)')) + + nvim('execute_lua', 'vim.api.nvim_set_option("equalalways", true)', {}) + status, rv = pcall(nvim, 'command_output', + 'verbose set equalalways?') + eq(true, status) + eq(' equalalways\n\tLast set from Lua', rv) + end) end) describe('nvim_{get,set}_current_buf, nvim_list_bufs', function() @@ -579,7 +687,8 @@ describe('api', function() screen:set_default_attr_ids({ [0] = {bold=true, foreground=Screen.colors.Blue}, [1] = {foreground = Screen.colors.White, background = Screen.colors.Red}, - [2] = {bold = true, foreground = Screen.colors.SeaGreen} + [2] = {bold = true, foreground = Screen.colors.SeaGreen}, + [3] = {bold = true, reverse = true}, }) end) @@ -600,11 +709,11 @@ describe('api', function() it('shows return prompt when more than &cmdheight lines', function() nvim_async('err_write', 'something happened\nvery bad\n') screen:expect([[ + | {0:~ }| {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| + {3: }| {1:something happened} | {1:very bad} | {2:Press ENTER or type command to continue}^ | @@ -614,9 +723,9 @@ describe('api', function() it('shows return prompt after all lines are shown', function() nvim_async('err_write', 'FAILURE\nERROR\nEXCEPTION\nTRACEBACK\n') screen:expect([[ + | {0:~ }| - {0:~ }| - {0:~ }| + {3: }| {1:FAILURE} | {1:ERROR} | {1:EXCEPTION} | @@ -644,11 +753,11 @@ describe('api', function() -- shows up to &cmdheight lines nvim_async('err_write', 'more fail\ntoo fail\n') screen:expect([[ + | {0:~ }| {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| + {3: }| {1:more fail} | {1:too fail} | {2:Press ENTER or type command to continue}^ | diff --git a/test/functional/autocmd/cmdline_spec.lua b/test/functional/autocmd/cmdline_spec.lua index 8ea086fb46..3f0504d02f 100644 --- a/test/functional/autocmd/cmdline_spec.lua +++ b/test/functional/autocmd/cmdline_spec.lua @@ -59,24 +59,25 @@ describe('cmdline autocommands', function() [1] = {bold = true, foreground = Screen.colors.Blue1}, [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, [3] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [4] = {bold = true, reverse = true}, }) command("autocmd CmdlineEnter * echoerr 'FAIL'") command("autocmd CmdlineLeave * echoerr 'very error'") feed(':') screen:expect([[ + | {1:~ }| {1:~ }| {1:~ }| - {1:~ }| - {1:~ }| + {4: }| : | {2:E5500: autocmd has thrown an exception: Vim(echoerr):FAIL} | :^ | ]]) feed("put ='lorem ipsum'<cr>") screen:expect([[ - {1:~ }| - {1:~ }| + | + {4: }| : | {2:E5500: autocmd has thrown an exception: Vim(echoerr):FAIL} | :put ='lorem ipsum' | diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua index 63cf0bc410..7979e91a4c 100644 --- a/test/functional/autocmd/dirchanged_spec.lua +++ b/test/functional/autocmd/dirchanged_spec.lua @@ -154,4 +154,11 @@ describe('autocmd DirChanged', function() eq('Failed to change directory', string.match(err, ': (.*)')) eq({cwd=dirs[2], scope='global'}, eval('g:ev')) end) + + it('works when local to buffer', function() + command('let g:triggered = 0') + command('autocmd DirChanged <buffer> let g:triggered = 1') + command('cd '..dirs[1]) + eq(1, eval('g:triggered')) + end) end) diff --git a/test/functional/autocmd/termclose_spec.lua b/test/functional/autocmd/termclose_spec.lua index 9918cbe4fa..db4e5379d0 100644 --- a/test/functional/autocmd/termclose_spec.lua +++ b/test/functional/autocmd/termclose_spec.lua @@ -1,3 +1,4 @@ +local luv = require('luv') local helpers = require('test.functional.helpers')(after_each) local clear, command, nvim, nvim_dir = @@ -31,7 +32,7 @@ describe('TermClose event', function() end) it('kills job trapping SIGTERM', function() - if helpers.pending_win32(pending) then return end + if iswin() then return end nvim('set_option', 'shell', 'sh') nvim('set_option', 'shellcmdflag', '-c') command([[ let g:test_job = jobstart('trap "" TERM && echo 1 && sleep 60', { ]] @@ -39,17 +40,19 @@ describe('TermClose event', function() .. [[ 'on_exit': {-> execute('let g:test_job_exited = 1')}}) ]]) retry(nil, nil, function() eq(1, eval('get(g:, "test_job_started", 0)')) end) - local start = os.time() + luv.update_time() + local start = luv.now() command('call jobstop(g:test_job)') retry(nil, nil, function() eq(1, eval('get(g:, "test_job_exited", 0)')) end) - local duration = os.time() - start - -- nvim starts sending SIGTERM after KILL_TIMEOUT_MS - ok(duration >= 2) - ok(duration <= 4) -- <= 2 + delta because of slow CI + luv.update_time() + local duration = luv.now() - start + -- Nvim begins SIGTERM after KILL_TIMEOUT_MS. + ok(duration >= 2000) + ok(duration <= 4000) -- Epsilon for slow CI end) - it('kills pty job trapping SIGHUP and SIGTERM', function() - if helpers.pending_win32(pending) then return end + it('kills PTY job trapping SIGHUP and SIGTERM', function() + if iswin() then return end nvim('set_option', 'shell', 'sh') nvim('set_option', 'shellcmdflag', '-c') command([[ let g:test_job = jobstart('trap "" HUP TERM && echo 1 && sleep 60', { ]] @@ -58,13 +61,15 @@ describe('TermClose event', function() .. [[ 'on_exit': {-> execute('let g:test_job_exited = 1')}}) ]]) retry(nil, nil, function() eq(1, eval('get(g:, "test_job_started", 0)')) end) - local start = os.time() + luv.update_time() + local start = luv.now() command('call jobstop(g:test_job)') retry(nil, nil, function() eq(1, eval('get(g:, "test_job_exited", 0)')) end) - local duration = os.time() - start - -- nvim starts sending kill after 2*KILL_TIMEOUT_MS - ok(duration >= 4) - ok(duration <= 7) -- <= 4 + delta because of slow CI + luv.update_time() + local duration = luv.now() - start + -- Nvim begins SIGKILL after (2 * KILL_TIMEOUT_MS). + ok(duration >= 4000) + ok(duration <= 7000) -- Epsilon for slow CI end) it('reports the correct <abuf>', function() diff --git a/test/functional/clipboard/clipboard_provider_spec.lua b/test/functional/clipboard/clipboard_provider_spec.lua index a3ea3b568f..a40c080a6d 100644 --- a/test/functional/clipboard/clipboard_provider_spec.lua +++ b/test/functional/clipboard/clipboard_provider_spec.lua @@ -83,7 +83,14 @@ local function basic_register_test(noblock) end describe('clipboard', function() - before_each(clear) + local screen + + before_each(function() + clear() + screen = Screen.new(72, 4) + screen:attach() + command("set display-=msgsep") + end) it('unnamed register works without provider', function() eq('"', eval('v:register')) @@ -92,8 +99,6 @@ describe('clipboard', function() it('`:redir @+>` with invalid g:clipboard shows exactly one error #7184', function() - local screen = Screen.new(72, 4) - screen:attach() command("let g:clipboard = 'bogus'") feed_command('redir @+> | :silent echo system("cat CONTRIBUTING.md") | redir END') screen:expect([[ @@ -106,8 +111,6 @@ describe('clipboard', function() it('`:redir @+>|bogus_cmd|redir END` + invalid g:clipboard must not recurse #7184', function() - local screen = Screen.new(72, 4) - screen:attach() command("let g:clipboard = 'bogus'") feed_command('redir @+> | bogus_cmd | redir END') screen:expect([[ @@ -123,8 +126,6 @@ describe('clipboard', function() eq('', eval('provider#clipboard#Executable()')) eq('clipboard: invalid g:clipboard', eval('provider#clipboard#Error()')) - local screen = Screen.new(72, 4) - screen:attach() command("let g:clipboard = 'bogus'") -- Explicit clipboard attempt, should show a hint message. feed_command('let @+="foo"') @@ -493,10 +494,10 @@ describe('clipboard', function() feed_command("let g:test_clip['+'] = ['such', 'plus', 'stuff']") feed_command("registers") screen:expect([[ - ~ | - ~ | - ~ | - ~ | + | + {0:~ }| + {0:~ }| + {4: }| :registers | {1:--- Registers ---} | "* some{2:^J}star data{2:^J} | @@ -504,10 +505,11 @@ describe('clipboard', function() ": let g:test_clip['+'] = ['such', 'plus', 'stuff'] | {3:Press ENTER or type command to continue}^ | ]], { + [0] = {bold = true, foreground = Screen.colors.Blue}, [1] = {bold = true, foreground = Screen.colors.Fuchsia}, [2] = {foreground = Screen.colors.Blue}, - [3] = {bold = true, foreground = Screen.colors.SeaGreen}}, - {{bold = true, foreground = Screen.colors.Blue}}) + [3] = {bold = true, foreground = Screen.colors.SeaGreen}, + [4] = {bold = true, reverse = true}}) feed('<cr>') -- clear out of Press ENTER screen end) diff --git a/test/functional/core/exit_spec.lua b/test/functional/core/exit_spec.lua index 188a6a2c11..80c65e4544 100644 --- a/test/functional/core/exit_spec.lua +++ b/test/functional/core/exit_spec.lua @@ -1,6 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local command = helpers.command +local feed_command = helpers.feed_command local eval = helpers.eval local eq = helpers.eq local run = helpers.run @@ -33,6 +34,18 @@ describe('v:exiting', function() end run(on_request, nil, on_setup) end) + it('is 0 on exit from ex-mode involving try-catch', function() + local function on_setup() + command('autocmd VimLeavePre * call rpcrequest('..cid..', "")') + command('autocmd VimLeave * call rpcrequest('..cid..', "")') + feed_command('call feedkey("Q")','try', 'call NoFunction()', 'catch', 'echo "bye"', 'endtry', 'quit') + end + local function on_request() + eq(0, eval('v:exiting')) + return '' + end + run(on_request, nil, on_setup) + end) end) describe(':cquit', function() diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua new file mode 100644 index 0000000000..09533e4e60 --- /dev/null +++ b/test/functional/core/fileio_spec.lua @@ -0,0 +1,68 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local feed = helpers.feed +local funcs = helpers.funcs +local nvim_prog = helpers.nvim_prog +local request = helpers.request +local retry = helpers.retry +local rmdir = helpers.rmdir +local sleep = helpers.sleep + +describe('fileio', function() + before_each(function() + end) + after_each(function() + command(':qall!') + os.remove('Xtest_startup_shada') + os.remove('Xtest_startup_file1') + os.remove('Xtest_startup_file2') + rmdir('Xtest_startup_swapdir') + end) + + it('fsync() codepaths #8304', function() + clear({ args={ '-i', 'Xtest_startup_shada', + '--cmd', 'set directory=Xtest_startup_swapdir' } }) + + -- These cases ALWAYS force fsync (regardless of 'fsync' option): + + -- 1. Idle (CursorHold) with modified buffers (+ 'swapfile'). + command('write Xtest_startup_file1') + feed('ifoo<esc>h') + command('write') + eq(0, request('nvim__stats').fsync) -- 'nofsync' is the default. + command('set swapfile') + command('set updatetime=1') + feed('izub<esc>h') -- File is 'modified'. + sleep(3) -- Allow 'updatetime' to expire. + retry(3, nil, function() + eq(1, request('nvim__stats').fsync) + end) + command('set updatetime=9999') + + -- 2. Exit caused by deadly signal (+ 'swapfile'). + local j = funcs.jobstart({ nvim_prog, '-u', 'NONE', '-i', + 'Xtest_startup_shada', '--headless', + '-c', 'set swapfile', + '-c', 'write Xtest_startup_file2', + '-c', 'put =localtime()', }) + sleep(10) -- Let Nvim start. + funcs.jobstop(j) -- Send deadly signal. + + -- 3. SIGPWR signal. + -- ?? + + -- 4. Explicit :preserve command. + command('preserve') + eq(2, request('nvim__stats').fsync) + + -- 5. Enable 'fsync' option, write file. + command('set fsync') + feed('ibaz<esc>h') + command('write') + eq(4, request('nvim__stats').fsync) + end) +end) + diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index c5326aedfe..e90339b0cd 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -6,6 +6,10 @@ local clear, eq, eval, exc_exec, feed_command, feed, insert, neq, next_msg, nvim helpers.nvim_dir, helpers.ok, helpers.source, helpers.write_file, helpers.mkdir, helpers.rmdir local command = helpers.command +local funcs = helpers.funcs +local retry = helpers.retry +local meths = helpers.meths +local NIL = helpers.NIL local wait = helpers.wait local iswin = helpers.iswin local get_pathsep = helpers.get_pathsep @@ -15,14 +19,18 @@ local expect_twostreams = helpers.expect_twostreams local expect_msg_seq = helpers.expect_msg_seq local Screen = require('test.functional.ui.screen') +-- Kill process with given pid +local function os_kill(pid) + return os.execute((iswin() + and 'taskkill /f /t /pid '..pid..' > nul' + or 'kill -9 '..pid..' > /dev/null')) +end + describe('jobs', function() local channel before_each(function() clear() - if iswin() then - helpers.set_shell_powershell() - end channel = nvim('get_api_info')[1] nvim('set_var', 'channel', channel) source([[ @@ -48,7 +56,7 @@ describe('jobs', function() it('uses &shell and &shellcmdflag if passed a string', function() nvim('command', "let $VAR = 'abc'") if iswin() then - nvim('command', "let j = jobstart('echo $env:VAR', g:job_opts)") + nvim('command', "let j = jobstart('echo %VAR%', g:job_opts)") else nvim('command', "let j = jobstart('echo $VAR', g:job_opts)") end @@ -60,7 +68,7 @@ describe('jobs', function() it('changes to given / directory', function() nvim('command', "let g:job_opts.cwd = '/'") if iswin() then - nvim('command', "let j = jobstart('(Get-Location).Path', g:job_opts)") + nvim('command', "let j = jobstart('cd', g:job_opts)") else nvim('command', "let j = jobstart('pwd', g:job_opts)") end @@ -75,7 +83,7 @@ describe('jobs', function() mkdir(dir) nvim('command', "let g:job_opts.cwd = '" .. dir .. "'") if iswin() then - nvim('command', "let j = jobstart('(Get-Location).Path', g:job_opts)") + nvim('command', "let j = jobstart('cd', g:job_opts)") else nvim('command', "let j = jobstart('pwd', g:job_opts)") end @@ -99,7 +107,7 @@ describe('jobs', function() local _, err = pcall(function() nvim('command', "let g:job_opts.cwd = '" .. dir .. "'") if iswin() then - nvim('command', "let j = jobstart('pwd|%{$_.Path}', g:job_opts)") + nvim('command', "let j = jobstart('cd', g:job_opts)") else nvim('command', "let j = jobstart('pwd', g:job_opts)") end @@ -130,10 +138,8 @@ describe('jobs', function() end) it('invokes callbacks when the job writes and exits', function() - -- TODO: hangs on Windows - if helpers.pending_win32(pending) then return end nvim('command', "let g:job_opts.on_stderr = function('OnEvent')") - nvim('command', [[call jobstart('echo ""', g:job_opts)]]) + nvim('command', [[call jobstart(has('win32') ? 'echo:' : 'echo', g:job_opts)]]) expect_twostreams({{'notification', 'stdout', {0, {'', ''}}}, {'notification', 'stdout', {0, {''}}}}, {{'notification', 'stderr', {0, {''}}}}) @@ -141,15 +147,28 @@ describe('jobs', function() end) it('allows interactive commands', function() - if helpers.pending_win32(pending) then return end -- TODO: Need `cat`. - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") + nvim('command', "let j = jobstart(has('win32') ? ['find', '/v', ''] : ['cat', '-'], g:job_opts)") neq(0, eval('j')) nvim('command', 'call jobsend(j, "abc\\n")') eq({'notification', 'stdout', {0, {'abc', ''}}}, next_msg()) nvim('command', 'call jobsend(j, "123\\nxyz\\n")') - eq({'notification', 'stdout', {0, {'123', 'xyz', ''}}}, next_msg()) + expect_msg_seq( + { {'notification', 'stdout', {0, {'123', 'xyz', ''}}} + }, + -- Alternative sequence: + { {'notification', 'stdout', {0, {'123', ''}}}, + {'notification', 'stdout', {0, {'xyz', ''}}} + } + ) nvim('command', 'call jobsend(j, [123, "xyz", ""])') - eq({'notification', 'stdout', {0, {'123', 'xyz', ''}}}, next_msg()) + expect_msg_seq( + { {'notification', 'stdout', {0, {'123', 'xyz', ''}}} + }, + -- Alternative sequence: + { {'notification', 'stdout', {0, {'123', ''}}}, + {'notification', 'stdout', {0, {'xyz', ''}}} + } + ) nvim('command', "call jobstop(j)") eq({'notification', 'stdout', {0, {''}}}, next_msg()) eq({'notification', 'exit', {0, 0}}, next_msg()) @@ -222,16 +241,14 @@ describe('jobs', function() end) it('closes the job streams with jobclose', function() - if helpers.pending_win32(pending) then return end -- TODO: Need `cat`. - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") + nvim('command', "let j = jobstart(has('win32') ? ['find', '/v', ''] : ['cat', '-'], g:job_opts)") nvim('command', 'call jobclose(j, "stdin")') eq({'notification', 'stdout', {0, {''}}}, next_msg()) - eq({'notification', 'exit', {0, 0}}, next_msg()) + eq({'notification', 'exit', {0, iswin() and 1 or 0}}, next_msg()) end) it("disallows jobsend on a job that closed stdin", function() - if helpers.pending_win32(pending) then return end -- TODO: Need `cat`. - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") + nvim('command', "let j = jobstart(has('win32') ? ['find', '/v', ''] : ['cat', '-'], g:job_opts)") nvim('command', 'call jobclose(j, "stdin")') eq(false, pcall(function() nvim('command', 'call jobsend(j, ["some data"])') @@ -244,8 +261,7 @@ describe('jobs', function() end) it('disallows jobstop twice on the same job', function() - if helpers.pending_win32(pending) then return end -- TODO: Need `cat`. - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") + nvim('command', "let j = jobstart(has('win32') ? ['find', '/v', ''] : ['cat', '-'], g:job_opts)") neq(0, eval('j')) eq(true, pcall(eval, "jobstop(j)")) eq(false, pcall(eval, "jobstop(j)")) @@ -256,41 +272,49 @@ describe('jobs', function() end) it('can get the pid value using getpid', function() - if helpers.pending_win32(pending) then return end -- TODO: Need `cat`. - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") + nvim('command', "let j = jobstart(has('win32') ? ['find', '/v', ''] : ['cat', '-'], g:job_opts)") local pid = eval('jobpid(j)') - eq(0,os.execute('ps -p '..pid..' > /dev/null')) + neq(NIL, meths.get_proc(pid)) nvim('command', 'call jobstop(j)') eq({'notification', 'stdout', {0, {''}}}, next_msg()) - eq({'notification', 'exit', {0, 0}}, next_msg()) - neq(0,os.execute('ps -p '..pid..' > /dev/null')) + if iswin() then + expect_msg_seq( + -- win64 + { {'notification', 'exit', {0, 1}} + }, + -- win32 + { {'notification', 'exit', {0, 15}} + } + ) + else + eq({'notification', 'exit', {0, 0}}, next_msg()) + end + eq(NIL, meths.get_proc(pid)) end) it("do not survive the exit of nvim", function() - if helpers.pending_win32(pending) then return end -- use sleep, which doesn't die on stdin close - nvim('command', "let g:j = jobstart(['sleep', '1000'], g:job_opts)") + nvim('command', "let g:j = jobstart(has('win32') ? ['ping', '-n', '1001', '127.0.0.1'] : ['sleep', '1000'], g:job_opts)") local pid = eval('jobpid(g:j)') - eq(0,os.execute('ps -p '..pid..' > /dev/null')) + neq(NIL, meths.get_proc(pid)) clear() - neq(0,os.execute('ps -p '..pid..' > /dev/null')) + eq(NIL, meths.get_proc(pid)) end) it('can survive the exit of nvim with "detach"', function() - if helpers.pending_win32(pending) then return end nvim('command', 'let g:job_opts.detach = 1') - nvim('command', "let g:j = jobstart(['sleep', '1000'], g:job_opts)") + nvim('command', "let g:j = jobstart(has('win32') ? ['ping', '-n', '1001', '127.0.0.1'] : ['sleep', '1000'], g:job_opts)") local pid = eval('jobpid(g:j)') - eq(0,os.execute('ps -p '..pid..' > /dev/null')) + neq(NIL, meths.get_proc(pid)) clear() - eq(0,os.execute('ps -p '..pid..' > /dev/null')) + neq(NIL, meths.get_proc(pid)) -- clean up after ourselves - os.execute('kill -9 '..pid..' > /dev/null') + eq(0, os_kill(pid)) end) it('can pass user data to the callback', function() nvim('command', 'let g:job_opts.user = {"n": 5, "s": "str", "l": [1]}') - nvim('command', [[call jobstart('echo "foo"', g:job_opts)]]) + nvim('command', [[call jobstart('echo foo', g:job_opts)]]) local data = {n = 5, s = 'str', l = {1}} expect_msg_seq( { {'notification', 'stdout', {data, {'foo', ''}}}, @@ -308,14 +332,14 @@ describe('jobs', function() it('can omit data callbacks', function() nvim('command', 'unlet g:job_opts.on_stdout') nvim('command', 'let g:job_opts.user = 5') - nvim('command', [[call jobstart('echo "foo"', g:job_opts)]]) + nvim('command', [[call jobstart('echo foo', g:job_opts)]]) eq({'notification', 'exit', {5, 0}}, next_msg()) end) it('can omit exit callback', function() nvim('command', 'unlet g:job_opts.on_exit') nvim('command', 'let g:job_opts.user = 5') - nvim('command', [[call jobstart('echo "foo"', g:job_opts)]]) + nvim('command', [[call jobstart('echo foo', g:job_opts)]]) expect_msg_seq( { {'notification', 'stdout', {5, {'foo', ''} } }, {'notification', 'stdout', {5, {''} } }, @@ -397,15 +421,14 @@ describe('jobs', function() end) it('does not repeat output with slow output handlers', function() - if helpers.pending_win32(pending) then return end source([[ let d = {'data': []} function! d.on_stdout(job, data, event) dict - call add(self.data, a:data) + call add(self.data, Normalize(a:data)) sleep 200m endfunction if has('win32') - let cmd = '1,2,3,4,5 | foreach-object -process {echo $_; sleep 0.1}' + let cmd = 'for /L %I in (1,1,5) do @(echo %I& ping -n 2 127.0.0.1 > nul)' else let cmd = ['sh', '-c', 'for i in $(seq 1 5); do echo $i; sleep 0.1; done'] endif @@ -424,7 +447,7 @@ describe('jobs', function() endfunction let Callback = function('PrintArgs', ["foo", "bar"]) let g:job_opts = {'on_stdout': Callback} - call jobstart('echo "some text"', g:job_opts) + call jobstart('echo some text', g:job_opts) ]]) expect_msg_seq( { {'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, @@ -444,7 +467,7 @@ describe('jobs', function() 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) + call jobstart('echo some text', g:job_opts) ]]) expect_msg_seq( { {'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, @@ -459,7 +482,7 @@ describe('jobs', function() 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) + call jobstart('echo some text', g:job_opts) ]]) expect_msg_seq( { {'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, @@ -472,9 +495,20 @@ describe('jobs', function() end) describe('jobwait', function() + before_each(function() + if iswin() then + helpers.set_shell_powershell() + end + end) + it('returns a list of status codes', function() source([[ - call rpcnotify(g:channel, 'wait', jobwait([ + call rpcnotify(g:channel, 'wait', jobwait(has('win32') ? [ + \ jobstart('Start-Sleep -Milliseconds 100; exit 4'), + \ jobstart('Start-Sleep -Milliseconds 300; exit 5'), + \ jobstart('Start-Sleep -Milliseconds 500; exit 6'), + \ jobstart('Start-Sleep -Milliseconds 700; exit 7') + \ ] : [ \ jobstart('sleep 0.10; exit 4'), \ jobstart('sleep 0.110; exit 5'), \ jobstart('sleep 0.210; exit 6'), @@ -494,7 +528,12 @@ describe('jobs', function() endif let g:exits += 1 endfunction - call jobwait([ + call jobwait(has('win32') ? [ + \ jobstart('Start-Sleep -Milliseconds 100; exit 5', g:dict), + \ jobstart('Start-Sleep -Milliseconds 300; exit 5', g:dict), + \ jobstart('Start-Sleep -Milliseconds 500; exit 5', g:dict), + \ jobstart('Start-Sleep -Milliseconds 700; exit 5', g:dict) + \ ] : [ \ jobstart('sleep 0.010; exit 5', g:dict), \ jobstart('sleep 0.030; exit 5', g:dict), \ jobstart('sleep 0.050; exit 5', g:dict), @@ -507,7 +546,12 @@ describe('jobs', function() it('will return status codes in the order of passed ids', function() source([[ - call rpcnotify(g:channel, 'wait', jobwait([ + call rpcnotify(g:channel, 'wait', jobwait(has('win32') ? [ + \ jobstart('Start-Sleep -Milliseconds 700; exit 4'), + \ jobstart('Start-Sleep -Milliseconds 500; exit 5'), + \ jobstart('Start-Sleep -Milliseconds 300; exit 6'), + \ jobstart('Start-Sleep -Milliseconds 100; exit 7') + \ ] : [ \ jobstart('sleep 0.070; exit 4'), \ jobstart('sleep 0.050; exit 5'), \ jobstart('sleep 0.030; exit 6'), @@ -521,7 +565,7 @@ describe('jobs', function() source([[ call rpcnotify(g:channel, 'wait', jobwait([ \ -10, - \ jobstart('sleep 0.01; exit 5'), + \ jobstart((has('win32') ? 'Start-Sleep -Milliseconds 100' : 'sleep 0.01').'; exit 5'), \ ])) ]]) eq({'notification', 'wait', {{-3, 5}}}, next_msg()) @@ -530,7 +574,9 @@ describe('jobs', function() it('will return -2 when interrupted without timeout', function() feed_command('call rpcnotify(g:channel, "ready") | '.. 'call rpcnotify(g:channel, "wait", '.. - 'jobwait([jobstart("sleep 10; exit 55")]))') + 'jobwait([jobstart("'.. + (iswin() and 'Start-Sleep 10' or 'sleep 10').. + '; exit 55")]))') eq({'notification', 'ready', {}}, next_msg()) feed('<c-c>') eq({'notification', 'wait', {{-2}}}, next_msg()) @@ -539,7 +585,9 @@ describe('jobs', function() it('will return -2 when interrupted with timeout', function() feed_command('call rpcnotify(g:channel, "ready") | '.. 'call rpcnotify(g:channel, "wait", '.. - 'jobwait([jobstart("sleep 10; exit 55")], 10000))') + 'jobwait([jobstart("'.. + (iswin() and 'Start-Sleep 10' or 'sleep 10').. + '; exit 55")], 10000))') eq({'notification', 'ready', {}}, next_msg()) feed('<c-c>') eq({'notification', 'wait', {{-2}}}, next_msg()) @@ -594,20 +642,22 @@ describe('jobs', function() end) describe('with timeout argument', function() - if helpers.pending_win32(pending) then return end it('will return -1 if the wait timed out', function() source([[ call rpcnotify(g:channel, 'wait', jobwait([ \ jobstart('exit 4'), - \ jobstart('sleep 10; exit 5'), - \ ], 100)) + \ jobstart((has('win32') ? 'Start-Sleep 10' : 'sleep 10').'; exit 5'), + \ ], has('win32') ? 3000 : 100)) ]]) eq({'notification', 'wait', {{4, -1}}}, next_msg()) end) it('can pass 0 to check if a job exists', function() source([[ - call rpcnotify(g:channel, 'wait', jobwait([ + call rpcnotify(g:channel, 'wait', jobwait(has('win32') ? [ + \ jobstart('Start-Sleep -Milliseconds 50; exit 4'), + \ jobstart('Start-Sleep -Milliseconds 300; exit 5'), + \ ] : [ \ jobstart('sleep 0.05; exit 4'), \ jobstart('sleep 0.3; exit 5'), \ ], 0)) @@ -629,13 +679,60 @@ describe('jobs', function() end) it('cannot have both rpc and pty options', function() - if helpers.pending_win32(pending) then return end -- TODO: Need `cat`. command("let g:job_opts.pty = v:true") command("let g:job_opts.rpc = v:true") - local _, err = pcall(command, "let j = jobstart(['cat', '-'], g:job_opts)") + local _, err = pcall(command, "let j = jobstart(has('win32') ? ['find', '/v', ''] : ['cat', '-'], g:job_opts)") ok(string.find(err, "E475: Invalid argument: job cannot have both 'pty' and 'rpc' options set") ~= nil) end) + it('does not crash when repeatedly failing to start shell', function() + source([[ + set shell=nosuchshell + func! DoIt() + call jobstart('true') + call jobstart('true') + endfunc + ]]) + -- The crash only triggered if both jobs are cleaned up on the same event + -- loop tick. This is also prevented by try-block, so feed must be used. + feed_command("call DoIt()") + feed('<cr>') -- press RETURN + eq(2,eval('1+1')) + end) + + it('jobstop() kills entire process tree #6530', function() + -- XXX: Using `nvim` isn't a good test, it reaps its children on exit. + -- local c = 'call jobstart([v:progpath, "-u", "NONE", "-i", "NONE", "--headless"])' + -- local j = eval("jobstart([v:progpath, '-u', 'NONE', '-i', 'NONE', '--headless', '-c', '" + -- ..c.."', '-c', '"..c.."'])") + + -- Create child with several descendants. + local sleep_cmd = (iswin() + and 'ping -n 31 127.0.0.1' + or 'sleep 30') + local j = eval("jobstart('"..sleep_cmd..' | '..sleep_cmd..' | '..sleep_cmd.."')") + local ppid = funcs.jobpid(j) + local children + retry(nil, nil, function() + children = meths.get_proc_children(ppid) + eq(3, #children) + end) + -- Assert that nvim_get_proc() sees the children. + for _, child_pid in ipairs(children) do + local info = meths.get_proc(child_pid) + -- eq((iswin() and 'nvim.exe' or 'nvim'), info.name) + eq(ppid, info.ppid) + end + -- Kill the root of the tree. + funcs.jobstop(j) + -- Assert that the children were killed. + retry(nil, nil, function() + for _, child_pid in ipairs(children) do + eq(NIL, meths.get_proc(child_pid)) + end + end) + end) + describe('running tty-test program', function() if helpers.pending_win32(pending) then return end local function next_chunk() diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua new file mode 100644 index 0000000000..cd396ef820 --- /dev/null +++ b/test/functional/core/main_spec.lua @@ -0,0 +1,131 @@ +local lfs = require('lfs') +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local eq = helpers.eq +local feed = helpers.feed +local eval = helpers.eval +local clear = helpers.clear +local funcs = helpers.funcs +local nvim_prog = helpers.nvim_prog +local write_file = helpers.write_file + +local function nvim_prog_abs() + -- system(['build/bin/nvim']) does not work for whatever reason. It needs to + -- either be executable searched in $PATH or something starting with / or ./. + if nvim_prog:match('[/\\]') then + return funcs.fnamemodify(nvim_prog, ':p') + else + return nvim_prog + end +end + +describe('Command-line option', function() + describe('-s', function() + local fname = 'Xtest-functional-core-main-s' + local fname_2 = fname .. '.2' + local nonexistent_fname = fname .. '.nonexistent' + local dollar_fname = '$' .. fname + before_each(function() + clear() + os.remove(fname) + os.remove(dollar_fname) + end) + after_each(function() + os.remove(fname) + os.remove(dollar_fname) + end) + it('treats - as stdin', function() + eq(nil, lfs.attributes(fname)) + funcs.system( + {nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', '--headless', + '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', + '-s', '-', fname}, + {':call setline(1, "42")', ':wqall!', ''}) + eq(0, eval('v:shell_error')) + local attrs = lfs.attributes(fname) + eq(#('42\n'), attrs.size) + end) + it('does not expand $VAR', function() + eq(nil, lfs.attributes(fname)) + eq(true, not not dollar_fname:find('%$%w+')) + write_file(dollar_fname, ':call setline(1, "100500")\n:wqall!\n') + funcs.system( + {nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', '--headless', + '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', + '-s', dollar_fname, fname}) + eq(0, eval('v:shell_error')) + local attrs = lfs.attributes(fname) + eq(#('100500\n'), attrs.size) + end) + it('does not crash after reading from stdin in non-headless mode', function() + if helpers.pending_win32(pending) then return end + local screen = Screen.new(40, 8) + screen:attach() + funcs.termopen({ + nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', + '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', + '-s', '-' + }) + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:[No Name] 0,0-1 All}| + | + | + ]], { + [1] = {foreground = 4210943, special = Screen.colors.Grey0}, + [2] = {special = Screen.colors.Grey0, bold = true, reverse = true} + }) + feed('i:cq<CR>') + screen:expect([[ + | + [Process exited 1] | + | + | + | + | + | + -- TERMINAL -- | + ]]) + --[=[ Example of incorrect output: + screen:expect([[ + ^nvim: /var/tmp/portage/dev-libs/libuv-1.| + 10.2/work/libuv-1.10.2/src/unix/core.c:5| + 19: uv__close: Assertion `fd > STDERR_FI| + LENO' failed. | + | + [Process exited 6] | + | + | + ]]) + ]=] + end) + it('errors out when trying to use nonexistent file with -s', function() + eq( + 'Cannot open for reading: "'..nonexistent_fname..'": no such file or directory\n', + funcs.system( + {nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', '--headless', + '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', + '--cmd', 'language C', + '-s', nonexistent_fname})) + eq(2, eval('v:shell_error')) + end) + it('errors out when trying to use -s twice', function() + write_file(fname, ':call setline(1, "1")\n:wqall!\n') + write_file(dollar_fname, ':call setline(1, "2")\n:wqall!\n') + eq( + 'Attempt to open script file again: "-s '..dollar_fname..'"\n', + funcs.system( + {nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', '--headless', + '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', + '--cmd', 'language C', + '-s', fname, '-s', dollar_fname, fname_2})) + eq(2, eval('v:shell_error')) + eq(nil, lfs.attributes(fname_2)) + end) + end) +end) diff --git a/test/functional/eval/buf_functions_spec.lua b/test/functional/eval/buf_functions_spec.lua index 7de58766b9..c0e264d4c9 100644 --- a/test/functional/eval/buf_functions_spec.lua +++ b/test/functional/eval/buf_functions_spec.lua @@ -15,6 +15,7 @@ local curwinmeths = helpers.curwinmeths local curtabmeths = helpers.curtabmeths local get_pathsep = helpers.get_pathsep local rmdir = helpers.rmdir +local expect_err = helpers.expect_err local fname = 'Xtest-functional-eval-buf_functions' local fname2 = fname .. '.2' @@ -296,8 +297,8 @@ describe('setbufvar() function', function() eq('Vim(call):E461: Illegal variable name: b:', exc_exec('call setbufvar(1, "", 0)')) eq(true, bufmeths.get_var(buf1, 'number')) - funcs.setbufvar(1, 'changedtick', true) - -- eq(true, bufmeths.get_var(buf1, 'changedtick')) + expect_err('Vim:E46: Cannot change read%-only variable "b:changedtick"', + funcs.setbufvar, 1, 'changedtick', true) eq(2, funcs.getbufvar(1, 'changedtick')) end) end) diff --git a/test/functional/eval/execute_spec.lua b/test/functional/eval/execute_spec.lua index 183884a51e..925e311c7d 100644 --- a/test/functional/eval/execute_spec.lua +++ b/test/functional/eval/execute_spec.lua @@ -106,16 +106,22 @@ describe('execute()', function() end) it('does not corrupt the command display #5422', function() - local screen = Screen.new(70, 5) + local screen = Screen.new(70, 7) screen:attach() feed(':echo execute("hi ErrorMsg")<CR>') screen:expect([[ - ~ | - ~ | + | + {1:~ }| + {1:~ }| + {2: }| :echo execute("hi ErrorMsg") | ErrorMsg xxx ctermfg=15 ctermbg=1 guifg=White guibg=Red | - Press ENTER or type command to continue^ | - ]]) + {3:Press ENTER or type command to continue}^ | + ]], { + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {bold = true, reverse = true}, + [3] = {bold = true, foreground = Screen.colors.SeaGreen4}, + }) feed('<CR>') end) diff --git a/test/functional/eval/input_spec.lua b/test/functional/eval/input_spec.lua index 1e6b107c60..777f49462d 100644 --- a/test/functional/eval/input_spec.lua +++ b/test/functional/eval/input_spec.lua @@ -58,6 +58,7 @@ before_each(function() RBP2={background=Screen.colors.Yellow}, RBP3={background=Screen.colors.Green}, RBP4={background=Screen.colors.Blue}, + SEP={bold = true, reverse = true}, }) end) @@ -65,9 +66,9 @@ describe('input()', function() it('works with multiline prompts', function() feed([[:call input("Test\nFoo")<CR>]]) screen:expect([[ + | {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {SEP: }| Test | Foo^ | ]]) @@ -75,9 +76,9 @@ describe('input()', function() it('works with multiline prompts and :echohl', function() feed([[:echohl Test | call input("Test\nFoo")<CR>]]) screen:expect([[ + | {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {SEP: }| {T:Test} | {T:Foo}^ | ]]) @@ -242,17 +243,17 @@ describe('input()', function() it('is not hidden by :silent', function() feed([[:silent call input('Foo: ')<CR>]]) screen:expect([[ + | {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {SEP: }| Foo: ^ | | ]]) feed('Bar') screen:expect([[ + | {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {SEP: }| Foo: Bar^ | | ]]) @@ -263,9 +264,9 @@ describe('inputdialog()', function() it('works with multiline prompts', function() feed([[:call inputdialog("Test\nFoo")<CR>]]) screen:expect([[ + | {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {SEP: }| Test | Foo^ | ]]) @@ -273,9 +274,9 @@ describe('inputdialog()', function() it('works with multiline prompts and :echohl', function() feed([[:echohl Test | call inputdialog("Test\nFoo")<CR>]]) screen:expect([[ + | {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {SEP: }| {T:Test} | {T:Foo}^ | ]]) diff --git a/test/functional/eval/match_functions_spec.lua b/test/functional/eval/match_functions_spec.lua index 7989b22b5e..0ec465a34c 100644 --- a/test/functional/eval/match_functions_spec.lua +++ b/test/functional/eval/match_functions_spec.lua @@ -6,6 +6,7 @@ local clear = helpers.clear local funcs = helpers.funcs local command = helpers.command local exc_exec = helpers.exc_exec +local expect_err = helpers.expect_err before_each(clear) @@ -40,9 +41,11 @@ describe('setmatches()', function() end) it('fails with -1 if highlight group is not defined', function() - eq(-1, funcs.setmatches({{group=1, pattern=2, id=3, priority=4}})) + expect_err('E28: No such highlight group name: 1', funcs.setmatches, + {{group=1, pattern=2, id=3, priority=4}}) eq({}, funcs.getmatches()) - eq(-1, funcs.setmatches({{group=1, pos1={2}, pos2={6}, id=3, priority=4, conceal=5}})) + expect_err('E28: No such highlight group name: 1', funcs.setmatches, + {{group=1, pos1={2}, pos2={6}, id=3, priority=4, conceal=5}}) eq({}, funcs.getmatches()) end) end) diff --git a/test/functional/eval/server_spec.lua b/test/functional/eval/server_spec.lua index 393616838e..563e619b39 100644 --- a/test/functional/eval/server_spec.lua +++ b/test/functional/eval/server_spec.lua @@ -1,31 +1,41 @@ - local helpers = require('test.functional.helpers')(after_each) local eq, neq, eval = helpers.eq, helpers.neq, helpers.eval local command = helpers.command local clear, funcs, meths = helpers.clear, helpers.funcs, helpers.meths -local os_name = helpers.os_name +local iswin = helpers.iswin +local ok = helpers.ok +local matches = helpers.matches +local expect_err = helpers.expect_err local function clear_serverlist() - for _, server in pairs(funcs.serverlist()) do - funcs.serverstop(server) - end + for _, server in pairs(funcs.serverlist()) do + funcs.serverstop(server) + end end -describe('serverstart(), serverstop()', function() +describe('server', function() before_each(clear) - it('sets $NVIM_LISTEN_ADDRESS on first invocation', function() + it('serverstart() sets $NVIM_LISTEN_ADDRESS on first invocation', function() -- Unset $NVIM_LISTEN_ADDRESS command('let $NVIM_LISTEN_ADDRESS = ""') local s = eval('serverstart()') assert(s ~= nil and s:len() > 0, "serverstart() returned empty") eq(s, eval('$NVIM_LISTEN_ADDRESS')) - command("call serverstop('"..s.."')") + eq(1, eval("serverstop('"..s.."')")) eq('', eval('$NVIM_LISTEN_ADDRESS')) end) - it('sets v:servername _only_ on nvim startup unless all servers are stopped', + it('sets new v:servername if $NVIM_LISTEN_ADDRESS is invalid', function() + clear({env={NVIM_LISTEN_ADDRESS='.'}}) + eq('.', eval('$NVIM_LISTEN_ADDRESS')) + local servers = funcs.serverlist() + eq(1, #servers) + ok(string.len(servers[1]) > 4) -- Like /tmp/nvim…/… or \\.\pipe\… + end) + + it('sets v:servername at startup or if all servers were stopped', function() local initial_server = meths.get_vvar('servername') assert(initial_server ~= nil and initial_server:len() > 0, @@ -38,24 +48,23 @@ describe('serverstart(), serverstop()', function() neq(initial_server, s) -- serverstop() does _not_ modify v:servername... - funcs.serverstop(s) + eq(1, funcs.serverstop(s)) eq(initial_server, meths.get_vvar('servername')) -- ...unless we stop _all_ servers. - funcs.serverstop(funcs.serverlist()[1]) + eq(1, funcs.serverstop(funcs.serverlist()[1])) eq('', meths.get_vvar('servername')) -- v:servername will take the next available server. - local servername = (os_name() == 'windows' - and [[\\.\pipe\Xtest-functional-server-pipe]] - or 'Xtest-functional-server-socket') + local servername = (iswin() and [[\\.\pipe\Xtest-functional-server-pipe]] + or 'Xtest-functional-server-socket') funcs.serverstart(servername) eq(servername, meths.get_vvar('servername')) end) - it('serverstop() ignores invalid input', function() - command("call serverstop('')") - command("call serverstop('bogus-socket-name')") + it('serverstop() returns false for invalid input', function() + eq(0, eval("serverstop('')")) + eq(0, eval("serverstop('bogus-socket-name')")) end) it('parses endpoints correctly', function() @@ -78,35 +87,32 @@ describe('serverstart(), serverstop()', function() local expected = {} local v4 = '127.0.0.1:12345' - s = funcs.serverstart(v4) - if #s > 0 then + local status, _ = pcall(funcs.serverstart, v4) + if status then table.insert(expected, v4) - funcs.serverstart(v4) -- exists already; ignore + pcall(funcs.serverstart, v4) -- exists already; ignore end local v6 = '::1:12345' - s = funcs.serverstart(v6) - if #s > 0 then + status, _ = pcall(funcs.serverstart, v6) + if status then table.insert(expected, v6) - funcs.serverstart(v6) -- exists already; ignore + pcall(funcs.serverstart, v6) -- exists already; ignore end eq(expected, funcs.serverlist()) clear_serverlist() - funcs.serverstart('127.0.0.1:65536') -- invalid port + expect_err('Failed to start server: invalid argument', + funcs.serverstart, '127.0.0.1:65536') -- invalid port eq({}, funcs.serverlist()) end) -end) - -describe('serverlist()', function() - before_each(clear) - it('returns the list of servers', function() + it('serverlist() returns the list of servers', function() -- There should already be at least one server. local n = eval('len(serverlist())') - -- Add a few - local servs = (os_name() == 'windows' + -- Add some servers. + local servs = (iswin() and { [[\\.\pipe\Xtest-pipe0934]], [[\\.\pipe\Xtest-pipe4324]] } or { [[Xtest-pipe0934]], [[Xtest-pipe4324]] }) for _, s in ipairs(servs) do @@ -120,9 +126,31 @@ describe('serverlist()', function() -- The new servers should be at the end of the list. for i = 1, #servs do eq(servs[i], new_servs[i + n]) - command("call serverstop('"..servs[i].."')") + eq(1, eval("serverstop('"..servs[i].."')")) end -- After serverstop() the servers should NOT be in the list. eq(n, eval('len(serverlist())')) end) end) + +describe('startup --listen', function() + it('validates', function() + clear() + + local cmd = { unpack(helpers.nvim_argv) } + table.insert(cmd, '--listen') + matches('nvim.*: Argument missing after: "%-%-listen"', funcs.system(cmd)) + + cmd = { unpack(helpers.nvim_argv) } + table.insert(cmd, '--listen2') + matches('nvim.*: Garbage after option argument: "%-%-listen2"', funcs.system(cmd)) + end) + + it('sets v:servername, overrides $NVIM_LISTEN_ADDRESS', function() + local addr = (iswin() and [[\\.\pipe\Xtest-listen-pipe]] + or 'Xtest-listen-pipe') + clear({ env={ NVIM_LISTEN_ADDRESS='Xtest-env-pipe' }, + args={ '--listen', addr } }) + eq(addr, meths.get_vvar('servername')) + end) +end) diff --git a/test/functional/eval/sort_spec.lua b/test/functional/eval/sort_spec.lua index 4e5a0afba4..82557575ce 100644 --- a/test/functional/eval/sort_spec.lua +++ b/test/functional/eval/sort_spec.lua @@ -8,6 +8,7 @@ local meths = helpers.meths local funcs = helpers.funcs local command = helpers.command local exc_exec = helpers.exc_exec +local redir_exec = helpers.redir_exec before_each(clear) @@ -38,4 +39,18 @@ describe('sort()', function() eq('[-1.0e-4, function(\'tr\'), v:true, v:false, v:null, [], {\'a\': 42}, \'check\', 1.0e-4]', eval('string(g:list)')) end) + + it('can yield E702 and stop sorting after that', function() + command([[ + function Cmp(a, b) + if type(a:a) == type([]) || type(a:b) == type([]) + return [] + endif + return (a:a > a:b) - (a:a < a:b) + endfunction + ]]) + eq('\nE745: Using a List as a Number\nE702: Sort compare function failed', + redir_exec('let sl = sort([1, 0, [], 3, 2], "Cmp")')) + eq({1, 0, {}, 3, 2}, meths.get_var('sl')) + end) end) diff --git a/test/functional/eval/system_spec.lua b/test/functional/eval/system_spec.lua index 446afefb59..23cea4c038 100644 --- a/test/functional/eval/system_spec.lua +++ b/test/functional/eval/system_spec.lua @@ -120,33 +120,47 @@ describe('system()', function() end end) - describe('executes shell function if passed a string', function() + describe('executes shell function', function() local screen before_each(function() - clear() - screen = Screen.new() - screen:attach() + clear() + screen = Screen.new() + screen:attach() end) after_each(function() - screen:detach() + screen:detach() end) if iswin() then + local function test_more() + eq('root = true', eval([[get(split(system('"more" ".editorconfig"'), "\n"), 0, '')]])) + end + local function test_shell_unquoting() + eval([[system('"ping" "-n" "1" "127.0.0.1"')]]) + eq(0, eval('v:shell_error')) + eq('"a b"\n', eval([[system('cmd /s/c "cmd /s/c "cmd /s/c "echo "a b""""')]])) + eq('"a b"\n', eval([[system('powershell -NoProfile -NoLogo -ExecutionPolicy RemoteSigned -Command echo ''\^"a b\^"''')]])) + end + it('with shell=cmd.exe', function() command('set shell=cmd.exe') eq('""\n', eval([[system('echo ""')]])) eq('"a b"\n', eval([[system('echo "a b"')]])) eq('a \nb\n', eval([[system('echo a & echo b')]])) eq('a \n', eval([[system('echo a 2>&1')]])) + test_more() eval([[system('cd "C:\Program Files"')]]) eq(0, eval('v:shell_error')) + test_shell_unquoting() end) it('with shell=cmd', function() command('set shell=cmd') eq('"a b"\n', eval([[system('echo "a b"')]])) + test_more() + test_shell_unquoting() end) it('with shell=$COMSPEC', function() @@ -154,6 +168,8 @@ describe('system()', function() if comspecshell == 'cmd.exe' then command('set shell=$COMSPEC') eq('"a b"\n', eval([[system('echo "a b"')]])) + test_more() + test_shell_unquoting() else pending('$COMSPEC is not cmd.exe: ' .. comspecshell) end @@ -187,7 +203,7 @@ describe('system()', function() ]]) end) - it('`yes` and is interrupted with CTRL-C', function() + it('`yes` interrupted with CTRL-C', function() feed(':call system("' .. (iswin() and 'for /L %I in (1,0,2) do @echo y' or 'yes') .. '")<cr>') @@ -240,7 +256,7 @@ describe('system()', function() end) it('to backgrounded command does not crash', function() -- This is indeterminate, just exercise the codepath. May get E5677. - feed_command('call system("echo -n echoed &")') + feed_command('call system(has("win32") ? "start /b /wait cmd /c echo echoed" : "echo -n echoed &")') local v_errnum = string.match(eval("v:errmsg"), "^E%d*:") if v_errnum then eq("E5677:", v_errnum) @@ -255,7 +271,7 @@ describe('system()', function() end) it('to backgrounded command does not crash', function() -- This is indeterminate, just exercise the codepath. May get E5677. - feed_command('call system("cat - &", "input")') + feed_command('call system(has("win32") ? "start /b /wait more" : "cat - &", "input")') local v_errnum = string.match(eval("v:errmsg"), "^E%d*:") if v_errnum then eq("E5677:", v_errnum) @@ -299,7 +315,7 @@ describe('system()', function() after_each(delete_file(fname)) it('replaces NULs by SOH characters', function() - eq('part1\001part2\001part3\n', eval('system("cat '..fname..'")')) + eq('part1\001part2\001part3\n', eval([[system('"cat" "]]..fname..[["')]])) end) end) @@ -366,7 +382,7 @@ describe('systemlist()', function() end end) - describe('exectues shell function', function() + describe('executes shell function', function() local screen before_each(function() @@ -399,7 +415,7 @@ describe('systemlist()', function() ]]) end) - it('`yes` and is interrupted with CTRL-C', function() + it('`yes` interrupted with CTRL-C', function() feed(':call systemlist("yes | xargs")<cr>') screen:expect([[ | @@ -464,7 +480,7 @@ describe('systemlist()', function() after_each(delete_file(fname)) it('replaces NULs by newline characters', function() - eq({'part1\npart2\npart3'}, eval('systemlist("cat '..fname..'")')) + eq({'part1\npart2\npart3'}, eval([[systemlist('"cat" "]]..fname..[["')]])) end) end) diff --git a/test/functional/eval/uniq_spec.lua b/test/functional/eval/uniq_spec.lua new file mode 100644 index 0000000000..0e7a013e32 --- /dev/null +++ b/test/functional/eval/uniq_spec.lua @@ -0,0 +1,31 @@ +local helpers = require('test.functional.helpers')(after_each) + +local eq = helpers.eq +local clear = helpers.clear +local meths = helpers.meths +local command = helpers.command +local exc_exec = helpers.exc_exec +local redir_exec = helpers.redir_exec + +before_each(clear) + +describe('uniq()', function() + it('errors out when processing special values', function() + eq('Vim(call):E907: Using a special value as a Float', + exc_exec('call uniq([v:true, v:false], "f")')) + end) + + it('can yield E882 and stop filtering after that', function() + command([[ + function Cmp(a, b) + if type(a:a) == type([]) || type(a:b) == type([]) + return [] + endif + return (a:a > a:b) - (a:a < a:b) + endfunction + ]]) + eq('\nE745: Using a List as a Number\nE882: Uniq compare function failed', + redir_exec('let fl = uniq([0, 0, [], 1, 1], "Cmp")')) + eq({0, {}, 1, 1}, meths.get_var('fl')) + end) +end) diff --git a/test/functional/ex_cmds/cmd_map_spec.lua b/test/functional/ex_cmds/cmd_map_spec.lua new file mode 100644 index 0000000000..77d025dcc7 --- /dev/null +++ b/test/functional/ex_cmds/cmd_map_spec.lua @@ -0,0 +1,772 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local feed_command = helpers.feed_command +local feed = helpers.feed +local eq = helpers.eq +local expect = helpers.expect +local eval = helpers.eval +local funcs = helpers.funcs +local insert = helpers.insert +local exc_exec = helpers.exc_exec +local Screen = require('test.functional.ui.screen') + +describe('mappings with <Cmd>', function() + local screen + local function cmdmap(lhs, rhs) + feed_command('noremap '..lhs..' <Cmd>'..rhs..'<cr>') + feed_command('noremap! '..lhs..' <Cmd>'..rhs..'<cr>') + end + + before_each(function() + clear() + screen = Screen.new(65, 8) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [3] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [4] = {bold = true}, + [5] = {background = Screen.colors.LightGrey}, + [6] = {foreground = Screen.colors.Blue1}, + [7] = {bold = true, reverse = true}, + }) + screen:attach() + + cmdmap('<F3>', 'let m = mode(1)') + cmdmap('<F4>', 'normal! ww') + cmdmap('<F5>', 'normal! "ay') + cmdmap('<F6>', 'throw "very error"') + feed_command([[ + function! TextObj() + if mode() !=# "v" + normal! v + end + call cursor(1,3) + normal! o + call cursor(2,4) + endfunction]]) + cmdmap('<F7>', 'call TextObj()') + insert([[ + some short lines + of test text]]) + feed('gg') + cmdmap('<F8>', 'startinsert') + cmdmap('<F9>', 'stopinsert') + feed_command("abbr foo <Cmd>let g:y = 17<cr>bar") + end) + + it('can be displayed', function() + feed_command('map <F3>') + screen:expect([[ + ^some short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {6:<F3>} {6:*} {6:<Cmd>}let m = mode(1){6:<CR>} | + ]]) + end) + + it('handles invalid mappings', function() + feed_command('let x = 0') + feed_command('noremap <F3> <Cmd><Cmd>let x = 1<cr>') + feed('<F3>') + screen:expect([[ + ^some short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:E5521: <Cmd> mapping must end with <CR> before second <Cmd>} | + ]]) + + feed_command('noremap <F3> <Cmd><F3>let x = 2<cr>') + feed('<F3>') + screen:expect([[ + ^some short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:E5522: <Cmd> mapping must not include <F3> key} | + ]]) + + feed_command('noremap <F3> <Cmd>let x = 3') + feed('<F3>') + screen:expect([[ + ^some short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:E5520: <Cmd> mapping must end with <CR>} | + ]]) + eq(0, eval('x')) + end) + + it('works in various modes and sees correct `mode()` value', function() + -- normal mode + feed('<F3>') + eq('n', eval('m')) + + -- visual mode + feed('v<F3>') + eq('v', eval('m')) + -- didn't leave visual mode + eq('v', eval('mode(1)')) + feed('<esc>') + eq('n', eval('mode(1)')) + + -- visual mapping in select mode + feed('gh<F3>') + eq('v', eval('m')) + -- didn't leave select mode + eq('s', eval('mode(1)')) + feed('<esc>') + eq('n', eval('mode(1)')) + + -- select mode mapping + feed_command('snoremap <F3> <Cmd>let m = mode(1)<cr>') + feed('gh<F3>') + eq('s', eval('m')) + -- didn't leave select mode + eq('s', eval('mode(1)')) + feed('<esc>') + eq('n', eval('mode(1)')) + + -- operator-pending mode + feed("d<F3>") + eq('no', eval('m')) + -- did leave operator-pending mode + eq('n', eval('mode(1)')) + + --insert mode + feed('i<F3>') + eq('i', eval('m')) + eq('i', eval('mode(1)')) + + -- replace mode + feed("<Ins><F3>") + eq('R', eval('m')) + eq('R', eval('mode(1)')) + feed('<esc>') + eq('n', eval('mode(1)')) + + -- virtual replace mode + feed("gR<F3>") + eq('Rv', eval('m')) + eq('Rv', eval('mode(1)')) + feed('<esc>') + eq('n', eval('mode(1)')) + + -- langmap works, but is not distinguished in mode(1) + feed(":set iminsert=1<cr>i<F3>") + eq('i', eval('m')) + eq('i', eval('mode(1)')) + feed('<esc>') + eq('n', eval('mode(1)')) + + feed(':<F3>') + eq('c', eval('m')) + eq('c', eval('mode(1)')) + feed('<esc>') + eq('n', eval('mode(1)')) + + -- terminal mode + feed_command('tnoremap <F3> <Cmd>let m = mode(1)<cr>') + feed_command('split | terminal') + feed('i') + eq('t', eval('mode(1)')) + feed('<F3>') + eq('t', eval('m')) + eq('t', eval('mode(1)')) + end) + + it('works in normal mode', function() + cmdmap('<F2>', 'let s = [mode(1), v:count, v:register]') + + -- check v:count and v:register works + feed('<F2>') + eq({'n', 0, '"'}, eval('s')) + feed('7<F2>') + eq({'n', 7, '"'}, eval('s')) + feed('"e<F2>') + eq({'n', 0, 'e'}, eval('s')) + feed('5"k<F2>') + eq({'n', 5, 'k'}, eval('s')) + feed('"+2<F2>') + eq({'n', 2, '+'}, eval('s')) + + -- text object enters visual mode + feed('<F7>') + screen:expect([[ + so{5:me short lines} | + {5:of }^test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- VISUAL --} | + ]]) + feed('<esc>') + + -- startinsert + feed('<F8>') + eq('i', eval('mode(1)')) + feed('<esc>') + + eq('n', eval('mode(1)')) + cmdmap(',a', 'call feedkeys("aalpha") \\| let g:a = getline(2)') + cmdmap(',b', 'call feedkeys("abeta", "x") \\| let g:b = getline(2)') + + feed(',a<F3>') + screen:expect([[ + some short lines | + of alpha^test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- INSERT --} | + ]]) + -- feedkeys were not executed immediately + eq({'n', 'of test text'}, eval('[m,a]')) + eq('i', eval('mode(1)')) + feed('<esc>') + + feed(',b<F3>') + screen:expect([[ + some short lines | + of alphabet^atest text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + -- feedkeys(..., 'x') was executed immediately, but insert mode gets aborted + eq({'n', 'of alphabetatest text'}, eval('[m,b]')) + eq('n', eval('mode(1)')) + end) + + it('works in :normal command', function() + feed_command('noremap ,x <Cmd>call append(1, "xx")\\| call append(1, "aa")<cr>') + feed_command('noremap ,f <Cmd>nosuchcommand<cr>') + feed_command('noremap ,e <Cmd>throw "very error"\\| call append(1, "yy")<cr>') + feed_command('noremap ,m <Cmd>echoerr "The message."\\| call append(1, "zz")<cr>') + feed_command('noremap ,w <Cmd>for i in range(5)\\|if i==1\\|echoerr "Err"\\|endif\\|call append(1, i)\\|endfor<cr>') + + feed(":normal ,x<cr>") + screen:expect([[ + ^some short lines | + aa | + xx | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + + eq('Vim:E492: Not an editor command: nosuchcommand', exc_exec("normal ,f")) + eq('very error', exc_exec("normal ,e")) + eq('Vim(echoerr):The message.', exc_exec("normal ,m")) + feed('w') + screen:expect([[ + some ^short lines | + aa | + xx | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + + feed_command(':%d') + eq('Vim(echoerr):Err', exc_exec("normal ,w")) + screen:expect([[ + ^ | + 0 | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + --No lines in buffer-- | + ]]) + + feed_command(':%d') + feed_command(':normal ,w') + screen:expect([[ + ^ | + 4 | + 3 | + 2 | + 1 | + 0 | + {1:~ }| + {2:Err} | + ]]) + end) + + it('works in visual mode', function() + -- can extend visual mode + feed('v<F4>') + screen:expect([[ + {5:some short }^lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- VISUAL --} | + ]]) + eq('v', funcs.mode(1)) + + -- can invoke operator, ending visual mode + feed('<F5>') + eq('n', funcs.mode(1)) + eq({'some short l'}, funcs.getreg('a',1,1)) + + -- error doesn't interrupt visual mode + feed('ggvw<F6>') + screen:expect([[ + {5:some }short lines | + of test text | + {1:~ }| + {1:~ }| + {7: }| + {2:Error detected while processing :} | + {2:E605: Exception not caught: very error} | + {3:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') + eq('E605: Exception not caught: very error', eval('v:errmsg')) + -- still in visual mode, <cr> was consumed by the error prompt + screen:expect([[ + {5:some }^short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- VISUAL --} | + ]]) + eq('v', funcs.mode(1)) + feed('<F7>') + screen:expect([[ + so{5:me short lines} | + {5:of }^test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- VISUAL --} | + ]]) + eq('v', funcs.mode(1)) + + -- startinsert gives "-- (insert) VISUAL --" mode + feed('<F8>') + screen:expect([[ + so{5:me short lines} | + {5:of }^test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- (insert) VISUAL --} | + ]]) + eq('v', eval('mode(1)')) + feed('<esc>') + eq('i', eval('mode(1)')) + end) + + it('works in select mode', function() + feed_command('snoremap <F1> <cmd>throw "very error"<cr>') + feed_command('snoremap <F2> <cmd>normal! <c-g>"by<cr>') + -- can extend select mode + feed('gh<F4>') + screen:expect([[ + {5:some short }^lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- SELECT --} | + ]]) + eq('s', funcs.mode(1)) + + -- visual mapping in select mode restart selct mode after operator + feed('<F5>') + eq('s', funcs.mode(1)) + eq({'some short l'}, funcs.getreg('a',1,1)) + + -- select mode mapping works, and does not restart select mode + feed('<F2>') + eq('n', funcs.mode(1)) + eq({'some short l'}, funcs.getreg('b',1,1)) + + -- error doesn't interrupt temporary visual mode + feed('<esc>ggvw<c-g><F6>') + screen:expect([[ + {5:some }short lines | + of test text | + {1:~ }| + {1:~ }| + {7: }| + {2:Error detected while processing :} | + {2:E605: Exception not caught: very error} | + {3:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') + eq('E605: Exception not caught: very error', eval('v:errmsg')) + -- still in visual mode, <cr> was consumed by the error prompt + screen:expect([[ + {5:some }^short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- VISUAL --} | + ]]) + -- quirk: restoration of select mode is not performed + eq('v', funcs.mode(1)) + + -- error doesn't interrupt select mode + feed('<esc>ggvw<c-g><F1>') + screen:expect([[ + {5:some }short lines | + of test text | + {1:~ }| + {1:~ }| + {7: }| + {2:Error detected while processing :} | + {2:E605: Exception not caught: very error} | + {3:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') + eq('E605: Exception not caught: very error', eval('v:errmsg')) + -- still in select mode, <cr> was consumed by the error prompt + screen:expect([[ + {5:some }^short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- SELECT --} | + ]]) + -- quirk: restoration of select mode is not performed + eq('s', funcs.mode(1)) + + feed('<F7>') + screen:expect([[ + so{5:me short lines} | + {5:of }^test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- SELECT --} | + ]]) + eq('s', funcs.mode(1)) + + -- startinsert gives "-- SELECT (insert) --" mode + feed('<F8>') + screen:expect([[ + so{5:me short lines} | + {5:of }^test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- (insert) SELECT --} | + ]]) + eq('s', eval('mode(1)')) + feed('<esc>') + eq('i', eval('mode(1)')) + end) + + + it('works in operator-pending mode', function() + feed('d<F4>') + expect([[ + lines + of test text]]) + eq({'some short '}, funcs.getreg('"',1,1)) + feed('.') + expect([[ + test text]]) + eq({'lines', 'of '}, funcs.getreg('"',1,1)) + feed('uu') + expect([[ + some short lines + of test text]]) + + -- error aborts operator-pending, operator not performed + feed('d<F6>') + screen:expect([[ + some short lines | + of test text | + {1:~ }| + {1:~ }| + {7: }| + {2:Error detected while processing :} | + {2:E605: Exception not caught: very error} | + {3:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') + eq('E605: Exception not caught: very error', eval('v:errmsg')) + expect([[ + some short lines + of test text]]) + + feed('"bd<F7>') + expect([[ + soest text]]) + eq(funcs.getreg('b',1,1), {'me short lines', 'of t'}) + + -- startinsert aborts operator + feed('d<F8>') + eq('i', eval('mode(1)')) + expect([[ + soest text]]) + end) + + it('works in insert mode', function() + + -- works the same as <c-o>w<c-o>w + feed('iindeed <F4>little ') + screen:expect([[ + indeed some short little ^lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- INSERT --} | + ]]) + + feed('<F6>') + screen:expect([[ + indeed some short little lines | + of test text | + {1:~ }| + {1:~ }| + {7: }| + {2:Error detected while processing :} | + {2:E605: Exception not caught: very error} | + {3:Press ENTER or type command to continue}^ | + ]]) + + + feed('<cr>') + eq('E605: Exception not caught: very error', eval('v:errmsg')) + -- still in insert + screen:expect([[ + indeed some short little ^lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- INSERT --} | + ]]) + eq('i', eval('mode(1)')) + + -- When entering visual mode from InsertEnter autocmd, an async event, or + -- a <cmd> mapping, vim ends up in undocumented "INSERT VISUAL" mode. If a + -- vim patch decides to disable this mode, this test is expected to fail. + feed('<F7>stuff ') + screen:expect([[ + in{5:deed some short little lines} | + {5:of stuff }^test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- INSERT VISUAL --} | + ]]) + expect([[ + indeed some short little lines + of stuff test text]]) + + feed('<F5>') + eq(funcs.getreg('a',1,1), {'deed some short little lines', 'of stuff t'}) + + -- still in insert + screen:expect([[ + in^deed some short little lines | + of stuff test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- INSERT --} | + ]]) + eq('i', eval('mode(1)')) + + -- also works as part of abbreviation + feed('<space>foo ') + screen:expect([[ + in bar ^deed some short little lines | + of stuff test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- INSERT --} | + ]]) + eq(17, eval('g:y')) + + -- :startinsert does nothing + feed('<F8>') + eq('i', eval('mode(1)')) + + -- :stopinsert works + feed('<F9>') + eq('n', eval('mode(1)')) + end) + + it('works in cmdline mode', function() + cmdmap('<F2>', 'call setcmdpos(2)') + feed(':text<F3>') + eq('c', eval('m')) + -- didn't leave cmdline mode + eq('c', eval('mode(1)')) + feed('<cr>') + eq('n', eval('mode(1)')) + screen:expect([[ + ^some short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:E492: Not an editor command: text} | + ]]) + + feed(':echo 2<F6>') + screen:expect([[ + some short lines | + of test text | + {1:~ }| + {7: }| + :echo 2 | + {2:Error detected while processing :} | + {2:E605: Exception not caught: very error} | + :echo 2^ | + ]]) + eq('E605: Exception not caught: very error', eval('v:errmsg')) + -- didn't leave cmdline mode + eq('c', eval('mode(1)')) + feed('+2<cr>') + screen:expect([[ + some short lines | + of test text | + {7: }| + :echo 2 | + {2:Error detected while processing :} | + {2:E605: Exception not caught: very error} | + 4 | + {3:Press ENTER or type command to continue}^ | + ]]) + -- however, message scrolling may cause extra CR prompt + -- This is consistent with output from async events. + feed('<cr>') + screen:expect([[ + ^some short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + eq('n', eval('mode(1)')) + + feed(':let g:x = 3<F4>') + screen:expect([[ + some short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :let g:x = 3^ | + ]]) + feed('+2<cr>') + -- cursor was moved in the background + screen:expect([[ + some short ^lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :let g:x = 3+2 | + ]]) + eq(5, eval('g:x')) + + feed(':let g:y = 7<F8>') + screen:expect([[ + some short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :let g:y = 7^ | + ]]) + eq('c', eval('mode(1)')) + feed('+2<cr>') + -- startinsert takes effect after leaving cmdline mode + screen:expect([[ + some short ^lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- INSERT --} | + ]]) + eq('i', eval('mode(1)')) + eq(9, eval('g:y')) + + end) + +end) + diff --git a/test/functional/ex_cmds/digraphs_spec.lua b/test/functional/ex_cmds/digraphs_spec.lua new file mode 100644 index 0000000000..f2d0b76739 --- /dev/null +++ b/test/functional/ex_cmds/digraphs_spec.lua @@ -0,0 +1,38 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local feed = helpers.feed +local Screen = require('test.functional.ui.screen') + +describe(':digraphs', function() + local screen + before_each(function() + clear() + screen = Screen.new(65, 8) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [3] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [4] = {bold = true}, + [5] = {background = Screen.colors.LightGrey}, + [6] = {foreground = Screen.colors.Blue1}, + [7] = {bold = true, reverse = true}, + }) + screen:attach() + end) + + it('displays digraphs', function() + feed(':digraphs<CR>') + screen:expect([[ + A@ {6:Å} 197 E` {6:È} 200 E^ {6:Ê} 202 E" {6:Ë} 203 I` {6:Ì} 204 | + I^ {6:Î} 206 I" {6:Ï} 207 N~ {6:Ñ} 209 O` {6:Ò} 210 O^ {6:Ô} 212 | + O~ {6:Õ} 213 /\ {6:×} 215 U` {6:Ù} 217 U^ {6:Û} 219 Ip {6:Þ} 222 | + a` {6:à} 224 a^ {6:â} 226 a~ {6:ã} 227 a" {6:ä} 228 a@ {6:å} 229 | + e` {6:è} 232 e^ {6:ê} 234 e" {6:ë} 235 i` {6:ì} 236 i^ {6:î} 238 | + n~ {6:ñ} 241 o` {6:ò} 242 o^ {6:ô} 244 o~ {6:õ} 245 u` {6:ù} 249 | + u^ {6:û} 251 y" {6:ÿ} 255 | + {3:Press ENTER or type command to continue}^ | + ]]) + end) + +end) + diff --git a/test/functional/ex_cmds/oldfiles_spec.lua b/test/functional/ex_cmds/oldfiles_spec.lua index 4002855c24..448326cdfb 100644 --- a/test/functional/ex_cmds/oldfiles_spec.lua +++ b/test/functional/ex_cmds/oldfiles_spec.lua @@ -29,6 +29,7 @@ describe(':oldfiles', function() it('shows most recently used files', function() local screen = Screen.new(100, 5) screen:attach() + feed_command("set display-=msgsep") feed_command('edit testfile1') feed_command('edit testfile2') feed_command('wshada') diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index b8d912114d..9895281773 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -12,13 +12,18 @@ local Paths = require('test.config.paths') local check_cores = global_helpers.check_cores local check_logs = global_helpers.check_logs -local neq = global_helpers.neq +local dedent = global_helpers.dedent local eq = global_helpers.eq -local ok = global_helpers.ok -local map = global_helpers.map +local expect_err = global_helpers.expect_err local filter = global_helpers.filter -local dedent = global_helpers.dedent +local map = global_helpers.map +local matches = global_helpers.matches +local neq = global_helpers.neq +local ok = global_helpers.ok +local read_file = global_helpers.read_file +local sleep = global_helpers.sleep local table_flatten = global_helpers.table_flatten +local write_file = global_helpers.write_file local start_dir = lfs.currentdir() -- XXX: NVIM_PROG takes precedence, QuickBuild sets it. @@ -304,12 +309,10 @@ local function retry(max, max_ms, fn) end luv.update_time() -- Update cached value of luv.now() (libuv: uv_now()). if (max and tries >= max) or (luv.now() - start_time > timeout) then - if type(result) == "string" then - result = "\nretry() attempts: "..tostring(tries).."\n"..result - end - error(result) + error("\nretry() attempts: "..tostring(tries).."\n"..tostring(result)) end tries = tries + 1 + luv.sleep(20) -- Avoid hot loop... end end @@ -374,34 +377,6 @@ local function feed_command(...) end end --- Dedent the given text and write it to the file name. -local function write_file(name, text, no_dedent, append) - local file = io.open(name, (append and 'a' or 'w')) - if type(text) == 'table' then - -- Byte blob - local bytes = text - text = '' - for _, char in ipairs(bytes) do - text = ('%s%c'):format(text, char) - end - elseif not no_dedent then - text = dedent(text) - end - file:write(text) - file:flush() - file:close() -end - -local function read_file(name) - local file = io.open(name, 'r') - if not file then - return nil - end - local ret = file:read('*a') - file:close() - return ret -end - local sourced_fnames = {} local function source(code) local fname = tmpname() @@ -425,7 +400,7 @@ end local function set_shell_powershell() source([[ set shell=powershell shellquote=( shellpipe=\| shellredir=> shellxquote= - set shellcmdflag=-NoLogo\ -NoProfile\ -ExecutionPolicy\ RemoteSigned\ -Command + let &shellcmdflag = '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command Remove-Item -Force alias:sleep;' ]]) end @@ -466,18 +441,6 @@ local function wait() session:request('nvim_eval', '1') end --- sleeps the test runner (_not_ the nvim instance) -local function sleep(ms) - local function notification_cb(method, _) - if method == "redraw" then - error("Screen is attached; use screen:sleep() instead.") - end - return true - end - - run(nil, notification_cb, nil, ms) -end - local function curbuf_contents() wait() -- Before inspecting the buffer, process all input. return table.concat(curbuf('get_lines', 0, -1, true), '\n') @@ -689,31 +652,6 @@ local function alter_slashes(obj) end end -local function hexdump(str) - local len = string.len(str) - local dump = "" - local hex = "" - local asc = "" - - for i = 1, len do - if 1 == i % 8 then - dump = dump .. hex .. asc .. "\n" - hex = string.format("%04x: ", i - 1) - asc = "" - end - - local ord = string.byte(str, i) - hex = hex .. string.format("%02x ", ord) - if ord >= 32 and ord <= 126 then - asc = asc .. string.char(ord) - else - asc = asc .. "." - end - end - - return dump .. hex .. string.rep(" ", 8 - len % 8) .. asc -end - local module = { NIL = mpack.NIL, alter_slashes = alter_slashes, @@ -736,6 +674,7 @@ local module = { exc_exec = exc_exec, expect = expect, expect_any = expect_any, + expect_err = expect_err, expect_msg_seq = expect_msg_seq, expect_twostreams = expect_twostreams, feed = feed, @@ -743,10 +682,10 @@ local module = { filter = filter, funcs = funcs, get_pathsep = get_pathsep, - hexdump = hexdump, insert = insert, iswin = iswin, map = map, + matches = matches, merge_args = merge_args, meth_pcall = meth_pcall, meths = meths, diff --git a/test/functional/insert/insert_spec.lua b/test/functional/insert/insert_spec.lua new file mode 100644 index 0000000000..427954f5a6 --- /dev/null +++ b/test/functional/insert/insert_spec.lua @@ -0,0 +1,41 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert +local command = helpers.command +local eq = helpers.eq +local expect = helpers.expect +local funcs = helpers.funcs + +describe('insert-mode', function() + before_each(function() + clear() + end) + + it('CTRL-@', function() + -- Inserts last-inserted text, leaves insert-mode. + insert('hello') + feed('i<C-@>x') + expect('hellhello') + + -- C-Space is the same as C-@. + -- CTRL-SPC inserts last-inserted text, leaves insert-mode. + feed('i<C-Space>x') + expect('hellhellhello') + + -- CTRL-A inserts last inserted text + feed('i<C-A>x') + expect('hellhellhellhelloxo') + end) + + it('ALT/META #8213', function() + -- Mapped ALT-chord behaves as mapped. + command('inoremap <M-l> meta-l') + command('inoremap <A-j> alt-j') + feed('i<M-l> xxx <A-j><M-h>a<A-h>') + expect('meta-l xxx alt-j') + eq({ 0, 1, 14, 0, }, funcs.getpos('.')) + -- Unmapped ALT-chord behaves as ESC+c. + command('iunmap <M-l>') + feed('0i<M-l>') + eq({ 0, 1, 2, 0, }, funcs.getpos('.')) + end) +end) diff --git a/test/functional/insert/last_inserted_spec.lua b/test/functional/insert/last_inserted_spec.lua deleted file mode 100644 index dce23a3790..0000000000 --- a/test/functional/insert/last_inserted_spec.lua +++ /dev/null @@ -1,22 +0,0 @@ -local helpers = require('test.functional.helpers')(after_each) -local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert -local expect = helpers.expect - -clear() - -describe('insert-mode', function() - it('CTRL-@ inserts last-inserted text, leaves insert-mode', function() - insert('hello') - feed('i<C-@>x') - expect('hellhello') - end) - -- C-Space is the same as C-@ - it('CTRL-SPC inserts last-inserted text, leaves insert-mode', function() - feed('i<C-Space>x') - expect('hellhellhello') - end) - it('CTRL-A inserts last inserted text', function() - feed('i<C-A>x') - expect('hellhellhellhelloxo') - end) -end) diff --git a/test/functional/legacy/077_mf_hash_grow_spec.lua b/test/functional/legacy/077_mf_hash_grow_spec.lua index c692127213..4719a3ecbf 100644 --- a/test/functional/legacy/077_mf_hash_grow_spec.lua +++ b/test/functional/legacy/077_mf_hash_grow_spec.lua @@ -18,7 +18,8 @@ describe('mf_hash_grow()', function() setup(clear) -- Check to see if cksum exists, otherwise skip the test - if os.execute('which cksum 2>&1 > /dev/null') ~= 0 then + local null = helpers.iswin() and 'nul' or '/dev/null' + if os.execute('cksum --help >' .. null .. ' 2>&1') ~= 0 then pending('was not tested because cksum was not found', function() end) else it('is working', function() diff --git a/test/functional/legacy/file_perm_spec.lua b/test/functional/legacy/file_perm_spec.lua index 77e82352c5..d61fdc9b83 100644 --- a/test/functional/legacy/file_perm_spec.lua +++ b/test/functional/legacy/file_perm_spec.lua @@ -15,7 +15,7 @@ describe('Test getting and setting file permissions', function() it('file permissions', function() eq('', call('getfperm', tempfile)) - eq(0, call('setfperm', tempfile, 'r------')) + eq(0, call('setfperm', tempfile, 'r--------')) call('writefile', {'one'}, tempfile) eq(9, call('len', call('getfperm', tempfile))) diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua index 6da0001cea..760105df6b 100644 --- a/test/functional/lua/luaeval_spec.lua +++ b/test/functional/lua/luaeval_spec.lua @@ -51,12 +51,12 @@ describe('luaeval()', function() end) describe('recursive lua values', function() it('are successfully transformed', function() - funcs.luaeval('rawset(_G, "d", {})') - funcs.luaeval('rawset(d, "d", d)') + command('lua rawset(_G, "d", {})') + command('lua rawset(d, "d", d)') eq('\n{\'d\': {...@0}}', funcs.execute('echo luaeval("d")')) - funcs.luaeval('rawset(_G, "l", {})') - funcs.luaeval('table.insert(l, l)') + command('lua rawset(_G, "l", {})') + command('lua table.insert(l, l)') eq('\n[[...@0]]', funcs.execute('echo luaeval("l")')) end) end) diff --git a/test/functional/lua/overrides_spec.lua b/test/functional/lua/overrides_spec.lua index 8ca5fe57ba..007d40874f 100644 --- a/test/functional/lua/overrides_spec.lua +++ b/test/functional/lua/overrides_spec.lua @@ -87,6 +87,7 @@ describe('debug.debug', function() E = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, cr = {bold = true, foreground = Screen.colors.SeaGreen4}, }) + command("set display-=msgsep") end) it('works', function() command([[lua diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index 9e29baba2d..f452cafd22 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -5,12 +5,15 @@ local Screen = require('test.functional.ui.screen') local meths = helpers.meths local command = helpers.command local clear = helpers.clear +local exc_exec = helpers.exc_exec local eval = helpers.eval local eq = helpers.eq +local funcs = helpers.funcs local insert = helpers.insert local neq = helpers.neq local mkdir = helpers.mkdir local rmdir = helpers.rmdir +local alter_slashes = helpers.alter_slashes describe('startup defaults', function() describe(':filetype', function() @@ -422,3 +425,276 @@ describe('XDG-based defaults', function() end) end) end) + + +describe('stdpath()', function() + context('returns a String', function() + describe('with "config"' , function () + it('knows XDG_CONFIG_HOME', function() + clear({env={ + XDG_CONFIG_HOME=alter_slashes('/home/docwhat/.config'), + }}) + eq(alter_slashes('/home/docwhat/.config/nvim'), funcs.stdpath('config')) + end) + + it('handles changes during runtime', function() + clear({env={ + XDG_CONFIG_HOME=alter_slashes('/home/original'), + }}) + eq(alter_slashes('/home/original/nvim'), funcs.stdpath('config')) + command("let $XDG_CONFIG_HOME='"..alter_slashes('/home/new').."'") + eq(alter_slashes('/home/new/nvim'), funcs.stdpath('config')) + end) + + it("doesn't expand $VARIABLES", function() + clear({env={ + XDG_CONFIG_HOME='$VARIABLES', + VARIABLES='this-should-not-happen', + }}) + eq(alter_slashes('$VARIABLES/nvim'), funcs.stdpath('config')) + end) + + it("doesn't expand ~/", function() + clear({env={ + XDG_CONFIG_HOME=alter_slashes('~/frobnitz'), + }}) + eq(alter_slashes('~/frobnitz/nvim'), funcs.stdpath('config')) + end) + end) + + describe('with "data"' , function () + local appended_dir + setup(function() + -- Windows appends 'nvim-data' instead of just 'nvim' to + -- prevent collisions due to XDG_CONFIG_HOME and XDG_DATA_HOME + -- being the same. + if helpers.iswin() then + appended_dir = '/nvim-data' + else + appended_dir = '/nvim' + end + end) + + it('knows XDG_DATA_HOME', function() + clear({env={ + XDG_DATA_HOME=alter_slashes('/home/docwhat/.local'), + }}) + eq(alter_slashes('/home/docwhat/.local' .. appended_dir), funcs.stdpath('data')) + end) + + it('handles changes during runtime', function() + clear({env={ + XDG_DATA_HOME=alter_slashes('/home/original'), + }}) + eq(alter_slashes('/home/original' .. appended_dir), funcs.stdpath('data')) + command("let $XDG_DATA_HOME='"..alter_slashes('/home/new').."'") + eq(alter_slashes('/home/new' .. appended_dir), funcs.stdpath('data')) + end) + + it("doesn't expand $VARIABLES", function() + clear({env={ + XDG_DATA_HOME='$VARIABLES', + VARIABLES='this-should-not-happen', + }}) + eq(alter_slashes('$VARIABLES' .. appended_dir), funcs.stdpath('data')) + end) + + it("doesn't expand ~/", function() + clear({env={ + XDG_DATA_HOME=alter_slashes('~/frobnitz'), + }}) + eq(alter_slashes('~/frobnitz' .. appended_dir), funcs.stdpath('data')) + end) + end) + + describe('with "cache"' , function () + it('knows XDG_CACHE_HOME', function() + clear({env={ + XDG_CACHE_HOME=alter_slashes('/home/docwhat/.cache'), + }}) + eq(alter_slashes('/home/docwhat/.cache/nvim'), funcs.stdpath('cache')) + end) + + it('handles changes during runtime', function() + clear({env={ + XDG_CACHE_HOME=alter_slashes('/home/original'), + }}) + eq(alter_slashes('/home/original/nvim'), funcs.stdpath('cache')) + command("let $XDG_CACHE_HOME='"..alter_slashes('/home/new').."'") + eq(alter_slashes('/home/new/nvim'), funcs.stdpath('cache')) + end) + + it("doesn't expand $VARIABLES", function() + clear({env={ + XDG_CACHE_HOME='$VARIABLES', + VARIABLES='this-should-not-happen', + }}) + eq(alter_slashes('$VARIABLES/nvim'), funcs.stdpath('cache')) + end) + + it("doesn't expand ~/", function() + clear({env={ + XDG_CACHE_HOME=alter_slashes('~/frobnitz'), + }}) + eq(alter_slashes('~/frobnitz/nvim'), funcs.stdpath('cache')) + end) + end) + end) + + context('returns a List', function() + -- Some OS specific variables the system would have set. + local function base_env() + if helpers.iswin() then + return { + HOME='C:\\Users\\docwhat', -- technically, is not a usual PATH + HOMEDRIVE='C:', + HOMEPATH='\\Users\\docwhat', + LOCALAPPDATA='C:\\Users\\docwhat\\AppData\\Local', + TEMP='C:\\Users\\docwhat\\AppData\\Local\\Temp', + TMPDIR='C:\\Users\\docwhat\\AppData\\Local\\Temp', + TMP='C:\\Users\\docwhat\\AppData\\Local\\Temp', + } + else + return { + HOME='/home/docwhat', + HOMEDRIVE='HOMEDRIVE-should-be-ignored', + HOMEPATH='HOMEPATH-should-be-ignored', + LOCALAPPDATA='LOCALAPPDATA-should-be-ignored', + TEMP='TEMP-should-be-ignored', + TMPDIR='TMPDIR-should-be-ignored', + TMP='TMP-should-be-ignored', + } + end + end + + local function set_paths_via_system(var_name, paths) + local env = base_env() + env[var_name] = table.concat(paths, ':') + clear({env=env}) + end + + local function set_paths_at_runtime(var_name, paths) + clear({env=base_env()}) + meths.set_var('env_val', table.concat(paths, ':')) + command(('let $%s=g:env_val'):format(var_name)) + end + + local function behaves_like_dir_list_env(msg, stdpath_arg, env_var_name, paths, expected_paths) + describe(msg, function() + it('set via system', function() + set_paths_via_system(env_var_name, paths) + eq(expected_paths, funcs.stdpath(stdpath_arg)) + end) + + it('set at runtime', function() + set_paths_at_runtime(env_var_name, paths) + eq(expected_paths, funcs.stdpath(stdpath_arg)) + end) + end) + end + + describe('with "config_dirs"' , function () + behaves_like_dir_list_env( + 'handles XDG_CONFIG_DIRS with one path', + 'config_dirs', 'XDG_CONFIG_DIRS', + { + alter_slashes('/home/docwhat/.config') + }, + { + alter_slashes('/home/docwhat/.config/nvim') + }) + + behaves_like_dir_list_env( + 'handles XDG_CONFIG_DIRS with two paths', + 'config_dirs', 'XDG_CONFIG_DIRS', + { + alter_slashes('/home/docwhat/.config'), + alter_slashes('/etc/config') + }, + { + alter_slashes('/home/docwhat/.config/nvim'), + alter_slashes('/etc/config/nvim') + }) + + behaves_like_dir_list_env( + "doesn't expand $VAR and $IBLES", + 'config_dirs', 'XDG_CONFIG_DIRS', + { '$HOME', '$TMP' }, + { + alter_slashes('$HOME/nvim'), + alter_slashes('$TMP/nvim') + }) + + + behaves_like_dir_list_env( + "doesn't expand ~/", + 'config_dirs', 'XDG_CONFIG_DIRS', + { + alter_slashes('~/.oldconfig'), + alter_slashes('~/.olderconfig') + }, + { + alter_slashes('~/.oldconfig/nvim'), + alter_slashes('~/.olderconfig/nvim') + }) + end) + + describe('with "data_dirs"' , function () + behaves_like_dir_list_env( + 'knows XDG_DATA_DIRS with one path', + 'data_dirs', 'XDG_DATA_DIRS', + { + alter_slashes('/home/docwhat/.data') + }, + { + alter_slashes('/home/docwhat/.data/nvim') + }) + + behaves_like_dir_list_env( + 'knows XDG_DATA_DIRS with two paths', + 'data_dirs', 'XDG_DATA_DIRS', + { + alter_slashes('/home/docwhat/.data'), + alter_slashes('/etc/local') + }, + { + alter_slashes('/home/docwhat/.data/nvim'), + alter_slashes('/etc/local/nvim'), + }) + + behaves_like_dir_list_env( + "doesn't expand $VAR and $IBLES", + 'data_dirs', 'XDG_DATA_DIRS', + { '$HOME', '$TMP' }, + { + alter_slashes('$HOME/nvim'), + alter_slashes('$TMP/nvim') + }) + + behaves_like_dir_list_env( + "doesn't expand ~/", + 'data_dirs', 'XDG_DATA_DIRS', + { + alter_slashes('~/.oldconfig'), + alter_slashes('~/.olderconfig') + }, + { + alter_slashes('~/.oldconfig/nvim'), + alter_slashes('~/.olderconfig/nvim'), + }) + end) + end) + + describe('errors', function() + it('on unknown strings', function() + eq('Vim(call):E6100: "capybara" is not a valid stdpath', exc_exec('call stdpath("capybara")')) + eq('Vim(call):E6100: "" is not a valid stdpath', exc_exec('call stdpath("")')) + eq('Vim(call):E6100: "23" is not a valid stdpath', exc_exec('call stdpath(23)')) + end) + + it('on non-strings', function() + eq('Vim(call):E731: using Dictionary as a String', exc_exec('call stdpath({"eris": 23})')) + eq('Vim(call):E730: using List as a String', exc_exec('call stdpath([23])')) + end) + end) +end) diff --git a/test/functional/options/num_options_spec.lua b/test/functional/options/num_options_spec.lua new file mode 100644 index 0000000000..ed17ffdd3c --- /dev/null +++ b/test/functional/options/num_options_spec.lua @@ -0,0 +1,97 @@ +-- Tests for :setlocal and :setglobal + +local helpers = require('test.functional.helpers')(after_each) +local clear, feed_command, eval, eq, meths = + helpers.clear, helpers.feed_command, helpers.eval, helpers.eq, helpers.meths + +local function should_fail(opt, value, errmsg) + feed_command('setglobal ' .. opt .. '=' .. value) + eq(errmsg, eval("v:errmsg"):match("E%d*")) + feed_command('let v:errmsg = ""') + feed_command('setlocal ' .. opt .. '=' .. value) + eq(errmsg, eval("v:errmsg"):match("E%d*")) + feed_command('let v:errmsg = ""') + local status, err = pcall(meths.set_option, opt, value) + eq(status, false) + eq(errmsg, err:match("E%d*")) + eq('', eval("v:errmsg")) +end + +local function should_succeed(opt, value) + feed_command('setglobal ' .. opt .. '=' .. value) + feed_command('setlocal ' .. opt .. '=' .. value) + meths.set_option(opt, value) + eq(value, meths.get_option(opt)) + eq('', eval("v:errmsg")) +end + +describe(':setlocal', function() + before_each(clear) + + it('setlocal sets only local value', function() + eq(0, meths.get_option('iminsert')) + feed_command('setlocal iminsert=1') + eq(0, meths.get_option('iminsert')) + eq(0, meths.get_option('imsearch')) + feed_command('setlocal imsearch=1') + eq(0, meths.get_option('imsearch')) + end) +end) + +describe(':set validation', function() + before_each(clear) + + it('setlocal and setglobal validate values', function() + should_fail('shiftwidth', -10, 'E487') + should_succeed('shiftwidth', 0) + should_fail('tabstop', -10, 'E487') + should_fail('winheight', -10, 'E487') + should_fail('winheight', 0, 'E487') + should_fail('winminheight', -1, 'E487') + should_succeed('winminheight', 0) + should_fail('winwidth', 0, 'E487') + should_fail('helpheight', -1, 'E487') + should_fail('maxcombine', 7, 'E474') + should_fail('iminsert', 3, 'E474') + should_fail('imsearch', 3, 'E474') + should_fail('titlelen', -1, 'E487') + should_fail('cmdheight', 0, 'E487') + should_fail('updatecount', -1, 'E487') + should_fail('textwidth', -1, 'E487') + should_fail('tabstop', 0, 'E487') + should_fail('timeoutlen', -1, 'E487') + should_fail('history', 1000000, 'E474') + should_fail('regexpengine', -1, 'E474') + should_fail('regexpengine', 3, 'E474') + should_succeed('regexpengine', 2) + should_fail('report', -1, 'E487') + should_succeed('report', 0) + should_fail('scrolloff', -1, 'E49') + should_fail('sidescrolloff', -1, 'E487') + should_fail('sidescroll', -1, 'E487') + should_fail('cmdwinheight', 0, 'E487') + should_fail('updatetime', -1, 'E487') + + should_fail('foldlevel', -5, 'E487') + should_fail('foldcolumn', 13, 'E474') + should_fail('conceallevel', 4, 'E474') + should_fail('numberwidth', 11, 'E474') + should_fail('numberwidth', 0, 'E487') + + -- If smaller than 1 this one is set to 'lines'-1 + feed_command('setglobal window=-10') + meths.set_option('window', -10) + eq(23, meths.get_option('window')) + eq('', eval("v:errmsg")) + end) + + it('set wmh/wh wmw/wiw checks', function() + feed_command('set winheight=2') + feed_command('set winminheight=3') + eq('E591', eval("v:errmsg"):match("E%d*")) + + feed_command('set winwidth=2') + feed_command('set winminwidth=3') + eq('E592', eval("v:errmsg"):match("E%d*")) + end) +end) diff --git a/test/functional/provider/nodejs_spec.lua b/test/functional/provider/nodejs_spec.lua index 0a12b1a154..f69c3e7c78 100644 --- a/test/functional/provider/nodejs_spec.lua +++ b/test/functional/provider/nodejs_spec.lua @@ -35,7 +35,7 @@ describe('nodejs host', function() nvim.command('call jobstop(g:job_id)'); ]]) command('let g:job_id = jobstart(["node", "'..fname..'"])') - retry(nil, 1000, function() eq('hello', eval('g:job_out')) end) + retry(nil, 2000, function() eq('hello', eval('g:job_out')) end) end) it('plugin works', function() local fname = 'Xtest-nodejs-hello-plugin.js' @@ -45,17 +45,15 @@ describe('nodejs host', function() const nvim = neovim.attach({socket: socket}); class TestPlugin { - hello() { - this.nvim.command('let g:job_out = "hello-plugin"') - } + hello() { + this.nvim.command('let g:job_out = "hello-plugin"'); + } } - const PluginClass = neovim.Plugin(TestPlugin); - const plugin = new PluginClass(nvim); - plugin.hello(); - nvim.command('call jobstop(g:job_id)'); + const plugin = new neovim.NvimPlugin(null, PluginClass, nvim); + plugin.instance.hello(); ]]) command('let g:job_id = jobstart(["node", "'..fname..'"])') - retry(nil, 1000, function() eq('hello-plugin', eval('g:job_out')) end) + retry(nil, 2000, function() eq('hello-plugin', eval('g:job_out')) end) end) end) diff --git a/test/functional/provider/python3_spec.lua b/test/functional/provider/python3_spec.lua index f06728ec0e..93ac3ae017 100644 --- a/test/functional/provider/python3_spec.lua +++ b/test/functional/provider/python3_spec.lua @@ -3,6 +3,7 @@ local eval, command, feed = helpers.eval, helpers.command, helpers.feed local eq, clear, insert = helpers.eq, helpers.clear, helpers.insert local expect, write_file = helpers.expect, helpers.write_file local feed_command = helpers.feed_command +local source = helpers.source local missing_provider = helpers.missing_provider do @@ -13,7 +14,7 @@ do end end -describe('python3 commands and functions', function() +describe('python3 provider', function() before_each(function() clear() command('python3 import vim') @@ -82,4 +83,20 @@ describe('python3 commands and functions', function() it('py3eval', function() eq({1, 2, {['key'] = 'val'}}, eval([[py3eval('[1, 2, {"key": "val"}]')]])) end) + + it('RPC call to expand("<afile>") during BufDelete #5245 #5617', function() + source([=[ + python3 << EOF + import vim + def foo(): + vim.eval('expand("<afile>:p")') + vim.eval('bufnr(expand("<afile>:p"))') + EOF + autocmd BufDelete * python3 foo() + autocmd BufUnload * python3 foo()]=]) + feed_command("exe 'split' tempname()") + feed_command("bwipeout!") + feed_command('help help') + eq(2, eval('1+1')) -- Still alive? + end) end) diff --git a/test/functional/provider/ruby_spec.lua b/test/functional/provider/ruby_spec.lua index a2c6c6a10e..e049ac7c41 100644 --- a/test/functional/provider/ruby_spec.lua +++ b/test/functional/provider/ruby_spec.lua @@ -1,16 +1,18 @@ local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local command = helpers.command +local curbufmeths = helpers.curbufmeths local eq = helpers.eq +local eval = helpers.eval +local expect = helpers.expect local feed = helpers.feed -local clear = helpers.clear +local feed_command = helpers.feed_command local funcs = helpers.funcs -local meths = helpers.meths local insert = helpers.insert -local expect = helpers.expect -local command = helpers.command -local write_file = helpers.write_file -local curbufmeths = helpers.curbufmeths +local meths = helpers.meths local missing_provider = helpers.missing_provider +local write_file = helpers.write_file do clear() @@ -90,3 +92,11 @@ describe(':rubydo command', function() eq(false, curbufmeths.get_option('modified')) end) end) + +describe('ruby provider', function() + it('RPC call to expand("<afile>") during BufDelete #5245 #5617', function() + command([=[autocmd BufDelete * ruby VIM::evaluate('expand("<afile>")')]=]) + feed_command('help help') + eq(2, eval('1+1')) -- Still alive? + end) +end) diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua index 7a15c8908b..a628baff53 100644 --- a/test/functional/shada/merging_spec.lua +++ b/test/functional/shada/merging_spec.lua @@ -525,6 +525,85 @@ describe('ShaDa marks support code', function() eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) end) + it('can merge with file with mark 9 as the only numeric mark', function() + wshada('\007\001\014\130\161f\196\006' .. mock_file_path .. '-\161n9') + eq(0, exc_exec(sdrcmd())) + nvim_command('normal! `9oabc') + eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = {} + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 7 and v.value.f == mock_file_path .. '-' then + local name = ('%c'):format(v.value.n) + found[name] = (found[name] or 0) + 1 + end + end + eq({['0']=1, ['1']=1}, found) + end) + + it('removes duplicates while merging', function() + wshada('\007\001\014\130\161f\196\006' .. mock_file_path .. '-\161n9' + .. '\007\001\014\130\161f\196\006' .. mock_file_path .. '-\161n9') + eq(0, exc_exec(sdrcmd())) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 7 and v.value.f == mock_file_path .. '-' then + print(require('test.helpers').format_luav(v)) + found = found + 1 + end + end + eq(1, found) + end) + + it('does not leak when no append is performed due to too many marks', + function() + wshada('\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'a\161n0' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'b\161n1' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161n2' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'd\161n3' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'e\161n4' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'f\161n5' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'g\161n6' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'h\161n7' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'i\161n8' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'j\161n9' + .. '\007\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'k\161n9') + eq(0, exc_exec(sdrcmd())) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = {} + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 7 and v.value.f:sub(1, #mock_file_path) == mock_file_path then + found[#found + 1] = v.value.f:sub(#v.value.f) + end + end + eq({'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}, found) + end) + + it('does not leak when last mark in file removes some of the earlier ones', + function() + wshada('\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'a\161n0' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'b\161n1' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161n2' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'd\161n3' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'e\161n4' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'f\161n5' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'g\161n6' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'h\161n7' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'i\161n8' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'j\161n9' + .. '\007\003\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'k\161n9') + eq(0, exc_exec(sdrcmd())) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = {} + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 7 and v.value.f:sub(1, #mock_file_path) == mock_file_path then + found[#found + 1] = v.value.f:sub(#v.value.f) + end + end + eq({'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k'}, found) + end) + it('uses last A mark with gt timestamp from file when reading with !', function() wshada('\007\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. '-\161nA') @@ -563,13 +642,14 @@ describe('ShaDa marks support code', function() nvim_command('normal! `A') eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) eq(0, exc_exec('wshada ' .. shada_fname)) - local found = 0 + local found = {} for _, v in ipairs(read_shada_file(shada_fname)) do - if v.type == 7 and v.value.f == '' .. mock_file_path .. '-' then - found = found + 1 + if v.type == 7 and v.value.f == mock_file_path .. '-' then + local name = ('%c'):format(v.value.n) + found[name] = (found[name] or 0) + 1 end end - eq(1, found) + eq({['0']=1, A=1}, found) end) it('uses last A mark with eq timestamp from instance when writing', @@ -580,30 +660,33 @@ describe('ShaDa marks support code', function() nvim_command('normal! `A') eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) eq(0, exc_exec('wshada ' .. shada_fname)) - local found = 0 + local found = {} for _, v in ipairs(read_shada_file(shada_fname)) do if v.type == 7 and v.value.f == mock_file_path .. '-' then - found = found + 1 + local name = ('%c'):format(v.value.n) + found[name] = (found[name] or 0) + 1 end end - eq(1, found) + eq({['0']=1, A=1}, found) end) - it('uses last A mark with gt timestamp from file when writing', - function() + it('uses last A mark with gt timestamp from file when writing', function() wshada('\007\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. '-\161nA') eq(0, exc_exec(sdrcmd())) wshada('\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. '?\161nA') nvim_command('normal! `A') eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) eq(0, exc_exec('wshada ' .. shada_fname)) - local found = 0 + local found = {} for _, v in ipairs(read_shada_file(shada_fname)) do - if v.type == 7 and v.value.f == '' .. mock_file_path .. '?' then - found = found + 1 + if v.type == 7 then + local name = ('%c'):format(v.value.n) + local t = found[name] or {} + t[v.value.f] = (t[v.value.f] or 0) + 1 + found[name] = t end end - eq(1, found) + eq({['0']={[mock_file_path .. '-']=1}, A={[mock_file_path .. '?']=1}}, found) end) it('uses last a mark with gt timestamp from instance when reading', diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua index ca44026852..720855860a 100644 --- a/test/functional/shada/shada_spec.lua +++ b/test/functional/shada/shada_spec.lua @@ -181,13 +181,13 @@ describe('ShaDa support code', function() nvim_command('set shada+=%') nvim_command('wshada! ' .. shada_fname) local readme_fname = funcs.resolve(paths.test_source_path) .. '/README.md' - eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname)) + eq({[7]=2, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname)) nvim_command('set shada+=r~') nvim_command('wshada! ' .. shada_fname) eq({}, find_file(readme_fname)) nvim_command('set shada-=r~') nvim_command('wshada! ' .. shada_fname) - eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname)) + eq({[7]=2, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname)) nvim_command('set shada+=r' .. funcs.escape( funcs.escape(paths.test_source_path, '$~'), ' "\\,')) nvim_command('wshada! ' .. shada_fname) @@ -206,7 +206,7 @@ describe('ShaDa support code', function() nvim_command('undo') nvim_command('set shada+=%') nvim_command('wshada! ' .. shada_fname) - eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=2}, find_file(fname)) + eq({[7]=2, [8]=2, [9]=1, [10]=4, [11]=2}, find_file(fname)) nvim_command('set shada+=r' .. pwd .. '/АБВ') nvim_command('wshada! ' .. shada_fname) eq({}, find_file(fname)) diff --git a/test/functional/terminal/edit_spec.lua b/test/functional/terminal/edit_spec.lua index d2b2d8a60c..84d7ae6e9c 100644 --- a/test/functional/terminal/edit_spec.lua +++ b/test/functional/terminal/edit_spec.lua @@ -36,6 +36,7 @@ describe(':edit term://*', function() local scr = get_screen(columns, lines) local rep = 'a' meths.set_option('shellcmdflag', 'REP ' .. rep) + command('set shellxquote=') -- win: avoid extra quotes local rep_size = rep:byte() -- 'a' => 97 local sb = 10 command('autocmd TermOpen * :setlocal scrollback='..tostring(sb) diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index 0a30b9bb02..4f22f7385d 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -8,6 +8,7 @@ local funcs = helpers.funcs local retry = helpers.retry local ok = helpers.ok local iswin = helpers.iswin +local command = helpers.command describe(':terminal', function() local screen @@ -143,6 +144,7 @@ describe(':terminal (with fake shell)', function() end) it('executes a given command through the shell', function() + command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell('echo hi') screen:expect([[ ^ready $ echo hi | @@ -154,6 +156,7 @@ describe(':terminal (with fake shell)', function() it("executes a given command through the shell, when 'shell' has arguments", function() nvim('set_option', 'shell', nvim_dir..'/shell-test -t jeff') + command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell('echo hi') screen:expect([[ ^jeff $ echo hi | @@ -164,6 +167,7 @@ describe(':terminal (with fake shell)', function() end) it('allows quotes and slashes', function() + command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell([[echo 'hello' \ "world"]]) screen:expect([[ ^ready $ echo 'hello' \ "world" | @@ -217,6 +221,7 @@ describe(':terminal (with fake shell)', function() end) it('works with gf', function() + command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell([[echo "scripts/shadacat.py"]]) screen:expect([[ ^ready $ echo "scripts/shadacat.py" | diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua index a21d9f0a56..263a5ce79d 100644 --- a/test/functional/terminal/mouse_spec.lua +++ b/test/functional/terminal/mouse_spec.lua @@ -101,7 +101,7 @@ describe('terminal mouse', function() line28 │line28 | line29 │line29 | line30 │line30 | - rows: 5, cols: 24 │rows: 5, cols: 24 | + rows: 5, cols: 25 │rows: 5, cols: 25 | {2:^ } │{2: } | ========== ========== | :vsp | @@ -111,7 +111,7 @@ describe('terminal mouse', function() {7: 1 }^ │line28 | {4:~ }│line29 | {4:~ }│line30 | - {4:~ }│rows: 5, cols: 24 | + {4:~ }│rows: 5, cols: 25 | {4:~ }│{2: } | ========== ========== | :enew | set number | @@ -121,7 +121,7 @@ describe('terminal mouse', function() {7: 27 }line │line28 | {7: 28 }line │line29 | {7: 29 }line │line30 | - {7: 30 }line │rows: 5, cols: 24 | + {7: 30 }line │rows: 5, cols: 25 | {7: 31 }^ │{2: } | ========== ========== | | @@ -131,7 +131,7 @@ describe('terminal mouse', function() {7: 27 }line │line28 | {7: 28 }line │line29 | {7: 29 }line │line30 | - {7: 30 }line │rows: 5, cols: 24 | + {7: 30 }line │rows: 5, cols: 25 | {7: 31 } │{1: } | ========== ========== | {3:-- TERMINAL --} | @@ -142,7 +142,7 @@ describe('terminal mouse', function() screen:expect([[ {7: 27 }line │line29 | {7: 28 }line │line30 | - {7: 29 }line │rows: 5, cols: 24 | + {7: 29 }line │rows: 5, cols: 25 | {7: 30 }line │mouse enabled | {7: 31 } │{1: } | ========== ========== | @@ -155,7 +155,7 @@ describe('terminal mouse', function() screen:expect([[ {7: 21 }line │line29 | {7: 22 }line │line30 | - {7: 23 }line │rows: 5, cols: 24 | + {7: 23 }line │rows: 5, cols: 25 | {7: 24 }line │mouse enabled | {7: 25 }line │{1: } | ========== ========== | @@ -165,7 +165,7 @@ describe('terminal mouse', function() screen:expect([[ {7: 26 }line │line29 | {7: 27 }line │line30 | - {7: 28 }line │rows: 5, cols: 24 | + {7: 28 }line │rows: 5, cols: 25 | {7: 29 }line │mouse enabled | {7: 30 }line │{1: } | ========== ========== | @@ -178,7 +178,7 @@ describe('terminal mouse', function() screen:expect([[ {7: 27 }line │line29 | {7: 28 }l^ine │line30 | - {7: 29 }line │rows: 5, cols: 24 | + {7: 29 }line │rows: 5, cols: 25 | {7: 30 }line │mouse enabled | {7: 31 } │{2: } | ========== ========== | diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 171745eb57..0ae5802a01 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -115,16 +115,12 @@ describe('tui', function() ]]) end) - it('does not mangle unmapped ALT-key chord', function() - -- Vim represents ALT/META by setting the "high bit" of the modified key; - -- we do _not_. #3982 - -- - -- Example: for input ALT+j: - -- * Vim (Nvim prior to #3982) sets high-bit, inserts "ê". - -- * Nvim (after #3982) inserts "j". - feed_data('i\027j') + it('interprets ESC+key as ALT chord', function() + -- Vim represents ALT/META by setting the "high bit" of the modified key: + -- ALT+j inserts "ê". Nvim does not (#3982). + feed_data('i\022\027j') screen:expect([[ - j{1: } | + <M-j>{1: } | {4:~ }| {4:~ }| {4:~ }| @@ -391,15 +387,7 @@ describe('tui FocusGained/FocusLost', function() -- Exit cmdline-mode. Redraws from timers/events are blocked during -- cmdline-mode, so the buffer won't be updated until we exit cmdline-mode. feed_data('\n') - screen:expect([[ - {1: } | - lost | - gained | - {4:~ }| - {5:[No Name] [+] }| - : | - {3:-- TERMINAL --} | - ]]) + screen:expect('lost'..(' '):rep(46)..'\ngained', nil, nil, nil, true) end) end) diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua index ffb6a26aef..3c316d1cfa 100644 --- a/test/functional/ui/cmdline_highlight_spec.lua +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -24,6 +24,7 @@ before_each(function() clear() screen = Screen.new(40, 8) screen:attach() + command("set display-=msgsep") source([[ highlight RBP1 guibg=Red highlight RBP2 guibg=Yellow @@ -737,6 +738,22 @@ describe('Command-line coloring', function() feed('<CR><CR>') eq('', meths.get_var('out')) end) + it('does not crash when callback has caught not-a-editor-command exception', + function() + source([[ + function CaughtExc(cmdline) abort + try + gibberish + catch + " Do nothing + endtry + return [] + endfunction + ]]) + set_color_cb('CaughtExc') + start_prompt('1') + eq(1, meths.eval('1')) + end) end) describe('Ex commands coloring support', function() it('works', function() diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index f8680678ef..41c290a462 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -157,6 +157,27 @@ describe('external cmdline', function() end) end) + it("redraws statusline on entering", function() + command('set laststatus=2') + command('set statusline=%{mode()}') + feed(':') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {3:c^ }| + | + ]], nil, nil, function() + eq({{ + content = { { {}, "" } }, + firstc = ":", + indent = 0, + pos = 0, + prompt = "" + }}, cmdline) + end) + end) + it("works with input()", function() feed(':call input("input", "default")<cr>') screen:expect([[ diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index b47210a777..812c095add 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -194,8 +194,8 @@ describe('ui/cursor', function() if m.blinkoff then m.blinkoff = 400 end if m.blinkwait then m.blinkwait = 700 end end - if m.hl_id then m.hl_id = 48 end - if m.id_lm then m.id_lm = 49 end + if m.hl_id then m.hl_id = 49 end + if m.id_lm then m.id_lm = 50 end end -- Assert the new expectation. diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index 6f1b31964b..ab3b1c3cac 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -94,6 +94,7 @@ describe('highlight defaults', function() clear() screen = Screen.new() screen:attach() + command("set display-=msgsep") end) after_each(function() @@ -312,7 +313,7 @@ describe('highlight defaults', function() end) end) -describe('guisp (special/undercurl)', function() +describe('highlight', function() local screen before_each(function() @@ -321,7 +322,31 @@ describe('guisp (special/undercurl)', function() screen:attach() end) - it('can be set and is applied like foreground or background', function() + it('cterm=standout gui=standout', function() + screen:detach() + screen = Screen.new(20,5) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {standout = true, bold = true, underline = true, + background = Screen.colors.Gray90, foreground = Screen.colors.Blue1}, + [3] = {standout = true, underline = true, + background = Screen.colors.Gray90} + }) + feed_command('hi CursorLine cterm=standout,underline gui=standout,underline') + feed_command('set cursorline') + feed_command('set listchars=space:.,eol:¬,tab:>-,extends:>,precedes:<,trail:* list') + feed('i\t abcd <cr>\t abcd <cr><esc>k') + screen:expect([[ + {1:>-------.}abcd{1:*¬} | + {2:^>-------.}{3:abcd}{2:*¬}{3: }| + {1:¬} | + {1:~ }| + | + ]]) + end) + + it('guisp (special/undercurl)', function() feed_command('syntax on') feed_command('syn keyword TmpKeyword neovim') feed_command('syn keyword TmpKeyword1 special') @@ -650,6 +675,76 @@ describe("'listchars' highlight", function() end) end) +describe("MsgSeparator highlight and msgsep fillchar", function() + before_each(clear) + it("works", function() + local screen = Screen.new(50,5) + screen:set_default_attr_ids({ + [1] = {bold=true, foreground=Screen.colors.Blue}, + [2] = {bold=true, reverse=true}, + [3] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [4] = {background = Screen.colors.Cyan, bold = true, reverse = true}, + [5] = {bold = true, background = Screen.colors.Magenta} + }) + screen:attach() + + -- defaults + feed_command("ls") + screen:expect([[ + | + {2: }| + :ls | + 1 %a "[No Name]" line 1 | + {3:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') + + feed_command("set fillchars+=msgsep:-") + feed_command("ls") + screen:expect([[ + | + {2:--------------------------------------------------}| + :ls | + 1 %a "[No Name]" line 1 | + {3:Press ENTER or type command to continue}^ | + ]]) + + -- linked to StatusLine per default + feed_command("hi StatusLine guibg=Cyan") + feed_command("ls") + screen:expect([[ + | + {4:--------------------------------------------------}| + :ls | + 1 %a "[No Name]" line 1 | + {3:Press ENTER or type command to continue}^ | + ]]) + + -- but can be unlinked + feed_command("hi clear MsgSeparator") + feed_command("hi MsgSeparator guibg=Magenta gui=bold") + feed_command("ls") + screen:expect([[ + | + {5:--------------------------------------------------}| + :ls | + 1 %a "[No Name]" line 1 | + {3:Press ENTER or type command to continue}^ | + ]]) + + -- when display doesn't contain msgsep, these options have no effect + feed_command("set display-=msgsep") + feed_command("ls") + screen:expect([[ + {1:~ }| + {1:~ }| + :ls | + 1 %a "[No Name]" line 1 | + {3:Press ENTER or type command to continue}^ | + ]]) + end) +end) + describe("'winhighlight' highlight", function() local screen @@ -683,6 +778,9 @@ describe("'winhighlight' highlight", function() [22] = {bold = true, foreground = Screen.colors.SeaGreen4}, [23] = {background = Screen.colors.LightMagenta}, [24] = {background = Screen.colors.WebGray}, + [25] = {bold = true, foreground = Screen.colors.Green1}, + [26] = {background = Screen.colors.Red}, + [27] = {background = Screen.colors.DarkBlue, bold = true, foreground = Screen.colors.Green1}, }) command("hi Background1 guibg=DarkBlue") command("hi Background2 guibg=DarkGreen") @@ -952,6 +1050,39 @@ describe("'winhighlight' highlight", function() ]]) end) + it("background doesn't override syntax background", function() + command('syntax on') + command('syntax keyword Foobar foobar') + command('syntax keyword Article the') + command('hi Foobar guibg=#FF0000') + command('hi Article guifg=#00FF00 gui=bold') + insert('the foobar was foobar') + screen:expect([[ + {25:the} {26:foobar} was {26:fooba}| + {26:^r} | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + + -- winhl=Normal:Group with background doesn't override syntax background, + -- but does combine with syntax foreground. + command('set winhl=Normal:Background1') + screen:expect([[ + {27:the}{1: }{26:foobar}{1: was }{26:fooba}| + {26:^r}{1: }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]) + end) + it('can override NonText, Conceal and EndOfBuffer', function() curbufmeths.set_lines(0,-1,true, {"raa\000"}) command('call matchaddpos("Conceal", [[1,2]], 0, -1, {"conceal": "#"})') diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index 8b0cb82365..ee1a3240a2 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -16,6 +16,8 @@ local retry = helpers.retry local source = helpers.source local wait = helpers.wait local nvim = helpers.nvim +local iswin = helpers.iswin +local sleep = helpers.sleep local default_text = [[ Inc substitution on @@ -63,6 +65,7 @@ local function common_setup(screen, inccommand, text) command("syntax on") command("set nohlsearch") command("hi Substitute guifg=red guibg=yellow") + command("set display-=msgsep") screen:attach() screen:set_default_attr_ids({ [1] = {foreground = Screen.colors.Fuchsia}, @@ -1353,6 +1356,23 @@ describe("inccommand=nosplit", function() :echo 'foo'^ | ]]) end) + + it("does not execute trailing bar-separated commands #7494", function() + feed(':%s/two/three/g|q!') + screen:expect([[ + Inc substitution on | + {12:three} lines | + Inc substitution on | + {12:three} lines | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/two/three/g|q!^ | + ]]) + eq(eval('v:null'), eval('v:exiting')) + end) end) describe(":substitute, 'inccommand' with a failing expression", function() @@ -1832,7 +1852,7 @@ describe(":substitute", function() clear() end) - it(", inccommand=split, highlights multiline substitutions", function() + it("inccommand=split, highlights multiline substitutions", function() common_setup(screen, "split", multiline_text) feed("gg") @@ -1894,7 +1914,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=nosplit, highlights multiline substitutions", function() + it("inccommand=nosplit, highlights multiline substitutions", function() common_setup(screen, "nosplit", multiline_text) feed("gg") @@ -1937,7 +1957,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=split, highlights multiple matches on a line", function() + it("inccommand=split, highlights multiple matches on a line", function() common_setup(screen, "split", multimatch_text) command("set gdefault") feed("gg") @@ -1962,7 +1982,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=nosplit, highlights multiple matches on a line", function() + it("inccommand=nosplit, highlights multiple matches on a line", function() common_setup(screen, "nosplit", multimatch_text) command("set gdefault") feed("gg") @@ -1987,7 +2007,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=split, with \\zs", function() + it("inccommand=split, with \\zs", function() common_setup(screen, "split", multiline_text) feed("gg") @@ -2011,7 +2031,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=nosplit, with \\zs", function() + it("inccommand=nosplit, with \\zs", function() common_setup(screen, "nosplit", multiline_text) feed("gg") @@ -2035,7 +2055,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=split, substitutions of different length", + it("inccommand=split, substitutions of different length", function() common_setup(screen, "split", "T T123 T2T TTT T090804\nx") @@ -2059,7 +2079,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=nosplit, substitutions of different length", function() + it("inccommand=nosplit, substitutions of different length", function() common_setup(screen, "nosplit", "T T123 T2T TTT T090804\nx") feed(":%s/T\\([0-9]\\+\\)/\\1\\1/g") @@ -2082,7 +2102,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=split, contraction of lines", function() + it("inccommand=split, contraction of lines", function() local text = [[ T T123 T T123 T2T TT T23423424 x @@ -2131,7 +2151,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=nosplit, contraction of lines", function() + it("inccommand=nosplit, contraction of lines", function() local text = [[ T T123 T T123 T2T TT T23423424 x @@ -2161,7 +2181,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=split, multibyte text", function() + it("inccommand=split, multibyte text", function() common_setup(screen, "split", multibyte_text) feed(":%s/£.*ѫ/X¥¥") screen:expect([[ @@ -2202,7 +2222,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=nosplit, multibyte text", function() + it("inccommand=nosplit, multibyte text", function() common_setup(screen, "nosplit", multibyte_text) feed(":%s/£.*ѫ/X¥¥") screen:expect([[ @@ -2243,7 +2263,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=split, small cmdwinheight", function() + it("inccommand=split, small cmdwinheight", function() common_setup(screen, "split", long_multiline_text) command("set cmdwinheight=2") @@ -2305,7 +2325,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=split, large cmdwinheight", function() + it("inccommand=split, large cmdwinheight", function() common_setup(screen, "split", long_multiline_text) command("set cmdwinheight=11") @@ -2367,7 +2387,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=split, lookaround", function() + it("inccommand=split, lookaround", function() common_setup(screen, "split", "something\neverything\nsomeone") feed([[:%s/\(some\)\@<lt>=thing/one/]]) screen:expect([[ @@ -2451,4 +2471,57 @@ describe(":substitute", function() :%s/some\(thing\)\@!/every/^ | ]]) end) + + it('with inccommand during :terminal activity', function() + command("set cmdwinheight=3") + if iswin() then + feed([[:terminal for /L \%I in (1,1,5000) do @(echo xxx & echo xxx & echo xxx)<cr>]]) + else + feed([[:terminal for i in $(seq 1 5000); do printf 'xxx\nxxx\nxxx\n'; done<cr>]]) + end + command('file term') + command('new') + common_setup(screen, 'split', 'foo bar baz\nbar baz fox\nbar foo baz') + command('wincmd =') + + -- Allow some terminal output. + screen:expect([[ + bar baz fox | + bar foo ba^z | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {11:[No Name] [+] }| + xxx | + xxx | + xxx | + xxx | + xxx | + xxx | + {10:term }| + | + ]]) + + feed('gg') + feed(':%s/foo/ZZZ') + sleep(50) -- Allow some terminal activity. + screen:expect([[ + {12:ZZZ} bar baz | + bar baz fox | + bar {12:ZZZ} baz | + {15:~ }| + {15:~ }| + {15:~ }| + {11:[No Name] [+] }| + xxx | + xxx | + {10:term }| + |1| {12:ZZZ} bar baz | + |3| bar {12:ZZZ} baz | + {15:~ }| + {10:[Preview] }| + :%s/foo/ZZZ^ | + ]]) + end) end) diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua index 8e62a37ef1..3dd9a2506e 100644 --- a/test/functional/ui/input_spec.lua +++ b/test/functional/ui/input_spec.lua @@ -1,6 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear, feed_command, nvim = helpers.clear, helpers.feed_command, helpers.nvim local feed, next_msg, eq = helpers.feed, helpers.next_msg, helpers.eq +local command = helpers.command local expect = helpers.expect local write_file = helpers.write_file local Screen = require('test.functional.ui.screen') @@ -127,6 +128,10 @@ describe('input utf sequences that contain CSI/K_SPECIAL', function() end) describe('input non-printable chars', function() + after_each(function() + os.remove('Xtest-overwrite') + end) + it("doesn't crash when echoing them back", function() write_file("Xtest-overwrite", [[foobar]]) clear() @@ -137,6 +142,7 @@ describe('input non-printable chars', function() [3] = {bold = true, foreground = Screen.colors.SeaGreen4} }) screen:attach() + command("set display-=msgsep") feed_command("e Xtest-overwrite") screen:expect([[ diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index 05d5a6409a..debd324977 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -26,6 +26,7 @@ describe('ui/mouse/input', function() [4] = {reverse = true}, [5] = {bold = true, reverse = true}, }) + command("set display-=msgsep") feed('itesting<cr>mouse<cr>support and selection<esc>') screen:expect([[ testing | diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua index 4246020fab..02ca566bd8 100644 --- a/test/functional/ui/output_spec.lua +++ b/test/functional/ui/output_spec.lua @@ -40,10 +40,10 @@ describe("shell command :!", function() -- to avoid triggering a UI flush. child_session.feed_data(":!printf foo; sleep 200\n") screen:expect([[ + | {4:~ }| {4:~ }| - {4:~ }| - {4:~ }| + {5: }| :!printf foo; sleep 200 | foo | {3:-- TERMINAL --} | @@ -99,6 +99,7 @@ describe("shell command :!", function() end local screen = Screen.new(50, 4) screen:attach() + command("set display-=msgsep") -- Print TAB chars. #2958 feed([[:!printf '1\t2\t3'<CR>]]) screen:expect([[ @@ -153,6 +154,7 @@ describe("shell command :!", function() [1] = {bold = true, foreground = Screen.colors.Blue1}, [2] = {foreground = Screen.colors.Blue1}, [3] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [4] = {bold = true, reverse = true}, }) screen:attach() end) @@ -170,10 +172,10 @@ describe("shell command :!", function() or [[:!ls bang_filter_spec ]]) feed([[\l]]) screen:expect([[ + | {1:~ }| {1:~ }| - {1:~ }| - {1:~ }| + {4: }| ]]..result..[[ | f1 | f2 | @@ -187,9 +189,9 @@ describe("shell command :!", function() feed_command('!cat test/functional/fixtures/shell_data.txt') screen.bell = false screen:expect([[ + | {1:~ }| - {1:~ }| - {1:~ }| + {4: }| :!cat test/functional/fixtures/shell_data.txt | {2:^@^A^B^C^D^E^F^H} | {2:^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\^]^^^_} | @@ -213,8 +215,8 @@ describe("shell command :!", function() feed_command(cmd) -- Note: only the first example of split composed char works screen:expect([[ - {1:~ }| - {1:~ }| + | + {4: }| :]]..cmd..[[ | å | ref: å̲ | diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index bbdf576641..06e4a19354 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -76,11 +76,26 @@ describe('Screen', function() local function check() eq(true, screen.suspended) end + + command('let g:ev = []') + command('autocmd VimResume * :call add(g:ev, "r")') + command('autocmd VimSuspend * :call add(g:ev, "s")') + + eq(false, screen.suspended) command('suspend') + eq({ 's', 'r' }, eval('g:ev')) + screen:expect(check) screen.suspended = false + feed('<c-z>') + eq({ 's', 'r', 's', 'r' }, eval('g:ev')) + screen:expect(check) + screen.suspended = false + + command('suspend') + eq({ 's', 'r', 's', 'r', 's', 'r' }, eval('g:ev')) end) end) @@ -225,8 +240,8 @@ describe('Screen', function() end) end) - describe('tabnew', function() - it('creates a new buffer', function() + describe('tabs', function() + it('tabnew creates a new buffer', function() command('sp') command('vsp') command('vsp') @@ -284,6 +299,62 @@ describe('Screen', function() | ]]) end) + + it('tabline is redrawn after messages', function() + command('tabnew') + screen:expect([[ + {4: [No Name] }{2: [No Name] }{3: }{4:X}| + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + + feed(':echo "'..string.rep('x\\n', 11)..'"<cr>') + screen:expect([[ + {1: }| + x | + x | + x | + x | + x | + x | + x | + x | + x | + x | + x | + | + {7:Press ENTER or type command to continue}^ | + ]]) + + feed('<cr>') + screen:expect([[ + {4: [No Name] }{2: [No Name] }{3: }{4:X}| + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) end) describe('insert mode', function() @@ -355,7 +426,8 @@ describe('Screen', function() ]]) end) - it('execute command with multi-line output', function() + it('execute command with multi-line output without msgsep', function() + command("set display-=msgsep") feed(':ls<cr>') screen:expect([[ {0:~ }| @@ -375,6 +447,28 @@ describe('Screen', function() ]]) feed('<cr>') -- skip the "Press ENTER..." state or tests will hang end) + + it('execute command with multi-line output and with msgsep', function() + command("set display+=msgsep") + feed(':ls<cr>') + screen:expect([[ + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {1: }| + :ls | + 1 %a "[No Name]" line 1 | + {7:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') -- skip the "Press ENTER..." state or tests will hang + end) end) describe('scrolling and clearing', function() @@ -573,6 +667,7 @@ describe('Screen', function() command('nnoremap <F1> :echo "TEST"<CR>') feed(':ls<CR>') screen:expect([[ + | {0:~ }| {0:~ }| {0:~ }| @@ -582,8 +677,7 @@ describe('Screen', function() {0:~ }| {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| + {1: }| :ls | 1 %a "[No Name]" line 1 | {7:Press ENTER or type command to continue}^ | @@ -607,22 +701,43 @@ describe('Screen', function() ]]) end) end) -end) -describe('nvim_ui_attach()', function() - before_each(function() - clear() - end) - it('handles very large width/height #2180', function() - local screen = Screen.new(999, 999) - screen:attach() - eq(999, eval('&lines')) - eq(999, eval('&columns')) - end) - it('invalid option returns error', function() - local screen = Screen.new() - local status, rv = pcall(function() screen:attach({foo={'foo'}}) end) - eq(false, status) - eq('No such ui option', rv:match("No such .*")) + -- Regression test for #8357 + it('does not have artifacts after temporary chars in insert mode', function() + command('inoremap jk <esc>') + feed('ifooj') + screen:expect([[ + foo^j | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {2:-- INSERT --} | + ]]) + feed('k') + screen:expect([[ + fo^o | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) end) end) diff --git a/test/functional/ui/searchhl_spec.lua b/test/functional/ui/searchhl_spec.lua index 9f273e8dc9..168080a092 100644 --- a/test/functional/ui/searchhl_spec.lua +++ b/test/functional/ui/searchhl_spec.lua @@ -4,6 +4,8 @@ local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert local feed_command = helpers.feed_command local eq = helpers.eq local eval = helpers.eval +local iswin = helpers.iswin +local sleep = helpers.sleep describe('search highlighting', function() local screen @@ -91,6 +93,38 @@ describe('search highlighting', function() ]]) end) + it('is preserved during :terminal activity', function() + if iswin() then + feed([[:terminal for /L \%I in (1,1,5000) do @(echo xxx & echo xxx & echo xxx)<cr>]]) + else + feed([[:terminal for i in $(seq 1 5000); do printf 'xxx\nxxx\nxxx\n'; done<cr>]]) + end + + feed(':file term<CR>') + feed(':vnew<CR>') + insert([[ + foo bar baz + bar baz foo + bar foo baz + ]]) + feed('/foo') + sleep(50) -- Allow some terminal activity. + screen:expect([[ + {3:foo} bar baz {3:│}xxx | + bar baz {2:foo} {3:│}xxx | + bar {2:foo} baz {3:│}xxx | + {3:│}xxx | + {1:~ }{3:│}xxx | + {5:[No Name] [+] }{3:term }| + /foo^ | + ]], { [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {background = Screen.colors.Yellow}, + [3] = {reverse = true}, + [4] = {foreground = Screen.colors.Red}, + [5] = {bold = true, reverse = true}, + }) + end) + it('works with incsearch', function() feed_command('set hlsearch') feed_command('set incsearch') diff --git a/test/helpers.lua b/test/helpers.lua index 91ceed4df1..0d3fe1316b 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -1,6 +1,44 @@ local assert = require('luassert') +local luv = require('luv') local lfs = require('lfs') +local quote_me = '[^.%w%+%-%@%_%/]' -- complement (needn't quote) +local function shell_quote(str) + if string.find(str, quote_me) or str == '' then + return '"' .. str:gsub('[$%%"\\]', '\\%0') .. '"' + else + return str + end +end + +local function argss_to_cmd(...) + local cmd = '' + for i = 1, select('#', ...) do + local arg = select(i, ...) + if type(arg) == 'string' then + cmd = cmd .. ' ' ..shell_quote(arg) + else + for _, subarg in ipairs(arg) do + cmd = cmd .. ' ' .. shell_quote(subarg) + end + end + end + return cmd +end + +local function popen_r(...) + return io.popen(argss_to_cmd(...), 'r') +end + +local function popen_w(...) + return io.popen(argss_to_cmd(...), 'w') +end + +-- sleeps the test runner (_not_ the nvim instance) +local function sleep(ms) + luv.sleep(ms) +end + local check_logs_useless_lines = { ['Warning: noted but unhandled ioctl']=1, ['could cause spurious value errors to appear']=2, @@ -16,6 +54,19 @@ end local function ok(res) return assert.is_true(res) end +local function matches(pat, actual) + if nil ~= string.match(actual, pat) then + return true + end + error(string.format('Pattern does not match.\nPattern:\n%s\nActual:\n%s', pat, actual)) +end +-- Expect an error matching pattern `pat`. +local function expect_err(pat, ...) + local fn = select(1, ...) + local fn_args = {...} + table.remove(fn_args, 1) + assert.error_matches(function() return fn(unpack(fn_args)) end, pat) +end -- initial_path: directory to recurse into -- re: include pattern (string) @@ -108,7 +159,7 @@ local uname = (function() return platform end - local status, f = pcall(io.popen, "uname -s") + local status, f = pcall(popen_r, 'uname', '-s') if status then platform = f:read("*l") f:close() @@ -240,7 +291,7 @@ local function check_cores(app, force) end local function which(exe) - local pipe = io.popen('which ' .. exe, 'r') + local pipe = popen_r('which', exe) local ret = pipe:read('*a') pipe:close() if ret == '' then @@ -250,6 +301,19 @@ local function which(exe) end end +local function repeated_read_cmd(...) + for _ = 1, 10 do + local stream = popen_r(...) + local ret = stream:read('*a') + stream:close() + if ret then + return ret + end + end + print('ERROR: Failed to execute ' .. argss_to_cmd(...) .. ': nil return after 10 attempts') + return nil +end + local function shallowcopy(orig) if type(orig) ~= 'table' then return orig @@ -469,6 +533,8 @@ format_luav = function(v, indent, opts) end elseif type(v) == 'nil' then ret = 'nil' + elseif type(v) == 'boolean' then + ret = (v and 'true' or 'false') else print(type(v)) -- Not implemented yet @@ -552,8 +618,62 @@ local function table_flatten(arr) return result end -return { +local function hexdump(str) + local len = string.len(str) + local dump = "" + local hex = "" + local asc = "" + + for i = 1, len do + if 1 == i % 8 then + dump = dump .. hex .. asc .. "\n" + hex = string.format("%04x: ", i - 1) + asc = "" + end + + local ord = string.byte(str, i) + hex = hex .. string.format("%02x ", ord) + if ord >= 32 and ord <= 126 then + asc = asc .. string.char(ord) + else + asc = asc .. "." + end + end + + return dump .. hex .. string.rep(" ", 8 - len % 8) .. asc +end + +local function read_file(name) + local file = io.open(name, 'r') + if not file then + return nil + end + local ret = file:read('*a') + file:close() + return ret +end + +-- Dedent the given text and write it to the file name. +local function write_file(name, text, no_dedent, append) + local file = io.open(name, (append and 'a' or 'w')) + if type(text) == 'table' then + -- Byte blob + local bytes = text + text = '' + for _, char in ipairs(bytes) do + text = ('%s%c'):format(text, char) + end + elseif not no_dedent then + text = dedent(text) + end + file:write(text) + file:flush() + file:close() +end + +local module = { REMOVE_THIS = REMOVE_THIS, + argss_to_cmd = argss_to_cmd, check_cores = check_cores, check_logs = check_logs, concat_tables = concat_tables, @@ -561,6 +681,7 @@ return { deepcopy = deepcopy, dictdiff = dictdiff, eq = eq, + expect_err = expect_err, filter = filter, fixtbl = fixtbl, fixtbl_rec = fixtbl_rec, @@ -568,15 +689,25 @@ return { format_string = format_string, glob = glob, hasenv = hasenv, + hexdump = hexdump, intchar2lua = intchar2lua, map = map, + matches = matches, mergedicts_copy = mergedicts_copy, neq = neq, ok = ok, + popen_r = popen_r, + popen_w = popen_w, + read_file = read_file, + repeated_read_cmd = repeated_read_cmd, + sleep = sleep, shallowcopy = shallowcopy, table_flatten = table_flatten, tmpname = tmpname, uname = uname, updated = updated, which = which, + write_file = write_file, } + +return module diff --git a/test/includes/pre/sys/stat.h b/test/includes/pre/sys/stat.h index c6cac80913..1cb811d579 100644 --- a/test/includes/pre/sys/stat.h +++ b/test/includes/pre/sys/stat.h @@ -1,4 +1,6 @@ -#define _GNU_SOURCE +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif #include <sys/stat.h> static const mode_t kS_IFMT = S_IFMT; diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index 5cc2be50b1..f8143a0125 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -779,7 +779,7 @@ local function gen_itp(it) end local function cppimport(path) - return cimport(Paths.test_include_path .. '/' .. path) + return cimport(Paths.test_source_path .. '/test/includes/pre/' .. path) end cimport('./src/nvim/types.h', './src/nvim/main.h', './src/nvim/os/time.h') diff --git a/test/unit/os/fileio_spec.lua b/test/unit/os/fileio_spec.lua index d9c98e8afa..4d58a8934e 100644 --- a/test/unit/os/fileio_spec.lua +++ b/test/unit/os/fileio_spec.lua @@ -113,7 +113,7 @@ end describe('file_open_fd', function() itp('can use file descriptor returned by os_open for reading', function() local fd = m.os_open(file1, m.kO_RDONLY, 0) - local err, fp = file_open_fd(fd, false) + local err, fp = file_open_fd(fd, m.kFileReadOnly) eq(0, err) eq({#fcontents, fcontents}, {file_read(fp, #fcontents)}) eq(0, m.file_close(fp, false)) @@ -121,7 +121,7 @@ describe('file_open_fd', function() itp('can use file descriptor returned by os_open for writing', function() eq(nil, lfs.attributes(filec)) local fd = m.os_open(filec, m.kO_WRONLY + m.kO_CREAT, 384) - local err, fp = file_open_fd(fd, true) + local err, fp = file_open_fd(fd, m.kFileWriteOnly) eq(0, err) eq(4, file_write(fp, 'test')) eq(0, m.file_close(fp, false)) @@ -133,7 +133,7 @@ end) describe('file_open_fd_new', function() itp('can use file descriptor returned by os_open for reading', function() local fd = m.os_open(file1, m.kO_RDONLY, 0) - local err, fp = file_open_fd_new(fd, false) + local err, fp = file_open_fd_new(fd, m.kFileReadOnly) eq(0, err) eq({#fcontents, fcontents}, {file_read(fp, #fcontents)}) eq(0, m.file_free(fp, false)) @@ -141,7 +141,7 @@ describe('file_open_fd_new', function() itp('can use file descriptor returned by os_open for writing', function() eq(nil, lfs.attributes(filec)) local fd = m.os_open(filec, m.kO_WRONLY + m.kO_CREAT, 384) - local err, fp = file_open_fd_new(fd, true) + local err, fp = file_open_fd_new(fd, m.kFileWriteOnly) eq(0, err) eq(4, file_write(fp, 'test')) eq(0, m.file_free(fp, false)) diff --git a/test/unit/os/fs_spec.lua b/test/unit/os/fs_spec.lua index bc39507c08..ae6dfe6423 100644 --- a/test/unit/os/fs_spec.lua +++ b/test/unit/os/fs_spec.lua @@ -199,7 +199,7 @@ describe('fs.c', function() itp('returns the absolute path when given an executable inside $PATH', function() local fullpath = exe('ls') - eq(1, fs.path_is_absolute_path(to_cstr(fullpath))) + eq(1, fs.path_is_absolute(to_cstr(fullpath))) end) itp('returns the absolute path when given an executable relative to the current dir', function() @@ -390,7 +390,7 @@ describe('fs.c', function() buf = ffi.new('char[?]', size + 1, ('\0'):rep(size)) end local eof = ffi.new('bool[?]', 1, {true}) - local ret2 = fs.os_read(fd, eof, buf, size) + local ret2 = fs.os_read(fd, eof, buf, size, false) local ret1 = eof[0] local ret3 = '' if buf ~= nil then @@ -408,7 +408,7 @@ describe('fs.c', function() end local iov = ffi.new('struct iovec[?]', #sizes, bufs) local eof = ffi.new('bool[?]', 1, {true}) - local ret2 = fs.os_readv(fd, eof, iov, #sizes) + local ret2 = fs.os_readv(fd, eof, iov, #sizes, false) local ret1 = eof[0] local ret3 = {} for i = 1,#sizes do @@ -418,7 +418,7 @@ describe('fs.c', function() return ret1, ret2, ret3 end local function os_write(fd, data) - return fs.os_write(fd, data, data and #data or 0) + return fs.os_write(fd, data, data and #data or 0, false) end describe('os_path_exists', function() @@ -491,6 +491,22 @@ describe('fs.c', function() end) end) + describe('os_dup', function() + itp('returns new file descriptor', function() + local dup0 = fs.os_dup(0) + local dup1 = fs.os_dup(1) + local dup2 = fs.os_dup(2) + local tbl = {[0]=true, [1]=true, [2]=true, + [tonumber(dup0)]=true, [tonumber(dup1)]=true, + [tonumber(dup2)]=true} + local i = 0 + for _, _ in pairs(tbl) do + i = i + 1 + end + eq(i, 6) -- All fds must be unique + end) + end) + describe('os_open', function() local new_file = 'test_new_file' local existing_file = 'unit-test-directory/test_existing.file' diff --git a/test/unit/path_spec.lua b/test/unit/path_spec.lua index ed597eaed7..e8ce660ce1 100644 --- a/test/unit/path_spec.lua +++ b/test/unit/path_spec.lua @@ -261,7 +261,7 @@ describe('path.c', function() end) end) -describe('path_shorten_fname_if_possible', function() +describe('path_try_shorten_fname', function() local cwd = lfs.currentdir() before_each(function() @@ -273,22 +273,22 @@ describe('path_shorten_fname_if_possible', function() lfs.rmdir('ut_directory') end) - describe('path_shorten_fname_if_possible', function() + describe('path_try_shorten_fname', function() itp('returns shortened path if possible', function() lfs.chdir('ut_directory') local full = to_cstr(lfs.currentdir() .. '/subdir/file.txt') - eq('subdir/file.txt', (ffi.string(cimp.path_shorten_fname_if_possible(full)))) + eq('subdir/file.txt', (ffi.string(cimp.path_try_shorten_fname(full)))) end) itp('returns `full_path` if a shorter version is not possible', function() local old = lfs.currentdir() lfs.chdir('ut_directory') local full = old .. '/subdir/file.txt' - eq(full, (ffi.string(cimp.path_shorten_fname_if_possible(to_cstr(full))))) + eq(full, (ffi.string(cimp.path_try_shorten_fname(to_cstr(full))))) end) itp('returns NULL if `full_path` is NULL', function() - eq(NULL, (cimp.path_shorten_fname_if_possible(NULL))) + eq(NULL, (cimp.path_try_shorten_fname(NULL))) end) end) end) @@ -585,22 +585,22 @@ describe('path.c', function() end) end) - describe('path_is_absolute_path', function() - local function path_is_absolute_path(filename) + describe('path_is_absolute', function() + local function path_is_absolute(filename) filename = to_cstr(filename) - return cimp.path_is_absolute_path(filename) + return cimp.path_is_absolute(filename) end itp('returns true if filename starts with a slash', function() - eq(OK, path_is_absolute_path('/some/directory/')) + eq(OK, path_is_absolute('/some/directory/')) end) itp('returns true if filename starts with a tilde', function() - eq(OK, path_is_absolute_path('~/in/my/home~/directory')) + eq(OK, path_is_absolute('~/in/my/home~/directory')) end) itp('returns false if filename starts not with slash nor tilde', function() - eq(FAIL, path_is_absolute_path('not/in/my/home~/directory')) + eq(FAIL, path_is_absolute('not/in/my/home~/directory')) end) end) end) diff --git a/test/unit/preprocess.lua b/test/unit/preprocess.lua index 363358d134..1073855a7d 100644 --- a/test/unit/preprocess.lua +++ b/test/unit/preprocess.lua @@ -2,6 +2,10 @@ -- windows, will probably need quite a bit of adjustment to run there. local ffi = require("ffi") +local global_helpers = require('test.helpers') + +local argss_to_cmd = global_helpers.argss_to_cmd +local repeated_read_cmd = global_helpers.repeated_read_cmd local ccs = {} @@ -22,15 +26,6 @@ table.insert(ccs, {path = {"/usr/bin/env", "gcc-4.7"}, type = "gcc"}) table.insert(ccs, {path = {"/usr/bin/env", "clang"}, type = "clang"}) table.insert(ccs, {path = {"/usr/bin/env", "icc"}, type = "gcc"}) -local quote_me = '[^.%w%+%-%@%_%/]' -- complement (needn't quote) -local function shell_quote(str) - if string.find(str, quote_me) or str == '' then - return "'" .. string.gsub(str, "'", [['"'"']]) .. "'" - else - return str - end -end - -- parse Makefile format dependencies into a Lua table local function parse_make_deps(deps) -- remove line breaks and line concatenators @@ -149,16 +144,6 @@ function Gcc:add_to_include_path(...) end end -local function argss_to_cmd(...) - local cmd = '' - for i = 1, select('#', ...) do - for _, arg in ipairs(select(i, ...)) do - cmd = cmd .. ' ' .. shell_quote(arg) - end - end - return cmd -end - -- returns a list of the headers files upon which this file relies function Gcc:dependencies(hdr) local cmd = argss_to_cmd(self.path, {'-M', hdr}) .. ' 2>&1' @@ -172,29 +157,15 @@ function Gcc:dependencies(hdr) end end -local function repeated_call(...) - local cmd = argss_to_cmd(...) - for _ = 1, 10 do - local stream = io.popen(cmd) - local ret = stream:read('*a') - stream:close() - if ret then - return ret - end - end - print('ERROR: preprocess.lua: Failed to execute ' .. cmd .. ': nil return after 10 attempts') - 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}) + local standard_defines = repeated_read_cmd(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 @@ -223,9 +194,9 @@ function Gcc:preprocess(previous_defines, ...) pseudoheader_file:flush() pseudoheader_file:close() - local defines = repeated_call(self.path, self.preprocessor_extra_flags, - self.get_defines_extra_flags, - {pseudoheader_fname}) + local defines = repeated_read_cmd(self.path, self.preprocessor_extra_flags, + self.get_defines_extra_flags, + {pseudoheader_fname}) defines = self:filter_standard_defines(defines) -- lfs = require("lfs") @@ -234,9 +205,10 @@ function Gcc:preprocess(previous_defines, ...) -- io.stderr\write("CWD: #{lfs.currentdir!}\n") -- io.stderr\write("CMD: #{cmd}\n") - local declarations = repeated_call(self.path, self.preprocessor_extra_flags, - self.get_declarations_extra_flags, - {pseudoheader_fname}) + local declarations = repeated_read_cmd(self.path, + self.preprocessor_extra_flags, + self.get_declarations_extra_flags, + {pseudoheader_fname}) os.remove(pseudoheader_fname) diff --git a/test/unit/undo_spec.lua b/test/unit/undo_spec.lua new file mode 100644 index 0000000000..f23110b329 --- /dev/null +++ b/test/unit/undo_spec.lua @@ -0,0 +1,215 @@ +local helpers = require('test.unit.helpers')(after_each) +local itp = helpers.gen_itp(it) +local lfs = require('lfs') +local child_call_once = helpers.child_call_once + +local global_helpers = require('test.helpers') +local sleep = global_helpers.sleep + +local ffi = helpers.ffi +local cimport = helpers.cimport +local to_cstr = helpers.to_cstr +local neq = helpers.neq +local eq = helpers.eq + +cimport('./src/nvim/ex_cmds_defs.h') +cimport('./src/nvim/buffer_defs.h') +local options = cimport('./src/nvim/option_defs.h') +-- TODO: remove: local vim = cimport('./src/nvim/vim.h') +local undo = cimport('./src/nvim/undo.h') +local buffer = cimport('./src/nvim/buffer.h') + +local old_p_udir = nil + +-- Values expected by tests. Set in the setup function and destroyed in teardown +local file_buffer = nil +local buffer_hash = nil + +child_call_once(function() + if old_p_udir == nil then + old_p_udir = options.p_udir -- save the old value of p_udir (undodir) + end + + -- create a new buffer + local c_file = to_cstr('Xtest-unit-undo') + file_buffer = buffer.buflist_new(c_file, c_file, 1, buffer.BLN_LISTED) + file_buffer.b_u_numhead = 1 -- Pretend that the buffer has been changed + + -- TODO(christopher.waldon.dev@gmail.com): replace the 32 with UNDO_HASH_SIZE + -- requires refactor of UNDO_HASH_SIZE into constant/enum for ffi + -- + -- compute a hash for this undofile + buffer_hash = ffi.new('char_u[32]') + undo.u_compute_hash(buffer_hash) +end) + + +describe('u_write_undo', function() + setup(function() + lfs.mkdir('unit-test-directory') + lfs.chdir('unit-test-directory') + options.p_udir = to_cstr(lfs.currentdir()) -- set p_udir to be the test dir + end) + + teardown(function() + lfs.chdir('..') + local success, err = lfs.rmdir('unit-test-directory') + if not success then + print(err) -- inform tester if directory fails to delete + end + options.p_udir = old_p_udir --restore old p_udir + end) + + -- Lua wrapper for u_write_undo + local function u_write_undo(name, forceit, buf, buf_hash) + if name ~= nil then + name = to_cstr(name) + end + + return undo.u_write_undo(name, forceit, buf, buf_hash) + end + + itp('writes an undo file to undodir given a buffer and hash', function() + u_write_undo(nil, false, file_buffer, buffer_hash) + local correct_name = ffi.string(undo.u_get_undo_file_name(file_buffer.b_ffname, false)) + local undo_file = io.open(correct_name, "r") + + neq(undo_file, nil) + local success, err = os.remove(correct_name) -- delete the file now that we're done with it. + if not success then + print(err) -- inform tester if undofile fails to delete + end + end) + + itp('writes a correctly-named undo file to undodir given a name, buffer, and hash', function() + local correct_name = "undofile.test" + u_write_undo(correct_name, false, file_buffer, buffer_hash) + local undo_file = io.open(correct_name, "r") + + neq(undo_file, nil) + local success, err = os.remove(correct_name) -- delete the file now that we're done with it. + if not success then + print(err) -- inform tester if undofile fails to delete + end + end) + + itp('does not write an undofile when the buffer has no valid undofile name', function() + -- TODO(christopher.waldon.dev@gmail.com): Figure out how to test this. + -- it's hard because u_get_undo_file_name() would need to return null + end) + + itp('writes the undofile with the same permissions as the original file', function() + -- Create Test file and set permissions + local test_file_name = "./test.file" + local test_permission_file = io.open(test_file_name, "w") + test_permission_file:write("testing permissions") + test_permission_file:close() + local test_permissions = lfs.attributes(test_file_name).permissions + + -- Create vim buffer + local c_file = to_cstr(test_file_name) + file_buffer = buffer.buflist_new(c_file, c_file, 1, buffer.BLN_LISTED) + file_buffer.b_u_numhead = 1 -- Pretend that the buffer has been changed + + u_write_undo(nil, false, file_buffer, buffer_hash) + + -- Find out the correct name of the undofile + local undo_file_name = ffi.string(undo.u_get_undo_file_name(file_buffer.b_ffname, false)) + + -- Find out the permissions of the new file + local permissions = lfs.attributes(undo_file_name).permissions + eq(test_permissions, permissions) + + -- delete the file now that we're done with it. + local success, err = os.remove(test_file_name) + if not success then + print(err) -- inform tester if undofile fails to delete + end + success, err = os.remove(undo_file_name) + if not success then + print(err) -- inform tester if undofile fails to delete + end + end) + + itp('writes an undofile only readable by the user if the buffer is unnamed', function() + local correct_permissions = "rw-------" + local undo_file_name = "test.undo" + + -- Create vim buffer + file_buffer = buffer.buflist_new(nil, nil, 1, buffer.BLN_LISTED) + file_buffer.b_u_numhead = 1 -- Pretend that the buffer has been changed + + u_write_undo(undo_file_name, false, file_buffer, buffer_hash) + + -- Find out the permissions of the new file + local permissions = lfs.attributes(undo_file_name).permissions + eq(correct_permissions, permissions) + + -- delete the file now that we're done with it. + local success, err = os.remove(undo_file_name) + if not success then + print(err) -- inform tester if undofile fails to delete + end + end) + + itp('forces writing undo file for :wundo! command', function() + local file_contents = "testing permissions" + -- Write a text file where the undofile should go + local correct_name = ffi.string(undo.u_get_undo_file_name(file_buffer.b_ffname, false)) + global_helpers.write_file(correct_name, file_contents, true, false) + + -- Call with `forceit`. + u_write_undo(correct_name, true, file_buffer, buffer_hash) + + local undo_file_contents = global_helpers.read_file(correct_name) + + neq(file_contents, undo_file_contents) + local success, deletion_err = os.remove(correct_name) -- delete the file now that we're done with it. + if not success then + print(deletion_err) -- inform tester if undofile fails to delete + end + end) + + itp('overwrites an existing undo file', function() + u_write_undo(nil, false, file_buffer, buffer_hash) + local correct_name = ffi.string(undo.u_get_undo_file_name(file_buffer.b_ffname, false)) + + local file_last_modified = lfs.attributes(correct_name).modification + + sleep(1000) -- Ensure difference in timestamps. + file_buffer.b_u_numhead = 1 -- Mark it as if there are changes + u_write_undo(nil, false, file_buffer, buffer_hash) + + local file_last_modified_2 = lfs.attributes(correct_name).modification + + -- print(file_last_modified, file_last_modified_2) + neq(file_last_modified, file_last_modified_2) + local success, err = os.remove(correct_name) -- delete the file now that we're done with it. + if not success then + print(err) -- inform tester if undofile fails to delete + end + end) + + itp('does not overwrite an existing file that is not an undo file', function() + -- TODO: write test + end) + + itp('does not overwrite an existing file that has the wrong permissions', function() + -- TODO: write test + end) + + itp('does not write an undo file if there is no undo information for the buffer', function() + file_buffer.b_u_numhead = 0 -- Mark it as if there is no undo information + local correct_name = ffi.string(undo.u_get_undo_file_name(file_buffer.b_ffname, false)) + + local existing_file = io.open(correct_name,"r") + if existing_file then + existing_file:close() + os.remove(correct_name) + end + u_write_undo(nil, false, file_buffer, buffer_hash) + local undo_file = io.open(correct_name, "r") + + eq(undo_file, nil) + end) +end) diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index 7c95bbb91d..30503e3c4a 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -29,6 +29,7 @@ set(DEPS_DOWNLOAD_DIR "${DEPS_BUILD_DIR}/downloads" CACHE PATH "Dependencies dow option(USE_BUNDLED "Use bundled dependencies." ON) +option(USE_BUNDLED_GPERF "Use the bundled version of gperf." ${USE_BUNDLED}) option(USE_BUNDLED_JEMALLOC "Use the bundled jemalloc." ${USE_BUNDLED}) option(USE_BUNDLED_UNIBILIUM "Use the bundled unibilium." ${USE_BUNDLED}) option(USE_BUNDLED_LIBTERMKEY "Use the bundled libtermkey." ${USE_BUNDLED}) @@ -42,10 +43,10 @@ option(USE_BUNDLED_LUV "Use the bundled version of luv." ${USE_BUNDLED}) # build it unless explicitly requested option(USE_BUNDLED_LUA "Use the bundled version of lua." OFF) -if(USE_BUNDLED AND (NOT WIN32)) - option(USE_BUNDLED_GPERF "Use the bundled version of gperf." ON) +if(USE_BUNDLED AND MSVC) + option(USE_BUNDLED_GETTEXT "Use the bundled version of gettext." ON) else() - option(USE_BUNDLED_GPERF "Use the bundled version of gperf." OFF) + option(USE_BUNDLED_GETTEXT "Use the bundled version of gettext." OFF) endif() option(USE_EXISTING_SRC_DIR "Skip download of deps sources in case of existing source directory." OFF) @@ -123,13 +124,8 @@ set(LUAROCKS_SHA256 ea1881d6954f2a98c34f93674571c8f0cbdbc28dedb3fa3cb56b6a91886d set(UNIBILIUM_URL https://github.com/mauke/unibilium/archive/v2.0.0.tar.gz) set(UNIBILIUM_SHA256 78997d38d4c8177c60d3d0c1aa8c53fd0806eb21825b7b335b1768d7116bc1c1) -if(WIN32) -set(LIBTERMKEY_URL https://github.com/equalsraf/libtermkey/archive/tb-windows.zip) -set(LIBTERMKEY_SHA256 c81e33e38662b151a49847ff4feef4f8c4b2a66f3e159a28b575cbc9bcd8ffea) -else() set(LIBTERMKEY_URL http://www.leonerd.org.uk/code/libtermkey/libtermkey-0.20.tar.gz) set(LIBTERMKEY_SHA256 6c0d87c94ab9915e76ecd313baec08dedf3bd56de83743d9aa923a081935d2f5) -endif() set(LIBVTERM_URL https://github.com/neovim/libvterm/archive/a9c7c6fd20fa35e0ad3e0e98901ca12dfca9c25c.tar.gz) set(LIBVTERM_SHA256 1a4272be91d9614dc183a503786df83b6584e4afaab7feaaa5409f841afbd796) @@ -159,6 +155,9 @@ set(WIN32YANK_X86_64_SHA256 33a747a92da60fb65e668edbf7661d3d902411a2d545fe9dc086 set(WINPTY_URL https://github.com/rprichard/winpty/releases/download/0.4.3/winpty-0.4.3-msvc2015.zip) set(WINPTY_SHA256 35a48ece2ff4acdcbc8299d4920de53eb86b1fb41e64d2fe5ae7898931bcee89) +set(GETTEXT_URL https://ftp.gnu.org/pub/gnu/gettext/gettext-0.19.8.1.tar.gz) +set(GETTEXT_SHA256 ff942af0e438ced4a8b0ea4b0b6e0d6d657157c5e2364de57baa279c1c125c43) + if(USE_BUNDLED_UNIBILIUM) include(BuildUnibilium) endif() @@ -203,6 +202,10 @@ if(USE_BUNDLED_GPERF) include(BuildGperf) endif() +if(USE_BUNDLED_GETTEXT) + include(BuildGettext) +endif() + include(GetBinaryDeps) if(WIN32) diff --git a/third-party/cmake/BuildGettext.cmake b/third-party/cmake/BuildGettext.cmake new file mode 100644 index 0000000000..1dd206a91b --- /dev/null +++ b/third-party/cmake/BuildGettext.cmake @@ -0,0 +1,34 @@ +if(MSVC) + + ExternalProject_Add(gettext + PREFIX ${DEPS_BUILD_DIR} + URL ${GETTEXT_URL} + DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/gettext + DOWNLOAD_COMMAND ${CMAKE_COMMAND} + -DPREFIX=${DEPS_BUILD_DIR} + -DDOWNLOAD_DIR=${DEPS_DOWNLOAD_DIR}/gettext + -DURL=${GETTEXT_URL} + -DEXPECTED_SHA256=${GETTEXT_SHA256} + -DTARGET=gettext + -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake + PATCH_COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/gettext init + COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/gettext apply --ignore-whitespace + ${CMAKE_CURRENT_SOURCE_DIR}/patches/gettext-Fix-compilation-on-a-system-without-alloca.patch + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/GettextCMakeLists.txt + ${DEPS_BUILD_DIR}/src/gettext/gettext-runtime/CMakeLists.txt + COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/gettext/gettext-runtime + -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} + # Pass toolchain + -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} + INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}) + +else() + message(FATAL_ERROR "Trying to build gettext in an unsupported system ${CMAKE_SYSTEM_NAME}/${CMAKE_C_COMPILER_ID}") +endif() + +list(APPEND THIRD_PARTY_DEPS gettext) diff --git a/third-party/cmake/BuildGperf.cmake b/third-party/cmake/BuildGperf.cmake index 494d0d9717..fa8b35f383 100644 --- a/third-party/cmake/BuildGperf.cmake +++ b/third-party/cmake/BuildGperf.cmake @@ -36,16 +36,28 @@ function(BuildGperf) INSTALL_COMMAND "${_gperf_INSTALL_COMMAND}") endfunction() -set(GPERF_BUILDARGS CC=${HOSTDEPS_C_COMPILER} LD=${HOSTDEPS_C_COMPILER}) - if(UNIX OR (MINGW AND CMAKE_CROSSCOMPILING)) BuildGperf( CONFIGURE_COMMAND ${DEPS_BUILD_DIR}/src/gperf/configure - --prefix=${HOSTDEPS_INSTALL_DIR} + --prefix=${HOSTDEPS_INSTALL_DIR} MAKE=${MAKE_PRG} INSTALL_COMMAND ${MAKE_PRG} install) +elseif(MSVC OR MINGW) + + BuildGperf( + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/GperfCMakeLists.txt + ${DEPS_BUILD_DIR}/src/gperf/CMakeLists.txt + COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/gperf/CMakeLists.txt + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} + BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} + INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}) + else() message(FATAL_ERROR "Trying to build gperf in an unsupported system ${CMAKE_SYSTEM_NAME}/${CMAKE_C_COMPILER_ID}") endif() - diff --git a/third-party/cmake/BuildLibtermkey.cmake b/third-party/cmake/BuildLibtermkey.cmake index b5cd0c4ca6..8147c47e1e 100644 --- a/third-party/cmake/BuildLibtermkey.cmake +++ b/third-party/cmake/BuildLibtermkey.cmake @@ -11,14 +11,22 @@ ExternalProject_Add(libtermkey -DTARGET=libtermkey -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake - CONFIGURE_COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/libtermkey - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - # Pass toolchain - -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} - # Hack to avoid -rdynamic in Mingw - -DCMAKE_SHARED_LIBRARY_LINK_C_FLAGS="" - -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + PATCH_COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/libtermkey init + COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/libtermkey apply --ignore-whitespace + ${CMAKE_CURRENT_SOURCE_DIR}/patches/libtermkey-Add-support-for-Windows.patch + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/libtermkeyCMakeLists.txt + ${DEPS_BUILD_DIR}/src/libtermkey/CMakeLists.txt + COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/libtermkey + -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} + # Pass toolchain + -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + # Hack to avoid -rdynamic in Mingw + -DCMAKE_SHARED_LIBRARY_LINK_C_FLAGS="" + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DUNIBILIUM_INCLUDE_DIRS=${DEPS_INSTALL_DIR}/include + -DUNIBILIUM_LIBRARIES=${DEPS_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}unibilium${CMAKE_STATIC_LIBRARY_SUFFIX} BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}) else() diff --git a/third-party/cmake/BuildLibuv.cmake b/third-party/cmake/BuildLibuv.cmake index a1c7e7f986..084e707f92 100644 --- a/third-party/cmake/BuildLibuv.cmake +++ b/third-party/cmake/BuildLibuv.cmake @@ -7,7 +7,7 @@ function(BuildLibuv) cmake_parse_arguments(_libuv "BUILD_IN_SOURCE" "TARGET" - "CONFIGURE_COMMAND;BUILD_COMMAND;INSTALL_COMMAND" + "PATCH_COMMAND;CONFIGURE_COMMAND;BUILD_COMMAND;INSTALL_COMMAND" ${ARGN}) if(NOT _libuv_CONFIGURE_COMMAND AND NOT _libuv_BUILD_COMMAND @@ -31,6 +31,7 @@ function(BuildLibuv) -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake BUILD_IN_SOURCE ${_libuv_BUILD_IN_SOURCE} + PATCH_COMMAND "${_libuv_PATCH_COMMAND}" CONFIGURE_COMMAND "${_libuv_CONFIGURE_COMMAND}" BUILD_COMMAND "${_libuv_BUILD_COMMAND}" INSTALL_COMMAND "${_libuv_INSTALL_COMMAND}") @@ -41,9 +42,14 @@ set(UNIX_CFGCMD sh ${DEPS_BUILD_DIR}/src/libuv/autogen.sh && --prefix=${DEPS_INSTALL_DIR} --libdir=${DEPS_INSTALL_DIR}/lib CC=${DEPS_C_COMPILER}) +set(LIBUV_PATCH_COMMAND +${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/libuv init + COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/libuv apply + ${CMAKE_CURRENT_SOURCE_DIR}/patches/libuv-overlapped.patch) + if(UNIX) BuildLibuv( - CONFIGURE_COMMAND ${UNIX_CFGCMD} + CONFIGURE_COMMAND ${UNIX_CFGCMD} MAKE=${MAKE_PRG} INSTALL_COMMAND ${MAKE_PRG} V=1 install) elseif(MINGW AND CMAKE_CROSSCOMPILING) @@ -54,6 +60,7 @@ elseif(MINGW AND CMAKE_CROSSCOMPILING) # Build libuv for the target BuildLibuv( + PATCH_COMMAND ${LIBUV_PATCH_COMMAND} CONFIGURE_COMMAND ${UNIX_CFGCMD} --host=${CROSS_TARGET} INSTALL_COMMAND ${MAKE_PRG} V=1 install) @@ -61,6 +68,7 @@ elseif(MINGW) # Native MinGW BuildLibUv(BUILD_IN_SOURCE + PATCH_COMMAND ${LIBUV_PATCH_COMMAND} BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} -f Makefile.mingw INSTALL_COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/lib COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/libuv/libuv.a ${DEPS_INSTALL_DIR}/lib @@ -70,28 +78,20 @@ elseif(MINGW) elseif(WIN32 AND MSVC) - find_package(PythonInterp 2.6 REQUIRED) - if(NOT PYTHONINTERP_FOUND OR PYTHON_VERSION_MAJOR GREATER 2) - message(FATAL_ERROR "Python2 is required to build libuv on windows, use -DPYTHON_EXECUTABLE to set a python interpreter") - endif() - - include(TargetArch) - if(TARGET_ARCH STREQUAL "X86_64") - set(TARGET_ARCH x64) - elseif(TARGET_ARCH STREQUAL "X86") - set(TARGET_ARCH x86) - endif() - if(CMAKE_BUILD_TYPE STREQUAL "Debug") - set(TARGET_CONFIG debug) - else() - set(TARGET_CONFIG release) - endif() - set(UV_OUTPUT_DIR ${DEPS_BUILD_DIR}/src/libuv/${TARGET_CONFIG}) - BuildLibUv( - BUILD_COMMAND set PYTHON=${PYTHON_EXECUTABLE} COMMAND ${DEPS_BUILD_DIR}/src/libuv/vcbuild.bat shared ${TARGET_CONFIG} ${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 + set(UV_OUTPUT_DIR ${DEPS_BUILD_DIR}/src/libuv/${CMAKE_BUILD_TYPE}) + BuildLibUv(BUILD_IN_SOURCE + PATCH_COMMAND ${LIBUV_PATCH_COMMAND} + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/LibuvCMakeLists.txt + ${DEPS_BUILD_DIR}/src/libuv/CMakeLists.txt + COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/libuv/CMakeLists.txt + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DBUILD_SHARED_LIBS=ON + -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} + BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} + INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE} # Some applications (lua-client/luarocks) look for uv.lib instead of libuv.lib COMMAND ${CMAKE_COMMAND} -E copy ${UV_OUTPUT_DIR}/libuv.lib ${DEPS_INSTALL_DIR}/lib/uv.lib COMMAND ${CMAKE_COMMAND} -E copy ${UV_OUTPUT_DIR}/libuv.dll ${DEPS_INSTALL_DIR}/bin/ diff --git a/third-party/cmake/BuildLibvterm.cmake b/third-party/cmake/BuildLibvterm.cmake index 73e2990654..e4649986af 100644 --- a/third-party/cmake/BuildLibvterm.cmake +++ b/third-party/cmake/BuildLibvterm.cmake @@ -38,13 +38,16 @@ if(WIN32) if(MSVC) set(LIBVTERM_PATCH_COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/libvterm init - COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/libvterm apply + COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/libvterm apply --ignore-whitespace ${CMAKE_CURRENT_SOURCE_DIR}/patches/libvterm-Remove-VLAs-for-MSVC.patch) endif() set(LIBVTERM_CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_CURRENT_SOURCE_DIR}/cmake/LibvtermCMakeLists.txt - ${DEPS_BUILD_DIR}/src/libvterm/CMakeLists.txt - COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/libvterm + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/LibvtermCMakeLists.txt + ${DEPS_BUILD_DIR}/src/libvterm/CMakeLists.txt + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Libvterm-tbl2inc_c.cmake + ${DEPS_BUILD_DIR}/src/libvterm/tbl2inc_c.cmake + COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/libvterm -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1} -fPIC" diff --git a/third-party/cmake/BuildLuajit.cmake b/third-party/cmake/BuildLuajit.cmake index e65a81bba5..2fda221b12 100644 --- a/third-party/cmake/BuildLuajit.cmake +++ b/third-party/cmake/BuildLuajit.cmake @@ -35,10 +35,16 @@ function(BuildLuajit) INSTALL_COMMAND "${_luajit_INSTALL_COMMAND}") endfunction() +if(CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + set(AMD64_ABI "LDFLAGS=-lpthread -lc++abi") +else() + set(AMD64_ABI "") +endif() set(INSTALLCMD_UNIX ${MAKE_PRG} CFLAGS=-fPIC CFLAGS+=-DLUAJIT_DISABLE_JIT CFLAGS+=-DLUA_USE_APICHECK CFLAGS+=-DLUA_USE_ASSERT + ${AMD64_ABI} CCDEBUG+=-g Q= install) diff --git a/third-party/cmake/BuildLuarocks.cmake b/third-party/cmake/BuildLuarocks.cmake index f9960495fc..b5fcf7f3e5 100644 --- a/third-party/cmake/BuildLuarocks.cmake +++ b/third-party/cmake/BuildLuarocks.cmake @@ -85,7 +85,7 @@ elseif(MSVC OR MINGW) BuildLuarocks( PATCH_COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/luarocks init - COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/luarocks apply + COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/luarocks apply --ignore-whitespace ${CMAKE_CURRENT_SOURCE_DIR}/patches/luarocks-Change-default-downloader-to-curl.patch INSTALL_COMMAND install.bat /FORCECONFIG /NOREG /NOADMIN /Q /F /LUA ${DEPS_INSTALL_DIR} diff --git a/third-party/cmake/BuildLuv.cmake b/third-party/cmake/BuildLuv.cmake index 2a786dd8f3..339264746c 100644 --- a/third-party/cmake/BuildLuv.cmake +++ b/third-party/cmake/BuildLuv.cmake @@ -37,9 +37,17 @@ set(LUV_SRC_DIR ${DEPS_BUILD_DIR}/src/luv) set(LUV_INCLUDE_FLAGS "-I${DEPS_INSTALL_DIR}/include -I${DEPS_INSTALL_DIR}/include/luajit-2.0") +# Replace luv default rockspec with the alternate one under the "rockspecs" +# directory set(LUV_PATCH_COMMAND - ${CMAKE_COMMAND} -DLUV_SRC_DIR=${LUV_SRC_DIR} - -P ${PROJECT_SOURCE_DIR}/cmake/PatchLuv.cmake) + ${CMAKE_COMMAND} -E copy_directory ${LUV_SRC_DIR}/rockspecs ${LUV_SRC_DIR}) +if(MINGW) + set(LUV_PATCH_COMMAND + ${LUV_PATCH_COMMAND} + COMMAND ${GIT_EXECUTABLE} -C ${LUV_SRC_DIR} init + COMMAND ${GIT_EXECUTABLE} -C ${LUV_SRC_DIR} apply --ignore-whitespace + ${CMAKE_CURRENT_SOURCE_DIR}/patches/luv-Add-missing-definitions-for-MinGW.patch) +endif() set(LUV_CONFIGURE_COMMAND_COMMON ${CMAKE_COMMAND} ${LUV_SRC_DIR} @@ -78,7 +86,12 @@ else() "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1} ${LUV_INCLUDE_FLAGS} -fPIC") endif() -set(LUV_BUILD_COMMAND ${CMAKE_COMMAND} --build .) +if(CMAKE_GENERATOR MATCHES "Unix Makefiles" AND + (CMAKE_SYSTEM_NAME MATCHES ".*BSD" OR CMAKE_SYSTEM_NAME MATCHES "DragonFly")) + set(LUV_BUILD_COMMAND ${CMAKE_COMMAND} "-DCMAKE_MAKE_PROGRAM=gmake" --build .) +else() + set(LUV_BUILD_COMMAND ${CMAKE_COMMAND} --build .) +endif() set(LUV_INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install) BuildLuv(PATCH_COMMAND ${LUV_PATCH_COMMAND} diff --git a/third-party/cmake/GettextCMakeLists.txt b/third-party/cmake/GettextCMakeLists.txt new file mode 100644 index 0000000000..60ee3b6f99 --- /dev/null +++ b/third-party/cmake/GettextCMakeLists.txt @@ -0,0 +1,83 @@ +cmake_minimum_required(VERSION 2.8.7) +project(libintl C) + +include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) + +file(READ config.h.in CONFIG_CONTENT) +string(REPLACE "#undef HAVE_GETCWD" "#define HAVE_GETCWD 1" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef uintmax_t" " + #if _WIN64 + # define intmax_t long long + # define uintmax_t unsigned long long + #elif _WIN32 + # define intmax_t long + # define uintmax_t unsigned long + #endif" + CONFIG_CONTENT ${CONFIG_CONTENT}) +file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/config.h ${CONFIG_CONTENT}) + +set(HAVE_NEWLOCALE 0) +set(HAVE_POSIX_PRINTF 0) +set(HAVE_SNPRINTF 0) +set(HAVE_ASPRINTF 0) +set(HAVE_WPRINTF 0) +configure_file(intl/libgnuintl.in.h libgnuintl.h) + +set(LOCALDIR "gettext") +add_definitions(-DLOCALEDIR=\"${LOCALDIR}\" + -DLOCALE_ALIAS_PATH=\"${LOCALDIR}\" + -DLIBDIR=\"${LOCALDIR}\" + -DINSTALLDIR=\"${LOCALDIR}\") + +add_definitions(-DBUILDING_LIBINTL + -DIN_LIBINTL + -DENABLE_RELOCATABLE=1 + -DIN_LIBRARY + -DNO_XMALLOC + -Dset_relocation_prefix=libintl_set_relocation_prefix + -Drelocate=libintl_relocate + -DHAVE_CONFIG_H + -D_CRT_SECURE_NO_WARNINGS) + +FILE(GLOB SOURCES + intl/bindtextdom.c + intl/dcgettext.c + intl/dcigettext.c + intl/dcngettext.c + intl/dgettext.c + intl/dngettext.c + intl/explodename.c + intl/finddomain.c + intl/gettext.c + intl/hash-string.c + intl/l10nflist.c + intl/langprefs.c + intl/loadmsgcat.c + intl/localcharset.c + intl/localealias.c + intl/localename.c + intl/lock.c + intl/log.c + intl/ngettext.c + intl/plural-exp.c + intl/plural.c + intl/printf.c + intl/relocatable.c + intl/setlocale.c + intl/textdomain.c + intl/threadlib.c + intl/version.c) + +add_library(libintl ${SOURCES}) + +include(GNUInstallDirs) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/libgnuintl.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + RENAME libintl.h) + +install(TARGETS libintl + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/third-party/cmake/GperfCMakeLists.txt b/third-party/cmake/GperfCMakeLists.txt new file mode 100644 index 0000000000..32837fc166 --- /dev/null +++ b/third-party/cmake/GperfCMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 2.8.7) +project(gperf LANGUAGES C CXX) + +add_executable(gperf + lib/getline.cc + lib/hash.cc + lib/getopt.c + lib/getopt1.c + src/version.cc + src/positions.cc + src/options.cc + src/keyword.cc + src/keyword-list.cc + src/input.cc + src/bool-array.cc + src/hash-table.cc + src/search.cc + src/output.cc + src/main.cc) + +include_directories(lib) + +# Copy the config.h template without modifying it +# because none of the definitions are necessary +execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/src/config.h.in ${CMAKE_BINARY_DIR}/config/config.h) +include_directories(${CMAKE_BINARY_DIR}/config) + +include(GNUInstallDirs) +install(TARGETS gperf + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/third-party/cmake/LibuvCMakeLists.txt b/third-party/cmake/LibuvCMakeLists.txt new file mode 100644 index 0000000000..063e4291f2 --- /dev/null +++ b/third-party/cmake/LibuvCMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 2.8.7) +project(libuv LANGUAGES C) + +file(GLOB UV_SOURCES_COMMON src/*.c) +file(GLOB UV_SOURCES_WIN src/win/*.c) + +add_library(libuv ${UV_SOURCES_COMMON} ${UV_SOURCES_WIN}) +target_compile_definitions(libuv PRIVATE WIN32_LEAN_AND_MEAN "_WIN32_WINNT=0x0600") +target_link_libraries(libuv iphlpapi psapi shell32 userenv ws2_32) +target_include_directories(libuv PUBLIC ./include PRIVATE ./src) +set_target_properties(libuv PROPERTIES DEFINE_SYMBOL BUILDING_UV_SHARED) + +install(FILES + include/tree.h + include/uv.h + include/uv-version.h + include/uv-errno.h + include/uv-threadpool.h + include/uv-win.h + DESTINATION include) + +include(GNUInstallDirs) +install(TARGETS libuv + PUBLIC_HEADER + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/third-party/cmake/Libvterm-tbl2inc_c.cmake b/third-party/cmake/Libvterm-tbl2inc_c.cmake new file mode 100644 index 0000000000..b1ee0246f1 --- /dev/null +++ b/third-party/cmake/Libvterm-tbl2inc_c.cmake @@ -0,0 +1,163 @@ +cmake_minimum_required(VERSION 2.8.7) + +set(HEX_ALPHABET "0123456789abcdef") + +function(ConvertToHex dec hex) + while(dec GREATER 0) + math(EXPR _val "${dec} % 16") + math(EXPR dec "${dec} / 16") + string(SUBSTRING ${HEX_ALPHABET} ${_val} 1 _val) + set(_res "${_val}${_res}") + endwhile() + # Pad the result with the number of zeros + # specified by the optional third argument + if(${ARGC} EQUAL 3) + set(padding ${ARGV2}) + string(LENGTH ${_res} _resLen) + if(_resLen LESS ${padding}) + math(EXPR _neededPadding "${padding} - ${_resLen}") + foreach(i RANGE 1 ${_neededPadding}) + set(_res "0${_res}") + endforeach() + endif() + endif() + set(${hex} "0x${_res}" PARENT_SCOPE) +endfunction() + +function(ConvertFromHex hex dec) + string(TOLOWER ${hex} hex) + string(LENGTH "${hex}" _strlen) + set(_res 0) + while(_strlen GREATER 0) + math(EXPR _res "${_res} * 16") + string(SUBSTRING "${hex}" 0 1 NIBBLE) + string(SUBSTRING "${hex}" 1 -1 hex) + string(FIND ${HEX_ALPHABET} ${NIBBLE} value) + if(value EQUAL -1) + message(FATAL_ERROR "Invalid hex character '${NIBBLE}'") + endif() + math(EXPR _res "${_res} + ${value}") + string(LENGTH "${hex}" _strlen) + endwhile() + set(${dec} ${_res} PARENT_SCOPE) +endfunction() + +# Based on http://www.json.org/JSON_checker/utf8_decode.c +function(DecodeUtf8 hexBytes codePoint) + string(SUBSTRING ${hexBytes} 0 2 hexByte1) + ConvertFromHex(${hexByte1} byte1) + # Zero continuations (0 to 127) + math(EXPR out "${byte1} & 128") + if(out EQUAL 0) + set(${codePoint} ${byte1} PARENT_SCOPE) + return() + endif() + # One continuation (128 to 2047) + math(EXPR out "${byte1} & 224") + if(out EQUAL 192) + string(SUBSTRING ${hexBytes} 2 2 hexByte2) + ConvertFromHex(${hexByte2} byte2) + math(EXPR result "((${byte1} & 31) << 6) | ${byte2}") + if(result GREATER 127) + set(${codePoint} ${result} PARENT_SCOPE) + return() + endif() + else() + # Two continuations (2048 to 55295 and 57344 to 65535) + math(EXPR result "${byte1} & 240") + if(result EQUAL 224) + string(SUBSTRING ${hexBytes} 2 2 hexByte2) + string(SUBSTRING ${hexBytes} 4 2 hexByte3) + ConvertFromHex(${hexByte2} byte2) + ConvertFromHex(${hexByte3} byte3) + math(EXPR result "${byte2} | ${byte3}") + if(result GREATER -1) + math(EXPR result "((${byte1} & 15) << 12) | (${byte2} << 6) | ${byte3}") + if((result GREATER 2047) AND (result LESS 55296 OR result GREATER 57343)) + set(${codePoint} ${result} PARENT_SCOPE) + return() + endif() + endif() + else() + # Three continuations (65536 to 1114111) + math(EXPR result "${byte1} & 248") + if(result EQUAL 224) + string(SUBSTRING ${hexBytes} 2 2 hexByte2) + string(SUBSTRING ${hexBytes} 4 2 hexByte3) + string(SUBSTRING ${hexBytes} 6 2 hexByte4) + ConvertFromHex(${hexByte2} byte2) + ConvertFromHex(${hexByte3} byte3) + ConvertFromHex(${hexByte4} byte4) + math(EXPR result "${byte2} | ${byte3} | ${byte4}") + if(result GREATER -1) + math(EXPR result "((c & 7) << 18) | (c1 << 12) | (c2 << 6) | c3") + if((result GREATER 65535) AND (result LESS 1114112)) + set(${codePoint} ${result} PARENT_SCOPE) + return() + endif() + endif() + endif() + endif() + endif() + message(FATAL_ERROR "Invalid UTF-8 encoding") +endfunction() + +set(inputFile ${CMAKE_ARGV3}) +set(outputFile ${CMAKE_ARGV4}) +# Get the file contents in text and hex-encoded format because +# CMake doesn't provide functions for converting between the two +file(READ "${inputFile}" contents) +file(READ "${inputFile}" hexContents HEX) + +# Convert the text contents into a list of lines by escaping +# the list separator ';' and then replacing new line characters +# with the list separator +string(REGEX REPLACE ";" "\\\\;" contents ${contents}) +string(REGEX REPLACE "\n" ";" contents ${contents}) + +get_filename_component(encname ${inputFile} NAME_WE) +set(output + "static const struct StaticTableEncoding encoding_${encname} = {\n" + " { .decode = &decode_table },\n" + " {") +set(hexIndex 0) +foreach(line ${contents}) + string(LENGTH ${line} lineLength) + # Convert "A" to 0x41 + string(FIND ${line} "\"" beginQuote) + if(NOT ${beginQuote} EQUAL -1) + string(FIND ${line} "\"" endQuote REVERSE) + if(${beginQuote} EQUAL ${endQuote}) + message(FATAL_ERROR "Line contains only one quote") + endif() + math(EXPR beginHexQuote "${hexIndex} + (${beginQuote} + 1)*2") + math(EXPR endHexQuote "${hexIndex} + (${endQuote} + 1)*2") + math(EXPR quoteLen "${endHexQuote} - ${beginHexQuote} - 1") + string(SUBSTRING ${hexContents} ${beginHexQuote} ${quoteLen} hexQuote) + DecodeUtf8(${hexQuote} codePoint) + ConvertToHex(${codePoint} hexCodePoint 4) + STRING(REGEX REPLACE "\"(.+)\"" ${hexCodePoint} line ${line}) + endif() + # Strip comment + string(REGEX REPLACE "[ \t\n]*#.*" "" line ${line}) + # Convert 3/1 to [0x31] + string(REGEX REPLACE "^([0-9]+)/([0-9]+).*" "\\1;\\2" numbers ${line}) + list(GET numbers 0 upperBits) + list(GET numbers 1 lowerBits) + math(EXPR res "${upperBits}*16 + ${lowerBits}") + ConvertToHex(${res} hex 2) + string(REGEX REPLACE "^([0-9]+)/([0-9]+)" "[${hex}]" line ${line}) + # Convert U+0041 to 0x0041 + string(REPLACE "U+" "0x" line ${line}) + # Indent and append a comma + set(line " ${line},") + set(output "${output}\n${line}") + # Increment the index by the number of characters in the line, + # plus one for the new line character then multiple by two for the hex digit index + math(EXPR hexIndex "${hexIndex} + 2*(${lineLength} + 1)") +endforeach() +set(output "${output}\n" + " }\n" + "}\;\n") + +file(WRITE ${outputFile} ${output}) diff --git a/third-party/cmake/LibvtermCMakeLists.txt b/third-party/cmake/LibvtermCMakeLists.txt index 72183e4b4c..27d0d11e9f 100644 --- a/third-party/cmake/LibvtermCMakeLists.txt +++ b/third-party/cmake/LibvtermCMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.8.11) project(libvterm LANGUAGES C) include(GNUInstallDirs) -find_package(Perl REQUIRED) +find_package(Perl) if(MSVC) add_definitions(/W3 -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE) @@ -18,11 +18,19 @@ foreach(file ${TBL_FILES}) set(tname encoding/${basename}.inc) add_custom_command(OUTPUT COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/encoding/ - COMMAND ${PERL_EXECUTABLE} -CSD ${CMAKE_SOURCE_DIR}/tbl2inc_c.pl ${file} > ${CMAKE_BINARY_DIR}/${tname} + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/tbl2inc_c.cmake ${file} ${CMAKE_BINARY_DIR}/${tname} COMMENT "Generating ${tname}" OUTPUT ${CMAKE_BINARY_DIR}/${tname} ) list(APPEND TBL_FILES_HEADERS ${tname}) + # Only used for verifying that the output of tbl2inc_c.cmake is correct + set(tname encoding-test/${basename}.inc) + add_custom_command(OUTPUT + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/encoding-test/ + COMMAND ${PERL_EXECUTABLE} -CSD ${CMAKE_SOURCE_DIR}/tbl2inc_c.pl ${file} > ${CMAKE_BINARY_DIR}/${tname} + COMMENT "Generating ${tname}" + OUTPUT ${CMAKE_BINARY_DIR}/${tname} + ) endforeach() include_directories(${CMAKE_SOURCE_DIR}/include) @@ -60,13 +68,24 @@ set_target_properties(harness PROPERTIES # run-test.pl expects to find the harness in t/.libs/ RUNTIME_OUTPUT_DIRECTORY t/.libs) -file(GLOB TESTFILES ${CMAKE_SOURCE_DIR}/t/*.test) -add_custom_target(check) -foreach(testfile ${TESTFILES}) - get_filename_component(target_name ${testfile} NAME_WE) - add_custom_target(${target_name} - COMMAND ${PERL_EXECUTABLE} ${CMAKE_SOURCE_DIR}/t/run-test.pl ${testfile} - COMMENT "**${target_name} **" - DEPENDS harness) - add_dependencies(check ${target_name}) -endforeach() +if(Perl_FOUND) + file(GLOB TESTFILES ${CMAKE_SOURCE_DIR}/t/*.test) + add_custom_target(check) + foreach(testfile ${TESTFILES}) + get_filename_component(target_name ${testfile} NAME_WE) + add_custom_target(${target_name} + COMMAND ${PERL_EXECUTABLE} ${CMAKE_SOURCE_DIR}/t/run-test.pl ${testfile} + COMMENT "**${target_name} **" + DEPENDS harness) + add_dependencies(check ${target_name}) + endforeach() + + foreach(header_path ${TBL_FILES_HEADERS}) + get_filename_component(header_name ${header_path} NAME) + set(perl_header_path ${CMAKE_BINARY_DIR}/encoding-test/${header_name}) + add_custom_target(test-${header_name} + COMMAND ${CMAKE_COMMAND} -E compare_files + ${header_path} ${perl_header_path} + DEPENDS ${header_path} ${perl_header_path}) + endforeach() +endif() diff --git a/third-party/cmake/PatchLuv.cmake b/third-party/cmake/PatchLuv.cmake deleted file mode 100644 index 96595a2f30..0000000000 --- a/third-party/cmake/PatchLuv.cmake +++ /dev/null @@ -1,29 +0,0 @@ -# replace luv default rockspec with the alternate one under the "rockspecs" -# directory -file(GLOB LUV_ROCKSPEC RELATIVE ${LUV_SRC_DIR} ${LUV_SRC_DIR}/*.rockspec) -file(RENAME ${LUV_SRC_DIR}/rockspecs/${LUV_ROCKSPEC} ${LUV_SRC_DIR}/${LUV_ROCKSPEC}) - -# Some versions of mingw are missing defines required by luv dns module, add -# them now -set(LUV_SRC_DNS_C_DEFS -"#ifndef AI_NUMERICSERV -# define AI_NUMERICSERV 0x0008 -#endif -#ifndef AI_ALL -# define AI_ALL 0x00000100 -#endif -#ifndef AI_ADDRCONFIG -# define AI_ADDRCONFIG 0x00000400 -#endif -#ifndef AI_V4MAPPED -# define AI_V4MAPPED 0x00000800 -#endif") - -file(READ ${LUV_SRC_DIR}/src/dns.c LUV_SRC_DNS_C) -string(REPLACE - "\n#include <netdb.h>" - "\n#include <netdb.h>\n#else\n${LUV_SRC_DNS_C_DEFS}" - LUV_SRC_DNS_C_PATCHED - "${LUV_SRC_DNS_C}") -file(WRITE ${LUV_SRC_DIR}/src/dns.c "${LUV_SRC_DNS_C_PATCHED}") - diff --git a/third-party/cmake/libtermkeyCMakeLists.txt b/third-party/cmake/libtermkeyCMakeLists.txt new file mode 100644 index 0000000000..cb57631c1c --- /dev/null +++ b/third-party/cmake/libtermkeyCMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 2.8.7) +project(libtermkey) + +add_definitions(-D _CRT_SECURE_NO_WARNINGS) +add_definitions(-DHAVE_UNIBILIUM) +if(NOT MSVC) + add_definitions(-std=c99) +endif() + +include_directories(${PROJECT_BINARY_DIR}/t) +include_directories(SYSTEM ${UNIBILIUM_INCLUDE_DIRS}) + +add_library(termkey termkey.c driver-csi.c driver-ti.c) +set_target_properties(termkey PROPERTIES + PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/termkey.h) +target_link_libraries(termkey ${UNIBILIUM_LIBRARIES}) + +include(GNUInstallDirs) +install(TARGETS termkey + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +enable_testing() +file(GLOB TESTSOURCES "t/[0-9]*.c") +foreach(f ${TESTSOURCES}) + get_filename_component(t ${f} NAME_WE) + if(${t} STREQUAL 05read) + continue() + endif() + + add_executable("test_${t}" ${f} t/taplib.c) + target_link_libraries("test_${t}" termkey) + add_test("${t}" "test_${t}") +endforeach() diff --git a/third-party/patches/gettext-Fix-compilation-on-a-system-without-alloca.patch b/third-party/patches/gettext-Fix-compilation-on-a-system-without-alloca.patch new file mode 100644 index 0000000000..5c472c470f --- /dev/null +++ b/third-party/patches/gettext-Fix-compilation-on-a-system-without-alloca.patch @@ -0,0 +1,28 @@ +From 1d12aeb7334104f77070361492ff7cc8225503f5 Mon Sep 17 00:00:00 2001 +From: Daiki Ueno <ueno@gnu.org> +Date: Mon, 14 Nov 2016 13:27:58 +0100 +Subject: [PATCH] intl: Fix compilation on a system without alloca + +* gettext-runtime/intl/dcigettext.c (DCIGETTEXT): Fix typo 'tmp_dirname' +-> 'resolved_dirname'. Reported by Egor Pugin in: +http://lists.gnu.org/archive/html/bug-gettext/2016-09/msg00008.html +--- + gettext-runtime/intl/dcigettext.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/gettext-runtime/intl/dcigettext.c b/gettext-runtime/intl/dcigettext.c +index 83bd77574..92f6fd685 100644 +--- a/gettext-runtime/intl/dcigettext.c ++++ b/gettext-runtime/intl/dcigettext.c +@@ -634,7 +634,7 @@ DCIGETTEXT (const char *domainname, const char *msgid1, const char *msgid2, + for (;;) + { + resolved_dirname = (char *) alloca (path_max + dirname_len); +- ADD_BLOCK (block_list, tmp_dirname); ++ ADD_BLOCK (block_list, resolved_dirname); + + __set_errno (0); + ret = getcwd (resolved_dirname, path_max); +-- +2.16.1.windows.4 + diff --git a/third-party/patches/libtermkey-Add-support-for-Windows.patch b/third-party/patches/libtermkey-Add-support-for-Windows.patch new file mode 100644 index 0000000000..b839e94d05 --- /dev/null +++ b/third-party/patches/libtermkey-Add-support-for-Windows.patch @@ -0,0 +1,170 @@ +From fbe91a958816d85fa93665eb8f7a7a8e05eb9650 Mon Sep 17 00:00:00 2001 +From: Rui Abreu Ferreira <raf-ep@gmx.com> +Date: Tue, 5 Apr 2016 00:12:41 +0100 +Subject: [PATCH] Add support for Windows + +Ported termkey for windows. + +- The TERMKEY_FLAG_NOTERMIOS is ignore in Windows, since there is no termios. +- The termkey_waitkey() function is not implemented in windows, since there + is no poll() alternative. +- The CMake recipe only supports unibilium, not curses. +--- + driver-ti.c | 8 +++++++- + termkey-internal.h | 11 ++++++++++- + termkey.c | 22 ++++++++++++++++++---- + 3 files changed, 35 insertions(+), 6 deletions(-) + +diff --git a/driver-ti.c b/driver-ti.c +index e673ab7..f5f8052 100644 +--- a/driver-ti.c ++++ b/driver-ti.c +@@ -17,7 +17,9 @@ + #include <ctype.h> + #include <stdio.h> + #include <string.h> +-#include <unistd.h> ++#ifndef _WIN32 ++# include <unistd.h> ++#endif + #include <sys/types.h> + #include <sys/stat.h> + +@@ -338,8 +340,10 @@ static int start_driver(TermKey *tk, void *info) + if(fstat(tk->fd, &statbuf) == -1) + return 0; + ++#ifndef _WIN32 + if(S_ISFIFO(statbuf.st_mode)) + return 1; ++#endif + + // Can't call putp or tputs because they suck and don't give us fd control + len = strlen(start_string); +@@ -367,8 +371,10 @@ static int stop_driver(TermKey *tk, void *info) + if(fstat(tk->fd, &statbuf) == -1) + return 0; + ++#ifndef _WIN32 + if(S_ISFIFO(statbuf.st_mode)) + return 1; ++#endif + + /* The terminfo database will contain keys in application cursor key mode. + * We may need to enable that mode +diff --git a/termkey-internal.h b/termkey-internal.h +index 52829b3..b796729 100644 +--- a/termkey-internal.h ++++ b/termkey-internal.h +@@ -4,7 +4,14 @@ + #include "termkey.h" + + #include <stdint.h> +-#include <termios.h> ++#ifndef _WIN32 ++# include <termios.h> ++#endif ++ ++#ifdef _MSC_VER ++#include <BaseTsd.h> ++typedef SSIZE_T ssize_t; ++#endif + + struct TermKeyDriver + { +@@ -41,8 +48,10 @@ struct TermKey { + size_t hightide; /* Position beyond buffstart at which peekkey() should next start + * normally 0, but see also termkey_interpret_csi */ + ++#ifndef _WIN32 + struct termios restore_termios; + char restore_termios_valid; ++#endif + + TermKey_Terminfo_Getstr_Hook *ti_getstr_hook; + void *ti_getstr_hook_data; +diff --git a/termkey.c b/termkey.c +index 2f01f3a..145b99f 100644 +--- a/termkey.c ++++ b/termkey.c +@@ -3,14 +3,20 @@ + + #include <ctype.h> + #include <errno.h> +-#include <poll.h> +-#include <unistd.h> ++#ifndef _WIN32 ++# include <poll.h> ++# include <unistd.h> ++# include <strings.h> ++#endif + #include <string.h> +-#include <strings.h> + + #include <stdio.h> + +-#define strcaseeq(a,b) (strcasecmp(a,b) == 0) ++#ifdef _MSC_VER ++# define strcaseeq(a,b) (_stricmp(a,b) == 0) ++#else ++# define strcaseeq(a,b) (strcasecmp(a,b) == 0) ++#endif + + void termkey_check_version(int major, int minor) + { +@@ -282,7 +288,9 @@ static TermKey *termkey_alloc(void) + tk->buffsize = 256; /* bytes */ + tk->hightide = 0; + ++#ifndef _WIN32 + tk->restore_termios_valid = 0; ++#endif + + tk->ti_getstr_hook = NULL; + tk->ti_getstr_hook_data = NULL; +@@ -483,6 +491,7 @@ int termkey_start(TermKey *tk) + if(tk->is_started) + return 1; + ++#ifndef _WIN32 + if(tk->fd != -1 && !(tk->flags & TERMKEY_FLAG_NOTERMIOS)) { + struct termios termios; + if(tcgetattr(tk->fd, &termios) == 0) { +@@ -517,6 +526,7 @@ int termkey_start(TermKey *tk) + tcsetattr(tk->fd, TCSANOW, &termios); + } + } ++#endif + + struct TermKeyDriverNode *p; + for(p = tk->drivers; p; p = p->next) +@@ -542,8 +552,10 @@ int termkey_stop(TermKey *tk) + if(p->driver->stop_driver) + (*p->driver->stop_driver)(tk, p->info); + ++#ifndef _WIN32 + if(tk->restore_termios_valid) + tcsetattr(tk->fd, TCSANOW, &tk->restore_termios); ++#endif + + tk->is_started = 0; + +@@ -1046,6 +1058,7 @@ TermKeyResult termkey_getkey_force(TermKey *tk, TermKeyKey *key) + return ret; + } + ++#ifndef _WIN32 + TermKeyResult termkey_waitkey(TermKey *tk, TermKeyKey *key) + { + if(tk->fd == -1) { +@@ -1105,6 +1118,7 @@ retry: + + /* UNREACHABLE */ + } ++#endif + + TermKeyResult termkey_advisereadable(TermKey *tk) + { +-- +2.16.1.windows.4 + diff --git a/third-party/patches/libuv-overlapped.patch b/third-party/patches/libuv-overlapped.patch new file mode 100644 index 0000000000..f4ea19a146 --- /dev/null +++ b/third-party/patches/libuv-overlapped.patch @@ -0,0 +1,33 @@ +diff --git a/include/uv.h b/include/uv.h +index cdd251d8..79b7930e 100644 +--- a/include/uv.h ++++ b/include/uv.h +@@ -865,7 +865,8 @@ typedef enum { + * flags may be specified to create a duplex data stream. + */ + UV_READABLE_PIPE = 0x10, +- UV_WRITABLE_PIPE = 0x20 ++ UV_WRITABLE_PIPE = 0x20, ++ UV_OVERLAPPED_PIPE = 0x40 + } uv_stdio_flags; + + typedef struct uv_stdio_container_s { +diff --git a/src/win/process-stdio.c b/src/win/process-stdio.c +index 032e3093..b53bdea7 100644 +--- a/src/win/process-stdio.c ++++ b/src/win/process-stdio.c +@@ -131,12 +131,13 @@ static int uv__create_stdio_pipe_pair(uv_loop_t* loop, + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + ++ BOOL overlap = server_pipe->ipc || (flags & UV_OVERLAPPED_PIPE); + child_pipe = CreateFileA(pipe_name, + client_access, + 0, + &sa, + OPEN_EXISTING, +- server_pipe->ipc ? FILE_FLAG_OVERLAPPED : 0, ++ overlap ? FILE_FLAG_OVERLAPPED : 0, + NULL); + if (child_pipe == INVALID_HANDLE_VALUE) { + err = GetLastError(); diff --git a/third-party/patches/libvterm-Remove-VLAs-for-MSVC.patch b/third-party/patches/libvterm-Remove-VLAs-for-MSVC.patch index 3fb18351be..e999c0fa9b 100644 --- a/third-party/patches/libvterm-Remove-VLAs-for-MSVC.patch +++ b/third-party/patches/libvterm-Remove-VLAs-for-MSVC.patch @@ -23,7 +23,7 @@ index 84299df..f9aabb3 100644 // We'll have at most len codepoints - uint32_t codepoints[len]; -+ uint32_t* codepoints = _alloca(len); ++ uint32_t* codepoints = _alloca(len * sizeof(uint32_t)); int npoints = 0; size_t eaten = 0; @@ -32,7 +32,7 @@ index 84299df..f9aabb3 100644 int width = 0; - uint32_t chars[glyph_ends - glyph_starts + 1]; -+ uint32_t* chars = _alloca(glyph_ends - glyph_starts + 1); ++ uint32_t* chars = _alloca((glyph_ends - glyph_starts + 1) * sizeof(uint32_t)); for( ; i < glyph_ends; i++) { chars[i - glyph_starts] = codepoints[i]; diff --git a/third-party/patches/luv-Add-missing-definitions-for-MinGW.patch b/third-party/patches/luv-Add-missing-definitions-for-MinGW.patch new file mode 100644 index 0000000000..8954ae21c3 --- /dev/null +++ b/third-party/patches/luv-Add-missing-definitions-for-MinGW.patch @@ -0,0 +1,24 @@ +diff --git a/src/dns.c b/src/dns.c +index 8634157..5f36625 100644 +--- a/src/dns.c ++++ b/src/dns.c +@@ -20,6 +20,19 @@ + #include <sys/types.h> + #include <sys/socket.h> + #include <netdb.h> ++#elif __MINGW32__ ++# ifndef AI_NUMERICSERV ++# define AI_NUMERICSERV 0x0008 ++# endif ++# ifndef AI_ALL ++# define AI_ALL 0x00000100 ++# endif ++# ifndef AI_ADDRCONFIG ++# define AI_ADDRCONFIG 0x00000400 ++# endif ++# ifndef AI_V4MAPPED ++# define AI_V4MAPPED 0x00000800 ++# endif + #endif + + static void luv_pushaddrinfo(lua_State* L, struct addrinfo* res) { |