Compare commits

...

3 Commits

Author SHA1 Message Date
Javier Lopez b8b49d6144
Merge 71fa9d4e93 into efb44e0cad 2024-05-05 10:04:15 -04:00
Maria José Solano efb44e0cad
docs: fix lua type warnings (#28633) 2024-05-05 06:08:17 +08:00
Javier López 71fa9d4e93 refactor(help): move it to Lua 2021-12-04 11:27:36 -05:00
7 changed files with 217 additions and 6 deletions

View File

@ -124,7 +124,7 @@ error('Cannot require a meta file')
--- @field commalist boolean
--- @field flaglist boolean
--- @field was_set boolean
--- @field last_set_id integer
--- @field last_set_sid integer
--- @field last_set_linenr integer
--- @field last_set_chan integer
--- @field type 'string'|'boolean'|'number'

View File

@ -127,3 +127,11 @@
--- @field skipcol integer
--- @field topfill integer
--- @field topline integer
--- @class vim.fn.getscriptinfo.ret
--- @field autoload false
--- @field functions? string[]
--- @field name string
--- @field sid string
--- @field variables? table<string, any>
--- @field version 1

View File

@ -3632,7 +3632,7 @@ function vim.fn.getregtype(regname) end
--- <
---
--- @param opts? table
--- @return any
--- @return vim.fn.getscriptinfo.ret[]
function vim.fn.getscriptinfo(opts) end
--- If {tabnr} is not specified, then information about all the

201
runtime/lua/vim/help.lua Normal file
View File

@ -0,0 +1,201 @@
local M = {}
local vim = vim
local nvim_get_runtime_file = vim.api.nvim_get_runtime_file
---Find the tagfiles to search tags on.
---Tag falenames have the pattern 'tags(-\w\w)?' :help :helptags
---@param lang string|nil The tagname to search for.
M.tag_files_search = function(lang)
local patt = "doc/tags"
local res = {}
if lang ~= nil then
patt = string.format("%s-%s", patt, lang)
end
res = nvim_get_runtime_file(patt, true)
return res
end
---Find the tags that match the given pattern in the given language.
M.find_tags = function(patterns, lang)
local files = M.tag_files_search(lang)
-- Matches will be stored here by find_in_tagfile_and_score
local results = { count = 0, matches = {} }
for _, fname in ipairs(files) do
-- Finds the tags in each file, scores them and adds them to the results
M.find_in_tagfile_and_score(fname, results, patterns)
end
return results
end
---A tagfile is a tab separated file with three fields: tagname, file, tag_regexp
---This function finds the tags that mtch the given patterns and scores the match. The results are
---appened to the results.matches table.
---@param filename string the full path of the tagfile to search in.
---@param results table with two keys count number, matches table
---@param patterns table with keys escaped, icase, wildcard
---@return nil
M.find_in_tagfile_and_score = function(filename, results, patterns)
-- TODO: Do you even vim.loop
local file = io.open(filename, "r")
local contents = file:read("*a")
file:close()
local escaped, icase, wildcard = patterns.escaped, patterns.icase, patterns.wildcard
local score, entry, tag, add = 0, {}, "", false
local matchpos, t
for line in vim.gsplit(contents, '\n', true) do
if line == "" then break end
entry = vim.split(line, "\t")
tag = entry[1]
matchpos = tag:find(escaped)
-- Regular case sensitive escaped pattern match
if matchpos ~= nil then
add = true
t = 'pattern'
else
-- Case insensitive match
matchpos = tag:find(icase)
if matchpos ~= nil then
add = true
-- Add 5000 if the match is case insensitive
score = score + 5000
t = 'icase'
else
-- Wildcard match
matchpos = tag:find(wildcard)
if matchpos ~= nil then
add = true
-- Add 20000 if it was a wildcard match
score = score + 20000
end
end
end
if add then
-- Add the number of chars/length of tag
score = score + #tag
-- Add 100 for every letter of the match
tag:gsub("%a", function()
score = score + 100
end)
-- Add 10000 when the match is not at the start but making sure matchpos and matchpos-1 are
-- alphanumeir
if matchpos > 1 and string.find(tag, '^%w%w', matchpos - 1, false) then
score = score + 10000
-- If it's over the third position mulyiply by 200
else if matchpos > 3 then
score = score * 200
end
end
results.count = results.count + 1
results.matches[results.count] = { entry[1], filename:gsub('tags.-$', entry[2]), entry[3], score, t}
end
add = false
score = 0
end
end
---Patterns in which the whole match is replaced
local full_replacements = {
["*"] = "star",
["g*"] = "gstar",
["[*"] = "[star",
["]*"] = "]star",
["/*"] = "/star",
["/\\*"] = "/\\star",
['"*'] = "quotestar",
["**"] = "starstar",
}
---Search for the following patterns in the tag name and replace them.
---Items: { pattern, replacement, should_escape_pattern }
local replacements = {
---NOTE: the order matters
{ '"', "quote", true }, -- Replace " with quote
{ "|", "bar", true }, -- Repce | with bar
-- The next two need to be applied in order one expands ^ the other separates repetitive
{ "%^(.)", "CTRL%-%1", false }, -- ^n to CTRL-n
{ "(CTRL%-.)([^_])", "%1_%2", false }, -- Insert _ between CTRL-x_CTRL-n
}
---Make lua pattern case insensitive.
---@param text string the text/pattern to make case insensitive.
---@return string The case insensitive pattern.
local function ignorecase_pattern(text)
return text:gsub("(%a)", function(a)
return string.format("[%s%s]", a:lower(), a:upper())
end)
end
---Escape any lua pattern matching characters.
---@param text string The text/pattern to escape.
local function escape_pattern(text)
return text:gsub("([^%w])", "%%%1")
end
---Given user input transform it to be a tagname, and return the lua patterns to search.
---Three patterns are necessary: normal, case insensitive, and wildcards enabled '*' or '?'.
---@param name string user input that represents a tagname.
---@return string, string first pattern is with all magic chatacters escaped, second is with wildcards enabled.
M.generate_search_patterns = function(name)
-- The escaping of the returned tag name is only escaped once at the time of return
if full_replacements[name] ~= nil then
name = full_replacements[name]
else
-- Perform the replacements
local patt, repl, should_escape
for _, expr in ipairs(replacements) do
patt, repl, should_escape = expr[1], expr[2], expr[3]
if should_escape then
patt = escape_pattern(patt)
end
name = name:gsub(patt, repl)
end
end
-- Escaped pattern assures the tag is looked by the name without the characters having any
-- special meaning
local patterns = {}
patterns.escaped = escape_pattern(name)
patterns.icase = ignorecase_pattern(patterns.escaped)
patterns.wildcard = patterns.escaped:gsub("%%%*", ".*"):gsub("%%%?", ".")
if patterns.escaped == patterns.wildcard then
patterns.wildcard ='^$'
end
return patterns
end
M.ex_help = function(tag_name)
local patterns = M.generate_search_patterns(tag_name)
print("Generated patterns", vim.inspect(patterns))
local results = M.find_tags(patterns, nil)
table.sort(results.matches, function(a, b)
return a[4] < b[4]
end)
print(vim.inspect(results))
end
-- Examples
-- M.ex_help([[CTRL-\_CTRL-N]])
-- M.ex_help([[^N]])
-- M.ex_help([[i_^_]])
-- M.ex_help([[i_^_^D]]) -- Special case doesn't work and I think it shouldn't
-- M.ex_help([[^_]])
-- M.ex_help([[/|]])
-- M.ex_help([[i_^x^E]])
-- M.ex_help([[^X^N]])
-- M.ex_help([[^x^N]])
-- M.ex_help([[Cino]])
-- M.ex_help([[sort]])
-- M.ex_help("[pattern]")
-- M.ex_help("s/\\1")
-- M.ex_help([[/\star]])
-- M.ex_help(":s\\=")
-- M.ex_help([['sp]])
-- M.ex_help([[motion]])
-- M.ex_help([[expr-!~]])
-- M.ex_help([[rw?]])
-- M.ex_help([[netrw-%]])
-- M.ex_help([[...]])
-- M.ex_help([[tw?]])
M.ex_help([[c*vis]])
-- M.ex_help([[z?]])
return M

View File

@ -310,6 +310,7 @@ local function is_empty_or_default(bufnr, option)
end
local info = api.nvim_get_option_info2(option, { buf = bufnr })
---@param e vim.fn.getscriptinfo.ret
local scriptinfo = vim.tbl_filter(function(e)
return e.sid == info.last_set_sid
end, vim.fn.getscriptinfo())
@ -515,7 +516,7 @@ local function buf_attach(bufnr)
textDocument = {
uri = uri,
},
reason = protocol.TextDocumentSaveReason.Manual,
reason = protocol.TextDocumentSaveReason.Manual, ---@type integer
}
if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSave') then
client.notify(ms.textDocument_willSave, params)
@ -899,7 +900,7 @@ end
--- a `client_id:result` map.
---@return function cancel Function that cancels all requests.
function lsp.buf_request_all(bufnr, method, params, handler)
local results = {} --- @type table<integer,{error:string, result:any}>
local results = {} --- @type table<integer,{error:lsp.ResponseError, result:any}>
local result_count = 0
local expected_result_count = 0
@ -940,7 +941,7 @@ end
---@return table<integer, {err: lsp.ResponseError, result: any}>? result Map of client_id:request_result.
---@return string? err On timeout, cancel, or error, `err` is a string describing the failure reason, and `result` is nil.
function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
local request_results
local request_results ---@type table
local cancel = lsp.buf_request_all(bufnr, method, params, function(it)
request_results = it

View File

@ -327,7 +327,7 @@ function M.get_captures_at_cursor(winnr)
end
--- Optional keyword arguments:
--- @class vim.treesitter.get_node.Opts
--- @class vim.treesitter.get_node.Opts : vim.treesitter.LanguageTree.tree_for_range.Opts
--- @inlinedoc
---
--- Buffer number (nil or 0 for current buffer)

View File

@ -4475,6 +4475,7 @@ M.funcs = {
]=],
name = 'getscriptinfo',
params = { { 'opts', 'table' } },
returns = 'vim.fn.getscriptinfo.ret[]',
signature = 'getscriptinfo([{opts}])',
},
gettabinfo = {