Merge pull request #19419 from vigoux/extmark_spell

Co-authored-by: Lewis Russell <lewis6991@gmail.com>
Co-authored-by: Björn Linse <bjorn.linse@gmail.com>
This commit is contained in:
Lewis Russell 2022-09-06 11:23:01 +01:00 committed by GitHub
commit 84d1094958
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 313 additions and 95 deletions

View File

@ -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|. When a character is supplied it is used as |:syn-cchar|.
"hl_group" is used as highlight for the cchar if provided, "hl_group" is used as highlight for the cchar if provided,
otherwise it defaults to |hl-Conceal|. 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 • ui_watched: boolean that indicates the mark should be
drawn by a UI. When set, the UI will receive win_extmark drawn by a UI. When set, the UI will receive win_extmark
events. Note: the mark is positioned by virt_text 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. dict that maps from names to namespace ids.
*nvim_set_decoration_provider()* *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 Set or change decoration provider for a namespace
This is a very general purpose interface for having lua callbacks being This is a very general purpose interface for having lua callbacks being
@ -2709,7 +2711,7 @@ nvim_set_decoration_provider({ns_id}, {opts})
Parameters: ~ Parameters: ~
{ns_id} Namespace id from |nvim_create_namespace()| {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", • on_start: called first on each screen redraw ["start",
tick] tick]
• on_buf: called for each buffer being redrawn (before window • on_buf: called for each buffer being redrawn (before window

View File

@ -5871,10 +5871,14 @@ A jump table for the options with a short description can be found at |Q_op|.
'spelloptions' 'spo' string (default "") 'spelloptions' 'spo' string (default "")
local to buffer local to buffer
A comma-separated list of options for spell checking: 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 separate word: every upper-case character in a word
that comes after a lower case character indicates the that comes after a lower case character indicates the
start of a new word. 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'*
'spellsuggest' 'sps' string (default "best") 'spellsuggest' 'sps' string (default "best")

View File

@ -97,6 +97,7 @@ function TSHighlighter.new(tree, opts)
if vim.g.syntax_on ~= 1 then if vim.g.syntax_on ~= 1 then
vim.api.nvim_command('runtime! syntax/synload.vim') vim.api.nvim_command('runtime! syntax/synload.vim')
end end
vim.bo[self.bufnr].spelloptions = 'noplainbuffer'
self.tree:parse() self.tree:parse()
@ -156,7 +157,7 @@ function TSHighlighter:get_query(lang)
end end
---@private ---@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) self.tree:for_each_tree(function(tstree, tree)
if not tstree then if not tstree then
return 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 start_row, start_col, end_row, end_col = node:range()
local hl = highlighter_query.hl_cache[capture] 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, { a.nvim_buf_set_extmark(buf, ns, start_row, start_col, {
end_line = end_row, end_line = end_row,
end_col = end_col, end_col = end_col,
@ -201,6 +204,7 @@ local function on_line_impl(self, buf, line)
ephemeral = true, ephemeral = true,
priority = tonumber(metadata.priority) or 100, -- Low but leaves room below priority = tonumber(metadata.priority) or 100, -- Low but leaves room below
conceal = metadata.conceal, conceal = metadata.conceal,
spell = is_spell,
}) })
end end
if start_row > line then if start_row > line then
@ -217,7 +221,21 @@ function TSHighlighter._on_line(_, _win, buf, line, _)
return return
end 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 end
---@private ---@private
@ -244,6 +262,7 @@ a.nvim_set_decoration_provider(ns, {
on_buf = TSHighlighter._on_buf, on_buf = TSHighlighter._on_buf,
on_win = TSHighlighter._on_win, on_win = TSHighlighter._on_win,
on_line = TSHighlighter._on_line, on_line = TSHighlighter._on_line,
_on_spell_nav = TSHighlighter._on_spell_nav,
}) })
return TSHighlighter return TSHighlighter

