feat(marks): add conceal_lines to nvim_buf_set_extmark()

Implement an extmark property that conceals lines vertically.
This commit is contained in:
Luuk van Baal 2024-11-23 23:03:46 +01:00
parent 17383870dd
commit 743ddaa764
23 changed files with 450 additions and 128 deletions

View File

@ -2774,6 +2774,10 @@ 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|.
• conceal_lines: string which should be either empty or a
single character. When provided, lines in the range are
not drawn at all; the next unconcealed line is drawn
instead.
• spell: boolean indicating that spell checking should be • spell: boolean indicating that spell checking should be
performed within this extmark performed within this extmark
• ui_watched: boolean that indicates the mark should be • ui_watched: boolean that indicates the mark should be

View File

@ -172,6 +172,7 @@ API
• |nvim__ns_set()| can set properties for a namespace • |nvim__ns_set()| can set properties for a namespace
• |vim.json.encode()| has an option to enable forward slash escaping • |vim.json.encode()| has an option to enable forward slash escaping
• New |nvim_buf_set_extmark()| flag `conceal_lines` to conceal an entire line.
DEFAULTS DEFAULTS

View File

@ -686,6 +686,9 @@ function vim.api.nvim_buf_line_count(buffer) end
--- 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`.
--- - conceal_lines: string which should be either empty or a single
--- character. When provided, lines in the range are not drawn
--- at all; the next unconcealed line is drawn instead.
--- - spell: boolean indicating that spell checking should be --- - spell: boolean indicating that spell checking should be
--- performed within this extmark --- performed within this extmark
--- - ui_watched: boolean that indicates the mark should be drawn --- - ui_watched: boolean that indicates the mark should be drawn

View File

@ -263,6 +263,7 @@ error('Cannot require a meta file')
--- @field line_hl_group? integer|string --- @field line_hl_group? integer|string
--- @field cursorline_hl_group? integer|string --- @field cursorline_hl_group? integer|string
--- @field conceal? string --- @field conceal? string
--- @field conceal_lines? string
--- @field spell? boolean --- @field spell? boolean
--- @field ui_watched? boolean --- @field ui_watched? boolean
--- @field undo_restore? boolean --- @field undo_restore? boolean

View File

@ -475,6 +475,9 @@ 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|.
/// - conceal_lines: string which should be either empty or a single
/// character. When provided, lines in the range are not drawn
/// at all; the next unconcealed line is drawn instead.
/// - spell: boolean indicating that spell checking should be /// - spell: boolean indicating that spell checking should be
/// performed within this extmark /// performed within this extmark
/// - ui_watched: boolean that indicates the mark should be drawn /// - ui_watched: boolean that indicates the mark should be drawn
@ -575,6 +578,11 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
} }
} }
if (HAS_KEY(opts, set_extmark, conceal_lines)) {
hl.flags |= kSHConcealLines;
has_hl = true;
}
if (HAS_KEY(opts, set_extmark, virt_text)) { if (HAS_KEY(opts, set_extmark, virt_text)) {
virt_text.data.virt_text = parse_virt_text(opts->virt_text, err, &virt_text.width); virt_text.data.virt_text = parse_virt_text(opts->virt_text, err, &virt_text.width);
if (ERROR_SET(err)) { if (ERROR_SET(err)) {
@ -793,6 +801,10 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
} }
} }
if (hl.flags & kSHConcealLines) {
decor_flags |= MT_FLAG_DECOR_CONCEAL_LINES;
}
DecorInline decor = DECOR_INLINE_INIT; DecorInline decor = DECOR_INLINE_INIT;
if (decor_alloc || decor_indexed != DECOR_ID_INVALID || url != NULL if (decor_alloc || decor_indexed != DECOR_ID_INVALID || url != NULL
|| schar_high(hl.conceal_char)) { || schar_high(hl.conceal_char)) {

View File

@ -51,6 +51,7 @@ typedef struct {
HLGroupID line_hl_group; HLGroupID line_hl_group;
HLGroupID cursorline_hl_group; HLGroupID cursorline_hl_group;
String conceal; String conceal;
String conceal_lines;
Boolean spell; Boolean spell;
Boolean ui_watched; Boolean ui_watched;
Boolean undo_restore; Boolean undo_restore;

View File

@ -813,6 +813,7 @@ typedef struct {
uint16_t wl_size; // height in screen lines uint16_t wl_size; // height in screen lines
char wl_valid; // true values are valid for text in buffer char wl_valid; // true values are valid for text in buffer
char wl_folded; // true when this is a range of folded lines char wl_folded; // true when this is a range of folded lines
linenr_T wl_foldend; // last buffer line number for folded line
linenr_T wl_lastlnum; // last buffer line number for logical line linenr_T wl_lastlnum; // last buffer line number for logical line
} wline_T; } wline_T;

View File

@ -206,9 +206,10 @@ static void changed_lines_invalidate_win(win_T *wp, linenr_T lnum, colnr_T col,
} else if (xtra != 0) { } else if (xtra != 0) {
// line below change // line below change
wp->w_lines[i].wl_lnum += xtra; wp->w_lines[i].wl_lnum += xtra;
wp->w_lines[i].wl_foldend += xtra;
wp->w_lines[i].wl_lastlnum += xtra; wp->w_lines[i].wl_lastlnum += xtra;
} }
} else if (wp->w_lines[i].wl_lastlnum >= lnum) { } else if (wp->w_lines[i].wl_foldend >= lnum) {
// change somewhere inside this range of folded lines, // change somewhere inside this range of folded lines,
// may need to be redrawn // may need to be redrawn
wp->w_lines[i].wl_valid = false; wp->w_lines[i].wl_valid = false;

View File

@ -125,6 +125,13 @@ void decor_redraw_sh(buf_T *buf, int row1, int row2, DecorSignHighlight sh)
redraw_buf_range_later(buf, row1 + 1, row2 + 1); redraw_buf_range_later(buf, row1 + 1, row2 + 1);
} }
} }
if (sh.flags & kSHConcealLines) {
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_buffer == buf) {
changed_window_setting(wp);
}
}
}
if (sh.flags & kSHUIWatched) { if (sh.flags & kSHUIWatched) {
redraw_buf_line_later(buf, row1 + 1, false); redraw_buf_line_later(buf, row1 + 1, false);
} }
@ -833,8 +840,47 @@ int sign_item_cmp(const void *p1, const void *p2)
return 0; return 0;
} }
static const uint32_t sign_filter[4] = {[kMTMetaSignText] = kMTFilterSelect, static const uint32_t conceal_filter[kMTMetaCount] = {[kMTMetaConcealLines] = kMTFilterSelect };
[kMTMetaSignHL] = kMTFilterSelect };
bool decor_conceal_line(win_T *wp, int row, bool check_cursor)
{
if (wp->w_p_cole < 2 || !buf_meta_total(wp->w_buffer, kMTMetaConcealLines)
|| (!check_cursor && ((row + 1 == wp->w_cursor.lnum) && !conceal_cursor_line(wp)))) {
return false;
}
MTPair pair;
MarkTreeIter itr[1];
marktree_itr_get_overlap(wp->w_buffer->b_marktree, row, 0, itr);
while (marktree_itr_step_overlap(wp->w_buffer->b_marktree, itr, &pair)) {
if (mt_conceal_lines(pair.start) && ns_in_win(pair.start.ns, wp)) {
return true;
}
}
marktree_itr_step_out_filter(wp->w_buffer->b_marktree, itr, conceal_filter);
while (itr->x) {
MTKey mark = marktree_itr_current(itr);
if (mark.pos.row > row) {
break;
}
if (mt_conceal_lines(mark) && ns_in_win(pair.start.ns, wp)) {
return true;
}
marktree_itr_next_filter(wp->w_buffer->b_marktree, itr, row + 1, 0, conceal_filter);
}
return false;
}
bool win_lines_concealed(win_T *wp)
{
return hasAnyFolding(wp)
|| (wp->w_p_cole >= 2 && buf_meta_total(wp->w_buffer, kMTMetaConcealLines));
}
static const uint32_t sign_filter[kMTMetaCount] = {[kMTMetaSignText] = kMTFilterSelect,
[kMTMetaSignHL] = kMTFilterSelect };
/// Return the sign attributes on the currently refreshed row. /// Return the sign attributes on the currently refreshed row.
/// ///
@ -856,7 +902,7 @@ void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[],
// TODO(bfredl): integrate with main decor loop. // TODO(bfredl): integrate with main decor loop.
marktree_itr_get_overlap(buf->b_marktree, row, 0, itr); marktree_itr_get_overlap(buf->b_marktree, row, 0, itr);
while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) {
if (!mt_invalid(pair.start) && mt_decor_sign(pair.start)) { if (!mt_invalid(pair.start) && mt_decor_sign(pair.start) && ns_in_win(pair.start.ns, wp)) {
DecorSignHighlight *sh = decor_find_sign(mt_decor(pair.start)); DecorSignHighlight *sh = decor_find_sign(mt_decor(pair.start));
num_text += (sh->text[0] != NUL); num_text += (sh->text[0] != NUL);
kv_push(signs, ((SignItem){ sh, pair.start.id })); kv_push(signs, ((SignItem){ sh, pair.start.id }));
@ -923,7 +969,7 @@ DecorSignHighlight *decor_find_sign(DecorInline decor)
} }
} }
static const uint32_t signtext_filter[4] = {[kMTMetaSignText] = kMTFilterSelect }; static const uint32_t signtext_filter[kMTMetaCount] = {[kMTMetaSignText] = kMTFilterSelect };
/// Count the number of signs in a range after adding/removing a sign, or to /// Count the number of signs in a range after adding/removing a sign, or to
/// (re-)initialize a range in "b_signcols.count". /// (re-)initialize a range in "b_signcols.count".
@ -1018,7 +1064,7 @@ bool decor_redraw_eol(win_T *wp, DecorState *state, int *eol_attr, int eol_col)
return has_virt_pos; return has_virt_pos;
} }
static const uint32_t lines_filter[4] = {[kMTMetaLines] = kMTFilterSelect }; static const uint32_t lines_filter[kMTMetaCount] = {[kMTMetaLines] = kMTFilterSelect };
/// @param apply_folds Only count virtual lines that are not in folds. /// @param apply_folds Only count virtual lines that are not in folds.
int decor_virt_lines(win_T *wp, int start_row, int end_row, VirtLines *lines, bool apply_folds) int decor_virt_lines(win_T *wp, int start_row, int end_row, VirtLines *lines, bool apply_folds)
@ -1049,7 +1095,8 @@ int decor_virt_lines(win_T *wp, int start_row, int end_row, VirtLines *lines, bo
int mrow = mark.pos.row; int mrow = mark.pos.row;
int draw_row = mrow + (above ? 0 : 1); int draw_row = mrow + (above ? 0 : 1);
if (draw_row >= start_row && draw_row < end_row if (draw_row >= start_row && draw_row < end_row
&& (!apply_folds || !hasFolding(wp, mrow + 1, NULL, NULL))) { && (!apply_folds || !(hasFolding(wp, mrow + 1, NULL, NULL)
|| decor_conceal_line(wp, mrow, false)))) {
virt_lines += (int)kv_size(vt->data.virt_lines); virt_lines += (int)kv_size(vt->data.virt_lines);
if (lines) { if (lines) {
kv_splice(*lines, vt->data.virt_lines); kv_splice(*lines, vt->data.virt_lines);
@ -1116,6 +1163,10 @@ void decor_to_dict_legacy(Dict *dict, DecorInline decor, bool hl_name, Arena *ar
PUT_C(*dict, "conceal", CSTR_TO_ARENA_OBJ(arena, buf)); PUT_C(*dict, "conceal", CSTR_TO_ARENA_OBJ(arena, buf));
} }
if (sh_hl.flags & kSHConcealLines) {
PUT_C(*dict, "conceal_lines", STRING_OBJ(cstr_as_string("")));
}
if (sh_hl.flags & kSHSpellOn) { if (sh_hl.flags & kSHSpellOn) {
PUT_C(*dict, "spell", BOOLEAN_OBJ(true)); PUT_C(*dict, "spell", BOOLEAN_OBJ(true));
} else if (sh_hl.flags & kSHSpellOff) { } else if (sh_hl.flags & kSHSpellOff) {

View File

@ -46,6 +46,7 @@ enum {
kSHSpellOn = 16, kSHSpellOn = 16,
kSHSpellOff = 32, kSHSpellOff = 32,
kSHConceal = 64, kSHConceal = 64,
kSHConcealLines = 128,
}; };
typedef struct { typedef struct {

View File

@ -24,6 +24,7 @@
#include "nvim/change.h" #include "nvim/change.h"
#include "nvim/charset.h" #include "nvim/charset.h"
#include "nvim/cursor.h" #include "nvim/cursor.h"
#include "nvim/decoration.h"
#include "nvim/diff.h" #include "nvim/diff.h"
#include "nvim/drawscreen.h" #include "nvim/drawscreen.h"
#include "nvim/errors.h" #include "nvim/errors.h"
@ -2082,7 +2083,7 @@ int diff_check_with_linestatus(win_T *wp, linenr_T lnum, int *linestatus)
} }
// A closed fold never has filler lines. // A closed fold never has filler lines.
if (hasFolding(wp, lnum, NULL, NULL)) { if (hasFolding(wp, lnum, NULL, NULL) || decor_conceal_line(wp, lnum - 1, false)) {
return 0; return 0;
} }

View File

@ -148,6 +148,11 @@ void conceal_check_cursor_line(void)
} }
redrawWinline(curwin, curwin->w_cursor.lnum); redrawWinline(curwin, curwin->w_cursor.lnum);
// Concealed line visibility toggled.
if (decor_conceal_line(curwin, curwin->w_cursor.lnum - 1, true)) {
changed_window_setting(curwin);
}
// Need to recompute cursor column, e.g., when starting Visual mode // Need to recompute cursor column, e.g., when starting Visual mode
// without concealing. // without concealing.
curs_columns(curwin, true); curs_columns(curwin, true);
@ -1622,7 +1627,7 @@ static void win_update(win_T *wp)
} }
} }
if (mod_top != 0 && hasAnyFolding(wp)) { if (mod_top != 0 && (win_lines_concealed(wp))) {
// A change in a line can cause lines above it to become folded or // A change in a line can cause lines above it to become folded or
// unfolded. Find the top most buffer line that may be affected. // unfolded. Find the top most buffer line that may be affected.
// If the line was previously folded and displayed, get the first // If the line was previously folded and displayed, get the first
@ -1719,12 +1724,12 @@ static void win_update(win_T *wp)
&& wp->w_topfill > wp->w_old_topfill))) { && wp->w_topfill > wp->w_old_topfill))) {
// New topline is above old topline: May scroll down. // New topline is above old topline: May scroll down.
int j; int j;
if (hasAnyFolding(wp)) { if (win_lines_concealed(wp)) {
// count the number of lines we are off, counting a sequence // Count the number of lines we are off, counting a sequence
// of folded lines as one // of folded lines as one, and skip concealed lines.
j = 0; j = 0;
for (linenr_T ln = wp->w_topline; ln < wp->w_lines[0].wl_lnum; ln++) { for (linenr_T ln = wp->w_topline; ln < wp->w_lines[0].wl_lnum; ln++) {
j++; j += !decor_conceal_line(wp, ln - 1, false);
if (j >= wp->w_grid.rows - 2) { if (j >= wp->w_grid.rows - 2) {
break; break;
} }
@ -2109,6 +2114,28 @@ static void win_update(win_T *wp)
top_to_mod = false; top_to_mod = false;
} }
// When lines are folded, display one line for all of them.
// Otherwise, display normally (can be several display lines when
// 'wrap' is on).
foldinfo_T foldinfo = wp->w_p_cul && lnum == wp->w_cursor.lnum
? cursorline_fi : fold_info(wp, lnum);
// Concealed line: continue to next line on the same row.
if (decor_conceal_line(wp, lnum - 1, false)) {
if (wp == curwin && lnum == curwin->w_cursor.lnum) {
conceal_cursor_used = conceal_cursor_line(curwin);
}
if (idx > 0) {
wp->w_lines[idx - 1].wl_lastlnum = lnum + foldinfo.fi_lines - 1;
}
if (lnum == mod_top && lnum < mod_bot) {
mod_top += foldinfo.fi_lines ? foldinfo.fi_lines : 1;
}
lnum += foldinfo.fi_lines ? foldinfo.fi_lines : 1;
spv.spv_capcol_lnum = 0;
continue;
}
// When at start of changed lines: May scroll following lines // When at start of changed lines: May scroll following lines
// up or down to minimize redrawing. // up or down to minimize redrawing.
// Don't do this when the change continues until the end. // Don't do this when the change continues until the end.
@ -2158,8 +2185,10 @@ static void win_update(win_T *wp)
// rows, and may insert/delete lines // rows, and may insert/delete lines
int j = idx; int j = idx;
for (l = lnum; l < mod_bot; l++) { for (l = lnum; l < mod_bot; l++) {
linenr_T first = l;
int prev_rows = new_rows;
if (hasFolding(wp, l, NULL, &l)) { if (hasFolding(wp, l, NULL, &l)) {
new_rows++; new_rows += !decor_conceal_line(wp, first - 1, false);
} else if (l == wp->w_topline) { } else if (l == wp->w_topline) {
int n = plines_win_nofill(wp, l, false) + wp->w_topfill int n = plines_win_nofill(wp, l, false) + wp->w_topfill
- adjust_plines_for_skipcol(wp); - adjust_plines_for_skipcol(wp);
@ -2168,7 +2197,8 @@ static void win_update(win_T *wp)
} else { } else {
new_rows += plines_win(wp, l, true); new_rows += plines_win(wp, l, true);
} }
j++; // Do not increment when height was 0 (for a concealed line).
j += (prev_rows != new_rows);
if (new_rows > wp->w_grid.rows - row - 2) { if (new_rows > wp->w_grid.rows - row - 2) {
// it's getting too much, must redraw the rest // it's getting too much, must redraw the rest
new_rows = 9999; new_rows = 9999;
@ -2250,12 +2280,6 @@ static void win_update(win_T *wp)
} }
} }
// When lines are folded, display one line for all of them.
// Otherwise, display normally (can be several display lines when
// 'wrap' is on).
foldinfo_T foldinfo = wp->w_p_cul && lnum == wp->w_cursor.lnum
? cursorline_fi : fold_info(wp, lnum);
if (foldinfo.fi_lines == 0 if (foldinfo.fi_lines == 0
&& idx < wp->w_lines_valid && idx < wp->w_lines_valid
&& wp->w_lines[idx].wl_valid && wp->w_lines[idx].wl_valid
@ -2290,11 +2314,13 @@ static void win_update(win_T *wp)
if (foldinfo.fi_lines == 0) { if (foldinfo.fi_lines == 0) {
wp->w_lines[idx].wl_folded = false; wp->w_lines[idx].wl_folded = false;
wp->w_lines[idx].wl_foldend = lnum;
wp->w_lines[idx].wl_lastlnum = lnum; wp->w_lines[idx].wl_lastlnum = lnum;
did_update = DID_LINE; did_update = DID_LINE;
} else { } else {
foldinfo.fi_lines--; foldinfo.fi_lines--;
wp->w_lines[idx].wl_folded = true; wp->w_lines[idx].wl_folded = true;
wp->w_lines[idx].wl_foldend = lnum + foldinfo.fi_lines;
wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines; wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines;
did_update = DID_FOLD; did_update = DID_FOLD;
} }

View File

@ -15,6 +15,7 @@
#include "nvim/change.h" #include "nvim/change.h"
#include "nvim/charset.h" #include "nvim/charset.h"
#include "nvim/cursor.h" #include "nvim/cursor.h"
#include "nvim/decoration.h"
#include "nvim/digraph.h" #include "nvim/digraph.h"
#include "nvim/drawscreen.h" #include "nvim/drawscreen.h"
#include "nvim/edit.h" #include "nvim/edit.h"
@ -2577,15 +2578,15 @@ int oneleft(void)
return OK; return OK;
} }
/// Move the cursor up "n" lines in window "wp". /// Move the cursor up "n" lines in window "wp". Takes care of closed folds.
/// Takes care of closed folds. /// Skips over concealed lines when "skip_conceal" is true.
void cursor_up_inner(win_T *wp, linenr_T n) void cursor_up_inner(win_T *wp, linenr_T n, bool skip_conceal)
{ {
linenr_T lnum = wp->w_cursor.lnum; linenr_T lnum = wp->w_cursor.lnum;
if (n >= lnum) { if (n >= lnum) {
lnum = 1; lnum = 1;
} else if (hasAnyFolding(wp)) { } else if (win_lines_concealed(wp)) {
// Count each sequence of folded lines as one logical line. // Count each sequence of folded lines as one logical line.
// go to the start of the current fold // go to the start of the current fold
@ -2594,6 +2595,7 @@ void cursor_up_inner(win_T *wp, linenr_T n)
while (n--) { while (n--) {
// move up one line // move up one line
lnum--; lnum--;
n += skip_conceal && decor_conceal_line(wp, lnum - 1, true);
if (lnum <= 1) { if (lnum <= 1) {
break; break;
} }
@ -2619,7 +2621,7 @@ int cursor_up(linenr_T n, bool upd_topline)
if (n > 0 && curwin->w_cursor.lnum <= 1) { if (n > 0 && curwin->w_cursor.lnum <= 1) {
return FAIL; return FAIL;
} }
cursor_up_inner(curwin, n); cursor_up_inner(curwin, n, false);
// try to advance to the column we want to be at // try to advance to the column we want to be at
coladvance(curwin, curwin->w_curswant); coladvance(curwin, curwin->w_curswant);
@ -2631,16 +2633,16 @@ int cursor_up(linenr_T n, bool upd_topline)
return OK; return OK;
} }
/// Move the cursor down "n" lines in window "wp". /// Move the cursor down "n" lines in window "wp". Takes care of closed folds.
/// Takes care of closed folds. /// Skips over concealed lines when "skip_conceal" is true.
void cursor_down_inner(win_T *wp, int n) void cursor_down_inner(win_T *wp, int n, bool skip_conceal)
{ {
linenr_T lnum = wp->w_cursor.lnum; linenr_T lnum = wp->w_cursor.lnum;
linenr_T line_count = wp->w_buffer->b_ml.ml_line_count; linenr_T line_count = wp->w_buffer->b_ml.ml_line_count;
if (lnum + n >= line_count) { if (lnum + n >= line_count) {
lnum = line_count; lnum = line_count;
} else if (hasAnyFolding(wp)) { } else if (win_lines_concealed(wp)) {
linenr_T last; linenr_T last;
// count each sequence of folded lines as one logical line // count each sequence of folded lines as one logical line
@ -2650,6 +2652,7 @@ void cursor_down_inner(win_T *wp, int n)
} else { } else {
lnum++; lnum++;
} }
n += skip_conceal && decor_conceal_line(wp, lnum - 1, true);
if (lnum >= line_count) { if (lnum >= line_count) {
break; break;
} }
@ -2671,7 +2674,7 @@ int cursor_down(int n, bool upd_topline)
if (n > 0 && lnum >= curwin->w_buffer->b_ml.ml_line_count) { if (n > 0 && lnum >= curwin->w_buffer->b_ml.ml_line_count) {
return FAIL; return FAIL;
} }
cursor_down_inner(curwin, n); cursor_down_inner(curwin, n, false);
// try to advance to the column we want to be at // try to advance to the column we want to be at
coladvance(curwin, curwin->w_curswant); coladvance(curwin, curwin->w_curswant);

View File

@ -194,7 +194,7 @@ bool hasFoldingWin(win_T *const win, const linenr_T lnum, linenr_T *const firstp
const int x = find_wl_entry(win, lnum); const int x = find_wl_entry(win, lnum);
if (x >= 0) { if (x >= 0) {
first = win->w_lines[x].wl_lnum; first = win->w_lines[x].wl_lnum;
last = win->w_lines[x].wl_lastlnum; last = win->w_lines[x].wl_foldend;
had_folded = win->w_lines[x].wl_folded; had_folded = win->w_lines[x].wl_folded;
} }
} }
@ -971,7 +971,7 @@ int find_wl_entry(win_T *win, linenr_T lnum)
if (lnum < win->w_lines[i].wl_lnum) { if (lnum < win->w_lines[i].wl_lnum) {
return -1; return -1;
} }
if (lnum <= win->w_lines[i].wl_lastlnum) { if (lnum <= win->w_lines[i].wl_foldend) {
return i; return i;
} }
} }

View File

@ -250,7 +250,7 @@ static inline void split_node(MarkTree *b, MTNode *x, const int i, MTKey next)
refkey(b, x, i); refkey(b, x, i);
x->n++; x->n++;
uint32_t meta_inc[4]; uint32_t meta_inc[kMTMetaCount];
meta_describe_key(meta_inc, x->key[i]); meta_describe_key(meta_inc, x->key[i]);
for (int m = 0; m < kMTMetaCount; m++) { for (int m = 0; m < kMTMetaCount; m++) {
// y used contain all of z and x->key[i], discount those // y used contain all of z and x->key[i], discount those
@ -451,6 +451,7 @@ static void meta_describe_key_inc(uint32_t *meta_inc, MTKey *k)
meta_inc[kMTMetaLines] += (k->flags & MT_FLAG_DECOR_VIRT_LINES) ? 1 : 0; meta_inc[kMTMetaLines] += (k->flags & MT_FLAG_DECOR_VIRT_LINES) ? 1 : 0;
meta_inc[kMTMetaSignHL] += (k->flags & MT_FLAG_DECOR_SIGNHL) ? 1 : 0; meta_inc[kMTMetaSignHL] += (k->flags & MT_FLAG_DECOR_SIGNHL) ? 1 : 0;
meta_inc[kMTMetaSignText] += (k->flags & MT_FLAG_DECOR_SIGNTEXT) ? 1 : 0; meta_inc[kMTMetaSignText] += (k->flags & MT_FLAG_DECOR_SIGNTEXT) ? 1 : 0;
meta_inc[kMTMetaConcealLines] += (k->flags & MT_FLAG_DECOR_CONCEAL_LINES) ? 1 : 0;
} }
} }
@ -505,10 +506,10 @@ void marktree_put_key(MarkTree *b, MTKey k)
r = s; r = s;
} }
uint32_t meta_inc[4]; uint32_t meta_inc[kMTMetaCount];
meta_describe_key(meta_inc, k); meta_describe_key(meta_inc, k);
marktree_putp_aux(b, r, k, meta_inc); marktree_putp_aux(b, r, k, meta_inc);
for (int m = 0; m < 4; m++) { for (int m = 0; m < kMTMetaCount; m++) {
b->meta_root[m] += meta_inc[m]; b->meta_root[m] += meta_inc[m];
} }
b->n_keys++; b->n_keys++;
@ -579,7 +580,7 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
assert(x->level == 0); assert(x->level == 0);
MTKey intkey = x->key[itr->i]; MTKey intkey = x->key[itr->i];
uint32_t meta_inc[4]; uint32_t meta_inc[kMTMetaCount];
meta_describe_key(meta_inc, intkey); meta_describe_key(meta_inc, intkey);
if (x->n > itr->i + 1) { if (x->n > itr->i + 1) {
memmove(&x->key[itr->i], &x->key[itr->i + 1], memmove(&x->key[itr->i], &x->key[itr->i + 1],
@ -776,7 +777,7 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
void marktree_revise_meta(MarkTree *b, MarkTreeIter *itr, MTKey old_key) void marktree_revise_meta(MarkTree *b, MarkTreeIter *itr, MTKey old_key)
{ {
uint32_t meta_old[4], meta_new[4]; uint32_t meta_old[kMTMetaCount], meta_new[kMTMetaCount];
meta_describe_key(meta_old, old_key); meta_describe_key(meta_old, old_key);
meta_describe_key(meta_new, rawkey(itr)); meta_describe_key(meta_new, rawkey(itr));
@ -1038,7 +1039,7 @@ static MTNode *merge_node(MarkTree *b, MTNode *p, int i)
relative(p->key[i - 1].pos, &x->key[x->n].pos); relative(p->key[i - 1].pos, &x->key[x->n].pos);
} }
uint32_t meta_inc[4]; uint32_t meta_inc[kMTMetaCount];
meta_describe_key(meta_inc, x->key[x->n]); meta_describe_key(meta_inc, x->key[x->n]);
memmove(&x->key[x->n + 1], y->key, (size_t)y->n * sizeof(MTKey)); memmove(&x->key[x->n + 1], y->key, (size_t)y->n * sizeof(MTKey));
@ -1128,9 +1129,9 @@ static void pivot_right(MarkTree *b, MTPos p_pos, MTNode *p, const int i)
p->key[i] = x->key[x->n - 1]; p->key[i] = x->key[x->n - 1];
refkey(b, p, i); refkey(b, p, i);
uint32_t meta_inc_y[4]; uint32_t meta_inc_y[kMTMetaCount];
meta_describe_key(meta_inc_y, y->key[0]); meta_describe_key(meta_inc_y, y->key[0]);
uint32_t meta_inc_x[4]; uint32_t meta_inc_x[kMTMetaCount];
meta_describe_key(meta_inc_x, p->key[i]); meta_describe_key(meta_inc_x, p->key[i]);
for (int m = 0; m < kMTMetaCount; m++) { for (int m = 0; m < kMTMetaCount; m++) {
@ -1214,9 +1215,9 @@ static void pivot_left(MarkTree *b, MTPos p_pos, MTNode *p, int i)
p->key[i] = y->key[0]; p->key[i] = y->key[0];
refkey(b, p, i); refkey(b, p, i);
uint32_t meta_inc_x[4]; uint32_t meta_inc_x[kMTMetaCount];
meta_describe_key(meta_inc_x, x->key[x->n]); meta_describe_key(meta_inc_x, x->key[x->n]);
uint32_t meta_inc_y[4]; uint32_t meta_inc_y[kMTMetaCount];
meta_describe_key(meta_inc_y, p->key[i]); meta_describe_key(meta_inc_y, p->key[i]);
for (int m = 0; m < kMTMetaCount; m++) { for (int m = 0; m < kMTMetaCount; m++) {
p->meta[i][m] += meta_inc_x[m]; p->meta[i][m] += meta_inc_x[m];
@ -1619,8 +1620,13 @@ bool marktree_itr_next_filter(MarkTree *b, MarkTreeIter *itr, int stop_row, int
return marktree_itr_check_filter(b, itr, stop_row, stop_col, meta_filter); return marktree_itr_check_filter(b, itr, stop_row, stop_col, meta_filter);
} }
const uint32_t meta_map[4] = { MT_FLAG_DECOR_VIRT_TEXT_INLINE, MT_FLAG_DECOR_VIRT_LINES, const uint32_t meta_map[kMTMetaCount] = {
MT_FLAG_DECOR_SIGNHL, MT_FLAG_DECOR_SIGNTEXT }; MT_FLAG_DECOR_VIRT_TEXT_INLINE,
MT_FLAG_DECOR_VIRT_LINES,
MT_FLAG_DECOR_SIGNHL,
MT_FLAG_DECOR_SIGNTEXT,
MT_FLAG_DECOR_CONCEAL_LINES
};
static bool marktree_itr_check_filter(MarkTree *b, MarkTreeIter *itr, int stop_row, int stop_col, static bool marktree_itr_check_filter(MarkTree *b, MarkTreeIter *itr, int stop_row, int stop_col,
MetaFilter meta_filter) MetaFilter meta_filter)
{ {
@ -1860,9 +1866,9 @@ static void swap_keys(MarkTree *b, MarkTreeIter *itr1, MarkTreeIter *itr2, Damag
itr2->i, itr1->i })); itr2->i, itr1->i }));
} }
uint32_t meta_inc_1[4]; uint32_t meta_inc_1[kMTMetaCount];
meta_describe_key(meta_inc_1, rawkey(itr1)); meta_describe_key(meta_inc_1, rawkey(itr1));
uint32_t meta_inc_2[4]; uint32_t meta_inc_2[kMTMetaCount];
meta_describe_key(meta_inc_2, rawkey(itr2)); meta_describe_key(meta_inc_2, rawkey(itr2));
if (memcmp(meta_inc_1, meta_inc_2, sizeof(meta_inc_1)) != 0) { if (memcmp(meta_inc_1, meta_inc_2, sizeof(meta_inc_1)) != 0) {
@ -2373,7 +2379,7 @@ size_t marktree_check_node(MarkTree *b, MTNode *x, MTPos *last, bool *last_right
*last = x->key[x->n - 1].pos; *last = x->key[x->n - 1].pos;
} }
uint32_t meta_node[4]; uint32_t meta_node[kMTMetaCount];
meta_describe_node(meta_node, x); meta_describe_node(meta_node, x);
for (int m = 0; m < kMTMetaCount; m++) { for (int m = 0; m < kMTMetaCount; m++) {
assert(meta_node_ref[m] == meta_node[m]); assert(meta_node_ref[m] == meta_node[m]);

View File

@ -34,6 +34,7 @@
#define MT_FLAG_DECOR_SIGNHL (((uint16_t)1) << 10) #define MT_FLAG_DECOR_SIGNHL (((uint16_t)1) << 10)
#define MT_FLAG_DECOR_VIRT_LINES (((uint16_t)1) << 11) #define MT_FLAG_DECOR_VIRT_LINES (((uint16_t)1) << 11)
#define MT_FLAG_DECOR_VIRT_TEXT_INLINE (((uint16_t)1) << 12) #define MT_FLAG_DECOR_VIRT_TEXT_INLINE (((uint16_t)1) << 12)
#define MT_FLAG_DECOR_CONCEAL_LINES (((uint16_t)1) << 13)
// These _must_ be last to preserve ordering of marks // These _must_ be last to preserve ordering of marks
#define MT_FLAG_RIGHT_GRAVITY (((uint16_t)1) << 14) #define MT_FLAG_RIGHT_GRAVITY (((uint16_t)1) << 14)
@ -43,8 +44,8 @@
| MT_FLAG_DECOR_SIGNHL | MT_FLAG_DECOR_VIRT_LINES \ | MT_FLAG_DECOR_SIGNHL | MT_FLAG_DECOR_VIRT_LINES \
| MT_FLAG_DECOR_VIRT_TEXT_INLINE) | MT_FLAG_DECOR_VIRT_TEXT_INLINE)
#define MT_FLAG_EXTERNAL_MASK (MT_FLAG_DECOR_MASK | MT_FLAG_NO_UNDO \ #define MT_FLAG_EXTERNAL_MASK (MT_FLAG_DECOR_MASK | MT_FLAG_NO_UNDO | MT_FLAG_INVALIDATE \
| MT_FLAG_INVALIDATE | MT_FLAG_INVALID) | MT_FLAG_INVALID | MT_FLAG_DECOR_CONCEAL_LINES)
// this is defined so that start and end of the same range have adjacent ids // this is defined so that start and end of the same range have adjacent ids
#define MARKTREE_END_FLAG ((uint64_t)1) #define MARKTREE_END_FLAG ((uint64_t)1)
@ -108,6 +109,11 @@ static inline bool mt_decor_sign(MTKey key)
return key.flags & (MT_FLAG_DECOR_SIGNTEXT | MT_FLAG_DECOR_SIGNHL); return key.flags & (MT_FLAG_DECOR_SIGNTEXT | MT_FLAG_DECOR_SIGNHL);
} }
static inline bool mt_conceal_lines(MTKey key)
{
return key.flags & MT_FLAG_DECOR_CONCEAL_LINES;
}
static inline uint16_t mt_flags(bool right_gravity, bool no_undo, bool invalidate, bool decor_ext) static inline uint16_t mt_flags(bool right_gravity, bool no_undo, bool invalidate, bool decor_ext)
{ {
return (uint16_t)((right_gravity ? MT_FLAG_RIGHT_GRAVITY : 0) return (uint16_t)((right_gravity ? MT_FLAG_RIGHT_GRAVITY : 0)

View File

@ -22,13 +22,12 @@ typedef struct {
} MTPos; } MTPos;
#define MTPos(r, c) ((MTPos){ .row = (r), .col = (c) }) #define MTPos(r, c) ((MTPos){ .row = (r), .col = (c) })
// Currently there are four counts, which makes for a uint32_t[4] per node
// which makes for nice autovectorization into a single XMM or NEON register
typedef enum { typedef enum {
kMTMetaInline, kMTMetaInline,
kMTMetaLines, kMTMetaLines,
kMTMetaSignHL, kMTMetaSignHL,
kMTMetaSignText, kMTMetaSignText,
kMTMetaConcealLines,
kMTMetaCount, // sentinel, must be last kMTMetaCount, // sentinel, must be last
} MetaIndex; } MetaIndex;

View File

@ -17,6 +17,7 @@
#include "nvim/buffer.h" #include "nvim/buffer.h"
#include "nvim/buffer_defs.h" #include "nvim/buffer_defs.h"
#include "nvim/cursor.h" #include "nvim/cursor.h"
#include "nvim/decoration.h"
#include "nvim/diff.h" #include "nvim/diff.h"
#include "nvim/drawscreen.h" #include "nvim/drawscreen.h"
#include "nvim/edit.h" #include "nvim/edit.h"
@ -318,14 +319,13 @@ void update_topline(win_T *wp)
halfheight = 2; halfheight = 2;
} }
int64_t n; int64_t n;
if (hasAnyFolding(wp)) { if (win_lines_concealed(wp)) {
// Count the number of logical lines between the cursor and // Count the number of logical lines between the cursor and
// topline + p_so (approximation of how much will be // topline + p_so (approximation of how much will be
// scrolled). // scrolled).
n = 0; n = 0;
for (linenr_T lnum = wp->w_cursor.lnum; for (linenr_T lnum = wp->w_cursor.lnum; lnum < wp->w_topline + *so_ptr; lnum++) {
lnum < wp->w_topline + *so_ptr; lnum++) { n += !decor_conceal_line(wp, lnum, false);
n++;
// stop at end of file or when we know we are far off // stop at end of file or when we know we are far off
assert(wp->w_buffer != 0); assert(wp->w_buffer != 0);
if (lnum >= wp->w_buffer->b_ml.ml_line_count || n >= halfheight) { if (lnum >= wp->w_buffer->b_ml.ml_line_count || n >= halfheight) {
@ -367,7 +367,7 @@ void update_topline(win_T *wp)
assert(wp->w_buffer != 0); assert(wp->w_buffer != 0);
if (wp->w_botline <= wp->w_buffer->b_ml.ml_line_count) { if (wp->w_botline <= wp->w_buffer->b_ml.ml_line_count) {
if (wp->w_cursor.lnum < wp->w_botline) { if (wp->w_cursor.lnum < wp->w_botline) {
if ((wp->w_cursor.lnum >= wp->w_botline - *so_ptr || hasAnyFolding(wp))) { if ((wp->w_cursor.lnum >= wp->w_botline - *so_ptr || win_lines_concealed(wp))) {
lineoff_T loff; lineoff_T loff;
// Cursor is (a few lines) above botline, check if there are // Cursor is (a few lines) above botline, check if there are
@ -399,13 +399,12 @@ void update_topline(win_T *wp)
} }
if (check_botline) { if (check_botline) {
int line_count = 0; int line_count = 0;
if (hasAnyFolding(wp)) { if (win_lines_concealed(wp)) {
// Count the number of logical lines between the cursor and // Count the number of logical lines between the cursor and
// botline - p_so (approximation of how much will be // botline - p_so (approximation of how much will be
// scrolled). // scrolled).
for (linenr_T lnum = wp->w_cursor.lnum; for (linenr_T lnum = wp->w_cursor.lnum; lnum >= wp->w_botline - *so_ptr; lnum--) {
lnum >= wp->w_botline - *so_ptr; lnum--) { line_count += !decor_conceal_line(wp, lnum - 1, false);
line_count++;
// stop at end of file or when we know we are far off // stop at end of file or when we know we are far off
if (lnum <= 0 || line_count > wp->w_height_inner + 1) { if (lnum <= 0 || line_count > wp->w_height_inner + 1) {
break; break;
@ -462,7 +461,7 @@ static int scrolljump_value(win_T *wp)
static bool check_top_offset(win_T *wp) static bool check_top_offset(win_T *wp)
{ {
int so = get_scrolloff_value(wp); int so = get_scrolloff_value(wp);
if (wp->w_cursor.lnum < wp->w_topline + so || hasAnyFolding(wp)) { if (wp->w_cursor.lnum < wp->w_topline + so || win_lines_concealed(wp)) {
lineoff_T loff; lineoff_T loff;
loff.lnum = wp->w_cursor.lnum; loff.lnum = wp->w_cursor.lnum;
loff.fill = 0; loff.fill = 0;
@ -506,6 +505,13 @@ void check_cursor_moved(win_T *wp)
if (wp->w_cursor.lnum != wp->w_valid_cursor.lnum) { if (wp->w_cursor.lnum != wp->w_valid_cursor.lnum) {
wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL
|VALID_CHEIGHT|VALID_CROW|VALID_TOPLINE); |VALID_CHEIGHT|VALID_CROW|VALID_TOPLINE);
// Concealed line visibility toggled.
if (wp->w_p_cole >= 2 && !conceal_cursor_line(wp)
&& (decor_conceal_line(wp, wp->w_cursor.lnum - 1, true)
|| decor_conceal_line(wp, wp->w_valid_cursor.lnum - 1, true))) {
changed_window_setting(wp);
}
wp->w_valid_cursor = wp->w_cursor; wp->w_valid_cursor = wp->w_cursor;
wp->w_valid_leftcol = wp->w_leftcol; wp->w_valid_leftcol = wp->w_leftcol;
wp->w_valid_skipcol = wp->w_skipcol; wp->w_valid_skipcol = wp->w_skipcol;
@ -1341,13 +1347,14 @@ bool scrolldown(win_T *wp, linenr_T line_count, int byfold)
// A sequence of folded lines only counts for one logical line // A sequence of folded lines only counts for one logical line
linenr_T first; linenr_T first;
if (hasFolding(wp, wp->w_topline, &first, NULL)) { if (hasFolding(wp, wp->w_topline, &first, NULL)) {
done++; done += !decor_conceal_line(wp, first - 1, false);
if (!byfold) { if (!byfold) {
todo -= wp->w_topline - first - 1; todo -= wp->w_topline - first - 1;
} }
wp->w_botline -= wp->w_topline - first; wp->w_botline -= wp->w_topline - first;
wp->w_topline = first; wp->w_topline = first;
} else { } else {
todo += decor_conceal_line(wp, wp->w_topline - 1, false);
if (do_sms) { if (do_sms) {
int size = win_linetabsize(wp, wp->w_topline, int size = win_linetabsize(wp, wp->w_topline,
ml_get_buf(wp->w_buffer, wp->w_topline), MAXCOL); ml_get_buf(wp->w_buffer, wp->w_topline), MAXCOL);
@ -1391,12 +1398,8 @@ bool scrolldown(win_T *wp, linenr_T line_count, int byfold)
while (wrow >= wp->w_height_inner && wp->w_cursor.lnum > 1) { while (wrow >= wp->w_height_inner && wp->w_cursor.lnum > 1) {
linenr_T first; linenr_T first;
if (hasFolding(wp, wp->w_cursor.lnum, &first, NULL)) { if (hasFolding(wp, wp->w_cursor.lnum, &first, NULL)) {
wrow--; wrow -= !decor_conceal_line(wp, wp->w_cursor.lnum - 1, false);
if (first == 1) { wp->w_cursor.lnum = MAX(first - 1, 1);
wp->w_cursor.lnum = 1;
} else {
wp->w_cursor.lnum = first - 1;
}
} else { } else {
wrow -= plines_win(wp, wp->w_cursor.lnum--, true); wrow -= plines_win(wp, wp->w_cursor.lnum--, true);
} }
@ -1424,7 +1427,7 @@ bool scrollup(win_T *wp, linenr_T line_count, bool byfold)
linenr_T botline = wp->w_botline; linenr_T botline = wp->w_botline;
bool do_sms = wp->w_p_wrap && wp->w_p_sms; bool do_sms = wp->w_p_wrap && wp->w_p_sms;
if (do_sms || (byfold && hasAnyFolding(wp)) || win_may_fill(wp)) { if (do_sms || (byfold && win_lines_concealed(wp)) || win_may_fill(wp)) {
int width1 = wp->w_width_inner - win_col_off(wp); int width1 = wp->w_width_inner - win_col_off(wp);
int width2 = width1 + win_col_off2(wp); int width2 = width1 + win_col_off2(wp);
int size = 0; int size = 0;
@ -1439,6 +1442,7 @@ bool scrollup(win_T *wp, linenr_T line_count, bool byfold)
// the line, then advance to the next line. // the line, then advance to the next line.
// folding: count each sequence of folded lines as one logical line. // folding: count each sequence of folded lines as one logical line.
for (int todo = line_count; todo > 0; todo--) { for (int todo = line_count; todo > 0; todo--) {
todo += decor_conceal_line(wp, wp->w_topline - 1, false);
if (wp->w_topfill > 0) { if (wp->w_topfill > 0) {
wp->w_topfill--; wp->w_topfill--;
} else { } else {
@ -1495,10 +1499,8 @@ bool scrollup(win_T *wp, linenr_T line_count, bool byfold)
check_topfill(wp, false); check_topfill(wp, false);
if (hasAnyFolding(wp)) { // Make sure w_topline is at the first of a sequence of folded lines.
// Make sure w_topline is at the first of a sequence of folded lines. hasFolding(wp, wp->w_topline, &wp->w_topline, NULL);
hasFolding(wp, wp->w_topline, &wp->w_topline, NULL);
}
wp->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE); wp->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE);
if (wp->w_cursor.lnum < wp->w_topline) { if (wp->w_cursor.lnum < wp->w_topline) {
@ -1702,8 +1704,8 @@ static void topline_back_winheight(win_T *wp, lineoff_T *lp, int winheight)
if (lp->lnum < 1) { if (lp->lnum < 1) {
lp->height = MAXCOL; lp->height = MAXCOL;
} else if (hasFolding(wp, lp->lnum, &lp->lnum, NULL)) { } else if (hasFolding(wp, lp->lnum, &lp->lnum, NULL)) {
// Add a closed fold // Add a closed fold unless concealed.
lp->height = 1; lp->height = !decor_conceal_line(wp, lp->lnum - 1, false);
} else { } else {
lp->height = plines_win_nofill(wp, lp->lnum, winheight); lp->height = plines_win_nofill(wp, lp->lnum, winheight);
} }
@ -1732,8 +1734,8 @@ static void botline_forw(win_T *wp, lineoff_T *lp)
if (lp->lnum > wp->w_buffer->b_ml.ml_line_count) { if (lp->lnum > wp->w_buffer->b_ml.ml_line_count) {
lp->height = MAXCOL; lp->height = MAXCOL;
} else if (hasFolding(wp, lp->lnum, NULL, &lp->lnum)) { } else if (hasFolding(wp, lp->lnum, NULL, &lp->lnum)) {
// Add a closed fold // Add a closed fold unless concealed.
lp->height = 1; lp->height = !decor_conceal_line(wp, lp->lnum - 1, false);
} else { } else {
lp->height = plines_win_nofill(wp, lp->lnum, true); lp->height = plines_win_nofill(wp, lp->lnum, true);
} }
@ -1785,9 +1787,8 @@ void scroll_cursor_top(win_T *wp, int min_scroll, int always)
// Check if the lines from "top" to "bot" fit in the window. If they do, // Check if the lines from "top" to "bot" fit in the window. If they do,
// set new_topline and advance "top" and "bot" to include more lines. // set new_topline and advance "top" and "bot" to include more lines.
while (top > 0) { while (top > 0) {
int i = hasFolding(wp, top, &top, NULL) int i = plines_win_nofill(wp, top, true);
? 1 // count one logical line for a sequence of folded lines hasFolding(wp, top, &top, NULL);
: plines_win_nofill(wp, top, true);
if (top < wp->w_topline) { if (top < wp->w_topline) {
scrolled += i; scrolled += i;
} }
@ -1799,12 +1800,7 @@ void scroll_cursor_top(win_T *wp, int min_scroll, int always)
used += i; used += i;
if (extra + i <= off && bot < wp->w_buffer->b_ml.ml_line_count) { if (extra + i <= off && bot < wp->w_buffer->b_ml.ml_line_count) {
if (hasFolding(wp, bot, NULL, &bot)) { used += plines_win_full(wp, bot, &bot, NULL, true, true);
// count one logical line for a sequence of folded lines
used++;
} else {
used += plines_win(wp, bot, true);
}
} }
if (used > wp->w_height_inner) { if (used > wp->w_height_inner) {
break; break;
@ -2265,7 +2261,7 @@ void cursor_correct(win_T *wp)
linenr_T cln = wp->w_cursor.lnum; // Cursor Line Number linenr_T cln = wp->w_cursor.lnum; // Cursor Line Number
if (cln >= wp->w_topline + above_wanted if (cln >= wp->w_topline + above_wanted
&& cln < wp->w_botline - below_wanted && cln < wp->w_botline - below_wanted
&& !hasAnyFolding(wp)) { && !win_lines_concealed(wp)) {
return; return;
} }
@ -2290,19 +2286,12 @@ void cursor_correct(win_T *wp)
int below = wp->w_filler_rows; // screen lines below botline int below = wp->w_filler_rows; // screen lines below botline
while ((above < above_wanted || below < below_wanted) && topline < botline) { while ((above < above_wanted || below < below_wanted) && topline < botline) {
if (below < below_wanted && (below <= above || above >= above_wanted)) { if (below < below_wanted && (below <= above || above >= above_wanted)) {
if (hasFolding(wp, botline, &botline, NULL)) { below += plines_win_full(wp, botline, &botline, NULL, true, true);
below++;
} else {
below += plines_win(wp, botline, true);
}
botline--; botline--;
} }
if (above < above_wanted && (above < below || below >= below_wanted)) { if (above < above_wanted && (above < below || below >= below_wanted)) {
if (hasFolding(wp, topline, NULL, &topline)) { above += plines_win_nofill(wp, topline, true);
above++; hasFolding(wp, topline, NULL, &topline);
} else {
above += plines_win_nofill(wp, topline, true);
}
// Count filler lines below this line as context. // Count filler lines below this line as context.
if (topline < botline) { if (topline < botline) {
@ -2456,7 +2445,8 @@ int pagescroll(Direction dir, int count, bool half)
int curscount = count; int curscount = count;
// Adjust count so as to not reveal end of buffer lines. // Adjust count so as to not reveal end of buffer lines.
if (dir == FORWARD if (dir == FORWARD
&& (curwin->w_topline + curwin->w_height_inner + count > buflen || hasAnyFolding(curwin))) { && (curwin->w_topline + curwin->w_height_inner + count > buflen
|| win_lines_concealed(curwin))) {
int n = plines_correct_topline(curwin, curwin->w_topline, NULL, false, NULL); int n = plines_correct_topline(curwin, curwin->w_topline, NULL, false, NULL);
if (n - count < curwin->w_height_inner && curwin->w_topline < buflen) { if (n - count < curwin->w_height_inner && curwin->w_topline < buflen) {
n += plines_m_win(curwin, curwin->w_topline + 1, buflen, curwin->w_height_inner + count); n += plines_m_win(curwin, curwin->w_topline + 1, buflen, curwin->w_height_inner + count);
@ -2474,13 +2464,14 @@ int pagescroll(Direction dir, int count, bool half)
curwin->w_curswant = prev_curswant; curwin->w_curswant = prev_curswant;
} }
// Move the cursor the same amount of screen lines. // Move the cursor the same amount of screen lines, skipping over
// concealed lines as those were not included in "curscount".
if (curwin->w_p_wrap) { if (curwin->w_p_wrap) {
nv_screengo(&oa, dir, curscount); nv_screengo(&oa, dir, curscount, true);
} else if (dir == FORWARD) { } else if (dir == FORWARD) {
cursor_down_inner(curwin, curscount); cursor_down_inner(curwin, curscount, true);
} else { } else {
cursor_up_inner(curwin, curscount); cursor_up_inner(curwin, curscount, true);
} }
} else { } else {
// Scroll [count] times 'window' or current window height lines. // Scroll [count] times 'window' or current window height lines.

View File

@ -24,6 +24,7 @@
#include "nvim/charset.h" #include "nvim/charset.h"
#include "nvim/cmdhist.h" #include "nvim/cmdhist.h"
#include "nvim/cursor.h" #include "nvim/cursor.h"
#include "nvim/decoration.h"
#include "nvim/diff.h" #include "nvim/diff.h"
#include "nvim/digraph.h" #include "nvim/digraph.h"
#include "nvim/drawscreen.h" #include "nvim/drawscreen.h"
@ -2486,7 +2487,7 @@ bool find_decl(char *ptr, size_t len, bool locally, bool thisblock, int flags_ar
/// 'dist' must be positive. /// 'dist' must be positive.
/// ///
/// @return true if able to move cursor, false otherwise. /// @return true if able to move cursor, false otherwise.
bool nv_screengo(oparg_T *oap, int dir, int dist) bool nv_screengo(oparg_T *oap, int dir, int dist, bool skip_conceal)
{ {
int linelen = linetabsize(curwin, curwin->w_cursor.lnum); int linelen = linetabsize(curwin, curwin->w_cursor.lnum);
bool retval = true; bool retval = true;
@ -2548,7 +2549,7 @@ bool nv_screengo(oparg_T *oap, int dir, int dist)
retval = false; retval = false;
break; break;
} }
cursor_up_inner(curwin, 1); cursor_up_inner(curwin, 1, skip_conceal);
linelen = linetabsize(curwin, curwin->w_cursor.lnum); linelen = linetabsize(curwin, curwin->w_cursor.lnum);
if (linelen > width1) { if (linelen > width1) {
@ -2573,7 +2574,7 @@ bool nv_screengo(oparg_T *oap, int dir, int dist)
retval = false; retval = false;
break; break;
} }
cursor_down_inner(curwin, 1); cursor_down_inner(curwin, 1, skip_conceal);
curwin->w_curswant %= width2; curwin->w_curswant %= width2;
// Check if the cursor has moved below the number display // Check if the cursor has moved below the number display
@ -3616,12 +3617,11 @@ static void nv_scroll(cmdarg_T *cap)
if (cap->count1 - 1 >= curwin->w_cursor.lnum) { if (cap->count1 - 1 >= curwin->w_cursor.lnum) {
curwin->w_cursor.lnum = 1; curwin->w_cursor.lnum = 1;
} else { } else {
if (hasAnyFolding(curwin)) { if (win_lines_concealed(curwin)) {
// Count a fold for one screen line. // Count a fold for one screen line.
for (n = cap->count1 - 1; n > 0 for (n = cap->count1 - 1; n > 0 && curwin->w_cursor.lnum > curwin->w_topline; n--) {
&& curwin->w_cursor.lnum > curwin->w_topline; n--) { hasFolding(curwin, curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL);
hasFolding(curwin, curwin->w_cursor.lnum, n += decor_conceal_line(curwin, curwin->w_cursor.lnum, true);
&curwin->w_cursor.lnum, NULL);
if (curwin->w_cursor.lnum > curwin->w_topline) { if (curwin->w_cursor.lnum > curwin->w_topline) {
curwin->w_cursor.lnum--; curwin->w_cursor.lnum--;
} }
@ -3634,8 +3634,7 @@ static void nv_scroll(cmdarg_T *cap)
if (cap->cmdchar == 'M') { if (cap->cmdchar == 'M') {
int used = 0; int used = 0;
// Don't count filler lines above the window. // Don't count filler lines above the window.
used -= win_get_fill(curwin, curwin->w_topline) used -= win_get_fill(curwin, curwin->w_topline) - curwin->w_topfill;
- curwin->w_topfill;
validate_botline(curwin); // make sure w_empty_rows is valid validate_botline(curwin); // make sure w_empty_rows is valid
int half = (curwin->w_height_inner - curwin->w_empty_rows + 1) / 2; int half = (curwin->w_height_inner - curwin->w_empty_rows + 1) / 2;
for (n = 0; curwin->w_topline + n < curbuf->b_ml.ml_line_count; n++) { for (n = 0; curwin->w_topline + n < curbuf->b_ml.ml_line_count; n++) {
@ -3658,10 +3657,11 @@ static void nv_scroll(cmdarg_T *cap)
} }
} else { // (cap->cmdchar == 'H') } else { // (cap->cmdchar == 'H')
n = cap->count1 - 1; n = cap->count1 - 1;
if (hasAnyFolding(curwin)) { if (win_lines_concealed(curwin)) {
// Count a fold for one screen line. // Count a fold for one screen line.
lnum = curwin->w_topline; lnum = curwin->w_topline;
while (n-- > 0 && lnum < curwin->w_botline - 1) { while ((decor_conceal_line(curwin, lnum - 1, true) || n-- > 0)
&& lnum < curwin->w_botline - 1) {
hasFolding(curwin, lnum, NULL, &lnum); hasFolding(curwin, lnum, NULL, &lnum);
lnum++; lnum++;
} }
@ -5313,7 +5313,7 @@ static void nv_g_dollar_cmd(cmdarg_T *cap)
curwin->w_cursor.col--; curwin->w_cursor.col--;
} }
} }
} else if (nv_screengo(oap, FORWARD, cap->count1 - 1) == false) { } else if (nv_screengo(oap, FORWARD, cap->count1 - 1, false) == false) {
clearopbeep(oap); clearopbeep(oap);
} }
} else { } else {
@ -5440,7 +5440,7 @@ static void nv_g_cmd(cmdarg_T *cap)
oap->motion_type = kMTLineWise; oap->motion_type = kMTLineWise;
i = cursor_down(cap->count1, oap->op_type == OP_NOP); i = cursor_down(cap->count1, oap->op_type == OP_NOP);
} else { } else {
i = nv_screengo(oap, FORWARD, cap->count1); i = nv_screengo(oap, FORWARD, cap->count1, false);
} }
if (!i) { if (!i) {
clearopbeep(oap); clearopbeep(oap);
@ -5454,7 +5454,7 @@ static void nv_g_cmd(cmdarg_T *cap)
oap->motion_type = kMTLineWise; oap->motion_type = kMTLineWise;
i = cursor_up(cap->count1, oap->op_type == OP_NOP); i = cursor_up(cap->count1, oap->op_type == OP_NOP);
} else { } else {
i = nv_screengo(oap, BACKWARD, cap->count1); i = nv_screengo(oap, BACKWARD, cap->count1, false);
} }
if (!i) { if (!i) {
clearopbeep(oap); clearopbeep(oap);

View File

@ -80,7 +80,7 @@ int linetabsize(win_T *wp, linenr_T lnum)
return win_linetabsize(wp, lnum, ml_get_buf(wp->w_buffer, lnum), MAXCOL); return win_linetabsize(wp, lnum, ml_get_buf(wp->w_buffer, lnum), MAXCOL);
} }
static const uint32_t inline_filter[4] = {[kMTMetaInline] = kMTFilterSelect }; static const uint32_t inline_filter[kMTMetaCount] = {[kMTMetaInline] = kMTFilterSelect };
/// Prepare the structure passed to charsize functions. /// Prepare the structure passed to charsize functions.
/// ///
@ -749,6 +749,10 @@ int plines_win(win_T *wp, linenr_T lnum, bool limit_winheight)
/// @param limit_winheight when true limit to window height /// @param limit_winheight when true limit to window height
int plines_win_nofill(win_T *wp, linenr_T lnum, bool limit_winheight) int plines_win_nofill(win_T *wp, linenr_T lnum, bool limit_winheight)
{ {
if (decor_conceal_line(wp, lnum - 1, false)) {
return 0;
}
if (!wp->w_p_wrap) { if (!wp->w_p_wrap) {
return 1; return 1;
} }
@ -885,6 +889,11 @@ int plines_win_full(win_T *wp, linenr_T lnum, linenr_T *const nextp, bool *const
if (foldedp != NULL) { if (foldedp != NULL) {
*foldedp = folded; *foldedp = folded;
} }
if (decor_conceal_line(wp, lnum - 1, false)) {
return 0;
}
return ((folded ? 1 : plines_win_nofill(wp, lnum, limit_winheight)) + return ((folded ? 1 : plines_win_nofill(wp, lnum, limit_winheight)) +
(lnum == wp->w_topline ? wp->w_topfill : win_get_fill(wp, lnum))); (lnum == wp->w_topline ? wp->w_topfill : win_get_fill(wp, lnum)));
} }
@ -963,8 +972,8 @@ int64_t win_text_height(win_T *const wp, const linenr_T start_lnum, const int64_
if (start_vcol >= 0) { if (start_vcol >= 0) {
linenr_T lnum_next = lnum; linenr_T lnum_next = lnum;
const bool folded = hasFolding(wp, lnum, &lnum, &lnum_next); hasFolding(wp, lnum, &lnum, &lnum_next);
height_cur_nofill = folded ? 1 : plines_win_nofill(wp, lnum, false); height_cur_nofill = plines_win_nofill(wp, lnum, false);
height_sum_nofill += height_cur_nofill; height_sum_nofill += height_cur_nofill;
const int64_t row_off = (start_vcol < width1 || width2 <= 0) const int64_t row_off = (start_vcol < width1 || width2 <= 0)
? 0 ? 0
@ -975,9 +984,9 @@ int64_t win_text_height(win_T *const wp, const linenr_T start_lnum, const int64_
while (lnum <= end_lnum) { while (lnum <= end_lnum) {
linenr_T lnum_next = lnum; linenr_T lnum_next = lnum;
const bool folded = hasFolding(wp, lnum, &lnum, &lnum_next); hasFolding(wp, lnum, &lnum, &lnum_next);
height_sum_fill += win_get_fill(wp, lnum); height_sum_fill += win_get_fill(wp, lnum);
height_cur_nofill = folded ? 1 : plines_win_nofill(wp, lnum, false); height_cur_nofill = plines_win_nofill(wp, lnum, false);
height_sum_nofill += height_cur_nofill; height_sum_nofill += height_cur_nofill;
lnum = lnum_next + 1; lnum = lnum_next + 1;
} }

View File

@ -6483,9 +6483,9 @@ void win_fix_scroll(bool resize)
// Add difference in height and row to botline. // Add difference in height and row to botline.
if (diff > 0) { if (diff > 0) {
cursor_down_inner(wp, diff); cursor_down_inner(wp, diff, false);
} else { } else {
cursor_up_inner(wp, -diff); cursor_up_inner(wp, -diff, false);
} }
// Scroll to put the new cursor position at the bottom of the // Scroll to put the new cursor position at the bottom of the
@ -6532,11 +6532,11 @@ static void win_fix_cursor(bool normal)
linenr_T lnum = wp->w_cursor.lnum; linenr_T lnum = wp->w_cursor.lnum;
wp->w_cursor.lnum = wp->w_topline; wp->w_cursor.lnum = wp->w_topline;
cursor_down_inner(wp, so); cursor_down_inner(wp, so, false);
linenr_T top = wp->w_cursor.lnum; linenr_T top = wp->w_cursor.lnum;
wp->w_cursor.lnum = wp->w_botline - 1; wp->w_cursor.lnum = wp->w_botline - 1;
cursor_up_inner(wp, so); cursor_up_inner(wp, so, false);
linenr_T bot = wp->w_cursor.lnum; linenr_T bot = wp->w_cursor.lnum;
wp->w_cursor.lnum = lnum; wp->w_cursor.lnum = lnum;
@ -6630,7 +6630,7 @@ void scroll_to_fraction(win_T *wp, int prev_height)
hasFolding(wp, lnum, &lnum, NULL); hasFolding(wp, lnum, &lnum, NULL);
if (lnum == 1) { if (lnum == 1) {
// first line in buffer is folded // first line in buffer is folded
line_size = 1; line_size = !decor_conceal_line(wp, lnum - 1, false);
sline--; sline--;
break; break;
} }

View File

@ -1552,6 +1552,7 @@ describe('API/extmarks', function()
it('can get details', function() it('can get details', function()
set_extmark(ns, marks[1], 0, 0, { set_extmark(ns, marks[1], 0, 0, {
conceal = 'c', conceal = 'c',
conceal_lines = '',
cursorline_hl_group = 'Statement', cursorline_hl_group = 'Statement',
end_col = 0, end_col = 0,
end_right_gravity = true, end_right_gravity = true,
@ -1586,6 +1587,7 @@ describe('API/extmarks', function()
0, 0,
{ {
conceal = 'c', conceal = 'c',
conceal_lines = '',
cursorline_hl_group = 'Statement', cursorline_hl_group = 'Statement',
end_col = 0, end_col = 0,
end_right_gravity = true, end_right_gravity = true,

View File

@ -2492,6 +2492,209 @@ describe('extmark decorations', function()
| |
]]) ]])
end) end)
it ('conceal_lines', function()
insert(example_text)
exec('set number conceallevel=3')
feed('ggj')
local not_concealed = {
grid = [[
{2: 1 }for _,item in ipairs(items) do |
{2: 2 }^ local text, hl_id_cell, count = unpack(ite|
{2: }m) |
{2: 3 } if hl_id_cell ~= nil then |
{2: 4 } hl_id = hl_id_cell |
{2: 5 } end |
{2: 6 } for _ = 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|
|
]]
}
screen:expect(not_concealed)
api.nvim_buf_set_extmark(0, ns, 1, 0, { conceal_lines = "" })
screen:expect_unchanged()
feed('j')
local concealed = {
grid = [[
{2: 1 }for _,item in ipairs(items) do |
{2: 3 }^ if hl_id_cell ~= nil then |
{2: 4 } hl_id = hl_id_cell |
{2: 5 } end |
{2: 6 } for _ = 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|*3
|
]]
}
screen:expect(concealed)
feed('k')
screen:expect(not_concealed)
exec('set concealcursor=n')
screen:expect(concealed)
api.nvim_buf_set_extmark(0, ns, 3, 0, { conceal_lines = "" })
screen:expect({
grid = [[
{2: 1 }for _,item in ipairs(items) do |
{2: 3 }^ if hl_id_cell ~= nil then |
{2: 5 } end |
{2: 6 } for _ = 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|*4
|
]]
})
feed('kjj')
screen:expect_unchanged()
api.nvim_buf_set_extmark(0, ns, 4, 0, { conceal_lines = "" })
feed('kjjjC')
screen:expect({
grid = [[
{2: 1 }for _,item in ipairs(items) do |
{2: 3 } if hl_id_cell ~= nil then |
{2: 5 }^ |
{2: 6 } for _ = 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|*4
{24:-- INSERT --} |
]]
})
feed('<esc>')
screen:expect({
grid = [[
{2: 1 }for _,item in ipairs(items) do |
{2: 3 } if hl_id_cell ~= nil then |
{2: 6 }^ for _ = 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|*5
|
]]
})
feed('kji')
screen:expect({
grid = [[
{2: 1 }for _,item in ipairs(items) do |
{2: 3 } if hl_id_cell ~= nil then |
{2: 5 }^ |
{2: 6 } for _ = 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|*4
{24:-- INSERT --} |
]]
})
feed('conceal text')
screen:expect({
grid = [[
{2: 1 }for _,item in ipairs(items) do |
{2: 3 } if hl_id_cell ~= nil then |
{2: 5 }conceal text^ |
{2: 6 } for _ = 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|*4
{24:-- INSERT --} |
]]
})
feed('<esc>')
screen:expect({
grid = [[
{2: 1 }for _,item in ipairs(items) do |
{2: 3 } if hl_id_cell ~= nil then |
{2: 6 } for _ =^ 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|*5
|
]]
})
feed('ggzfj')
screen:expect({
grid = [[
{2: 1 }{33:^+-- 2 lines: for _,item in ipairs(items) do··}|
{2: 3 } if hl_id_cell ~= nil then |
{2: 6 } for _ = 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|*5
|
]]
})
feed('j')
screen:expect({
grid = [[
{2: 1 }{33:+-- 2 lines: for _,item in ipairs(items) do··}|
{2: 3 }^ if hl_id_cell ~= nil then |
{2: 6 } for _ = 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|*5
|
]]
})
feed('ggzdjzfj')
screen:expect({
grid = [[
{2: 1 }for _,item in ipairs(items) do |
{2: 6 }^ for _ = 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|*6
|
]]
})
feed('jj')
screen:expect_unchanged()
end)
end) end)
describe('decorations: inline virtual text', function() describe('decorations: inline virtual text', function()