ruby-lsp 0.0.4 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +11 -1
  3. data/CHANGELOG.md +35 -0
  4. data/Gemfile +4 -3
  5. data/Gemfile.lock +26 -24
  6. data/README.md +3 -2
  7. data/Rakefile +8 -1
  8. data/VERSION +1 -1
  9. data/bin/console +19 -0
  10. data/exe/ruby-lsp +1 -3
  11. data/lib/ruby_lsp/document.rb +17 -4
  12. data/lib/ruby_lsp/handler.rb +54 -141
  13. data/lib/{internal.rb → ruby_lsp/internal.rb} +4 -2
  14. data/lib/ruby_lsp/requests/base_request.rb +9 -7
  15. data/lib/ruby_lsp/requests/code_actions.rb +20 -9
  16. data/lib/ruby_lsp/requests/diagnostics.rb +25 -8
  17. data/lib/ruby_lsp/requests/document_highlight.rb +32 -32
  18. data/lib/ruby_lsp/requests/document_symbol.rb +59 -10
  19. data/lib/ruby_lsp/requests/folding_ranges.rb +73 -34
  20. data/lib/ruby_lsp/requests/formatting.rb +25 -15
  21. data/lib/ruby_lsp/requests/selection_ranges.rb +18 -5
  22. data/lib/ruby_lsp/requests/semantic_highlighting.rb +179 -36
  23. data/lib/ruby_lsp/requests/support/highlight_target.rb +87 -0
  24. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +16 -4
  25. data/lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb +61 -0
  26. data/lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb +50 -0
  27. data/lib/ruby_lsp/requests/support/selection_range.rb +4 -1
  28. data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +13 -3
  29. data/lib/ruby_lsp/requests/support/syntax_error_diagnostic.rb +6 -1
  30. data/lib/ruby_lsp/requests.rb +13 -2
  31. data/lib/ruby_lsp/server.rb +160 -0
  32. data/lib/ruby_lsp/store.rb +17 -9
  33. data/rakelib/check_docs.rake +30 -5
  34. data/ruby-lsp.gemspec +6 -5
  35. data/sorbet/tapioca/require.rb +1 -1
  36. metadata +14 -26
  37. data/lib/ruby_lsp/cli.rb +0 -88
  38. data/lib/ruby_lsp/requests/rubocop_request.rb +0 -50
  39. data/shipit.production.yml +0 -1
@@ -1,8 +1,12 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "ruby_lsp/requests/support/rubocop_diagnostics_runner"
5
+
4
6
  module RubyLsp
5
7
  module Requests
8
+ # ![Diagnostics demo](../../misc/diagnostics.gif)
9
+ #
6
10
  # The
7
11
  # [diagnostics](https://microsoft.github.io/language-server-protocol/specification#textDocument_publishDiagnostics)
8
12
  # request informs the editor of RuboCop offenses for a given file.
@@ -14,21 +18,34 @@ module RubyLsp
14
18
  # puts "Hello" # --> diagnostics: incorrect indentantion
15
19
  # end
16
20
  # ```
17
- class Diagnostics < RuboCopRequest
18
- def run
19
- return syntax_error_diagnostics if @document.syntax_errors?
21
+ class Diagnostics < BaseRequest
22
+ extend T::Sig
20
23
 
21
- super
24
+ sig { params(uri: String, document: Document).void }
25
+ def initialize(uri, document)
26
+ super(document)
22
27
 
23
- @diagnostics
28
+ @uri = uri
24
29
  end
25
30
 
26
- def file_finished(_file, offenses)
27
- @diagnostics = offenses.map { |offense| Support::RuboCopDiagnostic.new(offense, @uri) }
31
+ sig do
32
+ override.returns(
33
+ T.any(
34
+ T.all(T::Array[Support::RuboCopDiagnostic], Object),
35
+ T.all(T::Array[Support::SyntaxErrorDiagnostic], Object),
36
+ )
37
+ )
38
+ end
39
+ def run
40
+ return syntax_error_diagnostics if @document.syntax_errors?
41
+ return [] unless defined?(Support::RuboCopDiagnosticsRunner)
42
+
43
+ Support::RuboCopDiagnosticsRunner.instance.run(@uri, @document)
28
44
  end
