mirror of
https://github.com/neovim/neovim.git
synced 2026-04-01 05:12:02 +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|
|
||||
LspRequest See |LspRequest|
|
||||
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 Just before showing the popup menu (under 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
|
||||
internal |event-loop|.)
|
||||
|
||||
All new editor events must be implemented using `aucmd_defer()` (and where
|
||||
possible, old events should be migrated to this), so that they are processed
|
||||
in a predictable manner, which avoids crashes and race conditions. See
|
||||
Where possible, new editor events should be implemented using `aucmd_defer()`
|
||||
(and where possible, old events migrate to this), so they are processed in
|
||||
a predictable manner, which avoids crashes and race conditions. See
|
||||
`do_markset_autocmd` for an example.
|
||||
|
||||
==============================================================================
|
||||
|
||||
@@ -226,6 +226,8 @@ EVENTS
|
||||
• New `msg_id` parameter for |ui-messages| `msg_show` event.
|
||||
• '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.
|
||||
• |MarkSet| is triggered after a |mark| is set by the user (currently doesn't
|
||||
support implicit marks like |'[| or |'<|, …).
|
||||
|
||||
HIGHLIGHTS
|
||||
|
||||
|
||||
@@ -2535,6 +2535,7 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
|LspProgress|,
|
||||
|LspRequest|,
|
||||
|LspTokenUpdate|,
|
||||
|MarkSet|,
|
||||
|MenuPopup|,
|
||||
|ModeChanged|,
|
||||
|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'
|
||||
--- |'LspRequest'
|
||||
--- |'LspTokenUpdate'
|
||||
--- |'MarkSet'
|
||||
--- |'MenuPopup'
|
||||
--- |'ModeChanged'
|
||||
--- |'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`,
|
||||
--- `LspRequest`,
|
||||
--- `LspTokenUpdate`,
|
||||
--- `MarkSet`,
|
||||
--- `MenuPopup`,
|
||||
--- `ModeChanged`,
|
||||
--- `OptionSet`,
|
||||
|
||||
@@ -81,6 +81,7 @@ return {
|
||||
LspProgress = false, -- after a LSP progress update
|
||||
LspRequest = false, -- after an LSP request is started, canceled, or completed
|
||||
LspTokenUpdate = false, -- after a visible LSP token is updated
|
||||
MarkSet = false, -- after a mark is set
|
||||
MenuPopup = false, -- just before popup menu is displayed
|
||||
ModeChanged = false, -- after changing the mode
|
||||
OptionSet = false, -- after setting any option
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "nvim/api/private/converter.h"
|
||||
#include "nvim/autocmd.h"
|
||||
#include "nvim/autocmd_defs.h"
|
||||
#include "nvim/buffer.h"
|
||||
#include "nvim/charset.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(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)
|
||||
{
|
||||
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".
|
||||
///
|
||||
/// @param event event that occurred
|
||||
@@ -1680,7 +1773,8 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
|
||||
// invalid.
|
||||
if (fname_io == NULL) {
|
||||
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;
|
||||
} else if (fname != NULL && !ends_excmd(*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_FILETYPE
|
||||
|| event == EVENT_FUNCUNDEFINED
|
||||
|| event == EVENT_MARKSET
|
||||
|| event == EVENT_MENUPOPUP
|
||||
|| event == EVENT_MODECHANGED
|
||||
|| event == EVENT_OPTIONSET
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "nvim/buffer_defs.h"
|
||||
#include "nvim/cmdexpand_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/macros_defs.h"
|
||||
#include "nvim/pos_defs.h"
|
||||
@@ -68,4 +69,8 @@ enum { BUFLOCAL_PAT_LEN = 25, };
|
||||
#define FOR_ALL_AUEVENTS(event) \
|
||||
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"
|
||||
|
||||
@@ -64,3 +64,13 @@ struct AutoPatCmd_S {
|
||||
};
|
||||
|
||||
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);
|
||||
resize_events = multiqueue_new_child(main_loop.events);
|
||||
|
||||
autocmd_init();
|
||||
signal_init();
|
||||
// mspgack-rpc initialization
|
||||
channel_init();
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/ascii_defs.h"
|
||||
#include "nvim/autocmd.h"
|
||||
#include "nvim/buffer.h"
|
||||
#include "nvim/buffer_defs.h"
|
||||
#include "nvim/charset.h"
|
||||
@@ -87,6 +89,25 @@ void clear_fmark(fmark_T *const fm, const 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".
|
||||
// When "c" is upper case use file "fnum".
|
||||
// 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 == '"') {
|
||||
RESET_FMARK(&buf->b_last_cursor, *pos, buf->b_fnum, view);
|
||||
do_markset_autocmd((char)c, pos, buf);
|
||||
return OK;
|
||||
}
|
||||
|
||||
@@ -126,10 +148,12 @@ int setmark_pos(int c, pos_T *pos, int fnum, fmarkv_T *view_pt)
|
||||
// file.
|
||||
if (c == '[') {
|
||||
buf->b_op_start = *pos;
|
||||
do_markset_autocmd((char)c, pos, buf);
|
||||
return OK;
|
||||
}
|
||||
if (c == ']') {
|
||||
buf->b_op_end = *pos;
|
||||
do_markset_autocmd((char)c, pos, buf);
|
||||
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.
|
||||
buf->b_visual.vi_mode = 'v';
|
||||
}
|
||||
do_markset_autocmd((char)c, pos, buf);
|
||||
return OK;
|
||||
}
|
||||
|
||||
@@ -154,6 +179,7 @@ int setmark_pos(int c, pos_T *pos, int fnum, fmarkv_T *view_pt)
|
||||
if (ASCII_ISLOWER(c)) {
|
||||
i = c - 'a';
|
||||
RESET_FMARK(buf->b_namedm + i, *pos, fnum, view);
|
||||
do_markset_autocmd((char)c, pos, buf);
|
||||
return OK;
|
||||
}
|
||||
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';
|
||||
}
|
||||
RESET_XFMARK(namedfm + i, *pos, fnum, view, NULL);
|
||||
do_markset_autocmd((char)c, pos, buf);
|
||||
return OK;
|
||||
}
|
||||
return FAIL;
|
||||
|
||||
@@ -1008,6 +1008,7 @@ void free_all_mem(void)
|
||||
ui_comp_free_all_mem();
|
||||
nlua_free_all_mem();
|
||||
rpc_free_all_mem();
|
||||
autocmd_free_all_mem();
|
||||
|
||||
// should be last, in case earlier free functions deallocates arenas
|
||||
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