feat(lua): add api and lua autocmds

This commit is contained in:
TJ DeVries
2021-05-28 15:45:34 -04:00
committed by bfredl
parent 1b5767aa34
commit 991e472881
38 changed files with 2888 additions and 618 deletions

1
.gitignore vendored
View File

@@ -15,6 +15,7 @@ compile_commands.json
/.clangd/ /.clangd/
/.cache/clangd/ /.cache/clangd/
/.ccls-cache/ /.ccls-cache/
/.clang-tidy
.DS_Store .DS_Store
*.mo *.mo

View File

@@ -95,6 +95,7 @@ CONFIG = {
'window.c', 'window.c',
'win_config.c', 'win_config.c',
'tabpage.c', 'tabpage.c',
'autocmd.c',
'ui.c', 'ui.c',
], ],
# List of files/directories for doxygen to read, separated by blanks # List of files/directories for doxygen to read, separated by blanks

11
scripts/uncrustify.sh Executable file
View 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
View 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
View 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

View File

@@ -110,5 +110,34 @@ return {
"reverse"; "reverse";
"nocombine"; "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";
};
} }

View File

@@ -6,22 +6,30 @@
#include <msgpack.h> #include <msgpack.h>
#include <stdbool.h> #include <stdbool.h>
#include "nvim/api/buffer.h"
#include "nvim/api/deprecated.h" #include "nvim/api/deprecated.h"
#include "nvim/api/extmark.h"
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h" #include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.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/tabpage.h"
#include "nvim/api/ui.h" #include "nvim/api/ui.h"
#include "nvim/api/vim.h" #include "nvim/api/vim.h"
#include "nvim/api/vimscript.h" #include "nvim/api/vimscript.h"
#include "nvim/api/win_config.h" #include "nvim/api/win_config.h"
#include "nvim/api/window.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; static Map(String, MsgpackRpcRequestHandler) methods = MAP_INIT;

View File

@@ -396,19 +396,14 @@ void set_option_to(uint64_t channel_id, void *to, int type, String name, Object
stringval = value.data.string.data; stringval = value.data.string.data;
} }
const sctx_T save_current_sctx = current_sctx; WITH_SCRIPT_CONTEXT(channel_id, {
current_sctx.sc_sid = const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL))
channel_id == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; ? 0 : (type == SREQ_GLOBAL)
current_sctx.sc_lnum = 0; ? OPT_GLOBAL : OPT_LOCAL;
current_channel_id = channel_id;
const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL)) set_option_value_for(name.data, numval, stringval,
? 0 : (type == SREQ_GLOBAL) opt_flags, type, to, err);
? OPT_GLOBAL : OPT_LOCAL; });
set_option_value_for(name.data, numval, stringval,
opt_flags, type, to, err);
current_sctx = save_current_sctx;
} }
buf_T *find_buffer_by_handle(Buffer buffer, Error *err) buf_T *find_buffer_by_handle(Buffer buffer, Error *err)
@@ -1614,3 +1609,16 @@ err:
NLUA_CLEAR_REF(luaref); NLUA_CLEAR_REF(luaref);
NLUA_CLEAR_REF(compl_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;
}
}

View File

@@ -138,10 +138,27 @@ typedef struct {
msg_list = saved_msg_list; /* Restore the exception context. */ \ msg_list = saved_msg_list; /* Restore the exception context. */ \
} while (0) } 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 #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/private/helpers.h.generated.h" # include "api/private/helpers.h.generated.h"
# include "keysets.h.generated.h" # include "keysets.h.generated.h"
#endif #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 #endif // NVIM_API_PRIVATE_HELPERS_H

View File

