From f7fa6d12668ab696fb1d64e04cfac6734cf86a4d Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Fri, 28 Feb 2025 20:25:58 +0100 Subject: refactor(messages): simplify message history --- src/nvim/memory.c | 6 +- src/nvim/message.c | 219 +++++++++++++++++++----------------------------- src/nvim/message.h | 5 +- src/nvim/message_defs.h | 6 +- 4 files changed, 91 insertions(+), 145 deletions(-) (limited to 'src') 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..49bad3c994 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -94,8 +94,8 @@ 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 int msg_hist_len = 0; static int msg_hist_max = 500; // The default max value is 500 @@ -322,7 +322,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 +360,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 +951,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 +1005,72 @@ 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; - } - 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; + // Allocate an entry and add the message at the end of the history. + MessageHistoryEntry *entry = xmalloc(sizeof(MessageHistoryEntry)); + entry->msg = msg; + entry->kind = msg_ext_kind; + entry->next = NULL; + + if (msg_hist_first == NULL) { + msg_hist_first = entry; } - last_msg_hist = p; - if (first_msg_hist == NULL) { - first_msg_hist = last_msg_hist; + if (msg_hist_last != NULL) { + msg_hist_last->next = 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; - } - 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->next == NULL) { + msg_hist_last = NULL; } - xfree(p->msg); - hl_msg_free(p->multihl); - xfree(p); - msg_hist_len--; - return OK; + msg_hist_first = 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_free_msg(msg_hist_first); } } @@ -1143,7 +1131,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 +1141,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 +1150,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 = msg_hist_first; + int skip = eap->addr_count ? (msg_hist_len - eap->line2) : 0; + while (p != NULL) { + if (skip-- > 0) { + // Skipping over 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 +1628,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 +2118,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 @@ -2252,6 +2206,7 @@ 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; + return; } 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..addb80fc25 100644 --- a/src/nvim/message_defs.h +++ b/src/nvim/message_defs.h @@ -14,9 +14,7 @@ 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. } MessageHistoryEntry; -- cgit From 124c655f56bcf31dfeaf91caee2e5f2fcbbfa089 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Sat, 1 Mar 2025 16:18:43 +0100 Subject: feat(messages): "g<" mapping for ext_messages Problem: Cannot use "g<" mapping with ext_messages. Mapping displays the scrollback buffer since the last command, but the scrollback buffer is not populated with ext_messages. Solution: With ext_messages; store messages in the history that otherwise wouldn't be. Mark them as temporary messages to be deleted when the scrollback buffer would be cleared. To this end, make the message history a doubly-linked list such that messages can be removed from an arbitrary position. Outlook: Default ext_messages UI might not show the hit-enter prompt so we want "g<" to work as a recommended way to show messages for the last command (prompted by an indicator). --- src/nvim/message.c | 64 +++++++++++++++++++++++++++++++++++++++++++------ src/nvim/message_defs.h | 1 + 2 files changed, 58 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/nvim/message.c b/src/nvim/message.c index 49bad3c994..a8635f1a61 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -96,6 +96,7 @@ static char *confirm_buttons; // ":confirm" buttons sent to cmdlin 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 @@ -1039,7 +1040,9 @@ static void msg_hist_add_multihl(HlMessage msg, bool temp) // 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) { @@ -1048,6 +1051,9 @@ static void msg_hist_add_multihl(HlMessage msg, bool temp) if (msg_hist_last != NULL) { msg_hist_last->next = entry; } + if (msg_hist_temp == NULL) { + msg_hist_temp = entry; + } msg_hist_len += !temp; msg_hist_last = entry; @@ -1058,9 +1064,18 @@ static void msg_hist_add_multihl(HlMessage msg, bool temp) static void msg_hist_free_msg(MessageHistoryEntry *entry) { if (entry->next == NULL) { - msg_hist_last = NULL; + msg_hist_last = entry->prev; + } else { + entry->next->prev = entry->prev; + } + if (entry->prev == NULL) { + msg_hist_first = entry->next; + } else { + entry->prev->next = entry->next; + } + if (entry == msg_hist_temp) { + msg_hist_temp = entry->next; } - msg_hist_first = entry->next; hl_msg_free(entry->msg); xfree(entry); } @@ -1069,11 +1084,22 @@ static void msg_hist_free_msg(MessageHistoryEntry *entry) void msg_hist_clear(int keep) { while (msg_hist_len > keep || (keep == 0 && msg_hist_first != NULL)) { - msg_hist_len--; + 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; + } +} + int messagesopt_changed(void) { int messages_flags_new = 0; @@ -1151,11 +1177,11 @@ void ex_messages(exarg_T *eap) } Array entries = ARRAY_DICT_INIT; - MessageHistoryEntry *p = msg_hist_first; + 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 (skip-- > 0) { - // Skipping over messages. + 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)); @@ -2179,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) @@ -2207,6 +2235,10 @@ static void msg_puts_display(const char *str, int maxlen, int hl_id, int recurse 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; } @@ -2573,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. @@ -2645,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); @@ -3125,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_defs.h b/src/nvim/message_defs.h index addb80fc25..a65f0cd97a 100644 --- a/src/nvim/message_defs.h +++ b/src/nvim/message_defs.h @@ -17,4 +17,5 @@ typedef struct msg_hist { struct msg_hist *prev; ///< Previous message. HlMessage msg; ///< Highlighted message. const char *kind; ///< Message kind (for msg_ext) + bool temp; ///< Temporary message since last command ("g<") } MessageHistoryEntry; -- cgit