neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

gen_filetype.lua (5793B)


      1 local do_not_run = true
      2 if do_not_run then
      3  print([[
      4    This script was used to bootstrap the filetype patterns in runtime/lua/vim/filetype.lua. It
      5    should no longer be used except for testing purposes. New filetypes, or changes to existing
      6    filetypes, should be ported manually as part of the vim-patch process.
      7  ]])
      8  return
      9 end
     10 
     11 local filetype_vim = 'runtime/filetype.vim'
     12 local filetype_lua = 'runtime/lua/vim/filetype.lua'
     13 
     14 local keywords = {
     15  ['for'] = true,
     16  ['or'] = true,
     17  ['and'] = true,
     18  ['end'] = true,
     19  ['do'] = true,
     20  ['if'] = true,
     21  ['while'] = true,
     22  ['repeat'] = true,
     23 }
     24 
     25 local sections = {
     26  extension = { str = {}, func = {} },
     27  filename = { str = {}, func = {} },
     28  pattern = { str = {}, func = {} },
     29 }
     30 
     31 local specialchars = '%*%?\\%$%[%]%{%}'
     32 
     33 local function add_pattern(pat, ft)
     34  local ok = true
     35 
     36  -- Patterns that start or end with { or } confuse splitting on commas and make parsing harder, so just skip those
     37  if not string.find(pat, '^%{') and not string.find(pat, '%}$') then
     38    for part in string.gmatch(pat, '[^,]+') do
     39      if not string.find(part, '[' .. specialchars .. ']') then
     40        if type(ft) == 'string' then
     41          sections.filename.str[part] = ft
     42        else
     43          sections.filename.func[part] = ft
     44        end
     45      elseif string.match(part, '^%*%.[^%./' .. specialchars .. ']+$') then
     46        if type(ft) == 'string' then
     47          sections.extension.str[part:sub(3)] = ft
     48        else
     49          sections.extension.func[part:sub(3)] = ft
     50        end
     51      else
     52        if string.match(part, '^%*/[^' .. specialchars .. ']+$') then
     53          -- For patterns matching */some/pattern we want to easily match files
     54          -- with path /some/pattern, so include those in filename detection
     55          if type(ft) == 'string' then
     56            sections.filename.str[part:sub(2)] = ft
     57          else
     58            sections.filename.func[part:sub(2)] = ft
     59          end
     60        end
     61 
     62        if string.find(part, '^[%w-_.*?%[%]/]+$') then
     63          local p = part:gsub('%.', '%%.'):gsub('%*', '.*'):gsub('%?', '.')
     64          -- Insert into array to maintain order rather than setting
     65          -- key-value directly
     66          if type(ft) == 'string' then
     67            sections.pattern.str[p] = ft
     68          else
     69            sections.pattern.func[p] = ft
     70          end
     71        else
     72          ok = false
     73        end
     74      end
     75    end
     76  end
     77 
     78  return ok
     79 end
     80 
     81 local function parse_line(line)
     82  local pat, ft
     83  pat, ft = line:match('^%s*au%a* Buf[%a,]+%s+(%S+)%s+setf%s+(%S+)')
     84  if pat then
     85    return add_pattern(pat, ft)
     86  else
     87    local func
     88    pat, func = line:match('^%s*au%a* Buf[%a,]+%s+(%S+)%s+call%s+(%S+)')
     89    if pat then
     90      return add_pattern(pat, function()
     91        return func
     92      end)
     93    end
     94  end
     95 end
     96 
     97 local unparsed = {}
     98 local full_line
     99 for line in io.lines(filetype_vim) do
    100  local cont = string.match(line, '^%s*\\%s*(.*)$')
    101  if cont then
    102    full_line = full_line .. ' ' .. cont
    103  else
    104    if full_line then
    105      if not parse_line(full_line) and string.find(full_line, '^%s*au%a* Buf') then
    106        table.insert(unparsed, full_line)
    107      end
    108    end
    109    full_line = line
    110  end
    111 end
    112 
    113 if #unparsed > 0 then
    114  print('Failed to parse the following patterns:')
    115  for _, v in ipairs(unparsed) do
    116    print(v)
    117  end
    118 end
    119 
    120 local function add_item(indent, key, ft)
    121  if type(ft) == 'string' then
    122    if string.find(key, '%A') or keywords[key] then
    123      key = string.format('["%s"]', key)
    124    end
    125    return string.format([[%s%s = "%s",]], indent, key, ft)
    126  elseif type(ft) == 'function' then
    127    local func = ft()
    128    if string.find(key, '%A') or keywords[key] then
    129      key = string.format('["%s"]', key)
    130    end
    131    -- Right now only a single argument is supported, which covers
    132    -- everything in filetype.vim as of this writing
    133    local arg = string.match(func, '%((.*)%)$')
    134    func = string.gsub(func, '%(.*$', '')
    135    if arg == '' then
    136      -- Function with no arguments, call the function directly
    137      return string.format([[%s%s = function() vim.fn["%s"]() end,]], indent, key, func)
    138    elseif string.match(arg, [[^(["']).*%1$]]) then
    139      -- String argument
    140      if func == 's:StarSetf' then
    141        return string.format([[%s%s = starsetf(%s),]], indent, key, arg)
    142      else
    143        return string.format([[%s%s = function() vim.fn["%s"](%s) end,]], indent, key, func, arg)
    144      end
    145    elseif string.find(arg, '%(') then
    146      -- Function argument
    147      return string.format(
    148        [[%s%s = function() vim.fn["%s"](vim.fn.%s) end,]],
    149        indent,
    150        key,
    151        func,
    152        arg
    153      )
    154    else
    155      assert(false, arg)
    156    end
    157  end
    158 end
    159 
    160 do
    161  local lines = {}
    162  local start = false
    163  for line in io.lines(filetype_lua) do
    164    if line:match('^%s+-- END [A-Z]+$') then
    165      start = false
    166    end
    167 
    168    if not start then
    169      table.insert(lines, line)
    170    end
    171 
    172    local indent, section = line:match('^(%s+)-- BEGIN ([A-Z]+)$')
    173    if section then
    174      start = true
    175      local t = sections[string.lower(section)]
    176 
    177      local sorted = {}
    178      for k, v in pairs(t.str) do
    179        table.insert(sorted, { [k] = v })
    180      end
    181 
    182      table.sort(sorted, function(a, b)
    183        return a[next(a)] < b[next(b)]
    184      end)
    185 
    186      for _, v in ipairs(sorted) do
    187        local k = next(v)
    188        table.insert(lines, add_item(indent, k, v[k]))
    189      end
    190 
    191      sorted = {}
    192      for k, v in pairs(t.func) do
    193        table.insert(sorted, { [k] = v })
    194      end
    195 
    196      table.sort(sorted, function(a, b)
    197        return next(a) < next(b)
    198      end)
    199 
    200      for _, v in ipairs(sorted) do
    201        local k = next(v)
    202        table.insert(lines, add_item(indent, k, v[k]))
    203      end
    204    end
    205  end
    206  local f = io.open(filetype_lua, 'w')
    207  f:write(table.concat(lines, '\n') .. '\n')
    208  f:close()
    209 end