diff options
author | Gregory Anders <8965202+gpanders@users.noreply.github.com> | 2022-06-26 10:41:20 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-06-26 18:41:20 +0200 |
commit | f3ce06cfa139ca3fb142cf5adf96a2ecc4d8f551 (patch) | |
tree | 2454132cd59362a4ad0b232af644ec10917c37ad | |
parent | ae3e371303a3f806e0deed3c57ed3892cbfc4f22 (diff) | |
download | rneovim-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.txt | 43 | ||||
-rw-r--r-- | runtime/filetype.lua | 2 | ||||
-rw-r--r-- | runtime/lua/vim/filetype.lua | 88 | ||||
-rw-r--r-- | test/functional/lua/filetype_spec.lua | 15 |
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) |