diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index f3332dc8a2..016a25b9d5 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -1028,7 +1028,7 @@ static void win_line_continue(winlinevars_T *wlv) /// @param lnum line to display /// @param startrow first row relative to window grid /// @param endrow last grid row to be redrawn -/// @param nochange not updating for changed text +/// @param mod_top top line updated for changed text /// @param number_only only update the number column /// @param foldinfo fold info for this line /// @param[in, out] providers decoration providers active this line @@ -1036,7 +1036,7 @@ static void win_line_continue(winlinevars_T *wlv) /// or explicitly return `false`. /// /// @return the number of last row the line occupies. -int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, bool number_only, +int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int mod_top, bool number_only, foldinfo_T foldinfo, DecorProviders *providers, char **provider_err) { winlinevars_T wlv; // variables passed between functions @@ -1227,12 +1227,16 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // When there was a sentence end in the previous line may require a // word starting with capital in this line. In line 1 always check - // the first word. - if (lnum != capcol_lnum) { - cap_col = -1; - } - if (lnum == 1) { + // the first word. Also check for sentence end in the line above + // when updating the first row in a window, the top line with + // changed text in a window, or if the previous line is folded. + if (lnum == 1 + || ((startrow == 0 || mod_top == lnum + || hasFoldingWin(wp, lnum - 1, NULL, NULL, true, NULL)) + && check_need_cap(wp, lnum, 0))) { cap_col = 0; + } else if (lnum != capcol_lnum) { + cap_col = -1; } capcol_lnum = 0; } @@ -2207,7 +2211,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, p = prev_ptr; } cap_col -= (int)(prev_ptr - line); - size_t tmplen = spell_check(wp, p, &spell_hlf, &cap_col, nochange); + size_t tmplen = spell_check(wp, p, &spell_hlf, &cap_col, mod_top == 0); assert(tmplen <= INT_MAX); int len = (int)tmplen; word_end = (int)v + len; diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 7f7c721379..4f79ba87af 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -2222,9 +2222,8 @@ static void win_update(win_T *wp, DecorProviders *providers) } // Display one line - row = win_line(wp, lnum, srow, - foldinfo.fi_lines ? srow : wp->w_grid.rows, - mod_top == 0, false, foldinfo, &line_providers, &provider_err); + row = win_line(wp, lnum, srow, foldinfo.fi_lines ? srow : wp->w_grid.rows, + mod_top, false, foldinfo, &line_providers, &provider_err); if (foldinfo.fi_lines == 0) { wp->w_lines[idx].wl_folded = false; @@ -2261,7 +2260,7 @@ static void win_update(win_T *wp, DecorProviders *providers) // text doesn't need to be drawn, but the number column does. foldinfo_T info = wp->w_p_cul && lnum == wp->w_cursor.lnum ? cursorline_fi : fold_info(wp, lnum); - (void)win_line(wp, lnum, srow, wp->w_grid.rows, true, true, + (void)win_line(wp, lnum, srow, wp->w_grid.rows, mod_top, true, info, &line_providers, &provider_err); } @@ -2359,7 +2358,7 @@ static void win_update(win_T *wp, DecorProviders *providers) // for ml_line_count+1 and only draw filler lines foldinfo_T info = { 0 }; row = win_line(wp, wp->w_botline, row, wp->w_grid.rows, - false, false, info, &line_providers, &provider_err); + mod_top, false, info, &line_providers, &provider_err); } } else if (dollar_vcol == -1) { wp->w_botline = lnum; diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 498bd56b9e..778266e5ac 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -1312,7 +1312,7 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att } else if (curline && wp == curwin) { // For spellbadword(): check if first word needs a capital. col = (colnr_T)getwhitecols(line); - if (check_need_cap(lnum, col)) { + if (check_need_cap(curwin, lnum, col)) { capcol = col; } @@ -2536,25 +2536,24 @@ int spell_casefold(const win_T *wp, char *str, int len, char *buf, int buflen) } // Check if the word at line "lnum" column "col" is required to start with a -// capital. This uses 'spellcapcheck' of the current buffer. -bool check_need_cap(linenr_T lnum, colnr_T col) +// capital. This uses 'spellcapcheck' of the buffer in window "wp". +bool check_need_cap(win_T *wp, linenr_T lnum, colnr_T col) { - bool need_cap = false; - - if (curwin->w_s->b_cap_prog == NULL) { + if (wp->w_s->b_cap_prog == NULL) { return false; } - char *line = get_cursor_line_ptr(); + bool need_cap = false; + char *line = col ? ml_get_buf(wp->w_buffer, lnum, false) : NULL; char *line_copy = NULL; colnr_T endcol = 0; - if (getwhitecols(line) >= (int)col) { + if (col == 0 || getwhitecols(line) >= col) { // At start of line, check if previous line is empty or sentence // ends there. if (lnum == 1) { need_cap = true; } else { - line = ml_get(lnum - 1); + line = ml_get_buf(wp->w_buffer, lnum - 1, false); if (*skipwhite(line) == NUL) { need_cap = true; } else { @@ -2571,13 +2570,13 @@ bool check_need_cap(linenr_T lnum, colnr_T col) if (endcol > 0) { // Check if sentence ends before the bad word. regmatch_T regmatch = { - .regprog = curwin->w_s->b_cap_prog, + .regprog = wp->w_s->b_cap_prog, .rm_ic = false }; char *p = line + endcol; while (true) { MB_PTR_BACK(line, p); - if (p == line || spell_iswordp_nmw(p, curwin)) { + if (p == line || spell_iswordp_nmw(p, wp)) { break; } if (vim_regexec(®match, p, 0) @@ -2586,7 +2585,7 @@ bool check_need_cap(linenr_T lnum, colnr_T col) break; } } - curwin->w_s->b_cap_prog = regmatch.regprog; + wp->w_s->b_cap_prog = regmatch.regprog; } xfree(line_copy); @@ -3601,7 +3600,7 @@ static bool spell_expand_need_cap; void spell_expand_check_cap(colnr_T col) { - spell_expand_need_cap = check_need_cap(curwin->w_cursor.lnum, col); + spell_expand_need_cap = check_need_cap(curwin, curwin->w_cursor.lnum, col); } // Get list of spelling suggestions. diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c index 84be88be7b..1c34c2487f 100644 --- a/src/nvim/spellsuggest.c +++ b/src/nvim/spellsuggest.c @@ -509,7 +509,7 @@ void spell_suggest(int count) // Get the word and its length. // Figure out if the word should be capitalised. - int need_cap = check_need_cap(curwin->w_cursor.lnum, curwin->w_cursor.col); + int need_cap = check_need_cap(curwin, curwin->w_cursor.lnum, curwin->w_cursor.col); // Make a copy of current line since autocommands may free the line. line = xstrdup(get_cursor_line_ptr()); diff --git a/test/functional/ui/spell_spec.lua b/test/functional/ui/spell_spec.lua index 7f11b06f78..630d0d0948 100644 --- a/test/functional/ui/spell_spec.lua +++ b/test/functional/ui/spell_spec.lua @@ -28,6 +28,7 @@ describe("'spell'", function() [7] = {foreground = Screen.colors.Blue}, [8] = {foreground = Screen.colors.Blue, special = Screen.colors.Red, undercurl = true}, [9] = {bold = true}, + [10] = {background = Screen.colors.LightGrey, foreground = Screen.colors.DarkBlue}, }) end) @@ -82,7 +83,7 @@ describe("'spell'", function() end) -- oldtest: Test_spell_screendump_spellcap() - it('has correct highlight at start of line with trailing space', function() + it('SpellCap highlight at start of line', function() exec([=[ call setline(1, [ \" This line has a sepll error. and missing caps and trailing spaces. ", @@ -117,7 +118,7 @@ describe("'spell'", function() | ]]) -- Deleting a full stop removes missing Cap in next line - feed('5Gddk$x') + feed('5Gddk$x') screen:expect([[ This line has a {1:sepll} error. {2:and} missing caps and trailing spaces. | {2:another} missing cap here. | @@ -140,6 +141,43 @@ describe("'spell'", function() {0:~ }| | ]]) + -- Folding an empty line does not remove Cap in next line + feed('uzfk:') + screen:expect([[ + This line has a {1:sepll} error. {2:and} missing caps and trailing spaces. | + {2:another} missing cap here. | + Not | + {10:^+-- 2 lines: and here.·························································}| + {2:and} here. | + {0:~ }| + {0:~ }| + | + ]]) + -- Folding the end of a sentence does not remove Cap in next line + -- and editing a line does not remove Cap in current line + feed('Jzfkk$x') + screen:expect([[ + This line has a {1:sepll} error. {2:and} missing caps and trailing spaces. | + {2:another} missing cap her^e | + {10:+-- 2 lines: Not·······························································}| + {2:and} here. | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + -- Cap is correctly applied in the first row of a window + feed('') + screen:expect([[ + {2:another} missing cap her^e | + {10:+-- 2 lines: Not·······························································}| + {2:and} here. | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) end) -- oldtest: Test_spell_compatible() diff --git a/test/old/testdir/test_spell.vim b/test/old/testdir/test_spell.vim index 14d6ce30c4..b0b2668758 100644 --- a/test/old/testdir/test_spell.vim +++ b/test/old/testdir/test_spell.vim @@ -1003,13 +1003,26 @@ func Test_spell_screendump_spellcap() call VerifyScreenDump(buf, 'Test_spell_3', {}) " Deleting a full stop removes missing Cap in next line - call term_sendkeys(buf, "5Gddk$x") + call term_sendkeys(buf, "5Gdd\k$x") call VerifyScreenDump(buf, 'Test_spell_4', {}) " Undo also updates the next line (go to command line to remove message) call term_sendkeys(buf, "u:\") call VerifyScreenDump(buf, 'Test_spell_5', {}) + " Folding an empty line does not remove Cap in next line + call term_sendkeys(buf, "uzfk:\") + call VerifyScreenDump(buf, 'Test_spell_6', {}) + + " Folding the end of a sentence does not remove Cap in next line + " and editing a line does not remove Cap in current line + call term_sendkeys(buf, "Jzfkk$x") + call VerifyScreenDump(buf, 'Test_spell_7', {}) + + " Cap is correctly applied in the first row of a window + call term_sendkeys(buf, "\\") + call VerifyScreenDump(buf, 'Test_spell_8', {}) + " clean up call StopVimInTerminal(buf) endfunc