diff options
246 files changed, 6724 insertions, 2188 deletions
diff --git a/.editorconfig b/.editorconfig index b08a27f2a2..ac902ecec5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,6 +7,10 @@ end_of_line = lf insert_final_newline = true charset = utf_8 +[runtime/doc/*.txt] +indent_style = tab +indent_size = 8 + [Makefile] indent_style = tab tab_width = 4 diff --git a/.gitignore b/.gitignore index 3a8994a5f6..61b96c158f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Build/deps dir /build/ +/cmake-build-debug/ /dist/ /.deps/ /tmp/ diff --git a/BSDmakefile b/BSDmakefile new file mode 100644 index 0000000000..93b7dc7f3d --- /dev/null +++ b/BSDmakefile @@ -0,0 +1,4 @@ +.DONE: + @echo "Please use GNU Make (gmake) to build neovim" +.DEFAULT: + @echo "Please use GNU Make (gmake) to build neovim" diff --git a/CMakeLists.txt b/CMakeLists.txt index 594f631ba0..e0daea5969 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,15 +113,14 @@ else() set(HAS_OG_FLAG 0) endif() -# Set custom build flags for RelWithDebInfo. -# -DNDEBUG purposely omitted because we want assertions. +# +# Build-type: RelWithDebInfo +# if(HAS_OG_FLAG) - set(CMAKE_C_FLAGS_RELWITHDEBINFO "-Og -g" - CACHE STRING "Flags used by the compiler during release-with-debug builds." FORCE) -elseif(NOT MSVC) - set(CMAKE_C_FLAGS_RELWITHDEBINFO "-O2 -g" - CACHE STRING "Flags used by the compiler during release-with-debug builds." FORCE) -elseif(CMAKE_C_FLAGS_RELWITHDEBINFO MATCHES DNDEBUG) + set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -Og -g") +endif() +# We _want_ assertions in RelWithDebInfo build-type. +if(CMAKE_C_FLAGS_RELWITHDEBINFO MATCHES DNDEBUG) string(REPLACE "-DNDEBUG" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") endif() @@ -254,7 +253,10 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.8.5") # Array-bounds testing is broken in some GCC versions before 4.8.5. # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56273 - add_definitions(-Wno-array-bounds) + check_c_compiler_flag(-Wno-array-bounds HAS_NO_ARRAY_BOUNDS_FLAG) + if(HAS_NO_ARRAY_BOUNDS_FLAG) + add_definitions(-Wno-array-bounds) + endif() endif() endif() @@ -344,6 +346,21 @@ if(FEAT_TUI) find_package(Unibilium REQUIRED) include_directories(SYSTEM ${UNIBILIUM_INCLUDE_DIRS}) + list(APPEND CMAKE_REQUIRED_INCLUDES "${UNIBILIUM_INCLUDE_DIRS}") + list(APPEND CMAKE_REQUIRED_LIBRARIES "${UNIBILIUM_LIBRARIES}") + check_c_source_compiles(" + #include <unibilium.h> + + int + main(void) + { + return unibi_num_from_var(unibi_var_from_num(0)); + } + " UNIBI_HAS_VAR_FROM) + if(UNIBI_HAS_VAR_FROM) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNVIM_UNIBI_HAS_VAR_FROM") + endif() + find_package(LibTermkey REQUIRED) include_directories(SYSTEM ${LIBTERMKEY_INCLUDE_DIRS}) endif() @@ -351,6 +368,11 @@ endif() find_package(LibVterm REQUIRED) include_directories(SYSTEM ${LIBVTERM_INCLUDE_DIRS}) +if(WIN32) + find_package(Winpty REQUIRED) + include_directories(SYSTEM ${WINPTY_INCLUDE_DIRS}) +endif() + option(CLANG_ASAN_UBSAN "Enable Clang address & undefined behavior sanitizer for nvim binary." OFF) option(CLANG_MSAN "Enable Clang memory sanitizer for nvim binary." OFF) option(CLANG_TSAN "Enable Clang thread sanitizer for nvim binary." OFF) @@ -456,20 +478,19 @@ install_helper( DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) # MIN_LOG_LEVEL for log.h -if(DEFINED MIN_LOG_LEVEL) +if("${MIN_LOG_LEVEL}" MATCHES "^$") + message(STATUS "MIN_LOG_LEVEL not specified") +else() if(NOT MIN_LOG_LEVEL MATCHES "^[0-3]$") message(FATAL_ERROR "invalid MIN_LOG_LEVEL: " ${MIN_LOG_LEVEL}) endif() message(STATUS "MIN_LOG_LEVEL set to ${MIN_LOG_LEVEL}") -else() - message(STATUS "MIN_LOG_LEVEL not specified, defaulting to 1 (INFO)") endif() # Go down the tree. add_subdirectory(src/nvim) -# Read compilation flags from src/nvim, -# used in config subdirectory below. +# Read compilation flags from src/nvim, used in config subdirectory below. include(GetCompileFlags) get_compile_flags(NVIM_VERSION_CFLAGS) @@ -48,17 +48,24 @@ and [more](https://github.com/neovim/neovim/wiki/Installing-Neovim)! Project layout -------------- - ├─ ci/ Build server scripts - ├─ cmake/ Build scripts - ├─ runtime/ User plugins/docs - ├─ src/ Source code - ├─ third-party/ CMake subproject to build dependencies - └─ test/ Test code - -- `third-party/` is activated if `USE_BUNDLED_DEPS` is undefined or the - `USE_BUNDLED` CMake option is true. -- [Source README](src/nvim/README.md) -- [Test README](test/README.md) + ├─ ci/ build automation + ├─ cmake/ build scripts + ├─ runtime/ user plugins/docs + ├─ src/ application source code (see src/nvim/README.md) + │ ├─ api/ API subsystem + │ ├─ eval/ VimL subsystem + │ ├─ event/ event-loop subsystem + │ ├─ generators/ code generation (pre-compilation) + │ ├─ lib/ generic data structures + │ ├─ lua/ lua subsystem + │ ├─ msgpack_rpc/ RPC subsystem + │ ├─ os/ low-level platform code + │ └─ tui/ built-in UI + ├─ third-party/ cmake subproject to build dependencies + └─ test/ tests (see test/README.md) + +- To disable `third-party/` specify `USE_BUNDLED_DEPS=NO` or `USE_BUNDLED=NO` + (CMake option). Features -------- diff --git a/appveyor.yml b/appveyor.yml index f016a12e1d..2d6135c7a2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,8 +1,13 @@ version: '{build}' +environment: + APPVEYOR_CACHE_ENTRY_ZIP_ARGS: "-t7z -m0=lzma -mx=9" configuration: - MINGW_64 - MINGW_32 - MINGW_64-gcov +matrix: + allow_failures: + - configuration: MINGW_64-gcov install: [] build_script: - call ci\build.bat diff --git a/ci/before_install.sh b/ci/before_install.sh index 5b36adaef2..f84ad935bc 100755 --- a/ci/before_install.sh +++ b/ci/before_install.sh @@ -11,6 +11,18 @@ if [[ "${TRAVIS_OS_NAME}" == osx ]]; then brew update fi +echo 'python info:' +( + 2>&1 python --version || true + 2>&1 python2 --version || true + 2>&1 python3 --version || true + 2>&1 pip --version || true + 2>&1 pip2 --version || true + 2>&1 pip3 --version || true + echo 'pyenv versions:' + 2>&1 pyenv versions || true +) | sed 's/^/ /' + echo "Upgrade Python 2 pip." pip2.7 -q install --user --upgrade pip @@ -21,5 +33,7 @@ if [[ "${TRAVIS_OS_NAME}" == osx ]]; then pip3 -q install --user --upgrade pip else echo "Upgrade Python 3 pip." - pip3 -q install --user --upgrade pip + # Allow failure. pyenv pip3 on travis is broken: + # https://github.com/travis-ci/travis-ci/issues/8363 + pip3 -q install --user --upgrade pip || true fi diff --git a/ci/install.sh b/ci/install.sh index 4ee99e1e44..c8a0c8825d 100755 --- a/ci/install.sh +++ b/ci/install.sh @@ -17,7 +17,9 @@ echo "Install neovim module and coveralls for Python 2." CC=cc pip2.7 -q install --user --upgrade neovim cpp-coveralls echo "Install neovim module for Python 3." -CC=cc pip3 -q install --user --upgrade neovim +# Allow failure. pyenv pip3 on travis is broken: +# https://github.com/travis-ci/travis-ci/issues/8363 +CC=cc pip3 -q install --user --upgrade neovim || true echo "Install neovim RubyGem." gem install --no-document --version ">= 0.2.0" neovim diff --git a/cmake/FindLibIntl.cmake b/cmake/FindLibIntl.cmake index 75926200c1..ab4632cf45 100644 --- a/cmake/FindLibIntl.cmake +++ b/cmake/FindLibIntl.cmake @@ -46,6 +46,7 @@ check_c_source_compiles(" int main(int argc, char** argv) { gettext(\"foo\"); + ngettext(\"foo\", \"bar\", 1); bindtextdomain(\"foo\", \"bar\"); bind_textdomain_codeset(\"foo\", \"bar\"); textdomain(\"foo\"); diff --git a/cmake/FindWinpty.cmake b/cmake/FindWinpty.cmake new file mode 100644 index 0000000000..8feafc58a8 --- /dev/null +++ b/cmake/FindWinpty.cmake @@ -0,0 +1,10 @@ +include(LibFindMacros) + +find_path(WINPTY_INCLUDE_DIR winpty.h) +set(WINPTY_INCLUDE_DIRS ${WINPTY_INCLUDE_DIR}) + +find_library(WINPTY_LIBRARY winpty) +find_program(WINPTY_AGENT_EXE winpty-agent.exe) +set(WINPTY_LIBRARIES ${WINPTY_LIBRARY}) + +find_package_handle_standard_args(Winpty DEFAULT_MSG WINPTY_LIBRARY WINPTY_INCLUDE_DIR) diff --git a/cmake/InstallHelpers.cmake b/cmake/InstallHelpers.cmake index ca20ddf354..bebc0d0d17 100644 --- a/cmake/InstallHelpers.cmake +++ b/cmake/InstallHelpers.cmake @@ -2,8 +2,8 @@ if(CMAKE_SYSTEM_NAME MATCHES "BSD" AND NOT DEFINED CMAKE_INSTALL_MANDIR) if(DEFINED ENV{MANPREFIX}) set(CMAKE_INSTALL_MANDIR "$ENV{MANPREFIX}/man") - else() - set(CMAKE_INSTALL_MANDIR "/usr/local/man") + elseif(CMAKE_INSTALL_PREFIX MATCHES "^/usr/local$") + set(CMAKE_INSTALL_MANDIR "man") endif() endif() diff --git a/.codecov.yml b/codecov.yml index 0b3de06b97..0b3de06b97 100644 --- a/.codecov.yml +++ b/codecov.yml diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index a9efc90b87..f81d8541b5 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -71,6 +71,7 @@ foreach(DF ${DOCFILES}) endforeach() add_custom_target(helptags + COMMAND ${CMAKE_COMMAND} -E remove_directory ${GENERATED_RUNTIME_DIR}/doc COMMAND ${CMAKE_COMMAND} -E copy_directory ${PROJECT_SOURCE_DIR}/runtime/doc ${GENERATED_RUNTIME_DIR}/doc COMMAND "${PROJECT_BINARY_DIR}/bin/nvim" diff --git a/runtime/autoload/health.vim b/runtime/autoload/health.vim index f875c8b797..b076f2456b 100644 --- a/runtime/autoload/health.vim +++ b/runtime/autoload/health.vim @@ -1,15 +1,15 @@ function! s:enhance_syntax() abort syntax case match - syntax keyword healthError ERROR + syntax keyword healthError ERROR[:] \ containedin=markdownCodeBlock,mkdListItemLine highlight link healthError Error - syntax keyword healthWarning WARNING + syntax keyword healthWarning WARNING[:] \ containedin=markdownCodeBlock,mkdListItemLine highlight link healthWarning WarningMsg - syntax keyword healthSuccess SUCCESS + syntax keyword healthSuccess OK[:] \ containedin=markdownCodeBlock,mkdListItemLine highlight healthSuccess guibg=#5fff00 guifg=#080808 ctermbg=82 ctermfg=232 @@ -18,7 +18,7 @@ function! s:enhance_syntax() abort syntax match healthBar "|" contained conceal highlight link healthHelp Identifier - " We do not care about markdown syntax errors in :CheckHealth output. + " We do not care about markdown syntax errors in :checkhealth output. highlight! link markdownError Normal endfunction @@ -90,27 +90,27 @@ endfunction " Changes ':h clipboard' to ':help |clipboard|'. function! s:help_to_link(s) abort - return substitute(a:s, '\v:h%[elp] ([^|][^"\r\n]+)', ':help |\1|', 'g') + return substitute(a:s, '\v:h%[elp] ([^|][^"\r\n ]+)', ':help |\1|', 'g') endfunction " Format a message for a specific report item function! s:format_report_message(status, msg, ...) abort " {{{ let output = ' - ' . a:status . ': ' . s:indent_after_line1(a:msg, 4) - let suggestions = [] + let advice = [] " Optional parameters if a:0 > 0 - let suggestions = type(a:1) == type("") ? [a:1] : a:1 - if type(suggestions) != type([]) - echoerr "Expected String or List" + let advice = type(a:1) == type("") ? [a:1] : a:1 + if type(advice) != type([]) + throw "Expected String or List" endif endif " Report each suggestion - if len(suggestions) > 0 - let output .= "\n - SUGGESTIONS:" + if len(advice) > 0 + let output .= "\n - ADVICE:" endif - for suggestion in suggestions + for suggestion in advice let output .= "\n - " . s:indent_after_line1(suggestion, 10) endfor @@ -124,7 +124,7 @@ endfunction " }}} " Reports a successful healthcheck. function! health#report_ok(msg) abort " {{{ - echo s:format_report_message('SUCCESS', a:msg) + echo s:format_report_message('OK', a:msg) endfunction " }}} " Reports a health warning. @@ -159,7 +159,10 @@ endfunction " Translates a list of plugin names to healthcheck function names. function! s:to_fn_names(plugin_names) abort let healthchecks = [] - for p in a:plugin_names + let plugin_names = type('') ==# type(a:plugin_names) + \ ? split(a:plugin_names, '', v:false) + \ : a:plugin_names + for p in plugin_names call add(healthchecks, 'health#'.p.'#check') endfor return healthchecks diff --git a/runtime/autoload/health/nvim.vim b/runtime/autoload/health/nvim.vim index 3834cbd054..7f6e943dc9 100644 --- a/runtime/autoload/health/nvim.vim +++ b/runtime/autoload/health/nvim.vim @@ -4,6 +4,12 @@ function! s:check_config() abort let ok = v:true call health#report_start('Configuration') + " If $VIM is empty we don't care. Else make sure it is valid. + if !empty($VIM) && !filereadable($VIM.'/runtime/doc/nvim.txt') + let ok = v:false + call health#report_error("$VIM is invalid: ".$VIM) + endif + if exists('$NVIM_TUI_ENABLE_CURSOR_SHAPE') let ok = v:false call health#report_warn("$NVIM_TUI_ENABLE_CURSOR_SHAPE is ignored in Nvim 0.2+", @@ -11,6 +17,13 @@ function! s:check_config() abort \ 'https://github.com/neovim/neovim/wiki/Following-HEAD#20170402' ]) endif + if &paste + let ok = v:false + call health#report_error("'paste' is enabled. This option is only for pasting text.\nIt should not be set in your config.", + \ [ 'Remove `set paste` from your init.vim, if applicable.', + \ 'Check `:verbose set paste?` to see if a plugin or script set the option.', ]) + endif + if ok call health#report_ok('no issues found') endif diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index ec20615f69..0eaa678459 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -121,14 +121,14 @@ function! s:check_clipboard() abort call health#report_start('Clipboard (optional)') let clipboard_tool = provider#clipboard#Executable() - if empty(clipboard_tool) + if exists('g:clipboard') && empty(clipboard_tool) + call health#report_error( + \ provider#clipboard#Error(), + \ ["Use the example in :help g:clipboard as a template, or don't set g:clipboard at all."]) + elseif empty(clipboard_tool) call health#report_warn( - \ 'No clipboard tool found. Clipboard registers will not work.', + \ 'No clipboard tool found. Clipboard registers (`"+` and `"*`) will not work.', \ [':help clipboard']) - elseif exists('g:clipboard') && (type({}) != type(g:clipboard) - \ || !has_key(g:clipboard, 'copy') || !has_key(g:clipboard, 'paste')) - call health#report_error( - \ 'g:clipboard exists but is malformed. It must be a dictionary with the keys documented at :help g:clipboard') else call health#report_ok('Clipboard tool found: '. clipboard_tool) endif @@ -239,7 +239,7 @@ function! s:check_python(version) abort let pyname = 'python'.(a:version == 2 ? '' : '3') let pyenv = resolve(exepath('pyenv')) - let pyenv_root = exists('$PYENV_ROOT') ? resolve($PYENV_ROOT) : 'n' + let pyenv_root = exists('$PYENV_ROOT') ? resolve($PYENV_ROOT) : '' let venv = exists('$VIRTUAL_ENV') ? resolve($VIRTUAL_ENV) : '' let host_prog_var = pyname.'_host_prog' let loaded_var = 'g:loaded_'.pyname.'_provider' @@ -251,6 +251,19 @@ function! s:check_python(version) abort return endif + if !empty(pyenv) + if empty(pyenv_root) + call health#report_warn( + \ 'pyenv was found, but $PYENV_ROOT is not set.', + \ ['Did you follow the final install instructions?', + \ 'If you use a shell "framework" like Prezto or Oh My Zsh, try without.', + \ 'Try a different shell (bash).'] + \ ) + else + call health#report_ok(printf('pyenv found: "%s"', pyenv)) + endif + endif + if exists('g:'.host_prog_var) call health#report_info(printf('Using: g:%s = "%s"', host_prog_var, get(g:, host_prog_var))) endif @@ -282,15 +295,6 @@ function! s:check_python(version) abort endif if !empty(pyenv) - if empty(pyenv_root) - call health#report_warn( - \ 'pyenv was found, but $PYENV_ROOT is not set.', - \ ['Did you follow the final install instructions?'] - \ ) - else - call health#report_ok(printf('pyenv found: "%s"', pyenv)) - endif - let python_bin = s:trim(s:system([pyenv, 'which', pyname], '', 1)) if empty(python_bin) @@ -320,9 +324,8 @@ function! s:check_python(version) abort if python_bin =~# '\<shims\>' call health#report_warn(printf('`%s` appears to be a pyenv shim.', python_bin), [ - \ 'The `pyenv` executable is not in $PATH,', - \ 'Your pyenv installation is broken. You should set ' - \ . '`g:'.host_prog_var.'` to avoid surprises.', + \ '`pyenv` is not in $PATH, your pyenv installation is broken. ' + \ .'Set `g:'.host_prog_var.'` to avoid surprises.', \ ]) endif endif @@ -335,7 +338,7 @@ function! s:check_python(version) abort call health#report_warn('pyenv is not set up optimally.', [ \ printf('Create a virtualenv specifically ' \ . 'for Neovim using pyenv, and set `g:%s`. This will avoid ' - \ . 'the need to install Neovim''s Python module in each ' + \ . 'the need to install the Neovim Python module in each ' \ . 'version/virtualenv.', host_prog_var) \ ]) elseif !empty(venv) && exists('g:'.host_prog_var) diff --git a/runtime/autoload/provider.vim b/runtime/autoload/provider.vim index a4d5241b57..e6514f5ba8 100644 --- a/runtime/autoload/provider.vim +++ b/runtime/autoload/provider.vim @@ -2,7 +2,7 @@ let s:stderr = {} -function! provider#stderr_collector(chan_id, data, event) dict +function! provider#stderr_collector(chan_id, data, event) let stderr = get(s:stderr, a:chan_id, ['']) let stderr[-1] .= a:data[0] call extend(stderr, a:data[1:]) @@ -10,7 +10,9 @@ function! provider#stderr_collector(chan_id, data, event) dict endfunction function! provider#clear_stderr(chan_id) - silent! call delete(s:stderr, a:chan_id) + if has_key(s:stderr, a:chan_id) + call remove(s:stderr, a:chan_id) + endif endfunction function! provider#get_stderr(chan_id) diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim index 8eb694e9fa..6454a01c2a 100644 --- a/runtime/autoload/provider/clipboard.vim +++ b/runtime/autoload/provider/clipboard.vim @@ -3,6 +3,7 @@ " available. let s:copy = {} let s:paste = {} +let s:clipboard = {} " When caching is enabled, store the jobid of the xclip/xsel process keeping " ownership of the selection, so we know how long the cache is valid. @@ -23,7 +24,7 @@ function! s:selection.on_exit(jobid, data, event) abort call provider#clear_stderr(a:jobid) endfunction -let s:selections = { '*': s:selection, '+': copy(s:selection)} +let s:selections = { '*': s:selection, '+': copy(s:selection) } function! s:try_cmd(cmd, ...) abort let argv = split(a:cmd, " ") @@ -31,7 +32,7 @@ function! s:try_cmd(cmd, ...) abort if v:shell_error if !exists('s:did_error_try_cmd') echohl WarningMsg - echomsg "clipboard: error: ".(len(out) ? out[0] : '') + echomsg "clipboard: error: ".(len(out) ? out[0] : v:shell_error) echohl None let s:did_error_try_cmd = 1 endif @@ -55,9 +56,15 @@ endfunction function! provider#clipboard#Executable() abort if exists('g:clipboard') + if type({}) isnot# type(g:clipboard) + \ || type({}) isnot# type(get(g:clipboard, 'copy', v:null)) + \ || type({}) isnot# type(get(g:clipboard, 'paste', v:null)) + let s:err = 'clipboard: invalid g:clipboard' + return '' + endif let s:copy = get(g:clipboard, 'copy', { '+': v:null, '*': v:null }) let s:paste = get(g:clipboard, 'paste', { '+': v:null, '*': v:null }) - let s:cache_enabled = get(g:clipboard, 'cache_enabled', 1) + let s:cache_enabled = get(g:clipboard, 'cache_enabled', 0) return get(g:clipboard, 'name', 'g:clipboard') elseif has('mac') && executable('pbcopy') let s:copy['+'] = 'pbcopy' @@ -104,16 +111,17 @@ function! provider#clipboard#Executable() abort return 'tmux' endif - let s:err = 'clipboard: No clipboard tool available. :help clipboard' + let s:err = 'clipboard: No clipboard tool. :help clipboard' return '' endfunction if empty(provider#clipboard#Executable()) + " provider#clipboard#Call() *must not* be defined if the provider is broken. + " Otherwise eval_has_provider() thinks the clipboard provider is + " functioning, and eval_call_provider() will happily call it. finish endif -let s:clipboard = {} - function! s:clipboard.get(reg) abort if s:selections[a:reg].owner > 0 return s:selections[a:reg].data @@ -154,9 +162,19 @@ function! s:clipboard.set(lines, regtype, reg) abort echohl WarningMsg echomsg 'clipboard: failed to execute: '.(s:copy[a:reg]) echohl None + return 0 endif + return 1 endfunction function! provider#clipboard#Call(method, args) abort - return call(s:clipboard[a:method],a:args,s:clipboard) + if get(s:, 'here', v:false) " Clipboard provider must not recurse. #7184 + return 0 + endif + let s:here = v:true + try + return call(s:clipboard[a:method],a:args,s:clipboard) + finally + let s:here = v:false + endtry endfunction diff --git a/runtime/autoload/provider/ruby.vim b/runtime/autoload/provider/ruby.vim index 91b7fb9f2c..7df3500267 100644 --- a/runtime/autoload/provider/ruby.vim +++ b/runtime/autoload/provider/ruby.vim @@ -75,7 +75,7 @@ let s:prog = provider#ruby#Detect() let s:plugin_path = expand('<sfile>:p:h') . '/script_host.rb' if empty(s:prog) - let s:err = 'Cannot find the neovim RubyGem. Try :CheckHealth' + let s:err = 'Cannot find the neovim RubyGem. Try :checkhealth' endif call remote#host#RegisterClone('legacy-ruby-provider', 'ruby') diff --git a/runtime/autoload/remote/define.vim b/runtime/autoload/remote/define.vim index b04a5d2280..b5c976c823 100644 --- a/runtime/autoload/remote/define.vim +++ b/runtime/autoload/remote/define.vim @@ -89,7 +89,8 @@ endfunction function! remote#define#AutocmdOnHost(host, method, sync, name, opts) let group = s:GetNextAutocmdGroup() - let forward = '"doau '.group.' '.a:name.' ".'.'expand("<amatch>")' + let forward = '"doau '.group.' '.a:name.' ".' + \ . 'fnameescape(expand("<amatch>"))' let a:opts.group = group let bootstrap_def = s:GetAutocmdPrefix(a:name, a:opts) \ .' call remote#define#AutocmdBootstrap("'.a:host.'"' diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 7c6b8a3c1a..8aead087db 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -11,7 +11,7 @@ via |msgpack-rpc|, Lua and VimL (|eval-api|). Applications can also embed libnvim to work with the C API directly. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== API Types *api-types* @@ -171,8 +171,8 @@ nvim_replace_termcodes({str}, {from_part}, {do_lt}, {special}) Parameters:~ {str} String to be converted. {from_part} Legacy Vim parameter. Usually true. - {do_lt} Also translate <lt>. Does nothing if - `special` is false. + {do_lt} Also translate <lt>. Ignored if `special` is + false. {special} Replace |keycodes|, e.g. <CR> becomes a "\n" char. @@ -309,20 +309,24 @@ nvim_set_option({name}, {value}) *nvim_set_option()* {value} New option value nvim_out_write({str}) *nvim_out_write()* - Writes a message to vim output buffer + Writes a message to the Vim output buffer. Does not append + "\n", the message is buffered (won't display) until a linefeed + is written. Parameters:~ {str} Message nvim_err_write({str}) *nvim_err_write()* - Writes a message to vim error buffer + Writes a message to the Vim error buffer. Does not append + "\n", the message is buffered (won't display) until a linefeed + is written. Parameters:~ {str} Message nvim_err_writeln({str}) *nvim_err_writeln()* - Writes a message to vim error buffer. Appends a linefeed to - ensure all contents are written. + Writes a message to the Vim error buffer. Appends "\n", so the + buffer is flushed (and displayed). Parameters:~ {str} Message diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index fd2a7c2641..2850c8058f 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -8,7 +8,7 @@ Automatic commands *autocommand* For a basic explanation, see section |40.3| in the user manual. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Introduction *autocmd-intro* diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt index f82d61370c..9610d7359f 100644 --- a/runtime/doc/change.txt +++ b/runtime/doc/change.txt @@ -11,7 +11,7 @@ commands with the "." command. For inserting text see |insert.txt|. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Deleting text *deleting* *E470* @@ -648,6 +648,7 @@ g& Synonym for `:%s//~/&` (repeat last substitute with *:s_flags* The flags that you can use for the substitute commands: + *:&&* [&] Must be the first one: Keep the flags from the previous substitute command. Examples: > :&& diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index 652487d8ab..4222a5b6f7 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -13,7 +13,7 @@ Command-line mode is used to enter Ex commands (":"), search patterns Basic command line editing is explained in chapter 20 of the user manual |usr_20.txt|. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Command-line editing *cmdline-editing* diff --git a/runtime/doc/debug.txt b/runtime/doc/debug.txt index fd2c4fa54e..422255fa02 100644 --- a/runtime/doc/debug.txt +++ b/runtime/doc/debug.txt @@ -9,7 +9,7 @@ Debugging Vim *debug-vim* This is for debugging Vim itself, when it doesn't work properly. For debugging Vim scripts, functions, etc. see |debug-scripts| - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt index 5d5523e73f..298f64bbee 100644 --- a/runtime/doc/develop.txt +++ b/runtime/doc/develop.txt @@ -11,7 +11,7 @@ Nvim is open source software. Everybody is encouraged to contribute. See src/nvim/README.md for an overview of the source code. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== Design goals *design-goals* diff --git a/runtime/doc/diff.txt b/runtime/doc/diff.txt index 74a3d183a3..8cb049584a 100644 --- a/runtime/doc/diff.txt +++ b/runtime/doc/diff.txt @@ -10,7 +10,7 @@ eight versions of the same file. The basics are explained in section |08.7| of the user manual. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Starting diff mode diff --git a/runtime/doc/digraph.txt b/runtime/doc/digraph.txt index 43f8ccab06..d3b03c6ef6 100644 --- a/runtime/doc/digraph.txt +++ b/runtime/doc/digraph.txt @@ -14,7 +14,7 @@ with CTRL-V (see |i_CTRL-V|). There is a brief introduction on digraphs in the user manual: |24.9| An alternative is using the 'keymap' option. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Defining digraphs *digraphs-define* diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index 9fe815ea9c..a747058991 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -6,7 +6,7 @@ Editing files *edit-files* - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Introduction *edit-intro* @@ -901,11 +901,12 @@ WRITING WITH MULTIPLE BUFFERS *buffer-write* *:wa* *:wall* :wa[ll] Write all changed buffers. Buffers without a file - name or which are readonly are not written. + name cause an error message. Buffers which are + readonly are not written. :wa[ll]! Write all changed buffers, even the ones that are readonly. Buffers without a file name are not - written. + written and cause an error message. Vim will warn you if you try to overwrite a file that has been changed @@ -1271,7 +1272,7 @@ Commands for changing the working directory can be suffixed with a bang "!" *:lcd-* :lcd[!] - Change to the previous current directory (before the - previous ":tcd {path}" command). + previous ":lcd {path}" command). *:pw* *:pwd* *E187* :pw[d] Print the current directory name. diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index dd8aec895d..300bdd061e 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -12,7 +12,7 @@ Note: Expression evaluation can be disabled at compile time. If this has been done, the features in this document are not available. See |+eval| and |no-eval-feature|. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Variables *variables* @@ -1522,14 +1522,16 @@ v:errors Errors found by assert functions, such as |assert_true()|. *v:event* *event-variable* v:event Dictionary of event data for the current |autocommand|. Valid - only during the autocommand lifetime: storing or passing - `v:event` is invalid. Copy it instead: > + only during the event lifetime; storing or passing v:event is + invalid! Copy it instead: > au TextYankPost * let g:foo = deepcopy(v:event) < Keys vary by event; see the documentation for the specific - event, e.g. |TextYankPost|. + event, e.g. |DirChanged| or |TextYankPost|. KEY DESCRIPTION ~ - operator The current |operator|. Also set for - Ex commands (unlike |v:operator|). For + cwd Current working directory + scope Event-specific scope name. + operator Current |operator|. Also set for Ex + commands (unlike |v:operator|). For example if |TextYankPost| is triggered by the |:yank| Ex command then `v:event['operator']` is "y". @@ -4703,6 +4705,7 @@ input({opts}) cancelreturn "" Same as {cancelreturn} from |inputdialog()|. Also works with input(). + highlight nothing Highlight handler: |Funcref|. The highlighting set with |:echohl| is used for the prompt. The input is entered just like a command-line, with the same @@ -4725,7 +4728,60 @@ input({opts}) "-complete=" argument. Refer to |:command-completion| for more information. Example: > let fname = input("File: ", "", "file") + +< *input()-highlight* *E5400* *E5402* + The optional `highlight` key allows specifying function which + will be used for highlighting user input. This function + receives user input as its only argument and must return + a list of 3-tuples [hl_start_col, hl_end_col + 1, hl_group] + where + hl_start_col is the first highlighted column, + hl_end_col is the last highlighted column (+ 1!), + hl_group is |:hl| group used for highlighting. + *E5403* *E5404* *E5405* *E5406* + Both hl_start_col and hl_end_col + 1 must point to the start + of the multibyte character (highlighting must not break + multibyte characters), hl_end_col + 1 may be equal to the + input length. Start column must be in range [0, len(input)), + end column must be in range (hl_start_col, len(input)], + sections must be ordered so that next hl_start_col is greater + then or equal to previous hl_end_col. + + Example (try some input with parentheses): > + highlight RBP1 guibg=Red ctermbg=red + highlight RBP2 guibg=Yellow ctermbg=yellow + highlight RBP3 guibg=Green ctermbg=green + highlight RBP4 guibg=Blue ctermbg=blue + let g:rainbow_levels = 4 + function! RainbowParens(cmdline) + let ret = [] + let i = 0 + let lvl = 0 + while i < len(a:cmdline) + if a:cmdline[i] is# '(' + call add(ret, [i, i + 1, 'RBP' . ((lvl % g:rainbow_levels) + 1)]) + let lvl += 1 + elseif a:cmdline[i] is# ')' + let lvl -= 1 + call add(ret, [i, i + 1, 'RBP' . ((lvl % g:rainbow_levels) + 1)]) + endif + let i += 1 + endwhile + return ret + endfunction + call input({'prompt':'>','highlight':'RainbowParens'}) < + Highlight function is called at least once for each new + displayed input string, before command-line is redrawn. It is + expected that function is pure for the duration of one input() + call, i.e. it produces the same output for the same input, so + output may be memoized. Function is run like under |:silent| + modifier. If the function causes any errors, it will be + skipped for the duration of the current input() call. + + Currently coloring is disabled when command-line contains + arabic characters. + NOTE: This function must not be used in a startup file, for the versions that only run in GUI mode (e.g., the Win32 GUI). Note: When input() is called from within a mapping it will diff --git a/runtime/doc/filetype.txt b/runtime/doc/filetype.txt index 64d9c6daa9..7f1e98fed4 100644 --- a/runtime/doc/filetype.txt +++ b/runtime/doc/filetype.txt @@ -8,7 +8,7 @@ Filetypes *filetype* *file-type* Also see |autocmd.txt|. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Filetypes *filetypes* *file-types* @@ -540,7 +540,7 @@ K or CTRL-] Jump to the manpage for the <cWORD> under the cursor. Takes a count for the section. CTRL-T Jump back to the location that the manpage was opened from. -META-] Show the manpage outline in the |location-list|. +gO Show the manpage outline. |gO| q :quit if invoked as $MANPAGER, otherwise :close. Variables: diff --git a/runtime/doc/fold.txt b/runtime/doc/fold.txt index 88dde9f61d..42efb04d7a 100644 --- a/runtime/doc/fold.txt +++ b/runtime/doc/fold.txt @@ -9,7 +9,7 @@ Folding *Folding* *folding* *folds* You can find an introduction on folding in chapter 28 of the user manual. |usr_28.txt| - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Fold methods *fold-methods* diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt index b66a60c860..fb05f178ad 100644 --- a/runtime/doc/gui.txt +++ b/runtime/doc/gui.txt @@ -6,7 +6,7 @@ Vim's Graphical User Interface *gui* *GUI* - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Starting the GUI *gui-start* *E229* *E233* diff --git a/runtime/doc/helphelp.txt b/runtime/doc/helphelp.txt index 5948b24667..d86a95e854 100644 --- a/runtime/doc/helphelp.txt +++ b/runtime/doc/helphelp.txt @@ -6,7 +6,7 @@ Help on help files *helphelp* - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Help commands *online-help* @@ -23,7 +23,7 @@ Help on help files *helphelp* The 'helplang' option is used to select a language, if the main help file is available in several languages. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. *{subject}* *E149* *E661* :h[elp] {subject} Like ":help", additionally jump to the tag {subject}. diff --git a/runtime/doc/if_cscop.txt b/runtime/doc/if_cscop.txt index 831b2c9840..e359f2144c 100644 --- a/runtime/doc/if_cscop.txt +++ b/runtime/doc/if_cscop.txt @@ -12,7 +12,7 @@ a cscope query is just like jumping to any tag; it is saved on the tag stack so that with the right keyboard mappings, you can jump back and forth between functions as you normally would with |tags|. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Cscope introduction *cscope-intro* diff --git a/runtime/doc/if_lua.txt b/runtime/doc/if_lua.txt index 49b4c6dce1..a123041866 100644 --- a/runtime/doc/if_lua.txt +++ b/runtime/doc/if_lua.txt @@ -1,26 +1,26 @@ *if_lua.txt* Nvim - VIM REFERENCE MANUAL by Luis Carvalho + NVIM REFERENCE MANUAL Lua Interface to Nvim *lua* *Lua* - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== -1. Importing modules *lua-require* +Importing modules *lua-require* -Neovim lua interface automatically adjusts `package.path` and `package.cpath` -according to effective &runtimepath value. Adjustment happens after -'runtimepath' is changed. `package.path` is adjusted by simply appending -`/lua/?.lua` and `/lua/?/init.lua` to each directory from 'runtimepath' (`/` -is actually the first character of `package.config`). +Nvim automatically adjusts `package.path` and `package.cpath` according to +effective 'runtimepath' value. Adjustment happens whenever 'runtimepath' is +changed. `package.path` is adjusted by simply appending `/lua/?.lua` and +`/lua/?/init.lua` to each directory from 'runtimepath' (`/` is actually the +first character of `package.config`). -Similarly to `package.path`, modified directories from `runtimepath` are also -added to `package.cpath`. In this case, instead of appending `/lua/?.lua` and -`/lua/?/init.lua` to each runtimepath, all unique `?`-containing suffixes of -the existing `package.cpath` are used. Here is an example: +Similarly to `package.path`, modified directories from 'runtimepath' are also +added to `package.cpath`. In this case, instead of appending `/lua/?.lua` and +`/lua/?/init.lua` to each runtimepath, all unique `?`-containing suffixes of +the existing `package.cpath` are used. Example: 1. Given that - 'runtimepath' contains `/foo/bar,/xxx;yyy/baz,/abc`; @@ -61,7 +61,7 @@ paths when path is removed from 'runtimepath', adding paths when they are added and reordering `package.path`/`package.cpath` content if 'runtimepath' was reordered. -Note 2: even though adjustments happens automatically Neovim does not track +Note 2: even though adjustments happens automatically Nvim does not track current values of `package.path` or `package.cpath`. If you happened to delete some paths from there you need to reset 'runtimepath' to make them readded. Just running `let &runtimepath = &runtimepath` should work. @@ -72,7 +72,7 @@ badly written plugins using shell which will not work with paths containing semicolons it is better to not have them in 'runtimepath' at all. ------------------------------------------------------------------------------ -1.1. Example of the plugin which uses lua modules: *lua-require-example* +Example of a plugin that uses lua modules *lua-require-example* The following example plugin adds a command `:MakeCharBlob` which transforms current buffer into a long `unsigned char` array. Lua contains transformation @@ -149,7 +149,7 @@ lua/charblob.lua: > } ============================================================================== -2. Commands *lua-commands* +Commands *lua-commands* *:lua* :[range]lua {chunk} @@ -157,7 +157,7 @@ lua/charblob.lua: > Examples: > - :lua vim.api.nvim_command('echo "Hello, Neovim!"') + :lua vim.api.nvim_command('echo "Hello, Nvim!"') < :[range]lua << {endmarker} @@ -230,22 +230,34 @@ All these commands execute a Lua chunk from either the command line (:lua and :luado) or a file (:luafile) with the given line [range]. Similarly to the Lua interpreter, each chunk has its own scope and so only global variables are shared between command calls. All Lua default libraries are available. In -addition, Lua "print" function has its output redirected to the Vim message +addition, Lua "print" function has its output redirected to the Nvim message area, with arguments separated by a white space instead of a tab. -Lua uses the "vim" module (see |lua-vim|) to issue commands to Neovim +Lua uses the "vim" module (see |lua-vim|) to issue commands to Nvim and manage buffers (|lua-buffer|) and windows (|lua-window|). However, procedures that alter buffer content, open new buffers, and change cursor position are restricted when the command is executed in the |sandbox|. ============================================================================== -2. The vim module *lua-vim* +The vim module *lua-vim* -Lua interfaces Vim through the "vim" module. Currently it only has `api` -submodule and some Neovim-specific utility values which is a table with all -API functions. Descriptions of the API functions may be found in |api.txt|. -Description of other utilities is below: +Lua interfaces Nvim through the "vim" module. Currently it has the `api` +submodule and some Nvim-specific utilities. + +------------------------------------------------------------------------------ +vim.api.* functions + +`vim.api` exposes the Nvim |API| as a table of Lua functions. All functions +are available. + +For example, to use the "nvim_get_current_line()" API function, call +"vim.api.nvim_get_current_line()": > + + print(tostring(vim.api.nvim_get_current_line())) + +------------------------------------------------------------------------------ +vim.* utility functions vim.stricmp(a, b) *lua-vim.stricmp* Function used for case-insensitive string comparison. Takes two @@ -263,8 +275,8 @@ vim.val_idx *lua-vim.val_idx* Value index for tables representing |Float|s. A table representing floating-point value 1.0 looks like this: > { - [vim.type_idx] = vim.types.float, - [vim.val_idx] = 1.0, + [vim.type_idx] = vim.types.float, + [vim.val_idx] = 1.0, } < See also |lua-vim.type_idx| and |lua-special-tbl|. @@ -288,10 +300,10 @@ vim.types *lua-vim.types* only contain values for these three types. ============================================================================== -3. The luaeval function *lua-luaeval* *lua-eval* +The luaeval function *lua-luaeval* *lua-eval* *luaeval()* -The (dual) equivalent of "vim.eval" for passing Lua values to Vim is +The (dual) equivalent of "vim.eval" for passing Lua values to Nvim is "luaeval". "luaeval" takes an expression string and an optional argument used for _A inside expression and returns the result of the expression. It is semantically equivalent in Lua to: @@ -303,7 +315,7 @@ semantically equivalent in Lua to: end Note that "_A" receives the argument to "luaeval". Lua nils, numbers, strings, -tables and booleans are converted to their Vim respective types. An error is +tables and booleans are converted to their respective VimL types. An error is thrown if conversion of any of the remaining Lua types is attempted. Note 2: lua tables are used as both dictionaries and lists, thus making it diff --git a/runtime/doc/if_pyth.txt b/runtime/doc/if_pyth.txt index 8baa2d309b..8940e69092 100644 --- a/runtime/doc/if_pyth.txt +++ b/runtime/doc/if_pyth.txt @@ -8,7 +8,7 @@ The Python Interface to Vim *python* *Python* See |provider-python| for more information. {Nvim} - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Commands *python-commands* diff --git a/runtime/doc/if_ruby.txt b/runtime/doc/if_ruby.txt index 54d81a958d..a657d88a44 100644 --- a/runtime/doc/if_ruby.txt +++ b/runtime/doc/if_ruby.txt @@ -10,7 +10,7 @@ The Ruby Interface to Vim *ruby* *Ruby* The home page for ruby is http://www.ruby-lang.org/. You can find links for downloading Ruby there. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Commands *ruby-commands* diff --git a/runtime/doc/indent.txt b/runtime/doc/indent.txt index 8e17de3fb0..cf45ec4f38 100644 --- a/runtime/doc/indent.txt +++ b/runtime/doc/indent.txt @@ -6,7 +6,7 @@ This file is about indenting C programs and other files. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Indenting C style programs *C-indenting* diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index c15587cffd..ad93dcb000 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -15,7 +15,7 @@ For an overview of built-in functions see |functions|. For a list of Vim variables see |vim-variable|. For a complete listing of all help items see |help-tags|. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Insert mode *insert-index* diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index 9219f45c83..9ef58fa54c 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -16,7 +16,7 @@ user manual |usr_24.txt|. Also see 'virtualedit', for moving the cursor to positions where there is no character. Useful for editing a table. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Special keys *ins-special-keys* diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt index b9cc94ce5f..90b0ff7da3 100644 --- a/runtime/doc/intro.txt +++ b/runtime/doc/intro.txt @@ -6,7 +6,7 @@ Introduction to Vim *ref* *reference* - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Introduction *intro* diff --git a/runtime/doc/job_control.txt b/runtime/doc/job_control.txt index edbc1bca81..2cf48f0f4f 100644 --- a/runtime/doc/job_control.txt +++ b/runtime/doc/job_control.txt @@ -6,7 +6,7 @@ Nvim's facilities for job control *job-control* - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Introduction *job-control-intro* diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index 944f7474be..e78b4cc942 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -9,7 +9,7 @@ Key mapping, abbreviations and user-defined commands. This subject is introduced in sections |05.3|, |24.7| and |40.1| of the user manual. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Key mapping *key-mapping* *mapping* *macro* diff --git a/runtime/doc/mbyte.txt b/runtime/doc/mbyte.txt index 2e2ca92656..531629fddc 100644 --- a/runtime/doc/mbyte.txt +++ b/runtime/doc/mbyte.txt @@ -14,7 +14,7 @@ For an introduction to the most common features, see |usr_45.txt| in the user manual. For changing the language of messages and menus see |mlang.txt|. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== Getting started *mbyte-first* diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt index c6c6f49026..904b9dfce4 100644 --- a/runtime/doc/message.txt +++ b/runtime/doc/message.txt @@ -8,7 +8,7 @@ This file contains an alphabetical list of messages and error messages that Vim produces. You can use this if you don't understand what the message means. It is not complete though. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Old messages *:messages* *:mes* *message-history* @@ -449,12 +449,6 @@ changed. To avoid the message reset the 'warn' option. Something inside Vim went wrong and resulted in a NULL pointer. If you know how to reproduce this problem, please report it. |bugs| - *E172* > - Only one file name allowed - -The ":edit" command only accepts one file name. When you want to specify -several files for editing use ":next" |:next|. - *E41* *E82* *E83* *E342* > Out of memory! Out of memory! (allocating {number} bytes) diff --git a/runtime/doc/mlang.txt b/runtime/doc/mlang.txt index 717ec9530c..6453a96877 100644 --- a/runtime/doc/mlang.txt +++ b/runtime/doc/mlang.txt @@ -11,7 +11,7 @@ multi-byte text see |multibyte|. The basics are explained in the user manual: |usr_45.txt|. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Messages *multilang-messages* diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt index 99aa76bfe5..81137b6069 100644 --- a/runtime/doc/motion.txt +++ b/runtime/doc/motion.txt @@ -26,7 +26,7 @@ The 'virtualedit' option can be set to make it possible to move the cursor to positions where there is no character or within a multi-column character (like a tab). - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Motions and operators *operator* @@ -886,6 +886,7 @@ was made yet in the current file. then the position can be near the end of what the command changed. For example when inserting a word, the position will be on the last character. + To jump to older changes use |g;|. *'(* *`(* '( `( To the start of the current sentence, like the |(| diff --git a/runtime/doc/msgpack_rpc.txt b/runtime/doc/msgpack_rpc.txt index 856476c6ae..98a74ccfd6 100644 --- a/runtime/doc/msgpack_rpc.txt +++ b/runtime/doc/msgpack_rpc.txt @@ -6,7 +6,7 @@ RPC API for Nvim *RPC* *rpc* *msgpack-rpc* - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Introduction *rpc-intro* diff --git a/runtime/doc/nvim.txt b/runtime/doc/nvim.txt index f3f4305ad5..2420227f6c 100644 --- a/runtime/doc/nvim.txt +++ b/runtime/doc/nvim.txt @@ -15,7 +15,7 @@ Nvim is emphatically a fork of Vim, not a clone: compatibility with Vim is maintained where possible. See |vim_diff.txt| for the complete reference of differences from Vim. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== Transitioning from Vim *nvim-from-vim* diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt index 4527a14710..9dae69ae26 100644 --- a/runtime/doc/nvim_terminal_emulator.txt +++ b/runtime/doc/nvim_terminal_emulator.txt @@ -18,7 +18,7 @@ Terminal buffers behave like normal buffers, except: closing the terminal buffer. - 'bufhidden' defaults to "hide". - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== Start *terminal-start* diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index c6965648ef..4df08ca5fb 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -14,7 +14,7 @@ achieve special effects. These options come in three forms: number has a numeric value string has a string value - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Setting options *set-option* *E764* @@ -187,7 +187,7 @@ opt+=val" the expansion is done before the adding or removing. Handling of local options *local-options* Some of the options only apply to a window or buffer. Each window or buffer -has its own copy of this option, thus can each have their own value. This +has its own copy of this option, thus each can have its own value. This allows you to set 'list' in one window but not in another. And set 'shiftwidth' to 3 in one buffer and 4 in another. @@ -3017,6 +3017,8 @@ A jump table for the options with a short description can be found at |Q_op|. The format of this option is like that of 'statusline'. 'guitabtooltip' is used for the tooltip, see below. + The expression will be evaluated in the |sandbox| when set from a + modeline, see |sandbox-option|. Only used when the GUI tab pages line is displayed. 'e' must be present in 'guioptions'. For the non-GUI tab pages line 'tabline' is @@ -4482,6 +4484,8 @@ A jump table for the options with a short description can be found at |Q_op|. and |+postscript| features} Expression used to print the PostScript produced with |:hardcopy|. See |pexpr-option|. + This option cannot be set from a |modeline| or in the |sandbox|, for + security reasons. *'printfont'* *'pfn'* 'printfont' 'pfn' string (default "courier") @@ -6267,11 +6271,11 @@ A jump table for the options with a short description can be found at |Q_op|. when part of a command has been typed. *'title'* *'notitle'* -'title' boolean (default off, on when title can be restored) +'title' boolean (default off) global When on, the title of the window will be set to the value of 'titlestring' (if it is not empty), or to: - filename [+=-] (path) - VIM + filename [+=-] (path) - NVIM Where: filename the name of the file being edited - indicates the file cannot be modified, 'ma' off @@ -6279,7 +6283,7 @@ A jump table for the options with a short description can be found at |Q_op|. = indicates the file is read-only =+ indicates the file is read-only and modified (path) is the path of the file being edited - - VIM the server name |v:servername| or "VIM" + - NVIM the server name |v:servername| or "NVIM" *'titlelen'* 'titlelen' number (default 85) @@ -6295,11 +6299,10 @@ A jump table for the options with a short description can be found at |Q_op|. 'titlelen' is also used for the 'titlestring' option. *'titleold'* -'titleold' string (default "Thanks for flying Vim") +'titleold' string (default "") global - This option will be used for the window title when exiting Vim if the - original title cannot be restored. Only happens if 'title' is on or - 'titlestring' is not empty. + If not empty, this option will be used to set the window title when + exiting. Only if 'title' is enabled. This option cannot be set from a |modeline| or in the |sandbox|, for security reasons. *'titlestring'* @@ -6748,19 +6751,19 @@ A jump table for the options with a short description can be found at |Q_op|. *'winhighlight'* *'winhl'* 'winhighlight' 'winhl' string (default empty) local to window - Window-local highlights. Comma-delimited list of |group-name| pairs - "{hl-builtin}:{hl-group},..." where each {hl-builtin} is a group (from - |highlight-groups|) to be overridden by {hl-group} in the window where - this option was set. Only builting ui highlights are supported, not - syntax highlighting. For that purpose, use |:ownsyntax|. + Window-local highlights. Comma-delimited list of highlight + |group-name| pairs "{hl-builtin}:{hl},..." where each {hl-builtin} is + a built-in |highlight-groups| item to be overridden by {hl} group in + the window. Only built-in |highlight-groups| are supported, not + syntax highlighting (use |:ownsyntax| for that). - Most highlights occuring within the frame of a window are supported. Highlights of vertical separators are determined by the window to the left of the separator. The highlight of a tabpage in |tabline| is - determined by the last focused window in the tabpage. Highlights of + determine by the last-focused window of the tabpage. Highlights of the popupmenu are determined by the current window. Highlights in the - message area are not overridable. Example for overriding the - backgrond color: > + message area cannot be overridden. + + Example: show a different color for non-current windows: > set winhighlight=Normal:MyNormal,NormalNC:MyNormalNC < *'winfixheight'* *'wfh'* *'nowinfixheight'* *'nowfh'* diff --git a/runtime/doc/pattern.txt b/runtime/doc/pattern.txt index f7f561dfa5..8243a3ca7d 100644 --- a/runtime/doc/pattern.txt +++ b/runtime/doc/pattern.txt @@ -9,7 +9,7 @@ Patterns and search commands *pattern-searches* The very basics can be found in section |03.9| of the user manual. A few more explanations are in chapter 27 |usr_27.txt|. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Search commands *search-commands* diff --git a/runtime/doc/pi_health.txt b/runtime/doc/pi_health.txt index 8354c0470f..99ff519bb9 100644 --- a/runtime/doc/pi_health.txt +++ b/runtime/doc/pi_health.txt @@ -2,53 +2,45 @@ Author: TJ DeVries <devries.timothyj@gmail.com> - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== -Introduction *healthcheck* *health.vim-intro* +Introduction *health* -Troubleshooting user configuration problems is a time-consuming task that -developers want to minimize. health.vim provides a simple framework for plugin -authors to hook into, and for users to invoke, to check and report the user's -configuration and environment. Type this command to try it: > +health.vim is a minimal framework to help with troubleshooting user +configuration. Nvim ships with healthchecks for configuration, performance, +python support, ruby support, clipboard support, and more. - :CheckHealth -< -For example, some users have broken or unusual Python setups, which breaks the -|:python| command. |:CheckHealth| detects several common Python configuration -problems and reports them. If the Neovim Python module is not installed, it -shows a warning: > +To run the healthchecks, use this command: > - You have not installed the Neovim Python module - You might want to try `pip install Neovim` -< -Plugin authors are encouraged to add healthchecks, see |health.vim-dev|. + :checkhealth +< +Plugin authors are encouraged to write new healthchecks. |health-dev| ============================================================================== -Commands and functions *health.vim-manual* - -Commands ------------------------------------------------------------------------------- - *:CheckHealth* -:CheckHealth Run all healthchecks and show the output in a new - tabpage. These healthchecks are included by default: - - python2 - - python3 - - ruby - - remote plugin - -:CheckHealth {plugins} - Run healthchecks for one or more plugins. E.g. to run +Commands *health-commands* + + *:checkhealth* *:CheckHealth* +:checkhealth Run all healthchecks. + *E5009* + Nvim depends on the |$VIMRUNTIME| environment variable + to find the standard "runtime files" for syntax + highlighting, filetype-specific behavior, and standard + plugins such as :checkhealth. If $VIMRUNTIME is invalid + then those features will not work. + +:checkhealth {plugins} + Run healthcheck(s) for one or more plugins. E.g. to run only the standard Nvim healthcheck: > - :CheckHealth nvim + :checkhealth nvim < To run the healthchecks for the "foo" and "bar" plugins (assuming these plugins are on your 'runtimepath' and they have implemented health#foo#check() and health#bar#check(), respectively): > - :CheckHealth foo bar + :checkhealth foo bar < -Functions ------------------------------------------------------------------------------- +============================================================================== +Functions *health-functions* health.vim functions are for creating new healthchecks. They mostly just do some layout and formatting, to give users a consistent presentation. @@ -59,51 +51,49 @@ health#report_start({name}) *health#report_start* per section. health#report_info({msg}) *health#report_info* - Displays an informational message. + Reports an informational message. health#report_ok({msg}) *health#report_ok* - Displays a "success" message. + Reports a "success" message. -health#report_warn({msg}, [{suggestions}]) *health#report_warn* - Displays a warning. {suggestions} is an optional List of suggestions. +health#report_warn({msg}, [{advice}]) *health#report_warn* + Reports a warning. {advice} is an optional List of suggestions. -health#report_error({msg}, [{suggestions}]) *health#report_error* - Displays an error. {suggestions} is an optional List of suggestions. +health#report_error({msg}, [{advice}]) *health#report_error* + Reports an error. {advice} is an optional List of suggestions. health#{plugin}#check() *health.user_checker* - This is the form of a healthcheck definition. Call the above functions - from this function, then |:CheckHealth| does the rest. Example: > + Healthcheck function for {plugin}. Called by |:checkhealth| + automatically. Example: > function! health#my_plug#check() abort silent call s:check_environment_vars() silent call s:check_python_configuration() endfunction < - The function will be found and called automatically when the user - invokes |:CheckHealth|. - All output will be captured from the healthcheck. Use the health#report_* functions so that your healthcheck has a format consistent with the standard healthchecks. ============================================================================== -Create a healthcheck *health.vim-dev* +Create a healthcheck *health-dev* -Healthchecks are functions that check the health of the system. Neovim has -built-in checkers, found in $VIMRUNTIME/autoload/health/. +Healthchecks are functions that check the user environment, configuration, +etc. Nvim has built-in healthchecks in $VIMRUNTIME/autoload/health/. -To add a new checker for your own plugin, simply define a +To add a new healthcheck for your own plugin, simply define a health#{plugin}#check() function in autoload/health/{plugin}.vim. -|:CheckHealth| automatically finds and invokes such functions. +|:checkhealth| automatically finds and invokes such functions. -If your plugin is named "jslint", then its healthcheck function must be > - health#jslint#check() +If your plugin is named "foo", then its healthcheck function must be > + health#foo#check() defined in this file on 'runtimepath': > - autoload/health/jslint.vim + autoload/health/foo.vim -Here's a sample to get started: > - function! health#jslint#check() abort +Copy this sample code into autoload/health/foo.vim and replace "foo" with your +plugin name: > + function! health#foo#check() abort call health#report_start('sanity checks') " perform arbitrary checks " ... @@ -111,8 +101,8 @@ Here's a sample to get started: > if looks_good call health#report_ok('found required dependencies') else - call health#report_error('cannot find jslint', - \ ['npm install --save jslint']) + call health#report_error('cannot find foo', + \ ['npm install --save foo']) endif endfunction diff --git a/runtime/doc/print.txt b/runtime/doc/print.txt index 01de3a5290..72625a450a 100644 --- a/runtime/doc/print.txt +++ b/runtime/doc/print.txt @@ -6,7 +6,7 @@ Printing *printing* - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Introduction *print-intro* diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt index 50307ccbf3..02f3fcab6c 100644 --- a/runtime/doc/provider.txt +++ b/runtime/doc/provider.txt @@ -8,7 +8,7 @@ Providers *provider* Nvim delegates some features to dynamic "providers". - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== Python integration *provider-python* @@ -20,7 +20,7 @@ Note: Only the Vim 7.3 API is supported; bindeval (Vim 7.4) is not. PYTHON QUICKSTART ~ If you used a package manager to install Nvim, you might already have the -required `neovim` Python package. Run |:CheckHealth| to see if your system is +required `neovim` Python package. Run |:checkhealth| to see if your system is up-to-date. Following are steps to install the package with Python's `pip` tool. @@ -88,7 +88,7 @@ Ruby integration *provider-ruby* Nvim supports the Vim legacy |ruby-vim| interface via external Ruby interpreters connected via |RPC|. -Run |:CheckHealth| to see if your system is up-to-date. +Run |:checkhealth| to see if your system is up-to-date. RUBY QUICKSTART ~ diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt index cad5bf98b5..a647318347 100644 --- a/runtime/doc/quickfix.txt +++ b/runtime/doc/quickfix.txt @@ -6,7 +6,7 @@ This subject is introduced in section |30.1| of the user manual. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================= 1. Using QuickFix commands *quickfix* *Quickfix* *E42* @@ -138,11 +138,15 @@ processing a quickfix or location list command, it will be aborted. current window is used instead of the quickfix list. *:cq* *:cquit* -:cq[uit][!] Quit Vim with an error code, so that the compiler - will not compile the same file again. - WARNING: All changes in files are lost! Also when the - [!] is not used. It works like ":qall!" |:qall|, - except that Vim returns a non-zero exit code. +:[count]cq[uit] Quit Nvim with an error code, or the code specified in + [count]. Useful when Nvim is called from another + program: e.g. `git commit` will abort the comitting + process, `fc` (built-in for shells like bash and zsh) + will not execute the command. + + WARNING: All changes in files are lost. It works like + ":qall!" |:qall|, except that Nvim exits non-zero or + [count]. *:cf* *:cfile* :cf[ile][!] [errorfile] Read the error file and jump to the first error. @@ -1534,4 +1538,4 @@ by Vim. - vim:tw=78:ts=8:ft=help:norl: + vim:noet:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/recover.txt b/runtime/doc/recover.txt index 2b49af1c96..0533157072 100644 --- a/runtime/doc/recover.txt +++ b/runtime/doc/recover.txt @@ -15,7 +15,7 @@ You can recover most of your changes from the files that Vim uses to store the contents of the file. Mostly you can recover your work with one command: vim -r filename - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. The swap file *swap-file* diff --git a/runtime/doc/remote.txt b/runtime/doc/remote.txt index bdc763b85f..67bfb0b48e 100644 --- a/runtime/doc/remote.txt +++ b/runtime/doc/remote.txt @@ -6,7 +6,7 @@ Vim client-server communication *client-server* - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Common functionality *clientserver* diff --git a/runtime/doc/remote_plugin.txt b/runtime/doc/remote_plugin.txt index cc2efd3d1f..eeb9cf8150 100644 --- a/runtime/doc/remote_plugin.txt +++ b/runtime/doc/remote_plugin.txt @@ -6,7 +6,7 @@ Nvim support for remote plugins *remote-plugin* - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Introduction *remote-plugin-intro* diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt index 0b3edc9bba..421ebab100 100644 --- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -8,7 +8,7 @@ Repeating commands, Vim scripts and debugging *repeating* Chapter 26 of the user manual introduces repeating |usr_26.txt|. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Single repeats *single-repeat* diff --git a/runtime/doc/russian.txt b/runtime/doc/russian.txt index e2c44ce54f..8c6076146c 100644 --- a/runtime/doc/russian.txt +++ b/runtime/doc/russian.txt @@ -6,7 +6,7 @@ Russian language localization and support in Vim *russian* *Russian* - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. =============================================================================== 1. Introduction *russian-intro* diff --git a/runtime/doc/scroll.txt b/runtime/doc/scroll.txt index 52e5cc9f0c..56af9ab75e 100644 --- a/runtime/doc/scroll.txt +++ b/runtime/doc/scroll.txt @@ -16,7 +16,7 @@ upwards in the buffer, the text in the window moves downwards on your screen. See section |03.7| of the user manual for an introduction. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Scrolling downwards *scroll-down* diff --git a/runtime/doc/sign.txt b/runtime/doc/sign.txt index ced0608e8a..977d73b7b2 100644 --- a/runtime/doc/sign.txt +++ b/runtime/doc/sign.txt @@ -7,7 +7,7 @@ Sign Support Features *sign-support* - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Introduction *sign-intro* *signs* diff --git a/runtime/doc/spell.txt b/runtime/doc/spell.txt index 5c99db42ba..f2be25097c 100644 --- a/runtime/doc/spell.txt +++ b/runtime/doc/spell.txt @@ -6,7 +6,7 @@ Spell checking *spell* - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Quick start *spell-quickstart* *E756* diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 4cfc98d5b6..7aba84b454 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -6,7 +6,7 @@ Starting Vim *starting* - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Vim arguments *vim-arguments* @@ -633,54 +633,40 @@ though. ============================================================================== 3. $VIM and $VIMRUNTIME *$VIM* -The environment variable "$VIM" is used to locate various user files for Vim, +The environment variable "$VIM" is used to locate various user files for Nvim, such as the user startup script |init.vim|. This depends on the system, see |startup|. -To avoid the need for every user to set the $VIM environment variable, Vim -will try to get the value for $VIM in this order: -1. The value defined by the $VIM environment variable. You can use this to - make Vim look in a specific directory for its support files. Example: > - setenv VIM /home/paul/vim -2. The path from 'helpfile' is used, unless it contains some environment - variable too (the default is "$VIMRUNTIME/doc/help.txt": chicken-egg - problem). The file name ("help.txt" or any other) is removed. Then - trailing directory names are removed, in this order: "doc", "runtime" and - "vim{version}" (e.g., "vim54"). -3. For MSDOS and Win32 Vim tries to use the directory name of the - executable. If it ends in "/src", this is removed. This is useful if you - unpacked the .zip file in some directory, and adjusted the search path to - find the vim executable. Trailing directory names are removed, in this - order: "runtime" and "vim{version}" (e.g., "vim54"). -4. For Unix the compile-time defined installation directory is used (see the - output of ":version"). - -Once Vim has done this once, it will set the $VIM environment variable. To -change it later, use a ":let" command like this: > - :let $VIM = "/home/paul/vim/" -< +Nvim will try to get the value for $VIM in this order: + +1. Environment variable $VIM, if it is set. +2. Path derived from the 'helpfile' option, unless it contains some + environment variable too (default is "$VIMRUNTIME/doc/help.txt"). File + name ("help.txt", etc.) is removed. Trailing directory names are removed, + in this order: "doc", "runtime". +3. Path derived from the location of the `nvim` executable. +4. Compile-time defined installation directory (see output of ":version"). + +After doing this once, Nvim sets the $VIM environment variable. + *$VIMRUNTIME* The environment variable "$VIMRUNTIME" is used to locate various support -files, such as the on-line documentation and files used for syntax -highlighting. For example, the main help file is normally -"$VIMRUNTIME/doc/help.txt". -You don't normally set $VIMRUNTIME yourself, but let Vim figure it out. This -is the order used to find the value of $VIMRUNTIME: -1. If the environment variable $VIMRUNTIME is set, it is used. You can use - this when the runtime files are in an unusual location. -2. If "$VIM/vim{version}" exists, it is used. {version} is the version - number of Vim, without any '-' or '.'. For example: "$VIM/vim54". This is - the normal value for $VIMRUNTIME. -3. If "$VIM/runtime" exists, it is used. -4. The value of $VIM is used. This is for backwards compatibility with older - versions. +files, such as the documentation and syntax-highlighting files. For example, +the main help file is normally "$VIMRUNTIME/doc/help.txt". + +Nvim will try to get the value for $VIMRUNTIME in this order: + +1. Environment variable $VIMRUNTIME, if it is set. +2. Directory path "$VIM/vim{version}", if it exists, where {version} is the + Vim version number without '-' or '.'. For example: "$VIM/vim54". +3. Directory path "$VIM/runtime", if it exists. +4. Value of $VIM environment variable. This is for backwards compatibility + with older Vim versions. 5. If "../share/nvim/runtime" exists relative to |v:progpath|, it is used. -6. When the 'helpfile' option is set and doesn't contain a '$', its value is - used, with "doc/help.txt" removed from the end. +6. Path derived from the 'helpfile' option (if it doesn't contain '$') with + "doc/help.txt" removed from the end. -Once Vim has done this once, it will set the $VIMRUNTIME environment variable. -To change it later, use a ":let" command like this: > - :let $VIMRUNTIME = "/home/piet/vim/vim54" +After doing this once, Nvim sets the $VIMRUNTIME environment variable. In case you need the value of $VIMRUNTIME in a shell (e.g., for a script that greps in the help files) you might be able to use this: > diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index fd17e3b381..cbaa2916e6 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -20,7 +20,7 @@ In the User Manual: |usr_06.txt| introduces syntax highlighting. |usr_44.txt| introduces writing a syntax file. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Quick start *:syn-qstart* @@ -3270,11 +3270,10 @@ improve screen updating rates (see |:syn-sync| for more on this). > The g:vimsyn_embed option allows users to select what, if any, types of embedded script highlighting they wish to have. > - g:vimsyn_embed == 0 : don't embed any scripts - g:vimsyn_embed =~ 'P' : support embedded python + g:vimsyn_embed == 0 : disable (don't embed any scripts) + g:vimsyn_embed == 'lPr' : support embedded lua, python and ruby < -By default, g:vimsyn_embed is a string supporting interpreters that your vim -itself supports. +This option is disabled by default. *g:vimsyn_folding* Some folding is now supported with syntax/vim.vim: > diff --git a/runtime/doc/tabpage.txt b/runtime/doc/tabpage.txt index 8f1eb9d8cd..6be7cf9746 100644 --- a/runtime/doc/tabpage.txt +++ b/runtime/doc/tabpage.txt @@ -10,7 +10,7 @@ The commands which have been added to use multiple tab pages are explained here. Additionally, there are explanations for commands that work differently when used in combination with more than one tab page. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Introduction *tab-page-intro* diff --git a/runtime/doc/tagsrch.txt b/runtime/doc/tagsrch.txt index d5a4f4e627..1ceb602512 100644 --- a/runtime/doc/tagsrch.txt +++ b/runtime/doc/tagsrch.txt @@ -8,7 +8,7 @@ Tags and special searches *tags-and-searches* See section |29.1| of the user manual for an introduction. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Jump to a tag *tag-commands* diff --git a/runtime/doc/term.txt b/runtime/doc/term.txt index d99f9b7160..a694185fc9 100644 --- a/runtime/doc/term.txt +++ b/runtime/doc/term.txt @@ -10,7 +10,7 @@ Nvim (except in |--headless| mode) uses information about the terminal you are using to present a built-in UI. If that information is not correct, the screen may be messed up or keys may not be recognized. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== Startup *startup-terminal* @@ -41,16 +41,17 @@ $TERM is also important because it is mirrored by SSH to the remote session, unlike other common client-end environment variables ($COLORTERM, $XTERM_VERSION, $VTE_VERSION, $KONSOLE_PROFILE_NAME, $TERM_PROGRAM, ...). - For this terminal Set $TERM to |builtin-terms|? - - iTerm.app "iterm" or "iTerm.app" Y - anything libvte based "vte" or "vte-256color" Y - (e.g. GNOME Terminal) (aliases: "gnome", "gnome-256color") - tmux "tmux" or "tmux-256color" Y - screen "screen" or "screen-256color" Y - PuTTY "putty" or "putty-256color" Y - Terminal.app "nsterm" N - Linux virtual terminal "linux" or "linux-256color" Y + For this terminal Set $TERM to |builtin-terms| + ------------------------------------------------------------------------- + iTerm (original) iterm, iTerm.app N + iTerm2 (new capabilities) iterm2, iTerm2.app Y + anything libvte-based vte, vte-256color Y + (e.g. GNOME Terminal) (aliases: gnome, gnome-256color) + tmux tmux, tmux-256color Y + screen screen, screen-256color Y + PuTTY putty, putty-256color Y + Terminal.app nsterm N + Linux virtual terminal linux, linux-256color Y *builtin-terms* *builtin_terms* If a |terminfo| database is not available, or no entry for the terminal type is diff --git a/runtime/doc/tips.txt b/runtime/doc/tips.txt index 0ac9a8303d..011e0f0565 100644 --- a/runtime/doc/tips.txt +++ b/runtime/doc/tips.txt @@ -13,7 +13,7 @@ http://www.vim.org Don't forget to browse the user manual, it also contains lots of useful tips |usr_toc.txt|. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== Editing C programs *C-editing* diff --git a/runtime/doc/undo.txt b/runtime/doc/undo.txt index cbce868cec..f8f6049119 100644 --- a/runtime/doc/undo.txt +++ b/runtime/doc/undo.txt @@ -8,7 +8,7 @@ Undo and redo *undo-redo* The basics are explained in section |02.5| of the user manual. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Undo and redo commands *undo-commands* diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index f06c062ee3..9150f3a809 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -6,7 +6,7 @@ Various commands *various* - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Various commands *various-cmds* @@ -212,7 +212,6 @@ g8 Print the hex values of the bytes used in the Equivalent to: > :enew :call termopen('{cmd}') - :startinsert < See |termopen()|. @@ -609,6 +608,13 @@ which it was defined is reported. the keyword. Only works when the highlighted text is not more than one line. + *gO* +gO Show a filetype-specific, navigable "outline" of the + current buffer. For example, in a |help| buffer this + shows the table of contents. + + Currently works in |help| and |:Man| buffers. + [N]gs *gs* *:sl* *:sleep* :[N]sl[eep] [N] [m] Do nothing for [N] seconds. When [m] is included, sleep for [N] milliseconds. The count for "gs" always @@ -647,4 +653,4 @@ LessInitFunc in your vimrc, for example: > endfunc < - vim:tw=78:ts=8:ft=help:norl: + vim:noet:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/vi_diff.txt b/runtime/doc/vi_diff.txt index b8bfcaa586..917e0e6f80 100644 --- a/runtime/doc/vi_diff.txt +++ b/runtime/doc/vi_diff.txt @@ -6,7 +6,7 @@ Differences between Vim and Vi *vi-differences* - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Limits *limits* diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 7f1e5ce543..c92fcd8994 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -6,11 +6,10 @@ Differences between Nvim and Vim *vim-differences* -Throughout the help files, differences between Nvim and Vim are indicated via -the "{Nvim}" tag. This document is a complete and centralized list of all -these differences. +Nvim differs from Vim in many ways, big and small. This document is +a complete and centralized reference of those differences. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Configuration *nvim-configuration* @@ -72,12 +71,18 @@ Clipboard integration |provider-clipboard| USER EXPERIENCE ~ -Working intuitively and consistently is a major goal of Nvim. Examples: +Working intuitively and consistently is a major goal of Nvim. -- Nvim does not have `-X`, a platform-specific option "sometimes" available in - Vim (with potential surprises: http://stackoverflow.com/q/14635295). Nvim - avoids features that cannot be provided on all platforms--instead that is - delegated to external plugins/extensions. + *feature-compile* +- Nvim always includes ALL features, in contrast to Vim (which ships with + various combinations of 100+ optional features). Think of it as a leaner + version of Vim's "HUGE" build. This reduces surface area for bugs, and + removes a common source of confusion and friction for users. + +- Nvim avoids features that cannot be provided on all platforms; instead that + is delegated to external plugins/extensions. E.g. the `-X` platform-specific + option is "sometimes" available in Vim (with potential surprises: + http://stackoverflow.com/q/14635295). - Vim's internal test functions (test_autochdir(), test_settime(), etc.) are not exposed (nor implemented); instead Nvim has a robust API. @@ -95,7 +100,7 @@ by Nvim developers. FEATURES ~ -"Outline": Type <M-]> in |:Man| and |:help| pages to see a document outline. +"Outline": Type |gO| in |:Man| and |:help| pages to see a document outline. |META| (ALT) chords are recognized, even in the terminal. Any |<M-| mapping will work. Some examples: <M-1>, <M-2>, <M-BS>, <M-Del>, <M-Ins>, <M-/>, @@ -122,14 +127,13 @@ Variables: |v:windowid| is always available (for use by external UIs) Commands: - |:CheckHealth| + |:checkhealth| |:drop| is available on all platforms |:Man| is available by default, with many improvements such as completion Functions: |dictwatcheradd()| notifies a callback whenever a |Dict| is modified |dictwatcherdel()| - |execute()| works with |:redir| |menu_get()| |msgpackdump()|, |msgpackparse()| provide msgpack de/serialization @@ -147,6 +151,14 @@ Highlight groups: |hl-TermCursorNC| |hl-Whitespace| highlights 'listchars' whitespace +UI: + *E5408* *E5409* *g:Nvim_color_expr* *g:Nvim_color_cmdline* + Command-line coloring is supported. Only |input()| and |inputdialog()| may + be colored. For testing purposes expressions (e.g. |i_CTRL-R_=|) and regular + command-line (|:|) are colored by callbacks defined in `g:Nvim_color_expr` + and `g:Nvim_color_cmdline` respectively (these callbacks are for testing + only, and will be removed in a future version). + ============================================================================== 4. Changed features *nvim-features-changed* @@ -174,6 +186,8 @@ one. It does not attempt to mix data from the two. |system()| does not support writing/reading "backgrounded" commands. |E5677| +|:redir| nested in |execute()| works. + Nvim may throttle (skip) messages from shell commands (|:!|, |:grep|, |:make|) if there is too much output. No data is lost, this only affects display and makes things faster. |:terminal| output is never throttled. @@ -259,19 +273,24 @@ Lua interface (|if_lua.txt|): - Lua has direct access to Nvim |API| via `vim.api`. - Lua package.path and package.cpath are automatically updated according to 'runtimepath': |lua-require|. -- Currently, most legacy Vim features are missing. -|input()| and |inputdialog()| gained support for each other’s features (return -on cancel and completion respectively) via dictionary argument (replaces all +|input()| and |inputdialog()| support for each other’s features (return on +cancel and completion respectively) via dictionary argument (replaces all other arguments if used). +|input()| and |inputdialog()| support user-defined cmdline highlighting. + +Highlight groups: + |hl-ColorColumn|, |hl-CursorColumn| are lower priority than most other + groups + ============================================================================== 5. Missing legacy features *nvim-features-missing* Some legacy Vim features are not implemented: - |if_py|: vim.bindeval() and vim.Function() are not supported -- |if_lua|: the `vim` object currently only supports `vim.api` +- |if_lua|: the `vim` object is missing most legacy methods - *if_perl* - *if_mzscheme* - *if_tcl* @@ -279,7 +298,7 @@ Some legacy Vim features are not implemented: ============================================================================== 6. Removed features *nvim-features-removed* -These features are in Vim, but have been intentionally removed from Nvim. +These Vim features were intentionally removed from Nvim. *'cp'* *'nocompatible'* *'nocp'* *'compatible'* Nvim is always "non-compatible" with Vi. diff --git a/runtime/doc/visual.txt b/runtime/doc/visual.txt index cf804444e5..6c4d44edb6 100644 --- a/runtime/doc/visual.txt +++ b/runtime/doc/visual.txt @@ -11,7 +11,7 @@ operator. It is the only way to select a block of text. This is introduced in section |04.4| of the user manual. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Using Visual mode *visual-use* diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt index f5d5321a5e..c37362a497 100644 --- a/runtime/doc/windows.txt +++ b/runtime/doc/windows.txt @@ -13,7 +13,7 @@ differently when used in combination with more than one window. The basics are explained in chapter 7 and 8 of the user manual |usr_07.txt| |usr_08.txt|. - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Introduction *windows-intro* *window* @@ -677,6 +677,8 @@ can also get to them with the buffer list commands, like ":bnext". - If the file is not open in a window edit the file in the current window. If the current buffer can't be |abandon|ed, the window is split first. + - Windows that are not in the argument list or are not full + width will be closed if possible. The |argument-list| is set, like with the |:next| command. The purpose of this command is that it can be used from a program that wants Vim to edit another file, e.g., a debugger. diff --git a/runtime/ftplugin/help.vim b/runtime/ftplugin/help.vim index 9d2361b413..e6d48454d9 100644 --- a/runtime/ftplugin/help.vim +++ b/runtime/ftplugin/help.vim @@ -90,7 +90,7 @@ if !exists('g:no_plugin_maps') let w:qf_toc = bufname endfunction - nnoremap <silent><buffer> <M-]> :call <sid>show_toc()<cr> + nnoremap <silent><buffer> gO :call <sid>show_toc()<cr> endif let &cpo = s:cpo_save diff --git a/runtime/ftplugin/man.vim b/runtime/ftplugin/man.vim index 27c8b88e44..e36dfc5a90 100644 --- a/runtime/ftplugin/man.vim +++ b/runtime/ftplugin/man.vim @@ -31,7 +31,7 @@ setlocal nolist setlocal nofoldenable if !exists('g:no_plugin_maps') && !exists('g:no_man_maps') - nnoremap <silent> <buffer> <M-]> :call man#show_toc()<CR> + nnoremap <silent> <buffer> gO :call man#show_toc()<CR> nnoremap <silent> <buffer> <C-]> :Man<CR> nnoremap <silent> <buffer> K :Man<CR> nnoremap <silent> <buffer> <C-T> :call man#pop_tag()<CR> diff --git a/runtime/syntax/c.vim b/runtime/syntax/c.vim index cc99f674a1..16c7ce4d92 100644 --- a/runtime/syntax/c.vim +++ b/runtime/syntax/c.vim @@ -1,7 +1,7 @@ " Vim syntax file " Language: C " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2016 Nov 17 +" Last Change: 2016 Nov 18 " Quit when a (custom) syntax file was already loaded if exists("b:current_syntax") @@ -363,23 +363,23 @@ syn match cPreConditMatch display "^\s*\zs\(%:\|#\)\s*\(else\|endif\)\>" if !exists("c_no_if0") syn cluster cCppOutInGroup contains=cCppInIf,cCppInElse,cCppInElse2,cCppOutIf,cCppOutIf2,cCppOutElse,cCppInSkip,cCppOutSkip syn region cCppOutWrapper start="^\s*\zs\(%:\|#\)\s*if\s\+0\+\s*\($\|//\|/\*\|&\)" end=".\@=\|$" contains=cCppOutIf,cCppOutElse,@NoSpell fold - syn region cCppOutIf contained start="0\+" matchgroup=cCppOutWrapper end="^\s*\zs\(%:\|#\)\s*endif\>" contains=cCppOutIf2,cCppOutElse + syn region cCppOutIf contained start="0\+" matchgroup=cCppOutWrapper end="^\s*\(%:\|#\)\s*endif\>" contains=cCppOutIf2,cCppOutElse if !exists("c_no_if0_fold") syn region cCppOutIf2 contained matchgroup=cCppOutWrapper start="0\+" end="^\s*\(%:\|#\)\s*\(else\>\|elif\s\+\(0\+\s*\($\|//\|/\*\|&\)\)\@!\|endif\>\)"me=s-1 contains=cSpaceError,cCppOutSkip,@Spell fold else syn region cCppOutIf2 contained matchgroup=cCppOutWrapper start="0\+" end="^\s*\(%:\|#\)\s*\(else\>\|elif\s\+\(0\+\s*\($\|//\|/\*\|&\)\)\@!\|endif\>\)"me=s-1 contains=cSpaceError,cCppOutSkip,@Spell endif - syn region cCppOutElse contained matchgroup=cCppOutWrapper start="^\s*\zs\(%:\|#\)\s*\(else\|elif\)" end="^\s*\zs\(%:\|#\)\s*endif\>"me=s-1 contains=TOP,cPreCondit + syn region cCppOutElse contained matchgroup=cCppOutWrapper start="^\s*\(%:\|#\)\s*\(else\|elif\)" end="^\s*\(%:\|#\)\s*endif\>"me=s-1 contains=TOP,cPreCondit syn region cCppInWrapper start="^\s*\zs\(%:\|#\)\s*if\s\+0*[1-9]\d*\s*\($\|//\|/\*\||\)" end=".\@=\|$" contains=cCppInIf,cCppInElse fold - syn region cCppInIf contained matchgroup=cCppInWrapper start="\d\+" end="^\s*\zs\(%:\|#\)\s*endif\>" contains=TOP,cPreCondit + syn region cCppInIf contained matchgroup=cCppInWrapper start="\d\+" end="^\s*\(%:\|#\)\s*endif\>" contains=TOP,cPreCondit if !exists("c_no_if0_fold") - syn region cCppInElse contained start="^\s*\zs\(%:\|#\)\s*\(else\>\|elif\s\+\(0*[1-9]\d*\s*\($\|//\|/\*\||\)\)\@!\)" end=".\@=\|$" containedin=cCppInIf contains=cCppInElse2 fold + syn region cCppInElse contained start="^\s*\(%:\|#\)\s*\(else\>\|elif\s\+\(0*[1-9]\d*\s*\($\|//\|/\*\||\)\)\@!\)" end=".\@=\|$" containedin=cCppInIf contains=cCppInElse2 fold else - syn region cCppInElse contained start="^\s*\zs\(%:\|#\)\s*\(else\>\|elif\s\+\(0*[1-9]\d*\s*\($\|//\|/\*\||\)\)\@!\)" end=".\@=\|$" containedin=cCppInIf contains=cCppInElse2 + syn region cCppInElse contained start="^\s*\(%:\|#\)\s*\(else\>\|elif\s\+\(0*[1-9]\d*\s*\($\|//\|/\*\||\)\)\@!\)" end=".\@=\|$" containedin=cCppInIf contains=cCppInElse2 endif - syn region cCppInElse2 contained matchgroup=cCppInWrapper start="^\s*\zs\(%:\|#\)\s*\(else\|elif\)\([^/]\|/[^/*]\)*" end="^\s*\zs\(%:\|#\)\s*endif\>"me=s-1 contains=cSpaceError,cCppOutSkip,@Spell - syn region cCppOutSkip contained start="^\s*\zs\(%:\|#\)\s*\(if\>\|ifdef\>\|ifndef\>\)" skip="\\$" end="^\s*\zs\(%:\|#\)\s*endif\>" contains=cSpaceError,cCppOutSkip - syn region cCppInSkip contained matchgroup=cCppInWrapper start="^\s*\zs\(%:\|#\)\s*\(if\s\+\(\d\+\s*\($\|//\|/\*\||\|&\)\)\@!\|ifdef\>\|ifndef\>\)" skip="\\$" end="^\s*\zs\(%:\|#\)\s*endif\>" containedin=cCppOutElse,cCppInIf,cCppInSkip contains=TOP,cPreProc + syn region cCppInElse2 contained matchgroup=cCppInWrapper start="^\s*\(%:\|#\)\s*\(else\|elif\)\([^/]\|/[^/*]\)*" end="^\s*\(%:\|#\)\s*endif\>"me=s-1 contains=cSpaceError,cCppOutSkip,@Spell + syn region cCppOutSkip contained start="^\s*\(%:\|#\)\s*\(if\>\|ifdef\>\|ifndef\>\)" skip="\\$" end="^\s*\(%:\|#\)\s*endif\>" contains=cSpaceError,cCppOutSkip + syn region cCppInSkip contained matchgroup=cCppInWrapper start="^\s*\(%:\|#\)\s*\(if\s\+\(\d\+\s*\($\|//\|/\*\||\|&\)\)\@!\|ifdef\>\|ifndef\>\)" skip="\\$" end="^\s*\(%:\|#\)\s*endif\>" containedin=cCppOutElse,cCppInIf,cCppInSkip contains=TOP,cPreProc endif syn region cIncluded display contained start=+"+ skip=+\\\\\|\\"+ end=+"+ syn match cIncluded display contained "<[^>]*>" diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 7025ee5369..26eea03a3c 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -3,8 +3,8 @@ " ############################################################################# " ############################################################################# " Note: Be careful when merging the upstream version of this file. -" Much of this is generated by scripts/genvimvim.lua (result is installed -" to: $VIMRUNTIME/syntax/vim/generated.vim) +" Much of this is generated by scripts/genvimvim.lua +" (installs to $VIMRUNTIME/syntax/vim/generated.vim) " ############################################################################# " ############################################################################# @@ -128,6 +128,13 @@ if exists("g:vimsyntax_noerror") let g:vimsyn_noerror= g:vimsyntax_noerror endif +" Variable options {{{2 +if exists("g:vim_maxlines") + let s:vimsyn_maxlines= g:vim_maxlines +else + let s:vimsyn_maxlines= 60 +endif + " Numbers {{{2 " ======= syn match vimNumber "\<\d\+\%(\.\d\+\%([eE][+-]\=\d\+\)\=\)\=" skipwhite nextgroup=vimGlobal,vimSubst,vimCommand @@ -601,7 +608,7 @@ syn region vimGlobal matchgroup=Statement start='\<v\%[global]!\=/' skip='\\.' e " g:vimsyn_embed =~# 'r' : embed ruby " g:vimsyn_embed =~# 't' : embed tcl if !exists("g:vimsyn_embed") - let g:vimsyn_embed= "lpPr" + let g:vimsyn_embed= 0 endif " [-- lua --] {{{3 @@ -744,9 +751,10 @@ if !filereadable(s:mzschemepath) endif if g:vimsyn_embed =~# 'm' && filereadable(s:mzschemepath) unlet! b:current_syntax - let iskKeep= &isk + let s:iskKeep= &isk exe "syn include @vimMzSchemeScript ".s:mzschemepath - let &isk= iskKeep + let &isk= s:iskKeep + unlet s:iskKeep VimFoldm syn region vimMzSchemeRegion matchgroup=vimScriptDelim start=+mz\%[scheme]\s*<<\s*\z(.*\)$+ end=+^\z1$+ contains=@vimMzSchemeScript VimFoldm syn region vimMzSchemeRegion matchgroup=vimScriptDelim start=+mz\%[scheme]\s*<<\s*$+ end=+\.$+ contains=@vimMzSchemeScript syn cluster vimFuncBodyList add=vimMzSchemeRegion @@ -761,11 +769,7 @@ unlet s:mzschemepath if exists("g:vimsyn_minlines") exe "syn sync minlines=".g:vimsyn_minlines endif -if exists("g:vimsyn_maxlines") - exe "syn sync maxlines=".g:vimsyn_maxlines -else - syn sync maxlines=60 -endif +exe "syn sync maxlines=".s:vimsyn_maxlines syn sync linecont "^\s\+\\" syn sync match vimAugroupSyncA groupthere NONE "\<aug\%[roup]\>\s\+[eE][nN][dD]" @@ -778,7 +782,7 @@ if !exists("skip_vim_syntax_inits") hi def link vimBehaveError vimError hi def link vimCollClassErr vimError hi def link vimErrSetting vimError - hi def link vimEmbedError vimError + hi def link vimEmbedError Normal hi def link vimFTError vimError hi def link vimFunctionError vimError hi def link vimFunc vimError @@ -935,6 +939,11 @@ if !exists("skip_vim_syntax_inits") hi def link vimUserFunc Normal hi def link vimVar Identifier hi def link vimWarn WarningMsg + + hi def link nvimAutoEvent vimAutoEvent + hi def link nvimHLGroup vimHLGroup + hi def link nvimMap vimMap + hi def link nvimUnmap vimUnmap endif " Current Syntax Variable: {{{2 diff --git a/runtime/tutor/en/vim-01-beginner.tutor b/runtime/tutor/en/vim-01-beginner.tutor index fee7de21d2..6364b201d5 100644 --- a/runtime/tutor/en/vim-01-beginner.tutor +++ b/runtime/tutor/en/vim-01-beginner.tutor @@ -23,7 +23,7 @@ relevant help (hopefully), and pressing K over any word will try to do so too. Sometimes you will be required to modify text like this here Once you have done the changes correctly, the ✗ sign at the left will change -to ✓. I imagine you can already see how neat Vim can be ;) +to ✓. I imagine you can already see how neat Vim can be. ;) Other times, you'll be prompted to run a command (I'll explain this later): ~~~ cmd :help <Enter> @@ -36,30 +36,30 @@ or press a sequence of keys Text within <'s and >'s (like `<Enter>`{normal}) describes a key to press instead of text to type. -Now, move to the next lesson (remember, use j). +Now, move to the next lesson (remember, use the `j`{normal} key to scroll down). ## Lesson 1.1: MOVING THE CURSOR ** To move the cursor, press the `h`, `j`, `k`, `l` keys as indicated. ** ↑ - k Hint: The h key is at the left and moves left. - ← h l → The l key is at the right and moves right. - j The j key looks like a down arrow. + k Hint: The `h`{normal} key is at the left and moves left. + ← h l → The `l`{normal} key is at the right and moves right. + j The `j`{normal} key looks like a down arrow. ↓ 1. Move the cursor around the screen until you are comfortable. - 2. Hold down the down key (j) until it repeats. + 2. Hold down the down key (`j`{normal}) until it repeats. Now you know how to move to the next lesson. 3. Using the down key, move to Lesson 1.2. NOTE: If you are ever unsure about something you typed, press <Esc> to place - you in Normal mode. Then retype the command you wanted. + you in Normal mode. Then retype the command you wanted. -NOTE: The cursor keys should also work. But using hjkl you will be able to - move around much faster, once you get used to it. Really! +NOTE: The cursor keys should also work. But using hjkl you will be able to + move around much faster, once you get used to it. Really! # Lesson 1.2: EXITING VIM @@ -81,7 +81,7 @@ NOTE: The cursor keys should also work. But using hjkl you will be able to 4. If you have these steps memorized and are confident, execute steps 1 through 3 to exit and re-enter the editor. -NOTE: [:q!](:q) <Enter> discards any changes you made. In a few lessons you +NOTE: [:q!](:q) <Enter> discards any changes you made. In a few lessons you will learn how to save the changes to a file. 5. Move the cursor down to Lesson 1.3. @@ -90,18 +90,18 @@ NOTE: [:q!](:q) <Enter> discards any changes you made. In a few lessons you ** Press `x`{normal} to delete the character under the cursor. ** -1. Move the cursor to the line below marked --->. + 1. Move the cursor to the line below marked --->. -2. To fix the errors, move the cursor until it is on top of the - character to be deleted. + 2. To fix the errors, move the cursor until it is on top of the + character to be deleted. -3. Press [the x key](x) to delete the unwanted character. + 3. Press [the x key](x) to delete the unwanted character. -4. Repeat steps 2 through 4 until the sentence is correct. + 4. Repeat steps 2 through 4 until the sentence is correct. The ccow jumpedd ovverr thhe mooon. -5. Now that the line is correct, go on to Lesson 1.4. + 5. Now that the line is correct, go on to Lesson 1.4. NOTE: As you go through this tutor, do not try to memorize, learn by usage. @@ -114,15 +114,15 @@ NOTE: As you go through this tutor, do not try to memorize, learn by usage. 2. To make the first line the same as the second, move the cursor on top of the first character AFTER where the text is to be inserted. - 3. Press i and type in the necessary additions. + 3. Press `i`{normal} and type in the necessary additions. - 4. As each error is fixed press <Esc> to return to Normal mode. + 4. As each error is fixed press `<Esc>`{normal} to return to Normal mode. Repeat steps 2 through 4 to correct the sentence. There is text misng this . There is some text missing from this line. - 5. When you are comfortable inserting text move to lesson 1.5. + 5. When you are comfortable inserting text move to Lesson 1.5. # Lesson 1.5: TEXT EDITING: APPENDING @@ -131,9 +131,9 @@ There is some text missing from this line. 1. Move the cursor to the first line below marked --->. It does not matter on what character the cursor is in that line. - 2. Press [A](A) and type in the necessary additions. + 2. Press [A](A) and type in the necessary additions. - 3. As the text has been appended press <Esc> to return to Normal mode. + 3. As the text has been appended press `<Esc>`{normal} to return to Normal mode. 4. Move the cursor to the second line marked ---> and repeat steps 2 and 3 to correct this sentence. @@ -143,7 +143,7 @@ There is some text missing from this line. There is also some text miss There is also some text missing here. - 5. When you are comfortable appending text move to lesson 1.6. + 5. When you are comfortable appending text move to Lesson 1.6. # Lesson 1.6: EDITING A FILE @@ -151,7 +151,7 @@ There is also some text missing here. !! NOTE: Before executing any of the steps below, read this entire lesson !! - 1. Exit this tutor as you did in lesson 1.2: :q! + 1. Exit this tutor as you did in Lesson 1.2: `:q!`{vim} Or, if you have access to another terminal, do the following there. 2. At the shell prompt type this command: @@ -159,7 +159,7 @@ There is also some text missing here. $ vim tutor ~~~ 'vim' is the command to start the Vim editor, 'tutor' is the name of the - file you wish to edit. Use a file that may be changed. + file you wish to edit. Use a file that may be changed. 3. Insert and delete text as you learned in the previous lessons. @@ -186,14 +186,14 @@ There is also some text missing here. $ vim FILENAME ~~~ - 3. To exit Vim type: <Esc> :q! <Enter> to trash all changes. - OR type: <Esc> :wq <Enter> to save the changes. + 3. To exit Vim type: `<Esc>`{normal} `:q!`{vim} `<Enter>`{normal} to trash all changes. + OR type: `<Esc>`{normal} `:wq`{vim} `<Enter>`{normal} to save the changes. - 4. To delete the character at the cursor type: `x`{normal} + 4. To delete the character at the cursor type: `x`{normal} 5. To insert or append text type: - `i`{normal} type inserted text `<Esc>`{normal} insert before the cursor - `A`{normal} type appended text `<Esc>`{normal} append after the line + `i`{normal} insert text `<Esc>`{normal} insert before the cursor. + `A`{normal} append text `<Esc>`{normal} append after the line. NOTE: Pressing `<Esc>`{normal} will place you in Normal mode or will cancel an unwanted and partially completed command. @@ -210,7 +210,7 @@ Now continue with Lesson 2. 3. Move the cursor to the beginning of a word that needs to be deleted. - 4. Type [d](d)[w](w) to make the word disappear. + 4. Type [d](d)[w](w) to make the word disappear. There are a some words fun that don't belong paper in this sentence. @@ -218,9 +218,9 @@ There are a some words fun that don't belong paper in this sentence. # Lesson 2.2: MORE DELETION COMMANDS -** Type `d$`{normal} to delete to the end of the line. ** +** Type `d$`{normal} to delete to the end of the line. ** - 1. Press <Esc> to make sure you are in Normal mode. + 1. Press `<Esc>`{normal} to make sure you are in Normal mode. 2. Move the cursor to the line below marked --->. @@ -236,7 +236,7 @@ Somebody typed the end of this line twice. end of this line twice. Many commands that change text are made from an [operator](operator) and a [motion](navigation). -The format for a delete command with the [d](d) delete operator is as follows: +The format for a delete command with the [d](d) delete operator is as follows: d motion @@ -249,7 +249,7 @@ The format for a delete command with the [d](d) delete operator is as follows: [e](e) - to the end of the current word, INCLUDING the last character. [$]($) - to the end of the line, INCLUDING the last character. - Thus typing `de`{normal} will delete from the cursor to the end of the word. + Thus typing `de`{normal} will delete from the cursor to the end of the word. NOTE: Pressing just the motion while in Normal mode without an operator will move the cursor as specified. @@ -260,11 +260,11 @@ NOTE: Pressing just the motion while in Normal mode without an operator will 1. Move the cursor to the start of the line marked ---> below. - 2. Type `2w`{normal} to move the cursor two words forward. + 2. Type `2w`{normal} to move the cursor two words forward. - 3. Type `3e`{normal} to move the cursor to the end of the third word forward. + 3. Type `3e`{normal} to move the cursor to the end of the third word forward. - 4. Type `0`{normal} ([zero](0)) to move to the start of the line. + 4. Type `0`{normal} ([zero](0)) to move to the start of the line. 5. Repeat steps 2 and 3 with different numbers. @@ -282,22 +282,22 @@ insert a count before the motion to delete more: 1. Move the cursor to the first UPPER CASE word in the line marked --->. - 2. Type `d2w`{normal} to delete the two UPPER CASE words + 2. Type `d2w`{normal} to delete the two UPPER CASE words 3. Repeat steps 1 and 2 with a different count to delete the consecutive UPPER CASE words with one command -this ABC DE line FGHI JK LMN OP of words is Q RS TUV cleaned up. +This ABC DE line FGHI JK LMN OP of words is Q RS TUV cleaned up. # Lesson 2.6: OPERATING ON LINES -** Type dd to delete a whole line. ** +** Type `dd`{normal} to delete a whole line. ** Due to the frequency of whole line deletion, the designers of Vi decided it would be easier to simply type two d's to delete a line. 1. Move the cursor to the second line in the phrase below. - 2. Type [dd](dd) to delete the line. + 2. Type [dd](dd) to delete the line. 3. Now move to the fourth line. 4. Type `2dd`{normal} to delete two lines. @@ -311,20 +311,20 @@ this ABC DE line FGHI JK LMN OP of words is Q RS TUV cleaned up. # Lesson 2.7: THE UNDO COMMAND -** Press u to undo the last commands, U to fix a whole line. ** +** Press `u`{normal} to undo the last commands, `U`{normal} to fix a whole line. ** 1. Move the cursor to the line below marked ---> and place it on the first error. - 2. Type `x`{normal} to delete the first unwanted character. - 3. Now type `u`{normal} to undo the last command executed. - 4. This time fix all the errors on the line using the `x`{normal} command. - 5. Now type a capital `U`{normal} to return the line to its original state. - 6. Now type `u`{normal} a few times to undo the U and preceding commands. - 7. Now type `<Ctrl-r>`{normal} a few times to redo the commands (undo the undo's). + 2. Type `x`{normal} to delete the first unwanted character. + 3. Now type `u`{normal} to undo the last command executed. + 4. This time fix all the errors on the line using the `x`{normal} command. + 5. Now type a capital `U`{normal} to return the line to its original state. + 6. Now type `u`{normal} a few times to undo the `U`{normal} and preceding commands. + 7. Now type `<Ctrl-r>`{normal} a few times to redo the commands (undo the undo's). Fiix the errors oon thhis line and reeplace them witth undo. - 8. These are very useful commands. Now move on to the Lesson 2 Summary. + 8. These are very useful commands. Now move on to the Lesson 2 Summary. # Lesson 2 SUMMARY @@ -336,13 +336,13 @@ Fiix the errors oon thhis line and reeplace them witth undo. 5. The format for a change command is: operator [number] motion where: - operator - is what to do, such as [d](d) for delete + operator - is what to do, such as [d](d) for delete [number] - is an optional count to repeat the motion motion - moves over the text to operate on, such as: [w](w) (word), [$]($) (to the end of line), etc. - 6. To move to the start of the line use a zero: [0](0) + 6. To move to the start of the line use a zero: [0](0) 7. To undo previous actions, type: `u`{normal} (lowercase u) To undo all the changes on a line, type: `U`{normal} (capital U) @@ -350,15 +350,15 @@ Fiix the errors oon thhis line and reeplace them witth undo. # Lesson 3.1: THE PUT COMMAND -** Type p to put previously deleted text after the cursor. ** +** Type `p`{normal} to put previously deleted text after the cursor. ** 1. Move the cursor to the first ---> line below. - 2. Type `dd`{normal} to delete the line and store it in a Vim register. + 2. Type `dd`{normal} to delete the line and store it in a Vim register. 3. Move the cursor to the c) line, ABOVE where the deleted line should go. - 4. Type `p`{normal} to put the line below the cursor. + 4. Type `p`{normal} to put the line below the cursor. 5. Repeat steps 2 through 4 to put all the lines in correct order. @@ -388,11 +388,11 @@ NOTE: Remember that you should be learning by doing, not memorization. # Lesson 3.3: THE CHANGE OPERATOR -** To change until the end of a word, type `ce`{normal} ** +** To change until the end of a word, type `ce`{normal}. ** 1. Move the cursor to the first line below marked --->. - 2. Place the cursor on the "u" in "lubw". + 2. Place the cursor on the "u" in "lubw". 3. Type `ce`{normal} and the correct word (in this case, type "ine" ). @@ -405,11 +405,11 @@ This line has a few words that need changing using the change operator. Notice that [c](c)e deletes the word and places you in Insert mode. -# Lesson 3.4: MORE CHANGES USING c +# Lesson 3.4: MORE CHANGES USING `c`{normal} ** The change operator is used with the same motions as delete. ** - 1. The change operator works in the same way as delete. The format is: + 1. The change operator works in the same way as delete. The format is: c [number] motion @@ -422,13 +422,13 @@ Notice that [c](c)e deletes the word and places you in Insert mode. 5. Type `c$`{normal} and type the rest of the line like the second and press `<Esc>`{normal}. The end of this line needs some help to make it like the second. -The end of this line needs to be corrected using the c$ command. +The end of this line needs to be corrected using the `c$`{normal} command. -NOTE: You can use the Backspace key to correct mistakes while typing. +NOTE: You can use the Backspace key to correct mistakes while typing. # Lesson 3 SUMMARY - 1. To put back text that has just been deleted, type [p](p). This puts the + 1. To put back text that has just been deleted, type [p](p). This puts the deleted text AFTER the cursor (if a line was deleted it will go on the line below the cursor). @@ -436,8 +436,8 @@ NOTE: You can use the Backspace key to correct mistakes while typing. character you want to have there. 3. The [change operator](c) allows you to change from the cursor to where the - motion takes you. eg. Type `ce`{normal} to change from the cursor to the end of - the word, `c$`{normal} to change to the end of a line. + motion takes you. Type `ce`{normal} to change from the cursor to the end of + the word, `c$`{normal} to change to the end of a line. 4. The format for change is: @@ -448,21 +448,21 @@ Now go on to the next lesson. # Lesson 4.1: CURSOR LOCATION AND FILE STATUS ** Type `<Ctrl-g>`{normal} to show your location in the file and the file status. - Type `G`{normal} to move to a line in the file. ** + Type `G`{normal} to move to a line in the file. ** NOTE: Read this entire lesson before executing any of the steps!! - 1. Hold down the `<Ctrl>`{normal} key and press `g`{normal}. We call this `<Ctrl-g>`{normal}. + 1. Hold down the `<Ctrl>`{normal} key and press `g`{normal}. We call this `<Ctrl-g>`{normal}. A message will appear at the bottom of the page with the filename and the - position in the file. Remember the line number for Step 3. + position in the file. Remember the line number for Step 3. -NOTE: You may see the cursor position in the lower right corner of the screen - This happens when the ['ruler']('ruler') option is set (see :help 'ruler' ) +NOTE: You may see the cursor position in the lower right corner of the screen + This happens when the ['ruler']('ruler') option is set (see `:help 'ruler'`{vim} ). - 2. Press [G](G) to move you to the bottom of the file. - Type [gg](gg) to move you to the start of the file. + 2. Press [G](G) to move you to the bottom of the file. + Type [gg](gg) to move you to the start of the file. - 3. Type the number of the line you were on and then `G`{normal} . This will + 3. Type the number of the line you were on and then `G`{normal}. This will return you to the line you were on when you first pressed `<Ctrl-g>`{normal}. 4. If you feel confident to do this, execute steps 1 through 3. @@ -471,20 +471,20 @@ NOTE: You may see the cursor position in the lower right corner of the screen ** Type `/`{normal} followed by a phrase to search for the phrase. ** - 1. In Normal mode type the `/`{normal} character. Notice that it and the cursor - appear at the bottom of the screen as with the : command. + 1. In Normal mode type the `/`{normal} character. Notice that it and the cursor + appear at the bottom of the screen as with the `:`{normal} command. - 2. Now type 'errroor' `<Enter>`{normal}. This is the word you want to search for. + 2. Now type 'errroor' `<Enter>`{normal}. This is the word you want to search for. - 3. To search for the same phrase again, simply type [n](n) . - To search for the same phrase in the opposite direction, type [N](N) . + 3. To search for the same phrase again, simply type [n](n). + To search for the same phrase in the opposite direction, type [N](N). - 4. To search for a phrase in the backward direction, use [?](?) instead of / . + 4. To search for a phrase in the backward direction, use [?](?) instead of `/`{normal}. - 5. To go back to where you came from press `<Ctrl-o>`{normal} (Keep Ctrl down while - pressing the letter o). Repeat to go back further. `<Ctrl-i>`{normal} goes forward. + 5. To go back to where you came from press `<Ctrl-o>`{normal} (keep `<Ctrl>`{normal} pressed down while + pressing the letter `o`{normal}). Repeat to go back further. `<Ctrl-i>`{normal} goes forward. -"errroor" is not the way to spell error; errroor is an error. +"errroor" is not the way to spell error; errroor is an error. NOTE: When the search reaches the end of the file it will continue at the start, unless the ['wrapscan']('wrapscan') option has been reset. @@ -495,7 +495,7 @@ NOTE: When the search reaches the end of the file it will continue at the 1. Place the cursor on any (, [, or { in the line below marked --->. - 2. Now type the [%](%) character. + 2. Now type the [%](%) character. 3. The cursor will move to the matching parenthesis or bracket. @@ -528,7 +528,7 @@ NOTE: This is very useful in debugging a program with unmatched parentheses! Adding the g [flag](:s_flags) means to substitute globally in the line, change all occurrences of "thee" in the line. -thee best time to see thee flowers is in thee spring. +Usually thee best time to see thee flowers is in thee spring. 4. To change every occurrence of a character string between two lines, type ~~~ cmd @@ -589,20 +589,20 @@ thee best time to see thee flowers is in thee spring. ** Type `:!`{vim} followed by an external command to execute that command. ** 1. Type the familiar command `:`{normal} to set the cursor at the bottom of the - screen. This allows you to enter a command-line command. + screen. This allows you to enter a command-line command. - 2. Now type the [!](!cmd) (exclamation point) character. This allows you to + 2. Now type the [!](!cmd) (exclamation point) character. This allows you to execute any external shell command. - 3. As an example type "ls" following the "!" and then hit `<Enter>`{normal}. This + 3. As an example type "ls" following the "!" and then hit `<Enter>`{normal}. This will show you a listing of your directory, just as if you were at the shell prompt. -NOTE: It is possible to execute any external command this way, also with - arguments. +NOTE: It is possible to execute any external command this way, also with + arguments. -NOTE: All : commands must be finished by hitting <Enter> - From here on we will not always mention it. +NOTE: All `:`{vim} commands must be finished by hitting `<Enter>`{normal}. + From here on we will not always mention it. # Lesson 5.2: MORE ON WRITING FILES @@ -622,7 +622,7 @@ NOTE: All : commands must be finished by hitting <Enter> 4. This saves the whole file (the Vim Tutor) under the name TEST. To verify this, type `:!ls`{vim} again to see your directory. -NOTE: If you were to exit Vim and start it again with vim TEST , the file +NOTE: If you were to exit Vim and start it again with `nvim TEST`, the file would be an exact copy of the tutor when you saved it. 5. Now remove the file by typing: @@ -632,14 +632,14 @@ NOTE: If you were to exit Vim and start it again with vim TEST , the file # Lesson 5.3: SELECTING TEXT TO WRITE -** To save part of the file, type `v`{normal} motion `:w FILENAME`{vim} ** +** To save part of the file, type `v`{normal} motion `:w FILENAME`{vim}. ** 1. Move the cursor to this line. - 2. Press [v](v) and move the cursor to the fifth item below. Notice that the + 2. Press [v](v) and move the cursor to the fifth item below. Notice that the text is highlighted. - 3. Press the `:`{normal} character. At the bottom of the screen + 3. Press the `:`{normal} character. At the bottom of the screen :'<,'> @@ -649,27 +649,27 @@ NOTE: If you were to exit Vim and start it again with vim TEST , the file `:w TEST`{vim} - where TEST is a filename that does not exist yet. Verify that you see + where TEST is a filename that does not exist yet. Verify that you see `:'<,'>w TEST`{vim} before you press `<Enter>`{normal}. - 5. Vim will write the selected lines to the file TEST. Use `:!ls`{vim} to see it. - Do not remove it yet! We will use it in the next lesson. + 5. Vim will write the selected lines to the file TEST. Use `:!ls`{vim} to see it. + Do not remove it yet! We will use it in the next lesson. -NOTE: Pressing [v](v) starts [Visual selection](visual-mode). You can move - the cursor around to make the selection bigger or smaller. Then you can - use an operator to do something with the text. For example, `d`{normal} deletes - the text. +NOTE: Pressing [v](v) starts [Visual selection](visual-mode). You can move + the cursor around to make the selection bigger or smaller. Then you can + use an operator to do something with the text. For example, `d`{normal} deletes + the text. # Lesson 5.4: RETRIEVING AND MERGING FILES -** To insert the contents of a file, type `:r FILENAME`{vim} ** +** To insert the contents of a file, type `:r FILENAME`{vim}. ** 1. Place the cursor just above this line. -NOTE: After executing Step 2 you will see text from Lesson 5.3. Then move +NOTE: After executing Step 2 you will see text from Lesson 5.3. Then move DOWN to see this lesson again. 2. Now retrieve your TEST file using the command @@ -682,31 +682,31 @@ NOTE: After executing Step 2 you will see text from Lesson 5.3. Then move 3. To verify that a file was retrieved, cursor back and notice that there are now two copies of Lesson 5.3, the original and the file version. -NOTE: You can also read the output of an external command. For example, +NOTE: You can also read the output of an external command. For example, - `:r !ls`{vim} + `:r !ls`{vim} - reads the output of the `ls` command and puts it below the cursor. + reads the output of the `ls` command and puts it below the cursor. # Lesson 5 SUMMARY - 1. [:!command](:!cmd) executes an external command. + 1. [:!command](:!cmd) executes an external command. - Some useful examples are: - `:!ls`{vim} - shows a directory listing - `:!rm FILENAME`{vim} - removes file FILENAME + Some useful examples are: + `:!ls`{vim} - shows a directory listing + `:!rm FILENAME`{vim} - removes file FILENAME - 2. [:w](:w) FILENAME writes the current Vim file to disk with - name FILENAME. + 2. [:w](:w) FILENAME writes the current Vim file to disk with + name FILENAME. - 3. [v](v) motion :w FILENAME saves the Visually selected lines in file - FILENAME. + 3. [v](v) motion :w FILENAME saves the Visually selected lines in file + FILENAME. - 4. [:r](:r) FILENAME retrieves disk file FILENAME and puts it - below the cursor position. + 4. [:r](:r) FILENAME retrieves disk file FILENAME and puts it + below the cursor position. - 5. [:r !dir](:r!) reads the output of the dir command and - puts it below the cursor position. + 5. [:r !dir](:r!) reads the output of the dir command and + puts it below the cursor position. # Lesson 6.1: THE OPEN COMMAND @@ -719,10 +719,10 @@ NOTE: You can also read the output of an external command. For example, 3. Now type some text and press `<Esc>`{normal} to exit Insert mode. -After typing o the cursor is placed on the open line in Insert mode. +After typing `o`{normal} the cursor is placed on the open line in Insert mode. 4. To open up a line ABOVE the cursor, simply type a [capital O](O), rather - than a lowercase `o`{normal}. Try this on the line below. + than a lowercase `o`{normal}. Try this on the line below. Open up a line above this by typing O while the cursor is on this line. @@ -734,9 +734,9 @@ Open up a line above this by typing O while the cursor is on this line. 2. Press `e`{normal} until the cursor is on the end of "li". - 3. Type an `a`{normal} (lowercase) to [append](a) text AFTER the cursor. + 3. Type the lowercase letter `a`{normal} to [append](a) text AFTER the cursor. - 4. Complete the word like the line below it. Press `<Esc>`{normal} to exit Insert + 4. Complete the word like the line below it. Press `<Esc>`{normal} to exit Insert mode. 5. Use `e`{normal} to move to the next incomplete word and repeat steps 3 and 4. @@ -744,20 +744,20 @@ Open up a line above this by typing O while the cursor is on this line. This li will allow you to pract appendi text to a line. This line will allow you to practice appending text to a line. -NOTE: [a](a), [i](i) and [A](A) all go to the same Insert mode, the only difference is where - the characters are inserted. +NOTE: [a](a), [i](i) and [A](A) all go to the same Insert mode, the only difference is where + the characters are inserted. # Lesson 6.3: ANOTHER WAY TO REPLACE ** Type a capital `R`{normal} to replace more than one character. ** - 1. Move the cursor to the first line below marked --->. Move the cursor to + 1. Move the cursor to the first line below marked --->. Move the cursor to the beginning of the first "xxx". 2. Now press `R`{normal} ([capital R](R)) and type the number below it in the second line, so that it replaces the "xxx". - 3. Press `<Esc>`{normal} to leave [Replace mode](mode-replace). Notice that the rest of the line + 3. Press `<Esc>`{normal} to leave [Replace mode](mode-replace). Notice that the rest of the line remains unmodified. 4. Repeat the steps to replace the remaining "xxx". @@ -765,12 +765,12 @@ NOTE: [a](a), [i](i) and [A](A) all go to the same Insert mode, the only differ Adding 123 to xxx gives you xxx. Adding 123 to 456 gives you 579. -NOTE: Replace mode is like Insert mode, but every typed character deletes an - existing character. +NOTE: Replace mode is like Insert mode, but every typed character deletes an + existing character. # Lesson 6.4: COPY AND PASTE TEXT -** Use the `y`{normal} operator to copy text and `p`{normal} to paste it ** +** Use the `y`{normal} operator to copy text and `p`{normal} to paste it. ** 1. Go to the line marked with ---> below and place the cursor after "a)". @@ -780,35 +780,37 @@ NOTE: Replace mode is like Insert mode, but every typed character deletes an 4. Move the cursor to the end of the next line: `j$`{normal} - 5. Type `p`{normal} to [put](put) (paste) the text. Then type: "a second"`<Esc>`{normal}. + 5. Type `p`{normal} to [put](put) (paste) the text. + + 6. Press `a`{normal} and then type "second". Press `<Esc>`{normal} to leave Insert mode. - 6. Use Visual mode to select " item.", yank it with `y`{normal}, move to the end of + 7. Use Visual mode to select " item.", yank it with `y`{normal}, move to the end of the next line with `j$`{normal} and put the text there with `p`{normal}. -a) this is the first item. - b) +a) This is the first item. + b) - NOTE: you can also use `y`{normal} as an operator; `yw`{normal} yanks one word. +NOTE: you can also use `y`{normal} as an operator; `yw`{normal} yanks one word. # Lesson 6.5: SET OPTION -** Set an option so a search or substitute ignores case ** +** Set an option so a search or substitute ignores case. ** - 1. Search for 'ignore' by entering: `/ignore` + 1. Search for 'ignore' by entering: `/ignore` Repeat several times by pressing `n`{normal}. 2. Set the 'ic' (Ignore case) option by entering: ~~~ cmd :set ic ~~~ - 3. Now search for 'ignore' again by pressing n + 3. Now search for 'ignore' again by pressing `n`{normal}. Notice that Ignore and IGNORE are now also found. 4. Set the 'hlsearch' and 'incsearch' options: ~~~ cmd :set hls is ~~~ - 5. Now type the search command again and see what happens: /ignore <Enter> + 5. Now type the search command again and see what happens: /ignore <Enter> 6. To disable ignoring case enter: ~~~ cmd @@ -818,12 +820,12 @@ a) this is the first item. ~~~ cmd :set invic ~~~ -NOTE: To remove the highlighting of matches enter: +NOTE: To remove the highlighting of matches enter: ~~~ cmd :nohlsearch ~~~ -NOTE: If you want to ignore case for just one search command, use [\c](/\c) - in the phrase: /ignore\c <Enter> +NOTE: If you want to ignore case for just one search command, use [\c](/\c) + in the phrase: /ignore\c <Enter> # Lesson 6 SUMMARY @@ -839,7 +841,7 @@ NOTE: If you want to ignore case for just one search command, use [\c](/\c) 5. Typing a capital `R`{normal} enters Replace mode until `<Esc>`{normal} is pressed. - 6. Typing "[:set](:set) xxx" sets the option "xxx". Some options are: + 6. Typing "[:set](:set) xxx" sets the option "xxx". Some options are: 'ic' 'ignorecase' ignore upper/lower case when searching 'is' 'incsearch' show partial matches for a search phrase @@ -858,9 +860,9 @@ NOTE: If you want to ignore case for just one search command, use [\c](/\c) # Lesson 7.1: GETTING HELP -** Use the on-line help system ** +** Use the on-line help system. ** -Vim has a comprehensive on-line help system. To get started, try one of +Vim has a comprehensive on-line help system. To get started, try one of these three: - press the `<HELP>`{normal} key (if you have one) - press the `<F1>`{normal} key (if you have one) @@ -872,7 +874,7 @@ Type `<Ctrl-w><Ctrl-w>`{normal} to jump from one window to another. Type `:q`{vim} to close the help window. You can find help on just about any subject, by giving an argument to the -":help" command. Try these (don't forget pressing <Enter>): +":help" command. Try these (don't forget pressing <Enter>): ~~~ cmd :help w :help c_CTRL-D @@ -881,13 +883,13 @@ You can find help on just about any subject, by giving an argument to the ~~~ # Lesson 7.2: CREATE A STARTUP SCRIPT -** Enable Vim features ** +** Enable Vim features. ** Vim has many more features than Vi, but most of them are disabled by -default. To start using more features you have to create a "vimrc" file. +default. To start using more features you have to create a "vimrc" file. - 1. Start editing the "vimrc" file. This depends on your system: - `:e ~/.config/nvim/init.vim`{vim} for Unix-like systems + 1. Start editing the "vimrc" file. This depends on your system: + `:e ~/.config/nvim/init.vim`{vim} for Unix-like systems 2. Now read the example "vimrc" file contents: `:r $VIMRUNTIME/vimrc_example.vim`{vim} @@ -897,15 +899,15 @@ default. To start using more features you have to create a "vimrc" file. The next time you start Vim it will use syntax highlighting. You can add all your preferred settings to this "vimrc" file. - For more information type :help vimrc-intro + For more information type `:help vimrc-intro`{vim}. # Lesson 7.3: COMPLETION -** Command line completion with `<Ctrl-d>`{normal} and `<Tab>`{normal} ** +** Command line completion with `<Ctrl-d>`{normal} and `<Tab>`{normal}. ** - 1. Look what files exist in the directory: `:!ls`{vim} + 1. Look what files exist in the directory: `:!ls`{vim} - 2. Type the start of a command: `:e`{vim} + 2. Type the start of a command: `:e`{vim} 3. Press `<Ctrl-d>`{normal} and Vim will show a list of commands that start with "e". @@ -913,20 +915,20 @@ default. To start using more features you have to create a "vimrc" file. 5. Now add a space and the start of an existing file name: `:edit FIL`{vim} - 6. Press `<Tab>`{normal}. Vim will complete the name (if it is unique). + 6. Press `<Tab>`{normal}. Vim will complete the name (if it is unique). -NOTE: Completion works for many commands. It is especially useful for `:help`{vim}. +NOTE: Completion works for many commands. It is especially useful for `:help`{vim}. # Lesson 7 SUMMARY 1. Type `:help`{vim} or press `<F1>`{normal} or `<Help>`{normal} to open a help window. - 2. Type `:help TOPIC`{vim} to find help on TOPIC. + 2. Type `:help TOPIC`{vim} to find help on TOPIC. - 3. Type `<Ctrl-w><Ctrl-w>`{normal} to jump to another window + 3. Type `<Ctrl-w><Ctrl-w>`{normal} to jump to another window - 4. Type `:q`{vim} to close the help window + 4. Type `:q`{vim} to close the help window 5. Create a vimrc startup script to keep your preferred settings. @@ -937,24 +939,24 @@ NOTE: Completion works for many commands. It is especially useful for `:help`{v This was intended to give a brief overview of the Vim editor, just enough to allow you to use the editor fairly easily. It is far from complete as Vim has -many many more commands. Consult the help often. +many many more commands. Consult the help often. There are many resources online to learn more about vim. Here's a bunch of them: - *Learn Vim Progressively*: http://yannesposito.com/Scratch/en/blog/Learn-Vim-Progressively/ - *Learning Vim in 2014*: http://benmccormick.org/learning-vim-in-2014/ -- Vimcasts: http://vimcasts.org/ -- Vim Video-Tutorials by Derek Wyatt: http://derekwyatt.org/vim/tutorials/ +- *Vimcasts*: http://vimcasts.org/ +- *Vim Video-Tutorials by Derek Wyatt*: http://derekwyatt.org/vim/tutorials/ - *Learn Vimscript the Hard Way*: http://learnvimscriptthehardway.stevelosh.com/ - *7 Habits of Effective Text Editing*: http://www.moolenaar.net/habits.html - *vim-galore*: https://github.com/mhinz/vim-galore If you prefer a book, *Practical Vim* by Drew Neil is recommended often (the sequel, *Modern -Vim*, includes material specific to nvim!). +Vim*, includes material specific to nvim). This tutorial was written by Michael C. Pierce and Robert K. Ware, Colorado School of Mines using ideas supplied by Charles Smith, Colorado State -University. E-mail: bware@mines.colorado.edu. +University. E-mail: bware@mines.colorado.edu. Modified for Vim by Bram Moolenaar. Modified for vim-tutor-mode by Felipe Morales. diff --git a/runtime/tutor/en/vim-01-beginner.tutor.json b/runtime/tutor/en/vim-01-beginner.tutor.json index 3f55971a09..444bd7c4b7 100644 --- a/runtime/tutor/en/vim-01-beginner.tutor.json +++ b/runtime/tutor/en/vim-01-beginner.tutor.json @@ -11,7 +11,7 @@ "215": "There are some words that don't belong in this sentence.", "231": "Somebody typed the end of this line twice.", "271": -1, - "290": "this line of words is cleaned up.", + "290": "This line of words is cleaned up.", "304": -1, "305": -1, "306": -1, @@ -32,14 +32,14 @@ "425": "The end of this line needs to be corrected using the c$ command.", "487": -1, "506": -1, - "531": "the best time to see the flowers is in the spring.", + "531": "Usually the best time to see the flowers is in the spring.", "722": -1, "727": -1, "744": "This line will allow you to practice appending text to a line.", "745": "This line will allow you to practice appending text to a line.", "765": "Adding 123 to 456 gives you 579.", "766": "Adding 123 to 456 gives you 579.", - "788": "a) this is the first item.", - "789": " b) this is the second item." + "790": "a) This is the first item.", + "791": " b) This is the second item." } } diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index 5ebb6a38b3..4f6bb40488 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -133,7 +133,7 @@ preprocess_patch() { # Remove *.proto, Make*, gui_*, some if_* local na_src='proto\|Make*\|gui_*\|if_lua\|if_mzsch\|if_olepp\|if_ole\|if_perl\|if_py\|if_ruby\|if_tcl\|if_xcmdsrv' - 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/\S*\<\%('${na_src}'\)@norm! d/\v(^diff)|%$
' +w +q "$file" + 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/\S*\<\%(testdir/\)\@<!\%('${na_src}'\)@norm! d/\v(^diff)|%$
' +w +q "$file" # Remove channel.txt, netbeans.txt, os_*.txt, term.txt, todo.txt, version*.txt, tags local na_doc='channel\.txt\|netbeans\.txt\|os_\w\+\.txt\|term\.txt\|todo\.txt\|version\d\.txt\|tags' diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index c46c0bed6d..a166ee6c02 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -10,6 +10,11 @@ if(USE_GCOV) endif() endif() +if(WIN32) + # tell MinGW compiler to enable wmain + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -municode") +endif() + set(TOUCHES_DIR ${PROJECT_BINARY_DIR}/touches) set(GENERATOR_DIR ${CMAKE_CURRENT_LIST_DIR}/generators) set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto) @@ -111,6 +116,9 @@ foreach(sfile ${NVIM_SOURCES}) if(WIN32 AND ${f} MATCHES "^(pty_process_unix.c)$") list(APPEND to_remove ${sfile}) endif() + if(NOT WIN32 AND ${f} MATCHES "^(pty_process_win.c)$") + list(APPEND to_remove ${sfile}) + endif() endforeach() list(REMOVE_ITEM NVIM_SOURCES ${to_remove}) @@ -156,7 +164,7 @@ if(NOT MSVC) endif() endif() -if(DEFINED MIN_LOG_LEVEL) +if(NOT "${MIN_LOG_LEVEL}" MATCHES "^$") add_definitions(-DMIN_LOG_LEVEL=${MIN_LOG_LEVEL}) endif() @@ -350,6 +358,10 @@ if(Iconv_LIBRARIES) list(APPEND NVIM_LINK_LIBRARIES ${Iconv_LIBRARIES}) endif() +if(WIN32) + list(APPEND NVIM_LINK_LIBRARIES ${WINPTY_LIBRARIES}) +endif() + # Put these last on the link line, since multiple things may depend on them. list(APPEND NVIM_LINK_LIBRARIES ${LIBUV_LIBRARIES} @@ -415,6 +427,7 @@ if(WIN32) COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/tee.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/tidy.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/win32yank.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/winpty-agent.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/ @@ -428,6 +441,7 @@ if(WIN32) COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/Qt5Network.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/Qt5Svg.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/Qt5Widgets.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/winpty.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/platforms/qwindows.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/platforms/ ) @@ -526,11 +540,7 @@ endfunction() set(NO_SINGLE_CHECK_HEADERS os/win_defs.h - regexp_defs.h - syntax_defs.h - terminal.h - undo.h - undo_defs.h + os/pty_process_win.h ) foreach(hfile ${NVIM_HEADERS}) get_test_target(test-includes "${hfile}" relative_path texe) diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 82de8fd4a2..c381e92dc7 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -793,7 +793,11 @@ Integer nvim_buf_add_highlight(Buffer buffer, col_end = MAXCOL; } - int hlg_id = syn_name2id((char_u *)(hl_group.data ? hl_group.data : "")); + int hlg_id = 0; + if (hl_group.size > 0) { + hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size); + } + src_id = bufhl_add_hl(buf, (int)src_id, hlg_id, (linenr_T)line+1, (colnr_T)col_start+1, (colnr_T)col_end); return src_id; diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 1ed2bc013e..e736e29e2d 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -37,7 +37,72 @@ typedef struct { # include "api/private/ui_events_metadata.generated.h" #endif +/// Start block that may cause VimL exceptions while evaluating another code +/// +/// Used when caller is supposed to be operating when other VimL code is being +/// processed and that “other VimL code” must not be affected. +/// +/// @param[out] tstate Location where try state should be saved. +void try_enter(TryState *const tstate) +{ + *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, + }; + msg_list = &tstate->private_msg_list; + current_exception = NULL; + trylevel = 1; + got_int = false; + did_throw = false; + need_rethrow = false; + did_emsg = false; +} + +/// End try block, set the error message if any and restore previous state +/// +/// @warning Return is consistent with most functions (false on error), not with +/// try_end (true on error). +/// +/// @param[in] tstate Previous state to restore. +/// @param[out] err Location where error should be saved. +/// +/// @return false if error occurred, true otherwise. +bool try_leave(const TryState *const tstate, Error *const err) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + const bool ret = !try_end(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); + assert(current_exception == NULL); + msg_list = (struct msglist **)tstate->msg_list; + 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; +} + /// Start block that may cause vimscript exceptions +/// +/// Each try_start() call should be mirrored by try_end() call. +/// +/// To be used as a replacement of `:try … catch … endtry` in C code, in cases +/// when error flag could not already be set. If there may be pending error +/// state at the time try_start() is executed which needs to be preserved, +/// try_enter()/try_leave() pair should be used instead. void try_start(void) { ++trylevel; @@ -50,7 +115,9 @@ void try_start(void) /// @return true if an error occurred bool try_end(Error *err) { - --trylevel; + // Note: all globals manipulated here should be saved/restored in + // 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 diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 159b9d5c2a..87f334ac30 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -6,6 +6,7 @@ #include "nvim/api/private/defs.h" #include "nvim/vim.h" #include "nvim/memory.h" +#include "nvim/ex_eval.h" #include "nvim/lib/kvec.h" #define OBJECT_OBJ(o) o @@ -82,6 +83,21 @@ #define api_free_window(value) #define api_free_tabpage(value) +/// Structure used for saving state for :try +/// +/// Used when caller is supposed to be operating when other VimL code is being +/// processed and that “other VimL code” must not be affected. +typedef struct { + except_T *current_exception; + struct msglist *private_msg_list; + const struct msglist *const *msg_list; + int trylevel; + int got_int; + int did_throw; + int need_rethrow; + int did_emsg; +} TryState; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/helpers.h.generated.h" #endif diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 573be23d8e..afbee09c1c 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -215,6 +215,7 @@ static void ui_set_option(UI *ui, String name, Object value, Error *error) #undef UI_EXT_OPTION } +/// Pushes data into UI.UIData, to be consumed later by remote_ui_flush(). static void push_call(UI *ui, char *name, Array args) { Array call = ARRAY_DICT_INIT; @@ -241,39 +242,7 @@ static void push_call(UI *ui, char *name, Array args) static void remote_ui_highlight_set(UI *ui, HlAttrs attrs) { Array args = ARRAY_DICT_INIT; - Dictionary hl = ARRAY_DICT_INIT; - - if (attrs.bold) { - PUT(hl, "bold", BOOLEAN_OBJ(true)); - } - - if (attrs.underline) { - PUT(hl, "underline", BOOLEAN_OBJ(true)); - } - - if (attrs.undercurl) { - PUT(hl, "undercurl", BOOLEAN_OBJ(true)); - } - - if (attrs.italic) { - PUT(hl, "italic", BOOLEAN_OBJ(true)); - } - - if (attrs.reverse) { - PUT(hl, "reverse", BOOLEAN_OBJ(true)); - } - - if (attrs.foreground != -1) { - PUT(hl, "foreground", INTEGER_OBJ(attrs.foreground)); - } - - if (attrs.background != -1) { - PUT(hl, "background", INTEGER_OBJ(attrs.background)); - } - - if (attrs.special != -1) { - PUT(hl, "special", INTEGER_OBJ(attrs.special)); - } + Dictionary hl = hlattrs2dict(attrs); ADD(args, DICTIONARY_OBJ(hl)); push_call(ui, "highlight_set", args); diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 2bc31b2812..98f4410347 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -55,6 +55,47 @@ void nvim_command(String command, Error *err) try_end(err); } +/// Gets a highlight definition by name. +/// +/// @param name Highlight group name +/// @param rgb Export RGB colors +/// @param[out] err Error details, if any +/// @return Highlight definition map +/// @see nvim_get_hl_by_id +Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Error *err) + FUNC_API_SINCE(3) +{ + Dictionary result = ARRAY_DICT_INIT; + int id = syn_name2id((const char_u *)name.data); + + if (id == 0) { + api_set_error(err, kErrorTypeException, "Invalid highlight name: %s", + name.data); + return result; + } + result = nvim_get_hl_by_id(id, rgb, err); + return result; +} + +/// Gets a highlight definition by id. |hlID()| +/// +/// @param hl_id Highlight id as returned by |hlID()| +/// @param rgb Export RGB colors +/// @param[out] err Error details, if any +/// @return Highlight definition map +/// @see nvim_get_hl_by_name +Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Error *err) + FUNC_API_SINCE(3) +{ + Dictionary dic = ARRAY_DICT_INIT; + if (syn_get_final_id((int)hl_id) == 0) { + api_set_error(err, kErrorTypeException, "Invalid highlight id: %d", hl_id); + return dic; + } + int attrcode = syn_id2attr((int)hl_id); + return hl_get_attr_by_id(attrcode, rgb, err); +} + /// Passes input keys to Nvim. /// On VimL error: Does not fail, but updates v:errmsg. /// @@ -255,12 +296,11 @@ free_vim_args: return rv; } -/// Execute lua code. Parameters might be passed, they are available inside -/// the chunk as `...`. The chunk can return a value. +/// Execute lua code. Parameters (if any) are available as `...` inside the +/// chunk. The chunk can return a value. /// -/// To evaluate an expression, it must be prefixed with "return ". For -/// instance, to call a lua function with arguments sent in and get its -/// return value back, use the code "return my_function(...)". +/// Only statements are executed. To evaluate an expression, prefix it +/// with `return`: return my_function(...) /// /// @param code lua code to execute /// @param args Arguments to the code @@ -423,29 +463,18 @@ void nvim_del_var(String name, Error *err) dict_set_var(&globvardict, name, NIL, true, false, err); } -/// Sets a global variable -/// /// @deprecated -/// -/// @param name Variable name -/// @param value Variable value -/// @param[out] err Error details, if any +/// @see nvim_set_var /// @return Old value or nil if there was no previous value. -/// -/// @warning It may return nil if there was no previous value -/// or if previous value was `v:null`. +/// @warning May return nil if there was no previous value +/// OR if previous value was `v:null`. Object vim_set_var(String name, Object value, Error *err) { return dict_set_var(&globvardict, name, value, false, true, err); } -/// Removes a global variable -/// /// @deprecated -/// -/// @param name Variable name -/// @param[out] err Error details, if any -/// @return Old value +/// @see nvim_del_var Object vim_del_var(String name, Error *err) { return dict_set_var(&globvardict, name, NIL, true, true, err); @@ -484,7 +513,8 @@ void nvim_set_option(String name, Object value, Error *err) set_option_to(NULL, SREQ_GLOBAL, name, value, err); } -/// Writes a message to vim output buffer +/// Writes a message to the Vim output buffer. Does not append "\n", the +/// message is buffered (won't display) until a linefeed is written. /// /// @param str Message void nvim_out_write(String str) @@ -493,7 +523,8 @@ void nvim_out_write(String str) write_msg(str, false); } -/// Writes a message to vim error buffer +/// Writes a message to the Vim error buffer. Does not append "\n", the +/// message is buffered (won't display) until a linefeed is written. /// /// @param str Message void nvim_err_write(String str) @@ -502,8 +533,8 @@ void nvim_err_write(String str) write_msg(str, true); } -/// Writes a message to vim error buffer. Appends a linefeed to ensure all -/// contents are written. +/// Writes a message to the Vim error buffer. Appends "\n", so the buffer is +/// flushed (and displayed). /// /// @param str Message /// @see nvim_err_write() diff --git a/src/nvim/aucmd.c b/src/nvim/aucmd.c new file mode 100644 index 0000000000..fc421116ea --- /dev/null +++ b/src/nvim/aucmd.c @@ -0,0 +1,41 @@ +// 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 "nvim/os/os.h" +#include "nvim/fileio.h" +#include "nvim/vim.h" +#include "nvim/main.h" +#include "nvim/ui.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "aucmd.c.generated.h" +#endif + +static void focusgained_event(void **argv) +{ + bool *gainedp = argv[0]; + do_autocmd_focusgained(*gainedp); + xfree(gainedp); +} +void aucmd_schedule_focusgained(bool gained) +{ + bool *gainedp = xmalloc(sizeof(*gainedp)); + *gainedp = gained; + loop_schedule_deferred(&main_loop, + event_create(focusgained_event, 1, gainedp)); +} + +static void do_autocmd_focusgained(bool gained) + FUNC_ATTR_NONNULL_ALL +{ + static bool recursive = false; + + if (recursive) { + return; // disallow recursion + } + recursive = true; + apply_autocmds((gained ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST), + NULL, NULL, false, curbuf); + recursive = false; +} + diff --git a/src/nvim/aucmd.h b/src/nvim/aucmd.h new file mode 100644 index 0000000000..6570ba7a92 --- /dev/null +++ b/src/nvim/aucmd.h @@ -0,0 +1,9 @@ +#ifndef NVIM_AUCMD_H +#define NVIM_AUCMD_H + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "aucmd.h.generated.h" +#endif + +#endif // NVIM_AUCMD_H + diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 724a8578ac..fc5bb90973 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -3069,8 +3069,8 @@ static bool ti_change(char_u *str, char_u **last) /// Set current window title void resettitle(void) { - ui_call_set_title(cstr_as_string((char *)lasttitle)); ui_call_set_icon(cstr_as_string((char *)lasticon)); + ui_call_set_title(cstr_as_string((char *)lasttitle)); ui_flush(); } diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 403ef65c4f..577fc13a31 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -762,7 +762,7 @@ bool vim_isIDc(int c) } /// Check that "c" is a keyword character: -/// Letters and characters from 'iskeyword' option for current buffer. +/// Letters and characters from 'iskeyword' option for the current buffer. /// For multi-byte characters mb_get_class() is used (builtin rules). /// /// @param c character to check diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index 60002f3cea..0e97e2203f 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -375,17 +375,30 @@ void check_cursor_col_win(win_T *win) win->w_cursor.col = 0; } - /* If virtual editing is on, we can leave the cursor on the old position, - * only we must set it to virtual. But don't do it when at the end of the - * line. */ - if (oldcol == MAXCOL) + // If virtual editing is on, we can leave the cursor on the old position, + // only we must set it to virtual. But don't do it when at the end of the + // line. + if (oldcol == MAXCOL) { win->w_cursor.coladd = 0; - else if (ve_flags == VE_ALL) { - if (oldcoladd > win->w_cursor.col) + } else if (ve_flags == VE_ALL) { + if (oldcoladd > win->w_cursor.col) { win->w_cursor.coladd = oldcoladd - win->w_cursor.col; - else - /* avoid weird number when there is a miscalculation or overflow */ + + // Make sure that coladd is not more than the char width. + // Not for the last character, coladd is then used when the cursor + // is actually after the last character. + if (win->w_cursor.col + 1 < len && win->w_cursor.coladd > 0) { + int cs, ce; + + getvcol(win, &win->w_cursor, &cs, NULL, &ce); + if (win->w_cursor.coladd > ce - cs) { + win->w_cursor.coladd = ce - cs; + } + } + } else { + // avoid weird number when there is a miscalculation or overflow win->w_cursor.coladd = 0; + } } } diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 7da6665cb7..cc0f3b2629 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -135,6 +135,20 @@ void diff_buf_add(buf_T *buf) EMSGN(_("E96: Cannot diff more than %" PRId64 " buffers"), DB_COUNT); } +/// +/// Remove all buffers to make diffs for. +/// +static void diff_buf_clear(void) +{ + for (int i = 0; i < DB_COUNT; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + curtab->tp_diffbuf[i] = NULL; + curtab->tp_diff_invalid = true; + diff_redraw(true); + } + } +} + /// Find buffer "buf" in the list of diff buffers for the current tab page. /// /// @param buf The buffer to find. @@ -1033,10 +1047,7 @@ void ex_diffsplit(exarg_T *eap) if (bufref_valid(&old_curbuf)) { // Move the cursor position to that of the old window. curwin->w_cursor.lnum = diff_get_corresponding_line( - old_curbuf.br_buf, - old_curwin->w_cursor.lnum, - curbuf, - curwin->w_cursor.lnum); + old_curbuf.br_buf, old_curwin->w_cursor.lnum); } } // Now that lines are folded scroll to show the cursor at the same @@ -1178,6 +1189,11 @@ void ex_diffoff(exarg_T *eap) diffwin |= wp->w_p_diff; } + // Also remove hidden buffers from the list. + if (eap->forceit) { + diff_buf_clear(); + } + // Remove "hor" from from 'scrollopt' if there are no diff windows left. if (!diffwin && (vim_strchr(p_sbo, 'h') != NULL)) { do_cmdline_cmd("set sbo-=hor"); @@ -2463,25 +2479,17 @@ int diff_move_to(int dir, long count) return OK; } -/// Finds the corresponding line in a diff. -/// -/// @param buf1 -/// @param lnum1 -/// @param buf2 -/// @param lnum3 -/// -/// @return The corresponding line. -linenr_T diff_get_corresponding_line(buf_T *buf1, linenr_T lnum1, buf_T *buf2, - linenr_T lnum3) +/// Return the line number in the current window that is closest to "lnum1" in +/// "buf1" in diff mode. +static linenr_T diff_get_corresponding_line_int(buf_T *buf1, linenr_T lnum1) { int idx1; int idx2; diff_T *dp; int baseline = 0; - linenr_T lnum2; idx1 = diff_buf_idx(buf1); - idx2 = diff_buf_idx(buf2); + idx2 = diff_buf_idx(curbuf); if ((idx1 == DB_COUNT) || (idx2 == DB_COUNT) @@ -2501,15 +2509,9 @@ linenr_T diff_get_corresponding_line(buf_T *buf1, linenr_T lnum1, buf_T *buf2, for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) { if (dp->df_lnum[idx1] > lnum1) { - lnum2 = lnum1 - baseline; - - // don't end up past the end of the file - if (lnum2 > buf2->b_ml.ml_line_count) { - lnum2 = buf2->b_ml.ml_line_count; - } - - return lnum2; - } else if ((dp->df_lnum[idx1] + dp->df_count[idx1]) > lnum1) { + return lnum1 - baseline; + } + if ((dp->df_lnum[idx1] + dp->df_count[idx1]) > lnum1) { // Inside the diffblock baseline = lnum1 - dp->df_lnum[idx1]; @@ -2518,30 +2520,42 @@ linenr_T diff_get_corresponding_line(buf_T *buf1, linenr_T lnum1, buf_T *buf2, } return dp->df_lnum[idx2] + baseline; - } else if ((dp->df_lnum[idx1] == lnum1) - && (dp->df_count[idx1] == 0) - && (dp->df_lnum[idx2] <= lnum3) - && ((dp->df_lnum[idx2] + dp->df_count[idx2]) > lnum3)) { + } + if ((dp->df_lnum[idx1] == lnum1) + && (dp->df_count[idx1] == 0) + && (dp->df_lnum[idx2] <= curwin->w_cursor.lnum) + && ((dp->df_lnum[idx2] + dp->df_count[idx2]) + > curwin->w_cursor.lnum)) { // Special case: if the cursor is just after a zero-count // block (i.e. all filler) and the target cursor is already // inside the corresponding block, leave the target cursor // unmoved. This makes repeated CTRL-W W operations work // as expected. - return lnum3; + return curwin->w_cursor.lnum; } baseline = (dp->df_lnum[idx1] + dp->df_count[idx1]) - - (dp->df_lnum[idx2] + dp->df_count[idx2]); + - (dp->df_lnum[idx2] + dp->df_count[idx2]); } // If we get here then the cursor is after the last diff - lnum2 = lnum1 - baseline; + return lnum1 - baseline; +} + +/// Finds the corresponding line in a diff. +/// +/// @param buf1 +/// @param lnum1 +/// +/// @return The corresponding line. +linenr_T diff_get_corresponding_line(buf_T *buf1, linenr_T lnum1) +{ + linenr_T lnum = diff_get_corresponding_line_int(buf1, lnum1); // don't end up past the end of the file - if (lnum2 > buf2->b_ml.ml_line_count) { - lnum2 = buf2->b_ml.ml_line_count; + if (lnum > curbuf->b_ml.ml_line_count) { + return curbuf->b_ml.ml_line_count; } - - return lnum2; + return lnum; } /// For line "lnum" in the current window find the equivalent lnum in window diff --git a/src/nvim/edit.c b/src/nvim/edit.c index ca62679fab..2bafb77fef 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -974,14 +974,6 @@ static int insert_handle_key(InsertState *s) multiqueue_process_events(main_loop.events); break; - case K_FOCUSGAINED: // Neovim has been given focus - apply_autocmds(EVENT_FOCUSGAINED, NULL, NULL, false, curbuf); - break; - - case K_FOCUSLOST: // Neovim has lost focus - apply_autocmds(EVENT_FOCUSLOST, NULL, NULL, false, curbuf); - break; - case K_HOME: // <Home> case K_KHOME: case K_S_HOME: @@ -2406,6 +2398,7 @@ void set_completion(colnr_T startcol, list_T *list) ins_compl_prep(' '); } ins_compl_clear(); + ins_compl_free(); compl_direction = FORWARD; if (startcol > curwin->w_cursor.col) @@ -3166,8 +3159,7 @@ static bool ins_compl_prep(int c) /* Ignore end of Select mode mapping and mouse scroll buttons. */ if (c == K_SELECT || c == K_MOUSEDOWN || c == K_MOUSEUP - || c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_EVENT - || c == K_FOCUSGAINED || c == K_FOCUSLOST) { + || c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_EVENT) { return retval; } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index c42929ef7c..aab777955c 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6733,6 +6733,39 @@ static void prepare_assert_error(garray_T *gap) } } +// Append "str" to "gap", escaping unprintable characters. +// Changes NL to \n, CR to \r, etc. +static void ga_concat_esc(garray_T *gap, char_u *str) +{ + char_u *p; + char_u buf[NUMBUFLEN]; + + if (str == NULL) { + ga_concat(gap, (char_u *)"NULL"); + return; + } + + for (p = str; *p != NUL; p++) { + switch (*p) { + case BS: ga_concat(gap, (char_u *)"\\b"); break; + case ESC: ga_concat(gap, (char_u *)"\\e"); break; + case FF: ga_concat(gap, (char_u *)"\\f"); break; + case NL: ga_concat(gap, (char_u *)"\\n"); break; + case TAB: ga_concat(gap, (char_u *)"\\t"); break; + case CAR: ga_concat(gap, (char_u *)"\\r"); break; + case '\\': ga_concat(gap, (char_u *)"\\\\"); break; + default: + if (*p < ' ') { + vim_snprintf((char *)buf, NUMBUFLEN, "\\x%02x", *p); + ga_concat(gap, buf); + } else { + ga_append(gap, *p); + } + break; + } + } +} + // Fill "gap" with information about an assert error. static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_str, typval_T *exp_tv, @@ -6747,28 +6780,30 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, } else { if (atype == ASSERT_MATCH || atype == ASSERT_NOTMATCH) { ga_concat(gap, (char_u *)"Pattern "); + } else if (atype == ASSERT_NOTEQUAL) { + ga_concat(gap, (char_u *)"Expected not equal to "); } else { ga_concat(gap, (char_u *)"Expected "); } if (exp_str == NULL) { - tofree = (char_u *) encode_tv2string(exp_tv, NULL); - ga_concat(gap, tofree); + tofree = (char_u *)encode_tv2string(exp_tv, NULL); + ga_concat_esc(gap, tofree); xfree(tofree); } else { - ga_concat(gap, exp_str); + ga_concat_esc(gap, exp_str); } - tofree = (char_u *)encode_tv2string(got_tv, NULL); - if (atype == ASSERT_MATCH) { - ga_concat(gap, (char_u *)" does not match "); - } else if (atype == ASSERT_NOTMATCH) { - ga_concat(gap, (char_u *)" does match "); - } else if (atype == ASSERT_NOTEQUAL) { - ga_concat(gap, (char_u *)" differs from "); - } else { - ga_concat(gap, (char_u *)" but got "); + if (atype != ASSERT_NOTEQUAL) { + if (atype == ASSERT_MATCH) { + ga_concat(gap, (char_u *)" does not match "); + } else if (atype == ASSERT_NOTMATCH) { + ga_concat(gap, (char_u *)" does match "); + } else { + ga_concat(gap, (char_u *)" but got "); + } + tofree = (char_u *)encode_tv2string(got_tv, NULL); + ga_concat_esc(gap, tofree); + xfree(tofree); } - ga_concat(gap, tofree); - xfree(tofree); } } @@ -11031,6 +11066,7 @@ void get_user_input(const typval_T *const argvars, const char *defstr = ""; const char *cancelreturn = NULL; const char *xp_name = NULL; + Callback input_callback = { .type = kCallbackNone }; char prompt_buf[NUMBUFLEN]; char defstr_buf[NUMBUFLEN]; char cancelreturn_buf[NUMBUFLEN]; @@ -11040,7 +11076,7 @@ void get_user_input(const typval_T *const argvars, emsgf(_("E5050: {opts} must be the only argument")); return; } - const dict_T *const dict = argvars[0].vval.v_dict; + dict_T *const dict = argvars[0].vval.v_dict; prompt = tv_dict_get_string_buf_chk(dict, S_LEN("prompt"), prompt_buf, ""); if (prompt == NULL) { return; @@ -11066,6 +11102,9 @@ void get_user_input(const typval_T *const argvars, if (xp_name == def) { // default to NULL xp_name = NULL; } + if (!tv_dict_get_callback(dict, S_LEN("highlight"), &input_callback)) { + return; + } } else { prompt = tv_get_string_buf_chk(&argvars[0], prompt_buf); if (prompt == NULL) { @@ -11124,12 +11163,13 @@ void get_user_input(const typval_T *const argvars, stuffReadbuffSpec(defstr); - int save_ex_normal_busy = ex_normal_busy; + const int save_ex_normal_busy = ex_normal_busy; ex_normal_busy = 0; rettv->vval.v_string = - getcmdline_prompt(inputsecret_flag ? NUL : '@', (char_u *)p, echo_attr, - xp_type, (char_u *)xp_arg); + (char_u *)getcmdline_prompt(inputsecret_flag ? NUL : '@', p, echo_attr, + xp_type, xp_arg, input_callback); ex_normal_busy = save_ex_normal_busy; + callback_free(&input_callback); if (rettv->vval.v_string == NULL && cancelreturn != NULL) { rettv->vval.v_string = (char_u *)xstrdup(cancelreturn); @@ -17488,7 +17528,7 @@ static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_add_nr(dict, S_LEN("skipcol"), (varnumber_T)curwin->w_skipcol); } -/// Writes list of strings to file +/// Write "list" of strings to file "fd". /// /// @param fp File to write to. /// @param[in] list List to write. @@ -22770,7 +22810,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) bool eval_has_provider(const char *name) { -#define check_provider(name) \ +#define CHECK_PROVIDER(name) \ if (has_##name == -1) { \ has_##name = !!find_func((char_u *)"provider#" #name "#Call"); \ if (!has_##name) { \ @@ -22786,17 +22826,17 @@ bool eval_has_provider(const char *name) static int has_python3 = -1; static int has_ruby = -1; - if (!strcmp(name, "clipboard")) { - check_provider(clipboard); + if (strequal(name, "clipboard")) { + CHECK_PROVIDER(clipboard); return has_clipboard; - } else if (!strcmp(name, "python3")) { - check_provider(python3); + } else if (strequal(name, "python3")) { + CHECK_PROVIDER(python3); return has_python3; - } else if (!strcmp(name, "python")) { - check_provider(python); + } else if (strequal(name, "python")) { + CHECK_PROVIDER(python); return has_python; - } else if (!strcmp(name, "ruby")) { - check_provider(ruby); + } else if (strequal(name, "ruby")) { + CHECK_PROVIDER(ruby); return has_ruby; } @@ -22810,3 +22850,32 @@ void eval_format_source_name_line(char *buf, size_t bufsize) (sourcing_name ? sourcing_name : (char_u *)"?"), (sourcing_name ? sourcing_lnum : 0)); } + +/// ":checkhealth [plugins]" +void ex_checkhealth(exarg_T *eap) +{ + bool found = !!find_func((char_u *)"health#check"); + if (!found + && script_autoload("health#check", sizeof("health#check") - 1, false)) { + found = !!find_func((char_u *)"health#check"); + } + if (!found) { + const char *vimruntime_env = os_getenv("VIMRUNTIME"); + if (vimruntime_env == NULL) { + EMSG(_("E5009: $VIMRUNTIME is empty or unset")); + return; + } else { + EMSG2(_("E5009: Invalid $VIMRUNTIME: %s"), os_getenv("VIMRUNTIME")); + return; + } + } + + size_t bufsize = STRLEN(eap->arg) + sizeof("call health#check('')"); + char *buf = xmalloc(bufsize); + snprintf(buf, bufsize, "call health#check('%s')", eap->arg); + + do_cmdline_cmd(buf); + + xfree(buf); +} + diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 3f8ed3b3f9..c44b85644d 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -43,7 +43,7 @@ typedef struct partial_S partial_T; typedef struct ufunc ufunc_T; typedef enum { - kCallbackNone, + kCallbackNone = 0, kCallbackFuncref, kCallbackPartial, } CallbackType; diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c index 25701a1621..5adf16c0f3 100644 --- a/src/nvim/event/loop.c +++ b/src/nvim/event/loop.c @@ -59,7 +59,14 @@ void loop_poll_events(Loop *loop, int ms) multiqueue_process_events(loop->fast_events); } -// Schedule an event from another thread +/// Schedules an event from another thread. +/// +/// @note Event is queued into `fast_events`, which is processed outside of the +/// primary `events` queue by loop_poll_events(). For `main_loop`, that +/// means `fast_events` is NOT processed in an "editor mode" +/// (VimState.execute), so redraw and other side-effects are likely to be +/// skipped. +/// @see loop_schedule_deferred void loop_schedule(Loop *loop, Event event) { uv_mutex_lock(&loop->mutex); @@ -68,6 +75,24 @@ void loop_schedule(Loop *loop, Event event) uv_mutex_unlock(&loop->mutex); } +/// Schedules an event from another thread. Unlike loop_schedule(), the event +/// is forwarded to `Loop.events`, instead of being processed immediately. +/// +/// @see loop_schedule +void loop_schedule_deferred(Loop *loop, Event event) +{ + Event *eventp = xmalloc(sizeof(*eventp)); + *eventp = event; + loop_schedule(loop, event_create(loop_deferred_event, 2, loop, eventp)); +} +static void loop_deferred_event(void **argv) +{ + Loop *loop = argv[0]; + Event *eventp = argv[1]; + multiqueue_put_event(loop->events, *eventp); + xfree(eventp); +} + void loop_on_put(MultiQueue *queue, void *data) { Loop *loop = data; diff --git a/src/nvim/event/loop.h b/src/nvim/event/loop.h index e7d7bdd483..b0ddc59469 100644 --- a/src/nvim/event/loop.h +++ b/src/nvim/event/loop.h @@ -16,10 +16,28 @@ KLIST_INIT(WatcherPtr, WatcherPtr, _noop) typedef struct loop { uv_loop_t uv; - MultiQueue *events, *fast_events, *thread_events; + MultiQueue *events; + MultiQueue *thread_events; + // Immediate events: + // "Events that should be processed after exiting uv_run() (to avoid + // recursion), but before returning from loop_poll_events()." + // 502aee690c980fcb3cfcb3f211dcfad06103db46 + // Practical consequence: these events are processed by + // state_enter()..os_inchar() + // whereas "regular" (main_loop.events) events are processed by + // state_enter()..VimState.execute() + // But state_enter()..os_inchar() can be "too early" if you want the event + // to trigger UI updates and other user-activity-related side-effects. + MultiQueue *fast_events; + + // used by process/job-control subsystem klist_t(WatcherPtr) *children; uv_signal_t children_watcher; - uv_timer_t children_kill_timer, poll_timer; + uv_timer_t children_kill_timer; + + // 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; diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index c936583841..8371d3cd48 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -233,8 +233,7 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL 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) + // stdout/stderr, they will be closed when it exits (voluntarily or not). process_close_in(proc); ILOG("Sending SIGTERM to pid %d", proc->pid); uv_kill(proc->pid, SIGTERM); diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c index 854af474b2..2c4db08b30 100644 --- a/src/nvim/event/rstream.c +++ b/src/nvim/event/rstream.c @@ -118,7 +118,7 @@ static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf) // to `alloc_cb` will return the same unused pointer(`rbuffer_produced` // won't be called) && cnt != 0) { - DLOG("Closing Stream (%p): %s (%s)", stream, + DLOG("closing Stream: %p: %s (%s)", stream, uv_err_name((int)cnt), os_strerror((int)cnt)); // Read error or EOF, either way stop the stream and invoke the callback // with eof == true diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c index 60ceff9b24..7c865bfe1e 100644 --- a/src/nvim/event/stream.c +++ b/src/nvim/event/stream.c @@ -7,6 +7,7 @@ #include <uv.h> +#include "nvim/log.h" #include "nvim/rbuffer.h" #include "nvim/macros.h" #include "nvim/event/stream.h" @@ -81,6 +82,7 @@ void stream_close(Stream *stream, stream_close_cb on_stream_close, void *data) FUNC_ATTR_NONNULL_ARG(1) { assert(!stream->closed); + DLOG("closing Stream: %p", stream); stream->closed = true; stream->close_cb = on_stream_close; stream->close_cb_data = data; diff --git a/src/nvim/event/wstream.c b/src/nvim/event/wstream.c index f453e5898d..320006890d 100644 --- a/src/nvim/event/wstream.c +++ b/src/nvim/event/wstream.c @@ -8,6 +8,7 @@ #include <uv.h> +#include "nvim/log.h" #include "nvim/event/loop.h" #include "nvim/event/wstream.h" #include "nvim/vim.h" diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index a555fb77e8..918e7a0c91 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -3332,10 +3332,12 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout) sub = regtilde(sub, p_magic); // Check for a match on each line. + // If preview: limit to max('cmdwinheight', viewport). linenr_T line2 = eap->line2; for (linenr_T lnum = eap->line1; - lnum <= line2 && !(got_quit || aborting()) - && (!preview || matched_lines.size <= (size_t)p_cwh); + lnum <= line2 && !got_quit && !aborting() + && (!preview || matched_lines.size < (size_t)p_cwh + || lnum <= curwin->w_botline); lnum++) { long nmatch = vim_regexec_multi(®match, curwin, curbuf, lnum, (colnr_T)0, NULL); @@ -3500,6 +3502,10 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout) setmouse(); /* disable mouse in xterm */ curwin->w_cursor.col = regmatch.startpos[0].col; + if (curwin->w_p_crb) { + do_check_cursorbind(); + } + /* When 'cpoptions' contains "u" don't sync undo when * asking for confirmation. */ if (vim_strchr(p_cpo, CPO_UNDO) != NULL) @@ -3659,6 +3665,42 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout) * use "\=col("."). */ curwin->w_cursor.col = regmatch.startpos[0].col; + // When the match included the "$" of the last line it may + // go beyond the last line of the buffer. + if (nmatch > curbuf->b_ml.ml_line_count - sub_firstlnum + 1) { + nmatch = curbuf->b_ml.ml_line_count - sub_firstlnum + 1; + skip_match = true; + } + +#define ADJUST_SUB_FIRSTLNUM() \ + do { \ + /* For a multi-line match, make a copy of the last matched */ \ + /* line and continue in that one. */ \ + if (nmatch > 1) { \ + sub_firstlnum += nmatch - 1; \ + xfree(sub_firstline); \ + sub_firstline = vim_strsave(ml_get(sub_firstlnum)); \ + /* When going beyond the last line, stop substituting. */ \ + if (sub_firstlnum <= line2) { \ + do_again = true; \ + } else { \ + subflags.do_all = false; \ + } \ + } \ + if (skip_match) { \ + /* Already hit end of the buffer, sub_firstlnum is one */ \ + /* less than what it ought to be. */ \ + xfree(sub_firstline); \ + sub_firstline = vim_strsave((char_u *)""); \ + copycol = 0; \ + } \ + } while (0) + + if (preview && !has_second_delim) { + ADJUST_SUB_FIRSTLNUM(); + goto skip; + } + // 3. Substitute the string. During 'inccommand' preview only do this if // there is a replace pattern. if (!preview || has_second_delim) { @@ -3685,13 +3727,6 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout) goto skip; } - // When the match included the "$" of the last line it may - // go beyond the last line of the buffer. - if (nmatch > curbuf->b_ml.ml_line_count - sub_firstlnum + 1) { - nmatch = curbuf->b_ml.ml_line_count - sub_firstlnum + 1; - skip_match = true; - } - // Need room for: // - result so far in new_start (not for first sub in line) // - original text up to match @@ -3722,30 +3757,10 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout) // is beyond the end of the line after the substitution. curwin->w_cursor.col = 0; - // For a multi-line match, make a copy of the last matched - // line and continue in that one. - if (nmatch > 1) { - sub_firstlnum += nmatch - 1; - xfree(sub_firstline); - sub_firstline = vim_strsave(ml_get(sub_firstlnum)); - // When going beyond the last line, stop substituting. - if (sub_firstlnum <= line2) { - do_again = true; - } else { - subflags.do_all = false; - } - } - // Remember next character to be copied. copycol = regmatch.endpos[0].col; - if (skip_match) { - // Already hit end of the buffer, sub_firstlnum is one - // less than what it ought to be. - xfree(sub_firstline); - sub_firstline = vim_strsave((char_u *)""); - copycol = 0; - } + ADJUST_SUB_FIRSTLNUM(); // Now the trick is to replace CTRL-M chars with a real line // break. This would make it impossible to insert a CTRL-M in @@ -4002,6 +4017,7 @@ skip: kv_destroy(matched_lines); return preview_buf; +#undef ADJUST_SUB_FIRSTLNUM } // NOLINT(readability/fn_size) /* diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 5a578cd088..e57e662039 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -451,6 +451,12 @@ return { func='ex_changes', }, { + command='checkhealth', + flags=bit.bor(EXTRA, TRLBAR), + addr_type=ADDR_LINES, + func='ex_checkhealth', + }, + { command='checkpath', flags=bit.bor(TRLBAR, BANG, CMDWIN), addr_type=ADDR_LINES, @@ -614,7 +620,7 @@ return { }, { command='cquit', - flags=bit.bor(TRLBAR, BANG), + flags=bit.bor(RANGE, NOTADR, COUNT, ZEROR, TRLBAR, BANG), addr_type=ADDR_LINES, func='ex_cquit', }, diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 1a728647ca..371f7b3bce 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -189,7 +189,8 @@ void do_debug(char_u *cmd) } xfree(cmdline); - cmdline = getcmdline_prompt('>', NULL, 0, EXPAND_NOTHING, NULL); + cmdline = (char_u *)getcmdline_prompt('>', NULL, 0, EXPAND_NOTHING, NULL, + CALLBACK_NONE); if (typeahead_saved) { restore_typeahead(&typeaheadbuf); @@ -2318,16 +2319,6 @@ static void source_callback(char_u *fname, void *cookie) (void)do_source(fname, false, DOSO_NONE); } -/// Source the file "name" from all directories in 'runtimepath'. -/// "name" can contain wildcards. -/// When "flags" has DIP_ALL: source all files, otherwise only the first one. -/// -/// return FAIL when no file could be sourced, OK otherwise. -int source_runtime(char_u *name, int flags) -{ - return do_in_runtimepath(name, flags, source_callback, NULL); -} - /// Find the file "name" in all directories in "path" and invoke /// "callback(fname, cookie)". /// "name" can contain wildcards. @@ -2433,21 +2424,21 @@ int do_in_path(char_u *path, char_u *name, int flags, return did_one ? OK : FAIL; } -/// Find "name" in 'runtimepath'. When found, invoke the callback function for +/// Find "name" in "path". When found, invoke the callback function for /// it: callback(fname, "cookie") /// When "flags" has DIP_ALL repeat for all matches, otherwise only the first /// one is used. /// Returns OK when at least one match found, FAIL otherwise. -/// If "name" is NULL calls callback for each entry in runtimepath. Cookie is +/// If "name" is NULL calls callback for each entry in "path". Cookie is /// passed by reference in this case, setting it to NULL indicates that callback /// has done its job. -int do_in_runtimepath(char_u *name, int flags, DoInRuntimepathCB callback, - void *cookie) +int do_in_path_and_pp(char_u *path, char_u *name, int flags, + DoInRuntimepathCB callback, void *cookie) { int done = FAIL; if ((flags & DIP_NORTP) == 0) { - done = do_in_path(p_rtp, name, flags, callback, cookie); + done = do_in_path(path, name, flags, callback, cookie); } if ((done == FAIL || (flags & DIP_ALL)) && (flags & DIP_START)) { @@ -2475,6 +2466,29 @@ int do_in_runtimepath(char_u *name, int flags, DoInRuntimepathCB callback, return done; } +/// Just like do_in_path_and_pp(), using 'runtimepath' for "path". +int do_in_runtimepath(char_u *name, int flags, DoInRuntimepathCB callback, + void *cookie) +{ + return do_in_path_and_pp(p_rtp, name, flags, callback, cookie); +} + +/// Source the file "name" from all directories in 'runtimepath'. +/// "name" can contain wildcards. +/// When "flags" has DIP_ALL: source all files, otherwise only the first one. +/// +/// return FAIL when no file could be sourced, OK otherwise. +int source_runtime(char_u *name, int flags) +{ + return source_in_path(p_rtp, name, flags); +} + +/// Just like source_runtime(), but use "path" instead of 'runtimepath'. +int source_in_path(char_u *path, char_u *name, int flags) +{ + return do_in_path_and_pp(path, name, flags, source_callback, NULL); +} + // Expand wildcards in "pat" and invoke do_source() for each match. static void source_all_matches(char_u *pat) { @@ -2497,6 +2511,7 @@ static int APP_BOTH; static void add_pack_plugin(char_u *fname, void *cookie) { char_u *p4, *p3, *p2, *p1, *p; + char_u *buf = NULL; char *const ffname = fix_fname((char *)fname); @@ -2524,26 +2539,30 @@ static void add_pack_plugin(char_u *fname, void *cookie) // Find "ffname" in "p_rtp", ignoring '/' vs '\' differences size_t fname_len = strlen(ffname); const char *insp = (const char *)p_rtp; - for (;;) { - if (path_fnamencmp(insp, ffname, fname_len) == 0) { - break; + buf = try_malloc(MAXPATHL); + if (buf == NULL) { + goto theend; + } + while (*insp != NUL) { + copy_option_part((char_u **)&insp, buf, MAXPATHL, ","); + add_pathsep((char *)buf); + char *const rtp_ffname = fix_fname((char *)buf); + if (rtp_ffname == NULL) { + goto theend; } - insp = strchr(insp, ','); - if (insp == NULL) { + bool match = path_fnamencmp(rtp_ffname, ffname, fname_len) == 0; + xfree(rtp_ffname); + if (match) { break; } - insp++; } - if (insp == NULL) { + if (*insp == NUL) { // not found, append at the end insp = (const char *)p_rtp + STRLEN(p_rtp); } else { // append after the matching directory. - insp += strlen(ffname); - while (*insp != NUL && *insp != ',') { - insp++; - } + insp--; } *p4 = c; @@ -2613,26 +2632,35 @@ static void add_pack_plugin(char_u *fname, void *cookie) } theend: + xfree(buf); xfree(ffname); } -static bool did_source_packages = false; +/// Add all packages in the "start" directory to 'runtimepath'. +void add_pack_start_dirs(void) +{ + do_in_path(p_pp, (char_u *)"pack/*/start/*", DIP_ALL + DIP_DIR, // NOLINT + add_pack_plugin, &APP_ADD_DIR); +} + +/// Load plugins from all packages in the "start" directory. +void load_start_packages(void) +{ + did_source_packages = true; + do_in_path(p_pp, (char_u *)"pack/*/start/*", DIP_ALL + DIP_DIR, // NOLINT + add_pack_plugin, &APP_LOAD); +} // ":packloadall" // Find plugins in the package directories and source them. -// "eap" is NULL when invoked during startup. void ex_packloadall(exarg_T *eap) { - if (!did_source_packages || (eap != NULL && eap->forceit)) { - did_source_packages = true; - + if (!did_source_packages || eap->forceit) { // First do a round to add all directories to 'runtimepath', then load // the plugins. This allows for plugins to use an autoload directory // of another plugin. - do_in_path(p_pp, (char_u *)"pack/*/start/*", DIP_ALL + DIP_DIR, // NOLINT - add_pack_plugin, &APP_ADD_DIR); - do_in_path(p_pp, (char_u *)"pack/*/start/*", DIP_ALL + DIP_DIR, // NOLINT - add_pack_plugin, &APP_LOAD); + add_pack_start_dirs(); + load_start_packages(); } } @@ -3610,18 +3638,11 @@ void ex_language(exarg_T *eap) static char_u **locales = NULL; // Array of all available locales -static bool did_init_locales = false; -/// Lazy initialization of all available locales. -static void init_locales(void) -{ - if (!did_init_locales) { - did_init_locales = true; - locales = find_locales(); - } -} +#ifndef WIN32 +static bool did_init_locales = false; -// Return an array of strings for all available locales + NULL for the +/// Return an array of strings for all available locales + NULL for the /// last element. Return NULL in case of error. static char_u **find_locales(void) { @@ -3653,6 +3674,18 @@ static char_u **find_locales(void) ((char_u **)locales_ga.ga_data)[locales_ga.ga_len] = NULL; return (char_u **)locales_ga.ga_data; } +#endif + +/// Lazy initialization of all available locales. +static void init_locales(void) +{ +#ifndef WIN32 + if (!did_init_locales) { + did_init_locales = true; + locales = find_locales(); + } +#endif +} # if defined(EXITFREE) void free_locales(void) diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 6e7938046a..f64c9fded8 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -9,6 +9,7 @@ #include <string.h> #include <stdbool.h> #include <stdint.h> +#include <stdlib.h> #include <inttypes.h> #include "nvim/vim.h" @@ -1668,8 +1669,8 @@ static char_u * do_one_cmd(char_u **cmdlinep, if (*ea.cmd == ';') { if (!ea.skip) { curwin->w_cursor.lnum = ea.line2; - // Don't leave the cursor on an illegal line (caused by ';') - check_cursor_lnum(); + // don't leave the cursor on an illegal line or column + check_cursor(); } } else if (*ea.cmd != ',') { break; @@ -1813,7 +1814,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, if (text_locked() && !(ea.argt & CMDWIN) && !IS_USER_CMDIDX(ea.cmdidx)) { // Command not allowed when editing the command line. - errormsg = get_text_locked_msg(); + errormsg = (char_u *)_(get_text_locked_msg()); goto doend; } /* Disallow editing another buffer when "curbuf_lock" is set. @@ -5995,7 +5996,7 @@ static void ex_quit(exarg_T *eap) */ static void ex_cquit(exarg_T *eap) { - getout(1); + getout(eap->addr_count > 0 ? (int)eap->line2 : EXIT_FAILURE); } /* @@ -8810,11 +8811,12 @@ makeopens ( && buf->b_fname != NULL && buf->b_p_bl) { if (fprintf(fd, "badd +%" PRId64 " ", - buf->b_wininfo == NULL ? - (int64_t)1L : - (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0 - || ses_fname(fd, buf, &ssop_flags) == FAIL) + buf->b_wininfo == NULL + ? (int64_t)1L + : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0 + || ses_fname(fd, buf, &ssop_flags, true) == FAIL) { return FAIL; + } } } @@ -8885,11 +8887,13 @@ makeopens ( && !bt_nofile(wp->w_buffer) ) { if (fputs(need_tabnew ? "tabedit " : "edit ", fd) < 0 - || ses_fname(fd, wp->w_buffer, &ssop_flags) == FAIL) + || ses_fname(fd, wp->w_buffer, &ssop_flags, true) == FAIL) { return FAIL; - need_tabnew = FALSE; - if (!wp->w_arg_idx_invalid) + } + need_tabnew = false; + if (!wp->w_arg_idx_invalid) { edited_win = wp; + } break; } } @@ -8933,6 +8937,8 @@ makeopens ( // resized when moving between windows. // Do this before restoring the view, so that the topline and the // cursor can be set. This is done again below. + // winminheight and winminwidth need to be set to avoid an error if the + // user has set winheight or winwidth. if (put_line(fd, "set winminheight=1 winminwidth=1 winheight=1 winwidth=1") == FAIL) { return FAIL; @@ -9221,24 +9227,35 @@ put_view ( if (wp->w_buffer->b_ffname != NULL && (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal) ) { - /* - * Editing a file in this buffer: use ":edit file". - * This may have side effects! (e.g., compressed or network file). - */ - if (fputs("edit ", fd) < 0 - || ses_fname(fd, wp->w_buffer, flagp) == FAIL) + // Editing a file in this buffer: use ":edit file". + // This may have side effects! (e.g., compressed or network file). + // + // Note, if a buffer for that file already exists, use :badd to + // edit that buffer, to not lose folding information (:edit resets + // folds in other buffers) + if (fputs("if bufexists('", fd) < 0 + || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL + || fputs("') | buffer ", fd) < 0 + || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL + || fputs(" | else | edit ", fd) < 0 + || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL + || fputs(" | endif", fd) < 0 + || put_eol(fd) == FAIL) { return FAIL; + } } else { - /* No file in this buffer, just make it empty. */ - if (put_line(fd, "enew") == FAIL) + // No file in this buffer, just make it empty. + if (put_line(fd, "enew") == FAIL) { return FAIL; + } if (wp->w_buffer->b_ffname != NULL) { - /* The buffer does have a name, but it's not a file name. */ + // The buffer does have a name, but it's not a file name. if (fputs("file ", fd) < 0 - || ses_fname(fd, wp->w_buffer, flagp) == FAIL) + || ses_fname(fd, wp->w_buffer, flagp, true) == FAIL) { return FAIL; + } } - do_cursor = FALSE; + do_cursor = false; } } @@ -9378,7 +9395,7 @@ ses_arglist ( (void)vim_FullName((char *)s, (char *)buf, MAXPATHL, FALSE); s = buf; } - if (fputs("argadd ", fd) < 0 || ses_put_fname(fd, s, flagp) == FAIL + if (fputs("$argadd ", fd) < 0 || ses_put_fname(fd, s, flagp) == FAIL || put_eol(fd) == FAIL) { xfree(buf); return FAIL; @@ -9389,12 +9406,10 @@ ses_arglist ( return OK; } -/* - * Write a buffer name to the session file. - * Also ends the line. - * Returns FAIL if writing fails. - */ -static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp) +/// Write a buffer name to the session file. +/// Also ends the line, if "add_eol" is true. +/// Returns FAIL if writing fails. +static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol) { char_u *name; @@ -9411,8 +9426,10 @@ static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp) name = buf->b_sfname; else name = buf->b_ffname; - if (ses_put_fname(fd, name, flagp) == FAIL || put_eol(fd) == FAIL) + if (ses_put_fname(fd, name, flagp) == FAIL + || (add_eol && put_eol(fd) == FAIL)) { return FAIL; + } return OK; } @@ -9835,7 +9852,7 @@ static void ex_terminal(exarg_T *eap) if (*eap->arg != NUL) { // Run {cmd} in 'shell'. char *name = (char *)vim_strsave_escaped(eap->arg, (char_u *)"\"\\"); snprintf(ex_cmd, sizeof(ex_cmd), - ":enew%s | call termopen(\"%s\") | startinsert", + ":enew%s | call termopen(\"%s\")", eap->forceit ? "!" : "", name); xfree(name); } else { // No {cmd}: run the job with tokenized 'shell'. @@ -9857,7 +9874,7 @@ static void ex_terminal(exarg_T *eap) shell_free_argv(argv); snprintf(ex_cmd, sizeof(ex_cmd), - ":enew%s | call termopen([%s]) | startinsert", + ":enew%s | call termopen([%s])", eap->forceit ? "!" : "", shell_argv + 1); } diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index c029df2f13..9037b3c151 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -561,7 +561,9 @@ static void discard_exception(except_T *excp, int was_finished) */ void discard_current_exception(void) { - discard_exception(current_exception, FALSE); + discard_exception(current_exception, false); + // Note: all globals manipulated here should be saved/restored in + // try_enter/try_leave. current_exception = NULL; did_throw = FALSE; need_rethrow = FALSE; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index ecd5c81822..54e5bcb9ff 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -63,6 +63,9 @@ #include "nvim/os/os.h" #include "nvim/event/loop.h" #include "nvim/os/time.h" +#include "nvim/lib/kvec.h" +#include "nvim/api/private/helpers.h" +#include "nvim/highlight_defs.h" /* * Variables shared between getcmdline(), redrawcmdline() and others. @@ -70,23 +73,27 @@ * structure. */ struct cmdline_info { - char_u *cmdbuff; /* pointer to command line buffer */ - int cmdbufflen; /* length of cmdbuff */ - int cmdlen; /* number of chars in command line */ - int cmdpos; /* current cursor position */ - int cmdspos; /* cursor column on screen */ - int cmdfirstc; /* ':', '/', '?', '=', '>' or NUL */ - int cmdindent; /* number of spaces before cmdline */ - char_u *cmdprompt; /* message in front of cmdline */ - int cmdattr; /* attributes for prompt */ - int overstrike; /* Typing mode on the command line. Shared by - getcmdline() and put_on_cmdline(). */ - expand_T *xpc; /* struct being used for expansion, xp_pattern - may point into cmdbuff */ - int xp_context; /* type of expansion */ - char_u *xp_arg; /* user-defined expansion arg */ - int input_fn; /* when TRUE Invoked for input() function */ + char_u *cmdbuff; // pointer to command line buffer + int cmdbufflen; // length of cmdbuff + int cmdlen; // number of chars in command line + int cmdpos; // current cursor position + int cmdspos; // cursor column on screen + int cmdfirstc; // ':', '/', '?', '=', '>' or NUL + int cmdindent; // number of spaces before cmdline + char_u *cmdprompt; // message in front of cmdline + int cmdattr; // attributes for prompt + int overstrike; // Typing mode on the command line. Shared by + // getcmdline() and put_on_cmdline(). + expand_T *xpc; // struct being used for expansion, xp_pattern + // may point into cmdbuff + int xp_context; // type of expansion + char_u *xp_arg; // user-defined expansion arg + int input_fn; // when TRUE Invoked for input() function + unsigned prompt_id; ///< Prompt number, used to disable coloring on errors. + Callback highlight_callback; ///< Callback used for coloring user input. }; +/// Last value of prompt_id, incremented when doing new prompt +static unsigned last_prompt_id = 0; typedef struct command_line_state { VimState state; @@ -136,6 +143,38 @@ typedef struct command_line_state { struct cmdline_info save_ccline; } CommandLineState; +/// Command-line colors: one chunk +/// +/// Defines a region which has the same highlighting. +typedef struct { + int start; ///< Colored chunk start. + int end; ///< Colored chunk end (exclusive, > start). + int attr; ///< Highlight attr. +} CmdlineColorChunk; + +/// Command-line colors +/// +/// Holds data about all colors. +typedef kvec_t(CmdlineColorChunk) CmdlineColors; + +/// Command-line coloring +/// +/// Holds both what are the colors and what have been colored. Latter is used to +/// suppress unnecessary calls to coloring callbacks. +typedef struct { + unsigned prompt_id; ///< ID of the prompt which was colored last. + char *cmdbuff; ///< What exactly was colored last time or NULL. + CmdlineColors colors; ///< Last colors. +} ColoredCmdline; + +/// Last command-line colors. +ColoredCmdline last_ccline_colors = { + .cmdbuff = NULL, + .colors = KV_INITIAL_VALUE +}; + +typedef struct cmdline_info CmdlineInfo; + /* The current cmdline_info. It is initialized in getcmdline() and after that * used by other functions. When invoking getcmdline() recursively it needs * to be saved with save_cmdline() and restored with restore_cmdline(). @@ -157,6 +196,12 @@ static int hisnum[HIST_COUNT] = {0, 0, 0, 0, 0}; /* identifying (unique) number of newest history entry */ static int hislen = 0; /* actual length of history tables */ +/// Flag for command_line_handle_key to ignore <C-c> +/// +/// Used if it was received while processing highlight function in order for +/// user interrupting highlight function to not interrupt command-line. +static bool getln_interrupted_highlight = false; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_getln.c.generated.h" @@ -193,6 +238,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) cmd_hkmap = 0; } + ccline.prompt_id = last_prompt_id++; ccline.overstrike = false; // always start in insert mode clearpos(&s->match_end); s->save_cursor = curwin->w_cursor; // may be restored later @@ -1160,8 +1206,11 @@ static int command_line_handle_key(CommandLineState *s) case ESC: // get here if p_wc != ESC or when ESC typed twice case Ctrl_C: // In exmode it doesn't make sense to return. Except when - // ":normal" runs out of characters. - if (exmode_active && (ex_normal_busy == 0 || typebuf.tb_len > 0)) { + // ":normal" runs out of characters. Also when highlight callback is active + // <C-c> should interrupt only it. + if ((exmode_active && (ex_normal_busy == 0 || typebuf.tb_len > 0)) + || (getln_interrupted_highlight && s->c == Ctrl_C)) { + getln_interrupted_highlight = false; return command_line_not_changed(s); } @@ -1571,14 +1620,6 @@ static int command_line_handle_key(CommandLineState *s) } return command_line_not_changed(s); - case K_FOCUSGAINED: // Neovim has been given focus - apply_autocmds(EVENT_FOCUSGAINED, NULL, NULL, false, curbuf); - return command_line_not_changed(s); - - case K_FOCUSLOST: // Neovim has lost focus - apply_autocmds(EVENT_FOCUSLOST, NULL, NULL, false, curbuf); - return command_line_not_changed(s); - default: // Normal character with no special meaning. Just set mod_mask // to 0x0 so that typing Shift-Space in the GUI doesn't enter @@ -1790,41 +1831,54 @@ getcmdline ( return command_line_enter(firstc, count, indent); } -/* - * Get a command line with a prompt. - * This is prepared to be called recursively from getcmdline() (e.g. by - * f_input() when evaluating an expression from CTRL-R =). - * Returns the command line in allocated memory, or NULL. - */ -char_u * -getcmdline_prompt ( - int firstc, - char_u *prompt, /* command line prompt */ - int attr, /* attributes for prompt */ - int xp_context, /* type of expansion */ - char_u *xp_arg /* user-defined expansion argument */ -) +/// Get a command line with a prompt +/// +/// This is prepared to be called recursively from getcmdline() (e.g. by +/// f_input() when evaluating an expression from `<C-r>=`). +/// +/// @param[in] firstc Prompt type: e.g. '@' for input(), '>' for debug. +/// @param[in] prompt Prompt string: what is displayed before the user text. +/// @param[in] attr Prompt highlighting. +/// @param[in] xp_context Type of expansion. +/// @param[in] xp_arg User-defined expansion argument. +/// @param[in] highlight_callback Callback used for highlighting user input. +/// +/// @return [allocated] Command line or NULL. +char *getcmdline_prompt(const char firstc, const char *const prompt, + const int attr, const int xp_context, + const char *const xp_arg, + const Callback highlight_callback) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC { - char_u *s; - struct cmdline_info save_ccline; - int msg_col_save = msg_col; + const int msg_col_save = msg_col; + struct cmdline_info save_ccline; save_cmdline(&save_ccline); - ccline.cmdprompt = prompt; + + ccline.prompt_id = last_prompt_id++; + ccline.cmdprompt = (char_u *)prompt; ccline.cmdattr = attr; ccline.xp_context = xp_context; - ccline.xp_arg = xp_arg; + ccline.xp_arg = (char_u *)xp_arg; ccline.input_fn = (firstc == '@'); - s = getcmdline(firstc, 1L, 0); + ccline.highlight_callback = highlight_callback; + + int msg_silent_saved = msg_silent; + msg_silent = 0; + + char *const ret = (char *)getcmdline(firstc, 1L, 0); + restore_cmdline(&save_ccline); - /* Restore msg_col, the prompt from input() may have changed it. - * But only if called recursively and the commandline is therefore being - * restored to an old one; if not, the input() prompt stays on the screen, - * so we need its modified msg_col left intact. */ - if (ccline.cmdbuff != NULL) + msg_silent = msg_silent_saved; + // Restore msg_col, the prompt from input() may have changed it. + // But only if called recursively and the commandline is therefore being + // restored to an old one; if not, the input() prompt stays on the screen, + // so we need its modified msg_col left intact. + if (ccline.cmdbuff != NULL) { msg_col = msg_col_save; + } - return s; + return ret; } /* @@ -2285,75 +2339,329 @@ void free_cmdline_buf(void) # endif +enum { MAX_CB_ERRORS = 1 }; + +/// Color command-line +/// +/// Should use built-in command parser or user-specified one. Currently only the +/// latter is supported. +/// +/// @param[in] colored_ccline Command-line to color. +/// @param[out] ret_ccline_colors What should be colored. Also holds a cache: +/// if ->prompt_id and ->cmdbuff values happen +/// to be equal to those from colored_cmdline it +/// will just do nothing, assuming that ->colors +/// already contains needed data. +/// +/// Always colors the whole cmdline. +/// +/// @return true if draw_cmdline may proceed, false if it does not need anything +/// to do. +static bool color_cmdline(const CmdlineInfo *const colored_ccline, + ColoredCmdline *const ret_ccline_colors) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + bool printed_errmsg = false; +#define PRINT_ERRMSG(...) \ + do { \ + msg_putchar('\n'); \ + msg_printf_attr(hl_attr(HLF_E)|MSG_HIST, __VA_ARGS__); \ + printed_errmsg = true; \ + } while (0) + bool ret = true; + + // Check whether result of the previous call is still valid. + if (ret_ccline_colors->prompt_id == colored_ccline->prompt_id + && ret_ccline_colors->cmdbuff != NULL + && STRCMP(ret_ccline_colors->cmdbuff, colored_ccline->cmdbuff) == 0) { + return ret; + } + + kv_size(ret_ccline_colors->colors) = 0; + + if (colored_ccline->cmdbuff == NULL || *colored_ccline->cmdbuff == NUL) { + // Nothing to do, exiting. + xfree(ret_ccline_colors->cmdbuff); + ret_ccline_colors->cmdbuff = NULL; + return ret; + } + + bool arg_allocated = false; + typval_T arg = { + .v_type = VAR_STRING, + .vval.v_string = colored_ccline->cmdbuff, + }; + typval_T tv = { .v_type = VAR_UNKNOWN }; + + static unsigned prev_prompt_id = UINT_MAX; + static int prev_prompt_errors = 0; + Callback color_cb = { .type = kCallbackNone }; + bool can_free_cb = false; + TryState tstate; + Error err = ERROR_INIT; + const char *err_errmsg = (const char *)e_intern2; + bool dgc_ret = true; + bool tl_ret = true; + + if (colored_ccline->prompt_id != prev_prompt_id) { + prev_prompt_errors = 0; + prev_prompt_id = colored_ccline->prompt_id; + } else if (prev_prompt_errors >= MAX_CB_ERRORS) { + goto color_cmdline_end; + } + if (colored_ccline->highlight_callback.type != kCallbackNone) { + // Currently this should only happen while processing input() prompts. + assert(colored_ccline->input_fn); + color_cb = colored_ccline->highlight_callback; + } else if (colored_ccline->cmdfirstc == ':') { + try_enter(&tstate); + err_errmsg = N_( + "E5408: Unable to get g:Nvim_color_cmdline callback: %s"); + dgc_ret = tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_cmdline"), + &color_cb); + tl_ret = try_leave(&tstate, &err); + can_free_cb = true; + } else if (colored_ccline->cmdfirstc == '=') { + try_enter(&tstate); + err_errmsg = N_( + "E5409: Unable to get g:Nvim_color_expr callback: %s"); + dgc_ret = tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_expr"), + &color_cb); + tl_ret = try_leave(&tstate, &err); + can_free_cb = true; + } + if (!tl_ret || !dgc_ret) { + goto color_cmdline_error; + } + + if (color_cb.type == kCallbackNone) { + goto color_cmdline_end; + } + if (colored_ccline->cmdbuff[colored_ccline->cmdlen] != NUL) { + arg_allocated = true; + arg.vval.v_string = xmemdupz((const char *)colored_ccline->cmdbuff, + (size_t)colored_ccline->cmdlen); + } + // msg_start() called by e.g. :echo may shift command-line to the first column + // even though msg_silent is here. Two ways to workaround this problem without + // altering message.c: use full_screen or save and restore msg_col. + // + // Saving and restoring full_screen does not work well with :redraw!. Saving + // and restoring msg_col is neither ideal, but while with full_screen it + // appears shifted one character to the right and cursor position is no longer + // correct, with msg_col it just misses leading `:`. Since `redraw!` in + // callback lags this is least of the user problems. + // + // Also using try_enter() because error messages may overwrite typed + // command-line which is not expected. + getln_interrupted_highlight = false; + try_enter(&tstate); + err_errmsg = N_("E5407: Callback has thrown an exception: %s"); + const int saved_msg_col = msg_col; + msg_silent++; + const bool cbcall_ret = callback_call(&color_cb, 1, &arg, &tv); + msg_silent--; + msg_col = saved_msg_col; + if (got_int) { + getln_interrupted_highlight = true; + } + if (!try_leave(&tstate, &err) || !cbcall_ret) { + goto color_cmdline_error; + } + if (tv.v_type != VAR_LIST) { + PRINT_ERRMSG(_("E5400: Callback should return list")); + goto color_cmdline_error; + } + if (tv.vval.v_list == NULL) { + goto color_cmdline_end; + } + varnumber_T prev_end = 0; + int i = 0; + for (const listitem_T *li = tv.vval.v_list->lv_first; + li != NULL; li = li->li_next, i++) { + if (li->li_tv.v_type != VAR_LIST) { + PRINT_ERRMSG(_("E5401: List item %i is not a List"), i); + goto color_cmdline_error; + } + const list_T *const l = li->li_tv.vval.v_list; + if (tv_list_len(l) != 3) { + PRINT_ERRMSG(_("E5402: List item %i has incorrect length: %li /= 3"), + i, tv_list_len(l)); + goto color_cmdline_error; + } + bool error = false; + const varnumber_T start = tv_get_number_chk(&l->lv_first->li_tv, &error); + if (error) { + goto color_cmdline_error; + } else if (!(prev_end <= start && start < colored_ccline->cmdlen)) { + PRINT_ERRMSG(_("E5403: Chunk %i start %" PRIdVARNUMBER " not in range " + "[%" PRIdVARNUMBER ", %i)"), + i, start, prev_end, colored_ccline->cmdlen); + goto color_cmdline_error; + } else if (utf8len_tab_zero[(uint8_t)colored_ccline->cmdbuff[start]] == 0) { + PRINT_ERRMSG(_("E5405: Chunk %i start %" PRIdVARNUMBER " splits " + "multibyte character"), i, start); + goto color_cmdline_error; + } + if (start != prev_end) { + kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { + .start = prev_end, + .end = start, + .attr = 0, + })); + } + const varnumber_T end = tv_get_number_chk(&l->lv_first->li_next->li_tv, + &error); + if (error) { + goto color_cmdline_error; + } else if (!(start < end && end <= colored_ccline->cmdlen)) { + PRINT_ERRMSG(_("E5404: Chunk %i end %" PRIdVARNUMBER " not in range " + "(%" PRIdVARNUMBER ", %i]"), + i, end, start, colored_ccline->cmdlen); + goto color_cmdline_error; + } else if (end < colored_ccline->cmdlen + && (utf8len_tab_zero[(uint8_t)colored_ccline->cmdbuff[end]] + == 0)) { + PRINT_ERRMSG(_("E5406: Chunk %i end %" PRIdVARNUMBER " splits multibyte " + "character"), i, end); + goto color_cmdline_error; + } + prev_end = end; + const char *const group = tv_get_string_chk(&l->lv_last->li_tv); + if (group == NULL) { + goto color_cmdline_error; + } + const int id = syn_name2id((char_u *)group); + const int attr = (id == 0 ? 0 : syn_id2attr(id)); + kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { + .start = start, + .end = end, + .attr = attr, + })); + } + if (prev_end < colored_ccline->cmdlen) { + kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { + .start = prev_end, + .end = colored_ccline->cmdlen, + .attr = 0, + })); + } + prev_prompt_errors = 0; +color_cmdline_end: + assert(!ERROR_SET(&err)); + if (can_free_cb) { + callback_free(&color_cb); + } + xfree(ret_ccline_colors->cmdbuff); + // Note: errors “output” is cached just as well as regular results. + ret_ccline_colors->prompt_id = colored_ccline->prompt_id; + if (arg_allocated) { + ret_ccline_colors->cmdbuff = (char *)arg.vval.v_string; + } else { + ret_ccline_colors->cmdbuff = xmemdupz((const char *)colored_ccline->cmdbuff, + (size_t)colored_ccline->cmdlen); + } + tv_clear(&tv); + return ret; +color_cmdline_error: + if (ERROR_SET(&err)) { + PRINT_ERRMSG(_(err_errmsg), err.msg); + api_clear_error(&err); + } + assert(printed_errmsg); + (void)printed_errmsg; + + prev_prompt_errors++; + kv_size(ret_ccline_colors->colors) = 0; + redrawcmdline(); + ret = false; + goto color_cmdline_end; +#undef PRINT_ERRMSG +} + /* * Draw part of the cmdline at the current cursor position. But draw stars * when cmdline_star is TRUE. */ static void draw_cmdline(int start, int len) { - int i; + if (!color_cmdline(&ccline, &last_ccline_colors)) { + return; + } - if (cmdline_star > 0) - for (i = 0; i < len; ++i) { + if (cmdline_star > 0) { + for (int i = 0; i < len; i++) { msg_putchar('*'); - if (has_mbyte) + if (has_mbyte) { i += (*mb_ptr2len)(ccline.cmdbuff + start + i) - 1; + } } - else if (p_arshape && !p_tbidi && enc_utf8 && len > 0) { - static int buflen = 0; - char_u *p; - int j; - int newlen = 0; + } else if (p_arshape && !p_tbidi && enc_utf8 && len > 0) { + bool do_arabicshape = false; int mb_l; - int pc, pc1 = 0; - int prev_c = 0; - int prev_c1 = 0; - int u8c; - int u8cc[MAX_MCO]; - int nc = 0; + for (int i = start; i < start + len; i += mb_l) { + char_u *p = ccline.cmdbuff + i; + int u8cc[MAX_MCO]; + int u8c = utfc_ptr2char_len(p, u8cc, start + len - i); + mb_l = utfc_ptr2len_len(p, start + len - i); + if (arabic_char(u8c)) { + do_arabicshape = true; + break; + } + } + if (!do_arabicshape) { + goto draw_cmdline_no_arabicshape; + } - /* - * Do arabic shaping into a temporary buffer. This is very - * inefficient! - */ + static int buflen = 0; + + // Do arabic shaping into a temporary buffer. This is very + // inefficient! if (len * 2 + 2 > buflen) { - /* Re-allocate the buffer. We keep it around to avoid a lot of - * alloc()/free() calls. */ + // Re-allocate the buffer. We keep it around to avoid a lot of + // alloc()/free() calls. xfree(arshape_buf); buflen = len * 2 + 2; arshape_buf = xmalloc(buflen); } + int newlen = 0; if (utf_iscomposing(utf_ptr2char(ccline.cmdbuff + start))) { - /* Prepend a space to draw the leading composing char on. */ + // Prepend a space to draw the leading composing char on. arshape_buf[0] = ' '; newlen = 1; } - for (j = start; j < start + len; j += mb_l) { - p = ccline.cmdbuff + j; - u8c = utfc_ptr2char_len(p, u8cc, start + len - j); - mb_l = utfc_ptr2len_len(p, start + len - j); + int prev_c = 0; + int prev_c1 = 0; + for (int i = start; i < start + len; i += mb_l) { + char_u *p = ccline.cmdbuff + i; + int u8cc[MAX_MCO]; + int u8c = utfc_ptr2char_len(p, u8cc, start + len - i); + mb_l = utfc_ptr2len_len(p, start + len - i); if (arabic_char(u8c)) { - /* Do Arabic shaping. */ + int pc; + int pc1 = 0; + int nc = 0; + // Do Arabic shaping. if (cmdmsg_rl) { - /* displaying from right to left */ + // Displaying from right to left. pc = prev_c; pc1 = prev_c1; prev_c1 = u8cc[0]; - if (j + mb_l >= start + len) + if (i + mb_l >= start + len) { nc = NUL; - else + } else { nc = utf_ptr2char(p + mb_l); + } } else { - /* displaying from left to right */ - if (j + mb_l >= start + len) + // Displaying from left to right. + if (i + mb_l >= start + len) { pc = NUL; - else { + } else { int pcc[MAX_MCO]; - pc = utfc_ptr2char_len(p + mb_l, pcc, - start + len - j - mb_l); + pc = utfc_ptr2char_len(p + mb_l, pcc, start + len - i - mb_l); pc1 = pcc[0]; } nc = prev_c; @@ -2377,8 +2685,23 @@ static void draw_cmdline(int start, int len) } msg_outtrans_len(arshape_buf, newlen); - } else - msg_outtrans_len(ccline.cmdbuff + start, len); + } else { +draw_cmdline_no_arabicshape: + if (kv_size(last_ccline_colors.colors)) { + for (size_t i = 0; i < kv_size(last_ccline_colors.colors); i++) { + CmdlineColorChunk chunk = kv_A(last_ccline_colors.colors, i); + if (chunk.end <= start) { + continue; + } + const int chunk_start = MAX(chunk.start, start); + msg_outtrans_len_attr(ccline.cmdbuff + chunk_start, + chunk.end - chunk_start, + chunk.attr); + } + } else { + msg_outtrans_len(ccline.cmdbuff + start, len); + } + } } /* @@ -5395,6 +5718,7 @@ static int ex_window(void) i = RedrawingDisabled; RedrawingDisabled = 0; + int save_count = save_batch_count(); /* * Call the main loop until <CR> or CTRL-C is typed. @@ -5403,6 +5727,7 @@ static int ex_window(void) normal_enter(true, false); RedrawingDisabled = i; + restore_batch_count(save_count); int save_KeyTyped = KeyTyped; diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index ca0134043c..36562c0be9 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -74,6 +74,7 @@ local get_flags = function(o) {'gettext'}, {'noglob'}, {'normal_fname_chars', 'P_NFNAME'}, + {'normal_dname_chars', 'P_NDNAME'}, {'pri_mkrc'}, {'deny_in_modelines', 'P_NO_ML'}, {'deny_duplicates', 'P_NODUP'}, diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index a22b716bb6..4f8a8528a0 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -92,17 +92,15 @@ static int typeahead_char = 0; /* typeahead char that's not flushed */ */ static int block_redo = FALSE; -/* - * Make a hash value for a mapping. - * "mode" is the lower 4 bits of the State for the mapping. - * "c1" is the first character of the "lhs". - * Returns a value between 0 and 255, index in maphash. - * Put Normal/Visual mode mappings mostly separately from Insert/Cmdline mode. - */ +// Make a hash value for a mapping. +// "mode" is the lower 4 bits of the State for the mapping. +// "c1" is the first character of the "lhs". +// Returns a value between 0 and 255, index in maphash. +// Put Normal/Visual mode mappings mostly separately from Insert/Cmdline mode. #define MAP_HASH(mode, \ c1) (((mode) & \ (NORMAL + VISUAL + SELECTMODE + \ - OP_PENDING)) ? (c1) : ((c1) ^ 0x80)) + OP_PENDING + TERM_FOCUS)) ? (c1) : ((c1) ^ 0x80)) // Each mapping is put in one of the MAX_MAPHASH hash lists, // to speed up finding it. @@ -870,20 +868,15 @@ int ins_typebuf(char_u *str, int noremap, int offset, int nottyped, bool silent) addlen = (int)STRLEN(str); - /* - * Easy case: there is room in front of typebuf.tb_buf[typebuf.tb_off] - */ if (offset == 0 && addlen <= typebuf.tb_off) { + // Easy case: there is room in front of typebuf.tb_buf[typebuf.tb_off] typebuf.tb_off -= addlen; memmove(typebuf.tb_buf + typebuf.tb_off, str, (size_t)addlen); - } - /* - * Need to allocate a new buffer. - * In typebuf.tb_buf there must always be room for 3 * MAXMAPLEN + 4 - * characters. We add some extra room to avoid having to allocate too - * often. - */ - else { + } else { + // Need to allocate a new buffer. + // In typebuf.tb_buf there must always be room for 3 * MAXMAPLEN + 4 + // characters. We add some extra room to avoid having to allocate too + // often. newoff = MAXMAPLEN + 4; newlen = typebuf.tb_len + addlen + newoff + 4 * (MAXMAPLEN + 4); if (newlen < 0) { /* string is getting too long */ @@ -1665,10 +1658,10 @@ static int vgetorpeek(int advance) } if (c != NUL && !got_int) { if (advance) { - /* KeyTyped = FALSE; When the command that stuffed something - * was typed, behave like the stuffed command was typed. - * needed for CTRL-W CTRl-] to open a fold, for example. */ - KeyStuffed = TRUE; + // KeyTyped = FALSE; When the command that stuffed something + // was typed, behave like the stuffed command was typed. + // needed for CTRL-W CTRL-] to open a fold, for example. + KeyStuffed = true; } if (typebuf.tb_no_abbr_cnt == 0) typebuf.tb_no_abbr_cnt = 1; /* no abbreviations now */ diff --git a/src/nvim/gettext.h b/src/nvim/gettext.h index aa0e97233e..60317b8484 100644 --- a/src/nvim/gettext.h +++ b/src/nvim/gettext.h @@ -13,6 +13,7 @@ #else # define _(x) ((char *)(x)) # define N_(x) x +# define ngettext(x, xs, n) ((n) == 1 ? (x) : (xs)) # define bindtextdomain(x, y) // empty # define bind_textdomain_codeset(x, y) // empty # define textdomain(x) // empty diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 13ecafcbe3..300e506854 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -407,6 +407,9 @@ EXTERN int garbage_collect_at_exit INIT(= FALSE); /* ID of script being sourced or was sourced to define the current function. */ EXTERN scid_T current_SID INIT(= 0); + +EXTERN bool did_source_packages INIT(= false); + // Scope information for the code that indirectly triggered the current // provider function call EXTERN struct caller_scope { @@ -1143,8 +1146,9 @@ EXTERN char_u e_winheight[] INIT(= N_( EXTERN char_u e_winwidth[] INIT(= N_( "E592: 'winwidth' cannot be smaller than 'winminwidth'")); EXTERN char_u e_write[] INIT(= N_("E80: Error while writing")); -EXTERN char_u e_zerocount[] INIT(= N_("Zero count")); -EXTERN char_u e_usingsid[] INIT(= N_("E81: Using <SID> not in a script context")); +EXTERN char_u e_zerocount[] INIT(= N_("E939: Positive count required")); +EXTERN char_u e_usingsid[] INIT(= N_( + "E81: Using <SID> not in a script context")); EXTERN char_u e_intern2[] INIT(= N_("E685: Internal error: %s")); EXTERN char_u e_maxmempat[] INIT(= N_( "E363: pattern uses more memory than 'maxmempattern'")); diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index fd194a4080..279d45bb0a 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -515,34 +515,41 @@ int cin_isscopedecl(char_u *s) /* Maximum number of lines to search back for a "namespace" line. */ #define FIND_NAMESPACE_LIM 20 -/* - * Recognize a "namespace" scope declaration. - */ -static int cin_is_cpp_namespace(char_u *s) +// Recognize a "namespace" scope declaration. +static bool cin_is_cpp_namespace(char_u *s) { - char_u *p; - int has_name = FALSE; + char_u *p; + bool has_name = false; + bool has_name_start = false; s = cin_skipcomment(s); if (STRNCMP(s, "namespace", 9) == 0 && (s[9] == NUL || !vim_iswordc(s[9]))) { p = cin_skipcomment(skipwhite(s + 9)); while (*p != NUL) { if (ascii_iswhite(*p)) { - has_name = TRUE; /* found end of a name */ + has_name = true; // found end of a name p = cin_skipcomment(skipwhite(p)); } else if (*p == '{') { break; } else if (vim_iswordc(*p)) { - if (has_name) - return FALSE; /* word character after skipping past name */ - ++p; + has_name_start = true; + if (has_name) { + return false; // word character after skipping past name + } + p++; + } else if (p[0] == ':' && p[1] == ':' && vim_iswordc(p[2])) { + if (!has_name_start || has_name) { + return false; + } + // C++ 17 nested namespace + p += 3; } else { - return FALSE; + return false; } } - return TRUE; + return true; } - return FALSE; + return false; } /* @@ -727,16 +734,20 @@ static int cin_ispreproc(char_u *s) return FALSE; } -/* - * Return TRUE if line "*pp" at "*lnump" is a preprocessor statement or a - * continuation line of a preprocessor statement. Decrease "*lnump" to the - * start and return the line in "*pp". - */ -static int cin_ispreproc_cont(char_u **pp, linenr_T *lnump) +/// Return TRUE if line "*pp" at "*lnump" is a preprocessor statement or a +/// continuation line of a preprocessor statement. Decrease "*lnump" to the +/// start and return the line in "*pp". +/// Put the amount of indent in "*amount". +static int cin_ispreproc_cont(char_u **pp, linenr_T *lnump, int *amount) { char_u *line = *pp; linenr_T lnum = *lnump; - int retval = FALSE; + int retval = false; + int candidate_amount = *amount; + + if (*line != NUL && line[STRLEN(line) - 1] == '\\') { + candidate_amount = get_indent_lnum(lnum); + } for (;; ) { if (cin_ispreproc(line)) { @@ -751,8 +762,12 @@ static int cin_ispreproc_cont(char_u **pp, linenr_T *lnump) break; } - if (lnum != *lnump) + if (lnum != *lnump) { *pp = ml_get(*lnump); + } + if (retval) { + *amount = candidate_amount; + } return retval; } @@ -1987,10 +2002,12 @@ int get_c_indent(void) amount = -1; for (lnum = cur_curpos.lnum - 1; lnum > our_paren_pos.lnum; --lnum) { l = skipwhite(ml_get(lnum)); - if (cin_nocode(l)) /* skip comment lines */ + if (cin_nocode(l)) { // skip comment lines continue; - if (cin_ispreproc_cont(&l, &lnum)) - continue; /* ignore #define, #if, etc. */ + } + if (cin_ispreproc_cont(&l, &lnum, &amount)) { + continue; // ignore #define, #if, etc. + } curwin->w_cursor.lnum = lnum; /* Skip a comment or raw string. XXX */ @@ -2346,15 +2363,14 @@ int get_c_indent(void) * up with it. */ if (curwin->w_cursor.lnum <= ourscope) { - /* we reached end of scope: - * if looking for an enum or structure initialization - * go further back: - * if it is an initializer (enum xxx or xxx =), then - * don't add ind_continuation, otherwise it is a variable - * declaration: - * int x, - * here; <-- add ind_continuation - */ + // We reached end of scope: + // If looking for a enum or structure initialization + // go further back: + // If it is an initializer (enum xxx or xxx =), then + // don't add ind_continuation, otherwise it is a variable + // declaration: + // int x, + // here; <-- add ind_continuation if (lookfor == LOOKFOR_ENUM_OR_INIT) { if (curwin->w_cursor.lnum == 0 || curwin->w_cursor.lnum @@ -2382,11 +2398,12 @@ int get_c_indent(void) continue; } - /* - * Skip preprocessor directives and blank lines. - */ - if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum)) + // + // Skip preprocessor directives and blank lines. + // + if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, &amount)) { continue; + } if (cin_nocode(l)) continue; @@ -2490,9 +2507,10 @@ int get_c_indent(void) continue; } - /* Skip preprocessor directives and blank lines. */ - if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum)) + // Skip preprocessor directives and blank lines. + if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, &amount)) { continue; + } /* Finally the actual check for "namespace". */ if (cin_is_cpp_namespace(l)) { @@ -2655,9 +2673,10 @@ int get_c_indent(void) * unlocked it) */ l = get_cursor_line_ptr(); - if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum) - || cin_nocode(l)) + if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, &amount) + || cin_nocode(l)) { continue; + } /* * Are we at the start of a cpp base class declaration or @@ -3302,11 +3321,12 @@ term_again: break; } - /* - * Skip preprocessor directives and blank lines. - */ - if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum)) + // + // Skip preprocessor directives and blank lines. + // + if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, &amount)) { continue; + } if (cin_nocode(l)) continue; @@ -3398,9 +3418,10 @@ term_again: while (curwin->w_cursor.lnum > 1) { look = ml_get(--curwin->w_cursor.lnum); - if (!(cin_nocode(look) || cin_ispreproc_cont( - &look, &curwin->w_cursor.lnum))) + if (!(cin_nocode(look) + || cin_ispreproc_cont(&look, &curwin->w_cursor.lnum, &amount))) { break; + } } if (curwin->w_cursor.lnum > 0 && cin_ends_in(look, (char_u *)"}", NULL)) diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index 3d7ebb6382..a75fe793ac 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -140,155 +140,153 @@ static char_u modifier_keys_table[] = }; static struct key_name_entry { - int key; /* Special key code or ascii value */ - char_u *name; /* Name of key */ + int key; // Special key code or ascii value + char *name; // Name of key } key_names_table[] = { - {' ', (char_u *)"Space"}, - {TAB, (char_u *)"Tab"}, - {K_TAB, (char_u *)"Tab"}, - {NL, (char_u *)"NL"}, - {NL, (char_u *)"NewLine"}, /* Alternative name */ - {NL, (char_u *)"LineFeed"}, /* Alternative name */ - {NL, (char_u *)"LF"}, /* Alternative name */ - {CAR, (char_u *)"CR"}, - {CAR, (char_u *)"Return"}, /* Alternative name */ - {CAR, (char_u *)"Enter"}, /* Alternative name */ - {K_BS, (char_u *)"BS"}, - {K_BS, (char_u *)"BackSpace"}, /* Alternative name */ - {ESC, (char_u *)"Esc"}, - {CSI, (char_u *)"CSI"}, - {K_CSI, (char_u *)"xCSI"}, - {'|', (char_u *)"Bar"}, - {'\\', (char_u *)"Bslash"}, - {K_DEL, (char_u *)"Del"}, - {K_DEL, (char_u *)"Delete"}, /* Alternative name */ - {K_KDEL, (char_u *)"kDel"}, - {K_UP, (char_u *)"Up"}, - {K_DOWN, (char_u *)"Down"}, - {K_LEFT, (char_u *)"Left"}, - {K_RIGHT, (char_u *)"Right"}, - {K_XUP, (char_u *)"xUp"}, - {K_XDOWN, (char_u *)"xDown"}, - {K_XLEFT, (char_u *)"xLeft"}, - {K_XRIGHT, (char_u *)"xRight"}, - - {K_F1, (char_u *)"F1"}, - {K_F2, (char_u *)"F2"}, - {K_F3, (char_u *)"F3"}, - {K_F4, (char_u *)"F4"}, - {K_F5, (char_u *)"F5"}, - {K_F6, (char_u *)"F6"}, - {K_F7, (char_u *)"F7"}, - {K_F8, (char_u *)"F8"}, - {K_F9, (char_u *)"F9"}, - {K_F10, (char_u *)"F10"}, - - {K_F11, (char_u *)"F11"}, - {K_F12, (char_u *)"F12"}, - {K_F13, (char_u *)"F13"}, - {K_F14, (char_u *)"F14"}, - {K_F15, (char_u *)"F15"}, - {K_F16, (char_u *)"F16"}, - {K_F17, (char_u *)"F17"}, - {K_F18, (char_u *)"F18"}, - {K_F19, (char_u *)"F19"}, - {K_F20, (char_u *)"F20"}, - - {K_F21, (char_u *)"F21"}, - {K_F22, (char_u *)"F22"}, - {K_F23, (char_u *)"F23"}, - {K_F24, (char_u *)"F24"}, - {K_F25, (char_u *)"F25"}, - {K_F26, (char_u *)"F26"}, - {K_F27, (char_u *)"F27"}, - {K_F28, (char_u *)"F28"}, - {K_F29, (char_u *)"F29"}, - {K_F30, (char_u *)"F30"}, - - {K_F31, (char_u *)"F31"}, - {K_F32, (char_u *)"F32"}, - {K_F33, (char_u *)"F33"}, - {K_F34, (char_u *)"F34"}, - {K_F35, (char_u *)"F35"}, - {K_F36, (char_u *)"F36"}, - {K_F37, (char_u *)"F37"}, - - {K_XF1, (char_u *)"xF1"}, - {K_XF2, (char_u *)"xF2"}, - {K_XF3, (char_u *)"xF3"}, - {K_XF4, (char_u *)"xF4"}, - - {K_HELP, (char_u *)"Help"}, - {K_UNDO, (char_u *)"Undo"}, - {K_INS, (char_u *)"Insert"}, - {K_INS, (char_u *)"Ins"}, /* Alternative name */ - {K_KINS, (char_u *)"kInsert"}, - {K_HOME, (char_u *)"Home"}, - {K_KHOME, (char_u *)"kHome"}, - {K_XHOME, (char_u *)"xHome"}, - {K_ZHOME, (char_u *)"zHome"}, - {K_END, (char_u *)"End"}, - {K_KEND, (char_u *)"kEnd"}, - {K_XEND, (char_u *)"xEnd"}, - {K_ZEND, (char_u *)"zEnd"}, - {K_PAGEUP, (char_u *)"PageUp"}, - {K_PAGEDOWN, (char_u *)"PageDown"}, - {K_KPAGEUP, (char_u *)"kPageUp"}, - {K_KPAGEDOWN, (char_u *)"kPageDown"}, - - {K_KPLUS, (char_u *)"kPlus"}, - {K_KMINUS, (char_u *)"kMinus"}, - {K_KDIVIDE, (char_u *)"kDivide"}, - {K_KMULTIPLY, (char_u *)"kMultiply"}, - {K_KENTER, (char_u *)"kEnter"}, - {K_KPOINT, (char_u *)"kPoint"}, - - {K_K0, (char_u *)"k0"}, - {K_K1, (char_u *)"k1"}, - {K_K2, (char_u *)"k2"}, - {K_K3, (char_u *)"k3"}, - {K_K4, (char_u *)"k4"}, - {K_K5, (char_u *)"k5"}, - {K_K6, (char_u *)"k6"}, - {K_K7, (char_u *)"k7"}, - {K_K8, (char_u *)"k8"}, - {K_K9, (char_u *)"k9"}, - - {'<', (char_u *)"lt"}, - - {K_MOUSE, (char_u *)"Mouse"}, - {K_LEFTMOUSE, (char_u *)"LeftMouse"}, - {K_LEFTMOUSE_NM, (char_u *)"LeftMouseNM"}, - {K_LEFTDRAG, (char_u *)"LeftDrag"}, - {K_LEFTRELEASE, (char_u *)"LeftRelease"}, - {K_LEFTRELEASE_NM, (char_u *)"LeftReleaseNM"}, - {K_MIDDLEMOUSE, (char_u *)"MiddleMouse"}, - {K_MIDDLEDRAG, (char_u *)"MiddleDrag"}, - {K_MIDDLERELEASE, (char_u *)"MiddleRelease"}, - {K_RIGHTMOUSE, (char_u *)"RightMouse"}, - {K_RIGHTDRAG, (char_u *)"RightDrag"}, - {K_RIGHTRELEASE, (char_u *)"RightRelease"}, - {K_MOUSEDOWN, (char_u *)"ScrollWheelUp"}, - {K_MOUSEUP, (char_u *)"ScrollWheelDown"}, - {K_MOUSELEFT, (char_u *)"ScrollWheelRight"}, - {K_MOUSERIGHT, (char_u *)"ScrollWheelLeft"}, - {K_MOUSEDOWN, (char_u *)"MouseDown"}, /* OBSOLETE: Use */ - {K_MOUSEUP, (char_u *)"MouseUp"}, /* ScrollWheelXXX instead */ - {K_X1MOUSE, (char_u *)"X1Mouse"}, - {K_X1DRAG, (char_u *)"X1Drag"}, - {K_X1RELEASE, (char_u *)"X1Release"}, - {K_X2MOUSE, (char_u *)"X2Mouse"}, - {K_X2DRAG, (char_u *)"X2Drag"}, - {K_X2RELEASE, (char_u *)"X2Release"}, - {K_DROP, (char_u *)"Drop"}, - {K_ZERO, (char_u *)"Nul"}, - {K_SNR, (char_u *)"SNR"}, - {K_PLUG, (char_u *)"Plug"}, - {K_PASTE, (char_u *)"Paste"}, - {K_FOCUSGAINED, (char_u *)"FocusGained"}, - {K_FOCUSLOST, (char_u *)"FocusLost"}, - {0, NULL} + { ' ', "Space" }, + { TAB, "Tab" }, + { K_TAB, "Tab" }, + { NL, "NL" }, + { NL, "NewLine" }, // Alternative name + { NL, "LineFeed" }, // Alternative name + { NL, "LF" }, // Alternative name + { CAR, "CR" }, + { CAR, "Return" }, // Alternative name + { CAR, "Enter" }, // Alternative name + { K_BS, "BS" }, + { K_BS, "BackSpace" }, // Alternative name + { ESC, "Esc" }, + { CSI, "CSI" }, + { K_CSI, "xCSI" }, + { '|', "Bar" }, + { '\\', "Bslash" }, + { K_DEL, "Del" }, + { K_DEL, "Delete" }, // Alternative name + { K_KDEL, "kDel" }, + { K_UP, "Up" }, + { K_DOWN, "Down" }, + { K_LEFT, "Left" }, + { K_RIGHT, "Right" }, + { K_XUP, "xUp" }, + { K_XDOWN, "xDown" }, + { K_XLEFT, "xLeft" }, + { K_XRIGHT, "xRight" }, + + { K_F1, "F1" }, + { K_F2, "F2" }, + { K_F3, "F3" }, + { K_F4, "F4" }, + { K_F5, "F5" }, + { K_F6, "F6" }, + { K_F7, "F7" }, + { K_F8, "F8" }, + { K_F9, "F9" }, + { K_F10, "F10" }, + + { K_F11, "F11" }, + { K_F12, "F12" }, + { K_F13, "F13" }, + { K_F14, "F14" }, + { K_F15, "F15" }, + { K_F16, "F16" }, + { K_F17, "F17" }, + { K_F18, "F18" }, + { K_F19, "F19" }, + { K_F20, "F20" }, + + { K_F21, "F21" }, + { K_F22, "F22" }, + { K_F23, "F23" }, + { K_F24, "F24" }, + { K_F25, "F25" }, + { K_F26, "F26" }, + { K_F27, "F27" }, + { K_F28, "F28" }, + { K_F29, "F29" }, + { K_F30, "F30" }, + + { K_F31, "F31" }, + { K_F32, "F32" }, + { K_F33, "F33" }, + { K_F34, "F34" }, + { K_F35, "F35" }, + { K_F36, "F36" }, + { K_F37, "F37" }, + + { K_XF1, "xF1" }, + { K_XF2, "xF2" }, + { K_XF3, "xF3" }, + { K_XF4, "xF4" }, + + { K_HELP, "Help" }, + { K_UNDO, "Undo" }, + { K_INS, "Insert" }, + { K_INS, "Ins" }, // Alternative name + { K_KINS, "kInsert" }, + { K_HOME, "Home" }, + { K_KHOME, "kHome" }, + { K_XHOME, "xHome" }, + { K_ZHOME, "zHome" }, + { K_END, "End" }, + { K_KEND, "kEnd" }, + { K_XEND, "xEnd" }, + { K_ZEND, "zEnd" }, + { K_PAGEUP, "PageUp" }, + { K_PAGEDOWN, "PageDown" }, + { K_KPAGEUP, "kPageUp" }, + { K_KPAGEDOWN, "kPageDown" }, + + { K_KPLUS, "kPlus" }, + { K_KMINUS, "kMinus" }, + { K_KDIVIDE, "kDivide" }, + { K_KMULTIPLY, "kMultiply" }, + { K_KENTER, "kEnter" }, + { K_KPOINT, "kPoint" }, + + { K_K0, "k0" }, + { K_K1, "k1" }, + { K_K2, "k2" }, + { K_K3, "k3" }, + { K_K4, "k4" }, + { K_K5, "k5" }, + { K_K6, "k6" }, + { K_K7, "k7" }, + { K_K8, "k8" }, + { K_K9, "k9" }, + + { '<', "lt" }, + + { K_MOUSE, "Mouse" }, + { K_LEFTMOUSE, "LeftMouse" }, + { K_LEFTMOUSE_NM, "LeftMouseNM" }, + { K_LEFTDRAG, "LeftDrag" }, + { K_LEFTRELEASE, "LeftRelease" }, + { K_LEFTRELEASE_NM, "LeftReleaseNM" }, + { K_MIDDLEMOUSE, "MiddleMouse" }, + { K_MIDDLEDRAG, "MiddleDrag" }, + { K_MIDDLERELEASE, "MiddleRelease" }, + { K_RIGHTMOUSE, "RightMouse" }, + { K_RIGHTDRAG, "RightDrag" }, + { K_RIGHTRELEASE, "RightRelease" }, + { K_MOUSEDOWN, "ScrollWheelUp" }, + { K_MOUSEUP, "ScrollWheelDown" }, + { K_MOUSELEFT, "ScrollWheelRight" }, + { K_MOUSERIGHT, "ScrollWheelLeft" }, + { K_MOUSEDOWN, "MouseDown" }, // OBSOLETE: Use + { K_MOUSEUP, "MouseUp" }, // ScrollWheelXXX instead + { K_X1MOUSE, "X1Mouse" }, + { K_X1DRAG, "X1Drag" }, + { K_X1RELEASE, "X1Release" }, + { K_X2MOUSE, "X2Mouse" }, + { K_X2DRAG, "X2Drag" }, + { K_X2RELEASE, "X2Release" }, + { K_DROP, "Drop" }, + { K_ZERO, "Nul" }, + { K_SNR, "SNR" }, + { K_PLUG, "Plug" }, + { K_PASTE, "Paste" }, + { 0, NULL } }; static struct mousetable { @@ -721,7 +719,7 @@ int find_special_key_in_table(int c) */ int get_special_key_code(const char_u *name) { - char_u *table_name; + char *table_name; int i, j; for (i = 0; key_names_table[i].name != NULL; i++) { diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h index b8fed77a90..ee64854c98 100644 --- a/src/nvim/keymap.h +++ b/src/nvim/keymap.h @@ -428,8 +428,6 @@ enum key_extra { #define K_CMDWIN TERMCAP2KEY(KS_EXTRA, KE_CMDWIN) #define K_DROP TERMCAP2KEY(KS_EXTRA, KE_DROP) -#define K_FOCUSGAINED TERMCAP2KEY(KS_EXTRA, KE_FOCUSGAINED) -#define K_FOCUSLOST TERMCAP2KEY(KS_EXTRA, KE_FOCUSLOST) #define K_EVENT TERMCAP2KEY(KS_EXTRA, KE_EVENT) #define K_PASTE TERMCAP2KEY(KS_EXTRA, KE_PASTE) diff --git a/src/nvim/log.c b/src/nvim/log.c index 3baf0b2ebd..7bfe5c4089 100644 --- a/src/nvim/log.c +++ b/src/nvim/log.c @@ -95,8 +95,12 @@ void log_unlock(void) uv_mutex_unlock(&mutex); } -bool do_log(int log_level, const char *func_name, int line_num, bool eol, - const char* fmt, ...) FUNC_ATTR_UNUSED +/// @param context description of a shared context or subsystem +/// @param func_name function name, or NULL +/// @param line_num source line number, or -1 +bool do_log(int log_level, const char *context, const char *func_name, + int line_num, bool eol, const char *fmt, ...) + FUNC_ATTR_UNUSED { if (log_level < MIN_LOG_LEVEL) { return false; @@ -112,8 +116,8 @@ bool do_log(int log_level, const char *func_name, int line_num, bool eol, va_list args; va_start(args, fmt); - ret = v_do_log_to_file(log_file, log_level, func_name, line_num, eol, - fmt, args); + ret = v_do_log_to_file(log_file, log_level, context, func_name, line_num, + eol, fmt, args); va_end(args); if (log_file != stderr && log_file != stdout) { @@ -151,7 +155,7 @@ FILE *open_log_file(void) static bool opening_log_file = false; // check if it's a recursive call if (opening_log_file) { - do_log_to_file(stderr, ERROR_LOG_LEVEL, __func__, __LINE__, true, + do_log_to_file(stderr, ERROR_LOG_LEVEL, NULL, __func__, __LINE__, true, "Cannot LOG() recursively."); return stderr; } @@ -171,7 +175,7 @@ FILE *open_log_file(void) // - LOG() is called before early_init() // - Directory does not exist // - File is not writable - do_log_to_file(stderr, ERROR_LOG_LEVEL, __func__, __LINE__, true, + do_log_to_file(stderr, ERROR_LOG_LEVEL, NULL, __func__, __LINE__, true, "Logging to stderr, failed to open $" LOG_FILE_ENV ": %s", log_file_path); return stderr; @@ -201,7 +205,7 @@ void log_callstack_to_file(FILE *log_file, const char *const func_name, // Now we have a command string like: // addr2line -e /path/to/exe -f -p 0x123 0x456 ... - do_log_to_file(log_file, DEBUG_LOG_LEVEL, func_name, line_num, true, + do_log_to_file(log_file, DEBUG_LOG_LEVEL, NULL, func_name, line_num, true, "trace:"); FILE *fp = popen(cmdbuf, "r"); char linebuf[IOSIZE]; @@ -230,27 +234,28 @@ end: } #endif -static bool do_log_to_file(FILE *log_file, int log_level, +static bool do_log_to_file(FILE *log_file, int log_level, const char *context, const char *func_name, int line_num, bool eol, const char* fmt, ...) { va_list args; va_start(args, fmt); - bool ret = v_do_log_to_file(log_file, log_level, func_name, line_num, eol, - fmt, args); + bool ret = v_do_log_to_file(log_file, log_level, context, func_name, + line_num, eol, fmt, args); va_end(args); return ret; } static bool v_do_log_to_file(FILE *log_file, int log_level, - const char *func_name, int line_num, bool eol, - const char* fmt, va_list args) + const char *context, const char *func_name, + int line_num, bool eol, const char *fmt, + va_list args) { static const char *log_levels[] = { [DEBUG_LOG_LEVEL] = "DEBUG", [INFO_LOG_LEVEL] = "INFO ", - [WARNING_LOG_LEVEL] = "WARN ", + [WARN_LOG_LEVEL] = "WARN ", [ERROR_LOG_LEVEL] = "ERROR", }; assert(log_level >= DEBUG_LOG_LEVEL && log_level <= ERROR_LOG_LEVEL); @@ -268,8 +273,15 @@ static bool v_do_log_to_file(FILE *log_file, int log_level, // print the log message prefixed by the current timestamp and pid int64_t pid = os_get_pid(); - if (fprintf(log_file, "%s %s %" PRId64 "/%s:%d: ", date_time, - log_levels[log_level], pid, func_name, line_num) < 0) { + int rv = (line_num == -1 || func_name == NULL) + ? fprintf(log_file, "%s %s %" PRId64 " %s", date_time, + log_levels[log_level], pid, + (context == NULL ? "?:" : context)) + : fprintf(log_file, "%s %s %" PRId64 " %s%s:%d: ", date_time, + log_levels[log_level], pid, + (context == NULL ? "" : context), + func_name, line_num); + if (rv < 0) { return false; } if (vfprintf(log_file, fmt, args) < 0) { diff --git a/src/nvim/log.h b/src/nvim/log.h index 5064d9333b..f378b92039 100644 --- a/src/nvim/log.h +++ b/src/nvim/log.h @@ -6,7 +6,7 @@ #define DEBUG_LOG_LEVEL 0 #define INFO_LOG_LEVEL 1 -#define WARNING_LOG_LEVEL 2 +#define WARN_LOG_LEVEL 2 #define ERROR_LOG_LEVEL 3 #define DLOG(...) @@ -22,42 +22,42 @@ # define MIN_LOG_LEVEL INFO_LOG_LEVEL #endif -#define LOG(level, ...) do_log((level), __func__, __LINE__, true, \ +#define LOG(level, ...) do_log((level), NULL, __func__, __LINE__, true, \ __VA_ARGS__) #if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL # undef DLOG # undef DLOGN -# define DLOG(...) do_log(DEBUG_LOG_LEVEL, __func__, __LINE__, true, \ +# define DLOG(...) do_log(DEBUG_LOG_LEVEL, NULL, __func__, __LINE__, true, \ __VA_ARGS__) -# define DLOGN(...) do_log(DEBUG_LOG_LEVEL, __func__, __LINE__, false, \ +# define DLOGN(...) do_log(DEBUG_LOG_LEVEL, NULL, __func__, __LINE__, false, \ __VA_ARGS__) #endif #if MIN_LOG_LEVEL <= INFO_LOG_LEVEL # undef ILOG # undef ILOGN -# define ILOG(...) do_log(INFO_LOG_LEVEL, __func__, __LINE__, true, \ +# define ILOG(...) do_log(INFO_LOG_LEVEL, NULL, __func__, __LINE__, true, \ __VA_ARGS__) -# define ILOGN(...) do_log(INFO_LOG_LEVEL, __func__, __LINE__, false, \ +# define ILOGN(...) do_log(INFO_LOG_LEVEL, NULL, __func__, __LINE__, false, \ __VA_ARGS__) #endif -#if MIN_LOG_LEVEL <= WARNING_LOG_LEVEL +#if MIN_LOG_LEVEL <= WARN_LOG_LEVEL # undef WLOG # undef WLOGN -# define WLOG(...) do_log(WARNING_LOG_LEVEL, __func__, __LINE__, true, \ +# define WLOG(...) do_log(WARN_LOG_LEVEL, NULL, __func__, __LINE__, true, \ __VA_ARGS__) -# define WLOGN(...) do_log(WARNING_LOG_LEVEL, __func__, __LINE__, false, \ +# define WLOGN(...) do_log(WARN_LOG_LEVEL, NULL, __func__, __LINE__, false, \ __VA_ARGS__) #endif #if MIN_LOG_LEVEL <= ERROR_LOG_LEVEL # undef ELOG # undef ELOGN -# define ELOG(...) do_log(ERROR_LOG_LEVEL, __func__, __LINE__, true, \ +# define ELOG(...) do_log(ERROR_LOG_LEVEL, NULL, __func__, __LINE__, true, \ __VA_ARGS__) -# define ELOGN(...) do_log(ERROR_LOG_LEVEL, __func__, __LINE__, false, \ +# define ELOGN(...) do_log(ERROR_LOG_LEVEL, NULL, __func__, __LINE__, false, \ __VA_ARGS__) #endif diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 54973a6e94..16bb4169c4 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -145,9 +145,8 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL } if (nul1 != NULL) { assert(nul2 != NULL); - // Due to lowercase letter having possibly different byte length then - // uppercase letter can’t shift both strings by the same amount of - // bytes. + // Can't shift both strings by the same amount of bytes: lowercase + // letter may have different byte-length than uppercase. s1_len -= (size_t)(nul1 - s1) + 1; s2_len -= (size_t)(nul2 - s2) + 1; s1 = nul1 + 1; @@ -315,7 +314,7 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL /// Initialize lua interpreter /// -/// Crashes NeoVim if initialization fails. Should be called once per lua +/// Crashes Nvim if initialization fails. Should be called once per lua /// interpreter instance. /// /// @return New lua interpreter instance. diff --git a/src/nvim/main.c b/src/nvim/main.c index 3f828d7be9..ea7a58bda3 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -7,6 +7,11 @@ #include <string.h> #include <stdbool.h> +#ifdef WIN32 +# include <wchar.h> +# include <winnls.h> +#endif + #include <msgpack.h> #include "nvim/ascii.h" @@ -215,10 +220,22 @@ void early_init(void) #ifdef MAKE_LIB int nvim_main(int argc, char **argv) +#elif defined(WIN32) +int wmain(int argc, wchar_t **argv_w) // multibyte args on Windows. #7060 #else int main(int argc, char **argv) #endif { +#if defined(WIN32) && !defined(MAKE_LIB) + char *argv[argc]; + for (int i = 0; i < argc; i++) { + char *buf = NULL; + utf16_to_utf8(argv_w[i], &buf); + assert(buf); + argv[i] = buf; + } +#endif + argv0 = argv[0]; char_u *fname = NULL; // file name from command line @@ -632,6 +649,11 @@ void getout(int exitval) /* Position the cursor again, the autocommands may have moved it */ ui_cursor_goto((int)Rows - 1, 0); + // Apply 'titleold'. + if (p_title && *p_titleold != NUL) { + ui_call_set_title(cstr_as_string((char *)p_titleold)); + } + #if defined(USE_ICONV) && defined(DYNAMIC_ICONV) iconv_end(); #endif @@ -1291,10 +1313,29 @@ static void set_window_layout(mparm_T *paramp) static void load_plugins(void) { if (p_lpl) { - source_runtime((char_u *)"plugin/**/*.vim", DIP_ALL | DIP_NOAFTER); // NOLINT + char_u *rtp_copy = NULL; + + // First add all package directories to 'runtimepath', so that their + // autoload directories can be found. Only if not done already with a + // :packloadall command. + // Make a copy of 'runtimepath', so that source_runtime does not use the + // pack directories. + if (!did_source_packages) { + rtp_copy = vim_strsave(p_rtp); + add_pack_start_dirs(); + } + + source_in_path(rtp_copy == NULL ? p_rtp : rtp_copy, + (char_u *)"plugin/**/*.vim", // NOLINT + DIP_ALL | DIP_NOAFTER); TIME_MSG("loading plugins"); + xfree(rtp_copy); - ex_packloadall(NULL); + // Only source "start" packages if not done already with a :packloadall + // command. + if (!did_source_packages) { + load_start_packages(); + } TIME_MSG("loading packages"); source_runtime((char_u *)"plugin/**/*.vim", DIP_ALL | DIP_AFTER); @@ -1878,54 +1919,47 @@ static void usage(void) signal_stop(); // kill us with CTRL-C here, if you like mch_msg(_("Usage:\n")); - mch_msg(_(" nvim [arguments] [file ...] Edit specified file(s)\n")); - mch_msg(_(" nvim [arguments] - Read text from stdin\n")); - mch_msg(_(" nvim [arguments] -t <tag> Edit file where tag is defined\n")); - mch_msg(_(" nvim [arguments] -q [errorfile] Edit file with first error\n")); - mch_msg(_("\nArguments:\n")); + mch_msg(_(" nvim [options] [file ...] Edit file(s)\n")); + mch_msg(_(" nvim [options] - Read text from stdin\n")); + mch_msg(_(" nvim [options] -t <tag> Edit file where tag is defined\n")); + mch_msg(_(" nvim [options] -q [errorfile] Edit file with first error\n")); + mch_msg(_("\nOptions:\n")); mch_msg(_(" -- Only file names after this\n")); -#if !defined(UNIX) - mch_msg(_(" --literal Don't expand wildcards\n")); -#endif - mch_msg(_(" -e Ex mode\n")); - mch_msg(_(" -E Improved Ex mode\n")); - mch_msg(_(" -s Silent (batch) mode (only for ex mode)\n")); + mch_msg(_(" + Start at end of file\n")); + mch_msg(_(" --cmd <cmd> Execute <cmd> before any config\n")); + mch_msg(_(" +<cmd>, -c <cmd> Execute <cmd> after config and first file\n")); + mch_msg("\n"); + mch_msg(_(" -b Binary mode\n")); mch_msg(_(" -d Diff mode\n")); - mch_msg(_(" -R Read-only mode\n")); - mch_msg(_(" -Z Restricted mode\n")); + mch_msg(_(" -e, -E Ex mode, Improved Ex mode\n")); + mch_msg(_(" -es Silent (batch) mode\n")); + mch_msg(_(" -h, --help Print this help message\n")); + mch_msg(_(" -i <shada> Use this shada file\n")); mch_msg(_(" -m Modifications (writing files) not allowed\n")); mch_msg(_(" -M Modifications in text not allowed\n")); - mch_msg(_(" -b Binary mode\n")); - mch_msg(_(" -l Lisp mode\n")); - mch_msg(_(" -A Arabic mode\n")); - mch_msg(_(" -F Farsi mode\n")); - mch_msg(_(" -H Hebrew mode\n")); - mch_msg(_(" -V[N][file] Be verbose [level N][log messages to file]\n")); - mch_msg(_(" -D Debugging mode\n")); mch_msg(_(" -n No swap file, use memory only\n")); - mch_msg(_(" -r, -L List swap files and exit\n")); - mch_msg(_(" -r <file> Recover crashed session\n")); - mch_msg(_(" -u <vimrc> Use <vimrc> instead of the default\n")); - mch_msg(_(" -i <shada> Use <shada> instead of the default\n")); - mch_msg(_(" --noplugin Don't load plugin scripts\n")); - mch_msg(_(" -o[N] Open N windows (default: one for each file)\n")); - mch_msg(_(" -O[N] Like -o but split vertically\n")); - mch_msg(_(" -p[N] Open N tab pages (default: one for each file)\n")); - mch_msg(_(" + Start at end of file\n")); - mch_msg(_(" +<linenum> Start at line <linenum>\n")); - mch_msg(_(" +/<pattern> Start at first occurrence of <pattern>\n")); - mch_msg(_(" --cmd <command> Execute <command> before loading any vimrc\n")); - mch_msg(_(" -c <command> Execute <command> after loading the first file\n")); + mch_msg(_(" -o[N] Open N windows (default: one per file)\n")); + mch_msg(_(" -O[N] Open N vertical windows (default: one per file)\n")); + mch_msg(_(" -p[N] Open N tab pages (default: one per file)\n")); + mch_msg(_(" -r, -L List swap files\n")); + mch_msg(_(" -r <file> Recover edit state for this file\n")); + mch_msg(_(" -R Read-only mode\n")); mch_msg(_(" -S <session> Source <session> after loading the first file\n")); mch_msg(_(" -s <scriptin> Read Normal mode commands from <scriptin>\n")); - mch_msg(_(" -w <scriptout> Append all typed characters to <scriptout>\n")); - mch_msg(_(" -W <scriptout> Write all typed characters to <scriptout>\n")); - mch_msg(_(" --startuptime <file> Write startup timing messages to <file>\n")); - mch_msg(_(" --api-info Dump API metadata serialized to msgpack and exit\n")); + mch_msg(_(" -u <config> Use this config file\n")); + mch_msg(_(" -v, --version Print version information\n")); + mch_msg(_(" -V[N][file] Verbose [level][file]\n")); + mch_msg(_(" -Z Restricted mode\n")); + mch_msg("\n"); + 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(_(" -v, --version Print version information and exit\n")); - mch_msg(_(" -h, --help Print this help message and exit\n")); +#if !defined(UNIX) + mch_msg(_(" --literal Don't expand wildcards\n")); +#endif + mch_msg(_(" --noplugin Don't load plugins\n")); + mch_msg(_(" --startuptime <file> Write startup timing messages to <file>\n")); + mch_msg(_("\nSee \":help startup-options\" for all options.\n")); } diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 3fad6c789d..b24770a409 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -75,7 +75,7 @@ struct interval { /* * Like utf8len_tab above, but using a zero for illegal lead bytes. */ -static uint8_t utf8len_tab_zero[256] = +const uint8_t utf8len_tab_zero[256] = { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h index ad9e38004c..bf6ccb13a5 100644 --- a/src/nvim/mbyte.h +++ b/src/nvim/mbyte.h @@ -1,6 +1,7 @@ #ifndef NVIM_MBYTE_H #define NVIM_MBYTE_H +#include <stdint.h> #include <stdbool.h> #include <string.h> @@ -18,6 +19,9 @@ #define MB_BYTE2LEN(b) utf8len_tab[b] #define MB_BYTE2LEN_CHECK(b) (((b) < 0 || (b) > 255) ? 1 : utf8len_tab[b]) +// max length of an unicode char +#define MB_MAXCHAR 6 + /* properties used in enc_canon_table[] (first three mutually exclusive) */ #define ENC_8BIT 0x01 #define ENC_DBCS 0x02 @@ -67,6 +71,8 @@ typedef struct { ///< otherwise use '?'. } vimconv_T; +extern const uint8_t utf8len_tab_zero[256]; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mbyte.h.generated.h" #endif diff --git a/src/nvim/menu.c b/src/nvim/menu.c index a498916e5e..88d968704b 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -666,8 +666,6 @@ static void free_menu_string(vimmenu_T *menu, int idx) static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) { dict_T *dict; - char buf[sizeof(menu->mnemonic)]; - int mnemonic_len; if (!menu || (menu->modes & modes) == 0x0) { return NULL; @@ -679,11 +677,15 @@ static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) tv_dict_add_nr(dict, S_LEN("hidden"), menu_is_hidden(menu->dname)); if (menu->mnemonic) { - mnemonic_len = utf_char2bytes(menu->mnemonic, (u_char *)buf); - buf[mnemonic_len] = '\0'; + char buf[MB_MAXCHAR + 1] = { 0 }; // > max value of utf8_char2bytes + utf_char2bytes(menu->mnemonic, (char_u *)buf); tv_dict_add_str(dict, S_LEN("shortcut"), buf); } + if (menu->actext) { + tv_dict_add_str(dict, S_LEN("actext"), (char *)menu->actext); + } + if (menu->modes & MENU_TIP_MODE && menu->strings[MENU_INDEX_TIP]) { tv_dict_add_str(dict, S_LEN("tooltip"), (char *)menu->strings[MENU_INDEX_TIP]); @@ -697,11 +699,9 @@ static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) for (int bit = 0; bit < MENU_MODES; bit++) { if ((menu->modes & modes & (1 << bit)) != 0) { dict_T *impl = tv_dict_alloc(); - if (*menu->strings[bit] == NUL) { - tv_dict_add_str(impl, S_LEN("rhs"), (char *)"<Nop>"); - } else { - tv_dict_add_str(impl, S_LEN("rhs"), (char *)menu->strings[bit]); - } + tv_dict_add_allocated_str(impl, S_LEN("rhs"), + str2special_save((char *)menu->strings[bit], + false, false)); tv_dict_add_nr(impl, S_LEN("silent"), menu->silent[bit]); tv_dict_add_nr(impl, S_LEN("enabled"), (menu->enabled & (1 << bit)) ? 1 : 0); @@ -717,7 +717,7 @@ static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) list_T *children_list = tv_list_alloc(); for (menu = menu->children; menu != NULL; menu = menu->next) { dict_T *dic = menu_get_recursive(menu, modes); - if (dict && tv_dict_len(dict) > 0) { + if (tv_dict_len(dict) > 0) { tv_list_append_dict(children_list, dic); } } diff --git a/src/nvim/message.c b/src/nvim/message.c index 28c88f5a14..b90c475ede 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1628,6 +1628,27 @@ void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr) } } +/// Print a formatted message +/// +/// Message printed is limited by #IOSIZE. Must not be used from inside +/// msg_puts_attr(). +/// +/// @param[in] attr Highlight attributes. +/// @param[in] fmt Format string. +void msg_printf_attr(const int attr, const char *const fmt, ...) + FUNC_ATTR_NONNULL_ARG(2) +{ + static char msgbuf[IOSIZE]; + + va_list ap; + va_start(ap, fmt); + const size_t len = vim_vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap, NULL); + va_end(ap); + + msg_scroll = true; + msg_puts_attr_len(msgbuf, (ptrdiff_t)len, attr); +} + /* * The display part of msg_puts_attr_len(). * May be called recursively to display scroll-back text. diff --git a/src/nvim/move.c b/src/nvim/move.c index 81d46a7f17..9693132846 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -2147,14 +2147,12 @@ void do_check_cursorbind(void) curbuf = curwin->w_buffer; /* skip original window and windows with 'noscrollbind' */ if (curwin != old_curwin && curwin->w_p_crb) { - if (curwin->w_p_diff) - curwin->w_cursor.lnum - = diff_get_corresponding_line(old_curbuf, - line, - curbuf, - curwin->w_cursor.lnum); - else + if (curwin->w_p_diff) { + curwin->w_cursor.lnum = + diff_get_corresponding_line(old_curbuf, line); + } else { curwin->w_cursor.lnum = line; + } curwin->w_cursor.col = col; curwin->w_cursor.coladd = coladd; curwin->w_curswant = curswant; @@ -2166,16 +2164,21 @@ void do_check_cursorbind(void) int restart_edit_save = restart_edit; restart_edit = true; check_cursor(); + if (curwin->w_p_cul || curwin->w_p_cuc) { + validate_cursor(); + } restart_edit = restart_edit_save; } - /* Correct cursor for multi-byte character. */ - if (has_mbyte) + // Correct cursor for multi-byte character. + if (has_mbyte) { mb_adjust_cursor(); + } redraw_later(VALID); - /* Only scroll when 'scrollbind' hasn't done this. */ - if (!curwin->w_p_scb) + // Only scroll when 'scrollbind' hasn't done this. + if (!curwin->w_p_scb) { update_topline(); + } curwin->w_redr_status = true; } } diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 6fd1af1ba6..88232a55de 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -62,7 +62,7 @@ typedef struct { ChannelType type; msgpack_unpacker *unpacker; union { - Stream stream; + Stream stream; // bidirectional (socket) Process *proc; struct { Stream in; @@ -133,6 +133,9 @@ uint64_t channel_from_process(Process *proc, uint64_t id, char *source) rstream_init(proc->out, 0); rstream_start(proc->out, receive_msgpack, channel); + DLOG("ch %" PRIu64 " in-stream=%p out-stream=%p", channel->id, proc->in, + proc->out); + return channel->id; } @@ -150,6 +153,9 @@ void channel_from_connection(SocketWatcher *watcher) wstream_init(&channel->data.stream, 0); rstream_init(&channel->data.stream, CHANNEL_BUFFER_SIZE); rstream_start(&channel->data.stream, receive_msgpack, channel); + + DLOG("ch %" PRIu64 " in/out-stream=%p", channel->id, + &channel->data.stream); } /// @param source description of source function, rplugin name, TCP addr, etc @@ -182,12 +188,11 @@ uint64_t channel_connect(bool tcp, const char *address, int timeout, return channel->id; } -/// Sends event/arguments to channel +/// Publishes an event to a channel. /// -/// @param id The channel id. If 0, the event will be sent to all -/// channels that have subscribed to the event type -/// @param name The event name, an arbitrary string -/// @param args Array with event arguments +/// @param id Channel id. 0 means "broadcast to all subscribed channels" +/// @param name Event name (application-defined) +/// @param args Array of event arguments /// @return True if the event was sent successfully, false otherwise. bool channel_send_event(uint64_t id, const char *name, Array args) { @@ -209,7 +214,6 @@ bool channel_send_event(uint64_t id, const char *name, Array args) send_event(channel, name, args); } } else { - // TODO(tarruda): Implement event broadcasting in vimscript broadcast_event(name, args); } @@ -344,6 +348,9 @@ void channel_from_stdio(void) rstream_start(&channel->data.std.in, receive_msgpack, channel); // write stream wstream_init_fd(&main_loop, &channel->data.std.out, 1, 0); + + DLOG("ch %" PRIu64 " in-stream=%p out-stream=%p", channel->id, + &channel->data.std.in, &channel->data.std.out); } /// Creates a loopback channel. This is used to avoid deadlock @@ -363,6 +370,7 @@ void channel_process_exit(uint64_t id, int status) decref(channel); } +// rstream.c:read_event() invokes this as stream->read_cb(). static void receive_msgpack(Stream *stream, RBuffer *rbuf, size_t c, void *data, bool eof) { @@ -374,12 +382,24 @@ static void receive_msgpack(Stream *stream, RBuffer *rbuf, size_t c, char buf[256]; snprintf(buf, sizeof(buf), "ch %" PRIu64 " was closed by the client", channel->id); - call_set_error(channel, buf, WARNING_LOG_LEVEL); + call_set_error(channel, buf, WARN_LOG_LEVEL); + goto end; + } + + if ((chan_wstream(channel) != NULL && chan_wstream(channel)->closed) + || (chan_rstream(channel) != NULL && chan_rstream(channel)->closed)) { + char buf[256]; + snprintf(buf, sizeof(buf), + "ch %" PRIu64 ": stream closed unexpectedly. " + "closing channel", + channel->id); + call_set_error(channel, buf, WARN_LOG_LEVEL); goto end; } size_t count = rbuffer_size(rbuf); - DLOG("parsing %u bytes of msgpack data from Stream(%p)", count, stream); + DLOG("ch %" PRIu64 ": parsing %u bytes from msgpack Stream: %p", + channel->id, count, stream); // Feed the unpacker with data msgpack_unpacker_reserve_buffer(channel->unpacker, count); @@ -435,8 +455,8 @@ static void parse_msgpack(Channel *channel) // causes for this error(search for 'goto _failed') // // A not so uncommon cause for this might be deserializing objects with - // a high nesting level: msgpack will break when it's internal parse stack - // size exceeds MSGPACK_EMBED_STACK_SIZE(defined as 32 by default) + // a high nesting level: msgpack will break when its internal parse stack + // size exceeds MSGPACK_EMBED_STACK_SIZE (defined as 32 by default) send_error(channel, 0, "Invalid msgpack payload. " "This error can also happen when deserializing " "an object with high level of nesting"); @@ -534,6 +554,39 @@ static void on_request_event(void **argv) api_clear_error(&error); } +/// Returns the Stream that a Channel writes to. +static Stream *chan_wstream(Channel *chan) +{ + switch (chan->type) { + case kChannelTypeSocket: + return &chan->data.stream; + case kChannelTypeProc: + return chan->data.proc->in; + case kChannelTypeStdio: + return &chan->data.std.out; + case kChannelTypeInternal: + return NULL; + } + abort(); +} + +/// Returns the Stream that a Channel reads from. +static Stream *chan_rstream(Channel *chan) +{ + switch (chan->type) { + case kChannelTypeSocket: + return &chan->data.stream; + case kChannelTypeProc: + return chan->data.proc->out; + case kChannelTypeStdio: + return &chan->data.std.in; + case kChannelTypeInternal: + return NULL; + } + abort(); +} + + static bool channel_write(Channel *channel, WBuffer *buffer) { bool success = false; @@ -545,13 +598,9 @@ static bool channel_write(Channel *channel, WBuffer *buffer) switch (channel->type) { case kChannelTypeSocket: - success = wstream_write(&channel->data.stream, buffer); - break; case kChannelTypeProc: - success = wstream_write(channel->data.proc->in, buffer); - break; case kChannelTypeStdio: - success = wstream_write(&channel->data.std.out, buffer); + success = wstream_write(chan_wstream(channel), buffer); break; case kChannelTypeInternal: incref(channel); @@ -565,8 +614,8 @@ static bool channel_write(Channel *channel, WBuffer *buffer) char buf[256]; snprintf(buf, sizeof(buf), - "Before returning from a RPC call, ch %" PRIu64 " was " - "closed due to a failed write", + "ch %" PRIu64 ": stream write failed. " + "RPC canceled; closing channel", channel->id); call_set_error(channel, buf, ERROR_LOG_LEVEL); } @@ -817,6 +866,7 @@ static void call_set_error(Channel *channel, char *msg, int loglevel) ChannelCallFrame *frame = kv_A(channel->call_stack, i); frame->returned = true; frame->errored = true; + api_free_object(frame->result); frame->result = STRING_OBJ(cstr_to_string(msg)); } diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c index 444c6cc256..fecae11d45 100644 --- a/src/nvim/msgpack_rpc/helpers.c +++ b/src/nvim/msgpack_rpc/helpers.c @@ -88,7 +88,12 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg) { bool ret = true; kvec_t(MPToAPIObjectStackItem) stack = KV_INITIAL_VALUE; - kv_push(stack, ((MPToAPIObjectStackItem) { obj, arg, false, 0 })); + kv_push(stack, ((MPToAPIObjectStackItem) { + .mobj = obj, + .aobj = arg, + .container = false, + .idx = 0, + })); while (ret && kv_size(stack)) { MPToAPIObjectStackItem cur = kv_last(stack); if (!cur.container) { @@ -361,7 +366,7 @@ typedef struct { size_t idx; } APIToMPObjectStackItem; -/// Convert type used by Neovim API to msgpack +/// Convert type used by Nvim API to msgpack type. /// /// @param[in] result Object to convert. /// @param[out] res Structure that defines where conversion results are saved. diff --git a/src/nvim/normal.c b/src/nvim/normal.c index c1676780d8..1103fe15d2 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -13,6 +13,7 @@ #include <stdbool.h> #include <stdlib.h> +#include "nvim/log.h" #include "nvim/vim.h" #include "nvim/ascii.h" #include "nvim/normal.h" @@ -344,8 +345,6 @@ 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_FOCUSGAINED, nv_focusgained, NV_KEEPREG, 0 }, - { K_FOCUSLOST, nv_focuslost, NV_KEEPREG, 0 }, }; /* Number of commands in nv_cmds[]. */ @@ -1549,8 +1548,10 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) } oap->start = VIsual; - if (VIsual_mode == 'V') + if (VIsual_mode == 'V') { oap->start.col = 0; + oap->start.coladd = 0; + } } /* @@ -1943,8 +1944,11 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) * the lines. */ auto_format(false, true); - if (restart_edit == 0) + if (restart_edit == 0) { restart_edit = restart_edit_save; + } else { + cap->retval |= CA_COMMAND_BUSY; + } } break; @@ -6258,15 +6262,18 @@ static void nv_gomark(cmdarg_T *cap) } else nv_cursormark(cap, cap->arg, pos); - /* May need to clear the coladd that a mark includes. */ - if (!virtual_active()) + // May need to clear the coladd that a mark includes. + if (!virtual_active()) { curwin->w_cursor.coladd = 0; + } + check_cursor_col(); if (cap->oap->op_type == OP_NOP && pos != NULL && (pos == (pos_T *)-1 || !equalpos(old_cursor, *pos)) && (fdo_flags & FDO_MARK) - && old_KeyTyped) + && old_KeyTyped) { foldOpenCursor(); + } } /* @@ -7957,18 +7964,7 @@ static void nv_event(cmdarg_T *cap) may_garbage_collect = false; multiqueue_process_events(main_loop.events); cap->retval |= CA_COMMAND_BUSY; // don't call edit() now -} - -/// Trigger FocusGained event. -static void nv_focusgained(cmdarg_T *cap) -{ - apply_autocmds(EVENT_FOCUSGAINED, NULL, NULL, false, curbuf); -} - -/// Trigger FocusLost event. -static void nv_focuslost(cmdarg_T *cap) -{ - apply_autocmds(EVENT_FOCUSLOST, NULL, NULL, false, curbuf); + finish_op = false; } /* diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 5c6f4d0d07..c6df71ea46 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -55,12 +55,11 @@ static yankreg_T y_regs[NUM_REGISTERS]; static yankreg_T *y_previous = NULL; /* ptr to last written yankreg */ -static bool clipboard_didwarn_unnamed = false; - // for behavior between start_batch_changes() and end_batch_changes()) -static bool clipboard_delay_update = false; // delay clipboard update static int batch_change_count = 0; // inside a script +static bool clipboard_delay_update = false; // delay clipboard update static bool clipboard_needs_update = false; // clipboard was updated +static bool clipboard_didwarn = false; /* * structure used by block_prep, op_delete and op_yank for blockwise operators @@ -2061,7 +2060,7 @@ void op_insert(oparg_T *oap, long count1) } t1 = oap->start; - edit(NUL, false, (linenr_T)count1); + (void)edit(NUL, false, (linenr_T)count1); // When a tab was inserted, and the characters in front of the tab // have been converted to a tab as well, the column of the cursor @@ -5524,7 +5523,7 @@ int get_default_register_name(void) } /// Determine if register `*name` should be used as a clipboard. -/// In an unnammed operation, `*name` is `NUL` and will be adjusted to `'*'/'+'` if +/// In an unnamed operation, `*name` is `NUL` and will be adjusted to */+ if /// `clipboard=unnamed[plus]` is set. /// /// @param name The name of register, or `NUL` if unnamed. @@ -5535,33 +5534,41 @@ int get_default_register_name(void) /// if the register isn't a clipboard or provider isn't available. static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing) { - if (*name == '*' || *name == '+') { - if(!eval_has_provider("clipboard")) { - if (!quiet) { - EMSG("clipboard: No provider. Try \":CheckHealth\" or " - "\":h clipboard\"."); - } - return NULL; - } - return &y_regs[*name == '*' ? STAR_REGISTER : PLUS_REGISTER]; - } else if ((*name == NUL) && (cb_flags & CB_UNNAMEDMASK)) { - if(!eval_has_provider("clipboard")) { - if (!quiet && !clipboard_didwarn_unnamed) { - msg((char_u *)"clipboard: No provider. Try \":CheckHealth\" or " - "\":h clipboard\"."); - clipboard_didwarn_unnamed = true; - } - return NULL; +#define MSG_NO_CLIP "clipboard: No provider. " \ + "Try \":checkhealth\" or \":h clipboard\"." + + yankreg_T *target = NULL; + bool explicit_cb_reg = (*name == '*' || *name == '+'); + bool implicit_cb_reg = (*name == NUL) && (cb_flags & CB_UNNAMEDMASK); + if (!explicit_cb_reg && !implicit_cb_reg) { + goto end; + } + + if (!eval_has_provider("clipboard")) { + if (batch_change_count == 1 && !quiet + && (!clipboard_didwarn || (explicit_cb_reg && !redirecting()))) { + clipboard_didwarn = true; + // Do NOT error (emsg()) here--if it interrupts :redir we get into + // a weird state, stuck in "redirect mode". + msg((char_u *)MSG_NO_CLIP); } + // ... else, be silent (don't flood during :while, :redir, etc.). + goto end; + } + + if (explicit_cb_reg) { + target = &y_regs[*name == '*' ? STAR_REGISTER : PLUS_REGISTER]; + goto end; + } else { // unnamed register: "implicit" clipboard if (writing && clipboard_delay_update) { + // For "set" (copy), defer the clipboard call. clipboard_needs_update = true; - return NULL; + goto end; } else if (!writing && clipboard_needs_update) { - // use the internal value - return NULL; + // For "get" (paste), use the internal value. + goto end; } - yankreg_T *target; if (cb_flags & CB_UNNAMEDPLUS) { *name = (cb_flags & CB_UNNAMED && writing) ? '"': '+'; target = &y_regs[PLUS_REGISTER]; @@ -5569,10 +5576,11 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing) *name = '*'; target = &y_regs[STAR_REGISTER]; } - return target; // unnamed register + goto end; } - // don't do anything for other register names - return NULL; + +end: + return target; } static bool get_clipboard(int name, yankreg_T **target, bool quiet) @@ -5740,17 +5748,16 @@ static void set_clipboard(int name, yankreg_T *reg) (void)eval_call_provider("clipboard", "set", args); } -/// Avoid clipboard (slow) during batch operations (i.e., a script). +/// Avoid slow things (clipboard) during batch operations (while/for-loops). void start_batch_changes(void) { if (++batch_change_count > 1) { return; } clipboard_delay_update = true; - clipboard_needs_update = false; } -/// Update the clipboard after batch changes finished. +/// Counterpart to start_batch_changes(). void end_batch_changes(void) { if (--batch_change_count > 0) { @@ -5759,11 +5766,37 @@ void end_batch_changes(void) } clipboard_delay_update = false; if (clipboard_needs_update) { + // must be before, as set_clipboard will invoke + // start/end_batch_changes recursively + clipboard_needs_update = false; + // unnamed ("implicit" clipboard) set_clipboard(NUL, y_previous); + } +} + +int save_batch_count(void) +{ + int save_count = batch_change_count; + batch_change_count = 0; + clipboard_delay_update = false; + if (clipboard_needs_update) { clipboard_needs_update = false; + // unnamed ("implicit" clipboard) + set_clipboard(NUL, y_previous); } + return save_count; } +void restore_batch_count(int save_count) +{ + assert(batch_change_count == 0); + batch_change_count = save_count; + if (batch_change_count > 0) { + clipboard_delay_update = true; + } +} + + /// Check whether register is empty static inline bool reg_empty(const yankreg_T *const reg) FUNC_ATTR_PURE diff --git a/src/nvim/option.c b/src/nvim/option.c index 8ba10fd38a..f6f334f432 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -242,6 +242,7 @@ typedef struct vimoption { #define P_NO_DEF_EXP 0x8000000U ///< Do not expand default value. #define P_RWINONLY 0x10000000U ///< only redraw current window +#define P_NDNAME 0x20000000U ///< only normal dir name chars allowed #define HIGHLIGHT_INIT \ "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText," \ @@ -1749,7 +1750,7 @@ do_set ( if (flags & P_FLAGLIST) { // Remove flags that appear twice. - for (s = newval; *s; s++) { + for (s = newval; *s;) { // if options have P_FLAGLIST and P_ONECOMMA such as // 'whichwrap' if (flags & P_ONECOMMA) { @@ -1757,15 +1758,16 @@ do_set ( && vim_strchr(s + 2, *s) != NULL) { // Remove the duplicated value and the next comma. STRMOVE(s, s + 2); - s -= 2; + continue; } } else { if ((!(flags & P_COMMA) || *s != ',') && vim_strchr(s + 1, *s) != NULL) { STRMOVE(s, s + 1); - s--; + continue; } } + s++; } } @@ -2453,11 +2455,14 @@ did_set_string_option ( if ((secure || sandbox != 0) && (options[opt_idx].flags & P_SECURE)) { errmsg = e_secure; - } else if ((options[opt_idx].flags & P_NFNAME) - && vim_strpbrk(*varp, (char_u *)"/\\*?[|;&<>\r\n") != NULL) { - // Check for a "normal" file name in some options. Disallow a path - // separator (slash and/or backslash), wildcards and characters that are - // often illegal in a file name. + } else if (((options[opt_idx].flags & P_NFNAME) + && vim_strpbrk(*varp, (char_u *)(secure ? "/\\*?[|;&<>\r\n" + : "/\\*?[<>\r\n")) != NULL) + || ((options[opt_idx].flags & P_NDNAME) + && vim_strpbrk(*varp, (char_u *)"*?[|;&<>\r\n") != NULL)) { + // Check for a "normal" directory or file name in some options. Disallow a + // path separator (slash and/or backslash), wildcards and characters that + // are often illegal in a file name. Be more permissive if "secure" is off. errmsg = e_invarg; } /* 'backupcopy' */ @@ -2996,9 +3001,10 @@ did_set_string_option ( if (s[-1] == 'k' || s[-1] == 's') { /* skip optional filename after 'k' and 's' */ while (*s && *s != ',' && *s != ' ') { - if (*s == '\\') - ++s; - ++s; + if (*s == '\\' && s[1] != NUL) { + s++; + } + s++; } } else { if (errbuf != NULL) { @@ -3171,17 +3177,18 @@ did_set_string_option ( } else { // Options that are a list of flags. p = NULL; - if (varp == &p_ww) + if (varp == &p_ww) { // 'whichwrap' p = (char_u *)WW_ALL; - if (varp == &p_shm) + } + if (varp == &p_shm) { // 'shortmess' p = (char_u *)SHM_ALL; - else if (varp == &(p_cpo)) + } else if (varp == &(p_cpo)) { // 'cpoptions' p = (char_u *)CPO_VI; - else if (varp == &(curbuf->b_p_fo)) + } else if (varp == &(curbuf->b_p_fo)) { // 'formatoptions' p = (char_u *)FO_ALL; - else if (varp == &curwin->w_p_cocu) + } else if (varp == &curwin->w_p_cocu) { // 'concealcursor' p = (char_u *)COCU_ALL; - else if (varp == &p_mouse) { + } else if (varp == &p_mouse) { // 'mouse' p = (char_u *)MOUSE_ALL; } if (p != NULL) { diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 103227f6b5..7cecb16686 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -7,7 +7,7 @@ -- enable_if=nil, -- defaults={condition=nil, if_true={vi=224, vim=0}, if_false=nil}, -- secure=nil, gettext=nil, noglob=nil, normal_fname_chars=nil, --- pri_mkrc=nil, deny_in_modelines=nil, +-- pri_mkrc=nil, deny_in_modelines=nil, normal_dname_chars=nil, -- expand=nil, nodefault=nil, no_mkrc=nil, vi_def=true, vim=true, -- alloced=nil, -- save_pv_indir=nil, @@ -575,6 +575,7 @@ return { full_name='dictionary', abbreviation='dict', type='string', list='onecomma', scope={'global', 'buffer'}, deny_duplicates=true, + normal_dname_chars=true, vi_def=true, expand=true, varname='p_dict', @@ -1750,6 +1751,7 @@ return { { full_name='printexpr', abbreviation='pexpr', type='string', scope={'global'}, + secure=true, vi_def=true, varname='p_pexpr', defaults={if_true={vi=""}} @@ -2449,6 +2451,7 @@ return { full_name='thesaurus', abbreviation='tsr', type='string', list='onecomma', scope={'global', 'buffer'}, deny_duplicates=true, + normal_dname_chars=true, vi_def=true, expand=true, varname='p_tsr', @@ -2498,7 +2501,7 @@ return { no_mkrc=true, vi_def=true, varname='p_titleold', - defaults={if_true={vi=N_("Thanks for flying Vim")}} + defaults={if_true={vi=N_("")}} }, { full_name='titlestring', diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c new file mode 100644 index 0000000000..ef8a699c56 --- /dev/null +++ b/src/nvim/os/pty_process_win.c @@ -0,0 +1,410 @@ +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> + +#include <winpty_constants.h> + +#include "nvim/os/os.h" +#include "nvim/ascii.h" +#include "nvim/memory.h" +#include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8 +#include "nvim/os/pty_process_win.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/pty_process_win.c.generated.h" +#endif + +static void CALLBACK pty_process_finish1(void *context, BOOLEAN unused) + FUNC_ATTR_NONNULL_ALL +{ + PtyProcess *ptyproc = (PtyProcess *)context; + Process *proc = (Process *)ptyproc; + + uv_timer_init(&proc->loop->uv, &ptyproc->wait_eof_timer); + ptyproc->wait_eof_timer.data = (void *)ptyproc; + uv_timer_start(&ptyproc->wait_eof_timer, wait_eof_timer_cb, 200, 200); +} + +/// @returns zero on success, or negative error code. +int pty_process_spawn(PtyProcess *ptyproc) + FUNC_ATTR_NONNULL_ALL +{ + Process *proc = (Process *)ptyproc; + int status = 0; + winpty_error_ptr_t err = NULL; + winpty_config_t *cfg = NULL; + winpty_spawn_config_t *spawncfg = NULL; + winpty_t *winpty_object = NULL; + char *in_name = NULL; + char *out_name = NULL; + HANDLE process_handle = NULL; + uv_connect_t *in_req = NULL; + uv_connect_t *out_req = NULL; + wchar_t *cmd_line = NULL; + wchar_t *cwd = NULL; + const char *emsg = NULL; + + assert(!proc->err); + + cfg = winpty_config_new(WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION, &err); + if (cfg == NULL) { + emsg = "Failed, winpty_config_new."; + goto cleanup; + } + + winpty_config_set_initial_size(cfg, ptyproc->width, ptyproc->height); + winpty_object = winpty_open(cfg, &err); + if (winpty_object == NULL) { + emsg = "Failed, winpty_open."; + goto cleanup; + } + + status = utf16_to_utf8(winpty_conin_name(winpty_object), &in_name); + if (status != 0) { + emsg = "Failed to convert in_name from utf16 to utf8."; + goto cleanup; + } + + status = utf16_to_utf8(winpty_conout_name(winpty_object), &out_name); + if (status != 0) { + emsg = "Failed to convert out_name from utf16 to utf8."; + goto cleanup; + } + + if (proc->in != NULL) { + in_req = xmalloc(sizeof(uv_connect_t)); + uv_pipe_connect( + in_req, + &proc->in->uv.pipe, + in_name, + pty_process_connect_cb); + } + + if (proc->out != NULL) { + out_req = xmalloc(sizeof(uv_connect_t)); + uv_pipe_connect( + out_req, + &proc->out->uv.pipe, + out_name, + pty_process_connect_cb); + } + + if (proc->cwd != NULL) { + status = utf8_to_utf16(proc->cwd, &cwd); + if (status != 0) { + emsg = "Failed to convert pwd form utf8 to utf16."; + goto cleanup; + } + } + + status = build_cmd_line(proc->argv, &cmd_line, + os_shell_is_cmdexe(proc->argv[0])); + if (status != 0) { + emsg = "Failed to convert cmd line form utf8 to utf16."; + goto cleanup; + } + + spawncfg = winpty_spawn_config_new( + WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, + NULL, // Optional application name + cmd_line, + cwd, + NULL, // Optional environment variables + &err); + if (spawncfg == NULL) { + emsg = "Failed winpty_spawn_config_new."; + goto cleanup; + } + + DWORD win_err = 0; + if (!winpty_spawn(winpty_object, + spawncfg, + &process_handle, + NULL, // Optional thread handle + &win_err, + &err)) { + if (win_err) { + status = (int)win_err; + emsg = "Failed spawn process."; + } else { + emsg = "Failed winpty_spawn."; + } + goto cleanup; + } + proc->pid = GetProcessId(process_handle); + + if (!RegisterWaitForSingleObject( + &ptyproc->finish_wait, + process_handle, + pty_process_finish1, + ptyproc, + INFINITE, + WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE)) { + abort(); + } + + // Wait until pty_process_connect_cb is called. + while ((in_req != NULL && in_req->handle != NULL) + || (out_req != NULL && out_req->handle != NULL)) { + uv_run(&proc->loop->uv, UV_RUN_ONCE); + } + + ptyproc->winpty_object = winpty_object; + ptyproc->process_handle = process_handle; + winpty_object = NULL; + process_handle = NULL; + +cleanup: + if (status) { + // In the case of an error of MultiByteToWideChar or CreateProcessW. + ELOG("%s error code: %d", emsg, status); + status = os_translate_sys_error(status); + } else if (err != NULL) { + status = (int)winpty_error_code(err); + ELOG("%s error code: %d", emsg, status); + status = translate_winpty_error(status); + } + winpty_error_free(err); + winpty_config_free(cfg); + winpty_spawn_config_free(spawncfg); + winpty_free(winpty_object); + xfree(in_name); + xfree(out_name); + if (process_handle != NULL) { + CloseHandle(process_handle); + } + xfree(in_req); + xfree(out_req); + xfree(cmd_line); + xfree(cwd); + return status; +} + +void pty_process_resize(PtyProcess *ptyproc, uint16_t width, + uint16_t height) + FUNC_ATTR_NONNULL_ALL +{ + if (ptyproc->winpty_object != NULL) { + winpty_set_size(ptyproc->winpty_object, width, height, NULL); + } +} + +void pty_process_close(PtyProcess *ptyproc) + FUNC_ATTR_NONNULL_ALL +{ + Process *proc = (Process *)ptyproc; + + pty_process_close_master(ptyproc); + + if (proc->internal_close_cb) { + proc->internal_close_cb(proc); + } +} + +void pty_process_close_master(PtyProcess *ptyproc) + FUNC_ATTR_NONNULL_ALL +{ + if (ptyproc->winpty_object != NULL) { + winpty_free(ptyproc->winpty_object); + ptyproc->winpty_object = NULL; + } +} + +void pty_process_teardown(Loop *loop) + FUNC_ATTR_NONNULL_ALL +{ +} + +static void pty_process_connect_cb(uv_connect_t *req, int status) + FUNC_ATTR_NONNULL_ALL +{ + assert(status == 0); + req->handle = NULL; +} + +static void wait_eof_timer_cb(uv_timer_t *wait_eof_timer) + FUNC_ATTR_NONNULL_ALL +{ + PtyProcess *ptyproc = wait_eof_timer->data; + Process *proc = (Process *)ptyproc; + + if (!proc->out || !uv_is_readable(proc->out->uvstream)) { + uv_timer_stop(&ptyproc->wait_eof_timer); + pty_process_finish2(ptyproc); + } +} + +static void pty_process_finish2(PtyProcess *ptyproc) + FUNC_ATTR_NONNULL_ALL +{ + Process *proc = (Process *)ptyproc; + + UnregisterWaitEx(ptyproc->finish_wait, NULL); + uv_close((uv_handle_t *)&ptyproc->wait_eof_timer, NULL); + + DWORD exit_code = 0; + GetExitCodeProcess(ptyproc->process_handle, &exit_code); + proc->status = (int)exit_code; + + CloseHandle(ptyproc->process_handle); + ptyproc->process_handle = NULL; + + proc->internal_exit_cb(proc); +} + +/// Build the command line to pass to CreateProcessW. +/// +/// @param[in] argv Array with string arguments. +/// @param[out] cmd_line Location where saved builded cmd line. +/// +/// @returns zero on success, or error code of MultiByteToWideChar function. +/// +static int build_cmd_line(char **argv, wchar_t **cmd_line, bool is_cmdexe) + FUNC_ATTR_NONNULL_ALL +{ + size_t utf8_cmd_line_len = 0; + size_t argc = 0; + QUEUE args_q; + + QUEUE_INIT(&args_q); + while (*argv) { + size_t buf_len = is_cmdexe ? (strlen(*argv) + 1) : (strlen(*argv) * 2 + 3); + ArgNode *arg_node = xmalloc(sizeof(*arg_node)); + arg_node->arg = xmalloc(buf_len); + if (is_cmdexe) { + xstrlcpy(arg_node->arg, *argv, buf_len); + } else { + quote_cmd_arg(arg_node->arg, buf_len, *argv); + } + utf8_cmd_line_len += strlen(arg_node->arg); + QUEUE_INIT(&arg_node->node); + QUEUE_INSERT_TAIL(&args_q, &arg_node->node); + argc++; + argv++; + } + + utf8_cmd_line_len += argc; + char *utf8_cmd_line = xmalloc(utf8_cmd_line_len); + *utf8_cmd_line = NUL; + while (1) { + QUEUE *head = QUEUE_HEAD(&args_q); + QUEUE_REMOVE(head); + ArgNode *arg_node = QUEUE_DATA(head, ArgNode, node); + xstrlcat(utf8_cmd_line, arg_node->arg, utf8_cmd_line_len); + xfree(arg_node->arg); + xfree(arg_node); + if (QUEUE_EMPTY(&args_q)) { + break; + } else { + xstrlcat(utf8_cmd_line, " ", utf8_cmd_line_len); + } + } + + int result = utf8_to_utf16(utf8_cmd_line, cmd_line); + xfree(utf8_cmd_line); + return result; +} + +/// Emulate quote_cmd_arg of libuv and quotes command line argument. +/// Most of the code came from libuv. +/// +/// @param[out] dest Location where saved quotes argument. +/// @param dest_remaining Destination buffer size. +/// @param[in] src Pointer to argument. +/// +static void quote_cmd_arg(char *dest, size_t dest_remaining, const char *src) + FUNC_ATTR_NONNULL_ALL +{ + size_t src_len = strlen(src); + bool quote_hit = true; + char *start = dest; + + if (src_len == 0) { + // Need double quotation for empty argument. + snprintf(dest, dest_remaining, "\"\""); + return; + } + + if (NULL == strpbrk(src, " \t\"")) { + // No quotation needed. + xstrlcpy(dest, src, dest_remaining); + return; + } + + if (NULL == strpbrk(src, "\"\\")) { + // No embedded double quotes or backlashes, so I can just wrap quote marks. + // around the whole thing. + snprintf(dest, dest_remaining, "\"%s\"", src); + return; + } + + // Expected input/output: + // input : hello"world + // output: "hello\"world" + // input : hello""world + // output: "hello\"\"world" + // input : hello\world + // output: hello\world + // input : hello\\world + // output: hello\\world + // input : hello\"world + // output: "hello\\\"world" + // input : hello\\"world + // output: "hello\\\\\"world" + // input : hello world\ + // output: "hello world\\" + + assert(dest_remaining--); + *(dest++) = NUL; + assert(dest_remaining--); + *(dest++) = '"'; + for (size_t i = src_len; i > 0; i--) { + assert(dest_remaining--); + *(dest++) = src[i - 1]; + if (quote_hit && src[i - 1] == '\\') { + assert(dest_remaining--); + *(dest++) = '\\'; + } else if (src[i - 1] == '"') { + quote_hit = true; + assert(dest_remaining--); + *(dest++) = '\\'; + } else { + quote_hit = false; + } + } + assert(dest_remaining); + *dest = '"'; + + while (start < dest) { + char tmp = *start; + *start = *dest; + *dest = tmp; + start++; + dest--; + } +} + +/// Translate winpty error code to libuv error. +/// +/// @param[in] winpty_errno Winpty error code returned by winpty_error_code +/// function. +/// +/// @returns Error code of libuv error. +int translate_winpty_error(int winpty_errno) +{ + if (winpty_errno <= 0) { + return winpty_errno; // If < 0 then it's already a libuv error. + } + + switch (winpty_errno) { + case WINPTY_ERROR_OUT_OF_MEMORY: return UV_ENOMEM; + case WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED: return UV_EAI_FAIL; + case WINPTY_ERROR_LOST_CONNECTION: return UV_ENOTCONN; + case WINPTY_ERROR_AGENT_EXE_MISSING: return UV_ENOENT; + case WINPTY_ERROR_UNSPECIFIED: return UV_UNKNOWN; + case WINPTY_ERROR_AGENT_DIED: return UV_ESRCH; + case WINPTY_ERROR_AGENT_TIMEOUT: return UV_ETIMEDOUT; + case WINPTY_ERROR_AGENT_CREATION_FAILED: return UV_EAI_FAIL; + default: return UV_UNKNOWN; + } +} diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h index 8e2b37a1c1..1a4019e654 100644 --- a/src/nvim/os/pty_process_win.h +++ b/src/nvim/os/pty_process_win.h @@ -1,20 +1,27 @@ #ifndef NVIM_OS_PTY_PROCESS_WIN_H #define NVIM_OS_PTY_PROCESS_WIN_H -#include "nvim/event/libuv_process.h" +#include <uv.h> +#include <winpty.h> + +#include "nvim/event/process.h" +#include "nvim/lib/queue.h" typedef struct pty_process { Process process; char *term_name; uint16_t width, height; + winpty_t *winpty_object; + HANDLE finish_wait; + HANDLE process_handle; + uv_timer_t wait_eof_timer; } PtyProcess; -#define pty_process_spawn(job) libuv_process_spawn((LibuvProcess *)job) -#define pty_process_close(job) libuv_process_close((LibuvProcess *)job) -#define pty_process_close_master(job) libuv_process_close((LibuvProcess *)job) -#define pty_process_resize(job, width, height) ( \ - (void)job, (void)width, (void)height, 0) -#define pty_process_teardown(loop) ((void)loop, 0) +// Structure used by build_cmd_line() +typedef struct arg_node { + char *arg; // pointer to argument. + QUEUE node; // QUEUE structure. +} ArgNode; static inline PtyProcess pty_process_init(Loop *loop, void *data) { @@ -23,7 +30,14 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data) rv.term_name = NULL; rv.width = 80; rv.height = 24; + rv.winpty_object = NULL; + rv.finish_wait = NULL; + rv.process_handle = NULL; return rv; } +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/pty_process_win.h.generated.h" +#endif + #endif // NVIM_OS_PTY_PROCESS_WIN_H diff --git a/src/nvim/path.c b/src/nvim/path.c index f2339c8046..51adcfb135 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -1690,6 +1690,9 @@ int vim_FullName(const char *fname, char *buf, size_t len, bool force) if (strlen(fname) > (len - 1)) { xstrlcpy(buf, fname, len); // truncate +#ifdef WIN32 + slash_adjust(buf); +#endif return FAIL; } @@ -1702,6 +1705,9 @@ int vim_FullName(const char *fname, char *buf, size_t len, bool force) if (rv == FAIL) { xstrlcpy(buf, fname, len); // something failed; use the filename } +#ifdef WIN32 + slash_adjust(buf); +#endif return rv; } @@ -2196,11 +2202,11 @@ static int path_get_absolute_path(const char_u *fname, char_u *buf, // expand it if forced or not an absolute path if (force || !path_is_absolute_path(fname)) { - if ((p = vim_strrchr(fname, '/')) != NULL) { + if ((p = vim_strrchr(fname, PATHSEP)) != NULL) { // relative to root if (p == fname) { // only one path component - relative_directory[0] = '/'; + relative_directory[0] = PATHSEP; relative_directory[1] = NUL; } else { assert(p >= fname); diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 847b2f273e..ae611a0005 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -3316,6 +3316,47 @@ bt_regexec_nl ( return (int)r; } +/// Wrapper around strchr which accounts for case-insensitive searches and +/// non-ASCII characters. +/// +/// This function is used a lot for simple searches, keep it fast! +/// +/// @param s string to search +/// @param c character to find in @a s +/// +/// @return NULL if no match, otherwise pointer to the position in @a s +static inline char_u *cstrchr(const char_u *const s, const int c) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_ALWAYS_INLINE +{ + if (!rex.reg_ic) { + return vim_strchr(s, c); + } + + // Use folded case for UTF-8, slow! For ASCII use libc strpbrk which is + // expected to be highly optimized. + if (c > 0x80) { + const int folded_c = utf_fold(c); + for (const char_u *p = s; *p != NUL; p += utfc_ptr2len(p)) { + if (utf_fold(utf_ptr2char(p)) == folded_c) { + return (char_u *)p; + } + } + return NULL; + } + + int cc; + if (ASCII_ISUPPER(c)) { + cc = TOLOWER_ASC(c); + } else if (ASCII_ISLOWER(c)) { + cc = TOUPPER_ASC(c); + } else { + return vim_strchr(s, c); + } + + char tofind[] = { (char)c, (char)cc, NUL }; + return (char_u *)strpbrk((const char *)s, tofind); +} /// Matches a regexp against multiple lines. /// "rmp->regprog" is a compiled regexp as returned by vim_regcomp(). @@ -6320,42 +6361,6 @@ static int cstrncmp(char_u *s1, char_u *s2, int *n) return result; } -/* - * cstrchr: This function is used a lot for simple searches, keep it fast! - */ -static inline char_u *cstrchr(const char_u *const s, const int c) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL - FUNC_ATTR_ALWAYS_INLINE -{ - if (!rex.reg_ic) { - return vim_strchr(s, c); - } - - // Use folded case for UTF-8, slow! For ASCII use libc strpbrk which is - // expected to be highly optimized. - if (c > 0x80) { - const int folded_c = utf_fold(c); - for (const char_u *p = s; *p != NUL; p += utfc_ptr2len(p)) { - if (utf_fold(utf_ptr2char(p)) == folded_c) { - return (char_u *)p; - } - } - return NULL; - } - - int cc; - if (ASCII_ISUPPER(c)) { - cc = TOLOWER_ASC(c); - } else if (ASCII_ISLOWER(c)) { - cc = TOUPPER_ASC(c); - } else { - return vim_strchr(s, c); - } - - char tofind[] = { (char)c, (char)cc, NUL }; - return (char_u *)strpbrk((const char *)s, tofind); -} - /*************************************************************** * regsub stuff * ***************************************************************/ diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h index 6426ee441b..b5d56e07fc 100644 --- a/src/nvim/regexp_defs.h +++ b/src/nvim/regexp_defs.h @@ -15,6 +15,8 @@ #include <stdbool.h> #include "nvim/pos.h" +#include "nvim/types.h" +#include "nvim/profile.h" /* * The number of sub-matches is limited to 10. @@ -41,18 +43,36 @@ #define NFA_ENGINE 2 typedef struct regengine regengine_T; +typedef struct regprog regprog_T; +typedef struct reg_extmatch reg_extmatch_T; + +/// Structure to be used for multi-line matching. +/// Sub-match "no" starts in line "startpos[no].lnum" column "startpos[no].col" +/// and ends in line "endpos[no].lnum" just before column "endpos[no].col". +/// The line numbers are relative to the first line, thus startpos[0].lnum is +/// always 0. +/// When there is no match, the line number is -1. +typedef struct { + regprog_T *regprog; + lpos_T startpos[NSUBEXP]; + lpos_T endpos[NSUBEXP]; + int rmm_ic; + colnr_T rmm_maxcol; /// when not zero: maximum column +} regmmatch_T; + +#include "nvim/buffer_defs.h" /* * Structure returned by vim_regcomp() to pass on to vim_regexec(). * This is the general structure. For the actual matcher, two specific * structures are used. See code below. */ -typedef struct regprog { +struct regprog { regengine_T *engine; unsigned regflags; unsigned re_engine; ///< Automatic, backtracking or NFA engine. unsigned re_flags; ///< Second argument for vim_regcomp(). -} regprog_T; +}; /* * Structure used by the back track matcher. @@ -126,30 +146,14 @@ typedef struct { } regmatch_T; /* - * Structure to be used for multi-line matching. - * Sub-match "no" starts in line "startpos[no].lnum" column "startpos[no].col" - * and ends in line "endpos[no].lnum" just before column "endpos[no].col". - * The line numbers are relative to the first line, thus startpos[0].lnum is - * always 0. - * When there is no match, the line number is -1. - */ -typedef struct { - regprog_T *regprog; - lpos_T startpos[NSUBEXP]; - lpos_T endpos[NSUBEXP]; - int rmm_ic; - colnr_T rmm_maxcol; /* when not zero: maximum column */ -} regmmatch_T; - -/* * Structure used to store external references: "\z\(\)" to "\z\1". * Use a reference count to avoid the need to copy this around. When it goes * from 1 to zero the matches need to be freed. */ -typedef struct { - short refcnt; +struct reg_extmatch { + int16_t refcnt; char_u *matches[NSUBEXP]; -} reg_extmatch_T; +}; struct regengine { regprog_T *(*regcomp)(char_u*, int); diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 95973354bc..f5730cf70a 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2201,16 +2201,16 @@ win_line ( int change_end = -1; /* last col of changed area */ colnr_T trailcol = MAXCOL; /* start of trailing spaces */ int need_showbreak = false; // overlong line, skip first x chars - int line_attr = 0; /* attribute for the whole line */ - matchitem_T *cur; /* points to the match list */ - match_T *shl; /* points to search_hl or a match */ - int shl_flag; /* flag to indicate whether search_hl - has been processed or not */ - int prevcol_hl_flag; /* flag to indicate whether prevcol - equals startcol of search_hl or one - of the matches */ - int prev_c = 0; /* previous Arabic character */ - int prev_c1 = 0; /* first composing char for prev_c */ + int line_attr = 0; // attribute for the whole line + matchitem_T *cur; // points to the match list + match_T *shl; // points to search_hl or a match + int shl_flag; // flag to indicate whether search_hl + // has been processed or not + int prevcol_hl_flag; // flag to indicate whether prevcol + // equals startcol of search_hl or one + // of the matches + int prev_c = 0; // previous Arabic character + int prev_c1 = 0; // first composing char for prev_c int did_line_attr = 0; bool search_attr_from_match = false; // if search_attr is from :match @@ -2427,10 +2427,11 @@ win_line ( filler_lines = wp->w_topfill; filler_todo = filler_lines; - /* If this line has a sign with line highlighting set line_attr. */ + // If this line has a sign with line highlighting set line_attr. v = buf_getsigntype(wp->w_buffer, lnum, SIGN_LINEHL); - if (v != 0) - line_attr = sign_get_attr((int)v, TRUE); + if (v != 0) { + line_attr = sign_get_attr((int)v, true); + } // Highlight the current line in the quickfix window. if (bt_quickfix(wp->w_buffer) && qf_current_entry(wp) == lnum) { @@ -2663,9 +2664,9 @@ win_line ( cur = cur->next; } - /* Cursor line highlighting for 'cursorline' in the current window. Not - * when Visual mode is active, because it's not clear what is selected - * then. */ + // Cursor line highlighting for 'cursorline' in the current window. Not + // when Visual mode is active, because it's not clear what is selected + // then. if (wp->w_p_cul && lnum == wp->w_cursor.lnum && !(wp == curwin && VIsual_active)) { if (line_attr != 0 && !(State & INSERT) && bt_quickfix(wp->w_buffer) @@ -3594,15 +3595,13 @@ win_line ( && lcs_eol_one > 0) { // Display a '$' after the line or highlight an extra // character if the line break is included. - // For a diff line the highlighting continues after the - // "$". + // For a diff line the highlighting continues after the "$". if (diff_hlf == (hlf_T)0 && line_attr == 0) { - /* In virtualedit, visual selections may extend - * beyond end of line. */ + // In virtualedit, visual selections may extend beyond end of line. if (area_highlighting && virtual_active() - && tocol != MAXCOL && vcol < tocol) + && tocol != MAXCOL && vcol < tocol) { n_extra = 0; - else { + } else { p_extra = at_end_str; n_extra = 1; c_extra = NUL; @@ -4035,10 +4034,10 @@ win_line ( if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol && lnum != wp->w_cursor.lnum) { vcol_save_attr = char_attr; - char_attr = hl_combine_attr(char_attr, win_hl_attr(wp, HLF_CUC)); + char_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUC), char_attr); } else if (draw_color_col && VCOL_HLC == *color_cols) { vcol_save_attr = char_attr; - char_attr = hl_combine_attr(char_attr, win_hl_attr(wp, HLF_MC)); + char_attr = hl_combine_attr(win_hl_attr(wp, HLF_MC), char_attr); } } @@ -5847,7 +5846,7 @@ static void screen_start_highlight(int attr) ui_start_highlight(attr); } -void screen_stop_highlight(void) +static void screen_stop_highlight(void) { ui_stop_highlight(); screen_attr = 0; diff --git a/src/nvim/search.c b/src/nvim/search.c index 1bf2317d2a..387614fd09 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -3557,11 +3557,15 @@ extend: --start_lnum; if (VIsual_active) { - /* Problem: when doing "Vipipip" nothing happens in a single white - * line, we get stuck there. Trap this here. */ - if (VIsual_mode == 'V' && start_lnum == curwin->w_cursor.lnum) + // Problem: when doing "Vipipip" nothing happens in a single white + // line, we get stuck there. Trap this here. + if (VIsual_mode == 'V' && start_lnum == curwin->w_cursor.lnum) { goto extend; - VIsual.lnum = start_lnum; + } + if (VIsual.lnum != start_lnum) { + VIsual.lnum = start_lnum; + VIsual.col = 0; + } VIsual_mode = 'V'; redraw_curbuf_later(INVERTED); /* update the inversion */ showmode(); diff --git a/src/nvim/state.c b/src/nvim/state.c index eb0b590a9b..4d9032b7a5 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -26,10 +26,11 @@ void state_enter(VimState *s) int check_result = s->check ? s->check(s) : 1; if (!check_result) { - break; + break; // Terminate this state. } else if (check_result == -1) { - continue; + continue; // check() again. } + // Execute this state. int key; @@ -48,11 +49,13 @@ getkey: ui_flush(); // Call `os_inchar` directly to block for events or user input without // consuming anything from `input_buffer`(os/input.c) or calling the - // mapping engine. If an event was put into the queue, we send K_EVENT - // directly. + // mapping engine. (void)os_inchar(NULL, 0, -1, 0); input_disable_events(); - key = !multiqueue_empty(main_loop.events) ? K_EVENT : safe_vgetc(); + // If an event was put into the queue, we send K_EVENT directly. + key = !multiqueue_empty(main_loop.events) + ? K_EVENT + : safe_vgetc(); } if (key == K_EVENT) { diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index f0171fa525..70bda42d83 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -42,6 +42,7 @@ #include "nvim/ui.h" #include "nvim/os/os.h" #include "nvim/os/time.h" +#include "nvim/api/private/helpers.h" static bool did_syntax_onoff = false; @@ -81,7 +82,10 @@ struct hl_group { // highlight groups for 'highlight' option static garray_T highlight_ga = GA_EMPTY_INIT_VALUE; -#define HL_TABLE() ((struct hl_group *)((highlight_ga.ga_data))) +static inline struct hl_group * HL_TABLE(void) +{ + return ((struct hl_group *)((highlight_ga.ga_data))); +} #define MAX_HL_ID 20000 /* maximum value for a highlight ID. */ @@ -100,10 +104,8 @@ static int include_none = 0; /* when 1 include "nvim/None" */ static int include_default = 0; /* when 1 include "nvim/default" */ static int include_link = 0; /* when 2 include "nvim/link" and "clear" */ -/* - * The "term", "cterm" and "gui" arguments can be any combination of the - * following names, separated by commas (but no spaces!). - */ +/// The "term", "cterm" and "gui" arguments can be any combination of the +/// following names, separated by commas (but no spaces!). static char *(hl_name_table[]) = {"bold", "standout", "underline", "undercurl", "italic", "reverse", "inverse", "NONE"}; @@ -1775,8 +1777,9 @@ syn_current_attr ( cur_si->si_trans_id = CUR_STATE( current_state.ga_len - 2).si_trans_id; } - } else + } else { cur_si->si_attr = syn_id2attr(syn_id); + } cur_si->si_cont_list = NULL; cur_si->si_next_list = next_list; check_keepend(); @@ -5252,12 +5255,10 @@ get_id_list ( /* * Handle full group name. */ - if (vim_strpbrk(name + 1, (char_u *)"\\.*^$~[") == NULL) + if (vim_strpbrk(name + 1, (char_u *)"\\.*^$~[") == NULL) { id = syn_check_group(name + 1, (int)(end - p)); - else { - /* - * Handle match of regexp with group names. - */ + } else { + // Handle match of regexp with group names. *name = '^'; STRCAT(name, "$"); regmatch.regprog = vim_regcomp(name, RE_MAGIC); @@ -5567,8 +5568,10 @@ bool syntax_present(win_T *win) static enum { - EXP_SUBCMD, /* expand ":syn" sub-commands */ - EXP_CASE /* expand ":syn case" arguments */ + EXP_SUBCMD, // expand ":syn" sub-commands + EXP_CASE, // expand ":syn case" arguments + EXP_SPELL, // expand ":syn spell" arguments + EXP_SYNC // expand ":syn sync" arguments } expand_what; /* @@ -5612,6 +5615,10 @@ void set_context_in_syntax_cmd(expand_T *xp, const char *arg) xp->xp_context = EXPAND_NOTHING; } else if (STRNICMP(arg, "case", p - arg) == 0) { expand_what = EXP_CASE; + } else if (STRNICMP(arg, "spell", p - arg) == 0) { + expand_what = EXP_SPELL; + } else if (STRNICMP(arg, "sync", p - arg) == 0) { + expand_what = EXP_SYNC; } else if (STRNICMP(arg, "keyword", p - arg) == 0 || STRNICMP(arg, "region", p - arg) == 0 || STRNICMP(arg, "match", p - arg) == 0 @@ -5624,17 +5631,33 @@ void set_context_in_syntax_cmd(expand_T *xp, const char *arg) } } -static char *(case_args[]) = {"match", "ignore", NULL}; - /* * Function given to ExpandGeneric() to obtain the list syntax names for * expansion. */ char_u *get_syntax_name(expand_T *xp, int idx) { - if (expand_what == EXP_SUBCMD) - return (char_u *)subcommands[idx].name; - return (char_u *)case_args[idx]; + switch (expand_what) { + case EXP_SUBCMD: + return (char_u *)subcommands[idx].name; + case EXP_CASE: { + static char *case_args[] = { "match", "ignore", NULL }; + return (char_u *)case_args[idx]; + } + case EXP_SPELL: { + static char *spell_args[] = + { "toplevel", "notoplevel", "default", NULL }; + return (char_u *)spell_args[idx]; + } + case EXP_SYNC: { + static char *sync_args[] = + { "ccomment", "clear", "fromstart", + "linebreaks=", "linecont", "lines=", "match", + "maxlines=", "minlines=", "region", NULL }; + return (char_u *)sync_args[idx]; + } + } + return NULL; } @@ -5845,9 +5868,12 @@ static void syntime_report(void) } } - /* sort on total time */ - qsort(ga.ga_data, (size_t)ga.ga_len, sizeof(time_entry_T), - syn_compare_syntime); + // Sort on total time. Skip if there are no items to avoid passing NULL + // pointer to qsort(). + if (ga.ga_len > 1) { + qsort(ga.ga_data, (size_t)ga.ga_len, sizeof(time_entry_T), + syn_compare_syntime); + } MSG_PUTS_TITLE(_( " TOTAL COUNT MATCH SLOWEST AVERAGE NAME PATTERN")); @@ -5958,6 +5984,7 @@ static char *highlight_init_light[] = "Title ctermfg=DarkMagenta gui=bold guifg=Magenta", "Visual guibg=LightGrey", "WarningMsg ctermfg=DarkRed guifg=Red", + "Normal gui=NONE", NULL }; @@ -5991,23 +6018,25 @@ static char *highlight_init_dark[] = "Title ctermfg=LightMagenta gui=bold guifg=Magenta", "Visual guibg=DarkGrey", "WarningMsg ctermfg=LightRed guifg=Red", + "Normal gui=NONE", NULL }; -void -init_highlight ( - int both, /* include groups where 'bg' doesn't matter */ - int reset /* clear group first */ -) + +/// Load colors from a file if "g:colors_name" is set, otherwise load builtin +/// colors +/// +/// @param both include groups where 'bg' doesn't matter +/// @param reset clear groups first +void +init_highlight(int both, int reset) { int i; char **pp; static int had_both = FALSE; - /* - * Try finding the color scheme file. Used when a color file was loaded - * and 'background' or 't_Co' is changed. - */ + // Try finding the color scheme file. Used when a color file was loaded + // and 'background' or 't_Co' is changed. char_u *p = get_var_value("g:colors_name"); if (p != NULL) { // Value of g:colors_name could be freed in load_colors() and make @@ -6026,33 +6055,34 @@ init_highlight ( if (both) { had_both = TRUE; pp = highlight_init_both; - for (i = 0; pp[i] != NULL; ++i) - do_highlight((char_u *)pp[i], reset, TRUE); - } else if (!had_both) - /* Don't do anything before the call with both == TRUE from main(). - * Not everything has been setup then, and that call will overrule - * everything anyway. */ + for (i = 0; pp[i] != NULL; i++) { + do_highlight((char_u *)pp[i], reset, true); + } + } else if (!had_both) { + // Don't do anything before the call with both == TRUE from main(). + // Not everything has been setup then, and that call will overrule + // everything anyway. return; + } - if (*p_bg == 'l') - pp = highlight_init_light; - else - pp = highlight_init_dark; - for (i = 0; pp[i] != NULL; ++i) - do_highlight((char_u *)pp[i], reset, TRUE); + pp = (*p_bg == 'l') ? highlight_init_light : highlight_init_dark; + + for (i = 0; pp[i] != NULL; i++) { + do_highlight((char_u *)pp[i], reset, true); + } /* Reverse looks ugly, but grey may not work for 8 colors. Thus let it * depend on the number of colors available. * With 8 colors brown is equal to yellow, need to use black for Search fg * to avoid Statement highlighted text disappears. * Clear the attributes, needed when changing the t_Co value. */ - if (t_colors > 8) + if (t_colors > 8) { do_highlight( (char_u *)(*p_bg == 'l' ? "Visual cterm=NONE ctermbg=LightGrey" - : "Visual cterm=NONE ctermbg=DarkGrey"), FALSE, - TRUE); - else { + : "Visual cterm=NONE ctermbg=DarkGrey"), false, + true); + } else { do_highlight((char_u *)"Visual cterm=reverse ctermbg=NONE", FALSE, TRUE); if (*p_bg == 'l') @@ -6112,12 +6142,7 @@ int load_colors(char_u *name) /// "forceit" and "init" both TRUE. /// @param init TRUE when called for initializing void -do_highlight( - char_u *line, - int forceit, - int init -) -{ +do_highlight(char_u *line, int forceit, int init) { char_u *name_end; char_u *linep; char_u *key_start; @@ -6134,15 +6159,16 @@ do_highlight( int dolink = FALSE; int error = FALSE; int color; - int is_normal_group = FALSE; /* "Normal" group */ + bool is_normal_group = false; // "Normal" group /* * If no argument, list current highlighting. */ if (ends_excmd(*line)) { - for (int i = 1; i <= highlight_ga.ga_len && !got_int; ++i) - /* TODO: only call when the group has attributes set */ + for (int i = 1; i <= highlight_ga.ga_len && !got_int; i++) { + // todo(vim): only call when the group has attributes set highlight_list_one(i); + } return; } @@ -6270,12 +6296,12 @@ do_highlight( return; idx = id - 1; /* index is ID minus one */ - /* Return if "default" was used and the group already has settings. */ - if (dodefault && hl_has_settings(idx, TRUE)) + // Return if "default" was used and the group already has settings + if (dodefault && hl_has_settings(idx, true)) { return; + } - if (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0) - is_normal_group = TRUE; + is_normal_group = (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0); /* Clear the highlighting for ":hi clear {group}" and ":hi clear". */ if (doclear || (forceit && init)) { @@ -6284,7 +6310,7 @@ do_highlight( HL_TABLE()[idx].sg_set = 0; } - if (!doclear) + if (!doclear) { while (!ends_excmd(*linep)) { key_start = linep; if (*linep == '=') { @@ -6390,12 +6416,12 @@ do_highlight( } } } else if (STRCMP(key, "FONT") == 0) { - /* in non-GUI fonts are simply ignored */ - } else if (STRCMP(key, - "CTERMFG") == 0 || STRCMP(key, "CTERMBG") == 0) { + // in non-GUI fonts are simply ignored + } else if (STRCMP(key, "CTERMFG") == 0 || STRCMP(key, "CTERMBG") == 0) { if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM)) { - if (!init) + if (!init) { HL_TABLE()[idx].sg_set |= SG_CTERM; + } /* When setting the foreground color, and previously the "bold" * flag was set for a light color, reset it now */ @@ -6489,9 +6515,10 @@ do_highlight( * colors (on some terminals, e.g. "linux") */ if (color & 8) { HL_TABLE()[idx].sg_cterm |= HL_BOLD; - HL_TABLE()[idx].sg_cterm_bold = TRUE; - } else + HL_TABLE()[idx].sg_cterm_bold = true; + } else { HL_TABLE()[idx].sg_cterm &= ~HL_BOLD; + } } color &= 7; // truncate to 8 colors } else if (t_colors == 16 || t_colors == 88 || t_colors >= 256) { @@ -6603,38 +6630,40 @@ do_highlight( /* * When highlighting has been given for a group, don't link it. */ - if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK)) + if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK)) { HL_TABLE()[idx].sg_link = 0; + } /* * Continue with next argument. */ linep = skipwhite(linep); } + } /* * If there is an error, and it's a new entry, remove it from the table. */ - if (error && idx == highlight_ga.ga_len) + if (error && idx == highlight_ga.ga_len) { syn_unadd_group(); - else { + } else { if (is_normal_group) { - HL_TABLE()[idx].sg_attr = 0; // Need to update all groups, because they might be using "bg" and/or // "fg", which have been changed now. highlight_attr_set_all(); // If the normal group has changed, it is simpler to refresh every UI ui_refresh(); - } else + } else { set_hl_attr(idx); + } HL_TABLE()[idx].sg_scriptID = current_SID; redraw_all_later(NOT_VALID); } xfree(key); xfree(arg); - /* Only call highlight_changed() once, after sourcing a syntax file */ - need_highlight_changed = TRUE; + // Only call highlight_changed() once, after sourcing a syntax file + need_highlight_changed = true; } #if defined(EXITFREE) @@ -6707,14 +6736,15 @@ static void highlight_clear(int idx) } -/* - * Table with the specifications for an attribute number. - * Note that this table is used by ALL buffers. This is required because the - * GUI can redraw at any time for any buffer. - */ +/// Table with the specifications for an attribute number. +/// Note that this table is used by ALL buffers. This is required because the +/// GUI can redraw at any time for any buffer. static garray_T attr_table = GA_EMPTY_INIT_VALUE; -#define ATTR_ENTRY(idx) ((attrentry_T *)attr_table.ga_data)[idx] +static inline attrentry_T * ATTR_ENTRY(int idx) +{ + return &((attrentry_T *)attr_table.ga_data)[idx]; +} /// Return the attr number for a set of colors and font. @@ -6804,7 +6834,7 @@ int hl_combine_attr(int char_attr, int prim_attr) { attrentry_T *char_aep = NULL; attrentry_T *spell_aep; - attrentry_T new_en; + attrentry_T new_en = ATTRENTRY_INIT; if (char_attr == 0) { return prim_attr; @@ -6820,8 +6850,6 @@ int hl_combine_attr(int char_attr, int prim_attr) if (char_aep != NULL) { // Copy all attributes from char_aep to the new entry new_en = *char_aep; - } else { - memset(&new_en, 0, sizeof(new_en)); } spell_aep = syn_cterm_attr2entry(prim_attr); @@ -6852,17 +6880,25 @@ int hl_combine_attr(int char_attr, int prim_attr) return get_attr_entry(&new_en); } +/// \note this function does not apply exclusively to cterm attr contrary +/// to what its name implies +/// \warn don't call it with attr 0 (i.e., the null attribute) attrentry_T *syn_cterm_attr2entry(int attr) { attr -= ATTR_OFF; - if (attr >= attr_table.ga_len) /* did ":syntax clear" */ + if (attr >= attr_table.ga_len) { + // did ":syntax clear" return NULL; - return &(ATTR_ENTRY(attr)); + } + return ATTR_ENTRY(attr); } +/// \addtogroup LIST_XXX +/// @{ #define LIST_ATTR 1 #define LIST_STRING 2 #define LIST_INT 3 +/// @} static void highlight_list_one(int id) { @@ -6901,7 +6937,13 @@ static void highlight_list_one(int id) last_set_msg(sgp->sg_scriptID); } -static int highlight_list_arg(int id, int didh, int type, int iarg, char_u *sarg, char *name) +/// Outputs a highlight when doing ":hi MyHighlight" +/// +/// @param type one of \ref LIST_XXX +/// @param iarg integer argument used if \p type == LIST_INT +/// @param sarg string used if \p type == LIST_STRING +static int highlight_list_arg(int id, int didh, int type, int iarg, + char_u *sarg, const char *name) { char_u buf[100]; char_u *ts; @@ -7041,24 +7083,23 @@ const char *highlight_color(const int id, const char *const what, return NULL; } -/* - * Output the syntax list header. - * Return TRUE when started a new line. - */ -static int -syn_list_header ( - int did_header, /* did header already */ - int outlen, /* length of string that comes */ - int id /* highlight group id */ -) +/// Output the syntax list header. +/// +/// @param did_header did header already +/// @param outlen length of string that comes +/// @param id highlight group id +/// @return true when started a new line. +static int +syn_list_header(int did_header, int outlen, int id) { int endcol = 19; int newline = TRUE; if (!did_header) { msg_putchar('\n'); - if (got_int) - return TRUE; + if (got_int) { + return true; + } msg_outtrans(HL_TABLE()[id - 1].sg_name); endcol = 15; } else if (msg_col + outlen + 1 >= Columns) { @@ -7086,21 +7127,14 @@ syn_list_header ( return newline; } -/* - * Set the attribute numbers for a highlight group. - * Called after one of the attributes has changed. - */ -static void -set_hl_attr ( - int idx /* index in array */ -) +/// Set the attribute numbers for a highlight group. +/// Called after one of the attributes has changed. +/// @param idx corrected highlight index +static void set_hl_attr(int idx) { - attrentry_T at_en; + attrentry_T at_en = ATTRENTRY_INIT; struct hl_group *sgp = HL_TABLE() + idx; - /* The "Normal" group doesn't need an attribute number */ - if (sgp->sg_name_u != NULL && STRCMP(sgp->sg_name_u, "NORMAL") == 0) - return; at_en.cterm_ae_attr = sgp->sg_cterm; at_en.cterm_fg_color = sgp->sg_cterm_fg; @@ -7124,10 +7158,10 @@ set_hl_attr ( } } -/* - * Lookup a highlight group name and return it's ID. - * If it is not found, 0 is returned. - */ +/// Lookup a highlight group name and return its ID. +/// +/// @param highlight name e.g. 'Cursor', 'Normal' +/// @return the highlight id, else 0 if \p name does not exist int syn_name2id(const char_u *name) { int i; @@ -7176,7 +7210,7 @@ int syn_namen2id(char_u *linep, int len) return id; } -/// Find highlight group name in the table and return it's ID. +/// Find highlight group name in the table and return its ID. /// If it doesn't exist yet, a new entry is created. /// /// @param pp Highlight group name @@ -7195,11 +7229,11 @@ int syn_check_group(char_u *pp, int len) return id; } -/* - * Add new highlight group and return it's ID. - * "name" must be an allocated string, it will be consumed. - * Return 0 for failure. - */ +/// Add new highlight group and return it's ID. +/// +/// @param name must be an allocated string, it will be consumed. +/// @return 0 for failure, else the allocated group id +/// @see syn_check_group syn_unadd_group static int syn_add_group(char_u *name) { char_u *p; @@ -7237,25 +7271,26 @@ static int syn_add_group(char_u *name) struct hl_group* hlgp = GA_APPEND_VIA_PTR(struct hl_group, &highlight_ga); memset(hlgp, 0, sizeof(*hlgp)); hlgp->sg_name = name; + hlgp->sg_rgb_bg = -1; + hlgp->sg_rgb_fg = -1; + hlgp->sg_rgb_sp = -1; hlgp->sg_name_u = vim_strsave_up(name); return highlight_ga.ga_len; /* ID is index plus one */ } -/* - * When, just after calling syn_add_group(), an error is discovered, this - * function deletes the new name. - */ +/// When, just after calling syn_add_group(), an error is discovered, this +/// function deletes the new name. static void syn_unadd_group(void) { - --highlight_ga.ga_len; + highlight_ga.ga_len--; xfree(HL_TABLE()[highlight_ga.ga_len].sg_name); xfree(HL_TABLE()[highlight_ga.ga_len].sg_name_u); } -/* - * Translate a group ID to highlight attributes. - */ + +/// Translate a group ID to highlight attributes. +/// @see syn_cterm_attr2entry int syn_id2attr(int hl_id) { struct hl_group *sgp; @@ -8208,6 +8243,30 @@ RgbValue name_to_color(const uint8_t *name) return -1; } +/// Gets highlight description for id `attr_id` as a map. +Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Error *err) +{ + HlAttrs attrs = HLATTRS_INIT; + Dictionary dic = ARRAY_DICT_INIT; + + if (attr_id == 0) { + goto end; + } + + attrentry_T *aep = syn_cterm_attr2entry((int)attr_id); + if (!aep) { + api_set_error(err, kErrorTypeException, + "Invalid attribute id: %d", attr_id); + return dic; + } + + attrs = attrentry2hlattrs(aep, rgb); + +end: + return hlattrs2dict(attrs); +} + + /************************************** * End of Highlighting stuff * **************************************/ diff --git a/src/nvim/syntax_defs.h b/src/nvim/syntax_defs.h index 9f309451b0..7260853703 100644 --- a/src/nvim/syntax_defs.h +++ b/src/nvim/syntax_defs.h @@ -2,7 +2,6 @@ #define NVIM_SYNTAX_DEFS_H #include "nvim/highlight_defs.h" -#include "nvim/regexp_defs.h" # define SST_MIN_ENTRIES 150 /* minimal size for state stack array */ # define SST_MAX_ENTRIES 1000 /* maximal size for state stack array */ @@ -10,6 +9,11 @@ # define SST_DIST 16 /* normal distance between entries */ # define SST_INVALID (synstate_T *)-1 /* invalid syn_state pointer */ +typedef struct syn_state synstate_T; + +#include "nvim/buffer_defs.h" +#include "nvim/regexp_defs.h" + typedef unsigned short disptick_T; /* display tick type */ /* struct passed to in_id_list() */ @@ -48,8 +52,6 @@ typedef struct buf_state { * syn_state contains the syntax state stack for the start of one line. * Used by b_sst_array[]. */ -typedef struct syn_state synstate_T; - struct syn_state { synstate_T *sst_next; /* next entry in used or free list */ linenr_T sst_lnum; /* line number for this state */ @@ -73,4 +75,14 @@ typedef struct attr_entry { int cterm_fg_color, cterm_bg_color; } attrentry_T; +#define ATTRENTRY_INIT { \ + .rgb_ae_attr = 0, \ + .cterm_ae_attr = 0, \ + .rgb_fg_color = -1, \ + .rgb_bg_color = -1, \ + .rgb_sp_color = -1, \ + .cterm_fg_color = 0, \ + .cterm_bg_color = 0, \ +} + #endif // NVIM_SYNTAX_DEFS_H diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index deec930ebd..1dac9c69bd 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -432,14 +432,6 @@ static int terminal_execute(VimState *state, int key) TerminalState *s = (TerminalState *)state; switch (key) { - case K_FOCUSGAINED: // nvim has been given focus - apply_autocmds(EVENT_FOCUSGAINED, NULL, NULL, false, curbuf); - break; - - case K_FOCUSLOST: // nvim has lost focus - apply_autocmds(EVENT_FOCUSLOST, NULL, NULL, false, curbuf); - break; - // Temporary fix until paste events gets implemented case K_PASTE: break; @@ -530,6 +522,12 @@ void terminal_send(Terminal *term, char *data, size_t size) void terminal_send_key(Terminal *term, int c) { VTermModifier mod = VTERM_MOD_NONE; + + // Convert K_ZERO back to ASCII + if (c == K_ZERO) { + c = Ctrl_AT; + } + VTermKey key = convert_key(c, &mod); if (key) { @@ -554,7 +552,7 @@ void terminal_receive(Terminal *term, char *data, size_t len) } void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, - int *term_attrs) + int *term_attrs) { int height, width; vterm_get_size(term->vt, &height, &width); @@ -783,26 +781,60 @@ static int term_sb_pop(int cols, VTermScreenCell *cells, void *data) // }}} // input handling {{{ -static void convert_modifiers(VTermModifier *statep) +static void convert_modifiers(int key, VTermModifier *statep) { if (mod_mask & MOD_MASK_SHIFT) { *statep |= VTERM_MOD_SHIFT; } if (mod_mask & MOD_MASK_CTRL) { *statep |= VTERM_MOD_CTRL; } if (mod_mask & MOD_MASK_ALT) { *statep |= VTERM_MOD_ALT; } + + switch (key) { + case K_S_TAB: + case K_S_UP: + case K_S_DOWN: + case K_S_LEFT: + case K_S_RIGHT: + case K_S_F1: + case K_S_F2: + case K_S_F3: + case K_S_F4: + case K_S_F5: + case K_S_F6: + case K_S_F7: + case K_S_F8: + case K_S_F9: + case K_S_F10: + case K_S_F11: + case K_S_F12: + *statep |= VTERM_MOD_SHIFT; + break; + + case K_C_LEFT: + case K_C_RIGHT: + *statep |= VTERM_MOD_CTRL; + break; + } } static VTermKey convert_key(int key, VTermModifier *statep) { - convert_modifiers(statep); + convert_modifiers(key, statep); switch (key) { case K_BS: return VTERM_KEY_BACKSPACE; + case K_S_TAB: // FALLTHROUGH case TAB: return VTERM_KEY_TAB; case Ctrl_M: return VTERM_KEY_ENTER; case ESC: return VTERM_KEY_ESCAPE; + case K_S_UP: // FALLTHROUGH case K_UP: return VTERM_KEY_UP; + case K_S_DOWN: // FALLTHROUGH case K_DOWN: return VTERM_KEY_DOWN; + case K_S_LEFT: // FALLTHROUGH + case K_C_LEFT: // FALLTHROUGH case K_LEFT: return VTERM_KEY_LEFT; + case K_S_RIGHT: // FALLTHROUGH + case K_C_RIGHT: // FALLTHROUGH case K_RIGHT: return VTERM_KEY_RIGHT; case K_INS: return VTERM_KEY_INS; @@ -812,22 +844,22 @@ static VTermKey convert_key(int key, VTermModifier *statep) case K_PAGEUP: return VTERM_KEY_PAGEUP; case K_PAGEDOWN: return VTERM_KEY_PAGEDOWN; - case K_K0: + case K_K0: // FALLTHROUGH case K_KINS: return VTERM_KEY_KP_0; - case K_K1: + case K_K1: // FALLTHROUGH case K_KEND: return VTERM_KEY_KP_1; case K_K2: return VTERM_KEY_KP_2; - case K_K3: + case K_K3: // FALLTHROUGH case K_KPAGEDOWN: return VTERM_KEY_KP_3; case K_K4: return VTERM_KEY_KP_4; case K_K5: return VTERM_KEY_KP_5; case K_K6: return VTERM_KEY_KP_6; - case K_K7: + case K_K7: // FALLTHROUGH case K_KHOME: return VTERM_KEY_KP_7; case K_K8: return VTERM_KEY_KP_8; - case K_K9: + case K_K9: // FALLTHROUGH case K_KPAGEUP: return VTERM_KEY_KP_9; - case K_KDEL: + case K_KDEL: // FALLTHROUGH case K_KPOINT: return VTERM_KEY_KP_PERIOD; case K_KENTER: return VTERM_KEY_KP_ENTER; case K_KPLUS: return VTERM_KEY_KP_PLUS; @@ -835,6 +867,57 @@ static VTermKey convert_key(int key, VTermModifier *statep) case K_KMULTIPLY: return VTERM_KEY_KP_MULT; case K_KDIVIDE: return VTERM_KEY_KP_DIVIDE; + case K_S_F1: // FALLTHROUGH + case K_F1: return VTERM_KEY_FUNCTION(1); + case K_S_F2: // FALLTHROUGH + case K_F2: return VTERM_KEY_FUNCTION(2); + case K_S_F3: // FALLTHROUGH + case K_F3: return VTERM_KEY_FUNCTION(3); + case K_S_F4: // FALLTHROUGH + case K_F4: return VTERM_KEY_FUNCTION(4); + case K_S_F5: // FALLTHROUGH + case K_F5: return VTERM_KEY_FUNCTION(5); + case K_S_F6: // FALLTHROUGH + case K_F6: return VTERM_KEY_FUNCTION(6); + case K_S_F7: // FALLTHROUGH + case K_F7: return VTERM_KEY_FUNCTION(7); + case K_S_F8: // FALLTHROUGH + case K_F8: return VTERM_KEY_FUNCTION(8); + case K_S_F9: // FALLTHROUGH + case K_F9: return VTERM_KEY_FUNCTION(9); + case K_S_F10: // FALLTHROUGH + case K_F10: return VTERM_KEY_FUNCTION(10); + case K_S_F11: // FALLTHROUGH + case K_F11: return VTERM_KEY_FUNCTION(11); + case K_S_F12: // FALLTHROUGH + case K_F12: return VTERM_KEY_FUNCTION(12); + + case K_F13: return VTERM_KEY_FUNCTION(13); + case K_F14: return VTERM_KEY_FUNCTION(14); + case K_F15: return VTERM_KEY_FUNCTION(15); + case K_F16: return VTERM_KEY_FUNCTION(16); + case K_F17: return VTERM_KEY_FUNCTION(17); + case K_F18: return VTERM_KEY_FUNCTION(18); + case K_F19: return VTERM_KEY_FUNCTION(19); + case K_F20: return VTERM_KEY_FUNCTION(20); + case K_F21: return VTERM_KEY_FUNCTION(21); + case K_F22: return VTERM_KEY_FUNCTION(22); + case K_F23: return VTERM_KEY_FUNCTION(23); + case K_F24: return VTERM_KEY_FUNCTION(24); + case K_F25: return VTERM_KEY_FUNCTION(25); + case K_F26: return VTERM_KEY_FUNCTION(26); + case K_F27: return VTERM_KEY_FUNCTION(27); + case K_F28: return VTERM_KEY_FUNCTION(28); + case K_F29: return VTERM_KEY_FUNCTION(29); + case K_F30: return VTERM_KEY_FUNCTION(30); + case K_F31: return VTERM_KEY_FUNCTION(31); + case K_F32: return VTERM_KEY_FUNCTION(32); + case K_F33: return VTERM_KEY_FUNCTION(33); + case K_F34: return VTERM_KEY_FUNCTION(34); + case K_F35: return VTERM_KEY_FUNCTION(35); + case K_F36: return VTERM_KEY_FUNCTION(36); + case K_F37: return VTERM_KEY_FUNCTION(37); + default: return VTERM_KEY_NONE; } } @@ -1176,6 +1259,10 @@ static void redraw(bool restore_cursor) update_screen(0); } + if (need_maketitle) { // Update title in terminal-mode. #7248 + maketitle(); + } + if (restore_cursor) { ui_cursor_goto(save_row, save_col); } else if (term) { diff --git a/src/nvim/terminal.h b/src/nvim/terminal.h index 25e609fb68..f2b0e232c3 100644 --- a/src/nvim/terminal.h +++ b/src/nvim/terminal.h @@ -10,6 +10,8 @@ typedef void (*terminal_write_cb)(char *buffer, size_t size, void *data); typedef void (*terminal_resize_cb)(uint16_t width, uint16_t height, void *data); typedef void (*terminal_close_cb)(void *data); +#include "nvim/buffer_defs.h" + typedef struct { void *data; uint16_t width, height; diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 7f1e25900b..38caa8815d 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -58,6 +58,8 @@ NEW_TESTS ?= \ test_match.res \ test_matchadd_conceal.res \ test_matchadd_conceal_utf8.res \ + test_mksession.res \ + test_mksession_utf8.res \ test_nested_function.res \ test_normal.res \ test_quickfix.res \ @@ -207,7 +209,7 @@ nolog: # 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; $(VALGRIND) $(NVIM_PRG) -u unix.vim -U NONE --headless --noplugin +RUN_VIMTEST = VIMRUNTIME=$(SCRIPTSOURCE); export VIMRUNTIME; $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE --headless --noplugin newtests: newtestssilent @/bin/sh -c "if test -f messages && grep -q 'FAILED' messages; then \ diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index d55170c27c..535e290a34 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -34,4 +34,5 @@ source test_taglist.vim source test_true_false.vim source test_unlet.vim source test_utf8.vim +source test_virtualedit.vim source test_window_cmd.vim diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index bab700284f..c0f04f4730 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -230,12 +230,41 @@ func Test_paste_in_cmdline() call feedkeys("f;:aaa \<C-R>\<C-A> bbb\<C-B>\"\<CR>", 'tx') call assert_equal('"aaa a;b-c*d bbb', @:) + + call feedkeys(":\<C-\>etoupper(getline(1))\<CR>\<C-B>\"\<CR>", 'tx') + call assert_equal('"ASDF.X /TMP/SOME VERYLONGWORD A;B-C*D ', @:) bwipe! endfunc -func Test_illegal_address() +func Test_remove_char_in_cmdline() + call feedkeys(":abc def\<S-Left>\<Del>\<C-B>\"\<CR>", 'tx') + call assert_equal('"abc ef', @:) + + call feedkeys(":abc def\<S-Left>\<BS>\<C-B>\"\<CR>", 'tx') + call assert_equal('"abcdef', @:) + + call feedkeys(":abc def ghi\<S-Left>\<C-W>\<C-B>\"\<CR>", 'tx') + call assert_equal('"abc ghi', @:) + + call feedkeys(":abc def\<S-Left>\<C-U>\<C-B>\"\<CR>", 'tx') + call assert_equal('"def', @:) +endfunc + +func Test_illegal_address1() new 2;'( 2;') quit endfunc + +func Test_illegal_address2() + call writefile(['c', 'x', ' x', '.', '1;y'], 'Xtest.vim') + new + source Xtest.vim + " Trigger calling validate_cursor() + diffsp Xtest.vim + quit! + bwipe! + call delete('Xtest.vim') +endfunc + diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 5de394de8e..8ee82bd538 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -212,9 +212,63 @@ func Test_diffoff() call setline(1, ['One', '', 'Two', 'Three']) diffthis redraw + call assert_notequal(normattr, screenattr(1, 1)) diffoff! redraw call assert_equal(normattr, screenattr(1, 1)) bwipe! bwipe! endfunc + +func Test_diffoff_hidden() + set diffopt=filler,foldcolumn:0 + e! one + call setline(1, ['Two', 'Three']) + let normattr = screenattr(1, 1) + diffthis + botright vert new two + call setline(1, ['One', 'Four']) + diffthis + redraw + call assert_notequal(normattr, screenattr(1, 1)) + set hidden + close + redraw + " diffing with hidden buffer two + call assert_notequal(normattr, screenattr(1, 1)) + diffoff + redraw + call assert_equal(normattr, screenattr(1, 1)) + diffthis + redraw + " still diffing with hidden buffer two + call assert_notequal(normattr, screenattr(1, 1)) + diffoff! + redraw + call assert_equal(normattr, screenattr(1, 1)) + diffthis + redraw + " no longer diffing with hidden buffer two + call assert_equal(normattr, screenattr(1, 1)) + + bwipe! + bwipe! + set hidden& diffopt& +endfunc + +func Test_setting_cursor() + new Xtest1 + put =range(1,90) + wq + new Xtest2 + put =range(1,100) + wq + + tabe Xtest2 + $ + diffsp Xtest1 + tabclose + + call delete('Xtest1') + call delete('Xtest2') +endfunc diff --git a/src/nvim/testdir/test_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim index 1ca0f722cf..65d99c644c 100644 --- a/src/nvim/testdir/test_help_tagjump.vim +++ b/src/nvim/testdir/test_help_tagjump.vim @@ -89,17 +89,8 @@ func s:doc_config_teardown() endif endfunc -func s:get_cmd_compl_list(cmd) - let list = [] - let str = '' - for cnt in range(1, 999) - call feedkeys(a:cmd . repeat("\<Tab>", cnt) . "'\<C-B>let str='\<CR>", 'tx') - if str ==# a:cmd[1:] - break - endif - call add(list, str) - endfor - return list +func s:get_help_compl_list(cmd) + return getcompletion(a:cmd, 'help') endfunc func Test_help_complete() @@ -111,49 +102,49 @@ func Test_help_complete() if has('multi_lang') set helplang= endif - let list = s:get_cmd_compl_list(":h test") - call assert_equal(['h test-col', 'h test-char'], list) + let list = s:get_help_compl_list("test") + call assert_equal(['test-col', 'test-char'], list) if has('multi_lang') " 'helplang=ab' and help file lang is 'en' set helplang=ab - let list = s:get_cmd_compl_list(":h test") - call assert_equal(['h test-col', 'h test-char'], list) + let list = s:get_help_compl_list("test") + call assert_equal(['test-col', 'test-char'], list) " 'helplang=' and help file lang is 'en' and 'ab' set rtp+=Xdir1/doc-ab set helplang= - let list = s:get_cmd_compl_list(":h test") - call assert_equal(sort(['h test-col@en', 'h test-col@ab', - \ 'h test-char@en', 'h test-char@ab']), sort(list)) + let list = s:get_help_compl_list("test") + call assert_equal(sort(['test-col@en', 'test-col@ab', + \ 'test-char@en', 'test-char@ab']), sort(list)) " 'helplang=ab' and help file lang is 'en' and 'ab' set helplang=ab - let list = s:get_cmd_compl_list(":h test") - call assert_equal(sort(['h test-col', 'h test-col@en', - \ 'h test-char', 'h test-char@en']), sort(list)) + let list = s:get_help_compl_list("test") + call assert_equal(sort(['test-col', 'test-col@en', + \ 'test-char', 'test-char@en']), sort(list)) " 'helplang=' and help file lang is 'en', 'ab' and 'ja' set rtp+=Xdir1/doc-ja set helplang= - let list = s:get_cmd_compl_list(":h test") - call assert_equal(sort(['h test-col@en', 'h test-col@ab', - \ 'h test-col@ja', 'h test-char@en', - \ 'h test-char@ab', 'h test-char@ja']), sort(list)) + let list = s:get_help_compl_list("test") + call assert_equal(sort(['test-col@en', 'test-col@ab', + \ 'test-col@ja', 'test-char@en', + \ 'test-char@ab', 'test-char@ja']), sort(list)) " 'helplang=ab' and help file lang is 'en', 'ab' and 'ja' set helplang=ab - let list = s:get_cmd_compl_list(":h test") - call assert_equal(sort(['h test-col', 'h test-col@en', - \ 'h test-col@ja', 'h test-char', - \ 'h test-char@en', 'h test-char@ja']), sort(list)) + let list = s:get_help_compl_list("test") + call assert_equal(sort(['test-col', 'test-col@en', + \ 'test-col@ja', 'test-char', + \ 'test-char@en', 'test-char@ja']), sort(list)) " 'helplang=ab,ja' and help file lang is 'en', 'ab' and 'ja' set helplang=ab,ja - let list = s:get_cmd_compl_list(":h test") - call assert_equal(sort(['h test-col', 'h test-col@ja', - \ 'h test-col@en', 'h test-char', - \ 'h test-char@ja', 'h test-char@en']), sort(list)) + let list = s:get_help_compl_list("test") + call assert_equal(sort(['test-col', 'test-col@ja', + \ 'test-col@en', 'test-char', + \ 'test-char@ja', 'test-char@en']), sort(list)) endif catch call assert_exception('X') diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim new file mode 100644 index 0000000000..4774cf4af5 --- /dev/null +++ b/src/nvim/testdir/test_mksession.vim @@ -0,0 +1,155 @@ +" Test for :mksession, :mkview and :loadview in latin1 encoding + +scriptencoding latin1 + +if !has('multi_byte') || !has('mksession') + finish +endif + +func Test_mksession() + tabnew + let wrap_save = &wrap + set sessionoptions=buffers splitbelow fileencoding=latin1 + call setline(1, [ + \ 'start:', + \ 'no multibyte chAracter', + \ ' one leaDing tab', + \ ' four leadinG spaces', + \ 'two consecutive tabs', + \ 'two tabs in one line', + \ 'one multibyteCharacter', + \ 'a two multiByte characters', + \ 'A three mulTibyte characters' + \ ]) + let tmpfile = 'Xtemp' + exec 'w! ' . tmpfile + /^start: + set wrap + vsplit + norm! j16| + split + norm! j16| + split + norm! j16| + split + norm! j8| + split + norm! j8| + split + norm! j16| + split + norm! j16| + split + norm! j16| + wincmd l + + set nowrap + /^start: + norm! j16|3zl + split + norm! j016|3zl + split + norm! j016|3zl + split + norm! j08|3zl + split + norm! j08|3zl + split + norm! j016|3zl + split + norm! j016|3zl + split + norm! j016|3zl + split + call wincol() + mksession! Xtest_mks.out + let li = filter(readfile('Xtest_mks.out'), 'v:val =~# "\\(^ *normal! 0\\|^ *exe ''normal!\\)"') + let expected = [ + \ 'normal! 016|', + \ 'normal! 016|', + \ 'normal! 016|', + \ 'normal! 08|', + \ 'normal! 08|', + \ 'normal! 016|', + \ 'normal! 016|', + \ 'normal! 016|', + \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", + \ " normal! 016|", + \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", + \ " normal! 016|", + \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", + \ " normal! 016|", + \ " exe 'normal! ' . s:c . '|zs' . 8 . '|'", + \ " normal! 08|", + \ " exe 'normal! ' . s:c . '|zs' . 8 . '|'", + \ " normal! 08|", + \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", + \ " normal! 016|", + \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", + \ " normal! 016|", + \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", + \ " normal! 016|", + \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", + \ " normal! 016|" + \ ] + call assert_equal(expected, li) + tabclose! + + call delete('Xtest_mks.out') + call delete(tmpfile) + let &wrap = wrap_save +endfunc + +func Test_mksession_winheight() + new + set winheight=10 winminheight=2 + mksession! Xtest_mks.out + source Xtest_mks.out + + call delete('Xtest_mks.out') +endfunc + +" Verify that arglist is stored correctly to the session file. +func Test_mksession_arglist() + argdel * + next file1 file2 file3 file4 + mksession! Xtest_mks.out + source Xtest_mks.out + call assert_equal(['file1', 'file2', 'file3', 'file4'], argv()) + + call delete('Xtest_mks.out') + argdel * +endfunc + + +func Test_mksession_one_buffer_two_windows() + edit Xtest1 + new Xtest2 + split + mksession! Xtest_mks.out + let lines = readfile('Xtest_mks.out') + let count1 = 0 + let count2 = 0 + let count2buf = 0 + for line in lines + if line =~ 'edit \f*Xtest1$' + let count1 += 1 + endif + if line =~ 'edit \f\{-}Xtest2' + let count2 += 1 + endif + if line =~ 'buffer \f\{-}Xtest2' + let count2buf += 1 + endif + endfor + call assert_equal(1, count1, 'Xtest1 count') + call assert_equal(2, count2, 'Xtest2 count') + call assert_equal(2, count2buf, 'Xtest2 buffer count') + + close + bwipe! + call delete('Xtest_mks.out') +endfunc + + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_mksession_utf8.vim b/src/nvim/testdir/test_mksession_utf8.vim new file mode 100644 index 0000000000..c05a1d3b6d --- /dev/null +++ b/src/nvim/testdir/test_mksession_utf8.vim @@ -0,0 +1,104 @@ +" Test for :mksession, :mkview and :loadview in utf-8 encoding + +set encoding=utf-8 +scriptencoding utf-8 + +if !has('multi_byte') || !has('mksession') + finish +endif + +func Test_mksession_utf8() + tabnew + let wrap_save = &wrap + set sessionoptions=buffers splitbelow fileencoding=utf-8 + call setline(1, [ + \ 'start:', + \ 'no multibyte chAracter', + \ ' one leaDing tab', + \ ' four leadinG spaces', + \ 'two consecutive tabs', + \ 'two tabs in one line', + \ 'one … multibyteCharacter', + \ 'a “b” two multiByte characters', + \ '“c”1€ three mulTibyte characters' + \ ]) + let tmpfile = tempname() + exec 'w! ' . tmpfile + /^start: + set wrap + vsplit + norm! j16| + split + norm! j16| + split + norm! j16| + split + norm! j8| + split + norm! j8| + split + norm! j16| + split + norm! j16| + split + norm! j16| + wincmd l + + set nowrap + /^start: + norm! j16|3zl + split + norm! j016|3zl + split + norm! j016|3zl + split + norm! j08|3zl + split + norm! j08|3zl + split + norm! j016|3zl + split + norm! j016|3zl + split + norm! j016|3zl + split + call wincol() + mksession! test_mks.out + let li = filter(readfile('test_mks.out'), 'v:val =~# "\\(^ *normal! 0\\|^ *exe ''normal!\\)"') + let expected = [ + \ 'normal! 016|', + \ 'normal! 016|', + \ 'normal! 016|', + \ 'normal! 08|', + \ 'normal! 08|', + \ 'normal! 016|', + \ 'normal! 016|', + \ 'normal! 016|', + \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", + \ " normal! 016|", + \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", + \ " normal! 016|", + \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", + \ " normal! 016|", + \ " exe 'normal! ' . s:c . '|zs' . 8 . '|'", + \ " normal! 08|", + \ " exe 'normal! ' . s:c . '|zs' . 8 . '|'", + \ " normal! 08|", + \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", + \ " normal! 016|", + \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", + \ " normal! 016|", + \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", + \ " normal! 016|", + \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", + \ " normal! 016|" + \ ] + call assert_equal(expected, li) + tabclose! + + call delete('test_mks.out') + call delete(tmpfile) + let &wrap = wrap_save +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 5ee0919e18..08ee00e352 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -13,6 +13,12 @@ function! Test_whichwrap() set whichwrap+=h,l call assert_equal('b,s,h,l', &whichwrap) + set whichwrap=h,h + call assert_equal('h', &whichwrap) + + set whichwrap=h,h,h + call assert_equal('h', &whichwrap) + set whichwrap& endfunction @@ -97,3 +103,36 @@ func Test_keymap_valid() call assert_fails(":set kmp=trunc\x00name", "E544:") call assert_fails(":set kmp=trunc\x00name", "trunc") endfunc + +func Check_dir_option(name) + " Check that it's possible to set the option. + exe 'set ' . a:name . '=/usr/share/dict/words' + call assert_equal('/usr/share/dict/words', eval('&' . a:name)) + exe 'set ' . a:name . '=/usr/share/dict/words,/and/there' + call assert_equal('/usr/share/dict/words,/and/there', eval('&' . a:name)) + exe 'set ' . a:name . '=/usr/share/dict\ words' + call assert_equal('/usr/share/dict words', eval('&' . a:name)) + + " Check rejecting weird characters. + call assert_fails("set " . a:name . "=/not&there", "E474:") + call assert_fails("set " . a:name . "=/not>there", "E474:") + call assert_fails("set " . a:name . "=/not.*there", "E474:") +endfunc + +func Test_dictionary() + call Check_dir_option('dictionary') +endfunc + +func Test_thesaurus() + call Check_dir_option('thesaurus') +endfunc + +func Test_complete() + " Trailing single backslash used to cause invalid memory access. + set complete=s\ + new + call feedkeys("i\<C-N>\<Esc>", 'xt') + bwipe! + set complete& +endfun + diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index 519d855cd8..e1ba142d1c 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -7,10 +7,10 @@ func! ListMonths() if g:setting != '' exe ":set" g:setting endif - let mth=copy(g:months) + let mth = copy(g:months) let entered = strcharpart(getline('.'),0,col('.')) if !empty(entered) - let mth=filter(mth, 'v:val=~"^".entered') + let mth = filter(mth, 'v:val=~"^".entered') endif call complete(1, mth) return '' @@ -468,7 +468,7 @@ endfunc " auto-wrap text. func Test_completion_ctrl_e_without_autowrap() new - let tw_save=&tw + let tw_save = &tw set tw=78 let li = [ \ '" zzz', @@ -478,7 +478,7 @@ func Test_completion_ctrl_e_without_autowrap() call feedkeys("A\<C-X>\<C-N>\<C-E>\<Esc>", "tx") call assert_equal(li, getline(1, '$')) - let &tw=tw_save + let &tw = tw_save q! endfunc @@ -541,4 +541,33 @@ func Test_completion_comment_formatting() bwipe! endfunc +function! DummyCompleteSix() + call complete(1, ['Hello', 'World']) + return '' +endfunction + +" complete() correctly clears the list of autocomplete candidates +func Test_completion_clear_candidate_list() + new + %d + " select first entry from the completion popup + call feedkeys("a xxx\<C-N>\<C-R>=DummyCompleteSix()\<CR>", "tx") + call assert_equal('Hello', getline(1)) + %d + " select second entry from the completion popup + call feedkeys("a xxx\<C-N>\<C-R>=DummyCompleteSix()\<CR>\<C-N>", "tx") + call assert_equal('World', getline(1)) + %d + " select original text + call feedkeys("a xxx\<C-N>\<C-R>=DummyCompleteSix()\<CR>\<C-N>\<C-N>", "tx") + call assert_equal(' xxx', getline(1)) + %d + " back at first entry from completion list + call feedkeys("a xxx\<C-N>\<C-R>=DummyCompleteSix()\<CR>\<C-N>\<C-N>\<C-N>", "tx") + call assert_equal('Hello', getline(1)) + + bw! +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index 64f7f31294..11e26d03aa 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -24,28 +24,34 @@ func Test_after_comes_later() \ 'set guioptions+=M', \ 'let $HOME = "/does/not/exist"', \ 'set loadplugins', - \ 'set rtp=Xhere,Xafter', + \ 'set rtp=Xhere,Xafter,Xanother', \ 'set packpath=Xhere,Xafter', \ 'set nomore', + \ 'let g:sequence = ""', \ ] let after = [ \ 'redir! > Xtestout', \ 'scriptnames', \ 'redir END', + \ 'redir! > Xsequence', + \ 'echo g:sequence', + \ 'redir END', \ 'quit', \ ] call mkdir('Xhere/plugin', 'p') - call writefile(['let done = 1'], 'Xhere/plugin/here.vim') + call writefile(['let g:sequence .= "here "'], 'Xhere/plugin/here.vim') + call mkdir('Xanother/plugin', 'p') + call writefile(['let g:sequence .= "another "'], 'Xanother/plugin/another.vim') call mkdir('Xhere/pack/foo/start/foobar/plugin', 'p') - call writefile(['let done = 1'], 'Xhere/pack/foo/start/foobar/plugin/foo.vim') + call writefile(['let g:sequence .= "pack "'], 'Xhere/pack/foo/start/foobar/plugin/foo.vim') call mkdir('Xafter/plugin', 'p') - call writefile(['let done = 1'], 'Xafter/plugin/later.vim') + call writefile(['let g:sequence .= "after "'], 'Xafter/plugin/later.vim') if RunVim(before, after, '') let lines = readfile('Xtestout') - let expected = ['Xbefore.vim', 'here.vim', 'foo.vim', 'later.vim', 'Xafter.vim'] + let expected = ['Xbefore.vim', 'here.vim', 'another.vim', 'foo.vim', 'later.vim', 'Xafter.vim'] let found = [] for line in lines for one in expected @@ -57,11 +63,47 @@ func Test_after_comes_later() call assert_equal(expected, found) endif + call assert_equal('here another pack after', substitute(join(readfile('Xsequence', 1), ''), '\s\+$', '', '')) + call delete('Xtestout') + call delete('Xsequence') call delete('Xhere', 'rf') + call delete('Xanother', 'rf') call delete('Xafter', 'rf') endfunc +func Test_pack_in_rtp_when_plugins_run() + if !has('packages') + return + endif + let before = [ + \ 'set nocp viminfo+=nviminfo', + \ 'set guioptions+=M', + \ 'let $HOME = "/does/not/exist"', + \ 'set loadplugins', + \ 'set rtp=Xhere', + \ 'set packpath=Xhere', + \ 'set nomore', + \ ] + let after = [ + \ 'quit', + \ ] + call mkdir('Xhere/plugin', 'p') + call writefile(['redir! > Xtestout', 'silent set runtimepath?', 'silent! call foo#Trigger()', 'redir END'], 'Xhere/plugin/here.vim') + call mkdir('Xhere/pack/foo/start/foobar/autoload', 'p') + call writefile(['function! foo#Trigger()', 'echo "autoloaded foo"', 'endfunction'], 'Xhere/pack/foo/start/foobar/autoload/foo.vim') + + if RunVim(before, after, '') + + let lines = filter(readfile('Xtestout'), '!empty(v:val)') + call assert_match('Xhere[/\\]pack[/\\]foo[/\\]start[/\\]foobar', get(lines, 0)) + call assert_match('autoloaded foo', get(lines, 1)) + endif + + call delete('Xtestout') + call delete('Xhere', 'rf') +endfunc + func Test_help_arg() if !has('unix') && has('gui') " this doesn't work with gvim on MS-Windows @@ -76,11 +118,11 @@ func Test_help_arg() let found = [] for line in lines if line =~ '-R.*Read-only mode' - call add(found, 'Readonly mode') + call add(found, 'Readonly mode') endif " Watch out for a second --version line in the Gnome version. - if line =~ '--version.*Print version information and exit' - call add(found, "--version") + if line =~ '--version.*Print version information' + call add(found, "--version") endif endfor call assert_equal(['Readonly mode', '--version'], found) diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index af2cbbfe8e..6c084dd2a7 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -50,7 +50,7 @@ func Test_syn_iskeyword() setlocal isk-=_ call assert_equal('DLTD_BY', GetSyntaxItem('DLTD')) /\<D\k\+\>/:norm! ygn - let b2=@0 + let b2 = @0 call assert_equal('DLTD', @0) syn iskeyword clear @@ -76,3 +76,85 @@ func Test_syntax_after_reload() call assert_true(exists('g:gotit')) call delete('Xsomefile') endfunc + +func Test_syntime() + if !has('profile') + return + endif + + syntax on + syntime on + let a = execute('syntime report') + call assert_equal("\nNo Syntax items defined for this buffer", a) + + view ../memfile_test.c + setfiletype cpp + redraw + let a = execute('syntime report') + call assert_match('^ TOTAL *COUNT *MATCH *SLOWEST *AVERAGE *NAME *PATTERN', a) + call assert_match(' \d*\.\d* \+[^0]\d* .* cppRawString ', a) + call assert_match(' \d*\.\d* \+[^0]\d* .* cppNumber ', a) + + syntime off + syntime clear + let a = execute('syntime report') + call assert_match('^ TOTAL *COUNT *MATCH *SLOWEST *AVERAGE *NAME *PATTERN', a) + call assert_notmatch('.* cppRawString *', a) + call assert_notmatch('.* cppNumber*', a) + call assert_notmatch('[1-9]', a) + + call assert_fails('syntime abc', 'E475') + + syntax clear + let a = execute('syntime report') + call assert_equal("\nNo Syntax items defined for this buffer", a) + + bd +endfunc + +func Test_syntax_list() + syntax on + let a = execute('syntax list') + call assert_equal("\nNo Syntax items defined for this buffer", a) + + view ../memfile_test.c + setfiletype c + + let a = execute('syntax list') + call assert_match('cInclude*', a) + call assert_match('cDefine', a) + + let a = execute('syntax list cDefine') + call assert_notmatch('cInclude*', a) + call assert_match('cDefine', a) + call assert_match(' links to Macro$', a) + + call assert_fails('syntax list ABCD', 'E28:') + call assert_fails('syntax list @ABCD', 'E392:') + + syntax clear + let a = execute('syntax list') + call assert_equal("\nNo Syntax items defined for this buffer", a) + + bd +endfunc + +func Test_syntax_completion() + call feedkeys(":syn \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"syn case clear cluster conceal enable include iskeyword keyword list manual match off on region reset spell sync', @:) + + call feedkeys(":syn case \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"syn case ignore match', @:) + + call feedkeys(":syn spell \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"syn spell default notoplevel toplevel', @:) + + call feedkeys(":syn sync \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"syn sync ccomment clear fromstart linebreaks= linecont lines= match maxlines= minlines= region', @:) + + call feedkeys(":syn list \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_match('^"syn list Boolean Character ', @:) + + call feedkeys(":syn match \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_match('^"syn match Boolean Character ', @:) +endfunc
\ No newline at end of file diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim new file mode 100644 index 0000000000..2b8849f488 --- /dev/null +++ b/src/nvim/testdir/test_virtualedit.vim @@ -0,0 +1,43 @@ +" Tests for 'virtualedit'. + +func Test_yank_move_change() + new + call setline(1, [ + \ "func foo() error {", + \ "\tif n, err := bar();", + \ "\terr != nil {", + \ "\t\treturn err", + \ "\t}", + \ "\tn = n * n", + \ ]) + set virtualedit=all + set ts=4 + function! MoveSelectionDown(count) abort + normal! m` + silent! exe "'<,'>move'>+".a:count + norm! `` + endfunction + + xmap ]e :<C-U>call MoveSelectionDown(v:count1)<CR> + 2 + normal 2gg + normal J + normal jVj + normal ]e + normal ce + bwipe! + set virtualedit= + set ts=8 +endfunc + +func Test_paste_end_of_line() + new + set virtualedit=all + call setline(1, ['456', '123']) + normal! gg0"ay$ + exe "normal! 2G$lllA\<C-O>:normal! \"agP\r" + call assert_equal('123456', getline(2)) + + bwipe! + set virtualedit= +endfunc diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index cf0e535937..1694adbd32 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -1,13 +1,13 @@ -" Tests for Visual mode -if !has('multi_byte') - finish -endif - +" Tests for various Visual mode. if !has('visual') finish endif func Test_block_shift_multibyte() + " Uses double-wide character. + if !has('multi_byte') + return + endif split call setline(1, ['xヹxxx', 'ヹxxx']) exe "normal 1G0l\<C-V>jl>" @@ -15,3 +15,31 @@ func Test_block_shift_multibyte() call assert_equal(' ヹxxx', getline(2)) q! endfunc + +func Test_Visual_ctrl_o() + new + call setline(1, ['one', 'two', 'three']) + call cursor(1,2) + set noshowmode + set tw=0 + call feedkeys("\<c-v>jjlIa\<c-\>\<c-o>:set tw=88\<cr>\<esc>", 'tx') + call assert_equal(['oane', 'tawo', 'tahree'], getline(1, 3)) + call assert_equal(88, &tw) + set tw& + bw! +endfu + +func Test_Visual_vapo() + new + normal oxx + normal vapo + bwipe! +endfunc + +func Test_dotregister_paste() + new + exe "norm! ihello world\<esc>" + norm! 0ve".p + call assert_equal('hello world world', getline(1)) + q! +endfunc diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index 188a7ed0f3..9d61921988 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -336,6 +336,50 @@ func Test_window_width() bw Xa Xb Xc endfunc +func Test_equalalways_on_close() + set equalalways + vsplit + windo split + split + wincmd J + " now we have a frame top-left with two windows, a frame top-right with two + " windows and a frame at the bottom, full-width. + let height_1 = winheight(1) + let height_2 = winheight(2) + let height_3 = winheight(3) + let height_4 = winheight(4) + " closing the bottom window causes all windows to be resized. + close + call assert_notequal(height_1, winheight(1)) + call assert_notequal(height_2, winheight(2)) + call assert_notequal(height_3, winheight(3)) + call assert_notequal(height_4, winheight(4)) + call assert_equal(winheight(1), winheight(3)) + call assert_equal(winheight(2), winheight(4)) + + 1wincmd w + split + 4wincmd w + resize + 5 + " left column has three windows, equalized heights. + " right column has two windows, top one a bit higher + let height_1 = winheight(1) + let height_2 = winheight(2) + let height_4 = winheight(4) + let height_5 = winheight(5) + 3wincmd w + " closing window in left column equalizes heights in left column but not in + " the right column + close + call assert_notequal(height_1, winheight(1)) + call assert_notequal(height_2, winheight(2)) + call assert_equal(height_4, winheight(3)) + call assert_equal(height_5, winheight(4)) + + only + set equalalways& +endfunc + func Test_window_jump_tag() help /iccf diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 03587d68f0..8bb5971bd4 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -8,6 +8,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/main.h" +#include "nvim/aucmd.h" #include "nvim/os/os.h" #include "nvim/os/input.h" #include "nvim/event/rstream.h" @@ -280,9 +281,9 @@ static void timer_cb(TimeWatcher *watcher, void *data) /// Handle focus events. /// -/// If the upcoming sequence of bytes in the input stream matches either the -/// escape code for focus gained `<ESC>[I` or focus lost `<ESC>[O` then consume -/// that sequence and push the appropriate event into the input queue +/// If the upcoming sequence of bytes in the input stream matches the termcode +/// for "focus gained" or "focus lost", consume that sequence and schedule an +/// event on the main loop. /// /// @param input the input stream /// @return true iff handle_focus_event consumed some input @@ -294,11 +295,7 @@ static bool handle_focus_event(TermInput *input) // Advance past the sequence bool focus_gained = *rbuffer_get(input->read_stream.buffer, 2) == 'I'; rbuffer_consumed(input->read_stream.buffer, 3); - if (focus_gained) { - enqueue_input(input, FOCUSGAINED_KEY, sizeof(FOCUSGAINED_KEY) - 1); - } else { - enqueue_input(input, FOCUSLOST_KEY, sizeof(FOCUSLOST_KEY) - 1); - } + aucmd_schedule_focusgained(focus_gained); return true; } return false; diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c index b8fffcb7d6..586fafba97 100644 --- a/src/nvim/tui/terminfo.c +++ b/src/nvim/tui/terminfo.c @@ -104,7 +104,9 @@ unibi_term *load_builtin_terminfo(const char * term) return unibi_from_mem((const char *)interix_8colour_terminfo, sizeof interix_8colour_terminfo); } else if (terminfo_is_term_family(term, "iterm") - || terminfo_is_term_family(term, "iTerm.app")) { + || terminfo_is_term_family(term, "iterm2") + || terminfo_is_term_family(term, "iTerm.app") + || terminfo_is_term_family(term, "iTerm2.app")) { return unibi_from_mem((const char *)iterm_256colour_terminfo, sizeof iterm_256colour_terminfo); } else if (terminfo_is_term_family(term, "st")) { diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index b5af5b0333..8e0e905bcd 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -52,6 +52,15 @@ #define LINUXSET0C "\x1b[?0c" #define LINUXSET1C "\x1b[?1c" +#ifdef NVIM_UNIBI_HAS_VAR_FROM +#define UNIBI_SET_NUM_VAR(var, num) \ + do { \ + (var) = unibi_var_from_num((num)); \ + } while (0) +#else +#define UNIBI_SET_NUM_VAR(var, num) (var).i = (num); +#endif + // Per the commentary in terminfo, only a minus sign is a true suffix // separator. bool terminfo_is_term_family(const char *term, const char *family) @@ -234,9 +243,9 @@ static void terminfo_start(UI *ui) unibi_out(ui, unibi_keypad_xmit); unibi_out(ui, unibi_clear_screen); // Enable bracketed paste - unibi_out(ui, data->unibi_ext.enable_bracketed_paste); + unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); // Enable focus reporting - unibi_out(ui, data->unibi_ext.enable_focus_reporting); + unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting); uv_loop_init(&data->write_loop); if (data->out_isatty) { uv_tty_init(&data->write_loop, &data->output_handle.tty, data->out_fd, 0); @@ -263,9 +272,9 @@ static void terminfo_stop(UI *ui) unibi_out(ui, unibi_keypad_local); unibi_out(ui, unibi_exit_ca_mode); // Disable bracketed paste - unibi_out(ui, data->unibi_ext.disable_bracketed_paste); + unibi_out_ext(ui, data->unibi_ext.disable_bracketed_paste); // Disable focus reporting - unibi_out(ui, data->unibi_ext.disable_focus_reporting); + unibi_out_ext(ui, data->unibi_ext.disable_focus_reporting); flush_buf(ui, true); uv_tty_reset_mode(); uv_close((uv_handle_t *)&data->output_handle, NULL); @@ -279,7 +288,7 @@ static void terminfo_stop(UI *ui) static void tui_terminal_start(UI *ui) { TUIData *data = ui->data; - data->print_attrs = EMPTY_ATTRS; + data->print_attrs = HLATTRS_INIT; ugrid_init(&data->grid); terminfo_start(ui); update_size(ui); @@ -391,15 +400,15 @@ static void update_attrs(UI *ui, HlAttrs attrs) if (unibi_get_str(data->ut, unibi_set_attributes)) { if (attrs.bold || attrs.reverse || attrs.underline || attrs.undercurl) { - data->params[0].i = 0; // standout - data->params[1].i = attrs.underline || attrs.undercurl; - data->params[2].i = attrs.reverse; - data->params[3].i = 0; // blink - data->params[4].i = 0; // dim - data->params[5].i = attrs.bold; - data->params[6].i = 0; // blank - data->params[7].i = 0; // protect - data->params[8].i = 0; // alternate character set + UNIBI_SET_NUM_VAR(data->params[0], 0); // standout + UNIBI_SET_NUM_VAR(data->params[1], attrs.underline || attrs.undercurl); + UNIBI_SET_NUM_VAR(data->params[2], attrs.reverse); + UNIBI_SET_NUM_VAR(data->params[3], 0); // blink + UNIBI_SET_NUM_VAR(data->params[4], 0); // dim + UNIBI_SET_NUM_VAR(data->params[5], attrs.bold); + UNIBI_SET_NUM_VAR(data->params[6], 0); // blank + UNIBI_SET_NUM_VAR(data->params[7], 0); // protect + UNIBI_SET_NUM_VAR(data->params[8], 0); // alternate character set unibi_out(ui, unibi_set_attributes); } else if (!data->default_attr) { unibi_out(ui, unibi_exit_attribute_mode); @@ -423,26 +432,26 @@ static void update_attrs(UI *ui, HlAttrs attrs) } if (ui->rgb) { if (fg != -1) { - data->params[0].i = (fg >> 16) & 0xff; // red - data->params[1].i = (fg >> 8) & 0xff; // green - data->params[2].i = fg & 0xff; // blue - unibi_out(ui, data->unibi_ext.set_rgb_foreground); + UNIBI_SET_NUM_VAR(data->params[0], (fg >> 16) & 0xff); // red + UNIBI_SET_NUM_VAR(data->params[1], (fg >> 8) & 0xff); // green + UNIBI_SET_NUM_VAR(data->params[2], fg & 0xff); // blue + unibi_out_ext(ui, data->unibi_ext.set_rgb_foreground); } if (bg != -1) { - data->params[0].i = (bg >> 16) & 0xff; // red - data->params[1].i = (bg >> 8) & 0xff; // green - data->params[2].i = bg & 0xff; // blue - unibi_out(ui, data->unibi_ext.set_rgb_background); + UNIBI_SET_NUM_VAR(data->params[0], (bg >> 16) & 0xff); // red + UNIBI_SET_NUM_VAR(data->params[1], (bg >> 8) & 0xff); // green + UNIBI_SET_NUM_VAR(data->params[2], bg & 0xff); // blue + unibi_out_ext(ui, data->unibi_ext.set_rgb_background); } } else { if (fg != -1) { - data->params[0].i = fg; + UNIBI_SET_NUM_VAR(data->params[0], fg); unibi_out(ui, unibi_set_a_foreground); } if (bg != -1) { - data->params[0].i = bg; + UNIBI_SET_NUM_VAR(data->params[0], bg); unibi_out(ui, unibi_set_a_background); } } @@ -558,7 +567,7 @@ static void cursor_goto(UI *ui, int row, int col) unibi_out(ui, unibi_cursor_left); } } else { - data->params[0].i = n; + UNIBI_SET_NUM_VAR(data->params[0], n); unibi_out(ui, unibi_parm_left_cursor); } ugrid_goto(grid, row, col); @@ -570,7 +579,7 @@ static void cursor_goto(UI *ui, int row, int col) unibi_out(ui, unibi_cursor_right); } } else { - data->params[0].i = n; + UNIBI_SET_NUM_VAR(data->params[0], n); unibi_out(ui, unibi_parm_right_cursor); } ugrid_goto(grid, row, col); @@ -585,7 +594,7 @@ static void cursor_goto(UI *ui, int row, int col) unibi_out(ui, unibi_cursor_down); } } else { - data->params[0].i = n; + UNIBI_SET_NUM_VAR(data->params[0], n); unibi_out(ui, unibi_parm_down_cursor); } ugrid_goto(grid, row, col); @@ -597,7 +606,7 @@ static void cursor_goto(UI *ui, int row, int col) unibi_out(ui, unibi_cursor_up); } } else { - data->params[0].i = n; + UNIBI_SET_NUM_VAR(data->params[0], n); unibi_out(ui, unibi_parm_up_cursor); } ugrid_goto(grid, row, col); @@ -619,7 +628,7 @@ static void clear_region(UI *ui, int top, int bot, int left, int right) if (grid->bg == -1 && right == ui->width -1) { // Background is set to the default color and the right edge matches the // screen end, try to use terminal codes for clearing the requested area. - HlAttrs clear_attrs = EMPTY_ATTRS; + HlAttrs clear_attrs = HLATTRS_INIT; clear_attrs.foreground = grid->fg; clear_attrs.background = grid->bg; update_attrs(ui, clear_attrs); @@ -675,19 +684,19 @@ static void set_scroll_region(UI *ui) TUIData *data = ui->data; UGrid *grid = &data->grid; - data->params[0].i = grid->top; - data->params[1].i = grid->bot; + UNIBI_SET_NUM_VAR(data->params[0], grid->top); + UNIBI_SET_NUM_VAR(data->params[1], grid->bot); unibi_out(ui, unibi_change_scroll_region); if (grid->left != 0 || grid->right != ui->width - 1) { - unibi_out(ui, data->unibi_ext.enable_lr_margin); + unibi_out_ext(ui, data->unibi_ext.enable_lr_margin); if (data->can_set_lr_margin) { - data->params[0].i = grid->left; - data->params[1].i = grid->right; + UNIBI_SET_NUM_VAR(data->params[0], grid->left); + UNIBI_SET_NUM_VAR(data->params[1], grid->right); unibi_out(ui, unibi_set_lr_margin); } else { - data->params[0].i = grid->left; + UNIBI_SET_NUM_VAR(data->params[0], grid->left); unibi_out(ui, unibi_set_left_margin_parm); - data->params[0].i = grid->right; + UNIBI_SET_NUM_VAR(data->params[0], grid->right); unibi_out(ui, unibi_set_right_margin_parm); } } @@ -700,24 +709,24 @@ static void reset_scroll_region(UI *ui) UGrid *grid = &data->grid; if (0 <= data->unibi_ext.reset_scroll_region) { - unibi_out(ui, data->unibi_ext.reset_scroll_region); + unibi_out_ext(ui, data->unibi_ext.reset_scroll_region); } else { - data->params[0].i = 0; - data->params[1].i = ui->height - 1; + UNIBI_SET_NUM_VAR(data->params[0], 0); + UNIBI_SET_NUM_VAR(data->params[1], ui->height - 1); unibi_out(ui, unibi_change_scroll_region); } if (grid->left != 0 || grid->right != ui->width - 1) { if (data->can_set_lr_margin) { - data->params[0].i = 0; - data->params[1].i = ui->width - 1; + UNIBI_SET_NUM_VAR(data->params[0], 0); + UNIBI_SET_NUM_VAR(data->params[1], ui->width - 1); unibi_out(ui, unibi_set_lr_margin); } else { - data->params[0].i = 0; + UNIBI_SET_NUM_VAR(data->params[0], 0); unibi_out(ui, unibi_set_left_margin_parm); - data->params[0].i = ui->width - 1; + UNIBI_SET_NUM_VAR(data->params[0], ui->width - 1); unibi_out(ui, unibi_set_right_margin_parm); } - unibi_out(ui, data->unibi_ext.disable_lr_margin); + unibi_out_ext(ui, data->unibi_ext.disable_lr_margin); } unibi_goto(ui, grid->row, grid->col); } @@ -728,9 +737,9 @@ static void tui_resize(UI *ui, Integer width, Integer height) ugrid_resize(&data->grid, (int)width, (int)height); if (!got_winch) { // Try to resize the terminal window. - data->params[0].i = (int)height; - data->params[1].i = (int)width; - unibi_out(ui, data->unibi_ext.resize_screen); + UNIBI_SET_NUM_VAR(data->params[0], (int)height); + UNIBI_SET_NUM_VAR(data->params[1], (int)width); + unibi_out_ext(ui, data->unibi_ext.resize_screen); // DECSLPP does not reset the scroll region. if (data->scroll_region_is_full_screen) { reset_scroll_region(ui); @@ -836,7 +845,7 @@ static void tui_mouse_on(UI *ui) { TUIData *data = ui->data; if (!data->mouse_enabled) { - unibi_out(ui, data->unibi_ext.enable_mouse); + unibi_out_ext(ui, data->unibi_ext.enable_mouse); data->mouse_enabled = true; } } @@ -845,7 +854,7 @@ static void tui_mouse_off(UI *ui) { TUIData *data = ui->data; if (data->mouse_enabled) { - unibi_out(ui, data->unibi_ext.disable_mouse); + unibi_out_ext(ui, data->unibi_ext.disable_mouse); data->mouse_enabled = false; } } @@ -863,8 +872,8 @@ static void tui_set_mode(UI *ui, ModeShape mode) int attr = syn_id2attr(c.id); if (attr > 0) { attrentry_T *aep = syn_cterm_attr2entry(attr); - data->params[0].i = aep->rgb_bg_color; - unibi_out(ui, data->unibi_ext.set_cursor_color); + UNIBI_SET_NUM_VAR(data->params[0], aep->rgb_bg_color); + unibi_out_ext(ui, data->unibi_ext.set_cursor_color); } } @@ -874,8 +883,8 @@ static void tui_set_mode(UI *ui, ModeShape mode) case SHAPE_VER: shape = 5; break; default: WLOG("Unknown shape value %d", shape); break; } - data->params[0].i = shape + (int)(c.blinkon == 0); - unibi_out(ui, data->unibi_ext.set_cursor_style); + UNIBI_SET_NUM_VAR(data->params[0], shape + (int)(c.blinkon == 0)); + unibi_out_ext(ui, data->unibi_ext.set_cursor_style); } /// @param mode editor mode @@ -917,7 +926,7 @@ static void tui_scroll(UI *ui, Integer count) cursor_goto(ui, grid->top, grid->left); // also set default color attributes or some terminals can become funny if (scroll_clears_to_current_colour) { - HlAttrs clear_attrs = EMPTY_ATTRS; + HlAttrs clear_attrs = HLATTRS_INIT; clear_attrs.foreground = grid->fg; clear_attrs.background = grid->bg; update_attrs(ui, clear_attrs); @@ -927,14 +936,14 @@ static void tui_scroll(UI *ui, Integer count) if (count == 1) { unibi_out(ui, unibi_delete_line); } else { - data->params[0].i = (int)count; + UNIBI_SET_NUM_VAR(data->params[0], (int)count); unibi_out(ui, unibi_parm_delete_line); } } else { if (count == -1) { unibi_out(ui, unibi_insert_line); } else { - data->params[0].i = -(int)count; + UNIBI_SET_NUM_VAR(data->params[0], -(int)count); unibi_out(ui, unibi_parm_insert_line); } } @@ -1177,30 +1186,33 @@ end: static void unibi_goto(UI *ui, int row, int col) { TUIData *data = ui->data; - data->params[0].i = row; - data->params[1].i = col; + UNIBI_SET_NUM_VAR(data->params[0], row); + UNIBI_SET_NUM_VAR(data->params[1], col); unibi_out(ui, unibi_cursor_address); } +#define UNIBI_OUT(fn) \ + do { \ + TUIData *data = ui->data; \ + const char *str = NULL; \ + if (unibi_index >= 0) { \ + str = fn(data->ut, (unsigned)unibi_index); \ + } \ + if (str) { \ + unibi_var_t vars[26 + 26]; \ + memset(&vars, 0, sizeof(vars)); \ + unibi_format(vars, vars + 26, str, data->params, out, ui, NULL, NULL); \ + } \ + } while (0) static void unibi_out(UI *ui, int unibi_index) { - TUIData *data = ui->data; - - const char *str = NULL; - - if (unibi_index >= 0) { - if (unibi_index < unibi_string_begin_) { - str = unibi_get_ext_str(data->ut, (unsigned)unibi_index); - } else { - str = unibi_get_str(data->ut, (unsigned)unibi_index); - } - } - - if (str) { - unibi_var_t vars[26 + 26] = {{0}}; - unibi_format(vars, vars + 26, str, data->params, out, ui, NULL, NULL); - } + UNIBI_OUT(unibi_get_str); +} +static void unibi_out_ext(UI *ui, int unibi_index) +{ + UNIBI_OUT(unibi_get_ext_str); } +#undef UNIBI_OUT static void out(void *ctx, const char *str, size_t len) { @@ -1261,7 +1273,9 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, bool gnome = terminfo_is_term_family(term, "gnome") || terminfo_is_term_family(term, "vte"); bool iterm = terminfo_is_term_family(term, "iterm") - || terminfo_is_term_family(term, "iTerm.app"); + || terminfo_is_term_family(term, "iterm2") + || terminfo_is_term_family(term, "iTerm.app") + || terminfo_is_term_family(term, "iTerm2.app"); // None of the following work over SSH; see :help TERM . bool iterm_pretending_xterm = xterm && iterm_env; bool konsole_pretending_xterm = xterm && konsole; @@ -1444,7 +1458,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, // teminfo entries. See // https://github.com/gnachman/iTerm2/pull/92 for more. // xterm even has an extended version that has a vertical bar. - if (true_xterm // per xterm ctlseqs doco (since version 282) + if (!konsole && (true_xterm // per xterm ctlseqs doco (since version 282) // per MinTTY 0.4.3-1 release notes from 2009 || putty // per https://bugzilla.gnome.org/show_bug.cgi?id=720821 @@ -1459,7 +1473,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, // Allows forcing the use of DECSCUSR on linux type terminals, such as // console-terminal-emulator from the nosh toolset, which does indeed // implement the xterm extension: - || (linuxvt && (xterm_version || (vte_version > 0) || colorterm))) { + || (linuxvt && (xterm_version || (vte_version > 0) || colorterm)))) { data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", "\x1b[%p1%d q"); if (-1 == data->unibi_ext.reset_cursor_style) { @@ -1533,7 +1547,9 @@ static void augment_terminfo(TUIData *data, const char *term, bool screen = terminfo_is_term_family(term, "screen"); bool tmux = terminfo_is_term_family(term, "tmux") || !!os_getenv("TMUX"); bool iterm = terminfo_is_term_family(term, "iterm") - || terminfo_is_term_family(term, "iTerm.app"); + || terminfo_is_term_family(term, "iterm2") + || terminfo_is_term_family(term, "iTerm.app") + || terminfo_is_term_family(term, "iTerm2.app"); // None of the following work over SSH; see :help TERM . bool iterm_pretending_xterm = xterm && iterm_env; diff --git a/src/nvim/ugrid.c b/src/nvim/ugrid.c index 7a0a16687e..2b5e96ee60 100644 --- a/src/nvim/ugrid.c +++ b/src/nvim/ugrid.c @@ -16,7 +16,7 @@ void ugrid_init(UGrid *grid) { - grid->attrs = EMPTY_ATTRS; + grid->attrs = HLATTRS_INIT; grid->fg = grid->bg = -1; grid->cells = NULL; } @@ -118,7 +118,7 @@ UCell *ugrid_put(UGrid *grid, uint8_t *text, size_t size) static void clear_region(UGrid *grid, int top, int bot, int left, int right) { - HlAttrs clear_attrs = EMPTY_ATTRS; + HlAttrs clear_attrs = HLATTRS_INIT; clear_attrs.foreground = grid->fg; clear_attrs.background = grid->bg; UGRID_FOREACH_CELL(grid, top, bot, left, right, { diff --git a/src/nvim/ugrid.h b/src/nvim/ugrid.h index 268362bf1b..1cf047502d 100644 --- a/src/nvim/ugrid.h +++ b/src/nvim/ugrid.h @@ -21,8 +21,6 @@ struct ugrid { UCell **cells; }; -#define EMPTY_ATTRS ((HlAttrs){ false, false, false, false, false, -1, -1, -1 }) - #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 b85a01814d..afe7a51d43 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -71,10 +71,10 @@ static char uilog_last_event[1024] = { 0 }; uilog_seen++; \ } else { \ if (uilog_seen > 0) { \ - do_log(DEBUG_LOG_LEVEL, "ui", 0, true, \ + do_log(DEBUG_LOG_LEVEL, "UI: ", NULL, -1, true, \ "%s (+%zu times...)", uilog_last_event, uilog_seen); \ } \ - DLOG("ui: " STR(funname)); \ + do_log(DEBUG_LOG_LEVEL, "UI: ", NULL, -1, true, STR(funname)); \ uilog_seen = 0; \ xstrlcpy(uilog_last_event, STR(funname), sizeof(uilog_last_event)); \ } \ @@ -166,6 +166,90 @@ void ui_event(char *name, Array args) } } + +/// Converts an attrentry_T into an HlAttrs +/// +/// @param[in] aep data to convert +/// @param use_rgb use 'gui*' settings if true, else resorts to 'cterm*' +HlAttrs attrentry2hlattrs(const attrentry_T *aep, bool use_rgb) +{ + assert(aep); + + HlAttrs attrs = HLATTRS_INIT; + int mask = 0; + + mask = use_rgb ? aep->rgb_ae_attr : aep->cterm_ae_attr; + + attrs.bold = mask & HL_BOLD; + attrs.underline = mask & HL_UNDERLINE; + attrs.undercurl = mask & HL_UNDERCURL; + attrs.italic = mask & HL_ITALIC; + attrs.reverse = mask & (HL_INVERSE | HL_STANDOUT); + + if (use_rgb) { + if (aep->rgb_fg_color != -1) { + attrs.foreground = aep->rgb_fg_color; + } + + if (aep->rgb_bg_color != -1) { + attrs.background = aep->rgb_bg_color; + } + + if (aep->rgb_sp_color != -1) { + attrs.special = aep->rgb_sp_color; + } + } else { + if (cterm_normal_fg_color != aep->cterm_fg_color) { + attrs.foreground = aep->cterm_fg_color - 1; + } + + if (cterm_normal_bg_color != aep->cterm_bg_color) { + attrs.background = aep->cterm_bg_color - 1; + } + } + + return attrs; +} + +Dictionary hlattrs2dict(HlAttrs attrs) +{ + Dictionary hl = ARRAY_DICT_INIT; + + if (attrs.bold) { + PUT(hl, "bold", BOOLEAN_OBJ(true)); + } + + if (attrs.underline) { + PUT(hl, "underline", BOOLEAN_OBJ(true)); + } + + if (attrs.undercurl) { + PUT(hl, "undercurl", BOOLEAN_OBJ(true)); + } + + if (attrs.italic) { + PUT(hl, "italic", BOOLEAN_OBJ(true)); + } + + if (attrs.reverse) { + PUT(hl, "reverse", BOOLEAN_OBJ(true)); + } + + if (attrs.foreground != -1) { + PUT(hl, "foreground", INTEGER_OBJ(attrs.foreground)); + } + + if (attrs.background != -1) { + PUT(hl, "background", INTEGER_OBJ(attrs.background)); + } + + if (attrs.special != -1) { + PUT(hl, "special", INTEGER_OBJ(attrs.special)); + } + + return hl; +} + void ui_refresh(void) { if (!ui_active()) { @@ -405,54 +489,20 @@ void ui_flush(void) static void set_highlight_args(int attr_code) { - HlAttrs rgb_attrs = { false, false, false, false, false, -1, -1, -1 }; + HlAttrs rgb_attrs = HLATTRS_INIT; HlAttrs cterm_attrs = rgb_attrs; if (attr_code == HL_NORMAL) { goto end; } - - int rgb_mask = 0; - int cterm_mask = 0; attrentry_T *aep = syn_cterm_attr2entry(attr_code); if (!aep) { goto end; } - rgb_mask = aep->rgb_ae_attr; - cterm_mask = aep->cterm_ae_attr; - - rgb_attrs.bold = rgb_mask & HL_BOLD; - rgb_attrs.underline = rgb_mask & HL_UNDERLINE; - rgb_attrs.undercurl = rgb_mask & HL_UNDERCURL; - rgb_attrs.italic = rgb_mask & HL_ITALIC; - rgb_attrs.reverse = rgb_mask & (HL_INVERSE | HL_STANDOUT); - cterm_attrs.bold = cterm_mask & HL_BOLD; - cterm_attrs.underline = cterm_mask & HL_UNDERLINE; - cterm_attrs.undercurl = cterm_mask & HL_UNDERCURL; - cterm_attrs.italic = cterm_mask & HL_ITALIC; - cterm_attrs.reverse = cterm_mask & (HL_INVERSE | HL_STANDOUT); - - if (aep->rgb_fg_color != normal_fg) { - rgb_attrs.foreground = aep->rgb_fg_color; - } - - if (aep->rgb_bg_color != normal_bg) { - rgb_attrs.background = aep->rgb_bg_color; - } - - if (aep->rgb_sp_color != normal_sp) { - rgb_attrs.special = aep->rgb_sp_color; - } - - if (cterm_normal_fg_color != aep->cterm_fg_color) { - cterm_attrs.foreground = aep->cterm_fg_color - 1; - } - - if (cterm_normal_bg_color != aep->cterm_bg_color) { - cterm_attrs.background = aep->cterm_bg_color - 1; - } + rgb_attrs = attrentry2hlattrs(aep, true); + cterm_attrs = attrentry2hlattrs(aep, false); end: UI_CALL(highlight_set, (ui->rgb ? rgb_attrs : cterm_attrs)); diff --git a/src/nvim/ui.h b/src/nvim/ui.h index 064f77fee1..f1ea0716e6 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -21,6 +21,9 @@ typedef struct { int foreground, background, special; } HlAttrs; +#define HLATTRS_INIT \ + ((HlAttrs){ false, false, false, false, false, -1, -1, -1 }) + typedef struct ui_t UI; struct ui_t { diff --git a/src/nvim/undo.h b/src/nvim/undo.h index ab8584fbb2..802cdc5583 100644 --- a/src/nvim/undo.h +++ b/src/nvim/undo.h @@ -2,6 +2,7 @@ #define NVIM_UNDO_H #include "nvim/undo_defs.h" +#include "nvim/ex_cmds_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "undo.h.generated.h" diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h index d841210815..6c7e2bba41 100644 --- a/src/nvim/undo_defs.h +++ b/src/nvim/undo_defs.h @@ -4,9 +4,10 @@ #include <time.h> // for time_t #include "nvim/pos.h" -#include "nvim/buffer_defs.h" #include "nvim/mark_defs.h" +typedef struct u_header u_header_T; + /* Structure to store info about the Visual area. */ typedef struct { pos_T vi_start; /* start pos of last VIsual */ @@ -15,8 +16,9 @@ typedef struct { colnr_T vi_curswant; /* MAXCOL from w_curswant */ } visualinfo_T; +#include "nvim/buffer_defs.h" + typedef struct u_entry u_entry_T; -typedef struct u_header u_header_T; struct u_entry { u_entry_T *ue_next; /* pointer to next entry in list */ linenr_T ue_top; /* number of line above undo block */ diff --git a/src/nvim/version.c b/src/nvim/version.c index f4984864f3..30ebbb22bc 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -77,6 +77,157 @@ static char *features[] = { // clang-format off static const int included_patches[] = { + // 1026, + 1025, + 1024, + // 1023, + // 1022, + // 1021, + // 1020, + // 1019, + // 1018, + // 1017, + // 1016, + // 1015, + // 1014, + // 1013, + // 1012, + // 1011, + // 1010, + // 1009, + // 1008, + // 1007, + // 1006, + // 1005, + // 1004, + // 1003, + // 1002, + // 1001, + // 1000, + // 999, + // 998, + // 997, + // 996, + // 995, + // 994, + // 993, + // 992, + // 991, + // 990, + // 989, + // 988, + // 987, + // 986, + // 985, + // 984, + // 983, + // 982, + // 981, + // 980, + // 979, + // 978, + // 977, + // 976, + // 975, + // 974, + // 973, + // 972, + // 971, + // 970, + // 969, + // 968, + // 967, + // 966, + // 965, + // 964, + // 963, + // 962, + // 961, + // 960, + // 959, + // 958, + // 957, + // 956, + // 955, + // 954, + // 953, + // 952, + // 951, + // 950, + // 949, + // 948, + // 947, + // 946, + // 945, + // 944, + // 943, + // 942, + // 941, + // 940, + // 939, + // 938, + // 937, + // 936, + // 935, + // 934, + // 933, + // 932, + // 931, + // 930, + // 929, + // 928, + // 927, + // 926, + // 925, + // 924, + // 923, + // 922, + // 921, + // 920, + // 919, + // 918, + // 917, + // 916, + // 915, + // 914, + // 913, + // 912, + // 911, + // 910, + // 909, + // 908, + // 907, + // 906, + // 905, + // 904, + // 903, + // 902, + // 901, + // 900, + // 899, + // 898, + // 897, + // 896, + // 895, + // 894, + // 893, + // 892, + // 891, + // 890, + // 889, + // 888, + // 887, + // 886, + // 885, + // 884, + // 883, + // 882, + // 881, + // 880, + // 879, + // 878, + // 877, + // 876, // 875, // 874, // 873, @@ -272,9 +423,9 @@ static const int included_patches[] = { // 683, // 682, // 681, - // 680, - // 679, - // 678, + 680, + 679, + 678, // 677, // 676, // 675, @@ -340,7 +491,7 @@ static const int included_patches[] = { // 615, 614, // 613, - // 612, + 612, // 611, // 610, // 609, @@ -515,7 +666,7 @@ static const int included_patches[] = { // 440, // 439, // 438, - // 437, + 437, // 436, // 435, // 434, @@ -619,16 +770,16 @@ static const int included_patches[] = { // 336, // 335, // 334, - // 333, + 333, // 332, 331, - // 330, + 330, // 329, - // 328, - // 327, - // 326, - // 325, - // 324, + 328, + 327, + 326, + 325, + 324, // 323, 322, // 321, @@ -644,24 +795,24 @@ static const int included_patches[] = { 311, // 310, // 309, - // 308, + 308, 307, // 306, - // 305, + 305, // 304, // 303, - // 302, + // 302, NA // 301, - // 300, + 300, // 299, // 298, 297, // 296, // 295, - // 294, + 294, // 293, // 292, - // 291, + 291, 290, // 289, // 288 NA @@ -670,7 +821,7 @@ static const int included_patches[] = { // 285 NA // 284 NA // 283, - // 282, + 282, // 281 NA 280, // 279 NA @@ -694,18 +845,18 @@ static const int included_patches[] = { // 261, // 260 NA 259, - // 258, + 258, // 257 NA // 256, // 255, // 254, - // 253, + 253, // 252, // 251, 250, // 249 NA // 248, - // 247, + 247, // 246 NA // 245, // 244, @@ -743,7 +894,7 @@ static const int included_patches[] = { // 212, // 211 NA // 210, - // 209, + 209, 208, // 207, // 206, @@ -764,21 +915,21 @@ static const int included_patches[] = { // 191 NA 190, // 189, - // 188, + 188, // 187 NA - // 186, + 186, // 185, // 184, - // 183, - // 182, - // 181, + // 183 NA + 182, + 181, // 180, 179, 178, 177, 176, // 175, - // 174, + 174, // 173 NA 172, // 171, @@ -788,31 +939,31 @@ static const int included_patches[] = { 167, // 166, 165, - // 164, + 164, // 163 NA // 162 NA // 161 NA // 160, 159, 158, - // 157, + 157, 156, - // 155, + 155, // 154, // 153, // 152 NA // 151, 150, 149, - // 148, + 148, 147, 146, // 145 NA // 144 NA 143, - // 142, + 142, // 141, - // 140, + 140, // 139 NA // 138 NA 137, @@ -820,38 +971,38 @@ static const int included_patches[] = { 135, 134, 133, - // 132, - // 131, + 132, + 131, // 130 NA // 129 NA 128, 127, 126, - // 125, + 125, 124, // 123 NA // 122 NA 121, // 120 NA 119, - // 118, + 118, // 117 NA 116, // 115 NA // 114 NA // 113 NA - // 112, + 112, 111, 110, // 109 NA // 108 NA // 107 NA - // 106, + 106, // 105 NA - // 104, + 104, // 103 NA - // 102, - // 101, + 102, + 101, 100, 99, // 98 NA @@ -860,8 +1011,8 @@ static const int included_patches[] = { // 95 NA // 94 NA // 93 NA - // 92, - // 91, + 92, + 91, 90, // 89 NA 88, @@ -908,7 +1059,7 @@ static const int included_patches[] = { 47, 46, // 45 NA - // 44, + 44, 43, 42, 41, @@ -1089,13 +1240,7 @@ static void list_features(void) msg_putchar('\n'); } } else { - while (msg_col % width) { - int old_msg_col = msg_col; - msg_putchar(' '); - if (old_msg_col == msg_col) { - break; // XXX: Avoid infinite loop. - } - } + msg_putchar(' '); } } else { if (msg_col > 0) { @@ -1103,7 +1248,7 @@ static void list_features(void) } } } - MSG_PUTS("For differences from Vim, see :help vim-differences\n\n"); + MSG_PUTS("See \":help feature-compile\"\n\n"); } void list_version(void) @@ -1144,7 +1289,7 @@ void list_version(void) } #endif // ifdef HAVE_PATHDEF - version_msg(_("\n\nOptional features included (+) or not (-): ")); + version_msg(_("\n\nFeatures: ")); list_features(); @@ -1216,12 +1361,11 @@ void intro_message(int colon) static char *(lines[]) = { N_(NVIM_VERSION_LONG), "", - N_("by al."), N_("Nvim is open source and freely distributable"), N_("https://neovim.io/community"), "", N_("type :help nvim<Enter> if you are new! "), - N_("type :CheckHealth<Enter> to optimize Nvim"), + N_("type :checkhealth<Enter> to optimize Nvim"), N_("type :q<Enter> to exit "), N_("type :help<Enter> for help "), "", diff --git a/src/nvim/window.c b/src/nvim/window.c index faf5bceb56..c2d0a9b3b1 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1878,6 +1878,7 @@ int win_close(win_T *win, int free_buf) int dir; int help_window = FALSE; tabpage_T *prev_curtab = curtab; + frame_T *win_frame = win->w_frame->fr_parent; if (last_window()) { EMSG(_("E444: Cannot close last window")); @@ -2027,7 +2028,9 @@ int win_close(win_T *win, int free_buf) check_cursor(); } if (p_ea && (*p_ead == 'b' || *p_ead == dir)) { - win_equal(curwin, true, dir); + // If the frame of the closed window contains the new current window, + // only resize that frame. Otherwise resize all windows. + win_equal(curwin, curwin->w_frame->fr_parent == win_frame, dir); } else { win_comp_pos(); } @@ -5512,11 +5515,14 @@ void restore_buffer(bufref_T *save_curbuf) } -// Add match to the match list of window 'wp'. The pattern 'pat' will be -// highlighted with the group 'grp' with priority 'prio'. -// Optionally, a desired ID 'id' can be specified (greater than or equal to 1). -// If no particular ID is desired, -1 must be specified for 'id'. -// Return ID of added match, -1 on failure. +/// Add match to the match list of window 'wp'. The pattern 'pat' will be +/// highlighted with the group 'grp' with priority 'prio'. +/// Optionally, a desired ID 'id' can be specified (greater than or equal to 1). +/// +/// @param[in] id a desired ID 'id' can be specified +/// (greater than or equal to 1). -1 must be specified if no +/// particular ID is desired +/// @return ID of added match, -1 on failure. int match_add(win_T *wp, const char *const grp, const char *const pat, int prio, int id, list_T *pos_list, const char *const conceal_char) @@ -5694,10 +5700,9 @@ fail: return -1; } -/* - * Delete match with ID 'id' in the match list of window 'wp'. - * Print error messages if 'perr' is TRUE. - */ + +/// Delete match with ID 'id' in the match list of window 'wp'. +/// Print error messages if 'perr' is TRUE. int match_delete(win_T *wp, int id, int perr) { matchitem_T *cur = wp->w_match_head; diff --git a/test/.luacheckrc b/test/.luacheckrc index 034b4f10df..abfa881754 100644 --- a/test/.luacheckrc +++ b/test/.luacheckrc @@ -17,4 +17,4 @@ ignore = { } -- Ignore whitespace issues in converted Vim legacy tests. -files["functional/legacy"] = {ignore = { "611", "612", "613", "621" }} +--files["functional/legacy"] = {ignore = { "611", "612", "613", "621" }} diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua new file mode 100644 index 0000000000..2297a0760f --- /dev/null +++ b/test/functional/api/highlight_spec.lua @@ -0,0 +1,103 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear, nvim = helpers.clear, helpers.nvim +local Screen = require('test.functional.ui.screen') +local eq, eval = helpers.eq, helpers.eval +local command = helpers.command +local meths = helpers.meths + +describe('highlight api',function() + local expected_rgb = { + background = Screen.colors.Yellow, + foreground = Screen.colors.Red, + special = Screen.colors.Blue, + bold = true, + } + local expected_cterm = { + background = 10, + underline = true, + } + local expected_rgb2 = { + background = Screen.colors.Yellow, + foreground = Screen.colors.Red, + special = Screen.colors.Blue, + bold = true, + italic = true, + reverse = true, + undercurl = true, + underline = true, + } + + before_each(function() + clear() + command("hi NewHighlight cterm=underline ctermbg=green guifg=red guibg=yellow guisp=blue gui=bold") + end) + + it("nvim_get_hl_by_id", function() + local hl_id = eval("hlID('NewHighlight')") + eq(expected_cterm, nvim("get_hl_by_id", hl_id, false)) + + hl_id = eval("hlID('NewHighlight')") + -- Test valid id. + eq(expected_rgb, nvim("get_hl_by_id", hl_id, true)) + + -- Test invalid id. + local err, emsg = pcall(meths.get_hl_by_id, 30000, false) + eq(false, err) + eq('Invalid highlight id: 30000', string.match(emsg, 'Invalid.*')) + + -- Test all highlight properties. + command('hi NewHighlight gui=underline,bold,undercurl,italic,reverse') + eq(expected_rgb2, nvim("get_hl_by_id", hl_id, true)) + + -- Test nil argument. + err, emsg = pcall(meths.get_hl_by_id, { nil }, false) + eq(false, err) + eq('Wrong type for argument 1, expecting Integer', + string.match(emsg, 'Wrong.*')) + + -- Test 0 argument. + err, emsg = pcall(meths.get_hl_by_id, 0, false) + eq(false, err) + eq('Invalid highlight id: 0', + string.match(emsg, 'Invalid.*')) + + -- Test -1 argument. + err, emsg = pcall(meths.get_hl_by_id, -1, false) + eq(false, err) + eq('Invalid highlight id: -1', + string.match(emsg, 'Invalid.*')) + end) + + it("nvim_get_hl_by_name", function() + local expected_normal = { background = Screen.colors.Yellow, + foreground = Screen.colors.Red } + + -- Test `Normal` default values. + eq({}, nvim("get_hl_by_name", 'Normal', true)) + + eq(expected_cterm, nvim("get_hl_by_name", 'NewHighlight', false)) + eq(expected_rgb, nvim("get_hl_by_name", 'NewHighlight', true)) + + -- Test `Normal` modified values. + command('hi Normal guifg=red guibg=yellow') + eq(expected_normal, nvim("get_hl_by_name", 'Normal', true)) + + -- Test invalid name. + local err, emsg = pcall(meths.get_hl_by_name , 'unknown_highlight', false) + eq(false, err) + eq('Invalid highlight name: unknown_highlight', + string.match(emsg, 'Invalid.*')) + + -- Test nil argument. + err, emsg = pcall(meths.get_hl_by_name , { nil }, false) + eq(false, err) + eq('Wrong type for argument 1, expecting String', + string.match(emsg, 'Wrong.*')) + + -- Test empty string argument. + err, emsg = pcall(meths.get_hl_by_name , '', false) + eq(false, err) + eq('Invalid highlight name: ', + string.match(emsg, 'Invalid.*')) + end) +end) diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua index 6a32f979ea..9f245d913b 100644 --- a/test/functional/api/server_requests_spec.lua +++ b/test/functional/api/server_requests_spec.lua @@ -20,6 +20,22 @@ describe('server -> client', function() cid = nvim('get_api_info')[1] end) + it('handles unexpected closed stream while preparing RPC response', function() + source([[ + let g:_nvim_args = [v:progpath, '--embed', '-n', '-u', 'NONE', '-i', 'NONE', ] + let ch1 = jobstart(g:_nvim_args, {'rpc': v:true}) + let child1_ch = rpcrequest(ch1, "nvim_get_api_info")[0] + call rpcnotify(ch1, 'nvim_eval', 'rpcrequest('.child1_ch.', "nvim_get_api_info")') + + let ch2 = jobstart(g:_nvim_args, {'rpc': v:true}) + let child2_ch = rpcrequest(ch2, "nvim_get_api_info")[0] + call rpcnotify(ch2, 'nvim_eval', 'rpcrequest('.child2_ch.', "nvim_get_api_info")') + + call jobstop(ch1) + ]]) + eq(2, eval("1+1")) -- Still alive? + end) + describe('simple call', function() it('works', function() local function on_setup() @@ -141,7 +157,7 @@ describe('server -> client', function() end) end) - describe('when the client is a recursive vim instance', function() + describe('recursive (child) nvim client', function() if os.getenv("TRAVIS") and helpers.os_name() == "osx" then -- XXX: Hangs Travis macOS since e9061117a5b8f195c3f26a5cb94e18ddd7752d86. pending("[Hangs on Travis macOS. #5002]", function() end) @@ -155,7 +171,7 @@ describe('server -> client', function() after_each(function() command('call rpcstop(vim)') end) - it('can send/recieve notifications and make requests', function() + it('can send/receive notifications and make requests', function() nvim('command', "call rpcnotify(vim, 'vim_set_current_line', 'SOME TEXT')") -- Wait for the notification to complete. @@ -188,7 +204,7 @@ describe('server -> client', function() end) end) - describe('when using jobstart', function() + describe('jobstart()', function() local jobid before_each(function() local channel = nvim('get_api_info')[1] @@ -227,7 +243,7 @@ describe('server -> client', function() end) end) - describe('when connecting to another nvim instance', function() + describe('connecting to another (peer) nvim', function() local function connect_test(server, mode, address) local serverpid = funcs.getpid() local client = spawn(nvim_argv) @@ -256,7 +272,7 @@ describe('server -> client', function() client:close() end - it('over a named pipe', function() + it('via named pipe', function() local server = spawn(nvim_argv) set_session(server) local address = funcs.serverlist()[1] @@ -265,7 +281,7 @@ describe('server -> client', function() connect_test(server, 'pipe', address) end) - it('to an ip adress', function() + it('via ip address', function() local server = spawn(nvim_argv) set_session(server) local address = funcs.serverstart("127.0.0.1:") @@ -273,7 +289,7 @@ describe('server -> client', function() connect_test(server, 'tcp', address) end) - it('to a hostname', function() + it('via hostname', function() local server = spawn(nvim_argv) set_session(server) local address = funcs.serverstart("localhost:") diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index e59b5d712d..b849304d45 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -329,24 +329,92 @@ describe('api', function() } eq({ { {mode='n', blocking=false}, 13, - {mode='n', blocking=false}, -- TODO: should be blocked=true + {mode='n', blocking=false}, -- TODO: should be blocked=true ? 1 }, NIL}, meths.call_atomic(req)) eq({mode='r', blocking=true}, nvim("get_mode")) end) - -- TODO: bug #6166 it("during insert-mode map-pending, returns blocking=true #6166", function() command("inoremap xx foo") nvim("input", "ix") eq({mode='i', blocking=true}, nvim("get_mode")) end) - -- TODO: bug #6166 it("during normal-mode gU, returns blocking=false #6166", function() nvim("input", "gu") eq({mode='no', blocking=false}, nvim("get_mode")) end) end) + describe('RPC (K_EVENT) #6166', function() + it('does not complete ("interrupt") normal-mode operator-pending', function() + helpers.insert([[ + FIRST LINE + SECOND LINE]]) + nvim('input', 'gg') + nvim('input', 'gu') + -- Make any RPC request (can be non-async: op-pending does not block). + nvim('get_current_buf') + -- Buffer should not change. + helpers.expect([[ + FIRST LINE + SECOND LINE]]) + -- Now send input to complete the operator. + nvim('input', 'j') + helpers.expect([[ + first line + second line]]) + end) + + it('does not complete ("interrupt") `d` #3732', function() + local screen = Screen.new(20, 4) + screen:attach() + command('set listchars=eol:$') + command('set list') + feed('ia<cr>b<cr>c<cr><Esc>kkk') + feed('d') + -- Make any RPC request (can be non-async: op-pending does not block). + nvim('get_current_buf') + screen:expect([[ + ^a$ | + b$ | + c$ | + | + ]]) + end) + + it('does not complete ("interrupt") normal-mode map-pending', function() + command("nnoremap dd :let g:foo='it worked...'<CR>") + helpers.insert([[ + FIRST LINE + SECOND LINE]]) + nvim('input', 'gg') + nvim('input', 'd') + -- Make any RPC request (must be async, because map-pending blocks). + nvim('get_api_info') + -- Send input to complete the mapping. + nvim('input', 'd') + helpers.expect([[ + FIRST LINE + SECOND LINE]]) + eq('it worked...', helpers.eval('g:foo')) + end) + it('does not complete ("interrupt") insert-mode map-pending', function() + command('inoremap xx foo') + command('set timeoutlen=9999') + helpers.insert([[ + FIRST LINE + SECOND LINE]]) + nvim('input', 'ix') + -- Make any RPC request (must be async, because map-pending blocks). + nvim('get_api_info') + -- Send input to complete the mapping. + nvim('input', 'x') + helpers.expect([[ + FIRST LINE + SECOND LINfooE]]) + end) + end) + describe('nvim_replace_termcodes', function() it('escapes K_SPECIAL as K_SPECIAL KS_SPECIAL KE_FILLER', function() eq('\128\254X', helpers.nvim('replace_termcodes', '\128', true, true, true)) diff --git a/test/functional/autocmd/termclose_spec.lua b/test/functional/autocmd/termclose_spec.lua index 8cc49c0d4c..c6c30494dd 100644 --- a/test/functional/autocmd/termclose_spec.lua +++ b/test/functional/autocmd/termclose_spec.lua @@ -57,7 +57,9 @@ describe('TermClose event', function() 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 - eq(4, duration) + -- nvim starts sending kill after 2*KILL_TIMEOUT_MS + helpers.ok(4 <= duration) + helpers.ok(duration <= 7) -- <= 4 + delta because of 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 eb2eeee0da..a3ea3b568f 100644 --- a/test/functional/clipboard/clipboard_provider_spec.lua +++ b/test/functional/clipboard/clipboard_provider_spec.lua @@ -4,6 +4,8 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert local feed_command, expect, eq, eval = helpers.feed_command, helpers.expect, helpers.eq, helpers.eval +local command = helpers.command +local meths = helpers.meths local function basic_register_test(noblock) insert("some words") @@ -80,15 +82,73 @@ local function basic_register_test(noblock) expect("two and three and one") end -describe('the unnamed register', function() +describe('clipboard', function() before_each(clear) - it('works without provider', function() + + it('unnamed register works without provider', function() eq('"', eval('v:register')) basic_register_test() end) + + 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([[ + ^ | + ~ | + ~ | + clipboard: No provider. Try ":checkhealth" or ":h clipboard". | + ]], nil, {{bold = true, foreground = Screen.colors.Blue}}) + end) + + 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([[ + ~ | + clipboard: No provider. Try ":checkhealth" or ":h clipboard". | + E492: Not an editor command: bogus_cmd | redir END | + Press ENTER or type command to continue^ | + ]], nil, {{bold = true, foreground = Screen.colors.Blue}}) + end) + + it('invalid g:clipboard shows hint if :redir is not active', function() + command("let g:clipboard = 'bogus'") + 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"') + screen:expect([[ + ^ | + ~ | + ~ | + clipboard: No provider. Try ":checkhealth" or ":h clipboard". | + ]], nil, {{bold = true, foreground = Screen.colors.Blue}}) + end) + + it('valid g:clipboard', function() + -- provider#clipboard#Executable() only checks the structure. + meths.set_var('clipboard', { + ['name'] = 'clippy!', + ['copy'] = { ['+'] = 'any command', ['*'] = 'some other' }, + ['paste'] = { ['+'] = 'any command', ['*'] = 'some other' }, + }) + eq('clippy!', eval('provider#clipboard#Executable()')) + eq('', eval('provider#clipboard#Error()')) + end) end) -describe('clipboard usage', function() +describe('clipboard', function() local function reset(...) clear('--cmd', 'let &rtp = "test/functional/fixtures,".&rtp', ...) end @@ -98,7 +158,36 @@ describe('clipboard usage', function() feed_command('call getreg("*")') -- force load of provider end) - it('has independent "* and unnamed registers per default', function() + it('`:redir @+>` invokes clipboard once-per-message', function() + eq(0, eval("g:clip_called_set")) + feed_command('redir @+> | :silent echo system("cat CONTRIBUTING.md") | redir END') + -- Assuming CONTRIBUTING.md has >100 lines. + assert(eval("g:clip_called_set") > 100) + end) + + it('`:redir @">` does NOT invoke clipboard', function() + -- :redir to a non-clipboard register, with `:set clipboard=unnamed` does + -- NOT propagate to the clipboard. This is consistent with Vim. + command("set clipboard=unnamedplus") + eq(0, eval("g:clip_called_set")) + feed_command('redir @"> | :silent echo system("cat CONTRIBUTING.md") | redir END') + eq(0, eval("g:clip_called_set")) + end) + + it('`:redir @+>|bogus_cmd|redir END` must not recurse #7184', + function() + local screen = Screen.new(72, 4) + screen:attach() + feed_command('redir @+> | bogus_cmd | redir END') + screen:expect([[ + ^ | + ~ | + ~ | + E492: Not an editor command: bogus_cmd | redir END | + ]], nil, {{bold = true, foreground = Screen.colors.Blue}}) + end) + + it('has independent "* and unnamed registers by default', function() insert("some words") feed('^"*dwdw"*P') expect('some ') @@ -139,7 +228,7 @@ describe('clipboard usage', function() eq({'some\ntext', '\nvery binary\n'}, eval("getreg('*', 1, 1)")) end) - it('support autodectection of regtype', function() + it('autodetects regtype', function() feed_command("let g:test_clip['*'] = ['linewise stuff','']") feed_command("let g:test_clip['+'] = ['charwise','stuff']") eq("V", eval("getregtype('*')")) @@ -169,7 +258,7 @@ describe('clipboard usage', function() eq({{' much', 'ktext', ''}, 'b'}, eval("g:test_clip['+']")) end) - it('supports setreg', function() + it('supports setreg()', function() feed_command('call setreg("*", "setted\\ntext", "c")') feed_command('call setreg("+", "explicitly\\nlines", "l")') feed('"+P"*p') @@ -187,7 +276,7 @@ describe('clipboard usage', function() ]]) end) - it('supports let @+ (issue #1427)', function() + it('supports :let @+ (issue #1427)', function() feed_command("let @+ = 'some'") feed_command("let @* = ' other stuff'") eq({{'some'}, 'v'}, eval("g:test_clip['+']")) @@ -303,9 +392,16 @@ describe('clipboard usage', function() eq('---', eval('getreg("*")')) end) + it('works in the cmdline window', function() + feed('q:itext<esc>yy') + eq({{'text', ''}, 'V'}, eval("g:test_clip['*']")) + command("let g:test_clip['*'] = [['star'], 'c']") + feed('p') + eq('textstar', meths.get_current_line()) + end) end) - describe('with clipboard=unnamedplus', function() + describe('clipboard=unnamedplus', function() before_each(function() feed_command('set clipboard=unnamedplus') end) @@ -349,6 +445,7 @@ describe('clipboard usage', function() really unnamed the plus]]) end) + it('is updated on global changes', function() insert([[ text diff --git a/test/functional/core/exit_spec.lua b/test/functional/core/exit_spec.lua index 3fb39f3e78..188a6a2c11 100644 --- a/test/functional/core/exit_spec.lua +++ b/test/functional/core/exit_spec.lua @@ -2,8 +2,12 @@ local helpers = require('test.functional.helpers')(after_each) local command = helpers.command local eval = helpers.eval -local eq, neq = helpers.eq, helpers.neq +local eq = helpers.eq local run = helpers.run +local funcs = helpers.funcs +local nvim_prog = helpers.nvim_prog +local redir_exec = helpers.redir_exec +local wait = helpers.wait describe('v:exiting', function() local cid @@ -29,18 +33,53 @@ describe('v:exiting', function() end run(on_request, nil, on_setup) end) +end) - it('is non-zero after :cquit', function() - local function on_setup() - command('autocmd VimLeavePre * call rpcrequest('..cid..', "")') - command('autocmd VimLeave * call rpcrequest('..cid..', "")') - command('cquit') - end - local function on_request() - neq(0, eval('v:exiting')) - return '' +describe(':cquit', function() + local function test_cq(cmdline, exit_code, redir_msg) + if redir_msg then + eq('\n' .. redir_msg, redir_exec(cmdline)) + wait() + eq(2, eval("1+1")) -- Still alive? + else + funcs.system({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless', '--cmd', cmdline}) + eq(exit_code, eval('v:shell_error')) end - run(on_request, nil, on_setup) + end + + before_each(function() + helpers.clear() + end) + + it('exits with non-zero after :cquit', function() + test_cq('cquit', 1, nil) end) + it('exits with non-zero after :cquit 123', function() + test_cq('cquit 123', 123, nil) + end) + + it('exits with non-zero after :123 cquit', function() + test_cq('123 cquit', 123, nil) + end) + + it('exits with 0 after :cquit 0', function() + test_cq('cquit 0', 0, nil) + end) + + it('exits with 0 after :0 cquit', function() + test_cq('0 cquit', 0, nil) + end) + + it('exits with redir msg for multiple exit codes after :cquit 1 2', function() + test_cq('cquit 1 2', nil, 'E488: Trailing characters: cquit 1 2') + end) + + it('exits with redir msg for non-number exit code after :cquit X', function() + test_cq('cquit X', nil, 'E488: Trailing characters: cquit X') + end) + + it('exits with redir msg for negative exit code after :cquit -1', function() + test_cq('cquit -1', nil, 'E488: Trailing characters: cquit -1') + end) end) diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 54e56f7f41..1b8a5b1b95 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -677,12 +677,12 @@ describe("pty process teardown", function() -- Exiting should terminate all descendants (PTY, its children, ...). screen:expect([[ - | + ^ | [Process exited 0] | | | | - -- TERMINAL -- | + | ]]) end) end) diff --git a/test/functional/core/path_spec.lua b/test/functional/core/path_spec.lua new file mode 100644 index 0000000000..669bc99136 --- /dev/null +++ b/test/functional/core/path_spec.lua @@ -0,0 +1,56 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local eq = helpers.eq +local eval = helpers.eval +local command = helpers.command +local iswin = helpers.iswin + +describe('path collapse', function() + local targetdir + local expected_path + + local function join_path(...) + local pathsep = (iswin() and '\\' or '/') + return table.concat({...}, pathsep) + end + + before_each(function() + targetdir = join_path('test', 'functional', 'fixtures') + clear() + command('edit '..join_path(targetdir, 'tty-test.c')) + expected_path = eval('expand("%:p")') + end) + + it('with /./ segment #7117', function() + command('edit '..join_path(targetdir, '.', 'tty-test.c')) + eq(expected_path, eval('expand("%:p")')) + end) + + it('with ./ prefix #7117', function() + command('edit '..join_path('.', targetdir, 'tty-test.c')) + eq(expected_path, eval('expand("%:p")')) + end) + + it('with ./ prefix, after directory change #7117', function() + command('edit '..join_path('.', targetdir, 'tty-test.c')) + command('cd test') + eq(expected_path, eval('expand("%:p")')) + end) + + it('with /../ segment #7117', function() + command('edit '..join_path(targetdir, '..', 'fixtures', 'tty-test.c')) + eq(expected_path, eval('expand("%:p")')) + end) + + it('with ../ and different starting directory #7117', function() + command('cd test') + command('edit '..join_path('..', targetdir, 'tty-test.c')) + eq(expected_path, eval('expand("%:p")')) + end) + + it('with ./../ and different starting directory #7117', function() + command('cd test') + command('edit '..join_path('.', '..', targetdir, 'tty-test.c')) + eq(expected_path, eval('expand("%:p")')) + end) +end) diff --git a/test/functional/eval/input_spec.lua b/test/functional/eval/input_spec.lua index 74ad32bc6c..1e6b107c60 100644 --- a/test/functional/eval/input_spec.lua +++ b/test/functional/eval/input_spec.lua @@ -23,10 +23,41 @@ before_each(function() function CustomListCompl(...) return ['FOO'] endfunction + + highlight RBP1 guibg=Red + highlight RBP2 guibg=Yellow + highlight RBP3 guibg=Green + highlight RBP4 guibg=Blue + let g:NUM_LVLS = 4 + function Redraw() + redraw! + return '' + endfunction + cnoremap <expr> {REDRAW} Redraw() + function RainBowParens(cmdline) + let ret = [] + let i = 0 + let lvl = 0 + while i < len(a:cmdline) + if a:cmdline[i] is# '(' + call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)]) + let lvl += 1 + elseif a:cmdline[i] is# ')' + let lvl -= 1 + call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)]) + endif + let i += 1 + endwhile + return ret + endfunction ]]) screen:set_default_attr_ids({ EOB={bold = true, foreground = Screen.colors.Blue1}, T={foreground=Screen.colors.Red}, + RBP1={background=Screen.colors.Red}, + RBP2={background=Screen.colors.Yellow}, + RBP3={background=Screen.colors.Green}, + RBP4={background=Screen.colors.Blue}, }) end) @@ -196,6 +227,37 @@ describe('input()', function() eq('Vim(call):E118: Too many arguments for function: input', exc_exec('call input("prompt> ", "default", "file", "extra")')) end) + it('supports highlighting', function() + command('nnoremap <expr> X input({"highlight": "RainBowParens"})[-1]') + feed([[X]]) + feed('(())') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {RBP1:(}{RBP2:()}{RBP1:)}^ | + ]]) + end) + it('is not hidden by :silent', function() + feed([[:silent call input('Foo: ')<CR>]]) + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + Foo: ^ | + | + ]]) + feed('Bar') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + Foo: Bar^ | + | + ]]) + feed('<CR>') + end) end) describe('inputdialog()', function() it('works with multiline prompts', function() @@ -363,4 +425,16 @@ describe('inputdialog()', function() eq('Vim(call):E118: Too many arguments for function: inputdialog', exc_exec('call inputdialog("prompt> ", "default", "file", "extra")')) end) + it('supports highlighting', function() + command('nnoremap <expr> X inputdialog({"highlight": "RainBowParens"})[-1]') + feed([[X]]) + feed('(())') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {RBP1:(}{RBP2:()}{RBP1:)}^ | + ]]) + end) end) diff --git a/test/functional/ex_cmds/menu_spec.lua b/test/functional/ex_cmds/menu_spec.lua index 55da8da8dc..2c0535acda 100644 --- a/test/functional/ex_cmds/menu_spec.lua +++ b/test/functional/ex_cmds/menu_spec.lua @@ -107,7 +107,7 @@ describe('menu_get', function() sid = 1, noremap = 1, enabled = 1, - rhs = "inormal\27", + rhs = "inormal<Esc>", silent = 0 }, v = { @@ -242,7 +242,7 @@ describe('menu_get', function() sid = 1, noremap = 1, enabled = 1, - rhs = "\18\"", + rhs = "<C-R>\"", silent = 0 }, n = { @@ -379,5 +379,251 @@ describe('menu_get', function() } eq(expected, m) end) +end) + +describe('menu_get', function() + + before_each(function() + clear() + end) + + it('returns <keycode> representation of special keys', function() + command('nnoremenu &Test.Test inormal<ESC>') + command('inoremenu &Test.Test2 <Tab><Esc>') + command('vnoremenu &Test.Test3 yA<C-R>0<Tab>xyz<Esc>') + command('inoremenu &Test.Test4 <c-r>*') + command('inoremenu &Test.Test5 <c-R>+') + command('nnoremenu &Test.Test6 <Nop>') + command('nnoremenu &Test.Test7 <NOP>') + command('nnoremenu &Test.Test8 <NoP>') + command('nnoremenu &Test.Test9 ""') + + local m = funcs.menu_get(""); + local expected = { + { + shortcut = "T", + hidden = 0, + submenus = { + { + priority = 500, + mappings = { + n = { + sid = 1, + noremap = 1, + enabled = 1, + rhs = "inormal<Esc>", + silent = 0 + } + }, + name = "Test", + hidden = 0 + }, + { + priority = 500, + mappings = { + i = { + sid = 1, + noremap = 1, + enabled = 1, + rhs = "<Tab><Esc>", + silent = 0 + } + }, + name = "Test2", + hidden = 0 + }, + { + priority = 500, + mappings = { + s = { + sid = 1, + noremap = 1, + enabled = 1, + rhs = "yA<C-R>0<Tab>xyz<Esc>", + silent = 0 + }, + v = { + sid = 1, + noremap = 1, + enabled = 1, + rhs = "yA<C-R>0<Tab>xyz<Esc>", + silent = 0 + } + }, + name = "Test3", + hidden = 0 + }, + { + priority = 500, + mappings = { + i = { + sid = 1, + noremap = 1, + enabled = 1, + rhs = "<C-R>*", + silent = 0 + } + }, + name = "Test4", + hidden = 0 + }, + { + priority = 500, + mappings = { + i = { + sid = 1, + noremap = 1, + enabled = 1, + rhs = "<C-R>+", + silent = 0 + } + }, + name = "Test5", + hidden = 0 + }, + { + priority = 500, + mappings = { + n = { + sid = 1, + noremap = 1, + enabled = 1, + rhs = "", + silent = 0 + } + }, + name = "Test6", + hidden = 0 + }, + { + priority = 500, + mappings = { + n = { + sid = 1, + noremap = 1, + enabled = 1, + rhs = "", + silent = 0 + } + }, + name = "Test7", + hidden = 0 + }, + { + priority = 500, + mappings = { + n = { + sid = 1, + noremap = 1, + enabled = 1, + rhs = "", + silent = 0 + } + }, + name = "Test8", + hidden = 0 + }, + { + priority = 500, + mappings = { + n = { + sid = 1, + noremap = 1, + enabled = 1, + rhs = "\"\"", + silent = 0 + } + }, + name = "Test9", + hidden = 0 + } + }, + priority = 500, + name = "Test" + } + } + + eq(m, expected) + end) + + it('works with right-aligned text and spaces', function() + command('nnoremenu &Test<Tab>Y.Test<Tab>X\\ x inormal<Alt-j>') + command('nnoremenu &Test\\ 1.Test\\ 2 Wargl') + command('nnoremenu &Test4.Test<Tab>3 i space<Esc>') + + local m = funcs.menu_get(""); + local expected = { + { + shortcut = "T", + hidden = 0, + actext = "Y", + submenus = { + { + mappings = { + n = { + sid = 1, + noremap = 1, + enabled = 1, + rhs = "inormal<Alt-j>", + silent = 0 + } + }, + hidden = 0, + actext = "X x", + priority = 500, + name = "Test" + } + }, + priority = 500, + name = "Test" + }, + { + shortcut = "T", + hidden = 0, + submenus = { + { + priority = 500, + mappings = { + n = { + sid = 1, + noremap = 1, + enabled = 1, + rhs = "Wargl", + silent = 0 + } + }, + name = "Test 2", + hidden = 0 + } + }, + priority = 500, + name = "Test 1" + }, + { + shortcut = "T", + hidden = 0, + submenus = { + { + mappings = { + n = { + sid = 1, + noremap = 1, + enabled = 1, + rhs = "i space<Esc>", + silent = 0 + } + }, + hidden = 0, + actext = "3", + priority = 500, + name = "Test" + } + }, + priority = 500, + name = "Test4" + } + } + eq(m, expected) + end) end) diff --git a/test/functional/fixtures/autoload/provider/clipboard.vim b/test/functional/fixtures/autoload/provider/clipboard.vim index 411e095c71..6d777255c8 100644 --- a/test/functional/fixtures/autoload/provider/clipboard.vim +++ b/test/functional/fixtures/autoload/provider/clipboard.vim @@ -5,7 +5,13 @@ let s:methods = {} let g:cliplossy = 0 let g:cliperror = 0 +" Count how many times the clipboard was invoked. +let g:clip_called_get = 0 +let g:clip_called_set = 0 + function! s:methods.get(reg) + let g:clip_called_get += 1 + if g:cliperror return 0 end @@ -19,6 +25,8 @@ function! s:methods.get(reg) endfunction function! s:methods.set(lines, regtype, reg) + let g:clip_called_set += 1 + if a:reg == '"' call s:methods.set(a:lines,a:regtype,'+') call s:methods.set(a:lines,a:regtype,'*') diff --git a/test/functional/fixtures/tty-test.c b/test/functional/fixtures/tty-test.c index 7ba21d652a..edcbe23f86 100644 --- a/test/functional/fixtures/tty-test.c +++ b/test/functional/fixtures/tty-test.c @@ -5,42 +5,45 @@ #include <stdio.h> #include <stdlib.h> #include <uv.h> +#ifdef _WIN32 +# include <windows.h> +#else +# include <unistd.h> +#endif // -V:STRUCT_CAST:641 #define STRUCT_CAST(Type, obj) ((Type *)(obj)) +#define is_terminal(stream) (uv_guess_handle(fileno(stream)) == UV_TTY) +#define BUF_SIZE 0xfff +#define CTRL_C 0x03 uv_tty_t tty; +uv_tty_t tty_out; -#ifdef _WIN32 -#include <windows.h> bool owns_tty(void) { - HWND consoleWnd = GetConsoleWindow(); - DWORD dwProcessId; - GetWindowThreadProcessId(consoleWnd, &dwProcessId); - return GetCurrentProcessId() == dwProcessId; -} +#ifdef _WIN32 + // XXX: We need to make proper detect owns tty + // HWND consoleWnd = GetConsoleWindow(); + // DWORD dwProcessId; + // GetWindowThreadProcessId(consoleWnd, &dwProcessId); + // return GetCurrentProcessId() == dwProcessId; + return true; #else -#include <unistd.h> -bool owns_tty(void) -{ return getsid(0) == getpid(); -} #endif +} -#define is_terminal(stream) (uv_guess_handle(fileno(stream)) == UV_TTY) -#define BUF_SIZE 0xfff - -static void walk_cb(uv_handle_t *handle, void *arg) { +static void walk_cb(uv_handle_t *handle, void *arg) +{ if (!uv_is_closing(handle)) { uv_close(handle, NULL); } } -#ifndef WIN32 static void sig_handler(int signum) { - switch(signum) { + switch (signum) { case SIGWINCH: { int width, height; uv_tty_get_winsize(&tty, &width, &height); @@ -54,15 +57,15 @@ static void sig_handler(int signum) return; } } -#endif -// static void sigwinch_cb(uv_signal_t *handle, int signum) -// { -// int width, height; -// uv_tty_t *tty = handle->data; -// uv_tty_get_winsize(tty, &width, &height); -// fprintf(stderr, "rows: %d, cols: %d\n", height, width); -// } +#ifdef WIN32 +static void sigwinch_cb(uv_signal_t *handle, int signum) +{ + int width, height; + uv_tty_get_winsize(&tty_out, &width, &height); + fprintf(stderr, "rows: %d, cols: %d\n", height, width); +} +#endif static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf) { @@ -80,7 +83,7 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf) int *interrupted = stream->data; for (int i = 0; i < cnt; i++) { - if (buf->base[i] == 3) { + if (buf->base[i] == CTRL_C) { (*interrupted)++; } } @@ -88,11 +91,13 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf) uv_loop_t write_loop; uv_loop_init(&write_loop); uv_tty_t out; - uv_tty_init(&write_loop, &out, 1, 0); + uv_tty_init(&write_loop, &out, fileno(stdout), 0); + uv_write_t req; uv_buf_t b = {.base = buf->base, .len = (size_t)cnt}; uv_write(&req, STRUCT_CAST(uv_stream_t, &out), &b, 1, NULL); uv_run(&write_loop, UV_RUN_DEFAULT); + uv_close(STRUCT_CAST(uv_handle_t, &out), NULL); uv_run(&write_loop, UV_RUN_DEFAULT); if (uv_loop_close(&write_loop)) { @@ -137,7 +142,7 @@ int main(int argc, char **argv) if (argc > 1) { int count = atoi(argv[1]); - for (int i = 0; i < count; ++i) { + for (int i = 0; i < count; i++) { printf("line%d\n", i); } fflush(stdout); @@ -148,8 +153,14 @@ int main(int argc, char **argv) uv_prepare_t prepare; uv_prepare_init(uv_default_loop(), &prepare); uv_prepare_start(&prepare, prepare_cb); - // uv_tty_t tty; +#ifndef WIN32 uv_tty_init(uv_default_loop(), &tty, fileno(stderr), 1); +#else + uv_tty_init(uv_default_loop(), &tty, fileno(stdin), 1); + uv_tty_init(uv_default_loop(), &tty_out, fileno(stdout), 0); + int width, height; + uv_tty_get_winsize(&tty_out, &width, &height); +#endif uv_tty_set_mode(&tty, UV_TTY_MODE_RAW); tty.data = &interrupted; uv_read_start(STRUCT_CAST(uv_stream_t, &tty), alloc_cb, read_cb); @@ -160,15 +171,17 @@ int main(int argc, char **argv) sa.sa_handler = sig_handler; sigaction(SIGHUP, &sa, NULL); sigaction(SIGWINCH, &sa, NULL); - // uv_signal_t sigwinch_watcher; - // uv_signal_init(uv_default_loop(), &sigwinch_watcher); - // sigwinch_watcher.data = &tty; - // uv_signal_start(&sigwinch_watcher, sigwinch_cb, SIGWINCH); +#else + uv_signal_t sigwinch_watcher; + uv_signal_init(uv_default_loop(), &sigwinch_watcher); + uv_signal_start(&sigwinch_watcher, sigwinch_cb, SIGWINCH); #endif uv_run(uv_default_loop(), UV_RUN_DEFAULT); +#ifndef WIN32 // XXX: Without this the SIGHUP handler is skipped on some systems. sleep(100); +#endif return 0; } diff --git a/test/functional/legacy/003_cindent_spec.lua b/test/functional/legacy/003_cindent_spec.lua index 27835fea28..58e87354fb 100644 --- a/test/functional/legacy/003_cindent_spec.lua +++ b/test/functional/legacy/003_cindent_spec.lua @@ -15,6 +15,8 @@ local function insert_(content) feed_command('1', 'set cin ts=4 sw=4') end +-- luacheck: ignore 621 (Indentation) +-- luacheck: ignore 613 (Trailing whitespace in a string) describe('cindent', function() before_each(clear) @@ -3915,6 +3917,26 @@ describe('cindent', function() { 111111111111111111; } + namespace test::cpp17 + { + 111111111111111111; + } + namespace ::incorrectcpp17 + { + 111111111111111111; + } + namespace test::incorrectcpp17:: + { + 111111111111111111; + } + namespace test:incorrectcpp17 + { + 111111111111111111; + } + namespace test:::incorrectcpp17 + { + 111111111111111111; + } namespace{ 111111111111111111; } @@ -3986,6 +4008,26 @@ describe('cindent', function() { 111111111111111111; } + namespace test::cpp17 + { + 111111111111111111; + } + namespace ::incorrectcpp17 + { + 111111111111111111; + } + namespace test::incorrectcpp17:: + { + 111111111111111111; + } + namespace test:incorrectcpp17 + { + 111111111111111111; + } + namespace test:::incorrectcpp17 + { + 111111111111111111; + } namespace{ 111111111111111111; } @@ -4676,4 +4718,38 @@ describe('cindent', function() JSEND ]=]) end) + + it('line continuations in macros / vim-patch 8.0.0148', function() + insert_([=[ + /* start of define */ + { + } + #define AAA \ + BBB\ + CCC + + #define CNT \ + 1 + \ + 2 + \ + 4 + /* end of define */]=]) + + feed_command('set cino&') + feed_command('/start of define') + feed('=/end of define<cr>') + + expect([=[ + /* start of define */ + { + } + #define AAA \ + BBB\ + CCC + + #define CNT \ + 1 + \ + 2 + \ + 4 + /* end of define */]=]) + end) end) diff --git a/test/functional/legacy/005_bufleave_delete_buffer_spec.lua b/test/functional/legacy/005_bufleave_delete_buffer_spec.lua index 417842c52d..8b92c877a6 100644 --- a/test/functional/legacy/005_bufleave_delete_buffer_spec.lua +++ b/test/functional/legacy/005_bufleave_delete_buffer_spec.lua @@ -9,6 +9,7 @@ local wait = helpers.wait describe('test5', function() setup(clear) + -- luacheck: ignore 621 (Indentation) it('is working', function() insert([[ start of test file Xxx diff --git a/test/functional/legacy/006_argument_list_spec.lua b/test/functional/legacy/006_argument_list_spec.lua index dac58df8a5..9f75a91fa8 100644 --- a/test/functional/legacy/006_argument_list_spec.lua +++ b/test/functional/legacy/006_argument_list_spec.lua @@ -78,8 +78,8 @@ describe('argument list', function() end) teardown(function() - os.remove('Xxx1') - os.remove('Xxx2') - os.remove('Xxx3') + os.remove('Xxx1') + os.remove('Xxx2') + os.remove('Xxx3') end) end) diff --git a/test/functional/legacy/007_ball_buffer_list_spec.lua b/test/functional/legacy/007_ball_buffer_list_spec.lua index 8501faabec..a180e73301 100644 --- a/test/functional/legacy/007_ball_buffer_list_spec.lua +++ b/test/functional/legacy/007_ball_buffer_list_spec.lua @@ -36,10 +36,10 @@ describe(':ball', function() -- Open window for all args, close Xxx2 feed('$r4:ball<cr>') - + -- Write contents of this file feed_command('%yank A') - + -- Append contents of second window (Xxx1) feed('') feed_command('%yank A') diff --git a/test/functional/legacy/011_autocommands_spec.lua b/test/functional/legacy/011_autocommands_spec.lua index e01af4583b..d969a8bd37 100644 --- a/test/functional/legacy/011_autocommands_spec.lua +++ b/test/functional/legacy/011_autocommands_spec.lua @@ -94,6 +94,8 @@ describe('file reading, writing and bufnew and filter autocommands', function() eq(gzip_data, io.open('Xtestfile.gz'):read('*all')) end) + -- luacheck: ignore 621 (Indentation) + -- luacheck: ignore 611 (Line contains only whitespaces) it('FileReadPre, FileReadPost', function() prepare_gz_file('Xtestfile', text1) feed_command('au! FileReadPre *.gz exe "silent !gzip -d " . shellescape(expand("<afile>"))') diff --git a/test/functional/legacy/015_alignment_spec.lua b/test/functional/legacy/015_alignment_spec.lua index 8423aa3d11..d73ff06972 100644 --- a/test/functional/legacy/015_alignment_spec.lua +++ b/test/functional/legacy/015_alignment_spec.lua @@ -9,6 +9,7 @@ local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers describe('alignment', function() setup(clear) + -- luacheck: ignore 621 (Indentation) it('is working', function() insert([[ test for :left @@ -112,7 +113,7 @@ describe('alignment', function() asxa;ofa axxxoikey asdfaqwer axxxoikey - xxxxx xx xxxxxx + xxxxx xx xxxxxx xxxxxxx xxxxxxxxx xxx xxxx xxxxx xxxxx xxx xx xxxxxxxxxxxxxxxxxx xxxxx xxxx, xxxx xxxx xxxx xxxx xxx xx xx xx xxxxxxx. xxxx xxxx. diff --git a/test/functional/legacy/019_smarttab_expandtab_spec.lua b/test/functional/legacy/019_smarttab_expandtab_spec.lua index ecb24885bb..7b03ee8e99 100644 --- a/test/functional/legacy/019_smarttab_expandtab_spec.lua +++ b/test/functional/legacy/019_smarttab_expandtab_spec.lua @@ -8,6 +8,7 @@ local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers describe([[performing "r<Tab>" with 'smarttab' and 'expandtab' set/not set, and "dv_"]], function() setup(clear) + -- luacheck: ignore 621 (Indentation) it('is working', function() insert([[ start text diff --git a/test/functional/legacy/029_join_spec.lua b/test/functional/legacy/029_join_spec.lua index 460b9291bf..b28f276a7c 100644 --- a/test/functional/legacy/029_join_spec.lua +++ b/test/functional/legacy/029_join_spec.lua @@ -11,6 +11,8 @@ local feed_command = helpers.feed_command describe('joining lines', function() before_each(clear) + -- luacheck: ignore 613 (Trailing whitespaces in a string) + -- luacheck: ignore 611 (Line contains only whitespaces) it("keeps marks with different 'joinspaces' settings", function() insert([[ firstline diff --git a/test/functional/legacy/033_lisp_indent_spec.lua b/test/functional/legacy/033_lisp_indent_spec.lua index 2b79ee024b..5132333a5c 100644 --- a/test/functional/legacy/033_lisp_indent_spec.lua +++ b/test/functional/legacy/033_lisp_indent_spec.lua @@ -9,6 +9,7 @@ local wait = helpers.wait describe('lisp indent', function() setup(clear) + -- luacheck: ignore 621 (Indentation) it('is working', function() insert([[ (defun html-file (base) @@ -22,7 +23,7 @@ describe('lisp indent', function() :if-exists :supersede) (let ((,ti ,title)) (as title ,ti) - (with center + (with center (as h2 (string-upcase ,ti))) (brs 3) ,@body)))) @@ -58,7 +59,7 @@ describe('lisp indent', function() :if-exists :supersede) (let ((,ti ,title)) (as title ,ti) - (with center + (with center (as h2 (string-upcase ,ti))) (brs 3) ,@body)))) diff --git a/test/functional/legacy/038_virtual_replace_spec.lua b/test/functional/legacy/038_virtual_replace_spec.lua index 2dfc959a8c..8dd7bdda6e 100644 --- a/test/functional/legacy/038_virtual_replace_spec.lua +++ b/test/functional/legacy/038_virtual_replace_spec.lua @@ -7,6 +7,7 @@ local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers describe('Virtual replace mode', function() setup(clear) + -- luacheck: ignore 621 (Indentation) it('is working', function() -- Make sure that backspace works, no matter what termcap is used. feed_command('set t_kD=x7f t_kb=x08') diff --git a/test/functional/legacy/039_visual_block_mode_commands_spec.lua b/test/functional/legacy/039_visual_block_mode_commands_spec.lua index dffef50950..135058c579 100644 --- a/test/functional/legacy/039_visual_block_mode_commands_spec.lua +++ b/test/functional/legacy/039_visual_block_mode_commands_spec.lua @@ -43,6 +43,7 @@ describe('Visual block mode', function() abcdqqqqijklm]]) end) + -- luacheck: ignore 611 (Line contains only whitespaces) it('should insert a block using cursor keys for movement', function() insert([[ aaaaaa @@ -104,6 +105,7 @@ describe('Visual block mode', function() 456ab7]]) end) + -- luacheck: ignore 621 (Indentation) it('should insert and append a block when virtualedit=all', function() insert([[ line1 diff --git a/test/functional/legacy/051_highlight_spec.lua b/test/functional/legacy/051_highlight_spec.lua index 60d29246ff..2ef74196ee 100644 --- a/test/functional/legacy/051_highlight_spec.lua +++ b/test/functional/legacy/051_highlight_spec.lua @@ -37,6 +37,7 @@ describe(':highlight', function() feed('q') wait() -- wait until we're back to normal command('hi Search') + command('hi Normal') -- Test setting colors. -- Test clearing one color and all doesn't generate error or warning diff --git a/test/functional/legacy/055_list_and_dict_types_spec.lua b/test/functional/legacy/055_list_and_dict_types_spec.lua index e84c415eb0..dcbd8b7dff 100644 --- a/test/functional/legacy/055_list_and_dict_types_spec.lua +++ b/test/functional/legacy/055_list_and_dict_types_spec.lua @@ -191,6 +191,7 @@ describe('list and dictionary types', function() [3]]=]) end) + -- luacheck: ignore 613 (Trailing whitespace in a string) it('assignment to a list', function() source([[ let l = [0, 1, 2, 3] diff --git a/test/functional/legacy/060_exists_and_has_functions_spec.lua b/test/functional/legacy/060_exists_and_has_functions_spec.lua index 3e99f6df57..1794f23b3a 100644 --- a/test/functional/legacy/060_exists_and_has_functions_spec.lua +++ b/test/functional/legacy/060_exists_and_has_functions_spec.lua @@ -12,7 +12,7 @@ describe('exists() and has() functions', function() write_file('test60.vim', [[ " Vim script for exists() function test " Script-local variables are checked here - + " Existing script-local variable let s:script_var = 1 echo 's:script_var: 1' @@ -21,7 +21,7 @@ describe('exists() and has() functions', function() else echo "FAILED" endif - + " Non-existing script-local variable unlet s:script_var echo 's:script_var: 0' @@ -30,7 +30,7 @@ describe('exists() and has() functions', function() else echo "FAILED" endif - + " Existing script-local list let s:script_list = ["blue", "orange"] echo 's:script_list: 1' @@ -39,7 +39,7 @@ describe('exists() and has() functions', function() else echo "FAILED" endif - + " Non-existing script-local list unlet s:script_list echo 's:script_list: 0' @@ -48,7 +48,7 @@ describe('exists() and has() functions', function() else echo "FAILED" endif - + " Existing script-local dictionary let s:script_dict = {"xcord":100, "ycord":2} echo 's:script_dict: 1' @@ -57,7 +57,7 @@ describe('exists() and has() functions', function() else echo "FAILED" endif - + " Non-existing script-local dictionary unlet s:script_dict echo 's:script_dict: 0' @@ -66,7 +66,7 @@ describe('exists() and has() functions', function() else echo "FAILED" endif - + " Existing script curly-brace variable let str = "script" let s:curly_{str}_var = 1 @@ -76,7 +76,7 @@ describe('exists() and has() functions', function() else echo "FAILED" endif - + " Non-existing script-local curly-brace variable unlet s:curly_{str}_var echo 's:curly_' . str . '_var: 0' @@ -85,21 +85,21 @@ describe('exists() and has() functions', function() else echo "FAILED" endif - + " Existing script-local function function! s:my_script_func() endfunction - + echo '*s:my_script_func: 1' if exists('*s:my_script_func') echo "OK" else echo "FAILED" endif - + " Non-existing script-local function delfunction s:my_script_func - + echo '*s:my_script_func: 0' if !exists('*s:my_script_func') echo "OK" @@ -644,7 +644,7 @@ describe('exists() and has() functions', function() -- Assert buffer contents. expect([[ - + #myagroup: 1 OK #myagroup+b: 0 diff --git a/test/functional/legacy/066_visual_block_tab_spec.lua b/test/functional/legacy/066_visual_block_tab_spec.lua index 7c4984362f..f10152d8ea 100644 --- a/test/functional/legacy/066_visual_block_tab_spec.lua +++ b/test/functional/legacy/066_visual_block_tab_spec.lua @@ -15,7 +15,7 @@ describe('visual block shift and tab characters', function() one two three one two three one two three - + abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz @@ -49,7 +49,7 @@ describe('visual block shift and tab characters', function() on1 two three on1 two three on1 two three - + abcdefghijklmnopqrstuvwxyz abcdefghij abc defghijklmnopqrstuvwxyz diff --git a/test/functional/legacy/068_text_formatting_spec.lua b/test/functional/legacy/068_text_formatting_spec.lua index 772dbc14cf..3a1b21bf87 100644 --- a/test/functional/legacy/068_text_formatting_spec.lua +++ b/test/functional/legacy/068_text_formatting_spec.lua @@ -9,6 +9,7 @@ local expect = helpers.expect describe('text formatting', function() setup(clear) + -- luacheck: ignore 613 (Trailing whitespace in a string) it('is working', function() -- The control character <C-A> (byte \x01) needs to be put in the buffer -- directly. But the insert function sends the text to nvim in insert diff --git a/test/functional/legacy/069_multibyte_formatting_spec.lua b/test/functional/legacy/069_multibyte_formatting_spec.lua index 38ca25d57a..9c248e3aa8 100644 --- a/test/functional/legacy/069_multibyte_formatting_spec.lua +++ b/test/functional/legacy/069_multibyte_formatting_spec.lua @@ -27,7 +27,7 @@ describe('multibyte text', function() XYZ abc XYZ - + XYZ abc XYZ @@ -62,7 +62,7 @@ describe('multibyte text', function() Y X Y - + X X a @@ -125,7 +125,7 @@ describe('multibyte text', function() ab X Y - + X X a @@ -166,7 +166,7 @@ describe('multibyte text', function() X X a - + X X a @@ -190,7 +190,7 @@ describe('multibyte text', function() X X a - + X X a @@ -239,7 +239,7 @@ describe('multibyte text', function() XX XXa XXY - + X Xa Xa @@ -259,7 +259,7 @@ describe('multibyte text', function() it('formatting in replace mode', function() insert([[ { - + }]]) feed_command('/^{/+1') feed_command('set tw=2 fo=tm') diff --git a/test/functional/legacy/078_swapfile_recover_spec.lua b/test/functional/legacy/078_swapfile_recover_spec.lua index 4390ba2ca8..45f0aed37a 100644 --- a/test/functional/legacy/078_swapfile_recover_spec.lua +++ b/test/functional/legacy/078_swapfile_recover_spec.lua @@ -22,32 +22,32 @@ describe('78', function() let linecount = 10000 while i <= linecount | call append(i - 1, i . text) | let i += 1 | endwhile preserve - + " Get the name of the swap file, and clean up the :redir capture. redir => g:swapname | swapname | redir END let g:swapname = substitute(g:swapname, '[[:blank:][:cntrl:]]*\(.\{-}\)[[:blank:][:cntrl:]]*$', '\1', 'g') let g:swapname = fnameescape(g:swapname) - + " Make a copy of the swap file in Xswap set bin exe 'sp ' . g:swapname w! Xswap - + set nobin new only! bwipe! Xtest call rename('Xswap', g:swapname) - + "TODO(jkeyes): without 'silent', this hangs the test " at message: " 'Recovery completed. You should check if everything is OK.' silent recover Xtest - + call delete(g:swapname) new call append(0, 'recovery start') wincmd w - + let g:linedollar = line('$') if g:linedollar < linecount wincmd w @@ -56,7 +56,7 @@ describe('78', function() wincmd w let linecount = g:linedollar endif - + let i = 1 while i <= linecount if getline(i) != i . text @@ -72,7 +72,7 @@ describe('78', function() expect([[ recovery start - + recovery end]]) end) end) diff --git a/test/functional/legacy/081_coptions_movement_spec.lua b/test/functional/legacy/081_coptions_movement_spec.lua index 993aff2ba2..d82c46a3d3 100644 --- a/test/functional/legacy/081_coptions_movement_spec.lua +++ b/test/functional/legacy/081_coptions_movement_spec.lua @@ -7,6 +7,7 @@ local feed_command, expect = helpers.feed_command, helpers.expect describe('coptions', function() setup(clear) + -- luacheck: ignore 613 (Trailing whitespace in a string) it('is working', function() insert([[ aaa two three four diff --git a/test/functional/legacy/082_string_comparison_spec.lua b/test/functional/legacy/082_string_comparison_spec.lua index cfc0b96bce..311822c34f 100644 --- a/test/functional/legacy/082_string_comparison_spec.lua +++ b/test/functional/legacy/082_string_comparison_spec.lua @@ -115,9 +115,9 @@ describe('case-insensitive string comparison in UTF-8', function() -- Assert buffer contents. expect([=[ 3732 checks passed - + ABCD - + defg]=]) end) end) diff --git a/test/functional/legacy/084_curswant_spec.lua b/test/functional/legacy/084_curswant_spec.lua index 9809ce5b88..42cb2fc56d 100644 --- a/test/functional/legacy/084_curswant_spec.lua +++ b/test/functional/legacy/084_curswant_spec.lua @@ -7,6 +7,7 @@ local clear, expect = helpers.clear, helpers.expect describe('curswant', function() setup(clear) + -- luacheck: ignore 621 (Indentation) it('is working', function() insert([[ start target options diff --git a/test/functional/legacy/088_conceal_tabs_spec.lua b/test/functional/legacy/088_conceal_tabs_spec.lua index c9414679ab..a4c7e26583 100644 --- a/test/functional/legacy/088_conceal_tabs_spec.lua +++ b/test/functional/legacy/088_conceal_tabs_spec.lua @@ -12,6 +12,7 @@ end describe('cursor and column position with conceal and tabulators', function() setup(clear) + -- luacheck: ignore 621 (Indentation) it('are working', function() insert([[ start: diff --git a/test/functional/legacy/089_number_relnumber_findfile_spec.lua b/test/functional/legacy/089_number_relnumber_findfile_spec.lua index 7a87fc8603..6708fd50b7 100644 --- a/test/functional/legacy/089_number_relnumber_findfile_spec.lua +++ b/test/functional/legacy/089_number_relnumber_findfile_spec.lua @@ -63,26 +63,26 @@ describe("setting 'number' and 'relativenumber'", function() -- Assert buffer contents. expect([[ results: - + number relativenumber - + number relativenumber :setlocal must NOT reset the other global value - + number - + relativenumber :setglobal MUST reset the other global value - + number - + relativenumber :set MUST reset the other global value - + number - + relativenumber]]) end) end) @@ -108,7 +108,7 @@ describe('findfile', function() expect([[ Testing findfile - + src/nvim/api/vim.c api/vim.c api/vim.c]]) diff --git a/test/functional/legacy/092_mksession_cursor_cols_utf8_spec.lua b/test/functional/legacy/092_mksession_cursor_cols_utf8_spec.lua index 44f1664abe..3c46c29951 100644 --- a/test/functional/legacy/092_mksession_cursor_cols_utf8_spec.lua +++ b/test/functional/legacy/092_mksession_cursor_cols_utf8_spec.lua @@ -15,6 +15,7 @@ describe('store cursor position in session file in UTF-8', function() os.remove('test.out') end) + -- luacheck: ignore 621 (Indentation) it('is working', function() insert([[ start: diff --git a/test/functional/legacy/093_mksession_cursor_cols_latin1_spec.lua b/test/functional/legacy/093_mksession_cursor_cols_latin1_spec.lua index 49bc43f76f..b1221ff8b6 100644 --- a/test/functional/legacy/093_mksession_cursor_cols_latin1_spec.lua +++ b/test/functional/legacy/093_mksession_cursor_cols_latin1_spec.lua @@ -17,6 +17,7 @@ describe('store cursor position in session file in Latin-1', function() os.remove('test.out') end) + -- luacheck: ignore 621 (Indentation) it('is working', function() insert([[ start: diff --git a/test/functional/legacy/094_visual_mode_operators_spec.lua b/test/functional/legacy/094_visual_mode_operators_spec.lua index 84e384050a..ff1d3e7bec 100644 --- a/test/functional/legacy/094_visual_mode_operators_spec.lua +++ b/test/functional/legacy/094_visual_mode_operators_spec.lua @@ -372,6 +372,7 @@ describe('Visual mode and operator', function() end) end) + -- luacheck: ignore 613 (Trailing whitespace in a string) it('gv in exclusive select mode after operation', function() source([[ $put ='zzz ' diff --git a/test/functional/legacy/103_visual_mode_reset_spec.lua b/test/functional/legacy/103_visual_mode_reset_spec.lua index d05b47fa32..f5cd861019 100644 --- a/test/functional/legacy/103_visual_mode_reset_spec.lua +++ b/test/functional/legacy/103_visual_mode_reset_spec.lua @@ -32,7 +32,7 @@ describe('E315 error', function() -- :del the ex-way will require the colon operator which resets the -- visual mode thus preventing the problem: feed('GV:call TriggerTheProblem()<cr>') - + source([[ %del _ call append(line('$'), g:msg) @@ -41,7 +41,7 @@ describe('E315 error', function() -- Assert buffer contents. expect([[ - + Everything's fine.]]) end) end) diff --git a/test/functional/legacy/106_errorformat_spec.lua b/test/functional/legacy/106_errorformat_spec.lua index 5d76adc786..3f017a704f 100644 --- a/test/functional/legacy/106_errorformat_spec.lua +++ b/test/functional/legacy/106_errorformat_spec.lua @@ -15,9 +15,9 @@ describe('errorformat', function() command("$put =strtrans(string(map(getqflist(), '[v:val.text, v:val.valid]')))") command("cgetexpr ['WWWW', 'GGGG', 'ZZZZ', 'EEEE', 'CCCC', 'YYYY']") command("$put =strtrans(string(map(getqflist(), '[v:val.text, v:val.valid]')))") - + expect([=[ - + [['W', 1], ['E^@CCCC', 1]] [['W', 1], ['E^@CCCC', 1]] [['W', 1], ['ZZZZ', 0], ['E^@CCCC', 1], ['YYYY', 0]]]=]) diff --git a/test/functional/legacy/108_backtrace_debug_commands_spec.lua b/test/functional/legacy/108_backtrace_debug_commands_spec.lua index b2e2fa4ed3..ff1917e90c 100644 --- a/test/functional/legacy/108_backtrace_debug_commands_spec.lua +++ b/test/functional/legacy/108_backtrace_debug_commands_spec.lua @@ -89,18 +89,18 @@ describe('108', function() -- Assert buffer contents. expect([=[ - - - + + + - show backtrace: - + 2 function Foo[2] 1 Bar[2] ->0 Bazz line 2: let var3 = "another var" - + show variables on different levels: - + 6 2 function Foo[2] ->1 Bar[2] @@ -112,9 +112,9 @@ describe('108', function() 0 Bazz line 2: let var3 = "another var" 1 - + - undefined vars: - + undefined var3 on former level: Error detected while processing function Foo[2]..Bar[2]..Bazz: line 3: @@ -122,7 +122,7 @@ describe('108', function() E15: Invalid expression: var3 here var3 is defined with "another var": another var - + undefined var2 on former level Error detected while processing function Foo[2]..Bar: line 3: @@ -130,37 +130,37 @@ describe('108', function() E15: Invalid expression: var2 here var2 is defined with 10: 10 - + - backtrace movements: - + 1 function Foo[2] ->0 Bar line 3: End of function - + next command cannot go down, we are on bottom - + frame is zero - + next command cannot go up, we are on top - + frame at highest level: 1 ->1 function Foo[2] 0 Bar line 3: End of function fil is not frame or finish, it is file "[No Name]" --No lines in buffer-- - + - relative backtrace movement - + 1 function Foo[2] ->0 Bar line 3: End of function ->1 function Foo[2] 0 Bar line 3: End of function - + - go beyond limits does not crash - + frame at highest level: 1 ->1 function Foo[2] 0 Bar @@ -169,7 +169,7 @@ describe('108', function() 1 function Foo[2] ->0 Bar line 3: End of function - + - final result 19: 19 ]=]) diff --git a/test/functional/legacy/assert_spec.lua b/test/functional/legacy/assert_spec.lua index d646e8dbf4..381461dc4f 100644 --- a/test/functional/legacy/assert_spec.lua +++ b/test/functional/legacy/assert_spec.lua @@ -89,7 +89,7 @@ describe('assert function:', function() it('should change v:errors when expected is equal to actual', function() call('assert_notequal', 'foo', 'foo') - expected_errors({"Expected 'foo' differs from 'foo'"}) + expected_errors({"Expected not equal to 'foo'"}) end) end) diff --git a/test/functional/legacy/breakindent_spec.lua b/test/functional/legacy/breakindent_spec.lua index 7594dba16c..fd25e809e0 100644 --- a/test/functional/legacy/breakindent_spec.lua +++ b/test/functional/legacy/breakindent_spec.lua @@ -7,6 +7,9 @@ local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers describe('breakindent', function() setup(clear) + -- luacheck: ignore 621 (Indentation) + -- luacheck: ignore 613 (Trailing whitespace in a string) + -- luacheck: ignore 611 (Line contains only whitespaces) it('is working', function() insert('dummy text') diff --git a/test/functional/legacy/command_count_spec.lua b/test/functional/legacy/command_count_spec.lua index ad5368430a..8707c0459c 100644 --- a/test/functional/legacy/command_count_spec.lua +++ b/test/functional/legacy/command_count_spec.lua @@ -4,6 +4,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear, source, expect = helpers.clear, helpers.source, helpers.expect local feed_command = helpers.feed_command +-- luacheck: ignore 613 (Trailing whitespace in a string) describe('command_count', function() it('is working', function() -- It is relevant for the test to load a file initially. If this is @@ -133,8 +134,8 @@ describe('command_count', function() let g:lines = [] func BufStatus() call add(g:lines, - \ 'aaa: ' . buflisted(g:buf_aaa) . - \ ' bbb: ' . buflisted(g:buf_bbb) . + \ 'aaa: ' . buflisted(g:buf_aaa) . + \ ' bbb: ' . buflisted(g:buf_bbb) . \ ' ccc: ' . buflisted(g:buf_ccc)) endfunc se nohidden @@ -214,7 +215,7 @@ describe('command_count', function() RangeTabsAll 1 5 RangeLines 2 5 LocalRangeLines 2 5 - + 5argu E16: Invalid range 4argu d 1argu a @@ -225,7 +226,7 @@ describe('command_count', function() $tabe 2 $+tabe E16: Invalid range 0tabm x - + aaa: 1 bbb: 1 ccc: 1 aaa: 1 bbb: 0 ccc: 0 aaa: 0 bbb: 0 ccc: 0 diff --git a/test/functional/legacy/erasebackword_spec.lua b/test/functional/legacy/erasebackword_spec.lua index 33b7704b65..8ca64df328 100644 --- a/test/functional/legacy/erasebackword_spec.lua +++ b/test/functional/legacy/erasebackword_spec.lua @@ -6,6 +6,7 @@ local clear, feed, expect = helpers.clear, helpers.feed, helpers.expect describe('CTRL-W in Insert mode', function() setup(clear) + -- luacheck: ignore 611 (Line contains only whitespaces) it('works for multi-byte characters', function() for i = 1, 6 do diff --git a/test/functional/legacy/increment_spec.lua b/test/functional/legacy/increment_spec.lua index 15273a4ad5..d51f9a2e02 100644 --- a/test/functional/legacy/increment_spec.lua +++ b/test/functional/legacy/increment_spec.lua @@ -685,7 +685,7 @@ describe('Ctrl-A/Ctrl-X on visual selections', function() " Text: " 1 23 " 4 56 - " + " " Expected: " 1) f2 Ctrl-V jl <ctrl-a>, repeat twice afterwards with . " 1 26 diff --git a/test/functional/legacy/listchars_spec.lua b/test/functional/legacy/listchars_spec.lua index 3c0fa48e76..cffb9fd376 100644 --- a/test/functional/legacy/listchars_spec.lua +++ b/test/functional/legacy/listchars_spec.lua @@ -4,12 +4,14 @@ local helpers = require('test.functional.helpers')(after_each) local feed, insert, source = helpers.feed, helpers.insert, helpers.source local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers.expect +-- luacheck: ignore 621 (Indentation) describe("'listchars'", function() before_each(function() clear() feed_command('set listchars&vi') end) + -- luacheck: ignore 613 (Trailing whitespace in a string) it("works with 'list'", function() source([[ function GetScreenCharsForLine(lnum) diff --git a/test/functional/legacy/listlbr_spec.lua b/test/functional/legacy/listlbr_spec.lua index d39125c9e6..f70d55f4a3 100644 --- a/test/functional/legacy/listlbr_spec.lua +++ b/test/functional/legacy/listlbr_spec.lua @@ -7,6 +7,9 @@ local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers describe('listlbr', function() setup(clear) + -- luacheck: ignore 621 (Indentation) + -- luacheck: ignore 611 (Line contains only whitespaces) + -- luacheck: ignore 613 (Trailing whitespaces in a string) it('is working', function() insert([[ dummy text]]) @@ -20,20 +23,20 @@ describe('listlbr', function() feed_command('set ts=4 sw=4 sts=4 linebreak sbr=+ wrap') source([[ fu! ScreenChar(width) - let c='' - for j in range(1,4) - for i in range(1,a:width) - let c.=nr2char(screenchar(j, i)) - endfor - let c.="\n" - endfor - return c + let c='' + for j in range(1,4) + for i in range(1,a:width) + let c.=nr2char(screenchar(j, i)) + endfor + let c.="\n" + endfor + return c endfu fu! DoRecordScreen() - wincmd l - $put =printf(\"\n%s\", g:test) - $put =g:line - wincmd p + wincmd l + $put =printf(\"\n%s\", g:test) + $put =g:line + wincmd p endfu ]]) feed_command('let g:test="Test 1: set linebreak"') diff --git a/test/functional/legacy/listlbr_utf8_spec.lua b/test/functional/legacy/listlbr_utf8_spec.lua index f06bca72ba..d7f4c71af2 100644 --- a/test/functional/legacy/listlbr_utf8_spec.lua +++ b/test/functional/legacy/listlbr_utf8_spec.lua @@ -8,6 +8,8 @@ local clear, expect = helpers.clear, helpers.expect describe('linebreak', function() setup(clear) + -- luacheck: ignore 621 (Indentation) + -- luacheck: ignore 613 (Trailing whitespaces in a string) it('is working', function() source([[ set wildchar=^E @@ -18,20 +20,20 @@ describe('linebreak', function() norm! zt set ts=4 sw=4 sts=4 linebreak sbr=+ wrap fu! ScreenChar(width, lines) - let c='' - for j in range(1,a:lines) - for i in range(1,a:width) - let c.=nr2char(screenchar(j, i)) - endfor + let c='' + for j in range(1,a:lines) + for i in range(1,a:width) + let c.=nr2char(screenchar(j, i)) + endfor let c.="\n" - endfor - return c + endfor + return c endfu fu! DoRecordScreen() - wincmd l - $put =printf(\"\n%s\", g:test) - $put =g:line - wincmd p + wincmd l + $put =printf(\"\n%s\", g:test) + $put =g:line + wincmd p endfu " let g:test ="Test 1: set linebreak + set list + fancy listchars" @@ -148,22 +150,22 @@ describe('linebreak', function() -- Assert buffer contents. expect([[ - + abcdef hijklmn pqrstuvwxyz 1060ABCDEFGHIJKLMNOP - + Test 1: set linebreak + set list + fancy listchars ▕———abcdef +hijklmn▕——— +pqrstuvwxyz␣1060ABC +DEFGHIJKLMNOPˑ¶ - + Test 2: set nolinebreak list ▕———abcdef hijklmn▕— +pqrstuvwxyz␣1060ABC +DEFGHIJKLMNOPˑ¶ ¶ *mask = nil; - + Test 3: set linebreak nolist *mask = nil; ~ @@ -177,7 +179,7 @@ describe('linebreak', function() #define MSG_MODE_CONSOLE 2 #define MSG_MODE_FILE_AND_CONSOLE 3 #define MSG_MODE_FILE_THEN_CONSOLE 4 - + Test 4: set linebreak list listchars and concealing #define ABCDE>-->---1 #define >CDEF>-->---1 @@ -187,7 +189,7 @@ describe('linebreak', function() #define >_CONSOLE>---------->---2 #define >_FILE_AND_CONSOLE>---------3 bbeeeeee ; some text - + Test 5: set linebreak list listchars and concealing part2 eeeeee>--->-;>some text Test 6: Screenattributes for comment @@ -196,10 +198,10 @@ describe('linebreak', function() Attribut 0 and 1 and 3 and 5 are different! Test 8: set linebreak with visual block mode and v_b_A and selection=exclusive and multibyte char long line: foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar TARGETÃx' at end - + a b c a b c - + Test 9: a multibyte sign and colorcolumn ¶ +a b c¶ diff --git a/test/functional/legacy/marks_spec.lua b/test/functional/legacy/marks_spec.lua index bcec179ca2..470ea49652 100644 --- a/test/functional/legacy/marks_spec.lua +++ b/test/functional/legacy/marks_spec.lua @@ -7,6 +7,7 @@ describe('marks', function() clear() end) + -- luacheck: ignore 621 (Indentation) it('restores a deleted mark after delete-undo-redo-undo', function() insert([[ diff --git a/test/functional/legacy/options_spec.lua b/test/functional/legacy/options_spec.lua index 4f4d4ceaf9..1db7afc7a7 100644 --- a/test/functional/legacy/options_spec.lua +++ b/test/functional/legacy/options_spec.lua @@ -21,7 +21,7 @@ describe('set', function() $put =&path]]) expect([[ - + foo,,bar]]) end) end) diff --git a/test/functional/legacy/packadd_spec.lua b/test/functional/legacy/packadd_spec.lua index c280888dda..2dfd36142b 100644 --- a/test/functional/legacy/packadd_spec.lua +++ b/test/functional/legacy/packadd_spec.lua @@ -83,6 +83,41 @@ describe('packadd', function() call assert_equal(new_rtp, &rtp) endfunc + func Test_packadd_symlink_dir() + if !has('unix') + return + endif + let top2_dir = s:topdir . '/Xdir2' + let real_dir = s:topdir . '/Xsym' + call mkdir(real_dir, 'p') + exec "silent! !ln -s Xsym" top2_dir + let &rtp = top2_dir . ',' . top2_dir . '/after' + let &packpath = &rtp + + let s:plugdir = top2_dir . '/pack/mine/opt/mytest' + call mkdir(s:plugdir . '/plugin', 'p') + + exe 'split ' . s:plugdir . '/plugin/test.vim' + call setline(1, 'let g:plugin_works = 44') + wq + let g:plugin_works = 0 + + packadd mytest + + " Must have been inserted in the middle, not at the end + call assert_true(&rtp =~ '/pack/mine/opt/mytest,') + call assert_equal(44, g:plugin_works) + + " No change when doing it again. + let rtp_before = &rtp + packadd mytest + call assert_equal(rtp_before, &rtp) + + set rtp& + let rtp = &rtp + exec "silent !rm" top2_dir + endfunc + func Test_packloadall() " plugin foo with an autoload directory let fooplugindir = &packpath . '/pack/mine/start/foo/plugin' @@ -137,9 +172,9 @@ describe('packadd', function() helptags ALL - let tags1 = readfile(docdir1 . '/tags') + let tags1 = readfile(docdir1 . '/tags') call assert_true(tags1[0] =~ 'look-here') - let tags2 = readfile(docdir2 . '/tags') + let tags2 = readfile(docdir2 . '/tags') call assert_true(tags2[0] =~ 'look-away') endfunc @@ -227,6 +262,11 @@ describe('packadd', function() expected_empty() end) + it('works with symlinks', function() + call('Test_packadd_symlink_dir') + expected_empty() + end) + it('works with :packloadall', function() call('Test_packloadall') expected_empty() diff --git a/test/functional/legacy/tagcase_spec.lua b/test/functional/legacy/tagcase_spec.lua index ed2876a375..9ca0e0009f 100644 --- a/test/functional/legacy/tagcase_spec.lua +++ b/test/functional/legacy/tagcase_spec.lua @@ -53,11 +53,11 @@ describe("'tagcase' option", function() -- Verify that the correct number of matching tags is found for all values of -- 'ignorecase' and global and local values 'tagcase', in all combinations. insert([[ - + Foo Bar foo - + end text]]) source([[ @@ -70,7 +70,7 @@ describe("'tagcase' option", function() endfor endfor endfor - + 1,/^end text$/d]]) expect([[ diff --git a/test/functional/legacy/utf8_spec.lua b/test/functional/legacy/utf8_spec.lua index 02de6ab735..5b93f25b24 100644 --- a/test/functional/legacy/utf8_spec.lua +++ b/test/functional/legacy/utf8_spec.lua @@ -54,6 +54,7 @@ describe('utf8', function() eq(1, eval('strchars("\\u20dd", 1)')) end) + -- luacheck: ignore 613 (Trailing whitespace in a string) it('customlist completion', function() source([[ function! CustomComplete1(lead, line, pos) diff --git a/test/functional/lua/utility_functions_spec.lua b/test/functional/lua/utility_functions_spec.lua index 9855a05e8c..d5756e134d 100644 --- a/test/functional/lua/utility_functions_spec.lua +++ b/test/functional/lua/utility_functions_spec.lua @@ -12,9 +12,10 @@ describe('vim.stricmp', function() -- length 2 (in bytes). -- Ⱥ: `tolower("Ⱥ")` is `ⱥ` which has length 2 while `Ⱥ` itself has -- length 3 (in bytes). - -- For some reason 'i' !=? 'İ' and 'ⱥ' !=? 'Ⱥ' on some systems. Also built-in - -- Neovim comparison (i.e. when there is no strcasecmp) works only on ASCII - -- characters. + -- + -- Note: 'i' !=? 'İ' and 'ⱥ' !=? 'Ⱥ' on some systems. + -- Note: Built-in Nvim comparison (on systems lacking `strcasecmp`) works + -- only on ASCII characters. it('works', function() eq(0, funcs.luaeval('vim.stricmp("a", "A")')) eq(0, funcs.luaeval('vim.stricmp("A", "a")')) diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index 3739540b09..b5374210e6 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -1,7 +1,30 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local plugin_helpers = require('test.functional.plugin.helpers') +local clear = helpers.clear +local curbuf_contents = helpers.curbuf_contents local command = helpers.command +local eq = helpers.eq + +describe(':checkhealth', function() + it("detects invalid $VIMRUNTIME", function() + clear({ + env={ VIMRUNTIME='bogus', }, + }) + local status, err = pcall(command, 'checkhealth') + eq(false, status) + eq('Invalid $VIMRUNTIME: bogus', string.match(err, 'Invalid.*')) + end) + it("detects invalid $VIM", function() + clear() + -- Do this after startup, otherwise it just breaks $VIMRUNTIME. + command("let $VIM='zub'") + command("checkhealth nvim") + eq("ERROR: $VIM is invalid: zub", + string.match(curbuf_contents(), "ERROR: $VIM .* zub")) + end) +end) describe('health.vim', function() before_each(function() @@ -13,7 +36,7 @@ describe('health.vim', function() command("set runtimepath+=test/functional/fixtures") end) - it("reports", function() + it("health#report_*()", function() helpers.source([[ let g:health_report = execute([ \ "call health#report_start('Check Bar')", @@ -30,41 +53,41 @@ describe('health.vim', function() ## Check Bar - - SUCCESS: Bar status - - SUCCESS: Other Bar status + - OK: Bar status + - OK: Other Bar status - WARNING: Zub ## Baz - WARNING: Zim - - SUGGESTIONS: + - ADVICE: - suggestion 1 - suggestion 2]]), result) end) - describe(":CheckHealth", function() + describe(":checkhealth", function() it("concatenates multiple reports", function() - command("CheckHealth success1 success2") + command("checkhealth success1 success2") helpers.expect([[ health#success1#check ======================================================================== ## report 1 - - SUCCESS: everything is fine + - OK: everything is fine ## report 2 - - SUCCESS: nothing to see here + - OK: nothing to see here health#success2#check ======================================================================== ## another 1 - - SUCCESS: ok + - OK: ok ]]) end) it("gracefully handles broken healthcheck", function() - command("CheckHealth broken") + command("checkhealth broken") helpers.expect([[ health#broken#check @@ -75,8 +98,38 @@ describe('health.vim', function() ]]) end) + it("highlights OK, ERROR", function() + local screen = Screen.new(72, 10) + screen:attach() + screen:set_default_attr_ids({ + Ok = { foreground = Screen.colors.Grey3, background = 6291200 }, + Error = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, + }) + screen:set_default_attr_ignore({ + Heading = { bold=true, foreground=Screen.colors.Magenta }, + Heading2 = { foreground = Screen.colors.SlateBlue }, + Bar = { foreground=Screen.colors.Purple }, + Bullet = { bold=true, foreground=Screen.colors.Brown }, + }) + command("checkhealth foo success1") + command("1tabclose") + command("set laststatus=0") + screen:expect([[ + ^ | + health#foo#check | + ========================================================================| + - {Error:ERROR:} No healthcheck found for "foo" plugin. | + | + health#success1#check | + ========================================================================| + ## report 1 | + - {Ok:OK:} everything is fine | + | + ]]) + end) + it("gracefully handles invalid healthcheck", function() - command("CheckHealth non_existent_healthcheck") + command("checkhealth non_existent_healthcheck") helpers.expect([[ health#non_existent_healthcheck#check diff --git a/test/functional/provider/ruby_spec.lua b/test/functional/provider/ruby_spec.lua index 9f5ef3b3fc..c70f90da1c 100644 --- a/test/functional/provider/ruby_spec.lua +++ b/test/functional/provider/ruby_spec.lua @@ -16,7 +16,7 @@ do clear() if missing_provider('ruby') then pending( - "Cannot find the neovim RubyGem. Try :CheckHealth", + "Cannot find the neovim RubyGem. Try :checkhealth", function() end) return end diff --git a/test/functional/shada/compatibility_spec.lua b/test/functional/shada/compatibility_spec.lua index 1287ac010c..a5ef60d91f 100644 --- a/test/functional/shada/compatibility_spec.lua +++ b/test/functional/shada/compatibility_spec.lua @@ -10,6 +10,13 @@ local read_shada_file = shada_helpers.read_shada_file local wshada, sdrcmd, shada_fname = get_shada_rw('Xtest-functional-shada-compatibility.shada') +local mock_file_path = '/a/b/' +local mock_file_path2 = '/d/e/' +if helpers.iswin() then + mock_file_path = 'C:/a/' + mock_file_path2 = 'C:/d/' +end + describe('ShaDa forward compatibility support code', function() before_each(reset) after_each(function() @@ -114,14 +121,14 @@ describe('ShaDa forward compatibility support code', function() funcs.garbagecollect(1) end) - for _, v in ipairs({{name='global mark', mpack='\007\001\018\131\162mX\195\161f\196\006/a/b/c\161nA'}, - {name='jump', mpack='\008\001\018\131\162mX\195\161f\196\006/a/b/c\161l\002'}, - {name='local mark', mpack='\010\001\018\131\162mX\195\161f\196\006/a/b/c\161na'}, - {name='change', mpack='\011\001\015\130\162mX\195\161f\196\006/a/b/c'}, + for _, v in ipairs({{name='global mark', mpack='\007\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161nA'}, + {name='jump', mpack='\008\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l\002'}, + {name='local mark', mpack='\010\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161na'}, + {name='change', mpack='\011\001\015\130\162mX\195\161f\196\006' .. mock_file_path .. 'c'}, }) do it('works with ' .. v.name .. ' item with BOOL unknown (mX) key value', function() - nvim_command('silent noautocmd edit /a/b/c') - eq('/a/b/c', funcs.bufname('%')) + nvim_command('silent noautocmd edit ' .. mock_file_path .. 'c') + eq('' .. mock_file_path .. 'c', funcs.bufname('%')) funcs.setline('.', {'1', '2', '3'}) wshada(v.mpack) eq(0, exc_exec(sdrcmd(true))) @@ -159,12 +166,12 @@ describe('ShaDa forward compatibility support code', function() if v.name == 'global mark' or v.name == 'local mark' then it('works with ' .. v.name .. ' item with <C-a> name', function() - nvim_command('silent noautocmd edit /a/b/c') - eq('/a/b/c', funcs.bufname('%')) + nvim_command('silent noautocmd edit ' .. mock_file_path .. 'c') + eq('' .. mock_file_path .. 'c', funcs.bufname('%')) funcs.setline('.', {'1', '2', '3'}) wshada(v.mpack:gsub('n.$', 'n\001') .. v.mpack:gsub('n.$', 'n\002') - .. v.mpack:gsub('n.$', 'n\003'):gsub('/a/b/c', '/d/e/f')) + .. v.mpack:gsub('n.$', 'n\003'):gsub('' .. mock_file_path .. 'c', '' .. mock_file_path2 .. 'f')) eq(0, exc_exec(sdrcmd(true))) nvim_command('wshada ' .. shada_fname) local found = 0 @@ -307,10 +314,10 @@ describe('ShaDa forward compatibility support code', function() it('works with buffer list item with BOOL unknown (bX) key', function() nvim_command('set shada+=%') - wshada('\009\000\016\145\130\161f\196\006/a/b/c\162bX\195') + wshada('\009\000\016\145\130\161f\196\006' .. mock_file_path .. 'c\162bX\195') eq(0, exc_exec(sdrcmd())) eq(2, funcs.bufnr('$')) - eq('/a/b/c', funcs.bufname(2)) + eq('' .. mock_file_path .. 'c', funcs.bufname(2)) os.remove(shada_fname) nvim_command('wshada ' .. shada_fname) local found = false diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua index 25c73b99eb..7a15c8908b 100644 --- a/test/functional/shada/merging_spec.lua +++ b/test/functional/shada/merging_spec.lua @@ -13,6 +13,11 @@ local read_shada_file = shada_helpers.read_shada_file local wshada, sdrcmd, shada_fname = get_shada_rw('Xtest-functional-shada-merging.shada') +local mock_file_path = '/a/b/' +if helpers.iswin() then + mock_file_path = 'C:/a/' +end + describe('ShaDa history merging code', function() before_each(reset) after_each(function() @@ -512,9 +517,9 @@ describe('ShaDa marks support code', function() it('uses last A mark with gt timestamp from instance when reading', function() - wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/-\161nA') + wshada('\007\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. '-\161nA') eq(0, exc_exec(sdrcmd())) - wshada('\007\000\018\131\162mX\195\161f\196\006/a/b/?\161nA') + wshada('\007\000\018\131\162mX\195\161f\196\006' .. mock_file_path .. '?\161nA') eq(0, exc_exec(sdrcmd())) nvim_command('normal! `A') eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) @@ -522,9 +527,9 @@ describe('ShaDa marks support code', function() it('uses last A mark with gt timestamp from file when reading with !', function() - wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/-\161nA') + wshada('\007\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. '-\161nA') eq(0, exc_exec(sdrcmd())) - wshada('\007\000\018\131\162mX\195\161f\196\006/a/b/?\161nA') + wshada('\007\000\018\131\162mX\195\161f\196\006' .. mock_file_path .. '?\161nA') eq(0, exc_exec(sdrcmd(true))) nvim_command('normal! `A') eq('?', funcs.fnamemodify(curbufmeths.get_name(), ':t')) @@ -532,9 +537,9 @@ describe('ShaDa marks support code', function() it('uses last A mark with eq timestamp from instance when reading', function() - wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/-\161nA') + wshada('\007\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. '-\161nA') eq(0, exc_exec(sdrcmd())) - wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/?\161nA') + wshada('\007\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. '?\161nA') eq(0, exc_exec(sdrcmd())) nvim_command('normal! `A') eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) @@ -542,9 +547,9 @@ describe('ShaDa marks support code', function() it('uses last A mark with gt timestamp from file when reading', function() - wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/-\161nA') + 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/a/b/?\161nA') + wshada('\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. '?\161nA') eq(0, exc_exec(sdrcmd())) nvim_command('normal! `A') eq('?', funcs.fnamemodify(curbufmeths.get_name(), ':t')) @@ -552,15 +557,15 @@ describe('ShaDa marks support code', function() it('uses last A mark with gt timestamp from instance when writing', function() - wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/-\161nA') + wshada('\007\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. '-\161nA') eq(0, exc_exec(sdrcmd())) - wshada('\007\000\018\131\162mX\195\161f\196\006/a/b/?\161nA') + wshada('\007\000\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 for _, v in ipairs(read_shada_file(shada_fname)) do - if v.type == 7 and v.value.f == '/a/b/-' then + if v.type == 7 and v.value.f == '' .. mock_file_path .. '-' then found = found + 1 end end @@ -569,15 +574,15 @@ describe('ShaDa marks support code', function() it('uses last A mark with eq timestamp from instance when writing', function() - wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/-\161nA') + wshada('\007\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. '-\161nA') eq(0, exc_exec(sdrcmd())) - wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/?\161nA') + wshada('\007\001\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 for _, v in ipairs(read_shada_file(shada_fname)) do - if v.type == 7 and v.value.f == '/a/b/-' then + if v.type == 7 and v.value.f == mock_file_path .. '-' then found = found + 1 end end @@ -586,15 +591,15 @@ describe('ShaDa marks support code', function() it('uses last A mark with gt timestamp from file when writing', function() - wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/-\161nA') + 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/a/b/?\161nA') + 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 for _, v in ipairs(read_shada_file(shada_fname)) do - if v.type == 7 and v.value.f == '/a/b/?' then + if v.type == 7 and v.value.f == '' .. mock_file_path .. '?' then found = found + 1 end end @@ -603,11 +608,11 @@ describe('ShaDa marks support code', function() it('uses last a mark with gt timestamp from instance when reading', function() - nvim_command('edit /a/b/-') + nvim_command('edit ' .. mock_file_path .. '-') funcs.setline(1, {'-', '?'}) - wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') + wshada('\010\001\017\131\161l\001\161f\196\006' .. mock_file_path .. '-\161na') eq(0, exc_exec(sdrcmd())) - wshada('\010\000\017\131\161l\002\161f\196\006/a/b/-\161na') + wshada('\010\000\017\131\161l\002\161f\196\006' .. mock_file_path .. '-\161na') eq(0, exc_exec(sdrcmd())) nvim_command('normal! `a') eq('-', funcs.getline('.')) @@ -615,11 +620,11 @@ describe('ShaDa marks support code', function() it('uses last a mark with gt timestamp from file when reading with !', function() - nvim_command('edit /a/b/-') + nvim_command('edit ' .. mock_file_path .. '-') funcs.setline(1, {'-', '?'}) - wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') + wshada('\010\001\017\131\161l\001\161f\196\006' .. mock_file_path .. '-\161na') eq(0, exc_exec(sdrcmd())) - wshada('\010\000\017\131\161l\002\161f\196\006/a/b/-\161na') + wshada('\010\000\017\131\161l\002\161f\196\006' .. mock_file_path .. '-\161na') eq(0, exc_exec(sdrcmd(true))) nvim_command('normal! `a') eq('?', funcs.getline('.')) @@ -627,11 +632,11 @@ describe('ShaDa marks support code', function() it('uses last a mark with eq timestamp from instance when reading', function() - nvim_command('edit /a/b/-') + nvim_command('edit ' .. mock_file_path .. '-') funcs.setline(1, {'-', '?'}) - wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') + wshada('\010\001\017\131\161l\001\161f\196\006' .. mock_file_path .. '-\161na') eq(0, exc_exec(sdrcmd())) - wshada('\010\001\017\131\161l\002\161f\196\006/a/b/-\161na') + wshada('\010\001\017\131\161l\002\161f\196\006' .. mock_file_path .. '-\161na') eq(0, exc_exec(sdrcmd())) nvim_command('normal! `a') eq('-', funcs.getline('.')) @@ -639,11 +644,11 @@ describe('ShaDa marks support code', function() it('uses last a mark with gt timestamp from file when reading', function() - nvim_command('edit /a/b/-') + nvim_command('edit ' .. mock_file_path .. '-') funcs.setline(1, {'-', '?'}) - wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') + wshada('\010\001\017\131\161l\001\161f\196\006' .. mock_file_path .. '-\161na') eq(0, exc_exec(sdrcmd())) - wshada('\010\002\017\131\161l\002\161f\196\006/a/b/-\161na') + wshada('\010\002\017\131\161l\002\161f\196\006' .. mock_file_path .. '-\161na') eq(0, exc_exec(sdrcmd())) nvim_command('normal! `a') eq('?', funcs.getline('.')) @@ -651,17 +656,17 @@ describe('ShaDa marks support code', function() it('uses last a mark with gt timestamp from instance when writing', function() - nvim_command('edit /a/b/-') + nvim_command('edit ' .. mock_file_path .. '-') funcs.setline(1, {'-', '?'}) - wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') + wshada('\010\001\017\131\161l\001\161f\196\006' .. mock_file_path .. '-\161na') eq(0, exc_exec(sdrcmd())) - wshada('\010\000\017\131\161l\002\161f\196\006/a/b/-\161na') + wshada('\010\000\017\131\161l\002\161f\196\006' .. mock_file_path .. '-\161na') nvim_command('normal! `a') eq('-', funcs.getline('.')) eq(0, exc_exec('wshada ' .. shada_fname)) local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do - if v.type == 10 and v.value.f == '/a/b/-' and v.value.n == ('a'):byte() then + if v.type == 10 and v.value.f == '' .. mock_file_path .. '-' and v.value.n == ('a'):byte() then eq(true, v.value.l == 1 or v.value.l == nil) found = found + 1 end @@ -671,17 +676,17 @@ describe('ShaDa marks support code', function() it('uses last a mark with eq timestamp from instance when writing', function() - nvim_command('edit /a/b/-') + nvim_command('edit ' .. mock_file_path .. '-') funcs.setline(1, {'-', '?'}) - wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') + wshada('\010\001\017\131\161l\001\161f\196\006' .. mock_file_path .. '-\161na') eq(0, exc_exec(sdrcmd())) - wshada('\010\001\017\131\161l\002\161f\196\006/a/b/-\161na') + wshada('\010\001\017\131\161l\002\161f\196\006' .. mock_file_path .. '-\161na') nvim_command('normal! `a') eq('-', funcs.getline('.')) eq(0, exc_exec('wshada ' .. shada_fname)) local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do - if v.type == 10 and v.value.f == '/a/b/-' and v.value.n == ('a'):byte() then + if v.type == 10 and v.value.f == '' .. mock_file_path .. '-' and v.value.n == ('a'):byte() then eq(true, v.value.l == 1 or v.value.l == nil) found = found + 1 end @@ -691,17 +696,17 @@ describe('ShaDa marks support code', function() it('uses last a mark with gt timestamp from file when writing', function() - nvim_command('edit /a/b/-') + nvim_command('edit ' .. mock_file_path .. '-') funcs.setline(1, {'-', '?'}) - wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') + wshada('\010\001\017\131\161l\001\161f\196\006' .. mock_file_path .. '-\161na') eq(0, exc_exec(sdrcmd())) - wshada('\010\002\017\131\161l\002\161f\196\006/a/b/-\161na') + wshada('\010\002\017\131\161l\002\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 for _, v in ipairs(read_shada_file(shada_fname)) do - if v.type == 10 and v.value.f == '/a/b/-' and v.value.n == ('a'):byte() then + if v.type == 10 and v.value.f == '' .. mock_file_path .. '-' and v.value.n == ('a'):byte() then eq(2, v.value.l) found = found + 1 end @@ -813,41 +818,41 @@ describe('ShaDa jumps support code', function() end) it('merges jumps when reading', function() - wshada('\008\001\018\131\162mX\195\161f\196\006/a/b/c\161l\002' - .. '\008\004\018\131\162mX\195\161f\196\006/a/b/d\161l\002' - .. '\008\007\018\131\162mX\195\161f\196\006/a/b/e\161l\002') + wshada('\008\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l\002' + .. '\008\004\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'd\161l\002' + .. '\008\007\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'e\161l\002') eq(0, exc_exec(sdrcmd())) - wshada('\008\001\018\131\162mX\195\161f\196\006/a/b/c\161l\002' - .. '\008\004\018\131\162mX\195\161f\196\006/a/b/d\161l\003' - .. '\008\007\018\131\162mX\195\161f\196\006/a/b/f\161l\002') + wshada('\008\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l\002' + .. '\008\004\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'd\161l\003' + .. '\008\007\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'f\161l\002') eq(0, exc_exec(sdrcmd())) eq('', curbufmeths.get_name()) eq('\n' .. ' jump line col file/text\n' - .. ' 6 2 0 /a/b/c\n' - .. ' 5 2 0 /a/b/d\n' - .. ' 4 3 0 /a/b/d\n' - .. ' 3 2 0 /a/b/e\n' - .. ' 2 2 0 /a/b/f\n' + .. ' 6 2 0 ' .. mock_file_path .. 'c\n' + .. ' 5 2 0 ' .. mock_file_path .. 'd\n' + .. ' 4 3 0 ' .. mock_file_path .. 'd\n' + .. ' 3 2 0 ' .. mock_file_path .. 'e\n' + .. ' 2 2 0 ' .. mock_file_path .. 'f\n' .. ' 1 1 0 \n' .. '>', redir_exec('jumps')) end) it('merges jumps when writing', function() - wshada('\008\001\018\131\162mX\195\161f\196\006/a/b/c\161l\002' - .. '\008\004\018\131\162mX\195\161f\196\006/a/b/d\161l\002' - .. '\008\007\018\131\162mX\195\161f\196\006/a/b/e\161l\002') + wshada('\008\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l\002' + .. '\008\004\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'd\161l\002' + .. '\008\007\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'e\161l\002') eq(0, exc_exec(sdrcmd())) - wshada('\008\001\018\131\162mX\195\161f\196\006/a/b/c\161l\002' - .. '\008\004\018\131\162mX\195\161f\196\006/a/b/d\161l\003' - .. '\008\007\018\131\162mX\195\161f\196\006/a/b/f\161l\002') + wshada('\008\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l\002' + .. '\008\004\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'd\161l\003' + .. '\008\007\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'f\161l\002') eq(0, exc_exec('wshada ' .. shada_fname)) local jumps = { - {file='/a/b/c', line=2}, - {file='/a/b/d', line=2}, - {file='/a/b/d', line=3}, - {file='/a/b/e', line=2}, - {file='/a/b/f', line=2}, + {file='' .. mock_file_path .. 'c', line=2}, + {file='' .. mock_file_path .. 'd', line=2}, + {file='' .. mock_file_path .. 'd', line=3}, + {file='' .. mock_file_path .. 'e', line=2}, + {file='' .. mock_file_path .. 'f', line=2}, } local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do @@ -864,9 +869,9 @@ describe('ShaDa jumps support code', function() local jumps = {} local shada = '' for i = 1,100 do - shada = shada .. ('\008%c\018\131\162mX\195\161f\196\006/a/b/c\161l%c' + shada = shada .. ('\008%c\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l%c' ):format(i, i) - jumps[i] = {file='/a/b/c', line=i} + jumps[i] = {file='' .. mock_file_path .. 'c', line=i} end wshada(shada) eq(0, exc_exec(sdrcmd())) @@ -874,9 +879,9 @@ describe('ShaDa jumps support code', function() for i = 1,101 do local t = i * 2 shada = shada .. ( - '\008\204%c\019\131\162mX\195\161f\196\006/a/b/c\161l\204%c' + '\008\204%c\019\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l\204%c' ):format(t, t) - jumps[(t > #jumps + 1) and (#jumps + 1) or t] = {file='/a/b/c', line=t} + jumps[(t > #jumps + 1) and (#jumps + 1) or t] = {file='' .. mock_file_path .. 'c', line=t} end wshada(shada) eq(0, exc_exec('wshada ' .. shada_fname)) @@ -904,15 +909,15 @@ describe('ShaDa changes support code', function() end) it('merges changes when reading', function() - nvim_command('edit /a/b/c') + nvim_command('edit ' .. mock_file_path .. 'c') nvim_command('keepjumps call setline(1, range(7))') - wshada('\011\001\018\131\162mX\195\161f\196\006/a/b/c\161l\001' - .. '\011\004\018\131\162mX\195\161f\196\006/a/b/c\161l\002' - .. '\011\007\018\131\162mX\195\161f\196\006/a/b/c\161l\003') + wshada('\011\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l\001' + .. '\011\004\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l\002' + .. '\011\007\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l\003') eq(0, exc_exec(sdrcmd())) - wshada('\011\001\018\131\162mX\194\161f\196\006/a/b/c\161l\001' - .. '\011\004\018\131\162mX\195\161f\196\006/a/b/c\161l\005' - .. '\011\008\018\131\162mX\195\161f\196\006/a/b/c\161l\004') + wshada('\011\001\018\131\162mX\194\161f\196\006' .. mock_file_path .. 'c\161l\001' + .. '\011\004\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l\005' + .. '\011\008\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l\004') eq(0, exc_exec(sdrcmd())) eq('\n' .. 'change line col text\n' @@ -925,15 +930,15 @@ describe('ShaDa changes support code', function() end) it('merges changes when writing', function() - nvim_command('edit /a/b/c') + nvim_command('edit ' .. mock_file_path .. 'c') nvim_command('keepjumps call setline(1, range(7))') - wshada('\011\001\018\131\162mX\195\161f\196\006/a/b/c\161l\001' - .. '\011\004\018\131\162mX\195\161f\196\006/a/b/c\161l\002' - .. '\011\007\018\131\162mX\195\161f\196\006/a/b/c\161l\003') + wshada('\011\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l\001' + .. '\011\004\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l\002' + .. '\011\007\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l\003') eq(0, exc_exec(sdrcmd())) - wshada('\011\001\018\131\162mX\194\161f\196\006/a/b/c\161l\001' - .. '\011\004\018\131\162mX\195\161f\196\006/a/b/c\161l\005' - .. '\011\008\018\131\162mX\195\161f\196\006/a/b/c\161l\004') + wshada('\011\001\018\131\162mX\194\161f\196\006' .. mock_file_path .. 'c\161l\001' + .. '\011\004\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l\005' + .. '\011\008\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l\004') eq(0, exc_exec('wshada ' .. shada_fname)) local changes = { {line=1}, @@ -944,7 +949,7 @@ describe('ShaDa changes support code', function() } local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do - if v.type == 11 and v.value.f == '/a/b/c' then + if v.type == 11 and v.value.f == '' .. mock_file_path .. 'c' then found = found + 1 eq(changes[found].line, v.value.l or 1) end @@ -953,12 +958,12 @@ describe('ShaDa changes support code', function() end) it('merges JUMPLISTSIZE changes when writing', function() - nvim_command('edit /a/b/c') + nvim_command('edit ' .. mock_file_path .. 'c') nvim_command('keepjumps call setline(1, range(202))') local changes = {} local shada = '' for i = 1,100 do - shada = shada .. ('\011%c\018\131\162mX\195\161f\196\006/a/b/c\161l%c' + shada = shada .. ('\011%c\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l%c' ):format(i, i) changes[i] = {line=i} end @@ -968,7 +973,7 @@ describe('ShaDa changes support code', function() for i = 1,101 do local t = i * 2 shada = shada .. ( - '\011\204%c\019\131\162mX\195\161f\196\006/a/b/c\161l\204%c' + '\011\204%c\019\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l\204%c' ):format(t, t) changes[(t > #changes + 1) and (#changes + 1) or t] = {line=t} end @@ -980,7 +985,7 @@ describe('ShaDa changes support code', function() end local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do - if v.type == 11 and v.value.f == '/a/b/c' then + if v.type == 11 and v.value.f == '' .. mock_file_path .. 'c' then found = found + 1 eq(changes[found].line, v.value.l) end @@ -990,20 +995,20 @@ describe('ShaDa changes support code', function() it('merges JUMPLISTSIZE changes when writing, with new items between old', function() - nvim_command('edit /a/b/c') + nvim_command('edit ' .. mock_file_path .. 'c') nvim_command('keepjumps call setline(1, range(202))') local shada = '' for i = 1,101 do local t = i * 2 shada = shada .. ( - '\011\204%c\019\131\162mX\195\161f\196\006/a/b/c\161l\204%c' + '\011\204%c\019\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l\204%c' ):format(t, t) end wshada(shada) eq(0, exc_exec(sdrcmd())) shada = '' for i = 1,100 do - shada = shada .. ('\011%c\018\131\162mX\195\161f\196\006/a/b/c\161l%c' + shada = shada .. ('\011%c\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161l%c' ):format(i, i) end local changes = {} @@ -1022,7 +1027,7 @@ describe('ShaDa changes support code', function() end local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do - if v.type == 11 and v.value.f == '/a/b/c' then + if v.type == 11 and v.value.f == '' .. mock_file_path .. 'c' then found = found + 1 eq(changes[found].line, v.value.l) end @@ -1030,3 +1035,5 @@ describe('ShaDa changes support code', function() eq(found, 100) end) end) + +-- vim: ts=2 sw=2 diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 48b8512bf0..4ce33fef7b 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -6,8 +6,6 @@ local eval, feed_command, source = helpers.eval, helpers.feed_command, helpers.s local eq, neq = helpers.eq, helpers.neq local write_file = helpers.write_file -if helpers.pending_win32(pending) then return end - describe('terminal buffer', function() local screen @@ -160,6 +158,7 @@ describe('terminal buffer', function() end) it('handles loss of focus gracefully', function() + if helpers.pending_win32(pending) then return end -- Change the statusline to avoid printing the file name, which varies. nvim('set_option', 'statusline', '==========') feed_command('set laststatus=0') @@ -205,7 +204,6 @@ describe('terminal buffer', function() end) describe('No heap-buffer-overflow when using', function() - local testfilename = 'Xtestfile-functional-terminal-buffers_spec' before_each(function() diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua index 84d0322f12..d49f1bfc23 100644 --- a/test/functional/terminal/cursor_spec.lua +++ b/test/functional/terminal/cursor_spec.lua @@ -7,8 +7,6 @@ local feed_command = helpers.feed_command local hide_cursor = thelpers.hide_cursor local show_cursor = thelpers.show_cursor -if helpers.pending_win32(pending) then return end - describe('terminal cursor', function() local screen diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index be0fd9f8ff..e015df10db 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -3,10 +3,11 @@ local Screen = require('test.functional.ui.screen') local clear, wait, nvim = helpers.clear, helpers.wait, helpers.nvim local nvim_dir, source, eq = helpers.nvim_dir, helpers.source, helpers.eq local feed_command, eval = helpers.feed_command, helpers.eval - -if helpers.pending_win32(pending) then return end +local retry = helpers.retry +local iswin = helpers.iswin describe(':terminal', function() + if helpers.pending_win32(pending) then return end local screen before_each(function() @@ -70,19 +71,19 @@ describe(':terminal (with fake shell)', function() it('with no argument, acts like termopen()', function() terminal_with_fake_shell() - wait() + retry(3, 4 * screen.timeout, function() screen:expect([[ - ready $ | + ^ready $ | [Process exited 0] | | - -- TERMINAL -- | + :terminal | ]]) + end) end) it("with no argument, and 'shell' is set to empty string", function() nvim('set_option', 'shell', '') terminal_with_fake_shell() - wait() screen:expect([[ ^ | ~ | @@ -94,46 +95,42 @@ describe(':terminal (with fake shell)', function() it("with no argument, but 'shell' has arguments, acts like termopen()", function() nvim('set_option', 'shell', nvim_dir..'/shell-test -t jeff') terminal_with_fake_shell() - wait() screen:expect([[ - jeff $ | + ^jeff $ | [Process exited 0] | | - -- TERMINAL -- | + :terminal | ]]) end) it('executes a given command through the shell', function() terminal_with_fake_shell('echo hi') - wait() screen:expect([[ - ready $ echo hi | + ^ready $ echo hi | | [Process exited 0] | - -- TERMINAL -- | + :terminal echo hi | ]]) end) it("executes a given command through the shell, when 'shell' has arguments", function() nvim('set_option', 'shell', nvim_dir..'/shell-test -t jeff') terminal_with_fake_shell('echo hi') - wait() screen:expect([[ - jeff $ echo hi | + ^jeff $ echo hi | | [Process exited 0] | - -- TERMINAL -- | + :terminal echo hi | ]]) end) it('allows quotes and slashes', function() terminal_with_fake_shell([[echo 'hello' \ "world"]]) - wait() screen:expect([[ - ready $ echo 'hello' \ "world" | + ^ready $ echo 'hello' \ "world" | | [Process exited 0] | - -- TERMINAL -- | + :terminal echo 'hello' \ "world" | ]]) end) @@ -164,27 +161,29 @@ describe(':terminal (with fake shell)', function() it('works with :find', function() terminal_with_fake_shell() - wait() screen:expect([[ - ready $ | + ^ready $ | [Process exited 0] | | - -- TERMINAL -- | + :terminal | ]]) eq('term://', string.match(eval('bufname("%")'), "^term://")) helpers.feed([[<C-\><C-N>]]) feed_command([[find */shadacat.py]]) - eq('scripts/shadacat.py', eval('bufname("%")')) + if iswin() then + eq('scripts\\shadacat.py', eval('bufname("%")')) + else + eq('scripts/shadacat.py', eval('bufname("%")')) + end end) it('works with gf', function() terminal_with_fake_shell([[echo "scripts/shadacat.py"]]) - wait() screen:expect([[ - ready $ echo "scripts/shadacat.py" | + ^ready $ echo "scripts/shadacat.py" | | [Process exited 0] | - -- TERMINAL -- | + :terminal echo "scripts/shadacat.py" | ]]) helpers.feed([[<C-\><C-N>]]) eq('term://', string.match(eval('bufname("%")'), "^term://")) diff --git a/test/functional/terminal/helpers.lua b/test/functional/terminal/helpers.lua index 3b04d17705..bd24b9785d 100644 --- a/test/functional/terminal/helpers.lua +++ b/test/functional/terminal/helpers.lua @@ -33,7 +33,6 @@ local function disable_mouse() feed_termcode('[?1002l') end local default_command = '["'..nvim_dir..'/tty-test'..'"]' - local function screen_setup(extra_rows, command, cols) extra_rows = extra_rows and extra_rows or 0 command = command and command or default_command diff --git a/test/functional/terminal/highlight_spec.lua b/test/functional/terminal/highlight_spec.lua index bb40770235..fddc0bbb71 100644 --- a/test/functional/terminal/highlight_spec.lua +++ b/test/functional/terminal/highlight_spec.lua @@ -5,8 +5,6 @@ local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim local nvim_dir, command = helpers.nvim_dir, helpers.command local eq, eval = helpers.eq, helpers.eval -if helpers.pending_win32(pending) then return end - describe('terminal window highlighting', function() local screen @@ -55,6 +53,7 @@ describe('terminal window highlighting', function() end) local function pass_attrs() + if helpers.pending_win32(pending) then return end screen:expect(sub([[ tty ready | {NUM:text}text{10: } | @@ -69,6 +68,7 @@ describe('terminal window highlighting', function() it('will pass the corresponding attributes', pass_attrs) it('will pass the corresponding attributes on scrollback', function() + if helpers.pending_win32(pending) then return end pass_attrs() local lines = {} for i = 1, 8 do @@ -145,6 +145,7 @@ describe('terminal window highlighting with custom palette', function() end) it('will use the custom color', function() + if helpers.pending_win32(pending) then return end thelpers.set_fg(3) thelpers.feed_data('text') thelpers.clear_attrs() diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua index 9239c2ad31..29c62d7be7 100644 --- a/test/functional/terminal/mouse_spec.lua +++ b/test/functional/terminal/mouse_spec.lua @@ -4,8 +4,6 @@ local clear, eq, eval = helpers.clear, helpers.eq, helpers.eval local feed, nvim = helpers.feed, helpers.nvim local feed_data = thelpers.feed_data -if helpers.pending_win32(pending) then return end - describe('terminal mouse', function() local screen @@ -67,6 +65,7 @@ describe('terminal mouse', function() end) it('will forward mouse clicks to the program', function() + if helpers.pending_win32(pending) then return end feed('<LeftMouse><1,2>') screen:expect([[ line27 | @@ -80,6 +79,7 @@ describe('terminal mouse', function() end) it('will forward mouse scroll to the program', function() + if helpers.pending_win32(pending) then return end feed('<ScrollWheelUp><0,0>') screen:expect([[ line27 | @@ -94,6 +94,7 @@ describe('terminal mouse', function() end) describe('with a split window and other buffer', function() + if helpers.pending_win32(pending) then return end before_each(function() feed('<c-\\><c-n>:vsp<cr>') screen:expect([[ diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index 05f81295c2..af9b414311 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -3,6 +3,7 @@ local helpers = require('test.functional.helpers')(after_each) local thelpers = require('test.functional.terminal.helpers') local clear, eq, curbuf = helpers.clear, helpers.eq, helpers.curbuf local feed, nvim_dir, feed_command = helpers.feed, helpers.nvim_dir, helpers.feed_command +local iswin = helpers.iswin local eval = helpers.eval local command = helpers.command local wait = helpers.wait @@ -11,8 +12,6 @@ local curbufmeths = helpers.curbufmeths local nvim = helpers.nvim local feed_data = thelpers.feed_data -if helpers.pending_win32(pending) then return end - describe('terminal scrollback', function() local screen @@ -58,7 +57,7 @@ describe('terminal scrollback', function() end) end) - describe('with the cursor at the last row', function() + describe('with cursor at last row', function() before_each(function() feed_data({'line1', 'line2', 'line3', 'line4', ''}) screen:expect([[ @@ -139,16 +138,18 @@ describe('terminal scrollback', function() end) - describe('and the height is decreased by 1', function() + describe('and height decreased by 1', function() + if helpers.pending_win32(pending) then return end local function will_hide_top_line() - screen:try_resize(screen._width, screen._height - 1) + feed([[<C-\><C-N>:]]) -- Go to cmdline-mode, so cursor is at bottom. + screen:try_resize(screen._width - 2, screen._height - 1) screen:expect([[ - line2 | - line3 | - line4 | - rows: 5, cols: 30 | - {1: } | - {3:-- TERMINAL --} | + line2 | + line3 | + line4 | + rows: 5, cols: 28 | + {2: } | + :^ | ]]) end @@ -157,23 +158,23 @@ describe('terminal scrollback', function() describe('and then decreased by 2', function() before_each(function() will_hide_top_line() - screen:try_resize(screen._width, screen._height - 2) + screen:try_resize(screen._width - 2, screen._height - 2) end) it('will hide the top 3 lines', function() screen:expect([[ - rows: 5, cols: 30 | - rows: 3, cols: 30 | - {1: } | - {3:-- TERMINAL --} | + rows: 5, cols: 28 | + rows: 3, cols: 26 | + {2: } | + :^ | ]]) eq(8, curbuf('line_count')) - feed('<c-\\><c-n>3k') + feed([[<C-\><C-N>3k]]) screen:expect([[ - ^line4 | - rows: 5, cols: 30 | - rows: 3, cols: 30 | - | + ^line4 | + rows: 5, cols: 28 | + rows: 3, cols: 26 | + | ]]) end) end) @@ -181,6 +182,11 @@ describe('terminal scrollback', function() end) describe('with empty lines after the cursor', function() + -- XXX: Can't test this reliably on Windows unless the cursor is _moved_ + -- by the resize. http://docs.libuv.org/en/v1.x/signal.html + -- See also: https://github.com/rprichard/winpty/issues/110 + if helpers.pending_win32(pending) then return end + describe('and the height is decreased by 2', function() before_each(function() screen:try_resize(screen._width, screen._height - 2) @@ -255,6 +261,10 @@ describe('terminal scrollback', function() end) describe('and the height is increased by 1', function() + -- XXX: Can't test this reliably on Windows unless the cursor is _moved_ + -- by the resize. http://docs.libuv.org/en/v1.x/signal.html + -- See also: https://github.com/rprichard/winpty/issues/110 + if helpers.pending_win32(pending) then return end local function pop_then_push() screen:try_resize(screen._width, screen._height + 1) screen:expect([[ @@ -384,10 +394,20 @@ describe("'scrollback' option", function() end it('set to 0 behaves as 1', function() - local screen = thelpers.screen_setup(nil, "['sh']", 30) + local screen + if iswin() then + screen = thelpers.screen_setup(nil, + "['powershell.exe', '-NoLogo', '-NoProfile', '-NoExit', '-Command', 'function global:prompt {return "..'"$"'.."}']", 30) + else + screen = thelpers.screen_setup(nil, "['sh']", 30) + end curbufmeths.set_option('scrollback', 0) - feed_data('for i in $(seq 1 30); do echo "line$i"; done\n') + if iswin() then + feed_data('for($i=1;$i -le 30;$i++){Write-Host \"line$i\"}\r') + else + feed_data('for i in $(seq 1 30); do echo "line$i"; done\n') + end screen:expect('line30 ', nil, nil, nil, true) retry(nil, nil, function() expect_lines(7) end) @@ -395,7 +415,13 @@ describe("'scrollback' option", function() end) it('deletes lines (only) if necessary', function() - local screen = thelpers.screen_setup(nil, "['sh']", 30) + local screen + if iswin() then + screen = thelpers.screen_setup(nil, + "['powershell.exe', '-NoLogo', '-NoProfile', '-NoExit', '-Command', 'function global:prompt {return "..'"$"'.."}']", 30) + else + screen = thelpers.screen_setup(nil, "['sh']", 30) + end curbufmeths.set_option('scrollback', 200) @@ -403,7 +429,11 @@ describe("'scrollback' option", function() screen:expect('$', nil, nil, nil, true) wait() - feed_data('for i in $(seq 1 30); do echo "line$i"; done\n') + if iswin() then + feed_data('for($i=1;$i -le 30;$i++){Write-Host \"line$i\"}\r') + else + feed_data('for i in $(seq 1 30); do echo "line$i"; done\n') + end screen:expect('line30 ', nil, nil, nil, true) @@ -416,7 +446,11 @@ describe("'scrollback' option", function() -- Terminal job data is received asynchronously, may happen before the -- 'scrollback' option is synchronized with the internal sb_buffer. command('sleep 100m') - feed_data('for i in $(seq 1 40); do echo "line$i"; done\n') + if iswin() then + feed_data('for($i=1;$i -le 40;$i++){Write-Host \"line$i\"}\r') + else + feed_data('for i in $(seq 1 40); do echo "line$i"; done\n') + end screen:expect('line40 ', nil, nil, nil, true) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 21b907c8f7..d36eb46e54 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -1,5 +1,6 @@ --- Some sanity checks for the TUI using the builtin terminal emulator --- as a simple way to send keys and assert screen state. +-- TUI acceptance tests. +-- Uses :terminal as a way to send keys and assert screen state. +local global_helpers = require('test.helpers') local helpers = require('test.functional.helpers')(after_each) local thelpers = require('test.functional.terminal.helpers') local feed_data = thelpers.feed_data @@ -194,7 +195,7 @@ describe('tui with non-tty file descriptors', function() end) end) -describe('tui focus event handling', function() +describe('tui FocusGained/FocusLost', function() local screen before_each(function() @@ -206,7 +207,8 @@ describe('tui focus event handling', function() feed_data("\034\016") -- CTRL-\ CTRL-N end) - it('can handle focus events in normal mode', function() + it('in normal-mode', function() + retry(2, 3 * screen.timeout, function() feed_data('\027[I') screen:expect([[ {1: } | @@ -228,11 +230,13 @@ describe('tui focus event handling', function() lost | {3:-- TERMINAL --} | ]]) + end) end) - it('can handle focus events in insert mode', function() + it('in insert-mode', function() feed_command('set noshowmode') feed_data('i') + retry(2, 3 * screen.timeout, function() feed_data('\027[I') screen:expect([[ {1: } | @@ -253,9 +257,12 @@ describe('tui focus event handling', function() lost | {3:-- TERMINAL --} | ]]) + end) end) - it('can handle focus events in cmdline mode', function() + -- During cmdline-mode we ignore :echo invoked by timers/events. + -- See commit: 5cc87d4dabd02167117be7a978b5c8faaa975419. + it('in cmdline-mode does NOT :echo', function() feed_data(':') feed_data('\027[I') screen:expect([[ @@ -264,7 +271,7 @@ describe('tui focus event handling', function() {4:~ }| {4:~ }| {5:[No Name] }| - g{1:a}ined | + :{1: } | {3:-- TERMINAL --} | ]]) feed_data('\027[O') @@ -274,21 +281,52 @@ describe('tui focus event handling', function() {4:~ }| {4:~ }| {5:[No Name] }| - l{1:o}st | + :{1: } | {3:-- TERMINAL --} | ]]) end) - it('can handle focus events in terminal mode', function() + it('in cmdline-mode', function() + -- Set up autocmds that modify the buffer, instead of just calling :echo. + -- This is how we can test handling of focus gained/lost during cmdline-mode. + -- See commit: 5cc87d4dabd02167117be7a978b5c8faaa975419. + feed_data(":autocmd!\n") + feed_data(":autocmd FocusLost * call append(line('$'), 'lost')\n") + feed_data(":autocmd FocusGained * call append(line('$'), 'gained')\n") + retry(2, 3 * screen.timeout, function() + -- Enter cmdline-mode. + feed_data(':') + screen:sleep(1) + -- Send focus lost/gained termcodes. + feed_data('\027[O') + feed_data('\027[I') + screen:sleep(1) + -- 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 --} | + ]]) + end) + end) + + it('in terminal-mode', function() feed_data(':set shell='..nvim_dir..'/shell-test\n') feed_data(':set noshowmode laststatus=0\n') retry(2, 3 * screen.timeout, function() feed_data(':terminal\n') + screen:sleep(1) feed_data('\027[I') screen:expect([[ - ready $ | - [Process exited 0]{1: } | + {1:r}eady $ | + [Process exited 0] | | | | @@ -297,8 +335,8 @@ describe('tui focus event handling', function() ]]) feed_data('\027[O') screen:expect([[ - ready $ | - [Process exited 0]{1: } | + {1:r}eady $ | + [Process exited 0] | | | | @@ -311,13 +349,30 @@ describe('tui focus event handling', function() feed_data(':bwipeout!\n') end) end) + + it('in press-enter prompt', function() + feed_data(":echom 'msg1'|echom 'msg2'|echom 'msg3'|echom 'msg4'|echom 'msg5'\n") + -- Execute :messages to provoke the press-enter prompt. + feed_data(":messages\n") + feed_data('\027[I') + feed_data('\027[I') + screen:expect([[ + msg1 | + msg2 | + msg3 | + msg4 | + msg5 | + {10:Press ENTER or type command to continue}{1: } | + {3:-- TERMINAL --} | + ]]) + end) end) -- These tests require `thelpers` because --headless/--embed -- does not initialize the TUI. describe("tui 't_Co' (terminal colors)", function() local screen - local is_freebsd = (helpers.eval("system('uname') =~? 'FreeBSD'") == 1) + local is_freebsd = (string.lower(global_helpers.uname()) == 'freebsd') local function assert_term_colors(term, colorterm, maxcolors) helpers.clear({env={TERM=term}, args={}}) diff --git a/test/functional/terminal/window_spec.lua b/test/functional/terminal/window_spec.lua index 888b1e1328..0f705cfe40 100644 --- a/test/functional/terminal/window_spec.lua +++ b/test/functional/terminal/window_spec.lua @@ -3,8 +3,6 @@ local thelpers = require('test.functional.terminal.helpers') local feed, clear = helpers.feed, helpers.clear local wait = helpers.wait -if helpers.pending_win32(pending) then return end - describe('terminal window', function() local screen diff --git a/test/functional/terminal/window_split_tab_spec.lua b/test/functional/terminal/window_split_tab_spec.lua index 4867e0d9fa..c5199f620e 100644 --- a/test/functional/terminal/window_split_tab_spec.lua +++ b/test/functional/terminal/window_split_tab_spec.lua @@ -4,8 +4,6 @@ local clear = helpers.clear local feed, nvim = helpers.feed, helpers.nvim local feed_command = helpers.feed_command -if helpers.pending_win32(pending) then return end - describe('terminal', function() local screen @@ -25,6 +23,7 @@ describe('terminal', function() end) it('resets its size when entering terminal window', function() + if helpers.pending_win32(pending) then return end feed('<c-\\><c-n>') feed_command('2split') screen:expect([[ @@ -69,31 +68,25 @@ describe('terminal', function() describe('when the screen is resized', function() it('will forward a resize request to the program', function() - screen:try_resize(screen._width + 3, screen._height + 5) + feed([[<C-\><C-N>:]]) -- Go to cmdline-mode, so cursor is at bottom. + screen:try_resize(screen._width - 3, screen._height - 2) screen:expect([[ - tty ready | - rows: 14, cols: 53 | - {1: } | - | - | - | - | - | - | - | - | - | - | - | - {3:-- TERMINAL --} | + tty ready | + rows: 7, cols: 47 | + {2: } | + | + | + | + | + :^ | ]]) - screen:try_resize(screen._width - 6, screen._height - 10) + screen:try_resize(screen._width - 6, screen._height - 3) screen:expect([[ - tty ready | - rows: 14, cols: 53 | - rows: 4, cols: 47 | - {1: } | - {3:-- TERMINAL --} | + tty ready | + rows: 7, cols: 47 | + rows: 4, cols: 41 | + {2: } | + :^ | ]]) end) end) diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua index e1e11203e0..2143c01139 100644 --- a/test/functional/ui/bufhl_spec.lua +++ b/test/functional/ui/bufhl_spec.lua @@ -24,7 +24,8 @@ describe('Buffer highlighting', function() [6] = {foreground = Screen.colors.DarkCyan}, -- Identifier [7] = {bold = true}, [8] = {underline = true, bold = true, foreground = Screen.colors.SlateBlue}, - [9] = {foreground = Screen.colors.SlateBlue, underline = true} + [9] = {foreground = Screen.colors.SlateBlue, underline = true}, + [10] = {foreground = Screen.colors.Red} }) curbuf = request('nvim_get_current_buf') end) @@ -255,4 +256,32 @@ describe('Buffer highlighting', function() | ]]) end) + + it('works with new syntax groups', function() + insert([[ + fancy code in a new fancy language]]) + add_hl(-1, "FancyLangItem", 0, 0, 5) + screen:expect([[ + fancy code in a new fancy languag^e | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + + command('hi FancyLangItem guifg=red') + screen:expect([[ + {10:fancy} code in a new fancy languag^e | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) end) diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua new file mode 100644 index 0000000000..d87ce72599 --- /dev/null +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -0,0 +1,893 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local eq = helpers.eq +local feed = helpers.feed +local clear = helpers.clear +local meths = helpers.meths +local funcs = helpers.funcs +local source = helpers.source +local dedent = helpers.dedent +local command = helpers.command +local curbufmeths = helpers.curbufmeths + +local screen + +-- Bug in input() handling: :redraw! will erase the whole prompt up until +-- user types something. It exists in Vim as well, so using `h<BS>` as +-- a workaround. +local function redraw_input() + feed('{REDRAW}h<BS>') +end + +before_each(function() + clear() + screen = Screen.new(40, 8) + screen:attach() + source([[ + highlight RBP1 guibg=Red + highlight RBP2 guibg=Yellow + highlight RBP3 guibg=Green + highlight RBP4 guibg=Blue + let g:NUM_LVLS = 4 + function Redraw() + redraw! + return '' + endfunction + let g:id = '' + cnoremap <expr> {REDRAW} Redraw() + function DoPrompt(do_return) abort + let id = g:id + let Cb = g:Nvim_color_input{g:id} + let out = input({'prompt': ':', 'highlight': Cb}) + let g:out{id} = out + return (a:do_return ? out : '') + endfunction + nnoremap <expr> {PROMPT} DoPrompt(0) + cnoremap <expr> {PROMPT} DoPrompt(1) + function RainBowParens(cmdline) + let ret = [] + let i = 0 + let lvl = 0 + while i < len(a:cmdline) + if a:cmdline[i] is# '(' + call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)]) + let lvl += 1 + elseif a:cmdline[i] is# ')' + let lvl -= 1 + call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)]) + endif + let i += 1 + endwhile + return ret + endfunction + function SplittedMultibyteStart(cmdline) + let ret = [] + let i = 0 + while i < len(a:cmdline) + let char = nr2char(char2nr(a:cmdline[i:])) + if a:cmdline[i:i + len(char) - 1] is# char + if len(char) > 1 + call add(ret, [i + 1, i + len(char), 'RBP2']) + endif + let i += len(char) + else + let i += 1 + endif + endwhile + return ret + endfunction + function SplittedMultibyteEnd(cmdline) + let ret = [] + let i = 0 + while i < len(a:cmdline) + let char = nr2char(char2nr(a:cmdline[i:])) + if a:cmdline[i:i + len(char) - 1] is# char + if len(char) > 1 + call add(ret, [i, i + 1, 'RBP1']) + endif + let i += len(char) + else + let i += 1 + endif + endwhile + return ret + endfunction + function Echoing(cmdline) + echo 'HERE' + return v:_null_list + endfunction + function Echoning(cmdline) + echon 'HERE' + return v:_null_list + endfunction + function Echomsging(cmdline) + echomsg 'HERE' + return v:_null_list + endfunction + function Echoerring(cmdline) + echoerr 'HERE' + return v:_null_list + endfunction + function Redrawing(cmdline) + redraw! + return v:_null_list + endfunction + function Throwing(cmdline) + throw "ABC" + return v:_null_list + endfunction + function Halting(cmdline) + while 1 + endwhile + endfunction + function ReturningGlobal(cmdline) + return g:callback_return + endfunction + function ReturningGlobal2(cmdline) + return g:callback_return[:len(a:cmdline)-1] + endfunction + function ReturningGlobalN(n, cmdline) + return g:callback_return{a:n} + endfunction + let g:recording_calls = [] + function Recording(cmdline) + call add(g:recording_calls, a:cmdline) + return [] + endfunction + ]]) + screen:set_default_attr_ids({ + RBP1={background = Screen.colors.Red}, + RBP2={background = Screen.colors.Yellow}, + RBP3={background = Screen.colors.Green}, + RBP4={background = Screen.colors.Blue}, + EOB={bold = true, foreground = Screen.colors.Blue1}, + ERR={foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + SK={foreground = Screen.colors.Blue}, + PE={bold = true, foreground = Screen.colors.SeaGreen4} + }) +end) + +local function set_color_cb(funcname, callback_return, id) + meths.set_var('id', id or '') + if id and id ~= '' and funcs.exists('*' .. funcname .. 'N') then + command(('let g:Nvim_color_input%s = {cmdline -> %sN(%s, cmdline)}'):format( + id, funcname, id)) + if callback_return then + meths.set_var('callback_return' .. id, callback_return) + end + else + meths.set_var('Nvim_color_input', funcname) + if callback_return then + meths.set_var('callback_return', callback_return) + end + end +end +local function start_prompt(text) + feed('{PROMPT}' .. (text or '')) +end + +describe('Command-line coloring', function() + it('works', function() + set_color_cb('RainBowParens') + meths.set_option('more', false) + start_prompt() + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :^ | + ]]) + feed('e') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :e^ | + ]]) + feed('cho ') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo ^ | + ]]) + feed('(') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo {RBP1:(}^ | + ]]) + feed('(') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo {RBP1:(}{RBP2:(}^ | + ]]) + feed('42') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo {RBP1:(}{RBP2:(}42^ | + ]]) + feed('))') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo {RBP1:(}{RBP2:(}42{RBP2:)}{RBP1:)}^ | + ]]) + feed('<BS>') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo {RBP1:(}{RBP2:(}42{RBP2:)}^ | + ]]) + redraw_input() + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo {RBP1:(}{RBP2:(}42{RBP2:)}^ | + ]]) + end) + for _, func_part in ipairs({'', 'n', 'msg'}) do + it('disables :echo' .. func_part .. ' messages', function() + set_color_cb('Echo' .. func_part .. 'ing') + start_prompt('echo') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo^ | + ]]) + end) + end + it('does the right thing when hl start appears to split multibyte char', + function() + set_color_cb('SplittedMultibyteStart') + start_prompt('echo "«') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo " | + {ERR:E5405: Chunk 0 start 7 splits multibyte }| + {ERR:character} | + :echo "«^ | + ]]) + feed('»') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo " | + {ERR:E5405: Chunk 0 start 7 splits multibyte }| + {ERR:character} | + :echo "«»^ | + ]]) + end) + it('does the right thing when hl end appears to split multibyte char', + function() + set_color_cb('SplittedMultibyteEnd') + start_prompt('echo "«') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo " | + {ERR:E5406: Chunk 0 end 7 splits multibyte ch}| + {ERR:aracter} | + :echo "«^ | + ]]) + end) + it('does the right thing when errorring', function() + set_color_cb('Echoerring') + start_prompt('e') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + : | + {ERR:E5407: Callback has thrown an exception:}| + {ERR: Vim(echoerr):HERE} | + :e^ | + ]]) + end) + it('silences :echo', function() + set_color_cb('Echoing') + start_prompt('e') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :e^ | + ]]) + eq('', meths.command_output('messages')) + end) + it('silences :echon', function() + set_color_cb('Echoning') + start_prompt('e') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :e^ | + ]]) + eq('', meths.command_output('messages')) + end) + it('silences :echomsg', function() + set_color_cb('Echomsging') + start_prompt('e') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :e^ | + ]]) + eq('', meths.command_output('messages')) + end) + it('does the right thing when throwing', function() + set_color_cb('Throwing') + start_prompt('e') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + : | + {ERR:E5407: Callback has thrown an exception:}| + {ERR: ABC} | + :e^ | + ]]) + end) + it('stops executing callback after a number of errors', function() + set_color_cb('SplittedMultibyteStart') + start_prompt('let x = "«»«»«»«»«»"\n') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :let x = " | + {ERR:E5405: Chunk 0 start 10 splits multibyte}| + {ERR: character} | + ^:let x = "«»«»«»«»«»" | + ]]) + feed('\n') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + | + ]]) + eq('let x = "«»«»«»«»«»"', meths.get_var('out')) + local msg = '\nE5405: Chunk 0 start 10 splits multibyte character' + eq(msg:rep(1), funcs.execute('messages')) + end) + it('allows interrupting callback with <C-c>', function() + set_color_cb('Halting') + start_prompt('echo 42') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + | + ]]) + screen:sleep(500) + feed('<C-c>') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + : | + {ERR:E5407: Callback has thrown an exception:}| + {ERR: Keyboard interrupt} | + :echo 42^ | + ]]) + redraw_input() + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo 42^ | + ]]) + feed('\n') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + ^:echo 42 | + ]]) + feed('\n') + eq('echo 42', meths.get_var('out')) + feed('<C-c>') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + Type :quit<Enter> to exit Nvim | + ]]) + end) + it('works fine with NUL, NL, CR', function() + set_color_cb('RainBowParens') + start_prompt('echo ("<C-v><CR><C-v><Nul><C-v><NL>")') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo {RBP1:(}"{SK:^M^@^@}"{RBP1:)}^ | + ]]) + end) + it('errors out when callback returns something wrong', function() + command('cnoremap + ++') + set_color_cb('ReturningGlobal', '') + start_prompt('#') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + : | + {ERR:E5400: Callback should return list} | + :#^ | + ]]) + + feed('<CR><CR><CR>') + set_color_cb('ReturningGlobal', {{0, 1, 'Normal'}, 42}) + start_prompt('#') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + : | + {ERR:E5401: List item 1 is not a List} | + :#^ | + ]]) + + feed('<CR><CR><CR>') + set_color_cb('ReturningGlobal2', {{0, 1, 'Normal'}, {1}}) + start_prompt('+') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :+ | + {ERR:E5402: List item 1 has incorrect length:}| + {ERR: 1 /= 3} | + :++^ | + ]]) + + feed('<CR><CR><CR>') + set_color_cb('ReturningGlobal2', {{0, 1, 'Normal'}, {2, 3, 'Normal'}}) + start_prompt('+') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :+ | + {ERR:E5403: Chunk 1 start 2 not in range [1, }| + {ERR:2)} | + :++^ | + ]]) + + feed('<CR><CR><CR>') + set_color_cb('ReturningGlobal2', {{0, 1, 'Normal'}, {1, 3, 'Normal'}}) + start_prompt('+') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :+ | + {ERR:E5404: Chunk 1 end 3 not in range (1, 2]}| + | + :++^ | + ]]) + end) + it('does not error out when called from a errorred out cycle', function() + set_color_cb('ReturningGlobal', {{0, 1, 'Normal'}}) + feed(dedent([[ + :set regexpengine=2 + :for pat in [' \ze*', ' \zs*'] + : try + : let l = matchlist('x x', pat) + : $put =input({'prompt':'>','highlight':'ReturningGlobal'}) + : + : $put ='E888 NOT detected for ' . pat + : catch + : $put =input({'prompt':'>','highlight':'ReturningGlobal'}) + : + : $put ='E888 detected for ' . pat + : endtry + :endfor + : + : + : + : + : + : + ]])) + eq({'', ':', 'E888 detected for \\ze*', ':', 'E888 detected for \\zs*'}, + curbufmeths.get_lines(0, -1, false)) + eq('', funcs.execute('messages')) + end) + it('allows nesting input()s', function() + set_color_cb('ReturningGlobal', {{0, 1, 'RBP1'}}, '') + start_prompt('1') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :{RBP1:1}^ | + ]]) + + set_color_cb('ReturningGlobal', {{0, 1, 'RBP2'}}, '1') + start_prompt('2') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :{RBP2:2}^ | + ]]) + + set_color_cb('ReturningGlobal', {{0, 1, 'RBP3'}}, '2') + start_prompt('3') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :{RBP3:3}^ | + ]]) + + set_color_cb('ReturningGlobal', {{0, 1, 'RBP4'}}, '3') + start_prompt('4') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :{RBP4:4}^ | + ]]) + + feed('<CR>') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :{RBP3:3}4^ | + ]]) + feed('<CR>') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :{RBP2:2}34^ | + ]]) + feed('<CR>') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :{RBP1:1}234^ | + ]]) + feed('<CR><CR><C-l>') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + | + ]]) + eq('1234', meths.get_var('out')) + eq('234', meths.get_var('out1')) + eq('34', meths.get_var('out2')) + eq('4', meths.get_var('out3')) + eq(0, funcs.exists('g:out4')) + end) + it('runs callback with the same data only once', function() + local function new_recording_calls(...) + eq({...}, meths.get_var('recording_calls')) + meths.set_var('recording_calls', {}) + end + set_color_cb('Recording') + start_prompt('') + -- Regression test. Disambiguation: + -- + -- new_recording_calls(expected_result) -- (actual_before_fix) + -- + feed('a') + new_recording_calls('a') -- ('a', 'a') + feed('b') + new_recording_calls('ab') -- ('a', 'ab', 'ab') + feed('c') + new_recording_calls('abc') -- ('ab', 'abc', 'abc') + feed('<BS>') + new_recording_calls('ab') -- ('abc', 'ab', 'ab') + feed('<BS>') + new_recording_calls('a') -- ('ab', 'a', 'a') + feed('<BS>') + new_recording_calls() -- ('a') + feed('<CR><CR>') + eq('', meths.get_var('out')) + end) +end) +describe('Ex commands coloring support', function() + it('works', function() + meths.set_var('Nvim_color_cmdline', 'RainBowParens') + feed(':echo (((1)))') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo {RBP1:(}{RBP2:(}{RBP3:(}1{RBP3:)}{RBP2:)}{RBP1:)}^ | + ]]) + end) + it('still executes command-line even if errored out', function() + meths.set_var('Nvim_color_cmdline', 'SplittedMultibyteStart') + feed(':let x = "«"\n') + eq('«', meths.get_var('x')) + local msg = 'E5405: Chunk 0 start 10 splits multibyte character' + eq('\n'..msg, funcs.execute('messages')) + end) + it('does not error out when called from a errorred out cycle', function() + -- Apparently when there is a cycle in which one of the commands errors out + -- this error may be caught by color_cmdline before it is presented to the + -- user. + feed(dedent([[ + :set regexpengine=2 + :for pat in [' \ze*', ' \zs*'] + : try + : let l = matchlist('x x', pat) + : $put ='E888 NOT detected for ' . pat + : catch + : $put ='E888 detected for ' . pat + : endtry + :endfor + ]])) + eq({'', 'E888 detected for \\ze*', 'E888 detected for \\zs*'}, + curbufmeths.get_lines(0, -1, false)) + eq('', funcs.execute('messages')) + end) + it('does not crash when using `n` in debug mode', function() + feed(':debug execute "echo 1"\n') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + Entering Debug mode. Type "cont" to con| + tinue. | + cmd: execute "echo 1" | + >^ | + ]]) + feed('n\n') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + Entering Debug mode. Type "cont" to con| + tinue. | + cmd: execute "echo 1" | + >n | + 1 | + {PE:Press ENTER or type command to continue}^ | + ]]) + feed('\n') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + | + ]]) + end) + it('does not prevent mapping error from cancelling prompt', function() + command("cnoremap <expr> x execute('throw 42')[-1]") + feed(':#x') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :# | + {ERR:Error detected while processing :} | + {ERR:E605: Exception not caught: 42} | + :#^ | + ]]) + feed('<CR>') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + | + ]]) + feed('<CR>') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + | + ]]) + eq('\nError detected while processing :\nE605: Exception not caught: 42', + meths.command_output('messages')) + end) + it('errors out when failing to get callback', function() + meths.set_var('Nvim_color_cmdline', 42) + feed(':#') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + : | + {ERR:E5408: Unable to get g:Nvim_color_cmdlin}| + {ERR:e callback: Vim:E6000: Argument is not a}| + {ERR: function or function name} | + :#^ | + ]]) + end) +end) +describe('Expressions coloring support', function() + it('works', function() + meths.set_var('Nvim_color_expr', 'RainBowParens') + feed(':echo <C-r>=(((1)))') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + ={RBP1:(}{RBP2:(}{RBP3:(}1{RBP3:)}{RBP2:)}{RBP1:)}^ | + ]]) + end) + it('errors out when failing to get callback', function() + meths.set_var('Nvim_color_expr', 42) + feed(':<C-r>=1') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + = | + {ERR:E5409: Unable to get g:Nvim_color_expr c}| + {ERR:allback: Vim:E6000: Argument is not a fu}| + {ERR:nction or function name} | + =1^ | + ]]) + end) +end) diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index 64965ccb94..c8fa2888d1 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -14,6 +14,7 @@ local neq = helpers.neq local ok = helpers.ok local source = helpers.source local wait = helpers.wait +local nvim = helpers.nvim local default_text = [[ Inc substitution on @@ -892,6 +893,31 @@ describe(":substitute, inccommand=split", function() ]]) end) + it('previews correctly when previewhight is small', function() + feed_command('set cwh=3') + feed_command('set hls') + feed('ggdG') + insert(string.rep('abc abc abc\n', 20)) + feed(':%s/abc/MMM/g') + screen:expect([[ + MMM MMM MMM | + MMM MMM MMM | + MMM MMM MMM | + MMM MMM MMM | + MMM MMM MMM | + MMM MMM MMM | + MMM MMM MMM | + MMM MMM MMM | + MMM MMM MMM | + {11:[No Name] [+] }| + | 1| {12:MMM} {12:MMM} {12:MMM} | + | 2| {12:MMM} {12:MMM} {12:MMM} | + | 3| {12:MMM} {12:MMM} {12:MMM} | + {10:[Preview] }| + :%s/abc/MMM/g^ | + ]]) + end) + it('actually replaces text', function() feed(":%s/tw/XX/g<Enter>") @@ -1622,3 +1648,29 @@ describe("'inccommand' split windows", function() end) end) + +describe("'inccommand' with 'gdefault'", function() + before_each(function() + clear() + end) + + it("does not lock up #7244", function() + common_setup(nil, "nosplit", "{") + command("set gdefault") + feed(":s/{\\n") + eq({mode='c', blocking=false}, nvim("get_mode")) + feed("/A<Enter>") + expect("A") + eq({mode='n', blocking=false}, nvim("get_mode")) + end) + + it("with multiline text and range, does not lock up #7244", function() + common_setup(nil, "nosplit", "{\n\n{") + command("set gdefault") + feed(":%s/{\\n") + eq({mode='c', blocking=false}, nvim("get_mode")) + feed("/A<Enter>") + expect("A\nA") + eq({mode='n', blocking=false}, nvim("get_mode")) + end) +end) diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 5408e1e195..a6b7fb2997 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -251,7 +251,7 @@ function Screen:expect(expected, attr_ids, attr_ignore, condition, any) ..'Expected:\n |'..table.concat(msg_expected_rows, '|\n |')..'|\n' ..'Actual:\n |'..table.concat(actual_rows, '|\n |')..'|\n\n'..[[ To print the expect() call that would assert the current screen state, use -screen:snaphot_util(). In case of non-deterministic failures, use +screen:snapshot_util(). In case of non-deterministic failures, use screen:redraw_debug() to show all intermediate screen states. ]]) end end diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index bfcdc7f652..b31d9cb32f 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -5,6 +5,7 @@ local feed, command = helpers.feed, helpers.command local insert = helpers.insert local eq = helpers.eq local eval = helpers.eval +local iswin = helpers.iswin describe('screen', function() local screen @@ -119,9 +120,10 @@ describe('Screen', function() end) it('has correct default title with named file', function() - local expected = 'myfile (/mydir) - NVIM' + local expected = (iswin() and 'myfile (C:\\mydir) - NVIM' + or 'myfile (/mydir) - NVIM') command('set title') - command('file /mydir/myfile') + command(iswin() and 'file C:\\mydir\\myfile' or 'file /mydir/myfile') screen:expect(function() eq(expected, screen.title) end) diff --git a/test/unit/path_spec.lua b/test/unit/path_spec.lua index a9cba7df84..befb204d0a 100644 --- a/test/unit/path_spec.lua +++ b/test/unit/path_spec.lua @@ -481,6 +481,20 @@ describe('path.c', function() eq('/tmp', ffi.string(buffer)) eq(OK, result) end) + + itp('expands "./" to the current directory #7117', function() + local force_expansion = 1 + local result = vim_FullName('./unit-test-directory/test.file', buffer, length, force_expansion) + eq(OK, result) + eq(lfs.currentdir() .. '/unit-test-directory/test.file', (ffi.string(buffer))) + end) + + itp('collapses "foo/../foo" to "foo" #7117', function() + local force_expansion = 1 + local result = vim_FullName('unit-test-directory/../unit-test-directory/test.file', buffer, length, force_expansion) + eq(OK, result) + eq(lfs.currentdir() .. '/unit-test-directory/test.file', (ffi.string(buffer))) + end) end) describe('path_fix_case', function() |