2933 lines
87 KiB
VimL
2933 lines
87 KiB
VimL
" 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:
|