My Vim Configuration
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

609 lines
16 KiB

" Vim completion script
" Language: Javascript(node)
" Maintainer: Lin Zhang ( myhere.2009 AT gmail DOT com )
" Last Change: 2012-8-18 1:32:00
" save current dir
let s:nodejs_doc_file = expand('<sfile>:p:h') . '/nodejs-doc.vim'
let s:js_varname_reg = '[$a-zA-Z_][$a-zA-Z0-9_]*'
let s:js_obj_declare_type = {
\ 'global': 0,
\ 'require': 1,
\ 'constructor': 2
\ }
" settings
" default setting
let s:nodejs_complete_config = {
\ 'js_compl_fn': 'javascriptcomplete#CompleteJS',
\ 'max_node_compl_len': 15
\}
if exists('g:nodejs_complete_config') && type(g:nodejs_complete_config) == type({})
let g:nodejs_complete_config = extend(s:nodejs_complete_config, g:nodejs_complete_config)
else
let g:nodejs_complete_config = s:nodejs_complete_config
endif
unlet s:nodejs_complete_config
function! nodejscomplete#CompleteJS(findstart, base)"{{{
if a:findstart
try
let JS_compl_fn = function(g:nodejs_complete_config.js_compl_fn)
let start = call(JS_compl_fn, [a:findstart, a:base])
catch /.*/
echo '!!!!!!!!!!function [' . g:nodejs_complete_config.js_compl_fn . '] is not exists!'
endtry
"Decho 'start: ' . start
" str[start: end] end 为负数时从末尾截取
if start - 1 < 0
let b:nodecompl_context = ''
else
let line = getline('.')
let b:nodecompl_context = line[:start-1]
endif
return start
else
let posi = getpos('.')
let result = s:getNodeComplete(a:base, b:nodecompl_context)
" the function above will move the cursor
" so we restore the cursor position
" JS_compl_fn below may rely on the cursor position
call setpos('.', posi)
"Decho 'nodecomplete: ' . string(result)
unlet b:nodecompl_context
let nodejs_compl = result.complete
" limit nodejs complete count
if g:nodejs_complete_config.max_node_compl_len != 0
let nodejs_compl = nodejs_compl[0 : g:nodejs_complete_config.max_node_compl_len - 1]
endif
if result.continue
try
let JS_compl_fn = function(g:nodejs_complete_config.js_compl_fn)
let js_compl = call(JS_compl_fn, [a:findstart, a:base])
catch /.*/
echo '!!!!!!!!!!function [' . g:nodejs_complete_config.js_compl_fn . '] is not exists!'
endtry
"Decho 'js_compl: ' . string(js_compl)
return nodejs_compl + js_compl
else
return nodejs_compl
endif
endif
endfunction"}}}
" get complete
function! s:getNodeComplete(base, context)"{{{
"Decho 'base: ' . a:base
"Decho 'context: ' . a:context
" TODO: 排除 module.property.h 情况
let mod_reg = '\(' . s:js_varname_reg . '\)\s*\(\.\|\[\s*["'']\?\)\s*$'
let matched = matchlist(a:context, mod_reg)
"Decho 'mod_reg: ' . mod_reg
" 模块属性补全
if len(matched) > 0
let var_name = matched[1]
let operator = matched[2]
let position = [line('.'), len(a:context) - len(matched[0])]
"Decho 'var_name: ' . var_name . ' ; operator: ' . operator
let declare_info = s:getObjDeclareInfo(var_name, position)
"Decho 'mod_info: ' . string(declare_info) . '; compl_prefix: ' . a:base
let compl_list = s:getObjectComplete(declare_info.type, declare_info.value,
\ a:base, operator)
let ret = {
\ 'complete': compl_list
\ }
if len(compl_list) == 0
let ret.continue = 1
else
let ret.continue = 0
endif
" 全局补全
else
"Decho 'var complete'
let ret = {
\ 'continue': 1,
\ 'complete': s:getVariableComplete(a:context, a:base)
\ }
endif
return ret
endfunction"}}}
function! s:getObjDeclareInfo(var_name, position)"{{{
let position = s:fixPosition(a:position)
"Decho 'position: ' . string(position)
if position[0] <= 0
return {
\ 'type': s:js_obj_declare_type.global,
\ 'value': a:var_name
\}
endif
let decl_stmt_prefix_reg = '\<' . a:var_name . '\_s*=\_s*'
" search backward, don't move the cursor, don't wrap
call cursor(position[0], position[1])
let begin_position = searchpos(decl_stmt_prefix_reg, 'bnW')
if begin_position[0] == 0
return {
\ 'type': s:js_obj_declare_type.global,
\ 'value': a:var_name
\}
endif
" make sure it's not in comments...
if !s:isDeclaration(begin_position)
return s:getObjDeclareInfo(a:var_name, begin_position)
endif
let lines = s:getLinesInRange(begin_position, position)
"Decho 'lines: ' . string(lines)
let code = join(lines, "\n")
" require
let require_stmt_reg = decl_stmt_prefix_reg .
\ 'require\_s*(\_s*\([''"]\)\zs[^)''"]\+\ze\1\_s*)'
let matched = matchstr(code, require_stmt_reg)
if len(matched)
return {
\ 'type': s:js_obj_declare_type.require,
\ 'value': matched
\}
endif
" new
let new_stmt_reg = decl_stmt_prefix_reg .
\ 'new\_s\+\zs' . s:js_varname_reg . '\%(\_s*\.\_s*' .
\ s:js_varname_reg . '\)*\ze'
let matched = matchstr(code, new_stmt_reg)
if len(matched)
let parts = split(matched, '\.')
return {
\ 'type': s:js_obj_declare_type.constructor,
\ 'value': [s:getObjDeclareInfo(parts[0], begin_position), join(parts[1:], '.')]
\}
endif
" new
" var emitter = new (require('events')).EventEmitter;
let new_stmt_reg = decl_stmt_prefix_reg .
\ 'new\_s\+' .
\ '(\_s*require\_s*(\([''"]\)\(' . s:js_varname_reg . '\)\1\_s*)\_s*)' .
\ '\_s*' .
\ '\(\%(\.' . s:js_varname_reg . '\)\+\)'
let matchedList = matchlist(code, new_stmt_reg)
if (len(matchedList))
"Decho 'new stmt: ' . string(matchedList)
let props = [matchedList[3][1:]] + matchedList[4:]
"Decho 'props: ' . string(props)
return {
\ 'type': s:js_obj_declare_type.constructor,
\ 'value': [
\ {
\ 'type': s:js_obj_declare_type.require,
\ 'value': matchedList[2]
\ },
\ join(props, '')
\ ]
\}
endif
" assign
let assign_stmt_reg = decl_stmt_prefix_reg . '\zs' . s:js_varname_reg . '\ze'
let matched = matchstr(code, assign_stmt_reg)
if len(matched)
return s:getObjDeclareInfo(a:var_name, begin_position)
endif
" continure to search backward
return s:getObjDeclareInfo(a:var_name, begin_position)
endfunction"}}}
function! s:isDeclaration(position)"{{{
let [line_num, col_num] = a:position
" syntaxName @see: $VIMRUNTIME/syntax/javascript.vim
let syntaxName = synIDattr(synID(line_num, col_num, 0), 'name')
if syntaxName =~ '^javaScript\%(Comment\|LineComment\|String\|RegexpString\)'
return 0
else
return 1
endif
endfunction"}}}
" only complete nodejs's module info
function! s:getObjectComplete(type, mod_name, prop_name, operator)"{{{
" new
if a:type == s:js_obj_declare_type.constructor
let list = s:getConstructedObjectComplete(a:mod_name)
" require and global
else
let list = s:getNodeDocList(a:type, a:mod_name, 'props')
endif
if !len(list)
return list
else
" no prop_name suplied
if (len(a:prop_name) == 0)
let ret = list
else
let ret = s:smartFilter(list, 'v:val["word"]', a:prop_name)
endif
let [prefix, suffix] = ['', '']
let matched = matchlist(a:operator, '\[\s*\(["'']\)\?')
"Decho 'operator_matched: ' . string(matched)
if len(matched)
if len(matched[1])
let [prefix, suffix] = ['', matched[1] . ']']
else
let [prefix, suffix] = ['''', ''']']
endif
endif
for item in ret
let item.word = prefix . item.word . suffix
endfor
call s:addFunctionParen(ret)
return ret
endif
endfunction"}}}
function! s:getVariableComplete(context, var_name)"{{{
"Decho 'var_name: ' . a:var_name
" complete require's arguments
let matched = matchlist(a:context, 'require\s*(\s*\%(\([''"]\)\(\.\{1,2}.*\)\=\)\=$')
if (len(matched) > 0)
"Decho 'require complete: ' . string(matched)
if (len(matched[2]) > 0) " complete -> require('./
let mod_names = s:getModuleInCurrentDir(a:context, a:var_name, matched)
else
let mod_names = s:getModuleNames()
if (len(matched[1]) == 0) " complete -> require(
call map(mod_names, '"''" . v:val . "'')"')
elseif (len(a:var_name) == 0) " complete -> require('
call map(mod_names, 'v:val . "' . escape(matched[1], '"') . ')"')
else " complete -> require('ti
let mod_names = filter(mod_names, 'v:val =~# "^' . a:var_name . '"')
call map(mod_names, 'v:val . "' . escape(matched[1], '"') . ')"')
endif
endif
return mod_names
endif
" complete global variables
let vars = []
if (len(a:var_name) == 0)
return vars
endif
call s:loadNodeDocData()
if (has_key(g:nodejs_complete_data, 'vars'))
let vars = deepcopy(g:nodejs_complete_data.vars)
endif
let ret = s:smartFilter(vars, 'v:val["word"]', a:var_name)
call s:addFunctionParen(ret)
return ret
endfunction"}}}
function! s:getModuleInCurrentDir(context, var_name, matched)"{{{
let mod_names = []
let path = a:matched[2] . a:var_name
" typed as require('..
" complete as require('../
" cause the latter one is more common
let compl_prefix = ''
if (path =~# '\.\.$')
let compl_prefix = '/'
let path = path . compl_prefix
endif
"Decho 'path: ' . path
let current_dir = expand('%:p:h')
let glob_path = current_dir . '/' . path . '*'
let files = s:fuzglob(glob_path)
"Decho 'glob: ' . glob_path
"Decho 'current dir files: ' . string(files)
for file in files
" not '.' and '..'
if ((isdirectory(file) ) || file =~? '\.json$\|\.js$')
let mod_file = file
" directory
if (file !~? '\.json$\|\.js$')
let mod_file = mod_file . '/'
endif
" get complete word
let mod_file = substitute(mod_file, '\', '/', 'g')
let start = len(glob_path) - 1 " substract character '*'
let compl_infix = strpart(mod_file, start)
"Decho 'idx: ' . start
"Decho 'compl_infix: ' . compl_infix
"Decho 'relative file: ' . mod_file
let mod_name = compl_prefix . a:var_name . compl_infix
" file module, not a directory
if (compl_infix !~# '/$')
let mod_name = mod_name . a:matched[1] . ')'
endif
"Decho 'mod_name: ' . mod_name
call add(mod_names, mod_name)
endif
endfor
"Decho 'relative path: ' . path
return mod_names
endfunction"}}}
function! s:getModuleNames()"{{{
call s:loadNodeDocData()
let mod_names = []
" build-in module name
if (has_key(g:nodejs_complete_data, 'modules'))
let mod_names = keys(g:nodejs_complete_data.modules)
endif
" find module in 'module_dir' folder
if (!exists('b:npm_module_names'))
let current_dir = expand('%:p:h')
let b:npm_module_names = s:getModuleNamesInNode_modulesFolder(current_dir)
endif
let mod_names = mod_names + b:npm_module_names
return sort(mod_names)
endfunction"}}}
function! s:getModuleNamesInNode_modulesFolder(current_dir)"{{{
" ensure platform coincidence
let base_dir = substitute(a:current_dir, '\', '/', 'g')
"Decho 'base_dir: ' . base_dir
let ret = []
let parts = split(base_dir, '/', 1)
"Decho 'parts: ' . string(parts)
let idx = 0
let len = len(parts)
let sub_parts = []
while idx < len
let sub_parts = add(sub_parts, parts[idx])
let module_dir = join(sub_parts, '/') . '/node_modules'
"Decho 'directory: ' . module_dir
if (isdirectory(module_dir))
let files = s:fuzglob(module_dir . '/*')
"Decho 'node_module files: ' . string(files)
for file in files
if (isdirectory(file) || file =~? '\.json$\|\.js$')
let mod_name = matchstr(file, '[^/\\]\+$')
let ret = add(ret, mod_name)
endif
endfor
endif
let idx = idx + 1
endwhile
"Decho 'npm modules: ' . string(ret)
return ret
endfunction"}}}
function! s:getConstructedObjectComplete(constructor_info)"{{{
"Decho 'getConstructedObjectComplete, constructor_info: ' . string(a:constructor_info)
let ret = []
let [declare_info, class_name] = a:constructor_info
let mod_name = declare_info.value
" global
if declare_info.type == s:js_obj_declare_type.global
" Buffer
if class_name == ''
let class_name = '.self'
endif
" global.Buffer
if mod_name == 'global'
let mod_name = class_name
let class_name = '.self'
endif
endif
" global or require
if declare_info.type == s:js_obj_declare_type.global ||
\ declare_info.type == s:js_obj_declare_type.require
let ret = s:getNodeDocList(declare_info.type, mod_name, 'classes', class_name)
endif
return ret
endfunction"}}}
function! s:addFunctionParen(compl_list)"{{{
for item in a:compl_list
if type(item) == 4
if item.kind == 'f'
let item.word = item.word . '('
endif
endif
endfor
return a:compl_list
endfunction"}}}
function! s:loadNodeDocData()"{{{
" load node module data
if (!exists('g:nodejs_complete_data'))
" load data from external file
let filename = s:nodejs_doc_file
"Decho 'filename: ' . filename
if (filereadable(filename))
execute 'so ' . filename
"Decho string(g:nodejs_complete_data)
else
"Decho 'not readable: ' . filename
endif
endif
endfunction"}}}
" get infomation from g:nodejs_complete_data
" @param mod_type {Enum}
" @param mod_name {String}
" @param type {Enum} 'props' | 'classes'
" @param {String} if type == 'classes', then it exists and is class_name
" else do not exist
function! s:getNodeDocList(mod_type, mod_name, type, ...)"{{{
call s:loadNodeDocData()
if a:mod_type == s:js_obj_declare_type.require
let type = 'modules'
else
let type = 'globals'
endif
if (has_key(g:nodejs_complete_data[type], a:mod_name))
let mod = g:nodejs_complete_data[type][a:mod_name]
else
let mod = {}
endif
" class
if a:0 != 0
let class_name = a:1
if (has_key(mod, a:type))
let classes = mod[a:type]
else
let classes = {}
endif
if (has_key(classes, a:1))
let ret = classes[class_name]
else
let ret = []
endif
" property
else
if (has_key(mod, a:type))
let ret = mod[a:type]
else
let ret = []
endif
endif
return deepcopy(ret)
endfunction"}}}
" copied from FuzzyFinder/autoload/fuf.vim
" returns list of paths.
" An argument for glob() is normalized in order to avoid a bug on Windows.
function! s:fuzglob(expr)"{{{
" Substitutes "\", because on Windows, "**\" doesn't include ".\",
" but "**/" include "./". I don't know why.
return split(glob(substitute(a:expr, '\', '/', 'g')), "\n")
endfunction"}}}
" when x <= 0, return [0, 0]
" when y <= 0, move to previous line end
function! s:fixPosition(position)"{{{
let [x, y] = a:position
if x <= 0
return [0, 0]
endif
if y <= 0
let x -= 1
let y = len(getline(x))
return s:fixPosition([x, y])
endif
return [x, y]
endfunction"}}}
" return a List contains every line
function! s:getLinesInRange(begin_position, end_position)"{{{
let [begin_x, begin_y] = a:begin_position
let [end_x, end_y] = a:end_position
let lines = []
if begin_x == end_x
let line = getline(begin_x)
call add(lines, line[begin_y - 1 : end_y - 1])
else
let line = getline(begin_x)
call add(lines, line[begin_y - 1 :])
let x = begin_x + 1
while x < end_x
let line = getline(x)
call add(lines, line)
let x += 1
endwhile
let line = getline(end_x)
call add(lines, line[: end_y - 1])
endif
return lines
endfunction"}}}
" filter items with exact match at first
function! s:smartFilter(items, str, keyword)"{{{
let items = filter(a:items, a:str . ' =~ "' . a:keyword . '"')
let [exact_ret, fuzzy_ret] = [[], []]
for item in items
if item.word =~ '^' . a:keyword
call add(exact_ret, item)
else
call add(fuzzy_ret, item)
endif
endfor
return exact_ret + fuzzy_ret
endfunction"}}}
"
" use plugin Decho(https://github.com/vim-scripts/Decho) for debug
"
" turn off debug mode
" :%s;^\(\s*\)\(Decho\);\1"\2;g | :w | so %
"
" turn on debug mode
" :%s;^\(\s*\)"\(Decho\);\1\2;g | :w | so %
"
" vim:set foldmethod=marker: