forked from neovim/neovim
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhelp.lua
More file actions
199 lines (181 loc) · 7.02 KB
/
help.lua
File metadata and controls
199 lines (181 loc) · 7.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
local M = {}
local tag_exceptions = {
-- Interpret asterisk (star, '*') literal but name it 'star'
['*'] = 'star',
['g*'] = 'gstar',
['[*'] = '[star',
[']*'] = ']star',
[':*'] = ':star',
['/*'] = '/star',
['/\\*'] = '/\\\\star',
['\\\\star'] = '/\\\\star',
['"*'] = 'quotestar',
['**'] = 'starstar',
['cpo-*'] = 'cpo-star',
-- Literal question mark '?'
['?'] = '?',
['??'] = '??',
[':?'] = ':?',
['?<CR>'] = '?<CR>',
['g?'] = 'g?',
['g?g?'] = 'g?g?',
['g??'] = 'g??',
['-?'] = '-?',
['q?'] = 'q?',
['v_g?'] = 'v_g?',
['/\\?'] = '/\\\\?',
-- Backslash-escaping hell
['/\\%(\\)'] = '/\\\\%(\\\\)',
['/\\z(\\)'] = '/\\\\z(\\\\)',
['\\='] = '\\\\=',
['\\%$'] = '/\\\\%\\$',
-- Some expressions are literal but without the 'expr-' prefix. Note: not all 'expr-' subjects!
['expr-!=?'] = '!=?',
['expr-!~?'] = '!\\~?',
['expr-<=?'] = '<=?',
['expr-<?'] = '<?',
['expr-==?'] = '==?',
['expr-=~?'] = '=~?',
['expr->=?'] = '>=?',
['expr->?'] = '>?',
['expr-is?'] = 'is?',
['expr-isnot?'] = 'isnot?',
}
---Transform a help tag query into a search pattern for find_tags().
---
---This function converts user input from `:help {subject}` into a regex pattern that balances
---literal matching with wildcard support. Vim help tags can contain characters that have special
---meaning in regex (like *, ?, |), but we also want to support wildcard searches.
---
---Examples:
--- '*' --> 'star' (literal match for the * command help tag)
--- 'buffer*' --> 'buffer.*' (wildcard: find all buffer-related tags)
--- 'CTRL-W' --> stays as 'CTRL-W' (already in tag format)
--- '^A' --> 'CTRL-A' (caret notation converted to tag format)
---
---@param word string The help subject as entered by the user
---@return string pattern The escaped regex pattern to search for in tag files
function M.escape_subject(word)
local replacement = tag_exceptions[word]
if replacement then
return replacement
end
-- Add prefix '/\\' to patterns starting with a backslash
-- Examples: \S, \%^, \%(, \zs, \z1, \@<, \@=, \@<=, \_$, \_^
if word:match([[^\.$]]) or word:match('^\\[%%_z@]') then
word = [[/\]] .. word
word = word:gsub('[$.~]', [[\%0]])
word = word:gsub('|', 'bar')
else
-- Fix for bracket expressions and curly braces:
-- '\' --> '\\' (needs to come first)
-- '[' --> '\[' (escape the opening bracket)
-- ':[' --> ':\[' (escape the opening bracket)
-- '\{' --> '\\{' (for '\{' pattern matching)
-- '(' --> '' (parentheses around option tags should be ignored)
word = word:gsub([[\+]], [[\\]])
word = word:gsub([[^%[]], [[\[]])
word = word:gsub([[^:%[]], [[:\[]])
word = word:gsub([[^\{]], [[\\{]])
word = word:gsub([[^%(']], [[']])
word = word:gsub('|', 'bar')
word = word:gsub([["]], 'quote')
word = word:gsub('[$.~]', [[\%0]])
word = word:gsub('%*', '.*')
word = word:gsub('?', '.')
-- Handle control characters.
-- First convert raw control chars to the caret notation
-- E.g. 0x01 --> '^A' etc.
---@type string
word = word:gsub('([\1-\31])', function(ctrl_char)
-- '^\' needs an extra backslash
local repr = string.char(ctrl_char:byte() + 64):gsub([[\]], [[\\]])
return '^' .. repr
end)
-- Change caret notation to 'CTRL-', except '^_'
-- E.g. 'i^G^J' --> 'iCTRL-GCTRL-J'
word = word:gsub('%^([^_])', 'CTRL-%1')
-- Add underscores around 'CTRL-X' characters
-- E.g. 'iCTRL-GCTRL-J' --> 'i_CTRL-G_CTRL-J'
-- Only exception: 'CTRL-{character}'
word = word:gsub('([^_])CTRL%-', '%1_CTRL-')
word = word:gsub('(CTRL%-[^{])([^_\\])', '%1_%2')
-- Skip function arguments
-- E.g. 'abs({expr})' --> 'abs'
-- E.g. 'abs([arg])' --> 'abs'
word = word:gsub('%({.*', '')
word = word:gsub('%(%[.*', '')
-- Skip punctuation after second apostrophe/curly brace
-- E.g. ''option',' --> ''option''
-- E.g. '{address},' --> '{address}'
-- E.g. '`command`,' --> 'command' (backticks are removed too, but '``' stays '``')
word = word:gsub([[^'([^']*)'.*]], [['%1']])
word = word:gsub([[^{([^}]*)}.*]], '{%1}')
word = word:gsub([[.*`([^`]+)`.*]], '%1')
end
return word
end
---Populates the |local-additions| section of a help buffer with references to locally-installed
---help files. These are help files outside of $VIMRUNTIME (typically from plugins) whose first
---line contains a tag (e.g. *plugin-name.txt*) and a short description.
---
---For each help file found in 'runtimepath', the first line is extracted and added to the buffer
---as a reference (converting '*tag*' to '|tag|'). If a translated version of a help file exists
---in the same language as the current buffer (e.g. 'plugin.nlx' alongside 'plugin.txt'), the
---translated version is preferred over the '.txt' file.
function M.local_additions()
local buf = vim.api.nvim_get_current_buf()
local bufname = vim.fs.basename(vim.api.nvim_buf_get_name(buf))
-- "help.txt" or "help.??x" where ?? is a language code, see |help-translated|.
local lang = bufname:match('^help%.(%a%a)x$')
if bufname ~= 'help.txt' and not lang then
return
end
-- Find local help files
---@type table<string, string>
local plugins = {}
local pattern = lang and ('doc/*.{txt,%sx}'):format(lang) or 'doc/*.txt'
for _, docpath in ipairs(vim.api.nvim_get_runtime_file(pattern, true)) do
if not vim.fs.relpath(vim.env.VIMRUNTIME, docpath) then
-- '/path/to/doc/plugin.txt' --> 'plugin'
local plugname = vim.fs.basename(docpath):sub(1, -5)
-- prefer language-specific files over .txt
if not plugins[plugname] or vim.endswith(plugins[plugname], '.txt') then
plugins[plugname] = docpath
end
end
end
-- Format plugin list lines
-- Default to 78 if 'textwidth' is not set (e.g. in sandbox)
local textwidth = math.max(vim.bo[buf].textwidth, 78)
local lines = {}
for _, path in vim.spairs(plugins) do
local fp = io.open(path, 'r')
if fp then
local tagline = fp:read('*l') or ''
fp:close()
---@type string, string
local plugname, desc = tagline:match('^%*([^*]+)%*%s*(.*)$')
if plugname and desc then
-- left-align taglink and right-align description by inserting spaces in between
local plug_width = vim.fn.strdisplaywidth(plugname)
local _, concealed_chars = desc:gsub('|', '')
local desc_width = vim.fn.strdisplaywidth(desc) - concealed_chars
-- max(l, 1) forces at least one space for if the description is too long
local spaces = string.rep(' ', math.max(textwidth - desc_width - plug_width - 2, 1))
local fmt = string.format('|%s|%s%s', plugname, spaces, desc)
table.insert(lines, fmt)
end
end
end
-- Add plugin list to local-additions section
for linenr, line in ipairs(vim.api.nvim_buf_get_lines(buf, 0, -1, false)) do
if line:find('*local-additions*', 1, true) then
vim._with({ buf = buf, bo = { modifiable = true, readonly = false } }, function()
vim.api.nvim_buf_set_lines(buf, linenr, linenr, true, lines)
end)
break
end
end
end
return M