Decoupling completers from Vim; still WIP & broken

Note to self: squash this commit before merging into master.
This commit is contained in:
Strahinja Val Markovic 2013-09-05 23:43:14 -07:00
parent bd374a7096
commit 29bb90a6b4
20 changed files with 665 additions and 454 deletions

View File

@ -41,6 +41,7 @@ function! youcompleteme#Enable()
py import vim py import vim
exe 'python sys.path.insert( 0, "' . s:script_folder_path . '/../python" )' exe 'python sys.path.insert( 0, "' . s:script_folder_path . '/../python" )'
py from ycm import base py from ycm import base
py from ycm import vimsupport
py from ycm import user_options_store py from ycm import user_options_store
py user_options_store.SetAll( base.BuildServerConf() ) py user_options_store.SetAll( base.BuildServerConf() )
py from ycm import extra_conf_store py from ycm import extra_conf_store
@ -260,7 +261,7 @@ function! s:OnCursorHold()
call s:SetUpCompleteopt() call s:SetUpCompleteopt()
" Order is important here; we need to extract any done diagnostics before " Order is important here; we need to extract any done diagnostics before
" reparsing the file again " reparsing the file again
call s:UpdateDiagnosticNotifications() " call s:UpdateDiagnosticNotifications()
call s:OnFileReadyToParse() call s:OnFileReadyToParse()
endfunction endfunction
@ -327,7 +328,7 @@ function! s:OnCursorMovedNormalMode()
return return
endif endif
call s:UpdateDiagnosticNotifications() " call s:UpdateDiagnosticNotifications()
call s:OnFileReadyToParse() call s:OnFileReadyToParse()
endfunction endfunction
@ -338,7 +339,7 @@ function! s:OnInsertLeave()
endif endif
let s:omnifunc_mode = 0 let s:omnifunc_mode = 0
call s:UpdateDiagnosticNotifications() " call s:UpdateDiagnosticNotifications()
call s:OnFileReadyToParse() call s:OnFileReadyToParse()
py ycm_state.OnInsertLeave() py ycm_state.OnInsertLeave()
if g:ycm_autoclose_preview_window_after_completion || 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 " required (currently that's on buffer save) OR when the SyntasticCheck command
" is invoked " is invoked
function! youcompleteme#CurrentFileDiagnostics() function! youcompleteme#CurrentFileDiagnostics()
return pyeval( 'ycm_state.GetDiagnosticsForCurrentFile()' ) " TODO: Make this work again.
" return pyeval( 'ycm_state.GetDiagnosticsForCurrentFile()' )
return []
endfunction endfunction
@ -595,28 +598,24 @@ function! s:CompleterCommand(...)
" to select the omni completer or "ft=ycm:ident" to select the identifier " to select the omni completer or "ft=ycm:ident" to select the identifier
" completer. The remaining arguments will passed to the completer. " completer. The remaining arguments will passed to the completer.
let arguments = copy(a:000) let arguments = copy(a:000)
let completer = ''
if a:0 > 0 && strpart(a:1, 0, 3) == 'ft=' if a:0 > 0 && strpart(a:1, 0, 3) == 'ft='
if a:1 == 'ft=ycm:omni' if a:1 == 'ft=ycm:omni'
py completer = ycm_state.GetOmniCompleter() let completer = 'omni'
elseif a:1 == 'ft=ycm:ident' elseif a:1 == 'ft=ycm:ident'
py completer = ycm_state.GetGeneralCompleter() let completer = 'identifier'
else
py completer = ycm_state.GetFiletypeCompleterForFiletype(
\ vim.eval('a:1').lstrip('ft=') )
endif endif
let arguments = arguments[1:] 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 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 endfunction

View File

