diff --git a/VimLite.vba b/VimLite.vmb similarity index 94% rename from VimLite.vba rename to VimLite.vmb index 01c94d7..a1aa3f9 100644 --- a/VimLite.vba +++ b/VimLite.vmb @@ -703,16 +703,17 @@ endfunction " vim:fdm=marker:fen:fdl=1:et: plugin/VIMClangCC.vim [[[1 -734 -" Vim Script +1398 +" Vim clang code completion plugin " Author: fanhe " License: GPLv2 " Create: 2011-12-16 -" Change: 2011-12-16 +" Change: 2013-01-23 if !has('python') echohl ErrorMsg - echom "Error: ".expand(':p')." required vim compiled with +python" + echom printf("[%s]: Required vim compiled with +python", + \ expand(':p')) echohl None finish endif @@ -722,7 +723,13 @@ if exists("g:loaded_VIMClangCC") endif let g:loaded_VIMClangCC = 1 -autocmd FileType c,cpp call g:InitVIMClangCodeCompletion() +" 在 console 跑的 vim 没法拥有这个特性... +let s:has_clientserver = 0 +if has('clientserver') + let s:has_clientserver = 1 +endif + +autocmd FileType c,cpp call VIMClangCodeCompletionInit() " 标识是否第一次初始化 let s:bFirstInit = 1 @@ -730,6 +737,23 @@ let s:bFirstInit = 1 let s:dCalltipsData = {} let s:dCalltipsData.usePrevTags = 0 " 是否使用最近一次的 tags 缓存 +" 检查是否支持 noexpand 选项 +let s:__temp = &completeopt +let s:has_noexpand = 1 +try + set completeopt+=noexpand +catch /.*/ + let s:has_noexpand = 0 +endtry +let &completeopt = s:__temp +unlet s:__temp +"let g:has_noexpand = s:has_noexpand + +let s:has_InsertCharPre = 0 +if v:version >= 703 && has('patch196') + let s:has_InsertCharPre = 1 +endif + " 关联的文件,一般用于头文件关联源文件 " 在头文件头部和尾部添加的额外的内容,用于修正在头文件时的头文件包含等等问题 " {头文件: {'line': 在关联文件中对应的行(#include), 'filename': 关联文件}, ...} @@ -766,8 +790,14 @@ function! s:SetOpts() set completeopt-=menuone,longest set completeopt+=menu elseif g:VIMCCC_ItemSelectionMode == 2 " 选择但不插入文本 - set completeopt-=menu,longest - set completeopt+=menuone + if s:has_noexpand + " 支持 noexpand 就最好了 + set completeopt+=noexpand + set completeopt-=longest + else + set completeopt-=menu,longest + set completeopt+=menuone + endif else set completeopt-=menu set completeopt+=menuone,longest @@ -791,7 +821,9 @@ function! s:RestoreOpts() elseif g:VIMCCC_ItemSelectionMode == 1 " 选择并插入文本 let sRet = "" elseif g:VIMCCC_ItemSelectionMode == 2 " 选择但不插入文本 - let sRet = "\\" + if !s:has_noexpand + let sRet = "\\" + endif else let sRet = "\" endif @@ -855,7 +887,7 @@ function! s:ShouldComplete() "{{{2 endif endfunction "}}} -function! s:LaunchVIMClangCodeCompletion() "{{{2 +function! s:LaunchCodeCompletion() "{{{2 if s:ShouldComplete() return "\\" else @@ -863,34 +895,452 @@ function! s:LaunchVIMClangCodeCompletion() "{{{2 endif endfunction "}}} +" 触发条件 +" +" 触发的情形: +" abcdefg +" ^ 并且离单词起始位置的长度大于或等于触发字符数 +" +" 不触发的情形: +" abcdefg +" ^ +" 插入模式光标自动命令的上一个状态 +" ccrow: 代码完成的行号 +" cccol: 代码完成的列号 +" base: base +" pumvisible : 0|1 +" init: 0|1 起始状态,暂时只有在进入插入模式时初始化 +" +" FIXME: 这些状态,连我自己都分不清了... +" 等 7.3.196 使用 InsertCharPre 事件就好了 +" InsertCharPre When a character is typed in Insert mode, +" before inserting the char. +" The |v:char| variable indicates the char typed +" and can be changed during the event to insert +" a different character. When |v:char| is set +" to more than one character this text is +" inserted literally. +" It is not allowed to change the text |textlock|. +" The event is not triggered when 'paste' is +" set. +let s:aucm_prev_stat = {} +function! VIMCCCAsyncComplete(charPre) "{{{2 + if pumvisible() " 不重复触发 + return '' + endif + + " InsertCharPre 自动命令用 + let sChar = '' + if a:charPre + let sChar = v:char + if sChar !~# '[A-Za-z_0-9]' + return '' + endif + endif + + let nTriggerCharCount = g:VIMCCC_TriggerCharCount + let nCol = col('.') + let sLine = getline('.') + let sPrevChar = sLine[nCol-2] + let sCurrChar = sLine[nCol-1] + " 光标在单词之间,不需要启动 {for CursorMovedI} + if !a:charPre && + \ (sPrevChar !~# '[A-Za-z_0-9]' || sCurrChar =~# '[A-Za-z_0-9]') + return '' + endif + +" ============================================================================== +" 利用前状态和当前状态优化 + " 前状态 + let dPrevStat = s:aucm_prev_stat + + " 刚进入插入模式 + if !a:charPre && get(dPrevStat, 'init', 0) + call s:ResetAucmPrevStat() + return '' + endif + + let sPrevWord = matchstr(sLine[: nCol-2], '[A-Za-z_]\w*$') + if a:charPre + let sPrevWord .= sChar + endif + if len(sPrevWord) < nTriggerCharCount + " 如果之前补全过就重置状态 + if get(dPrevStat, 'cccol', 0) > 0 + call s:ResetAucmPrevStat() + endif + return '' + endif + + " 获取当前状态 + let nRow = line('.') + let nCol = VIMCCCSearchStartColumn(0) + let sBase = getline('.')[nCol-1 : col('.')-2] + if a:charPre + let sBase .= sChar + endif + + " 补全起始位置一样就不需要再次启动了 + " 貌似是有条件的,要判断 sPrevWord 的长度 + " case: 如果前面的单词的长度 < nTriggerCharCount,那么就需要启动了 + " 例如一直删除字符 + " 1. 起始行和上次相同 + " 2. 起始列和上次相同 + " 3. 光标前的字符串长度大于等于触发长度 + " 4. 上次的 base 是光标前的字符串的前缀(InsertCharPre 专用) + " 1 && 2 && 3 && 4 则忽略请求 + let save_ic = &ignorecase + let &ignorecase = g:VIMCCC_IgnoreCase + if get(dPrevStat, 'ccrow', 0) == nRow && get(dPrevStat, 'cccol', 0) == nCol + \ && len(sPrevWord) >= nTriggerCharCount + \ && (!a:charPre || sBase =~ '^'.get(dPrevStat, 'base', '')) + call s:UpdateAucmPrevStat(nRow, nCol, sBase, pumvisible()) + let &ignorecase = save_ic + return '' + endif + let &ignorecase = save_ic + " 关于补全菜单和 CursorMovedI 自动命令 + " 不触发: + " 弹出补全菜单(eg. ) + " 触发: + " 取消补全菜单(eg. ) + " 接受补全结果(eg. ) + " 所以这里的条件是,只要前状态的 'pumvisible' 为真,本次就不启动 + " 并且需要重置状态 + if !a:charPre && get(dPrevStat, 'pumvisible', 0) + call s:ResetAucmPrevStat() + return '' + endif +" ============================================================================== + " NOTE: 无法处理的情况: 光标随意移动到单词的末尾 + " 因为无法分辨到底是输入字符到达末尾还是移动过去 {for CursorMovedI} + + " ok,启动 + call VIMCCCLaunchCCThread(nRow, nCol, sBase) + + " 更新状态 + call s:UpdateAucmPrevStat(nRow, nCol, sBase, pumvisible()) +endfunction +"}}} +" 重置状态 +function! s:ResetAucmPrevStat() "{{{2 + call s:UpdateAucmPrevStat(0, 0, '', 0) + let s:aucm_prev_stat['init'] = 0 +endfunction +"}}} +function! s:InitAucmPrevStat() "{{{2 + call s:UpdateAucmPrevStat(0, 0, '', 0) + let s:aucm_prev_stat['init'] = 1 +endfunction +"}}} +function! s:UpdateAucmPrevStat(nRow, nCol, sBase, pumv) "{{{2 + let s:aucm_prev_stat['ccrow'] = a:nRow + let s:aucm_prev_stat['cccol'] = a:nCol + let s:aucm_prev_stat['base'] = a:sBase + let s:aucm_prev_stat['pumvisible'] = a:pumv +endfunction +"}}} +" NOTE: a:char 必须是已经输入完成的,否则补全会失效, +" 因为补全线程需要获取这个字符 +function! s:CompleteByCharAsync(char) "{{{2 + let nRow = line('.') + let nCol = col('.') + let sBase = '' + if a:char ==# '.' + call s:UpdateAucmPrevStat(nRow, nCol, sBase, pumvisible()) + return VIMCCCLaunchCCThread(nRow, nCol, sBase) + elseif a:char ==# '>' + if getline('.')[col('.') - 3] != '-' + return '' + else + call s:UpdateAucmPrevStat(nRow, nCol, sBase, pumvisible()) + return VIMCCCLaunchCCThread(nRow, nCol, sBase) + endif + elseif a:char ==# ':' + if getline('.')[col('.') - 3] != ':' + return '' + else + call s:UpdateAucmPrevStat(nRow, nCol, sBase, pumvisible()) + return VIMCCCLaunchCCThread(nRow, nCol, sBase) + endif + else + " TODO: A-Za-Z_0-9 + endif +endfunction +"}}} function! s:CompleteByChar(char) "{{{2 if a:char ==# '.' - return a:char . s:LaunchVIMClangCodeCompletion() + return a:char . s:LaunchCodeCompletion() elseif a:char ==# '>' if getline('.')[col('.') - 2] != '-' return a:char else - return a:char . s:LaunchVIMClangCodeCompletion() + return a:char . s:LaunchCodeCompletion() endif elseif a:char ==# ':' if getline('.')[col('.') - 2] != ':' return a:char else - return a:char . s:LaunchVIMClangCodeCompletion() + return a:char . s:LaunchCodeCompletion() endif endif endfunction "}}} +function! s:InitPyIf() "{{{2 +python << PYTHON_EOF +import threading +import subprocess +import StringIO +import traceback +import vim + +# FIXME: 应该引用 +import json + +def ToVimEval(o): + '''把 python 字符串列表和字典转为健全的能被 vim 解析的数据结构 + 对于整个字符串的引用必须使用双引号,例如: + vim.command("echo %s" % ToVimEval(expr))''' + if isinstance(o, str): + return "'%s'" % o.replace("'", "''") + elif isinstance(o, unicode): + return "'%s'" % o.encode('utf-8').replace("'", "''") + elif isinstance(o, (list, dict)): + return json.dumps(o, ensure_ascii=False) + else: + return repr(o) + + +def vimcs_eval_expr(servername, expr, prog='vim'): + '''在vim服务器上执行表达式expr,返回输出——字符串 + FIXME: 这个函数不能对自身的服务器调用,否则死锁!''' + if not expr: + return '' + cmd = [prog, '--servername', servername, '--remote-expr', expr] + p = subprocess.Popen(cmd, shell=False, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate() + + # 最后的换行干掉 + if out.endswith('\r\n'): + return out[:-2] + elif out.endswith('\n'): + return out[:-1] + elif out.endswith('\r'): + return out[:-1] + else: + return out + +def vimcs_send_keys(servername, keys, prog='vim'): + '''发送按键到vim服务器''' + if not servername: + return -1 + cmd = [prog, '--servername', servername, '--remote-send', keys] + p = subprocess.Popen(cmd, shell=False, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate() + return p.returncode + +class cc_sync_data: + '''保存同步的补全的同步数据''' + def __init__(self): + self.__lock = threading.Lock() + self.__parsertd = None # 最近的那个 parser 线程 + + def lock(self): + return self.__lock.acquire() + def unlock(self): + return self.__lock.release() + + def latest_td(self): + return self.__parsertd + def push_td(self, td): + '''把新线程压入''' + self.__parsertd = td + def clear_td(self): + self.__parsertd = None + + def is_alive(self): + '''判断最近的线程是否正在运行''' + if self.__parsertd: + return self.__parsertd.is_alive() + return False + + def is_done(self): + '''判断最近的线程的补全结果是否已经产出''' + if self.__parsertd: + return self.__parsertd.done + return False + + def push_and_start_td(self, td): + self.push_td(td) + td.start() + +class cc_thread(threading.Thread): + # 保证 VIMCCCIndex 完整性的锁,否则可能段错误 + indexlock = threading.Lock() + + '''代码补全搜索线程''' + def __init__(self, arg, vimprog = 'vim'): + threading.Thread.__init__(self) + self.arg = arg # 传给 clang 补全的参数,字典 + self.result = [] # 补全结果 + self.done = False # 标识主要工作已经完成 + self.vimprog = vimprog + + @property + def row(self): + return self.arg['row'] + @property + def col(self): + return self.arg['col'] + @property + def base(self): + return self.arg['base'] + + def run(self): + try: + # 开始干活 + fil = self.arg['file'] + row = self.arg['row'] + col = self.arg['col'] + us_files = self.arg['us_files'] + base = self.arg['base'] + ic = self.arg['ic'] + flags = self.arg['flags'] + servername = self.arg['servername'] + if False: + # just for test + self.result = ['f', 'ff', 'fff'] + else: + # 必须加锁,因为 clang 的解析不是线程安全的(大概) + cc_thread.indexlock.acquire() + try: + self.result = VIMCCCIndex.GetVimCodeCompleteResults( + fil, row, col, us_files, base, ic, flags) + except: + # 异常后需要解锁并且传递异常 + cc_thread.indexlock.release() + raise + cc_thread.indexlock.release() + + if not self.result: # 有结果才继续 + return + + # 完成后 + self.done = True + brk = False + g_SyncData.lock() + if not g_SyncData.latest_td() is self: + brk = True + g_SyncData.unlock() + if brk: + return + + # 发送消息到服务器 + vimcs_eval_expr(servername, 'VIMCCCThreadHandler()', self.vimprog) + + except: + # 把异常信息显示给用户 + sio = StringIO.StringIO() + print >> sio, "Exception in user code:" + print >> sio, '-' * 60 + traceback.print_exc(file=sio) + print >> sio, '-' * 60 + errmsg = sio.getvalue() + sio.close() + vimcs_eval_expr(servername, "VIMCCCExceptHandler('%s')" + % errmsg.replace("'", "''"), + self.vimprog) + +PYTHON_EOF +endfunction +"}}} +" 这个函数是从后台线程调用的,现在假设这个调用和vim前台调用是串行的,无竟态的 +" NOTE: 这个函数被python后台线程调用的时候,python后台线程还没有退出 +function! VIMCCCThreadHandler() "{{{2 + if mode() !=# 'i' && mode() !=# 'R' + echoerr 'error mode' + return -1 + endif + let nRow = line('.') + let nCol = VIMCCCSearchStartColumn(0) + let sBase = getline('.')[nCol-1 : col('.')-2] + let td_row = 0 + let td_col = 0 + let td_base = '' + let brk = 0 + py g_SyncData.lock() + " 如果最近线程还没有完成,返回 + py if not g_SyncData.is_done(): vim.command('let brk = 1') +python << PYTHON_EOF +if g_SyncData.latest_td(): + vim.command("let td_row = %d" % g_SyncData.latest_td().row) + vim.command("let td_col = %d" % g_SyncData.latest_td().col) + vim.command("let td_base = '%s'" % + g_SyncData.latest_td().base.replace("'", "''")) +PYTHON_EOF + py g_SyncData.unlock() + if brk + echomsg 'cc_thread is not done' + return 1 + endif + + let save_ic = &ignorecase + let &ignorecase = g:VIMCCC_IgnoreCase + if !(td_row == nRow && td_col == nCol && sBase =~ '^'.td_base) + " 这个结果不适合于当前位置,直接返回 + let &ignorecase = save_ic + return 0 + endif + let &ignorecase = save_ic + + " TODO 可能需要进一步过滤这种情况的补全结果: sBase = 'abc', td_base = 'ab' + + let sKeys = "" + " FIXME \=Fun()\ 这样执行函数在命令行会显示,能避免吗? + let sKeys .= "\=VIMCCCAsyncCCPre()\" + let sKeys .= "\\" + let sKeys .= "\=VIMCCCAsyncCCPost()\" + call feedkeys(sKeys, "n") + return 0 +endfunction +"}}} +function! VIMCCCExceptHandler(msg) "{{{2 + if empty(a:msg) + return + endif + echohl ErrorMsg + for sLine in split(a:msg, '\n') + echomsg sLine + endfor + echomsg '!!!Catch an exception!!!' + echohl None +endfunction +"}}} +" VIMCCCThreadHandler() 调用,执行一些前置动作 +function! VIMCCCAsyncCCPre() "{{{2 + call s:SetOpts() + return '' +endfunction +"}}} +" VIMCCCThreadHandler() 调用,执行一些后续动作 +function! VIMCCCAsyncCCPost() "{{{2 + call feedkeys(s:RestoreOpts(), "n") + return '' +endfunction +"}}} " 强制启动 function! s:VIMCCCInitForcibly() "{{{2 let bak = g:VIMCCC_Enable let g:VIMCCC_Enable = 1 - call g:InitVIMClangCodeCompletion() + call VIMClangCodeCompletionInit() let g:VIMCCC_Enable = bak endfunction "}}} -" 可选参数存在且非零,不 '冷启动'(异步新建不存在的当前文件对应的翻译单元) -function! g:InitVIMClangCodeCompletion(...) "{{{2 +" 首次启动 +function! s:FirstInit() "{{{2 +" ============================================================================ " MayComplete to '.' call s:InitVariable('g:VIMCCC_MayCompleteDot', 1) @@ -940,18 +1390,19 @@ function! g:InitVIMClangCodeCompletion(...) "{{{2 " 跳转至符号实现处的默认快捷键 call s:InitVariable('g:VIMCCC_GotoImplementationKey', '') - " 用于外部控制 - call s:InitVariable('g:VIMCCC_Enable', 0) - if !g:VIMCCC_Enable - return - endif + " 异步自动弹出补全菜单 + call s:InitVariable('g:VIMCCC_AutoPopupMenu', 1) - if s:bFirstInit - command! -nargs=0 -bar VIMCCCQuickFix - \call VIMCCCUpdateClangQuickFix(expand('%:p')) + " 触发自动弹出补全菜单需要输入的字符数 + call s:InitVariable('g:VIMCCC_TriggerCharCount', 2) - command! -nargs=+ VIMCCCSetArgs call VIMCCCSetArgsCmd() - endif +" ============================================================================ + command! -nargs=0 -bar VIMCCCQuickFix + \ call VIMCCCUpdateClangQuickFix(expand('%:p')) + + command! -nargs=+ VIMCCCSetArgs call VIMCCCSetArgsCmd() + command! -nargs=+ VIMCCCAppendArgs call VIMCCCAppendArgsCmd() + command! -nargs=0 VIMCCCPrintArgs call VIMCCCPrintArgsCmd() let g:VIMCCC_CodeCompleteFlags = 0 if g:VIMCCC_CompleteMacros @@ -963,6 +1414,45 @@ function! g:InitVIMClangCodeCompletion(...) "{{{2 call s:InitPythonInterfaces() + " 这是异步接口 + call s:InitPyIf() + " 全局结构 + py g_SyncData = cc_sync_data() +endfunction +"}}} +" 可选参数存在且非零,不 '冷启动'(异步新建不存在的当前文件对应的翻译单元) +function! VIMClangCodeCompletionInit(...) "{{{2 + if s:bFirstInit + call s:FirstInit() + endif + let s:bFirstInit = 0 + + " 是否使用,可用于外部控制 + call s:InitVariable('g:VIMCCC_Enable', 0) + if !g:VIMCCC_Enable + return + endif + + let bAsync = g:VIMCCC_AutoPopupMenu + + " 特性检查 + if bAsync && (empty(v:servername) || !has('clientserver')) + echohl WarningMsg + echom '-------------------- VIMCCC --------------------' + if empty(v:servername) + echom "Please start vim as server, eg. vim --servername {name}" + echom "Auto popup menu feature will be disabled this time" + else + echom 'Auto popup menu feature required vim compiled vim with ' + \ . '+clientserver' + echom 'The feature will be disabled this time' + endif + echom "You can run ':let g:VIMCCC_AutoPopupMenu = 0' to diable this " + \ . "message" + echohl None + let bAsync = 0 + endif + setlocal omnifunc=VIMClangCodeCompletion if g:VIMCCC_PeriodicQuickFix @@ -977,35 +1467,50 @@ function! g:InitVIMClangCodeCompletion(...) "{{{2 call g:InitVLCalltips() if g:VIMCCC_MayCompleteDot - inoremap . + if bAsync + inoremap . . + \=CompleteByCharAsync('.') + else + inoremap . \=SetOpts() \=CompleteByChar('.') \=RestoreOpts() + endif endif if g:VIMCCC_MayCompleteArrow - inoremap > - \=SetOpts() - \=CompleteByChar('>') - \=RestoreOpts() + if bAsync + inoremap > > + \=CompleteByCharAsync('>') + else + inoremap > + \=SetOpts() + \=CompleteByChar('>') + \=RestoreOpts() + endif endif if g:VIMCCC_MayCompleteColon - inoremap : - \=SetOpts() - \=CompleteByChar(':') - \=RestoreOpts() + if bAsync + inoremap : : + \=CompleteByCharAsync(':') + else + inoremap : + \=SetOpts() + \=CompleteByChar(':') + \=RestoreOpts() + endif endif if g:VIMCCC_ItemSelectionMode > 4 inoremap \=CheckIfSetOpts() - \=LaunchVIMClangCodeCompletion() + \=LaunchCodeCompletion() \=RestoreOpts() else "inoremap "\=SetOpts() - "\=LaunchVIMClangCodeCompletion() + "\=LaunchCodeCompletion() "\=RestoreOpts() endif @@ -1024,6 +1529,26 @@ function! g:InitVIMClangCodeCompletion(...) "{{{2 exec 'nnoremap ' . g:VIMCCC_GotoImplementationKey \. ' :call VIMCCCSmartJump()' + if bAsync + " 真正的异步补全实现 + " 输入字符必须是 [A-Za-z_0-9] 才能触发 + if s:has_InsertCharPre + augroup VIMCCC_AUGROUP + autocmd! InsertEnter call InitAucmPrevStat() + autocmd! InsertCharPre call VIMCCCAsyncComplete(1) + autocmd! InsertLeave + \ call Autocmd_InsertLeaveHandler() + augroup END + else + augroup VIMCCC_AUGROUP + autocmd! CursorMovedI call VIMCCCAsyncComplete(0) + autocmd! InsertLeave + \ call Autocmd_InsertLeaveHandler() + " NOTE: 事件顺序是先 InsertEnter 再 CursorMovedI + autocmd! InsertEnter call InitAucmPrevStat() + augroup END + endif + endif if a:0 > 0 && a:1 " 可控制不 '冷启动' @@ -1032,7 +1557,118 @@ function! g:InitVIMClangCodeCompletion(...) "{{{2 py VIMCCCIndex.AsyncUpdateTranslationUnit(vim.eval("expand('%:p')")) endif - let s:bFirstInit = 0 + " 调试用 + "inoremap =VIMCCCLaunchCCThread() +endfunction +"}}} +function! s:Autocmd_InsertLeaveHandler() "{{{2 + call s:ResetAucmPrevStat() + " 清线程 + py g_SyncData.lock() + py g_SyncData.clear_td() + py g_SyncData.unlock() +endfunction +"}}} +" 启动一个新的补全线程 +function! VIMCCCLaunchCCThread(nRow, nCol, sBase) "{{{2 + if v:servername ==# '' + echoerr 'servername is null, can not start cc thread' + return '' + endif + + let sAppend = a:0 > 0 ? a:1 : '' + + let sFileName = expand('%:p') + let sFileName = s:ToClangFileName(sFileName) + let nRow = a:nRow + let nCol = a:nCol + let sBase = a:sBase + let sVimProg = v:progname + if has('win32') || has('win64') + " Windows 下暂时这样获取 + let sVimProg = $VIMRUNTIME . '\' . sVimProg + endif + " 上锁然后开始线程 + py g_SyncData.lock() + py g_SyncData.push_and_start_td(cc_thread( + \ {'file': vim.eval('sFileName'), + \ 'row': int(vim.eval('nRow')), + \ 'col': int(vim.eval('nCol')), + \ 'us_files': [GetCurUnsavedFile()], + \ 'base': vim.eval("sBase"), + \ 'ic': vim.eval("g:VIMCCC_IgnoreCase") != '0', + \ 'flags': int(vim.eval("g:VIMCCC_CodeCompleteFlags")), + \ 'servername': vim.eval('v:servername')}, + \ vim.eval("sVimProg"))) + py g_SyncData.unlock() + + return '' +endfunction +"}}} +" 搜索补全起始列 +" 以下7种情形 +" xxx yyy| +" ^ +" xxx.yyy| +" ^ +" xxx. | +" ^ +" xxx->yyy| +" ^ +" xxx-> | +" ^ +" xxx::yyy| +" ^ +" xxx:: | +" ^ +function! VIMCCCSearchStartColumn(bInCC) "{{{2 + let nRow = line('.') + let nCol = col('.') + "let lPos = searchpos('\<\|\.\|->\|::', 'cbn', nRow) + " NOTE: 光标下的字符应该不算在内 + let lPos = searchpos('\<\|\.\|->\|::', 'bn', nRow) + let nCol2 = lPos[1] " 搜索到的字符串的起始列号 + + if lPos == [0, 0] + " 这里已经处理了光标放到第一列并且第一列的字符是空白的情况 + let nStartCol = nCol + else + let sLine = getline('.') + + if sLine[nCol2 - 1] ==# '.' + " xxx. | + " ^ + let nStartCol = nCol2 + 1 + elseif sLine[nCol2 -1 : nCol2] ==# '->' + \ || sLine[nCol2 - 1: nCol2] ==# '::' + " xxx-> | + " ^ + let nStartCol = nCol2 + 2 + else + " xxx yyy| + " ^ + " xxx.yyy| + " ^ + " xxx->yyy| + " ^ + " 前一个字符可能是 '\W'. eg. xxx yyy(| + if sLine[nCol-2] =~# '\W' + " 不补全 + return -1 + endif + + if a:bInCC + " BUG: 返回 5 后,下次调用此函数是,居然 col('.') 返回 6 + " 亦即补全函数对返回值的解析有错误 + let nStartCol = nCol2 - 1 + else + " 不在补全函数里面调用的话,返回正确值... + let nStartCol = nCol2 + endif + endif + endif + + return nStartCol endfunction "}}} function! s:RequestCalltips(...) "{{{2 @@ -1130,10 +1766,35 @@ function! VIMCCCSetArgs(lArgs) "{{{2 \[GetCurUnsavedFile()], True, True) endfunction "}}} +function! VIMCCCGetArgs() "{{{2 + py vim.command('let lArgs = %s' % ToVimEval(VIMCCCIndex.GetParseArgs())) + return lArgs +endfunction +"}}} +function! VIMCCCAppendArgs(lArgs) "{{{2 + if type(a:lArgs) == type('') + let lArgs = split(a:lArgs) + else + let lArgs = a:lArgs + endif + + let lArgs += VIMCCCGetArgs() + call VIMCCCSetArgs(lArgs) +endfunction +"}}} function! s:VIMCCCSetArgsCmd(...) "{{{2 call VIMCCCSetArgs(a:000) endfunction "}}} +function! s:VIMCCCAppendArgsCmd(...) "{{{2 + call VIMCCCAppendArgs(a:000) +endfunction +"}}} +function! s:VIMCCCPrintArgsCmd() "{{{2 + let lArgs = VIMCCCGetArgs() + echo lArgs +endfunction +"}}} " 统一处理,主要处理 .h -> .hpp 的问题 function! s:ToClangFileName(sFileName) "{{{2 let sFileName = a:sFileName @@ -1165,29 +1826,7 @@ endfunction function! VIMClangCodeCompletion(findstart, base) "{{{2 if a:findstart "call vlutils#TimerStart() " 计时 - - let nRow = line('.') - let lPos = searchpos('\<\|\.\|->\|::', 'cb', nRow) - - let nCol = col('.') - - if lPos == [0, 0] - let nStartCol = nCol - else - let sLine = getline('.') - if sLine[nCol - 1] ==# '.' - let nStartCol = nCol + 1 - elseif sLine[nCol -1 : nCol] ==# '->' - \|| sLine[nCol - 1: nCol] ==# '::' - let nStartCol = nCol + 2 - else - " BUG: 返回 5 后,下次调用此函数是,居然 col('.') 返回 6 - "let nStartCol = nCol - let nStartCol = nCol - 1 - endif - endif - - return nStartCol + return VIMCCCSearchStartColumn(1) endif "=========================================================================== @@ -1198,22 +1837,43 @@ function! VIMClangCodeCompletion(findstart, base) "{{{2 let sBase = a:base let sFileName = expand('%:p') + let sFileName = s:ToClangFileName(sFileName) let nRow = line('.') let nCol = col('.') "列 - let sFileName = s:ToClangFileName(sFileName) - py lResults = VIMCCCIndex.GetVimCodeCompleteResults( - \vim.eval("sFileName"), - \int(vim.eval("nRow")), - \int(vim.eval("nCol")), - \[GetCurUnsavedFile()], - \vim.eval("sBase"), - \vim.eval("g:VIMCCC_IgnoreCase") != '0', - \int(vim.eval("g:VIMCCC_CodeCompleteFlags"))) - "call vlutils#TimerEndEcho() + let bAsync = 0 + py g_SyncData.lock() + py if g_SyncData.latest_td(): vim.command("let bAsync = 1") + py g_SyncData.unlock() + + if bAsync + " 异步触发 + let brk = 0 + py g_SyncData.lock() + py if not g_SyncData.is_done(): vim.command("let brk = 1") + py lResults = [] + py if g_SyncData.latest_td(): lResults = g_SyncData.latest_td().result + py g_SyncData.clear_td() + py g_SyncData.unlock() + " 线程未完成,返回,等待 + if brk + return [] + endif + " NOTE: 触发器已经提前检查了是否需要触发,所以无须再检查是否继续了 + else + py lResults = VIMCCCIndex.GetVimCodeCompleteResults( + \ vim.eval("sFileName"), + \ int(vim.eval("nRow")), + \ int(vim.eval("nCol")), + \ [GetCurUnsavedFile()], + \ vim.eval("sBase"), + \ vim.eval("g:VIMCCC_IgnoreCase") != '0', + \ int(vim.eval("g:VIMCCC_CodeCompleteFlags"))) + "call vlutils#TimerEndEcho() + endif + py vim.command("let lResults = %s" % lResults) "call vlutils#TimerEndEcho() - " 调试用 let b:lResults = lResults @@ -1228,7 +1888,8 @@ endfunction function! s:VIMCCCUpdateClangQuickFix(sFileName) "{{{2 let sFileName = a:sFileName - "py t = UpdateQuickFixThread(vim.eval("sFileName"), [GetCurUnsavedFile()], True) + "py t = UpdateQuickFixThread(vim.eval("sFileName"), + "\ [GetCurUnsavedFile()], True) "py t.start() "return @@ -1258,7 +1919,8 @@ function! s:VIMCCCGotoDeclaration() "{{{2 let nCol = col('.') let sFileName = s:ToClangFileName(sFileName) py vim.command("let dLocation = %s" - \% VIMCCCIndex.GetSymbolDeclarationLocation(vim.eval("sFileName"), + \% VIMCCCIndex.GetSymbolDeclarationLocation( + \ vim.eval("sFileName"), \ int(vim.eval("nRow")), int(vim.eval("nCol")), \ [GetCurUnsavedFile(UF_Related)], True)) @@ -1394,6 +2056,8 @@ UF_Related = 1 UF_RelatedPrepend = 2 def GetCurUnsavedFile(nFlags = UF_None): + ''' + nFlags: 控制一些特殊补全场合,例如在头文件补全''' sFileName = vim.eval("expand('%:p')") sClangFileName = vim.eval("s:ToClangFileName('%s')" % sFileName) @@ -1602,7 +2266,7 @@ endfunction " vim:fdm=marker:fen:expandtab:smarttab:fdl=1: doc/VimLite.txt [[[1 -788 +818 *VimLite.txt* A C/C++ IDE inspired by CodeLite _ _______ _ _____ _________________~ @@ -1840,7 +2504,10 @@ mouse. > 4.2. Commands *VimLite-ProjectManager-Commands* VLWorkspaceOpen [workspace_file] Open a workspace file or default - workspace. + workspace. If without specify a + workspace file and VimLite had + started, the command will open the + current workspace. VLWBuildActiveProject Build active projcet. @@ -1989,11 +2656,23 @@ in VLWorkspace buffer window, popup the menu, select "Parse Workspace (Quick)". VLWParseCurrentFile Parse current editing file. NOTE: You ought to save current file before run this command. + DEPRECATED~ VLWDeepParseCurrentFile Parse current editing file and the files it includes. NOTE: You ought to save current file before run this command. + DEPRECATED~ + + VLWAsyncParseCurrentFile Parse current editing file + asynchronously. + NOTE: You ought to save current file + before run this command. + + VLWDeepAsyncParseCurrentFile Parse current editing file and the + files it includes asynchronously. + NOTE: You ought to save current file + before run this command. VLWEnvVarSetttings Open 'Environment Variables Setting' dialog. @@ -2048,7 +2727,7 @@ clang, otherwise VIMCCC will not work correctly. VIMCCC currently work with libclang 3.0. -There are only two commands of VIMCCC. +There are several commands for VIMCCC. VIMCCCQuickFix Retrieve clang diagnostics to quickfix, if the diagnostics are not empty, open @@ -2056,6 +2735,10 @@ There are only two commands of VIMCCC. VIMCCCSetArgs {arg1} [arg2] ... Set clang compiler options + VIMCCCAppendArgs {arg1} [arg2] ... Append clang compiler options + + VIMCCCPrintArgs Print clang compiler options + VLWTagsSetttings Open 'Tags Setting' dialog. This also can set the search paths for clang. @@ -2324,6 +3007,17 @@ limitation. > let g:VIMCCC_GotoImplementationKey = '' +Auto popup code completion menu, this is an awesome feature. +So you do not need to press to trigger an code completion, just +typing. +> + let g:VIMCCC_AutoPopupMenu = 1 + +When g:VIMCCC_AutoPopupMenu is not 0, this defines the minimum chars to +trigger an code completion. +> + let g:VIMCCC_TriggerCharCount = 2 + ------------------------------------------------------------------------------ 7.5. Debugger Options *VimLite-Options-Debugger* @@ -5541,7 +6235,7 @@ endfunc "}}} " vim:fdm=marker:fen:et:sts=4:fdl=1: autoload/omnicpp/complete.vim [[[1 -1064 +1068 " Description: Omni completion script for resolve namespace " Maintainer: fanhe " Create: 2011 May 14 @@ -6316,8 +7010,12 @@ function! omnicpp#complete#Complete(findstart, base, ...) "{{{2 let s:bIsScopeOperation = 0 "call vlutils#TimerStart() "计时用 - let nStartIdx = col('.') - 1 - let sLine = getline('.')[: nStartIdx-1] + if mode() ==# 'i' + let sLine = getline('.')[: col('.') - 2] + else + " 非插入模式下,光标所在的字符算在内,为了符号跳转 + let sLine = getline('.')[: col('.') - 1] + endif " 跳过光标在注释和字符串中的补全请求 if omnicpp#utils#IsCursorInCommentOrString() @@ -8591,10 +9289,12 @@ function s:error(msg) echohl None endfunction autoload/videm/wsp.py [[[1 -1943 +1991 #!/usr/bin/env python # -*- coding:utf-8 -*- +'''工作区的 python 例程,和对应的 vim 脚本是互相依赖的''' + import sys import os import os.path @@ -8689,7 +9389,7 @@ class StartEdit: % (self.bufnr, self.bak_ma)) -class VimLiteWorkspace(): +class VimLiteWorkspace: '''VimLite 工作空间对象,主要用于操作缓冲区和窗口 所有操作假定已经在工作空间缓冲区''' @@ -8735,6 +9435,8 @@ class VimLiteWorkspace(): '-Sep2-', 'Batch Builds', '-Sep3-', + 'Parse Workspace (Full, Async)', + 'Parse Workspace (Quick, Async)', 'Parse Workspace (Full)', 'Parse Workspace (Quick)', '-Sep4-', @@ -8807,18 +9509,22 @@ class VimLiteWorkspace(): 'Rename...', 'Remove' ] + # 当前工作区选择构建设置名字,缓存,用于快速访问 + # self.RefreshStatusLine() 可以刷新此缓存 + self.cache_confName = '' + if fileName: self.OpenWorkspace(fileName) - #创建窗口 - vim.command("call s:CreateVLWorkspaceWin()") + # 创建窗口 + self.CreateWindow() + # 设置键位绑定。当前光标必须在需要设置键位绑定的缓冲区中 vim.command("call s:SetupKeyMappings()") - self.buffer = vim.current.buffer - self.window = vim.current.window - self.bufNum = int(vim.eval("bufnr('%')")) self.InstallPopupMenu() + # 创建窗口后需要执行的一些动作 + self.SetupStatusLine() self.RefreshStatusLine() self.RefreshBuffer() self.HlActiveProject() @@ -8828,6 +9534,20 @@ class VimLiteWorkspace(): self.debug = None + def CreateWindow(self): + # 创建窗口 + vim.command("call s:CreateVLWorkspaceWin()") + self.buffer = vim.current.buffer + self.bufNum = self.buffer.number + # 这个属性需要动态获取 + #self.window = vim.current.window + + @property + def window(self): + '''如果之前获取 self.window 的属性的时候,光标都是在工作区的窗口的话, + 这样做就不会有问题了''' + return vim.current.window + def OpenWorkspace(self, fileName): if fileName: self.VLWIns.OpenWorkspace(fileName) @@ -8840,6 +9560,8 @@ class VimLiteWorkspace(): vim.command('redraw | echo ""') # 清理输出... self.VLWIns.CloseWorkspace() self.tagsManager.CloseDatabase() + # 还原配置 + VLWRestoreConfigToGlobal() def ReloadWorkspace(self): fileName = self.VLWIns.fileName @@ -8872,6 +9594,13 @@ class VimLiteWorkspace(): Globals.C_SOURCE_EXT.add(i) for i in self.VLWSettings.cppSrcExts: Globals.CPP_SOURCE_EXT.add(i) + # == DEBUG -- + #self.VLWSettings.enableLocalConfig = True + #self.VLWSettings.localConfig['Base']['g:VLWorkspaceUseVIMCCC'] = 1 + # -- DEBUG == + # 根据载入的工作区配置刷新全局的配置 + if self.VLWSettings.enableLocalConfig: + VLWSetCurrentConfig(self.VLWSettings.localConfig, force=True) def SaveWspSettings(self): if self.VLWSettings.Save(): @@ -8968,12 +9697,17 @@ class VimLiteWorkspace(): # 重置偏移量 self.VLWIns.SetWorkspaceLineNum(1) + def SetupStatusLine(self): + vim.command('setlocal statusline=%!VLWStatusLine()') + def RefreshStatusLine(self): - string = self.VLWIns.GetName() + '[' + \ - self.VLWIns.GetBuildMatrix().GetSelectedConfigurationName() \ - + ']' - vim.command("call setwinvar(bufwinnr(%d), '&statusline', '%s')" - % (self.bufNum, ToVimStr(string))) + #string = self.VLWIns.GetName() + '[' + \ + #self.VLWIns.GetBuildMatrix().GetSelectedConfigName() \ + #+ ']' + #vim.command("call setwinvar(bufwinnr(%d), '&statusline', '%s')" + #% (self.bufNum, ToVimStr(string))) + self.cache_confName = \ + self.VLWIns.GetBuildMatrix().GetSelectedConfigName() def InitOmnicppTypesVar(self): vim.command("let g:dOCppTypes = {}") @@ -9744,7 +10478,10 @@ class VimLiteWorkspace(): extraMacros.extend(self.GetProjectPredefineMacros(actProjName)) return extraMacros - def ParseWorkspace(self, full = False): + def ParseWorkspace(self, async = True, full = False): + ''' + async: 是否异步 + full: 是否解析工作区的所有文件''' vim.command("redraw") vim.command("echo 'Preparing...'") @@ -9789,7 +10526,11 @@ class VimLiteWorkspace(): parseFiles = list(set(parseFiles)) parseFiles.sort() - self.ParseFiles(parseFiles, extraMacros=extraMacros) + if async: + vim.command("redraw | echo 'Start asynchronous parsing...'") + self.AsyncParseFiles(parseFiles, extraMacros=extraMacros) + else: + self.ParseFiles(parseFiles, extraMacros=extraMacros) def ParseFiles(self, files, indicate = True, extraMacros = []): ds = Globals.DirSaver() @@ -9821,7 +10562,7 @@ class VimLiteWorkspace(): except: pass - def AsyncParseFiles(self, files, extraMacros = []): + def AsyncParseFiles(self, files, extraMacros = [], filterNotNeed = True): def RemoveTmp(arg): os.close(arg[0]) os.remove(arg[1]) @@ -9831,7 +10572,8 @@ class VimLiteWorkspace(): tmpfd, tmpf = tempfile.mkstemp() # 在异步进程完成后才删除,使用回调机制 with open(tmpf, 'wb') as f: f.write('\n'.join(macros)) - self.tagsManager.AsyncParseFiles(files, [tmpf], RemoveTmp, [tmpfd, tmpf]) + self.tagsManager.AsyncParseFiles(files, [tmpf], RemoveTmp, + [tmpfd, tmpf], filterNotNeed) def GetTagsSearchPaths(self): '''获取 tags 包含文件的搜索路径''' @@ -10208,10 +10950,14 @@ class VimLiteWorkspace(): self.RefreshBuffer() elif choice == 'Reload Workspace': self.ReloadWorkspace() + elif choice == 'Parse Workspace (Full, Async)': + self.ParseWorkspace(async=True, full=True) + elif choice == 'Parse Workspace (Quick, Async)': + self.ParseWorkspace(async=True, full=False) elif choice == 'Parse Workspace (Full)': - self.ParseWorkspace(True) + self.ParseWorkspace(async=False, full=True) elif choice == 'Parse Workspace (Quick)': - self.ParseWorkspace(False) + self.ParseWorkspace(async=False, full=False) elif choice == 'Workspace Build Configuration...': vim.command("call s:WspBuildConfigManager()") elif choice == 'Workspace Batch Build Settings...': @@ -10536,7 +11282,7 @@ class VimLiteWorkspace(): #=========================================================================== autoload/videm/wsp.vim [[[1 -5590 +5797 " Vim global plugin for handle workspace " Author: fanhe " License: This file is placed in the public domain. @@ -10573,8 +11319,12 @@ let s:sfile = expand(':p') "noremap