ruby-lsp 0.3.6 → 0.3.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +29 -2
- data/VERSION +1 -1
- data/lib/ruby_lsp/document.rb +51 -46
- data/lib/ruby_lsp/handler.rb +2 -2
- data/lib/ruby_lsp/requests/base_request.rb +44 -20
- data/lib/ruby_lsp/requests/code_actions.rb +2 -2
- data/lib/ruby_lsp/requests/diagnostics.rb +1 -8
- data/lib/ruby_lsp/requests/document_highlight.rb +14 -11
- data/lib/ruby_lsp/requests/document_link.rb +30 -26
- data/lib/ruby_lsp/requests/document_symbol.rb +43 -83
- data/lib/ruby_lsp/requests/folding_ranges.rb +58 -38
- data/lib/ruby_lsp/requests/formatting.rb +2 -0
- data/lib/ruby_lsp/requests/hover.rb +16 -7
- data/lib/ruby_lsp/requests/inlay_hints.rb +1 -1
- data/lib/ruby_lsp/requests/on_type_formatting.rb +19 -22
- data/lib/ruby_lsp/requests/selection_ranges.rb +40 -38
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +96 -73
- data/lib/ruby_lsp/requests/support/highlight_target.rb +8 -9
- data/lib/ruby_lsp/requests/support/rails_document_client.rb +13 -6
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +15 -10
- data/lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb +1 -1
- data/lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb +1 -1
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +9 -6
- data/lib/ruby_lsp/requests/support/source_uri.rb +10 -7
- data/lib/ruby_lsp/server.rb +19 -7
- data/lib/ruby_lsp/store.rb +5 -1
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5f1ed447e51909ada66f6bebceca82d807686a4889468838a797e2ed8afff50e
|
4
|
+
data.tar.gz: 7b429773bbdebc171debbe446adb868136bd15b5f493542898df0a9c82f3514e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 62e8a6ee36c43dec6ec0af16fd19830b6369446455a5fc16871c368c8bb4ccdb47c0d9c5678990a43604bf03c622c2ef13bb82c6a6e9f26fdcde72b3b18ec99e
|
7
|
+
data.tar.gz: e441a670c22bcdbfdc3afe99743fe6e1c4aa5f28edaedcf09f47255372322c52ead68988bc6d7ce09170e368e4059c94dac73b395a640c86f20b2969ae8e8095
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# Ruby LSP
|
4
4
|
|
5
|
-
This gem is an implementation of the language server protocol specification for Ruby, used to improve editor features.
|
5
|
+
This gem is an implementation of the [language server protocol specification](https://microsoft.github.io/language-server-protocol/) for Ruby, used to improve editor features.
|
6
6
|
|
7
7
|
## Usage
|
8
8
|
|
@@ -14,7 +14,7 @@ group :development do
|
|
14
14
|
end
|
15
15
|
```
|
16
16
|
|
17
|
-
If using VS Code, install the [Ruby LSP
|
17
|
+
If using VS Code, install the [Ruby LSP extension](https://github.com/Shopify/vscode-ruby-lsp) to get the extra features in
|
18
18
|
the editor.
|
19
19
|
|
20
20
|
See the [documentation](https://shopify.github.io/ruby-lsp) for
|
@@ -28,10 +28,25 @@ are expected to adhere to the
|
|
28
28
|
[Contributor Covenant](https://github.com/Shopify/ruby-lsp/blob/main/CODE_OF_CONDUCT.md)
|
29
29
|
code of conduct.
|
30
30
|
|
31
|
+
### Running the test suite
|
32
|
+
|
33
|
+
Run the test suite with `bin/test`.
|
34
|
+
|
35
|
+
For more visibility into which tests are running, use the `SpecReporter`:
|
36
|
+
|
37
|
+
`SPEC_REPORTER=1 bin/test`
|
38
|
+
|
39
|
+
By default the tests run with warnings disabled to reduce noise. To enable warnings, pass `VERBOSE=1`.
|
40
|
+
Warnings are always shown when running in CI.
|
41
|
+
|
31
42
|
### Expectation testing
|
32
43
|
|
33
44
|
To simplify the way we run tests over different pieces of Ruby code, we use a custom expectations test framework against a set of Ruby fixtures.
|
34
45
|
|
46
|
+
We define expectations as `.exp` files, of which there are two variants:
|
47
|
+
* `.exp.rb`, to indicate the resulting code after an operation.
|
48
|
+
* `.exp.json`, consisting of a `result`, and an optional set of input `params`.
|
49
|
+
|
35
50
|
To add a new fixture to the expectations test suite:
|
36
51
|
|
37
52
|
1. Add a new fixture `my_fixture.rb` file under `test/fixtures`
|
@@ -78,6 +93,18 @@ Possible values are:
|
|
78
93
|
* `messages`: display requests and responses notifications
|
79
94
|
* `verbose`: display each request and response as JSON
|
80
95
|
|
96
|
+
### Debugging using VS Code
|
97
|
+
|
98
|
+
The `launch.json` contains a 'Minitest - current file' configuration for the debugger.
|
99
|
+
|
100
|
+
1. Add a breakpoint using the VS Code UI.
|
101
|
+
1. Open the relevant test file.
|
102
|
+
1. Open the **Run and Debug** panel on the sidebar.
|
103
|
+
1. Ensure `Minitest - current file` is selected in the top dropdown.
|
104
|
+
1. Press `F5` OR click the green triangle next to the top dropdown. VS Code will then run the test file with debugger activated.
|
105
|
+
1. When the breakpoint is triggered, the process will pause and VS Code will connect to the debugger and activate the debugger UI.
|
106
|
+
1. Open the Debug Console view to use the debugger's REPL.
|
107
|
+
|
81
108
|
## License
|
82
109
|
|
83
110
|
The gem is available as open source under the terms of the
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.3.
|
1
|
+
0.3.8
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -15,15 +15,11 @@ module RubyLsp
|
|
15
15
|
sig { returns(String) }
|
16
16
|
attr_reader :source
|
17
17
|
|
18
|
-
sig {
|
19
|
-
|
20
|
-
|
21
|
-
sig { params(source: String).void }
|
22
|
-
def initialize(source)
|
18
|
+
sig { params(source: String, encoding: String).void }
|
19
|
+
def initialize(source, encoding = "utf-8")
|
23
20
|
@cache = T.let({}, T::Hash[Symbol, T.untyped])
|
24
|
-
@
|
21
|
+
@encoding = T.let(encoding, String)
|
25
22
|
@source = T.let(source, String)
|
26
|
-
@parsable_source = T.let(source.dup, String)
|
27
23
|
@unparsed_edits = T.let([], T::Array[EditShape])
|
28
24
|
@tree = T.let(SyntaxTree.parse(@source), T.nilable(SyntaxTree::Node))
|
29
25
|
rescue SyntaxTree::Parser::ParseError
|
@@ -53,8 +49,15 @@ module RubyLsp
|
|
53
49
|
|
54
50
|
sig { params(edits: T::Array[EditShape]).void }
|
55
51
|
def push_edits(edits)
|
56
|
-
|
57
|
-
|
52
|
+
edits.each do |edit|
|
53
|
+
range = edit[:range]
|
54
|
+
scanner = create_scanner
|
55
|
+
|
56
|
+
start_position = scanner.find_char_position(range[:start])
|
57
|
+
end_position = scanner.find_char_position(range[:end])
|
58
|
+
|
59
|
+
@source[start_position...end_position] = edit[:text]
|
60
|
+
end
|
58
61
|
|
59
62
|
@unparsed_edits.concat(edits)
|
60
63
|
@cache.clear
|
@@ -65,17 +68,14 @@ module RubyLsp
|
|
65
68
|
return if @unparsed_edits.empty?
|
66
69
|
|
67
70
|
@tree = SyntaxTree.parse(@source)
|
68
|
-
@syntax_error_edits.clear
|
69
71
|
@unparsed_edits.clear
|
70
|
-
@parsable_source = @source.dup
|
71
72
|
rescue SyntaxTree::Parser::ParseError
|
72
|
-
|
73
|
-
update_parsable_source(@unparsed_edits)
|
73
|
+
# Do nothing if we fail parse
|
74
74
|
end
|
75
75
|
|
76
76
|
sig { returns(T::Boolean) }
|
77
|
-
def
|
78
|
-
@
|
77
|
+
def syntax_error?
|
78
|
+
@unparsed_edits.any?
|
79
79
|
end
|
80
80
|
|
81
81
|
sig { returns(T::Boolean) }
|
@@ -83,52 +83,57 @@ module RubyLsp
|
|
83
83
|
!@tree.nil?
|
84
84
|
end
|
85
85
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
def update_parsable_source(edits)
|
90
|
-
# If the new edits caused a syntax error, make all edits blank spaces and line breaks to adjust the line and
|
91
|
-
# column numbers. This is attempt to make the document parsable while partial edits are being applied
|
92
|
-
edits.each do |edit|
|
93
|
-
next if edit[:text].empty? # skip deletions, since they may have caused the syntax error
|
94
|
-
|
95
|
-
apply_edit(@parsable_source, edit[:range], edit[:text].gsub(/[^\r\n]/, " "))
|
96
|
-
end
|
97
|
-
|
98
|
-
@tree = SyntaxTree.parse(@parsable_source)
|
99
|
-
rescue StandardError
|
100
|
-
# Trying to maintain a parsable source when there are syntax errors is a best effort. If we fail to apply edits or
|
101
|
-
# parse, just ignore it
|
102
|
-
end
|
103
|
-
|
104
|
-
sig { params(source: String, range: RangeShape, text: String).void }
|
105
|
-
def apply_edit(source, range, text)
|
106
|
-
scanner = Scanner.new(source)
|
107
|
-
start_position = scanner.find_position(range[:start])
|
108
|
-
end_position = scanner.find_position(range[:end])
|
109
|
-
|
110
|
-
source[start_position...end_position] = text
|
86
|
+
sig { returns(Scanner) }
|
87
|
+
def create_scanner
|
88
|
+
Scanner.new(@source, @encoding)
|
111
89
|
end
|
112
90
|
|
113
91
|
class Scanner
|
114
92
|
extend T::Sig
|
115
93
|
|
116
|
-
|
117
|
-
|
94
|
+
LINE_BREAK = T.let(0x0A, Integer)
|
95
|
+
# After character 0xFFFF, UTF-16 considers characters to have length 2 and we have to account for that
|
96
|
+
SURROGATE_PAIR_START = T.let(0xFFFF, Integer)
|
97
|
+
|
98
|
+
sig { params(source: String, encoding: String).void }
|
99
|
+
def initialize(source, encoding)
|
118
100
|
@current_line = T.let(0, Integer)
|
119
101
|
@pos = T.let(0, Integer)
|
120
|
-
@source = source
|
102
|
+
@source = T.let(source.codepoints, T::Array[Integer])
|
103
|
+
@encoding = encoding
|
121
104
|
end
|
122
105
|
|
106
|
+
# Finds the character index inside the source string for a given line and column
|
123
107
|
sig { params(position: PositionShape).returns(Integer) }
|
124
|
-
def
|
108
|
+
def find_char_position(position)
|
109
|
+
# Find the character index for the beginning of the requested line
|
125
110
|
until @current_line == position[:line]
|
126
|
-
@pos += 1 until
|
111
|
+
@pos += 1 until LINE_BREAK == @source[@pos]
|
127
112
|
@pos += 1
|
128
113
|
@current_line += 1
|
129
114
|
end
|
130
115
|
|
131
|
-
|
116
|
+
# The final position is the beginning of the line plus the requested column. If the encoding is UTF-16, we also
|
117
|
+
# need to adjust for surrogate pairs
|
118
|
+
requested_position = @pos + position[:character]
|
119
|
+
requested_position -= utf_16_character_position_correction(@pos, requested_position) if @encoding == "utf-16"
|
120
|
+
requested_position
|
121
|
+
end
|
122
|
+
|
123
|
+
# Subtract 1 for each character after 0xFFFF in the current line from the column position, so that we hit the
|
124
|
+
# right character in the UTF-8 representation
|
125
|
+
sig { params(current_position: Integer, requested_position: Integer).returns(Integer) }
|
126
|
+
def utf_16_character_position_correction(current_position, requested_position)
|
127
|
+
utf16_unicode_correction = 0
|
128
|
+
|
129
|
+
until current_position == requested_position
|
130
|
+
codepoint = @source[current_position]
|
131
|
+
utf16_unicode_correction += 1 if codepoint && codepoint > SURROGATE_PAIR_START
|
132
|
+
|
133
|
+
current_position += 1
|
134
|
+
end
|
135
|
+
|
136
|
+
utf16_unicode_correction
|
132
137
|
end
|
133
138
|
end
|
134
139
|
end
|
data/lib/ruby_lsp/handler.rb
CHANGED
@@ -59,7 +59,7 @@ module RubyLsp
|
|
59
59
|
|
60
60
|
sig { void }
|
61
61
|
def start
|
62
|
-
|
62
|
+
warn("Starting Ruby LSP...")
|
63
63
|
|
64
64
|
@reader.read do |request|
|
65
65
|
handler = @handlers[request[:method]]
|
@@ -94,7 +94,7 @@ module RubyLsp
|
|
94
94
|
|
95
95
|
sig { void }
|
96
96
|
def shutdown
|
97
|
-
|
97
|
+
warn("Shutting down Ruby LSP...")
|
98
98
|
@queue.shutdown
|
99
99
|
store.clear
|
100
100
|
end
|
@@ -29,13 +29,17 @@ module RubyLsp
|
|
29
29
|
loc = node.location
|
30
30
|
|
31
31
|
LanguageServer::Protocol::Interface::Range.new(
|
32
|
-
start: LanguageServer::Protocol::Interface::Position.new(
|
33
|
-
|
32
|
+
start: LanguageServer::Protocol::Interface::Position.new(
|
33
|
+
line: loc.start_line - 1,
|
34
|
+
character: loc.start_column,
|
35
|
+
),
|
34
36
|
end: LanguageServer::Protocol::Interface::Position.new(line: loc.end_line - 1, character: loc.end_column),
|
35
37
|
)
|
36
38
|
end
|
37
39
|
|
38
|
-
sig
|
40
|
+
sig do
|
41
|
+
params(node: T.any(SyntaxTree::ConstPathRef, SyntaxTree::ConstRef, SyntaxTree::TopConstRef)).returns(String)
|
42
|
+
end
|
39
43
|
def full_constant_name(node)
|
40
44
|
name = +node.constant.value
|
41
45
|
constant = T.let(node, SyntaxTree::Node)
|
@@ -56,28 +60,48 @@ module RubyLsp
|
|
56
60
|
|
57
61
|
sig do
|
58
62
|
params(
|
59
|
-
|
60
|
-
target_nodes: T::Array[T.class_of(SyntaxTree::Node)],
|
63
|
+
node: SyntaxTree::Node,
|
61
64
|
position: Integer,
|
62
|
-
).returns(T::
|
65
|
+
).returns([T.nilable(SyntaxTree::Node), T.nilable(SyntaxTree::Node)])
|
63
66
|
end
|
64
|
-
def
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
67
|
+
def locate(node, position)
|
68
|
+
queue = T.let(node.child_nodes.compact, T::Array[T.nilable(SyntaxTree::Node)])
|
69
|
+
closest = node
|
70
|
+
|
71
|
+
until queue.empty?
|
72
|
+
candidate = queue.shift
|
73
|
+
|
74
|
+
# Skip nil child nodes
|
75
|
+
next if candidate.nil?
|
76
|
+
|
77
|
+
# Add the next child_nodes to the queue to be processed
|
78
|
+
queue.concat(candidate.child_nodes)
|
79
|
+
|
80
|
+
# Skip if the current node doesn't cover the desired position
|
81
|
+
loc = candidate.location
|
82
|
+
next unless (loc.start_char...loc.end_char).cover?(position)
|
83
|
+
|
84
|
+
# If the node's start character is already past the position, then we should've found the closest node already
|
85
|
+
break if position < loc.start_char
|
86
|
+
|
87
|
+
# If the current node is narrower than or equal to the previous closest node, then it is more precise
|
88
|
+
closest_loc = closest.location
|
89
|
+
if loc.end_char - loc.start_char <= closest_loc.end_char - closest_loc.start_char
|
90
|
+
parent = T.let(closest, SyntaxTree::Node)
|
91
|
+
closest = candidate
|
70
92
|
end
|
71
93
|
end
|
72
94
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
95
|
+
[closest, parent]
|
96
|
+
end
|
97
|
+
|
98
|
+
sig { params(node: T.nilable(SyntaxTree::Node), range: T.nilable(T::Range[Integer])).returns(T::Boolean) }
|
99
|
+
def visible?(node, range)
|
100
|
+
return true if range.nil?
|
101
|
+
return false if node.nil?
|
102
|
+
|
103
|
+
loc = node.location
|
104
|
+
range.cover?(loc.start_line - 1) && range.cover?(loc.end_line - 1)
|
81
105
|
end
|
82
106
|
end
|
83
107
|
end
|
@@ -6,7 +6,7 @@ module RubyLsp
|
|
6
6
|
# ![Code actions demo](../../misc/code_actions.gif)
|
7
7
|
#
|
8
8
|
# The [code actions](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction)
|
9
|
-
# request informs the editor of RuboCop quick fixes that can be applied. These are
|
9
|
+
# request informs the editor of RuboCop quick fixes that can be applied. These are accessible by hovering over a
|
10
10
|
# specific diagnostic.
|
11
11
|
#
|
12
12
|
# # Example
|
@@ -35,7 +35,7 @@ module RubyLsp
|
|
35
35
|
|
36
36
|
sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::CodeAction], Object)) }
|
37
37
|
def run
|
38
|
-
diagnostics = Diagnostics.new(@uri, @document).run
|
38
|
+
diagnostics = @document.cache_fetch(:diagnostics) { Diagnostics.new(@uri, @document).run }
|
39
39
|
corrections = diagnostics.select do |diagnostic|
|
40
40
|
diagnostic.correctable? && T.cast(diagnostic, Support::RuboCopDiagnostic).in_range?(@range)
|
41
41
|
end
|
@@ -37,18 +37,11 @@ module RubyLsp
|
|
37
37
|
)
|
38
38
|
end
|
39
39
|
def run
|
40
|
-
return
|
40
|
+
return [] if @document.syntax_error?
|
41
41
|
return [] unless defined?(Support::RuboCopDiagnosticsRunner)
|
42
42
|
|
43
43
|
Support::RuboCopDiagnosticsRunner.instance.run(@uri, @document)
|
44
44
|
end
|
45
|
-
|
46
|
-
private
|
47
|
-
|
48
|
-
sig { returns(T::Array[Support::SyntaxErrorDiagnostic]) }
|
49
|
-
def syntax_error_diagnostics
|
50
|
-
@document.syntax_error_edits.map { |e| Support::SyntaxErrorDiagnostic.new(e) }
|
51
|
-
end
|
52
45
|
end
|
53
46
|
end
|
54
47
|
end
|
@@ -7,7 +7,7 @@ module RubyLsp
|
|
7
7
|
#
|
8
8
|
# The [document highlight](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentHighlight)
|
9
9
|
# informs the editor all relevant elements of the currently pointed item for highlighting. For example, when
|
10
|
-
# the cursor is on the `F` of the constant `FOO`, the editor should identify other
|
10
|
+
# the cursor is on the `F` of the constant `FOO`, the editor should identify other occurrences of `FOO`
|
11
11
|
# and highlight them.
|
12
12
|
#
|
13
13
|
# For writable elements like constants or variables, their read/write occurrences should be highlighted differently.
|
@@ -30,10 +30,9 @@ module RubyLsp
|
|
30
30
|
super(document)
|
31
31
|
|
32
32
|
@highlights = T.let([], T::Array[LanguageServer::Protocol::Interface::DocumentHighlight])
|
33
|
-
position = Document::Scanner.new(document.source).find_position(position)
|
34
|
-
|
35
33
|
return unless document.parsed?
|
36
34
|
|
35
|
+
position = document.create_scanner.find_char_position(position)
|
37
36
|
@target = T.let(find(T.must(document.tree), position), T.nilable(Support::HighlightTarget))
|
38
37
|
end
|
39
38
|
|
@@ -56,13 +55,16 @@ module RubyLsp
|
|
56
55
|
|
57
56
|
private
|
58
57
|
|
59
|
-
DIRECT_HIGHLIGHTS = T.let(
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
58
|
+
DIRECT_HIGHLIGHTS = T.let(
|
59
|
+
[
|
60
|
+
SyntaxTree::GVar,
|
61
|
+
SyntaxTree::IVar,
|
62
|
+
SyntaxTree::Const,
|
63
|
+
SyntaxTree::CVar,
|
64
|
+
SyntaxTree::VarField,
|
65
|
+
],
|
66
|
+
T::Array[T.class_of(SyntaxTree::Node)],
|
67
|
+
)
|
66
68
|
|
67
69
|
sig do
|
68
70
|
params(
|
@@ -71,9 +73,10 @@ module RubyLsp
|
|
71
73
|
).returns(T.nilable(Support::HighlightTarget))
|
72
74
|
end
|
73
75
|
def find(node, position)
|
74
|
-
matched, parent =
|
76
|
+
matched, parent = locate(node, position)
|
75
77
|
|
76
78
|
return unless matched && parent
|
79
|
+
return unless matched.is_a?(SyntaxTree::Ident) || DIRECT_HIGHLIGHTS.include?(matched.class)
|
77
80
|
|
78
81
|
case matched
|
79
82
|
when *DIRECT_HIGHLIGHTS
|
@@ -33,35 +33,39 @@ module RubyLsp
|
|
33
33
|
|
34
34
|
sig { returns(T::Hash[String, T::Hash[String, T::Hash[String, String]]]) }
|
35
35
|
def gem_paths
|
36
|
-
@gem_paths ||= T.let(
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
36
|
+
@gem_paths ||= T.let(
|
37
|
+
begin
|
38
|
+
lookup = {}
|
39
|
+
|
40
|
+
Gem::Specification.stubs.each do |stub|
|
41
|
+
spec = stub.to_spec
|
42
|
+
lookup[spec.name] = {}
|
43
|
+
lookup[spec.name][spec.version.to_s] = {}
|
44
|
+
|
45
|
+
Dir.glob("**/*.rb", base: "#{spec.full_gem_path}/").each do |path|
|
46
|
+
lookup[spec.name][spec.version.to_s][path] = "#{spec.full_gem_path}/#{path}"
|
47
|
+
end
|
46
48
|
end
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
49
|
+
|
50
|
+
Gem::Specification.default_stubs.each do |stub|
|
51
|
+
spec = stub.to_spec
|
52
|
+
lookup[spec.name] = {}
|
53
|
+
lookup[spec.name][spec.version.to_s] = {}
|
54
|
+
prefix_matchers = Regexp.union(spec.require_paths.map do |rp|
|
55
|
+
Regexp.new("^#{rp}/")
|
56
|
+
end)
|
57
|
+
prefix_matcher = Regexp.union(prefix_matchers, //)
|
58
|
+
|
59
|
+
spec.files.each do |file|
|
60
|
+
path = file.sub(prefix_matcher, "")
|
61
|
+
lookup[spec.name][spec.version.to_s][path] = "#{RbConfig::CONFIG["rubylibdir"]}/#{path}"
|
62
|
+
end
|
60
63
|
end
|
61
|
-
end
|
62
64
|
|
63
|
-
|
64
|
-
|
65
|
+
lookup
|
66
|
+
end,
|
67
|
+
T.nilable(T::Hash[String, T::Hash[String, T::Hash[String, String]]]),
|
68
|
+
)
|
65
69
|
end
|
66
70
|
end
|
67
71
|
|
@@ -10,7 +10,7 @@ module RubyLsp
|
|
10
10
|
# informs the editor of all the important symbols, such as classes, variables, and methods, defined in a file. With
|
11
11
|
# this information, the editor can populate breadcrumbs, file outline and allow for fuzzy symbol searches.
|
12
12
|
#
|
13
|
-
# In VS Code, fuzzy symbol search can be accessed by
|
13
|
+
# In VS Code, fuzzy symbol search can be accessed by opening the command palette and inserting an `@` symbol.
|
14
14
|
#
|
15
15
|
# # Example
|
16
16
|
#
|
@@ -29,34 +29,37 @@ module RubyLsp
|
|
29
29
|
class DocumentSymbol < BaseRequest
|
30
30
|
extend T::Sig
|
31
31
|
|
32
|
-
SYMBOL_KIND = T.let(
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
32
|
+
SYMBOL_KIND = T.let(
|
33
|
+
{
|
34
|
+
file: 1,
|
35
|
+
module: 2,
|
36
|
+
namespace: 3,
|
37
|
+
package: 4,
|
38
|
+
class: 5,
|
39
|
+
method: 6,
|
40
|
+
property: 7,
|
41
|
+
field: 8,
|
42
|
+
constructor: 9,
|
43
|
+
enum: 10,
|
44
|
+
interface: 11,
|
45
|
+
function: 12,
|
46
|
+
variable: 13,
|
47
|
+
constant: 14,
|
48
|
+
string: 15,
|
49
|
+
number: 16,
|
50
|
+
boolean: 17,
|
51
|
+
array: 18,
|
52
|
+
object: 19,
|
53
|
+
key: 20,
|
54
|
+
null: 21,
|
55
|
+
enummember: 22,
|
56
|
+
struct: 23,
|
57
|
+
event: 24,
|
58
|
+
operator: 25,
|
59
|
+
typeparameter: 26,
|
60
|
+
}.freeze,
|
61
|
+
T::Hash[Symbol, Integer],
|
62
|
+
)
|
60
63
|
|
61
64
|
ATTR_ACCESSORS = T.let(["attr_reader", "attr_writer", "attr_accessor"].freeze, T::Array[String])
|
62
65
|
|
@@ -92,7 +95,7 @@ module RubyLsp
|
|
92
95
|
sig { override.params(node: SyntaxTree::ClassDeclaration).void }
|
93
96
|
def visit_class(node)
|
94
97
|
symbol = create_document_symbol(
|
95
|
-
name:
|
98
|
+
name: full_constant_name(node.constant),
|
96
99
|
kind: :class,
|
97
100
|
range_node: node,
|
98
101
|
selection_range_node: node.constant,
|
@@ -129,43 +132,19 @@ module RubyLsp
|
|
129
132
|
)
|
130
133
|
end
|
131
134
|
|
132
|
-
sig { override.params(node: SyntaxTree::
|
135
|
+
sig { override.params(node: SyntaxTree::DefNode).void }
|
133
136
|
def visit_def(node)
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
)
|
142
|
-
|
143
|
-
@stack << symbol
|
144
|
-
visit(node.bodystmt)
|
145
|
-
@stack.pop
|
146
|
-
end
|
147
|
-
|
148
|
-
sig { override.params(node: SyntaxTree::DefEndless).void }
|
149
|
-
def visit_def_endless(node)
|
150
|
-
name = node.name.value
|
137
|
+
if node.target&.value&.value == "self"
|
138
|
+
name = "self.#{node.name.value}"
|
139
|
+
kind = :method
|
140
|
+
else
|
141
|
+
name = node.name.value
|
142
|
+
kind = name == "initialize" ? :constructor : :method
|
143
|
+
end
|
151
144
|
|
152
145
|
symbol = create_document_symbol(
|
153
146
|
name: name,
|
154
|
-
kind:
|
155
|
-
range_node: node,
|
156
|
-
selection_range_node: node.name,
|
157
|
-
)
|
158
|
-
|
159
|
-
@stack << symbol
|
160
|
-
visit(node.statement)
|
161
|
-
@stack.pop
|
162
|
-
end
|
163
|
-
|
164
|
-
sig { override.params(node: SyntaxTree::Defs).void }
|
165
|
-
def visit_defs(node)
|
166
|
-
symbol = create_document_symbol(
|
167
|
-
name: "self.#{node.name.value}",
|
168
|
-
kind: :method,
|
147
|
+
kind: kind,
|
169
148
|
range_node: node,
|
170
149
|
selection_range_node: node.name,
|
171
150
|
)
|
@@ -178,7 +157,7 @@ module RubyLsp
|
|
178
157
|
sig { override.params(node: SyntaxTree::ModuleDeclaration).void }
|
179
158
|
def visit_module(node)
|
180
159
|
symbol = create_document_symbol(
|
181
|
-
name:
|
160
|
+
name: full_constant_name(node.constant),
|
182
161
|
kind: :module,
|
183
162
|
range_node: node,
|
184
163
|
selection_range_node: node.constant,
|
@@ -241,25 +220,6 @@ module RubyLsp
|
|
241
220
|
|
242
221
|
symbol
|
243
222
|
end
|
244
|
-
|
245
|
-
sig { params(node: T.any(SyntaxTree::ClassDeclaration, SyntaxTree::ModuleDeclaration)).returns(String) }
|
246
|
-
def fully_qualified_name(node)
|
247
|
-
constant = T.let(node.constant, SyntaxTree::Node)
|
248
|
-
name = +node.constant.constant.value
|
249
|
-
|
250
|
-
while constant.is_a?(SyntaxTree::ConstPathRef)
|
251
|
-
constant = constant.parent
|
252
|
-
|
253
|
-
case constant
|
254
|
-
when SyntaxTree::ConstPathRef
|
255
|
-
name.prepend("#{constant.constant.value}::")
|
256
|
-
when SyntaxTree::VarRef
|
257
|
-
name.prepend("#{constant.value.value}::")
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
|
-
name
|
262
|
-
end
|
263
223
|
end
|
264
224
|
end
|
265
225
|
end
|