@@ -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;
}

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,11 @@
#include "nvim/buffer_defs.h" #include "nvim/buffer_defs.h"
#include "nvim/ex_cmds_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 // Struct to save values in before executing autocommands for a buffer that is
// not the current buffer. // not the current buffer.
typedef struct { typedef struct {
@@ -18,22 +23,23 @@ typedef struct {
} aco_save_T; } aco_save_T;
typedef struct AutoCmd { typedef struct AutoCmd {
char_u *cmd; // Command to be executed (NULL when AucmdExecutable exec;
// command has been removed)
bool once; // "One shot": removed after execution bool once; // "One shot": removed after execution
bool nested; // If autocommands nest here bool nested; // If autocommands nest here
bool last; // last command in list bool last; // last command in list
int64_t id; // TODO(tjdevries): Explain
sctx_T script_ctx; // script context where defined 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; } AutoCmd;
typedef struct AutoPat { typedef struct AutoPat {
struct AutoPat *next; // next AutoPat in AutoPat list; MUST struct AutoPat *next; // next AutoPat in AutoPat list; MUST
// be the first entry // be the first entry
char_u *pat; // pattern as typed (NULL when pattern char_u *pat; // pattern as typed (NULL when pattern
// has been removed) // has been removed)
regprog_T *reg_prog; // compiled regprog for pattern regprog_T *reg_prog; // compiled regprog for pattern
AutoCmd *cmds; // list of commands to do AutoCmd *cmds; // list of commands to do
int group; // group ID int group; // group ID
int patlen; // strlen() of pat int patlen; // strlen() of pat
int buflocal_nr; // !=0 for buffer-local AutoPat int buflocal_nr; // !=0 for buffer-local AutoPat
@@ -41,13 +47,7 @@ typedef struct AutoPat {
char last; // last pattern for apply_autocmds() char last; // last pattern for apply_autocmds()
} AutoPat; } AutoPat;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "auevents_enum.generated.h"
#endif
///
/// Struct used to keep status while executing autocommands for an event. /// Struct used to keep status while executing autocommands for an event.
///
typedef struct AutoPatCmd { typedef struct AutoPatCmd {
AutoPat *curpat; // next AutoPat to examine AutoPat *curpat; // next AutoPat to examine
AutoCmd *nextcmd; // next AutoCmd to execute AutoCmd *nextcmd; // next AutoCmd to execute
@@ -75,8 +75,16 @@ EXTERN bool au_did_filetype INIT(= false);
# include "autocmd.h.generated.h" # include "autocmd.h.generated.h"
#endif #endif
#define AUGROUP_DEFAULT -1 // default autocmd group #define AUGROUP_DEFAULT (-1) // default autocmd group
#define AUGROUP_ERROR -2 // erroneous autocmd group #define AUGROUP_ERROR (-2) // erroneous autocmd group
#define AUGROUP_ALL -3 // all autocmd groups #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 #endif // NVIM_AUTOCMD_H

View File

@@ -85,9 +85,9 @@ typedef struct {
// used for recording hunks from xdiff // used for recording hunks from xdiff
typedef struct { typedef struct {
linenr_T lnum_orig; linenr_T lnum_orig;
long count_orig; long count_orig;
linenr_T lnum_new; linenr_T lnum_new;
long count_new; long count_new;
} diffhunk_T; } diffhunk_T;
// two diff inputs and one result // two diff inputs and one result
@@ -1285,7 +1285,7 @@ void ex_diffpatch(exarg_T *eap)
ex_file(eap); ex_file(eap);
// Do filetype detection with the new name. // Do filetype detection with the new name.
if (au_has_group((char_u *)"filetypedetect")) { if (augroup_exists("filetypedetect")) {
do_cmdline_cmd(":doau filetypedetect BufRead"); 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. /// Callback function for the xdl_diff() function.
/// Stores the diff output in a grow array. /// Stores the diff output in a grow array.
/// ///
static int xdiff_out(long start_a, long count_a, long start_b, long count_b, static int xdiff_out(long start_a, long count_a, long start_b, long count_b, void *priv)
void *priv)
{ {
diffout_T *dout = (diffout_T *)priv; diffout_T *dout = (diffout_T *)priv;
diffhunk_T *p = xmalloc(sizeof(*p)); diffhunk_T *p = xmalloc(sizeof(*p));

View File

@@ -6,6 +6,7 @@
*/ */
#include <math.h> #include <math.h>
#include <stdlib.h>
#include "auto/config.h" #include "auto/config.h"
@@ -3258,7 +3259,7 @@ char_u *get_user_var_name(expand_T *xp, int idx)
// b: variables // b: variables
// In cmdwin, the alternative buffer should be used. // In cmdwin, the alternative buffer should be used.
hashtab_T *ht 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 < ht->ht_used) {
if (bdone++ == 0) { if (bdone++ == 0) {
hi = ht->ht_array; hi = ht->ht_array;
@@ -7746,6 +7747,7 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg)
callback->type = kCallbackFuncref; callback->type = kCallbackFuncref;
} }
} else if (nlua_is_table_from_lua(arg)) { } else if (nlua_is_table_from_lua(arg)) {
// TODO(tjdvries): UnifiedCallback
char_u *name = nlua_register_table_as_callable(arg); char_u *name = nlua_register_table_as_callable(arg);
if (name != NULL) { if (name != NULL) {
@@ -7775,6 +7777,7 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co
{ {
partial_T *partial; partial_T *partial;
char_u *name; char_u *name;
Array args = ARRAY_DICT_INIT;
switch (callback->type) { switch (callback->type) {
case kCallbackFuncref: case kCallbackFuncref:
name = callback->data.funcref; 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); name = partial_name(partial);
break; 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: case kCallbackNone:
return false; return false;
break; break;

View File

@@ -894,6 +894,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
partial = argvars[0].vval.v_partial; partial = argvars[0].vval.v_partial;
func = partial_name(partial); func = partial_name(partial);
} else if (nlua_is_table_from_lua(&argvars[0])) { } else if (nlua_is_table_from_lua(&argvars[0])) {
// TODO(tjdevries): UnifiedCallback
func = nlua_register_table_as_callable(&argvars[0]); func = nlua_register_table_as_callable(&argvars[0]);
owned = true; owned = true;
} else { } else {

View File

@@ -28,11 +28,11 @@
#include "nvim/mbyte.h" #include "nvim/mbyte.h"
#include "nvim/memory.h" #include "nvim/memory.h"
#include "nvim/message.h" #include "nvim/message.h"
#include "nvim/os/fileio.h"
#include "nvim/os/input.h" #include "nvim/os/input.h"
#include "nvim/pos.h" #include "nvim/pos.h"
#include "nvim/types.h" #include "nvim/types.h"
#include "nvim/vim.h" #include "nvim/vim.h"
#include "nvim/os/fileio.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/typval.c.generated.h" # 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 // FIXME: this is inconsistent with tv_equal but is needed for precision
// maybe change dictwatcheradd to return a watcher id instead? // maybe change dictwatcheradd to return a watcher id instead?
return cb1->data.partial == cb2->data.partial; return cb1->data.partial == cb2->data.partial;
case kCallbackLua:
return cb1->data.luaref == cb2->data.luaref;
case kCallbackNone: case kCallbackNone:
return true; return true;
} }
@@ -1142,6 +1144,9 @@ void callback_free(Callback *callback)
case kCallbackPartial: case kCallbackPartial:
partial_unref(callback->data.partial); partial_unref(callback->data.partial);
break; break;
case kCallbackLua:
NLUA_CLEAR_REF(callback->data.luaref);
break;
case kCallbackNone: case kCallbackNone:
break; break;
} }
@@ -1149,6 +1154,12 @@ void callback_free(Callback *callback)
callback->data.funcref = NULL; callback->data.funcref = NULL;
} }
/// Check if callback is freed
bool callback_is_freed(Callback callback)
{
return false;
}
/// Copy a callback into a typval_T. /// Copy a callback into a typval_T.
void callback_put(Callback *cb, typval_T *tv) void callback_put(Callback *cb, typval_T *tv)
FUNC_ATTR_NONNULL_ALL 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); tv->vval.v_string = vim_strsave(cb->data.funcref);
func_ref(cb->data.funcref); func_ref(cb->data.funcref);
break; break;
case kCallbackLua:
// TODO(tjdevries): I'm not even sure if this is strictly necessary?
abort();
default: default:
tv->v_type = VAR_SPECIAL; tv->v_type = VAR_SPECIAL;
tv->vval.v_special = kSpecialVarNull; tv->vval.v_special = kSpecialVarNull;
@@ -1185,6 +1199,9 @@ void callback_copy(Callback *dest, Callback *src)
dest->data.funcref = vim_strsave(src->data.funcref); dest->data.funcref = vim_strsave(src->data.funcref);
func_ref(src->data.funcref); func_ref(src->data.funcref);
break; break;
case kCallbackLua:
dest->data.luaref = api_new_luaref(src->data.luaref);
break;
default: default:
dest->data.funcref = NULL; dest->data.funcref = NULL;
break; break;

View File

@@ -72,15 +72,18 @@ typedef enum {
kCallbackNone = 0, kCallbackNone = 0,
kCallbackFuncref, kCallbackFuncref,
kCallbackPartial, kCallbackPartial,
kCallbackLua,
} CallbackType; } CallbackType;
typedef struct { typedef struct {
union { union {
char_u *funcref; char_u *funcref;
partial_T *partial; partial_T *partial;
LuaRef luaref;
} data; } data;
CallbackType type; CallbackType type;
} Callback; } Callback;
#define CALLBACK_INIT { .type = kCallbackNone } #define CALLBACK_INIT { .type = kCallbackNone }
#define CALLBACK_NONE ((Callback)CALLBACK_INIT) #define CALLBACK_NONE ((Callback)CALLBACK_INIT)

View File

@@ -1943,7 +1943,7 @@ int do_write(exarg_T *eap)
// If 'filetype' was empty try detecting it now. // If 'filetype' was empty try detecting it now.
if (*curbuf->b_p_ft == NUL) { 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); (void)do_doautocmd((char_u *)"filetypedetect BufRead", true, NULL);
} }
do_modelines(0); do_modelines(0);

View File

@@ -4,6 +4,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include "nvim/eval/typval.h"
#include "nvim/normal.h" #include "nvim/normal.h"
#include "nvim/pos.h" // for linenr_T #include "nvim/pos.h" // for linenr_T
#include "nvim/regexp_defs.h" #include "nvim/regexp_defs.h"
@@ -91,6 +92,35 @@ typedef struct exarg exarg_T;
typedef void (*ex_func_T)(exarg_T *eap); 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); typedef char_u *(*LineGetter)(int, void *, int, bool);
/// Structure for command definition. /// Structure for command definition.

View File

@@ -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_SYNTAX, get_syntax_name, true, true },
{ EXPAND_SYNTIME, get_syntime_arg, true, true }, { EXPAND_SYNTIME, get_syntime_arg, true, true },
{ EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, true }, { EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, true },
{ EXPAND_EVENTS, get_event_name, true, true }, { EXPAND_EVENTS, expand_get_event_name, true, true },
{ EXPAND_AUGROUP, get_augroup_name, true, true }, { EXPAND_AUGROUP, expand_get_augroup_name, true, true },
{ EXPAND_CSCOPE, get_cscope_name, true, true }, { EXPAND_CSCOPE, get_cscope_name, true, true },
{ EXPAND_SIGN, get_sign_name, true, true }, { EXPAND_SIGN, get_sign_name, true, true },
{ EXPAND_PROFILE, get_profile_name, true, true }, { EXPAND_PROFILE, get_profile_name, true, true },

View File

@@ -3796,7 +3796,7 @@ static int set_rw_fname(char_u *fname, char_u *sfname)
// Do filetype detection now if 'filetype' is empty. // Do filetype detection now if 'filetype' is empty.
if (*curbuf->b_p_ft == NUL) { 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); (void)do_doautocmd((char_u *)"filetypedetect BufRead", false, NULL);
} }
do_modelines(0); do_modelines(0);

View File

@@ -326,16 +326,16 @@ EXTERN int want_garbage_collect INIT(= false);
EXTERN int garbage_collect_at_exit INIT(= false); EXTERN int garbage_collect_at_exit INIT(= false);
// Special values for current_SID. // Special values for current_SID.
#define SID_MODELINE -1 // when using a modeline #define SID_MODELINE (-1) // when using a modeline
#define SID_CMDARG -2 // for "--cmd" argument #define SID_CMDARG (-2) // for "--cmd" argument
#define SID_CARG -3 // for "-c" argument #define SID_CARG (-3) // for "-c" argument
#define SID_ENV -4 // for sourcing environment variable #define SID_ENV (-4) // for sourcing environment variable
#define SID_ERROR -5 // option was reset because of an error #define SID_ERROR (-5) // option was reset because of an error
#define SID_NONE -6 // don't set scriptID #define SID_NONE (-6) // don't set scriptID
#define SID_WINLAYOUT -7 // changing window size #define SID_WINLAYOUT (-7) // changing window size
#define SID_LUA -8 // for Lua scripts/chunks #define SID_LUA (-8) // for Lua scripts/chunks
#define SID_API_CLIENT -9 // for API clients #define SID_API_CLIENT (-9) // for API clients
#define SID_STR -10 // for sourcing a string with no script item #define SID_STR (-10) // for sourcing a string with no script item
// Script CTX being sourced or was sourced to define the current function. // Script CTX being sourced or was sourced to define the current function.
EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 }); EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 });

