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._files_being_compiled = EphemeralValuesSet() def SupportedFiletypes( self ): return CLANG_FILETYPES def GetUnsavedFilesVector( self, request_data ): files = ycm_core.UnsavedFileVector() for filename, file_data in iteritems( request_data[ 'file_data' ] ): 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 = ToCppStringCompatible( contents ) unsaved_file.contents_ = utf8_contents unsaved_file.length_ = len( utf8_contents ) unsaved_file.filename_ = ToCppStringCompatible( filename ) files.append( unsaved_file ) return files def ComputeCandidatesInner( self, request_data ): filename = request_data[ 'filepath' ] if not filename: return if self._completer.UpdatingTranslationUnit( ToCppStringCompatible( filename ) ): raise RuntimeError( PARSING_FILE_MESSAGE ) flags = self._FlagsForRequest( request_data ) if not flags: raise RuntimeError( NO_COMPILE_FLAGS_MESSAGE ) files = self.GetUnsavedFilesVector( request_data ) line = request_data[ 'line_num' ] column = request_data[ 'start_column' ] with self._files_being_compiled.GetExclusive( filename ): results = self._completer.CandidatesForLocationInFile( ToCppStringCompatible( filename ), line, column, files, flags ) if not results: raise RuntimeError( NO_COMPLETIONS_MESSAGE ) return [ ConvertCompletionData( x ) for x in results ] def GetSubcommandsMap( self ): return { 'GoToDefinition' : ( lambda self, request_data, args: self._GoToDefinition( request_data ) ), 'GoToDeclaration' : ( lambda self, request_data, args: self._GoToDeclaration( request_data ) ), 'GoTo' : ( lambda self, request_data, args: self._GoTo( request_data ) ), 'GoToImprecise' : ( lambda self, request_data, args: self._GoToImprecise( request_data ) ), 'GoToInclude' : ( lambda self, request_data, args: self._GoToInclude( request_data ) ), 'ClearCompilationFlagCache': ( lambda self, request_data, args: self._ClearCompilationFlagCache() ), 'GetType' : ( lambda self, request_data, args: self._GetSemanticInfo( request_data, func = 'GetTypeAtLocation' ) ), 'GetTypeImprecise' : ( lambda self, request_data, args: self._GetSemanticInfo( request_data, func = 'GetTypeAtLocation', reparse = False ) ), 'GetParent' : ( lambda self, request_data, args: self._GetSemanticInfo( request_data, func = 'GetEnclosingFunctionAtLocation' ) ), 'FixIt' : ( lambda self, request_data, args: self._FixIt( request_data ) ), 'GetDoc' : ( lambda self, request_data, args: self._GetSemanticInfo( request_data, reparse = True, func = 'GetDocsForLocationInFile', response_builder = _BuildGetDocResponse ) ), 'GetDocImprecise' : ( lambda self, request_data, args: self._GetSemanticInfo( request_data, reparse = False, func = 'GetDocsForLocationInFile', response_builder = _BuildGetDocResponse ) ), } def _LocationForGoTo( self, goto_function, request_data, reparse = True ): filename = request_data[ 'filepath' ] if not filename: raise ValueError( INVALID_FILE_MESSAGE ) flags = self._FlagsForRequest( request_data ) if not flags: raise ValueError( NO_COMPILE_FLAGS_MESSAGE ) files = self.GetUnsavedFilesVector( request_data ) line = request_data[ 'line_num' ] column = request_data[ 'column_num' ] return getattr( self._completer, goto_function )( ToCppStringCompatible( filename ), line, column, files, flags, reparse ) 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 _ResponseForLocation( location ) 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 _ResponseForLocation( location ) def _GoTo( self, request_data ): include_response = self._ResponseForInclude( request_data ) if include_response: return include_response 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 _ResponseForLocation( location ) def _GoToImprecise( self, request_data ): include_response = self._ResponseForInclude( request_data ) if include_response: return include_response location = self._LocationForGoTo( 'GetDefinitionLocation', request_data, reparse = False ) if not location or not location.IsValid(): location = self._LocationForGoTo( 'GetDeclarationLocation', request_data, reparse = False ) if not location or not location.IsValid(): raise RuntimeError( 'Can\'t jump to definition or declaration.' ) return _ResponseForLocation( location ) def _ResponseForInclude( self, request_data ): """Returns response for include file location if cursor is on the include statement, None otherwise. Throws RuntimeError if cursor is on include statement and corresponding include file not found.""" current_line = request_data[ 'line_value' ] include_file_name, quoted_include = GetIncludeStatementValue( current_line ) if not include_file_name: return None current_file_path = request_data[ 'filepath' ] client_data = request_data.get( 'extra_conf_data', None ) quoted_include_paths, include_paths = ( self._flags.UserIncludePaths( current_file_path, client_data ) ) if quoted_include: include_file_path = _GetAbsolutePath( include_file_name, quoted_include_paths ) if include_file_path: return responses.BuildGoToResponse( include_file_path, line_num = 1, column_num = 1 ) include_file_path = _GetAbsolutePath( include_file_name, include_paths ) if include_file_path: return responses.BuildGoToResponse( include_file_path, line_num = 1, column_num = 1 ) raise RuntimeError( 'Include file not found.' ) def _GoToInclude( self, request_data ): include_response = self._ResponseForInclude( request_data ) if not include_response: raise RuntimeError( 'Not an include/import line.' ) return include_response def _GetSemanticInfo( self, request_data, func, response_builder = responses.BuildDisplayMessageResponse, reparse = True ): filename = request_data[ 'filepath' ] if not filename: raise ValueError( INVALID_FILE_MESSAGE ) flags = self._FlagsForRequest( request_data ) if not flags: raise ValueError( NO_COMPILE_FLAGS_MESSAGE ) files = self.GetUnsavedFilesVector( request_data ) line = request_data[ 'line_num' ] column = request_data[ 'column_num' ] message = getattr( self._completer, func )( ToCppStringCompatible( filename ), line, column, files, flags, reparse) if not message: message = "No semantic information available" return response_builder( message ) def _ClearCompilationFlagCache( self ): self._flags.Clear() def _FixIt( self, request_data ): filename = request_data[ 'filepath' ] if not filename: raise ValueError( INVALID_FILE_MESSAGE ) flags = self._FlagsForRequest( request_data ) if not flags: raise ValueError( NO_COMPILE_FLAGS_MESSAGE ) files = self.GetUnsavedFilesVector( request_data ) line = request_data[ 'line_num' ] column = request_data[ 'column_num' ] fixits = getattr( self._completer, "GetFixItsForLocationInFile" )( ToCppStringCompatible( filename ), line, column, files, flags, True ) # don't raise an error if not fixits: - leave that to the client to respond # in a nice way return responses.BuildFixItResponse( fixits ) def OnFileReadyToParse( self, request_data ): filename = request_data[ 'filepath' ] if not filename: raise ValueError( INVALID_FILE_MESSAGE ) flags = self._FlagsForRequest( request_data ) if not flags: raise ValueError( NO_COMPILE_FLAGS_MESSAGE ) with self._files_being_compiled.GetExclusive( filename ): diagnostics = self._completer.UpdateTranslationUnit( ToCppStringCompatible( filename ), self.GetUnsavedFilesVector( request_data ), flags ) diagnostics = _FilterDiagnostics( diagnostics ) self._diagnostic_store = DiagnosticsToDiagStructure( diagnostics ) return [ responses.BuildDiagnosticData( x ) for x in diagnostics[ : self._max_diagnostics_to_display ] ] def OnBufferUnload( self, request_data ): self._completer.DeleteCachesForFile( ToCppStringCompatible( request_data[ 'filepath' ] ) ) def GetDetailedDiagnostic( self, request_data ): current_line = request_data[ 'line_num' ] current_column = request_data[ 'column_num' ] 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 # FIXME: all of these calculations are currently working with byte # offsets, which are technically incorrect. We should be working with # codepoint offsets, as we want the nearest character-wise diagnostic for diagnostic in diagnostics: distance = abs( current_column - diagnostic.location_.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' ] try: extra_conf = extra_conf_store.ModuleFileForSourceFile( filename ) except UnknownExtraConf as error: return ( 'C-family completer debug information:\n' ' Configuration file found but not loaded\n' ' Configuration path: {0}'.format( error.extra_conf_file ) ) try: # Note that it only raises NoExtraConfDetected: # - when extra_conf is None and, # - there is no compilation database flags = self._FlagsForRequest( request_data ) except NoExtraConfDetected: # No flags return ( 'C-family completer debug information:\n' ' No configuration file found\n' ' No compilation database found' ) # If _FlagsForRequest returns None or raises, we use an empty list in # practice. flags = flags or [] if extra_conf: # We got the flags from the extra conf file return ( 'C-family completer debug information:\n' ' Configuration file found and loaded\n' ' Configuration path: {0}\n' ' Flags: {1}'.format( extra_conf, list( flags ) ) ) try: database = self._flags.FindCompilationDatabase( os.path.dirname( filename ) ) except NoCompilationDatabase: # No flags return ( 'C-family completer debug information:\n' ' No configuration file found\n' ' No compilation database found' ) # We got the flags from the compilation database return ( 'C-family completer debug information:\n' ' No configuration file found\n' ' Using compilation database from: {0}\n' ' Flags: {1}'.format( database.database_directory, list( flags ) ) ) def _FlagsForRequest( self, request_data ): filename = request_data[ 'filepath' ] if 'compilation_flags' in request_data: return PrepareFlagsForClang( request_data[ 'compilation_flags' ], filename ) client_data = request_data.get( 'extra_conf_data', None ) return self._flags.FlagsForFile( filename, client_data = client_data )
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._files_being_compiled = EphemeralValuesSet() self._logger = logging.getLogger(__name__) def SupportedFiletypes(self): return CLANG_FILETYPES def GetUnsavedFilesVector(self, request_data): files = ycm_core.UnsavedFileVector() for filename, file_data in iteritems(request_data['file_data']): 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 = ToCppStringCompatible(contents) unsaved_file.contents_ = utf8_contents unsaved_file.length_ = len(utf8_contents) unsaved_file.filename_ = ToCppStringCompatible(filename) files.append(unsaved_file) return files def ShouldCompleteIncludeStatement(self, request_data): column_codepoint = request_data['column_codepoint'] - 1 current_line = request_data['line_value'] return INCLUDE_REGEX.match(current_line[:column_codepoint]) def ShouldUseNowInner(self, request_data): if self.ShouldCompleteIncludeStatement(request_data): return True return super(ClangCompleter, self).ShouldUseNowInner(request_data) def GetIncludePaths(self, request_data): column_codepoint = request_data['column_codepoint'] - 1 current_line = request_data['line_value'] line = current_line[:column_codepoint] path_dir, quoted_include, start_codepoint = ( GetIncompleteIncludeValue(line)) if start_codepoint is None: return None request_data['start_codepoint'] = start_codepoint # We do what GCC does for <> versus "": # http://gcc.gnu.org/onlinedocs/cpp/Include-Syntax.html flags = self._FlagsForRequest(request_data) filepath = request_data['filepath'] quoted_include_paths, include_paths = UserIncludePaths(flags, filepath) if quoted_include: include_paths.extend(quoted_include_paths) paths = [] for include_path in include_paths: unicode_path = ToUnicode(os.path.join(include_path, path_dir)) try: # We need to pass a unicode string to get unicode strings out of # listdir. relative_paths = os.listdir(unicode_path) except Exception: self._logger.exception('Error while listing %s folder.', unicode_path) relative_paths = [] paths.extend( os.path.join(include_path, path_dir, relative_path) for relative_path in relative_paths) return paths def ComputeCandidatesInner(self, request_data): filename = request_data['filepath'] if not filename: return paths = self.GetIncludePaths(request_data) if paths is not None: return GenerateCandidatesForPaths(paths) if self._completer.UpdatingTranslationUnit( ToCppStringCompatible(filename)): raise RuntimeError(PARSING_FILE_MESSAGE) flags = self._FlagsForRequest(request_data) if not flags: raise RuntimeError(NO_COMPILE_FLAGS_MESSAGE) files = self.GetUnsavedFilesVector(request_data) line = request_data['line_num'] column = request_data['start_column'] with self._files_being_compiled.GetExclusive(filename): results = self._completer.CandidatesForLocationInFile( ToCppStringCompatible(filename), line, column, files, flags) if not results: raise RuntimeError(NO_COMPLETIONS_MESSAGE) return [ConvertCompletionData(x) for x in results] def GetSubcommandsMap(self): return { 'GoToDefinition': (lambda self, request_data, args: self. _GoToDefinition(request_data)), 'GoToDeclaration': (lambda self, request_data, args: self. _GoToDeclaration(request_data)), 'GoTo': (lambda self, request_data, args: self._GoTo(request_data)), 'GoToImprecise': (lambda self, request_data, args: self._GoToImprecise(request_data) ), 'GoToInclude': (lambda self, request_data, args: self._GoToInclude(request_data)), 'ClearCompilationFlagCache': (lambda self, request_data, args: self ._ClearCompilationFlagCache()), 'GetType': (lambda self, request_data, args: self._GetSemanticInfo( request_data, func='GetTypeAtLocation')), 'GetTypeImprecise': (lambda self, request_data, args: self._GetSemanticInfo( request_data, func='GetTypeAtLocation', reparse=False)), 'GetParent': (lambda self, request_data, args: self._GetSemanticInfo( request_data, func='GetEnclosingFunctionAtLocation')), 'FixIt': (lambda self, request_data, args: self._FixIt(request_data)), 'GetDoc': (lambda self, request_data, args: self._GetSemanticInfo( request_data, reparse=True, func='GetDocsForLocationInFile', response_builder=_BuildGetDocResponse)), 'GetDocImprecise': (lambda self, request_data, args: self._GetSemanticInfo( request_data, reparse=False, func='GetDocsForLocationInFile', response_builder=_BuildGetDocResponse)), } def _LocationForGoTo(self, goto_function, request_data, reparse=True): filename = request_data['filepath'] if not filename: raise ValueError(INVALID_FILE_MESSAGE) flags = self._FlagsForRequest(request_data) if not flags: raise ValueError(NO_COMPILE_FLAGS_MESSAGE) files = self.GetUnsavedFilesVector(request_data) line = request_data['line_num'] column = request_data['column_num'] return getattr(self._completer, goto_function)(ToCppStringCompatible(filename), line, column, files, flags, reparse) 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 _ResponseForLocation(location) 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 _ResponseForLocation(location) def _GoTo(self, request_data): include_response = self._ResponseForInclude(request_data) if include_response: return include_response 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 _ResponseForLocation(location) def _GoToImprecise(self, request_data): include_response = self._ResponseForInclude(request_data) if include_response: return include_response location = self._LocationForGoTo('GetDefinitionLocation', request_data, reparse=False) if not location or not location.IsValid(): location = self._LocationForGoTo('GetDeclarationLocation', request_data, reparse=False) if not location or not location.IsValid(): raise RuntimeError('Can\'t jump to definition or declaration.') return _ResponseForLocation(location) def _ResponseForInclude(self, request_data): """Returns response for include file location if cursor is on the include statement, None otherwise. Throws RuntimeError if cursor is on include statement and corresponding include file not found.""" current_line = request_data['line_value'] include_file_name, quoted_include = GetFullIncludeValue(current_line) if not include_file_name: return None flags = self._FlagsForRequest(request_data) current_file_path = request_data['filepath'] quoted_include_paths, include_paths = UserIncludePaths( flags, current_file_path) if quoted_include: include_file_path = _GetAbsolutePath(include_file_name, quoted_include_paths) if include_file_path: return responses.BuildGoToResponse(include_file_path, line_num=1, column_num=1) include_file_path = _GetAbsolutePath(include_file_name, include_paths) if include_file_path: return responses.BuildGoToResponse(include_file_path, line_num=1, column_num=1) raise RuntimeError('Include file not found.') def _GoToInclude(self, request_data): include_response = self._ResponseForInclude(request_data) if not include_response: raise RuntimeError('Not an include/import line.') return include_response def _GetSemanticInfo( self, request_data, func, response_builder=responses.BuildDisplayMessageResponse, reparse=True): filename = request_data['filepath'] if not filename: raise ValueError(INVALID_FILE_MESSAGE) flags = self._FlagsForRequest(request_data) if not flags: raise ValueError(NO_COMPILE_FLAGS_MESSAGE) files = self.GetUnsavedFilesVector(request_data) line = request_data['line_num'] column = request_data['column_num'] message = getattr(self._completer, func)(ToCppStringCompatible(filename), line, column, files, flags, reparse) if not message: message = "No semantic information available" return response_builder(message) def _ClearCompilationFlagCache(self): self._flags.Clear() def _FixIt(self, request_data): filename = request_data['filepath'] if not filename: raise ValueError(INVALID_FILE_MESSAGE) flags = self._FlagsForRequest(request_data) if not flags: raise ValueError(NO_COMPILE_FLAGS_MESSAGE) files = self.GetUnsavedFilesVector(request_data) line = request_data['line_num'] column = request_data['column_num'] fixits = getattr(self._completer, "GetFixItsForLocationInFile")( ToCppStringCompatible(filename), line, column, files, flags, True) # don't raise an error if not fixits: - leave that to the client to respond # in a nice way return responses.BuildFixItResponse(fixits) def OnFileReadyToParse(self, request_data): filename = request_data['filepath'] if not filename: raise ValueError(INVALID_FILE_MESSAGE) flags = self._FlagsForRequest(request_data) if not flags: raise ValueError(NO_COMPILE_FLAGS_MESSAGE) with self._files_being_compiled.GetExclusive(filename): diagnostics = self._completer.UpdateTranslationUnit( ToCppStringCompatible(filename), self.GetUnsavedFilesVector(request_data), flags) diagnostics = _FilterDiagnostics(diagnostics) self._diagnostic_store = DiagnosticsToDiagStructure(diagnostics) return [ responses.BuildDiagnosticData(x) for x in diagnostics[:self._max_diagnostics_to_display] ] def OnBufferUnload(self, request_data): self._completer.DeleteCachesForFile( ToCppStringCompatible(request_data['filepath'])) def GetDetailedDiagnostic(self, request_data): current_line = request_data['line_num'] current_column = request_data['column_num'] 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 # FIXME: all of these calculations are currently working with byte # offsets, which are technically incorrect. We should be working with # codepoint offsets, as we want the nearest character-wise diagnostic for diagnostic in diagnostics: distance = abs(current_column - diagnostic.location_.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): try: # Note that it only raises NoExtraConfDetected: # - when extra_conf is None and, # - there is no compilation database flags = self._FlagsForRequest(request_data) or [] except (NoExtraConfDetected, UnknownExtraConf): # If _FlagsForRequest returns None or raises, we use an empty list in # practice. flags = [] try: database_directory = self._flags.FindCompilationDatabase( os.path.dirname(request_data['filepath'])).database_directory except NoCompilationDatabase: database_directory = None database_item = responses.DebugInfoItem( key='compilation database path', value='{0}'.format(database_directory)) flags_item = responses.DebugInfoItem(key='flags', value='{0}'.format(list(flags))) return responses.BuildDebugInfoResponse( name='C-family', items=[database_item, flags_item]) def _FlagsForRequest(self, request_data): filename = request_data['filepath'] if 'compilation_flags' in request_data: return PrepareFlagsForClang(request_data['compilation_flags'], filename) client_data = request_data.get('extra_conf_data', None) return self._flags.FlagsForFile(filename, client_data=client_data)