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
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

View File

@ -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 );
}

View File

@ -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:

View File

@ -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', {} )

View File

@ -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 ):

View File

@ -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' )

View File

@ -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

View File

@ -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 ) )

View File

@ -18,14 +18,23 @@
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
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 )

View File

@ -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 )

View File

@ -18,18 +18,18 @@
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
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

View File

@ -16,13 +16,13 @@
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
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

View File

@ -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 ):

View File

@ -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> ' + snip.description.encode('utf-8') ) }
for snip in rawsnips ]
return [ server_responses.BuildCompletionData(
str( snip.trigger ),
str( '<snip> ' + snip.description.encode( 'utf-8' ) ) )
for snip in rawsnips ]
except:
return []

View File

@ -19,9 +19,8 @@
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
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()' )

View File

@ -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 = []

View File

@ -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

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(): ]
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 ):

View File

@ -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