diff options
157 files changed, 4265 insertions, 2573 deletions
diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index 9ffd6d9c37..36f44f236b 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -29,12 +29,16 @@ tasks: - build: | cd neovim gmake CMAKE_BUILD_TYPE=Release CMAKE_EXTRA_FLAGS="${CMAKE_EXTRA_FLAGS}" nvim -- test: | +- functionaltest: | cd neovim - gmake unittest functionaltest + gmake functionaltest +- unittest: | + cd neovim + gmake unittest # Unfortunately, oldtest is tanking hard on sourcehut's FreeBSD instance # and not producing any logs as a result. So don't do this task for now. +# Ref: https://github.com/neovim/neovim/pull/11477#discussion_r352095005. # - test-oldtest: | # cd neovim # gmake oldtest diff --git a/.builds/openbsd.yml b/.builds/openbsd.yml index 6796802051..ed2962998c 100644 --- a/.builds/openbsd.yml +++ b/.builds/openbsd.yml @@ -16,26 +16,28 @@ packages: sources: - https://github.com/neovim/neovim +environment: + SOURCEHUT: 1 + LC_CTYPE: en_US.UTF-8 + CMAKE_EXTRA_FLAGS: -DTRAVIS_CI_BUILD=ON -DMIN_LOG_LEVEL=3 + tasks: -- build: | +- build-deps: | export AUTOCONF_VERSION=2.69 export AUTOMAKE_VERSION=1.15 - cd neovim - mkdir .deps - cd .deps + mkdir neovim/.deps + cd neovim/.deps cmake -G Ninja ../third-party/ cmake --build . --config Debug - cd .. - mkdir build - cd build - cmake -G Ninja -DMIN_LOG_LEVEL=3 .. +- build: | + mkdir neovim/build + cd neovim/build + cmake -G Ninja $CMAKE_EXTRA_FLAGS .. cmake --build . --config Debug ./bin/nvim --version -- test: | - export LC_CTYPE=en_US.UTF-8 - # functional tests +- functionaltest: | cd neovim/build cmake --build . --config Debug --target functionaltest - # oldtests - cd .. +- oldtest: | + cd neovim gmake oldtest diff --git a/.luacheckrc b/.luacheckrc index b945835bba..9c8bddb88e 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -2,6 +2,8 @@ -- Ignore W211 (unused variable) with preload files. files["**/preload.lua"] = {ignore = { "211" }} +-- Allow vim module to modify itself, but only here. +files["src/nvim/lua/vim.lua"] = {ignore = { "122/vim" }} -- Don't report unused self arguments of methods. self = false diff --git a/.travis.yml b/.travis.yml index d8a1d2ddf0..26e129a5e2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,19 @@ language: c env: global: + # Encrypted environment variables, see + # http://docs.travis-ci.com/user/encryption-keys/ + # + # SNAP_SECRET_KEY: generated by: + # travis encrypt SNAP_SECRET_KEY=xx --add + # https://github.com/neovim/neovim/pull/11428 + # snapcraft key expires after 1 year. Steps to refresh it: + # 1. snapcraft enable-ci travis --refresh + # 2. mv .snapcraft/travis_snapcraft.cfg ci/snap/travis_snapcraft.cfg + # 3. Copy after_success command to ci/snap/deploy.sh from .travis.yml + # 4. Undo changes to .travis.yml + - secure: hd0qn2u8ABbJg5Bx4pBRcUQbKYFmcSHoecyHIPTCnGJT+NI41Bvm/IkN/N5DhBF+LbD3Q2nmR/dzI5H/dqS7RxMFvEx1DuFLendFHHX3MYf0AuKpXYY3gwgMTmqx8p/v6srlU7RBGWNGzHCWqksAem+EIWCe3I7WvfdKo1/DV/Y= + # Set "false" to force rebuild of third-party dependencies. - CACHE_ENABLE=true # Build directory for Neovim. @@ -145,6 +158,36 @@ jobs: env: - CLANG_SANITIZER=TSAN - *common-job-env + - if: false + name: snap + os: linux + env: + - LC_ALL: C.UTF-8 + - LANG: C.UTF-8 + - SNAPCRAFT_ENABLE_SILENT_REPORT: y + - SNAPCRAFT_ENABLE_DEVELOPER_DEBUG: y + addons: + snaps: + - name: snapcraft + channel: stable + classic: true + - name: http + - name: transfer + - name: lxd + channel: stable + # Override default before_install, before_cache. + before_install: /bin/true + before_cache: /bin/true + install: ci/snap/install.sh + before_script: echo "Building snap..." + script: ci/snap/script.sh + after_success: ci/snap/after_success.sh + deploy: + skip_cleanup: true + provider: script + script: ci/snap/deploy.sh + on: + branch: master fast_finish: true before_install: ci/before_install.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index d25cd89342..dbe2bf4d10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,20 +134,18 @@ set(NVIM_VERSION_BUILD_TYPE "${CMAKE_BUILD_TYPE}") set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -# Minimize logging for release-type builds. -if(NOT CMAKE_C_FLAGS_RELEASE MATCHES DMIN_LOG_LEVEL) - set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -DMIN_LOG_LEVEL=3") -endif() -if(NOT CMAKE_C_FLAGS_MINSIZEREL MATCHES DMIN_LOG_LEVEL) - set(CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL} -DMIN_LOG_LEVEL=3") -endif() -if(NOT CMAKE_C_FLAGS_RELWITHDEBINFO MATCHES DMIN_LOG_LEVEL) - set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -DMIN_LOG_LEVEL=3") -endif() - # Log level (MIN_LOG_LEVEL in log.h) if("${MIN_LOG_LEVEL}" MATCHES "^$") - message(STATUS "MIN_LOG_LEVEL not specified, default is 1 (INFO)") + # Minimize logging for release-type builds. + if(CMAKE_BUILD_TYPE STREQUAL "Release" + OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo" + OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel") + message(STATUS "MIN_LOG_LEVEL not specified, default is 3 (ERROR) for release builds") + set(MIN_LOG_LEVEL 3) + else() + message(STATUS "MIN_LOG_LEVEL not specified, default is 1 (INFO)") + set(MIN_LOG_LEVEL 1) + endif() else() if(NOT MIN_LOG_LEVEL MATCHES "^[0-3]$") message(FATAL_ERROR "invalid MIN_LOG_LEVEL: " ${MIN_LOG_LEVEL}) @@ -119,8 +119,13 @@ oldtest: | nvim build/runtime/doc/tags ifeq ($(strip $(TEST_FILE)),) +$(SINGLE_MAKE) -C src/nvim/testdir NVIM_PRG="$(realpath build/bin/nvim)" $(MAKEOVERRIDES) else - +$(SINGLE_MAKE) -C src/nvim/testdir NVIM_PRG="$(realpath build/bin/nvim)" NEW_TESTS=$(TEST_FILE) SCRIPTS= $(MAKEOVERRIDES) + @# Handle TEST_FILE=test_foo{,.res,.vim}. + +$(SINGLE_MAKE) -C src/nvim/testdir NVIM_PRG="$(realpath build/bin/nvim)" SCRIPTS= $(MAKEOVERRIDES) $(patsubst %.vim,%,$(patsubst %.res,%,$(TEST_FILE))) endif +# Build oldtest by specifying the relative .vim filename. +.PHONY: phony_force +src/nvim/testdir/%.vim: phony_force + +$(SINGLE_MAKE) -C src/nvim/testdir NVIM_PRG="$(realpath build/bin/nvim)" SCRIPTS= $(MAKEOVERRIDES) $(patsubst src/nvim/testdir/%.vim,%,$@) build/runtime/doc/tags helptags: | nvim +$(BUILD_CMD) -C build runtime/doc/tags @@ -201,10 +206,10 @@ lint: check-single-includes clint lualint _opt_pylint _opt_shlint # Generic pattern rules, allowing for `make build/bin/nvim` etc. # Does not work with "Unix Makefiles". ifeq ($(BUILD_TYPE),Ninja) -build/%: +build/%: phony_force $(BUILD_CMD) -C build $(patsubst build/%,%,$@) -$(DEPS_BUILD_DIR)/%: +$(DEPS_BUILD_DIR)/%: phony_force $(BUILD_CMD) -C $(DEPS_BUILD_DIR) $(patsubst $(DEPS_BUILD_DIR)/%,%,$@) endif diff --git a/ci/build.ps1 b/ci/build.ps1 index 49fd61abf6..244b4766b2 100644 --- a/ci/build.ps1 +++ b/ci/build.ps1 @@ -114,6 +114,11 @@ if (-Not (Test-Path -PathType Leaf "$env:TREE_SITTER_DIR\bin\c.dll")) { exit 1 } +if ($compiler -eq 'MSVC') { + # Required for LuaRocks (https://github.com/luarocks/luarocks/issues/1039#issuecomment-507296940). + $env:VCINSTALLDIR = "C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/" +} + function convertToCmakeArgs($vars) { return $vars.GetEnumerator() | foreach { "-D$($_.Key)=$($_.Value)" } } diff --git a/ci/snap/after_success.sh b/ci/snap/after_success.sh new file mode 100755 index 0000000000..e66721a5e2 --- /dev/null +++ b/ci/snap/after_success.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + + +RESULT_SNAP=$(find ./ -name "*.snap") + +sudo snap install "$RESULT_SNAP" --dangerous --classic + +/snap/bin/nvim --version + +SHA256=$(sha256sum "$RESULT_SNAP") +echo "SHA256: ${SHA256} ." diff --git a/ci/snap/deploy.sh b/ci/snap/deploy.sh new file mode 100755 index 0000000000..5fbd52d775 --- /dev/null +++ b/ci/snap/deploy.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +# not a tagged release, abort +# [[ "$TRAVIS_TAG" != "$TRAVIS_BRANCH" ]] && exit 0 + +mkdir -p .snapcraft +# shellcheck disable=SC2154 +openssl aes-256-cbc -K "$encrypted_ece1c4844832_key" -iv "$encrypted_ece1c4844832_iv" \ + -in ci/snap/travis_snapcraft.cfg -out .snapcraft/snapcraft.cfg -d + +SNAP=$(find ./ -name "*.snap") + +# TODO(justinmk): This always does `edge` until we enable tagged builds. +if [[ "$SNAP" =~ "dirty" || "$SNAP" =~ "nightly" ]]; then + snapcraft push "$SNAP" --release edge +else + snapcraft push "$SNAP" --release candidate +fi diff --git a/ci/snap/install.sh b/ci/snap/install.sh new file mode 100755 index 0000000000..23e0bc5eb8 --- /dev/null +++ b/ci/snap/install.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +sudo apt update +sudo /snap/bin/lxd.migrate -yes +sudo /snap/bin/lxd waitready +sudo /snap/bin/lxd init --auto + diff --git a/ci/snap/script.sh b/ci/snap/script.sh new file mode 100755 index 0000000000..647cda4874 --- /dev/null +++ b/ci/snap/script.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +mkdir -p "$TRAVIS_BUILD_DIR/snaps-cache" +sudo snapcraft --use-lxd + diff --git a/ci/snap/travis_snapcraft.cfg b/ci/snap/travis_snapcraft.cfg Binary files differnew file mode 100644 index 0000000000..3e6a60c30d --- /dev/null +++ b/ci/snap/travis_snapcraft.cfg diff --git a/runtime/autoload/lsp.vim b/runtime/autoload/lsp.vim deleted file mode 100644 index 4c8f8b396a..0000000000 --- a/runtime/autoload/lsp.vim +++ /dev/null @@ -1,45 +0,0 @@ -function! lsp#add_filetype_config(config) abort - call luaeval('vim.lsp.add_filetype_config(_A)', a:config) -endfunction - -function! lsp#set_log_level(level) abort - call luaeval('vim.lsp.set_log_level(_A)', a:level) -endfunction - -function! lsp#get_log_path() abort - return luaeval('vim.lsp.get_log_path()') -endfunction - -function! lsp#omnifunc(findstart, base) abort - return luaeval("vim.lsp.omnifunc(_A[1], _A[2])", [a:findstart, a:base]) -endfunction - -function! lsp#text_document_hover() abort - lua vim.lsp.buf_request(nil, 'textDocument/hover', vim.lsp.protocol.make_text_document_position_params()) - return '' -endfunction - -function! lsp#text_document_declaration() abort - lua vim.lsp.buf_request(nil, 'textDocument/declaration', vim.lsp.protocol.make_text_document_position_params()) - return '' -endfunction - -function! lsp#text_document_definition() abort - lua vim.lsp.buf_request(nil, 'textDocument/definition', vim.lsp.protocol.make_text_document_position_params()) - return '' -endfunction - -function! lsp#text_document_signature_help() abort - lua vim.lsp.buf_request(nil, 'textDocument/signatureHelp', vim.lsp.protocol.make_text_document_position_params()) - return '' -endfunction - -function! lsp#text_document_type_definition() abort - lua vim.lsp.buf_request(nil, 'textDocument/typeDefinition', vim.lsp.protocol.make_text_document_position_params()) - return '' -endfunction - -function! lsp#text_document_implementation() abort - lua vim.lsp.buf_request(nil, 'textDocument/implementation', vim.lsp.protocol.make_text_document_position_params()) - return '' -endfunction diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index a9256638e1..e4c0080ae9 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -1,4 +1,4 @@ -" Maintainer: Anmol Sethi <anmol@aubble.com> +" Maintainer: Anmol Sethi <hi@nhooyr.io> if exists('s:loaded_man') finish @@ -64,33 +64,20 @@ function! man#open_page(count, count1, mods, ...) abort return endtry - call s:push_tag() - let bufname = 'man://'.name.(empty(sect)?'':'('.sect.')') - + let [l:buf, l:save_tfu] = [bufnr(), &tagfunc] try - set eventignore+=BufReadCmd + set tagfunc=man#goto_tag + let l:target = l:name . '(' . l:sect . ')' if a:mods !~# 'tab' && s:find_man() - execute 'silent keepalt edit' fnameescape(bufname) + execute 'silent keepalt tag' l:target else - execute 'silent keepalt' a:mods 'split' fnameescape(bufname) + execute 'silent keepalt' a:mods 'stag' l:target endif finally - set eventignore-=BufReadCmd - endtry - - try - let page = s:get_page(path) - catch - if a:mods =~# 'tab' || !s:find_man() - " a new window was opened - close - endif - call s:error(v:exception) - return + call setbufvar(l:buf, '&tagfunc', l:save_tfu) endtry let b:man_sect = sect - call s:put_page(page) endfunction function! man#read_page(ref) abort @@ -152,7 +139,7 @@ function! s:get_page(path) abort " Disable hard-wrap by using a big $MANWIDTH (max 1000 on some systems #9065). " Soft-wrap: ftplugin/man.vim sets wrap/breakindent/…. " Hard-wrap: driven by `man`. - let manwidth = !get(g:,'man_hardwrap') ? 999 : (empty($MANWIDTH) ? winwidth(0) : $MANWIDTH) + let manwidth = !get(g:,'man_hardwrap', 1) ? 999 : (empty($MANWIDTH) ? winwidth(0) : $MANWIDTH) " Force MANPAGER=cat to ensure Vim is not recursively invoked (by man-db). " http://comments.gmane.org/gmane.editors.vim.devel/29085 " Set MAN_KEEP_FORMATTING so Debian man doesn't discard backspaces. @@ -163,6 +150,9 @@ endfunction function! s:put_page(page) abort setlocal modifiable setlocal noreadonly + setlocal noswapfile + " git-ls-files(1) is all one keyword/tag-target + setlocal iskeyword+=(,) silent keepjumps %delete _ silent put =a:page while getline(1) =~# '^\s*$' @@ -254,24 +244,6 @@ function! s:verify_exists(sect, name) abort return s:extract_sect_and_name_path(path) + [path] endfunction -let s:tag_stack = [] - -function! s:push_tag() abort - let s:tag_stack += [{ - \ 'buf': bufnr('%'), - \ 'lnum': line('.'), - \ 'col': col('.'), - \ }] -endfunction - -function! man#pop_tag() abort - if !empty(s:tag_stack) - let tag = remove(s:tag_stack, -1) - execute 'silent' tag['buf'].'buffer' - call cursor(tag['lnum'], tag['col']) - endif -endfunction - " extracts the name and sect out of 'path/name.sect' function! s:extract_sect_and_name_path(path) abort let tail = fnamemodify(a:path, ':t') @@ -356,14 +328,18 @@ function! man#complete(arg_lead, cmd_line, cursor_pos) abort return s:complete(sect, sect, name) endfunction -function! s:complete(sect, psect, name) abort +function! s:get_paths(sect, name) abort try let mandirs = join(split(s:system(['man', s:find_arg]), ':\|\n'), ',') catch call s:error(v:exception) return endtry - let pages = globpath(mandirs,'man?/'.a:name.'*.'.a:sect.'*', 0, 1) + return globpath(mandirs,'man?/'.a:name.'*.'.a:sect.'*', 0, 1) +endfunction + +function! s:complete(sect, psect, name) abort + let pages = s:get_paths(a:sect, a:name) " We remove duplicates in case the same manpage in different languages was found. return uniq(sort(map(pages, 's:format_candidate(v:val, a:psect)'), 'i')) endfunction @@ -383,6 +359,10 @@ function! s:format_candidate(path, psect) abort endfunction function! man#init_pager() abort + " https://github.com/neovim/neovim/issues/6828 + let og_modifiable = &modifiable + setlocal modifiable + if getline(1) =~# '^\s*$' silent keepjumps 1delete _ else @@ -400,6 +380,31 @@ function! man#init_pager() abort if -1 == match(bufname('%'), 'man:\/\/') " Avoid duplicate buffers, E95. execute 'silent file man://'.tolower(fnameescape(ref)) endif + + let &l:modifiable = og_modifiable +endfunction + +function! man#goto_tag(pattern, flags, info) abort + let [l:sect, l:name] = man#extract_sect_and_name_ref(a:pattern) + + let l:paths = s:get_paths(l:sect, l:name) + let l:structured = [] + + for l:path in l:paths + let l:n = s:extract_sect_and_name_path(l:path)[1] + let l:structured += [{ 'name': l:n, 'path': l:path }] + endfor + + " sort by relevance - exact matches first, then the previous order + call sort(l:structured, { a, b -> a.name ==? l:name ? -1 : b.name ==? l:name ? 1 : 0 }) + + return map(l:structured, { + \ _, entry -> { + \ 'name': entry.name, + \ 'filename': 'man://' . entry.path, + \ 'cmd': '1' + \ } + \ }) endfunction call s:init() diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 57a72e6173..d52a9a8409 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -19,6 +19,7 @@ API Usage *api-rpc* *RPC* *rpc* *msgpack-rpc* RPC is the typical way to control Nvim programmatically. Nvim implements the MessagePack-RPC protocol: + https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md https://github.com/msgpack/msgpack/blob/0b8f5ac/spec.md Many clients use the API: user interfaces (GUIs), remote plugins, scripts like @@ -478,6 +479,29 @@ created for extmark changes. ============================================================================== Global Functions *api-global* +nvim_exec({src}, {output}) *nvim_exec()* + Executes Vimscript (multiline block of Ex-commands), like + anonymous |:source|. + + Unlike |nvim_command()| this function supports heredocs, + script-scope (s:), etc. + + On execution error: fails with VimL error, does not update + v:errmsg. + + Parameters: ~ + {src} Vimscript code + {output} Capture and return all (non-error, non-shell + |:!|) output + + Return: ~ + Output (non-error, non-shell |:!|) if `output` is true, + else empty string. + + See also: ~ + |execute()| + |nvim_command()| + nvim_command({command}) *nvim_command()* Executes an ex-command. @@ -487,6 +511,9 @@ nvim_command({command}) *nvim_command()* Parameters: ~ {command} Ex-command string + See also: ~ + |nvim_exec()| + nvim_get_hl_by_name({name}, {rgb}) *nvim_get_hl_by_name()* Gets a highlight definition by name. @@ -608,19 +635,9 @@ nvim_replace_termcodes({str}, {from_part}, {do_lt}, {special}) replace_termcodes cpoptions -nvim_command_output({command}) *nvim_command_output()* - Executes an ex-command and returns its (non-error) output. - Shell |:!| output is not captured. - - On execution error: fails with VimL error, does not update - v:errmsg. - - Parameters: ~ - {command} Ex-command string - nvim_eval({expr}) *nvim_eval()* - Evaluates a VimL expression (:help expression). Dictionaries - and Lists are recursively expanded. + Evaluates a VimL |expression|. Dictionaries and Lists are + recursively expanded. On execution error: fails with VimL error, does not update v:errmsg. @@ -631,7 +648,7 @@ nvim_eval({expr}) *nvim_eval()* Return: ~ Evaluation result or expanded object -nvim_execute_lua({code}, {args}) *nvim_execute_lua()* +nvim_exec_lua({code}, {args}) *nvim_exec_lua()* Execute Lua code. Parameters (if any) are available as `...` inside the chunk. The chunk can return a value. @@ -933,10 +950,11 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()* 'number', 'relativenumber', 'cursorline', 'cursorcolumn', 'foldcolumn', 'spell' and 'list' options. 'signcolumn' is changed to - `auto` . The end-of-buffer region is hidden - by setting `eob` flag of 'fillchars' to a - space char, and clearing the |EndOfBuffer| - region in 'winhighlight'. + `auto` and 'colorcolumn' is cleared. The + end-of-buffer region is hidden by setting + `eob` flag of 'fillchars' to a space char, + and clearing the |EndOfBuffer| region in + 'winhighlight'. Return: ~ Window handle, or 0 on error @@ -1021,7 +1039,7 @@ nvim_put({lines}, {type}, {after}, {follow}) *nvim_put()* {type} Edit behavior: any |getregtype()| result, or: • "b" |blockwise-visual| mode (may include width, e.g. "b3") - • "c" |characterwise| mode + • "c" |charwise| mode • "l" |linewise| mode • "" guess by contents, see |setreg()| {after} Insert after cursor (like |p|), or before (like @@ -1796,81 +1814,93 @@ nvim_buf_get_extmark_by_id({buffer}, {ns_id}, {id}) Returns position for a given extmark id Parameters: ~ - {buffer} The buffer handle - {namespace} a identifier returned previously with - nvim_create_namespace - {id} the extmark id + {buffer} Buffer handle, or 0 for current buffer + {ns_id} Namespace id from |nvim_create_namespace()| + {id} Extmark id Return: ~ (row, col) tuple or empty list () if extmark id was absent *nvim_buf_get_extmarks()* nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts}) - List extmarks in a range (inclusive) - - range ends can be specified as (row, col) tuples, as well as - extmark ids in the same namespace. In addition, 0 and -1 works - as shorthands for (0,0) and (-1,-1) respectively, so that all - marks in the buffer can be queried as: + Gets extmarks in "traversal order" from a |charwise| region + defined by buffer positions (inclusive, 0-indexed + |api-indexing|). + + Region can be given as (row,col) tuples, or valid extmark ids + (whose positions define the bounds). 0 and -1 are understood + as (0,0) and (-1,-1) respectively, thus the following are + equivalent: +> + nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) + nvim_buf_get_extmarks(0, my_ns, [0,0], [-1,-1], {}) +< - all_marks = nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) + If `end` is less than `start` , traversal works backwards. + (Useful with `limit` , to get the first marks prior to a given + position.) - If end is a lower position than start, then the range will be - traversed backwards. This is mostly useful with limited - amount, to be able to get the first marks prior to a given - position. + Example: +> + local a = vim.api + local pos = a.nvim_win_get_cursor(0) + local ns = a.nvim_create_namespace('my-plugin') + -- Create new extmark at line 1, column 1. + local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, 0, {}) + -- Create new extmark at line 3, column 1. + local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, 0, {}) + -- Get extmarks only from line 3. + local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) + -- Get all marks in this buffer + namespace. + local all = a.nvim_buf_get_extmarks(0, ns, 0, -1, {}) + print(vim.inspect(ms)) +< Parameters: ~ - {buffer} The buffer handle - {ns_id} An id returned previously from - nvim_create_namespace - {start} One of: extmark id, (row, col) or 0, -1 for - buffer ends - {end} One of: extmark id, (row, col) or 0, -1 for - buffer ends - {opts} additional options. Supports the keys: - • amount: Maximum number of marks to return + {buffer} Buffer handle, or 0 for current buffer + {ns_id} Namespace id from |nvim_create_namespace()| + {start} Start of range, given as (row, col) or valid + extmark id (whose position defines the bound) + {end} End of range, given as (row, col) or valid + extmark id (whose position defines the bound) + {opts} Optional parameters. Keys: + • limit: Maximum number of marks to return Return: ~ - [[extmark_id, row, col], ...] + List of [extmark_id, row, col] tuples in "traversal + order". *nvim_buf_set_extmark()* nvim_buf_set_extmark({buffer}, {ns_id}, {id}, {line}, {col}, {opts}) - Create or update an extmark at a position - - If an invalid namespace is given, an error will be raised. + Creates or updates an extmark. - To create a new extmark, pass in id=0. The new extmark id will - be returned. To move an existing mark, pass in its id. - - It is also allowed to create a new mark by passing in a - previously unused id, but the caller must then keep track of - existing and unused ids itself. This is mainly useful over - RPC, to avoid needing to wait for the return value. + To create a new extmark, pass id=0. The extmark id will be + returned. It is also allowed to create a new mark by passing + in a previously unused id, but the caller must then keep track + of existing and unused ids itself. (Useful over RPC, to avoid + waiting for the return value.) Parameters: ~ - {buffer} The buffer handle - {ns_id} a identifier returned previously with - nvim_create_namespace - {id} The extmark's id or 0 to create a new mark. - {line} The row to set the extmark to. - {col} The column to set the extmark to. + {buffer} Buffer handle, or 0 for current buffer + {ns_id} Namespace id from |nvim_create_namespace()| + {id} Extmark id, or 0 to create new + {line} Line number where to place the mark + {col} Column where to place the mark {opts} Optional parameters. Currently not used. Return: ~ - the id of the extmark. + Id of the created/updated extmark nvim_buf_del_extmark({buffer}, {ns_id}, {id}) *nvim_buf_del_extmark()* - Remove an extmark + Removes an extmark. Parameters: ~ - {buffer} The buffer handle - {ns_id} a identifier returned previously with - nvim_create_namespace - {id} The extmarks's id + {buffer} Buffer handle, or 0 for current buffer + {ns_id} Namespace id from |nvim_create_namespace()| + {id} Extmark id Return: ~ - true on success, false if the extmark was not found. + true if the extmark was found, else false *nvim_buf_add_highlight()* nvim_buf_add_highlight({buffer}, {ns_id}, {hl_group}, {line}, @@ -1915,8 +1945,8 @@ nvim_buf_add_highlight({buffer}, {ns_id}, {hl_group}, {line}, *nvim_buf_clear_namespace()* nvim_buf_clear_namespace({buffer}, {ns_id}, {line_start}, {line_end}) - Clears namespaced objects, highlights and virtual text, from a - line range + Clears namespaced objects (highlights, extmarks, virtual text) + from a region. Lines are 0-indexed. |api-indexing| To clear the namespace in the entire buffer, specify line_start=0 and line_end=-1. diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 34ea083f96..18dfa587e8 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -559,16 +559,14 @@ CmdlineLeave Before leaving the command-line (including *CmdwinEnter* CmdwinEnter After entering the command-line window. Useful for setting options specifically for - this special type of window. This is - triggered _instead_ of BufEnter and WinEnter. + this special type of window. <afile> is set to a single character, indicating the type of command-line. |cmdwin-char| *CmdwinLeave* CmdwinLeave Before leaving the command-line window. Useful to clean up any global setting done - with CmdwinEnter. This is triggered _instead_ - of BufLeave and WinLeave. + with CmdwinEnter. <afile> is set to a single character, indicating the type of command-line. |cmdwin-char| diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt index bd3f22a371..dcebbc524c 100644 --- a/runtime/doc/change.txt +++ b/runtime/doc/change.txt @@ -90,7 +90,7 @@ start and end of the motion are not in the same line, and there are only blanks before the start and there are no non-blanks after the end of the motion, the delete becomes linewise. This means that the delete also removes the line of blanks that you might expect to remain. Use the |o_v| operator to -force the motion to be characterwise. +force the motion to be charwise. Trying to delete an empty region of text (e.g., "d0" in the first column) is an error when 'cpoptions' includes the 'E' flag. @@ -1074,7 +1074,7 @@ also use these commands to move text from one file to another, because Vim preserves all registers when changing buffers (the CTRL-^ command is a quick way to toggle between two files). - *linewise-register* *characterwise-register* + *linewise-register* *charwise-register* You can repeat the put commands with "." (except for :put) and undo them. If the command that was used to get the text into the register was |linewise|, Vim inserts the text below ("p") or above ("P") the line where the cursor is. @@ -1116,10 +1116,9 @@ this happen. However, if the width of the block is not a multiple of a <Tab> width and the text after the inserted block contains <Tab>s, that text may be misaligned. -Note that after a characterwise yank command, Vim leaves the cursor on the -first yanked character that is closest to the start of the buffer. This means -that "yl" doesn't move the cursor, but "yh" moves the cursor one character -left. +Note that after a charwise yank command, Vim leaves the cursor on the first +yanked character that is closest to the start of the buffer. This means that +"yl" doesn't move the cursor, but "yh" moves the cursor one character left. Rationale: In Vi the "y" command followed by a backwards motion would sometimes not move the cursor to the first yanked character, because redisplaying was skipped. In Vim it always moves to diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index ee1f76e4e4..b31177ce0e 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -1122,11 +1122,9 @@ edited as described in |cmdwin-char|. AUTOCOMMANDS -Two autocommand events are used: |CmdwinEnter| and |CmdwinLeave|. Since this -window is of a special type, the WinEnter, WinLeave, BufEnter and BufLeave -events are not triggered. You can use the Cmdwin events to do settings -specifically for the command-line window. Be careful not to cause side -effects! +Two autocommand events are used: |CmdwinEnter| and |CmdwinLeave|. You can use +the Cmdwin events to do settings specifically for the command-line window. +Be careful not to cause side effects! Example: > :au CmdwinEnter : let b:cpt_save = &cpt | set cpt=. :au CmdwinLeave : let &cpt = b:cpt_save diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index b76a37810c..7c6b9ad1d3 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -14,6 +14,8 @@ updated. API ~ *nvim_buf_clear_highlight()* Use |nvim_buf_clear_namespace()| instead. +*nvim_command_output()* Use |nvim_exec()| instead. +*nvim_execute_lua()* Use |nvim_exec_lua()| instead. Commands ~ *:rv* diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt index ba887a83c8..09c5b7c4ad 100644 --- a/runtime/doc/develop.txt +++ b/runtime/doc/develop.txt @@ -236,10 +236,11 @@ with a {thing} that groups functions under a common concept). Use existing common {action} names if possible: add Append to, or insert into, a collection - get Get a thing (or group of things by query) - set Set a thing (or group of things) del Delete a thing (or group of things) + exec Execute code + get Get a thing (or group of things by query) list Get all things + set Set a thing (or group of things) Use consistent names for {thing} in all API functions. E.g. a buffer is called "buf" everywhere, not "buffer" in some places and "buf" in others. @@ -349,8 +350,8 @@ External UIs are expected to implement these common features: chords (<C-,> <C-Enter> <C-S-x> <D-x>) and patterns ("shift shift") that do not potentially conflict with Nvim defaults, plugins, etc. - Consider the "option_set" |ui-global| event as a hint for other GUI - behaviors. UI-related options ('guifont', 'ambiwidth', …) are published in - this event. + behaviors. Various UI-related options ('guifont', 'ambiwidth', …) are + published in this event. See also "mouse_on", "mouse_off". vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 84a893a205..ab65cc4560 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -4727,7 +4727,7 @@ getreg([{regname} [, 1 [, {list}]]]) *getreg()* getregtype([{regname}]) *getregtype()* The result is a String, which is type of register {regname}. The value will be one of: - "v" for |characterwise| text + "v" for |charwise| text "V" for |linewise| text "<CTRL-V>{width}" for |blockwise-visual| text "" for an empty or unknown register @@ -4969,9 +4969,11 @@ has({feature}) Returns 1 if {feature} is supported, 0 otherwise. The < *feature-list* List of supported pseudo-feature names: acl |ACL| support + bsd BSD system (not macOS, use "mac" for that). iconv Can use |iconv()| for conversion. +shellslash Can use backslashes in filenames (Windows) clipboard |clipboard| provider is available. + mac MacOS system. nvim This is Nvim. python2 Legacy Vim |python2| interface. |has-python| python3 Legacy Vim |python3| interface. |has-python| @@ -4981,6 +4983,7 @@ has({feature}) Returns 1 if {feature} is supported, 0 otherwise. The unix Unix system. *vim_starting* True during |startup|. win32 Windows system (32 or 64 bit). + win64 Windows system (64 bit). wsl WSL (Windows Subsystem for Linux) system *has-patch* @@ -6131,7 +6134,7 @@ mode([expr]) Return a string that indicates the current mode. n Normal no Operator-pending - nov Operator-pending (forced characterwise |o_v|) + nov Operator-pending (forced charwise |o_v|) noV Operator-pending (forced linewise |o_V|) noCTRL-V Operator-pending (forced blockwise |o_CTRL-V|) niI Normal using |i_CTRL-O| in |Insert-mode| @@ -7441,7 +7444,7 @@ setreg({regname}, {value} [, {options}]) If {options} contains "a" or {regname} is upper case, then the value is appended. {options} can also contain a register type specification: - "c" or "v" |characterwise| mode + "c" or "v" |charwise| mode "l" or "V" |linewise| mode "b" or "<CTRL-V>" |blockwise-visual| mode If a number immediately follows "b" or "<CTRL-V>" then this is @@ -9708,7 +9711,7 @@ This does NOT work: > register, "@/" for the search pattern. If the result of {expr1} ends in a <CR> or <NL>, the register will be linewise, otherwise it will be set to - characterwise. + charwise. This can be used to clear the last search pattern: > :let @/ = "" < This is different from searching for an empty string, diff --git a/runtime/doc/filetype.txt b/runtime/doc/filetype.txt index c579c390c6..c649688d99 100644 --- a/runtime/doc/filetype.txt +++ b/runtime/doc/filetype.txt @@ -549,7 +549,9 @@ Variables: *b:man_default_sects* Comma-separated, ordered list of preferred sections. For example in C one usually wants section 3 or 2: > :let b:man_default_sections = '3,2' -*g:man_hardwrap* Hard-wrap to $MANWIDTH. May improve layout. +*g:man_hardwrap* Hard-wrap to $MANWIDTH or window width if $MANWIDTH is + empty. Enabled by default. Set |FALSE| to enable soft + wrapping. To use Nvim as a manpager: > export MANPAGER='nvim +Man!' @@ -558,10 +560,13 @@ Note that when running `man` from the shell and with that `MANPAGER` in your environment, `man` will pre-format the manpage using `groff`. Thus, Neovim will inevitably display the manual page as it was passed to it from stdin. One of the caveats of this is that the width will _always_ be hard-wrapped and not -soft wrapped as with `:Man`. You can set in your environment: > +soft wrapped as with `g:man_hardwrap=0`. You can set in your environment: > export MANWIDTH=999 -So `groff`'s pre-formatting output will be the same as with `:Man` i.e soft-wrapped. +So `groff`'s pre-formatting output will be the same as with `g:man_hardwrap=0` i.e soft-wrapped. + +To disable bold highlighting: > + :highlight link manBold Normal PDF *ft-pdf-plugin* diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index 211b7be2cc..ed9853a8da 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -404,7 +404,7 @@ tag char note action in Normal mode ~ |t| t{char} 1 cursor till before Nth occurrence of {char} to the right |u| u 2 undo changes -|v| v start characterwise Visual mode +|v| v start charwise Visual mode |w| w 1 cursor N words forward |x| ["x]x 2 delete N characters under and after the cursor [into register x] @@ -866,7 +866,7 @@ These can be used after an operator, but before a {motion} has been entered. tag char action in Operator-pending mode ~ ----------------------------------------------------------------------- -|o_v| v force operator to work characterwise +|o_v| v force operator to work charwise |o_V| V force operator to work linewise |o_CTRL-V| CTRL-V force operator to work blockwise @@ -978,7 +978,7 @@ tag command note action in Visual mode ~ |v_r| r 2 replace highlighted area with a character |v_s| s 2 delete highlighted area and start insert |v_u| u 2 make highlighted area lowercase -|v_v| v make Visual mode characterwise or stop +|v_v| v make Visual mode charwise or stop Visual mode |v_x| x 2 delete the highlighted area |v_y| y yank the highlighted area diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt index 3292489eda..c59ed43a47 100644 --- a/runtime/doc/intro.txt +++ b/runtime/doc/intro.txt @@ -271,7 +271,7 @@ and <> are part of what you type, the context should make this clear. operator is pending. - Ex commands can be used to move the cursor. This can be used to call a function that does some complicated motion. - The motion is always characterwise exclusive, no matter + The motion is always charwise exclusive, no matter what ":" command is used. This means it's impossible to include the last character of a line without the line break (unless 'virtualedit' is set). diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 26850b3683..c4c164ab6c 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -1,18 +1,18 @@ -*lsp.txt* The Language Server Protocol +*lsp.txt* Nvim LSP API - NVIM REFERENCE MANUAL + NVIM REFERENCE MANUAL -Neovim Language Server Protocol (LSP) API +Nvim Language Server Protocol (LSP) API *lsp* -Neovim exposes a powerful API that conforms to Microsoft's published Language -Server Protocol specification. The documentation can be found here: +Nvim is a client to the Language Server Protocol: https://microsoft.github.io/language-server-protocol/ + Type |gO| to see the table of contents. ================================================================================ - *lsp-api* +LSP API *lsp-api* Neovim exposes a API for the language server protocol. To get the real benefits of this API, a language server must be installed. @@ -24,106 +24,15 @@ After installing a language server to your machine, you must let Neovim know how to start and interact with that language server. To do so, you can either: -- Use the |vim.lsp.add_filetype_config()|, which solves the common use-case of - a single server for one or more filetypes. This can also be used from vim - via |lsp#add_filetype_config()|. +- Use https://github.com/neovim/nvim-lsp and one of the existing servers there + or set up a new one using the `nvim_lsp/skeleton` interface (and contribute + it if you find it useful). This uses |vim.lsp.start_client()| under the + hood. - Or |vim.lsp.start_client()| and |vim.lsp.buf_attach_client()|. These are the backbone of the LSP API. These are easy to use enough for basic or more complex configurations such as in |lsp-advanced-js-example|. ================================================================================ - *lsp-filetype-config* - -These are utilities specific to filetype based configurations. - - *lsp#add_filetype_config()* - *vim.lsp.add_filetype_config()* -lsp#add_filetype_config({config}) for Vim. -vim.lsp.add_filetype_config({config}) for Lua - - These are functions which can be used to create a simple configuration which - will start a language server for a list of filetypes based on the |FileType| - event. - It will lazily start start the server, meaning that it will only start once - a matching filetype is encountered. - - The {config} options are the same as |vim.lsp.start_client()|, but - with a few additions and distinctions: - - Additional parameters:~ - `filetype` - {string} or {list} of filetypes to attach to. - `name` - A unique identifying string among all other servers configured with - |vim.lsp.add_filetype_config|. - - Differences:~ - `root_dir` - Will default to |getcwd()| instead of being required. - - NOTE: the function options in {config} like {config.on_init} are for Lua - callbacks, not Vim callbacks. -> - " Go example - call lsp#add_filetype_config({ - \ 'filetype': 'go', - \ 'name': 'gopls', - \ 'cmd': 'gopls' - \ }) - " Python example - call lsp#add_filetype_config({ - \ 'filetype': 'python', - \ 'name': 'pyls', - \ 'cmd': 'pyls' - \ }) - " Rust example - call lsp#add_filetype_config({ - \ 'filetype': 'rust', - \ 'name': 'rls', - \ 'cmd': 'rls', - \ 'capabilities': { - \ 'clippy_preference': 'on', - \ 'all_targets': v:false, - \ 'build_on_save': v:true, - \ 'wait_to_build': 0 - \ }}) -< -> - -- From Lua - vim.lsp.add_filetype_config { - name = "clangd"; - filetype = {"c", "cpp"}; - cmd = "clangd -background-index"; - capabilities = { - offsetEncoding = {"utf-8", "utf-16"}; - }; - on_init = vim.schedule_wrap(function(client, result) - if result.offsetEncoding then - client.offset_encoding = result.offsetEncoding - end - end) - } -< - *vim.lsp.copy_filetype_config()* -vim.lsp.copy_filetype_config({existing_name}, [{override_config}]) - - You can use this to copy an existing filetype configuration and change it by - specifying {override_config} which will override any properties in the - existing configuration. If you don't specify a new unique name with - {override_config.name} then it will try to create one and return it. - - Returns:~ - `name` the new configuration name. - - *vim.lsp.get_filetype_client_by_name()* -vim.lsp.get_filetype_client_by_name({name}) - - Use this to look up a client by its name created from - |vim.lsp.add_filetype_config()|. - - Returns nil if the client is not active or the name is not valid. - -================================================================================ *lsp-core-api* These are the core api functions for working with clients. You will mainly be using |vim.lsp.start_client()| and |vim.lsp.buf_attach_client()| for operations @@ -178,8 +87,8 @@ vim.lsp.start_client({config}) `callbacks` A {table} of whose keys are language server method names and the values are `function(err, method, params, client_id)` See |lsp-callbacks| for - more. This will be combined with |lsp-builtin-callbacks| to provide - defaults. + more. This will be combined with |lsp-default-callbacks| to resolve + the callbacks for a client as a fallback. `init_options` A {table} of values to pass in the initialization request as @@ -203,6 +112,12 @@ vim.lsp.start_client({config}) `vim.lsp.client_errors[code]` can be used to retrieve a human understandable string. + `before_init(initialize_params, config)` + A function which is called *before* the request `initialize` is completed. + `initialize_params` contains the parameters we are sending to the server + and `config` is the config that was passed to `start_client()` for + convenience. You can use this to modify parameters before they are sent. + `on_init(client, initialize_result)` A function which is called after the request `initialize` is completed. `initialize_result` contains `capabilities` and anything else the server @@ -346,75 +261,81 @@ vim.lsp.rpc_response_error({code}, [{message}], [{data}]) the server. ================================================================================ - *vim.lsp.builtin_callbacks* - -The |vim.lsp.builtin_callbacks| table contains the default |lsp-callbacks| -that are used when creating a new client. The keys are the LSP method names. - -The following requests and notifications have built-in callbacks defined to -handle the response in an idiomatic way. - - textDocument/completion - textDocument/declaration - textDocument/definition - textDocument/hover - textDocument/implementation - textDocument/rename - textDocument/signatureHelp - textDocument/typeDefinition +LSP CALLBACKS *lsp-callbacks* + +DEFAULT CALLBACKS ~ + *vim.lsp.default_callbacks* +The `vim.lsp.default_callbacks` table defines default callbacks used when +creating a new client. Keys are LSP method names: > + + :lua print(vim.inspect(vim.tbl_keys(vim.lsp.default_callbacks))) + +These LSP requests/notifications are defined by default: + + textDocument/publishDiagnostics window/logMessage window/showMessage -You can check these via `vim.tbl_keys(vim.lsp.builtin_callbacks)`. +You can check these via `vim.tbl_keys(vim.lsp.default_callbacks)`. -These will be automatically used and can be overridden by users (either by -modifying the |vim.lsp.builtin_callbacks| object or on a per-client basis -by passing in a table via the {callbacks} parameter on |vim.lsp.start_client| -or |vim.lsp.add_filetype_config|. +These will be used preferrentially in `vim.lsp.buf` methods when handling +requests. They will also be used when responding to server requests and +notifications. -More information about callbacks can be found in |lsp-callbacks|. +Use cases: +- Users can modify this to customize to their preferences. +- UI plugins can modify this by assigning to + `vim.lsp.default_callbacks[method]` so as to provide more specialized + handling, allowing you to leverage the UI capabilities available. UIs should + try to be conscientious of any existing changes the user may have set + already by checking for existing values. -================================================================================ - *lsp-callbacks* +Any callbacks passed directly to `request` methods on a server client will +have the highest precedence, followed by the `default_callbacks`. + +You can override the default handlers, +- globally: by modifying the `vim.lsp.default_callbacks` table +- per-client: by passing the {callbacks} table parameter to + |vim.lsp.start_client| + +Each handler has this signature: > + + function(err, method, params, client_id) Callbacks are functions which are called in a variety of situations by the client. Their signature is `function(err, method, params, client_id)` They can -be set by the {callbacks} parameter for |vim.lsp.start_client| and -|vim.lsp.add_filetype_config| or via the |vim.lsp.builtin_callbacks|. - -This will be called for: -- notifications from the server, where `err` will always be `nil` -- requests initiated by the server. The parameter `err` will be `nil` here as - well. - For these, you can respond by returning two values: `result, err` The - err must be in the format of an RPC error, which is - `{ code, message, data? }` - You can use |vim.lsp.rpc_response_error()| to help with creating this object. -- as a callback for requests initiated by the client if the request doesn't - explicitly specify a callback (such as in |vim.lsp.buf_request|). +be set by the {callbacks} parameter for |vim.lsp.start_client| or via the +|vim.lsp.default_callbacks|. + +Handlers are called for: +- Notifications from the server (`err` is always `nil`). +- Requests initiated by the server (`err` is always `nil`). + The handler can respond by returning two values: `result, err` + where `err` must be shaped like an RPC error: + `{ code, message, data? }` + You can use |vim.lsp.rpc_response_error()| to create this object. +- Handling requests initiated by the client if the request doesn't explicitly + specify a callback (such as in |vim.lsp.buf_request|). ================================================================================ - *vim.lsp.protocol* -vim.lsp.protocol +VIM.LSP.PROTOCOL *vim.lsp.protocol* - Contains constants as described in the Language Server Protocol - specification and helper functions for creating protocol related objects. +The `vim.lsp.protocol` module provides constants defined in the LSP +specification, and helper functions for creating protocol-related objects. https://github.com/microsoft/language-server-protocol/raw/gh-pages/_specifications/specification-3-14.md - Useful examples are `vim.lsp.protocol.ErrorCodes`. These objects allow - reverse lookup by either the number or string name. +Useful examples are `vim.lsp.protocol.ErrorCodes`. These objects allow reverse +lookup by either the number or string name. e.g. vim.lsp.protocol.TextDocumentSyncKind.Full == 1 vim.lsp.protocol.TextDocumentSyncKind[1] == "Full" Utility functions used internally are: - `vim.lsp.make_client_capabilities()` + `vim.lsp.protocol.make_client_capabilities()` Make a ClientCapabilities object. These are the builtin capabilities. - `vim.lsp.make_text_document_position_params()` - Make a TextDocumentPositionParams object. - `vim.lsp.resolve_capabilities(server_capabilites)` + `vim.lsp.protocol.resolve_capabilities(server_capabilites)` Creates a normalized object describing capabilities from the server capabilities. @@ -482,18 +403,16 @@ vim.lsp.buf_notify({bufnr}, {method}, {params}) ================================================================================ *lsp-logging* - *lsp#set_log_level()* -lsp#set_log_level({level}) + *vim.lsp.set_log_level()* +vim.lsp.set_log_level({level}) You can set the log level for language server client logging. Possible values: "trace", "debug", "info", "warn", "error" Default: "warn" - Example: `call lsp#set_log_level("debug")` + Example: `lua vim.lsp.set_log_level("debug")` - *lsp#get_log_path()* *vim.lsp.get_log_path()* -lsp#get_log_path() vim.lsp.get_log_path() Returns the path that LSP logs are written. @@ -508,46 +427,46 @@ vim.lsp.log_levels ================================================================================ *lsp-omnifunc* *vim.lsp.omnifunc()* - *lsp#omnifunc* -lsp#omnifunc({findstart}, {base}) vim.lsp.omnifunc({findstart}, {base}) To configure omnifunc, add the following in your init.vim: > - set omnifunc=lsp#omnifunc + " Configure for python + autocmd Filetype python setl omnifunc=v:lua.vim.lsp.omnifunc + + " Or with on_attach + start_client { + ... + on_attach = function(client, bufnr) + vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc') + end; + } - " This is optional, but you may find it useful - autocmd CompleteDone * pclose + " This is optional, but you may find it useful + autocmd CompleteDone * pclose < ================================================================================ - *lsp-vim-functions* +LSP FUNCTIONS *lsp-vim-functions* + +To use the functions from vim, it is recommended to use |v:lua| to interface +with the Lua functions. No direct vim functions are provided, but the +interface is still easy to use from mappings. These methods can be used in mappings and are the equivalent of using the request from lua as follows: > - lua vim.lsp.buf_request(0, "textDocument/hover", vim.lsp.protocol.make_text_document_position_params()) -< - - lsp#text_document_declaration() - lsp#text_document_definition() - lsp#text_document_hover() - lsp#text_document_implementation() - lsp#text_document_signature_help() - lsp#text_document_type_definition() - -> " Example config - autocmd Filetype rust,python,go,c,cpp setl omnifunc=lsp#omnifunc - nnoremap <silent> ;dc :call lsp#text_document_declaration()<CR> - nnoremap <silent> ;df :call lsp#text_document_definition()<CR> - nnoremap <silent> ;h :call lsp#text_document_hover()<CR> - nnoremap <silent> ;i :call lsp#text_document_implementation()<CR> - nnoremap <silent> ;s :call lsp#text_document_signature_help()<CR> - nnoremap <silent> ;td :call lsp#text_document_type_definition()<CR> + autocmd Filetype rust,python,go,c,cpp setl omnifunc=v:lua.vim.lsp.omnifunc + nnoremap <silent> ;dc <cmd>lua vim.lsp.buf.declaration()<CR> + nnoremap <silent> ;df <cmd>lua vim.lsp.buf.definition()<CR> + nnoremap <silent> ;h <cmd>lua vim.lsp.buf.hover()<CR> + nnoremap <silent> ;i <cmd>lua vim.lsp.buf.implementation()<CR> + nnoremap <silent> ;s <cmd>lua vim.lsp.buf.signature_help()<CR> + nnoremap <silent> ;td <cmd>lua vim.lsp.buf.type_definition()<CR> < ================================================================================ - *lsp-advanced-js-example* +LSP EXAMPLE *lsp-advanced-js-example* For more advanced configurations where just filtering by filetype isn't sufficient, you can use the `vim.lsp.start_client()` and @@ -569,7 +488,7 @@ The example will: local stat = vim.loop.fs_stat(filename) return stat and stat.type == 'directory' or false end - + local path_sep = vim.loop.os_uname().sysname == "Windows" and "\\" or "/" -- Asumes filepath is a file. local function dirname(filepath) @@ -580,11 +499,11 @@ The example will: end) return result, is_changed end - + local function path_join(...) return table.concat(vim.tbl_flatten {...}, path_sep) end - + -- Ascend the buffer's path until we find the rootdir. -- is_root_path is a function which returns bool local function buffer_find_root_dir(bufnr, is_root_path) @@ -606,7 +525,7 @@ The example will: end end end - + -- A table to store our root_dir to client_id lookup. We want one LSP per -- root directory, and this is how we assert that. local javascript_lsps = {} @@ -617,14 +536,14 @@ The example will: ["typescript"] = true; ["typescript.jsx"] = true; } - + -- Create a template configuration for a server to start, minus the root_dir -- which we will specify later. local javascript_lsp_config = { name = "javascript"; cmd = { path_join(os.getenv("JAVASCRIPT_LANGUAGE_SERVER_DIRECTORY"), "lib", "language-server-stdio.js") }; } - + -- This needs to be global so that we can call it from the autocmd. function check_start_javascript_lsp() local bufnr = vim.api.nvim_get_current_buf() @@ -641,7 +560,7 @@ The example will: end) -- We couldn't find a root directory, so ignore this file. if not root_dir then return end - + -- Check if we have a client alredy or start and store it. local client_id = javascript_lsps[root_dir] if not client_id then @@ -655,7 +574,7 @@ The example will: -- are already attached. vim.lsp.buf_attach_client(bufnr, client_id) end - + vim.api.nvim_command [[autocmd BufReadPost * lua check_start_javascript_lsp()]] < diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index edcf246295..c0da06ffe3 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -876,16 +876,23 @@ tbl_add_reverse_lookup({o}) *vim.tbl_add_reverse_lookup()* Parameters: ~ {o} table The table to add the reverse to. -list_extend({dst}, {src}) *vim.list_extend()* +list_extend({dst}, {src}, {start}, {finish}) *vim.list_extend()* Extends a list-like table with the values of another list-like table. + NOTE: This mutates dst! + Parameters: ~ - {dst} The list which will be modified and appended to. - {src} The list from which values will be inserted. + {dst} list which will be modified and appended to. + {src} list from which values will be inserted. + {start} Start index on src. defaults to 1 + {finish} Final index on src. defaults to #src + + Return: ~ + dst See also: ~ - |extend()| + |vim.tbl_extend()| tbl_flatten({t}) *vim.tbl_flatten()* Creates a copy of a list-like table such that any nested diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index abe86749c4..58c0d832e6 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -786,7 +786,7 @@ g@{motion} Call the function set by the 'operatorfunc' option. character of the text. The function is called with one String argument: "line" {motion} was |linewise| - "char" {motion} was |characterwise| + "char" {motion} was |charwise| "block" {motion} was |blockwise-visual| Although "block" would rarely appear, since it can only result from Visual mode where "g@" is not useful. diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt index 965b062728..bcfd985e71 100644 --- a/runtime/doc/message.txt +++ b/runtime/doc/message.txt @@ -679,8 +679,8 @@ no argument has been specified. Invalid argument: {arg} Duplicate argument: {arg} -An Ex command or function has been executed, but an invalid argument has been -specified. +Ex command or function has been executed, but an invalid argument was +specified. Or a non-executable command was given to |system()|. *E488* > Trailing characters diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt index e93c833c76..07ff4cf030 100644 --- a/runtime/doc/motion.txt +++ b/runtime/doc/motion.txt @@ -60,11 +60,11 @@ After applying the operator the cursor is mostly left at the start of the text that was operated upon. For example, "yfe" doesn't move the cursor, but "yFe" moves the cursor leftwards to the "e" where the yank started. - *linewise* *characterwise* + *linewise* *charwise* *characterwise* The operator either affects whole lines, or the characters between the start and end position. Generally, motions that move between lines affect lines (are linewise), and motions that move within a line affect characters (are -characterwise). However, there are some exceptions. +charwise). However, there are some exceptions. *exclusive* *inclusive* Character motion is either inclusive or exclusive. When inclusive, the @@ -106,10 +106,10 @@ This cannot be repeated: > d:if 1<CR> call search("f")<CR> endif<CR> -Note that when using ":" any motion becomes characterwise exclusive. +Note that when using ":" any motion becomes charwise exclusive. *forced-motion* -FORCING A MOTION TO BE LINEWISE, CHARACTERWISE OR BLOCKWISE +FORCING A MOTION TO BE LINEWISE, CHARWISE OR BLOCKWISE When a motion is not of the type you would like to use, you can force another type by using "v", "V" or CTRL-V just after the operator. @@ -121,22 +121,22 @@ deletes from the cursor position until the character below the cursor > d<C-V>j deletes the character under the cursor and the character below the cursor. > -Be careful with forcing a linewise movement to be used characterwise or -blockwise, the column may not always be defined. +Be careful with forcing a linewise movement to be used charwise or blockwise, +the column may not always be defined. *o_v* v When used after an operator, before the motion command: Force - the operator to work characterwise, also when the motion is + the operator to work charwise, also when the motion is linewise. If the motion was linewise, it will become |exclusive|. - If the motion already was characterwise, toggle + If the motion already was charwise, toggle inclusive/exclusive. This can be used to make an exclusive motion inclusive and an inclusive motion exclusive. *o_V* V When used after an operator, before the motion command: Force the operator to work linewise, also when the motion is - characterwise. + charwise. *o_CTRL-V* CTRL-V When used after an operator, before the motion command: Force @@ -508,36 +508,36 @@ aw "a word", select [count] words (see |word|). Leading or trailing white space is included, but not counted. When used in Visual linewise mode "aw" switches to - Visual characterwise mode. + Visual charwise mode. *v_iw* *iw* iw "inner word", select [count] words (see |word|). White space between words is counted too. When used in Visual linewise mode "iw" switches to - Visual characterwise mode. + Visual charwise mode. *v_aW* *aW* aW "a WORD", select [count] WORDs (see |WORD|). Leading or trailing white space is included, but not counted. When used in Visual linewise mode "aW" switches to - Visual characterwise mode. + Visual charwise mode. *v_iW* *iW* iW "inner WORD", select [count] WORDs (see |WORD|). White space between words is counted too. When used in Visual linewise mode "iW" switches to - Visual characterwise mode. + Visual charwise mode. *v_as* *as* as "a sentence", select [count] sentences (see |sentence|). - When used in Visual mode it is made characterwise. + When used in Visual mode it is made charwise. *v_is* *is* is "inner sentence", select [count] sentences (see |sentence|). - When used in Visual mode it is made characterwise. + When used in Visual mode it is made charwise. *v_ap* *ap* ap "a paragraph", select [count] paragraphs (see @@ -558,14 +558,14 @@ a[ "a [] block", select [count] '[' ']' blocks. This goes backwards to the [count] unclosed '[', and finds the matching ']'. The enclosed text is selected, including the '[' and ']'. - When used in Visual mode it is made characterwise. + When used in Visual mode it is made charwise. i] *v_i]* *v_i[* *i]* *i[* i[ "inner [] block", select [count] '[' ']' blocks. This goes backwards to the [count] unclosed '[', and finds the matching ']'. The enclosed text is selected, excluding the '[' and ']'. - When used in Visual mode it is made characterwise. + When used in Visual mode it is made charwise. a) *v_a)* *a)* *a(* a( *vab* *v_ab* *v_a(* *ab* @@ -573,54 +573,54 @@ ab "a block", select [count] blocks, from "[count] [(" to the matching ')', including the '(' and ')' (see |[(|). Does not include white space outside of the parenthesis. - When used in Visual mode it is made characterwise. + When used in Visual mode it is made charwise. i) *v_i)* *i)* *i(* i( *vib* *v_ib* *v_i(* *ib* ib "inner block", select [count] blocks, from "[count] [(" to the matching ')', excluding the '(' and ')' (see |[(|). - When used in Visual mode it is made characterwise. + When used in Visual mode it is made charwise. a> *v_a>* *v_a<* *a>* *a<* a< "a <> block", select [count] <> blocks, from the [count]'th unmatched '<' backwards to the matching '>', including the '<' and '>'. - When used in Visual mode it is made characterwise. + When used in Visual mode it is made charwise. i> *v_i>* *v_i<* *i>* *i<* i< "inner <> block", select [count] <> blocks, from the [count]'th unmatched '<' backwards to the matching '>', excluding the '<' and '>'. - When used in Visual mode it is made characterwise. + When used in Visual mode it is made charwise. *v_at* *at* at "a tag block", select [count] tag blocks, from the [count]'th unmatched "<aaa>" backwards to the matching "</aaa>", including the "<aaa>" and "</aaa>". See |tag-blocks| about the details. - When used in Visual mode it is made characterwise. + When used in Visual mode it is made charwise. *v_it* *it* it "inner tag block", select [count] tag blocks, from the [count]'th unmatched "<aaa>" backwards to the matching "</aaa>", excluding the "<aaa>" and "</aaa>". See |tag-blocks| about the details. - When used in Visual mode it is made characterwise. + When used in Visual mode it is made charwise. a} *v_a}* *a}* *a{* a{ *v_aB* *v_a{* *aB* aB "a Block", select [count] Blocks, from "[count] [{" to the matching '}', including the '{' and '}' (see |[{|). - When used in Visual mode it is made characterwise. + When used in Visual mode it is made charwise. i} *v_i}* *i}* *i{* i{ *v_iB* *v_i{* *iB* iB "inner Block", select [count] Blocks, from "[count] [{" to the matching '}', excluding the '{' and '}' (see |[{|). - When used in Visual mode it is made characterwise. + When used in Visual mode it is made charwise. a" *v_aquote* *aquote* a' *v_a'* *a'* @@ -634,7 +634,7 @@ a` *v_a`* *a`* start of the line. Any trailing white space is included, unless there is none, then leading white space is included. - When used in Visual mode it is made characterwise. + When used in Visual mode it is made charwise. Repeating this object in Visual mode another string is included. A count is currently not used. diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 30aebfc5ff..95265fa153 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -2362,7 +2362,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'fillchars'* *'fcs'* 'fillchars' 'fcs' string (default "") - local to window + global or local to window |global-local| Characters to fill the statuslines and vertical separators. It is a comma separated list of items: @@ -3671,7 +3671,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'listchars'* *'lcs'* 'listchars' 'lcs' string (default: "tab:> ,trail:-,nbsp:+" Vi default: "eol:$") - local to window + global or local to window |global-local| Strings to use in 'list' mode and for the |:list| command. It is a comma separated list of string settings. @@ -6014,6 +6014,8 @@ A jump table for the options with a short description can be found at |Q_op|. vsplit Just like "split" but split vertically. newtab Like "split", but open a new tab page. Overrules "split" when both are present. + uselast If included, jump to the previously used window when + jumping to errors with |quickfix| commands. *'synmaxcol'* *'smc'* 'synmaxcol' 'smc' number (default 3000) diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 7dbbb2d424..e3f0d593a7 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -1270,7 +1270,7 @@ exactly four MessagePack objects: Key Type Def Description ~ rt UInteger 0 Register type: No Description ~ - 0 |characterwise-register| + 0 |charwise-register| 1 |linewise-register| 2 |blockwise-register| rw UInteger 0 Register width. Only valid diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index a2f19593ae..d5f4a59ab3 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -201,8 +201,8 @@ the editor. sent from Nvim, like for |ui-cmdline|. ["mode_change", mode, mode_idx] - The mode changed. The first parameter `mode` is a string representing - the current mode. `mode_idx` is an index into the array received in + Editor mode changed. The `mode` parameter is a string representing + the current mode. `mode_idx` is an index into the array emitted in the `mode_info_set` event. UIs should change the cursor style according to the properties specified in the corresponding item. The set of modes reported will change in new versions of Nvim, for @@ -211,11 +211,11 @@ the editor. ["mouse_on"] ["mouse_off"] - Tells the client whether mouse support, as determined by |'mouse'| - option, is considered to be active in the current mode. This is mostly - useful for a terminal frontend, or other situations where Nvim mouse - would conflict with other usages of the mouse. It is safe for a client - to ignore this and always send mouse events. + |'mouse'| was enabled/disabled in the current editor mode. Useful for + a terminal UI, or other situations where Nvim mouse would conflict + with other usages of the mouse. UIs may ignore this and always send + mouse input, because 'mouse' decides the behavior of |nvim_input()| + implicitly. ["busy_start"] ["busy_stop"] diff --git a/runtime/doc/visual.txt b/runtime/doc/visual.txt index ccbbc092ec..0052382044 100644 --- a/runtime/doc/visual.txt +++ b/runtime/doc/visual.txt @@ -48,7 +48,7 @@ position. ============================================================================== 2. Starting and stopping Visual mode *visual-start* - *v* *characterwise-visual* + *v* *charwise-visual* [count]v Start Visual mode per character. With [count] select the same number of characters or lines as used for the last Visual operation, but at @@ -74,7 +74,7 @@ position. If you use <Esc>, click the left mouse button or use any command that does a jump to another buffer while in Visual mode, the highlighting stops -and no text is affected. Also when you hit "v" in characterwise Visual mode, +and no text is affected. Also when you hit "v" in charwise Visual mode, "CTRL-V" in blockwise Visual mode or "V" in linewise Visual mode. If you hit CTRL-Z the highlighting stops and the editor is suspended or a new shell is started |CTRL-Z|. @@ -477,7 +477,7 @@ Commands in Select mode: Otherwise, typed characters are handled as in Visual mode. When using an operator in Select mode, and the selection is linewise, the -selected lines are operated upon, but like in characterwise selection. For +selected lines are operated upon, but like in charwise selection. For example, when a whole line is deleted, it can later be pasted in the middle of a line. @@ -510,7 +510,7 @@ gV Avoid the automatic reselection of the Visual area selection. *gh* -gh Start Select mode, characterwise. This is like "v", +gh Start Select mode, charwise. This is like "v", but starts Select mode instead of Visual mode. Mnemonic: "get highlighted". diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 8ce45b6a50..e27d234a96 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -1,7 +1,7 @@ " Vim support file to detect file types " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2019 Aug 26 +" Last Change: 2019 Nov 26 " Listen very carefully, I will say this only once if exists("did_load_filetypes") @@ -796,8 +796,8 @@ au BufNewFile,BufRead *.java,*.jav setf java " JavaCC au BufNewFile,BufRead *.jj,*.jjt setf javacc -" JavaScript, ECMAScript -au BufNewFile,BufRead *.js,*.javascript,*.es,*.mjs setf javascript +" JavaScript, ECMAScript, ES module script, CommonJS script +au BufNewFile,BufRead *.js,*.javascript,*.es,*.mjs,*.cjs setf javascript " JavaScript with React au BufNewFile,BufRead *.jsx setf javascriptreact diff --git a/runtime/ftplugin/man.vim b/runtime/ftplugin/man.vim index 6c7f095f62..081181cfe9 100644 --- a/runtime/ftplugin/man.vim +++ b/runtime/ftplugin/man.vim @@ -1,4 +1,4 @@ -" Maintainer: Anmol Sethi <anmol@aubble.com> +" Maintainer: Anmol Sethi <hi@nhooyr.io> " Previous Maintainer: SungHyun Nam <goweol@gmail.com> if exists('b:did_ftplugin') || &filetype !=# 'man' @@ -20,13 +20,12 @@ setlocal wrap breakindent linebreak setlocal nonumber norelativenumber setlocal foldcolumn=0 colorcolumn=0 nolist nofoldenable +setlocal tagfunc=man#goto_tag + if !exists('g:no_plugin_maps') && !exists('g:no_man_maps') nnoremap <silent> <buffer> j gj nnoremap <silent> <buffer> k gk 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> if 1 == bufnr('%') || s:pager nnoremap <silent> <buffer> <nowait> q :lclose<CR>:q<CR> else diff --git a/runtime/keymap/russian-jcukenwintype.vim b/runtime/keymap/russian-jcukenwintype.vim new file mode 100644 index 0000000000..25d6286e24 --- /dev/null +++ b/runtime/keymap/russian-jcukenwintype.vim @@ -0,0 +1,112 @@ +" Vim Keymap file for russian characters, layout 'jcuken', MS Windows +" Typewriter variant (slightly incompatible with XFree86 'ru' keymap - +" makes use of NUMERO SIGN) +" Useful mainly with utf-8 but may work with other encodings + +" Derived from russian-jcuken.vim by Artem Chuprina <ran@ran.pp.ru> +" Typewriter variant of standart russian layout +" Maintainer: Denis Proskurin <danwerspb@gmail.com> +" Last Changed: 2015 May 15 + +" All characters are given literally, conversion to another encoding (e.g., +" UTF-8) should work. + +scriptencoding utf-8 + +let b:keymap_name = "ru" + +loadkeymap +F А CYRILLIC CAPITAL LETTER A +< Б CYRILLIC CAPITAL LETTER BE +D В CYRILLIC CAPITAL LETTER VE +U Г CYRILLIC CAPITAL LETTER GHE +L Д CYRILLIC CAPITAL LETTER DE +T Е CYRILLIC CAPITAL LETTER IE +? Ё CYRILLIC CAPITAL LETTER IO +: Ж CYRILLIC CAPITAL LETTER ZHE +P З CYRILLIC CAPITAL LETTER ZE +B И CYRILLIC CAPITAL LETTER I +Q Й CYRILLIC CAPITAL LETTER SHORT I +R К CYRILLIC CAPITAL LETTER KA +K Л CYRILLIC CAPITAL LETTER EL +V М CYRILLIC CAPITAL LETTER EM +Y Н CYRILLIC CAPITAL LETTER EN +J О CYRILLIC CAPITAL LETTER O +G П CYRILLIC CAPITAL LETTER PE +H Р CYRILLIC CAPITAL LETTER ER +C С CYRILLIC CAPITAL LETTER ES +N Т CYRILLIC CAPITAL LETTER TE +E У CYRILLIC CAPITAL LETTER U +A Ф CYRILLIC CAPITAL LETTER EF +{ Х CYRILLIC CAPITAL LETTER HA +W Ц CYRILLIC CAPITAL LETTER TSE +X Ч CYRILLIC CAPITAL LETTER CHE +I Ш CYRILLIC CAPITAL LETTER SHA +O Щ CYRILLIC CAPITAL LETTER SHCHA +} Ъ CYRILLIC CAPITAL LETTER HARD SIGN +S Ы CYRILLIC CAPITAL LETTER YERU +M Ь CYRILLIC CAPITAL LETTER SOFT SIGN +\" Э CYRILLIC CAPITAL LETTER E +> Ю CYRILLIC CAPITAL LETTER YU +Z Я CYRILLIC CAPITAL LETTER YA +f а CYRILLIC SMALL LETTER A +, б CYRILLIC SMALL LETTER BE +d в CYRILLIC SMALL LETTER VE +u г CYRILLIC SMALL LETTER GHE +l д CYRILLIC SMALL LETTER DE +t е CYRILLIC SMALL LETTER IE +/ ё CYRILLIC SMALL LETTER IO +; ж CYRILLIC SMALL LETTER ZHE +p з CYRILLIC SMALL LETTER ZE +b и CYRILLIC SMALL LETTER I +q й CYRILLIC SMALL LETTER SHORT I +r к CYRILLIC SMALL LETTER KA +k л CYRILLIC SMALL LETTER EL +v м CYRILLIC SMALL LETTER EM +y н CYRILLIC SMALL LETTER EN +j о CYRILLIC SMALL LETTER O +g п CYRILLIC SMALL LETTER PE +h р CYRILLIC SMALL LETTER ER +c с CYRILLIC SMALL LETTER ES +n т CYRILLIC SMALL LETTER TE +e у CYRILLIC SMALL LETTER U +a ф CYRILLIC SMALL LETTER EF +[ х CYRILLIC SMALL LETTER HA +w ц CYRILLIC SMALL LETTER TSE +x ч CYRILLIC SMALL LETTER CHE +i ш CYRILLIC SMALL LETTER SHA +o щ CYRILLIC SMALL LETTER SHCHA +] ъ CYRILLIC SMALL LETTER HARD SIGN +s ы CYRILLIC SMALL LETTER YERU +m ь CYRILLIC SMALL LETTER SOFT SIGN +' э CYRILLIC SMALL LETTER E +. ю CYRILLIC SMALL LETTER YU +z я CYRILLIC SMALL LETTER YA +` | +1 № +2 - +3 / +4 " +5 : +6 , +7 . +8 _ +9 ? +0 % +- ! += ; +~ + +! 1 +@ 2 +# 3 +$ 4 +% 5 +^ 6 +& 7 +* 8 +( 9 +) 0 +_ = ++ \\ +\\ ) +\| ( diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 9dbe03dace..82f4fda66f 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1,9 +1,10 @@ -local builtin_callbacks = require 'vim.lsp.builtin_callbacks' +local default_callbacks = require 'vim.lsp.callbacks' local log = require 'vim.lsp.log' local lsp_rpc = require 'vim.lsp.rpc' local protocol = require 'vim.lsp.protocol' local util = require 'vim.lsp.util' +local vim = vim local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option = vim.api.nvim_err_writeln, vim.api.nvim_buf_get_lines, vim.api.nvim_command, vim.api.nvim_buf_get_option local uv = vim.loop @@ -12,7 +13,8 @@ local validate = vim.validate local lsp = { protocol = protocol; - builtin_callbacks = builtin_callbacks; + callbacks = default_callbacks; + buf = require'vim.lsp.buf'; util = util; -- Allow raw RPC access. rpc = lsp_rpc; @@ -23,9 +25,13 @@ local lsp = { -- format_rpc_error = lsp_rpc.format_rpc_error; } --- TODO consider whether 'eol' or 'fixeol' should change the nvim_buf_get_lines that send. -- TODO improve handling of scratch buffers with LSP attached. +local function err_message(...) + nvim_err_writeln(table.concat(vim.tbl_flatten{...})) + nvim_command("redraw") +end + local function resolve_bufnr(bufnr) validate { bufnr = { bufnr, 'n', true } } if bufnr == nil or bufnr == 0 then @@ -93,11 +99,7 @@ local function for_each_buffer_client(bufnr, callback) return end for client_id in pairs(client_ids) do - -- This is unlikely to happen. Could only potentially happen in a race - -- condition between literally a single statement. - -- We could skip this error, but let's error for now. local client = active_clients[client_id] - -- or error(string.format("Client %d has already shut down.", client_id)) if client then callback(client, client_id) end @@ -155,13 +157,13 @@ local function validate_client_config(config) root_dir = { config.root_dir, is_dir, "directory" }; callbacks = { config.callbacks, "t", true }; capabilities = { config.capabilities, "t", true }; - -- cmd = { config.cmd, "s", false }; cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), "directory" }; cmd_env = { config.cmd_env, "f", true }; name = { config.name, 's', true }; on_error = { config.on_error, "f", true }; on_exit = { config.on_exit, "f", true }; on_init = { config.on_init, "f", true }; + before_init = { config.before_init, "f", true }; offset_encoding = { config.offset_encoding, "s", true }; } local cmd, cmd_args = validate_command(config.cmd) @@ -175,6 +177,14 @@ local function validate_client_config(config) } end +local function buf_get_full_text(bufnr) + local text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, true), '\n') + if nvim_buf_get_option(bufnr, 'eol') then + text = text .. '\n' + end + return text +end + local function text_document_did_open_handler(bufnr, client) if not client.resolved_capabilities.text_document_open_close then return @@ -188,7 +198,7 @@ local function text_document_did_open_handler(bufnr, client) uri = vim.uri_from_bufnr(bufnr); -- TODO make sure our filetypes are compatible with languageId names. languageId = nvim_buf_get_option(bufnr, 'filetype'); - text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, false), '\n'); + text = buf_get_full_text(bufnr); } } client.notify('textDocument/didOpen', params) @@ -254,6 +264,12 @@ end -- possible errors. `vim.lsp.client_errors[code]` can be used to retrieve a -- human understandable string. -- +-- before_init(initialize_params, config): A function which is called *before* +-- the request `initialize` is completed. `initialize_params` contains +-- the parameters we are sending to the server and `config` is the config that +-- was passed to `start_client()` for convenience. You can use this to modify +-- parameters before they are sent. +-- -- on_init(client, initialize_result): A function which is called after the -- request `initialize` is completed. `initialize_result` contains -- `capabilities` and anything else the server may send. For example, `clangd` @@ -283,19 +299,19 @@ function lsp.start_client(config) local client_id = next_client_id() - local callbacks = tbl_extend("keep", config.callbacks or {}, builtin_callbacks) - -- Copy metatable if it has one. - if config.callbacks and config.callbacks.__metatable then - setmetatable(callbacks, getmetatable(config.callbacks)) - end + local callbacks = config.callbacks or {} local name = config.name or tostring(client_id) local log_prefix = string.format("LSP[%s]", name) local handlers = {} + local function resolve_callback(method) + return callbacks[method] or default_callbacks[method] + end + function handlers.notification(method, params) local _ = log.debug() and log.debug('notification', method, params) - local callback = callbacks[method] + local callback = resolve_callback(method) if callback then -- Method name is provided here for convenience. callback(nil, method, params, client_id) @@ -304,7 +320,7 @@ function lsp.start_client(config) function handlers.server_request(method, params) local _ = log.debug() and log.debug('server_request', method, params) - local callback = callbacks[method] + local callback = resolve_callback(method) if callback then local _ = log.debug() and log.debug("server_request: found callback for", method) return callback(nil, method, params, client_id) @@ -315,12 +331,12 @@ function lsp.start_client(config) function handlers.on_error(code, err) local _ = log.error() and log.error(log_prefix, "on_error", { code = lsp.client_errors[code], err = err }) - nvim_err_writeln(string.format('%s: Error %s: %q', log_prefix, lsp.client_errors[code], vim.inspect(err))) + err_message(log_prefix, ': Error ', lsp.client_errors[code], ': ', vim.inspect(err)) if config.on_error then local status, usererr = pcall(config.on_error, code, err) if not status then local _ = log.error() and log.error(log_prefix, "user on_error failed", { err = usererr }) - nvim_err_writeln(log_prefix.." user on_error failed: "..tostring(usererr)) + err_message(log_prefix, ' user on_error failed: ', tostring(usererr)) end end end @@ -328,9 +344,19 @@ function lsp.start_client(config) function handlers.on_exit(code, signal) active_clients[client_id] = nil uninitialized_clients[client_id] = nil - for _, client_ids in pairs(all_buffer_active_clients) do + local active_buffers = {} + for bufnr, client_ids in pairs(all_buffer_active_clients) do + if client_ids[client_id] then + table.insert(active_buffers, bufnr) + end client_ids[client_id] = nil end + -- Buffer level cleanup + vim.schedule(function() + for _, bufnr in ipairs(active_buffers) do + util.buf_clear_diagnostics(bufnr) + end + end) if config.on_exit then pcall(config.on_exit, code, signal, client_id) end @@ -372,7 +398,6 @@ function lsp.start_client(config) -- The rootUri of the workspace. Is null if no folder is open. If both -- `rootPath` and `rootUri` are set `rootUri` wins. rootUri = vim.uri_from_fname(config.root_dir); --- rootUri = vim.uri_from_fname(vim.fn.expand("%:p:h")); -- User provided initialization options. initializationOptions = config.init_options; -- The capabilities provided by the client (editor or tool) @@ -396,11 +421,15 @@ function lsp.start_client(config) -- } workspaceFolders = nil; } + if config.before_init then + -- TODO(ashkan) handle errors here. + pcall(config.before_init, initialize_params, config) + end local _ = log.debug() and log.debug(log_prefix, "initialize_params", initialize_params) rpc.request('initialize', initialize_params, function(init_err, result) assert(not init_err, tostring(init_err)) assert(result, "server sent empty result") - rpc.notify('initialized', {}) + rpc.notify('initialized', {[vim.type_idx]=vim.types.dictionary}) client.initialized = true uninitialized_clients[client_id] = nil client.server_capabilities = assert(result.capabilities, "initialize result doesn't contain capabilities") @@ -432,15 +461,15 @@ function lsp.start_client(config) local function unsupported_method(method) local msg = "server doesn't support "..method local _ = log.warn() and log.warn(msg) - nvim_err_writeln(msg) + err_message(msg) return lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound, msg) end --- Checks capabilities before rpc.request-ing. function client.request(method, params, callback) if not callback then - callback = client.callbacks[method] - or error(string.format("request callback is empty and no default was found for client %s", client.name)) + callback = resolve_callback(method) + or error("not found: request callback for client "..client.name) end local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, callback) -- TODO keep these checks or just let it go anyway? @@ -551,7 +580,7 @@ do end) local full_changes = once(function() return { - text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, false), "\n"); + text = buf_get_full_text(bufnr); }; end) local uri = vim.uri_from_bufnr(bufnr) @@ -844,7 +873,7 @@ function lsp.omnifunc(findstart, base) position = { -- 0-indexed for both line and character line = pos[1] - 1, - character = pos[2], + character = vim.str_utfindex(line, pos[2]), }; -- The completion context. This is only available if the client specifies -- to send this using `ClientCapabilities.textDocument.completion.contextSupport === true` @@ -869,134 +898,8 @@ function lsp.omnifunc(findstart, base) end end ---- ---- FileType based configuration utility ---- - -local all_filetype_configs = {} - --- Lookup a filetype config client by its name. -function lsp.get_filetype_client_by_name(name) - local config = all_filetype_configs[name] - if config.client_id then - return active_clients[config.client_id] - end -end - -local function start_filetype_config(config) - config.client_id = lsp.start_client(config) - nvim_command(string.format( - "autocmd FileType %s silent lua vim.lsp.buf_attach_client(0, %d)", - table.concat(config.filetypes, ','), - config.client_id)) - return config.client_id -end - --- Easy configuration option for common LSP use-cases. --- This will lazy initialize the client when the filetypes specified are --- encountered and attach to those buffers. --- --- The configuration options are the same as |vim.lsp.start_client()|, but --- with a few additions and distinctions: --- --- Additional parameters: --- - filetype: {string} or {list} of filetypes to attach to. --- - name: A unique string among all other servers configured with --- |vim.lsp.add_filetype_config|. --- --- Differences: --- - root_dir: will default to |getcwd()| --- -function lsp.add_filetype_config(config) - -- Additional defaults. - -- Keep a copy of the user's input for debugging reasons. - local user_config = config - config = tbl_extend("force", {}, user_config) - config.root_dir = config.root_dir or uv.cwd() - -- Validate config. - validate_client_config(config) - validate { - name = { config.name, 's' }; - } - assert(config.filetype, "config must have 'filetype' key") - - local filetypes - if type(config.filetype) == 'string' then - filetypes = { config.filetype } - elseif type(config.filetype) == 'table' then - filetypes = config.filetype - assert(not tbl_isempty(filetypes), "config.filetype must not be an empty table") - else - error("config.filetype must be a string or a list of strings") - end - - if all_filetype_configs[config.name] then - -- If the client exists, then it is likely that they are doing some kind of - -- reload flow, so let's not throw an error here. - if all_filetype_configs[config.name].client_id then - -- TODO log here? It might be unnecessarily annoying. - return - end - error(string.format('A configuration with the name %q already exists. They must be unique', config.name)) - end - - all_filetype_configs[config.name] = tbl_extend("keep", config, { - client_id = nil; - filetypes = filetypes; - user_config = user_config; - }) - - nvim_command(string.format( - "autocmd FileType %s ++once silent lua vim.lsp._start_filetype_config_client(%q)", - table.concat(filetypes, ','), - config.name)) -end - --- Create a copy of an existing configuration, and override config with values --- from new_config. --- This is useful if you wish you create multiple LSPs with different root_dirs --- or other use cases. --- --- You can specify a new unique name, but if you do not, a unique name will be --- created like `name-dup_count`. --- --- existing_name: the name of the existing config to copy. --- new_config: the new configuration options. @see |vim.lsp.start_client()|. --- @returns string the new name. -function lsp.copy_filetype_config(existing_name, new_config) - local config = all_filetype_configs[existing_name] - or error(string.format("Configuration with name %q doesn't exist", existing_name)) - config = tbl_extend("force", config, new_config or {}) - config.client_id = nil - config.original_config_name = existing_name - - -- If the user didn't rename it, we will. - if config.name == existing_name then - -- Create a new, unique name. - local duplicate_count = 0 - for _, conf in pairs(all_filetype_configs) do - if conf.original_config_name == existing_name then - duplicate_count = duplicate_count + 1 - end - end - config.name = string.format("%s-%d", existing_name, duplicate_count + 1) - end - print("New config name:", config.name) - lsp.add_filetype_config(config) - return config.name -end - --- Autocmd handler to actually start the client when an applicable filetype is --- encountered. -function lsp._start_filetype_config_client(name) - local config = all_filetype_configs[name] - -- If it exists and is running, don't make it again. - if config.client_id and active_clients[config.client_id] then - -- TODO log here? - return - end - lsp.buf_attach_client(0, start_filetype_config(config)) - return config.client_id +function lsp.client_is_stopped(client_id) + return active_clients[client_id] == nil end --- @@ -1023,7 +926,7 @@ end -- Print some debug information about all LSP related things. -- The output of this function should not be relied upon and may change. function lsp.print_debug_info() - print(vim.inspect({ clients = active_clients, filetype_configs = all_filetype_configs })) + print(vim.inspect({ clients = active_clients })) end -- Log level dictionary with reverse lookup as well. diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua new file mode 100644 index 0000000000..a6a05fb095 --- /dev/null +++ b/runtime/lua/vim/lsp/buf.lua @@ -0,0 +1,136 @@ +local vim = vim +local validate = vim.validate +local api = vim.api +local vfn = vim.fn +local util = require 'vim.lsp.util' +local list_extend = vim.list_extend + +local M = {} + +local function ok_or_nil(status, ...) + if not status then return end + return ... +end +local function npcall(fn, ...) + return ok_or_nil(pcall(fn, ...)) +end + +local function request(method, params, callback) + validate { + method = {method, 's'}; + callback = {callback, 'f', true}; + } + return vim.lsp.buf_request(0, method, params, callback) +end + +function M.hover() + local params = util.make_position_params() + request('textDocument/hover', params) +end + +function M.peek_definition() + local params = util.make_position_params() + request('textDocument/peekDefinition', params) +end + + +function M.declaration() + local params = util.make_position_params() + request('textDocument/declaration', params) +end + +function M.definition() + local params = util.make_position_params() + request('textDocument/definition', params) +end + +function M.type_definition() + local params = util.make_position_params() + request('textDocument/typeDefinition', params) +end + +function M.implementation() + local params = util.make_position_params() + request('textDocument/implementation', params) +end + +function M.signature_help() + local params = util.make_position_params() + request('textDocument/signatureHelp', params) +end + +-- TODO(ashkan) ? +function M.completion(context) + local params = util.make_position_params() + params.context = context + return request('textDocument/completion', params) +end + +function M.formatting(options) + validate { options = {options, 't', true} } + options = vim.tbl_extend('keep', options or {}, { + tabSize = vim.bo.tabstop; + insertSpaces = vim.bo.expandtab; + }) + local params = { + textDocument = { uri = vim.uri_from_bufnr(0) }; + options = options; + } + return request('textDocument/formatting', params) +end + +function M.range_formatting(options, start_pos, end_pos) + validate { + options = {options, 't', true}; + start_pos = {start_pos, 't', true}; + end_pos = {end_pos, 't', true}; + } + options = vim.tbl_extend('keep', options or {}, { + tabSize = vim.bo.tabstop; + insertSpaces = vim.bo.expandtab; + }) + local A = list_extend({}, start_pos or api.nvim_buf_get_mark(0, '<')) + local B = list_extend({}, end_pos or api.nvim_buf_get_mark(0, '>')) + -- convert to 0-index + A[1] = A[1] - 1 + B[1] = B[1] - 1 + -- account for encoding. + if A[2] > 0 then + A = {A[1], util.character_offset(0, A[1], A[2])} + end + if B[2] > 0 then + B = {B[1], util.character_offset(0, B[1], B[2])} + end + local params = { + textDocument = { uri = vim.uri_from_bufnr(0) }; + range = { + start = { line = A[1]; character = A[2]; }; + ["end"] = { line = B[1]; character = B[2]; }; + }; + options = options; + } + return request('textDocument/rangeFormatting', params) +end + +function M.rename(new_name) + -- TODO(ashkan) use prepareRename + -- * result: [`Range`](#range) \| `{ range: Range, placeholder: string }` \| `null` describing the range of the string to rename and optionally a placeholder text of the string content to be renamed. If `null` is returned then it is deemed that a 'textDocument/rename' request is not valid at the given position. + local params = util.make_position_params() + new_name = new_name or npcall(vfn.input, "New Name: ") + if not (new_name and #new_name > 0) then return end + params.newName = new_name + request('textDocument/rename', params) +end + +function M.references(context) + validate { context = { context, 't', true } } + local params = util.make_position_params() + params.context = context or { + includeDeclaration = true; + } + params[vim.type_idx] = vim.types.dictionary + request('textDocument/references', params) +end + +return M +-- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/builtin_callbacks.lua b/runtime/lua/vim/lsp/builtin_callbacks.lua deleted file mode 100644 index cc739ce3ad..0000000000 --- a/runtime/lua/vim/lsp/builtin_callbacks.lua +++ /dev/null @@ -1,296 +0,0 @@ ---- Implements the following default callbacks: --- --- vim.api.nvim_buf_set_lines(0, 0, 0, false, vim.tbl_keys(vim.lsp.builtin_callbacks)) --- - --- textDocument/completion --- textDocument/declaration --- textDocument/definition --- textDocument/hover --- textDocument/implementation --- textDocument/publishDiagnostics --- textDocument/rename --- textDocument/signatureHelp --- textDocument/typeDefinition --- TODO codeLens/resolve --- TODO completionItem/resolve --- TODO documentLink/resolve --- TODO textDocument/codeAction --- TODO textDocument/codeLens --- TODO textDocument/documentHighlight --- TODO textDocument/documentLink --- TODO textDocument/documentSymbol --- TODO textDocument/formatting --- TODO textDocument/onTypeFormatting --- TODO textDocument/rangeFormatting --- TODO textDocument/references --- window/logMessage --- window/showMessage - -local log = require 'vim.lsp.log' -local protocol = require 'vim.lsp.protocol' -local util = require 'vim.lsp.util' -local api = vim.api - -local function split_lines(value) - return vim.split(value, '\n', true) -end - -local builtin_callbacks = {} - --- textDocument/completion --- https://microsoft.github.io/language-server-protocol/specification#textDocument_completion -builtin_callbacks['textDocument/completion'] = function(_, _, result) - if not result or vim.tbl_isempty(result) then - return - end - local pos = api.nvim_win_get_cursor(0) - local row, col = pos[1], pos[2] - local line = assert(api.nvim_buf_get_lines(0, row-1, row, false)[1]) - local line_to_cursor = line:sub(col+1) - - local matches = util.text_document_completion_list_to_complete_items(result, line_to_cursor) - local match_result = vim.fn.matchstrpos(line_to_cursor, '\\k\\+$') - local match_start, match_finish = match_result[2], match_result[3] - - vim.fn.complete(col + 1 - (match_finish - match_start), matches) -end - --- textDocument/rename -builtin_callbacks['textDocument/rename'] = function(_, _, result) - if not result then return end - util.workspace_apply_workspace_edit(result) -end - -local function uri_to_bufnr(uri) - return vim.fn.bufadd((vim.uri_to_fname(uri))) -end - -builtin_callbacks['textDocument/publishDiagnostics'] = function(_, _, result) - if not result then return end - local uri = result.uri - local bufnr = uri_to_bufnr(uri) - if not bufnr then - api.nvim_err_writeln(string.format("LSP.publishDiagnostics: Couldn't find buffer for %s", uri)) - return - end - util.buf_clear_diagnostics(bufnr) - util.buf_diagnostics_save_positions(bufnr, result.diagnostics) - util.buf_diagnostics_underline(bufnr, result.diagnostics) - util.buf_diagnostics_virtual_text(bufnr, result.diagnostics) - -- util.buf_loclist(bufnr, result.diagnostics) -end - --- textDocument/hover --- https://microsoft.github.io/language-server-protocol/specification#textDocument_hover --- @params MarkedString | MarkedString[] | MarkupContent -builtin_callbacks['textDocument/hover'] = function(_, _, result) - if result == nil or vim.tbl_isempty(result) then - return - end - - if result.contents ~= nil then - local markdown_lines = util.convert_input_to_markdown_lines(result.contents) - if vim.tbl_isempty(markdown_lines) then - markdown_lines = { 'No information available' } - end - util.open_floating_preview(markdown_lines, 'markdown') - end -end - -builtin_callbacks['textDocument/peekDefinition'] = function(_, _, result) - if result == nil or vim.tbl_isempty(result) then return end - -- TODO(ashkan) what to do with multiple locations? - result = result[1] - local bufnr = uri_to_bufnr(result.uri) - assert(bufnr) - local start = result.range.start - local finish = result.range["end"] - util.open_floating_peek_preview(bufnr, start, finish, { offset_x = 1 }) - util.open_floating_preview({"*Peek:*", string.rep(" ", finish.character - start.character + 1) }, 'markdown', { offset_y = -(finish.line - start.line) }) -end - ---- Convert SignatureHelp response to preview contents. --- https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_signatureHelp -local function signature_help_to_preview_contents(input) - if not input.signatures then - return - end - --The active signature. If omitted or the value lies outside the range of - --`signatures` the value defaults to zero or is ignored if `signatures.length - --=== 0`. Whenever possible implementors should make an active decision about - --the active signature and shouldn't rely on a default value. - local contents = {} - local active_signature = input.activeSignature or 0 - -- If the activeSignature is not inside the valid range, then clip it. - if active_signature >= #input.signatures then - active_signature = 0 - end - local signature = input.signatures[active_signature + 1] - if not signature then - return - end - vim.list_extend(contents, split_lines(signature.label)) - if signature.documentation then - util.convert_input_to_markdown_lines(signature.documentation, contents) - end - if input.parameters then - local active_parameter = input.activeParameter or 0 - -- If the activeParameter is not inside the valid range, then clip it. - if active_parameter >= #input.parameters then - active_parameter = 0 - end - local parameter = signature.parameters and signature.parameters[active_parameter] - if parameter then - --[=[ - --Represents a parameter of a callable-signature. A parameter can - --have a label and a doc-comment. - interface ParameterInformation { - --The label of this parameter information. - -- - --Either a string or an inclusive start and exclusive end offsets within its containing - --signature label. (see SignatureInformation.label). The offsets are based on a UTF-16 - --string representation as `Position` and `Range` does. - -- - --*Note*: a label of type string should be a substring of its containing signature label. - --Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`. - label: string | [number, number]; - --The human-readable doc-comment of this parameter. Will be shown - --in the UI but can be omitted. - documentation?: string | MarkupContent; - } - --]=] - -- TODO highlight parameter - if parameter.documentation then - util.convert_input_to_markdown_lines(parameter.documentation, contents) - end - end - end - return contents -end - --- textDocument/signatureHelp --- https://microsoft.github.io/language-server-protocol/specification#textDocument_signatureHelp -builtin_callbacks['textDocument/signatureHelp'] = function(_, _, result) - if result == nil or vim.tbl_isempty(result) then - return - end - - -- TODO show empty popup when signatures is empty? - if #result.signatures > 0 then - local markdown_lines = signature_help_to_preview_contents(result) - if vim.tbl_isempty(markdown_lines) then - markdown_lines = { 'No signature available' } - end - util.open_floating_preview(markdown_lines, 'markdown') - end -end - -local function update_tagstack() - local bufnr = api.nvim_get_current_buf() - local line = vim.fn.line('.') - local col = vim.fn.col('.') - local tagname = vim.fn.expand('<cWORD>') - local item = { bufnr = bufnr, from = { bufnr, line, col, 0 }, tagname = tagname } - local winid = vim.fn.win_getid() - local tagstack = vim.fn.gettagstack(winid) - - local action - - if tagstack.length == tagstack.curidx then - action = 'r' - tagstack.items[tagstack.curidx] = item - elseif tagstack.length > tagstack.curidx then - action = 'r' - if tagstack.curidx > 1 then - tagstack.items = table.insert(tagstack.items[tagstack.curidx - 1], item) - else - tagstack.items = { item } - end - else - action = 'a' - tagstack.items = { item } - end - - tagstack.curidx = tagstack.curidx + 1 - vim.fn.settagstack(winid, tagstack, action) -end - -local function handle_location(result) - -- We can sometimes get a list of locations, so set the first value as the - -- only value we want to handle - -- TODO(ashkan) was this correct^? We could use location lists. - if result[1] ~= nil then - result = result[1] - end - if result.uri == nil then - api.nvim_err_writeln('[LSP] Could not find a valid location') - return - end - local result_file = vim.uri_to_fname(result.uri) - local bufnr = vim.fn.bufadd(result_file) - update_tagstack() - api.nvim_set_current_buf(bufnr) - local start = result.range.start - api.nvim_win_set_cursor(0, {start.line + 1, start.character}) -end - -local function location_callback(_, method, result) - if result == nil or vim.tbl_isempty(result) then - local _ = log.info() and log.info(method, 'No location found') - return nil - end - handle_location(result) - return true -end - -local location_callbacks = { - -- https://microsoft.github.io/language-server-protocol/specification#textDocument_declaration - 'textDocument/declaration'; - -- https://microsoft.github.io/language-server-protocol/specification#textDocument_definition - 'textDocument/definition'; - -- https://microsoft.github.io/language-server-protocol/specification#textDocument_implementation - 'textDocument/implementation'; - -- https://microsoft.github.io/language-server-protocol/specification#textDocument_typeDefinition - 'textDocument/typeDefinition'; -} - -for _, location_method in ipairs(location_callbacks) do - builtin_callbacks[location_method] = location_callback -end - -local function log_message(_, _, result, client_id) - local message_type = result.type - local message = result.message - local client = vim.lsp.get_client_by_id(client_id) - local client_name = client and client.name or string.format("id=%d", client_id) - if not client then - api.nvim_err_writeln(string.format("LSP[%s] client has shut down after sending the message", client_name)) - end - if message_type == protocol.MessageType.Error then - -- Might want to not use err_writeln, - -- but displaying a message with red highlights or something - api.nvim_err_writeln(string.format("LSP[%s] %s", client_name, message)) - else - local message_type_name = protocol.MessageType[message_type] - api.nvim_out_write(string.format("LSP[%s][%s] %s\n", client_name, message_type_name, message)) - end - return result -end - -builtin_callbacks['window/showMessage'] = log_message -builtin_callbacks['window/logMessage'] = log_message - --- Add boilerplate error validation and logging for all of these. -for k, fn in pairs(builtin_callbacks) do - builtin_callbacks[k] = function(err, method, params, client_id) - local _ = log.debug() and log.debug('builtin_callback', method, { params = params, client_id = client_id, err = err }) - if err then - error(tostring(err)) - end - return fn(err, method, params, client_id) - end -end - -return builtin_callbacks --- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua new file mode 100644 index 0000000000..4fc3f74519 --- /dev/null +++ b/runtime/lua/vim/lsp/callbacks.lua @@ -0,0 +1,223 @@ +local log = require 'vim.lsp.log' +local protocol = require 'vim.lsp.protocol' +local util = require 'vim.lsp.util' +local vim = vim +local api = vim.api + +local M = {} + +local function err_message(...) + api.nvim_err_writeln(table.concat(vim.tbl_flatten{...})) + api.nvim_command("redraw") +end + +M['workspace/applyEdit'] = function(_, _, workspace_edit) + if not workspace_edit then return end + -- TODO(ashkan) Do something more with label? + if workspace_edit.label then + print("Workspace edit", workspace_edit.label) + end + util.apply_workspace_edit(workspace_edit.edit) +end + +M['textDocument/publishDiagnostics'] = function(_, _, result) + if not result then return end + local uri = result.uri + local bufnr = vim.uri_to_bufnr(uri) + if not bufnr then + err_message("LSP.publishDiagnostics: Couldn't find buffer for ", uri) + return + end + util.buf_clear_diagnostics(bufnr) + util.buf_diagnostics_save_positions(bufnr, result.diagnostics) + util.buf_diagnostics_underline(bufnr, result.diagnostics) + util.buf_diagnostics_virtual_text(bufnr, result.diagnostics) + -- util.set_loclist(result.diagnostics) +end + +M['textDocument/references'] = function(_, _, result) + if not result then return end + util.set_qflist(result) + api.nvim_command("copen") + api.nvim_command("wincmd p") +end + +M['textDocument/rename'] = function(_, _, result) + if not result then return end + util.apply_workspace_edit(result) +end + +M['textDocument/rangeFormatting'] = function(_, _, result) + if not result then return end + util.apply_text_edits(result) +end + +M['textDocument/formatting'] = function(_, _, result) + if not result then return end + util.apply_text_edits(result) +end + +M['textDocument/completion'] = function(_, _, result) + if vim.tbl_isempty(result or {}) then return end + local row, col = unpack(api.nvim_win_get_cursor(0)) + local line = assert(api.nvim_buf_get_lines(0, row-1, row, false)[1]) + local line_to_cursor = line:sub(col+1) + + local matches = util.text_document_completion_list_to_complete_items(result, line_to_cursor) + vim.fn.complete(col, matches) +end + +M['textDocument/hover'] = function(_, method, result) + util.focusable_preview(method, function() + if not (result and result.contents) then + return { 'No information available' } + end + local markdown_lines = util.convert_input_to_markdown_lines(result.contents) + markdown_lines = util.trim_empty_lines(markdown_lines) + if vim.tbl_isempty(markdown_lines) then + return { 'No information available' } + end + return markdown_lines, util.try_trim_markdown_code_blocks(markdown_lines) + end) +end + +local function location_callback(_, method, result) + if result == nil or vim.tbl_isempty(result) then + local _ = log.info() and log.info(method, 'No location found') + return nil + end + util.jump_to_location(result[1]) + if #result > 1 then + util.set_qflist(result) + api.nvim_command("copen") + api.nvim_command("wincmd p") + end +end + +M['textDocument/declaration'] = location_callback +M['textDocument/definition'] = location_callback +M['textDocument/typeDefinition'] = location_callback +M['textDocument/implementation'] = location_callback + +--- Convert SignatureHelp response to preview contents. +-- https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_signatureHelp +local function signature_help_to_preview_contents(input) + if not input.signatures then + return + end + --The active signature. If omitted or the value lies outside the range of + --`signatures` the value defaults to zero or is ignored if `signatures.length + --=== 0`. Whenever possible implementors should make an active decision about + --the active signature and shouldn't rely on a default value. + local contents = {} + local active_signature = input.activeSignature or 0 + -- If the activeSignature is not inside the valid range, then clip it. + if active_signature >= #input.signatures then + active_signature = 0 + end + local signature = input.signatures[active_signature + 1] + if not signature then + return + end + vim.list_extend(contents, vim.split(signature.label, '\n', true)) + if signature.documentation then + util.convert_input_to_markdown_lines(signature.documentation, contents) + end + if input.parameters then + local active_parameter = input.activeParameter or 0 + -- If the activeParameter is not inside the valid range, then clip it. + if active_parameter >= #input.parameters then + active_parameter = 0 + end + local parameter = signature.parameters and signature.parameters[active_parameter] + if parameter then + --[=[ + --Represents a parameter of a callable-signature. A parameter can + --have a label and a doc-comment. + interface ParameterInformation { + --The label of this parameter information. + -- + --Either a string or an inclusive start and exclusive end offsets within its containing + --signature label. (see SignatureInformation.label). The offsets are based on a UTF-16 + --string representation as `Position` and `Range` does. + -- + --*Note*: a label of type string should be a substring of its containing signature label. + --Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`. + label: string | [number, number]; + --The human-readable doc-comment of this parameter. Will be shown + --in the UI but can be omitted. + documentation?: string | MarkupContent; + } + --]=] + -- TODO highlight parameter + if parameter.documentation then + util.convert_input_to_markdown_lines(parameter.documentation, contents) + end + end + end + return contents +end + +M['textDocument/signatureHelp'] = function(_, method, result) + util.focusable_preview(method, function() + if not (result and result.signatures and result.signatures[1]) then + return { 'No signature available' } + end + -- TODO show popup when signatures is empty? + local lines = signature_help_to_preview_contents(result) + lines = util.trim_empty_lines(lines) + if vim.tbl_isempty(lines) then + return { 'No signature available' } + end + return lines, util.try_trim_markdown_code_blocks(lines) + end) +end + +M['textDocument/peekDefinition'] = function(_, _, result, _) + if not (result and result[1]) then return end + local loc = result[1] + local bufnr = vim.uri_to_bufnr(loc.uri) or error("not found: "..tostring(loc.uri)) + local start = loc.range.start + local finish = loc.range["end"] + util.open_floating_peek_preview(bufnr, start, finish, { offset_x = 1 }) + local headbuf = util.open_floating_preview({"Peek:"}, nil, { + offset_y = -(finish.line - start.line); + width = finish.character - start.character + 2; + }) + -- TODO(ashkan) change highlight group? + api.nvim_buf_add_highlight(headbuf, -1, 'Keyword', 0, -1) +end + +local function log_message(_, _, result, client_id) + local message_type = result.type + local message = result.message + local client = vim.lsp.get_client_by_id(client_id) + local client_name = client and client.name or string.format("id=%d", client_id) + if not client then + err_message("LSP[", client_name, "] client has shut down after sending the message") + end + if message_type == protocol.MessageType.Error then + err_message("LSP[", client_name, "] ", message) + else + local message_type_name = protocol.MessageType[message_type] + api.nvim_out_write(string.format("LSP[%s][%s] %s\n", client_name, message_type_name, message)) + end + return result +end + +M['window/showMessage'] = log_message +M['window/logMessage'] = log_message + +-- Add boilerplate error validation and logging for all of these. +for k, fn in pairs(M) do + M[k] = function(err, method, params, client_id) + local _ = log.debug() and log.debug('default_callback', method, { params = params, client_id = client_id, err = err }) + if err then + error(tostring(err)) + end + return fn(err, method, params, client_id) + end +end + +return M +-- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 1413a88ce2..ead90cc75a 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -10,7 +10,6 @@ end --[=[ -- Useful for interfacing with: --- https://github.com/microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md -- https://github.com/microsoft/language-server-protocol/raw/gh-pages/_specifications/specification-3-14.md function transform_schema_comments() nvim.command [[silent! '<,'>g/\/\*\*\|\*\/\|^$/d]] @@ -681,19 +680,6 @@ function protocol.make_client_capabilities() } end -function protocol.make_text_document_position_params() - local position = vim.api.nvim_win_get_cursor(0) - return { - textDocument = { - uri = vim.uri_from_bufnr() - }; - position = { - line = position[1] - 1; - character = position[2]; - } - } -end - --[=[ export interface DocumentFilter { --A language id, like `typescript`. diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index e0ec8863d6..a558f66a42 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -340,6 +340,7 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para local decoded, err = json_decode(body) if not decoded then on_error(client_errors.INVALID_SERVER_JSON, err) + return end local _ = log.debug() and log.debug("decoded", decoded) diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index f96e0f01a8..b9990ed082 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1,6 +1,8 @@ local protocol = require 'vim.lsp.protocol' +local vim = vim local validate = vim.validate local api = vim.api +local list_extend = vim.list_extend local M = {} @@ -9,7 +11,13 @@ local function split_lines(value) return split(value, '\n', true) end -local list_extend = vim.list_extend +local function ok_or_nil(status, ...) + if not status then return end + return ... +end +local function npcall(fn, ...) + return ok_or_nil(pcall(fn, ...)) +end --- Find the longest shared prefix between prefix and word. -- e.g. remove_prefix("123tes", "testing") == "ting" @@ -26,11 +34,90 @@ local function remove_prefix(prefix, word) return word:sub(prefix_length + 1) end -local function resolve_bufnr(bufnr) - if bufnr == nil or bufnr == 0 then - return api.nvim_get_current_buf() +-- TODO(ashkan) @performance this could do less copying. +function M.set_lines(lines, A, B, new_lines) + -- 0-indexing to 1-indexing + local i_0 = A[1] + 1 + local i_n = B[1] + 1 + if not (i_0 >= 1 and i_0 <= #lines and i_n >= 1 and i_n <= #lines) then + error("Invalid range: "..vim.inspect{A = A; B = B; #lines, new_lines}) + end + local prefix = "" + local suffix = lines[i_n]:sub(B[2]+1) + if A[2] > 0 then + prefix = lines[i_0]:sub(1, A[2]) + end + local n = i_n - i_0 + 1 + if n ~= #new_lines then + for _ = 1, n - #new_lines do table.remove(lines, i_0) end + for _ = 1, #new_lines - n do table.insert(lines, i_0, '') end + end + for i = 1, #new_lines do + lines[i - 1 + i_0] = new_lines[i] + end + if #suffix > 0 then + local i = i_0 + #new_lines - 1 + lines[i] = lines[i]..suffix + end + if #prefix > 0 then + lines[i_0] = prefix..lines[i_0] + end + return lines +end + +local function sort_by_key(fn) + return function(a,b) + local ka, kb = fn(a), fn(b) + assert(#ka == #kb) + for i = 1, #ka do + if ka[i] ~= kb[i] then + return ka[i] < kb[i] + end + end + -- every value must have been equal here, which means it's not less than. + return false + end +end +local edit_sort_key = sort_by_key(function(e) + return {e.A[1], e.A[2], e.i} +end) + +function M.apply_text_edits(text_edits, bufnr) + if not next(text_edits) then return end + local start_line, finish_line = math.huge, -1 + local cleaned = {} + for i, e in ipairs(text_edits) do + start_line = math.min(e.range.start.line, start_line) + finish_line = math.max(e.range["end"].line, finish_line) + -- TODO(ashkan) sanity check ranges for overlap. + table.insert(cleaned, { + i = i; + A = {e.range.start.line; e.range.start.character}; + B = {e.range["end"].line; e.range["end"].character}; + lines = vim.split(e.newText, '\n', true); + }) + end + + -- Reverse sort the orders so we can apply them without interfering with + -- eachother. Also add i as a sort key to mimic a stable sort. + table.sort(cleaned, edit_sort_key) + local lines = api.nvim_buf_get_lines(bufnr, start_line, finish_line + 1, false) + local fix_eol = api.nvim_buf_get_option(bufnr, 'fixeol') + local set_eol = fix_eol and api.nvim_buf_line_count(bufnr) == finish_line + 1 + if set_eol and #lines[#lines] ~= 0 then + table.insert(lines, '') + end + + for i = #cleaned, 1, -1 do + local e = cleaned[i] + local A = {e.A[1] - start_line, e.A[2]} + local B = {e.B[1] - start_line, e.B[2]} + lines = M.set_lines(lines, A, B, e.lines) end - return bufnr + if set_eol and #lines[#lines] == 0 then + table.remove(lines) + end + api.nvim_buf_set_lines(bufnr, start_line, finish_line + 1, false, lines) end -- local valid_windows_path_characters = "[^<>:\"/\\|?*]" @@ -40,30 +127,6 @@ end -- function M.glob_to_regex(glob) -- end ---- Apply the TextEdit response. --- @params TextEdit [table] see https://microsoft.github.io/language-server-protocol/specification -function M.text_document_apply_text_edit(text_edit, bufnr) - bufnr = resolve_bufnr(bufnr) - local range = text_edit.range - local start = range.start - local finish = range['end'] - local new_lines = split_lines(text_edit.newText) - if start.character == 0 and finish.character == 0 then - api.nvim_buf_set_lines(bufnr, start.line, finish.line, false, new_lines) - return - end - api.nvim_err_writeln('apply_text_edit currently only supports character ranges starting at 0') - error('apply_text_edit currently only supports character ranges starting at 0') - return - -- TODO test and finish this support for character ranges. --- local lines = api.nvim_buf_get_lines(0, start.line, finish.line + 1, false) --- local suffix = lines[#lines]:sub(finish.character+2) --- local prefix = lines[1]:sub(start.character+2) --- new_lines[#new_lines] = new_lines[#new_lines]..suffix --- new_lines[1] = prefix..new_lines[1] --- api.nvim_buf_set_lines(0, start.line, finish.line, false, new_lines) -end - -- textDocument/completion response returns one of CompletionItem[], CompletionList or null. -- https://microsoft.github.io/language-server-protocol/specification#textDocument_completion function M.extract_completion_items(result) @@ -78,18 +141,15 @@ end --- Apply the TextDocumentEdit response. -- @params TextDocumentEdit [table] see https://microsoft.github.io/language-server-protocol/specification -function M.text_document_apply_text_document_edit(text_document_edit, bufnr) - -- local text_document = text_document_edit.textDocument - -- TODO use text_document_version? - -- local text_document_version = text_document.version - - -- TODO technically, you could do this without doing multiple buf_get/set - -- by getting the full region (smallest line and largest line) and doing - -- the edits on the buffer, and then applying the buffer at the end. - -- I'm not sure if that's better. - for _, text_edit in ipairs(text_document_edit.edits) do - M.text_document_apply_text_edit(text_edit, bufnr) +function M.apply_text_document_edit(text_document_edit) + local text_document = text_document_edit.textDocument + local bufnr = vim.uri_to_bufnr(text_document.uri) + -- TODO(ashkan) check this is correct. + if api.nvim_buf_get_changedtick(bufnr) > text_document.version then + print("Buffer ", text_document.uri, " newer than edits.") + return end + M.apply_text_edits(text_document_edit.edits, bufnr) end function M.get_current_line_to_cursor() @@ -145,32 +205,27 @@ function M.text_document_completion_list_to_complete_items(result, line_prefix) end -- @params WorkspaceEdit [table] see https://microsoft.github.io/language-server-protocol/specification -function M.workspace_apply_workspace_edit(workspace_edit) +function M.apply_workspace_edit(workspace_edit) if workspace_edit.documentChanges then for _, change in ipairs(workspace_edit.documentChanges) do if change.kind then -- TODO(ashkan) handle CreateFile/RenameFile/DeleteFile error(string.format("Unsupported change: %q", vim.inspect(change))) else - M.text_document_apply_text_document_edit(change) + M.apply_text_document_edit(change) end end return end - if workspace_edit.changes == nil or #workspace_edit.changes == 0 then + local all_changes = workspace_edit.changes + if not (all_changes and not vim.tbl_isempty(all_changes)) then return end - for uri, changes in pairs(workspace_edit.changes) do - local fname = vim.uri_to_fname(uri) - -- TODO improve this approach. Try to edit open buffers without switching. - -- Not sure how to handle files which aren't open. This is deprecated - -- anyway, so I guess it could be left as is. - api.nvim_command('edit '..fname) - for _, change in ipairs(changes) do - M.text_document_apply_text_edit(change) - end + for uri, changes in pairs(all_changes) do + local bufnr = vim.uri_to_bufnr(uri) + M.apply_text_edits(changes, bufnr) end end @@ -255,35 +310,77 @@ function M.make_floating_popup_options(width, height, opts) } end +function M.jump_to_location(location) + if location.uri == nil then return end + local bufnr = vim.uri_to_bufnr(location.uri) + -- TODO(ashkan) use tagfunc here to update tagstack. + api.nvim_set_current_buf(bufnr) + local row = location.range.start.line + local col = location.range.start.character + local line = api.nvim_buf_get_lines(0, row, row+1, true)[1] + col = vim.str_byteindex(line, col) + api.nvim_win_set_cursor(0, {row + 1, col}) + return true +end + +local function find_window_by_var(name, value) + for _, win in ipairs(api.nvim_list_wins()) do + if npcall(api.nvim_win_get_var, win, name) == value then + return win + end + end +end + +-- Check if a window with `unique_name` tagged is associated with the current +-- buffer. If not, make a new preview. +-- +-- fn()'s return values will be passed directly to open_floating_preview in the +-- case that a new floating window should be created. +function M.focusable_preview(unique_name, fn) + if npcall(api.nvim_win_get_var, 0, unique_name) then + return api.nvim_command("wincmd p") + end + local bufnr = api.nvim_get_current_buf() + do + local win = find_window_by_var(unique_name, bufnr) + if win then + api.nvim_set_current_win(win) + api.nvim_command("stopinsert") + return + end + end + local pbufnr, pwinnr = M.open_floating_preview(fn()) + api.nvim_win_set_var(pwinnr, unique_name, bufnr) + return pbufnr, pwinnr +end + function M.open_floating_preview(contents, filetype, opts) validate { contents = { contents, 't' }; filetype = { filetype, 's', true }; opts = { opts, 't', true }; } + opts = opts or {} -- Trim empty lines from the end. - for i = #contents, 1, -1 do - if #contents[i] == 0 then - table.remove(contents) - else - break + contents = M.trim_empty_lines(contents) + + local width = opts.width + local height = opts.height or #contents + if not width then + width = 0 + for i, line in ipairs(contents) do + -- Clean up the input and add left pad. + line = " "..line:gsub("\r", "") + -- TODO(ashkan) use nvim_strdisplaywidth if/when that is introduced. + local line_width = vim.fn.strdisplaywidth(line) + width = math.max(line_width, width) + contents[i] = line end + -- Add right padding of 1 each. + width = width + 1 end - local width = 0 - local height = #contents - for i, line in ipairs(contents) do - -- Clean up the input and add left pad. - line = " "..line:gsub("\r", "") - -- TODO(ashkan) use nvim_strdisplaywidth if/when that is introduced. - local line_width = vim.fn.strdisplaywidth(line) - width = math.max(line_width, width) - contents[i] = line - end - -- Add right padding of 1 each. - width = width + 1 - local floating_bufnr = api.nvim_create_buf(false, true) if filetype then api.nvim_buf_set_option(floating_bufnr, 'filetype', filetype) @@ -295,7 +392,8 @@ function M.open_floating_preview(contents, filetype, opts) end api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents) api.nvim_buf_set_option(floating_bufnr, 'modifiable', false) - api.nvim_command("autocmd CursorMoved <buffer> ++once lua pcall(vim.api.nvim_win_close, "..floating_winnr..", true)") + -- TODO make InsertCharPre disappearing optional? + api.nvim_command("autocmd CursorMoved,BufHidden,InsertCharPre <buffer> ++once lua pcall(vim.api.nvim_win_close, "..floating_winnr..", true)") return floating_bufnr, floating_winnr end @@ -342,6 +440,11 @@ do local diagnostic_ns = api.nvim_create_namespace("vim_lsp_diagnostics") + local underline_highlight_name = "LspDiagnosticsUnderline" + api.nvim_command(string.format("highlight default %s gui=underline cterm=underline", underline_highlight_name)) + + local severity_highlights = {} + local default_severity_highlight = { [protocol.DiagnosticSeverity.Error] = { guifg = "Red" }; [protocol.DiagnosticSeverity.Warning] = { guifg = "Orange" }; @@ -349,60 +452,17 @@ do [protocol.DiagnosticSeverity.Hint] = { guifg = "LightGrey" }; } - local underline_highlight_name = "LspDiagnosticsUnderline" - api.nvim_command(string.format("highlight %s gui=underline cterm=underline", underline_highlight_name)) - - local function find_color_rgb(color) - local rgb_hex = api.nvim_get_color_by_name(color) - validate { color = {color, function() return rgb_hex ~= -1 end, "valid color name"} } - return rgb_hex - end - - --- Determine whether to use black or white text - -- Ref: https://stackoverflow.com/a/1855903/837964 - -- https://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color - local function color_is_bright(r, g, b) - -- Counting the perceptive luminance - human eye favors green color - local luminance = (0.299*r + 0.587*g + 0.114*b)/255 - if luminance > 0.5 then - return true -- Bright colors, black font - else - return false -- Dark colors, white font - end - end - - local severity_highlights = {} - - function M.set_severity_highlights(highlights) - validate {highlights = {highlights, 't'}} - for severity, default_color in pairs(default_severity_highlight) do - local severity_name = protocol.DiagnosticSeverity[severity] - local highlight_name = "LspDiagnostics"..severity_name - local hi_info = highlights[severity] or default_color - -- Try to fill in the foreground color with a sane default. - if not hi_info.guifg and hi_info.guibg then - -- TODO(ashkan) move this out when bitop is guaranteed to be included. - local bit = require 'bit' - local band, rshift = bit.band, bit.rshift - local rgb = find_color_rgb(hi_info.guibg) - local is_bright = color_is_bright(rshift(rgb, 16), band(rshift(rgb, 8), 0xFF), band(rgb, 0xFF)) - hi_info.guifg = is_bright and "Black" or "White" - end - if not hi_info.ctermfg and hi_info.ctermbg then - -- TODO(ashkan) move this out when bitop is guaranteed to be included. - local bit = require 'bit' - local band, rshift = bit.band, bit.rshift - local rgb = find_color_rgb(hi_info.ctermbg) - local is_bright = color_is_bright(rshift(rgb, 16), band(rshift(rgb, 8), 0xFF), band(rgb, 0xFF)) - hi_info.ctermfg = is_bright and "Black" or "White" - end - local cmd_parts = {"highlight", highlight_name} - for k, v in pairs(hi_info) do - table.insert(cmd_parts, k.."="..v) - end - api.nvim_command(table.concat(cmd_parts, ' ')) - severity_highlights[severity] = highlight_name + -- Initialize default severity highlights + for severity, hi_info in pairs(default_severity_highlight) do + local severity_name = protocol.DiagnosticSeverity[severity] + local highlight_name = "LspDiagnostics"..severity_name + -- Try to fill in the foreground color with a sane default. + local cmd_parts = {"highlight", "default", highlight_name} + for k, v in pairs(hi_info) do + table.insert(cmd_parts, k.."="..v) end + api.nvim_command(table.concat(cmd_parts, ' ')) + severity_highlights[severity] = highlight_name end function M.buf_clear_diagnostics(bufnr) @@ -411,9 +471,6 @@ do api.nvim_buf_clear_namespace(bufnr, diagnostic_ns, 0, -1) end - -- Initialize with the defaults. - M.set_severity_highlights(default_severity_highlight) - function M.get_severity_highlight_name(severity) return severity_highlights[severity] end @@ -527,30 +584,141 @@ do end end -function M.buf_loclist(bufnr, locations) - local targetwin - for _, winnr in ipairs(api.nvim_list_wins()) do - local winbuf = api.nvim_win_get_buf(winnr) - if winbuf == bufnr then - targetwin = winnr - break - end - end - if not targetwin then return end +local position_sort = sort_by_key(function(v) + return {v.line, v.character} +end) +-- Returns the items with the byte position calculated correctly and in sorted +-- order. +function M.locations_to_items(locations) local items = {} - local path = api.nvim_buf_get_name(bufnr) + local grouped = setmetatable({}, { + __index = function(t, k) + local v = {} + rawset(t, k, v) + return v + end; + }) for _, d in ipairs(locations) do - -- TODO: URL parsing here? local start = d.range.start - table.insert(items, { - filename = path, - lnum = start.line + 1, - col = start.character + 1, - text = d.message, - }) + local fname = assert(vim.uri_to_fname(d.uri)) + table.insert(grouped[fname], start) + end + local keys = vim.tbl_keys(grouped) + table.sort(keys) + -- TODO(ashkan) I wish we could do this lazily. + for _, fname in ipairs(keys) do + local rows = grouped[fname] + table.sort(rows, position_sort) + local i = 0 + for line in io.lines(fname) do + for _, pos in ipairs(rows) do + local row = pos.line + if i == row then + local col + if pos.character > #line then + col = #line + else + col = vim.str_byteindex(line, pos.character) + end + table.insert(items, { + filename = fname, + lnum = row + 1, + col = col + 1; + text = line; + }) + end + end + i = i + 1 + end + end + return items +end + +-- locations is Location[] +-- Only sets for the current window. +function M.set_loclist(locations) + vim.fn.setloclist(0, {}, ' ', { + title = 'Language Server'; + items = M.locations_to_items(locations); + }) +end + +-- locations is Location[] +function M.set_qflist(locations) + vim.fn.setqflist({}, ' ', { + title = 'Language Server'; + items = M.locations_to_items(locations); + }) +end + +-- Remove empty lines from the beginning and end. +function M.trim_empty_lines(lines) + local start = 1 + for i = 1, #lines do + if #lines[i] > 0 then + start = i + break + end + end + local finish = 1 + for i = #lines, 1, -1 do + if #lines[i] > 0 then + finish = i + break + end + end + return vim.list_extend({}, lines, start, finish) +end + +-- Accepts markdown lines and tries to reduce it to a filetype if it is +-- just a single code block. +-- Note: This modifies the input. +-- +-- Returns: filetype or 'markdown' if it was unchanged. +function M.try_trim_markdown_code_blocks(lines) + local language_id = lines[1]:match("^```(.*)") + if language_id then + local has_inner_code_fence = false + for i = 2, (#lines - 1) do + local line = lines[i] + if line:sub(1,3) == '```' then + has_inner_code_fence = true + break + end + end + -- No inner code fences + starting with code fence = hooray. + if not has_inner_code_fence then + table.remove(lines, 1) + table.remove(lines) + return language_id + end + end + return 'markdown' +end + +local str_utfindex = vim.str_utfindex +function M.make_position_params() + local row, col = unpack(api.nvim_win_get_cursor(0)) + row = row - 1 + local line = api.nvim_buf_get_lines(0, row, row+1, true)[1] + col = str_utfindex(line, col) + return { + textDocument = { uri = vim.uri_from_bufnr(0) }; + position = { line = row; character = col; } + } +end + +-- @param buf buffer handle or 0 for current. +-- @param row 0-indexed line +-- @param col 0-indexed byte offset in line +function M.character_offset(buf, row, col) + local line = api.nvim_buf_get_lines(buf, row, row+1, true)[1] + -- If the col is past the EOL, use the line length. + if col > #line then + return str_utfindex(line) end - vim.fn.setloclist(targetwin, items, ' ', 'Language Server') + return str_utfindex(line, col) end return M diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index ff89acc524..b5b04d7757 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -4,7 +4,7 @@ -- test-suite. If, in the future, Nvim itself is used to run the test-suite -- instead of "vanilla Lua", these functions could move to src/nvim/lua/vim.lua -local vim = {} +local vim = vim or {} --- Returns a deep copy of the given object. Non-table objects are copied as --- in a typical Lua assignment, whereas table objects are copied recursively. @@ -228,16 +228,24 @@ end --- Extends a list-like table with the values of another list-like table. --- ---NOTE: This *mutates* dst! ---@see |extend()| +--- NOTE: This mutates dst! +--- +--@see |vim.tbl_extend()| --- ---@param dst The list which will be modified and appended to. ---@param src The list from which values will be inserted. -function vim.list_extend(dst, src) - assert(type(dst) == 'table', "dst must be a table") - assert(type(src) == 'table', "src must be a table") - for _, v in ipairs(src) do - table.insert(dst, v) +--@param dst list which will be modified and appended to. +--@param src list from which values will be inserted. +--@param start Start index on src. defaults to 1 +--@param finish Final index on src. defaults to #src +--@returns dst +function vim.list_extend(dst, src, start, finish) + vim.validate { + dst = {dst, 't'}; + src = {src, 't'}; + start = {start, 'n', true}; + finish = {finish, 'n', true}; + } + for i = start or 1, finish or #src do + table.insert(dst, src[i]) end return dst end @@ -312,6 +320,26 @@ function vim.pesc(s) return s:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1') end +--- Test if `prefix` is a prefix of `s` for strings. +-- +-- @param s String to check +-- @param prefix Potential prefix +-- @return boolean True if prefix is a prefix of s +function vim.startswith(s, prefix) + vim.validate { s = {s, 's'}; prefix = {prefix, 's'}; } + return s:sub(1, #prefix) == prefix +end + +--- Test if `suffix` is a suffix of `s` for strings. +-- +-- @param s String to check +-- @param suffix Potential suffix +-- @return boolean True if suffix is a suffix of s +function vim.endswith(s, suffix) + vim.validate { s = {s, 's'}; suffix = {suffix, 's'}; } + return #suffix == 0 or s:sub(-#suffix) == suffix +end + --- Validates a parameter specification (types and values). --- --- Usage example: diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua index 0a6e0fcb97..1065f84f4c 100644 --- a/runtime/lua/vim/uri.lua +++ b/runtime/lua/vim/uri.lua @@ -77,13 +77,18 @@ local function uri_to_fname(uri) else uri = uri:gsub('^file://', '') end - return uri_decode(uri) end +-- Return or create a buffer for a uri. +local function uri_to_bufnr(uri) + return vim.fn.bufadd((uri_to_fname(uri))) +end + return { uri_from_fname = uri_from_fname, uri_from_bufnr = uri_from_bufnr, uri_to_fname = uri_to_fname, + uri_to_bufnr = uri_to_bufnr, } -- vim:sw=2 ts=2 et diff --git a/runtime/nvim.appdata.xml b/runtime/nvim.appdata.xml index ceffc7ba98..025de1b5a9 100644 --- a/runtime/nvim.appdata.xml +++ b/runtime/nvim.appdata.xml @@ -26,6 +26,7 @@ </screenshots> <releases> + <release date="2019-11-06" version="0.4.3"/> <release date="2019-09-15" version="0.4.2"/> <release date="2019-09-15" version="0.4.1"/> <release date="2019-09-15" version="0.4.0"/> diff --git a/runtime/plugin/man.vim b/runtime/plugin/man.vim index e18a5528bb..e762eb3664 100644 --- a/runtime/plugin/man.vim +++ b/runtime/plugin/man.vim @@ -1,4 +1,4 @@ -" Maintainer: Anmol Sethi <anmol@aubble.com> +" Maintainer: Anmol Sethi <hi@nhooyr.io> if exists('g:loaded_man') finish diff --git a/runtime/syntax/man.vim b/runtime/syntax/man.vim index 6afe56a6e3..7ac02c3f63 100644 --- a/runtime/syntax/man.vim +++ b/runtime/syntax/man.vim @@ -1,4 +1,4 @@ -" Maintainer: Anmol Sethi <anmol@aubble.com> +" Maintainer: Anmol Sethi <hi@nhooyr.io> " Previous Maintainer: SungHyun Nam <goweol@gmail.com> if exists('b:current_syntax') @@ -30,6 +30,7 @@ endif if !exists('b:man_sect') call man#init_pager() endif + if b:man_sect =~# '^[023]' syntax case match syntax include @c $VIMRUNTIME/syntax/c.vim diff --git a/runtime/tutor/tutor.tutor b/runtime/tutor/tutor.tutor index c937bd686a..b46fcc4836 100644 --- a/runtime/tutor/tutor.tutor +++ b/runtime/tutor/tutor.tutor @@ -118,7 +118,7 @@ and are hidden by default. Links to them look like \[label\]\(\*anchor\*\) -6. Add the appropiate link: +6. Add the appropriate link: A link to the Links section A link to the [Links](*links*) section diff --git a/scripts/lua2dox.lua b/scripts/lua2dox.lua index 438f734917..171621e38d 100644 --- a/scripts/lua2dox.lua +++ b/scripts/lua2dox.lua @@ -65,7 +65,7 @@ FILTER_PATTERNS = *.lua=lua2dox_filter Either add them to the end or find the appropriate entry in Doxyfile. -There are other lines that you might like to alter, but see futher documentation for details. +There are other lines that you might like to alter, but see further documentation for details. <li> When Doxyfile is edited run "doxygen" diff --git a/scripts/release.sh b/scripts/release.sh index 67268ba9bf..5b4902a2d7 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -93,6 +93,7 @@ fi _do_bump_commit echo " Next steps: + - Update runtime/nvim.appdata.xml on _master_ - Run tests/CI (version_spec.lua)! - Push the tag: git push --follow-tags diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 81ffb9adf3..da3e74d3e7 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,6 @@ -name: neovim -version: git +name: nvim +base: core18 +adopt-info: nvim summary: Vim-fork focused on extensibility and agility. description: | Neovim is a project that seeks to aggressively refactor Vim in order to: @@ -9,22 +10,41 @@ description: | Enable the implementation of new/modern user interfaces without any modifications to the core source Improve extensibility with a new plugin architecture For lots more details, see the wiki! + +grade: stable # must be 'stable' to release into candidate/stable channels confinement: classic apps: - neovim: - command: usr/local/bin/nvim - plugs: [network, network-bind, x11] + nvim: + command: usr/bin/nvim environment: HOME: /home/$USER - VIM: $SNAP/usr/local/share/nvim/runtime + VIM: $SNAP/usr/share/nvim + VIMRUNTIME: $SNAP/usr/share/nvim/runtime + desktop: usr/share/applications/nvim.desktop parts: - neovim: + nvim: source: . + override-pull: | + snapcraftctl pull + major="$(awk '/NVIM_VERSION_MAJOR/{gsub(")","",$2); print $2}' CMakeLists.txt)" + minor="$(awk '/NVIM_VERSION_MINOR/{gsub(")","",$2); print $2}' CMakeLists.txt)" + patch="$(awk '/NVIM_VERSION_PATCH/{gsub(")","",$2); print $2}' CMakeLists.txt)" + version_prefix="v$major.$minor.$patch" + git_described="$(git describe --first-parent --dirty 2> /dev/null | perl -lpe 's/v\d.\d.\d-//g')" + git_described="${git_described:-$(git describe --first-parent --tags --always --dirty)}" + snapcraftctl set-version "${version_prefix}-${git_described}" plugin: make make-parameters: - CMAKE_BUILD_TYPE=Release + - CMAKE_INSTALL_PREFIX=/usr + override-build: | + snapcraftctl build + # Fix Desktop file + sed -i 's|^Exec=nvim|Exec=/snap/bin/nvim.nvim|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop + sed -i 's|^TryExec=nvim|TryExec=/snap/bin/nvim.nvim|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop + sed -i 's|^Icon=.*|Icon=${SNAP}/usr/share/pixmaps/nvim.png|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop build-packages: - ninja-build - libtool @@ -33,9 +53,10 @@ parts: - automake - cmake - g++ + - git + - gettext - pkg-config - unzip - snap: - - usr/local/bin - - usr/local/share/nvim - - -usr/local/share/man + prime: + - -usr/share/man + diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index b00ac866b7..bc8e64dd41 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -52,7 +52,8 @@ set(UNICODE_TABLES_GENERATOR ${GENERATOR_DIR}/gen_unicode_tables.lua) set(UNICODE_DIR ${PROJECT_SOURCE_DIR}/unicode) set(GENERATED_UNICODE_TABLES ${GENERATED_DIR}/unicode_tables.generated.h) set(VIM_MODULE_FILE ${GENERATED_DIR}/lua/vim_module.generated.h) -set(VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/lua/vim.lua) +set(LUA_VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/lua/vim.lua) +set(LUA_SHARED_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/shared.lua) set(CHAR_BLOB_GENERATOR ${GENERATOR_DIR}/gen_char_blob.lua) set(LINT_SUPPRESS_FILE ${PROJECT_BINARY_DIR}/errors.json) set(LINT_SUPPRESS_URL_BASE "https://raw.githubusercontent.com/neovim/doc/gh-pages/reports/clint") @@ -317,11 +318,13 @@ add_custom_command( add_custom_command( OUTPUT ${VIM_MODULE_FILE} - COMMAND ${LUA_PRG} ${CHAR_BLOB_GENERATOR} ${VIM_MODULE_SOURCE} - ${VIM_MODULE_FILE} vim_module + COMMAND ${LUA_PRG} ${CHAR_BLOB_GENERATOR} ${VIM_MODULE_FILE} + ${LUA_VIM_MODULE_SOURCE} vim_module + ${LUA_SHARED_MODULE_SOURCE} shared_module DEPENDS ${CHAR_BLOB_GENERATOR} - ${VIM_MODULE_SOURCE} + ${LUA_VIM_MODULE_SOURCE} + ${LUA_SHARED_MODULE_SOURCE} ) list(APPEND NVIM_GENERATED_SOURCES @@ -462,6 +465,7 @@ install_helper(TARGETS nvim) set_property(TARGET nvim APPEND PROPERTY INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}) +set_property(TARGET nvim PROPERTY ENABLE_EXPORTS TRUE) if(ENABLE_LTO AND (POLICY CMP0069)) include(CheckIPOSupported) diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index a5f8b0974e..8f5718d97e 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1013,10 +1013,10 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) /// Returns position for a given extmark id /// -/// @param buffer The buffer handle -/// @param namespace a identifier returned previously with nvim_create_namespace -/// @param id the extmark id -/// @param[out] err Details of an error that may have occurred +/// @param buffer Buffer handle, or 0 for current buffer +/// @param ns_id Namespace id from |nvim_create_namespace()| +/// @param id Extmark id +/// @param[out] err Error details, if any /// @return (row, col) tuple or empty list () if extmark id was absent ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, Integer id, Error *err) @@ -1044,30 +1044,50 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, return rv; } -/// List extmarks in a range (inclusive) -/// -/// range ends can be specified as (row, col) tuples, as well as extmark -/// ids in the same namespace. In addition, 0 and -1 works as shorthands -/// for (0,0) and (-1,-1) respectively, so that all marks in the buffer can be -/// queried as: -/// -/// all_marks = nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) -/// -/// If end is a lower position than start, then the range will be traversed -/// backwards. This is mostly useful with limited amount, to be able to get the -/// first marks prior to a given position. -/// -/// @param buffer The buffer handle -/// @param ns_id An id returned previously from nvim_create_namespace -/// @param start One of: extmark id, (row, col) or 0, -1 for buffer ends -/// @param end One of: extmark id, (row, col) or 0, -1 for buffer ends -/// @param opts additional options. Supports the keys: -/// - amount: Maximum number of marks to return -/// @param[out] err Details of an error that may have occurred -/// @return [[extmark_id, row, col], ...] -Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, - Object start, Object end, Dictionary opts, - Error *err) +/// Gets extmarks in "traversal order" from a |charwise| region defined by +/// buffer positions (inclusive, 0-indexed |api-indexing|). +/// +/// Region can be given as (row,col) tuples, or valid extmark ids (whose +/// positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1) +/// respectively, thus the following are equivalent: +/// +/// <pre> +/// nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) +/// nvim_buf_get_extmarks(0, my_ns, [0,0], [-1,-1], {}) +/// </pre> +/// +/// If `end` is less than `start`, traversal works backwards. (Useful +/// with `limit`, to get the first marks prior to a given position.) +/// +/// Example: +/// +/// <pre> +/// local a = vim.api +/// local pos = a.nvim_win_get_cursor(0) +/// local ns = a.nvim_create_namespace('my-plugin') +/// -- Create new extmark at line 1, column 1. +/// local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, 0, {}) +/// -- Create new extmark at line 3, column 1. +/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, 0, {}) +/// -- Get extmarks only from line 3. +/// local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) +/// -- Get all marks in this buffer + namespace. +/// local all = a.nvim_buf_get_extmarks(0, ns, 0, -1, {}) +/// print(vim.inspect(ms)) +/// </pre> +/// +/// @param buffer Buffer handle, or 0 for current buffer +/// @param ns_id Namespace id from |nvim_create_namespace()| +/// @param start Start of range, given as (row, col) or valid extmark id +/// (whose position defines the bound) +/// @param end End of range, given as (row, col) or valid extmark id +/// (whose position defines the bound) +/// @param opts Optional parameters. Keys: +/// - limit: Maximum number of marks to return +/// @param[out] err Error details, if any +/// @return List of [extmark_id, row, col] tuples in "traversal order". +Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, + Object end, Dictionary opts, Error *err) FUNC_API_SINCE(7) { Array rv = ARRAY_DICT_INIT; @@ -1081,17 +1101,17 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); return rv; } - Integer amount = -1; + Integer limit = -1; for (size_t i = 0; i < opts.size; i++) { String k = opts.items[i].key; Object *v = &opts.items[i].value; - if (strequal("amount", k.data)) { + if (strequal("limit", k.data)) { if (v->type != kObjectTypeInteger) { - api_set_error(err, kErrorTypeValidation, "amount is not an integer"); + api_set_error(err, kErrorTypeValidation, "limit is not an integer"); return rv; } - amount = v->data.integer; + limit = v->data.integer; v->data.integer = LUA_NOREF; } else { api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); @@ -1099,7 +1119,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, } } - if (amount == 0) { + if (limit == 0) { return rv; } @@ -1108,13 +1128,13 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, linenr_T l_lnum; colnr_T l_col; - if (!set_extmark_index_from_obj(buf, ns_id, start, &l_lnum, &l_col, err)) { + if (!extmark_get_index_from_obj(buf, ns_id, start, &l_lnum, &l_col, err)) { return rv; } linenr_T u_lnum; colnr_T u_col; - if (!set_extmark_index_from_obj(buf, ns_id, end, &u_lnum, &u_col, err)) { + if (!extmark_get_index_from_obj(buf, ns_id, end, &u_lnum, &u_col, err)) { return rv; } @@ -1129,9 +1149,8 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, } - ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_lnum, l_col, - u_lnum, u_col, (int64_t)amount, - reverse); + ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_lnum, l_col, u_lnum, + u_col, (int64_t)limit, reverse); for (size_t i = 0; i < kv_size(marks); i++) { Array mark = ARRAY_DICT_INIT; @@ -1146,26 +1165,23 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, return rv; } -/// Create or update an extmark at a position +/// Creates or updates an extmark. /// -/// If an invalid namespace is given, an error will be raised. -/// -/// To create a new extmark, pass in id=0. The new extmark id will be -/// returned. To move an existing mark, pass in its id. +/// To create a new extmark, pass id=0. The extmark id will be returned. +// To move an existing mark, pass its id. /// /// It is also allowed to create a new mark by passing in a previously unused /// id, but the caller must then keep track of existing and unused ids itself. -/// This is mainly useful over RPC, to avoid needing to wait for the return -/// value. -/// -/// @param buffer The buffer handle -/// @param ns_id a identifier returned previously with nvim_create_namespace -/// @param id The extmark's id or 0 to create a new mark. -/// @param line The row to set the extmark to. -/// @param col The column to set the extmark to. -/// @param opts Optional parameters. Currently not used. -/// @param[out] err Details of an error that may have occurred -/// @return the id of the extmark. +/// (Useful over RPC, to avoid waiting for the return value.) +/// +/// @param buffer Buffer handle, or 0 for current buffer +/// @param ns_id Namespace id from |nvim_create_namespace()| +/// @param id Extmark id, or 0 to create new +/// @param line Line number where to place the mark +/// @param col Column where to place the mark +/// @param opts Optional parameters. Currently not used. +/// @param[out] err Error details, if any +/// @return Id of the created/updated extmark Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, Integer line, Integer col, Dictionary opts, Error *err) @@ -1191,7 +1207,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, api_set_error(err, kErrorTypeValidation, "line value outside range"); return 0; } else if (line < buf->b_ml.ml_line_count) { - len = STRLEN(ml_get_buf(curbuf, (linenr_T)line+1, false)); + len = STRLEN(ml_get_buf(buf, (linenr_T)line+1, false)); } if (col == -1) { @@ -1217,13 +1233,13 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, return (Integer)id_num; } -/// Remove an extmark +/// Removes an extmark. /// -/// @param buffer The buffer handle -/// @param ns_id a identifier returned previously with nvim_create_namespace -/// @param id The extmarks's id -/// @param[out] err Details of an error that may have occurred -/// @return true on success, false if the extmark was not found. +/// @param buffer Buffer handle, or 0 for current buffer +/// @param ns_id Namespace id from |nvim_create_namespace()| +/// @param id Extmark id +/// @param[out] err Error details, if any +/// @return true if the extmark was found, else false Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, @@ -1309,7 +1325,8 @@ Integer nvim_buf_add_highlight(Buffer buffer, return ns_id; } -/// Clears namespaced objects, highlights and virtual text, from a line range +/// Clears namespaced objects (highlights, extmarks, virtual text) from +/// a region. /// /// Lines are 0-indexed. |api-indexing| To clear the namespace in the entire /// buffer, specify line_start=0 and line_end=-1. diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index fbfdb27827..b8d62e42a1 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1512,7 +1512,7 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf) // If throw == true then an error will be raised if nothing // was found // Returns NULL if something went wrong -Extmark *extmark_from_id_or_pos(Buffer buffer, Integer namespace, Object id, +Extmark *extmark_from_id_or_pos(Buffer buffer, Integer ns, Object id, Error *err, bool throw) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -1536,7 +1536,7 @@ Extmark *extmark_from_id_or_pos(Buffer buffer, Integer namespace, Object id, } return NULL; } - extmark = extmark_from_pos(buf, (uint64_t)namespace, row, col); + extmark = extmark_from_pos(buf, (uint64_t)ns, row, col); } else if (id.type != kObjectTypeInteger) { if (throw) { api_set_error(err, kErrorTypeValidation, @@ -1550,7 +1550,7 @@ Extmark *extmark_from_id_or_pos(Buffer buffer, Integer namespace, Object id, return NULL; } else { extmark = extmark_from_id(buf, - (uint64_t)namespace, + (uint64_t)ns, (uint64_t)id.data.integer); } @@ -1572,17 +1572,17 @@ bool ns_initialized(uint64_t ns) return ns < (uint64_t)next_namespace_id; } -/// Get line and column from extmark object +/// Gets the line and column of an extmark. /// -/// Extmarks may be queried from position or name or even special names -/// in the future such as "cursor". This function sets the line and col -/// to make the extmark functions recognize what's required +/// Extmarks may be queried by position, name or even special names +/// in the future such as "cursor". /// -/// @param[out] lnum lnum to be set -/// @param[out] colnr col to be set -bool set_extmark_index_from_obj(buf_T *buf, Integer namespace, - Object obj, linenr_T *lnum, colnr_T *colnr, - Error *err) +/// @param[out] lnum extmark line +/// @param[out] colnr extmark column +/// +/// @return true if the extmark was found, else false +bool extmark_get_index_from_obj(buf_T *buf, Integer ns, Object obj, linenr_T + *lnum, colnr_T *colnr, Error *err) { // Check if it is mark id if (obj.type == kObjectTypeInteger) { @@ -1600,7 +1600,7 @@ bool set_extmark_index_from_obj(buf_T *buf, Integer namespace, return false; } - Extmark *extmark = extmark_from_id(buf, (uint64_t)namespace, (uint64_t)id); + Extmark *extmark = extmark_from_id(buf, (uint64_t)ns, (uint64_t)id); if (extmark) { *lnum = extmark->line->lnum; *colnr = extmark->col; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 10f7dd1a7b..19601b6539 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -25,6 +25,7 @@ #include "nvim/highlight.h" #include "nvim/window.h" #include "nvim/types.h" +#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/screen.h" #include "nvim/memline.h" @@ -72,10 +73,70 @@ void api_vim_free_all_mem(void) map_free(String, handle_T)(namespace_ids); } +/// Executes Vimscript (multiline block of Ex-commands), like anonymous +/// |:source|. +/// +/// Unlike |nvim_command()| this function supports heredocs, script-scope (s:), +/// etc. +/// +/// On execution error: fails with VimL error, does not update v:errmsg. +/// +/// @see |execute()| +/// @see |nvim_command()| +/// +/// @param src Vimscript code +/// @param output Capture and return all (non-error, non-shell |:!|) output +/// @param[out] err Error details (Vim error), if any +/// @return Output (non-error, non-shell |:!|) if `output` is true, +/// else empty string. +String nvim_exec(String src, Boolean output, Error *err) + FUNC_API_SINCE(7) +{ + const int save_msg_silent = msg_silent; + garray_T *const save_capture_ga = capture_ga; + garray_T capture_local; + if (output) { + ga_init(&capture_local, 1, 80); + capture_ga = &capture_local; + } + + try_start(); + msg_silent++; + do_source_str(src.data, "nvim_exec()"); + capture_ga = save_capture_ga; + msg_silent = save_msg_silent; + try_end(err); + + if (ERROR_SET(err)) { + goto theend; + } + + if (output && capture_local.ga_len > 1) { + String s = (String){ + .data = capture_local.ga_data, + .size = (size_t)capture_local.ga_len, + }; + // redir usually (except :echon) prepends a newline. + if (s.data[0] == '\n') { + memmove(s.data, s.data + 1, s.size - 1); + s.data[s.size - 1] = '\0'; + s.size = s.size - 1; + } + return s; // Caller will free the memory. + } +theend: + if (output) { + ga_clear(&capture_local); + } + return (String)STRING_INIT; +} + /// Executes an ex-command. /// /// On execution error: fails with VimL error, does not update v:errmsg. /// +/// @see |nvim_exec()| +/// /// @param command Ex-command string /// @param[out] err Error details (Vim error), if any void nvim_command(String command, Error *err) @@ -332,53 +393,16 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, return cstr_as_string(ptr); } -/// Executes an ex-command and returns its (non-error) output. -/// Shell |:!| output is not captured. -/// -/// On execution error: fails with VimL error, does not update v:errmsg. -/// -/// @param command Ex-command string -/// @param[out] err Error details (Vim error), if any +/// @deprecated +/// @see nvim_exec String nvim_command_output(String command, Error *err) FUNC_API_SINCE(1) + FUNC_API_DEPRECATED_SINCE(7) { - const int save_msg_silent = msg_silent; - garray_T *const save_capture_ga = capture_ga; - garray_T capture_local; - ga_init(&capture_local, 1, 80); - - try_start(); - msg_silent++; - capture_ga = &capture_local; - do_cmdline_cmd(command.data); - capture_ga = save_capture_ga; - msg_silent = save_msg_silent; - try_end(err); - - if (ERROR_SET(err)) { - goto theend; - } - - if (capture_local.ga_len > 1) { - String s = (String){ - .data = capture_local.ga_data, - .size = (size_t)capture_local.ga_len, - }; - // redir usually (except :echon) prepends a newline. - if (s.data[0] == '\n') { - memmove(s.data, s.data + 1, s.size - 1); - s.data[s.size - 1] = '\0'; - s.size = s.size - 1; - } - return s; // Caller will free the memory. - } - -theend: - ga_clear(&capture_local); - return (String)STRING_INIT; + return nvim_exec(command, true, err); } -/// Evaluates a VimL expression (:help expression). +/// Evaluates a VimL |expression|. /// Dictionaries and Lists are recursively expanded. /// /// On execution error: fails with VimL error, does not update v:errmsg. @@ -423,6 +447,15 @@ Object nvim_eval(String expr, Error *err) return rv; } +/// @deprecated Use nvim_exec_lua() instead. +Object nvim_execute_lua(String code, Array args, Error *err) + FUNC_API_SINCE(3) + FUNC_API_DEPRECATED_SINCE(7) + FUNC_API_REMOTE_ONLY +{ + return executor_exec_lua_api(code, args, err); +} + /// Execute Lua code. Parameters (if any) are available as `...` inside the /// chunk. The chunk can return a value. /// @@ -435,8 +468,9 @@ Object nvim_eval(String expr, Error *err) /// or executing the Lua code. /// /// @return Return value of Lua code if present or NIL. -Object nvim_execute_lua(String code, Array args, Error *err) - FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY +Object nvim_exec_lua(String code, Array args, Error *err) + FUNC_API_SINCE(7) + FUNC_API_REMOTE_ONLY { return executor_exec_lua_api(code, args, err); } @@ -1074,9 +1108,10 @@ fail: /// float where the text should not be edited. Disables /// 'number', 'relativenumber', 'cursorline', 'cursorcolumn', /// 'foldcolumn', 'spell' and 'list' options. 'signcolumn' -/// is changed to `auto`. The end-of-buffer region is hidden -/// by setting `eob` flag of 'fillchars' to a space char, -/// and clearing the |EndOfBuffer| region in 'winhighlight'. +/// is changed to `auto` and 'colorcolumn' is cleared. The +/// end-of-buffer region is hidden by setting `eob` flag of +/// 'fillchars' to a space char, and clearing the +/// |EndOfBuffer| region in 'winhighlight'. /// @param[out] err Error details, if any /// /// @return Window handle, or 0 on error @@ -1095,6 +1130,10 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dictionary config, if (enter) { win_enter(wp, false); } + if (!win_valid(wp)) { + api_set_error(err, kErrorTypeException, "Window was closed immediately"); + return 0; + } if (buffer > 0) { nvim_win_set_buf(wp->handle, buffer, err); } @@ -1246,8 +1285,8 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err) Array lines = string_to_array(data, crlf); ADD(args, ARRAY_OBJ(lines)); ADD(args, INTEGER_OBJ(phase)); - rv = nvim_execute_lua(STATIC_CSTR_AS_STRING("return vim.paste(...)"), args, - err); + rv = nvim_exec_lua(STATIC_CSTR_AS_STRING("return vim.paste(...)"), args, + err); if (ERROR_SET(err)) { draining = true; goto theend; @@ -1290,7 +1329,7 @@ theend: /// @param lines |readfile()|-style list of lines. |channel-lines| /// @param type Edit behavior: any |getregtype()| result, or: /// - "b" |blockwise-visual| mode (may include width, e.g. "b3") -/// - "c" |characterwise| mode +/// - "c" |charwise| mode /// - "l" |linewise| mode /// - "" guess by contents, see |setreg()| /// @param after Insert after cursor (like |p|), or before (like |P|). @@ -2381,7 +2420,7 @@ Array nvim_get_proc_children(Integer pid, Error *err) Array a = ARRAY_DICT_INIT; ADD(a, INTEGER_OBJ(pid)); String s = cstr_to_string("return vim._os_proc_children(select(1, ...))"); - Object o = nvim_execute_lua(s, a, err); + Object o = nvim_exec_lua(s, a, err); api_free_string(s); api_free_array(a); if (o.type == kObjectTypeArray) { @@ -2427,7 +2466,7 @@ Object nvim_get_proc(Integer pid, Error *err) Array a = ARRAY_DICT_INIT; ADD(a, INTEGER_OBJ(pid)); String s = cstr_to_string("return vim._os_proc_info(select(1, ...))"); - Object o = nvim_execute_lua(s, a, err); + Object o = nvim_exec_lua(s, a, err); api_free_string(s); api_free_array(a); if (o.type == kObjectTypeArray && o.data.array.size == 0) { diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 79f339b3aa..33ffff39f6 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -411,11 +411,11 @@ bool buf_valid(buf_T *buf) /// caller should get a new buffer very soon! /// The 'bufhidden' option can force freeing and deleting. /// @param abort_if_last -/// If TRUE, do not close the buffer if autocommands cause +/// If true, do not close the buffer if autocommands cause /// there to be only one window with this buffer. e.g. when /// ":quit" is supposed to close the window but autocommands /// close all other windows. -void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last) +void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) { bool unload_buf = (action != 0); bool del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE); @@ -1238,7 +1238,7 @@ do_buffer( return FAIL; } } else { - EMSG2(_("E89: %s will be killed(add ! to override)"), + EMSG2(_("E89: %s will be killed (add ! to override)"), (char *)buf->b_fname); return FAIL; } @@ -1585,10 +1585,12 @@ void enter_buffer(buf_T *buf) open_buffer(false, NULL, 0); } else { - if (!msg_silent) { + if (!msg_silent && !shortmess(SHM_FILEINFO)) { need_fileinfo = true; // display file info after redraw } - (void)buf_check_timestamp(curbuf, false); // check if file changed + // check if file changed + (void)buf_check_timestamp(curbuf, false); + curwin->w_topline = 1; curwin->w_topfill = 0; apply_autocmds(EVENT_BUFENTER, NULL, NULL, false, curbuf); @@ -2692,7 +2694,7 @@ setfname( buf_T *buf, char_u *ffname, char_u *sfname, - int message // give message when buffer already exists + bool message // give message when buffer already exists ) { buf_T *obuf = NULL; @@ -3799,14 +3801,20 @@ int build_stl_str_hl( buf_T *const save_curbuf = curbuf; win_T *const save_curwin = curwin; + const int save_VIsual_active = VIsual_active; curwin = wp; curbuf = wp->w_buffer; + // Visual mode is only valid in the current window. + if (curwin != save_curwin) { + VIsual_active = false; + } // Note: The result stored in `t` is unused. str = eval_to_string_safe(out_p, &t, use_sandbox); curwin = save_curwin; curbuf = save_curbuf; + VIsual_active = save_VIsual_active; // Remove the variable we just stored do_unlet(S_LEN("g:actual_curbuf"), true); @@ -5626,6 +5634,86 @@ void bufhl_mark_adjust(buf_T* buf, } } +/// Adjust a placed highlight for column changes and joined/broken lines +bool bufhl_mark_col_adjust(buf_T *buf, + linenr_T lnum, + colnr_T mincol, + long lnum_amount, + long col_amount) +{ + bool moved = false; + BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, lnum, false); + if (!lineinfo) { + // Old line empty, nothing to do + return false; + } + // Create the new line below only if needed + BufhlLine *lineinfo2 = NULL; + + colnr_T delcol = MAXCOL; + if (lnum_amount == 0 && col_amount < 0) { + delcol = mincol+(int)col_amount; + } + + size_t newidx = 0; + for (size_t i = 0; i < kv_size(lineinfo->items); i++) { + BufhlItem *item = &kv_A(lineinfo->items, i); + bool delete = false; + if (item->start >= mincol) { + moved = true; + item->start += (int)col_amount; + if (item->stop < MAXCOL) { + item->stop += (int)col_amount; + } + if (lnum_amount != 0) { + if (lineinfo2 == NULL) { + lineinfo2 = bufhl_tree_ref(&buf->b_bufhl_info, + lnum+lnum_amount, true); + } + kv_push(lineinfo2->items, *item); + delete = true; + } + } else { + if (item->start >= delcol) { + moved = true; + item->start = delcol; + } + if (item->stop == MAXCOL || item->stop+1 >= mincol) { + if (item->stop == MAXCOL) { + if (delcol < MAXCOL + && delcol > (colnr_T)STRLEN(ml_get_buf(buf, lnum, false))) { + delete = true; + } + } else { + moved = true; + item->stop += (int)col_amount; + } + assert(lnum_amount >= 0); + if (lnum_amount > 0) { + item->stop = MAXCOL; + } + } else if (item->stop+1 >= delcol) { + moved = true; + item->stop = delcol-1; + } + // we covered the entire range with a visual delete or something + if (item->stop < item->start) { + delete = true; + } + } + + if (!delete) { + if (i != newidx) { + kv_A(lineinfo->items, newidx) = kv_A(lineinfo->items, i); + } + newidx++; + } + } + kv_size(lineinfo->items) = newidx; + + return moved; +} + /// Get highlights to display at a specific line /// diff --git a/src/nvim/change.c b/src/nvim/change.c index 7558055696..8a782c2b20 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -359,6 +359,24 @@ void changed_bytes(linenr_T lnum, colnr_T col) } } +/// insert/delete bytes at column +/// +/// Like changed_bytes() but also adjust extmark for "added" bytes. +/// When "added" is negative text was deleted. +static void inserted_bytes(linenr_T lnum, colnr_T col, int added) +{ + if (added > 0) { + extmark_col_adjust(curbuf, lnum, col+1, 0, added, kExtmarkUndo); + } else if (added < 0) { + // TODO(bfredl): next revision of extmarks should handle both these + // with the same entry point. Also with more sane params.. + extmark_col_adjust_delete(curbuf, lnum, col+2, + col+(-added)+1, kExtmarkUndo, 0); + } + + changed_bytes(lnum, col); +} + /// Appended "count" lines below line "lnum" in the current buffer. /// Must be called AFTER the change and after mark_adjust(). /// Takes care of marking the buffer to be redrawn and sets the changed flag. @@ -630,7 +648,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) ml_replace(lnum, newp, false); // mark the buffer as changed and prepare for displaying - changed_bytes(lnum, (colnr_T)col); + inserted_bytes(lnum, (colnr_T)col, (int)(newlen - oldlen)); // If we're in Insert or Replace mode and 'showmatch' is set, then briefly // show the match for right parens and braces. @@ -676,7 +694,7 @@ void ins_str(char_u *s) assert(bytes >= 0); memmove(newp + col + newlen, oldp + col, (size_t)bytes); ml_replace(lnum, newp, false); - changed_bytes(lnum, col); + inserted_bytes(lnum, col, newlen); curwin->w_cursor.col += newlen; } @@ -797,7 +815,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) } // mark the buffer as changed and prepare for displaying - changed_bytes(lnum, curwin->w_cursor.col); + inserted_bytes(lnum, col, -count); return OK; } diff --git a/src/nvim/context.c b/src/nvim/context.c index 2f872ff2bf..1ae0510762 100644 --- a/src/nvim/context.c +++ b/src/nvim/context.c @@ -254,7 +254,7 @@ static inline void ctx_save_funcs(Context *ctx, bool scriptonly) size_t cmd_len = sizeof("func! ") + STRLEN(name); char *cmd = xmalloc(cmd_len); snprintf(cmd, cmd_len, "func! %s", name); - String func_body = nvim_command_output(cstr_as_string(cmd), &err); + String func_body = nvim_exec(cstr_as_string(cmd), true, &err); xfree(cmd); if (!ERROR_SET(&err)) { ADD(ctx->funcs, STRING_OBJ(func_body)); diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 25b6502b19..eecea03a19 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -3056,7 +3056,9 @@ static void ins_compl_clear(void) XFREE_CLEAR(compl_orig_text); compl_enter_selects = false; // clear v:completed_item - set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc()); + dict_T *const d = tv_dict_alloc(); + d->dv_lock = VAR_FIXED; + set_vim_var_dict(VV_COMPLETED_ITEM, d); } /// Check that Insert completion is active. @@ -4313,7 +4315,9 @@ static void ins_compl_delete(void) // causes flicker, thus we can't do that. changed_cline_bef_curs(); // clear v:completed_item - set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc()); + dict_T *const d = tv_dict_alloc(); + d->dv_lock = VAR_FIXED; + set_vim_var_dict(VV_COMPLETED_ITEM, d); } // Insert the new text being completed. @@ -4335,6 +4339,7 @@ static dict_T *ins_compl_dict_alloc(compl_T *match) { // { word, abbr, menu, kind, info } dict_T *dict = tv_dict_alloc(); + dict->dv_lock = VAR_FIXED; tv_dict_add_str( dict, S_LEN("word"), (const char *)EMPTY_IF_NULL(match->cp_str)); @@ -5595,9 +5600,6 @@ insertchar ( do_digraph(buf[i-1]); /* may be the start of a digraph */ buf[i] = NUL; ins_str(buf); - extmark_col_adjust(curbuf, curwin->w_cursor.lnum, - (colnr_T)(curwin->w_cursor.col + 1), 0, - (long)STRLEN(buf), kExtmarkUndo); if (flags & INSCHAR_CTRLV) { redo_literal(*buf); i = 1; @@ -5608,9 +5610,6 @@ insertchar ( } else { int cc; - extmark_col_adjust(curbuf, curwin->w_cursor.lnum, - (colnr_T)(curwin->w_cursor.col + 1), 0, - 1, kExtmarkUndo); if ((cc = utf_char2len(c)) > 1) { char_u buf[MB_MAXBYTES + 1]; @@ -8501,14 +8500,6 @@ static bool ins_tab(void) temp -= get_nolist_virtcol() % temp; - // Move extmarks - extmark_col_adjust(curbuf, - curwin->w_cursor.lnum, - curwin->w_cursor.col, - 0, - temp, - kExtmarkUndo); - /* * Insert the first space with ins_char(). It will delete one char in * replace mode. Insert the rest with ins_str(); it will not delete any diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 9fe92a92cc..e99c41a915 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2872,7 +2872,7 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx) int c; char_u *p; - if (cmdidx == CMD_let) { + if (cmdidx == CMD_let || cmdidx == CMD_const) { xp->xp_context = EXPAND_USER_VARS; if (vim_strpbrk(arg, (char_u *)"\"'+-*/%.=!?~|&$([<>,#") == NULL) { /* ":let var1 var2 ...": find last space. */ @@ -11503,6 +11503,9 @@ static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) { static const char *const has_list[] = { +#if defined(BSD) && !defined(__APPLE__) + "bsd", +#endif #ifdef UNIX "unix", #endif @@ -20524,7 +20527,7 @@ static hashtab_T *get_funccal_local_ht(void) return &get_funccal()->l_vars.dv_hashtab; } -/// Find the dict and hashtable used for a variable +/// Finds the dict (g:, l:, s:, …) and hashtable used for a variable. /// /// @param[in] name Variable name, possibly with scope prefix. /// @param[in] name_len Variable name length. diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 106b8f6eed..72ee45a03a 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1200,6 +1200,7 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key, typval_T rettv; + dict->dv_refcount++; QUEUE *w; QUEUE_FOREACH(w, &dict->watchers) { DictWatcher *watcher = tv_dict_watcher_node_data(w); @@ -1211,6 +1212,7 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key, tv_clear(&rettv); } } + tv_dict_unref(dict); for (size_t i = 1; i < ARRAY_SIZE(argv); i++) { tv_clear(argv + i); diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 4725246764..0c3b467612 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -1587,7 +1587,7 @@ int rename_buffer(char_u *new_fname) xfname = curbuf->b_fname; curbuf->b_ffname = NULL; curbuf->b_sfname = NULL; - if (setfname(curbuf, new_fname, NULL, TRUE) == FAIL) { + if (setfname(curbuf, new_fname, NULL, true) == FAIL) { curbuf->b_ffname = fname; curbuf->b_sfname = sfname; return FAIL; diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 84291b3637..496aecfb27 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -3015,11 +3015,78 @@ static FILE *fopen_noinh_readbin(char *filename) return fdopen(fd_tmp, READBIN); } +typedef struct { + char_u *buf; + size_t offset; +} GetStrLineCookie; -/// Read the file "fname" and execute its lines as EX commands. +/// Get one full line from a sourced string (in-memory, no file). +/// Called by do_cmdline() when it's called from do_source_str(). +/// +/// @return pointer to allocated line, or NULL for end-of-file or +/// some error. +static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat) +{ + GetStrLineCookie *p = cookie; + size_t i = p->offset; + if (strlen((char *)p->buf) <= p->offset) { + return NULL; + } + while (!(p->buf[i] == '\n' || p->buf[i] == '\0')) { + i++; + } + char buf[2046]; + char *dst; + dst = xstpncpy(buf, (char *)p->buf + p->offset, i - p->offset); + if ((uint32_t)(dst - buf) != i - p->offset) { + smsg(_(":source error parsing command %s"), p->buf); + return NULL; + } + buf[i - p->offset] = '\0'; + p->offset = i + 1; + return (char_u *)xstrdup(buf); +} + +/// Executes lines in `src` as Ex commands. +/// +/// @see do_source() +int do_source_str(const char *cmd, const char *traceback_name) +{ + char_u *save_sourcing_name = sourcing_name; + linenr_T save_sourcing_lnum = sourcing_lnum; + char_u sourcing_name_buf[256]; + if (save_sourcing_name == NULL) { + sourcing_name = (char_u *)traceback_name; + } else { + snprintf((char *)sourcing_name_buf, sizeof(sourcing_name_buf), + "%s called at %s:%"PRIdLINENR, traceback_name, save_sourcing_name, + save_sourcing_lnum); + sourcing_name = sourcing_name_buf; + } + sourcing_lnum = 0; + + GetStrLineCookie cookie = { + .buf = (char_u *)cmd, + .offset = 0, + }; + const sctx_T save_current_sctx = current_sctx; + current_sctx.sc_sid = SID_STR; + current_sctx.sc_seq = 0; + current_sctx.sc_lnum = save_sourcing_lnum; + int retval = do_cmdline(NULL, get_str_line, (void *)&cookie, + DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT); + current_sctx = save_current_sctx; + sourcing_lnum = save_sourcing_lnum; + sourcing_name = save_sourcing_name; + return retval; +} + +/// Reads the file `fname` and executes its lines as Ex commands. /// /// This function may be called recursively! /// +/// @see do_source_str +/// /// @param fname /// @param check_other check for .vimrc and _vimrc /// @param is_vimrc DOSO_ value @@ -3360,6 +3427,8 @@ char_u *get_scriptname(LastSet last_set, bool *should_free) _("API client (channel id %" PRIu64 ")"), last_set.channel_id); return IObuff; + case SID_STR: + return (char_u *)_("anonymous :source"); default: *should_free = true; return home_replace_save(NULL, diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 641edf4610..30c1373445 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -296,25 +296,23 @@ int do_cmdline_cmd(const char *cmd) DOCMD_NOWAIT|DOCMD_KEYTYPED); } -/* - * do_cmdline(): execute one Ex command line - * - * 1. Execute "cmdline" when it is not NULL. - * If "cmdline" is NULL, or more lines are needed, fgetline() is used. - * 2. Split up in parts separated with '|'. - * - * This function can be called recursively! - * - * flags: - * DOCMD_VERBOSE - The command will be included in the error message. - * DOCMD_NOWAIT - Don't call wait_return() and friends. - * DOCMD_REPEAT - Repeat execution until fgetline() returns NULL. - * DOCMD_KEYTYPED - Don't reset KeyTyped. - * DOCMD_EXCRESET - Reset the exception environment (used for debugging). - * DOCMD_KEEPLINE - Store first typed line (for repeating with "."). - * - * return FAIL if cmdline could not be executed, OK otherwise - */ +/// do_cmdline(): execute one Ex command line +/// +/// 1. Execute "cmdline" when it is not NULL. +/// If "cmdline" is NULL, or more lines are needed, fgetline() is used. +/// 2. Split up in parts separated with '|'. +/// +/// This function can be called recursively! +/// +/// flags: +/// DOCMD_VERBOSE - The command will be included in the error message. +/// DOCMD_NOWAIT - Don't call wait_return() and friends. +/// DOCMD_REPEAT - Repeat execution until fgetline() returns NULL. +/// DOCMD_KEYTYPED - Don't reset KeyTyped. +/// DOCMD_EXCRESET - Reset the exception environment (used for debugging). +/// DOCMD_KEEPLINE - Store first typed line (for repeating with "."). +/// +/// @return FAIL if cmdline could not be executed, OK otherwise int do_cmdline(char_u *cmdline, LineGetter fgetline, void *cookie, /* argument for fgetline() */ int flags) @@ -421,13 +419,12 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, // If force_abort is set, we cancel everything. did_emsg = false; - /* - * KeyTyped is only set when calling vgetc(). Reset it here when not - * calling vgetc() (sourced command lines). - */ + // KeyTyped is only set when calling vgetc(). Reset it here when not + // calling vgetc() (sourced command lines). if (!(flags & DOCMD_KEYTYPED) - && !getline_equal(fgetline, cookie, getexline)) + && !getline_equal(fgetline, cookie, getexline)) { KeyTyped = false; + } /* * Continue executing command lines: @@ -2126,6 +2123,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, case CMD_browse: case CMD_call: case CMD_confirm: + case CMD_const: case CMD_delfunction: case CMD_djump: case CMD_dlist: @@ -2150,6 +2148,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, case CMD_leftabove: case CMD_let: case CMD_lockmarks: + case CMD_lockvar: case CMD_lua: case CMD_match: case CMD_mzscheme: @@ -2178,6 +2177,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, case CMD_tilde: case CMD_topleft: case CMD_unlet: + case CMD_unlockvar: case CMD_verbose: case CMD_vertical: case CMD_wincmd: @@ -3437,6 +3437,7 @@ const char * set_one_cmd_context( case CMD_syntax: set_context_in_syntax_cmd(xp, arg); break; + case CMD_const: case CMD_let: case CMD_if: case CMD_elseif: @@ -5133,9 +5134,11 @@ static void uc_list(char_u *name, size_t name_len) ucmd_T *cmd; int len; uint32_t a; - garray_T *gap; - gap = &curbuf->b_ucmds; + // In cmdwin, the alternative buffer should be used. + garray_T *gap = (cmdwin_type != 0 && get_cmdline_type() == NUL) + ? &prevwin->w_buffer->b_ucmds + : &curbuf->b_ucmds; for (;; ) { for (i = 0; i < gap->ga_len; ++i) { cmd = USER_CMD_GA(gap, i); @@ -5984,13 +5987,21 @@ char_u *get_user_cmd_addr_type(expand_T *xp, int idx) /* * Function given to ExpandGeneric() to obtain the list of user command names. */ -char_u *get_user_commands(expand_T *xp, int idx) +char_u *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - if (idx < curbuf->b_ucmds.ga_len) - return USER_CMD_GA(&curbuf->b_ucmds, idx)->uc_name; - idx -= curbuf->b_ucmds.ga_len; - if (idx < ucmds.ga_len) + // In cmdwin, the alternative buffer should be used. + const buf_T *const buf = (cmdwin_type != 0 && get_cmdline_type() == NUL) + ? prevwin->w_buffer + : curbuf; + + if (idx < buf->b_ucmds.ga_len) { + return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; + } + idx -= buf->b_ucmds.ga_len; + if (idx < ucmds.ga_len) { return USER_CMD(idx)->uc_name; + } return NULL; } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 9e2671ca5e..73353f84cf 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -5008,19 +5008,24 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file, hashtab_T found_ht; hash_init(&found_ht); for (s = path; ; s = e) { + e = vim_strchr(s, ENV_SEPCHAR); + if (e == NULL) { + e = s + STRLEN(s); + } + if (*s == NUL) { if (did_curdir) { break; } // Find directories in the current directory, path is empty. did_curdir = true; - } else if (*s == '.') { + flags |= EW_DIR; + } else if (STRNCMP(s, ".", e - s) == 0) { did_curdir = true; - } - - e = vim_strchr(s, ENV_SEPCHAR); - if (e == NULL) { - e = s + STRLEN(s); + flags |= EW_DIR; + } else { + // Do not match directories inside a $PATH item. + flags &= ~EW_DIR; } l = (size_t)(e - s); @@ -6073,12 +6078,9 @@ static int open_cmdwin(void) set_bufref(&old_curbuf, curbuf); - /* Save current window sizes. */ + // Save current window sizes. win_size_save(&winsizes); - /* Don't execute autocommands while creating the window. */ - block_autocmds(); - // When using completion in Insert mode with <C-R>=<C-F> one can open the // command line window, but we don't want the popup menu then. pum_undisplay(true); @@ -6087,10 +6089,9 @@ static int open_cmdwin(void) cmdmod.tab = 0; cmdmod.noswapfile = 1; - /* Create a window for the command-line buffer. */ + // Create a window for the command-line buffer. if (win_split((int)p_cwh, WSP_BOT) == FAIL) { beep_flush(); - unblock_autocmds(); return K_IGNORE; } cmdwin_type = get_cmdline_type(); @@ -6105,13 +6106,11 @@ static int open_cmdwin(void) curbuf->b_p_ma = true; curwin->w_p_fen = false; - // Do execute autocommands for setting the filetype (load syntax). - unblock_autocmds(); - // But don't allow switching to another buffer. + // Don't allow switching to another buffer. curbuf_lock++; - /* Showing the prompt may have set need_wait_return, reset it. */ - need_wait_return = FALSE; + // Showing the prompt may have set need_wait_return, reset it. + need_wait_return = false; const int histtype = hist_char2type(cmdwin_type); if (histtype == HIST_CMD || histtype == HIST_DEBUG) { @@ -6123,11 +6122,11 @@ static int open_cmdwin(void) } curbuf_lock--; - /* Reset 'textwidth' after setting 'filetype' (the Vim filetype plugin - * sets 'textwidth' to 78). */ + // Reset 'textwidth' after setting 'filetype' (the Vim filetype plugin + // sets 'textwidth' to 78). curbuf->b_p_tw = 0; - /* Fill the buffer with the history. */ + // Fill the buffer with the history. init_history(); if (hislen > 0 && histtype != HIST_INVALID) { i = hisidx[histtype]; @@ -6168,9 +6167,10 @@ static int open_cmdwin(void) // Trigger CmdwinEnter autocommands. typestr[0] = (char_u)cmdwin_type; typestr[1] = NUL; - apply_autocmds(EVENT_CMDWINENTER, typestr, typestr, FALSE, curbuf); - if (restart_edit != 0) /* autocmd with ":startinsert" */ + apply_autocmds(EVENT_CMDWINENTER, typestr, typestr, false, curbuf); + if (restart_edit != 0) { // autocmd with ":startinsert" stuffcharReadbuff(K_NOP); + } i = RedrawingDisabled; RedrawingDisabled = 0; @@ -6187,10 +6187,10 @@ static int open_cmdwin(void) const bool save_KeyTyped = KeyTyped; - /* Trigger CmdwinLeave autocommands. */ - apply_autocmds(EVENT_CMDWINLEAVE, typestr, typestr, FALSE, curbuf); + // Trigger CmdwinLeave autocommands. + apply_autocmds(EVENT_CMDWINLEAVE, typestr, typestr, false, curbuf); - /* Restore KeyTyped in case it is modified by autocommands */ + // Restore KeyTyped in case it is modified by autocommands KeyTyped = save_KeyTyped; // Restore the command line info. @@ -6249,9 +6249,7 @@ static int open_cmdwin(void) } } - /* Don't execute autocommands while deleting the window. */ - block_autocmds(); - // Avoid command-line window first character being concealed + // Avoid command-line window first character being concealed. curwin->w_p_cole = 0; wp = curwin; set_bufref(&bufref, curbuf); @@ -6264,10 +6262,8 @@ static int open_cmdwin(void) close_buffer(NULL, bufref.br_buf, DOBUF_WIPE, false); } - /* Restore window sizes. */ + // Restore window sizes. win_size_restore(&winsizes); - - unblock_autocmds(); } ga_clear(&winsizes); diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index fcf15638c7..f518e59acc 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -3731,8 +3731,9 @@ static int set_rw_fname(char_u *fname, char_u *sfname) return FAIL; } - if (setfname(curbuf, fname, sfname, FALSE) == OK) + if (setfname(curbuf, fname, sfname, false) == OK) { curbuf->b_flags |= BF_NOTEDITED; + } /* ....and a new named one is created */ apply_autocmds(EVENT_BUFNEW, NULL, NULL, FALSE, curbuf); diff --git a/src/nvim/generators/gen_char_blob.lua b/src/nvim/generators/gen_char_blob.lua index 1702add2e4..a7dad50d48 100644 --- a/src/nvim/generators/gen_char_blob.lua +++ b/src/nvim/generators/gen_char_blob.lua @@ -1,49 +1,59 @@ if arg[1] == '--help' then print('Usage:') - print(' gencharblob.lua source target varname') + print(' '..arg[0]..' target source varname [source varname]...') print('') print('Generates C file with big uint8_t blob.') print('Blob will be stored in a static const array named varname.') os.exit() end -assert(#arg == 3) +assert(#arg >= 3 and (#arg - 1) % 2 == 0) -local source_file = arg[1] -local target_file = arg[2] -local varname = arg[3] - -local source = io.open(source_file, 'r') +local target_file = arg[1] or error('Need a target file') local target = io.open(target_file, 'w') target:write('#include <stdint.h>\n\n') -target:write(('static const uint8_t %s[] = {\n'):format(varname)) - -local num_bytes = 0 -local MAX_NUM_BYTES = 15 -- 78 / 5: maximum number of bytes on one line -target:write(' ') - -local increase_num_bytes -increase_num_bytes = function() - num_bytes = num_bytes + 1 - if num_bytes == MAX_NUM_BYTES then - num_bytes = 0 - target:write('\n ') + +local varnames = {} +for argi = 2, #arg, 2 do + local source_file = arg[argi] + local varname = arg[argi + 1] + if varnames[varname] then + error(string.format("varname %q is already specified for file %q", varname, varnames[varname])) end -end + varnames[varname] = source_file + + local source = io.open(source_file, 'r') + or error(string.format("source_file %q doesn't exist", source_file)) + + target:write(('static const uint8_t %s[] = {\n'):format(varname)) -for line in source:lines() do - for i = 1,string.len(line) do - local byte = string.byte(line, i) - assert(byte ~= 0) - target:write(string.format(' %3u,', byte)) + local num_bytes = 0 + local MAX_NUM_BYTES = 15 -- 78 / 5: maximum number of bytes on one line + target:write(' ') + + local increase_num_bytes + increase_num_bytes = function() + num_bytes = num_bytes + 1 + if num_bytes == MAX_NUM_BYTES then + num_bytes = 0 + target:write('\n ') + end + end + + for line in source:lines() do + for i = 1, string.len(line) do + local byte = line:byte(i) + assert(byte ~= 0) + target:write(string.format(' %3u,', byte)) + increase_num_bytes() + end + target:write(string.format(' %3u,', string.byte('\n', 1))) increase_num_bytes() end - target:write(string.format(' %3u,', string.byte('\n', 1))) - increase_num_bytes() -end -target:write(' 0};\n') + target:write(' 0};\n') + source:close() +end -source:close() target:close() diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 15ad6d8767..172c190df2 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -331,6 +331,7 @@ EXTERN int garbage_collect_at_exit INIT(= false); #define SID_NONE -6 // don't set scriptID #define SID_LUA -7 // for Lua scripts/chunks #define SID_API_CLIENT -8 // for API clients +#define SID_STR -9 // for sourcing a string // Script CTX being sourced or was sourced to define the current function. EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 }); diff --git a/src/nvim/log.c b/src/nvim/log.c index db36611933..225e40cdb4 100644 --- a/src/nvim/log.c +++ b/src/nvim/log.c @@ -93,6 +93,7 @@ static bool log_path_init(void) void log_init(void) { uv_mutex_init(&mutex); + log_path_init(); } void log_lock(void) diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 44fe60e9c8..09d1a68898 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -1225,7 +1225,7 @@ GENERATE_INDEX_FUNCTION(Tabpage) #undef GENERATE_INDEX_FUNCTION -/// Record some auxilary values in vim module +/// Record some auxiliary values in vim module /// /// Assumes that module table is on top of the stack. /// diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 093c130c5f..25f4be1c4d 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -268,12 +268,7 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL #endif // vim - const char *code = (char *)&vim_module[0]; - if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua") - || lua_pcall(lstate, 0, LUA_MULTRET, 0)) { - nlua_error(lstate, _("E5106: Error while creating vim module: %.*s")); - return 1; - } + lua_newtable(lstate); // vim.api nlua_add_api_functions(lstate); // vim.types, vim.type_idx, vim.val_idx @@ -334,6 +329,24 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_setglobal(lstate, "vim"); + { + const char *code = (char *)&shared_module[0]; + if (luaL_loadbuffer(lstate, code, strlen(code), "@shared.lua") + || lua_pcall(lstate, 0, 0, 0)) { + nlua_error(lstate, _("E5106: Error while creating shared module: %.*s")); + return 1; + } + } + + { + const char *code = (char *)&vim_module[0]; + if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua") + || lua_pcall(lstate, 0, 0, 0)) { + nlua_error(lstate, _("E5106: Error while creating vim module: %.*s")); + return 1; + } + } + return 0; } @@ -785,9 +798,9 @@ static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name, } } -/// Execute lua string +/// Execute Lua string /// -/// Used for nvim_execute_lua(). +/// Used for nvim_exec_lua(). /// /// @param[in] str String to execute. /// @param[in] args array of ... args diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 1665a55aff..090869c68e 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -33,35 +33,35 @@ -- - https://github.com/bakpakin/Fennel (pretty print, repl) -- - https://github.com/howl-editor/howl/tree/master/lib/howl/util +local vim = vim +assert(vim) -- Internal-only until comments in #8107 are addressed. -- Returns: -- {errcode}, {output} -local function _system(cmd) - local out = vim.api.nvim_call_function('system', { cmd }) - local err = vim.api.nvim_get_vvar('shell_error') +function vim._system(cmd) + local out = vim.fn.system(cmd) + local err = vim.v.shell_error return err, out end -- Gets process info from the `ps` command. -- Used by nvim_get_proc() as a fallback. -local function _os_proc_info(pid) +function vim._os_proc_info(pid) if pid == nil or pid <= 0 or type(pid) ~= 'number' then error('invalid pid') end local cmd = { 'ps', '-p', pid, '-o', 'comm=', } - local err, name = _system(cmd) - if 1 == err and string.gsub(name, '%s*', '') == '' then + local err, name = vim._system(cmd) + if 1 == err and vim.trim(name) == '' then return {} -- Process not found. elseif 0 ~= err then - local args_str = vim.api.nvim_call_function('string', { cmd }) - error('command failed: '..args_str) + error('command failed: '..vim.fn.string(cmd)) end - local _, ppid = _system({ 'ps', '-p', pid, '-o', 'ppid=', }) + local _, ppid = vim._system({ 'ps', '-p', pid, '-o', 'ppid=', }) -- Remove trailing whitespace. - name = string.gsub(string.gsub(name, '%s+$', ''), '^.*/', '') - ppid = string.gsub(ppid, '%s+$', '') - ppid = tonumber(ppid) == nil and -1 or tonumber(ppid) + name = vim.trim(name):gsub('^.*/', '') + ppid = tonumber(ppid) or -1 return { name = name, pid = pid, @@ -71,20 +71,19 @@ end -- Gets process children from the `pgrep` command. -- Used by nvim_get_proc_children() as a fallback. -local function _os_proc_children(ppid) +function vim._os_proc_children(ppid) if ppid == nil or ppid <= 0 or type(ppid) ~= 'number' then error('invalid ppid') end local cmd = { 'pgrep', '-P', ppid, } - local err, rv = _system(cmd) - if 1 == err and string.gsub(rv, '%s*', '') == '' then + local err, rv = vim._system(cmd) + if 1 == err and vim.trim(rv) == '' then return {} -- Process not found. elseif 0 ~= err then - local args_str = vim.api.nvim_call_function('string', { cmd }) - error('command failed: '..args_str) + error('command failed: '..vim.fn.string(cmd)) end local children = {} - for s in string.gmatch(rv, '%S+') do + for s in rv:gmatch('%S+') do local i = tonumber(s) if i ~= nil then table.insert(children, i) @@ -98,7 +97,7 @@ end -- Last inserted paths. Used to clear out items from package.[c]path when they -- are no longer in &runtimepath. local last_nvim_paths = {} -local function _update_package_paths() +function vim._update_package_paths() local cur_nvim_paths = {} local rtps = vim.api.nvim_list_runtime_paths() local sep = package.config:sub(1, 1) @@ -162,35 +161,35 @@ local function inspect(object, options) -- luacheck: no unused error(object, options) -- Stub for gen_vimdoc.py end ---- Paste handler, invoked by |nvim_paste()| when a conforming UI ---- (such as the |TUI|) pastes text into the editor. ---- ---- Example: To remove ANSI color codes when pasting: ---- <pre> ---- vim.paste = (function(overridden) ---- return function(lines, phase) ---- for i,line in ipairs(lines) do ---- -- Scrub ANSI color codes from paste input. ---- lines[i] = line:gsub('\27%[[0-9;mK]+', '') ---- end ---- overridden(lines, phase) ---- end ---- end)(vim.paste) ---- </pre> ---- ---@see |paste| ---- ---@param lines |readfile()|-style list of lines to paste. |channel-lines| ---@param phase -1: "non-streaming" paste: the call contains all lines. ---- If paste is "streamed", `phase` indicates the stream state: ---- - 1: starts the paste (exactly once) ---- - 2: continues the paste (zero or more times) ---- - 3: ends the paste (exactly once) ---@returns false if client should cancel the paste. -local function paste(lines, phase) end -- luacheck: no unused -paste = (function() +do local tdots, tick, got_line1 = 0, 0, false - return function(lines, phase) + + --- Paste handler, invoked by |nvim_paste()| when a conforming UI + --- (such as the |TUI|) pastes text into the editor. + --- + --- Example: To remove ANSI color codes when pasting: + --- <pre> + --- vim.paste = (function(overridden) + --- return function(lines, phase) + --- for i,line in ipairs(lines) do + --- -- Scrub ANSI color codes from paste input. + --- lines[i] = line:gsub('\27%[[0-9;mK]+', '') + --- end + --- overridden(lines, phase) + --- end + --- end)(vim.paste) + --- </pre> + --- + --@see |paste| + --- + --@param lines |readfile()|-style list of lines to paste. |channel-lines| + --@param phase -1: "non-streaming" paste: the call contains all lines. + --- If paste is "streamed", `phase` indicates the stream state: + --- - 1: starts the paste (exactly once) + --- - 2: continues the paste (zero or more times) + --- - 3: ends the paste (exactly once) + --@returns false if client should cancel the paste. + function vim.paste(lines, phase) local call = vim.api.nvim_call_function local now = vim.loop.now() local mode = call('mode', {}):sub(1,1) @@ -230,20 +229,33 @@ paste = (function() end return true -- Paste will not continue if not returning `true`. end -end)() +end --- Defers callback `cb` until the Nvim API is safe to call. --- ---@see |lua-loop-callbacks| ---@see |vim.schedule()| ---@see |vim.in_fast_event()| -local function schedule_wrap(cb) +function vim.schedule_wrap(cb) return (function (...) local args = {...} vim.schedule(function() cb(unpack(args)) end) end) end +-- vim.fn.{func}(...) +vim.fn = setmetatable({}, { + __index = function(t, key) + local function _fn(...) + return vim.call(key, ...) + end + t[key] = _fn + return _fn + end +}) + +-- These are for loading runtime modules lazily since they aren't available in +-- the nvim binary as specified in executor.c local function __index(t, key) if key == 'inspect' then t.inspect = require('vim.inspect') @@ -251,10 +263,6 @@ local function __index(t, key) elseif key == 'treesitter' then t.treesitter = require('vim.treesitter') return t.treesitter - elseif require('vim.shared')[key] ~= nil then - -- Expose all `vim.shared` functions on the `vim` module. - t[key] = require('vim.shared')[key] - return t[key] elseif require('vim.uri')[key] ~= nil then -- Expose all `vim.uri` functions on the `vim` module. t[key] = require('vim.uri')[key] @@ -265,29 +273,113 @@ local function __index(t, key) end end +setmetatable(vim, { + __index = __index +}) --- vim.fn.{func}(...) -local function _fn_index(t, key) - local function _fn(...) - return vim.call(key, ...) +-- An easier alias for commands. +vim.cmd = vim.api.nvim_command + +-- These are the vim.env/v/g/o/bo/wo variable magic accessors. +do + local a = vim.api + local validate = vim.validate + local function make_meta_accessor(get, set, del) + validate { + get = {get, 'f'}; + set = {set, 'f'}; + del = {del, 'f', true}; + } + local mt = {} + if del then + function mt:__newindex(k, v) + if v == nil then + return del(k) + end + return set(k, v) + end + else + function mt:__newindex(k, v) + return set(k, v) + end + end + function mt:__index(k) + return get(k) + end + return setmetatable({}, mt) + end + local function pcall_ret(status, ...) + if status then return ... end + end + local function nil_wrap(fn) + return function(...) + return pcall_ret(pcall(fn, ...)) + end + end + vim.g = make_meta_accessor(nil_wrap(a.nvim_get_var), a.nvim_set_var, a.nvim_del_var) + vim.v = make_meta_accessor(nil_wrap(a.nvim_get_vvar), a.nvim_set_vvar) + vim.o = make_meta_accessor(a.nvim_get_option, a.nvim_set_option) + local function getenv(k) + local v = vim.fn.getenv(k) + if v == vim.NIL then + return nil + end + return v + end + vim.env = make_meta_accessor(getenv, vim.fn.setenv) + -- TODO(ashkan) if/when these are available from an API, generate them + -- instead of hardcoding. + local window_options = { + arab = true; arabic = true; breakindent = true; breakindentopt = true; + bri = true; briopt = true; cc = true; cocu = true; + cole = true; colorcolumn = true; concealcursor = true; conceallevel = true; + crb = true; cuc = true; cul = true; cursorbind = true; + cursorcolumn = true; cursorline = true; diff = true; fcs = true; + fdc = true; fde = true; fdi = true; fdl = true; + fdm = true; fdn = true; fdt = true; fen = true; + fillchars = true; fml = true; fmr = true; foldcolumn = true; + foldenable = true; foldexpr = true; foldignore = true; foldlevel = true; + foldmarker = true; foldmethod = true; foldminlines = true; foldnestmax = true; + foldtext = true; lbr = true; lcs = true; linebreak = true; + list = true; listchars = true; nu = true; number = true; + numberwidth = true; nuw = true; previewwindow = true; pvw = true; + relativenumber = true; rightleft = true; rightleftcmd = true; rl = true; + rlc = true; rnu = true; scb = true; scl = true; + scr = true; scroll = true; scrollbind = true; signcolumn = true; + spell = true; statusline = true; stl = true; wfh = true; + wfw = true; winbl = true; winblend = true; winfixheight = true; + winfixwidth = true; winhighlight = true; winhl = true; wrap = true; + } + local function new_buf_opt_accessor(bufnr) + local function get(k) + if window_options[k] then + return a.nvim_err_writeln(k.." is a window option, not a buffer option") + end + if bufnr == nil and type(k) == "number" then + return new_buf_opt_accessor(k) + end + return a.nvim_buf_get_option(bufnr or 0, k) + end + local function set(k, v) + if window_options[k] then + return a.nvim_err_writeln(k.." is a window option, not a buffer option") + end + return a.nvim_buf_set_option(bufnr or 0, k, v) + end + return make_meta_accessor(get, set) + end + vim.bo = new_buf_opt_accessor(nil) + local function new_win_opt_accessor(winnr) + local function get(k) + if winnr == nil and type(k) == "number" then + return new_win_opt_accessor(k) + end + return a.nvim_win_get_option(winnr or nil, k) + end + local function set(k, v) return a.nvim_win_set_option(winnr or nil, k, v) end + return make_meta_accessor(get, set) end - t[key] = _fn - return _fn + vim.wo = new_win_opt_accessor(nil) end -local fn = setmetatable({}, {__index=_fn_index}) - -local module = { - _update_package_paths = _update_package_paths, - _os_proc_children = _os_proc_children, - _os_proc_info = _os_proc_info, - _system = _system, - paste = paste, - schedule_wrap = schedule_wrap, - fn=fn, -} - -setmetatable(module, { - __index = __index -}) return module diff --git a/src/nvim/main.c b/src/nvim/main.c index e39eec4038..c7011f4f4e 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -144,7 +144,6 @@ static const char *err_extra_cmd = void event_init(void) { - log_init(); loop_init(&main_loop, NULL); resize_events = multiqueue_new_child(main_loop.events); @@ -220,6 +219,7 @@ void early_init(void) // First find out the home directory, needed to expand "~" in options. init_homedir(); // find real value of $HOME set_init_1(); + log_init(); TIME_MSG("inits 1"); set_lang_var(); // set v:lang and v:ctype @@ -1516,7 +1516,7 @@ static void create_windows(mparm_T *parmp) /* We can't close the window, it would disturb what * happens next. Clear the file name and set the arg * index to -1 to delete it later. */ - setfname(curbuf, NULL, NULL, FALSE); + setfname(curbuf, NULL, NULL, false); curwin->w_arg_idx = -1; swap_exists_action = SEA_NONE; } else diff --git a/src/nvim/mark_extended.c b/src/nvim/mark_extended.c index 01745f484d..17776d438a 100644 --- a/src/nvim/mark_extended.c +++ b/src/nvim/mark_extended.c @@ -4,7 +4,7 @@ // Implements extended marks for plugins. Each mark exists in a btree of // lines containing btrees of columns. // -// The btree provides efficent range lookups. +// The btree provides efficient range lookups. // A map of pointers to the marks is used for fast lookup by mark id. // // Marks are moved by calls to: extmark_col_adjust, extmark_adjust, or @@ -300,7 +300,7 @@ Extmark *extmark_from_pos(buf_T *buf, uint64_t ns, linenr_T lnum, colnr_T col) return NULL; } -// Returns an avaliable id in a namespace +// Returns an available id in a namespace uint64_t extmark_free_id_get(buf_T *buf, uint64_t ns) { if (!buf->b_extmark_ns) { @@ -910,6 +910,9 @@ void extmark_col_adjust(buf_T *buf, linenr_T lnum, bool marks_moved = extmark_col_adjust_impl(buf, lnum, mincol, lnum_amount, false, col_amount); + marks_moved |= bufhl_mark_col_adjust(buf, lnum, mincol, + lnum_amount, col_amount); + if (undo == kExtmarkUndo && marks_moved) { u_extmark_col_adjust(buf, lnum, mincol, lnum_amount, col_amount); } @@ -938,6 +941,7 @@ void extmark_col_adjust_delete(buf_T *buf, linenr_T lnum, marks_moved = extmark_col_adjust_impl(buf, lnum, mincol, 0, true, (long)endcol); + marks_moved |= bufhl_mark_col_adjust(buf, lnum, endcol, 0, mincol-(endcol+1)); // Deletes at the end of the line have different behaviour than the normal // case when deleted. // Cleanup any marks that are floating beyond the end of line. diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 2824d57f49..e5ba17a0a7 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -540,7 +540,7 @@ void ml_open_file(buf_T *buf) /// file, or reading into an existing buffer, create a swap file now. /// /// @param newfile reading file into new buffer -void check_need_swap(int newfile) +void check_need_swap(bool newfile) { int old_msg_silent = msg_silent; // might be reset by an E325 message msg_silent = 0; // If swap dialog prompts for input, user needs to see it! @@ -937,8 +937,9 @@ void ml_recover(bool checkext) */ if (directly) { expand_env(b0p->b0_fname, NameBuff, MAXPATHL); - if (setfname(curbuf, NameBuff, NULL, TRUE) == FAIL) + if (setfname(curbuf, NameBuff, NULL, true) == FAIL) { goto theend; + } } home_replace(NULL, mfp->mf_fname, NameBuff, MAXPATHL, TRUE); diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index d0aa0653cb..deb7ee6342 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -508,31 +508,30 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp) return NULL; } -/* - * setmouse() - switch mouse on/off depending on current mode and 'mouse' - */ +/// Set UI mouse depending on current mode and 'mouse'. +/// +/// Emits mouse_on/mouse_off UI event (unless 'mouse' is empty). void setmouse(void) { - int checkfor; - ui_cursor_shape(); - /* be quick when mouse is off */ - if (*p_mouse == NUL) + // Be quick when mouse is off. + if (*p_mouse == NUL) { return; + } - if (VIsual_active) + int checkfor = MOUSE_NORMAL; // assume normal mode + if (VIsual_active) { checkfor = MOUSE_VISUAL; - else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE) + } else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE) { checkfor = MOUSE_RETURN; - else if (State & INSERT) + } else if (State & INSERT) { checkfor = MOUSE_INSERT; - else if (State & CMDLINE) + } else if (State & CMDLINE) { checkfor = MOUSE_COMMAND; - else if (State == CONFIRM || State == EXTERNCMD) - checkfor = ' '; /* don't use mouse for ":confirm" or ":!cmd" */ - else - checkfor = MOUSE_NORMAL; /* assume normal mode */ + } else if (State == CONFIRM || State == EXTERNCMD) { + checkfor = ' '; // don't use mouse for ":confirm" or ":!cmd" + } if (mouse_has(checkfor)) { ui_call_mouse_on(); diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 2ef2c3101f..b9dbcc6805 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1422,12 +1422,12 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) if (oap->motion_type == kMTLineWise) { oap->inclusive = false; } else if (oap->motion_type == kMTCharWise) { - // If the motion already was characterwise, toggle "inclusive" + // If the motion already was charwise, toggle "inclusive" oap->inclusive = !oap->inclusive; } oap->motion_type = kMTCharWise; } else if (oap->motion_force == Ctrl_V) { - // Change line- or characterwise motion into Visual block mode. + // Change line- or charwise motion into Visual block mode. if (!VIsual_active) { VIsual_active = true; VIsual = oap->start; @@ -1516,7 +1516,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) } // In Select mode, a linewise selection is operated upon like a - // characterwise selection. + // charwise selection. // Special case: gH<Del> deletes the last line. if (VIsual_select && VIsual_mode == 'V' && cap->oap->op_type != OP_DELETE) { @@ -4588,7 +4588,7 @@ static void nv_colon(cmdarg_T *cap) nv_operator(cap); } else { if (cap->oap->op_type != OP_NOP) { - // Using ":" as a movement is characterwise exclusive. + // Using ":" as a movement is charwise exclusive. cap->oap->motion_type = kMTCharWise; cap->oap->inclusive = false; } else if (cap->count0 && !is_cmdkey) { @@ -6372,8 +6372,8 @@ static void nv_visual(cmdarg_T *cap) if (cap->cmdchar == Ctrl_Q) cap->cmdchar = Ctrl_V; - /* 'v', 'V' and CTRL-V can be used while an operator is pending to make it - * characterwise, linewise, or blockwise. */ + // 'v', 'V' and CTRL-V can be used while an operator is pending to make it + // charwise, linewise, or blockwise. if (cap->oap->op_type != OP_NOP) { motion_force = cap->oap->motion_force = cap->cmdchar; finish_op = false; // operator doesn't finish now but later @@ -7887,15 +7887,17 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent) cap->oap->regname = regname; } - /* When deleted a linewise Visual area, put the register as - * lines to avoid it joined with the next line. When deletion was - * characterwise, split a line when putting lines. */ - if (VIsual_mode == 'V') + // When deleted a linewise Visual area, put the register as + // lines to avoid it joined with the next line. When deletion was + // charwise, split a line when putting lines. + if (VIsual_mode == 'V') { flags |= PUT_LINE; - else if (VIsual_mode == 'v') + } else if (VIsual_mode == 'v') { flags |= PUT_LINE_SPLIT; - if (VIsual_mode == Ctrl_V && dir == FORWARD) + } + if (VIsual_mode == Ctrl_V && dir == FORWARD) { flags |= PUT_LINE_FORWARD; + } dir = BACKWARD; if ((VIsual_mode != 'V' && curwin->w_cursor.col < curbuf->b_op_start.col) diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 2301b2159f..294c65ca03 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1644,8 +1644,6 @@ int op_delete(oparg_T *oap) curwin->w_cursor.col = 0; (void)del_bytes((colnr_T)n, !virtual_op, oap->op_type == OP_DELETE && !oap->is_VIsual); - extmark_col_adjust(curbuf, curwin->w_cursor.lnum, - (colnr_T)0, 0L, (long)-n, kExtmarkUndo); curwin->w_cursor = curpos; // restore curwin->w_cursor (void)do_join(2, false, false, false, false); } @@ -1685,7 +1683,6 @@ setmarks: if (oap->is_VIsual == false) { endcol = MAX(endcol - 1, mincol); } - extmark_col_adjust_delete(curbuf, lnum, mincol, endcol, kExtmarkUndo, 0); } return OK; } @@ -2279,7 +2276,7 @@ void op_insert(oparg_T *oap, long count1) colnr_T col = oap->start.col; for (linenr_T lnum = oap->start.lnum; lnum <= oap->end.lnum; lnum++) { extmark_col_adjust(curbuf, lnum, col, 0, 1, kExtmarkUndo); - } + } } /* @@ -4279,14 +4276,14 @@ format_lines( if (next_leader_len > 0) { (void)del_bytes(next_leader_len, false, false); mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L, - (long)-next_leader_len, 0, kExtmarkUndo); + (long)-next_leader_len, 0, kExtmarkNOOP); } else if (second_indent > 0) { // the "leader" for FO_Q_SECOND int indent = (int)getwhitecols_curline(); if (indent > 0) { - (void)del_bytes(indent, FALSE, FALSE); + (void)del_bytes(indent, false, false); mark_col_adjust(curwin->w_cursor.lnum, - (colnr_T)0, 0L, (long)-indent, 0, kExtmarkUndo); + (colnr_T)0, 0L, (long)-indent, 0, kExtmarkNOOP); } } curwin->w_cursor.lnum--; @@ -4951,23 +4948,6 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) curbuf->b_op_end.col--; } - // if buf1 wasn't allocated, only a singe ASCII char was changed in-place. - if (did_change && buf1 != NULL) { - extmark_col_adjust_delete(curbuf, - pos->lnum, - startpos.col + 2, - endpos.col + 1 + length, - kExtmarkUndo, - 0); - long col_amount = (long)STRLEN(buf1); - extmark_col_adjust(curbuf, - pos->lnum, - startpos.col + 1, - 0, - col_amount, - kExtmarkUndo); - } - theend: xfree(buf1); if (visual) { @@ -5224,8 +5204,7 @@ void write_reg_contents_lst(int name, char_u **strings, /// write_reg_contents_ex - store `str` in register `name` /// -/// If `str` ends in '\n' or '\r', use linewise, otherwise use -/// characterwise. +/// If `str` ends in '\n' or '\r', use linewise, otherwise use charwise. /// /// @warning when `name` is '/', `len` and `must_append` are ignored. This /// means that `str` MUST be NUL-terminated. diff --git a/src/nvim/option.c b/src/nvim/option.c index 20351d3908..68ab310329 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2205,10 +2205,10 @@ static void didset_options2(void) (void)opt_strings_flags(p_cb, p_cb_values, &cb_flags, true); // Parse default for 'fillchars'. - (void)set_chars_option(curwin, &curwin->w_p_fcs); + (void)set_chars_option(curwin, &curwin->w_p_fcs, true); // Parse default for 'listchars'. - (void)set_chars_option(curwin, &curwin->w_p_lcs); + (void)set_chars_option(curwin, &curwin->w_p_lcs, true); // Parse default for 'wildmode'. check_opt_wim(); @@ -2663,11 +2663,11 @@ did_set_string_option( errmsg = e_invarg; } else { FOR_ALL_TAB_WINDOWS(tp, wp) { - if (set_chars_option(wp, &wp->w_p_lcs) != NULL) { + if (set_chars_option(wp, &wp->w_p_lcs, true) != NULL) { errmsg = (char_u *)_("E834: Conflicts with value of 'listchars'"); goto ambw_end; } - if (set_chars_option(wp, &wp->w_p_fcs) != NULL) { + if (set_chars_option(wp, &wp->w_p_fcs, true) != NULL) { errmsg = (char_u *)_("E835: Conflicts with value of 'fillchars'"); goto ambw_end; } @@ -2868,10 +2868,26 @@ ambw_end: } s = skip_to_option_part(s); } - } else if (varp == &curwin->w_p_lcs) { // 'listchars' - errmsg = set_chars_option(curwin, varp); - } else if (varp == &curwin->w_p_fcs) { // 'fillchars' - errmsg = set_chars_option(curwin, varp); + } else if (varp == &p_lcs) { // 'listchars' + errmsg = set_chars_option(curwin, varp, false); + if (!errmsg) { + FOR_ALL_TAB_WINDOWS(tp, wp) { + set_chars_option(wp, &wp->w_p_lcs, true); + } + } + redraw_all_later(NOT_VALID); + } else if (varp == &curwin->w_p_lcs) { // local 'listchars' + errmsg = set_chars_option(curwin, varp, true); + } else if (varp == &p_fcs) { // 'fillchars' + errmsg = set_chars_option(curwin, varp, false); + if (!errmsg) { + FOR_ALL_TAB_WINDOWS(tp, wp) { + set_chars_option(wp, &wp->w_p_fcs, true); + } + } + redraw_all_later(NOT_VALID); + } else if (varp == &curwin->w_p_fcs) { // local 'fillchars' + errmsg = set_chars_option(curwin, varp, true); } else if (varp == &p_cedit) { // 'cedit' errmsg = check_cedit(); } else if (varp == &p_vfile) { // 'verbosefile' @@ -3501,7 +3517,7 @@ skip: /// /// @param varp either &curwin->w_p_lcs or &curwin->w_p_fcs /// @return error message, NULL if it's OK. -static char_u *set_chars_option(win_T *wp, char_u **varp) +static char_u *set_chars_option(win_T *wp, char_u **varp, bool set) { int round, i, len, entries; char_u *p, *s; @@ -3536,12 +3552,18 @@ static char_u *set_chars_option(win_T *wp, char_u **varp) { &wp->w_p_lcs_chars.conceal, "conceal", NUL }, }; - if (varp == &wp->w_p_lcs) { + if (varp == &p_lcs || varp == &wp->w_p_lcs) { tab = lcs_tab; entries = ARRAY_SIZE(lcs_tab); + if (varp == &wp->w_p_lcs && wp->w_p_lcs[0] == NUL) { + varp = &p_lcs; + } } else { tab = fcs_tab; entries = ARRAY_SIZE(fcs_tab); + if (varp == &wp->w_p_fcs && wp->w_p_fcs[0] == NUL) { + varp = &p_fcs; + } if (*p_ambw == 'd') { // XXX: If ambiwidth=double then "|" and "·" take 2 columns, which is // forbidden (TUI limitation?). Set old defaults. @@ -3554,7 +3576,7 @@ static char_u *set_chars_option(win_T *wp, char_u **varp) } // first round: check for valid value, second round: assign values - for (round = 0; round <= 1; round++) { + for (round = 0; round <= set ? 1 : 0; round++) { if (round > 0) { // After checking that the value is valid: set defaults for (i = 0; i < entries; i++) { @@ -3562,7 +3584,7 @@ static char_u *set_chars_option(win_T *wp, char_u **varp) *(tab[i].cp) = tab[i].def; } } - if (varp == &wp->w_p_lcs) { + if (varp == &p_lcs || varp == &wp->w_p_lcs) { wp->w_p_lcs_chars.tab1 = NUL; wp->w_p_lcs_chars.tab3 = NUL; } @@ -5173,6 +5195,13 @@ void ui_refresh_options(void) } ui_call_option_set(name, value); } + if (p_mouse != NULL) { + if (*p_mouse == NUL) { + ui_call_mouse_off(); + } else { + setmouse(); + } + } } /* @@ -5562,6 +5591,16 @@ void unset_global_local_option(char *name, void *from) case PV_MENC: clear_string_option(&buf->b_p_menc); break; + case PV_LCS: + clear_string_option(&((win_T *)from)->w_p_lcs); + set_chars_option((win_T *)from, &((win_T *)from)->w_p_lcs, true); + redraw_win_later((win_T *)from, NOT_VALID); + break; + case PV_FCS: + clear_string_option(&((win_T *)from)->w_p_fcs); + set_chars_option((win_T *)from, &((win_T *)from)->w_p_fcs, true); + redraw_win_later((win_T *)from, NOT_VALID); + break; } } @@ -5598,6 +5637,8 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags) case PV_LW: return (char_u *)&(curbuf->b_p_lw); case PV_BKC: return (char_u *)&(curbuf->b_p_bkc); case PV_MENC: return (char_u *)&(curbuf->b_p_menc); + case PV_FCS: return (char_u *)&(curwin->w_p_fcs); + case PV_LCS: return (char_u *)&(curwin->w_p_lcs); } return NULL; // "cannot happen" } @@ -5656,6 +5697,10 @@ static char_u *get_varp(vimoption_T *p) ? (char_u *)&(curbuf->b_p_lw) : p->var; case PV_MENC: return *curbuf->b_p_menc != NUL ? (char_u *)&(curbuf->b_p_menc) : p->var; + case PV_FCS: return *curwin->w_p_fcs != NUL + ? (char_u *)&(curwin->w_p_fcs) : p->var; + case PV_LCS: return *curwin->w_p_lcs != NUL + ? (char_u *)&(curwin->w_p_lcs) : p->var; case PV_ARAB: return (char_u *)&(curwin->w_p_arab); case PV_LIST: return (char_u *)&(curwin->w_p_list); @@ -5753,8 +5798,6 @@ static char_u *get_varp(vimoption_T *p) case PV_KMAP: return (char_u *)&(curbuf->b_p_keymap); case PV_SCL: return (char_u *)&(curwin->w_p_scl); case PV_WINHL: return (char_u *)&(curwin->w_p_winhl); - case PV_FCS: return (char_u *)&(curwin->w_p_fcs); - case PV_LCS: return (char_u *)&(curwin->w_p_lcs); case PV_WINBL: return (char_u *)&(curwin->w_p_winbl); default: IEMSG(_("E356: get_varp ERROR")); } @@ -5896,8 +5939,8 @@ void didset_window_options(win_T *wp) { check_colorcolumn(wp); briopt_check(wp); - set_chars_option(wp, &wp->w_p_fcs); - set_chars_option(wp, &wp->w_p_lcs); + set_chars_option(wp, &wp->w_p_fcs, true); + set_chars_option(wp, &wp->w_p_lcs, true); parse_winhl_opt(wp); // sets w_hl_needs_update also for w_p_winbl wp->w_grid.blending = wp->w_p_winbl > 0; } diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index e5a3c0bd95..c6e3f71016 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -484,6 +484,7 @@ EXTERN long p_linespace; // 'linespace' EXTERN char_u *p_lispwords; // 'lispwords' EXTERN long p_ls; // 'laststatus' EXTERN long p_stal; // 'showtabline' +EXTERN char_u *p_lcs; // 'listchars' EXTERN int p_lz; // 'lazyredraw' EXTERN int p_lpl; // 'loadplugins' @@ -611,13 +612,14 @@ EXTERN char_u *p_swb; // 'switchbuf' EXTERN unsigned swb_flags; #ifdef IN_OPTION_C static char *(p_swb_values[]) = - { "useopen", "usetab", "split", "newtab", "vsplit", NULL }; + { "useopen", "usetab", "split", "newtab", "vsplit", "uselast", NULL }; #endif #define SWB_USEOPEN 0x001 #define SWB_USETAB 0x002 #define SWB_SPLIT 0x004 #define SWB_NEWTAB 0x008 #define SWB_VSPLIT 0x010 +#define SWB_USELAST 0x020 EXTERN int p_tbs; ///< 'tagbsearch' EXTERN char_u *p_tc; ///< 'tagcase' EXTERN unsigned tc_flags; ///< flags from 'tagcase' @@ -652,6 +654,7 @@ EXTERN long p_ul; ///< 'undolevels' EXTERN long p_ur; ///< 'undoreload' EXTERN long p_uc; ///< 'updatecount' EXTERN long p_ut; ///< 'updatetime' +EXTERN char_u *p_fcs; ///< 'fillchar' EXTERN char_u *p_shada; ///< 'shada' EXTERN char *p_shadafile; ///< 'shadafile' EXTERN char_u *p_vdir; ///< 'viewdir' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 903b7e982a..1e7105219f 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -804,11 +804,12 @@ return { }, { full_name='fillchars', abbreviation='fcs', - type='string', list='onecomma', scope={'window'}, + type='string', list='onecomma', scope={'global', 'window'}, deny_duplicates=true, vi_def=true, alloced=true, redraw={'current_window'}, + varname='p_fcs', defaults={if_true={vi=''}} }, { @@ -1420,11 +1421,12 @@ return { }, { full_name='listchars', abbreviation='lcs', - type='string', list='onecomma', scope={'window'}, + type='string', list='onecomma', scope={'global', 'window'}, deny_duplicates=true, vim=true, alloced=true, redraw={'current_window'}, + varname='p_lcs', defaults={if_true={vi="eol:$", vim="tab:> ,trail:-,nbsp:+"}} }, { diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index ded575529f..0f44df2188 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -412,7 +412,7 @@ int mch_expand_wildcards(int num_pat, char_u **pat, int *num_file, fseek(fd, 0L, SEEK_SET); buffer = xmalloc(len + 1); // fread() doesn't terminate buffer with NUL; - // appropiate termination (not always NUL) is done below. + // appropriate termination (not always NUL) is done below. size_t readlen = fread((char *)buffer, 1, len, fd); fclose(fd); os_remove((char *)tempname); diff --git a/src/nvim/po/af.po b/src/nvim/po/af.po index 61ad72d7e0..79048eac39 100644 --- a/src/nvim/po/af.po +++ b/src/nvim/po/af.po @@ -100,7 +100,7 @@ msgid "E88: Cannot go before first buffer" msgstr "E88: Kan nie vr eerste buffer gaan nie" #, fuzzy, c-format -#~ msgid "E89: %s will be killed(add ! to override)" +#~ msgid "E89: %s will be killed (add ! to override)" #~ msgstr "E189: \"%s\" bestaan (gebruik ! om te dwing)" #, c-format diff --git a/src/nvim/po/fi.po b/src/nvim/po/fi.po index 4612988c95..f568a34b3c 100644 --- a/src/nvim/po/fi.po +++ b/src/nvim/po/fi.po @@ -323,7 +323,7 @@ msgid "E88: Cannot go before first buffer" msgstr "E88: Ensimmäisen puskurin ohi ei voi edetä" #, fuzzy, c-format -#~ msgid "E89: %s will be killed(add ! to override)" +#~ msgid "E89: %s will be killed (add ! to override)" #~ msgstr "E189: %s on jo olemassa (lisää komentoon ! ohittaaksesi)" #, fuzzy, c-format diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po index 211d38e53a..19ea8e897a 100644 --- a/src/nvim/po/uk.po +++ b/src/nvim/po/uk.po @@ -87,8 +87,8 @@ msgid "E88: Cannot go before first buffer" msgstr "E88: Це вже найперший буфер" #, c-format -msgid "E89: %s will be killed(add ! to override)" -msgstr "E89: «%s» буде вбито(! щоб не зважати)" +msgid "E89: %s will be killed (add ! to override)" +msgstr "E89: «%s» буде вбито (! щоб не зважати)" #, c-format msgid "" diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index ed57b28029..5d30ca624f 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -1,9 +1,7 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * quickfix.c: functions for quickfix mode, using a file with error messages - */ +// quickfix.c: functions for quickfix mode, using a file with error messages #include <assert.h> #include <inttypes.h> @@ -53,9 +51,7 @@ struct dir_stack_T { char_u *dirname; }; -/* - * For each error the next struct is allocated and linked in a list. - */ +// For each error the next struct is allocated and linked in a list. typedef struct qfline_S qfline_T; struct qfline_S { qfline_T *qf_next; ///< pointer to next error in the list @@ -74,9 +70,7 @@ struct qfline_S { char_u qf_valid; ///< valid error message detected }; -/* - * There is a stack of error lists. - */ +// There is a stack of error lists. #define LISTCOUNT 10 #define INVALID_QFIDX (-1) @@ -120,15 +114,13 @@ typedef struct qf_list_S { /// Quickfix/Location list stack definition /// Contains a list of quickfix/location lists (qf_list_T) struct qf_info_S { - /* - * Count of references to this list. Used only for location lists. - * When a location list window reference this list, qf_refcount - * will be 2. Otherwise, qf_refcount will be 1. When qf_refcount - * reaches 0, the list is freed. - */ + // Count of references to this list. Used only for location lists. + // When a location list window reference this list, qf_refcount + // will be 2. Otherwise, qf_refcount will be 1. When qf_refcount + // reaches 0, the list is freed. int qf_refcount; - int qf_listcount; /* current number of lists */ - int qf_curlist; /* current error list */ + int qf_listcount; // current number of lists + int qf_curlist; // current error list qf_list_T qf_lists[LISTCOUNT]; qfltype_T qfl_type; // type of list }; @@ -138,31 +130,29 @@ static unsigned last_qf_id = 0; // Last Used quickfix list id #define FMT_PATTERNS 11 // maximum number of % recognized -/* - * Structure used to hold the info of one part of 'errorformat' - */ +// Structure used to hold the info of one part of 'errorformat' typedef struct efm_S efm_T; struct efm_S { - regprog_T *prog; /* pre-formatted part of 'errorformat' */ - efm_T *next; /* pointer to next (NULL if last) */ - char_u addr[FMT_PATTERNS]; /* indices of used % patterns */ - char_u prefix; /* prefix of this format line: */ - /* 'D' enter directory */ - /* 'X' leave directory */ - /* 'A' start of multi-line message */ - /* 'E' error message */ - /* 'W' warning message */ - /* 'I' informational message */ - /* 'C' continuation line */ - /* 'Z' end of multi-line message */ - /* 'G' general, unspecific message */ - /* 'P' push file (partial) message */ - /* 'Q' pop/quit file (partial) message */ - /* 'O' overread (partial) message */ - char_u flags; /* additional flags given in prefix */ - /* '-' do not include this line */ - /* '+' include whole line in message */ - int conthere; /* %> used */ + regprog_T *prog; // pre-formatted part of 'errorformat' + efm_T *next; // pointer to next (NULL if last) + char_u addr[FMT_PATTERNS]; // indices of used % patterns + char_u prefix; // prefix of this format line: + // 'D' enter directory + // 'X' leave directory + // 'A' start of multi-line message + // 'E' error message + // 'W' warning message + // 'I' informational message + // 'C' continuation line + // 'Z' end of multi-line message + // 'G' general, unspecific message + // 'P' push file (partial) message + // 'Q' pop/quit file (partial) message + // 'O' overread (partial) message + char_u flags; // additional flags given in prefix + // '-' do not include this line + // '+' include whole line in message + int conthere; // %> used }; /// List of location lists to be deleted. @@ -221,7 +211,7 @@ static char_u *e_no_more_items = (char_u *)N_("E553: No more items"); // Quickfix window check helper macro #define IS_QF_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref == NULL) -/* Location list window check helper macro */ +// Location list window check helper macro #define IS_LL_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL) // Quickfix and location list stack check helper macros @@ -1156,16 +1146,12 @@ qf_init_ext( goto error2; } - /* - * got_int is reset here, because it was probably set when killing the - * ":make" command, but we still want to read the errorfile then. - */ - got_int = FALSE; + // got_int is reset here, because it was probably set when killing the + // ":make" command, but we still want to read the errorfile then. + got_int = false; - /* - * Read the lines in the error file one by one. - * Try to recognize one of the error formats in each line. - */ + // Read the lines in the error file one by one. + // Try to recognize one of the error formats in each line. while (!got_int) { status = qf_init_process_nextline(qfl, fmt_first, &state, &fields); if (status == QF_END_OF_INPUT) { // end of input @@ -1263,10 +1249,8 @@ static void qf_new_list(qf_info_T *qi, const char_u *qf_title) qf_free(&qi->qf_lists[--qi->qf_listcount]); } - /* - * When the stack is full, remove to oldest entry - * Otherwise, add a new entry. - */ + // When the stack is full, remove to oldest entry + // Otherwise, add a new entry. if (qi->qf_listcount == LISTCOUNT) { qf_free(&qi->qf_lists[0]); for (i = 1; i < LISTCOUNT; i++) { @@ -1714,7 +1698,7 @@ static void ll_free_all(qf_info_T **pqi) qi = *pqi; if (qi == NULL) return; - *pqi = NULL; /* Remove reference to this list */ + *pqi = NULL; // Remove reference to this list qi->qf_refcount--; if (qi->qf_refcount < 1) { @@ -1739,7 +1723,7 @@ void qf_free_all(win_T *wp) qf_info_T *qi = &ql_info; if (wp != NULL) { - /* location list */ + // location list ll_free_all(&wp->w_llist); ll_free_all(&wp->w_llist_ref); } else { @@ -1892,14 +1876,13 @@ static qf_info_T *qf_alloc_stack(qfltype_T qfltype) static qf_info_T *ll_get_or_alloc_list(win_T *wp) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { - if (IS_LL_WINDOW(wp)) - /* For a location list window, use the referenced location list */ + if (IS_LL_WINDOW(wp)) { + // For a location list window, use the referenced location list return wp->w_llist_ref; + } - /* - * For a non-location list window, w_llist_ref should not point to a - * location list. - */ + // For a non-location list window, w_llist_ref should not point to a + // location list. ll_free_all(&wp->w_llist_ref); if (wp->w_llist == NULL) { @@ -2133,22 +2116,21 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr, { struct dir_stack_T *ds_ptr; - /* allocate new stack element and hook it in */ + // allocate new stack element and hook it in struct dir_stack_T *ds_new = xmalloc(sizeof(struct dir_stack_T)); ds_new->next = *stackptr; *stackptr = ds_new; - /* store directory on the stack */ + // store directory on the stack if (vim_isAbsName(dirbuf) || (*stackptr)->next == NULL - || (*stackptr && is_file_stack)) + || (*stackptr && is_file_stack)) { (*stackptr)->dirname = vim_strsave(dirbuf); - else { - /* Okay we don't have an absolute path. - * dirbuf must be a subdir of one of the directories on the stack. - * Let's search... - */ + } else { + // Okay we don't have an absolute path. + // dirbuf must be a subdir of one of the directories on the stack. + // Let's search... ds_new = (*stackptr)->next; (*stackptr)->dirname = NULL; while (ds_new) { @@ -2161,7 +2143,7 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr, ds_new = ds_new->next; } - /* clean up all dirs we already left */ + // clean up all dirs we already left while ((*stackptr)->next != ds_new) { ds_ptr = (*stackptr)->next; (*stackptr)->next = (*stackptr)->next->next; @@ -2169,7 +2151,7 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr, xfree(ds_ptr); } - /* Nothing found -> it must be on top level */ + // Nothing found -> it must be on top level if (ds_new == NULL) { xfree((*stackptr)->dirname); (*stackptr)->dirname = vim_strsave(dirbuf); @@ -2187,18 +2169,16 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr, } -/* - * pop dirbuf from the directory stack and return previous directory or NULL if - * stack is empty - */ +// pop dirbuf from the directory stack and return previous directory or NULL if +// stack is empty static char_u *qf_pop_dir(struct dir_stack_T **stackptr) { struct dir_stack_T *ds_ptr; - /* TODO: Should we check if dirbuf is the directory on top of the stack? - * What to do if it isn't? */ + // TODO(vim): Should we check if dirbuf is the directory on top of the stack? + // What to do if it isn't? - /* pop top element and free it */ + // pop top element and free it if (*stackptr != NULL) { ds_ptr = *stackptr; *stackptr = (*stackptr)->next; @@ -2206,13 +2186,11 @@ static char_u *qf_pop_dir(struct dir_stack_T **stackptr) xfree(ds_ptr); } - /* return NEW top element as current dir or NULL if stack is empty*/ + // return NEW top element as current dir or NULL if stack is empty return *stackptr ? (*stackptr)->dirname : NULL; } -/* - * clean up directory stack - */ +// clean up directory stack static void qf_clean_dir_stack(struct dir_stack_T **stackptr) { struct dir_stack_T *ds_ptr; @@ -2617,8 +2595,11 @@ static void qf_goto_win_with_qfl_file(int qf_fnum) if (IS_QF_WINDOW(win)) { // Didn't find it, go to the window before the quickfix - // window. - if (altwin != NULL) { + // window, unless 'switchbuf' contains 'uselast': in this case we + // try to jump to the previously used window first. + if ((swb_flags & SWB_USELAST) && win_valid(prevwin)) { + win = prevwin; + } else if (altwin != NULL) { win = altwin; } else if (curwin->w_prev != NULL) { win = curwin->w_prev; @@ -2887,10 +2868,8 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) } } - /* - * If there is a file name, - * read the wanted file if needed, and check autowrite etc. - */ + // If there is a file name, + // read the wanted file if needed, and check autowrite etc. old_curbuf = curbuf; old_lnum = curwin->w_cursor.lnum; @@ -2938,8 +2917,8 @@ theend: qfl->qf_index = qf_index; } if (p_swb != old_swb && opened_window) { - /* Restore old 'switchbuf' value, but not when an autocommand or - * modeline has changed the value. */ + // Restore old 'switchbuf' value, but not when an autocommand or + // modeline has changed the value. if (p_swb == empty_option) { p_swb = old_swb; swb_flags = old_swb_flags; @@ -3038,10 +3017,8 @@ static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel) ui_flush(); // show one line at a time } -/* - * ":clist": list all errors - * ":llist": list all locations - */ +// ":clist": list all errors +// ":llist": list all locations void qf_list(exarg_T *eap) { qf_list_T *qfl; @@ -3116,10 +3093,8 @@ void qf_list(exarg_T *eap) } } -/* - * Remove newlines and leading whitespace from an error message. - * Put the result in "buf[bufsize]". - */ +// Remove newlines and leading whitespace from an error message. +// Put the result in "buf[bufsize]". static void qf_fmt_text(const char_u *restrict text, char_u *restrict buf, int bufsize) FUNC_ATTR_NONNULL_ALL @@ -3277,9 +3252,7 @@ static void qf_free(qf_list_T *qfl) qfl->qf_changedtick = 0L; } -/* - * qf_mark_adjust: adjust marks - */ +// qf_mark_adjust: adjust marks bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, long amount_after) { @@ -3321,21 +3294,19 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, return found_one; } -/* - * Make a nice message out of the error character and the error number: - * char number message - * e or E 0 " error" - * w or W 0 " warning" - * i or I 0 " info" - * 0 0 "" - * other 0 " c" - * e or E n " error n" - * w or W n " warning n" - * i or I n " info n" - * 0 n " error n" - * other n " c n" - * 1 x "" :helpgrep - */ +// Make a nice message out of the error character and the error number: +// char number message +// e or E 0 " error" +// w or W 0 " warning" +// i or I 0 " info" +// 0 0 "" +// other 0 " c" +// e or E n " error n" +// w or W n " warning n" +// i or I n " info n" +// 0 n " error n" +// other n " c n" +// 1 x "" :helpgrep static char_u *qf_types(int c, int nr) { static char_u buf[20]; @@ -3396,12 +3367,10 @@ void qf_view_result(bool split) do_cmdline_cmd((IS_LL_WINDOW(curwin) ? ".ll" : ".cc")); } -/* - * ":cwindow": open the quickfix window if we have errors to display, - * close it if not. - * ":lwindow": open the location list window if we have locations to display, - * close it if not. - */ +// ":cwindow": open the quickfix window if we have errors to display, +// close it if not. +// ":lwindow": open the location list window if we have locations to display, +// close it if not. void ex_cwindow(exarg_T *eap) { qf_info_T *qi; @@ -3414,14 +3383,12 @@ void ex_cwindow(exarg_T *eap) qfl = qf_get_curlist(qi); - /* Look for an existing quickfix window. */ + // Look for an existing quickfix window. win = qf_find_win(qi); - /* - * If a quickfix window is open but we have no errors to display, - * close the window. If a quickfix window is not open, then open - * it if we have errors; otherwise, leave it closed. - */ + // If a quickfix window is open but we have no errors to display, + // close the window. If a quickfix window is not open, then open + // it if we have errors; otherwise, leave it closed. if (qf_stack_empty(qi) || qfl->qf_nonevalid || qf_list_empty(qf_get_curlist(qi))) { @@ -3433,10 +3400,8 @@ void ex_cwindow(exarg_T *eap) } } -/* - * ":cclose": close the window showing the list of errors. - * ":lclose": close the window showing the location list - */ +// ":cclose": close the window showing the list of errors. +// ":lclose": close the window showing the location list void ex_cclose(exarg_T *eap) { win_T *win = NULL; @@ -3446,7 +3411,7 @@ void ex_cclose(exarg_T *eap) return; } - /* Find existing quickfix window and close it. */ + // Find existing quickfix window and close it. win = qf_find_win(qi); if (win != NULL) { win_close(win, false); @@ -3642,38 +3607,32 @@ void ex_cbottom(exarg_T *eap) } } -/* - * Return the number of the current entry (line number in the quickfix - * window). - */ +// Return the number of the current entry (line number in the quickfix +// window). linenr_T qf_current_entry(win_T *wp) { qf_info_T *qi = &ql_info; - if (IS_LL_WINDOW(wp)) - /* In the location list window, use the referenced location list */ + if (IS_LL_WINDOW(wp)) { + // In the location list window, use the referenced location list qi = wp->w_llist_ref; + } return qf_get_curlist(qi)->qf_index; } -/* - * Update the cursor position in the quickfix window to the current error. - * Return TRUE if there is a quickfix window. - */ -static int -qf_win_pos_update ( +// Update the cursor position in the quickfix window to the current error. +// Return TRUE if there is a quickfix window. +static int qf_win_pos_update( qf_info_T *qi, - int old_qf_index /* previous qf_index or zero */ + int old_qf_index // previous qf_index or zero ) { win_T *win; int qf_index = qf_get_curlist(qi)->qf_index; - /* - * Put the cursor on the current error in the quickfix window, so that - * it's viewable. - */ + // Put the cursor on the current error in the quickfix window, so that + // it's viewable. win = qf_find_win(qi); if (win != NULL && qf_index <= win->w_buffer->b_ml.ml_line_count @@ -3725,10 +3684,8 @@ static win_T *qf_find_win(const qf_info_T *qi) return NULL; } -/* - * Find a quickfix buffer. - * Searches in windows opened in all the tabs. - */ +// Find a quickfix buffer. +// Searches in windows opened in all the tabs. static buf_T *qf_find_buf(const qf_info_T *qi) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -3754,16 +3711,14 @@ static void qf_update_win_titlevar(qf_info_T *qi) } } -/* - * Find the quickfix buffer. If it exists, update the contents. - */ +// Find the quickfix buffer. If it exists, update the contents. static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last) { buf_T *buf; win_T *win; aco_save_T aco; - /* Check if a buffer for the quickfix list exists. Update it. */ + // Check if a buffer for the quickfix list exists. Update it. buf = qf_find_buf(qi); if (buf != NULL) { linenr_T old_line_count = buf->b_ml.ml_line_count; @@ -3936,7 +3891,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last) redraw_curbuf_later(NOT_VALID); } - /* Restore KeyTyped, setting 'filetype' may reset it. */ + // Restore KeyTyped, setting 'filetype' may reset it. KeyTyped = old_KeyTyped; } @@ -3990,9 +3945,7 @@ static void qf_jump_first(qf_info_T *qi, unsigned save_qfid, int forceit) } } -/* - * Return TRUE when using ":vimgrep" for ":grep". - */ +// Return TRUE when using ":vimgrep" for ":grep". int grep_internal(cmdidx_T cmdidx) { return (cmdidx == CMD_grep @@ -4055,9 +4008,7 @@ static char *make_get_fullcmd(const char_u *makecmd, const char_u *fname) return cmd; } -/* - * Used for ":make", ":lmake", ":grep", ":lgrep", ":grepadd", and ":lgrepadd" - */ +// Used for ":make", ":lmake", ":grep", ":lgrep", ":grepadd", and ":lgrepadd" void ex_make(exarg_T *eap) { char_u *fname; @@ -4128,11 +4079,9 @@ cleanup: xfree(cmd); } -/* - * Return the name for the errorfile, in allocated memory. - * Find a new unique name when 'makeef' contains "##". - * Returns NULL for error. - */ +// Return the name for the errorfile, in allocated memory. +// Find a new unique name when 'makeef' contains "##". +// Returns NULL for error. static char_u *get_mef_name(void) { char_u *p; @@ -4154,7 +4103,7 @@ static char_u *get_mef_name(void) if (*p == NUL) return vim_strsave(p_mef); - /* Keep trying until the name doesn't exist yet. */ + // Keep trying until the name doesn't exist yet. for (;; ) { if (start == -1) { start = (int)os_get_pid(); @@ -4703,10 +4652,8 @@ static char_u * cfile_get_auname(cmdidx_T cmdidx) } -/* - * ":cfile"/":cgetfile"/":caddfile" commands. - * ":lfile"/":lgetfile"/":laddfile" commands. - */ +// ":cfile"/":cgetfile"/":caddfile" commands. +// ":lfile"/":lgetfile"/":laddfile" commands. void ex_cfile(exarg_T *eap) { win_T *wp = NULL; @@ -4947,12 +4894,10 @@ static void vgr_jump_to_match(qf_info_T *qi, int forceit, int *redraw_for_dummy, } } -/* - * ":vimgrep {pattern} file(s)" - * ":vimgrepadd {pattern} file(s)" - * ":lvimgrep {pattern} file(s)" - * ":lvimgrepadd {pattern} file(s)" - */ +// ":vimgrep {pattern} file(s)" +// ":vimgrepadd {pattern} file(s)" +// ":lvimgrep {pattern} file(s)" +// ":lvimgrepadd {pattern} file(s)" void ex_vimgrep(exarg_T *eap) { regmmatch_T regmatch; @@ -4994,7 +4939,7 @@ void ex_vimgrep(exarg_T *eap) else tomatch = MAXLNUM; - /* Get the search pattern: either white-separated or enclosed in // */ + // Get the search pattern: either white-separated or enclosed in // regmatch.regprog = NULL; char_u *title = vim_strsave(qf_cmdtitle(*eap->cmdlinep)); p = skip_vimgrep_pat(eap->arg, &s, &flags); @@ -5021,9 +4966,10 @@ void ex_vimgrep(exarg_T *eap) qf_new_list(qi, title); } - /* parse the list of arguments */ - if (get_arglist_exp(p, &fcount, &fnames, true) == FAIL) + // parse the list of arguments + if (get_arglist_exp(p, &fcount, &fnames, true) == FAIL) { goto theend; + } if (fcount == 0) { EMSG(_(e_nomatch)); goto theend; @@ -5032,8 +4978,8 @@ void ex_vimgrep(exarg_T *eap) dirname_start = xmalloc(MAXPATHL); dirname_now = xmalloc(MAXPATHL); - /* Remember the current directory, because a BufRead autocommand that does - * ":lcd %:p:h" changes the meaning of short path names. */ + // Remember the current directory, because a BufRead autocommand that does + // ":lcd %:p:h" changes the meaning of short path names. os_dirname(dirname_start, MAXPATHL); incr_quickfix_busy(); @@ -5046,15 +4992,15 @@ void ex_vimgrep(exarg_T *eap) for (fi = 0; fi < fcount && !got_int && tomatch > 0; fi++) { fname = path_try_shorten_fname(fnames[fi]); if (time(NULL) > seconds) { - /* Display the file name every second or so, show the user we are - * working on it. */ + // Display the file name every second or so, show the user we are + // working on it. seconds = time(NULL); vgr_display_fname(fname); } buf = buflist_findname_exp(fnames[fi]); if (buf == NULL || buf->b_ml.ml_mfp == NULL) { - /* Remember that a buffer with this name already exists. */ + // Remember that a buffer with this name already exists. duplicate_name = (buf != NULL); using_dummy = TRUE; redraw_for_dummy = TRUE; @@ -5087,20 +5033,20 @@ void ex_vimgrep(exarg_T *eap) if (found_match && first_match_buf == NULL) first_match_buf = buf; if (duplicate_name) { - /* Never keep a dummy buffer if there is another buffer - * with the same name. */ + // Never keep a dummy buffer if there is another buffer + // with the same name. wipe_dummy_buffer(buf, dirname_start); buf = NULL; } else if (!cmdmod.hide - || buf->b_p_bh[0] == 'u' /* "unload" */ - || buf->b_p_bh[0] == 'w' /* "wipe" */ - || buf->b_p_bh[0] == 'd') { /* "delete" */ - /* When no match was found we don't need to remember the - * buffer, wipe it out. If there was a match and it - * wasn't the first one or we won't jump there: only - * unload the buffer. - * Ignore 'hidden' here, because it may lead to having too - * many swap files. */ + || buf->b_p_bh[0] == 'u' // "unload" + || buf->b_p_bh[0] == 'w' // "wipe" + || buf->b_p_bh[0] == 'd') { // "delete" + // When no match was found we don't need to remember the + // buffer, wipe it out. If there was a match and it + // wasn't the first one or we won't jump there: only + // unload the buffer. + // Ignore 'hidden' here, because it may lead to having too + // many swap files. if (!found_match) { wipe_dummy_buffer(buf, dirname_start); buf = NULL; @@ -5124,10 +5070,10 @@ void ex_vimgrep(exarg_T *eap) target_dir = vim_strsave(dirname_now); } - /* The buffer is still loaded, the Filetype autocommands - * need to be done now, in that buffer. And the modelines - * need to be done (again). But not the window-local - * options! */ + // The buffer is still loaded, the Filetype autocommands + // need to be done now, in that buffer. And the modelines + // need to be done (again). But not the window-local + // options! aucmd_prepbuf(&aco, buf); apply_autocmds(EVENT_FILETYPE, buf->b_p_ft, buf->b_fname, TRUE, buf); @@ -5171,8 +5117,8 @@ void ex_vimgrep(exarg_T *eap) decr_quickfix_busy(); - /* If we loaded a dummy buffer into the current window, the autocommands - * may have messed up things, need to redraw and recompute folds. */ + // If we loaded a dummy buffer into the current window, the autocommands + // may have messed up things, need to redraw and recompute folds. if (redraw_for_dummy) { foldUpdateAll(curwin); } @@ -5185,18 +5131,16 @@ theend: vim_regfree(regmatch.regprog); } -/* - * Restore current working directory to "dirname_start" if they differ, taking - * into account whether it is set locally or globally. - */ +// Restore current working directory to "dirname_start" if they differ, taking +// into account whether it is set locally or globally. static void restore_start_dir(char_u *dirname_start) { char_u *dirname_now = xmalloc(MAXPATHL); os_dirname(dirname_now, MAXPATHL); if (STRCMP(dirname_start, dirname_now) != 0) { - /* If the directory has changed, change it back by building up an - * appropriate ex command and executing it. */ + // If the directory has changed, change it back by building up an + // appropriate ex command and executing it. exarg_T ea = { .arg = dirname_start, .cmdidx = (curwin->w_localdir == NULL) ? CMD_cd : CMD_lcd, @@ -5206,23 +5150,21 @@ static void restore_start_dir(char_u *dirname_start) xfree(dirname_now); } -/* - * Load file "fname" into a dummy buffer and return the buffer pointer, - * placing the directory resulting from the buffer load into the - * "resulting_dir" pointer. "resulting_dir" must be allocated by the caller - * prior to calling this function. Restores directory to "dirname_start" prior - * to returning, if autocmds or the 'autochdir' option have changed it. - * - * If creating the dummy buffer does not fail, must call unload_dummy_buffer() - * or wipe_dummy_buffer() later! - * - * Returns NULL if it fails. - */ +// Load file "fname" into a dummy buffer and return the buffer pointer, +// placing the directory resulting from the buffer load into the +// "resulting_dir" pointer. "resulting_dir" must be allocated by the caller +// prior to calling this function. Restores directory to "dirname_start" prior +// to returning, if autocmds or the 'autochdir' option have changed it. +// +// If creating the dummy buffer does not fail, must call unload_dummy_buffer() +// or wipe_dummy_buffer() later! +// +// Returns NULL if it fails. static buf_T * load_dummy_buffer ( char_u *fname, - char_u *dirname_start, /* in: old directory */ - char_u *resulting_dir /* out: new directory */ + char_u *dirname_start, // in: old directory + char_u *resulting_dir // out: new directory ) { buf_T *newbuf; @@ -5239,24 +5181,24 @@ load_dummy_buffer ( } set_bufref(&newbufref, newbuf); - /* Init the options. */ + // Init the options. buf_copy_options(newbuf, BCO_ENTER | BCO_NOHELP); - /* need to open the memfile before putting the buffer in a window */ + // need to open the memfile before putting the buffer in a window if (ml_open(newbuf) == OK) { // Make sure this buffer isn't wiped out by autocommands. newbuf->b_locked++; // set curwin/curbuf to buf and save a few things aucmd_prepbuf(&aco, newbuf); - /* Need to set the filename for autocommands. */ - (void)setfname(curbuf, fname, NULL, FALSE); + // Need to set the filename for autocommands. + (void)setfname(curbuf, fname, NULL, false); - /* Create swap file now to avoid the ATTENTION message. */ - check_need_swap(TRUE); + // Create swap file now to avoid the ATTENTION message. + check_need_swap(true); - /* Remove the "dummy" flag, otherwise autocommands may not - * work. */ + // Remove the "dummy" flag, otherwise autocommands may not + // work. curbuf->b_flags &= ~BF_DUMMY; newbuf_to_wipe.br_buf = NULL; @@ -5289,11 +5231,9 @@ load_dummy_buffer ( newbuf->b_flags |= BF_DUMMY; } - /* - * When autocommands/'autochdir' option changed directory: go back. - * Let the caller know what the resulting dir was first, in case it is - * important. - */ + // When autocommands/'autochdir' option changed directory: go back. + // Let the caller know what the resulting dir was first, in case it is + // important. os_dirname(resulting_dir, MAXPATHL); restore_start_dir(dirname_start); @@ -5307,42 +5247,38 @@ load_dummy_buffer ( return newbuf; } -/* - * Wipe out the dummy buffer that load_dummy_buffer() created. Restores - * directory to "dirname_start" prior to returning, if autocmds or the - * 'autochdir' option have changed it. - */ +// Wipe out the dummy buffer that load_dummy_buffer() created. Restores +// directory to "dirname_start" prior to returning, if autocmds or the +// 'autochdir' option have changed it. static void wipe_dummy_buffer(buf_T *buf, char_u *dirname_start) { - if (curbuf != buf) { /* safety check */ + if (curbuf != buf) { // safety check cleanup_T cs; - /* Reset the error/interrupt/exception state here so that aborting() - * returns FALSE when wiping out the buffer. Otherwise it doesn't - * work when got_int is set. */ + // Reset the error/interrupt/exception state here so that aborting() + // returns FALSE when wiping out the buffer. Otherwise it doesn't + // work when got_int is set. enter_cleanup(&cs); wipe_buffer(buf, FALSE); - /* Restore the error/interrupt/exception state if not discarded by a - * new aborting error, interrupt, or uncaught exception. */ + // Restore the error/interrupt/exception state if not discarded by a + // new aborting error, interrupt, or uncaught exception. leave_cleanup(&cs); - /* When autocommands/'autochdir' option changed directory: go back. */ + // When autocommands/'autochdir' option changed directory: go back. restore_start_dir(dirname_start); } } -/* - * Unload the dummy buffer that load_dummy_buffer() created. Restores - * directory to "dirname_start" prior to returning, if autocmds or the - * 'autochdir' option have changed it. - */ +// Unload the dummy buffer that load_dummy_buffer() created. Restores +// directory to "dirname_start" prior to returning, if autocmds or the +// 'autochdir' option have changed it. static void unload_dummy_buffer(buf_T *buf, char_u *dirname_start) { - if (curbuf != buf) { /* safety check */ - close_buffer(NULL, buf, DOBUF_UNLOAD, FALSE); + if (curbuf != buf) { // safety check + close_buffer(NULL, buf, DOBUF_UNLOAD, false); - /* When autocommands/'autochdir' option changed directory: go back. */ + // When autocommands/'autochdir' option changed directory: go back. restore_start_dir(dirname_start); } } @@ -6297,14 +6233,12 @@ static int cbuffer_process_args(exarg_T *eap, return OK; } -/* - * ":[range]cbuffer [bufnr]" command. - * ":[range]caddbuffer [bufnr]" command. - * ":[range]cgetbuffer [bufnr]" command. - * ":[range]lbuffer [bufnr]" command. - * ":[range]laddbuffer [bufnr]" command. - * ":[range]lgetbuffer [bufnr]" command. - */ +// ":[range]cbuffer [bufnr]" command. +// ":[range]caddbuffer [bufnr]" command. +// ":[range]cgetbuffer [bufnr]" command. +// ":[range]lbuffer [bufnr]" command. +// ":[range]laddbuffer [bufnr]" command. +// ":[range]lgetbuffer [bufnr]" command. void ex_cbuffer(exarg_T *eap) { buf_T *buf = NULL; @@ -6405,8 +6339,8 @@ void ex_cexpr(exarg_T *eap) qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp); - /* Evaluate the expression. When the result is a string or a list we can - * use it to fill the errorlist. */ + // Evaluate the expression. When the result is a string or a list we can + // use it to fill the errorlist. typval_T tv; if (eval0(eap->arg, &tv, NULL, true) != FAIL) { if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL) diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 7b9601a5a6..1d29ae064e 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -124,7 +124,7 @@ // temporary buffer for rendering a single screenline, so it can be -// comparared with previous contents to calulate smallest delta. +// comparared with previous contents to calculate smallest delta. static size_t linebuf_size = 0; static schar_T *linebuf_char = NULL; static sattr_T *linebuf_attr = NULL; @@ -4021,10 +4021,13 @@ win_line ( if (wp->w_buffer->terminal) { // terminal buffers may need to highlight beyond the end of the // logical line - while (col < grid->Columns) { + int n = wp->w_p_rl ? -1 : 1; + while (col >= 0 && col < grid->Columns) { schar_from_ascii(linebuf_char[off], ' '); - linebuf_attr[off++] = term_attrs[vcol++]; - col++; + linebuf_attr[off] = term_attrs[vcol]; + off += n; + vcol += n; + col += n; } } grid_put_linebuf(grid, row, 0, col, grid->Columns, wp->w_p_rl, wp, diff --git a/src/nvim/search.c b/src/nvim/search.c index a298f7333e..5e32715e49 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -3777,30 +3777,31 @@ find_prev_quote( return col_start; } -/* - * Find quote under the cursor, cursor at end. - * Returns TRUE if found, else FALSE. - */ -int -current_quote( +// Find quote under the cursor, cursor at end. +// Returns true if found, else false. +bool current_quote( oparg_T *oap, long count, - int include, /* TRUE == include quote char */ - int quotechar /* Quote character */ + bool include, // true == include quote char + int quotechar // Quote character ) + FUNC_ATTR_NONNULL_ALL { char_u *line = get_cursor_line_ptr(); int col_end; int col_start = curwin->w_cursor.col; bool inclusive = false; - int vis_empty = true; // Visual selection <= 1 char - int vis_bef_curs = false; // Visual starts before cursor - int inside_quotes = false; // Looks like "i'" done before - int selected_quote = false; // Has quote inside selection + bool vis_empty = true; // Visual selection <= 1 char + bool vis_bef_curs = false; // Visual starts before cursor + bool did_exclusive_adj = false; // adjusted pos for 'selection' + bool inside_quotes = false; // Looks like "i'" done before + bool selected_quote = false; // Has quote inside selection int i; - int restore_vis_bef = false; // resotre VIsual on abort + bool restore_vis_bef = false; // resotre VIsual on abort - // Correct cursor when 'selection' is "exclusive". + // When 'selection' is "exclusive" move the cursor to where it would be + // with 'selection' "inclusive", so that the logic is the same for both. + // The cursor then is moved forward after adjusting the area. if (VIsual_active) { // this only works within one line if (VIsual.lnum != curwin->w_cursor.lnum) { @@ -3810,6 +3811,14 @@ current_quote( vis_bef_curs = lt(VIsual, curwin->w_cursor); vis_empty = equalpos(VIsual, curwin->w_cursor); if (*p_sel == 'e') { + if (vis_bef_curs) { + dec_cursor(); + did_exclusive_adj = true; + } else if (!vis_empty) { + dec(&VIsual); + did_exclusive_adj = true; + } + vis_empty = equalpos(VIsual, curwin->w_cursor); if (!vis_bef_curs && !vis_empty) { // VIsual needs to be start of Visual selection. pos_T t = curwin->w_cursor; @@ -3819,8 +3828,6 @@ current_quote( vis_bef_curs = true; restore_vis_bef = true; } - dec_cursor(); - vis_empty = equalpos(VIsual, curwin->w_cursor); } } @@ -3846,7 +3853,7 @@ current_quote( /* Find out if we have a quote in the selection. */ while (i <= col_end) if (line[i++] == quotechar) { - selected_quote = TRUE; + selected_quote = true; break; } } @@ -3935,8 +3942,8 @@ current_quote( } } - /* When "include" is TRUE, include spaces after closing quote or before - * the starting quote. */ + // When "include" is true, include spaces after closing quote or before + // the starting quote. if (include) { if (ascii_iswhite(line[col_end + 1])) while (ascii_iswhite(line[col_end + 1])) @@ -3982,9 +3989,10 @@ current_quote( inclusive = true; if (VIsual_active) { if (vis_empty || vis_bef_curs) { - /* decrement cursor when 'selection' is not exclusive */ - if (*p_sel != 'e') + // decrement cursor when 'selection' is not exclusive + if (*p_sel != 'e') { dec_cursor(); + } } else { /* Cursor is at start of Visual area. Set the end of the Visual * area when it was just inside quotes or it didn't end at a @@ -4008,11 +4016,13 @@ current_quote( oap->inclusive = inclusive; } - return OK; + return true; abort_search: if (VIsual_active && *p_sel == 'e') { - inc_cursor(); + if (did_exclusive_adj) { + inc_cursor(); + } if (restore_vis_bef) { pos_T t = curwin->w_cursor; diff --git a/src/nvim/state.c b/src/nvim/state.c index 81bc078a88..b195c1d96b 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -156,7 +156,7 @@ char *get_mode(void) buf[0] = 'n'; if (finish_op) { buf[1] = 'o'; - // to be able to detect force-linewise/blockwise/characterwise operations + // to be able to detect force-linewise/blockwise/charwise operations buf[2] = (char)motion_force; } else if (restart_edit == 'I' || restart_edit == 'R' || restart_edit == 'V') { diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index bdbc09a87a..61ee225eba 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -7316,7 +7316,7 @@ static void set_hl_attr(int idx) sgp->sg_attr = hl_get_syn_attr(idx+1, at_en); - // a cursor style uses this syn_id, make sure its atribute is updated. + // a cursor style uses this syn_id, make sure its attribute is updated. if (cursor_mode_uses_syn_id(idx+1)) { ui_mode_info_set(); } diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 9e8c05fb1e..3629b37c32 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -1194,12 +1194,10 @@ static int find_tagfunc_tags( taglist = rettv.vval.v_list; TV_LIST_ITER_CONST(taglist, li, { - char_u *mfp; char_u *res_name; char_u *res_fname; char_u *res_cmd; char_u *res_kind; - int len; int has_extra = 0; int name_only = flags & TAG_NAMES; @@ -1208,7 +1206,7 @@ static int find_tagfunc_tags( break; } - len = 2; + size_t len = 2; res_name = NULL; res_fname = NULL; res_cmd = NULL; @@ -1254,15 +1252,7 @@ static int find_tagfunc_tags( break; } - if (name_only) { - mfp = vim_strsave(res_name); - } else { - mfp = (char_u *)xmalloc((int)sizeof(char_u) + len + 1); - } - - if (mfp == NULL) { - continue; - } + char_u *const mfp = name_only ? vim_strsave(res_name) : xmalloc(len + 2); if (!name_only) { char_u *p = mfp; @@ -1669,12 +1659,9 @@ find_tags( break; /* End the binary search without a match. */ else search_info.curr_offset = offset; - } - /* - * Skipping back (after a match during binary search). - */ - else if (state == TS_SKIP_BACK) { - search_info.curr_offset -= LSIZE * 2; + } else if (state == TS_SKIP_BACK) { + // Skipping back (after a match during binary search). + search_info.curr_offset -= lbuf_size * 2; if (search_info.curr_offset < 0) { search_info.curr_offset = 0; rewind(fp); @@ -1690,7 +1677,7 @@ find_tags( /* Adjust the search file offset to the correct position */ search_info.curr_offset_used = search_info.curr_offset; vim_fseek(fp, search_info.curr_offset, SEEK_SET); - eof = vim_fgets(lbuf, LSIZE, fp); + eof = vim_fgets(lbuf, lbuf_size, fp); if (!eof && search_info.curr_offset != 0) { /* The explicit cast is to work around a bug in gcc 3.4.2 * (repeated below). */ @@ -1700,12 +1687,12 @@ find_tags( vim_fseek(fp, search_info.low_offset, SEEK_SET); search_info.curr_offset = search_info.low_offset; } - eof = vim_fgets(lbuf, LSIZE, fp); + eof = vim_fgets(lbuf, lbuf_size, fp); } /* skip empty and blank lines */ while (!eof && vim_isblankline(lbuf)) { search_info.curr_offset = vim_ftell(fp); - eof = vim_fgets(lbuf, LSIZE, fp); + eof = vim_fgets(lbuf, lbuf_size, fp); } if (eof) { /* Hit end of file. Skip backwards. */ @@ -1721,10 +1708,9 @@ find_tags( else { /* skip empty and blank lines */ do { - if (use_cscope) - eof = cs_fgets(lbuf, LSIZE); - else - eof = vim_fgets(lbuf, LSIZE, fp); + eof = use_cscope + ? cs_fgets(lbuf, lbuf_size) + : vim_fgets(lbuf, lbuf_size, fp); } while (!eof && vim_isblankline(lbuf)); if (eof) { @@ -1849,19 +1835,14 @@ parse_line: // When the line is too long the NUL will not be in the // last-but-one byte (see vim_fgets()). // Has been reported for Mozilla JS with extremely long names. - // In that case we can't parse it and we ignore the line. - if (lbuf[LSIZE - 2] != NUL && !use_cscope) { - if (p_verbose >= 5) { - verbose_enter(); - MSG(_("Ignoring long line in tags file")); - verbose_leave(); - } - if (state != TS_LINEAR) { - // Avoid getting stuck. - linear = true; - state = TS_LINEAR; - vim_fseek(fp, search_info.low_offset, SEEK_SET); - } + // In that case we need to increase lbuf_size. + if (lbuf[lbuf_size - 2] != NUL && !use_cscope) { + lbuf_size *= 2; + xfree(lbuf); + lbuf = xmalloc(lbuf_size); + // this will try the same thing again, make sure the offset is + // different + search_info.curr_offset = 0; continue; } @@ -2664,6 +2645,9 @@ static int jumpto_tag( str = tagp.command; for (pbuf_end = pbuf; *str && *str != '\n' && *str != '\r'; ) { *pbuf_end++ = *str++; + if (pbuf_end - pbuf + 1 >= LSIZE) { + break; + } } *pbuf_end = NUL; diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 08353509af..b470dbf8f6 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -15,7 +15,6 @@ export TMPDIR := $(abspath Xtest-tmpdir) SCRIPTS_DEFAULT = \ test42.out \ - test48.out \ test64.out \ ifneq ($(OS),Windows_NT) @@ -34,21 +33,23 @@ SCRIPTS ?= $(SCRIPTS_DEFAULT) # Tests using runtest.vim. NEW_TESTS_ALOT := test_alot_utf8 test_alot -NEW_TESTS_IN_ALOT := $(shell sed '/^source/ s/^source //;s/\.vim$$//' test_alot*.vim) +NEW_TESTS_IN_ALOT := $(shell sed -n '/^source/ s/^source //; s/\.vim$$//p' $(addsuffix .vim,$(NEW_TESTS_ALOT))) +NEW_TESTS_IN_ALOT_LATIN := $(shell sed -n '/^source/ s/^source //; s/\.vim$$//p' test_alot_latin.vim) # Ignored tests. # test_alot_latin: Nvim does not allow setting encoding. # test_autochdir: ported to Lua, but kept for easier merging. # test_eval_func: used as include in old-style test (test_eval.in). # test_listlbr: Nvim does not allow setting encoding. # test_largefile: uses too much resources to run on CI. -NEW_TESTS_IGNORE := $(NEW_TESTS_IN_ALOT) $(NEW_TESTS_ALOT) \ - test_alot_latin \ +NEW_TESTS_IGNORE := \ + test_alot_latin $(NEW_TESTS_IN_ALOT_LATIN) \ test_autochdir \ test_eval_func \ test_listlbr \ test_largefile \ -NEW_TESTS ?= $(addsuffix .res,$(sort $(filter-out $(NEW_TESTS_IGNORE),$(basename $(notdir $(wildcard test_*.vim))))) $(NEW_TESTS_ALOT)) +NEW_TESTS := $(sort $(basename $(notdir $(wildcard test_*.vim)))) +NEW_TESTS_RES := $(addsuffix .res,$(filter-out $(NEW_TESTS_ALOT) $(NEW_TESTS_IN_ALOT) $(NEW_TESTS_IGNORE),$(NEW_TESTS)) $(NEW_TESTS_ALOT)) ifdef VALGRIND_GDB @@ -112,6 +113,16 @@ fixff: -$(NVIM_PRG) $(NO_INITS) -u unix.vim "+argdo set ff=dos|upd" +q \ dotest.in +# Execute an individual new style test, e.g.: +# make test_largefile +$(NEW_TESTS): + rm -f $@.res test.log messages + @MAKEFLAGS=--no-print-directory $(MAKE) -f Makefile $@.res + @cat messages + @if test -f test.log; then \ + exit 1; \ + fi + RM_ON_RUN := test.out X* viminfo RM_ON_START := test.ok RUN_VIM := $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE -i viminfo --headless --noplugin -s dotest.in @@ -172,7 +183,7 @@ newtests: newtestssilent cat messages && cat test.log; \ fi" -newtestssilent: $(NEW_TESTS) +newtestssilent: $(NEW_TESTS_RES) %.res: %.vim .gdbinit @echo "[OLDTEST] Running" $* diff --git a/src/nvim/testdir/runnvim.sh b/src/nvim/testdir/runnvim.sh index 2dcd9150be..72f9254635 100755 --- a/src/nvim/testdir/runnvim.sh +++ b/src/nvim/testdir/runnvim.sh @@ -82,6 +82,11 @@ main() {( fi if test "$FAILED" = 1 ; then echo "Test $test_name failed, see output above and summary for more details" >> test.log + # When Neovim crashed/aborted it might not have created messages. + # test.log itself is used as an indicator to exit non-zero in the Makefile. + if ! test -f message; then + cp -a test.log messages + fi fi )} diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 5c2e570adf..2d4134a644 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -297,6 +297,8 @@ let s:flaky_tests = [ \ 'Test_repeat_three()', \ 'Test_state()', \ 'Test_stop_all_in_callback()', + \ 'Test_term_mouse_double_click_to_create_tab', + \ 'Test_term_mouse_multiple_clicks_to_visually_select()', \ 'Test_terminal_composing_unicode()', \ 'Test_terminal_redir_file()', \ 'Test_terminal_tmap()', diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index 84f636077d..b0b59db686 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -69,7 +69,8 @@ endfunc " Read the port number from the Xportnr file. func GetPort() let l = [] - for i in range(200) + " with 200 it sometimes failed + for i in range(400) try let l = readfile("Xportnr") catch @@ -252,6 +253,8 @@ func GetVimProg() endif endfunc +let g:valgrind_cnt = 1 + " Get the command to run Vim, with -u NONE and --headless arguments. " If there is an argument use it instead of "NONE". func GetVimCommand(...) @@ -267,14 +270,25 @@ func GetVimCommand(...) endif let cmd .= ' --headless -i NONE' let cmd = substitute(cmd, 'VIMRUNTIME=.*VIMRUNTIME;', '', '') + + " If using valgrind, make sure every run uses a different log file. + if cmd =~ 'valgrind.*--log-file=' + let cmd = substitute(cmd, '--log-file=\(^\s*\)', '--log-file=\1.' . g:valgrind_cnt, '') + let g:valgrind_cnt += 1 + endif + return cmd endfunc -" Get the command to run Vim, with --clean. +" Get the command to run Vim, with --clean instead of "-u NONE". func GetVimCommandClean() let cmd = GetVimCommand() let cmd = substitute(cmd, '-u NONE', '--clean', '') let cmd = substitute(cmd, '--headless', '', '') + + " Optionally run Vim under valgrind + " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd + return cmd endfunc @@ -290,9 +304,6 @@ endfunc func RunVimPiped(before, after, arguments, pipecmd) let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' let cmd = GetVimCommand() - if cmd == '' - return 0 - endif let args = '' if len(a:before) > 0 call writefile(a:before, 'Xbefore.vim') diff --git a/src/nvim/testdir/test48.in b/src/nvim/testdir/test48.in deleted file mode 100644 index 1df5a3c46a..0000000000 --- a/src/nvim/testdir/test48.in +++ /dev/null @@ -1,82 +0,0 @@ -This is a test of 'virtualedit'. - -STARTTEST -:set noswf -:set ve=all -j-dgg -:" -:" Insert "keyword keyw", ESC, C CTRL-N, shows "keyword ykeyword". -:" Repeating CTRL-N fixes it. (Mary Ellen Foster) -2/w -C -:" -:" Using "C" then then <CR> moves the last remaining character to the next -:" line. (Mary Ellen Foster) -j^/are -C
are belong to vim -:" -:" When past the end of a line that ends in a single character "b" skips -:" that word. -^$15lbC7 -:" -:" Make sure 'i' works -$4li<-- should be 3 ' ' -:" -:" Make sure 'C' works -$4lC<-- should be 3 ' ' -:" -:" Make sure 'a' works -$4la<-- should be 4 ' ' -:" -:" Make sure 'A' works -$4lA<-- should be 0 ' ' -:" -:" Make sure 'D' works -$4lDi<-- 'D' should be intact -:" -:" Test for yank bug reported by Mark Waggoner. -:set ve=block -^2w3jyGp -:" -:" Test "r" beyond the end of the line -:set ve=all -/^"r" -$5lrxa<-- should be 'x' -:" -:" Test "r" on a tab -:" Note that for this test, 'ts' must be 8 (the default). -^5lrxA<-- should be ' x ' -:" -:" Test to make sure 'x' can delete control characters -:set display=uhex -^xxxxxxi[This line should contain only the text between the brackets.] -:set display= -:" -:" Test for ^Y/^E due to bad w_virtcol value, reported by -:" Roy <royl@netropolis.net>. -^O3li4li4li <-- should show the name of a noted text editor -^o4li4li4li <-- and its version number-dd -:" -:" Test for yanking and pasting using the small delete register -gg/^foo -dewve"-p -:wq! test.out -ENDTEST -foo, bar -keyword keyw -all your base are belong to us -1 2 3 4 5 6 -'i' -'C' -'a' -'A' -'D' -this is a test -this is a test -this is a test -"r" -"r" -ab
sd -abcv6efi.him0kl - - diff --git a/src/nvim/testdir/test48.ok b/src/nvim/testdir/test48.ok deleted file mode 100644 index 14cd9b12ec..0000000000 --- a/src/nvim/testdir/test48.ok +++ /dev/null @@ -1,23 +0,0 @@ -, foo -keyword keyword -all your base -are belong to vim -1 2 3 4 5 7 -'i' <-- should be 3 ' ' -'C' <-- should be 3 ' ' -'a' <-- should be 4 ' ' -'A'<-- should be 0 ' ' -'D' <-- 'D' should be intact -this is a test -this is a test -this is a test -"r" x<-- should be 'x' -"r" x <-- should be ' x ' -[This line should contain only the text between the brackets.] - v i m <-- should show the name of a noted text editor - 6 . 0 <-- and its version number - -a -a -a - diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 0a3e6ae625..262ea11eb6 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -78,26 +78,45 @@ func Test_map_completion() call feedkeys(":map <silent> <sp\<Tab>\<Home>\"\<CR>", 'xt') call assert_equal('"map <silent> <special>', getreg(':')) + map <Middle>x middle + map ,f commaf map ,g commaf + map <Left> left + map <A-Left>x shiftleft call feedkeys(":map ,\<Tab>\<Home>\"\<CR>", 'xt') call assert_equal('"map ,f', getreg(':')) call feedkeys(":map ,\<Tab>\<Tab>\<Home>\"\<CR>", 'xt') call assert_equal('"map ,g', getreg(':')) + call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <Left>', getreg(':')) + call feedkeys(":map <A-Left>\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal("\"map <A-Left>\<Tab>", getreg(':')) unmap ,f unmap ,g + unmap <Left> + unmap <A-Left>x set cpo-=< cpo-=B cpo-=k map <Left> left call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt') call assert_equal('"map <Left>', getreg(':')) + call feedkeys(":map <M\<Tab>\<Home>\"\<CR>", 'xt') + " call assert_equal("\"map <M\<Tab>", getreg(':')) unmap <Left> " set cpo+=< map <Left> left + exe "set t_k6=\<Esc>[17~" + call feedkeys(":map \<Esc>[17~x f6x\<CR>", 'xt') call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt') call assert_equal('"map <Left>', getreg(':')) + if !has('gui_running') + call feedkeys(":map \<Esc>[17~\<Tab>\<Home>\"\<CR>", 'xt') + " call assert_equal("\"map <F6>x", getreg(':')) + endif unmap <Left> + call feedkeys(":unmap \<Esc>[17~x\<CR>", 'xt') set cpo-=< set cpo+=B @@ -113,6 +132,9 @@ func Test_map_completion() call assert_equal('"map <Left>', getreg(':')) unmap <Left> " set cpo-=k + + unmap <Middle>x + set cpo&vim endfunc func Test_match_completion() @@ -159,6 +181,7 @@ func Test_expr_completion() endif for cmd in [ \ 'let a = ', + \ 'const a = ', \ 'if', \ 'elseif', \ 'while', @@ -301,7 +324,7 @@ func Test_getcompletion() call assert_equal([], l) let l = getcompletion('.', 'shellcmd') - call assert_equal(['./', '../'], l[0:1]) + call assert_equal(['./', '../'], filter(l, 'v:val =~ "\\./"')) call assert_equal(-1, match(l[2:], '^\.\.\?/$')) let root = has('win32') ? 'C:\\' : '/' let l = getcompletion(root, 'shellcmd') @@ -375,6 +398,29 @@ func Test_getcompletion() call assert_fails('call getcompletion("", "burp")', 'E475:') endfunc +func Test_shellcmd_completion() + let save_path = $PATH + + call mkdir('Xpathdir/Xpathsubdir', 'p') + call writefile([''], 'Xpathdir/Xfile.exe') + call setfperm('Xpathdir/Xfile.exe', 'rwx------') + + " Set PATH to example directory without trailing slash. + let $PATH = getcwd() . '/Xpathdir' + + " Test for the ":!<TAB>" case. Previously, this would include subdirs of + " dirs in the PATH, even though they won't be executed. We check that only + " subdirs of the PWD and executables from the PATH are included in the + " suggestions. + let actual = getcompletion('X', 'shellcmd') + let expected = map(filter(glob('*', 0, 1), 'isdirectory(v:val) && v:val[0] == "X"'), 'v:val . "/"') + call insert(expected, 'Xfile.exe') + call assert_equal(expected, actual) + + call delete('Xpathdir', 'rf') + let $PATH = save_path +endfunc + func Test_expand_star_star() call mkdir('a/b', 'p') call writefile(['asdfasdf'], 'a/b/fileXname') @@ -492,8 +538,9 @@ func Test_cmdline_complete_user_names() let names = system('net user') if names =~ 'Administrator' " Trying completion of :e ~A should complete to Administrator. + " There could be other names starting with "A" before Administrator. call feedkeys(':e ~A' . "\<c-a>\<c-B>\"\<cr>", 'tx') - call assert_match('^"e \~Administrator', @:) + call assert_match('^"e \~.*Administrator', @:) endif endif endfunc @@ -572,6 +619,8 @@ func Check_cmdline(cmdtype) return '' endfunc +set cpo& + func Test_getcmdtype() call feedkeys(":MyCmd a\<C-R>=Check_cmdline(':')\<CR>\<Esc>", "xt") @@ -612,6 +661,37 @@ func Test_getcmdwintype() call assert_equal('', getcmdwintype()) endfunc +func Test_getcmdwin_autocmd() + let s:seq = [] + augroup CmdWin + au WinEnter * call add(s:seq, 'WinEnter ' .. win_getid()) + au WinLeave * call add(s:seq, 'WinLeave ' .. win_getid()) + au BufEnter * call add(s:seq, 'BufEnter ' .. bufnr()) + au BufLeave * call add(s:seq, 'BufLeave ' .. bufnr()) + au CmdWinEnter * call add(s:seq, 'CmdWinEnter ' .. win_getid()) + au CmdWinLeave * call add(s:seq, 'CmdWinLeave ' .. win_getid()) + + let org_winid = win_getid() + let org_bufnr = bufnr() + call feedkeys("q::let a = getcmdwintype()\<CR>:let s:cmd_winid = win_getid()\<CR>:let s:cmd_bufnr = bufnr()\<CR>:q\<CR>", 'x!') + call assert_equal(':', a) + call assert_equal([ + \ 'WinLeave ' .. org_winid, + \ 'WinEnter ' .. s:cmd_winid, + \ 'BufLeave ' .. org_bufnr, + \ 'BufEnter ' .. s:cmd_bufnr, + \ 'CmdWinEnter ' .. s:cmd_winid, + \ 'CmdWinLeave ' .. s:cmd_winid, + \ 'BufLeave ' .. s:cmd_bufnr, + \ 'WinLeave ' .. s:cmd_winid, + \ 'WinEnter ' .. org_winid, + \ 'BufEnter ' .. org_bufnr, + \ ], s:seq) + + au! + augroup END +endfunc + func Test_verbosefile() set verbosefile=Xlog echomsg 'foo' @@ -670,5 +750,3 @@ func Test_cmdline_overstrike() let &encoding = encoding_save endfunc - -set cpo& diff --git a/src/nvim/testdir/test_const.vim b/src/nvim/testdir/test_const.vim index 06062c5e58..eaf200e9bb 100644 --- a/src/nvim/testdir/test_const.vim +++ b/src/nvim/testdir/test_const.vim @@ -176,6 +176,26 @@ func Test_cannot_modify_existing_variable() call assert_fails('const [i2, f2, s2] = [1, 1.1, "vim"]', 'E995:') endfunc +func Test_const_with_condition() + const x = 0 + if 0 | const x = 1 | endif + call assert_equal(0, x) +endfunc + +func Test_lockvar() + let x = 'hello' + lockvar x + call assert_fails('let x = "there"', 'E741') + if 0 | unlockvar x | endif + call assert_fails('let x = "there"', 'E741') + unlockvar x + let x = 'there' + + if 0 | lockvar x | endif + let x = 'again' +endfunc + + func Test_const_with_index_access() let l = [1, 2, 3] call assert_fails('const l[0] = 4', 'E996:') diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim index 66c13ded82..1c2f5a05ff 100644 --- a/src/nvim/testdir/test_display.vim +++ b/src/nvim/testdir/test_display.vim @@ -71,6 +71,7 @@ func! Test_display_foldtext_mbyte() endfunc func Test_display_listchars_precedes() + set fillchars+=vert:\| call NewWindow(10, 10) " Need a physical line that wraps over the complete " window size diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 4053746c82..e085f58e56 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -223,7 +223,7 @@ let s:filename_checks = { \ 'jam': ['file.jpl', 'file.jpr'], \ 'java': ['file.java', 'file.jav'], \ 'javacc': ['file.jj', 'file.jjt'], - \ 'javascript': ['file.js', 'file.javascript', 'file.es', 'file.mjs'], + \ 'javascript': ['file.js', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs'], \ 'javascriptreact': ['file.jsx'], \ 'jess': ['file.clp'], \ 'jgraph': ['file.jgr'], diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index 7f52481ba8..52ec281d82 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -285,3 +285,21 @@ func Test_compl_feedkeys() bwipe! set completeopt& endfunc + +func Test_compl_in_cmdwin() + set wildmenu wildchar=<Tab> + com! -nargs=1 -complete=command GetInput let input = <q-args> + com! -buffer TestCommand echo 'TestCommand' + + let input = '' + call feedkeys("q:iGetInput T\<C-x>\<C-v>\<CR>", 'tx!') + call assert_equal('TestCommand', input) + + let input = '' + call feedkeys("q::GetInput T\<Tab>\<CR>:q\<CR>", 'tx!') + call assert_equal('T', input) + + delcom TestCommand + delcom GetInput + set wildmenu& wildchar& +endfunc diff --git a/src/nvim/testdir/test_let.vim b/src/nvim/testdir/test_let.vim index 3c0fefbd25..0b9331ee38 100644 --- a/src/nvim/testdir/test_let.vim +++ b/src/nvim/testdir/test_let.vim @@ -24,6 +24,10 @@ func Test_let() let out = execute('let a {0 == 1 ? "a" : "b"}') let s = "\na #1\nb #2" call assert_equal(s, out) + + let x = 0 + if 0 | let x = 1 | endif + call assert_equal(0, x) endfunc func s:set_arg1(a) abort diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index eab638d19a..ad6d325510 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -1367,8 +1367,9 @@ func Test_normal23_K() return endif - if has('mac') - " In MacOS, the option for specifying a pager is different + let not_gnu_man = has('mac') || has('bsd') + if not_gnu_man + " In MacOS and BSD, the option for specifying a pager is different set keywordprg=man\ -P\ cat else set keywordprg=man\ --pager=cat @@ -1376,7 +1377,7 @@ func Test_normal23_K() " Test for using man 2 let a = execute('unsilent norm! K') - if has('mac') + if not_gnu_man call assert_match("man -P cat 'man'", a) else call assert_match("man --pager=cat 'man'", a) diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index f4f5cbca61..6fcc372591 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -476,13 +476,19 @@ func Test_shortmess_F2() call assert_match('file2', execute('bn', '')) set shortmess+=F call assert_true(empty(execute('bn', ''))) + " call assert_false(test_getvalue('need_fileinfo')) call assert_true(empty(execute('bn', ''))) + " call assert_false(test_getvalue('need_fileinfo')) set hidden call assert_true(empty(execute('bn', ''))) + " call assert_false(test_getvalue('need_fileinfo')) call assert_true(empty(execute('bn', ''))) + " call assert_false(test_getvalue('need_fileinfo')) set nohidden call assert_true(empty(execute('bn', ''))) + " call assert_false(test_getvalue('need_fileinfo')) call assert_true(empty(execute('bn', ''))) + " call assert_false(test_getvalue('need_fileinfo')) " Accommodate Nvim default. set shortmess-=F call assert_match('file1', execute('bn', '')) diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 15cbf52cb5..d7b387c2c9 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1642,6 +1642,14 @@ func Test_switchbuf() call assert_equal(3, tabpagenr('$')) tabfirst | enew | tabonly | only + set switchbuf=uselast + split + let last_winid = win_getid() + copen + exe "normal 1G\<CR>" + call assert_equal(last_winid, win_getid()) + enew | only + set switchbuf= edit Xqftestfile1 let file1_winid = win_getid() @@ -2589,94 +2597,6 @@ func Test_resize_from_copen() endtry endfunc -" Test for aborting quickfix commands using QuickFixCmdPre -func Xtest_qfcmd_abort(cchar) - call s:setup_commands(a:cchar) - - call g:Xsetlist([], 'f') - - " cexpr/lexpr - let e = '' - try - Xexpr ["F1:10:Line10", "F2:20:Line20"] - catch /.*/ - let e = v:exception - endtry - call assert_equal('AbortCmd', e) - call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) - - " cfile/lfile - call writefile(["F1:10:Line10", "F2:20:Line20"], 'Xfile1') - let e = '' - try - Xfile Xfile1 - catch /.*/ - let e = v:exception - endtry - call assert_equal('AbortCmd', e) - call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) - call delete('Xfile1') - - " cgetbuffer/lgetbuffer - enew! - call append(0, ["F1:10:Line10", "F2:20:Line20"]) - let e = '' - try - Xgetbuffer - catch /.*/ - let e = v:exception - endtry - call assert_equal('AbortCmd', e) - call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) - enew! - - " vimgrep/lvimgrep - let e = '' - try - Xvimgrep /func/ test_quickfix.vim - catch /.*/ - let e = v:exception - endtry - call assert_equal('AbortCmd', e) - call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) - - " helpgrep/lhelpgrep - let e = '' - try - Xhelpgrep quickfix - catch /.*/ - let e = v:exception - endtry - call assert_equal('AbortCmd', e) - call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) - - " grep/lgrep - if has('unix') - let e = '' - try - silent Xgrep func test_quickfix.vim - catch /.*/ - let e = v:exception - endtry - call assert_equal('AbortCmd', e) - call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) - endif -endfunc - -func Test_qfcmd_abort() - augroup QF_Test - au! - autocmd QuickFixCmdPre * throw "AbortCmd" - augroup END - - call Xtest_qfcmd_abort('c') - call Xtest_qfcmd_abort('l') - - augroup QF_Test - au! - augroup END -endfunc - " Tests for the quickfix buffer b:changedtick variable func Xchangedtick_tests(cchar) call s:setup_commands(a:cchar) @@ -3033,185 +2953,101 @@ func Test_qf_id() call Xqfid_tests('l') endfunc -func Test_getqflist_invalid_nr() - " The following commands used to crash Vim - cexpr "" - call getqflist({'nr' : $XXX_DOES_NOT_EXIST_XXX}) - - " Cleanup - call setqflist([], 'r') -endfunc - -" Test for shortening/simplifying the file name when opening the -" quickfix window or when displaying the quickfix list -func Test_shorten_fname() - if !has('unix') - return - endif - %bwipe - " Create a quickfix list with a absolute path filename - let fname = getcwd() . '/test_quickfix.vim' - call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'}) - call assert_equal(fname, bufname('test_quickfix.vim')) - " Opening the quickfix window should simplify the file path - cwindow - call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim')) - cclose - %bwipe - " Create a quickfix list with a absolute path filename - call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'}) - call assert_equal(fname, bufname('test_quickfix.vim')) - " Displaying the quickfix list should simplify the file path - silent! clist - call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim')) -endfunc - -" Quickfix title tests -" In the below tests, 'exe "cmd"' is used to invoke the quickfix commands. -" Otherwise due to indentation, the title is set with spaces at the beginning -" of the command. -func Test_qftitle() - call writefile(["F1:1:Line1"], 'Xerr') - - " :cexpr - exe "cexpr readfile('Xerr')" - call assert_equal(":cexpr readfile('Xerr')", getqflist({'title' : 1}).title) - - " :cgetexpr - exe "cgetexpr readfile('Xerr')" - call assert_equal(":cgetexpr readfile('Xerr')", - \ getqflist({'title' : 1}).title) - - " :caddexpr - call setqflist([], 'f') - exe "caddexpr readfile('Xerr')" - call assert_equal(":caddexpr readfile('Xerr')", - \ getqflist({'title' : 1}).title) - - " :cbuffer - new Xerr - exe "cbuffer" - call assert_equal(':cbuffer (Xerr)', getqflist({'title' : 1}).title) - - " :cgetbuffer - edit Xerr - exe "cgetbuffer" - call assert_equal(':cgetbuffer (Xerr)', getqflist({'title' : 1}).title) - - " :caddbuffer - call setqflist([], 'f') - edit Xerr - exe "caddbuffer" - call assert_equal(':caddbuffer (Xerr)', getqflist({'title' : 1}).title) - - " :cfile - exe "cfile Xerr" - call assert_equal(':cfile Xerr', getqflist({'title' : 1}).title) - - " :cgetfile - exe "cgetfile Xerr" - call assert_equal(':cgetfile Xerr', getqflist({'title' : 1}).title) - - " :caddfile - call setqflist([], 'f') - exe "caddfile Xerr" - call assert_equal(':caddfile Xerr', getqflist({'title' : 1}).title) - - " :grep - set grepprg=internal - exe "grep F1 Xerr" - call assert_equal(':grep F1 Xerr', getqflist({'title' : 1}).title) - - " :grepadd - call setqflist([], 'f') - exe "grepadd F1 Xerr" - call assert_equal(':grepadd F1 Xerr', getqflist({'title' : 1}).title) - set grepprg&vim - - " :vimgrep - exe "vimgrep F1 Xerr" - call assert_equal(':vimgrep F1 Xerr', getqflist({'title' : 1}).title) +func Xqfjump_tests(cchar) + call s:setup_commands(a:cchar) - " :vimgrepadd - call setqflist([], 'f') - exe "vimgrepadd F1 Xerr" - call assert_equal(':vimgrepadd F1 Xerr', getqflist({'title' : 1}).title) + call writefile(["Line1\tFoo", "Line2"], 'F1') + call writefile(["Line1\tBar", "Line2"], 'F2') + call writefile(["Line1\tBaz", "Line2"], 'F3') - call setqflist(['F1:10:L10'], ' ') - call assert_equal(':setqflist()', getqflist({'title' : 1}).title) + call g:Xsetlist([], 'f') - call setqflist([], 'f') - call setqflist(['F1:10:L10'], 'a') - call assert_equal(':setqflist()', getqflist({'title' : 1}).title) + " Tests for + " Jumping to a line using a pattern + " Jumping to a column greater than the last column in a line + " Jumping to a line greater than the last line in the file + let l = [] + for i in range(1, 7) + call add(l, {}) + endfor + let l[0].filename='F1' + let l[0].pattern='Line1' + let l[1].filename='F2' + let l[1].pattern='Line1' + let l[2].filename='F3' + let l[2].pattern='Line1' + let l[3].filename='F3' + let l[3].lnum=1 + let l[3].col=9 + let l[3].vcol=1 + let l[4].filename='F3' + let l[4].lnum=99 + let l[5].filename='F3' + let l[5].lnum=1 + let l[5].col=99 + let l[5].vcol=1 + let l[6].filename='F3' + let l[6].pattern='abcxyz' - call setqflist([], 'f') - call setqflist(['F1:10:L10'], 'r') - call assert_equal(':setqflist()', getqflist({'title' : 1}).title) + call g:Xsetlist([], ' ', {'items' : l}) + Xopen | only + 2Xnext + call assert_equal(3, g:Xgetlist({'idx' : 0}).idx) + call assert_equal('F3', bufname('%')) + Xnext + call assert_equal(7, col('.')) + Xnext + call assert_equal(2, line('.')) + Xnext + call assert_equal(9, col('.')) + 2 + Xnext + call assert_equal(2, line('.')) - close - call delete('Xerr') + if a:cchar == 'l' + " When jumping to a location list entry in the location list window and + " no usable windows are available, then a new window should be opened. + enew! | new | only + call g:Xsetlist([], 'f') + setlocal buftype=nofile + new + call g:Xsetlist([], ' ', {'lines' : ['F1:1:1:Line1', 'F1:2:2:Line2', 'F2:1:1:Line1', 'F2:2:2:Line2', 'F3:1:1:Line1', 'F3:2:2:Line2']}) + Xopen + let winid = win_getid() + wincmd p + close + call win_gotoid(winid) + Xnext + call assert_equal(3, winnr('$')) + call assert_equal(1, winnr()) + call assert_equal(2, line('.')) - call setqflist([], ' ', {'title' : 'Errors'}) - copen - call assert_equal('Errors', w:quickfix_title) - call setqflist([], 'r', {'items' : [{'filename' : 'a.c', 'lnum' : 10}]}) - call assert_equal('Errors', w:quickfix_title) - cclose -endfunc + " When jumping to an entry in the location list window and the window + " associated with the location list is not present and a window containing + " the file is already present, then that window should be used. + close + belowright new + call g:Xsetlist([], 'f') + edit F3 + call win_gotoid(winid) + Xlast + call assert_equal(3, winnr()) + call assert_equal(6, g:Xgetlist({'size' : 1}).size) + call assert_equal(winid, g:Xgetlist({'winid' : 1}).winid) + endif -" Test for the position of the quickfix and location list window -func Test_qfwin_pos() - " Open two windows + " Cleanup + enew! new | only - new - cexpr ['F1:10:L10'] - copen - " Quickfix window should be the bottom most window - call assert_equal(3, winnr()) - close - " Open at the very top - wincmd t - topleft copen - call assert_equal(1, winnr()) - close - " open left of the current window - wincmd t - below new - leftabove copen - call assert_equal(2, winnr()) - close - " open right of the current window - rightbelow copen - call assert_equal(3, winnr()) - close -endfunc -" The following test used to crash Vim -func Test_lhelpgrep_autocmd() - lhelpgrep quickfix - autocmd QuickFixCmdPost * call setloclist(0, [], 'f') - lhelpgrep buffer - call assert_equal('help', &filetype) - call assert_equal(0, getloclist(0, {'nr' : '$'}).nr) - lhelpgrep tabpage - call assert_equal('help', &filetype) - call assert_equal(1, getloclist(0, {'nr' : '$'}).nr) - au! QuickFixCmdPost - new | only + call delete('F1') + call delete('F2') + call delete('F3') endfunc -" Test to make sure that an empty quickfix buffer is not reused for loading -" a normal buffer. -func Test_empty_qfbuf() - enew | only - call writefile(["Test"], 'Xfile1') - call setqflist([], 'f') - copen | only - let qfbuf = bufnr('') - edit Xfile1 - call assert_notequal(qfbuf, bufnr('')) - enew - call delete('Xfile1') +func Test_qfjump() + call Xqfjump_tests('c') + call Xqfjump_tests('l') endfunc " Tests for the getqflist() and getloclist() functions when the list is not @@ -3311,6 +3147,16 @@ func Test_getqflist() call Xgetlist_empty_tests('l') endfunc + +func Test_getqflist_invalid_nr() + " The following commands used to crash Vim + cexpr "" + call getqflist({'nr' : $XXX_DOES_NOT_EXIST_XXX}) + + " Cleanup + call setqflist([], 'r') +endfunc + " Tests for the quickfix/location list changedtick func Xqftick_tests(cchar) call s:setup_commands(a:cchar) @@ -3369,6 +3215,41 @@ func Test_qf_tick() call Xqftick_tests('l') endfunc +" Test helpgrep with lang specifier +func Xtest_helpgrep_with_lang_specifier(cchar) + call s:setup_commands(a:cchar) + Xhelpgrep Vim@en + call assert_equal('help', &filetype) + call assert_notequal(0, g:Xgetlist({'nr' : '$'}).nr) + new | only +endfunc + +func Test_helpgrep_with_lang_specifier() + call Xtest_helpgrep_with_lang_specifier('c') + call Xtest_helpgrep_with_lang_specifier('l') +endfunc + +" The following test used to crash Vim. +" Open the location list window and close the regular window associated with +" the location list. When the garbage collection runs now, it incorrectly +" marks the location list context as not in use and frees the context. +func Test_ll_window_ctx() + call setloclist(0, [], 'f') + call setloclist(0, [], 'a', {'context' : []}) + lopen | only + call test_garbagecollect_now() + echo getloclist(0, {'context' : 1}).context + enew | only +endfunc + +" The following test used to crash vim +func Test_lfile_crash() + sp Xtest + au QuickFixCmdPre * bw + call assert_fails('lfile', 'E40') + au! QuickFixCmdPre +endfunc + " The following test used to crash vim func Test_lbuffer_crash() sv Xtest @@ -3430,136 +3311,31 @@ func Test_lvimgrep_crash() enew | only endfunc -func Xqfjump_tests(cchar) - call s:setup_commands(a:cchar) - - call writefile(["Line1\tFoo", "Line2"], 'F1') - call writefile(["Line1\tBar", "Line2"], 'F2') - call writefile(["Line1\tBaz", "Line2"], 'F3') - - call g:Xsetlist([], 'f') - - " Tests for - " Jumping to a line using a pattern - " Jumping to a column greater than the last column in a line - " Jumping to a line greater than the last line in the file - let l = [] - for i in range(1, 7) - call add(l, {}) - endfor - let l[0].filename='F1' - let l[0].pattern='Line1' - let l[1].filename='F2' - let l[1].pattern='Line1' - let l[2].filename='F3' - let l[2].pattern='Line1' - let l[3].filename='F3' - let l[3].lnum=1 - let l[3].col=9 - let l[3].vcol=1 - let l[4].filename='F3' - let l[4].lnum=99 - let l[5].filename='F3' - let l[5].lnum=1 - let l[5].col=99 - let l[5].vcol=1 - let l[6].filename='F3' - let l[6].pattern='abcxyz' - - call g:Xsetlist([], ' ', {'items' : l}) - Xopen | only - 2Xnext - call assert_equal(3, g:Xgetlist({'idx' : 0}).idx) - call assert_equal('F3', bufname('%')) - Xnext - call assert_equal(7, col('.')) - Xnext - call assert_equal(2, line('.')) - Xnext - call assert_equal(9, col('.')) - 2 - Xnext - call assert_equal(2, line('.')) - - if a:cchar == 'l' - " When jumping to a location list entry in the location list window and - " no usable windows are available, then a new window should be opened. - enew! | new | only - call g:Xsetlist([], 'f') - setlocal buftype=nofile - new - call g:Xsetlist([], ' ', {'lines' : ['F1:1:1:Line1', 'F1:2:2:Line2', 'F2:1:1:Line1', 'F2:2:2:Line2', 'F3:1:1:Line1', 'F3:2:2:Line2']}) - Xopen - let winid = win_getid() - wincmd p - close - call win_gotoid(winid) - Xnext - call assert_equal(3, winnr('$')) - call assert_equal(1, winnr()) - call assert_equal(2, line('.')) - - " When jumping to an entry in the location list window and the window - " associated with the location list is not present and a window containing - " the file is already present, then that window should be used. - close - belowright new - call g:Xsetlist([], 'f') - edit F3 - call win_gotoid(winid) - Xlast - call assert_equal(3, winnr()) - call assert_equal(6, g:Xgetlist({'size' : 1}).size) - call assert_equal(winid, g:Xgetlist({'winid' : 1}).winid) - endif - - " Cleanup - enew! - new | only - - call delete('F1') - call delete('F2') - call delete('F3') -endfunc - -func Test_qfjump() - call Xqfjump_tests('c') - call Xqfjump_tests('l') -endfunc - -" Test helpgrep with lang specifier -func Xtest_helpgrep_with_lang_specifier(cchar) - call s:setup_commands(a:cchar) - Xhelpgrep Vim@en - call assert_equal('help', &filetype) - call assert_notequal(0, g:Xgetlist({'nr' : '$'}).nr) +" Test for the position of the quickfix and location list window +func Test_qfwin_pos() + " Open two windows new | only -endfunc - -func Test_helpgrep_with_lang_specifier() - call Xtest_helpgrep_with_lang_specifier('c') - call Xtest_helpgrep_with_lang_specifier('l') -endfunc - -" The following test used to crash Vim. -" Open the location list window and close the regular window associated with -" the location list. When the garbage collection runs now, it incorrectly -" marks the location list context as not in use and frees the context. -func Test_ll_window_ctx() - call setloclist(0, [], 'f') - call setloclist(0, [], 'a', {'context' : []}) - lopen | only - call test_garbagecollect_now() - echo getloclist(0, {'context' : 1}).context - enew | only -endfunc - -" The following test used to crash vim -func Test_lfile_crash() - sp Xtest - au QuickFixCmdPre * bw - call assert_fails('lfile', 'E40') - au! QuickFixCmdPre + new + cexpr ['F1:10:L10'] + copen + " Quickfix window should be the bottom most window + call assert_equal(3, winnr()) + close + " Open at the very top + wincmd t + topleft copen + call assert_equal(1, winnr()) + close + " open left of the current window + wincmd t + below new + leftabove copen + call assert_equal(2, winnr()) + close + " open right of the current window + rightbelow copen + call assert_equal(3, winnr()) + close endfunc " Tests for quickfix/location lists changed by autocommands when @@ -3603,6 +3379,137 @@ func Test_vimgrep_autocmd() call setqflist([], 'f') endfunc +" The following test used to crash Vim +func Test_lhelpgrep_autocmd() + lhelpgrep quickfix + autocmd QuickFixCmdPost * call setloclist(0, [], 'f') + lhelpgrep buffer + call assert_equal('help', &filetype) + call assert_equal(0, getloclist(0, {'nr' : '$'}).nr) + lhelpgrep tabpage + call assert_equal('help', &filetype) + call assert_equal(1, getloclist(0, {'nr' : '$'}).nr) + au! QuickFixCmdPost + new | only +endfunc + +" Test for shortening/simplifying the file name when opening the +" quickfix window or when displaying the quickfix list +func Test_shorten_fname() + if !has('unix') + return + endif + %bwipe + " Create a quickfix list with a absolute path filename + let fname = getcwd() . '/test_quickfix.vim' + call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'}) + call assert_equal(fname, bufname('test_quickfix.vim')) + " Opening the quickfix window should simplify the file path + cwindow + call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim')) + cclose + %bwipe + " Create a quickfix list with a absolute path filename + call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'}) + call assert_equal(fname, bufname('test_quickfix.vim')) + " Displaying the quickfix list should simplify the file path + silent! clist + call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim')) +endfunc + +" Quickfix title tests +" In the below tests, 'exe "cmd"' is used to invoke the quickfix commands. +" Otherwise due to indentation, the title is set with spaces at the beginning +" of the command. +func Test_qftitle() + call writefile(["F1:1:Line1"], 'Xerr') + + " :cexpr + exe "cexpr readfile('Xerr')" + call assert_equal(":cexpr readfile('Xerr')", getqflist({'title' : 1}).title) + + " :cgetexpr + exe "cgetexpr readfile('Xerr')" + call assert_equal(":cgetexpr readfile('Xerr')", + \ getqflist({'title' : 1}).title) + + " :caddexpr + call setqflist([], 'f') + exe "caddexpr readfile('Xerr')" + call assert_equal(":caddexpr readfile('Xerr')", + \ getqflist({'title' : 1}).title) + + " :cbuffer + new Xerr + exe "cbuffer" + call assert_equal(':cbuffer (Xerr)', getqflist({'title' : 1}).title) + + " :cgetbuffer + edit Xerr + exe "cgetbuffer" + call assert_equal(':cgetbuffer (Xerr)', getqflist({'title' : 1}).title) + + " :caddbuffer + call setqflist([], 'f') + edit Xerr + exe "caddbuffer" + call assert_equal(':caddbuffer (Xerr)', getqflist({'title' : 1}).title) + + " :cfile + exe "cfile Xerr" + call assert_equal(':cfile Xerr', getqflist({'title' : 1}).title) + + " :cgetfile + exe "cgetfile Xerr" + call assert_equal(':cgetfile Xerr', getqflist({'title' : 1}).title) + + " :caddfile + call setqflist([], 'f') + exe "caddfile Xerr" + call assert_equal(':caddfile Xerr', getqflist({'title' : 1}).title) + + " :grep + set grepprg=internal + exe "grep F1 Xerr" + call assert_equal(':grep F1 Xerr', getqflist({'title' : 1}).title) + + " :grepadd + call setqflist([], 'f') + exe "grepadd F1 Xerr" + call assert_equal(':grepadd F1 Xerr', getqflist({'title' : 1}).title) + set grepprg&vim + + " :vimgrep + exe "vimgrep F1 Xerr" + call assert_equal(':vimgrep F1 Xerr', getqflist({'title' : 1}).title) + + " :vimgrepadd + call setqflist([], 'f') + exe "vimgrepadd F1 Xerr" + call assert_equal(':vimgrepadd F1 Xerr', getqflist({'title' : 1}).title) + + call setqflist(['F1:10:L10'], ' ') + call assert_equal(':setqflist()', getqflist({'title' : 1}).title) + + call setqflist([], 'f') + call setqflist(['F1:10:L10'], 'a') + call assert_equal(':setqflist()', getqflist({'title' : 1}).title) + + call setqflist([], 'f') + call setqflist(['F1:10:L10'], 'r') + call assert_equal(':setqflist()', getqflist({'title' : 1}).title) + + close + call delete('Xerr') + + call setqflist([], ' ', {'title' : 'Errors'}) + copen + call assert_equal('Errors', w:quickfix_title) + call setqflist([], 'r', {'items' : [{'filename' : 'a.c', 'lnum' : 10}]}) + call assert_equal('Errors', w:quickfix_title) + cclose +endfunc + func Test_lbuffer_with_bwipe() new new @@ -3615,23 +3522,6 @@ func Test_lbuffer_with_bwipe() augroup END endfunc -" Tests for the ':filter /pat/ clist' command -func Test_filter_clist() - cexpr ['Xfile1:10:10:Line 10', 'Xfile2:15:15:Line 15'] - call assert_equal([' 2 Xfile2:15 col 15: Line 15'], - \ split(execute('filter /Line 15/ clist'), "\n")) - call assert_equal([' 1 Xfile1:10 col 10: Line 10'], - \ split(execute('filter /Xfile1/ clist'), "\n")) - call assert_equal([], split(execute('filter /abc/ clist'), "\n")) - - call setqflist([{'module' : 'abc', 'pattern' : 'pat1'}, - \ {'module' : 'pqr', 'pattern' : 'pat2'}], ' ') - call assert_equal([' 2 pqr:pat2: '], - \ split(execute('filter /pqr/ clist'), "\n")) - call assert_equal([' 1 abc:pat1: '], - \ split(execute('filter /pat1/ clist'), "\n")) -endfunc - " Test for an autocmd freeing the quickfix/location list when cexpr/lexpr is " running func Xexpr_acmd_freelist(cchar) @@ -3781,6 +3671,23 @@ func Test_autocmd_changelist() call Xautocmd_changelist('l') endfunc +" Tests for the ':filter /pat/ clist' command +func Test_filter_clist() + cexpr ['Xfile1:10:10:Line 10', 'Xfile2:15:15:Line 15'] + call assert_equal([' 2 Xfile2:15 col 15: Line 15'], + \ split(execute('filter /Line 15/ clist'), "\n")) + call assert_equal([' 1 Xfile1:10 col 10: Line 10'], + \ split(execute('filter /Xfile1/ clist'), "\n")) + call assert_equal([], split(execute('filter /abc/ clist'), "\n")) + + call setqflist([{'module' : 'abc', 'pattern' : 'pat1'}, + \ {'module' : 'pqr', 'pattern' : 'pat2'}], ' ') + call assert_equal([' 2 pqr:pat2: '], + \ split(execute('filter /pqr/ clist'), "\n")) + call assert_equal([' 1 abc:pat1: '], + \ split(execute('filter /pat1/ clist'), "\n")) +endfunc + " Tests for the "CTRL-W <CR>" command. func Xview_result_split_tests(cchar) call s:setup_commands(a:cchar) @@ -3864,6 +3771,20 @@ func Test_viscol() call delete('Xfile1') endfunc +" Test to make sure that an empty quickfix buffer is not reused for loading +" a normal buffer. +func Test_empty_qfbuf() + enew | only + call writefile(["Test"], 'Xfile1') + call setqflist([], 'f') + copen | only + let qfbuf = bufnr('') + edit Xfile1 + call assert_notequal(qfbuf, bufnr('')) + enew + call delete('Xfile1') +endfunc + " Test for the :cbelow, :cabove, :lbelow and :labove commands. func Xtest_below(cchar) call s:setup_commands(a:cchar) @@ -3970,4 +3891,92 @@ func Test_cbelow() call Xtest_below('l') endfunc +" Test for aborting quickfix commands using QuickFixCmdPre +func Xtest_qfcmd_abort(cchar) + call s:setup_commands(a:cchar) + + call g:Xsetlist([], 'f') + + " cexpr/lexpr + let e = '' + try + Xexpr ["F1:10:Line10", "F2:20:Line20"] + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + + " cfile/lfile + call writefile(["F1:10:Line10", "F2:20:Line20"], 'Xfile1') + let e = '' + try + Xfile Xfile1 + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + call delete('Xfile1') + + " cgetbuffer/lgetbuffer + enew! + call append(0, ["F1:10:Line10", "F2:20:Line20"]) + let e = '' + try + Xgetbuffer + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + enew! + + " vimgrep/lvimgrep + let e = '' + try + Xvimgrep /func/ test_quickfix.vim + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + + " helpgrep/lhelpgrep + let e = '' + try + Xhelpgrep quickfix + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + + " grep/lgrep + if has('unix') + let e = '' + try + silent Xgrep func test_quickfix.vim + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + endif +endfunc + +func Test_qfcmd_abort() + augroup QF_Test + au! + autocmd QuickFixCmdPre * throw "AbortCmd" + augroup END + + call Xtest_qfcmd_abort('c') + call Xtest_qfcmd_abort('l') + + augroup QF_Test + au! + augroup END +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index 298268a994..d4f58af10a 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -1,3 +1,16 @@ +" +" Tests for register operations +" + +" This test must be executed first to check for empty and unset registers. +func Test_aaa_empty_reg_test() + call assert_fails('normal @@', 'E748:') + call assert_fails('normal @%', 'E354:') + call assert_fails('normal @#', 'E354:') + call assert_fails('normal @!', 'E354:') + call assert_fails('normal @:', 'E30:') + call assert_fails('normal @.', 'E29:') +endfunc func Test_yank_shows_register() enew @@ -82,3 +95,76 @@ func Test_recording_esc_sequence() let &t_F2 = save_F2 endif endfunc + +" Test for executing the last used register (@) +func Test_last_used_exec_reg() + " Test for the @: command + let a = '' + call feedkeys(":let a ..= 'Vim'\<CR>", 'xt') + normal @: + call assert_equal('VimVim', a) + + " Test for the @= command + let x = '' + let a = ":let x ..= 'Vim'\<CR>" + exe "normal @=a\<CR>" + normal @@ + call assert_equal('VimVim', x) + + " Test for the @. command + let a = '' + call feedkeys("i:let a ..= 'Edit'\<CR>", 'xt') + normal @. + normal @@ + call assert_equal('EditEdit', a) + + enew! +endfunc + +func Test_get_register() + enew + edit Xfile1 + edit Xfile2 + call assert_equal('Xfile2', getreg('%')) + call assert_equal('Xfile1', getreg('#')) + + call feedkeys("iTwo\<Esc>", 'xt') + call assert_equal('Two', getreg('.')) + call assert_equal('', getreg('_')) + call assert_beeps('normal ":yy') + call assert_beeps('normal "%yy') + call assert_beeps('normal ".yy') + + call assert_equal('', getreg("\<C-F>")) + call assert_equal('', getreg("\<C-W>")) + call assert_equal('', getreg("\<C-L>")) + + call assert_equal('', getregtype('!')) + + enew! +endfunc + +func Test_set_register() + call assert_fails("call setreg('#', 200)", 'E86:') + + edit Xfile_alt_1 + let b1 = bufnr('') + edit Xfile_alt_2 + let b2 = bufnr('') + edit Xfile_alt_3 + let b3 = bufnr('') + call setreg('#', 'alt_1') + call assert_equal('Xfile_alt_1', getreg('#')) + call setreg('#', b2) + call assert_equal('Xfile_alt_2', getreg('#')) + + let ab = 'regwrite' + call setreg('=', '') + call setreg('=', 'a', 'a') + call setreg('=', 'b', 'a') + call assert_equal('regwrite', getreg('=')) + + enew! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 5d4c2a015f..68eb311e3c 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -57,7 +57,7 @@ func Test_search_cmdline() call feedkeys("/the".repeat("\<C-G>", 6)."\<cr>", 'tx') call assert_equal(' 8 them', getline('.')) :1 - " eigth match + " eighth match call feedkeys("/the".repeat("\<C-G>", 7)."\<cr>", 'tx') call assert_equal(' 9 these', getline('.')) :1 @@ -99,7 +99,7 @@ func Test_search_cmdline() call feedkeys("/the".repeat("\<C-G>", 6)."\<cr>", 'tx') call assert_equal(' 8 them', getline('.')) :1 - " eigth match + " eighth match call feedkeys("/the".repeat("\<C-G>", 7)."\<cr>", 'tx') call assert_equal(' 9 these', getline('.')) :1 diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index b86340a23a..48ec777ffd 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -347,3 +347,25 @@ func Test_statusline() set laststatus& set splitbelow& endfunc + +func Test_statusline_visual() + func CallWordcount() + call wordcount() + endfunc + new x1 + setl statusline=count=%{CallWordcount()} + " buffer must not be empty + call setline(1, 'hello') + + " window with more lines than x1 + new x2 + call setline(1, range(10)) + $ + " Visual mode in line below liast line in x1 should not give ml_get error + call feedkeys("\<C-V>", "xt") + redraw + + delfunc CallWordcount + bwipe! x1 + bwipe! x2 +endfunc diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index e209310a05..e94bd22cea 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -241,7 +241,7 @@ func Test_sub_cmd_3() call Run_SubCmd_Tests(tests) endfunc -" Test for submatch() on :substitue. +" Test for submatch() on :substitute. func Test_sub_cmd_4() set magic& set cpo& diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index f93af76f17..fe98ef1ae2 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -449,7 +449,8 @@ func Test_tag_line_toolong() call assert_report(v:exception) catch /.*/ endtry - call assert_equal('Ignoring long line in tags file', split(execute('messages'), '\n')[-1]) + call assert_equal('Searching tags file Xtags', split(execute('messages'), '\n')[-1]) + call writefile([ \ '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567 django/contrib/admin/templates/admin/edit_inline/stacked.html 16;" j line:16 language:HTML' \ ], 'Xtags') @@ -460,8 +461,26 @@ func Test_tag_line_toolong() call assert_report(v:exception) catch /.*/ endtry - call assert_equal('Ignoring long line in tags file', split(execute('messages'), '\n')[-1]) + call assert_equal('Searching tags file Xtags', split(execute('messages'), '\n')[-1]) + + " binary search works in file with long line + call writefile([ + \ 'asdfasfd nowhere 16', + \ 'foobar Xsomewhere 3; " 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567', + \ 'zasdfasfd nowhere 16', + \ ], 'Xtags') + call writefile([ + \ 'one', + \ 'two', + \ 'trhee', + \ 'four', + \ ], 'Xsomewhere') + tag foobar + call assert_equal('Xsomewhere', expand('%')) + call assert_equal(3, getcurpos()[1]) + call delete('Xtags') + call delete('Xsomewhere') set tags& let &verbose = old_vbs endfunc diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim index 448b2dc51c..b20c4df311 100644 --- a/src/nvim/testdir/test_textobjects.vim +++ b/src/nvim/testdir/test_textobjects.vim @@ -46,11 +46,18 @@ func Test_quote_selection_selection_exclusive() new call setline(1, "a 'bcde' f") set selection=exclusive + exe "norm! fdvhi'y" call assert_equal('bcde', @") + let @"='dummy' exe "norm! $gevi'y" call assert_equal('bcde', @") + + let @"='dummy' + exe "norm! 0fbhvi'y" + call assert_equal('bcde', @") + set selection&vim bw! endfunc diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim index 1e6b26a057..8f992f7501 100644 --- a/src/nvim/testdir/test_virtualedit.vim +++ b/src/nvim/testdir/test_virtualedit.vim @@ -82,3 +82,138 @@ func Test_edit_change() call assert_equal('x', getline(1)) bwipe! endfunc + +" Insert "keyword keyw", ESC, C CTRL-N, shows "keyword ykeyword". +" Repeating CTRL-N fixes it. (Mary Ellen Foster) +func Test_ve_completion() + new + set completeopt&vim + set virtualedit=all + exe "normal ikeyword keyw\<Esc>C\<C-N>" + call assert_equal('keyword keyword', getline(1)) + bwipe! + set virtualedit= +endfunc + +" Using "C" then then <CR> moves the last remaining character to the next +" line. (Mary Ellen Foster) +func Test_ve_del_to_eol() + new + set virtualedit=all + call append(0, 'all your base are belong to us') + call search('are', 'w') + exe "normal C\<CR>are belong to vim" + call assert_equal(['all your base ', 'are belong to vim'], getline(1, 2)) + bwipe! + set virtualedit= +endfunc + +" When past the end of a line that ends in a single character "b" skips +" that word. +func Test_ve_b_past_eol() + new + set virtualedit=all + call append(0, '1 2 3 4 5 6') + normal gg^$15lbC7 + call assert_equal('1 2 3 4 5 7', getline(1)) + bwipe! + set virtualedit= +endfunc + +" Make sure 'i', 'C', 'a', 'A' and 'D' works +func Test_ve_ins_del() + new + set virtualedit=all + call append(0, ["'i'", "'C'", "'a'", "'A'", "'D'"]) + call cursor(1, 1) + normal $4lix + call assert_equal("'i' x", getline(1)) + call cursor(2, 1) + normal $4lCx + call assert_equal("'C' x", getline(2)) + call cursor(3, 1) + normal $4lax + call assert_equal("'a' x", getline(3)) + call cursor(4, 1) + normal $4lAx + call assert_equal("'A'x", getline(4)) + call cursor(5, 1) + normal $4lDix + call assert_equal("'D' x", getline(5)) + bwipe! + set virtualedit= +endfunc + +" Test for yank bug reported by Mark Waggoner. +func Test_yank_block() + new + set virtualedit=block + call append(0, repeat(['this is a test'], 3)) + exe "normal gg^2w\<C-V>3jy" + call assert_equal("a\na\na\n ", @") + bwipe! + set virtualedit= +endfunc + +" Test "r" beyond the end of the line +func Test_replace_after_eol() + new + set virtualedit=all + call append(0, '"r"') + normal gg$5lrxa + call assert_equal('"r" x', getline(1)) + bwipe! + set virtualedit= +endfunc + +" Test "r" on a tab +" Note that for this test, 'ts' must be 8 (the default). +func Test_replace_on_tab() + new + set virtualedit=all + call append(0, "'r'\t") + normal gg^5lrxAy + call assert_equal("'r' x y", getline(1)) + bwipe! + set virtualedit= +endfunc + +" Test to make sure 'x' can delete control characters +func Test_ve_del_ctrl_chars() + new + set virtualedit=all + call append(0, "a\<C-V>b\<CR>sd") + set display=uhex + normal gg^xxxxxxi[text] + set display= + call assert_equal('[text]', getline(1)) + bwipe! + set virtualedit= +endfunc + +" Test for ^Y/^E due to bad w_virtcol value, reported by +" Roy <royl@netropolis.net>. +func Test_ins_copy_char() + new + set virtualedit=all + call append(0, 'abcv8efi.him2kl') + exe "normal gg^O\<Esc>3li\<C-E>\<Esc>4li\<C-E>\<Esc>4li\<C-E> <--" + exe "normal j^o\<Esc>4li\<C-Y>\<Esc>4li\<C-Y>\<Esc>4li\<C-Y> <--" + call assert_equal(' v i m <--', getline(1)) + call assert_equal(' 8 . 2 <--', getline(3)) + bwipe! + set virtualedit= +endfunc + +" Test for yanking and pasting using the small delete register +func Test_yank_paste_small_del_reg() + new + set virtualedit=all + call append(0, "foo, bar") + normal ggdewve"-p + call assert_equal(', foo', getline(1)) + bwipe! + set virtualedit= +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c index 14023ce2cb..03173afe07 100644 --- a/src/nvim/tui/terminfo.c +++ b/src/nvim/tui/terminfo.c @@ -47,9 +47,8 @@ bool terminfo_is_bsd_console(const char *term) // like cursor-shaping. Assume that TERM=xterm is degraded. #8644 return strequal(term, "xterm") && !!os_getenv("XTERM_VERSION"); # endif -#else - return false; #endif + return false; } /// Loads a built-in terminfo db when we (unibilium) failed to load a terminfo diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index 7d3ecfa0b8..e582d8f859 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -626,7 +626,7 @@ static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top, if (covered || curgrid->blending) { // TODO(bfredl): // 1. check if rectangles actually overlap - // 2. calulate subareas that can scroll. + // 2. calculate subareas that can scroll. compose_debug(top, bot, left, right, dbghl_recompose, true); for (int r = (int)(top + MAX(-rows, 0)); r < bot - MAX(rows, 0); r++) { // TODO(bfredl): workaround for win_update() performing two scrolls in a diff --git a/src/nvim/window.c b/src/nvim/window.c index 2a7578e33c..76fc36607c 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -633,6 +633,12 @@ void win_set_minimal_style(win_T *wp) xfree(wp->w_p_scl); wp->w_p_scl = (char_u *)xstrdup("auto"); } + + // colorcolumn: cleared + if (wp->w_p_cc != NULL && *wp->w_p_cc != NUL) { + xfree(wp->w_p_cc); + wp->w_p_cc = (char_u *)xstrdup(""); + } } void win_config_float(win_T *wp, FloatConfig fconfig) @@ -4366,9 +4372,10 @@ static void win_goto_hor(bool left, long count) } } -/* - * Make window "wp" the current window. - */ +/// Make window `wp` the current window. +/// +/// @warning Autocmds may close the window immediately, so caller must check +/// win_valid(wp). void win_enter(win_T *wp, bool undo_sync) { win_enter_ext(wp, undo_sync, false, false, true, true); diff --git a/test/README.md b/test/README.md index 2cee7da009..a6e9080a40 100644 --- a/test/README.md +++ b/test/README.md @@ -77,11 +77,14 @@ To run all legacy Vim tests: make oldtest -To run a *single* legacy test set `TEST_FILE`, for example: +To run a *single* legacy test file you can use either: - TEST_FILE=test_syntax.res make oldtest + make oldtest TEST_FILE=test_syntax.vim + +or: + + make src/nvim/testdir/test_syntax.vim -- The `.res` extension (instead of `.vim`) is required. - Specify only the test file name, not the full path. diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua index 773207d360..210394c83f 100644 --- a/test/functional/api/keymap_spec.lua +++ b/test/functional/api/keymap_spec.lua @@ -585,13 +585,13 @@ describe('nvim_set_keymap, nvim_del_keymap', function() end) it('can set <expr> mappings whose RHS change dynamically', function() - meths.command_output([[ + meths.exec([[ function! FlipFlop() abort if !exists('g:flip') | let g:flip = 0 | endif let g:flip = !g:flip return g:flip endfunction - ]]) + ]], true) eq(1, meths.call_function('FlipFlop', {})) eq(0, meths.call_function('FlipFlop', {})) eq(1, meths.call_function('FlipFlop', {})) diff --git a/test/functional/api/mark_extended_spec.lua b/test/functional/api/mark_extended_spec.lua index 76db9f9d81..bf910568b1 100644 --- a/test/functional/api/mark_extended_spec.lua +++ b/test/functional/api/mark_extended_spec.lua @@ -5,6 +5,7 @@ local request = helpers.request local eq = helpers.eq local ok = helpers.ok local curbufmeths = helpers.curbufmeths +local bufmeths = helpers.bufmeths local pcall_err = helpers.pcall_err local insert = helpers.insert local feed = helpers.feed @@ -36,7 +37,7 @@ local function get_extmarks(ns_id, start, end_, opts) return curbufmeths.get_extmarks(ns_id, start, end_, opts) end -describe('Extmarks buffer api', function() +describe('API/extmarks', function() local screen local marks, positions, ns_string2, ns_string, init_text, row, col local ns, ns2 @@ -153,26 +154,26 @@ describe('Extmarks buffer api', function() end -- next with mark id - rv = get_extmarks(ns, marks[1], {-1, -1}, {amount=1}) + rv = get_extmarks(ns, marks[1], {-1, -1}, {limit=1}) eq({{marks[1], positions[1][1], positions[1][2]}}, rv) - rv = get_extmarks(ns, marks[2], {-1, -1}, {amount=1}) + rv = get_extmarks(ns, marks[2], {-1, -1}, {limit=1}) eq({{marks[2], positions[2][1], positions[2][2]}}, rv) -- next with positional when mark exists at position - rv = get_extmarks(ns, positions[1], {-1, -1}, {amount=1}) + rv = get_extmarks(ns, positions[1], {-1, -1}, {limit=1}) eq({{marks[1], positions[1][1], positions[1][2]}}, rv) -- next with positional index (no mark at position) - rv = get_extmarks(ns, {positions[1][1], positions[1][2] +1}, {-1, -1}, {amount=1}) + rv = get_extmarks(ns, {positions[1][1], positions[1][2] +1}, {-1, -1}, {limit=1}) eq({{marks[2], positions[2][1], positions[2][2]}}, rv) -- next with Extremity index - rv = get_extmarks(ns, {0,0}, {-1, -1}, {amount=1}) + rv = get_extmarks(ns, {0,0}, {-1, -1}, {limit=1}) eq({{marks[1], positions[1][1], positions[1][2]}}, rv) -- nextrange with mark id rv = get_extmarks(ns, marks[1], marks[3]) eq({marks[1], positions[1][1], positions[1][2]}, rv[1]) eq({marks[2], positions[2][1], positions[2][2]}, rv[2]) - -- nextrange with amount - rv = get_extmarks(ns, marks[1], marks[3], {amount=2}) + -- nextrange with `limit` + rv = get_extmarks(ns, marks[1], marks[3], {limit=2}) eq(2, table.getn(rv)) -- nextrange with positional when mark exists at position rv = get_extmarks(ns, positions[1], positions[3]) @@ -196,18 +197,18 @@ describe('Extmarks buffer api', function() eq({{marks[3], positions[3][1], positions[3][2]}}, rv) -- prev with mark id - rv = get_extmarks(ns, marks[3], {0, 0}, {amount=1}) + rv = get_extmarks(ns, marks[3], {0, 0}, {limit=1}) eq({{marks[3], positions[3][1], positions[3][2]}}, rv) - rv = get_extmarks(ns, marks[2], {0, 0}, {amount=1}) + rv = get_extmarks(ns, marks[2], {0, 0}, {limit=1}) eq({{marks[2], positions[2][1], positions[2][2]}}, rv) -- prev with positional when mark exists at position - rv = get_extmarks(ns, positions[3], {0, 0}, {amount=1}) + rv = get_extmarks(ns, positions[3], {0, 0}, {limit=1}) eq({{marks[3], positions[3][1], positions[3][2]}}, rv) -- prev with positional index (no mark at position) - rv = get_extmarks(ns, {positions[1][1], positions[1][2] +1}, {0, 0}, {amount=1}) + rv = get_extmarks(ns, {positions[1][1], positions[1][2] +1}, {0, 0}, {limit=1}) eq({{marks[1], positions[1][1], positions[1][2]}}, rv) -- prev with Extremity index - rv = get_extmarks(ns, {-1,-1}, {0,0}, {amount=1}) + rv = get_extmarks(ns, {-1,-1}, {0,0}, {limit=1}) eq({{marks[3], positions[3][1], positions[3][2]}}, rv) -- prevrange with mark id @@ -215,8 +216,8 @@ describe('Extmarks buffer api', function() eq({marks[3], positions[3][1], positions[3][2]}, rv[1]) eq({marks[2], positions[2][1], positions[2][2]}, rv[2]) eq({marks[1], positions[1][1], positions[1][2]}, rv[3]) - -- prevrange with amount - rv = get_extmarks(ns, marks[3], marks[1], {amount=2}) + -- prevrange with limit + rv = get_extmarks(ns, marks[3], marks[1], {limit=2}) eq(2, table.getn(rv)) -- prevrange with positional when mark exists at position rv = get_extmarks(ns, positions[3], positions[1]) @@ -241,7 +242,7 @@ describe('Extmarks buffer api', function() eq({{marks[1], positions[1][1], positions[1][2]}}, rv) end) - it('querying for information with amount #extmarks', function() + it('querying for information with limit #extmarks', function() -- add some more marks for i, m in ipairs(marks) do if positions[i] ~= nil then @@ -250,19 +251,19 @@ describe('Extmarks buffer api', function() end end - local rv = get_extmarks(ns, {0, 0}, {-1, -1}, {amount=1}) + local rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=1}) eq(1, table.getn(rv)) - rv = get_extmarks(ns, {0, 0}, {-1, -1}, {amount=2}) + rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=2}) eq(2, table.getn(rv)) - rv = get_extmarks(ns, {0, 0}, {-1, -1}, {amount=3}) + rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=3}) eq(3, table.getn(rv)) -- now in reverse - rv = get_extmarks(ns, {0, 0}, {-1, -1}, {amount=1}) + rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=1}) eq(1, table.getn(rv)) - rv = get_extmarks(ns, {0, 0}, {-1, -1}, {amount=2}) + rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=2}) eq(2, table.getn(rv)) - rv = get_extmarks(ns, {0, 0}, {-1, -1}, {amount=3}) + rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=3}) eq(3, table.getn(rv)) end) @@ -295,9 +296,9 @@ describe('Extmarks buffer api', function() rv) end) - it('get_marks amount 0 returns nothing #extmarks', function() + it('get_marks limit=0 returns nothing #extmarks', function() set_extmark(ns, marks[1], positions[1][1], positions[1][2]) - local rv = get_extmarks(ns, {-1, -1}, {-1, -1}, {amount=0}) + local rv = get_extmarks(ns, {-1, -1}, {-1, -1}, {limit=0}) eq({}, rv) end) @@ -422,7 +423,7 @@ describe('Extmarks buffer api', function() set_extmark(ns, marks[1], 1, 2) -- Insert a fullwidth (two col) tilde, NICE feed('0i~<esc>') - check_undo_redo(ns, marks[1], 1, 2, 1, 3) + check_undo_redo(ns, marks[1], 1, 2, 1, 5) end) it('marks move with blockwise inserts #extmarks', function() @@ -475,6 +476,13 @@ describe('Extmarks buffer api', function() check_undo_redo(ns, marks[2], 0, 3, 1, 2) end) + it('deleting right before a mark works #extmarks', function() + -- op_delete in ops.c + set_extmark(ns, marks[1], 0, 2) + feed('0lx') + check_undo_redo(ns, marks[1], 0, 2, 0, 1) + end) + it('deleting on a mark works #extmarks', function() -- op_delete in ops.c set_extmark(ns, marks[1], 0, 2) @@ -499,7 +507,7 @@ describe('Extmarks buffer api', function() feed('0l3dl<esc>') check_undo_redo(ns, marks[1], 0, 2, 0, 1) check_undo_redo(ns, marks[2], 0, 3, 0, 1) - -- delete 1, nothing should happend to our marks + -- delete 1, nothing should happen to our marks feed('u') feed('$x') check_undo_redo(ns, marks[2], 0, 3, 0, 3) @@ -535,7 +543,7 @@ describe('Extmarks buffer api', function() check_undo_redo(ns, marks[1], 0, 1, 0, 0) check_undo_redo(ns, marks[2], 0, 3, 0, 0) check_undo_redo(ns, marks[3], 1, 2, 1, 0) - -- delete 1, nothing should happend to our marks + -- delete 1, nothing should happen to our marks feed('u') feed('$<c-v>jx') check_undo_redo(ns, marks[2], 0, 3, 0, 3) @@ -723,7 +731,7 @@ describe('Extmarks buffer api', function() -- Test updates feed('o<esc>') set_extmark(ns, marks[1], positions[1][1], positions[1][2]) - rv = get_extmarks(ns, marks[1], marks[1], {amount=1}) + rv = get_extmarks(ns, marks[1], marks[1], {limit=1}) eq(1, table.getn(rv)) feed("u") feed("<c-r>") @@ -764,23 +772,23 @@ describe('Extmarks buffer api', function() set_extmark(ns2, marks[2], positions[2][1], positions[2][2]) set_extmark(ns2, marks[3], positions[3][1], positions[3][2]) - -- get_next (amount set) - rv = get_extmarks(ns, {0, 0}, positions[2], {amount=1}) + -- get_next (limit set) + rv = get_extmarks(ns, {0, 0}, positions[2], {limit=1}) eq(1, table.getn(rv)) - rv = get_extmarks(ns2, {0, 0}, positions[2], {amount=1}) + rv = get_extmarks(ns2, {0, 0}, positions[2], {limit=1}) eq(1, table.getn(rv)) - -- get_prev (amount set) - rv = get_extmarks(ns, positions[1], {0, 0}, {amount=1}) + -- get_prev (limit set) + rv = get_extmarks(ns, positions[1], {0, 0}, {limit=1}) eq(1, table.getn(rv)) - rv = get_extmarks(ns2, positions[1], {0, 0}, {amount=1}) + rv = get_extmarks(ns2, positions[1], {0, 0}, {limit=1}) eq(1, table.getn(rv)) - -- get_next (amount not set) + -- get_next (no limit) rv = get_extmarks(ns, positions[1], positions[2]) eq(2, table.getn(rv)) rv = get_extmarks(ns2, positions[1], positions[2]) eq(2, table.getn(rv)) - -- get_prev (amount not set) + -- get_prev (no limit) rv = get_extmarks(ns, positions[2], positions[1]) eq(2, table.getn(rv)) rv = get_extmarks(ns2, positions[2], positions[1]) @@ -1243,7 +1251,7 @@ describe('Extmarks buffer api', function() eq("col value outside range", pcall_err(set_extmark, ns, marks[1], 0, invalid_col)) end) - it('when line > line_count, throw error #extmarks', function() + it('fails when line > line_count #extmarks', function() local invalid_col = init_text:len() + 1 local invalid_lnum = 3 eq('line value outside range', pcall_err(set_extmark, ns, marks[1], invalid_lnum, invalid_col)) @@ -1251,10 +1259,9 @@ describe('Extmarks buffer api', function() end) it('bug from check_col in extmark_set #extmarks_sub', function() - -- This bug was caused by extmark_set always using - -- check_col. check_col always uses the current buffer. - -- This wasn't working during undo so we now use - -- check_col and check_lnum only when they are required. + -- This bug was caused by extmark_set always using check_col. check_col + -- always uses the current buffer. This wasn't working during undo so we + -- now use check_col and check_lnum only when they are required. feed('A<cr>67890<cr>xx<esc>') feed('A<cr>12345<cr>67890<cr>xx<esc>') set_extmark(ns, marks[1], 3, 4) @@ -1268,6 +1275,13 @@ describe('Extmarks buffer api', function() local id = set_extmark(ns, 0, 0, 2) eq({{id, 0, 2}}, get_extmarks(ns,0, -1)) end) + + it('can set a mark to other buffer', function() + local buf = request('nvim_create_buf', 0, 1) + request('nvim_buf_set_lines', buf, 0, -1, 1, {"", ""}) + local id = bufmeths.set_extmark(buf, ns, 0, 1, 0, {}) + eq({{id, 1, 0}}, bufmeths.get_extmarks(buf, ns, 0, -1, {})) + end) end) describe('Extmarks buffer api with many marks', function() diff --git a/test/functional/api/proc_spec.lua b/test/functional/api/proc_spec.lua index 063d382790..d828bdf948 100644 --- a/test/functional/api/proc_spec.lua +++ b/test/functional/api/proc_spec.lua @@ -10,7 +10,7 @@ local request = helpers.request local retry = helpers.retry local NIL = helpers.NIL -describe('api', function() +describe('API', function() before_each(clear) describe('nvim_get_proc_children', function() diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 8b77dbcaa6..d901a5e2eb 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -16,6 +16,8 @@ local parse_context = helpers.parse_context local request = helpers.request local source = helpers.source local next_msg = helpers.next_msg +local tmpname = helpers.tmpname +local write_file = helpers.write_file local pcall_err = helpers.pcall_err local format_string = helpers.format_string @@ -74,9 +76,127 @@ describe('API', function() eq({mode='i', blocking=false}, nvim("get_mode")) end) + describe('nvim_exec', function() + it('one-line input', function() + nvim('exec', "let x1 = 'a'", false) + eq('a', nvim('get_var', 'x1')) + end) + + it(':verbose set {option}?', function() + nvim('exec', 'set nowrap', false) + eq('nowrap\n\tLast set from anonymous :source', + nvim('exec', 'verbose set wrap?', true)) + end) + + it('multiline input', function() + -- Heredoc + empty lines. + nvim('exec', "let x2 = 'a'\n", false) + eq('a', nvim('get_var', 'x2')) + nvim('exec','lua <<EOF\n\n\n\ny=3\n\n\nEOF', false) + eq(3, nvim('eval', "luaeval('y')")) + + eq('', nvim('exec', 'lua <<EOF\ny=3\nEOF', false)) + eq(3, nvim('eval', "luaeval('y')")) + + -- Multiple statements + nvim('exec', 'let x1=1\nlet x2=2\nlet x3=3\n', false) + eq(1, nvim('eval', 'x1')) + eq(2, nvim('eval', 'x2')) + eq(3, nvim('eval', 'x3')) + + -- Functions + nvim('exec', 'function Foo()\ncall setline(1,["xxx"])\nendfunction', false) + eq(nvim('get_current_line'), '') + nvim('exec', 'call Foo()', false) + eq(nvim('get_current_line'), 'xxx') + + -- Autocmds + nvim('exec','autocmd BufAdd * :let x1 = "Hello"', false) + nvim('command', 'new foo') + eq('Hello', request('nvim_eval', 'g:x1')) + end) + + it('non-ASCII input', function() + nvim('exec', [=[ + new + exe "normal! i ax \n Ax " + :%s/ax/--a1234--/g | :%s/Ax/--A1234--/g + ]=], false) + nvim('command', '1') + eq(' --a1234-- ', nvim('get_current_line')) + nvim('command', '2') + eq(' --A1234-- ', nvim('get_current_line')) + + nvim('exec', [[ + new + call setline(1,['xxx']) + call feedkeys('r') + call feedkeys('ñ', 'xt') + ]], false) + eq('ñxx', nvim('get_current_line')) + end) + + it('execution error', function() + eq('Vim:E492: Not an editor command: bogus_command', + pcall_err(request, 'nvim_exec', 'bogus_command', false)) + eq('', nvim('eval', 'v:errmsg')) -- v:errmsg was not updated. + eq('', eval('v:exception')) + + eq('Vim(buffer):E86: Buffer 23487 does not exist', + pcall_err(request, 'nvim_exec', 'buffer 23487', false)) + eq('', eval('v:errmsg')) -- v:errmsg was not updated. + eq('', eval('v:exception')) + end) + + it('recursion', function() + local fname = tmpname() + write_file(fname, 'let x1 = "set from :source file"\n') + -- nvim_exec + -- :source + -- nvim_exec + request('nvim_exec', [[ + let x2 = substitute('foo','o','X','g') + let x4 = 'should be overwritten' + call nvim_exec("source ]]..fname..[[\nlet x3 = substitute('foo','foo','set by recursive nvim_exec','g')\nlet x5='overwritten'\nlet x4=x5\n", v:false) + ]], false) + eq('set from :source file', request('nvim_get_var', 'x1')) + eq('fXX', request('nvim_get_var', 'x2')) + eq('set by recursive nvim_exec', request('nvim_get_var', 'x3')) + eq('overwritten', request('nvim_get_var', 'x4')) + eq('overwritten', request('nvim_get_var', 'x5')) + os.remove(fname) + end) + + it('traceback', function() + local fname = tmpname() + write_file(fname, 'echo "hello"\n') + local sourcing_fname = tmpname() + write_file(sourcing_fname, 'call nvim_exec("source '..fname..'", v:false)\n') + meths.exec('set verbose=2', false) + local traceback_output = 'line 0: sourcing "'..sourcing_fname..'"\n'.. + 'line 0: sourcing "'..fname..'"\n'.. + 'hello\n'.. + 'finished sourcing '..fname..'\n'.. + 'continuing in nvim_exec() called at '..sourcing_fname..':1\n'.. + 'finished sourcing '..sourcing_fname..'\n'.. + 'continuing in nvim_exec() called at nvim_exec():0' + eq(traceback_output, + meths.exec('call nvim_exec("source '..sourcing_fname..'", v:false)', true)) + os.remove(fname) + os.remove(sourcing_fname) + end) + + it('returns output', function() + eq('this is spinal tap', + nvim('exec', 'lua <<EOF\n\n\nprint("this is spinal tap")\n\n\nEOF', true)) + eq('', nvim('exec', 'echo', true)) + eq('foo 42', nvim('exec', 'echo "foo" 42', true)) + end) + end) + describe('nvim_command', function() it('works', function() - local fname = helpers.tmpname() + local fname = tmpname() nvim('command', 'new') nvim('command', 'edit '..fname) nvim('command', 'normal itesting\napi') @@ -313,41 +433,44 @@ describe('API', function() end) end) - describe('nvim_execute_lua', function() + describe('nvim_exec_lua', function() it('works', function() - meths.execute_lua('vim.api.nvim_set_var("test", 3)', {}) + meths.exec_lua('vim.api.nvim_set_var("test", 3)', {}) eq(3, meths.get_var('test')) - eq(17, meths.execute_lua('a, b = ...\nreturn a + b', {10,7})) + eq(17, meths.exec_lua('a, b = ...\nreturn a + b', {10,7})) - eq(NIL, meths.execute_lua('function xx(a,b)\nreturn a..b\nend',{})) + eq(NIL, meths.exec_lua('function xx(a,b)\nreturn a..b\nend',{})) + eq("xy", meths.exec_lua('return xx(...)', {'x','y'})) + + -- Deprecated name: nvim_execute_lua. eq("xy", meths.execute_lua('return xx(...)', {'x','y'})) end) it('reports errors', function() eq([[Error loading lua: [string "<nvim>"]:1: '=' expected near '+']], - pcall_err(meths.execute_lua, 'a+*b', {})) + pcall_err(meths.exec_lua, 'a+*b', {})) eq([[Error loading lua: [string "<nvim>"]:1: unexpected symbol near '1']], - pcall_err(meths.execute_lua, '1+2', {})) + pcall_err(meths.exec_lua, '1+2', {})) eq([[Error loading lua: [string "<nvim>"]:1: unexpected symbol]], - pcall_err(meths.execute_lua, 'aa=bb\0', {})) + pcall_err(meths.exec_lua, 'aa=bb\0', {})) eq([[Error executing lua: [string "<nvim>"]:1: attempt to call global 'bork' (a nil value)]], - pcall_err(meths.execute_lua, 'bork()', {})) + pcall_err(meths.exec_lua, 'bork()', {})) eq('Error executing lua: [string "<nvim>"]:1: did\nthe\nfail', - pcall_err(meths.execute_lua, 'error("did\\nthe\\nfail")', {})) + pcall_err(meths.exec_lua, 'error("did\\nthe\\nfail")', {})) end) it('uses native float values', function() - eq(2.5, meths.execute_lua("return select(1, ...)", {2.5})) - eq("2.5", meths.execute_lua("return vim.inspect(...)", {2.5})) + eq(2.5, meths.exec_lua("return select(1, ...)", {2.5})) + eq("2.5", meths.exec_lua("return vim.inspect(...)", {2.5})) -- "special" float values are still accepted as return values. - eq(2.5, meths.execute_lua("return vim.api.nvim_eval('2.5')", {})) - eq("{\n [false] = 2.5,\n [true] = 3\n}", meths.execute_lua("return vim.inspect(vim.api.nvim_eval('2.5'))", {})) + eq(2.5, meths.exec_lua("return vim.api.nvim_eval('2.5')", {})) + eq("{\n [false] = 2.5,\n [true] = 3\n}", meths.exec_lua("return vim.inspect(vim.api.nvim_eval('2.5'))", {})) end) end) @@ -453,7 +576,7 @@ describe('API', function() eq({0,3,14,0}, funcs.getpos('.')) end) it('vim.paste() failure', function() - nvim('execute_lua', 'vim.paste = (function(lines, phase) error("fake fail") end)', {}) + nvim('exec_lua', 'vim.paste = (function(lines, phase) error("fake fail") end)', {}) eq([[Error executing lua: [string "<nvim>"]:1: fake fail]], pcall_err(request, 'nvim_paste', 'line 1\nline 2\nline 3', false, 1)) end) @@ -677,7 +800,7 @@ describe('API', function() ok(nil ~= string.find(rv, 'noequalalways\n'.. '\tLast set from API client %(channel id %d+%)')) - nvim('execute_lua', 'vim.api.nvim_set_option("equalalways", true)', {}) + nvim('exec_lua', 'vim.api.nvim_set_option("equalalways", true)', {}) status, rv = pcall(nvim, 'command_output', 'verbose set equalalways?') eq(true, status) @@ -1680,7 +1803,7 @@ describe('API', function() eq(' 1 %a "[No Name]" line 1\n'.. ' 3 h "[Scratch]" line 0\n'.. ' 4 h "[Scratch]" line 0', - meths.command_output("ls")) + meths.exec('ls', true)) -- current buffer didn't change eq({id=1}, meths.get_current_buf()) diff --git a/test/functional/autocmd/cmdline_spec.lua b/test/functional/autocmd/cmdline_spec.lua index 51b7b819e9..8ec06dc148 100644 --- a/test/functional/autocmd/cmdline_spec.lua +++ b/test/functional/autocmd/cmdline_spec.lua @@ -33,7 +33,7 @@ describe('cmdline autocommands', function() eq({'notification', 'CmdlineEnter', {{cmdtype=':', cmdlevel=1}}}, next_msg()) -- note: feed('bork<c-c>') might not consume 'bork' - -- due to out-of-band interupt handling + -- due to out-of-band interrupt handling feed('bork<esc>') eq({'notification', 'CmdlineLeave', {{cmdtype=':', cmdlevel=1, abort=true}}}, next_msg()) diff --git a/test/functional/autocmd/tabclose_spec.lua b/test/functional/autocmd/tabclose_spec.lua index b7c33dc3d8..92d860c628 100644 --- a/test/functional/autocmd/tabclose_spec.lua +++ b/test/functional/autocmd/tabclose_spec.lua @@ -11,10 +11,10 @@ describe('TabClosed', function() repeat nvim('command', 'tabnew') until nvim('eval', 'tabpagenr()') == 6 -- current tab is now 6 - eq("tabclosed:6:6:5", nvim('command_output', 'tabclose')) -- close last 6, current tab is now 5 - eq("tabclosed:5:5:4", nvim('command_output', 'close')) -- close last window on tab, closes tab - eq("tabclosed:2:2:3", nvim('command_output', '2tabclose')) -- close tab 2, current tab is now 3 - eq("tabclosed:1:1:2\ntabclosed:1:1:1", nvim('command_output', 'tabonly')) -- close tabs 1 and 2 + eq("tabclosed:6:6:5", nvim('exec', 'tabclose', true)) -- close last 6, current tab is now 5 + eq("tabclosed:5:5:4", nvim('exec', 'close', true)) -- close last window on tab, closes tab + eq("tabclosed:2:2:3", nvim('exec', '2tabclose', true)) -- close tab 2, current tab is now 3 + eq("tabclosed:1:1:2\ntabclosed:1:1:1", nvim('exec', 'tabonly', true)) -- close tabs 1 and 2 end) it('is triggered when closing a window via bdelete from another tab', function() @@ -23,7 +23,7 @@ describe('TabClosed', function() nvim('command', '1tabedit Xtestfile') nvim('command', 'normal! 1gt') eq({1, 3}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) - eq("tabclosed:2:2:1\ntabclosed:2:2:1", nvim('command_output', 'bdelete Xtestfile')) + eq("tabclosed:2:2:1\ntabclosed:2:2:1", nvim('exec', 'bdelete Xtestfile', true)) eq({1, 1}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) end) @@ -35,7 +35,7 @@ describe('TabClosed', function() -- Only one tab is closed, and the alternate file is used for the other. eq({2, 3}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) - eq("tabclosed:2:2:2", nvim('command_output', 'bdelete Xtestfile2')) + eq("tabclosed:2:2:2", nvim('exec', 'bdelete Xtestfile2', true)) eq('Xtestfile1', nvim('eval', 'bufname("")')) end) end) @@ -48,9 +48,9 @@ describe('TabClosed', function() nvim('command', 'tabnew') until nvim('eval', 'tabpagenr()') == 7 -- current tab is now 7 -- sanity check, we shouldn't match on tabs with numbers other than 2 - eq("tabclosed:7:7:6", nvim('command_output', 'tabclose')) + eq("tabclosed:7:7:6", nvim('exec', 'tabclose', true)) -- close tab page 2, current tab is now 5 - eq("tabclosed:2:2:5\ntabclosed:match", nvim('command_output', '2tabclose')) + eq("tabclosed:2:2:5\ntabclosed:match", nvim('exec', '2tabclose', true)) end) end) @@ -59,7 +59,7 @@ describe('TabClosed', function() nvim('command', 'au! TabClosed * echom "tabclosed:".expand("<afile>").":".expand("<amatch>").":".tabpagenr()') nvim('command', 'tabedit Xtestfile') eq({2, 2}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) - eq("tabclosed:2:2:1", nvim('command_output', 'close')) + eq("tabclosed:2:2:1", nvim('exec', 'close', true)) eq({1, 1}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) end) end) diff --git a/test/functional/autocmd/tabnewentered_spec.lua b/test/functional/autocmd/tabnewentered_spec.lua index 59cac07b34..32e341184d 100644 --- a/test/functional/autocmd/tabnewentered_spec.lua +++ b/test/functional/autocmd/tabnewentered_spec.lua @@ -7,15 +7,15 @@ describe('TabNewEntered', function() it('matches when entering any new tab', function() clear() nvim('command', 'au! TabNewEntered * echom "tabnewentered:".tabpagenr().":".bufnr("")') - eq("tabnewentered:2:2", nvim('command_output', 'tabnew')) - eq("tabnewentered:3:3", nvim('command_output', 'tabnew test.x2')) + eq("tabnewentered:2:2", nvim('exec', 'tabnew', true)) + eq("tabnewentered:3:3", nvim('exec', 'tabnew test.x2', true)) end) end) describe('with FILE as <afile>', function() it('matches when opening a new tab for FILE', function() nvim('command', 'au! TabNewEntered Xtest-tabnewentered echom "tabnewentered:match"') eq('tabnewentered:4:4\ntabnewentered:match', - nvim('command_output', 'tabnew Xtest-tabnewentered')) + nvim('exec', 'tabnew Xtest-tabnewentered', true)) end) end) describe('with CTRL-W T', function() @@ -24,7 +24,7 @@ describe('TabNewEntered', function() nvim('command', 'au! TabNewEntered * echom "entered"') nvim('command', 'tabnew test.x2') nvim('command', 'split') - eq('entered', nvim('command_output', 'execute "normal \\<C-W>T"')) + eq('entered', nvim('exec', 'execute "normal \\<C-W>T"', true)) end) end) end) diff --git a/test/functional/cmdline/ctrl_r_spec.lua b/test/functional/cmdline/ctrl_r_spec.lua index d2dad23e98..a0f3955282 100644 --- a/test/functional/cmdline/ctrl_r_spec.lua +++ b/test/functional/cmdline/ctrl_r_spec.lua @@ -15,7 +15,7 @@ describe('cmdline CTRL-R', function() -- <CR> inserted between lines, NOT after the final line. eq('line1abc\rline2somemoretext', funcs.getcmdline()) - -- Yank 2 lines characterwise, then paste to cmdline. + -- Yank 2 lines charwise, then paste to cmdline. feed([[<C-\><C-N>gg05lyvj:<C-R>0]]) -- <CR> inserted between lines, NOT after the final line. eq('abc\rline2', funcs.getcmdline()) diff --git a/test/functional/cmdline/history_spec.lua b/test/functional/cmdline/history_spec.lua index 20f9cf06a2..ee2d36f642 100644 --- a/test/functional/cmdline/history_spec.lua +++ b/test/functional/cmdline/history_spec.lua @@ -6,7 +6,7 @@ describe('history support code', function() before_each(clear) it('correctly clears start of the history', function() - -- Regression test: check absense of the memory leak when clearing start of + -- Regression test: check absence of the memory leak when clearing start of -- the history using ex_getln.c/clr_history(). eq(1, funcs.histadd(':', 'foo')) eq(1, funcs.histdel(':')) @@ -14,7 +14,7 @@ describe('history support code', function() end) it('correctly clears end of the history', function() - -- Regression test: check absense of the memory leak when clearing end of + -- Regression test: check absence of the memory leak when clearing end of -- the history using ex_getln.c/clr_history(). meths.set_option('history', 1) eq(1, funcs.histadd(':', 'foo')) diff --git a/test/functional/ex_cmds/dict_notifications_spec.lua b/test/functional/ex_cmds/dict_notifications_spec.lua index 48e7e05e4c..5c67431221 100644 --- a/test/functional/ex_cmds/dict_notifications_spec.lua +++ b/test/functional/ex_cmds/dict_notifications_spec.lua @@ -357,4 +357,18 @@ describe('VimL dictionary notifications', function() eq(2, eval('1+1')) -- Still alive? end) + it('does not cause use-after-free when unletting from callback', function() + source([[ + let g:called = 0 + function W(...) abort + unlet g:d + let g:called = 1 + endfunction + let g:d = {} + call dictwatcheradd(g:d, '*', function('W')) + let g:d.foo = 123 + ]]) + eq(1, eval('g:called')) + end) + end) diff --git a/test/functional/ex_cmds/script_spec.lua b/test/functional/ex_cmds/script_spec.lua index 4e57d2755d..0a772c559b 100644 --- a/test/functional/ex_cmds/script_spec.lua +++ b/test/functional/ex_cmds/script_spec.lua @@ -22,7 +22,7 @@ describe('script_get-based command', function() %s %s endif ]])):format(cmd, garbage))) - eq('', meths.command_output('messages')) + eq('', meths.exec('messages', true)) if check_neq then neq(0, exc_exec(dedent([[ %s %s @@ -37,7 +37,7 @@ describe('script_get-based command', function() EOF endif ]])):format(cmd, garbage))) - eq('', meths.command_output('messages')) + eq('', meths.exec('messages', true)) if check_neq then eq(true, pcall(source, (dedent([[ let g:exc = 0 diff --git a/test/functional/ex_cmds/sign_spec.lua b/test/functional/ex_cmds/sign_spec.lua index 9d08a66625..891cfe1670 100644 --- a/test/functional/ex_cmds/sign_spec.lua +++ b/test/functional/ex_cmds/sign_spec.lua @@ -16,8 +16,8 @@ describe('sign', function() nvim('command', 'sign place 34 line=3 name=Foo buffer='..buf2) -- now unplace without specifying a buffer nvim('command', 'sign unplace 34') - eq("--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf1)) - eq("--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf2)) + eq("--- Signs ---\n", nvim('exec', 'sign place buffer='..buf1, true)) + eq("--- Signs ---\n", nvim('exec', 'sign place buffer='..buf2, true)) end) end) end) diff --git a/test/functional/example_spec.lua b/test/functional/example_spec.lua index 883fe4ba63..f07f88b2b6 100644 --- a/test/functional/example_spec.lua +++ b/test/functional/example_spec.lua @@ -3,7 +3,10 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') -local clear, feed = helpers.clear, helpers.feed +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local feed = helpers.feed describe('example', function() local screen @@ -33,4 +36,37 @@ describe('example', function() | ]]) end) + + it('override UI event-handler', function() + -- Example: override the "tabline_update" UI event handler. + -- + -- screen.lua defines default handlers for UI events, but tests + -- may sometimes want to override a handler. + + -- The UI must declare that it wants to handle the UI events. + -- For this example, we enable `ext_tabline`: + screen:detach() + screen = Screen.new(25, 5) + screen:attach({rgb=true, ext_tabline=true}) + + -- From ":help ui" we find that `tabline_update` receives `curtab` and + -- `tabs` objects. So we declare the UI handler like this: + local event_tabs, event_curtab + function screen:_handle_tabline_update(curtab, tabs) + event_curtab, event_tabs = curtab, tabs + end + + -- Create a tabpage... + command('tabedit foo') + + -- Use screen:expect{condition=…} to check the result. + screen:expect{condition=function() + eq({ id = 2 }, event_curtab) + eq({ + {tab = { id = 1 }, name = '[No Name]'}, + {tab = { id = 2 }, name = 'foo'}, + }, + event_tabs) + end} + end) end) diff --git a/test/functional/fixtures/lsp-test-rpc-server.lua b/test/functional/fixtures/lsp-test-rpc-server.lua index 971e61b072..798883ced0 100644 --- a/test/functional/fixtures/lsp-test-rpc-server.lua +++ b/test/functional/fixtures/lsp-test-rpc-server.lua @@ -170,7 +170,7 @@ function tests.basic_check_buffer_open() expect_notification('textDocument/didOpen', { textDocument = { languageId = ""; - text = table.concat({"testing"; "123"}, "\n"); + text = table.concat({"testing"; "123"}, "\n") .. '\n'; uri = "file://"; version = 0; }; @@ -197,6 +197,42 @@ function tests.basic_check_buffer_open_and_change() expect_notification('textDocument/didOpen', { textDocument = { languageId = ""; + text = table.concat({"testing"; "123"}, "\n") .. '\n'; + uri = "file://"; + version = 0; + }; + }) + expect_notification('textDocument/didChange', { + textDocument = { + uri = "file://"; + version = 3; + }; + contentChanges = { + { text = table.concat({"testing"; "boop"}, "\n") .. '\n'; }; + } + }) + expect_notification("finish") + notify('finish') + end; + } +end + +function tests.basic_check_buffer_open_and_change_noeol() + skeleton { + on_init = function(params) + local expected_capabilities = protocol.make_client_capabilities() + assert_eq(params.capabilities, expected_capabilities) + return { + capabilities = { + textDocumentSync = protocol.TextDocumentSyncKind.Full; + } + } + end; + body = function() + notify('start') + expect_notification('textDocument/didOpen', { + textDocument = { + languageId = ""; text = table.concat({"testing"; "123"}, "\n"); uri = "file://"; version = 0; @@ -216,7 +252,6 @@ function tests.basic_check_buffer_open_and_change() end; } end - function tests.basic_check_buffer_open_and_change_multi() skeleton { on_init = function(params) @@ -233,7 +268,7 @@ function tests.basic_check_buffer_open_and_change_multi() expect_notification('textDocument/didOpen', { textDocument = { languageId = ""; - text = table.concat({"testing"; "123"}, "\n"); + text = table.concat({"testing"; "123"}, "\n") .. '\n'; uri = "file://"; version = 0; }; @@ -244,7 +279,7 @@ function tests.basic_check_buffer_open_and_change_multi() version = 3; }; contentChanges = { - { text = table.concat({"testing"; "321"}, "\n"); }; + { text = table.concat({"testing"; "321"}, "\n") .. '\n'; }; } }) expect_notification('textDocument/didChange', { @@ -253,7 +288,7 @@ function tests.basic_check_buffer_open_and_change_multi() version = 4; }; contentChanges = { - { text = table.concat({"testing"; "boop"}, "\n"); }; + { text = table.concat({"testing"; "boop"}, "\n") .. '\n'; }; } }) expect_notification("finish") @@ -278,7 +313,7 @@ function tests.basic_check_buffer_open_and_change_multi_and_close() expect_notification('textDocument/didOpen', { textDocument = { languageId = ""; - text = table.concat({"testing"; "123"}, "\n"); + text = table.concat({"testing"; "123"}, "\n") .. '\n'; uri = "file://"; version = 0; }; @@ -289,7 +324,7 @@ function tests.basic_check_buffer_open_and_change_multi_and_close() version = 3; }; contentChanges = { - { text = table.concat({"testing"; "321"}, "\n"); }; + { text = table.concat({"testing"; "321"}, "\n") .. '\n'; }; } }) expect_notification('textDocument/didChange', { @@ -298,7 +333,7 @@ function tests.basic_check_buffer_open_and_change_multi_and_close() version = 4; }; contentChanges = { - { text = table.concat({"testing"; "boop"}, "\n"); }; + { text = table.concat({"testing"; "boop"}, "\n") .. '\n'; }; } }) expect_notification('textDocument/didClose', { @@ -328,7 +363,7 @@ function tests.basic_check_buffer_open_and_change_incremental() expect_notification('textDocument/didOpen', { textDocument = { languageId = ""; - text = table.concat({"testing"; "123"}, "\n"); + text = table.concat({"testing"; "123"}, "\n") .. '\n'; uri = "file://"; version = 0; }; diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 9fcd1cf7b1..97248973da 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -706,7 +706,7 @@ module.curwinmeths = module.create_callindex(module.curwin) module.curtabmeths = module.create_callindex(module.curtab) function module.exec_lua(code, ...) - return module.meths.execute_lua(code, {...}) + return module.meths.exec_lua(code, {...}) end function module.redir_exec(cmd) diff --git a/test/functional/lua/overrides_spec.lua b/test/functional/lua/overrides_spec.lua index 8c260632d9..1bccc02847 100644 --- a/test/functional/lua/overrides_spec.lua +++ b/test/functional/lua/overrides_spec.lua @@ -299,14 +299,11 @@ describe('package.path/package.cpath', function() end return new_paths end - local function execute_lua(cmd, ...) - return meths.execute_lua(cmd, {...}) - end local function eval_lua(expr, ...) - return meths.execute_lua('return ' .. expr, {...}) + return meths.exec_lua('return '..expr, {...}) end local function set_path(which, value) - return execute_lua('package[select(1, ...)] = select(2, ...)', which, value) + return exec_lua('package[select(1, ...)] = select(2, ...)', which, value) end it('contains directories from &runtimepath on first invocation', function() diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 028f2dcd52..22c975147f 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -117,6 +117,34 @@ describe('lua stdlib', function() eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0B\\0")')) end) + it('vim.startswith', function() + eq(true, funcs.luaeval('vim.startswith("123", "1")')) + eq(true, funcs.luaeval('vim.startswith("123", "")')) + eq(true, funcs.luaeval('vim.startswith("123", "123")')) + eq(true, funcs.luaeval('vim.startswith("", "")')) + + eq(false, funcs.luaeval('vim.startswith("123", " ")')) + eq(false, funcs.luaeval('vim.startswith("123", "2")')) + eq(false, funcs.luaeval('vim.startswith("123", "1234")')) + + eq("string", type(pcall_err(funcs.luaeval, 'vim.startswith("123", nil)'))) + eq("string", type(pcall_err(funcs.luaeval, 'vim.startswith(nil, "123")'))) + end) + + it('vim.endswith', function() + eq(true, funcs.luaeval('vim.endswith("123", "3")')) + eq(true, funcs.luaeval('vim.endswith("123", "")')) + eq(true, funcs.luaeval('vim.endswith("123", "123")')) + eq(true, funcs.luaeval('vim.endswith("", "")')) + + eq(false, funcs.luaeval('vim.endswith("123", " ")')) + eq(false, funcs.luaeval('vim.endswith("123", "2")')) + eq(false, funcs.luaeval('vim.endswith("123", "1234")')) + + eq("string", type(pcall_err(funcs.luaeval, 'vim.endswith("123", nil)'))) + eq("string", type(pcall_err(funcs.luaeval, 'vim.endswith(nil, "123")'))) + end) + it("vim.str_utfindex/str_byteindex", function() exec_lua([[_G.test_text = "xy åäö ɧ 汉语 ↥ 🤦x🦄 å بِيَّ"]]) local indicies32 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,29,33,34,35,37,38,40,42,44,46,48} @@ -353,10 +381,14 @@ describe('lua stdlib', function() it('vim.list_extend', function() eq({1,2,3}, exec_lua [[ return vim.list_extend({1}, {2,3}) ]]) - eq('Error executing lua: .../shared.lua: src must be a table', + eq('Error executing lua: .../shared.lua: src: expected table, got nil', pcall_err(exec_lua, [[ return vim.list_extend({1}, nil) ]])) eq({1,2}, exec_lua [[ return vim.list_extend({1}, {2;a=1}) ]]) eq(true, exec_lua [[ local a = {1} return vim.list_extend(a, {2;a=1}) == a ]]) + eq({2}, exec_lua [[ return vim.list_extend({}, {2;a=1}, 1) ]]) + eq({}, exec_lua [[ return vim.list_extend({}, {2;a=1}, 2) ]]) + eq({}, exec_lua [[ return vim.list_extend({}, {2;a=1}, 1, -1) ]]) + eq({2}, exec_lua [[ return vim.list_extend({}, {2;a=1}, -1, 2) ]]) end) it('vim.tbl_add_reverse_lookup', function() @@ -549,4 +581,78 @@ describe('lua stdlib', function() eq(false, exec_lua("return vim.is_callable('foo')")) eq(false, exec_lua("return vim.is_callable({})")) end) + + it('vim.g', function() + exec_lua [[ + vim.api.nvim_set_var("testing", "hi") + vim.api.nvim_set_var("other", 123) + ]] + eq('hi', funcs.luaeval "vim.g.testing") + eq(123, funcs.luaeval "vim.g.other") + eq(NIL, funcs.luaeval "vim.g.nonexistant") + end) + + it('vim.env', function() + exec_lua [[ + vim.fn.setenv("A", 123) + ]] + eq('123', funcs.luaeval "vim.env.A") + eq(true, funcs.luaeval "vim.env.B == nil") + end) + + it('vim.v', function() + eq(funcs.luaeval "vim.api.nvim_get_vvar('progpath')", funcs.luaeval "vim.v.progpath") + eq(false, funcs.luaeval "vim.v['false']") + eq(NIL, funcs.luaeval "vim.v.null") + end) + + it('vim.bo', function() + eq('', funcs.luaeval "vim.bo.filetype") + exec_lua [[ + vim.api.nvim_buf_set_option(0, "filetype", "markdown") + BUF = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_option(BUF, "modifiable", false) + ]] + eq(false, funcs.luaeval "vim.bo.modified") + eq('markdown', funcs.luaeval "vim.bo.filetype") + eq(false, funcs.luaeval "vim.bo[BUF].modifiable") + exec_lua [[ + vim.bo.filetype = '' + vim.bo[BUF].modifiable = true + ]] + eq('', funcs.luaeval "vim.bo.filetype") + eq(true, funcs.luaeval "vim.bo[BUF].modifiable") + matches("^Error executing lua: .*: Invalid option name: 'nosuchopt'$", + pcall_err(exec_lua, 'return vim.bo.nosuchopt')) + matches("^Error executing lua: .*: Expected lua string$", + pcall_err(exec_lua, 'return vim.bo[0][0].autoread')) + end) + + it('vim.wo', function() + eq('', funcs.luaeval "vim.bo.filetype") + exec_lua [[ + vim.api.nvim_win_set_option(0, "cole", 2) + BUF = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_option(BUF, "modifiable", false) + ]] + eq(2, funcs.luaeval "vim.wo.cole") + exec_lua [[ + vim.wo.conceallevel = 0 + vim.bo[BUF].modifiable = true + ]] + eq(0, funcs.luaeval "vim.wo.cole") + matches("^Error executing lua: .*: Invalid option name: 'notanopt'$", + pcall_err(exec_lua, 'return vim.wo.notanopt')) + matches("^Error executing lua: .*: Expected lua string$", + pcall_err(exec_lua, 'return vim.wo[0][0].list')) + end) + + it('vim.cmd', function() + exec_lua [[ + vim.cmd "autocmd BufNew * ++once lua BUF = vim.fn.expand('<abuf>')" + vim.cmd "new" + ]] + eq('2', funcs.luaeval "BUF") + eq(2, funcs.luaeval "#vim.api.nvim_list_bufs()") + end) end) diff --git a/test/functional/normal/put_spec.lua b/test/functional/normal/put_spec.lua index 40a4f051e3..357fafec44 100644 --- a/test/functional/normal/put_spec.lua +++ b/test/functional/normal/put_spec.lua @@ -307,7 +307,7 @@ describe('put command', function() -- }}} -- Conversion functions {{{ - local function convert_characterwise(expect_base, conversion_table, + local function convert_charwise(expect_base, conversion_table, virtualedit_end, visual_put) expect_base = dedent(expect_base) -- There is no difference between 'P' and 'p' when VIsual_active @@ -335,7 +335,7 @@ describe('put command', function() expect_base = expect_base:gsub('(test_stringx?)"', '%1.') end return expect_base - end -- convert_characterwise() + end -- convert_charwise() local function make_back(string) local prev_line @@ -500,7 +500,7 @@ describe('put command', function() local function run_normal_mode_tests(test_string, base_map, extra_setup, virtualedit_end, selection_string) local function convert_closure(e, c) - return convert_characterwise(e, c, virtualedit_end, selection_string) + return convert_charwise(e, c, virtualedit_end, selection_string) end local function expect_normal_creator(expect_base, conversion_table) local test_expect = expect_creator(convert_closure, expect_base, conversion_table) diff --git a/test/functional/options/chars_spec.lua b/test/functional/options/chars_spec.lua index 3453e79429..5439ca3dba 100644 --- a/test/functional/options/chars_spec.lua +++ b/test/functional/options/chars_spec.lua @@ -67,16 +67,29 @@ describe("'fillchars'", function() shouldfail('eob:xy') -- two ascii chars shouldfail('eob:\255', 'eob:<ff>') -- invalid UTF-8 end) - it('is local to window', function() - clear() - screen = Screen.new(50, 5) - screen:attach() + it('has global value', function() + screen:try_resize(50, 5) insert("foo\nbar") command('set laststatus=0') command('1,2fold') command('vsplit') command('set fillchars=fold:x') screen:expect([[ + ^+-- 2 lines: fooxxxxxxxx│+-- 2 lines: fooxxxxxxx| + ~ │~ | + ~ │~ | + ~ │~ | + | + ]]) + end) + it('has local window value', function() + screen:try_resize(50, 5) + insert("foo\nbar") + command('set laststatus=0') + command('1,2fold') + command('vsplit') + command('setl fillchars=fold:x') + screen:expect([[ ^+-- 2 lines: fooxxxxxxxx│+-- 2 lines: foo·······| ~ │~ | ~ │~ | @@ -96,12 +109,25 @@ describe("'listchars'", function() screen:attach() end) - it('is local to window', function() + it('has global value', function() + feed('i<tab><tab><tab><esc>') + command('set list laststatus=0') + command('vsplit') + command('set listchars=tab:<->') + screen:expect([[ + <------><------>^<------> │<------><------><------>| + ~ │~ | + ~ │~ | + ~ │~ | + | + ]]) + end) + it('has value local to window', function() feed('i<tab><tab><tab><esc>') - command('set laststatus=0') - command('set list listchars=tab:<->') + command('set list laststatus=0') + command('setl listchars=tab:<->') command('vsplit') - command('set listchars&') + command('setl listchars<') screen:expect([[ > > ^> │<------><------><------>| ~ │~ | diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index 490a04186d..57e5077989 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -224,9 +224,6 @@ describe('startup defaults', function() XDG_DATA_HOME=xdgdir, NVIM_LOG_FILE='', -- Empty is invalid. }}) - -- server_start() calls ELOG, which tickles log_path_init(). - pcall(command, 'call serverstart(serverlist()[0])') - eq(xdgdir..'/'..datasubdir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/')) end) it('defaults to stdpath("data")/log if invalid', function() @@ -235,9 +232,6 @@ describe('startup defaults', function() XDG_DATA_HOME=xdgdir, NVIM_LOG_FILE='.', -- Any directory is invalid. }}) - -- server_start() calls ELOG, which tickles log_path_init(). - pcall(command, 'call serverstart(serverlist()[0])') - eq(xdgdir..'/'..datasubdir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/')) end) it('defaults to .nvimlog if stdpath("data") is invalid', function() @@ -245,9 +239,6 @@ describe('startup defaults', function() XDG_DATA_HOME='Xtest-missing-xdg-dir', NVIM_LOG_FILE='.', -- Any directory is invalid. }}) - -- server_start() calls ELOG, which tickles log_path_init(). - pcall(command, 'call serverstart(serverlist()[0])') - eq('.nvimlog', eval('$NVIM_LOG_FILE')) end) end) diff --git a/test/functional/options/keymap_spec.lua b/test/functional/options/keymap_spec.lua index 7f6d623dc7..52a714f7a8 100644 --- a/test/functional/options/keymap_spec.lua +++ b/test/functional/options/keymap_spec.lua @@ -30,7 +30,7 @@ describe("'keymap' / :lmap", function() command('lmapclear <buffer>') command('set keymap=dvorak') command('set nomore') - local bindings = funcs.nvim_command_output('lmap') + local bindings = funcs.nvim_exec('lmap', true) eq(dedent([[ l " @_ diff --git a/test/functional/plugin/lsp/lsp_spec.lua b/test/functional/plugin/lsp/lsp_spec.lua index cd0974b81c..c38c9b72ce 100644 --- a/test/functional/plugin/lsp/lsp_spec.lua +++ b/test/functional/plugin/lsp/lsp_spec.lua @@ -410,6 +410,54 @@ describe('Language Client API', function() } end) + it('should check the body and didChange full with noeol', function() + local expected_callbacks = { + {NIL, "shutdown", {}, 1}; + {NIL, "finish", {}, 1}; + {NIL, "start", {}, 1}; + } + local client + test_rpc_server { + test_name = "basic_check_buffer_open_and_change_noeol"; + on_setup = function() + exec_lua [[ + BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { + "testing"; + "123"; + }) + vim.api.nvim_buf_set_option(BUFFER, 'eol', false) + ]] + end; + on_init = function(_client) + client = _client + local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + eq(full_kind, client.resolved_capabilities().text_document_did_change) + eq(true, client.resolved_capabilities().text_document_open_close) + exec_lua [[ + assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) + ]] + end; + on_exit = function(code, signal) + eq(0, code, "exit code") eq(0, signal, "exit signal") + end; + on_callback = function(err, method, params, client_id) + if method == 'start' then + exec_lua [[ + vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { + "boop"; + }) + ]] + client.notify('finish') + end + eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback") + if method == 'finish' then + client.stop() + end + end; + } + end) + -- TODO(askhan) we don't support full for now, so we can disable these tests. pending('should check the body and didChange incremental', function() local expected_callbacks = { diff --git a/test/functional/plugin/lsp/util_spec.lua b/test/functional/plugin/lsp/util_spec.lua new file mode 100644 index 0000000000..1cf0e48be4 --- /dev/null +++ b/test/functional/plugin/lsp/util_spec.lua @@ -0,0 +1,76 @@ +local helpers = require('test.functional.helpers')(after_each) +local eq = helpers.eq +local exec_lua = helpers.exec_lua +local dedent = helpers.dedent +local insert = helpers.insert +local clear = helpers.clear + +describe('LSP util', function() + local test_text = dedent([[ + First line of text + Second line of text + Third line of text + Fourth line of text]]) + + local function reset() + clear() + insert(test_text) + end + + before_each(reset) + + local function make_edit(y_0, x_0, y_1, x_1, text) + return { + range = { + start = { line = y_0, character = x_0 }; + ["end"] = { line = y_1, character = x_1 }; + }; + newText = type(text) == 'table' and table.concat(text, '\n') or (text or ""); + } + end + + local function buf_lines(bufnr) + return exec_lua("return vim.api.nvim_buf_get_lines((...), 0, -1, false)", bufnr) + end + + describe('apply_edits', function() + it('should apply simple edits', function() + local edits = { + make_edit(0, 0, 0, 0, {"123"}); + make_edit(1, 0, 1, 1, {"2"}); + make_edit(2, 0, 2, 2, {"3"}); + } + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + eq({ + '123First line of text'; + '2econd line of text'; + '3ird line of text'; + 'Fourth line of text'; + }, buf_lines(1)) + end) + + it('should apply complex edits', function() + local edits = { + make_edit(0, 0, 0, 0, {"", "12"}); + make_edit(0, 0, 0, 0, {"3", "foo"}); + make_edit(0, 1, 0, 1, {"bar", "123"}); + make_edit(0, #"First ", 0, #"First line of text", {"guy"}); + make_edit(1, 0, 1, #'Second', {"baz"}); + make_edit(2, #'Th', 2, #"Third", {"e next"}); + make_edit(3, #'', 3, #"Fourth", {"another line of text", "before this"}); + make_edit(3, #'Fourth', 3, #"Fourth line of text", {"!"}); + } + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + eq({ + ''; + '123'; + 'fooFbar'; + '123irst guy'; + 'baz line of text'; + 'The next line of text'; + 'another line of text'; + 'before this!'; + }, buf_lines(1)) + end) + end) +end) diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 7560b0e872..d40baca871 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -5,6 +5,7 @@ local wait = helpers.wait local eval, feed_command, source = helpers.eval, helpers.feed_command, helpers.source local eq, neq = helpers.eq, helpers.neq local write_file = helpers.write_file +local command= helpers.command describe(':terminal buffer', function() local screen @@ -59,7 +60,7 @@ describe(':terminal buffer', function() end) it('does not create swap files', function() - local swapfile = nvim('command_output', 'swapname'):gsub('\n', '') + local swapfile = nvim('exec', 'swapname', true):gsub('\n', '') eq(nil, io.open(swapfile)) end) @@ -224,6 +225,22 @@ describe(':terminal buffer', function() neq('terminal', eval('&buftype')) end) end) + + it('it works with set rightleft #11438', function() + local columns = eval('&columns') + feed(string.rep('a', columns)) + command('set rightleft') + screen:expect([[ + ydaer ytt| + {1:a}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + | + | + | + | + {3:-- TERMINAL --} | + ]]) + command('bdelete!') + end) end) describe('No heap-buffer-overflow when using', function() diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 676d6ef76d..077e9dc7d5 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -486,7 +486,7 @@ describe('TUI', function() end) it('paste: recovers from vim.paste() failure', function() - child_session:request('nvim_execute_lua', [[ + child_session:request('nvim_exec_lua', [[ _G.save_paste_fn = vim.paste vim.paste = function(lines, phase) error("fake fail") end ]], {}) @@ -544,7 +544,7 @@ describe('TUI', function() {3:-- TERMINAL --} | ]]} -- Paste works if vim.paste() succeeds. - child_session:request('nvim_execute_lua', [[ + child_session:request('nvim_exec_lua', [[ vim.paste = _G.save_paste_fn ]], {}) feed_data('\027[200~line A\nline B\n\027[201~') @@ -563,7 +563,7 @@ describe('TUI', function() it('paste: vim.paste() cancel (retval=false) #10865', function() -- This test only exercises the "cancel" case. Use-case would be "dangling -- paste", but that is not implemented yet. #10865 - child_session:request('nvim_execute_lua', [[ + child_session:request('nvim_exec_lua', [[ vim.paste = function(lines, phase) return false end ]], {}) feed_data('\027[200~line A\nline B\n\027[201~') diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua index 65c5f67726..f589bb0e83 100644 --- a/test/functional/ui/bufhl_spec.lua +++ b/test/functional/ui/bufhl_spec.lua @@ -217,6 +217,161 @@ describe('Buffer highlighting', function() | ]]) end) + + it('and adjusting columns', function() + -- insert before + feed('ggiquite <esc>') + screen:expect{grid=[[ + quite^ a {5:longer} example | + in {6:order} to {7:de}{5:monstr}{7:ate} | + {7:combin}{8:ing}{9: hi}ghlights | + {9:from }{8:diff}{7:erent} sources | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + feed('u') + screen:expect{grid=[[ + ^a {5:longer} example | + in {6:order} to {7:de}{5:monstr}{7:ate} | + {7:combin}{8:ing}{9: hi}ghlights | + {9:from }{8:diff}{7:erent} sources | + {1:~ }| + {1:~ }| + {1:~ }| + 1 change; before #2 {MATCH:.*}| + ]]} + + -- change/insert in the middle + feed('+fesAAAA') + screen:expect{grid=[[ + a {5:longer} example | + in {6:ordAAAA^r} to {7:de}{5:monstr}{7:ate} | + {7:combin}{8:ing}{9: hi}ghlights | + {9:from }{8:diff}{7:erent} sources | + {1:~ }| + {1:~ }| + {1:~ }| + {7:-- INSERT --} | + ]]} + + feed('<esc>tdD') + screen:expect{grid=[[ + a {5:longer} example | + in {6:ordAAAAr} t^o | + {7:combin}{8:ing}{9: hi}ghlights | + {9:from }{8:diff}{7:erent} sources | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + feed('u') + screen:expect{grid=[[ + a {5:longer} example | + in {6:ordAAAAr} to^ demonstrate | + {7:combin}{8:ing}{9: hi}ghlights | + {9:from }{8:diff}{7:erent} sources | + {1:~ }| + {1:~ }| + {1:~ }| + 1 change; before #4 {MATCH:.*}| + ]]} + + feed('u') + screen:expect{grid=[[ + a {5:longer} example | + in {6:ord^er} to demonstrate | + {7:combin}{8:ing}{9: hi}ghlights | + {9:from }{8:diff}{7:erent} sources | + {1:~ }| + {1:~ }| + {1:~ }| + 1 change; before #3 {MATCH:.*}| + ]]} + end) + + it('and joining lines', function() + feed('ggJJJ') + screen:expect{grid=[[ + a {5:longer} example in {6:order} to {7:de}{5:monstr}{7:ate}| + {7: combin}{8:ing hi}{7:ghlights^ }{8:from diff}{7:erent sou}| + {7:rces} | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + -- TODO(bfredl): perhaps better undo + feed('uuu') + screen:expect{grid=[[ + ^a longer example | + in order to demonstrate | + combining highlights | + from different sources | + {1:~ }| + {1:~ }| + {1:~ }| + 1 more line; before #2 {MATCH:.*}| + ]]} + end) + + it('and splitting lines', function() + feed('2Gtti<cr>') + screen:expect{grid=[[ + a {5:longer} example | + in {6:order} | + ^ to {7:de}{5:monstr}{7:ate} | + {7:combin}{8:ing}{9: hi}ghlights | + {9:from }{8:diff}{7:erent} sources | + {1:~ }| + {1:~ }| + {7:-- INSERT --} | + ]]} + + -- TODO(bfredl): keep both "parts" after split, requires proper extmark ranges + feed('<esc>tsi<cr>') + screen:expect{grid=[[ + a {5:longer} example | + in {6:order} | + to {7:de}{5:mo} | + ^nstrate | + {7:combin}{8:ing}{9: hi}ghlights | + {9:from }{8:diff}{7:erent} sources | + {1:~ }| + {7:-- INSERT --} | + ]]} + + -- TODO(bfredl): perhaps better undo + feed('<esc>u') + screen:expect{grid=[[ + a {5:longer} example | + in {6:order} | + to demo{7:^nstrat}{8:e} | + {7:combin}{8:ing}{9: hi}ghlights | + {9:from }{8:diff}{7:erent} sources | + {1:~ }| + {1:~ }| + 1 line less; before #3 {MATCH:.*}| + ]]} + + feed('<esc>u') + screen:expect{grid=[[ + a {5:longer} example | + in order^ to demonstrate | + {7:combin}{8:ing}{9: hi}ghlights | + {9:from }{8:diff}{7:erent} sources | + {1:~ }| + {1:~ }| + {1:~ }| + 1 line less; before #2 {MATCH:.*}| + ]]} + end) end) it('prioritizes latest added highlight', function() diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua index 052414a43d..9c746b99bd 100644 --- a/test/functional/ui/cmdline_highlight_spec.lua +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -362,7 +362,7 @@ describe('Command-line coloring', function() {EOB:~ }| :e^ | ]]) - eq('', meths.command_output('messages')) + eq('', meths.exec('messages', true)) end) it('silences :echon', function() set_color_cb('Echoning') @@ -377,7 +377,7 @@ describe('Command-line coloring', function() {EOB:~ }| :e^ | ]]) - eq('', meths.command_output('messages')) + eq('', meths.exec('messages', true)) end) it('silences :echomsg', function() set_color_cb('Echomsging') @@ -392,7 +392,7 @@ describe('Command-line coloring', function() {EOB:~ }| :e^ | ]]) - eq('', meths.command_output('messages')) + eq('', meths.exec('messages', true)) end) it('does the right thing when throwing', function() set_color_cb('Throwing') @@ -857,7 +857,7 @@ describe('Ex commands coloring', function() ]]) feed('<CR>') eq('Error detected while processing :\nE605: Exception not caught: 42\nE749: empty buffer', - meths.command_output('messages')) + meths.exec('messages', true)) end) it('errors out when failing to get callback', function() meths.set_var('Nvim_color_cmdline', 42) diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index c2354103c2..21c01b3458 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -775,7 +775,7 @@ local function test_cmdline(linegrid) }}} -- This used to send an invalid event where pos where larger than the total - -- lenght of content. Checked in _handle_cmdline_show. + -- length of content. Checked in _handle_cmdline_show. feed('<esc>') screen:expect([[ ^ | diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index dbaf6f802b..7a5569c14b 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -2,9 +2,11 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local os = require('os') local clear, feed = helpers.clear, helpers.feed +local assert_alive = helpers.assert_alive local command, feed_command = helpers.command, helpers.feed_command local eval = helpers.eval local eq = helpers.eq +local exec_lua = helpers.exec_lua local insert = helpers.insert local meths = helpers.meths local curbufmeths = helpers.curbufmeths @@ -12,7 +14,7 @@ local funcs = helpers.funcs local run = helpers.run local pcall_err = helpers.pcall_err -describe('floating windows', function() +describe('floatwin', function() before_each(function() clear() end) @@ -39,6 +41,7 @@ describe('floating windows', function() [19] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray}, [20] = {bold = true, foreground = Screen.colors.Brown}, [21] = {background = Screen.colors.Gray90}, + [22] = {background = Screen.colors.LightRed}, } it('behavior', function() @@ -55,6 +58,31 @@ describe('floating windows', function() eq(1000, funcs.win_getid()) end) + it('closed immediately by autocmd #11383', function() + eq('Error executing lua: [string "<nvim>"]:4: Window was closed immediately', + pcall_err(exec_lua, [[ + local a = vim.api + local function crashes(contents) + local buf = a.nvim_create_buf(false, true) + local floatwin = a.nvim_open_win(buf, true, { + relative = 'cursor'; + style = 'minimal'; + row = 0; col = 0; + height = #contents; + width = 10; + }) + a.nvim_buf_set_lines(buf, 0, -1, true, contents) + local winnr = vim.fn.win_id2win(floatwin) + a.nvim_command('wincmd p') + a.nvim_command('autocmd CursorMoved * ++once '..winnr..'wincmd c') + return buf, floatwin + end + crashes{'foo'} + crashes{'bar'} + ]])) + assert_alive() + end) + local function with_ext_multigrid(multigrid) local screen before_each(function() @@ -398,6 +426,7 @@ describe('floating windows', function() it("can use 'minimal' style", function() command('set number') command('set signcolumn=yes') + command('set colorcolumn=1') command('set cursorline') command('set foldcolumn=1') command('hi NormalFloat guibg=#333333') @@ -414,9 +443,9 @@ describe('floating windows', function() [2:----------------------------------------]| [3:----------------------------------------]| ## grid 2 - {19: }{20: 1 }{21:^x }| - {19: }{14: 2 }y | - {19: }{14: 3 } | + {19: }{20: 1 }{22:^x}{21: }| + {19: }{14: 2 }{22:y} | + {19: }{14: 3 }{22: } | {0:~ }| {0:~ }| {0:~ }| @@ -430,9 +459,9 @@ describe('floating windows', function() ]], float_pos={[4] = {{id = 1001}, "NW", 1, 4, 10, true}}} else screen:expect{grid=[[ - {19: }{20: 1 }{21:^x }| - {19: }{14: 2 }y | - {19: }{14: 3 } {15:x } | + {19: }{20: 1 }{22:^x}{21: }| + {19: }{14: 2 }{22:y} | + {19: }{14: 3 }{22: } {15:x } | {0:~ }{15:y }{0: }| {0:~ }{15: }{0: }| {0:~ }{15: }{0: }| @@ -454,9 +483,9 @@ describe('floating windows', function() [2:----------------------------------------]| [3:----------------------------------------]| ## grid 2 - {19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{21:^x }| - {19: }{14: 2 }y | - {19: }{14: 3 } | + {19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }| + {19: }{14: 2 }{22:y} | + {19: }{14: 3 }{22: } | {0:~ }| {0:~ }| {0:~ }| @@ -471,9 +500,9 @@ describe('floating windows', function() else screen:expect([[ - {19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{21:^x }| - {19: }{14: 2 }y | - {19: }{14: 3 } {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x } | + {19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }| + {19: }{14: 2 }{22:y} | + {19: }{14: 3 }{22: } {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x } | {0:~ }{19: }{15:y }{0: }| {0:~ }{19: }{15: }{0: }| {0:~ }{15: }{0: }| @@ -495,9 +524,9 @@ describe('floating windows', function() [2:----------------------------------------]| [3:----------------------------------------]| ## grid 2 - {19: }{20: 1 }{21:^x }| - {19: }{14: 2 }y | - {19: }{14: 3 } | + {19: }{20: 1 }{22:^x}{21: }| + {19: }{14: 2 }{22:y} | + {19: }{14: 3 }{22: } | {0:~ }| {0:~ }| {0:~ }| @@ -511,9 +540,9 @@ describe('floating windows', function() ]], float_pos={[4] = {{id = 1001}, "NW", 1, 4, 10, true}}} else screen:expect([[ - {19: }{20: 1 }{21:^x }| - {19: }{14: 2 }y | - {19: }{14: 3 } {15: } | + {19: }{20: 1 }{22:^x}{21: }| + {19: }{14: 2 }{22:y} | + {19: }{14: 3 }{22: } {15: } | {0:~ }{15: }{0: }| {0:~ }{15: }{0: }| {0:~ }{15: }{0: }| diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 40ea030f73..25b38b1feb 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -861,7 +861,7 @@ describe('ui/builtin messages', function() -- screen size doesn't affect internal output #10285 eq('ErrorMsg xxx ctermfg=15 ctermbg=1 guifg=White guibg=Red', - meths.command_output("hi ErrorMsg")) + meths.exec("hi ErrorMsg", true)) end) it(':syntax list langGroup output', function() @@ -900,7 +900,7 @@ vimComment xxx match /\s"[^\-:.%#=*].*$/ms=s+1,lc=1 excludenl contains=@vim match /\<endif\s\+".*$/ms=s+5,lc=5 contains=@vimCommentGroup,vimCommentString match /\<else\s\+".*$/ms=s+4,lc=4 contains=@vimCommentGroup,vimCommentString links to Comment]], - meths.command_output('syntax list vimComment')) + meths.exec('syntax list vimComment', true)) -- luacheck: pop end) diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index 7840ba9167..d857b57a31 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -12,7 +12,10 @@ describe('ui/mouse/input', function() clear() meths.set_option('mouse', 'a') meths.set_option('list', true) - meths.set_option('listchars', 'eol:$') + -- NB: this is weird, but mostly irrelevant to the test + -- So I didn't bother to change it + command('set listchars=eol:$') + command('setl listchars=nbsp:x') screen = Screen.new(25, 5) screen:attach() screen:set_default_attr_ids({ @@ -812,7 +815,8 @@ describe('ui/mouse/input', function() feed_command('set concealcursor=ni') feed_command('set nowrap') - feed_command('set shiftwidth=2 tabstop=4 list listchars=tab:>-') + feed_command('set shiftwidth=2 tabstop=4 list') + feed_command('setl listchars=tab:>-') feed_command('syntax match NonText "\\*" conceal') feed_command('syntax match NonText "cats" conceal cchar=X') feed_command('syntax match NonText "x" conceal cchar=>') diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua index ea71f5eae9..581e196bbb 100644 --- a/test/functional/ui/options_spec.lua +++ b/test/functional/ui/options_spec.lua @@ -5,7 +5,7 @@ local command = helpers.command local eq = helpers.eq local shallowcopy = helpers.shallowcopy -describe('ui receives option updates', function() +describe('UI receives option updates', function() local screen local function reset(opts, ...) @@ -47,6 +47,33 @@ describe('ui receives option updates', function() end) end) + it('on attach #11372', function() + clear() + local evs = {} + screen = Screen.new(20,5) + -- Override mouse_on/mouse_off handlers. + function screen:_handle_mouse_on() + table.insert(evs, 'mouse_on') + end + function screen:_handle_mouse_off() + table.insert(evs, 'mouse_off') + end + screen:attach() + screen:expect(function() + eq({'mouse_off'}, evs) + end) + command("set mouse=nvi") + screen:expect(function() + eq({'mouse_off','mouse_on'}, evs) + end) + screen:detach() + eq({'mouse_off','mouse_on'}, evs) + screen:attach() + screen:expect(function() + eq({'mouse_off','mouse_on','mouse_on'}, evs) + end) + end) + it("when setting options", function() local expected = reset() local defaults = shallowcopy(expected) diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index d3f78bf77b..64f784afe3 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -606,17 +606,12 @@ function Screen:_redraw(updates) for i = 2, #update do local handler_name = '_handle_'..method local handler = self[handler_name] - if handler ~= nil then - local status, res = pcall(handler, self, unpack(update[i])) - if not status then - error(handler_name..' failed' - ..'\n payload: '..inspect(update) - ..'\n error: '..tostring(res)) - end - else - assert(self._on_event, - "Add Screen:"..handler_name.." or call Screen:set_on_event_handler") - self._on_event(method, update[i]) + assert(handler ~= nil, "missing handler: Screen:"..handler_name) + local status, res = pcall(handler, self, unpack(update[i])) + if not status then + error(handler_name..' failed' + ..'\n payload: '..inspect(update) + ..'\n error: '..tostring(res)) end end if k == #updates and method == "flush" then @@ -626,10 +621,6 @@ function Screen:_redraw(updates) return did_flush end -function Screen:set_on_event_handler(callback) - self._on_event = callback -end - function Screen:_handle_resize(width, height) self:_handle_grid_resize(1, width, height) self._scroll_region = { diff --git a/test/functional/ui/searchhl_spec.lua b/test/functional/ui/searchhl_spec.lua index 486de02a09..635ce7392b 100644 --- a/test/functional/ui/searchhl_spec.lua +++ b/test/functional/ui/searchhl_spec.lua @@ -442,7 +442,7 @@ describe('search highlighting', function() feed_command("call matchadd('MyGroup', 'special')") feed_command("call matchadd('MyGroup2', 'text', 0)") - -- searchhl and matchadd matches are exclusive, only the higest priority + -- searchhl and matchadd matches are exclusive, only the highest priority -- is used (and matches with lower priorities are not combined) feed_command("/ial te") screen:expect([[ diff --git a/test/functional/ui/tabline_spec.lua b/test/functional/ui/tabline_spec.lua index 0ee7e03fac..23aae81745 100644 --- a/test/functional/ui/tabline_spec.lua +++ b/test/functional/ui/tabline_spec.lua @@ -10,11 +10,9 @@ describe('ui/ext_tabline', function() clear() screen = Screen.new(25, 5) screen:attach({rgb=true, ext_tabline=true}) - screen:set_on_event_handler(function(name, data) - if name == "tabline_update" then - event_curtab, event_tabs = unpack(data) - end - end) + function screen:_handle_tabline_update(curtab, tabs) + event_curtab, event_tabs = curtab, tabs + end end) it('publishes UI events', function() diff --git a/test/helpers.lua b/test/helpers.lua index 3f29a28c0d..98f003f208 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -99,6 +99,9 @@ function module.pcall_err(fn, ...) -- to this: -- Error executing lua: .../foo.lua:186: Expected string, got number errmsg = errmsg:gsub([[lua: [a-zA-Z]?:?[^:]-[/\]([^:/\]+):%d+: ]], 'lua: .../%1: ') + -- Compiled modules will not have a path and will just be a name like + -- shared.lua:186, so strip the number. + errmsg = errmsg:gsub([[lua: ([^:/\ ]+):%d+: ]], 'lua: .../%1: ') -- ^ Windows drive-letter (C:) return errmsg end diff --git a/test/unit/os/env_spec.lua b/test/unit/os/env_spec.lua index c543551607..e24a389d69 100644 --- a/test/unit/os/env_spec.lua +++ b/test/unit/os/env_spec.lua @@ -121,7 +121,7 @@ describe('env.c', function() local name = 'NVIM_UNIT_TEST_GETENV_1N' local value = 'NVIM_UNIT_TEST_GETENV_1V' eq(NULL, os_getenv(name)) - -- Use os_setenv because Lua dosen't have setenv. + -- Use os_setenv because Lua doesn't have setenv. os_setenv(name, value, 1) eq(value, os_getenv(name)) diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index fdeb7f342f..90ec475894 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -153,14 +153,14 @@ set(LUAJIT_SHA256 ad5077bd861241bf5e50ae4bf543d291c5fcffab95ccc3218401131f503e45 set(LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz) set(LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333) -set(LUAROCKS_URL https://github.com/luarocks/luarocks/archive/v2.4.4.tar.gz) -set(LUAROCKS_SHA256 9eb3d0738fd02ad8bf39bcedccac4e83e9b5fff2bcca247c3584b925b2075d9c) +set(LUAROCKS_URL https://github.com/luarocks/luarocks/archive/v3.2.1.tar.gz) +set(LUAROCKS_SHA256 0cab9f79311083f33e4d8f5a76021604f1d3f7141ce9a2ef1d8b717d92058370) set(UNIBILIUM_URL https://github.com/neovim/unibilium/archive/92d929f.tar.gz) set(UNIBILIUM_SHA256 29815283c654277ef77a3adcc8840db79ddbb20a0f0b0c8f648bd8cd49a02e4b) -set(LIBTERMKEY_URL http://www.leonerd.org.uk/code/libtermkey/libtermkey-0.21.1.tar.gz) -set(LIBTERMKEY_SHA256 cecbf737f35d18f433c8d7864f63c0f878af41f8bd0255a3ebb16010dc044d5f) +set(LIBTERMKEY_URL http://www.leonerd.org.uk/code/libtermkey/libtermkey-0.22.tar.gz) +set(LIBTERMKEY_SHA256 6945bd3c4aaa83da83d80a045c5563da4edd7d0374c62c0d35aec09eb3014600) set(LIBVTERM_URL https://github.com/neovim/libvterm/archive/7c72294d84ce20da4c27362dbd7fa4b08cfc91da.tar.gz) set(LIBVTERM_SHA256 f30c4d43e0c6db3e0912daf7188d98fbf6ee88f97589d72f6f304e5db48826a8) diff --git a/third-party/cmake/BuildLuarocks.cmake b/third-party/cmake/BuildLuarocks.cmake index 7551c52ecc..4b1b94a46b 100644 --- a/third-party/cmake/BuildLuarocks.cmake +++ b/third-party/cmake/BuildLuarocks.cmake @@ -52,13 +52,17 @@ if(NOT MSVC) set(LUAROCKS_BUILDARGS CC=${HOSTDEPS_C_COMPILER} LD=${HOSTDEPS_C_COMPILER}) endif() +# Lua version, used with rocks directories. +# Defaults to 5.1 for bundled LuaJIT/Lua. +set(LUA_VERSION "5.1") + if(UNIX OR (MINGW AND CMAKE_CROSSCOMPILING)) if(USE_BUNDLED_LUAJIT) list(APPEND LUAROCKS_OPTS --with-lua=${HOSTDEPS_INSTALL_DIR} --with-lua-include=${HOSTDEPS_INSTALL_DIR}/include/luajit-2.1 - --lua-suffix=jit) + --with-lua-interpreter=luajit) elseif(USE_BUNDLED_LUA) list(APPEND LUAROCKS_OPTS --with-lua=${HOSTDEPS_INSTALL_DIR}) @@ -66,9 +70,23 @@ if(UNIX OR (MINGW AND CMAKE_CROSSCOMPILING)) find_package(LuaJit) if(LUAJIT_FOUND) list(APPEND LUAROCKS_OPTS - --lua-version=5.1 --with-lua-include=${LUAJIT_INCLUDE_DIRS} - --lua-suffix=jit) + --with-lua-interpreter=luajit) + endif() + + # Get LUA_VERSION used with rocks output. + if(LUAJIT_FOUND) + set(LUA_EXE "luajit") + else() + set(LUA_EXE "lua") + endif() + execute_process( + COMMAND ${LUA_EXE} -e "print(string.sub(_VERSION, 5))" + OUTPUT_VARIABLE LUA_VERSION + ERROR_VARIABLE ERR + RESULT_VARIABLE RES) + if(NOT RES EQUAL 0) + message(FATAL_ERROR "Could not get LUA_VERSION with ${LUA_EXE}: ${ERR}") endif() endif() @@ -111,7 +129,7 @@ if(USE_BUNDLED_LUAJIT) elseif(USE_BUNDLED_LUA) add_dependencies(luarocks lua) endif() -set(ROCKS_DIR ${HOSTDEPS_LIB_DIR}/luarocks/rocks) +set(ROCKS_DIR ${HOSTDEPS_LIB_DIR}/luarocks/rocks-${LUA_VERSION}) # mpack add_custom_command(OUTPUT ${ROCKS_DIR}/mpack |