Skip to content

Commit

Permalink
perf(filetype): optimize internal data structures
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lewis6991 committed Nov 11, 2024
1 parent d0e78b5 commit ff575b3
Showing 1 changed file with 50 additions and 45 deletions.
95 changes: 50 additions & 45 deletions runtime/lua/vim/filetype.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@ local fn = vim.fn
local M = {}

--- @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.mapping.value string|vim.filetype.mapfn|vim.filetype.maptbl
--- @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 opts? vim.filetype.mapopts
--- @param priority? integer
--- @return vim.filetype.maptbl
local function starsetf(ft, opts)
local function starsetf(ft, priority)
return {
function(path, bufnr)
-- Note: when `ft` is a function its return value may be nil.
Expand All @@ -27,11 +34,8 @@ local function starsetf(ft, opts)
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
priority = (opts and opts.priority) or -math.huge,
priority = priority or -math.huge,
},
}
end
Expand Down Expand Up @@ -1874,11 +1878,10 @@ local filename = {
}

-- Re-use closures as much as possible
local detect_apache_diretc = starsetf('apache', { parent = '/etc/' })
local detect_apache_dotconf = starsetf('apache', { parent = '%.conf' })
local detect_muttrc = starsetf('muttrc', { parent = 'utt' })
local detect_neomuttrc = starsetf('neomuttrc', { parent = 'utt' })
local detect_xkb = starsetf('xkb', { parent = '/usr/' })
local detect_apache = starsetf('apache')
local detect_muttrc = starsetf('muttrc')
local detect_neomuttrc = starsetf('neomuttrc')
local detect_xkb = starsetf('xkb')

