aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/drawscreen.c
diff options
context:
space:
mode:
authorJosh Rahm <rahm@google.com>2022-08-19 12:26:08 -0600
committerJosh Rahm <rahm@google.com>2022-08-19 13:06:41 -0600
commita7237662f96933efe29eed8212464571e3778cd0 (patch)
tree27930202726b4251437c8cfa53069f65b4db90dc /src/nvim/drawscreen.c
parent02292344929069ea63c0bb872cc22d552d86b67f (diff)
parentb2f979b30beac67906b2dd717fcb6a34f46f5e54 (diff)
downloadrneovim-tmp.tar.gz
rneovim-tmp.tar.bz2
rneovim-tmp.zip
Merge branch 'master' of https://github.com/neovim/neovim into rahmtmp
Diffstat (limited to 'src/nvim/drawscreen.c')
-rw-r--r--src/nvim/drawscreen.c2373
1 files changed, 2373 insertions, 0 deletions
diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c
new file mode 100644
index 0000000000..75fe0565c2
--- /dev/null
+++ b/src/nvim/drawscreen.c
@@ -0,0 +1,2373 @@
+// 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;
+
+ char* title = wp->w_float_config.title;
+ size_t n_title = wp->w_float_config.n_title;
+ stl_hlrec_t* title_hl = wp->w_float_config.title_hl;
+
+ int m8[MAX_MCO + 1];
+ int cc;
+ int len;
+ int t_attr = title_hl != NULL && title_hl->userhl
+ ? syn_id2attr(title_hl->userhl)
+ : 0;
+ t_attr = hl_combine_attr(attrs[1], t_attr);
+
+ int title_pos = 2;
+ switch (wp->w_float_config.title_pos) {
+ case kTitleLeft:
+ title_pos = 2;
+ break;
+ case kTitleRight:
+ title_pos = icol - 2 - vim_strsize(title);
+ break;
+ case kTitleCenter:
+ title_pos = (icol - vim_strsize(title)) / 2 - 1;
+ break;
+ }
+ title_pos = title_pos < 2 ? 2 : title_pos;
+
+ 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++) {
+ schar_T ch;
+ int attr;
+ // Draw the title if in the correct position.
+ if (i > title_pos && n_title > 0 && i < icol - 2) {
+ cc = utfc_ptr2char((char_u*) title, m8);
+ len = utfc_ptr2len(title);
+ n_title -= len;
+ title += len;
+
+ while (title_hl != NULL &&
+ (title_hl + 1)->start != NULL &&
+ (title_hl + 1)->start < title) {
+ ++ title_hl;
+ t_attr = hl_combine_attr(attrs[1], syn_id2attr(-title_hl->userhl));
+ }
+
+ schar_from_cc(ch, cc, m8);
+ attr = t_attr;
+ } else {
+ memcpy(ch, chars[1], sizeof(schar_T));
+ attr = attrs[1];
+ }
+ grid_put_schar(grid, 0, i + adj[3], ch, attr);
+ }
+ 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;
+ }
+ }
+ }
+