mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
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:
commit
84d1094958
@ -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
|
||||
|
@ -5875,6 +5875,10 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
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")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -181,12 +181,14 @@
|
||||
;; Others
|
||||
|
||||
(comment) @comment
|
||||
(comment) @spell
|
||||
|
||||
(hash_bang_line) @comment
|
||||
|
||||
(number) @number
|
||||
|
||||
(string) @string
|
||||
(string) @spell
|
||||
|
||||
;; Error
|
||||
(ERROR) @error
|
||||
|
@ -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
|
||||
|
@ -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)) {
|
||||
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 (v->type != kObjectTypeLuaRef) {
|
||||
api_set_error(err, kErrorTypeValidation,
|
||||
"%s is not a function", cbs[j].name);
|
||||
"%s is not a function", cbs[i].name);
|
||||
goto error;
|
||||
}
|
||||
*(cbs[j].dest) = v->data.luaref;
|
||||
*(cbs[i].dest) = v->data.luaref;
|
||||
v->data.luaref = LUA_NOREF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!cbs[j].name) {
|
||||
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
p->active = true;
|
||||
|
@ -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 = {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 });
|
||||
|
@ -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,7 +138,7 @@ 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,
|
||||
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++) {
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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))) {
|
||||
|
@ -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)) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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'
|
||||
|
@ -2355,6 +2355,7 @@ return {
|
||||
secure=true,
|
||||
expand=true,
|
||||
varname='p_spo',
|
||||
redraw={'current_buffer'},
|
||||
defaults={if_true=""}
|
||||
},
|
||||
{
|
||||
|
@ -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'
|
||||
|
@ -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);
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
can_spell = true;
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user