aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE/bug_report.yml2
-rw-r--r--.github/ISSUE_TEMPLATE/lsp_bug_report.yml2
-rw-r--r--.github/labeler.yml18
-rw-r--r--.github/workflows/labeler.yml3
-rw-r--r--runtime/doc/eval.txt4
-rw-r--r--runtime/doc/lsp.txt16
-rw-r--r--runtime/lua/vim/lsp.lua10
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua47
-rw-r--r--src/nvim/api/buffer.c87
-rw-r--r--src/nvim/api/deprecated.c82
-rw-r--r--src/nvim/api/private/helpers.c35
-rw-r--r--src/nvim/decoration.c26
-rw-r--r--src/nvim/decoration.h13
-rw-r--r--src/nvim/extmark.c2
-rw-r--r--src/nvim/globals.h2
-rw-r--r--src/nvim/normal.c2
-rw-r--r--src/nvim/screen.c128
-rw-r--r--src/nvim/state.c3
-rw-r--r--src/nvim/testdir/test_functions.vim10
-rw-r--r--test/functional/ui/bufhl_spec.lua22
-rw-r--r--test/functional/ui/decorations_spec.lua94
21 files changed, 373 insertions, 235 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index e5530a1511..474fce0167 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -54,7 +54,7 @@ body:
- type: textarea
attributes:
label: "Expected behavior"
+ description: "A description of the behavior you expected. May optionally include logs, images, or videos."
- type: textarea
attributes:
label: "Actual behavior"
- description: "A description of the behavior you expected. May optionally include logs, images, or videos."
diff --git a/.github/ISSUE_TEMPLATE/lsp_bug_report.yml b/.github/ISSUE_TEMPLATE/lsp_bug_report.yml
index ff25bb0a23..ba1905329b 100644
--- a/.github/ISSUE_TEMPLATE/lsp_bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/lsp_bug_report.yml
@@ -47,10 +47,10 @@ body:
- type: textarea
attributes:
label: "Expected behavior"
+ description: "A description of the behavior you expected. May optionally include logs, images, or videos."
- type: textarea
attributes:
label: "Actual behavior"
- description: "A description of the behavior you expected. May optionally include logs, images, or videos."
- type: markdown
attributes:
diff --git a/.github/labeler.yml b/.github/labeler.yml
index 282c6b1e7c..2de3904314 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -17,33 +17,33 @@
"dependencies":
- third-party/**/*
-"topic: spell":
+"spell":
- src/nvim/spell*
-"topic: :terminal":
+"terminal":
- src/nvim/terminal.*
-"topic: column":
+"column":
- src/nvim/mark.h
- src/nvim/mark.c
- src/nvim/sign*
-"topic: folds":
+"folds":
- src/nvim/fold*
-"topic: mouse":
+"mouse":
- src/nvim/mouse*
-"topic: documentation":
+"documentation":
- runtime/doc/*
-"topic: clipboard":
+"clipboard":
- runtime/autoload/provider/clipboard.vim
-"topic: diff":
+"diff":
- src/nvim/diff.*
-"topic: build":
+"build":
- CMakeLists.txt
- "**/CMakeLists.txt"
- "**/*.cmake"
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
index 909e197b57..67ad4c0552 100644
--- a/.github/workflows/labeler.yml
+++ b/.github/workflows/labeler.yml
@@ -1,6 +1,7 @@
name: "Pull Request Labeler"
on:
-- pull_request_target
+ pull_request_target:
+ types: opened
jobs:
triage:
runs-on: ubuntu-latest
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index e32691dfb4..2b6e3aff29 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -6350,6 +6350,10 @@ mode([expr]) Return a string that indicates the current mode.
s Select by character
S Select by line
CTRL-S Select blockwise
+ vs Visual by character using |v_CTRL-O| from
+ Select mode
+ Vs Visual by line using |v_CTRL-O| from Select mode
+ CTRL-Vs Visual blockwise using |v_CTRL-O| from Select mode
i Insert
ic Insert mode completion |compl-generic|
ix Insert mode |i_CTRL-X| completion
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index c750a65c10..7daab4d6f1 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -1487,6 +1487,22 @@ on_publish_diagnostics({_}, {_}, {params}, {client_id}, {_}, {config})
• Sort diagnostics (and thus signs and virtual
text)
+redraw({bufnr}, {client_id}) *vim.lsp.diagnostic.redraw()*
+ Redraw diagnostics for the given buffer and client
+
+ This calls the "textDocument/publishDiagnostics" handler
+ manually using the cached diagnostics already received from
+ the server. This can be useful for redrawing diagnostics after
+ making changes in diagnostics configuration.
+ |lsp-handler-configuration|
+
+ Parameters: ~
+ {bufnr} (optional, number): Buffer handle, defaults
+ to current
+ {client_id} (optional, number): Redraw diagnostics for
+ the given client. The default is to redraw
+ diagnostics for all attached clients.
+
reset({client_id}, {buffer_client_map}) *vim.lsp.diagnostic.reset()*
Clear diagnotics and diagnostic cache
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index f5aefc8260..87ecc3eeea 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -453,15 +453,7 @@ local function text_document_did_open_handler(bufnr, client)
-- 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
- )
+ vim.lsp.diagnostic.redraw(bufnr, client.id)
end)
end
diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua
index 246665602d..120320becc 100644
--- a/runtime/lua/vim/lsp/diagnostic.lua
+++ b/runtime/lua/vim/lsp/diagnostic.lua
@@ -1168,6 +1168,40 @@ function M.display(diagnostics, bufnr, client_id, config)
save_extmarks(bufnr, client_id)
end
+--- Redraw diagnostics for the given buffer and client
+---
+--- This calls the "textDocument/publishDiagnostics" handler manually using
+--- the cached diagnostics already received from the server. This can be useful
+--- for redrawing diagnostics after making changes in diagnostics
+--- configuration. |lsp-handler-configuration|
+---
+--- @param bufnr (optional, number): Buffer handle, defaults to current
+--- @param client_id (optional, number): Redraw diagnostics for the given
+--- client. The default is to redraw diagnostics for all attached
+--- clients.
+function M.redraw(bufnr, client_id)
+ bufnr = get_bufnr(bufnr)
+ if not client_id then
+ return vim.lsp.for_each_buffer_client(bufnr, function(client)
+ M.redraw(bufnr, client.id)
+ end)
+ end
+
+ -- We need to invoke the publishDiagnostics handler directly instead of just
+ -- calling M.display so that we can preserve any custom configuration options
+ -- the user may have set with vim.lsp.with.
+ vim.lsp.handlers["textDocument/publishDiagnostics"](
+ nil,
+ "textDocument/publishDiagnostics",
+ {
+ uri = vim.uri_from_bufnr(bufnr),
+ diagnostics = M.get(bufnr, client_id),
+ },
+ client_id,
+ bufnr
+ )
+end
+
-- }}}
-- Diagnostic User Functions {{{
@@ -1361,18 +1395,7 @@ function M.enable(bufnr, client_id)
diagnostic_disabled[bufnr][client_id] = nil
- -- We need to invoke the publishDiagnostics handler directly instead of just
- -- calling M.display so that we can preserve any custom configuration options
- -- the user may have set with vim.lsp.with.
- vim.lsp.handlers["textDocument/publishDiagnostics"](
- nil,
- "textDocument/publishDiagnostics",
- {
- diagnostics = M.get(bufnr, client_id),
- uri = vim.uri_from_bufnr(bufnr),
- },
- client_id
- )
+ M.redraw(bufnr, client_id)
end
-- }}}
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 78e36e5ef0..77cff0cb4f 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -1433,8 +1433,14 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id,
/// - hl_group : name of the highlight group used to highlight
/// this mark.
/// - virt_text : virtual text to link to this mark.
-/// - virt_text_pos : positioning of virtual text. Possible
-/// values:
+/// A list of [text, highlight] tuples, each representing a
+/// text chunk with specified highlight. `highlight` element
+/// can either be a a single highlight group, or an array of
+/// multiple highlight groups that will be stacked
+/// (highest priority last). A highlight group can be supplied
+/// either as a string or as an integer, the latter which
+/// can be obtained using |nvim_get_hl_id_by_name|.
+/// - virt_text_pos : position of virtual text. Possible values:
/// - "eol": right after eol character (default)
/// - "overlay": display over the specified column, without
/// shifting the underlying text.
@@ -1560,7 +1566,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
"virt_text is not an Array");
goto error;
}
- decor.virt_text = parse_virt_text(v->data.array, err);
+ decor.virt_text = parse_virt_text(v->data.array, err,
+ &decor.virt_text_width);
if (ERROR_SET(err)) {
goto error;
}
@@ -1892,80 +1899,6 @@ void nvim_buf_clear_namespace(Buffer buffer,
(int)line_end-1, MAXCOL);
}
-/// Set the virtual text (annotation) for a buffer line.
-///
-/// By default (and currently the only option) the text will be placed after
-/// the buffer text. Virtual text will never cause reflow, rather virtual
-/// text will be truncated at the end of the screen line. The virtual text will
-/// begin one cell (|lcs-eol| or space) after the ordinary text.
-///
-/// Namespaces are used to support batch deletion/updating of virtual text.
-/// To create a namespace, use |nvim_create_namespace()|. Virtual text is
-/// cleared using |nvim_buf_clear_namespace()|. The same `ns_id` can be used for
-/// both virtual text and highlights added by |nvim_buf_add_highlight()|, both
-/// can then be cleared with a single call to |nvim_buf_clear_namespace()|. If
-/// the virtual text never will be cleared by an API call, pass `ns_id = -1`.
-///
-/// As a shorthand, `ns_id = 0` can be used to create a new namespace for the
-/// virtual text, the allocated id is then returned.
-///
-/// @param buffer Buffer handle, or 0 for current buffer
-/// @param ns_id Namespace to use or 0 to create a namespace,
-/// or -1 for a ungrouped annotation
-/// @param line Line to annotate with virtual text (zero-indexed)
-/// @param chunks A list of [text, hl_group] arrays, each representing a
-/// text chunk with specified highlight. `hl_group` element
-/// can be omitted for no highlight.
-/// @param opts Optional parameters. Currently not used.
-/// @param[out] err Error details, if any
-/// @return The ns_id that was used
-Integer nvim_buf_set_virtual_text(Buffer buffer,
- Integer src_id,
- Integer line,
- Array chunks,
- Dictionary opts,
- Error *err)
- FUNC_API_SINCE(5)
-{
- buf_T *buf = find_buffer_by_handle(buffer, err);
- if (!buf) {
- return 0;
- }
-
- if (line < 0 || line >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Line number outside range");
- return 0;
- }
-
- if (opts.size > 0) {
- api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
- return 0;
- }
-
- uint64_t ns_id = src2ns(&src_id);
-
- VirtText virt_text = parse_virt_text(chunks, err);
- if (ERROR_SET(err)) {
- return 0;
- }
-
-
- VirtText *existing = decor_find_virttext(buf, (int)line, ns_id);
-
- if (existing) {
- clear_virttext(existing);
- *existing = virt_text;
- return src_id;
- }
-
- Decoration *decor = xcalloc(1, sizeof(*decor));
- decor->virt_text = virt_text;
-
- extmark_set(buf, ns_id, 0, (int)line, 0, -1, -1, decor, true,
- false, kExtmarkNoUndo);
- return src_id;
-}
-
/// call a function with buffer as temporary current buffer
///
/// This temporarily switches current buffer to "buffer".
diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c
index 3989386bb9..554966e266 100644
--- a/src/nvim/api/deprecated.c
+++ b/src/nvim/api/deprecated.c
@@ -12,6 +12,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/api/private/defs.h"
#include "nvim/lua/executor.h"
+#include "nvim/extmark.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/deprecated.c.generated.h"
@@ -80,6 +81,87 @@ void nvim_buf_clear_highlight(Buffer buffer,
}
+/// Set the virtual text (annotation) for a buffer line.
+///
+/// @deprecated use nvim_buf_set_extmark to use full virtual text
+/// functionality.
+///
+/// The text will be placed after the buffer text. Virtual text will never
+/// cause reflow, rather virtual text will be truncated at the end of the screen
+/// line. The virtual text will begin one cell (|lcs-eol| or space) after the
+/// ordinary text.
+///
+/// Namespaces are used to support batch deletion/updating of virtual text.
+/// To create a namespace, use |nvim_create_namespace()|. Virtual text is
+/// cleared using |nvim_buf_clear_namespace()|. The same `ns_id` can be used for
+/// both virtual text and highlights added by |nvim_buf_add_highlight()|, both
+/// can then be cleared with a single call to |nvim_buf_clear_namespace()|. If
+/// the virtual text never will be cleared by an API call, pass `ns_id = -1`.
+///
+/// As a shorthand, `ns_id = 0` can be used to create a new namespace for the
+/// virtual text, the allocated id is then returned.
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace to use or 0 to create a namespace,
+/// or -1 for a ungrouped annotation
+/// @param line Line to annotate with virtual text (zero-indexed)
+/// @param chunks A list of [text, hl_group] arrays, each representing a
+/// text chunk with specified highlight. `hl_group` element
+/// can be omitted for no highlight.
+/// @param opts Optional parameters. Currently not used.
+/// @param[out] err Error details, if any
+/// @return The ns_id that was used
+Integer nvim_buf_set_virtual_text(Buffer buffer,
+ Integer src_id,
+ Integer line,
+ Array chunks,
+ Dictionary opts,
+ Error *err)
+ FUNC_API_SINCE(5)
+ FUNC_API_DEPRECATED_SINCE(8)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return 0;
+ }
+
+ if (line < 0 || line >= MAXLNUM) {
+ api_set_error(err, kErrorTypeValidation, "Line number outside range");
+ return 0;
+ }
+
+ if (opts.size > 0) {
+ api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ return 0;
+ }
+
+ uint64_t ns_id = src2ns(&src_id);
+ int width;
+
+ VirtText virt_text = parse_virt_text(chunks, err, &width);
+ if (ERROR_SET(err)) {
+ return 0;
+ }
+
+
+ Decoration *existing = decor_find_virttext(buf, (int)line, ns_id);
+
+ if (existing) {
+ clear_virttext(&existing->virt_text);
+ existing->virt_text = virt_text;
+ existing->virt_text_width = width;
+ return src_id;
+ }
+
+ Decoration *decor = xcalloc(1, sizeof(*decor));
+ decor->virt_text = virt_text;
+ decor->virt_text_width = width;
+
+ extmark_set(buf, ns_id, 0, (int)line, 0, -1, -1, decor, true,
+ false, kExtmarkNoUndo);
+ return src_id;
+}
+
/// Inserts a sequence of lines to a buffer at a certain index
///
/// @deprecated use nvim_buf_set_lines(buffer, lnum, lnum, true, lines)
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 9f2e94f31e..5a8c5de35c 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1592,9 +1592,10 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int
}
}
-VirtText parse_virt_text(Array chunks, Error *err)
+VirtText parse_virt_text(Array chunks, Error *err, int *width)
{
VirtText virt_text = KV_INITIAL_VALUE;
+ int w = 0;
for (size_t i = 0; i < chunks.size; i++) {
if (chunks.items[i].type != kObjectTypeArray) {
api_set_error(err, kErrorTypeValidation, "Chunk is not an array");
@@ -1603,25 +1604,44 @@ VirtText parse_virt_text(Array chunks, Error *err)
Array chunk = chunks.items[i].data.array;
if (chunk.size == 0 || chunk.size > 2
|| chunk.items[0].type != kObjectTypeString
- || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) {
+ || chunk.size > 2) {
api_set_error(err, kErrorTypeValidation,
"Chunk is not an array with one or two strings");
goto free_exit;
}
String str = chunk.items[0].data.string;
- char *text = transstr(str.size > 0 ? str.data : ""); // allocates
int hl_id = 0;
if (chunk.size == 2) {
- String hl = chunk.items[1].data.string;
- if (hl.size > 0) {
- hl_id = syn_check_group((char_u *)hl.data, (int)hl.size);
+ Object hl = chunk.items[1];
+ if (hl.type == kObjectTypeArray) {
+ Array arr = hl.data.array;
+ for (size_t j = 0; j < arr.size; j++) {
+ hl_id = object_to_hl_id(arr.items[j], "virt_text highlight", err);
+ if (ERROR_SET(err)) {
+ goto free_exit;
+ }
+ if (j < arr.size-1) {
+ kv_push(virt_text, ((VirtTextChunk){ .text = NULL,
+ .hl_id = hl_id }));
+ }
+ }
+ } else {
+ hl_id = object_to_hl_id(hl, "virt_text highlight", err);
+ if (ERROR_SET(err)) {
+ goto free_exit;
+ }
}
}
+
+ char *text = transstr(str.size > 0 ? str.data : ""); // allocates
+ w += (int)mb_string2cells((char_u *)text);
+
kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
}
+ *width = w;
return virt_text;
free_exit:
@@ -1656,7 +1676,7 @@ int object_to_hl_id(Object obj, const char *what, Error *err)
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;
+ return MAX((int)obj.data.integer, 0);
} else {
api_set_error(err, kErrorTypeValidation,
"%s is not a valid highlight", what);
@@ -1687,6 +1707,7 @@ HlMessage parse_hl_msg(Array chunks, Error *err)
if (chunk.size == 2) {
String hl = chunk.items[1].data.string;
if (hl.size > 0) {
+ // TODO(bfredl): use object_to_hl_id and allow integer
int hl_id = syn_check_group((char_u *)hl.data, (int)hl.size);
attr = hl_id > 0 ? syn_id2attr(hl_id) : 0;
}
diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c
index 9d969ada89..74cb9a26b7 100644
--- a/src/nvim/decoration.c
+++ b/src/nvim/decoration.c
@@ -119,7 +119,7 @@ void clear_virttext(VirtText *text)
*text = (VirtText)KV_INITIAL_VALUE;
}
-VirtText *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id)
+Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id)
{
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, row, 0, itr);
@@ -132,7 +132,7 @@ VirtText *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id)
mark.id, false);
if (item && (ns_id == 0 || ns_id == item->ns_id)
&& item->decor && kv_size(item->decor->virt_text)) {
- return &item->decor->virt_text;
+ return item->decor;
}
marktree_itr_next(buf->b_marktree, itr);
}
@@ -218,6 +218,7 @@ bool decor_redraw_line(buf_T *buf, int row, DecorState *state)
}
state->row = row;
state->col_until = -1;
+ state->eol_col = -1;
return true; // TODO(bfredl): be more precise
}
@@ -230,10 +231,6 @@ static void decor_add(DecorState *state, int start_row, int start_col,
*decor, attr_id,
kv_size(decor->virt_text) && owned, -1 };
- if (decor->virt_text_pos == kVTEndOfLine) {
- range.win_col = -2; // handled separately
- }
-
kv_pushp(state->active);
size_t index;
for (index = kv_size(state->active)-1; index > 0; index--) {
@@ -345,29 +342,22 @@ void decor_redraw_end(DecorState *state)
state->buf = NULL;
}
-VirtText decor_redraw_eol(buf_T *buf, DecorState *state, int *eol_attr,
- bool *aligned)
+bool decor_redraw_eol(buf_T *buf, DecorState *state, int *eol_attr, int eol_col)
{
decor_redraw_col(buf, MAXCOL, MAXCOL, false, state);
- VirtText text = VIRTTEXT_EMPTY;
+ state->eol_col = eol_col;
+ bool has_virttext = false;
for (size_t i = 0; i < kv_size(state->active); i++) {
DecorRange item = kv_A(state->active, i);
if (item.start_row == state->row && kv_size(item.decor.virt_text)) {
- if (!kv_size(text) && item.decor.virt_text_pos == kVTEndOfLine) {
- text = item.decor.virt_text;
- } else if (item.decor.virt_text_pos == kVTRightAlign
- || item.decor.virt_text_pos == kVTWinCol) {
- *aligned = true;
- }
+ has_virttext = true;
}
-
if (item.decor.hl_eol && item.start_row <= state->row) {
*eol_attr = hl_combine_attr(*eol_attr, item.attr_id);
}
}
-
- return text;
+ return has_virttext;
}
void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col,
diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h
index 4cebc0b731..28dabeeada 100644
--- a/src/nvim/decoration.h
+++ b/src/nvim/decoration.h
@@ -34,19 +34,20 @@ typedef enum {
struct Decoration
{
- int hl_id; // highlight group
VirtText virt_text;
+ int hl_id; // highlight group
VirtTextPos virt_text_pos;
- bool virt_text_hide;
HlMode hl_mode;
+ bool virt_text_hide;
bool hl_eol;
+ bool shared; // shared decoration, don't free
// TODO(bfredl): style, signs, etc
DecorPriority priority;
- bool shared; // shared decoration, don't free
int col; // fixed col value, like win_col
+ int virt_text_width; // width of virt_text
};
-#define DECORATION_INIT { 0, KV_INITIAL_VALUE, kVTEndOfLine, false, \
- kHlModeUnknown, false, DECOR_PRIORITY_BASE, false, 0 }
+#define DECORATION_INIT { KV_INITIAL_VALUE, 0, kVTEndOfLine, kHlModeUnknown, \
+ false, false, false, DECOR_PRIORITY_BASE, 0, 0 }
typedef struct {
int start_row;
@@ -67,6 +68,8 @@ typedef struct {
int row;
int col_until;
int current;
+
+ int eol_col;
VirtText *virt_text;
} DecorState;
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
index 60b7b024f1..4b2dccd8a4 100644
--- a/src/nvim/extmark.c
+++ b/src/nvim/extmark.c
@@ -268,7 +268,7 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id,
}
ExtmarkNs *my_ns = all_ns ? buf_ns_ref(buf, item.ns_id, false) : ns;
map_del(uint64_t, uint64_t)(my_ns->map, item.mark_id);
- map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark.id);
+ map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, start_id);
marktree_del_itr(buf->b_marktree, itr, false);
} else {
marktree_itr_next(buf->b_marktree, itr);
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 55a912b417..7b2320effa 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -524,6 +524,8 @@ EXTERN pos_T VIsual;
EXTERN int VIsual_active INIT(= false);
/// Whether Select mode is active.
EXTERN int VIsual_select INIT(= false);
+/// Restart Select mode when next cmd finished
+EXTERN int restart_VIsual_select INIT(= 0);
/// Whether to restart the selection after a Select-mode mapping or menu.
EXTERN int VIsual_reselect;
/// Type of Visual mode.
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 4213e6f946..9185062f94 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -92,8 +92,6 @@ static linenr_T resel_VIsual_line_count; /* number of lines */
static colnr_T resel_VIsual_vcol; /* nr of cols or end col */
static int VIsual_mode_orig = NUL; /* saved Visual mode */
-static int restart_VIsual_select = 0;
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "normal.c.generated.h"
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index ae785132bb..d8f5f1077c 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -165,7 +165,7 @@ static bool resizing = false;
#endif
#define SEARCH_HL_PRIORITY 0
-static char * provider_first_error = NULL;
+static char * provider_err = NULL;
static bool provider_invoke(NS ns_id, const char *name, LuaRef ref,
Array args, bool default_true)
@@ -187,10 +187,10 @@ static bool provider_invoke(NS ns_id, const char *name, LuaRef ref,
const char *ns_name = describe_ns(ns_id);
ELOG("error in provider %s:%s: %s", ns_name, name, err.msg);
bool verbose_errs = true; // TODO(bfredl):
- if (verbose_errs && provider_first_error == NULL) {
+ if (verbose_errs && provider_err == NULL) {
static char errbuf[IOSIZE];
snprintf(errbuf, sizeof errbuf, "%s: %s", ns_name, err.msg);
- provider_first_error = xstrdup(errbuf);
+ provider_err = xstrdup(errbuf);
}
}
@@ -2103,7 +2103,6 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
bool search_attr_from_match = false; // if search_attr is from :match
bool has_decor = false; // this buffer has decoration
- bool do_virttext = false; // draw virtual text for this line
int win_col_offset = 0; // offset for window columns
char_u buf_fold[FOLD_TEXT_LEN + 1]; // Hold value returned by get_foldtext
@@ -2148,8 +2147,6 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
row = startrow;
- char *err_text = NULL;
-
buf_T *buf = wp->w_buffer;
if (!number_only) {
@@ -2194,14 +2191,20 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
}
}
- if (has_decor) {
- extra_check = true;
+ if (provider_err) {
+ Decoration err_decor = DECORATION_INIT;
+ int hl_err = syn_check_group((char_u *)S_LEN("ErrorMsg"));
+ kv_push(err_decor.virt_text,
+ ((VirtTextChunk){ .text = provider_err,
+ .hl_id = hl_err }));
+ err_decor.virt_text_width = mb_string2cells((char_u *)provider_err);
+ decor_add_ephemeral(lnum-1, 0, lnum-1, 0, &err_decor);
+ provider_err = NULL;
+ has_decor = true;
}
- if (provider_first_error) {
- err_text = provider_first_error;
- provider_first_error = NULL;
- do_virttext = true;
+ if (has_decor) {
+ extra_check = true;
}
// Check for columns to display for 'colorcolumn'.
@@ -3953,19 +3956,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
if (draw_color_col)
draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
- VirtText virt_text = KV_INITIAL_VALUE;
- bool has_aligned = false;
- if (err_text) {
- int hl_err = syn_check_group((char_u *)S_LEN("ErrorMsg"));
- kv_push(virt_text, ((VirtTextChunk){ .text = err_text,
- .hl_id = hl_err }));
- do_virttext = true;
- } else if (has_decor) {
- virt_text = decor_redraw_eol(wp->w_buffer, &decor_state, &line_attr,
- &has_aligned);
- if (kv_size(virt_text)) {
- do_virttext = true;
- }
+ bool has_virttext = false;
+ // Make sure alignment is the same regardless
+ // if listchars=eol:X is used or not.
+ int eol_skip = (wp->w_p_lcs_chars.eol == lcs_eol_one && eol_hl_off == 0
+ ? 1 : 0);
+
+ if (has_decor) {
+ has_virttext = decor_redraw_eol(wp->w_buffer, &decor_state, &line_attr,
+ col + eol_skip);
}
if (((wp->w_p_cuc
@@ -3974,20 +3973,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
grid->Columns * (row - startrow + 1) + v
&& lnum != wp->w_cursor.lnum)
|| draw_color_col || line_attr_lowprio || line_attr
- || diff_hlf != (hlf_T)0 || do_virttext
- || has_aligned)) {
+ || diff_hlf != (hlf_T)0 || has_virttext)) {
int rightmost_vcol = 0;
int i;
- size_t virt_pos = 0;
- LineState s = LINE_STATE("");
- int virt_attr = 0;
-
- // Make sure alignment is the same regardless
- // if listchars=eol:X is used or not.
- bool delay_virttext = wp->w_p_lcs_chars.eol == lcs_eol_one
- && eol_hl_off == 0;
-
if (wp->w_p_cuc) {
rightmost_vcol = wp->w_virtcol;
}
@@ -4013,37 +4002,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
}
int base_attr = hl_combine_attr(line_attr_lowprio, diff_attr);
- if (base_attr || line_attr || has_aligned) {
+ if (base_attr || line_attr || has_virttext) {
rightmost_vcol = INT_MAX;
}
int col_stride = wp->w_p_rl ? -1 : 1;
while (wp->w_p_rl ? col >= 0 : col < grid->Columns) {
- int cells = -1;
- if (do_virttext && !delay_virttext) {
- if (*s.p == NUL) {
- if (virt_pos < virt_text.size) {
- s.p = kv_A(virt_text, virt_pos).text;
- int hl_id = kv_A(virt_text, virt_pos).hl_id;
- virt_attr = hl_id > 0 ? syn_id2attr(hl_id) : 0;
- virt_pos++;
- } else {
- do_virttext = false;
- }
- }
- if (*s.p != NUL) {
- cells = line_putchar(&s, &linebuf_char[off], grid->Columns - col,
- false);
- }
- }
- delay_virttext = false;
-
- if (cells == -1) {
- schar_from_ascii(linebuf_char[off], ' ');
- cells = 1;
- }
- col += cells * col_stride;
+ schar_from_ascii(linebuf_char[off], ' ');
+ col += col_stride;
if (draw_color_col) {
draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
}
@@ -4056,24 +4023,16 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
col_attr = mc_attr;
}
- if (do_virttext) {
- col_attr = hl_combine_attr(col_attr, virt_attr);
- }
-
col_attr = hl_combine_attr(col_attr, line_attr);
linebuf_attr[off] = col_attr;
- if (cells == 2) {
- linebuf_attr[off+1] = col_attr;
- }
- off += cells * col_stride;
+ off += col_stride;
- if (VCOL_HLC >= rightmost_vcol && *s.p == NUL
- && virt_pos >= virt_text.size) {
+ if (VCOL_HLC >= rightmost_vcol) {
break;
}
- vcol += cells;
+ vcol += 1;
}
}
@@ -4392,7 +4351,6 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
}
xfree(p_extra_free);
- xfree(err_text);
return row;
}
@@ -4400,13 +4358,17 @@ void draw_virt_text(buf_T *buf, int col_off, int *end_col, int max_col)
{
DecorState *state = &decor_state;
int right_pos = max_col;
+ bool do_eol = state->eol_col > -1;
for (size_t i = 0; i < kv_size(state->active); i++) {
DecorRange *item = &kv_A(state->active, i);
if (item->start_row == state->row && kv_size(item->decor.virt_text)) {
if (item->win_col == -1) {
if (item->decor.virt_text_pos == kVTRightAlign) {
- right_pos -= item->decor.col;
+ right_pos -= item->decor.virt_text_width;
item->win_col = right_pos;
+ } else if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) {
+ item->win_col = state->eol_col;
+ state->eol_col += item->decor.virt_text_width;
} else if (item->decor.virt_text_pos == kVTWinCol) {
item->win_col = MAX(item->decor.col+col_off, 0);
}
@@ -4424,14 +4386,20 @@ void draw_virt_text(buf_T *buf, int col_off, int *end_col, int max_col)
while (col < max_col) {
if (!*s.p) {
- if (virt_pos == kv_size(vt)) {
+ if (virt_pos >= kv_size(vt)) {
+ break;
+ }
+ virt_attr = 0;
+ do {
+ s.p = kv_A(vt, virt_pos).text;
+ int hl_id = kv_A(vt, virt_pos).hl_id;
+ virt_attr = hl_combine_attr(virt_attr,
+ hl_id > 0 ? syn_id2attr(hl_id) : 0);
+ virt_pos++;
+ } while (!s.p && virt_pos < kv_size(vt));
+ if (!s.p) {
break;
}
- s.p = kv_A(vt, virt_pos).text;
- int hl_id = kv_A(vt, virt_pos).hl_id;
- virt_attr = hl_id > 0 ? syn_id2attr(hl_id) : 0;
- virt_pos++;
- continue;
}
int attr;
bool through = false;
diff --git a/src/nvim/state.c b/src/nvim/state.c
index 437cb0db47..02d63d8ab1 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -144,6 +144,9 @@ char *get_mode(void)
buf[0] = (char)(VIsual_mode + 's' - 'v');
} else {
buf[0] = (char)VIsual_mode;
+ if (restart_VIsual_select) {
+ buf[1] = 's';
+ }
}
} else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE
|| State == CONFIRM) {
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index 224ca257ab..7c00b479a3 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -534,6 +534,7 @@ func Test_mode()
set complete=.
inoremap <F2> <C-R>=Save_mode()<CR>
+ xnoremap <F2> <Cmd>call Save_mode()<CR>
normal! 3G
exe "normal i\<F2>\<Esc>"
@@ -645,6 +646,14 @@ func Test_mode()
call assert_equal("\<C-S>", mode(1))
call feedkeys("\<Esc>", 'xt')
+ " v_CTRL-O
+ exe "normal gh\<C-O>\<F2>\<Esc>"
+ call assert_equal("v-vs", g:current_modes)
+ exe "normal gH\<C-O>\<F2>\<Esc>"
+ call assert_equal("V-Vs", g:current_modes)
+ exe "normal g\<C-H>\<C-O>\<F2>\<Esc>"
+ call assert_equal("\<C-V>-\<C-V>s", g:current_modes)
+
call feedkeys(":echo \<C-R>=Save_mode()\<C-U>\<CR>", 'xt')
call assert_equal('c-c', g:current_modes)
call feedkeys("gQecho \<C-R>=Save_mode()\<CR>\<CR>vi\<CR>", 'xt')
@@ -653,6 +662,7 @@ func Test_mode()
bwipe!
iunmap <F2>
+ xunmap <F2>
set complete&
endfunc
diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua
index af709cd521..16ed3b9486 100644
--- a/test/functional/ui/bufhl_spec.lua
+++ b/test/functional/ui/bufhl_spec.lua
@@ -858,8 +858,8 @@ describe('Buffer highlighting', function()
it('works with cursorline', function()
command("set cursorline")
- screen:expect([[
- {14:^1 + 2 }{15:=}{16: 3}{14: }|
+ screen:expect{grid=[[
+ {14:^1 + 2 }{3:=}{2: 3}{14: }|
3 + {11:ERROR:} invalid syntax |
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
, 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s|
@@ -867,32 +867,32 @@ describe('Buffer highlighting', function()
{1:~ }|
{1:~ }|
|
- ]])
+ ]]}
feed('j')
- screen:expect([[
+ screen:expect{grid=[[
1 + 2 {3:=}{2: 3} |
- {14:^3 + }{11:ERROR:}{14: invalid syntax }|
+ {14:^3 + }{11:ERROR:} invalid syntax{14: }|
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
, 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s|
x = 4 |
{1:~ }|
{1:~ }|
|
- ]])
+ ]]}
feed('j')
- screen:expect([[
+ screen:expect{grid=[[
1 + 2 {3:=}{2: 3} |
3 + {11:ERROR:} invalid syntax |
{14:^5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5}|
- {14:, 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s}|
+ {14:, 5, 5, 5, 5, 5, 5, }Lorem ipsum dolor s|
x = 4 |
{1:~ }|
{1:~ }|
|
- ]])
+ ]]}
end)
it('works with color column', function()
@@ -910,11 +910,11 @@ describe('Buffer highlighting', function()
command("set colorcolumn=9")
screen:expect{grid=[[
- ^1 + 2 {3:=}{2: }{17:3} |
+ ^1 + 2 {3:=}{2: 3} |
3 + {11:ERROR:} invalid syntax |
5, 5, 5,{18: }5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
, 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s|
- x = 4 {12:暗}{19:x}{12:事} |
+ x = 4 {12:暗x事} |
{1:~ }|
{1:~ }|
|
diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua
index 98aafd8757..4373d17890 100644
--- a/test/functional/ui/decorations_spec.lua
+++ b/test/functional/ui/decorations_spec.lua
@@ -562,7 +562,7 @@ end]]
{5:l}{8:blen}{7:dy}{10:e}{7:text}{10:h}{7:-}{10:_}{7:here}ell, count = unpack(item) |
{5:i}{12:c}{11:ombining color} {13:nil} {5:then} |
{11:replacing color}d_cell |
- {5:e}{8:bl}{14:endy}{15:i}{14:text}{15:o}{14:-}{15:o}{14:h}{7:ere} |
+ {5:e}{8:bl}{7:endy}{10: }{7:text}{10: }{7:-}{10: }{7:here} |
{5:f}{12:co}{11:mbini}{16:n}{11:g color}t {5:or} {13:1}) {5:do} |
{11:replacing color} line[colpos] |
cell.text = text |
@@ -697,4 +697,96 @@ end]]
|
]]}
end)
+
+ it('can have virtual text which combines foreground and backround groups', function()
+ screen:set_default_attr_ids {
+ [1] = {bold=true, foreground=Screen.colors.Blue};
+ [2] = {background = tonumber('0x123456'), foreground = tonumber('0xbbbbbb')};
+ [3] = {background = tonumber('0x123456'), foreground = tonumber('0xcccccc')};
+ [4] = {background = tonumber('0x234567'), foreground = tonumber('0xbbbbbb')};
+ [5] = {background = tonumber('0x234567'), foreground = tonumber('0xcccccc')};
+ [6] = {bold = true, foreground = tonumber('0xcccccc'), background = tonumber('0x234567')};
+ }
+
+ exec [[
+ hi BgOne guibg=#123456
+ hi BgTwo guibg=#234567
+ hi FgEin guifg=#bbbbbb
+ hi FgZwei guifg=#cccccc
+ hi VeryBold gui=bold
+ ]]
+
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text={
+ {'a', {'BgOne', 'FgEin'}};
+ {'b', {'BgOne', 'FgZwei'}};
+ {'c', {'BgTwo', 'FgEin'}};
+ {'d', {'BgTwo', 'FgZwei'}};
+ {'X', {'BgTwo', 'FgZwei', 'VeryBold'}};
+ }})
+
+ screen:expect{grid=[[
+ ^ {2:a}{3:b}{4:c}{5:d}{6:X} |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('does not crash when deleting a cleared buffer #15212', function()
+ exec_lua [[
+ ns = vim.api.nvim_create_namespace("myplugin")
+ vim.api.nvim_buf_set_extmark(0, ns, 0, 0, {virt_text = {{"a"}}, end_col = 0})
+ ]]
+ screen:expect{grid=[[
+ ^ a |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ exec_lua [[
+ vim.api.nvim_buf_clear_namespace(0, ns, 0, -1)
+ vim.cmd("bdelete")
+ ]]
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ helpers.assert_alive()
+ end)
end)