def IncludeCache_Cached_NoNewMtime_test(): include_cache = IncludeCache() eq_(include_cache._cache, {}) old_includes = include_cache.GetIncludes(PathToTestFile('cache_test')) old_mtime = os.path.getmtime(PathToTestFile('cache_test')) assert_that(old_includes, contains(has_properties({ 'name': 'foo.h', 'entry_type': 1 }))) assert_that( include_cache._cache, has_entry( PathToTestFile('cache_test'), has_entries({ 'mtime': old_mtime, 'includes': contains(has_properties({ 'name': 'foo.h', 'entry_type': 1 })) }))) new_includes = include_cache.GetIncludes(PathToTestFile('cache_test')) new_mtime = os.path.getmtime(PathToTestFile('cache_test')) eq_(new_mtime, old_mtime) assert_that(new_includes, contains(has_properties({ 'name': 'foo.h', 'entry_type': 1 }))) assert_that( include_cache._cache, has_entry( PathToTestFile('cache_test'), has_entries({ 'mtime': new_mtime, 'includes': contains(has_properties({ 'name': 'foo.h', 'entry_type': 1 })) })))
def test_IncludeCache_NotCached_DirAccessible( self ): include_cache = IncludeCache() assert_that( include_cache._cache, equal_to( {} ) ) includes = include_cache.GetIncludes( PathToTestFile( 'cache_test' ) ) mtime = os.path.getmtime( PathToTestFile( 'cache_test' ) ) assert_that( includes, contains_exactly( has_properties( { 'name': 'foo.h', 'entry_type': 1 } ) ) ) assert_that( include_cache._cache, has_entry( PathToTestFile( 'cache_test' ), has_entries( { 'mtime': mtime, 'includes': contains_exactly( has_properties( { 'name': 'foo.h', 'entry_type': 1 } ) ) } ) ) )
def IncludeCache_NotCached_DirInaccessible_test(): include_cache = IncludeCache() assert_that(include_cache._cache, equal_to({})) includes = include_cache.GetIncludes(PathToTestFile('unknown_dir')) assert_that(includes, equal_to([])) assert_that(include_cache._cache, equal_to({}))
def IncludeCache_Cached_NewMtime_test(): with TemporaryTestDir() as tmp_dir: include_cache = IncludeCache() assert_that(include_cache._cache, equal_to({})) foo_path = os.path.join(tmp_dir, 'foo') with open(foo_path, 'w') as foo_file: foo_file.write('foo') old_includes = include_cache.GetIncludes(tmp_dir) old_mtime = os.path.getmtime(tmp_dir) assert_that( old_includes, contains_exactly(has_properties({ 'name': 'foo', 'entry_type': 1 }))) assert_that( include_cache._cache, has_entry( tmp_dir, has_entries({ 'mtime': old_mtime, 'includes': contains_exactly( has_properties({ 'name': 'foo', 'entry_type': 1 })) }))) sleep(2) bar_path = os.path.join(tmp_dir, 'bar') with open(bar_path, 'w') as bar_file: bar_file.write('bar') new_includes = include_cache.GetIncludes(tmp_dir) new_mtime = os.path.getmtime(tmp_dir) assert_that(old_mtime, not_(equal_to(new_mtime))) assert_that( new_includes, contains_inanyorder( has_properties({ 'name': 'foo', 'entry_type': 1 }), has_properties({ 'name': 'bar', 'entry_type': 1 }))) assert_that( include_cache._cache, has_entry( tmp_dir, has_entries({ 'mtime': new_mtime, 'includes': contains_inanyorder( has_properties({ 'name': 'foo', 'entry_type': 1 }), has_properties({ 'name': 'bar', 'entry_type': 1 })) })))
class ClangCompleter(Completer): def __init__(self, user_options): super(ClangCompleter, self).__init__(user_options) self._completer = ycm_core.ClangCompleter() self._flags = Flags() self._include_cache = IncludeCache() 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 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, filepath = self._FlagsForRequest(request_data) (quoted_include_paths, include_paths, framework_paths) = UserIncludePaths(flags, filepath) if quoted_include: include_paths.extend(quoted_include_paths) includes = IncludeList() for include_path in include_paths: unicode_path = ToUnicode(os.path.join(include_path, path_dir)) includes.AddIncludes(self._include_cache.GetIncludes(unicode_path)) if framework_paths: if path_dir: head, tail = PathLeftSplit(path_dir) path_dir = os.path.join(head + '.framework', 'Headers', tail) for framework_path in framework_paths: unicode_path = ToUnicode(os.path.join(framework_path, path_dir)) includes.AddIncludes( self._include_cache.GetIncludes(unicode_path, is_framework=not path_dir)) return includes.GetIncludes() def ComputeCandidatesInner(self, request_data): flags, filename = self._FlagsForRequest(request_data) if not flags: raise RuntimeError(NO_COMPILE_FLAGS_MESSAGE) includes = self.GetIncludePaths(request_data) if includes is not None: return includes if self._completer.UpdatingTranslationUnit( ToCppStringCompatible(filename)): raise RuntimeError(PARSING_FILE_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), ToCppStringCompatible(request_data['filepath']), 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): flags, filename = self._FlagsForRequest(request_data) if not flags: raise ValueError(NO_COMPILE_FLAGS_MESSAGE) if self._completer.UpdatingTranslationUnit( ToCppStringCompatible(filename)): raise RuntimeError(PARSING_FILE_MESSAGE) files = self.GetUnsavedFilesVector(request_data) line = request_data['line_num'] column = request_data['column_num'] return getattr(self._completer, goto_function)( ToCppStringCompatible(filename), ToCppStringCompatible(request_data['filepath']), 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('GetDefinitionOrDeclarationLocation', 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('GetDefinitionOrDeclarationLocation', 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, current_file_path = self._FlagsForRequest(request_data) (quoted_include_paths, include_paths, framework_paths) = UserIncludePaths(flags, current_file_path) include_file_path = None if quoted_include: include_file_path = _GetAbsolutePath(include_file_name, quoted_include_paths) if not include_file_path: include_file_path = _GetAbsolutePath(include_file_name, include_paths) if not include_file_path and framework_paths: head, tail = PathLeftSplit(include_file_name) include_file_name = os.path.join(head + '.framework', 'Headers', tail) include_file_path = _GetAbsolutePath(include_file_name, framework_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): flags, filename = self._FlagsForRequest(request_data) if not flags: raise ValueError(NO_COMPILE_FLAGS_MESSAGE) if self._completer.UpdatingTranslationUnit( ToCppStringCompatible(filename)): raise RuntimeError(PARSING_FILE_MESSAGE) files = self.GetUnsavedFilesVector(request_data) line = request_data['line_num'] column = request_data['column_num'] message = getattr(self._completer, func)( ToCppStringCompatible(filename), ToCppStringCompatible(request_data['filepath']), 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): flags, filename = self._FlagsForRequest(request_data) if not flags: raise ValueError(NO_COMPILE_FLAGS_MESSAGE) if self._completer.UpdatingTranslationUnit( ToCppStringCompatible(filename)): raise RuntimeError(PARSING_FILE_MESSAGE) files = self.GetUnsavedFilesVector(request_data) line = request_data['line_num'] column = request_data['column_num'] fixits = getattr(self._completer, "GetFixItsForLocationInFile")( ToCppStringCompatible(filename), ToCppStringCompatible(request_data['filepath']), 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): flags, filename = 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.BuildDiagnosticResponse( diagnostics, request_data['filepath'], self.max_diagnostics_to_display) def OnBufferUnload(self, request_data): # FIXME: The filepath here is (possibly) wrong when overriding the # translation unit filename. If the buffer that the user closed is not the # "translation unit" filename, then we won't close the unit. It would # require the user to open the translation unit file, and close that. # Incidentally, doing so would flush the unit for any _other_ open files # which use that translation unit. # # Solving this would require remembering the graph of files to translation # units and only closing a unit when there are no files open which use it. 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, filename = self._FlagsForRequest(request_data) or [] except (NoExtraConfDetected, UnknownExtraConf): # If _FlagsForRequest returns None or raises, we use an empty list in # practice. flags = [] filename = request_data['filepath'] database = self._flags.LoadCompilationDatabase(filename) database_directory = database.database_directory if database else 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))) filename_item = responses.DebugInfoItem(key='translation unit', value=filename) return responses.BuildDebugInfoResponse( name='C-family', items=[database_item, flags_item, filename_item]) def _FlagsForRequest(self, request_data): filename = request_data['filepath'] if 'compilation_flags' in request_data: # Not supporting specifying the translation unit using this method as it # is only used by the tests. return (PrepareFlagsForClang(request_data['compilation_flags'], filename), filename) client_data = request_data['extra_conf_data'] return self._flags.FlagsForFile(filename, client_data=client_data)
def IncludeCache_NotCached_DirInaccessible_test(): include_cache = IncludeCache() eq_(include_cache._cache, {}) includes = include_cache.GetIncludes(PathToTestFile('unknown_dir')) eq_(includes, []) eq_(include_cache._cache, {})