mirror of
https://github.com/neovim/neovim.git
synced 2025-10-21 17:21:49 +00:00
refactor(runtime): port scripts.vim to lua (#18710)
This commit is contained in:

committed by
GitHub

parent
0313aba77a
commit
acb7a90281
@@ -2059,11 +2059,31 @@ add({filetypes}) *vim.filetype.add()*
|
||||
})
|
||||
<
|
||||
|
||||
To add a fallback match on contents (see
|
||||
|new-filetype-scripts|), use >
|
||||
|
||||
vim.filetype.add {
|
||||
pattern = {
|
||||
['.*'] = {
|
||||
priority = -math.huge,
|
||||
function(path, bufnr)
|
||||
local content = vim.filetype.getlines(bufnr, 1)
|
||||
if vim.filetype.matchregex(content, { [[^#!.*\<mine\>]] }) then
|
||||
return 'mine'
|
||||
elseif vim.filetype.matchregex(content, { [[\<drawing\>]] }) then
|
||||
return 'drawing'
|
||||
end
|
||||
end,
|
||||
},
|
||||
},
|
||||
}
|
||||
<
|
||||
|
||||
Parameters: ~
|
||||
{filetypes} (table) A table containing new filetype maps
|
||||
(see example).
|
||||
|
||||
match({arg}) *vim.filetype.match()*
|
||||
match({args}) *vim.filetype.match()*
|
||||
Perform filetype detection.
|
||||
|
||||
The filetype can be detected using one of three methods:
|
||||
@@ -2096,22 +2116,22 @@ match({arg}) *vim.filetype.match()*
|
||||
<
|
||||
|
||||
Parameters: ~
|
||||
{arg} (table) Table specifying which matching strategy to
|
||||
use. Accepted keys are:
|
||||
• buf (number): Buffer number to use for matching.
|
||||
Mutually exclusive with {contents}
|
||||
• filename (string): Filename to use for matching.
|
||||
When {buf} is given, defaults to the filename of
|
||||
the given buffer number. The file need not
|
||||
actually exist in the filesystem. When used
|
||||
without {buf} only the name of the file is used
|
||||
for filetype matching. This may result in failure
|
||||
to detect the filetype in cases where the
|
||||
filename alone is not enough to disambiguate the
|
||||
filetype.
|
||||
• contents (table): An array of lines representing
|
||||
file contents to use for matching. Can be used
|
||||
with {filename}. Mutually exclusive with {buf}.
|
||||
{args} (table) Table specifying which matching strategy
|
||||
to use. Accepted keys are:
|
||||
• buf (number): Buffer number to use for matching.
|
||||
Mutually exclusive with {contents}
|
||||
• filename (string): Filename to use for matching.
|
||||
When {buf} is given, defaults to the filename of
|
||||
the given buffer number. The file need not
|
||||
actually exist in the filesystem. When used
|
||||
without {buf} only the name of the file is used
|
||||
for filetype matching. This may result in
|
||||
failure to detect the filetype in cases where
|
||||
the filename alone is not enough to disambiguate
|
||||
the filetype.
|
||||
• contents (table): An array of lines representing
|
||||
file contents to use for matching. Can be used
|
||||
with {filename}. Mutually exclusive with {buf}.
|
||||
|
||||
Return: ~
|
||||
(string|nil) If a match was found, the matched filetype.
|
||||
|
@@ -24,18 +24,26 @@ local function starsetf(ft, opts)
|
||||
end
|
||||
|
||||
---@private
|
||||
--- Get a single line or line-range from the buffer.
|
||||
--- Get a single line or line range from the buffer.
|
||||
--- If only start_lnum is specified, return a single line as a string.
|
||||
--- If both start_lnum and end_lnum are omitted, return all lines from the buffer.
|
||||
---
|
||||
---@param bufnr number|nil The buffer to get the lines from
|
||||
---@param start_lnum number The line number of the first line (inclusive, 1-based)
|
||||
---@param start_lnum number|nil The line number of the first line (inclusive, 1-based)
|
||||
---@param end_lnum number|nil The line number of the last line (inclusive, 1-based)
|
||||
---@return table<string>|string Array of lines, or string when end_lnum is omitted
|
||||
function M.getlines(bufnr, start_lnum, end_lnum)
|
||||
if not end_lnum then
|
||||
-- Return a single line as a string
|
||||
return api.nvim_buf_get_lines(bufnr, start_lnum - 1, start_lnum, false)[1] or ''
|
||||
if end_lnum then
|
||||
-- Return a line range
|
||||
return api.nvim_buf_get_lines(bufnr, start_lnum - 1, end_lnum, false)
|
||||
end
|
||||
if start_lnum then
|
||||
-- Return a single line
|
||||
return api.nvim_buf_get_lines(bufnr, start_lnum - 1, start_lnum, false)[1] or ''
|
||||
else
|
||||
-- Return all lines
|
||||
return api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
end
|
||||
return api.nvim_buf_get_lines(bufnr, start_lnum - 1, end_lnum, false)
|
||||
end
|
||||
|
||||
---@private
|
||||
@@ -600,7 +608,8 @@ local extension = {
|
||||
end,
|
||||
quake = 'm3quake',
|
||||
['m4'] = function(path, bufnr)
|
||||
return require('vim.filetype.detect').m4(path)
|
||||
path = path:lower()
|
||||
return not (path:find('html%.m4$') or path:find('fvwm2rc')) and 'm4'
|
||||
end,
|
||||
eml = 'mail',
|
||||
mk = 'make',
|
||||
@@ -847,22 +856,22 @@ local extension = {
|
||||
sed = 'sed',
|
||||
sexp = 'sexplib',
|
||||
bash = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr, 'bash')
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
|
||||
end,
|
||||
ebuild = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr, 'bash')
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
|
||||
end,
|
||||
eclass = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr, 'bash')
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
|
||||
end,
|
||||
env = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
|
||||
end,
|
||||
ksh = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr, 'ksh')
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'ksh')
|
||||
end,
|
||||
sh = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
|
||||
end,
|
||||
sieve = 'sieve',
|
||||
siv = 'sieve',
|
||||
@@ -1090,7 +1099,7 @@ local extension = {
|
||||
return require('vim.filetype.detect').scd(bufnr)
|
||||
end,
|
||||
tcsh = function(path, bufnr)
|
||||
return require('vim.filetype.detect').shell(path, bufnr, 'tcsh')
|
||||
return require('vim.filetype.detect').shell(path, M.getlines(bufnr), 'tcsh')
|
||||
end,
|
||||
sql = function(path, bufnr)
|
||||
return vim.g.filetype_sql and vim.g.filetype_sql or 'sql'
|
||||
@@ -1510,40 +1519,40 @@ local filename = {
|
||||
['/etc/serial.conf'] = 'setserial',
|
||||
['/etc/udev/cdsymlinks.conf'] = 'sh',
|
||||
['bash.bashrc'] = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr, 'bash')
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
|
||||
end,
|
||||
bashrc = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr, 'bash')
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
|
||||
end,
|
||||
['.bashrc'] = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr, 'bash')
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
|
||||
end,
|
||||
['.env'] = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
|
||||
end,
|
||||
['.kshrc'] = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr, 'ksh')
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'ksh')
|
||||
end,
|
||||
['.profile'] = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
|
||||
end,
|
||||
['/etc/profile'] = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
|
||||
end,
|
||||
APKBUILD = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr, 'bash')
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
|
||||
end,
|
||||
PKGBUILD = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr, 'bash')
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
|
||||
end,
|
||||
['.tcshrc'] = function(path, bufnr)
|
||||
return require('vim.filetype.detect').shell(path, bufnr, 'tcsh')
|
||||
return require('vim.filetype.detect').shell(path, M.getlines(bufnr), 'tcsh')
|
||||
end,
|
||||
['tcsh.login'] = function(path, bufnr)
|
||||
return require('vim.filetype.detect').shell(path, bufnr, 'tcsh')
|
||||
return require('vim.filetype.detect').shell(path, M.getlines(bufnr), 'tcsh')
|
||||
end,
|
||||
['tcsh.tcshrc'] = function(path, bufnr)
|
||||
return require('vim.filetype.detect').shell(path, bufnr, 'tcsh')
|
||||
return require('vim.filetype.detect').shell(path, M.getlines(bufnr), 'tcsh')
|
||||
end,
|
||||
['/etc/slp.conf'] = 'slpconf',
|
||||
['/etc/slp.reg'] = 'slpreg',
|
||||
@@ -1934,28 +1943,28 @@ local pattern = {
|
||||
['.*/etc/serial%.conf'] = 'setserial',
|
||||
['.*/etc/udev/cdsymlinks%.conf'] = 'sh',
|
||||
['%.bash[_%-]aliases'] = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr, 'bash')
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
|
||||
end,
|
||||
['%.bash[_%-]logout'] = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr, 'bash')
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
|
||||
end,
|
||||
['%.bash[_%-]profile'] = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr, 'bash')
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
|
||||
end,
|
||||
['%.kshrc.*'] = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr, 'ksh')
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'ksh')
|
||||
end,
|
||||
['%.profile.*'] = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
|
||||
end,
|
||||
['.*/etc/profile'] = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
|
||||
end,
|
||||
['bash%-fc[%-%.]'] = function(path, bufnr)
|
||||
return require('vim.filetype.detect').sh(path, bufnr, 'bash')
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
|
||||
end,
|
||||
['%.tcshrc.*'] = function(path, bufnr)
|
||||
return require('vim.filetype.detect').shell(path, bufnr, 'tcsh')
|
||||
return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'tcsh')
|
||||
end,
|
||||
['.*/etc/sudoers%.d/.*'] = starsetf('sudoers'),
|
||||
['.*%._sst%.meta'] = 'sisu',
|
||||
@@ -2165,6 +2174,25 @@ end
|
||||
--- })
|
||||
--- </pre>
|
||||
---
|
||||
--- To add a fallback match on contents (see |new-filetype-scripts|), use
|
||||
--- <pre>
|
||||
--- vim.filetype.add {
|
||||
--- pattern = {
|
||||
--- ['.*'] = {
|
||||
--- priority = -math.huge,
|
||||
--- function(path, bufnr)
|
||||
--- local content = vim.filetype.getlines(bufnr, 1)
|
||||
--- if vim.filetype.matchregex(content, { [[^#!.*\\<mine\\>]] }) then
|
||||
--- return 'mine'
|
||||
--- elseif vim.filetype.matchregex(content, { [[\\<drawing\\>]] }) then
|
||||
--- return 'drawing'
|
||||
--- end
|
||||
--- end,
|
||||
--- },
|
||||
--- },
|
||||
--- }
|
||||
--- </pre>
|
||||
---
|
||||
---@param filetypes table A table containing new filetype maps (see example).
|
||||
function M.add(filetypes)
|
||||
for k, v in pairs(filetypes.extension or {}) do
|
||||
@@ -2253,7 +2281,7 @@ end
|
||||
--- vim.filetype.match({ contents = {'#!/usr/bin/env bash'} })
|
||||
--- </pre>
|
||||
---
|
||||
---@param arg table Table specifying which matching strategy to use. Accepted keys are:
|
||||
---@param args table Table specifying which matching strategy to use. Accepted keys are:
|
||||
--- * buf (number): Buffer number to use for matching. Mutually exclusive with
|
||||
--- {contents}
|
||||
--- * filename (string): Filename to use for matching. When {buf} is given,
|
||||
@@ -2270,22 +2298,18 @@ end
|
||||
---@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(arg)
|
||||
function M.match(args)
|
||||
vim.validate({
|
||||
arg = { arg, 't' },
|
||||
arg = { args, 't' },
|
||||
})
|
||||
|
||||
if not (arg.buf or arg.filename or arg.contents) then
|
||||
if not (args.buf or args.filename or args.contents) then
|
||||
error('At least one of "buf", "filename", or "contents" must be given')
|
||||
end
|
||||
|
||||
if arg.buf and arg.contents then
|
||||
error('Only one of "buf" or "contents" must be given')
|
||||
end
|
||||
|
||||
local bufnr = arg.buf
|
||||
local name = arg.filename
|
||||
local contents = arg.contents
|
||||
local bufnr = args.buf
|
||||
local name = args.filename
|
||||
local contents = args.contents
|
||||
|
||||
if bufnr and not name then
|
||||
name = api.nvim_buf_get_name(bufnr)
|
||||
@@ -2297,13 +2321,6 @@ function M.match(arg)
|
||||
|
||||
local ft, on_detect
|
||||
|
||||
if contents then
|
||||
-- Sanity check: this should not happen
|
||||
assert(not bufnr, '"buf" and "contents" are mutually exclusive')
|
||||
-- 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)
|
||||
@@ -2345,7 +2362,7 @@ function M.match(arg)
|
||||
return ft, on_detect
|
||||
end
|
||||
|
||||
-- Finally, check patterns with negative priority
|
||||
-- Next, check patterns with negative priority
|
||||
for i = j, #pattern_sorted do
|
||||
local v = pattern_sorted[i]
|
||||
local k = next(v)
|
||||
@@ -2359,6 +2376,19 @@ function M.match(arg)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Finally, check file contents
|
||||
if contents or bufnr then
|
||||
contents = contents or M.getlines(bufnr)
|
||||
-- If name is nil, catch any errors from the contents filetype detection function.
|
||||
-- If the function tries to use the filename that is nil then it will fail,
|
||||
-- but this enables checks which do not need a filename to still work.
|
||||
local ok
|
||||
ok, ft = pcall(require('vim.filetype.detect').match_contents, contents, name)
|
||||
if ok and ft then
|
||||
return ft
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
@@ -206,12 +206,52 @@ function M.csh(path, bufnr)
|
||||
-- Filetype was already detected
|
||||
return
|
||||
end
|
||||
local contents = getlines(bufnr)
|
||||
if vim.g.filetype_csh then
|
||||
return M.shell(path, bufnr, vim.g.filetype_csh)
|
||||
return M.shell(path, contents, vim.g.filetype_csh)
|
||||
elseif string.find(vim.o.shell, 'tcsh') then
|
||||
return M.shell(path, bufnr, 'tcsh')
|
||||
return M.shell(path, contents, 'tcsh')
|
||||
else
|
||||
return M.shell(path, bufnr, 'csh')
|
||||
return M.shell(path, contents, 'csh')
|
||||
end
|
||||
end
|
||||
|
||||
local function cvs_diff(path, contents)
|
||||
for _, line in ipairs(contents) do
|
||||
if not line:find('^%? ') then
|
||||
if matchregex(line, [[^Index:\s\+\f\+$]]) then
|
||||
-- CVS diff
|
||||
return 'diff'
|
||||
elseif
|
||||
-- Locale input files: Formal Definitions of Cultural Conventions
|
||||
-- Filename must be like en_US, fr_FR@euro or en_US.UTF-8
|
||||
findany(path, { '%a%a_%a%a$', '%a%a_%a%a[%.@]', '%a%a_%a%ai18n$', '%a%a_%a%aPOSIX$', '%a%a_%a%atranslit_' })
|
||||
then
|
||||
-- Only look at the first 100 lines
|
||||
for line_nr = 1, 100 do
|
||||
if not contents[line_nr] then
|
||||
break
|
||||
elseif
|
||||
findany(contents[line_nr], {
|
||||
'^LC_IDENTIFICATION$',
|
||||
'^LC_CTYPE$',
|
||||
'^LC_COLLATE$',
|
||||
'^LC_MONETARY$',
|
||||
'^LC_NUMERIC$',
|
||||
'^LC_TIME$',
|
||||
'^LC_MESSAGES$',
|
||||
'^LC_PAPER$',
|
||||
'^LC_TELEPHONE$',
|
||||
'^LC_MEASUREMENT$',
|
||||
'^LC_NAME$',
|
||||
'^LC_ADDRESS$',
|
||||
})
|
||||
then
|
||||
return 'fdcc'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -270,6 +310,38 @@ function M.dep3patch(path, bufnr)
|
||||
end
|
||||
end
|
||||
|
||||
local function diff(contents)
|
||||
if
|
||||
contents[1]:find('^%-%-%- ') and contents[2]:find('^%+%+%+ ')
|
||||
or contents[1]:find('^%* looking for ') and contents[2]:find('^%* comparing to ')
|
||||
or contents[1]:find('^%*%*%* ') and contents[2]:find('^%-%-%- ')
|
||||
or contents[1]:find('^=== ') and ((contents[2]:find('^' .. string.rep('=', 66)) and contents[3]:find('^%-%-% ') and contents[4]:find(
|
||||
'^%+%+%+'
|
||||
)) or (contents[2]:find('^%-%-%- ') and contents[3]:find('^%+%+%+ ')))
|
||||
or findany(contents[1], { '^=== removed', '^=== added', '^=== renamed', '^=== modified' })
|
||||
then
|
||||
return 'diff'
|
||||
end
|
||||
end
|
||||
|
||||
function M.dns_zone(contents)
|
||||
if
|
||||
findany(
|
||||
contents[1] .. contents[2] .. contents[3] .. contents[4],
|
||||
{ '^; <<>> DiG [0-9%.]+.* <<>>', '%$ORIGIN', '%$TTL', 'IN%s+SOA' }
|
||||
)
|
||||
then
|
||||
return 'bindzone'
|
||||
end
|
||||
-- BAAN
|
||||
if -- Check for 1 to 80 '*' characters
|
||||
contents[1]:find('|%*' .. string.rep('%*?', 79)) and contents[2]:find('VRC ')
|
||||
or contents[2]:find('|%*' .. string.rep('%*?', 79)) and contents[3]:find('VRC ')
|
||||
then
|
||||
return 'baan'
|
||||
end
|
||||
end
|
||||
|
||||
function M.dtrace(bufnr)
|
||||
if vim.fn.did_filetype() ~= 0 then
|
||||
-- Filetype was already detected
|
||||
@@ -483,7 +555,7 @@ function M.install(path, bufnr)
|
||||
if getlines(bufnr, 1):lower():find('<%?php') then
|
||||
return 'php'
|
||||
end
|
||||
return M.sh(path, bufnr, 'bash')
|
||||
return M.sh(path, getlines(bufnr), 'bash')
|
||||
end
|
||||
|
||||
-- Innovation Data Processing
|
||||
@@ -572,10 +644,15 @@ function M.m(bufnr)
|
||||
end
|
||||
end
|
||||
|
||||
function M.m4(path)
|
||||
path = path:lower()
|
||||
if not path:find('html%.m4$') and not path:find('fvwm2rc') then
|
||||
return 'm4'
|
||||
local function m4(contents)
|
||||
for _, line in ipairs(contents) do
|
||||
if matchregex(line, [[^\s*dnl\>]]) then
|
||||
return 'm4'
|
||||
end
|
||||
end
|
||||
if vim.env.TERM == 'amiga' and findany(contents[1]:lower(), { '^;', '^%.bra' }) then
|
||||
-- AmigaDos scripts
|
||||
return 'amiga'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -625,7 +702,7 @@ end
|
||||
local function is_lprolog(bufnr)
|
||||
-- Skip apparent comments and blank lines, what looks like
|
||||
-- LambdaProlog comment may be RAPID header
|
||||
for _, line in ipairs(getlines(bufnr, 1, -1)) do
|
||||
for _, line in ipairs(getlines(bufnr)) do
|
||||
-- The second pattern matches a LambdaProlog comment
|
||||
if not findany(line, { '^%s*$', '^%s*%%' }) then
|
||||
-- The pattern must not catch a go.mod file
|
||||
@@ -982,24 +1059,26 @@ function M.sgml(bufnr)
|
||||
end
|
||||
end
|
||||
|
||||
function M.sh(path, bufnr, name)
|
||||
if vim.fn.did_filetype() ~= 0 or path:find(vim.g.ft_ignore_pat) then
|
||||
function M.sh(path, contents, name)
|
||||
-- Path may be nil, do not fail in that case
|
||||
if vim.fn.did_filetype() ~= 0 or (path or ''):find(vim.g.ft_ignore_pat) then
|
||||
-- Filetype was already detected or detection should be skipped
|
||||
return
|
||||
end
|
||||
|
||||
local on_detect
|
||||
|
||||
name = name or getlines(bufnr, 1)
|
||||
-- Get the name from the first line if not specified
|
||||
name = name or contents[1]
|
||||
if matchregex(name, [[\<csh\>]]) then
|
||||
-- Some .sh scripts contain #!/bin/csh.
|
||||
return M.shell(path, bufnr, 'csh')
|
||||
return M.shell(path, contents, 'csh')
|
||||
-- Some .sh scripts contain #!/bin/tcsh.
|
||||
elseif matchregex(name, [[\<tcsh\>]]) then
|
||||
return M.shell(path, bufnr, 'tcsh')
|
||||
return M.shell(path, contents, 'tcsh')
|
||||
-- Some .sh scripts contain #!/bin/zsh.
|
||||
elseif matchregex(name, [[\<zsh\>]]) then
|
||||
return M.shell(path, bufnr, 'zsh')
|
||||
return M.shell(path, contents, 'zsh')
|
||||
elseif matchregex(name, [[\<ksh\>]]) then
|
||||
on_detect = function(b)
|
||||
vim.b[b].is_kornshell = 1
|
||||
@@ -1019,27 +1098,30 @@ function M.sh(path, bufnr, name)
|
||||
vim.b[b].is_bash = nil
|
||||
end
|
||||
end
|
||||
return M.shell(path, bufnr, 'sh'), on_detect
|
||||
return M.shell(path, contents, 'sh'), on_detect
|
||||
end
|
||||
|
||||
-- For shell-like file types, check for an "exec" command hidden in a comment, as used for Tcl.
|
||||
-- Also called from scripts.vim, thus can't be local to this script. [TODO]
|
||||
function M.shell(path, bufnr, name)
|
||||
function M.shell(path, contents, name)
|
||||
if vim.fn.did_filetype() ~= 0 or matchregex(path, vim.g.ft_ignore_pat) then
|
||||
-- Filetype was already detected or detection should be skipped
|
||||
return
|
||||
end
|
||||
|
||||
local prev_line = ''
|
||||
for _, line in ipairs(getlines(bufnr, 2, -1)) do
|
||||
line = line:lower()
|
||||
if line:find('%s*exec%s') and not prev_line:find('^%s*#.*\\$') then
|
||||
-- Found an "exec" line after a comment with continuation
|
||||
local n = line:gsub('%s*exec%s+([^ ]*/)?', '', 1)
|
||||
if matchregex(n, [[\c\<tclsh\|\<wish]]) then
|
||||
return 'tcl'
|
||||
for line_nr, line in ipairs(contents) do
|
||||
-- Skip the first line
|
||||
if line_nr ~= 1 then
|
||||
line = line:lower()
|
||||
if line:find('%s*exec%s') and not prev_line:find('^%s*#.*\\$') then
|
||||
-- Found an "exec" line after a comment with continuation
|
||||
local n = line:gsub('%s*exec%s+([^ ]*/)?', '', 1)
|
||||
if matchregex(n, [[\c\<tclsh\|\<wish]]) then
|
||||
return 'tcl'
|
||||
end
|
||||
end
|
||||
prev_line = line
|
||||
end
|
||||
prev_line = line
|
||||
end
|
||||
return name
|
||||
end
|
||||
@@ -1123,7 +1205,7 @@ end
|
||||
|
||||
-- Determine if a *.tf file is TF mud client or terraform
|
||||
function M.tf(bufnr)
|
||||
for _, line in ipairs(getlines(bufnr, 1, -1)) do
|
||||
for _, line in ipairs(getlines(bufnr)) do
|
||||
-- Assume terraform file on a non-empty line (not whitespace-only)
|
||||
-- and when the first non-whitespace character is not a ; or /
|
||||
if not line:find('^%s*$') and not line:find('^%s*[;/]') then
|
||||
@@ -1204,4 +1286,271 @@ end
|
||||
-- luacheck: pop
|
||||
-- luacheck: pop
|
||||
|
||||
local patterns_hashbang = {
|
||||
['^zsh\\>'] = { 'zsh', { vim_regex = true } },
|
||||
['^\\(tclsh\\|wish\\|expectk\\|itclsh\\|itkwish\\)\\>'] = { 'tcl', { vim_regex = true } },
|
||||
['^expect\\>'] = { 'expect', { vim_regex = true } },
|
||||
['^gnuplot\\>'] = { 'gnuplot', { vim_regex = true } },
|
||||
['make\\>'] = { 'make', { vim_regex = true } },
|
||||
['^pike\\%(\\>\\|[0-9]\\)'] = { 'pike', { vim_regex = true } },
|
||||
lua = 'lua',
|
||||
perl = 'perl',
|
||||
php = 'php',
|
||||
python = 'python',
|
||||
['^groovy\\>'] = { 'groovy', { vim_regex = true } },
|
||||
raku = 'raku',
|
||||
ruby = 'ruby',
|
||||
['node\\(js\\)\\=\\>\\|js\\>'] = { 'javascript', { vim_regex = true } },
|
||||
['rhino\\>'] = { 'javascript', { vim_regex = true } },
|
||||
-- BC calculator
|
||||
['^bc\\>'] = { 'bc', { vim_regex = true } },
|
||||
['sed\\>'] = { 'sed', { vim_regex = true } },
|
||||
ocaml = 'ocaml',
|
||||
-- Awk scripts; also finds "gawk"
|
||||
['awk\\>'] = { 'awk', { vim_regex = true } },
|
||||
wml = 'wml',
|
||||
scheme = 'scheme',
|
||||
cfengine = 'cfengine',
|
||||
escript = 'erlang',
|
||||
haskell = 'haskell',
|
||||
clojure = 'clojure',
|
||||
['scala\\>'] = { 'scala', { vim_regex = true } },
|
||||
-- Free Pascal
|
||||
['instantfpc\\>'] = { 'pascal', { vim_regex = true } },
|
||||
['fennel\\>'] = { 'fennel', { vim_regex = true } },
|
||||
-- MikroTik RouterOS script
|
||||
['rsc\\>'] = { 'routeros', { vim_regex = true } },
|
||||
['fish\\>'] = { 'fish', { vim_regex = true } },
|
||||
['gforth\\>'] = { 'forth', { vim_regex = true } },
|
||||
['icon\\>'] = { 'icon', { vim_regex = true } },
|
||||
}
|
||||
|
||||
---@private
|
||||
-- File starts with "#!".
|
||||
local function match_from_hashbang(contents, path)
|
||||
local first_line = contents[1]
|
||||
-- Check for a line like "#!/usr/bin/env {options} bash". Turn it into
|
||||
-- "#!/usr/bin/bash" to make matching easier.
|
||||
-- Recognize only a few {options} that are commonly used.
|
||||
if matchregex(first_line, [[^#!\s*\S*\<env\s]]) then
|
||||
first_line = first_line:gsub('%S+=%S+', '')
|
||||
first_line = first_line
|
||||
:gsub('%-%-ignore%-environment', '', 1)
|
||||
:gsub('%-%-split%-string', '', 1)
|
||||
:gsub('%-[iS]', '', 1)
|
||||
first_line = vim.fn.substitute(first_line, [[\<env\s\+]], '', '')
|
||||
end
|
||||
|
||||
-- Get the program name.
|
||||
-- Only accept spaces in PC style paths: "#!c:/program files/perl [args]".
|
||||
-- If the word env is used, use the first word after the space:
|
||||
-- "#!/usr/bin/env perl [path/args]"
|
||||
-- If there is no path use the first word: "#!perl [path/args]".
|
||||
-- Otherwise get the last word after a slash: "#!/usr/bin/perl [path/args]".
|
||||
local name
|
||||
if first_line:find('^#!%s*%a:[/\\]') then
|
||||
name = vim.fn.substitute(first_line, [[^#!.*[/\\]\(\i\+\).*]], '\\1', '')
|
||||
elseif matchregex(first_line, [[^#!.*\<env\>]]) then
|
||||
name = vim.fn.substitute(first_line, [[^#!.*\<env\>\s\+\(\i\+\).*]], '\\1', '')
|
||||
elseif matchregex(first_line, [[^#!\s*[^/\\ ]*\>\([^/\\]\|$\)]]) then
|
||||
name = vim.fn.substitute(first_line, [[^#!\s*\([^/\\ ]*\>\).*]], '\\1', '')
|
||||
else
|
||||
name = vim.fn.substitute(first_line, [[^#!\s*\S*[/\\]\(\i\+\).*]], '\\1', '')
|
||||
end
|
||||
|
||||
-- tcl scripts may have #!/bin/sh in the first line and "exec wish" in the
|
||||
-- third line. Suggested by Steven Atkinson.
|
||||
if contents[3] and contents[3]:find('^exec wish') then
|
||||
name = 'wish'
|
||||
end
|
||||
|
||||
if matchregex(name, [[^\(bash\d*\|\|ksh\d*\|sh\)\>]]) then
|
||||
-- Bourne-like shell scripts: bash bash2 ksh ksh93 sh
|
||||
return require('vim.filetype.detect').sh(path, contents, first_line)
|
||||
elseif matchregex(name, [[^csh\>]]) then
|
||||
return require('vim.filetype.detect').shell(path, contents, vim.g.filetype_csh or 'csh')
|
||||
elseif matchregex(name, [[^tcsh\>]]) then
|
||||
return require('vim.filetype.detect').shell(path, contents, 'tcsh')
|
||||
end
|
||||
|
||||
for k, v in pairs(patterns_hashbang) do
|
||||
local ft = type(v) == 'table' and v[1] or v
|
||||
local opts = type(v) == 'table' and v[2] or {}
|
||||
if opts.vim_regex and matchregex(name, k) or name:find(k) then
|
||||
return ft
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local patterns_text = {
|
||||
['^#compdef\\>'] = { 'zsh', { vim_regex = true } },
|
||||
['^#autoload\\>'] = { 'zsh', { vim_regex = true } },
|
||||
-- ELM Mail files
|
||||
['^From [a-zA-Z][a-zA-Z_0-9%.=%-]*(@[^ ]*)? .* 19%d%d$'] = 'mail',
|
||||
['^From [a-zA-Z][a-zA-Z_0-9%.=%-]*(@[^ ]*)? .* 20%d%d$'] = 'mail',
|
||||
['^From %- .* 19%d%d$'] = 'mail',
|
||||
['^From %- .* 20%d%d$'] = 'mail',
|
||||
-- Mason
|
||||
['^<[%%&].*>'] = 'mason',
|
||||
-- Vim scripts (must have '" vim' as the first line to trigger this)
|
||||
['^" *[vV]im$['] = 'vim',
|
||||
-- libcxx and libstdc++ standard library headers like ["iostream["] do not have
|
||||
-- an extension, recognize the Emacs file mode.
|
||||
['%-%*%-.*[cC]%+%+.*%-%*%-'] = 'cpp',
|
||||
['^\\*\\* LambdaMOO Database, Format Version \\%([1-3]\\>\\)\\@!\\d\\+ \\*\\*$'] = {
|
||||
'moo',
|
||||
{ vim_regex = true },
|
||||
},
|
||||
-- Diff file:
|
||||
-- - "diff" in first line (context diff)
|
||||
-- - "Only in " in first line
|
||||
-- - "--- " in first line and "+++ " in second line (unified diff).
|
||||
-- - "*** " in first line and "--- " in second line (context diff).
|
||||
-- - "# It was generated by makepatch " in the second line (makepatch diff).
|
||||
-- - "Index: <filename>" in the first line (CVS file)
|
||||
-- - "=== ", line of "=", "---", "+++ " (SVK diff)
|
||||
-- - "=== ", "--- ", "+++ " (bzr diff, common case)
|
||||
-- - "=== (removed|added|renamed|modified)" (bzr diff, alternative)
|
||||
-- - "# HG changeset patch" in first line (Mercurial export format)
|
||||
['^\\(diff\\>\\|Only in \\|\\d\\+\\(,\\d\\+\\)\\=[cda]\\d\\+\\>\\|# It was generated by makepatch \\|Index:\\s\\+\\f\\+\\r\\=$\\|===== \\f\\+ \\d\\+\\.\\d\\+ vs edited\\|==== //\\f\\+#\\d\\+\\|# HG changeset patch\\)'] = {
|
||||
'diff',
|
||||
{ vim_regex = true },
|
||||
},
|
||||
function(contents)
|
||||
return diff(contents)
|
||||
end,
|
||||
-- PostScript Files (must have %!PS as the first line, like a2ps output)
|
||||
['^%%![ \t]*PS'] = 'postscr',
|
||||
function(contents)
|
||||
return m4(contents)
|
||||
end,
|
||||
-- SiCAD scripts (must have procn or procd as the first line to trigger this)
|
||||
['^ *proc[nd] *$'] = { 'sicad', { ignore_case = true } },
|
||||
['^%*%*%*%* Purify'] = 'purifylog',
|
||||
-- XML
|
||||
['<%?%s*xml.*%?>'] = 'xml',
|
||||
-- XHTML (e.g.: PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN")
|
||||
['\\<DTD\\s\\+XHTML\\s'] = 'xhtml',
|
||||
-- HTML (e.g.: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN")
|
||||
-- Avoid "doctype html", used by slim.
|
||||
['\\c<!DOCTYPE\\s\\+html\\>'] = { 'html', { vim_regex = true } },
|
||||
-- PDF
|
||||
['^%%PDF%-'] = 'pdf',
|
||||
-- XXD output
|
||||
['^%x%x%x%x%x%x%x: %x%x ?%x%x ?%x%x ?%x%x '] = 'xxd',
|
||||
-- RCS/CVS log output
|
||||
['^RCS file:'] = { 'rcslog', { start_lnum = 1, end_lnum = 2 } },
|
||||
-- CVS commit
|
||||
['^CVS:'] = { 'cvs', { start_lnum = 2 } },
|
||||
['^CVS: '] = { 'cvs', { start_lnum = -1 } },
|
||||
-- Prescribe
|
||||
['^!R!'] = 'prescribe',
|
||||
-- Send-pr
|
||||
['^SEND%-PR:'] = 'sendpr',
|
||||
-- SNNS files
|
||||
['^SNNS network definition file'] = 'snnsnet',
|
||||
['^SNNS pattern definition file'] = 'snnspat',
|
||||
['^SNNS result file'] = 'snnsres',
|
||||
['^%%.-[Vv]irata'] = { 'virata', { start_lnum = 1, end_lnum = 5 } },
|
||||
['[0-9:%.]* *execve%('] = 'strace',
|
||||
['^__libc_start_main'] = 'strace',
|
||||
-- VSE JCL
|
||||
['^\\* $$ JOB\\>'] = { 'vsejcl', { vim_regex = true } },
|
||||
['^// *JOB\\>'] = { 'vsejcl', { vim_regex = true } },
|
||||
-- TAK and SINDA
|
||||
['K & K Associates'] = { 'takout', { start_lnum = 4 } },
|
||||
['TAK 2000'] = { 'takout', { start_lnum = 2 } },
|
||||
['S Y S T E M S I M P R O V E D '] = { 'syndaout', { start_lnum = 3 } },
|
||||
['Run Date: '] = { 'takcmp', { start_lnum = 6 } },
|
||||
['Node File 1'] = { 'sindacmp', { start_lnum = 9 } },
|
||||
function(contents)
|
||||
require('vim.filetype.detect').dns_zone(contents)
|
||||
end,
|
||||
-- Valgrind
|
||||
['^==%d+== valgrind'] = 'valgrind',
|
||||
['^==%d+== Using valgrind'] = { 'valgrind', { start_lnum = 3 } },
|
||||
-- Go docs
|
||||
['PACKAGE DOCUMENTATION$'] = 'godoc',
|
||||
-- Renderman Interface Bytestream
|
||||
['^##RenderMan'] = 'rib',
|
||||
-- Scheme scripts
|
||||
['exec%s%+%S*scheme'] = { 'scheme', { start_lnum = 1, end_lnum = 2 } },
|
||||
-- Git output
|
||||
['^\\(commit\\|tree\\|object\\) \\x\\{40,\\}\\>\\|^tag \\S\\+$'] = { 'git', { vim_regex = true } },
|
||||
function(lines)
|
||||
-- Gprof (gnu profiler)
|
||||
if lines[1] == 'Flat profile:' and lines[2] == '' and lines[3]:find('^Each sample counts as .* seconds%.$') then
|
||||
return 'gprof'
|
||||
end
|
||||
end,
|
||||
-- Erlang terms
|
||||
-- (See also: http://www.gnu.org/software/emacs/manual/html_node/emacs/Choosing-Modes.html#Choosing-Modes)
|
||||
['%-%*%-.*erlang.*%-%*%-'] = { 'erlang', { ignore_case = true } },
|
||||
-- YAML
|
||||
['^%%YAML'] = 'yaml',
|
||||
-- MikroTik RouterOS script
|
||||
['^#.*by RouterOS'] = 'routeros',
|
||||
-- Sed scripts
|
||||
-- #ncomment is allowed but most likely a false positive so require a space before any trailing comment text
|
||||
['^#n%s'] = 'sed',
|
||||
['^#n$'] = 'sed',
|
||||
}
|
||||
|
||||
---@private
|
||||
-- File does not start with "#!".
|
||||
local function match_from_text(contents, path)
|
||||
if contents[1]:find('^:$') then
|
||||
-- Bourne-like shell scripts: sh ksh bash bash2
|
||||
return M.sh(path, contents)
|
||||
elseif matchregex('\n' .. table.concat(contents, '\n'), [[\n\s*emulate\s\+\%(-[LR]\s\+\)\=[ckz]\=sh\>]]) then
|
||||
-- Z shell scripts
|
||||
return 'zsh'
|
||||
end
|
||||
|
||||
for k, v in pairs(patterns_text) do
|
||||
if type(v) == 'string' then
|
||||
-- Check the first line only
|
||||
if contents[1]:find(k) then
|
||||
return v
|
||||
end
|
||||
elseif type(v) == 'function' then
|
||||
-- If filetype detection fails, continue with the next pattern
|
||||
local ok, ft = pcall(v, contents)
|
||||
if ok and ft then
|
||||
return ft
|
||||
end
|
||||
else
|
||||
local opts = type(v) == 'table' and v[2] or {}
|
||||
if opts.start_lnum and opts.end_lnum then
|
||||
assert(not opts.ignore_case, 'ignore_case=true is ignored when start_lnum is also present, needs refactor')
|
||||
for i = opts.start_lnum, opts.end_lnum do
|
||||
if not contents[i] then
|
||||
break
|
||||
elseif contents[i]:find(k) then
|
||||
return v[1]
|
||||
end
|
||||
end
|
||||
else
|
||||
local line_nr = opts.start_lnum == -1 and #contents or opts.start_lnum or 1
|
||||
if contents[line_nr] then
|
||||
local line = opts.ignore_case and contents[line_nr]:lower() or contents[line_nr]
|
||||
if opts.vim_regex and matchregex(line, k) or line:find(k) then
|
||||
return v[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return cvs_diff(path, contents)
|
||||
end
|
||||
|
||||
M.match_contents = function(contents, path)
|
||||
local first_line = contents[1]
|
||||
if first_line:find('^#!') then
|
||||
return match_from_hashbang(contents, path)
|
||||
else
|
||||
return match_from_text(contents, path)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
@@ -11,9 +11,9 @@
|
||||
" 'ignorecase' option making a difference. Where case is to be ignored use
|
||||
" =~? instead. Do not use =~ anywhere.
|
||||
|
||||
|
||||
" Only do the rest when the FileType autocommand has not been triggered yet.
|
||||
if did_filetype()
|
||||
" Only do the rest when not using Lua filetype detection
|
||||
" and the FileType autocommand has not been triggered yet.
|
||||
if exists("g:do_filetype_lua") && g:do_filetype_lua || did_filetype()
|
||||
finish
|
||||
endif
|
||||
|
||||
|
Reference in New Issue
Block a user