mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
Merge #5928 'New event: DirChanged'
This commit is contained in:
commit
340f79b4b8
@ -273,6 +273,8 @@ Name triggered by ~
|
|||||||
|VimLeave| before exiting Vim, after writing the shada file
|
|VimLeave| before exiting Vim, after writing the shada file
|
||||||
|
|
||||||
Various
|
Various
|
||||||
|
|DirChanged| When the current working directory changed.
|
||||||
|
|
||||||
|FileChangedShell| Vim notices that a file changed since editing started
|
|FileChangedShell| Vim notices that a file changed since editing started
|
||||||
|FileChangedShellPost| After handling a file changed since editing started
|
|FileChangedShellPost| After handling a file changed since editing started
|
||||||
|FileChangedRO| before making the first change to a read-only file
|
|FileChangedRO| before making the first change to a read-only file
|
||||||
@ -563,6 +565,16 @@ CursorMoved After the cursor was moved in Normal or Visual
|
|||||||
CursorMovedI After the cursor was moved in Insert mode.
|
CursorMovedI After the cursor was moved in Insert mode.
|
||||||
Not triggered when the popup menu is visible.
|
Not triggered when the popup menu is visible.
|
||||||
Otherwise the same as CursorMoved.
|
Otherwise the same as CursorMoved.
|
||||||
|
*DirChanged*
|
||||||
|
DirChanged When the current working directory was changed
|
||||||
|
using the |:cd| family of commands,
|
||||||
|
|nvim_set_current_dir()|, or on 'autochdir'.
|
||||||
|
The pattern must be * because its meaning may
|
||||||
|
change in the future.
|
||||||
|
It sets these |v:event| keys:
|
||||||
|
cwd: String (current working directory)
|
||||||
|
scope: String ("global", "tab", "window")
|
||||||
|
Recursion is ignored.
|
||||||
*FileAppendCmd*
|
*FileAppendCmd*
|
||||||
FileAppendCmd Before appending to a file. Should do the
|
FileAppendCmd Before appending to a file. Should do the
|
||||||
appending to the file. Use the '[ and ']
|
appending to the file. Use the '[ and ']
|
||||||
|
@ -129,6 +129,7 @@ Functions:
|
|||||||
|msgpackdump()|, |msgpackparse()| provide msgpack de/serialization
|
|msgpackdump()|, |msgpackparse()| provide msgpack de/serialization
|
||||||
|
|
||||||
Events:
|
Events:
|
||||||
|
|DirChanged|
|
||||||
|TabNewEntered|
|
|TabNewEntered|
|
||||||
|TermClose|
|
|TermClose|
|
||||||
|TermOpen|
|
|TermOpen|
|
||||||
|
@ -315,7 +315,7 @@ void nvim_set_current_dir(String dir, Error *err)
|
|||||||
|
|
||||||
try_start();
|
try_start();
|
||||||
|
|
||||||
if (vim_chdir((char_u *)string)) {
|
if (vim_chdir((char_u *)string, kCdScopeGlobal)) {
|
||||||
if (!try_end(err)) {
|
if (!try_end(err)) {
|
||||||
api_set_error(err, Exception, _("Failed to change directory"));
|
api_set_error(err, Exception, _("Failed to change directory"));
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ return {
|
|||||||
'CursorHoldI', -- idem, in Insert mode
|
'CursorHoldI', -- idem, in Insert mode
|
||||||
'CursorMoved', -- cursor was moved
|
'CursorMoved', -- cursor was moved
|
||||||
'CursorMovedI', -- cursor was moved in Insert mode
|
'CursorMovedI', -- cursor was moved in Insert mode
|
||||||
|
'DirChanged', -- directory changed
|
||||||
'EncodingChanged', -- after changing the 'encoding' option
|
'EncodingChanged', -- after changing the 'encoding' option
|
||||||
'FileAppendCmd', -- append to a file using command
|
'FileAppendCmd', -- append to a file using command
|
||||||
'FileAppendPost', -- after appending to a file
|
'FileAppendPost', -- after appending to a file
|
||||||
@ -102,6 +103,7 @@ return {
|
|||||||
-- List of neovim-specific events or aliases for the purpose of generating
|
-- List of neovim-specific events or aliases for the purpose of generating
|
||||||
-- syntax file
|
-- syntax file
|
||||||
neovim_specific = {
|
neovim_specific = {
|
||||||
|
DirChanged=true,
|
||||||
TabClosed=true,
|
TabClosed=true,
|
||||||
TabNew=true,
|
TabNew=true,
|
||||||
TabNewEntered=true,
|
TabNewEntered=true,
|
||||||
|
@ -6998,8 +6998,6 @@ void post_chdir(CdScope scope)
|
|||||||
shorten_fnames(TRUE);
|
shorten_fnames(TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// `:cd`, `:tcd`, `:lcd`, `:chdir`, `:tchdir` and `:lchdir`.
|
/// `:cd`, `:tcd`, `:lcd`, `:chdir`, `:tchdir` and `:lchdir`.
|
||||||
void ex_cd(exarg_T *eap)
|
void ex_cd(exarg_T *eap)
|
||||||
{
|
{
|
||||||
@ -7041,30 +7039,31 @@ void ex_cd(exarg_T *eap)
|
|||||||
new_dir = NameBuff;
|
new_dir = NameBuff;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (vim_chdir(new_dir)) {
|
CdScope scope = kCdScopeGlobal; // Depends on command invoked
|
||||||
|
|
||||||
|
switch (eap->cmdidx) {
|
||||||
|
case CMD_tcd:
|
||||||
|
case CMD_tchdir:
|
||||||
|
scope = kCdScopeTab;
|
||||||
|
break;
|
||||||
|
case CMD_lcd:
|
||||||
|
case CMD_lchdir:
|
||||||
|
scope = kCdScopeWindow;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vim_chdir(new_dir, scope)) {
|
||||||
EMSG(_(e_failed));
|
EMSG(_(e_failed));
|
||||||
} else {
|
} else {
|
||||||
CdScope scope = kCdScopeGlobal; // Depends on command invoked
|
|
||||||
|
|
||||||
switch (eap->cmdidx) {
|
|
||||||
case CMD_tcd:
|
|
||||||
case CMD_tchdir:
|
|
||||||
scope = kCdScopeTab;
|
|
||||||
break;
|
|
||||||
case CMD_lcd:
|
|
||||||
case CMD_lchdir:
|
|
||||||
scope = kCdScopeWindow;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
post_chdir(scope);
|
post_chdir(scope);
|
||||||
|
// Echo the new current directory if the command was typed.
|
||||||
/* Echo the new current directory if the command was typed. */
|
if (KeyTyped || p_verbose >= 5) {
|
||||||
if (KeyTyped || p_verbose >= 5)
|
|
||||||
ex_pwd(eap);
|
ex_pwd(eap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
xfree(tofree);
|
xfree(tofree);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,21 +19,6 @@
|
|||||||
#define EXMODE_NORMAL 1
|
#define EXMODE_NORMAL 1
|
||||||
#define EXMODE_VIM 2
|
#define EXMODE_VIM 2
|
||||||
|
|
||||||
/// The scope of a working-directory command like `:cd`.
|
|
||||||
///
|
|
||||||
/// Scopes are enumerated from lowest to highest. When adding a scope make sure
|
|
||||||
/// to update all functions using scopes as well, such as the implementation of
|
|
||||||
/// `getcwd()`. When using scopes as limits (e.g. in loops) don't use the scopes
|
|
||||||
/// directly, use `MIN_CD_SCOPE` and `MAX_CD_SCOPE` instead.
|
|
||||||
typedef enum {
|
|
||||||
kCdScopeInvalid = -1,
|
|
||||||
kCdScopeWindow, ///< Affects one window.
|
|
||||||
kCdScopeTab, ///< Affects one tab page.
|
|
||||||
kCdScopeGlobal, ///< Affects the entire instance of Neovim.
|
|
||||||
} CdScope;
|
|
||||||
#define MIN_CD_SCOPE kCdScopeWindow
|
|
||||||
#define MAX_CD_SCOPE kCdScopeGlobal
|
|
||||||
|
|
||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
# include "ex_docmd.h.generated.h"
|
# include "ex_docmd.h.generated.h"
|
||||||
#endif
|
#endif
|
||||||
|
@ -48,6 +48,7 @@
|
|||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
||||||
#include "nvim/vim.h"
|
#include "nvim/vim.h"
|
||||||
|
#include "nvim/eval.h"
|
||||||
#include "nvim/ascii.h"
|
#include "nvim/ascii.h"
|
||||||
#include "nvim/file_search.h"
|
#include "nvim/file_search.h"
|
||||||
#include "nvim/charset.h"
|
#include "nvim/charset.h"
|
||||||
@ -1522,6 +1523,47 @@ theend:
|
|||||||
return file_name;
|
return file_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void do_autocmd_dirchanged(char_u *new_dir, CdScope scope)
|
||||||
|
{
|
||||||
|
static bool recursive = false;
|
||||||
|
|
||||||
|
if (recursive || !has_event(EVENT_DIRCHANGED)) {
|
||||||
|
// No autocommand was defined or we changed
|
||||||
|
// the directory from this autocommand.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
recursive = true;
|
||||||
|
|
||||||
|
dict_T *dict = get_vim_var_dict(VV_EVENT);
|
||||||
|
char buf[8];
|
||||||
|
|
||||||
|
switch (scope) {
|
||||||
|
case kCdScopeGlobal:
|
||||||
|
snprintf(buf, sizeof(buf), "global");
|
||||||
|
break;
|
||||||
|
case kCdScopeTab:
|
||||||
|
snprintf(buf, sizeof(buf), "tab");
|
||||||
|
break;
|
||||||
|
case kCdScopeWindow:
|
||||||
|
snprintf(buf, sizeof(buf), "window");
|
||||||
|
break;
|
||||||
|
case kCdScopeInvalid:
|
||||||
|
// Should never happen.
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
dict_add_nr_str(dict, "scope", 0L, (char_u *)buf);
|
||||||
|
dict_add_nr_str(dict, "cwd", 0L, new_dir);
|
||||||
|
dict_set_keys_readonly(dict);
|
||||||
|
|
||||||
|
apply_autocmds(EVENT_DIRCHANGED, NULL, new_dir, false, NULL);
|
||||||
|
|
||||||
|
dict_clear(dict);
|
||||||
|
|
||||||
|
recursive = false;
|
||||||
|
}
|
||||||
|
|
||||||
/// Change to a file's directory.
|
/// Change to a file's directory.
|
||||||
/// Caller must call shorten_fnames()!
|
/// Caller must call shorten_fnames()!
|
||||||
/// @return OK or FAIL
|
/// @return OK or FAIL
|
||||||
@ -1531,18 +1573,28 @@ int vim_chdirfile(char_u *fname)
|
|||||||
|
|
||||||
STRLCPY(dir, fname, MAXPATHL);
|
STRLCPY(dir, fname, MAXPATHL);
|
||||||
*path_tail_with_sep(dir) = NUL;
|
*path_tail_with_sep(dir) = NUL;
|
||||||
return os_chdir((char *)dir) == 0 ? OK : FAIL;
|
if (os_chdir((char *)dir) != 0) {
|
||||||
|
return FAIL;
|
||||||
|
}
|
||||||
|
do_autocmd_dirchanged(dir, kCdScopeWindow);
|
||||||
|
|
||||||
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change directory to "new_dir". Search 'cdpath' for relative directory names.
|
/// Change directory to "new_dir". Search 'cdpath' for relative directory names.
|
||||||
int vim_chdir(char_u *new_dir)
|
int vim_chdir(char_u *new_dir, CdScope scope)
|
||||||
{
|
{
|
||||||
char_u *dir_name = find_directory_in_path(new_dir, STRLEN(new_dir),
|
char_u *dir_name = find_directory_in_path(new_dir, STRLEN(new_dir),
|
||||||
FNAME_MESS, curbuf->b_ffname);
|
FNAME_MESS, curbuf->b_ffname);
|
||||||
if (dir_name == NULL) {
|
if (dir_name == NULL) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int r = os_chdir((char *)dir_name);
|
int r = os_chdir((char *)dir_name);
|
||||||
|
if (r == 0) {
|
||||||
|
do_autocmd_dirchanged(dir_name, scope);
|
||||||
|
}
|
||||||
|
|
||||||
xfree(dir_name);
|
xfree(dir_name);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
@ -6765,8 +6765,9 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
|
|||||||
fname = vim_strsave(fname); /* make a copy, so we can change it */
|
fname = vim_strsave(fname); /* make a copy, so we can change it */
|
||||||
} else {
|
} else {
|
||||||
sfname = vim_strsave(fname);
|
sfname = vim_strsave(fname);
|
||||||
// don't try expanding the following events
|
// Don't try expanding the following events.
|
||||||
if (event == EVENT_COLORSCHEME
|
if (event == EVENT_COLORSCHEME
|
||||||
|
|| event == EVENT_DIRCHANGED
|
||||||
|| event == EVENT_FILETYPE
|
|| event == EVENT_FILETYPE
|
||||||
|| event == EVENT_FUNCUNDEFINED
|
|| event == EVENT_FUNCUNDEFINED
|
||||||
|| event == EVENT_OPTIONSET
|
|| event == EVENT_OPTIONSET
|
||||||
@ -6775,10 +6776,11 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
|
|||||||
|| event == EVENT_REMOTEREPLY
|
|| event == EVENT_REMOTEREPLY
|
||||||
|| event == EVENT_SPELLFILEMISSING
|
|| event == EVENT_SPELLFILEMISSING
|
||||||
|| event == EVENT_SYNTAX
|
|| event == EVENT_SYNTAX
|
||||||
|| event == EVENT_TABCLOSED)
|
|| event == EVENT_TABCLOSED) {
|
||||||
fname = vim_strsave(fname);
|
fname = vim_strsave(fname);
|
||||||
else
|
} else {
|
||||||
fname = (char_u *)FullName_save((char *)fname, FALSE);
|
fname = (char_u *)FullName_save((char *)fname, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (fname == NULL) { /* out of memory */
|
if (fname == NULL) { /* out of memory */
|
||||||
xfree(sfname);
|
xfree(sfname);
|
||||||
|
@ -1249,4 +1249,20 @@ typedef enum {
|
|||||||
kBroken
|
kBroken
|
||||||
} WorkingStatus;
|
} WorkingStatus;
|
||||||
|
|
||||||
|
/// The scope of a working-directory command like `:cd`.
|
||||||
|
///
|
||||||
|
/// Scopes are enumerated from lowest to highest. When adding a scope make sure
|
||||||
|
/// to update all functions using scopes as well, such as the implementation of
|
||||||
|
/// `getcwd()`. When using scopes as limits (e.g. in loops) don't use the scopes
|
||||||
|
/// directly, use `MIN_CD_SCOPE` and `MAX_CD_SCOPE` instead.
|
||||||
|
typedef enum {
|
||||||
|
kCdScopeInvalid = -1,
|
||||||
|
kCdScopeWindow, ///< Affects one window.
|
||||||
|
kCdScopeTab, ///< Affects one tab page.
|
||||||
|
kCdScopeGlobal, ///< Affects the entire instance of Neovim.
|
||||||
|
} CdScope;
|
||||||
|
|
||||||
|
#define MIN_CD_SCOPE kCdScopeWindow
|
||||||
|
#define MAX_CD_SCOPE kCdScopeGlobal
|
||||||
|
|
||||||
#endif /* NVIM_GLOBALS_H */
|
#endif /* NVIM_GLOBALS_H */
|
||||||
|
@ -1404,7 +1404,7 @@ int op_delete(oparg_T *oap)
|
|||||||
|
|
||||||
if (oap->regname == 0) {
|
if (oap->regname == 0) {
|
||||||
set_clipboard(0, reg);
|
set_clipboard(0, reg);
|
||||||
yank_do_autocmd(oap, reg);
|
do_autocmd_textyankpost(oap, reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -2315,7 +2315,7 @@ bool op_yank(oparg_T *oap, bool message)
|
|||||||
yankreg_T *reg = get_yank_register(oap->regname, YREG_YANK);
|
yankreg_T *reg = get_yank_register(oap->regname, YREG_YANK);
|
||||||
op_yank_reg(oap, message, reg, is_append_register(oap->regname));
|
op_yank_reg(oap, message, reg, is_append_register(oap->regname));
|
||||||
set_clipboard(oap->regname, reg);
|
set_clipboard(oap->regname, reg);
|
||||||
yank_do_autocmd(oap, reg);
|
do_autocmd_textyankpost(oap, reg);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -2538,7 +2538,7 @@ static void yank_copy_line(yankreg_T *reg, struct block_def *bd, size_t y_idx)
|
|||||||
///
|
///
|
||||||
/// @param oap Operator arguments.
|
/// @param oap Operator arguments.
|
||||||
/// @param reg The yank register used.
|
/// @param reg The yank register used.
|
||||||
static void yank_do_autocmd(oparg_T *oap, yankreg_T *reg)
|
static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
|
||||||
FUNC_ATTR_NONNULL_ALL
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
static bool recursive = false;
|
static bool recursive = false;
|
||||||
|
@ -14,4 +14,5 @@ typedef unsigned char char_u;
|
|||||||
typedef uint32_t u8char_T;
|
typedef uint32_t u8char_T;
|
||||||
|
|
||||||
typedef struct expand expand_T;
|
typedef struct expand expand_T;
|
||||||
|
|
||||||
#endif // NVIM_TYPES_H
|
#endif // NVIM_TYPES_H
|
||||||
|
88
test/functional/autocmd/dirchanged_spec.lua
Normal file
88
test/functional/autocmd/dirchanged_spec.lua
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
local lfs = require('lfs')
|
||||||
|
local h = require('test.functional.helpers')(after_each)
|
||||||
|
|
||||||
|
local clear = h.clear
|
||||||
|
local command = h.command
|
||||||
|
local eq = h.eq
|
||||||
|
local eval = h.eval
|
||||||
|
local request = h.request
|
||||||
|
|
||||||
|
describe('DirChanged ->', function()
|
||||||
|
local curdir = lfs.currentdir()
|
||||||
|
local dirs = {
|
||||||
|
curdir .. '/Xtest-functional-autocmd-dirchanged.dir1',
|
||||||
|
curdir .. '/Xtest-functional-autocmd-dirchanged.dir2',
|
||||||
|
curdir .. '/Xtest-functional-autocmd-dirchanged.dir3',
|
||||||
|
}
|
||||||
|
|
||||||
|
setup(function() for _, dir in pairs(dirs) do h.mkdir(dir) end end)
|
||||||
|
teardown(function() for _, dir in pairs(dirs) do h.rmdir(dir) end end)
|
||||||
|
|
||||||
|
before_each(function()
|
||||||
|
clear()
|
||||||
|
command('autocmd DirChanged * let g:event = copy(v:event)')
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('"autocmd DirChanged *" sets v:event for all :cd variants', function()
|
||||||
|
command('lcd '..dirs[1])
|
||||||
|
eq({cwd=dirs[1], scope='window'}, eval('g:event'))
|
||||||
|
|
||||||
|
command('tcd '..dirs[2])
|
||||||
|
eq({cwd=dirs[2], scope='tab'}, eval('g:event'))
|
||||||
|
|
||||||
|
command('cd '..dirs[3])
|
||||||
|
eq({cwd=dirs[3], scope='global'}, eval('g:event'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('"autocmd DirChanged *" does not trigger for failing :cd variants', function()
|
||||||
|
command('let g:event = {}')
|
||||||
|
|
||||||
|
local status1, err1 = pcall(function()
|
||||||
|
command('lcd '..dirs[1] .. '/doesnotexist')
|
||||||
|
end)
|
||||||
|
eq({}, eval('g:event'))
|
||||||
|
|
||||||
|
local status2, err2 = pcall(function()
|
||||||
|
command('lcd '..dirs[2] .. '/doesnotexist')
|
||||||
|
end)
|
||||||
|
eq({}, eval('g:event'))
|
||||||
|
|
||||||
|
local status3, err3 = pcall(function()
|
||||||
|
command('lcd '..dirs[3] .. '/doesnotexist')
|
||||||
|
end)
|
||||||
|
eq({}, eval('g:event'))
|
||||||
|
|
||||||
|
eq(false, status1)
|
||||||
|
eq(false, status2)
|
||||||
|
eq(false, status3)
|
||||||
|
|
||||||
|
eq('E344', string.match(err1, 'Vim.*:(.*):'))
|
||||||
|
eq('E344', string.match(err2, 'Vim.*:(.*):'))
|
||||||
|
eq('E344', string.match(err3, 'Vim.*:(.*):'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("'autochdir' triggers DirChanged", function()
|
||||||
|
command('set autochdir')
|
||||||
|
|
||||||
|
command('split '..dirs[1]..'/foo')
|
||||||
|
eq({cwd=dirs[1], scope='window'}, eval('g:event'))
|
||||||
|
|
||||||
|
command('split '..dirs[2]..'/bar')
|
||||||
|
eq({cwd=dirs[2], scope='window'}, eval('g:event'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('nvim_set_current_dir() triggers DirChanged', function()
|
||||||
|
request('nvim_set_current_dir', dirs[1])
|
||||||
|
eq({cwd=dirs[1], scope='global'}, eval('g:event'))
|
||||||
|
|
||||||
|
request('nvim_set_current_dir', dirs[2])
|
||||||
|
eq({cwd=dirs[2], scope='global'}, eval('g:event'))
|
||||||
|
|
||||||
|
local status, err = pcall(function()
|
||||||
|
request('nvim_set_current_dir', '/doesnotexist')
|
||||||
|
end)
|
||||||
|
eq(false, status)
|
||||||
|
eq('Failed to change directory', string.match(err, ': (.*)'))
|
||||||
|
eq({cwd=dirs[2], scope='global'}, eval('g:event'))
|
||||||
|
end)
|
||||||
|
end)
|
Loading…
Reference in New Issue
Block a user