lib/ruby_lsp/requests/request.rb



# typed: strict
# frozen_string_literal: true

module RubyLsp
  module Requests
    # @abstract
    class Request
      class InvalidFormatter < StandardError; end

      # @abstract
      #: -> untyped
      def perform
        raise AbstractMethodInvokedError
      end

      private

      # Signals to the client that the request should be delegated to the language server server for the host language
      # in ERB files
      #: (GlobalState global_state, Document[untyped] document, Integer char_position) -> void
      def delegate_request_if_needed!(global_state, document, char_position)
        if global_state.client_capabilities.supports_request_delegation &&
            document.is_a?(ERBDocument) &&
            document.inside_host_language?(char_position)
          raise DelegateRequestError
        end
      end

      # Checks if a location covers a position
      #: (Prism::Location location, untyped position) -> bool
      def cover?(location, position)
        start_covered =
          location.start_line - 1 < position[:line] ||
          (
            location.start_line - 1 == position[:line] &&
              location.start_column <= position[:character]
          )
        end_covered =
          location.end_line - 1 > position[:line] ||
          (
            location.end_line - 1 == position[:line] &&
              location.end_column >= position[:character]
          )
        start_covered && end_covered
      end

      # Based on a constant node target, a constant path node parent and a position, this method will find the exact
      # portion of the constant path that matches the requested position, for higher precision in hover and
      # definition. For example:
      #
      # ```ruby
      # Foo::Bar::Baz
      #  #        ^ Going to definition here should go to Foo::Bar::Baz
      #  #   ^ Going to definition here should go to Foo::Bar
      # #^ Going to definition here should go to Foo
      # ```
      #: (Prism::Node target, Prism::Node parent, Hash[Symbol, Integer] position) -> Prism::Node
      def determine_target(target, parent, position)
        return target unless parent.is_a?(Prism::ConstantPathNode)

        target = parent #: Prism::Node
        parent = target #: as Prism::ConstantPathNode
          .parent #: Prism::Node?

        while parent && cover?(parent.location, position)
          target = parent
          parent = target.is_a?(Prism::ConstantPathNode) ? target.parent : nil
        end

        target
      end

      # Checks if a given location covers the position requested
      #: (Prism::Location? location, Hash[Symbol, untyped] position) -> bool
      def covers_position?(location, position)
        return false unless location

        start_line = location.start_line - 1
        end_line = location.end_line - 1
        line = position[:line]
        character = position[:character]

        (start_line < line || (start_line == line && location.start_column <= character)) &&
          (end_line > line || (end_line == line && location.end_column >= character))
      end
    end
  end
end