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/
/.cache/clangd/
/.ccls-cache/
/.clang-tidy
.DS_Store
*.mo

View File

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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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_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 },

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.
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);

View File

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

View File

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

View File

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

View File

@ -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 */ \

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

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

View File

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