diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 992e5ebe69..d6cd370ec7 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -432,6 +432,9 @@ capture marks comments as to be checked: > (comment) @spell < + +There is also `@nospell` which disables spellchecking regions with `@spell`. + *treesitter-highlight-conceal* Treesitter highlighting supports |conceal| via the `conceal` metadata. By convention, nodes to be concealed are captured as `@conceal`, but any capture diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 83a26aff13..f5e5ca1988 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -164,7 +164,7 @@ function TSHighlighter:get_query(lang) end ---@private -local function on_line_impl(self, buf, line, spell) +local function on_line_impl(self, buf, line, is_spell_nav) self.tree:for_each_tree(function(tstree, tree) if not tstree then return @@ -201,17 +201,26 @@ local function on_line_impl(self, buf, line, spell) local start_row, start_col, end_row, end_col = node:range() local hl = highlighter_query.hl_cache[capture] - local is_spell = highlighter_query:query().captures[capture] == 'spell' + local capture_name = highlighter_query:query().captures[capture] + local spell = nil + if capture_name == 'spell' then + spell = true + elseif capture_name == 'nospell' then + spell = false + end - if hl and end_row >= line and (not spell or is_spell) then + -- Give nospell a higher priority so it always overrides spell captures. + local spell_pri_offset = capture_name == 'nospell' and 1 or 0 + + if hl and end_row >= line and (not is_spell_nav or spell ~= nil) then a.nvim_buf_set_extmark(buf, ns, start_row, start_col, { end_line = end_row, end_col = end_col, hl_group = hl, ephemeral = true, - priority = tonumber(metadata.priority) or 100, -- Low but leaves room below + priority = (tonumber(metadata.priority) or 100) + spell_pri_offset, -- Low but leaves room below conceal = metadata.conceal, - spell = is_spell, + spell = spell, }) end if start_row > line then diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 3b1b470629..7c1f683993 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -721,8 +721,12 @@ 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) { + if (opts->spell.type == kObjectTypeNil) { + decor.spell = kNone; + } else { + bool spell = false; + OPTION_TO_BOOL(spell, spell, false); + decor.spell = spell ? kTrue : kFalse; has_decor = true; } diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 5a1708a57c..55bfc990f4 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -69,7 +69,11 @@ 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 || decor->spell) { + if (!decor + || decor->hl_id + || decor_has_sign(decor) + || decor->conceal + || decor->spell != kNone) { redraw_buf_range_later(buf, row1 + 1, row2 + 1); } } @@ -310,7 +314,7 @@ next_mark: bool conceal = 0; int conceal_char = 0; int conceal_attr = 0; - bool spell = false; + TriState spell = kNone; for (size_t i = 0; i < kv_size(state->active); i++) { DecorRange item = kv_A(state->active, i); @@ -344,8 +348,8 @@ next_mark: conceal_attr = item.attr_id; } } - if (active && item.decor.spell) { - spell = true; + if (active && item.decor.spell != kNone) { + spell = item.decor.spell; } if ((item.start_row == state->row && item.start_col <= col) && decor_virt_pos(item.decor) diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index bdbfd72a81..a269d2dbfa 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -46,7 +46,7 @@ struct Decoration { bool hl_eol; bool virt_lines_above; bool conceal; - bool spell; + TriState spell; // TODO(bfredl): style, etc DecorPriority priority; int col; // fixed col value, like win_col @@ -62,7 +62,7 @@ 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, false, \ + kHlModeUnknown, false, false, false, false, kNone, \ DECOR_PRIORITY_BASE, 0, 0, NULL, 0, 0, 0, 0, 0, false } typedef struct { @@ -92,7 +92,7 @@ typedef struct { int conceal_char; int conceal_attr; - bool spell; + TriState spell; } DecorState; EXTERN DecorState decor_state INIT(= { 0 }); diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 10e1d95730..1b5d26e544 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -33,6 +33,7 @@ #include "nvim/spell.h" #include "nvim/state.h" #include "nvim/syntax.h" +#include "nvim/types.h" #include "nvim/undo.h" #include "nvim/window.h" @@ -1719,9 +1720,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, decor_conceal = 2; // really?? } - if (decor_state.spell) { - can_spell = true; - } + can_spell = TRISTATE_TO_BOOL(decor_state.spell, can_spell); } // Check spelling (unless at the end of the line). diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index df87cc8ab6..015799be06 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -71,7 +71,7 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col || decor->conceal || decor_has_sign(decor) || decor->ui_watched - || decor->spell) { + || decor->spell != kNone) { decor_full = true; decor = xmemdup(decor, sizeof *decor); } diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 69c2b027bc..0dec44866e 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -1214,7 +1214,14 @@ static bool decor_spell_nav_col(win_T *wp, linenr_T lnum, linenr_T *decor_lnum, *decor_lnum = lnum; } decor_redraw_col(wp->w_buffer, col, col, false, &decor_state); - return decor_state.spell; + return decor_state.spell == kTrue; +} + +static inline bool can_syn_spell(win_T *wp, linenr_T lnum, int col) +{ + bool can_spell; + (void)syn_get_id(wp, lnum, col, false, &can_spell, false); + return can_spell; } /// Moves to the next spell error. @@ -1344,15 +1351,9 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att : p - buf) > wp->w_cursor.col)) { col = (colnr_T)(p - buf); - bool can_spell = decor_spell_nav_col(wp, lnum, &decor_lnum, col, &decor_error); - - if (!can_spell) { - if (has_syntax) { - (void)syn_get_id(wp, lnum, col, false, &can_spell, false); - } else { - can_spell = (wp->w_s->b_p_spo_flags & SPO_NPBUFFER) == 0; - } - } + bool can_spell = (!has_syntax && (wp->w_s->b_p_spo_flags & SPO_NPBUFFER) == 0) + || decor_spell_nav_col(wp, lnum, &decor_lnum, col, &decor_error) + || (has_syntax && can_syn_spell(wp, lnum, col)); if (!can_spell) { attr = HLF_COUNT; diff --git a/src/nvim/types.h b/src/nvim/types.h index fb10bf21d9..d90c623955 100644 --- a/src/nvim/types.h +++ b/src/nvim/types.h @@ -45,6 +45,9 @@ typedef enum { kTrue = 1, } TriState; +#define TRISTATE_TO_BOOL(val, \ + default) ((val) == kTrue ? true : ((val) == kFalse ? false : (default))) + typedef struct Decoration Decoration; #endif // NVIM_TYPES_H diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index e9072ebf98..7e321e2078 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -176,7 +176,13 @@ describe('decorations providers', function() 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 }) + a.nvim_buf_set_extmark(0, ns, 0, 0, { + end_row = 2, + end_col = 23, + spell = true, + priority = 20, + ephemeral = true + }) end table.insert(beamtrace, {kind, ...}) end @@ -234,6 +240,36 @@ describe('decorations providers', function() {1:~ }| | ]]} + + -- spell=false with lower priority doesn't disable spell + local ns = meths.create_namespace "spell" + local id = helpers.curbufmeths.set_extmark(ns, 0, 0, { priority = 30, end_row = 2, end_col = 23, spell = false }) + + screen:expect{grid=[[ + I am well written text. | + i am not capitalized. | + I am a ^speling mistakke. | + | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + -- spell=false with higher priority does disable spell + helpers.curbufmeths.set_extmark(ns, 0, 0, { id = id, priority = 10, end_row = 2, end_col = 23, spell = false }) + + 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()