29
45
 
30
46
  private
31
47
 
48
+ sig { returns(T::Array[Support::SyntaxErrorDiagnostic]) }
32
49
  def syntax_error_diagnostics
33
50
  @document.syntax_error_edits.map { |e| Support::SyntaxErrorDiagnostic.new(e) }
34
51
  end
@@ -1,8 +1,10 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyLsp
5
5
  module Requests
6
+ # ![Document highlight demo](../../misc/document_highlight.gif)
7
+ #
6
8
  # The [document highlight](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentHighlight)
7
9
  # informs the editor all relevant elements of the currently pointed item for highlighting. For example, when
8
10
  # the cursor is on the `F` of the constant `FOO`, the editor should identify other occurences of `FOO`
@@ -21,18 +23,18 @@ module RubyLsp
21
23
  # end
22
24
  # ```
23
25
  class DocumentHighlight < BaseRequest
24
- def self.run(document, position)
25
- new(document, position).run
26
- end
26
+ extend T::Sig
27
27
 
28
+ sig { params(document: Document, position: Document::PositionShape).void }
28
29
  def initialize(document, position)
29
- @highlights = []
30
+ @highlights = T.let([], T::Array[LanguageServer::Protocol::Interface::DocumentHighlight])
30
31
  position = Document::Scanner.new(document.source).find_position(position)
31
- @target = find(document.tree, position)
32
+ @target = T.let(find(T.must(document.tree), position), T.nilable(Support::HighlightTarget))
32
33
 
33
34
  super(document)
34
35
  end
35
36
 
37
+ sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::DocumentHighlight], Object)) }
36
38
  def run
37
39
  # no @target means the target is not highlightable
38
40
  return [] unless @target
@@ -41,30 +43,24 @@ module RubyLsp
41
43
  @highlights
42
44
  end
43
45
 
44
- def visit_var_field(node)
45
- if matches_target?(node.value)
46
- add_highlight(
47
- node.value,
48
- LanguageServer::Protocol::Constant::DocumentHighlightKind::WRITE
49
- )
50
- end
51
-
52
- super
53
- end
46
+ sig { params(node: T.nilable(SyntaxTree::Node)).void }
47
+ def visit(node)
48
+ return if node.nil?
54
49
 
55
- def visit_var_ref(node)
56
- if matches_target?(node.value)
57
- add_highlight(
58
- node.value,
59
- LanguageServer::Protocol::Constant::DocumentHighlightKind::READ
60
- )
61
- end
50
+ match = T.must(@target).highlight_type(node)
51
+ add_highlight(match) if match
62
52
 
63
53
  super
64
54
  end
65
55
 
66
56
  private
67
57
 
58
+ sig do
59
+ params(
60
+ node: SyntaxTree::Node,
61
+ position: Integer,
62
+ ).returns(T.nilable(Support::HighlightTarget))
63
+ end
68
64
  def find(node, position)
69
65
  matched =
70
66
  node.child_nodes.compact.bsearch do |child|
@@ -76,20 +72,24 @@ module RubyLsp
76
72
  end
77
73
 
78
74
  case matched
79
- when SyntaxTree::GVar, SyntaxTree::Ident, SyntaxTree::IVar, SyntaxTree::Const, SyntaxTree::CVar
80
- matched
75
+ when SyntaxTree::GVar,
76
+ SyntaxTree::IVar,
77
+ SyntaxTree::Const,
78
+ SyntaxTree::CVar,
79
+ SyntaxTree::VarField
80
+ Support::HighlightTarget.new(matched)
81
+ when SyntaxTree::Ident
82
+ relevant_node = node.is_a?(SyntaxTree::Params) ? matched : node
83
+ Support::HighlightTarget.new(relevant_node)
81
84
  when SyntaxTree::Node
