summaryrefslogtreecommitdiffstats
path: root/runtime/autoload/ccomplete.vim
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/autoload/ccomplete.vim')
-rw-r--r--runtime/autoload/ccomplete.vim693
1 files changed, 693 insertions, 0 deletions
diff --git a/runtime/autoload/ccomplete.vim b/runtime/autoload/ccomplete.vim
new file mode 100644
index 0000000..3bddba7
--- /dev/null
+++ b/runtime/autoload/ccomplete.vim
@@ -0,0 +1,693 @@
+vim9script noclear
+
+# Vim completion script
+# Language: C
+# Maintainer: Bram Moolenaar <Bram@vim.org>
+# Rewritten in Vim9 script by github user lacygoill
+# Last Change: 2022 Jan 31
+
+var prepended: string
+var grepCache: dict<list<dict<any>>>
+
+# This function is used for the 'omnifunc' option.
+export def Complete(findstart: bool, abase: string): any # {{{1
+ if findstart
+ # Locate the start of the item, including ".", "->" and "[...]".
+ var line: string = getline('.')
+ var start: number = charcol('.') - 1
+ var lastword: number = -1
+ while start > 0
+ if line[start - 1] =~ '\w'
+ --start
+ elseif line[start - 1] =~ '\.'
+ if lastword == -1
+ lastword = start
+ endif
+ --start
+ elseif start > 1 && line[start - 2] == '-'
+ && line[start - 1] == '>'
+ if lastword == -1
+ lastword = start
+ endif
+ start -= 2
+ elseif line[start - 1] == ']'
+ # Skip over [...].
+ var n: number = 0
+ --start
+ while start > 0
+ --start
+ if line[start] == '['
+ if n == 0
+ break
+ endif
+ --n
+ elseif line[start] == ']' # nested []
+ ++n
+ endif
+ endwhile
+ else
+ break
+ endif
+ endwhile
+
+ # Return the column of the last word, which is going to be changed.
+ # Remember the text that comes before it in prepended.
+ if lastword == -1
+ prepended = ''
+ return byteidx(line, start)
+ endif
+ prepended = line[start : lastword - 1]
+ return byteidx(line, lastword)
+ endif
+
+ # Return list of matches.
+
+ var base: string = prepended .. abase
+
+ # Don't do anything for an empty base, would result in all the tags in the
+ # tags file.
+ if base == ''
+ return []
+ endif
+
+ # init cache for vimgrep to empty
+ grepCache = {}
+
+ # Split item in words, keep empty word after "." or "->".
+ # "aa" -> ['aa'], "aa." -> ['aa', ''], "aa.bb" -> ['aa', 'bb'], etc.
+ # We can't use split, because we need to skip nested [...].
+ # "aa[...]" -> ['aa', '[...]'], "aa.bb[...]" -> ['aa', 'bb', '[...]'], etc.
+ var items: list<string>
+ var s: number = 0
+ var arrays: number = 0
+ while 1
+ var e: number = base->charidx(match(base, '\.\|->\|\[', s))
+ if e < 0
+ if s == 0 || base[s - 1] != ']'
+ items->add(base[s :])
+ endif
+ break
+ endif
+ if s == 0 || base[s - 1] != ']'
+ items->add(base[s : e - 1])
+ endif
+ if base[e] == '.'
+ # skip over '.'
+ s = e + 1
+ elseif base[e] == '-'
+ # skip over '->'
+ s = e + 2
+ else
+ # Skip over [...].
+ var n: number = 0
+ s = e
+ ++e
+ while e < strcharlen(base)
+ if base[e] == ']'
+ if n == 0
+ break
+ endif
+ --n
+ elseif base[e] == '[' # nested [...]
+ ++n
+ endif
+ ++e
+ endwhile
+ ++e
+ items->add(base[s : e - 1])
+ ++arrays
+ s = e
+ endif
+ endwhile
+
+ # Find the variable items[0].
+ # 1. in current function (like with "gd")
+ # 2. in tags file(s) (like with ":tag")
+ # 3. in current file (like with "gD")
+ var res: list<dict<any>>
+ if items[0]->searchdecl(false, true) == 0
+ # Found, now figure out the type.
+ # TODO: join previous line if it makes sense
+ var line: string = getline('.')
+ var col: number = charcol('.')
+ if line[: col - 1]->stridx(';') >= 0
+ # Handle multiple declarations on the same line.
+ var col2: number = col - 1
+ while line[col2] != ';'
+ --col2
+ endwhile
+ line = line[col2 + 1 :]
+ col -= col2
+ endif
+ if line[: col - 1]->stridx(',') >= 0
+ # Handle multiple declarations on the same line in a function
+ # declaration.
+ var col2: number = col - 1
+ while line[col2] != ','
+ --col2
+ endwhile
+ if line[col2 + 1 : col - 1] =~ ' *[^ ][^ ]* *[^ ]'
+ line = line[col2 + 1 :]
+ col -= col2
+ endif
+ endif
+ if len(items) == 1
+ # Completing one word and it's a local variable: May add '[', '.' or
+ # '->'.
+ var match: string = items[0]
+ var kind: string = 'v'
+ if match(line, '\<' .. match .. '\s*\[') > 0
+ match ..= '['
+ else
+ res = line[: col - 1]->Nextitem([''], 0, true)
+ if len(res) > 0
+ # There are members, thus add "." or "->".
+ if match(line, '\*[ \t(]*' .. match .. '\>') > 0
+ match ..= '->'
+ else
+ match ..= '.'
+ endif
+ endif
+ endif
+ res = [{match: match, tagline: '', kind: kind, info: line}]
+ elseif len(items) == arrays + 1
+ # Completing one word and it's a local array variable: build tagline
+ # from declaration line
+ var match: string = items[0]
+ var kind: string = 'v'
+ var tagline: string = "\t/^" .. line .. '$/'
+ res = [{match: match, tagline: tagline, kind: kind, info: line}]
+ else
+ # Completing "var.", "var.something", etc.
+ res = line[: col - 1]->Nextitem(items[1 :], 0, true)
+ endif
+ endif
+
+ if len(items) == 1 || len(items) == arrays + 1
+ # Only one part, no "." or "->": complete from tags file.
+ var tags: list<dict<any>>
+ if len(items) == 1
+ tags = taglist('^' .. base)
+ else
+ tags = taglist('^' .. items[0] .. '$')
+ endif
+
+ tags
+ # Remove members, these can't appear without something in front.
+ ->filter((_, v: dict<any>): bool =>
+ v->has_key('kind') ? v.kind != 'm' : true)
+ # Remove static matches in other files.
+ ->filter((_, v: dict<any>): bool =>
+ !v->has_key('static')
+ || !v['static']
+ || bufnr('%') == bufnr(v['filename']))
+
+ res = res->extend(tags->map((_, v: dict<any>) => Tag2item(v)))
+ endif
+
+ if len(res) == 0
+ # Find the variable in the tags file(s)
+ var diclist: list<dict<any>> = taglist('^' .. items[0] .. '$')
+ # Remove members, these can't appear without something in front.
+ ->filter((_, v: dict<string>): bool =>
+ v->has_key('kind') ? v.kind != 'm' : true)
+
+ res = []
+ for i: number in len(diclist)->range()
+ # New ctags has the "typeref" field. Patched version has "typename".
+ if diclist[i]->has_key('typename')
+ res = res->extend(diclist[i]['typename']->StructMembers(items[1 :], true))
+ elseif diclist[i]->has_key('typeref')
+ res = res->extend(diclist[i]['typeref']->StructMembers(items[1 :], true))
+ endif
+
+ # For a variable use the command, which must be a search pattern that
+ # shows the declaration of the variable.
+ if diclist[i]['kind'] == 'v'
+ var line: string = diclist[i]['cmd']
+ if line[: 1] == '/^'
+ var col: number = line->charidx(match(line, '\<' .. items[0] .. '\>'))
+ res = res->extend(line[2 : col - 1]->Nextitem(items[1 :], 0, true))
+ endif
+ endif
+ endfor
+ endif
+
+ if len(res) == 0 && items[0]->searchdecl(true) == 0
+ # Found, now figure out the type.
+ # TODO: join previous line if it makes sense
+ var line: string = getline('.')
+ var col: number = charcol('.')
+ res = line[: col - 1]->Nextitem(items[1 :], 0, true)
+ endif
+
+ # If the last item(s) are [...] they need to be added to the matches.
+ var last: number = len(items) - 1
+ var brackets: string = ''
+ while last >= 0
+ if items[last][0] != '['
+ break
+ endif
+ brackets = items[last] .. brackets
+ --last
+ endwhile
+
+ return res->map((_, v: dict<any>): dict<string> => Tagline2item(v, brackets))
+enddef
+
+def GetAddition( # {{{1
+ line: string,
+ match: string,
+ memarg: list<dict<any>>,
+ bracket: bool): string
+ # Guess if the item is an array.
+ if bracket && match(line, match .. '\s*\[') > 0
+ return '['
+ endif
+
+ # Check if the item has members.
+ if SearchMembers(memarg, [''], false)->len() > 0
+ # If there is a '*' before the name use "->".
+ if match(line, '\*[ \t(]*' .. match .. '\>') > 0
+ return '->'
+ else
+ return '.'
+ endif
+ endif
+ return ''
+enddef
+
+def Tag2item(val: dict<any>): dict<any> # {{{1
+# Turn the tag info "val" into an item for completion.
+# "val" is is an item in the list returned by taglist().
+# If it is a variable we may add "." or "->". Don't do it for other types,
+# such as a typedef, by not including the info that GetAddition() uses.
+ var res: dict<any> = {match: val['name']}
+
+ res['extra'] = Tagcmd2extra(val['cmd'], val['name'], val['filename'])
+
+ var s: string = Dict2info(val)
+ if s != ''
+ res['info'] = s
+ endif
+
+ res['tagline'] = ''
+ if val->has_key('kind')
+ var kind: string = val['kind']
+ res['kind'] = kind
+ if kind == 'v'
+ res['tagline'] = "\t" .. val['cmd']
+ res['dict'] = val
+ elseif kind == 'f'
+ res['match'] = val['name'] .. '('
+ endif
+ endif
+
+ return res
+enddef
+
+def Dict2info(dict: dict<any>): string # {{{1
+# Use all the items in dictionary for the "info" entry.
+ var info: string = ''
+ for k: string in dict->keys()->sort()
+ info ..= k .. repeat(' ', 10 - strlen(k))
+ if k == 'cmd'
+ info ..= dict['cmd']
+ ->matchstr('/^\s*\zs.*\ze$/')
+ ->substitute('\\\(.\)', '\1', 'g')
+ else
+ var dictk: any = dict[k]
+ if typename(dictk) != 'string'
+ info ..= dictk->string()
+ else
+ info ..= dictk
+ endif
+ endif
+ info ..= "\n"
+ endfor
+ return info
+enddef
+
+def ParseTagline(line: string): dict<any> # {{{1
+# Parse a tag line and return a dictionary with items like taglist()
+ var l: list<string> = split(line, "\t")
+ var d: dict<any>
+ if len(l) >= 3
+ d['name'] = l[0]
+ d['filename'] = l[1]
+ d['cmd'] = l[2]
+ var n: number = 2
+ if l[2] =~ '^/'
+ # Find end of cmd, it may contain Tabs.
+ while n < len(l) && l[n] !~ '/;"$'
+ ++n
+ d['cmd'] ..= ' ' .. l[n]
+ endwhile
+ endif
+ for i: number in range(n + 1, len(l) - 1)
+ if l[i] == 'file:'
+ d['static'] = 1
+ elseif l[i] !~ ':'
+ d['kind'] = l[i]
+ else
+ d[l[i]->matchstr('[^:]*')] = l[i]->matchstr(':\zs.*')
+ endif
+ endfor
+ endif
+
+ return d
+enddef
+
+def Tagline2item(val: dict<any>, brackets: string): dict<string> # {{{1
+# Turn a match item "val" into an item for completion.
+# "val['match']" is the matching item.
+# "val['tagline']" is the tagline in which the last part was found.
+ var line: string = val['tagline']
+ var add: string = GetAddition(line, val['match'], [val], brackets == '')
+ var res: dict<string> = {word: val['match'] .. brackets .. add}
+
+ if val->has_key('info')
+ # Use info from Tag2item().
+ res['info'] = val['info']
+ else
+ # Parse the tag line and add each part to the "info" entry.
+ var s: string = ParseTagline(line)->Dict2info()
+ if s != ''
+ res['info'] = s
+ endif
+ endif
+
+ if val->has_key('kind')
+ res['kind'] = val['kind']
+ elseif add == '('
+ res['kind'] = 'f'
+ else
+ var s: string = line->matchstr('\t\(kind:\)\=\zs\S\ze\(\t\|$\)')
+ if s != ''
+ res['kind'] = s
+ endif
+ endif
+
+ if val->has_key('extra')
+ res['menu'] = val['extra']
+ return res
+ endif
+
+ # Isolate the command after the tag and filename.
+ var s: string = line->matchstr('[^\t]*\t[^\t]*\t\zs\(/^.*$/\|[^\t]*\)\ze\(;"\t\|\t\|$\)')
+ if s != ''
+ res['menu'] = s->Tagcmd2extra(val['match'], line->matchstr('[^\t]*\t\zs[^\t]*\ze\t'))
+ endif
+ return res
+enddef
+
+def Tagcmd2extra( # {{{1
+ cmd: string,
+ name: string,
+ fname: string): string
+# Turn a command from a tag line to something that is useful in the menu
+ var x: string
+ if cmd =~ '^/^'
+ # The command is a search command, useful to see what it is.
+ x = cmd
+ ->matchstr('^/^\s*\zs.*\ze$/')
+ ->substitute('\<' .. name .. '\>', '@@', '')
+ ->substitute('\\\(.\)', '\1', 'g')
+ .. ' - ' .. fname
+ elseif cmd =~ '^\d*$'
+ # The command is a line number, the file name is more useful.
+ x = fname .. ' - ' .. cmd
+ else
+ # Not recognized, use command and file name.
+ x = cmd .. ' - ' .. fname
+ endif
+ return x
+enddef
+
+def Nextitem( # {{{1
+ lead: string,
+ items: list<string>,
+ depth: number,
+ all: bool): list<dict<string>>
+# Find composing type in "lead" and match items[0] with it.
+# Repeat this recursively for items[1], if it's there.
+# When resolving typedefs "depth" is used to avoid infinite recursion.
+# Return the list of matches.
+
+ # Use the text up to the variable name and split it in tokens.
+ var tokens: list<string> = split(lead, '\s\+\|\<')
+
+ # Try to recognize the type of the variable. This is rough guessing...
+ var res: list<dict<string>>
+ for tidx: number in len(tokens)->range()
+
+ # Skip tokens starting with a non-ID character.
+ if tokens[tidx] !~ '^\h'
+ continue
+ endif
+
+ # Recognize "struct foobar" and "union foobar".
+ # Also do "class foobar" when it's C++ after all (doesn't work very well
+ # though).
+ if (tokens[tidx] == 'struct'
+ || tokens[tidx] == 'union'
+ || tokens[tidx] == 'class')
+ && tidx + 1 < len(tokens)
+ res = StructMembers(tokens[tidx] .. ':' .. tokens[tidx + 1], items, all)
+ break
+ endif
+
+ # TODO: add more reserved words
+ if ['int', 'short', 'char', 'float',
+ 'double', 'static', 'unsigned', 'extern']->index(tokens[tidx]) >= 0
+ continue
+ endif
+
+ # Use the tags file to find out if this is a typedef.
+ var diclist: list<dict<any>> = taglist('^' .. tokens[tidx] .. '$')
+ for tagidx: number in len(diclist)->range()
+ var item: dict<any> = diclist[tagidx]
+
+ # New ctags has the "typeref" field. Patched version has "typename".
+ if item->has_key('typeref')
+ res = res->extend(item['typeref']->StructMembers(items, all))
+ continue
+ endif
+ if item->has_key('typename')
+ res = res->extend(item['typename']->StructMembers(items, all))
+ continue
+ endif
+
+ # Only handle typedefs here.
+ if item['kind'] != 't'
+ continue
+ endif
+
+ # Skip matches local to another file.
+ if item->has_key('static') && item['static']
+ && bufnr('%') != bufnr(item['filename'])
+ continue
+ endif
+
+ # For old ctags we recognize "typedef struct aaa" and
+ # "typedef union bbb" in the tags file command.
+ var cmd: string = item['cmd']
+ var ei: number = cmd->charidx(matchend(cmd, 'typedef\s\+'))
+ if ei > 1
+ var cmdtokens: list<string> = cmd[ei :]->split('\s\+\|\<')
+ if len(cmdtokens) > 1
+ if cmdtokens[0] == 'struct'
+ || cmdtokens[0] == 'union'
+ || cmdtokens[0] == 'class'
+ var name: string = ''
+ # Use the first identifier after the "struct" or "union"
+ for ti: number in (len(cmdtokens) - 1)->range()
+ if cmdtokens[ti] =~ '^\w'
+ name = cmdtokens[ti]
+ break
+ endif
+ endfor
+ if name != ''
+ res = res->extend(StructMembers(cmdtokens[0] .. ':' .. name, items, all))
+ endif
+ elseif depth < 10
+ # Could be "typedef other_T some_T".
+ res = res->extend(cmdtokens[0]->Nextitem(items, depth + 1, all))
+ endif
+ endif
+ endif
+ endfor
+ if len(res) > 0
+ break
+ endif
+ endfor
+
+ return res
+enddef
+
+def StructMembers( # {{{1
+ atypename: string,
+ items: list<string>,
+ all: bool): list<dict<string>>
+
+# Search for members of structure "typename" in tags files.
+# Return a list with resulting matches.
+# Each match is a dictionary with "match" and "tagline" entries.
+# When "all" is true find all, otherwise just return 1 if there is any member.
+
+ # Todo: What about local structures?
+ var fnames: string = tagfiles()
+ ->map((_, v: string) => escape(v, ' \#%'))
+ ->join()
+ if fnames == ''
+ return []
+ endif
+
+ var typename: string = atypename
+ var qflist: list<dict<any>>
+ var cached: number = 0
+ var n: string
+ if !all
+ n = '1' # stop at first found match
+ if grepCache->has_key(typename)
+ qflist = grepCache[typename]
+ cached = 1
+ endif
+ else
+ n = ''
+ endif
+ if !cached
+ while 1
+ execute 'silent! keepjumps noautocmd '
+ .. n .. 'vimgrep ' .. '/\t' .. typename .. '\(\t\|$\)/j '
+ .. fnames
+
+ qflist = getqflist()
+ if len(qflist) > 0 || match(typename, '::') < 0
+ break
+ endif
+ # No match for "struct:context::name", remove "context::" and try again.
+ typename = typename->substitute(':[^:]*::', ':', '')
+ endwhile
+
+ if !all
+ # Store the result to be able to use it again later.
+ grepCache[typename] = qflist
+ endif
+ endif
+
+ # Skip over [...] items
+ var idx: number = 0
+ var target: string
+ while 1
+ if idx >= len(items)
+ target = '' # No further items, matching all members
+ break
+ endif
+ if items[idx][0] != '['
+ target = items[idx]
+ break
+ endif
+ ++idx
+ endwhile
+ # Put matching members in matches[].
+ var matches: list<dict<string>>
+ for l: dict<any> in qflist
+ var memb: string = l['text']->matchstr('[^\t]*')
+ if memb =~ '^' .. target
+ # Skip matches local to another file.
+ if match(l['text'], "\tfile:") < 0
+ || bufnr('%') == l['text']->matchstr('\t\zs[^\t]*')->bufnr()
+ var item: dict<string> = {match: memb, tagline: l['text']}
+
+ # Add the kind of item.
+ var s: string = l['text']->matchstr('\t\(kind:\)\=\zs\S\ze\(\t\|$\)')
+ if s != ''
+ item['kind'] = s
+ if s == 'f'
+ item['match'] = memb .. '('
+ endif
+ endif
+
+ matches->add(item)
+ endif
+ endif
+ endfor
+
+ if len(matches) > 0
+ # Skip over next [...] items
+ ++idx
+ while 1
+ if idx >= len(items)
+ return matches # No further items, return the result.
+ endif
+ if items[idx][0] != '['
+ break
+ endif
+ ++idx
+ endwhile
+
+ # More items following. For each of the possible members find the
+ # matching following members.
+ return SearchMembers(matches, items[idx :], all)
+ endif
+
+ # Failed to find anything.
+ return []
+enddef
+
+def SearchMembers( # {{{1
+ matches: list<dict<any>>,
+ items: list<string>,
+ all: bool): list<dict<string>>
+
+# For matching members, find matches for following items.
+# When "all" is true find all, otherwise just return 1 if there is any member.
+ var res: list<dict<string>>
+ for i: number in len(matches)->range()
+ var typename: string = ''
+ var line: string
+ if matches[i]->has_key('dict')
+ if matches[i]['dict']->has_key('typename')
+ typename = matches[i]['dict']['typename']
+ elseif matches[i]['dict']->has_key('typeref')
+ typename = matches[i]['dict']['typeref']
+ endif
+ line = "\t" .. matches[i]['dict']['cmd']
+ else
+ line = matches[i]['tagline']
+ var eb: number = matchend(line, '\ttypename:')
+ var e: number = charidx(line, eb)
+ if e < 0
+ eb = matchend(line, '\ttyperef:')
+ e = charidx(line, eb)
+ endif
+ if e > 0
+ # Use typename field
+ typename = line->matchstr('[^\t]*', eb)
+ endif
+ endif
+
+ if typename != ''
+ res = res->extend(StructMembers(typename, items, all))
+ else
+ # Use the search command (the declaration itself).
+ var sb: number = line->match('\t\zs/^')
+ var s: number = charidx(line, sb)
+ if s > 0
+ var e: number = line
+ ->charidx(match(line, '\<' .. matches[i]['match'] .. '\>', sb))
+ if e > 0
+ res = res->extend(line[s : e - 1]->Nextitem(items, 0, all))
+ endif
+ endif
+ endif
+ if !all && len(res) > 0
+ break
+ endif
+ endfor
+ return res
+enddef
+#}}}1
+
+# vim: noet sw=2 sts=2