diff options
Diffstat (limited to 'src/nvim/drawscreen.c')
-rw-r--r-- | src/nvim/drawscreen.c | 2325 |
1 files changed, 2325 insertions, 0 deletions
diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c new file mode 100644 index 0000000000..8cd1bdddd8 --- /dev/null +++ b/src/nvim/drawscreen.c @@ -0,0 +1,2325 @@ +// 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. + +// update_screen() is the function that updates all windows and status lines. +// It is called from the main loop when must_redraw is non-zero. It may be +// called from other places when an immediate screen update is needed. +// +// The part of the buffer that is displayed in a window is set with: +// - w_topline (first buffer line in window) +// - w_topfill (filler lines above the first line) +// - w_leftcol (leftmost window cell in window), +// - w_skipcol (skipped window cells of first line) +// +// Commands that only move the cursor around in a window, do not need to take +// action to update the display. The main loop will check if w_topline is +// valid and update it (scroll the window) when needed. +// +// Commands that scroll a window change w_topline and must call +// check_cursor() to move the cursor into the visible part of the window, and +// call redraw_later(wp, VALID) to have the window displayed by update_screen() +// later. +// +// Commands that change text in the buffer must call changed_bytes() or +// changed_lines() to mark the area that changed and will require updating +// later. The main loop will call update_screen(), which will update each +// window that shows the changed buffer. This assumes text above the change +// can remain displayed as it is. Text after the change may need updating for +// scrolling, folding and syntax highlighting. +// +// Commands that change how a window is displayed (e.g., setting 'list') or +// invalidate the contents of a window in another way (e.g., change fold +// settings), must call redraw_later(wp, NOT_VALID) to have the whole window +// redisplayed by update_screen() later. +// +// Commands that change how a buffer is displayed (e.g., setting 'tabstop') +// must call redraw_curbuf_later(NOT_VALID) to have all the windows for the +// buffer redisplayed by update_screen() later. +// +// Commands that change highlighting and possibly cause a scroll too must call +// redraw_later(wp, SOME_VALID) to update the whole window but still use +// scrolling to avoid redrawing everything. But the length of displayed lines +// must not change, use NOT_VALID then. +// +// Commands that move the window position must call redraw_later(wp, NOT_VALID). +// TODO(neovim): should minimize redrawing by scrolling when possible. +// +// Commands that change everything (e.g., resizing the screen) must call +// redraw_all_later(NOT_VALID) or redraw_all_later(CLEAR). +// +// Things that are handled indirectly: +// - When messages scroll the screen up, msg_scrolled will be set and +// update_screen() called to redraw. + +#include <assert.h> +#include <inttypes.h> +#include <stdbool.h> +#include <string.h> + +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/diff.h" +#include "nvim/drawscreen.h" +#include "nvim/ex_getln.h" +#include "nvim/grid.h" +#include "nvim/highlight.h" +#include "nvim/highlight_group.h" +#include "nvim/insexpand.h" +#include "nvim/match.h" +#include "nvim/move.h" +#include "nvim/option.h" +#include "nvim/plines.h" +#include "nvim/popupmenu.h" +#include "nvim/profile.h" +#include "nvim/regexp.h" +#include "nvim/syntax.h" +#include "nvim/ui_compositor.h" +#include "nvim/undo.h" +#include "nvim/version.h" +#include "nvim/window.h" + +/// corner value flags for hsep_connected and vsep_connected +typedef enum { + WC_TOP_LEFT = 0, + WC_TOP_RIGHT, + WC_BOTTOM_LEFT, + WC_BOTTOM_RIGHT, +} WindowCorner; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "drawscreen.c.generated.h" +#endif + +static bool redraw_popupmenu = false; +static bool msg_grid_invalid = false; +static bool resizing = 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. +void conceal_check_cursor_line(void) +{ + bool should_conceal = conceal_cursor_line(curwin); + if (curwin->w_p_cole > 0 && (conceal_cursor_used != should_conceal)) { + redrawWinline(curwin, curwin->w_cursor.lnum); + // Need to recompute cursor column, e.g., when starting Visual mode + // without concealing. + curs_columns(curwin, true); + } +} + +/// Resize the screen to Rows and Columns. +/// +/// Allocate default_grid.chars[] and other grid arrays. +/// +/// There may be some time between setting Rows and Columns and (re)allocating +/// default_grid arrays. This happens when starting up and when +/// (manually) changing the screen size. Always use default_grid.rows and +/// default_grid.Columns to access items in default_grid.chars[]. Use Rows +/// and Columns for positioning text etc. where the final size of the screen is +/// needed. +void screenalloc(void) +{ + // It's possible that we produce an out-of-memory message below, which + // will cause this function to be called again. To break the loop, just + // return here. + if (resizing) { + return; + } + resizing = true; + + int retry_count = 0; + +retry: + // Allocation of the screen buffers is done only when the size changes and + // when Rows and Columns have been set and we have started doing full + // screen stuff. + if ((default_grid.chars != NULL + && Rows == default_grid.rows + && Columns == default_grid.cols) + || Rows == 0 + || Columns == 0 + || (!full_screen && default_grid.chars == NULL)) { + resizing = false; + return; + } + + // Note that the window sizes are updated before reallocating the arrays, + // thus we must not redraw here! + RedrawingDisabled++; + + // win_new_screensize will recompute floats position, but tell the + // compositor to not redraw them yet + ui_comp_set_screen_valid(false); + if (msg_grid.chars) { + msg_grid_invalid = true; + } + + win_new_screensize(); // fit the windows in the new sized screen + + comp_col(); // recompute columns for shown command and ruler + + // We're changing the size of the screen. + // - Allocate new arrays for default_grid + // - Move lines from the old arrays into the new arrays, clear extra + // lines (unless the screen is going to be cleared). + // - Free the old arrays. + // + // If anything fails, make grid arrays NULL, so we don't do anything! + // Continuing with the old arrays may result in a crash, because the + // size is wrong. + + grid_alloc(&default_grid, Rows, Columns, true, true); + StlClickDefinition *new_tab_page_click_defs = + xcalloc((size_t)Columns, sizeof(*new_tab_page_click_defs)); + + stl_clear_click_defs(tab_page_click_defs, tab_page_click_defs_size); + xfree(tab_page_click_defs); + + tab_page_click_defs = new_tab_page_click_defs; + tab_page_click_defs_size = Columns; + + default_grid.comp_height = Rows; + default_grid.comp_width = Columns; + + default_grid.row_offset = 0; + default_grid.col_offset = 0; + default_grid.handle = DEFAULT_GRID_HANDLE; + + must_redraw = CLEAR; // need to clear the screen later + + RedrawingDisabled--; + + // Do not apply autocommands more than 3 times to avoid an endless loop + // in case applying autocommands always changes Rows or Columns. + if (starting == 0 && ++retry_count <= 3) { + apply_autocmds(EVENT_VIMRESIZED, NULL, NULL, false, curbuf); + // In rare cases, autocommands may have altered Rows or Columns, + // jump back to check if we need to allocate the screen again. + goto retry; + } + + resizing = false; +} + +void screenclear(void) +{ + check_for_delay(false); + screenalloc(); // allocate screen buffers if size changed + + int i; + + if (starting == NO_SCREEN || default_grid.chars == NULL) { + return; + } + + // blank out the default grid + for (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 + ui_comp_set_screen_valid(true); + + ns_hl_fast = -1; + + clear_cmdline = false; + mode_displayed = false; + + redraw_all_later(NOT_VALID); + redraw_cmdline = true; + redraw_tabline = true; + redraw_popupmenu = true; + pum_invalidate(); + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_floating) { + wp->w_redr_type = CLEAR; + } + } + if (must_redraw == CLEAR) { + must_redraw = NOT_VALID; // no need to clear again + } + compute_cmdrow(); + msg_row = cmdline_row; // put cursor on last line for messages + msg_col = 0; + msg_scrolled = 0; // can't scroll back + msg_didany = false; + msg_didout = false; + if (HL_ATTR(HLF_MSG) > 0 && msg_use_grid() && msg_grid.chars) { + grid_invalidate(&msg_grid); + msg_grid_validate(); + msg_grid_invalid = false; + clear_cmdline = true; + } +} + +/// Set dimensions of the Nvim application "screen". +void screen_resize(int width, int height) +{ + // Avoid recursiveness, can happen when setting the window size causes + // another window-changed signal. + if (updating_screen || resizing_screen) { + return; + } + + if (width < 0 || height < 0) { // just checking... + return; + } + + if (State == MODE_HITRETURN || State == MODE_SETWSIZE) { + // postpone the resizing + State = MODE_SETWSIZE; + return; + } + + // curwin->w_buffer can be NULL when we are closing a window and the + // buffer has already been closed and removing a scrollbar causes a resize + // event. Don't resize then, it will happen after entering another buffer. + if (curwin->w_buffer == NULL) { + return; + } + + resizing_screen = true; + + Rows = height; + Columns = width; + check_screensize(); + int max_p_ch = Rows - min_rows() + 1; + if (!ui_has(kUIMessages) && p_ch > 0 && p_ch > max_p_ch) { + p_ch = max_p_ch ? max_p_ch : 1; + } + height = Rows; + width = Columns; + p_lines = Rows; + p_columns = Columns; + ui_call_grid_resize(1, width, height); + + /// The window layout used to be adjusted here, but it now happens in + /// screenalloc() (also invoked from screenclear()). That is because the + /// recursize "resizing_screen" check above may skip this, but not screenalloc(). + + if (State != MODE_ASKMORE && State != MODE_EXTERNCMD && State != MODE_CONFIRM) { + screenclear(); + } + + if (starting != NO_SCREEN) { + maketitle(); + + changed_line_abv_curs(); + invalidate_botline(); + + // We only redraw when it's needed: + // - While at the more prompt or executing an external command, don't + // redraw, but position the cursor. + // - While editing the command line, only redraw that. + // - in Ex mode, don't redraw anything. + // - Otherwise, redraw right now, and position the cursor. + // Always need to call update_screen() or screenalloc(), to make + // sure Rows/Columns and the size of the screen is correct! + if (State == MODE_ASKMORE || State == MODE_EXTERNCMD || State == MODE_CONFIRM + || exmode_active) { + screenalloc(); + if (msg_grid.chars) { + msg_grid_validate(); + } + // TODO(bfredl): sometimes messes up the output. Implement clear+redraw + // also for the pager? (or: what if the pager was just a modal window?) + ui_comp_set_screen_valid(true); + repeat_message(); + } else { + if (curwin->w_p_scb) { + do_check_scrollbind(true); + } + if (State & MODE_CMDLINE) { + redraw_popupmenu = false; + update_screen(NOT_VALID); + redrawcmdline(); + if (pum_drawn()) { + cmdline_pum_display(false); + } + } else { + update_topline(curwin); + if (pum_drawn()) { + // TODO(bfredl): ins_compl_show_pum wants to redraw the screen first. + // For now make sure the nested update_screen(0) won't redraw the + // pum at the old position. Try to untangle this later. + redraw_popupmenu = false; + ins_compl_show_pum(); + } + update_screen(NOT_VALID); + if (redrawing()) { + setcursor(); + } + } + } + ui_flush(); + } + resizing_screen = false; +} + +/// Redraw the parts of the screen that is marked for redraw. +/// +/// Most code shouldn't call this directly, rather use redraw_later() and +/// and redraw_all_later() to mark parts of the screen as needing a redraw. +/// +/// @param type set to a NOT_VALID to force redraw of entire screen +int update_screen(int type) +{ + static bool did_intro = false; + bool is_stl_global = global_stl_height() > 0; + + // Don't do anything if the screen structures are (not yet) valid. + // A VimResized autocmd can invoke redrawing in the middle of a resize, + // which would bypass the checks in screen_resize for popupmenu etc. + if (!default_grid.chars || resizing) { + return FAIL; + } + + // May have postponed updating diffs. + if (need_diff_redraw) { + diff_redraw(true); + } + + if (must_redraw) { + if (type < must_redraw) { // use maximal type + type = must_redraw; + } + + // must_redraw is reset here, so that when we run into some weird + // reason to redraw while busy redrawing (e.g., asynchronous + // scrolling), or update_topline() in win_update() will cause a + // scroll, or a decoration provider requires a redraw, the screen + // will be redrawn later or in win_update(). + must_redraw = 0; + } + + // Need to update w_lines[]. + if (curwin->w_lines_valid == 0 && type < NOT_VALID) { + type = NOT_VALID; + } + + // Postpone the redrawing when it's not needed and when being called + // recursively. + if (!redrawing() || updating_screen) { + must_redraw = type; + if (type > INVERTED_ALL) { + curwin->w_lines_valid = 0; // don't use w_lines[].wl_size now + } + return FAIL; + } + updating_screen = 1; + + display_tick++; // let syntax code know we're in a next round of + // display updating + + // Tricky: vim code can reset msg_scrolled behind our back, so need + // separate bookkeeping for now. + if (msg_did_scroll) { + msg_did_scroll = false; + msg_scrolled_at_flush = 0; + } + + if (type >= CLEAR || !default_grid.valid) { + ui_comp_set_screen_valid(false); + } + + // if the screen was scrolled up when displaying a message, scroll it down + if (msg_scrolled || msg_grid_invalid) { + clear_cmdline = true; + int valid = MAX(Rows - msg_scrollsize(), 0); + if (msg_grid.chars) { + // 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); + } + } + if (msg_use_msgsep()) { + msg_grid.throttled = false; + // CLEAR is already handled + if (type == NOT_VALID && !ui_has(kUIMultigrid) && msg_scrolled) { + ui_comp_set_screen_valid(false); + for (int i = valid; i < Rows - p_ch; i++) { + grid_clear_line(&default_grid, default_grid.line_offset[i], + Columns, false); + } + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_floating) { + continue; + } + if (W_ENDROW(wp) > valid) { + wp->w_redr_type = MAX(wp->w_redr_type, NOT_VALID); + } + if (!is_stl_global && W_ENDROW(wp) + wp->w_status_height > valid) { + wp->w_redr_status = true; + } + } + if (is_stl_global && Rows - p_ch - 1 > valid) { + curwin->w_redr_status = true; + } + } + msg_grid_set_pos(Rows - (int)p_ch, false); + msg_grid_invalid = false; + } else if (msg_scrolled > Rows - 5) { // clearing is faster + type = CLEAR; + } else if (type != CLEAR) { + check_for_delay(false); + grid_ins_lines(&default_grid, 0, msg_scrolled, Rows, 0, Columns); + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_floating) { + continue; + } + if (wp->w_winrow < msg_scrolled) { + if (W_ENDROW(wp) > msg_scrolled + && wp->w_redr_type < REDRAW_TOP + && wp->w_lines_valid > 0 + && wp->w_topline == wp->w_lines[0].wl_lnum) { + wp->w_upd_rows = msg_scrolled - wp->w_winrow; + wp->w_redr_type = REDRAW_TOP; + } else { + wp->w_redr_type = NOT_VALID; + if (wp->w_winrow + wp->w_winbar_height <= msg_scrolled) { + wp->w_redr_status = true; + } + } + } + } + if (is_stl_global && Rows - p_ch - 1 <= msg_scrolled) { + curwin->w_redr_status = true; + } + redraw_cmdline = true; + redraw_tabline = true; + } + msg_scrolled = 0; + msg_scrolled_at_flush = 0; + need_wait_return = false; + } + + win_ui_flush(); + msg_ext_check_clear(); + + // reset cmdline_row now (may have been changed temporarily) + compute_cmdrow(); + + bool hl_changed = false; + // Check for changed highlighting + if (need_highlight_changed) { + highlight_changed(); + hl_changed = true; + } + + if (type == CLEAR) { // first clear screen + screenclear(); // will reset clear_cmdline + cmdline_screen_cleared(); // clear external cmdline state + type = NOT_VALID; + // must_redraw may be set indirectly, avoid another redraw later + must_redraw = 0; + } else if (!default_grid.valid) { + grid_invalidate(&default_grid); + default_grid.valid = true; + } + + // After disabling msgsep the grid might not have been deallocated yet, + // hence we also need to check msg_grid.chars + if (type == NOT_VALID && (msg_use_grid() || msg_grid.chars)) { + grid_fill(&default_grid, Rows - (int)p_ch, Rows, 0, Columns, ' ', ' ', 0); + } + + ui_comp_set_screen_valid(true); + + DecorProviders providers; + decor_providers_start(&providers, type, &provider_err); + + // "start" callback could have changed highlights for global elements + if (win_check_ns_hl(NULL)) { + redraw_cmdline = true; + redraw_tabline = true; + } + + if (clear_cmdline) { // going to clear cmdline (done below) + check_for_delay(false); + } + + // Force redraw when width of 'number' or 'relativenumber' column + // changes. + if (curwin->w_redr_type < NOT_VALID + && curwin->w_nrwidth != ((curwin->w_p_nu || curwin->w_p_rnu) + ? number_width(curwin) : 0)) { + curwin->w_redr_type = NOT_VALID; + } + + // Only start redrawing if there is really something to do. + if (type == INVERTED) { + update_curswant(); + } + if (curwin->w_redr_type < type + && !((type == VALID + && curwin->w_lines[0].wl_valid + && curwin->w_topfill == curwin->w_old_topfill + && curwin->w_botfill == curwin->w_old_botfill + && curwin->w_topline == curwin->w_lines[0].wl_lnum) + || (type == INVERTED + && VIsual_active + && curwin->w_old_cursor_lnum == curwin->w_cursor.lnum + && curwin->w_old_visual_mode == VIsual_mode + && (curwin->w_valid & VALID_VIRTCOL) + && curwin->w_old_curswant == curwin->w_curswant))) { + curwin->w_redr_type = type; + } + + // Redraw the tab pages line if needed. + if (redraw_tabline || type >= NOT_VALID) { + update_window_hl(curwin, type >= NOT_VALID); + FOR_ALL_TABS(tp) { + if (tp != curtab) { + update_window_hl(tp->tp_curwin, type >= NOT_VALID); + } + } + 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) { + update_window_hl(wp, type >= NOT_VALID || hl_changed); + + buf_T *buf = wp->w_buffer; + if (buf->b_mod_set) { + if (buf->b_mod_tick_syn < display_tick + && syntax_present(wp)) { + syn_stack_apply_changes(buf); + buf->b_mod_tick_syn = display_tick; + } + + if (buf->b_mod_tick_decor < display_tick) { + decor_providers_invoke_buf(buf, &providers, &provider_err); + buf->b_mod_tick_decor = display_tick; + } + } + } + + // Go from top to bottom through the windows, redrawing the ones that need it. + bool did_one = false; + screen_search_hl.rm.regprog = NULL; + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid_alloc.chars) { + grid_invalidate(&wp->w_grid_alloc); + wp->w_redr_type = NOT_VALID; + } + + win_check_ns_hl(wp); + + // reallocate grid if needed. + win_grid_alloc(wp); + + if (wp->w_redr_border || wp->w_redr_type >= NOT_VALID) { + win_redr_border(wp); + } + + if (wp->w_redr_type != 0) { + if (!did_one) { + did_one = true; + start_search_hl(); + } + win_update(wp, &providers); + } + + // redraw status line and window bar after the window to minimize cursor movement + if (wp->w_redr_status) { + win_redr_winbar(wp); + win_redr_status(wp); + } + } + + end_search_hl(); + + // May need to redraw the popup menu. + if (pum_drawn() && must_redraw_pum) { + win_check_ns_hl(curwin); + pum_redraw(); + } + + win_check_ns_hl(NULL); + + // Reset b_mod_set flags. Going through all windows is probably faster + // than going through all buffers (there could be many buffers). + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + wp->w_buffer->b_mod_set = false; + } + + updating_screen = 0; + + // Clear or redraw the command line. Done last, because scrolling may + // mess up the command line. + if (clear_cmdline || redraw_cmdline || redraw_mode) { + showmode(); + } + + // May put up an introductory message when not editing a file + if (!did_intro) { + maybe_intro_message(); + } + did_intro = true; + + decor_providers_invoke_end(&providers, &provider_err); + kvi_destroy(providers); + + // either cmdline is cleared, not drawn or mode is last drawn + cmdline_was_last_drawn = false; + return OK; +} + +static void win_redr_border(win_T *wp) +{ + wp->w_redr_border = false; + if (!(wp->w_floating && wp->w_float_config.border)) { + return; + } + + ScreenGrid *grid = &wp->w_grid_alloc; + + schar_T *chars = wp->w_float_config.border_chars; + 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; + + if (adj[0]) { + grid_puts_line_start(grid, 0); + if (adj[3]) { + grid_put_schar(grid, 0, 0, chars[0], attrs[0]); + } + for (int i = 0; i < icol; i++) { + grid_put_schar(grid, 0, i + adj[3], chars[1], attrs[1]); + } + if (adj[1]) { + grid_put_schar(grid, 0, icol + adj[3], chars[2], attrs[2]); + } + grid_puts_line_flush(false); + } + + 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); + } + 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); + } + } + + if (adj[2]) { + grid_puts_line_start(grid, irow + adj[0]); + if (adj[3]) { + grid_put_schar(grid, irow + adj[0], 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]); + } + if (adj[1]) { + grid_put_schar(grid, irow + adj[0], icol + adj[3], chars[4], attrs[4]); + } + grid_puts_line_flush(false); + } +} + +/// Redraw the status line of window `wp`. +/// +/// If inversion is possible we use it. Else '=' characters are used. +static void win_redr_status(win_T *wp) +{ + int row; + int col; + char_u *p; + int len; + int fillchar; + int attr; + int width; + int this_ru_col; + bool is_stl_global = global_stl_height() > 0; + static bool busy = false; + + // May get here recursively when 'statusline' (indirectly) + // invokes ":redrawstatus". Simply ignore the call then. + if (busy + // Also ignore if wildmenu is showing. + || (wild_menu_showing != 0 && !ui_has(kUIWildmenu))) { + return; + } + busy = true; + + wp->w_redr_status = false; + if (wp->w_status_height == 0 && !(is_stl_global && wp == curwin)) { + // no status line, either global statusline is enabled or the window is a last window + redraw_cmdline = true; + } else if (!redrawing()) { + // Don't redraw right now, do it later. Don't update status line when + // popup menu is visible and may be drawn over it + wp->w_redr_status = true; + } else if (*p_stl != NUL || *wp->w_p_stl != NUL) { + // redraw custom status line + redraw_custom_statusline(wp); + } else { + fillchar = fillchar_status(&attr, wp); + width = is_stl_global ? Columns : wp->w_width; + + get_trans_bufname(wp->w_buffer); + p = NameBuff; + len = (int)STRLEN(p); + + if (bt_help(wp->w_buffer) + || wp->w_p_pvw + || bufIsChanged(wp->w_buffer) + || wp->w_buffer->b_p_ro) { + *(p + len++) = ' '; + } + if (bt_help(wp->w_buffer)) { + snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Help]")); + len += (int)STRLEN(p + len); + } + if (wp->w_p_pvw) { + snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Preview]")); + len += (int)STRLEN(p + len); + } + if (bufIsChanged(wp->w_buffer)) { + snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", "[+]"); + len += (int)STRLEN(p + len); + } + if (wp->w_buffer->b_p_ro) { + snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[RO]")); + // len += (int)STRLEN(p + len); // dead assignment + } + + this_ru_col = ru_col - (Columns - width); + if (this_ru_col < (width + 1) / 2) { + this_ru_col = (width + 1) / 2; + } + if (this_ru_col <= 1) { + p = (char_u *)"<"; // No room for file name! + len = 1; + } else { + int clen = 0, i; + + // Count total number of display cells. + clen = (int)mb_string2cells((char *)p); + + // Find first character that will fit. + // Going from start to end is much faster for DBCS. + for (i = 0; p[i] != NUL && clen >= this_ru_col - 1; + i += utfc_ptr2len((char *)p + i)) { + clen -= utf_ptr2cells((char *)p + i); + } + len = clen; + if (i > 0) { + p = p + i - 1; + *p = '<'; + len++; + } + } + + row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp); + col = is_stl_global ? 0 : wp->w_wincol; + grid_puts(&default_grid, p, row, col, attr); + grid_fill(&default_grid, row, row + 1, len + col, + this_ru_col + col, fillchar, fillchar, attr); + + if (get_keymap_str(wp, "<%s>", (char *)NameBuff, MAXPATHL) + && this_ru_col - len > (int)(STRLEN(NameBuff) + 1)) { + grid_puts(&default_grid, NameBuff, row, + (int)((size_t)this_ru_col - STRLEN(NameBuff) - 1), attr); + } + + win_redr_ruler(wp, true); + } + + // May need to draw the character below the vertical separator. + if (wp->w_vsep_width != 0 && wp->w_status_height != 0 && redrawing()) { + if (stl_connected(wp)) { + fillchar = fillchar_status(&attr, wp); + } else { + fillchar = fillchar_vsep(wp, &attr); + } + grid_putchar(&default_grid, fillchar, W_ENDROW(wp), W_ENDCOL(wp), attr); + } + busy = false; +} + +/// Redraw the status line according to 'statusline' and take care of any +/// errors encountered. +static void redraw_custom_statusline(win_T *wp) +{ + static bool entered = false; + int saved_did_emsg = did_emsg; + + // When called recursively return. This can happen when the statusline + // contains an expression that triggers a redraw. + if (entered) { + return; + } + entered = true; + + did_emsg = false; + win_redr_custom(wp, false, false); + if (did_emsg) { + // When there is an error disable the statusline, otherwise the + // display is messed up with errors and a redraw triggers the problem + // again and again. + set_string_option_direct("statusline", -1, "", + OPT_FREE | (*wp->w_p_stl != NUL + ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR); + } + did_emsg |= saved_did_emsg; + entered = false; +} + +static void win_redr_winbar(win_T *wp) +{ + static bool entered = false; + + // Return when called recursively. This can happen when the winbar contains an expression + // that triggers a redraw. + if (entered) { + return; + } + entered = true; + + if (wp->w_winbar_height == 0 || !redrawing()) { + // Do nothing. + } else if (*p_wbr != NUL || *wp->w_p_wbr != NUL) { + int saved_did_emsg = did_emsg; + + did_emsg = false; + win_redr_custom(wp, true, false); + if (did_emsg) { + // When there is an error disable the winbar, otherwise the + // display is messed up with errors and a redraw triggers the problem + // again and again. + set_string_option_direct("winbar", -1, "", + OPT_FREE | (*wp->w_p_stl != NUL + ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR); + } + did_emsg |= saved_did_emsg; + } + entered = false; +} + +/// Show current status info in ruler and various other places +/// +/// @param always if false, only show ruler if position has changed. +void showruler(bool always) +{ + if (!always && !redrawing()) { + return; + } + if ((*p_stl != NUL || *curwin->w_p_stl != NUL) + && (curwin->w_status_height || global_stl_height())) { + redraw_custom_statusline(curwin); + } else { + win_redr_ruler(curwin, always); + } + if (*p_wbr != NUL || *curwin->w_p_wbr != NUL) { + win_redr_winbar(curwin); + } + + if (need_maketitle + || (p_icon && (stl_syntax & STL_IN_ICON)) + || (p_title && (stl_syntax & STL_IN_TITLE))) { + maketitle(); + } + + // Redraw the tab pages line if needed. + if (redraw_tabline) { + draw_tabline(); + } +} + +static void redraw_win_signcol(win_T *wp) +{ + // If we can compute a change in the automatic sizing of the sign column + // under 'signcolumn=auto:X' and signs currently placed in the buffer, better + // figuring it out here so we can redraw the entire screen for it. + int scwidth = wp->w_scwidth; + wp->w_scwidth = win_signcol_count(wp); + if (wp->w_scwidth != scwidth) { + changed_line_abv_curs_win(wp); + } +} + +/// Check if horizontal separator of window "wp" at specified window corner is connected to the +/// horizontal separator of another window +/// Assumes global statusline is enabled +static bool hsep_connected(win_T *wp, WindowCorner corner) +{ + bool before = (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT); + int sep_row = (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) + ? wp->w_winrow - 1 : W_ENDROW(wp); + frame_T *fr = wp->w_frame; + + while (fr->fr_parent != NULL) { + if (fr->fr_parent->fr_layout == FR_ROW && (before ? fr->fr_prev : fr->fr_next) != NULL) { + fr = before ? fr->fr_prev : fr->fr_next; + break; + } + fr = fr->fr_parent; + } + if (fr->fr_parent == NULL) { + return false; + } + while (fr->fr_layout != FR_LEAF) { + fr = fr->fr_child; + if (fr->fr_parent->fr_layout == FR_ROW && before) { + while (fr->fr_next != NULL) { + fr = fr->fr_next; + } + } else { + while (fr->fr_next != NULL && frame2win(fr)->w_winrow + fr->fr_height < sep_row) { + fr = fr->fr_next; + } + } + } + + return (sep_row == fr->fr_win->w_winrow - 1 || sep_row == W_ENDROW(fr->fr_win)); +} + +/// Check if vertical separator of window "wp" at specified window corner is connected to the +/// vertical separator of another window +static bool vsep_connected(win_T *wp, WindowCorner corner) +{ + bool before = (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT); + int sep_col = (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) + ? wp->w_wincol - 1 : W_ENDCOL(wp); + frame_T *fr = wp->w_frame; + + while (fr->fr_parent != NULL) { + if (fr->fr_parent->fr_layout == FR_COL && (before ? fr->fr_prev : fr->fr_next) != NULL) { + fr = before ? fr->fr_prev : fr->fr_next; + break; + } + fr = fr->fr_parent; + } + if (fr->fr_parent == NULL) { + return false; + } + while (fr->fr_layout != FR_LEAF) { + fr = fr->fr_child; + if (fr->fr_parent->fr_layout == FR_COL && before) { + while (fr->fr_next != NULL) { + fr = fr->fr_next; + } + } else { + while (fr->fr_next != NULL && frame2win(fr)->w_wincol + fr->fr_width < sep_col) { + fr = fr->fr_next; + } + } + } + + return (sep_col == fr->fr_win->w_wincol - 1 || sep_col == W_ENDCOL(fr->fr_win)); +} + +/// Draw the vertical separator right of window "wp" +static void draw_vsep_win(win_T *wp) +{ + int hl; + int c; + + if (wp->w_vsep_width) { + // draw the vertical separator right of this window + c = fillchar_vsep(wp, &hl); + grid_fill(&default_grid, wp->w_winrow, W_ENDROW(wp), + W_ENDCOL(wp), W_ENDCOL(wp) + 1, c, ' ', hl); + } +} + +/// Draw the horizontal separator below window "wp" +static void draw_hsep_win(win_T *wp) +{ + int hl; + int c; + + if (wp->w_hsep_height) { + // draw the horizontal separator below this window + c = fillchar_hsep(wp, &hl); + 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) +{ + // It's impossible for windows to be connected neither vertically nor horizontally + // So if they're not vertically connected, assume they're horizontally connected + if (vsep_connected(wp, corner)) { + if (hsep_connected(wp, corner)) { + return wp->w_p_fcs_chars.verthoriz; + } else if (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) { + return wp->w_p_fcs_chars.vertright; + } else { + return wp->w_p_fcs_chars.vertleft; + } + } else if (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) { + return wp->w_p_fcs_chars.horizdown; + } else { + return wp->w_p_fcs_chars.horizup; + } +} + +/// Draw separator connecting characters on the corners of window "wp" +static void draw_sep_connectors_win(win_T *wp) +{ + // Don't draw separator connectors unless global statusline is enabled and the window has + // either a horizontal or vertical separator + if (global_stl_height() == 0 || !(wp->w_hsep_height == 1 || wp->w_vsep_width == 1)) { + return; + } + + int hl = win_hl_attr(wp, HLF_C); + + // Determine which edges of the screen the window is located on so we can avoid drawing separators + // on corners contained in those edges + bool win_at_top; + bool win_at_bottom = wp->w_hsep_height == 0; + bool win_at_left; + bool win_at_right = wp->w_vsep_width == 0; + frame_T *frp; + + for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) { + if (frp->fr_parent->fr_layout == FR_COL && frp->fr_prev != NULL) { + break; + } + } + win_at_top = frp->fr_parent == NULL; + for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) { + if (frp->fr_parent->fr_layout == FR_ROW && frp->fr_prev != NULL) { + break; + } + } + 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); + } +} + +/// Update a single window. +/// +/// This may cause the windows below it also to be redrawn (when clearing the +/// screen or scrolling lines). +/// +/// How the window is redrawn depends on wp->w_redr_type. Each type also +/// implies the one below it. +/// NOT_VALID redraw the whole window +/// SOME_VALID redraw the whole window but do scroll when possible +/// REDRAW_TOP redraw the top w_upd_rows window lines, otherwise like VALID +/// INVERTED redraw the changed part of the Visual area +/// INVERTED_ALL redraw the whole Visual area +/// VALID 1. scroll up/down to adjust for a changed w_topline +/// 2. update lines at the top when scrolled down +/// 3. redraw changed text: +/// - if wp->w_buffer->b_mod_set set, update lines between +/// b_mod_top and b_mod_bot. +/// - if wp->w_redraw_top non-zero, redraw lines between +/// wp->w_redraw_top and wp->w_redr_bot. +/// - continue redrawing when syntax status is invalid. +/// 4. if scrolled up, update lines at the bottom. +/// This results in three areas that may need updating: +/// top: from first row to top_end (when scrolled down) +/// mid: from mid_start to mid_end (update inversion or changed text) +/// bot: from bot_start to last row (when scrolled up) +static void win_update(win_T *wp, DecorProviders *providers) +{ + bool called_decor_providers = false; +win_update_start: + ; + buf_T *buf = wp->w_buffer; + int type; + int top_end = 0; // Below last row of the top area that needs + // updating. 0 when no top area updating. + int mid_start = 999; // first row of the mid area that needs + // updating. 999 when no mid area updating. + int mid_end = 0; // Below last row of the mid area that needs + // updating. 0 when no mid area updating. + int bot_start = 999; // first row of the bot area that needs + // updating. 999 when no bot area updating + bool scrolled_down = false; // true when scrolled down when w_topline got smaller a bit + bool top_to_mod = false; // redraw above mod_top + + int row; // current window row to display + linenr_T lnum; // current buffer lnum to display + int idx; // current index in w_lines[] + int srow; // starting row of the current line + + bool eof = false; // if true, we hit the end of the file + bool didline = false; // if true, we finished the last line + int i; + long j; + static bool recursive = false; // being called recursively + const linenr_T old_botline = wp->w_botline; + // Remember what happened to the previous line. +#define DID_NONE 1 // didn't update a line +#define DID_LINE 2 // updated a normal line +#define DID_FOLD 3 // updated a folded line + int did_update = DID_NONE; + linenr_T syntax_last_parsed = 0; // last parsed text line + linenr_T mod_top = 0; + linenr_T mod_bot = 0; + int save_got_int; + + type = wp->w_redr_type; + + if (type >= NOT_VALID) { + wp->w_redr_status = true; + wp->w_lines_valid = 0; + } + + // Window is zero-height: Only need to draw the separator + if (wp->w_grid.rows == 0) { + // draw the horizontal separator below this window + draw_hsep_win(wp); + draw_sep_connectors_win(wp); + wp->w_redr_type = 0; + return; + } + + // Window is zero-width: Only need to draw the separator. + if (wp->w_grid.cols == 0) { + // draw the vertical separator right of this window + draw_vsep_win(wp); + draw_sep_connectors_win(wp); + wp->w_redr_type = 0; + return; + } + + redraw_win_signcol(wp); + + init_search_hl(wp, &screen_search_hl); + + // Force redraw when width of 'number' or 'relativenumber' column + // changes. + i = (wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) : 0; + if (wp->w_nrwidth != i) { + type = NOT_VALID; + wp->w_nrwidth = i; + + if (buf->terminal) { + terminal_check_size(buf->terminal); + } + } else if (buf->b_mod_set + && buf->b_mod_xlines != 0 + && wp->w_redraw_top != 0) { + // When there are both inserted/deleted lines and specific lines to be + // redrawn, w_redraw_top and w_redraw_bot may be invalid, just redraw + // everything (only happens when redrawing is off for while). + type = NOT_VALID; + } else { + // Set mod_top to the first line that needs displaying because of + // changes. Set mod_bot to the first line after the changes. + mod_top = wp->w_redraw_top; + if (wp->w_redraw_bot != 0) { + mod_bot = wp->w_redraw_bot + 1; + } else { + mod_bot = 0; + } + if (buf->b_mod_set) { + if (mod_top == 0 || mod_top > buf->b_mod_top) { + mod_top = buf->b_mod_top; + // Need to redraw lines above the change that may be included + // in a pattern match. + if (syntax_present(wp)) { + mod_top -= buf->b_s.b_syn_sync_linebreaks; + if (mod_top < 1) { + mod_top = 1; + } + } + } + if (mod_bot == 0 || mod_bot < buf->b_mod_bot) { + mod_bot = buf->b_mod_bot; + } + + // When 'hlsearch' is on and using a multi-line search pattern, a + // change in one line may make the Search highlighting in a + // previous line invalid. Simple solution: redraw all visible + // lines above the change. + // Same for a match pattern. + if (screen_search_hl.rm.regprog != NULL + && re_multiline(screen_search_hl.rm.regprog)) { + top_to_mod = true; + } else { + const matchitem_T *cur = wp->w_match_head; + while (cur != NULL) { + if (cur->match.regprog != NULL + && re_multiline(cur->match.regprog)) { + top_to_mod = true; + break; + } + cur = cur->next; + } + } + } + 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 + // line of that fold. If the line is folded now, get the first + // folded line. Use the minimum of these two. + + // Find last valid w_lines[] entry above mod_top. Set lnumt to + // 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; + for (i = 0; i < wp->w_lines_valid; i++) { + if (wp->w_lines[i].wl_valid) { + if (wp->w_lines[i].wl_lastlnum < mod_top) { + lnumt = wp->w_lines[i].wl_lastlnum + 1; + } + if (lnumb == MAXLNUM && wp->w_lines[i].wl_lnum >= mod_bot) { + lnumb = wp->w_lines[i].wl_lnum; + // When there is a fold column it might need updating + // in the next line ("J" just above an open fold). + if (compute_foldcolumn(wp, 0) > 0) { + lnumb++; + } + } + } + } + + (void)hasFoldingWin(wp, mod_top, &mod_top, NULL, true, NULL); + if (mod_top > lnumt) { + mod_top = lnumt; + } + + // Now do the same for the bottom line (one above mod_bot). + mod_bot--; + (void)hasFoldingWin(wp, mod_bot, NULL, &mod_bot, true, NULL); + mod_bot++; + if (mod_bot < lnumb) { + mod_bot = lnumb; + } + } + + // When a change starts above w_topline and the end is below + // w_topline, start redrawing at w_topline. + // If the end of the change is above w_topline: do like no change was + // made, but redraw the first line to find changes in syntax. + if (mod_top != 0 && mod_top < wp->w_topline) { + if (mod_bot > wp->w_topline) { + mod_top = wp->w_topline; + } else if (syntax_present(wp)) { + top_end = 1; + } + } + + // When line numbers are displayed need to redraw all lines below + // inserted/deleted lines. + if (mod_top != 0 && buf->b_mod_xlines != 0 && wp->w_p_nu) { + mod_bot = MAXLNUM; + } + } + wp->w_redraw_top = 0; // reset for next time + wp->w_redraw_bot = 0; + + // When only displaying the lines at the top, set top_end. Used when + // window has scrolled down for msg_scrolled. + if (type == REDRAW_TOP) { + j = 0; + for (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; + break; + } + } + if (top_end == 0) { + // not found (cannot happen?): redraw everything + type = NOT_VALID; + } else { + // top area defined, the rest is VALID + type = VALID; + } + } + + // If there are no changes on the screen that require a complete redraw, + // handle three cases: + // 1: we are off the top of the screen by a few lines: scroll down + // 2: wp->w_topline is below wp->w_lines[0].wl_lnum: may scroll up + // 3: wp->w_topline is wp->w_lines[0].wl_lnum: find first entry in + // w_lines[] that needs updating. + if ((type == VALID || type == SOME_VALID + || type == INVERTED || type == INVERTED_ALL) + && !wp->w_botfill && !wp->w_old_botfill) { + if (mod_top != 0 + && wp->w_topline == mod_top + && (!wp->w_lines[0].wl_valid + || wp->w_topline == wp->w_lines[0].wl_lnum)) { + // w_topline is the first changed line and window is not scrolled, + // the scrolling from changed lines will be done further down. + } else if (wp->w_lines[0].wl_valid + && (wp->w_topline < wp->w_lines[0].wl_lnum + || (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. + if (hasAnyFolding(wp)) { + linenr_T ln; + + // count the number of lines we are off, counting a sequence + // of folded lines as one + j = 0; + for (ln = wp->w_topline; ln < wp->w_lines[0].wl_lnum; ln++) { + j++; + if (j >= wp->w_grid.rows - 2) { + break; + } + (void)hasFoldingWin(wp, ln, NULL, &ln, true, NULL); + } + } else { + j = wp->w_lines[0].wl_lnum - wp->w_topline; + } + if (j < wp->w_grid.rows - 2) { // not too far off + i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1); + // 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; + } + if (i != 0 && i < wp->w_grid.rows - 2) { // less than a screen off + // Try to insert the correct number of lines. + // If not the last window, delete the lines at the bottom. + // win_ins_lines may fail when the terminal can't do it. + win_scroll_lines(wp, 0, i); + if (wp->w_lines_valid != 0) { + // Need to update rows that are new, stop at the + // first one that scrolled down. + top_end = i; + scrolled_down = true; + + // Move the entries that were scrolled, disable + // the entries for the lines to be redrawn. + if ((wp->w_lines_valid += (linenr_T)j) > wp->w_grid.rows) { + wp->w_lines_valid = wp->w_grid.rows; + } + for (idx = wp->w_lines_valid; idx - j >= 0; idx--) { + wp->w_lines[idx] = wp->w_lines[idx - j]; + } + while (idx >= 0) { + wp->w_lines[idx--].wl_valid = false; + } + } + } else { + mid_start = 0; // redraw all lines + } + } else { + mid_start = 0; // redraw all lines + } + } else { + // New topline is at or below old topline: May scroll up. + // When topline didn't change, find first entry in w_lines[] that + // needs updating. + + // try to find wp->w_topline in wp->w_lines[].wl_lnum + j = -1; + row = 0; + for (i = 0; i < wp->w_lines_valid; i++) { + if (wp->w_lines[i].wl_valid + && wp->w_lines[i].wl_lnum == wp->w_topline) { + j = i; + break; + } + row += wp->w_lines[i].wl_size; + } + if (j == -1) { + // if wp->w_topline is not in wp->w_lines[].wl_lnum redraw all + // lines + mid_start = 0; + } else { + // Try to delete the correct number of lines. + // wp->w_topline is at wp->w_lines[i].wl_lnum. + + // If the topline didn't change, delete old filler lines, + // otherwise delete filler lines of the new topline... + if (wp->w_lines[0].wl_lnum == wp->w_topline) { + row += wp->w_old_topfill; + } else { + row += win_get_fill(wp, wp->w_topline); + } + // ... but don't delete new filler lines. + row -= wp->w_topfill; + if (row > 0) { + win_scroll_lines(wp, 0, -row); + bot_start = wp->w_grid.rows - row; + } + if ((row == 0 || bot_start < 999) && wp->w_lines_valid != 0) { + // Skip the lines (below the deleted lines) that are still + // valid and don't need redrawing. Copy their info + // upwards, to compensate for the deleted lines. Set + // bot_start to the first row that needs redrawing. + bot_start = 0; + idx = 0; + for (;;) { + wp->w_lines[idx] = wp->w_lines[j]; + // stop at line that didn't fit, unless it is still + // valid (no lines deleted) + if (row > 0 && bot_start + row + + (int)wp->w_lines[j].wl_size > wp->w_grid.rows) { + wp->w_lines_valid = idx + 1; + break; + } + bot_start += wp->w_lines[idx++].wl_size; + + // stop at the last valid entry in w_lines[].wl_size + if (++j >= wp->w_lines_valid) { + wp->w_lines_valid = idx; + break; + } + } + + // Correct the first entry for filler lines at the top + // when it won't get updated below. + if (win_may_fill(wp) && bot_start > 0) { + wp->w_lines[0].wl_size = (uint16_t)(plines_win_nofill(wp, wp->w_topline, true) + + wp->w_topfill); + } + } + } + } + + // When starting redraw in the first line, redraw all lines. + if (mid_start == 0) { + mid_end = wp->w_grid.rows; + } + } else { + // Not VALID or INVERTED: redraw all lines. + mid_start = 0; + mid_end = wp->w_grid.rows; + } + + if (type == SOME_VALID) { + // SOME_VALID: redraw all lines. + mid_start = 0; + mid_end = wp->w_grid.rows; + type = NOT_VALID; + } + + // check if we are updating or removing the inverted part + if ((VIsual_active && buf == curwin->w_buffer) + || (wp->w_old_cursor_lnum != 0 && type != NOT_VALID)) { + linenr_T from, to; + + if (VIsual_active) { + if (VIsual_mode != wp->w_old_visual_mode || type == INVERTED_ALL) { + // If the type of Visual selection changed, redraw the whole + // selection. Also when the ownership of the X selection is + // gained or lost. + if (curwin->w_cursor.lnum < VIsual.lnum) { + from = curwin->w_cursor.lnum; + to = VIsual.lnum; + } else { + from = VIsual.lnum; + to = curwin->w_cursor.lnum; + } + // redraw more when the cursor moved as well + if (wp->w_old_cursor_lnum < from) { + from = wp->w_old_cursor_lnum; + } + if (wp->w_old_cursor_lnum > to) { + to = wp->w_old_cursor_lnum; + } + if (wp->w_old_visual_lnum < from) { + from = wp->w_old_visual_lnum; + } + if (wp->w_old_visual_lnum > to) { + to = wp->w_old_visual_lnum; + } + } else { + // Find the line numbers that need to be updated: The lines + // between the old cursor position and the current cursor + // position. Also check if the Visual position changed. + if (curwin->w_cursor.lnum < wp->w_old_cursor_lnum) { + from = curwin->w_cursor.lnum; + to = wp->w_old_cursor_lnum; + } else { + from = wp->w_old_cursor_lnum; + to = curwin->w_cursor.lnum; + if (from == 0) { // Visual mode just started + from = to; + } + } + + if (VIsual.lnum != wp->w_old_visual_lnum + || VIsual.col != wp->w_old_visual_col) { + if (wp->w_old_visual_lnum < from + && wp->w_old_visual_lnum != 0) { + from = wp->w_old_visual_lnum; + } + if (wp->w_old_visual_lnum > to) { + to = wp->w_old_visual_lnum; + } + if (VIsual.lnum < from) { + from = VIsual.lnum; + } + if (VIsual.lnum > to) { + to = VIsual.lnum; + } + } + } + + // If in block mode and changed column or curwin->w_curswant: + // update all lines. + // 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; + + if (curwin->w_p_lbr) { + curwin->w_ve_flags = VE_ALL; + } + + getvcols(wp, &VIsual, &curwin->w_cursor, &fromc, &toc); + toc++; + curwin->w_ve_flags = save_ve_flags; + // Highlight to the end of the line, unless 'virtualedit' has + // "block". + if (curwin->w_curswant == MAXCOL) { + if (get_ve_flags() & VE_BLOCK) { + pos_T pos; + int cursor_above = curwin->w_cursor.lnum < VIsual.lnum; + + // Need to find the longest line. + toc = 0; + pos.coladd = 0; + for (pos.lnum = curwin->w_cursor.lnum; + cursor_above ? pos.lnum <= VIsual.lnum : pos.lnum >= VIsual.lnum; + pos.lnum += cursor_above ? 1 : -1) { + colnr_T t; + + pos.col = (colnr_T)STRLEN(ml_get_buf(wp->w_buffer, pos.lnum, false)); + getvvcol(wp, &pos, NULL, NULL, &t); + if (toc < t) { + toc = t; + } + } + toc++; + } else { + toc = MAXCOL; + } + } + + if (fromc != wp->w_old_cursor_fcol + || toc != wp->w_old_cursor_lcol) { + if (from > VIsual.lnum) { + from = VIsual.lnum; + } + if (to < VIsual.lnum) { + to = VIsual.lnum; + } + } + wp->w_old_cursor_fcol = fromc; + wp->w_old_cursor_lcol = toc; + } + } else { + // Use the line numbers of the old Visual area. + if (wp->w_old_cursor_lnum < wp->w_old_visual_lnum) { + from = wp->w_old_cursor_lnum; + to = wp->w_old_visual_lnum; + } else { + from = wp->w_old_visual_lnum; + to = wp->w_old_cursor_lnum; + } + } + + // There is no need to update lines above the top of the window. + if (from < wp->w_topline) { + from = wp->w_topline; + } + + // If we know the value of w_botline, use it to restrict the update to + // the lines that are visible in the window. + if (wp->w_valid & VALID_BOTLINE) { + if (from >= wp->w_botline) { + from = wp->w_botline - 1; + } + if (to >= wp->w_botline) { + to = wp->w_botline - 1; + } + } + + // Find the minimal part to be updated. + // Watch out for scrolling that made entries in w_lines[] invalid. + // E.g., CTRL-U makes the first half of w_lines[] invalid and sets + // top_end; need to redraw from top_end to the "to" line. + // A middle mouse click with a Visual selection may change the text + // above the Visual area and reset wl_valid, do count these for + // mid_end (in srow). + if (mid_start > 0) { + lnum = wp->w_topline; + idx = 0; + srow = 0; + if (scrolled_down) { + mid_start = top_end; + } else { + mid_start = 0; + } + while (lnum < from && idx < wp->w_lines_valid) { // find start + if (wp->w_lines[idx].wl_valid) { + mid_start += wp->w_lines[idx].wl_size; + } else if (!scrolled_down) { + srow += wp->w_lines[idx].wl_size; + } + idx++; + if (idx < wp->w_lines_valid && wp->w_lines[idx].wl_valid) { + lnum = wp->w_lines[idx].wl_lnum; + } else { + lnum++; + } + } + srow += mid_start; + mid_end = wp->w_grid.rows; + for (; idx < wp->w_lines_valid; idx++) { // find end + if (wp->w_lines[idx].wl_valid + && wp->w_lines[idx].wl_lnum >= to + 1) { + // Only update until first row of this line + mid_end = srow; + break; + } + srow += wp->w_lines[idx].wl_size; + } + } + } + + if (VIsual_active && buf == curwin->w_buffer) { + wp->w_old_visual_mode = (char)VIsual_mode; + wp->w_old_cursor_lnum = curwin->w_cursor.lnum; + wp->w_old_visual_lnum = VIsual.lnum; + wp->w_old_visual_col = VIsual.col; + wp->w_old_curswant = curwin->w_curswant; + } else { + wp->w_old_visual_mode = 0; + wp->w_old_cursor_lnum = 0; + wp->w_old_visual_lnum = 0; + wp->w_old_visual_col = 0; + } + + // reset got_int, otherwise regexp won't work + save_got_int = got_int; + got_int = 0; + // Set the time limit to 'redrawtime'. + proftime_T syntax_tm = profile_setlimit(p_rdt); + syn_set_timeout(&syntax_tm); + + // Update all the window rows. + idx = 0; // first entry in w_lines[].wl_size + row = 0; + srow = 0; + lnum = wp->w_topline; // first line shown in window + + win_extmark_arr.size = 0; + + decor_redraw_reset(buf, &decor_state); + + DecorProviders line_providers; + decor_providers_invoke_win(wp, providers, &line_providers, &provider_err); + (void)win_signcol_count(wp); // check if provider changed signcol width + if (must_redraw != 0) { + must_redraw = 0; + if (!called_decor_providers) { + called_decor_providers = true; + goto win_update_start; + } + } + + bool cursorline_standout = win_cursorline_standout(wp); + + win_check_ns_hl(wp); + + for (;;) { + // 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) { + didline = true; + break; + } + + // stop updating when hit the end of the file + if (lnum > buf->b_ml.ml_line_count) { + eof = true; + break; + } + + // Remember the starting row of the line that is going to be dealt + // with. It is used further down when the line doesn't fit. + srow = row; + + // Update a line when it is in an area that needs updating, when it + // has changes or w_lines[idx] is invalid. + // "bot_start" may be halfway a wrapped line after using + // win_scroll_lines(), check if the current line includes it. + // When syntax folding is being used, the saved syntax states will + // already have been updated, we can't see where the syntax state is + // the same again, just update until the end of the window. + if (row < top_end + || (row >= mid_start && row < mid_end) + || top_to_mod + || idx >= wp->w_lines_valid + || (row + wp->w_lines[idx].wl_size > bot_start) + || (mod_top != 0 + && (lnum == mod_top + || (lnum >= mod_top + && (lnum < mod_bot + || did_update == DID_FOLD + || (did_update == DID_LINE + && syntax_present(wp) + && ((foldmethodIsSyntax(wp) + && hasAnyFolding(wp)) + || syntax_check_changed(lnum))) + // match in fixed position might need redraw + // 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_last_cursorline) { + if (lnum == mod_top) { + top_to_mod = false; + } + + // When at start of changed lines: May scroll following lines + // up or down to minimize redrawing. + // Don't do this when the change continues until the end. + // Don't scroll when dollar_vcol >= 0, keep the "$". + // Don't scroll when redrawing the top, scrolled already above. + if (lnum == mod_top + && mod_bot != MAXLNUM + && !(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; + + // Count the old number of window rows, using w_lines[], which + // should still contain the sizes for the lines as they are + // currently displayed. + for (i = idx; i < wp->w_lines_valid; i++) { + // Only valid lines have a meaningful wl_lnum. Invalid + // lines are part of the changed area. + if (wp->w_lines[i].wl_valid + && wp->w_lines[i].wl_lnum == mod_bot) { + break; + } + old_rows += wp->w_lines[i].wl_size; + if (wp->w_lines[i].wl_valid + && wp->w_lines[i].wl_lastlnum + 1 == mod_bot) { + // Must have found the last valid entry above mod_bot. + // Add following invalid entries. + i++; + while (i < wp->w_lines_valid + && !wp->w_lines[i].wl_valid) { + old_rows += wp->w_lines[i++].wl_size; + } + break; + } + } + + if (i >= wp->w_lines_valid) { + // We can't find a valid line below the changed lines, + // need to redraw until the end of the window. + // Inserting/deleting lines has no use. + bot_start = 0; + } else { + // Able to count old number of rows: Count new window + // rows, and may insert/delete lines + 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; + } else { + new_rows += plines_win(wp, l, true); + } + j++; + if (new_rows > wp->w_grid.rows - row - 2) { + // it's getting too much, must redraw the rest + new_rows = 9999; + break; + } + } + 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 + // rest. If scrolling works, must redraw the text + // below the scrolled text. + if (row - xtra_rows >= wp->w_grid.rows - 2) { + mod_bot = MAXLNUM; + } else { + win_scroll_lines(wp, row, xtra_rows); + bot_start = wp->w_grid.rows + xtra_rows; + } + } else if (xtra_rows > 0) { + // May scroll text down. If there is not enough + // remaining text of scrolling fails, must redraw the + // rest. + if (row + xtra_rows >= wp->w_grid.rows - 2) { + mod_bot = MAXLNUM; + } else { + win_scroll_lines(wp, row + old_rows, xtra_rows); + if (top_end > row + old_rows) { + // Scrolled the part at the top that requires + // updating down. + top_end += xtra_rows; + } + } + } + + // When not updating the rest, may need to move w_lines[] + // entries. + if (mod_bot != MAXLNUM && i != j) { + if (j < i) { + int x = row + new_rows; + + // move entries in w_lines[] upwards + for (;;) { + // stop at last valid entry in w_lines[] + if (i >= wp->w_lines_valid) { + wp->w_lines_valid = (int)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; + break; + } + x += wp->w_lines[j++].wl_size; + i++; + } + if (bot_start > x) { + bot_start = x; + } + } else { // j > i + // move entries in w_lines[] downwards + j -= i; + wp->w_lines_valid += (linenr_T)j; + if (wp->w_lines_valid > wp->w_grid.rows) { + wp->w_lines_valid = wp->w_grid.rows; + } + for (i = wp->w_lines_valid; i - j >= idx; i--) { + wp->w_lines[i] = wp->w_lines[i - j]; + } + + // The w_lines[] entries for inserted lines are + // now invalid, but wl_size may be used above. + // Reset to zero. + while (i >= idx) { + wp->w_lines[i].wl_size = 0; + wp->w_lines[i--].wl_valid = false; + } + } + } + } + } + + // When lines are folded, display one line for all of them. + // Otherwise, display normally (can be several display lines when + // 'wrap' is on). + foldinfo_T foldinfo = fold_info(wp, lnum); + + if (foldinfo.fi_lines == 0 + && idx < wp->w_lines_valid + && wp->w_lines[idx].wl_valid + && wp->w_lines[idx].wl_lnum == lnum + && lnum > wp->w_topline + && !(dy_flags & (DY_LASTLINE | DY_TRUNCATE)) + && srow + wp->w_lines[idx].wl_size > wp->w_grid.rows + && win_get_fill(wp, lnum) == 0) { + // This line is not going to fit. Don't draw anything here, + // will draw "@ " lines below. + row = wp->w_grid.rows + 1; + } else { + prepare_search_hl(wp, &screen_search_hl, lnum); + // Let the syntax stuff know we skipped a few lines. + if (syntax_last_parsed != 0 && syntax_last_parsed + 1 < lnum + && syntax_present(wp)) { + syntax_end_parsing(syntax_last_parsed + 1); + } + + // 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); + + if (foldinfo.fi_lines == 0) { + wp->w_lines[idx].wl_folded = false; + wp->w_lines[idx].wl_lastlnum = lnum; + did_update = DID_LINE; + syntax_last_parsed = lnum; + } else { + foldinfo.fi_lines--; + wp->w_lines[idx].wl_folded = true; + wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines; + did_update = DID_FOLD; + } + } + + wp->w_lines[idx].wl_lnum = lnum; + wp->w_lines[idx].wl_valid = true; + + if (row > wp->w_grid.rows) { // past end of grid + // we may need the size of that too long line later on + if (dollar_vcol == -1) { + wp->w_lines[idx].wl_size = (uint16_t)plines_win(wp, lnum, true); + } + idx++; + break; + } + if (dollar_vcol == -1) { + wp->w_lines[idx].wl_size = (uint16_t)(row - srow); + } + idx++; + lnum += foldinfo.fi_lines + 1; + } else { + 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); + } + + // This line does not need to be drawn, advance to the next one. + row += wp->w_lines[idx++].wl_size; + if (row > wp->w_grid.rows) { // past end of screen + break; + } + lnum = wp->w_lines[idx - 1].wl_lastlnum + 1; + did_update = DID_NONE; + } + + if (lnum > buf->b_ml.ml_line_count) { + eof = true; + break; + } + } + // End of loop over all window lines. + + // 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_cursor_lnum_rnu = wp->w_p_rnu ? wp->w_cursor.lnum : 0; + + if (idx > wp->w_lines_valid) { + wp->w_lines_valid = idx; + } + + // Let the syntax stuff know we stop parsing here. + if (syntax_last_parsed != 0 && syntax_present(wp)) { + syntax_end_parsing(syntax_last_parsed + 1); + } + + // If we didn't hit the end of the file, and we didn't finish the last + // line we were working on, then the line didn't fit. + wp->w_empty_rows = 0; + wp->w_filler_rows = 0; + if (!eof && !didline) { + int at_attr = hl_combine_attr(win_bg_attr(wp), win_hl_attr(wp, HLF_AT)); + if (lnum == wp->w_topline) { + // Single line that does not fit! + // Don't overwrite it, it can be edited. + wp->w_botline = lnum + 1; + } else if (win_get_fill(wp, lnum) >= wp->w_grid.rows - srow) { + // Window ends in filler lines. + 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; + + // Last line isn't finished: Display "@@@" in the last screen line. + grid_puts_len(&wp->w_grid, (char_u *)"@@", MIN(wp->w_grid.cols, 2), scr_row, 0, at_attr); + + grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.cols, + '@', ' ', at_attr); + 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; + + // 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, '@', '@', at_attr); + set_empty_rows(wp, srow); + wp->w_botline = lnum; + } else { + win_draw_end(wp, '@', ' ', true, srow, wp->w_grid.rows, HLF_AT); + wp->w_botline = lnum; + } + } else { + if (eof) { // we hit the end of the file + wp->w_botline = buf->b_ml.ml_line_count + 1; + 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 = FOLDINFO_INIT; + row = win_line(wp, wp->w_botline, row, wp->w_grid.rows, + false, false, info, &line_providers, &provider_err); + } + } else if (dollar_vcol == -1) { + wp->w_botline = lnum; + } + + // make sure the rest of the screen is blank + // write the 'eob' character to rows that aren't part of the file. + win_draw_end(wp, wp->w_p_fcs_chars.eob, ' ', false, row, wp->w_grid.rows, + HLF_EOB); + } + + kvi_destroy(line_providers); + + if (wp->w_redr_type >= REDRAW_TOP) { + draw_vsep_win(wp); + draw_hsep_win(wp); + draw_sep_connectors_win(wp); + } + syn_set_timeout(NULL); + + // Reset the type of redrawing required, the window has been updated. + wp->w_redr_type = 0; + wp->w_old_topfill = wp->w_topfill; + wp->w_old_botfill = wp->w_botfill; + + // Send win_extmarks if needed + for (size_t n = 0; n < kv_size(win_extmark_arr); n++) { + ui_call_win_extmark(wp->w_grid_alloc.handle, wp->handle, + kv_A(win_extmark_arr, n).ns_id, (Integer)kv_A(win_extmark_arr, n).mark_id, + kv_A(win_extmark_arr, n).win_row, kv_A(win_extmark_arr, n).win_col); + } + + if (dollar_vcol == -1) { + // There is a trick with w_botline. If we invalidate it on each + // change that might modify it, this will cause a lot of expensive + // calls to plines_win() in update_topline() each time. Therefore the + // value of w_botline is often approximated, and this value is used to + // compute the value of w_topline. If the value of w_botline was + // wrong, check that the value of w_topline is correct (cursor is on + // the visible part of the text). If it's not, we need to redraw + // again. Mostly this just means scrolling up a few lines, so it + // doesn't look too bad. Only do this for the current window (where + // changes are relevant). + wp->w_valid |= VALID_BOTLINE; + wp->w_viewport_invalid = true; + if (wp == curwin && wp->w_botline != old_botline && !recursive) { + recursive = true; + curwin->w_valid &= ~VALID_TOPLINE; + update_topline(curwin); // may invalidate w_botline again + if (must_redraw != 0) { + // Don't update for changes in buffer again. + i = curbuf->b_mod_set; + curbuf->b_mod_set = false; + win_update(curwin, providers); + must_redraw = 0; + curbuf->b_mod_set = i; + } + recursive = false; + } + } + + // restore got_int, unless CTRL-C was hit while redrawing + if (!got_int) { + got_int = save_got_int; + } +} + +/// Redraw a window later, with update_screen(type). +/// +/// Set must_redraw only if not already set to a higher value. +/// e.g. if must_redraw is CLEAR, type NOT_VALID will do nothing. +void redraw_later(win_T *wp, int type) + FUNC_ATTR_NONNULL_ALL +{ + if (!exiting && wp->w_redr_type < type) { + wp->w_redr_type = type; + if (type >= NOT_VALID) { + wp->w_lines_valid = 0; + } + if (must_redraw < type) { // must_redraw is the maximum of all windows + must_redraw = type; + } + } +} + +/// Mark all windows to be redrawn later. +void redraw_all_later(int type) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + redraw_later(wp, type); + } + // This may be needed when switching tabs. + if (must_redraw < type) { + must_redraw = type; + } +} + +void screen_invalidate_highlights(void) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + redraw_later(wp, NOT_VALID); + wp->w_grid_alloc.valid = false; + } +} + +/// Mark all windows that are editing the current buffer to be updated later. +void redraw_curbuf_later(int type) +{ + redraw_buf_later(curbuf, type); +} + +void redraw_buf_later(buf_T *buf, int type) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf) { + redraw_later(wp, type); + } + } +} + +void redraw_buf_line_later(buf_T *buf, linenr_T line) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf + && line >= wp->w_topline && line < wp->w_botline) { + redrawWinline(wp, line); + } + } +} + +void redraw_buf_range_later(buf_T *buf, linenr_T firstline, linenr_T lastline) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf + && lastline >= wp->w_topline && firstline < wp->w_botline) { + if (wp->w_redraw_top == 0 || wp->w_redraw_top > firstline) { + wp->w_redraw_top = firstline; + } + if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lastline) { + wp->w_redraw_bot = lastline; + } + redraw_later(wp, VALID); + } + } +} + +/// called when the status bars for the buffer 'buf' need to be updated +void redraw_buf_status_later(buf_T *buf) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf + && (wp->w_status_height + || (wp == curwin && global_stl_height()) + || wp->w_winbar_height)) { + wp->w_redr_status = true; + if (must_redraw < VALID) { + must_redraw = VALID; + } + } + } +} + +/// Mark all status lines and window bars for redraw; used after first :cd +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) + || wp->w_winbar_height) { + wp->w_redr_status = true; + redraw_later(wp, VALID); + } + } +} + +/// Marks all status lines and window bars of the current buffer for redraw. +void status_redraw_curbuf(void) +{ + status_redraw_buf(curbuf); +} + +/// Marks all status lines and window bars of the given buffer for redraw. +void status_redraw_buf(buf_T *buf) +{ + bool is_stl_global = global_stl_height() != 0; + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf && ((!is_stl_global && wp->w_status_height) + || (is_stl_global && wp == curwin) || wp->w_winbar_height)) { + wp->w_redr_status = true; + redraw_later(wp, VALID); + } + } +} + +/// Redraw all status lines that need to be redrawn. +void redraw_statuslines(void) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_redr_status) { + win_redr_winbar(wp); + win_redr_status(wp); + } + } + if (redraw_tabline) { + draw_tabline(); + } +} + +/// Changed something in the current window, at buffer line "lnum", that +/// requires that line and possibly other lines to be redrawn. +/// Used when entering/leaving Insert mode with the cursor on a folded line. +/// Used to remove the "$" from a change command. +/// Note that when also inserting/deleting lines w_redraw_top and w_redraw_bot +/// may become invalid and the whole window will have to be redrawn. +void redrawWinline(win_T *wp, linenr_T lnum) + FUNC_ATTR_NONNULL_ALL +{ + if (lnum >= wp->w_topline + && lnum < wp->w_botline) { + if (wp->w_redraw_top == 0 || wp->w_redraw_top > lnum) { + wp->w_redraw_top = lnum; + } + if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lnum) { + wp->w_redraw_bot = lnum; + } + redraw_later(wp, VALID); + } +} |