rc/vim/autoload/javacomplete.vim

2933 lines
87 KiB
VimL
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

" 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: