ui: implement ext_messages

Co-Author: Dongdong Zhou <dzhou121@gmail.com>
This commit is contained in:
Björn Linse 2017-10-31 16:46:02 +01:00
parent 3ff1228f78
commit 51fc54325c
20 changed files with 1109 additions and 64 deletions

View File

@ -32,6 +32,7 @@ a dictionary with these (optional) keys:
`ext_tabline` Externalize the tabline. |ui-tabline|
`ext_cmdline` Externalize the cmdline. |ui-cmdline|
`ext_wildmenu` Externalize the wildmenu. |ui-wildmenu|
`ext_messages` Externalize messages. |ui-messages|
`ext_linegrid` Use new revision of the grid events. |ui-linegrid|
`ext_multigrid` Use per-window grid based events. |ui-multigrid|
`ext_hlstate` Use detailed highlight state. |ui-hlstate|
@ -572,7 +573,7 @@ Only sent if `ext_tabline` option is set in |ui-options|
==============================================================================
Cmdline Events *ui-cmdline*
Only sent if `ext_cmdline` option is set in |ui-options|
Only sent if `ext_cmdline` option is set in |ui-options|.
["cmdline_show", content, pos, firstc, prompt, indent, level]
content: List of [attrs, string]
@ -644,5 +645,70 @@ Only sent if `ext_wildmenu` option is set in |ui-options|
["wildmenu_hide"]
Hide the wildmenu.
==============================================================================
Message Events *ui-messages*
Only sent if `ext_messages` option is set in |ui-options|. This option implies
`ext_linegrid` and `ext_cmdline` also being set. |ui-linegrid| and |ui-cmdline| events
will thus also be sent.
This extension allows the UI to control the display of messages that otherwise
would have been displayed in the message/cmdline area in the bottom of the
screen.
Activating this extension means that Nvim will allocate no screen space for
the cmdline or messages, and 'cmdheight' will be set to zero. Attempting to
change 'cmdheight' will silently be ignored. |ui-cmdline| events will be used
to represent the state of the cmdline.
["msg_show", kind, content, replace_last]
Display a message to the user.
`kind` will be one of the following
`emsg`: Internal error message
`echo`: temporary message from plugin (|:echo|)
`echomsg`: ordinary message from plugin (|:echomsg|)
`echoerr`: error message from plugin (|:echoerr|)
`return_prompt`: |press-enter| prompt after a group of messages
`quickfix`: Quickfix navigation message
`kind` can also be the empty string. The message is then some internal
informative or warning message, that hasn't yet been assigned a kind.
New message kinds can be added in later versions; clients should
handle messages of an unknown kind just like empty kind.
`content` will be an array of `[attr_id, text_chunk]` tuples,
building up the message text of chunks of different highlights.
No extra spacing should be added between chunks, the `text_chunk` by
itself should contain any necessary whitespace. Messages can contain
line breaks `"\n"`.
`replace_last` controls how multiple messages should be displayed.
If `replace_last` is false, this message should be displayed together
with all previous messages that are still visible. If `replace_last`
is true, this message should replace the message in the most recent
`msg_show` call, but any other visible message should still remain.
["msg_clear"]
Clear all messages currently displayed by "msg_show". (Messages sent
by other "msg_" events below will not be affected).
["msg_showmode", content]
Shows 'showmode' and |recording| messages. `content` has the same
format as in "msg_show". This event is sent with empty `content` to
hide the last message.
["msg_showcmd", content]
Shows 'showcmd' messages. `content` has the same format as in "msg_show".
This event is sent with empty `content` to hide the last message.
["msg_ruler", content]
Used to display 'ruler' when there is no space for the ruler in a
statusline. `content` has the same format as in "msg_show". This event is
sent with empty `content` to hide the last message.
["msg_history_show", entries]
Sent when |:messages| command is invoked. History is sent as a list of
entries, where each entry is a `[kind, content]` tuple.
==============================================================================
vim:tw=78:ts=8:noet:ft=help:norl:

View File

@ -712,6 +712,12 @@ String cbuf_to_string(const char *buf, size_t size)
};
}
String cstrn_to_string(const char *str, size_t maxsize)
FUNC_ATTR_NONNULL_ALL
{
return cbuf_to_string(str, strnlen(str, maxsize));
}
/// Creates a String using the given C string. Unlike
/// cstr_to_string this function DOES NOT copy the C string.
///
@ -726,6 +732,18 @@ String cstr_as_string(char *str) FUNC_ATTR_PURE
return (String){ .data = str, .size = strlen(str) };
}
/// Return the owned memory of a ga as a String
///
/// Reinitializes the ga to a valid empty state.
String ga_take_string(garray_T *ga)
{
String str = { .data = (char *)ga->ga_data, .size = (size_t)ga->ga_len };
ga->ga_data = NULL;
ga->ga_len = 0;
ga->ga_maxlen = 0;
return str;
}
/// Collects `n` buffer lines into array `l`, optionally replacing newlines
/// with NUL.
///

View File

@ -132,6 +132,13 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
ui->ui_ext[kUILinegrid] = true;
}
if (ui->ui_ext[kUIMessages]) {
// This uses attribute indicies, so ext_linegrid is needed.
ui->ui_ext[kUILinegrid] = true;
// Cmdline uses the messages area, so it should be externalized too.
ui->ui_ext[kUICmdline] = true;
}
UIData *data = xmalloc(sizeof(UIData));
data->channel_id = channel_id;
data->buffer = (Array)ARRAY_DICT_INIT;

View File

@ -141,4 +141,17 @@ void wildmenu_select(Integer selected)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void wildmenu_hide(void)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void msg_show(String kind, Array content, Boolean replace_last)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void msg_clear(void)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void msg_showcmd(Array content)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void msg_showmode(Array content)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void msg_ruler(Array content)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void msg_history_show(Array entries)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
#endif // NVIM_API_UI_EVENTS_IN_H

