aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/drawscreen.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/drawscreen.c')
-rw-r--r--src/nvim/drawscreen.c2325
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);
+ }
+}