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).
This commit is contained in:
Luuk van Baal
2025-03-01 16:18:43 +01:00
parent f7fa6d1266
commit 124c655f56
3 changed files with 100 additions and 7 deletions

View File

@@ -96,6 +96,7 @@ static char *confirm_buttons; // ":confirm" buttons sent to cmdlin
MessageHistoryEntry *msg_hist_last = NULL; // Last message (extern for unittest) MessageHistoryEntry *msg_hist_last = NULL; // Last message (extern for unittest)
static MessageHistoryEntry *msg_hist_first = NULL; // First message 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_len = 0;
static int msg_hist_max = 500; // The default max value is 500 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. // Allocate an entry and add the message at the end of the history.
MessageHistoryEntry *entry = xmalloc(sizeof(MessageHistoryEntry)); MessageHistoryEntry *entry = xmalloc(sizeof(MessageHistoryEntry));
entry->msg = msg; entry->msg = msg;
entry->temp = temp;
entry->kind = msg_ext_kind; entry->kind = msg_ext_kind;
entry->prev = msg_hist_last;
entry->next = NULL; entry->next = NULL;
if (msg_hist_first == 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) { if (msg_hist_last != NULL) {
msg_hist_last->next = entry; msg_hist_last->next = entry;
} }
if (msg_hist_temp == NULL) {
msg_hist_temp = entry;
}
msg_hist_len += !temp; msg_hist_len += !temp;
msg_hist_last = entry; 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) static void msg_hist_free_msg(MessageHistoryEntry *entry)
{ {
if (entry->next == NULL) { 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); hl_msg_free(entry->msg);
xfree(entry); xfree(entry);
} }
@@ -1069,11 +1084,22 @@ static void msg_hist_free_msg(MessageHistoryEntry *entry)
void msg_hist_clear(int keep) void msg_hist_clear(int keep)
{ {
while (msg_hist_len > keep || (keep == 0 && msg_hist_first != NULL)) { 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); 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 messagesopt_changed(void)
{ {
int messages_flags_new = 0; int messages_flags_new = 0;
@@ -1151,11 +1177,11 @@ void ex_messages(exarg_T *eap)
} }
Array entries = ARRAY_DICT_INIT; 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; int skip = eap->addr_count ? (msg_hist_len - eap->line2) : 0;
while (p != NULL) { while (p != NULL) {
if (skip-- > 0) { if ((p->temp && !eap->skip) || skip-- > 0) {
// Skipping over messages. // Skipping over count or temporary "g<" messages.
} else if (ui_has(kUIMessages)) { } else if (ui_has(kUIMessages)) {
Array entry = ARRAY_DICT_INIT; Array entry = ARRAY_DICT_INIT;
ADD(entry, CSTR_TO_OBJ(p->kind)); 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)); ADD(*msg_ext_chunks, ARRAY_OBJ(chunk));
} }
static bool do_clear_hist_temp = true;
/// The display part of msg_puts_len(). /// The display part of msg_puts_len().
/// May be called recursively to display scroll-back text. /// May be called recursively to display scroll-back text.
static void msg_puts_display(const char *str, int maxlen, int hl_id, int recurse) 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))); int col = (int)(maxlen < 0 ? mb_string2cells(p) : mb_string2cells_len(p, (size_t)(maxlen)));
msg_col = (lastline ? 0 : msg_col) + col; 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; 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) void may_clear_sb_text(void)
{ {
do_clear_sb_text = SB_CLEAR_ALL; do_clear_sb_text = SB_CLEAR_ALL;
do_clear_hist_temp = true;
} }
/// Starting to edit the command line: do not clear messages now. /// Starting to edit the command line: do not clear messages now.
@@ -2645,6 +2678,11 @@ void clear_sb_text(bool all)
/// "g<" command. /// "g<" command.
void show_sb_text(void) 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 // Only show something if there is more than one line, otherwise it looks
// weird, typing a command without output results in one line. // weird, typing a command without output results in one line.
msgchunk_T *mp = msg_sb_start(last_msgchunk); msgchunk_T *mp = msg_sb_start(last_msgchunk);
@@ -3125,7 +3163,19 @@ void msg_ext_ui_flush(void)
if (msg_ext_chunks->size > 0) { if (msg_ext_chunks->size > 0) {
Array *tofree = msg_ext_init_chunks(); Array *tofree = msg_ext_init_chunks();
ui_call_msg_show(cstr_as_string(msg_ext_kind), *tofree, msg_ext_overwrite, msg_ext_history); 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); xfree(tofree);
if (!msg_ext_overwrite) { if (!msg_ext_overwrite) {
msg_ext_visible++; msg_ext_visible++;

View File

@@ -17,4 +17,5 @@ typedef struct msg_hist {
struct msg_hist *prev; ///< Previous message. struct msg_hist *prev; ///< Previous message.
HlMessage msg; ///< Highlighted message. HlMessage msg; ///< Highlighted message.
const char *kind; ///< Message kind (for msg_ext) const char *kind; ///< Message kind (for msg_ext)
bool temp; ///< Temporary message since last command ("g<")
} MessageHistoryEntry; } MessageHistoryEntry;

View File

@@ -1571,6 +1571,48 @@ stack traceback:
end, end,
}) })
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) end)
describe('ui/builtin messages', function() describe('ui/builtin messages', function()