1e344beadSBram Moolenaar" Vim completion script
2e344beadSBram Moolenaar" Language:	C
3e344beadSBram Moolenaar" Maintainer:	Bram Moolenaar <[email protected]>
4*4466ad6bSBram Moolenaar" Last Change:	2020 Nov 14
5e344beadSBram Moolenaar
6b6b046b2SBram Moolenaarlet s:cpo_save = &cpo
7b6b046b2SBram Moolenaarset cpo&vim
8a4a08388SBram Moolenaar
9f75a963eSBram Moolenaar" This function is used for the 'omnifunc' option.
10*4466ad6bSBram Moolenaarfunc ccomplete#Complete(findstart, base)
11e344beadSBram Moolenaar  if a:findstart
120e5bd96fSBram Moolenaar    " Locate the start of the item, including ".", "->" and "[...]".
13e344beadSBram Moolenaar    let line = getline('.')
14e344beadSBram Moolenaar    let start = col('.') - 1
15d5cdbeb8SBram Moolenaar    let lastword = -1
16e344beadSBram Moolenaar    while start > 0
17d5cdbeb8SBram Moolenaar      if line[start - 1] =~ '\w'
18d5cdbeb8SBram Moolenaar	let start -= 1
19d5cdbeb8SBram Moolenaar      elseif line[start - 1] =~ '\.'
20d5cdbeb8SBram Moolenaar	if lastword == -1
21d5cdbeb8SBram Moolenaar	  let lastword = start
22d5cdbeb8SBram Moolenaar	endif
23e344beadSBram Moolenaar	let start -= 1
24e344beadSBram Moolenaar      elseif start > 1 && line[start - 2] == '-' && line[start - 1] == '>'
25d5cdbeb8SBram Moolenaar	if lastword == -1
26d5cdbeb8SBram Moolenaar	  let lastword = start
27d5cdbeb8SBram Moolenaar	endif
28e344beadSBram Moolenaar	let start -= 2
290e5bd96fSBram Moolenaar      elseif line[start - 1] == ']'
300e5bd96fSBram Moolenaar	" Skip over [...].
310e5bd96fSBram Moolenaar	let n = 0
320e5bd96fSBram Moolenaar	let start -= 1
330e5bd96fSBram Moolenaar	while start > 0
340e5bd96fSBram Moolenaar	  let start -= 1
350e5bd96fSBram Moolenaar	  if line[start] == '['
360e5bd96fSBram Moolenaar	    if n == 0
370e5bd96fSBram Moolenaar	      break
380e5bd96fSBram Moolenaar	    endif
390e5bd96fSBram Moolenaar	    let n -= 1
400e5bd96fSBram Moolenaar	  elseif line[start] == ']'  " nested []
410e5bd96fSBram Moolenaar	    let n += 1
420e5bd96fSBram Moolenaar	  endif
430e5bd96fSBram Moolenaar	endwhile
44e344beadSBram Moolenaar      else
45e344beadSBram Moolenaar	break
46e344beadSBram Moolenaar      endif
47e344beadSBram Moolenaar    endwhile
48d5cdbeb8SBram Moolenaar
49d5cdbeb8SBram Moolenaar    " Return the column of the last word, which is going to be changed.
50d5cdbeb8SBram Moolenaar    " Remember the text that comes before it in s:prepended.
51d5cdbeb8SBram Moolenaar    if lastword == -1
52d5cdbeb8SBram Moolenaar      let s:prepended = ''
53e344beadSBram Moolenaar      return start
54e344beadSBram Moolenaar    endif
55d5cdbeb8SBram Moolenaar    let s:prepended = strpart(line, start, lastword - start)
56d5cdbeb8SBram Moolenaar    return lastword
57d5cdbeb8SBram Moolenaar  endif
58e344beadSBram Moolenaar
59caa0fcfaSBram Moolenaar  " Return list of matches.
60caa0fcfaSBram Moolenaar
61d5cdbeb8SBram Moolenaar  let base = s:prepended . a:base
62d5cdbeb8SBram Moolenaar
63d5cdbeb8SBram Moolenaar  " Don't do anything for an empty base, would result in all the tags in the
64d5cdbeb8SBram Moolenaar  " tags file.
65d5cdbeb8SBram Moolenaar  if base == ''
66d5cdbeb8SBram Moolenaar    return []
67d5cdbeb8SBram Moolenaar  endif
68d5cdbeb8SBram Moolenaar
691056d988SBram Moolenaar  " init cache for vimgrep to empty
701056d988SBram Moolenaar  let s:grepCache = {}
711056d988SBram Moolenaar
720e5bd96fSBram Moolenaar  " Split item in words, keep empty word after "." or "->".
730e5bd96fSBram Moolenaar  " "aa" -> ['aa'], "aa." -> ['aa', ''], "aa.bb" -> ['aa', 'bb'], etc.
740e5bd96fSBram Moolenaar  " We can't use split, because we need to skip nested [...].
7520aac6c1SBram Moolenaar  " "aa[...]" -> ['aa', '[...]'], "aa.bb[...]" -> ['aa', 'bb', '[...]'], etc.
760e5bd96fSBram Moolenaar  let items = []
770e5bd96fSBram Moolenaar  let s = 0
7820aac6c1SBram Moolenaar  let arrays = 0
790e5bd96fSBram Moolenaar  while 1
800e5bd96fSBram Moolenaar    let e = match(base, '\.\|->\|\[', s)
810e5bd96fSBram Moolenaar    if e < 0
820e5bd96fSBram Moolenaar      if s == 0 || base[s - 1] != ']'
830e5bd96fSBram Moolenaar	call add(items, strpart(base, s))
84e344beadSBram Moolenaar      endif
850e5bd96fSBram Moolenaar      break
860e5bd96fSBram Moolenaar    endif
870e5bd96fSBram Moolenaar    if s == 0 || base[s - 1] != ']'
880e5bd96fSBram Moolenaar      call add(items, strpart(base, s, e - s))
890e5bd96fSBram Moolenaar    endif
900e5bd96fSBram Moolenaar    if base[e] == '.'
910e5bd96fSBram Moolenaar      let s = e + 1	" skip over '.'
920e5bd96fSBram Moolenaar    elseif base[e] == '-'
930e5bd96fSBram Moolenaar      let s = e + 2	" skip over '->'
940e5bd96fSBram Moolenaar    else
950e5bd96fSBram Moolenaar      " Skip over [...].
960e5bd96fSBram Moolenaar      let n = 0
970e5bd96fSBram Moolenaar      let s = e
980e5bd96fSBram Moolenaar      let e += 1
990e5bd96fSBram Moolenaar      while e < len(base)
1000e5bd96fSBram Moolenaar	if base[e] == ']'
1010e5bd96fSBram Moolenaar	  if n == 0
1020e5bd96fSBram Moolenaar	    break
1030e5bd96fSBram Moolenaar	  endif
1040e5bd96fSBram Moolenaar	  let n -= 1
1050e5bd96fSBram Moolenaar	elseif base[e] == '['  " nested [...]
1060e5bd96fSBram Moolenaar	  let n += 1
1070e5bd96fSBram Moolenaar	endif
1080e5bd96fSBram Moolenaar	let e += 1
1090e5bd96fSBram Moolenaar      endwhile
1100e5bd96fSBram Moolenaar      let e += 1
1110e5bd96fSBram Moolenaar      call add(items, strpart(base, s, e - s))
11220aac6c1SBram Moolenaar      let arrays += 1
1130e5bd96fSBram Moolenaar      let s = e
1140e5bd96fSBram Moolenaar    endif
1150e5bd96fSBram Moolenaar  endwhile
116dd2436f3SBram Moolenaar
117a4a08388SBram Moolenaar  " Find the variable items[0].
118a4a08388SBram Moolenaar  " 1. in current function (like with "gd")
119a4a08388SBram Moolenaar  " 2. in tags file(s) (like with ":tag")
120a4a08388SBram Moolenaar  " 3. in current file (like with "gD")
121a4a08388SBram Moolenaar  let res = []
122f75a963eSBram Moolenaar  if searchdecl(items[0], 0, 1) == 0
123dd2436f3SBram Moolenaar    " Found, now figure out the type.
124dd2436f3SBram Moolenaar    " TODO: join previous line if it makes sense
125dd2436f3SBram Moolenaar    let line = getline('.')
126dd2436f3SBram Moolenaar    let col = col('.')
1278c8de839SBram Moolenaar    if stridx(strpart(line, 0, col), ';') != -1
1288c8de839SBram Moolenaar      " Handle multiple declarations on the same line.
1298c8de839SBram Moolenaar      let col2 = col - 1
1308c8de839SBram Moolenaar      while line[col2] != ';'
1318c8de839SBram Moolenaar	let col2 -= 1
1328c8de839SBram Moolenaar      endwhile
1338c8de839SBram Moolenaar      let line = strpart(line, col2 + 1)
1348c8de839SBram Moolenaar      let col -= col2
1358c8de839SBram Moolenaar    endif
1368c8de839SBram Moolenaar    if stridx(strpart(line, 0, col), ',') != -1
1378c8de839SBram Moolenaar      " Handle multiple declarations on the same line in a function
1388c8de839SBram Moolenaar      " declaration.
1398c8de839SBram Moolenaar      let col2 = col - 1
1408c8de839SBram Moolenaar      while line[col2] != ','
1418c8de839SBram Moolenaar	let col2 -= 1
1428c8de839SBram Moolenaar      endwhile
1438c8de839SBram Moolenaar      if strpart(line, col2 + 1, col - col2 - 1) =~ ' *[^ ][^ ]*  *[^ ]'
1448c8de839SBram Moolenaar	let line = strpart(line, col2 + 1)
1458c8de839SBram Moolenaar	let col -= col2
1468c8de839SBram Moolenaar      endif
1478c8de839SBram Moolenaar    endif
1480e5bd96fSBram Moolenaar    if len(items) == 1
1490e5bd96fSBram Moolenaar      " Completing one word and it's a local variable: May add '[', '.' or
1500e5bd96fSBram Moolenaar      " '->'.
1510e5bd96fSBram Moolenaar      let match = items[0]
152eb94e559SBram Moolenaar      let kind = 'v'
153eb94e559SBram Moolenaar      if match(line, '\<' . match . '\s*\[') > 0
1540e5bd96fSBram Moolenaar	let match .= '['
1550e5bd96fSBram Moolenaar      else
1561f35bf9cSBram Moolenaar	let res = s:Nextitem(strpart(line, 0, col), [''], 0, 1)
1570e5bd96fSBram Moolenaar	if len(res) > 0
1580e5bd96fSBram Moolenaar	  " There are members, thus add "." or "->".
1590e5bd96fSBram Moolenaar	  if match(line, '\*[ \t(]*' . match . '\>') > 0
1600e5bd96fSBram Moolenaar	    let match .= '->'
1610e5bd96fSBram Moolenaar	  else
1620e5bd96fSBram Moolenaar	    let match .= '.'
1630e5bd96fSBram Moolenaar	  endif
1640e5bd96fSBram Moolenaar	endif
1650e5bd96fSBram Moolenaar      endif
166eb94e559SBram Moolenaar      let res = [{'match': match, 'tagline' : '', 'kind' : kind, 'info' : line}]
16720aac6c1SBram Moolenaar    elseif len(items) == arrays + 1
16820aac6c1SBram Moolenaar      " Completing one word and it's a local array variable: build tagline
16920aac6c1SBram Moolenaar      " from declaration line
17020aac6c1SBram Moolenaar      let match = items[0]
17120aac6c1SBram Moolenaar      let kind = 'v'
17220aac6c1SBram Moolenaar      let tagline = "\t/^" . line . '$/'
17320aac6c1SBram Moolenaar      let res = [{'match': match, 'tagline' : tagline, 'kind' : kind, 'info' : line}]
1740e5bd96fSBram Moolenaar    else
1750e5bd96fSBram Moolenaar      " Completing "var.", "var.something", etc.
17600a927d6SBram Moolenaar      let res = s:Nextitem(strpart(line, 0, col), items[1:], 0, 1)
1770e5bd96fSBram Moolenaar    endif
1780e5bd96fSBram Moolenaar  endif
1790e5bd96fSBram Moolenaar
18020aac6c1SBram Moolenaar  if len(items) == 1 || len(items) == arrays + 1
1810e5bd96fSBram Moolenaar    " Only one part, no "." or "->": complete from tags file.
18220aac6c1SBram Moolenaar    if len(items) == 1
183eb94e559SBram Moolenaar      let tags = taglist('^' . base)
18420aac6c1SBram Moolenaar    else
18520aac6c1SBram Moolenaar      let tags = taglist('^' . items[0] . '$')
18620aac6c1SBram Moolenaar    endif
187eb94e559SBram Moolenaar
188eb94e559SBram Moolenaar    " Remove members, these can't appear without something in front.
189eb94e559SBram Moolenaar    call filter(tags, 'has_key(v:val, "kind") ? v:val["kind"] != "m" : 1')
190eb94e559SBram Moolenaar
191eb94e559SBram Moolenaar    " Remove static matches in other files.
192eb94e559SBram Moolenaar    call filter(tags, '!has_key(v:val, "static") || !v:val["static"] || bufnr("%") == bufnr(v:val["filename"])')
193eb94e559SBram Moolenaar
194eb94e559SBram Moolenaar    call extend(res, map(tags, 's:Tag2item(v:val)'))
195a4a08388SBram Moolenaar  endif
196a4a08388SBram Moolenaar
197a4a08388SBram Moolenaar  if len(res) == 0
198a4a08388SBram Moolenaar    " Find the variable in the tags file(s)
199caa0fcfaSBram Moolenaar    let diclist = taglist('^' . items[0] . '$')
200caa0fcfaSBram Moolenaar
201eb94e559SBram Moolenaar    " Remove members, these can't appear without something in front.
202eb94e559SBram Moolenaar    call filter(diclist, 'has_key(v:val, "kind") ? v:val["kind"] != "m" : 1')
203eb94e559SBram Moolenaar
204caa0fcfaSBram Moolenaar    let res = []
205dd2436f3SBram Moolenaar    for i in range(len(diclist))
20676b92b28SBram Moolenaar      " New ctags has the "typeref" field.  Patched version has "typename".
207a4a08388SBram Moolenaar      if has_key(diclist[i], 'typename')
2081f35bf9cSBram Moolenaar	call extend(res, s:StructMembers(diclist[i]['typename'], items[1:], 1))
20976b92b28SBram Moolenaar      elseif has_key(diclist[i], 'typeref')
21076b92b28SBram Moolenaar	call extend(res, s:StructMembers(diclist[i]['typeref'], items[1:], 1))
211a4a08388SBram Moolenaar      endif
212a4a08388SBram Moolenaar
213a4a08388SBram Moolenaar      " For a variable use the command, which must be a search pattern that
214a4a08388SBram Moolenaar      " shows the declaration of the variable.
215dd2436f3SBram Moolenaar      if diclist[i]['kind'] == 'v'
216dd2436f3SBram Moolenaar	let line = diclist[i]['cmd']
217dd2436f3SBram Moolenaar	if line[0] == '/' && line[1] == '^'
218e3226be9SBram Moolenaar	  let col = match(line, '\<' . items[0] . '\>')
2191f35bf9cSBram Moolenaar	  call extend(res, s:Nextitem(strpart(line, 2, col - 2), items[1:], 0, 1))
220caa0fcfaSBram Moolenaar	endif
221dd2436f3SBram Moolenaar      endif
222dd2436f3SBram Moolenaar    endfor
223dd2436f3SBram Moolenaar  endif
224dd2436f3SBram Moolenaar
225a4a08388SBram Moolenaar  if len(res) == 0 && searchdecl(items[0], 1) == 0
226a4a08388SBram Moolenaar    " Found, now figure out the type.
227a4a08388SBram Moolenaar    " TODO: join previous line if it makes sense
228a4a08388SBram Moolenaar    let line = getline('.')
229a4a08388SBram Moolenaar    let col = col('.')
2301f35bf9cSBram Moolenaar    let res = s:Nextitem(strpart(line, 0, col), items[1:], 0, 1)
231f75a963eSBram Moolenaar  endif
232f75a963eSBram Moolenaar
2330e5bd96fSBram Moolenaar  " If the last item(s) are [...] they need to be added to the matches.
2340e5bd96fSBram Moolenaar  let last = len(items) - 1
2350e5bd96fSBram Moolenaar  let brackets = ''
2360e5bd96fSBram Moolenaar  while last >= 0
2370e5bd96fSBram Moolenaar    if items[last][0] != '['
2380e5bd96fSBram Moolenaar      break
239f75a963eSBram Moolenaar    endif
2400e5bd96fSBram Moolenaar    let brackets = items[last] . brackets
2410e5bd96fSBram Moolenaar    let last -= 1
2420e5bd96fSBram Moolenaar  endwhile
243a4a08388SBram Moolenaar
2440e5bd96fSBram Moolenaar  return map(res, 's:Tagline2item(v:val, brackets)')
245caa0fcfaSBram Moolenaarendfunc
246dd2436f3SBram Moolenaar
247*4466ad6bSBram Moolenaarfunc s:GetAddition(line, match, memarg, bracket)
2480e5bd96fSBram Moolenaar  " Guess if the item is an array.
2490e5bd96fSBram Moolenaar  if a:bracket && match(a:line, a:match . '\s*\[') > 0
2500e5bd96fSBram Moolenaar    return '['
2510e5bd96fSBram Moolenaar  endif
2520e5bd96fSBram Moolenaar
2530e5bd96fSBram Moolenaar  " Check if the item has members.
2541f35bf9cSBram Moolenaar  if len(s:SearchMembers(a:memarg, [''], 0)) > 0
2550e5bd96fSBram Moolenaar    " If there is a '*' before the name use "->".
2560e5bd96fSBram Moolenaar    if match(a:line, '\*[ \t(]*' . a:match . '\>') > 0
2570e5bd96fSBram Moolenaar      return '->'
2580e5bd96fSBram Moolenaar    else
2590e5bd96fSBram Moolenaar      return '.'
2600e5bd96fSBram Moolenaar    endif
2610e5bd96fSBram Moolenaar  endif
2620e5bd96fSBram Moolenaar  return ''
263*4466ad6bSBram Moolenaarendfunc
2640e5bd96fSBram Moolenaar
265280f126eSBram Moolenaar" Turn the tag info "val" into an item for completion.
266280f126eSBram Moolenaar" "val" is is an item in the list returned by taglist().
2670e5bd96fSBram Moolenaar" If it is a variable we may add "." or "->".  Don't do it for other types,
2680e5bd96fSBram Moolenaar" such as a typedef, by not including the info that s:GetAddition() uses.
269*4466ad6bSBram Moolenaarfunc s:Tag2item(val)
270eb94e559SBram Moolenaar  let res = {'match': a:val['name']}
271f52c725cSBram Moolenaar
272eb94e559SBram Moolenaar  let res['extra'] = s:Tagcmd2extra(a:val['cmd'], a:val['name'], a:val['filename'])
273eb94e559SBram Moolenaar
27476b92b28SBram Moolenaar  let s = s:Dict2info(a:val)
275eb94e559SBram Moolenaar  if s != ''
27676b92b28SBram Moolenaar    let res['info'] = s
277eb94e559SBram Moolenaar  endif
278eb94e559SBram Moolenaar
279eb94e559SBram Moolenaar  let res['tagline'] = ''
2800e5bd96fSBram Moolenaar  if has_key(a:val, "kind")
281eb94e559SBram Moolenaar    let kind = a:val['kind']
282eb94e559SBram Moolenaar    let res['kind'] = kind
283eb94e559SBram Moolenaar    if kind == 'v'
284eb94e559SBram Moolenaar      let res['tagline'] = "\t" . a:val['cmd']
285eb94e559SBram Moolenaar      let res['dict'] = a:val
286eb94e559SBram Moolenaar    elseif kind == 'f'
287eb94e559SBram Moolenaar      let res['match'] = a:val['name'] . '('
288280f126eSBram Moolenaar    endif
289280f126eSBram Moolenaar  endif
290eb94e559SBram Moolenaar
291eb94e559SBram Moolenaar  return res
292*4466ad6bSBram Moolenaarendfunc
2930e5bd96fSBram Moolenaar
29476b92b28SBram Moolenaar" Use all the items in dictionary for the "info" entry.
295*4466ad6bSBram Moolenaarfunc s:Dict2info(dict)
29676b92b28SBram Moolenaar  let info = ''
29776b92b28SBram Moolenaar  for k in sort(keys(a:dict))
29876b92b28SBram Moolenaar    let info  .= k . repeat(' ', 10 - len(k))
29976b92b28SBram Moolenaar    if k == 'cmd'
30076b92b28SBram Moolenaar      let info .= substitute(matchstr(a:dict['cmd'], '/^\s*\zs.*\ze$/'), '\\\(.\)', '\1', 'g')
30176b92b28SBram Moolenaar    else
30276b92b28SBram Moolenaar      let info .= a:dict[k]
30376b92b28SBram Moolenaar    endif
30476b92b28SBram Moolenaar    let info .= "\n"
30576b92b28SBram Moolenaar  endfor
30676b92b28SBram Moolenaar  return info
30776b92b28SBram Moolenaarendfunc
30876b92b28SBram Moolenaar
30976b92b28SBram Moolenaar" Parse a tag line and return a dictionary with items like taglist()
310*4466ad6bSBram Moolenaarfunc s:ParseTagline(line)
31176b92b28SBram Moolenaar  let l = split(a:line, "\t")
31276b92b28SBram Moolenaar  let d = {}
31376b92b28SBram Moolenaar  if len(l) >= 3
31476b92b28SBram Moolenaar    let d['name'] = l[0]
31576b92b28SBram Moolenaar    let d['filename'] = l[1]
31676b92b28SBram Moolenaar    let d['cmd'] = l[2]
31776b92b28SBram Moolenaar    let n = 2
31876b92b28SBram Moolenaar    if l[2] =~ '^/'
31976b92b28SBram Moolenaar      " Find end of cmd, it may contain Tabs.
32076b92b28SBram Moolenaar      while n < len(l) && l[n] !~ '/;"$'
32176b92b28SBram Moolenaar	let n += 1
32276b92b28SBram Moolenaar	let d['cmd'] .= "  " . l[n]
32376b92b28SBram Moolenaar      endwhile
32476b92b28SBram Moolenaar    endif
32576b92b28SBram Moolenaar    for i in range(n + 1, len(l) - 1)
32676b92b28SBram Moolenaar      if l[i] == 'file:'
32776b92b28SBram Moolenaar	let d['static'] = 1
32876b92b28SBram Moolenaar      elseif l[i] !~ ':'
32976b92b28SBram Moolenaar	let d['kind'] = l[i]
33076b92b28SBram Moolenaar      else
33176b92b28SBram Moolenaar	let d[matchstr(l[i], '[^:]*')] = matchstr(l[i], ':\zs.*')
33276b92b28SBram Moolenaar      endif
33376b92b28SBram Moolenaar    endfor
33476b92b28SBram Moolenaar  endif
33576b92b28SBram Moolenaar
33676b92b28SBram Moolenaar  return d
337*4466ad6bSBram Moolenaarendfunc
33876b92b28SBram Moolenaar
3390e5bd96fSBram Moolenaar" Turn a match item "val" into an item for completion.
3400e5bd96fSBram Moolenaar" "val['match']" is the matching item.
3410e5bd96fSBram Moolenaar" "val['tagline']" is the tagline in which the last part was found.
342*4466ad6bSBram Moolenaarfunc s:Tagline2item(val, brackets)
343f52c725cSBram Moolenaar  let line = a:val['tagline']
344eb94e559SBram Moolenaar  let add = s:GetAddition(line, a:val['match'], [a:val], a:brackets == '')
345eb94e559SBram Moolenaar  let res = {'word': a:val['match'] . a:brackets . add }
346eb94e559SBram Moolenaar
347eb94e559SBram Moolenaar  if has_key(a:val, 'info')
348eb94e559SBram Moolenaar    " Use info from Tag2item().
349eb94e559SBram Moolenaar    let res['info'] = a:val['info']
350eb94e559SBram Moolenaar  else
35176b92b28SBram Moolenaar    " Parse the tag line and add each part to the "info" entry.
35276b92b28SBram Moolenaar    let s = s:Dict2info(s:ParseTagline(line))
353eb94e559SBram Moolenaar    if s != ''
35476b92b28SBram Moolenaar      let res['info'] = s
355eb94e559SBram Moolenaar    endif
356eb94e559SBram Moolenaar  endif
357eb94e559SBram Moolenaar
358eb94e559SBram Moolenaar  if has_key(a:val, 'kind')
359eb94e559SBram Moolenaar    let res['kind'] = a:val['kind']
360eb94e559SBram Moolenaar  elseif add == '('
361eb94e559SBram Moolenaar    let res['kind'] = 'f'
362eb94e559SBram Moolenaar  else
363eb94e559SBram Moolenaar    let s = matchstr(line, '\t\(kind:\)\=\zs\S\ze\(\t\|$\)')
364eb94e559SBram Moolenaar    if s != ''
365eb94e559SBram Moolenaar      let res['kind'] = s
366eb94e559SBram Moolenaar    endif
367eb94e559SBram Moolenaar  endif
368eb94e559SBram Moolenaar
3698b6144bdSBram Moolenaar  if has_key(a:val, 'extra')
370eb94e559SBram Moolenaar    let res['menu'] = a:val['extra']
371eb94e559SBram Moolenaar    return res
3728b6144bdSBram Moolenaar  endif
373f52c725cSBram Moolenaar
374f52c725cSBram Moolenaar  " Isolate the command after the tag and filename.
375f52c725cSBram Moolenaar  let s = matchstr(line, '[^\t]*\t[^\t]*\t\zs\(/^.*$/\|[^\t]*\)\ze\(;"\t\|\t\|$\)')
376f52c725cSBram Moolenaar  if s != ''
377eb94e559SBram Moolenaar    let res['menu'] = s:Tagcmd2extra(s, a:val['match'], matchstr(line, '[^\t]*\t\zs[^\t]*\ze\t'))
378f52c725cSBram Moolenaar  endif
379eb94e559SBram Moolenaar  return res
380*4466ad6bSBram Moolenaarendfunc
381280f126eSBram Moolenaar
382f52c725cSBram Moolenaar" Turn a command from a tag line to something that is useful in the menu
383*4466ad6bSBram Moolenaarfunc s:Tagcmd2extra(cmd, name, fname)
384f52c725cSBram Moolenaar  if a:cmd =~ '^/^'
385f52c725cSBram Moolenaar    " The command is a search command, useful to see what it is.
3861f35bf9cSBram Moolenaar    let x = matchstr(a:cmd, '^/^\s*\zs.*\ze$/')
3871f35bf9cSBram Moolenaar    let x = substitute(x, '\<' . a:name . '\>', '@@', '')
388f52c725cSBram Moolenaar    let x = substitute(x, '\\\(.\)', '\1', 'g')
389f52c725cSBram Moolenaar    let x = x . ' - ' . a:fname
390f52c725cSBram Moolenaar  elseif a:cmd =~ '^\d*$'
391f52c725cSBram Moolenaar    " The command is a line number, the file name is more useful.
392f52c725cSBram Moolenaar    let x = a:fname . ' - ' . a:cmd
393f52c725cSBram Moolenaar  else
394f52c725cSBram Moolenaar    " Not recognized, use command and file name.
395f52c725cSBram Moolenaar    let x = a:cmd . ' - ' . a:fname
396f52c725cSBram Moolenaar  endif
397f52c725cSBram Moolenaar  return x
398*4466ad6bSBram Moolenaarendfunc
399280f126eSBram Moolenaar
400a4a08388SBram Moolenaar" Find composing type in "lead" and match items[0] with it.
401a4a08388SBram Moolenaar" Repeat this recursively for items[1], if it's there.
4020e5bd96fSBram Moolenaar" When resolving typedefs "depth" is used to avoid infinite recursion.
403a4a08388SBram Moolenaar" Return the list of matches.
404*4466ad6bSBram Moolenaarfunc s:Nextitem(lead, items, depth, all)
405caa0fcfaSBram Moolenaar
406caa0fcfaSBram Moolenaar  " Use the text up to the variable name and split it in tokens.
407caa0fcfaSBram Moolenaar  let tokens = split(a:lead, '\s\+\|\<')
408caa0fcfaSBram Moolenaar
409caa0fcfaSBram Moolenaar  " Try to recognize the type of the variable.  This is rough guessing...
410a4a08388SBram Moolenaar  let res = []
411caa0fcfaSBram Moolenaar  for tidx in range(len(tokens))
412caa0fcfaSBram Moolenaar
4131056d988SBram Moolenaar    " Skip tokens starting with a non-ID character.
4141056d988SBram Moolenaar    if tokens[tidx] !~ '^\h'
4151056d988SBram Moolenaar      continue
4161056d988SBram Moolenaar    endif
4171056d988SBram Moolenaar
418a4a08388SBram Moolenaar    " Recognize "struct foobar" and "union foobar".
4198b2d9c43SBram Moolenaar    " Also do "class foobar" when it's C++ after all (doesn't work very well
4208b2d9c43SBram Moolenaar    " though).
4218b2d9c43SBram Moolenaar    if (tokens[tidx] == 'struct' || tokens[tidx] == 'union' || tokens[tidx] == 'class') && tidx + 1 < len(tokens)
4221f35bf9cSBram Moolenaar      let res = s:StructMembers(tokens[tidx] . ':' . tokens[tidx + 1], a:items, a:all)
423dd2436f3SBram Moolenaar      break
424dd2436f3SBram Moolenaar    endif
425dd2436f3SBram Moolenaar
426a4a08388SBram Moolenaar    " TODO: add more reserved words
4270e5bd96fSBram Moolenaar    if index(['int', 'short', 'char', 'float', 'double', 'static', 'unsigned', 'extern'], tokens[tidx]) >= 0
428a4a08388SBram Moolenaar      continue
429a4a08388SBram Moolenaar    endif
430a4a08388SBram Moolenaar
431a4a08388SBram Moolenaar    " Use the tags file to find out if this is a typedef.
432caa0fcfaSBram Moolenaar    let diclist = taglist('^' . tokens[tidx] . '$')
433f75a963eSBram Moolenaar    for tagidx in range(len(diclist))
434eb94e559SBram Moolenaar      let item = diclist[tagidx]
435eb94e559SBram Moolenaar
43676b92b28SBram Moolenaar      " New ctags has the "typeref" field.  Patched version has "typename".
43776b92b28SBram Moolenaar      if has_key(item, 'typeref')
43876b92b28SBram Moolenaar	call extend(res, s:StructMembers(item['typeref'], a:items, a:all))
43976b92b28SBram Moolenaar	continue
44076b92b28SBram Moolenaar      endif
441eb94e559SBram Moolenaar      if has_key(item, 'typename')
442eb94e559SBram Moolenaar	call extend(res, s:StructMembers(item['typename'], a:items, a:all))
443a4a08388SBram Moolenaar	continue
444a4a08388SBram Moolenaar      endif
445a4a08388SBram Moolenaar
446f75a963eSBram Moolenaar      " Only handle typedefs here.
447eb94e559SBram Moolenaar      if item['kind'] != 't'
448eb94e559SBram Moolenaar	continue
449eb94e559SBram Moolenaar      endif
450eb94e559SBram Moolenaar
451eb94e559SBram Moolenaar      " Skip matches local to another file.
452eb94e559SBram Moolenaar      if has_key(item, 'static') && item['static'] && bufnr('%') != bufnr(item['filename'])
453f75a963eSBram Moolenaar	continue
454f75a963eSBram Moolenaar      endif
455f75a963eSBram Moolenaar
456f75a963eSBram Moolenaar      " For old ctags we recognize "typedef struct aaa" and
457f75a963eSBram Moolenaar      " "typedef union bbb" in the tags file command.
458eb94e559SBram Moolenaar      let cmd = item['cmd']
459f75a963eSBram Moolenaar      let ei = matchend(cmd, 'typedef\s\+')
460f75a963eSBram Moolenaar      if ei > 1
461f75a963eSBram Moolenaar	let cmdtokens = split(strpart(cmd, ei), '\s\+\|\<')
462f75a963eSBram Moolenaar	if len(cmdtokens) > 1
4638b2d9c43SBram Moolenaar	  if cmdtokens[0] == 'struct' || cmdtokens[0] == 'union' || cmdtokens[0] == 'class'
464f75a963eSBram Moolenaar	    let name = ''
465f75a963eSBram Moolenaar	    " Use the first identifier after the "struct" or "union"
466f75a963eSBram Moolenaar	    for ti in range(len(cmdtokens) - 1)
467f75a963eSBram Moolenaar	      if cmdtokens[ti] =~ '^\w'
468f75a963eSBram Moolenaar		let name = cmdtokens[ti]
469f75a963eSBram Moolenaar		break
470f75a963eSBram Moolenaar	      endif
471f75a963eSBram Moolenaar	    endfor
472f75a963eSBram Moolenaar	    if name != ''
4731f35bf9cSBram Moolenaar	      call extend(res, s:StructMembers(cmdtokens[0] . ':' . name, a:items, a:all))
474f75a963eSBram Moolenaar	    endif
4750e5bd96fSBram Moolenaar	  elseif a:depth < 10
476f75a963eSBram Moolenaar	    " Could be "typedef other_T some_T".
4771f35bf9cSBram Moolenaar	    call extend(res, s:Nextitem(cmdtokens[0], a:items, a:depth + 1, a:all))
478f75a963eSBram Moolenaar	  endif
479f75a963eSBram Moolenaar	endif
480caa0fcfaSBram Moolenaar      endif
481caa0fcfaSBram Moolenaar    endfor
482a4a08388SBram Moolenaar    if len(res) > 0
483caa0fcfaSBram Moolenaar      break
484caa0fcfaSBram Moolenaar    endif
485a4a08388SBram Moolenaar  endfor
486dd2436f3SBram Moolenaar
487a4a08388SBram Moolenaar  return res
488*4466ad6bSBram Moolenaarendfunc
489a4a08388SBram Moolenaar
490a4a08388SBram Moolenaar
4910e5bd96fSBram Moolenaar" Search for members of structure "typename" in tags files.
492f75a963eSBram Moolenaar" Return a list with resulting matches.
493f75a963eSBram Moolenaar" Each match is a dictionary with "match" and "tagline" entries.
4941f35bf9cSBram Moolenaar" When "all" is non-zero find all, otherwise just return 1 if there is any
4951f35bf9cSBram Moolenaar" member.
496*4466ad6bSBram Moolenaarfunc s:StructMembers(typename, items, all)
497a4a08388SBram Moolenaar  " Todo: What about local structures?
498862c27a0SBram Moolenaar  let fnames = join(map(tagfiles(), 'escape(v:val, " \\#%")'))
499a4a08388SBram Moolenaar  if fnames == ''
500f75a963eSBram Moolenaar    return []
501a4a08388SBram Moolenaar  endif
502a4a08388SBram Moolenaar
503a4a08388SBram Moolenaar  let typename = a:typename
504a4a08388SBram Moolenaar  let qflist = []
5051056d988SBram Moolenaar  let cached = 0
5061f35bf9cSBram Moolenaar  if a:all == 0
5071f35bf9cSBram Moolenaar    let n = '1'	" stop at first found match
5081056d988SBram Moolenaar    if has_key(s:grepCache, a:typename)
5091056d988SBram Moolenaar      let qflist = s:grepCache[a:typename]
5101056d988SBram Moolenaar      let cached = 1
5111056d988SBram Moolenaar    endif
5121f35bf9cSBram Moolenaar  else
5131f35bf9cSBram Moolenaar    let n = ''
5141f35bf9cSBram Moolenaar  endif
5151056d988SBram Moolenaar  if !cached
516a4a08388SBram Moolenaar    while 1
51730b65817SBram Moolenaar      exe 'silent! keepj noautocmd ' . n . 'vimgrep /\t' . typename . '\(\t\|$\)/j ' . fnames
5181056d988SBram Moolenaar
519a4a08388SBram Moolenaar      let qflist = getqflist()
520a4a08388SBram Moolenaar      if len(qflist) > 0 || match(typename, "::") < 0
521a4a08388SBram Moolenaar	break
522a4a08388SBram Moolenaar      endif
523a4a08388SBram Moolenaar      " No match for "struct:context::name", remove "context::" and try again.
524a4a08388SBram Moolenaar      let typename = substitute(typename, ':[^:]*::', ':', '')
525a4a08388SBram Moolenaar    endwhile
526a4a08388SBram Moolenaar
5271056d988SBram Moolenaar    if a:all == 0
5281056d988SBram Moolenaar      " Store the result to be able to use it again later.
5291056d988SBram Moolenaar      let s:grepCache[a:typename] = qflist
5301056d988SBram Moolenaar    endif
5311056d988SBram Moolenaar  endif
5321056d988SBram Moolenaar
53320aac6c1SBram Moolenaar  " Skip over [...] items
53420aac6c1SBram Moolenaar  let idx = 0
53520aac6c1SBram Moolenaar  while 1
53620aac6c1SBram Moolenaar    if idx >= len(a:items)
53720aac6c1SBram Moolenaar      let target = ''		" No further items, matching all members
53820aac6c1SBram Moolenaar      break
53920aac6c1SBram Moolenaar    endif
54020aac6c1SBram Moolenaar    if a:items[idx][0] != '['
54120aac6c1SBram Moolenaar      let target = a:items[idx]
54220aac6c1SBram Moolenaar      break
54320aac6c1SBram Moolenaar    endif
54420aac6c1SBram Moolenaar    let idx += 1
54520aac6c1SBram Moolenaar  endwhile
546eb94e559SBram Moolenaar  " Put matching members in matches[].
547f75a963eSBram Moolenaar  let matches = []
548a4a08388SBram Moolenaar  for l in qflist
549a4a08388SBram Moolenaar    let memb = matchstr(l['text'], '[^\t]*')
55020aac6c1SBram Moolenaar    if memb =~ '^' . target
551eb94e559SBram Moolenaar      " Skip matches local to another file.
552eb94e559SBram Moolenaar      if match(l['text'], "\tfile:") < 0 || bufnr('%') == bufnr(matchstr(l['text'], '\t\zs[^\t]*'))
553eb94e559SBram Moolenaar	let item = {'match': memb, 'tagline': l['text']}
554eb94e559SBram Moolenaar
555eb94e559SBram Moolenaar	" Add the kind of item.
556eb94e559SBram Moolenaar	let s = matchstr(l['text'], '\t\(kind:\)\=\zs\S\ze\(\t\|$\)')
557eb94e559SBram Moolenaar	if s != ''
558eb94e559SBram Moolenaar	  let item['kind'] = s
559eb94e559SBram Moolenaar	  if s == 'f'
560eb94e559SBram Moolenaar	    let item['match'] = memb . '('
561eb94e559SBram Moolenaar	  endif
562eb94e559SBram Moolenaar	endif
563eb94e559SBram Moolenaar
564eb94e559SBram Moolenaar	call add(matches, item)
565eb94e559SBram Moolenaar      endif
566a4a08388SBram Moolenaar    endif
567caa0fcfaSBram Moolenaar  endfor
568dd2436f3SBram Moolenaar
569f75a963eSBram Moolenaar  if len(matches) > 0
57020aac6c1SBram Moolenaar    " Skip over next [...] items
57120aac6c1SBram Moolenaar    let idx += 1
5720e5bd96fSBram Moolenaar    while 1
5730e5bd96fSBram Moolenaar      if idx >= len(a:items)
5740e5bd96fSBram Moolenaar	return matches		" No further items, return the result.
575caa0fcfaSBram Moolenaar      endif
5760e5bd96fSBram Moolenaar      if a:items[idx][0] != '['
5770e5bd96fSBram Moolenaar	break
5780e5bd96fSBram Moolenaar      endif
5790e5bd96fSBram Moolenaar      let idx += 1
5800e5bd96fSBram Moolenaar    endwhile
581caa0fcfaSBram Moolenaar
582caa0fcfaSBram Moolenaar    " More items following.  For each of the possible members find the
583caa0fcfaSBram Moolenaar    " matching following members.
5841f35bf9cSBram Moolenaar    return s:SearchMembers(matches, a:items[idx :], a:all)
585dd2436f3SBram Moolenaar  endif
586dd2436f3SBram Moolenaar
587caa0fcfaSBram Moolenaar  " Failed to find anything.
588caa0fcfaSBram Moolenaar  return []
589*4466ad6bSBram Moolenaarendfunc
590f75a963eSBram Moolenaar
591f75a963eSBram Moolenaar" For matching members, find matches for following items.
5921f35bf9cSBram Moolenaar" When "all" is non-zero find all, otherwise just return 1 if there is any
5931f35bf9cSBram Moolenaar" member.
594*4466ad6bSBram Moolenaarfunc s:SearchMembers(matches, items, all)
595f75a963eSBram Moolenaar  let res = []
596f75a963eSBram Moolenaar  for i in range(len(a:matches))
597280f126eSBram Moolenaar    let typename = ''
598280f126eSBram Moolenaar    if has_key(a:matches[i], 'dict')
599280f126eSBram Moolenaar      if has_key(a:matches[i].dict, 'typename')
600280f126eSBram Moolenaar	let typename = a:matches[i].dict['typename']
60176b92b28SBram Moolenaar      elseif has_key(a:matches[i].dict, 'typeref')
60276b92b28SBram Moolenaar	let typename = a:matches[i].dict['typeref']
603280f126eSBram Moolenaar      endif
604280f126eSBram Moolenaar      let line = "\t" . a:matches[i].dict['cmd']
605280f126eSBram Moolenaar    else
606f75a963eSBram Moolenaar      let line = a:matches[i]['tagline']
607f75a963eSBram Moolenaar      let e = matchend(line, '\ttypename:')
60876b92b28SBram Moolenaar      if e < 0
60976b92b28SBram Moolenaar	let e = matchend(line, '\ttyperef:')
61076b92b28SBram Moolenaar      endif
611f75a963eSBram Moolenaar      if e > 0
612f75a963eSBram Moolenaar	" Use typename field
613280f126eSBram Moolenaar	let typename = matchstr(line, '[^\t]*', e)
614280f126eSBram Moolenaar      endif
615280f126eSBram Moolenaar    endif
6161f35bf9cSBram Moolenaar
617280f126eSBram Moolenaar    if typename != ''
6181f35bf9cSBram Moolenaar      call extend(res, s:StructMembers(typename, a:items, a:all))
619f75a963eSBram Moolenaar    else
620f75a963eSBram Moolenaar      " Use the search command (the declaration itself).
621f75a963eSBram Moolenaar      let s = match(line, '\t\zs/^')
622f75a963eSBram Moolenaar      if s > 0
623280f126eSBram Moolenaar	let e = match(line, '\<' . a:matches[i]['match'] . '\>', s)
624f75a963eSBram Moolenaar	if e > 0
6251f35bf9cSBram Moolenaar	  call extend(res, s:Nextitem(strpart(line, s, e - s), a:items, 0, a:all))
626f75a963eSBram Moolenaar	endif
627f75a963eSBram Moolenaar      endif
628f75a963eSBram Moolenaar    endif
6291f35bf9cSBram Moolenaar    if a:all == 0 && len(res) > 0
6301f35bf9cSBram Moolenaar      break
6311f35bf9cSBram Moolenaar    endif
632f75a963eSBram Moolenaar  endfor
633f75a963eSBram Moolenaar  return res
634f75a963eSBram Moolenaarendfunc
635b6b046b2SBram Moolenaar
636b6b046b2SBram Moolenaarlet &cpo = s:cpo_save
637b6b046b2SBram Moolenaarunlet s:cpo_save
638d1caa941SBram Moolenaar
639d1caa941SBram Moolenaar" vim: noet sw=2 sts=2
640