1

Regenerate nvim config

This commit is contained in:
2024-06-02 03:29:20 +02:00
parent 75eea0c030
commit ef2e28883d
5576 changed files with 604886 additions and 503 deletions

View File

@ -0,0 +1,6 @@
vim.notify(
"'lua-dev' was renamed to 'neodev'. Please update your config.",
vim.log.levels.WARN,
{ title = "neodev.nvim" }
)
return require("neodev")

View File

@ -0,0 +1,261 @@
---@class LuaParam
---@field name? string
---@field type? string
---@field doc? string
---@field optional? boolean
--- @class LuaFunction
--- @field name string,
--- @field doc string,
--- @field deprecated? boolean
--- @field overload? string[]
--- @field params LuaParam[]
--- @field return LuaParam[]
local M = {}
M.name2type = {
boolean = { "set" },
buffer = { "buffer", "bufid", "bufnr", "buf" },
window = { "win", "window", "winid", "winnr" },
number = { "col", "lnum", "tabnr", "nr", "pos", "ns", "index", "from", "to", "start", "stop", "end_", "integer" },
string = { "str", "text", "string" },
["table<string, any>"] = { "opt", "opts", "options", "dict", "dictionary" },
["fun()"] = { "fn", "function", "callback", "func", "funcref", "luaref" },
["any[]"] = { "args", "list", "array" },
["nil"] = { "void", "none" },
}
M.keywords =
{ "or", "and", "repeat", "function", "end", "return", "do", "break", "else", "elseif", "for", "goto", "if", "while" }
M.nvim_types = {
window = "integer",
buffer = "integer",
tabpage = "integer",
job = "number",
channel = "integer",
sends = "number",
blob = "number",
object = "any",
float = "number",
}
M.lua_types = {
["nil"] = "nil",
number = "number",
string = "string",
boolean = "boolean",
["function"] = "fun()",
table = "table",
}
function M.is_keyword(str)
return vim.tbl_contains(M.keywords, str)
end
---@param str string
---@param first? string
function M.comment(str, first)
first = first or ""
local prefix = "-- "
return first .. prefix .. str:gsub("\n", "\n" .. prefix)
end
---@param param LuaParam
function M.type(param)
local type = param.type and param.type ~= "" and param.type or "any"
if type == "any" then
for t, names in pairs(M.name2type) do
if vim.tbl_contains(names, param.name) then
return t
end
end
end
for t, names in pairs(M.name2type) do
if vim.tbl_contains(names, type) then
return t
end
end
if M.nvim_types[type] then
return type
end
if M.lua_types[type] then
return M.lua_types[type]
end
if type == "arrayof(string)" then
type = "string[]"
elseif type == "arrayof(integer, 2)" then
type = "number[]"
elseif type == "dictionaryof(luaref)" then
type = "table<string, luaref>"
elseif type:find("^arrayof%(") then
return "any[]"
elseif type:find("^dict%(") or type:find("^dictionaryof%(") then
return "table<string, any>"
end
return type
end
---@param param LuaParam
function M.param(param)
local parts = {}
if param.name then
if M.is_keyword(param.name) then
param.name = param.name .. "_"
end
table.insert(parts, param.name .. (param.optional and "?" or ""))
end
local type = M.type(param)
if type == "nil" then
return ""
end
if type then
table.insert(parts, type)
end
if param.doc then
table.insert(parts, "# " .. param.doc)
end
if not param.doc and type == "any" and not param.optional then
return ""
end
local ret = table.concat(parts, " ")
if not param.name then
return M.comment("@return " .. ret, "-") .. "\n"
else
return M.comment("@param " .. ret, "-") .. "\n"
end
end
function M.fqn(name)
local real_fn = vim.tbl_get(_G, unpack(vim.split(name, ".", { plain = true })))
if vim.api[name] then
return "vim.api." .. name
elseif vim[name] then
return "vim." .. name
elseif name:find("^[a-zA-Z_]+$") and vim.fn.exists("*" .. name) == 1 then
return "vim.fn." .. name
elseif name:find("^vim") and real_fn then
return name
end
-- if we get here, it means the function is RPC only, or no longer exists
end
function M.is_lua(name)
local real_fn = vim.tbl_get(_G, unpack(vim.split(name, ".", { plain = true })))
-- some plugins (like Noice) wrap api functions. This deals with that
if real_fn and name:find("vim%.api%.") then
return false
elseif type(real_fn) == "function" then
local info = debug.getinfo(real_fn, "S")
return info.what == "Lua"
elseif type(real_fn) == "table" then
return true
elseif not real_fn then
return true
end
return false
end
--- @param fun LuaFunction
function M.fun(fun)
local ret = ""
if fun.doc ~= "" then
-- make markdown lua code blocks for code regions
local ft = fun.name:find("vim.fn") and "vim" or "lua"
local lines = vim.split(fun.doc, "\n")
local l = 1
while l < #lines do
local line = lines[l]
local from, to, before, lang = line:find("^(%s*.*)>([a-z]*)%s*$")
if from then
before = (not before:find("^%s*$")) and before or nil
lang = lang ~= "" and lang or nil
for i = l + 1, #lines do
if lines[i]:find("^%S") or lines[i]:find("^%s*<") or i == #lines then
lines[l] = (before and (before .. "\n") or "") .. "```" .. (lang or ft)
if lines[i]:find("^%s*<%s*$") then
lines[i] = "```"
elseif lines[i]:find("^%s*<") then
lines[i] = "```\n" .. lines[i]:gsub("<", "")
else
lines[i] = lines[i] .. "\n```"
end
l = i
break
end
end
end
l = l + 1
end
local doc = table.concat(lines, "\n")
doc = M.fix_indent(doc)
ret = ret .. (M.comment(doc)) .. "\n"
end
if fun.seealso and #fun.seealso > 0 then
for _, also in ipairs(fun.seealso) do
ret = ret .. "--- @see " .. also .. "\n"
end
end
local params = {}
for _, param in pairs(fun.params) do
ret = ret .. M.param(param)
table.insert(params, param.name)
end
for _, r in pairs(fun["return"]) do
ret = ret .. M.param(r)
end
local signature = "function %s(%s) end"
-- handle special Lua names. Set as a field instead of a function
if M.is_keyword(fun.name:match("[^.]+$")) then
local prefix, name = fun.name:match("(.*)%.([^.]+)$")
fun.name = name
signature = prefix .. "[%q] = function(%s) end"
end
if fun.overload then
for _, overload in ipairs(fun.overload) do
ret = ret .. "--- @overload " .. overload .. "\n"
end
end
ret = ret .. signature:format(fun.name, table.concat(params, ", "))
return ret .. "\n\n"
end
---@param text string
function M.fix_indent(text)
local lines = vim.split(text, "\n")
local indent = 10
for l, line in ipairs(lines) do
if not (line:find("^%s*$") or line:find("^```")) then
line = line:gsub(" ", "\t")
lines[l] = line
local prefix = line:match("^\t+")
if prefix then
indent = math.min(indent, #prefix)
end
end
end
if indent > 0 then
for l, line in ipairs(lines) do
lines[l] = line:gsub("^" .. ("\t"):rep(indent), ""):gsub("\t", " ")
end
end
return table.concat(lines, "\n")
end
return M

View File

@ -0,0 +1,80 @@
local Annotations = require("neodev.build.annotations")
local Docs = require("neodev.build.docs")
---@class NvimApiInfo
---@field functions NvimApiFunction[]
---@class NvimApiFunction
---@field name string
---@field deprecated_since? number
---@field parameters {[1]: string, [2]:string}[]
---@field return_type string
---@field since number
---@field method? boolean
---@class LuaApiFunction: LuaFunction
---@field params_index? table<string, LuaParam>
---@field info? NvimApiFunction
local M = {}
---@return table<string, LuaFunction>
function M.get()
local functions = Docs.parse_functions("api", {
name = Annotations.fqn,
})
---@cast functions table<string, LuaApiFunction>
-- add params index
for _, fun in pairs(functions) do
fun.params_index = {}
for _, param in ipairs(fun.params) do
local name = param.name
if name then
fun.params_index[name] = param
end
end
end
---@type NvimApiInfo
local info = vim.fn.api_info()
-- add api info
for _, fun in ipairs(info.functions) do
if not fun.deprecated_since then
local name = Annotations.fqn(fun.name)
if name then
if not functions[name] then
functions[name] = {
doc = "",
name = name,
params = {},
params_index = {},
}
end
functions[name].info = fun
end
end
end
-- merge api info
for _, fun in pairs(functions) do
if fun.info then
for _, param in ipairs(fun.info.parameters) do
if fun.params_index[param[2]] then
fun.params_index[param[2]].type = param[1]:lower()
else
fun.params_index[param[2]] = { name = param[2], type = param[1]:lower() }
end
end
local return_type = fun.info.return_type:lower()
fun["return"] = { { type = return_type == "nil" and nil or return_type } }
end
end
return functions
end
M.get()
return M

View File

@ -0,0 +1,340 @@
local Util = require("neodev.util")
local Annotations = require("neodev.build.annotations")
local M = {}
M.function_pattern = "^(%S-%([^(]-%))"
M.function_signature_pattern = "^(%S-)%(([^(]-)%)"
M.vim_type_map = {
number = "number",
float = "float",
string = "string",
list = "any[]",
any = "any",
funcref = "fun()",
dict = "table<string, any>",
none = "nil",
set = "boolean",
boolean = "boolean",
}
---@param name string
function M.read(name)
local docs = vim.fn.expand("$VIMRUNTIME/doc")
local txtfile = docs .. "/" .. name .. ".txt"
---@type string[]
local lines = {}
for line in io.lines(txtfile) do
table.insert(lines, line)
end
return lines
end
---@return string, string[]
function M.strip_tags(str)
local tags = {}
return str
:gsub(
"(%*%S-%*)",
---@param tag string
function(tag)
tag = tag:sub(2, -2)
table.insert(tags, tag)
return ""
end
)
:gsub("%s*$", ""),
tags
end
---@param text string
function M.trim(text)
return text:gsub("^%s*\n", ""):gsub("\n+$", "")
end
---@param name string
---@param opts { pattern: string, continuation?: string, context?: number}
function M.parse(name, opts)
opts = opts or {}
opts.continuation = opts.continuation or "^[%s<>]"
opts.context = opts.context or 1
local tags = {}
local line_tags = {}
local chunk_tags = {}
local chunk_match = {}
local chunk = {}
---@type {tags:string[], text:string, match: string[]}[]
local ret = {}
local function save()
if #chunk > 0 then
table.insert(ret, {
tags = vim.deepcopy(chunk_tags),
text = M.trim(table.concat(chunk, "\n")),
match = vim.deepcopy(chunk_match),
})
end
chunk = {}
chunk_tags = {}
end
local lines = M.read(name)
for l, line in ipairs(lines) do
line, line_tags = M.strip_tags(line)
if #line_tags > 0 then
tags = line_tags
end
local context = line
for c = 1, opts.context do
if lines[l + c] then
context = context .. "\n" .. lines[l + c]
end
end
local match = { context:match(opts.pattern) }
if #match > 0 then
save()
chunk_match = match
chunk_tags = vim.deepcopy(tags)
table.insert(chunk, line)
elseif #chunk > 0 and (line:find(opts.continuation) or line:find("^%s*$")) then
table.insert(chunk, line)
else
save()
end
end
return ret
end
---@return {name: string, params: {name:string, optional?:boolean}[], doc: string}?
---@return LuaFunction?
function M.parse_signature(line)
---@type string, string, string
local name, sig, doc = line:match(M.function_signature_pattern .. "(.*)")
if name then
-- Parse args
local optional_from = sig:find("%[")
sig = sig:gsub("%[", "")
sig = sig:gsub("%]", "")
sig = sig:gsub("\n", " ")
sig = sig:gsub("\t", " ")
local params = {}
---@type table<string,boolean>
local index = {}
local from = 0
local to = 0
local param = ""
while from do
---@type number, number, string
from, to, param = sig:find("{?([^ ,{}]+)}?", to + 1)
if from then
local optional = optional_from and from >= optional_from and true or nil
if param:sub(1, 1) == "*" then
optional = true
param = param:sub(2)
end
param = param:gsub("%-", "_")
if param:find("^%d+$") then
param = "p" .. param
end
-- check for duplicate params
local p = param
local c = 1
while index[param] do
param = p .. c
c = c + 1
end
index[param] = true
table.insert(params, {
name = param,
optional = optional,
})
end
end
return { name = name, params = params, doc = M.trim(doc) }
end
end
function M.options()
---@type table<string, string>
local ret = {}
local option_pattern = "^'(%S-)'%s*"
local options = M.parse("options", { pattern = option_pattern })
for _, option in ipairs(options) do
local name = option.match[1]
local doc = option.text:gsub("'(%S-)'", "`'%1'` ")
ret[name] = doc
end
return ret
end
---@param doc string
---@param opts? {filter?: (fun(name:string):boolean), name?: (fun(name:string):string)}
function M.parse_functions(doc, opts)
opts = opts or {}
---@type table<string, LuaFunction>
local ret = {}
local functions = M.parse(doc, { pattern = M.function_pattern, context = 2 })
for _, fun in ipairs(functions) do
local text = fun.text
-- replace function name by the function tag, to make sure it is fully qualified
for _, tag in ipairs(fun.tags) do
if tag:find("vim.*%(%)$") then
tag = tag:sub(1, -3)
local name = text:match(M.function_signature_pattern)
if tag:sub(-#name) == name then
text = text:gsub("^%S-%(", tag .. "(")
end
end
end
local parse = M.parse_signature(text)
if parse then
local name = parse.name
if opts.name then
name = opts.name(name)
end
if name and (opts.filter == nil or opts.filter(name)) then
ret[name] = {
name = name,
params = parse.params,
doc = parse.doc,
["return"] = {},
}
end
end
end
return ret
end
function M.lua()
return M.parse_functions("lua", {
filter = function(name)
return not Annotations.is_lua(name)
end,
})
end
function M.luv()
local ret = M.parse_functions("luvref", {
filter = function(name)
return not Annotations.is_lua(name)
end,
name = function(name)
local ret = name:gsub("^uv%.", "vim.loop.")
return ret
end,
})
Util.for_each(ret, function(_, fun)
local returns = fun.doc:match("%s*Returns: (.*)\n")
if not returns then
returns = fun.doc:match("%s*Returns %(sync version%): (.*)\n")
end
---@type LuaParam
local retval = {}
if returns then
---@diagnostic disable-next-line: no-unknown
for t in returns:gmatch("`(.-)`") do
if t == "nil" or t == "fail" then
retval.optional = true
elseif not retval.type then
retval.type = t:find("userdata") and "userdata" or t
end
end
end
if not vim.tbl_isempty(retval) then
fun["return"] = { retval }
end
end)
return ret
end
function M.commands()
local pattern = "|:%S-|%s+:([a-z]%S-)%s+(.*)"
local builtins = M.parse("index", { pattern = pattern, context = 0, continuation = "^%s+" })
---@type table<string,string>
local ret = {}
for _, builtin in ipairs(builtins) do
if vim.tbl_contains(builtin.tags, "ex-cmd-index") then
local cmd = builtin.match[1]
local desc = builtin.match[2]
local i = cmd:find("%[")
if i then
ret[cmd:sub(1, i - 1)] = desc
cmd = cmd:gsub("[%[%]]", "")
end
ret[cmd] = desc
end
end
return ret
end
function M.functions()
local builtins = M.parse("builtin", { pattern = M.function_pattern, context = 2 })
---@type table<string, string>
local retvals = {}
-- Parse return values from `:h builtin-function-list`
for _, builtin in ipairs(builtins) do
if vim.tbl_contains(builtin.tags, "builtin-function-list") then
local text = builtin.text
-- replace any whitespace after the function by a tab character
text = text:gsub(M.function_pattern .. "%s+", "%1\t")
-- replace consecutive whitespace by tabs
text = text:gsub("%s%s+", "\t")
---@type string, string, string
local name, _args, retval = text:match(M.function_signature_pattern .. "\t(%w+)")
if name then
retval = retval:lower()
if M.vim_type_map[retval] then
retval = M.vim_type_map[retval]
if retval ~= "nil" then
retvals["vim.fn." .. name] = retval
end
else
Util.debug("Unknown retval: " .. retval)
end
else
Util.error("Couldnt parse builtin-function-list: " .. vim.inspect(builtin))
end
end
end
local ret = M.parse_functions("builtin", {
filter = function(name)
name = name:match("vim%.fn%.(.*)")
if name:find("%.") then
return false
end
return name and (vim.fn.exists("*" .. name) == 1)
end,
name = function(name)
return "vim.fn." .. name
end,
})
for k, fun in pairs(ret) do
if retvals[k] then
fun["return"] = { { type = retvals[k]:lower() } }
end
end
return ret
end
return M

View File

@ -0,0 +1,126 @@
local Config = require("neodev.config")
local Util = require("neodev.util")
local Annotations = require("neodev.build.annotations")
local Api = require("neodev.build.api")
local Docs = require("neodev.build.docs")
local Mpack = require("neodev.build.mpack")
local Writer = require("neodev.build.writer")
local Options = require("neodev.build.options")
local M = {}
function M.api()
local api = Api.get()
-- Only load mpack on nightly and add any missing functions
-- Typically hidden functions
if Config.version() == "nightly" then
local functions = Mpack.read("api.mpack")
for k, v in pairs(functions) do
if not api[k] then
api[k] = v
end
end
end
M.write("api", api)
end
---@return table<string, LuaFunction>, string?
function M.override(fname)
local override = Config.root("/types/override/" .. fname .. ".lua")
if override then
local code = Util.read_file(override)
local mod = {}
local mod_code = code:match("\n(return.*)") or code:match("^(return.*)")
if mod_code then
mod = load(mod_code)()
end
code = code:gsub("\nreturn.*", "")
code = code:gsub("^return.*", "")
return mod, code
end
return {}
end
---@param fname string
---@param functions table<string, LuaFunction>
function M.write(fname, functions)
local override, override_code = M.override(fname)
functions = vim.tbl_deep_extend("force", functions, override)
local writer = Writer(fname)
if override_code then
writer:write(override_code .. "\n\n")
end
Util.for_each(functions, function(_, fun)
writer:write(Annotations.fun(fun))
end)
writer:close()
end
function M.alias()
local writer = Writer("alias")
Util.for_each(Annotations.nvim_types, function(key, value)
writer:write(("---@alias %s %s"):format(key, value) .. "\n")
end)
writer:close()
end
function M.commands()
local writer = Writer("cmd")
Util.for_each(Docs.commands(), function(cmd, desc)
writer:write(Annotations.comment(desc) .. "\n")
if Annotations.is_keyword(cmd) then
writer:write(("vim.cmd[%q] = function(...)end"):format(cmd) .. "\n\n")
else
writer:write(("function vim.cmd.%s(...)end"):format(cmd) .. "\n\n")
end
end)
writer:close()
end
function M.clean()
local types = Config.types()
for _, f in pairs(vim.fn.expand(types .. "/*.lua", false, true)) do
if not f:find("/vim.lua", 1, true) then
vim.loop.fs_unlink(f)
end
end
end
function M.uv()
local writer = Writer("uv")
writer:write(Util.fetch("https://raw.githubusercontent.com/Bilal2453/luvit-meta/main/library/uv.lua"))
end
function M.lpeg()
local writer = Writer("lpeg")
writer:write(Util.fetch("https://raw.githubusercontent.com/LuaCATS/lpeg/main/library/lpeg.lua"))
end
function M.build()
M.clean()
M.uv()
M.alias()
M.commands()
if vim.fn.has("nvim-0.10") == 0 then
M.lpeg()
Options.build()
M.api()
-- M.write("luv", Docs.luv())
M.write("lua", Docs.lua())
M.write("vim.fn", Docs.functions())
end
end
M.build()
return M

View File

@ -0,0 +1,73 @@
local Annotations = require("neodev.build.annotations")
local util = require("neodev.util")
local M = {}
---@class MpackFunction
---@field doc string[]
---@field parameters {[1]: string, [2]:string}[]
---@field parameters_doc table<string, string>
---@field return string[]
---@field seealso string[]
---@field signature string
function M.read(mpack)
mpack = "data/" .. mpack
---@type table<string, MpackFunction>
local data = vim.mpack.decode(util.read_file(mpack))
---@type table<string, LuaFunction>
local ret = {}
for name, fun in pairs(data) do
name = Annotations.fqn(name)
if name then
ret[name] = M.process(name, fun)
end
end
return ret
end
---@param name string
--- @param fun MpackFunction
--- @return LuaFunction
function M.process(name, fun)
---@type LuaFunction
local ret = {
doc = (fun.doc and fun.doc[1]) and table.concat(fun.doc, "\n\n") or "",
name = name,
params = {},
seealso = fun.seealso or {},
["return"] = {},
}
for _, r in pairs(fun["return"]) do
table.insert(ret["return"], { doc = r })
end
for i, p in ipairs(fun.parameters or {}) do
local type = p[1]
local pname = p[2]
local param = { name = pname }
if type ~= "" then
param.type = type:lower()
end
param.doc = fun.parameters_doc and fun.parameters_doc[pname] or nil
if param.type and param.type:find("%*$") then
param.type = param.type:sub(1, -2)
param.optional = true
end
-- only include err param if it's documented
-- most nvim_ functions have an err param at the end, but these should not be included
local skip = i == #fun.parameters and (pname == "err" or pname == "error")
-- skip self params
if param.name == "self" or param.name == "" or param.name == "arena" then
skip = true
end
if not skip then
table.insert(ret.params, param)
end
end
return ret
end
return M

View File

@ -0,0 +1,106 @@
local Docs = require("neodev.build.docs")
local Writer = require("neodev.build.writer")
local Annotations = require("neodev.build.annotations")
local Util = require("neodev.util")
local Config = require("neodev.config")
---@class OptionInfo
---@field allows_duplicates boolean
---@field commalist boolean
---@field default any
---@field flaglist boolean
---@field global_local boolean
---@field name string
---@field scope "global" | "win" | "buf"
---@field shortname string
---@field type string
---@alias OptionsInfo table<string, OptionInfo>
local M = {}
M.metatype2lua = {
map = function(info)
return ("table<string, %s>"):format(info.type)
end,
set = function(info)
return ("%s[]"):format(info.type)
end,
array = function(info)
return ("%s[]"):format(info.type)
end,
}
function M.build()
local writer = Writer("options")
local docs = Docs.options()
---@type OptionsInfo
local info = vim.api.nvim_get_all_options_info()
---@param scope string
local function write(scope)
local var = "vim." .. scope:sub(1, 1) .. "o"
writer:write("---@class " .. var .. "\n")
writer:write(var .. " = {}\n\n")
Util.for_each(info, function(name, option)
if option.scope == scope then
local default = vim.inspect(option.default)
local str = ""
if docs[name] then
str = str .. Annotations.comment(docs[name]) .. "\n"
end
str = str .. ("%s.%s = %s\n"):format(var, name, default)
if option.shortname ~= "" then
str = str .. ("%s.%s = %s.%s\n"):format(var, option.shortname, var, name)
end
writer:write(str)
end
end)
writer:write("\n\n")
end
write("global")
write("win")
write("buf")
-- Write vim.opt
Util.for_each(info, function(name, option)
local str = ""
if docs[name] then
str = str .. Annotations.comment(docs[name]) .. "\n"
end
---@type string
local return_type = option.type
pcall(function()
---@diagnostic disable-next-line: no-unknown
return_type = vim.opt[name]._info.metatype
if M.metatype2lua[return_type] then
---@diagnostic disable-next-line: no-unknown
return_type = M.metatype2lua[return_type](option)
end
end)
str = str .. ("--- @class vim.opt.%s: vim.Option,%s\n"):format(name, return_type)
str = str .. ("--- @operator add: vim.opt.%s\n"):format(name)
str = str .. ("--- @operator sub: vim.opt.%s\n"):format(name)
str = str .. ("--- @operator pow: vim.opt.%s\n"):format(name)
str = str .. ("vim.opt.%s = %s\n"):format(name, vim.inspect(option.default))
if option.shortname ~= "" then
str = str .. ("vim.opt.%s = vim.opt.%s\n"):format(option.shortname, name)
end
str = str .. ("--- @return %s\nfunction vim.opt.%s:get()end\n\n"):format(return_type, name)
writer:write(str)
end)
local code = Util.read_file(Config.root("/types/override/options.lua"))
writer:write(code .. "\n")
writer:close()
end
return M

View File

@ -0,0 +1,53 @@
local config = require("neodev.config")
---@class AnnotationWriter
---@field name string
---@field fd? number
---@field fnum number
---@field size number
local M = {}
M.__index = M
M.MAX_SIZE = 1024 * 200
---@return AnnotationWriter
function M.new(name)
local self = setmetatable({}, M)
self.name = name
self.size = 0
self.fnum = 0
return self
end
function M:close()
if self.fd then
vim.loop.fs_close(self.fd)
end
end
function M:check()
if not self.fd or self.size > M.MAX_SIZE then
self:close()
local types = config.types()
local fname = types .. "/" .. self.name
if self.fnum > 0 then
fname = fname .. "." .. self.fnum
end
self.fd = vim.loop.fs_open(fname .. ".lua", "w+", 420)
self.fnum = self.fnum + 1
self.size = 0
self:intro()
end
end
function M:intro()
self:write("---@meta\n\n")
end
function M:write(text)
self:check()
vim.loop.fs_write(self.fd, text, -1)
self.size = self.size + #text
end
return M.new

View File

@ -0,0 +1,57 @@
local M = {}
--- @class LuaDevOptions
M.defaults = {
library = {
enabled = true, -- when not enabled, neodev will not change any settings to the LSP server
-- these settings will be used for your neovim config directory
runtime = true, -- runtime path
types = true, -- full signature, docs and completion of vim.api, vim.treesitter, vim.lsp and others
---@type boolean|string[]
plugins = true, -- installed opt or start plugins in packpath
-- you can also specify the list of plugins to make available as a workspace library
-- plugins = { "nvim-treesitter", "plenary.nvim", "telescope.nvim" },
},
setup_jsonls = true, -- configures jsonls to provide completion for .luarc.json files
-- for your neovim config directory, the config.library settings will be used as is
-- for plugin directories (root_dirs having a /lua directory), config.library.plugins will be disabled
-- for any other directory, config.library.enabled will be set to false
override = function(root_dir, options) end,
-- With lspconfig, Neodev will automatically setup your lua-language-server
-- If you disable this, then you have to set {before_init=require("neodev.lsp").before_init}
-- in your lsp start options
lspconfig = true,
-- much faster, but needs a recent built of lua-language-server
-- needs lua-language-server >= 3.6.0
pathStrict = true,
debug = false,
}
--- @type LuaDevOptions
M.options = {}
function M.setup(options)
M.options = vim.tbl_deep_extend("force", {}, M.defaults, options or {})
end
function M.types()
return M.root("/types/" .. M.version())
end
---@param root? string
function M.root(root)
local f = debug.getinfo(1, "S").source:sub(2)
return vim.loop.fs_realpath(vim.fn.fnamemodify(f, ":h:h:h") .. "/" .. (root or ""))
end
---@return "nightly" | "stable"
function M.version()
return vim.version().prerelease and "nightly" or "stable"
end
---@return LuaDevOptions
function M.merge(options)
return vim.tbl_deep_extend("force", {}, M.options, options or {})
end
return M

View File

@ -0,0 +1,39 @@
local M = {}
local function neoconf(config)
pcall(function()
require("neoconf.plugins").register({
on_schema = function(schema)
schema:import("neodev", config.defaults)
schema:set("neodev.library.plugins", {
description = "true/false or an array of plugin names to enable",
anyOf = {
{ type = "boolean" },
{ type = "array", items = { type = "string" } },
},
})
end,
})
end)
end
---@param opts? LuaDevOptions
function M.setup(opts)
local config = require("neodev.config")
config.setup(opts)
if config.options.lspconfig then
require("neodev.lsp").setup()
end
neoconf(config)
-- leave this for now for backward compatibility
return {
settings = {
legacy = true,
},
}
end
return M

View File

@ -0,0 +1,115 @@
local util = require("neodev.util")
local M = {}
function M.setup()
local opts = require("neodev.config").options
local lsputil = require("lspconfig.util")
local hook = lsputil.add_hook_after
lsputil.on_setup = hook(lsputil.on_setup, function(config)
if opts.setup_jsonls and config.name == "jsonls" then
M.setup_jsonls(config)
end
if config.name == "lua_ls" then
config.on_new_config = hook(config.on_new_config, M.on_new_config)
-- config.before_init = hook(config.before_init, M.before_init)
end
end)
end
function M.setup_jsonls(config)
local schemas = config.settings.json and config.settings.json.schemas or {}
table.insert(schemas, {
name = "LuaLS Settings",
url = "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
fileMatch = { ".luarc.json", ".luarc.jsonc" },
})
config.settings = vim.tbl_deep_extend("force", config.settings, {
json = {
schemas = schemas,
validate = {
enable = true,
},
},
})
end
function M.before_init(params, config)
M.on_new_config(config, params.rootPath)
end
function M.on_new_config(config, root_dir)
-- don't do anything when old style setup was used
if config.settings.legacy then
util.warn(
"You're using the old way of setting up neodev (previously lua-dev).\nPlease check the docs at https://github.com/folke/neodev.nvim#-setup"
)
return
end
local lua_root = util.find_root()
local opts = require("neodev.config").merge()
opts.library.enabled = util.is_nvim_config()
if not opts.library.enabled and lua_root then
opts.library.enabled = true
opts.library.plugins = false
end
pcall(function()
opts = require("neoconf").get("neodev", opts, { file = root_dir })
end)
pcall(opts.override, root_dir, opts.library)
local library = config.settings
and config.settings.Lua
and config.settings.Lua.workspace
and config.settings.Lua.workspace.library
or {}
local ignoreDir = config.settings
and config.settings.Lua
and config.settings.Lua.workspace
and config.settings.Lua.workspace.ignoreDir
or {}
if opts.library.enabled then
config.handlers = vim.tbl_extend("force", {}, config.handlers or {})
config.handlers["workspace/configuration"] = config.handlers["workspace/configuration"]
or function(err, result, ctx, cfg)
local ret = vim.lsp.handlers["workspace/configuration"](err, result, ctx, cfg)
ret = vim.deepcopy(ret)
-- when scopeUri is not set, then the requested config is for the fallback scope
-- Don't set workspace libraries for the fallback scope
-- Thanks to: https://github.com/LuaLS/lua-language-server/issues/1596#issuecomment-1855087288
for i, item in ipairs(result.items) do
if type(ret[i]) == "table" then
if not item.scopeUri and ret[i].workspace then
ret[i].workspace.library = nil
end
end
end
return ret
end
config.settings =
vim.tbl_deep_extend("force", config.settings or {}, require("neodev.luals").setup(opts, config.settings).settings)
for _, lib in ipairs(library) do
table.insert(config.settings.Lua.workspace.library, lib)
end
if require("neodev.config").options.pathStrict and lua_root then
table.insert(config.settings.Lua.workspace.library, lua_root)
end
for _, dir in ipairs(ignoreDir) do
table.insert(config.settings.Lua.workspace.ignoreDir, dir)
end
end
end
return M

View File

@ -0,0 +1,110 @@
local config = require("neodev.config")
local M = {}
---@param opts LuaDevOptions
function M.library(opts)
opts = config.merge(opts)
local ret = {}
if opts.library.types then
table.insert(ret, config.types())
end
local function add(lib, filter)
---@diagnostic disable-next-line: param-type-mismatch
for _, p in ipairs(vim.fn.expand(lib .. "/lua", false, true)) do
local plugin_name = vim.fn.fnamemodify(p, ":h:t")
p = vim.loop.fs_realpath(p)
if p and (not filter or filter[plugin_name]) then
if config.options.pathStrict then
table.insert(ret, p)
else
table.insert(ret, vim.fn.fnamemodify(p, ":h"))
end
end
end
end
if opts.library.runtime then
add(type(opts.library.runtime) == "string" and opts.library.runtime or "$VIMRUNTIME")
end
if opts.library.plugins then
---@type table<string, boolean>
local filter
if type(opts.library.plugins) == "table" then
filter = {}
for _, p in pairs(opts.library.plugins) do
filter[p] = true
end
end
for _, site in pairs(vim.split(vim.o.packpath, ",")) do
add(site .. "/pack/*/opt/*", filter)
add(site .. "/pack/*/start/*", filter)
end
-- add support for lazy.nvim
if package.loaded["lazy"] then
for _, plugin in ipairs(require("lazy").plugins()) do
add(plugin.dir, filter)
end
end
end
return ret
end
---@param settings? lspconfig.settings.lua_ls
function M.path(settings)
if config.options.pathStrict then
return { "?.lua", "?/init.lua" }
end
settings = settings or {}
local runtime = settings.Lua and settings.Lua.runtime or {}
local meta = runtime.meta or "${version} ${language} ${encoding}"
meta = meta:gsub("%${version}", runtime.version or "LuaJIT")
meta = meta:gsub("%${language}", "en-us")
meta = meta:gsub("%${encoding}", runtime.fileEncoding or "utf8")
return {
-- paths for builtin libraries
("meta/%s/?.lua"):format(meta),
("meta/%s/?/init.lua"):format(meta),
-- paths for meta/3rd libraries
"library/?.lua",
"library/?/init.lua",
-- Neovim lua files, config and plugins
"lua/?.lua",
"lua/?/init.lua",
}
end
---@param opts? LuaDevOptions
---@param settings? lspconfig.settings.lua_ls
function M.setup(opts, settings)
opts = config.merge(opts)
return {
---@type lspconfig.settings.lua_ls
settings = {
Lua = {
runtime = {
version = "LuaJIT",
path = M.path(settings),
pathStrict = config.options.pathStrict,
},
---@diagnostic disable-next-line: undefined-field
completion = opts.snippet and { callSnippet = "Replace" } or nil,
workspace = {
-- Make the server aware of Neovim runtime files
library = M.library(opts),
-- when pathStrict=false, we need to add the other types to ignoreDir,
-- otherwise they get indexed
ignoreDir = { config.version() == "stable" and "types/nightly" or "types/stable", "lua" },
},
},
},
}
end
return M

View File

@ -0,0 +1,87 @@
local config = require("neodev.config")
local M = {}
--- find the root directory that has /lua
---@param path string?
---@return string?
function M.find_root(path)
path = path or vim.api.nvim_buf_get_name(0)
return vim.fs.find({ "lua" }, { path = path, upward = true, type = "directory" })[1]
end
function M.fetch(url)
local fd = io.popen(string.format("curl -s -k %q", url))
if not fd then
error(("Could not download %s"):format(url))
end
local ret = fd:read("*a")
fd:close()
return ret
end
function M.is_nvim_config()
local path = vim.loop.fs_realpath(vim.api.nvim_buf_get_name(0))
if path then
path = vim.fs.normalize(path)
local config_root = vim.loop.fs_realpath(vim.fn.stdpath("config")) or vim.fn.stdpath("config")
config_root = vim.fs.normalize(config_root)
return path:find(config_root, 1, true) == 1
end
return false
end
function M.keys(tbl)
local ret = vim.tbl_keys(tbl)
table.sort(ret)
return ret
end
---@generic K
---@generic V
---@param tbl table<K, V>
---@param fn fun(key: K, value: V)
function M.for_each(tbl, fn)
local keys = M.keys(tbl)
for _, key in ipairs(keys) do
fn(key, tbl[key])
end
end
---@param file string
---@param flags? string
---@return string
function M.read_file(file, flags)
local fd = io.open(file, "r" .. (flags or ""))
if not fd then
error(("Could not open file %s for reading"):format(file))
end
local data = fd:read("*a")
fd:close()
return data
end
function M.write_file(file, data)
local fd = io.open(file, "w+")
if not fd then
error(("Could not open file %s for writing"):format(file))
end
fd:write(data)
fd:close()
end
function M.debug(msg)
if config.options.debug then
M.error(msg)
end
end
function M.error(msg)
vim.notify_once(msg, vim.log.levels.ERROR, { title = "neodev.nvim" })
end
function M.warn(msg)
vim.notify_once(msg, vim.log.levels.WARN, { title = "neodev.nvim" })
end
return M