From ff68fd6b8a84ce83c14358db1d70a562f1524ebe Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Tue, 21 Apr 2026 22:11:41 +0200 Subject: [PATCH] fix(messages): "progress" kind for busy messages #39280 Problem: The "Scanning:" completion, bufwrite, and indent (there may be more) messages which indicate progress can use the "progress" kind for their msg_show event. Indent message does not have a kind. Solution: Emit these messages with the "progress" kind. Set the message id to the replaced kind so that a UI knows to replace it (and to provide a migration path in case a UI was distinguishing these messages for whatever reason). --- runtime/doc/api-ui-events.txt | 1 - runtime/doc/news.txt | 2 ++ src/nvim/bufwrite.c | 6 +----- src/nvim/fileio.c | 7 +++++-- src/nvim/indent.c | 21 ++++++++++++--------- src/nvim/insexpand.c | 17 ++++------------- src/nvim/message.c | 27 ++++++++++++++++++++++++++- test/functional/ui/messages_spec.lua | 22 +++++++++++++++++++--- test/functional/ui/screen.lua | 6 ++++++ test/old/testdir/setup.vim | 1 - 10 files changed, 75 insertions(+), 35 deletions(-) diff --git a/runtime/doc/api-ui-events.txt b/runtime/doc/api-ui-events.txt index 07cc6efed7..1a7c913724 100644 --- a/runtime/doc/api-ui-events.txt +++ b/runtime/doc/api-ui-events.txt @@ -846,7 +846,6 @@ must handle. "empty" Empty message (`:echo ""`), with empty `content`. Should clear messages sharing the 'cmdheight' area if it is the only message in a batch. - "bufwrite" |:write| message "confirm" Message preceding a prompt (|:confirm|, |confirm()|, |inputlist()|, |z=|, …) "emsg" Error (|errors|, internal error, |:throw|, …) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 284d83f094..9da054ed87 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -65,6 +65,8 @@ API • |nvim_create_autocmd()|, |nvim_exec_autocmds()| and |nvim_clear_autocmds()| no longer treat an empty non-nil pattern as nil. • |nvim_clear_autocmds()| no longer treats an empty array event as nil. +• |ui-messages| `msg_show.bufwrite` and `msg_show.completion` messages are now + `msg_show.progress` events. DIAGNOSTICS diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c index f45d01a289..4a14a1ecb2 100644 --- a/src/nvim/bufwrite.c +++ b/src/nvim/bufwrite.c @@ -1081,7 +1081,6 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en msg_scroll = true; // don't overwrite previous file message } if (!filtering) { - msg_ext_set_kind("bufwrite"); // show that we are busy #ifndef UNIX filemess(buf, sfname, ""); @@ -1708,10 +1707,7 @@ restore_backup: xstrlcat(IObuff, shortmess(SHM_WRI) ? _(" [w]") : _(" written"), IOSIZE); } } - - msg_ext_set_kind("bufwrite"); - msg_ext_overwrite = true; - set_keep_msg(msg_trunc(IObuff, false, 0), 0); + set_keep_msg(msg_progress(IObuff, "bufwrite", "success", 0, true, true), 0); } // When written everything correctly: reset 'modified'. Unless not diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 5b659cb91d..69a9ba0e2d 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -131,9 +131,12 @@ void filemess(buf_T *buf, char *name, char *s) msg_scroll = msg_scroll_save; msg_scrolled_ign = true; // may truncate the message to avoid a hit-return prompt - msg_outtrans(msg_may_trunc(false, IObuff), 0, false); + if (*s == NUL) { + msg_progress(IObuff, "bufwrite", "running", 0, false, true); + } else { + msg_outtrans(msg_may_trunc(false, IObuff), 0, false); + } msg_clr_eos(); - ui_flush(); msg_scrolled_ign = false; } diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 806526b734..b10173135e 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -1004,19 +1004,20 @@ void op_reindent(oparg_T *oap, Indenter how) if (i > 1 && (i % 50 == 0 || i == oap->line_count - 1) && oap->line_count > p_report) { - smsg(0, _("%" PRId64 " lines to indent... "), (int64_t)i); + snprintf(IObuff, IOSIZE, _("%" PRId64 " lines to indent... "), (int64_t)i); + // Restore cursor to avoid redrawing curwin in msg_show callback. + linenr_T save_lnum = curwin->w_cursor.lnum; + curwin->w_cursor.lnum = start_lnum; + msg_progress(IObuff, "indent", "running", 0, true, false); + curwin->w_cursor.lnum = save_lnum; } // Be vi-compatible: For lisp indenting the first line is not // indented, unless there is only one line. - if (i != oap->line_count - 1 || oap->line_count == 1 - || how != get_lisp_indent) { + if (i != oap->line_count - 1 || oap->line_count == 1 || how != get_lisp_indent) { char *l = skipwhite(get_cursor_line_ptr()); - if (*l == NUL) { // empty or blank line - amount = 0; - } else { - amount = how(); // get the indent for this line - } + // Get indent for this line unless it is blank. + amount = *l == NUL ? 0 : how(); if (amount >= 0 && set_indent(amount, 0)) { // did change the indent, call changed_lines() later if (first_changed == 0) { @@ -1047,7 +1048,9 @@ void op_reindent(oparg_T *oap, Indenter how) if (oap->line_count > p_report) { i = oap->line_count - (i + 1); - smsg(0, NGETTEXT("%" PRId64 " line indented ", "%" PRId64 " lines indented ", i), (int64_t)i); + snprintf(IObuff, IOSIZE, + NGETTEXT("%" PRId64 " line indented ", "%" PRId64 " lines indented ", i), (int64_t)i); + msg_progress(IObuff, "indent", "success", 0, true, false); } if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { // set '[ and '] marks diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 6e986eb5fe..964c2517d6 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -1944,11 +1944,8 @@ static void ins_compl_files(int count, char **files, bool thesaurus, int flags, for (int i = 0; i < count && !got_int && !ins_compl_interrupted(); i++) { FILE *fp = os_fopen(files[i], "r"); // open dictionary file if (flags != DICT_EXACT && !shortmess(SHM_COMPLETIONSCAN) && !compl_autocomplete) { - msg_hist_off = true; // reset in msg_trunc() - msg_ext_set_kind("completion"); - vim_snprintf(IObuff, IOSIZE, - _("Scanning dictionary: %s"), files[i]); - msg_trunc(IObuff, true, HLF_R); + vim_snprintf(IObuff, IOSIZE, _("Scanning dictionary: %s"), files[i]); + msg_progress(IObuff, "completion", "running", HLF_R, false, true); } if (fp == NULL) { @@ -3823,16 +3820,13 @@ static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_ar st->dict_f = DICT_EXACT; } if (!shortmess(SHM_COMPLETIONSCAN) && !compl_autocomplete) { - msg_hist_off = true; // reset in msg_trunc() - msg_ext_overwrite = true; - msg_ext_set_kind("completion"); vim_snprintf(IObuff, IOSIZE, _("Scanning: %s"), st->ins_buf->b_fname == NULL ? buf_spname(st->ins_buf) : st->ins_buf->b_sfname == NULL ? st->ins_buf->b_fname : st->ins_buf->b_sfname); - msg_trunc(IObuff, true, HLF_R); + msg_progress(IObuff, "completion", "running", HLF_R, false, true); } } else if (*st->e_cpt == NUL) { status = INS_COMPL_CPT_END; @@ -3865,11 +3859,8 @@ static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_ar } else if (*st->e_cpt == ']' || *st->e_cpt == 't') { compl_type = CTRL_X_TAGS; if (!shortmess(SHM_COMPLETIONSCAN) && !compl_autocomplete) { - msg_ext_set_kind("completion"); - msg_hist_off = true; // reset in msg_trunc() - msg_ext_overwrite = true; vim_snprintf(IObuff, IOSIZE, "%s", _("Scanning tags.")); - msg_trunc(IObuff, true, HLF_R); + msg_progress(IObuff, "completion", "running", HLF_R, false, true); } } } diff --git a/src/nvim/message.c b/src/nvim/message.c index 4748bd1db9..1d7c24ce02 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -14,6 +14,7 @@ #include "klib/kvec.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/vim.h" #include "nvim/ascii_defs.h" #include "nvim/autocmd.h" #include "nvim/buffer_defs.h" @@ -387,7 +388,6 @@ MsgID msg_multihl(MsgID id, HlMessage hl_msg, const char *kind, bool history, bo msg_clr_eos(); bool need_clear = false; bool hl_msg_updated = false; - msg_ext_history = history; if (kind != NULL) { msg_ext_set_kind(kind); } @@ -1099,6 +1099,31 @@ char *msg_may_trunc(bool force, char *s) return s; } +char *msg_progress(char *s, char *id, char *status, int hl_id, bool hist, bool trunc) +{ + Error err = ERROR_INIT; + Dict(echo_opts) opts = { + .kind = cstr_as_string("progress"), + .source = cstr_as_string("nvim"), + .status = cstr_as_string(status), + .id = CSTR_AS_OBJ(id), + }; + if (hist && (!trunc || ui_has(kUIMessages))) { + msg_hist_add(s, -1, 0); + } + if (trunc) { + s = msg_may_trunc(false, s); + } + MAXSIZE_TEMP_ARRAY(chunk, 2); + MAXSIZE_TEMP_ARRAY(chunks, 1); + ADD_C(chunk, CSTR_AS_OBJ(s)); + ADD_C(chunk, INTEGER_OBJ(hl_id)); + ADD_C(chunks, ARRAY_OBJ(chunk)); + nvim_echo(chunks, false, &opts, &err); + ui_flush(); + return s; +} + void hl_msg_free(HlMessage hl_msg) { for (size_t i = 0; i < kv_size(hl_msg); i++) { diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index bba60513f7..16030dd82d 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -95,7 +95,7 @@ describe('ui/ext_messages', function() {1:~ }|*3 ]], messages = { - { content = { { writemsg } }, history = true, kind = 'bufwrite' }, + { content = { { writemsg } }, history = true, id = 'bufwrite', kind = 'progress' }, { content = { { 'W10: Warning: Changing a readonly file', 19, 'WarningMsg' } }, history = true, @@ -576,6 +576,20 @@ describe('ui/ext_messages', function() ]], messages = { { content = { { ' foldclose=' } }, history = true, kind = 'list_cmd' } }, }) + + -- Indent message + feed('A2\nline 3gg=G') + screen:expect({ + grid = [[ + ^line 1 | + line 2 | + line 3 | + {1:~ }|*2 + ]], + messages = { + { content = { { '3 lines indented ' } }, history = true, id = 'indent', kind = 'progress' }, + }, + }) end) it(':echoerr', function() @@ -1383,7 +1397,8 @@ stack traceback: messages = { { content = { { string.format('"%s" [New] 0L, 0B written', fname) } }, - kind = 'bufwrite', + kind = 'progress', + id = 'bufwrite', history = true, }, }, @@ -1629,7 +1644,8 @@ stack traceback: messages = { { content = { { 'Scanning tags.', 6, 'Question' } }, - kind = 'completion', + kind = 'progress', + id = 'completion', }, }, showmode = { diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 64701d2175..a64565d7cb 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -1453,6 +1453,12 @@ function Screen:_handle_msg_show(kind, chunks, replace_last, history, append, id if not replace_last or pos == 0 then pos = pos + 1 end + for i, msg in pairs(self.messages) do + if id ~= -1 and msg.id == id then + pos = i + break + end + end self.messages[pos] = { kind = kind, content = chunks, diff --git a/test/old/testdir/setup.vim b/test/old/testdir/setup.vim index 37c80177bc..efc63d24b1 100644 --- a/test/old/testdir/setup.vim +++ b/test/old/testdir/setup.vim @@ -18,7 +18,6 @@ if exists('s:did_load') set laststatus=1 set listchars=eol:$ set maxsearchcount=99 - set messagesopt=hit-enter,history:500 set mousemodel=extend set nohidden nosmarttab noautoindent noautoread set nohlsearch noincsearch