diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index 5d343f3d..19232222 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -23,6 +23,7 @@ set cpo&vim let s:script_folder_path = escape( expand( ':p:h' ), '\' ) let s:old_cursor_text = '' let g:ycm_min_num_of_chars_for_completion = 2 + " Set up the plugin, load all our modules, bind our keys etc. function! youcompleteme#Enable() @@ -60,24 +61,44 @@ function! youcompleteme#Enable() exe 'python sys.path = sys.path + ["' . s:script_folder_path . '/../python"]' py import ycm py csystem = ycm.CompletionSystem() - endfunction + function! s:SetCompleteFunc() let &completefunc = 'youcompleteme#Complete' let &l:completefunc = 'youcompleteme#Complete' endfunction + function! s:OnMovedI() + " Technically, what we are doing here is not thread-safe. We are adding a new + " identifier to the database while a background thread may be going through + " that db, searching for matches for the previous query. BUT, we don't care + " what junk that thread may get; those results don't matter anymore since + " right after this function is called, we start a new candidate search with a + " new query, and the old one is thrown away. The background thread never + " modifies the db, only reads it. + call s:AddIdentifierIfNeeded() call s:InvokeCompletion() endfunction + +function! s:AddIdentifierIfNeeded() + py vim.command( "let should_add_identifier = '" + + \ str( ycm.ShouldAddIdentifier() ) + "'" ) + if should_add_identifier != 1 + return + endif + py csystem.AddPreviousIdentifier() +endfunction + + function! s:InvokeCompletion() if &completefunc != "youcompleteme#Complete" return endif - py vim.command( "let cursor_text = '" + ycm.CurrentCursorText() + "'" ) + py vim.command( "let cursor_text = '" + ycm.CurrentCursorTextVim() + "'" ) " infinite loops are bad, mkay? if cursor_text == '' || cursor_text == s:old_cursor_text @@ -94,6 +115,7 @@ function! s:InvokeCompletion() call feedkeys( "\\\", 'n' ) endfunction + " This is our main entry point. This is what vim calls to get completions. function! youcompleteme#Complete(findstart, base) if a:findstart diff --git a/cpp/ycm/Completer.cpp b/cpp/ycm/Completer.cpp index eb710344..d3f7fcbc 100644 --- a/cpp/ycm/Completer.cpp +++ b/cpp/ycm/Completer.cpp @@ -56,7 +56,7 @@ void ThreadMain( TaskStack &task_stack ) Completer::Completer( const std::vector< std::string > &candidates ) : threading_enabled_( false ) { - AddCandidatesToDatabase( candidates, "", "" ); + AddCandidatesToDatabase( candidates, "", "", true ); } @@ -65,7 +65,7 @@ Completer::Completer( const std::vector< std::string > &candidates, const std::string &filepath ) : threading_enabled_( false ) { - AddCandidatesToDatabase( candidates, filetype, filepath ); + AddCandidatesToDatabase( candidates, filetype, filepath, true ); } @@ -90,7 +90,8 @@ void Completer::EnableThreading() void Completer::AddCandidatesToDatabase( const Pylist &new_candidates, const std::string &filetype, - const std::string &filepath ) + const std::string &filepath, + bool clear_database ) { int num_candidates = len( new_candidates ); std::vector< std::string > candidates; @@ -101,18 +102,21 @@ void Completer::AddCandidatesToDatabase( const Pylist &new_candidates, candidates.push_back( extract< std::string >( new_candidates[ i ] ) ); } - AddCandidatesToDatabase( candidates, filetype, filepath ); + AddCandidatesToDatabase( candidates, filetype, filepath, clear_database ); } void Completer::AddCandidatesToDatabase( const std::vector< std::string > &new_candidates, const std::string &filetype, - const std::string &filepath ) + const std::string &filepath, + bool clear_database ) { std::list< const Candidate *> &candidates = GetCandidateList( filetype, filepath ); - candidates.clear(); + + if ( clear_database ) + candidates.clear(); foreach ( const std::string &candidate_text, new_candidates ) { diff --git a/cpp/ycm/Completer.h b/cpp/ycm/Completer.h index e80d17c7..cc630d69 100644 --- a/cpp/ycm/Completer.h +++ b/cpp/ycm/Completer.h @@ -69,11 +69,13 @@ public: void AddCandidatesToDatabase( const std::vector< std::string > &new_candidates, const std::string &filetype, - const std::string &filepath ); + const std::string &filepath, + bool clear_database ); void AddCandidatesToDatabase( const Pylist &new_candidates, const std::string &filetype, - const std::string &filepath ); + const std::string &filepath, + bool clear_database ); // Only provided for tests! std::vector< std::string > CandidatesForQuery( diff --git a/cpp/ycm/indexer.cpp b/cpp/ycm/indexer.cpp index 707dfe20..e4711c08 100644 --- a/cpp/ycm/indexer.cpp +++ b/cpp/ycm/indexer.cpp @@ -32,7 +32,8 @@ BOOST_PYTHON_MODULE(indexer) void (Completer::*actd) (const Pylist&, const std::string&, - const std::string&) = + const std::string&, + bool) = &Completer::AddCandidatesToDatabase; class_< Completer, boost::noncopyable >( "Completer" ) diff --git a/python/ycm.py b/python/ycm.py index d7bcbb20..c379abfe 100644 --- a/python/ycm.py +++ b/python/ycm.py @@ -52,6 +52,24 @@ class CompletionSystem( object ): return results + def AddIdentifier( self, identifier ): + # print identifier + filetype = vim.eval( "&filetype" ) + filepath = vim.eval( "expand('%:p')" ) + + if not filetype or not filepath or not identifier: + return + + self.completer.AddCandidatesToDatabase( [ identifier ], + filetype, + filepath, + False ) + + + def AddPreviousIdentifier( self ): + self.AddIdentifier( PreviousIdentifier() ) + + def AddBufferIdentifiers( self ): text = "\n".join( vim.current.buffer ) text = RemoveIdentFreeText( text ) @@ -63,24 +81,42 @@ class CompletionSystem( object ): if not filetype or not filepath: return - self.completer.AddCandidatesToDatabase( idents, filetype, filepath ) + self.completer.AddCandidatesToDatabase( idents, + filetype, + filepath, + True ) def CurrentColumn(): + """Do NOT access the CurrentColumn in vim.current.line. It doesn't exist yet. + Only the chars befor the current column exist in vim.current.line.""" + # vim's columns start at 1 while vim.current.line columns start at 0 return int( vim.eval( "col('.')" ) ) - 1 +def CurrentLineAndColumn(): + result = vim.eval( "getpos('.')") + line_num = int( result[ 1 ] ) - 1 + column_num = int( result[ 2 ] ) - 1 + return line_num, column_num + + +def IsIdentifierChar( char ): + return char.isalnum() or char == '_' + + def CompletionStartColumn(): line = vim.current.line current_column = CurrentColumn() start_column = current_column - while start_column > 0 and line[ start_column - 1 ].isalnum(): + while start_column > 0 and IsIdentifierChar( line[ start_column - 1 ] ): start_column -= 1 if current_column - start_column < min_num_chars: - return -1 + # for vim, -2 means not found but don't trigger an error message + return -2 return start_column @@ -89,6 +125,37 @@ def EscapeForVim( text ): return text.replace( "'", "''" ) +def PreviousIdentifier(): + line_num, column_num = CurrentLineAndColumn() + buffer = vim.current.buffer + line = buffer[ line_num ] + + end_column = column_num + + while end_column > 0 and not IsIdentifierChar( line[ end_column - 1 ] ): + end_column -= 1 + + # Look at the previous line if we reached the end of the current one + if end_column == 0: + try: + line = buffer[ line_num - 1] + except: + return "" + end_column = len( line ) + while end_column > 0 and not IsIdentifierChar( line[ end_column - 1 ] ): + end_column -= 1 + print end_column, line + + start_column = end_column + while start_column > 0 and IsIdentifierChar( line[ start_column - 1 ] ): + start_column -= 1 + + if end_column - start_column < min_num_chars: + return "" + + return line[ start_column : end_column ] + + def CurrentCursorText(): start_column = CompletionStartColumn() current_column = CurrentColumn() @@ -96,8 +163,37 @@ def CurrentCursorText(): if current_column - start_column < min_num_chars: return "" - cursor_text = vim.current.line[ start_column : current_column ] - return EscapeForVim( cursor_text ) + return vim.current.line[ start_column : current_column ] + + +def CurrentCursorTextVim(): + return EscapeForVim( CurrentCursorText() ) + + +def ShouldAddIdentifier(): + current_column = CurrentColumn() + previous_char_index = current_column - 1 + if previous_char_index < 0: + return 1 + + line = vim.current.line + try: + previous_char = line[ previous_char_index ] + except IndexError: + return 0 + + if IsIdentifierChar( previous_char ): + return 0 + + if ( not IsIdentifierChar( previous_char ) and + previous_char_index > 0 and + IsIdentifierChar( line[ previous_char_index - 1 ] ) ): + return 1 + else: + if line[ : current_column ].strip(): + return 0 + else: + return 1 def SanitizeQuery( query ):