diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 22:40:31 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 22:40:31 +0000 |
commit | 339e2d15cc26fe86988ea06468d912a46c8d6f29 (patch) | |
tree | a6167fc8fcfc6ae2dc102f57b2473858eac34063 /src/nvim/drawscreen.c | |
parent | 067dc73729267c0262438a6fdd66e586f8496946 (diff) | |
parent | 4a8bf24ac690004aedf5540fa440e788459e5e34 (diff) | |
download | rneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.tar.gz rneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.tar.bz2 rneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.zip |
Merge remote-tracking branch 'upstream/master' into fix_repeatcmdline
Diffstat (limited to 'src/nvim/drawscreen.c')
-rw-r--r-- | src/nvim/drawscreen.c | 1031 |
1 files changed, 863 insertions, 168 deletions
diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 04c342e068..6cc623cb72 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -1,6 +1,3 @@ -// 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 - // drawscreen.c: Code for updating all the windows on the screen. // This is the top level, drawline.c is the middle and grid.c/screen.c the lower level. @@ -56,24 +53,32 @@ #include <assert.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include "klib/kvec.h" #include "nvim/api/private/defs.h" -#include "nvim/ascii.h" +#include "nvim/ascii_defs.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" +#include "nvim/cursor.h" #include "nvim/decoration.h" #include "nvim/decoration_provider.h" #include "nvim/diff.h" +#include "nvim/digraph.h" #include "nvim/drawline.h" #include "nvim/drawscreen.h" +#include "nvim/eval.h" #include "nvim/ex_getln.h" -#include "nvim/extmark_defs.h" #include "nvim/fold.h" +#include "nvim/func_attr.h" +#include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" @@ -86,20 +91,25 @@ #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" +#include "nvim/option_vars.h" #include "nvim/plines.h" #include "nvim/popupmenu.h" -#include "nvim/pos.h" +#include "nvim/pos_defs.h" #include "nvim/profile.h" #include "nvim/regexp.h" -#include "nvim/screen.h" +#include "nvim/search.h" +#include "nvim/sign_defs.h" +#include "nvim/spell.h" +#include "nvim/state.h" #include "nvim/statusline.h" +#include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/terminal.h" -#include "nvim/types.h" +#include "nvim/types_defs.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" #include "nvim/version.h" -#include "nvim/vim.h" +#include "nvim/vim_defs.h" #include "nvim/window.h" /// corner value flags for hsep_connected and vsep_connected @@ -118,8 +128,6 @@ static bool redraw_popupmenu = false; static bool msg_grid_invalid = false; static bool resizing_autocmd = false; -static char *provider_err = NULL; - /// Check if the cursor line needs to be redrawn because of 'concealcursor'. /// /// When cursor is moved at the same time, both lines will be redrawn regardless. @@ -202,7 +210,7 @@ bool default_grid_alloc(void) void screenclear(void) { - check_for_delay(false); + msg_check_for_delay(false); if (starting == NO_SCREEN || default_grid.chars == NULL) { return; @@ -212,7 +220,6 @@ void screenclear(void) for (int i = 0; i < default_grid.rows; i++) { grid_clear_line(&default_grid, default_grid.line_offset[i], default_grid.cols, true); - default_grid.line_wraps[i] = false; } ui_call_grid_clear(1); // clear the display @@ -318,11 +325,15 @@ void screen_resize(int width, int height) resizing_autocmd = false; redraw_all_later(UPD_CLEAR); + if (State != MODE_ASKMORE && State != MODE_EXTERNCMD && State != MODE_CONFIRM) { + screenclear(); + } + if (starting != NO_SCREEN) { maketitle(); changed_line_abv_curs(); - invalidate_botline(); + invalidate_botline(curwin); // We only redraw when it's needed: // - While at the more prompt or executing an external command, don't @@ -370,6 +381,32 @@ void screen_resize(int width, int height) resizing_screen = false; } +/// 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; + } +} + +/// Return true if redrawing should currently be done. +bool redrawing(void) +{ + return !RedrawingDisabled + && !(p_lz && char_avail() && !KeyTyped && !do_redraw); +} + /// Redraw the parts of the screen that is marked for redraw. /// /// Most code shouldn't call this directly, rather use redraw_later() and @@ -411,6 +448,15 @@ int update_screen(void) display_tick++; // let syntax code know we're in a next round of // display updating + // glyph cache full, very rare + if (schar_cache_clear_if_full()) { + // must use CLEAR, as the contents of screen buffers cannot be + // compared to their previous state here. + // TODO(bfredl): if start to cache schar_T values in places (like fcs/lcs) + // we need to revalidate these here as well! + type = MAX(type, UPD_CLEAR); + } + // Tricky: vim code can reset msg_scrolled behind our back, so need // separate bookkeeping for now. if (msg_did_scroll) { @@ -430,7 +476,7 @@ int update_screen(void) // non-displayed part of msg_grid is considered invalid. for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.rows); i++) { grid_clear_line(&msg_grid, msg_grid.line_offset[i], - msg_grid.cols, false); + msg_grid.cols, i < p_ch); } } msg_grid.throttled = false; @@ -507,7 +553,7 @@ int update_screen(void) ui_comp_set_screen_valid(true); DecorProviders providers; - decor_providers_start(&providers, &provider_err); + decor_providers_start(&providers); // "start" callback could have changed highlights for global elements if (win_check_ns_hl(NULL)) { @@ -516,7 +562,7 @@ int update_screen(void) } if (clear_cmdline) { // going to clear cmdline (done below) - check_for_delay(false); + msg_check_for_delay(false); } // Force redraw when width of 'number' or 'relativenumber' column @@ -540,9 +586,9 @@ int update_screen(void) draw_tabline(); } - // Correct stored syntax highlighting info for changes in each displayed - // buffer. Each buffer must only be done once. FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + // Correct stored syntax highlighting info for changes in each displayed + // buffer. Each buffer must only be done once. update_window_hl(wp, type >= UPD_NOT_VALID || hl_changed); buf_T *buf = wp->w_buffer; @@ -554,10 +600,19 @@ int update_screen(void) } if (buf->b_mod_tick_decor < display_tick) { - decor_providers_invoke_buf(buf, &providers, &provider_err); + decor_providers_invoke_buf(buf, &providers); buf->b_mod_tick_decor = display_tick; } } + + // Reset 'statuscolumn' if there is no dedicated signcolumn but it is invalid. + if (*wp->w_p_stc != NUL && wp->w_minscwidth <= SCL_NO + && (wp->w_buffer->b_signcols.invalid_bot || !wp->w_buffer->b_signcols.sentinel)) { + wp->w_nrwidth_line_count = 0; + wp->w_valid &= ~VALID_WCOL; + wp->w_redr_type = UPD_NOT_VALID; + wp->w_buffer->b_signcols.invalid_bot = 0; + } } // Go from top to bottom through the windows, redrawing the ones that need it. @@ -624,7 +679,7 @@ int update_screen(void) } did_intro = true; - decor_providers_invoke_end(&providers, &provider_err); + decor_providers_invoke_end(&providers); kvi_destroy(providers); // either cmdline is cleared, not drawn or mode is last drawn @@ -632,20 +687,56 @@ int update_screen(void) return OK; } -static void win_border_redr_title(win_T *wp, ScreenGrid *grid, int col) +/// 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) { - VirtText title_chunks = wp->w_float_config.title_chunks; + if (screen_search_hl.rm.regprog == NULL) { + return; + } - for (size_t i = 0; i < title_chunks.size; i++) { - char *text = title_chunks.items[i].text; - int cell = (int)mb_string2cells(text); - int hl_id = title_chunks.items[i].hl_id; - int attr = hl_id ? syn_id2attr(hl_id) : 0; - grid_puts(grid, text, 0, col, attr); - col += cell; + vim_regfree(screen_search_hl.rm.regprog); + screen_search_hl.rm.regprog = NULL; +} + +static void win_redr_bordertext(win_T *wp, VirtText vt, int col) +{ + for (size_t i = 0; i < kv_size(vt);) { + int attr = 0; + char *text = next_virt_text_chunk(vt, &i, &attr); + if (text == NULL) { + break; + } + attr = hl_apply_winblend(wp, attr); + col += grid_line_puts(col, text, -1, attr); } } +int win_get_bordertext_col(int total_col, int text_width, AlignTextPos align) +{ + switch (align) { + case kAlignLeft: + return 1; + case kAlignCenter: + return MAX((total_col - text_width) / 2 + 1, 1); + case kAlignRight: + return MAX(total_col - text_width + 1, 1); + } + UNREACHABLE; +} + static void win_redr_border(win_T *wp) { wp->w_redr_border = false; @@ -655,102 +746,459 @@ static void win_redr_border(win_T *wp) ScreenGrid *grid = &wp->w_grid_alloc; - schar_T *chars = wp->w_float_config.border_chars; + schar_T chars[8]; + for (int i = 0; i < 8; i++) { + chars[i] = schar_from_str(wp->w_float_config.border_chars[i]); + } int *attrs = wp->w_float_config.border_attr; int *adj = wp->w_border_adj; - int irow = wp->w_height_inner + wp->w_winbar_height, icol = wp->w_width_inner; + int irow = wp->w_height_inner + wp->w_winbar_height; + int icol = wp->w_width_inner; if (adj[0]) { - grid_puts_line_start(grid, 0); + grid_line_start(grid, 0); if (adj[3]) { - grid_put_schar(grid, 0, 0, chars[0], attrs[0]); + grid_line_put_schar(0, chars[0], attrs[0]); } for (int i = 0; i < icol; i++) { - grid_put_schar(grid, 0, i + adj[3], chars[1], attrs[1]); + grid_line_put_schar(i + adj[3], chars[1], attrs[1]); } if (wp->w_float_config.title) { - int title_col = 0; - int title_width = wp->w_float_config.title_width; - AlignTextPos title_pos = wp->w_float_config.title_pos; - - if (title_pos == kAlignCenter) { - title_col = (icol - title_width) / 2 + 1; - } else { - title_col = title_pos == kAlignLeft ? 1 : icol - title_width + 1; - } - - win_border_redr_title(wp, grid, title_col); + int title_col = win_get_bordertext_col(icol, wp->w_float_config.title_width, + wp->w_float_config.title_pos); + win_redr_bordertext(wp, wp->w_float_config.title_chunks, title_col); } if (adj[1]) { - grid_put_schar(grid, 0, icol + adj[3], chars[2], attrs[2]); + grid_line_put_schar(icol + adj[3], chars[2], attrs[2]); } - grid_puts_line_flush(false); + grid_line_flush(); } for (int i = 0; i < irow; i++) { if (adj[3]) { - grid_puts_line_start(grid, i + adj[0]); - grid_put_schar(grid, i + adj[0], 0, chars[7], attrs[7]); - grid_puts_line_flush(false); + grid_line_start(grid, i + adj[0]); + grid_line_put_schar(0, chars[7], attrs[7]); + grid_line_flush(); } if (adj[1]) { - int ic = (i == 0 && !adj[0] && chars[2][0]) ? 2 : 3; - grid_puts_line_start(grid, i + adj[0]); - grid_put_schar(grid, i + adj[0], icol + adj[3], chars[ic], attrs[ic]); - grid_puts_line_flush(false); + int ic = (i == 0 && !adj[0] && chars[2]) ? 2 : 3; + grid_line_start(grid, i + adj[0]); + grid_line_put_schar(icol + adj[3], chars[ic], attrs[ic]); + grid_line_flush(); } } if (adj[2]) { - grid_puts_line_start(grid, irow + adj[0]); + grid_line_start(grid, irow + adj[0]); if (adj[3]) { - grid_put_schar(grid, irow + adj[0], 0, chars[6], attrs[6]); + grid_line_put_schar(0, chars[6], attrs[6]); } + for (int i = 0; i < icol; i++) { - int ic = (i == 0 && !adj[3] && chars[6][0]) ? 6 : 5; - grid_put_schar(grid, irow + adj[0], i + adj[3], chars[ic], attrs[ic]); + int ic = (i == 0 && !adj[3] && chars[6]) ? 6 : 5; + grid_line_put_schar(i + adj[3], chars[ic], attrs[ic]); + } + + if (wp->w_float_config.footer) { + int footer_col = win_get_bordertext_col(icol, wp->w_float_config.footer_width, + wp->w_float_config.footer_pos); + win_redr_bordertext(wp, wp->w_float_config.footer_chunks, footer_col); } if (adj[1]) { - grid_put_schar(grid, irow + adj[0], icol + adj[3], chars[4], attrs[4]); + grid_line_put_schar(icol + adj[3], chars[4], attrs[4]); + } + grid_line_flush(); + } +} + +/// 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_puts_line_flush(false); + + grid_adjust(&grid, &row, &col); + ui_grid_cursor_goto(grid->handle, row, col); } } /// Show current cursor info in ruler and various other places /// /// @param always if false, only show ruler if position has changed. -void show_cursor_info(bool always) +void show_cursor_info_later(bool force) { - if (!always && !redrawing()) { - return; + int state = get_real_state(); + int empty_line = (State & MODE_INSERT) == 0 + && *ml_get_buf(curwin->w_buffer, curwin->w_cursor.lnum) == NUL; + + // Only draw when something changed. + validate_virtcol_win(curwin); + if (force + || curwin->w_cursor.lnum != curwin->w_stl_cursor.lnum + || curwin->w_cursor.col != curwin->w_stl_cursor.col + || curwin->w_virtcol != curwin->w_stl_virtcol + || curwin->w_cursor.coladd != curwin->w_stl_cursor.coladd + || curwin->w_topline != curwin->w_stl_topline + || curwin->w_buffer->b_ml.ml_line_count != curwin->w_stl_line_count + || curwin->w_topfill != curwin->w_stl_topfill + || empty_line != curwin->w_stl_empty + || reg_recording != curwin->w_stl_recording + || state != curwin->w_stl_state + || (VIsual_active && VIsual_mode != curwin->w_stl_visual_mode)) { + if (curwin->w_status_height || global_stl_height()) { + curwin->w_redr_status = true; + } else { + redraw_cmdline = true; + } + + if (*p_wbr != NUL || *curwin->w_p_wbr != NUL) { + curwin->w_redr_status = true; + } + + if ((p_icon && (stl_syntax & STL_IN_ICON)) + || (p_title && (stl_syntax & STL_IN_TITLE))) { + need_maketitle = true; + } + } + + curwin->w_stl_cursor = curwin->w_cursor; + curwin->w_stl_virtcol = curwin->w_virtcol; + curwin->w_stl_empty = (char)empty_line; + curwin->w_stl_topline = curwin->w_topline; + curwin->w_stl_line_count = curwin->w_buffer->b_ml.ml_line_count; + curwin->w_stl_topfill = curwin->w_topfill; + curwin->w_stl_recording = reg_recording; + curwin->w_stl_state = state; + if (VIsual_active) { + curwin->w_stl_visual_mode = VIsual_mode; } +} + +/// @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)); + + bool can_show_mode = (p_ch != 0 || ui_has(kUIMessages)); + if ((do_mode || reg_recording != 0) && can_show_mode) { + 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 + msg_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(edit_submode_pre, attr); + } + msg_puts_attr(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(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 current or last window has no status line and global statusline is disabled, + // the ruler is after the mode message and must be redrawn + win_T *ruler_win = curwin->w_status_height == 0 ? curwin : lastwin_nofloating(); + if (redrawing() && ruler_win->w_status_height == 0 && global_stl_height() == 0 + && !(p_ch == 0 && !ui_has(kUIMessages))) { + grid_line_start(&msg_grid_adj, Rows - 1); + win_redr_ruler(ruler_win); + grid_line_flush(); + } + + 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; +} - win_check_ns_hl(curwin); - if ((*p_stl != NUL || *curwin->w_p_stl != NUL) - && (curwin->w_status_height || global_stl_height())) { - redraw_custom_statusline(curwin); +/// 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 { - win_redr_ruler(curwin, always); + clearmode(); } - if (*p_wbr != NUL || *curwin->w_p_wbr != NUL) { - win_redr_winbar(curwin); +} + +// 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(); - if (need_maketitle - || (p_icon && (stl_syntax & STL_IN_ICON)) - || (p_title && (stl_syntax & STL_IN_TITLE))) { - maketitle(); + 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; } - win_check_ns_hl(NULL); - // Redraw the tab pages line if needed. - if (redraw_tabline) { - draw_tabline(); + char s[4]; + snprintf(s, ARRAY_SIZE(s), " @%c", reg_recording); + msg_puts_attr(s, attr); +} + +#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) +{ + bool last_has_status = last_stl_height(false) > 0; + + 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); } static void redraw_win_signcol(win_T *wp) @@ -762,6 +1210,7 @@ static void redraw_win_signcol(win_T *wp) wp->w_scwidth = win_signcol_count(wp); if (wp->w_scwidth != scwidth) { changed_line_abv_curs_win(wp); + redraw_later(wp, UPD_NOT_VALID); } } @@ -844,8 +1293,8 @@ static void draw_vsep_win(win_T *wp) } // draw the vertical separator right of this window - int hl; - int c = fillchar_vsep(wp, &hl); + int hl = win_hl_attr(wp, HLF_C); + int c = wp->w_p_fcs_chars.vert; grid_fill(&default_grid, wp->w_winrow, W_ENDROW(wp), W_ENDCOL(wp), W_ENDCOL(wp) + 1, c, ' ', hl); } @@ -858,30 +1307,32 @@ static void draw_hsep_win(win_T *wp) } // draw the horizontal separator below this window - int hl; - int c = fillchar_hsep(wp, &hl); + int hl = win_hl_attr(wp, HLF_C); + int c = wp->w_p_fcs_chars.horiz; grid_fill(&default_grid, W_ENDROW(wp), W_ENDROW(wp) + 1, wp->w_wincol, W_ENDCOL(wp), c, c, hl); } /// Get the separator connector for specified window corner of window "wp" -static int get_corner_sep_connector(win_T *wp, WindowCorner corner) +static schar_T get_corner_sep_connector(win_T *wp, WindowCorner corner) { // It's impossible for windows to be connected neither vertically nor horizontally // So if they're not vertically connected, assume they're horizontally connected + int c; if (vsep_connected(wp, corner)) { if (hsep_connected(wp, corner)) { - return wp->w_p_fcs_chars.verthoriz; + c = wp->w_p_fcs_chars.verthoriz; } else if (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) { - return wp->w_p_fcs_chars.vertright; + c = wp->w_p_fcs_chars.vertright; } else { - return wp->w_p_fcs_chars.vertleft; + c = wp->w_p_fcs_chars.vertleft; } } else if (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) { - return wp->w_p_fcs_chars.horizdown; + c = wp->w_p_fcs_chars.horizdown; } else { - return wp->w_p_fcs_chars.horizup; + c = wp->w_p_fcs_chars.horizup; } + return schar_from_char(c); } /// Draw separator connecting characters on the corners of window "wp" @@ -917,21 +1368,31 @@ static void draw_sep_connectors_win(win_T *wp) win_at_left = frp->fr_parent == NULL; // Draw the appropriate separator connector in every corner where drawing them is necessary - if (!(win_at_top || win_at_left)) { - grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_LEFT), - wp->w_winrow - 1, wp->w_wincol - 1, hl); - } - if (!(win_at_top || win_at_right)) { - grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_RIGHT), - wp->w_winrow - 1, W_ENDCOL(wp), hl); - } - if (!(win_at_bottom || win_at_left)) { - grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_LEFT), - W_ENDROW(wp), wp->w_wincol - 1, hl); - } - if (!(win_at_bottom || win_at_right)) { - grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_RIGHT), - W_ENDROW(wp), W_ENDCOL(wp), hl); + // Make sure not to send cursor position updates to ui. + bool top_left = !(win_at_top || win_at_left); + bool top_right = !(win_at_top || win_at_right); + bool bot_left = !(win_at_bottom || win_at_left); + bool bot_right = !(win_at_bottom || win_at_right); + + if (top_left) { + grid_line_start(&default_grid, wp->w_winrow - 1); + grid_line_put_schar(wp->w_wincol - 1, get_corner_sep_connector(wp, WC_TOP_LEFT), hl); + grid_line_flush(); + } + if (top_right) { + grid_line_start(&default_grid, wp->w_winrow - 1); + grid_line_put_schar(W_ENDCOL(wp), get_corner_sep_connector(wp, WC_TOP_RIGHT), hl); + grid_line_flush(); + } + if (bot_left) { + grid_line_start(&default_grid, W_ENDROW(wp)); + grid_line_put_schar(wp->w_wincol - 1, get_corner_sep_connector(wp, WC_BOTTOM_LEFT), hl); + grid_line_flush(); + } + if (bot_right) { + grid_line_start(&default_grid, W_ENDROW(wp)); + grid_line_put_schar(W_ENDCOL(wp), get_corner_sep_connector(wp, WC_BOTTOM_RIGHT), hl); + grid_line_flush(); } } @@ -992,9 +1453,7 @@ static void win_update(win_T *wp, DecorProviders *providers) int type = wp->w_redr_type; if (type >= UPD_NOT_VALID) { - // TODO(bfredl): should only be implied for CLEAR, not NOT_VALID! wp->w_redr_status = true; - wp->w_lines_valid = 0; } @@ -1027,25 +1486,41 @@ static void win_update(win_T *wp, DecorProviders *providers) win_extmark_arr.size = 0; - decor_redraw_reset(buf, &decor_state); + decor_redraw_reset(wp, &decor_state); DecorProviders line_providers; - decor_providers_invoke_win(wp, providers, &line_providers, &provider_err); + decor_providers_invoke_win(wp, providers, &line_providers); redraw_win_signcol(wp); init_search_hl(wp, &screen_search_hl); - // Force redraw when width of 'number' or 'relativenumber' column - // changes. - int nrwidth = (wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc) ? number_width(wp) : 0; - if (wp->w_nrwidth != nrwidth) { - type = UPD_NOT_VALID; - wp->w_nrwidth = nrwidth; - - if (buf->terminal) { - terminal_check_size(buf->terminal); + // Make sure skipcol is valid, it depends on various options and the window + // width. + if (wp->w_skipcol > 0) { + int w = 0; + int width1 = wp->w_width_inner - win_col_off(wp); + int width2 = width1 + win_col_off2(wp); + int add = width1; + + while (w < wp->w_skipcol) { + if (w > 0) { + add = width2; + } + w += add; + } + if (w != wp->w_skipcol) { + // always round down, the higher value may not be valid + wp->w_skipcol = w - add; } + } + + const int nrwidth_before = wp->w_nrwidth; + int nrwidth_new = (wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc) ? number_width(wp) : 0; + // Force redraw when width of 'number' or 'relativenumber' column changes. + if (wp->w_nrwidth != nrwidth_new) { + type = UPD_NOT_VALID; + wp->w_nrwidth = nrwidth_new; } else if (buf->b_mod_set && buf->b_mod_xlines != 0 && wp->w_redraw_top != 0) { @@ -1099,8 +1574,6 @@ static void win_update(win_T *wp, DecorProviders *providers) } } if (mod_top != 0 && hasAnyFolding(wp)) { - linenr_T lnumt, lnumb; - // A change in a line can cause lines above it to become folded or // unfolded. Find the top most buffer line that may be affected. // If the line was previously folded and displayed, get the first @@ -1111,8 +1584,8 @@ static void win_update(win_T *wp, DecorProviders *providers) // the line below it. If there is no valid entry, use w_topline. // Find the first valid w_lines[] entry below mod_bot. Set lnumb // to this line. If there is no valid entry, use MAXLNUM. - lnumt = wp->w_topline; - lnumb = MAXLNUM; + linenr_T lnumt = wp->w_topline; + linenr_T lnumb = MAXLNUM; for (int i = 0; i < wp->w_lines_valid; i++) { if (wp->w_lines[i].wl_valid) { if (wp->w_lines[i].wl_lastlnum < mod_top) { @@ -1168,11 +1641,11 @@ static void win_update(win_T *wp, DecorProviders *providers) // When only displaying the lines at the top, set top_end. Used when // window has scrolled down for msg_scrolled. if (type == UPD_REDRAW_TOP) { - long j = 0; + int j = 0; for (int i = 0; i < wp->w_lines_valid; i++) { j += wp->w_lines[i].wl_size; if (j >= wp->w_upd_rows) { - top_end = (int)j; + top_end = j; break; } } @@ -1205,7 +1678,7 @@ static void win_update(win_T *wp, DecorProviders *providers) || (wp->w_topline == wp->w_lines[0].wl_lnum && wp->w_topfill > wp->w_old_topfill))) { // New topline is above old topline: May scroll down. - long j; + int j; if (hasAnyFolding(wp)) { linenr_T ln; @@ -1223,7 +1696,7 @@ static void win_update(win_T *wp, DecorProviders *providers) j = wp->w_lines[0].wl_lnum - wp->w_topline; } if (j < wp->w_grid.rows - 2) { // not too far off - int i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1); + int i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1, true); // insert extra lines for previously invisible filler lines if (wp->w_lines[0].wl_lnum != wp->w_topline) { i += win_get_fill(wp, wp->w_lines[0].wl_lnum) - wp->w_old_topfill; @@ -1265,7 +1738,7 @@ static void win_update(win_T *wp, DecorProviders *providers) // needs updating. // try to find wp->w_topline in wp->w_lines[].wl_lnum - long j = -1; + int j = -1; int row = 0; for (int i = 0; i < wp->w_lines_valid; i++) { if (wp->w_lines[i].wl_valid @@ -1304,7 +1777,7 @@ static void win_update(win_T *wp, DecorProviders *providers) // bot_start to the first row that needs redrawing. bot_start = 0; int idx = 0; - for (;;) { + while (true) { wp->w_lines[idx] = wp->w_lines[j]; // stop at line that didn't fit, unless it is still // valid (no lines deleted) @@ -1417,7 +1890,7 @@ static void win_update(win_T *wp, DecorProviders *providers) // First compute the actual start and end column. if (VIsual_mode == Ctrl_V) { colnr_T fromc, toc; - unsigned int save_ve_flags = curwin->w_ve_flags; + unsigned save_ve_flags = curwin->w_ve_flags; if (curwin->w_p_lbr) { curwin->w_ve_flags = VE_ALL; @@ -1441,7 +1914,7 @@ static void win_update(win_T *wp, DecorProviders *providers) pos.lnum += cursor_above ? 1 : -1) { colnr_T t; - pos.col = (colnr_T)strlen(ml_get_buf(wp->w_buffer, pos.lnum, false)); + pos.col = (colnr_T)strlen(ml_get_buf(wp->w_buffer, pos.lnum)); getvvcol(wp, &pos, NULL, NULL, &t); if (toc < t) { toc = t; @@ -1548,19 +2021,34 @@ static void win_update(win_T *wp, DecorProviders *providers) wp->w_old_visual_col = 0; } - bool cursorline_standout = win_cursorline_standout(wp); + foldinfo_T cursorline_fi = { 0 }; + wp->w_cursorline = win_cursorline_standout(wp) ? wp->w_cursor.lnum : 0; + if (wp->w_p_cul) { + // Make sure that the cursorline on a closed fold is redrawn + cursorline_fi = fold_info(wp, wp->w_cursor.lnum); + if (cursorline_fi.fi_level != 0 && cursorline_fi.fi_lines > 0) { + wp->w_cursorline = cursorline_fi.fi_lnum; + } + } win_check_ns_hl(wp); + spellvars_T spv = { 0 }; + linenr_T lnum = wp->w_topline; // first line shown in window + // Initialize spell related variables for the first drawn line. + if (spell_check_window(wp)) { + spv.spv_has_spell = true; + spv.spv_unchanged = mod_top == 0; + } + // Update all the window rows. int idx = 0; // first entry in w_lines[].wl_size int row = 0; // current window row to display int srow = 0; // starting row of the current line - linenr_T lnum = wp->w_topline; // first line shown in window bool eof = false; // if true, we hit the end of the file bool didline = false; // if true, we finished the last line - for (;;) { + while (true) { // stop updating when reached the end of the window (check for _past_ // the end of the window is at the end of the loop) if (row == wp->w_grid.rows) { @@ -1604,7 +2092,7 @@ static void win_update(win_T *wp, DecorProviders *providers) // if lines were inserted or deleted || (wp->w_match_head != NULL && buf->b_mod_xlines != 0))))) - || (cursorline_standout && lnum == wp->w_cursor.lnum) + || lnum == wp->w_cursorline || lnum == wp->w_last_cursorline) { if (lnum == mod_top) { top_to_mod = false; @@ -1620,8 +2108,6 @@ static void win_update(win_T *wp, DecorProviders *providers) && !(dollar_vcol >= 0 && mod_bot == mod_top + 1) && row >= top_end) { int old_rows = 0; - int new_rows = 0; - int xtra_rows; linenr_T l; int i; @@ -1656,14 +2142,20 @@ static void win_update(win_T *wp, DecorProviders *providers) bot_start = 0; bot_scroll_start = 0; } else { + int new_rows = 0; // Able to count old number of rows: Count new window // rows, and may insert/delete lines - long j = idx; + int j = idx; for (l = lnum; l < mod_bot; l++) { if (hasFoldingWin(wp, l, NULL, &l, true, NULL)) { new_rows++; } else if (l == wp->w_topline) { - new_rows += plines_win_nofill(wp, l, true) + wp->w_topfill; + int n = plines_win_nofill(wp, l, false) + wp->w_topfill; + n -= adjust_plines_for_skipcol(wp); + if (n > wp->w_height_inner) { + n = wp->w_height_inner; + } + new_rows += n; } else { new_rows += plines_win(wp, l, true); } @@ -1674,7 +2166,7 @@ static void win_update(win_T *wp, DecorProviders *providers) break; } } - xtra_rows = new_rows - old_rows; + int xtra_rows = new_rows - old_rows; if (xtra_rows < 0) { // May scroll text up. If there is not enough // remaining text or scrolling fails, must redraw the @@ -1711,17 +2203,17 @@ static void win_update(win_T *wp, DecorProviders *providers) int x = row + new_rows; // move entries in w_lines[] upwards - for (;;) { + while (true) { // stop at last valid entry in w_lines[] if (i >= wp->w_lines_valid) { - wp->w_lines_valid = (int)j; + wp->w_lines_valid = j; break; } wp->w_lines[j] = wp->w_lines[i]; // stop at a line that won't fit if (x + (int)wp->w_lines[j].wl_size > wp->w_grid.rows) { - wp->w_lines_valid = (int)j + 1; + wp->w_lines_valid = j + 1; break; } x += wp->w_lines[j++].wl_size; @@ -1756,7 +2248,8 @@ static void win_update(win_T *wp, DecorProviders *providers) // When lines are folded, display one line for all of them. // Otherwise, display normally (can be several display lines when // 'wrap' is on). - foldinfo_T foldinfo = fold_info(wp, lnum); + foldinfo_T foldinfo = wp->w_p_cul && lnum == wp->w_cursor.lnum + ? cursorline_fi : fold_info(wp, lnum); if (foldinfo.fi_lines == 0 && idx < wp->w_lines_valid @@ -1778,9 +2271,10 @@ static void win_update(win_T *wp, DecorProviders *providers) } // Display one line - row = win_line(wp, lnum, srow, - foldinfo.fi_lines ? srow : wp->w_grid.rows, - mod_top == 0, false, foldinfo, &line_providers, &provider_err); + spellvars_T zero_spv = { 0 }; + row = win_line(wp, lnum, srow, wp->w_grid.rows, false, + foldinfo.fi_lines > 0 ? &zero_spv : &spv, + foldinfo, &line_providers); if (foldinfo.fi_lines == 0) { wp->w_lines[idx].wl_folded = false; @@ -1792,6 +2286,7 @@ static void win_update(win_T *wp, DecorProviders *providers) wp->w_lines[idx].wl_folded = true; wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines; did_update = DID_FOLD; + spv.spv_capcol_lnum = 0; } } @@ -1815,9 +2310,9 @@ static void win_update(win_T *wp, DecorProviders *providers) if (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum) { // 'relativenumber' set and cursor moved vertically: The // text doesn't need to be drawn, but the number column does. - foldinfo_T info = fold_info(wp, lnum); - (void)win_line(wp, lnum, srow, wp->w_grid.rows, true, true, - info, &line_providers, &provider_err); + foldinfo_T info = wp->w_p_cul && lnum == wp->w_cursor.lnum + ? cursorline_fi : fold_info(wp, lnum); + (void)win_line(wp, lnum, srow, wp->w_grid.rows, true, &spv, info, &line_providers); } // This line does not need to be drawn, advance to the next one. @@ -1827,6 +2322,7 @@ static void win_update(win_T *wp, DecorProviders *providers) } lnum = wp->w_lines[idx - 1].wl_lastlnum + 1; did_update = DID_NONE; + spv.spv_capcol_lnum = 0; } // 'statuscolumn' width has changed or errored, start from the top. @@ -1837,7 +2333,8 @@ static void win_update(win_T *wp, DecorProviders *providers) lnum = wp->w_topline; wp->w_lines_valid = 0; wp->w_valid &= ~VALID_WCOL; - decor_providers_invoke_win(wp, providers, &line_providers, &provider_err); + decor_redraw_reset(wp, &decor_state); + decor_providers_invoke_win(wp, providers, &line_providers); continue; } @@ -1850,7 +2347,7 @@ static void win_update(win_T *wp, DecorProviders *providers) // Now that the window has been redrawn with the old and new cursor line, // update w_last_cursorline. - wp->w_last_cursorline = cursorline_standout ? wp->w_cursor.lnum : 0; + wp->w_last_cursorline = wp->w_cursorline; wp->w_last_cursor_lnum_rnu = wp->w_p_rnu ? wp->w_cursor.lnum : 0; @@ -1880,24 +2377,21 @@ static void win_update(win_T *wp, DecorProviders *providers) wp->w_botline = lnum; wp->w_filler_rows = wp->w_grid.rows - srow; } else if (dy_flags & DY_TRUNCATE) { // 'display' has "truncate" - int scr_row = wp->w_grid.rows - 1; - int symbol = wp->w_p_fcs_chars.lastline; - char fillbuf[12]; // 2 characters of 6 bytes - int charlen = utf_char2bytes(symbol, &fillbuf[0]); - utf_char2bytes(symbol, &fillbuf[charlen]); - // Last line isn't finished: Display "@@@" in the last screen line. - grid_puts_len(&wp->w_grid, fillbuf, MIN(wp->w_grid.cols, 2) * charlen, scr_row, 0, at_attr); - grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.cols, symbol, ' ', at_attr); + grid_line_start(&wp->w_grid, wp->w_grid.rows - 1); + grid_line_fill(0, MIN(wp->w_grid.cols, 3), wp->w_p_fcs_chars.lastline, at_attr); + grid_line_fill(3, wp->w_grid.cols, ' ', at_attr); + grid_line_flush(); set_empty_rows(wp, srow); wp->w_botline = lnum; } else if (dy_flags & DY_LASTLINE) { // 'display' has "lastline" - int start_col = wp->w_grid.cols - 3; - int symbol = wp->w_p_fcs_chars.lastline; - // Last line isn't finished: Display "@@@" at the end. - grid_fill(&wp->w_grid, wp->w_grid.rows - 1, wp->w_grid.rows, - MAX(start_col, 0), wp->w_grid.cols, symbol, symbol, at_attr); + // If this would split a doublewidth char in two, we need to display "@@@@" instead + grid_line_start(&wp->w_grid, wp->w_grid.rows - 1); + int width = grid_line_getchar(MAX(wp->w_grid.cols - 3, 0), NULL) == NUL ? 4 : 3; + grid_line_fill(MAX(wp->w_grid.cols - width, 0), wp->w_grid.cols, + wp->w_p_fcs_chars.lastline, at_attr); + grid_line_flush(); set_empty_rows(wp, srow); wp->w_botline = lnum; } else { @@ -1908,13 +2402,14 @@ static void win_update(win_T *wp, DecorProviders *providers) } else { if (eof) { // we hit the end of the file wp->w_botline = buf->b_ml.ml_line_count + 1; - long j = win_get_fill(wp, wp->w_botline); + int j = win_get_fill(wp, wp->w_botline); if (j > 0 && !wp->w_botfill && row < wp->w_grid.rows) { // Display filler text below last line. win_line() will check // for ml_line_count+1 and only draw filler lines - foldinfo_T info = { 0 }; - row = win_line(wp, wp->w_botline, row, wp->w_grid.rows, - false, false, info, &line_providers, &provider_err); + spellvars_T zero_spv = { 0 }; + foldinfo_T zero_foldinfo = { 0 }; + row = win_line(wp, wp->w_botline, row, wp->w_grid.rows, false, &zero_spv, + zero_foldinfo, &line_providers); } } else if (dollar_vcol == -1) { wp->w_botline = lnum; @@ -1988,12 +2483,162 @@ static void win_update(win_T *wp, DecorProviders *providers) } } + if (nrwidth_before != wp->w_nrwidth && buf->terminal) { + terminal_check_size(buf->terminal); + } + // restore got_int, unless CTRL-C was hit while redrawing if (!got_int) { got_int = save_got_int; } } +/// 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); + } +} + +/// 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, SIGN_WIDTH * 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; +} + +/// 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_statuscol_line_count = 0; // make sure width is re-estimated + 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_signs_with_text && wp->w_minscwidth == SCL_NUM) { + n = 2; + } + + wp->w_nrwidth_width = n; + return n; +} + /// Redraw a window later, with wp->w_redr_type >= type. /// /// Set must_redraw only if not already set to a higher value. @@ -2097,7 +2742,7 @@ void status_redraw_all(void) bool is_stl_global = global_stl_height() != 0; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if ((!is_stl_global && wp->w_status_height) || (is_stl_global && wp == curwin) + if ((!is_stl_global && wp->w_status_height) || wp == curwin || wp->w_winbar_height) { wp->w_redr_status = true; redraw_later(wp, UPD_VALID); @@ -2123,6 +2768,11 @@ void status_redraw_buf(buf_T *buf) redraw_later(wp, UPD_VALID); } } + // Redraw the ruler if it is in the command line and was not marked for redraw above + if (p_ru && !curwin->w_status_height && !curwin->w_redr_status) { + redraw_cmdline = true; + redraw_later(curwin, UPD_VALID); + } } /// Redraw all status lines that need to be redrawn. @@ -2182,3 +2832,48 @@ void redrawWinline(win_T *wp, linenr_T lnum) redraw_later(wp, UPD_VALID); } } + +/// 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)); +} + +/// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set. +/// Also when concealing is on and 'concealcursor' is not active. +void redraw_for_cursorline(win_T *wp) + FUNC_ATTR_NONNULL_ALL +{ + if ((wp->w_valid & VALID_CROW) == 0 && !pum_visible() + && (wp->w_p_rnu || win_cursorline_standout(wp))) { + // win_line() will redraw the number column and cursorline only. + redraw_later(wp, UPD_VALID); + } +} |