mirror of
https://github.com/neovim/neovim.git
synced 2025-10-16 23:06:14 +00:00

Problem: 'fillchars' cannot have window-local values. Solution: Make 'fillchars' global-local. (closes vim/vim#5206)96ba25ac01
Cherry-pick g:run_nr from patch 8.2.0454. N/A patches for version.c: vim-patch:9.0.0037: build error Problem: Build error. Solution: Add missing change.510f03738d
2169 lines
71 KiB
C
2169 lines
71 KiB
C
// 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, UPD_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, UPD_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(UPD_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, UPD_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 UPD_NOT_VALID then.
|
|
//
|
|
// Commands that move the window position must call redraw_later(wp, UPD_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(UPD_NOT_VALID) or redraw_all_later(UPD_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/cmdexpand.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/statusline.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 = UPD_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(UPD_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 = UPD_CLEAR;
|
|
}
|
|
}
|
|
if (must_redraw == UPD_CLEAR) {
|
|
must_redraw = UPD_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(UPD_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(UPD_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 UPD_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 < UPD_NOT_VALID) {
|
|
type = UPD_NOT_VALID;
|
|
}
|
|
|
|
// Postpone the redrawing when it's not needed and when being called
|
|
// recursively.
|
|
if (!redrawing() || updating_screen) {
|
|
must_redraw = type;
|
|
if (type > UPD_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 >= UPD_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;
|
|
// UPD_CLEAR is already handled
|
|
if (type == UPD_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, UPD_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 = UPD_CLEAR;
|
|
} else if (type != UPD_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 < UPD_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 = UPD_REDRAW_TOP;
|
|
} else {
|
|
wp->w_redr_type = UPD_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 == UPD_CLEAR) { // first clear screen
|
|
screenclear(); // will reset clear_cmdline
|
|
cmdline_screen_cleared(); // clear external cmdline state
|
|
type = UPD_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 == UPD_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 < UPD_NOT_VALID
|
|
&& curwin->w_nrwidth != ((curwin->w_p_nu || curwin->w_p_rnu)
|
|
? number_width(curwin) : 0)) {
|
|
curwin->w_redr_type = UPD_NOT_VALID;
|
|
}
|
|
|
|
// Only start redrawing if there is really something to do.
|
|
if (type == UPD_INVERTED) {
|
|
update_curswant();
|
|
}
|
|
if (curwin->w_redr_type < type
|
|
&& !((type == UPD_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 == UPD_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 >= UPD_NOT_VALID) {
|
|
update_window_hl(curwin, type >= UPD_NOT_VALID);
|
|
FOR_ALL_TABS(tp) {
|
|
if (tp != curtab) {
|
|
update_window_hl(tp->tp_curwin, type >= UPD_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 >= UPD_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 == UPD_CLEAR && wp->w_floating && wp->w_grid_alloc.chars) {
|
|
grid_invalidate(&wp->w_grid_alloc);
|
|
wp->w_redr_type = UPD_NOT_VALID;
|
|
}
|
|
|
|
win_check_ns_hl(wp);
|
|
|
|
// reallocate grid if needed.
|
|
win_grid_alloc(wp);
|
|
|
|
if (wp->w_redr_border || wp->w_redr_type >= UPD_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);
|
|
}
|
|
}
|
|
|
|
/// Show current cursor info in ruler and various other places
|
|
///
|
|
/// @param always if false, only show ruler if position has changed.
|
|
void show_cursor_info(bool always)
|
|
{
|
|
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.
|
|
/// UPD_NOT_VALID redraw the whole window
|
|
/// UPD_SOME_VALID redraw the whole window but do scroll when possible
|
|
/// UPD_REDRAW_TOP redraw the top w_upd_rows window lines, otherwise like UPD_VALID
|
|
/// UPD_INVERTED redraw the changed part of the Visual area
|
|
/// UPD_INVERTED_ALL redraw the whole Visual area
|
|
/// UPD_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 >= UPD_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 = UPD_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 = UPD_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 == UPD_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 = UPD_NOT_VALID;
|
|
} else {
|
|
// top area defined, the rest is UPD_VALID
|
|
type = UPD_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 == UPD_VALID || type == UPD_SOME_VALID
|
|
|| type == UPD_INVERTED || type == UPD_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 UPD_VALID or UPD_INVERTED: redraw all lines.
|
|
mid_start = 0;
|
|
mid_end = wp->w_grid.rows;
|
|
}
|
|
|
|
if (type == UPD_SOME_VALID) {
|
|
// UPD_SOME_VALID: redraw all lines.
|
|
mid_start = 0;
|
|
mid_end = wp->w_grid.rows;
|
|
type = UPD_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 != UPD_NOT_VALID)) {
|
|
linenr_T from, to;
|
|
|
|
if (VIsual_active) {
|
|
if (VIsual_mode != wp->w_old_visual_mode || type == UPD_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 from 'fillchars' 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 >= UPD_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 UPD_CLEAR, type UPD_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 >= UPD_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, UPD_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, UPD_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 < UPD_VALID) {
|
|
must_redraw = UPD_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, UPD_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, UPD_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();
|
|
}
|
|
}
|
|
|
|
/// Redraw all status lines at the bottom of frame "frp".
|
|
void win_redraw_last_status(const frame_T *frp)
|
|
FUNC_ATTR_NONNULL_ARG(1)
|
|
{
|
|
if (frp->fr_layout == FR_LEAF) {
|
|
frp->fr_win->w_redr_status = true;
|
|
} else if (frp->fr_layout == FR_ROW) {
|
|
FOR_ALL_FRAMES(frp, frp->fr_child) {
|
|
win_redraw_last_status(frp);
|
|
}
|
|
} else {
|
|
assert(frp->fr_layout == FR_COL);
|
|
frp = frp->fr_child;
|
|
while (frp->fr_next != NULL) {
|
|
frp = frp->fr_next;
|
|
}
|
|
win_redraw_last_status(frp);
|
|
}
|
|
}
|
|
|
|
/// 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, UPD_VALID);
|
|
}
|
|
}
|