ccomplete.vim 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. " Vim completion script
  2. " Language: C
  3. " Maintainer: Bram Moolenaar <Bram@vim.org>
  4. " Last Change: 2020 Nov 14
  5. let s:cpo_save = &cpo
  6. set cpo&vim
  7. " This function is used for the 'omnifunc' option.
  8. func ccomplete#Complete(findstart, base)
  9. if a:findstart
  10. " Locate the start of the item, including ".", "->" and "[...]".
  11. let line = getline('.')
  12. let start = col('.') - 1
  13. let lastword = -1
  14. while start > 0
  15. if line[start - 1] =~ '\w'
  16. let start -= 1
  17. elseif line[start - 1] =~ '\.'
  18. if lastword == -1
  19. let lastword = start
  20. endif
  21. let start -= 1
  22. elseif start > 1 && line[start - 2] == '-' && line[start - 1] == '>'
  23. if lastword == -1
  24. let lastword = start
  25. endif
  26. let start -= 2
  27. elseif line[start - 1] == ']'
  28. " Skip over [...].
  29. let n = 0
  30. let start -= 1
  31. while start > 0
  32. let start -= 1
  33. if line[start] == '['
  34. if n == 0
  35. break
  36. endif
  37. let n -= 1
  38. elseif line[start] == ']' " nested []
  39. let n += 1
  40. endif
  41. endwhile
  42. else
  43. break
  44. endif
  45. endwhile
  46. " Return the column of the last word, which is going to be changed.
  47. " Remember the text that comes before it in s:prepended.
  48. if lastword == -1
  49. let s:prepended = ''
  50. return start
  51. endif
  52. let s:prepended = strpart(line, start, lastword - start)
  53. return lastword
  54. endif
  55. " Return list of matches.
  56. let base = s:prepended . a:base
  57. " Don't do anything for an empty base, would result in all the tags in the
  58. " tags file.
  59. if base == ''
  60. return []
  61. endif
  62. " init cache for vimgrep to empty
  63. let s:grepCache = {}
  64. " Split item in words, keep empty word after "." or "->".
  65. " "aa" -> ['aa'], "aa." -> ['aa', ''], "aa.bb" -> ['aa', 'bb'], etc.
  66. " We can't use split, because we need to skip nested [...].
  67. " "aa[...]" -> ['aa', '[...]'], "aa.bb[...]" -> ['aa', 'bb', '[...]'], etc.
  68. let items = []
  69. let s = 0
  70. let arrays = 0
  71. while 1
  72. let e = match(base, '\.\|->\|\[', s)
  73. if e < 0
  74. if s == 0 || base[s - 1] != ']'
  75. call add(items, strpart(base, s))
  76. endif
  77. break
  78. endif
  79. if s == 0 || base[s - 1] != ']'
  80. call add(items, strpart(base, s, e - s))
  81. endif
  82. if base[e] == '.'
  83. let s = e + 1 " skip over '.'
  84. elseif base[e] == '-'
  85. let s = e + 2 " skip over '->'
  86. else
  87. " Skip over [...].
  88. let n = 0
  89. let s = e
  90. let e += 1
  91. while e < len(base)
  92. if base[e] == ']'
  93. if n == 0
  94. break
  95. endif
  96. let n -= 1
  97. elseif base[e] == '[' " nested [...]
  98. let n += 1
  99. endif
  100. let e += 1
  101. endwhile
  102. let e += 1
  103. call add(items, strpart(base, s, e - s))
  104. let arrays += 1
  105. let s = e
  106. endif
  107. endwhile
  108. " Find the variable items[0].
  109. " 1. in current function (like with "gd")
  110. " 2. in tags file(s) (like with ":tag")
  111. " 3. in current file (like with "gD")
  112. let res = []
  113. if searchdecl(items[0], 0, 1) == 0
  114. " Found, now figure out the type.
  115. " TODO: join previous line if it makes sense
  116. let line = getline('.')
  117. let col = col('.')
  118. if stridx(strpart(line, 0, col), ';') != -1
  119. " Handle multiple declarations on the same line.
  120. let col2 = col - 1
  121. while line[col2] != ';'
  122. let col2 -= 1
  123. endwhile
  124. let line = strpart(line, col2 + 1)
  125. let col -= col2
  126. endif
  127. if stridx(strpart(line, 0, col), ',') != -1
  128. " Handle multiple declarations on the same line in a function
  129. " declaration.
  130. let col2 = col - 1
  131. while line[col2] != ','
  132. let col2 -= 1
  133. endwhile
  134. if strpart(line, col2 + 1, col - col2 - 1) =~ ' *[^ ][^ ]* *[^ ]'
  135. let line = strpart(line, col2 + 1)
  136. let col -= col2
  137. endif
  138. endif
  139. if len(items) == 1
  140. " Completing one word and it's a local variable: May add '[', '.' or
  141. " '->'.
  142. let match = items[0]
  143. let kind = 'v'
  144. if match(line, '\<' . match . '\s*\[') > 0
  145. let match .= '['
  146. else
  147. let res = s:Nextitem(strpart(line, 0, col), [''], 0, 1)
  148. if len(res) > 0
  149. " There are members, thus add "." or "->".
  150. if match(line, '\*[ \t(]*' . match . '\>') > 0
  151. let match .= '->'
  152. else
  153. let match .= '.'
  154. endif
  155. endif
  156. endif
  157. let res = [{'match': match, 'tagline' : '', 'kind' : kind, 'info' : line}]
  158. elseif len(items) == arrays + 1
  159. " Completing one word and it's a local array variable: build tagline
  160. " from declaration line
  161. let match = items[0]
  162. let kind = 'v'
  163. let tagline = "\t/^" . line . '$/'
  164. let res = [{'match': match, 'tagline' : tagline, 'kind' : kind, 'info' : line}]
  165. else
  166. " Completing "var.", "var.something", etc.
  167. let res = s:Nextitem(strpart(line, 0, col), items[1:], 0, 1)
  168. endif
  169. endif
  170. if len(items) == 1 || len(items) == arrays + 1
  171. " Only one part, no "." or "->": complete from tags file.
  172. if len(items) == 1
  173. let tags = taglist('^' . base)
  174. else
  175. let tags = taglist('^' . items[0] . '$')
  176. endif
  177. " Remove members, these can't appear without something in front.
  178. call filter(tags, 'has_key(v:val, "kind") ? v:val["kind"] != "m" : 1')
  179. " Remove static matches in other files.
  180. call filter(tags, '!has_key(v:val, "static") || !v:val["static"] || bufnr("%") == bufnr(v:val["filename"])')
  181. call extend(res, map(tags, 's:Tag2item(v:val)'))
  182. endif
  183. if len(res) == 0
  184. " Find the variable in the tags file(s)
  185. let diclist = taglist('^' . items[0] . '$')
  186. " Remove members, these can't appear without something in front.
  187. call filter(diclist, 'has_key(v:val, "kind") ? v:val["kind"] != "m" : 1')
  188. let res = []
  189. for i in range(len(diclist))
  190. " New ctags has the "typeref" field. Patched version has "typename".
  191. if has_key(diclist[i], 'typename')
  192. call extend(res, s:StructMembers(diclist[i]['typename'], items[1:], 1))
  193. elseif has_key(diclist[i], 'typeref')
  194. call extend(res, s:StructMembers(diclist[i]['typeref'], items[1:], 1))
  195. endif
  196. " For a variable use the command, which must be a search pattern that
  197. " shows the declaration of the variable.
  198. if diclist[i]['kind'] == 'v'
  199. let line = diclist[i]['cmd']
  200. if line[0] == '/' && line[1] == '^'
  201. let col = match(line, '\<' . items[0] . '\>')
  202. call extend(res, s:Nextitem(strpart(line, 2, col - 2), items[1:], 0, 1))
  203. endif
  204. endif
  205. endfor
  206. endif
  207. if len(res) == 0 && searchdecl(items[0], 1) == 0
  208. " Found, now figure out the type.
  209. " TODO: join previous line if it makes sense
  210. let line = getline('.')
  211. let col = col('.')
  212. let res = s:Nextitem(strpart(line, 0, col), items[1:], 0, 1)
  213. endif
  214. " If the last item(s) are [...] they need to be added to the matches.
  215. let last = len(items) - 1
  216. let brackets = ''
  217. while last >= 0
  218. if items[last][0] != '['
  219. break
  220. endif
  221. let brackets = items[last] . brackets
  222. let last -= 1
  223. endwhile
  224. return map(res, 's:Tagline2item(v:val, brackets)')
  225. endfunc
  226. func s:GetAddition(line, match, memarg, bracket)
  227. " Guess if the item is an array.
  228. if a:bracket && match(a:line, a:match . '\s*\[') > 0
  229. return '['
  230. endif
  231. " Check if the item has members.
  232. if len(s:SearchMembers(a:memarg, [''], 0)) > 0
  233. " If there is a '*' before the name use "->".
  234. if match(a:line, '\*[ \t(]*' . a:match . '\>') > 0
  235. return '->'
  236. else
  237. return '.'
  238. endif
  239. endif
  240. return ''
  241. endfunc
  242. " Turn the tag info "val" into an item for completion.
  243. " "val" is is an item in the list returned by taglist().
  244. " If it is a variable we may add "." or "->". Don't do it for other types,
  245. " such as a typedef, by not including the info that s:GetAddition() uses.
  246. func s:Tag2item(val)
  247. let res = {'match': a:val['name']}
  248. let res['extra'] = s:Tagcmd2extra(a:val['cmd'], a:val['name'], a:val['filename'])
  249. let s = s:Dict2info(a:val)
  250. if s != ''
  251. let res['info'] = s
  252. endif
  253. let res['tagline'] = ''
  254. if has_key(a:val, "kind")
  255. let kind = a:val['kind']
  256. let res['kind'] = kind
  257. if kind == 'v'
  258. let res['tagline'] = "\t" . a:val['cmd']
  259. let res['dict'] = a:val
  260. elseif kind == 'f'
  261. let res['match'] = a:val['name'] . '('
  262. endif
  263. endif
  264. return res
  265. endfunc
  266. " Use all the items in dictionary for the "info" entry.
  267. func s:Dict2info(dict)
  268. let info = ''
  269. for k in sort(keys(a:dict))
  270. let info .= k . repeat(' ', 10 - len(k))
  271. if k == 'cmd'
  272. let info .= substitute(matchstr(a:dict['cmd'], '/^\s*\zs.*\ze$/'), '\\\(.\)', '\1', 'g')
  273. else
  274. let info .= a:dict[k]
  275. endif
  276. let info .= "\n"
  277. endfor
  278. return info
  279. endfunc
  280. " Parse a tag line and return a dictionary with items like taglist()
  281. func s:ParseTagline(line)
  282. let l = split(a:line, "\t")
  283. let d = {}
  284. if len(l) >= 3
  285. let d['name'] = l[0]
  286. let d['filename'] = l[1]
  287. let d['cmd'] = l[2]
  288. let n = 2
  289. if l[2] =~ '^/'
  290. " Find end of cmd, it may contain Tabs.
  291. while n < len(l) && l[n] !~ '/;"$'
  292. let n += 1
  293. let d['cmd'] .= " " . l[n]
  294. endwhile
  295. endif
  296. for i in range(n + 1, len(l) - 1)
  297. if l[i] == 'file:'
  298. let d['static'] = 1
  299. elseif l[i] !~ ':'
  300. let d['kind'] = l[i]
  301. else
  302. let d[matchstr(l[i], '[^:]*')] = matchstr(l[i], ':\zs.*')
  303. endif
  304. endfor
  305. endif
  306. return d
  307. endfunc
  308. " Turn a match item "val" into an item for completion.
  309. " "val['match']" is the matching item.
  310. " "val['tagline']" is the tagline in which the last part was found.
  311. func s:Tagline2item(val, brackets)
  312. let line = a:val['tagline']
  313. let add = s:GetAddition(line, a:val['match'], [a:val], a:brackets == '')
  314. let res = {'word': a:val['match'] . a:brackets . add }
  315. if has_key(a:val, 'info')
  316. " Use info from Tag2item().
  317. let res['info'] = a:val['info']
  318. else
  319. " Parse the tag line and add each part to the "info" entry.
  320. let s = s:Dict2info(s:ParseTagline(line))
  321. if s != ''
  322. let res['info'] = s
  323. endif
  324. endif
  325. if has_key(a:val, 'kind')
  326. let res['kind'] = a:val['kind']
  327. elseif add == '('
  328. let res['kind'] = 'f'
  329. else
  330. let s = matchstr(line, '\t\(kind:\)\=\zs\S\ze\(\t\|$\)')
  331. if s != ''
  332. let res['kind'] = s
  333. endif
  334. endif
  335. if has_key(a:val, 'extra')
  336. let res['menu'] = a:val['extra']
  337. return res
  338. endif
  339. " Isolate the command after the tag and filename.
  340. let s = matchstr(line, '[^\t]*\t[^\t]*\t\zs\(/^.*$/\|[^\t]*\)\ze\(;"\t\|\t\|$\)')
  341. if s != ''
  342. let res['menu'] = s:Tagcmd2extra(s, a:val['match'], matchstr(line, '[^\t]*\t\zs[^\t]*\ze\t'))
  343. endif
  344. return res
  345. endfunc
  346. " Turn a command from a tag line to something that is useful in the menu
  347. func s:Tagcmd2extra(cmd, name, fname)
  348. if a:cmd =~ '^/^'
  349. " The command is a search command, useful to see what it is.
  350. let x = matchstr(a:cmd, '^/^\s*\zs.*\ze$/')
  351. let x = substitute(x, '\<' . a:name . '\>', '@@', '')
  352. let x = substitute(x, '\\\(.\)', '\1', 'g')
  353. let x = x . ' - ' . a:fname
  354. elseif a:cmd =~ '^\d*$'
  355. " The command is a line number, the file name is more useful.
  356. let x = a:fname . ' - ' . a:cmd
  357. else
  358. " Not recognized, use command and file name.
  359. let x = a:cmd . ' - ' . a:fname
  360. endif
  361. return x
  362. endfunc
  363. " Find composing type in "lead" and match items[0] with it.
  364. " Repeat this recursively for items[1], if it's there.
  365. " When resolving typedefs "depth" is used to avoid infinite recursion.
  366. " Return the list of matches.
  367. func s:Nextitem(lead, items, depth, all)
  368. " Use the text up to the variable name and split it in tokens.
  369. let tokens = split(a:lead, '\s\+\|\<')
  370. " Try to recognize the type of the variable. This is rough guessing...
  371. let res = []
  372. for tidx in range(len(tokens))
  373. " Skip tokens starting with a non-ID character.
  374. if tokens[tidx] !~ '^\h'
  375. continue
  376. endif
  377. " Recognize "struct foobar" and "union foobar".
  378. " Also do "class foobar" when it's C++ after all (doesn't work very well
  379. " though).
  380. if (tokens[tidx] == 'struct' || tokens[tidx] == 'union' || tokens[tidx] == 'class') && tidx + 1 < len(tokens)
  381. let res = s:StructMembers(tokens[tidx] . ':' . tokens[tidx + 1], a:items, a:all)
  382. break
  383. endif
  384. " TODO: add more reserved words
  385. if index(['int', 'short', 'char', 'float', 'double', 'static', 'unsigned', 'extern'], tokens[tidx]) >= 0
  386. continue
  387. endif
  388. " Use the tags file to find out if this is a typedef.
  389. let diclist = taglist('^' . tokens[tidx] . '$')
  390. for tagidx in range(len(diclist))
  391. let item = diclist[tagidx]
  392. " New ctags has the "typeref" field. Patched version has "typename".
  393. if has_key(item, 'typeref')
  394. call extend(res, s:StructMembers(item['typeref'], a:items, a:all))
  395. continue
  396. endif
  397. if has_key(item, 'typename')
  398. call extend(res, s:StructMembers(item['typename'], a:items, a:all))
  399. continue
  400. endif
  401. " Only handle typedefs here.
  402. if item['kind'] != 't'
  403. continue
  404. endif
  405. " Skip matches local to another file.
  406. if has_key(item, 'static') && item['static'] && bufnr('%') != bufnr(item['filename'])
  407. continue
  408. endif
  409. " For old ctags we recognize "typedef struct aaa" and
  410. " "typedef union bbb" in the tags file command.
  411. let cmd = item['cmd']
  412. let ei = matchend(cmd, 'typedef\s\+')
  413. if ei > 1
  414. let cmdtokens = split(strpart(cmd, ei), '\s\+\|\<')
  415. if len(cmdtokens) > 1
  416. if cmdtokens[0] == 'struct' || cmdtokens[0] == 'union' || cmdtokens[0] == 'class'
  417. let name = ''
  418. " Use the first identifier after the "struct" or "union"
  419. for ti in range(len(cmdtokens) - 1)
  420. if cmdtokens[ti] =~ '^\w'
  421. let name = cmdtokens[ti]
  422. break
  423. endif
  424. endfor
  425. if name != ''
  426. call extend(res, s:StructMembers(cmdtokens[0] . ':' . name, a:items, a:all))
  427. endif
  428. elseif a:depth < 10
  429. " Could be "typedef other_T some_T".
  430. call extend(res, s:Nextitem(cmdtokens[0], a:items, a:depth + 1, a:all))
  431. endif
  432. endif
  433. endif
  434. endfor
  435. if len(res) > 0
  436. break
  437. endif
  438. endfor
  439. return res
  440. endfunc
  441. " Search for members of structure "typename" in tags files.
  442. " Return a list with resulting matches.
  443. " Each match is a dictionary with "match" and "tagline" entries.
  444. " When "all" is non-zero find all, otherwise just return 1 if there is any
  445. " member.
  446. func s:StructMembers(typename, items, all)
  447. " Todo: What about local structures?
  448. let fnames = join(map(tagfiles(), 'escape(v:val, " \\#%")'))
  449. if fnames == ''
  450. return []
  451. endif
  452. let typename = a:typename
  453. let qflist = []
  454. let cached = 0
  455. if a:all == 0
  456. let n = '1' " stop at first found match
  457. if has_key(s:grepCache, a:typename)
  458. let qflist = s:grepCache[a:typename]
  459. let cached = 1
  460. endif
  461. else
  462. let n = ''
  463. endif
  464. if !cached
  465. while 1
  466. exe 'silent! keepj noautocmd ' . n . 'vimgrep /\t' . typename . '\(\t\|$\)/j ' . fnames
  467. let qflist = getqflist()
  468. if len(qflist) > 0 || match(typename, "::") < 0
  469. break
  470. endif
  471. " No match for "struct:context::name", remove "context::" and try again.
  472. let typename = substitute(typename, ':[^:]*::', ':', '')
  473. endwhile
  474. if a:all == 0
  475. " Store the result to be able to use it again later.
  476. let s:grepCache[a:typename] = qflist
  477. endif
  478. endif
  479. " Skip over [...] items
  480. let idx = 0
  481. while 1
  482. if idx >= len(a:items)
  483. let target = '' " No further items, matching all members
  484. break
  485. endif
  486. if a:items[idx][0] != '['
  487. let target = a:items[idx]
  488. break
  489. endif
  490. let idx += 1
  491. endwhile
  492. " Put matching members in matches[].
  493. let matches = []
  494. for l in qflist
  495. let memb = matchstr(l['text'], '[^\t]*')
  496. if memb =~ '^' . target
  497. " Skip matches local to another file.
  498. if match(l['text'], "\tfile:") < 0 || bufnr('%') == bufnr(matchstr(l['text'], '\t\zs[^\t]*'))
  499. let item = {'match': memb, 'tagline': l['text']}
  500. " Add the kind of item.
  501. let s = matchstr(l['text'], '\t\(kind:\)\=\zs\S\ze\(\t\|$\)')
  502. if s != ''
  503. let item['kind'] = s
  504. if s == 'f'
  505. let item['match'] = memb . '('
  506. endif
  507. endif
  508. call add(matches, item)
  509. endif
  510. endif
  511. endfor
  512. if len(matches) > 0
  513. " Skip over next [...] items
  514. let idx += 1
  515. while 1
  516. if idx >= len(a:items)
  517. return matches " No further items, return the result.
  518. endif
  519. if a:items[idx][0] != '['
  520. break
  521. endif
  522. let idx += 1
  523. endwhile
  524. " More items following. For each of the possible members find the
  525. " matching following members.
  526. return s:SearchMembers(matches, a:items[idx :], a:all)
  527. endif
  528. " Failed to find anything.
  529. return []
  530. endfunc
  531. " For matching members, find matches for following items.
  532. " When "all" is non-zero find all, otherwise just return 1 if there is any
  533. " member.
  534. func s:SearchMembers(matches, items, all)
  535. let res = []
  536. for i in range(len(a:matches))
  537. let typename = ''
  538. if has_key(a:matches[i], 'dict')
  539. if has_key(a:matches[i].dict, 'typename')
  540. let typename = a:matches[i].dict['typename']
  541. elseif has_key(a:matches[i].dict, 'typeref')
  542. let typename = a:matches[i].dict['typeref']
  543. endif
  544. let line = "\t" . a:matches[i].dict['cmd']
  545. else
  546. let line = a:matches[i]['tagline']
  547. let e = matchend(line, '\ttypename:')
  548. if e < 0
  549. let e = matchend(line, '\ttyperef:')
  550. endif
  551. if e > 0
  552. " Use typename field
  553. let typename = matchstr(line, '[^\t]*', e)
  554. endif
  555. endif
  556. if typename != ''
  557. call extend(res, s:StructMembers(typename, a:items, a:all))
  558. else
  559. " Use the search command (the declaration itself).
  560. let s = match(line, '\t\zs/^')
  561. if s > 0
  562. let e = match(line, '\<' . a:matches[i]['match'] . '\>', s)
  563. if e > 0
  564. call extend(res, s:Nextitem(strpart(line, s, e - s), a:items, 0, a:all))
  565. endif
  566. endif
  567. endif
  568. if a:all == 0 && len(res) > 0
  569. break
  570. endif
  571. endfor
  572. return res
  573. endfunc
  574. let &cpo = s:cpo_save
  575. unlet s:cpo_save
  576. " vim: noet sw=2 sts=2