gen_eval_files.lua 27 KB

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