diff options
author | bfredl <bjorn.linse@gmail.com> | 2023-03-14 11:49:46 +0100 |
---|---|---|
committer | bfredl <bjorn.linse@gmail.com> | 2023-03-14 13:37:43 +0100 |
commit | d6ecead36406233cc56353dd05f3380f0497630f (patch) | |
tree | 6adad28d9a446e422f114d285107595c563760a8 /src/nvim/screen.c | |
parent | ef31444cccdd93f515a8b7a968268cb04e680370 (diff) | |
download | rneovim-d6ecead36406233cc56353dd05f3380f0497630f.tar.gz rneovim-d6ecead36406233cc56353dd05f3380f0497630f.tar.bz2 rneovim-d6ecead36406233cc56353dd05f3380f0497630f.zip |
refactor(screen): screen.c delenda est
drawscreen.c vs screen.c makes absolutely no sense.
The screen exists only to draw upon it, therefore helper functions
are distributed randomly between screen.c and the file that
does the redrawing. In addition screen.c does a lot of drawing on the
screen.
It made more sense for vim/vim as our grid.c is their screen.c
Not sure if we want to dump all the code for option chars into
optionstr.c, so keep these in a optionchar.c for now.
Diffstat (limited to 'src/nvim/screen.c')
-rw-r--r-- | src/nvim/screen.c | 1098 |
1 files changed, 0 insertions, 1098 deletions
diff --git a/src/nvim/screen.c b/src/nvim/screen.c deleted file mode 100644 index 718c4f3d95..0000000000 --- a/src/nvim/screen.c +++ /dev/null @@ -1,1098 +0,0 @@ -// 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 - -// screen.c: Lower level code for displaying on the screen. -// grid.c contains some other lower-level code. - -// Output to the screen (console, terminal emulator or GUI window) is minimized -// by remembering what is already on the screen, and only updating the parts -// that changed. - -#include <assert.h> -#include <inttypes.h> -#include <limits.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "nvim/ascii.h" -#include "nvim/buffer.h" -#include "nvim/charset.h" -#include "nvim/cursor.h" -#include "nvim/eval.h" -#include "nvim/fold.h" -#include "nvim/getchar.h" -#include "nvim/gettext.h" -#include "nvim/globals.h" -#include "nvim/grid.h" -#include "nvim/grid_defs.h" -#include "nvim/highlight.h" -#include "nvim/mbyte.h" -#include "nvim/memline_defs.h" -#include "nvim/memory.h" -#include "nvim/message.h" -#include "nvim/move.h" -#include "nvim/normal.h" -#include "nvim/option.h" -#include "nvim/os/os.h" -#include "nvim/os/time.h" -#include "nvim/pos.h" -#include "nvim/profile.h" -#include "nvim/regexp.h" -#include "nvim/screen.h" -#include "nvim/search.h" -#include "nvim/state.h" -#include "nvim/statusline.h" -#include "nvim/strings.h" -#include "nvim/types.h" -#include "nvim/ui.h" -#include "nvim/vim.h" -#include "nvim/window.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "screen.c.generated.h" -#endif - -static char e_conflicts_with_value_of_listchars[] = N_("E834: Conflicts with value of 'listchars'"); -static char e_conflicts_with_value_of_fillchars[] = N_("E835: Conflicts with value of 'fillchars'"); - -/// Return true if the cursor line in window "wp" may be concealed, according -/// to the 'concealcursor' option. -bool conceal_cursor_line(const win_T *wp) - FUNC_ATTR_NONNULL_ALL -{ - int c; - - if (*wp->w_p_cocu == NUL) { - return false; - } - if (get_real_state() & MODE_VISUAL) { - c = 'v'; - } else if (State & MODE_INSERT) { - c = 'i'; - } else if (State & MODE_NORMAL) { - c = 'n'; - } else if (State & MODE_CMDLINE) { - c = 'c'; - } else { - return false; - } - return vim_strchr(wp->w_p_cocu, c) != NULL; -} - -/// Whether cursorline is drawn in a special way -/// -/// If true, both old and new cursorline will need to be redrawn when moving cursor within windows. -bool win_cursorline_standout(const win_T *wp) - FUNC_ATTR_NONNULL_ALL -{ - return wp->w_p_cul || (wp->w_p_cole > 0 && !conceal_cursor_line(wp)); -} - -/// Returns width of the signcolumn that should be used for the whole window -/// -/// @param wp window we want signcolumn width from -/// @return max width of signcolumn (cell unit) -/// -/// @note Returns a constant for now but hopefully we can improve neovim so that -/// the returned value width adapts to the maximum number of marks to draw -/// for the window -/// TODO(teto) -int win_signcol_width(win_T *wp) -{ - // 2 is vim default value - return 2; -} - -/// Call grid_fill() with columns adjusted for 'rightleft' if needed. -/// Return the new offset. -static int win_fill_end(win_T *wp, int c1, int c2, int off, int width, int row, int endrow, - int attr) -{ - int nn = off + width; - const int endcol = wp->w_grid.cols; - - if (nn > endcol) { - nn = endcol; - } - - if (wp->w_p_rl) { - grid_fill(&wp->w_grid, row, endrow, endcol - nn, endcol - off, c1, c2, attr); - } else { - grid_fill(&wp->w_grid, row, endrow, off, nn, c1, c2, attr); - } - - return nn; -} - -/// Clear lines near the end of the window and mark the unused lines with "c1". -/// Use "c2" as filler character. -/// When "draw_margin" is true, then draw the sign/fold/number columns. -void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, int endrow, hlf_T hl) -{ - assert(hl >= 0 && hl < HLF_COUNT); - int n = 0; - - if (draw_margin) { - // draw the fold column - int fdc = compute_foldcolumn(wp, 0); - if (fdc > 0) { - n = win_fill_end(wp, ' ', ' ', n, fdc, row, endrow, - win_hl_attr(wp, HLF_FC)); - } - // draw the sign column - int count = wp->w_scwidth; - if (count > 0) { - n = win_fill_end(wp, ' ', ' ', n, win_signcol_width(wp) * count, row, - endrow, win_hl_attr(wp, HLF_SC)); - } - // draw the number column - if ((wp->w_p_nu || wp->w_p_rnu) && vim_strchr(p_cpo, CPO_NUMCOL) == NULL) { - n = win_fill_end(wp, ' ', ' ', n, number_width(wp) + 1, row, endrow, - win_hl_attr(wp, HLF_N)); - } - } - - int attr = hl_combine_attr(win_bg_attr(wp), win_hl_attr(wp, (int)hl)); - - const int endcol = wp->w_grid.cols; - if (wp->w_p_rl) { - grid_fill(&wp->w_grid, row, endrow, 0, endcol - 1 - n, c2, c2, attr); - grid_fill(&wp->w_grid, row, endrow, endcol - 1 - n, endcol - n, c1, c2, attr); - } else { - grid_fill(&wp->w_grid, row, endrow, n, endcol, c1, c2, attr); - } -} - -/// Compute the width of the foldcolumn. Based on 'foldcolumn' and how much -/// space is available for window "wp", minus "col". -int compute_foldcolumn(win_T *wp, int col) -{ - int fdc = win_fdccol_count(wp); - int wmw = wp == curwin && p_wmw == 0 ? 1 : (int)p_wmw; - int wwidth = wp->w_grid.cols; - - if (fdc > wwidth - (col + wmw)) { - fdc = wwidth - (col + wmw); - } - return fdc; -} - -/// Fills the foldcolumn at "p" for window "wp". -/// Only to be called when 'foldcolumn' > 0. -/// -/// @param[out] p Char array to write into -/// @param lnum Absolute current line number -/// @param closed Whether it is in 'foldcolumn' mode -/// -/// Assume monocell characters -/// @return number of chars added to \param p -size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum) -{ - int i = 0; - int level; - int first_level; - int fdc = compute_foldcolumn(wp, 0); // available cell width - size_t char_counter = 0; - int symbol = 0; - int len = 0; - bool closed = foldinfo.fi_lines > 0; - // Init to all spaces. - memset(p, ' ', MAX_MCO * (size_t)fdc + 1); - - level = foldinfo.fi_level; - - // If the column is too narrow, we start at the lowest level that - // fits and use numbers to indicate the depth. - first_level = level - fdc - closed + 1; - if (first_level < 1) { - first_level = 1; - } - - for (i = 0; i < MIN(fdc, level); i++) { - if (foldinfo.fi_lnum == lnum - && first_level + i >= foldinfo.fi_low_level) { - symbol = wp->w_p_fcs_chars.foldopen; - } else if (first_level == 1) { - symbol = wp->w_p_fcs_chars.foldsep; - } else if (first_level + i <= 9) { - symbol = '0' + first_level + i; - } else { - symbol = '>'; - } - - len = utf_char2bytes(symbol, &p[char_counter]); - char_counter += (size_t)len; - if (first_level + i >= level) { - i++; - break; - } - } - - if (closed) { - if (symbol != 0) { - // rollback previous write - char_counter -= (size_t)len; - memset(&p[char_counter], ' ', (size_t)len); - } - len = utf_char2bytes(wp->w_p_fcs_chars.foldclosed, &p[char_counter]); - char_counter += (size_t)len; - } - - return MAX(char_counter + (size_t)(fdc - i), (size_t)fdc); -} - -/// Mirror text "str" for right-left displaying. -/// Only works for single-byte characters (e.g., numbers). -void rl_mirror(char *str) -{ - for (char *p1 = str, *p2 = str + strlen(str) - 1; p1 < p2; p1++, p2--) { - char t = *p1; - *p1 = *p2; - *p2 = t; - } -} - -/// Only call if (wp->w_vsep_width != 0). -/// -/// @return true if the status line of window "wp" is connected to the status -/// line of the window right of it. If not, then it's a vertical separator. -bool stl_connected(win_T *wp) -{ - frame_T *fr = wp->w_frame; - while (fr->fr_parent != NULL) { - if (fr->fr_parent->fr_layout == FR_COL) { - if (fr->fr_next != NULL) { - break; - } - } else { - if (fr->fr_next != NULL) { - return true; - } - } - fr = fr->fr_parent; - } - return false; -} - -/// Get the value to show for the language mappings, active 'keymap'. -/// -/// @param fmt format string containing one %s item -/// @param buf buffer for the result -/// @param len length of buffer -bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len) -{ - char *p; - - if (wp->w_buffer->b_p_iminsert != B_IMODE_LMAP) { - return false; - } - - buf_T *old_curbuf = curbuf; - win_T *old_curwin = curwin; - char *s; - - curbuf = wp->w_buffer; - curwin = wp; - STRCPY(buf, "b:keymap_name"); // must be writable - emsg_skip++; - s = p = eval_to_string(buf, NULL, false); - emsg_skip--; - curbuf = old_curbuf; - curwin = old_curwin; - if (p == NULL || *p == NUL) { - if (wp->w_buffer->b_kmap_state & KEYMAP_LOADED) { - p = wp->w_buffer->b_p_keymap; - } else { - p = "lang"; - } - } - if (vim_snprintf(buf, (size_t)len, fmt, p) > len - 1) { - buf[0] = NUL; - } - xfree(s); - return buf[0] != NUL; -} - -/// Prepare for 'hlsearch' highlighting. -void start_search_hl(void) -{ - if (!p_hls || no_hlsearch) { - return; - } - - end_search_hl(); // just in case it wasn't called before - last_pat_prog(&screen_search_hl.rm); - // Set the time limit to 'redrawtime'. - screen_search_hl.tm = profile_setlimit(p_rdt); -} - -/// Clean up for 'hlsearch' highlighting. -void end_search_hl(void) -{ - if (screen_search_hl.rm.regprog == NULL) { - return; - } - - vim_regfree(screen_search_hl.rm.regprog); - screen_search_hl.rm.regprog = NULL; -} - -/// Check if there should be a delay. Used before clearing or redrawing the -/// screen or the command line. -void check_for_delay(bool check_msg_scroll) -{ - if ((emsg_on_display || (check_msg_scroll && msg_scroll)) - && !did_wait_return - && emsg_silent == 0 - && !in_assert_fails) { - ui_flush(); - os_delay(1006L, true); - emsg_on_display = false; - if (check_msg_scroll) { - msg_scroll = false; - } - } -} - -/// Set cursor to its position in the current window. -void setcursor(void) -{ - setcursor_mayforce(false); -} - -/// Set cursor to its position in the current window. -/// @param force when true, also when not redrawing. -void setcursor_mayforce(bool force) -{ - if (force || redrawing()) { - validate_cursor(); - - ScreenGrid *grid = &curwin->w_grid; - int row = curwin->w_wrow; - int col = curwin->w_wcol; - if (curwin->w_p_rl) { - // With 'rightleft' set and the cursor on a double-wide character, - // position it on the leftmost column. - col = curwin->w_width_inner - curwin->w_wcol - - ((utf_ptr2cells(get_cursor_pos_ptr()) == 2 - && vim_isprintc(gchar_cursor())) ? 2 : 1); - } - - grid_adjust(&grid, &row, &col); - ui_grid_cursor_goto(grid->handle, row, col); - } -} - -/// Scroll `line_count` lines at 'row' in window 'wp'. -/// -/// Positive `line_count` means scrolling down, so that more space is available -/// at 'row'. Negative `line_count` implies deleting lines at `row`. -void win_scroll_lines(win_T *wp, int row, int line_count) -{ - if (!redrawing() || line_count == 0) { - return; - } - - // No lines are being moved, just draw over the entire area - if (row + abs(line_count) >= wp->w_grid.rows) { - return; - } - - if (line_count < 0) { - grid_del_lines(&wp->w_grid, row, -line_count, - wp->w_grid.rows, 0, wp->w_grid.cols); - } else { - grid_ins_lines(&wp->w_grid, row, line_count, - wp->w_grid.rows, 0, wp->w_grid.cols); - } -} - -/// @return true when postponing displaying the mode message: when not redrawing -/// or inside a mapping. -bool skip_showmode(void) -{ - // Call char_avail() only when we are going to show something, because it - // takes a bit of time. redrawing() may also call char_avail(). - if (global_busy || msg_silent != 0 || !redrawing() || (char_avail() && !KeyTyped)) { - redraw_mode = true; // show mode later - return true; - } - return false; -} - -/// Show the current mode and ruler. -/// -/// If clear_cmdline is true, clear the rest of the cmdline. -/// If clear_cmdline is false there may be a message there that needs to be -/// cleared only if a mode is shown. -/// If redraw_mode is true show or clear the mode. -/// @return the length of the message (0 if no message). -int showmode(void) -{ - int length = 0; - - if (ui_has(kUIMessages) && clear_cmdline) { - msg_ext_clear(true); - } - - // don't make non-flushed message part of the showmode - msg_ext_ui_flush(); - - msg_grid_validate(); - - int do_mode = ((p_smd && msg_silent == 0) - && ((State & MODE_TERMINAL) - || (State & MODE_INSERT) - || restart_edit != NUL - || VIsual_active)); - if (do_mode || reg_recording != 0) { - int sub_attr; - if (skip_showmode()) { - return 0; // show mode later - } - - bool nwr_save = need_wait_return; - - // wait a bit before overwriting an important message - check_for_delay(false); - - // if the cmdline is more than one line high, erase top lines - bool need_clear = clear_cmdline; - if (clear_cmdline && cmdline_row < Rows - 1) { - msg_clr_cmdline(); // will reset clear_cmdline - } - - // Position on the last line in the window, column 0 - msg_pos_mode(); - int attr = HL_ATTR(HLF_CM); // Highlight mode - - // When the screen is too narrow to show the entire mode message, - // avoid scrolling and truncate instead. - msg_no_more = true; - int save_lines_left = lines_left; - lines_left = 0; - - if (do_mode) { - msg_puts_attr("--", attr); - // CTRL-X in Insert mode - if (edit_submode != NULL && !shortmess(SHM_COMPLETIONMENU)) { - // These messages can get long, avoid a wrap in a narrow window. - // Prefer showing edit_submode_extra. With external messages there - // is no imposed limit. - if (ui_has(kUIMessages)) { - length = INT_MAX; - } else { - length = (Rows - msg_row) * Columns - 3; - } - if (edit_submode_extra != NULL) { - length -= vim_strsize(edit_submode_extra); - } - if (length > 0) { - if (edit_submode_pre != NULL) { - length -= vim_strsize(edit_submode_pre); - } - if (length - vim_strsize(edit_submode) > 0) { - if (edit_submode_pre != NULL) { - msg_puts_attr((const char *)edit_submode_pre, attr); - } - msg_puts_attr((const char *)edit_submode, attr); - } - if (edit_submode_extra != NULL) { - msg_puts_attr(" ", attr); // Add a space in between. - if ((int)edit_submode_highl < HLF_COUNT) { - sub_attr = win_hl_attr(curwin, (int)edit_submode_highl); - } else { - sub_attr = attr; - } - msg_puts_attr((const char *)edit_submode_extra, sub_attr); - } - } - } else { - if (State & MODE_TERMINAL) { - msg_puts_attr(_(" TERMINAL"), attr); - } else if (State & VREPLACE_FLAG) { - msg_puts_attr(_(" VREPLACE"), attr); - } else if (State & REPLACE_FLAG) { - msg_puts_attr(_(" REPLACE"), attr); - } else if (State & MODE_INSERT) { - if (p_ri) { - msg_puts_attr(_(" REVERSE"), attr); - } - msg_puts_attr(_(" INSERT"), attr); - } else if (restart_edit == 'I' || restart_edit == 'i' - || restart_edit == 'a' || restart_edit == 'A') { - if (curbuf->terminal) { - msg_puts_attr(_(" (terminal)"), attr); - } else { - msg_puts_attr(_(" (insert)"), attr); - } - } else if (restart_edit == 'R') { - msg_puts_attr(_(" (replace)"), attr); - } else if (restart_edit == 'V') { - msg_puts_attr(_(" (vreplace)"), attr); - } - if (State & MODE_LANGMAP) { - if (curwin->w_p_arab) { - msg_puts_attr(_(" Arabic"), attr); - } else if (get_keymap_str(curwin, " (%s)", - NameBuff, MAXPATHL)) { - msg_puts_attr(NameBuff, attr); - } - } - if ((State & MODE_INSERT) && p_paste) { - msg_puts_attr(_(" (paste)"), attr); - } - - if (VIsual_active) { - char *p; - - // Don't concatenate separate words to avoid translation - // problems. - switch ((VIsual_select ? 4 : 0) - + (VIsual_mode == Ctrl_V) * 2 - + (VIsual_mode == 'V')) { - case 0: - p = N_(" VISUAL"); break; - case 1: - p = N_(" VISUAL LINE"); break; - case 2: - p = N_(" VISUAL BLOCK"); break; - case 4: - p = N_(" SELECT"); break; - case 5: - p = N_(" SELECT LINE"); break; - default: - p = N_(" SELECT BLOCK"); break; - } - msg_puts_attr(_(p), attr); - } - msg_puts_attr(" --", attr); - } - - need_clear = true; - } - if (reg_recording != 0 - && edit_submode == NULL // otherwise it gets too long - ) { - recording_mode(attr); - need_clear = true; - } - - mode_displayed = true; - if (need_clear || clear_cmdline || redraw_mode) { - msg_clr_eos(); - } - msg_didout = false; // overwrite this message - length = msg_col; - msg_col = 0; - msg_no_more = false; - lines_left = save_lines_left; - need_wait_return = nwr_save; // never ask for hit-return for this - } else if (clear_cmdline && msg_silent == 0) { - // Clear the whole command line. Will reset "clear_cmdline". - msg_clr_cmdline(); - } else if (redraw_mode) { - msg_pos_mode(); - msg_clr_eos(); - } - - // NB: also handles clearing the showmode if it was empty or disabled - msg_ext_flush_showmode(); - - // In Visual mode the size of the selected area must be redrawn. - if (VIsual_active) { - clear_showcmd(); - } - - // If the last window has no status line and global statusline is disabled, - // the ruler is after the mode message and must be redrawn - win_T *last = curwin->w_floating ? curwin : lastwin_nofloating(); - if (redrawing() && last->w_status_height == 0 && global_stl_height() == 0) { - win_redr_ruler(last); - } - - redraw_cmdline = false; - redraw_mode = false; - clear_cmdline = false; - - return length; -} - -/// Position for a mode message. -static void msg_pos_mode(void) -{ - msg_col = 0; - msg_row = Rows - 1; -} - -/// Delete mode message. Used when ESC is typed which is expected to end -/// Insert mode (but Insert mode didn't end yet!). -/// Caller should check "mode_displayed". -void unshowmode(bool force) -{ - // Don't delete it right now, when not redrawing or inside a mapping. - if (!redrawing() || (!force && char_avail() && !KeyTyped)) { - redraw_cmdline = true; // delete mode later - } else { - clearmode(); - } -} - -// Clear the mode message. -void clearmode(void) -{ - const int save_msg_row = msg_row; - const int save_msg_col = msg_col; - - msg_ext_ui_flush(); - msg_pos_mode(); - if (reg_recording != 0) { - recording_mode(HL_ATTR(HLF_CM)); - } - msg_clr_eos(); - msg_ext_flush_showmode(); - - msg_col = save_msg_col; - msg_row = save_msg_row; -} - -static void recording_mode(int attr) -{ - msg_puts_attr(_("recording"), attr); - if (shortmess(SHM_RECORDING)) { - return; - } - - char s[4]; - snprintf(s, ARRAY_SIZE(s), " @%c", reg_recording); - msg_puts_attr(s, attr); -} - -void get_trans_bufname(buf_T *buf) -{ - if (buf_spname(buf) != NULL) { - xstrlcpy(NameBuff, buf_spname(buf), MAXPATHL); - } else { - home_replace(buf, buf->b_fname, NameBuff, MAXPATHL, true); - } - trans_characters(NameBuff, MAXPATHL); -} - -/// Get the character to use in a separator between vertically split windows. -/// Get its attributes in "*attr". -int fillchar_vsep(win_T *wp, int *attr) -{ - *attr = win_hl_attr(wp, HLF_C); - return wp->w_p_fcs_chars.vert; -} - -/// Get the character to use in a separator between horizontally split windows. -/// Get its attributes in "*attr". -int fillchar_hsep(win_T *wp, int *attr) -{ - *attr = win_hl_attr(wp, HLF_C); - return wp->w_p_fcs_chars.horiz; -} - -/// Return true if redrawing should currently be done. -bool redrawing(void) -{ - return !RedrawingDisabled - && !(p_lz && char_avail() && !KeyTyped && !do_redraw); -} - -/// Return true if printing messages should currently be done. -bool messaging(void) -{ - // TODO(bfredl): with general support for "async" messages with p_ch, - // this should be re-enabled. - return !(p_lz && char_avail() && !KeyTyped) && (p_ch > 0 || ui_has(kUIMessages)); -} - -#define COL_RULER 17 // columns needed by standard ruler - -/// Compute columns for ruler and shown command. 'sc_col' is also used to -/// decide what the maximum length of a message on the status line can be. -/// If there is a status line for the last window, 'sc_col' is independent -/// of 'ru_col'. -void comp_col(void) -{ - int last_has_status = (p_ls > 1 || (p_ls == 1 && !ONE_WINDOW)); - - sc_col = 0; - ru_col = 0; - if (p_ru) { - ru_col = (ru_wid ? ru_wid : COL_RULER) + 1; - // no last status line, adjust sc_col - if (!last_has_status) { - sc_col = ru_col; - } - } - if (p_sc) { - sc_col += SHOWCMD_COLS; - if (!p_ru || last_has_status) { // no need for separating space - sc_col++; - } - } - assert(sc_col >= 0 - && INT_MIN + sc_col <= Columns); - sc_col = Columns - sc_col; - assert(ru_col >= 0 - && INT_MIN + ru_col <= Columns); - ru_col = Columns - ru_col; - if (sc_col <= 0) { // screen too narrow, will become a mess - sc_col = 1; - } - if (ru_col <= 0) { - ru_col = 1; - } - set_vim_var_nr(VV_ECHOSPACE, sc_col - 1); -} - -/// Return the width of the 'number' and 'relativenumber' column. -/// Caller may need to check if 'number' or 'relativenumber' is set. -/// Otherwise it depends on 'numberwidth' and the line count. -int number_width(win_T *wp) -{ - linenr_T lnum; - - if (wp->w_p_rnu && !wp->w_p_nu) { - // cursor line shows "0" - lnum = wp->w_height_inner; - } else { - // cursor line shows absolute line number - lnum = wp->w_buffer->b_ml.ml_line_count; - } - - if (lnum == wp->w_nrwidth_line_count) { - return wp->w_nrwidth_width; - } - wp->w_nrwidth_line_count = lnum; - - // reset for 'statuscolumn' - if (*wp->w_p_stc != NUL) { - wp->w_nrwidth_width = (wp->w_p_nu || wp->w_p_rnu) * (int)wp->w_p_nuw; - return wp->w_nrwidth_width; - } - - int n = 0; - do { - lnum /= 10; - n++; - } while (lnum > 0); - - // 'numberwidth' gives the minimal width plus one - if (n < wp->w_p_nuw - 1) { - n = (int)wp->w_p_nuw - 1; - } - - // If 'signcolumn' is set to 'number' and there is a sign to display, then - // the minimal width for the number column is 2. - if (n < 2 && (wp->w_buffer->b_signlist != NULL) - && (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u')) { - n = 2; - } - - wp->w_nrwidth_width = n; - return n; -} - -/// Calls mb_cptr2char_adv(p) and returns the character. -/// If "p" starts with "\x", "\u" or "\U" the hex or unicode value is used. -/// Returns 0 for invalid hex or invalid UTF-8 byte. -static int get_encoded_char_adv(const char **p) -{ - const char *s = *p; - - if (s[0] == '\\' && (s[1] == 'x' || s[1] == 'u' || s[1] == 'U')) { - int64_t num = 0; - for (int bytes = s[1] == 'x' ? 1 : s[1] == 'u' ? 2 : 4; bytes > 0; bytes--) { - *p += 2; - int n = hexhex2nr(*p); - if (n < 0) { - return 0; - } - num = num * 256 + n; - } - *p += 2; - return (int)num; - } - - // TODO(bfredl): use schar_T representation and utfc_ptr2len - int clen = utf_ptr2len(s); - int c = mb_cptr2char_adv(p); - if (clen == 1 && c > 127) { // Invalid UTF-8 byte - return 0; - } - return c; -} - -/// Handle setting 'listchars' or 'fillchars'. -/// Assume monocell characters -/// -/// @param varp either the global or the window-local value. -/// @param apply if false, do not store the flags, only check for errors. -/// @return error message, NULL if it's OK. -char *set_chars_option(win_T *wp, char **varp, bool apply) -{ - const char *last_multispace = NULL; // Last occurrence of "multispace:" - const char *last_lmultispace = NULL; // Last occurrence of "leadmultispace:" - int multispace_len = 0; // Length of lcs-multispace string - int lead_multispace_len = 0; // Length of lcs-leadmultispace string - const bool is_listchars = (varp == &p_lcs || varp == &wp->w_p_lcs); - - struct chars_tab { - int *cp; ///< char value - char *name; ///< char id - int def; ///< default value - }; - - // XXX: Characters taking 2 columns is forbidden (TUI limitation?). Set old defaults in this case. - struct chars_tab fcs_tab[] = { - { &wp->w_p_fcs_chars.stl, "stl", ' ' }, - { &wp->w_p_fcs_chars.stlnc, "stlnc", ' ' }, - { &wp->w_p_fcs_chars.wbr, "wbr", ' ' }, - { &wp->w_p_fcs_chars.horiz, "horiz", char2cells(0x2500) == 1 ? 0x2500 : '-' }, // ─ - { &wp->w_p_fcs_chars.horizup, "horizup", char2cells(0x2534) == 1 ? 0x2534 : '-' }, // ┴ - { &wp->w_p_fcs_chars.horizdown, "horizdown", char2cells(0x252c) == 1 ? 0x252c : '-' }, // ┬ - { &wp->w_p_fcs_chars.vert, "vert", char2cells(0x2502) == 1 ? 0x2502 : '|' }, // │ - { &wp->w_p_fcs_chars.vertleft, "vertleft", char2cells(0x2524) == 1 ? 0x2524 : '|' }, // ┤ - { &wp->w_p_fcs_chars.vertright, "vertright", char2cells(0x251c) == 1 ? 0x251c : '|' }, // ├ - { &wp->w_p_fcs_chars.verthoriz, "verthoriz", char2cells(0x253c) == 1 ? 0x253c : '+' }, // ┼ - { &wp->w_p_fcs_chars.fold, "fold", char2cells(0x00b7) == 1 ? 0x00b7 : '-' }, // · - { &wp->w_p_fcs_chars.foldopen, "foldopen", '-' }, - { &wp->w_p_fcs_chars.foldclosed, "foldclose", '+' }, - { &wp->w_p_fcs_chars.foldsep, "foldsep", char2cells(0x2502) == 1 ? 0x2502 : '|' }, // │ - { &wp->w_p_fcs_chars.diff, "diff", '-' }, - { &wp->w_p_fcs_chars.msgsep, "msgsep", ' ' }, - { &wp->w_p_fcs_chars.eob, "eob", '~' }, - { &wp->w_p_fcs_chars.lastline, "lastline", '@' }, - }; - - struct chars_tab lcs_tab[] = { - { &wp->w_p_lcs_chars.eol, "eol", NUL }, - { &wp->w_p_lcs_chars.ext, "extends", NUL }, - { &wp->w_p_lcs_chars.nbsp, "nbsp", NUL }, - { &wp->w_p_lcs_chars.prec, "precedes", NUL }, - { &wp->w_p_lcs_chars.space, "space", NUL }, - { &wp->w_p_lcs_chars.tab2, "tab", NUL }, - { &wp->w_p_lcs_chars.lead, "lead", NUL }, - { &wp->w_p_lcs_chars.trail, "trail", NUL }, - { &wp->w_p_lcs_chars.conceal, "conceal", NUL }, - }; - - struct chars_tab *tab; - int entries; - const char *value = *varp; - if (is_listchars) { - tab = lcs_tab; - entries = ARRAY_SIZE(lcs_tab); - if (varp == &wp->w_p_lcs && wp->w_p_lcs[0] == NUL) { - value = p_lcs; // local value is empty, use the global value - } - } else { - tab = fcs_tab; - entries = ARRAY_SIZE(fcs_tab); - if (varp == &wp->w_p_fcs && wp->w_p_fcs[0] == NUL) { - value = p_fcs; // local value is empty, use the global value - } - } - - // first round: check for valid value, second round: assign values - for (int round = 0; round <= (apply ? 1 : 0); round++) { - if (round > 0) { - // After checking that the value is valid: set defaults - for (int i = 0; i < entries; i++) { - if (tab[i].cp != NULL) { - *(tab[i].cp) = tab[i].def; - } - } - if (is_listchars) { - wp->w_p_lcs_chars.tab1 = NUL; - wp->w_p_lcs_chars.tab3 = NUL; - - xfree(wp->w_p_lcs_chars.multispace); - if (multispace_len > 0) { - wp->w_p_lcs_chars.multispace = xmalloc(((size_t)multispace_len + 1) * sizeof(int)); - wp->w_p_lcs_chars.multispace[multispace_len] = NUL; - } else { - wp->w_p_lcs_chars.multispace = NULL; - } - - xfree(wp->w_p_lcs_chars.leadmultispace); - if (lead_multispace_len > 0) { - wp->w_p_lcs_chars.leadmultispace - = xmalloc(((size_t)lead_multispace_len + 1) * sizeof(int)); - wp->w_p_lcs_chars.leadmultispace[lead_multispace_len] = NUL; - } else { - wp->w_p_lcs_chars.leadmultispace = NULL; - } - } - } - const char *p = value; - while (*p) { - int i; - for (i = 0; i < entries; i++) { - const size_t len = strlen(tab[i].name); - if (strncmp(p, tab[i].name, len) == 0 - && p[len] == ':' - && p[len + 1] != NUL) { - const char *s = p + len + 1; - int c1 = get_encoded_char_adv(&s); - if (c1 == 0 || char2cells(c1) > 1) { - return e_invarg; - } - int c2 = 0, c3 = 0; - if (tab[i].cp == &wp->w_p_lcs_chars.tab2) { - if (*s == NUL) { - return e_invarg; - } - c2 = get_encoded_char_adv(&s); - if (c2 == 0 || char2cells(c2) > 1) { - return e_invarg; - } - if (!(*s == ',' || *s == NUL)) { - c3 = get_encoded_char_adv(&s); - if (c3 == 0 || char2cells(c3) > 1) { - return e_invarg; - } - } - } - if (*s == ',' || *s == NUL) { - if (round > 0) { - if (tab[i].cp == &wp->w_p_lcs_chars.tab2) { - wp->w_p_lcs_chars.tab1 = c1; - wp->w_p_lcs_chars.tab2 = c2; - wp->w_p_lcs_chars.tab3 = c3; - } else if (tab[i].cp != NULL) { - *(tab[i].cp) = c1; - } - } - p = s; - break; - } - } - } - - if (i == entries) { - const size_t len = strlen("multispace"); - const size_t len2 = strlen("leadmultispace"); - if (is_listchars - && strncmp(p, "multispace", len) == 0 - && p[len] == ':' - && p[len + 1] != NUL) { - const char *s = p + len + 1; - if (round == 0) { - // Get length of lcs-multispace string in the first round - last_multispace = p; - multispace_len = 0; - while (*s != NUL && *s != ',') { - int c1 = get_encoded_char_adv(&s); - if (c1 == 0 || char2cells(c1) > 1) { - return e_invarg; - } - multispace_len++; - } - if (multispace_len == 0) { - // lcs-multispace cannot be an empty string - return e_invarg; - } - p = s; - } else { - int multispace_pos = 0; - while (*s != NUL && *s != ',') { - int c1 = get_encoded_char_adv(&s); - if (p == last_multispace) { - wp->w_p_lcs_chars.multispace[multispace_pos++] = c1; - } - } - p = s; - } - } else if (is_listchars - && strncmp(p, "leadmultispace", len2) == 0 - && p[len2] == ':' - && p[len2 + 1] != NUL) { - const char *s = p + len2 + 1; - if (round == 0) { - // get length of lcs-leadmultispace string in first round - last_lmultispace = p; - lead_multispace_len = 0; - while (*s != NUL && *s != ',') { - int c1 = get_encoded_char_adv(&s); - if (c1 == 0 || char2cells(c1) > 1) { - return e_invarg; - } - lead_multispace_len++; - } - if (lead_multispace_len == 0) { - // lcs-leadmultispace cannot be an empty string - return e_invarg; - } - p = s; - } else { - int multispace_pos = 0; - while (*s != NUL && *s != ',') { - int c1 = get_encoded_char_adv(&s); - if (p == last_lmultispace) { - wp->w_p_lcs_chars.leadmultispace[multispace_pos++] = c1; - } - } - p = s; - } - } else { - return e_invarg; - } - } - - if (*p == ',') { - p++; - } - } - } - - return NULL; // no error -} - -/// Check all global and local values of 'listchars' and 'fillchars'. -/// May set different defaults in case character widths change. -/// -/// @return an untranslated error message if any of them is invalid, NULL otherwise. -char *check_chars_options(void) -{ - if (set_chars_option(curwin, &p_lcs, false) != NULL) { - return e_conflicts_with_value_of_listchars; - } - if (set_chars_option(curwin, &p_fcs, false) != NULL) { - return e_conflicts_with_value_of_fillchars; - } - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (set_chars_option(wp, &wp->w_p_lcs, true) != NULL) { - return e_conflicts_with_value_of_listchars; - } - if (set_chars_option(wp, &wp->w_p_fcs, true) != NULL) { - return e_conflicts_with_value_of_fillchars; - } - } - return NULL; -} - -/// Check if the new Nvim application "screen" dimensions are valid. -/// Correct it if it's too small or way too big. -void check_screensize(void) -{ - // Limit Rows and Columns to avoid an overflow in Rows * Columns. - if (Rows < min_rows()) { - // need room for one window and command line - Rows = min_rows(); - } else if (Rows > 1000) { - Rows = 1000; - } - - if (Columns < MIN_COLUMNS) { - Columns = MIN_COLUMNS; - } else if (Columns > 10000) { - Columns = 10000; - } -} |