Merge pull request #10400 from bfredl/msg_grid

Dedicated message grid.
This commit is contained in:
Björn Linse 2019-09-01 20:25:36 +02:00 committed by GitHub
commit 9df3a676e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 2057 additions and 1092 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

@ -2654,8 +2654,7 @@ void buflist_list(exarg_T *eap)
buf == curbuf ? (int64_t)curwin->w_cursor.lnum buf == curbuf ? (int64_t)curwin->w_cursor.lnum
: (int64_t)buflist_findlnum(buf)); : (int64_t)buflist_findlnum(buf));
msg_outtrans(IObuff); msg_outtrans(IObuff);
ui_flush(); // output one line at a time line_breakcheck();
os_breakcheck();
} }
} }

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,21 +54,38 @@ 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;
// offsets for the grid relative to the global screen // 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. Used by screen.c
// for windows that don't have w_grid->chars etc allocated
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;
// state owned by the compositor. // whether the grid can be focused with mouse clicks.
bool focusable;
// Below is state owned by the compositor. Should generally not be set/read
// outside this module, except for specific compatibilty hacks
// position of the grid on the composed screen.
int comp_row; int comp_row;
int comp_col; int comp_col;
// z-index of the grid. Grids with higher index is draw on top.
// default_grid.comp_index is always zero.
size_t comp_index; size_t comp_index;
// compositor should momentarily ignore the grid. Used internally when
// moving around grids etc.
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

