lib/steep/index/signature_symbol_provider.rb



module Steep
  module Index
    class SignatureSymbolProvider
      LSP = LanguageServer::Protocol

      class SymbolInformation < Struct.new(:name, :kind, :container_name, :location, keyword_init: true)
      end

      attr_reader :project
      attr_reader :indexes
      attr_reader :assignment

      def initialize(project:, assignment:)
        @indexes = {}
        @project = project
        @assignment = assignment
      end

      def self.test_type_name(query, type_name)
        case
        when query == ""
          true
        else
          type_name.to_s.upcase.include?(query.upcase)
        end
      end

      class <<self
        alias test_const_name test_type_name
        alias test_global_name test_type_name
      end

      def self.test_method_name(query, method_name)
        case
        when query == ""
          true
        else
          method_name.to_s.upcase.include?(query.upcase)
        end
      end

      def assigned?(target, path)
        if path.relative?
          if project.targets.any? {|target| target.possible_signature_file?(path) }
            path = project.absolute_path(path)
          end
        end

        assignment =~ [target, path]
      end

      def query_symbol(query)
        symbols = [] #: Array[SymbolInformation]

        indexes.each do |target, index|
          index.each_entry do |entry|
            case entry
            when RBSIndex::TypeEntry
              next unless SignatureSymbolProvider.test_type_name(query, entry.type_name)

              container_name = entry.type_name.namespace.relative!.to_s.delete_suffix("::")
              name = entry.type_name.name.to_s

              entry.declarations.each do |decl|
                location = decl.location or next
                next unless assigned?(target, Pathname(location.buffer.name))

                case decl
                when RBS::AST::Declarations::Class
                  symbols << SymbolInformation.new(
                    name: name,
                    location: location,
                    kind: LSP::Constant::SymbolKind::CLASS,
                    container_name: container_name
                  )
                when RBS::AST::Declarations::Module
                  symbols << SymbolInformation.new(
                    name: name,
                    location: location,
                    kind: LSP::Constant::SymbolKind::MODULE,
                    container_name: container_name
                  )
                when RBS::AST::Declarations::Interface
                  symbols << SymbolInformation.new(
                    name: name,
                    location: location,
                    kind: LSP::Constant::SymbolKind::INTERFACE,
                    container_name: container_name
                  )
                when RBS::AST::Declarations::TypeAlias
                  symbols << SymbolInformation.new(
                    name: name,
                    location: location,
                    kind: LSP::Constant::SymbolKind::ENUM,
                    container_name: container_name
                  )
                end
              end
            when RBSIndex::MethodEntry
              next unless SignatureSymbolProvider.test_method_name(query, entry.method_name)

              name = case entry.method_name
                     when InstanceMethodName
                       "##{entry.method_name.method_name}"
                     when SingletonMethodName
                       ".#{entry.method_name.method_name}"
                     else
                       raise
                     end
              container_name = entry.method_name.type_name.relative!.to_s

              entry.declarations.each do |decl|
                location = decl.location or next
                next unless assigned?(target, Pathname(location.buffer.name))

                case decl
                when RBS::AST::Members::MethodDefinition
                  symbols << SymbolInformation.new(
                    name: name,
                    location: location,
                    kind: LSP::Constant::SymbolKind::METHOD,
                    container_name: container_name
                  )
                when RBS::AST::Members::Alias
                  symbols << SymbolInformation.new(
                    name: name,
                    location: location,
                    kind: LSP::Constant::SymbolKind::METHOD,
                    container_name: container_name
                  )
                when RBS::AST::Members::AttrAccessor, RBS::AST::Members::AttrReader, RBS::AST::Members::AttrWriter
                  symbols << SymbolInformation.new(
                    name: name,
                    location: location,
                    kind: LSP::Constant::SymbolKind::PROPERTY,
                    container_name: container_name
                  )

                  if decl.ivar_name
                    symbols << SymbolInformation.new(
                      name: decl.ivar_name.to_s,
                      location: location,
                      kind: LSP::Constant::SymbolKind::FIELD,
                      container_name: container_name
                    )
                  end
                end
              end
            when RBSIndex::ConstantEntry
              next unless SignatureSymbolProvider.test_const_name(query, entry.const_name)

              entry.declarations.each do |decl|
                next unless decl.location
                next unless assigned?(target, Pathname(decl.location.buffer.name))

                symbols << SymbolInformation.new(
                  name: entry.const_name.name.to_s,
                  location: decl.location,
                  kind: LSP::Constant::SymbolKind::CONSTANT,
                  container_name: entry.const_name.namespace.relative!.to_s.delete_suffix("::")
                )
              end
            when RBSIndex::GlobalEntry
              next unless SignatureSymbolProvider.test_global_name(query, entry.global_name)

              entry.declarations.each do |decl|
                next unless decl.location
                next unless assigned?(target, Pathname(decl.location.buffer.name))

                symbols << SymbolInformation.new(
                  name: decl.name.to_s,
                  location: decl.location,
                  kind: LSP::Constant::SymbolKind::VARIABLE,
                  container_name: nil
                )
              end
            end
          end
        end

        symbols.uniq {|symbol| [symbol.name, symbol.location] }.sort_by {|symbol| symbol.name.to_s }
      end
    end
  end
end