diff --git a/plugin/clang_complete.vim b/plugin/clang_complete.vim index b4bdaa48..5f3ecab9 100644 --- a/plugin/clang_complete.vim +++ b/plugin/clang_complete.vim @@ -104,9 +104,9 @@ function! s:ClangCompleteInit() call LoadUserOptions() inoremap LaunchCompletion() - inoremap . CompleteDot() - inoremap > CompleteArrow() - inoremap : CompleteColon() + "inoremap . CompleteDot() + "inoremap > CompleteArrow() + "inoremap : CompleteColon() inoremap HandlePossibleSelectionEnter() if g:clang_snippets == 1 @@ -162,6 +162,9 @@ function! s:ClangCompleteInit() augroup end endif + au CursorMovedI call ReLaunchCompletion() + "au InsertCharPre call ReLaunchCompletion() + if !exists('g:clang_use_library') || g:clang_use_library == 1 " Try to use libclang. On failure, we fall back to the clang executable. let l:initialized = s:initClangCompletePython(exists('g:clang_use_library')) @@ -276,6 +279,7 @@ function! s:initClangCompletePython(user_requested) if l:res == 0 return 0 endif + au VimLeavePre * python FinishClangComplete() let s:libclang_loaded = 1 endif python WarmupCache() @@ -589,6 +593,9 @@ function! ClangComplete(findstart, base) while l:start > 0 && l:line[l:start - 1] =~ '\i' let l:start -= 1 endwhile + if l:start == 0 + return -1 + endif if l:line[l:start - 2:] =~ '->' || l:line[l:start - 1] == '.' let b:clang_complete_type = 0 endif @@ -604,37 +611,45 @@ function! ClangComplete(findstart, base) endif if g:clang_use_library == 1 - python completions, timer = getCurrentCompletions(vim.eval('a:base')) + python thread = getThread(vim.current.buffer.name) + python vim.command('let l:shouldRetry = ' + str(tryComplete(thread, vim.eval('b:col'), vim.eval("a:base")))) + if l:shouldRetry == 1 + return [] + endif + + python completions, timer = getCurrentCompletions(thread) python vim.command('let l:res = ' + completions) python timer.registerEvent("Load into vimscript") else let l:res = s:ClangCompleteBinary(a:base) endif - for item in l:res - if g:clang_snippets == 1 - let item['word'] = b:AddSnip(item['info'], item['args_pos']) - else - let item['word'] = item['abbr'] - endif - endfor + if l:res != [] + for item in l:res + if g:clang_snippets == 1 + let item['word'] = b:AddSnip(item['info'], item['args_pos']) + else + let item['word'] = item['abbr'] + endif + endfor - inoremap HandlePossibleSelectionCtrlY() - augroup ClangComplete - au CursorMovedI call TriggerSnippet() - augroup end - let b:snippet_chosen = 0 + inoremap HandlePossibleSelectionCtrlY() + augroup ClangComplete + au CursorMovedI call TriggerSnippet() + augroup end + let b:snippet_chosen = 0 - if g:clang_use_library == 1 - python timer.registerEvent("vimscript + snippets") - python timer.finish() - endif + if g:clang_use_library == 1 + python timer.registerEvent("vimscript + snippets") + python timer.finish() + endif - if g:clang_debug == 1 - echom 'clang_complete: completion time (' . (g:clang_use_library == 1 ? 'library' : 'binary') . ') '. split(reltimestr(reltime(l:time_start)))[0] + if g:clang_debug == 1 + echom 'clang_complete: completion time (' . (g:clang_use_library == 1 ? 'library' : 'binary') . ') '. split(reltimestr(reltime(l:time_start)))[0] + endif + endif + return l:res endif - return l:res -endif endfunction function! s:HandlePossibleSelectionEnter() @@ -726,9 +741,19 @@ function! s:CompleteColon() return ':' . s:LaunchCompletion() endfunction +let b:prev_line = "" + +function! s:ReLaunchCompletion() + if s:ShouldComplete() && b:prev_line != getline('.') + let b:prev_line = getline('.') + call feedkeys("\\") + endif + return '' +endfunction + " May be used in a mapping to update the quickfix window. function! g:ClangUpdateQuickFix() - call s:DoPeriodicQuickFix() + "call s:DoPeriodicQuickFix() return '' endfunction diff --git a/plugin/libclang.py b/plugin/libclang.py index 08533a1c..ee4dddd4 100644 --- a/plugin/libclang.py +++ b/plugin/libclang.py @@ -51,6 +51,7 @@ def initClangComplete(clang_complete_flags, clang_compilation_database, \ library_path, user_requested): global index + global debug debug = int(vim.eval("g:clang_debug")) == 1 printWarnings = (user_requested != "0") or debug @@ -82,6 +83,8 @@ def initClangComplete(clang_complete_flags, clang_compilation_database, \ global translationUnits translationUnits = dict() + global threads + threads = dict() global complete_flags complete_flags = int(clang_complete_flags) global compilation_database @@ -91,6 +94,8 @@ def initClangComplete(clang_complete_flags, clang_compilation_database, \ compilation_database = None global libclangLock libclangLock = threading.Lock() + global threadsShouldTerminate + threadsShouldTerminate = False return 1 # Get a tuple (fileName, fileContent) for the file opened in the current @@ -100,23 +105,23 @@ def getCurrentFile(): return (vim.current.buffer.name, file) class CodeCompleteTimer: - def __init__(self, debug, file, line, column, params): + def __init__(self): self._debug = debug - if not debug: + def start(self, file, line, column, args, cwd): + if not self._debug: return - content = vim.current.line print " " print "libclang code completion" print "========================" - print "Command: clang %s -fsyntax-only " % " ".join(params['args']), + print "Command: clang %s -fsyntax-only " % " ".join(args), print "-Xclang -code-completion-at=%s:%d:%d %s" % (file, line, column, file) - print "cwd: %s" % params['cwd'] + print "cwd: %s" % cwd print "File: %s" % file print "Line: %d, Column: %d" % (line, column) print " " - print "%s" % content + print "%s" % vim.current.buffer[line - 1] print " " @@ -160,11 +165,14 @@ def getCurrentTranslationUnit(args, currentFile, fileName, timer, timer.registerEvent("Reparsing") return tu + if threadsShouldTerminate: + return None + flags = TranslationUnit.PARSE_PRECOMPILED_PREAMBLE tu = index.parse(fileName, args, [currentFile], flags) timer.registerEvent("First parse") - if tu == None: + if tu == None or threadsShouldTerminate: return None translationUnits[fileName] = tu @@ -332,8 +340,9 @@ def updateCurrentDiagnostics(): global debug debug = int(vim.eval("g:clang_debug")) == 1 params = getCompileParams(vim.current.buffer.name) - timer = CodeCompleteTimer(debug, vim.current.buffer.name, -1, -1, params) + timer = CodeCompleteTimer() + timer.start(vim.current.buffer.name, -1, -1, params['args'], params['cwd']) with workingDir(params['cwd']): with libclangLock: getCurrentTranslationUnit(params['args'], getCurrentFile(), @@ -402,88 +411,140 @@ def formatResult(result): class CompleteThread(threading.Thread): - def __init__(self, line, column, currentFile, fileName, params, timer): + def __init__(self, fileName, params): threading.Thread.__init__(self) - self.line = line - self.column = column - self.currentFile = currentFile + self.line = -1 + self.column = -1 + self.base = "" + self.sorting = "" + self.fileContent = "" self.fileName = fileName self.result = None + self.shouldTerminate = False self.args = params['args'] self.cwd = params['cwd'] - self.timer = timer + self.timer = CodeCompleteTimer() + self.event = threading.Event() + self.doneEvent = threading.Event() + self.restartEvent = threading.Event() def run(self): + global threadsShouldTerminate with workingDir(self.cwd): with libclangLock: - if self.line == -1: - # Warm up the caches. For this it is sufficient to get the - # current translation unit. No need to retrieve completion - # results. This short pause is necessary to allow vim to - # initialize itself. Otherwise we would get: E293: block was - # not locked The user does not see any delay, as we just pause - # a background thread. - time.sleep(0.1) - getCurrentTranslationUnit(self.args, self.currentFile, self.fileName, - self.timer) - else: - self.result = getCurrentCompletionResults(self.line, self.column, - self.args, self.currentFile, - self.fileName, self.timer) + # We need to sleep, otherwise vim become crazy and crash. + time.sleep(0.1) + getCurrentTranslationUnit(self.args, self.fileContent, self.fileName, + self.timer) + + while True: + # FIXME: put a timeout and background reparse. + self.event.wait() + self.restartEvent.clear() + if self.shouldTerminate: + return + + with libclangLock: + results = getCurrentCompletionResults(self.line, self.column, + self.args, self.fileContent, + self.fileName, self.timer) + if results is not None: + res = results.results + self.timer.registerEvent("Count # Results (%d)" % len(res)) + + if self.base != "": + res = filter(lambda x: getAbbr(x.string).startswith(self.base), res) + + self.timer.registerEvent("Filter") + + if self.sorting == 'priority': + getPriority = lambda x: x.string.priority + res = sorted(res, None, getPriority) + if self.sorting == 'alpha': + getAbbrevation = lambda x: getAbbr(x.string).lower() + res = sorted(res, None, getAbbrevation) + self.timer.registerEvent("Sort") + + self.result = res + self.doneEvent.set() + self.event.clear() + self.restartEvent.wait() + if self.shouldTerminate: + return + self.doneEvent.clear() + + STATE_WAITING = 0 + STATE_RUNNING = 1 + STATE_FINISHED = 2 + def getCompletionState(self): + if self.doneEvent.is_set(): + return CompleteThread.STATE_FINISHED + + if self.event.is_set(): + return CompleteThread.STATE_RUNNING + else: + return CompleteThread.STATE_WAITING + + +def getThread(filename): + t = threads.get(filename) + if t is None: + params = getCompileParams(filename) + t = CompleteThread(filename, params) + threads[filename] = t + + return t + +def FinishClangComplete(): + # I've got no idea why threadsShouldTerminate is not enough for the + # threads. + threadsShouldTerminate = True + for t in threads.itervalues(): + t.shouldTerminate = True + t.event.set() + t.restartEvent.set() def WarmupCache(): - params = getCompileParams(vim.current.buffer.name) - timer = CodeCompleteTimer(0, "", -1, -1, params) - t = CompleteThread(-1, -1, getCurrentFile(), vim.current.buffer.name, - params, timer) + t = getThread(vim.current.buffer.name) + t.fileContent = getCurrentFile() + t.timer.start(vim.current.buffer.name, -1, -1, t.args, t.cwd) t.start() +def tryComplete(t, column, base): + state = t.getCompletionState() + column = int(column) + if state == CompleteThread.STATE_FINISHED and column == t.column: + return 0 -def getCurrentCompletions(base): - global debug - debug = int(vim.eval("g:clang_debug")) == 1 - sorting = vim.eval("g:clang_sort_algo") - line, _ = vim.current.window.cursor - column = int(vim.eval("b:col")) - params = getCompileParams(vim.current.buffer.name) + if state == CompleteThread.STATE_RUNNING: + return 1 - timer = CodeCompleteTimer(debug, vim.current.buffer.name, line, column, - params) + if not t.is_alive(): + t.start() - t = CompleteThread(line, column, getCurrentFile(), vim.current.buffer.name, - params, timer) - t.start() - while t.isAlive(): - t.join(0.01) - cancel = int(vim.eval('complete_check()')) - if cancel != 0: - return (str([]), timer) + # The thread is waiting, give him some work! + line, _ = vim.current.window.cursor + t.line = line + t.column = column + t.base = base + t.sorting = vim.eval("g:clang_sort_algo") + t.fileContent = getCurrentFile() + t.timer.start(vim.current.buffer.name, line, column, t.args, t.cwd) + t.event.set() + + return 1 +def getCurrentCompletions(t): + timer = t.timer cr = t.result + t.restartEvent.set() + if cr is None: print "Cannot parse this source file. The following arguments " \ - + "are used for clang: " + " ".join(params['args']) + + "are used for clang: " + " ".join(t.args) return (str([]), timer) - results = cr.results - - timer.registerEvent("Count # Results (%s)" % str(len(results))) - - if base != "": - results = filter(lambda x: getAbbr(x.string).startswith(base), results) - - timer.registerEvent("Filter") - - if sorting == 'priority': - getPriority = lambda x: x.string.priority - results = sorted(results, None, getPriority) - if sorting == 'alpha': - getAbbrevation = lambda x: getAbbr(x.string).lower() - results = sorted(results, None, getAbbrevation) - - timer.registerEvent("Sort") - - result = map(formatResult, results) + result = map(formatResult, cr) timer.registerEvent("Format") return (str(result), timer)