From 29bb90a6b434ec6b462398268fbe871924cbe8ed Mon Sep 17 00:00:00 2001 From: Strahinja Val Markovic Date: Thu, 5 Sep 2013 23:43:14 -0700 Subject: [PATCH] Decoupling completers from Vim; still WIP & broken Note to self: squash this commit before merging into master. --- autoload/youcompleteme.vim | 35 ++- cpp/ycm/ClangCompleter/ClangCompleter.cpp | 42 +-- cpp/ycm/ClangCompleter/ClangCompleter.h | 45 +-- plugin/youcompleteme.vim | 4 +- .../completers/all/identifier_completer.py | 40 ++- python/ycm/completers/all/omni_completer.py | 24 +- python/ycm/completers/completer.py | 82 +++--- python/ycm/completers/completer_utils.py | 8 +- python/ycm/completers/cpp/clang_completer.py | 272 +++++++++--------- python/ycm/completers/cpp/flags.py | 11 +- python/ycm/completers/cs/cs_completer.py | 103 ++++--- .../completers/general/filename_completer.py | 51 ++-- .../general/general_completer_store.py | 15 +- .../completers/general/ultisnips_completer.py | 18 +- .../ycm/completers/python/jedi_completer.py | 104 ++++--- python/ycm/completers/threaded_completer.py | 10 +- python/ycm/extra_conf_store.py | 1 + python/ycm/server_responses.py | 76 +++++ python/ycm/vimsupport.py | 41 ++- python/ycm/youcompleteme.py | 137 ++++++++- 20 files changed, 665 insertions(+), 454 deletions(-) create mode 100644 python/ycm/server_responses.py diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index 075b0112..d8e6e895 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -41,6 +41,7 @@ function! youcompleteme#Enable() py import vim exe 'python sys.path.insert( 0, "' . s:script_folder_path . '/../python" )' py from ycm import base + py from ycm import vimsupport py from ycm import user_options_store py user_options_store.SetAll( base.BuildServerConf() ) py from ycm import extra_conf_store @@ -260,7 +261,7 @@ function! s:OnCursorHold() call s:SetUpCompleteopt() " Order is important here; we need to extract any done diagnostics before " reparsing the file again - call s:UpdateDiagnosticNotifications() + " call s:UpdateDiagnosticNotifications() call s:OnFileReadyToParse() endfunction @@ -327,7 +328,7 @@ function! s:OnCursorMovedNormalMode() return endif - call s:UpdateDiagnosticNotifications() + " call s:UpdateDiagnosticNotifications() call s:OnFileReadyToParse() endfunction @@ -338,7 +339,7 @@ function! s:OnInsertLeave() endif let s:omnifunc_mode = 0 - call s:UpdateDiagnosticNotifications() + " call s:UpdateDiagnosticNotifications() call s:OnFileReadyToParse() py ycm_state.OnInsertLeave() if g:ycm_autoclose_preview_window_after_completion || @@ -572,7 +573,9 @@ command! YcmShowDetailedDiagnostic call s:ShowDetailedDiagnostic() " required (currently that's on buffer save) OR when the SyntasticCheck command " is invoked function! youcompleteme#CurrentFileDiagnostics() - return pyeval( 'ycm_state.GetDiagnosticsForCurrentFile()' ) + " TODO: Make this work again. + " return pyeval( 'ycm_state.GetDiagnosticsForCurrentFile()' ) + return [] endfunction @@ -595,28 +598,24 @@ function! s:CompleterCommand(...) " to select the omni completer or "ft=ycm:ident" to select the identifier " completer. The remaining arguments will passed to the completer. let arguments = copy(a:000) + let completer = '' if a:0 > 0 && strpart(a:1, 0, 3) == 'ft=' if a:1 == 'ft=ycm:omni' - py completer = ycm_state.GetOmniCompleter() + let completer = 'omni' elseif a:1 == 'ft=ycm:ident' - py completer = ycm_state.GetGeneralCompleter() - else - py completer = ycm_state.GetFiletypeCompleterForFiletype( - \ vim.eval('a:1').lstrip('ft=') ) + let completer = 'identifier' endif let arguments = arguments[1:] - elseif pyeval( 'ycm_state.NativeFiletypeCompletionAvailable()' ) - py completer = ycm_state.GetFiletypeCompleter() - else - echohl WarningMsg | - \ echomsg "No native completer found for current buffer." | - \ echomsg "Use ft=... as the first argument to specify a completer." | - \ echohl None - return endif - py completer.OnUserCommand( vim.eval( 'l:arguments' ) ) +py << EOF +response = ycm_state.SendCommandRequest( vim.eval( 'l:arguments' ), + vim.eval( 'l:completer' ) ) +if not response.Valid(): + vimsupport.PostVimMessage( 'No native completer found for current buffer. ' + + 'Use ft=... as the first argument to specify a completer.') +EOF endfunction diff --git a/cpp/ycm/ClangCompleter/ClangCompleter.cpp b/cpp/ycm/ClangCompleter/ClangCompleter.cpp index f822dbbd..961de1d7 100644 --- a/cpp/ycm/ClangCompleter/ClangCompleter.cpp +++ b/cpp/ycm/ClangCompleter/ClangCompleter.cpp @@ -103,7 +103,7 @@ void ClangCompleter::EnableThreading() { std::vector< Diagnostic > ClangCompleter::DiagnosticsForFile( - const std::string &filename ) { + std::string filename ) { shared_ptr< TranslationUnit > unit = translation_unit_store_.Get( filename ); if ( !unit ) @@ -127,9 +127,9 @@ bool ClangCompleter::UpdatingTranslationUnit( const std::string &filename ) { void ClangCompleter::UpdateTranslationUnit( - const std::string &filename, - const std::vector< UnsavedFile > &unsaved_files, - const std::vector< std::string > &flags ) { + std::string filename, + std::vector< UnsavedFile > unsaved_files, + std::vector< std::string > flags ) { bool translation_unit_created; shared_ptr< TranslationUnit > unit = translation_unit_store_.GetOrCreate( filename, @@ -182,11 +182,11 @@ Future< void > ClangCompleter::UpdateTranslationUnitAsync( std::vector< CompletionData > ClangCompleter::CandidatesForLocationInFile( - const std::string &filename, + std::string filename, int line, int column, - const std::vector< UnsavedFile > &unsaved_files, - const std::vector< std::string > &flags ) { + std::vector< UnsavedFile > unsaved_files, + std::vector< std::string > flags ) { shared_ptr< TranslationUnit > unit = translation_unit_store_.GetOrCreate( filename, unsaved_files, flags ); @@ -201,12 +201,12 @@ ClangCompleter::CandidatesForLocationInFile( Future< AsyncCompletions > ClangCompleter::CandidatesForQueryAndLocationInFileAsync( - const std::string &query, - const std::string &filename, + std::string query, + std::string filename, int line, int column, - const std::vector< UnsavedFile > &unsaved_files, - const std::vector< std::string > &flags ) { + std::vector< UnsavedFile > unsaved_files, + std::vector< std::string > flags ) { // TODO: throw exception when threading is not enabled and this is called if ( !threading_enabled_ ) return Future< AsyncCompletions >(); @@ -238,7 +238,11 @@ ClangCompleter::CandidatesForQueryAndLocationInFileAsync( CreateSortingTask( query, future ); if ( skip_clang_result_cache ) { - CreateClangTask( filename, line, column, unsaved_files, flags ); + CreateClangTask( boost::move( filename ), + line, + column, + boost::move( unsaved_files ), + boost::move( flags ) ); } return Future< AsyncCompletions >( boost::move( future ) ); @@ -246,11 +250,11 @@ ClangCompleter::CandidatesForQueryAndLocationInFileAsync( Location ClangCompleter::GetDeclarationLocation( - const std::string &filename, + std::string filename, int line, int column, - const std::vector< UnsavedFile > &unsaved_files, - const std::vector< std::string > &flags ) { + std::vector< UnsavedFile > unsaved_files, + std::vector< std::string > flags ) { shared_ptr< TranslationUnit > unit = translation_unit_store_.GetOrCreate( filename, unsaved_files, flags ); @@ -263,11 +267,11 @@ Location ClangCompleter::GetDeclarationLocation( Location ClangCompleter::GetDefinitionLocation( - const std::string &filename, + std::string filename, int line, int column, - const std::vector< UnsavedFile > &unsaved_files, - const std::vector< std::string > &flags ) { + std::vector< UnsavedFile > unsaved_files, + std::vector< std::string > flags ) { shared_ptr< TranslationUnit > unit = translation_unit_store_.GetOrCreate( filename, unsaved_files, flags ); @@ -279,7 +283,7 @@ Location ClangCompleter::GetDefinitionLocation( } -void ClangCompleter::DeleteCachesForFileAsync( const std::string &filename ) { +void ClangCompleter::DeleteCachesForFileAsync( std::string filename ) { file_cache_delete_stack_.Push( filename ); } diff --git a/cpp/ycm/ClangCompleter/ClangCompleter.h b/cpp/ycm/ClangCompleter/ClangCompleter.h index 266e3122..6ae7819c 100644 --- a/cpp/ycm/ClangCompleter/ClangCompleter.h +++ b/cpp/ycm/ClangCompleter/ClangCompleter.h @@ -59,18 +59,23 @@ public: void EnableThreading(); - std::vector< Diagnostic > DiagnosticsForFile( const std::string &filename ); + std::vector< Diagnostic > DiagnosticsForFile( std::string filename ); bool UpdatingTranslationUnit( const std::string &filename ); + // NOTE: params are taken by value on purpose! With a C++11 compiler we can + // avoid internal copies if params are taken by value (move ctors FTW), and we + // need to ensure we own the memory. + // TODO: Change some of these params back to const ref where possible after we + // get the server up. + // TODO: Remove the async methods and the threads when the server is ready. + // Public because of unit tests (gtest is not very thread-friendly) void UpdateTranslationUnit( - const std::string &filename, - const std::vector< UnsavedFile > &unsaved_files, - const std::vector< std::string > &flags ); + std::string filename, + std::vector< UnsavedFile > unsaved_files, + std::vector< std::string > flags ); - // NOTE: params are taken by value on purpose! With a C++11 compiler we can - // avoid internal copies if params are taken by value (move ctors FTW) Future< void > UpdateTranslationUnitAsync( std::string filename, std::vector< UnsavedFile > unsaved_files, @@ -78,35 +83,35 @@ public: // Public because of unit tests (gtest is not very thread-friendly) std::vector< CompletionData > CandidatesForLocationInFile( - const std::string &filename, + std::string filename, int line, int column, - const std::vector< UnsavedFile > &unsaved_files, - const std::vector< std::string > &flags ); + std::vector< UnsavedFile > unsaved_files, + std::vector< std::string > flags ); Future< AsyncCompletions > CandidatesForQueryAndLocationInFileAsync( - const std::string &query, - const std::string &filename, + std::string query, + std::string filename, int line, int column, - const std::vector< UnsavedFile > &unsaved_files, - const std::vector< std::string > &flags ); + std::vector< UnsavedFile > unsaved_files, + std::vector< std::string > flags ); Location GetDeclarationLocation( - const std::string &filename, + std::string filename, int line, int column, - const std::vector< UnsavedFile > &unsaved_files, - const std::vector< std::string > &flags ); + std::vector< UnsavedFile > unsaved_files, + std::vector< std::string > flags ); Location GetDefinitionLocation( - const std::string &filename, + std::string filename, int line, int column, - const std::vector< UnsavedFile > &unsaved_files, - const std::vector< std::string > &flags ); + std::vector< UnsavedFile > unsaved_files, + std::vector< std::string > flags ); - void DeleteCachesForFileAsync( const std::string &filename ); + void DeleteCachesForFileAsync( std::string filename ); private: diff --git a/plugin/youcompleteme.vim b/plugin/youcompleteme.vim index d3c2ee3e..020db8b2 100644 --- a/plugin/youcompleteme.vim +++ b/plugin/youcompleteme.vim @@ -143,8 +143,8 @@ let g:ycm_extra_conf_globlist = let g:ycm_filepath_completion_use_working_dir = \ get( g:, 'ycm_filepath_completion_use_working_dir', 0 ) -" Default semantic triggers are in python/ycm/completers/completer.py, these -" just append new triggers to the default dict. +" Default semantic triggers are in python/ycm/completers/completer_utils.py +" these just append new triggers to the default dict. let g:ycm_semantic_triggers = \ get( g:, 'ycm_semantic_triggers', {} ) diff --git a/python/ycm/completers/all/identifier_completer.py b/python/ycm/completers/all/identifier_completer.py index 70fcdbe3..196ff85a 100644 --- a/python/ycm/completers/all/identifier_completer.py +++ b/python/ycm/completers/all/identifier_completer.py @@ -25,6 +25,7 @@ from ycm.completers.general_completer import GeneralCompleter from ycm.completers.general import syntax_parse from ycm import vimsupport from ycm import utils +from ycm import server_responses MAX_IDENTIFIER_COMPLETIONS_RETURNED = 10 SYNTAX_FILENAME = 'YCM_PLACEHOLDER_FOR_SYNTAX' @@ -39,15 +40,14 @@ class IdentifierCompleter( GeneralCompleter ): self.filetypes_with_keywords_loaded = set() - def ShouldUseNow( self, start_column, unused_current_line ): - return self.QueryLengthAboveMinThreshold( start_column ) + def ShouldUseNow( self, request_data ): + return self.QueryLengthAboveMinThreshold( request_data ) - def CandidatesForQueryAsync( self, query, unused_start_column ): - filetype = vim.eval( "&filetype" ) + def CandidatesForQueryAsync( self, request_data ): self.completions_future = self.completer.CandidatesForQueryAndTypeAsync( - utils.SanitizeQuery( query ), - filetype ) + utils.SanitizeQuery( request_data[ 'query' ] ), + request_data[ 'filetypes' ][ 0 ] ) def AddIdentifier( self, identifier ): @@ -83,17 +83,16 @@ class IdentifierCompleter( GeneralCompleter ): self.AddIdentifier( stripped_cursor_identifier ) - def AddBufferIdentifiers( self ): - # TODO: use vimsupport.GetFiletypes; also elsewhere in file - filetype = vim.eval( "&filetype" ) - filepath = vim.eval( "expand('%:p')" ) + def AddBufferIdentifiers( self, request_data ): + filetype = request_data[ 'filetypes' ][ 0 ] + filepath = request_data[ 'filepath' ] collect_from_comments_and_strings = bool( self.user_options[ 'collect_identifiers_from_comments_and_strings' ] ) if not filetype or not filepath: return - text = "\n".join( vim.current.buffer ) + text = request_data[ 'file_data' ][ filepath ][ 'contents' ] self.completer.AddIdentifiersToDatabaseFromBufferAsync( text, filetype, @@ -147,14 +146,15 @@ class IdentifierCompleter( GeneralCompleter ): filepath ) - def OnFileReadyToParse( self ): - self.AddBufferIdentifiers() + def OnFileReadyToParse( self, request_data ): + self.AddBufferIdentifiers( request_data ) - if self.user_options[ 'collect_identifiers_from_tags_files' ]: - self.AddIdentifiersFromTagFiles() + # TODO: make these work again + # if self.user_options[ 'collect_identifiers_from_tags_files' ]: + # self.AddIdentifiersFromTagFiles() - if self.user_options[ 'seed_identifiers_with_syntax' ]: - self.AddIdentifiersFromSyntax() + # if self.user_options[ 'seed_identifiers_with_syntax' ]: + # self.AddIdentifiersFromSyntax() def OnInsertLeave( self ): @@ -174,11 +174,7 @@ class IdentifierCompleter( GeneralCompleter ): completions = _RemoveSmallCandidates( completions, self.user_options[ 'min_num_identifier_candidate_chars' ] ) - # We will never have duplicates in completions so with 'dup':1 we tell Vim - # to add this candidate even if it's a duplicate of an existing one (which - # will never happen). This saves us some expensive string matching - # operations in Vim. - return [ { 'word': x, 'dup': 1 } for x in completions ] + return [ server_responses.BuildCompletionData( x ) for x in completions ] def _PreviousIdentifier( min_num_completion_start_chars ): diff --git a/python/ycm/completers/all/omni_completer.py b/python/ycm/completers/all/omni_completer.py index 1c19452b..d0a51786 100644 --- a/python/ycm/completers/all/omni_completer.py +++ b/python/ycm/completers/all/omni_completer.py @@ -40,29 +40,27 @@ class OmniCompleter( Completer ): return bool( self.user_options[ 'cache_omnifunc' ] ) - def ShouldUseNow( self, start_column, current_line ): + def ShouldUseNow( self, request_data ): if self.ShouldUseCache(): - return super( OmniCompleter, self ).ShouldUseNow( start_column, - current_line ) - return self.ShouldUseNowInner( start_column, current_line ) + return super( OmniCompleter, self ).ShouldUseNow( request_data ) + return self.ShouldUseNowInner( request_data ) - def ShouldUseNowInner( self, start_column, current_line ): + def ShouldUseNowInner( self, request_data ): if not self.omnifunc: return False - return super( OmniCompleter, self ).ShouldUseNowInner( start_column, - current_line ) + return super( OmniCompleter, self ).ShouldUseNowInner( request_data ) - def CandidatesForQueryAsync( self, query, unused_start_column ): + def CandidatesForQueryAsync( self, request_data ): if self.ShouldUseCache(): return super( OmniCompleter, self ).CandidatesForQueryAsync( - query, unused_start_column ) + request_data ) else: - return self.CandidatesForQueryAsyncInner( query, unused_start_column ) + return self.CandidatesForQueryAsyncInner( request_data ) - def CandidatesForQueryAsyncInner( self, query, unused_start_column ): + def CandidatesForQueryAsyncInner( self, request_data ): if not self.omnifunc: self.stored_candidates = None return @@ -75,7 +73,7 @@ class OmniCompleter( Completer ): omnifunc_call = [ self.omnifunc, "(0,'", - vimsupport.EscapeForVim( query ), + vimsupport.EscapeForVim( request_data[ 'query' ] ), "')" ] items = vim.eval( ''.join( omnifunc_call ) ) @@ -98,7 +96,7 @@ class OmniCompleter( Completer ): return True - def OnFileReadyToParse( self ): + def OnFileReadyToParse( self, request_data ): self.omnifunc = vim.eval( '&omnifunc' ) diff --git a/python/ycm/completers/completer.py b/python/ycm/completers/completer.py index b7cf7abb..60dcd6ec 100644 --- a/python/ycm/completers/completer.py +++ b/python/ycm/completers/completer.py @@ -19,7 +19,6 @@ import abc import ycm_core -from ycm import vimsupport from ycm.completers.completer_utils import TriggersForFiletype NO_USER_COMMANDS = 'This completer does not define any commands.' @@ -116,32 +115,35 @@ class Completer( object ): def __init__( self, user_options ): self.user_options = user_options self.min_num_chars = user_options[ 'min_num_of_chars_for_completion' ] - self.triggers_for_filetype = TriggersForFiletype() + self.triggers_for_filetype = TriggersForFiletype( + user_options[ 'semantic_triggers' ] ) self.completions_future = None self.completions_cache = None - self.completion_start_column = None # It's highly likely you DON'T want to override this function but the *Inner # version of it. - def ShouldUseNow( self, start_column, current_line ): - inner_says_yes = self.ShouldUseNowInner( start_column, current_line ) + def ShouldUseNow( self, request_data ): + inner_says_yes = self.ShouldUseNowInner( request_data ) if not inner_says_yes: self.completions_cache = None previous_results_were_empty = ( self.completions_cache and self.completions_cache.CacheValid( - start_column ) and + request_data[ 'line_num' ], + request_data[ 'start_column' ] ) and not self.completions_cache.raw_completions ) return inner_says_yes and not previous_results_were_empty - def ShouldUseNowInner( self, start_column, current_line ): + def ShouldUseNowInner( self, request_data ): + current_line = request_data[ 'line_value' ] + start_column = request_data[ 'start_column' ] line_length = len( current_line ) if not line_length or start_column - 1 >= line_length: return False - filetype = self._CurrentFiletype() + filetype = self._CurrentFiletype( request_data[ 'filetypes' ] ) triggers = self.triggers_for_filetype[ filetype ] for trigger in triggers: @@ -158,52 +160,61 @@ class Completer( object ): return False - def QueryLengthAboveMinThreshold( self, start_column ): - query_length = vimsupport.CurrentColumn() - start_column + def QueryLengthAboveMinThreshold( self, request_data ): + query_length = request_data[ 'column_num' ] - request_data[ 'start_column' ] return query_length >= self.min_num_chars # It's highly likely you DON'T want to override this function but the *Inner # version of it. - def CandidatesForQueryAsync( self, query, start_column ): - self.completion_start_column = start_column + def CandidatesForQueryAsync( self, request_data ): + self.request_data = request_data - if query and self.completions_cache and self.completions_cache.CacheValid( - start_column ): + if ( request_data[ 'query' ] and + self.completions_cache and + self.completions_cache.CacheValid( request_data[ 'line_num' ], + request_data[ 'start_column' ] ) ): self.completions_cache.filtered_completions = ( self.FilterAndSortCandidates( self.completions_cache.raw_completions, - query ) ) + request_data[ 'query' ] ) ) else: self.completions_cache = None - self.CandidatesForQueryAsyncInner( query, start_column ) + self.CandidatesForQueryAsyncInner( request_data ) def DefinedSubcommands( self ): return [] - def EchoUserCommandsHelpMessage( self ): + def UserCommandsHelpMessage( self ): subcommands = self.DefinedSubcommands() if subcommands: - vimsupport.EchoText( 'Supported commands are:\n' + - '\n'.join( subcommands ) + - '\nSee the docs for information on what they do.' ) + return ( 'Supported commands are:\n' + + '\n'.join( subcommands ) + + '\nSee the docs for information on what they do.' ) else: - vimsupport.EchoText( 'No supported subcommands' ) + return 'No supported subcommands' def FilterAndSortCandidates( self, candidates, query ): if not candidates: return [] - if hasattr( candidates, 'words' ): - candidates = candidates.words - items_are_objects = 'word' in candidates[ 0 ] + # We need to handle both an omni_completer style completer and a server + # style completer + if 'words' in candidates: + candidates = candidates[ 'words' ] + + sort_property = '' + if 'word' in candidates[ 0 ]: + sort_property = 'word' + elif 'insertion_text' in candidates[ 0 ]: + sort_property = 'insertion_text' matches = ycm_core.FilterAndSortCandidates( candidates, - 'word' if items_are_objects else '', + sort_property, query ) return matches @@ -238,8 +249,8 @@ class Completer( object ): else: self.completions_cache = CompletionsCache() self.completions_cache.raw_completions = self.CandidatesFromStoredRequestInner() - self.completions_cache.line, _ = vimsupport.CurrentLineAndColumn() - self.completions_cache.column = self.completion_start_column + self.completions_cache.line = self.request_data[ 'line_num' ] + self.completions_cache.column = self.request_data[ 'start_column' ] return self.completions_cache.raw_completions @@ -249,7 +260,7 @@ class Completer( object ): return self.completions_future.GetResults() - def OnFileReadyToParse( self ): + def OnFileReadyToParse( self, request_data ): pass @@ -269,8 +280,8 @@ class Completer( object ): pass - def OnUserCommand( self, arguments ): - vimsupport.PostVimMessage( NO_USER_COMMANDS ) + def OnUserCommand( self, arguments, request_data ): + raise NotImplementedError( NO_USER_COMMANDS ) def OnCurrentIdentifierFinished( self ): @@ -285,7 +296,7 @@ class Completer( object ): return [] - def ShowDetailedDiagnostic( self ): + def GetDetailedDiagnostic( self ): pass @@ -293,8 +304,7 @@ class Completer( object ): return False - def _CurrentFiletype( self ): - filetypes = vimsupport.CurrentFiletypes() + def _CurrentFiletype( self, filetypes ): supported = self.SupportedFiletypes() for filetype in filetypes: @@ -321,9 +331,7 @@ class CompletionsCache( object ): self.filtered_completions = [] - def CacheValid( self, start_column ): - completion_line, _ = vimsupport.CurrentLineAndColumn() - completion_column = start_column - return completion_line == self.line and completion_column == self.column + def CacheValid( self, current_line, start_column ): + return current_line == self.line and start_column == self.column diff --git a/python/ycm/completers/completer_utils.py b/python/ycm/completers/completer_utils.py index 1f4d0e50..4fbd9b20 100644 --- a/python/ycm/completers/completer_utils.py +++ b/python/ycm/completers/completer_utils.py @@ -19,7 +19,6 @@ from collections import defaultdict from copy import deepcopy -import vim DEFAULT_FILETYPE_TRIGGERS = { 'c' : ['->', '.'], @@ -58,12 +57,9 @@ def _FiletypeDictUnion( dict_one, dict_two ): return final_dict -def TriggersForFiletype(): - user_triggers = _FiletypeTriggerDictFromSpec( - vim.eval( 'g:ycm_semantic_triggers' ) ) - +def TriggersForFiletype( user_triggers ): default_triggers = _FiletypeTriggerDictFromSpec( DEFAULT_FILETYPE_TRIGGERS ) - return _FiletypeDictUnion( default_triggers, user_triggers ) + return _FiletypeDictUnion( default_triggers, dict( user_triggers ) ) diff --git a/python/ycm/completers/cpp/clang_completer.py b/python/ycm/completers/cpp/clang_completer.py index 35d82572..fc976ab1 100644 --- a/python/ycm/completers/cpp/clang_completer.py +++ b/python/ycm/completers/cpp/clang_completer.py @@ -18,14 +18,23 @@ # along with YouCompleteMe. If not, see . from collections import defaultdict -import vim import ycm_core -from ycm import vimsupport +import logging +from ycm import server_responses from ycm import extra_conf_store from ycm.completers.completer import Completer from ycm.completers.cpp.flags import Flags CLANG_FILETYPES = set( [ 'c', 'cpp', 'objc', 'objcpp' ] ) +MIN_LINES_IN_FILE_TO_PARSE = 5 +PARSING_FILE_MESSAGE = 'Still parsing file, no completions yet.' +NO_COMPILE_FLAGS_MESSAGE = 'Still no compile flags, no completions yet.' +NO_COMPLETIONS_MESSAGE = 'No completions found; errors in the file?' +INVALID_FILE_MESSAGE = 'File is invalid.' +FILE_TOO_SHORT_MESSAGE = ( + 'File is less than {} lines long; not compiling.'.format( + MIN_LINES_IN_FILE_TO_PARSE ) ) +NO_DIAGNOSTIC_MESSAGE = 'No diagnostic for current line!' class ClangCompleter( Completer ): @@ -35,12 +44,11 @@ class ClangCompleter( Completer ): 'max_diagnostics_to_display' ] self.completer = ycm_core.ClangCompleter() self.completer.EnableThreading() - self.contents_holder = [] - self.filename_holder = [] self.last_prepared_diagnostics = [] self.parse_future = None self.flags = Flags() self.diagnostic_store = None + self._logger = logging.getLogger( __name__ ) # We set this flag when a compilation request comes in while one is already # in progress. We use this to trigger the pending request after the previous @@ -53,63 +61,52 @@ class ClangCompleter( Completer ): return CLANG_FILETYPES - def GetUnsavedFilesVector( self ): - # CAREFUL HERE! For UnsavedFile filename and contents we are referring - # directly to Python-allocated and -managed memory since we are accepting - # pointers to data members of python objects. We need to ensure that those - # objects outlive our UnsavedFile objects. This is why we need the - # contents_holder and filename_holder lists, to make sure the string objects - # are still around when we call CandidatesForQueryAndLocationInFile. We do - # this to avoid an extra copy of the entire file contents. - + def GetUnsavedFilesVector( self, request_data ): files = ycm_core.UnsavedFileVec() - self.contents_holder = [] - self.filename_holder = [] - for buffer in vimsupport.GetUnsavedBuffers(): - if not ClangAvailableForBuffer( buffer ): + for filename, file_data in request_data[ 'file_data' ].iteritems(): + if not ClangAvailableForFiletypes( file_data[ 'filetypes' ] ): continue - contents = '\n'.join( buffer ) - name = buffer.name - if not contents or not name: + contents = file_data[ 'contents' ] + if not contents or not filename: continue - self.contents_holder.append( contents ) - self.filename_holder.append( name ) unsaved_file = ycm_core.UnsavedFile() - unsaved_file.contents_ = self.contents_holder[ -1 ] - unsaved_file.length_ = len( self.contents_holder[ -1 ] ) - unsaved_file.filename_ = self.filename_holder[ -1 ] + unsaved_file.contents_ = contents + unsaved_file.length_ = len( contents ) + unsaved_file.filename_ = filename files.append( unsaved_file ) - return files - def CandidatesForQueryAsync( self, query, start_column ): - filename = vim.current.buffer.name + def CandidatesForQueryAsync( self, request_data ): + filename = request_data[ 'filepath' ] if not filename: return if self.completer.UpdatingTranslationUnit( filename ): - vimsupport.PostVimMessage( 'Still parsing file, no completions yet.' ) self.completions_future = None - return + self._logger.info( PARSING_FILE_MESSAGE ) + return server_responses.BuildDisplayMessageResponse( + PARSING_FILE_MESSAGE ) flags = self.flags.FlagsForFile( filename ) if not flags: - vimsupport.PostVimMessage( 'Still no compile flags, no completions yet.' ) self.completions_future = None - return + self._logger.info( NO_COMPILE_FLAGS_MESSAGE ) + return server_responses.BuildDisplayMessageResponse( + NO_COMPILE_FLAGS_MESSAGE ) # TODO: sanitize query, probably in C++ code files = ycm_core.UnsavedFileVec() + query = request_data[ 'query' ] if not query: - files = self.GetUnsavedFilesVector() + files = self.GetUnsavedFilesVector( request_data ) - line, _ = vim.current.window.cursor - column = start_column + 1 + line = request_data[ 'line_num' ] + 1 + column = request_data[ 'start_column' ] + 1 self.completions_future = ( self.completer.CandidatesForQueryAndLocationInFileAsync( query, @@ -123,10 +120,11 @@ class ClangCompleter( Completer ): def CandidatesFromStoredRequest( self ): if not self.completions_future: return [] - results = [ CompletionDataToDict( x ) for x in + results = [ ConvertCompletionData( x ) for x in self.completions_future.GetResults() ] if not results: - vimsupport.PostVimMessage( 'No completions found; errors in the file?' ) + self._logger.warning( NO_COMPLETIONS_MESSAGE ) + raise RuntimeError( NO_COMPLETIONS_MESSAGE ) return results @@ -137,37 +135,37 @@ class ClangCompleter( Completer ): 'ClearCompilationFlagCache'] - def OnUserCommand( self, arguments ): + def OnUserCommand( self, arguments, request_data ): if not arguments: - self.EchoUserCommandsHelpMessage() - return + raise ValueError( self.UserCommandsHelpMessage() ) command = arguments[ 0 ] if command == 'GoToDefinition': - self._GoToDefinition() + self._GoToDefinition( request_data ) elif command == 'GoToDeclaration': - self._GoToDeclaration() + self._GoToDeclaration( request_data ) elif command == 'GoToDefinitionElseDeclaration': - self._GoToDefinitionElseDeclaration() + self._GoToDefinitionElseDeclaration( request_data ) elif command == 'ClearCompilationFlagCache': - self._ClearCompilationFlagCache() + self._ClearCompilationFlagCache( request_data ) - def _LocationForGoTo( self, goto_function ): - filename = vim.current.buffer.name + def _LocationForGoTo( self, goto_function, request_data ): + filename = request_data[ 'filepath' ] if not filename: - return None + self._logger.warning( INVALID_FILE_MESSAGE ) + return server_responses.BuildDisplayMessageResponse( + INVALID_FILE_MESSAGE ) flags = self.flags.FlagsForFile( filename ) if not flags: - vimsupport.PostVimMessage( 'Still no compile flags, can\'t compile.' ) - return None + self._logger.info( NO_COMPILE_FLAGS_MESSAGE ) + return server_responses.BuildDisplayMessageResponse( + NO_COMPILE_FLAGS_MESSAGE ) files = self.GetUnsavedFilesVector() - line, column = vimsupport.CurrentLineAndColumn() - # Making the line & column 1-based instead of 0-based - line += 1 - column += 1 + line = request_data[ 'line_num' ] + 1 + column = request_data[ 'start_column' ] + 1 return getattr( self.completer, goto_function )( filename, line, @@ -176,39 +174,37 @@ class ClangCompleter( Completer ): flags ) - def _GoToDefinition( self ): + def _GoToDefinition( self, request_data ): location = self._LocationForGoTo( 'GetDefinitionLocation' ) if not location or not location.IsValid(): - vimsupport.PostVimMessage( 'Can\'t jump to definition.' ) - return + raise RuntimeError( 'Can\'t jump to definition.' ) - vimsupport.JumpToLocation( location.filename_, - location.line_number_, - location.column_number_ ) + return server_responses.BuildGoToResponse( location.filename_, + location.line_number_, + location.column_number_ ) - def _GoToDeclaration( self ): + def _GoToDeclaration( self, request_data ): location = self._LocationForGoTo( 'GetDeclarationLocation' ) if not location or not location.IsValid(): - vimsupport.PostVimMessage( 'Can\'t jump to declaration.' ) - return + raise RuntimeError( 'Can\'t jump to declaration.' ) - vimsupport.JumpToLocation( location.filename_, - location.line_number_, - location.column_number_ ) + return server_responses.BuildGoToResponse( location.filename_, + location.line_number_, + location.column_number_ ) - def _GoToDefinitionElseDeclaration( self ): + def _GoToDefinitionElseDeclaration( self, request_data ): location = self._LocationForGoTo( 'GetDefinitionLocation' ) if not location or not location.IsValid(): location = self._LocationForGoTo( 'GetDeclarationLocation' ) if not location or not location.IsValid(): - vimsupport.PostVimMessage( 'Can\'t jump to definition or declaration.' ) - return + raise RuntimeError( 'Can\'t jump to definition or declaration.' ) + + return server_responses.BuildGoToResponse( location.filename_, + location.line_number_, + location.column_number_ ) - vimsupport.JumpToLocation( location.filename_, - location.line_number_, - location.column_number_ ) def _ClearCompilationFlagCache( self ): @@ -216,14 +212,18 @@ class ClangCompleter( Completer ): - def OnFileReadyToParse( self ): - if vimsupport.NumLinesInBuffer( vim.current.buffer ) < 5: + def OnFileReadyToParse( self, request_data ): + filename = request_data[ 'filepath' ] + contents = request_data[ 'file_data' ][ filename ][ 'contents' ] + if contents.count( '\n' ) < MIN_LINES_IN_FILE_TO_PARSE: self.parse_future = None - return + self._logger.warning( FILE_TOO_SHORT_MESSAGE ) + raise ValueError( FILE_TOO_SHORT_MESSAGE ) - filename = vim.current.buffer.name if not filename: - return + self._logger.warning( INVALID_FILE_MESSAGE ) + return server_responses.BuildDisplayMessageResponse( + INVALID_FILE_MESSAGE ) if self.completer.UpdatingTranslationUnit( filename ): self.extra_parse_desired = True @@ -232,11 +232,13 @@ class ClangCompleter( Completer ): flags = self.flags.FlagsForFile( filename ) if not flags: self.parse_future = None - return + self._logger.info( NO_COMPILE_FLAGS_MESSAGE ) + return server_responses.BuildDisplayMessageResponse( + NO_COMPILE_FLAGS_MESSAGE ) self.parse_future = self.completer.UpdateTranslationUnitAsync( filename, - self.GetUnsavedFilesVector(), + self.GetUnsavedFilesVector( request_data ), flags ) self.extra_parse_desired = False @@ -253,16 +255,18 @@ class ClangCompleter( Completer ): return self.parse_future.ResultsReady() - def GettingCompletions( self ): - return self.completer.UpdatingTranslationUnit( vim.current.buffer.name ) + def GettingCompletions( self, request_data ): + return self.completer.UpdatingTranslationUnit( request_data[ 'filepath' ] ) - def GetDiagnosticsForCurrentFile( self ): + def GetDiagnosticsForCurrentFile( self, request_data ): + filename = request_data[ 'filepath' ] if self.DiagnosticsForCurrentFileReady(): - diagnostics = self.completer.DiagnosticsForFile( vim.current.buffer.name ) + diagnostics = self.completer.DiagnosticsForFile( filename ) self.diagnostic_store = DiagnosticsToDiagStructure( diagnostics ) - self.last_prepared_diagnostics = [ DiagnosticToDict( x ) for x in - diagnostics[ : self.max_diagnostics_to_display ] ] + self.last_prepared_diagnostics = [ + server_responses.BuildDiagnosticData( x ) for x in + diagnostics[ : self.max_diagnostics_to_display ] ] self.parse_future = None if self.extra_parse_desired: @@ -271,23 +275,19 @@ class ClangCompleter( Completer ): return self.last_prepared_diagnostics - def ShowDetailedDiagnostic( self ): - current_line, current_column = vimsupport.CurrentLineAndColumn() - - # CurrentLineAndColumn() numbers are 0-based, clang numbers are 1-based - current_line += 1 - current_column += 1 - - current_file = vim.current.buffer.name + def GetDetailedDiagnostic( self, request_data ): + current_line = request_data[ 'line_num' ] + 1 + current_column = request_data[ 'column_num' ] + 1 + current_file = request_data[ 'filepath' ] if not self.diagnostic_store: - vimsupport.PostVimMessage( "No diagnostic for current line!" ) - return + return server_responses.BuildDisplayMessageResponse( + NO_DIAGNOSTIC_MESSAGE ) diagnostics = self.diagnostic_store[ current_file ][ current_line ] if not diagnostics: - vimsupport.PostVimMessage( "No diagnostic for current line!" ) - return + return server_responses.BuildDisplayMessageResponse( + NO_DIAGNOSTIC_MESSAGE ) closest_diagnostic = None distance_to_closest_diagnostic = 999 @@ -298,50 +298,61 @@ class ClangCompleter( Completer ): distance_to_closest_diagnostic = distance closest_diagnostic = diagnostic - vimsupport.EchoText( closest_diagnostic.long_formatted_text_ ) + return server_responses.BuildDisplayMessageResponse( + closest_diagnostic.long_formatted_text_ ) - def ShouldUseNow( self, start_column, current_line ): + def ShouldUseNow( self, request_data ): # We don't want to use the Completer API cache, we use one in the C++ code. - return self.ShouldUseNowInner( start_column, current_line ) + return self.ShouldUseNowInner( request_data ) - def DebugInfo( self ): - filename = vim.current.buffer.name + def DebugInfo( self, request_data ): + filename = request_data[ 'filepath' ] if not filename: return '' flags = self.flags.FlagsForFile( filename ) or [] source = extra_conf_store.ModuleFileForSourceFile( filename ) - return 'Flags for {0} loaded from {1}:\n{2}'.format( filename, - source, - list( flags ) ) + return server_responses.BuildDisplayMessageResponse( + 'Flags for {0} loaded from {1}:\n{2}'.format( filename, + source, + list( flags ) ) ) # TODO: make these functions module-local -def CompletionDataToDict( completion_data ): - # see :h complete-items for a description of the dictionary fields - return { - 'word' : completion_data.TextToInsertInBuffer(), - 'abbr' : completion_data.MainCompletionText(), - 'menu' : completion_data.ExtraMenuInfo(), - 'kind' : completion_data.kind_, - 'info' : completion_data.DetailedInfoForPreviewWindow(), - 'dup' : 1, - } +# def CompletionDataToDict( completion_data ): +# # see :h complete-items for a description of the dictionary fields +# return { +# 'word' : completion_data.TextToInsertInBuffer(), +# 'abbr' : completion_data.MainCompletionText(), +# 'menu' : completion_data.ExtraMenuInfo(), +# 'kind' : completion_data.kind_, +# 'info' : completion_data.DetailedInfoForPreviewWindow(), +# 'dup' : 1, +# } -def DiagnosticToDict( diagnostic ): - # see :h getqflist for a description of the dictionary fields - return { - # TODO: wrap the bufnr generation into a function - 'bufnr' : int( vim.eval( "bufnr('{0}', 1)".format( - diagnostic.filename_ ) ) ), - 'lnum' : diagnostic.line_number_, - 'col' : diagnostic.column_number_, - 'text' : diagnostic.text_, - 'type' : diagnostic.kind_, - 'valid' : 1 - } +# def DiagnosticToDict( diagnostic ): +# # see :h getqflist for a description of the dictionary fields +# return { +# # TODO: wrap the bufnr generation into a function +# 'bufnr' : int( vim.eval( "bufnr('{0}', 1)".format( +# diagnostic.filename_ ) ) ), +# 'lnum' : diagnostic.line_number_, +# 'col' : diagnostic.column_number_, +# 'text' : diagnostic.text_, +# 'type' : diagnostic.kind_, +# 'valid' : 1 +# } + + +def ConvertCompletionData( completion_data ): + return server_responses.BuildCompletionData( + insertion_text = completion_data.TextToInsertInBuffer(), + menu_text = completion_data.MainCompletionText(), + extra_menu_info = completion_data.ExtraMenuInfo(), + kind = completion_data.kind_, + detailed_info = completion_data.DetailedInfoForPreviewWindow() ) def DiagnosticsToDiagStructure( diagnostics ): @@ -352,12 +363,9 @@ def DiagnosticsToDiagStructure( diagnostics ): return structure -def ClangAvailableForBuffer( buffer_object ): - filetypes = vimsupport.FiletypesForBuffer( buffer_object ) +def ClangAvailableForFiletypes( filetypes ): return any( [ filetype in CLANG_FILETYPES for filetype in filetypes ] ) -def InCFamilyFile(): - return any( [ filetype in CLANG_FILETYPES for filetype in - vimsupport.CurrentFiletypes() ] ) - +def InCFamilyFile( filetypes ): + return ClangAvailableForFiletypes( filetypes ) diff --git a/python/ycm/completers/cpp/flags.py b/python/ycm/completers/cpp/flags.py index 61a241f9..17c4981a 100644 --- a/python/ycm/completers/cpp/flags.py +++ b/python/ycm/completers/cpp/flags.py @@ -19,12 +19,11 @@ import ycm_core import os -from ycm import vimsupport from ycm import extra_conf_store -NO_EXTRA_CONF_FILENAME_MESSAGE = ('No {0} file detected, so no compile flags ' +NO_EXTRA_CONF_FILENAME_MESSAGE = ( 'No {0} file detected, so no compile flags ' 'are available. Thus no semantic support for C/C++/ObjC/ObjC++. Go READ THE ' - 'DOCS *NOW*, DON\'T file a bug report.').format( + 'DOCS *NOW*, DON\'T file a bug report.' ).format( extra_conf_store.YCM_EXTRA_CONF_FILENAME ) @@ -37,7 +36,6 @@ class Flags( object ): # It's caches all the way down... self.flags_for_file = {} self.special_clang_flags = _SpecialClangIncludes() - self.no_extra_conf_file_warning_posted = False def FlagsForFile( self, filename, add_special_clang_flags = True ): @@ -46,10 +44,7 @@ class Flags( object ): except KeyError: module = extra_conf_store.ModuleForSourceFile( filename ) if not module: - if not self.no_extra_conf_file_warning_posted: - vimsupport.PostVimMessage( NO_EXTRA_CONF_FILENAME_MESSAGE ) - self.no_extra_conf_file_warning_posted = True - return None + raise RuntimeError( NO_EXTRA_CONF_FILENAME_MESSAGE ) results = module.FlagsForFile( filename ) diff --git a/python/ycm/completers/cs/cs_completer.py b/python/ycm/completers/cs/cs_completer.py index a83b3a4e..f63b3b67 100755 --- a/python/ycm/completers/cs/cs_completer.py +++ b/python/ycm/completers/cs/cs_completer.py @@ -18,18 +18,18 @@ # You should have received a copy of the GNU General Public License # along with YouCompleteMe. If not, see . -import vim import os from sys import platform import glob from ycm.completers.threaded_completer import ThreadedCompleter -from ycm import vimsupport +from ycm import server_responses import urllib2 import urllib import urlparse import json import subprocess import tempfile +import logging SERVER_NOT_FOUND_MSG = ( 'OmniSharp server binary not found at {0}. ' + @@ -44,9 +44,10 @@ class CsharpCompleter( ThreadedCompleter ): def __init__( self, user_options ): super( CsharpCompleter, self ).__init__( user_options ) self._omnisharp_port = None + self._logger = logging.getLogger(__name__) - if self.user_options[ 'auto_start_csharp_server' ]: - self._StartServer() + # if self.user_options[ 'auto_start_csharp_server' ]: + # self._StartServer() def OnVimLeave( self ): @@ -60,11 +61,12 @@ class CsharpCompleter( ThreadedCompleter ): return [ 'cs' ] - def ComputeCandidates( self, unused_query, unused_start_column ): - return [ { 'word': str( completion[ 'CompletionText' ] ), - 'menu': str( completion[ 'DisplayText' ] ), - 'info': str( completion[ 'Description' ] ) } - for completion in self._GetCompletions() ] + def ComputeCandidates( self, request_data ): + return [ server_responses.BuildCompletionData( + completion[ 'CompletionText' ], + completion[ 'DisplayText' ], + completion[ 'Description' ] ) + for completion in self._GetCompletions( request_data ) ] def DefinedSubcommands( self ): @@ -76,24 +78,23 @@ class CsharpCompleter( ThreadedCompleter ): 'GoToDefinitionElseDeclaration' ] - def OnUserCommand( self, arguments ): + def OnUserCommand( self, arguments, request_data ): if not arguments: - self.EchoUserCommandsHelpMessage() - return + raise ValueError( self.UserCommandsHelpMessage() ) command = arguments[ 0 ] if command == 'StartServer': - self._StartServer() + self._StartServer( request_data ) elif command == 'StopServer': self._StopServer() elif command == 'RestartServer': if self._ServerIsRunning(): self._StopServer() - self._StartServer() + self._StartServer( request_data ) elif command in [ 'GoToDefinition', 'GoToDeclaration', 'GoToDefinitionElseDeclaration' ]: - self._GoToDefinition() + return self._GoToDefinition( request_data ) def DebugInfo( self ): @@ -104,35 +105,27 @@ class CsharpCompleter( ThreadedCompleter ): return 'Server is not running' - def _StartServer( self ): + def _StartServer( self, request_data ): """ Start the OmniSharp server """ self._omnisharp_port = self._FindFreePort() - solutionfiles, folder = _FindSolutionFiles() + solutionfiles, folder = _FindSolutionFiles( request_data[ 'filepath' ] ) if len( solutionfiles ) == 0: - vimsupport.PostVimMessage( - 'Error starting OmniSharp server: no solutionfile found' ) - return + raise RuntimeError( + 'Error starting OmniSharp server: no solutionfile found' ) elif len( solutionfiles ) == 1: solutionfile = solutionfiles[ 0 ] else: - choice = vimsupport.PresentDialog( - 'Which solutionfile should be loaded?', - [ str( i ) + " " + solution for i, solution in - enumerate( solutionfiles ) ] ) - if choice == -1: - vimsupport.PostVimMessage( 'OmniSharp not started' ) - return - else: - solutionfile = solutionfiles[ choice ] + raise RuntimeError( + 'Found multiple solution files instead of one!\n{}'.format( + solutionfiles ) ) omnisharp = os.path.join( os.path.abspath( os.path.dirname( __file__ ) ), 'OmniSharpServer/OmniSharp/bin/Debug/OmniSharp.exe' ) if not os.path.isfile( omnisharp ): - vimsupport.PostVimMessage( SERVER_NOT_FOUND_MSG.format( omnisharp ) ) - return + raise RuntimeError( SERVER_NOT_FOUND_MSG.format( omnisharp ) ) if not platform.startswith( 'win' ): omnisharp = 'mono ' + omnisharp @@ -154,40 +147,44 @@ class CsharpCompleter( ThreadedCompleter ): with open( self._filename_stdout, 'w' ) as fstdout: subprocess.Popen( command, stdout=fstdout, stderr=fstderr, shell=True ) - vimsupport.PostVimMessage( 'Starting OmniSharp server' ) + self._logger.info( 'Starting OmniSharp server' ) def _StopServer( self ): """ Stop the OmniSharp server """ self._GetResponse( '/stopserver' ) self._omnisharp_port = None - vimsupport.PostVimMessage( 'Stopping OmniSharp server' ) + self._logger.info( 'Stopping OmniSharp server' ) - def _GetCompletions( self ): + def _GetCompletions( self, request_data ): """ Ask server for completions """ - completions = self._GetResponse( '/autocomplete', self._DefaultParameters() ) + completions = self._GetResponse( '/autocomplete', + self._DefaultParameters( request_data ) ) return completions if completions != None else [] - def _GoToDefinition( self ): + def _GoToDefinition( self, request_data ): """ Jump to definition of identifier under cursor """ - definition = self._GetResponse( '/gotodefinition', self._DefaultParameters() ) + definition = self._GetResponse( '/gotodefinition', + self._DefaultParameters( request_data ) ) if definition[ 'FileName' ] != None: - vimsupport.JumpToLocation( definition[ 'FileName' ], - definition[ 'Line' ], - definition[ 'Column' ] ) + return server_responses.BuildGoToResponse( definition[ 'FileName' ], + definition[ 'Line' ], + definition[ 'Column' ] ) else: - vimsupport.PostVimMessage( 'Can\'t jump to definition' ) + raise RuntimeError( 'Can\'t jump to definition' ) - def _DefaultParameters( self ): + def _DefaultParameters( self, request_data ): """ Some very common request parameters """ - line, column = vimsupport.CurrentLineAndColumn() parameters = {} - parameters[ 'line' ], parameters[ 'column' ] = line + 1, column + 1 - parameters[ 'buffer' ] = '\n'.join( vim.current.buffer ) - parameters[ 'filename' ] = vim.current.buffer.name + parameters[ 'line' ] = request_data[ 'line_num' ] + 1 + parameters[ 'column' ] = request_data[ 'column_num' ] + 1 + filepath = request_data[ 'filepath' ] + parameters[ 'buffer' ] = request_data[ 'file_data' ][ filepath ][ + 'contents' ] + parameters[ 'filename' ] = filepath return parameters @@ -215,20 +212,16 @@ class CsharpCompleter( ThreadedCompleter ): def _GetResponse( self, endPoint, parameters={}, silent=False, port=None ): """ Handle communication with server """ + # TODO: Replace usage of urllib with Requests target = urlparse.urljoin( self._PortToHost( port ), endPoint ) parameters = urllib.urlencode( parameters ) - try: - response = urllib2.urlopen( target, parameters ) - return json.loads( response.read() ) - except Exception: - # TODO: Add logging for this case. We can't post a Vim message because Vim - # crashes when that's done from a no-GUI thread. - return None + response = urllib2.urlopen( target, parameters ) + return json.loads( response.read() ) -def _FindSolutionFiles(): +def _FindSolutionFiles( filepath ): """ Find solution files by searching upwards in the file tree """ - folder = os.path.dirname( vim.current.buffer.name ) + folder = os.path.dirname( filepath ) solutionfiles = glob.glob1( folder, '*.sln' ) while not solutionfiles: lastfolder = folder diff --git a/python/ycm/completers/general/filename_completer.py b/python/ycm/completers/general/filename_completer.py index abd22b41..985bda04 100644 --- a/python/ycm/completers/general/filename_completer.py +++ b/python/ycm/completers/general/filename_completer.py @@ -16,13 +16,13 @@ # You should have received a copy of the GNU General Public License # along with YouCompleteMe. If not, see . -import vim import os import re from ycm.completers.threaded_completer import ThreadedCompleter from ycm.completers.cpp.clang_completer import InCFamilyFile from ycm.completers.cpp.flags import Flags +from ycm import server_responses class FilenameCompleter( ThreadedCompleter ): """ @@ -52,23 +52,32 @@ class FilenameCompleter( ThreadedCompleter ): self._include_regex = re.compile( include_regex_common ) - def AtIncludeStatementStart( self, start_column ): - return ( InCFamilyFile() and + def AtIncludeStatementStart( self, request_data ): + start_column = request_data[ 'start_column' ] + current_line = request_data[ 'line_value' ] + filepath = request_data[ 'filepath' ] + filetypes = request_data[ 'file_data' ][ filepath ][ 'filetypes' ] + return ( InCFamilyFile( filetypes ) and self._include_start_regex.match( - vim.current.line[ :start_column ] ) ) + current_line[ :start_column ] ) ) - def ShouldUseNowInner( self, start_column, current_line ): + def ShouldUseNowInner( self, request_data ): + start_column = request_data[ 'start_column' ] + current_line = request_data[ 'line_value' ] return ( start_column and ( current_line[ start_column - 1 ] == '/' or - self.AtIncludeStatementStart( start_column ) ) ) + self.AtIncludeStatementStart( request_data ) ) ) def SupportedFiletypes( self ): return [] - def ComputeCandidates( self, unused_query, start_column ): - line = vim.current.line[ :start_column ] + def ComputeCandidates( self, request_data ): + current_line = request_data[ 'line_value' ] + start_column = request_data[ 'start_column' ] + filepath = request_data[ 'filepath' ] + line = current_line[ :start_column ] if InCFamilyFile(): include_match = self._include_regex.search( line ) @@ -78,22 +87,26 @@ class FilenameCompleter( ThreadedCompleter ): # http://gcc.gnu.org/onlinedocs/cpp/Include-Syntax.html include_current_file_dir = '<' not in include_match.group() return _GenerateCandidatesForPaths( - self.GetPathsIncludeCase( path_dir, include_current_file_dir ) ) + self.GetPathsIncludeCase( path_dir, + include_current_file_dir, + filepath ) ) path_match = self._path_regex.search( line ) path_dir = os.path.expanduser( path_match.group() ) if path_match else '' return _GenerateCandidatesForPaths( - _GetPathsStandardCase( path_dir, self.user_options[ - 'filepath_completion_use_working_dir' ] ) ) + _GetPathsStandardCase( + path_dir, + self.user_options[ 'filepath_completion_use_working_dir' ], + filepath ) ) - def GetPathsIncludeCase( self, path_dir, include_current_file_dir ): + def GetPathsIncludeCase( self, path_dir, include_current_file_dir, filepath ): paths = [] - include_paths = self._flags.UserIncludePaths( vim.current.buffer.name ) + include_paths = self._flags.UserIncludePaths( filepath ) if include_current_file_dir: - include_paths.append( os.path.dirname( vim.current.buffer.name ) ) + include_paths.append( os.path.dirname( filepath ) ) for include_path in include_paths: try: @@ -107,9 +120,9 @@ class FilenameCompleter( ThreadedCompleter ): return sorted( set( paths ) ) -def _GetPathsStandardCase( path_dir, use_working_dir ): +def _GetPathsStandardCase( path_dir, use_working_dir, filepath ): if not use_working_dir and not path_dir.startswith( '/' ): - path_dir = os.path.join( os.path.dirname( vim.current.buffer.name ), + path_dir = os.path.join( os.path.dirname( filepath ), path_dir ) try: @@ -132,8 +145,8 @@ def _GenerateCandidatesForPaths( absolute_paths ): seen_basenames.add( basename ) is_dir = os.path.isdir( absolute_path ) - completion_dicts.append( { 'word': basename, - 'dup': 1, - 'menu': '[Dir]' if is_dir else '[File]' } ) + completion_dicts.append( + server_responses.BuildCompletionData( basename, + '[Dir]' if is_dir else '[File]' ) ) return completion_dicts diff --git a/python/ycm/completers/general/general_completer_store.py b/python/ycm/completers/general/general_completer_store.py index 8d0cd55b..fcfe5f76 100644 --- a/python/ycm/completers/general/general_completer_store.py +++ b/python/ycm/completers/general/general_completer_store.py @@ -58,18 +58,17 @@ class GeneralCompleterStore( Completer ): return set() - def ShouldUseNow( self, start_column, current_line ): + def ShouldUseNow( self, request_data ): self._current_query_completers = [] - if self._filename_completer.ShouldUseNow( start_column, current_line ): + if self._filename_completer.ShouldUseNow( request_data ): self._current_query_completers = [ self._filename_completer ] return True should_use_now = False for completer in self._non_filename_completers: - should_use_this_completer = completer.ShouldUseNow( start_column, - current_line ) + should_use_this_completer = completer.ShouldUseNow( request_data ) should_use_now = should_use_now or should_use_this_completer if should_use_this_completer: @@ -78,9 +77,9 @@ class GeneralCompleterStore( Completer ): return should_use_now - def CandidatesForQueryAsync( self, query, start_column ): + def CandidatesForQueryAsync( self, request_data ): for completer in self._current_query_completers: - completer.CandidatesForQueryAsync( query, start_column ) + completer.CandidatesForQueryAsync( request_data ) def AsyncCandidateRequestReady( self ): @@ -96,9 +95,9 @@ class GeneralCompleterStore( Completer ): return candidates - def OnFileReadyToParse( self ): + def OnFileReadyToParse( self, request_data ): for completer in self._all_completers: - completer.OnFileReadyToParse() + completer.OnFileReadyToParse( request_data ) def OnBufferVisit( self ): diff --git a/python/ycm/completers/general/ultisnips_completer.py b/python/ycm/completers/general/ultisnips_completer.py index 50895910..d16b174f 100644 --- a/python/ycm/completers/general/ultisnips_completer.py +++ b/python/ycm/completers/general/ultisnips_completer.py @@ -20,6 +20,7 @@ from ycm.completers.general_completer import GeneralCompleter from UltiSnips import UltiSnips_Manager +from ycm import server_responses class UltiSnipsCompleter( GeneralCompleter ): @@ -33,13 +34,13 @@ class UltiSnipsCompleter( GeneralCompleter ): self._filtered_candidates = None - def ShouldUseNowInner( self, start_column, unused_current_line ): - return self.QueryLengthAboveMinThreshold( start_column ) + def ShouldUseNowInner( self, request_data ): + return self.QueryLengthAboveMinThreshold( request_data ) - def CandidatesForQueryAsync( self, query, unused_start_column ): - self._filtered_candidates = self.FilterAndSortCandidates( self._candidates, - query ) + def CandidatesForQueryAsync( self, request_data ): + self._filtered_candidates = self.FilterAndSortCandidates( + self._candidates, request_data[ 'query' ] ) def AsyncCandidateRequestReady( self ): @@ -61,8 +62,9 @@ def _GetCandidates(): # UltiSnips_Manager._snips() returns a class instance where: # class.trigger - name of snippet trigger word ( e.g. defn or testcase ) # class.description - description of the snippet - return [ { 'word': str( snip.trigger ), - 'menu': str( ' ' + snip.description.encode('utf-8') ) } - for snip in rawsnips ] + return [ server_responses.BuildCompletionData( + str( snip.trigger ), + str( ' ' + snip.description.encode( 'utf-8' ) ) ) + for snip in rawsnips ] except: return [] diff --git a/python/ycm/completers/python/jedi_completer.py b/python/ycm/completers/python/jedi_completer.py index cde83b63..7277d7bc 100644 --- a/python/ycm/completers/python/jedi_completer.py +++ b/python/ycm/completers/python/jedi_completer.py @@ -19,9 +19,8 @@ # You should have received a copy of the GNU General Public License # along with YouCompleteMe. If not, see . -import vim from ycm.completers.threaded_completer import ThreadedCompleter -from ycm import vimsupport +from ycm import server_responses import sys from os.path import join, abspath, dirname @@ -33,7 +32,7 @@ sys.path.insert( 0, join( abspath( dirname( __file__ ) ), 'jedi' ) ) try: import jedi except ImportError: - vimsupport.PostVimMessage( + raise ImportError( 'Error importing jedi. Make sure the jedi submodule has been checked out. ' 'In the YouCompleteMe folder, run "git submodule update --init --recursive"') sys.path.pop( 0 ) @@ -54,113 +53,108 @@ class JediCompleter( ThreadedCompleter ): return [ 'python' ] - def _GetJediScript( self ): - contents = '\n'.join( vim.current.buffer ) - line, column = vimsupport.CurrentLineAndColumn() + def _GetJediScript( self, request_data ): + filename = request_data[ 'filepath' ] + contents = request_data[ 'file_data' ][ filename ][ 'contents' ] # Jedi expects lines to start at 1, not 0 - line += 1 - filename = vim.current.buffer.name + line = request_data[ 'line_num' ] + 1 + column = request_data[ 'column_num' ] + print contents return jedi.Script( contents, line, column, filename ) - def ComputeCandidates( self, unused_query, unused_start_column ): - script = self._GetJediScript() - - return [ { 'word': str( completion.name ), - 'menu': str( completion.description ), - 'info': str( completion.doc ) } + def ComputeCandidates( self, request_data ): + script = self._GetJediScript( request_data ) + return [ server_responses.BuildCompletionData( completion.name, + completion.description, + completion.doc ) for completion in script.completions() ] - def DefinedSubcommands( self ): return [ "GoToDefinition", "GoToDeclaration", "GoToDefinitionElseDeclaration" ] - def OnUserCommand( self, arguments ): + def OnUserCommand( self, arguments, request_data ): if not arguments: - self.EchoUserCommandsHelpMessage() - return + raise ValueError( self.UserCommandsHelpMessage() ) command = arguments[ 0 ] if command == 'GoToDefinition': - self._GoToDefinition() + return self._GoToDefinition( request_data ) elif command == 'GoToDeclaration': - self._GoToDeclaration() + return self._GoToDeclaration( request_data ) elif command == 'GoToDefinitionElseDeclaration': - self._GoToDefinitionElseDeclaration() + return self._GoToDefinitionElseDeclaration( request_data ) - def _GoToDefinition( self ): - definitions = self._GetDefinitionsList() + def _GoToDefinition( self, request_data ): + definitions = self._GetDefinitionsList( request_data ) if definitions: - self._JumpToLocation( definitions ) + return self._BuildGoToResponse( definitions ) else: - vimsupport.PostVimMessage( 'Can\'t jump to definition.' ) + raise RuntimeError( 'Can\'t jump to definition.' ) - def _GoToDeclaration( self ): - definitions = self._GetDefinitionsList( declaration = True ) + def _GoToDeclaration( self, request_data ): + definitions = self._GetDefinitionsList( request_data, declaration = True ) if definitions: - self._JumpToLocation( definitions ) + return self._BuildGoToResponse( definitions ) else: - vimsupport.PostVimMessage( 'Can\'t jump to declaration.' ) + raise RuntimeError( 'Can\'t jump to declaration.' ) - def _GoToDefinitionElseDeclaration( self ): + def _GoToDefinitionElseDeclaration( self, request_data ): definitions = self._GetDefinitionsList() or \ - self._GetDefinitionsList( declaration = True ) + self._GetDefinitionsList( request_data, declaration = True ) if definitions: - self._JumpToLocation( definitions ) + return self._BuildGoToResponse( definitions ) else: - vimsupport.PostVimMessage( 'Can\'t jump to definition or declaration.' ) + raise RuntimeError( 'Can\'t jump to definition or declaration.' ) - def _GetDefinitionsList( self, declaration = False ): + def _GetDefinitionsList( self, request_data, declaration = False ): definitions = [] - script = self._GetJediScript() + script = self._GetJediScript( request_data ) try: if declaration: definitions = script.goto_definitions() else: definitions = script.goto_assignments() except jedi.NotFoundError: - vimsupport.PostVimMessage( - "Cannot follow nothing. Put your cursor on a valid name." ) - except Exception as e: - vimsupport.PostVimMessage( - "Caught exception, aborting. Full error: " + str( e ) ) + raise RuntimeError( + 'Cannot follow nothing. Put your cursor on a valid name.' ) return definitions - def _JumpToLocation( self, definition_list ): + def _BuildGoToResponse( self, definition_list ): if len( definition_list ) == 1: definition = definition_list[ 0 ] if definition.in_builtin_module(): if definition.is_keyword: - vimsupport.PostVimMessage( - "Cannot get the definition of Python keywords." ) + raise RuntimeError( + 'Cannot get the definition of Python keywords.' ) else: - vimsupport.PostVimMessage( "Builtin modules cannot be displayed." ) + raise RuntimeError( 'Builtin modules cannot be displayed.' ) else: - vimsupport.JumpToLocation( definition.module_path, - definition.line, - definition.column + 1 ) + return server_responses.BuildGoToResponse( definition.module_path, + definition.line -1, + definition.column ) else: # multiple definitions defs = [] for definition in definition_list: if definition.in_builtin_module(): - defs.append( {'text': 'Builtin ' + \ - definition.description.encode( 'utf-8' ) } ) + defs.append( server_responses.BuildDescriptionOnlyGoToResponse( + 'Builting ' + definition.description ) ) else: - defs.append( {'filename': definition.module_path.encode( 'utf-8' ), - 'lnum': definition.line, - 'col': definition.column + 1, - 'text': definition.description.encode( 'utf-8' ) } ) + defs.append( + server_responses.BuildGoToResponse( definition.module_path, + definition.line -1, + definition.column, + definition.description ) ) + return defs - vim.eval( 'setqflist( %s )' % repr( defs ) ) - vim.eval( 'youcompleteme#OpenGoToList()' ) diff --git a/python/ycm/completers/threaded_completer.py b/python/ycm/completers/threaded_completer.py index 6d048676..cf1a1b13 100644 --- a/python/ycm/completers/threaded_completer.py +++ b/python/ycm/completers/threaded_completer.py @@ -52,11 +52,10 @@ class ThreadedCompleter( Completer ): self._completion_thread.start() - def CandidatesForQueryAsyncInner( self, query, start_column ): + def CandidatesForQueryAsyncInner( self, request_data ): self._candidates = None self._candidates_ready.clear() - self._query = query - self._start_column = start_column + self._request_data = request_data self._query_ready.set() @@ -69,7 +68,7 @@ class ThreadedCompleter( Completer ): @abc.abstractmethod - def ComputeCandidates( self, query, start_column ): + def ComputeCandidates( self, request_data ): """This function should compute the candidates to show to the user. The return value should be of the same type as that for CandidatesFromStoredRequest().""" @@ -80,8 +79,7 @@ class ThreadedCompleter( Completer ): while True: try: WaitAndClearIfSet( self._query_ready ) - self._candidates = self.ComputeCandidates( self._query, - self._start_column ) + self._candidates = self.ComputeCandidates( self._request_data ) except: self._query_ready.clear() self._candidates = [] diff --git a/python/ycm/extra_conf_store.py b/python/ycm/extra_conf_store.py index 3248dc1d..5b4814d3 100644 --- a/python/ycm/extra_conf_store.py +++ b/python/ycm/extra_conf_store.py @@ -68,6 +68,7 @@ def CallExtraConfVimCloseIfExists(): def _CallExtraConfMethod( function_name ): vim_current_working_directory = vim.eval( 'getcwd()' ) path_to_dummy = os.path.join( vim_current_working_directory, 'DUMMY_FILE' ) + # The dummy file in the Vim CWD ensures we find the correct extra conf file module = ModuleForSourceFile( path_to_dummy ) if not module or not hasattr( module, function_name ): return diff --git a/python/ycm/server_responses.py b/python/ycm/server_responses.py new file mode 100644 index 00000000..f8414d9c --- /dev/null +++ b/python/ycm/server_responses.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# +# Copyright (C) 2013 Strahinja Val Markovic +# +# This file is part of YouCompleteMe. +# +# YouCompleteMe is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# YouCompleteMe is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with YouCompleteMe. If not, see . + + +def BuildGoToResponse( filepath, line_num, column_num, description = None ): + response = { + 'filepath': filepath, + 'line_num': line_num, + 'column_num': column_num + } + + if description: + response[ 'description' ] = description + return response + + +def BuildDescriptionOnlyGoToResponse( text ): + return { + 'description': text, + } + + +def BuildDisplayMessageResponse( text ): + return { + 'message': text + } + + +def BuildCompletionData( insertion_text, + extra_menu_info = None, + detailed_info = None, + menu_text = None, + kind = None ): + completion_data = { + 'insertion_text': insertion_text + } + + if extra_menu_info: + completion_data[ 'extra_menu_info' ] = extra_menu_info + if menu_text: + completion_data[ 'menu_text' ] = menu_text + if detailed_info: + completion_data[ 'detailed_info' ] = detailed_info + if kind: + completion_data[ 'kind' ] = kind + return completion_data + + +def BuildDiagnosticData( filepath, + line_num, + column_num, + text, + kind ): + return { + 'filepath': filepath, + 'line_num': line_num, + 'column_num': column_num, + 'text': text, + 'kind': kind + } diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index 209319b7..4ce05fb1 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -46,12 +46,33 @@ def TextAfterCursor(): return vim.current.line[ CurrentColumn(): ] -def GetUnsavedBuffers(): - def BufferModified( buffer_number ): - to_eval = 'getbufvar({0}, "&mod")'.format( buffer_number ) - return GetBoolValue( to_eval ) +# Note the difference between buffer OPTIONS and VARIABLES; the two are not +# the same. +def GetBufferOption( buffer_object, option ): + # The 'options' property is only available in recent (7.4+) Vim builds + if hasattr( buffer_object, 'options' ): + return buffer_object.options[ option ] - return ( x for x in vim.buffers if BufferModified( x.number ) ) + to_eval = 'getbufvar({0}, "&{1}")'.format( buffer.number, option ) + return GetVariableValue( to_eval ) + + +def GetUnsavedAndCurrentBufferData(): + def BufferModified( buffer_object ): + return bool( int( GetBufferOption( buffer_object, 'mod' ) ) ) + + buffers_data = {} + for buffer_object in vim.buffers: + if not ( BufferModified( buffer_object ) or + buffer_object == vim.current.buffer ): + continue + + buffers_data[ buffer_object.name ] = { + 'contents': '\n'.join( buffer_object ), + 'filetypes': FiletypesForBuffer( buffer_object ) + } + + return buffers_data # Both |line| and |column| need to be 1-based @@ -73,9 +94,9 @@ def JumpToLocation( filename, line, column ): vim.command( 'normal! zz' ) -def NumLinesInBuffer( buffer ): +def NumLinesInBuffer( buffer_object ): # This is actually less than obvious, that's why it's wrapped in a function - return len( buffer ) + return len( buffer_object ) def PostVimMessage( message ): @@ -128,15 +149,13 @@ def EscapeForVim( text ): def CurrentFiletypes(): - ft_string = vim.eval( "&filetype" ) - return ft_string.split( '.' ) + return vim.eval( "&filetype" ).split( '.' ) def FiletypesForBuffer( buffer_object ): # NOTE: Getting &ft for other buffers only works when the buffer has been # visited by the user at least once, which is true for modified buffers - ft_string = vim.eval( 'getbufvar({0}, "&ft")'.format( buffer_object.number ) ) - return ft_string.split( '.' ) + return GetBufferOption( buffer_object, 'ft' ).split( '.' ) def GetVariableValue( variable ): diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index 80132729..1e8d8efb 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -19,20 +19,25 @@ import imp import os +import time import vim import ycm_core +import logging +import tempfile from ycm import vimsupport from ycm import base from ycm.completers.all.omni_completer import OmniCompleter from ycm.completers.general.general_completer_store import GeneralCompleterStore +# TODO: Put the Request classes in separate files class CompletionRequest( object ): def __init__( self, ycm_state ): self._completion_start_column = base.CompletionStartColumn() self._ycm_state = ycm_state + self._request_data = _BuildRequestData( self._completion_start_column ) self._do_filetype_completion = self._ycm_state.ShouldUseFiletypeCompleter( - self._completion_start_column ) + self._request_data ) self._completer = ( self._ycm_state.GetFiletypeCompleter() if self._do_filetype_completion else self._ycm_state.GetGeneralCompleter() ) @@ -40,8 +45,7 @@ class CompletionRequest( object ): def ShouldComplete( self ): return ( self._do_filetype_completion or - self._ycm_state.ShouldUseGeneralCompleter( - self._completion_start_column ) ) + self._ycm_state.ShouldUseGeneralCompleter( self._request_data ) ) def CompletionStartColumn( self ): @@ -49,20 +53,80 @@ class CompletionRequest( object ): def Start( self, query ): - self._completer.CandidatesForQueryAsync( query, - self._completion_start_column ) + self._request_data[ 'query' ] = query + self._completer.CandidatesForQueryAsync( self._request_data ) def Done( self ): return self._completer.AsyncCandidateRequestReady() def Results( self ): - return self._completer.CandidatesFromStoredRequest() + try: + return [ _ConvertCompletionDataToVimData( x ) + for x in self._completer.CandidatesFromStoredRequest() ] + except Exception as e: + vimsupport.PostVimMessage( str( e ) ) + return [] + + + +class CommandRequest( object ): + class ServerResponse( object ): + def __init__( self ): + pass + + def Valid( self ): + return True + + def __init__( self, ycm_state, arguments, completer_target = None ): + if not completer_target: + completer_target = 'filetpe_default' + + if completer_target == 'omni': + self._completer = ycm_state.GetOmniCompleter() + elif completer_target == 'identifier': + self._completer = ycm_state.GetGeneralCompleter() + else: + self._completer = ycm_state.GetFiletypeCompleter() + self._arguments = arguments + + + def Start( self ): + self._completer.OnUserCommand( self._arguments, + _BuildRequestData() ) + + def Done( self ): + return True + + + def Response( self ): + # TODO: Call vimsupport.JumpToLocation if the user called a GoTo command... + # we may want to have specific subclasses of CommandRequest so that a + # GoToRequest knows it needs to jump after the data comes back. + # + # Also need to run the following on GoTo data: + # CAREFUL about line/column number 0-based/1-based confusion! + # + # defs = [] + # defs.append( {'filename': definition.module_path.encode( 'utf-8' ), + # 'lnum': definition.line, + # 'col': definition.column + 1, + # 'text': definition.description.encode( 'utf-8' ) } ) + # vim.eval( 'setqflist( %s )' % repr( defs ) ) + # vim.eval( 'youcompleteme#OpenGoToList()' ) + return self.ServerResponse() class YouCompleteMe( object ): def __init__( self, user_options ): + # TODO: This should go into the server + # TODO: Use more logging like we do in cs_completer + self._logfile = tempfile.NamedTemporaryFile() + logging.basicConfig( format='%(asctime)s - %(levelname)s - %(message)s', + filename=self._logfile.name, + level=logging.DEBUG ) + self._user_options = user_options self._gencomp = GeneralCompleterStore( user_options ) self._omnicomp = OmniCompleter( user_options ) @@ -78,6 +142,16 @@ class YouCompleteMe( object ): return self._current_completion_request + def SendCommandRequest( self, arguments, completer ): + # TODO: This should be inside a method in a command_request module + request = CommandRequest( self, arguments, completer ) + request.Start() + while not request.Done(): + time.sleep( 0.1 ) + + return request.Response() + + def GetCurrentCompletionRequest( self ): return self._current_completion_request @@ -131,14 +205,13 @@ class YouCompleteMe( object ): return completer - def ShouldUseGeneralCompleter( self, start_column ): - return self._gencomp.ShouldUseNow( start_column, vim.current.line ) + def ShouldUseGeneralCompleter( self, request_data ): + return self._gencomp.ShouldUseNow( request_data ) - def ShouldUseFiletypeCompleter( self, start_column ): + def ShouldUseFiletypeCompleter( self, request_data ): if self.FiletypeCompletionUsable(): - return self.GetFiletypeCompleter().ShouldUseNow( - start_column, vim.current.line ) + return self.GetFiletypeCompleter().ShouldUseNow( request_data ) return False @@ -162,10 +235,10 @@ class YouCompleteMe( object ): def OnFileReadyToParse( self ): - self._gencomp.OnFileReadyToParse() + self._gencomp.OnFileReadyToParse( _BuildRequestData() ) if self.FiletypeCompletionUsable(): - self.GetFiletypeCompleter().OnFileReadyToParse() + self.GetFiletypeCompleter().OnFileReadyToParse( _BuildRequestData() ) def OnBufferUnload( self, deleted_buffer_file ): @@ -208,9 +281,9 @@ class YouCompleteMe( object ): return [] - def ShowDetailedDiagnostic( self ): + def GetDetailedDiagnostic( self ): if self.FiletypeCompletionUsable(): - return self.GetFiletypeCompleter().ShowDetailedDiagnostic() + return self.GetFiletypeCompleter().GetDetailedDiagnostic() def GettingCompletions( self ): @@ -263,3 +336,37 @@ def _PathToFiletypeCompleterPluginLoader( filetype ): return os.path.join( _PathToCompletersFolder(), filetype, 'hook.py' ) +def _BuildRequestData( start_column = None, query = None ): + line, column = vimsupport.CurrentLineAndColumn() + request_data = { + 'filetypes': vimsupport.CurrentFiletypes(), + 'line_num': line, + 'column_num': column, + 'start_column': start_column, + 'line_value': vim.current.line, + 'filepath': vim.current.buffer.name, + 'file_data': vimsupport.GetUnsavedAndCurrentBufferData() + } + + if query: + request_data[ 'query' ] = query + + return request_data + +def _ConvertCompletionDataToVimData( completion_data ): + # see :h complete-items for a description of the dictionary fields + vim_data = { + 'word' : completion_data[ 'insertion_text' ], + 'dup' : 1, + } + + if 'menu_text' in completion_data: + vim_data[ 'abbr' ] = completion_data[ 'menu_text' ] + if 'extra_menu_info' in completion_data: + vim_data[ 'menu' ] = completion_data[ 'extra_menu_info' ] + if 'kind' in completion_data: + vim_data[ 'kind' ] = completion_data[ 'kind' ] + if 'detailed_info' in completion_data: + vim_data[ 'info' ] = completion_data[ 'detailed_info' ] + + return vim_data