aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/lua.txt19
-rw-r--r--runtime/filetype.lua10
-rw-r--r--runtime/lua/vim/filetype.lua127
-rw-r--r--runtime/lua/vim/filetype/detect.lua83
-rw-r--r--test/functional/lua/filetype_spec.lua18
5 files changed, 157 insertions, 100 deletions
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index bdd2e6ff8e..c9fd3d2786 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -2011,7 +2011,10 @@ add({filetypes}) *vim.filetype.add()*
takes the full path and buffer number of the file as arguments
(along with captures from the matched pattern, if any) and
should return a string that will be used as the buffer's
- filetype.
+ filetype. Optionally, the function can return a second
+ function value which, when called, modifies the state of the
+ buffer. This can be used to, for example, set
+ filetype-specific buffer variables.
Filename patterns can specify an optional priority to resolve
cases when a file path matches multiple patterns. Higher
@@ -2030,7 +2033,10 @@ add({filetypes}) *vim.filetype.add()*
foo = "fooscript",
bar = function(path, bufnr)
if some_condition() then
- return "barscript"
+ return "barscript", function(bufnr)
+ -- Set a buffer variable
+ vim.b[bufnr].barscript_version = 2
+ end
end
return "bar"
end,
@@ -2059,7 +2065,7 @@ add({filetypes}) *vim.filetype.add()*
(see example).
match({name}, {bufnr}) *vim.filetype.match()*
- Set the filetype for the given buffer from a file name.
+ Find the filetype for the given filename and buffer.
Parameters: ~
{name} (string) File name (can be an absolute or
@@ -2067,6 +2073,13 @@ match({name}, {bufnr}) *vim.filetype.match()*
{bufnr} (number|nil) The buffer to set the filetype for.
Defaults to the current buffer.
+ Return: ~
+ (string|nil) If a match was found, the matched filetype.
+ (function|nil) A function that modifies buffer state when
+ called (for example, to set some filetype specific buffer
+ variables). The function accepts a buffer number as its
+ only argument.
+
==============================================================================
Lua module: keymap *lua-keymap*
diff --git a/runtime/filetype.lua b/runtime/filetype.lua
index d2510c5494..b002b8971b 100644
--- a/runtime/filetype.lua
+++ b/runtime/filetype.lua
@@ -11,8 +11,14 @@ vim.api.nvim_create_augroup('filetypedetect', { clear = false })
vim.api.nvim_create_autocmd({ 'BufRead', 'BufNewFile' }, {
group = 'filetypedetect',
- callback = function()
- vim.filetype.match(vim.fn.expand('<afile>'))
+ callback = function(args)
+ local ft, on_detect = vim.filetype.match(args.file, args.buf)
+ if ft then
+ vim.api.nvim_buf_set_option(args.buf, 'filetype', ft)
+ if on_detect then
+ on_detect(args.buf)
+ end
+ end
end,
})
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index c091e2f35c..536580c604 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -26,7 +26,7 @@ end
---@private
local function getline(bufnr, start_lnum, end_lnum)
end_lnum = end_lnum or start_lnum
- local lines = vim.api.nvim_buf_get_lines(bufnr, start_lnum - 1, end_lnum, false)
+ local lines = api.nvim_buf_get_lines(bufnr, start_lnum - 1, end_lnum, false)
return table.concat(lines) or ''
end
@@ -40,9 +40,9 @@ end
function M.getlines(bufnr, start_lnum, end_lnum)
if not end_lnum then
-- Return a single line as a string
- return vim.api.nvim_buf_get_lines(bufnr, start_lnum - 1, start_lnum, false)[1]
+ return api.nvim_buf_get_lines(bufnr, start_lnum - 1, start_lnum, false)[1]
end
- return vim.api.nvim_buf_get_lines(bufnr, start_lnum - 1, end_lnum, false)
+ return api.nvim_buf_get_lines(bufnr, start_lnum - 1, end_lnum, false)
end
---@private
@@ -1513,12 +1513,14 @@ local filename = {
['/.pinforc'] = 'pinfo',
['.povrayrc'] = 'povini',
['printcap'] = function(path, bufnr)
- vim.b[bufnr].ptcap_type = 'print'
- return 'ptcap'
+ return 'ptcap', function(b)
+ vim.b[b].ptcap_type = 'print'
+ end
end,
['termcap'] = function(path, bufnr)
- vim.b[bufnr].ptcap_type = 'term'
- return 'ptcap'
+ return 'ptcap', function(b)
+ vim.b[b].ptcap_type = 'term'
+ end
end,
['.procmailrc'] = 'procmail',
['.procmail'] = 'procmail',
@@ -1604,12 +1606,14 @@ local filename = {
['xdm-config'] = 'xdefaults',
['.Xdefaults'] = 'xdefaults',
['xorg.conf'] = function(path, bufnr)
- vim.b[bufnr].xf86conf_xfree86_version = 4
- return 'xf86conf'
+ return 'xf86conf', function(b)
+ vim.b[b].xf86conf_xfree86_version = 4
+ end
end,
['xorg.conf-4'] = function(path, bufnr)
- vim.b[bufnr].xf86conf_xfree86_version = 4
- return 'xf86conf'
+ return 'xf86conf', function(b)
+ vim.b[b].xf86conf_xfree86_version = 4
+ end
end,
['/etc/xinetd.conf'] = 'xinetd',
fglrxrc = 'xml',
@@ -1662,7 +1666,7 @@ local filename = {
bashrc = function(path, bufnr)
return require('vim.filetype.detect').sh(path, bufnr, 'bash')
end,
- crontab = starsetf('crontab'),
+ crontab = 'crontab',
['csh.cshrc'] = function(path, bufnr)
return require('vim.filetype.detect').csh(path, bufnr)
end,
@@ -1682,7 +1686,7 @@ local filename = {
return require('vim.filetype.detect').shell(path, bufnr, 'tcsh')
end,
['XF86Config'] = function(path, bufnr)
- return require('vim.filetype.detect').xf86conf(bufnr)
+ return require('vim.filetype.detect').xfree86(bufnr)
end,
-- END FILENAME
}
@@ -1830,8 +1834,9 @@ local pattern = {
['.*/etc/protocols'] = 'protocols',
['.*printcap.*'] = starsetf(function(path, bufnr)
if vim.fn.did_filetype() == 0 then
- vim.b[bufnr].ptcap_type = 'print'
- return 'ptcap'
+ return 'ptcap', function(b)
+ vim.b[b].ptcap_type = 'print'
+ end
end
end),
['.*baseq[2-3]/.*%.cfg'] = 'quake',
@@ -1881,8 +1886,9 @@ local pattern = {
['.*/%.config/systemd/user/%.#.*'] = 'systemd',
['.*termcap.*'] = starsetf(function(path, bufnr)
if vim.fn.did_filetype() == 0 then
- vim.b[bufnr].ptcap_type = 'term'
- return 'ptcap'
+ return 'ptcap', function(b)
+ vim.b[b].ptcap_type = 'term'
+ end
end
end),
['.*%.t%.html'] = 'tilde',
@@ -1966,12 +1972,14 @@ local pattern = {
['.*%.vhdl_[0-9].*'] = starsetf('vhdl'),
['.*/%.fvwm/.*'] = starsetf('fvwm'),
['.*fvwmrc.*'] = starsetf(function(path, bufnr)
- vim.b[bufnr].fvwm_version = 1
- return 'fvwm'
+ return 'fvwm', function(b)
+ vim.b[b].fvwm_version = 1
+ end
end),
['.*fvwm95.*%.hook'] = starsetf(function(path, bufnr)
- vim.b[bufnr].fvwm_version = 1
- return 'fvwm'
+ return 'fvwm', function(b)
+ vim.b[b].fvwm_version = 1
+ end
end),
['.*/%.gitconfig%.d/.*'] = starsetf('gitconfig'),
['.*/Xresources/.*'] = starsetf('xdefaults'),
@@ -2117,17 +2125,18 @@ local pattern = {
['.*/queries/.*%.scm'] = 'query', -- tree-sitter queries
['.*,v'] = 'rcs',
['.*/xorg%.conf%.d/.*%.conf'] = function(path, bufnr)
- vim.b[bufnr].xf86conf_xfree86_version = 4
- return 'xf86config'
+ return 'xf86config', function(b)
+ vim.b[b].xf86conf_xfree86_version = 4
+ end
end,
-- Increase priority to run before the pattern below
- ['XF86Config%-4'] = starsetf(function(path, bufnr)
- vim.b[bufnr].xf86conf_xfree86_version = 4
- return 'xf86config'
+ ['XF86Config%-4.*'] = starsetf(function(path, bufnr)
+ return 'xf86conf', function(b)
+ vim.b[b].xf86conf_xfree86_version = 4
+ end
end, { priority = -math.huge + 1 }),
['XF86Config.*'] = starsetf(function(path, bufnr)
- vim.b[bufnr].xf86conf_xfree86_version = 4
- return require('vim.filetype.detect').xf86conf(bufnr)
+ return require('vim.filetype.detect').xfree86(bufnr)
end),
['[cC]hange[lL]og.*'] = starsetf(function(path, bufnr)
local line = getline(bufnr, 1):lower()
@@ -2141,8 +2150,9 @@ local pattern = {
if vim.fn.fnamemodify(path, ':e') == 'm4' then
return 'fvwm2m4'
else
- vim.b[bufnr].fvwm_version = 2
- return 'fvwm'
+ return 'fvwm', function(b)
+ vim.b[b].fvwm_version = 2
+ end
end
end),
['.*%.[Ll][Oo][Gg]'] = function(path, bufnr)
@@ -2220,7 +2230,9 @@ end
--- filetype directly) or a function. If a function, it takes the full path and
--- buffer number of the file as arguments (along with captures from the matched
--- pattern, if any) and should return a string that will be used as the
---- buffer's filetype.
+--- buffer's filetype. Optionally, the function can return a second function
+--- value which, when called, modifies the state of the buffer. This can be used
+--- to, for example, set filetype-specific buffer variables.
---
--- Filename patterns can specify an optional priority to resolve cases when a
--- file path matches multiple patterns. Higher priorities are matched first.
@@ -2238,7 +2250,10 @@ end
--- foo = "fooscript",
--- bar = function(path, bufnr)
--- if some_condition() then
---- return "barscript"
+--- return "barscript", function(bufnr)
+--- -- Set a buffer variable
+--- vim.b[bufnr].barscript_version = 2
+--- end
--- end
--- return "bar"
--- end,
@@ -2283,13 +2298,13 @@ end
---@private
local function dispatch(ft, path, bufnr, ...)
+ local on_detect
if type(ft) == 'function' then
- ft = ft(path, bufnr, ...)
+ ft, on_detect = ft(path, bufnr, ...)
end
if type(ft) == 'string' then
- api.nvim_buf_set_option(bufnr, 'filetype', ft)
- return true
+ return ft, on_detect
end
-- Any non-falsey value (that is, anything other than 'nil' or 'false') will
@@ -2314,11 +2329,20 @@ local function match_pattern(name, path, tail, pat)
return matches
end
---- Set the filetype for the given buffer from a file name.
+--- Find the filetype for the given filename and buffer.
---
---@param name string File name (can be an absolute or relative path)
---@param bufnr number|nil The buffer to set the filetype for. Defaults to the current buffer.
+---@return string|nil If a match was found, the matched filetype.
+---@return function|nil A function that modifies buffer state when called (for example, to set some
+--- filetype specific buffer variables). The function accepts a buffer number as
+--- its only argument.
function M.match(name, bufnr)
+ vim.validate({
+ name = { name, 's' },
+ bufnr = { bufnr, 'n', true },
+ })
+
-- When fired from the main filetypedetect autocommand the {bufnr} argument is omitted, so we use
-- the current buffer. The {bufnr} argument is provided to allow extensibility in case callers
-- wish to perform filetype detection on buffers other than the current one.
@@ -2326,16 +2350,20 @@ function M.match(name, bufnr)
name = normalize_path(name)
+ local ft, on_detect
+
-- First check for the simple case where the full path exists as a key
local path = vim.fn.resolve(vim.fn.fnamemodify(name, ':p'))
- if dispatch(filename[path], path, bufnr) then
- return
+ ft, on_detect = dispatch(filename[path], path, bufnr)
+ if ft then
+ return ft, on_detect
end
-- Next check against just the file name
local tail = vim.fn.fnamemodify(name, ':t')
- if dispatch(filename[tail], path, bufnr) then
- return
+ ft, on_detect = dispatch(filename[tail], path, bufnr)
+ if ft then
+ return ft, on_detect
end
-- Next, check the file path against available patterns with non-negative priority
@@ -2348,19 +2376,21 @@ function M.match(name, bufnr)
break
end
- local ft = v[k][1]
+ local filetype = v[k][1]
local matches = match_pattern(name, path, tail, k)
if matches then
- if dispatch(ft, path, bufnr, matches) then
- return
+ ft, on_detect = dispatch(filetype, path, bufnr, matches)
+ if ft then
+ return ft, on_detect
end
end
end
-- Next, check file extension
local ext = vim.fn.fnamemodify(name, ':e')
- if dispatch(extension[ext], path, bufnr) then
- return
+ ft, on_detect = dispatch(extension[ext], path, bufnr)
+ if ft then
+ return ft, on_detect
end
-- Finally, check patterns with negative priority
@@ -2368,11 +2398,12 @@ function M.match(name, bufnr)
local v = pattern_sorted[i]
local k = next(v)
- local ft = v[k][1]
+ local filetype = v[k][1]
local matches = match_pattern(name, path, tail, k)
if matches then
- if dispatch(ft, path, bufnr, matches) then
- return
+ ft, on_detect = dispatch(filetype, path, bufnr, matches)
+ if ft then
+ return ft, on_detect
end
end
end
diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua
index 2cc75b026e..7e5ed0f4d1 100644
--- a/runtime/lua/vim/filetype/detect.lua
+++ b/runtime/lua/vim/filetype/detect.lua
@@ -27,24 +27,22 @@ local matchregex = vim.filetype.matchregex
-- This function checks for the kind of assembly that is wanted by the user, or
-- can be detected from the first five lines of the file.
function M.asm(bufnr)
- -- Make sure b:asmsyntax exists
- if not vim.b[bufnr].asmsyntax then
- vim.b[bufnr].asmsyntax = ''
- end
-
- if vim.b[bufnr].asmsyntax == '' then
- M.asm_syntax(bufnr)
+ local syntax = vim.b[bufnr].asmsyntax
+ if not syntax or syntax == '' then
+ syntax = M.asm_syntax(bufnr)
end
-- If b:asmsyntax still isn't set, default to asmsyntax or GNU
- if vim.b[bufnr].asmsyntax == '' then
+ if not syntax or syntax == '' then
if vim.g.asmsyntax and vim.g.asmsyntax ~= 0 then
- vim.b[bufnr].asmsyntax = vim.g.asmsyntax
+ syntax = vim.g.asmsyntax
else
- vim.b[bufnr].asmsyntax = 'asm'
+ syntax = 'asm'
end
end
- return vim.fn.fnameescape(vim.b[bufnr].asmsyntax)
+ return syntax, function(b)
+ vim.b[b].asmsyntax = syntax
+ end
end
-- Checks the first 5 lines for a asmsyntax=foo override.
@@ -53,9 +51,9 @@ function M.asm_syntax(bufnr)
local lines = table.concat(getlines(bufnr, 1, 5), ' '):lower()
local match = lines:match('%sasmsyntax=([a-zA-Z0-9]+)%s')
if match then
- vim.b['asmsyntax'] = match
+ return match
elseif findany(lines, { '%.title', '%.ident', '%.macro', '%.subtitle', '%.library' }) then
- vim.b['asmsyntax'] = 'vmasm'
+ return 'vmasm'
end
end
@@ -376,12 +374,13 @@ function M.inc(bufnr)
elseif findany(lines, { '^%s{', '^%s%(%*' }) or matchregex(lines, pascal_keywords) then
return 'pascal'
else
- M.asm_syntax(bufnr)
- if vim.b[bufnr].asm_syntax then
- return vim.fn.fnameescape(vim.b[bufnr].asm_syntax)
- else
+ local syntax = M.asm_syntax(bufnr)
+ if not syntax or syntax == '' then
return 'pov'
end
+ return syntax, function(b)
+ vim.b[b].asmsyntax = syntax
+ end
end
end
@@ -778,6 +777,8 @@ function M.sh(path, bufnr, name)
return
end
+ local on_detect
+
if matchregex(name, [[\<csh\>]]) then
-- Some .sh scripts contain #!/bin/csh.
return M.shell(path, bufnr, 'csh')
@@ -788,19 +789,25 @@ function M.sh(path, bufnr, name)
elseif matchregex(name, [[\<zsh\>]]) then
return M.shell(path, bufnr, 'zsh')
elseif matchregex(name, [[\<ksh\>]]) then
- vim.b[bufnr].is_kornshell = 1
- vim.b[bufnr].is_bash = nil
- vim.b[bufnr].is_sh = nil
+ on_detect = function(b)
+ vim.b[b].is_kornshell = 1
+ vim.b[b].is_bash = nil
+ vim.b[b].is_sh = nil
+ end
elseif vim.g.bash_is_sh or matchregex(name, [[\<bash\>]]) or matchregex(name, [[\<bash2\>]]) then
- vim.b[bufnr].is_bash = 1
- vim.b[bufnr].is_kornshell = nil
- vim.b[bufnr].is_sh = nil
+ on_detect = function(b)
+ vim.b[b].is_bash = 1
+ vim.b[b].is_kornshell = nil
+ vim.b[b].is_sh = nil
+ end
elseif matchregex(name, [[\<sh\>]]) then
- vim.b[bufnr].is_sh = 1
- vim.b[bufnr].is_kornshell = nil
- vim.b[bufnr].is_bash = nil
+ on_detect = function(b)
+ vim.b[b].is_sh = 1
+ vim.b[b].is_kornshell = nil
+ vim.b[b].is_bash = nil
+ end
end
- return M.shell(path, bufnr, 'sh')
+ return M.shell(path, bufnr, 'sh'), on_detect
end
-- For shell-like file types, check for an "exec" command hidden in a comment, as used for Tcl.
@@ -918,9 +925,11 @@ function M.xml(bufnr)
line = line:lower()
local is_docbook5 = line:find([[ xmlns="http://docbook.org/ns/docbook"]])
if is_docbook4 or is_docbook5 then
- vim.b[bufnr].docbk_type = 'xml'
- vim.b[bufnr].docbk_ver = is_docbook4 and 4 or 5
- return 'docbk'
+ return 'docbk',
+ function(b)
+ vim.b[b].docbk_type = 'xml'
+ vim.b[b].docbk_ver = is_docbook4 and 4 or 5
+ end
end
if line:find([[xmlns:xbl="http://www.mozilla.org/xbl"]]) then
return 'xbl'
@@ -954,9 +963,10 @@ function M.sgml(bufnr)
if lines:find('linuxdoc') then
return 'smgllnx'
elseif lines:find('<!DOCTYPE.*DocBook') then
- vim.b[bufnr].docbk_type = 'sgml'
- vim.b[bufnr].docbk_ver = 4
- return 'docbk'
+ return 'docbk', function(b)
+ vim.b[b].docbk_type = 'sgml'
+ vim.b[b].docbk_ver = 4
+ end
else
return 'sgml'
end
@@ -1049,10 +1059,13 @@ end
-- XFree86 config
function M.xfree86(bufnr)
local line = getlines(bufnr, 1)
+ local on_detect
if matchregex(line, [[\<XConfigurator\>]]) then
- vim.b[bufnr].xf86conf_xfree86_version = 3
- return 'xf86conf'
+ on_detect = function(b)
+ vim.b[b].xf86conf_xfree86_version = 3
+ end
end
+ return 'xf86conf', on_detect
end
-- luacheck: pop
diff --git a/test/functional/lua/filetype_spec.lua b/test/functional/lua/filetype_spec.lua
index e83dd2eb24..d0cef53c4b 100644
--- a/test/functional/lua/filetype_spec.lua
+++ b/test/functional/lua/filetype_spec.lua
@@ -23,8 +23,7 @@ describe('vim.filetype', function()
rs = 'radicalscript',
},
})
- vim.filetype.match('main.rs')
- return vim.bo.filetype
+ return vim.filetype.match('main.rs')
]])
end)
@@ -38,8 +37,7 @@ describe('vim.filetype', function()
['main.rs'] = 'somethingelse',
},
})
- vim.filetype.match('main.rs')
- return vim.bo.filetype
+ return vim.filetype.match('main.rs')
]])
end)
@@ -50,8 +48,7 @@ describe('vim.filetype', function()
['s_O_m_e_F_i_l_e'] = 'nim',
},
})
- vim.filetype.match('s_O_m_e_F_i_l_e')
- return vim.bo.filetype
+ return vim.filetype.match('s_O_m_e_F_i_l_e')
]])
eq('dosini', exec_lua([[
@@ -62,8 +59,7 @@ describe('vim.filetype', function()
[root .. '/.config/fun/config'] = 'dosini',
},
})
- vim.filetype.match(root .. '/.config/fun/config')
- return vim.bo.filetype
+ return vim.filetype.match(root .. '/.config/fun/config')
]], root))
end)
@@ -76,8 +72,7 @@ describe('vim.filetype', function()
['~/blog/.*%.txt'] = 'markdown',
}
})
- vim.filetype.match('~/blog/why_neovim_is_awesome.txt')
- return vim.bo.filetype
+ return vim.filetype.match('~/blog/why_neovim_is_awesome.txt')
]], root))
end)
@@ -92,8 +87,7 @@ describe('vim.filetype', function()
end,
}
})
- vim.filetype.match('relevant_to_me')
- return vim.bo.filetype
+ return vim.filetype.match('relevant_to_me')
]])
end)
end)