Merge #32687 "g<" for ext_messages

This commit is contained in:
Justin M. Keyes
2025-03-09 13:38:32 -07:00
committed by GitHub
7 changed files with 193 additions and 155 deletions

View File

@@ -856,11 +856,7 @@ void free_all_mem(void)
first_tabpage = NULL; first_tabpage = NULL;
// message history // message history
while (true) { msg_hist_clear(0);
if (delete_first_msg() == FAIL) {
break;
}
}
channel_free_all_mem(); channel_free_all_mem();
eval_clear(); eval_clear();

View File

@@ -94,8 +94,9 @@ static int confirm_msg_used = false; // displaying confirm_msg
static char *confirm_msg = NULL; // ":confirm" message static char *confirm_msg = NULL; // ":confirm" message
static char *confirm_buttons; // ":confirm" buttons sent to cmdline as prompt static char *confirm_buttons; // ":confirm" buttons sent to cmdline as prompt
MessageHistoryEntry *first_msg_hist = NULL; MessageHistoryEntry *msg_hist_last = NULL; // Last message (extern for unittest)
MessageHistoryEntry *last_msg_hist = NULL; 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
@@ -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); assert(!ui_has(kUIMessages) || kind == NULL || msg_ext_kind == kind);
} }
if (history && kv_size(hl_msg)) { 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; is_multihl = false;
no_wait_return--; no_wait_return--;
@@ -360,13 +361,12 @@ bool msg_keep(const char *s, int hl_id, bool keep, bool multiline)
} }
entered++; entered++;
// Add message to history (unless it's a truncated, repeated kept or multihl message). // Add message to history unless it's a multihl, repeated kept or truncated message.
if ((s != keep_msg if (!is_multihl
|| (*s != '<' && (s != keep_msg
&& last_msg_hist != NULL || (*s != '<' && msg_hist_last != NULL
&& last_msg_hist->msg != NULL && strcmp(s, msg_hist_last->msg.items[0].text.data) != 0))) {
&& strcmp(s, last_msg_hist->msg) != 0)) && !is_multihl) { msg_hist_add(s, -1, hl_id);
add_msg_hist(s, -1, hl_id, multiline);
} }
if (!is_multihl) { 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) char *msg_trunc(char *s, bool force, int hl_id)
{ {
// Add message to history before truncating. // 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); char *ts = msg_may_trunc(force, s);
@@ -1006,83 +1006,97 @@ void hl_msg_free(HlMessage hl_msg)
kv_destroy(hl_msg); kv_destroy(hl_msg);
} }
/// Add the message at the end of the history
///
/// @param[in] len Length of s or -1. /// @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, static void msg_hist_add_multihl(HlMessage msg, bool temp)
HlMessage multihl)
{ {
if (msg_hist_off || msg_silent != 0 || (s != NULL && *s == NUL)) { if (msg_hist_off || msg_silent != 0) {
hl_msg_free(multihl); hl_msg_free(msg);
return; return;
} }
// 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.
struct msg_hist *p = xmalloc(sizeof(struct msg_hist)); MessageHistoryEntry *entry = xmalloc(sizeof(MessageHistoryEntry));
if (s) { entry->msg = msg;
if (len < 0) { entry->temp = temp;
len = (int)strlen(s); entry->kind = msg_ext_kind;
entry->prev = msg_hist_last;
entry->next = NULL;
if (msg_hist_first == NULL) {
msg_hist_first = entry;
} }
assert(len > 0); if (msg_hist_last != NULL) {
// remove leading and trailing newlines msg_hist_last->next = entry;
while (*s == '\n') {
s++;
len--;
} }
while (s[len - 1] == '\n') { if (msg_hist_temp == NULL) {
len--; msg_hist_temp = entry;
} }
p->msg = xmemdupz(s, (size_t)len);
} else { msg_hist_len += !temp;
p->msg = NULL; msg_hist_last = 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;
}
last_msg_hist = p;
if (first_msg_hist == NULL) {
first_msg_hist = last_msg_hist;
}
msg_hist_len++;
msg_ext_history = true; msg_ext_history = true;
msg_hist_clear(msg_hist_max);
check_msg_hist();
} }
/// Delete the first (oldest) message from the history. static void msg_hist_free_msg(MessageHistoryEntry *entry)
///
/// @return FAIL if there are no messages.
int delete_first_msg(void)
{ {
if (msg_hist_len <= 0) { if (entry->next == NULL) {
return FAIL; msg_hist_last = entry->prev;
} else {
entry->next->prev = entry->prev;
} }
struct msg_hist *p = first_msg_hist; if (entry->prev == NULL) {
first_msg_hist = p->next; msg_hist_first = entry->next;
if (first_msg_hist == NULL) { // history is becoming empty } else {
assert(msg_hist_len == 1); entry->prev->next = entry->next;
last_msg_hist = NULL;
} }
xfree(p->msg); if (entry == msg_hist_temp) {
hl_msg_free(p->multihl); msg_hist_temp = entry->next;
xfree(p); }
msg_hist_len--; hl_msg_free(entry->msg);
return OK; 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 > keep || (keep == 0 && msg_hist_first != NULL)) {
while (msg_hist_len > 0 && msg_hist_len > msg_hist_max) { msg_hist_len -= !msg_hist_first->temp;
(void)delete_first_msg(); 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_wait = messages_wait_new;
msg_hist_max = messages_history_new; msg_hist_max = messages_history_new;
check_msg_hist(); msg_hist_clear(msg_hist_max);
return OK; return OK;
} }
@@ -1153,11 +1167,7 @@ void ex_messages(exarg_T *eap)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_ALL
{ {
if (strcmp(eap->arg, "clear") == 0) { if (strcmp(eap->arg, "clear") == 0) {
int keep = eap->addr_count == 0 ? 0 : eap->line2; msg_hist_clear(eap->addr_count ? eap->line2 : 0);
while (msg_hist_len > keep) {
delete_first_msg();
}
return; return;
} }
@@ -1166,66 +1176,36 @@ void ex_messages(exarg_T *eap)
return; 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; Array entries = ARRAY_DICT_INIT;
for (; p != NULL; p = p->next) { MessageHistoryEntry *p = eap->skip ? msg_hist_temp : msg_hist_first;
if (kv_size(p->multihl) || (p->msg && p->msg[0])) { 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; Array entry = ARRAY_DICT_INIT;
ADD(entry, CSTR_TO_OBJ(p->kind)); ADD(entry, CSTR_TO_OBJ(p->kind));
Array content = ARRAY_DICT_INIT; Array content = ARRAY_DICT_INIT;
if (kv_size(p->multihl)) { for (uint32_t i = 0; i < kv_size(p->msg); i++) {
for (uint32_t i = 0; i < kv_size(p->multihl); i++) { HlMessageChunk chunk = kv_A(p->msg, i);
HlMessageChunk chunk = kv_A(p->multihl, i);
Array content_entry = ARRAY_DICT_INIT; Array content_entry = ARRAY_DICT_INIT;
ADD(content_entry, INTEGER_OBJ(chunk.hl_id ? syn_id2attr(chunk.hl_id) : 0)); 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, STRING_OBJ(copy_string(chunk.text, NULL)));
ADD(content_entry, INTEGER_OBJ(chunk.hl_id)); ADD(content_entry, INTEGER_OBJ(chunk.hl_id));
ADD(content, ARRAY_OBJ(content_entry)); 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(entry, ARRAY_OBJ(content));
ADD(entries, ARRAY_OBJ(entry)); 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); ui_call_msg_history_show(entries);
api_free_array(entries); api_free_array(entries);
msg_ext_history_visible = true; msg_ext_history_visible = true;
wait_return(false); 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; got_int = false;
if (hist) { 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 // 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) { 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 // 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)); 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)
@@ -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; const char *p = lastline ? lastline + 1 : str;
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;
} }
@@ -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) 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.
@@ -2690,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);
@@ -3170,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);
if (msg_ext_history || strequal(msg_ext_kind, "return_prompt")) {
api_free_array(*tofree); 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

@@ -28,10 +28,7 @@ enum {
VIM_DISCARDALL = 6, VIM_DISCARDALL = 6,
}; };
/// First message extern MessageHistoryEntry *msg_hist_last;
extern MessageHistoryEntry *first_msg_hist;
/// Last message
extern MessageHistoryEntry *last_msg_hist;
EXTERN bool msg_ext_need_clear INIT( = false); 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. // Set to true to force grouping a set of message chunks into a single `cmdline_show` event.

View File

@@ -14,9 +14,8 @@ typedef kvec_t(HlMessageChunk) HlMessage;
/// Message history for `:messages` /// Message history for `:messages`
typedef struct msg_hist { typedef struct msg_hist {
struct msg_hist *next; ///< Next message. 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) const char *kind; ///< Message kind (for msg_ext)
int hl_id; ///< Message highlighting. bool temp; ///< Temporary message since last command ("g<")
bool multiline; ///< Multiline message.
HlMessage multihl; ///< Multihl message.
} 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()

View File

@@ -70,8 +70,8 @@ describe('json_decode_string()', function()
local rettv = ffi.new('typval_T', { v_type = decode.VAR_UNKNOWN }) local rettv = ffi.new('typval_T', { v_type = decode.VAR_UNKNOWN })
eq(0, decode.json_decode_string(s, len, rettv)) eq(0, decode.json_decode_string(s, len, rettv))
eq(decode.VAR_UNKNOWN, rettv.v_type) eq(decode.VAR_UNKNOWN, rettv.v_type)
neq(nil, decode.last_msg_hist) neq(nil, decode.msg_hist_last)
eq(msg, ffi.string(decode.last_msg_hist.msg)) eq(msg, ffi.string(decode.msg_hist_last.msg.items[0].text.data))
end end
itp('does not overflow in error messages', function() itp('does not overflow in error messages', function()

View File

@@ -101,20 +101,19 @@ local function ga_alloc(itemsize, growsize)
end end
local function check_emsg(f, msg) local function check_emsg(f, msg)
local saved_last_msg_hist = lib.last_msg_hist local saved_msg_hist_last = lib.msg_hist_last
if saved_last_msg_hist == nil then if saved_msg_hist_last == nil then
saved_last_msg_hist = nil saved_msg_hist_last = nil
end end
local ret = { f() } 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 if msg ~= nil then
eq(msg, last_msg) eq(msg, ffi.string(lib.msg_hist_last.msg.items[0].text.data))
neq(saved_last_msg_hist, lib.last_msg_hist) neq(saved_msg_hist_last, lib.msg_hist_last)
else else
if saved_last_msg_hist ~= lib.last_msg_hist then if saved_msg_hist_last ~= lib.msg_hist_last then
eq(nil, last_msg) eq(nil, lib.msg_hist_last)
else else
eq(saved_last_msg_hist, lib.last_msg_hist) eq(saved_msg_hist_last, lib.msg_hist_last)
end end
end end
return unpack(ret) return unpack(ret)