ruby-lsp 0.16.4 → 0.16.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|