aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
20 files changed, 514 insertions, 171 deletions
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)