Support for additional FixIts on java completions

Java completer can include FixIts which are applied when a completion
entry is selected. We use the existing mechanism implemented for c-sharp
to perform these edits using the CompleteDone autocommand.

However, the existing mechanism relies on pattern matching the source to
work out which item was completed. Vim patch 8.0.1493 introduces support
for user_data on completion items, so when available we populate it with
the completion array index of the item and use that to get the exact
element that was selected. This is both a lot faster and a lot more
accirate.

Of course when applying these 'FixIts' we don't interrupt the user with
confirmation or the quickfix list as this would just be annoying. If the
server reports that an edit must be made, we just make the edit. This is
achieved by adding a silent flag to ReplaceChunks.
This commit is contained in:
Ben Jackson 2018-02-10 23:48:22 +00:00
parent 39fe6d1f86
commit 7989b7b0fd
8 changed files with 514 additions and 154 deletions

View File

@ -826,7 +826,7 @@ Quick Feature Summary
**NOTE**: Java support is currently experimental. Please let us know your **NOTE**: Java support is currently experimental. Please let us know your
[feedback](#contact). [feedback](#contact).
* Semantic auto-completion * Semantic auto-completion with automatic import insertion
* Go to definition (`GoTo`, `GoToDefinition`, and `GoToDeclaration` are * Go to definition (`GoTo`, `GoToDefinition`, and `GoToDeclaration` are
identical) identical)
* Reference finding (`GoToReferences`) * Reference finding (`GoToReferences`)
@ -834,7 +834,7 @@ Quick Feature Summary
* Renaming symbols (`RefactorRename <new name>`) * Renaming symbols (`RefactorRename <new name>`)
* View documentation comments for identifiers (`GetDoc`) * View documentation comments for identifiers (`GetDoc`)
* Type information for identifiers (`GetType`) * Type information for identifiers (`GetType`)
* Automatically fix certain errors (`FixIt`) * Automatically fix certain errors including code generation (`FixIt`)
* Detection of java projects * Detection of java projects
* Management of `jdt.ls` server instance * Management of `jdt.ls` server instance
@ -1185,6 +1185,9 @@ package you have in the virtual environment.
4. Edit a Java file from your project. 4. Edit a Java file from your project.
For the best experience, we highly recommend at least Vim 8.0.1493 when using
Java support with YouCompleteMe.
#### Java Project Files #### Java Project Files
In order to provide semantic analysis, the Java completion engine requires In order to provide semantic analysis, the Java completion engine requires

View File

@ -1074,7 +1074,7 @@ Java ~
**NOTE**: Java support is currently experimental. Please let us know your **NOTE**: Java support is currently experimental. Please let us know your
feedback. feedback.
- Semantic auto-completion - Semantic auto-completion with automatic import insertion
- Go to definition (|GoTo|, |GoToDefinition|, and |GoToDeclaration| are - Go to definition (|GoTo|, |GoToDefinition|, and |GoToDeclaration| are
identical) identical)
- Reference finding (|GoToReferences|) - Reference finding (|GoToReferences|)
@ -1082,7 +1082,7 @@ feedback.
- Renaming symbols ('RefactorRename <new name>') - Renaming symbols ('RefactorRename <new name>')
- View documentation comments for identifiers (|GetDoc|) - View documentation comments for identifiers (|GetDoc|)
- Type information for identifiers (|GetType|) - Type information for identifiers (|GetType|)
- Automatically fix certain errors (|FixIt|) - Automatically fix certain errors including code generation (|FixIt|)
- Detection of java projects - Detection of java projects
- Management of 'jdt.ls' server instance - Management of 'jdt.ls' server instance
@ -1454,6 +1454,9 @@ Java quick Start ~
4. Edit a Java file from your project. 4. Edit a Java file from your project.
For the best experience, we highly recommend at least Vim 8.0.1493 when using
Java support with YouCompleteMe.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
*youcompleteme-java-project-files* *youcompleteme-java-project-files*
Java Project Files ~ Java Project Files ~

View File

@ -69,7 +69,7 @@ class CompletionRequest( BaseRequest ):
return response return response
def ConvertCompletionDataToVimData( completion_data ): def ConvertCompletionDataToVimData( completion_identifier, completion_data ):
# see :h complete-items for a description of the dictionary fields # see :h complete-items for a description of the dictionary fields
vim_data = { vim_data = {
'word' : '', 'word' : '',
@ -100,9 +100,19 @@ def ConvertCompletionDataToVimData( completion_data ):
elif doc_string: elif doc_string:
vim_data[ 'info' ] = doc_string vim_data[ 'info' ] = doc_string
# We store the completion item index as a string in the completion user_data.
# This allows us to identify the _exact_ item that was completed in the
# CompleteDone handler, by inspecting this item from v:completed_item
#
# We convert to string because completion user data items must be strings.
#
# Note: Not all versions of Vim support this (added in 8.0.1483), but adding
# the item to the dictionary is harmless in earlier Vims.
vim_data[ 'user_data' ] = str( completion_identifier )
return vim_data return vim_data
def _ConvertCompletionDatasToVimDatas( response_data ): def _ConvertCompletionDatasToVimDatas( response_data ):
return [ ConvertCompletionDataToVimData( x ) return [ ConvertCompletionDataToVimData( i, x )
for x in response_data ] for i, x in enumerate( response_data ) ]

View File

@ -33,8 +33,9 @@ class ConvertCompletionResponseToVimDatas_test( object ):
""" This class tests the """ This class tests the
completion_request._ConvertCompletionResponseToVimDatas method """ completion_request._ConvertCompletionResponseToVimDatas method """
def _Check( self, completion_data, expected_vim_data ): def _Check( self, completion_id, completion_data, expected_vim_data ):
vim_data = completion_request.ConvertCompletionDataToVimData( vim_data = completion_request.ConvertCompletionDataToVimData(
completion_id,
completion_data ) completion_data )
try: try:
@ -48,7 +49,7 @@ class ConvertCompletionResponseToVimDatas_test( object ):
def All_Fields_test( self ): def All_Fields_test( self ):
self._Check( { self._Check( 0, {
'insertion_text': 'INSERTION TEXT', 'insertion_text': 'INSERTION TEXT',
'menu_text': 'MENU TEXT', 'menu_text': 'MENU TEXT',
'extra_menu_info': 'EXTRA MENU INFO', 'extra_menu_info': 'EXTRA MENU INFO',
@ -65,11 +66,12 @@ class ConvertCompletionResponseToVimDatas_test( object ):
'info' : 'DETAILED INFO\nDOC STRING', 'info' : 'DETAILED INFO\nDOC STRING',
'dup' : 1, 'dup' : 1,
'empty' : 1, 'empty' : 1,
'user_data': '0',
} ) } )
def Just_Detailed_Info_test( self ): def Just_Detailed_Info_test( self ):
self._Check( { self._Check( 9999999999, {
'insertion_text': 'INSERTION TEXT', 'insertion_text': 'INSERTION TEXT',
'menu_text': 'MENU TEXT', 'menu_text': 'MENU TEXT',
'extra_menu_info': 'EXTRA MENU INFO', 'extra_menu_info': 'EXTRA MENU INFO',
@ -83,11 +85,12 @@ class ConvertCompletionResponseToVimDatas_test( object ):
'info' : 'DETAILED INFO', 'info' : 'DETAILED INFO',
'dup' : 1, 'dup' : 1,
'empty' : 1, 'empty' : 1,
'user_data': '9999999999',
} ) } )
def Just_Doc_String_test( self ): def Just_Doc_String_test( self ):
self._Check( { self._Check( 'not_an_int', {
'insertion_text': 'INSERTION TEXT', 'insertion_text': 'INSERTION TEXT',
'menu_text': 'MENU TEXT', 'menu_text': 'MENU TEXT',
'extra_menu_info': 'EXTRA MENU INFO', 'extra_menu_info': 'EXTRA MENU INFO',
@ -103,11 +106,12 @@ class ConvertCompletionResponseToVimDatas_test( object ):
'info' : 'DOC STRING', 'info' : 'DOC STRING',
'dup' : 1, 'dup' : 1,
'empty' : 1, 'empty' : 1,
'user_data': 'not_an_int',
} ) } )
def Extra_Info_No_Doc_String_test( self ): def Extra_Info_No_Doc_String_test( self ):
self._Check( { self._Check( 0, {
'insertion_text': 'INSERTION TEXT', 'insertion_text': 'INSERTION TEXT',
'menu_text': 'MENU TEXT', 'menu_text': 'MENU TEXT',
'extra_menu_info': 'EXTRA MENU INFO', 'extra_menu_info': 'EXTRA MENU INFO',
@ -121,11 +125,12 @@ class ConvertCompletionResponseToVimDatas_test( object ):
'kind' : 'k', 'kind' : 'k',
'dup' : 1, 'dup' : 1,
'empty' : 1, 'empty' : 1,
'user_data': '0',
} ) } )
def Extra_Info_No_Doc_String_With_Detailed_Info_test( self ): def Extra_Info_No_Doc_String_With_Detailed_Info_test( self ):
self._Check( { self._Check( '0', {
'insertion_text': 'INSERTION TEXT', 'insertion_text': 'INSERTION TEXT',
'menu_text': 'MENU TEXT', 'menu_text': 'MENU TEXT',
'extra_menu_info': 'EXTRA MENU INFO', 'extra_menu_info': 'EXTRA MENU INFO',
@ -141,11 +146,12 @@ class ConvertCompletionResponseToVimDatas_test( object ):
'info' : 'DETAILED INFO', 'info' : 'DETAILED INFO',
'dup' : 1, 'dup' : 1,
'empty' : 1, 'empty' : 1,
'user_data': '0',
} ) } )
def Empty_Insertion_Text_test( self ): def Empty_Insertion_Text_test( self ):
self._Check( { self._Check( 0, {
'insertion_text': '', 'insertion_text': '',
'menu_text': 'MENU TEXT', 'menu_text': 'MENU TEXT',
'extra_menu_info': 'EXTRA MENU INFO', 'extra_menu_info': 'EXTRA MENU INFO',
@ -162,11 +168,12 @@ class ConvertCompletionResponseToVimDatas_test( object ):
'info' : 'DETAILED INFO\nDOC STRING', 'info' : 'DETAILED INFO\nDOC STRING',
'dup' : 1, 'dup' : 1,
'empty' : 1, 'empty' : 1,
'user_data': '0',
} ) } )
def No_Insertion_Text_test( self ): def No_Insertion_Text_test( self ):
self._Check( { self._Check( 0, {
'menu_text': 'MENU TEXT', 'menu_text': 'MENU TEXT',
'extra_menu_info': 'EXTRA MENU INFO', 'extra_menu_info': 'EXTRA MENU INFO',
'kind': 'K', 'kind': 'K',
@ -182,4 +189,5 @@ class ConvertCompletionResponseToVimDatas_test( object ):
'info' : 'DETAILED INFO\nDOC STRING', 'info' : 'DETAILED INFO\nDOC STRING',
'dup' : 1, 'dup' : 1,
'empty' : 1, 'empty' : 1,
'user_data': '0'
} ) } )

View File

@ -36,27 +36,43 @@ from ycm import vimsupport
from ycm.tests import YouCompleteMeInstance from ycm.tests import YouCompleteMeInstance
from ycmd.utils import ToBytes from ycmd.utils import ToBytes
from ycm.youcompleteme import _CompleteDoneHook_CSharp
from ycm.youcompleteme import _CompleteDoneHook_Java
def GetVariableValue_CompleteItemIs( word, abbr = None, menu = None,
info = None, kind = None ): def CompleteItemIs( word, abbr = None, menu = None,
def Result( variable ): info = None, kind = None, **kwargs ):
if variable == 'v:completed_item': item = {
return {
'word': ToBytes( word ), 'word': ToBytes( word ),
'abbr': ToBytes( abbr ), 'abbr': ToBytes( abbr ),
'menu': ToBytes( menu ), 'menu': ToBytes( menu ),
'info': ToBytes( info ), 'info': ToBytes( info ),
'kind': ToBytes( kind ), 'kind': ToBytes( kind ),
} }
item.update( **kwargs )
return item
def GetVariableValue_CompleteItemIs( word, abbr = None, menu = None,
info = None, kind = None, **kwargs ):
def Result( variable ):
if variable == 'v:completed_item':
return CompleteItemIs( word, abbr, menu, info, kind, **kwargs )
return DEFAULT return DEFAULT
return MagicMock( side_effect = Result ) return MagicMock( side_effect = Result )
def BuildCompletion( namespace = None, insertion_text = 'Test', def BuildCompletion( insertion_text = 'Test',
menu_text = None, extra_menu_info = None, menu_text = None,
detailed_info = None, kind = None ): extra_menu_info = None,
detailed_info = None,
kind = None,
extra_data = None ):
if extra_data is None:
extra_data = {}
return { return {
'extra_data': { 'required_namespace_import': namespace }, 'extra_data': extra_data,
'insertion_text': insertion_text, 'insertion_text': insertion_text,
'menu_text': menu_text, 'menu_text': menu_text,
'extra_menu_info': extra_menu_info, 'extra_menu_info': extra_menu_info,
@ -65,13 +81,53 @@ def BuildCompletion( namespace = None, insertion_text = 'Test',
} }
def BuildCompletionNamespace( namespace = None,
insertion_text = 'Test',
menu_text = None,
extra_menu_info = None,
detailed_info = None,
kind = None ):
return BuildCompletion( insertion_text = insertion_text,
menu_text = menu_text,
extra_menu_info = extra_menu_info,
detailed_info = detailed_info,
kind = kind,
extra_data = {
'required_namespace_import': namespace
} )
def BuildCompletionFixIt( fixits,
insertion_text = 'Test',
menu_text = None,
extra_menu_info = None,
detailed_info = None,
kind = None ):
return BuildCompletion( insertion_text = insertion_text,
menu_text = menu_text,
extra_menu_info = extra_menu_info,
detailed_info = detailed_info,
kind = kind,
extra_data = {
'fixits': fixits,
} )
@contextlib.contextmanager @contextlib.contextmanager
def _SetupForCsharpCompletionDone( ycm, completions ): def _SetupForCsharpCompletionDone( ycm, completions ):
with patch( 'ycm.vimsupport.InsertNamespace' ): with patch( 'ycm.vimsupport.InsertNamespace' ):
with _SetUpCompleteDone( ycm, completions ):
yield
@contextlib.contextmanager
def _SetUpCompleteDone( ycm, completions ):
with patch( 'ycm.vimsupport.TextBeforeCursor', return_value = ' Test' ): with patch( 'ycm.vimsupport.TextBeforeCursor', return_value = ' Test' ):
request = MagicMock() request = MagicMock()
request.Done = MagicMock( return_value = True ) request.Done = MagicMock( return_value = True )
request.RawResponse = MagicMock( return_value = completions ) request.RawResponse = MagicMock( return_value = {
'completions': completions
} )
ycm._latest_completion_request = request ycm._latest_completion_request = request
yield yield
@ -79,127 +135,137 @@ def _SetupForCsharpCompletionDone( ycm, completions ):
@patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ 'cs' ] ) @patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ 'cs' ] )
@YouCompleteMeInstance() @YouCompleteMeInstance()
def GetCompleteDoneHooks_ResultOnCsharp_test( ycm, *args ): def GetCompleteDoneHooks_ResultOnCsharp_test( ycm, *args ):
result = ycm.GetCompleteDoneHooks() result = list( ycm.GetCompleteDoneHooks() )
eq_( 1, len( list( result ) ) ) eq_( [ _CompleteDoneHook_CSharp ], result )
@patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ 'txt' ] ) @patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ 'java' ] )
@YouCompleteMeInstance()
def GetCompleteDoneHooks_ResultOnJava_test( ycm, *args ):
result = list( ycm.GetCompleteDoneHooks() )
eq_( [ _CompleteDoneHook_Java ], result )
@patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ 'ycmtest' ] )
@YouCompleteMeInstance() @YouCompleteMeInstance()
def GetCompleteDoneHooks_EmptyOnOtherFiletype_test( ycm, *args ): def GetCompleteDoneHooks_EmptyOnOtherFiletype_test( ycm, *args ):
result = ycm.GetCompleteDoneHooks() result = ycm.GetCompleteDoneHooks()
eq_( 0, len( list( result ) ) ) eq_( 0, len( list( result ) ) )
@patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ 'txt' ] ) @patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ 'ycmtest' ] )
@YouCompleteMeInstance() @YouCompleteMeInstance()
def OnCompleteDone_WithActionCallsIt_test( ycm, *args ): def OnCompleteDone_WithActionCallsIt_test( ycm, *args ):
action = MagicMock() action = MagicMock()
ycm._complete_done_hooks[ 'txt' ] = action ycm._complete_done_hooks[ 'ycmtest' ] = action
ycm.OnCompleteDone() ycm.OnCompleteDone()
ok_( action.called ) ok_( action.called )
@patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ 'txt' ] ) @patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ 'ycmtest' ] )
@YouCompleteMeInstance() @YouCompleteMeInstance()
def OnCompleteDone_NoActionNoError_test( ycm, *args ): def OnCompleteDone_NoActionNoError_test( ycm, *args ):
with patch.object( ycm, '_OnCompleteDone_Csharp' ) as csharp:
with patch.object( ycm, '_OnCompleteDone_Java' ) as java:
ycm.OnCompleteDone() ycm.OnCompleteDone()
csharp.assert_not_called()
java.assert_not_called()
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'Test' ) )
@YouCompleteMeInstance() @YouCompleteMeInstance()
def FilterToCompletedCompletions_MatchIsReturned_test( ycm, *args ): def FilterToCompletedCompletions_MatchIsReturned_test( ycm, *args ):
completions = [ BuildCompletion( insertion_text = 'Test' ) ] completions = [ BuildCompletion( insertion_text = 'Test' ) ]
result = ycm._FilterToMatchingCompletions( completions, False ) result = ycm._FilterToMatchingCompletions( CompleteItemIs( 'Test' ),
completions,
False )
eq_( list( result ), completions ) eq_( list( result ), completions )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'A' ) )
@YouCompleteMeInstance() @YouCompleteMeInstance()
def FilterToCompletedCompletions_ShortTextDoesntRaise_test( ycm, *args ): def FilterToCompletedCompletions_ShortTextDoesntRaise_test( ycm, *args ):
completions = [ BuildCompletion( insertion_text = 'AAA' ) ] completions = [ BuildCompletion( insertion_text = 'AAA' ) ]
ycm._FilterToMatchingCompletions( completions, False ) ycm._FilterToMatchingCompletions( CompleteItemIs( 'A' ),
completions,
False )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'Test' ) )
@YouCompleteMeInstance() @YouCompleteMeInstance()
def FilterToCompletedCompletions_ExactMatchIsReturned_test( ycm, *args ): def FilterToCompletedCompletions_ExactMatchIsReturned_test( ycm, *args ):
completions = [ BuildCompletion( insertion_text = 'Test' ) ] completions = [ BuildCompletion( insertion_text = 'Test' ) ]
result = ycm._FilterToMatchingCompletions( completions, False ) result = ycm._FilterToMatchingCompletions( CompleteItemIs( 'Test' ),
completions,
False )
eq_( list( result ), completions ) eq_( list( result ), completions )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( ' Quote' ) )
@YouCompleteMeInstance() @YouCompleteMeInstance()
def FilterToCompletedCompletions_NonMatchIsntReturned_test( ycm, *args ): def FilterToCompletedCompletions_NonMatchIsntReturned_test( ycm, *args ):
completions = [ BuildCompletion( insertion_text = 'A' ) ] completions = [ BuildCompletion( insertion_text = 'A' ) ]
result = ycm._FilterToMatchingCompletions( completions, False ) result = ycm._FilterToMatchingCompletions( CompleteItemIs( ' Quote' ),
completions,
False )
assert_that( list( result ), empty() ) assert_that( list( result ), empty() )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( '†es†' ) )
@YouCompleteMeInstance() @YouCompleteMeInstance()
def FilterToCompletedCompletions_Unicode_test( ycm, *args ): def FilterToCompletedCompletions_Unicode_test( ycm, *args ):
completions = [ BuildCompletion( insertion_text = '†es†' ) ] completions = [ BuildCompletion( insertion_text = '†es†' ) ]
result = ycm._FilterToMatchingCompletions( completions, False ) result = ycm._FilterToMatchingCompletions( CompleteItemIs( '†es†' ),
completions,
False )
eq_( list( result ), completions ) eq_( list( result ), completions )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'Te' ) )
@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = ' Quote' ) @patch( 'ycm.vimsupport.TextBeforeCursor', return_value = ' Quote' )
@YouCompleteMeInstance() @YouCompleteMeInstance()
def HasCompletionsThatCouldBeCompletedWithMoreText_MatchIsReturned_test( def HasCompletionsThatCouldBeCompletedWithMoreText_MatchIsReturned_test(
ycm, *args ): ycm, *args ):
completions = [ BuildCompletion( insertion_text = 'Test' ) ] completions = [ BuildCompletion( insertion_text = 'Test' ) ]
result = ycm._HasCompletionsThatCouldBeCompletedWithMoreText( completions ) result = ycm._HasCompletionsThatCouldBeCompletedWithMoreText(
CompleteItemIs( 'Te' ),
completions )
eq_( result, True ) eq_( result, True )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'X' ) )
@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = ' Quote' ) @patch( 'ycm.vimsupport.TextBeforeCursor', return_value = ' Quote' )
@YouCompleteMeInstance() @YouCompleteMeInstance()
def HasCompletionsThatCouldBeCompletedWithMoreText_ShortTextDoesntRaise_test( def HasCompletionsThatCouldBeCompletedWithMoreText_ShortTextDoesntRaise_test(
ycm, *args ): ycm, *args ):
completions = [ BuildCompletion( insertion_text = 'AAA' ) ] completions = [ BuildCompletion( insertion_text = 'AAA' ) ]
ycm._HasCompletionsThatCouldBeCompletedWithMoreText( completions ) ycm._HasCompletionsThatCouldBeCompletedWithMoreText( CompleteItemIs( 'X' ),
completions )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'Test' ) )
@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = ' Quote' ) @patch( 'ycm.vimsupport.TextBeforeCursor', return_value = ' Quote' )
@YouCompleteMeInstance() @YouCompleteMeInstance()
def HasCompletionsThatCouldBeCompletedWithMoreText_ExactMatchIsntReturned_test( def HasCompletionsThatCouldBeCompletedWithMoreText_ExactMatchIsntReturned_test(
ycm, *args ): ycm, *args ):
completions = [ BuildCompletion( insertion_text = 'Test' ) ] completions = [ BuildCompletion( insertion_text = 'Test' ) ]
result = ycm._HasCompletionsThatCouldBeCompletedWithMoreText( completions ) result = ycm._HasCompletionsThatCouldBeCompletedWithMoreText(
CompleteItemIs( 'Test' ),
completions )
eq_( result, False ) eq_( result, False )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( ' Quote' ) )
@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = ' Quote' ) @patch( 'ycm.vimsupport.TextBeforeCursor', return_value = ' Quote' )
@YouCompleteMeInstance() @YouCompleteMeInstance()
def HasCompletionsThatCouldBeCompletedWithMoreText_NonMatchIsntReturned_test( def HasCompletionsThatCouldBeCompletedWithMoreText_NonMatchIsntReturned_test(
ycm, *args ): ycm, *args ):
completions = [ BuildCompletion( insertion_text = "A" ) ] completions = [ BuildCompletion( insertion_text = "A" ) ]
result = ycm._HasCompletionsThatCouldBeCompletedWithMoreText( completions ) result = ycm._HasCompletionsThatCouldBeCompletedWithMoreText(
CompleteItemIs( ' Quote' ),
completions )
eq_( result, False ) eq_( result, False )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'Uniç' ) )
@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = 'Uniç' ) @patch( 'ycm.vimsupport.TextBeforeCursor', return_value = 'Uniç' )
@YouCompleteMeInstance() @YouCompleteMeInstance()
def HasCompletionsThatCouldBeCompletedWithMoreText_Unicode_test( def HasCompletionsThatCouldBeCompletedWithMoreText_Unicode_test(
ycm, *args ): ycm, *args ):
completions = [ BuildCompletion( insertion_text = 'Uniçø∂¢' ) ] completions = [ BuildCompletion( insertion_text = 'Uniçø∂¢' ) ]
result = ycm._HasCompletionsThatCouldBeCompletedWithMoreText( completions ) result = ycm._HasCompletionsThatCouldBeCompletedWithMoreText(
CompleteItemIs( 'Uniç' ),
completions )
eq_( result, True ) eq_( result, True )
@ -212,7 +278,7 @@ def GetRequiredNamespaceImport_ReturnNoneForNoExtraData_test( ycm ):
def GetRequiredNamespaceImport_ReturnNamespaceFromExtraData_test( ycm ): def GetRequiredNamespaceImport_ReturnNamespaceFromExtraData_test( ycm ):
namespace = 'A_NAMESPACE' namespace = 'A_NAMESPACE'
eq_( namespace, ycm._GetRequiredNamespaceImport( eq_( namespace, ycm._GetRequiredNamespaceImport(
BuildCompletion( namespace ) BuildCompletionNamespace( namespace )
) ) ) )
@ -228,7 +294,7 @@ def GetCompletionsUserMayHaveCompleted_ReturnEmptyIfNotDone_test( ycm ):
@YouCompleteMeInstance() @YouCompleteMeInstance()
def GetCompletionsUserMayHaveCompleted_ReturnEmptyIfPendingMatches_test( def GetCompletionsUserMayHaveCompleted_ReturnEmptyIfPendingMatches_test(
ycm, *args ): ycm, *args ):
completions = [ BuildCompletion( None ) ] completions = [ BuildCompletionNamespace( None ) ]
with _SetupForCsharpCompletionDone( ycm, completions ): with _SetupForCsharpCompletionDone( ycm, completions ):
eq_( [], ycm.GetCompletionsUserMayHaveCompleted() ) eq_( [], ycm.GetCompletionsUserMayHaveCompleted() )
@ -237,7 +303,7 @@ def GetCompletionsUserMayHaveCompleted_ReturnEmptyIfPendingMatches_test(
def GetCompletionsUserMayHaveCompleted_ReturnMatchIfExactMatches_test( def GetCompletionsUserMayHaveCompleted_ReturnMatchIfExactMatches_test(
ycm, *args ): ycm, *args ):
info = [ 'NS', 'Test', 'Abbr', 'Menu', 'Info', 'Kind' ] info = [ 'NS', 'Test', 'Abbr', 'Menu', 'Info', 'Kind' ]
completions = [ BuildCompletion( *info ) ] completions = [ BuildCompletionNamespace( *info ) ]
with _SetupForCsharpCompletionDone( ycm, completions ): with _SetupForCsharpCompletionDone( ycm, completions ):
with patch( 'ycm.vimsupport.GetVariableValue', with patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( *info[ 1: ] ) ): GetVariableValue_CompleteItemIs( *info[ 1: ] ) ):
@ -248,7 +314,7 @@ def GetCompletionsUserMayHaveCompleted_ReturnMatchIfExactMatches_test(
def GetCompletionsUserMayHaveCompleted_ReturnMatchIfExactMatchesEvenIfPartial_test( # noqa def GetCompletionsUserMayHaveCompleted_ReturnMatchIfExactMatchesEvenIfPartial_test( # noqa
ycm, *args ): ycm, *args ):
info = [ 'NS', 'Test', 'Abbr', 'Menu', 'Info', 'Kind' ] info = [ 'NS', 'Test', 'Abbr', 'Menu', 'Info', 'Kind' ]
completions = [ BuildCompletion( *info ), completions = [ BuildCompletionNamespace( *info ),
BuildCompletion( insertion_text = 'TestTest' ) ] BuildCompletion( insertion_text = 'TestTest' ) ]
with _SetupForCsharpCompletionDone( ycm, completions ): with _SetupForCsharpCompletionDone( ycm, completions ):
with patch( 'ycm.vimsupport.GetVariableValue', with patch( 'ycm.vimsupport.GetVariableValue',
@ -272,11 +338,41 @@ def GetCompletionsUserMayHaveCompleted_DontReturnMatchIfNoExactMatchesAndPartial
GetVariableValue_CompleteItemIs( 'Test' ) ) GetVariableValue_CompleteItemIs( 'Test' ) )
@YouCompleteMeInstance() @YouCompleteMeInstance()
def GetCompletionsUserMayHaveCompleted_ReturnMatchIfMatches_test( ycm, *args ): def GetCompletionsUserMayHaveCompleted_ReturnMatchIfMatches_test( ycm, *args ):
completions = [ BuildCompletion( None ) ] completions = [ BuildCompletionNamespace( None ) ]
with _SetupForCsharpCompletionDone( ycm, completions ): with _SetupForCsharpCompletionDone( ycm, completions ):
eq_( completions, ycm.GetCompletionsUserMayHaveCompleted() ) eq_( completions, ycm.GetCompletionsUserMayHaveCompleted() )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'Test', user_data='0' ) )
@YouCompleteMeInstance()
def GetCompletionsUserMayHaveCompleted_UseUserData0_test( ycm, *args ):
# identical completions but we specify the first one via user_data
completions = [
BuildCompletionNamespace( 'namespace1' ),
BuildCompletionNamespace( 'namespace2' )
]
with _SetupForCsharpCompletionDone( ycm, completions ):
eq_( [ BuildCompletionNamespace( 'namespace1' ) ],
ycm.GetCompletionsUserMayHaveCompleted() )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'Test', user_data='1' ) )
@YouCompleteMeInstance()
def GetCompletionsUserMayHaveCompleted_UseUserData1_test( ycm, *args ):
# identical completions but we specify the second one via user_data
completions = [
BuildCompletionNamespace( 'namespace1' ),
BuildCompletionNamespace( 'namespace2' )
]
with _SetupForCsharpCompletionDone( ycm, completions ):
eq_( [ BuildCompletionNamespace( 'namespace2' ) ],
ycm.GetCompletionsUserMayHaveCompleted() )
@patch( 'ycm.vimsupport.GetVariableValue', @patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'Test' ) ) GetVariableValue_CompleteItemIs( 'Test' ) )
@YouCompleteMeInstance() @YouCompleteMeInstance()
@ -291,7 +387,7 @@ def PostCompleteCsharp_EmptyDoesntInsertNamespace_test( ycm, *args ):
@YouCompleteMeInstance() @YouCompleteMeInstance()
def PostCompleteCsharp_ExistingWithoutNamespaceDoesntInsertNamespace_test( def PostCompleteCsharp_ExistingWithoutNamespaceDoesntInsertNamespace_test(
ycm, *args ): ycm, *args ):
completions = [ BuildCompletion( None ) ] completions = [ BuildCompletionNamespace( None ) ]
with _SetupForCsharpCompletionDone( ycm, completions ): with _SetupForCsharpCompletionDone( ycm, completions ):
ycm._OnCompleteDone_Csharp() ycm._OnCompleteDone_Csharp()
ok_( not vimsupport.InsertNamespace.called ) ok_( not vimsupport.InsertNamespace.called )
@ -302,7 +398,7 @@ def PostCompleteCsharp_ExistingWithoutNamespaceDoesntInsertNamespace_test(
@YouCompleteMeInstance() @YouCompleteMeInstance()
def PostCompleteCsharp_ValueDoesInsertNamespace_test( ycm, *args ): def PostCompleteCsharp_ValueDoesInsertNamespace_test( ycm, *args ):
namespace = 'A_NAMESPACE' namespace = 'A_NAMESPACE'
completions = [ BuildCompletion( namespace ) ] completions = [ BuildCompletionNamespace( namespace ) ]
with _SetupForCsharpCompletionDone( ycm, completions ): with _SetupForCsharpCompletionDone( ycm, completions ):
ycm._OnCompleteDone_Csharp() ycm._OnCompleteDone_Csharp()
vimsupport.InsertNamespace.assert_called_once_with( namespace ) vimsupport.InsertNamespace.assert_called_once_with( namespace )
@ -316,9 +412,92 @@ def PostCompleteCsharp_InsertSecondNamespaceIfSelected_test( ycm, *args ):
namespace = 'A_NAMESPACE' namespace = 'A_NAMESPACE'
namespace2 = 'ANOTHER_NAMESPACE' namespace2 = 'ANOTHER_NAMESPACE'
completions = [ completions = [
BuildCompletion( namespace ), BuildCompletionNamespace( namespace ),
BuildCompletion( namespace2 ), BuildCompletionNamespace( namespace2 ),
] ]
with _SetupForCsharpCompletionDone( ycm, completions ): with _SetupForCsharpCompletionDone( ycm, completions ):
ycm._OnCompleteDone_Csharp() ycm._OnCompleteDone_Csharp()
vimsupport.InsertNamespace.assert_called_once_with( namespace2 ) vimsupport.InsertNamespace.assert_called_once_with( namespace2 )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'Test' ) )
@patch( 'ycm.vimsupport.ReplaceChunks' )
@YouCompleteMeInstance()
def PostCompleteJava_ApplyFixIt_NoFixIts_test( ycm, replace_chunks, *args ):
completions = [
BuildCompletionFixIt( [] )
]
with _SetUpCompleteDone( ycm, completions ):
ycm._OnCompleteDone_Java()
replace_chunks.assert_not_called()
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'Test' ) )
@patch( 'ycm.vimsupport.ReplaceChunks' )
@YouCompleteMeInstance()
def PostCompleteJava_ApplyFixIt_EmptyFixIt_test( ycm, replace_chunks, *args ):
completions = [
BuildCompletionFixIt( [ { 'chunks': [] } ] )
]
with _SetUpCompleteDone( ycm, completions ):
ycm._OnCompleteDone_Java()
replace_chunks.assert_called_once_with( [], silent=True )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'Test' ) )
@patch( 'ycm.vimsupport.ReplaceChunks' )
@YouCompleteMeInstance()
def PostCompleteJava_ApplyFixIt_NoFixIt_test( ycm, replace_chunks, *args ):
completions = [
BuildCompletion( )
]
with _SetUpCompleteDone( ycm, completions ):
ycm._OnCompleteDone_Java()
replace_chunks.assert_not_called()
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'Test' ) )
@patch( 'ycm.vimsupport.ReplaceChunks' )
@YouCompleteMeInstance()
def PostCompleteJava_ApplyFixIt_PickFirst_test( ycm, replace_chunks, *args ):
completions = [
BuildCompletionFixIt( [ { 'chunks': 'one' } ] ),
BuildCompletionFixIt( [ { 'chunks': 'two' } ] ),
]
with _SetUpCompleteDone( ycm, completions ):
ycm._OnCompleteDone_Java()
replace_chunks.assert_called_once_with( 'one', silent=True )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'Test', user_data='0' ) )
@patch( 'ycm.vimsupport.ReplaceChunks' )
@YouCompleteMeInstance()
def PostCompleteJava_ApplyFixIt_PickFirstUserData_test( ycm,
replace_chunks,
*args ):
completions = [
BuildCompletionFixIt( [ { 'chunks': 'one' } ] ),
BuildCompletionFixIt( [ { 'chunks': 'two' } ] ),
]
with _SetUpCompleteDone( ycm, completions ):
ycm._OnCompleteDone_Java()
replace_chunks.assert_called_once_with( 'one', silent=True )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'Test', user_data='1' ) )
@patch( 'ycm.vimsupport.ReplaceChunks' )
@YouCompleteMeInstance()
def PostCompleteJava_ApplyFixIt_PickSecond_test( ycm, replace_chunks, *args ):
completions = [
BuildCompletionFixIt( [ { 'chunks': 'one' } ] ),
BuildCompletionFixIt( [ { 'chunks': 'two' } ] ),
]
with _SetUpCompleteDone( ycm, completions ):
ycm._OnCompleteDone_Java()
replace_chunks.assert_called_once_with( 'two', silent=True )

