screen: use dedicated message grid

add proper msg_set_pos event, delet win_scroll_over_*

make compositor click through unfocusable grids

add MsgArea attribute for the message/cmdline area, and add docs and tests
This commit is contained in:
Björn Linse
2019-07-02 15:53:43 +02:00
parent 9cec097ffa
commit b51ba122c1
22 changed files with 528 additions and 180 deletions

View File

@@ -4969,6 +4969,8 @@ MatchParen The character under the cursor or just before it, if it
*hl-ModeMsg* *hl-ModeMsg*
ModeMsg 'showmode' message (e.g., "-- INSERT --") ModeMsg 'showmode' message (e.g., "-- INSERT --")
*hl-MsgArea*
MsgArea Area for messages and cmdline
*hl-MsgSeparator* *hl-MsgSeparator*
MsgSeparator Separator for scrolled messages, `msgsep` flag of 'display' MsgSeparator Separator for scrolled messages, `msgsep` flag of 'display'
*hl-MoreMsg* *hl-MoreMsg*

View File

@@ -543,6 +543,8 @@ The multigrid extension gives UIs more control over how windows are displayed:
occupies on the global layout. So the UI could use a different font size occupies on the global layout. So the UI could use a different font size
per-window. Or reserve space around the border of the window for its own per-window. Or reserve space around the border of the window for its own
elements, such as scrollbars from the UI toolkit. elements, such as scrollbars from the UI toolkit.
- A dedicated grid is used for messages, which may scroll over the window
area. (Alternatively |ext_messages| can be used).
By default, the grid size is handled by Nvim and set to the outer grid size By default, the grid size is handled by Nvim and set to the outer grid size
(i.e. the size of the window frame in Nvim) whenever the split is created. (i.e. the size of the window frame in Nvim) whenever the split is created.
@@ -573,19 +575,20 @@ tabs.
["win_hide", grid] ["win_hide", grid]
Stop displaying the window. The window can be shown again later. Stop displaying the window. The window can be shown again later.
["win_scroll_over_start"]
Hint that following `grid_scroll` on the default grid should
scroll over windows. This is a temporary workaround to allow
UIs to use the builtin message drawing. Later on, messages will be
drawn on a dedicated grid. Using |ui-messages| also avoids this issue.
["win_scroll_over_reset"]
Hint that scrolled over windows should be redrawn again, and not be
overdrawn by default grid scrolling anymore.
["win_close", grid] ["win_close", grid]
Close the window. Close the window.
["msg_set_pos", grid, row, scrolled, sep_char]
Display messages on `grid`. The grid will be displayed at `row` on the
default grid (grid=1), covering the full column width. `scrolled`
indicates whether the message area has been scrolled to cover other
grids. It can be useful to draw a separator then ('display' msgsep
flag). The Builtin TUI draws a full line filled with `sep_char` and
|hl-MsgSeparator| highlight.
When |ext_messages| is active, no message grid is used, and this event
will not be sent.
============================================================================== ==============================================================================
Popupmenu Events *ui-popupmenu* Popupmenu Events *ui-popupmenu*

View File

@@ -133,8 +133,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
ui->set_title = remote_ui_set_title; ui->set_title = remote_ui_set_title;
ui->set_icon = remote_ui_set_icon; ui->set_icon = remote_ui_set_icon;
ui->option_set = remote_ui_option_set; ui->option_set = remote_ui_option_set;
ui->win_scroll_over_start = remote_ui_win_scroll_over_start; ui->msg_set_pos = remote_ui_msg_set_pos;
ui->win_scroll_over_reset = remote_ui_win_scroll_over_reset;
ui->event = remote_ui_event; ui->event = remote_ui_event;
ui->inspect = remote_ui_inspect; ui->inspect = remote_ui_inspect;

View File

@@ -112,9 +112,7 @@ void win_hide(Integer grid)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void win_close(Integer grid) void win_close(Integer grid)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void win_scroll_over_start(void) void msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char)
FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL;
void win_scroll_over_reset(void)
FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL; FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL;
void popupmenu_show(Array items, Integer selected, void popupmenu_show(Array items, Integer selected,

View File

@@ -2656,7 +2656,6 @@ void buflist_list(exarg_T *eap)
msg_outtrans(IObuff); msg_outtrans(IObuff);
line_breakcheck(); line_breakcheck();
} }
ui_flush();
} }
/* /*

View File

@@ -14648,6 +14648,21 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
} }
} }
static void screenchar_adjust_grid(ScreenGrid **grid, int *row, int *col)
{
// TODO(bfredl): this is a hack for legacy tests which use screenchar()
// to check printed messages on the screen (but not floats etc
// as these are not legacy features). If the compositor is refactored to
// have its own buffer, this should just read from it instead.
msg_scroll_flush();
if (msg_grid.chars && msg_grid.comp_index > 0 && *row >= msg_grid.comp_row
&& *row < (msg_grid.Rows + msg_grid.comp_row)
&& *col < msg_grid.Columns) {
*grid = &msg_grid;
*row -= msg_grid.comp_row;
}
}
/* /*
* "screenattr()" function * "screenattr()" function
*/ */
@@ -14655,13 +14670,15 @@ static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{ {
int c; int c;
const int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1; int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
const int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1; int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
if (row < 0 || row >= default_grid.Rows if (row < 0 || row >= default_grid.Rows
|| col < 0 || col >= default_grid.Columns) { || col < 0 || col >= default_grid.Columns) {
c = -1; c = -1;
} else { } else {
c = default_grid.attrs[default_grid.line_offset[row] + col]; ScreenGrid *grid = &default_grid;
screenchar_adjust_grid(&grid, &row, &col);
c = grid->attrs[grid->line_offset[row] + col];
} }
rettv->vval.v_number = c; rettv->vval.v_number = c;
} }
@@ -14671,17 +14688,17 @@ static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
*/ */
static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{ {
int off;
int c; int c;
const int row = tv_get_number_chk(&argvars[0], NULL) - 1; int row = tv_get_number_chk(&argvars[0], NULL) - 1;
const int col = tv_get_number_chk(&argvars[1], NULL) - 1; int col = tv_get_number_chk(&argvars[1], NULL) - 1;
if (row < 0 || row >= default_grid.Rows if (row < 0 || row >= default_grid.Rows
|| col < 0 || col >= default_grid.Columns) { || col < 0 || col >= default_grid.Columns) {
c = -1; c = -1;
} else { } else {
off = default_grid.line_offset[row] + col; ScreenGrid *grid = &default_grid;
c = utf_ptr2char(default_grid.chars[off]); screenchar_adjust_grid(&grid, &row, &col);
c = utf_ptr2char(grid->chars[grid->line_offset[row] + col]);
} }
rettv->vval.v_number = c; rettv->vval.v_number = c;
} }

View File

