aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Vigouroux <thomas.vigouroux@protonmail.com>2022-07-18 14:21:40 +0200
committerLewis Russell <lewis6991@gmail.com>2022-09-06 10:14:11 +0100
commit75adfefc85bcf0d62d2c0f51a951e6003b595cea (patch)
tree98e20f97a5c5ce818ade4f330bca45ca1d4d26bf
parent05893aea391d91e159a41f03acc20049c7049510 (diff)
downloadrneovim-75adfefc85bcf0d62d2c0f51a951e6003b595cea.tar.gz
rneovim-75adfefc85bcf0d62d2c0f51a951e6003b595cea.tar.bz2
rneovim-75adfefc85bcf0d62d2c0f51a951e6003b595cea.zip
feat(extmarks,ts,spell): full support for spelling
- Added 'spell' option to extmarks: Extmarks with this set will have the region spellchecked. - Added 'noplainbuffer' option to 'spelloptions': This is used to tell Neovim not to spellcheck the buffer. The old behaviour was to spell check the whole buffer unless :syntax was set. - Added spelling support to the treesitter highlighter: @spell captures in highlights.scm are used to define regions which should be spell checked. - Added support for navigating spell errors for extmarks: Works for both ephemeral and static extmarks - Added '_on_spell_nav' callback for decoration providers: Since ephemeral callbacks are only drawn for the visible screen, providers must implement this callback to instruct Neovim which regions in the buffer need can be spell checked. The callback takes a start position and an end position. Note: this callback is subject to change hence the _ prefix. - Added spell captures for built-in support languages Co-authored-by: Lewis Russell <lewis6991@gmail.com> Co-authored-by: Björn Linse <bjorn.linse@gmail.com>
-rw-r--r--runtime/doc/api.txt6
-rw-r--r--runtime/doc/options.txt6
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua25
-rw-r--r--runtime/queries/c/highlights.scm2
-rw-r--r--runtime/queries/lua/highlights.scm2
-rw-r--r--runtime/queries/vim/highlights.scm2
-rw-r--r--src/nvim/api/extmark.c65
-rw-r--r--src/nvim/api/keysets.lua10
-rw-r--r--src/nvim/buffer_defs.h3
-rw-r--r--src/nvim/decoration.c12
-rw-r--r--src/nvim/decoration.h7
-rw-r--r--src/nvim/decoration_provider.c33
-rw-r--r--src/nvim/decoration_provider.h1
-rw-r--r--src/nvim/drawline.c53
-rw-r--r--src/nvim/drawscreen.c2
-rw-r--r--src/nvim/extmark.c3
-rw-r--r--src/nvim/option_defs.h1
-rw-r--r--src/nvim/options.lua1
-rw-r--r--src/nvim/optionstr.c4
-rw-r--r--src/nvim/spell.c84
-rw-r--r--test/functional/ui/decorations_spec.lua86
21 files changed, 313 insertions, 95 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index ea562e60b6..00a3759936 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -2646,6 +2646,8 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {*opts})
When a character is supplied it is used as |:syn-cchar|.
"hl_group" is used as highlight for the cchar if provided,
otherwise it defaults to |hl-Conceal|.
+ • spell: boolean indicating that spell checking should be
+ performed within this extmark
• ui_watched: boolean that indicates the mark should be
drawn by a UI. When set, the UI will receive win_extmark
events. Note: the mark is positioned by virt_text
@@ -2677,7 +2679,7 @@ nvim_get_namespaces() *nvim_get_namespaces()*
dict that maps from names to namespace ids.
*nvim_set_decoration_provider()*
-nvim_set_decoration_provider({ns_id}, {opts})
+nvim_set_decoration_provider({ns_id}, {*opts})
Set or change decoration provider for a namespace
This is a very general purpose interface for having lua callbacks being
@@ -2709,7 +2711,7 @@ nvim_set_decoration_provider({ns_id}, {opts})
Parameters: ~
{ns_id} Namespace id from |nvim_create_namespace()|
- {opts} Callbacks invoked during redraw:
+ {opts} Table of callbacks:
• on_start: called first on each screen redraw ["start",
tick]
• on_buf: called for each buffer being redrawn (before window
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 316b7ae3f1..b84a41eabc 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -5871,10 +5871,14 @@ A jump table for the options with a short description can be found at |Q_op|.
'spelloptions' 'spo' string (default "")
local to buffer
A comma-separated list of options for spell checking:
- camel When a word is CamelCased, assume "Cased" is a
+ camel When a word is CamelCased, assume "Cased" is a
separate word: every upper-case character in a word
that comes after a lower case character indicates the
start of a new word.
+ noplainbuffer Only spellcheck a buffer when 'syntax' is enabled, or
+ or when extmarks are set within the buffer. Only
+ designated regions of the buffer are spellchecked in
+ this case.
*'spellsuggest'* *'sps'*
'spellsuggest' 'sps' string (default "best")
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index 1f242b0fdd..9e95af98db 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -97,6 +97,7 @@ function TSHighlighter.new(tree, opts)
if vim.g.syntax_on ~= 1 then
vim.api.nvim_command('runtime! syntax/synload.vim')
end
+ vim.bo[self.bufnr].spelloptions = 'noplainbuffer'
self.tree:parse()
@@ -156,7 +157,7 @@ function TSHighlighter:get_query(lang)
end
---@private
-local function on_line_impl(self, buf, line)
+local function on_line_impl(self, buf, line, spell)
self.tree:for_each_tree(function(tstree, tree)
if not tstree then
return
@@ -193,7 +194,9 @@ local function on_line_impl(self, buf, line)
local start_row, start_col, end_row, end_col = node:range()
local hl = highlighter_query.hl_cache[capture]
- if hl and end_row >= line then
+ local is_spell = highlighter_query:query().captures[capture] == 'spell'
+
+ if hl and end_row >= line and (not spell or is_spell) then
a.nvim_buf_set_extmark(buf, ns, start_row, start_col, {
end_line = end_row,
end_col = end_col,
@@ -201,6 +204,7 @@ local function on_line_impl(self, buf, line)
ephemeral = true,
priority = tonumber(metadata.priority) or 100, -- Low but leaves room below
conceal = metadata.conceal,
+ spell = is_spell,
})
end
if start_row > line then
@@ -217,7 +221,21 @@ function TSHighlighter._on_line(_, _win, buf, line, _)
return
end
- on_line_impl(self, buf, line)
+ on_line_impl(self, buf, line, false)
+end
+
+---@private
+function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _)
+ local self = TSHighlighter.active[buf]
+ if not self then
+ return
+ end
+
+ self:reset_highlight_state()
+
+ for row = srow, erow do
+ on_line_impl(self, buf, row, true)
+ end
end
---@private
@@ -244,6 +262,7 @@ a.nvim_set_decoration_provider(ns, {
on_buf = TSHighlighter._on_buf,
on_win = TSHighlighter._on_win,
on_line = TSHighlighter._on_line,
+ _on_spell_nav = TSHighlighter._on_spell_nav,
})
return TSHighlighter
diff --git a/runtime/queries/c/highlights.scm b/runtime/queries/c/highlights.scm
index 260750a85b..33e6df74ab 100644
--- a/runtime/queries/c/highlights.scm
+++ b/runtime/queries/c/highlights.scm
@@ -101,6 +101,7 @@
[ "(" ")" "[" "]" "{" "}"] @punctuation.bracket
(string_literal) @string
+(string_literal) @spell
(system_lib_string) @string
(null) @constant.builtin
@@ -148,6 +149,7 @@
(comment) @comment
+(comment) @spell
;; Parameters
(parameter_declaration
diff --git a/runtime/queries/lua/highlights.scm b/runtime/queries/lua/highlights.scm
index 92baba0f39..054d787932 100644
--- a/runtime/queries/lua/highlights.scm
+++ b/runtime/queries/lua/highlights.scm
@@ -181,12 +181,14 @@
;; Others
(comment) @comment
+(comment) @spell
(hash_bang_line) @comment
(number) @number
(string) @string
+(string) @spell
;; Error
(ERROR) @error
diff --git a/runtime/queries/vim/highlights.scm b/runtime/queries/vim/highlights.scm
index c02e226b66..1bbaebef01 100644
--- a/runtime/queries/vim/highlights.scm
+++ b/runtime/queries/vim/highlights.scm
@@ -162,9 +162,11 @@
;; Literals
(string_literal) @string
+(string_literal) @spell
(integer_literal) @number
(float_literal) @float
(comment) @comment
+(comment) @spell
(pattern) @string.special
(pattern_multi) @string.regex
(filename) @string
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index 6ff0a2ed21..ae051d8cab 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -473,6 +473,8 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// When a character is supplied it is used as |:syn-cchar|.
/// "hl_group" is used as highlight for the cchar if provided,
/// otherwise it defaults to |hl-Conceal|.
+/// - spell: boolean indicating that spell checking should be
+/// performed within this extmark
/// - ui_watched: boolean that indicates the mark should be drawn
/// by a UI. When set, the UI will receive win_extmark events.
/// Note: the mark is positioned by virt_text attributes. Can be
@@ -719,6 +721,11 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
bool ephemeral = false;
OPTION_TO_BOOL(ephemeral, ephemeral, false);
+ OPTION_TO_BOOL(decor.spell, spell, false);
+ if (decor.spell) {
+ has_decor = true;
+ }
+
OPTION_TO_BOOL(decor.ui_watched, ui_watched, false);
if (decor.ui_watched) {
has_decor = true;
@@ -972,20 +979,21 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
/// for the moment.
///
/// @param ns_id Namespace id from |nvim_create_namespace()|
-/// @param opts Callbacks invoked during redraw:
+/// @param opts Table of callbacks:
/// - on_start: called first on each screen redraw
/// ["start", tick]
-/// - on_buf: called for each buffer being redrawn (before window
-/// callbacks)
+/// - on_buf: called for each buffer being redrawn (before
+/// window callbacks)
/// ["buf", bufnr, tick]
-/// - on_win: called when starting to redraw a specific window.
+/// - on_win: called when starting to redraw a
+/// specific window.
/// ["win", winid, bufnr, topline, botline_guess]
-/// - on_line: called for each buffer line being redrawn. (The
-/// interaction with fold lines is subject to change)
+/// - on_line: called for each buffer line being redrawn.
+/// (The interaction with fold lines is subject to change)
/// ["win", winid, bufnr, row]
/// - on_end: called at the end of a redraw cycle
/// ["end", tick]
-void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Error *err)
+void nvim_set_decoration_provider(Integer ns_id, Dict(set_decoration_provider) *opts, Error *err)
FUNC_API_SINCE(7) FUNC_API_LUA_ONLY
{
DecorProvider *p = get_decor_provider((NS)ns_id, true);
@@ -997,37 +1005,32 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Erro
struct {
const char *name;
+ Object *source;
LuaRef *dest;
} cbs[] = {
- { "on_start", &p->redraw_start },
- { "on_buf", &p->redraw_buf },
- { "on_win", &p->redraw_win },
- { "on_line", &p->redraw_line },
- { "on_end", &p->redraw_end },
- { "_on_hl_def", &p->hl_def },
- { NULL, NULL },
+ { "on_start", &opts->on_start, &p->redraw_start },
+ { "on_buf", &opts->on_buf, &p->redraw_buf },
+ { "on_win", &opts->on_win, &p->redraw_win },
+ { "on_line", &opts->on_line, &p->redraw_line },
+ { "on_end", &opts->on_end, &p->redraw_end },
+ { "_on_hl_def", &opts->_on_hl_def, &p->hl_def },
+ { "_on_spell_nav", &opts->_on_spell_nav, &p->spell_nav },
+ { NULL, NULL, NULL },
};
- for (size_t i = 0; i < opts.size; i++) {
- String k = opts.items[i].key;
- Object *v = &opts.items[i].value;
- size_t j;
- for (j = 0; cbs[j].name && cbs[j].dest; j++) {
- if (strequal(cbs[j].name, k.data)) {
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation,
- "%s is not a function", cbs[j].name);
- goto error;
- }
- *(cbs[j].dest) = v->data.luaref;
- v->data.luaref = LUA_NOREF;
- break;
- }
+ for (size_t i = 0; cbs[i].source && cbs[i].dest && cbs[i].name; i++) {
+ Object *v = cbs[i].source;
+ if (v->type == kObjectTypeNil) {
+ continue;
}
- if (!cbs[j].name) {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+
+ if (v->type != kObjectTypeLuaRef) {
+ api_set_error(err, kErrorTypeValidation,
+ "%s is not a function", cbs[i].name);
goto error;
}
+ *(cbs[i].dest) = v->data.luaref;
+ v->data.luaref = LUA_NOREF;
}
p->active = true;
diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
index af3dc24f51..ea8949bd2c 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -2,6 +2,15 @@ return {
context = {
"types";
};
+ set_decoration_provider = {
+ "on_start";
+ "on_buf";
+ "on_win";
+ "on_line";
+ "on_end";
+ "_on_hl_def";
+ "_on_spell_nav";
+ };
set_extmark = {
"id";
"end_line";
@@ -28,6 +37,7 @@ return {
"line_hl_group";
"cursorline_hl_group";
"conceal";
+ "spell";
"ui_watched";
};
keymap = {
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index c26b18a7e7..a520f3c0e7 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -462,6 +462,9 @@ typedef struct {
char *b_p_spf; // 'spellfile'
char *b_p_spl; // 'spelllang'
char *b_p_spo; // 'spelloptions'
+#define SPO_CAMEL 0x1
+#define SPO_NPBUFFER 0x2
+ unsigned b_p_spo_flags; // 'spelloptions' flags
int b_cjk; // all CJK letters as OK
uint8_t b_syn_chartab[32]; // syntax iskeyword option
char *b_syn_isk; // iskeyword option
diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c
index 8955048f45..778f9293fb 100644
--- a/src/nvim/decoration.c
+++ b/src/nvim/decoration.c
@@ -69,7 +69,7 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start
void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor)
{
if (row2 >= row1) {
- if (!decor || decor->hl_id || decor_has_sign(decor) || decor->conceal) {
+ if (!decor || decor->hl_id || decor_has_sign(decor) || decor->conceal || decor->spell) {
redraw_buf_range_later(buf, row1 + 1, row2 + 1);
}
}
@@ -116,6 +116,11 @@ void decor_free(Decoration *decor)
}
}
+void decor_state_free(DecorState *state)
+{
+ xfree(state->active.items);
+}
+
void clear_virttext(VirtText *text)
{
for (size_t i = 0; i < kv_size(*text); i++) {
@@ -306,6 +311,7 @@ next_mark:
bool conceal = 0;
int conceal_char = 0;
int conceal_attr = 0;
+ bool spell = false;
for (size_t i = 0; i < kv_size(state->active); i++) {
DecorRange item = kv_A(state->active, i);
@@ -339,6 +345,9 @@ next_mark:
conceal_attr = item.attr_id;
}
}
+ if (active && item.decor.spell) {
+ spell = true;
+ }
if ((item.start_row == state->row && item.start_col <= col)
&& decor_virt_pos(item.decor)
&& item.decor.virt_text_pos == kVTOverlay && item.win_col == -1) {
@@ -355,6 +364,7 @@ next_mark:
state->conceal = conceal;
state->conceal_char = conceal_char;
state->conceal_attr = conceal_attr;
+ state->spell = spell;
return attr;
}
diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h
index 8f28442d41..bdbfd72a81 100644
--- a/src/nvim/decoration.h
+++ b/src/nvim/decoration.h
@@ -46,6 +46,7 @@ struct Decoration {
bool hl_eol;
bool virt_lines_above;
bool conceal;
+ bool spell;
// TODO(bfredl): style, etc
DecorPriority priority;
int col; // fixed col value, like win_col
@@ -61,8 +62,8 @@ struct Decoration {
bool ui_watched; // watched for win_extmark
};
#define DECORATION_INIT { KV_INITIAL_VALUE, KV_INITIAL_VALUE, 0, kVTEndOfLine, \
- kHlModeUnknown, false, false, false, false, DECOR_PRIORITY_BASE, \
- 0, 0, NULL, 0, 0, 0, 0, 0, false }
+ kHlModeUnknown, false, false, false, false, false, \
+ DECOR_PRIORITY_BASE, 0, 0, NULL, 0, 0, 0, 0, 0, false }
typedef struct {
int start_row;
@@ -90,6 +91,8 @@ typedef struct {
bool conceal;
int conceal_char;
int conceal_attr;
+
+ bool spell;
} DecorState;
EXTERN DecorState decor_state INIT(= { 0 });
diff --git a/src/nvim/decoration_provider.c b/src/nvim/decoration_provider.c
index 14c1238fa4..a11a4d2b04 100644
--- a/src/nvim/decoration_provider.c
+++ b/src/nvim/decoration_provider.c
@@ -7,6 +7,7 @@
#include "nvim/decoration.h"
#include "nvim/decoration_provider.h"
#include "nvim/highlight.h"
+#include "nvim/lib/kvec.h"
#include "nvim/lua/executor.h"
static kvec_t(DecorProvider) decor_providers = KV_INITIAL_VALUE;
@@ -14,7 +15,7 @@ static kvec_t(DecorProvider) decor_providers = KV_INITIAL_VALUE;
#define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \
{ ns_id, false, LUA_NOREF, LUA_NOREF, \
LUA_NOREF, LUA_NOREF, LUA_NOREF, \
- LUA_NOREF, -1, false }
+ LUA_NOREF, -1, false, false }
static bool decor_provider_invoke(NS ns_id, const char *name, LuaRef ref, Array args,
bool default_true, char **perr)
@@ -47,11 +48,33 @@ static bool decor_provider_invoke(NS ns_id, const char *name, LuaRef ref, Array
return false;
}
+void decor_providers_invoke_spell(win_T *wp, int start_row, int start_col, int end_row, int end_col,
+ char **err)
+{
+ for (size_t i = 0; i < kv_size(decor_providers); i++) {
+ DecorProvider *p = &kv_A(decor_providers, i);
+ if (!p->active) {
+ continue;
+ }
+
+ if (p->spell_nav != LUA_NOREF) {
+ MAXSIZE_TEMP_ARRAY(args, 6);
+ ADD_C(args, INTEGER_OBJ(wp->handle));
+ ADD_C(args, INTEGER_OBJ(wp->w_buffer->handle));
+ ADD_C(args, INTEGER_OBJ(start_row));
+ ADD_C(args, INTEGER_OBJ(start_col));
+ ADD_C(args, INTEGER_OBJ(end_row));
+ ADD_C(args, INTEGER_OBJ(end_col));
+ decor_provider_invoke(p->ns_id, "spell", p->spell_nav, args, true, err);
+ }
+ }
+}
+
/// For each provider invoke the 'start' callback
///
/// @param[out] providers Decoration providers
/// @param[out] err Provider err
-void decor_providers_start(DecorProviders *providers, int type, char **err)
+void decor_providers_start(DecorProviders *providers, char **err)
{
kvi_init(*providers);
@@ -65,7 +88,6 @@ void decor_providers_start(DecorProviders *providers, int type, char **err)
if (p->redraw_start != LUA_NOREF) {
MAXSIZE_TEMP_ARRAY(args, 2);
ADD_C(args, INTEGER_OBJ((int)display_tick));
- ADD_C(args, INTEGER_OBJ(type));
active = decor_provider_invoke(p->ns_id, "start", p->redraw_start, args, true, err);
} else {
active = true;
@@ -116,8 +138,8 @@ void decor_providers_invoke_win(win_T *wp, DecorProviders *providers,
/// @param row Row to invoke line callback for
/// @param[out] has_decor Set when at least one provider invokes a line callback
/// @param[out] err Provider error
-void providers_invoke_line(win_T *wp, DecorProviders *providers, int row, bool *has_decor,
- char **err)
+void decor_providers_invoke_line(win_T *wp, DecorProviders *providers, int row, bool *has_decor,
+ char **err)
{
for (size_t k = 0; k < kv_size(*providers); k++) {
DecorProvider *p = kv_A(*providers, k);
@@ -215,6 +237,7 @@ void decor_provider_clear(DecorProvider *p)
NLUA_CLEAR_REF(p->redraw_win);
NLUA_CLEAR_REF(p->redraw_line);
NLUA_CLEAR_REF(p->redraw_end);
+ NLUA_CLEAR_REF(p->spell_nav);
p->active = false;
}
diff --git a/src/nvim/decoration_provider.h b/src/nvim/decoration_provider.h
index dd1ed6c581..852b1583b9 100644
--- a/src/nvim/decoration_provider.h
+++ b/src/nvim/decoration_provider.h
@@ -12,6 +12,7 @@ typedef struct {
LuaRef redraw_line;
LuaRef redraw_end;
LuaRef hl_def;
+ LuaRef spell_nav;
int hl_valid;
bool hl_cached;
} DecorProvider;
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
index 85a0bf2f5f..7846510ad9 100644
--- a/src/nvim/drawline.c
+++ b/src/nvim/drawline.c
@@ -11,9 +11,11 @@
#include "nvim/arabic.h"
#include "nvim/buffer.h"
+#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/cursor_shape.h"
+#include "nvim/decoration.h"
#include "nvim/diff.h"
#include "nvim/drawline.h"
#include "nvim/fold.h"
@@ -654,7 +656,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
has_decor = decor_redraw_line(buf, lnum - 1, &decor_state);
- providers_invoke_line(wp, providers, lnum - 1, &has_decor, provider_err);
+ decor_providers_invoke_line(wp, providers, lnum - 1, &has_decor, provider_err);
if (*provider_err) {
provider_err_virt_text(lnum, *provider_err);
@@ -1646,7 +1648,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
ptr++;
if (extra_check) {
- bool can_spell = true;
+ bool no_plain_buffer = (wp->w_s->b_p_spo_flags & SPO_NPBUFFER) != 0;
+ bool can_spell = !no_plain_buffer;
// Get syntax attribute, unless still at the start of the line
// (double-wide char that doesn't fit).
@@ -1698,6 +1701,29 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
char_attr = 0;
}
+ if (has_decor && v > 0) {
+ bool selected = (area_active || (area_highlighting && noinvcur
+ && (colnr_T)vcol == wp->w_virtcol));
+ int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v - 1, off,
+ selected, &decor_state);
+ if (extmark_attr != 0) {
+ if (!attr_pri) {
+ char_attr = hl_combine_attr(char_attr, extmark_attr);
+ } else {
+ char_attr = hl_combine_attr(extmark_attr, char_attr);
+ }
+ }
+
+ decor_conceal = decor_state.conceal;
+ if (decor_conceal && decor_state.conceal_char) {
+ decor_conceal = 2; // really??
+ }
+
+ if (decor_state.spell) {
+ can_spell = true;
+ }
+ }
+
// Check spelling (unless at the end of the line).
// Only do this when there is no syntax highlighting, the
// @Spell cluster is not used or the current syntax item
@@ -1706,9 +1732,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
if (has_spell && v >= word_end && v > cur_checked_col) {
spell_attr = 0;
if (!attr_pri) {
- char_attr = syntax_attr;
+ char_attr = hl_combine_attr(char_attr, syntax_attr);
}
- if (c != 0 && (!has_syntax || can_spell)) {
+ if (c != 0 && ((!has_syntax && !no_plain_buffer) || can_spell)) {
char_u *prev_ptr;
char_u *p;
int len;
@@ -1781,25 +1807,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
char_attr = hl_combine_attr(term_attrs[vcol], char_attr);
}
- if (has_decor && v > 0) {
- bool selected = (area_active || (area_highlighting && noinvcur
- && (colnr_T)vcol == wp->w_virtcol));
- int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v - 1, off,
- selected, &decor_state);
- if (extmark_attr != 0) {
- if (!attr_pri) {
- char_attr = hl_combine_attr(char_attr, extmark_attr);
- } else {
- char_attr = hl_combine_attr(extmark_attr, char_attr);
- }
- }
-
- decor_conceal = decor_state.conceal;
- if (decor_conceal && decor_state.conceal_char) {
- decor_conceal = 2; // really??
- }
- }
-
// Found last space before word: check for line break.
if (wp->w_p_lbr && c0 == c && vim_isbreak(c)
&& !vim_isbreak((int)(*ptr))) {
diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c
index 29ff261461..5b83821927 100644
--- a/src/nvim/drawscreen.c
+++ b/src/nvim/drawscreen.c
@@ -539,7 +539,7 @@ int update_screen(int type)
ui_comp_set_screen_valid(true);
DecorProviders providers;
- decor_providers_start(&providers, type, &provider_err);
+ decor_providers_start(&providers, &provider_err);
// "start" callback could have changed highlights for global elements
if (win_check_ns_hl(NULL)) {
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
index 8e780f4aaa..290d20b749 100644
--- a/src/nvim/extmark.c
+++ b/src/nvim/extmark.c
@@ -70,7 +70,8 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col
|| kv_size(decor->virt_lines)
|| decor->conceal
|| decor_has_sign(decor)
- || decor->ui_watched) {
+ || decor->ui_watched
+ || decor->spell) {
decor_full = true;
decor = xmemdup(decor, sizeof *decor);
}
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index 64b3f69df4..0684db39ba 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -731,6 +731,7 @@ EXTERN char *p_spc; ///< 'spellcapcheck'
EXTERN char *p_spf; ///< 'spellfile'
EXTERN char *p_spl; ///< 'spelllang'
EXTERN char *p_spo; // 'spelloptions'
+EXTERN unsigned int spo_flags;
EXTERN char *p_sps; // 'spellsuggest'
EXTERN int p_spr; // 'splitright'
EXTERN int p_sol; // 'startofline'
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 717ca6747b..49adf03aa0 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -2355,6 +2355,7 @@ return {
secure=true,
expand=true,
varname='p_spo',
+ redraw={'current_buffer'},
defaults={if_true=""}
},
{
diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c
index 1bdfcbecb9..c226966ef6 100644
--- a/src/nvim/optionstr.c
+++ b/src/nvim/optionstr.c
@@ -107,6 +107,7 @@ static char *(p_fdc_values[]) = { "auto", "auto:1", "auto:2", "auto:3", "auto:4"
"auto:6", "auto:7", "auto:8", "auto:9", "0", "1", "2", "3", "4",
"5", "6", "7", "8", "9", NULL };
static char *(p_cb_values[]) = { "unnamed", "unnamedplus", NULL };
+static char *(p_spo_values[]) = { "camel", "noplainbuffer", NULL };
static char *(p_icm_values[]) = { "nosplit", "split", NULL };
static char *(p_jop_values[]) = { "stack", "view", NULL };
static char *(p_tpf_values[]) = { "BS", "HT", "FF", "ESC", "DEL", "C0", "C1", NULL };
@@ -1125,7 +1126,8 @@ char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf
// When 'spellcapcheck' is set compile the regexp program.
errmsg = compile_cap_prog(curwin->w_s);
} else if (varp == &(curwin->w_s->b_p_spo)) { // 'spelloptions'
- if (**varp != NUL && STRCMP("camel", *varp) != 0) {
+ if (opt_strings_flags(curwin->w_s->b_p_spo, p_spo_values, &(curwin->w_s->b_p_spo_flags),
+ true) != OK) {
errmsg = e_invarg;
}
} else if (varp == &p_sps) { // 'spellsuggest'
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index c0d05eb799..30e256b37a 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -71,6 +71,7 @@
#include "nvim/change.h" // for changed_bytes
#include "nvim/charset.h" // for skipwhite, getwhitecols, skipbin
#include "nvim/cursor.h" // for get_cursor_line_ptr
+#include "nvim/decoration.h"
#include "nvim/drawscreen.h" // for NOT_VALID, redraw_later
#include "nvim/eval/typval.h" // for semsg
#include "nvim/ex_cmds.h" // for do_sub_msg
@@ -220,7 +221,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou
size_t nrlen = 0; // found a number first
size_t wrongcaplen = 0;
bool count_word = docount;
- bool use_camel_case = *wp->w_s->b_p_spo != NUL;
+ bool use_camel_case = (wp->w_s->b_p_spo_flags & SPO_CAMEL) != 0;
bool camel_case = false;
// A word never starts at a space or a control character. Return quickly
@@ -1198,6 +1199,24 @@ bool no_spell_checking(win_T *wp)
return false;
}
+static void decor_spell_nav_start(win_T *wp)
+{
+ decor_state = (DecorState){ 0 };
+ decor_redraw_reset(wp->w_buffer, &decor_state);
+}
+
+static bool decor_spell_nav_col(win_T *wp, linenr_T lnum, linenr_T *decor_lnum, int col,
+ char **decor_error)
+{
+ if (*decor_lnum != lnum) {
+ decor_providers_invoke_spell(wp, lnum - 1, col, lnum - 1, -1, decor_error);
+ decor_redraw_line(wp->w_buffer, lnum - 1, &decor_state);
+ *decor_lnum = lnum;
+ }
+ decor_redraw_col(wp->w_buffer, col, col, false, &decor_state);
+ return decor_state.spell;
+}
+
/// Moves to the next spell error.
/// "curline" is false for "[s", "]s", "[S" and "]S".
/// "curline" is true to find word under/after cursor in the same line.
@@ -1216,11 +1235,11 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att
hlf_T attr = HLF_COUNT;
size_t len;
int has_syntax = syntax_present(wp);
- int col;
+ colnr_T col;
char_u *buf = NULL;
size_t buflen = 0;
int skip = 0;
- int capcol = -1;
+ colnr_T capcol = -1;
bool found_one = false;
bool wrapped = false;
@@ -1228,6 +1247,8 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att
return 0;
}
+ size_t ret = 0;
+
// Start looking for bad word at the start of the line, because we can't
// start halfway through a word, we don't know where it starts or ends.
//
@@ -1240,6 +1261,19 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att
linenr_T lnum = wp->w_cursor.lnum;
clearpos(&found_pos);
+ char *decor_error = NULL;
+ // Ephemeral extmarks are currently stored in the global decor_state.
+ // When looking for spell errors, we need to:
+ // - temporarily reset decor_state
+ // - run the _on_spell_nav decor callback for each line we look at
+ // - detect if any spell marks are present
+ // - restore decor_state to the value saved here.
+ // TODO(lewis6991): un-globalize decor_state and allow ephemeral marks to be stored into a
+ // temporary DecorState.
+ DecorState saved_decor_start = decor_state;
+ linenr_T decor_lnum = -1;
+ decor_spell_nav_start(wp);
+
while (!got_int) {
char_u *line = ml_get_buf(wp->w_buffer, lnum, false);
@@ -1258,10 +1292,10 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att
// For checking first word with a capital skip white space.
if (capcol == 0) {
- capcol = (int)getwhitecols((char *)line);
+ capcol = (colnr_T)getwhitecols((char *)line);
} else if (curline && wp == curwin) {
// For spellbadword(): check if first word needs a capital.
- col = (int)getwhitecols((char *)line);
+ col = (colnr_T)getwhitecols((char *)line);
if (check_need_cap(lnum, col)) {
capcol = col;
}
@@ -1308,33 +1342,37 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att
|| ((colnr_T)(curline
? p - buf + (ptrdiff_t)len
: p - buf) > wp->w_cursor.col)) {
- bool can_spell;
- if (has_syntax) {
- col = (int)(p - buf);
- (void)syn_get_id(wp, lnum, (colnr_T)col,
- false, &can_spell, false);
- if (!can_spell) {
- attr = HLF_COUNT;
- }
- } else {
- can_spell = true;
+ col = (colnr_T)(p - buf);
+
+ bool can_spell = (wp->w_s->b_p_spo_flags & SPO_NPBUFFER) == 0;
+
+ if (!can_spell) {
+ can_spell = decor_spell_nav_col(wp, lnum, &decor_lnum, col, &decor_error);
+ }
+
+ if (!can_spell && has_syntax) {
+ (void)syn_get_id(wp, lnum, col, false, &can_spell, false);
+ }
+
+ if (!can_spell) {
+ attr = HLF_COUNT;
}
if (can_spell) {
found_one = true;
found_pos = (pos_T) {
.lnum = lnum,
- .col = (int)(p - buf),
+ .col = col,
.coladd = 0
};
if (dir == FORWARD) {
// No need to search further.
wp->w_cursor = found_pos;
- xfree(buf);
if (attrp != NULL) {
*attrp = attr;
}
- return len;
+ ret = len;
+ goto theend;
} else if (curline) {
// Insert mode completion: put cursor after
// the bad word.
@@ -1358,8 +1396,8 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att
if (dir == BACKWARD && found_pos.lnum != 0) {
// Use the last match in the line (before the cursor).
wp->w_cursor = found_pos;
- xfree(buf);
- return found_len;
+ ret = found_len;
+ goto theend;
}
if (curline) {
@@ -1429,8 +1467,12 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att
line_breakcheck();
}
+theend:
+ decor_state_free(&decor_state);
+ xfree(decor_error);
+ decor_state = saved_decor_start;
xfree(buf);
- return 0;
+ return ret;
}
// For spell checking: concatenate the start of the following line "line" into
diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua
index 789f1c6487..db5a775632 100644
--- a/test/functional/ui/decorations_spec.lua
+++ b/test/functional/ui/decorations_spec.lua
@@ -31,6 +31,8 @@ describe('decorations providers', function()
[12] = {foreground = tonumber('0x990000')};
[13] = {background = Screen.colors.LightBlue};
[14] = {background = Screen.colors.WebGray, foreground = Screen.colors.DarkBlue};
+ [15] = {special = Screen.colors.Blue1, undercurl = true},
+ [16] = {special = Screen.colors.Red, undercurl = true},
}
end)
@@ -56,7 +58,7 @@ describe('decorations providers', function()
a.nvim_set_decoration_provider(_G.ns1, {
on_start = on_do; on_buf = on_do;
on_win = on_do; on_line = on_do;
- on_end = on_do;
+ on_end = on_do; _on_spell_nav = on_do;
})
return _G.ns1
]])
@@ -95,7 +97,7 @@ describe('decorations providers', function()
|
]]}
check_trace {
- { "start", 4, 40 };
+ { "start", 4 };
{ "win", 1000, 1, 0, 8 };
{ "line", 1000, 1, 0 };
{ "line", 1000, 1, 1 };
@@ -119,7 +121,7 @@ describe('decorations providers', function()
|
]]}
check_trace {
- { "start", 5, 10 };
+ { "start", 5 };
{ "buf", 1 };
{ "win", 1000, 1, 0, 8 };
{ "line", 1000, 1, 6 };
@@ -156,6 +158,84 @@ describe('decorations providers', function()
]]}
end)
+ it('can indicate spellchecked points', function()
+ exec [[
+ set spell
+ set spelloptions=noplainbuffer
+ syntax off
+ ]]
+
+ insert [[
+ I am well written text.
+ i am not capitalized.
+ I am a speling mistakke.
+ ]]
+
+ setup_provider [[
+ local ns = a.nvim_create_namespace "spell"
+ beamtrace = {}
+ local function on_do(kind, ...)
+ if kind == 'win' or kind == 'spell' then
+ a.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 2, end_col = 23, spell = true, ephemeral = true })
+ end
+ table.insert(beamtrace, {kind, ...})
+ end
+ ]]
+
+ check_trace {
+ { "start", 5 };
+ { "win", 1000, 1, 0, 5 };
+ { "line", 1000, 1, 0 };
+ { "line", 1000, 1, 1 };
+ { "line", 1000, 1, 2 };
+ { "line", 1000, 1, 3 };
+ { "end", 5 };
+ }
+
+ feed "gg0"
+
+ screen:expect{grid=[[
+ ^I am well written text. |
+ {15:i} am not capitalized. |
+ I am a {16:speling} {16:mistakke}. |
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed "]s"
+ check_trace {
+ { "spell", 1000, 1, 1, 0, 1, -1 };
+ }
+ screen:expect{grid=[[
+ I am well written text. |
+ {15:^i} am not capitalized. |
+ I am a {16:speling} {16:mistakke}. |
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed "]s"
+ check_trace {
+ { "spell", 1000, 1, 2, 7, 2, -1 };
+ }
+ screen:expect{grid=[[
+ I am well written text. |
+ {15:i} am not capitalized. |
+ I am a {16:^speling} {16:mistakke}. |
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
it('can predefine highlights', function()
screen:try_resize(40, 16)
insert(mulholland)