ruby-lsp 0.16.4 → 0.16.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp +6 -2
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +1 -0
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +32 -27
- data/lib/ruby_indexer/lib/ruby_indexer/location.rb +26 -0
- data/lib/ruby_indexer/ruby_indexer.rb +1 -0
- data/lib/ruby_indexer/test/configuration_test.rb +8 -0
- data/lib/ruby_lsp/base_server.rb +1 -1
- data/lib/ruby_lsp/global_state.rb +41 -30
- data/lib/ruby_lsp/listeners/definition.rb +10 -3
- data/lib/ruby_lsp/requests/definition.rb +21 -12
- data/lib/ruby_lsp/requests/diagnostics.rb +7 -4
- data/lib/ruby_lsp/requests/hover.rb +11 -7
- data/lib/ruby_lsp/requests/request.rb +14 -0
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +6 -3
- data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +36 -13
- data/lib/ruby_lsp/server.rb +2 -2
- data/lib/ruby_lsp/utils.rb +1 -1
- metadata +8 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d725a0a2fbd58820e2a87efae85564498221feb8410292055b0caafd889284e6
|
4
|
+
data.tar.gz: 5376ca8a9d08ca0883fca3aa19a1a178102eb60f49ab3fe5f07bb1a58ba63ddf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 81fbb2e5018ad55d8888c98e277e7ac6ed8b45314627b1a3e3dabde2cb8aac0284dc70487a694226e5a08817feb82b64daadfcb955e9642b288e575189a56015
|
7
|
+
data.tar.gz: f32ef084e4412752cc32df55da0dd7b985d3826db110bf91ba3473773043c50345ceb597432483a0a75f1790cabfca796fa9581d3ba86edff96a8642fb2312f2
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.16.
|
1
|
+
0.16.6
|
data/exe/ruby-lsp
CHANGED
@@ -75,14 +75,18 @@ require "ruby_lsp/internal"
|
|
75
75
|
|
76
76
|
if options[:debug]
|
77
77
|
if ["x64-mingw-ucrt", "x64-mingw32"].include?(RUBY_PLATFORM)
|
78
|
-
puts "Debugging is not supported on Windows"
|
78
|
+
$stderr.puts "Debugging is not supported on Windows"
|
79
79
|
exit 1
|
80
80
|
end
|
81
81
|
|
82
82
|
begin
|
83
|
+
original_stdout = $stdout
|
84
|
+
$stdout = $stderr
|
83
85
|
require "debug/open_nonstop"
|
84
86
|
rescue LoadError
|
85
|
-
|
87
|
+
$stderr.puts("You need to install the debug gem to use the --debug flag")
|
88
|
+
ensure
|
89
|
+
$stdout = original_stdout
|
86
90
|
end
|
87
91
|
end
|
88
92
|
|
@@ -56,6 +56,7 @@ module RubyIndexer
|
|
56
56
|
load_path_entry = T.let(nil, T.nilable(String))
|
57
57
|
|
58
58
|
Dir.glob(pattern, File::FNM_PATHNAME | File::FNM_EXTGLOB).map! do |path|
|
59
|
+
path = File.expand_path(path)
|
59
60
|
# All entries for the same pattern match the same $LOAD_PATH entry. Since searching the $LOAD_PATH for every
|
60
61
|
# entry is expensive, we memoize it until we find a path that doesn't belong to that $LOAD_PATH. This happens
|
61
62
|
# on repositories that define multiple gems, like Rails. All frameworks are defined inside the Dir.pwd, but
|
@@ -11,7 +11,7 @@ module RubyIndexer
|
|
11
11
|
sig { returns(String) }
|
12
12
|
attr_reader :file_path
|
13
13
|
|
14
|
-
sig { returns(
|
14
|
+
sig { returns(RubyIndexer::Location) }
|
15
15
|
attr_reader :location
|
16
16
|
|
17
17
|
sig { returns(T::Array[String]) }
|
@@ -20,13 +20,33 @@ module RubyIndexer
|
|
20
20
|
sig { returns(Symbol) }
|
21
21
|
attr_accessor :visibility
|
22
22
|
|
23
|
-
sig
|
23
|
+
sig do
|
24
|
+
params(
|
25
|
+
name: String,
|
26
|
+
file_path: String,
|
27
|
+
location: T.any(Prism::Location, RubyIndexer::Location),
|
28
|
+
comments: T::Array[String],
|
29
|
+
).void
|
30
|
+
end
|
24
31
|
def initialize(name, file_path, location, comments)
|
25
32
|
@name = name
|
26
33
|
@file_path = file_path
|
27
|
-
@location = location
|
28
34
|
@comments = comments
|
29
35
|
@visibility = T.let(:public, Symbol)
|
36
|
+
|
37
|
+
@location = T.let(
|
38
|
+
if location.is_a?(Prism::Location)
|
39
|
+
Location.new(
|
40
|
+
location.start_line,
|
41
|
+
location.end_line,
|
42
|
+
location.start_column,
|
43
|
+
location.end_column,
|
44
|
+
)
|
45
|
+
else
|
46
|
+
location
|
47
|
+
end,
|
48
|
+
RubyIndexer::Location,
|
49
|
+
)
|
30
50
|
end
|
31
51
|
|
32
52
|
sig { returns(String) }
|
@@ -41,28 +61,13 @@ module RubyIndexer
|
|
41
61
|
abstract!
|
42
62
|
|
43
63
|
sig { returns(T::Array[String]) }
|
44
|
-
|
45
|
-
|
46
|
-
sig { returns(T::Array[String]) }
|
47
|
-
attr_accessor :prepended_modules
|
48
|
-
|
49
|
-
sig do
|
50
|
-
params(
|
51
|
-
name: String,
|
52
|
-
file_path: String,
|
53
|
-
location: Prism::Location,
|
54
|
-
comments: T::Array[String],
|
55
|
-
).void
|
56
|
-
end
|
57
|
-
def initialize(name, file_path, location, comments)
|
58
|
-
super(name, file_path, location, comments)
|
59
|
-
@included_modules = T.let([], T::Array[String])
|
60
|
-
@prepended_modules = T.let([], T::Array[String])
|
64
|
+
def included_modules
|
65
|
+
@included_modules ||= T.let([], T.nilable(T::Array[String]))
|
61
66
|
end
|
62
67
|
|
63
|
-
sig { returns(String) }
|
64
|
-
def
|
65
|
-
T.
|
68
|
+
sig { returns(T::Array[String]) }
|
69
|
+
def prepended_modules
|
70
|
+
@prepended_modules ||= T.let([], T.nilable(T::Array[String]))
|
66
71
|
end
|
67
72
|
end
|
68
73
|
|
@@ -81,7 +86,7 @@ module RubyIndexer
|
|
81
86
|
params(
|
82
87
|
name: String,
|
83
88
|
file_path: String,
|
84
|
-
location: Prism::Location,
|
89
|
+
location: T.any(Prism::Location, RubyIndexer::Location),
|
85
90
|
comments: T::Array[String],
|
86
91
|
parent_class: T.nilable(String),
|
87
92
|
).void
|
@@ -181,7 +186,7 @@ module RubyIndexer
|
|
181
186
|
params(
|
182
187
|
name: String,
|
183
188
|
file_path: String,
|
184
|
-
location: Prism::Location,
|
189
|
+
location: T.any(Prism::Location, RubyIndexer::Location),
|
185
190
|
comments: T::Array[String],
|
186
191
|
owner: T.nilable(Entry::Namespace),
|
187
192
|
).void
|
@@ -219,7 +224,7 @@ module RubyIndexer
|
|
219
224
|
params(
|
220
225
|
name: String,
|
221
226
|
file_path: String,
|
222
|
-
location: Prism::Location,
|
227
|
+
location: T.any(Prism::Location, RubyIndexer::Location),
|
223
228
|
comments: T::Array[String],
|
224
229
|
parameters_node: T.nilable(Prism::ParametersNode),
|
225
230
|
owner: T.nilable(Entry::Namespace),
|
@@ -349,7 +354,7 @@ module RubyIndexer
|
|
349
354
|
nesting: T::Array[String],
|
350
355
|
name: String,
|
351
356
|
file_path: String,
|
352
|
-
location: Prism::Location,
|
357
|
+
location: T.any(Prism::Location, RubyIndexer::Location),
|
353
358
|
comments: T::Array[String],
|
354
359
|
).void
|
355
360
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyIndexer
|
5
|
+
class Location
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { returns(Integer) }
|
9
|
+
attr_reader :start_line, :end_line, :start_column, :end_column
|
10
|
+
|
11
|
+
sig do
|
12
|
+
params(
|
13
|
+
start_line: Integer,
|
14
|
+
end_line: Integer,
|
15
|
+
start_column: Integer,
|
16
|
+
end_column: Integer,
|
17
|
+
).void
|
18
|
+
end
|
19
|
+
def initialize(start_line, end_line, start_column, end_column)
|
20
|
+
@start_line = start_line
|
21
|
+
@end_line = end_line
|
22
|
+
@start_column = start_column
|
23
|
+
@end_column = end_column
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -10,6 +10,7 @@ require "ruby_indexer/lib/ruby_indexer/index"
|
|
10
10
|
require "ruby_indexer/lib/ruby_indexer/entry"
|
11
11
|
require "ruby_indexer/lib/ruby_indexer/configuration"
|
12
12
|
require "ruby_indexer/lib/ruby_indexer/prefix_tree"
|
13
|
+
require "ruby_indexer/lib/ruby_indexer/location"
|
13
14
|
|
14
15
|
module RubyIndexer
|
15
16
|
@configuration = T.let(Configuration.new, Configuration)
|
@@ -20,6 +20,14 @@ module RubyIndexer
|
|
20
20
|
assert(indexables.none? { |indexable| indexable.full_path == __FILE__ })
|
21
21
|
end
|
22
22
|
|
23
|
+
def test_indexables_have_expanded_full_paths
|
24
|
+
@config.apply_config({ "included_patterns" => ["**/*.rb"] })
|
25
|
+
indexables = @config.indexables
|
26
|
+
|
27
|
+
# All paths should be expanded
|
28
|
+
assert(indexables.none? { |indexable| indexable.full_path.start_with?("lib/") })
|
29
|
+
end
|
30
|
+
|
23
31
|
def test_indexables_only_includes_gem_require_paths
|
24
32
|
indexables = @config.indexables
|
25
33
|
|
data/lib/ruby_lsp/base_server.rb
CHANGED
@@ -62,7 +62,7 @@ module RubyLsp
|
|
62
62
|
# The following requests need to be executed in the main thread directly to avoid concurrency issues. Everything
|
63
63
|
# else is pushed into the incoming queue
|
64
64
|
case method
|
65
|
-
when "initialize", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
|
65
|
+
when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
|
66
66
|
process_message(message)
|
67
67
|
when "shutdown"
|
68
68
|
$stderr.puts("Shutting down Ruby LSP...")
|
@@ -29,8 +29,9 @@ module RubyLsp
|
|
29
29
|
@encoding = T.let(Encoding::UTF_8, Encoding)
|
30
30
|
|
31
31
|
@formatter = T.let("auto", String)
|
32
|
-
@
|
33
|
-
@
|
32
|
+
@linters = T.let([], T::Array[String])
|
33
|
+
@test_library = T.let("minitest", String)
|
34
|
+
@typechecker = T.let(true, T::Boolean)
|
34
35
|
@index = T.let(RubyIndexer::Index.new, RubyIndexer::Index)
|
35
36
|
@supported_formatters = T.let({}, T::Hash[String, Requests::Support::Formatter])
|
36
37
|
@supports_watching_files = T.let(false, T::Boolean)
|
@@ -46,14 +47,25 @@ module RubyLsp
|
|
46
47
|
@supported_formatters[@formatter]
|
47
48
|
end
|
48
49
|
|
50
|
+
sig { returns(T::Array[Requests::Support::Formatter]) }
|
51
|
+
def active_linters
|
52
|
+
@linters.filter_map { |name| @supported_formatters[name] }
|
53
|
+
end
|
54
|
+
|
49
55
|
sig { params(options: T::Hash[Symbol, T.untyped]).void }
|
50
56
|
def apply_options(options)
|
57
|
+
dependencies = gather_dependencies
|
51
58
|
workspace_uri = options.dig(:workspaceFolders, 0, :uri)
|
52
59
|
@workspace_uri = URI(workspace_uri) if workspace_uri
|
53
60
|
|
54
61
|
specified_formatter = options.dig(:initializationOptions, :formatter)
|
55
62
|
@formatter = specified_formatter if specified_formatter
|
56
|
-
@formatter = detect_formatter if @formatter == "auto"
|
63
|
+
@formatter = detect_formatter(dependencies) if @formatter == "auto"
|
64
|
+
|
65
|
+
specified_linters = options.dig(:initializationOptions, :linters)
|
66
|
+
@linters = specified_linters || detect_linters(dependencies)
|
67
|
+
@test_library = detect_test_library(dependencies)
|
68
|
+
@typechecker = detect_typechecker(dependencies)
|
57
69
|
|
58
70
|
encodings = options.dig(:capabilities, :general, :positionEncodings)
|
59
71
|
@encoding = if !encodings || encodings.empty?
|
@@ -89,28 +101,32 @@ module RubyLsp
|
|
89
101
|
end
|
90
102
|
end
|
91
103
|
|
92
|
-
sig { params(gem_pattern: Regexp).returns(T::Boolean) }
|
93
|
-
def direct_dependency?(gem_pattern)
|
94
|
-
dependencies.any?(gem_pattern)
|
95
|
-
end
|
96
|
-
|
97
104
|
private
|
98
105
|
|
99
|
-
sig { returns(String) }
|
100
|
-
def detect_formatter
|
106
|
+
sig { params(dependencies: T::Array[String]).returns(String) }
|
107
|
+
def detect_formatter(dependencies)
|
101
108
|
# NOTE: Intentionally no $ at end, since we want to match rubocop-shopify, etc.
|
102
|
-
if
|
109
|
+
if dependencies.any?(/^rubocop/)
|
103
110
|
"rubocop"
|
104
|
-
elsif
|
111
|
+
elsif dependencies.any?(/^syntax_tree$/)
|
105
112
|
"syntax_tree"
|
106
113
|
else
|
107
114
|
"none"
|
108
115
|
end
|
109
116
|
end
|
110
117
|
|
111
|
-
|
112
|
-
|
113
|
-
|
118
|
+
# Try to detect if there are linters in the project's dependencies. For auto-detection, we always only consider a
|
119
|
+
# single linter. To have multiple linters running, the user must configure them manually
|
120
|
+
sig { params(dependencies: T::Array[String]).returns(T::Array[String]) }
|
121
|
+
def detect_linters(dependencies)
|
122
|
+
linters = []
|
123
|
+
linters << "rubocop" if dependencies.any?(/^rubocop/)
|
124
|
+
linters
|
125
|
+
end
|
126
|
+
|
127
|
+
sig { params(dependencies: T::Array[String]).returns(String) }
|
128
|
+
def detect_test_library(dependencies)
|
129
|
+
if dependencies.any?(/^rspec/)
|
114
130
|
"rspec"
|
115
131
|
# A Rails app may have a dependency on minitest, but we would instead want to use the Rails test runner provided
|
116
132
|
# by ruby-lsp-rails. A Rails app doesn't need to depend on the rails gem itself, individual components like
|
@@ -119,23 +135,23 @@ module RubyLsp
|
|
119
135
|
elsif File.exist?(File.join(workspace_path, "bin/rails"))
|
120
136
|
"rails"
|
121
137
|
# NOTE: Intentionally ends with $ to avoid mis-matching minitest-reporters, etc. in a Rails app.
|
122
|
-
elsif
|
138
|
+
elsif dependencies.any?(/^minitest$/)
|
123
139
|
"minitest"
|
124
|
-
elsif
|
140
|
+
elsif dependencies.any?(/^test-unit/)
|
125
141
|
"test-unit"
|
126
142
|
else
|
127
143
|
"unknown"
|
128
144
|
end
|
129
145
|
end
|
130
146
|
|
131
|
-
sig { returns(T::Boolean) }
|
132
|
-
def detect_typechecker
|
147
|
+
sig { params(dependencies: T::Array[String]).returns(T::Boolean) }
|
148
|
+
def detect_typechecker(dependencies)
|
133
149
|
return false if ENV["RUBY_LSP_BYPASS_TYPECHECKER"]
|
134
150
|
|
135
151
|
# We can't read the env from within `Bundle.with_original_env` so we need to set it here.
|
136
152
|
ruby_lsp_env_is_test = (ENV["RUBY_LSP_ENV"] == "test")
|
137
153
|
Bundler.with_original_env do
|
138
|
-
sorbet_static_detected =
|
154
|
+
sorbet_static_detected = dependencies.any?(/^sorbet-static/)
|
139
155
|
# Don't show message while running tests, since it's noisy
|
140
156
|
if sorbet_static_detected && !ruby_lsp_env_is_test
|
141
157
|
$stderr.puts("Ruby LSP detected this is a Sorbet project so will defer to Sorbet LSP for some functionality")
|
@@ -147,16 +163,11 @@ module RubyLsp
|
|
147
163
|
end
|
148
164
|
|
149
165
|
sig { returns(T::Array[String]) }
|
150
|
-
def
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
rescue Bundler::GemfileNotFound
|
156
|
-
[]
|
157
|
-
end,
|
158
|
-
T.nilable(T::Array[String]),
|
159
|
-
)
|
166
|
+
def gather_dependencies
|
167
|
+
Bundler.with_original_env { Bundler.default_gemfile }
|
168
|
+
Bundler.locked_gems.dependencies.keys + gemspec_dependencies
|
169
|
+
rescue Bundler::GemfileNotFound
|
170
|
+
[]
|
160
171
|
end
|
161
172
|
|
162
173
|
sig { returns(T::Array[String]) }
|
@@ -7,6 +7,8 @@ module RubyLsp
|
|
7
7
|
extend T::Sig
|
8
8
|
include Requests::Support::Common
|
9
9
|
|
10
|
+
MAX_NUMBER_OF_DEFINITION_CANDIDATES_WITHOUT_RECEIVER = 10
|
11
|
+
|
10
12
|
sig do
|
11
13
|
params(
|
12
14
|
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
|
@@ -64,12 +66,17 @@ module RubyLsp
|
|
64
66
|
|
65
67
|
sig { params(node: Prism::CallNode).void }
|
66
68
|
def handle_method_definition(node)
|
67
|
-
return unless self_receiver?(node)
|
68
|
-
|
69
69
|
message = node.message
|
70
70
|
return unless message
|
71
71
|
|
72
|
-
methods =
|
72
|
+
methods = if self_receiver?(node)
|
73
|
+
@index.resolve_method(message, @nesting.join("::"))
|
74
|
+
else
|
75
|
+
# If the method doesn't have a receiver, then we provide a few candidates to jump to
|
76
|
+
# But we don't want to provide too many candidates, as it can be overwhelming
|
77
|
+
@index[message]&.take(MAX_NUMBER_OF_DEFINITION_CANDIDATES_WITHOUT_RECEIVER)
|
78
|
+
end
|
79
|
+
|
73
80
|
return unless methods
|
74
81
|
|
75
82
|
methods.each do |target_method|
|
@@ -43,6 +43,7 @@ module RubyLsp
|
|
43
43
|
ResponseBuilders::CollectionResponseBuilder[Interface::Location].new,
|
44
44
|
ResponseBuilders::CollectionResponseBuilder[Interface::Location],
|
45
45
|
)
|
46
|
+
@dispatcher = dispatcher
|
46
47
|
|
47
48
|
target, parent, nesting = document.locate_node(
|
48
49
|
position,
|
@@ -50,33 +51,41 @@ module RubyLsp
|
|
50
51
|
)
|
51
52
|
|
52
53
|
if target.is_a?(Prism::ConstantReadNode) && parent.is_a?(Prism::ConstantPathNode)
|
54
|
+
# If the target is part of a constant path node, we need to find the exact portion of the constant that the
|
55
|
+
# user is requesting to go to definition for
|
53
56
|
target = determine_target(
|
54
57
|
target,
|
55
58
|
parent,
|
56
59
|
position,
|
57
60
|
)
|
61
|
+
elsif target.is_a?(Prism::CallNode) && target.name != :require && target.name != :require_relative &&
|
62
|
+
!covers_position?(target.message_loc, position)
|
63
|
+
# If the target is a method call, we need to ensure that the requested position is exactly on top of the
|
64
|
+
# method identifier. Otherwise, we risk showing definitions for unrelated things
|
65
|
+
target = nil
|
58
66
|
end
|
59
67
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
+
if target
|
69
|
+
Listeners::Definition.new(
|
70
|
+
@response_builder,
|
71
|
+
global_state,
|
72
|
+
document.uri,
|
73
|
+
nesting,
|
74
|
+
dispatcher,
|
75
|
+
typechecker_enabled,
|
76
|
+
)
|
68
77
|
|
69
|
-
|
70
|
-
|
78
|
+
Addon.addons.each do |addon|
|
79
|
+
addon.create_definition_listener(@response_builder, document.uri, nesting, dispatcher)
|
80
|
+
end
|
71
81
|
end
|
72
82
|
|
73
83
|
@target = T.let(target, T.nilable(Prism::Node))
|
74
|
-
@dispatcher = dispatcher
|
75
84
|
end
|
76
85
|
|
77
86
|
sig { override.returns(T::Array[Interface::Location]) }
|
78
87
|
def perform
|
79
|
-
@dispatcher.dispatch_once(@target)
|
88
|
+
@dispatcher.dispatch_once(@target) if @target
|
80
89
|
@response_builder.response
|
81
90
|
end
|
82
91
|
end
|
@@ -34,7 +34,7 @@ module RubyLsp
|
|
34
34
|
sig { params(global_state: GlobalState, document: Document).void }
|
35
35
|
def initialize(global_state, document)
|
36
36
|
super()
|
37
|
-
@
|
37
|
+
@active_linters = T.let(global_state.active_linters, T::Array[Support::Formatter])
|
38
38
|
@document = document
|
39
39
|
@uri = T.let(document.uri, URI::Generic)
|
40
40
|
end
|
@@ -45,10 +45,13 @@ module RubyLsp
|
|
45
45
|
diagnostics.concat(syntax_error_diagnostics, syntax_warning_diagnostics)
|
46
46
|
|
47
47
|
# Running RuboCop is slow, so to avoid excessive runs we only do so if the file is syntactically valid
|
48
|
-
return diagnostics if @document.syntax_error? ||
|
48
|
+
return diagnostics if @document.syntax_error? || @active_linters.empty?
|
49
|
+
|
50
|
+
@active_linters.each do |linter|
|
51
|
+
linter_diagnostics = linter.run_diagnostic(@uri, @document)
|
52
|
+
diagnostics.concat(linter_diagnostics) if linter_diagnostics
|
53
|
+
end
|
49
54
|
|
50
|
-
formatter_diagnostics = @active_formatter.run_diagnostic(@uri, @document)
|
51
|
-
diagnostics.concat(formatter_diagnostics) if formatter_diagnostics
|
52
55
|
diagnostics
|
53
56
|
end
|
54
57
|
|
@@ -41,25 +41,29 @@ module RubyLsp
|
|
41
41
|
end
|
42
42
|
def initialize(document, global_state, position, dispatcher, typechecker_enabled)
|
43
43
|
super()
|
44
|
-
|
45
|
-
@target, parent, nesting = document.locate_node(
|
44
|
+
target, parent, nesting = document.locate_node(
|
46
45
|
position,
|
47
46
|
node_types: Listeners::Hover::ALLOWED_TARGETS,
|
48
47
|
)
|
49
48
|
|
50
49
|
if (Listeners::Hover::ALLOWED_TARGETS.include?(parent.class) &&
|
51
|
-
!Listeners::Hover::ALLOWED_TARGETS.include?(
|
52
|
-
(parent.is_a?(Prism::ConstantPathNode) &&
|
53
|
-
|
54
|
-
T.must(
|
50
|
+
!Listeners::Hover::ALLOWED_TARGETS.include?(target.class)) ||
|
51
|
+
(parent.is_a?(Prism::ConstantPathNode) && target.is_a?(Prism::ConstantReadNode))
|
52
|
+
target = determine_target(
|
53
|
+
T.must(target),
|
55
54
|
T.must(parent),
|
56
55
|
position,
|
57
56
|
)
|
57
|
+
elsif target.is_a?(Prism::CallNode) && target.name != :require && target.name != :require_relative &&
|
58
|
+
!covers_position?(target.message_loc, position)
|
59
|
+
|
60
|
+
target = nil
|
58
61
|
end
|
59
62
|
|
60
63
|
# Don't need to instantiate any listeners if there's no target
|
61
|
-
return unless
|
64
|
+
return unless target
|
62
65
|
|
66
|
+
@target = T.let(target, T.nilable(Prism::Node))
|
63
67
|
uri = document.uri
|
64
68
|
@response_builder = T.let(ResponseBuilders::Hover.new, ResponseBuilders::Hover)
|
65
69
|
Listeners::Hover.new(@response_builder, global_state, uri, nesting, dispatcher, typechecker_enabled)
|
@@ -65,6 +65,20 @@ module RubyLsp
|
|
65
65
|
|
66
66
|
target
|
67
67
|
end
|
68
|
+
|
69
|
+
# Checks if a given location covers the position requested
|
70
|
+
sig { params(location: T.nilable(Prism::Location), position: T::Hash[Symbol, T.untyped]).returns(T::Boolean) }
|
71
|
+
def covers_position?(location, position)
|
72
|
+
return false unless location
|
73
|
+
|
74
|
+
start_line = location.start_line - 1
|
75
|
+
end_line = location.end_line - 1
|
76
|
+
line = position[:line]
|
77
|
+
character = position[:character]
|
78
|
+
|
79
|
+
(start_line < line || (start_line == line && location.start_column <= character)) &&
|
80
|
+
(end_line > line || (end_line == line && location.end_column >= character))
|
81
|
+
end
|
68
82
|
end
|
69
83
|
end
|
70
84
|
end
|
@@ -40,10 +40,13 @@ module RubyLsp
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
-
sig { params(dispatcher: Prism::Dispatcher, range: T.nilable(T::Range[Integer])).void }
|
44
|
-
def initialize(dispatcher, range: nil)
|
43
|
+
sig { params(global_state: GlobalState, dispatcher: Prism::Dispatcher, range: T.nilable(T::Range[Integer])).void }
|
44
|
+
def initialize(global_state, dispatcher, range: nil)
|
45
45
|
super()
|
46
|
-
@response_builder = T.let(
|
46
|
+
@response_builder = T.let(
|
47
|
+
ResponseBuilders::SemanticHighlighting.new(global_state.encoding),
|
48
|
+
ResponseBuilders::SemanticHighlighting,
|
49
|
+
)
|
47
50
|
Listeners::SemanticHighlighting.new(dispatcher, @response_builder, range: range)
|
48
51
|
|
49
52
|
Addon.addons.each do |addon|
|
@@ -55,19 +55,21 @@ module RubyLsp
|
|
55
55
|
|
56
56
|
ResponseType = type_member { { fixed: Interface::SemanticTokens } }
|
57
57
|
|
58
|
-
sig { void }
|
59
|
-
def initialize
|
60
|
-
super
|
58
|
+
sig { params(encoding: Encoding).void }
|
59
|
+
def initialize(encoding)
|
60
|
+
super()
|
61
|
+
@encoding = encoding
|
61
62
|
@stack = T.let([], T::Array[SemanticToken])
|
62
63
|
end
|
63
64
|
|
64
65
|
sig { params(location: Prism::Location, type: Symbol, modifiers: T::Array[Symbol]).void }
|
65
66
|
def add_token(location, type, modifiers = [])
|
66
|
-
length = location.
|
67
|
+
length = location.end_code_units_offset(@encoding) - location.start_code_units_offset(@encoding)
|
67
68
|
modifiers_indices = modifiers.filter_map { |modifier| TOKEN_MODIFIERS[modifier] }
|
68
69
|
@stack.push(
|
69
70
|
SemanticToken.new(
|
70
|
-
|
71
|
+
start_line: location.start_line,
|
72
|
+
start_code_unit_column: location.start_code_units_column(@encoding),
|
71
73
|
length: length,
|
72
74
|
type: T.must(TOKEN_TYPES[type]),
|
73
75
|
modifier: modifiers_indices,
|
@@ -75,6 +77,15 @@ module RubyLsp
|
|
75
77
|
)
|
76
78
|
end
|
77
79
|
|
80
|
+
sig { params(location: Prism::Location).returns(T::Boolean) }
|
81
|
+
def last_token_matches?(location)
|
82
|
+
token = @stack.last
|
83
|
+
return false unless token
|
84
|
+
|
85
|
+
token.start_line == location.start_line &&
|
86
|
+
token.start_code_unit_column == location.start_code_units_column(@encoding)
|
87
|
+
end
|
88
|
+
|
78
89
|
sig { returns(T.nilable(SemanticToken)) }
|
79
90
|
def last
|
80
91
|
@stack.last
|
@@ -88,8 +99,11 @@ module RubyLsp
|
|
88
99
|
class SemanticToken
|
89
100
|
extend T::Sig
|
90
101
|
|
91
|
-
sig { returns(
|
92
|
-
attr_reader :
|
102
|
+
sig { returns(Integer) }
|
103
|
+
attr_reader :start_line
|
104
|
+
|
105
|
+
sig { returns(Integer) }
|
106
|
+
attr_reader :start_code_unit_column
|
93
107
|
|
94
108
|
sig { returns(Integer) }
|
95
109
|
attr_reader :length
|
@@ -100,9 +114,18 @@ module RubyLsp
|
|
100
114
|
sig { returns(T::Array[Integer]) }
|
101
115
|
attr_reader :modifier
|
102
116
|
|
103
|
-
sig
|
104
|
-
|
105
|
-
|
117
|
+
sig do
|
118
|
+
params(
|
119
|
+
start_line: Integer,
|
120
|
+
start_code_unit_column: Integer,
|
121
|
+
length: Integer,
|
122
|
+
type: Integer,
|
123
|
+
modifier: T::Array[Integer],
|
124
|
+
).void
|
125
|
+
end
|
126
|
+
def initialize(start_line:, start_code_unit_column:, length:, type:, modifier:)
|
127
|
+
@start_line = start_line
|
128
|
+
@start_code_unit_column = start_code_unit_column
|
106
129
|
@length = length
|
107
130
|
@type = type
|
108
131
|
@modifier = modifier
|
@@ -146,7 +169,7 @@ module RubyLsp
|
|
146
169
|
# Enumerable#sort_by is not deterministic when the compared values are equal.
|
147
170
|
# When that happens, we need to use the index as a tie breaker to ensure
|
148
171
|
# that the order of the tokens is always the same.
|
149
|
-
[token.
|
172
|
+
[token.start_line, token.start_code_unit_column, index]
|
150
173
|
end
|
151
174
|
|
152
175
|
delta = sorted_tokens.flat_map do |token|
|
@@ -167,8 +190,8 @@ module RubyLsp
|
|
167
190
|
# https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens
|
168
191
|
sig { params(token: SemanticToken).returns(T::Array[Integer]) }
|
169
192
|
def compute_delta(token)
|
170
|
-
row = token.
|
171
|
-
column = token.
|
193
|
+
row = token.start_line - 1
|
194
|
+
column = token.start_code_unit_column
|
172
195
|
|
173
196
|
begin
|
174
197
|
delta_line = row - @current_row
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -334,7 +334,7 @@ module RubyLsp
|
|
334
334
|
document_link = Requests::DocumentLink.new(uri, document.comments, dispatcher)
|
335
335
|
code_lens = Requests::CodeLens.new(@global_state, uri, dispatcher)
|
336
336
|
|
337
|
-
semantic_highlighting = Requests::SemanticHighlighting.new(dispatcher)
|
337
|
+
semantic_highlighting = Requests::SemanticHighlighting.new(@global_state, dispatcher)
|
338
338
|
dispatcher.dispatch(document.tree)
|
339
339
|
|
340
340
|
# Store all responses retrieve in this round of visits in the cache and then return the response for the request
|
@@ -366,7 +366,7 @@ module RubyLsp
|
|
366
366
|
end_line = range.dig(:end, :line)
|
367
367
|
|
368
368
|
dispatcher = Prism::Dispatcher.new
|
369
|
-
request = Requests::SemanticHighlighting.new(dispatcher, range: start_line..end_line)
|
369
|
+
request = Requests::SemanticHighlighting.new(@global_state, dispatcher, range: start_line..end_line)
|
370
370
|
dispatcher.visit(document.tree)
|
371
371
|
|
372
372
|
response = request.perform
|
data/lib/ruby_lsp/utils.rb
CHANGED
@@ -75,7 +75,7 @@ module RubyLsp
|
|
75
75
|
class Request < Message
|
76
76
|
extend T::Sig
|
77
77
|
|
78
|
-
sig { params(id: Integer, method: String, params: Object).void }
|
78
|
+
sig { params(id: T.any(Integer, String), method: String, params: Object).void }
|
79
79
|
def initialize(id:, method:, params:)
|
80
80
|
@id = id
|
81
81
|
super(method: method, params: params)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-lsp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.16.
|
4
|
+
version: 0.16.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-04-
|
11
|
+
date: 2024-04-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: language_server-protocol
|
@@ -30,20 +30,20 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
33
|
+
version: 0.23.0
|
34
34
|
- - "<"
|
35
35
|
- !ruby/object:Gem::Version
|
36
|
-
version: '0.
|
36
|
+
version: '0.28'
|
37
37
|
type: :runtime
|
38
38
|
prerelease: false
|
39
39
|
version_requirements: !ruby/object:Gem::Requirement
|
40
40
|
requirements:
|
41
41
|
- - ">="
|
42
42
|
- !ruby/object:Gem::Version
|
43
|
-
version: 0.
|
43
|
+
version: 0.23.0
|
44
44
|
- - "<"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: '0.
|
46
|
+
version: '0.28'
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: sorbet-runtime
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -83,6 +83,7 @@ files:
|
|
83
83
|
- lib/ruby_indexer/lib/ruby_indexer/entry.rb
|
84
84
|
- lib/ruby_indexer/lib/ruby_indexer/index.rb
|
85
85
|
- lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb
|
86
|
+
- lib/ruby_indexer/lib/ruby_indexer/location.rb
|
86
87
|
- lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb
|
87
88
|
- lib/ruby_indexer/ruby_indexer.rb
|
88
89
|
- lib/ruby_indexer/test/classes_and_modules_test.rb
|
@@ -177,7 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
177
178
|
- !ruby/object:Gem::Version
|
178
179
|
version: '0'
|
179
180
|
requirements: []
|
180
|
-
rubygems_version: 3.5.
|
181
|
+
rubygems_version: 3.5.9
|
181
182
|
signing_key:
|
182
183
|
specification_version: 4
|
183
184
|
summary: An opinionated language server for Ruby
|