ruby-lsp 0.10.1 → 0.11.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp-check +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +40 -5
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +141 -5
- data/lib/ruby_indexer/lib/ruby_indexer/visitor.rb +66 -18
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +23 -0
- data/lib/ruby_indexer/test/configuration_test.rb +2 -0
- data/lib/ruby_indexer/test/constant_test.rb +202 -0
- data/lib/ruby_indexer/test/index_test.rb +20 -0
- data/lib/ruby_lsp/{extension.rb → addon.rb} +27 -25
- data/lib/ruby_lsp/check_docs.rb +7 -8
- data/lib/ruby_lsp/document.rb +35 -38
- data/lib/ruby_lsp/event_emitter.rb +239 -77
- data/lib/ruby_lsp/executor.rb +45 -55
- data/lib/ruby_lsp/internal.rb +2 -3
- data/lib/ruby_lsp/listener.rb +8 -7
- data/lib/ruby_lsp/parameter_scope.rb +33 -0
- data/lib/ruby_lsp/requests/base_request.rb +3 -3
- data/lib/ruby_lsp/requests/code_action_resolve.rb +14 -14
- data/lib/ruby_lsp/requests/code_lens.rb +39 -63
- data/lib/ruby_lsp/requests/completion.rb +54 -32
- data/lib/ruby_lsp/requests/definition.rb +30 -27
- data/lib/ruby_lsp/requests/diagnostics.rb +26 -3
- data/lib/ruby_lsp/requests/document_highlight.rb +18 -19
- data/lib/ruby_lsp/requests/document_link.rb +50 -9
- data/lib/ruby_lsp/requests/document_symbol.rb +82 -75
- data/lib/ruby_lsp/requests/folding_ranges.rb +199 -222
- data/lib/ruby_lsp/requests/formatting.rb +5 -6
- data/lib/ruby_lsp/requests/hover.rb +33 -22
- data/lib/ruby_lsp/requests/inlay_hints.rb +2 -3
- data/lib/ruby_lsp/requests/selection_ranges.rb +65 -40
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +187 -145
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +3 -4
- data/lib/ruby_lsp/requests/support/annotation.rb +18 -17
- data/lib/ruby_lsp/requests/support/common.rb +17 -26
- data/lib/ruby_lsp/requests/support/dependency_detector.rb +67 -42
- data/lib/ruby_lsp/requests/support/highlight_target.rb +64 -45
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +9 -4
- data/lib/ruby_lsp/requests/support/selection_range.rb +5 -4
- data/lib/ruby_lsp/requests/support/sorbet.rb +2 -57
- data/lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb +7 -1
- data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -1
- data/lib/ruby_lsp/server.rb +6 -44
- data/lib/ruby_lsp/utils.rb +2 -12
- metadata +11 -30
@@ -2,24 +2,24 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module RubyLsp
|
5
|
-
# To register an
|
5
|
+
# To register an addon, inherit from this class and implement both `name` and `activate`
|
6
6
|
#
|
7
7
|
# # Example
|
8
8
|
#
|
9
9
|
# ```ruby
|
10
10
|
# module MyGem
|
11
|
-
# class
|
11
|
+
# class MyAddon < Addon
|
12
12
|
# def activate
|
13
13
|
# # Perform any relevant initialization
|
14
14
|
# end
|
15
15
|
#
|
16
16
|
# def name
|
17
|
-
# "My
|
17
|
+
# "My addon name"
|
18
18
|
# end
|
19
19
|
# end
|
20
20
|
# end
|
21
21
|
# ```
|
22
|
-
class
|
22
|
+
class Addon
|
23
23
|
extend T::Sig
|
24
24
|
extend T::Helpers
|
25
25
|
|
@@ -28,37 +28,37 @@ module RubyLsp
|
|
28
28
|
class << self
|
29
29
|
extend T::Sig
|
30
30
|
|
31
|
-
# Automatically track and instantiate
|
32
|
-
sig { params(child_class: T.class_of(
|
31
|
+
# Automatically track and instantiate addon classes
|
32
|
+
sig { params(child_class: T.class_of(Addon)).void }
|
33
33
|
def inherited(child_class)
|
34
|
-
|
34
|
+
addons << child_class.new
|
35
35
|
super
|
36
36
|
end
|
37
37
|
|
38
|
-
sig { returns(T::Array[
|
39
|
-
def
|
40
|
-
@
|
38
|
+
sig { returns(T::Array[Addon]) }
|
39
|
+
def addons
|
40
|
+
@addons ||= T.let([], T.nilable(T::Array[Addon]))
|
41
41
|
end
|
42
42
|
|
43
|
-
# Discovers and loads all
|
44
|
-
sig { returns(T::Array[
|
45
|
-
def
|
46
|
-
# Require all
|
47
|
-
# `some_gem/lib/ruby_lsp/your_gem_name/
|
48
|
-
Gem.find_files("ruby_lsp/**/
|
49
|
-
require File.expand_path(
|
43
|
+
# Discovers and loads all addons. Returns the list of activated addons
|
44
|
+
sig { returns(T::Array[Addon]) }
|
45
|
+
def load_addons
|
46
|
+
# Require all addons entry points, which should be placed under
|
47
|
+
# `some_gem/lib/ruby_lsp/your_gem_name/addon.rb`
|
48
|
+
Gem.find_files("ruby_lsp/**/addon.rb").each do |addon|
|
49
|
+
require File.expand_path(addon)
|
50
50
|
rescue => e
|
51
51
|
warn(e.message)
|
52
52
|
warn(e.backtrace.to_s) # rubocop:disable Lint/RedundantStringCoercion
|
53
53
|
end
|
54
54
|
|
55
|
-
# Activate each one of the discovered
|
55
|
+
# Activate each one of the discovered addons. If any problems occur in the addons, we don't want to
|
56
56
|
# fail to boot the server
|
57
|
-
|
58
|
-
|
57
|
+
addons.each do |addon|
|
58
|
+
addon.activate
|
59
59
|
nil
|
60
60
|
rescue => e
|
61
|
-
|
61
|
+
addon.add_error(e)
|
62
62
|
end
|
63
63
|
end
|
64
64
|
end
|
@@ -92,17 +92,17 @@ module RubyLsp
|
|
92
92
|
@errors.filter_map(&:backtrace).join("\n\n")
|
93
93
|
end
|
94
94
|
|
95
|
-
# Each
|
95
|
+
# Each addon should implement `MyAddon#activate` and use to perform any sort of initialization, such as
|
96
96
|
# reading information into memory or even spawning a separate process
|
97
97
|
sig { abstract.void }
|
98
98
|
def activate; end
|
99
99
|
|
100
|
-
# Each
|
100
|
+
# Each addon should implement `MyAddon#deactivate` and use to perform any clean up, like shutting down a
|
101
101
|
# child process
|
102
102
|
sig { abstract.void }
|
103
103
|
def deactivate; end
|
104
104
|
|
105
|
-
#
|
105
|
+
# Addons should override the `name` method to return the addon name
|
106
106
|
sig { abstract.returns(String) }
|
107
107
|
def name; end
|
108
108
|
|
@@ -119,11 +119,13 @@ module RubyLsp
|
|
119
119
|
# Creates a new Hover listener. This method is invoked on every Hover request
|
120
120
|
sig do
|
121
121
|
overridable.params(
|
122
|
+
nesting: T::Array[String],
|
123
|
+
index: RubyIndexer::Index,
|
122
124
|
emitter: EventEmitter,
|
123
125
|
message_queue: Thread::Queue,
|
124
126
|
).returns(T.nilable(Listener[T.nilable(Interface::Hover)]))
|
125
127
|
end
|
126
|
-
def create_hover_listener(emitter, message_queue); end
|
128
|
+
def create_hover_listener(nesting, index, emitter, message_queue); end
|
127
129
|
|
128
130
|
# Creates a new DocumentSymbol listener. This method is invoked on every DocumentSymbol request
|
129
131
|
sig do
|
data/lib/ruby_lsp/check_docs.rb
CHANGED
@@ -5,9 +5,9 @@ require "ruby_lsp/internal"
|
|
5
5
|
require "objspace"
|
6
6
|
|
7
7
|
module RubyLsp
|
8
|
-
# This rake task checks that all requests or
|
9
|
-
# specify the absolute path for all files that must be required in order to discover all listeners and their
|
10
|
-
#
|
8
|
+
# This rake task checks that all requests or addons 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 and their related
|
10
|
+
# GIFs
|
11
11
|
#
|
12
12
|
# # Rakefile
|
13
13
|
# request_files = FileList.new("#{__dir__}/lib/ruby_lsp/requests/*.rb") do |fl|
|
@@ -53,8 +53,7 @@ module RubyLsp
|
|
53
53
|
# documented
|
54
54
|
features = ObjectSpace.each_object(Class).filter_map do |k|
|
55
55
|
klass = T.unsafe(k)
|
56
|
-
klass if klass <
|
57
|
-
(klass < RubyLsp::Listener && klass != RubyLsp::ExtensibleListener)
|
56
|
+
klass if klass < Requests::BaseRequest || (klass < Listener && klass != ExtensibleListener)
|
58
57
|
end
|
59
58
|
|
60
59
|
missing_docs = T.let(Hash.new { |h, k| h[k] = [] }, T::Hash[String, T::Array[String]])
|
@@ -82,14 +81,14 @@ module RubyLsp
|
|
82
81
|
T.must(missing_docs[class_name]) << "No documentation found"
|
83
82
|
elsif !%r{\(https://microsoft.github.io/language-server-protocol/specification#.*\)}.match?(documentation)
|
84
83
|
T.must(missing_docs[class_name]) << <<~DOCS
|
85
|
-
Missing specification link. Requests and
|
84
|
+
Missing specification link. Requests and addons should include a link to the LSP specification for the
|
86
85
|
related feature. For example:
|
87
86
|
|
88
87
|
[Inlay hint](https://microsoft.github.io/language-server-protocol/specification#textDocument_inlayHint)
|
89
88
|
DOCS
|
90
89
|
elsif !documentation.include?("# Example")
|
91
90
|
T.must(missing_docs[class_name]) << <<~DOCS
|
92
|
-
Missing example. Requests and
|
91
|
+
Missing example. Requests and addons should include a code example that explains what the feature does.
|
93
92
|
|
94
93
|
# # Example
|
95
94
|
# ```ruby
|
@@ -99,7 +98,7 @@ module RubyLsp
|
|
99
98
|
DOCS
|
100
99
|
elsif !/\[.* demo\]\(.*\.gif\)/.match?(documentation)
|
101
100
|
T.must(missing_docs[class_name]) << <<~DOCS
|
102
|
-
Missing demonstration GIF. Each request and
|
101
|
+
Missing demonstration GIF. Each request and addon must be documented with a GIF that shows the feature
|
103
102
|
working. For example:
|
104
103
|
|
105
104
|
# [Inlay hint demo](../../inlay_hint.gif)
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -9,8 +9,8 @@ module RubyLsp
|
|
9
9
|
RangeShape = T.type_alias { { start: PositionShape, end: PositionShape } }
|
10
10
|
EditShape = T.type_alias { { range: RangeShape, text: String } }
|
11
11
|
|
12
|
-
sig { returns(
|
13
|
-
attr_reader :
|
12
|
+
sig { returns(YARP::ParseResult) }
|
13
|
+
attr_reader :parse_result
|
14
14
|
|
15
15
|
sig { returns(String) }
|
16
16
|
attr_reader :source
|
@@ -28,11 +28,18 @@ module RubyLsp
|
|
28
28
|
@source = T.let(source, String)
|
29
29
|
@version = T.let(version, Integer)
|
30
30
|
@uri = T.let(uri, URI::Generic)
|
31
|
-
@
|
32
|
-
@
|
33
|
-
|
34
|
-
|
35
|
-
|
31
|
+
@needs_parsing = T.let(false, T::Boolean)
|
32
|
+
@parse_result = T.let(YARP.parse(@source), YARP::ParseResult)
|
33
|
+
end
|
34
|
+
|
35
|
+
sig { returns(YARP::ProgramNode) }
|
36
|
+
def tree
|
37
|
+
@parse_result.value
|
38
|
+
end
|
39
|
+
|
40
|
+
sig { returns(T::Array[YARP::Comment]) }
|
41
|
+
def comments
|
42
|
+
@parse_result.comments
|
36
43
|
end
|
37
44
|
|
38
45
|
sig { params(other: Document).returns(T::Boolean) }
|
@@ -80,29 +87,21 @@ module RubyLsp
|
|
80
87
|
end
|
81
88
|
|
82
89
|
@version = version
|
83
|
-
@
|
90
|
+
@needs_parsing = true
|
84
91
|
@cache.clear
|
85
92
|
end
|
86
93
|
|
87
94
|
sig { void }
|
88
95
|
def parse
|
89
|
-
return
|
96
|
+
return unless @needs_parsing
|
90
97
|
|
91
|
-
@
|
92
|
-
@
|
93
|
-
@syntax_error = false
|
94
|
-
rescue SyntaxTree::Parser::ParseError
|
95
|
-
@syntax_error = true
|
98
|
+
@needs_parsing = false
|
99
|
+
@parse_result = YARP.parse(@source)
|
96
100
|
end
|
97
101
|
|
98
102
|
sig { returns(T::Boolean) }
|
99
103
|
def syntax_error?
|
100
|
-
@
|
101
|
-
end
|
102
|
-
|
103
|
-
sig { returns(T::Boolean) }
|
104
|
-
def parsed?
|
105
|
-
!@tree.nil?
|
104
|
+
@parse_result.failure?
|
106
105
|
end
|
107
106
|
|
108
107
|
sig { returns(Scanner) }
|
@@ -113,27 +112,25 @@ module RubyLsp
|
|
113
112
|
sig do
|
114
113
|
params(
|
115
114
|
position: PositionShape,
|
116
|
-
node_types: T::Array[T.class_of(
|
117
|
-
).returns([T.nilable(
|
115
|
+
node_types: T::Array[T.class_of(YARP::Node)],
|
116
|
+
).returns([T.nilable(YARP::Node), T.nilable(YARP::Node), T::Array[String]])
|
118
117
|
end
|
119
118
|
def locate_node(position, node_types: [])
|
120
|
-
|
121
|
-
|
122
|
-
locate(T.must(@tree), create_scanner.find_char_position(position), node_types: node_types)
|
119
|
+
locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types)
|
123
120
|
end
|
124
121
|
|
125
122
|
sig do
|
126
123
|
params(
|
127
|
-
node:
|
124
|
+
node: YARP::Node,
|
128
125
|
char_position: Integer,
|
129
|
-
node_types: T::Array[T.class_of(
|
130
|
-
).returns([T.nilable(
|
126
|
+
node_types: T::Array[T.class_of(YARP::Node)],
|
127
|
+
).returns([T.nilable(YARP::Node), T.nilable(YARP::Node), T::Array[String]])
|
131
128
|
end
|
132
129
|
def locate(node, char_position, node_types: [])
|
133
|
-
queue = T.let(node.child_nodes.compact, T::Array[T.nilable(
|
130
|
+
queue = T.let(node.child_nodes.compact, T::Array[T.nilable(YARP::Node)])
|
134
131
|
closest = node
|
135
|
-
parent = T.let(nil, T.nilable(
|
136
|
-
nesting = T.let([], T::Array[T.any(
|
132
|
+
parent = T.let(nil, T.nilable(YARP::Node))
|
133
|
+
nesting = T.let([], T::Array[T.any(YARP::ClassNode, YARP::ModuleNode)])
|
137
134
|
|
138
135
|
until queue.empty?
|
139
136
|
candidate = queue.shift
|
@@ -144,24 +141,24 @@ module RubyLsp
|
|
144
141
|
# Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
|
145
142
|
# same order as the visiting mechanism, which means searching the child nodes before moving on to the next
|
146
143
|
# sibling
|
147
|
-
queue.unshift(*candidate.child_nodes)
|
144
|
+
T.unsafe(queue).unshift(*candidate.child_nodes)
|
148
145
|
|
149
146
|
# Skip if the current node doesn't cover the desired position
|
150
147
|
loc = candidate.location
|
151
|
-
next unless (loc.
|
148
|
+
next unless (loc.start_offset...loc.end_offset).cover?(char_position)
|
152
149
|
|
153
150
|
# If the node's start character is already past the position, then we should've found the closest node
|
154
151
|
# already
|
155
|
-
break if char_position < loc.
|
152
|
+
break if char_position < loc.start_offset
|
156
153
|
|
157
154
|
# If the candidate starts after the end of the previous nesting level, then we've exited that nesting level and
|
158
155
|
# need to pop the stack
|
159
156
|
previous_level = nesting.last
|
160
|
-
nesting.pop if previous_level &&
|
157
|
+
nesting.pop if previous_level && loc.start_offset > previous_level.location.end_offset
|
161
158
|
|
162
159
|
# Keep track of the nesting where we found the target. This is used to determine the fully qualified name of the
|
163
160
|
# target when it is a constant
|
164
|
-
if candidate.is_a?(
|
161
|
+
if candidate.is_a?(YARP::ClassNode) || candidate.is_a?(YARP::ModuleNode)
|
165
162
|
nesting << candidate
|
166
163
|
end
|
167
164
|
|
@@ -170,13 +167,13 @@ module RubyLsp
|
|
170
167
|
|
171
168
|
# If the current node is narrower than or equal to the previous closest node, then it is more precise
|
172
169
|
closest_loc = closest.location
|
173
|
-
if loc.
|
170
|
+
if loc.end_offset - loc.start_offset <= closest_loc.end_offset - closest_loc.start_offset
|
174
171
|
parent = closest
|
175
172
|
closest = candidate
|
176
173
|
end
|
177
174
|
end
|
178
175
|
|
179
|
-
[closest, parent, nesting.map { |n| n.
|
176
|
+
[closest, parent, nesting.map { |n| n.constant_path.location.slice }]
|
180
177
|
end
|
181
178
|
|
182
179
|
class Scanner
|