82
85
  find(matched, position)
83
86
  end
84
87
  end
85
88
 
86
- def matches_target?(node)
87
- node.is_a?(@target.class) && node.value == @target.value
88
- end
89
-
90
- def add_highlight(node, kind)
91
- range = range_from_syntax_tree_node(node)
92
- @highlights << LanguageServer::Protocol::Interface::DocumentHighlight.new(range: range, kind: kind)
89
+ sig { params(match: Support::HighlightTarget::HighlightMatch).void }
90
+ def add_highlight(match)
91
+ range = range_from_syntax_tree_node(match.node)
92
+ @highlights << LanguageServer::Protocol::Interface::DocumentHighlight.new(range: range, kind: match.type)
93
93
  end
94
94
  end
95
95
  end
@@ -1,8 +1,10 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyLsp
5
5
  module Requests
6
+ # ![Document symbol demo](../../misc/document_symbol.gif)
7
+ #
6
8
  # The [document
7
9
  # symbol](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol) request
8
10
  # informs the editor of all the important symbols, such as classes, variables, and methods, defined in a file. With
@@ -25,7 +27,9 @@ module RubyLsp
25
27
  # end
26
28
  # ```
27
29
  class DocumentSymbol < BaseRequest
28
- SYMBOL_KIND = {
30
+ extend T::Sig
31
+
32
+ SYMBOL_KIND = T.let({
29
33
  file: 1,
30
34
  module: 2,
31
35
  namespace: 3,
@@ -52,33 +56,43 @@ module RubyLsp
52
56
  event: 24,
53
57
  operator: 25,
54
58
  typeparameter: 26,
55
- }.freeze
59
+ }.freeze, T::Hash[Symbol, Integer])
56
60
 
57
- ATTR_ACCESSORS = ["attr_reader", "attr_writer", "attr_accessor"].freeze
61
+ ATTR_ACCESSORS = T.let(["attr_reader", "attr_writer", "attr_accessor"].freeze, T::Array[String])
58
62
 
59
63
  class SymbolHierarchyRoot
64
+ extend T::Sig
65
+
66
+ sig { returns(T::Array[LanguageServer::Protocol::Interface::DocumentSymbol]) }
60
67
  attr_reader :children
61
68
 
69
+ sig { void }
62
70
  def initialize
63
- @children = []
71
+ @children = T.let([], T::Array[LanguageServer::Protocol::Interface::DocumentSymbol])
64
72
  end
65
73
  end
66
74
 
75
+ sig { params(document: Document).void }
67
76
  def initialize(document)
68
77
  super
69
78
 
70
- @root = SymbolHierarchyRoot.new
71
- @stack = [@root]
79
+ @root = T.let(SymbolHierarchyRoot.new, SymbolHierarchyRoot)
80
+ @stack = T.let(
81
+ [@root],
82
+ T::Array[T.any(SymbolHierarchyRoot, LanguageServer::Protocol::Interface::DocumentSymbol)]
83
+ )
72
84
  end
73
85
 
86
+ sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::DocumentSymbol], Object)) }
74
87
  def run
75
88
  visit(@document.tree)
76
89
  @root.children
77
90
  end
78
91
 
92
+ sig { params(node: SyntaxTree::ClassDeclaration).void }
79
93
  def visit_class(node)
