mirror of
https://github.com/neovim/neovim.git
synced 2026-07-10 19:39:38 +00:00
fix(filetype): ensure directory bufname ends w/ slash sep #40552
Problem: `vim.filetype.match()` needs a cheap way to recognize directory buffers without doing filesystem stat work. Solution: Ensure full buffer names for directories end in a trailing slash. Now directory buffers can proceed through the normal 'filetype' path. Note side-effects: session and ShaDa buffer-list restore behavior must be compatible, so those + corresponding tests must be updated.
This commit is contained in:
@@ -2609,6 +2609,9 @@ nvim_buf_get_name({buf}) *nvim_buf_get_name()*
|
||||
Gets the full/absolute filepath of the buffer, or the buffer name for
|
||||
non-file buffers.
|
||||
|
||||
If the buffer represents a directory, the name ends with a path separator,
|
||||
unless it was changed by |:file| or |nvim_buf_set_name()|.
|
||||
|
||||
Attributes: ~
|
||||
Since: 0.1.0
|
||||
|
||||
|
||||
@@ -692,7 +692,9 @@ bufname([{buf}]) *bufname()*
|
||||
|
||||
The result is the name of a buffer. Mostly as it is displayed
|
||||
by the `:ls` command, but not using special names such as
|
||||
"[No Name]".
|
||||
"[No Name]". If the buffer represents a directory, the name
|
||||
ends with a path separator, unless it was changed by |:file| or
|
||||
|nvim_buf_set_name()|.
|
||||
If {buf} is omitted the current buffer is used.
|
||||
If {buf} is a Number, that buffer number's name is given.
|
||||
Number zero is the alternate buffer for the current window.
|
||||
|
||||
@@ -158,8 +158,7 @@ end
|
||||
|
||||
---@param buf integer
|
||||
local function open_parent(buf)
|
||||
local path = api.nvim_buf_get_name(buf)
|
||||
-- TODO(barrettruth): simplify after #40552
|
||||
local path = fs.normalize(api.nvim_buf_get_name(buf))
|
||||
local name = encode_name(fs.basename(path)) .. (vim.fn.isdirectory(path) == 1 and '/' or '')
|
||||
navigate(fs.dirname(path))
|
||||
vim.fn.search([[\C\m^\V]] .. vim.fn.escape(name, [[\]]) .. [[\m$]], 'cw')
|
||||
|
||||
3
runtime/lua/vim/_meta/api.gen.lua
generated
3
runtime/lua/vim/_meta/api.gen.lua
generated
@@ -511,6 +511,9 @@ function vim.api.nvim_buf_get_mark(buf, name) end
|
||||
|
||||
--- Gets the full/absolute filepath of the buffer, or the buffer name for non-file buffers.
|
||||
---
|
||||
--- If the buffer represents a directory, the name ends with a path separator,
|
||||
--- unless it was changed by `:file` or `nvim_buf_set_name()`.
|
||||
---
|
||||
--- @param buf integer Buffer id, or 0 for current buffer
|
||||
--- @return string # Buffer name
|
||||
function vim.api.nvim_buf_get_name(buf) end
|
||||
|
||||
4
runtime/lua/vim/_meta/vimfn.gen.lua
generated
4
runtime/lua/vim/_meta/vimfn.gen.lua
generated
@@ -605,7 +605,9 @@ function vim.fn.bufloaded(buf) end
|
||||
---
|
||||
--- The result is the name of a buffer. Mostly as it is displayed
|
||||
--- by the `:ls` command, but not using special names such as
|
||||
--- "[No Name]".
|
||||
--- "[No Name]". If the buffer represents a directory, the name
|
||||
--- ends with a path separator, unless it was changed by |:file| or
|
||||
--- |nvim_buf_set_name()|.
|
||||
--- If {buf} is omitted the current buffer is used.
|
||||
--- If {buf} is a Number, that buffer number's name is given.
|
||||
--- Number zero is the alternate buffer for the current window.
|
||||
|
||||
@@ -3254,6 +3254,9 @@ function M.match(args)
|
||||
end
|
||||
|
||||
if name then
|
||||
if name:sub(-1) == '/' then
|
||||
return 'directory'
|
||||
end
|
||||
name = normalize_path(name)
|
||||
|
||||
local ok_abspath, path = pcall(vim.fs.abspath, name)
|
||||
|
||||
@@ -942,6 +942,9 @@ void nvim_buf_del_var(Buffer buf, String name, Error *err)
|
||||
|
||||
/// Gets the full/absolute filepath of the buffer, or the buffer name for non-file buffers.
|
||||
///
|
||||
/// If the buffer represents a directory, the name ends with a path separator,
|
||||
/// unless it was changed by |:file| or |nvim_buf_set_name()|.
|
||||
///
|
||||
/// @param buf Buffer id, or 0 for current buffer
|
||||
/// @param[out] err Error details, if any
|
||||
/// @return Buffer name
|
||||
|
||||
@@ -404,6 +404,19 @@ int open_buffer(bool read_stdin, exarg_T *eap, int flags_arg)
|
||||
curbuf->b_flags |= BF_READERR;
|
||||
}
|
||||
|
||||
// Directory buf:
|
||||
// readfile() returns NOTDONE without firing BufReadPost when it did not read a
|
||||
// file (e.g. a directory). Since BufReadPost is what normally runs filetype
|
||||
// detection, do it here so FileType fires before the BufEnter below.
|
||||
if (retval == NOTDONE && *curbuf->b_p_ft == NUL
|
||||
&& curbuf->b_ffname != NULL
|
||||
&& after_pathsep(curbuf->b_ffname,
|
||||
curbuf->b_ffname + strlen(curbuf->b_ffname))) {
|
||||
if (augroup_exists("filetypedetect")) {
|
||||
do_doautocmd("filetypedetect BufRead", false, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// Need to update automatic folding. Do this before the autocommands,
|
||||
// they may use the fold info.
|
||||
foldUpdateAll(curwin);
|
||||
@@ -2022,6 +2035,7 @@ buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags)
|
||||
}
|
||||
|
||||
if (ffname != NULL) {
|
||||
assert(sfname != NULL);
|
||||
buf->b_ffname = ffname;
|
||||
buf->b_sfname = xstrdup(sfname);
|
||||
}
|
||||
@@ -3597,6 +3611,9 @@ int append_arg_number(win_T *wp, char *buf, size_t buflen)
|
||||
}
|
||||
|
||||
/// Make "*ffname" a full file name, set "*sfname" to "*ffname" if not NULL.
|
||||
/// For a directory the resulting "*ffname" gets a trailing path separator. When
|
||||
/// "*sfname" already ends with a separator the final component is not resolved,
|
||||
/// so the symbolic link path is preserved.
|
||||
/// "*ffname" becomes a pointer to allocated memory (or NULL).
|
||||
/// When resolving a link both "*sfname" and "*ffname" will point to the same
|
||||
/// allocated memory.
|
||||
@@ -3610,7 +3627,30 @@ void fname_expand(buf_T *buf, char **ffname, char **sfname)
|
||||
if (*sfname == NULL) { // no short file name given, use ffname
|
||||
*sfname = *ffname;
|
||||
}
|
||||
*ffname = fix_fname((*ffname)); // expand to full path
|
||||
*ffname = fix_fname(*ffname); // expand to full path
|
||||
if (*ffname == NULL) {
|
||||
*sfname = NULL;
|
||||
return;
|
||||
}
|
||||
// Preserve a symbolic link path for directory buffers. fix_fname("link/")
|
||||
// resolves to the target, so re-expand without the trailing separator: if
|
||||
// "link" is a symbolic link to directory "target", ":edit link/" produces
|
||||
// ".../link/", not ".../target/".
|
||||
if (os_isdir(*ffname)) {
|
||||
char *name = xstrdup(*sfname);
|
||||
size_t name_len = strlen(name);
|
||||
if (name_len > 0 && after_pathsep(name, name + name_len)
|
||||
&& name + name_len > get_past_head(name)) {
|
||||
*path_tail_with_sep(name) = NUL;
|
||||
}
|
||||
char *full = fix_fname(name);
|
||||
xfree(name);
|
||||
if (full != NULL) {
|
||||
xfree(*ffname);
|
||||
*ffname = full;
|
||||
}
|
||||
*ffname = concat_fnames_realloc(*ffname, "", true);
|
||||
}
|
||||
|
||||
#ifdef MSWIN
|
||||
if (!buf->b_p_bin) {
|
||||
|
||||
@@ -823,7 +823,9 @@ M.funcs = {
|
||||
desc = [=[
|
||||
The result is the name of a buffer. Mostly as it is displayed
|
||||
by the `:ls` command, but not using special names such as
|
||||
"[No Name]".
|
||||
"[No Name]". If the buffer represents a directory, the name
|
||||
ends with a path separator, unless it was changed by |:file| or
|
||||
|nvim_buf_set_name()|.
|
||||
If {buf} is omitted the current buffer is used.
|
||||
If {buf} is a Number, that buffer number's name is given.
|
||||
Number zero is the alternate buffer for the current window.
|
||||
|
||||
@@ -389,7 +389,7 @@ int path_fnamecmp(const char *fname1, const char *fname2)
|
||||
const size_t len2 = strlen(fname2);
|
||||
return path_fnamencmp(fname1, fname2, MAX(len1, len2));
|
||||
#else
|
||||
return mb_strcmp_ic((bool)p_fic, fname1, fname2);
|
||||
return pathcmp(fname1, fname2, -1);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -2392,6 +2392,32 @@ describe('api/buf', function()
|
||||
os.remove(new_name)
|
||||
end)
|
||||
|
||||
it('directory buffer-name always ends with slash', function()
|
||||
local cwd = t.fix_slashes(fn.getcwd())
|
||||
local dir = 'Xtest_dir_name'
|
||||
t.mkdir(dir)
|
||||
finally(function()
|
||||
n.rmdir(dir)
|
||||
end)
|
||||
api.nvim_buf_set_name(0, dir)
|
||||
eq(cwd .. '/' .. dir .. '/', t.fix_slashes(api.nvim_buf_get_name(0)))
|
||||
end)
|
||||
|
||||
it('directory buffer-name preserves symbolic link path', function()
|
||||
t.skip(t.is_os('win'), 'N/A for Windows')
|
||||
local cwd = t.fix_slashes(fn.getcwd())
|
||||
local target = 'Xtest_dir_target'
|
||||
local link = 'Xtest_dir_link'
|
||||
t.mkdir(target)
|
||||
assert(vim.uv.fs_symlink(assert(vim.uv.fs_realpath(target)), link, { dir = true }))
|
||||
finally(function()
|
||||
os.remove(link)
|
||||
n.rmdir(target)
|
||||
end)
|
||||
api.nvim_buf_set_name(0, link .. '/')
|
||||
eq(cwd .. '/' .. link .. '/', t.fix_slashes(api.nvim_buf_get_name(0)))
|
||||
end)
|
||||
|
||||
describe("with 'autochdir'", function()
|
||||
local topdir
|
||||
local oldbuf
|
||||
|
||||
@@ -166,6 +166,26 @@ describe(':mksession', function()
|
||||
eq(cwd_dir .. '/' .. tmpfile_base .. '2', fn.expand('%:p'))
|
||||
end)
|
||||
|
||||
it('restores symlinked directory buffer names', function()
|
||||
skip(is_os('win'), 'N/A for Windows')
|
||||
|
||||
local cwd_dir = t.fix_slashes(fn.getcwd())
|
||||
local link_dir = file_prefix .. '-link'
|
||||
assert(vim.uv.fs_symlink(assert(vim.uv.fs_realpath(tab_dir)), link_dir, { dir = true }))
|
||||
finally(function()
|
||||
os.remove(link_dir)
|
||||
end)
|
||||
|
||||
command('set sessionoptions=buffers')
|
||||
command('edit ' .. link_dir)
|
||||
local expected = cwd_dir .. '/' .. link_dir .. '/'
|
||||
eq(expected, api.nvim_buf_get_name(0))
|
||||
command('mksession ' .. session_file)
|
||||
command('%bwipeout!')
|
||||
command('source ' .. session_file)
|
||||
eq(expected, api.nvim_buf_get_name(0))
|
||||
end)
|
||||
|
||||
it('restores CWD for :terminal buffers #11288', function()
|
||||
local cwd_dir = fn.fnamemodify('.', ':p:~'):gsub([[[\/]*$]], '')
|
||||
cwd_dir = t.fix_slashes(cwd_dir) -- :mksession always uses unix slashes.
|
||||
|
||||
@@ -73,6 +73,15 @@ describe('vim.filetype', function()
|
||||
)
|
||||
end)
|
||||
|
||||
it('detects directory filenames', function()
|
||||
eq(
|
||||
'directory',
|
||||
exec_lua(function()
|
||||
return vim.filetype.match({ filename = '/tmp/example/' })
|
||||
end)
|
||||
)
|
||||
end)
|
||||
|
||||
it('works without defined g:ft_ignore_pat', function()
|
||||
local match_opts = { filename = 'unknown-ft', buf = api.nvim_create_buf(false, true) }
|
||||
eq(
|
||||
|
||||
@@ -37,8 +37,9 @@ local function bufopt(name)
|
||||
end
|
||||
|
||||
local function assert_directory(path)
|
||||
eq(path, api.nvim_buf_get_name(0))
|
||||
eq(path, fn.bufname('%'))
|
||||
local ffname = path:sub(-1) == '/' and path or path .. '/'
|
||||
eq(ffname, api.nvim_buf_get_name(0))
|
||||
eq(path, vim.fs.normalize(fn.bufname('%')))
|
||||
eq('directory', bufopt('filetype'))
|
||||
eq(true, bufopt('buflisted'))
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user