@ -3523,10 +3523,7 @@ 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_scrolled_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,121 @@ 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));
}
/// Send throttled message output to UI clients
///
/// The way message.c uses the grid_xx family of functions is quite inefficient
/// relative to the "gridline" UI protocol used by TUI and modern clients.
/// For instance scrolling is done one line at a time. By throttling drawing
/// on the message grid, we can coalesce scrolling to a single grid_scroll
/// per screen update.
///
/// NB: The bookkeeping is quite messy, and rests on a bunch of poorly
/// documented assumtions. For instance that the message area always grows while
/// being throttled, messages are only being output on the last line etc.
///
/// Probably message scrollback storage should reimplented as a file_buffer, and
/// message scrolling in TUI be reimplemented as a modal floating window. Then
/// we get throttling "for free" using standard redraw_win_later code paths.
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_scrolled_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_scrolled_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_scrolled_at_flush = 0;
} }
/* /*
@ -2285,6 +2461,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 +2474,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 +2696,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 +2713,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_scrolled_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 +2743,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 +2796,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 +2818,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 +2875,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
}
} }
/* /*
@ -2721,10 +2923,11 @@ int msg_end(void)
return FALSE; return FALSE;
} }
// @TODO(bfredl): calling flush here inhibits substantial performance // NOTE: ui_flush() used to be called here. This had to be removed, as it
// improvements. Caller should call ui_flush before waiting on user input or // inhibited substantial performance improvements. It is assumed that relevant
// CPU busywork. // callers invoke ui_flush() before going into CPU busywork, or restricted
ui_flush(); // calls msg_ext_ui_flush // event processing after displaying a message to the user.
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,23 @@ extern MessageHistoryEntry *last_msg_hist;
EXTERN bool msg_ext_need_clear INIT(= false); EXTERN bool msg_ext_need_clear INIT(= false);
// allocated grid for messages. Used when display+=msgsep is set, or
// ext_multigrid is active. See also the description at msg_scroll_flush()
EXTERN ScreenGrid msg_grid INIT(= SCREEN_GRID_INIT);
EXTERN int msg_grid_pos INIT(= 0);
// "adjusted" message grid. This grid accepts positions relative to
// default_grid. Internally it will be translated to a position on msg_grid
// relative to the start of the message area, or directly mapped to default_grid
// for legacy (display-=msgsep) message scroll behavior.
// // TODO(bfredl): refactor "internal" message logic, msg_row etc
// to use the correct positions already.
EXTERN ScreenGrid msg_grid_adj INIT(= SCREEN_GRID_INIT);
// value of msg_scrolled at latest msg_scroll_flush.
EXTERN int msg_scrolled_at_flush 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_scrolled_at_flush = 0;
}
if (type >= CLEAR || !default_grid.valid) {
ui_comp_set_screen_valid(false);
} }
// if the screen was scrolled up when displaying a message, scroll it down // if 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_scrolled_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,30 @@ 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)); // TODO(bfredl): when we implement borders around floating windows, then
memcpy(attrbuf+(col-startcol), grid->attrs+off, n * sizeof(*attrbuf)); // msgsep can just be a border "around" the message grid.
grid = &msg_grid;
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 +409,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 +526,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 +548,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 +600,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 +612,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)

View File

@ -222,6 +222,7 @@ describe('timers', function()
let g:val = 0 let g:val = 0
func! MyHandler(timer) func! MyHandler(timer)
echo "evil" echo "evil"
redraw
let g:val = 1 let g:val = 1
endfunc endfunc
]]) ]])

View File

@ -56,6 +56,8 @@ local function screen_setup(extra_rows, command, cols, opts)
[8] = {foreground = 15, background = 1}, -- error message [8] = {foreground = 15, background = 1}, -- error message
[9] = {foreground = 4}, [9] = {foreground = 4},
[10] = {foreground = 121}, -- "Press ENTER" in embedded :terminal session. [10] = {foreground = 121}, -- "Press ENTER" in embedded :terminal session.
[11] = {foreground = tonumber('0x00000b')},
[12] = {reverse = true, foreground = tonumber('0x000079')},
}) })
screen:attach(opts or {rgb=false}) screen:attach(opts or {rgb=false})

View File

@ -89,6 +89,85 @@ describe('TUI', function()
eq(2, eval("1+1")) -- Still alive? eq(2, eval("1+1")) -- Still alive?
end) end)
it('accepts resize while pager is active', function()
child_session:request("nvim_command", [[
set more
func! ManyErr()
for i in range(10)
echoerr "FAIL ".i
endfor
endfunc
]])
feed_data(':call ManyErr()\r')
screen:expect{grid=[[
{8:Error detected while processing function ManyErr:} |
{11:line 2:} |
{8:FAIL 0} |
{8:FAIL 1} |
{8:FAIL 2} |
{10:-- More --}{1: } |
{3:-- TERMINAL --} |
]]}
feed_data('d')
screen:expect{grid=[[
{8:FAIL 1} |
{8:FAIL 2} |
{8:FAIL 3} |
{8:FAIL 4} |
{8:FAIL 5} |
{10:-- More --}{1: } |
{3:-- TERMINAL --} |
]]}
screen:try_resize(50,5)
screen:expect{grid=[[
{8:FAIL 1} |
{8:FAIL 2} |
{8:FAIL 3} |
{10:-- More -- SPACE/d/j: screen/page/line down, b/u/}{12:k}|
{3:-- TERMINAL --} |
]]}
-- TODO(bfredl): messes up the output (just like vim does).
feed_data('g')
screen:expect{grid=[[
{8:FAIL 1} ) |
{8:Error detected while processing function ManyErr:} |
{11:line 2:} |
{10:-- More --}{1: } |
{3:-- TERMINAL --} |
]]}
screen:try_resize(50,10)
screen:expect{grid=[[
{8:FAIL 1} ) |
{8:Error detected while processing function ManyErr:} |
{11:line 2:} |
{10:-- More --} |
{10: }|
{10: }|
{10: }|
{10: }|
{10:-- More -- SPACE/d/j: screen/page/line down, b/u/}{12:k}|
{3:-- TERMINAL --} |
]]}
feed_data('\003')
screen:expect{grid=[[
{1: } |
{4:~ }|
{4:~ }|
{4:~ }|
{4:~ }|
{4:~ }|
{4:~ }|
{5:[No Name] }|
|
{3:-- TERMINAL --} |
]]}
end)
it('accepts basic utf-8 input', function() it('accepts basic utf-8 input', function()
feed_data('iabc\ntest1\ntest2') feed_data('iabc\ntest1\ntest2')
screen:expect([[ screen:expect([[

View File

@ -158,7 +158,7 @@ describe(':terminal with multigrid', function()
[2:--------------------------------------------------]| [2:--------------------------------------------------]|
[2:--------------------------------------------------]| [2:--------------------------------------------------]|
[2:--------------------------------------------------]| [2:--------------------------------------------------]|
{3:-- TERMINAL --} | [3:--------------------------------------------------]|
## grid 2 ## grid 2
tty ready | tty ready |
{1: } | {1: } |
@ -166,6 +166,8 @@ describe(':terminal with multigrid', function()
| |
| |
| |
## grid 3
{3:-- TERMINAL --} |
]]) ]])
screen:try_resize_grid(2, 20, 10) screen:try_resize_grid(2, 20, 10)
@ -180,7 +182,7 @@ describe(':terminal with multigrid', function()
[2:--------------------------------------------------]| [2:--------------------------------------------------]|
[2:--------------------------------------------------]| [2:--------------------------------------------------]|
[2:--------------------------------------------------]| [2:--------------------------------------------------]|
{3:-- TERMINAL --} | [3:--------------------------------------------------]|
## grid 2 ## grid 2
tty ready | tty ready |
rows: 10, cols: 20 | rows: 10, cols: 20 |
@ -192,6 +194,8 @@ describe(':terminal with multigrid', function()
| |
| |
| |
## grid 3
{3:-- TERMINAL --} |
]]) ]])
end end
@ -207,11 +211,13 @@ describe(':terminal with multigrid', function()
[2:--------------------------------------------------]| [2:--------------------------------------------------]|
[2:--------------------------------------------------]| [2:--------------------------------------------------]|
[2:--------------------------------------------------]| [2:--------------------------------------------------]|
{3:-- TERMINAL --} | [3:--------------------------------------------------]|
## grid 2 ## grid 2
rows: 10, cols: 20 | rows: 10, cols: 20 |
rows: 3, cols: 70 | rows: 3, cols: 70 |
{1: } | {1: } |
## grid 3
{3:-- TERMINAL --} |
]]) ]])
end end
@ -227,7 +233,7 @@ describe(':terminal with multigrid', function()
[2:--------------------------------------------------]| [2:--------------------------------------------------]|
[2:--------------------------------------------------]| [2:--------------------------------------------------]|
[2:--------------------------------------------------]| [2:--------------------------------------------------]|
{3:-- TERMINAL --} | [3:--------------------------------------------------]|
## grid 2 ## grid 2
tty ready | tty ready |
rows: 10, cols: 20 | rows: 10, cols: 20 |
@ -235,6 +241,8 @@ describe(':terminal with multigrid', function()
rows: 6, cols: 50 | rows: 6, cols: 50 |
{1: } | {1: } |
| |
## grid 3
{3:-- TERMINAL --} |
]]) ]])
end end
end) end)

View File

@ -216,10 +216,10 @@ describe('ui/cursor', function()
if m.blinkwait then m.blinkwait = 700 end if m.blinkwait then m.blinkwait = 700 end
end end
if m.hl_id then if m.hl_id then
m.hl_id = 54 m.hl_id = 55
m.attr = {background = Screen.colors.DarkGray} m.attr = {background = Screen.colors.DarkGray}
end end
if m.id_lm then m.id_lm = 55 end if m.id_lm then m.id_lm = 56 end
end end
-- Assert the new expectation. -- Assert the new expectation.

View File

@ -18,6 +18,7 @@ local function test_embed(ext_linegrid)
[2] = {bold = true, foreground = Screen.colors.SeaGreen4}, [2] = {bold = true, foreground = Screen.colors.SeaGreen4},
[3] = {bold = true, foreground = Screen.colors.Blue1}, [3] = {bold = true, foreground = Screen.colors.Blue1},
[4] = {bold = true, foreground = Screen.colors.Green}, [4] = {bold = true, foreground = Screen.colors.Green},
[5] = {bold = true, reverse = true},
}) })
end end
@ -53,7 +54,7 @@ local function test_embed(ext_linegrid)
| |
| |
| |
| {5: }|
Error detected while processing pre-vimrc command line: | Error detected while processing pre-vimrc command line: |
foo | foo |
{1:bar} | {1:bar} |

File diff suppressed because it is too large Load Diff

View File

@ -933,17 +933,29 @@ end)
describe("MsgSeparator highlight and msgsep fillchar", function() describe("MsgSeparator highlight and msgsep fillchar", function()
before_each(clear) local screen
it("works", function() before_each(function()
local screen = Screen.new(50,5) clear()
screen = Screen.new(50,5)
screen:set_default_attr_ids({ screen:set_default_attr_ids({
[1] = {bold=true, foreground=Screen.colors.Blue}, [1] = {bold=true, foreground=Screen.colors.Blue},
[2] = {bold=true, reverse=true}, [2] = {bold=true, reverse=true},
[3] = {bold = true, foreground = Screen.colors.SeaGreen4}, [3] = {bold = true, foreground = Screen.colors.SeaGreen4},
[4] = {background = Screen.colors.Cyan, bold = true, reverse = true}, [4] = {background = Screen.colors.Cyan, bold = true, reverse = true},
[5] = {bold = true, background = Screen.colors.Magenta} [5] = {bold = true, background = Screen.colors.Magenta},
[6] = {background = Screen.colors.WebGray},
[7] = {background = Screen.colors.WebGray, bold = true, foreground = Screen.colors.SeaGreen4},
[8] = {foreground = Screen.colors.Grey0, background = Screen.colors.Gray60},
[9] = {foreground = Screen.colors.Grey40, background = Screen.colors.Gray60},
[10] = {foreground = tonumber('0x000019'), background = Screen.colors.Gray60},
[11] = {background = Screen.colors.Gray60, bold = true, foreground = tonumber('0x666699')},
[12] = {background = Screen.colors.Gray60, bold = true, foreground = tonumber('0x297d4e')},
[13] = {background = tonumber('0xff4cff'), bold = true, foreground = tonumber('0xb200ff')},
}) })
screen:attach() screen:attach()
end)
it("works", function()
-- defaults -- defaults
feed_command("ls") feed_command("ls")
@ -1000,6 +1012,61 @@ describe("MsgSeparator highlight and msgsep fillchar", function()
{3:Press ENTER or type command to continue}^ | {3:Press ENTER or type command to continue}^ |
]]) ]])
end) end)
it("and MsgArea", function()
feed_command("hi MsgArea guibg=Gray")
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{6: }|
]]}
feed(":ls")
screen:expect{grid=[[
|
{1:~ }|
{1:~ }|
{1:~ }|
{6::ls^ }|
]]}
feed(":<cr>")
screen:expect{grid=[[
|
{2: }|
{6::ls: }|
{6: 1 %a "[No Name]" line 1 }|
{7:Press ENTER or type command to continue}{6:^ }|
]]}
-- support madness^Wblending of message "overlay"
feed_command("hi MsgArea blend=20")
feed_command("hi clear MsgSeparator")
feed_command("hi MsgSeparator blend=30 guibg=Magenta")
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{8::hi}{9: }{8:MsgSeparator}{9: }{8:blend=30}{9: }{8:guibg=Magenta}{9: }|
]]}
feed(":ls")
screen:expect{grid=[[
|
{1:~ }|
{1:~ }|
{1:~ }|
{8::ls}{9:^ }|
]]}
feed("<cr>")
screen:expect{grid=[[
|
{13:~ }|
{10::ls}{11: }|
{11:~ }{10:1}{11: }{10:%a}{11: }{10:"[No}{11: }{10:Name]"}{11: }{10:line}{11: }{10:1}{11: }|
{12:Press}{9: }{12:ENTER}{9: }{12:or}{9: }{12:type}{9: }{12:command}{9: }{12:to}{9: }{12:continue}{9:^ }|
]]}
end)
end) end)
describe("'winhighlight' highlight", function() describe("'winhighlight' highlight", function()

View File

@ -39,7 +39,7 @@ describe('ext_hlstate detailed highlights', function()
{5:~ }| {5:~ }|
{5:~ }| {5:~ }|
{5:~ }| {5:~ }|
{6:search hit BOTTOM, continuing at TOP} | {8:search hit BOTTOM, continuing at TOP}{7: }|
]], { ]], {
[1] = {{foreground = Screen.colors.Magenta}, [1] = {{foreground = Screen.colors.Magenta},
{{hi_name = "Constant", kind = "syntax"}}}, {{hi_name = "Constant", kind = "syntax"}}},
@ -52,6 +52,8 @@ describe('ext_hlstate detailed highlights', function()
{{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}}, {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}},
[6] = {{foreground = Screen.colors.Red}, [6] = {{foreground = Screen.colors.Red},
{{hi_name = "WarningMsg", ui_name = "WarningMsg", kind = "ui"}}}, {{hi_name = "WarningMsg", ui_name = "WarningMsg", kind = "ui"}}},
[7] = {{}, {{hi_name = "MsgArea", ui_name = "MsgArea", kind = "ui"}}},
[8] = {{foreground = Screen.colors.Red}, {7, 6}},
}) })
end) end)
@ -66,6 +68,7 @@ describe('ext_hlstate detailed highlights', function()
{{hi_name = "StatusLineNC", ui_name = "StatusLineNC" , kind = "ui"}}}, {{hi_name = "StatusLineNC", ui_name = "StatusLineNC" , kind = "ui"}}},
[5] = {{}, {{hi_name = "StatusLine", ui_name = "StatusLine", kind = "ui"}}}, [5] = {{}, {{hi_name = "StatusLine", ui_name = "StatusLine", kind = "ui"}}},
[6] = {{}, {{hi_name = "StatusLineNC", ui_name = "StatusLineNC", kind = "ui"}}}, [6] = {{}, {{hi_name = "StatusLineNC", ui_name = "StatusLineNC", kind = "ui"}}},
[7] = {{}, {{hi_name = "MsgArea", ui_name = "MsgArea", kind = "ui"}}},
}) })
command("hi clear VertSplit") command("hi clear VertSplit")
command("vsplit") command("vsplit")
@ -78,7 +81,7 @@ describe('ext_hlstate detailed highlights', function()
{2:~ }{1:}{2:~ }| {2:~ }{1:}{2:~ }|
{2:~ }{1:}{2:~ }| {2:~ }{1:}{2:~ }|
{3:[No Name] }{4:[No Name] }| {3:[No Name] }{4:[No Name] }|
| {7: }|
]]) ]])
command("hi clear StatusLine | hi clear StatuslineNC") command("hi clear StatusLine | hi clear StatuslineNC")
@ -90,7 +93,7 @@ describe('ext_hlstate detailed highlights', function()
{2:~ }{1:}{2:~ }| {2:~ }{1:}{2:~ }|
{2:~ }{1:}{2:~ }| {2:~ }{1:}{2:~ }|
{5:[No Name] }{6:[No Name] }| {5:[No Name] }{6:[No Name] }|
| {7: }|
]]) ]])
-- redrawing is done even if visible highlights didn't change -- redrawing is done even if visible highlights didn't change
@ -103,7 +106,7 @@ describe('ext_hlstate detailed highlights', function()
{2:~ }{1:}{2:~ }| {2:~ }{1:}{2:~ }|
{2:~ }{1:}{2:~ }| {2:~ }{1:}{2:~ }|
{6:[No Name] }{5:[No Name] }| {6:[No Name] }{5:[No Name] }|
| {7: }|
]]) ]])
end) end)
@ -120,7 +123,8 @@ describe('ext_hlstate detailed highlights', function()
[8] = {{foreground = Screen.colors.Blue1, bold = true, reverse = true}, {6, 2}}, [8] = {{foreground = Screen.colors.Blue1, bold = true, reverse = true}, {6, 2}},
[9] = {{bold = true, foreground = Screen.colors.Brown}, {{hi_name = "Statement", ui_name = "NormalNC", kind = "ui"}}}, [9] = {{bold = true, foreground = Screen.colors.Brown}, {{hi_name = "Statement", ui_name = "NormalNC", kind = "ui"}}},
[10] = {{bold = true, foreground = Screen.colors.Brown}, {9, 1}}, [10] = {{bold = true, foreground = Screen.colors.Brown}, {9, 1}},
[11] = {{bold = true, foreground = Screen.colors.Blue1}, {9, 2}} [11] = {{bold = true, foreground = Screen.colors.Blue1}, {9, 2}},
[12] = {{}, {{hi_name = "MsgArea", ui_name = "MsgArea", kind = "ui"}}},
}) })
command("set number") command("set number")
@ -134,7 +138,7 @@ describe('ext_hlstate detailed highlights', function()
{1: 1 } | {1: 1 } |
{2:~ }| {2:~ }|
{4:[No Name] }| {4:[No Name] }|
| {12: }|
]]) ]])
command("set winhl=LineNr:ErrorMsg") command("set winhl=LineNr:ErrorMsg")
@ -146,7 +150,7 @@ describe('ext_hlstate detailed highlights', function()
{1: 1 } | {1: 1 } |
{2:~ }| {2:~ }|
{4:[No Name] }| {4:[No Name] }|
| {12: }|
]]) ]])
command("set winhl=Normal:MsgSeparator,NormalNC:Statement") command("set winhl=Normal:MsgSeparator,NormalNC:Statement")
@ -158,7 +162,7 @@ describe('ext_hlstate detailed highlights', function()
{1: 1 } | {1: 1 } |
{2:~ }| {2:~ }|
{4:[No Name] }| {4:[No Name] }|
| {12: }|
]]) ]])
command("wincmd w") command("wincmd w")
@ -170,7 +174,7 @@ describe('ext_hlstate detailed highlights', function()
{1: 1 }^ | {1: 1 }^ |
{2:~ }| {2:~ }|
{3:[No Name] }| {3:[No Name] }|
| {12: }|
]]) ]])
end) end)
@ -182,6 +186,7 @@ describe('ext_hlstate detailed highlights', function()
[4] = {{foreground = 52479}, {2, 1}}, [4] = {{foreground = 52479}, {2, 1}},
[5] = {{foreground = 4259839}, {{kind = "term"}}}, [5] = {{foreground = 4259839}, {{kind = "term"}}},
[6] = {{foreground = 4259839}, {5, 1}}, [6] = {{foreground = 4259839}, {5, 1}},
[7] = {{}, {{hi_name = "MsgArea", ui_name = "MsgArea", kind = "ui"}}},
}) })
command('enew | call termopen(["'..nvim_dir..'/tty-test"])') command('enew | call termopen(["'..nvim_dir..'/tty-test"])')
screen:expect([[ screen:expect([[
@ -192,7 +197,7 @@ describe('ext_hlstate detailed highlights', function()
| |
| |
| |
| {7: }|
]]) ]])
thelpers.feed_data('x ') thelpers.feed_data('x ')
@ -210,7 +215,7 @@ describe('ext_hlstate detailed highlights', function()
| |
| |
| |
| {7: }|
]]) ]])
else else
screen:expect([[ screen:expect([[
@ -221,7 +226,7 @@ describe('ext_hlstate detailed highlights', function()
| |
| |
| |
| {7: }|
]]) ]])
end end
@ -236,7 +241,7 @@ describe('ext_hlstate detailed highlights', function()
| |
| |
| |
| {7: }|
]]) ]])
else else
screen:expect([[ screen:expect([[
@ -247,7 +252,7 @@ describe('ext_hlstate detailed highlights', function()
| |
| |
| |
| {7: }|
]]) ]])
end end
end) end)
@ -259,6 +264,7 @@ describe('ext_hlstate detailed highlights', function()
screen:set_default_attr_ids({ screen:set_default_attr_ids({
[1] = {{bold = true, foreground = Screen.colors.Blue1}, {foreground = 12}, {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}}, [1] = {{bold = true, foreground = Screen.colors.Blue1}, {foreground = 12}, {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}},
[2] = {{reverse = true, foreground = Screen.colors.Red}, {foreground = 10, italic=true}, {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}}, [2] = {{reverse = true, foreground = Screen.colors.Red}, {foreground = 10, italic=true}, {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}},
[3] = {{}, {}, {{hi_name = "MsgArea", ui_name = "MsgArea", kind = "ui"}}},
}) })
screen:expect([[ screen:expect([[
^ | ^ |
@ -268,7 +274,7 @@ describe('ext_hlstate detailed highlights', function()
{1:~ }| {1:~ }|
{1:~ }| {1:~ }|
{1:~ }| {1:~ }|
| {3: }|
]]) ]])
command("hi NonText guifg=Red gui=reverse ctermfg=Green cterm=italic") command("hi NonText guifg=Red gui=reverse ctermfg=Green cterm=italic")
@ -280,7 +286,7 @@ describe('ext_hlstate detailed highlights', function()
{2:~ }| {2:~ }|
{2:~ }| {2:~ }|
{2:~ }| {2:~ }|
| {3: }|
]]) ]])
end) end)

File diff suppressed because it is too large Load Diff

View File

@ -1128,7 +1128,7 @@ describe('builtin popupmenu', function()
prefix | prefix |
bef{n: word } | bef{n: word } |
tex{n: }^ | tex{n: }^ |
{2:-- }{s: text } | {2:-- INSERT -} |
]]) ]])
-- can't draw the pum, but check we don't crash -- can't draw the pum, but check we don't crash
@ -1597,6 +1597,54 @@ describe('builtin popupmenu', function()
]]) ]])
end) end)
it('works with wildoptions=pum with scrolled mesages ', function()
screen:try_resize(40,10)
command('set wildmenu')
command('set wildoptions=pum')
feed(':echoerr "fail"|echoerr "error"<cr>')
screen:expect{grid=[[
|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{4: }|
{6:fail} |
{6:error} |
{5:Press ENTER or type command to continue}^ |
]]}
feed(':sign <tab>')
screen:expect{grid=[[
|
{1:~ }|
{1:~ }|
{1:~ }{s: define }{1: }|
{1:~ }{n: jump }{1: }|
{1:~ }{n: list }{1: }|
{4: }{n: place }{4: }|
{6:fail} {n: undefine } |
{6:error}{n: unplace } |
:sign define^ |
]]}
feed('d')
screen:expect{grid=[[
|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{4: }|
{6:fail} |
{6:error} |
:sign defined^ |
]]}
end)
it("'pumblend' RGB-color", function() it("'pumblend' RGB-color", function()
screen:try_resize(60,14) screen:try_resize(60,14)
screen:set_default_attr_ids({ screen:set_default_attr_ids({

View File

@ -159,6 +159,8 @@ function Screen.new(width, height)
wildmenu_selected = nil, wildmenu_selected = nil,
win_position = {}, win_position = {},
float_pos = {}, float_pos = {},
msg_grid = nil,
msg_grid_pos = nil,
_session = nil, _session = nil,
messages = {}, messages = {},
msg_history = {}, msg_history = {},
@ -391,27 +393,31 @@ function Screen:expect(expected, attr_ids, attr_ignore, ...)
end end
if grid ~= nil then if grid ~= nil then
local err_msg, msg_expected_rows = nil, {}
-- `expected` must match the screen lines exactly. -- `expected` must match the screen lines exactly.
if #actual_rows ~= #expected_rows then if #actual_rows ~= #expected_rows then
return "Expected screen state's row count(" .. #expected_rows err_msg = "Expected screen height " .. #expected_rows
.. ') differs from configured height(' .. #actual_rows .. ') of Screen.' .. ' differs from actual height ' .. #actual_rows .. '.'
end end
for i = 1, #actual_rows do for i = 1, #expected_rows do
msg_expected_rows[i] = expected_rows[i]
if expected_rows[i] ~= actual_rows[i] and expected_rows[i] ~= "{IGNORE}|" then if expected_rows[i] ~= actual_rows[i] and expected_rows[i] ~= "{IGNORE}|" then
local msg_expected_rows = {}
for j = 1, #expected_rows do
msg_expected_rows[j] = expected_rows[j]
end
msg_expected_rows[i] = '*' .. msg_expected_rows[i] msg_expected_rows[i] = '*' .. msg_expected_rows[i]
actual_rows[i] = '*' .. actual_rows[i] if i <= #actual_rows then
return ( actual_rows[i] = '*' .. actual_rows[i]
'Row ' .. tostring(i) .. ' did not match.\n' end
..'Expected:\n |'..table.concat(msg_expected_rows, '\n |')..'\n' if err_msg == nil then
..'Actual:\n |'..table.concat(actual_rows, '\n |')..'\n\n'..[[ err_msg = 'Row ' .. tostring(i) .. ' did not match.'
end
end
end
if err_msg ~= nil then
return (
err_msg..'\nExpected:\n |'..table.concat(msg_expected_rows, '\n |')..'\n'
..'Actual:\n |'..table.concat(actual_rows, '\n |')..'\n\n'..[[
To print the expect() call that would assert the current screen state, use To print the expect() call that would assert the current screen state, use
screen:snapshot_util(). In case of non-deterministic failures, use screen:snapshot_util(). In case of non-deterministic failures, use
screen:redraw_debug() to show all intermediate screen states. ]]) screen:redraw_debug() to show all intermediate screen states. ]])
end
end end
end end
@ -672,13 +678,12 @@ function Screen:_handle_grid_resize(grid, width, height)
} }
end end
function Screen:_handle_win_scroll_over_start()
self.scroll_over = true
self.scroll_over_pos = self._grids[1].height
end
function Screen:_handle_win_scroll_over_reset() function Screen:_handle_msg_set_pos(grid, row, scrolled, char)
self.scroll_over = false self.msg_grid = grid
self.msg_grid_pos = row
self.msg_scrolled = scrolled
self.msg_sep_char = char
end end
function Screen:_handle_flush() function Screen:_handle_flush()
@ -819,10 +824,6 @@ function Screen:_handle_scroll(count)
end end
function Screen:_handle_grid_scroll(g, top, bot, left, right, rows, cols) function Screen:_handle_grid_scroll(g, top, bot, left, right, rows, cols)
if self.scroll_over and g == 1 and top < self.scroll_over_pos then
self.scroll_over_pos = top
end
top = top+1 top = top+1
left = left+1 left = left+1
assert(cols == 0) assert(cols == 0)
@ -1072,10 +1073,10 @@ function Screen:_row_repr(gridnr, rownr, attr_state, cursor)
local current_attr_id local current_attr_id
local i = 1 local i = 1
local has_windows = self._options.ext_multigrid and gridnr == 1 local has_windows = self._options.ext_multigrid and gridnr == 1
if self.scroll_over and self.scroll_over_pos < rownr then
has_windows = false
end
local row = self._grids[gridnr].rows[rownr] local row = self._grids[gridnr].rows[rownr]
if has_windows and self.msg_grid and self.msg_grid_pos < rownr then
return '['..self.msg_grid..':'..string.rep('-',#row)..']'
end
while i <= #row do while i <= #row do
local did_window = false local did_window = false
if has_windows then if has_windows then
@ -1214,12 +1215,17 @@ function Screen:render(headers, attr_state, preview)
for igrid,grid in pairs(self._grids) do for igrid,grid in pairs(self._grids) do
if headers then if headers then
local suffix = "" local suffix = ""
if igrid > 1 and self.win_position[igrid] == nil and self.float_pos[igrid] == nil then if igrid > 1 and self.win_position[igrid] == nil
and self.float_pos[igrid] == nil and self.msg_grid ~= igrid then
suffix = " (hidden)" suffix = " (hidden)"
end end
table.insert(rv, "## grid "..igrid..suffix) table.insert(rv, "## grid "..igrid..suffix)
end end
for i = 1, grid.height do local height = grid.height
if igrid == self.msg_grid then
height = self._grids[1].height - self.msg_grid_pos
end
for i = 1, height do
local cursor = self._cursor.grid == igrid and self._cursor.row == i local cursor = self._cursor.grid == igrid and self._cursor.row == i
local prefix = (headers or preview) and " " or "" local prefix = (headers or preview) and " " or ""
table.insert(rv, prefix..self:_row_repr(igrid, i, attr_state, cursor).."|") table.insert(rv, prefix..self:_row_repr(igrid, i, attr_state, cursor).."|")
@ -1298,6 +1304,7 @@ function Screen:print_snapshot(attrs, ignore)
end end
attrstr = (", attr_ids={\n"..table.concat(attrstrs, "\n").."\n}") attrstr = (", attr_ids={\n"..table.concat(attrstrs, "\n").."\n}")
end end
print( "\nscreen:expect{grid=[[") print( "\nscreen:expect{grid=[[")
print(kwargs.grid) print(kwargs.grid)
io.stdout:write( "]]"..attrstr) io.stdout:write( "]]"..attrstr)