aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua')
-rw-r--r--runtime/lua/_vim9script.lua650
-rw-r--r--runtime/lua/editorconfig.lua246
-rw-r--r--runtime/lua/man.lua626
-rw-r--r--runtime/lua/nvim/health.lua406
-rw-r--r--runtime/lua/vim/F.lua8
-rw-r--r--runtime/lua/vim/_editor.lua69
-rw-r--r--runtime/lua/vim/_init_packages.lua6
-rw-r--r--runtime/lua/vim/_inspector.lua238
-rw-r--r--runtime/lua/vim/_meta.lua871
-rw-r--r--runtime/lua/vim/compat.lua12
-rw-r--r--runtime/lua/vim/diagnostic.lua358
-rw-r--r--runtime/lua/vim/filetype.lua162
-rw-r--r--runtime/lua/vim/filetype/detect.lua79
-rw-r--r--runtime/lua/vim/fs.lua175
-rw-r--r--runtime/lua/vim/health.lua15
-rw-r--r--runtime/lua/vim/highlight.lua3
-rw-r--r--runtime/lua/vim/inspect.lua32
-rw-r--r--runtime/lua/vim/keymap.lua39
-rw-r--r--runtime/lua/vim/lsp.lua481
-rw-r--r--runtime/lua/vim/lsp/buf.lua316
-rw-r--r--runtime/lua/vim/lsp/codelens.lua27
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua2
-rw-r--r--runtime/lua/vim/lsp/handlers.lua145
-rw-r--r--runtime/lua/vim/lsp/health.lua16
-rw-r--r--runtime/lua/vim/lsp/log.lua17
-rw-r--r--runtime/lua/vim/lsp/protocol.lua97
-rw-r--r--runtime/lua/vim/lsp/rpc.lua741
-rw-r--r--runtime/lua/vim/lsp/semantic_tokens.lua702
-rw-r--r--runtime/lua/vim/lsp/sync.lua2
-rw-r--r--runtime/lua/vim/lsp/util.lua207
-rw-r--r--runtime/lua/vim/secure.lua188
-rw-r--r--runtime/lua/vim/shared.lua167
-rw-r--r--runtime/lua/vim/treesitter.lua461
-rw-r--r--runtime/lua/vim/treesitter/health.lua21
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua177
-rw-r--r--runtime/lua/vim/treesitter/language.lua22
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua145
-rw-r--r--runtime/lua/vim/treesitter/playground.lua186
-rw-r--r--runtime/lua/vim/treesitter/query.lua180
-rw-r--r--runtime/lua/vim/ui.lua21
40 files changed, 6269 insertions, 2047 deletions
diff --git a/runtime/lua/_vim9script.lua b/runtime/lua/_vim9script.lua
new file mode 100644
index 0000000000..363d061451
--- /dev/null
+++ b/runtime/lua/_vim9script.lua
@@ -0,0 +1,650 @@
+-------------------------------------------------------------------------------
+-- This file is auto generated by vim9jit. Do not edit by hand.
+-- All content is in the source repository.
+-- Bugs should be reported to: github.com/tjdevries/vim9jit
+--
+-- In addition, this file is considered "private" by neovim. You should
+-- not expect any of the APIs, functions, etc to be stable. They are subject
+-- to change at any time.
+-------------------------------------------------------------------------------
+
+local vim9 = (function()
+ local M = {}
+
+ M.ternary = function(cond, if_true, if_false)
+ if cond then
+ if type(if_true) == 'function' then
+ return if_true()
+ else
+ return if_true
+ end
+ else
+ if type(if_false) == 'function' then
+ return if_false()
+ else
+ return if_false
+ end
+ end
+ end
+
+ M.fn_ref = function(module, name, copied, ...)
+ for _, val in ipairs({ ... }) do
+ table.insert(copied, val)
+ end
+
+ local funcref = name
+ if type(funcref) == 'function' then
+ return funcref(unpack(copied))
+ elseif type(funcref) == 'string' then
+ if vim.fn.exists('*' .. funcref) == 1 then
+ return vim.fn[funcref](unpack(copied))
+ end
+
+ if module[funcref] then
+ module[funcref](unpack(copied))
+ end
+
+ error('unknown function: ' .. funcref)
+ else
+ error(string.format('unable to call funcref: %s', funcref))
+ end
+ end
+
+ M.fn_mut = function(name, args, info)
+ local result = vim.fn._Vim9ScriptFn(name, args)
+ for idx, val in pairs(result[2]) do
+ M.replace(args[idx], val)
+ end
+
+ -- Substitute returning the reference to the
+ -- returned value
+ if info.replace then
+ return args[info.replace + 1]
+ end
+
+ return result[1]
+ end
+
+ M.replace = function(orig, new)
+ if type(orig) == 'table' and type(new) == 'table' then
+ for k in pairs(orig) do
+ orig[k] = nil
+ end
+
+ for k, v in pairs(new) do
+ orig[k] = v
+ end
+
+ return orig
+ end
+
+ return new
+ end
+
+ M.index = function(obj, idx)
+ if vim.tbl_islist(obj) then
+ if idx < 0 then
+ return obj[#obj + idx + 1]
+ else
+ return obj[idx + 1]
+ end
+ elseif type(obj) == 'table' then
+ return obj[idx]
+ elseif type(obj) == 'string' then
+ return string.sub(obj, idx + 1, idx + 1)
+ end
+
+ error('invalid type for indexing: ' .. vim.inspect(obj))
+ end
+
+ M.index_expr = function(idx)
+ if type(idx) == 'string' then
+ return idx
+ elseif type(idx) == 'number' then
+ return idx + 1
+ else
+ error(string.format('not yet handled: %s', vim.inspect(idx)))
+ end
+ end
+
+ M.slice = function(obj, start, finish)
+ if start == nil then
+ start = 0
+ end
+
+ if start < 0 then
+ start = #obj + start
+ end
+ assert(type(start) == 'number')
+
+ if finish == nil then
+ finish = #obj
+ end
+
+ if finish < 0 then
+ finish = #obj + finish
+ end
+ assert(type(finish) == 'number')
+
+ local slicer
+ if vim.tbl_islist(obj) then
+ slicer = vim.list_slice
+ elseif type(obj) == 'string' then
+ slicer = string.sub
+ else
+ error('invalid type for slicing: ' .. vim.inspect(obj))
+ end
+
+ return slicer(obj, start + 1, finish + 1)
+ end
+
+ -- Currently unused, but this could be used to embed vim9jit within a
+ -- running nvim application and transpile "on the fly" as files are
+ -- sourced. There would still need to be some work done to make that
+ -- work correctly with imports and what not, but overall it could
+ -- work well for calling ":source X" from within a vimscript/vim9script
+ -- function
+ M.make_source_cmd = function()
+ local group = vim.api.nvim_create_augroup('vim9script-source', {})
+ vim.api.nvim_create_autocmd('SourceCmd', {
+ pattern = '*.vim',
+ group = group,
+ callback = function(a)
+ local file = vim.fn.readfile(a.file)
+ for _, line in ipairs(file) do
+ -- TODO: Or starts with def <something>
+ -- You can use def in legacy vim files
+ if vim.startswith(line, 'vim9script') then
+ -- TODO: Use the rust lib to actually
+ -- generate the corresponding lua code and then
+ -- execute that (instead of sourcing it directly)
+ return
+ end
+ end
+
+ vim.api.nvim_exec(table.concat(file, '\n'), false)
+ end,
+ })
+ end
+
+ M.iter = function(expr)
+ if vim.tbl_islist(expr) then
+ return ipairs(expr)
+ else
+ return pairs(expr)
+ end
+ end
+
+ M.ITER_DEFAULT = 0
+ M.ITER_CONTINUE = 1
+ M.ITER_BREAK = 2
+ M.ITER_RETURN = 3
+
+ return M
+end)()
+
+vim.cmd([[
+function! _Vim9ScriptFn(name, args) abort
+ try
+ let ret = function(a:name, a:args)()
+ catch
+ echo "Failed..."
+ echo a:name
+ echo a:args
+
+ throw v:errmsg
+ endtry
+
+ return [ret, a:args]
+endfunction
+]])
+
+vim9['autoload'] = (function()
+ return function(path)
+ return loadfile(path)()
+ end
+end)()
+vim9['bool'] = (function()
+ return function(...)
+ return vim9.convert.to_vim_bool(...)
+ end
+end)()
+vim9['convert'] = (function()
+ local M = {}
+
+ M.decl_bool = function(val)
+ if type(val) == 'boolean' then
+ return val
+ elseif type(val) == 'number' then
+ if val == 0 then
+ return false
+ elseif val == 1 then
+ return true
+ else
+ error(string.format('bad number passed to bool declaration: %s', val))
+ end
+ end
+
+ error(string.format('invalid bool declaration: %s', vim.inspect(val)))
+ end
+
+ M.decl_dict = function(val)
+ if type(val) == 'nil' then
+ return vim.empty_dict()
+ elseif type(val) == 'table' then
+ if vim.tbl_isempty(val) then
+ return vim.empty_dict()
+ elseif vim.tbl_islist(val) then
+ error(string.format('Cannot pass list to dictionary? %s', vim.inspect(val)))
+ else
+ return val
+ end
+ end
+
+ error(string.format('invalid dict declaration: %s', vim.inspect(val)))
+ end
+
+ M.to_vim_bool = function(val)
+ if type(val) == 'boolean' then
+ return val
+ elseif type(val) == 'number' then
+ return val ~= 0
+ elseif type(val) == 'string' then
+ return string.len(val) ~= 0
+ elseif type(val) == 'table' then
+ return not vim.tbl_isempty(val)
+ elseif val == nil then
+ return false
+ end
+
+ error('unhandled type: ' .. vim.inspect(val))
+ end
+
+ return M
+end)()
+vim9['fn'] = (function()
+ local M = {}
+
+ M.insert = function(list, item, idx)
+ if idx == nil then
+ idx = 1
+ end
+
+ table.insert(list, idx + 1, item)
+
+ return list
+ end
+
+ M.extend = function(left, right, expr3)
+ if expr3 ~= nil then
+ error("haven't written this code yet")
+ end
+
+ if vim.tbl_islist(right) then
+ vim.list_extend(left, right)
+ return left
+ else
+ -- local result = vim.tbl_extend(left, right)
+ for k, v in pairs(right) do
+ left[k] = v
+ end
+
+ return left
+ end
+ end
+
+ M.add = function(list, item)
+ table.insert(list, item)
+ return list
+ end
+
+ M.has_key = function(obj, key)
+ return not not obj[key]
+ end
+
+ M.prop_type_add = function(...)
+ local args = { ... }
+ print('[prop_type_add]', vim.inspect(args))
+ end
+
+ do
+ local has_overrides = {
+ -- We do have vim9script ;) that's this plugin
+ ['vim9script'] = true,
+
+ -- Include some vim patches that are sometimes required by variuos vim9script plugins
+ -- that we implement via vim9jit
+ [ [[patch-8.2.2261]] ] = true,
+ [ [[patch-8.2.4257]] ] = true,
+ }
+
+ M.has = function(patch)
+ if has_overrides[patch] then
+ return true
+ end
+
+ return vim.fn.has(patch)
+ end
+ end
+
+ --[=[
+Currently missing patch, can be removed in the future.
+
+readdirex({directory} [, {expr} [, {dict}]]) *readdirex()*
+ Extended version of |readdir()|.
+ Return a list of Dictionaries with file and directory
+ information in {directory}.
+ This is useful if you want to get the attributes of file and
+ directory at the same time as getting a list of a directory.
+ This is much faster than calling |readdir()| then calling
+ |getfperm()|, |getfsize()|, |getftime()| and |getftype()| for
+ each file and directory especially on MS-Windows.
+ The list will by default be sorted by name (case sensitive),
+ the sorting can be changed by using the optional {dict}
+ argument, see |readdir()|.
+
+ The Dictionary for file and directory information has the
+ following items:
+ group Group name of the entry. (Only on Unix)
+ name Name of the entry.
+ perm Permissions of the entry. See |getfperm()|.
+ size Size of the entry. See |getfsize()|.
+ time Timestamp of the entry. See |getftime()|.
+ type Type of the entry.
+ On Unix, almost same as |getftype()| except:
+ Symlink to a dir "linkd"
+ Other symlink "link"
+ On MS-Windows:
+ Normal file "file"
+ Directory "dir"
+ Junction "junction"
+ Symlink to a dir "linkd"
+ Other symlink "link"
+ Other reparse point "reparse"
+ user User name of the entry's owner. (Only on Unix)
+ On Unix, if the entry is a symlink, the Dictionary includes
+ the information of the target (except the "type" item).
+ On MS-Windows, it includes the information of the symlink
+ itself because of performance reasons.
+--]=]
+ M.readdirex = function(dir)
+ local files = vim.fn.readdir(dir)
+ local direx = {}
+ for _, f in ipairs(files) do
+ table.insert(direx, {
+ name = f,
+ type = vim.fn.getftype(f),
+ })
+ end
+
+ return direx
+ end
+
+ M.mapnew = function(tbl, expr)
+ return vim.fn.map(tbl, expr)
+ end
+
+ M.typename = function(val)
+ local ty = type(val)
+ if ty == 'string' then
+ return 'string'
+ elseif ty == 'boolean' then
+ return 'bool'
+ elseif ty == 'number' then
+ return 'number'
+ else
+ error(string.format('typename: %s', val))
+ end
+ end
+
+ -- Popup menu stuff: Could be rolled into other plugin later
+ -- but currently is here for testing purposes (and implements
+ -- some very simple compat layers at the moment)
+ do
+ local pos_map = {
+ topleft = 'NW',
+ topright = 'NE',
+ botleft = 'SW',
+ botright = 'SE',
+ }
+
+ M.popup_menu = function(_, options)
+ -- print "OPTIONS:"
+
+ local buf = vim.api.nvim_create_buf(false, true)
+ local win = vim.api.nvim_open_win(buf, true, {
+ relative = 'editor',
+ style = 'minimal',
+ anchor = pos_map[options.pos],
+ height = options.maxheight or options.minheight,
+ width = options.maxwidth or options.minwidth,
+ row = options.line,
+ col = options.col,
+ })
+
+ if options.filter then
+ local loop
+ loop = function()
+ vim.cmd([[redraw!]])
+ local ok, ch = pcall(vim.fn.getcharstr)
+ if not ok then
+ return
+ end -- interrupted
+
+ if ch == '<C-C>' then
+ return
+ end
+
+ if not require('vim9script').bool(options.filter(nil, ch)) then
+ vim.cmd.normal(ch)
+ end
+
+ vim.schedule(loop)
+ end
+
+ vim.schedule(loop)
+ end
+
+ return win
+ end
+
+ M.popup_settext = function(id, text)
+ if type(text) == 'string' then
+ -- text = vim.split(text, "\n")
+ error("Haven't handled string yet")
+ end
+
+ local lines = {}
+ for _, obj in ipairs(text) do
+ table.insert(lines, obj.text)
+ end
+
+ vim.api.nvim_buf_set_lines(vim.api.nvim_win_get_buf(id), 0, -1, false, lines)
+ end
+
+ M.popup_filter_menu = function()
+ print('ok, just pretend we filtered the menu')
+ end
+
+ M.popup_setoptions = function(id, _)
+ print('setting options...', id)
+ end
+ end
+
+ M = setmetatable(M, {
+ __index = vim.fn,
+ })
+
+ return M
+end)()
+vim9['heredoc'] = (function()
+ local M = {}
+
+ M.trim = function(lines)
+ local min_whitespace = 9999
+ for _, line in ipairs(lines) do
+ local _, finish = string.find(line, '^%s*')
+ min_whitespace = math.min(min_whitespace, finish)
+ end
+
+ local trimmed_lines = {}
+ for _, line in ipairs(lines) do
+ table.insert(trimmed_lines, string.sub(line, min_whitespace + 1))
+ end
+
+ return trimmed_lines
+ end
+
+ return M
+end)()
+vim9['import'] = (function()
+ local imported = {}
+ imported.autoload = setmetatable({}, {
+ __index = function(_, name)
+ local luaname = 'autoload/' .. string.gsub(name, '%.vim$', '.lua')
+ local runtime_file = vim.api.nvim_get_runtime_file(luaname, false)[1]
+ if not runtime_file then
+ error('unable to find autoload file:' .. name)
+ end
+
+ return imported.absolute[vim.fn.fnamemodify(runtime_file, ':p')]
+ end,
+ })
+
+ imported.absolute = setmetatable({}, {
+ __index = function(self, name)
+ if vim.loop.fs_stat(name) then
+ local result = loadfile(name)()
+ rawset(self, name, result)
+
+ return result
+ end
+
+ error(string.format('unabled to find absolute file: %s', name))
+ end,
+ })
+
+ return function(info)
+ local name = info.name
+
+ if info.autoload then
+ return imported.autoload[info.name]
+ end
+
+ local debug_info = debug.getinfo(2, 'S')
+ local sourcing_path = vim.fn.fnamemodify(string.sub(debug_info.source, 2), ':p')
+
+ -- Relative paths
+ if vim.startswith(name, '../') or vim.startswith(name, './') then
+ local luaname = string.gsub(name, '%.vim$', '.lua')
+ local directory = vim.fn.fnamemodify(sourcing_path, ':h')
+ local search = directory .. '/' .. luaname
+ return imported.absolute[search]
+ end
+
+ if vim.startswith(name, '/') then
+ error('absolute path')
+ -- local luaname = string.gsub(name, "%.vim", ".lua")
+ -- local runtime_file = vim.api.nvim_get_runtime_file(luaname, false)[1]
+ -- if runtime_file then
+ -- runtime_file = vim.fn.fnamemodify(runtime_file, ":p")
+ -- return loadfile(runtime_file)()
+ -- end
+ end
+
+ error('Unhandled case' .. vim.inspect(info) .. vim.inspect(debug_info))
+ end
+end)()
+vim9['ops'] = (function()
+ local lib = vim9
+
+ local M = {}
+
+ M['And'] = function(left, right)
+ return lib.bool(left) and lib.bool(right)
+ end
+
+ M['Or'] = function(left, right)
+ return lib.bool(left) or lib.bool(right)
+ end
+
+ M['Plus'] = function(left, right)
+ return left + right
+ end
+
+ M['Multiply'] = function(left, right)
+ return left * right
+ end
+
+ M['Divide'] = function(left, right)
+ return left / right
+ end
+
+ M['StringConcat'] = function(left, right)
+ return left .. right
+ end
+
+ M['EqualTo'] = function(left, right)
+ return left == right
+ end
+
+ M['NotEqualTo'] = function(left, right)
+ return not M['EqualTo'](left, right)
+ end
+
+ M['LessThan'] = function(left, right)
+ return left < right
+ end
+
+ M['LessThanOrEqual'] = function(left, right)
+ return left <= right
+ end
+
+ M['GreaterThan'] = function(left, right)
+ return left > right
+ end
+
+ M['GreaterThanOrEqual'] = function(left, right)
+ return left >= right
+ end
+
+ M['RegexpMatches'] = function(left, right)
+ return not not vim.regex(right):match_str(left)
+ end
+
+ M['RegexpMatchesIns'] = function(left, right)
+ return not not vim.regex('\\c' .. right):match_str(left)
+ end
+
+ M['NotRegexpMatches'] = function(left, right)
+ return not M['RegexpMatches'](left, right)
+ end
+
+ M['Modulo'] = function(left, right)
+ return left % right
+ end
+
+ M['Minus'] = function(left, right)
+ -- TODO: This is not right :)
+ return left - right
+ end
+
+ return M
+end)()
+vim9['prefix'] = (function()
+ local lib = vim9
+
+ local M = {}
+
+ M['Minus'] = function(right)
+ return -right
+ end
+
+ M['Bang'] = function(right)
+ return not lib.bool(right)
+ end
+
+ return M
+end)()
+
+return vim9
diff --git a/runtime/lua/editorconfig.lua b/runtime/lua/editorconfig.lua
new file mode 100644
index 0000000000..2079006234
--- /dev/null
+++ b/runtime/lua/editorconfig.lua
@@ -0,0 +1,246 @@
+local M = {}
+
+M.properties = {}
+
+--- Modified version of the builtin assert that does not include error position information
+---
+---@param v any Condition
+---@param message string Error message to display if condition is false or nil
+---@return any v if not false or nil, otherwise an error is displayed
+---
+---@private
+local function assert(v, message)
+ return v or error(message, 0)
+end
+
+--- Show a warning message
+---
+---@param msg string Message to show
+---
+---@private
+local function warn(msg, ...)
+ vim.notify(string.format(msg, ...), vim.log.levels.WARN, {
+ title = 'editorconfig',
+ })
+end
+
+function M.properties.charset(bufnr, val)
+ assert(
+ vim.tbl_contains({ 'utf-8', 'utf-8-bom', 'latin1', 'utf-16be', 'utf-16le' }, val),
+ 'charset must be one of "utf-8", "utf-8-bom", "latin1", "utf-16be", or "utf-16le"'
+ )
+ if val == 'utf-8' or val == 'utf-8-bom' then
+ vim.bo[bufnr].fileencoding = 'utf-8'
+ vim.bo[bufnr].bomb = val == 'utf-8-bom'
+ elseif val == 'utf-16be' then
+ vim.bo[bufnr].fileencoding = 'utf-16'
+ else
+ vim.bo[bufnr].fileencoding = val
+ end
+end
+
+function M.properties.end_of_line(bufnr, val)
+ vim.bo[bufnr].fileformat = assert(
+ ({ lf = 'unix', crlf = 'dos', cr = 'mac' })[val],
+ 'end_of_line must be one of "lf", "crlf", or "cr"'
+ )
+end
+
+function M.properties.indent_style(bufnr, val, opts)
+ assert(val == 'tab' or val == 'space', 'indent_style must be either "tab" or "space"')
+ vim.bo[bufnr].expandtab = val == 'space'
+ if val == 'tab' and not opts.indent_size then
+ vim.bo[bufnr].shiftwidth = 0
+ vim.bo[bufnr].softtabstop = 0
+ end
+end
+
+function M.properties.indent_size(bufnr, val, opts)
+ if val == 'tab' then
+ vim.bo[bufnr].shiftwidth = 0
+ vim.bo[bufnr].softtabstop = 0
+ else
+ local n = assert(tonumber(val), 'indent_size must be a number')
+ vim.bo[bufnr].shiftwidth = n
+ vim.bo[bufnr].softtabstop = -1
+ if not opts.tab_width then
+ vim.bo[bufnr].tabstop = n
+ end
+ end
+end
+
+function M.properties.tab_width(bufnr, val)
+ vim.bo[bufnr].tabstop = assert(tonumber(val), 'tab_width must be a number')
+end
+
+function M.properties.max_line_length(bufnr, val)
+ local n = tonumber(val)
+ if n then
+ vim.bo[bufnr].textwidth = n
+ else
+ assert(val == 'off', 'max_line_length must be a number or "off"')
+ vim.bo[bufnr].textwidth = 0
+ end
+end
+
+function M.properties.trim_trailing_whitespace(bufnr, val)
+ assert(
+ val == 'true' or val == 'false',
+ 'trim_trailing_whitespace must be either "true" or "false"'
+ )
+ if val == 'true' then
+ vim.api.nvim_create_autocmd('BufWritePre', {
+ group = 'editorconfig',
+ buffer = bufnr,
+ callback = function()
+ local view = vim.fn.winsaveview()
+ vim.api.nvim_command('silent! undojoin')
+ vim.api.nvim_command('silent keepjumps keeppatterns %s/\\s\\+$//e')
+ vim.fn.winrestview(view)
+ end,
+ })
+ else
+ vim.api.nvim_clear_autocmds({
+ event = 'BufWritePre',
+ group = 'editorconfig',
+ buffer = bufnr,
+ })
+ end
+end
+
+function M.properties.insert_final_newline(bufnr, val)
+ assert(val == 'true' or val == 'false', 'insert_final_newline must be either "true" or "false"')
+ vim.bo[bufnr].fixendofline = val == 'true'
+ vim.bo[bufnr].endofline = val == 'true'
+end
+
+--- Modified version of |glob2regpat()| that does not match path separators on *.
+---
+--- This function replaces single instances of * with the regex pattern [^/]*. However, the star in
+--- the replacement pattern also gets interpreted by glob2regpat, so we insert a placeholder, pass
+--- it through glob2regpat, then replace the placeholder with the actual regex pattern.
+---
+---@param glob string Glob to convert into a regular expression
+---@return string Regular expression
+---
+---@private
+local function glob2regpat(glob)
+ local placeholder = '@@PLACEHOLDER@@'
+ return (
+ string.gsub(
+ vim.fn.glob2regpat(
+ vim.fn.substitute(
+ string.gsub(glob, '{(%d+)%.%.(%d+)}', '[%1-%2]'),
+ '\\*\\@<!\\*\\*\\@!',
+ placeholder,
+ 'g'
+ )
+ ),
+ placeholder,
+ '[^/]*'
+ )
+ )
+end
+
+--- Parse a single line in an EditorConfig file
+---
+---@param line string Line
+---@return string|nil If the line contains a pattern, the glob pattern
+---@return string|nil If the line contains a key-value pair, the key
+---@return string|nil If the line contains a key-value pair, the value
+---
+---@private
+local function parse_line(line)
+ if line:find('^%s*[^ #;]') then
+ local glob = (line:match('%b[]') or ''):match('^%s*%[(.*)%]%s*$')
+ if glob then
+ return glob, nil, nil
+ end
+
+ local key, val = line:match('^%s*([^:= ][^:=]-)%s*[:=]%s*(.-)%s*$')
+ if key ~= nil and val ~= nil then
+ return nil, key:lower(), val:lower()
+ end
+ end
+end
+
+--- Parse options from an .editorconfig file
+---
+---@param filepath string File path of the file to apply EditorConfig settings to
+---@param dir string Current directory
+---@return table Table of options to apply to the given file
+---
+---@private
+local function parse(filepath, dir)
+ local pat = nil
+ local opts = {}
+ local f = io.open(dir .. '/.editorconfig')
+ if f then
+ for line in f:lines() do
+ local glob, key, val = parse_line(line)
+ if glob then
+ glob = glob:find('/') and (dir .. '/' .. glob:gsub('^/', '')) or ('**/' .. glob)
+ local ok, regpat = pcall(glob2regpat, glob)
+ if ok then
+ pat = vim.regex(regpat)
+ else
+ pat = nil
+ warn('editorconfig: Error occurred while parsing glob pattern "%s": %s', glob, regpat)
+ end
+ elseif key ~= nil and val ~= nil then
+ if key == 'root' then
+ opts.root = val == 'true'
+ elseif pat and pat:match_str(filepath) then
+ opts[key] = val
+ end
+ end
+ end
+ f:close()
+ end
+ return opts
+end
+
+--- Configure the given buffer with options from an .editorconfig file
+---
+---@param bufnr number Buffer number to configure
+---
+---@private
+function M.config(bufnr)
+ bufnr = bufnr or vim.api.nvim_get_current_buf()
+ local path = vim.fs.normalize(vim.api.nvim_buf_get_name(bufnr))
+ if vim.bo[bufnr].buftype ~= '' or not vim.bo[bufnr].modifiable or path == '' then
+ return
+ end
+
+ local opts = {}
+ for parent in vim.fs.parents(path) do
+ for k, v in pairs(parse(path, parent)) do
+ if opts[k] == nil then
+ opts[k] = v
+ end
+ end
+
+ if opts.root then
+ break
+ end
+ end
+
+ local applied = {}
+ for opt, val in pairs(opts) do
+ if val ~= 'unset' then
+ local func = M.properties[opt]
+ if func then
+ local ok, err = pcall(func, bufnr, val, opts)
+ if ok then
+ applied[opt] = val
+ else
+ warn('editorconfig: invalid value for option %s: %s. %s', opt, val, err)
+ end
+ end
+ end
+ end
+
+ vim.b[bufnr].editorconfig = applied
+end
+
+return M
diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua
index 5da3d2a92f..732a4ab92e 100644
--- a/runtime/lua/man.lua
+++ b/runtime/lua/man.lua
@@ -1,7 +1,89 @@
-require('vim.compat')
+local api, fn = vim.api, vim.fn
+local FIND_ARG = '-w'
+local localfile_arg = true -- Always use -l if possible. #668