mirror of
https://github.com/neovim/neovim.git
synced 2024-12-19 18:55:14 -07:00
refactor(help): move it to Lua
This commit is contained in:
parent
76af219e3e
commit
71fa9d4e93
201
runtime/lua/vim/help.lua
Normal file
201
runtime/lua/vim/help.lua
Normal 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
|
Loading…
Reference in New Issue
Block a user