ccomplete.lua 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859
  1. ----------------------------------------
  2. -- This file is generated via github.com/tjdevries/vim9jit
  3. -- For any bugs, please first consider reporting there.
  4. ----------------------------------------
  5. -- Ignore "value assigned to a local variable is unused" because
  6. -- we can't guarantee that local variables will be used by plugins
  7. -- luacheck: ignore 311
  8. local vim9 = require('_vim9script')
  9. local M = {}
  10. local prepended = nil
  11. local grepCache = nil
  12. local Complete = nil
  13. local GetAddition = nil
  14. local Tag2item = nil
  15. local Dict2info = nil
  16. local ParseTagline = nil
  17. local Tagline2item = nil
  18. local Tagcmd2extra = nil
  19. local Nextitem = nil
  20. local StructMembers = nil
  21. local SearchMembers = nil
  22. -- vim9script
  23. -- # Vim completion script
  24. -- # Language: C
  25. -- # Maintainer: The Vim Project <https://github.com/vim/vim>
  26. -- # Last Change: 2023 Aug 10
  27. -- # Rewritten in Vim9 script by github user lacygoill
  28. -- # Former Maintainer: Bram Moolenaar <Bram@vim.org>
  29. prepended = ''
  30. grepCache = vim.empty_dict()
  31. -- # This function is used for the 'omnifunc' option.
  32. Complete = function(findstart, abase)
  33. findstart = vim9.bool(findstart)
  34. if vim9.bool(findstart) then
  35. -- # Locate the start of the item, including ".", "->" and "[...]".
  36. local line = vim9.fn.getline('.')
  37. local start = vim9.fn.charcol('.') - 1
  38. local lastword = -1
  39. while start > 0 do
  40. if vim9.ops.RegexpMatches(vim9.index(line, vim9.ops.Minus(start, 1)), '\\w') then
  41. start = start - 1
  42. elseif
  43. vim9.bool(vim9.ops.RegexpMatches(vim9.index(line, vim9.ops.Minus(start, 1)), '\\.'))
  44. then
  45. if lastword == -1 then
  46. lastword = start
  47. end
  48. start = start - 1
  49. elseif
  50. vim9.bool(
  51. start > 1
  52. and vim9.index(line, vim9.ops.Minus(start, 2)) == '-'
  53. and vim9.index(line, vim9.ops.Minus(start, 1)) == '>'
  54. )
  55. then
  56. if lastword == -1 then
  57. lastword = start
  58. end
  59. start = vim9.ops.Minus(start, 2)
  60. elseif vim9.bool(vim9.index(line, vim9.ops.Minus(start, 1)) == ']') then
  61. -- # Skip over [...].
  62. local n = 0
  63. start = start - 1
  64. while start > 0 do
  65. start = start - 1
  66. if vim9.index(line, start) == '[' then
  67. if n == 0 then
  68. break
  69. end
  70. n = n - 1
  71. elseif vim9.bool(vim9.index(line, start) == ']') then
  72. n = n + 1
  73. end
  74. end
  75. else
  76. break
  77. end
  78. end
  79. -- # Return the column of the last word, which is going to be changed.
  80. -- # Remember the text that comes before it in prepended.
  81. if lastword == -1 then
  82. prepended = ''
  83. return vim9.fn.byteidx(line, start)
  84. end
  85. prepended = vim9.slice(line, start, vim9.ops.Minus(lastword, 1))
  86. return vim9.fn.byteidx(line, lastword)
  87. end
  88. -- # Return list of matches.
  89. local base = prepended .. abase
  90. -- # Don't do anything for an empty base, would result in all the tags in the
  91. -- # tags file.
  92. if base == '' then
  93. return {}
  94. end
  95. -- # init cache for vimgrep to empty
  96. grepCache = {}
  97. -- # Split item in words, keep empty word after "." or "->".
  98. -- # "aa" -> ['aa'], "aa." -> ['aa', ''], "aa.bb" -> ['aa', 'bb'], etc.
  99. -- # We can't use split, because we need to skip nested [...].
  100. -- # "aa[...]" -> ['aa', '[...]'], "aa.bb[...]" -> ['aa', 'bb', '[...]'], etc.
  101. local items = {}
  102. local s = 0
  103. local arrays = 0
  104. while 1 do
  105. local e = vim9.fn.charidx(base, vim9.fn.match(base, '\\.\\|->\\|\\[', s))
  106. if e < 0 then
  107. if s == 0 or vim9.index(base, vim9.ops.Minus(s, 1)) ~= ']' then
  108. vim9.fn.add(items, vim9.slice(base, s, nil))
  109. end
  110. break
  111. end
  112. if s == 0 or vim9.index(base, vim9.ops.Minus(s, 1)) ~= ']' then
  113. vim9.fn.add(items, vim9.slice(base, s, vim9.ops.Minus(e, 1)))
  114. end
  115. if vim9.index(base, e) == '.' then
  116. -- # skip over '.'
  117. s = vim9.ops.Plus(e, 1)
  118. elseif vim9.bool(vim9.index(base, e) == '-') then
  119. -- # skip over '->'
  120. s = vim9.ops.Plus(e, 2)
  121. else
  122. -- # Skip over [...].
  123. local n = 0
  124. s = e
  125. e = e + 1
  126. while e < vim9.fn.strcharlen(base) do
  127. if vim9.index(base, e) == ']' then
  128. if n == 0 then
  129. break
  130. end
  131. n = n - 1
  132. elseif vim9.bool(vim9.index(base, e) == '[') then
  133. n = n + 1
  134. end
  135. e = e + 1
  136. end
  137. e = e + 1
  138. vim9.fn.add(items, vim9.slice(base, s, vim9.ops.Minus(e, 1)))
  139. arrays = arrays + 1
  140. s = e
  141. end
  142. end
  143. -- # Find the variable items[0].
  144. -- # 1. in current function (like with "gd")
  145. -- # 2. in tags file(s) (like with ":tag")
  146. -- # 3. in current file (like with "gD")
  147. local res = {}
  148. if vim9.fn.searchdecl(vim9.index(items, 0), false, true) == 0 then
  149. -- # Found, now figure out the type.
  150. -- # TODO: join previous line if it makes sense
  151. local line = vim9.fn.getline('.')
  152. local col = vim9.fn.charcol('.')
  153. if vim9.fn.stridx(vim9.slice(line, nil, vim9.ops.Minus(col, 1)), ';') >= 0 then
  154. -- # Handle multiple declarations on the same line.
  155. local col2 = vim9.ops.Minus(col, 1)
  156. while vim9.index(line, col2) ~= ';' do
  157. col2 = col2 - 1
  158. end
  159. line = vim9.slice(line, vim9.ops.Plus(col2, 1), nil)
  160. col = vim9.ops.Minus(col, col2)
  161. end
  162. if vim9.fn.stridx(vim9.slice(line, nil, vim9.ops.Minus(col, 1)), ',') >= 0 then
  163. -- # Handle multiple declarations on the same line in a function
  164. -- # declaration.
  165. local col2 = vim9.ops.Minus(col, 1)
  166. while vim9.index(line, col2) ~= ',' do
  167. col2 = col2 - 1
  168. end
  169. if
  170. vim9.ops.RegexpMatches(
  171. vim9.slice(line, vim9.ops.Plus(col2, 1), vim9.ops.Minus(col, 1)),
  172. ' *[^ ][^ ]* *[^ ]'
  173. )
  174. then
  175. line = vim9.slice(line, vim9.ops.Plus(col2, 1), nil)
  176. col = vim9.ops.Minus(col, col2)
  177. end
  178. end
  179. if vim9.fn.len(items) == 1 then
  180. -- # Completing one word and it's a local variable: May add '[', '.' or
  181. -- # '->'.
  182. local match = vim9.index(items, 0)
  183. local kind = 'v'
  184. if vim9.fn.match(line, '\\<' .. match .. '\\s*\\[') > 0 then
  185. match = match .. '['
  186. else
  187. res = Nextitem(vim9.slice(line, nil, vim9.ops.Minus(col, 1)), { '' }, 0, true)
  188. if vim9.fn.len(res) > 0 then
  189. -- # There are members, thus add "." or "->".
  190. if vim9.fn.match(line, '\\*[ \\t(]*' .. match .. '\\>') > 0 then
  191. match = match .. '->'
  192. else
  193. match = match .. '.'
  194. end
  195. end
  196. end
  197. res = { { ['match'] = match, ['tagline'] = '', ['kind'] = kind, ['info'] = line } }
  198. elseif vim9.bool(vim9.fn.len(items) == vim9.ops.Plus(arrays, 1)) then
  199. -- # Completing one word and it's a local array variable: build tagline
  200. -- # from declaration line
  201. local match = vim9.index(items, 0)
  202. local kind = 'v'
  203. local tagline = '\t/^' .. line .. '$/'
  204. res = { { ['match'] = match, ['tagline'] = tagline, ['kind'] = kind, ['info'] = line } }
  205. else
  206. -- # Completing "var.", "var.something", etc.
  207. res =
  208. Nextitem(vim9.slice(line, nil, vim9.ops.Minus(col, 1)), vim9.slice(items, 1, nil), 0, true)
  209. end
  210. end
  211. if vim9.fn.len(items) == 1 or vim9.fn.len(items) == vim9.ops.Plus(arrays, 1) then
  212. -- # Only one part, no "." or "->": complete from tags file.
  213. local tags = {}
  214. if vim9.fn.len(items) == 1 then
  215. tags = vim9.fn.taglist('^' .. base)
  216. else
  217. tags = vim9.fn.taglist('^' .. vim9.index(items, 0) .. '$')
  218. end
  219. vim9.fn_mut('filter', {
  220. vim9.fn_mut('filter', {
  221. tags,
  222. function(_, v)
  223. return vim9.ternary(vim9.fn.has_key(v, 'kind'), function()
  224. return v.kind ~= 'm'
  225. end, true)
  226. end,
  227. }, { replace = 0 }),
  228. function(_, v)
  229. return vim9.ops.Or(
  230. vim9.ops.Or(
  231. vim9.prefix['Bang'](vim9.fn.has_key(v, 'static')),
  232. vim9.prefix['Bang'](vim9.index(v, 'static'))
  233. ),
  234. vim9.fn.bufnr('%') == vim9.fn.bufnr(vim9.index(v, 'filename'))
  235. )
  236. end,
  237. }, { replace = 0 })
  238. res = vim9.fn.extend(
  239. res,
  240. vim9.fn.map(tags, function(_, v)
  241. return Tag2item(v)
  242. end)
  243. )
  244. end
  245. if vim9.fn.len(res) == 0 then
  246. -- # Find the variable in the tags file(s)
  247. local diclist = vim9.fn.filter(
  248. vim9.fn.taglist('^' .. vim9.index(items, 0) .. '$'),
  249. function(_, v)
  250. return vim9.ternary(vim9.fn.has_key(v, 'kind'), function()
  251. return v.kind ~= 'm'
  252. end, true)
  253. end
  254. )
  255. res = {}
  256. for _, i in vim9.iter(vim9.fn.range(vim9.fn.len(diclist))) do
  257. -- # New ctags has the "typeref" field. Patched version has "typename".
  258. if vim9.bool(vim9.fn.has_key(vim9.index(diclist, i), 'typename')) then
  259. res = vim9.fn.extend(
  260. res,
  261. StructMembers(
  262. vim9.index(vim9.index(diclist, i), 'typename'),
  263. vim9.slice(items, 1, nil),
  264. true
  265. )
  266. )
  267. elseif vim9.bool(vim9.fn.has_key(vim9.index(diclist, i), 'typeref')) then
  268. res = vim9.fn.extend(
  269. res,
  270. StructMembers(
  271. vim9.index(vim9.index(diclist, i), 'typeref'),
  272. vim9.slice(items, 1, nil),
  273. true
  274. )
  275. )
  276. end
  277. -- # For a variable use the command, which must be a search pattern that
  278. -- # shows the declaration of the variable.
  279. if vim9.index(vim9.index(diclist, i), 'kind') == 'v' then
  280. local line = vim9.index(vim9.index(diclist, i), 'cmd')
  281. if vim9.slice(line, nil, 1) == '/^' then
  282. local col =
  283. vim9.fn.charidx(line, vim9.fn.match(line, '\\<' .. vim9.index(items, 0) .. '\\>'))
  284. res = vim9.fn.extend(
  285. res,
  286. Nextitem(
  287. vim9.slice(line, 2, vim9.ops.Minus(col, 1)),
  288. vim9.slice(items, 1, nil),
  289. 0,
  290. true
  291. )
  292. )
  293. end
  294. end
  295. end
  296. end
  297. if vim9.fn.len(res) == 0 and vim9.fn.searchdecl(vim9.index(items, 0), true) == 0 then
  298. -- # Found, now figure out the type.
  299. -- # TODO: join previous line if it makes sense
  300. local line = vim9.fn.getline('.')
  301. local col = vim9.fn.charcol('.')
  302. res =
  303. Nextitem(vim9.slice(line, nil, vim9.ops.Minus(col, 1)), vim9.slice(items, 1, nil), 0, true)
  304. end
  305. -- # If the last item(s) are [...] they need to be added to the matches.
  306. local last = vim9.fn.len(items) - 1
  307. local brackets = ''
  308. while last >= 0 do
  309. if vim9.index(vim9.index(items, last), 0) ~= '[' then
  310. break
  311. end
  312. brackets = vim9.index(items, last) .. brackets
  313. last = last - 1
  314. end
  315. return vim9.fn.map(res, function(_, v)
  316. return Tagline2item(v, brackets)
  317. end)
  318. end
  319. M['Complete'] = Complete
  320. GetAddition = function(line, match, memarg, bracket)
  321. bracket = vim9.bool(bracket)
  322. -- # Guess if the item is an array.
  323. if vim9.bool(vim9.ops.And(bracket, vim9.fn.match(line, match .. '\\s*\\[') > 0)) then
  324. return '['
  325. end
  326. -- # Check if the item has members.
  327. if vim9.fn.len(SearchMembers(memarg, { '' }, false)) > 0 then
  328. -- # If there is a '*' before the name use "->".
  329. if vim9.fn.match(line, '\\*[ \\t(]*' .. match .. '\\>') > 0 then
  330. return '->'
  331. else
  332. return '.'
  333. end
  334. end
  335. return ''
  336. end
  337. Tag2item = function(val)
  338. -- # Turn the tag info "val" into an item for completion.
  339. -- # "val" is is an item in the list returned by taglist().
  340. -- # If it is a variable we may add "." or "->". Don't do it for other types,
  341. -- # such as a typedef, by not including the info that GetAddition() uses.
  342. local res = vim9.convert.decl_dict({ ['match'] = vim9.index(val, 'name') })
  343. res[vim9.index_expr('extra')] =
  344. Tagcmd2extra(vim9.index(val, 'cmd'), vim9.index(val, 'name'), vim9.index(val, 'filename'))
  345. local s = Dict2info(val)
  346. if s ~= '' then
  347. res[vim9.index_expr('info')] = s
  348. end
  349. res[vim9.index_expr('tagline')] = ''
  350. if vim9.bool(vim9.fn.has_key(val, 'kind')) then
  351. local kind = vim9.index(val, 'kind')
  352. res[vim9.index_expr('kind')] = kind
  353. if kind == 'v' then
  354. res[vim9.index_expr('tagline')] = '\t' .. vim9.index(val, 'cmd')
  355. res[vim9.index_expr('dict')] = val
  356. elseif vim9.bool(kind == 'f') then
  357. res[vim9.index_expr('match')] = vim9.index(val, 'name') .. '('
  358. end
  359. end
  360. return res
  361. end
  362. Dict2info = function(dict)
  363. -- # Use all the items in dictionary for the "info" entry.
  364. local info = ''
  365. for _, k in vim9.iter(vim9.fn_mut('sort', { vim9.fn.keys(dict) }, { replace = 0 })) do
  366. info = info .. k .. vim9.fn['repeat'](' ', 10 - vim9.fn.strlen(k))
  367. if k == 'cmd' then
  368. info = info
  369. .. vim9.fn.substitute(
  370. vim9.fn.matchstr(vim9.index(dict, 'cmd'), '/^\\s*\\zs.*\\ze$/'),
  371. '\\\\\\(.\\)',
  372. '\\1',
  373. 'g'
  374. )
  375. else
  376. local dictk = vim9.index(dict, k)
  377. if vim9.fn.typename(dictk) ~= 'string' then
  378. info = info .. vim9.fn.string(dictk)
  379. else
  380. info = info .. dictk
  381. end
  382. end
  383. info = info .. '\n'
  384. end
  385. return info
  386. end
  387. ParseTagline = function(line)
  388. -- # Parse a tag line and return a dictionary with items like taglist()
  389. local l = vim9.fn.split(line, '\t')
  390. local d = vim.empty_dict()
  391. if vim9.fn.len(l) >= 3 then
  392. d[vim9.index_expr('name')] = vim9.index(l, 0)
  393. d[vim9.index_expr('filename')] = vim9.index(l, 1)
  394. d[vim9.index_expr('cmd')] = vim9.index(l, 2)
  395. local n = 2
  396. if vim9.ops.RegexpMatches(vim9.index(l, 2), '^/') then
  397. -- # Find end of cmd, it may contain Tabs.
  398. while n < vim9.fn.len(l) and vim9.ops.NotRegexpMatches(vim9.index(l, n), '/;"$') do
  399. n = n + 1
  400. d[vim9.index_expr('cmd')] = vim9.index(d, 'cmd') .. ' ' .. vim9.index(l, n)
  401. end
  402. end
  403. for _, i in vim9.iter(vim9.fn.range(vim9.ops.Plus(n, 1), vim9.fn.len(l) - 1)) do
  404. if vim9.index(l, i) == 'file:' then
  405. d[vim9.index_expr('static')] = 1
  406. elseif vim9.bool(vim9.ops.NotRegexpMatches(vim9.index(l, i), ':')) then
  407. d[vim9.index_expr('kind')] = vim9.index(l, i)
  408. else
  409. d[vim9.index_expr(vim9.fn.matchstr(vim9.index(l, i), '[^:]*'))] =
  410. vim9.fn.matchstr(vim9.index(l, i), ':\\zs.*')
  411. end
  412. end
  413. end
  414. return d
  415. end
  416. Tagline2item = function(val, brackets)
  417. -- # Turn a match item "val" into an item for completion.
  418. -- # "val['match']" is the matching item.
  419. -- # "val['tagline']" is the tagline in which the last part was found.
  420. local line = vim9.index(val, 'tagline')
  421. local add = GetAddition(line, vim9.index(val, 'match'), { val }, brackets == '')
  422. local res = vim9.convert.decl_dict({ ['word'] = vim9.index(val, 'match') .. brackets .. add })
  423. if vim9.bool(vim9.fn.has_key(val, 'info')) then
  424. -- # Use info from Tag2item().
  425. res[vim9.index_expr('info')] = vim9.index(val, 'info')
  426. else
  427. -- # Parse the tag line and add each part to the "info" entry.
  428. local s = Dict2info(ParseTagline(line))
  429. if s ~= '' then
  430. res[vim9.index_expr('info')] = s
  431. end
  432. end
  433. if vim9.bool(vim9.fn.has_key(val, 'kind')) then
  434. res[vim9.index_expr('kind')] = vim9.index(val, 'kind')
  435. elseif vim9.bool(add == '(') then
  436. res[vim9.index_expr('kind')] = 'f'
  437. else
  438. local s = vim9.fn.matchstr(line, '\\t\\(kind:\\)\\=\\zs\\S\\ze\\(\\t\\|$\\)')
  439. if s ~= '' then
  440. res[vim9.index_expr('kind')] = s
  441. end
  442. end
  443. if vim9.bool(vim9.fn.has_key(val, 'extra')) then
  444. res[vim9.index_expr('menu')] = vim9.index(val, 'extra')
  445. return res
  446. end
  447. -- # Isolate the command after the tag and filename.
  448. local s = vim9.fn.matchstr(
  449. line,
  450. '[^\\t]*\\t[^\\t]*\\t\\zs\\(/^.*$/\\|[^\\t]*\\)\\ze\\(;"\\t\\|\\t\\|$\\)'
  451. )
  452. if s ~= '' then
  453. res[vim9.index_expr('menu')] = Tagcmd2extra(
  454. s,
  455. vim9.index(val, 'match'),
  456. vim9.fn.matchstr(line, '[^\\t]*\\t\\zs[^\\t]*\\ze\\t')
  457. )
  458. end
  459. return res
  460. end
  461. Tagcmd2extra = function(cmd, name, fname)
  462. -- # Turn a command from a tag line to something that is useful in the menu
  463. local x = ''
  464. if vim9.ops.RegexpMatches(cmd, '^/^') then
  465. -- # The command is a search command, useful to see what it is.
  466. x = vim9.fn.substitute(
  467. vim9.fn.substitute(
  468. vim9.fn.matchstr(cmd, '^/^\\s*\\zs.*\\ze$/'),
  469. '\\<' .. name .. '\\>',
  470. '@@',
  471. ''
  472. ),
  473. '\\\\\\(.\\)',
  474. '\\1',
  475. 'g'
  476. ) .. ' - ' .. fname
  477. elseif vim9.bool(vim9.ops.RegexpMatches(cmd, '^\\d*$')) then
  478. -- # The command is a line number, the file name is more useful.
  479. x = fname .. ' - ' .. cmd
  480. else
  481. -- # Not recognized, use command and file name.
  482. x = cmd .. ' - ' .. fname
  483. end
  484. return x
  485. end
  486. Nextitem = function(lead, items, depth, all)
  487. all = vim9.bool(all)
  488. -- # Find composing type in "lead" and match items[0] with it.
  489. -- # Repeat this recursively for items[1], if it's there.
  490. -- # When resolving typedefs "depth" is used to avoid infinite recursion.
  491. -- # Return the list of matches.
  492. -- # Use the text up to the variable name and split it in tokens.
  493. local tokens = vim9.fn.split(lead, '\\s\\+\\|\\<')
  494. -- # Try to recognize the type of the variable. This is rough guessing...
  495. local res = {}
  496. local body = function(_, tidx)
  497. -- # Skip tokens starting with a non-ID character.
  498. if vim9.ops.NotRegexpMatches(vim9.index(tokens, tidx), '^\\h') then
  499. return vim9.ITER_CONTINUE
  500. end
  501. -- # Recognize "struct foobar" and "union foobar".
  502. -- # Also do "class foobar" when it's C++ after all (doesn't work very well
  503. -- # though).
  504. if
  505. (
  506. vim9.index(tokens, tidx) == 'struct'
  507. or vim9.index(tokens, tidx) == 'union'
  508. or vim9.index(tokens, tidx) == 'class'
  509. ) and vim9.ops.Plus(tidx, 1) < vim9.fn.len(tokens)
  510. then
  511. res = StructMembers(
  512. vim9.index(tokens, tidx) .. ':' .. vim9.index(tokens, vim9.ops.Plus(tidx, 1)),
  513. items,
  514. all
  515. )
  516. return vim9.ITER_BREAK
  517. end
  518. -- # TODO: add more reserved words
  519. if
  520. vim9.fn.index(
  521. { 'int', 'short', 'char', 'float', 'double', 'static', 'unsigned', 'extern' },
  522. vim9.index(tokens, tidx)
  523. ) >= 0
  524. then
  525. return vim9.ITER_CONTINUE
  526. end
  527. -- # Use the tags file to find out if this is a typedef.
  528. local diclist = vim9.fn.taglist('^' .. vim9.index(tokens, tidx) .. '$')
  529. local body = function(_, tagidx)
  530. local item = vim9.convert.decl_dict(vim9.index(diclist, tagidx))
  531. -- # New ctags has the "typeref" field. Patched version has "typename".
  532. if vim9.bool(vim9.fn.has_key(item, 'typeref')) then
  533. res = vim9.fn.extend(res, StructMembers(vim9.index(item, 'typeref'), items, all))
  534. return vim9.ITER_CONTINUE
  535. end
  536. if vim9.bool(vim9.fn.has_key(item, 'typename')) then
  537. res = vim9.fn.extend(res, StructMembers(vim9.index(item, 'typename'), items, all))
  538. return vim9.ITER_CONTINUE
  539. end
  540. -- # Only handle typedefs here.
  541. if vim9.index(item, 'kind') ~= 't' then
  542. return vim9.ITER_CONTINUE
  543. end
  544. -- # Skip matches local to another file.
  545. if
  546. vim9.bool(
  547. vim9.ops.And(
  548. vim9.ops.And(vim9.fn.has_key(item, 'static'), vim9.index(item, 'static')),
  549. vim9.fn.bufnr('%') ~= vim9.fn.bufnr(vim9.index(item, 'filename'))
  550. )
  551. )
  552. then
  553. return vim9.ITER_CONTINUE
  554. end
  555. -- # For old ctags we recognize "typedef struct aaa" and
  556. -- # "typedef union bbb" in the tags file command.
  557. local cmd = vim9.index(item, 'cmd')
  558. local ei = vim9.fn.charidx(cmd, vim9.fn.matchend(cmd, 'typedef\\s\\+'))
  559. if ei > 1 then
  560. local cmdtokens = vim9.fn.split(vim9.slice(cmd, ei, nil), '\\s\\+\\|\\<')
  561. if vim9.fn.len(cmdtokens) > 1 then
  562. if
  563. vim9.index(cmdtokens, 0) == 'struct'
  564. or vim9.index(cmdtokens, 0) == 'union'
  565. or vim9.index(cmdtokens, 0) == 'class'
  566. then
  567. local name = ''
  568. -- # Use the first identifier after the "struct" or "union"
  569. for _, ti in vim9.iter(vim9.fn.range((vim9.fn.len(cmdtokens) - 1))) do
  570. if vim9.ops.RegexpMatches(vim9.index(cmdtokens, ti), '^\\w') then
  571. name = vim9.index(cmdtokens, ti)
  572. break
  573. end
  574. end
  575. if name ~= '' then
  576. res = vim9.fn.extend(
  577. res,
  578. StructMembers(vim9.index(cmdtokens, 0) .. ':' .. name, items, all)
  579. )
  580. end
  581. elseif vim9.bool(depth < 10) then
  582. -- # Could be "typedef other_T some_T".
  583. res = vim9.fn.extend(
  584. res,
  585. Nextitem(vim9.index(cmdtokens, 0), items, vim9.ops.Plus(depth, 1), all)
  586. )
  587. end
  588. end
  589. end
  590. return vim9.ITER_DEFAULT
  591. end
  592. for _, tagidx in vim9.iter(vim9.fn.range(vim9.fn.len(diclist))) do
  593. local nvim9_status, nvim9_ret = body(_, tagidx)
  594. if nvim9_status == vim9.ITER_BREAK then
  595. break
  596. elseif nvim9_status == vim9.ITER_RETURN then
  597. return nvim9_ret
  598. end
  599. end
  600. if vim9.fn.len(res) > 0 then
  601. return vim9.ITER_BREAK
  602. end
  603. return vim9.ITER_DEFAULT
  604. end
  605. for _, tidx in vim9.iter(vim9.fn.range(vim9.fn.len(tokens))) do
  606. local nvim9_status, nvim9_ret = body(_, tidx)
  607. if nvim9_status == vim9.ITER_BREAK then
  608. break
  609. elseif nvim9_status == vim9.ITER_RETURN then
  610. return nvim9_ret
  611. end
  612. end
  613. return res
  614. end
  615. StructMembers = function(atypename, items, all)
  616. all = vim9.bool(all)
  617. -- # Search for members of structure "typename" in tags files.
  618. -- # Return a list with resulting matches.
  619. -- # Each match is a dictionary with "match" and "tagline" entries.
  620. -- # When "all" is true find all, otherwise just return 1 if there is any member.
  621. -- # Todo: What about local structures?
  622. local fnames = vim9.fn.join(vim9.fn.map(vim9.fn.tagfiles(), function(_, v)
  623. return vim9.fn.escape(v, ' \\#%')
  624. end))
  625. if fnames == '' then
  626. return {}
  627. end
  628. local typename = atypename
  629. local qflist = {}
  630. local cached = 0
  631. local n = ''
  632. if vim9.bool(vim9.prefix['Bang'](all)) then
  633. n = '1'
  634. if vim9.bool(vim9.fn.has_key(grepCache, typename)) then
  635. qflist = vim9.index(grepCache, typename)
  636. cached = 1
  637. end
  638. else
  639. n = ''
  640. end
  641. if vim9.bool(vim9.prefix['Bang'](cached)) then
  642. while 1 do
  643. vim.api.nvim_command(
  644. 'silent! keepjumps noautocmd '
  645. .. n
  646. .. 'vimgrep '
  647. .. '/\\t'
  648. .. typename
  649. .. '\\(\\t\\|$\\)/j '
  650. .. fnames
  651. )
  652. qflist = vim9.fn.getqflist()
  653. if vim9.fn.len(qflist) > 0 or vim9.fn.match(typename, '::') < 0 then
  654. break
  655. end
  656. -- # No match for "struct:context::name", remove "context::" and try again.
  657. typename = vim9.fn.substitute(typename, ':[^:]*::', ':', '')
  658. end
  659. if vim9.bool(vim9.prefix['Bang'](all)) then
  660. -- # Store the result to be able to use it again later.
  661. grepCache[vim9.index_expr(typename)] = qflist
  662. end
  663. end
  664. -- # Skip over [...] items
  665. local idx = 0
  666. local target = ''
  667. while 1 do
  668. if idx >= vim9.fn.len(items) then
  669. target = ''
  670. break
  671. end
  672. if vim9.index(vim9.index(items, idx), 0) ~= '[' then
  673. target = vim9.index(items, idx)
  674. break
  675. end
  676. idx = idx + 1
  677. end
  678. -- # Put matching members in matches[].
  679. local matches = {}
  680. for _, l in vim9.iter(qflist) do
  681. local memb = vim9.fn.matchstr(vim9.index(l, 'text'), '[^\\t]*')
  682. if vim9.ops.RegexpMatches(memb, '^' .. target) then
  683. -- # Skip matches local to another file.
  684. if
  685. vim9.fn.match(vim9.index(l, 'text'), '\tfile:') < 0
  686. or vim9.fn.bufnr('%')
  687. == vim9.fn.bufnr(vim9.fn.matchstr(vim9.index(l, 'text'), '\\t\\zs[^\\t]*'))
  688. then
  689. local item =
  690. vim9.convert.decl_dict({ ['match'] = memb, ['tagline'] = vim9.index(l, 'text') })
  691. -- # Add the kind of item.
  692. local s =
  693. vim9.fn.matchstr(vim9.index(l, 'text'), '\\t\\(kind:\\)\\=\\zs\\S\\ze\\(\\t\\|$\\)')
  694. if s ~= '' then
  695. item[vim9.index_expr('kind')] = s
  696. if s == 'f' then
  697. item[vim9.index_expr('match')] = memb .. '('
  698. end
  699. end
  700. vim9.fn.add(matches, item)
  701. end
  702. end
  703. end
  704. if vim9.fn.len(matches) > 0 then
  705. -- # Skip over next [...] items
  706. idx = idx + 1
  707. while 1 do
  708. if idx >= vim9.fn.len(items) then
  709. return matches
  710. end
  711. if vim9.index(vim9.index(items, idx), 0) ~= '[' then
  712. break
  713. end
  714. idx = idx + 1
  715. end
  716. -- # More items following. For each of the possible members find the
  717. -- # matching following members.
  718. return SearchMembers(matches, vim9.slice(items, idx, nil), all)
  719. end
  720. -- # Failed to find anything.
  721. return {}
  722. end
  723. SearchMembers = function(matches, items, all)
  724. all = vim9.bool(all)
  725. -- # For matching members, find matches for following items.
  726. -- # When "all" is true find all, otherwise just return 1 if there is any member.
  727. local res = {}
  728. for _, i in vim9.iter(vim9.fn.range(vim9.fn.len(matches))) do
  729. local typename = ''
  730. local line = ''
  731. if vim9.bool(vim9.fn.has_key(vim9.index(matches, i), 'dict')) then
  732. if vim9.bool(vim9.fn.has_key(vim9.index(vim9.index(matches, i), 'dict'), 'typename')) then
  733. typename = vim9.index(vim9.index(vim9.index(matches, i), 'dict'), 'typename')
  734. elseif vim9.bool(vim9.fn.has_key(vim9.index(vim9.index(matches, i), 'dict'), 'typeref')) then
  735. typename = vim9.index(vim9.index(vim9.index(matches, i), 'dict'), 'typeref')
  736. end
  737. line = '\t' .. vim9.index(vim9.index(vim9.index(matches, i), 'dict'), 'cmd')
  738. else
  739. line = vim9.index(vim9.index(matches, i), 'tagline')
  740. local eb = vim9.fn.matchend(line, '\\ttypename:')
  741. local e = vim9.fn.charidx(line, eb)
  742. if e < 0 then
  743. eb = vim9.fn.matchend(line, '\\ttyperef:')
  744. e = vim9.fn.charidx(line, eb)
  745. end
  746. if e > 0 then
  747. -- # Use typename field
  748. typename = vim9.fn.matchstr(line, '[^\\t]*', eb)
  749. end
  750. end
  751. if typename ~= '' then
  752. res = vim9.fn.extend(res, StructMembers(typename, items, all))
  753. else
  754. -- # Use the search command (the declaration itself).
  755. local sb = vim9.fn.match(line, '\\t\\zs/^')
  756. local s = vim9.fn.charidx(line, sb)
  757. if s > 0 then
  758. local e = vim9.fn.charidx(
  759. line,
  760. vim9.fn.match(line, '\\<' .. vim9.index(vim9.index(matches, i), 'match') .. '\\>', sb)
  761. )
  762. if e > 0 then
  763. res =
  764. vim9.fn.extend(res, Nextitem(vim9.slice(line, s, vim9.ops.Minus(e, 1)), items, 0, all))
  765. end
  766. end
  767. end
  768. if vim9.bool(vim9.ops.And(vim9.prefix['Bang'](all), vim9.fn.len(res) > 0)) then
  769. break
  770. end
  771. end
  772. return res
  773. end
  774. -- #}}}1
  775. -- # vim: noet sw=2 sts=2
  776. return M