mirror of
https://github.com/neovim/neovim.git
synced 2024-12-19 10:45:16 -07:00
feat(lua): add api and lua autocmds
This commit is contained in:
parent
1b5767aa34
commit
991e472881
1
.gitignore
vendored
1
.gitignore
vendored
@ -15,6 +15,7 @@ compile_commands.json
|
||||
/.clangd/
|
||||
/.cache/clangd/
|
||||
/.ccls-cache/
|
||||
/.clang-tidy
|
||||
|
||||
.DS_Store
|
||||
*.mo
|
||||
|
@ -95,6 +95,7 @@ CONFIG = {
|
||||
'window.c',
|
||||
'win_config.c',
|
||||
'tabpage.c',
|
||||
'autocmd.c',
|
||||
'ui.c',
|
||||
],
|
||||
# List of files/directories for doxygen to read, separated by blanks
|
||||
|
11
scripts/uncrustify.sh
Executable file
11
scripts/uncrustify.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# Check that you have uncrustify
|
||||
hash uncrustify
|
||||
|
||||
COMMITISH="${1:-master}"
|
||||
for file in $(git diff --diff-filter=d --name-only $COMMITISH | grep '\.[ch]$'); do
|
||||
uncrustify -c src/uncrustify.cfg -l C --replace --no-backup "$file"
|
||||
done
|
669
src/nvim/api/autocmd.c
Normal file
669
src/nvim/api/autocmd.c
Normal file
@ -0,0 +1,669 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "lauxlib.h"
|
||||
#include "nvim/api/autocmd.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/ascii.h"
|
||||
#include "nvim/buffer.h"
|
||||
#include "nvim/eval/typval.h"
|
||||
#include "nvim/fileio.h"
|
||||
#include "nvim/lua/executor.h"
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "api/autocmd.c.generated.h"
|
||||
#endif
|
||||
|
||||
#define AUCMD_MAX_PATTERNS 256
|
||||
|
||||
// Check whether every item in the array is a kObjectTypeString
|
||||
#define CHECK_STRING_ARRAY(__array, k, v, goto_name) \
|
||||
for (size_t j = 0; j < __array.size; j++) { \
|
||||
Object item = __array.items[j]; \
|
||||
if (item.type != kObjectTypeString) { \
|
||||
api_set_error(err, \
|
||||
kErrorTypeValidation, \
|
||||
"All entries in '%s' must be strings", \
|
||||
k); \
|
||||
goto goto_name; \
|
||||
} \
|
||||
}
|
||||
|
||||
// Copy string or array of strings into an empty array.
|
||||
#define UNPACK_STRING_OR_ARRAY(__array, k, v, goto_name) \
|
||||
if (v->type == kObjectTypeString) { \
|
||||
ADD(__array, copy_object(*v)); \
|
||||
} else if (v->type == kObjectTypeArray) { \
|
||||
CHECK_STRING_ARRAY(__array, k, v, goto_name); \
|
||||
__array = copy_array(v->data.array); \
|
||||
} else { \
|
||||
api_set_error(err, \
|
||||
kErrorTypeValidation, \
|
||||
"'%s' must be an array or a string.", \
|
||||
k); \
|
||||
goto goto_name; \
|
||||
}
|
||||
|
||||
// Get the event number, unless it is an error. Then goto `goto_name`.
|
||||
#define GET_ONE_EVENT(event_nr, event_str, goto_name) \
|
||||
char_u *__next_ev; \
|
||||
event_T event_nr = \
|
||||
event_name2nr((char_u *)event_str.data.string.data, &__next_ev); \
|
||||
if (event_nr >= NUM_EVENTS) { \
|
||||
api_set_error(err, kErrorTypeValidation, "unexpected event"); \
|
||||
goto goto_name; \
|
||||
}
|
||||
|
||||
|
||||
// ID for associating autocmds created via nvim_create_autocmd
|
||||
// Used to delete autocmds from nvim_del_autocmd
|
||||
static int64_t next_autocmd_id = 1;
|
||||
|
||||
/// Get autocmds that match the requirements passed to {opts}.
|
||||
/// group
|
||||
/// event
|
||||
/// pattern
|
||||
///
|
||||
/// -- @param {string} event - event or events to match against
|
||||
/// vim.api.nvim_get_autocmds({ event = "FileType" })
|
||||
///
|
||||
Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
|
||||
FUNC_API_SINCE(9)
|
||||
{
|
||||
Array autocmd_list = ARRAY_DICT_INIT;
|
||||
char_u *pattern_filters[AUCMD_MAX_PATTERNS];
|
||||
char_u pattern_buflocal[BUFLOCAL_PAT_LEN];
|
||||
|
||||
bool event_set[NUM_EVENTS] = { false };
|
||||
bool check_event = false;
|
||||
|
||||
int group = 0;
|
||||
|
||||
if (opts->group.type != kObjectTypeNil) {
|
||||
Object v = opts->group;
|
||||
if (v.type != kObjectTypeString) {
|
||||
api_set_error(err, kErrorTypeValidation, "group must be a string.");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
group = augroup_find(v.data.string.data);
|
||||
|
||||
if (group < 0) {
|
||||
api_set_error(err, kErrorTypeValidation, "invalid augroup passed.");
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
if (opts->event.type != kObjectTypeNil) {
|
||||
check_event = true;
|
||||
|
||||
Object v = opts->event;
|
||||
if (v.type == kObjectTypeString) {
|
||||
GET_ONE_EVENT(event_nr, v, cleanup);
|
||||
event_set[event_nr] = true;
|
||||
} else if (v.type == kObjectTypeArray) {
|
||||
FOREACH_ITEM(v.data.array, event_v, {
|
||||
if (event_v.type != kObjectTypeString) {
|
||||
api_set_error(err,
|
||||
kErrorTypeValidation,
|
||||
"Every event must be a string in 'event'");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
GET_ONE_EVENT(event_nr, event_v, cleanup);
|
||||
event_set[event_nr] = true;
|
||||
})
|
||||
} else {
|
||||
api_set_error(err,
|
||||
kErrorTypeValidation,
|
||||
"Not a valid 'event' value. Must be a string or an array");
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
int pattern_filter_count = 0;
|
||||
if (opts->pattern.type != kObjectTypeNil) {
|
||||
Object v = opts->pattern;
|
||||
if (v.type == kObjectTypeString) {
|
||||
pattern_filters[pattern_filter_count] = (char_u *)v.data.string.data;
|
||||
pattern_filter_count += 1;
|
||||
} else if (v.type == kObjectTypeArray) {
|
||||
FOREACH_ITEM(v.data.array, item, {
|
||||
pattern_filters[pattern_filter_count] = (char_u *)item.data.string.data;
|
||||
pattern_filter_count += 1;
|
||||
});
|
||||
} else {
|
||||
api_set_error(err,
|
||||
kErrorTypeValidation,
|
||||
"Not a valid 'pattern' value. Must be a string or an array");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (pattern_filter_count >= AUCMD_MAX_PATTERNS) {
|
||||
api_set_error(err,
|
||||
kErrorTypeValidation,
|
||||
"Too many patterns. Please limit yourself to less");
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
FOR_ALL_AUEVENTS(event) {
|
||||
if (check_event && !event_set[event]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (AutoPat *ap = au_get_autopat_for_event(event);
|
||||
ap != NULL;
|
||||
ap = ap->next) {
|
||||
if (ap == NULL || ap->cmds == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip autocmds from invalid groups if passed.
|
||||
if (group != 0 && ap->group != group) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip 'pattern' from invalid patterns if passed.
|
||||
if (pattern_filter_count > 0) {
|
||||
bool passed = false;
|
||||
for (int i = 0; i < pattern_filter_count; i++) {
|
||||
assert(i < AUCMD_MAX_PATTERNS);
|
||||
assert(pattern_filters[i]);
|
||||
|
||||
char_u *pat = pattern_filters[i];
|
||||
int patlen = (int)STRLEN(pat);
|
||||
|
||||
if (aupat_is_buflocal(pat, patlen)) {
|
||||
aupat_normalize_buflocal_pat(pattern_buflocal,
|
||||
pat,
|
||||
patlen,
|
||||
aupat_get_buflocal_nr(pat, patlen));
|
||||
|
||||
pat = pattern_buflocal;
|
||||
}
|
||||
|
||||
if (strequal((char *)ap->pat, (char *)pat)) {
|
||||
passed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!passed) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
|
||||
if (aucmd_exec_is_deleted(ac->exec)) {
|
||||
continue;
|
||||
}
|
||||
Dictionary autocmd_info = ARRAY_DICT_INIT;
|
||||
|
||||
if (ap->group != AUGROUP_DEFAULT) {
|
||||
PUT(autocmd_info, "group", INTEGER_OBJ(ap->group));
|
||||
}
|
||||
|
||||
if (ac->id > 0) {
|
||||
PUT(autocmd_info, "id", INTEGER_OBJ(ac->id));
|
||||
}
|
||||
|
||||
if (ac->desc != NULL) {
|
||||
PUT(autocmd_info, "desc", CSTR_TO_OBJ(ac->desc));
|
||||
}
|
||||
|
||||
PUT(autocmd_info,
|
||||
"command",
|
||||
STRING_OBJ(cstr_to_string(aucmd_exec_to_string(ac, ac->exec))));
|
||||
|
||||
PUT(autocmd_info,
|
||||
"pattern",
|
||||
STRING_OBJ(cstr_to_string((char *)ap->pat)));
|
||||
|
||||
PUT(autocmd_info, "once", BOOLEAN_OBJ(ac->once));
|
||||
|
||||
if (ap->buflocal_nr) {
|
||||
PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(true));
|
||||
PUT(autocmd_info, "buffer", INTEGER_OBJ(ap->buflocal_nr));
|
||||
} else {
|
||||
PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(false));
|
||||
}
|
||||
|
||||
// TODO(sctx): It would be good to unify script_ctx to actually work with lua
|
||||
// right now it's just super weird, and never really gives you the info that
|
||||
// you would expect from this.
|
||||
//
|
||||
// I think we should be able to get the line number, filename, etc. from lua
|
||||
// when we're executing something, and it should be easy to then save that
|
||||
// info here.
|
||||
//
|
||||
// I think it's a big loss not getting line numbers of where options, autocmds,
|
||||
// etc. are set (just getting "Sourced (lua)" or something is not that helpful.
|
||||
//
|
||||
// Once we do that, we can put these into the autocmd_info, but I don't think it's
|
||||
// useful to do that at this time.
|
||||
//
|
||||
// PUT(autocmd_info, "sid", INTEGER_OBJ(ac->script_ctx.sc_sid));
|
||||
// PUT(autocmd_info, "lnum", INTEGER_OBJ(ac->script_ctx.sc_lnum));
|
||||
|
||||
ADD(autocmd_list, DICTIONARY_OBJ(autocmd_info));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
return autocmd_list;
|
||||
}
|
||||
|
||||
/// Define an autocmd.
|
||||
/// @param opts Dictionary
|
||||
/// Required keys:
|
||||
/// event: string | ArrayOf(string)
|
||||
/// event = "pat1,pat2,pat3",
|
||||
/// event = "pat1"
|
||||
/// event = {"pat1"}
|
||||
/// event = {"pat1", "pat2", "pat3"}
|
||||
///
|
||||
///
|
||||
/// -- @param {string} name - augroup name
|
||||
/// -- @param {string | table} event - event or events to match against
|
||||
/// -- @param {string | table} pattern - pattern or patterns to match against
|
||||
/// -- @param {string | function} callback - function or string to execute on autocmd
|
||||
/// -- @param {string} command - optional, vimscript command
|
||||
/// Eg. command = "let g:value_set = v:true"
|
||||
/// -- @param {boolean} once - optional, defaults to false
|
||||
///
|
||||
/// -- pattern = comma delimited list of patterns | pattern | { pattern, ... }
|
||||
///
|
||||
/// pattern = "*.py,*.pyi"
|
||||
/// pattern = "*.py"
|
||||
/// pattern = {"*.py"}
|
||||
/// pattern = { "*.py", "*.pyi" }
|
||||
///
|
||||
/// -- not supported
|
||||
/// pattern = {"*.py,*.pyi"}
|
||||
///
|
||||
/// -- event = string | string[]
|
||||
/// event = "FileType,CursorHold"
|
||||
/// event = "BufPreWrite"
|
||||
/// event = {"BufPostWrite"}
|
||||
/// event = {"CursorHold", "BufPreWrite", "BufPostWrite"}
|
||||
Integer nvim_create_autocmd(uint64_t channel_id, Dict(create_autocmd) *opts, Error *err)
|
||||
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;
|
||||
Array event_array = ARRAY_DICT_INIT;
|
||||
|
||||
AucmdExecutable aucmd = AUCMD_EXECUTABLE_INIT;
|
||||
Callback cb = CALLBACK_NONE;
|
||||
|
||||
if (opts->callback.type != kObjectTypeNil && opts->command.type != kObjectTypeNil) {
|
||||
api_set_error(err, kErrorTypeValidation,
|
||||
"cannot pass both: 'callback' and 'command' for the same autocmd");
|
||||
goto cleanup;
|
||||
} else if (opts->callback.type != kObjectTypeNil) {
|
||||
// TODO(tjdevries): It's possible we could accept callable tables,
|
||||
// but we don't do that many other places, so for the moment let's
|
||||
// not do that.
|
||||
|
||||
Object *callback = &opts->callback;
|
||||
if (callback->type == kObjectTypeLuaRef) {
|
||||
if (callback->data.luaref == LUA_NOREF) {
|
||||
api_set_error(err,
|
||||
kErrorTypeValidation,
|
||||
"must pass an actual value");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!nlua_ref_is_function(callback->data.luaref)) {
|
||||
api_set_error(err,
|
||||
kErrorTypeValidation,
|
||||
"must pass a function for callback");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cb.type = kCallbackLua;
|
||||
cb.data.luaref = api_new_luaref(callback->data.luaref);
|
||||
} else if (callback->type == kObjectTypeString) {
|
||||
cb.type = kCallbackFuncref;
|
||||
cb.data.funcref = vim_strsave((char_u *)callback->data.string.data);
|
||||
} else {
|
||||
api_set_error(err,
|
||||
kErrorTypeException,
|
||||
"'callback' must be a lua function or name of vim function");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
aucmd.type = CALLABLE_CB;
|
||||
aucmd.callable.cb = cb;
|
||||
} else if (opts->command.type != kObjectTypeNil) {
|
||||
Object *command = &opts->command;
|
||||
if (command->type == kObjectTypeString) {
|
||||
aucmd.type = CALLABLE_EX;
|
||||
aucmd.callable.cmd = vim_strsave((char_u *)command->data.string.data);
|
||||
} else {
|
||||
api_set_error(err,
|
||||
kErrorTypeValidation,
|
||||
"'command' must be a string");
|
||||
goto cleanup;
|
||||
}
|
||||
} else {
|
||||
api_set_error(err, kErrorTypeValidation, "must pass one of: 'command', 'callback'");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (opts->event.type != kObjectTypeNil) {
|
||||
UNPACK_STRING_OR_ARRAY(event_array, "event", (&opts->event), cleanup)
|
||||
}
|
||||
|
||||
bool is_once = api_object_to_bool(opts->once, "once", false, err);
|
||||
bool is_nested = api_object_to_bool(opts->nested, "nested", false, err);
|
||||
|
||||
// TOOD: accept number for namespace instead
|
||||
if (opts->group.type != kObjectTypeNil) {
|
||||
Object *v = &opts->group;
|
||||
if (v->type != kObjectTypeString) {
|
||||
api_set_error(err, kErrorTypeValidation, "'group' must be a string");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
au_group = augroup_find(v->data.string.data);
|
||||
|
||||
if (au_group == AUGROUP_ERROR) {
|
||||
api_set_error(err,
|
||||
kErrorTypeException,
|
||||
"invalid augroup: %s", v->data.string.data);
|
||||
|
||||
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");
|
||||
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) {
|
||||
CHECK_STRING_ARRAY(patterns, "pattern", v, 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 (aucmd.type == CALLABLE_NONE) {
|
||||
api_set_error(err,
|
||||
kErrorTypeValidation,
|
||||
"'command' or 'callback' is required");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (opts->desc.type != kObjectTypeNil) {
|
||||
if (opts->desc.type == kObjectTypeString) {
|
||||
desc = opts->desc.data.string.data;
|
||||
} else {
|
||||
api_set_error(err,
|
||||
kErrorTypeValidation,
|
||||
"'desc' must be a string");
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
if (patterns.size == 0) {
|
||||
ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("*")));
|
||||
}
|
||||
|
||||
if (event_array.size == 0) {
|
||||
api_set_error(err, kErrorTypeValidation, "'event' is a required key");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
autocmd_id = next_autocmd_id++;
|
||||
FOREACH_ITEM(event_array, event_str, {
|
||||
GET_ONE_EVENT(event_nr, event_str, cleanup);
|
||||
|
||||
int retval;
|
||||
|
||||
for (size_t i = 0; i < patterns.size; i++) {
|
||||
Object pat = patterns.items[i];
|
||||
|
||||
// See: TODO(sctx)
|
||||
WITH_SCRIPT_CONTEXT(channel_id, {
|
||||
retval = autocmd_register(autocmd_id,
|
||||
event_nr,
|
||||
(char_u *)pat.data.string.data,
|
||||
(int)pat.data.string.size,
|
||||
au_group,
|
||||
is_once,
|
||||
is_nested,
|
||||
desc,
|
||||
aucmd);
|
||||
});
|
||||
|
||||
if (retval == FAIL) {
|
||||
api_set_error(err, kErrorTypeException, "Failed to set autocmd");
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
cleanup:
|
||||
aucmd_exec_free(&aucmd);
|
||||
api_free_array(event_array);
|
||||
api_free_array(patterns);
|
||||
|
||||
return autocmd_id;
|
||||
}
|
||||
|
||||
/// Delete an autocmd by ID. Autocmds only return IDs when created
|
||||
/// via the API.
|
||||
///
|
||||
/// @param id Integer The ID returned by nvim_create_autocmd
|
||||
void nvim_del_autocmd(Integer id)
|
||||
FUNC_API_SINCE(9)
|
||||
{
|
||||
autocmd_delete_id(id);
|
||||
}
|
||||
|
||||
/// Create or get an augroup.
|
||||
///
|
||||
/// To get an existing augroup ID, do:
|
||||
/// <pre>
|
||||
/// local id = vim.api.nvim_create_augroup({ name = name, clear = false });
|
||||
/// </pre>
|
||||
///
|
||||
/// @param opts Parameters
|
||||
/// - name (string): The name of the augroup
|
||||
/// - clear (bool): Whether to clear existing commands or not.
|
||||
// Defaults to true.
|
||||
/// See |autocmd-groups|
|
||||
Integer nvim_create_augroup(uint64_t channel_id, Dict(create_augroup) *opts, Error *err)
|
||||
FUNC_API_SINCE(9)
|
||||
{
|
||||
bool clear_autocmds = api_object_to_bool(opts->clear, "clear", true, err);
|
||||
|
||||
if (opts->name.type != kObjectTypeString) {
|
||||
api_set_error(err, kErrorTypeValidation, "'name' is required and must be a string");
|
||||
return -1;
|
||||
}
|
||||
char *name = opts->name.data.string.data;
|
||||
|
||||
int augroup = -1;
|
||||
WITH_SCRIPT_CONTEXT(channel_id, {
|
||||
augroup = augroup_add(name);
|
||||
if (augroup == AUGROUP_ERROR) {
|
||||
api_set_error(err, kErrorTypeException, "Failed to set augroup");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (clear_autocmds) {
|
||||
FOR_ALL_AUEVENTS(event) {
|
||||
aupat_del_for_event_and_group(event, augroup);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return augroup;
|
||||
}
|
||||
|
||||
/// NOTE: behavior differs from augroup-delete.
|
||||
/// When deleting an augroup, autocmds contained by this augroup will also be deleted and cleared.
|
||||
/// This augroup will no longer exist
|
||||
void nvim_del_augroup_by_id(Integer id)
|
||||
FUNC_API_SINCE(9)
|
||||
{
|
||||
char *name = augroup_name((int)id);
|
||||
augroup_del(name, false);
|
||||
}
|
||||
|
||||
/// NOTE: behavior differs from augroup-delete.
|
||||
/// When deleting an augroup, autocmds contained by this augroup will also be deleted and cleared.
|
||||
/// This augroup will no longer exist
|
||||
void nvim_del_augroup_by_name(String name)
|
||||
FUNC_API_SINCE(9)
|
||||
{
|
||||
augroup_del(name.data, false);
|
||||
}
|
||||
|
||||
/// -- @param {string} group - autocmd group name
|
||||
/// -- @param {number} buffer - buffer number
|
||||
/// -- @param {string | table} event - event or events to match against
|
||||
/// -- @param {string | table} pattern - optional, defaults to "*".
|
||||
/// vim.api.nvim_do_autcmd({ group, buffer, pattern, event, modeline })
|
||||
void nvim_do_autocmd(Dict(do_autocmd) *opts, Error *err)
|
||||
FUNC_API_SINCE(9)
|
||||
{
|
||||
int au_group = AUGROUP_ALL;
|
||||
bool modeline = true;
|
||||
|
||||
buf_T *buf = curbuf;
|
||||
bool set_buf = false;
|
||||
|
||||
char_u *pattern = NULL;
|
||||
bool set_pattern = false;
|
||||
|
||||
Array event_array = ARRAY_DICT_INIT;
|
||||
|
||||
if (opts->group.type != kObjectTypeNil) {
|
||||
if (opts->group.type != kObjectTypeString) {
|
||||
api_set_error(err, kErrorTypeValidation, "'group' must be a string");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
au_group = augroup_find(opts->group.data.string.data);
|
||||
|
||||
if (au_group == AUGROUP_ERROR) {
|
||||
api_set_error(err,
|
||||
kErrorTypeException,
|
||||
"invalid augroup: %s", opts->group.data.string.data);
|
||||
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
if (opts->buffer.type != kObjectTypeNil) {
|
||||
Object buf_obj = opts->buffer;
|
||||
if (buf_obj.type != kObjectTypeInteger && buf_obj.type != kObjectTypeBuffer) {
|
||||
api_set_error(err, kErrorTypeException, "invalid buffer: %d", buf_obj.type);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
buf = find_buffer_by_handle((Buffer)buf_obj.data.integer, err);
|
||||
set_buf = true;
|
||||
|
||||
if (ERROR_SET(err)) {
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
if (opts->pattern.type != kObjectTypeNil) {
|
||||
if (opts->pattern.type != kObjectTypeString) {
|
||||
api_set_error(err, kErrorTypeValidation, "'pattern' must be a string");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
pattern = vim_strsave((char_u *)opts->pattern.data.string.data);
|
||||
set_pattern = true;
|
||||
}
|
||||
|
||||
if (opts->event.type != kObjectTypeNil) {
|
||||
UNPACK_STRING_OR_ARRAY(event_array, "event", (&opts->event), cleanup)
|
||||
}
|
||||
|
||||
if (opts->modeline.type != kObjectTypeNil) {
|
||||
modeline = api_object_to_bool(opts->modeline, "modeline", true, err);
|
||||
}
|
||||
|
||||
if (set_pattern && set_buf) {
|
||||
api_set_error(err, kErrorTypeValidation, "must not set 'buffer' and 'pattern'");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
bool did_aucmd = false;
|
||||
FOREACH_ITEM(event_array, event_str, {
|
||||
GET_ONE_EVENT(event_nr, event_str, cleanup)
|
||||
|
||||
did_aucmd |= apply_autocmds_group(event_nr, pattern, NULL, true, au_group, buf, NULL);
|
||||
})
|
||||
|
||||
if (did_aucmd && modeline) {
|
||||
do_modelines(0);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
api_free_array(event_array);
|
||||
XFREE_CLEAR(pattern);
|
||||
}
|
||||
|
||||
|
||||
#undef UNPACK_STRING_OR_ARRAY
|
||||
#undef CHECK_STRING_ARRAY
|
||||
#undef GET_ONE_EVENT
|
11
src/nvim/api/autocmd.h
Normal file
11
src/nvim/api/autocmd.h
Normal file
@ -0,0 +1,11 @@
|
||||
#ifndef NVIM_API_AUTOCMD_H
|
||||
#define NVIM_API_AUTOCMD_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "nvim/api/private/defs.h"
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "api/autocmd.h.generated.h"
|
||||
#endif
|
||||
#endif // NVIM_API_AUTOCMD_H
|
@ -110,5 +110,34 @@ return {
|
||||
"reverse";
|
||||
"nocombine";
|
||||
};
|
||||
-- Autocmds
|
||||
create_autocmd = {
|
||||
"buffer";
|
||||
"callback";
|
||||
"command";
|
||||
"desc";
|
||||
"event";
|
||||
"group";
|
||||
"once";
|
||||
"nested";
|
||||
"pattern";
|
||||
};
|
||||
do_autocmd = {
|
||||
"buffer";
|
||||
"event";
|
||||
"group";
|
||||
"modeline";
|
||||
"pattern";
|
||||
};
|
||||
get_autocmds = {
|
||||
"event";
|
||||
"group";
|
||||
"id";
|
||||
"pattern";
|
||||
};
|
||||
create_augroup = {
|
||||
"clear";
|
||||
"name";
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -6,22 +6,30 @@
|
||||
#include <msgpack.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "nvim/api/buffer.h"
|
||||
#include "nvim/api/deprecated.h"
|
||||
#include "nvim/api/extmark.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/api/private/dispatch.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/log.h"
|
||||
#include "nvim/map.h"
|
||||
#include "nvim/msgpack_rpc/helpers.h"
|
||||
#include "nvim/vim.h"
|
||||
|
||||
// ===========================================================================
|
||||
// NEW API FILES MUST GO HERE.
|
||||
//
|
||||
// When creating a new API file, you must include it here,
|
||||
// so that the dispatcher can find the C functions that you are creating!
|
||||
// ===========================================================================
|
||||
#include "nvim/api/autocmd.h"
|
||||
#include "nvim/api/buffer.h"
|
||||
#include "nvim/api/extmark.h"
|
||||
#include "nvim/api/tabpage.h"
|
||||
#include "nvim/api/ui.h"
|
||||
#include "nvim/api/vim.h"
|
||||
#include "nvim/api/vimscript.h"
|
||||
#include "nvim/api/win_config.h"
|
||||
#include "nvim/api/window.h"
|
||||
#include "nvim/log.h"
|
||||
#include "nvim/map.h"
|
||||
#include "nvim/msgpack_rpc/helpers.h"
|
||||
#include "nvim/vim.h"
|
||||
|
||||
static Map(String, MsgpackRpcRequestHandler) methods = MAP_INIT;
|
||||
|
||||
|
@ -396,19 +396,14 @@ void set_option_to(uint64_t channel_id, void *to, int type, String name, Object
|
||||
stringval = value.data.string.data;
|
||||
}
|
||||
|
||||
const sctx_T save_current_sctx = current_sctx;
|
||||
current_sctx.sc_sid =
|
||||
channel_id == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT;
|
||||
current_sctx.sc_lnum = 0;
|
||||
current_channel_id = channel_id;
|
||||
WITH_SCRIPT_CONTEXT(channel_id, {
|
||||
const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL))
|
||||
? 0 : (type == SREQ_GLOBAL)
|
||||
? OPT_GLOBAL : OPT_LOCAL;
|
||||
|
||||
const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL))
|
||||
? 0 : (type == SREQ_GLOBAL)
|
||||
? OPT_GLOBAL : OPT_LOCAL;
|
||||
set_option_value_for(name.data, numval, stringval,
|
||||
opt_flags, type, to, err);
|
||||
|
||||
current_sctx = save_current_sctx;
|
||||
set_option_value_for(name.data, numval, stringval,
|
||||
opt_flags, type, to, err);
|
||||
});
|
||||
}
|
||||
|
||||
buf_T *find_buffer_by_handle(Buffer buffer, Error *err)
|
||||
@ -1614,3 +1609,16 @@ err:
|
||||
NLUA_CLEAR_REF(luaref);
|
||||
NLUA_CLEAR_REF(compl_luaref);
|
||||
}
|
||||
|
||||
int find_sid(uint64_t channel_id)
|
||||
{
|
||||
switch (channel_id) {
|
||||
case VIML_INTERNAL_CALL:
|
||||
// TODO(autocmd): Figure out what this should be
|
||||
// return SID_API_CLIENT;
|
||||
case LUA_INTERNAL_CALL:
|
||||
return SID_LUA;
|
||||
default:
|
||||
return SID_API_CLIENT;
|
||||
}
|
||||
}
|
||||
|
@ -138,10 +138,27 @@ typedef struct {
|
||||
msg_list = saved_msg_list; /* Restore the exception context. */ \
|
||||
} while (0)
|
||||
|
||||
// Useful macro for executing some `code` for each item in an array.
|
||||
#define FOREACH_ITEM(a, __foreach_item, code) \
|
||||
for (size_t __foreach_i = 0; __foreach_i < (a).size; __foreach_i++) { \
|
||||
Object __foreach_item = (a).items[__foreach_i]; \
|
||||
code; \
|
||||
}
|
||||
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "api/private/helpers.h.generated.h"
|
||||
# include "keysets.h.generated.h"
|
||||
#endif
|
||||
|
||||
#define WITH_SCRIPT_CONTEXT(channel_id, code) \
|
||||
const sctx_T save_current_sctx = current_sctx; \
|
||||
current_sctx.sc_sid = \
|
||||
(channel_id) == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; \
|
||||
current_sctx.sc_lnum = 0; \
|
||||
current_channel_id = channel_id; \
|
||||
code; \
|
||||
current_sctx = save_current_sctx;
|
||||
|
||||
|
||||
#endif // NVIM_API_PRIVATE_HELPERS_H
|
||||
|
123
src/nvim/aucmd.c
123
src/nvim/aucmd.c
@ -1,123 +0,0 @@
|
||||
// This is an open source non-commercial project. Dear PVS-Studio, please check
|
||||
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||
|
||||
#include "nvim/aucmd.h"
|
||||
#include "nvim/buffer.h"
|
||||
#include "nvim/eval.h"
|
||||
#include "nvim/ex_docmd.h"
|
||||
#include "nvim/ex_getln.h"
|
||||
#include "nvim/fileio.h"
|
||||
#include "nvim/main.h"
|
||||
#include "nvim/os/os.h"
|
||||
#include "nvim/ui.h"
|
||||
#include "nvim/vim.h"
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "aucmd.c.generated.h"
|
||||
#endif
|
||||
|
||||
void do_autocmd_uienter(uint64_t chanid, bool attached)
|
||||
{
|
||||
static bool recursive = false;
|
||||
|
||||
if (recursive) {
|
||||
return; // disallow recursion
|
||||
}
|
||||
recursive = true;
|
||||
|
||||
save_v_event_T save_v_event;
|
||||
dict_T *dict = get_v_event(&save_v_event);
|
||||
assert(chanid < VARNUMBER_MAX);
|
||||
tv_dict_add_nr(dict, S_LEN("chan"), (varnumber_T)chanid);
|
||||
tv_dict_set_keys_readonly(dict);
|
||||
apply_autocmds(attached ? EVENT_UIENTER : EVENT_UILEAVE,
|
||||
NULL, NULL, false, curbuf);
|
||||
restore_v_event(dict, &save_v_event);
|
||||
|
||||
recursive = false;
|
||||
}
|
||||
|
||||
void init_default_autocmds(void)
|
||||
{
|
||||
// open terminals when opening files that start with term://
|
||||
#define PROTO "term://"
|
||||
do_cmdline_cmd("augroup nvim_terminal");
|
||||
do_cmdline_cmd("autocmd BufReadCmd " PROTO "* ++nested "
|
||||
"if !exists('b:term_title')|call termopen("
|
||||
// Capture the command string
|
||||
"matchstr(expand(\"<amatch>\"), "
|
||||
"'\\c\\m" PROTO "\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), "
|
||||
// capture the working directory
|
||||
"{'cwd': expand(get(matchlist(expand(\"<amatch>\"), "
|
||||
"'\\c\\m" PROTO "\\(.\\{-}\\)//'), 1, ''))})"
|
||||
"|endif");
|
||||
do_cmdline_cmd("augroup END");
|
||||
#undef PROTO
|
||||
|
||||
// limit syntax synchronization in the command window
|
||||
do_cmdline_cmd("augroup nvim_cmdwin");
|
||||
do_cmdline_cmd("autocmd! CmdwinEnter [:>] syntax sync minlines=1 maxlines=1");
|
||||
do_cmdline_cmd("augroup END");
|
||||
}
|
||||
|
||||
static void focusgained_event(void **argv)
|
||||
{
|
||||
bool *gainedp = argv[0];
|
||||
do_autocmd_focusgained(*gainedp);
|
||||
xfree(gainedp);
|
||||
}
|
||||
void aucmd_schedule_focusgained(bool gained)
|
||||
{
|
||||
bool *gainedp = xmalloc(sizeof(*gainedp));
|
||||
*gainedp = gained;
|
||||
loop_schedule_deferred(&main_loop,
|
||||
event_create(focusgained_event, 1, gainedp));
|
||||
}
|
||||
|
||||
static void do_autocmd_focusgained(bool gained)
|
||||
{
|
||||
static bool recursive = false;
|
||||
static Timestamp last_time = (time_t)0;
|
||||
bool need_redraw = false;
|
||||
|
||||
if (recursive) {
|
||||
return; // disallow recursion
|
||||
}
|
||||
recursive = true;
|
||||
need_redraw |= apply_autocmds((gained ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST),
|
||||
NULL, NULL, false, curbuf);
|
||||
|
||||
// When activated: Check if any file was modified outside of Vim.
|
||||
// Only do this when not done within the last two seconds as:
|
||||
// 1. Some filesystems have modification time granularity in seconds. Fat32
|
||||
// has a granularity of 2 seconds.
|
||||
// 2. We could get multiple notifications in a row.
|
||||
if (gained && last_time + (Timestamp)2000 < os_now()) {
|
||||
need_redraw = check_timestamps(true);
|
||||
last_time = os_now();
|
||||
}
|
||||
|
||||
if (need_redraw) {
|
||||
// Something was executed, make sure the cursor is put back where it
|
||||
// belongs.
|
||||
need_wait_return = false;
|
||||
|
||||
if (State & CMDLINE) {
|
||||
redrawcmdline();
|
||||
} else if ((State & NORMAL) || (State & INSERT)) {
|
||||
if (must_redraw != 0) {
|
||||
update_screen(0);
|
||||
}
|
||||
|
||||
setcursor();
|
||||
}
|
||||
|
||||
ui_flush();
|
||||
}
|
||||
|
||||
if (need_maketitle) {
|
||||
maketitle();
|
||||
}
|
||||
|
||||
recursive = false;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
#ifndef NVIM_AUCMD_H
|
||||
#define NVIM_AUCMD_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "aucmd.h.generated.h"
|
||||
#endif
|
||||
|
||||
#endif // NVIM_AUCMD_H
|
||||
|
1369
src/nvim/autocmd.c
1369
src/nvim/autocmd.c
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,11 @@
|
||||
#include "nvim/buffer_defs.h"
|
||||
#include "nvim/ex_cmds_defs.h"
|
||||
|
||||
// event_T definition
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "auevents_enum.generated.h"
|
||||
#endif
|
||||
|
||||
// Struct to save values in before executing autocommands for a buffer that is
|
||||
// not the current buffer.
|
||||
typedef struct {
|
||||
@ -18,22 +23,23 @@ typedef struct {
|
||||
} aco_save_T;
|
||||
|
||||
typedef struct AutoCmd {
|
||||
char_u *cmd; // Command to be executed (NULL when
|
||||
// command has been removed)
|
||||
AucmdExecutable exec;
|
||||
bool once; // "One shot": removed after execution
|
||||
bool nested; // If autocommands nest here
|
||||
bool last; // last command in list
|
||||
int64_t id; // TODO(tjdevries): Explain
|
||||
sctx_T script_ctx; // script context where defined
|
||||
struct AutoCmd *next; // Next AutoCmd in list
|
||||
char *desc; // Description for the autocmd.
|
||||
struct AutoCmd *next; // Next AutoCmd in list
|
||||
} AutoCmd;
|
||||
|
||||
typedef struct AutoPat {
|
||||
struct AutoPat *next; // next AutoPat in AutoPat list; MUST
|
||||
// be the first entry
|
||||
char_u *pat; // pattern as typed (NULL when pattern
|
||||
// has been removed)
|
||||
regprog_T *reg_prog; // compiled regprog for pattern
|
||||
AutoCmd *cmds; // list of commands to do
|
||||
struct AutoPat *next; // next AutoPat in AutoPat list; MUST
|
||||
// be the first entry
|
||||
char_u *pat; // pattern as typed (NULL when pattern
|
||||
// has been removed)
|
||||
regprog_T *reg_prog; // compiled regprog for pattern
|
||||
AutoCmd *cmds; // list of commands to do
|
||||
int group; // group ID
|
||||
int patlen; // strlen() of pat
|
||||
int buflocal_nr; // !=0 for buffer-local AutoPat
|
||||
@ -41,13 +47,7 @@ typedef struct AutoPat {
|
||||
char last; // last pattern for apply_autocmds()
|
||||
} AutoPat;
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "auevents_enum.generated.h"
|
||||
#endif
|
||||
|
||||
///
|
||||
/// Struct used to keep status while executing autocommands for an event.
|
||||
///
|
||||
typedef struct AutoPatCmd {
|
||||
AutoPat *curpat; // next AutoPat to examine
|
||||
AutoCmd *nextcmd; // next AutoCmd to execute
|
||||
@ -75,8 +75,16 @@ EXTERN bool au_did_filetype INIT(= false);
|
||||
# include "autocmd.h.generated.h"
|
||||
#endif
|
||||
|
||||
#define AUGROUP_DEFAULT -1 // default autocmd group
|
||||
#define AUGROUP_ERROR -2 // erroneous autocmd group
|
||||
#define AUGROUP_ALL -3 // all autocmd groups
|
||||
#define AUGROUP_DEFAULT (-1) // default autocmd group
|
||||
#define AUGROUP_ERROR (-2) // erroneous autocmd group
|
||||
#define AUGROUP_ALL (-3) // all autocmd groups
|
||||
#define AUGROUP_DELETED (-4) // all autocmd groups
|
||||
// #define AUGROUP_NS -5 // TODO(tjdevries): Support namespaced based augroups
|
||||
|
||||
#define BUFLOCAL_PAT_LEN 25
|
||||
|
||||
/// Iterates over all the events for auto commands
|
||||
#define FOR_ALL_AUEVENTS(event) \
|
||||
for (event_T event = (event_T)0; (int)event < (int)NUM_EVENTS; event = (event_T)((int)event + 1)) // NOLINT
|
||||
|
||||
#endif // NVIM_AUTOCMD_H
|
||||
|
@ -85,9 +85,9 @@ typedef struct {
|
||||
// used for recording hunks from xdiff
|
||||
typedef struct {
|
||||
linenr_T lnum_orig;
|
||||
long count_orig;
|
||||
long count_orig;
|
||||
linenr_T lnum_new;
|
||||
long count_new;
|
||||
long count_new;
|
||||
} diffhunk_T;
|
||||
|
||||
// two diff inputs and one result
|
||||
@ -1285,7 +1285,7 @@ void ex_diffpatch(exarg_T *eap)
|
||||
ex_file(eap);
|
||||
|
||||
// Do filetype detection with the new name.
|
||||
if (au_has_group((char_u *)"filetypedetect")) {
|
||||
if (augroup_exists("filetypedetect")) {
|
||||
do_cmdline_cmd(":doau filetypedetect BufRead");
|
||||
}
|
||||
}
|
||||
@ -3159,8 +3159,7 @@ static int parse_diff_unified(char_u *line, diffhunk_T *hunk)
|
||||
/// Callback function for the xdl_diff() function.
|
||||
/// Stores the diff output in a grow array.
|
||||
///
|
||||
static int xdiff_out(long start_a, long count_a, long start_b, long count_b,
|
||||
void *priv)
|
||||
static int xdiff_out(long start_a, long count_a, long start_b, long count_b, void *priv)
|
||||
{
|
||||
diffout_T *dout = (diffout_T *)priv;
|
||||
diffhunk_T *p = xmalloc(sizeof(*p));
|
||||
|
@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "auto/config.h"
|
||||
|
||||
@ -3258,7 +3259,7 @@ char_u *get_user_var_name(expand_T *xp, int idx)
|
||||
// b: variables
|
||||
// In cmdwin, the alternative buffer should be used.
|
||||
hashtab_T *ht
|
||||
= is_in_cmdwin() ? &prevwin->w_buffer->b_vars->dv_hashtab : &curbuf->b_vars->dv_hashtab;
|
||||
= is_in_cmdwin() ? &prevwin->w_buffer->b_vars->dv_hashtab : &curbuf->b_vars->dv_hashtab;
|
||||
if (bdone < ht->ht_used) {
|
||||
if (bdone++ == 0) {
|
||||
hi = ht->ht_array;
|
||||
@ -7746,6 +7747,7 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg)
|
||||
callback->type = kCallbackFuncref;
|
||||
}
|
||||
} else if (nlua_is_table_from_lua(arg)) {
|
||||
// TODO(tjdvries): UnifiedCallback
|
||||
char_u *name = nlua_register_table_as_callable(arg);
|
||||
|
||||
if (name != NULL) {
|
||||
@ -7775,6 +7777,7 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co
|
||||
{
|
||||
partial_T *partial;
|
||||
char_u *name;
|
||||
Array args = ARRAY_DICT_INIT;
|
||||
switch (callback->type) {
|
||||
case kCallbackFuncref:
|
||||
name = callback->data.funcref;
|
||||
@ -7786,6 +7789,13 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co
|
||||
name = partial_name(partial);
|
||||
break;
|
||||
|
||||
case kCallbackLua:
|
||||
ILOG(" We tryin to call dat dang lua ref ");
|
||||
nlua_call_ref(callback->data.luaref, "aucmd", args, false, NULL);
|
||||
|
||||
return false;
|
||||
break;
|
||||
|
||||
case kCallbackNone:
|
||||
return false;
|
||||
break;
|
||||
|
@ -894,6 +894,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
partial = argvars[0].vval.v_partial;
|
||||
func = partial_name(partial);
|
||||
} else if (nlua_is_table_from_lua(&argvars[0])) {
|
||||
// TODO(tjdevries): UnifiedCallback
|
||||
func = nlua_register_table_as_callable(&argvars[0]);
|
||||
owned = true;
|
||||
} else {
|
||||
|
@ -28,11 +28,11 @@
|
||||
#include "nvim/mbyte.h"
|
||||
#include "nvim/memory.h"
|
||||
#include "nvim/message.h"
|
||||
#include "nvim/os/fileio.h"
|
||||
#include "nvim/os/input.h"
|
||||
#include "nvim/pos.h"
|
||||
#include "nvim/types.h"
|
||||
#include "nvim/vim.h"
|
||||
#include "nvim/os/fileio.h"
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "eval/typval.c.generated.h"
|
||||
@ -1123,6 +1123,8 @@ bool tv_callback_equal(const Callback *cb1, const Callback *cb2)
|
||||
// FIXME: this is inconsistent with tv_equal but is needed for precision
|
||||
// maybe change dictwatcheradd to return a watcher id instead?
|
||||
return cb1->data.partial == cb2->data.partial;
|
||||
case kCallbackLua:
|
||||
return cb1->data.luaref == cb2->data.luaref;
|
||||
case kCallbackNone:
|
||||
return true;
|
||||
}
|
||||
@ -1142,6 +1144,9 @@ void callback_free(Callback *callback)
|
||||
case kCallbackPartial:
|
||||
partial_unref(callback->data.partial);
|
||||
break;
|
||||
case kCallbackLua:
|
||||
NLUA_CLEAR_REF(callback->data.luaref);
|
||||
break;
|
||||
case kCallbackNone:
|
||||
break;
|
||||
}
|
||||
@ -1149,6 +1154,12 @@ void callback_free(Callback *callback)
|
||||
callback->data.funcref = NULL;
|
||||
}
|
||||
|
||||
/// Check if callback is freed
|
||||
bool callback_is_freed(Callback callback)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Copy a callback into a typval_T.
|
||||
void callback_put(Callback *cb, typval_T *tv)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
@ -1164,6 +1175,9 @@ void callback_put(Callback *cb, typval_T *tv)
|
||||
tv->vval.v_string = vim_strsave(cb->data.funcref);
|
||||
func_ref(cb->data.funcref);
|
||||
break;
|
||||
case kCallbackLua:
|
||||
// TODO(tjdevries): I'm not even sure if this is strictly necessary?
|
||||
abort();
|
||||
default:
|
||||
tv->v_type = VAR_SPECIAL;
|
||||
tv->vval.v_special = kSpecialVarNull;
|
||||
@ -1185,6 +1199,9 @@ void callback_copy(Callback *dest, Callback *src)
|
||||
dest->data.funcref = vim_strsave(src->data.funcref);
|
||||
func_ref(src->data.funcref);
|
||||
break;
|
||||
case kCallbackLua:
|
||||
dest->data.luaref = api_new_luaref(src->data.luaref);
|
||||
break;
|
||||
default:
|
||||
dest->data.funcref = NULL;
|
||||
break;
|
||||
|
@ -72,15 +72,18 @@ typedef enum {
|
||||
kCallbackNone = 0,
|
||||
kCallbackFuncref,
|
||||
kCallbackPartial,
|
||||
kCallbackLua,
|
||||
} CallbackType;
|
||||
|
||||
typedef struct {
|
||||
union {
|
||||
char_u *funcref;
|
||||
partial_T *partial;
|
||||
LuaRef luaref;
|
||||
} data;
|
||||
CallbackType type;
|
||||
} Callback;
|
||||
|
||||
#define CALLBACK_INIT { .type = kCallbackNone }
|
||||
#define CALLBACK_NONE ((Callback)CALLBACK_INIT)
|
||||
|
||||
|
@ -1943,7 +1943,7 @@ int do_write(exarg_T *eap)
|
||||
|
||||
// If 'filetype' was empty try detecting it now.
|
||||
if (*curbuf->b_p_ft == NUL) {
|
||||
if (au_has_group((char_u *)"filetypedetect")) {
|
||||
if (augroup_exists("filetypedetect")) {
|
||||
(void)do_doautocmd((char_u *)"filetypedetect BufRead", true, NULL);
|
||||
}
|
||||
do_modelines(0);
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "nvim/eval/typval.h"
|
||||
#include "nvim/normal.h"
|
||||
#include "nvim/pos.h" // for linenr_T
|
||||
#include "nvim/regexp_defs.h"
|
||||
@ -91,6 +92,35 @@ typedef struct exarg exarg_T;
|
||||
|
||||
typedef void (*ex_func_T)(exarg_T *eap);
|
||||
|
||||
// NOTE: These possible could be removed and changed so that
|
||||
// Callback could take a "command" style string, and simply
|
||||
// execute that (instead of it being a function).
|
||||
//
|
||||
// But it's still a bit weird to do that.
|
||||
//
|
||||
// Another option would be that we just make a callback reference to
|
||||
// "execute($INPUT)" or something like that, so whatever the user
|
||||
// sends in via autocmds is just executed via this.
|
||||
//
|
||||
// However, that would probably have some performance cost (probably
|
||||
// very marginal, but still some cost either way).
|
||||
typedef enum {
|
||||
CALLABLE_NONE,
|
||||
CALLABLE_EX,
|
||||
CALLABLE_CB,
|
||||
} AucmdExecutableType;
|
||||
|
||||
typedef struct aucmd_executable_t AucmdExecutable;
|
||||
struct aucmd_executable_t {
|
||||
AucmdExecutableType type;
|
||||
union {
|
||||
char_u *cmd;
|
||||
Callback cb;
|
||||
} callable;
|
||||
};
|
||||
|
||||
#define AUCMD_EXECUTABLE_INIT { .type = CALLABLE_NONE }
|
||||
|
||||
typedef char_u *(*LineGetter)(int, void *, int, bool);
|
||||
|
||||
/// Structure for command definition.
|
||||
|
@ -5049,8 +5049,8 @@ static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char_u **
|
||||
{ EXPAND_SYNTAX, get_syntax_name, true, true },
|
||||
{ EXPAND_SYNTIME, get_syntime_arg, true, true },
|
||||
{ EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, true },
|
||||
{ EXPAND_EVENTS, get_event_name, true, true },
|
||||
{ EXPAND_AUGROUP, get_augroup_name, true, true },
|
||||
{ EXPAND_EVENTS, expand_get_event_name, true, true },
|
||||
{ EXPAND_AUGROUP, expand_get_augroup_name, true, true },
|
||||
{ EXPAND_CSCOPE, get_cscope_name, true, true },
|
||||
{ EXPAND_SIGN, get_sign_name, true, true },
|
||||
{ EXPAND_PROFILE, get_profile_name, true, true },
|
||||
|
@ -3796,7 +3796,7 @@ static int set_rw_fname(char_u *fname, char_u *sfname)
|
||||
|
||||
// Do filetype detection now if 'filetype' is empty.
|
||||
if (*curbuf->b_p_ft == NUL) {
|
||||
if (au_has_group((char_u *)"filetypedetect")) {
|
||||
if (augroup_exists("filetypedetect")) {
|
||||
(void)do_doautocmd((char_u *)"filetypedetect BufRead", false, NULL);
|
||||
}
|
||||
do_modelines(0);
|
||||
|
@ -326,16 +326,16 @@ EXTERN int want_garbage_collect INIT(= false);
|
||||
EXTERN int garbage_collect_at_exit INIT(= false);
|
||||
|
||||
// Special values for current_SID.
|
||||
#define SID_MODELINE -1 // when using a modeline
|
||||
#define SID_CMDARG -2 // for "--cmd" argument
|
||||
#define SID_CARG -3 // for "-c" argument
|
||||
#define SID_ENV -4 // for sourcing environment variable
|
||||
#define SID_ERROR -5 // option was reset because of an error
|
||||
#define SID_NONE -6 // don't set scriptID
|
||||
#define SID_WINLAYOUT -7 // changing window size
|
||||
#define SID_LUA -8 // for Lua scripts/chunks
|
||||
#define SID_API_CLIENT -9 // for API clients
|
||||
#define SID_STR -10 // for sourcing a string with no script item
|
||||
#define SID_MODELINE (-1) // when using a modeline
|
||||
#define SID_CMDARG (-2) // for "--cmd" argument
|
||||
#define SID_CARG (-3) // for "-c" argument
|
||||
#define SID_ENV (-4) // for sourcing environment variable
|
||||
#define SID_ERROR (-5) // option was reset because of an error
|
||||
#define SID_NONE (-6) // don't set scriptID
|
||||
#define SID_WINLAYOUT (-7) // changing window size
|
||||
#define SID_LUA (-8) // for Lua scripts/chunks
|
||||
#define SID_API_CLIENT (-9) // for API clients
|
||||
#define SID_STR (-10) // for sourcing a string with no script item
|
||||
|
||||
// Script CTX being sourced or was sourced to define the current function.
|
||||
EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 });
|
||||
|
@ -245,6 +245,7 @@ enum key_extra {
|
||||
KE_EVENT = 102, // event
|
||||
KE_LUA = 103, // lua special key
|
||||
KE_COMMAND = 104, // <Cmd> special key
|
||||
KE_AUCMD_SPECIAL = 105,
|
||||
};
|
||||
|
||||
/*
|
||||
@ -443,6 +444,8 @@ enum key_extra {
|
||||
#define K_COMMAND TERMCAP2KEY(KS_EXTRA, KE_COMMAND)
|
||||
#define K_LUA TERMCAP2KEY(KS_EXTRA, KE_LUA)
|
||||
|
||||
#define K_AUCMD_SPECIAL TERMCAP2KEY(KS_EXTRA, KE_AUCMD_SPECIAL)
|
||||
|
||||
// Bits for modifier mask
|
||||
// 0x01 cannot be used, because the modifier must be 0x02 or higher
|
||||
#define MOD_MASK_SHIFT 0x02
|
||||
|
@ -1350,6 +1350,16 @@ Object nlua_exec(const String str, const Array args, Error *err)
|
||||
return nlua_pop_Object(lstate, false, err);
|
||||
}
|
||||
|
||||
bool nlua_ref_is_function(LuaRef ref)
|
||||
{
|
||||
lua_State *const lstate = global_lstate;
|
||||
nlua_pushref(lstate, ref);
|
||||
bool is_function = (lua_type(lstate, -1) == LUA_TFUNCTION);
|
||||
lua_pop(lstate, 1);
|
||||
|
||||
return is_function;
|
||||
}
|
||||
|
||||
/// call a LuaRef as a function (or table with __call metamethod)
|
||||
///
|
||||
/// @param ref the reference to call (not consumed)
|
||||
|
@ -24,14 +24,6 @@ typedef struct {
|
||||
#endif
|
||||
} nlua_ref_state_t;
|
||||
|
||||
#define set_api_error(s, err) \
|
||||
do { \
|
||||
Error *err_ = (err); \
|
||||
err_->type = kErrorTypeException; \
|
||||
err_->set = true; \
|
||||
memcpy(&err_->msg[0], s, sizeof(s)); \
|
||||
} while (0)
|
||||
|
||||
#define NLUA_CLEAR_REF(x) \
|
||||
do { \
|
||||
/* Take the address to avoid double evaluation. #1375 */ \
|
||||
|
@ -9,7 +9,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "nvim/ascii.h"
|
||||
#include "nvim/aucmd.h"
|
||||
#include "nvim/autocmd.h"
|
||||
#include "nvim/buffer.h"
|
||||
#include "nvim/channel.h"
|
||||
#include "nvim/charset.h"
|
||||
|
@ -177,6 +177,7 @@ MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER)
|
||||
MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER)
|
||||
MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER)
|
||||
MAP_IMPL(String, handle_T, 0)
|
||||
MAP_IMPL(String, int, DEFAULT_INITIALIZER)
|
||||
|
||||
MAP_IMPL(ColorKey, ColorItem, COLOR_ITEM_INITIALIZER)
|
||||
|
||||
|
@ -46,6 +46,7 @@ MAP_DECLS(handle_T, ptr_t)
|
||||
MAP_DECLS(String, MsgpackRpcRequestHandler)
|
||||
MAP_DECLS(HlEntry, int)
|
||||
MAP_DECLS(String, handle_T)
|
||||
MAP_DECLS(String, int)
|
||||
|
||||
MAP_DECLS(ColorKey, ColorItem)
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/api/vim.h"
|
||||
#include "nvim/ascii.h"
|
||||
#include "nvim/aucmd.h"
|
||||
#include "nvim/autocmd.h"
|
||||
#include "nvim/charset.h"
|
||||
#include "nvim/ex_docmd.h"
|
||||
#include "nvim/macros.h"
|
||||
@ -378,7 +378,7 @@ static bool handle_focus_event(TermInput *input)
|
||||
bool focus_gained = *rbuffer_get(input->read_stream.buffer, 2) == 'I';
|
||||
// Advance past the sequence
|
||||
rbuffer_consumed(input->read_stream.buffer, 3);
|
||||
aucmd_schedule_focusgained(focus_gained);
|
||||
autocmd_schedule_focusgained(focus_gained);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -8,7 +8,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "nvim/ascii.h"
|
||||
#include "nvim/aucmd.h"
|
||||
#include "nvim/autocmd.h"
|
||||
#include "nvim/charset.h"
|
||||
#include "nvim/cursor.h"
|
||||
#include "nvim/cursor_shape.h"
|
||||
|
798
test/functional/api/autocmd_spec.lua
Normal file
798
test/functional/api/autocmd_spec.lua
Normal file
@ -0,0 +1,798 @@
|
||||
local helpers = require('test.functional.helpers')(after_each)
|
||||
|
||||
local clear = helpers.clear
|
||||
local command = helpers.command
|
||||
local eq = helpers.eq
|
||||
local neq = helpers.neq
|
||||
local exec_lua = helpers.exec_lua
|
||||
local matches = helpers.matches
|
||||
local meths = helpers.meths
|
||||
local source = helpers.source
|
||||
|
||||
before_each(clear)
|
||||
|
||||
describe('autocmd api', function()
|
||||
describe('nvim_create_autocmd', function()
|
||||
it('does not allow "command" and "callback" in the same autocmd', function()
|
||||
local ok, _ = pcall(meths.create_autocmd, {
|
||||
event = "BufReadPost",
|
||||
pattern = "*.py,*.pyi",
|
||||
command = "echo 'Should Have Errored",
|
||||
callback = "not allowed",
|
||||
})
|
||||
|
||||
eq(false, ok)
|
||||
end)
|
||||
|
||||
it('doesnt leak when you use ++once', function()
|
||||
eq(1, exec_lua([[
|
||||
local count = 0
|
||||
|
||||
vim.api.nvim_create_autocmd {
|
||||
event = "FileType",
|
||||
pattern = "*",
|
||||
callback = function() count = count + 1 end,
|
||||
once = true
|
||||
}
|
||||
|
||||
vim.cmd "set filetype=txt"
|
||||
vim.cmd "set filetype=python"
|
||||
|
||||
return count
|
||||
]], {}))
|
||||
end)
|
||||
|
||||
it('allows passing buffer by key', function()
|
||||
meths.set_var('called', 0)
|
||||
|
||||
meths.create_autocmd {
|
||||
event = "Filetype",
|
||||
command = "let g:called = g:called + 1",
|
||||
buffer = 0,
|
||||
}
|
||||
|
||||
meths.command "set filetype=txt"
|
||||
eq(1, meths.get_var('called'))
|
||||
|
||||
-- switch to a new buffer
|
||||
meths.command "new"
|
||||
meths.command "set filetype=python"
|
||||
|
||||
eq(1, meths.get_var('called'))
|
||||
end)
|
||||
|
||||
it('does not allow passing buffer and patterns', function()
|
||||
local ok = pcall(meths.create_autocmd, {
|
||||
event = "Filetype",
|
||||
command = "let g:called = g:called + 1",
|
||||
buffer = 0,
|
||||
pattern = "*.py",
|
||||
})
|
||||
|
||||
eq(false, ok)
|
||||
end)
|
||||
|
||||
it('does not allow passing invalid buffers', function()
|
||||
local ok, msg = pcall(meths.create_autocmd, {
|
||||
event = "Filetype",
|
||||
command = "let g:called = g:called + 1",
|
||||
buffer = -1,
|
||||
})
|
||||
|
||||
eq(false, ok)
|
||||
matches('Invalid buffer id', msg)
|
||||
end)
|
||||
|
||||
it('errors on non-functions for cb', function()
|
||||
eq(false, pcall(exec_lua, [[
|
||||
vim.api.nvim_create_autocmd {
|
||||
event = "BufReadPost",
|
||||
pattern = "*.py,*.pyi",
|
||||
callback = 5,
|
||||
}
|
||||
]]))
|
||||
end)
|
||||
|
||||
it('allow passing pattern and <buffer> in same pattern', function()
|
||||
local ok = pcall(meths.create_autocmd, {
|
||||
event = "BufReadPost",
|
||||
pattern = "*.py,<buffer>",
|
||||
command = "echo 'Should Not Error'"
|
||||
})
|
||||
|
||||
eq(true, ok)
|
||||
end)
|
||||
|
||||
it('should handle multiple values as comma separated list', function()
|
||||
meths.create_autocmd {
|
||||
event = "BufReadPost",
|
||||
pattern = "*.py,*.pyi",
|
||||
command = "echo 'Should Not Have Errored'"
|
||||
}
|
||||
|
||||
-- We should have one autocmd for *.py and one for *.pyi
|
||||
eq(2, #meths.get_autocmds { event = "BufReadPost" })
|
||||
end)
|
||||
|
||||
it('should handle multiple values as array', function()
|
||||
meths.create_autocmd {
|
||||
event = "BufReadPost",
|
||||
pattern = { "*.py", "*.pyi", },
|
||||
command = "echo 'Should Not Have Errored'"
|
||||
}
|
||||
|
||||
-- We should have one autocmd for *.py and one for *.pyi
|
||||
eq(2, #meths.get_autocmds { event = "BufReadPost" })
|
||||
end)
|
||||
|
||||
describe('desc', function()
|
||||
it('can add description to one autocmd', function()
|
||||
meths.create_autocmd {
|
||||
event = "BufReadPost",
|
||||
pattern = "*.py",
|
||||
command = "echo 'Should Not Have Errored'",
|
||||
desc = "Can show description",
|
||||
}
|
||||
|
||||
eq("Can show description", meths.get_autocmds { event = "BufReadPost" }[1].desc)
|
||||
end)
|
||||
|
||||
it('can add description to multiple autocmd', function()
|
||||
meths.create_autocmd {
|
||||
event = "BufReadPost",
|
||||
pattern = {"*.py", "*.pyi"},
|
||||
command = "echo 'Should Not Have Errored'",
|
||||
desc = "Can show description",
|
||||
}
|
||||
|
||||
local aus = meths.get_autocmds { event = "BufReadPost" }
|
||||
eq(2, #aus)
|
||||
eq("Can show description", aus[1].desc)
|
||||
eq("Can show description", aus[2].desc)
|
||||
end)
|
||||
end)
|
||||
|
||||
pending('script and verbose settings', function()
|
||||
it('marks API client', function()
|
||||
meths.create_autocmd {
|
||||
event = "BufReadPost",
|
||||
pattern = "*.py",
|
||||
command = "echo 'Should Not Have Errored'",
|
||||
desc = "Can show description",
|
||||
}
|
||||
|
||||
local aus = meths.get_autocmds { event = "BufReadPost" }
|
||||
eq(1, #aus, aus)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('nvim_get_autocmds', function()
|
||||
describe('events', function()
|
||||
it('should return one autocmd when there is only one for an event', function()
|
||||
command [[au! InsertEnter]]
|
||||
command [[au InsertEnter * :echo "1"]]
|
||||
|
||||
local aus = meths.get_autocmds { event = "InsertEnter" }
|
||||
eq(1, #aus)
|
||||
end)
|
||||
|
||||
it('should return two autocmds when there are two for an event', function()
|
||||
command [[au! InsertEnter]]
|
||||
command [[au InsertEnter * :echo "1"]]
|
||||
command [[au InsertEnter * :echo "2"]]
|
||||
|
||||
local aus = meths.get_autocmds { event = "InsertEnter" }
|
||||
eq(2, #aus)
|
||||
end)
|
||||
|
||||
it('should return the same thing if you use string or list', function()
|
||||
command [[au! InsertEnter]]
|
||||
command [[au InsertEnter * :echo "1"]]
|
||||
command [[au InsertEnter * :echo "2"]]
|
||||
|
||||
local string_aus = meths.get_autocmds { event = "InsertEnter" }
|
||||
local array_aus = meths.get_autocmds { event = { "InsertEnter" } }
|
||||
eq(string_aus, array_aus)
|
||||
end)
|
||||
|
||||
it('should return two autocmds when there are two for an event', function()
|
||||
command [[au! InsertEnter]]
|
||||
command [[au! InsertLeave]]
|
||||
command [[au InsertEnter * :echo "1"]]
|
||||
command [[au InsertEnter * :echo "2"]]
|
||||
|
||||
local aus = meths.get_autocmds { event = { "InsertEnter", "InsertLeave" } }
|
||||
eq(2, #aus)
|
||||
end)
|
||||
|
||||
it('should return different IDs for different autocmds', function()
|
||||
command [[au! InsertEnter]]
|
||||
command [[au! InsertLeave]]
|
||||
command [[au InsertEnter * :echo "1"]]
|
||||
source [[
|
||||
call nvim_create_autocmd(#{
|
||||
\ event: "InsertLeave",
|
||||
\ command: ":echo 2",
|
||||
\ })
|
||||
]]
|
||||
|
||||
local aus = meths.get_autocmds { event = { "InsertEnter", "InsertLeave" } }
|
||||
local first = aus[1]
|
||||
eq(first.id, nil)
|
||||
|
||||
-- TODO: Maybe don't have this number, just assert it's not nil
|
||||
local second = aus[2]
|
||||
neq(second.id, nil)
|
||||
|
||||
meths.del_autocmd(second.id)
|
||||
local new_aus = meths.get_autocmds { event = { "InsertEnter", "InsertLeave" } }
|
||||
eq(1, #new_aus)
|
||||
eq(first, new_aus[1])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('groups', function()
|
||||
before_each(function()
|
||||
command [[au! InsertEnter]]
|
||||
|
||||
command [[au InsertEnter * :echo "No Group"]]
|
||||
|
||||
command [[augroup GroupOne]]
|
||||
command [[ au InsertEnter * :echo "GroupOne:1"]]
|
||||
command [[augroup END]]
|
||||
|
||||
command [[augroup GroupTwo]]
|
||||
command [[ au InsertEnter * :echo "GroupTwo:2"]]
|
||||
command [[ au InsertEnter * :echo "GroupTwo:3"]]
|
||||
command [[augroup END]]
|
||||
end)
|
||||
|
||||
it('should return all groups if no group is specified', function()
|
||||
local aus = meths.get_autocmds { event = "InsertEnter" }
|
||||
if #aus ~= 4 then
|
||||
eq({}, aus)
|
||||
end
|
||||
|
||||
eq(4, #aus)
|
||||
end)
|
||||
|
||||
it('should return only the group specified', function()
|
||||
local aus = meths.get_autocmds {
|
||||
event = "InsertEnter",
|
||||
group = "GroupOne",
|
||||
}
|
||||
|
||||
eq(1, #aus)
|
||||
eq([[:echo "GroupOne:1"]], aus[1].command)
|
||||
end)
|
||||
|
||||
it('should return only the group specified, multiple values', function()
|
||||
local aus = meths.get_autocmds {
|
||||
event = "InsertEnter",
|
||||
group = "GroupTwo",
|
||||
}
|
||||
|
||||
eq(2, #aus)
|
||||
eq([[:echo "GroupTwo:2"]], aus[1].command)
|
||||
eq([[:echo "GroupTwo:3"]], aus[2].command)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('groups: 2', function()
|
||||
it('raises error for undefined augroup', function()
|
||||
local success, code = unpack(meths.exec_lua([[
|
||||
return {pcall(function()
|
||||
vim.api.nvim_create_autocmd {
|
||||
event = "FileType",
|
||||
pattern = "*",
|
||||
group = "NotDefined",
|
||||
command = "echo 'hello'",
|
||||
}
|
||||
end)}
|
||||
]], {}))
|
||||
|
||||
eq(false, success)
|
||||
matches('invalid augroup: NotDefined', code)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('patterns', function()
|
||||
before_each(function()
|
||||
command [[au! InsertEnter]]
|
||||
|
||||
command [[au InsertEnter * :echo "No Group"]]
|
||||
command [[au InsertEnter *.one :echo "GroupOne:1"]]
|
||||
command [[au InsertEnter *.two :echo "GroupTwo:2"]]
|
||||
command [[au InsertEnter *.two :echo "GroupTwo:3"]]
|
||||
command [[au InsertEnter <buffer> :echo "Buffer"]]
|
||||
end)
|
||||
|
||||
it('should should return for literal match', function()
|
||||
local aus = meths.get_autocmds {
|
||||
event = "InsertEnter",
|
||||
pattern = "*"
|
||||
}
|
||||
|
||||
eq(1, #aus)
|
||||
eq([[:echo "No Group"]], aus[1].command)
|
||||
end)
|
||||
|
||||
it('should return for multiple matches', function()
|
||||
-- vim.api.nvim_get_autocmds
|
||||
local aus = meths.get_autocmds {
|
||||
event = "InsertEnter",
|
||||
pattern = { "*.one", "*.two" },
|
||||
}
|
||||
|
||||
eq(3, #aus)
|
||||
eq([[:echo "GroupOne:1"]], aus[1].command)
|
||||
eq([[:echo "GroupTwo:2"]], aus[2].command)
|
||||
eq([[:echo "GroupTwo:3"]], aus[3].command)
|
||||
end)
|
||||
|
||||
it('should work for buffer autocmds', function()
|
||||
local normalized_aus = meths.get_autocmds {
|
||||
event = "InsertEnter",
|
||||
pattern = "<buffer=1>",
|
||||
}
|
||||
|
||||
local raw_aus = meths.get_autocmds {
|
||||
event = "InsertEnter",
|
||||
pattern = "<buffer>",
|
||||
}
|
||||
|
||||
local zero_aus = meths.get_autocmds {
|
||||
event = "InsertEnter",
|
||||
pattern = "<buffer=0>",
|
||||
}
|
||||
|
||||
eq(normalized_aus, raw_aus)
|
||||
eq(normalized_aus, zero_aus)
|
||||
eq([[:echo "Buffer"]], normalized_aus[1].command)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('nvim_do_autocmd', function()
|
||||
it("can trigger builtin autocmds", function()
|
||||
meths.set_var("autocmd_executed", false)
|
||||
|
||||
meths.create_autocmd {
|
||||
event = "BufReadPost",
|
||||
pattern = "*",
|
||||
command = "let g:autocmd_executed = v:true",
|
||||
}
|
||||
|
||||
eq(false, meths.get_var("autocmd_executed"))
|
||||
meths.do_autocmd { event = "BufReadPost" }
|
||||
eq(true, meths.get_var("autocmd_executed"))
|
||||
end)
|
||||
|
||||
it("can pass the buffer", function()
|
||||
meths.set_var("buffer_executed", -1)
|
||||
eq(-1, meths.get_var("buffer_executed"))
|
||||
|
||||
meths.create_autocmd {
|
||||
event = "BufLeave",
|
||||
pattern = "*",
|
||||
command = 'let g:buffer_executed = +expand("<abuf>")',
|
||||
}
|
||||
|
||||
-- Doesn't execute for other non-matching events
|
||||
meths.do_autocmd { event = "CursorHold", buffer = 1 }
|
||||
eq(-1, meths.get_var("buffer_executed"))
|
||||
|
||||
meths.do_autocmd { event = "BufLeave", buffer = 1 }
|
||||
eq(1, meths.get_var("buffer_executed"))
|
||||
end)
|
||||
|
||||
it("can pass the filename, pattern match", function()
|
||||
meths.set_var("filename_executed", 'none')
|
||||
eq('none', meths.get_var("filename_executed"))
|
||||
|
||||
meths.create_autocmd {
|
||||
event = "BufEnter",
|
||||
pattern = "*.py",
|
||||
command = 'let g:filename_executed = expand("<afile>")',
|
||||
}
|
||||
|
||||
-- Doesn't execute for other non-matching events
|
||||
meths.do_autocmd { event = "CursorHold", buffer = 1 }
|
||||
eq('none', meths.get_var("filename_executed"))
|
||||
|
||||
meths.command('edit __init__.py')
|
||||
eq('__init__.py', meths.get_var("filename_executed"))
|
||||
end)
|
||||
|
||||
it('cannot pass buf and fname', function()
|
||||
local ok = pcall(meths.do_autocmd, { pattern = "literally_cannot_error.rs", buffer = 1 })
|
||||
eq(false, ok)
|
||||
end)
|
||||
|
||||
it("can pass the filename, exact match", function()
|
||||
meths.set_var("filename_executed", 'none')
|
||||
eq('none', meths.get_var("filename_executed"))
|
||||
|
||||
meths.command('edit other_file.txt')
|
||||
meths.command('edit __init__.py')
|
||||
eq('none', meths.get_var("filename_executed"))
|
||||
|
||||
meths.create_autocmd {
|
||||
event = "CursorHoldI",
|
||||
pattern = "__init__.py",
|
||||
command = 'let g:filename_executed = expand("<afile>")',
|
||||
}
|
||||
|
||||
-- Doesn't execute for other non-matching events
|
||||
meths.do_autocmd { event = "CursorHoldI", buffer = 1 }
|
||||
eq('none', meths.get_var("filename_executed"))
|
||||
|
||||
meths.do_autocmd { event = "CursorHoldI", buffer = tonumber(meths.get_current_buf()) }
|
||||
eq('__init__.py', meths.get_var("filename_executed"))
|
||||
|
||||
-- Reset filename
|
||||
meths.set_var("filename_executed", 'none')
|
||||
|
||||
meths.do_autocmd { event = "CursorHoldI", pattern = '__init__.py' }
|
||||
eq('__init__.py', meths.get_var("filename_executed"))
|
||||
end)
|
||||
|
||||
it("works with user autocmds", function()
|
||||
meths.set_var("matched", 'none')
|
||||
|
||||
meths.create_autocmd {
|
||||
event = "User",
|
||||
pattern = "TestCommand",
|
||||
command = 'let g:matched = "matched"'
|
||||
}
|
||||
|
||||
meths.do_autocmd { event = "User", pattern = "OtherCommand" }
|
||||
eq('none', meths.get_var('matched'))
|
||||
meths.do_autocmd { event = "User", pattern = "TestCommand" }
|
||||
eq('matched', meths.get_var('matched'))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('nvim_create_augroup', function()
|
||||
before_each(function()
|
||||
clear()
|
||||
|
||||
meths.set_var('executed', 0)
|
||||
end)
|
||||
|
||||
local make_counting_autocmd = function(opts)
|
||||
opts = opts or {}
|
||||
|
||||
local resulting = {
|
||||
event = "FileType",
|
||||
pattern = "*",
|
||||
command = "let g:executed = g:executed + 1",
|
||||
}
|
||||
|
||||
resulting.group = opts.group
|
||||
resulting.once = opts.once
|
||||
|
||||
meths.create_autocmd(resulting)
|
||||
end
|
||||
|
||||
local set_ft = function(ft)
|
||||
ft = ft or "txt"
|
||||
source(string.format("set filetype=%s", ft))
|
||||
end
|
||||
|
||||
local get_executed_count = function()
|
||||
return meths.get_var('executed')
|
||||
end
|
||||
|
||||
it('can be added in a group', function()
|
||||
local augroup = "TestGroup"
|
||||
meths.create_augroup({ name = augroup, clear = true })
|
||||
make_counting_autocmd { group = augroup }
|
||||
|
||||
set_ft("txt")
|
||||
set_ft("python")
|
||||
|
||||
eq(get_executed_count(), 2)
|
||||
end)
|
||||
|
||||
it('works getting called multiple times', function()
|
||||
make_counting_autocmd()
|
||||
set_ft()
|
||||
set_ft()
|
||||
set_ft()
|
||||
|
||||
eq(get_executed_count(), 3)
|
||||
end)
|
||||
|
||||
it('handles ++once', function()
|
||||
make_counting_autocmd {once = true}
|
||||
set_ft('txt')
|
||||
set_ft('help')
|
||||
set_ft('txt')
|
||||
set_ft('help')
|
||||
|
||||
eq(get_executed_count(), 1)
|
||||
end)
|
||||
|
||||
it('errors on unexpected keys', function()
|
||||
local success, code = pcall(meths.create_autocmd, {
|
||||
event = "FileType",
|
||||
pattern = "*",
|
||||
not_a_valid_key = "NotDefined",
|
||||
})
|
||||
|
||||
eq(false, success)
|
||||
matches('not_a_valid_key', code)
|
||||
end)
|
||||
|
||||
it('can execute simple callback', function()
|
||||
exec_lua([[
|
||||
vim.g.executed = false
|
||||
|
||||
vim.api.nvim_create_autocmd {
|
||||
event = "FileType",
|
||||
pattern = "*",
|
||||
callback = function() vim.g.executed = true end,
|
||||
}
|
||||
]], {})
|
||||
|
||||
|
||||
eq(true, exec_lua([[
|
||||
vim.cmd "set filetype=txt"
|
||||
return vim.g.executed
|
||||
]], {}))
|
||||
end)
|
||||
|
||||
it('calls multiple lua callbacks for the same autocmd execution', function()
|
||||
eq(4, exec_lua([[
|
||||
local count = 0
|
||||
local counter = function()
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
vim.api.nvim_create_autocmd {
|
||||
event = "FileType",
|
||||
pattern = "*",
|
||||
callback = counter,
|
||||
}
|
||||
|
||||
vim.api.nvim_create_autocmd {
|
||||
event = "FileType",
|
||||
pattern = "*",
|
||||
callback = counter,
|
||||
}
|
||||
|
||||
vim.cmd "set filetype=txt"
|
||||
vim.cmd "set filetype=txt"
|
||||
|
||||
return count
|
||||
]], {}))
|
||||
end)
|
||||
|
||||
it('properly releases functions with ++once', function()
|
||||
exec_lua([[
|
||||
WeakTable = setmetatable({}, { __mode = "k" })
|
||||
|
||||
OnceCount = 0
|
||||
|
||||
MyVal = {}
|
||||
WeakTable[MyVal] = true
|
||||
|
||||
vim.api.nvim_create_autocmd {
|
||||
event = "FileType",
|
||||
pattern = "*",
|
||||
callback = function()
|
||||
OnceCount = OnceCount + 1
|
||||
MyVal = {}
|
||||
end,
|
||||
once = true
|
||||
}
|
||||
]])
|
||||
|
||||
command [[set filetype=txt]]
|
||||
eq(1, exec_lua([[return OnceCount]], {}))
|
||||
|
||||
exec_lua([[collectgarbage()]], {})
|
||||
|
||||
command [[set filetype=txt]]
|
||||
eq(1, exec_lua([[return OnceCount]], {}))
|
||||
|
||||
eq(0, exec_lua([[
|
||||
local count = 0
|
||||
for _ in pairs(WeakTable) do
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
return count
|
||||
]]), "Should have no keys remaining")
|
||||
end)
|
||||
|
||||
it('groups can be cleared', function()
|
||||
local augroup = "TestGroup"
|
||||
meths.create_augroup({ name = augroup, clear = true })
|
||||
meths.create_autocmd({
|
||||
group = augroup,
|
||||
event = "FileType",
|
||||
command = "let g:executed = g:executed + 1"
|
||||
})
|
||||
|
||||
set_ft("txt")
|
||||
set_ft("txt")
|
||||
eq(2, get_executed_count(), "should only count twice")
|
||||
|
||||
meths.create_augroup({ name = augroup, clear = true })
|
||||
eq({}, meths.get_autocmds { group = augroup })
|
||||
|
||||
set_ft("txt")
|
||||
set_ft("txt")
|
||||
eq(2, get_executed_count(), "No additional counts")
|
||||
end)
|
||||
|
||||
it('groups work with once', function()
|
||||
local augroup = "TestGroup"
|
||||
|
||||
meths.create_augroup({ name = augroup, clear = true })
|
||||
make_counting_autocmd { group = augroup, once = true }
|
||||
|
||||
set_ft("txt")
|
||||
set_ft("python")
|
||||
|
||||
eq(get_executed_count(), 1)
|
||||
end)
|
||||
|
||||
it('autocmds can be registered multiple times.', function()
|
||||
local augroup = "TestGroup"
|
||||
|
||||
meths.create_augroup({ name = augroup, clear = true })
|
||||
make_counting_autocmd { group = augroup, once = false }
|
||||
make_counting_autocmd { group = augroup, once = false }
|
||||
make_counting_autocmd { group = augroup, once = false }
|
||||
|
||||
set_ft("txt")
|
||||
set_ft("python")
|
||||
|
||||
eq(get_executed_count(), 3 * 2)
|
||||
end)
|
||||
|
||||
it('can be deleted', function()
|
||||
local augroup = "WillBeDeleted"
|
||||
|
||||
meths.create_augroup({ name = augroup, clear = true })
|
||||
meths.create_autocmd {
|
||||
event = {"Filetype"},
|
||||
pattern = "*",
|
||||
command = "echo 'does not matter'",
|
||||
}
|
||||
|
||||
-- Clears the augroup from before, which erases the autocmd
|
||||
meths.create_augroup({ name = augroup, clear = true })
|
||||
|
||||
local result = #meths.get_autocmds { group = augroup }
|
||||
|
||||
eq(0, result)
|
||||
end)
|
||||
|
||||
it('can be used for buffer local autocmds', function()
|
||||
local augroup = "WillBeDeleted"
|
||||
|
||||
meths.set_var("value_set", false)
|
||||
|
||||
meths.create_augroup({ name = augroup, clear = true })
|
||||
meths.create_autocmd {
|
||||
event = "Filetype",
|
||||
pattern = "<buffer>",
|
||||
command = "let g:value_set = v:true",
|
||||
}
|
||||
|
||||
command "new"
|
||||
command "set filetype=python"
|
||||
|
||||
eq(false, meths.get_var("value_set"))
|
||||
end)
|
||||
|
||||
it('can accept vimscript functions', function()
|
||||
source [[
|
||||
let g:vimscript_executed = 0
|
||||
|
||||
function! MyVimscriptFunction() abort
|
||||
let g:vimscript_executed = g:vimscript_executed + 1
|
||||
endfunction
|
||||
|
||||
call nvim_create_autocmd(#{
|
||||
\ event: "Filetype",
|
||||
\ pattern: ["python", "javascript"],
|
||||
\ callback: "MyVimscriptFunction",
|
||||
\ })
|
||||
|
||||
set filetype=txt
|
||||
set filetype=python
|
||||
set filetype=txt
|
||||
set filetype=javascript
|
||||
set filetype=txt
|
||||
]]
|
||||
|
||||
eq(2, meths.get_var("vimscript_executed"))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('augroup!', function()
|
||||
it('legacy: should clear and not return any autocmds for delete groups', function()
|
||||
command('augroup TEMP_A')
|
||||
command(' autocmd! BufReadPost *.py :echo "Hello"')
|
||||
command('augroup END')
|
||||
|
||||
command('augroup! TEMP_A')
|
||||
|
||||
eq(false, pcall(meths.get_autocmds, { group = 'TEMP_A' }))
|
||||
|
||||
-- For some reason, augroup! doesn't clear the autocmds themselves, which is just wild
|
||||
-- but we managed to keep this behavior.
|
||||
eq(1, #meths.get_autocmds { event = 'BufReadPost' })
|
||||
end)
|
||||
|
||||
it('legacy: remove augroups that have no autocmds', function()
|
||||
command('augroup TEMP_AB')
|
||||
command('augroup END')
|
||||
|
||||
command('augroup! TEMP_AB')
|
||||
|
||||
eq(false, pcall(meths.get_autocmds, { group = 'TEMP_AB' }))
|
||||
eq(0, #meths.get_autocmds { event = 'BufReadPost' })
|
||||
end)
|
||||
|
||||
it('legacy: multiple remove and add augroup', function()
|
||||
command('augroup TEMP_ABC')
|
||||
command(' au!')
|
||||
command(' autocmd BufReadPost *.py echo "Hello"')
|
||||
command('augroup END')
|
||||
|
||||
command('augroup! TEMP_ABC')
|
||||
|
||||
-- Should still have one autocmd :'(
|
||||
local aus = meths.get_autocmds { event = 'BufReadPost' }
|
||||
eq(1, #aus, aus)
|
||||
|
||||
command('augroup TEMP_ABC')
|
||||
command(' au!')
|
||||
command(' autocmd BufReadPost *.py echo "Hello"')
|
||||
command('augroup END')
|
||||
|
||||
-- Should now have two autocmds :'(
|
||||
aus = meths.get_autocmds { event = 'BufReadPost' }
|
||||
eq(2, #aus, aus)
|
||||
|
||||
command('augroup! TEMP_ABC')
|
||||
|
||||
eq(false, pcall(meths.get_autocmds, { group = 'TEMP_ABC' }))
|
||||
eq(2, #meths.get_autocmds { event = 'BufReadPost' })
|
||||
end)
|
||||
|
||||
it('api: should clear and not return any autocmds for delete groups by id', function()
|
||||
command('augroup TEMP_ABCD')
|
||||
command('autocmd! BufReadPost *.py :echo "Hello"')
|
||||
command('augroup END')
|
||||
|
||||
local augroup_id = meths.create_augroup { name = "TEMP_ABCD", clear = false }
|
||||
meths.del_augroup_by_id(augroup_id)
|
||||
|
||||
-- For good reason, we kill all the autocmds from del_augroup,
|
||||
-- so now this works as expected
|
||||
eq(false, pcall(meths.get_autocmds, { group = 'TEMP_ABCD' }))
|
||||
eq(0, #meths.get_autocmds { event = 'BufReadPost' })
|
||||
end)
|
||||
|
||||
it('api: should clear and not return any autocmds for delete groups by name', function()
|
||||
command('augroup TEMP_ABCDE')
|
||||
command('autocmd! BufReadPost *.py :echo "Hello"')
|
||||
command('augroup END')
|
||||
|
||||
meths.del_augroup_by_name("TEMP_ABCDE")
|
||||
|
||||
-- For good reason, we kill all the autocmds from del_augroup,
|
||||
-- so now this works as expected
|
||||
eq(false, pcall(meths.get_autocmds, { group = 'TEMP_ABCDE' }))
|
||||
eq(0, #meths.get_autocmds { event = 'BufReadPost' })
|
||||
end)
|
||||
end)
|
||||
end)
|
86
test/functional/autocmd/autocmd_oldtest_spec.lua
Normal file
86
test/functional/autocmd/autocmd_oldtest_spec.lua
Normal file
@ -0,0 +1,86 @@
|
||||
local helpers = require('test.functional.helpers')(after_each)
|
||||
|
||||
local clear = helpers.clear
|
||||
local eq = helpers.eq
|
||||
local meths = helpers.meths
|
||||
local funcs = helpers.funcs
|
||||
|
||||
local exec = function(str)
|
||||
meths.exec(str, false)
|
||||
end
|
||||
|
||||
describe('oldtests', function()
|
||||
before_each(clear)
|
||||
|
||||
local exec_lines = function(str)
|
||||
return funcs.split(funcs.execute(str), "\n")
|
||||
end
|
||||
|
||||
local add_an_autocmd = function()
|
||||
exec [[
|
||||
augroup vimBarTest
|
||||
au BufReadCmd * echo 'hello'
|
||||
augroup END
|
||||
]]
|
||||
|
||||
eq(3, #exec_lines('au vimBarTest'))
|
||||
eq(1, #meths.get_autocmds({ group = 'vimBarTest' }))
|
||||
end
|
||||
|
||||
it('should recognize a bar before the {event}', function()
|
||||
-- Good spacing
|
||||
add_an_autocmd()
|
||||
exec [[ augroup vimBarTest | au! | augroup END ]]
|
||||
eq(1, #exec_lines('au vimBarTest'))
|
||||
eq({}, meths.get_autocmds({ group = 'vimBarTest' }))
|
||||
|
||||
-- Sad spacing
|
||||
add_an_autocmd()
|
||||
exec [[ augroup vimBarTest| au!| augroup END ]]
|
||||
eq(1, #exec_lines('au vimBarTest'))
|
||||
|
||||
|
||||
-- test that a bar is recognized after the {event}
|
||||
add_an_autocmd()
|
||||
exec [[ augroup vimBarTest| au!BufReadCmd| augroup END ]]
|
||||
eq(1, #exec_lines('au vimBarTest'))
|
||||
|
||||
add_an_autocmd()
|
||||
exec [[ au! vimBarTest|echo 'hello' ]]
|
||||
eq(1, #exec_lines('au vimBarTest'))
|
||||
end)
|
||||
|
||||
it('should fire on unload buf', function()
|
||||
funcs.writefile({'Test file Xxx1'}, 'Xxx1')
|
||||
funcs.writefile({'Test file Xxx2'}, 'Xxx2')
|
||||
|
||||
local content = [[
|
||||
func UnloadAllBufs()
|
||||
let i = 1
|
||||
while i <= bufnr('$')
|
||||
if i != bufnr('%') && bufloaded(i)
|
||||
exe i . 'bunload'
|
||||
endif
|
||||
let i += 1
|
||||
endwhile
|
||||
endfunc
|
||||
au BufUnload * call UnloadAllBufs()
|
||||
au VimLeave * call writefile(['Test Finished'], 'Xout')
|
||||
set nohidden
|
||||
edit Xxx1
|
||||
split Xxx2
|
||||
q
|
||||
]]
|
||||
|
||||
funcs.writefile(funcs.split(content, "\n"), 'Xtest')
|
||||
|
||||
funcs.delete('Xout')
|
||||
funcs.system(meths.get_vvar('progpath') .. ' -u NORC -i NONE -N -S Xtest')
|
||||
eq(1, funcs.filereadable('Xout'))
|
||||
|
||||
funcs.delete('Xxx1')
|
||||
funcs.delete('Xxx2')
|
||||
funcs.delete('Xtest')
|
||||
funcs.delete('Xout')
|
||||
end)
|
||||
end)
|
@ -5,6 +5,7 @@ local assert_visible = helpers.assert_visible
|
||||
local assert_alive = helpers.assert_alive
|
||||
local dedent = helpers.dedent
|
||||
local eq = helpers.eq
|
||||
local neq = helpers.neq
|
||||
local eval = helpers.eval
|
||||
local feed = helpers.feed
|
||||
local clear = helpers.clear
|
||||
@ -418,4 +419,106 @@ describe('autocmd', function()
|
||||
:doautocmd SessionLoadPost |
|
||||
]]}
|
||||
end)
|
||||
|
||||
describe('old_tests', function()
|
||||
it('vimscript: WinNew ++once', function()
|
||||
source [[
|
||||
" Without ++once WinNew triggers twice
|
||||
let g:did_split = 0
|
||||
augroup Testing
|
||||
au!
|
||||
au WinNew * let g:did_split += 1
|
||||
augroup END
|
||||
split
|
||||
split
|
||||
call assert_equal(2, g:did_split)
|
||||
call assert_true(exists('#WinNew'))
|
||||
close
|
||||
close
|
||||
|
||||
" With ++once WinNew triggers once
|
||||
let g:did_split = 0
|
||||
augroup Testing
|
||||
au!
|
||||
au WinNew * ++once let g:did_split += 1
|
||||
augroup END
|
||||
split
|
||||
split
|
||||
call assert_equal(1, g:did_split)
|
||||
call assert_false(exists('#WinNew'))
|
||||
close
|
||||
close
|
||||
|
||||
call assert_fails('au WinNew * ++once ++once echo bad', 'E983:')
|
||||
]]
|
||||
|
||||
meths.set_var('did_split', 0)
|
||||
|
||||
source [[
|
||||
augroup Testing
|
||||
au!
|
||||
au WinNew * let g:did_split += 1
|
||||
augroup END
|
||||
|
||||
split
|
||||
split
|
||||
]]
|
||||
|
||||
eq(2, meths.get_var('did_split'))
|
||||
eq(1, funcs.exists('#WinNew'))
|
||||
|
||||
-- Now with once
|
||||
meths.set_var('did_split', 0)
|
||||
|
||||
source [[
|
||||
augroup Testing
|
||||
au!
|
||||
au WinNew * ++once let g:did_split += 1
|
||||
augroup END
|
||||
|
||||
split
|
||||
split
|
||||
]]
|
||||
|
||||
eq(1, meths.get_var('did_split'))
|
||||
eq(0, funcs.exists('#WinNew'))
|
||||
|
||||
-- call assert_fails('au WinNew * ++once ++once echo bad', 'E983:')
|
||||
local ok, msg = pcall(source, [[
|
||||
au WinNew * ++once ++once echo bad
|
||||
]])
|
||||
|
||||
eq(false, ok)
|
||||
eq(true, not not string.find(msg, 'E983:'))
|
||||
end)
|
||||
|
||||
it('should have autocmds in filetypedetect group', function()
|
||||
source [[filetype on]]
|
||||
neq({}, meths.get_autocmds { group = "filetypedetect" })
|
||||
end)
|
||||
|
||||
it('should not access freed mem', function()
|
||||
source [[
|
||||
au BufEnter,BufLeave,WinEnter,WinLeave 0 vs xxx
|
||||
arg 0
|
||||
argadd
|
||||
all
|
||||
all
|
||||
au!
|
||||
bwipe xxx
|
||||
]]
|
||||
end)
|
||||
|
||||
it('should allow comma-separated patterns', function()
|
||||
source [[
|
||||
augroup TestingPatterns
|
||||
au!
|
||||
autocmd BufReadCmd *.shada,*.shada.tmp.[a-z] echo 'hello'
|
||||
autocmd BufReadCmd *.shada,*.shada.tmp.[a-z] echo 'hello'
|
||||
augroup END
|
||||
]]
|
||||
|
||||
eq(4, #meths.get_autocmds { event = "BufReadCmd", group = "TestingPatterns" })
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
@ -35,6 +35,7 @@ describe('CursorMoved', function()
|
||||
it("is not triggered by cursor movement prior to first CursorMoved instantiation", function()
|
||||
source([[
|
||||
let g:cursormoved = 0
|
||||
autocmd! CursorMoved
|
||||
autocmd CursorMoved * let g:cursormoved += 1
|
||||
]])
|
||||
eq(0, eval('g:cursormoved'))
|
||||
|
35
test/functional/autocmd/show_spec.lua
Normal file
35
test/functional/autocmd/show_spec.lua
Normal file
@ -0,0 +1,35 @@
|
||||
local helpers = require('test.functional.helpers')(after_each)
|
||||
|
||||
local clear = helpers.clear
|
||||
local command = helpers.command
|
||||
local dedent = helpers.dedent
|
||||
local eq = helpers.eq
|
||||
local funcs = helpers.funcs
|
||||
|
||||
describe(":autocmd", function()
|
||||
before_each(clear)
|
||||
|
||||
it("should not segfault when you just do autocmd", function()
|
||||
command ":autocmd"
|
||||
end)
|
||||
|
||||
it("should filter based on ++once", function()
|
||||
command "autocmd! BufEnter"
|
||||
command "autocmd BufEnter * :echo 'Hello'"
|
||||
command [[augroup TestingOne]]
|
||||
command [[ autocmd BufEnter * :echo "Line 1"]]
|
||||
command [[ autocmd BufEnter * :echo "Line 2"]]
|
||||
command [[augroup END]]
|
||||
|
||||
eq(dedent([[
|
||||
|
||||
--- Autocommands ---
|
||||
BufEnter
|
||||
* :echo 'Hello'
|
||||
TestingOne BufEnter
|
||||
* :echo "Line 1"
|
||||
:echo "Line 2"]]),
|
||||
funcs.execute('autocmd BufEnter'))
|
||||
|
||||
end)
|
||||
end)
|
@ -32,7 +32,7 @@ describe('autocmd TermClose', function()
|
||||
retry(nil, nil, function() eq(23, eval('g:test_termclose')) end)
|
||||
end)
|
||||
|
||||
it('kills job trapping SIGTERM', function()
|
||||
pending('kills job trapping SIGTERM', function()
|
||||
if iswin() then return end
|
||||
nvim('set_option', 'shell', 'sh')
|
||||
nvim('set_option', 'shellcmdflag', '-c')
|
||||
@ -52,7 +52,7 @@ describe('autocmd TermClose', function()
|
||||
ok(duration <= 4000) -- Epsilon for slow CI
|
||||
end)
|
||||
|
||||
it('kills PTY job trapping SIGHUP and SIGTERM', function()
|
||||
pending('kills PTY job trapping SIGHUP and SIGTERM', function()
|
||||
if iswin() then return end
|
||||
nvim('set_option', 'shell', 'sh')
|
||||
nvim('set_option', 'shellcmdflag', '-c')
|
||||
|
@ -1,7 +1,7 @@
|
||||
local helpers = require('test.functional.helpers')(after_each)
|
||||
local clear = helpers.clear
|
||||
local eq, nvim_eval, nvim_command, nvim, exc_exec, funcs, nvim_feed, curbuf =
|
||||
helpers.eq, helpers.eval, helpers.command, helpers.nvim, helpers.exc_exec,
|
||||
local eq, meths, nvim_eval, nvim_command, nvim, exc_exec, funcs, nvim_feed, curbuf =
|
||||
helpers.eq, helpers.meths, helpers.eval, helpers.command, helpers.nvim, helpers.exc_exec,
|
||||
helpers.funcs, helpers.feed, helpers.curbuf
|
||||
local neq = helpers.neq
|
||||
local read_file = helpers.read_file
|
||||
@ -2162,6 +2162,10 @@ describe('plugin/shada.vim', function()
|
||||
wshada('\004\000\009\147\000\196\002ab\196\001a')
|
||||
wshada_tmp('\004\000\009\147\000\196\002ab\196\001b')
|
||||
|
||||
|
||||
local bufread_commands = meths.get_autocmds({ group = "ShaDaCommands", event = "BufReadCmd" })
|
||||
eq(2, #bufread_commands--[[, vim.inspect(bufread_commands) ]])
|
||||
|
||||
-- Need to set nohidden so that the buffer containing 'fname' is not unloaded
|
||||
-- after loading 'fname_tmp', otherwise the '++opt not supported' test below
|
||||
-- won't work since the BufReadCmd autocmd won't be triggered.
|
||||
|
Loading…
Reference in New Issue
Block a user