ruby-lsp 0.14.3 → 0.14.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e762c728055d0a7ee471dd6455cb0b8ed5f1a2ed1137a71c70f4238e18d7b8bd
4
- data.tar.gz: 61636e671131322ffd9792c1e23f70e54ebea9f9dd15401abac8ec0c91bf4131
3
+ metadata.gz: '046081e0caf521f948f3952bd7a356ef6f0beb80c5a9ff72930a6d0c9fd8d9f4'
4
+ data.tar.gz: e65b5224342c412a0b28198af0e56b17a421eaacc7748a17d2ed30890c0a2d86
5
5
  SHA512:
6
- metadata.gz: dd8712a89c31f1a43bed4744537a08997acf927e7dad6cd3e64aa31f8c2beb08bfdf2867b66a7c302cf11f7c803a52e2a4dfe46930bcb17f7f57c6cf196140ee
7
- data.tar.gz: 7bd900943556a4e5e54d46894ca855740de8effb0f2675ec4b01efd9a08f3b2517a516e38ab99abf3266a3984dcbcc4ec5b2de5c976a0eabd08dfdd0ce38516f
6
+ metadata.gz: 682219fbef2c6e1483c0e6b0c36bf2ce8c3bd15aed88e53ee1a05623f5569c7e52227300b1a6fb4968a6ba2b0844a7e985a0b134af9e9498036199673d660117
7
+ data.tar.gz: a13eb33a1a41514819f4d0612a5d50eb170a0adb36158b799cebffae34781a5d41c1b07922f0bec8310cf6439ea8356305224bcf1de7423b96ff6e28c9e744c8
data/LICENSE.txt CHANGED
@@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  THE SOFTWARE.
22
-
data/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ <p align="center">
2
+ <img alt="Ruby LSP logo" width="200" src="vscode/icon.png" />
3
+ </p>
4
+
1
5
  [![Build Status](https://github.com/Shopify/ruby-lsp/workflows/CI/badge.svg)](https://github.com/Shopify/ruby-lsp/actions/workflows/ci.yml)
2
6
  [![Ruby LSP extension](https://img.shields.io/badge/VS%20Code-Ruby%20LSP-success?logo=visual-studio-code)](https://marketplace.visualstudio.com/items?itemName=Shopify.ruby-lsp)
3
7
  [![Ruby DX Slack](https://img.shields.io/badge/Slack-Ruby%20DX-success?logo=slack)](https://join.slack.com/t/ruby-dx/shared_invite/zt-2c8zjlir6-uUDJl8oIwcen_FS_aA~b6Q)
@@ -11,12 +15,38 @@ experience to Ruby developers using modern standards for cross-editor features,
11
15
  Want to discuss Ruby developer experience? Consider joining the public
12
16
  [Ruby DX Slack workspace](https://join.slack.com/t/ruby-dx/shared_invite/zt-2c8zjlir6-uUDJl8oIwcen_FS_aA~b6Q).
13
17
 
18
+ ## Features
19
+
20
+ ![Ruby LSP demo](vscode/extras/ruby_lsp_demo.gif)
21
+
22
+ The Ruby LSP features include
23
+
24
+ - Semantic highlighting
25
+ - Symbol search and code outline
26
+ - RuboCop errors and warnings (diagnostics)
27
+ - Format on save (with RuboCop or Syntax Tree)
28
+ - Format on type
29
+ - Debugging support
30
+ - Running and debugging tests through VS Code's UI
31
+ - Go to definition for classes, modules, constants and required files
32
+ - Showing documentaton on hover for classes, modules and constants
33
+ - Completion for classes, modules, constants and require paths
34
+ - Fuzzy search classes, modules and constants anywhere in the project and its dependencies (workspace symbol)
35
+
36
+ Adding method support for definition, completion, hover and workspace symbol is planned, but not yet completed.
37
+
38
+ See complete information about features [here](https://shopify.github.io/ruby-lsp/RubyLsp/Requests.html).
39
+
40
+ If you experience issues, please see the [troubleshooting
41
+ guide](https://github.com/Shopify/ruby-lsp/blob/main/TROUBLESHOOTING.md).
42
+
14
43
  ## Usage
15
44
 
16
45
  ### With VS Code
17
46
 
18
- If using VS Code, all you have to do is install the [Ruby LSP extension](https://github.com/Shopify/vscode-ruby-lsp) to
19
- get the extra features in the editor. Do not install this gem manually.
47
+ If using VS Code, all you have to do is install the [Ruby LSP
48
+ extension](https://marketplace.visualstudio.com/items?itemName=Shopify.ruby-lsp) to get the extra features in the
49
+ editor. Do not install the `ruby-lsp` gem manually.
20
50
 
21
51
  ### With other editors
22
52
 
@@ -27,16 +57,8 @@ The gem can be installed by doing
27
57
  gem install ruby-lsp
28
58
  ```
29
59
 
30
- **NOTE**: starting with v0.7.0, it is no longer recommended to add the `ruby-lsp` to the bundle. The gem will generate a
31
- custom bundle in `.ruby-lsp/Gemfile` which is used to identify the versions of dependencies that should be used for the
32
- application (e.g.: the correct RuboCop version).
33
-
34
- For older versions, if you decide to add the gem to the bundle, it is not necessary to require it.
35
- ```ruby
36
- group :development do
37
- gem "ruby-lsp", require: false
38
- end
39
- ```
60
+ and the language server can be launched running `ruby-lsp` (without bundle exec in order to properly hook into your
61
+ project's dependencies).
40
62
 
41
63
  ### Documentation
42
64
 
@@ -98,16 +120,13 @@ For instructions on how to create addons, see the [addons documentation](ADDONS.
98
120
 
99
121
  ## Contributing
100
122
 
101
- Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/ruby-lsp.
102
- This project is intended to be a safe, welcoming space for collaboration, and contributors
103
- are expected to adhere to the
104
- [Contributor Covenant](CODE_OF_CONDUCT.md)
105
- code of conduct.
123
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/ruby-lsp. This project is intended to
124
+ be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor
125
+ Covenant](CODE_OF_CONDUCT.md) code of conduct.
106
126
 
107
127
  If you wish to contribute, see [CONTRIBUTING](CONTRIBUTING.md) for development instructions and check out our pinned
108
128
  [roadmap issue](https://github.com/Shopify/ruby-lsp/issues) for a list of tasks to get started.
109
129
 
110
130
  ## License
111
131
 
112
- The gem is available as open source under the terms of the
113
- [MIT License](LICENSE.txt).
132
+ The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.14.3
1
+ 0.14.5
@@ -27,6 +27,8 @@ module RubyLsp
27
27
 
28
28
  @addons = T.let([], T::Array[Addon])
29
29
  @addon_classes = T.let([], T::Array[T.class_of(Addon)])
30
+ # Addon instances that have declared a handler to accept file watcher events
31
+ @file_watcher_addons = T.let([], T::Array[Addon])
30
32
 
31
33
  class << self
32
34
  extend T::Sig
@@ -34,6 +36,9 @@ module RubyLsp
34
36
  sig { returns(T::Array[Addon]) }
35
37
  attr_accessor :addons
36
38
 
39
+ sig { returns(T::Array[Addon]) }
40
+ attr_accessor :file_watcher_addons
41
+
37
42
  sig { returns(T::Array[T.class_of(Addon)]) }
38
43
  attr_reader :addon_classes
39
44
 
@@ -57,6 +62,7 @@ module RubyLsp
57
62
 
58
63
  # Instantiate all discovered addon classes
59
64
  self.addons = addon_classes.map(&:new)
65
+ self.file_watcher_addons = addons.select { |addon| addon.respond_to?(:workspace_did_change_watched_files) }
60
66
 
61
67
  # Activate each one of the discovered addons. If any problems occur in the addons, we don't want to
62
68
  # fail to boot the server
@@ -121,24 +121,10 @@ module RubyLsp
121
121
  begin
122
122
  formatting(uri)
123
123
  rescue Requests::Formatting::InvalidFormatter => error
124
- @message_queue << Notification.new(
125
- message: "window/showMessage",
126
- params: Interface::ShowMessageParams.new(
127
- type: Constant::MessageType::ERROR,
128
- message: "Configuration error: #{error.message}",
129
- ),
130
- )
131
-
124
+ @message_queue << Notification.window_show_error("Configuration error: #{error.message}")
132
125
  nil
133
126
  rescue StandardError, LoadError => error
134
- @message_queue << Notification.new(
135
- message: "window/showMessage",
136
- params: Interface::ShowMessageParams.new(
137
- type: Constant::MessageType::ERROR,
138
- message: "Formatting error: #{error.message}",
139
- ),
140
- )
141
-
127
+ @message_queue << Notification.window_show_error("Formatting error: #{error.message}")
142
128
  nil
143
129
  end
144
130
  when "textDocument/documentHighlight"
@@ -174,14 +160,7 @@ module RubyLsp
174
160
  begin
175
161
  diagnostic(uri)
176
162
  rescue StandardError, LoadError => error
177
- @message_queue << Notification.new(
178
- message: "window/showMessage",
179
- params: Interface::ShowMessageParams.new(
180
- type: Constant::MessageType::ERROR,
181
- message: "Error running diagnostics: #{error.message}",
182
- ),
183
- )
184
-
163
+ @message_queue << Notification.window_show_error("Error running diagnostics: #{error.message}")
185
164
  nil
186
165
  end
187
166
  when "textDocument/completion"
@@ -250,6 +229,7 @@ module RubyLsp
250
229
  end
251
230
  end
252
231
 
232
+ Addon.file_watcher_addons.each { |addon| T.unsafe(addon).workspace_did_change_watched_files(changes) }
253
233
  VOID
254
234
  end
255
235
 
@@ -287,13 +267,7 @@ module RubyLsp
287
267
  false
288
268
  end
289
269
  rescue StandardError => error
290
- @message_queue << Notification.new(
291
- message: "window/showMessage",
292
- params: Interface::ShowMessageParams.new(
293
- type: Constant::MessageType::ERROR,
294
- message: "Error while indexing: #{error.message}",
295
- ),
296
- )
270
+ @message_queue << Notification.window_show_error("Error while indexing: #{error.message}")
297
271
  end
298
272
 
299
273
  # Always end the progress notification even if indexing failed or else it never goes away and the user has no
@@ -402,21 +376,11 @@ module RubyLsp
402
376
 
403
377
  case result
404
378
  when Requests::CodeActionResolve::Error::EmptySelection
405
- @message_queue << Notification.new(
406
- message: "window/showMessage",
407
- params: Interface::ShowMessageParams.new(
408
- type: Constant::MessageType::ERROR,
409
- message: "Invalid selection for Extract Variable refactor",
410
- ),
411
- )
379
+ @message_queue << Notification.window_show_error("Invalid selection for Extract Variable refactor")
412
380
  raise Requests::CodeActionResolve::CodeActionError
413
381
  when Requests::CodeActionResolve::Error::InvalidTargetRange
414
- @message_queue << Notification.new(
415
- message: "window/showMessage",
416
- params: Interface::ShowMessageParams.new(
417
- type: Constant::MessageType::ERROR,
418
- message: "Couldn't find an appropriate location to place extracted refactor",
419
- ),
382
+ @message_queue << Notification.window_show_error(
383
+ "Couldn't find an appropriate location to place extracted refactor",
420
384
  )
421
385
  raise Requests::CodeActionResolve::CodeActionError
422
386
  else
@@ -639,12 +603,8 @@ module RubyLsp
639
603
  unless defined?(RubyLsp::Requests::Support::RuboCopRunner)
640
604
  @store.formatter = "none"
641
605
 
642
- @message_queue << Notification.new(
643
- message: "window/showMessage",
644
- params: Interface::ShowMessageParams.new(
645
- type: Constant::MessageType::ERROR,
646
- message: "Ruby LSP formatter is set to `rubocop` but RuboCop was not found in the Gemfile or gemspec.",
647
- ),
606
+ @message_queue << Notification.window_show_error(
607
+ "Ruby LSP formatter is set to `rubocop` but RuboCop was not found in the Gemfile or gemspec.",
648
608
  )
649
609
  end
650
610
  end
@@ -14,30 +14,24 @@ module RubyLsp
14
14
  nesting: T::Array[String],
15
15
  typechecker_enabled: T::Boolean,
16
16
  dispatcher: Prism::Dispatcher,
17
+ uri: URI::Generic,
17
18
  ).void
18
19
  end
19
- def initialize(response_builder, index, nesting, typechecker_enabled, dispatcher)
20
+ def initialize(response_builder, index, nesting, typechecker_enabled, dispatcher, uri) # rubocop:disable Metrics/ParameterLists
20
21
  @response_builder = response_builder
21
22
  @index = index
22
23
  @nesting = nesting
23
24
  @typechecker_enabled = typechecker_enabled
25
+ @uri = uri
24
26
 
25
27
  dispatcher.register(
26
28
  self,
27
- :on_string_node_enter,
28
29
  :on_constant_path_node_enter,
29
30
  :on_constant_read_node_enter,
30
31
  :on_call_node_enter,
31
32
  )
32
33
  end
33
34
 
34
- sig { params(node: Prism::StringNode).void }
35
- def on_string_node_enter(node)
36
- @index.search_require_paths(node.content).map!(&:require_path).sort!.each do |path|
37
- @response_builder << build_completion(T.must(path), node)
38
- end
39
- end
40
-
41
35
  # Handle completion on regular constant references (e.g. `Bar`)
42
36
  sig { params(node: Prism::ConstantReadNode).void }
43
37
  def on_constant_read_node_enter(node)
@@ -106,12 +100,66 @@ module RubyLsp
106
100
 
107
101
  sig { params(node: Prism::CallNode).void }
108
102
  def on_call_node_enter(node)
109
- return if @typechecker_enabled
110
- return unless self_receiver?(node)
111
-
112
103
  name = node.message
113
104
  return unless name
114
105
 
106
+ case name
107
+ when "require"
108
+ complete_require(node)
109
+ when "require_relative"
110
+ complete_require_relative(node)
111
+ else
112
+ complete_self_receiver_method(node, name) if !@typechecker_enabled && self_receiver?(node)
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ sig { params(node: Prism::CallNode).void }
119
+ def complete_require(node)
120
+ arguments_node = node.arguments
121
+ return unless arguments_node
122
+
123
+ path_node_to_complete = arguments_node.arguments.first
124
+
125
+ return unless path_node_to_complete.is_a?(Prism::StringNode)
126
+
127
+ @index.search_require_paths(path_node_to_complete.content).map!(&:require_path).sort!.each do |path|
128
+ @response_builder << build_completion(T.must(path), path_node_to_complete)
129
+ end
130
+ end
131
+
132
+ sig { params(node: Prism::CallNode).void }
133
+ def complete_require_relative(node)
134
+ arguments_node = node.arguments
135
+ return unless arguments_node
136
+
137
+ path_node_to_complete = arguments_node.arguments.first
138
+
139
+ return unless path_node_to_complete.is_a?(Prism::StringNode)
140
+
141
+ origin_dir = Pathname.new(@uri.to_standardized_path).dirname
142
+
143
+ content = path_node_to_complete.content
144
+ # if the path is not a directory, glob all possible next characters
145
+ # for example ../somethi| (where | is the cursor position)
146
+ # should find files for ../somethi*/
147
+ path_query = if content.end_with?("/") || content.empty?
148
+ "#{content}**/*.rb"
149
+ else
150
+ "{#{content}*/**/*.rb,**/#{content}*.rb}"
151
+ end
152
+
153
+ Dir.glob(path_query, File::FNM_PATHNAME | File::FNM_EXTGLOB, base: origin_dir).sort!.each do |path|
154
+ @response_builder << build_completion(
155
+ path.delete_suffix(".rb"),
156
+ path_node_to_complete,
157
+ )
158
+ end
159
+ end
160
+
161
+ sig { params(node: Prism::CallNode, name: String).void }
162
+ def complete_self_receiver_method(node, name)
115
163
  receiver_entries = @index[@nesting.join("::")]
116
164
  return unless receiver_entries
117
165
 
@@ -125,8 +173,6 @@ module RubyLsp
125
173
  end
126
174
  end
127
175
 
128
- private
129
-
130
176
  sig do
131
177
  params(
132
178
  entry: RubyIndexer::Entry::Member,
@@ -34,7 +34,7 @@ module RubyLsp
34
34
  def provider
35
35
  Interface::CompletionOptions.new(
36
36
  resolve_provider: false,
37
- trigger_characters: ["/"],
37
+ trigger_characters: ["/", "\"", "'"],
38
38
  completion_item: {
39
39
  labelDetailsSupport: true,
40
40
  },
@@ -68,26 +68,13 @@ module RubyLsp
68
68
  ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
69
69
  )
70
70
 
71
- Listeners::Completion.new(@response_builder, index, nesting, typechecker_enabled, dispatcher)
71
+ Listeners::Completion.new(@response_builder, index, nesting, typechecker_enabled, dispatcher, document.uri)
72
72
 
73
73
  return unless matched && parent
74
74
 
75
75
  @target = case matched
76
76
  when Prism::CallNode
77
- message = matched.message
78
-
79
- if message == "require"
80
- args = matched.arguments&.arguments
81
- return if args.nil? || args.is_a?(Prism::ForwardingArgumentsNode)
82
-
83
- argument = args.first
84
- return unless argument.is_a?(Prism::StringNode)
85
- return unless (argument.location.start_offset..argument.location.end_offset).cover?(char_position)
86
-
87
- argument
88
- else
89
- matched
90
- end
77
+ matched
91
78
  when Prism::ConstantReadNode, Prism::ConstantPathNode
92
79
  if parent.is_a?(Prism::ConstantPathNode) && matched.is_a?(Prism::ConstantReadNode)
93
80
  parent
@@ -42,27 +42,61 @@ module RubyLsp
42
42
 
43
43
  sig { override.returns(T.nilable(T.all(T::Array[Interface::Diagnostic], Object))) }
44
44
  def perform
45
+ diagnostics = []
46
+ diagnostics.concat(syntax_error_diagnostics, syntax_warning_diagnostics)
47
+
45
48
  # Running RuboCop is slow, so to avoid excessive runs we only do so if the file is syntactically valid
46
- return syntax_error_diagnostics if @document.syntax_error?
47
- return [] unless defined?(Support::RuboCopDiagnosticsRunner)
49
+ return diagnostics if @document.syntax_error?
50
+
51
+ diagnostics.concat(
52
+ Support::RuboCopDiagnosticsRunner.instance.run(
53
+ @uri,
54
+ @document,
55
+ ).map!(&:to_lsp_diagnostic),
56
+ ) if defined?(Support::RuboCopDiagnosticsRunner)
48
57
 
49
- Support::RuboCopDiagnosticsRunner.instance.run(@uri, @document).map!(&:to_lsp_diagnostic)
58
+ diagnostics
50
59
  end
51
60
 
52
61
  private
53
62
 
54
- sig { returns(T.nilable(T::Array[Interface::Diagnostic])) }
63
+ sig { returns(T::Array[Interface::Diagnostic]) }
64
+ def syntax_warning_diagnostics
65
+ @document.parse_result.warnings.map do |warning|
66
+ location = warning.location
67
+
68
+ Interface::Diagnostic.new(
69
+ source: "Prism",
70
+ message: warning.message,
71
+ severity: Constant::DiagnosticSeverity::WARNING,
72
+ range: Interface::Range.new(
73
+ start: Interface::Position.new(
74
+ line: location.start_line - 1,
75
+ character: location.start_column,
76
+ ),
77
+ end: Interface::Position.new(
78
+ line: location.end_line - 1,
79
+ character: location.end_column,
80
+ ),
81
+ ),
82
+ )
83
+ end
84
+ end
85
+
86
+ sig { returns(T::Array[Interface::Diagnostic]) }
55
87
  def syntax_error_diagnostics
56
88
  @document.parse_result.errors.map do |error|
89
+ location = error.location
90
+
57
91
  Interface::Diagnostic.new(
58
92
  range: Interface::Range.new(
59
93
  start: Interface::Position.new(
60
- line: error.location.start_line - 1,
61
- character: error.location.start_column,
94
+ line: location.start_line - 1,
95
+ character: location.start_column,
62
96
  ),
63
97
  end: Interface::Position.new(
64
- line: error.location.end_line - 1,
65
- character: error.location.end_column,
98
+ line: location.end_line - 1,
99
+ character: location.end_column,
66
100
  ),
67
101
  ),
68
102
  message: error.message,
@@ -178,7 +178,7 @@ module RubyLsp
178
178
 
179
179
  sig { params(line: Integer, character: Integer).void }
180
180
  def move_cursor_to(line, character)
181
- return if @client_name != "Visual Studio Code"
181
+ return unless @client_name.start_with?("Visual Studio Code")
182
182
 
183
183
  position = Interface::Position.new(
184
184
  line: line,
@@ -40,6 +40,9 @@ module RubyLsp
40
40
 
41
41
  sig { returns(Interface::Diagnostic) }
42
42
  def to_lsp_diagnostic
43
+ # highlighted_area contains the begin and end position of the first line
44
+ # This ensures that multiline offenses don't clutter the editor
45
+ highlighted = @offense.highlighted_area
43
46
  Interface::Diagnostic.new(
44
47
  message: message,
45
48
  source: "RuboCop",
@@ -49,11 +52,11 @@ module RubyLsp
49
52
  range: Interface::Range.new(
50
53
  start: Interface::Position.new(
51
54
  line: @offense.line - 1,
52
- character: @offense.column,
55
+ character: highlighted.begin_pos,
53
56
  ),
54
57
  end: Interface::Position.new(
55
- line: @offense.last_line - 1,
56
- character: @offense.last_column,
58
+ line: @offense.line - 1,
59
+ character: highlighted.end_pos,
57
60
  ),
58
61
  ),
59
62
  data: {
@@ -13,6 +13,10 @@ rescue LoadError
13
13
  raise StandardError, "Incompatible RuboCop version. Ruby LSP requires >= 1.4.0"
14
14
  end
15
15
 
16
+ if RuboCop.const_defined?(:LSP) # This condition will be removed when requiring RuboCop >= 1.61.
17
+ RuboCop::LSP.enable
18
+ end
19
+
16
20
  module RubyLsp
17
21
  module Requests
18
22
  module Support
@@ -41,7 +41,22 @@ module RubyLsp
41
41
  end
42
42
  end
43
43
 
44
- class Notification < Message; end
44
+ class Notification < Message
45
+ class << self
46
+ extend T::Sig
47
+ sig { params(message: String).returns(Notification) }
48
+ def window_show_error(message)
49
+ new(
50
+ message: "window/showMessage",
51
+ params: Interface::ShowMessageParams.new(
52
+ type: Constant::MessageType::ERROR,
53
+ message: message,
54
+ ),
55
+ )
56
+ end
57
+ end
58
+ end
59
+
45
60
  class Request < Message; end
46
61
 
47
62
  # The final result of running a request before its IO is finalized
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.14.3
4
+ version: 0.14.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-25 00:00:00.000000000 Z
11
+ date: 2024-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol