aboutsummaryrefslogtreecommitdiff
path: root/scripts/gen_lsp.lua
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/gen_lsp.lua')
-rw-r--r--scripts/gen_lsp.lua514
1 files changed, 0 insertions, 514 deletions
diff --git a/scripts/gen_lsp.lua b/scripts/gen_lsp.lua
deleted file mode 100644
index 3e419c7d59..0000000000
--- a/scripts/gen_lsp.lua
+++ /dev/null
@@ -1,514 +0,0 @@
--- Generates lua-ls annotations for lsp.
-
-local USAGE = [[
-Generates lua-ls annotations for lsp.
-
-USAGE:
-nvim -l scripts/gen_lsp.lua gen # by default, this will overwrite runtime/lua/vim/lsp/_meta/protocol.lua
-nvim -l scripts/gen_lsp.lua gen --version 3.18 --out runtime/lua/vim/lsp/_meta/protocol.lua
-nvim -l scripts/gen_lsp.lua gen --version 3.18 --methods --capabilities
-]]
-
-local DEFAULT_LSP_VERSION = '3.18'
-
-local M = {}
-
-local function tofile(fname, text)
- local f = io.open(fname, 'w')
- if not f then
- error(('failed to write: %s'):format(f))
- else
- print(('Written to: %s'):format(fname))
- f:write(text)
- f:close()
- end
-end
-
---- The LSP protocol JSON data (it's partial, non-exhaustive).
---- https://raw.githubusercontent.com/microsoft/language-server-protocol/gh-pages/_specifications/lsp/3.18/metaModel/metaModel.schema.json
---- @class vim._gen_lsp.Protocol
---- @field requests vim._gen_lsp.Request[]
---- @field notifications vim._gen_lsp.Notification[]
---- @field structures vim._gen_lsp.Structure[]
---- @field enumerations vim._gen_lsp.Enumeration[]
---- @field typeAliases vim._gen_lsp.TypeAlias[]
-
----@param opt vim._gen_lsp.opt
----@return vim._gen_lsp.Protocol
-local function read_json(opt)
- local uri = 'https://raw.githubusercontent.com/microsoft/language-server-protocol/gh-pages/_specifications/lsp/'
- .. opt.version
- .. '/metaModel/metaModel.json'
- print('Reading ' .. uri)
-
- local res = vim.system({ 'curl', '--no-progress-meter', uri, '-o', '-' }):wait()
- if res.code ~= 0 or (res.stdout or ''):len() < 999 then
- print(('URL failed: %s'):format(uri))
- vim.print(res)
- error(res.stdout)
- end
- return vim.json.decode(res.stdout)
-end
-
--- Gets the Lua symbol for a given fully-qualified LSP method name.
-local function to_luaname(s)
- -- "$/" prefix is special: https://microsoft.github.io/language-server-protocol/specification/#dollarRequests
- return s:gsub('^%$', 'dollar'):gsub('/', '_')
-end
-
----@param protocol vim._gen_lsp.Protocol
----@param gen_methods boolean
----@param gen_capabilities boolean
-local function write_to_protocol(protocol, gen_methods, gen_capabilities)
- if not gen_methods and not gen_capabilities then
- return
- end
-
- local indent = (' '):rep(2)
-
- --- @class vim._gen_lsp.Request
- --- @field deprecated? string
- --- @field documentation? string
- --- @field messageDirection string
- --- @field clientCapability? string
- --- @field serverCapability? string
- --- @field method string
- --- @field params? any
- --- @field proposed? boolean
- --- @field registrationMethod? string
- --- @field registrationOptions? any
- --- @field since? string
-
- --- @class vim._gen_lsp.Notification
- --- @field deprecated? string
- --- @field documentation? string
- --- @field errorData? any
- --- @field messageDirection string
- --- @field clientCapability? string
- --- @field serverCapability? string
- --- @field method string
- --- @field params? any[]
- --- @field partialResult? any
- --- @field proposed? boolean
- --- @field registrationMethod? string
- --- @field registrationOptions? any
- --- @field result any
- --- @field since? string
-
- ---@type (vim._gen_lsp.Request|vim._gen_lsp.Notification)[]
- local all = vim.list_extend(protocol.requests, protocol.notifications)
- table.sort(all, function(a, b)
- return to_luaname(a.method) < to_luaname(b.method)
- end)
-
- local output = { '-- Generated by gen_lsp.lua, keep at end of file.' }
-
- if gen_methods then
- output[#output + 1] = '--- @alias vim.lsp.protocol.Method.ClientToServer'
-
- for _, item in ipairs(all) do
- if item.method and item.messageDirection == 'clientToServer' then
- output[#output + 1] = ("--- | '%s',"):format(item.method)
- end
- end
-
- vim.list_extend(output, {
- '',
- '--- @alias vim.lsp.protocol.Method.ServerToClient',
- })
- for _, item in ipairs(all) do
- if item.method and item.messageDirection == 'serverToClient' then
- output[#output + 1] = ("--- | '%s',"):format(item.method)
- end
- end
-
- vim.list_extend(output, {
- '',
- '--- @alias vim.lsp.protocol.Method',
- '--- | vim.lsp.protocol.Method.ClientToServer',
- '--- | vim.lsp.protocol.Method.ServerToClient',
- '',
- '-- Generated by gen_lsp.lua, keep at end of file.',
- '--- @enum vim.lsp.protocol.Methods',
- '--- @see https://microsoft.github.io/language-server-protocol/specification/#metaModel',
- '--- LSP method names.',
- 'protocol.Methods = {',
- })
-
- for _, item in ipairs(all) do
- if item.method then
- if item.documentation then
- local document = vim.split(item.documentation, '\n?\n', { trimempty = true })
- for _, docstring in ipairs(document) do
- output[#output + 1] = indent .. '--- ' .. docstring
- end
- end
- output[#output + 1] = ("%s%s = '%s',"):format(indent, to_luaname(item.method), item.method)
- end
- end
- output[#output + 1] = '}'
- end
-
- if gen_capabilities then
- vim.list_extend(output, {
- '',
- '-- stylua: ignore start',
- '-- Generated by gen_lsp.lua, keep at end of file.',
- '--- Maps method names to the required server capability',
- 'protocol._request_name_to_capability = {',
- })
-
- for _, item in ipairs(all) do
- if item.serverCapability then
- output[#output + 1] = ("%s['%s'] = { %s },"):format(
- indent,
- item.method,
- table.concat(
- vim
- .iter(vim.split(item.serverCapability, '.', { plain = true }))
- :map(function(segment)
- return "'" .. segment .. "'"
- end)
- :totable(),
- ', '
- )
- )
- end
- end
-
- output[#output + 1] = '}'
- output[#output + 1] = '-- stylua: ignore end'
- end
-
- output[#output + 1] = ''
- output[#output + 1] = 'return protocol'
-
- local fname = './runtime/lua/vim/lsp/protocol.lua'
- local bufnr = vim.fn.bufadd(fname)
- vim.fn.bufload(bufnr)
- vim.api.nvim_set_current_buf(bufnr)
- local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
- local index = vim.iter(ipairs(lines)):find(function(key, item)
- return vim.startswith(item, '-- Generated by') and key or nil
- end)
- index = index and index - 1 or vim.api.nvim_buf_line_count(bufnr) - 1
- vim.api.nvim_buf_set_lines(bufnr, index, -1, true, output)
- vim.cmd.write()
-end
-
----@class vim._gen_lsp.opt
----@field output_file string
----@field version string
----@field methods boolean
----@field capabilities boolean
-
----@param opt vim._gen_lsp.opt
-function M.gen(opt)
- --- @type vim._gen_lsp.Protocol
- local protocol = read_json(opt)
-
- write_to_protocol(protocol, opt.methods, opt.capabilities)
-
- local output = {
- '--' .. '[[',
- 'THIS FILE IS GENERATED by scripts/gen_lsp.lua',
- 'DO NOT EDIT MANUALLY',
- '',
- 'Based on LSP protocol ' .. opt.version,
- '',
- 'Regenerate:',
- ([=[nvim -l scripts/gen_lsp.lua gen --version %s]=]):format(DEFAULT_LSP_VERSION),
- '--' .. ']]',
- '',
- '---@meta',
- "error('Cannot require a meta file')",
- '',
- '---@alias lsp.null nil',
- '---@alias uinteger integer',
- '---@alias decimal number',
- '---@alias lsp.DocumentUri string',
- '---@alias lsp.URI string',
- '',
- }
-
- local anonymous_num = 0
-
- ---@type string[]
- local anonym_classes = {}
-
- local simple_types = {
- 'string',
- 'boolean',
- 'integer',
- 'uinteger',
- 'decimal',
- }
-
- ---@param documentation string
- local _process_documentation = function(documentation)
- documentation = documentation:gsub('\n', '\n---')
- -- Remove <200b> (zero-width space) unicode characters: e.g., `**/<200b>*`
- documentation = documentation:gsub('\226\128\139', '')
- -- Escape annotations that are not recognized by lua-ls
- documentation = documentation:gsub('%^---@sample', '---\\@sample')
- return '---' .. documentation
- end
-
- --- @class vim._gen_lsp.Type
- --- @field kind string a common field for all Types.
- --- @field name? string for ReferenceType, BaseType
- --- @field element? any for ArrayType
- --- @field items? vim._gen_lsp.Type[] for OrType, AndType
- --- @field key? vim._gen_lsp.Type for MapType
- --- @field value? string|vim._gen_lsp.Type for StringLiteralType, MapType, StructureLiteralType
-
- ---@param type vim._gen_lsp.Type
- ---@param prefix? string Optional prefix associated with the this type, made of (nested) field name.
- --- Used to generate class name for structure literal types.
- ---@return string
- local function parse_type(type, prefix)
- -- ReferenceType | BaseType
- if type.kind == 'reference' or type.kind == 'base' then
- if vim.tbl_contains(simple_types, type.name) then
- return type.name
- end
- return 'lsp.' .. type.name
-
- -- ArrayType
- elseif type.kind == 'array' then
- local parsed_items = parse_type(type.element, prefix)
- if type.element.items and #type.element.items > 1 then
- parsed_items = '(' .. parsed_items .. ')'
- end
- return parsed_items .. '[]'
-
- -- OrType
- elseif type.kind == 'or' then
- local val = ''
- for _, item in ipairs(type.items) do
- val = val .. parse_type(item, prefix) .. '|' --[[ @as string ]]
- end
- val = val:sub(0, -2)
- return val
-
- -- StringLiteralType
- elseif type.kind == 'stringLiteral' then
- return '"' .. type.value .. '"'
-
- -- MapType
- elseif type.kind == 'map' then
- local key = assert(type.key)
- local value = type.value --[[ @as vim._gen_lsp.Type ]]
- return 'table<' .. parse_type(key, prefix) .. ', ' .. parse_type(value, prefix) .. '>'
-
- -- StructureLiteralType
- elseif type.kind == 'literal' then
- -- can I use ---@param disabled? {reason: string}
- -- use | to continue the inline class to be able to add docs
- -- https://github.com/LuaLS/lua-language-server/issues/2128
- anonymous_num = anonymous_num + 1
- local anonymous_classname = 'lsp._anonym' .. anonymous_num
- if prefix then
- anonymous_classname = anonymous_classname .. '.' .. prefix
- end
- local anonym = vim
- .iter({
- (anonymous_num > 1 and { '' } or {}),
- { '---@class ' .. anonymous_classname },
- })
- :flatten()
- :totable()
-
- --- @class vim._gen_lsp.StructureLiteral translated to anonymous @class.
- --- @field deprecated? string
- --- @field description? string
- --- @field properties vim._gen_lsp.Property[]
- --- @field proposed? boolean
- --- @field since? string
-
- ---@type vim._gen_lsp.StructureLiteral
- local structural_literal = assert(type.value) --[[ @as vim._gen_lsp.StructureLiteral ]]
- for _, field in ipairs(structural_literal.properties) do
- anonym[#anonym + 1] = '---'
- if field.documentation then
- anonym[#anonym + 1] = _process_documentation(field.documentation)
- end
- anonym[#anonym + 1] = '---@field '
- .. field.name
- .. (field.optional and '?' or '')
- .. ' '
- .. parse_type(field.type, prefix .. '.' .. field.name)
- end
- -- anonym[#anonym + 1] = ''
- for _, line in ipairs(anonym) do
- if line then
- anonym_classes[#anonym_classes + 1] = line
- end
- end
- return anonymous_classname
-
- -- TupleType
- elseif type.kind == 'tuple' then
- local tuple = '['
- for _, value in ipairs(type.items) do
- tuple = tuple .. parse_type(value, prefix) .. ', '
- end
- -- remove , at the end
- tuple = tuple:sub(0, -3)
- return tuple .. ']'
- end
-
- vim.print('WARNING: Unknown type ', type)
- return ''
- end
-
- --- @class vim._gen_lsp.Structure translated to @class
- --- @field deprecated? string
- --- @field documentation? string
- --- @field extends? { kind: string, name: string }[]
- --- @field mixins? { kind: string, name: string }[]
- --- @field name string
- --- @field properties? vim._gen_lsp.Property[] members, translated to @field
- --- @field proposed? boolean
- --- @field since? string
- for _, structure in ipairs(protocol.structures) do
- -- output[#output + 1] = ''
- if structure.documentation then
- output[#output + 1] = _process_documentation(structure.documentation)
- end
- local class_string = ('---@class lsp.%s'):format(structure.name)
- if structure.extends or structure.mixins then
- local inherits_from = table.concat(
- vim.list_extend(
- vim.tbl_map(parse_type, structure.extends or {}),
- vim.tbl_map(parse_type, structure.mixins or {})
- ),
- ', '
- )
- class_string = class_string .. ': ' .. inherits_from
- end
- output[#output + 1] = class_string
-
- --- @class vim._gen_lsp.Property translated to @field
- --- @field deprecated? string
- --- @field documentation? string
- --- @field name string
- --- @field optional? boolean
- --- @field proposed? boolean
- --- @field since? string
- --- @field type { kind: string, name: string }
- for _, field in ipairs(structure.properties or {}) do
- output[#output + 1] = '---' -- Insert a single newline between @fields (and after @class)
- if field.documentation then
- output[#output + 1] = _process_documentation(field.documentation)
- end
- output[#output + 1] = '---@field '
- .. field.name
- .. (field.optional and '?' or '')
- .. ' '
- .. parse_type(field.type, field.name)
- end
- output[#output + 1] = ''
- end
-
- --- @class vim._gen_lsp.Enumeration translated to @enum
- --- @field deprecated string?
- --- @field documentation string?
- --- @field name string?
- --- @field proposed boolean?
- --- @field since string?
- --- @field suportsCustomValues boolean?
- --- @field values { name: string, value: string, documentation?: string, since?: string }[]
- for _, enum in ipairs(protocol.enumerations) do
- if enum.documentation then
- output[#output + 1] = _process_documentation(enum.documentation)
- end
- local enum_type = '---@alias lsp.' .. enum.name
- for _, value in ipairs(enum.values) do
- enum_type = enum_type
- .. '\n---| '
- .. (type(value.value) == 'string' and '"' .. value.value .. '"' or value.value)
- .. ' # '
- .. value.name
- end
- output[#output + 1] = enum_type
- output[#output + 1] = ''
- end
-
- --- @class vim._gen_lsp.TypeAlias translated to @alias
- --- @field deprecated? string?
- --- @field documentation? string
- --- @field name string
- --- @field proposed? boolean
- --- @field since? string
- --- @field type vim._gen_lsp.Type
- for _, alias in ipairs(protocol.typeAliases) do
- if alias.documentation then
- output[#output + 1] = _process_documentation(alias.documentation)
- end
- if alias.type.kind == 'or' then
- local alias_type = '---@alias lsp.' .. alias.name .. ' '
- for _, item in ipairs(alias.type.items) do
- alias_type = alias_type .. parse_type(item, alias.name) .. '|'
- end
- alias_type = alias_type:sub(0, -2)
- output[#output + 1] = alias_type
- else
- output[#output + 1] = '---@alias lsp.'
- .. alias.name
- .. ' '
- .. parse_type(alias.type, alias.name)
- end
- output[#output + 1] = ''
- end
-
- -- anonymous classes
- for _, line in ipairs(anonym_classes) do
- output[#output + 1] = line
- end
-
- tofile(opt.output_file, table.concat(output, '\n') .. '\n')
-end
-
----@type vim._gen_lsp.opt
-local opt = {
- output_file = 'runtime/lua/vim/lsp/_meta/protocol.lua',
- version = DEFAULT_LSP_VERSION,
- methods = false,
- capabilities = false,
-}
-
-local command = nil
-local i = 1
-while i <= #_G.arg do
- if _G.arg[i] == '--out' then
- opt.output_file = assert(_G.arg[i + 1], '--out <outfile> needed')
- i = i + 1
- elseif _G.arg[i] == '--version' then
- opt.version = assert(_G.arg[i + 1], '--version <version> needed')
- i = i + 1
- elseif _G.arg[i] == '--methods' then
- opt.methods = true
- elseif _G.arg[i] == '--capabilities' then
- opt.capabilities = true
- elseif vim.startswith(_G.arg[i], '-') then
- error('Unrecognized args: ' .. _G.arg[i])
- else
- if command then
- error('More than one command was given: ' .. _G.arg[i])
- else
- command = _G.arg[i]
- end
- end
- i = i + 1
-end
-
-if not command then
- print(USAGE)
-elseif M[command] then
- M[command](opt) -- see M.gen()
-else
- error('Unknown command: ' .. command)
-end
-
-return M