View File

@ -841,6 +841,103 @@ def ReplaceChunks_SingleFile_NotOpen_test( vim_command,
] ) ] )
@patch( 'ycm.vimsupport.VariableExists', return_value = False )
@patch( 'ycm.vimsupport.SetFittingHeightForCurrentWindow' )
@patch( 'ycm.vimsupport.GetBufferNumberForFilename',
side_effect = [ -1, 1 ],
new_callable = ExtendedMock )
@patch( 'ycm.vimsupport.BufferIsVisible',
side_effect = [ False, True ],
new_callable = ExtendedMock )
@patch( 'ycm.vimsupport.OpenFilename',
new_callable = ExtendedMock )
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
@patch( 'ycm.vimsupport.Confirm',
return_value = True,
new_callable = ExtendedMock )
@patch( 'vim.eval', return_value = 10, new_callable = ExtendedMock )
@patch( 'vim.command', new_callable = ExtendedMock )
def ReplaceChunks_SingleFile_NotOpen_Silent_test(
vim_command,
vim_eval,
confirm,
post_vim_message,
open_filename,
buffer_is_visible,
get_buffer_number_for_filename,
set_fitting_height,
variable_exists ):
# This test is the same as ReplaceChunks_SingleFile_NotOpen_test, but we pass
# the silent flag, as used by post-complete actions, and shows the stuff we
# _don't_ call in that case.
single_buffer_name = os.path.realpath( 'single_file' )
chunks = [
_BuildChunk( 1, 1, 2, 1, 'replacement', single_buffer_name )
]
result_buffer = VimBuffer(
single_buffer_name,
contents = [
'line1',
'line2',
'line3'
]
)
with patch( 'vim.buffers', [ None, result_buffer, None ] ):
vimsupport.ReplaceChunks( chunks, silent=True )
# We didn't check if it was OK to open the file (silent)
confirm.assert_not_called()
# Ensure that we applied the replacement correctly
eq_( result_buffer.GetLines(), [
'replacementline2',
'line3',
] )
# GetBufferNumberForFilename is called 2 times. The return values are set in
# the @patch call above:
# - once whilst applying the changes (-1 return)
# - finally after calling OpenFilename (1 return)
get_buffer_number_for_filename.assert_has_exact_calls( [
call( single_buffer_name ),
call( single_buffer_name ),
] )
# BufferIsVisible is called 2 times for the same reasons as above, with the
# return of each one
buffer_is_visible.assert_has_exact_calls( [
call( -1 ),
call( 1 ),
] )
# We open 'single_file' as expected.
open_filename.assert_called_with( single_buffer_name, {
'focus': True,
'fix': True,
'size': 10
} )
# And close it again, but don't show the quickfix window
vim_command.assert_has_exact_calls( [
call( 'lclose' ),
call( 'hide' ),
] )
set_fitting_height.assert_not_called()
# But we _don't_ update the QuickFix list
vim_eval.assert_has_exact_calls( [
call( '&previewheight' ),
] )
# And we don't print a message either
post_vim_message.assert_not_called()
@patch( 'ycm.vimsupport.GetBufferNumberForFilename', @patch( 'ycm.vimsupport.GetBufferNumberForFilename',
side_effect = [ -1, -1, 1 ], side_effect = [ -1, -1, 1 ],
new_callable = ExtendedMock ) new_callable = ExtendedMock )

