ccomplete.lua 26 KB

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