ruby-lsp 0.4.3 → 0.4.4
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/README.md +1 -0
- data/VERSION +1 -1
- data/lib/ruby_lsp/document.rb +62 -3
- data/lib/ruby_lsp/event_emitter.rb +52 -0
- data/lib/ruby_lsp/executor.rb +40 -2
- data/lib/ruby_lsp/internal.rb +2 -0
- data/lib/ruby_lsp/listener.rb +39 -0
- data/lib/ruby_lsp/requests/base_request.rb +2 -85
- data/lib/ruby_lsp/requests/code_action_resolve.rb +46 -19
- data/lib/ruby_lsp/requests/code_lens.rb +135 -0
- data/lib/ruby_lsp/requests/document_highlight.rb +4 -6
- data/lib/ruby_lsp/requests/hover.rb +27 -32
- data/lib/ruby_lsp/requests/path_completion.rb +7 -7
- data/lib/ruby_lsp/requests/support/common.rb +55 -0
- data/lib/ruby_lsp/requests.rb +3 -0
- data/lib/ruby_lsp/store.rb +3 -3
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 61c8fe2a55057823533a8a17b4472309e3d463ec3d63302fa3393f046ec4b339
|
4
|
+
data.tar.gz: fb9636b8aafac9d5002a872d0f34f7f9852e9348009cdc2934369637a4cadb4c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: db51fd5526431b773125c0b27bfe8ca53b2861759457f9a36e7a681bcfbffb0f58026b4f8cc4dc991be2380309959aacde7c8cc2cd0a0f0373dfb313d0d28db9
|
7
|
+
data.tar.gz: 13cc3991bffd1a18dd1e2580c8d1051161b03a171a25e8ce681f46fb8d5baedb1de0f9f96304556e08f3db95c2262e29693d22bf3659ba45eff620157adc035c
|
data/README.md
CHANGED
@@ -44,6 +44,7 @@ See the [documentation](https://shopify.github.io/ruby-lsp) for more in-depth de
|
|
44
44
|
## Learn More
|
45
45
|
|
46
46
|
* [RubyConf 2022: Improving the development experience with language servers](https://www.youtube.com/watch?v=kEfXPTm1aCI) ([Vinicius Stock](https://github.com/vinistock))
|
47
|
+
* [Remote Ruby: Ruby Language Server with Vinicius Stock](https://remoteruby.com/221)
|
47
48
|
|
48
49
|
## Contributing
|
49
50
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.4.
|
1
|
+
0.4.4
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -22,7 +22,7 @@ module RubyLsp
|
|
22
22
|
attr_reader :uri
|
23
23
|
|
24
24
|
sig { params(source: String, version: Integer, uri: String, encoding: String).void }
|
25
|
-
def initialize(source:, version:, uri:, encoding:
|
25
|
+
def initialize(source:, version:, uri:, encoding: Constant::PositionEncodingKind::UTF8)
|
26
26
|
@cache = T.let({}, T::Hash[Symbol, T.untyped])
|
27
27
|
@encoding = T.let(encoding, String)
|
28
28
|
@source = T.let(source, String)
|
@@ -77,9 +77,9 @@ module RubyLsp
|
|
77
77
|
def parse
|
78
78
|
return if @unparsed_edits.empty?
|
79
79
|
|
80
|
+
@unparsed_edits.clear
|
80
81
|
@tree = SyntaxTree.parse(@source)
|
81
82
|
@syntax_error = false
|
82
|
-
@unparsed_edits.clear
|
83
83
|
rescue SyntaxTree::Parser::ParseError
|
84
84
|
@syntax_error = true
|
85
85
|
end
|
@@ -99,6 +99,61 @@ module RubyLsp
|
|
99
99
|
Scanner.new(@source, @encoding)
|
100
100
|
end
|
101
101
|
|
102
|
+
sig do
|
103
|
+
params(
|
104
|
+
position: PositionShape,
|
105
|
+
node_types: T::Array[T.class_of(SyntaxTree::Node)],
|
106
|
+
).returns([T.nilable(SyntaxTree::Node), T.nilable(SyntaxTree::Node)])
|
107
|
+
end
|
108
|
+
def locate_node(position, node_types: [])
|
109
|
+
return [nil, nil] unless parsed?
|
110
|
+
|
111
|
+
locate(T.must(@tree), create_scanner.find_char_position(position))
|
112
|
+
end
|
113
|
+
|
114
|
+
sig do
|
115
|
+
params(
|
116
|
+
node: SyntaxTree::Node,
|
117
|
+
char_position: Integer,
|
118
|
+
node_types: T::Array[T.class_of(SyntaxTree::Node)],
|
119
|
+
).returns([T.nilable(SyntaxTree::Node), T.nilable(SyntaxTree::Node)])
|
120
|
+
end
|
121
|
+
def locate(node, char_position, node_types: [])
|
122
|
+
queue = T.let(node.child_nodes.compact, T::Array[T.nilable(SyntaxTree::Node)])
|
123
|
+
closest = node
|
124
|
+
parent = T.let(nil, T.nilable(SyntaxTree::Node))
|
125
|
+
|
126
|
+
until queue.empty?
|
127
|
+
candidate = queue.shift
|
128
|
+
|
129
|
+
# Skip nil child nodes
|
130
|
+
next if candidate.nil?
|
131
|
+
|
132
|
+
# Add the next child_nodes to the queue to be processed
|
133
|
+
queue.concat(candidate.child_nodes)
|
134
|
+
|
135
|
+
# Skip if the current node doesn't cover the desired position
|
136
|
+
loc = candidate.location
|
137
|
+
next unless (loc.start_char...loc.end_char).cover?(char_position)
|
138
|
+
|
139
|
+
# If the node's start character is already past the position, then we should've found the closest node
|
140
|
+
# already
|
141
|
+
break if char_position < loc.start_char
|
142
|
+
|
143
|
+
# If there are node types to filter by, and the current node is not one of those types, then skip it
|
144
|
+
next if node_types.any? && node_types.none? { |type| candidate.class == type }
|
145
|
+
|
146
|
+
# If the current node is narrower than or equal to the previous closest node, then it is more precise
|
147
|
+
closest_loc = closest.location
|
148
|
+
if loc.end_char - loc.start_char <= closest_loc.end_char - closest_loc.start_char
|
149
|
+
parent = closest
|
150
|
+
closest = candidate
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
[closest, parent]
|
155
|
+
end
|
156
|
+
|
102
157
|
class Scanner
|
103
158
|
extend T::Sig
|
104
159
|
|
@@ -127,7 +182,11 @@ module RubyLsp
|
|
127
182
|
# The final position is the beginning of the line plus the requested column. If the encoding is UTF-16, we also
|
128
183
|
# need to adjust for surrogate pairs
|
129
184
|
requested_position = @pos + position[:character]
|
130
|
-
|
185
|
+
|
186
|
+
if @encoding == Constant::PositionEncodingKind::UTF16
|
187
|
+
requested_position -= utf_16_character_position_correction(@pos, requested_position)
|
188
|
+
end
|
189
|
+
|
131
190
|
requested_position
|
132
191
|
end
|
133
192
|
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
# EventEmitter is an intermediary between our requests and Syntax Tree visitors. It's used to visit the document's AST
|
6
|
+
# and emit events that the requests can listen to for providing functionality. Usages:
|
7
|
+
#
|
8
|
+
# - For positional requests, locate the target node and use `emit_for_target` to fire events for each listener
|
9
|
+
# - For nonpositional requests, use `visit` to go through the AST, which will fire events for each listener as nodes
|
10
|
+
# are found
|
11
|
+
#
|
12
|
+
# = Example
|
13
|
+
#
|
14
|
+
# ```ruby
|
15
|
+
# target_node = document.locate_node(position)
|
16
|
+
# listener = Requests::Hover.new
|
17
|
+
# EventEmitter.new(listener).emit_for_target(target_node)
|
18
|
+
# listener.response
|
19
|
+
# ```
|
20
|
+
class EventEmitter < SyntaxTree::Visitor
|
21
|
+
extend T::Sig
|
22
|
+
|
23
|
+
sig { params(listeners: Listener[T.untyped]).void }
|
24
|
+
def initialize(*listeners)
|
25
|
+
@listeners = listeners
|
26
|
+
|
27
|
+
# Create a map of event name to listeners that have registered it, so that we avoid unnecessary invocations
|
28
|
+
@event_to_listener_map = T.let(
|
29
|
+
listeners.each_with_object(Hash.new { |h, k| h[k] = [] }) do |listener, hash|
|
30
|
+
listener.class.events&.each { |event| hash[event] << listener }
|
31
|
+
end,
|
32
|
+
T::Hash[Symbol, T::Array[Listener[T.untyped]]],
|
33
|
+
)
|
34
|
+
|
35
|
+
super()
|
36
|
+
end
|
37
|
+
|
38
|
+
# Emit events for a specific node. This is similar to the regular `visit` method, but avoids going deeper into the
|
39
|
+
# tree for performance
|
40
|
+
sig { params(node: T.nilable(SyntaxTree::Node)).void }
|
41
|
+
def emit_for_target(node)
|
42
|
+
case node
|
43
|
+
when SyntaxTree::Command
|
44
|
+
@event_to_listener_map[:on_command]&.each { |listener| T.unsafe(listener).on_command(node) }
|
45
|
+
when SyntaxTree::CallNode
|
46
|
+
@event_to_listener_map[:on_call]&.each { |listener| T.unsafe(listener).on_call(node) }
|
47
|
+
when SyntaxTree::ConstPathRef
|
48
|
+
@event_to_listener_map[:on_const_path_ref]&.each { |listener| T.unsafe(listener).on_const_path_ref(node) }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/ruby_lsp/executor.rb
CHANGED
@@ -123,6 +123,8 @@ module RubyLsp
|
|
123
123
|
end
|
124
124
|
when "textDocument/completion"
|
125
125
|
completion(uri, request.dig(:params, :position))
|
126
|
+
when "textDocument/codeLens"
|
127
|
+
code_lens(uri)
|
126
128
|
end
|
127
129
|
end
|
128
130
|
|
@@ -133,6 +135,13 @@ module RubyLsp
|
|
133
135
|
end
|
134
136
|
end
|
135
137
|
|
138
|
+
sig { params(uri: String).returns(T::Array[Interface::CodeLens]) }
|
139
|
+
def code_lens(uri)
|
140
|
+
@store.cache_fetch(uri, :code_lens) do |document|
|
141
|
+
Requests::CodeLens.new(document).run
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
136
145
|
sig do
|
137
146
|
params(
|
138
147
|
uri: String,
|
@@ -140,7 +149,20 @@ module RubyLsp
|
|
140
149
|
).returns(T.nilable(Interface::Hover))
|
141
150
|
end
|
142
151
|
def hover(uri, position)
|
143
|
-
|
152
|
+
document = @store.get(uri)
|
153
|
+
document.parse
|
154
|
+
return if document.syntax_error?
|
155
|
+
|
156
|
+
target, parent = document.locate_node(position)
|
157
|
+
|
158
|
+
if !Requests::Hover::ALLOWED_TARGETS.include?(target.class) &&
|
159
|
+
Requests::Hover::ALLOWED_TARGETS.include?(parent.class)
|
160
|
+
target = parent
|
161
|
+
end
|
162
|
+
|
163
|
+
listener = RubyLsp::Requests::Hover.new
|
164
|
+
EventEmitter.new(listener).emit_for_target(target)
|
165
|
+
listener.response
|
144
166
|
end
|
145
167
|
|
146
168
|
sig { params(uri: String).returns(T::Array[Interface::DocumentLink]) }
|
@@ -326,11 +348,21 @@ module RubyLsp
|
|
326
348
|
sig { params(options: T::Hash[Symbol, T.untyped]).returns(Interface::InitializeResult) }
|
327
349
|
def initialize_request(options)
|
328
350
|
@store.clear
|
329
|
-
|
351
|
+
|
352
|
+
encodings = options.dig(:capabilities, :general, :positionEncodings)
|
353
|
+
@store.encoding = if encodings.nil? || encodings.empty?
|
354
|
+
Constant::PositionEncodingKind::UTF16
|
355
|
+
elsif encodings.include?(Constant::PositionEncodingKind::UTF8)
|
356
|
+
Constant::PositionEncodingKind::UTF8
|
357
|
+
else
|
358
|
+
encodings.first
|
359
|
+
end
|
360
|
+
|
330
361
|
formatter = options.dig(:initializationOptions, :formatter)
|
331
362
|
@store.formatter = formatter unless formatter.nil?
|
332
363
|
|
333
364
|
configured_features = options.dig(:initializationOptions, :enabledFeatures)
|
365
|
+
experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled)
|
334
366
|
|
335
367
|
enabled_features = case configured_features
|
336
368
|
when Array
|
@@ -359,6 +391,10 @@ module RubyLsp
|
|
359
391
|
Interface::DocumentLinkOptions.new(resolve_provider: false)
|
360
392
|
end
|
361
393
|
|
394
|
+
code_lens_provider = if experimental_features
|
395
|
+
Interface::CodeLensOptions.new(resolve_provider: false)
|
396
|
+
end
|
397
|
+
|
362
398
|
hover_provider = if enabled_features["hover"]
|
363
399
|
Interface::HoverClientCapabilities.new(dynamic_registration: false)
|
364
400
|
end
|
@@ -414,6 +450,7 @@ module RubyLsp
|
|
414
450
|
change: Constant::TextDocumentSyncKind::INCREMENTAL,
|
415
451
|
open_close: true,
|
416
452
|
),
|
453
|
+
position_encoding: @store.encoding,
|
417
454
|
selection_range_provider: enabled_features["selectionRanges"],
|
418
455
|
hover_provider: hover_provider,
|
419
456
|
document_symbol_provider: document_symbol_provider,
|
@@ -427,6 +464,7 @@ module RubyLsp
|
|
427
464
|
diagnostic_provider: diagnostics_provider,
|
428
465
|
inlay_hint_provider: inlay_hint_provider,
|
429
466
|
completion_provider: completion_provider,
|
467
|
+
code_lens_provider: code_lens_provider,
|
430
468
|
),
|
431
469
|
)
|
432
470
|
end
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -0,0 +1,39 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
# Listener is an abstract class to be used by requests for listening to events emitted when visiting an AST using the
|
6
|
+
# EventEmitter.
|
7
|
+
class Listener
|
8
|
+
extend T::Sig
|
9
|
+
extend T::Helpers
|
10
|
+
extend T::Generic
|
11
|
+
include Requests::Support::Common
|
12
|
+
|
13
|
+
ResponseType = type_member
|
14
|
+
|
15
|
+
abstract!
|
16
|
+
|
17
|
+
class << self
|
18
|
+
extend T::Sig
|
19
|
+
|
20
|
+
sig { returns(T.nilable(T::Array[Symbol])) }
|
21
|
+
attr_reader :events
|
22
|
+
|
23
|
+
# All listener events must be defined inside of a `listener_events` block. This is to ensure we know which events
|
24
|
+
# have been registered. Defining an event outside of this block will simply not register it and it'll never be
|
25
|
+
# invoked
|
26
|
+
sig { params(block: T.proc.void).void }
|
27
|
+
def listener_events(&block)
|
28
|
+
current_methods = instance_methods
|
29
|
+
block.call
|
30
|
+
@events = T.let(instance_methods - current_methods, T.nilable(T::Array[Symbol]))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Override this method with an attr_reader that returns the response of your listener. The listener should
|
35
|
+
# accumulate results in a @response variable and then provide the reader so that it is accessible
|
36
|
+
sig { abstract.returns(ResponseType) }
|
37
|
+
def response; end
|
38
|
+
end
|
39
|
+
end
|
@@ -7,6 +7,7 @@ module RubyLsp
|
|
7
7
|
class BaseRequest < SyntaxTree::Visitor
|
8
8
|
extend T::Sig
|
9
9
|
extend T::Helpers
|
10
|
+
include Support::Common
|
10
11
|
|
11
12
|
abstract!
|
12
13
|
|
@@ -31,94 +32,10 @@ module RubyLsp
|
|
31
32
|
# Syntax Tree implements `visit_all` using `map` instead of `each` for users who want to use the pattern
|
32
33
|
# `result = visitor.visit(tree)`. However, we don't use that pattern and should avoid producing a new array for
|
33
34
|
# every single node visited
|
34
|
-
sig { params(nodes: T::Array[SyntaxTree::Node]).void }
|
35
|
+
sig { params(nodes: T::Array[T.nilable(SyntaxTree::Node)]).void }
|
35
36
|
def visit_all(nodes)
|
36
37
|
nodes.each { |node| visit(node) }
|
37
38
|
end
|
38
|
-
|
39
|
-
sig { params(node: SyntaxTree::Node).returns(Interface::Range) }
|
40
|
-
def range_from_syntax_tree_node(node)
|
41
|
-
loc = node.location
|
42
|
-
|
43
|
-
Interface::Range.new(
|
44
|
-
start: Interface::Position.new(
|
45
|
-
line: loc.start_line - 1,
|
46
|
-
character: loc.start_column,
|
47
|
-
),
|
48
|
-
end: Interface::Position.new(line: loc.end_line - 1, character: loc.end_column),
|
49
|
-
)
|
50
|
-
end
|
51
|
-
|
52
|
-
sig do
|
53
|
-
params(node: T.any(SyntaxTree::ConstPathRef, SyntaxTree::ConstRef, SyntaxTree::TopConstRef)).returns(String)
|
54
|
-
end
|
55
|
-
def full_constant_name(node)
|
56
|
-
name = +node.constant.value
|
57
|
-
constant = T.let(node, SyntaxTree::Node)
|
58
|
-
|
59
|
-
while constant.is_a?(SyntaxTree::ConstPathRef)
|
60
|
-
constant = constant.parent
|
61
|
-
|
62
|
-
case constant
|
63
|
-
when SyntaxTree::ConstPathRef
|
64
|
-
name.prepend("#{constant.constant.value}::")
|
65
|
-
when SyntaxTree::VarRef
|
66
|
-
name.prepend("#{constant.value.value}::")
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
name
|
71
|
-
end
|
72
|
-
|
73
|
-
sig do
|
74
|
-
params(
|
75
|
-
node: SyntaxTree::Node,
|
76
|
-
position: Integer,
|
77
|
-
node_types: T::Array[T.class_of(SyntaxTree::Node)],
|
78
|
-
).returns([T.nilable(SyntaxTree::Node), T.nilable(SyntaxTree::Node)])
|
79
|
-
end
|
80
|
-
def locate(node, position, node_types: [])
|
81
|
-
queue = T.let(node.child_nodes.compact, T::Array[T.nilable(SyntaxTree::Node)])
|
82
|
-
closest = node
|
83
|
-
|
84
|
-
until queue.empty?
|
85
|
-
candidate = queue.shift
|
86
|
-
|
87
|
-
# Skip nil child nodes
|
88
|
-
next if candidate.nil?
|
89
|
-
|
90
|
-
# Add the next child_nodes to the queue to be processed
|
91
|
-
queue.concat(candidate.child_nodes)
|
92
|
-
|
93
|
-
# Skip if the current node doesn't cover the desired position
|
94
|
-
loc = candidate.location
|
95
|
-
next unless (loc.start_char...loc.end_char).cover?(position)
|
96
|
-
|
97
|
-
# If the node's start character is already past the position, then we should've found the closest node already
|
98
|
-
break if position < loc.start_char
|
99
|
-
|
100
|
-
# If there are node types to filter by, and the current node is not one of those types, then skip it
|
101
|
-
next if node_types.any? && node_types.none? { |type| candidate.is_a?(type) }
|
102
|
-
|
103
|
-
# If the current node is narrower than or equal to the previous closest node, then it is more precise
|
104
|
-
closest_loc = closest.location
|
105
|
-
if loc.end_char - loc.start_char <= closest_loc.end_char - closest_loc.start_char
|
106
|
-
parent = T.let(closest, SyntaxTree::Node)
|
107
|
-
closest = candidate
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
[closest, parent]
|
112
|
-
end
|
113
|
-
|
114
|
-
sig { params(node: T.nilable(SyntaxTree::Node), range: T.nilable(T::Range[Integer])).returns(T::Boolean) }
|
115
|
-
def visible?(node, range)
|
116
|
-
return true if range.nil?
|
117
|
-
return false if node.nil?
|
118
|
-
|
119
|
-
loc = node.location
|
120
|
-
range.cover?(loc.start_line - 1) && range.cover?(loc.end_line - 1)
|
121
|
-
end
|
122
39
|
end
|
123
40
|
end
|
124
41
|
end
|
@@ -54,7 +54,8 @@ module RubyLsp
|
|
54
54
|
extracted_source = T.must(@document.source[start_index...end_index])
|
55
55
|
|
56
56
|
# Find the closest statements node, so that we place the refactor in a valid position
|
57
|
-
closest_statements =
|
57
|
+
closest_statements, parent_statements = @document
|
58
|
+
.locate(T.must(@document.tree), start_index, node_types: [SyntaxTree::Statements])
|
58
59
|
return Error::InvalidTargetRange if closest_statements.nil?
|
59
60
|
|
60
61
|
# Find the node with the end line closest to the requested position, so that we can place the refactor
|
@@ -64,25 +65,51 @@ module RubyLsp
|
|
64
65
|
distance <= 0 ? Float::INFINITY : distance
|
65
66
|
end
|
66
67
|
|
67
|
-
#
|
68
|
-
|
69
|
-
|
68
|
+
# Find the parent expression of the closest node
|
69
|
+
parent_expression = parent_statements.child_nodes.compact.find do |node|
|
70
|
+
loc = node.location
|
71
|
+
loc.start_line - 1 <= source_range.dig(:start, :line) && loc.end_line - 1 >= source_range.dig(:end, :line)
|
72
|
+
end if parent_statements
|
73
|
+
|
74
|
+
closest_node_loc = closest_node.location
|
75
|
+
# If the parent expression is a single line block, then we have to extract it inside of the oneline block
|
76
|
+
if parent_expression.is_a?(SyntaxTree::MethodAddBlock) &&
|
77
|
+
parent_expression.location.start_line == parent_expression.location.end_line
|
78
|
+
|
79
|
+
variable_source = " #{NEW_VARIABLE_NAME} = #{extracted_source};"
|
80
|
+
character = source_range.dig(:start, :character) - 1
|
81
|
+
target_range = {
|
82
|
+
start: { line: closest_node_loc.end_line - 1, character: character },
|
83
|
+
end: { line: closest_node_loc.end_line - 1, character: character },
|
84
|
+
}
|
70
85
|
else
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
+
# If the closest node covers the requested location, then we're extracting a statement nested inside of it. In
|
87
|
+
# that case, we want to place the extraction at the start of the closest node (one line above). Otherwise, we
|
88
|
+
# want to place the extract right below the closest node
|
89
|
+
if closest_node_loc.start_line - 1 <= source_range.dig(
|
90
|
+
:start,
|
91
|
+
:line,
|
92
|
+
) && closest_node_loc.end_line - 1 >= source_range.dig(:end, :line)
|
93
|
+
indentation_line = closest_node_loc.start_line - 1
|
94
|
+
target_line = indentation_line
|
95
|
+
else
|
96
|
+
target_line = closest_node_loc.end_line
|
97
|
+
indentation_line = closest_node_loc.end_line - 1
|
98
|
+
end
|
99
|
+
|
100
|
+
lines = @document.source.lines
|
101
|
+
indentation = T.must(T.must(lines[indentation_line])[/\A */]).size
|
102
|
+
|
103
|
+
target_range = {
|
104
|
+
start: { line: target_line, character: indentation },
|
105
|
+
end: { line: target_line, character: indentation },
|
106
|
+
}
|
107
|
+
|
108
|
+
variable_source = if T.must(lines[target_line]).strip.empty?
|
109
|
+
"\n#{" " * indentation}#{NEW_VARIABLE_NAME} = #{extracted_source}"
|
110
|
+
else
|
111
|
+
"#{NEW_VARIABLE_NAME} = #{extracted_source}\n#{" " * indentation}"
|
112
|
+
end
|
86
113
|
end
|
87
114
|
|
88
115
|
Interface::CodeAction.new(
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Requests
|
6
|
+
# 
|
7
|
+
#
|
8
|
+
# This feature is currently experimental. Clients will need to pass `experimentalFeaturesEnabled`
|
9
|
+
# in the initialization options to enable it.
|
10
|
+
#
|
11
|
+
# The
|
12
|
+
# [code lens](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeLens)
|
13
|
+
# request informs the editor of runnable commands such as tests
|
14
|
+
#
|
15
|
+
# # Example
|
16
|
+
#
|
17
|
+
# ```ruby
|
18
|
+
# # Run
|
19
|
+
# class Test < Minitest::Test
|
20
|
+
# end
|
21
|
+
# ```
|
22
|
+
|
23
|
+
class CodeLens < BaseRequest
|
24
|
+
BASE_COMMAND = T.let((File.exist?("Gemfile.lock") ? "bundle exec ruby" : "ruby") + " -Itest ", String)
|
25
|
+
ACCESS_MODIFIERS = T.let(["public", "private", "protected"], T::Array[String])
|
26
|
+
|
27
|
+
sig do
|
28
|
+
params(
|
29
|
+
document: Document,
|
30
|
+
).void
|
31
|
+
end
|
32
|
+
def initialize(document)
|
33
|
+
super(document)
|
34
|
+
@results = T.let([], T::Array[Interface::CodeLens])
|
35
|
+
@path = T.let(document.uri.delete_prefix("file://"), String)
|
36
|
+
@modifier = T.let("public", String)
|
37
|
+
end
|
38
|
+
|
39
|
+
sig { override.returns(T.all(T::Array[Interface::CodeLens], Object)) }
|
40
|
+
def run
|
41
|
+
visit(@document.tree) if @document.parsed?
|
42
|
+
@results
|
43
|
+
end
|
44
|
+
|
45
|
+
sig { override.params(node: SyntaxTree::ClassDeclaration).void }
|
46
|
+
def visit_class(node)
|
47
|
+
class_name = node.constant.constant.value
|
48
|
+
if class_name.end_with?("Test")
|
49
|
+
add_code_lens(node, name: class_name, command: BASE_COMMAND + @path)
|
50
|
+
end
|
51
|
+
visit(node.bodystmt)
|
52
|
+
end
|
53
|
+
|
54
|
+
sig { override.params(node: SyntaxTree::DefNode).void }
|
55
|
+
def visit_def(node)
|
56
|
+
if @modifier == "public"
|
57
|
+
method_name = node.name.value
|
58
|
+
if method_name.start_with?("test_")
|
59
|
+
add_code_lens(
|
60
|
+
node,
|
61
|
+
name: method_name,
|
62
|
+
command: BASE_COMMAND + @path + " --name " + method_name,
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
sig { override.params(node: SyntaxTree::Command).void }
|
69
|
+
def visit_command(node)
|
70
|
+
if node.message.value == "public"
|
71
|
+
with_visiblity("public", node)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
sig { override.params(node: SyntaxTree::CallNode).void }
|
76
|
+
def visit_call(node)
|
77
|
+
ident = node.message if node.message.is_a?(SyntaxTree::Ident)
|
78
|
+
|
79
|
+
if ident
|
80
|
+
if T.cast(ident, SyntaxTree::Ident).value == "public"
|
81
|
+
with_visiblity("public", node)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
sig { override.params(node: SyntaxTree::VCall).void }
|
87
|
+
def visit_vcall(node)
|
88
|
+
vcall_value = node.value.value
|
89
|
+
|
90
|
+
if ACCESS_MODIFIERS.include?(vcall_value)
|
91
|
+
@modifier = vcall_value
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
sig do
|
98
|
+
params(
|
99
|
+
visibility: String,
|
100
|
+
node: T.any(SyntaxTree::CallNode, SyntaxTree::Command),
|
101
|
+
).void
|
102
|
+
end
|
103
|
+
def with_visiblity(visibility, node)
|
104
|
+
current_visibility = @modifier
|
105
|
+
@modifier = visibility
|
106
|
+
visit(node.arguments)
|
107
|
+
ensure
|
108
|
+
@modifier = T.must(current_visibility)
|
109
|
+
end
|
110
|
+
|
111
|
+
sig { params(node: SyntaxTree::Node, name: String, command: String).void }
|
112
|
+
def add_code_lens(node, name:, command:)
|
113
|
+
@results << Interface::CodeLens.new(
|
114
|
+
range: range_from_syntax_tree_node(node),
|
115
|
+
command: Interface::Command.new(
|
116
|
+
title: "Run",
|
117
|
+
command: "rubyLsp.runTest",
|
118
|
+
arguments: [
|
119
|
+
@path,
|
120
|
+
name,
|
121
|
+
command,
|
122
|
+
{
|
123
|
+
start_line: node.location.start_line - 1,
|
124
|
+
start_column: node.location.start_column,
|
125
|
+
end_line: node.location.end_line - 1,
|
126
|
+
end_column: node.location.end_column,
|
127
|
+
},
|
128
|
+
],
|
129
|
+
),
|
130
|
+
data: { type: "test" },
|
131
|
+
)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -32,8 +32,7 @@ module RubyLsp
|
|
32
32
|
@highlights = T.let([], T::Array[Interface::DocumentHighlight])
|
33
33
|
return unless document.parsed?
|
34
34
|
|
35
|
-
|
36
|
-
@target = T.let(find(T.must(document.tree), position), T.nilable(Support::HighlightTarget))
|
35
|
+
@target = T.let(find(position), T.nilable(Support::HighlightTarget))
|
37
36
|
end
|
38
37
|
|
39
38
|
sig { override.returns(T.all(T::Array[Interface::DocumentHighlight], Object)) }
|
@@ -68,12 +67,11 @@ module RubyLsp
|
|
68
67
|
|
69
68
|
sig do
|
70
69
|
params(
|
71
|
-
|
72
|
-
position: Integer,
|
70
|
+
position: Document::PositionShape,
|
73
71
|
).returns(T.nilable(Support::HighlightTarget))
|
74
72
|
end
|
75
|
-
def find(
|
76
|
-
matched, parent =
|
73
|
+
def find(position)
|
74
|
+
matched, parent = @document.locate_node(position)
|
77
75
|
|
78
76
|
return unless matched && parent
|
79
77
|
return unless matched.is_a?(SyntaxTree::Ident) || DIRECT_HIGHLIGHTS.include?(matched.class)
|
@@ -17,8 +17,11 @@ module RubyLsp
|
|
17
17
|
# before_save :do_something # when hovering on before_save, the link will be rendered
|
18
18
|
# end
|
19
19
|
# ```
|
20
|
-
class Hover <
|
20
|
+
class Hover < Listener
|
21
21
|
extend T::Sig
|
22
|
+
extend T::Generic
|
23
|
+
|
24
|
+
ResponseType = type_member { { fixed: T.nilable(Interface::Hover) } }
|
22
25
|
|
23
26
|
ALLOWED_TARGETS = T.let(
|
24
27
|
[
|
@@ -29,53 +32,45 @@ module RubyLsp
|
|
29
32
|
T::Array[T.class_of(SyntaxTree::Node)],
|
30
33
|
)
|
31
34
|
|
32
|
-
sig {
|
33
|
-
|
34
|
-
super(document)
|
35
|
+
sig { override.returns(ResponseType) }
|
36
|
+
attr_reader :response
|
35
37
|
|
36
|
-
|
38
|
+
sig { void }
|
39
|
+
def initialize
|
40
|
+
@response = T.let(nil, ResponseType)
|
41
|
+
super()
|
37
42
|
end
|
38
43
|
|
39
|
-
|
40
|
-
|
41
|
-
|
44
|
+
listener_events do
|
45
|
+
sig { params(node: SyntaxTree::Command).void }
|
46
|
+
def on_command(node)
|
47
|
+
message = node.message
|
48
|
+
@response = generate_rails_document_link_hover(message.value, message)
|
49
|
+
end
|
42
50
|
|
43
|
-
|
44
|
-
|
51
|
+
sig { params(node: SyntaxTree::ConstPathRef).void }
|
52
|
+
def on_const_path_ref(node)
|
53
|
+
@response = generate_rails_document_link_hover(full_constant_name(node), node)
|
54
|
+
end
|
45
55
|
|
46
|
-
|
47
|
-
|
48
|
-
message =
|
49
|
-
generate_rails_document_link_hover(message.value, message)
|
50
|
-
when SyntaxTree::CallNode
|
51
|
-
message = target.message
|
56
|
+
sig { params(node: SyntaxTree::CallNode).void }
|
57
|
+
def on_call(node)
|
58
|
+
message = node.message
|
52
59
|
return if message.is_a?(Symbol)
|
53
60
|
|
54
|
-
generate_rails_document_link_hover(message.value, message)
|
55
|
-
when SyntaxTree::ConstPathRef
|
56
|
-
constant_name = full_constant_name(target)
|
57
|
-
generate_rails_document_link_hover(constant_name, target)
|
61
|
+
@response = generate_rails_document_link_hover(message.value, message)
|
58
62
|
end
|
59
63
|
end
|
60
64
|
|
61
65
|
private
|
62
66
|
|
63
|
-
sig
|
64
|
-
params(name: String, node: SyntaxTree::Node).returns(T.nilable(Interface::Hover))
|
65
|
-
end
|
67
|
+
sig { params(name: String, node: SyntaxTree::Node).returns(T.nilable(Interface::Hover)) }
|
66
68
|
def generate_rails_document_link_hover(name, node)
|
67
69
|
urls = Support::RailsDocumentClient.generate_rails_document_urls(name)
|
68
|
-
|
69
70
|
return if urls.empty?
|
70
71
|
|
71
|
-
contents = Interface::MarkupContent.new(
|
72
|
-
|
73
|
-
value: urls.join("\n\n"),
|
74
|
-
)
|
75
|
-
Interface::Hover.new(
|
76
|
-
range: range_from_syntax_tree_node(node),
|
77
|
-
contents: contents,
|
78
|
-
)
|
72
|
+
contents = Interface::MarkupContent.new(kind: "markdown", value: urls.join("\n\n"))
|
73
|
+
Interface::Hover.new(range: range_from_syntax_tree_node(node), contents: contents)
|
79
74
|
end
|
80
75
|
end
|
81
76
|
end
|
@@ -29,8 +29,7 @@ module RubyLsp
|
|
29
29
|
# We can't verify if we're inside a require when there are syntax errors
|
30
30
|
return [] if @document.syntax_error?
|
31
31
|
|
32
|
-
|
33
|
-
target = T.let(find(char_position), T.nilable(SyntaxTree::TStringContent))
|
32
|
+
target = T.let(find, T.nilable(SyntaxTree::TStringContent))
|
34
33
|
# no target means the we are not inside a `require` call
|
35
34
|
return [] unless target
|
36
35
|
|
@@ -51,11 +50,12 @@ module RubyLsp
|
|
51
50
|
end
|
52
51
|
end
|
53
52
|
|
54
|
-
sig {
|
55
|
-
def find
|
56
|
-
|
53
|
+
sig { returns(T.nilable(SyntaxTree::TStringContent)) }
|
54
|
+
def find
|
55
|
+
char_position = @document.create_scanner.find_char_position(@position)
|
56
|
+
matched, parent = @document.locate(
|
57
57
|
T.must(@document.tree),
|
58
|
-
|
58
|
+
char_position,
|
59
59
|
node_types: [SyntaxTree::Command, SyntaxTree::CommandCall, SyntaxTree::CallNode],
|
60
60
|
)
|
61
61
|
|
@@ -76,7 +76,7 @@ module RubyLsp
|
|
76
76
|
|
77
77
|
path_node = argument.parts.first
|
78
78
|
return unless path_node.is_a?(SyntaxTree::TStringContent)
|
79
|
-
return unless (path_node.location.start_char..path_node.location.end_char).cover?(
|
79
|
+
return unless (path_node.location.start_char..path_node.location.end_char).cover?(char_position)
|
80
80
|
|
81
81
|
path_node
|
82
82
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Requests
|
6
|
+
module Support
|
7
|
+
module Common
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { params(node: SyntaxTree::Node).returns(Interface::Range) }
|
11
|
+
def range_from_syntax_tree_node(node)
|
12
|
+
loc = node.location
|
13
|
+
|
14
|
+
Interface::Range.new(
|
15
|
+
start: Interface::Position.new(
|
16
|
+
line: loc.start_line - 1,
|
17
|
+
character: loc.start_column,
|
18
|
+
),
|
19
|
+
end: Interface::Position.new(line: loc.end_line - 1, character: loc.end_column),
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
sig do
|
24
|
+
params(node: T.any(SyntaxTree::ConstPathRef, SyntaxTree::ConstRef, SyntaxTree::TopConstRef)).returns(String)
|
25
|
+
end
|
26
|
+
def full_constant_name(node)
|
27
|
+
name = +node.constant.value
|
28
|
+
constant = T.let(node, SyntaxTree::Node)
|
29
|
+
|
30
|
+
while constant.is_a?(SyntaxTree::ConstPathRef)
|
31
|
+
constant = constant.parent
|
32
|
+
|
33
|
+
case constant
|
34
|
+
when SyntaxTree::ConstPathRef
|
35
|
+
name.prepend("#{constant.constant.value}::")
|
36
|
+
when SyntaxTree::VarRef
|
37
|
+
name.prepend("#{constant.value.value}::")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
name
|
42
|
+
end
|
43
|
+
|
44
|
+
sig { params(node: T.nilable(SyntaxTree::Node), range: T.nilable(T::Range[Integer])).returns(T::Boolean) }
|
45
|
+
def visible?(node, range)
|
46
|
+
return true if range.nil?
|
47
|
+
return false if node.nil?
|
48
|
+
|
49
|
+
loc = node.location
|
50
|
+
range.cover?(loc.start_line - 1) && range.cover?(loc.end_line - 1)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/ruby_lsp/requests.rb
CHANGED
@@ -18,6 +18,7 @@ module RubyLsp
|
|
18
18
|
# - {RubyLsp::Requests::DocumentHighlight}
|
19
19
|
# - {RubyLsp::Requests::InlayHints}
|
20
20
|
# - {RubyLsp::Requests::PathCompletion}
|
21
|
+
# - {RubyLsp::Requests::CodeLens}
|
21
22
|
|
22
23
|
module Requests
|
23
24
|
autoload :BaseRequest, "ruby_lsp/requests/base_request"
|
@@ -35,6 +36,7 @@ module RubyLsp
|
|
35
36
|
autoload :DocumentHighlight, "ruby_lsp/requests/document_highlight"
|
36
37
|
autoload :InlayHints, "ruby_lsp/requests/inlay_hints"
|
37
38
|
autoload :PathCompletion, "ruby_lsp/requests/path_completion"
|
39
|
+
autoload :CodeLens, "ruby_lsp/requests/code_lens"
|
38
40
|
|
39
41
|
# :nodoc:
|
40
42
|
module Support
|
@@ -46,6 +48,7 @@ module RubyLsp
|
|
46
48
|
autoload :HighlightTarget, "ruby_lsp/requests/support/highlight_target"
|
47
49
|
autoload :RailsDocumentClient, "ruby_lsp/requests/support/rails_document_client"
|
48
50
|
autoload :PrefixTree, "ruby_lsp/requests/support/prefix_tree"
|
51
|
+
autoload :Common, "ruby_lsp/requests/support/common"
|
49
52
|
end
|
50
53
|
end
|
51
54
|
end
|
data/lib/ruby_lsp/store.rb
CHANGED
@@ -9,8 +9,8 @@ module RubyLsp
|
|
9
9
|
class Store
|
10
10
|
extend T::Sig
|
11
11
|
|
12
|
-
sig {
|
13
|
-
|
12
|
+
sig { returns(String) }
|
13
|
+
attr_accessor :encoding
|
14
14
|
|
15
15
|
sig { returns(String) }
|
16
16
|
attr_accessor :formatter
|
@@ -18,7 +18,7 @@ module RubyLsp
|
|
18
18
|
sig { void }
|
19
19
|
def initialize
|
20
20
|
@state = T.let({}, T::Hash[String, Document])
|
21
|
-
@encoding = T.let(
|
21
|
+
@encoding = T.let(Constant::PositionEncodingKind::UTF8, String)
|
22
22
|
@formatter = T.let("auto", String)
|
23
23
|
end
|
24
24
|
|
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.4.
|
4
|
+
version: 0.4.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-04-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: language_server-protocol
|
@@ -73,12 +73,15 @@ files:
|
|
73
73
|
- lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
|
74
74
|
- lib/ruby-lsp.rb
|
75
75
|
- lib/ruby_lsp/document.rb
|
76
|
+
- lib/ruby_lsp/event_emitter.rb
|
76
77
|
- lib/ruby_lsp/executor.rb
|
77
78
|
- lib/ruby_lsp/internal.rb
|
79
|
+
- lib/ruby_lsp/listener.rb
|
78
80
|
- lib/ruby_lsp/requests.rb
|
79
81
|
- lib/ruby_lsp/requests/base_request.rb
|
80
82
|
- lib/ruby_lsp/requests/code_action_resolve.rb
|
81
83
|
- lib/ruby_lsp/requests/code_actions.rb
|
84
|
+
- lib/ruby_lsp/requests/code_lens.rb
|
82
85
|
- lib/ruby_lsp/requests/diagnostics.rb
|
83
86
|
- lib/ruby_lsp/requests/document_highlight.rb
|
84
87
|
- lib/ruby_lsp/requests/document_link.rb
|
@@ -92,6 +95,7 @@ files:
|
|
92
95
|
- lib/ruby_lsp/requests/selection_ranges.rb
|
93
96
|
- lib/ruby_lsp/requests/semantic_highlighting.rb
|
94
97
|
- lib/ruby_lsp/requests/support/annotation.rb
|
98
|
+
- lib/ruby_lsp/requests/support/common.rb
|
95
99
|
- lib/ruby_lsp/requests/support/highlight_target.rb
|
96
100
|
- lib/ruby_lsp/requests/support/prefix_tree.rb
|
97
101
|
- lib/ruby_lsp/requests/support/rails_document_client.rb
|
@@ -127,7 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
131
|
- !ruby/object:Gem::Version
|
128
132
|
version: '0'
|
129
133
|
requirements: []
|
130
|
-
rubygems_version: 3.4.
|
134
|
+
rubygems_version: 3.4.10
|
131
135
|
signing_key:
|
132
136
|
specification_version: 4
|
133
137
|
summary: An opinionated language server for Ruby
|