diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 21:52:58 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 21:52:58 +0000 |
commit | 931bffbda3668ddc609fc1da8f9eb576b170aa52 (patch) | |
tree | d8c1843a95da5ea0bb4acc09f7e37843d9995c86 /src/nvim/plines.c | |
parent | 142d9041391780ac15b89886a54015fdc5c73995 (diff) | |
parent | 4a8bf24ac690004aedf5540fa440e788459e5e34 (diff) | |
download | rneovim-931bffbda3668ddc609fc1da8f9eb576b170aa52.tar.gz rneovim-931bffbda3668ddc609fc1da8f9eb576b170aa52.tar.bz2 rneovim-931bffbda3668ddc609fc1da8f9eb576b170aa52.zip |
Merge remote-tracking branch 'upstream/master' into userreguserreg
Diffstat (limited to 'src/nvim/plines.c')
-rw-r--r-- | src/nvim/plines.c | 1126 |
1 files changed, 785 insertions, 341 deletions
diff --git a/src/nvim/plines.c b/src/nvim/plines.c index 5469d94800..6e9f92c193 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -1,223 +1,34 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - // plines.c: calculate the vertical and horizontal size of text in a window -#include <assert.h> -#include <inttypes.h> #include <limits.h> #include <stdbool.h> +#include <stdint.h> #include <string.h> -#include "nvim/ascii.h" +#include "nvim/ascii_defs.h" #include "nvim/charset.h" #include "nvim/decoration.h" #include "nvim/diff.h" #include "nvim/fold.h" +#include "nvim/func_attr.h" #include "nvim/globals.h" #include "nvim/indent.h" -#include "nvim/macros.h" +#include "nvim/macros_defs.h" +#include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/move.h" #include "nvim/option.h" +#include "nvim/option_vars.h" #include "nvim/plines.h" -#include "nvim/pos.h" -#include "nvim/vim.h" +#include "nvim/pos_defs.h" +#include "nvim/state.h" +#include "nvim/types_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "plines.c.generated.h" #endif -/// Functions calculating vertical size of text when displayed inside a window. -/// Calls horizontal size functions defined below. - -/// Return the number of window lines occupied by buffer line "lnum". -/// Includes any filler lines. -/// -/// @param winheight when true limit to window height -int plines_win(win_T *wp, linenr_T lnum, bool winheight) -{ - // Check for filler lines above this buffer line. When folded the result - // is one line anyway. - return plines_win_nofill(wp, lnum, winheight) + win_get_fill(wp, lnum); -} - -/// Return the number of filler lines above "lnum". -/// -/// @param wp -/// @param lnum -/// -/// @return Number of filler lines above lnum -int win_get_fill(win_T *wp, linenr_T lnum) -{ - int virt_lines = decor_virt_lines(wp, lnum, NULL, kNone); - - // be quick when there are no filler lines - if (diffopt_filler()) { - int n = diff_check(wp, lnum); - - if (n > 0) { - return virt_lines + n; - } - } - return virt_lines; -} - -bool win_may_fill(win_T *wp) -{ - return (wp->w_p_diff && diffopt_filler()) || wp->w_buffer->b_virt_line_blocks; -} - -/// Return the number of window lines occupied by buffer line "lnum". -/// Does not include filler lines. -/// -/// @param winheight when true limit to window height -int plines_win_nofill(win_T *wp, linenr_T lnum, bool winheight) -{ - if (!wp->w_p_wrap) { - return 1; - } - - if (wp->w_width_inner == 0) { - return 1; - } - - // Folded lines are handled just like an empty line. - if (lineFolded(wp, lnum)) { - return 1; - } - - const int lines = plines_win_nofold(wp, lnum); - if (winheight && lines > wp->w_height_inner) { - return wp->w_height_inner; - } - return lines; -} - -/// @Return number of window lines physical line "lnum" will occupy in window -/// "wp". Does not care about folding, 'wrap' or 'diff'. -int plines_win_nofold(win_T *wp, linenr_T lnum) -{ - char *s; - unsigned int col; - int width; - - s = ml_get_buf(wp->w_buffer, lnum, false); - if (*s == NUL) { // empty line - return 1; - } - col = win_linetabsize(wp, lnum, s, MAXCOL); - - // If list mode is on, then the '$' at the end of the line may take up one - // extra column. - if (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL) { - col += 1; - } - - // Add column offset for 'number', 'relativenumber' and 'foldcolumn'. - width = wp->w_width_inner - win_col_off(wp); - if (width <= 0 || col > 32000) { - return 32000; // bigger than the number of screen columns - } - if (col <= (unsigned int)width) { - return 1; - } - col -= (unsigned int)width; - width += win_col_off2(wp); - assert(col <= INT_MAX && (int)col < INT_MAX - (width - 1)); - return ((int)col + (width - 1)) / width + 1; -} - -/// Like plines_win(), but only reports the number of physical screen lines -/// used from the start of the line to the given column number. -int plines_win_col(win_T *wp, linenr_T lnum, long column) -{ - // Check for filler lines above this buffer line. When folded the result - // is one line anyway. - int lines = win_get_fill(wp, lnum); - - if (!wp->w_p_wrap) { - return lines + 1; - } - - if (wp->w_width_inner == 0) { - return lines + 1; - } - - char *line = ml_get_buf(wp->w_buffer, lnum, false); - - colnr_T col = 0; - chartabsize_T cts; - - 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); - } - - // If *cts.cts_ptr 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; - } - clear_chartabsize_arg(&cts); - - // Add column offset for 'number', 'relativenumber', 'foldcolumn', etc. - int width = wp->w_width_inner - win_col_off(wp); - if (width <= 0) { - return 9999; - } - - lines += 1; - if (col > width) { - lines += (col - width) / (width + win_col_off2(wp)) + 1; - } - return lines; -} - -/// Get the number of screen lines lnum takes up. This takes care of -/// both folds and topfill, and limits to the current window height. -/// -/// @param[in] wp window line is in -/// @param[in] lnum line number -/// @param[out] nextp if not NULL, the line after a fold -/// @param[out] foldedp if not NULL, whether lnum is on a fold -/// @param[in] cache whether to use the window's cache for folds -/// -/// @return the total number of screen lines -int plines_win_full(win_T *wp, linenr_T lnum, linenr_T *const nextp, bool *const foldedp, - const bool cache) -{ - bool folded = hasFoldingWin(wp, lnum, NULL, nextp, cache, NULL); - if (foldedp) { - *foldedp = folded; - } - if (folded) { - return 1; - } else if (lnum == wp->w_topline) { - return plines_win_nofill(wp, lnum, true) + wp->w_topfill; - } - return plines_win(wp, lnum, true); -} - -int plines_m_win(win_T *wp, linenr_T first, linenr_T last) -{ - int count = 0; - - while (first <= last) { - linenr_T next = first; - count += plines_win_full(wp, first, &next, NULL, false); - first = next + 1; - } - return count; -} - /// Functions calculating horizontal size of text, when displayed in a window. /// Return the number of characters 'c' will take on the screen, taking @@ -243,12 +54,12 @@ int win_chartabsize(win_T *wp, char *p, colnr_T col) /// @param s /// /// @return Number of characters the string will take on the screen. -int linetabsize(char *s) +int linetabsize_str(char *s) { return linetabsize_col(0, s); } -/// Like linetabsize(), but "s" starts at column "startcol". +/// Like linetabsize_str(), but "s" starts at column "startcol". /// /// @param startcol /// @param s @@ -265,50 +76,76 @@ int linetabsize_col(int startcol, char *s) return cts.cts_vcol; } -/// Like linetabsize(), but for a given window instead of the current one. +/// 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. -unsigned int win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T len) +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); - for (; *cts.cts_ptr != NUL && (len == MAXCOL || cts.cts_ptr < line + len); - MB_PTR_ADV(cts.cts_ptr)) { - cts.cts_vcol += win_lbr_chartabsize(&cts, NULL); - } + win_linetabsize_cts(&cts, len); clear_chartabsize_arg(&cts); - return (unsigned int)cts.cts_vcol; + return cts.cts_vcol; +} + +/// Return the number of cells line "lnum" of window "wp" will take on the +/// screen, taking into account the size of a tab and inline virtual text. +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->cts_has_virt_text && *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. /// /// "line" is the start of the line, "ptr" is the first relevant character. -/// When "lnum" is zero do not use text properties that insert text. -void init_chartabsize_arg(chartabsize_T *cts, win_T *wp, linenr_T lnum FUNC_ATTR_UNUSED, - colnr_T col, char *line, char *ptr) +/// 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) { cts->cts_win = wp; cts->cts_vcol = col; cts->cts_line = line; cts->cts_ptr = ptr; - cts->cts_cur_text_width = 0; - // TODO(bfredl): actually lookup inline virtual text here + 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; + + 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); + MTKey mark = marktree_itr_current(cts->cts_iter); + if (mark.pos.row == cts->cts_row) { + cts->cts_has_virt_text = true; + } + } } /// 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 line -/// @param s -/// @param col +/// @param cts /// /// @return The number of characters taken up on the screen. int lbr_chartabsize(chartabsize_T *cts) @@ -325,47 +162,39 @@ int lbr_chartabsize(chartabsize_T *cts) /// Call lbr_chartabsize() and advance the pointer. /// -/// @param line -/// @param s -/// @param col +/// @param cts /// /// @return The number of characters take up on the screen. int lbr_chartabsize_adv(chartabsize_T *cts) { - int retval; - - retval = lbr_chartabsize(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 +/// 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 what we for 'showbreak' -/// string at start of line. Warning: *headp is only set if it's a non-zero -/// value, init to 0 before calling. +/// 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 +/// of 'showbreak'/'breakindent' before where cursor should be placed. /// -/// @param cts -/// @param headp -/// -/// @return The number of characters taken up on the screen. +/// Warning: "*headp" may not be set if it's 0, init to 0 before calling. int win_lbr_chartabsize(chartabsize_T *cts, int *headp) { 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; - - colnr_T col2; - colnr_T col_adj = 0; // vcol + screen size of tab - colnr_T colmax; - int added; int mb_added = 0; - int numberextra; - char *ps; - int n; - cts->cts_cur_text_width = 0; + cts->cts_cur_text_width_left = 0; + cts->cts_cur_text_width_right = 0; // No 'linebreak', 'showbreak' and 'breakindent': return quickly. if (!wp->w_p_lbr && !wp->w_p_bri && *get_showbreak_value(wp) == NUL @@ -376,134 +205,186 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) return win_chartabsize(wp, s, vcol); } - // First get normal size, without 'linebreak' or virtual text + 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 + } + bool is_doublewidth = size == 2 && MB_BYTE2LEN((uint8_t)(*s)) > 1; + if (cts->cts_has_virt_text) { - // TODO(bfredl): inline virtual text + 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) { + break; + } else if (mark.pos.col == col) { + if (!mt_end(mark) && mark.flags & (MT_FLAG_DECOR_VIRT_TEXT_INLINE)) { + DecorInline decor = mt_decor(mark); + DecorVirtText *vt = decor.ext ? decor.data.ext.vt : NULL; + while (vt) { + if (!(vt->flags & kVTIsLines) && vt->pos == kVPosInline) { + if (mt_right(mark)) { + cts->cts_cur_text_width_right += vt->width; + } else { + cts->cts_cur_text_width_left += vt->width; + } + size += vt->width; + if (*s == TAB) { + // tab size changes because of the inserted text + size -= tab_size; + tab_size = win_chartabsize(wp, s, vcol + size); + size += tab_size; + } + } + vt = vt->next; + } + } + } + marktree_itr_next(wp->w_buffer->b_marktree, cts->cts_iter); + } + } + + if (is_doublewidth && wp->w_p_wrap && in_win_border(wp, vcol + size - 2)) { + // Count the ">" in the last column. + size++; + mb_added = 1; + } + + // 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); + 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; + int added = 0; + + // cells taken by 'showbreak'/'breakindent' before current char + int head_prev = 0; + if (wcol >= wp->w_width_inner) { + wcol -= wp->w_width_inner; + col_off_prev = wp->w_width_inner - width2; + 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); + } + if (wcol < head_prev) { + head_prev -= wcol; + wcol += head_prev; + added += head_prev; + if (max_head_vcol <= 0 || vcol < max_head_vcol) { + head += head_prev; + } + } else { + head_prev = 0; + } + wcol += col_off_prev; + } + + 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); + } + if (head_mid > 0 && wcol + size > wp->w_width_inner) { + // Calculate effective window width. + int prev_rem = wp->w_width_inner - wcol; + int width = width2 - head_mid; + + if (width <= 0) { + width = 1; + } + // Divide "size - prev_rem" by "width", rounding up. + int cnt = (size - prev_rem + width - 1) / width; + added += cnt * head_mid; + + if (max_head_vcol == 0 || vcol + size + added < max_head_vcol) { + head += cnt * head_mid; + } else if (max_head_vcol > vcol + head_prev + prev_rem) { + 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); + if (off >= prev_rem) { + if (size > off) { + head += (1 + (off - prev_rem) / width) * head_mid; + } else { + head += (off - prev_rem + width - 1) / width * head_mid; + } + } + } + } + } + + size += added; } - int c = (uint8_t)(*s); - if (*s == TAB) { - col_adj = size - 1; + if (headp != NULL) { + *headp = head; } + 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 - && vim_isbreak(c) + if (wp->w_p_lbr && wp->w_p_wrap && wp->w_width_inner != 0) { + char *t = cts->cts_line; + while (vim_isbreak((uint8_t)t[0])) { + t++; + } + vcol_start = (colnr_T)(t - cts->cts_line); + } + if (wp->w_p_lbr && vcol_start <= vcol + && vim_isbreak((uint8_t)s[0]) && !vim_isbreak((uint8_t)s[1]) && wp->w_p_wrap - && (wp->w_width_inner != 0)) { + && wp->w_width_inner != 0) { // Count all characters from first non-blank after a blank up to next // non-blank after a blank. - numberextra = win_col_off(wp); - col2 = vcol; - colmax = (colnr_T)(wp->w_width_inner - numberextra - col_adj); - + int numberextra = win_col_off(wp); + colnr_T col_adj = size - 1; + colnr_T colmax = (colnr_T)(wp->w_width_inner - numberextra - col_adj); if (vcol >= colmax) { colmax += col_adj; - n = colmax + win_col_off2(wp); - + int n = colmax + win_col_off2(wp); if (n > 0) { colmax += (((vcol - colmax) / n) + 1) * n - col_adj; } } - for (;;) { - ps = s; + colnr_T vcol2 = vcol; + while (true) { + char *ps = s; MB_PTR_ADV(s); - c = (uint8_t)(*s); - + int c = (uint8_t)(*s); if (!(c != NUL - && (vim_isbreak(c) || col2 == vcol || !vim_isbreak((uint8_t)(*ps))))) { + && (vim_isbreak(c) || vcol2 == vcol || !vim_isbreak((uint8_t)(*ps))))) { break; } - col2 += win_chartabsize(wp, s, col2); - - if (col2 >= colmax) { // doesn't fit + vcol2 += win_chartabsize(wp, s, vcol2); + if (vcol2 >= colmax) { // doesn't fit size = colmax - vcol + col_adj; break; } } - } else if ((size == 2) - && (MB_BYTE2LEN((uint8_t)(*s)) > 1) - && wp->w_p_wrap - && in_win_border(wp, vcol)) { - // Count the ">" in the last column. - size++; - mb_added = 1; - } - - // May have to add something for 'breakindent' and/or 'showbreak' - // string at start of line. - // Set *headp to the size of what we add. - // Do not use 'showbreak' at the NUL after the text. - added = 0; - char *const sbr = c == NUL ? empty_option : get_showbreak_value(wp); - if ((*sbr != NUL || wp->w_p_bri) && wp->w_p_wrap && vcol != 0) { - colnr_T sbrlen = 0; - int numberwidth = win_col_off(wp); - - numberextra = numberwidth; - vcol += numberextra + mb_added; - - if (vcol >= (colnr_T)wp->w_width_inner) { - vcol -= wp->w_width_inner; - numberextra = wp->w_width_inner - (numberextra - win_col_off2(wp)); - if (vcol >= numberextra && numberextra > 0) { - vcol %= numberextra; - } - if (*sbr != NUL) { - sbrlen = (colnr_T)mb_charlen(sbr); - if (vcol >= sbrlen) { - vcol -= sbrlen; - } - } - if (vcol >= numberextra && numberextra > 0) { - vcol %= numberextra; - } else if (vcol > 0 && numberextra > 0) { - vcol += numberwidth - win_col_off2(wp); - } - - numberwidth -= win_col_off2(wp); - } - - if (vcol == 0 || (vcol + size + sbrlen > (colnr_T)wp->w_width_inner)) { - if (*sbr != NUL) { - if (size + sbrlen + numberwidth > (colnr_T)wp->w_width_inner) { - // Calculate effective window width. - int width = (colnr_T)wp->w_width_inner - sbrlen - numberwidth; - int prev_width = vcol ? ((colnr_T)wp->w_width_inner - (sbrlen + vcol)) - : 0; - - if (width <= 0) { - width = 1; - } - added += ((size - prev_width) / width) * vim_strsize(sbr); - if ((size - prev_width) % width) { - // Wrapped, add another length of 'sbr'. - added += vim_strsize(sbr); - } - } else { - added += vim_strsize(sbr); - } - } - - if (wp->w_p_bri) { - added += get_breakindent_win(wp, line); - } - - size += added; - if (vcol != 0) { - added = 0; - } - } } - if (headp != NULL) { - *headp = added + mb_added; - } return size; } @@ -511,9 +392,7 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) /// '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. /// -/// @param wp -/// @param s -/// @param col +/// @param cts /// @param headp /// /// @return The number of characters take up on the screen. @@ -522,14 +401,13 @@ static int win_nolbr_chartabsize(chartabsize_T *cts, int *headp) win_T *wp = cts->cts_win; char *s = cts->cts_ptr; colnr_T col = cts->cts_vcol; - int n; 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); } - n = ptr2cells(s); + int n = ptr2cells(s); // Add one cell for a double-width character in the last column of the // window, displayed with a ">". @@ -541,3 +419,569 @@ static int win_nolbr_chartabsize(chartabsize_T *cts, int *headp) } return n; } + +/// Check that virtual column "vcol" is in the rightmost column of window "wp". +/// +/// @param wp window +/// @param vcol column number +static bool in_win_border(win_T *wp, colnr_T vcol) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1) +{ + if (wp->w_width_inner == 0) { + // there is no border + return false; + } + int width1 = wp->w_width_inner - win_col_off(wp); // width of first line (after line number) + + if ((int)vcol < width1 - 1) { + return false; + } + + if ((int)vcol == width1 - 1) { + return true; + } + int width2 = width1 + win_col_off2(wp); // width of further lines + + if (width2 <= 0) { + return false; + } + return (vcol - width1) % width2 == width2 - 1; +} + +/// 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) +{ + int off = 0; + if (!on_NUL || !(State & MODE_NORMAL)) { + off += cts->cts_cur_text_width_left; + } + if (!on_NUL && (State & MODE_NORMAL)) { + off += cts->cts_cur_text_width_right; + } + return off; +} + +/// Get virtual column number of pos. +/// start: on the first position of this character (TAB, ctrl) +/// cursor: where the cursor is on this character (first char, except for TAB) +/// end: on the last position of this character (TAB, ctrl) +/// +/// This is used very often, keep it fast! +/// +/// @param wp +/// @param pos +/// @param start +/// @param cursor +/// @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 + char *posptr; // points to char at pos->col + 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 + + 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); + } + + chartabsize_T cts; + 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 'list', '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.cts_has_virt_text) { + while (true) { + head = 0; + int c = (uint8_t)(*ptr); + + // make sure we don't go past the end of the line + if (c == NUL) { + // NUL at end of line only takes one column + incr = 1; + break; + } + + // A tab gets expanded, depending on the current column + if (c == 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); + } else { + incr = byte2cells(c); + } + + // 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)) { + incr++; + head = 1; + } + } + + if ((posptr != NULL) && (ptr >= posptr)) { + // character at pos->col + break; + } + + vcol += incr; + MB_PTR_ADV(ptr); + } + } 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); + on_NUL = true; + break; + } + + if ((posptr != NULL) && (cts.cts_ptr >= posptr)) { + // character at pos->col + break; + } + + cts.cts_vcol += incr; + MB_PTR_ADV(cts.cts_ptr); + } + vcol = cts.cts_vcol; + ptr = cts.cts_ptr; + } + clear_chartabsize_arg(&cts); + + if (start != NULL) { + *start = vcol + head; + } + + if (end != NULL) { + *end = vcol + incr - 1; + } + + if (cursor != NULL) { + if ((*ptr == TAB) + && (State & MODE_NORMAL) + && !wp->w_p_list + && !virtual_active() + && !(VIsual_active && ((*p_sel == 'e') || ltoreq(*pos, VIsual)))) { + // cursor at end + *cursor = vcol + incr - 1; + } else { + vcol += virt_text_cursor_off(&cts, on_NUL); + // cursor at start + *cursor = vcol + head; + } + } +} + +/// Get virtual cursor column in the current window, pretending 'list' is off. +/// +/// @param posp +/// +/// @retujrn The virtual cursor column. +colnr_T getvcol_nolist(pos_T *posp) +{ + int list_save = curwin->w_p_list; + colnr_T vcol; + + curwin->w_p_list = false; + if (posp->coladd) { + getvvcol(curwin, posp, NULL, &vcol, NULL); + } else { + getvcol(curwin, posp, NULL, &vcol, NULL); + } + curwin->w_p_list = list_save; + return vcol; +} + +/// Get virtual column in virtual mode. +/// +/// @param wp +/// @param pos +/// @param start +/// @param cursor +/// @param end +void getvvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end) +{ + colnr_T col; + + if (virtual_active()) { + // For virtual mode, only want one value + getvcol(wp, pos, &col, NULL, NULL); + + colnr_T coladd = pos->coladd; + colnr_T endadd = 0; + + // Cannot put the cursor on part of a wide character. + char *ptr = ml_get_buf(wp->w_buffer, pos->lnum); + + if (pos->col < (colnr_T)strlen(ptr)) { + int c = utf_ptr2char(ptr + pos->col); + if ((c != TAB) && vim_isprintc(c)) { + endadd = (colnr_T)(char2cells(c) - 1); + if (coladd > endadd) { + // past end of line + endadd = 0; + } else { + coladd = 0; + } + } + } + col += coladd; + + if (start != NULL) { + *start = col; + } + + if (cursor != NULL) { + *cursor = col; + } + + if (end != NULL) { + *end = col + endadd; + } + } else { + getvcol(wp, pos, start, cursor, end); + } +} + +/// Get the leftmost and rightmost virtual column of pos1 and pos2. +/// Used for Visual block mode. +/// +/// @param wp +/// @param pos1 +/// @param pos2 +/// @param left +/// @param right +void getvcols(win_T *wp, pos_T *pos1, pos_T *pos2, colnr_T *left, colnr_T *right) +{ + colnr_T from1; + colnr_T from2; + colnr_T to1; + colnr_T to2; + + if (lt(*pos1, *pos2)) { + getvvcol(wp, pos1, &from1, NULL, &to1); + getvvcol(wp, pos2, &from2, NULL, &to2); + } else { + getvvcol(wp, pos2, &from1, NULL, &to1); + getvvcol(wp, pos1, &from2, NULL, &to2); + } + + if (from2 < from1) { + *left = from2; + } else { + *left = from1; + } + + if (to2 > to1) { + if ((*p_sel == 'e') && (from2 - 1 >= to1)) { + *right = from2 - 1; + } else { + *right = to2; + } + } else { + *right = to1; + } +} + +/// Functions calculating vertical size of text when displayed inside a window. +/// Calls horizontal size functions defined above. + +/// Check if there may be filler lines anywhere in window "wp". +bool win_may_fill(win_T *wp) +{ + return (wp->w_p_diff && diffopt_filler()) || wp->w_buffer->b_virt_line_blocks; +} + +/// Return the number of filler lines above "lnum". +/// +/// @param wp +/// @param lnum +/// +/// @return Number of filler lines above lnum +int win_get_fill(win_T *wp, linenr_T lnum) +{ + int virt_lines = decor_virt_lines(wp, lnum, NULL, kNone); + + // be quick when there are no filler lines + if (diffopt_filler()) { + int n = diff_check(wp, lnum); + + if (n > 0) { + return virt_lines + n; + } + } + return virt_lines; +} + +/// Return the number of window lines occupied by buffer line "lnum". +/// Includes any filler lines. +/// +/// @param limit_winheight when true limit to window height +int plines_win(win_T *wp, linenr_T lnum, bool limit_winheight) +{ + // Check for filler lines above this buffer line. + return plines_win_nofill(wp, lnum, limit_winheight) + win_get_fill(wp, lnum); +} + +/// Return the number of window lines occupied by buffer line "lnum". +/// Does not include filler lines. +/// +/// @param limit_winheight when true limit to window height +int plines_win_nofill(win_T *wp, linenr_T lnum, bool limit_winheight) +{ + if (!wp->w_p_wrap) { + return 1; + } + + if (wp->w_width_inner == 0) { + return 1; + } + + // Folded lines are handled just like an empty line. + if (lineFolded(wp, lnum)) { + return 1; + } + + const int lines = plines_win_nofold(wp, lnum); + if (limit_winheight && lines > wp->w_height_inner) { + return wp->w_height_inner; + } + return lines; +} + +/// Get number of window lines physical line "lnum" will occupy in window "wp". +/// Does not care about folding, 'wrap' or filler lines. +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) { + 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; + + // If list mode is on, then the '$' at the end of the line may take up one + // extra column. + if (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL) { + col += 1; + } + + // Add column offset for 'number', 'relativenumber' and 'foldcolumn'. + int width = wp->w_width_inner - win_col_off(wp); + if (width <= 0) { + return 32000; // bigger than the number of screen lines + } + if (col <= width) { + return 1; + } + col -= width; + width += win_col_off2(wp); + const int64_t lines = (col + (width - 1)) / width + 1; + return (lines > 0 && lines <= INT_MAX) ? (int)lines : INT_MAX; +} + +/// Like plines_win(), but only reports the number of physical screen lines +/// used from the start of the line to the given column number. +int plines_win_col(win_T *wp, linenr_T lnum, long column) +{ + // Check for filler lines above this buffer line. + int lines = win_get_fill(wp, lnum); + + if (!wp->w_p_wrap) { + return lines + 1; + } + + if (wp->w_width_inner == 0) { + return lines + 1; + } + + char *line = ml_get_buf(wp->w_buffer, lnum); + + colnr_T col = 0; + chartabsize_T cts; + + 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); + } + + // If *cts.cts_ptr 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; + } + clear_chartabsize_arg(&cts); + + // Add column offset for 'number', 'relativenumber', 'foldcolumn', etc. + int width = wp->w_width_inner - win_col_off(wp); + if (width <= 0) { + return 9999; + } + + lines += 1; + if (col > width) { + lines += (col - width) / (width + win_col_off2(wp)) + 1; + } + return lines; +} + +/// Get the number of screen lines buffer line "lnum" will take in window "wp". +/// This takes care of both folds and topfill. +/// +/// XXX: Because of topfill, this only makes sense when lnum >= wp->w_topline. +/// +/// @param[in] wp window the line is in +/// @param[in] lnum line number +/// @param[out] nextp if not NULL, the line after a fold +/// @param[out] foldedp if not NULL, whether lnum is on a fold +/// @param[in] cache whether to use the window's cache for folds +/// @param[in] limit_winheight when true limit to window height +/// +/// @return the total number of screen lines +int plines_win_full(win_T *wp, linenr_T lnum, linenr_T *const nextp, bool *const foldedp, + const bool cache, const bool limit_winheight) +{ + bool folded = hasFoldingWin(wp, lnum, &lnum, nextp, cache, NULL); + if (foldedp != NULL) { + *foldedp = folded; + } + return ((folded ? 1 : plines_win_nofill(wp, lnum, limit_winheight)) + + (lnum == wp->w_topline ? wp->w_topfill : win_get_fill(wp, lnum))); +} + +/// Get the number of screen lines a range of buffer lines will take in window "wp". +/// This takes care of both folds and topfill. +/// +/// XXX: Because of topfill, this only makes sense when first >= wp->w_topline. +/// +/// @param first first line number +/// @param last last line number +/// @param limit_winheight when true limit each line to window height +/// +/// @see win_text_height +int plines_m_win(win_T *wp, linenr_T first, linenr_T last, bool limit_winheight) +{ + int count = 0; + + while (first <= last) { + linenr_T next = first; + count += plines_win_full(wp, first, &next, NULL, false, limit_winheight); + first = next + 1; + } + return count; +} + +/// Get the number of screen lines a range of text will take in window "wp". +/// +/// @param[in] start_lnum Starting line number, 1-based inclusive. +/// @param[in] start_vcol >= 0: Starting virtual column index on "start_lnum", +/// 0-based inclusive, rounded down to full screen lines. +/// < 0: Count a full "start_lnum", including filler lines above. +/// @param[in] end_lnum Ending line number, 1-based inclusive. +/// @param[in] end_vcol >= 0: Ending virtual column index on "end_lnum", +/// 0-based exclusive, rounded up to full screen lines. +/// < 0: Count a full "end_lnum", not including filler lines below. +/// @param[out] fill If not NULL, set to the number of filler lines in the range. +int64_t win_text_height(win_T *const wp, const linenr_T start_lnum, const int64_t start_vcol, + const linenr_T end_lnum, const int64_t end_vcol, int64_t *const fill) +{ + int width1 = 0; + int width2 = 0; + if (start_vcol >= 0 || end_vcol >= 0) { + width1 = wp->w_width_inner - win_col_off(wp); + width2 = width1 + win_col_off2(wp); + width1 = MAX(width1, 0); + width2 = MAX(width2, 0); + } + + int64_t height_sum_fill = 0; + int64_t height_cur_nofill = 0; + int64_t height_sum_nofill = 0; + linenr_T lnum = start_lnum; + + if (start_vcol >= 0) { + linenr_T lnum_next = lnum; + const bool folded = hasFoldingWin(wp, lnum, &lnum, &lnum_next, true, NULL); + height_cur_nofill = folded ? 1 : plines_win_nofill(wp, lnum, false); + height_sum_nofill += height_cur_nofill; + const int64_t row_off = (start_vcol < width1 || width2 <= 0) + ? 0 + : 1 + (start_vcol - width1) / width2; + height_sum_nofill -= MIN(row_off, height_cur_nofill); + lnum = lnum_next + 1; + } + + while (lnum <= end_lnum) { + linenr_T lnum_next = lnum; + const bool folded = hasFoldingWin(wp, lnum, &lnum, &lnum_next, true, NULL); + height_sum_fill += win_get_fill(wp, lnum); + height_cur_nofill = folded ? 1 : plines_win_nofill(wp, lnum, false); + height_sum_nofill += height_cur_nofill; + lnum = lnum_next + 1; + } + + if (end_vcol >= 0) { + height_sum_nofill -= height_cur_nofill; + const int64_t row_off = end_vcol == 0 + ? 0 + : (end_vcol <= width1 || width2 <= 0) + ? 1 + : 1 + (end_vcol - width1 + width2 - 1) / width2; + height_sum_nofill += MIN(row_off, height_cur_nofill); + } + + if (fill != NULL) { + *fill = height_sum_fill; + } + return height_sum_fill + height_sum_nofill; +} |