View File

@ -735,7 +735,7 @@ def _OpenFileInSplitIfNeeded( filepath ):
return ( buffer_num, True ) return ( buffer_num, True )
def ReplaceChunks( chunks ): def ReplaceChunks( chunks, silent=False ):
"""Apply the source file deltas supplied in |chunks| to arbitrary files. """Apply the source file deltas supplied in |chunks| to arbitrary files.
|chunks| is a list of changes defined by ycmd.responses.FixItChunk, |chunks| is a list of changes defined by ycmd.responses.FixItChunk,
which may apply arbitrary modifications to arbitrary files. which may apply arbitrary modifications to arbitrary files.
@ -755,6 +755,7 @@ def ReplaceChunks( chunks ):
# We sort the file list simply to enable repeatable testing. # We sort the file list simply to enable repeatable testing.
sorted_file_list = sorted( iterkeys( chunks_by_file ) ) sorted_file_list = sorted( iterkeys( chunks_by_file ) )
if not silent:
# Make sure the user is prepared to have her screen mutilated by the new # Make sure the user is prepared to have her screen mutilated by the new
# buffers. # buffers.
num_files_to_open = _GetNumNonVisibleFiles( sorted_file_list ) num_files_to_open = _GetNumNonVisibleFiles( sorted_file_list )
@ -788,6 +789,7 @@ def ReplaceChunks( chunks ):
vim.command( 'hide' ) vim.command( 'hide' )
# Open the quickfix list, populated with entries for each location we changed. # Open the quickfix list, populated with entries for each location we changed.
if not silent:
if locations: if locations:
SetQuickFixList( locations ) SetQuickFixList( locations )
OpenQuickFixList() OpenQuickFixList()