80
94
  symbol = create_document_symbol(
81
- name: node.constant.constant.value,
95
+ name: fully_qualified_name(node),
82
96
  kind: :class,
83
97
  range_node: node,
84
98
  selection_range_node: node.constant
@@ -89,6 +103,7 @@ module RubyLsp
89
103
  @stack.pop
90
104
  end
91
105
 
106
+ sig { params(node: SyntaxTree::Command).void }
92
107
  def visit_command(node)
93
108
  return unless ATTR_ACCESSORS.include?(node.message.value)
94
109
 
@@ -104,6 +119,7 @@ module RubyLsp
104
119
  end
105
120
  end
106
121
 
122
+ sig { params(node: SyntaxTree::ConstPathField).void }
107
123
  def visit_const_path_field(node)
108
124
  create_document_symbol(
109
125
  name: node.constant.value,
@@ -113,6 +129,7 @@ module RubyLsp
113
129
  )
114
130
  end
115
131
 
132
+ sig { params(node: SyntaxTree::Def).void }
116
133
  def visit_def(node)
117
134
  name = node.name.value
118
135
 
@@ -128,6 +145,7 @@ module RubyLsp
128
145
  @stack.pop
129
146
  end
130
147
 
148
+ sig { params(node: SyntaxTree::DefEndless).void }
131
149
  def visit_def_endless(node)
132
150
  name = node.name.value
133
151
 
@@ -143,6 +161,7 @@ module RubyLsp
143
161
  @stack.pop
144
162
  end
145
163
 
164
+ sig { params(node: SyntaxTree::Defs).void }
146
165
  def visit_defs(node)
147
166
  symbol = create_document_symbol(
148
167
  name: "self.#{node.name.value}",
@@ -156,9 +175,10 @@ module RubyLsp
156
175
  @stack.pop
157
176
  end
158
177
 
178
+ sig { params(node: SyntaxTree::ModuleDeclaration).void }
159
179
  def visit_module(node)
160
180
  symbol = create_document_symbol(
161
- name: node.constant.constant.value,
181
+ name: fully_qualified_name(node),
162
182
  kind: :module,
163
183
  range_node: node,
164
184
  selection_range_node: node.constant
@@ -169,6 +189,7 @@ module RubyLsp
169
189
  @stack.pop
170
190
  end
171
191
 
192
+ sig { params(node: SyntaxTree::TopConstField).void }
172
193
  def visit_top_const_field(node)
173
194
  create_document_symbol(
174
195
  name: node.constant.value,
@@ -178,6 +199,7 @@ module RubyLsp
178
199
  )
179
200
  end
180
201
 
202
+ sig { params(node: SyntaxTree::VarField).void }
181
203
  def visit_var_field(node)
182
204
  kind = case node.value
183
205
  when SyntaxTree::Const
@@ -198,6 +220,14 @@ module RubyLsp
198
220
 
199
221
  private
200
222
 
223
+ sig do
224
+ params(
225
+ name: String,
226
+ kind: Symbol,
227
+ range_node: SyntaxTree::Node,
228
+ selection_range_node: SyntaxTree::Node
229
+ ).returns(LanguageServer::Protocol::Interface::DocumentSymbol)
230
+ end
201
231
  def create_document_symbol(name:, kind:, range_node:, selection_range_node:)
202
232
  symbol = LanguageServer::Protocol::Interface::DocumentSymbol.new(
203
233
  name: name,
@@ -207,10 +237,29 @@ module RubyLsp
207
237
  children: [],
208
238
  )
209
239
 
210
- @stack.last.children << symbol
240
+ T.must(@stack.last).children << symbol
211
241
 
212
242
  symbol
213
243
  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
214
263
  end
215
264
  end
216
265
  end
@@ -1,19 +1,24 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyLsp
5
5
  module Requests
6
+ # ![Folding ranges demo](../../misc/folding_ranges.gif)
7
+ #
6
8
  # 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.
9
+ # request informs the editor of the ranges where and how code can be folded.
8
10
  #
9
11
  # # Example
12
+ #
10
13
  # ```ruby
11
14
  # def say_hello # <-- folding range start
12
15
  # puts "Hello"
13
16
  # end # <-- folding range end
14
17
  # ```
15
18
  class FoldingRanges < BaseRequest
16
- SIMPLE_FOLDABLES = [
19
+ extend T::Sig
20
+
21
+ SIMPLE_FOLDABLES = T.let([
17
22
  SyntaxTree::ArrayLiteral,
18
23
  SyntaxTree::BraceBlock,
19
24
  SyntaxTree::Case,
@@ -30,24 +35,36 @@ module RubyLsp
30
35
  SyntaxTree::Unless,
31
36
  SyntaxTree::Until,
32
37
  SyntaxTree::While,
33
- ].freeze
34
-
35
- NODES_WITH_STATEMENTS = [
36
38
  SyntaxTree::Else,
37
- SyntaxTree::Elsif,
38
39
  SyntaxTree::Ensure,
40
+ SyntaxTree::Begin,
41
+ ].freeze, T::Array[T.class_of(SyntaxTree::Node)])
42
+
43
+ NODES_WITH_STATEMENTS = T.let([
44
+ SyntaxTree::Elsif,
39
45
  SyntaxTree::In,
40
46
  SyntaxTree::Rescue,
41
47
  SyntaxTree::When,
42
- ].freeze
48
+ ].freeze, T::Array[T.class_of(SyntaxTree::Node)])
49
+
50
+ StatementNode = T.type_alias do
51
+ T.any(
52
+ SyntaxTree::Elsif,
53
+ SyntaxTree::In,
54
+ SyntaxTree::Rescue,
55
+ SyntaxTree::When,
56
+ )
57
+ end
43
58
 
59
+ sig { params(document: Document).void }
44
60
  def initialize(document)
45
61
  super
46
62
 
47
- @ranges = []
48
- @partial_range = nil
63
+ @ranges = T.let([], T::Array[LanguageServer::Protocol::Interface::FoldingRange])
64
+ @partial_range = T.let(nil, T.nilable(PartialRange))
49
65
  end
50
66
 
67
+ sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::FoldingRange], Object)) }
51
68
  def run
