refactor: implement augroups as namespaces

This commit is contained in:
altermo 2024-04-21 15:20:11 +02:00
parent 6d732ad3c9
commit 9f9e7e82cf
8 changed files with 225 additions and 129 deletions

View File

@ -8,6 +8,7 @@
#include <string.h>
#include "klib/kvec.h"
#include "nvim/api/extmark.h"
#include "nvim/api/private/helpers.h"
#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
@ -90,12 +91,11 @@ static const char e_autocommand_nesting_too_deep[]
// Code for automatic commands.
static AutoPatCmd *active_apc_list = NULL; // stack of active autocommands
// ID for associating autocmds created via nvim_create_autocmd
// Used to delete autocmds from nvim_del_autocmd
static int next_augroup_id = 1;
// use get_deleted_augroup() to get this
static const char *deleted_augroup = NULL;
// The ID of the deleted group.
// Zero until the group is initialized.
static int deleted_augroup_id = 0;
static const char *deleted_augroup_name = "--Deleted--";
static Set(int) deleted_augroup_map = SET_INIT;
// The ID of the current group.
static int current_augroup = AUGROUP_DEFAULT;
@ -111,31 +111,16 @@ static bool autocmd_include_groups = false;
static char *old_termresponse = NULL;
// Map of autocmd group names and ids.
// name -> ID
// ID -> name
static Map(String, int) map_augroup_name_to_id = MAP_INIT;
static Map(int, String) map_augroup_id_to_name = MAP_INIT;
static void augroup_map_del(int id, const char *name)
static void augroup_map_del(int id)
{
if (name != NULL) {
String key;
map_del(String, int)(&map_augroup_name_to_id, cstr_as_string(name), &key);
api_free_string(key);
}
if (id > 0) {
String mapped = map_del(int, String)(&map_augroup_id_to_name, id, NULL);
api_free_string(mapped);
}
set_put(int, &deleted_augroup_map, id);
}
static inline const char *get_deleted_augroup(void) FUNC_ATTR_ALWAYS_INLINE
static int get_deleted_augroup_id(void)
{
if (deleted_augroup == NULL) {
deleted_augroup = _("--Deleted--");
}
return deleted_augroup;
int id = augroup_add(deleted_augroup_name);
deleted_augroup_id = id;
return id;
}
static void au_show_for_all_events(int group, const char *pat)
@ -219,7 +204,7 @@ static void au_show_for_event(int group, event_T event, const char *pat)
// show the group name, if it's not the default group
if (ac->pat->group != AUGROUP_DEFAULT) {
if (last_group_name == NULL) {
msg_puts_attr(get_deleted_augroup(), HL_ATTR(HLF_E));
msg_puts_attr(deleted_augroup_name, HL_ATTR(HLF_E));
} else {
msg_puts_attr(last_group_name, HL_ATTR(HLF_T));
}
@ -395,25 +380,17 @@ void aubuflocal_remove(buf_T *buf)
// Return its ID.
int augroup_add(const char *name)
{
assert(STRICMP(name, "end") != 0);
int existing_id = augroup_find(name);
if (existing_id > 0) {
assert(existing_id != AUGROUP_DELETED);
return existing_id;
if (STRICMP(name, "") == 0) {
return AUGROUP_ERROR;
}
if (existing_id == AUGROUP_DELETED) {
augroup_map_del(existing_id, name);
int id = (int)nvim_create_namespace(cstr_as_string(name));
if (set_has(int, &deleted_augroup_map, id)) {
set_del(int, &deleted_augroup_map, id);
}
int next_id = next_augroup_id++;
String name_key = cstr_to_string(name);
String name_val = cstr_to_string(name);
map_put(String, int)(&map_augroup_name_to_id, name_key, next_id);
map_put(int, String)(&map_augroup_id_to_name, next_id, name_val);
return next_id;
return id;
}
/// Delete the augroup that matches name.
@ -445,9 +422,7 @@ void augroup_del(char *name, bool stupid_legacy_mode)
AutoPat *const ap = kv_A(*acs, i).pat;
if (ap != NULL && ap->group == group) {
give_warning(_("W19: Deleting augroup that is still in use"), true);
map_put(String, int)(&map_augroup_name_to_id, cstr_as_string(name), AUGROUP_DELETED);
augroup_map_del(ap->group, NULL);
return;
ap->group = get_deleted_augroup_id();
}
}
}
@ -464,7 +439,7 @@ void augroup_del(char *name, bool stupid_legacy_mode)
}
// Remove the group because it's not currently in use.
augroup_map_del(group, name);
augroup_map_del(group);
au_cleanup();
}
@ -476,11 +451,10 @@ void augroup_del(char *name, bool stupid_legacy_mode)
int augroup_find(const char *name)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
int existing_id = map_get(String, int)(&map_augroup_name_to_id, cstr_as_string(name));
if (existing_id == AUGROUP_DELETED) {
return existing_id;
int existing_id = map_get(String, int)(&namespace_ids, cstr_as_string(name));
if (set_has(int, &deleted_augroup_map, existing_id)) {
return AUGROUP_ERROR;
}
if (existing_id > 0) {
return existing_id;
}
@ -493,38 +467,34 @@ char *augroup_name(int group)
{
assert(group != 0);
if (group == AUGROUP_DELETED) {
return (char *)get_deleted_augroup();
}
if (group == AUGROUP_ALL) {
group = current_augroup;
}
// next_augroup_id is the "source of truth" about what autocmds have existed
//
// The map_size is not the source of truth because groups can be removed from
// the map. When this happens, the map size is reduced. That's why this function
// relies on next_augroup_id instead.
if (group < 0) {
return (char *)deleted_augroup_name;
}
// "END" is always considered the last augroup ID.
// Used for expand_get_event_name and expand_get_augroup_name
if (group == next_augroup_id) {
if (group == next_namespace_id) {
return "END";
}
// If it's larger than the largest group, then it doesn't have a name
if (group > next_augroup_id) {
if (group > next_namespace_id) {
return NULL;
}
String key = map_get(int, String)(&map_augroup_id_to_name, group);
if (key.data != NULL) {
return key.data;
if (set_has(int, &deleted_augroup_map, group)) {
return "";
}
// If it's not in the map anymore, then it must have been deleted.
return (char *)get_deleted_augroup();
String name;
int value;
map_foreach(&namespace_ids, name, value, {
if (value == group) {
return name.data;
}
});
return "";
}
/// Return true if augroup "name" exists.
@ -554,14 +524,11 @@ void do_augroup(char *arg, bool del_group)
String name;
int value;
map_foreach(&map_augroup_name_to_id, name, value, {
if (value > 0) {
map_foreach(&namespace_ids, name, value, {
if (value > 0 && !set_has(int, &deleted_augroup_map, value)) {
msg_puts(name.data);
} else {
msg_puts(augroup_name(value));
}
msg_puts(" ");
}
});
msg_clr_eos();
@ -581,17 +548,7 @@ void free_all_autocmds(void)
au_need_clean = false;
}
// Delete the augroup_map, including free the data
String name;
map_foreach_key(&map_augroup_name_to_id, name, {
api_free_string(name);
})
map_destroy(String, &map_augroup_name_to_id);
map_foreach_value(&map_augroup_id_to_name, name, {
api_free_string(name);
})
map_destroy(int, &map_augroup_id_to_name);
set_destroy(int, &deleted_augroup_map);
// aucmd_win[] is freed in win_free_all()
}
@ -2028,10 +1985,12 @@ static bool call_autocmd_callback(const AutoCmd *ac, const AutoPatCmd *apc)
abort(); // unreachable
case AUGROUP_DEFAULT:
case AUGROUP_ALL:
case AUGROUP_DELETED:
// omit group in these cases
break;
default:
if (group == deleted_augroup_id) {
break;
}
PUT_C(data, "group", INTEGER_OBJ(group));
break;
}
@ -2232,7 +2191,7 @@ char *expand_get_event_name(expand_T *xp, int idx)
char *name = augroup_name(idx + 1);
if (name != NULL) {
// skip when not including groups or skip deleted entries
if (!autocmd_include_groups || name == get_deleted_augroup()) {
if (!autocmd_include_groups || name == deleted_augroup_name) {
return "";
}
@ -2240,7 +2199,7 @@ char *expand_get_event_name(expand_T *xp, int idx)
}
// List event names
return event_names[idx - next_augroup_id].name;
return event_names[idx - next_namespace_id].name;
}
/// Function given to ExpandGeneric() to obtain the list of event names. Don't

View File

@ -58,8 +58,6 @@ enum {
AUGROUP_DEFAULT = -1, ///< default autocmd group
AUGROUP_ERROR = -2, ///< erroneous autocmd group
AUGROUP_ALL = -3, ///< all autocmd groups
AUGROUP_DELETED = -4, ///< all autocmd groups
// AUGROUP_NS = -5, // TODO(tjdevries): Support namespaced based augroups
};
enum { BUFLOCAL_PAT_LEN = 25, };

View File

@ -1539,4 +1539,136 @@ describe('autocmd api', function()
eq(1, #with_group)
end)
end)
describe('autocmd as namespace:', function()
it('named namespaces work as augroups', function()
local ns = api.nvim_create_namespace('test')
eq(true, pcall(api.nvim_get_autocmds, { group = 'test' }))
eq(true, pcall(api.nvim_get_autocmds, { group = ns }))
eq(true, pcall(api.nvim_create_autocmd, 'BufNew', { command = '', group = 'test' }))
eq(true, pcall(api.nvim_create_autocmd, 'BufNew', { command = '', group = ns }))
end)
it('anonymous namespaces error when used as augroups', function()
local ns = api.nvim_create_namespace('')
eq(false, pcall(api.nvim_get_autocmds, { group = ns }))
eq(false, pcall(api.nvim_create_autocmd, 'BufNew', { command = '', group = ns }))
eq(false, pcall(api.nvim_create_augroup, '', {}))
end)
it('legacy style deleted autocmds work as expected', function()
api.nvim_create_augroup('TEMP_A', {})
api.nvim_create_autocmd('BufNew', { command = '', group = 'TEMP_A' })
local ns = api.nvim_create_namespace('')
command('augroup! TEMP_A')
api.nvim_create_augroup('TEMP_B', {})
api.nvim_create_autocmd('BufNew', { command = '', group = 'TEMP_B' })
command('augroup! TEMP_B')
eq(ns + 1, api.nvim_get_namespaces()['--Deleted--'])
local autocmds = api.nvim_get_autocmds({ event = 'BufNew' })
eq(ns + 1, autocmds[1].group)
eq(ns + 1, autocmds[2].group)
eq('--Deleted--', autocmds[2].group_name)
eq(2, #api.nvim_get_autocmds({ group = ns + 1 }))
eq(false, pcall(api.nvim_create_autocmd, 'BufNew', { command = '', group = ns }))
end)
it('listing augroups show named namespaces', function()
api.nvim_create_namespace('')
api.nvim_create_namespace('test')
api.nvim_create_namespace('')
eq(
{ 'END', 'nvim_cmdwin', 'nvim_swapfile', 'nvim_terminal', 'test' },
exec_lua([[return vim.fn.getcompletion("","augroup")]])
)
eq(
'nvim_terminal nvim_cmdwin nvim_swapfile test ',
api.nvim_exec2('augroup', { output = true }).output
)
end)
it('deleted augroup are sometimes not shown when listing augroups', function()
api.nvim_create_namespace('test')
api.nvim_del_augroup_by_name('test')
--eq(
-- { '--Deleted--', 'END', 'nvim_cmdwin', 'nvim_swapfile', 'nvim_terminal' },
-- exec_lua([[return vim.fn.getcompletion("","augroup")]])
--)
api.nvim_create_augroup('test', {})
api.nvim_del_augroup_by_name('test')
--eq(
-- { '--Deleted--', '--Deleted--', 'END', 'nvim_cmdwin', 'nvim_swapfile', 'nvim_terminal' },
-- exec_lua([[return vim.fn.getcompletion("","augroup")]])
--)
eq(
'nvim_terminal nvim_cmdwin nvim_swapfile ',
api.nvim_exec2('augroup', { output = true }).output
)
end)
it(
'legacy deleted augroup are sometimes shown as `--Deleted--` when listing augroups',
function()
api.nvim_create_augroup('TEMP_A', {})
api.nvim_create_autocmd('BufNew', { command = '', group = 'TEMP_A' })
command('augroup! TEMP_A')
eq(
{ '--Deleted--', 'END', 'nvim_cmdwin', 'nvim_swapfile', 'nvim_terminal' },
exec_lua([[return vim.fn.getcompletion("","augroup")]])
)
eq(
'nvim_terminal nvim_cmdwin nvim_swapfile --Deleted-- ',
api.nvim_exec2('augroup', { output = true }).output
)
api.nvim_create_augroup('TEMP_A', {})
api.nvim_create_autocmd('BufNew', { command = '', group = 'TEMP_A' })
command('augroup! TEMP_A')
eq(
'nvim_terminal nvim_cmdwin nvim_swapfile --Deleted-- ',
api.nvim_exec2('augroup', { output = true }).output
)
--api.nvim_create_augroup('TEMP_B', {})
--api.nvim_create_autocmd('BufNew', { command = '', group = 'TEMP_B' })
--command('augroup! TEMP_B')
--
--eq(
-- 'nvim_terminal nvim_cmdwin nvim_swapfile --Deleted-- --Deleted-- ',
-- api.nvim_exec2('augroup', { output = true }).output
--)
--
--api.nvim_create_augroup('TEMP_B', {})
--api.nvim_create_autocmd('BufNew', { command = '', group = 'TEMP_B' })
--command('augroup! TEMP_B')
--
--eq({
-- '--Deleted--',
-- '--Deleted--',
-- '--Deleted--',
-- '--Deleted--',
-- 'END',
-- 'nvim_cmdwin',
-- 'nvim_swapfile',
-- 'nvim_terminal',
--}, exec_lua([[return vim.fn.getcompletion("","augroup")]]))
--eq(
-- 'nvim_terminal nvim_cmdwin nvim_swapfile --Deleted-- --Deleted-- ',
-- api.nvim_exec2('augroup', { output = true }).output
--)
end
)
end)
end)

View File

@ -1423,13 +1423,11 @@ describe('API/extmarks', function()
it('throws consistent error codes', function()
local ns_invalid = ns2 + 1
eq(
"Invalid 'ns_id': 3",
pcall_err(set_extmark, ns_invalid, marks[1], positions[1][1], positions[1][2])
)
eq("Invalid 'ns_id': 3", pcall_err(api.nvim_buf_del_extmark, 0, ns_invalid, marks[1]))
eq("Invalid 'ns_id': 3", pcall_err(get_extmarks, ns_invalid, positions[1], positions[2]))
eq("Invalid 'ns_id': 3", pcall_err(get_extmark_by_id, ns_invalid, marks[1]))
local errmsg = "Invalid 'ns_id': " .. ns_invalid
eq(errmsg, pcall_err(set_extmark, ns_invalid, marks[1], positions[1][1], positions[1][2]))
eq(errmsg, pcall_err(api.nvim_buf_del_extmark, 0, ns_invalid, marks[1]))
eq(errmsg, pcall_err(get_extmarks, ns_invalid, positions[1], positions[2]))
eq(errmsg, pcall_err(get_extmark_by_id, ns_invalid, marks[1]))
end)
it('when col = line-length, set the mark on eol', function()
@ -1532,7 +1530,7 @@ describe('API/extmarks', function()
0,
0,
{
ns_id = 1,
ns_id = ns,
end_col = 0,
end_row = 1,
right_gravity = true,
@ -1595,7 +1593,7 @@ describe('API/extmarks', function()
hl_group = 'String',
hl_mode = 'blend',
line_hl_group = 'Statement',
ns_id = 1,
ns_id = ns,
number_hl_group = 'Statement',
priority = 0,
right_gravity = false,
@ -1618,7 +1616,7 @@ describe('API/extmarks', function()
0,
0,
{
ns_id = 1,
ns_id = ns,
right_gravity = true,
priority = 0,
virt_text = { { '', 'Macro' }, { '', { 'Type', 'Search' } }, { '' } },
@ -1633,7 +1631,7 @@ describe('API/extmarks', function()
0,
0,
{
ns_id = 1,
ns_id = ns,
cursorline_hl_group = 'Statement',
priority = 4096,
right_gravity = true,
@ -1652,7 +1650,7 @@ describe('API/extmarks', function()
end_col = 1,
end_right_gravity = false,
end_row = 0,
ns_id = 1,
ns_id = ns,
right_gravity = true,
spell = true,
},
@ -1668,7 +1666,7 @@ describe('API/extmarks', function()
end_col = 1,
end_right_gravity = false,
end_row = 0,
ns_id = 1,
ns_id = ns,
right_gravity = true,
spell = false,
},

View File

@ -3013,14 +3013,22 @@ describe('API', function()
describe('nvim_create_namespace', function()
it('works', function()
eq({}, api.nvim_get_namespaces())
eq(1, api.nvim_create_namespace('ns-1'))
eq(2, api.nvim_create_namespace('ns-2'))
eq(1, api.nvim_create_namespace('ns-1'))
eq({ ['ns-1'] = 1, ['ns-2'] = 2 }, api.nvim_get_namespaces())
eq(3, api.nvim_create_namespace(''))
eq(4, api.nvim_create_namespace(''))
eq({ ['ns-1'] = 1, ['ns-2'] = 2 }, api.nvim_get_namespaces())
local augoup_ns = { nvim_cmdwin = 2, nvim_swapfile = 3, nvim_terminal = 1 }
eq(augoup_ns, api.nvim_get_namespaces())
local ns_offset = vim.tbl_count(augoup_ns)
eq(1 + ns_offset, api.nvim_create_namespace('ns-1'))
eq(2 + ns_offset, api.nvim_create_namespace('ns-2'))
eq(1 + ns_offset, api.nvim_create_namespace('ns-1'))
eq(
vim.tbl_extend('error', { ['ns-1'] = 1 + ns_offset, ['ns-2'] = 2 + ns_offset }, augoup_ns),
api.nvim_get_namespaces()
)
eq(3 + ns_offset, api.nvim_create_namespace(''))
eq(4 + ns_offset, api.nvim_create_namespace(''))
eq(
vim.tbl_extend('error', { ['ns-1'] = 1 + ns_offset, ['ns-2'] = 2 + ns_offset }, augoup_ns),
api.nvim_get_namespaces()
)
end)
end)

View File

@ -23,9 +23,9 @@ describe('vim.inspect_pos', function()
vim.api.nvim_buf_set_extmark(buf, ns1, 0, 10, { hl_group = "Normal" })
vim.api.nvim_buf_set_extmark(buf, ns2, 0, 10, { hl_group = "Normal" })
vim.cmd("syntax on")
return {buf, vim.inspect_pos(0, 0, 10), vim.inspect_pos(buf1, 0, 10).syntax }
return {ns1, buf, vim.inspect_pos(0, 0, 10), vim.inspect_pos(buf1, 0, 10).syntax }
]])
local buf, items, other_buf_syntax = unpack(ret)
local ns1, buf, items, other_buf_syntax = unpack(ret)
eq('', eval('v:errmsg'))
eq({
@ -39,12 +39,12 @@ describe('vim.inspect_pos', function()
end_row = 0,
id = 1,
ns = 'ns1',
ns_id = 1,
ns_id = ns1,
opts = {
hl_eol = false,
hl_group = 'Normal',
hl_group_link = 'Normal',
ns_id = 1,
ns_id = ns1,
priority = 4096,
right_gravity = true,
},
@ -56,12 +56,12 @@ describe('vim.inspect_pos', function()
end_row = 0,
id = 1,
ns = '',
ns_id = 2,
ns_id = ns1 + 1,
opts = {
hl_eol = false,
hl_group = 'Normal',
hl_group_link = 'Normal',
ns_id = 2,
ns_id = ns1 + 1,
priority = 4096,
right_gravity = true,
},

View File

@ -702,14 +702,14 @@ describe('Buffer highlighting', function()
local s1 = { { 'Köttbullar', 'Comment' }, { 'Kräuterbutter' } }
local s2 = { { 'こんにちは', 'Comment' } }
set_virtual_text(0, id1, 0, s1, {})
local ns = set_virtual_text(0, id1, 0, s1, {})
eq({
{
1,
0,
0,
{
ns_id = 1,
ns_id = ns,
priority = 0,
virt_text = s1,
-- other details
@ -729,7 +729,7 @@ describe('Buffer highlighting', function()
lastline,
0,
{
ns_id = 1,
ns_id = ns,
priority = 0,
virt_text = s2,
-- other details
@ -898,11 +898,12 @@ describe('Buffer highlighting', function()
end)
it('and virtual text use the same namespace counter', function()
eq(1, add_highlight(0, 0, 'String', 0, 0, -1))
eq(2, set_virtual_text(0, 0, 0, { { '= text', 'Comment' } }, {}))
eq(3, api.nvim_create_namespace('my-ns'))
eq(4, add_highlight(0, 0, 'String', 0, 0, -1))
eq(5, set_virtual_text(0, 0, 0, { { '= text', 'Comment' } }, {}))
eq(6, api.nvim_create_namespace('other-ns'))
local ns = api.nvim_create_namespace('')
eq(1 + ns, add_highlight(0, 0, 'String', 0, 0, -1))
eq(2 + ns, set_virtual_text(0, 0, 0, { { '= text', 'Comment' } }, {}))
eq(3 + ns, api.nvim_create_namespace('my-ns'))
eq(4 + ns, add_highlight(0, 0, 'String', 0, 0, -1))
eq(5 + ns, set_virtual_text(0, 0, 0, { { '= text', 'Comment' } }, {}))
eq(6 + ns, api.nvim_create_namespace('other-ns'))
end)
end)

View File

@ -2144,7 +2144,7 @@ describe('extmark decorations', function()
eq({ { 1, 0, 8, { end_col = 13, end_right_gravity = false, end_row = 0,
hl_eol = false, hl_group = "NonText", undo_restore = false,
ns_id = 1, priority = 4096, right_gravity = true } } },
ns_id = ns, priority = 4096, right_gravity = true } } },
api.nvim_buf_get_extmarks(0, ns, {0,0}, {0, -1}, {details=true}))
end)