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