ruby-lsp 0.0.1 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +7 -16
  3. data/.github/pull_request_template.md +15 -0
  4. data/.github/workflows/ci.yml +31 -0
  5. data/.github/workflows/publish_docs.yml +32 -0
  6. data/.gitignore +9 -12
  7. data/.rubocop.yml +20 -2
  8. data/.vscode/settings.json +5 -0
  9. data/CHANGELOG.md +29 -0
  10. data/Gemfile +8 -4
  11. data/Gemfile.lock +76 -14
  12. data/README.md +69 -2
  13. data/Rakefile +5 -0
  14. data/VERSION +1 -1
  15. data/bin/tapioca +29 -0
  16. data/bin/test +7 -1
  17. data/dev.yml +7 -7
  18. data/exe/ruby-lsp +19 -2
  19. data/lib/internal.rb +7 -0
  20. data/lib/ruby-lsp.rb +4 -1
  21. data/lib/ruby_lsp/cli.rb +88 -0
  22. data/lib/ruby_lsp/document.rb +113 -0
  23. data/lib/ruby_lsp/handler.rb +236 -0
  24. data/lib/ruby_lsp/requests/base_request.rb +33 -0
  25. data/lib/ruby_lsp/requests/code_actions.rb +37 -0
  26. data/lib/ruby_lsp/requests/diagnostics.rb +37 -0
  27. data/lib/ruby_lsp/requests/document_highlight.rb +96 -0
  28. data/lib/ruby_lsp/requests/document_symbol.rb +216 -0
  29. data/lib/ruby_lsp/requests/folding_ranges.rb +213 -0
  30. data/lib/ruby_lsp/requests/formatting.rb +52 -0
  31. data/lib/ruby_lsp/requests/rubocop_request.rb +50 -0
  32. data/lib/ruby_lsp/requests/selection_ranges.rb +103 -0
  33. data/lib/ruby_lsp/requests/semantic_highlighting.rb +112 -0
  34. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +88 -0
  35. data/lib/ruby_lsp/requests/support/selection_range.rb +17 -0
  36. data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +60 -0
  37. data/lib/ruby_lsp/requests/support/syntax_error_diagnostic.rb +27 -0
  38. data/lib/ruby_lsp/requests.rb +24 -0
  39. data/lib/ruby_lsp/store.rb +59 -0
  40. data/rakelib/check_docs.rake +56 -0
  41. data/ruby-lsp.gemspec +5 -1
  42. data/{shipit.yml → shipit.production.yml} +0 -0
  43. data/sorbet/config +4 -0
  44. data/sorbet/rbi/.rubocop.yml +8 -0
  45. data/sorbet/rbi/gems/ansi@1.5.0.rbi +338 -0
  46. data/sorbet/rbi/gems/ast@2.4.2.rbi +522 -0
  47. data/sorbet/rbi/gems/builder@3.2.4.rbi +418 -0
  48. data/sorbet/rbi/gems/coderay@1.1.3.rbi +8 -0
  49. data/sorbet/rbi/gems/debug@1.5.0.rbi +1273 -0
  50. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +867 -0
  51. data/sorbet/rbi/gems/io-console@0.5.11.rbi +8 -0
  52. data/sorbet/rbi/gems/irb@1.4.1.rbi +376 -0
  53. data/sorbet/rbi/gems/language_server-protocol@3.16.0.3.rbi +7325 -0
  54. data/sorbet/rbi/gems/method_source@1.0.0.rbi +8 -0
  55. data/sorbet/rbi/gems/minitest-reporters@1.5.0.rbi +612 -0
  56. data/sorbet/rbi/gems/minitest@5.15.0.rbi +994 -0
  57. data/sorbet/rbi/gems/parallel@1.22.1.rbi +163 -0
  58. data/sorbet/rbi/gems/parser@3.1.2.0.rbi +3968 -0
  59. data/sorbet/rbi/gems/prettier_print@0.1.0.rbi +734 -0
  60. data/sorbet/rbi/gems/pry@0.14.1.rbi +8 -0
  61. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +227 -0
  62. data/sorbet/rbi/gems/rake@13.0.6.rbi +1853 -0
  63. data/sorbet/rbi/gems/rbi@0.0.14.rbi +2337 -0
  64. data/sorbet/rbi/gems/regexp_parser@2.5.0.rbi +1854 -0
  65. data/sorbet/rbi/gems/reline@0.3.1.rbi +1274 -0
  66. data/sorbet/rbi/gems/rexml@3.2.5.rbi +3852 -0
  67. data/sorbet/rbi/gems/rubocop-ast@1.18.0.rbi +4180 -0
  68. data/sorbet/rbi/gems/rubocop-minitest@0.20.0.rbi +1369 -0
  69. data/sorbet/rbi/gems/rubocop-rake@0.6.0.rbi +246 -0
  70. data/sorbet/rbi/gems/rubocop-shopify@2.6.0.rbi +8 -0
  71. data/sorbet/rbi/gems/rubocop-sorbet@0.6.8.rbi +652 -0
  72. data/sorbet/rbi/gems/rubocop@1.30.0.rbi +36729 -0
  73. data/sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi +732 -0
  74. data/sorbet/rbi/gems/spoom@1.1.11.rbi +1600 -0
  75. data/sorbet/rbi/gems/syntax_tree@2.7.1.rbi +6777 -0
  76. data/sorbet/rbi/gems/tapioca@0.8.1.rbi +1972 -0
  77. data/sorbet/rbi/gems/thor@1.2.1.rbi +2921 -0
  78. data/sorbet/rbi/gems/unicode-display_width@2.1.0.rbi +27 -0
  79. data/sorbet/rbi/gems/unparser@0.6.5.rbi +2789 -0
  80. data/sorbet/rbi/gems/webrick@1.7.0.rbi +1779 -0
  81. data/sorbet/rbi/gems/yard-sorbet@0.6.1.rbi +289 -0
  82. data/sorbet/rbi/gems/yard@0.9.27.rbi +13048 -0
  83. data/sorbet/rbi/shims/fiddle.rbi +4 -0
  84. data/sorbet/rbi/shims/hash.rbi +6 -0
  85. data/sorbet/rbi/shims/rdoc.rbi +4 -0
  86. data/sorbet/tapioca/config.yml +13 -0
  87. data/sorbet/tapioca/require.rb +7 -0
  88. metadata +119 -9
  89. data/.vscode/launch.json +0 -19
  90. data/bin/package_extension +0 -5
  91. data/bin/style +0 -10
  92. data/lib/ruby/lsp/cli.rb +0 -37
  93. data/lib/ruby/lsp.rb +0 -3
