ruby-lsp 0.6.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +5 -1
- data/VERSION +1 -1
- data/exe/ruby-lsp +13 -0
- data/exe/ruby-lsp-check +62 -0
- data/lib/ruby_lsp/document.rb +21 -7
- data/lib/ruby_lsp/executor.rb +27 -11
- data/lib/ruby_lsp/requests/code_lens.rb +18 -0
- data/lib/ruby_lsp/requests/definition.rb +95 -0
- data/lib/ruby_lsp/requests/hover.rb +17 -0
- data/lib/ruby_lsp/requests/on_type_formatting.rb +29 -4
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +33 -0
- data/lib/ruby_lsp/requests.rb +4 -0
- data/lib/ruby_lsp/server.rb +2 -0
- data/lib/ruby_lsp/setup_bundler.rb +86 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 812bcf5c0bc5512ad4382440118051b99d79e79d37a2e91e1fbe4341458dd94c
|
4
|
+
data.tar.gz: 73a83f9e143bde544d72819e0c867182a0bc8b82ff2ef2dc98032aecdc28e825
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 03b1252a63bc78186983a8962b91f2e89efa679e2ee3b4978d655a83e9a6cbdeb6a8171647e416dd4ebf43ae82fc36e04b805444acbc2a7eade386166873026e
|
7
|
+
data.tar.gz: 443e7b58bd600a27827043915897c1d848fe8f07c00d348677a9322dcff005bdb4427d2ceade5347f8256fbb6a68897f85215bdd563110d02b0a1887abb71224
|
data/README.md
CHANGED
@@ -27,7 +27,11 @@ The gem can be installed by doing
|
|
27
27
|
gem install ruby-lsp
|
28
28
|
```
|
29
29
|
|
30
|
-
|
30
|
+
**NOTE**: starting with v0.7.0, it is no longer recommended to add the `ruby-lsp` to the bundle. The gem will generate a
|
31
|
+
custom bundle in `.ruby-lsp/Gemfile` which is used to identify the versions of dependencies that should be used for the
|
32
|
+
application (e.g.: the correct RuboCop version).
|
33
|
+
|
34
|
+
For older versions, if you decide to add the gem to the bundle, it is not necessary to require it.
|
31
35
|
```ruby
|
32
36
|
group :development do
|
33
37
|
gem "ruby-lsp", require: false
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.7.0
|
data/exe/ruby-lsp
CHANGED
@@ -1,6 +1,19 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
# When we're running without bundler, then we need to make sure the custom bundle is fully configured and re-execute
|
5
|
+
# using `BUNDLE_GEMFILE=.ruby-lsp/Gemfile bundle exec ruby-lsp` so that we have access to the gems that are a part of
|
6
|
+
# the application's bundle
|
7
|
+
if ENV["BUNDLE_GEMFILE"].nil? && File.exist?("Gemfile.lock")
|
8
|
+
require_relative "../lib/ruby_lsp/setup_bundler"
|
9
|
+
|
10
|
+
# In some cases, like when the `ruby-lsp` is already a part of the bundle, we don't generate `.ruby-lsp/Gemfile`.
|
11
|
+
# However, we still want to run the server with `bundle exec`. We need to make sure we're pointing to the right
|
12
|
+
# `Gemfile`
|
13
|
+
bundle_gemfile = File.exist?(".ruby-lsp/Gemfile") ? ".ruby-lsp/Gemfile" : "Gemfile"
|
14
|
+
exit exec("BUNDLE_GEMFILE=#{bundle_gemfile} bundle exec ruby-lsp #{ARGV.join(" ")}")
|
15
|
+
end
|
16
|
+
|
4
17
|
require "sorbet-runtime"
|
5
18
|
|
6
19
|
begin
|
data/exe/ruby-lsp-check
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# This executable checks if all automatic LSP requests run successfully on every Ruby file under the current directory
|
5
|
+
|
6
|
+
require "sorbet-runtime"
|
7
|
+
|
8
|
+
begin
|
9
|
+
T::Configuration.default_checked_level = :never
|
10
|
+
T::Configuration.call_validation_error_handler = ->(*) {}
|
11
|
+
T::Configuration.inline_type_error_handler = ->(*) {}
|
12
|
+
T::Configuration.sig_validation_error_handler = ->(*) {}
|
13
|
+
rescue
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
|
17
|
+
require_relative "../lib/ruby_lsp/internal"
|
18
|
+
|
19
|
+
RubyLsp::Extension.load_extensions
|
20
|
+
|
21
|
+
T::Utils.run_all_sig_blocks
|
22
|
+
|
23
|
+
files = Dir.glob("#{Dir.pwd}/**/*.rb")
|
24
|
+
|
25
|
+
puts "Verifying that all automatic LSP requests execute successfully. This may take a while..."
|
26
|
+
|
27
|
+
errors = {}
|
28
|
+
store = RubyLsp::Store.new
|
29
|
+
message_queue = Thread::Queue.new
|
30
|
+
executor = RubyLsp::Executor.new(store, message_queue)
|
31
|
+
|
32
|
+
files.each_with_index do |file, index|
|
33
|
+
uri = "file://#{file}"
|
34
|
+
store.set(uri: uri, source: File.read(file), version: 1)
|
35
|
+
|
36
|
+
# Executing any of the automatic requests will execute all of them, so here we just pick one
|
37
|
+
result = executor.execute({
|
38
|
+
method: "textDocument/documentSymbol",
|
39
|
+
params: { textDocument: { uri: uri } },
|
40
|
+
})
|
41
|
+
|
42
|
+
error = result.error
|
43
|
+
errors[file] = error if error
|
44
|
+
ensure
|
45
|
+
store.delete(uri)
|
46
|
+
print("\033[M\033[0KCompleted #{index + 1}/#{files.length}") unless ENV["CI"]
|
47
|
+
end
|
48
|
+
|
49
|
+
puts "\n"
|
50
|
+
message_queue.close
|
51
|
+
|
52
|
+
if errors.empty?
|
53
|
+
puts "All automatic LSP requests executed successfully"
|
54
|
+
exit
|
55
|
+
end
|
56
|
+
|
57
|
+
puts <<~ERRORS
|
58
|
+
Errors while executing requests:
|
59
|
+
|
60
|
+
#{errors.map { |file, error| "#{file}: #{error.message}" }.join("\n")}
|
61
|
+
ERRORS
|
62
|
+
exit!
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -114,12 +114,12 @@ module RubyLsp
|
|
114
114
|
params(
|
115
115
|
position: PositionShape,
|
116
116
|
node_types: T::Array[T.class_of(SyntaxTree::Node)],
|
117
|
-
).returns([T.nilable(SyntaxTree::Node), T.nilable(SyntaxTree::Node)])
|
117
|
+
).returns([T.nilable(SyntaxTree::Node), T.nilable(SyntaxTree::Node), T::Array[String]])
|
118
118
|
end
|
119
119
|
def locate_node(position, node_types: [])
|
120
|
-
return [nil, nil] unless parsed?
|
120
|
+
return [nil, nil, []] unless parsed?
|
121
121
|
|
122
|
-
locate(T.must(@tree), create_scanner.find_char_position(position))
|
122
|
+
locate(T.must(@tree), create_scanner.find_char_position(position), node_types: node_types)
|
123
123
|
end
|
124
124
|
|
125
125
|
sig do
|
@@ -127,12 +127,13 @@ module RubyLsp
|
|
127
127
|
node: SyntaxTree::Node,
|
128
128
|
char_position: Integer,
|
129
129
|
node_types: T::Array[T.class_of(SyntaxTree::Node)],
|
130
|
-
).returns([T.nilable(SyntaxTree::Node), T.nilable(SyntaxTree::Node)])
|
130
|
+
).returns([T.nilable(SyntaxTree::Node), T.nilable(SyntaxTree::Node), T::Array[String]])
|
131
131
|
end
|
132
132
|
def locate(node, char_position, node_types: [])
|
133
133
|
queue = T.let(node.child_nodes.compact, T::Array[T.nilable(SyntaxTree::Node)])
|
134
134
|
closest = node
|
135
135
|
parent = T.let(nil, T.nilable(SyntaxTree::Node))
|
136
|
+
nesting = T.let([], T::Array[T.any(SyntaxTree::ClassDeclaration, SyntaxTree::ModuleDeclaration)])
|
136
137
|
|
137
138
|
until queue.empty?
|
138
139
|
candidate = queue.shift
|
@@ -140,8 +141,10 @@ module RubyLsp
|
|
140
141
|
# Skip nil child nodes
|
141
142
|
next if candidate.nil?
|
142
143
|
|
143
|
-
# Add the next child_nodes to the queue to be processed
|
144
|
-
|
144
|
+
# Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
|
145
|
+
# same order as the visiting mechanism, which means searching the child nodes before moving on to the next
|
146
|
+
# sibling
|
147
|
+
queue.unshift(*candidate.child_nodes)
|
145
148
|
|
146
149
|
# Skip if the current node doesn't cover the desired position
|
147
150
|
loc = candidate.location
|
@@ -151,6 +154,17 @@ module RubyLsp
|
|
151
154
|
# already
|
152
155
|
break if char_position < loc.start_char
|
153
156
|
|
157
|
+
# If the candidate starts after the end of the previous nesting level, then we've exited that nesting level and
|
158
|
+
# need to pop the stack
|
159
|
+
previous_level = nesting.last
|
160
|
+
nesting.pop if previous_level && candidate.start_char > previous_level.end_char
|
161
|
+
|
162
|
+
# Keep track of the nesting where we found the target. This is used to determine the fully qualified name of the
|
163
|
+
# target when it is a constant
|
164
|
+
if candidate.is_a?(SyntaxTree::ClassDeclaration) || candidate.is_a?(SyntaxTree::ModuleDeclaration)
|
165
|
+
nesting << candidate
|
166
|
+
end
|
167
|
+
|
154
168
|
# If there are node types to filter by, and the current node is not one of those types, then skip it
|
155
169
|
next if node_types.any? && node_types.none? { |type| candidate.class == type }
|
156
170
|
|
@@ -162,7 +176,7 @@ module RubyLsp
|
|
162
176
|
end
|
163
177
|
end
|
164
178
|
|
165
|
-
[closest, parent]
|
179
|
+
[closest, parent, nesting.map { |n| n.constant.constant.value }]
|
166
180
|
end
|
167
181
|
|
168
182
|
class Scanner
|
data/lib/ruby_lsp/executor.rb
CHANGED
@@ -97,13 +97,11 @@ module RubyLsp
|
|
97
97
|
document_symbol = Requests::DocumentSymbol.new(emitter, @message_queue)
|
98
98
|
document_link = Requests::DocumentLink.new(uri, emitter, @message_queue)
|
99
99
|
code_lens = Requests::CodeLens.new(uri, emitter, @message_queue, @test_library)
|
100
|
-
|
101
|
-
T.unsafe(l).new(document.uri, emitter, @message_queue)
|
102
|
-
end
|
100
|
+
|
103
101
|
semantic_highlighting = Requests::SemanticHighlighting.new(emitter, @message_queue)
|
104
102
|
emitter.visit(document.tree) if document.parsed?
|
105
103
|
|
106
|
-
|
104
|
+
code_lens.merge_external_listeners_responses!
|
107
105
|
|
108
106
|
# Store all responses retrieve in this round of visits in the cache and then return the response for the request
|
109
107
|
# we actually received
|
@@ -169,9 +167,26 @@ module RubyLsp
|
|
169
167
|
end
|
170
168
|
when "textDocument/completion"
|
171
169
|
completion(uri, request.dig(:params, :position))
|
170
|
+
when "textDocument/definition"
|
171
|
+
definition(uri, request.dig(:params, :position))
|
172
|
+
when "rubyLsp/textDocument/showSyntaxTree"
|
173
|
+
{ ast: Requests::ShowSyntaxTree.new(@store.get(uri)).run }
|
172
174
|
end
|
173
175
|
end
|
174
176
|
|
177
|
+
sig { params(uri: String, position: Document::PositionShape).returns(T.nilable(Interface::Location)) }
|
178
|
+
def definition(uri, position)
|
179
|
+
document = @store.get(uri)
|
180
|
+
return if document.syntax_error?
|
181
|
+
|
182
|
+
target, _parent = document.locate_node(position, node_types: [SyntaxTree::Command])
|
183
|
+
|
184
|
+
emitter = EventEmitter.new
|
185
|
+
base_listener = Requests::Definition.new(uri, emitter, @message_queue)
|
186
|
+
emitter.emit_for_target(target)
|
187
|
+
base_listener.response
|
188
|
+
end
|
189
|
+
|
175
190
|
sig { params(uri: String).returns(T::Array[Interface::FoldingRange]) }
|
176
191
|
def folding_range(uri)
|
177
192
|
@store.cache_fetch(uri, "textDocument/foldingRange") do |document|
|
@@ -198,15 +213,13 @@ module RubyLsp
|
|
198
213
|
|
199
214
|
# Instantiate all listeners
|
200
215
|
emitter = EventEmitter.new
|
201
|
-
|
202
|
-
listeners = Requests::Hover.listeners.map { |l| l.new(emitter, @message_queue) }
|
216
|
+
hover = Requests::Hover.new(emitter, @message_queue)
|
203
217
|
|
204
218
|
# Emit events for all listeners
|
205
219
|
emitter.emit_for_target(target)
|
206
220
|
|
207
|
-
|
208
|
-
|
209
|
-
base_listener.response
|
221
|
+
hover.merge_external_listeners_responses!
|
222
|
+
hover.response
|
210
223
|
end
|
211
224
|
|
212
225
|
sig { params(uri: String, content_changes: T::Array[Document::EditShape], version: Integer).returns(Object) }
|
@@ -436,7 +449,9 @@ module RubyLsp
|
|
436
449
|
end
|
437
450
|
|
438
451
|
configured_features = options.dig(:initializationOptions, :enabledFeatures)
|
439
|
-
|
452
|
+
|
453
|
+
# Uncomment the line below and use the variable to gate features behind the experimental flag
|
454
|
+
# experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled)
|
440
455
|
|
441
456
|
enabled_features = case configured_features
|
442
457
|
when Array
|
@@ -465,7 +480,7 @@ module RubyLsp
|
|
465
480
|
Interface::DocumentLinkOptions.new(resolve_provider: false)
|
466
481
|
end
|
467
482
|
|
468
|
-
code_lens_provider = if
|
483
|
+
code_lens_provider = if enabled_features["codeLens"]
|
469
484
|
Interface::CodeLensOptions.new(resolve_provider: false)
|
470
485
|
end
|
471
486
|
|
@@ -539,6 +554,7 @@ module RubyLsp
|
|
539
554
|
inlay_hint_provider: inlay_hint_provider,
|
540
555
|
completion_provider: completion_provider,
|
541
556
|
code_lens_provider: code_lens_provider,
|
557
|
+
definition_provider: enabled_features["definition"],
|
542
558
|
),
|
543
559
|
)
|
544
560
|
end
|
@@ -38,6 +38,8 @@ module RubyLsp
|
|
38
38
|
def initialize(uri, emitter, message_queue, test_library)
|
39
39
|
super(emitter, message_queue)
|
40
40
|
|
41
|
+
@uri = T.let(uri, String)
|
42
|
+
@external_listeners = T.let([], T::Array[RubyLsp::Listener[ResponseType]])
|
41
43
|
@test_library = T.let(test_library, String)
|
42
44
|
@response = T.let([], ResponseType)
|
43
45
|
@path = T.let(T.must(URI(uri).path), String)
|
@@ -56,6 +58,22 @@ module RubyLsp
|
|
56
58
|
:after_call,
|
57
59
|
:on_vcall,
|
58
60
|
)
|
61
|
+
|
62
|
+
register_external_listeners!
|
63
|
+
end
|
64
|
+
|
65
|
+
sig { void }
|
66
|
+
def register_external_listeners!
|
67
|
+
self.class.listeners.each do |l|
|
68
|
+
@external_listeners << T.unsafe(l).new(@uri, @emitter, @message_queue)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
sig { void }
|
73
|
+
def merge_external_listeners_responses!
|
74
|
+
@external_listeners.each do |l|
|
75
|
+
merge_response!(l)
|
76
|
+
end
|
59
77
|
end
|
60
78
|
|
61
79
|
sig { params(node: SyntaxTree::ClassDeclaration).void }
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Requests
|
6
|
+
# ![Definition demo](../../definition.gif)
|
7
|
+
#
|
8
|
+
# The [definition
|
9
|
+
# request](https://microsoft.github.io/language-server-protocol/specification#textDocument_definition) jumps to the
|
10
|
+
# definition of the symbol under the cursor.
|
11
|
+
#
|
12
|
+
# Currently, only jumping to required files is supported.
|
13
|
+
#
|
14
|
+
# # Example
|
15
|
+
#
|
16
|
+
# ```ruby
|
17
|
+
# require "some_gem/file" # <- Request go to definition on this string will take you to the file
|
18
|
+
# ```
|
19
|
+
class Definition < Listener
|
20
|
+
extend T::Sig
|
21
|
+
extend T::Generic
|
22
|
+
|
23
|
+
ResponseType = type_member { { fixed: T.nilable(Interface::Location) } }
|
24
|
+
|
25
|
+
sig { override.returns(ResponseType) }
|
26
|
+
attr_reader :response
|
27
|
+
|
28
|
+
sig { params(uri: String, emitter: EventEmitter, message_queue: Thread::Queue).void }
|
29
|
+
def initialize(uri, emitter, message_queue)
|
30
|
+
super(emitter, message_queue)
|
31
|
+
|
32
|
+
@uri = uri
|
33
|
+
@response = T.let(nil, ResponseType)
|
34
|
+
emitter.register(self, :on_command)
|
35
|
+
end
|
36
|
+
|
37
|
+
sig { params(node: SyntaxTree::Command).void }
|
38
|
+
def on_command(node)
|
39
|
+
message = node.message.value
|
40
|
+
return unless message == "require" || message == "require_relative"
|
41
|
+
|
42
|
+
argument = node.arguments.parts.first
|
43
|
+
return unless argument.is_a?(SyntaxTree::StringLiteral)
|
44
|
+
|
45
|
+
string = argument.parts.first
|
46
|
+
return unless string.is_a?(SyntaxTree::TStringContent)
|
47
|
+
|
48
|
+
required_file = "#{string.value}.rb"
|
49
|
+
|
50
|
+
case message
|
51
|
+
when "require"
|
52
|
+
candidate = find_file_in_load_path(required_file)
|
53
|
+
|
54
|
+
if candidate
|
55
|
+
@response = Interface::Location.new(
|
56
|
+
uri: "file://#{candidate}",
|
57
|
+
range: Interface::Range.new(
|
58
|
+
start: Interface::Position.new(line: 0, character: 0),
|
59
|
+
end: Interface::Position.new(line: 0, character: 0),
|
60
|
+
),
|
61
|
+
)
|
62
|
+
end
|
63
|
+
when "require_relative"
|
64
|
+
current_file = T.must(URI.parse(@uri).path)
|
65
|
+
current_folder = Pathname.new(current_file).dirname
|
66
|
+
candidate = File.expand_path(File.join(current_folder, required_file))
|
67
|
+
|
68
|
+
if candidate
|
69
|
+
@response = Interface::Location.new(
|
70
|
+
uri: "file://#{candidate}",
|
71
|
+
range: Interface::Range.new(
|
72
|
+
start: Interface::Position.new(line: 0, character: 0),
|
73
|
+
end: Interface::Position.new(line: 0, character: 0),
|
74
|
+
),
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
sig { params(file: String).returns(T.nilable(String)) }
|
83
|
+
def find_file_in_load_path(file)
|
84
|
+
return unless file.include?("/")
|
85
|
+
|
86
|
+
$LOAD_PATH.each do |p|
|
87
|
+
found = Dir.glob("**/#{file}", base: p).first
|
88
|
+
return "#{p}/#{found}" if found
|
89
|
+
end
|
90
|
+
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -39,8 +39,25 @@ module RubyLsp
|
|
39
39
|
def initialize(emitter, message_queue)
|
40
40
|
super
|
41
41
|
|
42
|
+
@external_listeners = T.let([], T::Array[RubyLsp::Listener[ResponseType]])
|
42
43
|
@response = T.let(nil, ResponseType)
|
43
44
|
emitter.register(self, :on_command, :on_const_path_ref, :on_call)
|
45
|
+
|
46
|
+
register_external_listeners!
|
47
|
+
end
|
48
|
+
|
49
|
+
sig { void }
|
50
|
+
def register_external_listeners!
|
51
|
+
self.class.listeners.each do |l|
|
52
|
+
@external_listeners << T.unsafe(l).new(@emitter, @message_queue)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
sig { void }
|
57
|
+
def merge_external_listeners_responses!
|
58
|
+
@external_listeners.each do |l|
|
59
|
+
merge_response!(l)
|
60
|
+
end
|
44
61
|
end
|
45
62
|
|
46
63
|
# Merges responses from other hover listeners
|
@@ -62,9 +62,34 @@ module RubyLsp
|
|
62
62
|
|
63
63
|
sig { void }
|
64
64
|
def handle_pipe
|
65
|
-
|
65
|
+
current_line = @lines[@position[:line]]
|
66
|
+
return unless /((?<=do)|(?<={))\s+\|/.match?(current_line)
|
67
|
+
|
68
|
+
line = T.must(current_line)
|
69
|
+
|
70
|
+
# If the current character is a pipe and both previous ones are pipes too, then we autocompleted a pipe and the
|
71
|
+
# user inserted a third one. In this case, we need to avoid adding a fourth and remove the previous one
|
72
|
+
if line[@position[:character] - 2] == "|" &&
|
73
|
+
line[@position[:character] - 1] == "|" &&
|
74
|
+
line[@position[:character]] == "|"
|
75
|
+
|
76
|
+
@edits << Interface::TextEdit.new(
|
77
|
+
range: Interface::Range.new(
|
78
|
+
start: Interface::Position.new(
|
79
|
+
line: @position[:line],
|
80
|
+
character: @position[:character],
|
81
|
+
),
|
82
|
+
end: Interface::Position.new(
|
83
|
+
line: @position[:line],
|
84
|
+
character: @position[:character] + 1,
|
85
|
+
),
|
86
|
+
),
|
87
|
+
new_text: "",
|
88
|
+
)
|
89
|
+
else
|
90
|
+
add_edit_with_text("|")
|
91
|
+
end
|
66
92
|
|
67
|
-
add_edit_with_text("|")
|
68
93
|
move_cursor_to(@position[:line], @position[:character])
|
69
94
|
end
|
70
95
|
|
@@ -88,10 +113,10 @@ module RubyLsp
|
|
88
113
|
current_line = @lines[@position[:line]]
|
89
114
|
next_line = @lines[@position[:line] + 1]
|
90
115
|
|
91
|
-
if current_line.nil? || current_line.
|
116
|
+
if current_line.nil? || current_line.strip.empty?
|
92
117
|
add_edit_with_text(" \n#{indents}end")
|
93
118
|
move_cursor_to(@position[:line], @indentation + 2)
|
94
|
-
elsif next_line.nil? || next_line.
|
119
|
+
elsif next_line.nil? || next_line.strip.empty?
|
95
120
|
add_edit_with_text("#{indents}end", { line: @position[:line] + 1, character: @position[:character] })
|
96
121
|
move_cursor_to(@position[:line], @indentation + 3)
|
97
122
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Requests
|
6
|
+
# ![Show syntax tree demo](../../show_syntax_tree.gif)
|
7
|
+
#
|
8
|
+
# Show syntax tree is a custom [LSP
|
9
|
+
# request](https://microsoft.github.io/language-server-protocol/specification#requestMessage) that displays the AST
|
10
|
+
# for the current document in a new tab.
|
11
|
+
#
|
12
|
+
# # Example
|
13
|
+
#
|
14
|
+
# ```ruby
|
15
|
+
# # Executing the Ruby LSP: Show syntax tree command will display the AST for the document
|
16
|
+
# 1 + 1
|
17
|
+
# # (program (statements ((binary (int "1") + (int "1")))))
|
18
|
+
# ```
|
19
|
+
#
|
20
|
+
class ShowSyntaxTree < BaseRequest
|
21
|
+
extend T::Sig
|
22
|
+
|
23
|
+
sig { override.returns(String) }
|
24
|
+
def run
|
25
|
+
return "Document contains syntax error" if @document.syntax_error?
|
26
|
+
|
27
|
+
output_string = +""
|
28
|
+
PP.pp(@document.tree, output_string)
|
29
|
+
output_string
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/ruby_lsp/requests.rb
CHANGED
@@ -19,6 +19,8 @@ module RubyLsp
|
|
19
19
|
# - [InlayHint](rdoc-ref:RubyLsp::Requests::InlayHints)
|
20
20
|
# - [PathCompletion](rdoc-ref:RubyLsp::Requests::PathCompletion)
|
21
21
|
# - [CodeLens](rdoc-ref:RubyLsp::Requests::CodeLens)
|
22
|
+
# - [Definition](rdoc-ref:RubyLsp::Requests::Definition)
|
23
|
+
# - [ShowSyntaxTree](rdoc-ref:RubyLsp::Requests::ShowSyntaxTree)
|
22
24
|
|
23
25
|
module Requests
|
24
26
|
autoload :BaseRequest, "ruby_lsp/requests/base_request"
|
@@ -37,6 +39,8 @@ module RubyLsp
|
|
37
39
|
autoload :InlayHints, "ruby_lsp/requests/inlay_hints"
|
38
40
|
autoload :PathCompletion, "ruby_lsp/requests/path_completion"
|
39
41
|
autoload :CodeLens, "ruby_lsp/requests/code_lens"
|
42
|
+
autoload :Definition, "ruby_lsp/requests/definition"
|
43
|
+
autoload :ShowSyntaxTree, "ruby_lsp/requests/show_syntax_tree"
|
40
44
|
|
41
45
|
# :nodoc:
|
42
46
|
module Support
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -0,0 +1,86 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler"
|
5
|
+
require "fileutils"
|
6
|
+
require "pathname"
|
7
|
+
|
8
|
+
# This file is a script that will configure a custom bundle for the Ruby LSP. The custom bundle allows developers to use
|
9
|
+
# the Ruby LSP without including the gem in their application's Gemfile while at the same time giving us access to the
|
10
|
+
# exact locked versions of dependencies.
|
11
|
+
|
12
|
+
# Do not setup a custom bundle if we're working on the Ruby LSP, since it's already included by default
|
13
|
+
if Pathname.new(Dir.pwd).basename == "ruby-lsp"
|
14
|
+
warn("Ruby LSP> Skipping custom bundle setup since we're working on the Ruby LSP itself")
|
15
|
+
return
|
16
|
+
end
|
17
|
+
|
18
|
+
# We need to parse the Gemfile.lock manually here. If we try to do `bundler/setup` to use something more convenient, we
|
19
|
+
# may end up with issues when the globally installed `ruby-lsp` version mismatches the one included in the `Gemfile`
|
20
|
+
dependencies = Bundler::LockfileParser.new(Bundler.read_file("Gemfile.lock")).dependencies
|
21
|
+
|
22
|
+
# When working on a gem, the `ruby-lsp` might be listed as a dependency in the gemspec. We need to make sure we check
|
23
|
+
# those as well or else we may get version mismatch errors
|
24
|
+
gemspec_path = Dir.glob("*.gemspec").first
|
25
|
+
if gemspec_path
|
26
|
+
gemspec_dependencies = Bundler.load_gemspec(gemspec_path).dependencies.to_h { |dep| [dep.name, dep] }
|
27
|
+
dependencies.merge!(gemspec_dependencies)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Do not setup a custom bundle if both `ruby-lsp` and `debug` are already in the Gemfile
|
31
|
+
if dependencies["ruby-lsp"] && dependencies["debug"]
|
32
|
+
warn("Ruby LSP> Skipping custom bundle setup since both `ruby-lsp` and `debug` are already in the Gemfile")
|
33
|
+
return
|
34
|
+
end
|
35
|
+
|
36
|
+
# Automatically create and ignore the .ruby-lsp folder for users
|
37
|
+
FileUtils.mkdir(".ruby-lsp") unless Dir.exist?(".ruby-lsp")
|
38
|
+
File.write(".ruby-lsp/.gitignore", "*") unless File.exist?(".ruby-lsp/.gitignore")
|
39
|
+
|
40
|
+
parts = [
|
41
|
+
"# This custom gemfile is automatically generated by the Ruby LSP.",
|
42
|
+
"# It should be automatically git ignored, but in any case: do not commit it to your repository.",
|
43
|
+
"",
|
44
|
+
"eval_gemfile(File.expand_path(\"../Gemfile\", __dir__))",
|
45
|
+
]
|
46
|
+
|
47
|
+
unless dependencies["ruby-lsp"]
|
48
|
+
parts << 'gem "ruby-lsp", require: false, group: :development, source: "https://rubygems.org"'
|
49
|
+
end
|
50
|
+
|
51
|
+
unless dependencies["debug"]
|
52
|
+
parts << 'gem "debug", require: false, group: :development, platforms: :mri, source: "https://rubygems.org"'
|
53
|
+
end
|
54
|
+
|
55
|
+
gemfile_content = parts.join("\n")
|
56
|
+
|
57
|
+
unless File.exist?(".ruby-lsp/Gemfile") && File.read(".ruby-lsp/Gemfile") == gemfile_content
|
58
|
+
File.write(".ruby-lsp/Gemfile", gemfile_content)
|
59
|
+
end
|
60
|
+
|
61
|
+
# If .ruby-lsp/Gemfile.lock already exists and the top level Gemfile.lock hasn't been modified since it was last
|
62
|
+
# updated, then we're ready to boot the server
|
63
|
+
if File.exist?(".ruby-lsp/Gemfile.lock") && File.stat(".ruby-lsp/Gemfile.lock").mtime > File.stat("Gemfile.lock").mtime
|
64
|
+
warn("Ruby LSP> Skipping custom bundle setup since .ruby-lsp/Gemfile.lock already exists and is up to date")
|
65
|
+
return
|
66
|
+
end
|
67
|
+
|
68
|
+
FileUtils.cp("Gemfile.lock", ".ruby-lsp/Gemfile.lock")
|
69
|
+
|
70
|
+
# If the user has a custom bundle path configured, we need to ensure that we will use the absolute and not relative
|
71
|
+
# version of it when running bundle install. This is necessary to avoid installing the gems under the `.ruby-lsp`
|
72
|
+
# folder, which is not the user's intention. For example, if path is configured as `vendor`, we want to install it in
|
73
|
+
# the top level `vendor` and not `.ruby-lsp/vendor`
|
74
|
+
path = Bundler.settings["path"]
|
75
|
+
|
76
|
+
command = +""
|
77
|
+
# Use the absolute `BUNDLE_PATH` to prevent accidentally creating unwanted folders under `.ruby-lsp`
|
78
|
+
command << "BUNDLE_PATH=#{File.expand_path(path, Dir.pwd)} " if path
|
79
|
+
# Install gems using the custom bundle
|
80
|
+
command << "BUNDLE_GEMFILE=.ruby-lsp/Gemfile bundle install "
|
81
|
+
# Redirect stdout to stderr to prevent going into an infinite loop. The extension might confuse stdout output with
|
82
|
+
# responses
|
83
|
+
command << "1>&2"
|
84
|
+
|
85
|
+
warn("Ruby LSP> Running bundle install for the custom bundle. This may take a while...")
|
86
|
+
system(command)
|
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
|
+
version: 0.7.0
|
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-07-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: language_server-protocol
|
@@ -63,6 +63,7 @@ email:
|
|
63
63
|
- ruby@shopify.com
|
64
64
|
executables:
|
65
65
|
- ruby-lsp
|
66
|
+
- ruby-lsp-check
|
66
67
|
extensions: []
|
67
68
|
extra_rdoc_files: []
|
68
69
|
files:
|
@@ -70,6 +71,7 @@ files:
|
|
70
71
|
- README.md
|
71
72
|
- VERSION
|
72
73
|
- exe/ruby-lsp
|
74
|
+
- exe/ruby-lsp-check
|
73
75
|
- lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
|
74
76
|
- lib/ruby-lsp.rb
|
75
77
|
- lib/ruby_lsp/check_docs.rb
|
@@ -84,6 +86,7 @@ files:
|
|
84
86
|
- lib/ruby_lsp/requests/code_action_resolve.rb
|
85
87
|
- lib/ruby_lsp/requests/code_actions.rb
|
86
88
|
- lib/ruby_lsp/requests/code_lens.rb
|
89
|
+
- lib/ruby_lsp/requests/definition.rb
|
87
90
|
- lib/ruby_lsp/requests/diagnostics.rb
|
88
91
|
- lib/ruby_lsp/requests/document_highlight.rb
|
89
92
|
- lib/ruby_lsp/requests/document_link.rb
|
@@ -96,6 +99,7 @@ files:
|
|
96
99
|
- lib/ruby_lsp/requests/path_completion.rb
|
97
100
|
- lib/ruby_lsp/requests/selection_ranges.rb
|
98
101
|
- lib/ruby_lsp/requests/semantic_highlighting.rb
|
102
|
+
- lib/ruby_lsp/requests/show_syntax_tree.rb
|
99
103
|
- lib/ruby_lsp/requests/support/annotation.rb
|
100
104
|
- lib/ruby_lsp/requests/support/common.rb
|
101
105
|
- lib/ruby_lsp/requests/support/dependency_detector.rb
|
@@ -113,6 +117,7 @@ files:
|
|
113
117
|
- lib/ruby_lsp/requests/support/source_uri.rb
|
114
118
|
- lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb
|
115
119
|
- lib/ruby_lsp/server.rb
|
120
|
+
- lib/ruby_lsp/setup_bundler.rb
|
116
121
|
- lib/ruby_lsp/store.rb
|
117
122
|
- lib/ruby_lsp/utils.rb
|
118
123
|
homepage: https://github.com/Shopify/ruby-lsp
|
@@ -135,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
135
140
|
- !ruby/object:Gem::Version
|
136
141
|
version: '0'
|
137
142
|
requirements: []
|
138
|
-
rubygems_version: 3.4.
|
143
|
+
rubygems_version: 3.4.16
|
139
144
|
signing_key:
|
140
145
|
specification_version: 4
|
141
146
|
summary: An opinionated language server for Ruby
|