From a9c551e5e38f484e9055a930b3feaa9ac65d07be Mon Sep 17 00:00:00 2001 From: VanaIgr Date: Wed, 13 Dec 2023 13:25:48 -0600 Subject: perf: cache breakindent/showbreak width in win_lbr_chartabsize breakindent was recomputed on every call to win_lbr_charbabsize() when the character is past the end of the first row of a wrapped line. Even though the function for computing breakindent cached the last result, reusing the cached value required strcmp of the cached line with the given line. --- src/nvim/plines.c | 64 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 27 deletions(-) (limited to 'src/nvim/plines.c') diff --git a/src/nvim/plines.c b/src/nvim/plines.c index c2cf3796a7..48c43e155e 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -109,8 +109,8 @@ void win_linetabsize_cts(chartabsize_T *cts, colnr_T len) cts->cts_vcol += win_lbr_chartabsize(cts, NULL); } // check for inline virtual text after the end of the line - if (len == MAXCOL && cts->cts_has_virt_text && *cts->cts_ptr == NUL) { - win_lbr_chartabsize(cts, NULL); + if (len == MAXCOL && cts->virt_row >= 0 && *cts->cts_ptr == NUL) { + (void)win_lbr_chartabsize(cts, NULL); cts->cts_vcol += cts->cts_cur_text_width_left + cts->cts_cur_text_width_right; } } @@ -129,14 +129,14 @@ void init_chartabsize_arg(chartabsize_T *cts, win_T *wp, linenr_T lnum, colnr_T cts->cts_max_head_vcol = 0; cts->cts_cur_text_width_left = 0; cts->cts_cur_text_width_right = 0; - cts->cts_has_virt_text = false; - cts->cts_row = lnum - 1; + cts->virt_row = -1; + cts->indent_width = INT_MIN; - if (cts->cts_row >= 0 && wp->w_buffer->b_virt_text_inline > 0) { - marktree_itr_get(wp->w_buffer->b_marktree, cts->cts_row, 0, cts->cts_iter); + if (lnum > 0 && wp->w_buffer->b_virt_text_inline > 0) { + marktree_itr_get(wp->w_buffer->b_marktree, lnum - 1, 0, cts->cts_iter); MTKey mark = marktree_itr_current(cts->cts_iter); - if (mark.pos.row == cts->cts_row) { - cts->cts_has_virt_text = true; + if (mark.pos.row == lnum - 1) { + cts->virt_row = lnum - 1; } } } @@ -154,7 +154,7 @@ void clear_chartabsize_arg(chartabsize_T *cts) int lbr_chartabsize(chartabsize_T *cts) { if (!curwin->w_p_lbr && *get_showbreak_value(curwin) == NUL - && !curwin->w_p_bri && !cts->cts_has_virt_text) { + && !curwin->w_p_bri && cts->virt_row < 0) { if (curwin->w_p_wrap) { return win_nolbr_chartabsize(cts, NULL); } @@ -199,9 +199,11 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) cts->cts_cur_text_width_left = 0; cts->cts_cur_text_width_right = 0; + char *const sbr = get_showbreak_value(wp); + // No 'linebreak', 'showbreak' and 'breakindent': return quickly. - if (!wp->w_p_lbr && !wp->w_p_bri && *get_showbreak_value(wp) == NUL - && !cts->cts_has_virt_text) { + if (!wp->w_p_lbr && !wp->w_p_bri && *sbr == NUL + && cts->virt_row < 0) { if (wp->w_p_wrap) { return win_nolbr_chartabsize(cts, headp); } @@ -217,12 +219,12 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) } bool is_doublewidth = size == 2 && MB_BYTE2LEN((uint8_t)(*s)) > 1; - if (cts->cts_has_virt_text) { + if (cts->virt_row >= 0) { int tab_size = size; int col = (int)(s - line); while (true) { MTKey mark = marktree_itr_current(cts->cts_iter); - if (mark.pos.row != cts->cts_row || mark.pos.col > col) { + if (mark.pos.row != cts->virt_row || mark.pos.col > col) { break; } else if (mark.pos.col == col) { if (!mt_end(mark) && mark.flags & (MT_FLAG_DECOR_VIRT_TEXT_INLINE)) { @@ -260,7 +262,6 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) // May have to add something for 'breakindent' and/or 'showbreak' // string at the start of a screen line. int head = mb_added; - char *const sbr = get_showbreak_value(wp); // When "size" is 0, no new screen line is started. if (size > 0 && wp->w_p_wrap && (*sbr != NUL || wp->w_p_bri)) { int col_off_prev = win_col_off(wp); @@ -277,11 +278,16 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) if (wcol >= width2 && width2 > 0) { wcol %= width2; } - if (*sbr != NUL) { - head_prev += vim_strsize(sbr); - } - if (wp->w_p_bri) { - head_prev += get_breakindent_win(wp, line); + head_prev = cts->indent_width; + if (head_prev == INT_MIN) { + head_prev = 0; + if (*sbr != NUL) { + head_prev += vim_strsize(sbr); + } + if (wp->w_p_bri) { + head_prev += get_breakindent_win(wp, line); + } + cts->indent_width = head_prev; } if (wcol < head_prev) { head_prev -= wcol; @@ -298,12 +304,16 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) if (wcol + size > wp->w_width) { // cells taken by 'showbreak'/'breakindent' halfway current char - int head_mid = 0; - if (*sbr != NUL) { - head_mid += vim_strsize(sbr); - } - if (wp->w_p_bri) { - head_mid += get_breakindent_win(wp, line); + int head_mid = cts->indent_width; + if (head_mid == INT_MIN) { + head_mid = 0; + if (*sbr != NUL) { + head_mid += vim_strsize(sbr); + } + if (wp->w_p_bri) { + head_mid += get_breakindent_win(wp, line); + } + cts->indent_width = head_mid; } if (head_mid > 0 && wcol + size > wp->w_width_inner) { // Calculate effective window width. @@ -520,7 +530,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en && !wp->w_p_lbr && *get_showbreak_value(wp) == NUL && !wp->w_p_bri - && !cts.cts_has_virt_text) { + && cts.virt_row < 0) { while (true) { head = 0; int c = (uint8_t)(*ptr); @@ -800,7 +810,7 @@ int plines_win_nofold(win_T *wp, linenr_T lnum) char *s = ml_get_buf(wp->w_buffer, lnum); chartabsize_T cts; init_chartabsize_arg(&cts, wp, lnum, 0, s, s); - if (*s == NUL && !cts.cts_has_virt_text) { + if (*s == NUL && cts.virt_row < 0) { return 1; // be quick for an empty line } win_linetabsize_cts(&cts, (colnr_T)MAXCOL); -- cgit From 2f2f12122f3883dc6faf25751e176646f044db77 Mon Sep 17 00:00:00 2001 From: VanaIgr Date: Wed, 13 Dec 2023 13:54:28 -0600 Subject: perf: remove loop for computing last position in getvcol() The function computed the start of the multibyte character and iterated until the current character reached this position. The loop at the start of the function handed the case where the index passed to the function was past the end of the line. This loop can be removed if the function instead compares the next position against the end position end exits the loop if it is greater than the end position. --- src/nvim/plines.c | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) (limited to 'src/nvim/plines.c') diff --git a/src/nvim/plines.c b/src/nvim/plines.c index 48c43e155e..13b580b142 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -493,7 +493,6 @@ static int virt_text_cursor_off(chartabsize_T *cts, bool on_NUL) void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end) { char *ptr; // points to current char - char *posptr; // points to char at pos->col int incr; int head; colnr_T *vts = wp->w_buffer->b_p_vts_array; @@ -502,21 +501,10 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en colnr_T vcol = 0; char *line = ptr = ml_get_buf(wp->w_buffer, pos->lnum); // start of the line - if (pos->col == MAXCOL) { - // continue until the NUL - posptr = NULL; - } else { - // In a few cases the position can be beyond the end of the line. - for (colnr_T i = 0; i < pos->col; i++) { - if (ptr[i] == NUL) { - pos->col = i; - break; - } - } - posptr = ptr + pos->col; - posptr -= utf_head_off(line, posptr); + uintptr_t last_pos = (uintptr_t)(ptr + pos->col); + if (last_pos < (uintptr_t)ptr) { + last_pos = UINTPTR_MAX; // unsigned overflow } - chartabsize_T cts; bool on_NUL = false; init_chartabsize_arg(&cts, wp, pos->lnum, 0, line, line); @@ -566,13 +554,13 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en } } - if ((posptr != NULL) && (ptr >= posptr)) { - // character at pos->col + char *const next = ptr + utfc_ptr2len(ptr); + if ((uintptr_t)next > last_pos) { break; } + ptr = next; vcol += incr; - MB_PTR_ADV(ptr); } } else { while (true) { @@ -589,13 +577,13 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en break; } - if ((posptr != NULL) && (cts.cts_ptr >= posptr)) { - // character at pos->col + char *const next = cts.cts_ptr + utfc_ptr2len(cts.cts_ptr); + if ((uintptr_t)next > last_pos) { break; } + cts.cts_ptr = next; cts.cts_vcol += incr; - MB_PTR_ADV(cts.cts_ptr); } vcol = cts.cts_vcol; ptr = cts.cts_ptr; -- cgit From b5653984e5de514410b5654d2a9b92bdcb9eedf3 Mon Sep 17 00:00:00 2001 From: VanaIgr Date: Sun, 17 Dec 2023 16:48:27 -0600 Subject: perf: don't decode utf8 character multiple times in getvcol() The optimized virtual column calculation loop in getvcol() was decoding the current character twice: once in ptr2cells() and the second time in utfc_ptr2len(). For combining charcters, they were decoded up to 2 times in utfc_ptr2len(). Additionally, the function used to decode the character could be further optimised. --- src/nvim/plines.c | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) (limited to 'src/nvim/plines.c') diff --git a/src/nvim/plines.c b/src/nvim/plines.c index 13b580b142..1fa2812dab 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -511,55 +511,47 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en cts.cts_max_head_vcol = -1; // This function is used very often, do some speed optimizations. - // When 'list', 'linebreak', 'showbreak' and 'breakindent' are not set + // When 'linebreak', 'showbreak' and 'breakindent' are not set // and there are no virtual text use a simple loop. - // Also use this when 'list' is set but tabs take their normal size. - if ((!wp->w_p_list || (wp->w_p_lcs_chars.tab1 != NUL)) - && !wp->w_p_lbr - && *get_showbreak_value(wp) == NUL - && !wp->w_p_bri - && cts.virt_row < 0) { + if (!wp->w_p_lbr && !wp->w_p_bri && cts.virt_row < 0 && *get_showbreak_value(wp) == NUL) { + bool const special_tab = !wp->w_p_list || wp->w_p_lcs_chars.tab1 != NUL; + CharInfo cur_char = utf_ptr2CharInfo(ptr); while (true) { head = 0; - int c = (uint8_t)(*ptr); - // make sure we don't go past the end of the line - if (c == NUL) { + if (cur_char.value == 0 && cur_char.len == 1) { // NUL at end of line only takes one column incr = 1; break; } // A tab gets expanded, depending on the current column - if (c == TAB) { + if (cur_char.value == TAB && special_tab) { incr = tabstop_padding(vcol, ts, vts); } else { - // For utf-8, if the byte is >= 0x80, need to look at - // further bytes to find the cell width. - if (c >= 0x80) { - incr = utf_ptr2cells(ptr); + if (cur_char.value < 0) { + incr = kInvalidByteCells; } else { - incr = byte2cells(c); + incr = char2cells(cur_char.value); } // If a double-cell char doesn't fit at the end of a line // it wraps to the next line, it's like this char is three // cells wide. - if ((incr == 2) - && wp->w_p_wrap - && (MB_BYTE2LEN((uint8_t)(*ptr)) > 1) - && in_win_border(wp, vcol)) { + if (incr == 2 && cur_char.value >= 0x80 + && wp->w_p_wrap && in_win_border(wp, vcol)) { incr++; head = 1; } } - char *const next = ptr + utfc_ptr2len(ptr); - if ((uintptr_t)next > last_pos) { + StrCharInfo const next_char = utfc_next((StrCharInfo){ ptr, cur_char }); + if ((uintptr_t)next_char.ptr > last_pos) { break; } - ptr = next; + cur_char = next_char.chr; + ptr = next_char.ptr; vcol += incr; } } else { -- cgit From cdf848a314bf91a0c87c717f9a44742dea877515 Mon Sep 17 00:00:00 2001 From: VanaIgr Date: Mon, 18 Dec 2023 20:57:04 -0600 Subject: perf: reuse fast character size calculation algorithm from getvcol() --- src/nvim/plines.c | 475 ++++++++++++++++++++++++------------------------------ 1 file changed, 211 insertions(+), 264 deletions(-) (limited to 'src/nvim/plines.c') diff --git a/src/nvim/plines.c b/src/nvim/plines.c index 1fa2812dab..d63c032080 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -51,48 +51,21 @@ int win_chartabsize(win_T *wp, char *p, colnr_T col) return ptr2cells(p); } -/// Return the number of characters the string 's' will take on the screen, -/// taking into account the size of a tab. -/// -/// @param s -/// -/// @return Number of characters the string will take on the screen. -int linetabsize_str(char *s) -{ - return linetabsize_col(0, s); -} - -/// Like linetabsize_str(), but "s" starts at column "startcol". +/// Like linetabsize_str(), but "s" starts at virtual column "startvcol". /// /// @param startcol /// @param s /// /// @return Number of characters the string will take on the screen. -int linetabsize_col(int startcol, char *s) +int linetabsize_col(int startvcol, char *s) { - chartabsize_T cts; - init_chartabsize_arg(&cts, curwin, 0, startcol, s, s); - while (*cts.cts_ptr != NUL) { - cts.cts_vcol += lbr_chartabsize_adv(&cts); + CharsizeArg arg; + CSType const cstype = init_charsize_arg(&arg, curwin, 0, s); + if (cstype == kCharsizeFast) { + return linesize_fast(&arg, startvcol, MAXCOL); + } else { + return linesize_regular(&arg, startvcol, MAXCOL); } - clear_chartabsize_arg(&cts); - return cts.cts_vcol; -} - -/// Like linetabsize_str(), but for a given window instead of the current one. -/// -/// @param wp -/// @param line -/// @param len -/// -/// @return Number of characters the string will take on the screen. -int win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T len) -{ - chartabsize_T cts; - init_chartabsize_arg(&cts, wp, lnum, 0, line, line); - win_linetabsize_cts(&cts, len); - clear_chartabsize_arg(&cts); - return cts.cts_vcol; } /// Return the number of cells line "lnum" of window "wp" will take on the @@ -102,128 +75,79 @@ int linetabsize(win_T *wp, linenr_T lnum) return win_linetabsize(wp, lnum, ml_get_buf(wp->w_buffer, lnum), (colnr_T)MAXCOL); } -void win_linetabsize_cts(chartabsize_T *cts, colnr_T len) -{ - for (; *cts->cts_ptr != NUL && (len == MAXCOL || cts->cts_ptr < cts->cts_line + len); - MB_PTR_ADV(cts->cts_ptr)) { - cts->cts_vcol += win_lbr_chartabsize(cts, NULL); - } - // check for inline virtual text after the end of the line - if (len == MAXCOL && cts->virt_row >= 0 && *cts->cts_ptr == NUL) { - (void)win_lbr_chartabsize(cts, NULL); - cts->cts_vcol += cts->cts_cur_text_width_left + cts->cts_cur_text_width_right; - } -} - -/// Prepare the structure passed to chartabsize functions. +/// Prepare the structure passed to charsize functions. /// -/// "line" is the start of the line, "ptr" is the first relevant character. +/// "line" is the start of the line. /// When "lnum" is zero do not use inline virtual text. -void init_chartabsize_arg(chartabsize_T *cts, win_T *wp, linenr_T lnum, colnr_T col, char *line, - char *ptr) +CSType init_charsize_arg(CharsizeArg *cts, win_T *wp, linenr_T lnum, char *line) { - cts->cts_win = wp; - cts->cts_vcol = col; - cts->cts_line = line; - cts->cts_ptr = ptr; - cts->cts_max_head_vcol = 0; - cts->cts_cur_text_width_left = 0; - cts->cts_cur_text_width_right = 0; + cts->win = wp; + cts->line = line; + cts->max_head_vcol = 0; + cts->cur_text_width_left = 0; + cts->cur_text_width_right = 0; cts->virt_row = -1; cts->indent_width = INT_MIN; + cts->use_tabstop = !wp->w_p_list || wp->w_p_lcs_chars.tab1; if (lnum > 0 && wp->w_buffer->b_virt_text_inline > 0) { - marktree_itr_get(wp->w_buffer->b_marktree, lnum - 1, 0, cts->cts_iter); - MTKey mark = marktree_itr_current(cts->cts_iter); + marktree_itr_get(wp->w_buffer->b_marktree, lnum - 1, 0, cts->iter); + MTKey mark = marktree_itr_current(cts->iter); if (mark.pos.row == lnum - 1) { cts->virt_row = lnum - 1; } } -} - -/// Free any allocated item in "cts". -void clear_chartabsize_arg(chartabsize_T *cts) -{ -} -/// like win_chartabsize(), but also check for line breaks on the screen -/// -/// @param cts -/// -/// @return The number of characters taken up on the screen. -int lbr_chartabsize(chartabsize_T *cts) -{ - if (!curwin->w_p_lbr && *get_showbreak_value(curwin) == NUL - && !curwin->w_p_bri && cts->virt_row < 0) { - if (curwin->w_p_wrap) { - return win_nolbr_chartabsize(cts, NULL); - } - return win_chartabsize(curwin, cts->cts_ptr, cts->cts_vcol); + if (cts->virt_row >= 0 + || (wp->w_p_wrap && (wp->w_p_lbr || wp->w_p_bri || *get_showbreak_value(wp) != NUL))) { + return kCharsizeRegular; + } else { + return kCharsizeFast; } - return win_lbr_chartabsize(cts, NULL); } -/// Call lbr_chartabsize() and advance the pointer. -/// -/// @param cts -/// -/// @return The number of characters take up on the screen. -int lbr_chartabsize_adv(chartabsize_T *cts) -{ - int retval = lbr_chartabsize(cts); - MB_PTR_ADV(cts->cts_ptr); - return retval; -} - -/// Get the number of characters taken up on the screen indicated by "cts". -/// "cts->cts_cur_text_width_left" and "cts->cts_cur_text_width_right" are set +/// Get the number of characters taken up on the screen for the given cts and position. +/// "cts->cur_text_width_left" and "cts->cur_text_width_right" are set /// to the extra size for inline virtual text. /// This function is used very often, keep it fast!!!! /// -/// If "headp" not NULL, set "*headp" to the size of 'showbreak'/'breakindent' -/// included in the return value. -/// When "cts->cts_max_head_vcol" is positive, only count in "*headp" the size -/// of 'showbreak'/'breakindent' before "cts->cts_max_head_vcol". -/// When "cts->cts_max_head_vcol" is negative, only count in "*headp" the size +/// When "cts->max_head_vcol" is positive, only count in "head" the size +/// of 'showbreak'/'breakindent' before "cts->max_head_vcol". +/// When "cts->max_head_vcol" is negative, only count in "head" the size /// of 'showbreak'/'breakindent' before where cursor should be placed. -/// -/// Warning: "*headp" may not be set if it's 0, init to 0 before calling. -int win_lbr_chartabsize(chartabsize_T *cts, int *headp) +CharSize charsize_regular(CharsizeArg *cts, char *const cur, colnr_T const vcol, + int32_t const cur_char) { - win_T *wp = cts->cts_win; - char *line = cts->cts_line; // start of the line - char *s = cts->cts_ptr; - colnr_T vcol = cts->cts_vcol; - int mb_added = 0; - - cts->cts_cur_text_width_left = 0; - cts->cts_cur_text_width_right = 0; - - char *const sbr = get_showbreak_value(wp); + cts->cur_text_width_left = 0; + cts->cur_text_width_right = 0; - // No 'linebreak', 'showbreak' and 'breakindent': return quickly. - if (!wp->w_p_lbr && !wp->w_p_bri && *sbr == NUL - && cts->virt_row < 0) { - if (wp->w_p_wrap) { - return win_nolbr_chartabsize(cts, headp); - } - return win_chartabsize(wp, s, vcol); - } + win_T *wp = cts->win; + buf_T *buf = wp->w_buffer; + char *line = cts->line; + bool const use_tabstop = cur_char == TAB && cts->use_tabstop; + int mb_added = 0; bool has_lcs_eol = wp->w_p_list && wp->w_p_lcs_chars.eol != NUL; // First get normal size, without 'linebreak' or inline virtual text - int size = win_chartabsize(wp, s, vcol); - if (*s == NUL && !has_lcs_eol) { - size = 0; // NUL is not displayed + int size; + int is_doublewidth = false; + if (use_tabstop) { + size = tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array); + } else if (*cur == NUL && !has_lcs_eol) { + size = 0; + } else if (cur_char < 0) { + size = kInvalidByteCells; + } else { + size = char2cells(cur_char); + is_doublewidth = size == 2 && cur_char > 0x80; } - bool is_doublewidth = size == 2 && MB_BYTE2LEN((uint8_t)(*s)) > 1; if (cts->virt_row >= 0) { int tab_size = size; - int col = (int)(s - line); + int col = (int)(cur - line); while (true) { - MTKey mark = marktree_itr_current(cts->cts_iter); + MTKey mark = marktree_itr_current(cts->iter); if (mark.pos.row != cts->virt_row || mark.pos.col > col) { break; } else if (mark.pos.col == col) { @@ -233,15 +157,15 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) while (vt) { if (!(vt->flags & kVTIsLines) && vt->pos == kVPosInline) { if (mt_right(mark)) { - cts->cts_cur_text_width_right += vt->width; + cts->cur_text_width_right += vt->width; } else { - cts->cts_cur_text_width_left += vt->width; + cts->cur_text_width_left += vt->width; } size += vt->width; - if (*s == TAB) { + if (use_tabstop) { // tab size changes because of the inserted text size -= tab_size; - tab_size = win_chartabsize(wp, s, vcol + size); + tab_size = tabstop_padding(vcol + size, buf->b_p_ts, buf->b_p_vts_array); size += tab_size; } } @@ -249,7 +173,7 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) } } } - marktree_itr_next(wp->w_buffer->b_marktree, cts->cts_iter); + marktree_itr_next(wp->w_buffer->b_marktree, cts->iter); } } @@ -259,6 +183,8 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) mb_added = 1; } + char *const sbr = get_showbreak_value(wp); + // May have to add something for 'breakindent' and/or 'showbreak' // string at the start of a screen line. int head = mb_added; @@ -267,7 +193,7 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) int col_off_prev = win_col_off(wp); int width2 = wp->w_width_inner - col_off_prev + win_col_off2(wp); colnr_T wcol = vcol + col_off_prev; - colnr_T max_head_vcol = cts->cts_max_head_vcol; + colnr_T max_head_vcol = cts->max_head_vcol; int added = 0; // cells taken by 'showbreak'/'breakindent' before current char @@ -333,7 +259,7 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) head += (max_head_vcol - (vcol + head_prev + prev_rem) + width2 - 1) / width2 * head_mid; } else if (max_head_vcol < 0) { - int off = virt_text_cursor_off(cts, *s == NUL); + int off = virt_text_cursor_off(cts, *cur == NUL); if (off >= prev_rem) { if (size > off) { head += (1 + (off - prev_rem) / width) * head_mid; @@ -348,19 +274,16 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) size += added; } - if (headp != NULL) { - *headp = head; - } - + char *s = cur; colnr_T vcol_start = 0; // start from where to consider linebreak // If 'linebreak' set check at a blank before a non-blank if the line // needs a break here if (wp->w_p_lbr && wp->w_p_wrap && wp->w_width_inner != 0) { - char *t = cts->cts_line; + char *t = cts->line; while (vim_isbreak((uint8_t)t[0])) { t++; } - vcol_start = (colnr_T)(t - cts->cts_line); + vcol_start = (colnr_T)(t - cts->line); } if (wp->w_p_lbr && vcol_start <= vcol && vim_isbreak((uint8_t)s[0]) @@ -398,39 +321,50 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) } } - return size; + return (CharSize){ .width = size, .head = head }; } -/// Like win_lbr_chartabsize(), except that we know 'linebreak' is off and -/// 'wrap' is on. This means we need to check for a double-byte character that -/// doesn't fit at the end of the screen line. +/// Like charsize_regular(), except it doesn't handle virtual text, +/// linebreak, breakindent and showbreak. Handles normal characters, tabs and wrapping. +/// This function is always inlined. /// -/// @param cts -/// @param headp -/// -/// @return The number of characters take up on the screen. -static int win_nolbr_chartabsize(chartabsize_T *cts, int *headp) +/// @see charsize_regular +/// @see charsize_fast +static inline CharSize charsize_fast_impl(win_T *const wp, bool use_tabstop, colnr_T const vcol, + int32_t const cur_char) + FUNC_ATTR_PURE FUNC_ATTR_ALWAYS_INLINE { - win_T *wp = cts->cts_win; - char *s = cts->cts_ptr; - colnr_T col = cts->cts_vcol; - - if ((*s == TAB) && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { - return tabstop_padding(col, - wp->w_buffer->b_p_ts, - wp->w_buffer->b_p_vts_array); - } - int n = ptr2cells(s); - - // Add one cell for a double-width character in the last column of the - // window, displayed with a ">". - if ((n == 2) && (MB_BYTE2LEN((uint8_t)(*s)) > 1) && in_win_border(wp, col)) { - if (headp != NULL) { - *headp = 1; + // A tab gets expanded, depending on the current column + if (cur_char == TAB && use_tabstop) { + return (CharSize){ + .width = tabstop_padding(vcol, wp->w_buffer->b_p_ts, + wp->w_buffer->b_p_vts_array) + }; + } else { + int width; + if (cur_char < 0) { + width = kInvalidByteCells; + } else { + width = char2cells(cur_char); + } + + // If a double-width char doesn't fit at the end of a line, it wraps to the next line, + // and the last column displays a '>'. + if (width == 2 && cur_char >= 0x80 && wp->w_p_wrap && in_win_border(wp, vcol)) { + return (CharSize){ .width = 3, .head = 1 }; + } else { + return (CharSize){ .width = width }; } - return 3; } - return n; +} + +/// Like charsize_regular(), except it doesn't handle virtual text, +/// linebreak, breakindent and showbreak. Handles normal characters, tabs and wrapping. +/// Can be used if CSType is kCharsizeFast. +CharSize charsize_fast(CharsizeArg *cts, colnr_T const vcol, int32_t const cur_char) + FUNC_ATTR_PURE +{ + return charsize_fast_impl(cts->win, cts->use_tabstop, vcol, cur_char); } /// Check that virtual column "vcol" is in the rightmost column of window "wp". @@ -461,19 +395,63 @@ static bool in_win_border(win_T *wp, colnr_T vcol) return (vcol - width1) % width2 == width2 - 1; } +/// Calculate virtual column until the given 'len'. +/// +/// @param arg Argument to charsize functions. +/// @param vcol Starting virtual column. +/// @param len First byte of the end character, or MAXCOL. +/// +/// @return virtual column before the character at 'len', +/// or full size of the line if 'len' is MAXCOL. +int linesize_regular(CharsizeArg *const arg, int vcol, colnr_T const len) +{ + char *const line = arg->line; + + StrCharInfo ci = utf_ptr2StrCharInfo(line); + while (ci.ptr - line < len && *ci.ptr != NUL) { + vcol += charsize_regular(arg, ci.ptr, vcol, ci.chr.value).width; + ci = utfc_next(ci); + } + + // Check for inline virtual text after the end of the line. + if (len == MAXCOL && arg->virt_row >= 0) { + (void)charsize_regular(arg, ci.ptr, vcol, ci.chr.value); + vcol += arg->cur_text_width_left + arg->cur_text_width_right; + } + + return vcol; +} + +/// Like win_linesize_regular, but can be used when CStype is kCharsizeFast. +/// +/// @see win_linesize_regular +int linesize_fast(CharsizeArg const *const arg, int vcol, colnr_T const len) +{ + win_T *const wp = arg->win; + bool const use_tabstop = arg->use_tabstop; + + char *const line = arg->line; + + StrCharInfo ci = utf_ptr2StrCharInfo(line); + while (ci.ptr - line < len && *ci.ptr != NUL) { + vcol += charsize_fast_impl(wp, use_tabstop, vcol, ci.chr.value).width; + ci = utfc_next(ci); + } + + return vcol; +} + /// Get how many virtual columns inline virtual text should offset the cursor. /// -/// @param cts should contain information stored by win_lbr_chartabsize() -/// about widths of left and right gravity virtual text /// @param on_NUL whether this is the end of the line -static int virt_text_cursor_off(chartabsize_T *cts, bool on_NUL) +static int virt_text_cursor_off(CharsizeArg *cts, bool on_NUL) { int off = 0; if (!on_NUL || !(State & MODE_NORMAL)) { - off += cts->cts_cur_text_width_left; + off += cts->cur_text_width_left; } if (!on_NUL && (State & MODE_NORMAL)) { - off += cts->cts_cur_text_width_right; + off += cts->cur_text_width_right; } return off; } @@ -492,95 +470,53 @@ static int virt_text_cursor_off(chartabsize_T *cts, bool on_NUL) /// @param end void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end) { - char *ptr; // points to current char - int incr; - int head; - colnr_T *vts = wp->w_buffer->b_p_vts_array; - int ts = (int)wp->w_buffer->b_p_ts; - - colnr_T vcol = 0; - char *line = ptr = ml_get_buf(wp->w_buffer, pos->lnum); // start of the line + char *const line = ml_get_buf(wp->w_buffer, pos->lnum); // start of the line + int const end_col = pos->col; - uintptr_t last_pos = (uintptr_t)(ptr + pos->col); - if (last_pos < (uintptr_t)ptr) { - last_pos = UINTPTR_MAX; // unsigned overflow - } - chartabsize_T cts; + CharsizeArg arg; bool on_NUL = false; - init_chartabsize_arg(&cts, wp, pos->lnum, 0, line, line); - cts.cts_max_head_vcol = -1; - - // This function is used very often, do some speed optimizations. - // When 'linebreak', 'showbreak' and 'breakindent' are not set - // and there are no virtual text use a simple loop. - if (!wp->w_p_lbr && !wp->w_p_bri && cts.virt_row < 0 && *get_showbreak_value(wp) == NUL) { - bool const special_tab = !wp->w_p_list || wp->w_p_lcs_chars.tab1 != NUL; - CharInfo cur_char = utf_ptr2CharInfo(ptr); + CSType const cstype = init_charsize_arg(&arg, wp, pos->lnum, line); + arg.max_head_vcol = -1; + + colnr_T vcol = 0; + CharSize char_size; + StrCharInfo ci = utf_ptr2StrCharInfo(line); + if (cstype == kCharsizeFast) { + bool const use_tabstop = arg.use_tabstop; while (true) { - head = 0; - // make sure we don't go past the end of the line - if (cur_char.value == 0 && cur_char.len == 1) { - // NUL at end of line only takes one column - incr = 1; + if (*ci.ptr == NUL) { + // if cursor is at NUL, it is treated like 1 cell char + char_size = (CharSize){ .width = 1 }; break; } - - // A tab gets expanded, depending on the current column - if (cur_char.value == TAB && special_tab) { - incr = tabstop_padding(vcol, ts, vts); - } else { - if (cur_char.value < 0) { - incr = kInvalidByteCells; - } else { - incr = char2cells(cur_char.value); - } - - // If a double-cell char doesn't fit at the end of a line - // it wraps to the next line, it's like this char is three - // cells wide. - if (incr == 2 && cur_char.value >= 0x80 - && wp->w_p_wrap && in_win_border(wp, vcol)) { - incr++; - head = 1; - } - } - - StrCharInfo const next_char = utfc_next((StrCharInfo){ ptr, cur_char }); - if ((uintptr_t)next_char.ptr > last_pos) { + char_size = charsize_fast_impl(wp, use_tabstop, vcol, ci.chr.value); + StrCharInfo const next = utfc_next(ci); + if (next.ptr - line > end_col) { break; } - - cur_char = next_char.chr; - ptr = next_char.ptr; - vcol += incr; + ci = next; + vcol += char_size.width; } } else { while (true) { - // A tab gets expanded, depending on the current column - // Other things also take up space. - head = 0; - incr = win_lbr_chartabsize(&cts, &head); - - // make sure we don't go past the end of the line - if (*cts.cts_ptr == NUL) { - // NUL at end of line only takes one column, unless there is virtual text - incr = MAX(1, cts.cts_cur_text_width_left + cts.cts_cur_text_width_right); + char_size = charsize_regular(&arg, ci.ptr, vcol, ci.chr.value); + if (*ci.ptr == NUL) { + // if cursor is at NUL, it is treated like 1 cell char unless there is virtual text + char_size.width = MAX(1, arg.cur_text_width_left + arg.cur_text_width_right); on_NUL = true; break; } - - char *const next = cts.cts_ptr + utfc_ptr2len(cts.cts_ptr); - if ((uintptr_t)next > last_pos) { + StrCharInfo const next = utfc_next(ci); + if (next.ptr - line > end_col) { break; } - - cts.cts_ptr = next; - cts.cts_vcol += incr; + ci = next; + vcol += char_size.width; } - vcol = cts.cts_vcol; - ptr = cts.cts_ptr; } - clear_chartabsize_arg(&cts); + + int head = char_size.head; + int incr = char_size.width; if (start != NULL) { *start = vcol + head; @@ -591,7 +527,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en } if (cursor != NULL) { - if ((*ptr == TAB) + if (ci.chr.value == TAB && (State & MODE_NORMAL) && !wp->w_p_list && !virtual_active() @@ -599,7 +535,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en // cursor at end *cursor = vcol + incr - 1; } else { - vcol += virt_text_cursor_off(&cts, on_NUL); + vcol += virt_text_cursor_off(&arg, on_NUL); // cursor at start *cursor = vcol + head; } @@ -788,14 +724,18 @@ int plines_win_nofill(win_T *wp, linenr_T lnum, bool limit_winheight) int plines_win_nofold(win_T *wp, linenr_T lnum) { char *s = ml_get_buf(wp->w_buffer, lnum); - chartabsize_T cts; - init_chartabsize_arg(&cts, wp, lnum, 0, s, s); - if (*s == NUL && cts.virt_row < 0) { + CharsizeArg arg; + CSType const cstype = init_charsize_arg(&arg, wp, lnum, s); + if (*s == NUL && arg.virt_row < 0) { return 1; // be quick for an empty line } - win_linetabsize_cts(&cts, (colnr_T)MAXCOL); - clear_chartabsize_arg(&cts); - int64_t col = cts.cts_vcol; + + int64_t col; + if (cstype == kCharsizeFast) { + col = linesize_fast(&arg, 0, MAXCOL); + } else { + col = linesize_regular(&arg, 0, MAXCOL); + } // If list mode is on, then the '$' at the end of the line may take up one // extra column. @@ -834,26 +774,33 @@ int plines_win_col(win_T *wp, linenr_T lnum, long column) char *line = ml_get_buf(wp->w_buffer, lnum); - colnr_T col = 0; - chartabsize_T cts; + CharsizeArg cts; + CSType const cstype = init_charsize_arg(&cts, wp, lnum, line); - init_chartabsize_arg(&cts, wp, lnum, 0, line, line); - while (*cts.cts_ptr != NUL && --column >= 0) { - cts.cts_vcol += win_lbr_chartabsize(&cts, NULL); - MB_PTR_ADV(cts.cts_ptr); + colnr_T vcol = 0; + StrCharInfo ci = utf_ptr2StrCharInfo(line); + if (cstype == kCharsizeFast) { + bool const use_tabstop = cts.use_tabstop; + while (*ci.ptr != NUL && --column >= 0) { + vcol += charsize_fast_impl(wp, use_tabstop, vcol, ci.chr.value).width; + ci = utfc_next(ci); + } + } else { + while (*ci.ptr != NUL && --column >= 0) { + vcol += charsize_regular(&cts, ci.ptr, vcol, ci.chr.value).width; + ci = utfc_next(ci); + } } - // If *cts.cts_ptr is a TAB, and the TAB is not displayed as ^I, and we're not + // If current char is a TAB, and the TAB is not displayed as ^I, and we're not // in MODE_INSERT state, then col must be adjusted so that it represents the // last screen position of the TAB. This only fixes an error when the TAB // wraps from one screen line to the next (when 'columns' is not a multiple // of 'ts') -- webb. - col = cts.cts_vcol; - if (*cts.cts_ptr == TAB && (State & MODE_NORMAL) - && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { - col += win_lbr_chartabsize(&cts, NULL) - 1; + colnr_T col = vcol; + if (ci.chr.value == TAB && (State & MODE_NORMAL) && cts.use_tabstop) { + col += win_charsize(cstype, col, ci.ptr, ci.chr.value, &cts).width - 1; } - clear_chartabsize_arg(&cts); // Add column offset for 'number', 'relativenumber', 'foldcolumn', etc. int width = wp->w_width_inner - win_col_off(wp); -- cgit