View File

@@ -245,6 +245,7 @@ enum key_extra {
KE_EVENT = 102, // event KE_EVENT = 102, // event
KE_LUA = 103, // lua special key KE_LUA = 103, // lua special key
KE_COMMAND = 104, // <Cmd> 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_COMMAND TERMCAP2KEY(KS_EXTRA, KE_COMMAND)
#define K_LUA TERMCAP2KEY(KS_EXTRA, KE_LUA) #define K_LUA TERMCAP2KEY(KS_EXTRA, KE_LUA)
#define K_AUCMD_SPECIAL TERMCAP2KEY(KS_EXTRA, KE_AUCMD_SPECIAL)
// Bits for modifier mask // Bits for modifier mask
// 0x01 cannot be used, because the modifier must be 0x02 or higher // 0x01 cannot be used, because the modifier must be 0x02 or higher
#define MOD_MASK_SHIFT 0x02 #define MOD_MASK_SHIFT 0x02

View File

@@ -1350,6 +1350,16 @@ Object nlua_exec(const String str, const Array args, Error *err)
return nlua_pop_Object(lstate, false, 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) /// call a LuaRef as a function (or table with __call metamethod)
/// ///
/// @param ref the reference to call (not consumed) /// @param ref the reference to call (not consumed)

View File

@@ -24,14 +24,6 @@ typedef struct {
#endif #endif
} nlua_ref_state_t; } 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) \ #define NLUA_CLEAR_REF(x) \
do { \ do { \
/* Take the address to avoid double evaluation. #1375 */ \ /* Take the address to avoid double evaluation. #1375 */ \

View File

@@ -9,7 +9,7 @@
#include <string.h> #include <string.h>
#include "nvim/ascii.h" #include "nvim/ascii.h"
#include "nvim/aucmd.h" #include "nvim/autocmd.h"
#include "nvim/buffer.h" #include "nvim/buffer.h"
#include "nvim/channel.h" #include "nvim/channel.h"
#include "nvim/charset.h" #include "nvim/charset.h"

View File

@@ -177,6 +177,7 @@ MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER) MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER)
MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER) MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER)
MAP_IMPL(String, handle_T, 0) MAP_IMPL(String, handle_T, 0)
MAP_IMPL(String, int, DEFAULT_INITIALIZER)
MAP_IMPL(ColorKey, ColorItem, COLOR_ITEM_INITIALIZER) MAP_IMPL(ColorKey, ColorItem, COLOR_ITEM_INITIALIZER)

