diff options
109 files changed, 4458 insertions, 834 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44a911b21b..bd90aeb932 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,7 @@ jobs: runner: macos-10.15 os: osx runs-on: ${{ matrix.runner }} + if: github.event.pull_request.draft == false env: CC: ${{ matrix.cc }} CI_OS_NAME: ${{ matrix.os }} @@ -88,6 +89,7 @@ jobs: windows: runs-on: windows-2016 + if: github.event.pull_request.draft == false env: DEPS_BUILD_DIR: "C:/projects/nvim-deps" DEPS_PREFIX: "C:/projects/nvim-deps/usr" diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml new file mode 100644 index 0000000000..431ccd8b61 --- /dev/null +++ b/.github/workflows/nightly.yaml @@ -0,0 +1,49 @@ +name: Nightly +on: + schedule: + - cron: '3 3 * * *' + +jobs: + update-vim-patches: + runs-on: ubuntu-20.04 + env: + VIM_SOURCE_DIR: ${{ format('{0}/vim-src', github.workspace) }} + VERSION_BRANCH: marvim/ci-version-update + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - uses: actions/checkout@v2 + with: + repository: vim/vim + path: ${{ env.VIM_SOURCE_DIR }} + fetch-depth: 0 + + - run: | + gh release download -R neovim/neovim -p nvim.appimage + chmod a+x nvim.appimage + mkdir -p $HOME/.local/bin + mv nvim.appimage $HOME/.local/bin/nvim + printf '%s\n' "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Setup git config + run: | + git config --global user.name 'marvim' + git config --global user.email 'marvim@users.noreply.github.com' + + - name: Update src/version.c + id: update-version + run: | + git checkout -b ${VERSION_BRANCH} + nvim -i NONE -u NONE --headless +'luafile scripts/vimpatch.lua' +q + printf '::set-output name=NEW_PATCHES::%s\n' $([ -z "$(git diff)" ]; echo $?) + + - name: Automatic PR + if: ${{ steps.update-version.outputs.NEW_PATCHES != 0 }} + run: | + git add -u + git commit -m 'version.c: update [skip ci]' + git push --force https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY} ${VERSION_BRANCH} + gh pr create --fill --label vim-patch --base master --head ${VERSION_BRANCH} || true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 753142e555..43fe1d5101 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,7 +26,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y autoconf automake build-essential cmake gcc-multilib gettext gperf libtool-bin locales ninja-build pkg-config unzip + sudo apt-get install -y autoconf automake build-essential cmake gettext gperf libtool-bin locales ninja-build pkg-config unzip - name: Build release id: build run: | @@ -51,7 +51,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y autoconf automake build-essential cmake gcc-multilib gettext gperf libtool-bin locales ninja-build pkg-config unzip + sudo apt-get install -y autoconf automake build-essential cmake gettext gperf libtool-bin locales ninja-build pkg-config unzip - if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly') run: make appimage-latest - if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name == 'nightly') @@ -156,7 +156,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - delete_release: '' + delete_release: true tag_name: nightly - uses: meeDamian/github-release@2.0 with: diff --git a/.gitignore b/.gitignore index 6004101cce..670340a519 100644 --- a/.gitignore +++ b/.gitignore @@ -35,7 +35,7 @@ compile_commands.json /src/nvim/testdir/del /src/nvim/testdir/test*.out /src/nvim/testdir/test*.res -/src/nvim/testdir/test.log +/src/nvim/testdir/test*.log /src/nvim/testdir/messages /src/nvim/testdir/viminfo /src/nvim/testdir/test.ok diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e427eea26..c22ab8dbae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -679,3 +679,14 @@ set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") set(CPACK_NSIS_MODIFY_PATH ON) set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) include(CPack) + +#add uninstall target +if(NOT TARGET uninstall) + configure_file( + "cmake/UninstallHelper.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/UninstallHelper.cmake" + IMMEDIATE @ONLY) + + add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/UninstallHelper.cmake) +endif() diff --git a/cmake/UninstallHelper.cmake.in b/cmake/UninstallHelper.cmake.in new file mode 100644 index 0000000000..c2d34d4796 --- /dev/null +++ b/cmake/UninstallHelper.cmake.in @@ -0,0 +1,21 @@ +if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") +endif() + +file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +foreach(file ${files}) + message(STATUS "Uninstalling $ENV{DESTDIR}${file}") + if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + exec_program( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + if(NOT "${rm_retval}" STREQUAL 0) + message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") + endif() + else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + message(STATUS "File $ENV{DESTDIR}${file} does not exist.") + endif() +endforeach() diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt index 8a70d864c4..6b88c92cf0 100644 --- a/config/CMakeLists.txt +++ b/config/CMakeLists.txt @@ -52,6 +52,7 @@ check_function_exists(setsid HAVE_SETSID) check_function_exists(sigaction HAVE_SIGACTION) check_function_exists(strcasecmp HAVE_STRCASECMP) check_function_exists(strncasecmp HAVE_STRNCASECMP) +check_function_exists(strptime HAVE_STRPTIME) # Symbols check_symbol_exists(FD_CLOEXEC "fcntl.h" HAVE_FD_CLOEXEC) diff --git a/config/config.h.in b/config/config.h.in index 275bff79a0..502f84bbcf 100644 --- a/config/config.h.in +++ b/config/config.h.in @@ -33,6 +33,7 @@ #cmakedefine HAVE_STRCASECMP #cmakedefine HAVE_STRINGS_H #cmakedefine HAVE_STRNCASECMP +#cmakedefine HAVE_STRPTIME #cmakedefine HAVE_SYS_SDT_H #cmakedefine HAVE_SYS_UTSNAME_H #cmakedefine HAVE_SYS_WAIT_H diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 5d7f559c0d..1e287281cf 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -538,6 +538,21 @@ nvim__screenshot({path}) *nvim__screenshot()* Attributes: ~ {fast} +nvim__set_hl_ns({ns_id}) *nvim__set_hl_ns()* + Set active namespace for highlights. + + NB: this function can be called from async contexts, but the + semantics are not yet well-defined. To start with + |nvim_set_decoration_provider| on_win and on_line callbacks + are explicitly allowed to change the namespace during a redraw + cycle. + + Attributes: ~ + {fast} + + Parameters: ~ + {ns_id} the namespace to activate + nvim__stats() *nvim__stats()* Gets internal stats. @@ -599,6 +614,22 @@ nvim_call_function({fn}, {args}) *nvim_call_function()* Return: ~ Result of the function call +nvim_chan_send({chan}, {data}) *nvim_chan_send()* + Send data to channel `id` . For a job, it writes it to the + stdin of the process. For the stdio channel |channel-stdio|, + it writes to Nvim's stdout. For an internal terminal instance + (|nvim_open_term()|) it writes directly to terimal output. See + |channel-bytes| for more information. + + This function writes raw data, not RPC messages. If the + channel was created with `rpc=true` then the channel expects + RPC messages, use |vim.rpcnotify()| and |vim.rpcrequest()| + instead. + + Parameters: ~ + {chan} id of the channel + {data} data to write. 8-bit clean: can contain NUL bytes. + nvim_command({command}) *nvim_command()* Executes an ex-command. @@ -1160,6 +1191,27 @@ nvim_notify({msg}, {log_level}, {opts}) *nvim_notify()* {log_level} The log level {opts} Reserved for future use. +nvim_open_term({buffer}, {opts}) *nvim_open_term()* + Open a terminal instance in a buffer + + By default (and currently the only option) the terminal will + not be connected to an external process. Instead, input send + on the channel will be echoed directly by the terminal. This + is useful to disply ANSI terminal sequences returned as part + of a rpc message, or similar. + + Note: to directly initiate the terminal using the right size, + display the buffer in a configured window before calling this. + For instance, for a floating display, first create an empty + buffer using |nvim_create_buf()|, then display it using + |nvim_open_win()|, and then call this function. Then + |nvim_chan_send()| cal be called immediately to process + sequences in a virtual terminal having the intended size. + + Parameters: ~ + {buffer} the buffer to use (expected to be empty) + {opts} Optional parameters. Reserved for future use. + nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()* Open a new window. @@ -1623,21 +1675,6 @@ nvim_set_hl({ns_id}, {name}, {val}) *nvim_set_hl()* keys are also recognized: `default` : don't override existing definition, like `hi default` -nvim_set_hl_ns({ns_id}) *nvim_set_hl_ns()* - Set active namespace for highlights. - - NB: this function can be called from async contexts, but the - semantics are not yet well-defined. To start with - |nvim_set_decoration_provider| on_win and on_line callbacks - are explicitly allowed to change the namespace during a redraw - cycle. - - Attributes: ~ - {fast} - - Parameters: ~ - {ns_id} the namespace to activate - nvim_set_keymap({mode}, {lhs}, {rhs}, {opts}) *nvim_set_keymap()* Sets a global |mapping| for the given mode. @@ -2202,6 +2239,19 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {opts}) column, without shifting the underlying text. + • virt_text_hide : hide the virtual text when + the background text is selected or hidden due + to horizontal scroll 'nowrap' + • hl_mode : control how highlights are combined + with the highlights of the text. Currently + only affects virt_text highlights, but might + affect`hl_group`in later versions. + • "replace": only show the virt_text color. + This is the default + • "combine": combine with background text + color + • "blend": blend with background text color. + • ephemeral : for use with |nvim_set_decoration_provider| callbacks. The mark will only be used for the current redraw diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index be7c026f5a..112958f78b 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -444,7 +444,7 @@ as a key. To avoid having to put quotes around every key the #{} form can be used. This does require the key to consist only of ASCII letters, digits, '-' and '_'. Example: > - let mydict = #{zero: 0, one_key: 1, two-key: 2, 333: 3} + :let mydict = #{zero: 0, one_key: 1, two-key: 2, 333: 3} Note that 333 here is the string "333". Empty keys are not possible with #{}. A value can be any expression. Using a Dictionary for a value creates a @@ -2430,7 +2430,7 @@ strcharpart({str}, {start} [, {len}]) String {len} characters of {str} at character {start} strdisplaywidth({expr} [, {col}]) Number display length of the String {expr} -strftime({format} [, {time}]) String time in specified format +strftime({format} [, {time}]) String format time with a specified format strgetchar({str}, {index}) Number get char {index} from {str} stridx({haystack}, {needle} [, {start}]) Number index of {needle} in {haystack} @@ -2439,6 +2439,8 @@ strlen({expr}) Number length of the String {expr} strpart({str}, {start} [, {len} [, {chars}]]) String {len} bytes/chars of {str} at byte {start} +strptime({format}, {timestring}) + Number Convert {timestring} to unix timestamp strridx({haystack}, {needle} [, {start}]) Number last index of {needle} in {haystack} strtrans({expr}) String translate string to make it printable @@ -2498,6 +2500,8 @@ win_gotoid({expr}) Number go to |window-ID| {expr} win_id2tabwin({expr}) List get tab and window nr from |window-ID| win_id2win({expr}) Number get window nr from |window-ID| win_screenpos({nr}) List get screen position of window {nr} +win_splitmove({nr}, {target} [, {options}]) + none move window {nr} to split of {target} winbufnr({nr}) Number buffer number of window {nr} wincol() Number window column of the cursor winheight({nr}) Number height of window {nr} @@ -4981,7 +4985,7 @@ getwininfo([{winid}]) *getwininfo()* getwinpos([{timeout}]) *getwinpos()* The result is a list with two numbers, the result of - getwinposx() and getwinposy() combined: + |getwinposx()| and |getwinposy()| combined: [x-pos, y-pos] {timeout} can be used to specify how long to wait in msec for a response from the terminal. When omitted 100 msec is used. @@ -5851,7 +5855,7 @@ list2str({list} [, {utf8}]) *list2str()* < localtime() *localtime()* Return the current time, measured as seconds since 1st Jan - 1970. See also |strftime()| and |getftime()|. + 1970. See also |strftime()|, |strptime()| and |getftime()|. log({expr}) *log()* @@ -8486,7 +8490,7 @@ strftime({format} [, {time}]) *strftime()* {format} depends on your system, thus this is not portable! See the manual page of the C function strftime() for the format. The maximum length of the result is 80 characters. - See also |localtime()| and |getftime()|. + See also |localtime()|, |getftime()| and |strptime()|. The language can be changed with the |:language| command. Examples: > :echo strftime("%c") Sun Apr 27 11:49:23 1997 @@ -8576,6 +8580,31 @@ strpart({src}, {start} [, {len} [, {chars}]]) *strpart()* example, to get the character under the cursor: > strpart(getline("."), col(".") - 1, 1, v:true) < +strptime({format}, {timestring}) *strptime()* + The result is a Number, which is a unix timestamp representing + the date and time in {timestring}, which is expected to match + the format specified in {format}. + + The accepted {format} depends on your system, thus this is not + portable! See the manual page of the C function strptime() + for the format. Especially avoid "%c". The value of $TZ also + matters. + + If the {timestring} cannot be parsed with {format} zero is + returned. If you do not know the format of {timestring} you + can try different {format} values until you get a non-zero + result. + + See also |strftime()|. + Examples: > + :echo strptime("%Y %b %d %X", "1997 Apr 27 11:49:23") +< 862156163 > + :echo strftime("%c", strptime("%y%m%d %T", "970427 11:53:55")) +< Sun Apr 27 11:53:55 1997 > + :echo strftime("%c", strptime("%Y%m%d%H%M%S", "19970427115355") + 3600) +< Sun Apr 27 12:53:55 1997 + + strridx({haystack}, {needle} [, {start}]) *strridx()* The result is a Number, which gives the byte index in {haystack} of the last occurrence of the String {needle}. @@ -9370,6 +9399,25 @@ win_screenpos({nr}) *win_screenpos()* Return [0, 0] if the window cannot be found in the current tabpage. +win_splitmove({nr}, {target} [, {options}]) *win_splitmove()* + Move the window {nr} to a new split of the window {target}. + This is similar to moving to {target}, creating a new window + using |:split| but having the same contents as window {nr}, and + then closing {nr}. + + Both {nr} and {target} can be window numbers or |window-ID|s. + + Returns zero for success, non-zero for failure. + + {options} is a Dictionary with the following optional entries: + "vertical" When TRUE, the split is created vertically, + like with |:vsplit|. + "rightbelow" When TRUE, the split is made below or to the + right (if vertical). When FALSE, it is done + above or to the left (if vertical). When not + present, the values of 'splitbelow' and + 'splitright' are used. + *winbufnr()* winbufnr({nr}) The result is a Number, which is the number of the buffer associated with window {nr}. {nr} can be the window number or diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 1f6ecc911d..67a10c7efb 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -749,15 +749,6 @@ start_client({config}) *vim.lsp.start_client()* The following parameters describe fields in the {config} table. -> - - -- In attach function for the client, you can do: - local custom_attach = function(client) - if client.config.flags then - client.config.flags.allow_incremental_sync = true - end - end -< Parameters: ~ {root_dir} (required, string) Directory where the @@ -799,6 +790,8 @@ start_client({config}) *vim.lsp.start_client()* See `initialize` in the LSP spec. {name} (string, default=client-id) Name in log messages. + {get_language_id} function(bufnr, filetype) -> language + ID as string. Defaults to the filetype. {offset_encoding} (default="utf-16") One of "utf-8", "utf-16", or "utf-32" which is the encoding that the LSP server expects. @@ -854,8 +847,8 @@ start_client({config}) *vim.lsp.start_client()* {flags} A table with flags for the client. The current (experimental) flags are: • allow_incremental_sync (bool, default - false): Allow using on_line callbacks - for lsp + true): Allow using incremental sync + for buffer edits Return: ~ Client id. |vim.lsp.get_client_by_id()| Note: client may @@ -1547,6 +1540,18 @@ close_preview_autocmd({events}, {winnr}) See also: ~ |autocmd-events| + *vim.lsp.util.compute_diff()* +compute_diff({old_lines}, {new_lines}, {start_line_idx}, {end_line_idx}) + Returns the range table for the difference between old and new + lines + + Parameters: ~ + {old_lines} table list of lines + {new_lines} table list of lines + + Return: ~ + table start_line_idx and start_col_idx of range + *vim.lsp.util.convert_input_to_markdown_lines()* convert_input_to_markdown_lines({input}, {contents}) Converts any of `MarkedString` | `MarkedString[]` | @@ -1581,6 +1586,12 @@ convert_signature_help_to_markdown_lines({signature_help}) See also: ~ https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp +create_file({change}) *vim.lsp.util.create_file()* + TODO: Documentation + +delete_file({change}) *vim.lsp.util.delete_file()* + TODO: Documentation + *vim.lsp.util.extract_completion_items()* extract_completion_items({result}) Can be used to extract the completion items from a `textDocument/completion` request, which may return one of `CompletionItem[]` , `CompletionList` or null. @@ -1788,12 +1799,12 @@ make_workspace_params({added}, {removed}) {removed} *vim.lsp.util.open_floating_preview()* -open_floating_preview({contents}, {filetype}, {opts}) +open_floating_preview({contents}, {syntax}, {opts}) Shows contents in a floating window. Parameters: ~ {contents} table of lines to show in window - {filetype} string of filetype to set for opened buffer + {syntax} string of syntax to set for opened buffer {opts} dictionary with optional fields Return: ~ @@ -1824,6 +1835,10 @@ preview_location({location}) *vim.lsp.util.preview_location()* (bufnr,winnr) buffer and window number of floating window or nil +rename({old_fname}, {new_fname}, {opts}) *vim.lsp.util.rename()* + Parameters: ~ + {opts} (table) + set_lines({lines}, {A}, {B}, {new_lines}) *vim.lsp.util.set_lines()* Replaces text in a range with new text. diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 0bbed56662..c2fc25431c 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -1055,6 +1055,18 @@ list_extend({dst}, {src}, {start}, {finish}) *vim.list_extend()* See also: ~ |vim.tbl_extend()| +list_slice({list}, {start}, {finish}) *vim.list_slice()* + Creates a copy of a table containing only elements from start + to end (inclusive) + + Parameters: ~ + {list} table table + {start} integer Start range of slice + {finish} integer End range of slice + + Return: ~ + Copy of table sliced from start to finish (inclusive) + pesc({s}) *vim.pesc()* Escapes magic chars in a Lua pattern. diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt index bec2b362ea..5885b20ab7 100644 --- a/runtime/doc/nvim_terminal_emulator.txt +++ b/runtime/doc/nvim_terminal_emulator.txt @@ -312,6 +312,8 @@ Other commands ~ *:Program* jump to the window with the running program *:Source* jump to the window with the source code, create it if there isn't one + *:Asm* jump to the window with the disassembly, create it if there + isn't one Prompt mode ~ @@ -330,6 +332,12 @@ This works slightly differently: Prompt mode can be used even when the |+terminal| feature is present with: > let g:termdebug_use_prompt = 1 +< + *termdebug_disasm_window* +If you want the Asm window shown by default, set this to 1. Setting to +any value greater than 1 will set the Asm window height to that value: > + let g:termdebug_disasm_window = 15 +< Communication ~ *termdebug-communication* diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index b45e9ed450..aeee02a1e0 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -4689,6 +4689,7 @@ in their own color. highlighting for groups added by the user! Uses the current value of 'background' to decide which default colors to use. + If there was a default link, restore it. |:hi-link| :hi[ghlight] clear {group-name} :hi[ghlight] {group-name} NONE diff --git a/runtime/doc/tagsrch.txt b/runtime/doc/tagsrch.txt index 23db809543..7d09ca86ac 100644 --- a/runtime/doc/tagsrch.txt +++ b/runtime/doc/tagsrch.txt @@ -337,11 +337,11 @@ the same as above, with a "p" prepended. A static tag is a tag that is defined for a specific file. In a C program this could be a static function. -In Vi jumping to a tag sets the current search pattern. This means that -the "n" command after jumping to a tag does not search for the same pattern -that it did before jumping to the tag. Vim does not do this as we consider it -to be a bug. You can still find the tag search pattern in the search history. -If you really want the old Vi behavior, set the 't' flag in 'cpoptions'. +In Vi jumping to a tag sets the current search pattern. This means that the +"n" command after jumping to a tag does not search for the same pattern that +it did before jumping to the tag. Vim does not do this as we consider it to +be a bug. If you really want the old Vi behavior, set the 't' flag in +'cpoptions'. *tag-binary-search* Vim uses binary searching in the tags file to find the desired tag quickly @@ -419,8 +419,7 @@ would otherwise go unnoticed. Example: > In Vi the ":tag" command sets the last search pattern when the tag is searched for. In Vim this is not done, the previous search pattern is still remembered, -unless the 't' flag is present in 'cpoptions'. The search pattern is always -put in the search history, so you can modify it if searching fails. +unless the 't' flag is present in 'cpoptions'. *tags-option* The 'tags' option is a list of file names. Each of these files is searched @@ -847,19 +846,25 @@ like |CTRL-]|. The function used for generating the taglist is specified by setting the 'tagfunc' option. The function will be called with three arguments: - a:pattern The tag identifier used during the tag search. - a:flags List of flags to control the function behavior. + a:pattern The tag identifier or pattern used during the tag search. + a:flags String containing flags to control the function behavior. a:info Dict containing the following entries: buf_ffname Full filename which can be used for priority. user_data Custom data String, if stored in the tag stack previously by tagfunc. -Currently two flags may be passed to the tag function: +Currently up to three flags may be passed to the tag function: 'c' The function was invoked by a normal command being processed (mnemonic: the tag function may use the context around the cursor to perform a better job of generating the tag list.) 'i' In Insert mode, the user was completing a tag (with - |i_CTRL-X_CTRL-]|). + |i_CTRL-X_CTRL-]| or 'completeopt' contains `t`). + 'r' The first argument to tagfunc should be interpreted as a + |pattern| (see |tag-regexp|), such as when using: > + :tag /pat +< It is also given when completing in insert mode. + If this flag is not present, the argument is usually taken + literally as the full tag name. Note that when 'tagfunc' is set, the priority of the tags described in |tag-priority| does not apply. Instead, the priority is exactly as the diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 63c899da0c..97aacc1403 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -788,6 +788,7 @@ Date and Time: *date-functions* *time-functions* getftime() get last modification time of a file localtime() get current time in seconds strftime() convert time to a string + strptime() convert a date/time string to time reltime() get the current or elapsed time accurately reltimestr() convert reltime() result to a string reltimefloat() convert reltime() result to a Float diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 0c1e216164..eadc1c04a0 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -70,6 +70,8 @@ the differences. - |matchit| plugin is enabled. To disable it in your config: > :let loaded_matchit = 1 +- |g:vimsyn_embed| defaults to "l" to enable Lua highlighting + ============================================================================== 3. New Features *nvim-features* diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 6a13e67ac5..36352db533 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -164,6 +164,9 @@ au BufNewFile,BufRead *.mar setf vmasm " Atlas au BufNewFile,BufRead *.atl,*.as setf atlas +" Atom is based on XML +au BufNewFile,BufRead *.atom setf xml + " Autoit v3 au BufNewFile,BufRead *.au3 setf autoit @@ -1330,6 +1333,9 @@ au BufNewFile,BufRead *.pml setf promela au BufNewFile,BufRead *.proto setf proto au BufNewFile,BufRead *.pbtxt setf pbtxt +" Poke +au BufNewFile,BufRead *.pk setf poke + " Protocols au BufNewFile,BufRead */etc/protocols setf protocols @@ -1394,6 +1400,9 @@ else au BufNewFile,BufRead *.rmd,*.smd setf rmd endif +" RSS looks like XML +au BufNewFile,BufRead *.rss setf xml + " R reStructuredText file if has("fname_case") au BufNewFile,BufRead *.Rrst,*.rrst,*.Srst,*.srst setf rrst diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 841c365cbe..4c453df3f6 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -42,6 +42,8 @@ lsp._request_name_to_capability = { ['textDocument/prepareCallHierarchy'] = 'call_hierarchy'; ['textDocument/rename'] = 'rename'; ['textDocument/codeAction'] = 'code_action'; + ['textDocument/codeLens'] = 'code_lens'; + ['codeLens/resolve'] = 'code_lens_resolve'; ['workspace/executeCommand'] = 'execute_command'; ['textDocument/references'] = 'find_references'; ['textDocument/rangeFormatting'] = 'document_range_formatting'; @@ -228,6 +230,7 @@ local function validate_client_config(config) before_init = { config.before_init, "f", true }; offset_encoding = { config.offset_encoding, "s", true }; flags = { config.flags, "t", true }; + get_language_id = { config.get_language_id, "f", true }; } local cmd, cmd_args = lsp._cmd_parts(config.cmd) @@ -262,23 +265,47 @@ end --@param bufnr (Number) Number of the buffer, or 0 for current --@param client Client object local function text_document_did_open_handler(bufnr, client) + local use_incremental_sync = ( + if_nil(client.config.flags.allow_incremental_sync, true) + and client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.Incremental + ) + if use_incremental_sync then + if not client._cached_buffers then + client._cached_buffers = {} + end + client._cached_buffers[bufnr] = nvim_buf_get_lines(bufnr, 0, -1, true) + end if not client.resolved_capabilities.text_document_open_close then return end if not vim.api.nvim_buf_is_loaded(bufnr) then return end + local filetype = nvim_buf_get_option(bufnr, 'filetype') + local params = { textDocument = { version = 0; uri = vim.uri_from_bufnr(bufnr); - -- TODO make sure our filetypes are compatible with languageId names. - languageId = nvim_buf_get_option(bufnr, 'filetype'); + languageId = client.config.get_language_id(bufnr, filetype); text = buf_get_full_text(bufnr); } } client.notify('textDocument/didOpen', params) util.buf_versions[bufnr] = params.textDocument.version + + -- Next chance we get, we should re-do the diagnostics + vim.schedule(function() + vim.lsp.handlers["textDocument/publishDiagnostics"]( + nil, + "textDocument/publishDiagnostics", + { + diagnostics = vim.lsp.diagnostic.get(bufnr, client.id), + uri = vim.uri_from_bufnr(bufnr), + }, + client.id + ) + end) end -- FIXME: DOC: Shouldn't need to use a dummy function @@ -400,6 +427,9 @@ end --- --@param name (string, default=client-id) Name in log messages. --- +--@param get_language_id function(bufnr, filetype) -> language ID as string. +--- Defaults to the filetype. +--- --@param offset_encoding (default="utf-16") One of "utf-8", "utf-16", --- or "utf-32" which is the encoding that the LSP server expects. Client does --- not verify this is correct. @@ -438,16 +468,7 @@ end --@param trace: "off" | "messages" | "verbose" | nil passed directly to the language --- server in the initialize request. Invalid/empty values will default to "off" --@param flags: A table with flags for the client. The current (experimental) flags are: ---- - allow_incremental_sync (bool, default false): Allow using on_line callbacks for lsp ---- ---- <pre> ---- -- In attach function for the client, you can do: ---- local custom_attach = function(client) ---- if client.config.flags then ---- client.config.flags.allow_incremental_sync = true ---- end ---- end ---- </pre> +--- - allow_incremental_sync (bool, default true): Allow using incremental sync for buffer edits --- --@returns Client id. |vim.lsp.get_client_by_id()| Note: client may not be --- fully initialized. Use `on_init` to do any actions once @@ -459,6 +480,11 @@ function lsp.start_client(config) config.flags = config.flags or {} config.settings = config.settings or {} + -- By default, get_language_id just returns the exact filetype it is passed. + -- It is possible to pass in something that will calculate a different filetype, + -- to be sent by the client. + config.get_language_id = config.get_language_id or function(_, filetype) return filetype end + local client_id = next_client_id() local handlers = config.handlers or {} @@ -808,7 +834,6 @@ end --- Notify all attached clients that a buffer has changed. local text_document_did_change_handler do - local encoding_index = { ["utf-8"] = 1; ["utf-16"] = 2; ["utf-32"] = 3; } text_document_did_change_handler = function(_, bufnr, changedtick, firstline, lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size) @@ -827,23 +852,12 @@ do util.buf_versions[bufnr] = changedtick -- Lazy initialize these because clients may not even need them. local incremental_changes = once(function(client) - local size_index = encoding_index[client.offset_encoding] - local length = select(size_index, old_byte_size, old_utf16_size, old_utf32_size) - local lines = nvim_buf_get_lines(bufnr, firstline, new_lastline, true) - - -- This is necessary because we are specifying the full line including the - -- newline in range. Therefore, we must replace the newline as well. - if #lines > 0 then - table.insert(lines, '') - end - return { - range = { - start = { line = firstline, character = 0 }; - ["end"] = { line = lastline, character = 0 }; - }; - rangeLength = length; - text = table.concat(lines, '\n'); - }; + local lines = nvim_buf_get_lines(bufnr, 0, -1, true) + local startline = math.min(firstline + 1, math.min(#client._cached_buffers[bufnr], #lines)) + local endline = math.min(-(#lines - new_lastline), -1) + local incremental_change = vim.lsp.util.compute_diff(client._cached_buffers[bufnr], lines, startline, endline) + client._cached_buffers[bufnr] = lines + return incremental_change end) local full_changes = once(function() return { @@ -851,19 +865,12 @@ do }; end) local uri = vim.uri_from_bufnr(bufnr) - for_each_buffer_client(bufnr, function(client, _client_id) - local allow_incremental_sync = if_nil(client.config.flags.allow_incremental_sync, false) - + for_each_buffer_client(bufnr, function(client) + local allow_incremental_sync = if_nil(client.config.flags.allow_incremental_sync, true) local text_document_did_change = client.resolved_capabilities.text_document_did_change local changes if text_document_did_change == protocol.TextDocumentSyncKind.None then return - --[=[ TODO(ashkan) there seem to be problems with the byte_sizes sent by - -- neovim right now so only send the full content for now. In general, we - -- can assume that servers *will* support both versions anyway, as there - -- is no way to specify the sync capability by the client. - -- See https://github.com/palantir/python-language-server/commit/cfd6675bc10d5e8dbc50fc50f90e4a37b7178821#diff-f68667852a14e9f761f6ebf07ba02fc8 for an example of pyls handling both. - --]=] elseif not allow_incremental_sync or text_document_did_change == protocol.TextDocumentSyncKind.Full then changes = full_changes(client) elseif text_document_did_change == protocol.TextDocumentSyncKind.Incremental then @@ -931,6 +938,9 @@ function lsp.buf_attach_client(bufnr, client_id) if client.resolved_capabilities.text_document_open_close then client.notify('textDocument/didClose', params) end + if client._cached_buffers then + client._cached_buffers[bufnr] = nil + end end) util.buf_versions[bufnr] = nil all_buffer_active_clients[bufnr] = nil diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index a1f24706c0..4e82c46fef 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -270,8 +270,10 @@ local function set_diagnostic_cache(diagnostics, bufnr, client_id) diagnostic.severity = DiagnosticSeverity.Error end -- Account for servers that place diagnostics on terminating newline - local start = diagnostic.range.start - start.line = math.min(start.line, buf_line_count - 1) + if buf_line_count > 0 then + local start = diagnostic.range.start + start.line = math.min(start.line, buf_line_count - 1) + end end diagnostic_cache[bufnr][client_id] = diagnostics diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 0cf80e1443..eacbd90077 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -304,7 +304,7 @@ M['textDocument/typeDefinition'] = location_handler M['textDocument/implementation'] = location_handler --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp -M['textDocument/signatureHelp'] = function(_, method, result) +M['textDocument/signatureHelp'] = function(_, method, result, _, bufnr) -- When use `autocmd CompleteDone <silent><buffer> lua vim.lsp.buf.signature_help()` to call signatureHelp handler -- If the completion item doesn't have signatures It will make noise. Change to use `print` that can use `<silent>` to ignore if not (result and result.signatures and result.signatures[1]) then @@ -317,9 +317,11 @@ M['textDocument/signatureHelp'] = function(_, method, result) print('No signature help available') return end - util.focusable_preview(method, function() + local syntax = api.nvim_buf_get_option(bufnr, 'syntax') + local p_bufnr, _ = util.focusable_preview(method, function() return lines, util.try_trim_markdown_code_blocks(lines) end) + api.nvim_buf_set_option(p_bufnr, 'syntax', syntax) end --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 388f65c180..7e43eb84de 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -749,6 +749,9 @@ function protocol.make_client_capabilities() }; workspaceFolders = true; applyEdit = true; + workspaceEdit = { + resourceOperations = {'rename', 'create', 'delete',}, + }; }; callHierarchy = { dynamicRegistration = false; @@ -975,6 +978,16 @@ function protocol.resolve_capabilities(server_capabilities) general_properties.rename = true end + if server_capabilities.codeLensProvider == nil then + general_properties.code_lens = false + general_properties.code_lens_resolve = false + elseif type(server_capabilities.codeLensProvider) == 'table' then + general_properties.code_lens = true + general_properties.code_lens_resolve = server_capabilities.codeLensProvider.resolveProvider or false + else + error("The server sent invalid codeLensProvider") + end + if server_capabilities.codeActionProvider == nil then general_properties.code_action = false elseif type(server_capabilities.codeActionProvider) == 'boolean' diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 6fb9f09c99..412be68396 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -226,6 +226,168 @@ end -- function M.glob_to_regex(glob) -- end +--@private +--- Finds the first line and column of the difference between old and new lines +--@param old_lines table list of lines +--@param new_lines table list of lines +--@returns (int, int) start_line_idx and start_col_idx of range +local function first_difference(old_lines, new_lines, start_line_idx) + local line_count = math.min(#old_lines, #new_lines) + if line_count == 0 then return 1, 1 end + if not start_line_idx then + for i = 1, line_count do + start_line_idx = i + if old_lines[start_line_idx] ~= new_lines[start_line_idx] then + break + end + end + end + local old_line = old_lines[start_line_idx] + local new_line = new_lines[start_line_idx] + local length = math.min(#old_line, #new_line) + local start_col_idx = 1 + while start_col_idx <= length do + if string.sub(old_line, start_col_idx, start_col_idx) ~= string.sub(new_line, start_col_idx, start_col_idx) then + break + end + start_col_idx = start_col_idx + 1 + end + return start_line_idx, start_col_idx +end + + +--@private +--- Finds the last line and column of the differences between old and new lines +--@param old_lines table list of lines +--@param new_lines table list of lines +--@param start_char integer First different character idx of range +--@returns (int, int) end_line_idx and end_col_idx of range +local function last_difference(old_lines, new_lines, start_char, end_line_idx) + local line_count = math.min(#old_lines, #new_lines) + if line_count == 0 then return 0,0 end + if not end_line_idx then + end_line_idx = -1 + end + for i = end_line_idx, -line_count, -1 do + if old_lines[#old_lines + i + 1] ~= new_lines[#new_lines + i + 1] then + end_line_idx = i + break + end + end + local old_line + local new_line + if end_line_idx <= -line_count then + end_line_idx = -line_count + old_line = string.sub(old_lines[#old_lines + end_line_idx + 1], start_char) + new_line = string.sub(new_lines[#new_lines + end_line_idx + 1], start_char) + else + old_line = old_lines[#old_lines + end_line_idx + 1] + new_line = new_lines[#new_lines + end_line_idx + 1] + end + local old_line_length = #old_line + local new_line_length = #new_line + local length = math.min(old_line_length, new_line_length) + local end_col_idx = -1 + while end_col_idx >= -length do + local old_char = string.sub(old_line, old_line_length + end_col_idx + 1, old_line_length + end_col_idx + 1) + local new_char = string.sub(new_line, new_line_length + end_col_idx + 1, new_line_length + end_col_idx + 1) + if old_char ~= new_char then + break + end + end_col_idx = end_col_idx - 1 + end + return end_line_idx, end_col_idx + +end + +--@private +--- Get the text of the range defined by start and end line/column +--@param lines table list of lines +--@param start_char integer First different character idx of range +--@param end_char integer Last different character idx of range +--@param start_line integer First different line idx of range +--@param end_line integer Last different line idx of range +--@returns string text extracted from defined region +local function extract_text(lines, start_line, start_char, end_line, end_char) + if start_line == #lines + end_line + 1 then + if end_line == 0 then return '' end + local line = lines[start_line] + local length = #line + end_char - start_char + return string.sub(line, start_char, start_char + length + 1) + end + local result = string.sub(lines[start_line], start_char) .. '\n' + for line_idx = start_line + 1, #lines + end_line do + result = result .. lines[line_idx] .. '\n' + end + if end_line ~= 0 then + local line = lines[#lines + end_line + 1] + local length = #line + end_char + 1 + result = result .. string.sub(line, 1, length) + end + return result +end + +--@private +--- Compute the length of the substituted range +--@param lines table list of lines +--@param start_char integer First different character idx of range +--@param end_char integer Last different character idx of range +--@param start_line integer First different line idx of range +--@param end_line integer Last different line idx of range +--@returns (int, int) end_line_idx and end_col_idx of range +local function compute_length(lines, start_line, start_char, end_line, end_char) + local adj_end_line = #lines + end_line + 1 + local adj_end_char + if adj_end_line > #lines then + adj_end_char = end_char - 1 + else + adj_end_char = #lines[adj_end_line] + end_char + end + if start_line == adj_end_line then + return adj_end_char - start_char + 1 + end + local result = #lines[start_line] - start_char + 1 + for line = start_line + 1, adj_end_line -1 do + result = result + #lines[line] + 1 + end + result = result + adj_end_char + 1 + return result +end + +--- Returns the range table for the difference between old and new lines +--@param old_lines table list of lines +--@param new_lines table list of lines +--@returns table start_line_idx and start_col_idx of range +function M.compute_diff(old_lines, new_lines, start_line_idx, end_line_idx) + local start_line, start_char = first_difference(old_lines, new_lines, start_line_idx) + local end_line, end_char = last_difference(vim.list_slice(old_lines, start_line, #old_lines), + vim.list_slice(new_lines, start_line, #new_lines), start_char, end_line_idx) + local text = extract_text(new_lines, start_line, start_char, end_line, end_char) + local length = compute_length(old_lines, start_line, start_char, end_line, end_char) + + local adj_end_line = #old_lines + end_line + local adj_end_char + if end_line == 0 then + adj_end_char = 0 + else + adj_end_char = #old_lines[#old_lines + end_line + 1] + end_char + 1 + end + + local _, utf16_start_char = vim.str_utfindex(old_lines[start_line], start_char - 1) + local _, utf16_end_char = vim.str_utfindex(old_lines[#old_lines + end_line + 1], adj_end_char) + + local result = { + range = { + start = { line = start_line - 1, character = utf16_start_char}, + ["end"] = { line = adj_end_line, character = utf16_end_char} + }, + text = text, + rangeLength = length + 1, + } + + return result +end + --- Can be used to extract the completion items from a --- `textDocument/completion` request, which may return one of --- `CompletionItem[]`, `CompletionList` or null. @@ -359,13 +521,13 @@ end --- precedence is as follows: textEdit.newText > insertText > label --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion local function get_completion_word(item) - if item.textEdit ~= nil and item.textEdit.newText ~= nil then + if item.textEdit ~= nil and item.textEdit.newText ~= nil and item.textEdit.newText ~= "" then if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then return item.textEdit.newText else return M.parse_snippet(item.textEdit.newText) end - elseif item.insertText ~= nil then + elseif item.insertText ~= nil and item.insertText ~= "" then if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then return item.insertText else @@ -453,6 +615,62 @@ function M.text_document_completion_list_to_complete_items(result, prefix) return matches end + +--- Rename old_fname to new_fname +-- +--@param opts (table) +-- overwrite? bool +-- ignoreIfExists? bool +function M.rename(old_fname, new_fname, opts) + opts = opts or {} + local bufnr = vim.fn.bufadd(old_fname) + vim.fn.bufload(bufnr) + local target_exists = vim.loop.fs_stat(new_fname) ~= nil + if target_exists and not opts.overwrite or opts.ignoreIfExists then + vim.notify('Rename target already exists. Skipping rename.') + return + end + local ok, err = os.rename(old_fname, new_fname) + assert(ok, err) + api.nvim_buf_call(bufnr, function() + vim.cmd('saveas! ' .. vim.fn.fnameescape(new_fname)) + end) +end + + +local function create_file(change) + local opts = change.options or {} + -- from spec: Overwrite wins over `ignoreIfExists` + local fname = vim.uri_to_fname(change.uri) + if not opts.ignoreIfExists or opts.overwrite then + local file = io.open(fname, 'w') + file:close() + end + vim.fn.bufadd(fname) +end + + +local function delete_file(change) + local opts = change.options or {} + local fname = vim.uri_to_fname(change.uri) + local stat = vim.loop.fs_stat(fname) + if opts.ignoreIfNotExists and not stat then + return + end + assert(stat, "Cannot delete not existing file or folder " .. fname) + local flags + if stat and stat.type == 'directory' then + flags = opts.recursive and 'rf' or 'd' + else + flags = '' + end + local bufnr = vim.fn.bufadd(fname) + local result = tonumber(vim.fn.delete(fname, flags)) + assert(result == 0, 'Could not delete file: ' .. fname .. ', stat: ' .. vim.inspect(stat)) + api.nvim_buf_delete(bufnr, { force = true }) +end + + --- Applies a `WorkspaceEdit`. --- --@param workspace_edit (table) `WorkspaceEdit` @@ -460,8 +678,17 @@ end function M.apply_workspace_edit(workspace_edit) if workspace_edit.documentChanges then for idx, change in ipairs(workspace_edit.documentChanges) do - if change.kind then - -- TODO(ashkan) handle CreateFile/RenameFile/DeleteFile + if change.kind == "rename" then + M.rename( + vim.uri_to_fname(change.oldUri), + vim.uri_to_fname(change.newUri), + change.options + ) + elseif change.kind == 'create' then + create_file(change) + elseif change.kind == 'delete' then + delete_file(change) + elseif change.kind then error(string.format("Unsupported change: %q", vim.inspect(change))) else M.apply_text_document_edit(change, idx) @@ -687,8 +914,8 @@ function M.preview_location(location) end local range = location.targetRange or location.range local contents = api.nvim_buf_get_lines(bufnr, range.start.line, range["end"].line+1, false) - local filetype = api.nvim_buf_get_option(bufnr, 'filetype') - return M.open_floating_preview(contents, filetype) + local syntax = api.nvim_buf_get_option(bufnr, 'syntax') + return M.open_floating_preview(contents, syntax) end --@private @@ -873,8 +1100,10 @@ function M.fancy_floating_markdown(contents, opts) -- This is because the syntax command doesn't accept a target. local cwin = vim.api.nvim_get_current_win() vim.api.nvim_set_current_win(winnr) + api.nvim_win_set_option(winnr, 'conceallevel', 2) + api.nvim_win_set_option(winnr, 'concealcursor', 'n') - vim.cmd("ownsyntax markdown") + vim.cmd("ownsyntax lsp_markdown") local idx = 1 --@private local function apply_syntax_to_region(ft, start, finish) @@ -975,7 +1204,7 @@ end --- Shows contents in a floating window. --- --@param contents table of lines to show in window ---@param filetype string of filetype to set for opened buffer +--@param syntax string of syntax to set for opened buffer --@param opts dictionary with optional fields -- - height of floating window -- - width of floating window @@ -988,10 +1217,10 @@ end -- - pad_bottom number of lines to pad contents at bottom --@returns bufnr,winnr buffer and window number of the newly created floating ---preview window -function M.open_floating_preview(contents, filetype, opts) +function M.open_floating_preview(contents, syntax, opts) validate { contents = { contents, 't' }; - filetype = { filetype, 's', true }; + syntax = { syntax, 's', true }; opts = { opts, 't', true }; } opts = opts or {} @@ -1004,12 +1233,12 @@ function M.open_floating_preview(contents, filetype, opts) local width, height = M._make_floating_popup_size(contents, opts) local floating_bufnr = api.nvim_create_buf(false, true) - if filetype then - api.nvim_buf_set_option(floating_bufnr, 'filetype', filetype) + if syntax then + api.nvim_buf_set_option(floating_bufnr, 'syntax', syntax) end local float_option = M.make_floating_popup_options(width, height, opts) local floating_winnr = api.nvim_open_win(floating_bufnr, false, float_option) - if filetype == 'markdown' then + if syntax == 'markdown' then api.nvim_win_set_option(floating_winnr, 'conceallevel', 2) end api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents) diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 998e04f568..0a663628a5 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -400,6 +400,20 @@ function vim.tbl_count(t) return count end +--- Creates a copy of a table containing only elements from start to end (inclusive) +--- +--@param list table table +--@param start integer Start range of slice +--@param finish integer End range of slice +--@returns Copy of table sliced from start to finish (inclusive) +function vim.list_slice(list, start, finish) + local new_list = {} + for i = start or 1, finish or #list do + new_list[#new_list+1] = list[i] + end + return new_list +end + --- Trim whitespace (Lua pattern "%s") from both sides of a string. --- --@see https://www.lua.org/pil/20.2.html diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 3af66b134c..64a5ba1fd8 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -12,11 +12,7 @@ local M = vim.tbl_extend("error", query, language) setmetatable(M, { __index = function (t, k) - if k == "TSHighlighter" then - a.nvim_err_writeln("vim.TSHighlighter is deprecated, please use vim.treesitter.highlighter") - t[k] = require'vim.treesitter.highlighter' - return t[k] - elseif k == "highlighter" then + if k == "highlighter" then t[k] = require'vim.treesitter.highlighter' return t[k] end diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 8b94348994..1b29618997 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -8,10 +8,23 @@ Query.__index = Query local M = {} +local function dedupe_files(files) + local result = {} + local seen = {} + + for _, path in ipairs(files) do + if not seen[path] then + table.insert(result, path) + seen[path] = true + end + end + + return result +end function M.get_query_files(lang, query_name, is_included) local query_path = string.format('queries/%s/%s.scm', lang, query_name) - local lang_files = a.nvim_get_runtime_file(query_path, true) + local lang_files = dedupe_files(a.nvim_get_runtime_file(query_path, true)) if #lang_files == 0 then return {} end diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim index 6870bcec75..fa5d064048 100644 --- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim +++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim @@ -43,7 +43,7 @@ " balloon -> nvim floating window " " The code for opening the floating window was taken from the beautiful -" implementation of LanguageClient-Neovim: +" implementation of LanguageClient-Neovim: " https://github.com/autozimu/LanguageClient-neovim/blob/0ed9b69dca49c415390a8317b19149f97ae093fa/autoload/LanguageClient.vim#L304 " " Neovim terminal also works seamlessly on windows, which is why the ability @@ -76,9 +76,14 @@ if !exists('g:termdebugger') endif let s:pc_id = 12 -let s:break_id = 13 " breakpoint number is added to this +let s:asm_id = 13 +let s:break_id = 14 " breakpoint number is added to this let s:stopped = 1 +let s:parsing_disasm_msg = 0 +let s:asm_lines = [] +let s:asm_addr = '' + " Take a breakpoint number as used by GDB and turn it into an integer. " The breakpoint may contain a dot: 123.4 -> 123004 " The main breakpoint has a zero subid. @@ -120,6 +125,7 @@ func s:StartDebug_internal(dict) let s:ptywin = 0 let s:pid = 0 + let s:asmwin = 0 " Uncomment this line to write logging in "debuglog". " call ch_logfile('debuglog', 'w') @@ -155,6 +161,14 @@ func s:StartDebug_internal(dict) else call s:StartDebug_term(a:dict) endif + + if exists('g:termdebug_disasm_window') + if g:termdebug_disasm_window + let curwinid = win_getid(winnr()) + call s:GotoAsmwinOrCreateIt() + call win_gotoid(curwinid) + endif + endif endfunc " Use when debugger didn't start or ended. @@ -321,9 +335,9 @@ func s:StartDebug_prompt(dict) "call ch_log('executing "' . join(cmd) . '"') let s:gdbjob = jobstart(cmd, { - \ 'on_exit': function('s:EndPromptDebug'), - \ 'on_stdout': function('s:GdbOutCallback'), - \ }) + \ 'on_exit': function('s:EndPromptDebug'), + \ 'on_stdout': function('s:GdbOutCallback'), + \ }) if s:gdbjob == 0 echoerr 'invalid argument (or job table is full) while starting gdb job' exe 'bwipe! ' . s:ptybuf @@ -562,6 +576,15 @@ func s:GetFullname(msg) return name endfunc +" Extract the "addr" value from a gdb message with addr="0x0001234". +func s:GetAsmAddr(msg) + if a:msg !~ 'addr=' + return '' + endif + let addr = s:DecodeMessage(substitute(a:msg, '.*addr=', '', '')) + return addr +endfunc + function s:EndTermDebug(job_id, exit_code, event) unlet s:gdbwin @@ -601,6 +624,66 @@ func s:EndPromptDebug(job_id, exit_code, event) "call ch_log("Returning from EndPromptDebug()") endfunc +" - CommOutput: disassemble $pc +" - CommOutput: &"disassemble $pc\n" +" - CommOutput: ~"Dump of assembler code for function main(int, char**):\n" +" - CommOutput: ~" 0x0000555556466f69 <+0>:\tpush rbp\n" +" ... +" - CommOutput: ~" 0x0000555556467cd0:\tpop rbp\n" +" - CommOutput: ~" 0x0000555556467cd1:\tret \n" +" - CommOutput: ~"End of assembler dump.\n" +" - CommOutput: ^done + +" - CommOutput: disassemble $pc +" - CommOutput: &"disassemble $pc\n" +" - CommOutput: &"No function contains specified address.\n" +" - CommOutput: ^error,msg="No function contains specified address." +func s:HandleDisasmMsg(msg) + if a:msg =~ '^\^done' + let curwinid = win_getid(winnr()) + if win_gotoid(s:asmwin) + silent normal! gg0"_dG + call setline(1, s:asm_lines) + set nomodified + set filetype=asm + + let lnum = search('^' . s:asm_addr) + if lnum != 0 + exe 'sign unplace ' . s:asm_id + exe 'sign place ' . s:asm_id . ' line=' . lnum . ' name=debugPC' + endif + + call win_gotoid(curwinid) + endif + + let s:parsing_disasm_msg = 0 + let s:asm_lines = [] + elseif a:msg =~ '^\^error,msg=' + if s:parsing_disasm_msg == 1 + " Disassemble call ran into an error. This can happen when gdb can't + " find the function frame address, so let's try to disassemble starting + " at current PC + call s:SendCommand('disassemble $pc,+100') + endif + let s:parsing_disasm_msg = 0 + elseif a:msg =~ '\&\"disassemble \$pc' + if a:msg =~ '+100' + " This is our second disasm attempt + let s:parsing_disasm_msg = 2 + endif + else + let value = substitute(a:msg, '^\~\"[ ]*', '', '') + let value = substitute(value, '^=>[ ]*', '', '') + let value = substitute(value, '\\n\"
$', '', '') + let value = substitute(value, '
', '', '') + let value = substitute(value, '\\t', ' ', 'g') + + if value != '' || !empty(s:asm_lines) + call add(s:asm_lines, value) + endif + endif +endfunc + func s:CommOutput(job_id, msgs, event) for msg in a:msgs @@ -608,7 +691,10 @@ func s:CommOutput(job_id, msgs, event) if msg[0] == "\n" let msg = msg[1:] endif - if msg != '' + + if s:parsing_disasm_msg + call s:HandleDisasmMsg(msg) + elseif msg != '' if msg =~ '^\(\*stopped\|\*running\|=thread-selected\)' call s:HandleCursor(msg) elseif msg =~ '^\^done,bkpt=' || msg =~ '^=breakpoint-created,' @@ -621,6 +707,9 @@ func s:CommOutput(job_id, msgs, event) call s:HandleEvaluate(msg) elseif msg =~ '^\^error,msg=' call s:HandleError(msg) + elseif msg =~ '^disassemble' + let s:parsing_disasm_msg = 1 + let s:asm_lines = [] endif endif endfor @@ -651,6 +740,7 @@ func s:InstallCommands() command Gdb call win_gotoid(s:gdbwin) command Program call win_gotoid(s:ptywin) command Source call s:GotoSourcewinOrCreateIt() + command Asm call s:GotoAsmwinOrCreateIt() command Winbar call s:InstallWinbar() " TODO: can the K mapping be restored? @@ -689,6 +779,7 @@ func s:DeleteCommands() delcommand Gdb delcommand Program delcommand Source + delcommand Asm delcommand Winbar nunmap K @@ -963,6 +1054,48 @@ func s:GotoSourcewinOrCreateIt() endif endfunc +func s:GotoAsmwinOrCreateIt() + if !win_gotoid(s:asmwin) + if win_gotoid(s:sourcewin) + exe 'rightbelow new' + else + exe 'new' + endif + + let s:asmwin = win_getid(winnr()) + + setlocal nowrap + setlocal number + setlocal noswapfile + setlocal buftype=nofile + + let asmbuf = bufnr('Termdebug-asm-listing') + if asmbuf > 0 + exe 'buffer' . asmbuf + else + exe 'file Termdebug-asm-listing' + endif + + if exists('g:termdebug_disasm_window') + if g:termdebug_disasm_window > 1 + exe 'resize ' . g:termdebug_disasm_window + endif + endif + endif + + if s:asm_addr != '' + let lnum = search('^' . s:asm_addr) + if lnum == 0 + if s:stopped + call s:SendCommand('disassemble $pc') + endif + else + exe 'sign unplace ' . s:asm_id + exe 'sign place ' . s:asm_id . ' line=' . lnum . ' name=debugPC' + endif + endif +endfunc + " Handle stopping and running message from gdb. " Will update the sign that shows the current position. func s:HandleCursor(msg) @@ -981,10 +1114,31 @@ func s:HandleCursor(msg) else let fname = '' endif + + if a:msg =~ 'addr=' + let asm_addr = s:GetAsmAddr(a:msg) + if asm_addr != '' + let s:asm_addr = asm_addr + + let curwinid = win_getid(winnr()) + if win_gotoid(s:asmwin) + let lnum = search('^' . s:asm_addr) + if lnum == 0 + call s:SendCommand('disassemble $pc') + else + exe 'sign unplace ' . s:asm_id + exe 'sign place ' . s:asm_id . ' line=' . lnum . ' name=debugPC' + endif + + call win_gotoid(curwinid) + endif + endif + endif + if a:msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname) let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '') if lnum =~ '^[0-9]*$' - call s:GotoSourcewinOrCreateIt() + call s:GotoSourcewinOrCreateIt() if expand('%:p') != fnamemodify(fname, ':p') if &modified " TODO: find existing window diff --git a/runtime/queries/c/highlights.scm b/runtime/queries/c/highlights.scm index 96b43cf0d0..260750a85b 100644 --- a/runtime/queries/c/highlights.scm +++ b/runtime/queries/c/highlights.scm @@ -14,6 +14,7 @@ "union" "volatile" "goto" + "register" ] @keyword [ @@ -81,6 +82,8 @@ "|=" "&=" "^=" + ">>=" + "<<=" "--" "++" ] @operator @@ -117,7 +120,6 @@ (preproc_arg) (preproc_defined) ] @function.macro -; TODO (preproc_arg) @embedded (field_identifier) @property (statement_identifier) @label @@ -129,13 +131,22 @@ (type_descriptor) ] @type -(declaration type: [(identifier) (type_identifier)] @type) -(cast_expression type: [(identifier) (type_identifier)] @type) +(declaration (type_qualifier) @type) +(cast_expression type: (type_descriptor) @type) (sizeof_expression value: (parenthesized_expression (identifier) @type)) ((identifier) @constant (#match? @constant "^[A-Z][A-Z0-9_]+$")) +;; Preproc def / undef +(preproc_def + name: (_) @constant) +(preproc_call + directive: (preproc_directive) @_u + argument: (_) @constant + (#eq? @_u "#undef")) + + (comment) @comment ;; Parameters diff --git a/runtime/syntax/lsp_markdown.vim b/runtime/syntax/lsp_markdown.vim new file mode 100644 index 0000000000..d5c1414f01 --- /dev/null +++ b/runtime/syntax/lsp_markdown.vim @@ -0,0 +1,15 @@ +" Vim syntax file +" Language: lsp_markdown +" Maintainer: Michael Lingelbach <m.j.lbach@gmail.com +" URL: http://neovim.io +" Remark: Uses markdown syntax file + +runtime! syntax/markdown.vim + +syn cluster mkdNonListItem add=mkdEscape,mkdNbsp + +syntax region mkdEscape matchgroup=mkdEscape start=/\\\ze[\\\x60*{}\[\]()#+\-,.!_>~|"$%&'\/:;<=?@^ ]/ end=/.\zs/ keepend contains=mkdEscapeCh oneline concealends +syntax match mkdEscapeCh /./ contained +syntax match mkdNbsp / / conceal cchar= + +hi def link mkdEscape special diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 1a37af1c8a..92348d57ec 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -616,7 +616,7 @@ syn region vimGlobal matchgroup=Statement start='\<v\%[global]!\=/' skip='\\.' e " g:vimsyn_embed =~# 'r' : embed ruby " g:vimsyn_embed =~# 't' : embed tcl if !exists("g:vimsyn_embed") - let g:vimsyn_embed= 0 + let g:vimsyn_embed = 'l' endif " [-- lua --] {{{3 diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 2c2e8a024f..66c4454f7b 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1430,6 +1430,18 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, /// - "eol": right after eol character (default) /// - "overlay": display over the specified column, without /// shifting the underlying text. +/// - virt_text_hide : hide the virtual text when the background +/// text is selected or hidden due to +/// horizontal scroll 'nowrap' +/// - hl_mode : control how highlights are combined with the +/// highlights of the text. Currently only affects +/// virt_text highlights, but might affect `hl_group` +/// in later versions. +/// - "replace": only show the virt_text color. This is the +/// default +/// - "combine": combine with background text color +/// - "blend": blend with background text color. +/// /// - ephemeral : for use with |nvim_set_decoration_provider| /// callbacks. The mark will only be used for the current /// redraw cycle, and not be permantently stored in the @@ -1477,11 +1489,10 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, bool ephemeral = false; uint64_t id = 0; - int line2 = -1, hl_id = 0; - DecorPriority priority = DECOR_PRIORITY_BASE; + int line2 = -1; + Decoration decor = DECORATION_INIT; colnr_T col2 = -1; - VirtText virt_text = KV_INITIAL_VALUE; - VirtTextPos virt_text_pos = kVTEndOfLine; + bool right_gravity = true; bool end_right_gravity = false; bool end_gravity_set = false; @@ -1528,12 +1539,12 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, switch (v->type) { case kObjectTypeString: hl_group = v->data.string; - hl_id = syn_check_group( + decor.hl_id = syn_check_group( (char_u *)(hl_group.data), (int)hl_group.size); break; case kObjectTypeInteger: - hl_id = (int)v->data.integer; + decor.hl_id = (int)v->data.integer; break; default: api_set_error(err, kErrorTypeValidation, @@ -1546,7 +1557,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, "virt_text is not an Array"); goto error; } - virt_text = parse_virt_text(v->data.array, err); + decor.virt_text = parse_virt_text(v->data.array, err); if (ERROR_SET(err)) { goto error; } @@ -1558,9 +1569,33 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, } String str = v->data.string; if (strequal("eol", str.data)) { - virt_text_pos = kVTEndOfLine; + decor.virt_text_pos = kVTEndOfLine; } else if (strequal("overlay", str.data)) { - virt_text_pos = kVTOverlay; + decor.virt_text_pos = kVTOverlay; + } else { + api_set_error(err, kErrorTypeValidation, + "virt_text_pos: invalid value"); + goto error; + } + } else if (strequal("virt_text_hide", k.data)) { + decor.virt_text_hide = api_object_to_bool(*v, + "virt_text_hide", false, err); + if (ERROR_SET(err)) { + goto error; + } + } else if (strequal("hl_mode", k.data)) { + if (v->type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, + "hl_mode is not a String"); + goto error; + } + String str = v->data.string; + if (strequal("replace", str.data)) { + decor.hl_mode = kHlModeReplace; + } else if (strequal("combine", str.data)) { + decor.hl_mode = kHlModeCombine; + } else if (strequal("blend", str.data)) { + decor.hl_mode = kHlModeBlend; } else { api_set_error(err, kErrorTypeValidation, "virt_text_pos: invalid value"); @@ -1583,7 +1618,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, "priority is not a valid value"); goto error; } - priority = (DecorPriority)v->data.integer; + decor.priority = (DecorPriority)v->data.integer; } else if (strequal("right_gravity", k.data)) { if (v->type != kObjectTypeBoolean) { api_set_error(err, kErrorTypeValidation, @@ -1631,23 +1666,23 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, col2 = 0; } - Decoration *decor = NULL, tmp = { 0 }; + Decoration *d = NULL; - if (kv_size(virt_text) || priority != DECOR_PRIORITY_BASE) { + if (ephemeral) { + d = &decor; + } else if (kv_size(decor.virt_text) + || decor.priority != DECOR_PRIORITY_BASE) { // TODO(bfredl): this is a bit sketchy. eventually we should // have predefined decorations for both marks/ephemerals - decor = ephemeral ? &tmp : xcalloc(1, sizeof(*decor)); - decor->hl_id = hl_id; - decor->virt_text = virt_text; - decor->priority = priority; - decor->virt_text_pos = virt_text_pos; - } else if (hl_id) { - decor = decor_hl(hl_id); + d = xcalloc(1, sizeof(*d)); + *d = decor; + } else if (decor.hl_id) { + d = decor_hl(decor.hl_id); } // TODO(bfredl): synergize these two branches even more if (ephemeral && decor_state.buf == buf) { - decor_add_ephemeral((int)line, (int)col, line2, col2, decor, 0); + decor_add_ephemeral((int)line, (int)col, line2, col2, &decor, 0); } else { if (ephemeral) { api_set_error(err, kErrorTypeException, "not yet implemented"); @@ -1655,14 +1690,14 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, } id = extmark_set(buf, (uint64_t)ns_id, id, (int)line, (colnr_T)col, - line2, col2, decor, right_gravity, + line2, col2, d, right_gravity, end_right_gravity, kExtmarkNoUndo); } return (Integer)id; error: - clear_virttext(&virt_text); + clear_virttext(&decor.virt_text); return 0; } diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 7cee569989..d2b787a6f5 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1645,6 +1645,20 @@ bool api_object_to_bool(Object obj, const char *what, } } +int object_to_hl_id(Object obj, const char *what, Error *err) +{ + if (obj.type == kObjectTypeString) { + String str = obj.data.string; + return str.size ? syn_check_group((char_u *)str.data, (int)str.size) : 0; + } else if (obj.type == kObjectTypeInteger) { + return (int)obj.data.integer; + } else { + api_set_error(err, kErrorTypeValidation, + "%s is not a valid highlight", what); + return 0; + } +} + HlMessage parse_hl_msg(Array chunks, Error *err) { HlMessage hl_msg = KV_INITIAL_VALUE; @@ -1720,3 +1734,341 @@ DecorProvider *get_provider(NS ns_id, bool force) return item; } + +static bool parse_float_anchor(String anchor, FloatAnchor *out) +{ + if (anchor.size == 0) { + *out = (FloatAnchor)0; + } + char *str = anchor.data; + if (striequal(str, "NW")) { + *out = 0; // NW is the default + } else if (striequal(str, "NE")) { + *out = kFloatAnchorEast; + } else if (striequal(str, "SW")) { + *out = kFloatAnchorSouth; + } else if (striequal(str, "SE")) { + *out = kFloatAnchorSouth | kFloatAnchorEast; + } else { + return false; + } + return true; +} + +static bool parse_float_relative(String relative, FloatRelative *out) +{ + char *str = relative.data; + if (striequal(str, "editor")) { + *out = kFloatRelativeEditor; + } else if (striequal(str, "win")) { + *out = kFloatRelativeWindow; + } else if (striequal(str, "cursor")) { + *out = kFloatRelativeCursor; + } else { + return false; + } + return true; +} + +static bool parse_float_bufpos(Array bufpos, lpos_T *out) +{ + if (bufpos.size != 2 + || bufpos.items[0].type != kObjectTypeInteger + || bufpos.items[1].type != kObjectTypeInteger) { + return false; + } + out->lnum = bufpos.items[0].data.integer; + out->col = (colnr_T)bufpos.items[1].data.integer; + return true; +} + +static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) +{ + struct { + const char *name; + schar_T chars[8]; + } defaults[] = { + { "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" } }, + { "single", { "┌", "─", "┐", "│", "┘", "─", "└", "│" } }, + { NULL, { { NUL } } }, + }; + + schar_T *chars = fconfig->border_chars; + int *hl_ids = fconfig->border_hl_ids; + + fconfig->border = true; + + if (style.type == kObjectTypeArray) { + Array arr = style.data.array; + size_t size = arr.size; + if (!size || size > 8 || (size & (size-1))) { + api_set_error(err, kErrorTypeValidation, + "invalid number of border chars"); + return; + } + for (size_t i = 0; i < size; i++) { + Object iytem = arr.items[i]; + String string = NULL_STRING; + int hl_id = 0; + if (iytem.type == kObjectTypeArray) { + Array iarr = iytem.data.array; + if (!iarr.size || iarr.size > 2) { + api_set_error(err, kErrorTypeValidation, "invalid border char"); + return; + } + if (iarr.items[0].type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "invalid border char"); + return; + } + string = iarr.items[0].data.string; + if (iarr.size == 2) { + hl_id = object_to_hl_id(iarr.items[1], "border char highlight", err); + if (ERROR_SET(err)) { + return; + } + } + + } else if (iytem.type == kObjectTypeString) { + string = iytem.data.string; + } else { + api_set_error(err, kErrorTypeValidation, "invalid border char"); + return; + } + if (!string.size + || mb_string2cells_len((char_u *)string.data, string.size) != 1) { + api_set_error(err, kErrorTypeValidation, + "border chars must be one cell"); + } + size_t len = MIN(string.size, sizeof(*chars)-1); + memcpy(chars[i], string.data, len); + chars[i][len] = NUL; + hl_ids[i] = hl_id; + } + while (size < 8) { + memcpy(chars+size, chars, sizeof(*chars) * size); + memcpy(hl_ids+size, hl_ids, sizeof(*hl_ids) * size); + size <<= 1; + } + } else if (style.type == kObjectTypeString) { + String str = style.data.string; + if (str.size == 0 || strequal(str.data, "none")) { + fconfig->border = false; + return; + } + for (size_t i = 0; defaults[i].name; i++) { + if (strequal(str.data, defaults[i].name)) { + memcpy(chars, defaults[i].chars, sizeof(defaults[i].chars)); + memset(hl_ids, 0, 8 * sizeof(*hl_ids)); + return; + } + } + api_set_error(err, kErrorTypeValidation, + "invalid border style \"%s\"", str.data); + } +} + +bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, + Error *err) +{ + // TODO(bfredl): use a get/has_key interface instead and get rid of extra + // flags + bool has_row = false, has_col = false, has_relative = false; + bool has_external = false, has_window = false; + bool has_width = false, has_height = false; + bool has_bufpos = false; + + for (size_t i = 0; i < config.size; i++) { + char *key = config.items[i].key.data; + Object val = config.items[i].value; + if (!strcmp(key, "row")) { + has_row = true; + if (val.type == kObjectTypeInteger) { + fconfig->row = (double)val.data.integer; + } else if (val.type == kObjectTypeFloat) { + fconfig->row = val.data.floating; + } else { + api_set_error(err, kErrorTypeValidation, + "'row' key must be Integer or Float"); + return false; + } + } else if (!strcmp(key, "col")) { + has_col = true; + if (val.type == kObjectTypeInteger) { + fconfig->col = (double)val.data.integer; + } else if (val.type == kObjectTypeFloat) { + fconfig->col = val.data.floating; + } else { + api_set_error(err, kErrorTypeValidation, + "'col' key must be Integer or Float"); + return false; + } + } else if (strequal(key, "width")) { + has_width = true; + if (val.type == kObjectTypeInteger && val.data.integer > 0) { + fconfig->width = (int)val.data.integer; + } else { + api_set_error(err, kErrorTypeValidation, + "'width' key must be a positive Integer"); + return false; + } + } else if (strequal(key, "height")) { + has_height = true; + if (val.type == kObjectTypeInteger && val.data.integer > 0) { + fconfig->height= (int)val.data.integer; + } else { + api_set_error(err, kErrorTypeValidation, + "'height' key must be a positive Integer"); + return false; + } + } else if (!strcmp(key, "anchor")) { + if (val.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, + "'anchor' key must be String"); + return false; + } + if (!parse_float_anchor(val.data.string, &fconfig->anchor)) { + api_set_error(err, kErrorTypeValidation, + "Invalid value of 'anchor' key"); + return false; + } + } else if (!strcmp(key, "relative")) { + if (val.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, + "'relative' key must be String"); + return false; + } + // ignore empty string, to match nvim_win_get_config + if (val.data.string.size > 0) { + has_relative = true; + if (!parse_float_relative(val.data.string, &fconfig->relative)) { + api_set_error(err, kErrorTypeValidation, + "Invalid value of 'relative' key"); + return false; + } + } + } else if (!strcmp(key, "win")) { + has_window = true; + if (val.type != kObjectTypeInteger + && val.type != kObjectTypeWindow) { + api_set_error(err, kErrorTypeValidation, + "'win' key must be Integer or Window"); + return false; + } + fconfig->window = (Window)val.data.integer; + } else if (!strcmp(key, "bufpos")) { + if (val.type != kObjectTypeArray) { + api_set_error(err, kErrorTypeValidation, + "'bufpos' key must be Array"); + return false; + } + if (!parse_float_bufpos(val.data.array, &fconfig->bufpos)) { + api_set_error(err, kErrorTypeValidation, + "Invalid value of 'bufpos' key"); + return false; + } + has_bufpos = true; + } else if (!strcmp(key, "external")) { + if (val.type == kObjectTypeInteger) { + fconfig->external = val.data.integer; + } else if (val.type == kObjectTypeBoolean) { + fconfig->external = val.data.boolean; + } else { + api_set_error(err, kErrorTypeValidation, + "'external' key must be Boolean"); + return false; + } + has_external = fconfig->external; + } else if (!strcmp(key, "focusable")) { + if (val.type == kObjectTypeInteger) { + fconfig->focusable = val.data.integer; + } else if (val.type == kObjectTypeBoolean) { + fconfig->focusable = val.data.boolean; + } else { + api_set_error(err, kErrorTypeValidation, + "'focusable' key must be Boolean"); + return false; + } + } else if (!strcmp(key, "border")) { + parse_border_style(val, fconfig, err); + if (ERROR_SET(err)) { + return false; + } + } else if (!strcmp(key, "style")) { + if (val.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, + "'style' key must be String"); + return false; + } + if (val.data.string.data[0] == NUL) { + fconfig->style = kWinStyleUnused; + } else if (striequal(val.data.string.data, "minimal")) { + fconfig->style = kWinStyleMinimal; + } else { + api_set_error(err, kErrorTypeValidation, + "Invalid value of 'style' key"); + } + } else { + api_set_error(err, kErrorTypeValidation, + "Invalid key '%s'", key); + return false; + } + } + + if (has_window && !(has_relative + && fconfig->relative == kFloatRelativeWindow)) { + api_set_error(err, kErrorTypeValidation, + "'win' key is only valid with relative='win'"); + return false; + } + + if ((has_relative && fconfig->relative == kFloatRelativeWindow) + && (!has_window || fconfig->window == 0)) { + fconfig->window = curwin->handle; + } + + if (has_window && !has_bufpos) { + fconfig->bufpos.lnum = -1; + } + + if (has_bufpos) { + if (!has_row) { + fconfig->row = (fconfig->anchor & kFloatAnchorSouth) ? 0 : 1; + has_row = true; + } + if (!has_col) { + fconfig->col = 0; + has_col = true; + } + } + + if (has_relative && has_external) { + api_set_error(err, kErrorTypeValidation, + "Only one of 'relative' and 'external' must be used"); + return false; + } else if (!reconf && !has_relative && !has_external) { + api_set_error(err, kErrorTypeValidation, + "One of 'relative' and 'external' must be used"); + return false; + } else if (has_relative) { + fconfig->external = false; + } + + if (!reconf && !(has_height && has_width)) { + api_set_error(err, kErrorTypeValidation, + "Must specify 'width' and 'height'"); + return false; + } + + if (fconfig->external && !ui_has(kUIMultigrid)) { + api_set_error(err, kErrorTypeValidation, + "UI doesn't support external windows"); + return false; + } + + if (has_relative != has_row || has_row != has_col) { + api_set_error(err, kErrorTypeValidation, + "'relative' requires 'row'/'col' or 'bufpos'"); + return false; + } + return true; +} diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 586123aac1..9dde62f0ee 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -39,6 +39,7 @@ #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/fileio.h" +#include "nvim/move.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/state.h" @@ -241,8 +242,7 @@ void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err) /// /// @param ns_id the namespace to activate /// @param[out] err Error details, if any -void nvim_set_hl_ns(Integer ns_id, Error *err) - FUNC_API_SINCE(7) +void nvim__set_hl_ns(Integer ns_id, Error *err) FUNC_API_FAST { if (ns_id >= 0) { @@ -1246,6 +1246,99 @@ fail: return 0; } +/// Open a terminal instance in a buffer +/// +/// By default (and currently the only option) the terminal will not be +/// connected to an external process. Instead, input send on the channel +/// will be echoed directly by the terminal. This is useful to disply +/// ANSI terminal sequences returned as part of a rpc message, or similar. +/// +/// Note: to directly initiate the terminal using the right size, display the +/// buffer in a configured window before calling this. For instance, for a +/// floating display, first create an empty buffer using |nvim_create_buf()|, +/// then display it using |nvim_open_win()|, and then call this function. +/// Then |nvim_chan_send()| cal be called immediately to process sequences +/// in a virtual terminal having the intended size. +/// +/// @param buffer the buffer to use (expected to be empty) +/// @param opts Optional parameters. Reserved for future use. +/// @param[out] err Error details, if any +Integer nvim_open_term(Buffer buffer, Dictionary opts, Error *err) + FUNC_API_SINCE(7) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return 0; + } + + if (opts.size > 0) { + api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); + return 0; + } + + TerminalOptions topts; + Channel *chan = channel_alloc(kChannelStreamInternal); + topts.data = chan; + // NB: overriden in terminal_check_size if a window is already + // displaying the buffer + topts.width = (uint16_t)MAX(curwin->w_width_inner - win_col_off(curwin), 0); + topts.height = (uint16_t)curwin->w_height_inner; + topts.write_cb = term_write; + topts.resize_cb = term_resize; + topts.close_cb = term_close; + Terminal *term = terminal_open(buf, topts); + terminal_check_size(term); + chan->term = term; + channel_incref(chan); + return (Integer)chan->id; +} + +static void term_write(char *buf, size_t size, void *data) +{ + // TODO(bfredl): lua callback +} + +static void term_resize(uint16_t width, uint16_t height, void *data) +{ + // TODO(bfredl): lua callback +} + +static void term_close(void *data) +{ + Channel *chan = data; + terminal_destroy(chan->term); + chan->term = NULL; + channel_decref(chan); +} + + +/// Send data to channel `id`. For a job, it writes it to the +/// stdin of the process. For the stdio channel |channel-stdio|, +/// it writes to Nvim's stdout. For an internal terminal instance +/// (|nvim_open_term()|) it writes directly to terimal output. +/// See |channel-bytes| for more information. +/// +/// This function writes raw data, not RPC messages. If the channel +/// was created with `rpc=true` then the channel expects RPC +/// messages, use |vim.rpcnotify()| and |vim.rpcrequest()| instead. +/// +/// @param chan id of the channel +/// @param data data to write. 8-bit clean: can contain NUL bytes. +/// @param[out] err Error details, if any +void nvim_chan_send(Integer chan, String data, Error *err) + FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY FUNC_API_LUA_ONLY +{ + const char *error = NULL; + if (!data.size) { + return; + } + + channel_send((uint64_t)chan, data.data, data.size, &error); + if (error) { + api_set_error(err, kErrorTypeValidation, "%s", error); + } +} + /// Open a new window. /// /// Currently this is used to open floating and external windows. @@ -1323,6 +1416,25 @@ fail: /// end-of-buffer region is hidden by setting `eob` flag of /// 'fillchars' to a space char, and clearing the /// |EndOfBuffer| region in 'winhighlight'. +/// - `border`: style of (optional) window border. This can either be a string +/// or an array. the string values are: +/// - "none" No border. This is the default +/// - "single" a single line box +/// - "double" a double line box +/// If it is an array it should be an array of eight items or any divisor of +/// eight. The array will specifify the eight chars building up the border +/// in a clockwise fashion starting with the top-left corner. As, an +/// example, the double box style could be specified as: +/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ] +/// if the number of chars are less than eight, they will be repeated. Thus +/// an ASCII border could be specified as: +/// [ "/", "-", "\\", "|" ] +/// or all chars the same as: +/// [ "x" ] +/// By default `FloatBorder` highlight is used which links to `VertSplit` +/// when not defined. It could also be specified by character: +/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ] +/// /// @param[out] err Error details, if any /// /// @return Window handle, or 0 on error @@ -2738,8 +2850,8 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err) g = &pum_grid; } else if (grid > 1) { win_T *wp = get_win_by_grid_handle((handle_T)grid); - if (wp != NULL && wp->w_grid.chars != NULL) { - g = &wp->w_grid; + if (wp != NULL && wp->w_grid_alloc.chars != NULL) { + g = &wp->w_grid_alloc; } else { api_set_error(err, kErrorTypeValidation, "No grid with the given handle"); diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 3de2e0f342..f71075ae74 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -968,7 +968,7 @@ static int do_autocmd_event(event_T event, // Implementation of ":doautocmd [group] event [fname]". // Return OK for success, FAIL for failure; int do_doautocmd(char_u *arg, - int do_msg, // give message for no matching autocmds? + bool do_msg, // give message for no matching autocmds? bool *did_something) { char_u *fname; @@ -1017,11 +1017,12 @@ int do_doautocmd(char_u *arg, // ":doautoall": execute autocommands for each loaded buffer. void ex_doautoall(exarg_T *eap) { - int retval; + int retval = OK; aco_save_T aco; char_u *arg = eap->arg; int call_do_modelines = check_nomodeline(&arg); bufref_T bufref; + bool did_aucmd; // This is a bit tricky: For some commands curwin->w_buffer needs to be // equal to curbuf, but for some buffers there may not be a window. @@ -1029,14 +1030,14 @@ void ex_doautoall(exarg_T *eap) // gives problems when the autocommands make changes to the list of // buffers or windows... FOR_ALL_BUFFERS(buf) { - if (buf->b_ml.ml_mfp == NULL) { + // Only do loaded buffers and skip the current buffer, it's done last. + if (buf->b_ml.ml_mfp == NULL || buf == curbuf) { continue; } // Find a window for this buffer and save some values. aucmd_prepbuf(&aco, buf); set_bufref(&bufref, buf); - bool did_aucmd; // execute the autocommands for this buffer retval = do_doautocmd(arg, false, &did_aucmd); @@ -1052,10 +1053,19 @@ void ex_doautoall(exarg_T *eap) // Stop if there is some error or buffer was deleted. if (retval == FAIL || !bufref_valid(&bufref)) { + retval = FAIL; break; } } + // Execute autocommands for the current buffer last. + if (retval == OK) { + (void)do_doautocmd(arg, false, &did_aucmd); + if (call_do_modelines && did_aucmd) { + do_modelines(0); + } + } + check_cursor(); // just in case lines got deleted } @@ -1182,10 +1192,10 @@ void aucmd_restbuf(aco_save_T *aco) win_remove(curwin, NULL); handle_unregister_window(curwin); - if (curwin->w_grid.chars != NULL) { - ui_comp_remove_grid(&curwin->w_grid); - ui_call_win_hide(curwin->w_grid.handle); - grid_free(&curwin->w_grid); + if (curwin->w_grid_alloc.chars != NULL) { + ui_comp_remove_grid(&curwin->w_grid_alloc); + ui_call_win_hide(curwin->w_grid_alloc.handle); + grid_free(&curwin->w_grid_alloc); } aucmd_win_used = false; @@ -1661,11 +1671,13 @@ static bool apply_autocmds_group(event_T event, did_filetype = false; while (au_pending_free_buf != NULL) { buf_T *b = au_pending_free_buf->b_next; + xfree(au_pending_free_buf); au_pending_free_buf = b; } while (au_pending_free_win != NULL) { win_T *w = au_pending_free_win->w_next; + xfree(au_pending_free_win); au_pending_free_win = w; } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index a05bd6fcc7..e8038e7281 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -1079,6 +1079,10 @@ typedef struct { bool external; bool focusable; WinStyle style; + bool border; + schar_T border_chars[8]; + int border_hl_ids[8]; + int border_attr[8]; } FloatConfig; #define FLOAT_CONFIG_INIT ((FloatConfig){ .height = 0, .width = 0, \ @@ -1256,6 +1260,11 @@ struct window_S { int w_height_request; int w_width_request; + int w_border_adj; + // outer size of window grid, including border + int w_height_outer; + int w_width_outer; + /* * === start of cached values ==== */ @@ -1331,7 +1340,8 @@ struct window_S { // w_redr_type is REDRAW_TOP linenr_T w_redraw_top; // when != 0: first line needing redraw linenr_T w_redraw_bot; // when != 0: last line needing redraw - int w_redr_status; // if TRUE status line must be redrawn + bool w_redr_status; // if true status line must be redrawn + bool w_redr_border; // if true border must be redrawn // remember what is shown in the ruler for this window (if 'ruler' set) pos_T w_ru_cursor; // cursor position shown in ruler @@ -1409,6 +1419,7 @@ struct window_S { int w_tagstacklen; // number of tags on stack ScreenGrid w_grid; // the grid specific to the window + ScreenGrid w_grid_alloc; // the grid specific to the window bool w_pos_changed; // true if window position changed bool w_floating; ///< whether the window is floating FloatConfig w_float_config; diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 09a34ca9fe..7a08ba58d0 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -161,7 +161,7 @@ void channel_init(void) /// /// Channel is allocated with refcount 1, which should be decreased /// when the underlying stream closes. -static Channel *channel_alloc(ChannelStreamType type) +Channel *channel_alloc(ChannelStreamType type) { Channel *chan = xcalloc(1, sizeof(*chan)); if (type == kChannelStreamStdio) { @@ -503,7 +503,7 @@ size_t channel_send(uint64_t id, char *data, size_t len, const char **error) { Channel *chan = find_channel(id); if (!chan) { - EMSG(_(e_invchan)); + *error = _(e_invchan); goto err; } @@ -518,6 +518,11 @@ size_t channel_send(uint64_t id, char *data, size_t len, const char **error) return len * written; } + if (chan->streamtype == kChannelStreamInternal && chan->term) { + terminal_receive(chan->term, data, len); + return len; + } + Stream *in = channel_instream(chan); if (in->closed) { @@ -724,8 +729,8 @@ static void channel_callback_call(Channel *chan, CallbackReader *reader) /// Open terminal for channel /// /// Channel `chan` is assumed to be an open pty channel, -/// and curbuf is assumed to be a new, unmodified buffer. -void channel_terminal_open(Channel *chan) +/// and `buf` is assumed to be a new, unmodified buffer. +void channel_terminal_open(buf_T *buf, Channel *chan) { TerminalOptions topts; topts.data = chan; @@ -734,8 +739,8 @@ void channel_terminal_open(Channel *chan) topts.write_cb = term_write; topts.resize_cb = term_resize; topts.close_cb = term_close; - curbuf->b_p_channel = (long)chan->id; // 'channel' option - Terminal *term = terminal_open(topts); + buf->b_p_channel = (long)chan->id; // 'channel' option + Terminal *term = terminal_open(buf, topts); chan->term = term; channel_incref(chan); } diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index a1289f202a..e16598e7d2 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -145,8 +145,7 @@ bool decor_redraw_reset(buf_T *buf, DecorState *state) for (size_t i = 0; i < kv_size(state->active); i++) { HlRange item = kv_A(state->active, i); if (item.virt_text_owned) { - clear_virttext(item.virt_text); - xfree(item.virt_text); + clear_virttext(&item.virt_text); } } kv_size(state->active) = 0; @@ -229,8 +228,8 @@ static void decor_add(DecorState *state, int start_row, int start_col, HlRange range = { start_row, start_col, end_row, end_col, attr_id, MAX(priority, decor->priority), - kv_size(decor->virt_text) ? &decor->virt_text : NULL, - decor->virt_text_pos, + decor->virt_text, + decor->virt_text_pos, decor->virt_text_hide, decor->hl_mode, kv_size(decor->virt_text) && owned, -1 }; kv_pushp(state->active); @@ -245,7 +244,8 @@ static void decor_add(DecorState *state, int start_row, int start_col, kv_A(state->active, index) = range; } -int decor_redraw_col(buf_T *buf, int col, int virt_col, DecorState *state) +int decor_redraw_col(buf_T *buf, int col, int virt_col, bool hidden, + DecorState *state) { if (col <= state->col_until) { return state->current; @@ -303,7 +303,7 @@ next_mark: bool active = false, keep = true; if (item.end_row < state->row || (item.end_row == state->row && item.end_col <= col)) { - if (!(item.start_row >= state->row && item.virt_text)) { + if (!(item.start_row >= state->row && kv_size(item.virt_text))) { keep = false; } } else { @@ -323,14 +323,13 @@ next_mark: attr = hl_combine_attr(attr, item.attr_id); } if ((item.start_row == state->row && item.start_col <= col) - && item.virt_text && item.virt_col == -1) { - item.virt_col = virt_col; + && kv_size(item.virt_text) && item.virt_col == -1) { + item.virt_col = (item.virt_text_hide && hidden) ? -2 : virt_col; } if (keep) { kv_A(state->active, j++) = item; } else if (item.virt_text_owned) { - clear_virttext(item.virt_text); - xfree(item.virt_text); + clear_virttext(&item.virt_text); } } kv_size(state->active) = j; @@ -343,22 +342,26 @@ void decor_redraw_end(DecorState *state) state->buf = NULL; } -VirtText *decor_redraw_virt_text(buf_T *buf, DecorState *state) +VirtText decor_redraw_virt_text(buf_T *buf, DecorState *state) { - decor_redraw_col(buf, MAXCOL, MAXCOL, state); + decor_redraw_col(buf, MAXCOL, MAXCOL, false, state); for (size_t i = 0; i < kv_size(state->active); i++) { HlRange item = kv_A(state->active, i); - if (item.start_row == state->row && item.virt_text + if (item.start_row == state->row && kv_size(item.virt_text) && item.virt_text_pos == kVTEndOfLine) { return item.virt_text; } } - return NULL; + return VIRTTEXT_EMPTY; } void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col, Decoration *decor, DecorPriority priority) { + if (end_row == -1) { + end_row = start_row; + end_col = start_col; + } decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true, priority); } diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index 47bd9abbc3..c5424a1642 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -23,15 +23,26 @@ typedef enum { kVTOverlay, } VirtTextPos; +typedef enum { + kHlModeUnknown, + kHlModeReplace, + kHlModeCombine, + kHlModeBlend, +} HlMode; + struct Decoration { int hl_id; // highlight group VirtText virt_text; VirtTextPos virt_text_pos; + bool virt_text_hide; + HlMode hl_mode; // TODO(bfredl): style, signs, etc DecorPriority priority; bool shared; // shared decoration, don't free }; +#define DECORATION_INIT { 0, KV_INITIAL_VALUE, kVTEndOfLine, false, \ + kHlModeUnknown, DECOR_PRIORITY_BASE, false } typedef struct { int start_row; @@ -39,9 +50,13 @@ typedef struct { int end_row; int end_col; int attr_id; + // TODO(bfredl): embed decoration instead, perhaps using an arena + // for ephemerals? DecorPriority priority; - VirtText *virt_text; + VirtText virt_text; VirtTextPos virt_text_pos; + bool virt_text_hide; + HlMode hl_mode; bool virt_text_owned; int virt_col; } HlRange; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 68c7438ea3..b5d5d67e90 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1565,7 +1565,7 @@ void edit_putchar(int c, bool highlight) { int attr; - if (curwin->w_grid.chars != NULL || default_grid.chars != NULL) { + if (curwin->w_grid_alloc.chars != NULL || default_grid.chars != NULL) { update_topline(curwin); // just in case w_topline isn't valid validate_cursor(); if (highlight) { @@ -8764,6 +8764,10 @@ static bool ins_tab(void) getvcol(curwin, &fpos, &vcol, NULL, NULL); getvcol(curwin, cursor, &want_vcol, NULL, NULL); + // save start of changed region for extmark_splice + int start_row = fpos.lnum; + colnr_T start_col = fpos.col; + // Use as many TABs as possible. Beware of 'breakindent', 'showbreak' // and 'linebreak' adding extra virtual columns. while (ascii_iswhite(*ptr)) { @@ -8813,6 +8817,11 @@ static bool ins_tab(void) replace_join(repl_off); } } + if (!(State & VREPLACE_FLAG)) { + extmark_splice_cols(curbuf, start_row - 1, start_col, + cursor->col - start_col, fpos.col - start_col, + kExtmarkUndo); + } } cursor->col -= i; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 63d5216cc4..e1fcbdce25 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -3007,7 +3007,8 @@ static size_t varnamebuflen = 0; /* * Function to concatenate a prefix and a variable name. */ -static char_u *cat_prefix_varname(int prefix, char_u *name) +char_u *cat_prefix_varname(int prefix, const char_u *name) + FUNC_ATTR_NONNULL_ALL { size_t len = STRLEN(name) + 3; @@ -8500,7 +8501,7 @@ static bool tv_is_luafunc(typval_T *tv) int check_luafunc_name(const char *str, bool paren) { const char *p = str; - while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.') { + while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.' || *p == '\'') { p++; } if (*p != (paren ? '(' : NUL)) { diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index eac0feafcf..d1a3ae3ff8 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -341,6 +341,7 @@ return { string={args=1}, strlen={args=1}, strpart={args={2, 4}}, + strptime={args=2}, strridx={args={2, 3}}, strtrans={args=1}, strwidth={args=1}, @@ -392,6 +393,7 @@ return { win_id2tabwin={args=1}, win_id2win={args=1}, win_screenpos={args=1}, + win_splitmove={args={2, 3}}, winbufnr={args=1}, wincol={}, windowsversion={}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 60229e1ebc..650b4e3882 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -117,8 +117,12 @@ char_u *get_function_name(expand_T *xp, int idx) intidx = -1; if (intidx < 0) { name = get_user_func_name(xp, idx); - if (name != NULL) + if (name != NULL) { + if (*name != '<' && STRNCMP("g:", xp->xp_pattern, 2) == 0) { + return cat_prefix_varname('g', name); + } return name; + } } while ((size_t)++intidx < ARRAY_SIZE(functions) && functions[intidx].name[0] == '\0') { @@ -3976,6 +3980,87 @@ static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1); } +// +// Move the window wp into a new split of targetwin in a given direction +// +static void win_move_into_split(win_T *wp, win_T *targetwin, + int size, int flags) +{ + int dir; + int height = wp->w_height; + win_T *oldwin = curwin; + + if (wp == targetwin) { + return; + } + + // Jump to the target window + if (curwin != targetwin) { + win_goto(targetwin); + } + + // Remove the old window and frame from the tree of frames + (void)winframe_remove(wp, &dir, NULL); + win_remove(wp, NULL); + last_status(false); // may need to remove last status line + (void)win_comp_pos(); // recompute window positions + + // Split a window on the desired side and put the old window there + (void)win_split_ins(size, flags, wp, dir); + + // If splitting horizontally, try to preserve height + if (size == 0 && !(flags & WSP_VERT)) { + win_setheight_win(height, wp); + if (p_ea) { + win_equal(wp, true, 'v'); + } + } + + if (oldwin != curwin) { + win_goto(oldwin); + } +} + +// "win_splitmove()" function +static void f_win_splitmove(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp; + win_T *targetwin; + int flags = 0, size = 0; + + wp = find_win_by_nr_or_id(&argvars[0]); + targetwin = find_win_by_nr_or_id(&argvars[1]); + + if (wp == NULL || targetwin == NULL || wp == targetwin + || !win_valid(wp) || !win_valid(targetwin) + || win_valid_floating(wp) || win_valid_floating(targetwin)) { + EMSG(_(e_invalwindow)); + rettv->vval.v_number = -1; + return; + } + + if (argvars[2].v_type != VAR_UNKNOWN) { + dict_T *d; + dictitem_T *di; + + if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) { + EMSG(_(e_invarg)); + return; + } + + d = argvars[2].vval.v_dict; + if (tv_dict_get_number(d, "vertical")) { + flags |= WSP_VERT; + } + if ((di = tv_dict_find(d, "rightbelow", -1)) != NULL) { + flags |= tv_get_number(&di->di_tv) ? WSP_BELOW : WSP_ABOVE; + } + size = tv_dict_get_number(d, "size"); + } + + win_move_into_split(wp, targetwin, size, flags); +} + // "getwinpos({timeout})" function static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -8724,8 +8809,6 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (set_tagstack(wp, d, action) == OK) { rettv->vval.v_number = 0; - } else { - EMSG(_(e_listreq)); } } @@ -10106,6 +10189,38 @@ static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = (char_u *)xmemdupz(p + n, (size_t)len); } +// "strptime({format}, {timestring})" function +static void f_strptime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char fmt_buf[NUMBUFLEN]; + char str_buf[NUMBUFLEN]; + + struct tm tmval = { + .tm_isdst = -1, + }; + char *fmt = (char *)tv_get_string_buf(&argvars[0], fmt_buf); + char *str = (char *)tv_get_string_buf(&argvars[1], str_buf); + + vimconv_T conv = { + .vc_type = CONV_NONE, + }; + char_u *enc = enc_locale(); + convert_setup(&conv, p_enc, enc); + if (conv.vc_type != CONV_NONE) { + fmt = (char *)string_convert(&conv, (char_u *)fmt, NULL); + } + if (fmt == NULL + || os_strptime(str, fmt, &tmval) == NULL + || (rettv->vval.v_number = mktime(&tmval)) == -1) { + rettv->vval.v_number = 0; + } + if (conv.vc_type != CONV_NONE) { + xfree(fmt); + } + convert_setup(&conv, NULL, NULL); + xfree(enc); +} + /* * "strridx()" function */ @@ -10731,7 +10846,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) INTEGER_OBJ(pid), false, false, &err); api_clear_error(&err); - channel_terminal_open(chan); + channel_terminal_open(curbuf, chan); channel_create_event(chan, NULL); } @@ -11293,17 +11408,23 @@ static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - int winnr = 1; garray_T ga; char_u buf[50]; ga_init(&ga, (int)sizeof(char), 70); - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - sprintf((char *)buf, "%dresize %d|", winnr, wp->w_height); - ga_concat(&ga, buf); - sprintf((char *)buf, "vert %dresize %d|", winnr, wp->w_width); - ga_concat(&ga, buf); - ++winnr; + + // Do this twice to handle some window layouts properly. + for (int i = 0; i < 2; i++) { + int winnr = 1; + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + snprintf((char *)buf, sizeof(buf), "%dresize %d|", winnr, + wp->w_height); + ga_concat(&ga, buf); + snprintf((char *)buf, sizeof(buf), "vert %dresize %d|", winnr, + wp->w_width); + ga_concat(&ga, buf); + winnr++; + } } ga_append(&ga, NUL); diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 5979f4d3a0..9977be56ca 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -5117,6 +5117,18 @@ ExpandFromContext ( if (xp->xp_context == EXPAND_PACKADD) { return ExpandPackAddDir(pat, num_file, file); } + + // When expanding a function name starting with s:, match the <SNR>nr_ + // prefix. + char_u *tofree = NULL; + if (xp->xp_context == EXPAND_USER_FUNC && STRNCMP(pat, "^s:", 3) == 0) { + const size_t len = STRLEN(pat) + 20; + + tofree = xmalloc(len); + snprintf((char *)tofree, len, "^<SNR>\\d\\+_%s", pat + 3); + pat = tofree; + } + if (xp->xp_context == EXPAND_LUA) { ILOG("PAT %s", pat); return nlua_expand_pat(xp, pat, num_file, file); @@ -5195,6 +5207,7 @@ ExpandFromContext ( } vim_regfree(regmatch.regprog); + xfree(tofree); return ret; } diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index 63789b3981..09453e100d 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -387,9 +387,10 @@ static int put_view( if (wp->w_alt_fnum) { buf_T *const alt = buflist_findnr(wp->w_alt_fnum); - // Set the alternate file. + // Set the alternate file if the buffer is listed. if ((flagp == &ssop_flags) && alt != NULL && alt->b_fname != NULL && *alt->b_fname != NUL + && alt->b_p_bl && (fputs("balt ", fd) < 0 || ses_fname(fd, alt, flagp, true) == FAIL)) { return FAIL; diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 7e78b9e9d6..d2a7c16186 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -104,8 +104,11 @@ for _,f in ipairs(shallowcopy(functions)) do elseif startswith(f.name, "nvim_tabpage_") then ismethod = true end + f.remote = f.remote_only or not f.lua_only + f.lua = f.lua_only or not f.remote_only + f.eval = (not f.lua_only) and (not f.remote_only) else - f.remote_only = true + f.remote = true f.since = 0 f.deprecated_since = 1 end @@ -127,7 +130,8 @@ for _,f in ipairs(shallowcopy(functions)) do newf.return_type = "Object" end newf.impl_name = f.name - newf.remote_only = true + newf.lua = false + newf.eval = false newf.since = 0 newf.deprecated_since = 1 functions[#functions+1] = newf @@ -192,7 +196,7 @@ end -- the real API. for i = 1, #functions do local fn = functions[i] - if fn.impl_name == nil and not fn.lua_only then + if fn.impl_name == nil and fn.remote then local args = {} output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Error *error)') @@ -323,7 +327,7 @@ void msgpack_rpc_init_method_table(void) for i = 1, #functions do local fn = functions[i] - if not fn.lua_only then + if fn.remote then output:write(' msgpack_rpc_add_method_handler('.. '(String) {.data = "'..fn.name..'", '.. '.size = sizeof("'..fn.name..'") - 1}, '.. @@ -492,7 +496,7 @@ local function process_function(fn) end for _, fn in ipairs(functions) do - if not fn.remote_only or fn.name:sub(1, 4) == '_vim' then + if fn.lua or fn.name:sub(1, 4) == '_vim' then process_function(fn) end end diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua index d16453530f..679895421a 100644 --- a/src/nvim/generators/gen_eval.lua +++ b/src/nvim/generators/gen_eval.lua @@ -25,7 +25,7 @@ local gperfpipe = io.open(funcsfname .. '.gperf', 'wb') local funcs = require('eval').funcs local metadata = mpack.unpack(io.open(metadata_file, 'rb'):read("*all")) for _,fun in ipairs(metadata) do - if not (fun.remote_only or fun.lua_only) then + if fun.eval then funcs[fun.name] = { args=#fun.parameters, func='api_wrapper', diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h index e14aae73d8..3b34af46e4 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -7,7 +7,7 @@ #include "nvim/types.h" -#define MAX_MCO 6 // maximum value for 'maxcombine' +#define MAX_MCO 6 // fixed value for 'maxcombine' // The characters and attributes drawn on grids. typedef char_u schar_T[(MAX_MCO+1) * 4 + 1]; @@ -35,7 +35,8 @@ typedef int sattr_T; /// line_wraps[] is an array of boolean flags indicating if the screen line /// wraps to the next line. It can only be true if a window occupies the entire /// screen width. -typedef struct { +typedef struct ScreenGrid ScreenGrid; +struct ScreenGrid { handle_T handle; schar_T *chars; @@ -58,10 +59,13 @@ typedef struct { // external UI. bool throttled; - // offsets for the grid relative to the global screen. Used by screen.c - // for windows that don't have w_grid->chars etc allocated + // TODO(bfredl): maybe physical grids and "views" (i e drawing + // specifications) should be two separate types? + // offsets for the grid relative to another grid. Used for grids + // that are views into another, actually allocated grid 'target' int row_offset; int col_offset; + ScreenGrid *target; // whether the compositor should blend the grid with the background grid bool blending; @@ -76,6 +80,12 @@ typedef struct { int comp_row; int comp_col; + // Requested width and height of the grid upon resize. Used by + // `ui_compositor` to correctly determine which regions need to + // be redrawn. + int comp_width; + int comp_height; + // z-index of the grid. Grids with higher index is draw on top. // default_grid.comp_index is always zero. size_t comp_index; @@ -83,9 +93,10 @@ typedef struct { // compositor should momentarily ignore the grid. Used internally when // moving around grids etc. bool comp_disabled; -} ScreenGrid; +}; #define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, NULL, 0, 0, false, \ - false, 0, 0, false, true, 0, 0, 0, false } + false, 0, 0, NULL, false, true, \ + 0, 0, 0, 0, 0, false } #endif // NVIM_GRID_DEFS_H diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index b01cdde236..f03382bea7 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -341,6 +341,17 @@ void update_window_hl(win_T *wp, bool invalid) } wp->w_hl_attrs[hlf] = attr; } + + if (wp->w_floating && wp->w_float_config.border) { + for (int i = 0; i < 8; i++) { + int attr = wp->w_hl_attrs[HLF_BORDER]; + if (wp->w_float_config.border_hl_ids[i]) { + attr = hl_get_ui_attr(HLF_BORDER, wp->w_float_config.border_hl_ids[i], + false); + } + wp->w_float_config.border_attr[i] = attr; + } + } } /// Gets HL_UNDERLINE highlight. @@ -517,6 +528,10 @@ static HlAttrs get_colors_force(int attr) /// @return the resulting attributes. int hl_blend_attrs(int back_attr, int front_attr, bool *through) { + if (front_attr < 0 || back_attr < 0) { + return -1; + } + HlAttrs fattrs = get_colors_force(front_attr); int ratio = fattrs.hl_blend; if (ratio <= 0) { diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 2bda094d8e..ed4aefb577 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -101,6 +101,7 @@ typedef enum { , HLF_MSGSEP // message separator line , HLF_NFLOAT // Floating window , HLF_MSG // Message area + , HLF_BORDER // Floating window border , HLF_COUNT // MUST be the last one } hlf_T; @@ -155,6 +156,7 @@ EXTERN const char *hlf_names[] INIT(= { [HLF_MSGSEP] = "MsgSeparator", [HLF_NFLOAT] = "NormalFloat", [HLF_MSG] = "MsgArea", + [HLF_BORDER] = "FloatBorder", }); diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index a640b97d3b..33974c71cb 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -171,7 +171,7 @@ int tslua_add_language(lua_State *L) TSLanguage *lang = lang_parser(); if (lang == NULL) { - return luaL_error(L, "Failed to load parser: internal error"); + return luaL_error(L, "Failed to load parser %s: internal error", path); } uint32_t lang_version = ts_language_version(lang); @@ -179,7 +179,8 @@ int tslua_add_language(lua_State *L) || lang_version > TREE_SITTER_LANGUAGE_VERSION) { return luaL_error( L, - "ABI version mismatch : supported between %d and %d, found %d", + "ABI version mismatch for %s: supported between %d and %d, found %d", + path, TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION, TREE_SITTER_LANGUAGE_VERSION, lang_version); } diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index ec4f4cbc21..73e3ba53a5 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -571,11 +571,12 @@ size_t mb_string2cells(const char_u *str) /// @param size maximum length of string. It will terminate on earlier NUL. /// @return The number of cells occupied by string `str` size_t mb_string2cells_len(const char_u *str, size_t size) + FUNC_ATTR_NONNULL_ARG(1) { size_t clen = 0; for (const char_u *p = str; *p != NUL && p < str+size; - p += utf_ptr2len_len(p, size+(p-str))) { + p += utfc_ptr2len_len(p, size+(p-str))) { clen += utf_ptr2cells(p); } diff --git a/src/nvim/message.c b/src/nvim/message.c index ba7a667a60..71cb345878 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -178,6 +178,7 @@ void msg_grid_validate(void) msg_grid.throttled = false; // don't throttle in 'cmdheight' area msg_scrolled_at_flush = msg_scrolled; msg_grid.focusable = false; + msg_grid_adj.target = &msg_grid; if (!msg_scrolled) { msg_grid_set_pos(Rows - p_ch, false); } @@ -188,6 +189,7 @@ void msg_grid_validate(void) ui_call_grid_destroy(msg_grid.handle); msg_grid.throttled = false; msg_grid_adj.row_offset = 0; + msg_grid_adj.target = &default_grid; redraw_cmdline = true; } else if (msg_grid.chars && !msg_scrolled && msg_grid_pos != Rows - p_ch) { msg_grid_set_pos(Rows - p_ch, false); diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index f28658aa29..fa9787a3ac 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -470,21 +470,21 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp) *gridp = DEFAULT_GRID_HANDLE; } else if (*gridp > 1) { win_T *wp = get_win_by_grid_handle(*gridp); - if (wp && wp->w_grid.chars + if (wp && wp->w_grid_alloc.chars && !(wp->w_floating && !wp->w_float_config.focusable)) { - *rowp = MIN(*rowp, wp->w_grid.Rows-1); - *colp = MIN(*colp, wp->w_grid.Columns-1); + *rowp = MIN(*rowp-wp->w_grid.row_offset, wp->w_grid.Rows-1); + *colp = MIN(*colp-wp->w_grid.col_offset, wp->w_grid.Columns-1); return wp; } } else if (*gridp == 0) { ScreenGrid *grid = ui_comp_mouse_focus(*rowp, *colp); FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (&wp->w_grid != grid) { + if (&wp->w_grid_alloc != grid) { continue; } *gridp = grid->handle; - *rowp -= grid->comp_row; - *colp -= grid->comp_col; + *rowp -= grid->comp_row+wp->w_grid.row_offset; + *colp -= grid->comp_col+wp->w_grid.col_offset; return wp; } @@ -717,23 +717,22 @@ static int mouse_adjust_click(win_T *wp, int row, int col) // Check clicked cell is foldcolumn int mouse_check_fold(void) { - int grid = mouse_grid; - int row = mouse_row; - int col = mouse_col; + int click_grid = mouse_grid; + int click_row = mouse_row; + int click_col = mouse_col; int mouse_char = ' '; win_T *wp; - wp = mouse_find_win(&grid, &row, &col); + wp = mouse_find_win(&click_grid, &click_row, &click_col); if (wp && mouse_row >= 0 && mouse_row < Rows && mouse_col >= 0 && mouse_col <= Columns) { int multigrid = ui_has(kUIMultigrid); - ScreenGrid *gp = multigrid ? &wp->w_grid : &default_grid; + ScreenGrid *gp = multigrid ? &wp->w_grid_alloc : &default_grid; int fdc = win_fdccol_count(wp); - - row = multigrid && mouse_grid == 0 ? row : mouse_row; - col = multigrid && mouse_grid == 0 ? col : mouse_col; + int row = multigrid && mouse_grid == 0 ? click_row : mouse_row; + int col = multigrid && mouse_grid == 0 ? click_col : mouse_col; // Remember the character under the mouse, might be one of foldclose or // foldopen fillchars in the fold column. @@ -743,8 +742,8 @@ int mouse_check_fold(void) } // Check for position outside of the fold column. - if (wp->w_p_rl ? col < wp->w_width_inner - fdc : - col >= fdc + (cmdwin_type == 0 ? 0 : 1)) { + if (wp->w_p_rl ? click_col < wp->w_width_inner - fdc : + click_col >= fdc + (cmdwin_type == 0 ? 0 : 1)) { mouse_char = ' '; } } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 3038fad894..0ff427c261 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -3177,7 +3177,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) // insert the new text totlen = (size_t)(count * (yanklen + spaces) + bd.startspaces + bd.endspaces); - newp = (char_u *) xmalloc(totlen + oldlen + 1); + int addcount = (int)totlen + lines_appended; + newp = (char_u *)xmalloc(totlen + oldlen + 1); // copy part up to cursor to new line ptr = newp; memmove(ptr, oldp, (size_t)bd.textcol); @@ -3194,6 +3195,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if ((j < count - 1 || !shortline) && spaces) { memset(ptr, ' ', (size_t)spaces); ptr += spaces; + } else { + addcount -= spaces; } } // may insert some spaces after the new text @@ -3205,7 +3208,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) memmove(ptr, oldp + bd.textcol + delcount, (size_t)columns); ml_replace(curwin->w_cursor.lnum, newp, false); extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum-1, bd.textcol, - delcount, (int)totlen + lines_appended, kExtmarkUndo); + delcount, addcount, kExtmarkUndo); ++curwin->w_cursor.lnum; if (i == 0) diff --git a/src/nvim/option.c b/src/nvim/option.c index ac25c86b5f..949558894f 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -379,8 +379,8 @@ void set_init_1(bool clean_arg) # else static char *(names[3]) = {"TMPDIR", "TEMP", "TMP"}; # endif - int len; garray_T ga; + opt_idx = findoption("backupskip"); ga_init(&ga, 1, 100); for (size_t n = 0; n < ARRAY_SIZE(names); n++) { @@ -401,15 +401,23 @@ void set_init_1(bool clean_arg) } if (p != NULL && *p != NUL) { // First time count the NUL, otherwise count the ','. - len = (int)strlen(p) + 3; - ga_grow(&ga, len); - if (!GA_EMPTY(&ga)) { - STRCAT(ga.ga_data, ","); + const size_t len = strlen(p) + 3; + char *item = xmalloc(len); + xstrlcpy(item, p, len); + add_pathsep(item); + xstrlcat(item, "*", len); + if (find_dup_item(ga.ga_data, (char_u *)item, options[opt_idx].flags) + == NULL) { + ga_grow(&ga, (int)len); + if (!GA_EMPTY(&ga)) { + STRCAT(ga.ga_data, ","); + } + STRCAT(ga.ga_data, p); + add_pathsep(ga.ga_data); + STRCAT(ga.ga_data, "*"); + ga.ga_len += (int)len; } - STRCAT(ga.ga_data, p); - add_pathsep(ga.ga_data); - STRCAT(ga.ga_data, "*"); - ga.ga_len += len; + xfree(item); } if(mustfree) { xfree(p); @@ -713,6 +721,38 @@ static void set_string_default(const char *name, char *val, bool allocated) } } +// For an option value that contains comma separated items, find "newval" in +// "origval". Return NULL if not found. +static char_u *find_dup_item(char_u *origval, const char_u *newval, + uint32_t flags) + FUNC_ATTR_NONNULL_ARG(2) +{ + int bs = 0; + + if (origval == NULL) { + return NULL; + } + + const size_t newlen = STRLEN(newval); + for (char_u *s = origval; *s != NUL; s++) { + if ((!(flags & P_COMMA) || s == origval || (s[-1] == ',' && !(bs & 1))) + && STRNCMP(s, newval, newlen) == 0 + && (!(flags & P_COMMA) || s[newlen] == ',' || s[newlen] == NUL)) { + return s; + } + // Count backslashes. Only a comma with an even number of backslashes + // or a single backslash preceded by a comma before it is recognized as + // a separator. + if ((s > origval + 1 && s[-1] == '\\' && s[-2] != ',') + || (s == origval + 1 && s[-1] == '\\')) { + bs++; + } else { + bs = 0; + } + } + return NULL; +} + /// Set the Vi-default value of a number option. /// Used for 'lines' and 'columns'. void set_number_default(char *name, long val) @@ -1285,9 +1325,7 @@ int do_set( char *saved_newval = NULL; unsigned newlen; int comma; - int bs; - int new_value_alloced; /* new string option - was allocated */ + bool new_value_alloced = false; // new string option was allocated /* When using ":set opt=val" for a global option * with a local value the local value will be @@ -1486,34 +1524,20 @@ int do_set( i = 0; // init for GCC if (removing || (flags & P_NODUP)) { i = (int)STRLEN(newval); - bs = 0; - for (s = origval; *s; s++) { - if ((!(flags & P_COMMA) - || s == origval - || (s[-1] == ',' && !(bs & 1))) - && STRNCMP(s, newval, i) == 0 - && (!(flags & P_COMMA) - || s[i] == ',' - || s[i] == NUL)) { - break; - } - // Count backslashes. Only a comma with an even number of - // backslashes or a single backslash preceded by a comma - // before it is recognized as a separator - if ((s > origval + 1 && s[-1] == '\\' && s[-2] != ',') - || (s == origval + 1 && s[-1] == '\\')) { - bs++; - } else { - bs = 0; - } - } + s = find_dup_item(origval, newval, flags); // do not add if already there - if ((adding || prepending) && *s) { + if ((adding || prepending) && s != NULL) { prepending = false; adding = false; STRCPY(newval, origval); } + + // if no duplicate, move pointer to end of + // original value + if (s == NULL) { + s = origval + (int)STRLEN(origval); + } } /* concatenate the two strings; add a ',' if @@ -2086,9 +2110,12 @@ int was_set_insecurely(win_T *const wp, char_u *opt, int opt_flags) /// Get a pointer to the flags used for the P_INSECURE flag of option /// "opt_idx". For some local options a local flags field is used. +/// NOTE: Caller must make sure that "wp" is set to the window from which +/// the option is used. static uint32_t *insecure_flag(win_T *const wp, int opt_idx, int opt_flags) { - if (opt_flags & OPT_LOCAL) + if (opt_flags & OPT_LOCAL) { + assert(wp != NULL); switch ((int)options[opt_idx].indir) { case PV_STL: return &wp->w_p_stl_flags; case PV_FDE: return &wp->w_p_fde_flags; @@ -2097,6 +2124,7 @@ static uint32_t *insecure_flag(win_T *const wp, int opt_idx, int opt_flags) case PV_FEX: return &wp->w_buffer->b_p_fex_flags; case PV_INEX: return &wp->w_buffer->b_p_inex_flags; } + } // Nothing special, return global flags field. return &options[opt_idx].flags; @@ -2306,7 +2334,7 @@ static char_u * did_set_string_option( int opt_idx, // index in options[] table char_u **varp, // pointer to the option variable - int new_value_alloced, // new value was allocated + bool new_value_alloced, // new value was allocated char_u *oldval, // previous value of the option char_u *errbuf, // buffer for errors, or NULL size_t errbuflen, // length of errors buffer @@ -4283,7 +4311,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, // 'floatblend' curwin->w_p_winbl = MAX(MIN(curwin->w_p_winbl, 100), 0); curwin->w_hl_needs_update = true; - curwin->w_grid.blending = curwin->w_p_winbl > 0; + curwin->w_grid_alloc.blending = curwin->w_p_winbl > 0; } @@ -5796,7 +5824,7 @@ void didset_window_options(win_T *wp) 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; + wp->w_grid_alloc.blending = wp->w_p_winbl > 0; } @@ -6178,6 +6206,8 @@ set_context_in_set_cmd( xp->xp_backslash = XP_BS_THREE; else xp->xp_backslash = XP_BS_ONE; + } else if (p == (char_u *)&p_ft) { + xp->xp_context = EXPAND_FILETYPE; } else { xp->xp_context = EXPAND_FILES; // for 'tags' need three backslashes for a space diff --git a/src/nvim/options.lua b/src/nvim/options.lua index fe108ef1cc..f4c1ac9131 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -900,6 +900,7 @@ return { normal_fname_chars=true, vi_def=true, alloced=true, + expand=true, varname='p_ft', defaults={if_true={vi=""}} }, diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index 5cf628935f..e7e0dc4013 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -196,6 +196,22 @@ char *os_ctime(char *result, size_t result_len) return os_ctime_r(&rawtime, result, result_len); } +/// Portable version of POSIX strptime() +/// +/// @param str[in] string to convert +/// @param format[in] format to parse "str" +/// @param tm[out] time representation of "str" +/// @return Pointer to first unprocessed character or NULL +char *os_strptime(const char *str, const char *format, struct tm *tm) + FUNC_ATTR_NONNULL_ALL +{ +#ifdef HAVE_STRPTIME + return strptime(str, format, tm); +#else + return NULL; +#endif +} + /// Obtains the current Unix timestamp. /// /// @return Seconds since epoch. diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index aef7ffa397..68abf57413 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -139,8 +139,10 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, cursor_col = curwin->w_wcol; } - pum_anchor_grid = (int)curwin->w_grid.handle; - if (!ui_has(kUIMultigrid)) { + pum_anchor_grid = (int)curwin->w_grid.target->handle; + pum_win_row += curwin->w_grid.row_offset; + cursor_col += curwin->w_grid.col_offset; + if (!ui_has(kUIMultigrid) && curwin->w_grid.target != &default_grid) { pum_anchor_grid = (int)default_grid.handle; pum_win_row += curwin->w_winrow; cursor_col += curwin->w_wincol; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 47f1cb6423..749627de80 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -232,7 +232,7 @@ void screen_invalidate_highlights(void) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { redraw_later(wp, NOT_VALID); - wp->w_grid.valid = false; + wp->w_grid_alloc.valid = false; } } @@ -582,11 +582,18 @@ int update_screen(int type) FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid.chars) { - grid_invalidate(&wp->w_grid); + if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid_alloc.chars) { + grid_invalidate(&wp->w_grid_alloc); wp->w_redr_type = NOT_VALID; } + // reallocate grid if needed. + win_grid_alloc(wp); + + if (wp->w_redr_border || wp->w_redr_type >= NOT_VALID) { + win_redr_border(wp); + } + if (wp->w_redr_type != 0) { if (!did_one) { did_one = TRUE; @@ -774,8 +781,6 @@ static void win_update(win_T *wp, Providers *providers) type = wp->w_redr_type; - win_grid_alloc(wp); - if (type >= NOT_VALID) { wp->w_redr_status = true; wp->w_lines_valid = 0; @@ -2096,6 +2101,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, char_u buf_fold[FOLD_TEXT_LEN + 1]; // Hold value returned by get_foldtext + bool area_active = false; + /* draw_state: items that are drawn in sequence: */ #define WL_START 0 /* nothing done yet */ # define WL_CMDLINE WL_START + 1 /* cmdline window column */ @@ -2315,7 +2322,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, getvcol(curwin, &pos, (colnr_T *)&tocol, NULL, NULL); } // do at least one character; happens when past end of line - if (fromcol == tocol) { + if (fromcol == tocol && search_match_endcol) { tocol = fromcol + 1; } area_highlighting = true; @@ -2751,7 +2758,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, // :sign defined with "numhl" highlight. char_attr = sign_get_attr(num_sign, SIGN_NUMHL); } else if ((wp->w_p_cul || wp->w_p_rnu) - && lnum == wp->w_cursor.lnum) { + && lnum == wp->w_cursor.lnum + && filler_todo == 0) { // When 'cursorline' is set highlight the line number of // the current line differently. // TODO(vim): Can we use CursorLine instead of CursorLineNr @@ -2850,6 +2858,12 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, if (draw_state == WL_LINE - 1 && n_extra == 0) { sign_idx = 0; draw_state = WL_LINE; + + if (has_decor && row == startrow + filler_lines) { + // hide virt_text on text hidden by 'nowrap' + decor_redraw_col(wp->w_buffer, vcol, off, true, &decor_state); + } + if (saved_n_extra) { /* Continue item from end of wrapped line. */ n_extra = saved_n_extra; @@ -2934,10 +2948,14 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, && vcol_prev < vcol // not at margin && vcol < tocol)) { area_attr = attr; // start highlighting + if (area_highlighting) { + area_active = true; + } } else if (area_attr != 0 && (vcol == tocol || (noinvcur && (colnr_T)vcol == wp->w_virtcol))) { area_attr = 0; // stop highlighting + area_active = false; } if (!n_extra) { @@ -3397,9 +3415,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, char_attr = hl_combine_attr(spell_attr, char_attr); } + if (wp->w_buffer->terminal) { + char_attr = hl_combine_attr(term_attrs[vcol], char_attr); + } + if (has_decor && v > 0) { + bool selected = (area_active || (area_highlighting && noinvcur + && (colnr_T)vcol == wp->w_virtcol)); int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v-1, off, - &decor_state); + selected, &decor_state); if (extmark_attr != 0) { if (!attr_pri) { char_attr = hl_combine_attr(char_attr, extmark_attr); @@ -3409,10 +3433,6 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } } - if (wp->w_buffer->terminal) { - char_attr = hl_combine_attr(term_attrs[vcol], char_attr); - } - // Found last space before word: check for line break. if (wp->w_p_lbr && c0 == c && vim_isbreak(c) && !vim_isbreak((int)(*ptr))) { @@ -3902,9 +3922,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, .hl_id = hl_err })); do_virttext = true; } else if (has_decor) { - VirtText *vp = decor_redraw_virt_text(wp->w_buffer, &decor_state); - if (vp) { - virt_text = *vp; + virt_text = decor_redraw_virt_text(wp->w_buffer, &decor_state); + if (kv_size(virt_text)) { do_virttext = true; } } @@ -4334,10 +4353,10 @@ void draw_virt_text(buf_T *buf, int *end_col, int max_col) DecorState *state = &decor_state; for (size_t i = 0; i < kv_size(state->active); i++) { HlRange *item = &kv_A(state->active, i); - if (item->start_row == state->row && item->virt_text + if (item->start_row == state->row && kv_size(item->virt_text) && item->virt_text_pos == kVTOverlay && item->virt_col >= 0) { - VirtText vt = *item->virt_text; + VirtText vt = item->virt_text; LineState s = LINE_STATE(""); int virt_attr = 0; int col = item->virt_col; @@ -4355,10 +4374,22 @@ void draw_virt_text(buf_T *buf, int *end_col, int max_col) virt_pos++; continue; } - int cells = line_putchar(&s, &linebuf_char[col], 2, false); - linebuf_attr[col++] = virt_attr; + int attr; + bool through = false; + if (item->hl_mode == kHlModeCombine) { + attr = hl_combine_attr(linebuf_attr[col], virt_attr); + } else if (item->hl_mode == kHlModeBlend) { + through = (*s.p == ' '); + attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through); + } else { + attr = virt_attr; + } + schar_T dummy[2]; + int cells = line_putchar(&s, through ? dummy : &linebuf_char[col], + max_col-col, false); + linebuf_attr[col++] = attr; if (cells > 1) { - linebuf_attr[col++] = virt_attr; + linebuf_attr[col++] = attr; } } *end_col = MAX(*end_col, col); @@ -4378,14 +4409,10 @@ void draw_virt_text(buf_T *buf, int *end_col, int max_col) /// screen positions. void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off) { - if (!(*grid)->chars && *grid != &default_grid) { - *row_off += (*grid)->row_offset; - *col_off += (*grid)->col_offset; - if (*grid == &msg_grid_adj && msg_grid.chars) { - *grid = &msg_grid; - } else { - *grid = &default_grid; - } + if ((*grid)->target) { + *row_off += (*grid)->row_offset; + *col_off += (*grid)->col_offset; + *grid = (*grid)->target; } } @@ -5389,6 +5416,46 @@ theend: entered = FALSE; } +static void win_redr_border(win_T *wp) +{ + wp->w_redr_border = false; + if (!(wp->w_floating && wp->w_float_config.border)) { + return; + } + + ScreenGrid *grid = &wp->w_grid_alloc; + + schar_T *chars = wp->w_float_config.border_chars; + int *attrs = wp->w_float_config.border_attr; + + int endrow = grid->Rows-1, endcol = grid->Columns-1; + + grid_puts_line_start(grid, 0); + grid_put_schar(grid, 0, 0, chars[0], attrs[0]); + for (int i = 1; i < endcol; i++) { + grid_put_schar(grid, 0, i, chars[1], attrs[1]); + } + grid_put_schar(grid, 0, endcol, chars[2], attrs[2]); + grid_puts_line_flush(false); + + for (int i = 1; i < endrow; i++) { + grid_puts_line_start(grid, i); + grid_put_schar(grid, i, 0, chars[7], attrs[7]); + grid_puts_line_flush(false); + grid_puts_line_start(grid, i); + grid_put_schar(grid, i, endcol, chars[3], attrs[3]); + grid_puts_line_flush(false); + } + + grid_puts_line_start(grid, endrow); + grid_put_schar(grid, endrow, 0, chars[6], attrs[6]); + for (int i = 1; i < endcol; i++) { + grid_put_schar(grid, endrow, i, chars[5], attrs[5]); + } + grid_put_schar(grid, endrow, endcol, chars[4], attrs[4]); + grid_puts_line_flush(false); +} + // Low-level functions to manipulate invidual character cells on the // screen grid. @@ -5526,6 +5593,20 @@ void grid_puts_line_start(ScreenGrid *grid, int row) put_dirty_grid = grid; } +void grid_put_schar(ScreenGrid *grid, int row, int col, char_u *schar, int attr) +{ + assert(put_dirty_row == row); + unsigned int off = grid->line_offset[row] + col; + if (grid->attrs[off] != attr || schar_cmp(grid->chars[off], schar)) { + schar_copy(grid->chars[off], schar); + grid->attrs[off] = attr; + + put_dirty_first = MIN(put_dirty_first, col); + // TODO(bfredl): Y U NO DOUBLEWIDTH? + put_dirty_last = MAX(put_dirty_last, col+1); + } +} + /// like grid_puts(), but output "text[len]". When "len" is -1 output up to /// a NUL. void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, @@ -6117,12 +6198,15 @@ void check_for_delay(int check_msg_scroll) void win_grid_alloc(win_T *wp) { ScreenGrid *grid = &wp->w_grid; + ScreenGrid *grid_allocated = &wp->w_grid_alloc; int rows = wp->w_height_inner; int cols = wp->w_width_inner; + int total_rows = wp->w_height_outer; + int total_cols = wp->w_width_outer; bool want_allocation = ui_has(kUIMultigrid) || wp->w_floating; - bool has_allocation = (grid->chars != NULL); + bool has_allocation = (grid_allocated->chars != NULL); if (grid->Rows != rows) { wp->w_lines_valid = 0; @@ -6131,35 +6215,47 @@ void win_grid_alloc(win_T *wp) } int was_resized = false; - if ((has_allocation != want_allocation) - || grid->Rows != rows - || grid->Columns != cols) { - if (want_allocation) { - grid_alloc(grid, rows, cols, wp->w_grid.valid, false); - grid->valid = true; - } else { - // Single grid mode, all rendering will be redirected to default_grid. - // Only keep track of the size and offset of the window. - grid_free(grid); - grid->Rows = rows; - grid->Columns = cols; - grid->valid = false; + if (want_allocation && (!has_allocation + || grid_allocated->Rows != total_rows + || grid_allocated->Columns != total_cols)) { + grid_alloc(grid_allocated, total_rows, total_cols, + wp->w_grid_alloc.valid, false); + grid_allocated->valid = true; + if (wp->w_border_adj) { + wp->w_redr_border = true; } was_resized = true; - } else if (want_allocation && has_allocation && !wp->w_grid.valid) { - grid_invalidate(grid); - grid->valid = true; + } else if (!want_allocation && has_allocation) { + // Single grid mode, all rendering will be redirected to default_grid. + // Only keep track of the size and offset of the window. + grid_free(grid_allocated); + grid_allocated->valid = false; + was_resized = true; + } else if (want_allocation && has_allocation && !wp->w_grid_alloc.valid) { + grid_invalidate(grid_allocated); + grid_allocated->valid = true; } - grid->row_offset = wp->w_winrow; - grid->col_offset = wp->w_wincol; + grid->Rows = rows; + grid->Columns = cols; + + if (want_allocation) { + grid->target = grid_allocated; + grid->row_offset = wp->w_border_adj; + grid->col_offset = wp->w_border_adj; + } else { + grid->target = &default_grid; + grid->row_offset = wp->w_winrow; + grid->col_offset = wp->w_wincol; + } // send grid resize event if: // - a grid was just resized // - screen_resize was called and all grid sizes must be sent // - the UI wants multigrid event (necessary) if ((send_grid_resize || was_resized) && want_allocation) { - ui_call_grid_resize(grid->handle, grid->Columns, grid->Rows); + ui_call_grid_resize(grid_allocated->handle, + grid_allocated->Columns, grid_allocated->Rows); } } @@ -6248,6 +6344,9 @@ retry: tab_page_click_defs = new_tab_page_click_defs; tab_page_click_defs_size = Columns; + default_grid.comp_height = Rows; + default_grid.comp_width = Columns; + default_grid.row_offset = 0; default_grid.col_offset = 0; default_grid.handle = DEFAULT_GRID_HANDLE; @@ -7502,7 +7601,7 @@ void win_new_shellsize(void) win_T *get_win_by_grid_handle(handle_T handle) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_grid.handle == handle) { + if (wp->w_grid_alloc.handle == handle) { return wp; } } diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 547d953be9..f1eb7879b0 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -59,7 +59,9 @@ struct hl_group { bool sg_cleared; ///< "hi clear" was used int sg_attr; ///< Screen attr @see ATTR_ENTRY int sg_link; ///< link to this highlight group ID + int sg_deflink; ///< default link; restored in highlight_clear() int sg_set; ///< combination of flags in \ref SG_SET + sctx_T sg_deflink_sctx; ///< script where the default link was set sctx_T sg_script_ctx; ///< script in which the group was last set // for terminal UIs int sg_cterm; ///< "cterm=" highlighting attr @@ -6044,6 +6046,7 @@ static const char *highlight_init_both[] = { "default link Whitespace NonText", "default link MsgSeparator StatusLine", "default link NormalFloat Pmenu", + "default link FloatBorder VertSplit", "RedrawDebugNormal cterm=reverse gui=reverse", "RedrawDebugClear ctermbg=Yellow guibg=Yellow", "RedrawDebugComposed ctermbg=Green guibg=Green", @@ -6601,6 +6604,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) const char *to_end; int from_id; int to_id; + struct hl_group *hlgroup = NULL; from_end = (const char *)skiptowhite((const char_u *)from_start); to_start = (const char *)skipwhite((const char_u *)from_end); @@ -6627,7 +6631,16 @@ void do_highlight(const char *line, const bool forceit, const bool init) (int)(to_end - to_start)); } - if (from_id > 0 && (!init || HL_TABLE()[from_id - 1].sg_set == 0)) { + if (from_id > 0) { + hlgroup = &HL_TABLE()[from_id - 1]; + if (dodefault && (forceit || hlgroup->sg_deflink == 0)) { + hlgroup->sg_deflink = to_id; + hlgroup->sg_deflink_sctx = current_sctx; + hlgroup->sg_deflink_sctx.sc_lnum += sourcing_lnum; + } + } + + if (from_id > 0 && (!init || hlgroup->sg_set == 0)) { // Don't allow a link when there already is some highlighting // for the group, unless '!' is used if (to_id > 0 && !forceit && !init @@ -6635,17 +6648,16 @@ void do_highlight(const char *line, const bool forceit, const bool init) if (sourcing_name == NULL && !dodefault) { EMSG(_("E414: group has settings, highlight link ignored")); } - } else if (HL_TABLE()[from_id - 1].sg_link != to_id - || HL_TABLE()[from_id - 1].sg_script_ctx.sc_sid - != current_sctx.sc_sid - || HL_TABLE()[from_id - 1].sg_cleared) { + } else if (hlgroup->sg_link != to_id + || hlgroup->sg_script_ctx.sc_sid != current_sctx.sc_sid + || hlgroup->sg_cleared) { if (!init) { - HL_TABLE()[from_id - 1].sg_set |= SG_LINK; + hlgroup->sg_set |= SG_LINK; } - HL_TABLE()[from_id - 1].sg_link = to_id; - HL_TABLE()[from_id - 1].sg_script_ctx = current_sctx; - HL_TABLE()[from_id - 1].sg_script_ctx.sc_lnum += sourcing_lnum; - HL_TABLE()[from_id - 1].sg_cleared = false; + hlgroup->sg_link = to_id; + hlgroup->sg_script_ctx = current_sctx; + hlgroup->sg_script_ctx.sc_lnum += sourcing_lnum; + hlgroup->sg_cleared = false; redraw_all_later(SOME_VALID); // Only call highlight changed() once after multiple changes @@ -7076,13 +7088,14 @@ void restore_cterm_colors(void) */ static int hl_has_settings(int idx, int check_link) { - return HL_TABLE()[idx].sg_attr != 0 - || HL_TABLE()[idx].sg_cterm_fg != 0 - || HL_TABLE()[idx].sg_cterm_bg != 0 - || HL_TABLE()[idx].sg_rgb_fg_name != NULL - || HL_TABLE()[idx].sg_rgb_bg_name != NULL - || HL_TABLE()[idx].sg_rgb_sp_name != NULL - || (check_link && (HL_TABLE()[idx].sg_set & SG_LINK)); + return HL_TABLE()[idx].sg_cleared == 0 + && (HL_TABLE()[idx].sg_attr != 0 + || HL_TABLE()[idx].sg_cterm_fg != 0 + || HL_TABLE()[idx].sg_cterm_bg != 0 + || HL_TABLE()[idx].sg_rgb_fg_name != NULL + || HL_TABLE()[idx].sg_rgb_bg_name != NULL + || HL_TABLE()[idx].sg_rgb_sp_name != NULL + || (check_link && (HL_TABLE()[idx].sg_set & SG_LINK))); } /* @@ -7105,12 +7118,11 @@ static void highlight_clear(int idx) XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_bg_name); XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_sp_name); HL_TABLE()[idx].sg_blend = -1; - // Clear the script ID only when there is no link, since that is not - // cleared. - if (HL_TABLE()[idx].sg_link == 0) { - HL_TABLE()[idx].sg_script_ctx.sc_sid = 0; - HL_TABLE()[idx].sg_script_ctx.sc_lnum = 0; - } + // Restore default link and context if they exist. Otherwise clears. + HL_TABLE()[idx].sg_link = HL_TABLE()[idx].sg_deflink; + // Since we set the default link, set the location to where the default + // link was set. + HL_TABLE()[idx].sg_script_ctx = HL_TABLE()[idx].sg_deflink_sctx; } diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 4ea298fba9..6b8f393572 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -908,7 +908,7 @@ add_llist_tags( if (len > 128) { len = 128; } - xstrlcpy((char *)tag_name, (const char *)tagp.tagname, len); + xstrlcpy((char *)tag_name, (const char *)tagp.tagname, len + 1); tag_name[len] = NUL; // Save the tag file name @@ -975,7 +975,8 @@ add_llist_tags( if (cmd_len > (CMDBUFFSIZE - 5)) { cmd_len = CMDBUFFSIZE - 5; } - xstrlcat((char *)cmd, (char *)cmd_start, cmd_len); + snprintf((char *)cmd + len, CMDBUFFSIZE + 1 - len, + "%.*s", cmd_len, cmd_start); len += cmd_len; if (cmd[len - 1] == '$') { @@ -1141,7 +1142,7 @@ static int find_tagfunc_tags( int result = FAIL; typval_T args[4]; typval_T rettv; - char_u flagString[3]; + char_u flagString[4]; dict_T *d; taggy_T *tag = &curwin->w_tagstack[curwin->w_tagstackidx]; @@ -1170,9 +1171,10 @@ static int find_tagfunc_tags( args[3].v_type = VAR_UNKNOWN; vim_snprintf((char *)flagString, sizeof(flagString), - "%s%s", + "%s%s%s", g_tag_at_cursor ? "c": "", - flags & TAG_INS_COMP ? "i": ""); + flags & TAG_INS_COMP ? "i": "", + flags & TAG_REGEXP ? "r": ""); save_pos = curwin->w_cursor; result = call_vim_function(curbuf->b_p_tfu, 3, args, &rettv); @@ -3002,7 +3004,8 @@ static int test_for_current(char_u *fname, char_u *fname_end, char_u *tag_fname, */ static int find_extra(char_u **pp) { - char_u *str = *pp; + char_u *str = *pp; + char_u first_char = **pp; // Repeat for addresses separated with ';' for (;; ) { @@ -3010,7 +3013,7 @@ static int find_extra(char_u **pp) str = skipdigits(str); } else if (*str == '/' || *str == '?') { str = skip_regexp(str + 1, *str, false, NULL); - if (*str != **pp) { + if (*str != first_char) { str = NULL; } else { str++; @@ -3028,6 +3031,7 @@ static int find_extra(char_u **pp) break; } str++; // skip ';' + first_char = *str; } if (str != NULL && STRNCMP(str, ";\"", 2) == 0) { @@ -3404,6 +3408,7 @@ int set_tagstack(win_T *wp, const dict_T *d, int action) if ((di = tv_dict_find(d, "items", -1)) != NULL) { if (di->di_tv.v_type != VAR_LIST) { + EMSG(_(e_listreq)); return FAIL; } l = di->di_tv.vval.v_list; diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index f6995cddb6..913ef3baed 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -169,19 +169,20 @@ void terminal_teardown(void) multiqueue_free(refresh_timer.events); time_watcher_close(&refresh_timer, NULL); pmap_free(ptr_t)(invalidated_terminals); + invalidated_terminals = NULL; } // public API {{{ -Terminal *terminal_open(TerminalOptions opts) +Terminal *terminal_open(buf_T *buf, TerminalOptions opts) { // Create a new terminal instance and configure it Terminal *rv = xcalloc(1, sizeof(Terminal)); rv->opts = opts; rv->cursor.visible = true; // Associate the terminal instance with the new buffer - rv->buf_handle = curbuf->handle; - curbuf->terminal = rv; + rv->buf_handle = buf->handle; + buf->terminal = rv; // Create VTerm rv->vt = vterm_new(opts.height, opts.width); vterm_set_utf8(rv->vt, 1); @@ -198,28 +199,36 @@ Terminal *terminal_open(TerminalOptions opts) // have as many lines as screen rows when refresh_scrollback is called rv->invalid_start = 0; rv->invalid_end = opts.height; - refresh_screen(rv, curbuf); + + aco_save_T aco; + aucmd_prepbuf(&aco, buf); + + refresh_screen(rv, buf); set_option_value("buftype", 0, "terminal", OPT_LOCAL); // -V666 // Default settings for terminal buffers - curbuf->b_p_ma = false; // 'nomodifiable' - curbuf->b_p_ul = -1; // 'undolevels' - curbuf->b_p_scbk = // 'scrollback' (initialize local from global) + buf->b_p_ma = false; // 'nomodifiable' + buf->b_p_ul = -1; // 'undolevels' + buf->b_p_scbk = // 'scrollback' (initialize local from global) (p_scbk < 0) ? 10000 : MAX(1, p_scbk); - curbuf->b_p_tw = 0; // 'textwidth' + buf->b_p_tw = 0; // 'textwidth' set_option_value("wrap", false, NULL, OPT_LOCAL); set_option_value("list", false, NULL, OPT_LOCAL); - buf_set_term_title(curbuf, (char *)curbuf->b_ffname); + if (buf->b_ffname != NULL) { + buf_set_term_title(buf, (char *)buf->b_ffname); + } RESET_BINDING(curwin); // Reset cursor in current window. curwin->w_cursor = (pos_T){ .lnum = 1, .col = 0, .coladd = 0 }; // Apply TermOpen autocmds _before_ configuring the scrollback buffer. - apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, curbuf); + apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, buf); // Local 'scrollback' _after_ autocmds. - curbuf->b_p_scbk = (curbuf->b_p_scbk < 1) ? SB_MAX : curbuf->b_p_scbk; + buf->b_p_scbk = (buf->b_p_scbk < 1) ? SB_MAX : buf->b_p_scbk; + + aucmd_restbuf(&aco); // Configure the scrollback buffer. - rv->sb_size = (size_t)curbuf->b_p_scbk; + rv->sb_size = (size_t)buf->b_p_scbk; rv->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * rv->sb_size); // Configure the color palette. Try to get the color from: @@ -511,7 +520,9 @@ void terminal_destroy(Terminal *term) } if (!term->refcount) { - if (pmap_has(ptr_t)(invalidated_terminals, term)) { + // might be destroyed after terminal_teardown is invoked + if (invalidated_terminals + && pmap_has(ptr_t)(invalidated_terminals, term)) { // flush any pending changes to the buffer block_autocmds(); refresh_terminal(term); @@ -1324,6 +1335,7 @@ static void refresh_scrollback(Terminal *term, buf_T *buf) // focused) of a invalidated terminal static void refresh_screen(Terminal *term, buf_T *buf) { + assert(buf == curbuf); // TODO(bfredl): remove this condition int changed = 0; int added = 0; int height; diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index e52fd888bd..b760828458 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -36,15 +36,9 @@ NEW_TESTS_IN_ALOT := $(shell sed -n '/^source/ s/^source //; s/\.vim$$//p' $(add 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 := \ test_alot_latin $(NEW_TESTS_IN_ALOT_LATIN) \ - test_autochdir \ - test_eval_func \ - test_listlbr \ test_largefile \ NEW_TESTS := $(sort $(basename $(notdir $(wildcard test_*.vim)))) diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim index 24d3959f83..7b06e53dd5 100644 --- a/src/nvim/testdir/check.vim +++ b/src/nvim/testdir/check.vim @@ -108,3 +108,30 @@ func CheckNotMSWindows() throw 'Skipped: does not work on MS-Windows' endif endfunc + +" Command to check for satisfying any of the conditions. +" e.g. CheckAnyOf Feature:bsd Feature:sun Linux +command -nargs=+ CheckAnyOf call CheckAnyOf(<f-args>) +func CheckAnyOf(...) + let excp = [] + for arg in a:000 + try + exe 'Check' .. substitute(arg, ':', ' ', '') + return + catch /^Skipped:/ + let excp += [substitute(v:exception, '^Skipped:\s*', '', '')] + endtry + endfor + throw 'Skipped: ' .. join(excp, '; ') +endfunc + +" Command to check for satisfying all of the conditions. +" e.g. CheckAllOf Unix Gui Option:ballooneval +command -nargs=+ CheckAllOf call CheckAllOf(<f-args>) +func CheckAllOf(...) + for arg in a:000 + exe 'Check' .. substitute(arg, ':', ' ', '') + endfor +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 275edece1e..2d94b637e0 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -373,9 +373,6 @@ let s:flaky_tests = [ \ 'Test_with_partial_callback()', \ ] -" Pattern indicating a common flaky test failure. -let s:flaky_errors_re = 'StopVimInTerminal\|VerifyScreenDump' - " Locate Test_ functions and execute them. redir @q silent function /^Test_ @@ -410,6 +407,9 @@ for s:test in sort(s:tests) let total_errors = [] let run_nr = 1 + " A test can set g:test_is_flaky to retry running the test. + let g:test_is_flaky = 0 + call RunTheTest(s:test) " Repeat a flaky test. Give up when: @@ -417,7 +417,7 @@ for s:test in sort(s:tests) " - it fails five times (with a different message) if len(v:errors) > 0 \ && (index(s:flaky_tests, s:test) >= 0 - \ || v:errors[0] =~ s:flaky_errors_re) + \ || g:test_is_flaky) while 1 call add(s:messages, 'Found errors in ' . s:test . ':') call extend(s:messages, v:errors) diff --git a/src/nvim/testdir/script_util.vim b/src/nvim/testdir/script_util.vim new file mode 100644 index 0000000000..9913b1dfaf --- /dev/null +++ b/src/nvim/testdir/script_util.vim @@ -0,0 +1,69 @@ +" Functions shared by the tests for Vim Script + +" Commands to track the execution path of a script +com! XpathINIT let g:Xpath = '' +com! -nargs=1 -bar Xpath let g:Xpath ..= <args> +com! XloopINIT let g:Xloop = 1 +com! -nargs=1 -bar Xloop let g:Xpath ..= <args> .. g:Xloop +com! XloopNEXT let g:Xloop += 1 + +" MakeScript() - Make a script file from a function. {{{2 +" +" Create a script that consists of the body of the function a:funcname. +" Replace any ":return" by a ":finish", any argument variable by a global +" variable, and every ":call" by a ":source" for the next following argument +" in the variable argument list. This function is useful if similar tests are +" to be made for a ":return" from a function call or a ":finish" in a script +" file. +func MakeScript(funcname, ...) + let script = tempname() + execute "redir! >" . script + execute "function" a:funcname + redir END + execute "edit" script + " Delete the "function" and the "endfunction" lines. Do not include the + " word "function" in the pattern since it might be translated if LANG is + " set. When MakeScript() is being debugged, this deletes also the debugging + " output of its line 3 and 4. + exec '1,/.*' . a:funcname . '(.*)/d' + /^\d*\s*endfunction\>/,$d + %s/^\d*//e + %s/return/finish/e + %s/\<a:\(\h\w*\)/g:\1/ge + normal gg0 + let cnt = 0 + while search('\<call\s*\%(\u\|s:\)\w*\s*(.*)', 'W') > 0 + let cnt = cnt + 1 + s/\<call\s*\%(\u\|s:\)\w*\s*(.*)/\='source ' . a:{cnt}/ + endwhile + g/^\s*$/d + write + bwipeout + return script +endfunc + +" ExecAsScript - Source a temporary script made from a function. {{{2 +" +" Make a temporary script file from the function a:funcname, ":source" it, and +" delete it afterwards. However, if an exception is thrown the file may remain, +" the caller should call DeleteTheScript() afterwards. +let s:script_name = '' +function! ExecAsScript(funcname) + " Make a script from the function passed as argument. + let s:script_name = MakeScript(a:funcname) + + " Source and delete the script. + exec "source" s:script_name + call delete(s:script_name) + let s:script_name = '' +endfunction + +function! DeleteTheScript() + if s:script_name + call delete(s:script_name) + let s:script_name = '' + endif +endfunc + +com! -nargs=1 -bar ExecAsScript call ExecAsScript(<f-args>) + diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index daf3c9c110..a47d20a265 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -33,7 +33,6 @@ source test_move.vim source test_partial.vim source test_popup.vim source test_put.vim -source test_recover.vim source test_scroll_opt.vim source test_sort.vim source test_sha256.vim diff --git a/src/nvim/testdir/test_autochdir.vim b/src/nvim/testdir/test_autochdir.vim index 67c537b407..d071f4b325 100644 --- a/src/nvim/testdir/test_autochdir.vim +++ b/src/nvim/testdir/test_autochdir.vim @@ -1,10 +1,10 @@ " Test 'autochdir' behavior -if !exists("+autochdir") - throw 'Skipped: autochdir feature missing' -endif +source check.vim +CheckOption autochdir func Test_set_filename() + CheckFunction test_autochdir let cwd = getcwd() call test_autochdir() set acd @@ -17,3 +17,5 @@ func Test_set_filename() exe 'cd ' . cwd call delete('samples/Xtest') endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 1f3a45a9ab..5e99edf233 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -76,7 +76,7 @@ if has('timers') endfunc func Test_OptionSet_modeline() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override call test_override('starting', 1) au! OptionSet augroup set_tabstop @@ -507,7 +507,7 @@ func s:AutoCommandOptionSet(match) endfunc func Test_OptionSet() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !has("eval") || !exists("+autochdir") return endif @@ -648,7 +648,7 @@ func Test_OptionSet() endfunc func Test_OptionSet_diffmode() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override call test_override('starting', 1) " 18: Changing an option when entering diff mode new @@ -682,7 +682,7 @@ func Test_OptionSet_diffmode() endfunc func Test_OptionSet_diffmode_close() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override call test_override('starting', 1) " 19: Try to close the current window when entering diff mode " should not segfault @@ -1285,9 +1285,9 @@ func Test_autocommand_all_events() endfunc " Test TextChangedI and TextChangedP +" See test/functional/viml/completion_spec.lua' func Test_ChangedP() - " Nvim does not support test_override(). - throw 'skipped: see test/functional/viml/completion_spec.lua' + CheckFunction test_override new call setline(1, ['foo', 'bar', 'foobar']) call test_override("char_avail", 1) @@ -1350,7 +1350,7 @@ func SetLineOne() endfunc func Test_TextChangedI_with_setline() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override new call test_override('char_avail', 1) autocmd TextChangedI <buffer> call SetLineOne() @@ -1366,9 +1366,11 @@ func Test_TextChangedI_with_setline() endfunc func Test_Changed_FirstTime() - if !has('terminal') || has('gui_running') - return - endif + CheckFeature terminal + CheckNotGui + " Starting a terminal to run Vim is always considered flaky. + let g:test_is_flaky = 1 + " Prepare file for TextChanged event. call writefile([''], 'Xchanged.txt') let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile'], {'term_rows': 3}) @@ -1922,20 +1924,28 @@ func Test_autocmd_window() %bw! edit one.txt tabnew two.txt + vnew three.txt + tabnew four.txt + tabprevious let g:blist = [] - augroup aucmd_win_test + augroup aucmd_win_test1 au! au BufEnter * call add(g:blist, [expand('<afile>'), \ win_gettype(bufwinnr(expand('<afile>')))]) augroup END doautoall BufEnter - call assert_equal([['one.txt', 'autocmd'], ['two.txt', '']], g:blist) + call assert_equal([ + \ ['one.txt', 'autocmd'], + \ ['two.txt', ''], + \ ['four.txt', 'autocmd'], + \ ['three.txt', ''], + \ ], g:blist) - augroup aucmd_win_test + augroup aucmd_win_test1 au! augroup END - augroup! aucmd_win_test + augroup! aucmd_win_test1 %bw! endfunc diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index a66aee5e02..489b2477e6 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -569,6 +569,21 @@ func Test_cmdline_complete_user_cmd() delcommand Foo endfunc +func s:ScriptLocalFunction() + echo 'yes' +endfunc + +func Test_cmdline_complete_user_func() + call feedkeys(":func Test_cmdline_complete_user\<Tab>\<Home>\"\<cr>", 'tx') + call assert_match('"func Test_cmdline_complete_user', @:) + call feedkeys(":func s:ScriptL\<Tab>\<Home>\"\<cr>", 'tx') + call assert_match('"func <SNR>\d\+_ScriptLocalFunction', @:) + + " g: prefix also works + call feedkeys(":echo g:Test_cmdline_complete_user_f\<Tab>\<Home>\"\<cr>", 'tx') + call assert_match('"echo g:Test_cmdline_complete_user_func', @:) +endfunc + func Test_cmdline_complete_user_names() if has('unix') && executable('whoami') let whoami = systemlist('whoami')[0] diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim index ff50d53d86..73b57f302e 100644 --- a/src/nvim/testdir/test_eval_stuff.vim +++ b/src/nvim/testdir/test_eval_stuff.vim @@ -24,7 +24,7 @@ endfunc func Test_for_invalid() call assert_fails("for x in 99", 'E714:') - call assert_fails("for x in 'asdf'", 'E714:') + call assert_fails("for x in function('winnr')", 'E714:') call assert_fails("for x in {'a': 9}", 'E714:') if 0 diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 6bc5fba5db..44b8479621 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -358,6 +358,7 @@ let s:filename_checks = { \ 'po': ['file.po', 'file.pot'], \ 'pod': ['file.pod'], \ 'pod6': ['file.pod6'], + \ 'poke': ['file.pk'], \ 'postscr': ['file.ps', 'file.pfa', 'file.afm', 'file.eps', 'file.epsf', 'file.epsi', 'file.ai'], \ 'pov': ['file.pov'], \ 'povini': ['.povrayrc'], @@ -520,7 +521,7 @@ let s:filename_checks = { \ 'xhtml': ['file.xhtml', 'file.xht'], \ 'xinetd': ['/etc/xinetd.conf'], \ 'xmath': ['file.msc', 'file.msf'], - \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl'], + \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl', 'file.wpl', 'any/etc/blkid.tab', 'any/etc/blkid.tab.old', 'any/etc/xdg/menus/file.menu', 'file.atom', 'file.rss'], \ 'xmodmap': ['anyXmodmap'], \ 'xf86conf': ['xorg.conf', 'xorg.conf-4'], \ 'xpm2': ['file.xpm2'], diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 5dae8d681a..555f549743 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -1,5 +1,7 @@ " Tests for various functions. + source shared.vim +source check.vim " Must be done first, since the alternate buffer must be unset. func Test_00_bufexists() @@ -171,9 +173,8 @@ func Test_str2nr() endfunc func Test_strftime() - if !exists('*strftime') - return - endif + CheckFunction strftime + " Format of strftime() depends on system. We assume " that basic formats tested here are available and " identical on all systems which support strftime(). @@ -214,6 +215,33 @@ func Test_strftime() endif endfunc +func Test_strptime() + CheckFunction strptime + CheckNotMSWindows + + if exists('$TZ') + let tz = $TZ + endif + let $TZ = 'UTC' + + call assert_equal(1484653763, strptime('%Y-%m-%d %T', '2017-01-17 11:49:23')) + + " Force DST and check that it's considered + let $TZ = 'WINTER0SUMMER,J1,J365' + call assert_equal(1484653763 - 3600, strptime('%Y-%m-%d %T', '2017-01-17 11:49:23')) + + call assert_fails('call strptime()', 'E119:') + call assert_fails('call strptime("xxx")', 'E119:') + call assert_equal(0, strptime("%Y", '')) + call assert_equal(0, strptime("%Y", "xxx")) + + if exists('tz') + let $TZ = tz + else + unlet $TZ + endif +endfunc + func Test_resolve_unix() if !has('unix') return diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim index 4cc4d775d1..ce22de09ca 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -3,6 +3,7 @@ source view_util.vim source screendump.vim source check.vim +source script_util.vim func Test_highlight() " basic test if ":highlight" doesn't crash @@ -623,4 +624,103 @@ func Test_xxlast_highlight_RGB_color() hi clear endfunc +func Test_highlight_clear_restores_links() + let aaa_id = hlID('aaa') + call assert_equal(aaa_id, 0) + + " create default link aaa --> bbb + hi def link aaa bbb + let id_aaa = hlID('aaa') + let hl_aaa_bbb = HighlightArgs('aaa') + + " try to redefine default link aaa --> ccc; check aaa --> bbb + hi def link aaa ccc + call assert_equal(HighlightArgs('aaa'), hl_aaa_bbb) + + " clear aaa; check aaa --> bbb + hi clear aaa + call assert_equal(HighlightArgs('aaa'), hl_aaa_bbb) + + " link aaa --> ccc; clear aaa; check aaa --> bbb + hi link aaa ccc + let id_ccc = hlID('ccc') + call assert_equal(synIDtrans(id_aaa), id_ccc) + hi clear aaa + call assert_equal(HighlightArgs('aaa'), hl_aaa_bbb) + + " forcibly set default link aaa --> ddd + hi! def link aaa ddd + let id_ddd = hlID('ddd') + let hl_aaa_ddd = HighlightArgs('aaa') + call assert_equal(synIDtrans(id_aaa), id_ddd) + + " link aaa --> eee; clear aaa; check aaa --> ddd + hi link aaa eee + let eee_id = hlID('eee') + call assert_equal(synIDtrans(id_aaa), eee_id) + hi clear aaa + call assert_equal(HighlightArgs('aaa'), hl_aaa_ddd) +endfunc + +func Test_highlight_clear_restores_context() + func FuncContextDefault() + hi def link Context ContextDefault + endfun + + func FuncContextRelink() + " Dummy line + hi link Context ContextRelink + endfunc + + let scriptContextDefault = MakeScript("FuncContextDefault") + let scriptContextRelink = MakeScript("FuncContextRelink") + let patContextDefault = fnamemodify(scriptContextDefault, ':t') .. ' line 1' + let patContextRelink = fnamemodify(scriptContextRelink, ':t') .. ' line 2' + + exec "source" scriptContextDefault + let hlContextDefault = execute("verbose hi Context") + call assert_match(patContextDefault, hlContextDefault) + + exec "source" scriptContextRelink + let hlContextRelink = execute("verbose hi Context") + call assert_match(patContextRelink, hlContextRelink) + + hi clear + let hlContextAfterClear = execute("verbose hi Context") + call assert_match(patContextDefault, hlContextAfterClear) + + delfunc FuncContextDefault + delfunc FuncContextRelink + call delete(scriptContextDefault) + call delete(scriptContextRelink) +endfunc + +func Test_highlight_default_colorscheme_restores_links() + hi link TestLink Identifier + hi TestHi ctermbg=red + + let hlTestLinkPre = HighlightArgs('TestLink') + let hlTestHiPre = HighlightArgs('TestHi') + + " Test colorscheme + hi clear + if exists('syntax_on') + syntax reset + endif + let g:colors_name = 'test' + hi link TestLink ErrorMsg + hi TestHi ctermbg=green + + " Restore default highlighting + colorscheme default + " 'default' should work no matter if highlight group was cleared + hi def link TestLink Identifier + hi def TestHi ctermbg=red + let hlTestLinkPost = HighlightArgs('TestLink') + let hlTestHiPost = HighlightArgs('TestHi') + call assert_equal(hlTestLinkPre, hlTestLinkPost) + call assert_equal(hlTestHiPre, hlTestHiPost) + hi clear +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index 9435931d41..3da3648fec 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -469,6 +469,34 @@ func Test_pum_with_folds_two_tabs() call delete('Xpumscript') endfunc +" Test for inserting the tag search pattern in insert mode +func Test_ins_compl_tag_sft() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfoo\t/^int first() {}$/", + \ "second\tXfoo\t/^int second() {}$/", + \ "third\tXfoo\t/^int third() {}$/"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int second() {} + int third() {} + [CODE] + call writefile(code, 'Xfoo') + + enew + set showfulltag + exe "normal isec\<C-X>\<C-]>\<C-N>\<CR>" + call assert_equal('int second() {}', getline(1)) + set noshowfulltag + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe! +endfunc + " Test to ensure 'Scanning...' messages are not recorded in messages history func Test_z1_complete_no_history() new diff --git a/src/nvim/testdir/test_listlbr.vim b/src/nvim/testdir/test_listlbr.vim index d619ac0eb5..e0518de3c2 100644 --- a/src/nvim/testdir/test_listlbr.vim +++ b/src/nvim/testdir/test_listlbr.vim @@ -1,9 +1,5 @@ " Test for linebreak and list option (non-utf8) -" Nvim does not allow setting 'encoding', so skip this test. -finish - -set encoding=latin1 scriptencoding latin1 if !exists("+linebreak") || !has("conceal") @@ -46,6 +42,7 @@ func Test_set_linebreak() endfunc func Test_linebreak_with_list() + throw 'skipped: Nvim does not support enc=latin1' call s:test_windows('setl ts=4 sbr=+ list listchars=') call setline(1, "\tabcdef hijklmn\tpqrstuvwxyz_1060ABCDEFGHIJKLMNOP ") let lines = s:screen_lines([1, 4], winwidth(0)) @@ -217,6 +214,7 @@ func Test_norm_after_block_visual() endfunc func Test_block_replace_after_wrapping() + throw 'skipped: Nvim does not support enc=latin1' call s:test_windows() call setline(1, repeat("a", 150)) exe "norm! 0yypk147|\<C-V>jr0" diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim index 3ebd048f46..08586dffe1 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -1,5 +1,6 @@ " Tests for :messages, :echomsg, :echoerr +source check.vim source shared.vim func Test_messages() @@ -77,7 +78,7 @@ func Test_echomsg() endfunc func Test_echoerr() - throw 'skipped: Nvim does not support test_ignore_error()' + CheckFunction test_ignore_error call test_ignore_error('IgNoRe') call assert_equal("\nIgNoRe hello", execute(':echoerr "IgNoRe hello"')) call assert_equal("\n12345 IgNoRe", execute(':echoerr 12345 "IgNoRe"')) diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index 7c7804212b..8486f3ff68 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -331,6 +331,20 @@ func Test_mkview_no_balt() %bwipe endfunc +func Test_mksession_no_balt() + edit Xtestfile1 + edit Xtestfile2 + + bdelete Xtestfile1 + mksession! Xtestview + + source Xtestview + call assert_equal(0, buflisted('Xtestfile1')) + + call delete('Xtestview') + %bwipe +endfunc + " Test :mkview with a file argument. func Test_mkview_file() " Create a view with line number and a fold. diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 1202b842fd..5aef33cb09 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -225,11 +225,18 @@ func Test_set_completion() " Expand files and directories. call feedkeys(":set tags=./\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_match('./samples/ ./sautest/ ./screendump.vim ./setup.vim ./shared.vim', @:) + call assert_match('./samples/ ./sautest/ ./screendump.vim ./script_util.vim ./setup.vim ./shared.vim', @:) call feedkeys(":set tags=./\\\\ dif\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"set tags=./\\ diff diffexpr diffopt', @:) set tags& + + " Expand values for 'filetype' + call feedkeys(":set filetype=sshdconfi\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"set filetype=sshdconfig', @:) + call feedkeys(":set filetype=a\<C-A>\<C-B>\"\<CR>", 'xt') + " call assert_equal('"set filetype=' .. getcompletion('a*', 'filetype')->join(), @:) + call assert_equal('"set filetype=' .. join(getcompletion('a*', 'filetype')), @:) endfunc func Test_set_errors() @@ -441,6 +448,36 @@ func Test_backupskip() endif endfor + " Duplicates from environment variables should be filtered out (option has + " P_NODUP). Run this in a separate instance and write v:errors in a file, + " so that we see what happens on startup. + let after =<< trim [CODE] + let bsklist = split(&backupskip, ',') + call assert_equal(uniq(copy(bsklist)), bsklist) + call writefile(['errors:'] + v:errors, 'Xtestout') + qall + [CODE] + call writefile(after, 'Xafter') + " let cmd = GetVimProg() . ' --not-a-term -S Xafter --cmd "set enc=utf8"' + let cmd = GetVimProg() . ' -S Xafter --cmd "set enc=utf8"' + + let saveenv = {} + for var in ['TMPDIR', 'TMP', 'TEMP'] + let saveenv[var] = getenv(var) + call setenv(var, '/duplicate/path') + endfor + + exe 'silent !' . cmd + call assert_equal(['errors:'], readfile('Xtestout')) + + " restore environment variables + for var in ['TMPDIR', 'TMP', 'TEMP'] + call setenv(var, saveenv[var]) + endfor + + call delete('Xtestout') + call delete('Xafter') + " Duplicates should be filtered out (option has P_NODUP) let backupskip = &backupskip set backupskip= @@ -627,6 +664,28 @@ func Test_opt_winminheight_term() call delete('Xwinminheight') endfunc +func Test_opt_winminheight_term_tabs() + " See test/functional/legacy/options_spec.lua + CheckRunVimInTerminal + + " The tabline should be taken into account. + let lines =<< trim END + set wmh=0 stal=2 + split + split + split + split + tabnew + END + call writefile(lines, 'Xwinminheight') + let buf = RunVimInTerminal('-S Xwinminheight', #{rows: 11}) + call term_sendkeys(buf, ":set wmh=1\n") + call WaitForAssert({-> assert_match('E36: Not enough room', term_getline(buf, 11))}) + + call StopVimInTerminal(buf) + call delete('Xwinminheight') +endfunc + " Test for setting option value containing spaces with isfname+=32 func Test_isfname_with_options() set isfname+=32 diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index 4ee16558a0..9443958984 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -871,7 +871,7 @@ func Test_popup_complete_backwards_ctrl_p() endfunc fun! Test_complete_o_tab() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override let s:o_char_pressed = 0 fun! s:act_on_text_changed() diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 704fdacdcd..da949f5940 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -2660,7 +2660,7 @@ endfunc " Test for incsearch highlighting of the :vimgrep pattern " This test used to cause "E315: ml_get: invalid lnum" errors. func Test_vimgrep_incsearch() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override enew set incsearch call test_override("char_avail", 1) diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 0703a6b141..d4d529e4b9 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -2,10 +2,11 @@ source shared.vim source screendump.vim +source check.vim +" See test/functional/legacy/search_spec.lua func Test_search_cmdline() - " See test/functional/legacy/search_spec.lua - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !exists('+incsearch') return endif @@ -202,9 +203,9 @@ func Test_search_cmdline() bw! endfunc +" See test/functional/legacy/search_spec.lua func Test_search_cmdline2() - " See test/functional/legacy/search_spec.lua - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !exists('+incsearch') return endif @@ -351,7 +352,7 @@ func Test_searchc() endfunc func Cmdline3_prep() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override " need to disable char_avail, " so that expansion of commandline works call test_override("char_avail", 1) @@ -361,14 +362,13 @@ func Cmdline3_prep() endfunc func Incsearch_cleanup() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override set noincsearch call test_override("char_avail", 0) bw! endfunc func Test_search_cmdline3() - throw 'skipped: Nvim does not support test_override()' if !exists('+incsearch') return endif @@ -382,7 +382,6 @@ func Test_search_cmdline3() endfunc func Test_search_cmdline3s() - throw 'skipped: Nvim does not support test_override()' if !exists('+incsearch') return endif @@ -409,7 +408,6 @@ func Test_search_cmdline3s() endfunc func Test_search_cmdline3g() - throw 'skipped: Nvim does not support test_override()' if !exists('+incsearch') return endif @@ -433,7 +431,6 @@ func Test_search_cmdline3g() endfunc func Test_search_cmdline3v() - throw 'skipped: Nvim does not support test_override()' if !exists('+incsearch') return endif @@ -450,9 +447,9 @@ func Test_search_cmdline3v() call Incsearch_cleanup() endfunc +" See test/functional/legacy/search_spec.lua func Test_search_cmdline4() - " See test/functional/legacy/search_spec.lua - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !exists('+incsearch') return endif @@ -507,7 +504,7 @@ func Test_search_cmdline5() endfunc func Test_search_cmdline7() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override " Test that pressing <c-g> in an empty command line " does not move the cursor if !exists('+incsearch') @@ -798,7 +795,7 @@ func Test_incsearch_vimgrep_dump() endfunc func Test_keep_last_search_pattern() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !exists('+incsearch') return endif @@ -820,7 +817,7 @@ func Test_keep_last_search_pattern() endfunc func Test_word_under_cursor_after_match() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !exists('+incsearch') return endif @@ -840,7 +837,7 @@ func Test_word_under_cursor_after_match() endfunc func Test_subst_word_under_cursor() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !exists('+incsearch') return endif @@ -882,7 +879,7 @@ func Test_incsearch_with_change() endfunc func Test_incsearch_cmdline_modifier() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !exists('+incsearch') return endif @@ -960,7 +957,7 @@ func Test_incsearch_search_dump() endfunc func Test_incsearch_substitute() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !exists('+incsearch') return endif @@ -982,7 +979,7 @@ func Test_incsearch_substitute() endfunc func Test_incsearch_substitute_long_line() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override new call test_override("char_avail", 1) set incsearch @@ -1104,7 +1101,7 @@ func Test_one_error_msg() endfunc func Test_incsearch_add_char_under_cursor() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !exists('+incsearch') return endif @@ -1192,4 +1189,40 @@ func Test_search_smartcase_utf8() close! endfunc +func Test_zzzz_incsearch_highlighting_newline() + CheckRunVimInTerminal + CheckOption incsearch + CheckScreendump + new + call test_override("char_avail", 1) + + let commands =<< trim [CODE] + set incsearch nohls + call setline(1, ['test', 'xxx']) + [CODE] + call writefile(commands, 'Xincsearch_nl') + let buf = RunVimInTerminal('-S Xincsearch_nl', {'rows': 5, 'cols': 10}) + " Need to send one key at a time to force a redraw + call term_sendkeys(buf, '/test') + sleep 100m + call VerifyScreenDump(buf, 'Test_incsearch_newline1', {}) + call term_sendkeys(buf, '\n') + sleep 100m + call VerifyScreenDump(buf, 'Test_incsearch_newline2', {}) + call term_sendkeys(buf, 'x') + sleep 100m + call VerifyScreenDump(buf, 'Test_incsearch_newline3', {}) + call term_sendkeys(buf, 'x') + call VerifyScreenDump(buf, 'Test_incsearch_newline4', {}) + call term_sendkeys(buf, "\<CR>") + sleep 100m + call VerifyScreenDump(buf, 'Test_incsearch_newline5', {}) + call StopVimInTerminal(buf) + + " clean up + call delete('Xincsearch_nl') + call test_override("char_avail", 0) + bw +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim index 4bbd722fdb..9c3a5636ce 100644 --- a/src/nvim/testdir/test_signs.vim +++ b/src/nvim/testdir/test_signs.vim @@ -1,8 +1,7 @@ " Test for signs -if !has('signs') - finish -endif +source check.vim +CheckFeature signs source screendump.vim @@ -1541,7 +1540,7 @@ endfunc " Tests for memory allocation failures in sign functions func Test_sign_memfailures() - throw 'skipped: Nvim does not support test_alloc_fail()' + CheckFunction test_alloc_fail call writefile(repeat(["Sun is shining"], 30), "Xsign") edit Xsign diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index e6ad92f483..eb9378194f 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -111,10 +111,9 @@ func Test_pack_in_rtp_when_plugins_run() endfunc func Test_help_arg() - if !has('unix') && has('gui') - " this doesn't work with gvim on MS-Windows - return - endif + " This does not work with a GUI-only binary, such as on MS-Windows. + CheckAnyOf Unix NotGui + if RunVim([], [], '--help >Xtestout') let lines = readfile('Xtestout') call assert_true(len(lines) > 20) @@ -412,6 +411,134 @@ func Test_A_F_H_arg() call delete('Xtestout') endfunc +" Test the --echo-wid argument (for GTK GUI only). +func Test_echo_wid() + CheckCanRunGui + CheckFeature gui_gtk + + if RunVim([], [], '-g --echo-wid -cq >Xtest_echo_wid') + let lines = readfile('Xtest_echo_wid') + call assert_equal(1, len(lines)) + call assert_match('^WID: \d\+$', lines[0]) + endif + + call delete('Xtest_echo_wid') +endfunction + +" Test the -reverse and +reverse arguments (for GUI only). +func Test_reverse() + CheckCanRunGui + CheckAnyOf Feature:gui_gtk Feature:gui_motif Feature:gui_athena + + let after =<< trim [CODE] + call writefile([&background], "Xtest_reverse") + qall + [CODE] + if RunVim([], after, '-f -g -reverse') + let lines = readfile('Xtest_reverse') + call assert_equal(['dark'], lines) + endif + if RunVim([], after, '-f -g +reverse') + let lines = readfile('Xtest_reverse') + call assert_equal(['light'], lines) + endif + + call delete('Xtest_reverse') +endfunc + +" Test the -background and -foreground arguments (for GUI only). +func Test_background_foreground() + CheckCanRunGui + CheckAnyOf Feature:gui_gtk Feature:gui_motif Feature:gui_athena + + " Is there a better way to check the effect of -background & -foreground + " other than merely looking at &background (dark or light)? + let after =<< trim [CODE] + call writefile([&background], "Xtest_fg_bg") + qall + [CODE] + if RunVim([], after, '-f -g -background darkred -foreground yellow') + let lines = readfile('Xtest_fg_bg') + call assert_equal(['dark'], lines) + endif + if RunVim([], after, '-f -g -background ivory -foreground darkgreen') + let lines = readfile('Xtest_fg_bg') + call assert_equal(['light'], lines) + endif + + call delete('Xtest_fg_bg') +endfunc + +" Test the -font argument (for GUI only). +func Test_font() + CheckCanRunGui + CheckNotMSWindows + + if has('gui_gtk') + let font = 'Courier 14' + elseif has('gui_motif') || has('gui_athena') + let font = '-misc-fixed-bold-*' + else + throw 'Skipped: test does not set a valid font for this GUI' + endif + + let after =<< trim [CODE] + call writefile([&guifont], "Xtest_font") + qall + [CODE] + + if RunVim([], after, '--nofork -g -font "' .. font .. '"') + let lines = readfile('Xtest_font') + call assert_equal([font], lines) + endif + + call delete('Xtest_font') +endfunc + +" Test the -geometry argument (for GUI only). +func Test_geometry() + CheckCanRunGui + CheckAnyOf Feature:gui_gtk Feature:gui_motif Feature:gui_athena + + if has('gui_motif') || has('gui_athena') + " FIXME: With GUI Athena or Motif, the value of getwinposx(), + " getwinposy() and getwinpos() do not match exactly the + " value given in -geometry. Why? + " So only check &columns and &lines for those GUIs. + let after =<< trim [CODE] + call writefile([&columns, &lines], "Xtest_geometry") + qall + [CODE] + if RunVim([], after, '-f -g -geometry 31x13+41+43') + let lines = readfile('Xtest_geometry') + call assert_equal(['31', '13'], lines) + endif + else + let after =<< trim [CODE] + call writefile([&columns, &lines, getwinposx(), getwinposy(), string(getwinpos())], "Xtest_geometry") + qall + [CODE] + if RunVim([], after, '-f -g -geometry 31x13+41+43') + let lines = readfile('Xtest_geometry') + call assert_equal(['31', '13', '41', '43', '[41, 43]'], lines) + endif + endif + + call delete('Xtest_geometry') +endfunc + +" Test the -iconic argument (for GUI only). +func Test_iconic() + CheckCanRunGui + CheckAnyOf Feature:gui_gtk Feature:gui_motif Feature:gui_athena + + call RunVim([], [], '-f -g -iconic -cq') + + " TODO: currently only start vim iconified, but does not + " check that vim is iconified. How could this be checked? +endfunc + + func Test_invalid_args() if !has('unix') || has('gui_running') " can't get output of Vim. @@ -735,6 +862,34 @@ func Test_x_arg() call delete('Xtest_x_arg') endfunc +" Test for --not-a-term avoiding escape codes. +func Test_not_a_term() + CheckUnix + CheckNotGui + + if &shellredir =~ '%s' + let redir = printf(&shellredir, 'Xvimout') + else + let redir = &shellredir .. ' Xvimout' + endif + + " Without --not-a-term there are a few escape sequences. + " This will take 2 seconds because of the missing --not-a-term + let cmd = GetVimProg() .. ' --cmd quit ' .. redir + exe "silent !" . cmd + " call assert_match("\<Esc>", readfile('Xvimout')->join()) + call assert_match("\<Esc>", join(readfile('Xvimout'))) + call delete('Xvimout') + + " With --not-a-term there are no escape sequences. + let cmd = GetVimProg() .. ' --not-a-term --cmd quit ' .. redir + exe "silent !" . cmd + " call assert_notmatch("\<Esc>", readfile('Xvimout')->join()) + call assert_notmatch("\<Esc>", join(readfile('Xvimout'))) + call delete('Xvimout') +endfunc + + " Test starting vim with various names: vim, ex, view, evim, etc. func Test_progname() CheckUnix @@ -777,17 +932,12 @@ func Test_progname() let prognames = ['nvim'] for progname in prognames - if empty($DISPLAY) - if progname =~# 'g' - " Can't run gvim, gview (etc.) if $DISPLAY is not setup. - continue - endif - if has('gui') && (progname ==# 'evim' || progname ==# 'eview') - " evim or eview will start the GUI if there is gui support. - " So don't try to start them either if $DISPLAY is not setup. - continue - endif - endif + let run_with_gui = (progname =~# 'g') || (has('gui') && (progname ==# 'evim' || progname ==# 'eview')) + + if empty($DISPLAY) && run_with_gui + " Can't run gvim, gview (etc.) if $DISPLAY is not setup. + continue + endif exe 'silent !ln -s -f ' ..exepath(GetVimProg()) .. ' Xprogname/' .. progname @@ -801,7 +951,15 @@ func Test_progname() if progname =~# 'g' && !has('gui') call assert_equal("E25: GUI cannot be used: Not enabled at compile time\n", stdout_stderr, progname) else - call assert_equal('', stdout_stderr, progname) + " GUI motif can output some warnings like this: + " Warning: + " Name: subMenu + " Class: XmCascadeButton + " Illegal mnemonic character; Could not convert X KEYSYM to a keycode + " So don't check that stderr is empty with GUI Motif. + if run_with_gui && !has('gui_motif') + call assert_equal('', stdout_stderr, progname) + endif call assert_equal(expectations[progname], readfile('Xprogname_out'), progname) endif diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim index 9cf8690d57..6bbe714d19 100644 --- a/src/nvim/testdir/test_system.vim +++ b/src/nvim/testdir/test_system.vim @@ -93,7 +93,6 @@ function! Test_system_exmode() endfunc func Test_system_with_shell_quote() - throw 'skipped: enable after porting method patches' CheckMSWindows call mkdir('Xdir with spaces', 'p') @@ -122,7 +121,8 @@ func Test_system_with_shell_quote() let msg = printf('shell=%s shellxquote=%s', &shell, &shellxquote) try - let out = 'echo 123'->system() + " let out = 'echo 123'->system() + let out = system('echo 123') catch call assert_report(printf('%s: %s', msg, v:exception)) continue diff --git a/src/nvim/testdir/test_tagfunc.vim b/src/nvim/testdir/test_tagfunc.vim index 242aa3a235..ffc1d63b90 100644 --- a/src/nvim/testdir/test_tagfunc.vim +++ b/src/nvim/testdir/test_tagfunc.vim @@ -43,12 +43,24 @@ func Test_tagfunc() call assert_equal('one', g:tagfunc_args[0]) call assert_equal('c', g:tagfunc_args[1]) + let g:tagfunc_args=[] + execute "tag /foo$" + call assert_equal('foo$', g:tagfunc_args[0]) + call assert_equal('r', g:tagfunc_args[1]) + set cpt=t let g:tagfunc_args=[] execute "normal! i\<c-n>\<c-y>" - call assert_equal('ci', g:tagfunc_args[1]) + call assert_equal('\<\k\k', g:tagfunc_args[0]) + call assert_equal('cir', g:tagfunc_args[1]) call assert_equal('nothing1', getline('.')[0:7]) + let g:tagfunc_args=[] + execute "normal! ono\<c-n>\<c-n>\<c-y>" + call assert_equal('\<no', g:tagfunc_args[0]) + call assert_equal('cir', g:tagfunc_args[1]) + call assert_equal('nothing2', getline('.')[0:7]) + func BadTagFunc1(...) return 0 endfunc @@ -81,4 +93,28 @@ func Test_tagfunc() call delete('Xfile1') endfunc +" Test for modifying the tag stack from a tag function and jumping to a tag +" from a tag function +func Test_tagfunc_settagstack() + func Mytagfunc1(pat, flags, info) + call settagstack(1, {'tagname' : 'mytag', 'from' : [0, 10, 1, 0]}) + return [{'name' : 'mytag', 'filename' : 'Xtest', 'cmd' : '1'}] + endfunc + set tagfunc=Mytagfunc1 + call writefile([''], 'Xtest') + call assert_fails('tag xyz', 'E986:') + + func Mytagfunc2(pat, flags, info) + tag test_tag + return [{'name' : 'mytag', 'filename' : 'Xtest', 'cmd' : '1'}] + endfunc + set tagfunc=Mytagfunc2 + call assert_fails('tag xyz', 'E986:') + + call delete('Xtest') + set tagfunc& + delfunc Mytagfunc1 + delfunc Mytagfunc2 +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index 7057cdefb2..9f02af7d8e 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -609,6 +609,295 @@ func Test_tagline() set tags& endfunc +" Test for expanding environment variable in a tag file name +func Test_tag_envvar() + call writefile(["Func1\t$FOO\t/^Func1/"], 'Xtags') + set tags=Xtags + + let $FOO='TagTestEnv' + + let caught_exception = v:false + try + tag Func1 + catch /E429:/ + call assert_match('E429:.*"TagTestEnv".*', v:exception) + let caught_exception = v:true + endtry + call assert_true(caught_exception) + + set tags& + call delete('Xtags') + unlet $FOO +endfunc + +" Test for :ptag +func Test_ptag() + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "second\tXfile1\t2", + \ "third\tXfile1\t3",], + \ 'Xtags') + set tags=Xtags + call writefile(['first', 'second', 'third'], 'Xfile1') + + enew | only + ptag third + call assert_equal(2, winnr()) + call assert_equal(2, winnr('$')) + call assert_equal(1, getwinvar(1, '&previewwindow')) + call assert_equal(0, getwinvar(2, '&previewwindow')) + wincmd w + call assert_equal(3, line('.')) + + " jump to the tag again + ptag third + call assert_equal(3, line('.')) + + " close the preview window + pclose + call assert_equal(1, winnr('$')) + + call delete('Xfile1') + call delete('Xtags') + set tags& +endfunc + +" Tests for guessing the tag location +func Test_tag_guess() + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "func1\tXfoo\t/^int func1(int x)/", + \ "func2\tXfoo\t/^int func2(int y)/", + \ "func3\tXfoo\t/^func3/", + \ "func4\tXfoo\t/^func4/"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + + int FUNC1 (int x) { } + int + func2 (int y) { } + int * func3 () { } + + [CODE] + call writefile(code, 'Xfoo') + + let v:statusmsg = '' + ta func1 + call assert_match('E435:', v:statusmsg) + call assert_equal(2, line('.')) + let v:statusmsg = '' + ta func2 + call assert_match('E435:', v:statusmsg) + call assert_equal(4, line('.')) + let v:statusmsg = '' + ta func3 + call assert_match('E435:', v:statusmsg) + call assert_equal(5, line('.')) + call assert_fails('ta func4', 'E434:') + + call delete('Xtags') + call delete('Xfoo') + set tags& +endfunc + +" Test for an unsorted tags file +func Test_tag_sort() + call writefile([ + \ "first\tXfoo\t1", + \ "ten\tXfoo\t3", + \ "six\tXfoo\t2"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int six() {} + int ten() {} + [CODE] + call writefile(code, 'Xfoo') + + call assert_fails('tag first', 'E432:') + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + +" Test for an unsorted tags file +func Test_tag_fold() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "!_TAG_FILE_SORTED\t2\t/0=unsorted, 1=sorted, 2=foldcase/", + \ "first\tXfoo\t1", + \ "second\tXfoo\t2", + \ "third\tXfoo\t3"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int second() {} + int third() {} + [CODE] + call writefile(code, 'Xfoo') + + enew + tag second + call assert_equal('Xfoo', bufname('')) + call assert_equal(2, line('.')) + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + +" Test for the :ltag command +func Test_ltag() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfoo\t1", + \ "second\tXfoo\t/^int second() {}$/", + \ "third\tXfoo\t3"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int second() {} + int third() {} + [CODE] + call writefile(code, 'Xfoo') + + enew + call setloclist(0, [], 'f') + ltag third + call assert_equal('Xfoo', bufname('')) + call assert_equal(3, line('.')) + call assert_equal([{'lnum': 3, 'bufnr': bufnr('Xfoo'), 'col': 0, + \ 'pattern': '', 'valid': 1, 'vcol': 0, 'nr': 0, 'type': '', + \ 'module': '', 'text': 'third'}], getloclist(0)) + + ltag second + call assert_equal(2, line('.')) + call assert_equal([{'lnum': 0, 'bufnr': bufnr('Xfoo'), 'col': 0, + \ 'pattern': '^\Vint second() {}\$', 'valid': 1, 'vcol': 0, 'nr': 0, + \ 'type': '', 'module': '', 'text': 'second'}], getloclist(0)) + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + +" Test for setting the last search pattern to the tag search pattern +" when cpoptions has 't' +func Test_tag_last_search_pat() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfoo\t/^int first() {}/", + \ "second\tXfoo\t/^int second() {}/", + \ "third\tXfoo\t/^int third() {}/"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int second() {} + int third() {} + [CODE] + call writefile(code, 'Xfoo') + + enew + let save_cpo = &cpo + set cpo+=t + let @/ = '' + tag second + call assert_equal('^int second() {}', @/) + let &cpo = save_cpo + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + +" Test for jumping to a tag when the tag stack is full +func Test_tag_stack_full() + let l = [] + for i in range(10, 31) + let l += ["var" .. i .. "\tXfoo\t/^int var" .. i .. ";$/"] + endfor + call writefile(l, 'Xtags') + set tags=Xtags + + let l = [] + for i in range(10, 31) + let l += ["int var" .. i .. ";"] + endfor + call writefile(l, 'Xfoo') + + enew + for i in range(10, 30) + exe "tag var" .. i + endfor + let l = gettagstack() + call assert_equal(20, l.length) + call assert_equal('var11', l.items[0].tagname) + tag var31 + let l = gettagstack() + call assert_equal('var12', l.items[0].tagname) + call assert_equal('var31', l.items[19].tagname) + + " Jump from the top of the stack + call assert_fails('tag', 'E556:') + + " Pop from an unsaved buffer + enew! + call append(1, "sample text") + call assert_fails('pop', 'E37:') + call assert_equal(21, gettagstack().curidx) + enew! + + " Pop all the entries in the tag stack + call assert_fails('30pop', 'E555:') + + " Pop the tag stack when it is empty + call settagstack(1, {'items' : []}) + call assert_fails('pop', 'E73:') + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + +" Test for browsing multiple matching tags +func Test_tag_multimatch() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfoo\t1", + \ "first\tXfoo\t2", + \ "first\tXfoo\t3"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int first() {} + int first() {} + [CODE] + call writefile(code, 'Xfoo') + + tag first + tlast + call assert_equal(3, line('.')) + call assert_fails('tnext', 'E428:') + tfirst + call assert_equal(1, line('.')) + call assert_fails('tprev', 'E425:') + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + " Test for the 'taglength' option func Test_tag_length() set tags=Xtags diff --git a/src/nvim/testdir/test_taglist.vim b/src/nvim/testdir/test_taglist.vim index d4ff42fd68..e830813081 100644 --- a/src/nvim/testdir/test_taglist.vim +++ b/src/nvim/testdir/test_taglist.vim @@ -126,3 +126,99 @@ func Test_tagsfile_without_trailing_newline() call delete('Xtags') set tags& endfunc + +" Test for ignoring comments in a tags file +func Test_tagfile_ignore_comments() + call writefile([ + \ "!_TAG_PROGRAM_NAME /Test tags generator/", + \ "FBar\tXfoo\t2" .. ';"' .. "\textrafield\tf", + \ "!_TAG_FILE_FORMAT 2 /extended format/", + \ ], 'Xtags') + set tags=Xtags + + let l = taglist('.*') + call assert_equal(1, len(l)) + call assert_equal('FBar', l[0].name) + + set tags& + call delete('Xtags') +endfunc + +" Test for using an excmd in a tags file to position the cursor (instead of a +" search pattern or a line number) +func Test_tagfile_excmd() + call writefile([ + \ "vFoo\tXfoo\tcall cursor(3, 4)" .. '|;"' .. "\tv", + \ ], 'Xtags') + set tags=Xtags + + let l = taglist('.*') + call assert_equal([{ + \ 'cmd' : 'call cursor(3, 4)', + \ 'static' : 0, + \ 'name' : 'vFoo', + \ 'kind' : 'v', + \ 'filename' : 'Xfoo'}], l) + + set tags& + call delete('Xtags') +endfunc + +" Test for duplicate fields in a tag in a tags file +func Test_duplicate_field() + call writefile([ + \ "vFoo\tXfoo\t4" .. ';"' .. "\ttypename:int\ttypename:int\tv", + \ ], 'Xtags') + set tags=Xtags + + let l = taglist('.*') + call assert_equal([{ + \ 'cmd' : '4', + \ 'static' : 0, + \ 'name' : 'vFoo', + \ 'kind' : 'v', + \ 'typename' : 'int', + \ 'filename' : 'Xfoo'}], l) + + set tags& + call delete('Xtags') +endfunc + +" Test for tag address with ; +func Test_tag_addr_with_semicolon() + call writefile([ + \ "Func1\tXfoo\t6;/^Func1/" .. ';"' .. "\tf" + \ ], 'Xtags') + set tags=Xtags + + let l = taglist('.*') + call assert_equal([{ + \ 'cmd' : '6;/^Func1/', + \ 'static' : 0, + \ 'name' : 'Func1', + \ 'kind' : 'f', + \ 'filename' : 'Xfoo'}], l) + + set tags& + call delete('Xtags') +endfunc + +" Test for format error in a tags file +func Test_format_error() + call writefile(['vFoo-Xfoo-4'], 'Xtags') + set tags=Xtags + + let caught_exception = v:false + try + let l = taglist('.*') + catch /E431:/ + " test succeeded + let caught_exception = v:true + catch + call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint) + endtry + call assert_true(caught_exception) + + set tags& + call delete('Xtags') +endfunc diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index 13971a918d..ceaa5de92b 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -317,8 +317,8 @@ endfunc " Test that the garbage collector isn't triggered if a timer callback invokes " vgetc(). func Test_nocatch_garbage_collect() - " skipped: Nvim does not support test_garbagecollect_soon(), test_override() - return + CheckFunction test_garbagecollect_soon + CheckFunction test_override " 'uptimetime. must be bigger than the timer timeout set ut=200 call test_garbagecollect_soon() diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim index 3b66071d6d..54caed3983 100644 --- a/src/nvim/testdir/test_undo.vim +++ b/src/nvim/testdir/test_undo.vim @@ -3,6 +3,8 @@ " undo-able pieces. Do that by setting 'undolevels'. " Also tests :earlier and :later. +source check.vim + func Test_undotree() new @@ -135,7 +137,7 @@ func BackOne(expected) endfunc func Test_undo_del_chars() - throw 'skipped: Nvim does not support test_settime()' + CheckFunction test_settime " Setup a buffer without creating undo entries new @@ -330,7 +332,7 @@ func Test_insert_expr() endfunc func Test_undofile_earlier() - throw 'skipped: Nvim does not support test_settime()' + CheckFunction test_settime let t0 = localtime() - 43200 call test_settime(t0) diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index 969b75d424..a522705238 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -550,16 +550,29 @@ endfunc func Test_winrestcmd() 2split 3vsplit - let a = winrestcmd() + let restcmd = winrestcmd() call assert_equal(2, winheight(0)) call assert_equal(3, winwidth(0)) wincmd = call assert_notequal(2, winheight(0)) call assert_notequal(3, winwidth(0)) - exe a + exe restcmd call assert_equal(2, winheight(0)) call assert_equal(3, winwidth(0)) only + + wincmd v + wincmd s + wincmd v + redraw + let restcmd = winrestcmd() + wincmd _ + wincmd | + exe restcmd + redraw + call assert_equal(restcmd, winrestcmd()) + + only endfunc function! Fun_RenewFile() @@ -808,6 +821,55 @@ func Test_winnr() only | tabonly endfunc +func Test_win_splitmove() + edit a + leftabove split b + leftabove vsplit c + leftabove split d + call assert_equal(0, win_splitmove(winnr(), winnr('l'))) + call assert_equal(bufname(winbufnr(1)), 'c') + call assert_equal(bufname(winbufnr(2)), 'd') + call assert_equal(bufname(winbufnr(3)), 'b') + call assert_equal(bufname(winbufnr(4)), 'a') + call assert_equal(0, win_splitmove(winnr(), winnr('j'), {'vertical': 1})) + call assert_equal(0, win_splitmove(winnr(), winnr('j'), {'vertical': 1})) + call assert_equal(bufname(winbufnr(1)), 'c') + call assert_equal(bufname(winbufnr(2)), 'b') + call assert_equal(bufname(winbufnr(3)), 'd') + call assert_equal(bufname(winbufnr(4)), 'a') + call assert_equal(0, win_splitmove(winnr(), winnr('k'), {'vertical': 1})) + call assert_equal(bufname(winbufnr(1)), 'd') + call assert_equal(bufname(winbufnr(2)), 'c') + call assert_equal(bufname(winbufnr(3)), 'b') + call assert_equal(bufname(winbufnr(4)), 'a') + call assert_equal(0, win_splitmove(winnr(), winnr('j'), {'rightbelow': v:true})) + call assert_equal(bufname(winbufnr(1)), 'c') + call assert_equal(bufname(winbufnr(2)), 'b') + call assert_equal(bufname(winbufnr(3)), 'a') + call assert_equal(bufname(winbufnr(4)), 'd') + only | bd + + call assert_fails('call win_splitmove(winnr(), 123)', 'E957:') + call assert_fails('call win_splitmove(123, winnr())', 'E957:') + call assert_fails('call win_splitmove(winnr(), winnr())', 'E957:') + + tabnew + call assert_fails('call win_splitmove(1, win_getid(1, 1))', 'E957:') + tabclose +endfunc + +func Test_floatwin_splitmove() + vsplit + let win2 = win_getid() + let popup_winid = nvim_open_win(0, 0, {'relative': 'win', + \ 'row': 3, 'col': 3, 'width': 12, 'height': 3}) + call assert_fails('call win_splitmove(popup_winid, win2)', 'E957:') + call assert_fails('call win_splitmove(win2, popup_winid)', 'E957:') + + call nvim_win_close(popup_winid, 1) + bwipe +endfunc + func Test_window_resize() " Vertical :resize (absolute, relative, min and max size). vsplit diff --git a/src/nvim/ui.c b/src/nvim/ui.c index c6c09c80d7..94b6e9e39d 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -110,6 +110,7 @@ static char uilog_last_event[1024] = { 0 }; void ui_init(void) { default_grid.handle = 1; + msg_grid_adj.target = &default_grid; ui_comp_init(); } diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index 06efc9fa99..a2e9266fbb 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -127,6 +127,9 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width, bool valid, bool on_top) { bool moved; + + grid->comp_height = height; + grid->comp_width = width; if (grid->comp_index != 0) { moved = (row != grid->comp_row) || (col != grid->comp_col); if (ui_comp_should_draw()) { @@ -181,14 +184,12 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width, insert_at--; } // not found: new grid - kv_push(layers, grid); - if (insert_at < kv_size(layers)-1) { - for (size_t i = kv_size(layers)-1; i > insert_at; i--) { - kv_A(layers, i) = kv_A(layers, i-1); - kv_A(layers, i)->comp_index = i; - } - kv_A(layers, insert_at) = grid; + kv_pushp(layers); + for (size_t i = kv_size(layers)-1; i > insert_at; i--) { + kv_A(layers, i) = kv_A(layers, i-1); + kv_A(layers, i)->comp_index = i; } + kv_A(layers, insert_at) = grid; grid->comp_row = row; grid->comp_col = col; @@ -277,6 +278,9 @@ static void ui_comp_grid_cursor_goto(UI *ui, Integer grid_handle, // should configure all grids before entering win_update() if (curgrid != &default_grid) { size_t new_index = kv_size(layers)-1; + if (kv_A(layers, new_index) == &msg_grid) { + new_index--; + } if (kv_A(layers, new_index) == &pum_grid) { new_index--; } @@ -334,17 +338,25 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, sattr_T *bg_attrs = &default_grid.attrs[default_grid.line_offset[row] +(size_t)startcol]; + int grid_width, grid_height; while (col < endcol) { int until = 0; for (size_t i = 0; i < kv_size(layers); i++) { ScreenGrid *g = kv_A(layers, i); - if (g->comp_row > row || row >= g->comp_row + g->Rows + // compose_line may have been called after a shrinking operation but + // before the resize has actually been applied. Therefore, we need to + // first check to see if any grids have pending updates to width/height, + // to ensure that we don't accidentally put any characters into `linebuf` + // that have been invalidated. + grid_width = MIN(g->Columns, g->comp_width); + grid_height = MIN(g->Rows, g->comp_height); + if (g->comp_row > row || row >= g->comp_row + grid_height || g->comp_disabled) { continue; } - if (g->comp_col <= col && col < g->comp_col+g->Columns) { + if (g->comp_col <= col && col < g->comp_col + grid_width) { grid = g; - until = g->comp_col+g->Columns; + until = g->comp_col + grid_width; } else if (g->comp_col > col) { until = MIN(until, g->comp_col); } diff --git a/src/nvim/window.c b/src/nvim/window.c index 0f717a2f90..fd7af108b7 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -605,6 +605,7 @@ win_T *win_new_float(win_T *wp, FloatConfig fconfig, Error *err) wp->w_vsep_width = 0; win_config_float(wp, fconfig); + win_set_inner_size(wp); wp->w_pos_changed = true; redraw_later(wp, VALID); return wp; @@ -667,6 +668,8 @@ void win_config_float(win_T *wp, FloatConfig fconfig) } bool change_external = fconfig.external != wp->w_float_config.external; + bool change_border = fconfig.border != wp->w_float_config.border; + wp->w_float_config = fconfig; if (!ui_has(kUIMultigrid)) { @@ -676,11 +679,18 @@ void win_config_float(win_T *wp, FloatConfig fconfig) win_set_inner_size(wp); must_redraw = MAX(must_redraw, VALID); + wp->w_pos_changed = true; - if (change_external) { + if (change_external || change_border) { wp->w_hl_needs_update = true; redraw_later(wp, NOT_VALID); } + + // changing border style while keeping border only requires redrawing border + if (fconfig.border) { + wp->w_redr_border = true; + redraw_later(wp, VALID); + } } void win_check_anchored_floats(win_T *win) @@ -710,10 +720,10 @@ int win_fdccol_count(win_T *wp) } -static void ui_ext_win_position(win_T *wp) +void ui_ext_win_position(win_T *wp) { if (!wp->w_floating) { - ui_call_win_pos(wp->w_grid.handle, wp->handle, wp->w_winrow, + ui_call_win_pos(wp->w_grid_alloc.handle, wp->handle, wp->w_winrow, wp->w_wincol, wp->w_width, wp->w_height); return; } @@ -743,8 +753,8 @@ static void ui_ext_win_position(win_T *wp) } if (ui_has(kUIMultigrid)) { String anchor = cstr_to_string(float_anchor_str[c.anchor]); - ui_call_win_float_pos(wp->w_grid.handle, wp->handle, anchor, grid->handle, - row, col, c.focusable); + ui_call_win_float_pos(wp->w_grid_alloc.handle, wp->handle, anchor, + grid->handle, row, col, c.focusable); } else { // TODO(bfredl): ideally, compositor should work like any multigrid UI // and use standard win_pos events. @@ -759,17 +769,17 @@ static void ui_ext_win_position(win_T *wp) wp->w_wincol = comp_col; bool valid = (wp->w_redr_type == 0); bool on_top = (curwin == wp) || !curwin->w_floating; - ui_comp_put_grid(&wp->w_grid, comp_row, comp_col, wp->w_height, - wp->w_width, valid, on_top); - ui_check_cursor_grid(wp->w_grid.handle); - wp->w_grid.focusable = wp->w_float_config.focusable; + ui_comp_put_grid(&wp->w_grid_alloc, comp_row, comp_col, + wp->w_height_outer, wp->w_width_outer, valid, on_top); + ui_check_cursor_grid(wp->w_grid_alloc.handle); + wp->w_grid_alloc.focusable = wp->w_float_config.focusable; if (!valid) { - wp->w_grid.valid = false; + wp->w_grid_alloc.valid = false; redraw_later(wp, NOT_VALID); } } } else { - ui_call_win_external_pos(wp->w_grid.handle, wp->handle); + ui_call_win_external_pos(wp->w_grid_alloc.handle, wp->handle); } } @@ -784,260 +794,12 @@ void ui_ext_win_viewport(win_T *wp) // interact with incomplete final line? Diff filler lines? botline = wp->w_buffer->b_ml.ml_line_count; } - ui_call_win_viewport(wp->w_grid.handle, wp->handle, wp->w_topline-1, + ui_call_win_viewport(wp->w_grid_alloc.handle, wp->handle, wp->w_topline-1, botline, wp->w_cursor.lnum-1, wp->w_cursor.col); wp->w_viewport_invalid = false; } } -static bool parse_float_anchor(String anchor, FloatAnchor *out) -{ - if (anchor.size == 0) { - *out = (FloatAnchor)0; - } - char *str = anchor.data; - if (striequal(str, "NW")) { - *out = 0; // NW is the default - } else if (striequal(str, "NE")) { - *out = kFloatAnchorEast; - } else if (striequal(str, "SW")) { - *out = kFloatAnchorSouth; - } else if (striequal(str, "SE")) { - *out = kFloatAnchorSouth | kFloatAnchorEast; - } else { - return false; - } - return true; -} - -static bool parse_float_relative(String relative, FloatRelative *out) -{ - char *str = relative.data; - if (striequal(str, "editor")) { - *out = kFloatRelativeEditor; - } else if (striequal(str, "win")) { - *out = kFloatRelativeWindow; - } else if (striequal(str, "cursor")) { - *out = kFloatRelativeCursor; - } else { - return false; - } - return true; -} - -static bool parse_float_bufpos(Array bufpos, lpos_T *out) -{ - if (bufpos.size != 2 - || bufpos.items[0].type != kObjectTypeInteger - || bufpos.items[1].type != kObjectTypeInteger) { - return false; - } - out->lnum = bufpos.items[0].data.integer; - out->col = bufpos.items[1].data.integer; - return true; -} - -bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, - Error *err) -{ - // TODO(bfredl): use a get/has_key interface instead and get rid of extra - // flags - bool has_row = false, has_col = false, has_relative = false; - bool has_external = false, has_window = false; - bool has_width = false, has_height = false; - bool has_bufpos = false; - - for (size_t i = 0; i < config.size; i++) { - char *key = config.items[i].key.data; - Object val = config.items[i].value; - if (!strcmp(key, "row")) { - has_row = true; - if (val.type == kObjectTypeInteger) { - fconfig->row = val.data.integer; - } else if (val.type == kObjectTypeFloat) { - fconfig->row = val.data.floating; - } else { - api_set_error(err, kErrorTypeValidation, - "'row' key must be Integer or Float"); - return false; - } - } else if (!strcmp(key, "col")) { - has_col = true; - if (val.type == kObjectTypeInteger) { - fconfig->col = val.data.integer; - } else if (val.type == kObjectTypeFloat) { - fconfig->col = val.data.floating; - } else { - api_set_error(err, kErrorTypeValidation, - "'col' key must be Integer or Float"); - return false; - } - } else if (strequal(key, "width")) { - has_width = true; - if (val.type == kObjectTypeInteger && val.data.integer > 0) { - fconfig->width = val.data.integer; - } else { - api_set_error(err, kErrorTypeValidation, - "'width' key must be a positive Integer"); - return false; - } - } else if (strequal(key, "height")) { - has_height = true; - if (val.type == kObjectTypeInteger && val.data.integer > 0) { - fconfig->height= val.data.integer; - } else { - api_set_error(err, kErrorTypeValidation, - "'height' key must be a positive Integer"); - return false; - } - } else if (!strcmp(key, "anchor")) { - if (val.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, - "'anchor' key must be String"); - return false; - } - if (!parse_float_anchor(val.data.string, &fconfig->anchor)) { - api_set_error(err, kErrorTypeValidation, - "Invalid value of 'anchor' key"); - return false; - } - } else if (!strcmp(key, "relative")) { - if (val.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, - "'relative' key must be String"); - return false; - } - // ignore empty string, to match nvim_win_get_config - if (val.data.string.size > 0) { - has_relative = true; - if (!parse_float_relative(val.data.string, &fconfig->relative)) { - api_set_error(err, kErrorTypeValidation, - "Invalid value of 'relative' key"); - return false; - } - } - } else if (!strcmp(key, "win")) { - has_window = true; - if (val.type != kObjectTypeInteger - && val.type != kObjectTypeWindow) { - api_set_error(err, kErrorTypeValidation, - "'win' key must be Integer or Window"); - return false; - } - fconfig->window = val.data.integer; - } else if (!strcmp(key, "bufpos")) { - if (val.type != kObjectTypeArray) { - api_set_error(err, kErrorTypeValidation, - "'bufpos' key must be Array"); - return false; - } - if (!parse_float_bufpos(val.data.array, &fconfig->bufpos)) { - api_set_error(err, kErrorTypeValidation, - "Invalid value of 'bufpos' key"); - return false; - } - has_bufpos = true; - } else if (!strcmp(key, "external")) { - if (val.type == kObjectTypeInteger) { - fconfig->external = val.data.integer; - } else if (val.type == kObjectTypeBoolean) { - fconfig->external = val.data.boolean; - } else { - api_set_error(err, kErrorTypeValidation, - "'external' key must be Boolean"); - return false; - } - has_external = fconfig->external; - } else if (!strcmp(key, "focusable")) { - if (val.type == kObjectTypeInteger) { - fconfig->focusable = val.data.integer; - } else if (val.type == kObjectTypeBoolean) { - fconfig->focusable = val.data.boolean; - } else { - api_set_error(err, kErrorTypeValidation, - "'focusable' key must be Boolean"); - return false; - } - } else if (!strcmp(key, "style")) { - if (val.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, - "'style' key must be String"); - return false; - } - if (val.data.string.data[0] == NUL) { - fconfig->style = kWinStyleUnused; - } else if (striequal(val.data.string.data, "minimal")) { - fconfig->style = kWinStyleMinimal; - } else { - api_set_error(err, kErrorTypeValidation, - "Invalid value of 'style' key"); - } - } else { - api_set_error(err, kErrorTypeValidation, - "Invalid key '%s'", key); - return false; - } - } - - if (has_window && !(has_relative - && fconfig->relative == kFloatRelativeWindow)) { - api_set_error(err, kErrorTypeValidation, - "'win' key is only valid with relative='win'"); - return false; - } - - if ((has_relative && fconfig->relative == kFloatRelativeWindow) - && (!has_window || fconfig->window == 0)) { - fconfig->window = curwin->handle; - } - - if (has_window && !has_bufpos) { - fconfig->bufpos.lnum = -1; - } - - if (has_bufpos) { - if (!has_row) { - fconfig->row = (fconfig->anchor & kFloatAnchorSouth) ? 0 : 1; - has_row = true; - } - if (!has_col) { - fconfig->col = 0; - has_col = true; - } - } - - if (has_relative && has_external) { - api_set_error(err, kErrorTypeValidation, - "Only one of 'relative' and 'external' must be used"); - return false; - } else if (!reconf && !has_relative && !has_external) { - api_set_error(err, kErrorTypeValidation, - "One of 'relative' and 'external' must be used"); - return false; - } else if (has_relative) { - fconfig->external = false; - } - - if (!reconf && !(has_height && has_width)) { - api_set_error(err, kErrorTypeValidation, - "Must specify 'width' and 'height'"); - return false; - } - - if (fconfig->external && !ui_has(kUIMultigrid)) { - api_set_error(err, kErrorTypeValidation, - "UI doesn't support external windows"); - return false; - } - - if (has_relative != has_row || has_row != has_col) { - api_set_error(err, kErrorTypeValidation, - "'relative' requires 'row'/'col' or 'bufpos'"); - return false; - } - return true; -} - /* * split the current window, implements CTRL-W s and :split * @@ -1619,6 +1381,23 @@ static void win_init_some(win_T *newp, win_T *oldp) win_copy_options(oldp, newp); } +/// Return TRUE if "win" is floating window in the current tab page. +/// +/// @param win window to check +bool win_valid_floating(const win_T *win) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (win == NULL) { + return false; + } + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp == win) { + return wp->w_floating; + } + } + return false; +} /// Check if "win" is a pointer to an existing window in the current tabpage. /// @@ -1936,12 +1715,12 @@ static void win_totop(int size, int flags) } if (curwin->w_floating) { - ui_comp_remove_grid(&curwin->w_grid); + ui_comp_remove_grid(&curwin->w_grid_alloc); if (ui_has(kUIMultigrid)) { curwin->w_pos_changed = true; } else { // No longer a float, a non-multigrid UI shouldn't draw it as such - ui_call_win_hide(curwin->w_grid.handle); + ui_call_win_hide(curwin->w_grid_alloc.handle); win_free_grid(curwin, false); } } else { @@ -2564,11 +2343,11 @@ int win_close(win_T *win, bool free_buf) bool was_floating = win->w_floating; if (ui_has(kUIMultigrid)) { - ui_call_win_close(win->w_grid.handle); + ui_call_win_close(win->w_grid_alloc.handle); } if (win->w_floating) { - ui_comp_remove_grid(&win->w_grid); + ui_comp_remove_grid(&win->w_grid_alloc); if (win->w_float_config.external) { for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) { if (tp == curtab) { @@ -3746,9 +3525,11 @@ void win_init_size(void) { firstwin->w_height = ROWS_AVAIL; firstwin->w_height_inner = firstwin->w_height; + firstwin->w_height_outer = firstwin->w_height; topframe->fr_height = ROWS_AVAIL; firstwin->w_width = Columns; firstwin->w_width_inner = firstwin->w_width; + firstwin->w_width_outer = firstwin->w_width; topframe->fr_width = Columns; } @@ -4114,7 +3895,7 @@ static void tabpage_check_windows(tabpage_T *old_curtab) win_remove(wp, old_curtab); win_append(lastwin_nofloating(), wp); } else { - ui_comp_remove_grid(&wp->w_grid); + ui_comp_remove_grid(&wp->w_grid_alloc); } } wp->w_pos_changed = true; @@ -4709,7 +4490,7 @@ static win_T *win_alloc(win_T *after, int hidden) new_wp->handle = ++last_win_id; handle_register_window(new_wp); - grid_assign_handle(&new_wp->w_grid); + grid_assign_handle(&new_wp->w_grid_alloc); // Init w: variables. new_wp->w_vars = tv_dict_alloc(); @@ -4833,15 +4614,14 @@ win_free ( void win_free_grid(win_T *wp, bool reinit) { - if (wp->w_grid.handle != 0 && ui_has(kUIMultigrid)) { - ui_call_grid_destroy(wp->w_grid.handle); - wp->w_grid.handle = 0; + if (wp->w_grid_alloc.handle != 0 && ui_has(kUIMultigrid)) { + ui_call_grid_destroy(wp->w_grid_alloc.handle); } - grid_free(&wp->w_grid); + grid_free(&wp->w_grid_alloc); if (reinit) { // if a float is turned into a split and back into a float, the grid // data structure will be reused - memset(&wp->w_grid, 0, sizeof(wp->w_grid)); + memset(&wp->w_grid_alloc, 0, sizeof(wp->w_grid_alloc)); } } @@ -5501,8 +5281,8 @@ void win_setminheight(void) // loop until there is a 'winminheight' that is possible while (p_wmh > 0) { - const int room = Rows - p_ch - tabline_height(); - const int needed = frame_minheight(topframe, NULL); + const int room = Rows - p_ch; + const int needed = min_rows() - 1; // 1 was added for the cmdline if (room >= needed) { break; } @@ -5946,6 +5726,10 @@ void win_set_inner_size(win_T *wp) if (wp->w_buffer->terminal) { terminal_check_size(wp->w_buffer->terminal); } + + wp->w_border_adj = wp->w_floating && wp->w_float_config.border ? 1 : 0; + wp->w_height_outer = wp->w_height_inner + 2 * wp->w_border_adj; + wp->w_width_outer = wp->w_width_inner + 2 * wp->w_border_adj; } /// Set the width of a window. @@ -7082,11 +6866,11 @@ void get_framelayout(const frame_T *fr, list_T *l, bool outer) void win_ui_flush(void) { FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_pos_changed && wp->w_grid.chars != NULL) { + if (wp->w_pos_changed && wp->w_grid_alloc.chars != NULL) { if (tp == curtab) { ui_ext_win_position(wp); } else { - ui_call_win_hide(wp->w_grid.handle); + ui_call_win_hide(wp->w_grid_alloc.handle); } wp->w_pos_changed = false; } diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 437a1858f3..3db44f3f11 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -2087,4 +2087,67 @@ describe('API', function() eq("", meths.exec("messages", true)) end) end) + + + describe('nvim_open_term', function() + local screen + + before_each(function() + clear() + screen = Screen.new(100, 35) + screen:attach() + screen:set_default_attr_ids({ + [0] = {bold=true, foreground=Screen.colors.Blue}, + [1] = {background = Screen.colors.Plum1}; + [2] = {background = tonumber('0xffff40'), bg_indexed = true}; + [3] = {background = Screen.colors.Plum1, fg_indexed = true, foreground = tonumber('0x00e000')}; + [4] = {bold = true, reverse = true, background = Screen.colors.Plum1}; + }) + end) + + it('can batch process sequences', function() + local b = meths.create_buf(true,true) + meths.open_win(b, false, {width=79, height=31, row=1, col=1, relative='editor'}) + local t = meths.open_term(b, {}) + + meths.chan_send(t, io.open("test/functional/fixtures/smile2.cat", "r"):read("*a")) + screen:expect{grid=[[ + ^ | + {0:~}{1::smile }{0: }| + {0:~}{1: }{2:oooo$$$$$$$$$$$$oooo}{1: }{0: }| + {0:~}{1: }{2:oo$$$$$$$$$$$$$$$$$$$$$$$$o}{1: }{0: }| + {0:~}{1: }{2:oo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o}{1: }{2:o$}{1: }{2:$$}{1: }{2:o$}{1: }{0: }| + {0:~}{1: }{2:o}{1: }{2:$}{1: }{2:oo}{1: }{2:o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o}{1: }{2:$$}{1: }{2:$$}{1: }{2:$$o$}{1: }{0: }| + {0:~}{1: }{2:oo}{1: }{2:$}{1: }{2:$}{1: "}{2:$}{1: }{2:o$$$$$$$$$}{1: }{2:$$$$$$$$$$$$$}{1: }{2:$$$$$$$$$o}{1: }{2:$$$o$$o$}{1: }{0: }| + {0:~}{1: "}{2:$$$$$$o$}{1: }{2:o$$$$$$$$$}{1: }{2:$$$$$$$$$$$}{1: }{2:$$$$$$$$$$o}{1: }{2:$$$$$$$$}{1: }{0: }| + {0:~}{1: }{2:$$$$$$$}{1: }{2:$$$$$$$$$$$}{1: }{2:$$$$$$$$$$$}{1: }{2:$$$$$$$$$$$$$$$$$$$$$$$}{1: }{0: }| + {0:~}{1: }{2:$$$$$$$$$$$$$$$$$$$$$$$}{1: }{2:$$$$$$$$$$$$$}{1: }{2:$$$$$$$$$$$$$$}{1: """}{2:$$$}{1: }{0: }| + {0:~}{1: "}{2:$$$}{1:""""}{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1: "}{2:$$$}{1: }{0: }| + {0:~}{1: }{2:$$$}{1: }{2:o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1: "}{2:$$$o}{1: }{0: }| + {0:~}{1: }{2:o$$}{1:" }{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1: }{2:$$$o}{1: }{0: }| + {0:~}{1: }{2:$$$}{1: }{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1:" "}{2:$$$$$$ooooo$$$$o}{1: }{0: }| + {0:~}{1: }{2:o$$$oooo$$$$$}{1: }{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1: }{2:o$$$$$$$$$$$$$$$$$}{1: }{0: }| + {0:~}{1: }{2:$$$$$$$$}{1:"}{2:$$$$}{1: }{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1: }{2:$$$$}{1:"""""""" }{0: }| + {0:~}{1: """" }{2:$$$$}{1: "}{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1:" }{2:o$$$}{1: }{0: }| + {0:~}{1: "}{2:$$$o}{1: """}{2:$$$$$$$$$$$$$$$$$$}{1:"}{2:$$}{1:" }{2:$$$}{1: }{0: }| + {0:~}{1: }{2:$$$o}{1: "}{2:$$}{1:""}{2:$$$$$$}{1:"""" }{2:o$$$}{1: }{0: }| + {0:~}{1: }{2:$$$$o}{1: }{2:o$$$}{1:" }{0: }| + {0:~}{1: "}{2:$$$$o}{1: }{2:o$$$$$$o}{1:"}{2:$$$$o}{1: }{2:o$$$$}{1: }{0: }| + {0:~}{1: "}{2:$$$$$oo}{1: ""}{2:$$$$o$$$$$o}{1: }{2:o$$$$}{1:"" }{0: }| + {0:~}{1: ""}{2:$$$$$oooo}{1: "}{2:$$$o$$$$$$$$$}{1:""" }{0: }| + {0:~}{1: ""}{2:$$$$$$$oo}{1: }{2:$$$$$$$$$$}{1: }{0: }| + {0:~}{1: """"}{2:$$$$$$$$$$$}{1: }{0: }| + {0:~}{1: }{2:$$$$$$$$$$$$}{1: }{0: }| + {0:~}{1: }{2:$$$$$$$$$$}{1:" }{0: }| + {0:~}{1: "}{2:$$$}{1:"""" }{0: }| + {0:~}{1: }{0: }| + {0:~}{3:Press ENTER or type command to continue}{1: }{0: }| + {0:~}{4:term://~/config2/docs/pres//32693:vim --clean +smile 29,39 All}{0: }| + {0:~}{1::call nvim__screenshot("smile2.cat") }{0: }| + {0:~ }| + {0:~ }| + | + ]]} + end) + end) end) diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua index 3bbb4c4517..bcd5e22492 100644 --- a/test/functional/fixtures/fake-lsp-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -154,6 +154,7 @@ function tests.capabilities_for_client_supports_method() hoverProvider = true; definitionProvider = false; referencesProvider = false; + codeLensProvider = { resolveProvider = true; }; } } end; @@ -402,11 +403,11 @@ function tests.basic_check_buffer_open_and_change_incremental() contentChanges = { { range = { - start = { line = 1; character = 0; }; - ["end"] = { line = 2; character = 0; }; + start = { line = 1; character = 3; }; + ["end"] = { line = 1; character = 3; }; }; - rangeLength = 4; - text = "boop\n"; + rangeLength = 0; + text = "boop"; }; } }) diff --git a/test/functional/fixtures/smile2.cat b/test/functional/fixtures/smile2.cat new file mode 100644 index 0000000000..0feb32f293 --- /dev/null +++ b/test/functional/fixtures/smile2.cat @@ -0,0 +1,32 @@ +31,79 +[?25l[H[2J:smile
+ [103moooo$$$$$$$$$$$$oooo(B[m
+ [103moo$$$$$$$$$$$$$$$$$$$$$$$$o(B[m
+ [103moo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o(B[m [103mo$(B[m [103m$$(B[m [103mo$(B[m
+ [103mo(B[m [103m$(B[m [103moo(B[m [103mo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o(B[m [103m$$(B[m [103m$$(B[m [103m$$o$(B[m
+ [103moo(B[m [103m$(B[m [103m$(B[m "[103m$(B[m [103mo$$$$$$$$$(B[m [103m$$$$$$$$$$$$$(B[m [103m$$$$$$$$$o(B[m [103m$$$o$$o$(B[m
+ "[103m$$$$$$o$(B[m [103mo$$$$$$$$$(B[m [103m$$$$$$$$$$$(B[m [103m$$$$$$$$$$o(B[m [103m$$$$$$$$(B[m
+ [103m$$$$$$$(B[m [103m$$$$$$$$$$$(B[m [103m$$$$$$$$$$$(B[m [103m$$$$$$$$$$$$$$$$$$$$$$$(B[m
+ [103m$$$$$$$$$$$$$$$$$$$$$$$(B[m [103m$$$$$$$$$$$$$(B[m [103m$$$$$$$$$$$$$$(B[m """[103m$$$(B[m
+ "[103m$$$(B[m""""[103m$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m "[103m$$$(B[m
+ [103m$$$(B[m [103mo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m "[103m$$$o(B[m
+ [103mo$$(B[m" [103m$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m [103m$$$o(B[m
+ [103m$$$(B[m [103m$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m" "[103m$$$$$$ooooo$$$$o(B[m
+ [103mo$$$oooo$$$$$(B[m [103m$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m [103mo$$$$$$$$$$$$$$$$$(B[m
+ [103m$$$$$$$$(B[m"[103m$$$$(B[m [103m$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m [103m$$$$(B[m""""""""
+ """" [103m$$$$(B[m "[103m$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m" [103mo$$$(B[m
+ "[103m$$$o(B[m """[103m$$$$$$$$$$$$$$$$$$(B[m"[103m$$(B[m" [103m$$$(B[m
+ [103m$$$o(B[m "[103m$$(B[m""[103m$$$$$$(B[m"""" [103mo$$$(B[m
+ [103m$$$$o(B[m [103mo$$$(B[m"
+ "[103m$$$$o(B[m [103mo$$$$$$o(B[m"[103m$$$$o(B[m [103mo$$$$(B[m
+ "[103m$$$$$oo(B[m ""[103m$$$$o$$$$$o(B[m [103mo$$$$(B[m""
+ ""[103m$$$$$oooo(B[m "[103m$$$o$$$$$$$$$(B[m"""
+ ""[103m$$$$$$$oo(B[m [103m$$$$$$$$$$(B[m
+ """"[103m$$$$$$$$$$$(B[m
+ [103m$$$$$$$$$$$$(B[m
+ [103m$$$$$$$$$$(B[m"
+ "[103m$$$(B[m""""
+
+[32mPress ENTER or type command to continue(B[m
+(B[0;1;7mterm://~/config2/docs/pres//32693:vim --clean +smile 29,39 All
+(B[m:call nvim__screenshot("smile2.cat") [?25h
\ No newline at end of file diff --git a/test/functional/legacy/memory_usage_spec.lua b/test/functional/legacy/memory_usage_spec.lua index 5f7bbd887f..97ac96804e 100644 --- a/test/functional/legacy/memory_usage_spec.lua +++ b/test/functional/legacy/memory_usage_spec.lua @@ -157,8 +157,8 @@ describe('memory usage', function() -- The usage may be a bit less than the last value, use 80%. -- Allow for 20% tolerance at the upper limit. That's very permissive, but -- otherwise the test fails sometimes. On Sourcehut CI with FreeBSD we need to - -- be even more permissive. - local upper_multiplier = uname() == 'freebsd' and 15 or 12 + -- be even much more permissive. + local upper_multiplier = uname() == 'freebsd' and 19 or 12 local lower = before.last * 8 / 10 local upper = load_adjust((after.max + (after.last - before.last)) * upper_multiplier / 10) check_result({before=before, after=after, last=last}, diff --git a/test/functional/legacy/options_spec.lua b/test/functional/legacy/options_spec.lua index d7f5df3a1e..023cdd4ae1 100644 --- a/test/functional/legacy/options_spec.lua +++ b/test/functional/legacy/options_spec.lua @@ -42,6 +42,20 @@ describe('set', function() matches('E36: Not enough room', exc_exec('set wmh=1')) end) + it('winminheight works with tabline', function() + local screen = Screen.new(20, 11) + screen:attach() + source([[ + set wmh=0 stal=2 + split + split + split + split + tabnew + ]]) + matches('E36: Not enough room', exc_exec('set wmh=1')) + end) + it('scroll works', function() local screen = Screen.new(42, 16) screen:attach() diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index 7a6b5be8bc..6d7d9b4d8b 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -666,6 +666,70 @@ describe('lua: nvim_buf_attach on_bytes', function() } end) + it("tab with noexpandtab and softtabstop", function() + command("set noet") + command("set ts=4") + command("set sw=2") + command("set sts=2") + + local check_events = setup_eventcheck(verify, {'asdfasdf'}) + + feed("gg0i<tab>") + + check_events { + { "test1", "bytes", 1, 3, 0, 0, 0, 0, 0, 0, 0, 1, 1 }, + { "test1", "bytes", 1, 4, 0, 1, 1, 0, 0, 0, 0, 1, 1 }, + } + feed("<tab>") + + -- when spaces are merged into a tabstop + check_events { + { "test1", "bytes", 1, 5, 0, 2, 2, 0, 0, 0, 0, 1, 1 }, + { "test1", "bytes", 1, 6, 0, 3, 3, 0, 0, 0, 0, 1, 1 }, + { "test1", "bytes", 1, 7, 0, 0, 0, 0, 4, 4, 0, 1, 1 }, + } + + feed("<esc>u") + check_events { + { "test1", "bytes", 1, 8, 0, 0, 0, 0, 1, 1, 0, 4, 4 }, + { "test1", "bytes", 1, 8, 0, 0, 0, 0, 4, 4, 0, 0, 0 } + } + + -- in REPLACE mode + feed("R<tab><tab>") + check_events { + { "test1", "bytes", 1, 9, 0, 0, 0, 0, 1, 1, 0, 1, 1 }, + { "test1", "bytes", 1, 10, 0, 1, 1, 0, 0, 0, 0, 1, 1 }, + { "test1", "bytes", 1, 11, 0, 2, 2, 0, 1, 1, 0, 1, 1 }, + { "test1", "bytes", 1, 12, 0, 3, 3, 0, 0, 0, 0, 1, 1 }, + { "test1", "bytes", 1, 13, 0, 0, 0, 0, 4, 4, 0, 1, 1 }, + } + feed("<esc>u") + check_events { + { "test1", "bytes", 1, 14, 0, 0, 0, 0, 1, 1, 0, 4, 4 }, + { "test1", "bytes", 1, 14, 0, 2, 2, 0, 2, 2, 0, 1, 1 }, + { "test1", "bytes", 1, 14, 0, 0, 0, 0, 2, 2, 0, 1, 1 } + } + + -- in VISUALREPLACE mode + feed("gR<tab><tab>") + check_events { + { "test1", "bytes", 1, 15, 0, 0, 0, 0, 1, 1, 0, 1, 1 }; + { "test1", "bytes", 1, 16, 0, 1, 1, 0, 1, 1, 0, 1, 1 }; + { "test1", "bytes", 1, 17, 0, 2, 2, 0, 1, 1, 0, 1, 1 }; + { "test1", "bytes", 1, 18, 0, 3, 3, 0, 1, 1, 0, 1, 1 }; + { "test1", "bytes", 1, 19, 0, 3, 3, 0, 1, 1, 0, 0, 0 }; + { "test1", "bytes", 1, 20, 0, 3, 3, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 22, 0, 2, 2, 0, 1, 1, 0, 0, 0 }; + { "test1", "bytes", 1, 23, 0, 2, 2, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 25, 0, 1, 1, 0, 1, 1, 0, 0, 0 }; + { "test1", "bytes", 1, 26, 0, 1, 1, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 28, 0, 0, 0, 0, 1, 1, 0, 0, 0 }; + { "test1", "bytes", 1, 29, 0, 0, 0, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 31, 0, 0, 0, 0, 4, 4, 0, 1, 1 }; + } + end) + it("sends events when undoing with undofile", function() write_file("Xtest-undofile", dedent([[ 12345 @@ -717,6 +781,26 @@ describe('lua: nvim_buf_attach on_bytes', function() command("bw!") end) + it("blockwise paste with uneven line lengths", function() + local check_events = setup_eventcheck(verify, {'aaaa', 'aaa', 'aaa'}) + + -- eq({}, meths.buf_get_lines(0, 0, -1, true)) + feed("gg0<c-v>jj$d") + + check_events { + { "test1", "bytes", 1, 3, 0, 0, 0, 0, 4, 4, 0, 0, 0 }, + { "test1", "bytes", 1, 3, 1, 0, 1, 0, 3, 3, 0, 0, 0 }, + { "test1", "bytes", 1, 3, 2, 0, 2, 0, 3, 3, 0, 0, 0 }, + } + + feed("p") + check_events { + { "test1", "bytes", 1, 4, 0, 0, 0, 0, 0, 0, 0, 4, 4 }, + { "test1", "bytes", 1, 4, 1, 0, 5, 0, 0, 0, 0, 3, 3 }, + { "test1", "bytes", 1, 4, 2, 0, 9, 0, 0, 0, 0, 3, 3 }, + } + + end) teardown(function() os.remove "Xtest-reload" diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index d5791fbbfa..6d3af115fa 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -11,6 +11,8 @@ local pesc = helpers.pesc local insert = helpers.insert local retry = helpers.retry local NIL = helpers.NIL +local read_file = require('test.helpers').read_file +local write_file = require('test.helpers').write_file -- Use these to get access to a coroutine so that I can run async tests and use -- yield. @@ -27,10 +29,10 @@ teardown(function() os.remove(fake_lsp_logfile) end) -local function fake_lsp_server_setup(test_name, timeout_ms) +local function fake_lsp_server_setup(test_name, timeout_ms, options) exec_lua([=[ lsp = require('vim.lsp') - local test_name, fixture_filename, logfile, timeout = ... + local test_name, fixture_filename, logfile, timeout, options = ... TEST_RPC_CLIENT_ID = lsp.start_client { cmd_env = { NVIM_LOG_FILE = logfile; @@ -52,18 +54,19 @@ local function fake_lsp_server_setup(test_name, timeout_ms) on_init = function(client, result) TEST_RPC_CLIENT = client vim.rpcrequest(1, "init", result) + client.config.flags.allow_incremental_sync = options.allow_incremental_sync or false end; on_exit = function(...) vim.rpcnotify(1, "exit", ...) end; } - ]=], test_name, fake_lsp_code, fake_lsp_logfile, timeout_ms or 1e3) + ]=], test_name, fake_lsp_code, fake_lsp_logfile, timeout_ms or 1e3, options or {}) end local function test_rpc_server(config) if config.test_name then clear() - fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3) + fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3, config.options) end local client = setmetatable({}, { __index = function(_, name) @@ -335,6 +338,8 @@ describe('LSP', function() 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_save) + eq(false, client.resolved_capabilities().code_lens) + eq(false, client.resolved_capabilities().code_lens_resolve) end; on_exit = function(code, signal) eq(0, code, "exit code", fake_lsp_logfile) @@ -360,6 +365,8 @@ describe('LSP', function() eq(true, client.resolved_capabilities().hover) eq(false, client.resolved_capabilities().goto_definition) eq(false, client.resolved_capabilities().rename) + eq(true, client.resolved_capabilities().code_lens) + eq(true, client.resolved_capabilities().code_lens_resolve) -- known methods for resolved capabilities eq(true, client.supports_method("textDocument/hover")) @@ -681,8 +688,7 @@ describe('LSP', function() } end) - -- TODO(askhan) we don't support full for now, so we can disable these tests. - pending('should check the body and didChange incremental', function() + it('should check the body and didChange incremental', function() local expected_callbacks = { {NIL, "shutdown", {}, 1}; {NIL, "finish", {}, 1}; @@ -691,6 +697,7 @@ describe('LSP', function() local client test_rpc_server { test_name = "basic_check_buffer_open_and_change_incremental"; + options = { allow_incremental_sync = true }; on_setup = function() exec_lua [[ BUFFER = vim.api.nvim_create_buf(false, true) @@ -717,7 +724,7 @@ describe('LSP', function() if method == 'start' then exec_lua [[ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { - "boop"; + "123boop"; }) ]] client.notify('finish') @@ -1258,6 +1265,99 @@ describe('LSP', function() return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) ]], make_workspace_edit(edits), target_bufnr)) end) + it('Supports file creation with CreateFile payload', function() + local tmpfile = helpers.tmpname() + os.remove(tmpfile) -- Should not exist, only interested in a tmpname + local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) + local edit = { + documentChanges = { + { + kind = 'create', + uri = uri, + }, + } + } + exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit) + eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) + end) + it('createFile does not touch file if it exists and ignoreIfExists is set', function() + local tmpfile = helpers.tmpname() + write_file(tmpfile, 'Dummy content') + local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) + local edit = { + documentChanges = { + { + kind = 'create', + uri = uri, + options = { + ignoreIfExists = true, + }, + }, + } + } + exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit) + eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) + eq('Dummy content', read_file(tmpfile)) + end) + it('createFile overrides file if overwrite is set', function() + local tmpfile = helpers.tmpname() + write_file(tmpfile, 'Dummy content') + local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) + local edit = { + documentChanges = { + { + kind = 'create', + uri = uri, + options = { + overwrite = true, + ignoreIfExists = true, -- overwrite must win over ignoreIfExists + }, + }, + } + } + exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit) + eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) + eq('', read_file(tmpfile)) + end) + it('DeleteFile delete file and buffer', function() + local tmpfile = helpers.tmpname() + write_file(tmpfile, 'Be gone') + local uri = exec_lua([[ + local fname = select(1, ...) + local bufnr = vim.fn.bufadd(fname) + vim.fn.bufload(bufnr) + return vim.uri_from_fname(fname) + ]], tmpfile) + local edit = { + documentChanges = { + { + kind = 'delete', + uri = uri, + } + } + } + eq(true, pcall(exec_lua, 'vim.lsp.util.apply_workspace_edit(...)', edit)) + eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) + eq(false, exec_lua('return vim.api.nvim_buf_is_loaded(vim.fn.bufadd(...))', tmpfile)) + end) + it('DeleteFile fails if file does not exist and ignoreIfNotExists is false', function() + local tmpfile = helpers.tmpname() + os.remove(tmpfile) + local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) + local edit = { + documentChanges = { + { + kind = 'delete', + uri = uri, + options = { + ignoreIfNotExists = false, + } + } + } + } + eq(false, pcall(exec_lua, 'vim.lsp.util.apply_workspace_edit(...)', edit)) + eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) + end) end) describe('completion_list_to_complete_items', function() @@ -1304,6 +1404,72 @@ describe('LSP', function() end) end) + describe('lsp.util.rename', function() + it('Can rename an existing file', function() + local old = helpers.tmpname() + write_file(old, 'Test content') + local new = helpers.tmpname() + os.remove(new) -- only reserve the name, file must not exist for the test scenario + local lines = exec_lua([[ + local old = select(1, ...) + local new = select(2, ...) + vim.lsp.util.rename(old, new) + + -- after rename the target file must have the contents of the source file + local bufnr = vim.fn.bufadd(new) + return vim.api.nvim_buf_get_lines(bufnr, 0, -1, true) + ]], old, new) + eq({'Test content'}, lines) + local exists = exec_lua('return vim.loop.fs_stat(...) ~= nil', old) + eq(false, exists) + exists = exec_lua('return vim.loop.fs_stat(...) ~= nil', new) + eq(true, exists) + os.remove(new) + end) + it('Does not rename file if target exists and ignoreIfExists is set or overwrite is false', function() + local old = helpers.tmpname() + write_file(old, 'Old File') + local new = helpers.tmpname() + write_file(new, 'New file') + + exec_lua([[ + local old = select(1, ...) + local new = select(2, ...) + + vim.lsp.util.rename(old, new, { ignoreIfExists = true }) + ]], old, new) + + eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', old)) + eq('New file', read_file(new)) + + exec_lua([[ + local old = select(1, ...) + local new = select(2, ...) + + vim.lsp.util.rename(old, new, { overwrite = false }) + ]], old, new) + + eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', old)) + eq('New file', read_file(new)) + end) + it('Does override target if overwrite is true', function() + local old = helpers.tmpname() + write_file(old, 'Old file') + local new = helpers.tmpname() + write_file(new, 'New file') + exec_lua([[ + local old = select(1, ...) + local new = select(2, ...) + + vim.lsp.util.rename(old, new, { overwrite = true }) + ]], old, new) + + eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', old)) + eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', new)) + eq('Old file\n', read_file(new)) + end) + end) + describe('lsp.util.locations_to_items', function() it('Convert Location[] to items', function() local expected = { diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index e1a72ced05..f75f700fb5 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -212,10 +212,10 @@ describe('ui/cursor', function() if m.blinkwait then m.blinkwait = 700 end end if m.hl_id then - m.hl_id = 55 + m.hl_id = 56 m.attr = {background = Screen.colors.DarkGray} end - if m.id_lm then m.id_lm = 56 end + if m.id_lm then m.id_lm = 57 end end -- Assert the new expectation. diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 7a87521a6b..295a54aec8 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -8,6 +8,7 @@ local exec_lua = helpers.exec_lua local exec = helpers.exec local expect_events = helpers.expect_events local meths = helpers.meths +local command = helpers.command describe('decorations providers', function() local screen @@ -178,7 +179,7 @@ describe('decorations providers', function() | ]]} - meths.set_hl_ns(ns1) + meths._set_hl_ns(ns1) screen:expect{grid=[[ {10: 1 }{11:// just to see if there was an accid}| {10: }{11:ent} | @@ -204,7 +205,7 @@ describe('decorations providers', function() local ns2 = a.nvim_create_namespace 'ns2' a.nvim_set_decoration_provider (ns2, { on_win = function (_, win, buf) - a.nvim_set_hl_ns(win == thewin and _G.ns1 or ns2) + a.nvim__set_hl_ns(win == thewin and _G.ns1 or ns2) end; }) ]] @@ -251,7 +252,7 @@ describe('decorations providers', function() ]]} meths.set_hl(ns1, 'LinkGroup', {fg = 'Blue'}) - meths.set_hl_ns(ns1) + meths._set_hl_ns(ns1) screen:expect{grid=[[ // just to see if there was an accident | @@ -287,7 +288,7 @@ describe('decorations providers', function() ]]} meths.set_hl(ns1, 'LinkGroup', {fg = 'Blue', default=true}) - meths.set_hl_ns(ns1) + meths._set_hl_ns(ns1) feed 'k' screen:expect{grid=[[ @@ -301,6 +302,35 @@ describe('decorations providers', function() | ]]} end) + + it('can have virtual text', function() + insert(mulholland) + setup_provider [[ + local hl = a.nvim_get_hl_id_by_name "ErrorMsg" + local test_ns = a.nvim_create_namespace "mulholland" + function on_do(event, ...) + if event == "line" then + local win, buf, line = ... + a.nvim_buf_set_extmark(buf, test_ns, line, 0, { + virt_text = {{'+', 'ErrorMsg'}}; + virt_text_pos='overlay'; + ephemeral = true; + }) + end + end + ]] + + screen:expect{grid=[[ + {2:+}/ just to see if there was an accident | + {2:+}/ on Mulholland Drive | + {2:+}ry_start(); | + {2:+}ufref_T save_buf; | + {2:+}witch_buffer(&save_buf, buf); | + {2:+}osp = getmark(mark, false); | + {2:+}estore_buffer(&save_buf);^ | + | + ]]} + end) end) describe('extmark decorations', function() @@ -314,11 +344,30 @@ describe('extmark decorations', function() [2] = {foreground = Screen.colors.Brown}; [3] = {bold = true, foreground = Screen.colors.SeaGreen}; [4] = {background = Screen.colors.Red1, foreground = Screen.colors.Gray100}; + [5] = {foreground = Screen.colors.Brown, bold = true}; + [6] = {foreground = Screen.colors.DarkCyan}; + [7] = {foreground = Screen.colors.Grey0, background = tonumber('0xff4c4c')}; + [8] = {foreground = tonumber('0x180606'), background = tonumber('0xff4c4c')}; + [9] = {foreground = tonumber('0xe40c0c'), background = tonumber('0xff4c4c'), bold = true}; + [10] = {foreground = tonumber('0xb20000'), background = tonumber('0xff4c4c')}; + [11] = {blend = 30, background = Screen.colors.Red1}; + [12] = {foreground = Screen.colors.Brown, blend = 30, background = Screen.colors.Red1, bold = true}; + [13] = {foreground = Screen.colors.Fuchsia}; + [14] = {background = Screen.colors.Red1, foreground = Screen.colors.Black}; + [15] = {background = Screen.colors.Red1, foreground = tonumber('0xb20000')}; + [16] = {blend = 30, background = Screen.colors.Red1, foreground = Screen.colors.Magenta1}; + [17] = {bold = true, foreground = Screen.colors.Brown, background = Screen.colors.LightGrey}; + [18] = {background = Screen.colors.LightGrey}; + [19] = {foreground = Screen.colors.Cyan4, background = Screen.colors.LightGrey}; + [20] = {foreground = tonumber('0x180606'), background = tonumber('0xf13f3f')}; + [21] = {foreground = Screen.colors.Gray0, background = tonumber('0xf13f3f')}; + [22] = {foreground = tonumber('0xb20000'), background = tonumber('0xf13f3f')}; + [23] = {foreground = Screen.colors.Magenta1, background = Screen.colors.LightGrey}; + [24] = {bold = true}; } end) - it('can have virtual text of overlay style', function() - insert [[ + local example_text = [[ for _,item in ipairs(items) do local text, hl_id_cell, count = unpack(item) if hl_id_cell ~= nil then @@ -331,69 +380,164 @@ for _,item in ipairs(items) do colpos = colpos+1 end end]] - feed 'gg' - local ns = meths.create_namespace 'test' - for i = 1,9 do - meths.buf_set_extmark(0, ns, i, 0, { virt_text={{'|', 'LineNr'}}, virt_text_pos='overlay'}) - if i == 3 or (i >= 6 and i <= 9) then - meths.buf_set_extmark(0, ns, i, 4, { virt_text={{'|', 'NonText'}}, virt_text_pos='overlay'}) + it('can have virtual text of overlay position', function() + insert(example_text) + feed 'gg' + + local ns = meths.create_namespace 'test' + for i = 1,9 do + meths.buf_set_extmark(0, ns, i, 0, { virt_text={{'|', 'LineNr'}}, virt_text_pos='overlay'}) + if i == 3 or (i >= 6 and i <= 9) then + meths.buf_set_extmark(0, ns, i, 4, { virt_text={{'|', 'NonText'}}, virt_text_pos='overlay'}) + end end - end - meths.buf_set_extmark(0, ns, 9, 10, { virt_text={{'foo'}, {'bar', 'MoreMsg'}, {'!!', 'ErrorMsg'}}, virt_text_pos='overlay'}) - - -- can "float" beyond end of line - meths.buf_set_extmark(0, ns, 5, 28, { virt_text={{'loopy', 'ErrorMsg'}}, virt_text_pos='overlay'}) - -- bound check: right edge of window - meths.buf_set_extmark(0, ns, 2, 26, { virt_text={{'bork bork bork ' }, {'bork bork bork', 'ErrorMsg'}}, virt_text_pos='overlay'}) - - screen:expect{grid=[[ - ^for _,item in ipairs(items) do | - {2:|} local text, hl_id_cell, count = unpack(item) | - {2:|} if hl_id_cell ~= nil tbork bork bork {4:bork bork}| - {2:|} {1:|} hl_id = hl_id_cell | - {2:|} end | - {2:|} for _ = 1, (count or 1) {4:loopy} | - {2:|} {1:|} local cell = line[colpos] | - {2:|} {1:|} cell.text = text | - {2:|} {1:|} cell.hl_id = hl_id | - {2:|} {1:|} cofoo{3:bar}{4:!!}olpos+1 | - end | - end | - {1:~ }| - {1:~ }| - | - ]]} - - - -- handles broken lines - screen:try_resize(22, 25) - screen:expect{grid=[[ - ^for _,item in ipairs(i| - tems) do | - {2:|} local text, hl_id_| - cell, count = unpack(i| - tem) | - {2:|} if hl_id_cell ~= n| - il tbork bork bork {4:bor}| - {2:|} {1:|} hl_id = hl_id_| - cell | - {2:|} end | - {2:|} for _ = 1, (count | - or 1) {4:loopy} | - {2:|} {1:|} local cell = l| - ine[colpos] | - {2:|} {1:|} cell.text = te| - xt | - {2:|} {1:|} cell.hl_id = h| - l_id | - {2:|} {1:|} cofoo{3:bar}{4:!!}olpo| - s+1 | - end | - end | - {1:~ }| - {1:~ }| - | - ]]} + meths.buf_set_extmark(0, ns, 9, 10, { virt_text={{'foo'}, {'bar', 'MoreMsg'}, {'!!', 'ErrorMsg'}}, virt_text_pos='overlay'}) + + -- can "float" beyond end of line + meths.buf_set_extmark(0, ns, 5, 28, { virt_text={{'loopy', 'ErrorMsg'}}, virt_text_pos='overlay'}) + -- bound check: right edge of window + meths.buf_set_extmark(0, ns, 2, 26, { virt_text={{'bork bork bork ' }, {'bork bork bork', 'ErrorMsg'}}, virt_text_pos='overlay'}) + + screen:expect{grid=[[ + ^for _,item in ipairs(items) do | + {2:|} local text, hl_id_cell, count = unpack(item) | + {2:|} if hl_id_cell ~= nil tbork bork bork {4:bork bork}| + {2:|} {1:|} hl_id = hl_id_cell | + {2:|} end | + {2:|} for _ = 1, (count or 1) {4:loopy} | + {2:|} {1:|} local cell = line[colpos] | + {2:|} {1:|} cell.text = text | + {2:|} {1:|} cell.hl_id = hl_id | + {2:|} {1:|} cofoo{3:bar}{4:!!}olpos+1 | + end | + end | + {1:~ }| + {1:~ }| + | + ]]} + + + -- handles broken lines + screen:try_resize(22, 25) + screen:expect{grid=[[ + ^for _,item in ipairs(i| + tems) do | + {2:|} local text, hl_id_| + cell, count = unpack(i| + tem) | + {2:|} if hl_id_cell ~= n| + il tbork bork bork {4:bor}| + {2:|} {1:|} hl_id = hl_id_| + cell | + {2:|} end | + {2:|} for _ = 1, (count | + or 1) {4:loopy} | + {2:|} {1:|} local cell = l| + ine[colpos] | + {2:|} {1:|} cell.text = te| + xt | + {2:|} {1:|} cell.hl_id = h| + l_id | + {2:|} {1:|} cofoo{3:bar}{4:!!}olpo| + s+1 | + end | + end | + {1:~ }| + {1:~ }| + | + ]]} + end) + + it('can have virtual text of overlay position and styling', function() + insert(example_text) + feed 'gg' + local ns = meths.create_namespace 'test' + + command 'set ft=lua' + command 'syntax on' + + screen:expect{grid=[[ + {5:^for} _,item {5:in} {6:ipairs}(items) {5:do} | + {5:local} text, hl_id_cell, count = unpack(item) | + {5:if} hl_id_cell ~= {13:nil} {5:then} | + hl_id = hl_id_cell | + {5:end} | + {5:for} _ = {13:1}, (count {5:or} {13:1}) {5:do} | + {5:local} cell = line[colpos] | + cell.text = text | + cell.hl_id = hl_id | + colpos = colpos+{13:1} | + {5:end} | + {5:end} | + {1:~ }| + {1:~ }| + | + ]]} + + command 'hi Blendy guibg=Red blend=30' + meths.buf_set_extmark(0, ns, 1, 5, { virt_text={{'blendy text - here', 'Blendy'}}, virt_text_pos='overlay', hl_mode='blend'}) + meths.buf_set_extmark(0, ns, 2, 5, { virt_text={{'combining color', 'Blendy'}}, virt_text_pos='overlay', hl_mode='combine'}) + meths.buf_set_extmark(0, ns, 3, 5, { virt_text={{'replacing color', 'Blendy'}}, virt_text_pos='overlay', hl_mode='replace'}) + + meths.buf_set_extmark(0, ns, 4, 5, { virt_text={{'blendy text - here', 'Blendy'}}, virt_text_pos='overlay', hl_mode='blend', virt_text_hide=true}) + meths.buf_set_extmark(0, ns, 5, 5, { virt_text={{'combining color', 'Blendy'}}, virt_text_pos='overlay', hl_mode='combine', virt_text_hide=true}) + meths.buf_set_extmark(0, ns, 6, 5, { virt_text={{'replacing color', 'Blendy'}}, virt_text_pos='overlay', hl_mode='replace', virt_text_hide=true}) + + screen:expect{grid=[[ + {5:^for} _,item {5:in} {6:ipairs}(items) {5:do} | + {5:l}{8:blen}{7:dy}{10:e}{7:text}{10:h}{7:-}{10:_}{7:here}ell, count = unpack(item) | + {5:i}{12:c}{11:ombining color} {13:nil} {5:then} | + {11:replacing color}d_cell | + {5:e}{8:bl}{14:endy}{15:i}{14:text}{15:o}{14:-}{15:o}{14:h}{7:ere} | + {5:f}{12:co}{11:mbini}{16:n}{11:g color}t {5:or} {13:1}) {5:do} | + {11:replacing color} line[colpos] | + cell.text = text | + cell.hl_id = hl_id | + colpos = colpos+{13:1} | + {5:end} | + {5:end} | + {1:~ }| + {1:~ }| + | + ]]} + + feed 'V5G' + screen:expect{grid=[[ + {17:for}{18: _,item }{17:in}{18: }{19:ipairs}{18:(items) }{17:do} | + {18: }{17:l}{20:blen}{21:dy}{22:e}{21:text}{22:h}{21:-}{22:_}{21:here}{18:ell, count = unpack(item)} | + {18: }{17:i}{12:c}{11:ombining color}{18: }{23:nil}{18: }{17:then} | + {18: }{11:replacing color}{18:d_cell} | + {18: }{5:^e}{17:nd} | + {5:f}{12:co}{11:mbini}{16:n}{11:g color}t {5:or} {13:1}) {5:do} | + {11:replacing color} line[colpos] | + cell.text = text | + cell.hl_id = hl_id | + colpos = colpos+{13:1} | + {5:end} | + {5:end} | + {1:~ }| + {1:~ }| + {24:-- VISUAL LINE --} | + ]]} + + feed 'jj' + screen:expect{grid=[[ + {17:for}{18: _,item }{17:in}{18: }{19:ipairs}{18:(items) }{17:do} | + {18: }{17:l}{20:blen}{21:dy}{22:e}{21:text}{22:h}{21:-}{22:_}{21:here}{18:ell, count = unpack(item)} | + {18: }{17:i}{12:c}{11:ombining color}{18: }{23:nil}{18: }{17:then} | + {18: }{11:replacing color}{18:d_cell} | + {18: }{17:end} | + {18: }{17:for}{18: _ = }{23:1}{18:, (count }{17:or}{18: }{23:1}{18:) }{17:do} | + {18: }^ {18: }{17:local}{18: cell = line[colpos]} | + cell.text = text | + cell.hl_id = hl_id | + colpos = colpos+{13:1} | + {5:end} | + {5:end} | + {1:~ }| + {1:~ }| + {24:-- VISUAL LINE --} | + ]]} end) end) diff --git a/test/functional/ui/diff_spec.lua b/test/functional/ui/diff_spec.lua index 69b6ab8cf0..a8d9fb02fc 100644 --- a/test/functional/ui/diff_spec.lua +++ b/test/functional/ui/diff_spec.lua @@ -1049,6 +1049,8 @@ it('diff updates line numbers below filler lines', function() [9] = {background = Screen.colors.LightMagenta}, [10] = {bold = true, foreground = Screen.colors.Brown}, [11] = {foreground = Screen.colors.Brown}, + [12] = {foreground = Screen.colors.Brown, bold = true, background = Screen.colors.Red}; + [13] = {background = Screen.colors.Gray90}; }) source([[ call setline(1, ['a', 'a', 'a', 'y', 'b', 'b', 'b', 'b', 'b']) @@ -1107,4 +1109,22 @@ it('diff updates line numbers below filler lines', function() {3:[No Name] [+] }{7:[No Name] [+] }| | ]]) + command("set signcolumn number tgc cursorline") + command("hi CursorLineNr guibg=red") + screen:expect{grid=[[ + {1: }a {3:│}{11: 2 }a | + {1: }a {3:│}{11: 1 }a | + {1: }a {3:│}{12:3 }{13:^a }| + {1: }{8:x}{9: }{3:│}{11: 1 }{8:y}{9: }| + {1: }{4:x }{3:│}{11: }{2:----------------}| + {1: }{4:x }{3:│}{11: }{2:----------------}| + {1: }b {3:│}{11: 2 }b | + {1: }b {3:│}{11: 3 }b | + {1: }b {3:│}{11: 4 }b | + {1: }b {3:│}{11: 5 }b | + {1: }b {3:│}{11: 6 }b | + {6:~ }{3:│}{6:~ }| + {3:[No Name] [+] }{7:[No Name] [+] }| + signcolumn=auto | + ]]} end) diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 32f9ae030f..664b8e7ab7 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -14,7 +14,7 @@ local funcs = helpers.funcs local run = helpers.run local pcall_err = helpers.pcall_err -describe('floatwin', function() +describe('float window', function() before_each(function() clear() end) @@ -131,7 +131,7 @@ describe('floatwin', function() local screen before_each(function() screen = Screen.new(40,7) - screen:attach({ext_multigrid=multigrid}) + screen:attach {ext_multigrid=multigrid} screen:set_default_attr_ids(attrs) end) @@ -595,6 +595,310 @@ describe('floatwin', function() end end) + it('can have border', function() + local buf = meths.create_buf(false, false) + meths.buf_set_lines(buf, 0, -1, true, {' halloj! ', + ' BORDAA '}) + local win = meths.open_win(buf, false, {relative='editor', width=9, height=2, row=2, col=5, border="double"}) + + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5:╔═════════╗}| + {5:║}{1: halloj! }{5:║}| + {5:║}{1: BORDAA }{5:║}| + {5:╚═════════╝}| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 2, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }{5:╔═════════╗}{0: }| + {0:~ }{5:║}{1: halloj! }{5:║}{0: }| + {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| + {0:~ }{5:╚═════════╝}{0: }| + | + ]]} + end + + meths.win_set_config(win, {border="single"}) + + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5:┌─────────┐}| + {5:│}{1: halloj! }{5:│}| + {5:│}{1: BORDAA }{5:│}| + {5:└─────────┘}| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 2, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }{5:┌─────────┐}{0: }| + {0:~ }{5:│}{1: halloj! }{5:│}{0: }| + {0:~ }{5:│}{1: BORDAA }{5:│}{0: }| + {0:~ }{5:└─────────┘}{0: }| + | + ]]} + end + + -- support: ascii char, UTF-8 char, composed char, highlight per char + meths.win_set_config(win, {border={"x", {"å", "ErrorMsg"}, {"\\"}, {"n̈̊", "Search"}}}) + + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5:xååååååååå\}| + {5:n̈̊}{1: halloj! }{5:n̈̊}| + {5:n̈̊}{1: BORDAA }{5:n̈̊}| + {5:\åååååååååx}| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 2, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }{5:x}{7:ååååååååå}{5:\}{0: }| + {0:~ }{17:n̈̊}{1: halloj! }{17:n̈̊}{0: }| + {0:~ }{17:n̈̊}{1: BORDAA }{17:n̈̊}{0: }| + {0:~ }{5:\}{7:ååååååååå}{5:x}{0: }| + | + ]]} + end + + meths.win_set_config(win, {border="none"}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {1: halloj! }| + {1: BORDAA }| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 2, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }{1: halloj! }{0: }| + {0:~ }{1: BORDAA }{0: }| + {0:~ }| + {0:~ }| + | + ]]} + end + end) + + it('with border show popupmenu', function() + screen:try_resize(40,10) + local buf = meths.create_buf(false, false) + meths.buf_set_lines(buf, 0, -1, true, {'aaa aab ', + 'abb acc ', ''}) + meths.open_win(buf, true, {relative='editor', width=9, height=3, row=0, col=5, border="double"}) + feed 'G' + + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5:╔═════════╗}| + {5:║}{1:aaa aab }{5:║}| + {5:║}{1:abb acc }{5:║}| + {5:║}{1:^ }{5:║}| + {5:╚═════════╝}| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 0, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0}; + [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 2, curcol = 0}; + }} + else + screen:expect{grid=[[ + {5:╔═════════╗} | + {0:~ }{5:║}{1:aaa aab }{5:║}{0: }| + {0:~ }{5:║}{1:abb acc }{5:║}{0: }| + {0:~ }{5:║}{1:^ }{5:║}{0: }| + {0:~ }{5:╚═════════╝}{0: }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]} + end + + feed 'i<c-x><c-p>' + + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {3:-- }{8:match 1 of 4} | + ## grid 5 + {5:╔═════════╗}| + {5:║}{1:aaa aab }{5:║}| + {5:║}{1:abb acc }{5:║}| + {5:║}{1:acc^ }{5:║}| + {5:╚═════════╝}| + ## grid 6 + {1: aaa }| + {1: aab }| + {1: abb }| + {13: acc }| + ]], float_pos={ + [5] = { { + id = 1002 + }, "NW", 1, 0, 5, true }, + [6] = { { + id = -1 + }, "NW", 5, 4, 0, false } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0}; + [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 2, curcol = 3}; + }} + else + screen:expect{grid=[[ + {5:╔═════════╗} | + {0:~ }{5:║}{1:aaa aab }{5:║}{0: }| + {0:~ }{5:║}{1:abb acc }{5:║}{0: }| + {0:~ }{5:║}{1:acc^ }{5:║}{0: }| + {0:~ }{1: aaa }{0: }| + {0:~ }{1: aab }{0: }| + {0:~ }{1: abb }{0: }| + {0:~ }{13: acc }{0: }| + {0:~ }| + {3:-- }{8:match 1 of 4} | + ]]} + end + end) + it('can have minimum size', function() insert("the background text") local buf = meths.create_buf(false, true) @@ -5433,6 +5737,155 @@ describe('floatwin', function() ]]) end end) + + it("correctly redraws when overlaid windows are resized #13991", function() + helpers.source([[ + let popup_config = {"relative" : "editor", + \ "width" : 7, + \ "height" : 3, + \ "row" : 1, + \ "col" : 1, + \ "style" : "minimal"} + + let border_config = {"relative" : "editor", + \ "width" : 9, + \ "height" : 5, + \ "row" : 0, + \ "col" : 0, + \ "style" : "minimal"} + + let popup_buffer = nvim_create_buf(v:false, v:true) + let border_buffer = nvim_create_buf(v:false, v:true) + let popup_win = nvim_open_win(popup_buffer, v:true, popup_config) + let border_win = nvim_open_win(border_buffer, v:false, border_config) + + call nvim_buf_set_lines(popup_buffer, 0, -1, v:true, + \ ["long", "longer", "longest"]) + + call nvim_buf_set_lines(border_buffer, 0, -1, v:true, + \ ["---------", "- -", "- -"]) + ]]) + + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 5 + {2:^long }| + {2:longer }| + {2:longest}| + ## grid 6 + {2:---------}| + {2:- -}| + {2:- -}| + {2: }| + {2: }| + ]], attr_ids={ + [1] = {foreground = Screen.colors.Blue1, bold = true}; + [2] = {background = Screen.colors.LightMagenta}; + }, float_pos={ + [5] = { { + id = 1002 + }, "NW", 1, 1, 1, true }, + [6] = { { + id = 1003 + }, "NW", 1, 0, 0, true } + }} + else + screen:expect([[ + {1:---------} | + {1:-^long -}{0: }| + {1:-longer -}{0: }| + {1: longest }{0: }| + {1: }{0: }| + {0:~ }| + | + ]]) + end + + helpers.source([[ + let new_popup_config = {"width" : 1, "height" : 3} + let new_border_config = {"width" : 3, "height" : 5} + + function! Resize() + call nvim_win_set_config(g:popup_win, g:new_popup_config) + call nvim_win_set_config(g:border_win, g:new_border_config) + + call nvim_buf_set_lines(g:border_buffer, 0, -1, v:true, + \ ["---", "- -", "- -"]) + endfunction + + nnoremap zz <cmd>call Resize()<cr> + ]]) + + helpers.feed("zz") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 5 + {2:^l}| + {2:o}| + {2:n}| + ## grid 6 + {2:---}| + {2:- -}| + {2:- -}| + {2: }| + {2: }| + ]], attr_ids={ + [1] = {foreground = Screen.colors.Blue1, bold = true}; + [2] = {background = Screen.colors.LightMagenta}; + }, float_pos={ + [5] = { { + id = 1002 + }, "NW", 1, 1, 1, true }, + [6] = { { + id = 1003 + }, "NW", 1, 0, 0, true } + }} + else + screen:expect([[ + {1:---} | + {1:-^l-}{0: }| + {1:-o-}{0: }| + {1: n }{0: }| + {1: }{0: }| + {0:~ }| + | + ]]) + end + end) end describe('with ext_multigrid', function() diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua index d3b1d33956..8883ad8270 100644 --- a/test/functional/ui/fold_spec.lua +++ b/test/functional/ui/fold_spec.lua @@ -464,6 +464,206 @@ describe("folded lines", function() end end) + it("works with vsplit", function() + insert([[ + aa + bb + cc + dd + ee + ff]]) + feed_command('2') + command("set foldcolumn=1") + feed('zf3j') + feed_command('1') + feed('zf2j') + feed('zO') + feed_command("rightbelow vnew") + insert([[ + aa + bb + cc + dd + ee + ff]]) + feed_command('2') + command("set foldcolumn=1") + feed('zf3j') + feed_command('1') + feed('zf2j') + if multigrid then + meths.input_mouse('left', 'press', '', 4, 0, 0) + screen:expect([[ + ## grid 1 + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + {2:[No Name] [+] }{3:[No Name] [+] }| + [3:---------------------------------------------]| + ## grid 2 + {7:-}aa | + {7:-}bb | + {7:2}cc | + {7:2}dd | + {7:2}ee | + {7:│}ff | + ## grid 3 + :1 | + ## grid 4 + {7:-}^aa | + {7:+}{5:+--- 4 lines: bb····}| + {7:│}ff | + {1:~ }| + {1:~ }| + {1:~ }| + ]]) + else + meths.input_mouse('left', 'press', '', 0, 0, 23) + screen:expect([[ + {7:-}aa {2:│}{7:-}^aa | + {7:-}bb {2:│}{7:+}{5:+--- 4 lines: bb····}| + {7:2}cc {2:│}{7:│}ff | + {7:2}dd {2:│}{1:~ }| + {7:2}ee {2:│}{1:~ }| + {7:│}ff {2:│}{1:~ }| + {2:[No Name] [+] }{3:[No Name] [+] }| + :1 | + ]]) + end + + if multigrid then + meths.input_mouse('left', 'press', '', 4, 1, 0) + screen:expect([[ + ## grid 1 + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + {2:[No Name] [+] }{3:[No Name] [+] }| + [3:---------------------------------------------]| + ## grid 2 + {7:-}aa | + {7:-}bb | + {7:2}cc | + {7:2}dd | + {7:2}ee | + {7:│}ff | + ## grid 3 + :1 | + ## grid 4 + {7:-}^aa | + {7:-}bb | + {7:2}cc | + {7:2}dd | + {7:2}ee | + {7:│}ff | + ]]) + else + meths.input_mouse('left', 'press', '', 0, 1, 23) + screen:expect([[ + {7:-}aa {2:│}{7:-}^aa | + {7:-}bb {2:│}{7:-}bb | + {7:2}cc {2:│}{7:2}cc | + {7:2}dd {2:│}{7:2}dd | + {7:2}ee {2:│}{7:2}ee | + {7:│}ff {2:│}{7:│}ff | + {2:[No Name] [+] }{3:[No Name] [+] }| + :1 | + ]]) + end + + if multigrid then + meths.input_mouse('left', 'press', '', 2, 1, 0) + screen:expect([[ + ## grid 1 + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + {3:[No Name] [+] }{2:[No Name] [+] }| + [3:---------------------------------------------]| + ## grid 2 + {7:-}aa | + {7:+}{5:^+--- 4 lines: bb····}| + {7:│}ff | + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + :1 | + ## grid 4 + {7:-}aa | + {7:-}bb | + {7:2}cc | + {7:2}dd | + {7:2}ee | + {7:│}ff | + ]]) + else + meths.input_mouse('left', 'press', '', 0, 1, 0) + screen:expect([[ + {7:-}aa {2:│}{7:-}aa | + {7:+}{5:^+--- 4 lines: bb····}{2:│}{7:-}bb | + {7:│}ff {2:│}{7:2}cc | + {1:~ }{2:│}{7:2}dd | + {1:~ }{2:│}{7:2}ee | + {1:~ }{2:│}{7:│}ff | + {3:[No Name] [+] }{2:[No Name] [+] }| + :1 | + ]]) + end + + if multigrid then + meths.input_mouse('left', 'press', '', 2, 0, 0) + screen:expect([[ + ## grid 1 + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + {3:[No Name] [+] }{2:[No Name] [+] }| + [3:---------------------------------------------]| + ## grid 2 + {7:+}{5:^+-- 6 lines: aa·····}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + :1 | + ## grid 4 + {7:-}aa | + {7:-}bb | + {7:2}cc | + {7:2}dd | + {7:2}ee | + {7:│}ff | + ]]) + else + meths.input_mouse('left', 'press', '', 0, 0, 0) + screen:expect([[ + {7:+}{5:^+-- 6 lines: aa·····}{2:│}{7:-}aa | + {1:~ }{2:│}{7:-}bb | + {1:~ }{2:│}{7:2}cc | + {1:~ }{2:│}{7:2}dd | + {1:~ }{2:│}{7:2}ee | + {1:~ }{2:│}{7:│}ff | + {3:[No Name] [+] }{2:[No Name] [+] }| + :1 | + ]]) + end + end) + it("works with tab", function() insert([[ aa diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index ef3acd7d2e..8992ee27ce 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -333,10 +333,10 @@ describe('highlight defaults', function() command('highlight clear EndOfBuffer') screen:expect{grid=[[ ^ | - ~ | - ~ | + {1:~ }| + {1:~ }| | - ]], hl_groups={EndOfBuffer=0, MsgSeparator=2}} + ]], hl_groups={EndOfBuffer=1, MsgSeparator=2}} end) end) diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index ff9f30d0a1..958e137f65 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -24,10 +24,6 @@ describe('screen', function() } ) end) - after_each(function() - screen:detach() - end) - it('default initial screen', function() screen:expect([[ ^ | @@ -67,10 +63,6 @@ local function screen_tests(linegrid) } ) end) - after_each(function() - screen:detach() - end) - describe(':suspend', function() it('is forwarded to the UI', function() local function check() @@ -1004,3 +996,39 @@ describe('Screen default colors', function() end} end) end) + + +describe('screen with msgsep deactivated on startup', function() + local screen + + before_each(function() + clear('--cmd', 'set display-=msgsep') + screen = Screen.new() + screen:attach() + screen:set_default_attr_ids { + [0] = {bold=true, foreground=255}; + [7] = {bold = true, foreground = Screen.colors.SeaGreen}; + } + end) + + it('execute command with multi-line output', function() + feed ':ls<cr>' + screen:expect([[ + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + :ls | + 1 %a "[No Name]" line 1 | + {7:Press ENTER or type command to continue}^ | + ]]) + feed '<cr>' -- skip the "Press ENTER..." state or tests will hang + end) +end) diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index aab8eb4464..578ad982dc 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -147,8 +147,8 @@ set(MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/cpp-3.0.0 set(MSGPACK_SHA256 bfbb71b7c02f806393bc3cbc491b40523b89e64f83860c58e3e54af47de176e4) # https://github.com/LuaJIT/LuaJIT/tree/v2.1 -set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/1d8b747c161db457e032a023ebbff511f5de5ec2.tar.gz) -set(LUAJIT_SHA256 20a159c38a98ecdb6368e8d655343b6036622a29a1621da9dc303f7ed9bf37f3) +set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/787736990ac3b7d5ceaba2697c7d0f58f77bb782.tar.gz) +set(LUAJIT_SHA256 2e3f74bc279f46cc463abfc67b36e69faaf0366237004771f4cac4bf2a9f5efb) set(LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz) set(LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333) @@ -196,11 +196,11 @@ set(GETTEXT_SHA256 66415634c6e8c3fa8b71362879ec7575e27da43da562c798a8a2f223e6e47 set(LIBICONV_URL https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.15.tar.gz) set(LIBICONV_SHA256 ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178) -set(TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/99151b1.tar.gz) -set(TREESITTER_C_SHA256 950386f9ba77fb6a7e992198d4f219c34238a2bbc005c5f53c4212d0f8772b06) +set(TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/5aa0bbb.tar.gz) +set(TREESITTER_C_SHA256 a5dcb37460d83002dfae7f9a208170ddbc9a047f231b9d6b75da7d36d707db2f) -set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/0.18.0.zip) -set(TREESITTER_SHA256 ac53b7708ca47161dac7f8e852bd61accb8527d45b7ad72e29e12e8e72dbe440) +set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.19.3.zip) +set(TREESITTER_SHA256 1a2c5b816fa7f78587672a022a5f671004ac656ebad39857b3c15442c657fcb0) if(USE_BUNDLED_UNIBILIUM) include(BuildUnibilium) |