View File

@ -108,6 +108,15 @@ SERVER_LOGFILE_FORMAT = 'ycmd_{port}_{std}_'
HANDLE_FLAG_INHERIT = 0x00000001 HANDLE_FLAG_INHERIT = 0x00000001
# The following two methods exist for testability only
def _CompleteDoneHook_CSharp( ycm ):
ycm._OnCompleteDone_Csharp()
def _CompleteDoneHook_Java( ycm ):
ycm._OnCompleteDone_Java()
class YouCompleteMe( object ): class YouCompleteMe( object ):
def __init__( self, user_options ): def __init__( self, user_options ):
self._available_completers = {} self._available_completers = {}
@ -128,7 +137,8 @@ class YouCompleteMe( object ):
self._SetUpServer() self._SetUpServer()
self._ycmd_keepalive.Start() self._ycmd_keepalive.Start()
self._complete_done_hooks = { self._complete_done_hooks = {
'cs': lambda self: self._OnCompleteDone_Csharp() 'cs': _CompleteDoneHook_CSharp,
'java': _CompleteDoneHook_Java,
} }
@ -491,42 +501,63 @@ class YouCompleteMe( object ):
if not latest_completion_request or not latest_completion_request.Done(): if not latest_completion_request or not latest_completion_request.Done():
return [] return []
completions = latest_completion_request.RawResponse() completed_item = vimsupport.GetVariableValue( 'v:completed_item' )
completions = latest_completion_request.RawResponse()[ 'completions' ]
result = self._FilterToMatchingCompletions( completions, True ) if 'user_data' in completed_item and completed_item[ 'user_data' ] != '':
# Vim supports user_data (8.0.1493) or later, so we actually know the
# _exact_ element that was selected, having put its index in the user_data
# field.
return [ completions[ int( completed_item[ 'user_data' ] ) ] ]
# Otherwise, we have to guess by matching the values in the completed item
# and the list of completions. Sometimes this returns multiple
# possibilities, which is essentially unresolvable.
result = self._FilterToMatchingCompletions( completed_item,
completions,
True )
result = list( result ) result = list( result )
if result: if result:
return result return result
if self._HasCompletionsThatCouldBeCompletedWithMoreText( completions ): if self._HasCompletionsThatCouldBeCompletedWithMoreText( completed_item,
completions ):
# Since the way that YCM works leads to CompleteDone called on every # Since the way that YCM works leads to CompleteDone called on every
# character, return blank if the completion might not be done. This won't # character, return blank if the completion might not be done. This won't
# match if the completion is ended with typing a non-keyword character. # match if the completion is ended with typing a non-keyword character.
return [] return []
result = self._FilterToMatchingCompletions( completions, False ) result = self._FilterToMatchingCompletions( completed_item,
completions,
False )
return list( result ) return list( result )
def _FilterToMatchingCompletions( self, completions, full_match_only ): def _FilterToMatchingCompletions( self,
completed_item,
completions,
full_match_only ):
"""Filter to completions matching the item Vim said was completed""" """Filter to completions matching the item Vim said was completed"""
completed = vimsupport.GetVariableValue( 'v:completed_item' )
for completion in completions:
item = ConvertCompletionDataToVimData( completion )
match_keys = ( [ "word", "abbr", "menu", "info" ] if full_match_only match_keys = ( [ "word", "abbr", "menu", "info" ] if full_match_only
else [ 'word' ] ) else [ 'word' ] )
for index, completion in enumerate( completions ):
item = ConvertCompletionDataToVimData( index, completion )
def matcher( key ): def matcher( key ):
return ( utils.ToUnicode( completed.get( key, "" ) ) == return ( utils.ToUnicode( completed_item.get( key, "" ) ) ==
utils.ToUnicode( item.get( key, "" ) ) ) utils.ToUnicode( item.get( key, "" ) ) )
if all( [ matcher( i ) for i in match_keys ] ): if all( [ matcher( i ) for i in match_keys ] ):
yield completion yield completion
def _HasCompletionsThatCouldBeCompletedWithMoreText( self, completions ): def _HasCompletionsThatCouldBeCompletedWithMoreText( self,
completed_item = vimsupport.GetVariableValue( 'v:completed_item' ) completed_item,
completions ):
if not completed_item: if not completed_item:
return False return False
@ -542,9 +573,9 @@ class YouCompleteMe( object ):
reject_exact_match = False reject_exact_match = False
completed_word += text[ -1 ] completed_word += text[ -1 ]
for completion in completions: for index, completion in enumerate( completions ):
word = utils.ToUnicode( word = utils.ToUnicode(
ConvertCompletionDataToVimData( completion )[ 'word' ] ) ConvertCompletionDataToVimData( index, completion )[ 'word' ] )
if reject_exact_match and word == completed_word: if reject_exact_match and word == completed_word:
continue continue
if word.startswith( completed_word ): if word.startswith( completed_word ):
@ -580,6 +611,33 @@ class YouCompleteMe( object ):
return completion[ "extra_data" ][ "required_namespace_import" ] return completion[ "extra_data" ][ "required_namespace_import" ]
def _OnCompleteDone_Java( self ):
completions = self.GetCompletionsUserMayHaveCompleted()
fixit_completions = [ self._GetFixItCompletion( c ) for c in completions ]
fixit_completions = [ f for f in fixit_completions if f ]
if not fixit_completions:
return
# If we have user_data in completions (8.0.1493 or later), then we would
# only ever return max. 1 completion here. However, if we had to guess, it
# is possible that we matched multiple completion items (e.g. for overloads,
# or similar classes in multiple packages). In any case, rather than
# prompting the user and disturbing her workflow, we just apply the first
# one. This might be wrong, but the solution is to use a (very) new version
# of Vim which supports user_data on completion items
fixit_completion = fixit_completions[ 0 ]
for fixit in fixit_completion:
vimsupport.ReplaceChunks( fixit[ 'chunks' ], silent=True )
def _GetFixItCompletion( self, completion ):
if ( "extra_data" not in completion
or "fixits" not in completion[ "extra_data" ] ):
return None
return completion[ "extra_data" ][ "fixits" ]
def GetErrorCount( self ): def GetErrorCount( self ):
return self.CurrentBuffer().GetErrorCount() return self.CurrentBuffer().GetErrorCount()