gen_eval_files.lua 28 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091
  1. #!/usr/bin/env -S nvim -l
  2. -- Generator for various vimdoc and Lua type files
  3. local util = require('scripts.util')
  4. local fmt = string.format
  5. local DEP_API_METADATA = 'build/funcs_metadata.mpack'
  6. local TEXT_WIDTH = 78
  7. --- @class vim.api.metadata
  8. --- @field name string
  9. --- @field parameters [string,string][]
  10. --- @field return_type string
  11. --- @field deprecated_since integer
  12. --- @field eval boolean
  13. --- @field fast boolean
  14. --- @field handler_id integer
  15. --- @field impl_name string
  16. --- @field lua boolean
  17. --- @field method boolean
  18. --- @field remote boolean
  19. --- @field since integer
  20. local LUA_API_RETURN_OVERRIDES = {
  21. nvim_buf_get_command = 'table<string,vim.api.keyset.command_info>',
  22. nvim_buf_get_extmark_by_id = 'vim.api.keyset.get_extmark_item_by_id',
  23. nvim_buf_get_extmarks = 'vim.api.keyset.get_extmark_item[]',
  24. nvim_buf_get_keymap = 'vim.api.keyset.get_keymap[]',
  25. nvim_get_autocmds = 'vim.api.keyset.get_autocmds.ret[]',
  26. nvim_get_color_map = 'table<string,integer>',
  27. nvim_get_command = 'table<string,vim.api.keyset.command_info>',
  28. nvim_get_keymap = 'vim.api.keyset.get_keymap[]',
  29. nvim_get_mark = 'vim.api.keyset.get_mark',
  30. -- Can also return table<string,vim.api.keyset.get_hl_info>, however we need to
  31. -- pick one to get some benefit.
  32. -- REVISIT lewrus01 (26/01/24): we can maybe add
  33. -- @overload fun(ns: integer, {}): table<string,vim.api.keyset.get_hl_info>
  34. nvim_get_hl = 'vim.api.keyset.get_hl_info',
  35. nvim_get_mode = 'vim.api.keyset.get_mode',
  36. nvim_get_namespaces = 'table<string,integer>',
  37. nvim_get_option_info = 'vim.api.keyset.get_option_info',
  38. nvim_get_option_info2 = 'vim.api.keyset.get_option_info',
  39. nvim_parse_cmd = 'vim.api.keyset.parse_cmd',
  40. nvim_win_get_config = 'vim.api.keyset.win_config',
  41. }
  42. local LUA_API_KEYSET_OVERRIDES = {
  43. create_autocmd = {
  44. callback = 'string|(fun(args: vim.api.keyset.create_autocmd.callback_args): boolean?)',
  45. },
  46. }
  47. local LUA_API_PARAM_OVERRIDES = {
  48. nvim_create_user_command = {
  49. command = 'string|fun(args: vim.api.keyset.create_user_command.command_args)',
  50. },
  51. }
  52. local LUA_META_HEADER = {
  53. '--- @meta _',
  54. '-- THIS FILE IS GENERATED',
  55. '-- DO NOT EDIT',
  56. "error('Cannot require a meta file')",
  57. }
  58. local LUA_API_META_HEADER = {
  59. '--- @meta _',
  60. '-- THIS FILE IS GENERATED',
  61. '-- DO NOT EDIT',
  62. "error('Cannot require a meta file')",
  63. '',
  64. '--- This file embeds vimdoc as the function descriptions',
  65. '--- so ignore any doc related errors.',
  66. '--- @diagnostic disable: undefined-doc-name,luadoc-miss-symbol',
  67. '',
  68. 'vim.api = {}',
  69. }
  70. local LUA_OPTION_META_HEADER = {
  71. '--- @meta _',
  72. '-- THIS FILE IS GENERATED',
  73. '-- DO NOT EDIT',
  74. "error('Cannot require a meta file')",
  75. '',
  76. '---@class vim.bo',
  77. '---@field [integer] vim.bo',
  78. 'vim.bo = vim.bo',
  79. '',
  80. '---@class vim.wo',
  81. '---@field [integer] vim.wo',
  82. 'vim.wo = vim.wo',
  83. }
  84. local LUA_VVAR_META_HEADER = {
  85. '--- @meta _',
  86. '-- THIS FILE IS GENERATED',
  87. '-- DO NOT EDIT',
  88. "error('Cannot require a meta file')",
  89. '',
  90. '--- @class vim.v',
  91. 'vim.v = ...',
  92. }
  93. local LUA_KEYWORDS = {
  94. ['and'] = true,
  95. ['end'] = true,
  96. ['function'] = true,
  97. ['or'] = true,
  98. ['if'] = true,
  99. ['while'] = true,
  100. ['repeat'] = true,
  101. ['true'] = true,
  102. ['false'] = true,
  103. }
  104. local OPTION_TYPES = {
  105. boolean = 'boolean',
  106. number = 'integer',
  107. string = 'string',
  108. }
  109. local API_TYPES = {
  110. Window = 'integer',
  111. Tabpage = 'integer',
  112. Buffer = 'integer',
  113. Boolean = 'boolean',
  114. Object = 'any',
  115. Integer = 'integer',
  116. String = 'string',
  117. Array = 'any[]',
  118. LuaRef = 'function',
  119. Dict = 'table<string,any>',
  120. Float = 'number',
  121. HLGroupID = 'integer|string',
  122. void = '',
  123. }
  124. --- @param s string
  125. --- @return string
  126. local function luaescape(s)
  127. if LUA_KEYWORDS[s] then
  128. return s .. '_'
  129. end
  130. return s
  131. end
  132. --- @param x string
  133. --- @param sep? string
  134. --- @return string[]
  135. local function split(x, sep)
  136. return vim.split(x, sep or '\n', { plain = true })
  137. end
  138. --- Convert an API type to Lua
  139. --- @param t string
  140. --- @return string
  141. local function api_type(t)
  142. if vim.startswith(t, '*') then
  143. return api_type(t:sub(2)) .. '?'
  144. end
  145. local as0 = t:match('^ArrayOf%((.*)%)')
  146. if as0 then
  147. local as = split(as0, ', ')
  148. return api_type(as[1]) .. '[]'
  149. end
  150. local d = t:match('^Dict%((.*)%)')
  151. if d then
  152. return 'vim.api.keyset.' .. d
  153. end
  154. local d0 = t:match('^DictOf%((.*)%)')
  155. if d0 then
  156. return 'table<string,' .. api_type(d0) .. '>'
  157. end
  158. local u = t:match('^Union%((.*)%)')
  159. if u then
  160. local us = vim.split(u, ',%s*')
  161. return table.concat(vim.tbl_map(api_type, us), '|')
  162. end
  163. local l = t:match('^LuaRefOf%((.*)%)')
  164. if l then
  165. --- @type string
  166. l = l:gsub('%s+', ' ')
  167. --- @type string?, string?
  168. local as, r = l:match('%((.*)%),%s*(.*)')
  169. if not as then
  170. --- @type string
  171. as = assert(l:match('%((.*)%)'))
  172. end
  173. local as1 = {} --- @type string[]
  174. for a in vim.gsplit(as, ',%s') do
  175. local a1 = vim.split(a, '%s+', { trimempty = true })
  176. local nm = a1[2]:gsub('%*(.*)$', '%1?')
  177. as1[#as1 + 1] = nm .. ': ' .. api_type(a1[1])
  178. end
  179. return ('fun(%s)%s'):format(table.concat(as1, ', '), r and ': ' .. api_type(r) or '')
  180. end
  181. return API_TYPES[t] or t
  182. end
  183. --- @param f string
  184. --- @param params [string,string][]|true
  185. --- @return string
  186. local function render_fun_sig(f, params)
  187. local param_str --- @type string
  188. if params == true then
  189. param_str = '...'
  190. else
  191. param_str = table.concat(
  192. vim.tbl_map(
  193. --- @param v [string,string]
  194. --- @return string
  195. function(v)
  196. return luaescape(v[1])
  197. end,
  198. params
  199. ),
  200. ', '
  201. )
  202. end
  203. if LUA_KEYWORDS[f] then
  204. return fmt("vim.fn['%s'] = function(%s) end", f, param_str)
  205. else
  206. return fmt('function vim.fn.%s(%s) end', f, param_str)
  207. end
  208. end
  209. --- Uniquify names
  210. --- @param params [string,string,string][]
  211. --- @return [string,string,string][]
  212. local function process_params(params)
  213. local seen = {} --- @type table<string,true>
  214. local sfx = 1
  215. for _, p in ipairs(params) do
  216. if seen[p[1]] then
  217. p[1] = p[1] .. sfx
  218. sfx = sfx + 1
  219. else
  220. seen[p[1]] = true
  221. end
  222. end
  223. return params
  224. end
  225. --- @return table<string, vim.EvalFn>
  226. local function get_api_meta()
  227. local ret = {} --- @type table<string, vim.EvalFn>
  228. local cdoc_parser = require('scripts.cdoc_parser')
  229. local f = 'src/nvim/api'
  230. local function include(fun)
  231. if not vim.startswith(fun.name, 'nvim_') then
  232. return false
  233. end
  234. if vim.tbl_contains(fun.attrs or {}, 'lua_only') then
  235. return true
  236. end
  237. if vim.tbl_contains(fun.attrs or {}, 'remote_only') then
  238. return false
  239. end
  240. return true
  241. end
  242. --- @type table<string,nvim.cdoc.parser.fun>
  243. local functions = {}
  244. for path, ty in vim.fs.dir(f) do
  245. if ty == 'file' then
  246. local filename = vim.fs.joinpath(f, path)
  247. local _, funs = cdoc_parser.parse(filename)
  248. for _, fn in ipairs(funs) do
  249. if include(fn) then
  250. functions[fn.name] = fn
  251. end
  252. end
  253. end
  254. end
  255. for _, fun in pairs(functions) do
  256. local deprecated = fun.deprecated_since ~= nil
  257. local notes = {} --- @type string[]
  258. for _, note in ipairs(fun.notes or {}) do
  259. notes[#notes + 1] = note.desc
  260. end
  261. local sees = {} --- @type string[]
  262. for _, see in ipairs(fun.see or {}) do
  263. sees[#sees + 1] = see.desc
  264. end
  265. local pty_overrides = LUA_API_PARAM_OVERRIDES[fun.name] or {}
  266. local params = {} --- @type [string,string][]
  267. for _, p in ipairs(fun.params) do
  268. params[#params + 1] = {
  269. p.name,
  270. api_type(pty_overrides[p.name] or p.type),
  271. not deprecated and p.desc or nil,
  272. }
  273. end
  274. local r = {
  275. signature = 'NA',
  276. name = fun.name,
  277. params = params,
  278. notes = notes,
  279. see = sees,
  280. returns = api_type(fun.returns[1].type),
  281. deprecated = deprecated,
  282. }
  283. if not deprecated then
  284. r.desc = fun.desc
  285. r.returns_desc = fun.returns[1].desc
  286. end
  287. ret[fun.name] = r
  288. end
  289. return ret
  290. end
  291. --- Convert vimdoc references to markdown literals
  292. --- Convert vimdoc codeblocks to markdown codeblocks
  293. ---
  294. --- Ensure code blocks have one empty line before the start fence and after the closing fence.
  295. ---
  296. --- @param x string
  297. --- @param special string?
  298. --- | 'see-api-meta' Normalize `@see` for API meta docstrings.
  299. --- @return string
  300. local function norm_text(x, special)
  301. if special == 'see-api-meta' then
  302. -- Try to guess a symbol that actually works in @see.
  303. -- "nvim_xx()" => "vim.api.nvim_xx"
  304. x = x:gsub([=[%|?(nvim_[^.()| ]+)%(?%)?%|?]=], 'vim.api.%1')
  305. -- TODO: Remove backticks when LuaLS resolves: https://github.com/LuaLS/lua-language-server/issues/2889
  306. -- "|foo|" => "`:help foo`"
  307. x = x:gsub([=[|([^ ]+)|]=], '`:help %1`')
  308. end
  309. return (
  310. x:gsub('|([^ ]+)|', '`%1`')
  311. :gsub('\n*>lua', '\n\n```lua')
  312. :gsub('\n*>vim', '\n\n```vim')
  313. :gsub('\n+<$', '\n```')
  314. :gsub('\n+<\n+', '\n```\n\n')
  315. :gsub('%s+>\n+', '\n```\n')
  316. :gsub('\n+<%s+\n?', '\n```\n')
  317. )
  318. end
  319. --- Generates LuaLS docstring for an API function.
  320. --- @param _f string
  321. --- @param fun vim.EvalFn
  322. --- @param write fun(line: string)
  323. local function render_api_meta(_f, fun, write)
  324. write('')
  325. if vim.startswith(fun.name, 'nvim__') then
  326. write('--- @private')
  327. end
  328. if fun.deprecated then
  329. write('--- @deprecated')
  330. end
  331. local desc = fun.desc
  332. if desc then
  333. write(util.prefix_lines('--- ', norm_text(desc)))
  334. end
  335. -- LuaLS doesn't support @note. Render @note items as a markdown list.
  336. if fun.notes and #fun.notes > 0 then
  337. write('--- Note:')
  338. write(util.prefix_lines('--- ', table.concat(fun.notes, '\n')))
  339. write('---')
  340. end
  341. for _, see in ipairs(fun.see or {}) do
  342. write(util.prefix_lines('--- @see ', norm_text(see, 'see-api-meta')))
  343. end
  344. local param_names = {} --- @type string[]
  345. local params = process_params(fun.params)
  346. for _, p in ipairs(params) do
  347. local pname, ptype, pdesc = luaescape(p[1]), p[2], p[3]
  348. param_names[#param_names + 1] = pname
  349. if pdesc then
  350. local s = '--- @param ' .. pname .. ' ' .. ptype .. ' '
  351. local pdesc_a = split(vim.trim(norm_text(pdesc)))
  352. write(s .. pdesc_a[1])
  353. for i = 2, #pdesc_a do
  354. if not pdesc_a[i] then
  355. break
  356. end
  357. write('--- ' .. pdesc_a[i])
  358. end
  359. else
  360. write('--- @param ' .. pname .. ' ' .. ptype)
  361. end
  362. end
  363. if fun.returns ~= '' then
  364. local ret_desc = fun.returns_desc and ' # ' .. fun.returns_desc or ''
  365. local ret = LUA_API_RETURN_OVERRIDES[fun.name] or fun.returns
  366. write(util.prefix_lines('--- ', '@return ' .. ret .. ret_desc))
  367. end
  368. local param_str = table.concat(param_names, ', ')
  369. write(fmt('function vim.api.%s(%s) end', fun.name, param_str))
  370. end
  371. --- @return table<string, vim.EvalFn>
  372. local function get_api_keysets_meta()
  373. local mpack_f = assert(io.open(DEP_API_METADATA, 'rb'))
  374. local metadata = assert(vim.mpack.decode(mpack_f:read('*all')))
  375. local ret = {} --- @type table<string, vim.EvalFn>
  376. --- @type {name: string, keys: string[], types: table<string,string>}[]
  377. local keysets = metadata.keysets
  378. for _, k in ipairs(keysets) do
  379. local pty_overrides = LUA_API_KEYSET_OVERRIDES[k.name] or {}
  380. local params = {}
  381. for _, key in ipairs(k.keys) do
  382. local pty = pty_overrides[key] or k.types[key] or 'any'
  383. table.insert(params, { key .. '?', api_type(pty) })
  384. end
  385. ret[k.name] = {
  386. signature = 'NA',
  387. name = k.name,
  388. params = params,
  389. }
  390. end
  391. return ret
  392. end
  393. --- Generates LuaLS docstring for an API keyset.
  394. --- @param _f string
  395. --- @param fun vim.EvalFn
  396. --- @param write fun(line: string)
  397. local function render_api_keyset_meta(_f, fun, write)
  398. if string.sub(fun.name, 1, 1) == '_' then
  399. return -- not exported
  400. end
  401. write('')
  402. write('--- @class vim.api.keyset.' .. fun.name)
  403. for _, p in ipairs(fun.params) do
  404. write('--- @field ' .. p[1] .. ' ' .. p[2])
  405. end
  406. end
  407. --- @return table<string, vim.EvalFn>
  408. local function get_eval_meta()
  409. return require('src/nvim/eval').funcs
  410. end
  411. --- Generates LuaLS docstring for a Vimscript "eval" function.
  412. --- @param f string
  413. --- @param fun vim.EvalFn
  414. --- @param write fun(line: string)
  415. local function render_eval_meta(f, fun, write)
  416. if fun.lua == false then
  417. return
  418. end
  419. local funname = fun.name or f
  420. local params = process_params(fun.params)
  421. write('')
  422. if fun.deprecated then
  423. write('--- @deprecated')
  424. end
  425. local desc = fun.desc
  426. if desc then
  427. --- @type string
  428. desc = desc:gsub('\n%s*\n%s*$', '\n')
  429. for _, l in ipairs(split(desc)) do
  430. l = l:gsub('^ ', ''):gsub('\t', ' '):gsub('@', '\\@')
  431. write('--- ' .. l)
  432. end
  433. end
  434. for _, text in ipairs(vim.fn.reverse(fun.generics or {})) do
  435. write(fmt('--- @generic %s', text))
  436. end
  437. local req_args = type(fun.args) == 'table' and fun.args[1] or fun.args or 0
  438. for i, param in ipairs(params) do
  439. local pname, ptype = luaescape(param[1]), param[2]
  440. local optional = (pname ~= '...' and i > req_args) and '?' or ''
  441. write(fmt('--- @param %s%s %s', pname, optional, ptype))
  442. end
  443. if fun.returns ~= false then
  444. local ret_desc = fun.returns_desc and ' # ' .. fun.returns_desc or ''
  445. write('--- @return ' .. (fun.returns or 'any') .. ret_desc)
  446. end
  447. write(render_fun_sig(funname, params))
  448. end
  449. --- Generates vimdoc heading for a Vimscript "eval" function signature.
  450. --- @param name string
  451. --- @param name_tag boolean
  452. --- @param fun vim.EvalFn
  453. --- @param write fun(line: string)
  454. local function render_sig_and_tag(name, name_tag, fun, write)
  455. if not fun.signature then
  456. return
  457. end
  458. local tags = name_tag and { '*' .. name .. '()*' } or {}
  459. if fun.tags then
  460. for _, t in ipairs(fun.tags) do
  461. tags[#tags + 1] = '*' .. t .. '*'
  462. end
  463. end
  464. if #tags == 0 then
  465. write(fun.signature)
  466. return
  467. end
  468. local tag = table.concat(tags, ' ')
  469. local siglen = #fun.signature
  470. local conceal_offset = 2 * (#tags - 1)
  471. local tag_pad_len = math.max(1, 80 - #tag + conceal_offset)
  472. if siglen + #tag > 80 then
  473. write(string.rep(' ', tag_pad_len) .. tag)
  474. write(fun.signature)
  475. else
  476. write(fmt('%s%s%s', fun.signature, string.rep(' ', tag_pad_len - siglen), tag))
  477. end
  478. end
  479. --- Generates vimdoc for a Vimscript "eval" function.
  480. --- @param f string
  481. --- @param fun vim.EvalFn
  482. --- @param write fun(line: string)
  483. local function render_eval_doc(f, fun, write)
  484. if fun.deprecated or not fun.signature then
  485. return
  486. end
  487. render_sig_and_tag(fun.name or f, not f:find('__%d+$'), fun, write)
  488. if not fun.desc then
  489. return
  490. end
  491. local params = process_params(fun.params)
  492. local req_args = type(fun.args) == 'table' and fun.args[1] or fun.args or 0
  493. local desc_l = split(vim.trim(fun.desc))
  494. for _, l in ipairs(desc_l) do
  495. l = l:gsub('^ ', '')
  496. if vim.startswith(l, '<') and not l:match('^<[^ \t]+>') then
  497. write('<\t\t' .. l:sub(2))
  498. elseif l:match('^>[a-z0-9]*$') then
  499. write(l)
  500. else
  501. write('\t\t' .. l)
  502. end
  503. end
  504. if #desc_l > 0 and not desc_l[#desc_l]:match('^<?$') then
  505. write('')
  506. end
  507. if #params > 0 then
  508. write(util.md_to_vimdoc('Parameters: ~', 16, 16, TEXT_WIDTH))
  509. for i, param in ipairs(params) do
  510. local pname, ptype = param[1], param[2]
  511. local optional = (pname ~= '...' and i > req_args) and '?' or ''
  512. local s = fmt('- %-14s (`%s%s`)', fmt('{%s}', pname), ptype, optional)
  513. write(util.md_to_vimdoc(s, 16, 18, TEXT_WIDTH))
  514. end
  515. write('')
  516. end
  517. if fun.returns ~= false then
  518. write(util.md_to_vimdoc('Return: ~', 16, 16, TEXT_WIDTH))
  519. local ret = ('(`%s`)'):format((fun.returns or 'any'))
  520. ret = ret .. (fun.returns_desc and ' ' .. fun.returns_desc or '')
  521. ret = util.md_to_vimdoc(ret, 18, 18, TEXT_WIDTH)
  522. write(ret)
  523. write('')
  524. end
  525. end
  526. --- @param d vim.option_defaults
  527. --- @param vimdoc? boolean
  528. --- @return string
  529. local function render_option_default(d, vimdoc)
  530. local dt --- @type integer|boolean|string|fun(): string
  531. if d.if_false ~= nil then
  532. dt = d.if_false
  533. else
  534. dt = d.if_true
  535. end
  536. if vimdoc then
  537. if d.doc then
  538. return d.doc
  539. end
  540. if type(dt) == 'boolean' then
  541. return dt and 'on' or 'off'
  542. end
  543. end
  544. if dt == '' or dt == nil or type(dt) == 'function' then
  545. dt = d.meta
  546. end
  547. local v --- @type string
  548. if not vimdoc then
  549. v = vim.inspect(dt) --[[@as string]]
  550. else
  551. v = type(dt) == 'string' and '"' .. dt .. '"' or tostring(dt)
  552. end
  553. --- @type table<string, string|false>
  554. local envvars = {
  555. TMPDIR = false,
  556. VIMRUNTIME = false,
  557. XDG_CONFIG_HOME = vim.env.HOME .. '/.local/config',
  558. XDG_DATA_HOME = vim.env.HOME .. '/.local/share',
  559. XDG_STATE_HOME = vim.env.HOME .. '/.local/state',
  560. }
  561. for name, default in pairs(envvars) do
  562. local value = vim.env[name] or default
  563. if value then
  564. v = v:gsub(vim.pesc(value), '$' .. name)
  565. end
  566. end
  567. return v
  568. end
  569. --- @param _f string
  570. --- @param opt vim.option_meta
  571. --- @param write fun(line: string)
  572. local function render_option_meta(_f, opt, write)
  573. write('')
  574. for _, l in ipairs(split(norm_text(opt.desc))) do
  575. write('--- ' .. l)
  576. end
  577. if opt.type == 'string' and not opt.list and opt.values then
  578. local values = {} --- @type string[]
  579. for _, e in ipairs(opt.values) do
  580. values[#values + 1] = fmt("'%s'", e)
  581. end
  582. write('--- @type ' .. table.concat(values, '|'))
  583. else
  584. write('--- @type ' .. OPTION_TYPES[opt.type])
  585. end
  586. write('vim.o.' .. opt.full_name .. ' = ' .. render_option_default(opt.defaults))
  587. if opt.abbreviation then
  588. write('vim.o.' .. opt.abbreviation .. ' = vim.o.' .. opt.full_name)
  589. end
  590. for _, s in pairs {
  591. { 'wo', 'win' },
  592. { 'bo', 'buf' },
  593. { 'go', 'global' },
  594. } do
  595. local id, scope = s[1], s[2]
  596. if vim.list_contains(opt.scope, scope) or (id == 'go' and #opt.scope > 1) then
  597. local pfx = 'vim.' .. id .. '.'
  598. write(pfx .. opt.full_name .. ' = vim.o.' .. opt.full_name)
  599. if opt.abbreviation then
  600. write(pfx .. opt.abbreviation .. ' = ' .. pfx .. opt.full_name)
  601. end
  602. end
  603. end
  604. end
  605. --- @param _f string
  606. --- @param opt vim.option_meta
  607. --- @param write fun(line: string)
  608. local function render_vvar_meta(_f, opt, write)
  609. write('')
  610. local desc = split(norm_text(opt.desc))
  611. while desc[#desc]:match('^%s*$') do
  612. desc[#desc] = nil
  613. end
  614. for _, l in ipairs(desc) do
  615. write('--- ' .. l)
  616. end
  617. write('--- @type ' .. (opt.type or 'any'))
  618. if LUA_KEYWORDS[opt.full_name] then
  619. write("vim.v['" .. opt.full_name .. "'] = ...")
  620. else
  621. write('vim.v.' .. opt.full_name .. ' = ...')
  622. end
  623. end
  624. --- @param s string[]
  625. --- @return string
  626. local function scope_to_doc(s)
  627. local m = {
  628. global = 'global',
  629. buf = 'local to buffer',
  630. win = 'local to window',
  631. tab = 'local to tab page',
  632. }
  633. if #s == 1 then
  634. return m[s[1]]
  635. end
  636. assert(s[1] == 'global')
  637. return 'global or ' .. m[s[2]] .. (s[2] ~= 'tab' and ' |global-local|' or '')
  638. end
  639. -- @param o vim.option_meta
  640. -- @return string
  641. local function scope_more_doc(o)
  642. if
  643. vim.list_contains({
  644. 'bufhidden',
  645. 'buftype',
  646. 'filetype',
  647. 'modified',
  648. 'previewwindow',
  649. 'readonly',
  650. 'scroll',
  651. 'syntax',
  652. 'winfixheight',
  653. 'winfixwidth',
  654. }, o.full_name)
  655. then
  656. return ' |local-noglobal|'
  657. end
  658. return ''
  659. end
  660. --- @param x string
  661. --- @return string
  662. local function dedent(x)
  663. local xs = split(x)
  664. local leading_ws = xs[1]:match('^%s*') --[[@as string]]
  665. local leading_ws_pat = '^' .. leading_ws
  666. for i in ipairs(xs) do
  667. local strip_pat = xs[i]:match(leading_ws_pat) and leading_ws_pat or '^%s*'
  668. xs[i] = xs[i]:gsub(strip_pat, '')
  669. end
  670. return table.concat(xs, '\n')
  671. end
  672. --- @return table<string,vim.option_meta>
  673. local function get_option_meta()
  674. local opts = require('src/nvim/options').options
  675. local optinfo = vim.api.nvim_get_all_options_info()
  676. local ret = {} --- @type table<string,vim.option_meta>
  677. for _, o in ipairs(opts) do
  678. local is_window_option = #o.scope == 1 and o.scope[1] == 'win'
  679. local is_option_hidden = o.immutable and not o.varname and not is_window_option
  680. if not is_option_hidden and o.desc then
  681. if o.full_name == 'cmdheight' then
  682. table.insert(o.scope, 'tab')
  683. end
  684. local r = vim.deepcopy(o) --[[@as vim.option_meta]]
  685. r.desc = o.desc:gsub('^ ', ''):gsub('\n ', '\n')
  686. r.defaults = r.defaults or {}
  687. if r.defaults.meta == nil then
  688. r.defaults.meta = optinfo[o.full_name].default
  689. end
  690. ret[o.full_name] = r
  691. end
  692. end
  693. return ret
  694. end
  695. --- @return table<string,vim.option_meta>
  696. local function get_vvar_meta()
  697. local info = require('src/nvim/vvars').vars
  698. local ret = {} --- @type table<string,vim.option_meta>
  699. for name, o in pairs(info) do
  700. o.desc = dedent(o.desc)
  701. o.full_name = name
  702. ret[name] = o
  703. end
  704. return ret
  705. end
  706. --- @param opt vim.option_meta
  707. --- @return string[]
  708. local function build_option_tags(opt)
  709. --- @type string[]
  710. local tags = { opt.full_name }
  711. tags[#tags + 1] = opt.abbreviation
  712. if opt.type == 'boolean' then
  713. for i = 1, #tags do
  714. tags[#tags + 1] = 'no' .. tags[i]
  715. end
  716. end
  717. for i, t in ipairs(tags) do
  718. tags[i] = "'" .. t .. "'"
  719. end
  720. for _, t in ipairs(opt.tags or {}) do
  721. tags[#tags + 1] = t
  722. end
  723. for i, t in ipairs(tags) do
  724. tags[i] = '*' .. t .. '*'
  725. end
  726. return tags
  727. end
  728. --- @param _f string
  729. --- @param opt vim.option_meta
  730. --- @param write fun(line: string)
  731. local function render_option_doc(_f, opt, write)
  732. local tags = build_option_tags(opt)
  733. local tag_str = table.concat(tags, ' ')
  734. local conceal_offset = 2 * (#tags - 1)
  735. local tag_pad = string.rep('\t', math.ceil((64 - #tag_str + conceal_offset) / 8))
  736. -- local pad = string.rep(' ', 80 - #tag_str + conceal_offset)
  737. write(tag_pad .. tag_str)
  738. local name_str --- @type string
  739. if opt.abbreviation then
  740. name_str = fmt("'%s' '%s'", opt.full_name, opt.abbreviation)
  741. else
  742. name_str = fmt("'%s'", opt.full_name)
  743. end
  744. local otype = opt.type == 'boolean' and 'boolean' or opt.type
  745. if opt.defaults.doc or opt.defaults.if_true ~= nil or opt.defaults.meta ~= nil then
  746. local v = render_option_default(opt.defaults, true)
  747. local pad = string.rep('\t', math.max(1, math.ceil((24 - #name_str) / 8)))
  748. if opt.defaults.doc then
  749. local deflen = #fmt('%s%s%s (', name_str, pad, otype)
  750. --- @type string
  751. v = v:gsub('\n', '\n' .. string.rep(' ', deflen - 2))
  752. end
  753. write(fmt('%s%s%s\t(default %s)', name_str, pad, otype, v))
  754. else
  755. write(fmt('%s\t%s', name_str, otype))
  756. end
  757. write('\t\t\t' .. scope_to_doc(opt.scope) .. scope_more_doc(opt))
  758. for _, l in ipairs(split(opt.desc)) do
  759. if l == '<' or l:match('^<%s') then
  760. write(l)
  761. else
  762. write('\t' .. l:gsub('\\<', '<'))
  763. end
  764. end
  765. end
  766. --- @param _f string
  767. --- @param vvar vim.option_meta
  768. --- @param write fun(line: string)
  769. local function render_vvar_doc(_f, vvar, write)
  770. local name = vvar.full_name
  771. local tags = { 'v:' .. name, name .. '-variable' }
  772. if vvar.tags then
  773. vim.list_extend(tags, vvar.tags)
  774. end
  775. for i, t in ipairs(tags) do
  776. tags[i] = '*' .. t .. '*'
  777. end
  778. local tag_str = table.concat(tags, ' ')
  779. local conceal_offset = 2 * (#tags - 1)
  780. local tag_pad = string.rep('\t', math.ceil((64 - #tag_str + conceal_offset) / 8))
  781. write(tag_pad .. tag_str)
  782. local desc = split(vvar.desc)
  783. if (#desc == 1 or #desc == 2 and desc[2]:match('^%s*$')) and #name < 10 then
  784. -- single line
  785. write('v:' .. name .. '\t' .. desc[1]:gsub('^%s*', ''))
  786. write('')
  787. else
  788. write('v:' .. name)
  789. for _, l in ipairs(desc) do
  790. if l == '<' or l:match('^<%s') then
  791. write(l)
  792. else
  793. write('\t\t' .. l:gsub('\\<', '<'))
  794. end
  795. end
  796. end
  797. end
  798. --- @class nvim.gen_eval_files.elem
  799. --- @field path string
  800. --- @field from? string Skip lines in path until this pattern is reached.
  801. --- @field funcs fun(): table<string, table>
  802. --- @field render fun(f:string,obj:table,write:fun(line:string))
  803. --- @field header? string[]
  804. --- @field footer? string[]
  805. --- @type nvim.gen_eval_files.elem[]
  806. local CONFIG = {
  807. {
  808. path = 'runtime/lua/vim/_meta/vimfn.lua',
  809. header = LUA_META_HEADER,
  810. funcs = get_eval_meta,
  811. render = render_eval_meta,
  812. },
  813. {
  814. path = 'runtime/lua/vim/_meta/api.lua',
  815. header = LUA_API_META_HEADER,
  816. funcs = get_api_meta,
  817. render = render_api_meta,
  818. },
  819. {
  820. path = 'runtime/lua/vim/_meta/api_keysets.lua',
  821. header = LUA_META_HEADER,
  822. funcs = get_api_keysets_meta,
  823. render = render_api_keyset_meta,
  824. },
  825. {
  826. path = 'runtime/doc/builtin.txt',
  827. funcs = get_eval_meta,
  828. render = render_eval_doc,
  829. header = {
  830. '*builtin.txt* Nvim',
  831. '',
  832. '',
  833. '\t\t NVIM REFERENCE MANUAL',
  834. '',
  835. '',
  836. 'Builtin functions\t\t*vimscript-functions* *builtin-functions*',
  837. '',
  838. 'For functions grouped by what they are used for see |function-list|.',
  839. '',
  840. '\t\t\t\t Type |gO| to see the table of contents.',
  841. '==============================================================================',
  842. '1. Details *builtin-function-details*',
  843. '',
  844. },
  845. footer = {
  846. '==============================================================================',
  847. '2. Matching a pattern in a String *string-match*',
  848. '',
  849. 'This is common between several functions. A regexp pattern as explained at',
  850. '|pattern| is normally used to find a match in the buffer lines. When a',
  851. 'pattern is used to find a match in a String, almost everything works in the',
  852. 'same way. The difference is that a String is handled like it is one line.',
  853. 'When it contains a "\\n" character, this is not seen as a line break for the',
  854. 'pattern. It can be matched with a "\\n" in the pattern, or with ".". Example:',
  855. '>vim',
  856. '\tlet a = "aaaa\\nxxxx"',
  857. '\techo matchstr(a, "..\\n..")',
  858. '\t" aa',
  859. '\t" xx',
  860. '\techo matchstr(a, "a.x")',
  861. '\t" a',
  862. '\t" x',
  863. '',
  864. 'Don\'t forget that "^" will only match at the first character of the String and',
  865. '"$" at the last character of the string. They don\'t match after or before a',
  866. '"\\n".',
  867. '',
  868. ' vim:tw=78:ts=8:noet:ft=help:norl:',
  869. },
  870. },
  871. {
  872. path = 'runtime/lua/vim/_meta/options.lua',
  873. header = LUA_OPTION_META_HEADER,
  874. funcs = get_option_meta,
  875. render = render_option_meta,
  876. },
  877. {
  878. path = 'runtime/doc/options.txt',
  879. header = { '' },
  880. from = 'A jump table for the options with a short description can be found at |Q_op|.',
  881. footer = {
  882. ' vim:tw=78:ts=8:noet:ft=help:norl:',
  883. },
  884. funcs = get_option_meta,
  885. render = render_option_doc,
  886. },
  887. {
  888. path = 'runtime/lua/vim/_meta/vvars.lua',
  889. header = LUA_VVAR_META_HEADER,
  890. funcs = get_vvar_meta,
  891. render = render_vvar_meta,
  892. },
  893. {
  894. path = 'runtime/doc/vvars.txt',
  895. header = { '' },
  896. from = 'Type |gO| to see the table of contents.',
  897. footer = {
  898. ' vim:tw=78:ts=8:noet:ft=help:norl:',
  899. },
  900. funcs = get_vvar_meta,
  901. render = render_vvar_doc,
  902. },
  903. }
  904. --- @param elem nvim.gen_eval_files.elem
  905. local function render(elem)
  906. print('Rendering ' .. elem.path)
  907. local from_lines = {} --- @type string[]
  908. local from = elem.from
  909. if from then
  910. for line in io.lines(elem.path) do
  911. from_lines[#from_lines + 1] = line
  912. if line:match(from) then
  913. break
  914. end
  915. end
  916. end
  917. local o = assert(io.open(elem.path, 'w'))
  918. --- @param l string
  919. local function write(l)
  920. local l1 = l:gsub('%s+$', '')
  921. o:write(l1)
  922. o:write('\n')
  923. end
  924. for _, l in ipairs(from_lines) do
  925. write(l)
  926. end
  927. for _, l in ipairs(elem.header or {}) do
  928. write(l)
  929. end
  930. local funcs = elem.funcs()
  931. --- @type string[]
  932. local fnames = vim.tbl_keys(funcs)
  933. table.sort(fnames)
  934. for _, f in ipairs(fnames) do
  935. elem.render(f, funcs[f], write)
  936. end
  937. for _, l in ipairs(elem.footer or {}) do
  938. write(l)
  939. end
  940. o:close()
  941. end
  942. local function main()
  943. for _, c in ipairs(CONFIG) do
  944. render(c)
  945. end
  946. end
  947. main()