mirror of
https://github.com/neovim/neovim.git
synced 2025-12-16 03:15:39 +00:00
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)`
This commit is contained in:
@@ -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.
|
||||
---
|
||||
---@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.
|
||||
--- 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 })
|
||||
---
|
||||
--- -- 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
|
||||
|
||||
name = normalize_path(name)
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user