class RubyLsp::Listeners::Hover
def generate_gem_hover(node)
def generate_gem_hover(node) first_argument = node.arguments&.arguments&.first return unless first_argument.is_a?(Prism::StringNode) spec = Gem::Specification.find_by_name(first_argument.content) return unless spec info = T.let( [ spec.description, spec.summary, "This rubygem does not have a description or summary.", ].find { |text| !text.nil? && !text.empty? }, String, ) # Remove leading whitespace if a heredoc was used for the summary or description info = info.gsub(/^ +/, "") remote_url = [spec.homepage, spec.metadata["source_code_uri"]].compact.find do |page| page.start_with?(*ALLOWED_REMOTE_PROVIDERS) end @response_builder.push( "**#{spec.name}** (#{spec.version}) #{remote_url && " - [open remote](#{remote_url})"}", category: :title, ) @response_builder.push(info, category: :documentation) rescue Gem::MissingSpecError # Do nothing if the spec cannot be found end
def generate_hover(name, location)
def generate_hover(name, location) entries = @index.resolve(name, @nesting) return unless entries # We should only show hover for private constants if the constant is defined in the same namespace as the # reference first_entry = T.must(entries.first) return if first_entry.visibility == :private && first_entry.name != "#{@nesting.join("::")}::#{name}" categorized_markdown_from_index_entries(name, entries).each do |category, content| @response_builder.push(content, category: category) end end
def initialize(response_builder, uri, nesting, index, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
def initialize(response_builder, uri, nesting, index, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists @response_builder = response_builder @path = T.let(uri.to_standardized_path, T.nilable(String)) @nesting = nesting @index = index @typechecker_enabled = typechecker_enabled dispatcher.register( self, :on_constant_read_node_enter, :on_constant_write_node_enter, :on_constant_path_node_enter, :on_call_node_enter, ) end
def on_call_node_enter(node)
def on_call_node_enter(node) return unless self_receiver?(node) if @path && File.basename(@path) == GEMFILE_NAME && node.name == :gem generate_gem_hover(node) return end return if @typechecker_enabled message = node.message return unless message target_method = @index.resolve_method(message, @nesting.join("::")) return unless target_method categorized_markdown_from_index_entries(message, target_method).each do |category, content| @response_builder.push(content, category: category) end end
def on_constant_path_node_enter(node)
def on_constant_path_node_enter(node) return if DependencyDetector.instance.typechecker generate_hover(node.slice, node.location) end
def on_constant_read_node_enter(node)
def on_constant_read_node_enter(node) return if @typechecker_enabled generate_hover(node.slice, node.location) end
def on_constant_write_node_enter(node)
def on_constant_write_node_enter(node) return if DependencyDetector.instance.typechecker generate_hover(node.name.to_s, node.name_loc) end