52
69
  visit(@document.tree)
53
70
  emit_partial_range
@@ -56,16 +73,16 @@ module RubyLsp
56
73
 
57
74
  private
58
75
 
76
+ sig { params(node: T.nilable(SyntaxTree::Node)).void }
59
77
  def visit(node)
60
78
  return unless handle_partial_range(node)
61
79
 
62
80
  case node
63
81
  when *SIMPLE_FOLDABLES
64
- add_node_range(node)
82
+ location = T.must(node).location
83
+ add_lines_range(location.start_line, location.end_line - 1)
65
84
  when *NODES_WITH_STATEMENTS
66
- add_statements_range(node, node.statements)
67
- when SyntaxTree::Begin
68
- add_statements_range(node, node.bodystmt.statements)
85
+ add_statements_range(T.must(node), T.cast(node, StatementNode).statements)
69
86
  when SyntaxTree::Call, SyntaxTree::CommandCall
70
87
  add_call_range(node)
71
88
  return
@@ -80,27 +97,38 @@ module RubyLsp
80
97
  end
81
98
 
82
99
  class PartialRange
83
- attr_reader :kind, :end_line
100
+ extend T::Sig
84
101
 
102
+ sig { returns(String) }
103
+ attr_reader :kind
104
+
105
+ sig { returns(Integer) }
106
+ attr_reader :end_line
107
+
108
+ sig { params(node: SyntaxTree::Node, kind: String).returns(PartialRange) }
85
109
  def self.from(node, kind)
86
110
  new(node.location.start_line - 1, node.location.end_line - 1, kind)
87
111
  end
88
112
 
113
+ sig { params(start_line: Integer, end_line: Integer, kind: String).void }
89
114
  def initialize(start_line, end_line, kind)
90
115
  @start_line = start_line
91
116
  @end_line = end_line
92
117
  @kind = kind
93
118
  end
94
119
 
120
+ sig { params(node: SyntaxTree::Node).returns(PartialRange) }
95
121
  def extend_to(node)
