ruby-lsp 0.6.0 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +9 -0
- data/VERSION +1 -1
- data/lib/ruby_lsp/check_docs.rb +23 -5
- data/lib/ruby_lsp/event_emitter.rb +6 -0
- data/lib/ruby_lsp/executor.rb +11 -4
- data/lib/ruby_lsp/requests/code_lens.rb +16 -4
- data/lib/ruby_lsp/requests/document_highlight.rb +32 -37
- data/lib/ruby_lsp/requests/inlay_hints.rb +1 -1
- data/lib/ruby_lsp/requests/on_type_formatting.rb +10 -27
- data/lib/ruby_lsp/requests/support/dependency_detector.rb +7 -3
- data/lib/ruby_lsp/requests/support/source_uri.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0da1b154c2c048cdea8d1ca6f87e77b895482cdbea155058550eff983fdbae1c
|
4
|
+
data.tar.gz: 7a67502b28e7e525acc6349fafe5de9e9945350d84af72d0a9afaa5d5b0803d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 77968226990bdcbab7026e962a3b7e1e3459f920939aa19a50331daa7b4a24b180eb46a342b5385412a43b73d1d6e40d6f9de2f1321a512cf65d36046b978cbc
|
7
|
+
data.tar.gz: a27e182f7c554d9b231f4ea69b15b52d0c4c32fe0cbb17879480c8b9ada87288c59ffa93fbfd99963c456bf4a6298164c920f720611532609f5e985e3c1e890c
|
data/README.md
CHANGED
@@ -42,6 +42,15 @@ See the [documentation](https://shopify.github.io/ruby-lsp) for more in-depth de
|
|
42
42
|
For creating rich themes for Ruby using the semantic highlighting information, see the [semantic highlighting
|
43
43
|
documentation](SEMANTIC_HIGHLIGHTING.md).
|
44
44
|
|
45
|
+
### Extensions
|
46
|
+
|
47
|
+
The Ruby LSP provides a server extension system that allows other gems to enhance the base functionality with more
|
48
|
+
editor features. This is the mechanism that powers extensions like
|
49
|
+
|
50
|
+
- [Ruby LSP Rails](https://github.com/Shopify/ruby-lsp-rails)
|
51
|
+
|
52
|
+
For instructions on how to create extensions, see the [server extensions documentation](SERVER_EXTENSIONS.md).
|
53
|
+
|
45
54
|
## Learn More
|
46
55
|
|
47
56
|
* [RubyConf 2022: Improving the development experience with language servers](https://www.youtube.com/watch?v=kEfXPTm1aCI) ([Vinicius Stock](https://github.com/vinistock))
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.6.
|
1
|
+
0.6.2
|
data/lib/ruby_lsp/check_docs.rb
CHANGED
@@ -6,23 +6,26 @@ require "objspace"
|
|
6
6
|
|
7
7
|
module RubyLsp
|
8
8
|
# This rake task checks that all requests or extensions are fully documented. Add the rake task to your Rakefile and
|
9
|
-
# specify the absolute path for all files that must be required in order to discover all listeners
|
9
|
+
# specify the absolute path for all files that must be required in order to discover all listeners and their
|
10
|
+
# related GIFs
|
10
11
|
#
|
11
12
|
# # Rakefile
|
12
13
|
# request_files = FileList.new("#{__dir__}/lib/ruby_lsp/requests/*.rb") do |fl|
|
13
14
|
# fl.exclude(/base_request\.rb/)
|
14
15
|
# end
|
15
|
-
#
|
16
|
+
# gif_files = FileList.new("#{__dir__}/**/*.gif")
|
17
|
+
# RubyLsp::CheckDocs.new(request_files, gif_files)
|
16
18
|
# # Run with bundle exec rake ruby_lsp:check_docs
|
17
19
|
class CheckDocs < Rake::TaskLib
|
18
20
|
extend T::Sig
|
19
21
|
|
20
|
-
sig { params(require_files: Rake::FileList).void }
|
21
|
-
def initialize(require_files)
|
22
|
+
sig { params(require_files: Rake::FileList, gif_files: Rake::FileList).void }
|
23
|
+
def initialize(require_files, gif_files)
|
22
24
|
super()
|
23
25
|
|
24
26
|
@name = T.let("ruby_lsp:check_docs", String)
|
25
27
|
@file_list = require_files
|
28
|
+
@gif_list = gif_files
|
26
29
|
define_task
|
27
30
|
end
|
28
31
|
|
@@ -34,6 +37,13 @@ module RubyLsp
|
|
34
37
|
task(@name) { run_task }
|
35
38
|
end
|
36
39
|
|
40
|
+
sig { params(request_path: String).returns(T::Boolean) }
|
41
|
+
def gif_exists?(request_path)
|
42
|
+
request_gif = request_path.gsub(".rb", ".gif").split("/").last
|
43
|
+
|
44
|
+
@gif_list.any? { |gif_path| gif_path.end_with?(request_gif) }
|
45
|
+
end
|
46
|
+
|
37
47
|
sig { void }
|
38
48
|
def run_task
|
39
49
|
# Require all files configured to make sure all listeners are loaded
|
@@ -42,7 +52,7 @@ module RubyLsp
|
|
42
52
|
# Find all classes that inherit from BaseRequest or Listener, which are the ones we want to make sure are
|
43
53
|
# documented
|
44
54
|
features = ObjectSpace.each_object(Class).filter_map do |k|
|
45
|
-
klass = T.
|
55
|
+
klass = T.unsafe(k)
|
46
56
|
klass if klass < RubyLsp::Requests::BaseRequest || klass < RubyLsp::Listener
|
47
57
|
end
|
48
58
|
|
@@ -93,6 +103,14 @@ module RubyLsp
|
|
93
103
|
|
94
104
|
# [Inlay hint demo](../../inlay_hint.gif)
|
95
105
|
DOCS
|
106
|
+
elsif !gif_exists?(file_path)
|
107
|
+
T.must(missing_docs[class_name]) << <<~DOCS
|
108
|
+
The GIF for the request documentation does not exist. Make sure to add it,
|
109
|
+
with the same naming as the request. For example:
|
110
|
+
|
111
|
+
# lib/ruby_lsp/requests/code_lens.rb
|
112
|
+
# foo/bar/code_lens.gif
|
113
|
+
DOCS
|
96
114
|
end
|
97
115
|
end
|
98
116
|
|
@@ -53,6 +53,12 @@ module RubyLsp
|
|
53
53
|
|
54
54
|
# Visit dispatchers are below. Notice that for nodes that create a new scope (e.g.: classes, modules, method defs)
|
55
55
|
# we need both an `on_*` and `after_*` event. This is because some requests must know when we exit the scope
|
56
|
+
sig { override.params(node: T.nilable(SyntaxTree::Node)).void }
|
57
|
+
def visit(node)
|
58
|
+
@listeners[:on_node]&.each { |l| T.unsafe(l).on_node(node) }
|
59
|
+
super
|
60
|
+
end
|
61
|
+
|
56
62
|
sig { override.params(node: SyntaxTree::ClassDeclaration).void }
|
57
63
|
def visit_class(node)
|
58
64
|
@listeners[:on_class]&.each { |l| T.unsafe(l).on_class(node) }
|
data/lib/ruby_lsp/executor.rb
CHANGED
@@ -130,7 +130,7 @@ module RubyLsp
|
|
130
130
|
)
|
131
131
|
|
132
132
|
nil
|
133
|
-
rescue StandardError => error
|
133
|
+
rescue StandardError, LoadError => error
|
134
134
|
@message_queue << Notification.new(
|
135
135
|
message: "window/showMessage",
|
136
136
|
params: Interface::ShowMessageParams.new(
|
@@ -156,7 +156,7 @@ module RubyLsp
|
|
156
156
|
when "textDocument/diagnostic"
|
157
157
|
begin
|
158
158
|
diagnostic(uri)
|
159
|
-
rescue StandardError => error
|
159
|
+
rescue StandardError, LoadError => error
|
160
160
|
@message_queue << Notification.new(
|
161
161
|
message: "window/showMessage",
|
162
162
|
params: Interface::ShowMessageParams.new(
|
@@ -275,10 +275,17 @@ module RubyLsp
|
|
275
275
|
params(
|
276
276
|
uri: String,
|
277
277
|
position: Document::PositionShape,
|
278
|
-
).returns(T::Array[Interface::DocumentHighlight])
|
278
|
+
).returns(T.nilable(T::Array[Interface::DocumentHighlight]))
|
279
279
|
end
|
280
280
|
def document_highlight(uri, position)
|
281
|
-
|
281
|
+
document = @store.get(uri)
|
282
|
+
return if document.syntax_error?
|
283
|
+
|
284
|
+
target, parent = document.locate_node(position)
|
285
|
+
emitter = EventEmitter.new
|
286
|
+
listener = Requests::DocumentHighlight.new(target, parent, emitter, @message_queue)
|
287
|
+
emitter.visit(document.tree)
|
288
|
+
listener.response
|
282
289
|
end
|
283
290
|
|
284
291
|
sig { params(uri: String, range: Document::RangeShape).returns(T.nilable(T::Array[Interface::InlayHint])) }
|
@@ -29,6 +29,7 @@ module RubyLsp
|
|
29
29
|
|
30
30
|
BASE_COMMAND = T.let((File.exist?("Gemfile.lock") ? "bundle exec ruby" : "ruby") + " -Itest ", String)
|
31
31
|
ACCESS_MODIFIERS = T.let(["public", "private", "protected"], T::Array[String])
|
32
|
+
SUPPORTED_TEST_LIBRARIES = T.let(["minitest", "test-unit"], T::Array[String])
|
32
33
|
|
33
34
|
sig { override.returns(ResponseType) }
|
34
35
|
attr_reader :response
|
@@ -61,8 +62,9 @@ module RubyLsp
|
|
61
62
|
def on_class(node)
|
62
63
|
@visibility_stack.push(["public", "public"])
|
63
64
|
class_name = node.constant.constant.value
|
65
|
+
@class_stack.push(class_name)
|
66
|
+
|
64
67
|
if class_name.end_with?("Test")
|
65
|
-
@class_stack.push(class_name)
|
66
68
|
add_test_code_lens(
|
67
69
|
node,
|
68
70
|
name: class_name,
|
@@ -80,11 +82,13 @@ module RubyLsp
|
|
80
82
|
|
81
83
|
sig { params(node: SyntaxTree::DefNode).void }
|
82
84
|
def on_def(node)
|
85
|
+
class_name = @class_stack.last
|
86
|
+
return unless class_name&.end_with?("Test")
|
87
|
+
|
83
88
|
visibility, _ = @visibility_stack.last
|
84
89
|
if visibility == "public"
|
85
90
|
method_name = node.name.value
|
86
91
|
if method_name.start_with?("test_")
|
87
|
-
class_name = T.must(@class_stack.last)
|
88
92
|
add_test_code_lens(
|
89
93
|
node,
|
90
94
|
name: method_name,
|
@@ -154,6 +158,9 @@ module RubyLsp
|
|
154
158
|
|
155
159
|
sig { params(node: SyntaxTree::Node, name: String, command: String, kind: Symbol).void }
|
156
160
|
def add_test_code_lens(node, name:, command:, kind:)
|
161
|
+
# don't add code lenses if the test library is not supported or unknown
|
162
|
+
return unless SUPPORTED_TEST_LIBRARIES.include?(@test_library)
|
163
|
+
|
157
164
|
arguments = [
|
158
165
|
@path,
|
159
166
|
name,
|
@@ -193,8 +200,13 @@ module RubyLsp
|
|
193
200
|
|
194
201
|
sig { params(node: SyntaxTree::Command).returns(T.nilable(String)) }
|
195
202
|
def resolve_gem_remote(node)
|
196
|
-
|
197
|
-
|
203
|
+
gem_statement = node.arguments.parts.first
|
204
|
+
return unless gem_statement.is_a?(SyntaxTree::StringLiteral)
|
205
|
+
|
206
|
+
gem_name = gem_statement.parts.first
|
207
|
+
return unless gem_name.is_a?(SyntaxTree::TStringContent)
|
208
|
+
|
209
|
+
spec = Gem::Specification.stubs.find { |gem| gem.name == gem_name.value }&.to_spec
|
198
210
|
return if spec.nil?
|
199
211
|
|
200
212
|
[spec.homepage, spec.metadata["source_code_uri"]].compact.find do |page|
|
@@ -22,34 +22,49 @@ module RubyLsp
|
|
22
22
|
# FOO # should be highlighted as "read"
|
23
23
|
# end
|
24
24
|
# ```
|
25
|
-
class DocumentHighlight <
|
25
|
+
class DocumentHighlight < Listener
|
26
26
|
extend T::Sig
|
27
27
|
|
28
|
-
|
29
|
-
def initialize(document, position)
|
30
|
-
super(document)
|
28
|
+
ResponseType = type_member { { fixed: T::Array[Interface::DocumentHighlight] } }
|
31
29
|
|
32
|
-
|
33
|
-
|
30
|
+
sig { override.returns(ResponseType) }
|
31
|
+
attr_reader :response
|
34
32
|
|
35
|
-
|
33
|
+
sig do
|
34
|
+
params(
|
35
|
+
target: T.nilable(SyntaxTree::Node),
|
36
|
+
parent: T.nilable(SyntaxTree::Node),
|
37
|
+
emitter: EventEmitter,
|
38
|
+
message_queue: Thread::Queue,
|
39
|
+
).void
|
36
40
|
end
|
41
|
+
def initialize(target, parent, emitter, message_queue)
|
42
|
+
super(emitter, message_queue)
|
43
|
+
|
44
|
+
@response = T.let([], T::Array[Interface::DocumentHighlight])
|
45
|
+
|
46
|
+
return unless target && parent
|
37
47
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
48
|
+
highlight_target =
|
49
|
+
case target
|
50
|
+
when *DIRECT_HIGHLIGHTS
|
51
|
+
Support::HighlightTarget.new(target)
|
52
|
+
when SyntaxTree::Ident
|
53
|
+
relevant_node = parent.is_a?(SyntaxTree::Params) ? target : parent
|
54
|
+
Support::HighlightTarget.new(relevant_node)
|
55
|
+
end
|
56
|
+
|
57
|
+
@target = T.let(highlight_target, T.nilable(Support::HighlightTarget))
|
58
|
+
|
59
|
+
emitter.register(self, :on_node) if @target
|
43
60
|
end
|
44
61
|
|
45
|
-
sig {
|
46
|
-
def
|
62
|
+
sig { params(node: T.nilable(SyntaxTree::Node)).void }
|
63
|
+
def on_node(node)
|
47
64
|
return if node.nil?
|
48
65
|
|
49
66
|
match = T.must(@target).highlight_type(node)
|
50
67
|
add_highlight(match) if match
|
51
|
-
|
52
|
-
super
|
53
68
|
end
|
54
69
|
|
55
70
|
private
|
@@ -65,30 +80,10 @@ module RubyLsp
|
|
65
80
|
T::Array[T.class_of(SyntaxTree::Node)],
|
66
81
|
)
|
67
82
|
|
68
|
-
sig do
|
69
|
-
params(
|
70
|
-
position: Document::PositionShape,
|
71
|
-
).returns(T.nilable(Support::HighlightTarget))
|
72
|
-
end
|
73
|
-
def find(position)
|
74
|
-
matched, parent = @document.locate_node(position)
|
75
|
-
|
76
|
-
return unless matched && parent
|
77
|
-
return unless matched.is_a?(SyntaxTree::Ident) || DIRECT_HIGHLIGHTS.include?(matched.class)
|
78
|
-
|
79
|
-
case matched
|
80
|
-
when *DIRECT_HIGHLIGHTS
|
81
|
-
Support::HighlightTarget.new(matched)
|
82
|
-
when SyntaxTree::Ident
|
83
|
-
relevant_node = parent.is_a?(SyntaxTree::Params) ? matched : parent
|
84
|
-
Support::HighlightTarget.new(relevant_node)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
83
|
sig { params(match: Support::HighlightTarget::HighlightMatch).void }
|
89
84
|
def add_highlight(match)
|
90
85
|
range = range_from_syntax_tree_node(match.node)
|
91
|
-
@
|
86
|
+
@response << Interface::DocumentHighlight.new(range: range, kind: match.type)
|
92
87
|
end
|
93
88
|
end
|
94
89
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
module RubyLsp
|
5
5
|
module Requests
|
6
|
-
# ![Inlay hint demo](../../
|
6
|
+
# ![Inlay hint demo](../../inlay_hints.gif)
|
7
7
|
#
|
8
8
|
# [Inlay hints](https://microsoft.github.io/language-server-protocol/specification#textDocument_inlayHint)
|
9
9
|
# are labels added directly in the code that explicitly show the user something that might
|
@@ -30,13 +30,11 @@ module RubyLsp
|
|
30
30
|
def initialize(document, position, trigger_character)
|
31
31
|
super(document)
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
@line_end = T.let(scanner.find_char_position(position), Integer)
|
36
|
-
line = T.must(@document.source[line_begin..@line_end])
|
33
|
+
@lines = T.let(@document.source.lines, T::Array[String])
|
34
|
+
line = @lines[[position[:line] - 1, 0].max]
|
37
35
|
|
38
|
-
@indentation = T.let(find_indentation(line), Integer)
|
39
|
-
@previous_line = T.let(line.strip.chomp, String)
|
36
|
+
@indentation = T.let(line ? find_indentation(line) : 0, Integer)
|
37
|
+
@previous_line = T.let(line ? line.strip.chomp : "", String)
|
40
38
|
@position = position
|
41
39
|
@edits = T.let([], T::Array[Interface::TextEdit])
|
42
40
|
@trigger_character = trigger_character
|
@@ -87,30 +85,15 @@ module RubyLsp
|
|
87
85
|
return unless END_REGEXES.any? { |regex| regex.match?(@previous_line) }
|
88
86
|
|
89
87
|
indents = " " * @indentation
|
88
|
+
current_line = @lines[@position[:line]]
|
89
|
+
next_line = @lines[@position[:line] + 1]
|
90
90
|
|
91
|
-
if
|
92
|
-
# If the previous line has a line break, then it means there's content after the line break that triggered
|
93
|
-
# this completion. For these cases, we want to add the `end` after the content and move the cursor back to the
|
94
|
-
# keyword that triggered the completion
|
95
|
-
|
96
|
-
line = @position[:line]
|
97
|
-
|
98
|
-
# If there are enough lines in the document, we want to add the `end` token on the line below the extra
|
99
|
-
# content. Otherwise, we want to insert and extra line break ourselves
|
100
|
-
correction = if T.must(@document.source[@line_end..-1]).count("\n") >= 2
|
101
|
-
line -= 1
|
102
|
-
"#{indents}end"
|
103
|
-
else
|
104
|
-
"#{indents}\nend"
|
105
|
-
end
|
106
|
-
|
107
|
-
add_edit_with_text(correction, { line: @position[:line] + 1, character: @position[:character] })
|
108
|
-
move_cursor_to(line, @indentation + 3)
|
109
|
-
else
|
110
|
-
# If there's nothing after the new line break that triggered the completion, then we want to add the `end` and
|
111
|
-
# move the cursor to the body of the statement
|
91
|
+
if current_line.nil? || current_line.blank?
|
112
92
|
add_edit_with_text(" \n#{indents}end")
|
113
93
|
move_cursor_to(@position[:line], @indentation + 2)
|
94
|
+
elsif next_line.nil? || next_line.blank?
|
95
|
+
add_edit_with_text("#{indents}end", { line: @position[:line] + 1, character: @position[:character] })
|
96
|
+
move_cursor_to(@position[:line], @indentation + 3)
|
114
97
|
end
|
115
98
|
end
|
116
99
|
|
@@ -20,15 +20,19 @@ module RubyLsp
|
|
20
20
|
|
21
21
|
sig { returns(String) }
|
22
22
|
def detected_test_library
|
23
|
-
|
23
|
+
# A Rails app may have a dependency on minitest, but we would instead want to use the Rails test runner provided
|
24
|
+
# by ruby-lsp-rails.
|
25
|
+
if direct_dependency?(/^rails$/)
|
26
|
+
"rails"
|
27
|
+
# NOTE: Intentionally ends with $ to avoid mis-matching minitest-reporters, etc. in a Rails app.
|
28
|
+
elsif direct_dependency?(/^minitest$/)
|
24
29
|
"minitest"
|
25
30
|
elsif direct_dependency?(/^test-unit/)
|
26
31
|
"test-unit"
|
27
32
|
elsif direct_dependency?(/^rspec/)
|
28
33
|
"rspec"
|
29
34
|
else
|
30
|
-
|
31
|
-
"minitest"
|
35
|
+
"unknown"
|
32
36
|
end
|
33
37
|
end
|
34
38
|
|
@@ -78,7 +78,7 @@ module URI
|
|
78
78
|
if URI.respond_to?(:register_scheme)
|
79
79
|
URI.register_scheme("SOURCE", self)
|
80
80
|
else
|
81
|
-
@@schemes = T.let(@@schemes, T::Hash[String, T
|
81
|
+
@@schemes = T.let(@@schemes, T::Hash[String, T.untyped]) # rubocop:disable Style/ClassVars
|
82
82
|
@@schemes["SOURCE"] = self
|
83
83
|
end
|
84
84
|
end
|
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.6.
|
4
|
+
version: 0.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-06-
|
11
|
+
date: 2023-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: language_server-protocol
|