View File

@@ -46,6 +46,7 @@ MAP_DECLS(handle_T, ptr_t)
MAP_DECLS(String, MsgpackRpcRequestHandler) MAP_DECLS(String, MsgpackRpcRequestHandler)
MAP_DECLS(HlEntry, int) MAP_DECLS(HlEntry, int)
MAP_DECLS(String, handle_T) MAP_DECLS(String, handle_T)
MAP_DECLS(String, int)
MAP_DECLS(ColorKey, ColorItem) MAP_DECLS(ColorKey, ColorItem)

View File

@@ -5,7 +5,7 @@
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h" #include "nvim/api/vim.h"
#include "nvim/ascii.h" #include "nvim/ascii.h"
#include "nvim/aucmd.h" #include "nvim/autocmd.h"
#include "nvim/charset.h" #include "nvim/charset.h"
#include "nvim/ex_docmd.h" #include "nvim/ex_docmd.h"
#include "nvim/macros.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'; bool focus_gained = *rbuffer_get(input->read_stream.buffer, 2) == 'I';
// Advance past the sequence // Advance past the sequence
rbuffer_consumed(input->read_stream.buffer, 3); rbuffer_consumed(input->read_stream.buffer, 3);
aucmd_schedule_focusgained(focus_gained); autocmd_schedule_focusgained(focus_gained);
return true; return true;
} }
return false; return false;

