ruby-lsp 0.6.0 → 0.6.2
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 +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
|
-
# 
|
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
|