Пример #1
0
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 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)):
            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(
                ToUtf8IfNeeded(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: self._GoToDefinition(request_data)),
            'GoToDeclaration':
            (lambda self, request_data: self._GoToDeclaration(request_data)),
            'GoTo': (lambda self, request_data: self._GoTo(request_data)),
            'GoToImprecise':
            (lambda self, request_data: self._GoToImprecise(request_data)),
            'GoToInclude':
            (lambda self, request_data: self._GoToInclude(request_data)),
            'ClearCompilationFlagCache':
            (lambda self, request_data: self._ClearCompilationFlagCache()),
            'GetType': (lambda self, request_data: self._GetSemanticInfo(
                request_data, func='GetTypeAtLocation')),
            'GetParent': (lambda self, request_data: self._GetSemanticInfo(
                request_data, func='GetEnclosingFunctionAtLocation')),
            'FixIt': (lambda self, request_data: self._FixIt(request_data)),
            'GetDoc': (lambda self, request_data: self._GetSemanticInfo(
                request_data,
                reparse=True,
                func='GetDocsForLocationInFile',
                response_builder=_BuildGetDocResponse)),
            'GetDocQuick': (lambda self, request_data: 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)(ToUtf8IfNeeded(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 = ToUtf8IfNeeded(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)(ToUtf8IfNeeded(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")(
            ToUtf8IfNeeded(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(
                ToUtf8IfNeeded(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(
            ToUtf8IfNeeded(request_data['unloaded_buffer']))

    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

        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']
        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 = ToUtf8IfNeeded(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)
Пример #2
0
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)
Пример #3
0
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 )
Пример #4
0
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 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

        compfilename = filename
        filename = self._ParentForRequest(filename)

        if self._completer.UpdatingTranslationUnit(ToUtf8IfNeeded(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(
                ToUtf8IfNeeded(filename), ToUtf8IfNeeded(compfilename), line,
                column, files, flags)

        if not results:
            raise RuntimeError(NO_COMPLETIONS_MESSAGE)

        return [ConvertCompletionData(x) for x in results]

    def DefinedSubcommands(self):
        return [
            'GoToDefinition', 'GoToDeclaration', 'GoTo', 'GoToImprecise',
            'ClearCompilationFlagCache', 'GetType', 'GetParent'
        ]

    def OnUserCommand(self, arguments, request_data):
        if not arguments:
            raise ValueError(self.UserCommandsHelpMessage())

        # command_map maps: command -> { method, args }
        #
        # where:
        #  "command" is the completer command entered by the user
        #            (e.g. GoToDefinition)
        #  "method"  is a method to call for that command
        #            (e.g. self._GoToDefinition)
        #  "args"    is a dictionary of
        #               "method_argument" : "value" ...
        #            which defines the kwargs (via the ** double splat)
        #            when calling "method"
        command_map = {
            'GoToDefinition': {
                'method': self._GoToDefinition,
                'args': {
                    'request_data': request_data
                }
            },
            'GoToDeclaration': {
                'method': self._GoToDeclaration,
                'args': {
                    'request_data': request_data
                }
            },
            'GoTo': {
                'method': self._GoTo,
                'args': {
                    'request_data': request_data
                }
            },
            'GoToImprecise': {
                'method': self._GoToImprecise,
                'args': {
                    'request_data': request_data
                }
            },
            'ClearCompilationFlagCache': {
                'method': self._ClearCompilationFlagCache,
                'args': {}
            },
            'GetType': {
                'method': self._GetSemanticInfo,
                'args': {
                    'request_data': request_data,
                    'func': 'GetTypeAtLocation'
                }
            },
            'GetParent': {
                'method': self._GetSemanticInfo,
                'args': {
                    'request_data': request_data,
                    'func': 'GetEnclosingFunctionAtLocation'
                }
            },
        }

        try:
            command_def = command_map[arguments[0]]
            return command_def['method'](**(command_def['args']))
        except KeyError:
            raise ValueError(self.UserCommandsHelpMessage())

    def _LocationForGoTo(self, goto_function, request_data, reparse=True):
        filename = request_data['filepath']
        if not filename:
            raise ValueError(INVALID_FILE_MESSAGE)

        gotofilename = filename
        filename = self._ParentForRequest(filename)

        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)(ToUtf8IfNeeded(filename),
                                      ToUtf8IfNeeded(gotofilename), 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):
        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):
        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 _GetSemanticInfo(self, request_data, func, reparse=True):
        filename = request_data['filepath']
        if not filename:
            raise ValueError(INVALID_FILE_MESSAGE)

        gotofilename = filename
        filename = self._ParentForRequest(filename)

        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)(ToUtf8IfNeeded(filename),
                                ToUtf8IfNeeded(gotofilename), line, column,
                                files, flags, reparse)

        if not message:
            message = "No semantic information available"

        return responses.BuildDisplayMessageResponse(message)

    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:
            raise ValueError(FILE_TOO_SHORT_MESSAGE)

        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(
                ToUtf8IfNeeded(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(
            ToUtf8IfNeeded(request_data['unloaded_buffer']))

    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

        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']
        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 = ToUtf8IfNeeded(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)

    def _ParentForRequest(self, filename):
        return self._flags.ParentForFile(ToUtf8IfNeeded(filename))