@@ -310,6 +310,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
cmdmsg_rl = false; cmdmsg_rl = false;
} }
msg_grid_validate();
redir_off = true; // don't redirect the typed command redir_off = true; // don't redirect the typed command
if (!cmd_silent) { if (!cmd_silent) {
gotocmdline(true); gotocmdline(true);
@@ -908,7 +910,7 @@ static int command_line_execute(VimState *state, int key)
if (!cmd_silent) { if (!cmd_silent) {
if (!ui_has(kUICmdline)) { if (!ui_has(kUICmdline)) {
ui_cursor_goto(msg_row, 0); cmd_cursor_goto(msg_row, 0);
} }
ui_flush(); ui_flush();
} }
@@ -2323,7 +2325,7 @@ redraw:
} }
} }
msg_clr_eos(); msg_clr_eos();
ui_cursor_goto(msg_row, msg_col); cmd_cursor_goto(msg_row, msg_col);
continue; continue;
} }
@@ -2391,7 +2393,7 @@ redraw:
line_ga.ga_len += len; line_ga.ga_len += len;
escaped = FALSE; escaped = FALSE;
ui_cursor_goto(msg_row, msg_col); cmd_cursor_goto(msg_row, msg_col);
pend = (char_u *)(line_ga.ga_data) + line_ga.ga_len; pend = (char_u *)(line_ga.ga_data) + line_ga.ga_len;
/* We are done when a NL is entered, but not when it comes after an /* We are done when a NL is entered, but not when it comes after an
@@ -3436,7 +3438,7 @@ void redrawcmd(void)
/* when 'incsearch' is set there may be no command line while redrawing */ /* when 'incsearch' is set there may be no command line while redrawing */
if (ccline.cmdbuff == NULL) { if (ccline.cmdbuff == NULL) {
ui_cursor_goto(cmdline_row, 0); cmd_cursor_goto(cmdline_row, 0);
msg_clr_eos(); msg_clr_eos();
return; return;
} }
@@ -3510,7 +3512,14 @@ static void cursorcmd(void)
} }
} }
ui_cursor_goto(msg_row, msg_col); cmd_cursor_goto(msg_row, msg_col);
}
static void cmd_cursor_goto(int row, int col)
{
ScreenGrid *grid = &msg_grid_adj;
screen_adjust_grid(&grid, &row, &col);
ui_grid_cursor_goto(grid->handle, row, col);
} }
void gotocmdline(int clr) void gotocmdline(int clr)
@@ -3519,13 +3528,15 @@ void gotocmdline(int clr)
return; return;
} }
msg_start(); msg_start();
if (cmdmsg_rl) if (cmdmsg_rl) {
msg_col = Columns - 1; msg_col = Columns - 1;
else } else {
msg_col = 0; /* always start in column 0 */ msg_col = 0; // always start in column 0
if (clr) /* clear the bottom line(s) */ }
msg_clr_eos(); /* will reset clear_cmdline */ if (clr) { // clear the bottom line(s)
ui_cursor_goto(cmdline_row, 0); msg_clr_eos(); // will reset clear_cmdline
}
cmd_cursor_goto(cmdline_row, 0);
} }
/* /*

View File

@@ -43,6 +43,10 @@ typedef struct {
unsigned *line_offset; unsigned *line_offset;
char_u *line_wraps; char_u *line_wraps;
// last column that was drawn (not cleared with the default background).
// only used when "throttled" is set. Not allocated by grid_alloc!
int *dirty_col;
// the size of the allocated grid. // the size of the allocated grid.
int Rows; int Rows;
int Columns; int Columns;
@@ -50,12 +54,17 @@ typedef struct {
// The state of the grid is valid. Otherwise it needs to be redrawn. // The state of the grid is valid. Otherwise it needs to be redrawn.
bool valid; bool valid;
// only draw internally and don't send updates yet to the compositor or
// external UI.
bool throttled;
// offsets for the grid relative to the global screen // offsets for the grid relative to the global screen
int row_offset; int row_offset;
int col_offset; int col_offset;
// whether the compositor should blend the grid with the background grid // whether the compositor should blend the grid with the background grid
bool blending; bool blending;
bool focusable;
// state owned by the compositor. // state owned by the compositor.
int comp_row; int comp_row;
@@ -64,7 +73,7 @@ typedef struct {
bool comp_disabled; bool comp_disabled;
} ScreenGrid; } ScreenGrid;
#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, 0, 0, false, 0, 0, \ #define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, NULL, 0, 0, false, \
false, 0, 0, 0, false } false, 0, 0, false, true, 0, 0, 0, false }
#endif // NVIM_GRID_DEFS_H #endif // NVIM_GRID_DEFS_H

View File

@@ -7,6 +7,7 @@
#include "nvim/highlight.h" #include "nvim/highlight.h"
#include "nvim/highlight_defs.h" #include "nvim/highlight_defs.h"
#include "nvim/map.h" #include "nvim/map.h"
#include "nvim/message.h"
#include "nvim/popupmnu.h" #include "nvim/popupmnu.h"
#include "nvim/screen.h" #include "nvim/screen.h"
#include "nvim/syntax.h" #include "nvim/syntax.h"
@@ -161,6 +162,8 @@ int hl_get_ui_attr(int idx, int final_id, bool optional)
if (pum_drawn()) { if (pum_drawn()) {
must_redraw_pum = true; must_redraw_pum = true;
} }
} else if (idx == HLF_MSG) {
msg_grid.blending = attrs.hl_blend > -1;
} }
if (optional && !available) { if (optional && !available) {

View File

@@ -93,6 +93,7 @@ typedef enum {
, HLF_INACTIVE // NormalNC: Normal text in non-current windows , HLF_INACTIVE // NormalNC: Normal text in non-current windows
, HLF_MSGSEP // message separator line , HLF_MSGSEP // message separator line
, HLF_NFLOAT // Floating window , HLF_NFLOAT // Floating window
, HLF_MSG // Message area
, HLF_COUNT // MUST be the last one , HLF_COUNT // MUST be the last one
} hlf_T; } hlf_T;
@@ -146,6 +147,7 @@ EXTERN const char *hlf_names[] INIT(= {
[HLF_INACTIVE] = "NormalNC", [HLF_INACTIVE] = "NormalNC",
[HLF_MSGSEP] = "MsgSeparator", [HLF_MSGSEP] = "MsgSeparator",
[HLF_NFLOAT] = "NormalFloat", [HLF_NFLOAT] = "NormalFloat",
[HLF_MSG] = "MsgArea",
}); });

View File

@@ -3522,11 +3522,9 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname,
} }
xfree(name); xfree(name);
// pretend screen didn't scroll, need redraw anyway // pretend screen didn't scroll, need redraw anyway
// TODO(bfredl): when doing the message grid refactor, msg_reset_scroll();
// simplify this special case.
msg_scrolled = 0;
redraw_all_later(NOT_VALID);
} }
if (choice > 0) { if (choice > 0) {

View File

@@ -35,7 +35,9 @@
#include "nvim/screen.h" #include "nvim/screen.h"
#include "nvim/strings.h" #include "nvim/strings.h"
#include "nvim/syntax.h" #include "nvim/syntax.h"
#include "nvim/highlight.h"
#include "nvim/ui.h" #include "nvim/ui.h"
#include "nvim/ui_compositor.h"
#include "nvim/mouse.h" #include "nvim/mouse.h"
#include "nvim/os/os.h" #include "nvim/os/os.h"
#include "nvim/os/input.h" #include "nvim/os/input.h"
@@ -124,6 +126,75 @@ static int msg_ext_visible = 0; ///< number of messages currently visible
/// Shouldn't clear message after leaving cmdline /// Shouldn't clear message after leaving cmdline
static bool msg_ext_keep_after_cmdline = false; static bool msg_ext_keep_after_cmdline = false;
static int msg_grid_pos_at_flush = 0;
static int msg_grid_scroll_discount = 0;
static void ui_ext_msg_set_pos(int row, bool scrolled)
{
char buf[MAX_MCO];
size_t size = utf_char2bytes(curwin->w_p_fcs_chars.msgsep, (char_u *)buf);
buf[size] = '\0';
ui_call_msg_set_pos(msg_grid.handle, row, scrolled,
(String){ .data = buf, .size = size });
}
void msg_grid_set_pos(int row, bool scrolled)
{
if (!msg_grid.throttled) {
ui_ext_msg_set_pos(row, scrolled);
msg_grid_pos_at_flush = row;
}
msg_grid_pos = row;
if (msg_grid.chars) {
msg_grid_adj.row_offset = -row;
}
}
void msg_grid_validate(void)
{
grid_assign_handle(&msg_grid);
bool should_alloc = msg_dothrottle();
if (msg_grid.Rows != Rows || msg_grid.Columns != Columns
|| (should_alloc && !msg_grid.chars)) {
// TODO(bfredl): eventually should be set to "invalid". I e all callers
// will use the grid including clear to EOS if necessary.
grid_alloc(&msg_grid, Rows, Columns, false, true);
xfree(msg_grid.dirty_col);
msg_grid.dirty_col = xcalloc(Rows, sizeof(*msg_grid.dirty_col));
// Tricky: allow resize while pager is active
int pos = msg_scrolled ? msg_grid_pos : Rows - p_ch;
ui_comp_put_grid(&msg_grid, pos, 0, msg_grid.Rows, msg_grid.Columns,
false, true);
ui_call_grid_resize(msg_grid.handle, msg_grid.Columns, msg_grid.Rows);
msg_grid.throttled = false; // don't throttle in 'cmdheight' area
msg_scroll_at_flush = msg_scrolled;
msg_grid.focusable = false;
if (!msg_scrolled) {
msg_grid_set_pos(Rows - p_ch, false);
}
} else if (!should_alloc && msg_grid.chars) {
ui_comp_remove_grid(&msg_grid);
grid_free(&msg_grid);
XFREE_CLEAR(msg_grid.dirty_col);
ui_call_grid_destroy(msg_grid.handle);
msg_grid.throttled = false;
msg_grid_adj.row_offset = 0;
redraw_cmdline = true;
} else if (msg_grid.chars && !msg_scrolled && msg_grid_pos != Rows - p_ch) {
msg_grid_set_pos(Rows - p_ch, false);
}
if (msg_grid.chars && cmdline_row < msg_grid_pos) {
// TODO(bfredl): this should already be the case, but fails in some
// "batched" executions where compute_cmdrow() use stale positions or
// something.
cmdline_row = msg_grid_pos;
}
}
/* /*
* msg(s) - displays the string 's' on the status line * msg(s) - displays the string 's' on the status line
* When terminal not initialized (yet) mch_errmsg(..) is used. * When terminal not initialized (yet) mch_errmsg(..) is used.
@@ -1701,6 +1772,7 @@ void msg_prt_line(char_u *s, int list)
static char_u *screen_puts_mbyte(char_u *s, int l, int attr) static char_u *screen_puts_mbyte(char_u *s, int l, int attr)
{ {
int cw; int cw;
attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr);
msg_didout = true; // remember that line is not empty msg_didout = true; // remember that line is not empty
cw = utf_ptr2cells(s); cw = utf_ptr2cells(s);
@@ -1711,7 +1783,7 @@ static char_u *screen_puts_mbyte(char_u *s, int l, int attr)
return s; return s;
} }
grid_puts_len(&default_grid, s, l, msg_row, msg_col, attr); grid_puts_len(&msg_grid_adj, s, l, msg_row, msg_col, attr);
if (cmdmsg_rl) { if (cmdmsg_rl) {
msg_col -= cw; msg_col -= cw;
if (msg_col == 0) { if (msg_col == 0) {
@@ -1900,6 +1972,8 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr,
return; return;
} }
msg_grid_validate();
cmdline_was_last_drawn = redrawing_cmdline; cmdline_was_last_drawn = redrawing_cmdline;
while ((maxlen < 0 || (int)(s - str) < maxlen) && *s != NUL) { while ((maxlen < 0 || (int)(s - str) < maxlen) && *s != NUL) {
@@ -1929,15 +2003,16 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr,
if (msg_no_more && lines_left == 0) if (msg_no_more && lines_left == 0)
break; break;
/* Scroll the screen up one line. */ // Scroll the screen up one line.
msg_scroll_up(); bool has_last_char = (*s >= ' ' && !cmdmsg_rl);
msg_scroll_up(!has_last_char);
msg_row = Rows - 2; msg_row = Rows - 2;
if (msg_col >= Columns) /* can happen after screen resize */ if (msg_col >= Columns) /* can happen after screen resize */
msg_col = Columns - 1; msg_col = Columns - 1;
// Display char in last column before showing more-prompt. // Display char in last column before showing more-prompt.
if (*s >= ' ' && !cmdmsg_rl) { if (has_last_char) {
if (maxlen >= 0) { if (maxlen >= 0) {
// Avoid including composing chars after the end. // Avoid including composing chars after the end.
l = utfc_ptr2len_len(s, (int)((str + maxlen) - s)); l = utfc_ptr2len_len(s, (int)((str + maxlen) - s));
@@ -1950,6 +2025,15 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr,
did_last_char = false; did_last_char = false;
} }
// Tricky: if last cell will be written, delay the throttle until
// after the first scroll. Otherwise we would need to keep track of it.
if (has_last_char && msg_dothrottle()) {
if (!msg_grid.throttled) {
msg_grid_scroll_discount++;
}
msg_grid.throttled = true;
}
if (p_more) { if (p_more) {
// Store text for scrolling back. // Store text for scrolling back.
store_sb_text((char_u **)&sb_str, (char_u *)s, attr, &sb_col, true); store_sb_text((char_u **)&sb_str, (char_u *)s, attr, &sb_col, true);
@@ -2074,29 +2158,106 @@ int msg_scrollsize(void)
return msg_scrolled + p_ch + 1; return msg_scrolled + p_ch + 1;
} }
bool msg_dothrottle(void)
{
return default_grid.chars && msg_use_msgsep()
&& !ui_has(kUIMessages);
}
bool msg_use_msgsep(void)
{
// the full-screen scroll behavior doesn't really make sense with
// 'ext_multigrid'
return ((dy_flags & DY_MSGSEP) || ui_has(kUIMultigrid));
}
/* /*
* Scroll the screen up one line for displaying the next message line. * Scroll the screen up one line for displaying the next message line.
*/ */
void msg_scroll_up(void) void msg_scroll_up(bool may_throttle)
{ {
if (!msg_did_scroll) { if (may_throttle && msg_dothrottle()) {
ui_call_win_scroll_over_start(); msg_grid.throttled = true;
msg_did_scroll = true;
} }
if (dy_flags & DY_MSGSEP) { msg_did_scroll = true;
if (msg_scrolled == 0) { if (msg_use_msgsep()) {
grid_fill(&default_grid, Rows-p_ch-1, Rows-p_ch, 0, (int)Columns, if (msg_grid_pos > 0) {
curwin->w_p_fcs_chars.msgsep, curwin->w_p_fcs_chars.msgsep, msg_grid_set_pos(msg_grid_pos-1, true);
HL_ATTR(HLF_MSGSEP)); } else {
grid_del_lines(&msg_grid, 0, 1, msg_grid.Rows, 0, msg_grid.Columns);
memmove(msg_grid.dirty_col, msg_grid.dirty_col+1,
(msg_grid.Rows-1) * sizeof(*msg_grid.dirty_col));
msg_grid.dirty_col[msg_grid.Rows-1] = 0;
} }
int nscroll = MIN(msg_scrollsize()+1, Rows);
grid_del_lines(&default_grid, Rows-nscroll, 1, Rows, 0, Columns);
} else { } else {
grid_del_lines(&default_grid, 0, 1, (int)Rows, 0, Columns); grid_del_lines(&msg_grid_adj, 0, 1, Rows, 0, Columns);
} }
// TODO(bfredl): when msgsep display is properly batched, this fill should be
// eliminated. grid_fill(&msg_grid_adj, Rows-1, Rows, 0, Columns, ' ', ' ',
grid_fill(&default_grid, Rows-1, Rows, 0, (int)Columns, ' ', ' ', 0); HL_ATTR(HLF_MSG));
}
void msg_scroll_flush(void)
{
if (!msg_grid.throttled) {
return;
}
msg_grid.throttled = false;
int pos_delta = msg_grid_pos_at_flush - msg_grid_pos;
assert(pos_delta >= 0);
int delta = MIN(msg_scrolled - msg_scroll_at_flush, msg_grid.Rows);
if (pos_delta > 0) {
ui_ext_msg_set_pos(msg_grid_pos, true);
msg_grid_pos_at_flush = msg_grid_pos;
}
int to_scroll = delta-pos_delta-msg_grid_scroll_discount;
assert(to_scroll >= 0);
// TODO(bfredl): msg_grid_pos should be 0 already when starting scrolling
// but this sometimes fails in "headless" message printing.
if (to_scroll > 0 && msg_grid_pos == 0) {
ui_call_grid_scroll(msg_grid.handle, 0, Rows, 0, Columns, to_scroll, 0);
}
for (int i = MAX(Rows-MAX(delta, 1), 0); i < Rows; i++) {
int row = i-msg_grid_pos;
assert(row >= 0);
ui_line(&msg_grid, row, 0, msg_grid.dirty_col[row], msg_grid.Columns,
HL_ATTR(HLF_MSG), false);
msg_grid.dirty_col[row] = 0;
}
msg_scroll_at_flush = msg_scrolled;
msg_grid_scroll_discount = 0;
}
void msg_reset_scroll(void)
{
if (ui_has(kUIMessages)) {
msg_ext_clear(true);
return;
}
// TODO(bfredl): some duplicate logic with update_screen(). Later on
// we should properly disentangle message clear with full screen redraw.
if (msg_dothrottle()) {
msg_grid.throttled = false;
// TODO(bfredl): risk for extra flicker i e with
// "nvim -o has_swap also_has_swap"
msg_grid_set_pos(Rows - p_ch, false);
clear_cmdline = true;
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],
(int)msg_grid.Columns, false);
}
}
} else {
redraw_all_later(NOT_VALID);
}
msg_scrolled = 0;
msg_scroll_at_flush = 0;
} }
/* /*
@@ -2285,6 +2446,11 @@ static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp)
break; break;
mp = mp->sb_next; mp = mp->sb_next;
} }
if (msg_col < Columns) {
grid_fill(&msg_grid_adj, row, row+1, msg_col, Columns, ' ', ' ',
HL_ATTR(HLF_MSG));
}
return mp->sb_next; return mp->sb_next;
} }
@@ -2293,9 +2459,10 @@ static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp)
*/ */
static void t_puts(int *t_col, const char_u *t_s, const char_u *s, int attr) static void t_puts(int *t_col, const char_u *t_s, const char_u *s, int attr)
{ {
attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr);
// Output postponed text. // Output postponed text.
msg_didout = true; // Remember that line is not empty. msg_didout = true; // Remember that line is not empty.
grid_puts_len(&default_grid, (char_u *)t_s, (int)(s - t_s), msg_row, msg_col, grid_puts_len(&msg_grid_adj, (char_u *)t_s, (int)(s - t_s), msg_row, msg_col,
attr); attr);
msg_col += *t_col; msg_col += *t_col;
*t_col = 0; *t_col = 0;
@@ -2514,14 +2681,14 @@ static int do_more_prompt(int typed_char)
} }
if (toscroll == -1) { if (toscroll == -1) {
grid_ins_lines(&default_grid, 0, 1, (int)Rows, 0, (int)Columns); grid_ins_lines(&msg_grid_adj, 0, 1, Rows, 0, Columns);
grid_fill(&default_grid, 0, 1, 0, (int)Columns, ' ', ' ', 0);
// display line at top // display line at top
(void)disp_sb_line(0, mp); (void)disp_sb_line(0, mp);
} else { } else {
/* redisplay all lines */ // redisplay all lines
screenclear(); // TODO(bfredl): this case is not optimized (though only concerns
for (i = 0; mp != NULL && i < Rows - 1; ++i) { // event fragmentization, not unnecessary scroll events).
for (i = 0; mp != NULL && i < Rows - 1; i++) {
mp = disp_sb_line(i, mp); mp = disp_sb_line(i, mp);
++msg_scrolled; ++msg_scrolled;
} }
@@ -2531,20 +2698,24 @@ static int do_more_prompt(int typed_char)
} else { } else {
/* First display any text that we scrolled back. */ /* First display any text that we scrolled back. */
while (toscroll > 0 && mp_last != NULL) { while (toscroll > 0 && mp_last != NULL) {
/* scroll up, display line at bottom */ if (msg_dothrottle() && !msg_grid.throttled) {
msg_scroll_up(); // Tricky: we redraw at one line higher than usual. Therefore
// the non-flushed area is one line larger.
msg_scroll_at_flush--;
msg_grid_scroll_discount++;
}
// scroll up, display line at bottom
msg_scroll_up(true);
inc_msg_scrolled(); inc_msg_scrolled();
grid_fill(&default_grid, (int)Rows - 2, (int)Rows - 1, 0, mp_last = disp_sb_line(Rows - 2, mp_last);
(int)Columns, ' ', ' ', 0); toscroll--;
mp_last = disp_sb_line((int)Rows - 2, mp_last);
--toscroll;
} }
} }
if (toscroll <= 0) { if (toscroll <= 0) {
// displayed the requested text, more prompt again // displayed the requested text, more prompt again
grid_fill(&default_grid, (int)Rows - 1, (int)Rows, 0, grid_fill(&msg_grid_adj, Rows - 1, Rows, 0, Columns, ' ', ' ',
(int)Columns, ' ', ' ', 0); HL_ATTR(HLF_MSG));
msg_moremsg(false); msg_moremsg(false);
continue; continue;
} }
@@ -2557,8 +2728,11 @@ static int do_more_prompt(int typed_char)
} }
// clear the --more-- message // clear the --more-- message
grid_fill(&default_grid, (int)Rows - 1, (int)Rows, 0, (int)Columns, ' ', ' ', grid_fill(&msg_grid_adj, Rows - 1, Rows, 0, Columns, ' ', ' ', 0);
0); redraw_cmdline = true;
clear_cmdline = false;
mode_displayed = false;
State = oldState; State = oldState;
setmouse(); setmouse();
if (quit_more) { if (quit_more) {
@@ -2607,8 +2781,9 @@ void mch_msg(char *str)
*/ */
static void msg_screen_putchar(int c, int attr) static void msg_screen_putchar(int c, int attr)
{ {
attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr);
msg_didout = true; // remember that line is not empty msg_didout = true; // remember that line is not empty
grid_putchar(&default_grid, c, msg_row, msg_col, attr); grid_putchar(&msg_grid_adj, c, msg_row, msg_col, attr);
if (cmdmsg_rl) { if (cmdmsg_rl) {
if (--msg_col == 0) { if (--msg_col == 0) {
msg_col = Columns; msg_col = Columns;
@@ -2628,11 +2803,11 @@ void msg_moremsg(int full)
char_u *s = (char_u *)_("-- More --"); char_u *s = (char_u *)_("-- More --");
attr = HL_ATTR(HLF_M); attr = HL_ATTR(HLF_M);
grid_puts(&default_grid, s, (int)Rows - 1, 0, attr); grid_puts(&msg_grid_adj, s, Rows - 1, 0, attr);
if (full) { if (full) {
grid_puts(&default_grid, (char_u *) grid_puts(&msg_grid_adj, (char_u *)
_(" SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "), _(" SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "),
(int)Rows - 1, vim_strsize(s), attr); Rows - 1, vim_strsize(s), attr);
} }
} }
@@ -2685,12 +2860,24 @@ void msg_clr_eos_force(void)
return; return;
} }
int msg_startcol = (cmdmsg_rl) ? 0 : msg_col; int msg_startcol = (cmdmsg_rl) ? 0 : msg_col;
int msg_endcol = (cmdmsg_rl) ? msg_col + 1 : (int)Columns; int msg_endcol = (cmdmsg_rl) ? msg_col + 1 : Columns;
grid_fill(&default_grid, msg_row, msg_row + 1, msg_startcol, msg_endcol, ' ', if (msg_grid.chars && msg_row < msg_grid_pos) {
' ', 0); // TODO(bfredl): ugly, this state should already been validated at this
grid_fill(&default_grid, msg_row + 1, (int)Rows, 0, (int)Columns, ' ', ' ', // point. But msg_clr_eos() is called in a lot of places.
0); msg_row = msg_grid_pos;
}
grid_fill(&msg_grid_adj, msg_row, msg_row + 1, msg_startcol, msg_endcol, ' ',
' ', HL_ATTR(HLF_MSG));
grid_fill(&msg_grid_adj, msg_row + 1, Rows, 0, Columns, ' ', ' ',
HL_ATTR(HLF_MSG));
redraw_cmdline = true; // overwritten the command line
if (msg_row < Rows-1 || msg_col == (cmdmsg_rl ? Columns : 0)) {
clear_cmdline = false; // command line has been cleared
mode_displayed = false; // mode cleared or overwritten
}
} }
/* /*
@@ -2724,7 +2911,8 @@ int msg_end(void)
// @TODO(bfredl): calling flush here inhibits substantial performance // @TODO(bfredl): calling flush here inhibits substantial performance
// improvements. Caller should call ui_flush before waiting on user input or // improvements. Caller should call ui_flush before waiting on user input or
// CPU busywork. // CPU busywork.
ui_flush(); // calls msg_ext_ui_flush // ui_flush(); // calls msg_ext_ui_flush
msg_ext_ui_flush();
return true; return true;
} }

View File

@@ -7,6 +7,7 @@
#include "nvim/macros.h" #include "nvim/macros.h"
#include "nvim/types.h" #include "nvim/types.h"
#include "nvim/grid_defs.h"
/* /*
* Types of dialogs passed to do_dialog(). * Types of dialogs passed to do_dialog().
@@ -90,6 +91,13 @@ extern MessageHistoryEntry *last_msg_hist;
EXTERN bool msg_ext_need_clear INIT(= false); EXTERN bool msg_ext_need_clear INIT(= false);
EXTERN ScreenGrid msg_grid INIT(= SCREEN_GRID_INIT);
EXTERN ScreenGrid msg_grid_adj INIT(= SCREEN_GRID_INIT);
EXTERN int msg_scroll_at_flush INIT(= 0);
EXTERN int msg_grid_pos INIT(= 0);
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "message.h.generated.h" # include "message.h.generated.h"
#endif #endif

View File

@@ -440,8 +440,11 @@ win_T *mouse_find_win(int *gridp, int *rowp, int *colp)
win_T *wp_grid = mouse_find_grid_win(gridp, rowp, colp); win_T *wp_grid = mouse_find_grid_win(gridp, rowp, colp);
if (wp_grid) { if (wp_grid) {
return wp_grid; return wp_grid;
} else if (*gridp > 1) {
return NULL;
} }
frame_T *fp; frame_T *fp;
fp = topframe; fp = topframe;
@@ -475,7 +478,10 @@ win_T *mouse_find_win(int *gridp, int *rowp, int *colp)
static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp) static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp)
{ {
if (*gridp > 1) { if (*gridp == msg_grid.handle) {
rowp += msg_grid_pos;
*gridp = DEFAULT_GRID_HANDLE;
} else if (*gridp > 1) {
win_T *wp = get_win_by_grid_handle(*gridp); win_T *wp = get_win_by_grid_handle(*gridp);
if (wp && wp->w_grid.chars if (wp && wp->w_grid.chars
&& !(wp->w_floating && !wp->w_float_config.focusable)) { && !(wp->w_floating && !wp->w_float_config.focusable)) {
@@ -486,7 +492,7 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp)
} else if (*gridp == 0) { } else if (*gridp == 0) {
ScreenGrid *grid = ui_comp_mouse_focus(*rowp, *colp); ScreenGrid *grid = ui_comp_mouse_focus(*rowp, *colp);
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (&wp->w_grid != grid || !wp->w_float_config.focusable) { if (&wp->w_grid != grid) {
continue; continue;
} }
*gridp = grid->handle; *gridp = grid->handle;

View File

@@ -3455,16 +3455,18 @@ static void display_showcmd(void)
return; return;
} }
msg_grid_validate();
int showcmd_row = Rows - 1; int showcmd_row = Rows - 1;
grid_puts_line_start(&default_grid, showcmd_row); grid_puts_line_start(&msg_grid_adj, showcmd_row);
if (!showcmd_is_clear) { if (!showcmd_is_clear) {
grid_puts(&default_grid, showcmd_buf, showcmd_row, sc_col, 0); grid_puts(&msg_grid_adj, showcmd_buf, showcmd_row, sc_col,
HL_ATTR(HLF_MSG));
} }
// clear the rest of an old message by outputting up to SHOWCMD_COLS spaces // clear the rest of an old message by outputting up to SHOWCMD_COLS spaces
grid_puts(&default_grid, (char_u *)" " + len, showcmd_row, grid_puts(&msg_grid_adj, (char_u *)" " + len, showcmd_row,
sc_col + len, 0); sc_col + len, HL_ATTR(HLF_MSG));
grid_puts_line_flush(false); grid_puts_line_flush(false);
} }

View File

@@ -2989,6 +2989,7 @@ ambw_end:
errmsg = e_invarg; errmsg = e_invarg;
} else { } else {
(void)init_chartab(); (void)init_chartab();
msg_grid_validate();
} }
} else if (varp == &p_ead) { // 'eadirection' } else if (varp == &p_ead) { // 'eadirection'
if (check_opt_strings(p_ead, p_ead_values, false) != OK) { if (check_opt_strings(p_ead, p_ead_values, false) != OK) {

View File

@@ -399,6 +399,7 @@ static char *(p_dy_values[]) = { "lastline", "truncate", "uhex", "msgsep",
#define DY_LASTLINE 0x001 #define DY_LASTLINE 0x001
#define DY_TRUNCATE 0x002 #define DY_TRUNCATE 0x002
#define DY_UHEX 0x004 #define DY_UHEX 0x004
// code should use msg_use_msgsep() to check if msgsep is active
#define DY_MSGSEP 0x008 #define DY_MSGSEP 0x008
EXTERN int p_ed; // 'edcompatible' EXTERN int p_ed; // 'edcompatible'
EXTERN int p_emoji; // 'emoji' EXTERN int p_emoji; // 'emoji'

View File

@@ -152,6 +152,7 @@ static bool send_grid_resize = false;
static bool conceal_cursor_used = false; static bool conceal_cursor_used = false;
static bool redraw_popupmenu = false; static bool redraw_popupmenu = false;
static bool msg_grid_invalid = false;
static bool resizing = false; static bool resizing = false;
@@ -318,27 +319,37 @@ int update_screen(int type)
// Tricky: vim code can reset msg_scrolled behind our back, so need // Tricky: vim code can reset msg_scrolled behind our back, so need
// separate bookkeeping for now. // separate bookkeeping for now.
if (msg_did_scroll) { if (msg_did_scroll) {
ui_call_win_scroll_over_reset();
msg_did_scroll = false; msg_did_scroll = false;
msg_scroll_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 the screen was scrolled up when displaying a message, scroll it down
if (msg_scrolled) { if (msg_scrolled || msg_grid_invalid) {
clear_cmdline = true; clear_cmdline = true;
if (dy_flags & DY_MSGSEP) { int valid = MAX(Rows - msg_scrollsize(), 0);
int valid = MAX(Rows - msg_scrollsize(), 0); if (msg_grid.chars) {
if (valid == 0) { // non-displayed part of msg_grid is considered invalid.
redraw_tabline = true; for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.Rows); i++) {
grid_clear_line(&msg_grid, msg_grid.line_offset[i],
(int)msg_grid.Columns, false);
} }
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { }
if (W_ENDROW(wp) > valid) { if (msg_use_msgsep()) {
wp->w_redr_type = NOT_VALID; msg_grid.throttled = false;
wp->w_lines_valid = 0; // CLEAR is already handled
} if (type == NOT_VALID && !ui_has(kUIMultigrid) && msg_scrolled) {
if (W_ENDROW(wp) + wp->w_status_height > valid) { ui_comp_set_screen_valid(false);
wp->w_redr_status = true; for (int i = valid; i < Rows-p_ch; i++) {
grid_clear_line(&default_grid, default_grid.line_offset[i],
Columns, false);
} }
} }
msg_grid_set_pos(Rows-p_ch, false);
msg_grid_invalid = false;
} else if (msg_scrolled > Rows - 5) { // clearing is faster } else if (msg_scrolled > Rows - 5) { // clearing is faster
type = CLEAR; type = CLEAR;
} else if (type != CLEAR) { } else if (type != CLEAR) {
@@ -368,12 +379,10 @@ int update_screen(int type)
redraw_tabline = TRUE; redraw_tabline = TRUE;
} }
msg_scrolled = 0; msg_scrolled = 0;
need_wait_return = FALSE; msg_scroll_at_flush = 0;
need_wait_return = false;
} }
if (type >= CLEAR || !default_grid.valid) {
ui_comp_set_screen_valid(false);
}
win_ui_flush_positions(); win_ui_flush_positions();
msg_ext_check_clear(); msg_ext_check_clear();
@@ -394,6 +403,11 @@ int update_screen(int type)
grid_invalidate(&default_grid); grid_invalidate(&default_grid);
default_grid.valid = true; default_grid.valid = true;
} }
if (type == NOT_VALID && msg_dothrottle()) {
grid_fill(&default_grid, Rows-p_ch, Rows, 0, Columns, ' ', ' ', 0);
}
ui_comp_set_screen_valid(true); ui_comp_set_screen_valid(true);
if (clear_cmdline) /* going to clear cmdline (done below) */ if (clear_cmdline) /* going to clear cmdline (done below) */
@@ -4310,9 +4324,13 @@ win_line (
void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off) void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off)
{ {
if (!(*grid)->chars && *grid != &default_grid) { if (!(*grid)->chars && *grid != &default_grid) {
*row_off += (*grid)->row_offset; *row_off += (*grid)->row_offset;
*col_off += (*grid)->col_offset; *col_off += (*grid)->col_offset;
*grid = &default_grid; if (*grid == &msg_grid_adj && msg_grid.chars) {
*grid = &msg_grid;
} else {
*grid = &default_grid;
}
} }
} }
@@ -4799,7 +4817,7 @@ win_redr_status_matches (
/* Put the wildmenu just above the command line. If there is /* Put the wildmenu just above the command line. If there is
* no room, scroll the screen one line up. */ * no room, scroll the screen one line up. */
if (cmdline_row == Rows - 1) { if (cmdline_row == Rows - 1) {
msg_scroll_up(); msg_scroll_up(false);
msg_scrolled++; msg_scrolled++;
} else { } else {
cmdline_row++; cmdline_row++;
@@ -4821,13 +4839,18 @@ win_redr_status_matches (
} }
} }
grid_puts(&default_grid, buf, row, 0, attr); // Tricky: wildmenu can be drawn either over a status line, or at empty
// scrolled space in the message output
ScreenGrid *grid = (wild_menu_showing == WM_SCROLLED)
? &msg_grid_adj : &default_grid;
grid_puts(grid, buf, row, 0, attr);
if (selstart != NULL && highlight) { if (selstart != NULL && highlight) {
*selend = NUL; *selend = NUL;
grid_puts(&default_grid, selstart, row, selstart_col, HL_ATTR(HLF_WM)); grid_puts(grid, selstart, row, selstart_col, HL_ATTR(HLF_WM));
} }
grid_fill(&default_grid, row, row + 1, clen, Columns, grid_fill(grid, row, row + 1, clen, Columns,
fillchar, fillchar, attr); fillchar, fillchar, attr);
} }
@@ -5350,6 +5373,8 @@ static int put_dirty_last = 0;
/// another line. /// another line.
void grid_puts_line_start(ScreenGrid *grid, int row) void grid_puts_line_start(ScreenGrid *grid, int row)
{ {
int col = 0; // unused
screen_adjust_grid(&grid, &row, &col);
assert(put_dirty_row == -1); assert(put_dirty_row == -1);
put_dirty_row = row; put_dirty_row = row;
put_dirty_grid = grid; put_dirty_grid = grid;
@@ -5379,7 +5404,7 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row,
screen_adjust_grid(&grid, &row, &col); screen_adjust_grid(&grid, &row, &col);
// Safety check. The check for negative row and column is to fix issue // Safety check. The check for negative row and column is to fix issue
// vim/vim#4102. TODO: find out why row/col could be negative. // vim/vim#4102. TODO(neovim): find out why row/col could be negative.
if (grid->chars == NULL if (grid->chars == NULL
|| row >= grid->Rows || row < 0 || row >= grid->Rows || row < 0
|| col >= grid->Columns || col < 0) { || col >= grid->Columns || col < 0) {
@@ -5511,8 +5536,14 @@ void grid_puts_line_flush(bool set_cursor)
ui_grid_cursor_goto(put_dirty_grid->handle, put_dirty_row, ui_grid_cursor_goto(put_dirty_grid->handle, put_dirty_row,
MIN(put_dirty_last, put_dirty_grid->Columns-1)); MIN(put_dirty_last, put_dirty_grid->Columns-1));
} }
ui_line(put_dirty_grid, put_dirty_row, put_dirty_first, put_dirty_last, if (!put_dirty_grid->throttled) {
put_dirty_last, 0, false); ui_line(put_dirty_grid, put_dirty_row, put_dirty_first, put_dirty_last,
put_dirty_last, 0, false);
} else if (put_dirty_grid->dirty_col) {
if (put_dirty_last > put_dirty_grid->dirty_col[put_dirty_row]) {
put_dirty_grid->dirty_col[put_dirty_row] = put_dirty_last;
}
}
put_dirty_first = INT_MAX; put_dirty_first = INT_MAX;
put_dirty_last = 0; put_dirty_last = 0;
} }
@@ -5886,6 +5917,18 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col,
if (put_dirty_row == row) { if (put_dirty_row == row) {
put_dirty_first = MIN(put_dirty_first, dirty_first); put_dirty_first = MIN(put_dirty_first, dirty_first);
put_dirty_last = MAX(put_dirty_last, dirty_last); put_dirty_last = MAX(put_dirty_last, dirty_last);
} else if (grid->throttled) {
// Note: assumes msg_grid is the only throttled grid
assert(grid == &msg_grid);
int dirty = 0;
if (attr != HL_ATTR(HLF_MSG) || c2 != ' ') {
dirty = dirty_last;
} else if (c1 != ' ') {
dirty = dirty_first + 1;
}
if (grid->dirty_col && dirty > grid->dirty_col[row]) {
grid->dirty_col[row] = dirty;
}
} else { } else {
int last = c2 != ' ' ? dirty_last : dirty_first + (c1 != ' '); int last = c2 != ' ' ? dirty_last : dirty_first + (c1 != ' ');
ui_line(grid, row, dirty_first, last, dirty_last, attr, false); ui_line(grid, row, dirty_first, last, dirty_last, attr, false);
@@ -5895,19 +5938,6 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col,
if (end_col == grid->Columns) { if (end_col == grid->Columns) {
grid->line_wraps[row] = false; grid->line_wraps[row] = false;
} }
// TODO(bfredl): The relevant caller should do this
if (row == Rows - 1 && !ui_has(kUIMessages)) {
// overwritten the command line
redraw_cmdline = true;
if (start_col == 0 && end_col == Columns
&& c1 == ' ' && c2 == ' ' && attr == 0) {
clear_cmdline = false; // command line has been cleared
}
if (start_col == 0) {
mode_displayed = false; // mode cleared or overwritten
}
}
} }
} }
@@ -6039,6 +6069,9 @@ retry:
// win_new_shellsize will recompute floats position, but tell the // win_new_shellsize will recompute floats position, but tell the
// compositor to not redraw them yet // compositor to not redraw them yet
ui_comp_set_screen_valid(false); ui_comp_set_screen_valid(false);
if (msg_grid.chars) {
msg_grid_invalid = true;
}
win_new_shellsize(); /* fit the windows in the new sized shell */ win_new_shellsize(); /* fit the windows in the new sized shell */
@@ -6217,12 +6250,17 @@ void screenclear(void)
msg_scrolled = 0; // can't scroll back msg_scrolled = 0; // can't scroll back
msg_didany = false; msg_didany = false;
msg_didout = false; msg_didout = false;
if (HL_ATTR(HLF_MSG) > 0 && msg_dothrottle() && msg_grid.chars) {
grid_invalidate(&msg_grid);
msg_grid_validate();
msg_grid_invalid = false;
clear_cmdline = true;
}
} }
/// clear a line in the grid starting at "off" until "width" characters /// clear a line in the grid starting at "off" until "width" characters
/// are cleared. /// are cleared.
static void grid_clear_line(ScreenGrid *grid, unsigned off, int width, void grid_clear_line(ScreenGrid *grid, unsigned off, int width, bool valid)
bool valid)
{ {
for (int col = 0; col < width; col++) { for (int col = 0; col < width; col++) {
schar_from_ascii(grid->chars[off + col], ' '); schar_from_ascii(grid->chars[off + col], ' ');
@@ -6361,7 +6399,9 @@ void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col,
} }
} }
ui_call_grid_scroll(grid->handle, row, end, col, col+width, -line_count, 0); if (!grid->throttled) {
ui_call_grid_scroll(grid->handle, row, end, col, col+width, -line_count, 0);
}
return; return;
} }
@@ -6412,7 +6452,9 @@ void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col,
} }
} }
ui_call_grid_scroll(grid->handle, row, end, col, col+width, line_count, 0); if (!grid->throttled) {
ui_call_grid_scroll(grid->handle, row, end, col, col+width, line_count, 0);
}
return; return;
} }
@@ -6440,6 +6482,8 @@ int showmode(void)
// don't make non-flushed message part of the showmode // don't make non-flushed message part of the showmode
msg_ext_ui_flush(); msg_ext_ui_flush();
msg_grid_validate();
do_mode = ((p_smd && msg_silent == 0) do_mode = ((p_smd && msg_silent == 0)
&& ((State & TERM_FOCUS) && ((State & TERM_FOCUS)
|| (State & INSERT) || (State & INSERT)
@@ -7094,13 +7138,11 @@ static void win_redr_ruler(win_T *wp, int always)
} }
} }
grid_puts(&default_grid, buffer, row, this_ru_col + off, attr); ScreenGrid *grid = part_of_status ? &default_grid : &msg_grid_adj;
i = redraw_cmdline; grid_puts(grid, buffer, row, this_ru_col + off, attr);
grid_fill(&default_grid, row, row + 1, grid_fill(grid, row, row + 1,
this_ru_col + off + (int)STRLEN(buffer), off + width, fillchar, this_ru_col + off + (int)STRLEN(buffer), off + width, fillchar,
fillchar, attr); fillchar, attr);
// don't redraw the cmdline because of showing the ruler
redraw_cmdline = i;
} }
wp->w_ru_cursor = wp->w_cursor; wp->w_ru_cursor = wp->w_cursor;
@@ -7214,6 +7256,12 @@ void screen_resize(int width, int height)
if (State == ASKMORE || State == EXTERNCMD || State == CONFIRM if (State == ASKMORE || State == EXTERNCMD || State == CONFIRM
|| exmode_active) { || exmode_active) {
screenalloc(); 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(); repeat_message();
} else { } else {
if (curwin->w_p_scb) if (curwin->w_p_scb)

View File

@@ -7533,6 +7533,9 @@ void highlight_changed(void)
hlf == (int)HLF_INACTIVE); hlf == (int)HLF_INACTIVE);
if (highlight_attr[hlf] != highlight_attr_last[hlf]) { if (highlight_attr[hlf] != highlight_attr_last[hlf]) {
if (hlf == HLF_MSG) {
clear_cmdline = true;
}
ui_call_hl_group_set(cstr_as_string((char *)hlf_names[hlf]), ui_call_hl_group_set(cstr_as_string((char *)hlf_names[hlf]),
highlight_attr[hlf]); highlight_attr[hlf]);
highlight_attr_last[hlf] = highlight_attr[hlf]; highlight_attr_last[hlf] = highlight_attr[hlf];

View File

@@ -333,6 +333,7 @@ void ui_set_ext_option(UI *ui, UIExtension ext, bool active)
void ui_line(ScreenGrid *grid, int row, int startcol, int endcol, int clearcol, void ui_line(ScreenGrid *grid, int row, int startcol, int endcol, int clearcol,
int clearattr, bool wrap) int clearattr, bool wrap)
{ {
assert(0 <= row && row < grid->Rows);
LineFlags flags = wrap ? kLineFlagWrap : 0; LineFlags flags = wrap ? kLineFlagWrap : 0;
if (startcol == -1) { if (startcol == -1) {
startcol = 0; startcol = 0;
@@ -404,6 +405,7 @@ void ui_flush(void)
cmdline_ui_flush(); cmdline_ui_flush();
win_ui_flush_positions(); win_ui_flush_positions();
msg_ext_ui_flush(); msg_ext_ui_flush();
msg_scroll_flush();
if (pending_cursor_update) { if (pending_cursor_update) {
ui_call_grid_cursor_goto(cursor_grid_handle, cursor_row, cursor_col); ui_call_grid_cursor_goto(cursor_grid_handle, cursor_row, cursor_col);

View File

@@ -19,6 +19,7 @@
#include "nvim/ui.h" #include "nvim/ui.h"
#include "nvim/highlight.h" #include "nvim/highlight.h"
#include "nvim/memory.h" #include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/popupmnu.h" #include "nvim/popupmnu.h"
#include "nvim/ui_compositor.h" #include "nvim/ui_compositor.h"
#include "nvim/ugrid.h" #include "nvim/ugrid.h"
@@ -46,8 +47,11 @@ static int chk_width = 0, chk_height = 0;
static ScreenGrid *curgrid; static ScreenGrid *curgrid;
static bool valid_screen = true; static bool valid_screen = true;
static bool msg_scroll_mode = false; static int msg_current_row = INT_MAX;
static int msg_first_invalid = 0; static bool msg_was_scrolled = false;
static int msg_sep_row = -1;
static schar_T msg_sep_char = { ' ', NUL };
static int dbghl_normal, dbghl_clear, dbghl_composed, dbghl_recompose; static int dbghl_normal, dbghl_clear, dbghl_composed, dbghl_recompose;
@@ -63,8 +67,7 @@ void ui_comp_init(void)
compositor->grid_scroll = ui_comp_grid_scroll; compositor->grid_scroll = ui_comp_grid_scroll;
compositor->grid_cursor_goto = ui_comp_grid_cursor_goto; compositor->grid_cursor_goto = ui_comp_grid_cursor_goto;
compositor->raw_line = ui_comp_raw_line; compositor->raw_line = ui_comp_raw_line;
compositor->win_scroll_over_start = ui_comp_win_scroll_over_start; compositor->msg_set_pos = ui_comp_msg_set_pos;
compositor->win_scroll_over_reset = ui_comp_win_scroll_over_reset;
// Be unopinionated: will be attached together with a "real" ui anyway // Be unopinionated: will be attached together with a "real" ui anyway
compositor->width = INT_MAX; compositor->width = INT_MAX;
@@ -158,8 +161,19 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width,
} }
#endif #endif
// TODO(bfredl): this is pretty ad-hoc, add a proper z-order/priority
// scheme. For now:
// - msg_grid is always on top.
// - pum_grid is on top of all windows but not msg_grid. Except for when
// wildoptions=pum, and completing the cmdline with scrolled messages,
// then the pum has to be drawn over the scrolled messages.
size_t insert_at = kv_size(layers); size_t insert_at = kv_size(layers);
if (kv_A(layers, insert_at-1) == &pum_grid) { bool cmd_completion = (grid == &pum_grid && (State & CMDLINE)
&& (wop_flags & WOP_PUM));
if (kv_A(layers, insert_at-1) == &msg_grid && !cmd_completion) {
insert_at--;
}
if (kv_A(layers, insert_at-1) == &pum_grid && (grid != &msg_grid)) {
insert_at--; insert_at--;
} }
if (insert_at > 1 && !on_top) { if (insert_at > 1 && !on_top) {
@@ -280,10 +294,10 @@ static void ui_comp_grid_cursor_goto(UI *ui, Integer grid_handle,
ScreenGrid *ui_comp_mouse_focus(int row, int col) ScreenGrid *ui_comp_mouse_focus(int row, int col)
{ {
// TODO(bfredl): click "through" unfocusable grids?
for (ssize_t i = (ssize_t)kv_size(layers)-1; i > 0; i--) { for (ssize_t i = (ssize_t)kv_size(layers)-1; i > 0; i--) {
ScreenGrid *grid = kv_A(layers, i); ScreenGrid *grid = kv_A(layers, i);
if (row >= grid->comp_row && row < grid->comp_row+grid->Rows if (grid->focusable
&& row >= grid->comp_row && row < grid->comp_row+grid->Rows
&& col >= grid->comp_col && col < grid->comp_col+grid->Columns) { && col >= grid->comp_col && col < grid->comp_col+grid->Columns) {
return grid; return grid;
} }
@@ -337,10 +351,28 @@ static void compose_line(Integer row, Integer startcol, Integer endcol,
assert(until > col); assert(until > col);
assert(until <= default_grid.Columns); assert(until <= default_grid.Columns);
size_t n = (size_t)(until-col); size_t n = (size_t)(until-col);
size_t off = grid->line_offset[row-grid->comp_row]
+ (size_t)(col-grid->comp_col); if (row == msg_sep_row && grid->comp_index <= msg_grid.comp_index) {
memcpy(linebuf+(col-startcol), grid->chars+off, n * sizeof(*linebuf)); grid = &msg_grid;
memcpy(attrbuf+(col-startcol), grid->attrs+off, n * sizeof(*attrbuf)); sattr_T msg_sep_attr = (sattr_T)HL_ATTR(HLF_MSGSEP);
for (int i = col; i < until; i++) {
memcpy(linebuf[i-startcol], msg_sep_char, sizeof(*linebuf));
attrbuf[i-startcol] = msg_sep_attr;
}
} else {
size_t off = grid->line_offset[row-grid->comp_row]
+ (size_t)(col-grid->comp_col);
memcpy(linebuf+(col-startcol), grid->chars+off, n * sizeof(*linebuf));
memcpy(attrbuf+(col-startcol), grid->attrs+off, n * sizeof(*attrbuf));
if (grid->comp_col+grid->Columns > until
&& grid->chars[off+n][0] == NUL) {
linebuf[until-1-startcol][0] = ' ';
linebuf[until-1-startcol][1] = '\0';
if (col == startcol && n == 1) {
skipstart = 0;
}
}
}
// 'pumblend' and 'winblend' // 'pumblend' and 'winblend'
if (grid->blending) { if (grid->blending) {
@@ -375,14 +407,6 @@ static void compose_line(Integer row, Integer startcol, Integer endcol,
} else if (n > 1 && linebuf[col-startcol+1][0] == NUL) { } else if (n > 1 && linebuf[col-startcol+1][0] == NUL) {
skipstart = 0; skipstart = 0;
} }
if (grid->comp_col+grid->Columns > until
&& grid->chars[off+n][0] == NUL) {
linebuf[until-1-startcol][0] = ' ';
linebuf[until-1-startcol][1] = '\0';
if (col == startcol && n == 1) {
skipstart = 0;
}
}
col = until; col = until;
} }
@@ -500,9 +524,12 @@ static void ui_comp_raw_line(UI *ui, Integer grid, Integer row,
endcol = MIN(endcol, clearcol); endcol = MIN(endcol, clearcol);
} }
if (flags & kLineFlagInvalid bool above_msg = (kv_A(layers, kv_size(layers)-1) == &msg_grid
|| kv_size(layers) > curgrid->comp_index+1 && row < msg_current_row-(msg_was_scrolled?1:0));
|| curgrid->blending) { bool covered = kv_size(layers)-(above_msg?1:0) > curgrid->comp_index+1;
// TODO(bfredl): eventually should just fix compose_line to respect clearing
// and optimize it for uncovered lines.
if (flags & kLineFlagInvalid || covered || curgrid->blending) {
compose_debug(row, row+1, startcol, clearcol, dbghl_composed, true); compose_debug(row, row+1, startcol, clearcol, dbghl_composed, true);
compose_line(row, startcol, clearcol, flags); compose_line(row, startcol, clearcol, flags);
} else { } else {
@@ -519,27 +546,44 @@ static void ui_comp_raw_line(UI *ui, Integer grid, Integer row,
void ui_comp_set_screen_valid(bool valid) void ui_comp_set_screen_valid(bool valid)
{ {
valid_screen = valid; valid_screen = valid;
if (!valid) {
msg_sep_row = -1;
}
} }
// TODO(bfredl): These events are somewhat of a hack. multiline messages static void ui_comp_msg_set_pos(UI *ui, Integer grid, Integer row,
// should later on be a separate grid, then this would just be ordinary Boolean scrolled, String sep_char)
// ui_comp_put_grid and ui_comp_remove_grid calls.
static void ui_comp_win_scroll_over_start(UI *ui)
{ {
msg_scroll_mode = true; msg_grid.comp_row = (int)row;
msg_first_invalid = ui->height; if (scrolled && row > 0) {
} msg_sep_row = (int)row-1;
if (sep_char.data) {
STRLCPY(msg_sep_char, sep_char.data, sizeof(msg_sep_char));
}
} else {
msg_sep_row = -1;
}
static void ui_comp_win_scroll_over_reset(UI *ui) if (row > msg_current_row && ui_comp_should_draw()) {
{ compose_area(MAX(msg_current_row-1, 0), row, 0, default_grid.Columns);
msg_scroll_mode = false; } else if (row < msg_current_row && ui_comp_should_draw()
for (size_t i = 1; i < kv_size(layers); i++) { && msg_current_row < Rows) {
ScreenGrid *grid = kv_A(layers, i); int delta = msg_current_row - (int)row;
if (grid->comp_row+grid->Rows > msg_first_invalid) { if (msg_grid.blending) {
compose_area(msg_first_invalid, grid->comp_row+grid->Rows, int first_row = MAX((int)row-(scrolled?1:0), 0);
grid->comp_col, grid->comp_col+grid->Columns); compose_area(first_row, Rows-delta, 0, Columns);
} else {
// scroll separator togheter with message text
int first_row = MAX((int)row-(msg_was_scrolled?1:0), 0);
ui_composed_call_grid_scroll(1, first_row, Rows, 0, Columns, delta, 0);
if (scrolled && !msg_was_scrolled && row > 0) {
compose_area(row-1, row, 0, Columns);
}
} }
} }
msg_current_row = (int)row;
msg_was_scrolled = scrolled;
} }
static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top, static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top,
@@ -554,7 +598,8 @@ static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top,
left += curgrid->comp_col; left += curgrid->comp_col;
right += curgrid->comp_col; right += curgrid->comp_col;
bool covered = kv_size(layers) > curgrid->comp_index+1 || curgrid->blending; bool covered = kv_size(layers) > curgrid->comp_index+1 || curgrid->blending;
if (!msg_scroll_mode && covered) {
if (covered) {
// TODO(bfredl): // TODO(bfredl):
// 1. check if rectangles actually overlap // 1. check if rectangles actually overlap
// 2. calulate subareas that can scroll. // 2. calulate subareas that can scroll.
@@ -565,7 +610,6 @@ static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top,
} }
compose_area(top, bot, left, right); compose_area(top, bot, left, right);
} else { } else {
msg_first_invalid = MIN(msg_first_invalid, (int)top);
ui_composed_call_grid_scroll(1, top, bot, left, right, rows, cols); ui_composed_call_grid_scroll(1, top, bot, left, right, rows, cols);
if (rdb_flags & RDB_COMPOSITOR) { if (rdb_flags & RDB_COMPOSITOR) {
debug_delay(2); debug_delay(2);

View File

@@ -697,6 +697,7 @@ static void ui_ext_win_position(win_T *wp)
ui_comp_put_grid(&wp->w_grid, comp_row, comp_col, wp->w_height, ui_comp_put_grid(&wp->w_grid, comp_row, comp_col, wp->w_height,
wp->w_width, valid, on_top); wp->w_width, valid, on_top);
ui_check_cursor_grid(wp->w_grid.handle); ui_check_cursor_grid(wp->w_grid.handle);
wp->w_grid.focusable = wp->w_float_config.focusable;
if (!valid) { if (!valid) {
wp->w_grid.valid = false; wp->w_grid.valid = false;
redraw_win_later(wp, NOT_VALID); redraw_win_later(wp, NOT_VALID);
@@ -5359,6 +5360,9 @@ void win_drag_status_line(win_T *dragwin, int offset)
} }
row = win_comp_pos(); row = win_comp_pos();
grid_fill(&default_grid, row, cmdline_row, 0, Columns, ' ', ' ', 0); grid_fill(&default_grid, row, cmdline_row, 0, Columns, ' ', ' ', 0);
if (msg_grid.chars) {
clear_cmdline = true;
}
cmdline_row = row; cmdline_row = row;
p_ch = Rows - cmdline_row; p_ch = Rows - cmdline_row;
if (p_ch < 1) if (p_ch < 1)