mirror of
https://github.com/neovim/neovim.git
synced 2025-10-04 17:06:30 +00:00
feat(api): nvim_echo can emit Progress messages/events #34846
Problem: Nvim does not have a core concept for indicating "progress" of long-running tasks. The LspProgress event is specific to LSP. Solution: - `nvim_echo` can emit `kind="progress"` messages. - Emits a `Progress` event. - Includes new fields (id, status, percent) in the `msg_show` ui-event. - The UI is expected to overwrite any message having the same id. - Messages have a globally unique ID. - `nvim_echo` returns the message ID. - `nvim_echo(… {id=…})` updates existing messages. Example: local grp = vim.api.nvim_create_augroup("Msg", {clear = true}) vim.api.nvim_create_autocmd('Progress', { pattern={"term"}, group = grp, callback = function(ev) print(string.format('event fired: %s', vim.inspect(ev))..'\n') end }) -- require('vim._extui').enable({enable=true, msg={target='msg', timeout=1000}}) vim.api.nvim_echo({{'searching'}}, true, {kind='progress', percent=80, status='running', title="terminal(ripgrep)"}) local id = vim.api.nvim_echo({{'searching'}}, true, {kind='progress', status='running', percent=10, title="terminal(ripgrep)"}) vim.api.nvim_echo({}, true, {id = id, kind='progress', percent=20, status = 'running', title='find tests'}) vim.api.nvim_echo({}, true, {id = id, kind='progress', status='running', percent=70}) vim.api.nvim_echo({{'complete'}}, true, {id = id, kind='progress', status='success', percent=100, title="find tests"}) Followups: - Integrate with 'statusline' by listening to the Progress autocmd event. - Integrate progress ui-event with `vim._extui`.
This commit is contained in:
@@ -825,7 +825,7 @@ will be set to zero, but can be changed and used for the replacing cmdline or
|
||||
message window. Cmdline state is emitted as |ui-cmdline| events, which the UI
|
||||
must handle.
|
||||
|
||||
["msg_show", kind, content, replace_last, history, append] ~
|
||||
["msg_show", kind, content, replace_last, history, append, msg_id, progress] ~
|
||||
Display a message to the user.
|
||||
|
||||
kind
|
||||
@@ -845,6 +845,7 @@ must handle.
|
||||
"list_cmd" List output for various commands (|:ls|, |:set|, …)
|
||||
"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
|
||||
"search_cmd" Entered search command
|
||||
@@ -881,6 +882,22 @@ must handle.
|
||||
True if the message should be appeneded to the previous message,
|
||||
rather than started on a new line. Is set for |:echon|.
|
||||
|
||||
msg_id
|
||||
Unique identifier for the message. It can either be an integer or
|
||||
string. When message of same id appears it should replace the older message.
|
||||
|
||||
progress
|
||||
Progress-message properties:
|
||||
• title: Title string of the progress message.
|
||||
• status: Status of the progress message. Can contain one of
|
||||
the following values
|
||||
• success: The progress item completed successfully
|
||||
• running: The progress is ongoing
|
||||
• failed: The progress item failed
|
||||
• cancel: The progressing process should be canceled.
|
||||
• percent: How much progress is done on the progress
|
||||
message
|
||||
|
||||
["msg_clear"] ~
|
||||
Clear all messages currently displayed by "msg_show", emitted after
|
||||
clearing the screen (messages sent by other "msg_" events below should
|
||||
|
@@ -662,6 +662,7 @@ nvim_echo({chunks}, {history}, {opts}) *nvim_echo()*
|
||||
(optional) name or ID `hl_group`.
|
||||
• {history} (`boolean`) if true, add to |message-history|.
|
||||
• {opts} (`vim.api.keyset.echo_opts`) Optional parameters.
|
||||
• id: message id for updating existing message.
|
||||
• err: Treat the message like `:echoerr`. Sets `hl_group`
|
||||
to |hl-ErrorMsg| by default.
|
||||
• kind: Set the |ui-messages| kind with which this message
|
||||
@@ -669,6 +670,22 @@ nvim_echo({chunks}, {history}, {opts}) *nvim_echo()*
|
||||
• verbose: Message is controlled by the 'verbose' option.
|
||||
Nvim invoked with `-V3log` will write the message to the
|
||||
"log" file instead of standard output.
|
||||
• title: The title for |progress-message|.
|
||||
• status: Current status of the |progress-message|. Can be
|
||||
one of the following values
|
||||
• success: The progress item completed successfully
|
||||
• running: The progress is ongoing
|
||||
• failed: The progress item failed
|
||||
• cancel: The progressing process should be canceled.
|
||||
note: Cancel needs to be handled by progress initiator
|
||||
by listening for the `Progress` event
|
||||
• percent: How much progress is done on the progress
|
||||
message
|
||||
• data: dictionary containing additional information
|
||||
|
||||
Return: ~
|
||||
(`integer|string`) Message id.
|
||||
• -1 means nvim_echo didn't show a message
|
||||
|
||||
nvim_eval_statusline({str}, {opts}) *nvim_eval_statusline()*
|
||||
Evaluates statusline string.
|
||||
|
@@ -787,6 +787,31 @@ ModeChanged After changing the mode. The pattern is
|
||||
:au ModeChanged [vV\x16]*:* let &l:rnu = mode() =~# '^[vV\x16]'
|
||||
:au ModeChanged *:[vV\x16]* let &l:rnu = mode() =~# '^[vV\x16]'
|
||||
:au WinEnter,WinLeave * let &l:rnu = mode() =~# '^[vV\x16]'
|
||||
Progress *Progress*
|
||||
After a progress message is created or updated via
|
||||
`nvim_echo`. The pattern is matched against
|
||||
title of the message. The |event-data| contains:
|
||||
id: id of the message
|
||||
text: text of the message
|
||||
title: title of the progress message
|
||||
status: status of the progress message
|
||||
percent: how much progress has been
|
||||
made for this progress item
|
||||
Usage example:
|
||||
>
|
||||
vim.api.nvim_create_autocmd('Progress', {
|
||||
pattern={"term"},
|
||||
callback = function(ev)
|
||||
print(string.format('event fired: %s', vim.inspect(ev)))
|
||||
end
|
||||
})
|
||||
local id = vim.api.nvim_echo({{'searching...'}}, true,
|
||||
{kind='progress', status='running', percent=10, title="term"})
|
||||
vim.api.nvim_echo({{'searching'}}, true,
|
||||
{id = id, kind='progress', status='running', percent=50, title="term"})
|
||||
vim.api.nvim_echo({{'done'}}, true,
|
||||
{id = id, kind='progress', status='success', percent=100, title="term"})
|
||||
|
||||
< *OptionSet*
|
||||
OptionSet After setting an option (except during
|
||||
|startup|). The |autocmd-pattern| is matched
|
||||
|
@@ -845,4 +845,29 @@ The |g<| command can be used to see the last page of previous command output.
|
||||
This is especially useful if you accidentally typed <Space> at the hit-enter
|
||||
prompt.
|
||||
|
||||
==============================================================================
|
||||
4. PROGRESS MESSAGE *progress-message*
|
||||
|
||||
Nvim can emit progress-message, which are a special kind of |ui-messages|
|
||||
used to report the state of long-running tasks.
|
||||
|
||||
Progress messages are created or updated using |nvim_echo()| with `kind='progress'`
|
||||
and the related options. Each message has a unique `msg_id`. A subsequent
|
||||
message with the same `msg_id` replaces the older one.
|
||||
|
||||
Events: ~
|
||||
• msg_show |ui-messages| event is fired for ext-ui upon creation/update of a
|
||||
progress-message
|
||||
• Updating or creating a progress message also triggers the |Progress| autocommand.
|
||||
|
||||
Example: >
|
||||
local id = vim.api.nvim_echo({{'searching...'}}, true,
|
||||
{kind='progress', status='running', percent=10, title="term"})
|
||||
vim.api.nvim_echo({{'searching'}}, true,
|
||||
{id=id, kind='progress', status='running', percent=50, title="term"})
|
||||
vim.api.nvim_echo({{'done'}}, true,
|
||||
{id=id, kind='progress', status='success', percent=100, title="term"})
|
||||
<
|
||||
See also: |nvim_echo()| |ui-messages| |Progress|
|
||||
|
||||
vim:tw=78:ts=8:noet:ft=help:norl:
|
||||
|
@@ -139,9 +139,10 @@ API
|
||||
actually trusted.
|
||||
• Added |vim.lsp.is_enabled()| to check if a given LSP config has been enabled
|
||||
by |vim.lsp.enable()|.
|
||||
• |nvim_echo()| can set the |ui-messages| kind with which to emit the message.
|
||||
• |nvim_ui_send()| writes arbitrary data to a UI's stdout. Use this to write
|
||||
escape sequences to the terminal when Nvim is running in the |TUI|.
|
||||
• |nvim_echo()| can set the |ui-messages| kind with which to emit the message.
|
||||
• |nvim_echo()| can create |Progress| messages
|
||||
|
||||
BUILD
|
||||
|
||||
@@ -189,6 +190,8 @@ EVENTS
|
||||
|
||||
• |CmdlineLeavePre| triggered before preparing to leave the command line.
|
||||
• New `append` paremeter for |ui-messages| `msg_show` event.
|
||||
• New `msg_id` and `progress` paremeter for |ui-messages| `msg_show` event.
|
||||
• Creating or updating a progress message with |nvim_echo()| triggers a |Progress| event.
|
||||
|
||||
HIGHLIGHTS
|
||||
|
||||
|
@@ -2545,6 +2545,7 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
|OptionSet|,
|
||||
|PackChanged|,
|
||||
|PackChangedPre|,
|
||||
|Progress|,
|
||||
|QuickFixCmdPost|,
|
||||
|QuickFixCmdPre|,
|
||||
|QuitPre|,
|
||||
|
15
runtime/lua/vim/_meta/api.lua
generated
15
runtime/lua/vim/_meta/api.lua
generated
@@ -1100,10 +1100,25 @@ function vim.api.nvim_del_var(name) end
|
||||
--- the (optional) name or ID `hl_group`.
|
||||
--- @param history boolean if true, add to `message-history`.
|
||||
--- @param opts vim.api.keyset.echo_opts Optional parameters.
|
||||
--- - id: message id for updating existing message.
|
||||
--- - err: Treat the message like `:echoerr`. Sets `hl_group` to `hl-ErrorMsg` by default.
|
||||
--- - kind: Set the `ui-messages` kind with which this message will be emitted.
|
||||
--- - verbose: Message is controlled by the 'verbose' option. Nvim invoked with `-V3log`
|
||||
--- will write the message to the "log" file instead of standard output.
|
||||
--- - title: The title for `progress-message`.
|
||||
--- - status: Current status of the `progress-message`. Can be
|
||||
--- one of the following values
|
||||
--- - success: The progress item completed successfully
|
||||
--- - running: The progress is ongoing
|
||||
--- - failed: The progress item failed
|
||||
--- - cancel: The progressing process should be canceled.
|
||||
--- note: Cancel needs to be handled by progress
|
||||
--- initiator by listening for the `Progress` event
|
||||
--- - percent: How much progress is done on the progress
|
||||
--- message
|
||||
--- - data: dictionary containing additional information
|
||||
--- @return integer|string # Message id.
|
||||
--- - -1 means nvim_echo didn't show a message
|
||||
function vim.api.nvim_echo(chunks, history, opts) end
|
||||
|
||||
--- @deprecated
|
||||
|
6
runtime/lua/vim/_meta/api_keysets.lua
generated
6
runtime/lua/vim/_meta/api_keysets.lua
generated
@@ -165,6 +165,7 @@ error('Cannot require a meta file')
|
||||
--- |'OptionSet'
|
||||
--- |'PackChanged'
|
||||
--- |'PackChangedPre'
|
||||
--- |'Progress'
|
||||
--- |'QuickFixCmdPost'
|
||||
--- |'QuickFixCmdPre'
|
||||
--- |'QuitPre'
|
||||
@@ -233,6 +234,11 @@ error('Cannot require a meta file')
|
||||
--- @field err? boolean
|
||||
--- @field verbose? boolean
|
||||
--- @field kind? string
|
||||
--- @field id? integer|string
|
||||
--- @field title? string
|
||||
--- @field status? string
|
||||
--- @field percent? integer
|
||||
--- @field data? table<string,any>
|
||||
|
||||
--- @class vim.api.keyset.empty
|
||||
|
||||
|
1
runtime/lua/vim/_meta/options.lua
generated
1
runtime/lua/vim/_meta/options.lua
generated
@@ -2224,6 +2224,7 @@ vim.go.ei = vim.go.eventignore
|
||||
--- `OptionSet`,
|
||||
--- `PackChanged`,
|
||||
--- `PackChangedPre`,
|
||||
--- `Progress`,
|
||||
--- `QuickFixCmdPost`,
|
||||
--- `QuickFixCmdPre`,
|
||||
--- `QuitPre`,
|
||||
|
@@ -395,7 +395,8 @@ local function new_progress_report(title)
|
||||
local progress = kind == 'end' and 'done' or ('%3d%%'):format(percent)
|
||||
local details = (' %s %s'):format(title, fmt:format(...))
|
||||
local chunks = { { 'vim.pack', 'ModeMsg' }, { ': ' }, { progress, 'WarningMsg' }, { details } }
|
||||
vim.api.nvim_echo(chunks, true, { kind = 'progress' })
|
||||
-- TODO: need to add support for progress-messages api
|
||||
api.nvim_echo(chunks, true, {})
|
||||
-- Force redraw to show installation progress during startup
|
||||
vim.cmd.redraw({ bang = true })
|
||||
end)
|
||||
|
@@ -336,6 +336,11 @@ typedef struct {
|
||||
Boolean err;
|
||||
Boolean verbose;
|
||||
String kind;
|
||||
Union(Integer, String) id;
|
||||
String title;
|
||||
String status;
|
||||
Integer percent;
|
||||
DictOf(Object) data;
|
||||
} Dict(echo_opts);
|
||||
|
||||
typedef struct {
|
||||
|
@@ -164,7 +164,8 @@ void wildmenu_select(Integer selected)
|
||||
void wildmenu_hide(void)
|
||||
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
|
||||
|
||||
void msg_show(String kind, Array content, Boolean replace_last, Boolean history, Boolean append)
|
||||
void msg_show(String kind, Array content, Boolean replace_last, Boolean history, Boolean append,
|
||||
Object id, Dict progress)
|
||||
FUNC_API_SINCE(6) FUNC_API_FAST FUNC_API_REMOTE_ONLY;
|
||||
void msg_clear(void)
|
||||
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
|
||||
|
@@ -758,14 +758,31 @@ void nvim_set_vvar(String name, Object value, Error *err)
|
||||
/// the (optional) name or ID `hl_group`.
|
||||
/// @param history if true, add to |message-history|.
|
||||
/// @param opts Optional parameters.
|
||||
/// - id: message id for updating existing message.
|
||||
/// - err: Treat the message like `:echoerr`. Sets `hl_group` to |hl-ErrorMsg| by default.
|
||||
/// - kind: Set the |ui-messages| kind with which this message will be emitted.
|
||||
/// - verbose: Message is controlled by the 'verbose' option. Nvim invoked with `-V3log`
|
||||
/// will write the message to the "log" file instead of standard output.
|
||||
void nvim_echo(ArrayOf(Tuple(String, *HLGroupID)) chunks, Boolean history, Dict(echo_opts) *opts,
|
||||
Error *err)
|
||||
/// - title: The title for |progress-message|.
|
||||
/// - status: Current status of the |progress-message|. Can be
|
||||
/// one of the following values
|
||||
/// - success: The progress item completed successfully
|
||||
/// - running: The progress is ongoing
|
||||
/// - failed: The progress item failed
|
||||
/// - cancel: The progressing process should be canceled.
|
||||
/// note: Cancel needs to be handled by progress
|
||||
/// initiator by listening for the `Progress` event
|
||||
/// - percent: How much progress is done on the progress
|
||||
/// message
|
||||
/// - data: dictionary containing additional information
|
||||
/// @return Message id.
|
||||
/// - -1 means nvim_echo didn't show a message
|
||||
Union(Integer, String) nvim_echo(ArrayOf(Tuple(String, *HLGroupID)) chunks, Boolean history,
|
||||
Dict(echo_opts) *opts,
|
||||
Error *err)
|
||||
FUNC_API_SINCE(7)
|
||||
{
|
||||
MsgID id = INTEGER_OBJ(-1);
|
||||
HlMessage hl_msg = parse_hl_msg(chunks, opts->err, err);
|
||||
if (ERROR_SET(err)) {
|
||||
goto error;
|
||||
@@ -778,20 +795,52 @@ void nvim_echo(ArrayOf(Tuple(String, *HLGroupID)) chunks, Boolean history, Dict(
|
||||
kind = opts->err ? "echoerr" : history ? "echomsg" : "echo";
|
||||
}
|
||||
|
||||
msg_multihl(hl_msg, kind, history, opts->err);
|
||||
bool is_progress = strequal(kind, "progress");
|
||||
|
||||
VALIDATE(is_progress
|
||||
|| (opts->status.size == 0 && opts->title.size == 0 && opts->percent == 0
|
||||
&& opts->data.size == 0),
|
||||
"%s",
|
||||
"title, status, percent and data fields can only be used with progress messages",
|
||||
{
|
||||
goto error;
|
||||
});
|
||||
|
||||
VALIDATE_EXP((!is_progress || strequal(opts->status.data, "success")
|
||||
|| strequal(opts->status.data, "failed")
|
||||
|| strequal(opts->status.data, "running")
|
||||
|| strequal(opts->status.data, "cancel")),
|
||||
"status", "success|failed|running|cancel", opts->status.data, {
|
||||
goto error;
|
||||
});
|
||||
|
||||
VALIDATE_RANGE(!is_progress || (opts->percent >= 0 && opts->percent <= 100),
|
||||
"percent", {
|
||||
goto error;
|
||||
});
|
||||
|
||||
MessageData msg_data = { .title = opts->title, .status = opts->status,
|
||||
.percent = opts->percent, .data = opts->data };
|
||||
|
||||
id = msg_multihl(opts->id, hl_msg, kind, history, opts->err, &msg_data);
|
||||
|
||||
if (opts->verbose) {
|
||||
verbose_leave();
|
||||
verbose_stop(); // flush now
|
||||
}
|
||||
|
||||
if (is_progress) {
|
||||
do_autocmd_progress(id, hl_msg, &msg_data);
|
||||
}
|
||||
|
||||
if (history) {
|
||||
// history takes ownership
|
||||
return;
|
||||
return id;
|
||||
}
|
||||
|
||||
error:
|
||||
hl_msg_free(hl_msg);
|
||||
return id;
|
||||
}
|
||||
|
||||
/// Gets the current list of buffers.
|
||||
|
@@ -89,6 +89,7 @@ return {
|
||||
QuitPre = false, -- before :quit
|
||||
PackChangedPre = false, -- before trying to change state of `vim.pack` plugin
|
||||
PackChanged = false, -- after changing state of `vim.pack` plugin
|
||||
Progress = false, -- after showing/updating a progress message
|
||||
RecordingEnter = true, -- when starting to record a macro
|
||||
RecordingLeave = true, -- just before a macro stops recording
|
||||
RemoteReply = false, -- upon string reception from a remote vim
|
||||
@@ -162,6 +163,7 @@ return {
|
||||
LspTokenUpdate = true,
|
||||
PackChangedPre = true,
|
||||
PackChanged = true,
|
||||
Progress = true,
|
||||
RecordingEnter = true,
|
||||
RecordingLeave = true,
|
||||
Signal = true,
|
||||
|
@@ -966,7 +966,7 @@ static void nlua_print_event(void **argv)
|
||||
HlMessage msg = KV_INITIAL_VALUE;
|
||||
HlMessageChunk chunk = { { .data = argv[0], .size = (size_t)(intptr_t)argv[1] - 1 }, 0 };
|
||||
kv_push(msg, chunk);
|
||||
msg_multihl(msg, "lua_print", true, false);
|
||||
msg_multihl(INTEGER_OBJ(0), msg, "lua_print", true, false, NULL);
|
||||
}
|
||||
|
||||
/// Print as a Vim message
|
||||
|
@@ -15,6 +15,7 @@
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/ascii_defs.h"
|
||||
#include "nvim/autocmd.h"
|
||||
#include "nvim/buffer_defs.h"
|
||||
#include "nvim/channel.h"
|
||||
#include "nvim/charset.h"
|
||||
@@ -50,6 +51,7 @@
|
||||
#include "nvim/memory.h"
|
||||
#include "nvim/memory_defs.h"
|
||||
#include "nvim/message.h"
|
||||
#include "nvim/message_defs.h"
|
||||
#include "nvim/mouse.h"
|
||||
#include "nvim/ops.h"
|
||||
#include "nvim/option.h"
|
||||
@@ -149,6 +151,8 @@ bool keep_msg_more = false; // keep_msg was set by msgmore()
|
||||
|
||||
// Extended msg state, currently used for external UIs with ext_messages
|
||||
static const char *msg_ext_kind = NULL;
|
||||
static MsgID msg_ext_id = { .type = kObjectTypeInteger, .data.integer = 0 };
|
||||
static DictOf(Object) msg_ext_progress = ARRAY_DICT_INIT;
|
||||
static Array *msg_ext_chunks = NULL;
|
||||
static garray_T msg_ext_last_chunk = GA_INIT(sizeof(char), 40);
|
||||
static sattr_T msg_ext_last_attr = -1;
|
||||
@@ -158,6 +162,8 @@ static bool msg_ext_history = false; ///< message was added to history
|
||||
|
||||
static int msg_grid_pos_at_flush = 0;
|
||||
|
||||
static int64_t msg_id_next = 1; ///< message id to be allocated to next message
|
||||
|
||||
static void ui_ext_msg_set_pos(int row, bool scrolled)
|
||||
{
|
||||
char buf[MAX_SCHAR_SIZE];
|
||||
@@ -293,7 +299,8 @@ static bool is_multihl = false;
|
||||
/// @param kind Message kind (can be NULL to avoid setting kind)
|
||||
/// @param history Whether to add message to history
|
||||
/// @param err Whether to print message as an error
|
||||
void msg_multihl(HlMessage hl_msg, const char *kind, bool history, bool err)
|
||||
MsgID msg_multihl(MsgID id, HlMessage hl_msg, const char *kind, bool history, bool err,
|
||||
MessageData *msg_data)
|
||||
{
|
||||
no_wait_return++;
|
||||
msg_start();
|
||||
@@ -305,6 +312,17 @@ void msg_multihl(HlMessage hl_msg, const char *kind, bool history, bool err)
|
||||
}
|
||||
is_multihl = true;
|
||||
msg_ext_skip_flush = true;
|
||||
|
||||
// provide a new id if not given
|
||||
if (id.type == kObjectTypeNil) {
|
||||
id = INTEGER_OBJ(msg_id_next++);
|
||||
} else if (id.type == kObjectTypeInteger) {
|
||||
id = id.data.integer > 0 ? id : INTEGER_OBJ(msg_id_next++);
|
||||
if (msg_id_next < id.data.integer) {
|
||||
msg_id_next = id.data.integer + 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < kv_size(hl_msg); i++) {
|
||||
HlMessageChunk chunk = kv_A(hl_msg, i);
|
||||
if (err) {
|
||||
@@ -315,12 +333,14 @@ void msg_multihl(HlMessage hl_msg, const char *kind, bool history, bool err)
|
||||
assert(!ui_has(kUIMessages) || kind == NULL || msg_ext_kind == kind);
|
||||
}
|
||||
if (history && kv_size(hl_msg)) {
|
||||
msg_hist_add_multihl(hl_msg, false);
|
||||
msg_hist_add_multihl(id, hl_msg, false, msg_data);
|
||||
}
|
||||
|
||||
msg_ext_skip_flush = false;
|
||||
is_multihl = false;
|
||||
no_wait_return--;
|
||||
msg_end();
|
||||
return id;
|
||||
}
|
||||
|
||||
/// @param keep set keep_msg if it doesn't scroll
|
||||
@@ -1018,12 +1038,35 @@ static void msg_hist_add(const char *s, int len, int hl_id)
|
||||
|
||||
HlMessage msg = KV_INITIAL_VALUE;
|
||||
kv_push(msg, ((HlMessageChunk){ text, hl_id }));
|
||||
msg_hist_add_multihl(msg, false);
|
||||
msg_hist_add_multihl(INTEGER_OBJ(0), msg, false, NULL);
|
||||
}
|
||||
|
||||
static bool do_clear_hist_temp = true;
|
||||
|
||||
static void msg_hist_add_multihl(HlMessage msg, bool temp)
|
||||
void do_autocmd_progress(MsgID msg_id, HlMessage msg, MessageData *msg_data)
|
||||
{
|
||||
MAXSIZE_TEMP_DICT(data, 7);
|
||||
ArrayOf(String) messages = ARRAY_DICT_INIT;
|
||||
for (size_t i = 0; i < msg.size; i++) {
|
||||
ADD(messages, STRING_OBJ(msg.items[i].text));
|
||||
}
|
||||
|
||||
PUT_C(data, "id", OBJECT_OBJ(msg_id));
|
||||
PUT_C(data, "text", ARRAY_OBJ(messages));
|
||||
if (msg_data != NULL) {
|
||||
PUT_C(data, "percent", INTEGER_OBJ(msg_data->percent));
|
||||
PUT_C(data, "status", STRING_OBJ(msg_data->status));
|
||||
PUT_C(data, "title", STRING_OBJ(msg_data->title));
|
||||
PUT_C(data, "data", DICT_OBJ(msg_data->data));
|
||||
}
|
||||
|
||||
apply_autocmds_group(EVENT_PROGRESS, msg_data ? msg_data->title.data : "", NULL, true,
|
||||
AUGROUP_ALL, NULL,
|
||||
NULL, &DICT_OBJ(data));
|
||||
kv_destroy(messages);
|
||||
}
|
||||
|
||||
static void msg_hist_add_multihl(MsgID msg_id, HlMessage msg, bool temp, MessageData *msg_data)
|
||||
{
|
||||
if (do_clear_hist_temp) {
|
||||
msg_hist_clear_temp();
|
||||
@@ -1061,6 +1104,20 @@ static void msg_hist_add_multihl(HlMessage msg, bool temp)
|
||||
msg_hist_len += !temp;
|
||||
msg_hist_last = entry;
|
||||
msg_ext_history = true;
|
||||
|
||||
msg_ext_id = msg_id;
|
||||
if (strequal(msg_ext_kind, "progress") && msg_data != NULL && ui_has(kUIMessages)) {
|
||||
kv_resize(msg_ext_progress, 3);
|
||||
if (msg_data->title.size != 0) {
|
||||
PUT_C(msg_ext_progress, "title", STRING_OBJ(msg_data->title));
|
||||
}
|
||||
if (msg_data->status.size != 0) {
|
||||
PUT_C(msg_ext_progress, "status", STRING_OBJ(msg_data->status));
|
||||
}
|
||||
if (msg_data->percent >= 0) {
|
||||
PUT_C(msg_ext_progress, "percent", INTEGER_OBJ(msg_data->percent));
|
||||
}
|
||||
}
|
||||
msg_hist_clear(msg_hist_max);
|
||||
}
|
||||
|
||||
@@ -1205,7 +1262,7 @@ void ex_messages(exarg_T *eap)
|
||||
}
|
||||
if (redirecting() || !ui_has(kUIMessages)) {
|
||||
msg_silent += ui_has(kUIMessages);
|
||||
msg_multihl(p->msg, p->kind, false, false);
|
||||
msg_multihl(INTEGER_OBJ(0), p->msg, p->kind, false, false, NULL);
|
||||
msg_silent -= ui_has(kUIMessages);
|
||||
}
|
||||
}
|
||||
@@ -2152,7 +2209,9 @@ void msg_puts_len(const char *const str, const ptrdiff_t len, int hl_id, bool hi
|
||||
// Don't print anything when using ":silent cmd" or empty message.
|
||||
if (msg_silent != 0 || *str == NUL) {
|
||||
if (*str == NUL && ui_has(kUIMessages)) {
|
||||
ui_call_msg_show(cstr_as_string("empty"), (Array)ARRAY_DICT_INIT, false, false, false);
|
||||
ui_call_msg_show(cstr_as_string("empty"), (Array)ARRAY_DICT_INIT, false, false, false,
|
||||
INTEGER_OBJ(-1),
|
||||
(Dict)ARRAY_DICT_INIT);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -3178,8 +3237,10 @@ void msg_ext_ui_flush(void)
|
||||
msg_ext_emit_chunk();
|
||||
if (msg_ext_chunks->size > 0) {
|
||||
Array *tofree = msg_ext_init_chunks();
|
||||
|
||||
ui_call_msg_show(cstr_as_string(msg_ext_kind), *tofree, msg_ext_overwrite, msg_ext_history,
|
||||
msg_ext_append);
|
||||
msg_ext_append, msg_ext_id, msg_ext_progress);
|
||||
// clear info after emiting message.
|
||||
if (msg_ext_history) {
|
||||
api_free_array(*tofree);
|
||||
} else {
|
||||
@@ -3191,13 +3252,15 @@ void msg_ext_ui_flush(void)
|
||||
xfree(chunk);
|
||||
}
|
||||
xfree(tofree->items);
|
||||
msg_hist_add_multihl(msg, true);
|
||||
msg_hist_add_multihl(INTEGER_OBJ(0), msg, true, NULL);
|
||||
}
|
||||
xfree(tofree);
|
||||
msg_ext_overwrite = false;
|
||||
msg_ext_history = false;
|
||||
msg_ext_append = false;
|
||||
msg_ext_kind = NULL;
|
||||
msg_ext_id = INTEGER_OBJ(0);
|
||||
kv_destroy(msg_ext_progress);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -10,7 +10,14 @@ typedef struct {
|
||||
} HlMessageChunk;
|
||||
|
||||
typedef kvec_t(HlMessageChunk) HlMessage;
|
||||
#define MsgID Union(Integer, String)
|
||||
|
||||
typedef struct msg_data {
|
||||
Integer percent; ///< Progress percentage
|
||||
String title; ///< Title for progress message
|
||||
String status; ///< Status for progress message
|
||||
DictOf(String, Object) data; ///< Extra info for 'echo' messages
|
||||
} MessageData;
|
||||
/// Message history for `:messages`
|
||||
typedef struct msg_hist {
|
||||
struct msg_hist *next; ///< Next message.
|
||||
|
@@ -3138,3 +3138,385 @@ it('pager works in headless mode with UI attached', function()
|
||||
-- More --^ |
|
||||
]])
|
||||
end)
|
||||
|
||||
describe('progress-message', function()
|
||||
local screen
|
||||
|
||||
local function setup_autocmd(pattern)
|
||||
exec_lua(function()
|
||||
local grp = vim.api.nvim_create_augroup('ProgressListener', { clear = true })
|
||||
vim.api.nvim_create_autocmd('Progress', {
|
||||
pattern = pattern,
|
||||
group = grp,
|
||||
callback = function(ev)
|
||||
_G.progress_autocmd_result = ev.data
|
||||
end,
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
local function assert_progress_autocmd(expected, context)
|
||||
local progress_autocmd_result = exec_lua(function()
|
||||
return _G.progress_autocmd_result
|
||||
end)
|
||||
eq(expected, progress_autocmd_result, context)
|
||||
exec_lua(function()
|
||||
_G.progress_autocmd_result = nil
|
||||
end)
|
||||
end
|
||||
|
||||
local function setup_screen(with_ext_msg)
|
||||
if with_ext_msg then
|
||||
screen = Screen.new(25, 5, { ext_messages = true })
|
||||
screen:add_extra_attr_ids {
|
||||
[100] = { undercurl = true, special = Screen.colors.Red },
|
||||
[101] = { foreground = Screen.colors.Magenta1, bold = true },
|
||||
}
|
||||
else
|
||||
screen = Screen.new(40, 5)
|
||||
end
|
||||
end
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
setup_screen(true)
|
||||
setup_autocmd()
|
||||
end)
|
||||
|
||||
it('can be sent by nvim_echo', function()
|
||||
local id = api.nvim_echo(
|
||||
{ { 'test-message' } },
|
||||
true,
|
||||
{ kind = 'progress', title = 'testsuit', percent = 10, status = 'running' }
|
||||
)
|
||||
|
||||
screen:expect({
|
||||
grid = [[
|
||||
^ |
|
||||
{1:~ }|*4
|
||||
]],
|
||||
messages = {
|
||||
{
|
||||
content = { { 'test-message' } },
|
||||
progress = {
|
||||
percent = 10,
|
||||
status = 'running',
|
||||
title = 'testsuit',
|
||||
},
|
||||
history = true,
|
||||
id = 1,
|
||||
kind = 'progress',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
assert_progress_autocmd({
|
||||
text = { 'test-message' },
|
||||
percent = 10,
|
||||
status = 'running',
|
||||
title = 'testsuit',
|
||||
id = 1,
|
||||
data = {},
|
||||
}, 'progress autocmd receives progress messages')
|
||||
|
||||
-- can update progress messages
|
||||
api.nvim_echo(
|
||||
{ { 'test-message-updated' } },
|
||||
true,
|
||||
{ id = id, kind = 'progress', title = 'TestSuit', percent = 50, status = 'running' }
|
||||
)
|
||||
screen:expect({
|
||||
grid = [[
|
||||
^ |
|
||||
{1:~ }|*4
|
||||
]],
|
||||
messages = {
|
||||
{
|
||||
content = { { 'test-message-updated' } },
|
||||
progress = {
|
||||
percent = 50,
|
||||
status = 'running',
|
||||
title = 'TestSuit',
|
||||
},
|
||||
history = true,
|
||||
id = 1,
|
||||
kind = 'progress',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
assert_progress_autocmd({
|
||||
text = { 'test-message-updated' },
|
||||
percent = 50,
|
||||
status = 'running',
|
||||
title = 'TestSuit',
|
||||
id = 1,
|
||||
data = {},
|
||||
}, 'Progress autocmd receives progress update')
|
||||
|
||||
-- progress event can filter by title
|
||||
setup_autocmd('Special Title')
|
||||
api.nvim_echo(
|
||||
{ { 'test-message-updated' } },
|
||||
true,
|
||||
{ id = id, kind = 'progress', percent = 80, status = 'running' }
|
||||
)
|
||||
assert_progress_autocmd(nil, 'No progress message with Special Title yet')
|
||||
|
||||
api.nvim_echo(
|
||||
{ { 'test-message-updated' } },
|
||||
true,
|
||||
{ id = id, kind = 'progress', title = 'Special Title', percent = 100, status = 'success' }
|
||||
)
|
||||
assert_progress_autocmd({
|
||||
text = { 'test-message-updated' },
|
||||
percent = 100,
|
||||
status = 'success',
|
||||
title = 'Special Title',
|
||||
id = 1,
|
||||
data = {},
|
||||
}, 'Progress autocmd receives progress update')
|
||||
end)
|
||||
|
||||
it('user-defined data in `data` field', function()
|
||||
api.nvim_echo({ { 'test-message' } }, true, {
|
||||
kind = 'progress',
|
||||
title = 'TestSuit',
|
||||
percent = 10,
|
||||
status = 'running',
|
||||
data = { test_attribute = 1 },
|
||||
})
|
||||
|
||||
screen:expect({
|
||||
grid = [[
|
||||
^ |
|
||||
{1:~ }|*4
|
||||
]],
|
||||
messages = {
|
||||
{
|
||||
content = { { 'test-message' } },
|
||||
history = true,
|
||||
id = 1,
|
||||
kind = 'progress',
|
||||
progress = {
|
||||
percent = 10,
|
||||
status = 'running',
|
||||
title = 'TestSuit',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
assert_progress_autocmd({
|
||||
text = { 'test-message' },
|
||||
percent = 10,
|
||||
status = 'running',
|
||||
title = 'TestSuit',
|
||||
id = 1,
|
||||
data = { test_attribute = 1 },
|
||||
}, 'Progress autocmd receives progress messages')
|
||||
end)
|
||||
|
||||
it('validates', function()
|
||||
-- throws error if title, status, percent, data is used in non progress message
|
||||
eq(
|
||||
'title, status, percent and data fields can only be used with progress messages',
|
||||
t.pcall_err(api.nvim_echo, { { 'test-message' } }, false, { title = 'TestSuit' })
|
||||
)
|
||||
|
||||
eq(
|
||||
'title, status, percent and data fields can only be used with progress messages',
|
||||
t.pcall_err(api.nvim_echo, { { 'test-message' } }, false, { status = 'running' })
|
||||
)
|
||||
|
||||
eq(
|
||||
'title, status, percent and data fields can only be used with progress messages',
|
||||
t.pcall_err(api.nvim_echo, { { 'test-message' } }, false, { percent = 10 })
|
||||
)
|
||||
|
||||
eq(
|
||||
'title, status, percent and data fields can only be used with progress messages',
|
||||
t.pcall_err(api.nvim_echo, { { 'test-message' } }, false, { data = { tag = 'test' } })
|
||||
)
|
||||
|
||||
-- throws error if anything other then running/success/failed/cancel is used in status
|
||||
eq(
|
||||
"Invalid 'status': expected success|failed|running|cancel, got live",
|
||||
t.pcall_err(
|
||||
api.nvim_echo,
|
||||
{ { 'test-message' } },
|
||||
false,
|
||||
{ kind = 'progress', status = 'live' }
|
||||
)
|
||||
)
|
||||
|
||||
-- throws error if parcent is not in 0-100
|
||||
eq(
|
||||
"Invalid 'percent': out of range",
|
||||
t.pcall_err(
|
||||
api.nvim_echo,
|
||||
{ { 'test-message' } },
|
||||
false,
|
||||
{ kind = 'progress', status = 'running', percent = -1 }
|
||||
)
|
||||
)
|
||||
|
||||
eq(
|
||||
"Invalid 'percent': out of range",
|
||||
t.pcall_err(
|
||||
api.nvim_echo,
|
||||
{ { 'test-message' } },
|
||||
false,
|
||||
{ kind = 'progress', status = 'running', percent = 101 }
|
||||
)
|
||||
)
|
||||
|
||||
-- throws error if data is not a dictionary
|
||||
eq(
|
||||
"Invalid 'data': expected Dict, got String",
|
||||
t.pcall_err(
|
||||
api.nvim_echo,
|
||||
{ { 'test-message' } },
|
||||
false,
|
||||
{ kind = 'progress', title = 'TestSuit', percent = 10, status = 'running', data = 'test' }
|
||||
)
|
||||
)
|
||||
end)
|
||||
|
||||
it('gets placed in history', function()
|
||||
local id = api.nvim_echo(
|
||||
{ { 'test-message 10' } },
|
||||
true,
|
||||
{ kind = 'progress', title = 'TestSuit', percent = 10, status = 'running' }
|
||||
)
|
||||
eq('test-message 10', exec_capture('messages'))
|
||||
|
||||
api.nvim_echo(
|
||||
{ { 'test-message 20' } },
|
||||
true,
|
||||
{ id = id, kind = 'progress', title = 'TestSuit', percent = 20, status = 'running' }
|
||||
)
|
||||
eq('test-message 10\ntest-message 20', exec_capture('messages'))
|
||||
|
||||
api.nvim_echo({ { 'middle msg' } }, true, {})
|
||||
eq('test-message 10\ntest-message 20\nmiddle msg', exec_capture('messages'))
|
||||
api.nvim_echo(
|
||||
{ { 'test-message 30' } },
|
||||
true,
|
||||
{ id = id, kind = 'progress', title = 'TestSuit', percent = 30, status = 'running' }
|
||||
)
|
||||
eq('test-message 10\ntest-message 20\nmiddle msg\ntest-message 30', exec_capture('messages'))
|
||||
|
||||
api.nvim_echo(
|
||||
{ { 'test-message 50' } },
|
||||
true,
|
||||
{ id = id, kind = 'progress', title = 'TestSuit', percent = 50, status = 'running' }
|
||||
)
|
||||
eq(
|
||||
'test-message 10\ntest-message 20\nmiddle msg\ntest-message 30\ntest-message 50',
|
||||
exec_capture('messages')
|
||||
)
|
||||
end)
|
||||
|
||||
it('sets msg-id correctly', function()
|
||||
local id1 = api.nvim_echo(
|
||||
{ { 'test-message 10' } },
|
||||
true,
|
||||
{ kind = 'progress', title = 'TestSuit', percent = 10, status = 'running' }
|
||||
)
|
||||
eq(1, id1)
|
||||
|
||||
local id2 = api.nvim_echo(
|
||||
{ { 'test-message 20' } },
|
||||
true,
|
||||
{ kind = 'progress', title = 'TestSuit', percent = 20, status = 'running' }
|
||||
)
|
||||
eq(2, id2)
|
||||
|
||||
local id3 = api.nvim_echo({ { 'normal message' } }, true, {})
|
||||
eq(3, id3)
|
||||
|
||||
local id4 = api.nvim_echo({ { 'without history' } }, false, {})
|
||||
eq(4, id4)
|
||||
|
||||
local id5 = api.nvim_echo(
|
||||
{ { 'test-message 30' } },
|
||||
true,
|
||||
{ id = 10, kind = 'progress', title = 'TestSuit', percent = 30, status = 'running' }
|
||||
)
|
||||
eq(10, id5)
|
||||
|
||||
-- updating progress message does not create new msg-id
|
||||
local id5_update = api.nvim_echo(
|
||||
{ { 'test-message 40' } },
|
||||
true,
|
||||
{ id = id5, kind = 'progress', title = 'TestSuit', percent = 40, status = 'running' }
|
||||
)
|
||||
eq(id5, id5_update)
|
||||
|
||||
local id6 = api.nvim_echo(
|
||||
{ { 'test-message 30' } },
|
||||
true,
|
||||
{ kind = 'progress', title = 'TestSuit', percent = 30, status = 'running' }
|
||||
)
|
||||
eq(11, id6)
|
||||
|
||||
local id7 = api.nvim_echo(
|
||||
{ { 'supports str-id' } },
|
||||
true,
|
||||
{ id = 'str-id', kind = 'progress', title = 'TestSuit', percent = 30, status = 'running' }
|
||||
)
|
||||
eq('str-id', id7)
|
||||
|
||||
local id8 = api.nvim_echo(
|
||||
{ { 'test-message 30' } },
|
||||
true,
|
||||
{ kind = 'progress', title = 'TestSuit', percent = 30, status = 'running' }
|
||||
)
|
||||
eq(12, id8)
|
||||
end)
|
||||
|
||||
it('supports string ids', function()
|
||||
-- string id works
|
||||
local id = api.nvim_echo(
|
||||
{ { 'supports str-id' } },
|
||||
true,
|
||||
{ id = 'str-id', kind = 'progress', title = 'TestSuit', percent = 30, status = 'running' }
|
||||
)
|
||||
eq('str-id', id)
|
||||
|
||||
screen:expect({
|
||||
grid = [[
|
||||
^ |
|
||||
{1:~ }|*4
|
||||
]],
|
||||
messages = {
|
||||
{
|
||||
content = { { 'supports str-id' } },
|
||||
history = true,
|
||||
id = 'str-id',
|
||||
kind = 'progress',
|
||||
progress = {
|
||||
percent = 30,
|
||||
status = 'running',
|
||||
title = 'TestSuit',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
local id_update = api.nvim_echo(
|
||||
{ { 'supports str-id updated' } },
|
||||
true,
|
||||
{ id = id, kind = 'progress', title = 'testsuit', percent = 40, status = 'running' }
|
||||
)
|
||||
eq(id, id_update)
|
||||
assert_progress_autocmd({
|
||||
text = { 'supports str-id updated' },
|
||||
percent = 40,
|
||||
status = 'running',
|
||||
title = 'testsuit',
|
||||
id = 'str-id',
|
||||
data = {},
|
||||
})
|
||||
end)
|
||||
end)
|
||||
|
@@ -1397,12 +1397,19 @@ function Screen:_handle_wildmenu_hide()
|
||||
self.wildmenu_items, self.wildmenu_pos = nil, nil
|
||||
end
|
||||
|
||||
function Screen:_handle_msg_show(kind, chunks, replace_last, history, append)
|
||||
function Screen:_handle_msg_show(kind, chunks, replace_last, history, append, id, progress)
|
||||
local pos = #self.messages
|
||||
if not replace_last or pos == 0 then
|
||||
pos = pos + 1
|
||||
end
|
||||
self.messages[pos] = { kind = kind, content = chunks, history = history, append = append }
|
||||
self.messages[pos] = {
|
||||
kind = kind,
|
||||
content = chunks,
|
||||
history = history,
|
||||
append = append,
|
||||
id = id,
|
||||
progress = progress,
|
||||
}
|
||||
end
|
||||
|
||||
function Screen:_handle_msg_clear()
|
||||
@@ -1533,6 +1540,8 @@ function Screen:_extstate_repr(attr_state)
|
||||
content = self:_chunks_repr(entry.content, attr_state),
|
||||
history = entry.history or nil,
|
||||
append = entry.append or nil,
|
||||
id = entry.kind == 'progress' and entry.id or nil,
|
||||
progress = entry.kind == 'progress' and entry.progress or nil,
|
||||
}
|
||||
end
|
||||
|
||||
|
Reference in New Issue
Block a user