View File

@ -101,6 +101,7 @@
[ "(" ")" "[" "]" "{" "}"] @punctuation.bracket [ "(" ")" "[" "]" "{" "}"] @punctuation.bracket
(string_literal) @string (string_literal) @string
(string_literal) @spell
(system_lib_string) @string (system_lib_string) @string
(null) @constant.builtin (null) @constant.builtin
@ -148,6 +149,7 @@
(comment) @comment (comment) @comment
(comment) @spell
;; Parameters ;; Parameters
(parameter_declaration (parameter_declaration

View File

@ -181,12 +181,14 @@
;; Others ;; Others
(comment) @comment (comment) @comment
(comment) @spell
(hash_bang_line) @comment (hash_bang_line) @comment
(number) @number (number) @number
(string) @string (string) @string
(string) @spell
;; Error ;; Error
(ERROR) @error (ERROR) @error

View File

@ -162,9 +162,11 @@
;; Literals ;; Literals
(string_literal) @string (string_literal) @string
(string_literal) @spell
(integer_literal) @number (integer_literal) @number
(float_literal) @float (float_literal) @float
(comment) @comment (comment) @comment
(comment) @spell
(pattern) @string.special (pattern) @string.special
(pattern_multi) @string.regex (pattern_multi) @string.regex
(filename) @string (filename) @string

View File

@ -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|. /// When a character is supplied it is used as |:syn-cchar|.
/// "hl_group" is used as highlight for the cchar if provided, /// "hl_group" is used as highlight for the cchar if provided,
/// otherwise it defaults to |hl-Conceal|. /// 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 /// - ui_watched: boolean that indicates the mark should be drawn
/// by a UI. When set, the UI will receive win_extmark events. /// by a UI. When set, the UI will receive win_extmark events.
/// Note: the mark is positioned by virt_text attributes. Can be /// 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; bool ephemeral = false;
OPTION_TO_BOOL(ephemeral, 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); OPTION_TO_BOOL(decor.ui_watched, ui_watched, false);
if (decor.ui_watched) { if (decor.ui_watched) {
has_decor = true; has_decor = true;
@ -972,20 +979,21 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
/// for the moment. /// for the moment.
/// ///
/// @param ns_id Namespace id from |nvim_create_namespace()| /// @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 /// - on_start: called first on each screen redraw
/// ["start", tick] /// ["start", tick]
/// - on_buf: called for each buffer being redrawn (before window /// - on_buf: called for each buffer being redrawn (before
/// callbacks) /// window callbacks)
/// ["buf", bufnr, tick] /// ["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] /// ["win", winid, bufnr, topline, botline_guess]
/// - on_line: called for each buffer line being redrawn. (The /// - on_line: called for each buffer line being redrawn.
/// interaction with fold lines is subject to change) /// (The interaction with fold lines is subject to change)
/// ["win", winid, bufnr, row] /// ["win", winid, bufnr, row]
/// - on_end: called at the end of a redraw cycle /// - on_end: called at the end of a redraw cycle
/// ["end", tick] /// ["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 FUNC_API_SINCE(7) FUNC_API_LUA_ONLY
{ {
DecorProvider *p = get_decor_provider((NS)ns_id, true); 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 { struct {
const char *name; const char *name;
Object *source;
LuaRef *dest; LuaRef *dest;
} cbs[] = { } cbs[] = {
{ "on_start", &p->redraw_start }, { "on_start", &opts->on_start, &p->redraw_start },
{ "on_buf", &p->redraw_buf }, { "on_buf", &opts->on_buf, &p->redraw_buf },
{ "on_win", &p->redraw_win }, { "on_win", &opts->on_win, &p->redraw_win },
{ "on_line", &p->redraw_line }, { "on_line", &opts->on_line, &p->redraw_line },
{ "on_end", &p->redraw_end }, { "on_end", &opts->on_end, &p->redraw_end },
{ "_on_hl_def", &p->hl_def }, { "_on_hl_def", &opts->_on_hl_def, &p->hl_def },
{ NULL, NULL }, { "_on_spell_nav", &opts->_on_spell_nav, &p->spell_nav },
{ NULL, NULL, NULL },
}; };
for (size_t i = 0; i < opts.size; i++) { for (size_t i = 0; cbs[i].source && cbs[i].dest && cbs[i].name; i++) {
String k = opts.items[i].key; Object *v = cbs[i].source;
Object *v = &opts.items[i].value; if (v->type == kObjectTypeNil) {
size_t j; continue;
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;
}
} }
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; goto error;
} }
*(cbs[i].dest) = v->data.luaref;
v->data.luaref = LUA_NOREF;
} }
p->active = true; p->active = true;

View File

@ -2,6 +2,15 @@ return {
context = { context = {
"types"; "types";
}; };
set_decoration_provider = {
"on_start";
"on_buf";
"on_win";
"on_line";
"on_end";
"_on_hl_def";
"_on_spell_nav";
};
set_extmark = { set_extmark = {
"id"; "id";
"end_line"; "end_line";
@ -28,6 +37,7 @@ return {
"line_hl_group"; "line_hl_group";
"cursorline_hl_group"; "cursorline_hl_group";
"conceal"; "conceal";
"spell";
"ui_watched"; "ui_watched";
}; };
keymap = { keymap = {

View File

@ -462,6 +462,9 @@ typedef struct {
char *b_p_spf; // 'spellfile' char *b_p_spf; // 'spellfile'
char *b_p_spl; // 'spelllang' char *b_p_spl; // 'spelllang'
char *b_p_spo; // 'spelloptions' 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 int b_cjk; // all CJK letters as OK
uint8_t b_syn_chartab[32]; // syntax iskeyword option uint8_t b_syn_chartab[32]; // syntax iskeyword option
char *b_syn_isk; // iskeyword option char *b_syn_isk; // iskeyword option

View File

@ -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) void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor)
{ {
if (row2 >= row1) { 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); 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) void clear_virttext(VirtText *text)
{ {
for (size_t i = 0; i < kv_size(*text); i++) { for (size_t i = 0; i < kv_size(*text); i++) {
@ -306,6 +311,7 @@ next_mark:
bool conceal = 0; bool conceal = 0;
int conceal_char = 0; int conceal_char = 0;
int conceal_attr = 0; int conceal_attr = 0;
bool spell = false;
for (size_t i = 0; i < kv_size(state->active); i++) { for (size_t i = 0; i < kv_size(state->active); i++) {
DecorRange item = kv_A(state->active, i); DecorRange item = kv_A(state->active, i);
@ -339,6 +345,9 @@ next_mark:
conceal_attr = item.attr_id; conceal_attr = item.attr_id;
} }
} }
if (active && item.decor.spell) {
spell = true;
}
if ((item.start_row == state->row && item.start_col <= col) if ((item.start_row == state->row && item.start_col <= col)
&& decor_virt_pos(item.decor) && decor_virt_pos(item.decor)
&& item.decor.virt_text_pos == kVTOverlay && item.win_col == -1) { && item.decor.virt_text_pos == kVTOverlay && item.win_col == -1) {
@ -355,6 +364,7 @@ next_mark:
state->conceal = conceal; state->conceal = conceal;
state->conceal_char = conceal_char; state->conceal_char = conceal_char;
state->conceal_attr = conceal_attr; state->conceal_attr = conceal_attr;
state->spell = spell;
return attr; return attr;
} }

View File

@ -46,6 +46,7 @@ struct Decoration {
bool hl_eol; bool hl_eol;
bool virt_lines_above; bool virt_lines_above;
bool conceal; bool conceal;
bool spell;
// TODO(bfredl): style, etc // TODO(bfredl): style, etc
DecorPriority priority; DecorPriority priority;
int col; // fixed col value, like win_col int col; // fixed col value, like win_col
@ -61,8 +62,8 @@ struct Decoration {
bool ui_watched; // watched for win_extmark bool ui_watched; // watched for win_extmark
}; };
#define DECORATION_INIT { KV_INITIAL_VALUE, KV_INITIAL_VALUE, 0, kVTEndOfLine, \ #define DECORATION_INIT { KV_INITIAL_VALUE, KV_INITIAL_VALUE, 0, kVTEndOfLine, \
kHlModeUnknown, false, false, false, false, DECOR_PRIORITY_BASE, \ kHlModeUnknown, false, false, false, false, false, \
0, 0, NULL, 0, 0, 0, 0, 0, false } DECOR_PRIORITY_BASE, 0, 0, NULL, 0, 0, 0, 0, 0, false }
typedef struct { typedef struct {
int start_row; int start_row;
@ -90,6 +91,8 @@ typedef struct {
bool conceal; bool conceal;
int conceal_char; int conceal_char;
int conceal_attr; int conceal_attr;
bool spell;
} DecorState; } DecorState;
EXTERN DecorState decor_state INIT(= { 0 }); EXTERN DecorState decor_state INIT(= { 0 });

View File

@ -7,6 +7,7 @@
#include "nvim/decoration.h" #include "nvim/decoration.h"
#include "nvim/decoration_provider.h" #include "nvim/decoration_provider.h"
#include "nvim/highlight.h" #include "nvim/highlight.h"
#include "nvim/lib/kvec.h"
#include "nvim/lua/executor.h" #include "nvim/lua/executor.h"
static kvec_t(DecorProvider) decor_providers = KV_INITIAL_VALUE; 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) \ #define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \
{ ns_id, false, LUA_NOREF, LUA_NOREF, \ { ns_id, false, LUA_NOREF, LUA_NOREF, \
LUA_NOREF, 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, static bool decor_provider_invoke(NS ns_id, const char *name, LuaRef ref, Array args,
bool default_true, char **perr) 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; 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 /// For each provider invoke the 'start' callback
/// ///
/// @param[out] providers Decoration providers /// @param[out] providers Decoration providers
/// @param[out] err Provider err /// @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); kvi_init(*providers);
@ -65,7 +88,6 @@ void decor_providers_start(DecorProviders *providers, int type, char **err)
if (p->redraw_start != LUA_NOREF) { if (p->redraw_start != LUA_NOREF) {
MAXSIZE_TEMP_ARRAY(args, 2); MAXSIZE_TEMP_ARRAY(args, 2);
ADD_C(args, INTEGER_OBJ((int)display_tick)); 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); active = decor_provider_invoke(p->ns_id, "start", p->redraw_start, args, true, err);
} else { } else {
active = true; 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 row Row to invoke line callback for
/// @param[out] has_decor Set when at least one provider invokes a line callback /// @param[out] has_decor Set when at least one provider invokes a line callback
/// @param[out] err Provider error /// @param[out] err Provider error
void providers_invoke_line(win_T *wp, DecorProviders *providers, int row, bool *has_decor, void decor_providers_invoke_line(win_T *wp, DecorProviders *providers, int row, bool *has_decor,
char **err) char **err)
{ {
for (size_t k = 0; k < kv_size(*providers); k++) { for (size_t k = 0; k < kv_size(*providers); k++) {
DecorProvider *p = kv_A(*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_win);
NLUA_CLEAR_REF(p->redraw_line); NLUA_CLEAR_REF(p->redraw_line);
NLUA_CLEAR_REF(p->redraw_end); NLUA_CLEAR_REF(p->redraw_end);
NLUA_CLEAR_REF(p->spell_nav);
p->active = false; p->active = false;
} }

View File

@ -12,6 +12,7 @@ typedef struct {
LuaRef redraw_line; LuaRef redraw_line;
LuaRef redraw_end; LuaRef redraw_end;
LuaRef hl_def; LuaRef hl_def;
LuaRef spell_nav;
int hl_valid; int hl_valid;
bool hl_cached; bool hl_cached;
} DecorProvider; } DecorProvider;

View File

@ -11,9 +11,11 @@
#include "nvim/arabic.h" #include "nvim/arabic.h"
#include "nvim/buffer.h" #include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h" #include "nvim/charset.h"
#include "nvim/cursor.h" #include "nvim/cursor.h"
#include "nvim/cursor_shape.h" #include "nvim/cursor_shape.h"
#include "nvim/decoration.h"
#include "nvim/diff.h" #include "nvim/diff.h"
#include "nvim/drawline.h" #include "nvim/drawline.h"
#include "nvim/fold.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); 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) { if (*provider_err) {
provider_err_virt_text(lnum, *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++; ptr++;
if (extra_check) { 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 // Get syntax attribute, unless still at the start of the line
// (double-wide char that doesn't fit). // (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; 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). // Check spelling (unless at the end of the line).
// Only do this when there is no syntax highlighting, the // Only do this when there is no syntax highlighting, the
// @Spell cluster is not used or the current syntax item // @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) { if (has_spell && v >= word_end && v > cur_checked_col) {
spell_attr = 0; spell_attr = 0;
if (!attr_pri) { 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 *prev_ptr;
char_u *p; char_u *p;
int len; 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); 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. // Found last space before word: check for line break.
if (wp->w_p_lbr && c0 == c && vim_isbreak(c) if (wp->w_p_lbr && c0 == c && vim_isbreak(c)
&& !vim_isbreak((int)(*ptr))) { && !vim_isbreak((int)(*ptr))) {

View File

@ -539,7 +539,7 @@ int update_screen(int type)
ui_comp_set_screen_valid(true); ui_comp_set_screen_valid(true);
DecorProviders providers; DecorProviders providers;
decor_providers_start(&providers, type, &provider_err); decor_providers_start(&providers, &provider_err);
// "start" callback could have changed highlights for global elements // "start" callback could have changed highlights for global elements
if (win_check_ns_hl(NULL)) { if (win_check_ns_hl(NULL)) {

View File

@ -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) || kv_size(decor->virt_lines)
|| decor->conceal || decor->conceal
|| decor_has_sign(decor) || decor_has_sign(decor)
|| decor->ui_watched) { || decor->ui_watched
|| decor->spell) {
decor_full = true; decor_full = true;
decor = xmemdup(decor, sizeof *decor); decor = xmemdup(decor, sizeof *decor);
} }

View File

@ -731,6 +731,7 @@ EXTERN char *p_spc; ///< 'spellcapcheck'
EXTERN char *p_spf; ///< 'spellfile' EXTERN char *p_spf; ///< 'spellfile'
EXTERN char *p_spl; ///< 'spelllang' EXTERN char *p_spl; ///< 'spelllang'
EXTERN char *p_spo; // 'spelloptions' EXTERN char *p_spo; // 'spelloptions'
EXTERN unsigned int spo_flags;
EXTERN char *p_sps; // 'spellsuggest' EXTERN char *p_sps; // 'spellsuggest'
EXTERN int p_spr; // 'splitright' EXTERN int p_spr; // 'splitright'
EXTERN int p_sol; // 'startofline' EXTERN int p_sol; // 'startofline'

View File

@ -2355,6 +2355,7 @@ return {
secure=true, secure=true,
expand=true, expand=true,
varname='p_spo', varname='p_spo',
redraw={'current_buffer'},
defaults={if_true=""} defaults={if_true=""}
}, },
{ {

View File

@ -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", "auto:6", "auto:7", "auto:8", "auto:9", "0", "1", "2", "3", "4",
"5", "6", "7", "8", "9", NULL }; "5", "6", "7", "8", "9", NULL };
static char *(p_cb_values[]) = { "unnamed", "unnamedplus", 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_icm_values[]) = { "nosplit", "split", NULL };
static char *(p_jop_values[]) = { "stack", "view", NULL }; static char *(p_jop_values[]) = { "stack", "view", NULL };
static char *(p_tpf_values[]) = { "BS", "HT", "FF", "ESC", "DEL", "C0", "C1", 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. // When 'spellcapcheck' is set compile the regexp program.
errmsg = compile_cap_prog(curwin->w_s); errmsg = compile_cap_prog(curwin->w_s);
} else if (varp == &(curwin->w_s->b_p_spo)) { // 'spelloptions' } 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; errmsg = e_invarg;
} }
} else if (varp == &p_sps) { // 'spellsuggest' } else if (varp == &p_sps) { // 'spellsuggest'

View File

@ -71,6 +71,7 @@
#include "nvim/change.h" // for changed_bytes #include "nvim/change.h" // for changed_bytes
#include "nvim/charset.h" // for skipwhite, getwhitecols, skipbin #include "nvim/charset.h" // for skipwhite, getwhitecols, skipbin
#include "nvim/cursor.h" // for get_cursor_line_ptr #include "nvim/cursor.h" // for get_cursor_line_ptr
#include "nvim/decoration.h"
#include "nvim/drawscreen.h" // for NOT_VALID, redraw_later #include "nvim/drawscreen.h" // for NOT_VALID, redraw_later
#include "nvim/eval/typval.h" // for semsg #include "nvim/eval/typval.h" // for semsg
#include "nvim/ex_cmds.h" // for do_sub_msg #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 nrlen = 0; // found a number first
size_t wrongcaplen = 0; size_t wrongcaplen = 0;
bool count_word = docount; 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; bool camel_case = false;
// A word never starts at a space or a control character. Return quickly // 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; 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. /// Moves to the next spell error.
/// "curline" is false for "[s", "]s", "[S" and "]S". /// "curline" is false for "[s", "]s", "[S" and "]S".
/// "curline" is true to find word under/after cursor in the same line. /// "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; hlf_T attr = HLF_COUNT;
size_t len; size_t len;
int has_syntax = syntax_present(wp); int has_syntax = syntax_present(wp);
int col; colnr_T col;
char_u *buf = NULL; char_u *buf = NULL;
size_t buflen = 0; size_t buflen = 0;
int skip = 0; int skip = 0;
int capcol = -1; colnr_T capcol = -1;
bool found_one = false; bool found_one = false;
bool wrapped = 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; return 0;
} }
size_t ret = 0;
// Start looking for bad word at the start of the line, because we can't // 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. // 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; linenr_T lnum = wp->w_cursor.lnum;
clearpos(&found_pos); 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) { while (!got_int) {
char_u *line = ml_get_buf(wp->w_buffer, lnum, false); 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. // For checking first word with a capital skip white space.
if (capcol == 0) { if (capcol == 0) {
capcol = (int)getwhitecols((char *)line); capcol = (colnr_T)getwhitecols((char *)line);
} else if (curline && wp == curwin) { } else if (curline && wp == curwin) {
// For spellbadword(): check if first word needs a capital. // 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)) { if (check_need_cap(lnum, col)) {
capcol = 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 || ((colnr_T)(curline
? p - buf + (ptrdiff_t)len ? p - buf + (ptrdiff_t)len
: p - buf) > wp->w_cursor.col)) { : p - buf) > wp->w_cursor.col)) {
bool can_spell; col = (colnr_T)(p - buf);
if (has_syntax) {
col = (int)(p - buf); bool can_spell = (wp->w_s->b_p_spo_flags & SPO_NPBUFFER) == 0;
(void)syn_get_id(wp, lnum, (colnr_T)col,
false, &can_spell, false); if (!can_spell) {
if (!can_spell) { can_spell = decor_spell_nav_col(wp, lnum, &decor_lnum, col, &decor_error);
attr = HLF_COUNT; }
}
} else { if (!can_spell && has_syntax) {
can_spell = true; (void)syn_get_id(wp, lnum, col, false, &can_spell, false);
}
if (!can_spell) {
attr = HLF_COUNT;
} }
if (can_spell) { if (can_spell) {
found_one = true; found_one = true;
found_pos = (pos_T) { found_pos = (pos_T) {
.lnum = lnum, .lnum = lnum,
.col = (int)(p - buf), .col = col,
.coladd = 0 .coladd = 0
}; };
if (dir == FORWARD) { if (dir == FORWARD) {
// No need to search further. // No need to search further.
wp->w_cursor = found_pos; wp->w_cursor = found_pos;
xfree(buf);
if (attrp != NULL) { if (attrp != NULL) {
*attrp = attr; *attrp = attr;
} }
return len; ret = len;
goto theend;
} else if (curline) { } else if (curline) {
// Insert mode completion: put cursor after // Insert mode completion: put cursor after
// the bad word. // 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) { if (dir == BACKWARD && found_pos.lnum != 0) {
// Use the last match in the line (before the cursor). // Use the last match in the line (before the cursor).
wp->w_cursor = found_pos; wp->w_cursor = found_pos;
xfree(buf); ret = found_len;
return found_len; goto theend;
} }
if (curline) { 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(); line_breakcheck();
} }
theend:
decor_state_free(&decor_state);
xfree(decor_error);
decor_state = saved_decor_start;
xfree(buf); xfree(buf);
return 0; return ret;
} }
// For spell checking: concatenate the start of the following line "line" into // For spell checking: concatenate the start of the following line "line" into

View File

@ -31,6 +31,8 @@ describe('decorations providers', function()
[12] = {foreground = tonumber('0x990000')}; [12] = {foreground = tonumber('0x990000')};
[13] = {background = Screen.colors.LightBlue}; [13] = {background = Screen.colors.LightBlue};
[14] = {background = Screen.colors.WebGray, foreground = Screen.colors.DarkBlue}; [14] = {background = Screen.colors.WebGray, foreground = Screen.colors.DarkBlue};
[15] = {special = Screen.colors.Blue1, undercurl = true},
[16] = {special = Screen.colors.Red, undercurl = true},
} }
end) end)
@ -56,7 +58,7 @@ describe('decorations providers', function()
a.nvim_set_decoration_provider(_G.ns1, { a.nvim_set_decoration_provider(_G.ns1, {
on_start = on_do; on_buf = on_do; on_start = on_do; on_buf = on_do;
on_win = on_do; on_line = 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 return _G.ns1
]]) ]])
@ -95,7 +97,7 @@ describe('decorations providers', function()
| |
]]} ]]}
check_trace { check_trace {
{ "start", 4, 40 }; { "start", 4 };
{ "win", 1000, 1, 0, 8 }; { "win", 1000, 1, 0, 8 };
{ "line", 1000, 1, 0 }; { "line", 1000, 1, 0 };
{ "line", 1000, 1, 1 }; { "line", 1000, 1, 1 };
@ -119,7 +121,7 @@ describe('decorations providers', function()
| |
]]} ]]}
check_trace { check_trace {
{ "start", 5, 10 }; { "start", 5 };
{ "buf", 1 }; { "buf", 1 };
{ "win", 1000, 1, 0, 8 }; { "win", 1000, 1, 0, 8 };
{ "line", 1000, 1, 6 }; { "line", 1000, 1, 6 };
@ -156,6 +158,84 @@ describe('decorations providers', function()
]]} ]]}
end) 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() it('can predefine highlights', function()
screen:try_resize(40, 16) screen:try_resize(40, 16)
insert(mulholland) insert(mulholland)