Replaced Syntastic support with YCM-native code

Currently, the only supported Syntastic features are the error signs in the
gutter. Other features will be added in the future.
This commit is contained in:
Strahinja Val Markovic 2014-01-04 14:28:27 -08:00
parent e8d1a4cef8
commit 6c01881e1a
6 changed files with 149 additions and 52 deletions

View File

@ -29,7 +29,7 @@ let s:cursor_moved = 0
let s:moved_vertically_in_insert_mode = 0
let s:previous_num_chars_on_current_line = -1
let s:forced_syntastic_checker_for = {
let s:diagnostic_ui_filetypes = {
\ 'cpp': 1,
\ 'c': 1,
\ 'objc': 1,
@ -70,10 +70,12 @@ function! youcompleteme#Enable()
call s:SetUpCompleteopt()
call s:SetUpKeyMappings()
if g:ycm_register_as_syntastic_checker
call s:TweakSyntasticOptions()
if g:ycm_show_diagnostics_ui
call s:TurnOffSyntasticForCFamily()
endif
call s:SetUpSigns()
if g:ycm_allow_changing_updatetime
set ut=2000
endif
@ -157,6 +159,41 @@ function! s:SetUpKeyMappings()
endfunction
function! s:SetUpSigns()
" We try to ensure backwards compatibility with Syntastic if the user has
" already defined styling for Syntastic highlight groups.
if !hlexists( 'YcmErrorSign' )
if hlexists( 'SyntasticErrorSign')
highlight link YcmErrorSign SyntasticErrorSign
else
highlight link YcmErrorSign error
endif
endif
if !hlexists( 'YcmWarningSign' )
if hlexists( 'SyntasticWarningSign')
highlight link YcmWarningSign SyntasticWarningSign
else
highlight link YcmWarningSign todo
endif
endif
if !hlexists( 'YcmErrorLine' )
highlight link YcmErrorLine SyntasticErrorLine
endif
if !hlexists( 'YcmWarningLine' )
highlight link YcmWarningLine SyntasticWarningLine
endif
exe 'sign define YcmError text=' . g:ycm_error_symbol .
\ ' texthl=YcmErrorSign linehl=YcmErrorLine'
exe 'sign define YcmWarning text=' . g:ycm_warning_symbol .
\ ' texthl=YcmWarningSign linehl=YcmWarningLine'
endfunction
function! s:SetUpBackwardsCompatibility()
let complete_in_comments_and_strings =
\ get( g:, 'ycm_complete_in_comments_and_strings', 0 )
@ -173,38 +210,17 @@ function! s:SetUpBackwardsCompatibility()
endfunction
function! s:TweakSyntasticOptions()
call s:ForceCFamilyFiletypesSyntasticPassiveMode()
call s:ForceSyntasticCFamilyChecker()
" We set this to work around segfaults in old versions of Vim
" See here for details: https://github.com/scrooloose/syntastic/issues/834
let g:syntastic_delayed_redraws = 1
" Needed so that YCM is used instead of Syntastic
function! s:TurnOffSyntasticForCFamily()
let g:syntastic_cpp_checkers = []
let g:syntastic_c_checkers = []
let g:syntastic_objc_checkers = []
let g:syntastic_objcpp_checkers = []
endfunction
" Needed so that YCM is used as the syntastic checker
function! s:ForceSyntasticCFamilyChecker()
let g:syntastic_cpp_checkers = ['ycm']
let g:syntastic_c_checkers = ['ycm']
let g:syntastic_objc_checkers = ['ycm']
let g:syntastic_objcpp_checkers = ['ycm']
endfunction
" Needed so that Syntastic doesn't call :SyntasticCheck (and thus YCM code) on
" file save unnecessarily. We call :SyntasticCheck ourselves often enough.
function! s:ForceCFamilyFiletypesSyntasticPassiveMode()
let mode_map = get( g:, 'syntastic_mode_map', {} )
let mode_map.passive_filetypes = get( mode_map, 'passive_filetypes', [] ) +
\ ['cpp', 'c', 'objc', 'objcpp']
let g:syntastic_mode_map = mode_map
endfunction
function! s:ForcedAsSyntasticCheckerForCurrentFiletype()
return g:ycm_register_as_syntastic_checker &&
\ get( s:forced_syntastic_checker_for, &filetype, 0 )
function! s:DiagnosticUiSupportedForCurrentFiletype()
return get( s:diagnostic_ui_filetypes, &filetype, 0 )
endfunction
@ -464,18 +480,15 @@ endfunction
function! s:UpdateDiagnosticNotifications()
let should_display_diagnostics =
\ get( g:, 'loaded_syntastic_plugin', 0 ) &&
\ s:ForcedAsSyntasticCheckerForCurrentFiletype() &&
let should_display_diagnostics = g:ycm_show_diagnostics_ui &&
\ s:DiagnosticUiSupportedForCurrentFiletype() &&
\ pyeval( 'ycm_state.NativeFiletypeCompletionUsable()' )
if !should_display_diagnostics
return
endif
if pyeval( 'ycm_state.DiagnosticsForCurrentFileReady()' )
SyntasticCheck
endif
py ycm_state.UpdateDiagnosticInterface()
endfunction
@ -639,14 +652,6 @@ endfunction
command! YcmShowDetailedDiagnostic call s:ShowDetailedDiagnostic()
" This is what Syntastic calls indirectly when it decides an auto-check is
" required (currently that's on buffer save) OR when the SyntasticCheck command
" is invoked
function! youcompleteme#CurrentFileDiagnostics()
return pyeval( 'ycm_state.GetDiagnosticsFromStoredRequest()' )
endfunction
function! s:DebugInfo()
echom "Printing YouCompleteMe debug information..."
let debug_info = pyeval( 'ycm_state.DebugInfo()' )

View File

@ -71,9 +71,6 @@ let g:loaded_youcompleteme = 1
" The only defaults that are here are the ones that are only relevant to the YCM
" Vim client and not the server.
let g:ycm_register_as_syntastic_checker =
\ get( g:, 'ycm_register_as_syntastic_checker', 1 )
let g:ycm_allow_changing_updatetime =
\ get( g:, 'ycm_allow_changing_updatetime', 1 )
@ -116,6 +113,17 @@ let g:ycm_extra_conf_vim_data =
let g:ycm_path_to_python_interpreter =
\ get( g:, 'ycm_path_to_python_interpreter', '' )
let g:ycm_show_diagnostics_ui =
\ get( g:, 'ycm_show_diagnostics_ui',
\ get( g:, 'ycm_register_as_syntastic_checker', 1 ) )
let g:ycm_error_symbol =
\ get( g:, 'ycm_error_symbol',
\ get( g:, 'syntastic_error_symbol', '>>' ) )
let g:ycm_warning_symbol =
\ get( g:, 'ycm_warning_symbol',
\ get( g:, 'syntastic_warning_symbol', '>>' ) )
" On-demand loading. Let's use the autoload folder and not slow down vim's
" startup procedure.

View File

@ -77,7 +77,7 @@ def _ConvertDiagnosticDataToVimData( diagnostic ):
# line/column numbers are 1 or 0 based in its various APIs. Here, it wants
# them to be 1-based.
return {
'bufnr' : vimsupport.GetBufferNumberForFilename( diagnostic[ 'filepath' ]),
'bufnr' : vimsupport.GetBufferNumberForFilename( diagnostic[ 'filepath' ] ),
'lnum' : diagnostic[ 'line_num' ] + 1,
'col' : diagnostic[ 'column_num' ] + 1,
'text' : diagnostic[ 'text' ],

View File

@ -0,0 +1,55 @@
#!/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/>.
from collections import defaultdict
from operator import itemgetter
from ycm import vimsupport
class DiagnosticInterface( object ):
def __init__( self ):
self._buffer_number_to_diags = {}
self._next_sign_id = 1
def UpdateWithNewDiagnostics( self, diags ):
self._buffer_number_to_diags = ConvertDiagListToDict( diags )
for buffer_number, buffer_diags in self._buffer_number_to_diags.iteritems():
if not vimsupport.BufferIsVisible( buffer_number ):
continue
vimsupport.UnplaceAllSignsInBuffer( buffer_number )
for diag in buffer_diags:
vimsupport.PlaceSign( self._next_sign_id,
diag[ 'lnum' ],
buffer_number,
diag[ 'type' ] == 'E' )
self._next_sign_id += 1
def ConvertDiagListToDict( diags ):
buffer_to_diags = defaultdict( list )
for diag in diags:
buffer_to_diags[ diag[ 'bufnr' ] ].append( diag )
for buffer_diags in buffer_to_diags.itervalues():
# We also want errors to be listed before warnings so that errors aren't
# hidden by the warnings; Vim won't place a sign oven an existing one.
buffer_diags.sort( key = lambda diag: itemgetter( 'lnum', 'col', 'type' ) )
return buffer_to_diags

View File

@ -83,15 +83,22 @@ def GetUnsavedAndCurrentBufferData():
def GetBufferNumberForFilename( filename, open_file_if_needed = True ):
return int( vim.eval( "bufnr('{0}', {1})".format(
return GetIntValue( "bufnr('{0}', {1})".format(
os.path.realpath( filename ),
int( open_file_if_needed ) ) ) )
int( open_file_if_needed ) ) )
def GetCurrentBufferFilepath():
return GetBufferFilepath( vim.current.buffer )
def BufferIsVisible( buffer_number ):
if buffer_number < 0:
return False
window_number = GetIntValue( "bufwinnr({0})".format( buffer_number ) )
return window_number != -1
def GetBufferFilepath( buffer_object ):
if buffer_object.name:
return buffer_object.name
@ -100,6 +107,18 @@ def GetBufferFilepath( buffer_object ):
return os.path.join( os.getcwd(), str( buffer_object.number ) )
def UnplaceAllSignsInBuffer( buffer_number ):
if buffer_number < 0:
return
vim.command( 'sign unplace * buffer={0}'.format( buffer_number ) )
def PlaceSign( sign_id, line_num, buffer_num, is_error = True ):
sign_name = 'YcmError' if is_error else 'YcmWarning'
vim.command( 'sign place {0} line={1} name={2} buffer={3}'.format(
sign_id, line_num, sign_name, buffer_num ) )
# Given a dict like {'a': 1}, loads it into Vim as if you ran 'let g:a = 1'
# When |overwrite| is True, overwrites the existing value in Vim.
def LoadDictIntoVimGlobals( new_globals, overwrite = True ):
@ -164,6 +183,7 @@ def PostVimMessage( message ):
vim.command( "echohl WarningMsg | echom '{0}' | echohl None"
.format( EscapeForVim( str( message ) ) ) )
# Unlike PostVimMesasge, this supports messages with newlines in them because it
# uses 'echo' instead of 'echomsg'. This also means that the message will NOT
# appear in Vim's message log.

View File

@ -23,6 +23,7 @@ import tempfile
import json
from ycm import vimsupport
from ycm import utils
from ycm.diagnostic_interface import DiagnosticInterface
from ycm.completers.all.omni_completer import OmniCompleter
from ycm.completers.general import syntax_parse
from ycm.completers.completer_utils import FiletypeCompleterExistsForFiletype
@ -64,6 +65,7 @@ class YouCompleteMe( object ):
def __init__( self, user_options ):
self._user_options = user_options
self._user_notified_about_crash = False
self._diag_interface = DiagnosticInterface()
self._omnicomp = OmniCompleter( user_options )
self._latest_completion_request = None
self._latest_file_parse_request = None
@ -271,6 +273,13 @@ class YouCompleteMe( object ):
return []
def UpdateDiagnosticInterface( self ):
if not self.DiagnosticsForCurrentFileReady():
return
self._diag_interface.UpdateWithNewDiagnostics(
self.GetDiagnosticsFromStoredRequest() )
def ShowDetailedDiagnostic( self ):
if not self._IsServerAlive():
return