From 6c01881e1a615b1b87fc5ef20855d11f9fde32a7 Mon Sep 17 00:00:00 2001 From: Strahinja Val Markovic Date: Sat, 4 Jan 2014 14:28:27 -0800 Subject: [PATCH] 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. --- autoload/youcompleteme.vim | 97 +++++++++++++------------ plugin/youcompleteme.vim | 14 +++- python/ycm/client/event_notification.py | 2 +- python/ycm/diagnostic_interface.py | 55 ++++++++++++++ python/ycm/vimsupport.py | 24 +++++- python/ycm/youcompleteme.py | 9 +++ 6 files changed, 149 insertions(+), 52 deletions(-) create mode 100644 python/ycm/diagnostic_interface.py diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index 404b493a..8db48339 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -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()' ) diff --git a/plugin/youcompleteme.vim b/plugin/youcompleteme.vim index f4b5905f..4b0cf32c 100644 --- a/plugin/youcompleteme.vim +++ b/plugin/youcompleteme.vim @@ -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. diff --git a/python/ycm/client/event_notification.py b/python/ycm/client/event_notification.py index 9300e174..498cd5c7 100644 --- a/python/ycm/client/event_notification.py +++ b/python/ycm/client/event_notification.py @@ -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' ], diff --git a/python/ycm/diagnostic_interface.py b/python/ycm/diagnostic_interface.py new file mode 100644 index 00000000..9c9c239b --- /dev/null +++ b/python/ycm/diagnostic_interface.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# +# Copyright (C) 2013 Strahinja Val Markovic +# +# This file is part of YouCompleteMe. +# +# YouCompleteMe is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# YouCompleteMe is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with YouCompleteMe. If not, see . + +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 + diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index e3a3c0ea..95013f66 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -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. diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index 028b9f94..0877fc03 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -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