96
122
  @end_line = node.location.end_line - 1
97
123
  self
98
124
  end
99
125
 
126
+ sig { params(node: SyntaxTree::Node).returns(T::Boolean) }
100
127
  def new_section?(node)
101
128
  node.is_a?(SyntaxTree::Comment) && @end_line + 1 != node.location.start_line - 1
102
129
  end
103
130
 
131
+ sig { returns(LanguageServer::Protocol::Interface::FoldingRange) }
104
132
  def to_range
105
133
  LanguageServer::Protocol::Interface::FoldingRange.new(
106
134
  start_line: @start_line,
@@ -108,8 +136,14 @@ module RubyLsp
108
136
  kind: @kind
109
137
  )
110
138
  end
139
+
140
+ sig { returns(T::Boolean) }
141
+ def multiline?
142
+ @end_line > @start_line
143
+ end
111
144
  end
112
145
 
146
+ sig { params(node: T.nilable(SyntaxTree::Node)).returns(T::Boolean) }
113
147
  def handle_partial_range(node)
114
148
  kind = partial_range_kind(node)
115
149
 
@@ -118,18 +152,20 @@ module RubyLsp
118
152
  return true
119
153
  end
120
154
 
155
+ target_node = T.must(node)
121
156
  @partial_range = if @partial_range.nil?
122
- PartialRange.from(node, kind)
123
- elsif @partial_range.kind != kind || @partial_range.new_section?(node)
157
+ PartialRange.from(target_node, kind)
158
+ elsif @partial_range.kind != kind || @partial_range.new_section?(target_node)
124
159
  emit_partial_range
125
- PartialRange.from(node, kind)
160
+ PartialRange.from(target_node, kind)
126
161
  else
127
- @partial_range.extend_to(node)
162
+ @partial_range.extend_to(target_node)
128
163
  end
129
164
 
130
165
  false
131
166
  end
132
167
 
168
+ sig { params(node: T.nilable(SyntaxTree::Node)).returns(T.nilable(String)) }
133
169
  def partial_range_kind(node)
134
170
  case node
135
171
  when SyntaxTree::Comment
@@ -141,13 +177,15 @@ module RubyLsp
141
177
  end
142
178
  end
143
179
 
180
+ sig { void }
144
181
  def emit_partial_range
145
182
  return if @partial_range.nil?
146
183
 
147
- @ranges << @partial_range.to_range
184
+ @ranges << @partial_range.to_range if @partial_range.multiline?
148
185
  @partial_range = nil
149
186
  end
150
187
 
188
+ sig { params(node: T.any(SyntaxTree::Call, SyntaxTree::CommandCall)).void }
151
189
  def add_call_range(node)
152
190
  receiver = T.let(node.receiver, SyntaxTree::Node)
153
191
  loop do
@@ -157,48 +195,49 @@ module RubyLsp
157
195
  receiver = receiver.receiver
158
196
  when SyntaxTree::MethodAddBlock
159
197
  visit(receiver.block)
160
- receiver = receiver.call.receiver
198
+ receiver = receiver.call
199
+
200
+ if receiver.is_a?(SyntaxTree::Call) || receiver.is_a?(SyntaxTree::CommandCall)
201
+ receiver = receiver.receiver
202
+ end
161
203
  else
162
204
  break
163
205
  end
164
206
  end
165
207
 
166
- add_lines_range(receiver.location.start_line, node.location.end_line)
208
+ add_lines_range(receiver.location.start_line, node.location.end_line - 1)
167
209
 
168
210
  visit(node.arguments)
169
211
  end
170
212
 
213
+ sig { params(node: T.any(SyntaxTree::Def, SyntaxTree::Defs)).void }
171
214
  def add_def_range(node)
172
215
  params_location = node.params.location
173
216
 
174
217
  if params_location.start_line < params_location.end_line