@@ -0,0 +1,216 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ # The [document
7
+ # symbol](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol) request
8
+ # informs the editor of all the important symbols, such as classes, variables, and methods, defined in a file. With
9
+ # this information, the editor can populate breadcrumbs, file outline and allow for fuzzy symbol searches.
10
+ #
11
+ # In VS Code, fuzzy symbol search can be accessed by opened the command palette and inserting an `@` symbol.
12
+ #
13
+ # # Example
14
+ #
15
+ # ```ruby
16
+ # class Person # --> document symbol: class
17
+ # attr_reader :age # --> document symbol: field
18
+ #
19
+ # def initialize
20
+ # @age = 0 # --> document symbol: variable
21
+ # end
22
+ #
23
+ # def age # --> document symbol: method
24
+ # end
25
+ # end
26
+ # ```
27
+ class DocumentSymbol < BaseRequest
28
+ SYMBOL_KIND = {
29
+ file: 1,
30
+ module: 2,
31
+ namespace: 3,
32
+ package: 4,
33
+ class: 5,
34
+ method: 6,
35
+ property: 7,
36
+ field: 8,
37
+ constructor: 9,
38
+ enum: 10,
39
+ interface: 11,
40
+ function: 12,
41
+ variable: 13,
42
+ constant: 14,
43
+ string: 15,
44
+ number: 16,
45
+ boolean: 17,
46
+ array: 18,
47
+ object: 19,
48
+ key: 20,
49
+ null: 21,
50
+ enummember: 22,
51
+ struct: 23,
52
+ event: 24,
53
+ operator: 25,
54
+ typeparameter: 26,
55
+ }.freeze
56
+
57
+ ATTR_ACCESSORS = ["attr_reader", "attr_writer", "attr_accessor"].freeze
58
+
59
+ class SymbolHierarchyRoot
60
+ attr_reader :children
61
+
62
+ def initialize
63
+ @children = []
64
+ end
65
+ end
66
+
67
+ def initialize(document)
68
+ super
69
+
70
+ @root = SymbolHierarchyRoot.new
71
+ @stack = [@root]
72
+ end
73
+
74
+ def run
75
+ visit(@document.tree)
76
+ @root.children
77
+ end
78
+
79
+ def visit_class(node)
80
+ symbol = create_document_symbol(
81
+ name: node.constant.constant.value,
82
+ kind: :class,
83
+ range_node: node,
84
+ selection_range_node: node.constant
85
+ )
86
+
87
+ @stack << symbol
88
+ visit(node.bodystmt)
89
+ @stack.pop
90
+ end
91
+
92
+ def visit_command(node)
93
+ return unless ATTR_ACCESSORS.include?(node.message.value)
94
+
95
+ node.arguments.parts.each do |argument|
96
+ next unless argument.is_a?(SyntaxTree::SymbolLiteral)
97
+
98
+ create_document_symbol(
99
+ name: argument.value.value,
100
+ kind: :field,
101
+ range_node: argument,
102
+ selection_range_node: argument.value
103
+ )
104
+ end
105
+ end
106
+
107
+ def visit_const_path_field(node)
108
+ create_document_symbol(
109
+ name: node.constant.value,
110
+ kind: :constant,
111
+ range_node: node,
112
+ selection_range_node: node.constant
113
+ )
114
+ end
115
+
116
+ def visit_def(node)
117
+ name = node.name.value
118
+
119
+ symbol = create_document_symbol(
120
+ name: name,
121
+ kind: name == "initialize" ? :constructor : :method,
122
+ range_node: node,
123
+ selection_range_node: node.name
124
+ )
125
+
126
+ @stack << symbol
127
+ visit(node.bodystmt)
128
+ @stack.pop
129
+ end
130
+
131
+ def visit_def_endless(node)
132
+ name = node.name.value
133
+
134
+ symbol = create_document_symbol(
135
+ name: name,
136
+ kind: name == "initialize" ? :constructor : :method,
137
+ range_node: node,
138
+ selection_range_node: node.name
139
+ )
140
+
141
+ @stack << symbol
142
+ visit(node.statement)
143
+ @stack.pop
144
+ end
145
+
146
+ def visit_defs(node)
147
+ symbol = create_document_symbol(
148
+ name: "self.#{node.name.value}",
149
+ kind: :method,
150
+ range_node: node,
151
+ selection_range_node: node.name
152
+ )
153
+
154
+ @stack << symbol
155
+ visit(node.bodystmt)
156
+ @stack.pop
157
+ end
158
+
159
+ def visit_module(node)
160
+ symbol = create_document_symbol(
161
+ name: node.constant.constant.value,
162
+ kind: :module,
163
+ range_node: node,
164
+ selection_range_node: node.constant
165
+ )
166
+
167
+ @stack << symbol
168
+ visit(node.bodystmt)
169
+ @stack.pop
170
+ end
171
+
172
+ def visit_top_const_field(node)
173
+ create_document_symbol(
174
+ name: node.constant.value,
175
+ kind: :constant,
176
+ range_node: node,
177
+ selection_range_node: node.constant
178
+ )
179
+ end
180
+
181
+ def visit_var_field(node)
182
+ kind = case node.value
183
+ when SyntaxTree::Const
184
+ :constant
185
+ when SyntaxTree::CVar, SyntaxTree::IVar
186
+ :variable
187
+ else
188
+ return
189
+ end
190
+
191
+ create_document_symbol(
192
+ name: node.value.value,
193
+ kind: kind,
194
+ range_node: node,
195
+ selection_range_node: node.value
196
+ )
197
+ end
198
+
199
+ private
200
+
201
+ def create_document_symbol(name:, kind:, range_node:, selection_range_node:)
202
+ symbol = LanguageServer::Protocol::Interface::DocumentSymbol.new(
203
+ name: name,
204
+ kind: SYMBOL_KIND[kind],
205
+ range: range_from_syntax_tree_node(range_node),
206
+ selection_range: range_from_syntax_tree_node(selection_range_node),
207
+ children: [],
208
+ )
209
+
210
+ @stack.last.children << symbol
211
+
212
+ symbol
213
+ end
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,213 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ # The [folding ranges](https://microsoft.github.io/language-server-protocol/specification#textDocument_foldingRange)
7
+ # request informs the editor of the ranges where code can be folded.
8
+ #
9
+ # # Example
10
+ # ```ruby
11
+ # def say_hello # <-- folding range start
12
+ # puts "Hello"
13
+ # end # <-- folding range end
14
+ # ```
15
+ class FoldingRanges < BaseRequest
16
+ SIMPLE_FOLDABLES = [
17
+ SyntaxTree::ArrayLiteral,
18
+ SyntaxTree::BraceBlock,
19
+ SyntaxTree::Case,
20
+ SyntaxTree::ClassDeclaration,
21
+ SyntaxTree::Command,
22
+ SyntaxTree::DoBlock,
23
+ SyntaxTree::FCall,
24
+ SyntaxTree::For,
25
+ SyntaxTree::HashLiteral,
26
+ SyntaxTree::Heredoc,
27
+ SyntaxTree::If,
28
+ SyntaxTree::ModuleDeclaration,
29
+ SyntaxTree::SClass,
30
+ SyntaxTree::Unless,
31
+ SyntaxTree::Until,
32
+ SyntaxTree::While,
33
+ ].freeze
34
+
35
+ NODES_WITH_STATEMENTS = [
36
+ SyntaxTree::Else,
37
+ SyntaxTree::Elsif,
38
+ SyntaxTree::Ensure,
39
+ SyntaxTree::In,
40
+ SyntaxTree::Rescue,
41
+ SyntaxTree::When,
42
+ ].freeze
43
+
44
+ def initialize(document)
45
+ super
46
+
47
+ @ranges = []
48
+ @partial_range = nil
49
+ end
50
+
51
+ def run
52
+ visit(@document.tree)
53
+ emit_partial_range
54
+ @ranges
55
+ end
56
+
57
+ private
58
+
59
+ def visit(node)
60
+ return unless handle_partial_range(node)
61
+
62
+ case node
63
+ when *SIMPLE_FOLDABLES
64
+ add_node_range(node)
65
+ when *NODES_WITH_STATEMENTS
66
+ add_statements_range(node, node.statements)
67
+ when SyntaxTree::Begin
68
+ add_statements_range(node, node.bodystmt.statements)
69
+ when SyntaxTree::Call, SyntaxTree::CommandCall
70
+ add_call_range(node)
71
+ return
72
+ when SyntaxTree::Def, SyntaxTree::Defs
73
+ add_def_range(node)
74
+ when SyntaxTree::StringConcat
75
+ add_string_concat(node)
76
+ return
77
+ end
78
+
79
+ super
80
+ end
81
+
82
+ class PartialRange
83
+ attr_reader :kind, :end_line
84
+
85
+ def self.from(node, kind)
86
+ new(node.location.start_line - 1, node.location.end_line - 1, kind)
87
+ end
88
+
89
+ def initialize(start_line, end_line, kind)
90
+ @start_line = start_line
91
+ @end_line = end_line
92
+ @kind = kind
93
+ end
94
+
95
+ def extend_to(node)
96
+ @end_line = node.location.end_line - 1
97
+ self
98
+ end
99
+
100
+ def new_section?(node)
101
+ node.is_a?(SyntaxTree::Comment) && @end_line + 1 != node.location.start_line - 1
102
+ end
103
+
104
+ def to_range
105
+ LanguageServer::Protocol::Interface::FoldingRange.new(
106
+ start_line: @start_line,
107
+ end_line: @end_line,
108
+ kind: @kind
109
+ )
110
+ end
111
+ end
112
+
113
+ def handle_partial_range(node)
114
+ kind = partial_range_kind(node)
115
+
116
+ if kind.nil?
117
+ emit_partial_range
118
+ return true
119
+ end
120
+
121
+ @partial_range = if @partial_range.nil?
122
+ PartialRange.from(node, kind)
123
+ elsif @partial_range.kind != kind || @partial_range.new_section?(node)
124
+ emit_partial_range
125
+ PartialRange.from(node, kind)
126
+ else
127
+ @partial_range.extend_to(node)
128
+ end
129
+
130
+ false
131
+ end
132
+
133
+ def partial_range_kind(node)
134
+ case node
135
+ when SyntaxTree::Comment
136
+ "comment"
137
+ when SyntaxTree::Command
138
+ if node.message.value == "require" || node.message.value == "require_relative"
139
+ "imports"
140
+ end
141
+ end
142
+ end
143
+
144
+ def emit_partial_range
145
+ return if @partial_range.nil?
146
+
147
+ @ranges << @partial_range.to_range
148
+ @partial_range = nil
149
+ end
150
+
151
+ def add_call_range(node)
152
+ receiver = T.let(node.receiver, SyntaxTree::Node)
153
+ loop do
154
+ case receiver
155
+ when SyntaxTree::Call
156
+ visit(receiver.arguments)
157
+ receiver = receiver.receiver
158
+ when SyntaxTree::MethodAddBlock
159
+ visit(receiver.block)
160
+ receiver = receiver.call.receiver
161
+ else
162
+ break
163
+ end
164
+ end
165
+
166
+ add_lines_range(receiver.location.start_line, node.location.end_line)
167
+
168
+ visit(node.arguments)
169
+ end
170
+
171
+ def add_def_range(node)
172
+ params_location = node.params.location
173
+
174
+ if params_location.start_line < params_location.end_line
175
+ add_lines_range(params_location.end_line, node.location.end_line)
176
+ else
177
+ add_node_range(node)
178
+ end
179
+
180
+ visit(node.bodystmt.statements)
181
+ end
182
+
183
+ def add_statements_range(node, statements)
184
+ add_lines_range(node.location.start_line, statements.location.end_line) unless statements.empty?
185
+ end
186
+
187
+ def add_string_concat(node)
188
+ left = T.let(node.left, SyntaxTree::Node)
189
+ left = left.left while left.is_a?(SyntaxTree::StringConcat)
190
+
191
+ add_lines_range(left.location.start_line, node.right.location.end_line)
192
+ end
193
+
194
+ def add_node_range(node)
195
+ add_location_range(node.location)
196
+ end
197
+
198
+ def add_location_range(location)
199
+ add_lines_range(location.start_line, location.end_line)
200
+ end
201
+
202
+ def add_lines_range(start_line, end_line)
203
+ return if start_line >= end_line
204
+
205
+ @ranges << LanguageServer::Protocol::Interface::FoldingRange.new(
206
+ start_line: start_line - 1,
207
+ end_line: end_line - 1,
208
+ kind: "region"
209
+ )
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,52 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ # The [formatting](https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting)
7
+ # request uses RuboCop to fix auto-correctable offenses in the document. This requires enabling format on save and
8
+ # registering the ruby-lsp as the Ruby formatter.
9
+ #
10
+ # # Example
11
+ #
12
+ # ```ruby
13
+ # def say_hello
14
+ # puts "Hello" # --> formatting: fixes the indentation on save
15
+ # end
16
+ # ```
17
+ class Formatting < RuboCopRequest
18
+ RUBOCOP_FLAGS = (COMMON_RUBOCOP_FLAGS + ["--autocorrect"]).freeze
19
+
20
+ def initialize(uri, document)
21
+ super
22
+ @formatted_text = nil
23
+ end
24
+
25
+ def run
26
+ super
27
+
28
+ @formatted_text = @options[:stdin] # Rubocop applies the corrections on stdin
29
+ return unless @formatted_text
30
+
31
+ [
32
+ LanguageServer::Protocol::Interface::TextEdit.new(
33
+ range: LanguageServer::Protocol::Interface::Range.new(
34
+ start: LanguageServer::Protocol::Interface::Position.new(line: 0, character: 0),
35
+ end: LanguageServer::Protocol::Interface::Position.new(
36
+ line: text.size,
37
+ character: text.size
38
+ )
39
+ ),
40
+ new_text: @formatted_text
41
+ ),
42
+ ]
43
+ end
44
+
45
+ private
46
+
47
+ def rubocop_flags
48
+ RUBOCOP_FLAGS
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,50 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "rubocop"
5
+ require "cgi"
6
+
7
+ module RubyLsp
8
+ module Requests
9
+ # :nodoc:
10
+ class RuboCopRequest < RuboCop::Runner
11
+ COMMON_RUBOCOP_FLAGS = [
12
+ "--stderr", # Print any output to stderr so that our stdout does not get polluted
13
+ "--format",
14
+ "RuboCop::Formatter::BaseFormatter", # Suppress any output by using the base formatter
15
+ ].freeze
16
+
17
+ attr_reader :file, :text
18
+
19
+ def self.run(uri, document)
20
+ new(uri, document).run
21
+ end
22
+
23
+ def initialize(uri, document)
24
+ @file = CGI.unescape(URI.parse(uri).path)
25
+ @document = document
26
+ @text = document.source
27
+ @uri = uri
28
+
29
+ super(
30
+ ::RuboCop::Options.new.parse(rubocop_flags).first,
31
+ ::RuboCop::ConfigStore.new
32
+ )
33
+ end
34
+
35
+ def run
36
+ # We communicate with Rubocop via stdin
37
+ @options[:stdin] = text
38
+
39
+ # Invoke the actual run method with just this file in `paths`
40
+ super([file])
41
+ end
42
+
43
+ private
44
+
45
+ def rubocop_flags
46
+ COMMON_RUBOCOP_FLAGS
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,103 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ # The [selection ranges](https://microsoft.github.io/language-server-protocol/specification#textDocument_selectionRange)
7
+ # request informs the editor of ranges that the user may want to select based on the location(s)
8
+ # of their cursor(s).
9
+ #
10
+ # Trigger this request with: Ctrl + Shift + -> or Ctrl + Shift + <-
11
+ #
12
+ # # Example
13
+ #
14
+ # ```ruby
15
+ # def foo # --> The next selection range encompasses the entire method definition.
16
+ # puts "Hello, world!" # --> Cursor is on this line
17
+ # end
18
+ # ```
19
+ class SelectionRanges < BaseRequest
20
+ NODES_THAT_CAN_BE_PARENTS = [
21
+ SyntaxTree::Assign,
22
+ SyntaxTree::ArrayLiteral,
23
+ SyntaxTree::Begin,
24
+ SyntaxTree::BraceBlock,
25
+ SyntaxTree::Call,
26
+ SyntaxTree::Case,
27
+ SyntaxTree::ClassDeclaration,
28
+ SyntaxTree::Command,
29
+ SyntaxTree::Def,
30
+ SyntaxTree::Defs,
31
+ SyntaxTree::DoBlock,
32
+ SyntaxTree::Elsif,
33
+ SyntaxTree::Else,
34
+ SyntaxTree::EmbDoc,
35
+ SyntaxTree::Ensure,
36
+ SyntaxTree::FCall,
37
+ SyntaxTree::For,
38
+ SyntaxTree::HashLiteral,
39
+ SyntaxTree::Heredoc,
40
+ SyntaxTree::HeredocBeg,
41
+ SyntaxTree::HshPtn,
42
+ SyntaxTree::If,
43
+ SyntaxTree::In,
44
+ SyntaxTree::Lambda,
45
+ SyntaxTree::MethodAddBlock,
46
+ SyntaxTree::ModuleDeclaration,
47
+ SyntaxTree::Params,
48
+ SyntaxTree::Rescue,
49
+ SyntaxTree::RescueEx,
50
+ SyntaxTree::StringConcat,
51
+ SyntaxTree::StringLiteral,
52
+ SyntaxTree::Unless,
53
+ SyntaxTree::Until,
54
+ SyntaxTree::VCall,
55
+ SyntaxTree::When,
56
+ SyntaxTree::While,
57
+ ].freeze
58
+
59
+ def initialize(document)
60
+ super(document)
61
+
62
+ @ranges = []
63
+ @stack = []
64
+ end
65
+
66
+ def run
67
+ visit(@document.tree)
68
+ @ranges.reverse!
69
+ end
70
+
71
+ private
72
+
73
+ def visit(node)
74
+ return if node.nil?
75
+
76
+ range = create_selection_range(node.location, @stack.last)
77
+
78
+ @ranges << range
79
+ return if node.child_nodes.empty?
80
+
81
+ @stack << range if NODES_THAT_CAN_BE_PARENTS.include?(node.class)
82
+ visit_all(node.child_nodes)
83
+ @stack.pop if NODES_THAT_CAN_BE_PARENTS.include?(node.class)
84
+ end
85
+
86
+ def create_selection_range(location, parent = nil)
87
+ RubyLsp::Requests::Support::SelectionRange.new(
88
+ range: LanguageServer::Protocol::Interface::Range.new(
89
+ start: LanguageServer::Protocol::Interface::Position.new(
90
+ line: location.start_line - 1,
91
+ character: location.start_column,
92
+ ),
93
+ end: LanguageServer::Protocol::Interface::Position.new(
94
+ line: location.end_line - 1,
95
+ character: location.end_column,
96
+ ),
97
+ ),
98
+ parent: parent
99
+ )
100
+ end
101
+ end
102
+ end
103
+ end