diff options
-rw-r--r-- | src/nvim/memory.c | 6 | ||||
-rw-r--r-- | src/nvim/message.c | 267 | ||||
-rw-r--r-- | src/nvim/message.h | 5 | ||||
-rw-r--r-- | src/nvim/message_defs.h | 7 | ||||
-rw-r--r-- | test/functional/ui/messages_spec.lua | 42 | ||||
-rw-r--r-- | test/unit/eval/decode_spec.lua | 4 | ||||
-rw-r--r-- | test/unit/eval/typval_spec.lua | 17 |
7 files changed, 193 insertions, 155 deletions
diff --git a/src/nvim/memory.c b/src/nvim/memory.c index fa102f4e12..af766c0d20 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -856,11 +856,7 @@ void free_all_mem(void) first_tabpage = NULL; // message history - while (true) { - if (delete_first_msg() == FAIL) { - break; - } - } + msg_hist_clear(0); channel_free_all_mem(); eval_clear(); diff --git a/src/nvim/message.c b/src/nvim/message.c index c077e70060..a8635f1a61 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -94,8 +94,9 @@ static int confirm_msg_used = false; // displaying confirm_msg static char *confirm_msg = NULL; // ":confirm" message static char *confirm_buttons; // ":confirm" buttons sent to cmdline as prompt -MessageHistoryEntry *first_msg_hist = NULL; -MessageHistoryEntry *last_msg_hist = NULL; +MessageHistoryEntry *msg_hist_last = NULL; // Last message (extern for unittest) +static MessageHistoryEntry *msg_hist_first = NULL; // First message +static MessageHistoryEntry *msg_hist_temp = NULL; // First potentially temporary message static int msg_hist_len = 0; static int msg_hist_max = 500; // The default max value is 500 @@ -322,7 +323,7 @@ void msg_multihl(HlMessage hl_msg, const char *kind, bool history, bool err) assert(!ui_has(kUIMessages) || kind == NULL || msg_ext_kind == kind); } if (history && kv_size(hl_msg)) { - add_msg_hist_multihl(NULL, 0, 0, true, hl_msg); + msg_hist_add_multihl(hl_msg, false); } is_multihl = false; no_wait_return--; @@ -360,13 +361,12 @@ bool msg_keep(const char *s, int hl_id, bool keep, bool multiline) } entered++; - // Add message to history (unless it's a truncated, repeated kept or multihl message). - if ((s != keep_msg - || (*s != '<' - && last_msg_hist != NULL - && last_msg_hist->msg != NULL - && strcmp(s, last_msg_hist->msg) != 0)) && !is_multihl) { - add_msg_hist(s, -1, hl_id, multiline); + // Add message to history unless it's a multihl, repeated kept or truncated message. + if (!is_multihl + && (s != keep_msg + || (*s != '<' && msg_hist_last != NULL + && strcmp(s, msg_hist_last->msg.items[0].text.data) != 0))) { + msg_hist_add(s, -1, hl_id); } if (!is_multihl) { @@ -952,7 +952,7 @@ void msg_schedule_semsg_multiline(const char *const fmt, ...) char *msg_trunc(char *s, bool force, int hl_id) { // Add message to history before truncating. - add_msg_hist(s, -1, hl_id, false); + msg_hist_add(s, -1, hl_id); char *ts = msg_may_trunc(force, s); @@ -1006,83 +1006,97 @@ void hl_msg_free(HlMessage hl_msg) kv_destroy(hl_msg); } +/// Add the message at the end of the history +/// /// @param[in] len Length of s or -1. -static void add_msg_hist(const char *s, int len, int hl_id, bool multiline) +static void msg_hist_add(const char *s, int len, int hl_id) { - add_msg_hist_multihl(s, len, hl_id, multiline, (HlMessage)KV_INITIAL_VALUE); + String text = { .size = len < 0 ? strlen(s) : (size_t)len }; + // Remove leading and trailing newlines. + while (text.size > 0 && *s == '\n') { + text.size--; + s++; + } + while (text.size > 0 && s[text.size - 1] == '\n') { + text.size--; + } + if (text.size == 0) { + return; + } + text.data = xmemdupz(s, text.size); + + HlMessage msg = KV_INITIAL_VALUE; + kv_push(msg, ((HlMessageChunk){ text, hl_id })); + msg_hist_add_multihl(msg, false); } -static void add_msg_hist_multihl(const char *s, int len, int hl_id, bool multiline, - HlMessage multihl) +static void msg_hist_add_multihl(HlMessage msg, bool temp) { - if (msg_hist_off || msg_silent != 0 || (s != NULL && *s == NUL)) { - hl_msg_free(multihl); + if (msg_hist_off || msg_silent != 0) { + hl_msg_free(msg); return; } - // allocate an entry and add the message at the end of the history - struct msg_hist *p = xmalloc(sizeof(struct msg_hist)); - if (s) { - if (len < 0) { - len = (int)strlen(s); - } - assert(len > 0); - // remove leading and trailing newlines - while (*s == '\n') { - s++; - len--; - } - while (s[len - 1] == '\n') { - len--; - } - p->msg = xmemdupz(s, (size_t)len); - } else { - p->msg = NULL; + // Allocate an entry and add the message at the end of the history. + MessageHistoryEntry *entry = xmalloc(sizeof(MessageHistoryEntry)); + entry->msg = msg; + entry->temp = temp; + entry->kind = msg_ext_kind; + entry->prev = msg_hist_last; + entry->next = NULL; + + if (msg_hist_first == NULL) { + msg_hist_first = entry; } - p->next = NULL; - p->hl_id = hl_id; - p->multiline = multiline; - p->multihl = multihl; - p->kind = msg_ext_kind; - if (last_msg_hist != NULL) { - last_msg_hist->next = p; + if (msg_hist_last != NULL) { + msg_hist_last->next = entry; } - last_msg_hist = p; - if (first_msg_hist == NULL) { - first_msg_hist = last_msg_hist; + if (msg_hist_temp == NULL) { + msg_hist_temp = entry; } - msg_hist_len++; - msg_ext_history = true; - check_msg_hist(); + msg_hist_len += !temp; + msg_hist_last = entry; + msg_ext_history = true; + msg_hist_clear(msg_hist_max); } -/// Delete the first (oldest) message from the history. -/// -/// @return FAIL if there are no messages. -int delete_first_msg(void) +static void msg_hist_free_msg(MessageHistoryEntry *entry) { - if (msg_hist_len <= 0) { - return FAIL; + if (entry->next == NULL) { + msg_hist_last = entry->prev; + } else { + entry->next->prev = entry->prev; } - struct msg_hist *p = first_msg_hist; - first_msg_hist = p->next; - if (first_msg_hist == NULL) { // history is becoming empty - assert(msg_hist_len == 1); - last_msg_hist = NULL; + if (entry->prev == NULL) { + msg_hist_first = entry->next; + } else { + entry->prev->next = entry->next; } - xfree(p->msg); - hl_msg_free(p->multihl); - xfree(p); - msg_hist_len--; - return OK; + if (entry == msg_hist_temp) { + msg_hist_temp = entry->next; + } + hl_msg_free(entry->msg); + xfree(entry); } -static void check_msg_hist(void) +/// Delete oldest messages from the history until there are "keep" messages. +void msg_hist_clear(int keep) { - // Don't let the message history get too big - while (msg_hist_len > 0 && msg_hist_len > msg_hist_max) { - (void)delete_first_msg(); + while (msg_hist_len > keep || (keep == 0 && msg_hist_first != NULL)) { + msg_hist_len -= !msg_hist_first->temp; + msg_hist_free_msg(msg_hist_first); + } +} + +void msg_hist_clear_temp(void) +{ + while (msg_hist_temp != NULL) { + MessageHistoryEntry *next = msg_hist_temp->next; + if (msg_hist_temp->temp) { + msg_hist_free_msg(msg_hist_temp); + } + msg_hist_temp = next; } } @@ -1143,7 +1157,7 @@ int messagesopt_changed(void) msg_wait = messages_wait_new; msg_hist_max = messages_history_new; - check_msg_hist(); + msg_hist_clear(msg_hist_max); return OK; } @@ -1153,11 +1167,7 @@ void ex_messages(exarg_T *eap) FUNC_ATTR_NONNULL_ALL { if (strcmp(eap->arg, "clear") == 0) { - int keep = eap->addr_count == 0 ? 0 : eap->line2; - - while (msg_hist_len > keep) { - delete_first_msg(); - } + msg_hist_clear(eap->addr_count ? eap->line2 : 0); return; } @@ -1166,66 +1176,36 @@ void ex_messages(exarg_T *eap) return; } - struct msg_hist *p = first_msg_hist; - - if (eap->addr_count != 0) { - int c = 0; - // Count total messages - for (; p != NULL && !got_int; p = p->next) { - c++; - } - - c -= eap->line2; - - // Skip without number of messages specified - for (p = first_msg_hist; p != NULL && !got_int && c > 0; p = p->next, c--) {} - } - - // Display what was not skipped. - if (ui_has(kUIMessages)) { - if (msg_silent) { - return; - } - Array entries = ARRAY_DICT_INIT; - for (; p != NULL; p = p->next) { - if (kv_size(p->multihl) || (p->msg && p->msg[0])) { - Array entry = ARRAY_DICT_INIT; - ADD(entry, CSTR_TO_OBJ(p->kind)); - Array content = ARRAY_DICT_INIT; - if (kv_size(p->multihl)) { - for (uint32_t i = 0; i < kv_size(p->multihl); i++) { - HlMessageChunk chunk = kv_A(p->multihl, i); - Array content_entry = ARRAY_DICT_INIT; - ADD(content_entry, INTEGER_OBJ(chunk.hl_id ? syn_id2attr(chunk.hl_id) : 0)); - ADD(content_entry, STRING_OBJ(copy_string(chunk.text, NULL))); - ADD(content_entry, INTEGER_OBJ(chunk.hl_id)); - ADD(content, ARRAY_OBJ(content_entry)); - } - } else if (p->msg && p->msg[0]) { - Array content_entry = ARRAY_DICT_INIT; - ADD(content_entry, INTEGER_OBJ(p->hl_id ? syn_id2attr(p->hl_id) : 0)); - ADD(content_entry, CSTR_TO_OBJ(p->msg)); - ADD(content_entry, INTEGER_OBJ(p->hl_id)); - ADD(content, ARRAY_OBJ(content_entry)); - } - ADD(entry, ARRAY_OBJ(content)); - ADD(entries, ARRAY_OBJ(entry)); + Array entries = ARRAY_DICT_INIT; + MessageHistoryEntry *p = eap->skip ? msg_hist_temp : msg_hist_first; + int skip = eap->addr_count ? (msg_hist_len - eap->line2) : 0; + while (p != NULL) { + if ((p->temp && !eap->skip) || skip-- > 0) { + // Skipping over count or temporary "g<" messages. + } else if (ui_has(kUIMessages)) { + Array entry = ARRAY_DICT_INIT; + ADD(entry, CSTR_TO_OBJ(p->kind)); + Array content = ARRAY_DICT_INIT; + for (uint32_t i = 0; i < kv_size(p->msg); i++) { + HlMessageChunk chunk = kv_A(p->msg, i); + Array content_entry = ARRAY_DICT_INIT; + ADD(content_entry, INTEGER_OBJ(chunk.hl_id ? syn_id2attr(chunk.hl_id) : 0)); + ADD(content_entry, STRING_OBJ(copy_string(chunk.text, NULL))); + ADD(content_entry, INTEGER_OBJ(chunk.hl_id)); + ADD(content, ARRAY_OBJ(content_entry)); } + ADD(entry, ARRAY_OBJ(content)); + ADD(entries, ARRAY_OBJ(entry)); + } else { + msg_multihl(p->msg, p->kind, false, false); } + p = p->next; + } + if (kv_size(entries) > 0) { ui_call_msg_history_show(entries); api_free_array(entries); msg_ext_history_visible = true; wait_return(false); - } else { - msg_hist_off = true; - for (; p != NULL && !got_int; p = p->next) { - if (kv_size(p->multihl)) { - msg_multihl(p->multihl, p->kind, false, false); - } else if (p->msg != NULL) { - msg_keep(p->msg, p->hl_id, false, p->multiline); - } - } - msg_hist_off = false; } } @@ -1674,7 +1654,7 @@ int msg_outtrans_len(const char *msgstr, int len, int hl_id, bool hist) got_int = false; if (hist) { - add_msg_hist(str, len, hl_id, false); + msg_hist_add(str, len, hl_id); } // When drawing over the command line no need to clear it later or remove @@ -2164,7 +2144,7 @@ void msg_puts_len(const char *const str, const ptrdiff_t len, int hl_id, bool hi } if (hist) { - add_msg_hist(str, (int)len, hl_id, false); + msg_hist_add(str, (int)len, hl_id); } // When writing something to the screen after it has scrolled, requires a @@ -2225,6 +2205,8 @@ static void msg_ext_emit_chunk(void) ADD(*msg_ext_chunks, ARRAY_OBJ(chunk)); } +static bool do_clear_hist_temp = true; + /// The display part of msg_puts_len(). /// May be called recursively to display scroll-back text. static void msg_puts_display(const char *str, int maxlen, int hl_id, int recurse) @@ -2252,6 +2234,11 @@ static void msg_puts_display(const char *str, int maxlen, int hl_id, int recurse const char *p = lastline ? lastline + 1 : str; int col = (int)(maxlen < 0 ? mb_string2cells(p) : mb_string2cells_len(p, (size_t)(maxlen))); msg_col = (lastline ? 0 : msg_col) + col; + + if (do_clear_hist_temp && !strequal(msg_ext_kind, "return_prompt")) { + msg_hist_clear_temp(); + do_clear_hist_temp = false; + } return; } @@ -2618,6 +2605,7 @@ static void store_sb_text(const char **sb_str, const char *s, int hl_id, int *sb void may_clear_sb_text(void) { do_clear_sb_text = SB_CLEAR_ALL; + do_clear_hist_temp = true; } /// Starting to edit the command line: do not clear messages now. @@ -2690,6 +2678,11 @@ void clear_sb_text(bool all) /// "g<" command. void show_sb_text(void) { + if (ui_has(kUIMessages)) { + exarg_T ea = { .arg = "", .skip = true }; + ex_messages(&ea); + return; + } // Only show something if there is more than one line, otherwise it looks // weird, typing a command without output results in one line. msgchunk_T *mp = msg_sb_start(last_msgchunk); @@ -3170,7 +3163,19 @@ void msg_ext_ui_flush(void) if (msg_ext_chunks->size > 0) { Array *tofree = msg_ext_init_chunks(); ui_call_msg_show(cstr_as_string(msg_ext_kind), *tofree, msg_ext_overwrite, msg_ext_history); - api_free_array(*tofree); + if (msg_ext_history || strequal(msg_ext_kind, "return_prompt")) { + api_free_array(*tofree); + } else { + // Add to history as temporary message for "g<". + HlMessage msg = KV_INITIAL_VALUE; + for (size_t i = 0; i < kv_size(*tofree); i++) { + Object *chunk = kv_A(*tofree, i).data.array.items; + kv_push(msg, ((HlMessageChunk){ chunk[1].data.string, (int)chunk[2].data.integer })); + xfree(chunk); + } + xfree(tofree->items); + msg_hist_add_multihl(msg, true); + } xfree(tofree); if (!msg_ext_overwrite) { msg_ext_visible++; diff --git a/src/nvim/message.h b/src/nvim/message.h index 55c58114f4..d9af660121 100644 --- a/src/nvim/message.h +++ b/src/nvim/message.h @@ -28,10 +28,7 @@ enum { VIM_DISCARDALL = 6, }; -/// First message -extern MessageHistoryEntry *first_msg_hist; -/// Last message -extern MessageHistoryEntry *last_msg_hist; +extern MessageHistoryEntry *msg_hist_last; EXTERN bool msg_ext_need_clear INIT( = false); // Set to true to force grouping a set of message chunks into a single `cmdline_show` event. diff --git a/src/nvim/message_defs.h b/src/nvim/message_defs.h index 8d23b79385..a65f0cd97a 100644 --- a/src/nvim/message_defs.h +++ b/src/nvim/message_defs.h @@ -14,9 +14,8 @@ typedef kvec_t(HlMessageChunk) HlMessage; /// Message history for `:messages` typedef struct msg_hist { struct msg_hist *next; ///< Next message. - char *msg; ///< Message text. + struct msg_hist *prev; ///< Previous message. + HlMessage msg; ///< Highlighted message. const char *kind; ///< Message kind (for msg_ext) - int hl_id; ///< Message highlighting. - bool multiline; ///< Multiline message. - HlMessage multihl; ///< Multihl message. + bool temp; ///< Temporary message since last command ("g<") } MessageHistoryEntry; diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 4770c5e143..a6577f3ca0 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -1571,6 +1571,48 @@ stack traceback: end, }) end) + + it('g< mapping shows recent messages', function() + command('echo "foo" | echo "bar"') + local s1 = [[ + ^ | + {1:~ }|*4 + ]] + screen:expect({ + grid = s1, + messages = { + { + content = { { 'bar' } }, + history = false, + kind = 'echo', + }, + }, + }) + feed(':messages<CR>g<lt>') + screen:expect({ + grid = [[ + ^ | + {1:~ }|*4 + ]], + messages = { + { + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, + kind = 'return_prompt', + }, + }, + msg_history = { + { + content = { { 'foo' } }, + kind = 'echo', + }, + { + content = { { 'bar' } }, + kind = 'echo', + }, + }, + }) + end) end) describe('ui/builtin messages', function() diff --git a/test/unit/eval/decode_spec.lua b/test/unit/eval/decode_spec.lua index 7e037500b9..96e04dbbe5 100644 --- a/test/unit/eval/decode_spec.lua +++ b/test/unit/eval/decode_spec.lua @@ -70,8 +70,8 @@ describe('json_decode_string()', function() local rettv = ffi.new('typval_T', { v_type = decode.VAR_UNKNOWN }) eq(0, decode.json_decode_string(s, len, rettv)) eq(decode.VAR_UNKNOWN, rettv.v_type) - neq(nil, decode.last_msg_hist) - eq(msg, ffi.string(decode.last_msg_hist.msg)) + neq(nil, decode.msg_hist_last) + eq(msg, ffi.string(decode.msg_hist_last.msg.items[0].text.data)) end itp('does not overflow in error messages', function() diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index 14fd7986e7..b4f115850b 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -101,20 +101,19 @@ local function ga_alloc(itemsize, growsize) end local function check_emsg(f, msg) - local saved_last_msg_hist = lib.last_msg_hist - if saved_last_msg_hist == nil then - saved_last_msg_hist = nil + local saved_msg_hist_last = lib.msg_hist_last + if saved_msg_hist_last == nil then + saved_msg_hist_last = nil end local ret = { f() } - local last_msg = lib.last_msg_hist ~= nil and ffi.string(lib.last_msg_hist.msg) or nil if msg ~= nil then - eq(msg, last_msg) - neq(saved_last_msg_hist, lib.last_msg_hist) + eq(msg, ffi.string(lib.msg_hist_last.msg.items[0].text.data)) + neq(saved_msg_hist_last, lib.msg_hist_last) else - if saved_last_msg_hist ~= lib.last_msg_hist then - eq(nil, last_msg) + if saved_msg_hist_last ~= lib.msg_hist_last then + eq(nil, lib.msg_hist_last) else - eq(saved_last_msg_hist, lib.last_msg_hist) + eq(saved_msg_hist_last, lib.msg_hist_last) end end return unpack(ret) |