UI/ext_messages: learn more message kinds

ref #6201
This commit is contained in:
Justin M. Keyes 2019-05-09 23:54:04 +02:00
parent 7c9d4d971c
commit 3d1ed7c959
8 changed files with 175 additions and 49 deletions

View File

@ -672,29 +672,33 @@ of the cmdline.
["msg_show", kind, content, replace_last] ["msg_show", kind, content, replace_last]
Display a message to the user. Display a message to the user.
`kind` will be one of the following kind
`emsg`: Internal error message Name indicating the message kind:
`echo`: temporary message from plugin (|:echo|) "" (empty) Unknown, report a |feature-request|
`echomsg`: ordinary message from plugin (|:echomsg|) "confirm" |confirm()| or |:confirm| dialog
`echoerr`: error message from plugin (|:echoerr|) "confirm_sub" |:substitute| confirm dialog |:s_c|
`return_prompt`: |press-enter| prompt after a group of messages "emsg" Error (|errors|, internal error, |:throw|, …)
`quickfix`: Quickfix navigation message "echo" |:echo| message
`kind` can also be the empty string. The message is then some internal "echomsg" |:echomsg| message
informative or warning message, that hasn't yet been assigned a kind. "echoerr" |:echoerr| message
New message kinds can be added in later versions; clients should "return_prompt" |press-enter| prompt after a multiple messages
handle messages of an unknown kind just like empty kind. "quickfix" Quickfix navigation message
"wmsg" Warning ("search hit BOTTOM", |W10|, …)
New kinds may be added in the future; clients should treat unknown
kinds as the empty kind.
`content` will be an array of `[attr_id, text_chunk]` tuples, content
building up the message text of chunks of different highlights. Array of `[attr_id, text_chunk]` tuples, building up the message
No extra spacing should be added between chunks, the `text_chunk` by text of chunks of different highlights. No extra spacing should be
itself should contain any necessary whitespace. Messages can contain added between chunks, the `text_chunk` by itself contains any
line breaks `"\n"`. necessary whitespace. Messages can contain line breaks "\n".
`replace_last` controls how multiple messages should be displayed. replace_last
If `replace_last` is false, this message should be displayed together Decides how multiple messages should be displayed:
with all previous messages that are still visible. If `replace_last` false: Display the message together with all previous messages
is true, this message should replace the message in the most recent that are still visible.
`msg_show` call, but any other visible message should still remain. true: Replace the message in the most-recent `msg_show` call,
but any other visible message should still remain.
["msg_clear"] ["msg_clear"]
Clear all messages currently displayed by "msg_show". (Messages sent Clear all messages currently displayed by "msg_show". (Messages sent

View File

@ -3692,10 +3692,9 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
i = msg_scroll; i = msg_scroll;
msg_scroll = 0; /* truncate msg when msg_scroll = 0; /* truncate msg when
needed */ needed */
msg_no_more = TRUE; msg_no_more = true;
/* write message same highlighting as for msg_ext_set_kind("confirm_sub");
* wait_return */ smsg_attr(HL_ATTR(HLF_R), // Same highlight as wait_return().
smsg_attr(HL_ATTR(HLF_R),
_("replace with %s (y/n/a/q/l/^E/^Y)?"), sub); _("replace with %s (y/n/a/q/l/^E/^Y)?"), sub);
msg_no_more = FALSE; msg_no_more = FALSE;
msg_scroll = i; msg_scroll = i;

View File

@ -839,9 +839,10 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
sourcing_lnum = current_exception->throw_lnum; sourcing_lnum = current_exception->throw_lnum;
current_exception->throw_name = NULL; current_exception->throw_name = NULL;
discard_current_exception(); /* uses IObuff if 'verbose' */ discard_current_exception(); // uses IObuff if 'verbose'
suppress_errthrow = TRUE; suppress_errthrow = true;
force_abort = TRUE; force_abort = true;
msg_ext_set_kind("emsg"); // kind=emsg for :throw, exceptions. #9993
if (messages != NULL) { if (messages != NULL) {
do { do {

View File

@ -3008,6 +3008,8 @@ void give_warning(char_u *message, bool hl) FUNC_ATTR_NONNULL_ARG(1)
} else { } else {
keep_msg_attr = 0; keep_msg_attr = 0;
} }
msg_ext_set_kind("wmsg");
if (msg_attr((const char *)message, keep_msg_attr) && msg_scrolled == 0) { if (msg_attr((const char *)message, keep_msg_attr) && msg_scrolled == 0) {
set_keep_msg(message, keep_msg_attr); set_keep_msg(message, keep_msg_attr);
} }
@ -3348,6 +3350,7 @@ void display_confirm_msg(void)
// Avoid that 'q' at the more prompt truncates the message here. // Avoid that 'q' at the more prompt truncates the message here.
confirm_msg_used++; confirm_msg_used++;
if (confirm_msg != NULL) { if (confirm_msg != NULL) {
msg_ext_set_kind("confirm");
msg_puts_attr((const char *)confirm_msg, HL_ATTR(HLF_M)); msg_puts_attr((const char *)confirm_msg, HL_ATTR(HLF_M));
} }
confirm_msg_used--; confirm_msg_used--;

View File

@ -2248,6 +2248,7 @@ change_warning (
if (msg_row == Rows - 1) if (msg_row == Rows - 1)
msg_col = col; msg_col = col;
msg_source(HL_ATTR(HLF_W)); msg_source(HL_ATTR(HLF_W));
msg_ext_set_kind("wmsg");
MSG_PUTS_ATTR(_(w_readonly), HL_ATTR(HLF_W) | MSG_HIST); MSG_PUTS_ATTR(_(w_readonly), HL_ATTR(HLF_W) | MSG_HIST);
set_vim_var_string(VV_WARNINGMSG, _(w_readonly), -1); set_vim_var_string(VV_WARNINGMSG, _(w_readonly), -1);
msg_clr_eos(); msg_clr_eos();

View File

@ -277,7 +277,6 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext()
// Enums need a typecast to be used as array index (for Ultrix). // Enums need a typecast to be used as array index (for Ultrix).
#define HL_ATTR(n) highlight_attr[(int)(n)] #define HL_ATTR(n) highlight_attr[(int)(n)]
#define TERM_STR(n) term_strings[(int)(n)]
/// Maximum number of bytes in a multi-byte character. It can be one 32-bit /// Maximum number of bytes in a multi-byte character. It can be one 32-bit
/// character of up to 6 bytes, or one 16-bit character of up to three bytes /// character of up to 6 bytes, or one 16-bit character of up to three bytes

View File

@ -22,8 +22,129 @@ describe('ui/ext_messages', function()
[6] = {bold = true, reverse = true}, [6] = {bold = true, reverse = true},
}) })
end) end)
after_each(function()
os.remove('Xtest')
end)
it('supports :echoerr', function() it('msg_show kind=confirm,confirm_sub,emsg,wmsg', function()
feed('iline 1\nline 2<esc>')
-- kind=confirm
feed(':echo confirm("test")<cr>')
screen:expect{grid=[[
line 1 |
line ^2 |
{1:~ }|
{1:~ }|
{1:~ }|
]], messages={ {
content = {{"\ntest\n[O]k: ", 4}},
kind = 'confirm',
}}}
feed('<cr><cr>')
screen:expect{grid=[[
line 1 |
line ^2 |
{1:~ }|
{1:~ }|
{1:~ }|
]], messages={ {
content = { { "\ntest\n[O]k: ", 4 } },
kind = "confirm"
}, {
content = { { "1" } },
kind = "echo"
}, {
content = { { "Press ENTER or type command to continue", 4 } },
kind = "return_prompt"
} }}
feed('<cr><cr>')
-- kind=confirm_sub
feed(':%s/i/X/gc<cr>')
screen:expect{grid=[[
l{7:i}ne 1 |
l{8:i}ne ^2 |
{1:~ }|
{1:~ }|
{1:~ }|
]], attr_ids={
[1] = {bold = true, foreground = Screen.colors.Blue1},
[2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
[3] = {bold = true},
[4] = {bold = true, foreground = Screen.colors.SeaGreen4},
[5] = {foreground = Screen.colors.Blue1},
[6] = {bold = true, reverse = true},
[7] = {reverse = true},
[8] = {background = Screen.colors.Yellow},
}, messages={ {
content = { { "replace with X (y/n/a/q/l/^E/^Y)?", 4 } },
kind = "confirm_sub"
} }}
feed('nq')
-- kind=wmsg (editing readonly file)
command('write Xtest')
command('set readonly nohls')
feed('G$x')
screen:expect{grid=[[
line 1 |
{IGNORE}|
{1:~ }|
{1:~ }|
{1:~ }|
]], attr_ids={
[1] = {bold = true, foreground = Screen.colors.Blue1},
[7] = {foreground = Screen.colors.Red},
}, messages={ {
content = { { "W10: Warning: Changing a readonly file", 7 } },
kind = "wmsg"
}
}}
-- kind=wmsg ('wrapscan' after search reaches EOF)
feed('uG$/i<cr>')
screen:expect{grid=[[
l^ine 1 |
line 2 |
{1:~ }|
{1:~ }|
{1:~ }|
]], attr_ids={
[1] = {bold = true, foreground = Screen.colors.Blue1},
[2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
[3] = {bold = true},
[4] = {bold = true, foreground = Screen.colors.SeaGreen4},
[5] = {foreground = Screen.colors.Blue1},
[6] = {bold = true, reverse = true},
[7] = {foreground = Screen.colors.Red},
}, messages={ {
content = { { "search hit BOTTOM, continuing at TOP", 7 } },
kind = "wmsg"
} }}
-- kind=emsg after :throw
feed(':throw "foo"<cr>')
screen:expect{grid=[[
l^ine 1 |
line 2 |
{1:~ }|
{1:~ }|
{1:~ }|
]], messages={ {
content = { { "Error detected while processing :", 2 } },
kind = "emsg"
}, {
content = { { "E605: Exception not caught: foo", 2 } },
kind = ""
}, {
content = { { "Press ENTER or type command to continue", 4 } },
kind = "return_prompt"
} }
}
end)
it(':echoerr', function()
feed(':echoerr "raa"<cr>') feed(':echoerr "raa"<cr>')
screen:expect{grid=[[ screen:expect{grid=[[
^ | ^ |
@ -142,7 +263,7 @@ describe('ui/ext_messages', function()
}} }}
end) end)
it('supports showmode', function() it('&showmode', function()
command('imap <f2> <cmd>echomsg "stuff"<cr>') command('imap <f2> <cmd>echomsg "stuff"<cr>')
feed('i') feed('i')
screen:expect{grid=[[ screen:expect{grid=[[
@ -230,7 +351,7 @@ describe('ui/ext_messages', function()
}} }}
end) end)
it('supports showmode with recording message', function() it('&showmode with macro-recording message', function()
feed('qq') feed('qq')
screen:expect{grid=[[ screen:expect{grid=[[
^ | ^ |
@ -268,7 +389,7 @@ describe('ui/ext_messages', function()
]]) ]])
end) end)
it('shows recording message with noshowmode', function() it('shows macro-recording message with &noshowmode', function()
command("set noshowmode") command("set noshowmode")
feed('qq') feed('qq')
-- also check mode to avoid immediate success -- also check mode to avoid immediate success
@ -308,7 +429,7 @@ describe('ui/ext_messages', function()
]], mode="normal"} ]], mode="normal"}
end) end)
it('supports showcmd and ruler', function() it('supports &showcmd and &ruler', function()
command('set showcmd ruler') command('set showcmd ruler')
screen:expect{grid=[[ screen:expect{grid=[[
^ | ^ |

View File

@ -74,6 +74,7 @@
local global_helpers = require('test.helpers') local global_helpers = require('test.helpers')
local deepcopy = global_helpers.deepcopy local deepcopy = global_helpers.deepcopy
local shallowcopy = global_helpers.shallowcopy local shallowcopy = global_helpers.shallowcopy
local concat_tables = global_helpers.concat_tables
local helpers = require('test.functional.helpers')(nil) local helpers = require('test.functional.helpers')(nil)
local request, run_session = helpers.request, helpers.run_session local request, run_session = helpers.request, helpers.run_session
local eq = helpers.eq local eq = helpers.eq
@ -413,26 +414,23 @@ screen:redraw_debug() to show all intermediate screen states. ]])
end end
end end
-- Extension features. The default expectations should cover the case of -- UI extensions. The default expectations should cover the case of
-- the ext_ feature being disabled, or the feature currently not activated -- the ext_ feature being disabled, or the feature currently not activated
-- (for instance no external cmdline visible). Some extensions require -- (e.g. no external cmdline visible). Some extensions require
-- preprocessing to represent highlights in a reproducible way. -- preprocessing to represent highlights in a reproducible way.
local extstate = self:_extstate_repr(attr_state) local extstate = self:_extstate_repr(attr_state)
if expected['mode'] ~= nil then
-- convert assertion errors into invalid screen state descriptions extstate['mode'] = self.mode
local status, res = pcall(function() end
for _, k in ipairs(ext_keys) do -- Convert assertion errors into invalid screen state descriptions.
-- Empty states is considered the default and need not be mentioned for _, k in ipairs(concat_tables(ext_keys, {'mode'})) do
if not (expected[k] == nil and isempty(extstate[k])) then -- Empty states are considered the default and need not be mentioned.
eq(expected[k], extstate[k], k) if (not (expected[k] == nil and isempty(extstate[k]))) then
local status, res = pcall(eq, expected[k], extstate[k], k)
if not status then
return (tostring(res)..'\nHint: full state of "'..k..'":\n '..inspect(extstate[k]))
end end
end end
if expected.mode ~= nil then
eq(expected.mode, self.mode, "mode")
end
end)
if not status then
return tostring(res)
end end
end, expected) end, expected)
end end