perf(filetype): optimize internal data structures

This changes the type for the sorted pattern table from
`vim.filetype.mapping[]` to `vim.filetype.mapping.sorted[]`

E.g. instead of:

```lua
{
  { ['/debian/changelog$'] = {'debchangelog', { parent = '/debian/' } },
  { ['%.git/']             = { detect.git   , { parent = 'git/', priority = -1 } },
}
```

It is now:

```lua
{
  { '/debian/, '/debian/changelog$', 'debchangelog' },
  { 'git/'   , '%.git/'            , detect.git    , -1 },
}
```

Overall this should roughly cut the amount of tables used by 3, and
replaces lots of hash indexes with array indexes.
This commit is contained in:
Lewis Russell
2024-11-07 11:42:08 +00:00
committed by Lewis Russell
parent d0e78b5871
commit ff575b3886

View File

@ -4,15 +4,22 @@ local fn = vim.fn
local M = {} local M = {}
--- @alias vim.filetype.mapfn fun(path:string,bufnr:integer, ...):string?, fun(b:integer)? --- @alias vim.filetype.mapfn fun(path:string,bufnr:integer, ...):string?, fun(b:integer)?
--- @alias vim.filetype.mapopts { parent: string, priority: number } --- @alias vim.filetype.mapopts { priority: number }
--- @alias vim.filetype.maptbl [string|vim.filetype.mapfn, vim.filetype.mapopts] --- @alias vim.filetype.maptbl [string|vim.filetype.mapfn, vim.filetype.mapopts]
--- @alias vim.filetype.mapping.value string|vim.filetype.mapfn|vim.filetype.maptbl --- @alias vim.filetype.mapping.value string|vim.filetype.mapfn|vim.filetype.maptbl
--- @alias vim.filetype.mapping table<string,vim.filetype.mapping.value> --- @alias vim.filetype.mapping table<string,vim.filetype.mapping.value>
--- @class vim.filetype.mapping.sorted
--- @nodoc
--- @field [1] string parent pattern
--- @field [2] string pattern
--- @field [3] string|vim.filetype.mapfn
--- @field [4] integer priority
--- @param ft string|vim.filetype.mapfn --- @param ft string|vim.filetype.mapfn
--- @param opts? vim.filetype.mapopts --- @param priority? integer
--- @return vim.filetype.maptbl --- @return vim.filetype.maptbl
local function starsetf(ft, opts) local function starsetf(ft, priority)
return { return {
function(path, bufnr) function(path, bufnr)
-- Note: when `ft` is a function its return value may be nil. -- Note: when `ft` is a function its return value may be nil.
@ -27,11 +34,8 @@ local function starsetf(ft, opts)
end end
end, end,
{ {
-- Allow setting "parent" to be reused in closures, but don't have default as it will be
-- assigned later from grouping
parent = opts and opts.parent,
-- Starset matches should have lowest priority by default -- Starset matches should have lowest priority by default
priority = (opts and opts.priority) or -math.huge, priority = priority or -math.huge,
}, },
} }
end end
@ -1874,11 +1878,10 @@ local filename = {
} }
-- Re-use closures as much as possible -- Re-use closures as much as possible
local detect_apache_diretc = starsetf('apache', { parent = '/etc/' }) local detect_apache = starsetf('apache')
local detect_apache_dotconf = starsetf('apache', { parent = '%.conf' }) local detect_muttrc = starsetf('muttrc')
local detect_muttrc = starsetf('muttrc', { parent = 'utt' }) local detect_neomuttrc = starsetf('neomuttrc')
local detect_neomuttrc = starsetf('neomuttrc', { parent = 'utt' }) local detect_xkb = starsetf('xkb')
local detect_xkb = starsetf('xkb', { parent = '/usr/' })
---@type table<string,vim.filetype.mapping> ---@type table<string,vim.filetype.mapping>
local pattern = { local pattern = {
@ -1895,14 +1898,14 @@ local pattern = {
['/etc/asound%.conf$'] = 'alsaconf', ['/etc/asound%.conf$'] = 'alsaconf',
['/etc/apache2/sites%-.*/.*%.com$'] = 'apache', ['/etc/apache2/sites%-.*/.*%.com$'] = 'apache',
['/etc/httpd/.*%.conf$'] = 'apache', ['/etc/httpd/.*%.conf$'] = 'apache',
['/etc/apache2/.*%.conf'] = detect_apache_diretc, ['/etc/apache2/.*%.conf'] = detect_apache,
['/etc/apache2/conf%..*/'] = detect_apache_diretc, ['/etc/apache2/conf%..*/'] = detect_apache,
['/etc/apache2/mods%-.*/'] = detect_apache_diretc, ['/etc/apache2/mods%-.*/'] = detect_apache,
['/etc/apache2/sites%-.*/'] = detect_apache_diretc, ['/etc/apache2/sites%-.*/'] = detect_apache,
['/etc/httpd/conf%..*/'] = detect_apache_diretc, ['/etc/httpd/conf%..*/'] = detect_apache,
['/etc/httpd/conf%.d/.*%.conf'] = detect_apache_diretc, ['/etc/httpd/conf%.d/.*%.conf'] = detect_apache,
['/etc/httpd/mods%-.*/'] = detect_apache_diretc, ['/etc/httpd/mods%-.*/'] = detect_apache,
['/etc/httpd/sites%-.*/'] = detect_apache_diretc, ['/etc/httpd/sites%-.*/'] = detect_apache,
['/etc/proftpd/.*%.conf'] = starsetf('apachestyle'), ['/etc/proftpd/.*%.conf'] = starsetf('apachestyle'),
['/etc/proftpd/conf%..*/'] = starsetf('apachestyle'), ['/etc/proftpd/conf%..*/'] = starsetf('apachestyle'),
['/etc/cdrdao%.conf$'] = 'cdrdaoconf', ['/etc/cdrdao%.conf$'] = 'cdrdaoconf',
@ -2193,13 +2196,13 @@ local pattern = {
}, },
['%.conf'] = { ['%.conf'] = {
['^proftpd%.conf'] = starsetf('apachestyle'), ['^proftpd%.conf'] = starsetf('apachestyle'),
['^access%.conf'] = detect_apache_dotconf, ['^access%.conf'] = detect_apache,
['^apache%.conf'] = detect_apache_dotconf, ['^apache%.conf'] = detect_apache,
['^apache2%.conf'] = detect_apache_dotconf, ['^apache2%.conf'] = detect_apache,
['^httpd%.conf'] = detect_apache_dotconf, ['^httpd%.conf'] = detect_apache,
['^httpd%-.*%.conf'] = detect_apache_dotconf, ['^httpd%-.*%.conf'] = detect_apache,
['^proxy%-html%.conf'] = detect_apache_dotconf, ['^proxy%-html%.conf'] = detect_apache,
['^srm%.conf'] = detect_apache_dotconf, ['^srm%.conf'] = detect_apache,
['asterisk/.*%.conf'] = starsetf('asterisk'), ['asterisk/.*%.conf'] = starsetf('asterisk'),
['asterisk.*/.*voicemail%.conf'] = starsetf('asteriskvm'), ['asterisk.*/.*voicemail%.conf'] = starsetf('asteriskvm'),
['^dictd.*%.conf$'] = 'dictdconf', ['^dictd.*%.conf$'] = 'dictdconf',
@ -2372,7 +2375,7 @@ local pattern = {
['/app%-defaults/'] = starsetf('xdefaults'), ['/app%-defaults/'] = starsetf('xdefaults'),
['^Xresources'] = starsetf('xdefaults'), ['^Xresources'] = starsetf('xdefaults'),
-- Increase priority to run before the pattern below -- Increase priority to run before the pattern below
['^XF86Config%-4'] = starsetf(detect.xfree86_v4, { priority = -math.huge + 1 }), ['^XF86Config%-4'] = starsetf(detect.xfree86_v4, -math.huge + 1),
['^XF86Config'] = starsetf(detect.xfree86_v3), ['^XF86Config'] = starsetf(detect.xfree86_v3),
['Xmodmap$'] = 'xmodmap', ['Xmodmap$'] = 'xmodmap',
['xmodmap'] = starsetf('xmodmap'), ['xmodmap'] = starsetf('xmodmap'),
@ -2398,8 +2401,10 @@ local pattern = {
--- @type table<string,vim.filetype.pattern_cache> --- @type table<string,vim.filetype.pattern_cache>
local pattern_lookup = {} local pattern_lookup = {}
--- @param a vim.filetype.mapping.sorted
--- @param b vim.filetype.mapping.sorted
local function compare_by_priority(a, b) local function compare_by_priority(a, b)
return a[next(a)][2].priority > b[next(b)][2].priority return a[4] > b[4]
end end
--- @param pat string --- @param pat string
@ -2409,30 +2414,30 @@ local function parse_pattern(pat)
end end
--- @param t table<string,vim.filetype.mapping> --- @param t table<string,vim.filetype.mapping>
--- @return vim.filetype.mapping[] --- @return vim.filetype.mapping.sorted[]
--- @return vim.filetype.mapping[] --- @return vim.filetype.mapping.sorted[]
local function sort_by_priority(t) local function sort_by_priority(t)
-- Separate patterns with non-negative and negative priority because they -- Separate patterns with non-negative and negative priority because they
-- will be processed separately -- will be processed separately
local pos = {} --- @type vim.filetype.mapping[] local pos = {} --- @type vim.filetype.mapping.sorted[]
local neg = {} --- @type vim.filetype.mapping[] local neg = {} --- @type vim.filetype.mapping.sorted[]
for parent, ft_map in pairs(t) do for parent, ft_map in pairs(t) do
pattern_lookup[parent] = pattern_lookup[parent] or parse_pattern(parent) pattern_lookup[parent] = pattern_lookup[parent] or parse_pattern(parent)
for pat, maptbl in pairs(ft_map) do for pat, maptbl in pairs(ft_map) do
local ft = type(maptbl) == 'table' and maptbl[1] or maptbl local ft_or_fun = type(maptbl) == 'table' and maptbl[1] or maptbl
assert( assert(
type(ft) == 'string' or type(ft) == 'function', type(ft_or_fun) == 'string' or type(ft_or_fun) == 'function',
'Expected string or function for filetype' 'Expected string or function for filetype'
) )
-- Parse pattern for common data and cache it once -- Parse pattern for common data and cache it once
pattern_lookup[pat] = pattern_lookup[pat] or parse_pattern(pat) pattern_lookup[pat] = pattern_lookup[pat] or parse_pattern(pat)
local opts = (type(maptbl) == 'table' and type(maptbl[2]) == 'table') and maptbl[2] or {} --- @type vim.filetype.mapopts?
opts.parent = opts.parent or parent local opts = (type(maptbl) == 'table' and type(maptbl[2]) == 'table') and maptbl[2] or nil
opts.priority = opts.priority or 0 local priority = opts and opts.priority or 0
table.insert(opts.priority >= 0 and pos or neg, { [pat] = { ft, opts } }) table.insert(priority >= 0 and pos or neg, { parent, pat, ft_or_fun, priority })
end end
end end
@ -2643,7 +2648,8 @@ local function match_pattern(name, path, tail, pat, try_all_candidates)
if some_env_missing then if some_env_missing then
return nil return nil
end end
pat, has_slash = expanded, expanded:find('/') ~= nil pat = expanded
has_slash = has_slash or expanded:find('/') ~= nil
end end
-- Try all possible candidates to make parent patterns not depend on slash presence -- Try all possible candidates to make parent patterns not depend on slash presence
@ -2665,14 +2671,13 @@ end
--- @param name string --- @param name string
--- @param path string --- @param path string
--- @param tail string --- @param tail string
--- @param pattern_sorted vim.filetype.mapping[] --- @param pattern_sorted vim.filetype.mapping.sorted[]
--- @param parent_matches table<string,boolean> --- @param parent_matches table<string,boolean>
--- @param bufnr integer? --- @param bufnr integer?
local function match_pattern_sorted(name, path, tail, pattern_sorted, parent_matches, bufnr) local function match_pattern_sorted(name, path, tail, pattern_sorted, parent_matches, bufnr)
for i = 1, #pattern_sorted do for _, p in ipairs(pattern_sorted) do
local pat, ft_data = next(pattern_sorted[i]) local parent, pat, ft_or_fn = p[1], p[2], p[3]
local parent = ft_data[2].parent
local parent_is_matched = parent_matches[parent] local parent_is_matched = parent_matches[parent]
if parent_is_matched == nil then if parent_is_matched == nil then
parent_matches[parent] = match_pattern(name, path, tail, parent, true) ~= nil parent_matches[parent] = match_pattern(name, path, tail, parent, true) ~= nil
@ -2682,7 +2687,7 @@ local function match_pattern_sorted(name, path, tail, pattern_sorted, parent_mat
if parent_is_matched then if parent_is_matched then
local matches = match_pattern(name, path, tail, pat, false) local matches = match_pattern(name, path, tail, pat, false)
if matches then if matches then
local ft, on_detect = dispatch(ft_data[1], path, bufnr, matches) local ft, on_detect = dispatch(ft_or_fn, path, bufnr, matches)
if ft then if ft then
return ft, on_detect return ft, on_detect
end end