216 lines
7.8 KiB
VimL
216 lines
7.8 KiB
VimL
|
" File: cocoacomplete.vim (part of the cocoa.vim plugin)
|
||
|
" Author: Michael Sanders (msanders42 [at] gmail [dot] com)
|
||
|
" Last Updated: June 30, 2009
|
||
|
" Description: An omni-completion plugin for Cocoa/Objective-C.
|
||
|
|
||
|
let s:lib_dir = fnameescape(expand('<sfile>:p:h:h:h').'/lib/')
|
||
|
let s:cocoa_indexes = s:lib_dir.'cocoa_indexes/'
|
||
|
|
||
|
if !isdirectory(s:cocoa_indexes)
|
||
|
echom 'Error in cocoacomplete.vim: could not find ~/.vim/lib/cocoa_indexes directory'
|
||
|
endif
|
||
|
|
||
|
fun! objc#cocoacomplete#Complete(findstart, base)
|
||
|
if a:findstart
|
||
|
" Column where completion starts:
|
||
|
return match(getline('.'), '\k\+\%'.col('.').'c')
|
||
|
else
|
||
|
let matches = []
|
||
|
let complete_type = s:GetCompleteType(line('.'), col('.') - 1)
|
||
|
|
||
|
if complete_type == 'methods'
|
||
|
call s:Complete(a:base, ['alloc', 'init', 'retain', 'release',
|
||
|
\ 'autorelease', 'retainCount',
|
||
|
\ 'description', 'class', 'superclass',
|
||
|
\ 'self', 'zone', 'isProxy', 'hash'])
|
||
|
let obj_pos = s:GetObjPos(line('.'), col('.'))
|
||
|
call extend(matches, s:CompleteMethod(line('.'), obj_pos, a:base))
|
||
|
elseif complete_type == 'types' || complete_type == 'returntypes'
|
||
|
let opt_types = complete_type == 'returntypes' ? ['IBAction'] : []
|
||
|
call s:Complete(a:base, opt_types + ['void', 'id', 'BOOL', 'int',
|
||
|
\ 'double', 'float', 'char'])
|
||
|
call extend(matches, s:CompleteCocoa(a:base, 'classes', 'types',
|
||
|
\ 'notifications'))
|
||
|
elseif complete_type != ''
|
||
|
if complete_type =~ 'function_params$'
|
||
|
let complete_type = substitute(complete_type, 'function_params$', '', '')
|
||
|
let functions = s:CompleteFunction(a:base)
|
||
|
endif
|
||
|
|
||
|
" Mimic vim's dot syntax for other complete types (see :h ft).
|
||
|
let word = a:base == '' ? 'NS' : a:base
|
||
|
let args = [word] + split(complete_type, '\.')
|
||
|
call extend(matches, call('s:CompleteCocoa', args))
|
||
|
|
||
|
" List functions after the other items in the menu.
|
||
|
if exists('functions') | call extend(matches, functions) | endif
|
||
|
endif
|
||
|
return matches
|
||
|
endif
|
||
|
endf
|
||
|
|
||
|
fun s:GetCompleteType(lnum, col)
|
||
|
let scopelist = map(synstack(a:lnum, a:col), 'synIDattr(v:val, "name")')
|
||
|
if empty(scopelist) | return 'types' | endif
|
||
|
|
||
|
let current_scope = scopelist[-1]
|
||
|
let beforeCursor = strpart(getline(a:lnum), 0, a:col)
|
||
|
|
||
|
" Completing a function name:
|
||
|
if getline(a:lnum) =~ '\%'.(a:col + 1).'c\s*('
|
||
|
return 'functions'
|
||
|
elseif current_scope == 'objcSuperclass'
|
||
|
return 'classes'
|
||
|
" Inside brackets "[ ... ]":
|
||
|
elseif index(scopelist, 'objcMethodCall') != -1
|
||
|
return beforeCursor =~ '\[\k*$' ? 'classes' : 'methods'
|
||
|
" Inside parentheses "( ... )":
|
||
|
elseif current_scope == 'cParen'
|
||
|
" Inside parentheses for method definition:
|
||
|
if beforeCursor =~ '^\s*[-+]\s*([^{;]*'
|
||
|
return beforeCursor =~ '^\s*[-+]\s*([^)]*$' ? 'returntypes' : 'types'
|
||
|
" Inside function, loop, or conditional:
|
||
|
else
|
||
|
return 'classes.types.constants.function_params'
|
||
|
endif
|
||
|
" Inside braces "{ ... }" or after equals "=":
|
||
|
elseif current_scope == 'cBlock' || current_scope == 'objcAssign' || current_scope == ''
|
||
|
let type = current_scope == 'cBlock' ? 'types.constants.' : ''
|
||
|
let type = 'classes.'.type.'function_params'
|
||
|
|
||
|
if beforeCursor =~ 'IBOutlet' | return 'classes' | endif
|
||
|
return beforeCursor =~ '\v(^|[{};=\])]|return)\s*\k*$'? type : 'methods'
|
||
|
" Directly inside "@implementation ... @end" or "@interface ... @end"
|
||
|
elseif current_scope == 'objcImp' || current_scope == 'objcHeader'
|
||
|
" TODO: Complete delegate/subclass methods
|
||
|
endif
|
||
|
return ''
|
||
|
endf
|
||
|
|
||
|
" Adds item to the completion menu if they match the base.
|
||
|
fun s:Complete(base, items)
|
||
|
for item in a:items
|
||
|
if item =~ '^'.a:base | call complete_add(item) | endif
|
||
|
endfor
|
||
|
endf
|
||
|
|
||
|
" Returns position of "foo" in "[foo bar]" or "[baz bar: [foo bar]]".
|
||
|
fun s:GetObjPos(lnum, col)
|
||
|
let beforeCursor = strpart(getline(a:lnum), 0, a:col)
|
||
|
return match(beforeCursor, '\v.*(^|[\[=;])\s*\[*\zs[A-Za-z0-9_@]+') + 1
|
||
|
endf
|
||
|
|
||
|
" Completes a method given the position of the object and the method
|
||
|
" being completed.
|
||
|
fun s:CompleteMethod(lnum, col, method)
|
||
|
let class = s:GetCocoaClass(a:lnum, a:col)
|
||
|
if class == ''
|
||
|
let object = matchstr(getline(a:lnum), '\%'.a:col.'c\k\+')
|
||
|
let class = s:GetDeclWord(object)
|
||
|
if class == '' | return [] | endif
|
||
|
endif
|
||
|
let method = s:GetMethodName(a:lnum, a:col, a:method)
|
||
|
let matches = split(system(s:lib_dir.'get_methods.sh '.class.
|
||
|
\ '|grep "^'.method.'"'), "\n")
|
||
|
if exists('g:loaded_snips') " Use snipMate if it's installed
|
||
|
call objc#pum_snippet#Map()
|
||
|
else " Otherwise, only complete the method name.
|
||
|
call map(matches, 'substitute(v:val, ''\v:\zs.{-}\ze(\w+:|$)'', " ", "g")')
|
||
|
endif
|
||
|
|
||
|
" If dealing with a partial method name, only complete past it. E.g., in
|
||
|
" "[NSString stringWithCharacters:baz l|]" (where | is the cursor),
|
||
|
" only return "length", not "stringWithCharacters:length:".
|
||
|
if stridx(method, ':') != -1
|
||
|
let method = substitute(method, a:method.'$', '\\\\zs&', '')
|
||
|
call map(matches, 'matchstr(v:val, "'.method.'.*")')
|
||
|
endif
|
||
|
return matches
|
||
|
endf
|
||
|
|
||
|
" Returns the Cocoa class at a given position if it exists, or
|
||
|
" an empty string "" if it doesn't.
|
||
|
fun s:GetCocoaClass(lnum, col)
|
||
|
let class = matchstr(getline(a:lnum), '\%'.a:col.'c[A-Za-z0-9_"@]\+')
|
||
|
if class =~ '^@"' | return 'NSString' | endif " Treat @"..." as an NSString
|
||
|
let v:errmsg = ''
|
||
|
sil! hi cocoaClass
|
||
|
if v:errmsg == '' && synIDattr(synID(a:lnum, a:col, 0), 'name') == 'cocoaClass'
|
||
|
return class " If cocoaClass is defined, try using that.
|
||
|
endif
|
||
|
return system('grep ^'.class.' '.s:cocoa_indexes.'classes.txt') != ''
|
||
|
\ ? class : '' " Use grep as a fallback.
|
||
|
endf
|
||
|
|
||
|
" Returns the word before a variable declaration.
|
||
|
fun s:GetDeclWord(var)
|
||
|
let startpos = [line('.'), col('.')]
|
||
|
let line_found = searchdecl(a:var) != 0 ? 0 : line('.')
|
||
|
call cursor(startpos)
|
||
|
let matchstr = '\v(IBOutlet\s+)=\zs\k+\s*\ze\**\s*'
|
||
|
|
||
|
" If the declaration was not found in the implementation file, check
|
||
|
" the header.
|
||
|
if !line_found && expand('%:e') == 'm'
|
||
|
let header_path = expand('%:p:r').'.h'
|
||
|
if filereadable(header_path)
|
||
|
for line in readfile(header_path)
|
||
|
if line =~ '^\s*\(IBOutlet\)\=\s*\k*\s*\ze\**\s*'.a:var.'\s*'
|
||
|
return matchstr(line, matchstr)
|
||
|
endif
|
||
|
endfor
|
||
|
return ''
|
||
|
endif
|
||
|
endif
|
||
|
|
||
|
return matchstr(getline(line_found), matchstr.a:var)
|
||
|
endf
|
||
|
|
||
|
fun s:SearchList(list, regex)
|
||
|
for line in a:list
|
||
|
if line =~ a:regex
|
||
|
return line
|
||
|
endif
|
||
|
endfor
|
||
|
return ''
|
||
|
endf
|
||
|
|
||
|
" Returns the method name, ready to be searched by grep.
|
||
|
" The "base" word needs to be passed in separately, because
|
||
|
" Vim apparently removes it from the line during completions.
|
||
|
fun s:GetMethodName(lnum, col, base)
|
||
|
let line = getline(a:lnum)
|
||
|
let col = matchend(line, '\%'.a:col.'c\S\+\s\+') + 1 " Skip past class name.
|
||
|
if line =~ '\%'.col.'c\k\+:'
|
||
|
let base = a:base == '' ? '' : ' '.a:base
|
||
|
let method = matchstr(line, '\%'.col.'c.\{-}\ze]').base
|
||
|
return substitute(method, '\v\k+:\zs.{-}\ze(\s*\k+:|'.base.'$)', '[^:]*', 'g')
|
||
|
else
|
||
|
return a:base
|
||
|
endif
|
||
|
endf
|
||
|
|
||
|
" Completes Cocoa functions, using snippets for the parameters if possible.
|
||
|
fun s:CompleteFunction(word)
|
||
|
let files = s:cocoa_indexes.'functions.txt' " TODO: Add C functions.
|
||
|
let matches = split(system('zgrep -h "^'.a:word.'" '.files), "\n")
|
||
|
if exists('g:loaded_snips') " Use snipMate if it's installed
|
||
|
call objc#pum_snippet#Map()
|
||
|
else " Otherwise, just complete the function name
|
||
|
call map(matches, "{'word':matchstr(v:val, '^\\k\\+'), 'abbr':v:val}")
|
||
|
endif
|
||
|
return matches
|
||
|
endf
|
||
|
|
||
|
" Completes word for Cocoa "classes", "types", "notifications", or "constants".
|
||
|
" (supplied as the optional parameters).
|
||
|
fun s:CompleteCocoa(word, file, ...)
|
||
|
let files = ''
|
||
|
for file in [a:file] + a:000
|
||
|
let files .= ' '.s:cocoa_indexes.file.'.txt'
|
||
|
endfor
|
||
|
|
||
|
return split(system('grep -ho "^'.a:word.'[A-Za-z0-9_]*" '.files), "\n")
|
||
|
endf
|
||
|
" vim:noet:sw=4:ts=4:ft=vim
|