175
- add_lines_range(params_location.end_line, node.location.end_line)
218
+ add_lines_range(params_location.end_line, node.location.end_line - 1)
176
219
  else
177
- add_node_range(node)
220
+ location = node.location
221
+ add_lines_range(location.start_line, location.end_line - 1)
178
222
  end
179
223
 
180
224
  visit(node.bodystmt.statements)
181
225
  end
182
226
 
227
+ sig { params(node: SyntaxTree::Node, statements: SyntaxTree::Statements).void }
183
228
  def add_statements_range(node, statements)
184
- add_lines_range(node.location.start_line, statements.location.end_line) unless statements.empty?
229
+ add_lines_range(node.location.start_line, statements.body.last.location.end_line) unless statements.empty?
185
230
  end
186
231
 
232
+ sig { params(node: SyntaxTree::StringConcat).void }
187
233
  def add_string_concat(node)
188
234
  left = T.let(node.left, SyntaxTree::Node)
189
235
  left = left.left while left.is_a?(SyntaxTree::StringConcat)
190
236
 
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)
237
+ add_lines_range(left.location.start_line, node.right.location.end_line - 1)
200
238
  end
201
239
 
240
+ sig { params(start_line: Integer, end_line: Integer).void }
202
241
  def add_lines_range(start_line, end_line)
203
242
  return if start_line >= end_line
204
243
 
@@ -1,8 +1,12 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "ruby_lsp/requests/support/rubocop_formatting_runner"
5
+
4
6
  module RubyLsp
5
7
  module Requests
8
+ # ![Formatting symbol demo](../../misc/formatting.gif)
9
+ #
6
10
  # The [formatting](https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting)
7
11
  # request uses RuboCop to fix auto-correctable offenses in the document. This requires enabling format on save and
8
12
  # registering the ruby-lsp as the Ruby formatter.
@@ -14,38 +18,44 @@ module RubyLsp
14
18
  # puts "Hello" # --> formatting: fixes the indentation on save
15
19
  # end
16
20
  # ```
17
- class Formatting < RuboCopRequest
18
- RUBOCOP_FLAGS = (COMMON_RUBOCOP_FLAGS + ["--autocorrect"]).freeze
21
+ class Formatting < BaseRequest
22
+ extend T::Sig
19
23
 
24
+ sig { params(uri: String, document: Document).void }
20
25
  def initialize(uri, document)
21
- super
22
- @formatted_text = nil
26
+ super(document)
27
+
28
+ @uri = uri
23
29
  end
24
30
 
31
+ sig { override.returns(T.nilable(T.all(T::Array[LanguageServer::Protocol::Interface::TextEdit], Object))) }
25
32
  def run
26
- super
33
+ formatted_text = formatted_file
34
+ return unless formatted_text
27
35
 
28
- @formatted_text = @options[:stdin] # Rubocop applies the corrections on stdin
29
- return unless @formatted_text
36
+ size = @document.source.size
37
+ return if formatted_text.size == size && formatted_text == @document.source
30
38
 
31
39
  [
32
40
  LanguageServer::Protocol::Interface::TextEdit.new(
33
41
  range: LanguageServer::Protocol::Interface::Range.new(
34
42
  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
- )
43
+ end: LanguageServer::Protocol::Interface::Position.new(line: size, character: size)
39
44
  ),
40
- new_text: @formatted_text
45
+ new_text: formatted_text
41
46
  ),
42
47
  ]
43
48
  end
44
49
 
45
50
  private
46
51
 
47
- def rubocop_flags
48
- RUBOCOP_FLAGS
52
+ sig { returns(T.nilable(String)) }
53
+ def formatted_file
54
+ if defined?(Support::RuboCopFormattingRunner)
55
+ Support::RuboCopFormattingRunner.instance.run(@uri, @document)
56
+ else
57
+ SyntaxTree.format(@document.source)
58
+ end
49
59
  end
50
60
  end
51
61
  end