Skip to content

Commit 4c95ce6

Browse files
committed
Handle typeHierarchy/supertypes requests
Signed-off-by: Alexandre Terrasa <[email protected]>
1 parent 5364b12 commit 4c95ce6

File tree

5 files changed

+177
-0
lines changed

5 files changed

+177
-0
lines changed

lib/ruby_lsp/requests.rb

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ module Requests
4848
autoload :WorkspaceSymbol, "ruby_lsp/requests/workspace_symbol"
4949
autoload :SignatureHelp, "ruby_lsp/requests/signature_help"
5050
autoload :PrepareTypeHierarchy, "ruby_lsp/requests/prepare_type_hierarchy"
51+
autoload :TypeHierarchySupertypes, "ruby_lsp/requests/type_hierarchy_supertypes"
5152

5253
# :nodoc:
5354
module Support
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# typed: strict
2+
# frozen_string_literal: true
3+
4+
module RubyLsp
5+
module Requests
6+
# ![Type hierarchy supertypes demo](../../type_hierarchy_supertypes.gif)
7+
#
8+
# The [type hierarchy supertypes
9+
# request](https://microsoft.github.io/language-server-protocol/specification#typeHierarchy_supertypes)
10+
# displays the list of ancestors (supertypes) for the selected type.
11+
#
12+
# # Example
13+
#
14+
# ```ruby
15+
# class Foo; end
16+
# class Bar < Foo; end
17+
#
18+
# puts Bar # <-- right click on `Bar` and select "Show Type Hierarchy"
19+
# ```
20+
class TypeHierarchySupertypes < Request
21+
extend T::Sig
22+
23+
include Support::Common
24+
25+
sig { params(index: RubyIndexer::Index, item: T::Hash[Symbol, T.untyped]).void }
26+
def initialize(index, item)
27+
super()
28+
29+
@index = index
30+
@item = item
31+
end
32+
33+
sig { override.returns(T.nilable(T::Array[Interface::TypeHierarchyItem])) }
34+
def perform
35+
name = @item[:name]
36+
entries = @index[name]
37+
38+
parents = T.let([], T::Array[RubyIndexer::Entry::Namespace])
39+
return unless entries&.any?
40+
41+
entries.each do |entry|
42+
next unless entry.is_a?(RubyIndexer::Entry::Namespace)
43+
44+
if entry.is_a?(RubyIndexer::Entry::Class)
45+
parent_class_name = entry.parent_class
46+
if parent_class_name
47+
resolved_parent_entries = @index.resolve(parent_class_name, entry.nesting)
48+
resolved_parent_entries&.each do |entry|
49+
next unless entry.is_a?(RubyIndexer::Entry::Class)
50+
51+
parents << entry
52+
end
53+
end
54+
end
55+
56+
entry.mixin_operations.each do |mixin_operation|
57+
next if mixin_operation.is_a?(RubyIndexer::Entry::Extend)
58+
59+
mixin_name = mixin_operation.module_name
60+
resolved_mixin_entries = @index.resolve(mixin_name, entry.nesting)
61+
next unless resolved_mixin_entries
62+
63+
resolved_mixin_entries.each do |mixin_entry|
64+
next unless mixin_entry.is_a?(RubyIndexer::Entry::Module)
65+
66+
parents << mixin_entry
67+
end
68+
end
69+
end
70+
71+
parents.uniq.map { |entry| hierarchy_item(entry) }
72+
end
73+
74+
private
75+
76+
sig { params(entry: RubyIndexer::Entry).returns(Interface::TypeHierarchyItem) }
77+
def hierarchy_item(entry)
78+
range = range_from_location(entry.location)
79+
80+
Interface::TypeHierarchyItem.new(
81+
name: entry.name,
82+
kind: kind_for_entry(entry),
83+
uri: URI::Generic.from_path(path: entry.file_path).to_s,
84+
range: range,
85+
selection_range: range,
86+
detail: entry.file_name,
87+
)
88+
end
89+
end
90+
end
91+
end

lib/ruby_lsp/server.rb

+11
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ def process_message(message)
7070
text_document_definition(message)
7171
when "textDocument/prepareTypeHierarchy"
7272
text_document_prepare_type_hierarchy(message)
73+
when "typeHierarchy/supertypes"
74+
type_hierarchy_supertypes(message)
7375
when "workspace/didChangeWatchedFiles"
7476
workspace_did_change_watched_files(message)
7577
when "workspace/symbol"
@@ -688,6 +690,15 @@ def text_document_prepare_type_hierarchy(message)
688690
send_message(Result.new(id: message[:id], response: response))
689691
end
690692

693+
sig { params(message: T::Hash[Symbol, T.untyped]).void }
694+
def type_hierarchy_supertypes(message)
695+
response = Requests::TypeHierarchySupertypes.new(
696+
@global_state.index,
697+
message.dig(:params, :item),
698+
).perform
699+
send_message(Result.new(id: message[:id], response: response))
700+
end
701+
691702
sig { params(message: T::Hash[Symbol, T.untyped]).void }
692703
def workspace_dependencies(message)
693704
response = begin

misc/type_hierarchy_supertypes.gif

100 KB
Loading
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# typed: true
2+
# frozen_string_literal: true
3+
4+
require "test_helper"
5+
6+
class TypeHierarchySupertypesTest < Minitest::Test
7+
def test_type_hierarchy_supertypes_returns_nil_if_item_name_not_indexed
8+
source = +<<~RUBY
9+
class Foo; end
10+
RUBY
11+
12+
with_server(source) do |server, uri|
13+
server.process_message(id: 1, method: "typeHierarchy/supertypes", params: {
14+
textDocument: { uri: uri },
15+
item: { name: "Bar" },
16+
})
17+
result = server.pop_response.response
18+
19+
assert_nil(result)
20+
end
21+
end
22+
23+
def test_type_hierarchy_supertypes_returns_empty_array_if_no_supertypes
24+
source = +<<~RUBY
25+
class Foo::Bar; end
26+
RUBY
27+
28+
with_server(source) do |server, uri|
29+
server.process_message(id: 1, method: "typeHierarchy/supertypes", params: {
30+
textDocument: { uri: uri },
31+
item: { name: "Foo::Bar" },
32+
})
33+
result = server.pop_response.response
34+
35+
assert_empty(result)
36+
end
37+
end
38+
39+
def test_type_hierarchy_returns_supertypes
40+
source = <<~RUBY
41+
module Foo
42+
class Bar; end
43+
class Baz < Bar; end
44+
class Qux < Baz; end
45+
end
46+
RUBY
47+
48+
with_server(source) do |server, uri|
49+
server.process_message(id: 1, method: "typeHierarchy/supertypes", params: {
50+
textDocument: { uri: uri },
51+
item: { name: "Foo::Qux" },
52+
})
53+
result = server.pop_response.response
54+
55+
assert_equal(["Foo::Baz"], result.map(&:name))
56+
57+
server.process_message(id: 2, method: "typeHierarchy/supertypes", params: {
58+
textDocument: { uri: uri },
59+
item: { name: "Foo::Baz" },
60+
})
61+
result = server.pop_response.response
62+
63+
assert_equal(["Foo::Bar"], result.map(&:name))
64+
65+
server.process_message(id: 2, method: "typeHierarchy/supertypes", params: {
66+
textDocument: { uri: uri },
67+
item: { name: "Foo::Bar" },
68+
})
69+
result = server.pop_response.response
70+
71+
assert_empty(result)
72+
end
73+
end
74+
end

0 commit comments

Comments
 (0)