feat(api): nvim_clear_autocmd

Co-authored-by: Christian Clason <christian.clason@uni-due.de>
This commit is contained in:
TJ DeVries
2022-03-12 15:12:02 -05:00
committed by bfredl
parent b2bd800416
commit b80651eda9
5 changed files with 336 additions and 87 deletions

View File

@@ -392,9 +392,6 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
FUNC_API_SINCE(9)
{
int64_t autocmd_id = -1;
const char_u pattern_buflocal[BUFLOCAL_PAT_LEN];
int au_group = AUGROUP_DEFAULT;
char *desc = NULL;
Array patterns = ARRAY_DICT_INIT;
@@ -404,7 +401,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
Callback cb = CALLBACK_NONE;
if (!unpack_string_or_array(&event_array, &event, "event", err)) {
if (!unpack_string_or_array(&event_array, &event, "event", true, err)) {
goto cleanup;
}
@@ -466,84 +463,13 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
bool is_once = api_object_to_bool(opts->once, "once", false, err);
bool is_nested = api_object_to_bool(opts->nested, "nested", false, err);
switch (opts->group.type) {
case kObjectTypeNil:
break;
case kObjectTypeString:
au_group = augroup_find(opts->group.data.string.data);
if (au_group == AUGROUP_ERROR) {
api_set_error(err,
kErrorTypeValidation,
"invalid augroup: %s", opts->group.data.string.data);
goto cleanup;
}
break;
case kObjectTypeInteger:
au_group = (int)opts->group.data.integer;
char *name = augroup_name(au_group);
if (!augroup_exists(name)) {
api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group);
goto cleanup;
}
break;
default:
api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer.");
int au_group = get_augroup_from_object(opts->group, err);
if (au_group == AUGROUP_ERROR) {
goto cleanup;
}
if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) {
api_set_error(err, kErrorTypeValidation,
"cannot pass both: 'pattern' and 'buffer' for the same autocmd");
if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) {
goto cleanup;
} else if (opts->pattern.type != kObjectTypeNil) {
Object *v = &opts->pattern;
if (v->type == kObjectTypeString) {
char_u *pat = (char_u *)v->data.string.data;
size_t patlen = aucmd_pattern_length(pat);
while (patlen) {
ADD(patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen)));
pat = aucmd_next_pattern(pat, patlen);
patlen = aucmd_pattern_length(pat);
}
} else if (v->type == kObjectTypeArray) {
if (!check_autocmd_string_array(patterns, "pattern", err)) {
goto cleanup;
}
Array array = v->data.array;
for (size_t i = 0; i < array.size; i++) {
char_u *pat = (char_u *)array.items[i].data.string.data;
size_t patlen = aucmd_pattern_length(pat);
while (patlen) {
ADD(patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen)));
pat = aucmd_next_pattern(pat, patlen);
patlen = aucmd_pattern_length(pat);
}
}
} else {
api_set_error(err,
kErrorTypeValidation,
"'pattern' must be a string");
goto cleanup;
}
} else if (opts->buffer.type != kObjectTypeNil) {
if (opts->buffer.type != kObjectTypeInteger) {
api_set_error(err,
kErrorTypeValidation,
"'buffer' must be an integer");
goto cleanup;
}
buf_T *buf = find_buffer_by_handle((Buffer)opts->buffer.data.integer, err);
if (ERROR_SET(err)) {
goto cleanup;
}
snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
ADD(patterns, STRING_OBJ(cstr_to_string((char *)pattern_buflocal)));
}
if (opts->desc.type != kObjectTypeNil) {
@@ -615,6 +541,94 @@ void nvim_del_autocmd(Integer id)
autocmd_delete_id(id);
}
/// Clear all autocommands that match the corresponding {opts}. To delete
/// a particular autocmd, see |nvim_del_autocmd|.
/// @param opts Parameters
/// - event: (string|table)
/// Examples:
/// - event: "pat1"
/// - event: { "pat1" }
/// - event: { "pat1", "pat2", "pat3" }
/// - pattern: (string|table)
/// - pattern or patterns to match exactly.
/// - For example, if you have `*.py` as that pattern for the autocmd,
/// you must pass `*.py` exactly to clear it. `test.py` will not
/// match the pattern.
/// - defaults to clearing all patterns.
/// - NOTE: Cannot be used with {buffer}
/// - buffer: (bufnr)
/// - clear only |autocmd-buflocal| autocommands.
/// - NOTE: Cannot be used with {pattern}
/// - group: (string|int) The augroup name or id.
/// - NOTE: If not passed, will only delete autocmds *not* in any group.
///
void nvim_clear_autocmd(Dict(clear_autocmd) *opts, Error *err)
FUNC_API_SINCE(9)
{
// TODO(tjdevries): Future improvements:
// - once: (boolean) - Only clear autocmds with once. See |autocmd-once|
// - nested: (boolean) - Only clear autocmds with nested. See |autocmd-nested|
// - group: Allow passing "*" or true or something like that to force doing all
// autocmds, regardless of their group.
Array patterns = ARRAY_DICT_INIT;
Array event_array = ARRAY_DICT_INIT;
if (!unpack_string_or_array(&event_array, &opts->event, "event", false, err)) {
goto cleanup;
}
if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) {
api_set_error(err, kErrorTypeValidation,
"Cannot use both 'pattern' and 'buffer'");
goto cleanup;
}
int au_group = get_augroup_from_object(opts->group, err);
if (au_group == AUGROUP_ERROR) {
goto cleanup;
}
if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) {
goto cleanup;
}
// When we create the autocmds, we want to say that they are all matched, so that's *
// but when we clear them, we want to say that we didn't pass a pattern, so that's NUL
if (patterns.size == 0) {
ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("")));
}
// If we didn't pass any events, that means clear all events.
if (event_array.size == 0) {
FOR_ALL_AUEVENTS(event) {
FOREACH_ITEM(patterns, pat_object, {
char_u *pat = (char_u *)pat_object.data.string.data;
if (!clear_autocmd(event, pat, au_group, err)) {
goto cleanup;
}
});
}
} else {
FOREACH_ITEM(event_array, event_str, {
GET_ONE_EVENT(event_nr, event_str, cleanup);
FOREACH_ITEM(patterns, pat_object, {
char_u *pat = (char_u *)pat_object.data.string.data;
if (!clear_autocmd(event_nr, pat, au_group, err)) {
goto cleanup;
}
});
});
}
cleanup:
api_free_array(event_array);
api_free_array(patterns);
return;
}
/// Create or get an autocommand group |autocmd-groups|.
///
/// To get an existing group id, do:
@@ -709,7 +723,7 @@ void nvim_exec_autocmd(Object event, Dict(exec_autocmd) *opts, Error *err)
Array event_array = ARRAY_DICT_INIT;
if (!unpack_string_or_array(&event_array, &event, "event", err)) {
if (!unpack_string_or_array(&event_array, &event, "event", true, err)) {
goto cleanup;
}
@@ -808,7 +822,7 @@ static bool check_autocmd_string_array(Array arr, char *k, Error *err)
return true;
}
static bool unpack_string_or_array(Array *array, Object *v, char *k, Error *err)
static bool unpack_string_or_array(Array *array, Object *v, char *k, bool required, Error *err)
{
if (v->type == kObjectTypeString) {
ADD(*array, copy_object(*v));
@@ -818,10 +832,119 @@ static bool unpack_string_or_array(Array *array, Object *v, char *k, Error *err)
}
*array = copy_array(v->data.array);
} else {
api_set_error(err,
kErrorTypeValidation,
"'%s' must be an array or a string.",
k);
if (required) {
api_set_error(err,
kErrorTypeValidation,
"'%s' must be an array or a string.",
k);
return false;
}
}
return true;
}
// Returns AUGROUP_ERROR if there was a problem with {group}
static int get_augroup_from_object(Object group, Error *err)
{
int au_group = AUGROUP_ERROR;
switch (group.type) {
case kObjectTypeNil:
return AUGROUP_DEFAULT;
case kObjectTypeString:
au_group = augroup_find(group.data.string.data);
if (au_group == AUGROUP_ERROR) {
api_set_error(err,
kErrorTypeValidation,
"invalid augroup: %s", group.data.string.data);
return AUGROUP_ERROR;
}
return au_group;
case kObjectTypeInteger:
au_group = (int)group.data.integer;
char *name = augroup_name(au_group);
if (!augroup_exists(name)) {
api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group);
return AUGROUP_ERROR;
}
return au_group;
default:
api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer.");
return AUGROUP_ERROR;
}
}
static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Object buffer,
Error *err)
{
const char_u pattern_buflocal[BUFLOCAL_PAT_LEN];
if (pattern.type != kObjectTypeNil && buffer.type != kObjectTypeNil) {
api_set_error(err, kErrorTypeValidation,
"cannot pass both: 'pattern' and 'buffer' for the same autocmd");
return false;
} else if (pattern.type != kObjectTypeNil) {
Object *v = &pattern;
if (v->type == kObjectTypeString) {
char_u *pat = (char_u *)v->data.string.data;
size_t patlen = aucmd_pattern_length(pat);
while (patlen) {
ADD(*patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen)));
pat = aucmd_next_pattern(pat, patlen);
patlen = aucmd_pattern_length(pat);
}
} else if (v->type == kObjectTypeArray) {
if (!check_autocmd_string_array(*patterns, "pattern", err)) {
return false;
}
Array array = v->data.array;
for (size_t i = 0; i < array.size; i++) {
char_u *pat = (char_u *)array.items[i].data.string.data;
size_t patlen = aucmd_pattern_length(pat);
while (patlen) {
ADD(*patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen)));
pat = aucmd_next_pattern(pat, patlen);
patlen = aucmd_pattern_length(pat);
}
}
} else {
api_set_error(err,
kErrorTypeValidation,
"'pattern' must be a string");
return false;
}
} else if (buffer.type != kObjectTypeNil) {
if (buffer.type != kObjectTypeInteger) {
api_set_error(err,
kErrorTypeValidation,
"'buffer' must be an integer");
return false;
}
buf_T *buf = find_buffer_by_handle((Buffer)buffer.data.integer, err);
if (ERROR_SET(err)) {
return false;
}
snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
ADD(*patterns, STRING_OBJ(cstr_to_string((char *)pattern_buflocal)));
}
return true;
}
static bool clear_autocmd(event_T event, char_u *pat, int au_group, Error *err)
{
if (do_autocmd_event(event, pat, false, false, (char_u *)"", true, au_group) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to clear autocmd");
return false;
}