---@type table<string,vim.filetype.mapping>
local pattern = {
Expand All @@ -1895,14 +1898,14 @@ local pattern = {
['/etc/asound%.conf$'] = 'alsaconf',
['/etc/apache2/sites%-.*/.*%.com$'] = 'apache',
['/etc/httpd/.*%.conf$'] = 'apache',
['/etc/apache2/.*%.conf'] = detect_apache_diretc,
['/etc/apache2/conf%..*/'] = detect_apache_diretc,
['/etc/apache2/mods%-.*/'] = detect_apache_diretc,
['/etc/apache2/sites%-.*/'] = detect_apache_diretc,
['/etc/httpd/conf%..*/'] = detect_apache_diretc,
['/etc/httpd/conf%.d/.*%.conf'] = detect_apache_diretc,
['/etc/httpd/mods%-.*/'] = detect_apache_diretc,
['/etc/httpd/sites%-.*/'] = detect_apache_diretc,
['/etc/apache2/.*%.conf'] = detect_apache,
['/etc/apache2/conf%..*/'] = detect_apache,
['/etc/apache2/mods%-.*/'] = detect_apache,
['/etc/apache2/sites%-.*/'] = detect_apache,
['/etc/httpd/conf%..*/'] = detect_apache,
['/etc/httpd/conf%.d/.*%.conf'] = detect_apache,
['/etc/httpd/mods%-.*/'] = detect_apache,
['/etc/httpd/sites%-.*/'] = detect_apache,
['/etc/proftpd/.*%.conf'] = starsetf('apachestyle'),
['/etc/proftpd/conf%..*/'] = starsetf('apachestyle'),
['/etc/cdrdao%.conf$'] = 'cdrdaoconf',
Expand Down Expand Up @@ -2193,13 +2196,13 @@ local pattern = {
},
['%.conf'] = {
['^proftpd%.conf'] = starsetf('apachestyle'),
['^access%.conf'] = detect_apache_dotconf,
['^apache%.conf'] = detect_apache_dotconf,
['^apache2%.conf'] = detect_apache_dotconf,
['^httpd%.conf'] = detect_apache_dotconf,
['^httpd%-.*%.conf'] = detect_apache_dotconf,
['^proxy%-html%.conf'] = detect_apache_dotconf,
['^srm%.conf'] = detect_apache_dotconf,
['^access%.conf'] = detect_apache,
['^apache%.conf'] = detect_apache,
['^apache2%.conf'] = detect_apache,
['^httpd%.conf'] = detect_apache,
['^httpd%-.*%.conf'] = detect_apache,
['^proxy%-html%.conf'] = detect_apache,
['^srm%.conf'] = detect_apache,
['asterisk/.*%.conf'] = starsetf('asterisk'),
['asterisk.*/.*voicemail%.conf'] = starsetf('asteriskvm'),
['^dictd.*%.conf$'] = 'dictdconf',
Expand Down Expand Up @@ -2372,7 +2375,7 @@ local pattern = {
['/app%-defaults/'] = starsetf('xdefaults'),
['^Xresources'] = starsetf('xdefaults'),
-- 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),
['Xmodmap$'] = 'xmodmap',
['xmodmap'] = starsetf('xmodmap'),
Expand All @@ -2398,8 +2401,10 @@ local pattern = {
--- @type table<string,vim.filetype.pattern_cache>
local pattern_lookup = {}

--- @param a vim.filetype.mapping.sorted
--- @param b vim.filetype.mapping.sorted
local function compare_by_priority(a, b)
return a[next(a)][2].priority > b[next(b)][2].priority
return a[4] > b[4]
end

--- @param pat string
Expand All @@ -2409,30 +2414,30 @@ local function parse_pattern(pat)
end

--- @param t table<string,vim.filetype.mapping>
--- @return vim.filetype.mapping[]
--- @return vim.filetype.mapping[]
--- @return vim.filetype.mapping.sorted[]
--- @return vim.filetype.mapping.sorted[]
local function sort_by_priority(t)
-- Separate patterns with non-negative and negative priority because they
-- will be processed separately
local pos = {} --- @type vim.filetype.mapping[]
local neg = {} --- @type vim.filetype.mapping[]
local pos = {} --- @type vim.filetype.mapping.sorted[]
local neg = {} --- @type vim.filetype.mapping.sorted[]
for parent, ft_map in pairs(t) do
pattern_lookup[parent] = pattern_lookup[parent] or parse_pattern(parent)
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(
type(ft) == 'string' or type(ft) == 'function',
type(ft_or_fun) == 'string' or type(ft_or_fun) == 'function',
'Expected string or function for filetype'
)

-- Parse pattern for common data and cache it once
pattern_lookup[pat] = pattern_lookup[pat] or parse_pattern(pat)

local opts = (type(maptbl) == 'table' and type(maptbl[2]) == 'table') and maptbl[2] or {}
opts.parent = opts.parent or parent
opts.priority = opts.priority or 0
--- @type vim.filetype.mapopts?
local opts = (type(maptbl) == 'table' and type(maptbl[2]) == 'table') and maptbl[2] or nil
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

Expand Down Expand Up @@ -2643,7 +2648,8 @@ local function match_pattern(name, path, tail, pat, try_all_candidates)
if some_env_missing then
return nil
end
pat, has_slash = expanded, expanded:find('/') ~= nil
pat = expanded
has_slash = has_slash or expanded:find('/') ~= nil
end

-- Try all possible candidates to make parent patterns not depend on slash presence
Expand All @@ -2665,14 +2671,13 @@ end
--- @param name string
--- @param path string
--- @param tail string
--- @param pattern_sorted vim.filetype.mapping[]
--- @param pattern_sorted vim.filetype.mapping.sorted[]
--- @param parent_matches table<string,boolean>
--- @param bufnr integer?
local function match_pattern_sorted(name, path, tail, pattern_sorted, parent_matches, bufnr)
for i = 1, #pattern_sorted do
local pat, ft_data = next(pattern_sorted[i])
for _, p in ipairs(pattern_sorted) do
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]
if parent_is_matched == nil then
parent_matches[parent] = match_pattern(name, path, tail, parent, true) ~= nil
Expand All @@ -2682,7 +2687,7 @@ local function match_pattern_sorted(name, path, tail, pattern_sorted, parent_mat
if parent_is_matched then
local matches = match_pattern(name, path, tail, pat, false)
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
return ft, on_detect
end
Expand Down

0 comments on commit ff575b3

Please sign in to comment.