diff options
author | Gregory Anders <8965202+gpanders@users.noreply.github.com> | 2022-06-09 13:12:36 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-06-09 13:12:36 -0600 |
commit | 58323b1fe2e494cf6a75f108780e21c08996c08e (patch) | |
tree | a301a6d047003efa3835460a793a87d4f205f785 | |
parent | 28e43881b74b800fa37957e77b5e56994e1120cd (diff) | |
download | rneovim-58323b1fe2e494cf6a75f108780e21c08996c08e.tar.gz rneovim-58323b1fe2e494cf6a75f108780e21c08996c08e.tar.bz2 rneovim-58323b1fe2e494cf6a75f108780e21c08996c08e.zip |
feat(filetype): remove side effects from vim.filetype.match (#18894)
Many filetypes from filetype.vim set buffer-local variables, meaning
vim.filetype.match cannot be used without side effects. Instead of
setting these buffer-local variables in the filetype detection functions
themselves, have vim.filetype.match return an optional function value
that, when called, sets these variables. This allows vim.filetype.match
to work without side effects.
-rw-r--r-- | runtime/doc/lua.txt | 19 | ||||
-rw-r--r-- | runtime/filetype.lua | 10 | ||||
-rw-r--r-- | runtime/lua/vim/filetype.lua | 127 | ||||
-rw-r--r-- | runtime/lua/vim/filetype/detect.lua | 83 | ||||
-rw-r--r-- | test/functional/lua/filetype_spec.lua | 18 |
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) |