rc/vim/autoload/javacomplete.vim

2933 lines
87 KiB
VimL
Raw Normal View History

" Vim completion script - hit 80% complete tasks
" Version: 0.77.1.2
" Language: Java
" Maintainer: cheng fang <fangread@yahoo.com.cn>
" Last Change: 2011-01-30
" Copyright: Copyright (C) 2006-2007 cheng fang. All rights reserved.
" License: Vim License (see vim's :help license)
" constants {{{1
" input context type
let s:CONTEXT_AFTER_DOT = 1
let s:CONTEXT_METHOD_PARAM = 2
let s:CONTEXT_IMPORT = 3
let s:CONTEXT_IMPORT_STATIC = 4
let s:CONTEXT_PACKAGE_DECL = 6
let s:CONTEXT_NEED_TYPE = 7
let s:CONTEXT_OTHER = 0
let s:ARRAY_TYPE_MEMBERS = [
\ {'kind': 'm', 'word': 'clone(', 'abbr': 'clone()', 'menu': 'Object clone()', },
\ {'kind': 'm', 'word': 'equals(', 'abbr': 'equals()', 'menu': 'boolean equals(Object)', },
\ {'kind': 'm', 'word': 'getClass(', 'abbr': 'getClass()', 'menu': 'Class Object.getClass()', },
\ {'kind': 'm', 'word': 'hashCode(', 'abbr': 'hashCode()', 'menu': 'int hashCode()', },
\ {'kind': 'f', 'word': 'length', 'menu': 'int'},
\ {'kind': 'm', 'word': 'notify(', 'abbr': 'notify()', 'menu': 'void Object.notify()', },
\ {'kind': 'm', 'word': 'notifyAll(', 'abbr': 'notifyAll()', 'menu': 'void Object.notifyAll()', },
\ {'kind': 'm', 'word': 'toString(', 'abbr': 'toString()', 'menu': 'String toString()', },
\ {'kind': 'm', 'word': 'wait(', 'abbr': 'wait()', 'menu': 'void Object.wait() throws InterruptedException', },
\ {'kind': 'm', 'dup': 1, 'word': 'wait(', 'abbr': 'wait()', 'menu': 'void Object.wait(long timeout) throws InterruptedException', },
\ {'kind': 'm', 'dup': 1, 'word': 'wait(', 'abbr': 'wait()', 'menu': 'void Object.wait(long timeout, int nanos) throws InterruptedException', }]
let s:ARRAY_TYPE_INFO = {'tag': 'CLASSDEF', 'name': '[', 'ctors': [],
\ 'fields': [{'n': 'length', 'm': '1', 't': 'int'}],
\ 'methods':[
\ {'n': 'clone', 'm': '1', 'r': 'Object', 'p': [], 'd': 'Object clone()'},
\ {'n': 'equals', 'm': '1', 'r': 'boolean', 'p': ['Object'], 'd': 'boolean Object.equals(Object obj)'},
\ {'n': 'getClass', 'm': '100010001', 'r': 'Class', 'p': [], 'd': 'Class Object.getClass()'},
\ {'n': 'hashCode', 'm': '100000001', 'r': 'int', 'p': [], 'd': 'int Object.hashCode()'},
\ {'n': 'notify', 'm': '100010001', 'r': 'void', 'p': [], 'd': 'void Object.notify()'},
\ {'n': 'notifyAll','m': '100010001', 'r': 'void', 'p': [], 'd': 'void Object.notifyAll()'},
\ {'n': 'toString', 'm': '1', 'r': 'String', 'p': [], 'd': 'String Object.toString()'},
\ {'n': 'wait', 'm': '10001', 'r': 'void', 'p': [], 'd': 'void Object.wait() throws InterruptedException'},
\ {'n': 'wait', 'm': '100010001', 'r': 'void', 'p': ['long'], 'd': 'void Object.wait(long timeout) throws InterruptedException'},
\ {'n': 'wait', 'm': '10001', 'r': 'void', 'p': ['long','int'], 'd': 'void Object.wait(long timeout, int nanos) throws InterruptedException'},
\ ]}
let s:PRIMITIVE_TYPE_INFO = {'tag': 'CLASSDEF', 'name': '!', 'fields': [{'n': 'class','m': '1','t': 'Class'}]}
let s:JSP_BUILTIN_OBJECTS = {'session': 'javax.servlet.http.HttpSession',
\ 'request': 'javax.servlet.http.HttpServletRequest',
\ 'response': 'javax.servlet.http.HttpServletResponse',
\ 'pageContext': 'javax.servlet.jsp.PageContext',
\ 'application': 'javax.servlet.ServletContext',
\ 'config': 'javax.servlet.ServletConfig',
\ 'out': 'javax.servlet.jsp.JspWriter',
\ 'page': 'javax.servlet.jsp.HttpJspPage', }
let s:PRIMITIVE_TYPES = ['boolean', 'byte', 'char', 'int', 'short', 'long', 'float', 'double']
let s:KEYWORDS_MODS = ['public', 'private', 'protected', 'static', 'final', 'synchronized', 'volatile', 'transient', 'native', 'strictfp', 'abstract']
let s:KEYWORDS_TYPE = ['class', 'interface', 'enum']
let s:KEYWORDS = s:PRIMITIVE_TYPES + s:KEYWORDS_MODS + s:KEYWORDS_TYPE + ['super', 'this', 'void'] + ['assert', 'break', 'case', 'catch', 'const', 'continue', 'default', 'do', 'else', 'extends', 'finally', 'for', 'goto', 'if', 'implements', 'import', 'instanceof', 'interface', 'new', 'package', 'return', 'switch', 'throw', 'throws', 'try', 'while', 'true', 'false', 'null']
let s:PATH_SEP = ':'
let s:FILE_SEP = '/'
if has("win32") || has("win64") || has("win16") || has("dos32") || has("dos16")
let s:PATH_SEP = ';'
let s:FILE_SEP = '\'
endif
let s:RE_BRACKETS = '\%(\s*\[\s*\]\)'
let s:RE_IDENTIFIER = '[a-zA-Z_$][a-zA-Z0-9_$]*'
let s:RE_QUALID = s:RE_IDENTIFIER. '\%(\s*\.\s*' .s:RE_IDENTIFIER. '\)*'
let s:RE_REFERENCE_TYPE = s:RE_QUALID . s:RE_BRACKETS . '*'
let s:RE_TYPE = s:RE_REFERENCE_TYPE
let s:RE_TYPE_ARGUMENT = '\%(?\s\+\%(extends\|super\)\s\+\)\=' . s:RE_TYPE
let s:RE_TYPE_ARGUMENTS = '<' . s:RE_TYPE_ARGUMENT . '\%(\s*,\s*' . s:RE_TYPE_ARGUMENT . '\)*>'
let s:RE_TYPE_WITH_ARGUMENTS_I = s:RE_IDENTIFIER . '\s*' . s:RE_TYPE_ARGUMENTS
let s:RE_TYPE_WITH_ARGUMENTS = s:RE_TYPE_WITH_ARGUMENTS_I . '\%(\s*' . s:RE_TYPE_WITH_ARGUMENTS_I . '\)*'
let s:RE_TYPE_MODS = '\%(public\|protected\|private\|abstract\|static\|final\|strictfp\)'
let s:RE_TYPE_DECL_HEAD = '\(class\|interface\|enum\)[ \t\n\r ]\+'
let s:RE_TYPE_DECL = '\<\C\(\%(' .s:RE_TYPE_MODS. '\s\+\)*\)' .s:RE_TYPE_DECL_HEAD. '\(' .s:RE_IDENTIFIER. '\)[< \t\n\r ]'
let s:RE_ARRAY_TYPE = '^\s*\(' .s:RE_QUALID . '\)\(' . s:RE_BRACKETS . '\+\)\s*$'
let s:RE_SELECT_OR_ACCESS = '^\s*\(' . s:RE_IDENTIFIER . '\)\s*\(\[.*\]\)\=\s*$'
let s:RE_ARRAY_ACCESS = '^\s*\(' . s:RE_IDENTIFIER . '\)\s*\(\[.*\]\)\+\s*$'
let s:RE_CASTING = '^\s*(\(' .s:RE_QUALID. '\))\s*\(' . s:RE_IDENTIFIER . '\)\>'
let s:RE_KEYWORDS = '\<\%(' . join(s:KEYWORDS, '\|') . '\)\>'
" local variables {{{1
let b:context_type = s:CONTEXT_OTHER
"let b:statement = '' " statement before cursor
let b:dotexpr = '' " expression ends with '.'
let b:incomplete = '' " incomplete word: 1. dotexpr.method(|) 2. new classname(|) 3. dotexpr.ab|, 4. ja|, 5. method(|
let b:errormsg = ''
" script variables {{{1
let s:cache = {} " FQN -> member list, e.g. {'java.lang.StringBuffer': classinfo, 'java.util': packageinfo, '/dir/TopLevelClass.java': compilationUnit}
let s:files = {} " srouce file path -> properties, e.g. {filekey: {'unit': compilationUnit, 'changedtick': tick, }}
let s:history = {} "
" This function is used for the 'omnifunc' option. {{{1
function! javacomplete#Complete(findstart, base)
if a:findstart
let s:et_whole = reltime()
let start = col('.') - 1
let s:log = []
" reset enviroment
let b:dotexpr = ''
let b:incomplete = ''
let b:context_type = s:CONTEXT_OTHER
let statement = s:GetStatement()
call s:WatchVariant('statement: "' . statement . '"')
if statement =~ '[.0-9A-Za-z_]\s*$'
let valid = 1
if statement =~ '\.\s*$'
let valid = statement =~ '[")0-9A-Za-z_\]]\s*\.\s*$' && statement !~ '\<\H\w\+\.\s*$' && statement !~ '\<\(abstract\|assert\|break\|case\|catch\|const\|continue\|default\|do\|else\|enum\|extends\|final\|finally\|for\|goto\|if\|implements\|import\|instanceof\|interface\|native\|new\|package\|private\|protected\|public\|return\|static\|strictfp\|switch\|synchronized\|throw\|throws\|transient\|try\|volatile\|while\|true\|false\|null\)\.\s*$'
endif
if !valid
return -1
endif
let b:context_type = s:CONTEXT_AFTER_DOT
" import or package declaration
if statement =~# '^\s*\(import\|package\)\s\+'
let statement = substitute(statement, '\s\+\.', '.', 'g')
let statement = substitute(statement, '\.\s\+', '.', 'g')
if statement =~ '^\s*import\s\+'
let b:context_type = statement =~# '\<static\s\+' ? s:CONTEXT_IMPORT_STATIC : s:CONTEXT_IMPORT
let b:dotexpr = substitute(statement, '^\s*import\s\+\(static\s\+\)\?', '', '')
else
let b:context_type = s:CONTEXT_PACKAGE_DECL
let b:dotexpr = substitute(statement, '\s*package\s\+', '', '')
endif
" String literal
elseif statement =~ '"\s*\.\s*$'
let b:dotexpr = substitute(statement, '\s*\.\s*$', '\.', '')
return start - strlen(b:incomplete)
else
" type declaration NOTE: not supported generic yet.
let idx_type = matchend(statement, '^\s*' . s:RE_TYPE_DECL)
if idx_type != -1
let b:dotexpr = strpart(statement, idx_type)
" return if not after extends or implements
if b:dotexpr !~ '^\(extends\|implements\)\s\+'
return -1
endif
let b:context_type = s:CONTEXT_NEED_TYPE
endif
let b:dotexpr = s:ExtractCleanExpr(statement)
endif
" all cases: " java.ut|" or " java.util.|" or "ja|"
let b:incomplete = strpart(b:dotexpr, strridx(b:dotexpr, '.')+1)
let b:dotexpr = strpart(b:dotexpr, 0, strridx(b:dotexpr, '.')+1)
return start - strlen(b:incomplete)
" method parameters, treat methodname or 'new' as an incomplete word
elseif statement =~ '(\s*$'
" TODO: Need to exclude method declaration?
let b:context_type = s:CONTEXT_METHOD_PARAM
let pos = strridx(statement, '(')
let s:padding = strpart(statement, pos+1)
let start = start - (len(statement) - pos)
let statement = substitute(statement, '\s*(\s*$', '', '')
" new ClassName?
let str = matchstr(statement, '\<new\s\+' . s:RE_QUALID . '$')
if str != ''
let str = substitute(str, '^new\s\+', '', '')
if !s:IsKeyword(str)
let b:incomplete = '+'
let b:dotexpr = str
return start - len(b:dotexpr)
endif
" normal method invocations
else
let pos = match(statement, '\s*' . s:RE_IDENTIFIER . '$')
" case: "method(|)", "this(|)", "super(|)"
if pos == 0
let statement = substitute(statement, '^\s*', '', '')
" treat "this" or "super" as a type name.
if statement == 'this' || statement == 'super'
let b:dotexpr = statement
let b:incomplete = '+'
return start - len(b:dotexpr)
elseif !s:IsKeyword(statement)
let b:incomplete = statement
return start - strlen(b:incomplete)
endif
" case: "expr.method(|)"
elseif statement[pos-1] == '.' && !s:IsKeyword(strpart(statement, pos))
let b:dotexpr = s:ExtractCleanExpr(strpart(statement, 0, pos))
let b:incomplete = strpart(statement, pos)
return start - strlen(b:incomplete)
endif
endif
endif
return -1
endif
" Return list of matches.
call s:WatchVariant('b:context_type: "' . b:context_type . '" b:incomplete: "' . b:incomplete . '" b:dotexpr: "' . b:dotexpr . '"')
if b:dotexpr =~ '^\s*$' && b:incomplete =~ '^\s*$'
return []
endif
let result = []
if b:dotexpr !~ '^\s*$'
if b:context_type == s:CONTEXT_AFTER_DOT
let result = s:CompleteAfterDot(b:dotexpr)
elseif b:context_type == s:CONTEXT_IMPORT || b:context_type == s:CONTEXT_IMPORT_STATIC || b:context_type == s:CONTEXT_PACKAGE_DECL || b:context_type == s:CONTEXT_NEED_TYPE
let result = s:GetMembers(b:dotexpr[:-2])
elseif b:context_type == s:CONTEXT_METHOD_PARAM
if b:incomplete == '+'
let result = s:GetConstructorList(b:dotexpr)
else
let result = s:CompleteAfterDot(b:dotexpr)
endif
endif
" only incomplete word
elseif b:incomplete !~ '^\s*$'
" only need methods
if b:context_type == s:CONTEXT_METHOD_PARAM
let methods = s:SearchForName(b:incomplete, 0, 1)[1]
call extend(result, eval('[' . s:DoGetMethodList(methods) . ']'))
else
let result = s:CompleteAfterWord(b:incomplete)
endif
" then no filter needed
let b:incomplete = ''
endif
if len(result) > 0
" filter according to b:incomplete
if len(b:incomplete) > 0 && b:incomplete != '+'
let result = filter(result, "type(v:val) == type('') ? v:val =~ '^" . b:incomplete . "' : v:val['word'] =~ '^" . b:incomplete . "'")
endif
if exists('s:padding') && !empty(s:padding)
for item in result
if type(item) == type("")
let item .= s:padding
else
let item.word .= s:padding
endif
endfor
unlet s:padding
endif
call s:Debug('finish completion' . reltimestr(reltime(s:et_whole)) . 's')
return result
endif
if strlen(b:errormsg) > 0
echoerr 'javacomplete error: ' . b:errormsg
let b:errormsg = ''
endif
endfunction
" Precondition: incomplete must be a word without '.'.
" return all the matched, variables, fields, methods, types, packages
fu! s:CompleteAfterWord(incomplete)
" packages in jar files
if !exists('s:all_packages_in_jars_loaded')
call s:DoGetInfoByReflection('-', '-P')
let s:all_packages_in_jars_loaded = 1
endif
let pkgs = []
let types = []
for key in keys(s:cache)
if key =~# '^' . a:incomplete
if type(s:cache[key]) == type('') || get(s:cache[key], 'tag', '') == 'PACKAGE'
call add(pkgs, {'kind': 'P', 'word': key})
" filter out type info
elseif b:context_type != s:CONTEXT_PACKAGE_DECL && b:context_type != s:CONTEXT_IMPORT && b:context_type != s:CONTEXT_IMPORT_STATIC
call add(types, {'kind': 'C', 'word': key})
endif
endif
endfor
let pkgs += s:DoGetPackageInfoInDirs(a:incomplete, b:context_type == s:CONTEXT_PACKAGE_DECL, 1)
" add accessible types which name beginning with the incomplete in source files
" TODO: remove the inaccessible
if b:context_type != s:CONTEXT_PACKAGE_DECL
" single type import
for fqn in s:GetImports('imports_fqn')
let name = fqn[strridx(fqn, ".")+1:]
if name =~ '^' . a:incomplete
call add(types, {'kind': 'C', 'word': name})
endif
endfor
" current file
let lnum_old = line('.')
let col_old = col('.')
call cursor(1, 1)
while 1
let lnum = search('\<\C\(class\|interface\|enum\)[ \t\n\r ]\+' . a:incomplete . '[a-zA-Z0-9_$]*[< \t\n\r ]', 'W')
if lnum == 0
break
elseif s:InCommentOrLiteral(line('.'), col('.'))
continue
else
normal w
call add(types, {'kind': 'C', 'word': matchstr(getline(line('.'))[col('.')-1:], s:RE_IDENTIFIER)})
endif
endwhile
call cursor(lnum_old, col_old)
" other files
let filepatterns = ''
for dirpath in s:GetSourceDirs(expand('%:p'))
let filepatterns .= escape(dirpath, ' \') . '/*.java '
endfor
exe 'vimgrep /\s*' . s:RE_TYPE_DECL . '/jg ' . filepatterns
for item in getqflist()
if item.text !~ '^\s*\*\s\+'
let text = matchstr(s:Prune(item.text, -1), '\s*' . s:RE_TYPE_DECL)
if text != ''
let subs = split(substitute(text, '\s*' . s:RE_TYPE_DECL, '\1;\2;\3', ''), ';', 1)
if subs[2] =~# '^' . a:incomplete && (subs[0] =~ '\C\<public\>' || fnamemodify(bufname(item.bufnr), ':p:h') == expand('%:p:h'))
call add(types, {'kind': 'C', 'word': subs[2]})
endif
endif
endif
endfor
endif
let result = []
" add variables and members in source files
if b:context_type == s:CONTEXT_AFTER_DOT
let matches = s:SearchForName(a:incomplete, 0, 0)
let result += sort(eval('[' . s:DoGetFieldList(matches[2]) . ']'))
let result += sort(eval('[' . s:DoGetMethodList(matches[1]) . ']'))
endif
let result += sort(pkgs)
let result += sort(types)
return result
endfu
" Precondition: expr must end with '.'
" return members of the value of expression
function! s:CompleteAfterDot(expr)
let items = s:ParseExpr(a:expr) " TODO: return a dict containing more than items
if empty(items)
return []
endif
" 0. String literal
call s:Info('P0. "str".|')
if items[-1] =~ '"$'
return s:GetMemberList("java.lang.String")
endif
let ti = {}
let ii = 1 " item index
let itemkind = 0
"
" optimized process
"
" search the longest expr consisting of ident
let i = 1
let k = i
while i < len(items) && items[i] =~ '^\s*' . s:RE_IDENTIFIER . '\s*$'
let ident = substitute(items[i], '\s', '', 'g')
if ident == 'class' || ident == 'this' || ident == 'super'
let k = i
" return when found other keywords
elseif s:IsKeyword(ident)
return []
endif
let items[i] = substitute(items[i], '\s', '', 'g')
let i += 1
endwhile
if i > 1
" cases: "this.|", "super.|", "ClassName.this.|", "ClassName.super.|", "TypeName.class.|"
if items[k] ==# 'class' || items[k] ==# 'this' || items[k] ==# 'super'
call s:Info('O1. ' . items[k] . ' ' . join(items[:k-1], '.'))
let ti = s:DoGetClassInfo(items[k] == 'class' ? 'java.lang.Class' : join(items[:k-1], '.'))
if !empty(ti)
let itemkind = items[k] ==# 'this' ? 1 : items[k] ==# 'super' ? 2 : 0
let ii = k+1
else
return []
endif
" case: "java.io.File.|"
else
let fqn = join(items[:i-1], '.')
let srcpath = join(s:GetSourceDirs(expand('%:p'), s:GetPackageName()), ',')
call s:Info('O2. ' . fqn)
call s:DoGetTypeInfoForFQN([fqn], srcpath)
if get(get(s:cache, fqn, {}), 'tag', '') == 'CLASSDEF'
let ti = s:cache[fqn]
let itemkind = 11
let ii = i
endif
endif
endif
"
" first item
"
if empty(ti)
" cases:
" 1) "int.|", "void.|" - primitive type or pseudo-type, return `class`
" 2) "this.|", "super.|" - special reference
" 3) "var.|" - variable or field
" 4) "String.|" - type imported or defined locally
" 5) "java.|" - package
if items[0] =~ '^\s*' . s:RE_IDENTIFIER . '\s*$'
let ident = substitute(items[0], '\s', '', 'g')
if s:IsKeyword(ident)
" 1)
call s:Info('F1. "' . ident . '.|"')
if ident ==# 'void' || s:IsBuiltinType(ident)
let ti = s:PRIMITIVE_TYPE_INFO
let itemkind = 11
" 2)
call s:Info('F2. "' . ident . '.|"')
elseif ident ==# 'this' || ident ==# 'super'
let itemkind = ident ==# 'this' ? 1 : ident ==# 'super' ? 2 : 0
let ti = s:DoGetClassInfo(ident)
endif
else
" 3)
let typename = s:GetDeclaredClassName(ident)
call s:Info('F3. "' . ident . '.|" typename: "' . typename . '"')
if (typename != '')
if typename[0] == '[' || typename[-1:] == ']'
let ti = s:ARRAY_TYPE_INFO
elseif typename != 'void' && !s:IsBuiltinType(typename)
let ti = s:DoGetClassInfo(typename)
endif
else
" 4)
call s:Info('F4. "TypeName.|"')
let ti = s:DoGetClassInfo(ident)
let itemkind = 11
if get(ti, 'tag', '') != 'CLASSDEF'
let ti = {}
endif
" 5)
if empty(ti)
call s:Info('F5. "package.|"')
unlet ti
let ti = s:GetMembers(ident) " s:DoGetPackegInfo(ident)
let itemkind = 20
endif
endif
endif
" method invocation: "method().|" - "this.method().|"
elseif items[0] =~ '^\s*' . s:RE_IDENTIFIER . '\s*('
let ti = s:MethodInvocation(items[0], ti, itemkind)
" array type, return `class`: "int[] [].|", "java.lang.String[].|", "NestedClass[].|"
elseif items[0] =~# s:RE_ARRAY_TYPE
call s:Info('array type. "' . items[0] . '"')
let qid = substitute(items[0], s:RE_ARRAY_TYPE, '\1', '')
if s:IsBuiltinType(qid) || (!s:HasKeyword(qid) && !empty(s:DoGetClassInfo(qid)))
let ti = s:PRIMITIVE_TYPE_INFO
let itemkind = 11
endif
" class instance creation expr: "new String().|", "new NonLoadableClass().|"
" array creation expr: "new int[i=1] [val()].|", "new java.lang.String[].|"
elseif items[0] =~ '^\s*new\s\+'
call s:Info('creation expr. "' . items[0] . '"')
let subs = split(substitute(items[0], '^\s*new\s\+\(' .s:RE_QUALID. '\)\s*\([([]\)', '\1;\2', ''), ';')
if subs[1][0] == '['
let ti = s:ARRAY_TYPE_INFO
elseif subs[1][0] == '('
let ti = s:DoGetClassInfo(subs[0])
" exclude interfaces and abstract class. TODO: exclude the inaccessible
if get(ti, 'flags', '')[-10:-10] || get(ti, 'flags', '')[-11:-11]
echo 'cannot instantiate the type ' . subs[0]
let ti = {}
return []
endif
endif
" casting conversion: "(Object)o.|"
elseif items[0] =~ s:RE_CASTING
call s:Info('Casting conversion. "' . items[0] . '"')
let subs = split(substitute(items[0], s:RE_CASTING, '\1;\2', ''), ';')
let ti = s:DoGetClassInfo(subs[0])
" array access: "var[i][j].|" Note: "var[i][]" is incorrect
elseif items[0] =~# s:RE_ARRAY_ACCESS
let subs = split(substitute(items[0], s:RE_ARRAY_ACCESS, '\1;\2', ''), ';')
if get(subs, 1, '') !~ s:RE_BRACKETS
let typename = s:GetDeclaredClassName(subs[0])
call s:Info('ArrayAccess. "' .items[0]. '.|" typename: "' . typename . '"')
if (typename != '')
let ti = s:ArrayAccess(typename, items[0])
endif
endif
endif
endif
"
" next items
"
while !empty(ti) && ii < len(items)
" method invocation: "PrimaryExpr.method(parameters)[].|"
if items[ii] =~ '^\s*' . s:RE_IDENTIFIER . '\s*('
let ti = s:MethodInvocation(items[ii], ti, itemkind)
let itemkind = 0
let ii += 1
continue
" expression of selection, field access, array access
elseif items[ii] =~ s:RE_SELECT_OR_ACCESS
let subs = split(substitute(items[ii], s:RE_SELECT_OR_ACCESS, '\1;\2', ''), ';')
let ident = subs[0]
let brackets = get(subs, 1, '')
" package members
if itemkind/10 == 2 && empty(brackets) && !s:IsKeyword(ident)
let qn = join(items[:ii], '.')
if type(ti) == type([])
let idx = s:Index(ti, ident, 'word')
if idx >= 0
if ti[idx].kind == 'P'
unlet ti
let ti = s:GetMembers(qn)
let ii += 1
continue
elseif ti[idx].kind == 'C'
unlet ti
let ti = s:DoGetClassInfo(qn)
let itemkind = 11
let ii += 1
continue
endif
endif
endif
" type members
elseif itemkind/10 == 1 && empty(brackets)
if ident ==# 'class' || ident ==# 'this' || ident ==# 'super'
let ti = s:DoGetClassInfo(ident == 'class' ? 'java.lang.Class' : join(items[:ii-1], '.'))
let itemkind = ident ==# 'this' ? 1 : ident ==# 'super' ? 2 : 0
let ii += 1
continue
elseif !s:IsKeyword(ident) && type(ti) == type({}) && get(ti, 'tag', '') == 'CLASSDEF'
" accessible static field
"let idx = s:Index(get(ti, 'fields', []), ident, 'n')
"if idx >= 0 && s:IsStatic(ti.fields[idx].m)
" let ti = s:ArrayAccess(ti.fields[idx].t, items[ii])
let members = s:SearchMember(ti, ident, 1, itemkind, 1, 0)
if !empty(members[2])
let ti = s:ArrayAccess(members[2][0].t, items[ii])
let itemkind = 0
let ii += 1
continue
endif
" accessible nested type
"if !empty(filter(copy(get(ti, 'classes', [])), 'strpart(v:val, strridx(v:val, ".")) ==# "' . ident . '"'))
if !empty(members[0])
let ti = s:DoGetClassInfo(join(items[:ii], '.'))
let ii += 1
continue
endif
endif
" instance members
elseif itemkind/10 == 0 && !s:IsKeyword(ident)
if type(ti) == type({}) && get(ti, 'tag', '') == 'CLASSDEF'
"let idx = s:Index(get(ti, 'fields', []), ident, 'n')
"if idx >= 0
" let ti = s:ArrayAccess(ti.fields[idx].t, items[ii])
let members = s:SearchMember(ti, ident, 1, itemkind, 1, 0)
let itemkind = 0
if !empty(members[2])
let ti = s:ArrayAccess(members[2][0].t, items[ii])
let ii += 1
continue
endif
endif
endif
endif
return []
endwhile
" type info or package info --> members
if !empty(ti)
if type(ti) == type({})
if get(ti, 'tag', '') == 'CLASSDEF'
if get(ti, 'name', '') == '!'
return [{'kind': 'f', 'word': 'class', 'menu': 'Class'}]
elseif get(ti, 'name', '') == '['
return s:ARRAY_TYPE_MEMBERS
elseif itemkind < 20
return s:DoGetMemberList(ti, itemkind)
endif
elseif get(ti, 'tag', '') == 'PACKAGE'
" TODO: ti -> members, in addition to packages in dirs
return s:GetMembers( substitute(join(items, '.'), '\s', '', 'g') )
endif
elseif type(ti) == type([])
return ti
endif
endif
return []
endfunction
fu! s:MethodInvocation(expr, ti, itemkind)
let subs = split(substitute(a:expr, '\s*\(' . s:RE_IDENTIFIER . '\)\s*\((.*\)', '\1;\2', ''), ';')
" all methods matched
if empty(a:ti)
let methods = s:SearchForName(subs[0], 0, 1)[1]
elseif type(a:ti) == type({}) && get(a:ti, 'tag', '') == 'CLASSDEF'
let methods = s:SearchMember(a:ti, subs[0], 1, a:itemkind, 1, 0, a:itemkind == 2)[1]
" let methods = s:filter(get(a:ti, 'methods', []), 'item.n == "' . subs[0] . '"')
" if a:itemkind == 1 || a:itemkind == 2
" let methods += s:filter(get(a:ti, 'declared_methods', []), 'item.n == "' . subs[0] . '"')
" endif
else
let methods = []
endif
let method = s:DetermineMethod(methods, subs[1])
if !empty(method)
return s:ArrayAccess(method.r, a:expr)
endif
return {}
endfu
fu! s:ArrayAccess(arraytype, expr)
if a:expr =~ s:RE_BRACKETS | return {} | endif
let typename = a:arraytype
let dims = 0
if typename[0] == '[' || typename[-1:] == ']' || a:expr[-1:] == ']'
let dims = s:CountDims(a:expr) - s:CountDims(typename)
if dims == 0
let typename = matchstr(typename, s:RE_IDENTIFIER)
elseif dims < 0
return s:ARRAY_TYPE_INFO
else
"echoerr 'dims exceeds'
endif
endif
if dims == 0
if typename != 'void' && !s:IsBuiltinType(typename)
return s:DoGetClassInfo(typename)
endif
endif
return {}
endfu
" Quick information {{{1
function! MyBalloonExpr()
if (searchdecl(v:beval_text, 1, 0) == 0)
return s:GetVariableDeclaration()
endif
return ''
" return 'Cursor is at line ' . v:beval_lnum .
" \', column ' . v:beval_col .
" \ ' of file ' . bufname(v:beval_bufnr) .
" \ ' on word "' . v:beval_text . '"'
endfunction
"set bexpr=MyBalloonExpr()
"set ballooneval
" parameters information {{{1
fu! javacomplete#CompleteParamsInfo(findstart, base)
if a:findstart
return col('.') - 1
endif
let mi = s:GetMethodInvocationExpr(s:GetStatement())
if empty(mi.method)
return []
endif
" TODO: how to determine overloaded functions
"let mi.params = s:EvalParams(mi.params)
if empty(mi.expr)
let methods = s:SearchForName(mi.method, 0, 1)[1]
let result = eval('[' . s:DoGetMethodList(methods) . ']')
elseif mi.method == '+'
let result = s:GetConstructorList(mi.expr)
else
let result = s:CompleteAfterDot(mi.expr)
endif
if !empty(result)
if !empty(mi.method) && mi.method != '+'
let result = filter(result, "type(v:val) == type('') ? v:val ==# '" . mi.method . "' : v:val['word'] ==# '" . mi.method . "('")
endif
return result
endif
endfu
" scanning and parsing {{{1
" Search back from the cursor position till meeting '{' or ';'.
" '{' means statement start, ';' means end of a previous statement.
" Return: statement before cursor
" Note: It's the base for parsing. And It's OK for most cases.
function! s:GetStatement()
if getline('.') =~ '^\s*\(import\|package\)\s\+'
return strpart(getline('.'), match(getline('.'), '\(import\|package\)'), col('.')-1)
endif
let lnum_old = line('.')
let col_old = col('.')
while 1
if search('[{};]\|<%\|<%!', 'bW') == 0
let lnum = 1
let col = 1
else
if s:InCommentOrLiteral(line('.'), col('.'))
continue
endif
normal w
let lnum = line('.')
let col = col('.')
endif
break
endwhile
silent call cursor(lnum_old, col_old)
return s:MergeLines(lnum, col, lnum_old, col_old)
endfunction
fu! s:MergeLines(lnum, col, lnum_old, col_old)
let lnum = a:lnum
let col = a:col
let str = ''
if lnum < a:lnum_old
let str = s:Prune(strpart(getline(lnum), a:col-1))
let lnum += 1
while lnum < a:lnum_old
let str .= s:Prune(getline(lnum))
let lnum += 1
endwhile
let col = 1
endif
let lastline = strpart(getline(a:lnum_old), col-1, a:col_old-col)
let str .= s:Prune(lastline, col)
let str = s:RemoveBlockComments(str)
" generic in JAVA 5+
while match(str, s:RE_TYPE_ARGUMENTS) != -1
let str = substitute(str, '\(' . s:RE_TYPE_ARGUMENTS . '\)', '\=repeat(" ", len(submatch(1)))', 'g')
endwhile
let str = substitute(str, '\s\s\+', ' ', 'g')
let str = substitute(str, '\([.()]\)[ \t]\+', '\1', 'g')
let str = substitute(str, '[ \t]\+\([.()]\)', '\1', 'g')
return s:Trim(str) . matchstr(lastline, '\s*$')
endfu
" Extract a clean expr, removing some non-necessary characters.
fu! s:ExtractCleanExpr(expr)
let cmd = substitute(a:expr, '[ \t\r\n ]\+\([.()[\]]\)', '\1', 'g')
let cmd = substitute(cmd, '\([.()[\]]\)[ \t\r\n ]\+', '\1', 'g')
let pos = strlen(cmd)-1
while pos >= 0 && cmd[pos] =~ '[a-zA-Z0-9_.)\]]'
if cmd[pos] == ')'
let pos = s:SearchPairBackward(cmd, pos, '(', ')')
elseif cmd[pos] == ']'
let pos = s:SearchPairBackward(cmd, pos, '[', ']')
endif
let pos -= 1
endwhile
" try looking back for "new"
let idx = match(strpart(cmd, 0, pos+1), '\<new[ \t\r\n ]*$')
return strpart(cmd, idx != -1 ? idx : pos+1)
endfu
fu! s:ParseExpr(expr)
let items = []
let s = 0
" recognize ClassInstanceCreationExpr as a whole
let e = matchend(a:expr, '^\s*new\s\+' . s:RE_QUALID . '\s*[([]')-1
if e < 0
let e = match(a:expr, '[.([]')
endif
let isparen = 0
while e >= 0
if a:expr[e] == '.'
let subexpr = strpart(a:expr, s, e-s)
call extend(items, isparen ? s:ProcessParentheses(subexpr) : [subexpr])
let isparen = 0
let s = e + 1
elseif a:expr[e] == '('
let e = s:GetMatchedIndexEx(a:expr, e, '(', ')')
let isparen = 1
if e < 0
break
else
let e = matchend(a:expr, '^\s*[.[]', e+1)-1
continue
endif
elseif a:expr[e] == '['
let e = s:GetMatchedIndexEx(a:expr, e, '[', ']')
if e < 0
break
else
let e = matchend(a:expr, '^\s*[.[]', e+1)-1
continue
endif
endif
let e = match(a:expr, '[.([]', s)
endwhile
let tail = strpart(a:expr, s)
if tail !~ '^\s*$'
call extend(items, isparen ? s:ProcessParentheses(tail) : [tail])
endif
return items
endfu
" Given optional argument, call s:ParseExpr() to parser the nonparentheses expr
fu! s:ProcessParentheses(expr, ...)
let s = matchend(a:expr, '^\s*(')
if s != -1
let e = s:GetMatchedIndexEx(a:expr, s-1, '(', ')')
if e >= 0
let tail = strpart(a:expr, e+1)
if tail =~ '^\s*[\=$'
return s:ProcessParentheses(strpart(a:expr, s, e-s), 1)
elseif tail =~ '^\s*\w'
return [strpart(a:expr, 0, e+1) . 'obj.']
endif
endif
" multi-dot-expr except for new expr
elseif a:0 > 0 && stridx(a:expr, '.') != match(a:expr, '\.\s*$') && a:expr !~ '^\s*new\s\+'
return s:ParseExpr(a:expr)
endif
return [a:expr]
endfu
" return {'expr': , 'method': , 'params': }
fu! s:GetMethodInvocationExpr(expr)
let idx = strlen(a:expr)-1
while idx >= 0
if a:expr[idx] == '('
break
elseif a:expr[idx] == ')'
let idx = s:SearchPairBackward(a:expr, idx, '(', ')')
elseif a:expr[idx] == ']'
let idx = s:SearchPairBackward(a:expr, idx, '[', ']')
endif
let idx -= 1
endwhile
let mi = {'expr': strpart(a:expr, 0, idx+1), 'method': '', 'params': strpart(a:expr, idx+1)}
let idx = match(mi.expr, '\<new\s\+' . s:RE_QUALID . '\s*(\s*$')
if idx >= 0
let mi.method = '+'
let mi.expr = substitute(matchstr(strpart(mi.expr, idx+4), s:RE_QUALID), '\s', '', 'g')
else
let idx = match(mi.expr, '\<' . s:RE_IDENTIFIER . '\s*(\s*$')
if idx >= 0
let subs = s:SplitAt(mi.expr, idx-1)
let mi.method = substitute(subs[1], '\s*(\s*$', '', '')
let mi.expr = s:ExtractCleanExpr(subs[0])
endif
endif
return mi
endfu
" imports {{{1
function! s:GenerateImports()
let imports = []
let lnum_old = line('.')
let col_old = col('.')
call cursor(1, 1)
if &ft == 'jsp'
while 1
let lnum = search('\<import\s*=[''"]', 'W')
if (lnum == 0)
break
endif
let str = getline(lnum)
if str =~ '<%\s*@\s*page\>' || str =~ '<jsp:\s*directive.page\>'
let str = substitute(str, '.*import=[''"]\([a-zA-Z0-9_$.*, \t]\+\)[''"].*', '\1', '')
for item in split(str, ',')
call add(imports, substitute(item, '\s', '', 'g'))
endfor
endif
endwhile
else
while 1
let lnum = search('\<import\>', 'W')
if (lnum == 0)
break
elseif !s:InComment(line("."), col(".")-1)
normal w
" TODO: search semicolon or import keyword, excluding comment
let stat = matchstr(getline(lnum)[col('.')-1:], '\(static\s\+\)\?\(' .s:RE_QUALID. '\%(\s*\.\s*\*\)\?\)\s*;')
if !empty(stat)
call add(imports, stat[:-2])
endif
endif
endwhile
endif
call cursor(lnum_old, col_old)
return imports
endfunction
fu! s:GetImports(kind, ...)
let filekey = a:0 > 0 && !empty(a:1) ? a:1 : s:GetCurrentFileKey()
let props = get(s:files, filekey, {})
if !has_key(props, a:kind)
let props['imports'] = filekey == s:GetCurrentFileKey() ? s:GenerateImports() : props.unit.imports
let props['imports_static'] = []
let props['imports_fqn'] = []
let props['imports_star'] = ['java.lang.']
if &ft == 'jsp' || filekey =~ '\.jsp$'
let props.imports_star += ['javax.servlet.', 'javax.servlet.http.', 'javax.servlet.jsp.']
endif
for import in props.imports
let subs = split(substitute(import, '^\s*\(static\s\+\)\?\(' .s:RE_QUALID. '\%(\s*\.\s*\*\)\?\)\s*$', '\1;\2', ''), ';', 1)
let qid = substitute(subs[1] , '\s', '', 'g')
if !empty(subs[0])
call add(props.imports_static, qid)
elseif qid[-1:] == '*'
call add(props.imports_star, qid[:-2])
else
call add(props.imports_fqn, qid)
endif
endfor
let s:files[filekey] = props
endif
return get(props, a:kind, [])
endfu
" search for name in
" return the fqn matched
fu! s:SearchSingleTypeImport(name, fqns)
let matches = s:filter(a:fqns, 'item =~# ''\<' . a:name . '$''')
if len(matches) == 1
return matches[0]
elseif !empty(matches)
echoerr 'Name "' . a:name . '" conflicts between ' . join(matches, ' and ')
return matches[0]
endif
return ''
endfu
" search for name in static imports, return list of members with the same name
" return [types, methods, fields]
fu! s:SearchStaticImports(name, fullmatch)
let result = [[], [], []]
let candidates = [] " list of the canonical name
for item in s:GetImports('imports_static')
if item[-1:] == '*' " static import on demand
call add(candidates, item[:-3])
elseif item[strridx(item, '.')+1:] ==# a:name
\ || (!a:fullmatch && item[strridx(item, '.')+1:] =~ '^' . a:name)
call add(candidates, item[:strridx(item, '.')])
endif
endfor
if empty(candidates)
return result
endif
" read type info which are not in cache
let commalist = ''
for typename in candidates
if !has_key(s:cache, typename)
let commalist .= typename . ','
endif
endfor
if commalist != ''
let res = s:RunReflection('-E', commalist, 's:SearchStaticImports in Batch')
if res =~ "^{'"
let dict = eval(res)
for key in keys(dict)
let s:cache[key] = s:Sort(dict[key])
endfor
endif
endif
" search in all candidates
for typename in candidates
let ti = get(s:cache, typename, 0)
if type(ti) == type({}) && get(ti, 'tag', '') == 'CLASSDEF'
let members = s:SearchMember(ti, a:name, a:fullmatch, 12, 1, 0)
let result[1] += members[1]
let result[2] += members[2]
"let pattern = 'item.n ' . (a:fullmatch ? '==# ''' : '=~# ''^') . a:name . ''' && s:IsStatic(item.m)'
"let result[1] += s:filter(get(ti, 'methods', []), pattern)
"let result[2] += s:filter(get(ti, 'fields', []), pattern)
else
" TODO: mark the wrong import declaration.
endif
endfor
return result
endfu
" search decl {{{1
" Return: The declaration of identifier under the cursor
" Note: The type of a variable must be imported or a fqn.
function! s:GetVariableDeclaration()
let lnum_old = line('.')
let col_old = col('.')
silent call search('[^a-zA-Z0-9$_.,?<>[\] \t\r\n ]', 'bW') " call search('[{};(,]', 'b')
normal w
let lnum = line('.')
let col = col('.')
if (lnum == lnum_old && col == col_old)
return ''
endif
" silent call search('[;){]')
" let lnum_end = line('.')
" let col_end = col('.')
" let declaration = ''
" while (lnum <= lnum_end)
" let declaration = declaration . getline(lnum)
" let lnum = lnum + 1
" endwhile
" let declaration = strpart(declaration, col-1)
" let declaration = substitute(declaration, '\.[ \t]\+', '.', 'g')
silent call cursor(lnum_old, col_old)
return s:MergeLines(lnum, col, lnum_old, col_old)
endfunction
function! s:FoundClassDeclaration(type)
let lnum_old = line('.')
let col_old = col('.')
call cursor(1, 1)
while 1
let lnum = search('\<\C\(class\|interface\|enum\)[ \t\n\r ]\+' . a:type . '[< \t\n\r ]', 'W')
if lnum == 0 || !s:InCommentOrLiteral(line('.'), col('.'))
break
endif
endwhile
" search mainly for the cases: " class /* block comment */ Ident"
" " class // comment \n Ident "
if lnum == 0
let found = 0
while !found
let lnum = search('\<\C\(class\|interface\|enum\)[ \t\n\r ]\+', 'W')
if lnum == 0
break
elseif s:InCommentOrLiteral(line('.'), col('.'))
continue
else
normal w
" skip empty line
while getline(line('.'))[col('.')-1] == ''
normal w
endwhile
let lnum = line('.')
let col = col('.')
while 1
if match(getline(lnum)[col-1:], '^[ \t\n\r ]*' . a:type . '[< \t\n\r ]') >= 0
let found = 1
" meets comment
elseif match(getline(lnum)[col-1:], '^[ \t\n\r ]*\(//\|/\*\)') >= 0
if getline(lnum)[col-1:col] == '//'
normal $eb
else
let lnum = search('\*\/', 'W')
if lnum == 0
break
endif
normal web
endif
let lnum = line('.')
let col = col('.')
continue
endif
break
endwhile
endif
endwhile
endif
silent call cursor(lnum_old, col_old)
return lnum
endfu
fu! s:FoundClassLocally(type)
" current path
if globpath(expand('%:p:h'), a:type . '.java') != ''
return 1
endif
"
let srcpath = javacomplete#GetSourcePath(1)
let file = globpath(srcpath, substitute(fqn, '\.', '/', 'g') . '.java')
if file != ''
return 1
endif
return 0
endfu
" regexp samples:
" echo search('\(\(public\|protected|private\)[ \t\n\r]\+\)\?\(\(static\)[ \t\n\r]\+\)\?\(\<class\>\|\<interface\>\)[ \t\n\r]\+HelloWorld[^a-zA-Z0-9_$]', 'W')
" echo substitute(getline('.'), '.*\(\(public\|protected\|private\)[ \t\n\r]\+\)\?\(\(static\)[ \t\n\r]\+\)\?\(\<class\>\|\<interface\>\)\s\+\([a-zA-Z0-9_]\+\)\s\+\(\(implements\|extends\)\s\+\([^{]\+\)\)\?\s*{.*', '["\1", "\2", "\3", "\4", "\5", "\6", "\8", "\9"]', '')
" code sample:
function! s:GetClassDeclarationOf(type)
call cursor(1, 1)
let decl = []
let lnum = search('\(\<class\>\|\<interface\>\)[ \t\n\r]\+' . a:type . '[^a-zA-Z0-9_$]', 'W')
if (lnum != 0)
" TODO: search back for optional 'public | private' and 'static'
" join lines till to '{'
let lnum_end = search('{')
if (lnum_end != 0)
let str = ''
while (lnum <= lnum_end)
let str = str . getline(lnum)
let lnum = lnum + 1
endwhile
exe "let decl = " . substitute(str, '.*\(\<class\>\|\<interface\>\)\s\+\([a-zA-Z0-9_]\+\)\s\+\(\(implements\|extends\)\s\+\([^{]\+\)\)\?\s*{.*', '["\1", "\2", "\4", "\5"]', '')
endif
endif
return decl
endfunction
" return list
" 0 class | interface
" 1 name
" [2 implements | extends ]
" [3 parent list ]
function! s:GetThisClassDeclaration()
let lnum_old = line('.')
let col_old = col('.')
while (1)
call search('\(\<class\C\>\|\<interface\C\>\|\<enum\C\>\)[ \t\r\n]\+', 'bW')
if !s:InComment(line("."), col(".")-1)
if getline('.')[col('.')-2] !~ '\S'
break
endif
end
endwhile
" join lines till to '{'
let str = ''
let lnum = line('.')
call search('{')
let lnum_end = line('.')
while (lnum <= lnum_end)
let str = str . getline(lnum)
let lnum = lnum + 1
endwhile
let declaration = substitute(str, '.*\(\<class\>\|\<interface\>\)\s\+\([a-zA-Z0-9_]\+\)\(\s\+\(implements\|extends\)\s\+\([^{]\+\)\)\?\s*{.*', '["\1", "\2", "\4", "\5"]', '')
call cursor(lnum_old, col_old)
if declaration !~ '^['
echoerr 'Some error occurs when recognizing this class:' . declaration
return ['', '']
endif
exe "let list = " . declaration
return list
endfunction
" searches for name of a var or a field and determines the meaning {{{1
" The standard search order of a variable or field is as follows:
" 1. Local variables declared in the code block, for loop, or catch clause
" from current scope up to the most outer block, a method or an initialization block
" 2. Parameters if the code is in a method or ctor
" 3. Fields of the type
" 4. Accessible inherited fields.
" 5. If the type is a nested type,
" local variables of the enclosing block or fields of the enclosing class.
" Note that if the type is a static nested type, only static members of an enclosing block or class are searched
" Reapply this rule to the upper block and class enclosing the enclosing type recursively
" 6. Accessible static fields imported.
" It is allowed that several fields with the same name.
" The standard search order of a method is as follows:
" 1. Methods of the type
" 2. Accessible inherited methods.
" 3. Methods of the enclosing class if the type is a nested type.
" 4. Accessible static methods imported.
" It is allowed that several methods with the same name and signature.
" first return at once if found one.
" fullmatch 1 - equal, 0 - match beginning
" return [types, methods, fields, vars]
fu! s:SearchForName(name, first, fullmatch)
let result = [[], [], [], []]
if s:IsKeyword(a:name)
return result
endif
" use java_parser.vim
if javacomplete#GetSearchdeclMethod() == 4
" declared in current file
let unit = javacomplete#parse()
let targetPos = java_parser#MakePos(line('.')-1, col('.')-1)
let trees = s:SearchNameInAST(unit, a:name, targetPos, a:fullmatch)
for tree in trees
if tree.tag == 'VARDEF'
call add(result[2], tree)
elseif tree.tag == 'METHODDEF'
call add(result[1], tree)
elseif tree.tag == 'CLASSDEF'
call add(result[0], tree.name)
endif
endfor
if a:first && result != [[], [], [], []] | return result | endif
" Accessible inherited members
let type = get(s:SearchTypeAt(unit, targetPos), -1, {})
if !empty(type)
let members = s:SearchMember(type, a:name, a:fullmatch, 2, 1, 0, 1)
let result[0] += members[0]
let result[1] += members[1]
let result[2] += members[2]
" "let ti = s:AddInheritedClassInfo({}, type)
" if !empty(ti)
" let comparator = a:fullmatch ? "=~# '^" : "==# '"
" let result[0] += s:filter(get(ti, 'classes', []), 'item ' . comparator . a:name . "'")
" let result[1] += s:filter(get(ti, 'methods', []), 'item.n ' . comparator . a:name . "'")
" let result[2] += s:filter(get(ti, 'fields', []), 'item.n ' . comparator . a:name . "'")
" if a:0 > 0
" let result[1] += s:filter(get(ti, 'declared_methods', []), 'item.n ' . comparator . a:name . "'")
" let result[2] += s:filter(get(ti, 'declared_fields', []), 'item.n ' . comparator . a:name . "'")
" endif
" if a:first && result != [[], [], [], []] | return result | endif
" endif
endif
" static import
let si = s:SearchStaticImports(a:name, a:fullmatch)
let result[1] += si[1]
let result[2] += si[2]
endif
return result
endfu
" TODO: how to determine overloaded functions
fu! s:DetermineMethod(methods, parameters)
return get(a:methods, 0, {})
endfu
" Parser.GetType() in insenvim
function! s:GetDeclaredClassName(var)
let var = s:Trim(a:var)
call s:Trace('GetDeclaredClassName for "' . var . '"')
if var =~# '^\(this\|super\)$'
return var
endif
" Special handling for builtin objects in JSP
if &ft == 'jsp'
if get(s:JSP_BUILTIN_OBJECTS, a:var, '') != ''
return s:JSP_BUILTIN_OBJECTS[a:var]
endif
endif
" use java_parser.vim
if javacomplete#GetSearchdeclMethod() == 4
let variable = get(s:SearchForName(var, 1, 1)[2], -1, {})
return get(variable, 'tag', '') == 'VARDEF' ? java_parser#type2Str(variable.vartype) : get(variable, 't', '')
endif
let ic = &ignorecase
setlocal noignorecase
let searched = javacomplete#GetSearchdeclMethod() == 2 ? s:Searchdecl(var, 1, 0) : searchdecl(var, 1, 0)
if (searched == 0)
" code sample:
" String tmp; java.
" lang. String str, value;
" for (int i = 0, j = 0; i < 10; i++) {
" j = 0;
" }
let declaration = s:GetVariableDeclaration()
" Assume it a class member, and remove modifiers
let class = substitute(declaration, '^\(public\s\+\|protected\s\+\|private\s\+\|abstract\s\+\|static\s\+\|final\s\+\|native\s\+\)*', '', '')
let class = substitute(class, '\s*\([a-zA-Z0-9_.]\+\)\(\[\]\)\?\s\+.*', '\1\2', '')
let class = substitute(class, '\([a-zA-Z0-9_.]\)<.*', '\1', '')
call s:Info('class: "' . class . '" declaration: "' . declaration . '" for ' . a:var)
let &ignorecase = ic
if class != '' && class !=# a:var && class !=# 'import' && class !=# 'class'
return class
endif
endif
let &ignorecase = ic
call s:Trace('GetDeclaredClassName: cannot find')
return ''
endfunction
" using java_parser.vim {{{1
" javacomplete#parse() {{{2
fu! javacomplete#parse(...)
let filename = a:0 == 0 ? '%' : a:1
let changed = 0
if filename == '%'
let filename = s:GetCurrentFileKey()
let props = get(s:files, filename, {})
if get(props, 'changedtick', -1) != b:changedtick
let changed = 1
let props.changedtick = b:changedtick
let lines = getline('^', '$')
endif
else
let props = get(s:files, filename, {})
if get(props, 'modifiedtime', 0) != getftime(filename)
let changed = 1
let props.modifiedtime = getftime(filename)
let lines = readfile(filename)
endif
endif
if changed
call java_parser#InitParser(lines)
call java_parser#SetLogLevel(5)
let props.unit = java_parser#compilationUnit()
let package = has_key(props.unit, 'package') ? props.unit.package . '.' : ''
call s:UpdateFQN(props.unit, package)
endif
let s:files[filename] = props
return props.unit
endfu
" update fqn for toplevel types or nested types.
" not for local type or anonymous type
fu! s:UpdateFQN(tree, qn)
if a:tree.tag == 'TOPLEVEL'
for def in a:tree.types
call s:UpdateFQN(def, a:qn)
endfor
elseif a:tree.tag == 'CLASSDEF'
let a:tree.fqn = a:qn . a:tree.name
for def in a:tree.defs
if def.tag == 'CLASSDEF'
call s:UpdateFQN(def, a:tree.fqn . '.')
endif
endfor
endif
endfu
" TreeVisitor {{{2
fu! s:visitTree(tree, param) dict
if type(a:tree) == type({})
exe get(self, get(a:tree, 'tag', ''), '')
elseif type(a:tree) == type([])
for tree in a:tree
call self.visit(tree, a:param)
endfor
endif
endfu
let s:TreeVisitor = {'visit': function('s:visitTree'),
\ 'TOPLEVEL' : 'call self.visit(a:tree.types, a:param)',
\ 'BLOCK' : 'let stats = a:tree.stats | if stats == [] | call java_parser#GotoPosition(a:tree.pos) | let stats = java_parser#block().stats | endif | call self.visit(stats, a:param)',
\ 'DOLOOP' : 'call self.visit(a:tree.body, a:param) | call self.visit(a:tree.cond, a:param)',
\ 'WHILELOOP' : 'call self.visit(a:tree.cond, a:param) | call self.visit(a:tree.body, a:param)',
\ 'FORLOOP' : 'call self.visit(a:tree.init, a:param) | call self.visit(a:tree.cond, a:param) | call self.visit(a:tree.step, a:param) | call self.visit(a:tree.body, a:param)',
\ 'FOREACHLOOP' : 'call self.visit(a:tree.var, a:param) | call self.visit(a:tree.expr, a:param) | call self.visit(a:tree.body, a:param)',
\ 'LABELLED' : 'call self.visit(a:tree.body, a:param)',
\ 'SWITCH' : 'call self.visit(a:tree.selector, a:param) | call self.visit(a:tree.cases, a:param)',
\ 'CASE' : 'call self.visit(a:tree.pat, a:param) | call self.visit(a:tree.stats, a:param)',
\ 'SYNCHRONIZED': 'call self.visit(a:tree.lock, a:param) | call self.visit(a:tree.body, a:param)',
\ 'TRY' : 'call self.visit(a:tree.body, a:param) | call self.visit(a:tree.catchers, a:param) | call self.visit(a:tree.finalizer, a:param) ',
\ 'CATCH' : 'call self.visit(a:tree.param,a:param) | call self.visit(a:tree.body, a:param)',
\ 'CONDEXPR' : 'call self.visit(a:tree.cond, a:param) | call self.visit(a:tree.truepart, a:param) | call self.visit(a:tree.falsepart, a:param)',
\ 'IF' : 'call self.visit(a:tree.cond, a:param) | call self.visit(a:tree.thenpart, a:param) | if has_key(a:tree, "elsepart") | call self.visit(a:tree.elsepart, a:param) | endif',
\ 'EXEC' : 'call self.visit(a:tree.expr, a:param)',
\ 'APPLY' : 'call self.visit(a:tree.meth, a:param) | call self.visit(a:tree.args, a:param)',
\ 'NEWCLASS' : 'call self.visit(a:tree.def, a:param)'
\}
let s:TV_CMP_POS = 'a:tree.pos <= a:param.pos && a:param.pos <= get(a:tree, "endpos", -1)'
let s:TV_CMP_POS_BODY = 'has_key(a:tree, "body") && a:tree.body.pos <= a:param.pos && a:param.pos <= get(a:tree.body, "endpos", -1)'
" Return a stack of enclosing types (including local or anonymous classes).
" Given the optional argument, return all (toplevel or static member) types besides enclosing types.
fu! s:SearchTypeAt(tree, targetPos, ...)
let s:TreeVisitor.CLASSDEF = 'if a:param.allNonLocal || ' . s:TV_CMP_POS . ' | call add(a:param.result, a:tree) | call self.visit(a:tree.defs, a:param) | endif'
let s:TreeVisitor.METHODDEF = 'if ' . s:TV_CMP_POS_BODY . ' | call self.visit(a:tree.body, a:param) | endif'
let s:TreeVisitor.VARDEF = 'if has_key(a:tree, "init") && !a:param.allNonLocal && ' . s:TV_CMP_POS . ' | call self.visit(a:tree.init, a:param) | endif'
let result = []
call s:TreeVisitor.visit(a:tree, {'result': result, 'pos': a:targetPos, 'allNonLocal': a:0 == 0 ? 0 : 1})
return result
endfu
" a:1 match beginning
" return a stack of matching name
fu! s:SearchNameInAST(tree, name, targetPos, fullmatch)
let comparator = a:fullmatch ? '==#' : '=~# "^" .'
let cmd = 'if a:tree.name ' .comparator. ' a:param.name | call add(a:param.result, a:tree) | endif'
let s:TreeVisitor.CLASSDEF = 'if ' . s:TV_CMP_POS . ' | ' . cmd . ' | call self.visit(a:tree.defs, a:param) | endif'
let s:TreeVisitor.METHODDEF = cmd . ' | if ' . s:TV_CMP_POS_BODY . ' | call self.visit(a:tree.params, a:param) | call self.visit(a:tree.body, a:param) | endif'
let s:TreeVisitor.VARDEF = cmd . ' | if has_key(a:tree, "init") && ' . s:TV_CMP_POS . ' | call self.visit(a:tree.init, a:param) | endif'
let result = []
call s:TreeVisitor.visit(a:tree, {'result': result, 'pos': a:targetPos, 'name': a:name})
"call s:Info(a:name . ' ' . string(result) . ' line: ' . line('.') . ' col: ' . col('.')) . ' ' . a:targetPos
return result
endfu
" javacomplete#Searchdecl {{{2
" TODO:
fu! javacomplete#Searchdecl()
let var = expand('<cword>')
let line = line('.')-1
let col = col('.')-1
if var =~# '^\(this\|super\)$'
if &ft == 'jsp'
return ''
endif
let matchs = s:SearchTypeAt(javacomplete#parse(), java_parser#MakePos(line, col))
let stat = s:GetStatement()
for t in matchs
if stat =~ t.name
let coor = java_parser#DecodePos(t.pos)
return var . '(' . (coor.line+1) . ',' . (coor.col) . ') ' . getline(coor.line+1)
endif
endfor
if len(matchs) > 0
let coor = java_parser#DecodePos(matchs[len(matchs)-1].pos)
return var . '(' . (coor.line+1) . ',' . (coor.col) . ') ' . getline(coor.line+1)
endif
return ''
endif
" Type.this.
" new Type()
" new Type(param1, param2)
" this.field
" super.field
let s:log = []
" It may be an imported class.
let imports = []
for fqn in s:GetImports('imports_fqn')
if fqn =~# '\<' . var . '\>$'
call add(imports, fqn)
endif
endfor
if len(imports) > 1
echoerr 'Imports conflicts between ' . join(imports, ' and ')
endif
" Search in this buffer
let matchs = s:SearchNameInAST(javacomplete#parse(), var, java_parser#MakePos(line, col), 1)
let hint = var . ' '
if !empty(matchs)
let tree = matchs[len(matchs)-1]
let coor = java_parser#DecodePos(tree.pos)
let hint .= '(' . (coor.line+1) . ',' . (coor.col) . ') '
let hint .= getline(coor.line+1) "string(tree)
else
for fqn in imports
let ci = s:DoGetClassInfo(fqn)
if !empty(ci)
let hint .= ' ' . fqn
endif
" TODO: get javadoc
endfor
endif
return hint
endfu
" java {{{1
fu! s:IsBuiltinType(name)
return index(s:PRIMITIVE_TYPES, a:name) >= 0
endfu
fu! s:IsKeyword(name)
return index(s:KEYWORDS, a:name) >= 0
endfu
fu! s:HasKeyword(name)
return a:name =~# s:RE_KEYWORDS
endfu
fu! s:TailOfQN(qn)
return a:qn[strridx(a:qn, '.')+1:]
endfu
" options {{{1
" Methods to search declaration {{{2
" 1 - by builtin searchdecl()
" 2 - by special Searchdecl()
" 4 - by java_parser
fu! javacomplete#GetSearchdeclMethod()
if &ft == 'jsp'
return 1
endif
return exists('s:searchdecl') ? s:searchdecl : 4
endfu
fu! javacomplete#SetSearchdeclMethod(method)
let s:searchdecl = a:method
endfu
" JDK1.1 {{{2
fu! javacomplete#UseJDK11()
let s:isjdk11 = 1
endfu
" java compiler {{{2
fu! javacomplete#GetCompiler()
return exists('s:compiler') && s:compiler !~ '^\s*$' ? s:compiler : 'javac'
endfu
fu! javacomplete#SetCompiler(compiler)
let s:compiler = a:compiler
endfu
" jvm launcher {{{2
fu! javacomplete#GetJVMLauncher()
return exists('s:interpreter') && s:interpreter !~ '^\s*$' ? s:interpreter : 'java'
endfu
fu! javacomplete#SetJVMLauncher(interpreter)
if javacomplete#GetJVMLauncher() != a:interpreter
let s:cache = {}
endif
let s:interpreter = a:interpreter
endfu
" sourcepath {{{2
fu! javacomplete#AddSourcePath(s)
if !isdirectory(a:s)
echoerr 'invalid source path: ' . a:s
return
endif
let path = fnamemodify(a:s, ':p:h')
if !exists('s:sourcepath')
let s:sourcepath = [path]
elseif index(s:sourcepath, path) == -1
call add(s:sourcepath, path)
endif
endfu
fu! javacomplete#DelSourcePath(s)
if !exists('s:sourcepath') || !isdirectory(a:s)| return | endif
let idx = index(s:sourcepath, a:s)
if idx != -1
call remove(s:sourcepath, idx)
endif
endfu
fu! javacomplete#SetSourcePath(s)
let paths = type(a:s) == type([]) ? a:s : split(a:s, javacomplete#GetClassPathSep())
let s:sourcepath = []
for path in paths
if isdirectory(path)
call add(s:sourcepath, fnamemodify(path, ':p:h'))
endif
endfor
endfu
" return the sourcepath. Given argument, add current path or default package root path
" NOTE: Avoid path duplicate, otherwise globpath() will return duplicate result.
fu! javacomplete#GetSourcePath(...)
return join(s:GetSourceDirs(a:0 > 0 && a:1 ? expand('%:p') : ''), s:PATH_SEP)
endfu
fu! s:GetSourceDirs(filepath, ...)
let dirs = exists('s:sourcepath') ? s:sourcepath : []
if !empty(a:filepath)
let filepath = fnamemodify(a:filepath, ':p:h')
" get source path according to file path and package name
let packageName = a:0 > 0 ? a:1 : s:GetPackageName()
if packageName != ''
let path = fnamemodify(substitute(filepath, packageName, '', 'g'), ':p:h')
if index(dirs, path) < 0
call add(dirs, path)
endif
endif
" Consider current path as a sourcepath
if index(dirs, filepath) < 0
call add(dirs, filepath)
endif
endif
return dirs
endfu
" classpath {{{2
fu! javacomplete#AddClassPath(s)
if !isdirectory(a:s)
echoerr 'invalid classpath: ' . a:s
return
endif
if !exists('s:classpath')
let s:classpath = [a:s]
elseif index(s:classpath, a:s) == -1
call add(s:classpath, a:s)
endif
let s:cache = {}
endfu
fu! javacomplete#DelClassPath(s)
if !exists('s:classpath') | return | endif
let idx = index(s:classpath, a:s)
if idx != -1
call remove(s:classpath, idx)
endif
endfu
fu! javacomplete#SetClassPath(s)
if type(a:s) == type("")
let s:classpath = split(a:s, javacomplete#GetClassPathSep())
elseif type(a:s) == type([])
let s:classpath = a:s
endif
let s:cache = {}
endfu
fu! javacomplete#GetClassPathSep()
return s:PATH_SEP
endfu
fu! javacomplete#GetClassPath()
return exists('s:classpath') ? join(s:classpath, javacomplete#GetClassPathSep()) : ''
endfu
" s:GetClassPath() {{{2
fu! s:GetClassPath()
let path = s:GetJavaCompleteClassPath() . javacomplete#GetClassPathSep()
if &ft == 'jsp'
let path .= s:GetClassPathOfJsp()
endif
if exists('b:classpath') && b:classpath !~ '^\s*$'
return path . b:classpath
endif
if exists('s:classpath')
return path . javacomplete#GetClassPath()
endif
if exists('g:java_classpath') && g:java_classpath !~ '^\s*$'
return path . g:java_classpath
endif
return path . $CLASSPATH
endfu
fu! s:GetJavaCompleteClassPath()
" remove *.class from wildignore if it exists, so that globpath doesn't ignore Reflection.class
" vim versions >= 702 can add the 1 flag to globpath which ignores '*.class" in wildingore
let has_class = 0
if &wildignore =~# "*.class"
set wildignore-=*.class
let has_class = 1
endif
let classfile = globpath(&rtp, 'autoload/Reflection.class')
if classfile == ''
let classfile = globpath($HOME, 'Reflection.class')
endif
if classfile == ''
" try to find source file and compile to $HOME
let srcfile = globpath(&rtp, 'autoload/Reflection.java')
if srcfile != ''
exe '!' . javacomplete#GetCompiler() . ' -d "' . $HOME . '" "' . srcfile . '"'
let classfile = globpath($HOME, 'Reflection.class')
if classfile == ''
echo srcfile . ' can not be compiled. Please check it'
endif
else
echo 'No Reflection.class found in $HOME or any autoload directory of the &rtp. And no Reflection.java found in any autoload directory of the &rtp to compile.'
endif
endif
" add *.class to wildignore if it existed before
if has_class == 1
set wildignore+=*.class
endif
return fnamemodify(classfile, ':p:h')
endfu
fu! s:GetClassPathOfJsp()
if exists('b:classpath_jsp')
return b:classpath_jsp
endif
let b:classpath_jsp = ''
let path = expand('%:p:h')
while 1
if isdirectory(path . '/WEB-INF' )
if isdirectory(path . '/WEB-INF/classes')
let b:classpath_jsp .= s:PATH_SEP . path . '/WEB-INF/classes'
endif
if isdirectory(path . '/WEB-INF/lib')
let libs = globpath(path . '/WEB-INF/lib', '*.jar')
if libs != ''
let b:classpath_jsp .= s:PATH_SEP . substitute(libs, "\n", s:PATH_SEP, 'g')
endif
endif
return b:classpath_jsp
endif
let prev = path
let path = fnamemodify(path, ":p:h:h")
if path == prev
break
endif
endwhile
return ''
endfu
" return only classpath which are directories
fu! s:GetClassDirs()
let dirs = []
for path in split(s:GetClassPath(), s:PATH_SEP)
if isdirectory(path)
call add(dirs, fnamemodify(path, ':p:h'))
endif
endfor
return dirs
endfu
" s:GetPackageName() {{{2
fu! s:GetPackageName()
let lnum_old = line('.')
let col_old = col('.')
call cursor(1, 1)
let lnum = search('^\s*package[ \t\r\n]\+\([a-zA-Z][a-zA-Z0-9.]*\);', 'w')
let packageName = substitute(getline(lnum), '^\s*package\s\+\([a-zA-Z][a-zA-Z0-9.]*\);', '\1', '')
call cursor(lnum_old, col_old)
return packageName
endfu
fu! s:IsStatic(modifier)
return a:modifier[strlen(a:modifier)-4]
endfu
" utilities {{{1
" Convert a file name into the unique form.
" Similar with fnamemodify(). NOTE that ':gs' should not be used.
fu! s:fnamecanonize(fname, mods)
return fnamemodify(a:fname, a:mods . ':gs?[\\/]\+?/?')
endfu
" Similar with filter(), but returns a new list instead of operating in-place.
" `item` has the value of the current item.
fu! s:filter(expr, string)
if type(a:expr) == type([])
let result = []
for item in a:expr
if eval(a:string)
call add(result, item)
endif
endfor
return result
else
let result = {}
for item in items(a:expr)
if eval(a:string)
let result[item[0]] = item[1]
endif
endfor
return result
endif
endfu
fu! s:Index(list, expr, key)
let i = 0
while i < len(a:list)
if get(a:list[i], a:key, '') == a:expr
return i
endif
let i += 1
endwhile
return -1
endfu
fu! s:Match(list, expr, key)
let i = 0
while i < len(a:list)
if get(a:list[i], a:key, '') =~ a:expr
return i
endif
let i += 1
endwhile
return -1
endfu
fu! s:KeepCursor(cmd)
let lnum_old = line('.')
let col_old = col('.')
exe a:cmd
call cursor(lnum_old, col_old)
endfu
fu! s:InCommentOrLiteral(line, col)
if has("syntax") && &ft != 'jsp'
return synIDattr(synID(a:line, a:col, 1), "name") =~? '\(Comment\|String\|Character\)'
endif
endfu
function! s:InComment(line, col)
if has("syntax") && &ft != 'jsp'
return synIDattr(synID(a:line, a:col, 1), "name") =~? 'comment'
endif
" if getline(a:line) =~ '\s*\*'
" return 1
" endif
" let idx = strridx(getline(a:line), '//')
" if idx >= 0 && idx < a:col
" return 1
" endif
" return 0
endfunction
" set string literal empty, remove comments, trim begining or ending spaces
" test case: ' sb. /* block comment*/ append( "stringliteral" ) // comment '
function! s:Prune(str, ...)
if a:str =~ '^\s*$' | return '' | endif
let str = substitute(a:str, '"\(\\\(["\\''ntbrf]\)\|[^"]\)*"', '""', 'g')
let str = substitute(str, '\/\/.*', '', 'g')
let str = s:RemoveBlockComments(str)
return a:0 > 0 ? str : str . ' '
endfunction
" Given argument, replace block comments with spaces of same number
fu! s:RemoveBlockComments(str, ...)
let result = a:str
let ib = match(result, '\/\*')
let ie = match(result, '\*\/')
while ib != -1 && ie != -1 && ib < ie
let result = strpart(result, 0, ib) . (a:0 == 0 ? ' ' : repeat(' ', ie-ib+2)) . result[ie+2: ]
let ib = match(result, '\/\*')
let ie = match(result, '\*\/')
endwhile
return result
endfu
fu! s:Trim(str)
let str = substitute(a:str, '^\s*', '', '')
return substitute(str, '\s*$', '', '')
endfu
fu! s:SplitAt(str, index)
return [strpart(a:str, 0, a:index+1), strpart(a:str, a:index+1)]
endfu
" TODO: search pair used in string, like
" 'create(ao.fox("("), new String).foo().'
function! s:GetMatchedIndexEx(str, idx, one, another)
let pos = a:idx
while 0 <= pos && pos < len(a:str)
let pos = match(a:str, '['. a:one . escape(a:another, ']') .']', pos+1)
if pos != -1
if a:str[pos] == a:one
let pos = s:GetMatchedIndexEx(a:str, pos, a:one, a:another)
elseif a:str[pos] == a:another
break
endif
endif
endwhile
return 0 <= pos && pos < len(a:str) ? pos : -3
endfunction
function! s:SearchPairBackward(str, idx, one, another)
let idx = a:idx
let n = 0
while idx >= 0
let idx -= 1
if a:str[idx] == a:one
if n == 0
break
endif
let n -= 1
elseif a:str[idx] == a:another " nested
let n += 1
endif
endwhile
return idx
endfunction
fu! s:CountDims(str)
if match(a:str, '[[\]]') == -1
return 0
endif
" int[] -> [I, String[] ->
let dims = len(matchstr(a:str, '^[\+'))
if dims == 0
let idx = len(a:str)-1
while idx >= 0 && a:str[idx] == ']'
let dims += 1
let idx = s:SearchPairBackward(a:str, idx, '[', ']')-1
endwhile
endif
return dims
endfu
fu! s:GotoUpperBracket()
let searched = 0
while (!searched)
call search('[{}]', 'bW')
if getline('.')[col('.')-1] == '}'
normal %
else
let searched = 1
endif
endwhile
endfu
" Improve recognition of variable declaration using my version of searchdecl() for accuracy reason.
" TODO:
fu! s:Searchdecl(name, ...)
let global = a:0 > 0 ? a:1 : 0
let thisblock = a:0 > 1 ? a:2 : 1
call search('\<' . a:name . '\>', 'bW')
let lnum_old = line('.')
let col_old = col('.')
call s:GotoUpperBracket()
let lnum_bracket = line('.')
let col_bracket = col('.')
call search('\<' . a:name . '\>', 'W', lnum_old)
if line('.') != lnum_old || col('.') != col_old
return 0
endif
" search globally
if global
call cursor(lnum_bracket, col_bracket)
" search backward
while (1)
if search('\([{}]\|\<' . a:name . '\>\)', 'bW') == 0
break
endif
if s:InComment(line('.'), col('.')) "|| s:InStringLiteral()
continue
endif
let cword = expand('<cword>')
if cword == a:name
return 0
endif
if getline('.')[col('.')-1] == '}'
normal %
endif
endwhile
call cursor(lnum_old, col_old)
" search forward
call search('[{};]', 'W')
while (1)
if search('\([{}]\|\<' . a:name . '\>\)', 'W') == 0
break
endif
if s:InComment(line('.'), col('.')) "|| s:InStringLiteral()
continue
endif
let cword = expand('<cword>')
if cword == a:name
return 0
endif
if getline('.')[col('.')-1] == '{'
normal %
endif
endwhile
endif
return 1
endfu
"nmap <F8> :call <SID>Searchdecl(expand('<cword>'))<CR>
fu! javacomplete#Exe(cmd)
exe a:cmd
endfu
" cache utilities {{{1
" key of s:files for current buffer. It may be the full path of current file or the bufnr of unnamed buffer, and is updated when BufEnter, BufLeave.
fu! s:GetCurrentFileKey()
return has("autocmd") ? s:curfilekey : empty(expand('%')) ? bufnr('%') : expand('%:p')
endfu
fu! s:SetCurrentFileKey()
let s:curfilekey = empty(expand('%')) ? bufnr('%') : expand('%:p')
endfu
call s:SetCurrentFileKey()
if has("autocmd")
autocmd BufEnter *.java call s:SetCurrentFileKey()
autocmd FileType java call s:SetCurrentFileKey()
endif
" Log utilities {{{1
fu! s:WatchVariant(variant)
"echoerr a:variant
endfu
" level
" 5 off/fatal
" 4 error
" 3 warn
" 2 info
" 1 debug
" 0 trace
fu! javacomplete#SetLogLevel(level)
let s:loglevel = a:level
endfu
fu! javacomplete#GetLogLevel()
return exists('s:loglevel') ? s:loglevel : 3
endfu
fu! javacomplete#GetLogContent()
return s:log
endfu
fu! s:Trace(msg)
call s:Log(0, a:msg)
endfu
fu! s:Debug(msg)
call s:Log(1, a:msg)
endfu
fu! s:Info(msg)
call s:Log(2, a:msg)
endfu
fu! s:Log(level, key, ...)
if a:level >= javacomplete#GetLogLevel()
echo a:key
call add(s:log, a:key)
endif
endfu
fu! s:System(cmd, caller)
call s:WatchVariant(a:cmd)
let t = reltime()
let res = system(a:cmd)
call s:Debug(reltimestr(reltime(t)) . 's to exec "' . a:cmd . '" by ' . a:caller)
return res
endfu
" functions to get information {{{1
" utilities {{{2
fu! s:MemberCompare(m1, m2)
return a:m1['n'] == a:m2['n'] ? 0 : a:m1['n'] > a:m2['n'] ? 1 : -1
endfu
fu! s:Sort(ci)
let ci = a:ci
if has_key(ci, 'fields')
call sort(ci['fields'], 's:MemberCompare')
endif
if has_key(ci, 'methods')
call sort(ci['methods'], 's:MemberCompare')
endif
return ci
endfu
" Function to run Reflection {{{2
fu! s:RunReflection(option, args, log)
let classpath = ''
if !exists('s:isjdk11')
let classpath = ' -classpath "' . s:GetClassPath() . '" '
endif
let cmd = javacomplete#GetJVMLauncher() . classpath . ' Reflection ' . a:option . ' "' . a:args . '"'
return s:System(cmd, a:log)
endfu
" class information {{{2
" The standard search order of a FQN is as follows:
" 1. a file-name toplevel type or static member type accessed by the file-name type declared in source files
" 2. other types declared in source files
" 3. an accessible loadable type.
" parameters:
" fqns - list of fqn
" srcpaths - a comma-separated list of directory names.
" a:1 - search all.
" return a dict of fqn -> type info
" precondition:
" NOTE: call expand() to convert path to standard form
fu! s:DoGetTypeInfoForFQN(fqns, srcpath, ...)
if empty(a:fqns) || empty(a:srcpath)
return
endif
" 1
let files = {} " fqn -> java file path
for fqn in a:fqns
" toplevel type
let filepath = globpath(a:srcpath, substitute(fqn, '\.', '/', 'g') . '.java')
if filepath != ''
let files[fqn] = expand(filepath)
" nested type
elseif stridx(fqn, '.') >= 0
let idents = split(fqn, '\.')
let i = len(idents)-2
while i >= 0
let filepath = globpath(a:srcpath, join(idents[:i], '/') . '.java')
if filepath != ''
let files[fqn] = expand(filepath)
break
endif
let i -= 1
endwhile
endif
endfor
" 2
let dirs = {} " dir.idents -> names of nested type
" dir.qfitems -> items of quick fix
" dir.fqn -> fqn
for fqn in a:fqns
if !has_key(files, fqn)
for path in split(a:srcpath, ',')
let idents = split(fqn, '\.')
let i = len(idents)-2
while i >= 0
let dirpath = path . '/' . join(idents[:i], '/')
" it is a package
if isdirectory(dirpath)
let dirs[fnamemodify(dirpath, ':p:h:gs?[\\/]\+?/?')] = {'fqn': fqn, 'idents': idents[i+1:]}
break
endif
let i -= 1
endwhile
endfor
endif
endfor
if !empty(dirs)
let items = {} " dir -> items of quick fix
let filepatterns = ''
for dirpath in keys(dirs)
let filepatterns .= escape(dirpath, ' \') . '/*.java '
endfor
let cwd = fnamemodify(expand('%:p:h'), ':p:h:gs?[\\/]\+?/?')
exe 'vimgrep /\s*' . s:RE_TYPE_DECL . '/jg ' . filepatterns
for item in getqflist()
if item.text !~ '^\s*\*\s\+'
let text = matchstr(s:Prune(item.text, -1), '\s*' . s:RE_TYPE_DECL)
if text != ''
let subs = split(substitute(text, '\s*' . s:RE_TYPE_DECL, '\1;\2;\3', ''), ';', 1)
let dirpath = fnamemodify(bufname(item.bufnr), ':p:h:gs?[\\/]\+?/?')
let idents = dirs[dirpath].idents
if index(idents, subs[2]) >= 0 && (subs[0] =~ '\C\<public\>' || dirpath == cwd) " FIXME?
let item.subs = subs
let dirs[dirpath].qfitems = get(dirs[dirpath], 'qfitems', []) + [item]
endif
endif
endif
endfor
for dirpath in keys(dirs)
" a. names of nested type must be existed in the same file
" PackageName.NonFileNameTypeName.NestedType.NestedNestedType
let qfitems = get(dirs[dirpath], 'qfitems', [])
let nr = 0
for ident in dirs[dirpath].idents
for item in qfitems
if item.subs[2] == ident
let nr += 1
endif
endfor
endfor
if nr == len(dirs[dirpath].idents)
" b. TODO: Check whether one enclosed another is correct
let files[fqn] = expand(bufname(qfitems[0].bufnr))
endif
endfor
endif
call s:Info('FQN1&2: ' . string(keys(files)))
for fqn in keys(files)
if !has_key(s:cache, fqn) || get(get(s:files, files[fqn], {}), 'modifiedtime', 0) != getftime(files[fqn])
let ti = s:GetClassInfoFromSource(fqn[strridx(fqn, '.')+1:], files[fqn])
if !empty(ti)
let s:cache[fqn] = s:Sort(ti)
endif
endif
if (a:0 == 0 || !a:1)
return
endif
endfor
" 3
let commalist = ''
for fqn in a:fqns
if has_key(s:cache, fqn) && (a:0 == 0 || !a:1)
return
else "if stridx(fqn, '.') >= 0
let commalist .= fqn . ','
endif
endfor
if !empty(commalist)
let res = s:RunReflection('-E', commalist, 'DoGetTypeInfoForFQN in Batch')
if res =~ "^{'"
let dict = eval(res)
for key in keys(dict)
if !has_key(s:cache, key)
if type(dict[key]) == type({})
let s:cache[key] = s:Sort(dict[key])
elseif type(dict[key]) == type([])
let s:cache[key] = sort(dict[key])
endif
endif
endfor
endif
endif
endfu
" a:1 filepath
" a:2 package name
fu! s:DoGetClassInfo(class, ...)
if has_key(s:cache, a:class)
return s:cache[a:class]
endif
" array type: TypeName[] or '[I' or '[[Ljava.lang.String;'
if a:class[-1:] == ']' || a:class[0] == '['
return s:ARRAY_TYPE_INFO
endif
" either this or super is not qualified
if a:class == 'this' || a:class == 'super'
if &ft == 'jsp'
let ci = s:DoGetReflectionClassInfo('javax.servlet.jsp.HttpJspPage')
if a:class == 'this'
" TODO: search methods defined in <%! [declarations] %>
" search methods defined in other jsp files included
" avoid including self directly or indirectly
endif
return ci
endif
call s:Info('A0. ' . a:class)
" this can be a local class or anonymous class as well as static type
let t = get(s:SearchTypeAt(javacomplete#parse(), java_parser#MakePos(line('.')-1, col('.')-1)), -1, {})
if !empty(t)
" What will be returned for super?
" - the protected or public inherited fields and methods. No ctors.
" - the (public static) fields of interfaces.
" - the methods of the Object class.
" What will be returned for this?
" - besides the above, all fields and methods of current class. No ctors.
return s:Sort(s:Tree2ClassInfo(t))
"return s:Sort(s:AddInheritedClassInfo(a:class == 'this' ? s:Tree2ClassInfo(t) : {}, t, 1))
endif
return {}
endif
if a:class !~ '^\s*' . s:RE_QUALID . '\s*$' || s:HasKeyword(a:class)
return {}
endif
let typename = substitute(a:class, '\s', '', 'g')
let filekey = a:0 > 0 ? a:1 : s:GetCurrentFileKey()
let packagename = a:0 > 1 ? a:2 : s:GetPackageName()
let srcpath = join(s:GetSourceDirs(a:0 > 0 && a:1 != bufnr('%') ? a:1 : expand('%:p'), packagename), ',')
let names = split(typename, '\.')
" remove the package name if in the same packge
if len(names) > 1
if packagename == join(names[:-2], '.')
let names = names[-1:]
endif
endif
" a FQN
if len(names) > 1
call s:DoGetTypeInfoForFQN([typename], srcpath)
let ci = get(s:cache, typename, {})
if get(ci, 'tag', '') == 'CLASSDEF'
return s:cache[typename]
elseif get(ci, 'tag', '') == 'PACKAGE'
return {}
endif
endif
" The standard search order of a simple type name is as follows:
" 1. The current type including inherited types.
" 2. A nested type of the current type.
" 3. Explicitly named imported types (single type import).
" 4. Other types declared in the same package. Not only current directory.
" 5. Implicitly named imported types (import on demand).
" 1 & 2.
" NOTE: inherited types are treated as normal
if filekey == s:GetCurrentFileKey()
let simplename = typename[strridx(typename, '.')+1:]
if s:FoundClassDeclaration(simplename) != 0
call s:Info('A1&2')
let ci = s:GetClassInfoFromSource(simplename, '%')
" do not cache it
if !empty(ci)
return ci
endif
endif
else
let ci = s:GetClassInfoFromSource(typename, filekey)
if !empty(ci)
return ci
endif
endif
" 3.
" NOTE: PackageName.Ident, TypeName.Ident
let fqn = s:SearchSingleTypeImport(typename, s:GetImports('imports_fqn', filekey))
if !empty(fqn)
call s:Info('A3')
call s:DoGetTypeInfoForFQN([fqn], srcpath)
let ti = get(s:cache, fqn, {})
if get(ti, 'tag', '') != 'CLASSDEF'
" TODO: mark the wrong import declaration.
endif
return ti
endif
" 4 & 5
" NOTE: Keeps the fqn of the same package first!!
call s:Info('A4&5')
let fqns = [empty(packagename) ? typename : packagename . '.' . typename]
for p in s:GetImports('imports_star', filekey)
call add(fqns, p . typename)
endfor
call s:DoGetTypeInfoForFQN(fqns, srcpath)
for fqn in fqns
if has_key(s:cache, fqn)
return get(s:cache[fqn], 'tag', '') == 'CLASSDEF' ? s:cache[fqn] : {}
endif
endfor
return {}
endfu
" Rules of overriding and hiding:
" 1. Fields cannot be overridden; they can only be hidden.
" In the subclass, the hidden field of superclass can no longer be accessed
" directly by its simple name. `super` or another reference must be used.
" 2. A method can be overriden only if it is accessible.
" When overriding methods, both the signature and return type must be the
" same as in the superclass.
" 3. Static members cannot be overridden; they can only be hidden
" -- whether a field or a method. But hiding static members has little effect,
" because static should be accessed via the name of its declaring class.
" Given optional argument, add protected, default (package) access, private members.
"fu! s:MergeClassInfo(ci, another, ...)
" if empty(a:another) | return a:ci | endif
"
" if empty(a:ci)
" let ci = copy(a:another)
"" if a:0 > 0 && a:1
"" call extend(ci.fields, get(a:another, 'declared_fields', []))
"" call extend(ci.methods, get(a:another, 'declared_methods', []))
"" endif
" return ci
" endif
"
" call extend(a:ci.methods, a:another.methods)
"
" for f in a:another.fields
" if s:Index(a:ci.fields, f.n, 'n') < 0
" call add(a:ci.fields, f)
" endif
" endfor
" return a:ci
"endfu
" Parameters:
" class the qualified class name
" Return: TClassInfo or {} when not found
" See ClassInfoFactory.getClassInfo() in insenvim.
function! s:DoGetReflectionClassInfo(fqn)
if !has_key(s:cache, a:fqn)
let res = s:RunReflection('-C', a:fqn, 's:DoGetReflectionClassInfo')
if res =~ '^{'
let s:cache[a:fqn] = s:Sort(eval(res))
elseif res =~ '^['
for type in eval(res)
if get(type, 'name', '') != ''
let s:cache[type.name] = s:Sort(type)
endif
endfor
else
let b:errormsg = res
endif
endif
return get(s:cache, a:fqn, {})
endfunction
fu! s:GetClassInfoFromSource(class, filename)
let ci = {}
if len(tagfiles()) > 0
let ci = s:DoGetClassInfoFromTags(a:class)
endif
if empty(ci)
call s:Info('Use java_parser.vim to generate class information')
let unit = javacomplete#parse(a:filename)
let targetPos = a:filename == '%' ? java_parser#MakePos(line('.')-1, col('.')-1) : -1
for t in s:SearchTypeAt(unit, targetPos, 1)
if t.name == a:class
let t.filepath = a:filename == '%' ? s:GetCurrentFileKey() : expand(a:filename)
return s:Tree2ClassInfo(t)
"return s:AddInheritedClassInfo(s:Tree2ClassInfo(t), t)
endif
endfor
endif
return ci
endfu
fu! s:Tree2ClassInfo(t)
let t = a:t
" fill fields and methods
let t.fields = []
let t.methods = []
let t.ctors = []
let t.classes = []
for def in t.defs
if def.tag == 'METHODDEF'
call add(def.n == t.name ? t.ctors : t.methods, def)
elseif def.tag == 'VARDEF'
call add(t.fields, def)
elseif def.tag == 'CLASSDEF'
call add(t.classes, t.fqn . '.' . def.name)
endif
endfor
" convert type name in extends to fqn for class defined in source files
if !has_key(a:t, 'classpath') && has_key(a:t, 'extends')
if has_key(a:t, 'filepath') && a:t.filepath != s:GetCurrentFileKey()
let filepath = a:t.filepath
let packagename = get(s:files[filepath].unit, 'package', '')
else
let filepath = expand('%:p')
let packagename = s:GetPackageName()
endif
let extends = a:t.extends
let i = 0
while i < len(extends)
let ci = s:DoGetClassInfo(java_parser#type2Str(extends[i]), filepath, packagename)
if has_key(ci, 'fqn')
let extends[i] = ci.fqn
endif
let i += 1
endwhile
endif
return t
endfu
"fu! s:AddInheritedClassInfo(ci, t, ...)
" let ci = a:ci
" " add inherited fields and methods
" let list = []
" for i in get(a:t, 'extends', [])
" call add(list, java_parser#type2Str(i))
" endfor
"
" if has_key(a:t, 'filepath') && a:t.filepath != expand('%:p')
" let filepath = a:t.filepath
" let props = get(s:files, a:t.filepath, {})
" let packagename = get(props.unit, 'package', '')
" else
" let filepath = expand('%:p')
" let packagename = s:GetPackageName()
" endif
"
" for id in list
" let ci = s:MergeClassInfo(ci, s:DoGetClassInfo(id, filepath, packagename), a:0 > 0 && a:1)
" endfor
" return ci
"endfu
" To obtain information of the class in current file or current folder, or
" even in current project.
function! s:DoGetClassInfoFromTags(class)
" find tag of a:class declaration
let tags = taglist('^' . a:class)
let filename = ''
let cmd = ''
for tag in tags
if has_key(tag, 'kind')
if tag['kind'] == 'c'
let filename = tag['filename']
let cmd = tag['cmd']
break
endif
endif
endfor
let tags = taglist('^' . (empty(b:incomplete) ? '.*' : b:incomplete) )
if filename != ''
call filter(tags, "v:val['filename'] == '" . filename . "' && has_key(v:val, 'class') ? v:val['class'] == '" . a:class . "' : 1")
endif
let ci = {'name': a:class}
" extends and implements
let ci['ctors'] = []
let ci['fields'] = []
let ci['methods'] = []
" members
for tag in tags
let member = {'n': tag['name']}
" determine kind
let kind = 'm'
if has_key(tag, 'kind')
let kind = tag['kind']
endif
let cmd = tag['cmd']
if cmd =~ '\<static\>'
let member['m'] = '1000'
else
let member['m'] = ''
endif
let desc = substitute(cmd, '/^\s*', '', '')
let desc = substitute(desc, '\s*{\?\s*$/$', '', '')
if kind == 'm'
" description
if cmd =~ '\<static\>'
let desc = substitute(desc, '\s\+static\s\+', ' ', '')
endif
let member['d'] = desc
let member['p'] = ''
let member['r'] = ''
if tag['name'] == a:class
call add(ci['ctors'], member)
else
call add(ci['methods'], member)
endif
elseif kind == 'f'
let member['t'] = substitute(desc, '\([a-zA-Z0-9_[\]]\)\s\+\<' . tag['name'] . '\>.*$', '\1', '')
call add(ci['fields'], member)
endif
endfor
return ci
endfu
" package information {{{2
fu! s:DoGetInfoByReflection(class, option)
if has_key(s:cache, a:class)
return s:cache[a:class]
endif
let res = s:RunReflection(a:option, a:class, 's:DoGetInfoByReflection')
if res =~ '^[{\[]'
let v = eval(res)
if type(v) == type([])
let s:cache[a:class] = sort(v)
elseif type(v) == type({})
if get(v, 'tag', '') =~# '^\(PACKAGE\|CLASSDEF\)$'
let s:cache[a:class] = v
else
call extend(s:cache, v, 'force')
endif
endif
unlet v
else
let b:errormsg = res
endif
return get(s:cache, a:class, {})
endfu
" search in members {{{2
" TODO: what about default access?
" public for all
" protected for this or super
" private for this
fu! s:CanAccess(mods, kind)
return (a:mods[-4:-4] || a:kind/10 == 0)
\ && (a:kind == 1 || a:mods[-1:]
\ || (a:mods[-3:-3] && (a:kind == 1 || a:kind == 2))
\ || (a:mods[-2:-2] && a:kind == 1))
endfu
fu! s:SearchMember(ci, name, fullmatch, kind, returnAll, memberkind, ...)
let result = [[], [], []]
if a:kind != 13
for m in (a:0 > 0 && a:1 ? [] : get(a:ci, 'fields', [])) + ((a:kind == 1 || a:kind == 2) ? get(a:ci, 'declared_fields', []) : [])
if empty(a:name) || (a:fullmatch ? m.n ==# a:name : m.n =~# '^' . a:name)
if s:CanAccess(m.m, a:kind)
call add(result[2], m)
endif
endif
endfor
for m in (a:0 > 0 && a:1 ? [] : get(a:ci, 'methods', [])) + ((a:kind == 1 || a:kind == 2) ? get(a:ci, 'declared_methods', []) : [])
if empty(a:name) || (a:fullmatch ? m.n ==# a:name : m.n =~# '^' . a:name)
if s:CanAccess(m.m, a:kind)
call add(result[1], m)
endif
endif
endfor
endif
if a:kind/10 != 0
let types = get(a:ci, 'classes', [])
for t in types
if empty(a:name) || (a:fullmatch ? t[strridx(t, '.')+1:] ==# a:name : t[strridx(t, '.')+1:] =~# '^' . a:name)
if !has_key(s:cache, t) || !has_key(s:cache[t], 'flags') || a:kind == 1 || s:cache[t].flags[-1:]
call add(result[0], t)
endif
endif
endfor
endif
" key `classpath` indicates it is a loaded class from classpath
" All public members of a loaded class are stored in current ci
if !has_key(a:ci, 'classpath') || (a:kind == 1 || a:kind == 2)
for i in get(a:ci, 'extends', [])
let ci = s:DoGetClassInfo(java_parser#type2Str(i))
let members = s:SearchMember(ci, a:name, a:fullmatch, a:kind == 1 ? 2 : a:kind, a:returnAll, a:memberkind)
let result[0] += members[0]
let result[1] += members[1]
let result[2] += members[2]
endfor
endif
return result
endfu
" generate member list {{{2
fu! s:DoGetFieldList(fields)
let s = ''
for field in a:fields
let s .= "{'kind':'" . (s:IsStatic(field.m) ? "F" : "f") . "','word':'" . field.n . "','menu':'" . field.t . "','dup':1},"
endfor
return s
endfu
fu! s:DoGetMethodList(methods, ...)
let paren = a:0 == 0 || !a:1 ? '(' : ''
let s = ''
for method in a:methods
let s .= "{'kind':'" . (s:IsStatic(method.m) ? "M" : "m") . "','word':'" . method.n . paren . "','abbr':'" . method.n . "()','menu':'" . method.d . "','dup':'1'},"
endfor
return s
endfu
" kind:
" 0 - for instance, 1 - this, 2 - super, 3 - class, 4 - array, 5 - method result, 6 - primitive type
" 11 - for type, with `class` and static member and nested types.
" 12 - for import static, no lparen for static methods
" 13 - for import or extends or implements, only nested types
" 20 - for package
fu! s:DoGetMemberList(ci, kind)
if type(a:ci) != type({}) || a:ci == {}
return []
endif
let s = a:kind == 11 ? "{'kind': 'C', 'word': 'class', 'menu': 'Class'}," : ''
let members = s:SearchMember(a:ci, '', 1, a:kind, 1, 0, a:kind == 2)
" add accessible member types
if a:kind / 10 != 0
" Use dup here for member type can share name with field.
for class in members[0]
"for class in get(a:ci, 'classes', [])
let v = get(s:cache, class, {})
if v == {} || v.flags[-1:]
let s .= "{'kind': 'C', 'word': '" . substitute(class, a:ci.name . '\.', '\1', '') . "','dup':1},"
endif
endfor
endif
if a:kind != 13
let fieldlist = []
let sfieldlist = []
for field in members[2]
"for field in get(a:ci, 'fields', [])
if s:IsStatic(field['m'])
call add(sfieldlist, field)
elseif a:kind / 10 == 0
call add(fieldlist, field)
endif
endfor
let methodlist = []
let smethodlist = []
for method in members[1]
if s:IsStatic(method['m'])
call add(smethodlist, method)
elseif a:kind / 10 == 0
call add(methodlist, method)
endif
endfor
if a:kind / 10 == 0
let s .= s:DoGetFieldList(fieldlist)
let s .= s:DoGetMethodList(methodlist)
endif
let s .= s:DoGetFieldList(sfieldlist)
let s .= s:DoGetMethodList(smethodlist, a:kind == 12)
let s = substitute(s, '\<' . a:ci.name . '\.', '', 'g')
let s = substitute(s, '\<java\.lang\.', '', 'g')
let s = substitute(s, '\<\(public\|static\|synchronized\|transient\|volatile\|final\|strictfp\|serializable\|native\)\s\+', '', 'g')
endif
return eval('[' . s . ']')
endfu
" interface {{{2
function! s:GetMemberList(class)
if s:IsBuiltinType(a:class)
return []
endif
return s:DoGetMemberList(s:DoGetClassInfo(a:class), 0)
endfunction
fu! s:GetStaticMemberList(class)
return s:DoGetMemberList(s:DoGetClassInfo(a:class), 11)
endfu
function! s:GetConstructorList(class)
let ci = s:DoGetClassInfo(a:class)
if empty(ci)
return []
endif
let s = ''
for ctor in get(ci, 'ctors', [])
let s .= "{'kind': '+', 'word':'". a:class . "(','abbr':'" . ctor.d . "','dup':1},"
endfor
let s = substitute(s, '\<java\.lang\.', '', 'g')
let s = substitute(s, '\<public\s\+', '', 'g')
return eval('[' . s . ']')
endfunction
" Name can be a (simple or qualified) package name, or a (simple or qualified)
" type name.
fu! s:GetMembers(fqn, ...)
let list = []
let isClass = 0
let v = s:DoGetInfoByReflection(a:fqn, '-E')
if type(v) == type([])
let list = v
elseif type(v) == type({}) && v != {}
if get(v, 'tag', '') == 'PACKAGE'
if b:context_type == s:CONTEXT_IMPORT_STATIC || b:context_type == s:CONTEXT_IMPORT
call add(list, {'kind': 'P', 'word': '*;'})
endif
if b:context_type != s:CONTEXT_PACKAGE_DECL
for c in sort(get(v, 'classes', []))
call add(list, {'kind': 'C', 'word': c})
endfor
endif
for p in sort(get(v, 'subpackages', []))
call add(list, {'kind': 'P', 'word': p})
endfor
else " elseif get(v, 'tag', '') == 'CLASSDEF'
let isClass = 1
let list += s:DoGetMemberList(v, b:context_type == s:CONTEXT_IMPORT || b:context_type == s:CONTEXT_NEED_TYPE ? 13 : b:context_type == s:CONTEXT_IMPORT_STATIC ? 12 : 11)
endif
endif
if !isClass
let list += s:DoGetPackageInfoInDirs(a:fqn, b:context_type == s:CONTEXT_PACKAGE_DECL)
endif
return list
endfu
" a:1 incomplete mode
" return packages in classes directories or source pathes
fu! s:DoGetPackageInfoInDirs(package, onlyPackages, ...)
let list = []
let pathes = s:GetSourceDirs(expand('%:p'))
for path in s:GetClassDirs()
if index(pathes, path) <= 0
call add(pathes, path)
endif
endfor
let globpattern = a:0 > 0 ? a:package . '*' : substitute(a:package, '\.', '/', 'g') . '/*'
let matchpattern = a:0 > 0 ? a:package : a:package . '[\\/]'
for f in split(globpath(join(pathes, ','), globpattern), "\n")
for path in pathes
let idx = matchend(f, escape(path, ' \') . '[\\/]\?\C' . matchpattern)
if idx != -1
let name = (a:0 > 0 ? a:package : '') . strpart(f, idx)
if f[-5:] == '.java'
if !a:onlyPackages
call add(list, {'kind': 'C', 'word': name[:-6]})
endif
elseif name =~ '^' . s:RE_IDENTIFIER . '$' && isdirectory(f) && f !~# 'CVS$'
call add(list, {'kind': 'P', 'word': name})
endif
endif
endfor
endfor
return list
endfu
" }}}
"}}}
" vim:set fdm=marker sw=2 nowrap: