blob: 3bddba7a9510921dc1bf21f07e9e8f22646441da [file] [log] [blame]
Bram Moolenaara4d131d2021-12-27 21:33:07 +00001vim9script noclear
Bram Moolenaare344bea2005-09-01 20:46:49 +00002
Bram Moolenaara4d131d2021-12-27 21:33:07 +00003# Vim completion script
4# Language: C
5# Maintainer: Bram Moolenaar <Bram@vim.org>
6# Rewritten in Vim9 script by github user lacygoill
Bram Moolenaarc4573eb2022-01-31 15:40:56 +00007# Last Change: 2022 Jan 31
Bram Moolenaara4a08382005-09-09 19:52:02 +00008
Bram Moolenaara4d131d2021-12-27 21:33:07 +00009var prepended: string
10var grepCache: dict<list<dict<any>>>
11
12# This function is used for the 'omnifunc' option.
Bram Moolenaarc4573eb2022-01-31 15:40:56 +000013export def Complete(findstart: bool, abase: string): any # {{{1
Bram Moolenaara4d131d2021-12-27 21:33:07 +000014 if findstart
15 # Locate the start of the item, including ".", "->" and "[...]".
16 var line: string = getline('.')
17 var start: number = charcol('.') - 1
18 var lastword: number = -1
Bram Moolenaare344bea2005-09-01 20:46:49 +000019 while start > 0
Bram Moolenaard5cdbeb2005-10-10 20:59:28 +000020 if line[start - 1] =~ '\w'
Bram Moolenaara4d131d2021-12-27 21:33:07 +000021 --start
Bram Moolenaard5cdbeb2005-10-10 20:59:28 +000022 elseif line[start - 1] =~ '\.'
Bram Moolenaara4d131d2021-12-27 21:33:07 +000023 if lastword == -1
24 lastword = start
25 endif
26 --start
27 elseif start > 1 && line[start - 2] == '-'
28 && line[start - 1] == '>'
29 if lastword == -1
30 lastword = start
31 endif
32 start -= 2
Bram Moolenaar0e5bd962006-02-04 00:59:56 +000033 elseif line[start - 1] == ']'
Bram Moolenaara4d131d2021-12-27 21:33:07 +000034 # Skip over [...].
35 var n: number = 0
36 --start
37 while start > 0
38 --start
39 if line[start] == '['
40 if n == 0
41 break
42 endif
43 --n
44 elseif line[start] == ']' # nested []
45 ++n
46 endif
47 endwhile
Bram Moolenaare344bea2005-09-01 20:46:49 +000048 else
Bram Moolenaara4d131d2021-12-27 21:33:07 +000049 break
Bram Moolenaare344bea2005-09-01 20:46:49 +000050 endif
51 endwhile
Bram Moolenaard5cdbeb2005-10-10 20:59:28 +000052
Bram Moolenaara4d131d2021-12-27 21:33:07 +000053 # Return the column of the last word, which is going to be changed.
54 # Remember the text that comes before it in prepended.
Bram Moolenaard5cdbeb2005-10-10 20:59:28 +000055 if lastword == -1
Bram Moolenaara4d131d2021-12-27 21:33:07 +000056 prepended = ''
57 return byteidx(line, start)
Bram Moolenaard5cdbeb2005-10-10 20:59:28 +000058 endif
Bram Moolenaara4d131d2021-12-27 21:33:07 +000059 prepended = line[start : lastword - 1]
60 return byteidx(line, lastword)
Bram Moolenaare344bea2005-09-01 20:46:49 +000061 endif
62
Bram Moolenaara4d131d2021-12-27 21:33:07 +000063 # Return list of matches.
Bram Moolenaarcaa0fcf2005-09-07 21:21:14 +000064
Bram Moolenaara4d131d2021-12-27 21:33:07 +000065 var base: string = prepended .. abase
Bram Moolenaard5cdbeb2005-10-10 20:59:28 +000066
Bram Moolenaara4d131d2021-12-27 21:33:07 +000067 # Don't do anything for an empty base, would result in all the tags in the
68 # tags file.
Bram Moolenaar0e5bd962006-02-04 00:59:56 +000069 if base == ''
70 return []
71 endif
72
Bram Moolenaara4d131d2021-12-27 21:33:07 +000073 # init cache for vimgrep to empty
74 grepCache = {}
Bram Moolenaar1056d982006-03-09 22:37:52 +000075
Bram Moolenaara4d131d2021-12-27 21:33:07 +000076 # Split item in words, keep empty word after "." or "->".
77 # "aa" -> ['aa'], "aa." -> ['aa', ''], "aa.bb" -> ['aa', 'bb'], etc.
78 # We can't use split, because we need to skip nested [...].
79 # "aa[...]" -> ['aa', '[...]'], "aa.bb[...]" -> ['aa', 'bb', '[...]'], etc.
80 var items: list<string>
81 var s: number = 0
82 var arrays: number = 0
Bram Moolenaar0e5bd962006-02-04 00:59:56 +000083 while 1
Bram Moolenaara4d131d2021-12-27 21:33:07 +000084 var e: number = base->charidx(match(base, '\.\|->\|\[', s))
Bram Moolenaar0e5bd962006-02-04 00:59:56 +000085 if e < 0
86 if s == 0 || base[s - 1] != ']'
Bram Moolenaara4d131d2021-12-27 21:33:07 +000087 items->add(base[s :])
Bram Moolenaar0e5bd962006-02-04 00:59:56 +000088 endif
89 break
Bram Moolenaard5cdbeb2005-10-10 20:59:28 +000090 endif
Bram Moolenaar0e5bd962006-02-04 00:59:56 +000091 if s == 0 || base[s - 1] != ']'
Bram Moolenaara4d131d2021-12-27 21:33:07 +000092 items->add(base[s : e - 1])
Bram Moolenaar0e5bd962006-02-04 00:59:56 +000093 endif
94 if base[e] == '.'
Bram Moolenaara4d131d2021-12-27 21:33:07 +000095 # skip over '.'
96 s = e + 1
Bram Moolenaar0e5bd962006-02-04 00:59:56 +000097 elseif base[e] == '-'
Bram Moolenaara4d131d2021-12-27 21:33:07 +000098 # skip over '->'
99 s = e + 2
Bram Moolenaar0e5bd962006-02-04 00:59:56 +0000100 else
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000101 # Skip over [...].
102 var n: number = 0
103 s = e
104 ++e
105 while e < strcharlen(base)
106 if base[e] == ']'
107 if n == 0
108 break
109 endif
110 --n
111 elseif base[e] == '[' # nested [...]
112 ++n
113 endif
114 ++e
Bram Moolenaar0e5bd962006-02-04 00:59:56 +0000115 endwhile
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000116 ++e
117 items->add(base[s : e - 1])
118 ++arrays
119 s = e
Bram Moolenaar0e5bd962006-02-04 00:59:56 +0000120 endif
121 endwhile
Bram Moolenaardd2436f2005-09-05 22:14:46 +0000122
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000123 # Find the variable items[0].
124 # 1. in current function (like with "gd")
125 # 2. in tags file(s) (like with ":tag")
126 # 3. in current file (like with "gD")
127 var res: list<dict<any>>
128 if items[0]->searchdecl(false, true) == 0
129 # Found, now figure out the type.
130 # TODO: join previous line if it makes sense
131 var line: string = getline('.')
132 var col: number = charcol('.')
133 if line[: col - 1]->stridx(';') >= 0
134 # Handle multiple declarations on the same line.
135 var col2: number = col - 1
Bram Moolenaar8c8de832008-06-24 22:58:06 +0000136 while line[col2] != ';'
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000137 --col2
Bram Moolenaar8c8de832008-06-24 22:58:06 +0000138 endwhile
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000139 line = line[col2 + 1 :]
140 col -= col2
Bram Moolenaar8c8de832008-06-24 22:58:06 +0000141 endif
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000142 if line[: col - 1]->stridx(',') >= 0
143 # Handle multiple declarations on the same line in a function
144 # declaration.
145 var col2: number = col - 1
Bram Moolenaar8c8de832008-06-24 22:58:06 +0000146 while line[col2] != ','
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000147 --col2
Bram Moolenaar8c8de832008-06-24 22:58:06 +0000148 endwhile
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000149 if line[col2 + 1 : col - 1] =~ ' *[^ ][^ ]* *[^ ]'
150 line = line[col2 + 1 :]
151 col -= col2
Bram Moolenaar8c8de832008-06-24 22:58:06 +0000152 endif
153 endif
Bram Moolenaar0e5bd962006-02-04 00:59:56 +0000154 if len(items) == 1
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000155 # Completing one word and it's a local variable: May add '[', '.' or
156 # '->'.
157 var match: string = items[0]
158 var kind: string = 'v'
159 if match(line, '\<' .. match .. '\s*\[') > 0
160 match ..= '['
Bram Moolenaar0e5bd962006-02-04 00:59:56 +0000161 else
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000162 res = line[: col - 1]->Nextitem([''], 0, true)
163 if len(res) > 0
164 # There are members, thus add "." or "->".
165 if match(line, '\*[ \t(]*' .. match .. '\>') > 0
166 match ..= '->'
167 else
168 match ..= '.'
169 endif
170 endif
Bram Moolenaar0e5bd962006-02-04 00:59:56 +0000171 endif
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000172 res = [{match: match, tagline: '', kind: kind, info: line}]
Bram Moolenaar20aac6c2018-09-02 21:07:30 +0200173 elseif len(items) == arrays + 1
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000174 # Completing one word and it's a local array variable: build tagline
175 # from declaration line
176 var match: string = items[0]
177 var kind: string = 'v'
178 var tagline: string = "\t/^" .. line .. '$/'
179 res = [{match: match, tagline: tagline, kind: kind, info: line}]
Bram Moolenaar0e5bd962006-02-04 00:59:56 +0000180 else
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000181 # Completing "var.", "var.something", etc.
182 res = line[: col - 1]->Nextitem(items[1 :], 0, true)
Bram Moolenaar0e5bd962006-02-04 00:59:56 +0000183 endif
184 endif
185
Bram Moolenaar20aac6c2018-09-02 21:07:30 +0200186 if len(items) == 1 || len(items) == arrays + 1
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000187 # Only one part, no "." or "->": complete from tags file.
188 var tags: list<dict<any>>
Bram Moolenaar20aac6c2018-09-02 21:07:30 +0200189 if len(items) == 1
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000190 tags = taglist('^' .. base)
Bram Moolenaar20aac6c2018-09-02 21:07:30 +0200191 else
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000192 tags = taglist('^' .. items[0] .. '$')
Bram Moolenaar20aac6c2018-09-02 21:07:30 +0200193 endif
Bram Moolenaareb94e552006-03-11 21:35:11 +0000194
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000195 tags
196 # Remove members, these can't appear without something in front.
197 ->filter((_, v: dict<any>): bool =>
198 v->has_key('kind') ? v.kind != 'm' : true)
199 # Remove static matches in other files.
200 ->filter((_, v: dict<any>): bool =>
201 !v->has_key('static')
202 || !v['static']
203 || bufnr('%') == bufnr(v['filename']))
Bram Moolenaareb94e552006-03-11 21:35:11 +0000204
Bram Moolenaarc4573eb2022-01-31 15:40:56 +0000205 res = res->extend(tags->map((_, v: dict<any>) => Tag2item(v)))
Bram Moolenaara4a08382005-09-09 19:52:02 +0000206 endif
207
208 if len(res) == 0
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000209 # Find the variable in the tags file(s)
210 var diclist: list<dict<any>> = taglist('^' .. items[0] .. '$')
211 # Remove members, these can't appear without something in front.
212 ->filter((_, v: dict<string>): bool =>
213 v->has_key('kind') ? v.kind != 'm' : true)
Bram Moolenaarcaa0fcf2005-09-07 21:21:14 +0000214
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000215 res = []
216 for i: number in len(diclist)->range()
217 # New ctags has the "typeref" field. Patched version has "typename".
218 if diclist[i]->has_key('typename')
Bram Moolenaarc4573eb2022-01-31 15:40:56 +0000219 res = res->extend(diclist[i]['typename']->StructMembers(items[1 :], true))
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000220 elseif diclist[i]->has_key('typeref')
Bram Moolenaarc4573eb2022-01-31 15:40:56 +0000221 res = res->extend(diclist[i]['typeref']->StructMembers(items[1 :], true))
Bram Moolenaara4a08382005-09-09 19:52:02 +0000222 endif
223
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000224 # For a variable use the command, which must be a search pattern that
225 # shows the declaration of the variable.
Bram Moolenaardd2436f2005-09-05 22:14:46 +0000226 if diclist[i]['kind'] == 'v'
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000227 var line: string = diclist[i]['cmd']
228 if line[: 1] == '/^'
229 var col: number = line->charidx(match(line, '\<' .. items[0] .. '\>'))
Bram Moolenaarc4573eb2022-01-31 15:40:56 +0000230 res = res->extend(line[2 : col - 1]->Nextitem(items[1 :], 0, true))
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000231 endif
Bram Moolenaardd2436f2005-09-05 22:14:46 +0000232 endif
233 endfor
234 endif
235
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000236 if len(res) == 0 && items[0]->searchdecl(true) == 0
237 # Found, now figure out the type.
238 # TODO: join previous line if it makes sense
239 var line: string = getline('.')
240 var col: number = charcol('.')
241 res = line[: col - 1]->Nextitem(items[1 :], 0, true)
Bram Moolenaarf75a9632005-09-13 21:20:47 +0000242 endif
243
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000244 # If the last item(s) are [...] they need to be added to the matches.
245 var last: number = len(items) - 1
246 var brackets: string = ''
Bram Moolenaar0e5bd962006-02-04 00:59:56 +0000247 while last >= 0
248 if items[last][0] != '['
249 break
Bram Moolenaarf75a9632005-09-13 21:20:47 +0000250 endif
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000251 brackets = items[last] .. brackets
252 --last
Bram Moolenaar0e5bd962006-02-04 00:59:56 +0000253 endwhile
Bram Moolenaara4a08382005-09-09 19:52:02 +0000254
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000255 return res->map((_, v: dict<any>): dict<string> => Tagline2item(v, brackets))
256enddef
Bram Moolenaardd2436f2005-09-05 22:14:46 +0000257
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000258def GetAddition( # {{{1
Bram Moolenaarc4573eb2022-01-31 15:40:56 +0000259 line: string,
260 match: string,
261 memarg: list<dict<any>>,
262 bracket: bool): string
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000263 # Guess if the item is an array.
264 if bracket && match(line, match .. '\s*\[') > 0
Bram Moolenaar0e5bd962006-02-04 00:59:56 +0000265 return '['
266 endif
267
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000268 # Check if the item has members.
269 if SearchMembers(memarg, [''], false)->len() > 0
270 # If there is a '*' before the name use "->".
271 if match(line, '\*[ \t(]*' .. match .. '\>') > 0
Bram Moolenaar0e5bd962006-02-04 00:59:56 +0000272 return '->'
273 else
274 return '.'
Bram Moolenaar280f1262006-01-30 00:14:18 +0000275 endif
276 endif
Bram Moolenaar0e5bd962006-02-04 00:59:56 +0000277 return ''
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000278enddef
Bram Moolenaar0e5bd962006-02-04 00:59:56 +0000279
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000280def Tag2item(val: dict<any>): dict<any> # {{{1
281# Turn the tag info "val" into an item for completion.
282# "val" is is an item in the list returned by taglist().
283# If it is a variable we may add "." or "->". Don't do it for other types,
284# such as a typedef, by not including the info that GetAddition() uses.
285 var res: dict<any> = {match: val['name']}
Bram Moolenaarf52c7252006-02-10 23:23:57 +0000286
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000287 res['extra'] = Tagcmd2extra(val['cmd'], val['name'], val['filename'])
Bram Moolenaareb94e552006-03-11 21:35:11 +0000288
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000289 var s: string = Dict2info(val)
Bram Moolenaareb94e552006-03-11 21:35:11 +0000290 if s != ''
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000291 res['info'] = s
Bram Moolenaareb94e552006-03-11 21:35:11 +0000292 endif
293
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000294 res['tagline'] = ''
295 if val->has_key('kind')
296 var kind: string = val['kind']
297 res['kind'] = kind
Bram Moolenaareb94e552006-03-11 21:35:11 +0000298 if kind == 'v'
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000299 res['tagline'] = "\t" .. val['cmd']
300 res['dict'] = val
Bram Moolenaareb94e552006-03-11 21:35:11 +0000301 elseif kind == 'f'
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000302 res['match'] = val['name'] .. '('
Bram Moolenaar0e5bd962006-02-04 00:59:56 +0000303 endif
304 endif
Bram Moolenaareb94e552006-03-11 21:35:11 +0000305
306 return res
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000307enddef
Bram Moolenaar0e5bd962006-02-04 00:59:56 +0000308
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000309def Dict2info(dict: dict<any>): string # {{{1
310# Use all the items in dictionary for the "info" entry.
311 var info: string = ''
312 for k: string in dict->keys()->sort()
313 info ..= k .. repeat(' ', 10 - strlen(k))
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000314 if k == 'cmd'
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000315 info ..= dict['cmd']
316 ->matchstr('/^\s*\zs.*\ze$/')
317 ->substitute('\\\(.\)', '\1', 'g')
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000318 else
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000319 var dictk: any = dict[k]
320 if typename(dictk) != 'string'
321 info ..= dictk->string()
322 else
323 info ..= dictk
324 endif
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000325 endif
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000326 info ..= "\n"
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000327 endfor
328 return info
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000329enddef
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000330
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000331def ParseTagline(line: string): dict<any> # {{{1
332# Parse a tag line and return a dictionary with items like taglist()
333 var l: list<string> = split(line, "\t")
334 var d: dict<any>
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000335 if len(l) >= 3
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000336 d['name'] = l[0]
337 d['filename'] = l[1]
338 d['cmd'] = l[2]
339 var n: number = 2
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000340 if l[2] =~ '^/'
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000341 # Find end of cmd, it may contain Tabs.
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000342 while n < len(l) && l[n] !~ '/;"$'
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000343 ++n
344 d['cmd'] ..= ' ' .. l[n]
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000345 endwhile
346 endif
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000347 for i: number in range(n + 1, len(l) - 1)
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000348 if l[i] == 'file:'
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000349 d['static'] = 1
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000350 elseif l[i] !~ ':'
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000351 d['kind'] = l[i]
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000352 else
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000353 d[l[i]->matchstr('[^:]*')] = l[i]->matchstr(':\zs.*')
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000354 endif
355 endfor
356 endif
357
358 return d
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000359enddef
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000360
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000361def Tagline2item(val: dict<any>, brackets: string): dict<string> # {{{1
362# Turn a match item "val" into an item for completion.
363# "val['match']" is the matching item.
364# "val['tagline']" is the tagline in which the last part was found.
365 var line: string = val['tagline']
366 var add: string = GetAddition(line, val['match'], [val], brackets == '')
367 var res: dict<string> = {word: val['match'] .. brackets .. add}
Bram Moolenaareb94e552006-03-11 21:35:11 +0000368
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000369 if val->has_key('info')
370 # Use info from Tag2item().
371 res['info'] = val['info']
Bram Moolenaareb94e552006-03-11 21:35:11 +0000372 else
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000373 # Parse the tag line and add each part to the "info" entry.
374 var s: string = ParseTagline(line)->Dict2info()
Bram Moolenaareb94e552006-03-11 21:35:11 +0000375 if s != ''
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000376 res['info'] = s
Bram Moolenaareb94e552006-03-11 21:35:11 +0000377 endif
378 endif
379
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000380 if val->has_key('kind')
381 res['kind'] = val['kind']
Bram Moolenaareb94e552006-03-11 21:35:11 +0000382 elseif add == '('
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000383 res['kind'] = 'f'
Bram Moolenaareb94e552006-03-11 21:35:11 +0000384 else
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000385 var s: string = line->matchstr('\t\(kind:\)\=\zs\S\ze\(\t\|$\)')
Bram Moolenaareb94e552006-03-11 21:35:11 +0000386 if s != ''
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000387 res['kind'] = s
Bram Moolenaareb94e552006-03-11 21:35:11 +0000388 endif
389 endif
390
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000391 if val->has_key('extra')
392 res['menu'] = val['extra']
Bram Moolenaareb94e552006-03-11 21:35:11 +0000393 return res
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000394 endif
Bram Moolenaarf52c7252006-02-10 23:23:57 +0000395
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000396 # Isolate the command after the tag and filename.
397 var s: string = line->matchstr('[^\t]*\t[^\t]*\t\zs\(/^.*$/\|[^\t]*\)\ze\(;"\t\|\t\|$\)')
Bram Moolenaarf52c7252006-02-10 23:23:57 +0000398 if s != ''
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000399 res['menu'] = s->Tagcmd2extra(val['match'], line->matchstr('[^\t]*\t\zs[^\t]*\ze\t'))
Bram Moolenaarf52c7252006-02-10 23:23:57 +0000400 endif
Bram Moolenaareb94e552006-03-11 21:35:11 +0000401 return res
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000402enddef
Bram Moolenaar280f1262006-01-30 00:14:18 +0000403
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000404def Tagcmd2extra( # {{{1
Bram Moolenaarc4573eb2022-01-31 15:40:56 +0000405 cmd: string,
406 name: string,
407 fname: string): string
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000408# Turn a command from a tag line to something that is useful in the menu
409 var x: string
410 if cmd =~ '^/^'
411 # The command is a search command, useful to see what it is.
412 x = cmd
413 ->matchstr('^/^\s*\zs.*\ze$/')
414 ->substitute('\<' .. name .. '\>', '@@', '')
415 ->substitute('\\\(.\)', '\1', 'g')
416 .. ' - ' .. fname
417 elseif cmd =~ '^\d*$'
418 # The command is a line number, the file name is more useful.
419 x = fname .. ' - ' .. cmd
Bram Moolenaarf52c7252006-02-10 23:23:57 +0000420 else
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000421 # Not recognized, use command and file name.
422 x = cmd .. ' - ' .. fname
Bram Moolenaarf52c7252006-02-10 23:23:57 +0000423 endif
424 return x
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000425enddef
Bram Moolenaar280f1262006-01-30 00:14:18 +0000426
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000427def Nextitem( # {{{1
Bram Moolenaarc4573eb2022-01-31 15:40:56 +0000428 lead: string,
429 items: list<string>,
430 depth: number,
431 all: bool): list<dict<string>>
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000432# Find composing type in "lead" and match items[0] with it.
433# Repeat this recursively for items[1], if it's there.
434# When resolving typedefs "depth" is used to avoid infinite recursion.
435# Return the list of matches.
Bram Moolenaarcaa0fcf2005-09-07 21:21:14 +0000436
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000437 # Use the text up to the variable name and split it in tokens.
438 var tokens: list<string> = split(lead, '\s\+\|\<')
Bram Moolenaarcaa0fcf2005-09-07 21:21:14 +0000439
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000440 # Try to recognize the type of the variable. This is rough guessing...
441 var res: list<dict<string>>
442 for tidx: number in len(tokens)->range()
Bram Moolenaarcaa0fcf2005-09-07 21:21:14 +0000443
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000444 # Skip tokens starting with a non-ID character.
Bram Moolenaar1056d982006-03-09 22:37:52 +0000445 if tokens[tidx] !~ '^\h'
446 continue
447 endif
448
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000449 # Recognize "struct foobar" and "union foobar".
450 # Also do "class foobar" when it's C++ after all (doesn't work very well
451 # though).
452 if (tokens[tidx] == 'struct'
453 || tokens[tidx] == 'union'
454 || tokens[tidx] == 'class')
455 && tidx + 1 < len(tokens)
456 res = StructMembers(tokens[tidx] .. ':' .. tokens[tidx + 1], items, all)
Bram Moolenaardd2436f2005-09-05 22:14:46 +0000457 break
458 endif
Bram Moolenaardd2436f2005-09-05 22:14:46 +0000459
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000460 # TODO: add more reserved words
461 if ['int', 'short', 'char', 'float',
462 'double', 'static', 'unsigned', 'extern']->index(tokens[tidx]) >= 0
Bram Moolenaara4a08382005-09-09 19:52:02 +0000463 continue
464 endif
465
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000466 # Use the tags file to find out if this is a typedef.
467 var diclist: list<dict<any>> = taglist('^' .. tokens[tidx] .. '$')
468 for tagidx: number in len(diclist)->range()
469 var item: dict<any> = diclist[tagidx]
Bram Moolenaareb94e552006-03-11 21:35:11 +0000470
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000471 # New ctags has the "typeref" field. Patched version has "typename".
472 if item->has_key('typeref')
Bram Moolenaarc4573eb2022-01-31 15:40:56 +0000473 res = res->extend(item['typeref']->StructMembers(items, all))
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000474 continue
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000475 endif
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000476 if item->has_key('typename')
Bram Moolenaarc4573eb2022-01-31 15:40:56 +0000477 res = res->extend(item['typename']->StructMembers(items, all))
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000478 continue
Bram Moolenaara4a08382005-09-09 19:52:02 +0000479 endif
480
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000481 # Only handle typedefs here.
Bram Moolenaareb94e552006-03-11 21:35:11 +0000482 if item['kind'] != 't'
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000483 continue
Bram Moolenaareb94e552006-03-11 21:35:11 +0000484 endif
485
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000486 # Skip matches local to another file.
487 if item->has_key('static') && item['static']
488 && bufnr('%') != bufnr(item['filename'])
489 continue
Bram Moolenaarf75a9632005-09-13 21:20:47 +0000490 endif
491
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000492 # For old ctags we recognize "typedef struct aaa" and
493 # "typedef union bbb" in the tags file command.
494 var cmd: string = item['cmd']
495 var ei: number = cmd->charidx(matchend(cmd, 'typedef\s\+'))
Bram Moolenaarf75a9632005-09-13 21:20:47 +0000496 if ei > 1
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000497 var cmdtokens: list<string> = cmd[ei :]->split('\s\+\|\<')
498 if len(cmdtokens) > 1
499 if cmdtokens[0] == 'struct'
500 || cmdtokens[0] == 'union'
501 || cmdtokens[0] == 'class'
502 var name: string = ''
503 # Use the first identifier after the "struct" or "union"
504 for ti: number in (len(cmdtokens) - 1)->range()
505 if cmdtokens[ti] =~ '^\w'
506 name = cmdtokens[ti]
507 break
508 endif
509 endfor
510 if name != ''
Bram Moolenaarc4573eb2022-01-31 15:40:56 +0000511 res = res->extend(StructMembers(cmdtokens[0] .. ':' .. name, items, all))
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000512 endif
513 elseif depth < 10
514 # Could be "typedef other_T some_T".
Bram Moolenaarc4573eb2022-01-31 15:40:56 +0000515 res = res->extend(cmdtokens[0]->Nextitem(items, depth + 1, all))
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000516 endif
517 endif
Bram Moolenaarcaa0fcf2005-09-07 21:21:14 +0000518 endif
519 endfor
Bram Moolenaara4a08382005-09-09 19:52:02 +0000520 if len(res) > 0
Bram Moolenaarcaa0fcf2005-09-07 21:21:14 +0000521 break
Bram Moolenaardd2436f2005-09-05 22:14:46 +0000522 endif
Bram Moolenaara4a08382005-09-09 19:52:02 +0000523 endfor
Bram Moolenaarcaa0fcf2005-09-07 21:21:14 +0000524
Bram Moolenaara4a08382005-09-09 19:52:02 +0000525 return res
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000526enddef
Bram Moolenaara4a08382005-09-09 19:52:02 +0000527
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000528def StructMembers( # {{{1
Bram Moolenaarc4573eb2022-01-31 15:40:56 +0000529 atypename: string,
530 items: list<string>,
531 all: bool): list<dict<string>>
Bram Moolenaara4a08382005-09-09 19:52:02 +0000532
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000533# Search for members of structure "typename" in tags files.
534# Return a list with resulting matches.
535# Each match is a dictionary with "match" and "tagline" entries.
536# When "all" is true find all, otherwise just return 1 if there is any member.
537
538 # Todo: What about local structures?
539 var fnames: string = tagfiles()
540 ->map((_, v: string) => escape(v, ' \#%'))
541 ->join()
Bram Moolenaara4a08382005-09-09 19:52:02 +0000542 if fnames == ''
Bram Moolenaarf75a9632005-09-13 21:20:47 +0000543 return []
Bram Moolenaara4a08382005-09-09 19:52:02 +0000544 endif
545
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000546 var typename: string = atypename
547 var qflist: list<dict<any>>
548 var cached: number = 0
549 var n: string
550 if !all
551 n = '1' # stop at first found match
552 if grepCache->has_key(typename)
553 qflist = grepCache[typename]
554 cached = 1
Bram Moolenaar1056d982006-03-09 22:37:52 +0000555 endif
Bram Moolenaar1f35bf92006-03-07 22:38:47 +0000556 else
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000557 n = ''
Bram Moolenaar1f35bf92006-03-07 22:38:47 +0000558 endif
Bram Moolenaar1056d982006-03-09 22:37:52 +0000559 if !cached
560 while 1
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000561 execute 'silent! keepjumps noautocmd '
562 .. n .. 'vimgrep ' .. '/\t' .. typename .. '\(\t\|$\)/j '
563 .. fnames
Bram Moolenaar1056d982006-03-09 22:37:52 +0000564
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000565 qflist = getqflist()
566 if len(qflist) > 0 || match(typename, '::') < 0
567 break
Bram Moolenaar1056d982006-03-09 22:37:52 +0000568 endif
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000569 # No match for "struct:context::name", remove "context::" and try again.
570 typename = typename->substitute(':[^:]*::', ':', '')
Bram Moolenaar1056d982006-03-09 22:37:52 +0000571 endwhile
572
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000573 if !all
574 # Store the result to be able to use it again later.
575 grepCache[typename] = qflist
Bram Moolenaara4a08382005-09-09 19:52:02 +0000576 endif
Bram Moolenaar1056d982006-03-09 22:37:52 +0000577 endif
Bram Moolenaara4a08382005-09-09 19:52:02 +0000578
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000579 # Skip over [...] items
580 var idx: number = 0
581 var target: string
Bram Moolenaar20aac6c2018-09-02 21:07:30 +0200582 while 1
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000583 if idx >= len(items)
584 target = '' # No further items, matching all members
Bram Moolenaar20aac6c2018-09-02 21:07:30 +0200585 break
586 endif
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000587 if items[idx][0] != '['
588 target = items[idx]
Bram Moolenaar20aac6c2018-09-02 21:07:30 +0200589 break
590 endif
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000591 ++idx
Bram Moolenaar20aac6c2018-09-02 21:07:30 +0200592 endwhile
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000593 # Put matching members in matches[].
594 var matches: list<dict<string>>
595 for l: dict<any> in qflist
596 var memb: string = l['text']->matchstr('[^\t]*')
597 if memb =~ '^' .. target
598 # Skip matches local to another file.
599 if match(l['text'], "\tfile:") < 0
600 || bufnr('%') == l['text']->matchstr('\t\zs[^\t]*')->bufnr()
601 var item: dict<string> = {match: memb, tagline: l['text']}
Bram Moolenaareb94e552006-03-11 21:35:11 +0000602
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000603 # Add the kind of item.
604 var s: string = l['text']->matchstr('\t\(kind:\)\=\zs\S\ze\(\t\|$\)')
605 if s != ''
606 item['kind'] = s
607 if s == 'f'
608 item['match'] = memb .. '('
609 endif
610 endif
Bram Moolenaareb94e552006-03-11 21:35:11 +0000611
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000612 matches->add(item)
Bram Moolenaareb94e552006-03-11 21:35:11 +0000613 endif
Bram Moolenaara4a08382005-09-09 19:52:02 +0000614 endif
Bram Moolenaardd2436f2005-09-05 22:14:46 +0000615 endfor
616
Bram Moolenaarf75a9632005-09-13 21:20:47 +0000617 if len(matches) > 0
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000618 # Skip over next [...] items
619 ++idx
Bram Moolenaar0e5bd962006-02-04 00:59:56 +0000620 while 1
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000621 if idx >= len(items)
622 return matches # No further items, return the result.
Bram Moolenaar0e5bd962006-02-04 00:59:56 +0000623 endif
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000624 if items[idx][0] != '['
625 break
Bram Moolenaar0e5bd962006-02-04 00:59:56 +0000626 endif
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000627 ++idx
Bram Moolenaar0e5bd962006-02-04 00:59:56 +0000628 endwhile
Bram Moolenaarcaa0fcf2005-09-07 21:21:14 +0000629
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000630 # More items following. For each of the possible members find the
631 # matching following members.
632 return SearchMembers(matches, items[idx :], all)
Bram Moolenaarcaa0fcf2005-09-07 21:21:14 +0000633 endif
634
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000635 # Failed to find anything.
Bram Moolenaarcaa0fcf2005-09-07 21:21:14 +0000636 return []
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000637enddef
Bram Moolenaarf75a9632005-09-13 21:20:47 +0000638
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000639def SearchMembers( # {{{1
Bram Moolenaarc4573eb2022-01-31 15:40:56 +0000640 matches: list<dict<any>>,
641 items: list<string>,
642 all: bool): list<dict<string>>
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000643
644# For matching members, find matches for following items.
645# When "all" is true find all, otherwise just return 1 if there is any member.
646 var res: list<dict<string>>
647 for i: number in len(matches)->range()
648 var typename: string = ''
649 var line: string
650 if matches[i]->has_key('dict')
651 if matches[i]['dict']->has_key('typename')
652 typename = matches[i]['dict']['typename']
653 elseif matches[i]['dict']->has_key('typeref')
654 typename = matches[i]['dict']['typeref']
Bram Moolenaar280f1262006-01-30 00:14:18 +0000655 endif
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000656 line = "\t" .. matches[i]['dict']['cmd']
Bram Moolenaar280f1262006-01-30 00:14:18 +0000657 else
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000658 line = matches[i]['tagline']
659 var eb: number = matchend(line, '\ttypename:')
660 var e: number = charidx(line, eb)
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000661 if e < 0
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000662 eb = matchend(line, '\ttyperef:')
663 e = charidx(line, eb)
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000664 endif
Bram Moolenaar280f1262006-01-30 00:14:18 +0000665 if e > 0
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000666 # Use typename field
667 typename = line->matchstr('[^\t]*', eb)
Bram Moolenaar280f1262006-01-30 00:14:18 +0000668 endif
669 endif
Bram Moolenaar1f35bf92006-03-07 22:38:47 +0000670
Bram Moolenaar280f1262006-01-30 00:14:18 +0000671 if typename != ''
Bram Moolenaarc4573eb2022-01-31 15:40:56 +0000672 res = res->extend(StructMembers(typename, items, all))
Bram Moolenaarf75a9632005-09-13 21:20:47 +0000673 else
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000674 # Use the search command (the declaration itself).
675 var sb: number = line->match('\t\zs/^')
676 var s: number = charidx(line, sb)
Bram Moolenaarf75a9632005-09-13 21:20:47 +0000677 if s > 0
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000678 var e: number = line
679 ->charidx(match(line, '\<' .. matches[i]['match'] .. '\>', sb))
680 if e > 0
Bram Moolenaarc4573eb2022-01-31 15:40:56 +0000681 res = res->extend(line[s : e - 1]->Nextitem(items, 0, all))
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000682 endif
Bram Moolenaarf75a9632005-09-13 21:20:47 +0000683 endif
684 endif
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000685 if !all && len(res) > 0
Bram Moolenaar1f35bf92006-03-07 22:38:47 +0000686 break
687 endif
Bram Moolenaarf75a9632005-09-13 21:20:47 +0000688 endfor
689 return res
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000690enddef
691#}}}1
Bram Moolenaarb6b046b2011-12-30 13:11:27 +0100692
Bram Moolenaara4d131d2021-12-27 21:33:07 +0000693# vim: noet sw=2 sts=2