require 'lspconfig' local configs = require 'lspconfig/configs' local util = require 'lspconfig/util' local inspect = vim.inspect local uv = vim.loop local fn = vim.fn local tbl_flatten = vim.tbl_flatten local function template(s, params) return (s:gsub("{{([^{}]+)}}", params)) end local function map_list(t, func) local res = {} for i, v in ipairs(t) do local x = func(v, i) if x ~= nil then table.insert(res, x) end end return res end local function indent(n, s) local prefix if type(n) == 'number' then if n <= 0 then return s end prefix = string.rep(" ", n) else assert(type(n) == 'string', 'n must be number or string') prefix = n end local lines = vim.split(s, '\n', true) for i, line in ipairs(lines) do lines[i] = prefix..line end return table.concat(lines, '\n') end local function make_parts(fns) return tbl_flatten(map_list(fns, function(v) if type(v) == 'function' then v = v() end return {v} end)) end local function make_section(indentlvl, sep, parts) return indent(indentlvl, table.concat(make_parts(parts), sep)) end local function readfile(path) assert(util.path.is_file(path)) return io.open(path):read("*a") end local function sorted_map_table(t, func) local keys = vim.tbl_keys(t) table.sort(keys) return map_list(keys, function(k) return func(k, t[k]) end) end local lsp_section_template = [[ ## {{template_name}} {{preamble}} ```lua require'lspconfig'.{{template_name}}.setup{} {{body}} ``` ]] local function require_all_configs() -- Configs are lazy-loaded, tickle them to populate the `configs` singleton. for _,v in ipairs(vim.fn.glob('lua/lspconfig/*.lua', 1, 1)) do local module_name = v:gsub('.*/', ''):gsub('%.lua$', '') require('lspconfig/'..module_name) end end local function make_lsp_sections() return make_section(0, '\n', sorted_map_table(configs, function(template_name, template_object) local template_def = template_object.document_config local docs = template_def.docs local params = { template_name = template_name; preamble = ""; body = ""; } params.body = make_section(2, '\n\n', { function() if not template_def.commands then return end return make_section(0, '\n', { "Commands:"; sorted_map_table(template_def.commands, function(name, def) if def.description then return string.format("- %s: %s", name, def.description) end return string.format("- %s", name) end) }) end; function() if not template_def.default_config then return end return make_section(0, '\n', { "Default Values:"; sorted_map_table(template_def.default_config, function(k, v) local description = ((docs or {}).default_config or {})[k] if description and type(description) ~= 'string' then description = inspect(description) elseif not description and type(v) == "function" then local info = debug.getinfo(v) local file = io.open(string.sub(info.source, 2), 'r') local fileContent = {} for line in file:lines() do table.insert (fileContent, line) end io.close(file) local root_dir = {} for i = info.linedefined, info.lastlinedefined do table.insert(root_dir, fileContent[i]) end description = table.concat(root_dir, '\n') description = string.gsub(description, ".*function", "function") end return indent(2, string.format("%s = %s", k, description or inspect(v))) end) }) end; }) if docs then local tempdir = os.getenv("DOCGEN_TEMPDIR") or uv.fs_mkdtemp("/tmp/nvim-lsp.XXXXXX") local preamble_parts = make_parts { function() if docs.description and #docs.description > 0 then return docs.description end end; function() local package_json_name = util.path.join(tempdir, template_name..'.package.json'); if docs.package_json then if not util.path.is_file(package_json_name) then os.execute(string.format("curl -v -L -o %q %q", package_json_name, docs.package_json)) end if not util.path.is_file(package_json_name) then print(string.format("Failed to download package.json for %q at %q", template_name, docs.package_json)) os.exit(1) return end local data = fn.json_decode(readfile(package_json_name)) -- The entire autogenerated section. return make_section(0, '\n', { -- The default settings section function() local default_settings = (data.contributes or {}).configuration if not default_settings.properties then return end -- The outer section. return make_section(0, '\n', { 'This server accepts configuration via the `settings` key.'; '
Available settings:'; ''; -- The list of properties. make_section(0, '\n\n', sorted_map_table(default_settings.properties, function(k, v) local function tick(s) return string.format("`%s`", s) end local function bold(s) return string.format("**%s**", s) end -- https://github.github.com/gfm/#backslash-escapes local function excape_markdown_punctuations(str) local pattern = "\\(\\*\\|\\.\\|?\\|!\\|\"\\|#\\|\\$\\|%\\|'\\|(\\|)\\|,\\|-\\|\\/\\|:\\|;\\|<\\|=\\|>\\|@\\|\\[\\|\\\\\\|\\]\\|\\^\\|_\\|`\\|{\\|\\\\|\\|}\\)" return fn.substitute(str, pattern, "\\\\\\0", "g") end -- local function pre(s) return string.format("
%s
", s) end -- local function code(s) return string.format("%s", s) end if not (type(v) == "table") then return end return make_section(0, '\n', { "- "..make_section(0, ': ', { bold(tick(k)); function() if v.enum then return tick("enum "..inspect(v.enum)) end if v.type then return tick(table.concat(tbl_flatten{v.type}, '|')) end end; }); ''; make_section(2, '\n\n', { {v.default and "Default: "..tick(inspect(v.default, {newline='';indent=''}))}; {v.items and "Array items: "..tick(inspect(v.items, {newline='';indent=''}))}; {excape_markdown_punctuations(v.description)}; }); }) end)); ''; '
'; }) end; }) end end } if not os.getenv("DOCGEN_TEMPDIR") then os.execute("rm -rf "..tempdir) end -- Insert a newline after the preamble if it exists. if #preamble_parts > 0 then table.insert(preamble_parts, '') end params.preamble = table.concat(preamble_parts, '\n') end return template(lsp_section_template, params) end)) end local function make_implemented_servers_list() return make_section(0, '\n', sorted_map_table(configs, function(k) return template("- [{{server}}](#{{server}})", {server=k}) end)) end local function generate_readme(template_file, params) vim.validate { lsp_server_details = {params.lsp_server_details, 's'}; implemented_servers_list = {params.implemented_servers_list, 's'}; } local input_template = readfile(template_file) local readme_data = template(input_template, params) local writer = io.open("CONFIG.md", "w") writer:write(readme_data) writer:close() end require_all_configs() generate_readme("scripts/README_template.md", { implemented_servers_list = make_implemented_servers_list(); lsp_server_details = make_lsp_sections(); }) -- vim:et ts=2 sw=2