@ -103,7 +103,7 @@ void ClangCompleter::EnableThreading() {
std::vector< Diagnostic > ClangCompleter::DiagnosticsForFile( std::vector< Diagnostic > ClangCompleter::DiagnosticsForFile(
const std::string &filename ) { std::string filename ) {
shared_ptr< TranslationUnit > unit = translation_unit_store_.Get( filename ); shared_ptr< TranslationUnit > unit = translation_unit_store_.Get( filename );
if ( !unit ) if ( !unit )
@ -127,9 +127,9 @@ bool ClangCompleter::UpdatingTranslationUnit( const std::string &filename ) {
void ClangCompleter::UpdateTranslationUnit( void ClangCompleter::UpdateTranslationUnit(
const std::string &filename, std::string filename,
const std::vector< UnsavedFile > &unsaved_files, std::vector< UnsavedFile > unsaved_files,
const std::vector< std::string > &flags ) { std::vector< std::string > flags ) {
bool translation_unit_created; bool translation_unit_created;
shared_ptr< TranslationUnit > unit = translation_unit_store_.GetOrCreate( shared_ptr< TranslationUnit > unit = translation_unit_store_.GetOrCreate(
filename, filename,
@ -182,11 +182,11 @@ Future< void > ClangCompleter::UpdateTranslationUnitAsync(
std::vector< CompletionData > std::vector< CompletionData >
ClangCompleter::CandidatesForLocationInFile( ClangCompleter::CandidatesForLocationInFile(
const std::string &filename, std::string filename,
int line, int line,
int column, int column,
const std::vector< UnsavedFile > &unsaved_files, std::vector< UnsavedFile > unsaved_files,
const std::vector< std::string > &flags ) { std::vector< std::string > flags ) {
shared_ptr< TranslationUnit > unit = shared_ptr< TranslationUnit > unit =
translation_unit_store_.GetOrCreate( filename, unsaved_files, flags ); translation_unit_store_.GetOrCreate( filename, unsaved_files, flags );
@ -201,12 +201,12 @@ ClangCompleter::CandidatesForLocationInFile(
Future< AsyncCompletions > Future< AsyncCompletions >
ClangCompleter::CandidatesForQueryAndLocationInFileAsync( ClangCompleter::CandidatesForQueryAndLocationInFileAsync(
const std::string &query, std::string query,
const std::string &filename, std::string filename,
int line, int line,
int column, int column,
const std::vector< UnsavedFile > &unsaved_files, std::vector< UnsavedFile > unsaved_files,
const std::vector< std::string > &flags ) { std::vector< std::string > flags ) {
// TODO: throw exception when threading is not enabled and this is called // TODO: throw exception when threading is not enabled and this is called
if ( !threading_enabled_ ) if ( !threading_enabled_ )
return Future< AsyncCompletions >(); return Future< AsyncCompletions >();
@ -238,7 +238,11 @@ ClangCompleter::CandidatesForQueryAndLocationInFileAsync(
CreateSortingTask( query, future ); CreateSortingTask( query, future );
if ( skip_clang_result_cache ) { 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 ) ); return Future< AsyncCompletions >( boost::move( future ) );
@ -246,11 +250,11 @@ ClangCompleter::CandidatesForQueryAndLocationInFileAsync(
Location ClangCompleter::GetDeclarationLocation( Location ClangCompleter::GetDeclarationLocation(
const std::string &filename, std::string filename,
int line, int line,
int column, int column,
const std::vector< UnsavedFile > &unsaved_files, std::vector< UnsavedFile > unsaved_files,
const std::vector< std::string > &flags ) { std::vector< std::string > flags ) {
shared_ptr< TranslationUnit > unit = shared_ptr< TranslationUnit > unit =
translation_unit_store_.GetOrCreate( filename, unsaved_files, flags ); translation_unit_store_.GetOrCreate( filename, unsaved_files, flags );
@ -263,11 +267,11 @@ Location ClangCompleter::GetDeclarationLocation(
Location ClangCompleter::GetDefinitionLocation( Location ClangCompleter::GetDefinitionLocation(
const std::string &filename, std::string filename,
int line, int line,
int column, int column,
const std::vector< UnsavedFile > &unsaved_files, std::vector< UnsavedFile > unsaved_files,
const std::vector< std::string > &flags ) { std::vector< std::string > flags ) {
shared_ptr< TranslationUnit > unit = shared_ptr< TranslationUnit > unit =
translation_unit_store_.GetOrCreate( filename, unsaved_files, flags ); 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 ); file_cache_delete_stack_.Push( filename );
} }

View File

@ -59,18 +59,23 @@ public:
void EnableThreading(); void EnableThreading();
std::vector< Diagnostic > DiagnosticsForFile( const std::string &filename ); std::vector< Diagnostic > DiagnosticsForFile( std::string filename );
bool UpdatingTranslationUnit( const 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) // Public because of unit tests (gtest is not very thread-friendly)
void UpdateTranslationUnit( void UpdateTranslationUnit(
const std::string &filename, std::string filename,
const std::vector< UnsavedFile > &unsaved_files, std::vector< UnsavedFile > unsaved_files,
const std::vector< std::string > &flags ); 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( Future< void > UpdateTranslationUnitAsync(
std::string filename, std::string filename,
std::vector< UnsavedFile > unsaved_files, std::vector< UnsavedFile > unsaved_files,
@ -78,35 +83,35 @@ public:
// Public because of unit tests (gtest is not very thread-friendly) // Public because of unit tests (gtest is not very thread-friendly)
std::vector< CompletionData > CandidatesForLocationInFile( std::vector< CompletionData > CandidatesForLocationInFile(
const std::string &filename, std::string filename,
int line, int line,
int column, int column,
const std::vector< UnsavedFile > &unsaved_files, std::vector< UnsavedFile > unsaved_files,
const std::vector< std::string > &flags ); std::vector< std::string > flags );
Future< AsyncCompletions > CandidatesForQueryAndLocationInFileAsync( Future< AsyncCompletions > CandidatesForQueryAndLocationInFileAsync(
const std::string &query, std::string query,
const std::string &filename, std::string filename,
int line, int line,
int column, int column,
const std::vector< UnsavedFile > &unsaved_files, std::vector< UnsavedFile > unsaved_files,
const std::vector< std::string > &flags ); std::vector< std::string > flags );
Location GetDeclarationLocation( Location GetDeclarationLocation(
const std::string &filename, std::string filename,
int line, int line,
int column, int column,
const std::vector< UnsavedFile > &unsaved_files, std::vector< UnsavedFile > unsaved_files,
const std::vector< std::string > &flags ); std::vector< std::string > flags );
Location GetDefinitionLocation( Location GetDefinitionLocation(
const std::string &filename, std::string filename,
int line, int line,
int column, int column,
const std::vector< UnsavedFile > &unsaved_files, std::vector< UnsavedFile > unsaved_files,
const std::vector< std::string > &flags ); std::vector< std::string > flags );
void DeleteCachesForFileAsync( const std::string &filename ); void DeleteCachesForFileAsync( std::string filename );
private: private:

View File

@ -143,8 +143,8 @@ let g:ycm_extra_conf_globlist =
let g:ycm_filepath_completion_use_working_dir = let g:ycm_filepath_completion_use_working_dir =
\ get( g:, 'ycm_filepath_completion_use_working_dir', 0 ) \ get( g:, 'ycm_filepath_completion_use_working_dir', 0 )
" Default semantic triggers are in python/ycm/completers/completer.py, these " Default semantic triggers are in python/ycm/completers/completer_utils.py
" just append new triggers to the default dict. " these just append new triggers to the default dict.
let g:ycm_semantic_triggers = let g:ycm_semantic_triggers =
\ get( g:, 'ycm_semantic_triggers', {} ) \ get( g:, 'ycm_semantic_triggers', {} )

View File

@ -25,6 +25,7 @@ from ycm.completers.general_completer import GeneralCompleter
from ycm.completers.general import syntax_parse from ycm.completers.general import syntax_parse
from ycm import vimsupport from ycm import vimsupport
from ycm import utils from ycm import utils
from ycm import server_responses
MAX_IDENTIFIER_COMPLETIONS_RETURNED = 10 MAX_IDENTIFIER_COMPLETIONS_RETURNED = 10
SYNTAX_FILENAME = 'YCM_PLACEHOLDER_FOR_SYNTAX' SYNTAX_FILENAME = 'YCM_PLACEHOLDER_FOR_SYNTAX'
@ -39,15 +40,14 @@ class IdentifierCompleter( GeneralCompleter ):
self.filetypes_with_keywords_loaded = set() self.filetypes_with_keywords_loaded = set()
def ShouldUseNow( self, start_column, unused_current_line ): def ShouldUseNow( self, request_data ):
return self.QueryLengthAboveMinThreshold( start_column ) return self.QueryLengthAboveMinThreshold( request_data )
def CandidatesForQueryAsync( self, query, unused_start_column ): def CandidatesForQueryAsync( self, request_data ):
filetype = vim.eval( "&filetype" )
self.completions_future = self.completer.CandidatesForQueryAndTypeAsync( self.completions_future = self.completer.CandidatesForQueryAndTypeAsync(
utils.SanitizeQuery( query ), utils.SanitizeQuery( request_data[ 'query' ] ),
filetype ) request_data[ 'filetypes' ][ 0 ] )
def AddIdentifier( self, identifier ): def AddIdentifier( self, identifier ):
@ -83,17 +83,16 @@ class IdentifierCompleter( GeneralCompleter ):
self.AddIdentifier( stripped_cursor_identifier ) self.AddIdentifier( stripped_cursor_identifier )
def AddBufferIdentifiers( self ): def AddBufferIdentifiers( self, request_data ):
# TODO: use vimsupport.GetFiletypes; also elsewhere in file filetype = request_data[ 'filetypes' ][ 0 ]
filetype = vim.eval( "&filetype" ) filepath = request_data[ 'filepath' ]
filepath = vim.eval( "expand('%:p')" )
collect_from_comments_and_strings = bool( self.user_options[ collect_from_comments_and_strings = bool( self.user_options[
'collect_identifiers_from_comments_and_strings' ] ) 'collect_identifiers_from_comments_and_strings' ] )
if not filetype or not filepath: if not filetype or not filepath:
return return
text = "\n".join( vim.current.buffer ) text = request_data[ 'file_data' ][ filepath ][ 'contents' ]
self.completer.AddIdentifiersToDatabaseFromBufferAsync( self.completer.AddIdentifiersToDatabaseFromBufferAsync(
text, text,
filetype, filetype,
@ -147,14 +146,15 @@ class IdentifierCompleter( GeneralCompleter ):
filepath ) filepath )
def OnFileReadyToParse( self ): def OnFileReadyToParse( self, request_data ):
self.AddBufferIdentifiers() self.AddBufferIdentifiers( request_data )
if self.user_options[ 'collect_identifiers_from_tags_files' ]: # TODO: make these work again
self.AddIdentifiersFromTagFiles() # if self.user_options[ 'collect_identifiers_from_tags_files' ]:
# self.AddIdentifiersFromTagFiles()
if self.user_options[ 'seed_identifiers_with_syntax' ]: # if self.user_options[ 'seed_identifiers_with_syntax' ]:
self.AddIdentifiersFromSyntax() # self.AddIdentifiersFromSyntax()
def OnInsertLeave( self ): def OnInsertLeave( self ):
@ -174,11 +174,7 @@ class IdentifierCompleter( GeneralCompleter ):
completions = _RemoveSmallCandidates( completions = _RemoveSmallCandidates(
completions, self.user_options[ 'min_num_identifier_candidate_chars' ] ) completions, self.user_options[ 'min_num_identifier_candidate_chars' ] )
# We will never have duplicates in completions so with 'dup':1 we tell Vim return [ server_responses.BuildCompletionData( x ) for x in completions ]
# 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 ]
def _PreviousIdentifier( min_num_completion_start_chars ): def _PreviousIdentifier( min_num_completion_start_chars ):

View File

@ -40,29 +40,27 @@ class OmniCompleter( Completer ):
return bool( self.user_options[ 'cache_omnifunc' ] ) return bool( self.user_options[ 'cache_omnifunc' ] )
def ShouldUseNow( self, start_column, current_line ): def ShouldUseNow( self, request_data ):
if self.ShouldUseCache(): if self.ShouldUseCache():
return super( OmniCompleter, self ).ShouldUseNow( start_column, return super( OmniCompleter, self ).ShouldUseNow( request_data )
current_line ) return self.ShouldUseNowInner( request_data )
return self.ShouldUseNowInner( start_column, current_line )
def ShouldUseNowInner( self, start_column, current_line ): def ShouldUseNowInner( self, request_data ):
if not self.omnifunc: if not self.omnifunc:
return False return False
return super( OmniCompleter, self ).ShouldUseNowInner( start_column, return super( OmniCompleter, self ).ShouldUseNowInner( request_data )
current_line )
def CandidatesForQueryAsync( self, query, unused_start_column ): def CandidatesForQueryAsync( self, request_data ):
if self.ShouldUseCache(): if self.ShouldUseCache():
return super( OmniCompleter, self ).CandidatesForQueryAsync( return super( OmniCompleter, self ).CandidatesForQueryAsync(
query, unused_start_column ) request_data )
else: 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: if not self.omnifunc:
self.stored_candidates = None self.stored_candidates = None
return return
@ -75,7 +73,7 @@ class OmniCompleter( Completer ):
omnifunc_call = [ self.omnifunc, omnifunc_call = [ self.omnifunc,
"(0,'", "(0,'",
vimsupport.EscapeForVim( query ), vimsupport.EscapeForVim( request_data[ 'query' ] ),
"')" ] "')" ]
items = vim.eval( ''.join( omnifunc_call ) ) items = vim.eval( ''.join( omnifunc_call ) )
@ -98,7 +96,7 @@ class OmniCompleter( Completer ):
return True return True
def OnFileReadyToParse( self ): def OnFileReadyToParse( self, request_data ):
self.omnifunc = vim.eval( '&omnifunc' ) self.omnifunc = vim.eval( '&omnifunc' )

View File

@ -19,7 +19,6 @@
import abc import abc
import ycm_core import ycm_core
from ycm import vimsupport
from ycm.completers.completer_utils import TriggersForFiletype from ycm.completers.completer_utils import TriggersForFiletype
NO_USER_COMMANDS = 'This completer does not define any commands.' NO_USER_COMMANDS = 'This completer does not define any commands.'
@ -116,32 +115,35 @@ class Completer( object ):
def __init__( self, user_options ): def __init__( self, user_options ):
self.user_options = user_options self.user_options = user_options
self.min_num_chars = user_options[ 'min_num_of_chars_for_completion' ] 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_future = None
self.completions_cache = 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 # It's highly likely you DON'T want to override this function but the *Inner
# version of it. # version of it.
def ShouldUseNow( self, start_column, current_line ): def ShouldUseNow( self, request_data ):
inner_says_yes = self.ShouldUseNowInner( start_column, current_line ) inner_says_yes = self.ShouldUseNowInner( request_data )
if not inner_says_yes: if not inner_says_yes:
self.completions_cache = None self.completions_cache = None
previous_results_were_empty = ( self.completions_cache and previous_results_were_empty = ( self.completions_cache and
self.completions_cache.CacheValid( self.completions_cache.CacheValid(
start_column ) and request_data[ 'line_num' ],
request_data[ 'start_column' ] ) and
not self.completions_cache.raw_completions ) not self.completions_cache.raw_completions )
return inner_says_yes and not previous_results_were_empty 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 ) line_length = len( current_line )
if not line_length or start_column - 1 >= line_length: if not line_length or start_column - 1 >= line_length:
return False return False
filetype = self._CurrentFiletype() filetype = self._CurrentFiletype( request_data[ 'filetypes' ] )
triggers = self.triggers_for_filetype[ filetype ] triggers = self.triggers_for_filetype[ filetype ]
for trigger in triggers: for trigger in triggers:
@ -158,52 +160,61 @@ class Completer( object ):
return False return False
def QueryLengthAboveMinThreshold( self, start_column ): def QueryLengthAboveMinThreshold( self, request_data ):
query_length = vimsupport.CurrentColumn() - start_column query_length = request_data[ 'column_num' ] - request_data[ 'start_column' ]
return query_length >= self.min_num_chars return query_length >= self.min_num_chars
# It's highly likely you DON'T want to override this function but the *Inner # It's highly likely you DON'T want to override this function but the *Inner
# version of it. # version of it.
def CandidatesForQueryAsync( self, query, start_column ): def CandidatesForQueryAsync( self, request_data ):
self.completion_start_column = start_column self.request_data = request_data
if query and self.completions_cache and self.completions_cache.CacheValid( if ( request_data[ 'query' ] and
start_column ): self.completions_cache and
self.completions_cache.CacheValid( request_data[ 'line_num' ],
request_data[ 'start_column' ] ) ):
self.completions_cache.filtered_completions = ( self.completions_cache.filtered_completions = (
self.FilterAndSortCandidates( self.FilterAndSortCandidates(
self.completions_cache.raw_completions, self.completions_cache.raw_completions,
query ) ) request_data[ 'query' ] ) )
else: else:
self.completions_cache = None self.completions_cache = None
self.CandidatesForQueryAsyncInner( query, start_column ) self.CandidatesForQueryAsyncInner( request_data )
def DefinedSubcommands( self ): def DefinedSubcommands( self ):
return [] return []
def EchoUserCommandsHelpMessage( self ): def UserCommandsHelpMessage( self ):
subcommands = self.DefinedSubcommands() subcommands = self.DefinedSubcommands()
if subcommands: if subcommands:
vimsupport.EchoText( 'Supported commands are:\n' + return ( 'Supported commands are:\n' +
'\n'.join( subcommands ) + '\n'.join( subcommands ) +
'\nSee the docs for information on what they do.' ) '\nSee the docs for information on what they do.' )
else: else:
vimsupport.EchoText( 'No supported subcommands' ) return 'No supported subcommands'
def FilterAndSortCandidates( self, candidates, query ): def FilterAndSortCandidates( self, candidates, query ):
if not candidates: if not candidates:
return [] return []
if hasattr( candidates, 'words' ): # We need to handle both an omni_completer style completer and a server
candidates = candidates.words # style completer
items_are_objects = 'word' in candidates[ 0 ] 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( matches = ycm_core.FilterAndSortCandidates(
candidates, candidates,
'word' if items_are_objects else '', sort_property,
query ) query )
return matches return matches
@ -238,8 +249,8 @@ class Completer( object ):
else: else:
self.completions_cache = CompletionsCache() self.completions_cache = CompletionsCache()
self.completions_cache.raw_completions = self.CandidatesFromStoredRequestInner() self.completions_cache.raw_completions = self.CandidatesFromStoredRequestInner()
self.completions_cache.line, _ = vimsupport.CurrentLineAndColumn() self.completions_cache.line = self.request_data[ 'line_num' ]
self.completions_cache.column = self.completion_start_column self.completions_cache.column = self.request_data[ 'start_column' ]
return self.completions_cache.raw_completions return self.completions_cache.raw_completions
@ -249,7 +260,7 @@ class Completer( object ):
return self.completions_future.GetResults() return self.completions_future.GetResults()
def OnFileReadyToParse( self ): def OnFileReadyToParse( self, request_data ):
pass pass
@ -269,8 +280,8 @@ class Completer( object ):
pass pass
def OnUserCommand( self, arguments ): def OnUserCommand( self, arguments, request_data ):
vimsupport.PostVimMessage( NO_USER_COMMANDS ) raise NotImplementedError( NO_USER_COMMANDS )
def OnCurrentIdentifierFinished( self ): def OnCurrentIdentifierFinished( self ):
@ -285,7 +296,7 @@ class Completer( object ):
return [] return []
def ShowDetailedDiagnostic( self ): def GetDetailedDiagnostic( self ):
pass pass
@ -293,8 +304,7 @@ class Completer( object ):
return False return False
def _CurrentFiletype( self ): def _CurrentFiletype( self, filetypes ):
filetypes = vimsupport.CurrentFiletypes()
supported = self.SupportedFiletypes() supported = self.SupportedFiletypes()
for filetype in filetypes: for filetype in filetypes:
@ -321,9 +331,7 @@ class CompletionsCache( object ):
self.filtered_completions = [] self.filtered_completions = []
def CacheValid( self, start_column ): def CacheValid( self, current_line, start_column ):
completion_line, _ = vimsupport.CurrentLineAndColumn() return current_line == self.line and start_column == self.column
completion_column = start_column
return completion_line == self.line and completion_column == self.column

View File

@ -19,7 +19,6 @@
from collections import defaultdict from collections import defaultdict
from copy import deepcopy from copy import deepcopy
import vim
DEFAULT_FILETYPE_TRIGGERS = { DEFAULT_FILETYPE_TRIGGERS = {
'c' : ['->', '.'], 'c' : ['->', '.'],
@ -58,12 +57,9 @@ def _FiletypeDictUnion( dict_one, dict_two ):
return final_dict return final_dict
def TriggersForFiletype(): def TriggersForFiletype( user_triggers ):
user_triggers = _FiletypeTriggerDictFromSpec(
vim.eval( 'g:ycm_semantic_triggers' ) )
default_triggers = _FiletypeTriggerDictFromSpec( default_triggers = _FiletypeTriggerDictFromSpec(
DEFAULT_FILETYPE_TRIGGERS ) DEFAULT_FILETYPE_TRIGGERS )
return _FiletypeDictUnion( default_triggers, user_triggers ) return _FiletypeDictUnion( default_triggers, dict( user_triggers ) )

View File

@ -18,14 +18,23 @@
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
from collections import defaultdict from collections import defaultdict
import vim
import ycm_core import ycm_core
from ycm import vimsupport import logging
from ycm import server_responses
from ycm import extra_conf_store from ycm import extra_conf_store
from ycm.completers.completer import Completer from ycm.completers.completer import Completer
from ycm.completers.cpp.flags import Flags from ycm.completers.cpp.flags import Flags
CLANG_FILETYPES = set( [ 'c', 'cpp', 'objc', 'objcpp' ] ) 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 ): class ClangCompleter( Completer ):
@ -35,12 +44,11 @@ class ClangCompleter( Completer ):
'max_diagnostics_to_display' ] 'max_diagnostics_to_display' ]
self.completer = ycm_core.ClangCompleter() self.completer = ycm_core.ClangCompleter()
self.completer.EnableThreading() self.completer.EnableThreading()
self.contents_holder = []
self.filename_holder = []
self.last_prepared_diagnostics = [] self.last_prepared_diagnostics = []
self.parse_future = None self.parse_future = None
self.flags = Flags() self.flags = Flags()
self.diagnostic_store = None self.diagnostic_store = None
self._logger = logging.getLogger( __name__ )
# We set this flag when a compilation request comes in while one is already # 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 # in progress. We use this to trigger the pending request after the previous
@ -53,63 +61,52 @@ class ClangCompleter( Completer ):
return CLANG_FILETYPES return CLANG_FILETYPES
def GetUnsavedFilesVector( self ): def GetUnsavedFilesVector( self, request_data ):
# 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.
files = ycm_core.UnsavedFileVec() files = ycm_core.UnsavedFileVec()
self.contents_holder = [] for filename, file_data in request_data[ 'file_data' ].iteritems():
self.filename_holder = [] if not ClangAvailableForFiletypes( file_data[ 'filetypes' ] ):
for buffer in vimsupport.GetUnsavedBuffers():
if not ClangAvailableForBuffer( buffer ):
continue continue
contents = '\n'.join( buffer ) contents = file_data[ 'contents' ]
name = buffer.name if not contents or not filename:
if not contents or not name:
continue continue
self.contents_holder.append( contents )
self.filename_holder.append( name )
unsaved_file = ycm_core.UnsavedFile() unsaved_file = ycm_core.UnsavedFile()
unsaved_file.contents_ = self.contents_holder[ -1 ] unsaved_file.contents_ = contents
unsaved_file.length_ = len( self.contents_holder[ -1 ] ) unsaved_file.length_ = len( contents )
unsaved_file.filename_ = self.filename_holder[ -1 ] unsaved_file.filename_ = filename
files.append( unsaved_file ) files.append( unsaved_file )
return files return files
def CandidatesForQueryAsync( self, query, start_column ): def CandidatesForQueryAsync( self, request_data ):
filename = vim.current.buffer.name filename = request_data[ 'filepath' ]
if not filename: if not filename:
return return
if self.completer.UpdatingTranslationUnit( filename ): if self.completer.UpdatingTranslationUnit( filename ):
vimsupport.PostVimMessage( 'Still parsing file, no completions yet.' )
self.completions_future = None self.completions_future = None
return self._logger.info( PARSING_FILE_MESSAGE )
return server_responses.BuildDisplayMessageResponse(
PARSING_FILE_MESSAGE )
flags = self.flags.FlagsForFile( filename ) flags = self.flags.FlagsForFile( filename )
if not flags: if not flags:
vimsupport.PostVimMessage( 'Still no compile flags, no completions yet.' )
self.completions_future = None 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 # TODO: sanitize query, probably in C++ code
files = ycm_core.UnsavedFileVec() files = ycm_core.UnsavedFileVec()
query = request_data[ 'query' ]
if not query: if not query:
files = self.GetUnsavedFilesVector() files = self.GetUnsavedFilesVector( request_data )
line, _ = vim.current.window.cursor line = request_data[ 'line_num' ] + 1
column = start_column + 1 column = request_data[ 'start_column' ] + 1
self.completions_future = ( self.completions_future = (
self.completer.CandidatesForQueryAndLocationInFileAsync( self.completer.CandidatesForQueryAndLocationInFileAsync(
query, query,
@ -123,10 +120,11 @@ class ClangCompleter( Completer ):
def CandidatesFromStoredRequest( self ): def CandidatesFromStoredRequest( self ):
if not self.completions_future: if not self.completions_future:
return [] return []
results = [ CompletionDataToDict( x ) for x in results = [ ConvertCompletionData( x ) for x in
self.completions_future.GetResults() ] self.completions_future.GetResults() ]
if not results: 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 return results
@ -137,37 +135,37 @@ class ClangCompleter( Completer ):
'ClearCompilationFlagCache'] 'ClearCompilationFlagCache']
def OnUserCommand( self, arguments ): def OnUserCommand( self, arguments, request_data ):
if not arguments: if not arguments:
self.EchoUserCommandsHelpMessage() raise ValueError( self.UserCommandsHelpMessage() )
return
command = arguments[ 0 ] command = arguments[ 0 ]
if command == 'GoToDefinition': if command == 'GoToDefinition':
self._GoToDefinition() self._GoToDefinition( request_data )
elif command == 'GoToDeclaration': elif command == 'GoToDeclaration':
self._GoToDeclaration() self._GoToDeclaration( request_data )
elif command == 'GoToDefinitionElseDeclaration': elif command == 'GoToDefinitionElseDeclaration':
self._GoToDefinitionElseDeclaration() self._GoToDefinitionElseDeclaration( request_data )
elif command == 'ClearCompilationFlagCache': elif command == 'ClearCompilationFlagCache':
self._ClearCompilationFlagCache() self._ClearCompilationFlagCache( request_data )
def _LocationForGoTo( self, goto_function ): def _LocationForGoTo( self, goto_function, request_data ):
filename = vim.current.buffer.name filename = request_data[ 'filepath' ]
if not filename: if not filename:
return None self._logger.warning( INVALID_FILE_MESSAGE )
return server_responses.BuildDisplayMessageResponse(
INVALID_FILE_MESSAGE )
flags = self.flags.FlagsForFile( filename ) flags = self.flags.FlagsForFile( filename )
if not flags: if not flags:
vimsupport.PostVimMessage( 'Still no compile flags, can\'t compile.' ) self._logger.info( NO_COMPILE_FLAGS_MESSAGE )
return None return server_responses.BuildDisplayMessageResponse(
NO_COMPILE_FLAGS_MESSAGE )
files = self.GetUnsavedFilesVector() files = self.GetUnsavedFilesVector()
line, column = vimsupport.CurrentLineAndColumn() line = request_data[ 'line_num' ] + 1
# Making the line & column 1-based instead of 0-based column = request_data[ 'start_column' ] + 1
line += 1
column += 1
return getattr( self.completer, goto_function )( return getattr( self.completer, goto_function )(
filename, filename,
line, line,
@ -176,54 +174,56 @@ class ClangCompleter( Completer ):
flags ) flags )
def _GoToDefinition( self ): def _GoToDefinition( self, request_data ):
location = self._LocationForGoTo( 'GetDefinitionLocation' ) location = self._LocationForGoTo( 'GetDefinitionLocation' )
if not location or not location.IsValid(): if not location or not location.IsValid():
vimsupport.PostVimMessage( 'Can\'t jump to definition.' ) raise RuntimeError( 'Can\'t jump to definition.' )
return
vimsupport.JumpToLocation( location.filename_, return server_responses.BuildGoToResponse( location.filename_,
location.line_number_, location.line_number_,
location.column_number_ ) location.column_number_ )
def _GoToDeclaration( self ): def _GoToDeclaration( self, request_data ):
location = self._LocationForGoTo( 'GetDeclarationLocation' ) location = self._LocationForGoTo( 'GetDeclarationLocation' )
if not location or not location.IsValid(): if not location or not location.IsValid():
vimsupport.PostVimMessage( 'Can\'t jump to declaration.' ) raise RuntimeError( 'Can\'t jump to declaration.' )
return
vimsupport.JumpToLocation( location.filename_, return server_responses.BuildGoToResponse( location.filename_,
location.line_number_, location.line_number_,
location.column_number_ ) location.column_number_ )
def _GoToDefinitionElseDeclaration( self ): def _GoToDefinitionElseDeclaration( self, request_data ):
location = self._LocationForGoTo( 'GetDefinitionLocation' ) location = self._LocationForGoTo( 'GetDefinitionLocation' )
if not location or not location.IsValid(): if not location or not location.IsValid():
location = self._LocationForGoTo( 'GetDeclarationLocation' ) location = self._LocationForGoTo( 'GetDeclarationLocation' )
if not location or not location.IsValid(): if not location or not location.IsValid():
vimsupport.PostVimMessage( 'Can\'t jump to definition or declaration.' ) raise RuntimeError( 'Can\'t jump to definition or declaration.' )
return
vimsupport.JumpToLocation( location.filename_, return server_responses.BuildGoToResponse( location.filename_,
location.line_number_, location.line_number_,
location.column_number_ ) location.column_number_ )
def _ClearCompilationFlagCache( self ): def _ClearCompilationFlagCache( self ):
self.flags.Clear() self.flags.Clear()
def OnFileReadyToParse( self ): def OnFileReadyToParse( self, request_data ):
if vimsupport.NumLinesInBuffer( vim.current.buffer ) < 5: 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 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: if not filename:
return self._logger.warning( INVALID_FILE_MESSAGE )
return server_responses.BuildDisplayMessageResponse(
INVALID_FILE_MESSAGE )
if self.completer.UpdatingTranslationUnit( filename ): if self.completer.UpdatingTranslationUnit( filename ):
self.extra_parse_desired = True self.extra_parse_desired = True
@ -232,11 +232,13 @@ class ClangCompleter( Completer ):
flags = self.flags.FlagsForFile( filename ) flags = self.flags.FlagsForFile( filename )
if not flags: if not flags:
self.parse_future = None 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( self.parse_future = self.completer.UpdateTranslationUnitAsync(
filename, filename,
self.GetUnsavedFilesVector(), self.GetUnsavedFilesVector( request_data ),
flags ) flags )
self.extra_parse_desired = False self.extra_parse_desired = False
@ -253,15 +255,17 @@ class ClangCompleter( Completer ):
return self.parse_future.ResultsReady() return self.parse_future.ResultsReady()
def GettingCompletions( self ): def GettingCompletions( self, request_data ):
return self.completer.UpdatingTranslationUnit( vim.current.buffer.name ) return self.completer.UpdatingTranslationUnit( request_data[ 'filepath' ] )
def GetDiagnosticsForCurrentFile( self ): def GetDiagnosticsForCurrentFile( self, request_data ):
filename = request_data[ 'filepath' ]
if self.DiagnosticsForCurrentFileReady(): if self.DiagnosticsForCurrentFileReady():
diagnostics = self.completer.DiagnosticsForFile( vim.current.buffer.name ) diagnostics = self.completer.DiagnosticsForFile( filename )
self.diagnostic_store = DiagnosticsToDiagStructure( diagnostics ) self.diagnostic_store = DiagnosticsToDiagStructure( diagnostics )
self.last_prepared_diagnostics = [ DiagnosticToDict( x ) for x in self.last_prepared_diagnostics = [
server_responses.BuildDiagnosticData( x ) for x in
diagnostics[ : self.max_diagnostics_to_display ] ] diagnostics[ : self.max_diagnostics_to_display ] ]
self.parse_future = None self.parse_future = None
@ -271,23 +275,19 @@ class ClangCompleter( Completer ):
return self.last_prepared_diagnostics return self.last_prepared_diagnostics
def ShowDetailedDiagnostic( self ): def GetDetailedDiagnostic( self, request_data ):
current_line, current_column = vimsupport.CurrentLineAndColumn() current_line = request_data[ 'line_num' ] + 1
current_column = request_data[ 'column_num' ] + 1
# CurrentLineAndColumn() numbers are 0-based, clang numbers are 1-based current_file = request_data[ 'filepath' ]
current_line += 1
current_column += 1
current_file = vim.current.buffer.name
if not self.diagnostic_store: if not self.diagnostic_store:
vimsupport.PostVimMessage( "No diagnostic for current line!" ) return server_responses.BuildDisplayMessageResponse(
return NO_DIAGNOSTIC_MESSAGE )
diagnostics = self.diagnostic_store[ current_file ][ current_line ] diagnostics = self.diagnostic_store[ current_file ][ current_line ]
if not diagnostics: if not diagnostics:
vimsupport.PostVimMessage( "No diagnostic for current line!" ) return server_responses.BuildDisplayMessageResponse(
return NO_DIAGNOSTIC_MESSAGE )
closest_diagnostic = None closest_diagnostic = None
distance_to_closest_diagnostic = 999 distance_to_closest_diagnostic = 999
@ -298,50 +298,61 @@ class ClangCompleter( Completer ):
distance_to_closest_diagnostic = distance distance_to_closest_diagnostic = distance
closest_diagnostic = diagnostic 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. # 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 ): def DebugInfo( self, request_data ):
filename = vim.current.buffer.name filename = request_data[ 'filepath' ]
if not filename: if not filename:
return '' return ''
flags = self.flags.FlagsForFile( filename ) or [] flags = self.flags.FlagsForFile( filename ) or []
source = extra_conf_store.ModuleFileForSourceFile( filename ) source = extra_conf_store.ModuleFileForSourceFile( filename )
return 'Flags for {0} loaded from {1}:\n{2}'.format( filename, return server_responses.BuildDisplayMessageResponse(
'Flags for {0} loaded from {1}:\n{2}'.format( filename,
source, source,
list( flags ) ) list( flags ) ) )
# TODO: make these functions module-local # TODO: make these functions module-local
def CompletionDataToDict( completion_data ): # def CompletionDataToDict( completion_data ):
# see :h complete-items for a description of the dictionary fields # # see :h complete-items for a description of the dictionary fields
return { # return {
'word' : completion_data.TextToInsertInBuffer(), # 'word' : completion_data.TextToInsertInBuffer(),
'abbr' : completion_data.MainCompletionText(), # 'abbr' : completion_data.MainCompletionText(),
'menu' : completion_data.ExtraMenuInfo(), # 'menu' : completion_data.ExtraMenuInfo(),
'kind' : completion_data.kind_, # 'kind' : completion_data.kind_,
'info' : completion_data.DetailedInfoForPreviewWindow(), # 'info' : completion_data.DetailedInfoForPreviewWindow(),
'dup' : 1, # 'dup' : 1,
} # }
def DiagnosticToDict( diagnostic ): # def DiagnosticToDict( diagnostic ):
# see :h getqflist for a description of the dictionary fields # # see :h getqflist for a description of the dictionary fields
return { # return {
# TODO: wrap the bufnr generation into a function # # TODO: wrap the bufnr generation into a function
'bufnr' : int( vim.eval( "bufnr('{0}', 1)".format( # 'bufnr' : int( vim.eval( "bufnr('{0}', 1)".format(
diagnostic.filename_ ) ) ), # diagnostic.filename_ ) ) ),
'lnum' : diagnostic.line_number_, # 'lnum' : diagnostic.line_number_,
'col' : diagnostic.column_number_, # 'col' : diagnostic.column_number_,
'text' : diagnostic.text_, # 'text' : diagnostic.text_,
'type' : diagnostic.kind_, # 'type' : diagnostic.kind_,
'valid' : 1 # '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 ): def DiagnosticsToDiagStructure( diagnostics ):
@ -352,12 +363,9 @@ def DiagnosticsToDiagStructure( diagnostics ):
return structure return structure
def ClangAvailableForBuffer( buffer_object ): def ClangAvailableForFiletypes( filetypes ):
filetypes = vimsupport.FiletypesForBuffer( buffer_object )
return any( [ filetype in CLANG_FILETYPES for filetype in filetypes ] ) return any( [ filetype in CLANG_FILETYPES for filetype in filetypes ] )
def InCFamilyFile(): def InCFamilyFile( filetypes ):
return any( [ filetype in CLANG_FILETYPES for filetype in return ClangAvailableForFiletypes( filetypes )
vimsupport.CurrentFiletypes() ] )

View File

@ -19,7 +19,6 @@
import ycm_core import ycm_core
import os import os
from ycm import vimsupport
from ycm import extra_conf_store 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 '
@ -37,7 +36,6 @@ class Flags( object ):
# It's caches all the way down... # It's caches all the way down...
self.flags_for_file = {} self.flags_for_file = {}
self.special_clang_flags = _SpecialClangIncludes() self.special_clang_flags = _SpecialClangIncludes()
self.no_extra_conf_file_warning_posted = False
def FlagsForFile( self, filename, add_special_clang_flags = True ): def FlagsForFile( self, filename, add_special_clang_flags = True ):
@ -46,10 +44,7 @@ class Flags( object ):
except KeyError: except KeyError:
module = extra_conf_store.ModuleForSourceFile( filename ) module = extra_conf_store.ModuleForSourceFile( filename )
if not module: if not module:
if not self.no_extra_conf_file_warning_posted: raise RuntimeError( NO_EXTRA_CONF_FILENAME_MESSAGE )
vimsupport.PostVimMessage( NO_EXTRA_CONF_FILENAME_MESSAGE )
self.no_extra_conf_file_warning_posted = True
return None
results = module.FlagsForFile( filename ) results = module.FlagsForFile( filename )

View File

@ -18,18 +18,18 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import vim
import os import os
from sys import platform from sys import platform
import glob import glob
from ycm.completers.threaded_completer import ThreadedCompleter from ycm.completers.threaded_completer import ThreadedCompleter
from ycm import vimsupport from ycm import server_responses
import urllib2 import urllib2
import urllib import urllib
import urlparse import urlparse
import json import json
import subprocess import subprocess
import tempfile import tempfile
import logging
SERVER_NOT_FOUND_MSG = ( 'OmniSharp server binary not found at {0}. ' + SERVER_NOT_FOUND_MSG = ( 'OmniSharp server binary not found at {0}. ' +
@ -44,9 +44,10 @@ class CsharpCompleter( ThreadedCompleter ):
def __init__( self, user_options ): def __init__( self, user_options ):
super( CsharpCompleter, self ).__init__( user_options ) super( CsharpCompleter, self ).__init__( user_options )
self._omnisharp_port = None self._omnisharp_port = None
self._logger = logging.getLogger(__name__)
if self.user_options[ 'auto_start_csharp_server' ]: # if self.user_options[ 'auto_start_csharp_server' ]:
self._StartServer() # self._StartServer()
def OnVimLeave( self ): def OnVimLeave( self ):
@ -60,11 +61,12 @@ class CsharpCompleter( ThreadedCompleter ):
return [ 'cs' ] return [ 'cs' ]
def ComputeCandidates( self, unused_query, unused_start_column ): def ComputeCandidates( self, request_data ):
return [ { 'word': str( completion[ 'CompletionText' ] ), return [ server_responses.BuildCompletionData(
'menu': str( completion[ 'DisplayText' ] ), completion[ 'CompletionText' ],
'info': str( completion[ 'Description' ] ) } completion[ 'DisplayText' ],
for completion in self._GetCompletions() ] completion[ 'Description' ] )
for completion in self._GetCompletions( request_data ) ]
def DefinedSubcommands( self ): def DefinedSubcommands( self ):
@ -76,24 +78,23 @@ class CsharpCompleter( ThreadedCompleter ):
'GoToDefinitionElseDeclaration' ] 'GoToDefinitionElseDeclaration' ]
def OnUserCommand( self, arguments ): def OnUserCommand( self, arguments, request_data ):
if not arguments: if not arguments:
self.EchoUserCommandsHelpMessage() raise ValueError( self.UserCommandsHelpMessage() )
return
command = arguments[ 0 ] command = arguments[ 0 ]
if command == 'StartServer': if command == 'StartServer':
self._StartServer() self._StartServer( request_data )
elif command == 'StopServer': elif command == 'StopServer':
self._StopServer() self._StopServer()
elif command == 'RestartServer': elif command == 'RestartServer':
if self._ServerIsRunning(): if self._ServerIsRunning():
self._StopServer() self._StopServer()
self._StartServer() self._StartServer( request_data )
elif command in [ 'GoToDefinition', elif command in [ 'GoToDefinition',
'GoToDeclaration', 'GoToDeclaration',
'GoToDefinitionElseDeclaration' ]: 'GoToDefinitionElseDeclaration' ]:
self._GoToDefinition() return self._GoToDefinition( request_data )
def DebugInfo( self ): def DebugInfo( self ):
@ -104,35 +105,27 @@ class CsharpCompleter( ThreadedCompleter ):
return 'Server is not running' return 'Server is not running'
def _StartServer( self ): def _StartServer( self, request_data ):
""" Start the OmniSharp server """ """ Start the OmniSharp server """
self._omnisharp_port = self._FindFreePort() self._omnisharp_port = self._FindFreePort()
solutionfiles, folder = _FindSolutionFiles() solutionfiles, folder = _FindSolutionFiles( request_data[ 'filepath' ] )
if len( solutionfiles ) == 0: if len( solutionfiles ) == 0:
vimsupport.PostVimMessage( raise RuntimeError(
'Error starting OmniSharp server: no solutionfile found' ) 'Error starting OmniSharp server: no solutionfile found' )
return
elif len( solutionfiles ) == 1: elif len( solutionfiles ) == 1:
solutionfile = solutionfiles[ 0 ] solutionfile = solutionfiles[ 0 ]
else: else:
choice = vimsupport.PresentDialog( raise RuntimeError(
'Which solutionfile should be loaded?', 'Found multiple solution files instead of one!\n{}'.format(
[ str( i ) + " " + solution for i, solution in solutionfiles ) )
enumerate( solutionfiles ) ] )
if choice == -1:
vimsupport.PostVimMessage( 'OmniSharp not started' )
return
else:
solutionfile = solutionfiles[ choice ]
omnisharp = os.path.join( omnisharp = os.path.join(
os.path.abspath( os.path.dirname( __file__ ) ), os.path.abspath( os.path.dirname( __file__ ) ),
'OmniSharpServer/OmniSharp/bin/Debug/OmniSharp.exe' ) 'OmniSharpServer/OmniSharp/bin/Debug/OmniSharp.exe' )
if not os.path.isfile( omnisharp ): if not os.path.isfile( omnisharp ):
vimsupport.PostVimMessage( SERVER_NOT_FOUND_MSG.format( omnisharp ) ) raise RuntimeError( SERVER_NOT_FOUND_MSG.format( omnisharp ) )
return
if not platform.startswith( 'win' ): if not platform.startswith( 'win' ):
omnisharp = 'mono ' + omnisharp omnisharp = 'mono ' + omnisharp
@ -154,40 +147,44 @@ class CsharpCompleter( ThreadedCompleter ):
with open( self._filename_stdout, 'w' ) as fstdout: with open( self._filename_stdout, 'w' ) as fstdout:
subprocess.Popen( command, stdout=fstdout, stderr=fstderr, shell=True ) subprocess.Popen( command, stdout=fstdout, stderr=fstderr, shell=True )
vimsupport.PostVimMessage( 'Starting OmniSharp server' ) self._logger.info( 'Starting OmniSharp server' )
def _StopServer( self ): def _StopServer( self ):
""" Stop the OmniSharp server """ """ Stop the OmniSharp server """
self._GetResponse( '/stopserver' ) self._GetResponse( '/stopserver' )
self._omnisharp_port = None 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 """ """ Ask server for completions """
completions = self._GetResponse( '/autocomplete', self._DefaultParameters() ) completions = self._GetResponse( '/autocomplete',
self._DefaultParameters( request_data ) )
return completions if completions != None else [] return completions if completions != None else []
def _GoToDefinition( self ): def _GoToDefinition( self, request_data ):
""" Jump to definition of identifier under cursor """ """ 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: if definition[ 'FileName' ] != None:
vimsupport.JumpToLocation( definition[ 'FileName' ], return server_responses.BuildGoToResponse( definition[ 'FileName' ],
definition[ 'Line' ], definition[ 'Line' ],
definition[ 'Column' ] ) definition[ 'Column' ] )
else: 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 """ """ Some very common request parameters """
line, column = vimsupport.CurrentLineAndColumn()
parameters = {} parameters = {}
parameters[ 'line' ], parameters[ 'column' ] = line + 1, column + 1 parameters[ 'line' ] = request_data[ 'line_num' ] + 1
parameters[ 'buffer' ] = '\n'.join( vim.current.buffer ) parameters[ 'column' ] = request_data[ 'column_num' ] + 1
parameters[ 'filename' ] = vim.current.buffer.name filepath = request_data[ 'filepath' ]
parameters[ 'buffer' ] = request_data[ 'file_data' ][ filepath ][
'contents' ]
parameters[ 'filename' ] = filepath
return parameters return parameters
@ -215,20 +212,16 @@ class CsharpCompleter( ThreadedCompleter ):
def _GetResponse( self, endPoint, parameters={}, silent=False, port=None ): def _GetResponse( self, endPoint, parameters={}, silent=False, port=None ):
""" Handle communication with server """ """ Handle communication with server """
# TODO: Replace usage of urllib with Requests
target = urlparse.urljoin( self._PortToHost( port ), endPoint ) target = urlparse.urljoin( self._PortToHost( port ), endPoint )
parameters = urllib.urlencode( parameters ) parameters = urllib.urlencode( parameters )
try:
response = urllib2.urlopen( target, parameters ) response = urllib2.urlopen( target, parameters )
return json.loads( response.read() ) 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
def _FindSolutionFiles(): def _FindSolutionFiles( filepath ):
""" Find solution files by searching upwards in the file tree """ """ 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' ) solutionfiles = glob.glob1( folder, '*.sln' )
while not solutionfiles: while not solutionfiles:
lastfolder = folder lastfolder = folder

View File

@ -16,13 +16,13 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import vim
import os import os
import re import re
from ycm.completers.threaded_completer import ThreadedCompleter from ycm.completers.threaded_completer import ThreadedCompleter
from ycm.completers.cpp.clang_completer import InCFamilyFile from ycm.completers.cpp.clang_completer import InCFamilyFile
from ycm.completers.cpp.flags import Flags from ycm.completers.cpp.flags import Flags
from ycm import server_responses
class FilenameCompleter( ThreadedCompleter ): class FilenameCompleter( ThreadedCompleter ):
""" """
@ -52,23 +52,32 @@ class FilenameCompleter( ThreadedCompleter ):
self._include_regex = re.compile( include_regex_common ) self._include_regex = re.compile( include_regex_common )
def AtIncludeStatementStart( self, start_column ): def AtIncludeStatementStart( self, request_data ):
return ( InCFamilyFile() and 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( 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 return ( start_column and ( current_line[ start_column - 1 ] == '/' or
self.AtIncludeStatementStart( start_column ) ) ) self.AtIncludeStatementStart( request_data ) ) )
def SupportedFiletypes( self ): def SupportedFiletypes( self ):
return [] return []
def ComputeCandidates( self, unused_query, start_column ): def ComputeCandidates( self, request_data ):
line = vim.current.line[ :start_column ] current_line = request_data[ 'line_value' ]
start_column = request_data[ 'start_column' ]
filepath = request_data[ 'filepath' ]
line = current_line[ :start_column ]
if InCFamilyFile(): if InCFamilyFile():
include_match = self._include_regex.search( line ) include_match = self._include_regex.search( line )
@ -78,22 +87,26 @@ class FilenameCompleter( ThreadedCompleter ):
# http://gcc.gnu.org/onlinedocs/cpp/Include-Syntax.html # http://gcc.gnu.org/onlinedocs/cpp/Include-Syntax.html
include_current_file_dir = '<' not in include_match.group() include_current_file_dir = '<' not in include_match.group()
return _GenerateCandidatesForPaths( 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_match = self._path_regex.search( line )
path_dir = os.path.expanduser( path_match.group() ) if path_match else '' path_dir = os.path.expanduser( path_match.group() ) if path_match else ''
return _GenerateCandidatesForPaths( return _GenerateCandidatesForPaths(
_GetPathsStandardCase( path_dir, self.user_options[ _GetPathsStandardCase(
'filepath_completion_use_working_dir' ] ) ) 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 = [] paths = []
include_paths = self._flags.UserIncludePaths( vim.current.buffer.name ) include_paths = self._flags.UserIncludePaths( filepath )
if include_current_file_dir: 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: for include_path in include_paths:
try: try:
@ -107,9 +120,9 @@ class FilenameCompleter( ThreadedCompleter ):
return sorted( set( paths ) ) 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( '/' ): 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 ) path_dir )
try: try:
@ -132,8 +145,8 @@ def _GenerateCandidatesForPaths( absolute_paths ):
seen_basenames.add( basename ) seen_basenames.add( basename )
is_dir = os.path.isdir( absolute_path ) is_dir = os.path.isdir( absolute_path )
completion_dicts.append( { 'word': basename, completion_dicts.append(
'dup': 1, server_responses.BuildCompletionData( basename,
'menu': '[Dir]' if is_dir else '[File]' } ) '[Dir]' if is_dir else '[File]' ) )
return completion_dicts return completion_dicts

View File

@ -58,18 +58,17 @@ class GeneralCompleterStore( Completer ):
return set() return set()
def ShouldUseNow( self, start_column, current_line ): def ShouldUseNow( self, request_data ):
self._current_query_completers = [] 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 ] self._current_query_completers = [ self._filename_completer ]
return True return True
should_use_now = False should_use_now = False
for completer in self._non_filename_completers: for completer in self._non_filename_completers:
should_use_this_completer = completer.ShouldUseNow( start_column, should_use_this_completer = completer.ShouldUseNow( request_data )
current_line )
should_use_now = should_use_now or should_use_this_completer should_use_now = should_use_now or should_use_this_completer
if should_use_this_completer: if should_use_this_completer:
@ -78,9 +77,9 @@ class GeneralCompleterStore( Completer ):
return should_use_now return should_use_now
def CandidatesForQueryAsync( self, query, start_column ): def CandidatesForQueryAsync( self, request_data ):
for completer in self._current_query_completers: for completer in self._current_query_completers:
completer.CandidatesForQueryAsync( query, start_column ) completer.CandidatesForQueryAsync( request_data )
def AsyncCandidateRequestReady( self ): def AsyncCandidateRequestReady( self ):
@ -96,9 +95,9 @@ class GeneralCompleterStore( Completer ):
return candidates return candidates
def OnFileReadyToParse( self ): def OnFileReadyToParse( self, request_data ):
for completer in self._all_completers: for completer in self._all_completers:
completer.OnFileReadyToParse() completer.OnFileReadyToParse( request_data )
def OnBufferVisit( self ): def OnBufferVisit( self ):

View File

@ -20,6 +20,7 @@
from ycm.completers.general_completer import GeneralCompleter from ycm.completers.general_completer import GeneralCompleter
from UltiSnips import UltiSnips_Manager from UltiSnips import UltiSnips_Manager
from ycm import server_responses
class UltiSnipsCompleter( GeneralCompleter ): class UltiSnipsCompleter( GeneralCompleter ):
@ -33,13 +34,13 @@ class UltiSnipsCompleter( GeneralCompleter ):
self._filtered_candidates = None self._filtered_candidates = None
def ShouldUseNowInner( self, start_column, unused_current_line ): def ShouldUseNowInner( self, request_data ):
return self.QueryLengthAboveMinThreshold( start_column ) return self.QueryLengthAboveMinThreshold( request_data )
def CandidatesForQueryAsync( self, query, unused_start_column ): def CandidatesForQueryAsync( self, request_data ):
self._filtered_candidates = self.FilterAndSortCandidates( self._candidates, self._filtered_candidates = self.FilterAndSortCandidates(
query ) self._candidates, request_data[ 'query' ] )
def AsyncCandidateRequestReady( self ): def AsyncCandidateRequestReady( self ):
@ -61,8 +62,9 @@ def _GetCandidates():
# UltiSnips_Manager._snips() returns a class instance where: # UltiSnips_Manager._snips() returns a class instance where:
# class.trigger - name of snippet trigger word ( e.g. defn or testcase ) # class.trigger - name of snippet trigger word ( e.g. defn or testcase )
# class.description - description of the snippet # class.description - description of the snippet
return [ { 'word': str( snip.trigger ), return [ server_responses.BuildCompletionData(
'menu': str( '<snip> ' + snip.description.encode('utf-8') ) } str( snip.trigger ),
str( '<snip> ' + snip.description.encode( 'utf-8' ) ) )
for snip in rawsnips ] for snip in rawsnips ]
except: except:
return [] return []

View File

@ -19,9 +19,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import vim
from ycm.completers.threaded_completer import ThreadedCompleter from ycm.completers.threaded_completer import ThreadedCompleter
from ycm import vimsupport from ycm import server_responses
import sys import sys
from os.path import join, abspath, dirname from os.path import join, abspath, dirname
@ -33,7 +32,7 @@ sys.path.insert( 0, join( abspath( dirname( __file__ ) ), 'jedi' ) )
try: try:
import jedi import jedi
except ImportError: except ImportError:
vimsupport.PostVimMessage( raise ImportError(
'Error importing jedi. Make sure the jedi submodule has been checked out. ' 'Error importing jedi. Make sure the jedi submodule has been checked out. '
'In the YouCompleteMe folder, run "git submodule update --init --recursive"') 'In the YouCompleteMe folder, run "git submodule update --init --recursive"')
sys.path.pop( 0 ) sys.path.pop( 0 )
@ -54,113 +53,108 @@ class JediCompleter( ThreadedCompleter ):
return [ 'python' ] return [ 'python' ]
def _GetJediScript( self ): def _GetJediScript( self, request_data ):
contents = '\n'.join( vim.current.buffer ) filename = request_data[ 'filepath' ]
line, column = vimsupport.CurrentLineAndColumn() contents = request_data[ 'file_data' ][ filename ][ 'contents' ]
# Jedi expects lines to start at 1, not 0 # Jedi expects lines to start at 1, not 0
line += 1 line = request_data[ 'line_num' ] + 1
filename = vim.current.buffer.name column = request_data[ 'column_num' ]
print contents
return jedi.Script( contents, line, column, filename ) return jedi.Script( contents, line, column, filename )
def ComputeCandidates( self, unused_query, unused_start_column ): def ComputeCandidates( self, request_data ):
script = self._GetJediScript() script = self._GetJediScript( request_data )
return [ server_responses.BuildCompletionData( completion.name,
return [ { 'word': str( completion.name ), completion.description,
'menu': str( completion.description ), completion.doc )
'info': str( completion.doc ) }
for completion in script.completions() ] for completion in script.completions() ]
def DefinedSubcommands( self ): def DefinedSubcommands( self ):
return [ "GoToDefinition", return [ "GoToDefinition",
"GoToDeclaration", "GoToDeclaration",
"GoToDefinitionElseDeclaration" ] "GoToDefinitionElseDeclaration" ]
def OnUserCommand( self, arguments ): def OnUserCommand( self, arguments, request_data ):
if not arguments: if not arguments:
self.EchoUserCommandsHelpMessage() raise ValueError( self.UserCommandsHelpMessage() )
return
command = arguments[ 0 ] command = arguments[ 0 ]
if command == 'GoToDefinition': if command == 'GoToDefinition':
self._GoToDefinition() return self._GoToDefinition( request_data )
elif command == 'GoToDeclaration': elif command == 'GoToDeclaration':
self._GoToDeclaration() return self._GoToDeclaration( request_data )
elif command == 'GoToDefinitionElseDeclaration': elif command == 'GoToDefinitionElseDeclaration':
self._GoToDefinitionElseDeclaration() return self._GoToDefinitionElseDeclaration( request_data )
def _GoToDefinition( self ): def _GoToDefinition( self, request_data ):
definitions = self._GetDefinitionsList() definitions = self._GetDefinitionsList( request_data )
if definitions: if definitions:
self._JumpToLocation( definitions ) return self._BuildGoToResponse( definitions )
else: else:
vimsupport.PostVimMessage( 'Can\'t jump to definition.' ) raise RuntimeError( 'Can\'t jump to definition.' )
def _GoToDeclaration( self ): def _GoToDeclaration( self, request_data ):
definitions = self._GetDefinitionsList( declaration = True ) definitions = self._GetDefinitionsList( request_data, declaration = True )
if definitions: if definitions:
self._JumpToLocation( definitions ) return self._BuildGoToResponse( definitions )
else: 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 \ definitions = self._GetDefinitionsList() or \
self._GetDefinitionsList( declaration = True ) self._GetDefinitionsList( request_data, declaration = True )
if definitions: if definitions:
self._JumpToLocation( definitions ) return self._BuildGoToResponse( definitions )
else: 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 = [] definitions = []
script = self._GetJediScript() script = self._GetJediScript( request_data )
try: try:
if declaration: if declaration:
definitions = script.goto_definitions() definitions = script.goto_definitions()
else: else:
definitions = script.goto_assignments() definitions = script.goto_assignments()
except jedi.NotFoundError: except jedi.NotFoundError:
vimsupport.PostVimMessage( raise RuntimeError(
"Cannot follow nothing. Put your cursor on a valid name." ) 'Cannot follow nothing. Put your cursor on a valid name.' )
except Exception as e:
vimsupport.PostVimMessage(
"Caught exception, aborting. Full error: " + str( e ) )
return definitions return definitions
def _JumpToLocation( self, definition_list ): def _BuildGoToResponse( self, definition_list ):
if len( definition_list ) == 1: if len( definition_list ) == 1:
definition = definition_list[ 0 ] definition = definition_list[ 0 ]
if definition.in_builtin_module(): if definition.in_builtin_module():
if definition.is_keyword: if definition.is_keyword:
vimsupport.PostVimMessage( raise RuntimeError(
"Cannot get the definition of Python keywords." ) 'Cannot get the definition of Python keywords.' )
else: else:
vimsupport.PostVimMessage( "Builtin modules cannot be displayed." ) raise RuntimeError( 'Builtin modules cannot be displayed.' )
else: else:
vimsupport.JumpToLocation( definition.module_path, return server_responses.BuildGoToResponse( definition.module_path,
definition.line, definition.line -1,
definition.column + 1 ) definition.column )
else: else:
# multiple definitions # multiple definitions
defs = [] defs = []
for definition in definition_list: for definition in definition_list:
if definition.in_builtin_module(): if definition.in_builtin_module():
defs.append( {'text': 'Builtin ' + \ defs.append( server_responses.BuildDescriptionOnlyGoToResponse(
definition.description.encode( 'utf-8' ) } ) 'Builting ' + definition.description ) )
else: else:
defs.append( {'filename': definition.module_path.encode( 'utf-8' ), defs.append(
'lnum': definition.line, server_responses.BuildGoToResponse( definition.module_path,
'col': definition.column + 1, definition.line -1,
'text': definition.description.encode( 'utf-8' ) } ) definition.column,
definition.description ) )
return defs
vim.eval( 'setqflist( %s )' % repr( defs ) )
vim.eval( 'youcompleteme#OpenGoToList()' )

View File

@ -52,11 +52,10 @@ class ThreadedCompleter( Completer ):
self._completion_thread.start() self._completion_thread.start()
def CandidatesForQueryAsyncInner( self, query, start_column ): def CandidatesForQueryAsyncInner( self, request_data ):
self._candidates = None self._candidates = None
self._candidates_ready.clear() self._candidates_ready.clear()
self._query = query self._request_data = request_data
self._start_column = start_column
self._query_ready.set() self._query_ready.set()
@ -69,7 +68,7 @@ class ThreadedCompleter( Completer ):
@abc.abstractmethod @abc.abstractmethod
def ComputeCandidates( self, query, start_column ): def ComputeCandidates( self, request_data ):
"""This function should compute the candidates to show to the user. """This function should compute the candidates to show to the user.
The return value should be of the same type as that for The return value should be of the same type as that for
CandidatesFromStoredRequest().""" CandidatesFromStoredRequest()."""
@ -80,8 +79,7 @@ class ThreadedCompleter( Completer ):
while True: while True:
try: try:
WaitAndClearIfSet( self._query_ready ) WaitAndClearIfSet( self._query_ready )
self._candidates = self.ComputeCandidates( self._query, self._candidates = self.ComputeCandidates( self._request_data )
self._start_column )
except: except:
self._query_ready.clear() self._query_ready.clear()
self._candidates = [] self._candidates = []

View File

@ -68,6 +68,7 @@ def CallExtraConfVimCloseIfExists():
def _CallExtraConfMethod( function_name ): def _CallExtraConfMethod( function_name ):
vim_current_working_directory = vim.eval( 'getcwd()' ) vim_current_working_directory = vim.eval( 'getcwd()' )
path_to_dummy = os.path.join( vim_current_working_directory, 'DUMMY_FILE' ) 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 ) module = ModuleForSourceFile( path_to_dummy )
if not module or not hasattr( module, function_name ): if not module or not hasattr( module, function_name ):
return return

View File

@ -0,0 +1,76 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
#
# 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 <http://www.gnu.org/licenses/>.
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
}

View File

@ -46,12 +46,33 @@ def TextAfterCursor():
return vim.current.line[ CurrentColumn(): ] return vim.current.line[ CurrentColumn(): ]
def GetUnsavedBuffers(): # Note the difference between buffer OPTIONS and VARIABLES; the two are not
def BufferModified( buffer_number ): # the same.
to_eval = 'getbufvar({0}, "&mod")'.format( buffer_number ) def GetBufferOption( buffer_object, option ):
return GetBoolValue( to_eval ) # 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 # Both |line| and |column| need to be 1-based
@ -73,9 +94,9 @@ def JumpToLocation( filename, line, column ):
vim.command( 'normal! zz' ) 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 # 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 ): def PostVimMessage( message ):
@ -128,15 +149,13 @@ def EscapeForVim( text ):
def CurrentFiletypes(): def CurrentFiletypes():
ft_string = vim.eval( "&filetype" ) return vim.eval( "&filetype" ).split( '.' )
return ft_string.split( '.' )
def FiletypesForBuffer( buffer_object ): def FiletypesForBuffer( buffer_object ):
# NOTE: Getting &ft for other buffers only works when the buffer has been # 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 # 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 GetBufferOption( buffer_object, 'ft' ).split( '.' )
return ft_string.split( '.' )
def GetVariableValue( variable ): def GetVariableValue( variable ):

View File

@ -19,20 +19,25 @@
import imp import imp
import os import os
import time
import vim import vim
import ycm_core import ycm_core
import logging
import tempfile
from ycm import vimsupport from ycm import vimsupport
from ycm import base from ycm import base
from ycm.completers.all.omni_completer import OmniCompleter from ycm.completers.all.omni_completer import OmniCompleter
from ycm.completers.general.general_completer_store import GeneralCompleterStore from ycm.completers.general.general_completer_store import GeneralCompleterStore
# TODO: Put the Request classes in separate files
class CompletionRequest( object ): class CompletionRequest( object ):
def __init__( self, ycm_state ): def __init__( self, ycm_state ):
self._completion_start_column = base.CompletionStartColumn() self._completion_start_column = base.CompletionStartColumn()
self._ycm_state = ycm_state self._ycm_state = ycm_state
self._request_data = _BuildRequestData( self._completion_start_column )
self._do_filetype_completion = self._ycm_state.ShouldUseFiletypeCompleter( self._do_filetype_completion = self._ycm_state.ShouldUseFiletypeCompleter(
self._completion_start_column ) self._request_data )
self._completer = ( self._ycm_state.GetFiletypeCompleter() if self._completer = ( self._ycm_state.GetFiletypeCompleter() if
self._do_filetype_completion else self._do_filetype_completion else
self._ycm_state.GetGeneralCompleter() ) self._ycm_state.GetGeneralCompleter() )
@ -40,8 +45,7 @@ class CompletionRequest( object ):
def ShouldComplete( self ): def ShouldComplete( self ):
return ( self._do_filetype_completion or return ( self._do_filetype_completion or
self._ycm_state.ShouldUseGeneralCompleter( self._ycm_state.ShouldUseGeneralCompleter( self._request_data ) )
self._completion_start_column ) )
def CompletionStartColumn( self ): def CompletionStartColumn( self ):
@ -49,20 +53,80 @@ class CompletionRequest( object ):
def Start( self, query ): def Start( self, query ):
self._completer.CandidatesForQueryAsync( query, self._request_data[ 'query' ] = query
self._completion_start_column ) self._completer.CandidatesForQueryAsync( self._request_data )
def Done( self ): def Done( self ):
return self._completer.AsyncCandidateRequestReady() return self._completer.AsyncCandidateRequestReady()
def Results( self ): 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 ): class YouCompleteMe( object ):
def __init__( self, user_options ): 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._user_options = user_options
self._gencomp = GeneralCompleterStore( user_options ) self._gencomp = GeneralCompleterStore( user_options )
self._omnicomp = OmniCompleter( user_options ) self._omnicomp = OmniCompleter( user_options )
@ -78,6 +142,16 @@ class YouCompleteMe( object ):
return self._current_completion_request 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 ): def GetCurrentCompletionRequest( self ):
return self._current_completion_request return self._current_completion_request
@ -131,14 +205,13 @@ class YouCompleteMe( object ):
return completer return completer
def ShouldUseGeneralCompleter( self, start_column ): def ShouldUseGeneralCompleter( self, request_data ):
return self._gencomp.ShouldUseNow( start_column, vim.current.line ) return self._gencomp.ShouldUseNow( request_data )
def ShouldUseFiletypeCompleter( self, start_column ): def ShouldUseFiletypeCompleter( self, request_data ):
if self.FiletypeCompletionUsable(): if self.FiletypeCompletionUsable():
return self.GetFiletypeCompleter().ShouldUseNow( return self.GetFiletypeCompleter().ShouldUseNow( request_data )
start_column, vim.current.line )
return False return False
@ -162,10 +235,10 @@ class YouCompleteMe( object ):
def OnFileReadyToParse( self ): def OnFileReadyToParse( self ):
self._gencomp.OnFileReadyToParse() self._gencomp.OnFileReadyToParse( _BuildRequestData() )
if self.FiletypeCompletionUsable(): if self.FiletypeCompletionUsable():
self.GetFiletypeCompleter().OnFileReadyToParse() self.GetFiletypeCompleter().OnFileReadyToParse( _BuildRequestData() )
def OnBufferUnload( self, deleted_buffer_file ): def OnBufferUnload( self, deleted_buffer_file ):
@ -208,9 +281,9 @@ class YouCompleteMe( object ):
return [] return []
def ShowDetailedDiagnostic( self ): def GetDetailedDiagnostic( self ):
if self.FiletypeCompletionUsable(): if self.FiletypeCompletionUsable():
return self.GetFiletypeCompleter().ShowDetailedDiagnostic() return self.GetFiletypeCompleter().GetDetailedDiagnostic()
def GettingCompletions( self ): def GettingCompletions( self ):
@ -263,3 +336,37 @@ def _PathToFiletypeCompleterPluginLoader( filetype ):
return os.path.join( _PathToCompletersFolder(), filetype, 'hook.py' ) 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