aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/nightly.yaml49
-rw-r--r--runtime/doc/eval.txt21
-rw-r--r--runtime/doc/lsp.txt14
-rw-r--r--runtime/doc/syntax.txt1
-rw-r--r--runtime/doc/tagsrch.txt27
-rw-r--r--runtime/filetype.vim6
-rw-r--r--runtime/lua/vim/lsp.lua13
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua6
-rw-r--r--runtime/lua/vim/lsp/util.lua20
-rw-r--r--src/nvim/api/private/helpers.c352
-rw-r--r--src/nvim/api/vim.c23
-rw-r--r--src/nvim/autocmd.c8
-rw-r--r--src/nvim/buffer_defs.h13
-rw-r--r--src/nvim/decoration.c22
-rw-r--r--src/nvim/decoration.h2
-rw-r--r--src/nvim/edit.c11
-rw-r--r--src/nvim/eval.c2
-rw-r--r--src/nvim/eval.lua1
-rw-r--r--src/nvim/eval/funcs.c103
-rw-r--r--src/nvim/grid_defs.h17
-rw-r--r--src/nvim/highlight.c15
-rw-r--r--src/nvim/highlight_defs.h2
-rw-r--r--src/nvim/mbyte.c3
-rw-r--r--src/nvim/message.c2
-rw-r--r--src/nvim/mouse.c14
-rw-r--r--src/nvim/option.c4
-rw-r--r--src/nvim/popupmnu.c2
-rw-r--r--src/nvim/screen.c150
-rw-r--r--src/nvim/syntax.c58
-rw-r--r--src/nvim/tag.c19
-rw-r--r--src/nvim/testdir/script_util.vim69
-rw-r--r--src/nvim/testdir/test_filetype.vim2
-rw-r--r--src/nvim/testdir/test_highlight.vim100
-rw-r--r--src/nvim/testdir/test_ins_complete.vim28
-rw-r--r--src/nvim/testdir/test_options.vim2
-rw-r--r--src/nvim/testdir/test_tagfunc.vim38
-rw-r--r--src/nvim/testdir/test_tagjump.vim289
-rw-r--r--src/nvim/testdir/test_taglist.vim96
-rw-r--r--src/nvim/testdir/test_window_cmd.vim66
-rw-r--r--src/nvim/ui.c1
-rw-r--r--src/nvim/window.c328
-rw-r--r--test/functional/lua/buffer_updates_spec.lua64
-rw-r--r--test/functional/ui/cursor_spec.lua4
-rw-r--r--test/functional/ui/decorations_spec.lua29
-rw-r--r--test/functional/ui/diff_spec.lua20
-rw-r--r--test/functional/ui/float_spec.lua183
-rw-r--r--test/functional/ui/highlight_spec.lua6
-rw-r--r--test/functional/ui/screen_basic_spec.lua44
48 files changed, 1916 insertions, 433 deletions
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/runtime/doc/eval.txt b/runtime/doc/eval.txt
index be7c026f5a..1832e2443f 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -2498,6 +2498,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}
@@ -9370,6 +9372,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 061a851157..67a10c7efb 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -1586,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.
@@ -1793,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: ~
@@ -1829,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/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/filetype.vim b/runtime/filetype.vim
index 4130db2534..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
@@ -1397,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 f0845bfc80..4c453df3f6 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -293,6 +293,19 @@ local function text_document_did_open_handler(bufnr, client)
}
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
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/util.lua b/runtime/lua/vim/lsp/util.lua
index 6945d0f1ec..a75ab37508 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -518,13 +518,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
@@ -911,8 +911,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
@@ -1201,7 +1201,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
@@ -1214,10 +1214,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 {}
@@ -1230,12 +1230,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/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 6358f35d0a..9dde62f0ee 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -1416,6 +1416,25 @@ void nvim_chan_send(Integer chan, String data, Error *err)
/// 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
@@ -2831,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 2ddb925d40..f71075ae74 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -1192,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;
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/decoration.c b/src/nvim/decoration.c
index 9a20b06660..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,7 +228,7 @@ 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,
decor->virt_text_pos, decor->virt_text_hide, decor->hl_mode,
kv_size(decor->virt_text) && owned, -1 };
@@ -304,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 {
@@ -324,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) {
+ && 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;
@@ -344,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, 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 264e8a4a82..c5424a1642 100644
--- a/src/nvim/decoration.h
+++ b/src/nvim/decoration.h
@@ -53,7 +53,7 @@ typedef struct {
// 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;
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 f190ef14c4..e1fcbdce25 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -8501,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..e94d7831b0 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -392,6 +392,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 2b04469af7..01d23654de 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -3980,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)
{
@@ -8728,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));
}
}
@@ -11297,17 +11376,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/grid_defs.h b/src/nvim/grid_defs.h
index 752be4f5a8..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;
@@ -89,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, 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/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 9dc85797b4..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;
}
@@ -729,7 +729,7 @@ int mouse_check_fold(void)
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);
int row = multigrid && mouse_grid == 0 ? click_row : mouse_row;
int col = multigrid && mouse_grid == 0 ? click_col : mouse_col;
diff --git a/src/nvim/option.c b/src/nvim/option.c
index dbd8ceb55c..d04329e104 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -4287,7 +4287,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;
}
@@ -5800,7 +5800,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;
}
diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c
index aef7ffa397..69c614fff9 100644
--- a/src/nvim/popupmnu.c
+++ b/src/nvim/popupmnu.c
@@ -139,7 +139,7 @@ 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;
+ pum_anchor_grid = (int)curwin->w_grid.target->handle;
if (!ui_has(kUIMultigrid)) {
pum_anchor_grid = (int)default_grid.handle;
pum_win_row += curwin->w_winrow;
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 467cac4f27..095c020fe4 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;
@@ -2753,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
@@ -3916,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;
}
}
@@ -4348,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;
@@ -4404,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;
}
}
@@ -5415,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.
@@ -5552,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,
@@ -6143,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;
@@ -6157,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);
}
}
@@ -7531,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/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_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 76e5de7df9..44b8479621 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -521,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_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_options.vim b/src/nvim/testdir/test_options.vim
index 79481097ec..07e2481f95 100644
--- a/src/nvim/testdir/test_options.vim
+++ b/src/nvim/testdir/test_options.vim
@@ -225,7 +225,7 @@ 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', @:)
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_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/window.c b/src/nvim/window.c
index aa8d8727e7..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)
@@ -713,7 +723,7 @@ int win_fdccol_count(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 @@ 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 @@ 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));
}
}
@@ -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/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua
index 7a6b5be8bc..8a25f4dc2d 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
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 7f4ab3ee5d..295a54aec8 100644
--- a/test/functional/ui/decorations_spec.lua
+++ b/test/functional/ui/decorations_spec.lua
@@ -302,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()
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 3ad14e749e..e4824521b0 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,185 @@ 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('can have minimum size', function()
insert("the background text")
local buf = meths.create_buf(false, true)
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)