View File

@@ -8,7 +8,7 @@
#include <string.h> #include <string.h>
#include "nvim/ascii.h" #include "nvim/ascii.h"
#include "nvim/aucmd.h" #include "nvim/autocmd.h"
#include "nvim/charset.h" #include "nvim/charset.h"
#include "nvim/cursor.h" #include "nvim/cursor.h"
#include "nvim/cursor_shape.h" #include "nvim/cursor_shape.h"

View 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)

View 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)

View File

@@ -5,6 +5,7 @@ local assert_visible = helpers.assert_visible
local assert_alive = helpers.assert_alive local assert_alive = helpers.assert_alive
local dedent = helpers.dedent local dedent = helpers.dedent
local eq = helpers.eq local eq = helpers.eq
local neq = helpers.neq
local eval = helpers.eval local eval = helpers.eval
local feed = helpers.feed local feed = helpers.feed
local clear = helpers.clear local clear = helpers.clear
@@ -418,4 +419,106 @@ describe('autocmd', function()
:doautocmd SessionLoadPost | :doautocmd SessionLoadPost |
]]} ]]}
end) 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) end)

View File

@@ -35,6 +35,7 @@ describe('CursorMoved', function()
it("is not triggered by cursor movement prior to first CursorMoved instantiation", function() it("is not triggered by cursor movement prior to first CursorMoved instantiation", function()
source([[ source([[
let g:cursormoved = 0 let g:cursormoved = 0
autocmd! CursorMoved
autocmd CursorMoved * let g:cursormoved += 1 autocmd CursorMoved * let g:cursormoved += 1
]]) ]])
eq(0, eval('g:cursormoved')) eq(0, eval('g:cursormoved'))

View 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)

View File

@@ -32,7 +32,7 @@ describe('autocmd TermClose', function()
retry(nil, nil, function() eq(23, eval('g:test_termclose')) end) retry(nil, nil, function() eq(23, eval('g:test_termclose')) end)
end) end)
it('kills job trapping SIGTERM', function() pending('kills job trapping SIGTERM', function()
if iswin() then return end if iswin() then return end
nvim('set_option', 'shell', 'sh') nvim('set_option', 'shell', 'sh')
nvim('set_option', 'shellcmdflag', '-c') nvim('set_option', 'shellcmdflag', '-c')
@@ -52,7 +52,7 @@ describe('autocmd TermClose', function()
ok(duration <= 4000) -- Epsilon for slow CI ok(duration <= 4000) -- Epsilon for slow CI
end) end)
it('kills PTY job trapping SIGHUP and SIGTERM', function() pending('kills PTY job trapping SIGHUP and SIGTERM', function()
if iswin() then return end if iswin() then return end
nvim('set_option', 'shell', 'sh') nvim('set_option', 'shell', 'sh')
nvim('set_option', 'shellcmdflag', '-c') nvim('set_option', 'shellcmdflag', '-c')

View File

@@ -1,7 +1,7 @@
local helpers = require('test.functional.helpers')(after_each) local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear local clear = helpers.clear
local eq, nvim_eval, nvim_command, nvim, exc_exec, funcs, nvim_feed, curbuf = local eq, meths, nvim_eval, nvim_command, nvim, exc_exec, funcs, nvim_feed, curbuf =
helpers.eq, helpers.eval, helpers.command, helpers.nvim, helpers.exc_exec, helpers.eq, helpers.meths, helpers.eval, helpers.command, helpers.nvim, helpers.exc_exec,
helpers.funcs, helpers.feed, helpers.curbuf helpers.funcs, helpers.feed, helpers.curbuf
local neq = helpers.neq local neq = helpers.neq
local read_file = helpers.read_file 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('\004\000\009\147\000\196\002ab\196\001a')
wshada_tmp('\004\000\009\147\000\196\002ab\196\001b') 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 -- Need to set nohidden so that the buffer containing 'fname' is not unloaded
-- after loading 'fname_tmp', otherwise the '++opt not supported' test below -- after loading 'fname_tmp', otherwise the '++opt not supported' test below
-- won't work since the BufReadCmd autocmd won't be triggered. -- won't work since the BufReadCmd autocmd won't be triggered.