Browse Source

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.
Strahinja Val Markovic 11 years ago
parent
commit
6c01881e1a

+ 51 - 46
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()' )

+ 11 - 3
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.

+ 1 - 1
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' ],

+ 55 - 0
python/ycm/diagnostic_interface.py

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

+ 22 - 2
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.

+ 9 - 0
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