#!/usr/bin/env python # # Copyright (C) 2011, 2012 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 import ycm_core import logging from ycm.server import responses from ycm import extra_conf_store from ycm.utils import ToUtf8IfNeeded from ycm.completers.completer import Completer from ycm.completers.cpp.flags import Flags, PrepareFlagsForClang CLANG_FILETYPES = set( [ 'c', 'cpp', 'objc', 'objcpp' ] ) MIN_LINES_IN_FILE_TO_PARSE = 5 PARSING_FILE_MESSAGE = 'Still parsing file, no completions yet.' NO_COMPILE_FLAGS_MESSAGE = 'Still no compile flags, no completions yet.' INVALID_FILE_MESSAGE = 'File is invalid.' NO_COMPLETIONS_MESSAGE = 'No completions found; errors in the file?' FILE_TOO_SHORT_MESSAGE = ( 'File is less than {0} lines long; not compiling.'.format( MIN_LINES_IN_FILE_TO_PARSE ) ) NO_DIAGNOSTIC_MESSAGE = 'No diagnostic for current line!' class ClangCompleter( Completer ): def __init__( self, user_options ): super( ClangCompleter, self ).__init__( user_options ) self._max_diagnostics_to_display = user_options[ 'max_diagnostics_to_display' ] self._completer = ycm_core.ClangCompleter() self._flags = Flags() self._diagnostic_store = None self._logger = logging.getLogger( __name__ ) def SupportedFiletypes( self ): return CLANG_FILETYPES def GetUnsavedFilesVector( self, request_data ): files = ycm_core.UnsavedFileVec() for filename, file_data in request_data[ 'file_data' ].iteritems(): if not ClangAvailableForFiletypes( file_data[ 'filetypes' ] ): continue contents = file_data[ 'contents' ] if not contents or not filename: continue unsaved_file = ycm_core.UnsavedFile() utf8_contents = ToUtf8IfNeeded( contents ) unsaved_file.contents_ = utf8_contents unsaved_file.length_ = len( utf8_contents ) unsaved_file.filename_ = ToUtf8IfNeeded( filename ) files.append( unsaved_file ) return files def ComputeCandidatesInner( self, request_data ): filename = request_data[ 'filepath' ] if not filename: return if self._completer.UpdatingTranslationUnit( ToUtf8IfNeeded( filename ) ): self._logger.info( PARSING_FILE_MESSAGE ) raise RuntimeError( PARSING_FILE_MESSAGE ) flags = self._FlagsForRequest( request_data ) if not flags: self._logger.info( NO_COMPILE_FLAGS_MESSAGE ) raise RuntimeError( NO_COMPILE_FLAGS_MESSAGE ) files = self.GetUnsavedFilesVector( request_data ) line = request_data[ 'line_num' ] + 1 column = request_data[ 'start_column' ] + 1 results = self._completer.CandidatesForLocationInFile( ToUtf8IfNeeded( filename ), line, column, files, flags ) if not results: self._logger.warning( NO_COMPLETIONS_MESSAGE ) raise RuntimeError( NO_COMPLETIONS_MESSAGE ) return [ ConvertCompletionData( x ) for x in results ] def DefinedSubcommands( self ): return [ 'GoToDefinition', 'GoToDeclaration', 'GoToDefinitionElseDeclaration', 'ClearCompilationFlagCache'] def OnUserCommand( self, arguments, request_data ): if not arguments: raise ValueError( self.UserCommandsHelpMessage() ) command = arguments[ 0 ] if command == 'GoToDefinition': return self._GoToDefinition( request_data ) elif command == 'GoToDeclaration': return self._GoToDeclaration( request_data ) elif command == 'GoToDefinitionElseDeclaration': return self._GoToDefinitionElseDeclaration( request_data ) elif command == 'ClearCompilationFlagCache': return self._ClearCompilationFlagCache() raise ValueError( self.UserCommandsHelpMessage() ) def _LocationForGoTo( self, goto_function, request_data ): filename = request_data[ 'filepath' ] if not filename: self._logger.warning( INVALID_FILE_MESSAGE ) raise ValueError( INVALID_FILE_MESSAGE ) flags = self._FlagsForRequest( request_data ) if not flags: self._logger.info( NO_COMPILE_FLAGS_MESSAGE ) raise ValueError( NO_COMPILE_FLAGS_MESSAGE ) files = self.GetUnsavedFilesVector( request_data ) line = request_data[ 'line_num' ] + 1 column = request_data[ 'column_num' ] + 1 return getattr( self._completer, goto_function )( ToUtf8IfNeeded( filename ), line, column, files, flags ) def _GoToDefinition( self, request_data ): location = self._LocationForGoTo( 'GetDefinitionLocation', request_data ) if not location or not location.IsValid(): raise RuntimeError( 'Can\'t jump to definition.' ) return responses.BuildGoToResponse( location.filename_, location.line_number_ - 1, location.column_number_ - 1) def _GoToDeclaration( self, request_data ): location = self._LocationForGoTo( 'GetDeclarationLocation', request_data ) if not location or not location.IsValid(): raise RuntimeError( 'Can\'t jump to declaration.' ) return responses.BuildGoToResponse( location.filename_, location.line_number_ - 1, location.column_number_ - 1) def _GoToDefinitionElseDeclaration( self, request_data ): location = self._LocationForGoTo( 'GetDefinitionLocation', request_data ) if not location or not location.IsValid(): location = self._LocationForGoTo( 'GetDeclarationLocation', request_data ) if not location or not location.IsValid(): raise RuntimeError( 'Can\'t jump to definition or declaration.' ) return responses.BuildGoToResponse( location.filename_, location.line_number_ - 1, location.column_number_ - 1) def _ClearCompilationFlagCache( self ): self._flags.Clear() def OnFileReadyToParse( self, request_data ): filename = request_data[ 'filepath' ] contents = request_data[ 'file_data' ][ filename ][ 'contents' ] if contents.count( '\n' ) < MIN_LINES_IN_FILE_TO_PARSE: self._logger.warning( FILE_TOO_SHORT_MESSAGE ) raise ValueError( FILE_TOO_SHORT_MESSAGE ) if not filename: self._logger.warning( INVALID_FILE_MESSAGE ) raise ValueError( INVALID_FILE_MESSAGE ) flags = self._FlagsForRequest( request_data ) if not flags: self._logger.info( NO_COMPILE_FLAGS_MESSAGE ) raise ValueError( NO_COMPILE_FLAGS_MESSAGE ) diagnostics = self._completer.UpdateTranslationUnit( ToUtf8IfNeeded( filename ), self.GetUnsavedFilesVector( request_data ), flags ) self._diagnostic_store = DiagnosticsToDiagStructure( diagnostics ) return [ ConvertToDiagnosticResponse( x ) for x in diagnostics[ : self._max_diagnostics_to_display ] ] def OnBufferUnload( self, request_data ): self._completer.DeleteCachesForFile( ToUtf8IfNeeded( request_data[ 'unloaded_buffer' ] ) ) def GettingCompletions( self, request_data ): return self._completer.UpdatingTranslationUnit( ToUtf8IfNeeded( request_data[ 'filepath' ] ) ) def GetDetailedDiagnostic( self, request_data ): current_line = request_data[ 'line_num' ] + 1 current_column = request_data[ 'column_num' ] + 1 current_file = request_data[ 'filepath' ] if not self._diagnostic_store: raise ValueError( NO_DIAGNOSTIC_MESSAGE ) diagnostics = self._diagnostic_store[ current_file ][ current_line ] if not diagnostics: raise ValueError( NO_DIAGNOSTIC_MESSAGE ) closest_diagnostic = None distance_to_closest_diagnostic = 999 for diagnostic in diagnostics: distance = abs( current_column - diagnostic.column_number_ ) if distance < distance_to_closest_diagnostic: distance_to_closest_diagnostic = distance closest_diagnostic = diagnostic return responses.BuildDisplayMessageResponse( closest_diagnostic.long_formatted_text_ ) def DebugInfo( self, request_data ): filename = request_data[ 'filepath' ] if not filename: return '' flags = self._FlagsForRequest( request_data ) or [] source = extra_conf_store.ModuleFileForSourceFile( filename ) return 'Flags for {0} loaded from {1}:\n{2}'.format( filename, source, list( flags ) ) def _FlagsForRequest( self, request_data ): filename = request_data[ 'filepath' ] if 'compilation_flags' in request_data: return PrepareFlagsForClang( request_data[ 'compilation_flags' ], filename ) return self._flags.FlagsForFile( filename ) def ConvertCompletionData( completion_data ): return responses.BuildCompletionData( insertion_text = completion_data.TextToInsertInBuffer(), menu_text = completion_data.MainCompletionText(), extra_menu_info = completion_data.ExtraMenuInfo(), kind = completion_data.kind_, detailed_info = completion_data.DetailedInfoForPreviewWindow() ) def DiagnosticsToDiagStructure( diagnostics ): structure = defaultdict(lambda : defaultdict(list)) for diagnostic in diagnostics: structure[ diagnostic.filename_ ][ diagnostic.line_number_ ].append( diagnostic ) return structure def ClangAvailableForFiletypes( filetypes ): return any( [ filetype in CLANG_FILETYPES for filetype in filetypes ] ) def InCFamilyFile( filetypes ): return ClangAvailableForFiletypes( filetypes ) def ConvertToDiagnosticResponse( diagnostic ): return responses.BuildDiagnosticData( diagnostic.filename_, diagnostic.line_number_ - 1, diagnostic.column_number_ - 1, diagnostic.text_, diagnostic.kind_ )