mirror of
https://github.com/neovim/neovim.git
synced 2026-07-11 20:09:44 +00:00
feat(events): MarkSet event, aucmd_defer() #35793
Problem: - Can't subscribe to "mark" events. - Executing events is risky because they can't be deferred. Solution: - Introduce `MarkSet` event. - Introduce `aucmd_defer()`. Helped-by: zeertzjq <zeertzjq@outlook.com> Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
This commit is contained in:
@@ -759,6 +759,19 @@ LspNotify See |LspNotify|
|
|||||||
LspProgress See |LspProgress|
|
LspProgress See |LspProgress|
|
||||||
LspRequest See |LspRequest|
|
LspRequest See |LspRequest|
|
||||||
LspTokenUpdate See |LspTokenUpdate|
|
LspTokenUpdate See |LspTokenUpdate|
|
||||||
|
*MarkSet*
|
||||||
|
MarkSet After a |mark| is set by |m|, |:mark|, and
|
||||||
|
|nvim_buf_set_mark()|. Supports `[a-zA-Z]`
|
||||||
|
marks (may support more in the future).
|
||||||
|
The |autocmd-pattern| is matched against the
|
||||||
|
mark name (e.g. `[ab]` matches `a` or `b`, `*`
|
||||||
|
matches all).
|
||||||
|
|
||||||
|
The |event-data| has these keys:
|
||||||
|
- name: Mark name (e.g. "a")
|
||||||
|
- line: Mark line.
|
||||||
|
- col: Mark column.
|
||||||
|
|
||||||
*MenuPopup*
|
*MenuPopup*
|
||||||
MenuPopup Just before showing the popup menu (under the
|
MenuPopup Just before showing the popup menu (under the
|
||||||
right mouse button). Useful for adjusting the
|
right mouse button). Useful for adjusting the
|
||||||
|
|||||||
@@ -60,9 +60,9 @@ the Nvim editor. (There is an unrelated, low-level concept defined by the
|
|||||||
`event/defs.h#Event` struct, which is just a bag of data passed along the
|
`event/defs.h#Event` struct, which is just a bag of data passed along the
|
||||||
internal |event-loop|.)
|
internal |event-loop|.)
|
||||||
|
|
||||||
All new editor events must be implemented using `aucmd_defer()` (and where
|
Where possible, new editor events should be implemented using `aucmd_defer()`
|
||||||
possible, old events should be migrated to this), so that they are processed
|
(and where possible, old events migrate to this), so they are processed in
|
||||||
in a predictable manner, which avoids crashes and race conditions. See
|
a predictable manner, which avoids crashes and race conditions. See
|
||||||
`do_markset_autocmd` for an example.
|
`do_markset_autocmd` for an example.
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|||||||
@@ -226,6 +226,8 @@ EVENTS
|
|||||||
• New `msg_id` parameter for |ui-messages| `msg_show` event.
|
• New `msg_id` parameter for |ui-messages| `msg_show` event.
|
||||||
• 'rulerformat' is emitted as `msg_ruler` when not part of the statusline.
|
• 'rulerformat' is emitted as `msg_ruler` when not part of the statusline.
|
||||||
• Creating or updating a progress message with |nvim_echo()| triggers a |Progress| event.
|
• Creating or updating a progress message with |nvim_echo()| triggers a |Progress| event.
|
||||||
|
• |MarkSet| is triggered after a |mark| is set by the user (currently doesn't
|
||||||
|
support implicit marks like |'[| or |'<|, …).
|
||||||
|
|
||||||
HIGHLIGHTS
|
HIGHLIGHTS
|
||||||
|
|
||||||
|
|||||||
@@ -2535,6 +2535,7 @@ A jump table for the options with a short description can be found at |Q_op|.
|
|||||||
|LspProgress|,
|
|LspProgress|,
|
||||||
|LspRequest|,
|
|LspRequest|,
|
||||||
|LspTokenUpdate|,
|
|LspTokenUpdate|,
|
||||||
|
|MarkSet|,
|
||||||
|MenuPopup|,
|
|MenuPopup|,
|
||||||
|ModeChanged|,
|
|ModeChanged|,
|
||||||
|OptionSet|,
|
|OptionSet|,
|
||||||
|
|||||||
1
runtime/lua/vim/_meta/api_keysets.lua
generated
1
runtime/lua/vim/_meta/api_keysets.lua
generated
@@ -160,6 +160,7 @@ error('Cannot require a meta file')
|
|||||||
--- |'LspProgress'
|
--- |'LspProgress'
|
||||||
--- |'LspRequest'
|
--- |'LspRequest'
|
||||||
--- |'LspTokenUpdate'
|
--- |'LspTokenUpdate'
|
||||||
|
--- |'MarkSet'
|
||||||
--- |'MenuPopup'
|
--- |'MenuPopup'
|
||||||
--- |'ModeChanged'
|
--- |'ModeChanged'
|
||||||
--- |'OptionSet'
|
--- |'OptionSet'
|
||||||
|
|||||||
1
runtime/lua/vim/_meta/options.lua
generated
1
runtime/lua/vim/_meta/options.lua
generated
@@ -2213,6 +2213,7 @@ vim.go.ei = vim.go.eventignore
|
|||||||
--- `LspProgress`,
|
--- `LspProgress`,
|
||||||
--- `LspRequest`,
|
--- `LspRequest`,
|
||||||
--- `LspTokenUpdate`,
|
--- `LspTokenUpdate`,
|
||||||
|
--- `MarkSet`,
|
||||||
--- `MenuPopup`,
|
--- `MenuPopup`,
|
||||||
--- `ModeChanged`,
|
--- `ModeChanged`,
|
||||||
--- `OptionSet`,
|
--- `OptionSet`,
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ return {
|
|||||||
LspProgress = false, -- after a LSP progress update
|
LspProgress = false, -- after a LSP progress update
|
||||||
LspRequest = false, -- after an LSP request is started, canceled, or completed
|
LspRequest = false, -- after an LSP request is started, canceled, or completed
|
||||||
LspTokenUpdate = false, -- after a visible LSP token is updated
|
LspTokenUpdate = false, -- after a visible LSP token is updated
|
||||||
|
MarkSet = false, -- after a mark is set
|
||||||
MenuPopup = false, -- just before popup menu is displayed
|
MenuPopup = false, -- just before popup menu is displayed
|
||||||
ModeChanged = false, -- after changing the mode
|
ModeChanged = false, -- after changing the mode
|
||||||
OptionSet = false, -- after setting any option
|
OptionSet = false, -- after setting any option
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "nvim/api/private/converter.h"
|
||||||
#include "nvim/autocmd.h"
|
#include "nvim/autocmd.h"
|
||||||
|
#include "nvim/autocmd_defs.h"
|
||||||
#include "nvim/buffer.h"
|
#include "nvim/buffer.h"
|
||||||
#include "nvim/charset.h"
|
#include "nvim/charset.h"
|
||||||
#include "nvim/cmdexpand_defs.h"
|
#include "nvim/cmdexpand_defs.h"
|
||||||
@@ -111,6 +113,18 @@ static char *old_termresponse = NULL;
|
|||||||
static Map(String, int) map_augroup_name_to_id = MAP_INIT;
|
static Map(String, int) map_augroup_name_to_id = MAP_INIT;
|
||||||
static Map(int, String) map_augroup_id_to_name = MAP_INIT;
|
static Map(int, String) map_augroup_id_to_name = MAP_INIT;
|
||||||
|
|
||||||
|
void autocmd_init(void)
|
||||||
|
{
|
||||||
|
deferred_events = multiqueue_new_child(main_loop.events);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef EXITFREE
|
||||||
|
void autocmd_free_all_mem(void)
|
||||||
|
{
|
||||||
|
multiqueue_free(deferred_events);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static void augroup_map_del(int id, const char *name)
|
static void augroup_map_del(int id, const char *name)
|
||||||
{
|
{
|
||||||
if (name != NULL) {
|
if (name != NULL) {
|
||||||
@@ -1493,6 +1507,85 @@ win_found:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Schedules an autocommand event, to be executed at the next event-loop tick.
|
||||||
|
///
|
||||||
|
/// @param event Event to schedule
|
||||||
|
/// @param fname Name to use as `<amatch>` (the "pattern"). NULL/empty means use actual filename.
|
||||||
|
/// @param fname_io Filename to use for <afile> on cmdline, NULL means use `fname`.
|
||||||
|
/// @param group Group ID or AUGROUP_ALL
|
||||||
|
/// @param buf Buffer for <abuf>
|
||||||
|
/// @param eap Ex command arguments
|
||||||
|
/// @param data Event-specific data. Will be copied, caller must free `data`.
|
||||||
|
/// The `data` items will also be copied to `v:event`.
|
||||||
|
void aucmd_defer(event_T event, char *fname, char *fname_io, int group, buf_T *buf, exarg_T *eap,
|
||||||
|
Object *data)
|
||||||
|
{
|
||||||
|
AutoCmdEvent *evdata = xmalloc(sizeof(AutoCmdEvent));
|
||||||
|
evdata->event = event;
|
||||||
|
evdata->fname = fname != NULL ? xstrdup(fname) : NULL;
|
||||||
|
evdata->fname_io = fname_io != NULL ? xstrdup(fname_io) : NULL;
|
||||||
|
evdata->group = group;
|
||||||
|
evdata->buf = buf->handle;
|
||||||
|
evdata->eap = eap;
|
||||||
|
if (data) {
|
||||||
|
evdata->data = xmalloc(sizeof(Object));
|
||||||
|
*evdata->data = copy_object(*data, NULL);
|
||||||
|
} else {
|
||||||
|
evdata->data = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
multiqueue_put(deferred_events, deferred_event, evdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes a deferred autocommand event.
|
||||||
|
static void deferred_event(void **argv)
|
||||||
|
{
|
||||||
|
AutoCmdEvent *e = argv[0];
|
||||||
|
event_T event = e->event;
|
||||||
|
char *fname = e->fname;
|
||||||
|
char *fname_io = e->fname_io;
|
||||||
|
int group = e->group;
|
||||||
|
exarg_T *eap = e->eap;
|
||||||
|
Object *data = e->data;
|
||||||
|
|
||||||
|
Error err = ERROR_INIT;
|
||||||
|
buf_T *buf = find_buffer_by_handle(e->buf, &err);
|
||||||
|
if (buf) {
|
||||||
|
// Copy `data` to `v:event`.
|
||||||
|
save_v_event_T save_v_event;
|
||||||
|
dict_T *v_event = get_v_event(&save_v_event);
|
||||||
|
if (data && data->type == kObjectTypeDict) {
|
||||||
|
for (size_t i = 0; i < data->data.dict.size; i++) {
|
||||||
|
KeyValuePair item = data->data.dict.items[i];
|
||||||
|
typval_T tv;
|
||||||
|
object_to_vim(item.value, &tv, &err);
|
||||||
|
if (ERROR_SET(&err)) {
|
||||||
|
api_clear_error(&err);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
tv_dict_add_tv(v_event, item.key.data, item.key.size, &tv);
|
||||||
|
tv_clear(&tv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tv_dict_set_keys_readonly(v_event);
|
||||||
|
|
||||||
|
aco_save_T aco;
|
||||||
|
aucmd_prepbuf(&aco, buf);
|
||||||
|
apply_autocmds_group(event, fname, fname_io, false, group, buf, eap, data);
|
||||||
|
aucmd_restbuf(&aco);
|
||||||
|
|
||||||
|
restore_v_event(v_event, &save_v_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
xfree(fname);
|
||||||
|
xfree(fname_io);
|
||||||
|
if (data) {
|
||||||
|
api_free_object(*data);
|
||||||
|
xfree(data);
|
||||||
|
}
|
||||||
|
xfree(e);
|
||||||
|
}
|
||||||
|
|
||||||
/// Execute autocommands for "event" and file name "fname".
|
/// Execute autocommands for "event" and file name "fname".
|
||||||
///
|
///
|
||||||
/// @param event event that occurred
|
/// @param event event that occurred
|
||||||
@@ -1680,7 +1773,8 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
|
|||||||
// invalid.
|
// invalid.
|
||||||
if (fname_io == NULL) {
|
if (fname_io == NULL) {
|
||||||
if (event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE
|
if (event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE
|
||||||
|| event == EVENT_OPTIONSET || event == EVENT_MODECHANGED) {
|
|| event == EVENT_OPTIONSET || event == EVENT_MODECHANGED
|
||||||
|
|| event == EVENT_MARKSET) {
|
||||||
autocmd_fname = NULL;
|
autocmd_fname = NULL;
|
||||||
} else if (fname != NULL && !ends_excmd(*fname)) {
|
} else if (fname != NULL && !ends_excmd(*fname)) {
|
||||||
autocmd_fname = fname;
|
autocmd_fname = fname;
|
||||||
@@ -1742,6 +1836,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
|
|||||||
|| event == EVENT_DIRCHANGEDPRE
|
|| event == EVENT_DIRCHANGEDPRE
|
||||||
|| event == EVENT_FILETYPE
|
|| event == EVENT_FILETYPE
|
||||||
|| event == EVENT_FUNCUNDEFINED
|
|| event == EVENT_FUNCUNDEFINED
|
||||||
|
|| event == EVENT_MARKSET
|
||||||
|| event == EVENT_MENUPOPUP
|
|| event == EVENT_MENUPOPUP
|
||||||
|| event == EVENT_MODECHANGED
|
|| event == EVENT_MODECHANGED
|
||||||
|| event == EVENT_OPTIONSET
|
|| event == EVENT_OPTIONSET
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "nvim/buffer_defs.h"
|
#include "nvim/buffer_defs.h"
|
||||||
#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
|
#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
|
||||||
#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
|
#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
|
||||||
|
#include "nvim/event/defs.h"
|
||||||
#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
|
#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
|
||||||
#include "nvim/macros_defs.h"
|
#include "nvim/macros_defs.h"
|
||||||
#include "nvim/pos_defs.h"
|
#include "nvim/pos_defs.h"
|
||||||
@@ -68,4 +69,8 @@ enum { BUFLOCAL_PAT_LEN = 25, };
|
|||||||
#define FOR_ALL_AUEVENTS(event) \
|
#define FOR_ALL_AUEVENTS(event) \
|
||||||
for (event_T event = (event_T)0; (int)event < (int)NUM_EVENTS; event = (event_T)((int)event + 1))
|
for (event_T event = (event_T)0; (int)event < (int)NUM_EVENTS; event = (event_T)((int)event + 1))
|
||||||
|
|
||||||
|
/// Stores events for execution until a known safe state.
|
||||||
|
/// This should be the default for all new autocommands.
|
||||||
|
EXTERN MultiQueue *deferred_events INIT( = NULL);
|
||||||
|
|
||||||
#include "autocmd.h.generated.h"
|
#include "autocmd.h.generated.h"
|
||||||
|
|||||||
@@ -64,3 +64,13 @@ struct AutoPatCmd_S {
|
|||||||
};
|
};
|
||||||
|
|
||||||
typedef kvec_t(AutoCmd) AutoCmdVec;
|
typedef kvec_t(AutoCmd) AutoCmdVec;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
event_T event;
|
||||||
|
char *fname;
|
||||||
|
char *fname_io;
|
||||||
|
Buffer buf;
|
||||||
|
int group;
|
||||||
|
exarg_T *eap;
|
||||||
|
Object *data;
|
||||||
|
} AutoCmdEvent; // Used for "deferred" events, but can represent any event.
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ void event_init(void)
|
|||||||
loop_init(&main_loop, NULL);
|
loop_init(&main_loop, NULL);
|
||||||
resize_events = multiqueue_new_child(main_loop.events);
|
resize_events = multiqueue_new_child(main_loop.events);
|
||||||
|
|
||||||
|
autocmd_init();
|
||||||
signal_init();
|
signal_init();
|
||||||
// mspgack-rpc initialization
|
// mspgack-rpc initialization
|
||||||
channel_init();
|
channel_init();
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "nvim/api/private/helpers.h"
|
||||||
#include "nvim/ascii_defs.h"
|
#include "nvim/ascii_defs.h"
|
||||||
|
#include "nvim/autocmd.h"
|
||||||
#include "nvim/buffer.h"
|
#include "nvim/buffer.h"
|
||||||
#include "nvim/buffer_defs.h"
|
#include "nvim/buffer_defs.h"
|
||||||
#include "nvim/charset.h"
|
#include "nvim/charset.h"
|
||||||
@@ -87,6 +89,25 @@ void clear_fmark(fmark_T *const fm, const Timestamp timestamp)
|
|||||||
fm->timestamp = timestamp;
|
fm->timestamp = timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Schedules "MarkSet" event.
|
||||||
|
///
|
||||||
|
/// @param c The name of the mark, e.g., 'a'.
|
||||||
|
/// @param pos Position of the mark in the buffer.
|
||||||
|
/// @param buf The buffer of the mark.
|
||||||
|
static void do_markset_autocmd(char c, pos_T *pos, buf_T *buf)
|
||||||
|
{
|
||||||
|
if (!has_event(EVENT_MARKSET)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MAXSIZE_TEMP_DICT(data, 3);
|
||||||
|
char mark_str[2] = { c, '\0' };
|
||||||
|
PUT_C(data, "name", STRING_OBJ(((String){ .data = mark_str, .size = 1 })));
|
||||||
|
PUT_C(data, "line", INTEGER_OBJ(pos->lnum));
|
||||||
|
PUT_C(data, "col", INTEGER_OBJ(pos->col));
|
||||||
|
aucmd_defer(EVENT_MARKSET, mark_str, NULL, AUGROUP_ALL, buf, NULL, &DICT_OBJ(data));
|
||||||
|
}
|
||||||
|
|
||||||
// Set named mark "c" to position "pos".
|
// Set named mark "c" to position "pos".
|
||||||
// When "c" is upper case use file "fnum".
|
// When "c" is upper case use file "fnum".
|
||||||
// Returns OK on success, FAIL if bad name given.
|
// Returns OK on success, FAIL if bad name given.
|
||||||
@@ -119,6 +140,7 @@ int setmark_pos(int c, pos_T *pos, int fnum, fmarkv_T *view_pt)
|
|||||||
|
|
||||||
if (c == '"') {
|
if (c == '"') {
|
||||||
RESET_FMARK(&buf->b_last_cursor, *pos, buf->b_fnum, view);
|
RESET_FMARK(&buf->b_last_cursor, *pos, buf->b_fnum, view);
|
||||||
|
do_markset_autocmd((char)c, pos, buf);
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,10 +148,12 @@ int setmark_pos(int c, pos_T *pos, int fnum, fmarkv_T *view_pt)
|
|||||||
// file.
|
// file.
|
||||||
if (c == '[') {
|
if (c == '[') {
|
||||||
buf->b_op_start = *pos;
|
buf->b_op_start = *pos;
|
||||||
|
do_markset_autocmd((char)c, pos, buf);
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
if (c == ']') {
|
if (c == ']') {
|
||||||
buf->b_op_end = *pos;
|
buf->b_op_end = *pos;
|
||||||
|
do_markset_autocmd((char)c, pos, buf);
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,6 +167,7 @@ int setmark_pos(int c, pos_T *pos, int fnum, fmarkv_T *view_pt)
|
|||||||
// Visual_mode has not yet been set, use a sane default.
|
// Visual_mode has not yet been set, use a sane default.
|
||||||
buf->b_visual.vi_mode = 'v';
|
buf->b_visual.vi_mode = 'v';
|
||||||
}
|
}
|
||||||
|
do_markset_autocmd((char)c, pos, buf);
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +179,7 @@ int setmark_pos(int c, pos_T *pos, int fnum, fmarkv_T *view_pt)
|
|||||||
if (ASCII_ISLOWER(c)) {
|
if (ASCII_ISLOWER(c)) {
|
||||||
i = c - 'a';
|
i = c - 'a';
|
||||||
RESET_FMARK(buf->b_namedm + i, *pos, fnum, view);
|
RESET_FMARK(buf->b_namedm + i, *pos, fnum, view);
|
||||||
|
do_markset_autocmd((char)c, pos, buf);
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
if (ASCII_ISUPPER(c) || ascii_isdigit(c)) {
|
if (ASCII_ISUPPER(c) || ascii_isdigit(c)) {
|
||||||
@@ -163,6 +189,7 @@ int setmark_pos(int c, pos_T *pos, int fnum, fmarkv_T *view_pt)
|
|||||||
i = c - 'A';
|
i = c - 'A';
|
||||||
}
|
}
|
||||||
RESET_XFMARK(namedfm + i, *pos, fnum, view, NULL);
|
RESET_XFMARK(namedfm + i, *pos, fnum, view, NULL);
|
||||||
|
do_markset_autocmd((char)c, pos, buf);
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
return FAIL;
|
return FAIL;
|
||||||
|
|||||||
@@ -1008,6 +1008,7 @@ void free_all_mem(void)
|
|||||||
ui_comp_free_all_mem();
|
ui_comp_free_all_mem();
|
||||||
nlua_free_all_mem();
|
nlua_free_all_mem();
|
||||||
rpc_free_all_mem();
|
rpc_free_all_mem();
|
||||||
|
autocmd_free_all_mem();
|
||||||
|
|
||||||
// should be last, in case earlier free functions deallocates arenas
|
// should be last, in case earlier free functions deallocates arenas
|
||||||
arena_free_reuse_blks();
|
arena_free_reuse_blks();
|
||||||
|
|||||||
286
test/functional/autocmd/markset_spec.lua
Normal file
286
test/functional/autocmd/markset_spec.lua
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
local t = require('test.testutil')
|
||||||
|
local n = require('test.functional.testnvim')()
|
||||||
|
|
||||||
|
local api = n.api
|
||||||
|
local clear = n.clear
|
||||||
|
local command = n.command
|
||||||
|
local feed = n.feed
|
||||||
|
local poke_eventloop = n.poke_eventloop
|
||||||
|
local eval = n.eval
|
||||||
|
|
||||||
|
local eq = t.eq
|
||||||
|
local neq = t.neq
|
||||||
|
|
||||||
|
describe('MarkSet', function()
|
||||||
|
-- TODO(justinmk): support other marks?: [, ] <, > . ^ " '
|
||||||
|
|
||||||
|
before_each(function()
|
||||||
|
clear()
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('emits when lowercase/uppercase/[/] marks are set', function()
|
||||||
|
command([[
|
||||||
|
let g:mark_names = ''
|
||||||
|
let g:mark_events = []
|
||||||
|
autocmd MarkSet * call add(g:mark_events, {'event': deepcopy(v:event)}) | let g:mark_names ..= expand('<amatch>')
|
||||||
|
" TODO: there is a bug lurking here.
|
||||||
|
" autocmd MarkSet * let g:mark_names ..= expand('<amatch>')
|
||||||
|
]])
|
||||||
|
|
||||||
|
api.nvim_buf_set_lines(0, 0, -1, true, {
|
||||||
|
'foo\0bar',
|
||||||
|
'baz text',
|
||||||
|
'line 3',
|
||||||
|
})
|
||||||
|
|
||||||
|
feed('ma')
|
||||||
|
feed('j')
|
||||||
|
command('mark b')
|
||||||
|
|
||||||
|
poke_eventloop()
|
||||||
|
eq('ab', eval('g:mark_names'))
|
||||||
|
|
||||||
|
-- event-data is copied to `v:event`.
|
||||||
|
eq({
|
||||||
|
{
|
||||||
|
event = {
|
||||||
|
col = 0,
|
||||||
|
line = 1,
|
||||||
|
name = 'a',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event = {
|
||||||
|
col = 0,
|
||||||
|
line = 2,
|
||||||
|
name = 'b',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, eval('g:mark_events'))
|
||||||
|
|
||||||
|
feed('mA')
|
||||||
|
feed('l')
|
||||||
|
feed('mB')
|
||||||
|
feed('j')
|
||||||
|
feed('mC')
|
||||||
|
|
||||||
|
feed('x') -- TODO(justinmk): Sets [,] marks but does not emit MarkSet event (yet).
|
||||||
|
feed('0vll<esc>') -- TODO(justinmk): Sets <,> marks but does not emit MarkSet event (yet).
|
||||||
|
-- XXX: set these marks manually to exercise these cases.
|
||||||
|
api.nvim_buf_set_mark(0, '[', 2, 0, {})
|
||||||
|
api.nvim_buf_set_mark(0, ']', 2, 0, {})
|
||||||
|
api.nvim_buf_set_mark(0, '<', 2, 0, {})
|
||||||
|
api.nvim_buf_set_mark(0, '>', 2, 0, {})
|
||||||
|
api.nvim_buf_set_mark(0, '"', 2, 0, {})
|
||||||
|
|
||||||
|
poke_eventloop()
|
||||||
|
eq('abABC[]<>"', eval('g:mark_names'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can subscribe to specific marks by pattern', function()
|
||||||
|
command([[
|
||||||
|
let g:mark_names = ''
|
||||||
|
autocmd MarkSet [ab] let g:mark_names ..= expand('<amatch>')
|
||||||
|
]])
|
||||||
|
|
||||||
|
api.nvim_buf_set_lines(0, 0, -1, true, {
|
||||||
|
'foo\0bar',
|
||||||
|
'baz text',
|
||||||
|
})
|
||||||
|
|
||||||
|
feed('md')
|
||||||
|
feed('mc')
|
||||||
|
feed('l')
|
||||||
|
feed('mb')
|
||||||
|
feed('j')
|
||||||
|
feed('ma')
|
||||||
|
|
||||||
|
poke_eventloop()
|
||||||
|
eq('ba', eval('g:mark_names'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('handles marks across multiple windows/buffers', function()
|
||||||
|
local orig_bufnr = api.nvim_get_current_buf()
|
||||||
|
|
||||||
|
command('enew')
|
||||||
|
local second_bufnr = api.nvim_get_current_buf()
|
||||||
|
api.nvim_buf_set_lines(second_bufnr, 0, -1, true, {
|
||||||
|
'second buffer line 1',
|
||||||
|
'second buffer line 2',
|
||||||
|
})
|
||||||
|
|
||||||
|
command('enew')
|
||||||
|
local third_bufnr = api.nvim_get_current_buf()
|
||||||
|
api.nvim_buf_set_lines(third_bufnr, 0, -1, true, {
|
||||||
|
'third buffer line 1',
|
||||||
|
'third buffer line 2',
|
||||||
|
})
|
||||||
|
|
||||||
|
command('split')
|
||||||
|
command('vsplit')
|
||||||
|
|
||||||
|
command('tabnew')
|
||||||
|
command('split')
|
||||||
|
|
||||||
|
command([[
|
||||||
|
let g:markset_events = []
|
||||||
|
autocmd MarkSet * call add(g:markset_events, { 'buf': 0 + expand('<abuf>'), 'event': deepcopy(v:event) })
|
||||||
|
]])
|
||||||
|
|
||||||
|
command('buffer ' .. orig_bufnr)
|
||||||
|
feed('gg')
|
||||||
|
feed('mA')
|
||||||
|
|
||||||
|
command('wincmd w')
|
||||||
|
command('tabnext')
|
||||||
|
|
||||||
|
feed('mB')
|
||||||
|
|
||||||
|
command('wincmd w')
|
||||||
|
command('enew')
|
||||||
|
|
||||||
|
local final_bufnr = api.nvim_get_current_buf()
|
||||||
|
api.nvim_buf_set_lines(final_bufnr, 0, -1, true, {
|
||||||
|
'final buffer after chaos',
|
||||||
|
'line 2 of final buffer',
|
||||||
|
})
|
||||||
|
|
||||||
|
feed('j')
|
||||||
|
feed('mC')
|
||||||
|
|
||||||
|
command('tabclose')
|
||||||
|
|
||||||
|
feed('mD')
|
||||||
|
|
||||||
|
poke_eventloop()
|
||||||
|
eq({
|
||||||
|
{
|
||||||
|
buf = 1,
|
||||||
|
event = {
|
||||||
|
col = 0,
|
||||||
|
line = 1,
|
||||||
|
name = 'A',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buf = 2,
|
||||||
|
event = {
|
||||||
|
col = 0,
|
||||||
|
line = 1,
|
||||||
|
name = 'B',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buf = 4,
|
||||||
|
event = {
|
||||||
|
col = 0,
|
||||||
|
line = 2,
|
||||||
|
name = 'C',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buf = 3,
|
||||||
|
event = {
|
||||||
|
col = 0,
|
||||||
|
line = 1,
|
||||||
|
name = 'D',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, eval('g:markset_events'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('handles an autocommand that calls bwipeout!', function()
|
||||||
|
api.nvim_buf_set_lines(0, 0, -1, true, {
|
||||||
|
'line 1',
|
||||||
|
'line 2',
|
||||||
|
'line 3',
|
||||||
|
})
|
||||||
|
|
||||||
|
local test_bufnr = api.nvim_get_current_buf()
|
||||||
|
|
||||||
|
command("autocmd MarkSet * let g:autocmd ..= expand('<amatch>') | bwipeout!")
|
||||||
|
command([[let g:autocmd = '']])
|
||||||
|
|
||||||
|
feed('ma')
|
||||||
|
poke_eventloop()
|
||||||
|
|
||||||
|
eq('a', eval('g:autocmd'))
|
||||||
|
|
||||||
|
eq(false, api.nvim_buf_is_valid(test_bufnr))
|
||||||
|
|
||||||
|
local current_bufnr = api.nvim_get_current_buf()
|
||||||
|
neq(current_bufnr, test_bufnr)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('when autocommand switches windows and tabs', function()
|
||||||
|
api.nvim_buf_set_lines(0, 0, -1, true, {
|
||||||
|
'first buffer line 1',
|
||||||
|
'first buffer line 2',
|
||||||
|
'first buffer line 3',
|
||||||
|
})
|
||||||
|
local first_bufnr = api.nvim_get_current_buf()
|
||||||
|
|
||||||
|
command('split')
|
||||||
|
command('enew')
|
||||||
|
api.nvim_buf_set_lines(0, 0, -1, true, {
|
||||||
|
'second buffer line 1',
|
||||||
|
'second buffer line 2',
|
||||||
|
})
|
||||||
|
local second_bufnr = api.nvim_get_current_buf()
|
||||||
|
|
||||||
|
command('tabnew')
|
||||||
|
api.nvim_buf_set_lines(0, 0, -1, true, {
|
||||||
|
'third buffer line 1',
|
||||||
|
'third buffer line 2',
|
||||||
|
'third buffer line 3',
|
||||||
|
})
|
||||||
|
local third_bufnr = api.nvim_get_current_buf()
|
||||||
|
|
||||||
|
command([[
|
||||||
|
let g:markset_events = []
|
||||||
|
autocmd MarkSet * call add(g:markset_events, {'buf': 0 + expand('<abuf>'), 'event': deepcopy(v:event)}) | wincmd w | tabnext
|
||||||
|
]])
|
||||||
|
|
||||||
|
command('buffer ' .. second_bufnr)
|
||||||
|
feed('j')
|
||||||
|
feed('mA')
|
||||||
|
command('buffer ' .. third_bufnr)
|
||||||
|
feed('l')
|
||||||
|
feed('mB')
|
||||||
|
command('buffer ' .. first_bufnr)
|
||||||
|
feed('jj')
|
||||||
|
feed('mC')
|
||||||
|
poke_eventloop()
|
||||||
|
|
||||||
|
eq({
|
||||||
|
{
|
||||||
|
buf = 2,
|
||||||
|
event = {
|
||||||
|
col = 0,
|
||||||
|
line = 2,
|
||||||
|
name = 'A',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buf = 3,
|
||||||
|
event = {
|
||||||
|
col = 1,
|
||||||
|
line = 1,
|
||||||
|
name = 'B',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buf = 1,
|
||||||
|
event = {
|
||||||
|
col = 0,
|
||||||
|
line = 3,
|
||||||
|
name = 'C',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, eval('g:markset_events'))
|
||||||
|
|
||||||
|
eq({ 2, 0 }, api.nvim_buf_get_mark(second_bufnr, 'A'))
|
||||||
|
eq({ 1, 1 }, api.nvim_buf_get_mark(third_bufnr, 'B'))
|
||||||
|
eq({ 3, 0 }, api.nvim_buf_get_mark(first_bufnr, 'C'))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
Reference in New Issue
Block a user