From 62db5bebdd3289f6bbec108770313512e5705c49 Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Tue, 28 Apr 2026 16:05:06 +0200 Subject: [PATCH] fix(messages): message kind for :recover and swapfile attention #39444 Problem: No message kind and multiple events for :recover and (non-prompt) swapfile attention messages. Solution: Assign these the "list_cmd" and "wmsg" kind. --- runtime/doc/api-ui-events.txt | 2 +- src/nvim/memline.c | 28 +++- .../swapfile_preserve_recover_spec.lua | 125 +++++++++++++----- 3 files changed, 118 insertions(+), 37 deletions(-) diff --git a/runtime/doc/api-ui-events.txt b/runtime/doc/api-ui-events.txt index 1a7c913724..fdfd901f46 100644 --- a/runtime/doc/api-ui-events.txt +++ b/runtime/doc/api-ui-events.txt @@ -857,8 +857,8 @@ must handle. "lua_error" Error in |:lua| code "lua_print" |print()| from |:lua| code "progress" Progress message emitted by |nvim_echo()| - "rpc_error" Error response from |rpcrequest()| "quickfix" Quickfix navigation message + "rpc_error" Error response from |rpcrequest()| "search_cmd" Entered search command "search_count" Search count message ("S" flag of 'shortmess') "shell_cmd" |:!cmd| executed command diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 334ab935d2..4f929977d3 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -801,7 +801,9 @@ void ml_recover(bool checkext) } else { // several swapfiles found, choose // list the names of the swapfiles recover_names(fname, true, NULL, 0, NULL); - msg_putchar('\n'); + if (!ui_has(kUIMessages)) { + msg_putchar('\n'); + } i = prompt_for_input(_("Enter number of swap file to use (0 to quit): "), 0, false, NULL); if (i < 1 || i > len) { goto theend; @@ -849,6 +851,7 @@ void ml_recover(bool checkext) mfp->mf_page_size = MIN_SWAP_PAGE_SIZE; int hl_id = HLF_E; + msg_ext_set_kind("emsg"); // try to read block 0 if ((hp = mf_get(mfp, 0, 1)) == NULL) { msg_start(); @@ -920,6 +923,8 @@ void ml_recover(bool checkext) } } + msg_ext_set_kind("wmsg"); + msg_ext_skip_flush = true; home_replace(NULL, mfp->mf_fname, NameBuff, MAXPATHL, true); smsg(0, _("Using swap file \"%s\""), NameBuff); @@ -928,8 +933,10 @@ void ml_recover(bool checkext) } else { home_replace(NULL, curbuf->b_ffname, NameBuff, MAXPATHL, true); } + msg_putchar('\n'); smsg(0, _("Original file \"%s\""), NameBuff); msg_putchar('\n'); + msg_ext_skip_flush = false; // check date of swapfile and original file FileInfo org_file_info; @@ -1209,17 +1216,21 @@ void ml_recover(bool checkext) curbuf->b_flags |= BF_RECOVERED; check_cursor(curwin); + msg_ext_skip_flush = !got_int; recoverymode = false; if (got_int) { emsg(_("E311: Recovery Interrupted")); } else if (error) { no_wait_return++; - msg(">>>>>>>>>>>>>", 0); + msg_ext_set_kind("emsg"); + msg(">>>>>>>>>>>>>\n", 0); emsg(_("E312: Errors detected while recovering; look for lines starting with ???")); no_wait_return--; + msg_putchar('\n'); msg(_("See \":help E312\" for more information."), 0); - msg(">>>>>>>>>>>>>", 0); + msg("\n>>>>>>>>>>>>>", 0); } else { + msg_ext_set_kind("wmsg"); if (curbuf->b_changed) { msg(_("Recovery completed. You should check if everything is OK."), 0); msg_puts(_("\n(You might want to write out this file under another name\n")); @@ -1234,12 +1245,15 @@ void ml_recover(bool checkext) msg_puts(_("\nNote: process STILL RUNNING: ")); msg_outnum((int)char_to_long(b0p->b0_pid)); } - msg_puts("\n\n"); + if (!ui_has(kUIMessages)) { + msg_puts("\n\n"); + } cmdline_row = msg_row; } redraw_curbuf_later(UPD_NOT_VALID); theend: + msg_ext_skip_flush = false; xfree(fname_used); recoverymode = false; if (mfp != NULL) { @@ -1298,8 +1312,10 @@ int recover_names(char *fname, bool do_list, list_T *ret_list, int nr, char **fn #endif } + msg_ext_skip_flush = true; if (do_list) { // use msg() to start the scrolling properly + msg_ext_set_kind("list_cmd"); msg(_("Swap files found:"), 0); msg_putchar('\n'); } @@ -1448,6 +1464,7 @@ int recover_names(char *fname, bool do_list, list_T *ret_list, int nr, char **fn FreeWild(num_files, files); } } + msg_ext_skip_flush = false; xfree(dir_name.data); return file_count; } @@ -1548,7 +1565,7 @@ static time_t swapfile_info(char *fname, StringBuilder *msg) kv_printf(*msg, _(" dated: ")); } #else - msg_puts(_(" dated: ")); + kv_printf(*msg, _(" dated: ")); #endif x = file_info.stat.st_mtim.tv_sec; char ctime_buf[100]; // hopefully enough for every language @@ -3604,6 +3621,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ msg_reset_scroll(); } else { bool need_clear = false; + msg_ext_set_kind("wmsg"); msg_multiline(cbuf_as_string(msg.items, msg.size), 0, false, false, &need_clear); } no_wait_return--; diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua index dc2fce66a6..c3dc8fff7c 100644 --- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua +++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua @@ -142,37 +142,70 @@ describe("preserve and (R)ecover with custom 'directory'", function() test_recover(swappath1) end) - it('manual :recover with multiple swapfiles', function() - local swappath1 = setup_swapname() - eq('.swp', swappath1:match('%.[^.]+$')) - nvim0:close() - neq(nil, uv.fs_stat(swappath1)) - local swappath2 = swappath1:gsub('%.swp$', '.swo') - eq(true, uv.fs_copyfile(swappath1, swappath2)) - clear() - exec(init) - local screen = Screen.new(256, 40) - feed(':recover! ' .. testfile .. '') - screen:expect({ - any = { - '\nSwap files found:', - '\n In directory ', - vim.pesc('\n1. '), - vim.pesc('\n2. '), - vim.pesc('\nEnter number of swap file to use (0 to quit): ^'), - }, - none = vim.pesc('{18:^@}'), - }) - feed('2') - screen:expect({ - any = { - vim.pesc('\nRecovery completed.'), - vim.pesc('\n{6:Press ENTER or type command to continue}^'), - }, - }) - feed('') - expect('sometext') - end) + for _, ext in ipairs({ false, true }) do + it('manual :recover with multiple swapfiles' .. (ext and 'with ext_messages' or ''), function() + local swappath1 = setup_swapname() + eq('.swp', swappath1:match('%.[^.]+$')) + nvim0:close() + neq(nil, uv.fs_stat(swappath1)) + local swappath2 = swappath1:gsub('%.swp$', '.swo') + eq(true, uv.fs_copyfile(swappath1, swappath2)) + clear() + exec(init) + local screen, msg = Screen.new(256, 40, { ext_messages = ext }), nil + feed(':recover! ' .. testfile .. '') + if ext then + screen:expect({ + cmdline = { + { + content = { { '' } }, + pos = 0, + prompt = 'Enter number of swap file to use (0 to quit): ', + }, + }, + condition = function() + msg = msg or screen.messages[1] + eq(true, msg.content[1][2]:match('Swap.*none --') ~= nil) + eq('list_cmd', msg.kind) + screen.messages = {} + end, + }) + else + screen:expect({ + any = { + '\nSwap files found:', + '\n In directory ', + vim.pesc('\n1. '), + vim.pesc('\n2. '), + vim.pesc('\nEnter number of swap file to use (0 to quit): ^'), + }, + none = vim.pesc('{18:^@}'), + }) + end + feed('2') + if ext then + screen:expect({ + any = { 'sometext' }, + condition = function() + eq('wmsg', screen.messages[1].kind) + eq(true, screen.messages[1].content[1][2]:match('Using.*Original') ~= nil) + eq('wmsg', screen.messages[1].kind) + eq(true, screen.messages[2].content[1][2]:match('Recovery.*You might.*You may') ~= nil) + screen.messages = {} + end, + }) + else + screen:expect({ + any = { + vim.pesc('\nRecovery completed.'), + vim.pesc('\n{6:Press ENTER or type command to continue}^'), + }, + }) + end + feed('') + expect('sometext') + end) + end end) describe('swapfile detection', function() @@ -393,6 +426,36 @@ pcall(vim.cmd.edit, 'Xtest_swapredraw.lua') nvim1:close() end) + it('attention message kind', function() + exec(init) + command('edit Xfile1') + command('preserve') -- Make sure the swap file exists. + + local screen = Screen.new(nil, nil, { ext_messages = true }) + local nvim = n.new_session(true) + set_session(nvim) + screen:attach() + exec(init) + command('edit Xfile1') + command('autocmd! nvim.swapfile') -- Delete the default handler (which skips the dialog). + command('edit Xfile2 | bunload 1') -- Unload to get non-prompt attention message. + command('silent! call bufload("Xfile1")') + screen:expect({ + condition = function() + for _, msg in pairs(screen.messages) do + local ok = msg.content[1][2]:match('W325') + eq(true, ok and msg.kind == 'echomsg' or msg.kind == 'wmsg') + eq(true, (ok or msg.content[1][2]:match('Found a swap.*If you did')) ~= nil) + end + screen.messages = {} + end, + cmdline = { + { content = { { '' } }, hl = 'MoreMsg', pos = 0, prompt = 'Press any key to continue' }, + }, + }) + nvim:close() + end) + -- oldtest: Test_swap_prompt_splitwin() it('selecting "q" in the attention prompt', function() exec(init)