aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregory Anders <8965202+gpanders@users.noreply.github.com>2022-06-26 10:41:20 -0600
committerGitHub <noreply@github.com>2022-06-26 18:41:20 +0200
commitf3ce06cfa139ca3fb142cf5adf96a2ecc4d8f551 (patch)
tree2454132cd59362a4ad0b232af644ec10917c37ad
parentae3e371303a3f806e0deed3c57ed3892cbfc4f22 (diff)
downloadrneovim-f3ce06cfa139ca3fb142cf5adf96a2ecc4d8f551.tar.gz
rneovim-f3ce06cfa139ca3fb142cf5adf96a2ecc4d8f551.tar.bz2
rneovim-f3ce06cfa139ca3fb142cf5adf96a2ecc4d8f551.zip
refactor(filetype)!: allow vim.filetype.match to use different strategies (#18895)
This enables vim.filetype.match to match based on a buffer (most accurate) or simply a filename or file contents, which are less accurate but may still be useful for some scenarios. When matching based on a buffer, the buffer's name and contents are both used to do full filetype matching. When using a filename, if the file exists the file is loaded into a buffer and full filetype detection is performed. If the file does not exist then filetype matching is only performed against the filename itself. Content-based matching does the equivalent of scripts.vim, and matches solely based on file contents without any information from the name of the file itself (e.g. for shebangs). BREAKING CHANGE: use `vim.filetype.match({buf = bufnr})` instead of `vim.filetype.match(name, bufnr)`
-rw-r--r--runtime/doc/lua.txt43
-rw-r--r--runtime/filetype.lua2
-rw-r--r--runtime/lua/vim/filetype.lua88
-rw-r--r--test/functional/lua/filetype_spec.lua15
4 files changed, 117 insertions, 31 deletions
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 9f8d4a8479..f40b32c469 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -2064,14 +2064,45 @@ add({filetypes}) *vim.filetype.add()*
{filetypes} (table) A table containing new filetype maps
(see example).
-match({name}, {bufnr}) *vim.filetype.match()*
- Find the filetype for the given filename and buffer.
+match({arg}) *vim.filetype.match()*
+ Perform filetype detection.
+
+ The filetype can be detected using one of three methods:
+ 1. Using an existing buffer
+ 2. Using only a file name
+ 3. Using only file contents
+
+ Of these, option 1 provides the most accurate result as it
+ uses both the buffer's filename and (optionally) the buffer
+ contents. Options 2 and 3 can be used without an existing
+ buffer, but may not always provide a match in cases where the
+ filename (or contents) cannot unambiguously determine the
+ filetype.
+
+ Each of the three options is specified using a key to the
+ single argument of this function. Example:
+>
+
+ -- Using a buffer number
+ vim.filetype.match({ buf = 42 })
+
+ -- Using a filename
+ vim.filetype.match({ filename = "main.lua" })
+
+ -- Using file contents
+ vim.filetype.match({ contents = {"#!/usr/bin/env bash"} })
+<
Parameters: ~
- {name} (string) File name (can be an absolute or
- relative path)
- {bufnr} (number|nil) The buffer to set the filetype for.
- Defaults to the current buffer.
+ {arg} (table) Table specifying which matching strategy to
+ use. It is an error to provide more than one
+ strategy. Accepted keys are:
+ • buf (number): Buffer number to use for matching
+ • filename (string): Filename to use for matching.
+ Note that the file need not actually exist in the
+ filesystem, only the name itself is used.
+ • contents (table): An array of lines representing
+ file contents to use for matching.
Return: ~
(string|nil) If a match was found, the matched filetype.
diff --git a/runtime/filetype.lua b/runtime/filetype.lua
index b002b8971b..35bb31edce 100644
--- a/runtime/filetype.lua
+++ b/runtime/filetype.lua
@@ -12,7 +12,7 @@ vim.api.nvim_create_augroup('filetypedetect', { clear = false })
vim.api.nvim_create_autocmd({ 'BufRead', 'BufNewFile' }, {
group = 'filetypedetect',
callback = function(args)
- local ft, on_detect = vim.filetype.match(args.file, args.buf)
+ local ft, on_detect = vim.filetype.match({ buf = args.buf })
if ft then
vim.api.nvim_buf_set_option(args.buf, 'filetype', ft)
if on_detect then
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index 6c4894208f..3e86159489 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -2047,7 +2047,7 @@ local pattern = {
end
end, { priority = -math.huge + 1 }),
['XF86Config.*'] = starsetf(function(path, bufnr)
- return require('vim.filetype.detect').xfree86(bufnr)
+ return require('vim.filetype.detect').xfree86()
end),
['%.zcompdump.*'] = starsetf('zsh'),
-- .zlog* and zlog*
@@ -2185,17 +2185,24 @@ end
local function dispatch(ft, path, bufnr, ...)
local on_detect
if type(ft) == 'function' then
- ft, on_detect = ft(path, bufnr, ...)
+ if bufnr then
+ ft, on_detect = ft(path, bufnr, ...)
+ else
+ -- If bufnr is nil (meaning we are matching only against the filename), set it to an invalid
+ -- value (-1) and catch any errors from the filetype detection function. If the function tries
+ -- to use the buffer then it will fail, but this enables functions which do not need a buffer
+ -- to still work.
+ local ok
+ ok, ft, on_detect = pcall(ft, path, -1, ...)
+ if not ok then
+ return
+ end
+ end
end
if type(ft) == 'string' then
return ft, on_detect
end
-
- -- Any non-falsey value (that is, anything other than 'nil' or 'false') will
- -- end filetype matching. This is useful for e.g. the dist#ft functions that
- -- return 0, but set the buffer's filetype themselves
- return ft
end
---@private
@@ -2214,29 +2221,74 @@ local function match_pattern(name, path, tail, pat)
return matches
end
---- Find the filetype for the given filename and buffer.
+--- Perform filetype detection.
+---
+--- The filetype can be detected using one of three methods:
+--- 1. Using an existing buffer
+--- 2. Using only a file name
+--- 3. Using only file contents
+---
+--- Of these, option 1 provides the most accurate result as it uses both the buffer's filename and
+--- (optionally) the buffer contents. Options 2 and 3 can be used without an existing buffer, but
+--- may not always provide a match in cases where the filename (or contents) cannot unambiguously
+--- determine the filetype.
+---
+--- Each of the three options is specified using a key to the single argument of this function.
+--- Example:
+---
+--- <pre>
+--- -- Using a buffer number
+--- vim.filetype.match({ buf = 42 })
---
----@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.
+--- -- Using a filename
+--- vim.filetype.match({ filename = "main.lua" })
+---
+--- -- Using file contents
+--- vim.filetype.match({ contents = {"#!/usr/bin/env bash"} })
+--- </pre>
+---
+---@param arg table Table specifying which matching strategy to use. It is an error to provide more
+--- than one strategy. Accepted keys are:
+--- * buf (number): Buffer number to use for matching
+--- * filename (string): Filename to use for matching. Note that the file need not
+--- actually exist in the filesystem, only the name itself is
+--- used.
+--- * contents (table): An array of lines representing file contents to use for
+--- matching.
---@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)
+function M.match(arg)
vim.validate({
- name = { name, 's' },
- bufnr = { bufnr, 'n', true },
+ arg = { arg, 't' },
})
- -- 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.
- bufnr = bufnr or api.nvim_get_current_buf()
+ if not (arg.buf or arg.filename or arg.contents) then
+ error('One of "buf", "filename", or "contents" must be given')
+ end
+
+ if (arg.buf and arg.filename) or (arg.buf and arg.contents) or (arg.filename and arg.contents) then
+ error('Only one of "buf", "filename", or "contents" must be given')
+ end
+
+ local bufnr = arg.buf
+ local name = bufnr and api.nvim_buf_get_name(bufnr) or arg.filename
+ local contents = arg.contents
- name = normalize_path(name)
+ if name then
+ name = normalize_path(name)
+ end
local ft, on_detect
+ if not (bufnr or name) then
+ -- Sanity check: this should not happen
+ assert(contents, 'contents should be non-nil when bufnr and filename are nil')
+ -- TODO: "scripts.lua" content matching
+ return
+ end
+
-- First check for the simple case where the full path exists as a key
local path = vim.fn.resolve(vim.fn.fnamemodify(name, ':p'))
ft, on_detect = dispatch(filename[path], path, bufnr)
diff --git a/test/functional/lua/filetype_spec.lua b/test/functional/lua/filetype_spec.lua
index d0cef53c4b..be57b2db31 100644
--- a/test/functional/lua/filetype_spec.lua
+++ b/test/functional/lua/filetype_spec.lua
@@ -3,6 +3,7 @@ local exec_lua = helpers.exec_lua
local eq = helpers.eq
local clear = helpers.clear
local pathroot = helpers.pathroot
+local command = helpers.command
local root = pathroot()
@@ -23,7 +24,7 @@ describe('vim.filetype', function()
rs = 'radicalscript',
},
})
- return vim.filetype.match('main.rs')
+ return vim.filetype.match({ filename = 'main.rs' })
]])
end)
@@ -37,7 +38,7 @@ describe('vim.filetype', function()
['main.rs'] = 'somethingelse',
},
})
- return vim.filetype.match('main.rs')
+ return vim.filetype.match({ filename = 'main.rs' })
]])
end)
@@ -48,7 +49,7 @@ describe('vim.filetype', function()
['s_O_m_e_F_i_l_e'] = 'nim',
},
})
- return vim.filetype.match('s_O_m_e_F_i_l_e')
+ return vim.filetype.match({ filename = 's_O_m_e_F_i_l_e' })
]])
eq('dosini', exec_lua([[
@@ -59,7 +60,7 @@ describe('vim.filetype', function()
[root .. '/.config/fun/config'] = 'dosini',
},
})
- return vim.filetype.match(root .. '/.config/fun/config')
+ return vim.filetype.match({ filename = root .. '/.config/fun/config' })
]], root))
end)
@@ -72,11 +73,13 @@ describe('vim.filetype', function()
['~/blog/.*%.txt'] = 'markdown',
}
})
- return vim.filetype.match('~/blog/why_neovim_is_awesome.txt')
+ return vim.filetype.match({ filename = '~/blog/why_neovim_is_awesome.txt' })
]], root))
end)
it('works with functions', function()
+ command('new')
+ command('file relevant_to_me')
eq('foss', exec_lua [[
vim.filetype.add({
pattern = {
@@ -87,7 +90,7 @@ describe('vim.filetype', function()
end,
}
})
- return vim.filetype.match('relevant_to_me')
+ return vim.filetype.match({ buf = 0 })
]])
end)
end)