View File

@ -19597,7 +19597,10 @@ void ex_echo(exarg_T *eap)
msg_puts_attr(" ", echo_attr);
}
char *tofree = encode_tv2echo(&rettv, NULL);
msg_multiline_attr(tofree, echo_attr);
if (*tofree != NUL) {
msg_ext_set_kind("echo");
msg_multiline_attr(tofree, echo_attr);
}
xfree(tofree);
}
tv_clear(&rettv);
@ -19689,11 +19692,13 @@ void ex_execute(exarg_T *eap)
}
if (eap->cmdidx == CMD_echomsg) {
msg_ext_set_kind("echomsg");
MSG_ATTR(ga.ga_data, echo_attr);
ui_flush();
} else if (eap->cmdidx == CMD_echoerr) {
/* We don't want to abort following commands, restore did_emsg. */
save_did_emsg = did_emsg;
msg_ext_set_kind("echoerr");
EMSG((char_u *)ga.ga_data);
if (!force_abort)
did_emsg = save_did_emsg;

View File

@ -308,6 +308,9 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
gotocmdline(true);
redrawcmdprompt(); // draw prompt or indent
set_cmdspos();
if (!msg_scroll) {
msg_ext_clear(false);
}
}
s->xpc.xp_context = EXPAND_NOTHING;
s->xpc.xp_backslash = XP_BS_NONE;
@ -496,6 +499,12 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
if (ui_has(kUICmdline)) {
ui_call_cmdline_hide(ccline.level);
if (msg_ext_is_visible()) {
msg_ext_did_cmdline = true;
if (must_redraw < VALID) {
must_redraw = VALID;
}
}
}
cmdline_level--;
@ -3613,7 +3622,7 @@ nextwild (
return FAIL;
}
if (!ui_has(kUIWildmenu)) {
if (!(ui_has(kUICmdline) || ui_has(kUIWildmenu))) {
MSG_PUTS("..."); // show that we are busy
ui_flush();
}

View File

@ -18,6 +18,7 @@ typedef struct growarray {
} garray_T;
#define GA_EMPTY_INIT_VALUE { 0, 0, 0, 1, NULL }
#define GA_INIT(itemsize, growsize) { 0, 0, (itemsize), (growsize), NULL }
#define GA_EMPTY(ga_ptr) ((ga_ptr)->ga_len <= 0)

View File

@ -34,11 +34,13 @@
#include "nvim/regexp.h"
#include "nvim/screen.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/ui.h"
#include "nvim/mouse.h"
#include "nvim/os/os.h"
#include "nvim/os/input.h"
#include "nvim/os/time.h"
#include "nvim/api/private/helpers.h"
/*
* To be able to scroll back at the "more" and "hit-enter" prompts we need to
@ -108,6 +110,19 @@ static int verbose_did_open = FALSE;
* This is an allocated string or NULL when not used.
*/
// Extended msg state, currently used for external UIs with ext_messages
static const char *msg_ext_kind = NULL;
static Array msg_ext_chunks = ARRAY_DICT_INIT;
static garray_T msg_ext_last_chunk = GA_INIT(sizeof(char), 40);
static sattr_T msg_ext_last_attr = -1;
static bool msg_ext_overwrite = false; ///< will overwrite last message
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;
/*
* msg(s) - displays the string 's' on the status line
* When terminal not initialized (yet) mch_errmsg(..) is used.
@ -256,7 +271,8 @@ msg_strtrunc (
/* May truncate message to avoid a hit-return prompt */
if ((!msg_scroll && !need_wait_return && shortmess(SHM_TRUNCALL)
&& !exmode_active && msg_silent == 0) || force) {
&& !exmode_active && msg_silent == 0 && !ui_has(kUIMessages))
|| force) {
len = vim_strsize(s);
if (msg_scrolled != 0)
/* Use all the columns. */
@ -594,6 +610,9 @@ static bool emsg_multiline(const char *s, bool multiline)
} // wait_return has reset need_wait_return
// and a redraw is expected because
// msg_scrolled is non-zero
if (msg_ext_kind == NULL) {
msg_ext_set_kind("emsg");
}
/*
* Display name and line number for the source of the error.
@ -802,6 +821,7 @@ static void add_msg_hist(const char *s, int len, int attr, bool multiline)
p->next = NULL;
p->attr = attr;
p->multiline = multiline;
p->kind = msg_ext_kind;
if (last_msg_hist != NULL) {
last_msg_hist->next = p;
}
@ -856,7 +876,6 @@ void ex_messages(void *const eap_p)
return;
}
msg_hist_off = true;
p = first_msg_hist;
@ -874,13 +893,31 @@ void ex_messages(void *const eap_p)
}
// Display what was not skipped.
for (; p != NULL && !got_int; p = p->next) {
if (p->msg != NULL) {
msg_attr_keep(p->msg, p->attr, false, p->multiline);
if (ui_has(kUIMessages)) {
Array entries = ARRAY_DICT_INIT;
for (; p != NULL; p = p->next) {
if (p->msg != NULL && p->msg[0] != NUL) {
Array entry = ARRAY_DICT_INIT;
ADD(entry, STRING_OBJ(cstr_to_string(p->kind)));
Array content_entry = ARRAY_DICT_INIT;
ADD(content_entry, INTEGER_OBJ(p->attr));
ADD(content_entry, STRING_OBJ(cstr_to_string((char *)(p->msg))));
Array content = ARRAY_DICT_INIT;
ADD(content, ARRAY_OBJ(content_entry));
ADD(entry, ARRAY_OBJ(content));
ADD(entries, ARRAY_OBJ(entry));
}
}
ui_call_msg_history_show(entries);
} else {
msg_hist_off = true;
for (; p != NULL && !got_int; p = p->next) {
if (p->msg != NULL) {
msg_attr_keep(p->msg, p->attr, false, p->multiline);
}
}
msg_hist_off = false;
}
msg_hist_off = false;
}
/*
@ -1058,8 +1095,9 @@ void wait_return(int redraw)
if (c == ':' || c == '?' || c == '/') {
if (!exmode_active)
cmdline_row = msg_row;
skip_redraw = TRUE; /* skip redraw once */
do_redraw = FALSE;
skip_redraw = true; // skip redraw once
do_redraw = false;
msg_ext_keep_after_cmdline = true;
}
/*
@ -1084,9 +1122,13 @@ void wait_return(int redraw)
if (tmpState == SETWSIZE) { /* got resize event while in vgetc() */
ui_refresh();
} else if (!skip_redraw
&& (redraw == TRUE || (msg_scrolled != 0 && redraw != -1))) {
redraw_later(VALID);
} else if (!skip_redraw) {
if (redraw == true || (msg_scrolled != 0 && redraw != -1)) {
redraw_later(VALID);
}
if (ui_has(kUIMessages)) {
msg_ext_clear(true);
}
}
}
@ -1100,8 +1142,10 @@ static void hit_return_msg(void)
p_more = FALSE; /* don't want see this message when scrolling back */
if (msg_didout) /* start on a new line */
msg_putchar('\n');
if (got_int)
msg_ext_set_kind("return_prompt");
if (got_int) {
MSG_PUTS(_("Interrupt: "));
}
MSG_PUTS_ATTR(_("Press ENTER or type command to continue"), HL_ATTR(HLF_R));
if (!msg_use_printf()) {
@ -1124,6 +1168,17 @@ void set_keep_msg(char_u *s, int attr)
keep_msg_attr = attr;
}
void msg_ext_set_kind(const char *msg_kind)
{
// Don't change the label of an existing batch:
msg_ext_ui_flush();
// TODO(bfredl): would be nice to avoid dynamic scoping, but that would
// need refactoring the msg_ interface to not be "please pretend nvim is
// a terminal for a moment"
msg_ext_kind = msg_kind;
}
/*
* Prepare for outputting characters in the command line.
*/
@ -1160,6 +1215,14 @@ void msg_start(void)
msg_didout = FALSE; /* no output on current line yet */
}
if (ui_has(kUIMessages)) {
msg_ext_ui_flush();
if (!msg_scroll && msg_ext_visible) {
// Will overwrite last message.
msg_ext_overwrite = true;
}
}
// When redirecting, may need to start a new line.
if (!did_return) {
redir_write("\n", 1);
@ -1727,7 +1790,18 @@ void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr)
// wait-return prompt later. Needed when scrolling, resetting
// need_wait_return after some prompt, and then outputting something
// without scrolling
if (msg_scrolled != 0 && !msg_scrolled_ign) {
bool overflow = false;
if (ui_has(kUIMessages)) {
int count = msg_ext_visible + (msg_ext_overwrite ? 0 : 1);
// TODO(bfredl): possible extension point, let external UI control this
if (count > 1) {
overflow = true;
}
} else {
overflow = msg_scrolled != 0;
}
if (overflow && !msg_scrolled_ign) {
need_wait_return = true;
}
msg_didany = true; // remember that something was outputted
@ -1765,6 +1839,20 @@ void msg_printf_attr(const int attr, const char *const fmt, ...)
msg_puts_attr_len(msgbuf, (ptrdiff_t)len, attr);
}
static void msg_ext_emit_chunk(void)
{
// Color was changed or a message flushed, end current chunk.
if (msg_ext_last_attr == -1) {
return; // no chunk
}
Array chunk = ARRAY_DICT_INIT;
ADD(chunk, INTEGER_OBJ(msg_ext_last_attr));
msg_ext_last_attr = -1;
String text = ga_take_string(&msg_ext_last_chunk);
ADD(chunk, STRING_OBJ(text));
ADD(msg_ext_chunks, ARRAY_OBJ(chunk));
}
/*
* The display part of msg_puts_attr_len().
* May be called recursively to display scroll-back text.
@ -1783,6 +1871,18 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr,
int did_last_char;
did_wait_return = false;
if (ui_has(kUIMessages)) {
if (attr != msg_ext_last_attr) {
msg_ext_emit_chunk();
msg_ext_last_attr = attr;
}
// Concat pieces with the same highlight
ga_concat_len(&msg_ext_last_chunk, (char *)str,
strnlen((char *)str, maxlen));
return;
}
while ((maxlen < 0 || (int)(s - str) < maxlen) && *s != NUL) {
// We are at the end of the screen line when:
// - When outputting a newline.
@ -2607,6 +2707,9 @@ void msg_clr_eos(void)
*/
void msg_clr_eos_force(void)
{
if (ui_has(kUIMessages)) {
return;
}
int msg_startcol = (cmdmsg_rl) ? 0 : msg_col;
int msg_endcol = (cmdmsg_rl) ? msg_col + 1 : (int)Columns;
@ -2643,8 +2746,66 @@ int msg_end(void)
wait_return(FALSE);
return FALSE;
}
ui_flush();
return TRUE;
// @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
return true;
}
void msg_ext_ui_flush(void)
{
if (!ui_has(kUIMessages)) {
return;
}
msg_ext_emit_chunk();
if (msg_ext_chunks.size > 0) {
ui_call_msg_show(cstr_to_string(msg_ext_kind),
msg_ext_chunks, msg_ext_overwrite);
if (!msg_ext_overwrite) {
msg_ext_visible++;
}
msg_ext_kind = NULL;
msg_ext_chunks = (Array)ARRAY_DICT_INIT;
msg_ext_overwrite = false;
}
}
void msg_ext_flush_showmode(void)
{
// Showmode messages doesn't interrupt normal message flow, so we use
// separate event. Still reuse the same chunking logic, for simplicity.
msg_ext_emit_chunk();
ui_call_msg_showmode(msg_ext_chunks);
msg_ext_chunks = (Array)ARRAY_DICT_INIT;
}
void msg_ext_clear(bool force)
{
if (msg_ext_visible && (!msg_ext_keep_after_cmdline || force)) {
ui_call_msg_clear();
msg_ext_visible = 0;
msg_ext_overwrite = false; // nothing to overwrite
}
// Only keep once.
msg_ext_keep_after_cmdline = false;
}
void msg_ext_check_prompt(void)
{
// Redraw after cmdline is expected to clear messages.
if (msg_ext_did_cmdline) {
msg_ext_clear(true);
msg_ext_did_cmdline = false;
}
}
bool msg_ext_is_visible(void)
{
return ui_has(kUIMessages) && msg_ext_visible > 0;
}
/*
@ -2653,6 +2814,9 @@ int msg_end(void)
*/
void msg_check(void)
{
if (ui_has(kUIMessages)) {
return;
}
if (msg_row == Rows - 1 && msg_col >= sc_col) {
need_wait_return = TRUE;
redraw_cmdline = TRUE;

View File

@ -5,6 +5,7 @@
#include <stdarg.h>
#include <stddef.h>
#include "nvim/macros.h"
#include "nvim/types.h"
/*
@ -77,6 +78,7 @@
typedef struct msg_hist {
struct msg_hist *next; ///< Next message.
char_u *msg; ///< Message text.
const char *kind; ///< Message kind (for msg_ext)
int attr; ///< Message highlighting.
bool multiline; ///< Multiline message.
} MessageHistoryEntry;
@ -86,6 +88,8 @@ extern MessageHistoryEntry *first_msg_hist;
/// Last message
extern MessageHistoryEntry *last_msg_hist;
EXTERN bool msg_ext_did_cmdline INIT(= false);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "message.h.generated.h"
#endif

View File

@ -61,6 +61,7 @@
#include "nvim/event/loop.h"
#include "nvim/os/time.h"
#include "nvim/os/input.h"
#include "nvim/api/private/helpers.h"
typedef struct normal_state {
VimState state;
@ -1258,8 +1259,9 @@ static void normal_redraw(NormalState *s)
maketitle();
}
// display message after redraw
if (keep_msg != NULL) {
// Display message after redraw. If an external message is still visible,
// it contains the kept message already.
if (keep_msg != NULL && !msg_ext_is_visible()) {
// msg_attr_keep() will set keep_msg to NULL, must free the string here.
// Don't reset keep_msg, msg_attr_keep() uses it to check for duplicates.
char *p = (char *)keep_msg;
@ -3317,7 +3319,8 @@ void clear_showcmd(void)
else
sprintf((char *)showcmd_buf, "%d-%d", chars, bytes);
}
showcmd_buf[SHOWCMD_COLS] = NUL; /* truncate */
int limit = ui_has(kUIMessages) ? SHOWCMD_BUFLEN-1 : SHOWCMD_COLS;
showcmd_buf[limit] = NUL; // truncate
showcmd_visual = true;
} else {
showcmd_buf[0] = NUL;
@ -3370,8 +3373,9 @@ bool add_to_showcmd(int c)
STRCPY(p, "<20>");
size_t old_len = STRLEN(showcmd_buf);
size_t extra_len = STRLEN(p);
if (old_len + extra_len > SHOWCMD_COLS) {
size_t overflow = old_len + extra_len - SHOWCMD_COLS;
size_t limit = ui_has(kUIMessages) ? SHOWCMD_BUFLEN-1 : SHOWCMD_COLS;
if (old_len + extra_len > limit) {
size_t overflow = old_len + extra_len - limit;
memmove(showcmd_buf, showcmd_buf + overflow, old_len - overflow + 1);
}
STRCAT(showcmd_buf, p);
@ -3432,13 +3436,24 @@ void pop_showcmd(void)
static void display_showcmd(void)
{
int len;
len = (int)STRLEN(showcmd_buf);
if (len == 0) {
showcmd_is_clear = true;
} else {
showcmd_is_clear = (len == 0);
if (ui_has(kUIMessages)) {
Array content = ARRAY_DICT_INIT;
if (len > 0) {
Array chunk = ARRAY_DICT_INIT;
// placeholder for future highlight support
ADD(chunk, INTEGER_OBJ(0));
ADD(chunk, STRING_OBJ(cstr_to_string((char *)showcmd_buf)));
ADD(content, ARRAY_OBJ(chunk));
}
ui_call_msg_showcmd(content);
return;
}
if (!showcmd_is_clear) {
grid_puts(&default_grid, showcmd_buf, (int)Rows - 1, sc_col, 0);
showcmd_is_clear = false;
}
/*

View File

@ -865,8 +865,12 @@ int do_record(int c)
* needs to be removed again to put it in a register. exec_reg then
* adds the escaping back later.
*/
Recording = FALSE;
MSG("");
Recording = false;
if (ui_has(kUIMessages)) {
showmode();
} else {
MSG("");
}
p = get_recorded();
if (p == NULL)
retval = FAIL;

View File

@ -4168,7 +4168,8 @@ static char *set_num_option(int opt_idx, char_u *varp, long value,
errmsg = e_positive;
}
} else if (pp == &p_ch) {
if (value < 1) {
int minval = ui_has(kUIMessages) ? 0 : 1;
if (value < minval) {
errmsg = e_positive;
}
} else if (pp == &p_tm) {
@ -4276,6 +4277,9 @@ static char *set_num_option(int opt_idx, char_u *varp, long value,
p_window = Rows - 1;
}
} else if (pp == &p_ch) {
if (ui_has(kUIMessages)) {
p_ch = 0;
}
if (p_ch > Rows - min_rows() + 1) {
p_ch = Rows - min_rows() + 1;
}

View File

@ -44,6 +44,7 @@
#include "nvim/window.h"
#include "nvim/os/os.h"
#include "nvim/os/input.h"
#include "nvim/api/private/helpers.h"
struct dir_stack_T {
@ -2155,6 +2156,7 @@ win_found:
} else if (!msg_scrolled && shortmess(SHM_OVERALL)) {
msg_scroll = false;
}
msg_ext_set_kind("quickfix");
msg_attr_keep(IObuff, 0, true, false);
msg_scroll = (int)i;
}

View File

@ -362,6 +362,8 @@ void update_screen(int type)
need_wait_return = FALSE;
}
msg_ext_check_prompt();
/* reset cmdline_row now (may have been changed temporarily) */
compute_cmdrow();
@ -483,8 +485,9 @@ void update_screen(int type)
/* Clear or redraw the command line. Done last, because scrolling may
* mess up the command line. */
if (clear_cmdline || redraw_cmdline)
if (clear_cmdline || redraw_cmdline) {
showmode();
}
/* May put up an introductory message when not editing a file */
if (!did_intro)
@ -5864,7 +5867,8 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col,
}
// TODO(bfredl): The relevant caller should do this
if (row == Rows - 1) { // overwritten the command line
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) {
@ -6394,6 +6398,13 @@ int showmode(void)
int nwr_save;
int sub_attr;
if (ui_has(kUIMessages) && clear_cmdline) {
msg_ext_clear(true);
}
// don't make non-flushed message part of the showmode
msg_ext_ui_flush();
do_mode = ((p_smd && msg_silent == 0)
&& ((State & TERM_FOCUS)
|| (State & INSERT)
@ -6436,9 +6447,14 @@ int showmode(void)
MSG_PUTS_ATTR("--", attr);
// CTRL-X in Insert mode
if (edit_submode != NULL && !shortmess(SHM_COMPLETIONMENU)) {
/* These messages can get long, avoid a wrap in a narrow
* window. Prefer showing edit_submode_extra. */
length = (Rows - msg_row) * Columns - 3;
// These messages can get long, avoid a wrap in a narrow window.
// Prefer showing edit_submode_extra. With external messages there
// is no imposed limit.
if (ui_has(kUIMessages)) {
length = INT_MAX;
} else {
length = (Rows - msg_row) * Columns - 3;
}
if (edit_submode_extra != NULL) {
length -= vim_strsize(edit_submode_extra);
}
@ -6540,6 +6556,9 @@ int showmode(void)
msg_clr_cmdline();
}
// NB: also handles clearing the showmode if it was emtpy or disabled
msg_ext_flush_showmode();
/* In Visual mode the size of the selected area must be redrawn. */
if (VIsual_active)
clear_showcmd();
@ -6581,11 +6600,13 @@ void unshowmode(bool force)
// Clear the mode message.
void clearmode(void)
{
msg_pos_mode();
if (Recording) {
recording_mode(HL_ATTR(HLF_CM));
}
msg_clr_eos();
msg_ext_ui_flush();
msg_pos_mode();
if (Recording) {
recording_mode(HL_ATTR(HLF_CM));
}
msg_clr_eos();
msg_ext_flush_showmode();
}
static void recording_mode(int attr)
@ -6894,9 +6915,12 @@ void showruler(int always)
static void win_redr_ruler(win_T *wp, int always)
{
/* If 'ruler' off or redrawing disabled, don't do anything */
if (!p_ru)
static bool did_show_ext_ruler = false;
// If 'ruler' off or redrawing disabled, don't do anything
if (!p_ru) {
return;
}
/*
* Check if cursor.lnum is valid, since win_redr_ruler() may be called
@ -6951,12 +6975,14 @@ static void win_redr_ruler(win_T *wp, int always)
int fillchar;
int attr;
int off;
bool part_of_status = false;
if (wp->w_status_height) {
row = W_ENDROW(wp);
fillchar = fillchar_status(&attr, wp);
off = wp->w_wincol;
width = wp->w_width;
part_of_status = true;
} else {
row = Rows - 1;
fillchar = ' ';
@ -7016,23 +7042,39 @@ static void win_redr_ruler(win_T *wp, int always)
}
get_rel_pos(wp, buffer + i, RULER_BUF_LEN - i);
}
// Truncate at window boundary.
o = 0;
for (i = 0; buffer[i] != NUL; i += utfc_ptr2len(buffer + i)) {
o += utf_ptr2cells(buffer + i);
if (this_ru_col + o > width) {
buffer[i] = NUL;
break;
if (ui_has(kUIMessages) && !part_of_status) {
Array content = ARRAY_DICT_INIT;
Array chunk = ARRAY_DICT_INIT;
ADD(chunk, INTEGER_OBJ(attr));
ADD(chunk, STRING_OBJ(cstr_to_string((char *)buffer)));
ADD(content, ARRAY_OBJ(chunk));
ui_call_msg_ruler(content);
did_show_ext_ruler = true;
} else {
if (did_show_ext_ruler) {
ui_call_msg_ruler((Array)ARRAY_DICT_INIT);
did_show_ext_ruler = false;
}
// Truncate at window boundary.
o = 0;
for (i = 0; buffer[i] != NUL; i += utfc_ptr2len(buffer + i)) {
o += utf_ptr2cells(buffer + i);
if (this_ru_col + o > width) {
buffer[i] = NUL;
break;
}
}
grid_puts(&default_grid, buffer, row, this_ru_col + off, attr);
i = redraw_cmdline;
grid_fill(&default_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;
}
grid_puts(&default_grid, buffer, row, this_ru_col + off, attr);
i = redraw_cmdline;
grid_fill(&default_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;
wp->w_ru_virtcol = wp->w_virtcol;
wp->w_ru_empty = empty_line;

View File

@ -200,6 +200,10 @@ void ui_refresh(void)
screen_resize(width, height);
p_lz = save_p_lz;
if (ext_widgets[kUIMessages]) {
p_ch = 0;
command_height();
}
ui_mode_info_set();
pending_mode_update = true;
ui_cursor_shape();
@ -380,6 +384,8 @@ void ui_flush(void)
{
cmdline_ui_flush();
win_ui_flush();
msg_ext_ui_flush();
if (pending_cursor_update) {
ui_call_grid_cursor_goto(cursor_grid_handle, cursor_row, cursor_col);
pending_cursor_update = false;

View File

@ -14,7 +14,8 @@ typedef enum {
kUIPopupmenu,
kUITabline,
kUIWildmenu,
#define kUIGlobalCount (kUIWildmenu+1)
kUIMessages,
#define kUIGlobalCount kUILinegrid
kUILinegrid,
kUIMultigrid,
kUIHlState,
@ -27,6 +28,7 @@ EXTERN const char *ui_ext_names[] INIT(= {
"ext_popupmenu",
"ext_tabline",
"ext_wildmenu",
"ext_messages",
"ext_linegrid",
"ext_multigrid",
"ext_hlstate",

View File

@ -1277,8 +1277,9 @@ describe('API', function()
ext_wildmenu = false,
ext_linegrid = screen._options.ext_linegrid or false,
ext_multigrid = false,
ext_hlstate=false,
ext_termcolors=false,
ext_hlstate = false,
ext_termcolors = false,
ext_messages = false,
height = 4,
rgb = true,
width = 20,

View File

@ -0,0 +1,632 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear, feed = helpers.clear, helpers.feed
local eval = helpers.eval
local eq = helpers.eq
local command = helpers.command
describe('ui/ext_messages', function()
local screen
before_each(function()
clear()
screen = Screen.new(25, 5)
screen:attach({rgb=true, ext_messages=true, ext_popupmenu=true})
screen:set_default_attr_ids({
[1] = {bold = true, foreground = Screen.colors.Blue1},
[2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
[3] = {bold = true},
[4] = {bold = true, foreground = Screen.colors.SeaGreen4},
[5] = {foreground = Screen.colors.Blue1},
[6] = {bold = true, reverse = true},
})
end)
it('supports :echoerr', function()
feed(':echoerr "raa"<cr>')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], messages={{
content = {{"raa", 2}},
kind = "echoerr",
}}}
-- cmdline in a later input cycle clears error message
feed(':')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], cmdline={{
firstc = ":",
content = {{ "" }},
pos = 0,
}}}
feed('echoerr "bork" | echoerr "fail"<cr>')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], messages={{
content = {{ "bork", 2 }},
kind = "echoerr"
}, {
content = {{ "fail", 2 }},
kind = "echoerr"
}, {
content = {{ "Press ENTER or type command to continue", 4 }},
kind = "return_prompt"
}}}
feed(':echoerr "extrafail"<cr>')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], messages={{
content = { { "bork", 2 } },
kind = "echoerr"
}, {
content = { { "fail", 2 } },
kind = "echoerr"
}, {
content = { { "extrafail", 2 } },
kind = "echoerr"
}, {
content = { { "Press ENTER or type command to continue", 4 } },
kind = "return_prompt"
}}}
feed('<cr>')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]]}
-- cmdline without interleaving wait/display keeps the error message
feed(':echoerr "problem" | let x = input("foo> ")<cr>')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], messages={{
content = {{ "problem", 2 }},
kind = "echoerr"
}}, cmdline={{
prompt = "foo> ",
content = {{ "" }},
pos = 0,
}}}
feed('solution<cr>')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]]}
eq('solution', eval('x'))
feed(":messages<cr>")
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], messages={
{kind="echoerr", content={{"raa", 2}}},
{kind="echoerr", content={{"bork", 2}}},
{kind="echoerr", content={{"fail", 2}}},
{kind="echoerr", content={{"extrafail", 2}}},
{kind="echoerr", content={{"problem", 2}}}
}}
end)
it('supports showmode', function()
command('imap <f2> <cmd>echomsg "stuff"<cr>')
feed('i')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], showmode={{"-- INSERT --", 3}}}
feed('alphpabet<cr>alphanum<cr>')
screen:expect{grid=[[
alphpabet |
alphanum |
^ |
{1:~ }|
{1:~ }|
]], showmode={ { "-- INSERT --", 3 } }}
feed('<c-x>')
screen:expect{grid=[[
alphpabet |
alphanum |
^ |
{1:~ }|
{1:~ }|
]], showmode={ { "-- ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)", 3 } }}
feed('<c-p>')
screen:expect{grid=[[
alphpabet |
alphanum |
alphanum^ |
{1:~ }|
{1:~ }|
]], popupmenu={
anchor = { 2, 0 },
items = { { "alphpabet", "", "", "" }, { "alphanum", "", "", "" } },
pos = 1
}, showmode={ { "-- Keyword Local completion (^N^P) ", 3 }, { "match 1 of 2", 4 } }}
-- echomsg and showmode don't overwrite each other, this is the same
-- as the TUI behavior with cmdheight=2 or larger.
feed('<f2>')
screen:expect{grid=[[
alphpabet |
alphanum |
alphanum^ |
{1:~ }|
{1:~ }|
]], popupmenu={
anchor = { 2, 0 },
items = { { "alphpabet", "", "", "" }, { "alphanum", "", "", "" } },
pos = 1
}, messages={ {
content = { { "stuff" } },
kind = "echomsg"
} }, showmode={ { "-- Keyword Local completion (^N^P) ", 3 }, { "match 1 of 2", 4 } }}
feed('<c-p>')
screen:expect{grid=[[
alphpabet |
alphanum |
alphpabet^ |
{1:~ }|
{1:~ }|
]], popupmenu={
anchor = { 2, 0 },
items = { { "alphpabet", "", "", "" }, { "alphanum", "", "", "" } },
pos = 0
}, messages={ {
content = { { "stuff" } },
kind = "echomsg"
} }, showmode={ { "-- Keyword Local completion (^N^P) ", 3 }, { "match 2 of 2", 4 } }}
feed("<esc>:messages<cr>")
screen:expect{grid=[[
alphpabet |
alphanum |
alphpabe^t |
{1:~ }|
{1:~ }|
]], messages={
{kind="echomsg", content={{"stuff"}}},
}}
end)
it('supports showmode with recording message', function()
feed('qq')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], showmode={ { "recording @q", 3 } }}
feed('i')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], showmode={ { "-- INSERT --recording @q", 3 } }}
feed('<esc>')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], showmode={ { "recording @q", 3 } }}
feed('q')
screen:expect([[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]])
end)
it('shows recording message with noshowmode', function()
command("set noshowmode")
feed('qq')
-- also check mode to avoid immediate success
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], showmode={ { "recording @q", 3 } }, mode="normal"}
feed('i')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], showmode={ { "recording @q", 3 } }, mode="insert"}
feed('<esc>')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], showmode={ { "recording @q", 3 } }, mode="normal"}
feed('q')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], mode="normal"}
end)
it('supports showcmd and ruler', function()
command('set showcmd ruler')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], ruler={ { "0,0-1 All" } }}
feed('i')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], showmode={ { "-- INSERT --", 3 } }, ruler={ { "0,1 All" } }}
feed('abcde<cr>12345<esc>')
screen:expect{grid=[[
abcde |
1234^5 |
{1:~ }|
{1:~ }|
{1:~ }|
]], ruler={ { "2,5 All" } }}
feed('d')
screen:expect{grid=[[
abcde |
1234^5 |
{1:~ }|
{1:~ }|
{1:~ }|
]], showcmd={ { "d" } }, ruler={ { "2,5 All" } }}
feed('<esc>^')
screen:expect{grid=[[
abcde |
^12345 |
{1:~ }|
{1:~ }|
{1:~ }|
]], ruler={ { "2,1 All" } }}
feed('d')
screen:expect{grid=[[
abcde |
^12345 |
{1:~ }|
{1:~ }|
{1:~ }|
]], showcmd={ { "d" } }, ruler={ { "2,1 All" } }}
feed('i')
screen:expect{grid=[[
abcde |
^12345 |
{1:~ }|
{1:~ }|
{1:~ }|
]], showcmd={ { "di" } }, ruler={ { "2,1 All" } }}
feed('w')
screen:expect{grid=[[
abcde |
^ |
{1:~ }|
{1:~ }|
{1:~ }|
]], ruler={ { "2,0-1 All" } }}
-- when ruler is part of statusline it is not externalized.
-- this will be added as part of future ext_statusline support
command("set laststatus=2")
screen:expect([[
abcde |
^ |
{1:~ }|
{1:~ }|
{6:<o Name] [+] 2,0-1 All}|
]])
end)
it('keeps history of message of different kinds', function()
feed(':echomsg "howdy"<cr>')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], messages={{
content = {{ "howdy" }}, kind = "echomsg"}
}}
-- always test a message without kind. If this one gets promoted to a
-- category, add a new message without kind.
feed('<c-c>')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], messages={{
content = {{ "Type :qa! and press <Enter> to abandon all changes and exit Nvim" }},
kind = ""}
}}
feed(':echoerr "bork"<cr>')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], messages={{
content = {{ "bork", 2 }}, kind = "echoerr"}
}}
feed(':echo "xyz"<cr>')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], messages={{
content = {{ "xyz" }}, kind = "echo"}
}}
feed(':call nosuchfunction()<cr>')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], messages={{
content = {{ "E117: Unknown function: nosuchfunction", 2 }},
kind = "emsg"}
}}
feed(':messages<cr>')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], messages={
{kind="echomsg", content={{"howdy"}}},
{kind="", content={{"Type :qa! and press <Enter> to abandon all changes and exit Nvim"}}},
{kind="echoerr", content={{"bork", 2}}},
{kind="emsg", content={{"E117: Unknown function: nosuchfunction", 2}}}
}}
end)
it('implies ext_cmdline and ignores cmdheight', function()
eq(0, eval('&cmdheight'))
feed(':set cmdheight=1')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], cmdline={{
content = { { "set cmdheight=1" } },
firstc = ":",
pos = 15 }
}}
feed('<cr>')
screen:expect([[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]])
eq(0, eval('&cmdheight'))
-- normally this would be an error
feed(':set cmdheight=0')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], cmdline={{
content = { { "set cmdheight=0" } },
firstc = ":",
pos = 15 }
}}
feed('<cr>')
screen:expect([[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]])
eq(0, eval('&cmdheight'))
end)
it('supports multiline messages', function()
feed(':lua error("such\\nmultiline\\nerror")<cr>')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]], messages={{
content = {{'E5105: Error while calling lua chunk: [string "<VimL compiled string>"]:1: such\nmultiline\nerror', 2}},
kind = "emsg"
}}}
end)
end)
describe('ui/ext_messages', function()
local screen
before_each(function()
clear{headless=false, args={"--cmd", "set shortmess-=I"}}
screen = Screen.new(80, 24)
screen:attach({rgb=true, ext_messages=true, ext_popupmenu=true})
screen:set_default_attr_ids({
[1] = {bold = true, foreground = Screen.colors.Blue1},
[2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
[3] = {bold = true},
[4] = {bold = true, foreground = Screen.colors.SeaGreen4},
[5] = {foreground = Screen.colors.Blue1},
})
end)
it('supports intro screen', function()
-- intro message is not externalized. But check that it still works.
-- Note parts of it depends on version or is indeterministic. We ignore those parts.
screen:expect([[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{IGNORE}|
{1:~ }|
{1:~ }Nvim is open source and freely distributable{1: }|
{1:~ }https://neovim.io/#chat{1: }|
{1:~ }|
{1:~ }type :help nvim{5:<Enter>} if you are new! {1: }|
{1:~ }type :checkhealth{5:<Enter>} to optimize Nvim{1: }|
{1:~ }type :q{5:<Enter>} to exit {1: }|
{1:~ }type :help{5:<Enter>} for help {1: }|
{1:~ }|
{IGNORE}|
{IGNORE}|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]])
feed("<c-l>")
screen:expect([[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
]])
feed(":intro<cr>")
screen:expect{grid=[[
|
|
|
|
|
|
{IGNORE}|
|
Nvim is open source and freely distributable |
https://neovim.io/#chat |
|
type :help nvim{5:<Enter>} if you are new! |
type :checkhealth{5:<Enter>} to optimize Nvim |
type :q{5:<Enter>} to exit |
type :help{5:<Enter>} for help |
|
{IGNORE}|
{IGNORE}|
|
|
|
|
|
|
]], messages={
{content = { { "Press ENTER or type command to continue", 4 } }, kind = "return_prompt" }
}}
end)
end)

View File

@ -28,6 +28,7 @@ describe('ui receives option updates', function()
ext_linegrid=false,
ext_hlstate=false,
ext_multigrid=false,
ext_messages=false,
ext_termcolors=false,
}

View File

@ -159,6 +159,11 @@ function Screen.new(width, height)
wildmenu_selected = nil,
win_position = {},
_session = nil,
messages = {},
msg_history = {},
showmode = {},
showcmd = {},
ruler = {},
_default_attr_ids = nil,
_default_attr_ignore = nil,
_mouse_enabled = true,
@ -250,7 +255,8 @@ end
-- canonical order of ext keys, used to generate asserts
local ext_keys = {
'popupmenu', 'cmdline', 'cmdline_block', 'wildmenu_items', 'wildmenu_pos'
'popupmenu', 'cmdline', 'cmdline_block', 'wildmenu_items', 'wildmenu_pos',
'messages', 'showmode', 'showcmd', 'ruler',
}
-- Asserts that the screen state eventually matches an expected state
@ -392,7 +398,7 @@ function Screen:expect(expected, attr_ids, attr_ignore)
.. ') differs from configured height(' .. #actual_rows .. ') of Screen.'
end
for i = 1, #actual_rows do
if expected_rows[i] ~= actual_rows[i] then
if expected_rows[i] ~= actual_rows[i] and expected_rows[i] ~= "{IGNORE}|" then
local msg_expected_rows = {}
for j = 1, #expected_rows do
msg_expected_rows[j] = expected_rows[j]
@ -917,7 +923,7 @@ function Screen:_handle_option_set(name, value)
end
function Screen:_handle_popupmenu_show(items, selected, row, col)
self.popupmenu = {items=items,pos=selected, anchor={row, col}}
self.popupmenu = {items=items, pos=selected, anchor={row, col}}
end
function Screen:_handle_popupmenu_select(selected)
@ -973,6 +979,34 @@ function Screen:_handle_wildmenu_hide()
self.wildmenu_items, self.wildmenu_pos = nil, nil
end
function Screen:_handle_msg_show(kind, chunks, replace_last)
local pos = #self.messages
if not replace_last or pos == 0 then
pos = pos + 1
end
self.messages[pos] = {kind=kind, content=chunks}
end
function Screen:_handle_msg_clear()
self.messages = {}
end
function Screen:_handle_msg_showcmd(msg)
self.showcmd = msg
end
function Screen:_handle_msg_showmode(msg)
self.showmode = msg
end
function Screen:_handle_msg_ruler(msg)
self.ruler = msg
end
function Screen:_handle_msg_history_show(entries)
self.msg_history = entries
end
function Screen:_clear_block(grid, top, bot, left, right)
for i = top, bot do
self:_clear_row_section(grid, i, left, right)
@ -1057,12 +1091,27 @@ function Screen:_extstate_repr(attr_state)
cmdline_block[i] = self:_chunks_repr(entry, attr_state)
end
local messages = {}
for i, entry in ipairs(self.messages) do
messages[i] = {kind=entry.kind, content=self:_chunks_repr(entry.content, attr_state)}
end
local msg_history = {}
for i, entry in ipairs(self.msg_history) do
messages[i] = {kind=entry[1], content=self:_chunks_repr(entry[2], attr_state)}
end
return {
popupmenu=self.popupmenu,
cmdline=cmdline,
cmdline_block=cmdline_block,
wildmenu_items=self.wildmenu_items,
wildmenu_pos=self.wildmenu_pos,
messages=messages,
showmode=self:_chunks_repr(self.showmode, attr_state),
showcmd=self:_chunks_repr(self.showcmd, attr_state),
ruler=self:_chunks_repr(self.ruler, attr_state),
msg_history=msg_history,
}
end