Auto merge of #1905 - davits:easy_handler, r=Valloric
[READY] Simplify parse request handling Moved file parse request handling into python, trying to simplify the overall flow, and allow easier additions of additional post handlers such as semantic token extraction. <!-- Reviewable:start --> [<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/valloric/youcompleteme/1905) <!-- Reviewable:end -->
This commit is contained in:
commit
7b8b1acb5e
@ -29,14 +29,6 @@ let s:cursor_moved = 0
|
||||
let s:moved_vertically_in_insert_mode = 0
|
||||
let s:previous_num_chars_on_current_line = strlen( getline('.') )
|
||||
|
||||
let s:diagnostic_ui_filetypes = {
|
||||
\ 'cpp': 1,
|
||||
\ 'cs': 1,
|
||||
\ 'c': 1,
|
||||
\ 'objc': 1,
|
||||
\ 'objcpp': 1,
|
||||
\ }
|
||||
|
||||
|
||||
function! youcompleteme#Enable()
|
||||
" When vim is in diff mode, don't run
|
||||
@ -308,11 +300,6 @@ function! s:TurnOffSyntasticForCFamily()
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:DiagnosticUiSupportedForCurrentFiletype()
|
||||
return get( s:diagnostic_ui_filetypes, &filetype, 0 )
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:AllowedToCompleteInCurrentFile()
|
||||
if empty( &filetype ) ||
|
||||
\ getbufvar( winbufnr( winnr() ), "&buftype" ) ==# 'nofile' ||
|
||||
@ -462,11 +449,11 @@ function! s:OnFileReadyToParse()
|
||||
" happen for special buffers.
|
||||
call s:SetUpYcmChangedTick()
|
||||
|
||||
" Order is important here; we need to extract any done diagnostics before
|
||||
" Order is important here; we need to extract any information before
|
||||
" reparsing the file again. If we sent the new parse request first, then
|
||||
" the response would always be pending when we called
|
||||
" UpdateDiagnosticNotifications.
|
||||
call s:UpdateDiagnosticNotifications()
|
||||
" HandleFileParseRequest.
|
||||
py ycm_state.HandleFileParseRequest()
|
||||
|
||||
let buffer_changed = b:changedtick != b:ycm_changedtick.file_ready_to_parse
|
||||
if buffer_changed
|
||||
@ -612,19 +599,6 @@ function! s:ClosePreviewWindowIfNeeded()
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:UpdateDiagnosticNotifications()
|
||||
let should_display_diagnostics = g:ycm_show_diagnostics_ui &&
|
||||
\ s:DiagnosticUiSupportedForCurrentFiletype()
|
||||
|
||||
if !should_display_diagnostics
|
||||
py ycm_state.ValidateParseRequest()
|
||||
return
|
||||
endif
|
||||
|
||||
py ycm_state.UpdateDiagnosticInterface()
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:IdentifierFinishedOperations()
|
||||
if !pyeval( 'base.CurrentIdentifierFinished()' )
|
||||
return
|
||||
@ -853,15 +827,8 @@ function! s:ForceCompile()
|
||||
|
||||
echom "Forcing compilation, this will block Vim until done."
|
||||
py ycm_state.OnFileReadyToParse()
|
||||
while 1
|
||||
let diagnostics_ready = pyeval(
|
||||
\ 'ycm_state.DiagnosticsForCurrentFileReady()' )
|
||||
if diagnostics_ready
|
||||
break
|
||||
endif
|
||||
py ycm_state.HandleFileParseRequest( True )
|
||||
|
||||
sleep 100m
|
||||
endwhile
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
@ -871,8 +838,6 @@ function! s:ForceCompileAndDiagnostics()
|
||||
if !compilation_succeeded
|
||||
return
|
||||
endif
|
||||
|
||||
call s:UpdateDiagnosticNotifications()
|
||||
echom "Diagnostics refreshed."
|
||||
endfunction
|
||||
|
||||
@ -883,11 +848,7 @@ function! s:ShowDiagnostics()
|
||||
return
|
||||
endif
|
||||
|
||||
let diags = pyeval(
|
||||
\ 'ycm_state.GetDiagnosticsFromStoredRequest( qflist_format = True )' )
|
||||
if !empty( diags )
|
||||
call setloclist( 0, diags )
|
||||
|
||||
if pyeval( 'ycm_state.PopulateLocationListWithLatestDiagnostics()' )
|
||||
if g:ycm_open_loclist_on_ycm_diags
|
||||
lopen
|
||||
endif
|
||||
|
@ -50,6 +50,11 @@ class DiagnosticInterface( object ):
|
||||
return len( self._FilterDiagnostics( _DiagnosticIsWarning ) )
|
||||
|
||||
|
||||
def PopulateLocationList( self, diags ):
|
||||
vimsupport.SetLocationList(
|
||||
vimsupport.ConvertDiagnosticsToQfList( diags ) )
|
||||
|
||||
|
||||
def UpdateWithNewDiagnostics( self, diags ):
|
||||
normalized_diags = [ _NormalizeDiagnostic( x ) for x in diags ]
|
||||
self._buffer_number_to_line_to_diags = _ConvertDiagListToDict(
|
||||
@ -65,8 +70,7 @@ class DiagnosticInterface( object ):
|
||||
_UpdateSquiggles( self._buffer_number_to_line_to_diags )
|
||||
|
||||
if self._user_options[ 'always_populate_location_list' ]:
|
||||
vimsupport.SetLocationList(
|
||||
vimsupport.ConvertDiagnosticsToQfList( normalized_diags ) )
|
||||
self.PopulateLocationList( normalized_diags )
|
||||
|
||||
def _EchoDiagnosticForLine( self, line_num ):
|
||||
buffer_num = vim.current.buffer.number
|
||||
|
@ -17,85 +17,85 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from nose.tools import eq_, ok_, with_setup
|
||||
from mock import MagicMock
|
||||
import contextlib
|
||||
from nose.tools import eq_, ok_
|
||||
from mock import patch
|
||||
|
||||
from ycm.test_utils import MockVimModule
|
||||
vim_mock = MockVimModule()
|
||||
from ycm import base
|
||||
from ycm import vimsupport
|
||||
import sys
|
||||
|
||||
# column is 0-based
|
||||
def SetVimCurrentColumnAndLineValue( column, line_value ):
|
||||
vimsupport.CurrentColumn = MagicMock( return_value = column )
|
||||
vimsupport.CurrentLineContents = MagicMock( return_value = line_value )
|
||||
|
||||
|
||||
def Setup():
|
||||
sys.modules[ 'ycm.vimsupport' ] = MagicMock()
|
||||
vimsupport.CurrentFiletypes = MagicMock( return_value = [''] )
|
||||
vimsupport.CurrentColumn = MagicMock( return_value = 1 )
|
||||
vimsupport.CurrentLineContents = MagicMock( return_value = '' )
|
||||
@contextlib.contextmanager
|
||||
def MockCurrentFiletypes( filetypes = [''] ):
|
||||
with patch( 'ycm.vimsupport.CurrentFiletypes', return_value = filetypes ):
|
||||
yield
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def MockCurrentColumnAndLineContents( column, line_contents ):
|
||||
with patch( 'ycm.vimsupport.CurrentColumn', return_value = column ):
|
||||
with patch( 'ycm.vimsupport.CurrentLineContents',
|
||||
return_value = line_contents ):
|
||||
yield
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def MockTextAfterCursor( text ):
|
||||
with patch( 'ycm.vimsupport.TextAfterCursor', return_value = text ):
|
||||
yield
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def AdjustCandidateInsertionText_Basic_test():
|
||||
vimsupport.TextAfterCursor = MagicMock( return_value = 'bar' )
|
||||
with MockTextAfterCursor( 'bar' ):
|
||||
eq_( [ { 'abbr': 'foobar', 'word': 'foo' } ],
|
||||
base.AdjustCandidateInsertionText( [ 'foobar' ] ) )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def AdjustCandidateInsertionText_ParenInTextAfterCursor_test():
|
||||
vimsupport.TextAfterCursor = MagicMock( return_value = 'bar(zoo' )
|
||||
with MockTextAfterCursor( 'bar(zoo' ):
|
||||
eq_( [ { 'abbr': 'foobar', 'word': 'foo' } ],
|
||||
base.AdjustCandidateInsertionText( [ 'foobar' ] ) )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def AdjustCandidateInsertionText_PlusInTextAfterCursor_test():
|
||||
vimsupport.TextAfterCursor = MagicMock( return_value = 'bar+zoo' )
|
||||
with MockTextAfterCursor( 'bar+zoo' ):
|
||||
eq_( [ { 'abbr': 'foobar', 'word': 'foo' } ],
|
||||
base.AdjustCandidateInsertionText( [ 'foobar' ] ) )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def AdjustCandidateInsertionText_WhitespaceInTextAfterCursor_test():
|
||||
vimsupport.TextAfterCursor = MagicMock( return_value = 'bar zoo' )
|
||||
with MockTextAfterCursor( 'bar zoo' ):
|
||||
eq_( [ { 'abbr': 'foobar', 'word': 'foo' } ],
|
||||
base.AdjustCandidateInsertionText( [ 'foobar' ] ) )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def AdjustCandidateInsertionText_MoreThanWordMatchingAfterCursor_test():
|
||||
vimsupport.TextAfterCursor = MagicMock( return_value = 'bar.h' )
|
||||
with MockTextAfterCursor( 'bar.h' ):
|
||||
eq_( [ { 'abbr': 'foobar.h', 'word': 'foo' } ],
|
||||
base.AdjustCandidateInsertionText( [ 'foobar.h' ] ) )
|
||||
|
||||
vimsupport.TextAfterCursor = MagicMock( return_value = 'bar(zoo' )
|
||||
with MockTextAfterCursor( 'bar(zoo' ):
|
||||
eq_( [ { 'abbr': 'foobar(zoo', 'word': 'foo' } ],
|
||||
base.AdjustCandidateInsertionText( [ 'foobar(zoo' ] ) )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def AdjustCandidateInsertionText_NotSuffix_test():
|
||||
vimsupport.TextAfterCursor = MagicMock( return_value = 'bar' )
|
||||
with MockTextAfterCursor( 'bar' ):
|
||||
eq_( [ { 'abbr': 'foofoo', 'word': 'foofoo' } ],
|
||||
base.AdjustCandidateInsertionText( [ 'foofoo' ] ) )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def AdjustCandidateInsertionText_NothingAfterCursor_test():
|
||||
vimsupport.TextAfterCursor = MagicMock( return_value = '' )
|
||||
with MockTextAfterCursor( '' ):
|
||||
eq_( [ 'foofoo',
|
||||
'zobar' ],
|
||||
base.AdjustCandidateInsertionText( [ 'foofoo',
|
||||
'zobar' ] ) )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def AdjustCandidateInsertionText_MultipleStrings_test():
|
||||
vimsupport.TextAfterCursor = MagicMock( return_value = 'bar' )
|
||||
with MockTextAfterCursor( 'bar' ):
|
||||
eq_( [ { 'abbr': 'foobar', 'word': 'foo' },
|
||||
{ 'abbr': 'zobar', 'word': 'zo' },
|
||||
{ 'abbr': 'qbar', 'word': 'q' },
|
||||
@ -107,172 +107,158 @@ def AdjustCandidateInsertionText_MultipleStrings_test():
|
||||
'bar' ] ) )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def AdjustCandidateInsertionText_DictInput_test():
|
||||
vimsupport.TextAfterCursor = MagicMock( return_value = 'bar' )
|
||||
with MockTextAfterCursor( 'bar' ):
|
||||
eq_( [ { 'abbr': 'foobar', 'word': 'foo' } ],
|
||||
base.AdjustCandidateInsertionText(
|
||||
[ { 'word': 'foobar' } ] ) )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def AdjustCandidateInsertionText_DontTouchAbbr_test():
|
||||
vimsupport.TextAfterCursor = MagicMock( return_value = 'bar' )
|
||||
with MockTextAfterCursor( 'bar' ):
|
||||
eq_( [ { 'abbr': '1234', 'word': 'foo' } ],
|
||||
base.AdjustCandidateInsertionText(
|
||||
[ { 'abbr': '1234', 'word': 'foobar' } ] ) )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def OverlapLength_Basic_test():
|
||||
eq_( 3, base.OverlapLength( 'foo bar', 'bar zoo' ) )
|
||||
eq_( 3, base.OverlapLength( 'foobar', 'barzoo' ) )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def OverlapLength_BasicWithUnicode_test():
|
||||
eq_( 3, base.OverlapLength( u'bar fäö', u'fäö bar' ) )
|
||||
eq_( 3, base.OverlapLength( u'zoofäö', u'fäözoo' ) )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def OverlapLength_OneCharOverlap_test():
|
||||
eq_( 1, base.OverlapLength( 'foo b', 'b zoo' ) )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def OverlapLength_SameStrings_test():
|
||||
eq_( 6, base.OverlapLength( 'foobar', 'foobar' ) )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def OverlapLength_Substring_test():
|
||||
eq_( 6, base.OverlapLength( 'foobar', 'foobarzoo' ) )
|
||||
eq_( 6, base.OverlapLength( 'zoofoobar', 'foobar' ) )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def OverlapLength_LongestOverlap_test():
|
||||
eq_( 7, base.OverlapLength( 'bar foo foo', 'foo foo bar' ) )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def OverlapLength_EmptyInput_test():
|
||||
eq_( 0, base.OverlapLength( '', 'goobar' ) )
|
||||
eq_( 0, base.OverlapLength( 'foobar', '' ) )
|
||||
eq_( 0, base.OverlapLength( '', '' ) )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def OverlapLength_NoOverlap_test():
|
||||
eq_( 0, base.OverlapLength( 'foobar', 'goobar' ) )
|
||||
eq_( 0, base.OverlapLength( 'foobar', '(^($@#$#@' ) )
|
||||
eq_( 0, base.OverlapLength( 'foo bar zoo', 'foo zoo bar' ) )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def LastEnteredCharIsIdentifierChar_Basic_test():
|
||||
SetVimCurrentColumnAndLineValue( 3, 'abc' )
|
||||
with MockCurrentFiletypes():
|
||||
with MockCurrentColumnAndLineContents( 3, 'abc' ):
|
||||
ok_( base.LastEnteredCharIsIdentifierChar() )
|
||||
|
||||
SetVimCurrentColumnAndLineValue( 2, 'abc' )
|
||||
with MockCurrentColumnAndLineContents( 2, 'abc' ):
|
||||
ok_( base.LastEnteredCharIsIdentifierChar() )
|
||||
|
||||
SetVimCurrentColumnAndLineValue( 1, 'abc' )
|
||||
with MockCurrentColumnAndLineContents( 1, 'abc' ):
|
||||
ok_( base.LastEnteredCharIsIdentifierChar() )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def LastEnteredCharIsIdentifierChar_FiletypeHtml_test():
|
||||
SetVimCurrentColumnAndLineValue( 3, 'ab-' )
|
||||
vimsupport.CurrentFiletypes = MagicMock( return_value = ['html'] )
|
||||
with MockCurrentFiletypes( ['html'] ):
|
||||
with MockCurrentColumnAndLineContents( 3, 'ab-' ):
|
||||
ok_( base.LastEnteredCharIsIdentifierChar() )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def LastEnteredCharIsIdentifierChar_ColumnIsZero_test():
|
||||
SetVimCurrentColumnAndLineValue( 0, 'abc' )
|
||||
with MockCurrentColumnAndLineContents( 0, 'abc' ):
|
||||
ok_( not base.LastEnteredCharIsIdentifierChar() )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def LastEnteredCharIsIdentifierChar_LineEmpty_test():
|
||||
SetVimCurrentColumnAndLineValue( 3, '' )
|
||||
with MockCurrentFiletypes():
|
||||
with MockCurrentColumnAndLineContents( 3, '' ):
|
||||
ok_( not base.LastEnteredCharIsIdentifierChar() )
|
||||
|
||||
SetVimCurrentColumnAndLineValue( 0, '' )
|
||||
with MockCurrentColumnAndLineContents( 0, '' ):
|
||||
ok_( not base.LastEnteredCharIsIdentifierChar() )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def LastEnteredCharIsIdentifierChar_NotIdentChar_test():
|
||||
SetVimCurrentColumnAndLineValue( 3, 'ab;' )
|
||||
with MockCurrentFiletypes():
|
||||
with MockCurrentColumnAndLineContents( 3, 'ab;' ):
|
||||
ok_( not base.LastEnteredCharIsIdentifierChar() )
|
||||
|
||||
SetVimCurrentColumnAndLineValue( 1, ';' )
|
||||
with MockCurrentColumnAndLineContents( 1, ';' ):
|
||||
ok_( not base.LastEnteredCharIsIdentifierChar() )
|
||||
|
||||
SetVimCurrentColumnAndLineValue( 3, 'ab-' )
|
||||
with MockCurrentColumnAndLineContents( 3, 'ab-' ):
|
||||
ok_( not base.LastEnteredCharIsIdentifierChar() )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def CurrentIdentifierFinished_Basic_test():
|
||||
SetVimCurrentColumnAndLineValue( 3, 'ab;' )
|
||||
with MockCurrentFiletypes():
|
||||
with MockCurrentColumnAndLineContents( 3, 'ab;' ):
|
||||
ok_( base.CurrentIdentifierFinished() )
|
||||
|
||||
SetVimCurrentColumnAndLineValue( 2, 'ab;' )
|
||||
with MockCurrentColumnAndLineContents( 2, 'ab;' ):
|
||||
ok_( not base.CurrentIdentifierFinished() )
|
||||
|
||||
SetVimCurrentColumnAndLineValue( 1, 'ab;' )
|
||||
with MockCurrentColumnAndLineContents( 1, 'ab;' ):
|
||||
ok_( not base.CurrentIdentifierFinished() )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def CurrentIdentifierFinished_NothingBeforeColumn_test():
|
||||
SetVimCurrentColumnAndLineValue( 0, 'ab;' )
|
||||
with MockCurrentColumnAndLineContents( 0, 'ab;' ):
|
||||
ok_( base.CurrentIdentifierFinished() )
|
||||
|
||||
SetVimCurrentColumnAndLineValue( 0, '' )
|
||||
with MockCurrentColumnAndLineContents( 0, '' ):
|
||||
ok_( base.CurrentIdentifierFinished() )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def CurrentIdentifierFinished_InvalidColumn_test():
|
||||
SetVimCurrentColumnAndLineValue( 5, '' )
|
||||
with MockCurrentFiletypes():
|
||||
with MockCurrentColumnAndLineContents( 5, '' ):
|
||||
ok_( not base.CurrentIdentifierFinished() )
|
||||
|
||||
SetVimCurrentColumnAndLineValue( 5, 'abc' )
|
||||
with MockCurrentColumnAndLineContents( 5, 'abc' ):
|
||||
ok_( not base.CurrentIdentifierFinished() )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def CurrentIdentifierFinished_InMiddleOfLine_test():
|
||||
SetVimCurrentColumnAndLineValue( 4, 'bar.zoo' )
|
||||
with MockCurrentFiletypes():
|
||||
with MockCurrentColumnAndLineContents( 4, 'bar.zoo' ):
|
||||
ok_( base.CurrentIdentifierFinished() )
|
||||
|
||||
SetVimCurrentColumnAndLineValue( 4, 'bar(zoo' )
|
||||
with MockCurrentColumnAndLineContents( 4, 'bar(zoo' ):
|
||||
ok_( base.CurrentIdentifierFinished() )
|
||||
|
||||
SetVimCurrentColumnAndLineValue( 4, 'bar-zoo' )
|
||||
with MockCurrentColumnAndLineContents( 4, 'bar-zoo' ):
|
||||
ok_( base.CurrentIdentifierFinished() )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def CurrentIdentifierFinished_Html_test():
|
||||
SetVimCurrentColumnAndLineValue( 4, 'bar-zoo' )
|
||||
vimsupport.CurrentFiletypes = MagicMock( return_value = ['html'] )
|
||||
with MockCurrentFiletypes( ['html'] ):
|
||||
with MockCurrentColumnAndLineContents( 4, 'bar-zoo' ):
|
||||
ok_( not base.CurrentIdentifierFinished() )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def CurrentIdentifierFinished_WhitespaceOnly_test():
|
||||
SetVimCurrentColumnAndLineValue( 1, '\n' )
|
||||
with MockCurrentFiletypes():
|
||||
with MockCurrentColumnAndLineContents( 1, '\n' ):
|
||||
ok_( base.CurrentIdentifierFinished() )
|
||||
|
||||
SetVimCurrentColumnAndLineValue( 3, '\n ' )
|
||||
with MockCurrentColumnAndLineContents( 3, '\n ' ):
|
||||
ok_( base.CurrentIdentifierFinished() )
|
||||
|
||||
SetVimCurrentColumnAndLineValue( 3, '\t\t\t\t' )
|
||||
with MockCurrentColumnAndLineContents( 3, '\t\t\t\t' ):
|
||||
ok_( base.CurrentIdentifierFinished() )
|
||||
|
||||
|
@ -23,9 +23,11 @@ import os
|
||||
|
||||
from ycm.youcompleteme import YouCompleteMe
|
||||
from ycmd import user_options_store
|
||||
from ycmd.responses import UnknownExtraConf
|
||||
from ycmd.responses import ( BuildDiagnosticData, Diagnostic, Location, Range,
|
||||
UnknownExtraConf )
|
||||
|
||||
from mock import call, MagicMock, patch
|
||||
from nose.tools import eq_, ok_
|
||||
|
||||
|
||||
# The default options which are only relevant to the client, not the server and
|
||||
@ -34,6 +36,10 @@ from mock import call, MagicMock, patch
|
||||
DEFAULT_CLIENT_OPTIONS = {
|
||||
'server_log_level': 'info',
|
||||
'extra_conf_vim_data': [],
|
||||
'show_diagnostics_ui': 1,
|
||||
'enable_diagnostic_signs': 1,
|
||||
'enable_diagnostic_highlighting': 0,
|
||||
'always_populate_location_list': 0,
|
||||
}
|
||||
|
||||
|
||||
@ -51,6 +57,17 @@ def PresentDialog_Confirm_Call( message ):
|
||||
return call( message, [ 'Ok', 'Cancel' ] )
|
||||
|
||||
|
||||
def PlaceSign_Call( sign_id, line_num, buffer_num, is_error ):
|
||||
sign_name = 'YcmError' if is_error else 'YcmWarning'
|
||||
return call( 'sign place {0} line={1} name={2} buffer={3}'
|
||||
.format( sign_id, line_num, sign_name, buffer_num ) )
|
||||
|
||||
|
||||
def UnplaceSign_Call( sign_id, buffer_num ):
|
||||
return call( 'try | exec "sign unplace {0} buffer={1}" |'
|
||||
' catch /E158/ | endtry'.format( sign_id, buffer_num ) )
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def MockArbitraryBuffer( filetype, native_available = True ):
|
||||
"""Used via the with statement, set up mocked versions of the vim module such
|
||||
@ -72,6 +89,12 @@ def MockArbitraryBuffer( filetype, native_available = True ):
|
||||
if value == 'getbufvar(0, "&ft")' or value == '&filetype':
|
||||
return filetype
|
||||
|
||||
if value.startswith( 'bufnr(' ):
|
||||
return 0
|
||||
|
||||
if value.startswith( 'bufwinnr(' ):
|
||||
return 0
|
||||
|
||||
raise ValueError( 'Unexpected evaluation' )
|
||||
|
||||
# Arbitrary, but valid, cursor position
|
||||
@ -82,6 +105,7 @@ def MockArbitraryBuffer( filetype, native_available = True ):
|
||||
current_buffer.number = 0
|
||||
current_buffer.filename = os.path.realpath( 'TEST_BUFFER' )
|
||||
current_buffer.name = 'TEST_BUFFER'
|
||||
current_buffer.window = 0
|
||||
|
||||
# The rest just mock up the Vim module so that our single arbitrary buffer
|
||||
# makes sense to vimsupport module.
|
||||
@ -144,8 +168,8 @@ class EventNotification_test( object ):
|
||||
|
||||
@patch( 'vim.command', new_callable = ExtendedMock )
|
||||
def FileReadyToParse_NonDiagnostic_Error_test( self, vim_command ):
|
||||
# This test validates the behaviour of YouCompleteMe.ValidateParseRequest in
|
||||
# combination with YouCompleteMe.OnFileReadyToParse when the completer
|
||||
# This test validates the behaviour of YouCompleteMe.HandleFileParseRequest
|
||||
# in combination with YouCompleteMe.OnFileReadyToParse when the completer
|
||||
# raises an exception handling FileReadyToParse event notification
|
||||
ERROR_TEXT = 'Some completer response text'
|
||||
|
||||
@ -155,8 +179,8 @@ class EventNotification_test( object ):
|
||||
with MockArbitraryBuffer( 'javascript' ):
|
||||
with MockEventNotification( ErrorResponse ):
|
||||
self.server_state.OnFileReadyToParse()
|
||||
assert self.server_state.DiagnosticsForCurrentFileReady()
|
||||
self.server_state.ValidateParseRequest()
|
||||
assert self.server_state.FileParseRequestReady()
|
||||
self.server_state.HandleFileParseRequest()
|
||||
|
||||
# The first call raises a warning
|
||||
vim_command.assert_has_exact_calls( [
|
||||
@ -164,15 +188,15 @@ class EventNotification_test( object ):
|
||||
] )
|
||||
|
||||
# Subsequent calls don't re-raise the warning
|
||||
self.server_state.ValidateParseRequest()
|
||||
self.server_state.HandleFileParseRequest()
|
||||
vim_command.assert_has_exact_calls( [
|
||||
PostVimMessage_Call( ERROR_TEXT ),
|
||||
] )
|
||||
|
||||
# But it does if a subsequent event raises again
|
||||
self.server_state.OnFileReadyToParse()
|
||||
assert self.server_state.DiagnosticsForCurrentFileReady()
|
||||
self.server_state.ValidateParseRequest()
|
||||
assert self.server_state.FileParseRequestReady()
|
||||
self.server_state.HandleFileParseRequest()
|
||||
vim_command.assert_has_exact_calls( [
|
||||
PostVimMessage_Call( ERROR_TEXT ),
|
||||
PostVimMessage_Call( ERROR_TEXT ),
|
||||
@ -184,7 +208,7 @@ class EventNotification_test( object ):
|
||||
with MockArbitraryBuffer( 'javascript' ):
|
||||
with MockEventNotification( None, False ):
|
||||
self.server_state.OnFileReadyToParse()
|
||||
self.server_state.ValidateParseRequest()
|
||||
self.server_state.HandleFileParseRequest()
|
||||
vim_command.assert_not_called()
|
||||
|
||||
|
||||
@ -198,8 +222,8 @@ class EventNotification_test( object ):
|
||||
load_extra_conf,
|
||||
*args ):
|
||||
|
||||
# This test validates the behaviour of YouCompleteMe.ValidateParseRequest in
|
||||
# combination with YouCompleteMe.OnFileReadyToParse when the completer
|
||||
# This test validates the behaviour of YouCompleteMe.HandleFileParseRequest
|
||||
# in combination with YouCompleteMe.OnFileReadyToParse when the completer
|
||||
# raises the (special) UnknownExtraConf exception
|
||||
|
||||
FILE_NAME = 'a_file'
|
||||
@ -217,8 +241,8 @@ class EventNotification_test( object ):
|
||||
return_value = 0,
|
||||
new_callable = ExtendedMock ) as present_dialog:
|
||||
self.server_state.OnFileReadyToParse()
|
||||
assert self.server_state.DiagnosticsForCurrentFileReady()
|
||||
self.server_state.ValidateParseRequest()
|
||||
assert self.server_state.FileParseRequestReady()
|
||||
self.server_state.HandleFileParseRequest()
|
||||
|
||||
present_dialog.assert_has_exact_calls( [
|
||||
PresentDialog_Confirm_Call( MESSAGE ),
|
||||
@ -228,7 +252,7 @@ class EventNotification_test( object ):
|
||||
] )
|
||||
|
||||
# Subsequent calls don't re-raise the warning
|
||||
self.server_state.ValidateParseRequest()
|
||||
self.server_state.HandleFileParseRequest()
|
||||
|
||||
present_dialog.assert_has_exact_calls( [
|
||||
PresentDialog_Confirm_Call( MESSAGE )
|
||||
@ -239,8 +263,8 @@ class EventNotification_test( object ):
|
||||
|
||||
# But it does if a subsequent event raises again
|
||||
self.server_state.OnFileReadyToParse()
|
||||
assert self.server_state.DiagnosticsForCurrentFileReady()
|
||||
self.server_state.ValidateParseRequest()
|
||||
assert self.server_state.FileParseRequestReady()
|
||||
self.server_state.HandleFileParseRequest()
|
||||
|
||||
present_dialog.assert_has_exact_calls( [
|
||||
PresentDialog_Confirm_Call( MESSAGE ),
|
||||
@ -256,8 +280,8 @@ class EventNotification_test( object ):
|
||||
return_value = 1,
|
||||
new_callable = ExtendedMock ) as present_dialog:
|
||||
self.server_state.OnFileReadyToParse()
|
||||
assert self.server_state.DiagnosticsForCurrentFileReady()
|
||||
self.server_state.ValidateParseRequest()
|
||||
assert self.server_state.FileParseRequestReady()
|
||||
self.server_state.HandleFileParseRequest()
|
||||
|
||||
present_dialog.assert_has_exact_calls( [
|
||||
PresentDialog_Confirm_Call( MESSAGE ),
|
||||
@ -267,7 +291,7 @@ class EventNotification_test( object ):
|
||||
] )
|
||||
|
||||
# Subsequent calls don't re-raise the warning
|
||||
self.server_state.ValidateParseRequest()
|
||||
self.server_state.HandleFileParseRequest()
|
||||
|
||||
present_dialog.assert_has_exact_calls( [
|
||||
PresentDialog_Confirm_Call( MESSAGE )
|
||||
@ -278,8 +302,8 @@ class EventNotification_test( object ):
|
||||
|
||||
# But it does if a subsequent event raises again
|
||||
self.server_state.OnFileReadyToParse()
|
||||
assert self.server_state.DiagnosticsForCurrentFileReady()
|
||||
self.server_state.ValidateParseRequest()
|
||||
assert self.server_state.FileParseRequestReady()
|
||||
self.server_state.HandleFileParseRequest()
|
||||
|
||||
present_dialog.assert_has_exact_calls( [
|
||||
PresentDialog_Confirm_Call( MESSAGE ),
|
||||
@ -289,3 +313,92 @@ class EventNotification_test( object ):
|
||||
call( FILE_NAME ),
|
||||
call( FILE_NAME ),
|
||||
] )
|
||||
|
||||
|
||||
def FileReadyToParse_Diagnostic_Error_Native_test( self ):
|
||||
self._Check_FileReadyToParse_Diagnostic_Error()
|
||||
self._Check_FileReadyToParse_Diagnostic_Warning()
|
||||
self._Check_FileReadyToParse_Diagnostic_Clean()
|
||||
|
||||
|
||||
@patch( 'vim.command' )
|
||||
def _Check_FileReadyToParse_Diagnostic_Error( self, vim_command ):
|
||||
# Tests Vim sign placement and error/warning count python API
|
||||
# when one error is returned.
|
||||
def DiagnosticResponse( *args ):
|
||||
start = Location( 1, 2, 'TEST_BUFFER' )
|
||||
end = Location( 1, 4, 'TEST_BUFFER' )
|
||||
extent = Range( start, end )
|
||||
diagnostic = Diagnostic( [], start, extent, 'expected ;', 'ERROR' )
|
||||
return [ BuildDiagnosticData( diagnostic ) ]
|
||||
|
||||
with MockArbitraryBuffer( 'cpp' ):
|
||||
with MockEventNotification( DiagnosticResponse ):
|
||||
self.server_state.OnFileReadyToParse()
|
||||
ok_( self.server_state.FileParseRequestReady() )
|
||||
self.server_state.HandleFileParseRequest()
|
||||
vim_command.assert_has_calls( [
|
||||
PlaceSign_Call( 1, 1, 0, True )
|
||||
] )
|
||||
eq_( self.server_state.GetErrorCount(), 1 )
|
||||
eq_( self.server_state.GetWarningCount(), 0 )
|
||||
|
||||
# Consequent calls to HandleFileParseRequest shouldn't mess with
|
||||
# existing diagnostics, when there is no new parse request.
|
||||
vim_command.reset_mock()
|
||||
ok_( not self.server_state.FileParseRequestReady() )
|
||||
self.server_state.HandleFileParseRequest()
|
||||
vim_command.assert_not_called()
|
||||
eq_( self.server_state.GetErrorCount(), 1 )
|
||||
eq_( self.server_state.GetWarningCount(), 0 )
|
||||
|
||||
|
||||
@patch( 'vim.command' )
|
||||
def _Check_FileReadyToParse_Diagnostic_Warning( self, vim_command ):
|
||||
# Tests Vim sign placement/unplacement and error/warning count python API
|
||||
# when one warning is returned.
|
||||
# Should be called after _Check_FileReadyToParse_Diagnostic_Error
|
||||
def DiagnosticResponse( *args ):
|
||||
start = Location( 2, 2, 'TEST_BUFFER' )
|
||||
end = Location( 2, 4, 'TEST_BUFFER' )
|
||||
extent = Range( start, end )
|
||||
diagnostic = Diagnostic( [], start, extent, 'cast', 'WARNING' )
|
||||
return [ BuildDiagnosticData( diagnostic ) ]
|
||||
|
||||
with MockArbitraryBuffer( 'cpp' ):
|
||||
with MockEventNotification( DiagnosticResponse ):
|
||||
self.server_state.OnFileReadyToParse()
|
||||
ok_( self.server_state.FileParseRequestReady() )
|
||||
self.server_state.HandleFileParseRequest()
|
||||
vim_command.assert_has_calls( [
|
||||
PlaceSign_Call( 2, 2, 0, False ),
|
||||
UnplaceSign_Call( 1, 0 )
|
||||
] )
|
||||
eq_( self.server_state.GetErrorCount(), 0 )
|
||||
eq_( self.server_state.GetWarningCount(), 1 )
|
||||
|
||||
# Consequent calls to HandleFileParseRequest shouldn't mess with
|
||||
# existing diagnostics, when there is no new parse request.
|
||||
vim_command.reset_mock()
|
||||
ok_( not self.server_state.FileParseRequestReady() )
|
||||
self.server_state.HandleFileParseRequest()
|
||||
vim_command.assert_not_called()
|
||||
eq_( self.server_state.GetErrorCount(), 0 )
|
||||
eq_( self.server_state.GetWarningCount(), 1 )
|
||||
|
||||
|
||||
@patch( 'vim.command' )
|
||||
def _Check_FileReadyToParse_Diagnostic_Clean( self, vim_command ):
|
||||
# Tests Vim sign unplacement and error/warning count python API
|
||||
# when there are no errors/warnings left.
|
||||
# Should be called after _Check_FileReadyToParse_Diagnostic_Warning
|
||||
with MockArbitraryBuffer( 'cpp' ):
|
||||
with MockEventNotification( MagicMock( return_value = [] ) ):
|
||||
self.server_state.OnFileReadyToParse()
|
||||
self.server_state.HandleFileParseRequest()
|
||||
vim_command.assert_has_calls( [
|
||||
UnplaceSign_Call( 2, 0 )
|
||||
] )
|
||||
eq_( self.server_state.GetErrorCount(), 0 )
|
||||
eq_( self.server_state.GetWarningCount(), 0 )
|
||||
|
||||
|
@ -75,6 +75,7 @@ SERVER_CRASH_MESSAGE_STDERR_FILE_DELETED = (
|
||||
"Logfile was deleted; set 'g:ycm_server_keep_logfiles' to see errors "
|
||||
"in the future." )
|
||||
SERVER_IDLE_SUICIDE_SECONDS = 10800 # 3 hours
|
||||
DIAGNOSTIC_UI_FILETYPES = set( [ 'cpp', 'cs', 'c', 'objc', 'objcpp' ] )
|
||||
|
||||
|
||||
class YouCompleteMe( object ):
|
||||
@ -85,6 +86,7 @@ class YouCompleteMe( object ):
|
||||
self._omnicomp = OmniCompleter( user_options )
|
||||
self._latest_file_parse_request = None
|
||||
self._latest_completion_request = None
|
||||
self._latest_diagnostics = []
|
||||
self._server_stdout = None
|
||||
self._server_stderr = None
|
||||
self._server_popen = None
|
||||
@ -447,56 +449,64 @@ class YouCompleteMe( object ):
|
||||
return None
|
||||
return completion[ "extra_data" ][ "required_namespace_import" ]
|
||||
|
||||
|
||||
def GetErrorCount( self ):
|
||||
return self._diag_interface.GetErrorCount()
|
||||
|
||||
|
||||
def GetWarningCount( self ):
|
||||
return self._diag_interface.GetWarningCount()
|
||||
|
||||
def DiagnosticsForCurrentFileReady( self ):
|
||||
return bool( self._latest_file_parse_request and
|
||||
self._latest_file_parse_request.Done() )
|
||||
|
||||
def DiagnosticUiSupportedForCurrentFiletype( self ):
|
||||
return any( [ x in DIAGNOSTIC_UI_FILETYPES
|
||||
for x in vimsupport.CurrentFiletypes() ] )
|
||||
|
||||
|
||||
def GetDiagnosticsFromStoredRequest( self, qflist_format = False ):
|
||||
if self.DiagnosticsForCurrentFileReady():
|
||||
diagnostics = self._latest_file_parse_request.Response()
|
||||
# We set the diagnostics request to None because we want to prevent
|
||||
# repeated refreshing of the buffer with the same diags. Setting this to
|
||||
# None makes DiagnosticsForCurrentFileReady return False until the next
|
||||
# request is created.
|
||||
self._latest_file_parse_request = None
|
||||
if qflist_format:
|
||||
return vimsupport.ConvertDiagnosticsToQfList( diagnostics )
|
||||
else:
|
||||
return diagnostics
|
||||
return []
|
||||
def ShouldDisplayDiagnostics( self ):
|
||||
return bool( self._user_options[ 'show_diagnostics_ui' ] and
|
||||
self.DiagnosticUiSupportedForCurrentFiletype() )
|
||||
|
||||
|
||||
def PopulateLocationListWithLatestDiagnostics( self ):
|
||||
# Do nothing if loc list is already populated by diag_interface
|
||||
if not self._user_options[ 'always_populate_location_list' ]:
|
||||
self._diag_interface.PopulateLocationList( self._latest_diagnostics )
|
||||
return bool( self._latest_diagnostics )
|
||||
|
||||
|
||||
def UpdateDiagnosticInterface( self ):
|
||||
if ( self.DiagnosticsForCurrentFileReady() and
|
||||
self.NativeFiletypeCompletionUsable() ):
|
||||
self._diag_interface.UpdateWithNewDiagnostics(
|
||||
self.GetDiagnosticsFromStoredRequest() )
|
||||
self._diag_interface.UpdateWithNewDiagnostics( self._latest_diagnostics )
|
||||
|
||||
|
||||
def ValidateParseRequest( self ):
|
||||
if ( self.DiagnosticsForCurrentFileReady() and
|
||||
def FileParseRequestReady( self, block = False ):
|
||||
return bool( self._latest_file_parse_request and
|
||||
( block or self._latest_file_parse_request.Done() ) )
|
||||
|
||||
|
||||
def HandleFileParseRequest( self, block = False ):
|
||||
# Order is important here:
|
||||
# FileParseRequestReady has a low cost, while
|
||||
# NativeFiletypeCompletionUsable is a blocking server request
|
||||
if ( self.FileParseRequestReady( block ) and
|
||||
self.NativeFiletypeCompletionUsable() ):
|
||||
|
||||
# YCM client has a hard-coded list of filetypes which are known to support
|
||||
# diagnostics. These are found in autoload/youcompleteme.vim in
|
||||
# s:diagnostic_ui_filetypes.
|
||||
if self.ShouldDisplayDiagnostics():
|
||||
self._latest_diagnostics = self._latest_file_parse_request.Response()
|
||||
self.UpdateDiagnosticInterface()
|
||||
else:
|
||||
# YCM client has a hard-coded list of filetypes which are known
|
||||
# to support diagnostics, self.DiagnosticUiSupportedForCurrentFiletype()
|
||||
#
|
||||
# For filetypes which don't support diagnostics, we just want to check the
|
||||
# _latest_file_parse_request for any exception or UnknownExtraConf
|
||||
# For filetypes which don't support diagnostics, we just want to check
|
||||
# the _latest_file_parse_request for any exception or UnknownExtraConf
|
||||
# response, to allow the server to raise configuration warnings, etc.
|
||||
# to the user. We ignore any other supplied data.
|
||||
self._latest_file_parse_request.Response()
|
||||
|
||||
# We set the diagnostics request to None because we want to prevent
|
||||
# We set the file parse request to None because we want to prevent
|
||||
# repeated issuing of the same warnings/errors/prompts. Setting this to
|
||||
# None makes DiagnosticsForCurrentFileReady return False until the next
|
||||
# None makes FileParseRequestReady return False until the next
|
||||
# request is created.
|
||||
#
|
||||
# Note: it is the server's responsibility to determine the frequency of
|
||||
|
Loading…
Reference in New Issue
Block a user