aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjörn Linse <bjorn.linse@gmail.com>2019-07-02 15:53:43 +0200
committerBjörn Linse <bjorn.linse@gmail.com>2019-09-01 15:55:10 +0200
commitb51ba122c1edfb769e72c25c4f14f469f59f1b8e (patch)
tree84052cf27b2e1aa904b898cd01d9d35df9afaf92
parent9cec097ffacfd0de100912781df2efed1a4c8ba9 (diff)
downloadrneovim-b51ba122c1edfb769e72c25c4f14f469f59f1b8e.tar.gz
rneovim-b51ba122c1edfb769e72c25c4f14f469f59f1b8e.tar.bz2
rneovim-b51ba122c1edfb769e72c25c4f14f469f59f1b8e.zip
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
-rw-r--r--runtime/doc/syntax.txt2
-rw-r--r--runtime/doc/ui.txt23
-rw-r--r--src/nvim/api/ui.c3
-rw-r--r--src/nvim/api/ui_events.in.h4
-rw-r--r--src/nvim/buffer.c1
-rw-r--r--src/nvim/eval.c33
-rw-r--r--src/nvim/ex_getln.c33
-rw-r--r--src/nvim/grid_defs.h13
-rw-r--r--src/nvim/highlight.c3
-rw-r--r--src/nvim/highlight_defs.h2
-rw-r--r--src/nvim/memline.c6
-rw-r--r--src/nvim/message.c278
-rw-r--r--src/nvim/message.h8
-rw-r--r--src/nvim/mouse.c10
-rw-r--r--src/nvim/normal.c10
-rw-r--r--src/nvim/option.c1
-rw-r--r--src/nvim/option_defs.h1
-rw-r--r--src/nvim/screen.c148
-rw-r--r--src/nvim/syntax.c3
-rw-r--r--src/nvim/ui.c2
-rw-r--r--src/nvim/ui_compositor.c122
-rw-r--r--src/nvim/window.c4
22 files changed, 529 insertions, 181 deletions
diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt
index e5b65554d4..f813717eeb 100644
--- a/runtime/doc/syntax.txt
+++ b/runtime/doc/syntax.txt
@@ -4969,6 +4969,8 @@ MatchParen The character under the cursor or just before it, if it
*hl-ModeMsg*
ModeMsg 'showmode' message (e.g., "-- INSERT --")
+ *hl-MsgArea*
+MsgArea Area for messages and cmdline
*hl-MsgSeparator*
MsgSeparator Separator for scrolled messages, `msgsep` flag of 'display'
*hl-MoreMsg*
diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt
index 7d213f959b..3b7debd99e 100644
--- a/runtime/doc/ui.txt
+++ b/runtime/doc/ui.txt
@@ -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
per-window. Or reserve space around the border of the window for its own
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
(i.e. the size of the window frame in Nvim) whenever the split is created.
@@ -573,19 +575,20 @@ tabs.
["win_hide", grid]
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]
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*
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index 20ed77afad..7e45abb897 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -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_icon = remote_ui_set_icon;
ui->option_set = remote_ui_option_set;
- ui->win_scroll_over_start = remote_ui_win_scroll_over_start;
- ui->win_scroll_over_reset = remote_ui_win_scroll_over_reset;
+ ui->msg_set_pos = remote_ui_msg_set_pos;
ui->event = remote_ui_event;
ui->inspect = remote_ui_inspect;
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index 9f58257e53..6677e248cf 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -112,9 +112,7 @@ void win_hide(Integer grid)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void win_close(Integer grid)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
-void win_scroll_over_start(void)
- FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL;
-void win_scroll_over_reset(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 popupmenu_show(Array items, Integer selected,
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 2cc9643b7e..382b4c45c1 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -2656,7 +2656,6 @@ void buflist_list(exarg_T *eap)
msg_outtrans(IObuff);
line_breakcheck();
}
- ui_flush();
}
/*
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 12c53fa804..488790970e 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -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
*/
@@ -14655,13 +14670,15 @@ static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
int c;
- const int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
- const int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
+ int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
+ int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
if (row < 0 || row >= default_grid.Rows
|| col < 0 || col >= default_grid.Columns) {
c = -1;
} 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;
}
@@ -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)
{
- int off;
int c;
- const int row = tv_get_number_chk(&argvars[0], NULL) - 1;
- const int col = tv_get_number_chk(&argvars[1], NULL) - 1;
+ int row = tv_get_number_chk(&argvars[0], NULL) - 1;
+ int col = tv_get_number_chk(&argvars[1], NULL) - 1;
if (row < 0 || row >= default_grid.Rows
|| col < 0 || col >= default_grid.Columns) {
c = -1;
} else {
- off = default_grid.line_offset[row] + col;
- c = utf_ptr2char(default_grid.chars[off]);
+ ScreenGrid *grid = &default_grid;
+ screenchar_adjust_grid(&grid, &row, &col);
+ c = utf_ptr2char(grid->chars[grid->line_offset[row] + col]);
}
rettv->vval.v_number = c;
}
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 4e43e95c2e..4f35555098 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -310,6 +310,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
cmdmsg_rl = false;
}
+ msg_grid_validate();
+
redir_off = true; // don't redirect the typed command
if (!cmd_silent) {
gotocmdline(true);
@@ -908,7 +910,7 @@ static int command_line_execute(VimState *state, int key)
if (!cmd_silent) {
if (!ui_has(kUICmdline)) {
- ui_cursor_goto(msg_row, 0);
+ cmd_cursor_goto(msg_row, 0);
}
ui_flush();
}
@@ -2323,7 +2325,7 @@ redraw:
}
}
msg_clr_eos();
- ui_cursor_goto(msg_row, msg_col);
+ cmd_cursor_goto(msg_row, msg_col);
continue;
}
@@ -2391,7 +2393,7 @@ redraw:
line_ga.ga_len += len;
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;
/* 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 */
if (ccline.cmdbuff == NULL) {
- ui_cursor_goto(cmdline_row, 0);
+ cmd_cursor_goto(cmdline_row, 0);
msg_clr_eos();
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)
@@ -3519,13 +3528,15 @@ void gotocmdline(int clr)
return;
}
msg_start();
- if (cmdmsg_rl)
+ if (cmdmsg_rl) {
msg_col = Columns - 1;
- else
- msg_col = 0; /* always start in column 0 */
- if (clr) /* clear the bottom line(s) */
- msg_clr_eos(); /* will reset clear_cmdline */
- ui_cursor_goto(cmdline_row, 0);
+ } else {
+ msg_col = 0; // always start in column 0
+ }
+ if (clr) { // clear the bottom line(s)
+ msg_clr_eos(); // will reset clear_cmdline
+ }
+ cmd_cursor_goto(cmdline_row, 0);
}
/*
diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h
index e4021c033b..d9be53468f 100644
--- a/src/nvim/grid_defs.h
+++ b/src/nvim/grid_defs.h
@@ -43,6 +43,10 @@ typedef struct {
unsigned *line_offset;
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.
int Rows;
int Columns;
@@ -50,12 +54,17 @@ typedef struct {
// The state of the grid is valid. Otherwise it needs to be redrawn.
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
int row_offset;
int col_offset;
// whether the compositor should blend the grid with the background grid
bool blending;
+ bool focusable;
// state owned by the compositor.
int comp_row;
@@ -64,7 +73,7 @@ typedef struct {
bool comp_disabled;
} ScreenGrid;
-#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, 0, 0, false, 0, 0, \
- false, 0, 0, 0, false }
+#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, NULL, 0, 0, false, \
+ false, 0, 0, false, true, 0, 0, 0, false }
#endif // NVIM_GRID_DEFS_H
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index 3a61409dfe..f44cd5cdab 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -7,6 +7,7 @@
#include "nvim/highlight.h"
#include "nvim/highlight_defs.h"
#include "nvim/map.h"
+#include "nvim/message.h"
#include "nvim/popupmnu.h"
#include "nvim/screen.h"
#include "nvim/syntax.h"
@@ -161,6 +162,8 @@ int hl_get_ui_attr(int idx, int final_id, bool optional)
if (pum_drawn()) {
must_redraw_pum = true;
}
+ } else if (idx == HLF_MSG) {
+ msg_grid.blending = attrs.hl_blend > -1;
}
if (optional && !available) {
diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h
index afccf9e6f6..512d87fa34 100644
--- a/src/nvim/highlight_defs.h
+++ b/src/nvim/highlight_defs.h
@@ -93,6 +93,7 @@ typedef enum {
, HLF_INACTIVE // NormalNC: Normal text in non-current windows
, HLF_MSGSEP // message separator line
, HLF_NFLOAT // Floating window
+ , HLF_MSG // Message area
, HLF_COUNT // MUST be the last one
} hlf_T;
@@ -146,6 +147,7 @@ EXTERN const char *hlf_names[] INIT(= {
[HLF_INACTIVE] = "NormalNC",
[HLF_MSGSEP] = "MsgSeparator",
[HLF_NFLOAT] = "NormalFloat",
+ [HLF_MSG] = "MsgArea",
});
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 34774055c1..bb125df968 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -3522,11 +3522,9 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname,
}
xfree(name);
+
// pretend screen didn't scroll, need redraw anyway
- // TODO(bfredl): when doing the message grid refactor,
- // simplify this special case.
- msg_scrolled = 0;
- redraw_all_later(NOT_VALID);
+ msg_reset_scroll();
}
if (choice > 0) {
diff --git a/src/nvim/message.c b/src/nvim/message.c
index c8deaa590c..9e5c35d58b 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -35,7 +35,9 @@
#include "nvim/screen.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
+#include "nvim/highlight.h"
#include "nvim/ui.h"
+#include "nvim/ui_compositor.h"
#include "nvim/mouse.h"
#include "nvim/os/os.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
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
* 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)
{
int cw;
+ attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr);
msg_didout = true; // remember that line is not empty
cw = utf_ptr2cells(s);
@@ -1711,7 +1783,7 @@ static char_u *screen_puts_mbyte(char_u *s, int l, int attr)
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) {
msg_col -= cw;
if (msg_col == 0) {
@@ -1900,6 +1972,8 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr,
return;
}
+ msg_grid_validate();
+
cmdline_was_last_drawn = redrawing_cmdline;
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)
break;
- /* Scroll the screen up one line. */
- msg_scroll_up();
+ // Scroll the screen up one line.
+ bool has_last_char = (*s >= ' ' && !cmdmsg_rl);
+ msg_scroll_up(!has_last_char);
msg_row = Rows - 2;
if (msg_col >= Columns) /* can happen after screen resize */
msg_col = Columns - 1;
// Display char in last column before showing more-prompt.
- if (*s >= ' ' && !cmdmsg_rl) {
+ if (has_last_char) {
if (maxlen >= 0) {
// Avoid including composing chars after the end.
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;
}
+ // 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) {
// Store text for scrolling back.
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;
}
+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.
*/
-void msg_scroll_up(void)
+void msg_scroll_up(bool may_throttle)
+{
+ if (may_throttle && msg_dothrottle()) {
+ msg_grid.throttled = true;
+ }
+ msg_did_scroll = true;
+ if (msg_use_msgsep()) {
+ if (msg_grid_pos > 0) {
+ msg_grid_set_pos(msg_grid_pos-1, true);
+ } 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;
+ }
+ } else {
+ grid_del_lines(&msg_grid_adj, 0, 1, Rows, 0, Columns);
+ }
+
+ grid_fill(&msg_grid_adj, Rows-1, Rows, 0, Columns, ' ', ' ',
+ 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 (!msg_did_scroll) {
- ui_call_win_scroll_over_start();
- msg_did_scroll = true;
+ if (ui_has(kUIMessages)) {
+ msg_ext_clear(true);
+ return;
}
- if (dy_flags & DY_MSGSEP) {
- if (msg_scrolled == 0) {
- grid_fill(&default_grid, Rows-p_ch-1, Rows-p_ch, 0, (int)Columns,
- curwin->w_p_fcs_chars.msgsep, curwin->w_p_fcs_chars.msgsep,
- HL_ATTR(HLF_MSGSEP));
+ // 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);
+ }
}
- int nscroll = MIN(msg_scrollsize()+1, Rows);
- grid_del_lines(&default_grid, Rows-nscroll, 1, Rows, 0, Columns);
} else {
- grid_del_lines(&default_grid, 0, 1, (int)Rows, 0, Columns);
+ redraw_all_later(NOT_VALID);
}
- // TODO(bfredl): when msgsep display is properly batched, this fill should be
- // eliminated.
- grid_fill(&default_grid, Rows-1, Rows, 0, (int)Columns, ' ', ' ', 0);
+ msg_scrolled = 0;
+ msg_scroll_at_flush = 0;
}
/*
@@ -2285,6 +2446,11 @@ static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp)
break;
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;
}
@@ -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)
{
+ attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr);
// Output postponed text.
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);
msg_col += *t_col;
*t_col = 0;
@@ -2514,14 +2681,14 @@ static int do_more_prompt(int typed_char)
}
if (toscroll == -1) {
- grid_ins_lines(&default_grid, 0, 1, (int)Rows, 0, (int)Columns);
- grid_fill(&default_grid, 0, 1, 0, (int)Columns, ' ', ' ', 0);
+ grid_ins_lines(&msg_grid_adj, 0, 1, Rows, 0, Columns);
// display line at top
(void)disp_sb_line(0, mp);
} else {
- /* redisplay all lines */
- screenclear();
- for (i = 0; mp != NULL && i < Rows - 1; ++i) {
+ // redisplay all lines
+ // TODO(bfredl): this case is not optimized (though only concerns
+ // event fragmentization, not unnecessary scroll events).
+ for (i = 0; mp != NULL && i < Rows - 1; i++) {
mp = disp_sb_line(i, mp);
++msg_scrolled;
}
@@ -2531,20 +2698,24 @@ static int do_more_prompt(int typed_char)
} else {
/* First display any text that we scrolled back. */
while (toscroll > 0 && mp_last != NULL) {
- /* scroll up, display line at bottom */
- msg_scroll_up();
+ if (msg_dothrottle() && !msg_grid.throttled) {
+ // 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();
- grid_fill(&default_grid, (int)Rows - 2, (int)Rows - 1, 0,
- (int)Columns, ' ', ' ', 0);
- mp_last = disp_sb_line((int)Rows - 2, mp_last);
- --toscroll;
+ mp_last = disp_sb_line(Rows - 2, mp_last);
+ toscroll--;
}
}
if (toscroll <= 0) {
// displayed the requested text, more prompt again
- grid_fill(&default_grid, (int)Rows - 1, (int)Rows, 0,
- (int)Columns, ' ', ' ', 0);
+ grid_fill(&msg_grid_adj, Rows - 1, Rows, 0, Columns, ' ', ' ',
+ HL_ATTR(HLF_MSG));
msg_moremsg(false);
continue;
}
@@ -2557,8 +2728,11 @@ static int do_more_prompt(int typed_char)
}
// clear the --more-- message
- grid_fill(&default_grid, (int)Rows - 1, (int)Rows, 0, (int)Columns, ' ', ' ',
- 0);
+ grid_fill(&msg_grid_adj, Rows - 1, Rows, 0, Columns, ' ', ' ', 0);
+ redraw_cmdline = true;
+ clear_cmdline = false;
+ mode_displayed = false;
+
State = oldState;
setmouse();
if (quit_more) {
@@ -2607,8 +2781,9 @@ void mch_msg(char *str)
*/
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
- 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 (--msg_col == 0) {
msg_col = Columns;
@@ -2628,11 +2803,11 @@ void msg_moremsg(int full)
char_u *s = (char_u *)_("-- More --");
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) {
- 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 "),
- (int)Rows - 1, vim_strsize(s), attr);
+ Rows - 1, vim_strsize(s), attr);
}
}
@@ -2685,12 +2860,24 @@ void msg_clr_eos_force(void)
return;
}
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;
+
+ if (msg_grid.chars && msg_row < msg_grid_pos) {
+ // TODO(bfredl): ugly, this state should already been validated at this
+ // point. But msg_clr_eos() is called in a lot of places.
+ 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));
- grid_fill(&default_grid, msg_row, msg_row + 1, msg_startcol, msg_endcol, ' ',
- ' ', 0);
- grid_fill(&default_grid, msg_row + 1, (int)Rows, 0, (int)Columns, ' ', ' ',
- 0);
+ 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
// improvements. Caller should call ui_flush before waiting on user input or
// CPU busywork.
- ui_flush(); // calls msg_ext_ui_flush
+ // ui_flush(); // calls msg_ext_ui_flush
+ msg_ext_ui_flush();
return true;
}
diff --git a/src/nvim/message.h b/src/nvim/message.h
index 914c10af43..1703384bb5 100644
--- a/src/nvim/message.h
+++ b/src/nvim/message.h
@@ -7,6 +7,7 @@
#include "nvim/macros.h"
#include "nvim/types.h"
+#include "nvim/grid_defs.h"
/*
* 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 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
# include "message.h.generated.h"
#endif
diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c
index 6d1a517ce8..d0aa0653cb 100644
--- a/src/nvim/mouse.c
+++ b/src/nvim/mouse.c
@@ -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);
if (wp_grid) {
return wp_grid;
+ } else if (*gridp > 1) {
+ return NULL;
}
+
frame_T *fp;
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)
{
- 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);
if (wp && wp->w_grid.chars
&& !(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) {
ScreenGrid *grid = ui_comp_mouse_focus(*rowp, *colp);
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (&wp->w_grid != grid || !wp->w_float_config.focusable) {
+ if (&wp->w_grid != grid) {
continue;
}
*gridp = grid->handle;
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 819ca83d27..e6a4c38c59 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -3455,16 +3455,18 @@ static void display_showcmd(void)
return;
}
+ msg_grid_validate();
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) {
- 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
- grid_puts(&default_grid, (char_u *)" " + len, showcmd_row,
- sc_col + len, 0);
+ grid_puts(&msg_grid_adj, (char_u *)" " + len, showcmd_row,
+ sc_col + len, HL_ATTR(HLF_MSG));
grid_puts_line_flush(false);
}
diff --git a/src/nvim/option.c b/src/nvim/option.c
index fae4507f1e..04349414a2 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -2989,6 +2989,7 @@ ambw_end:
errmsg = e_invarg;
} else {
(void)init_chartab();
+ msg_grid_validate();
}
} else if (varp == &p_ead) { // 'eadirection'
if (check_opt_strings(p_ead, p_ead_values, false) != OK) {
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index fa6ebc70e5..f9cddf5550 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -399,6 +399,7 @@ static char *(p_dy_values[]) = { "lastline", "truncate", "uhex", "msgsep",
#define DY_LASTLINE 0x001
#define DY_TRUNCATE 0x002
#define DY_UHEX 0x004
+// code should use msg_use_msgsep() to check if msgsep is active
#define DY_MSGSEP 0x008
EXTERN int p_ed; // 'edcompatible'
EXTERN int p_emoji; // 'emoji'
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 1b2143c419..e182c62c71 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -152,6 +152,7 @@ static bool send_grid_resize = false;
static bool conceal_cursor_used = false;
static bool redraw_popupmenu = false;
+static bool msg_grid_invalid = 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
// separate bookkeeping for now.
if (msg_did_scroll) {
- ui_call_win_scroll_over_reset();
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 (msg_scrolled) {
+ if (msg_scrolled || msg_grid_invalid) {
clear_cmdline = true;
- if (dy_flags & DY_MSGSEP) {
- int valid = MAX(Rows - msg_scrollsize(), 0);
- if (valid == 0) {
- redraw_tabline = true;
- }
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (W_ENDROW(wp) > valid) {
- wp->w_redr_type = NOT_VALID;
- wp->w_lines_valid = 0;
- }
- if (W_ENDROW(wp) + wp->w_status_height > valid) {
- wp->w_redr_status = true;
+ int valid = MAX(Rows - msg_scrollsize(), 0);
+ if (msg_grid.chars) {
+ // non-displayed part of msg_grid is considered invalid.
+ for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.Rows); i++) {
+ grid_clear_line(&msg_grid, msg_grid.line_offset[i],
+ (int)msg_grid.Columns, false);
+ }
+ }
+ if (msg_use_msgsep()) {
+ msg_grid.throttled = false;
+ // CLEAR is already handled
+ if (type == NOT_VALID && !ui_has(kUIMultigrid) && msg_scrolled) {
+ ui_comp_set_screen_valid(false);
+ for (int i = valid; i < Rows-p_ch; i++) {
+ grid_clear_line(&default_grid, default_grid.line_offset[i],
+ Columns, false);
}
}
+ msg_grid_set_pos(Rows-p_ch, false);
+ msg_grid_invalid = false;
} else if (msg_scrolled > Rows - 5) { // clearing is faster
type = CLEAR;
} else if (type != CLEAR) {
@@ -368,12 +379,10 @@ int update_screen(int type)
redraw_tabline = TRUE;
}
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();
msg_ext_check_clear();
@@ -394,6 +403,11 @@ int update_screen(int type)
grid_invalidate(&default_grid);
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);
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)
{
if (!(*grid)->chars && *grid != &default_grid) {
- *row_off += (*grid)->row_offset;
- *col_off += (*grid)->col_offset;
- *grid = &default_grid;
+ *row_off += (*grid)->row_offset;
+ *col_off += (*grid)->col_offset;
+ 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
* no room, scroll the screen one line up. */
if (cmdline_row == Rows - 1) {
- msg_scroll_up();
+ msg_scroll_up(false);
msg_scrolled++;
} else {
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) {
*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);
}
@@ -5350,6 +5373,8 @@ static int put_dirty_last = 0;
/// another line.
void grid_puts_line_start(ScreenGrid *grid, int row)
{
+ int col = 0; // unused
+ screen_adjust_grid(&grid, &row, &col);
assert(put_dirty_row == -1);
put_dirty_row = row;
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);
// 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
|| row >= grid->Rows || row < 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,
MIN(put_dirty_last, put_dirty_grid->Columns-1));
}
- ui_line(put_dirty_grid, put_dirty_row, put_dirty_first, put_dirty_last,
- put_dirty_last, 0, false);
+ if (!put_dirty_grid->throttled) {
+ 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_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) {
put_dirty_first = MIN(put_dirty_first, dirty_first);
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 {
int last = c2 != ' ' ? dirty_last : dirty_first + (c1 != ' ');
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) {
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
// compositor to not redraw them yet
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 */
@@ -6217,12 +6250,17 @@ void screenclear(void)
msg_scrolled = 0; // can't scroll back
msg_didany = 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
/// are cleared.
-static void grid_clear_line(ScreenGrid *grid, unsigned off, int width,
- bool valid)
+void grid_clear_line(ScreenGrid *grid, unsigned off, int width, bool valid)
{
for (int col = 0; col < width; 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;
}
@@ -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;
}
@@ -6440,6 +6482,8 @@ int showmode(void)
// don't make non-flushed message part of the showmode
msg_ext_ui_flush();
+ msg_grid_validate();
+
do_mode = ((p_smd && msg_silent == 0)
&& ((State & TERM_FOCUS)
|| (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);
- i = redraw_cmdline;
- grid_fill(&default_grid, row, row + 1,
+ ScreenGrid *grid = part_of_status ? &default_grid : &msg_grid_adj;
+ grid_puts(grid, buffer, row, this_ru_col + off, attr);
+ grid_fill(grid, row, row + 1,
this_ru_col + off + (int)STRLEN(buffer), off + width, fillchar,
fillchar, attr);
- // don't redraw the cmdline because of showing the ruler
- redraw_cmdline = i;
}
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
|| exmode_active) {
screenalloc();
+ if (msg_grid.chars) {
+ msg_grid_validate();
+ }
+ // TODO(bfredl): sometimes messes up the output. Implement clear+redraw
+ // also for the pager? (or: what if the pager was just a modal window?)
+ ui_comp_set_screen_valid(true);
repeat_message();
} else {
if (curwin->w_p_scb)
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index 02b72be0ae..4b9e84745a 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -7533,6 +7533,9 @@ void highlight_changed(void)
hlf == (int)HLF_INACTIVE);
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]),
highlight_attr[hlf]);
highlight_attr_last[hlf] = highlight_attr[hlf];
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 5d191314ba..94fae0a774 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -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,
int clearattr, bool wrap)
{
+ assert(0 <= row && row < grid->Rows);
LineFlags flags = wrap ? kLineFlagWrap : 0;
if (startcol == -1) {
startcol = 0;
@@ -404,6 +405,7 @@ void ui_flush(void)
cmdline_ui_flush();
win_ui_flush_positions();
msg_ext_ui_flush();
+ msg_scroll_flush();
if (pending_cursor_update) {
ui_call_grid_cursor_goto(cursor_grid_handle, cursor_row, cursor_col);
diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c
index 9517b362af..163eadbc95 100644
--- a/src/nvim/ui_compositor.c
+++ b/src/nvim/ui_compositor.c
@@ -19,6 +19,7 @@
#include "nvim/ui.h"
#include "nvim/highlight.h"
#include "nvim/memory.h"
+#include "nvim/message.h"
#include "nvim/popupmnu.h"
#include "nvim/ui_compositor.h"
#include "nvim/ugrid.h"
@@ -46,8 +47,11 @@ static int chk_width = 0, chk_height = 0;
static ScreenGrid *curgrid;
static bool valid_screen = true;
-static bool msg_scroll_mode = false;
-static int msg_first_invalid = 0;
+static int msg_current_row = INT_MAX;
+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;
@@ -63,8 +67,7 @@ void ui_comp_init(void)
compositor->grid_scroll = ui_comp_grid_scroll;
compositor->grid_cursor_goto = ui_comp_grid_cursor_goto;
compositor->raw_line = ui_comp_raw_line;
- compositor->win_scroll_over_start = ui_comp_win_scroll_over_start;
- compositor->win_scroll_over_reset = ui_comp_win_scroll_over_reset;
+ compositor->msg_set_pos = ui_comp_msg_set_pos;
// Be unopinionated: will be attached together with a "real" ui anyway
compositor->width = INT_MAX;
@@ -158,8 +161,19 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width,
}
#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);
- 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--;
}
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)
{
- // TODO(bfredl): click "through" unfocusable grids?
for (ssize_t i = (ssize_t)kv_size(layers)-1; i > 0; 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) {
return grid;
}
@@ -337,10 +351,28 @@ static void compose_line(Integer row, Integer startcol, Integer endcol,
assert(until > col);
assert(until <= default_grid.Columns);
size_t n = (size_t)(until-col);
- 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 (row == msg_sep_row && grid->comp_index <= msg_grid.comp_index) {
+ 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'
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) {
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;
}
@@ -500,9 +524,12 @@ static void ui_comp_raw_line(UI *ui, Integer grid, Integer row,
endcol = MIN(endcol, clearcol);
}
- if (flags & kLineFlagInvalid
- || kv_size(layers) > curgrid->comp_index+1
- || curgrid->blending) {
+ bool above_msg = (kv_A(layers, kv_size(layers)-1) == &msg_grid
+ && row < msg_current_row-(msg_was_scrolled?1:0));
+ 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_line(row, startcol, clearcol, flags);
} 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)
{
valid_screen = valid;
+ if (!valid) {
+ msg_sep_row = -1;
+ }
}
-// TODO(bfredl): These events are somewhat of a hack. multiline messages
-// should later on be a separate grid, then this would just be ordinary
-// ui_comp_put_grid and ui_comp_remove_grid calls.
-static void ui_comp_win_scroll_over_start(UI *ui)
+static void ui_comp_msg_set_pos(UI *ui, Integer grid, Integer row,
+ Boolean scrolled, String sep_char)
{
- msg_scroll_mode = true;
- msg_first_invalid = ui->height;
-}
+ msg_grid.comp_row = (int)row;
+ 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)
-{
- msg_scroll_mode = false;
- for (size_t i = 1; i < kv_size(layers); i++) {
- ScreenGrid *grid = kv_A(layers, i);
- if (grid->comp_row+grid->Rows > msg_first_invalid) {
- compose_area(msg_first_invalid, grid->comp_row+grid->Rows,
- grid->comp_col, grid->comp_col+grid->Columns);
+ if (row > msg_current_row && ui_comp_should_draw()) {
+ compose_area(MAX(msg_current_row-1, 0), row, 0, default_grid.Columns);
+ } else if (row < msg_current_row && ui_comp_should_draw()
+ && msg_current_row < Rows) {
+ int delta = msg_current_row - (int)row;
+ if (msg_grid.blending) {
+ int first_row = MAX((int)row-(scrolled?1:0), 0);
+ 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,
@@ -554,7 +598,8 @@ static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top,
left += curgrid->comp_col;
right += curgrid->comp_col;
bool covered = kv_size(layers) > curgrid->comp_index+1 || curgrid->blending;
- if (!msg_scroll_mode && covered) {
+
+ if (covered) {
// TODO(bfredl):
// 1. check if rectangles actually overlap
// 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);
} else {
- msg_first_invalid = MIN(msg_first_invalid, (int)top);
ui_composed_call_grid_scroll(1, top, bot, left, right, rows, cols);
if (rdb_flags & RDB_COMPOSITOR) {
debug_delay(2);
diff --git a/src/nvim/window.c b/src/nvim/window.c
index b5d34bbbdb..0e8cfcedf5 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -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,
wp->w_width, valid, on_top);
ui_check_cursor_grid(wp->w_grid.handle);
+ wp->w_grid.focusable = wp->w_float_config.focusable;
if (!valid) {
wp->w_grid.valid = false;
redraw_win_later(wp, NOT_VALID);
@@ -5359,6 +5360,9 @@ void win_drag_status_line(win_T *dragwin, int offset)
}
row = win_comp_pos();
grid_fill(&default_grid, row, cmdline_row, 0, Columns, ' ', ' ', 0);
+ if (msg_grid.chars) {
+ clear_cmdline = true;
+ }
cmdline_row = row;
p_ch = Rows - cmdline_row;
if (p_ch < 1)