ruby-lsp 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 65db08cd80bfe40116f70065b717ad88d1b1fd4fd86c6fcf1de2d5be9636ddce
4
- data.tar.gz: 84a483aef0e88650a0b2bdbd0db0c3a5c1cd1cfff1e87508a09d7d4d8a616f36
3
+ metadata.gz: '0812eed9a874fa78fbe75cf2e8dc2bd58c2dfb67a61353f732aaba965ae9d66f'
4
+ data.tar.gz: 6d9a567aa54c95f5fd7ada3756d4b5575f068bee90f19f99f83f485d91de83a0
5
5
  SHA512:
6
- metadata.gz: 1ff522bae2c2d469c1fb5817c1b9ed65867ad19d64e00c0b14d4faa6b38bd62321c3109292f1021174766e2da240bd17faea1f8185672164307d2dafe88845bb
7
- data.tar.gz: 7e89c4c38b833269e9d9a09f55333d32f6e1ef4872174d92e7b92a048cf51f62a837b7d9d4c217f3cccb9a196bb6d3b004439e4de8cc9e588c9b445a2f0c46e8
6
+ metadata.gz: 53311978b0c6d32a34e1e69ddeea1d4b154ee0e2bc1cd426fd7e4db6ab46eeb7a5dff7d0516b5c2c722f95f05a0c5051fe7caf874e14740b5023b5f0e79da962
7
+ data.tar.gz: ce04150602707622dd0d463a35a2a9c94fe7b24b1e376dbeeae4e45f4487438219f8ab48056658946e9f7cc56136714496fdfbddf7513b77fea334aac2687ae3
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.0
1
+ 0.4.1
@@ -21,9 +21,10 @@ module RubyLsp
21
21
  @encoding = T.let(encoding, String)
22
22
  @source = T.let(source, String)
23
23
  @unparsed_edits = T.let([], T::Array[EditShape])
24
+ @syntax_error = T.let(false, T::Boolean)
24
25
  @tree = T.let(SyntaxTree.parse(@source), T.nilable(SyntaxTree::Node))
25
26
  rescue SyntaxTree::Parser::ParseError
26
- # Do not raise if we failed to parse
27
+ @syntax_error = true
27
28
  end
28
29
 
29
30
  sig { params(other: Document).returns(T::Boolean) }
@@ -68,14 +69,15 @@ module RubyLsp
68
69
  return if @unparsed_edits.empty?
69
70
 
70
71
  @tree = SyntaxTree.parse(@source)
72
+ @syntax_error = false
71
73
  @unparsed_edits.clear
72
74
  rescue SyntaxTree::Parser::ParseError
73
- # Do nothing if we fail parse
75
+ @syntax_error = true
74
76
  end
75
77
 
76
78
  sig { returns(T::Boolean) }
77
79
  def syntax_error?
78
- @unparsed_edits.any?
80
+ @syntax_error
79
81
  end
80
82
 
81
83
  sig { returns(T::Boolean) }
@@ -37,6 +37,9 @@ module RubyLsp
37
37
  case request[:method]
38
38
  when "initialize"
39
39
  initialize_request(request.dig(:params))
40
+ when "initialized"
41
+ warn("Ruby LSP is ready")
42
+ VOID
40
43
  when "textDocument/didOpen"
41
44
  text_document_did_open(uri, request.dig(:params, :textDocument, :text))
42
45
  when "textDocument/didClose"
@@ -84,6 +87,8 @@ module RubyLsp
84
87
  inlay_hint(uri, request.dig(:params, :range))
85
88
  when "textDocument/codeAction"
86
89
  code_action(uri, request.dig(:params, :range), request.dig(:params, :context))
90
+ when "codeAction/resolve"
91
+ code_action_resolve(request.dig(:params))
87
92
  when "textDocument/diagnostic"
88
93
  begin
89
94
  diagnostic(uri)
@@ -233,11 +238,30 @@ module RubyLsp
233
238
  ).returns(T.nilable(T::Array[Interface::CodeAction]))
234
239
  end
235
240
  def code_action(uri, range, context)
236
- start_line = range.dig(:start, :line)
237
- end_line = range.dig(:end, :line)
238
241
  document = @store.get(uri)
239
242
 
240
- Requests::CodeActions.new(uri, document, start_line..end_line, context).run
243
+ Requests::CodeActions.new(uri, document, range, context).run
244
+ end
245
+
246
+ sig { params(params: T::Hash[Symbol, T.untyped]).returns(Interface::CodeAction) }
247
+ def code_action_resolve(params)
248
+ uri = params.dig(:data, :uri)
249
+ document = @store.get(uri)
250
+ result = Requests::CodeActionResolve.new(document, params).run
251
+
252
+ case result
253
+ when Requests::CodeActionResolve::Error::EmptySelection
254
+ @notifications << Notification.new(
255
+ message: "window/showMessage",
256
+ params: Interface::ShowMessageParams.new(
257
+ type: Constant::MessageType::ERROR,
258
+ message: "Invalid selection for Extract Variable refactor",
259
+ ),
260
+ )
261
+ raise Requests::CodeActionResolve::CodeActionError
262
+ else
263
+ result
264
+ end
241
265
  end
242
266
 
243
267
  sig { params(uri: String).returns(T.nilable(Interface::FullDocumentDiagnosticReport)) }
@@ -325,6 +349,10 @@ module RubyLsp
325
349
  )
326
350
  end
327
351
 
352
+ code_action_provider = if enabled_features.include?("codeActions")
353
+ Interface::CodeActionOptions.new(resolve_provider: true)
354
+ end
355
+
328
356
  inlay_hint_provider = if enabled_features.include?("inlayHint")
329
357
  Interface::InlayHintOptions.new(resolve_provider: false)
330
358
  end
@@ -350,7 +378,7 @@ module RubyLsp
350
378
  semantic_tokens_provider: semantic_tokens_provider,
351
379
  document_formatting_provider: enabled_features.include?("formatting"),
352
380
  document_highlight_provider: enabled_features.include?("documentHighlights"),
353
- code_action_provider: enabled_features.include?("codeActions"),
381
+ code_action_provider: code_action_provider,
354
382
  document_on_type_formatting_provider: on_type_formatting_provider,
355
383
  diagnostic_provider: diagnostics_provider,
356
384
  inlay_hint_provider: inlay_hint_provider,
@@ -0,0 +1,100 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ # ![Code action resolve demo](../../misc/code_action_resolve.gif)
7
+ #
8
+ # The [code action resolve](https://microsoft.github.io/language-server-protocol/specification#codeAction_resolve)
9
+ # request is used to to resolve the edit field for a given code action, if it is not already provided in the
10
+ # textDocument/codeAction response. We can use it for scenarios that require more computation such as refactoring.
11
+ #
12
+ # # Example: Extract to variable
13
+ #
14
+ # ```ruby
15
+ # # Before:
16
+ # 1 + 1 # Select the text and use Refactor: Extract Variable
17
+ #
18
+ # # After:
19
+ # new_variable = 1 + 1
20
+ # new_variable
21
+ #
22
+ # ```
23
+ #
24
+ class CodeActionResolve < BaseRequest
25
+ extend T::Sig
26
+ NEW_VARIABLE_NAME = "new_variable"
27
+
28
+ class CodeActionError < StandardError; end
29
+
30
+ class Error < ::T::Enum
31
+ enums do
32
+ EmptySelection = new
33
+ end
34
+ end
35
+
36
+ sig { params(document: Document, code_action: T::Hash[Symbol, T.untyped]).void }
37
+ def initialize(document, code_action)
38
+ super(document)
39
+
40
+ @code_action = code_action
41
+ end
42
+
43
+ sig { override.returns(T.any(Interface::CodeAction, Error)) }
44
+ def run
45
+ source_range = @code_action.dig(:data, :range)
46
+ return Error::EmptySelection if source_range[:start] == source_range[:end]
47
+
48
+ scanner = @document.create_scanner
49
+ start_index = scanner.find_char_position(source_range[:start])
50
+ end_index = scanner.find_char_position(source_range[:end])
51
+ extraction_source = T.must(@document.source[start_index...end_index])
52
+ source_line_indentation = T.must(T.must(@document.source.lines[source_range.dig(:start, :line)])[/\A */]).size
53
+
54
+ Interface::CodeAction.new(
55
+ title: "Refactor: Extract Variable",
56
+ edit: Interface::WorkspaceEdit.new(
57
+ document_changes: [
58
+ Interface::TextDocumentEdit.new(
59
+ text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
60
+ uri: @code_action.dig(:data, :uri),
61
+ version: nil,
62
+ ),
63
+ edits: edits_to_extract_variable(source_range, extraction_source, source_line_indentation),
64
+ ),
65
+ ],
66
+ ),
67
+ )
68
+ end
69
+
70
+ private
71
+
72
+ sig do
73
+ params(range: Document::RangeShape, source: String, indentation: Integer)
74
+ .returns(T::Array[Interface::TextEdit])
75
+ end
76
+ def edits_to_extract_variable(range, source, indentation)
77
+ target_range = {
78
+ start: { line: range.dig(:start, :line), character: indentation },
79
+ end: { line: range.dig(:start, :line), character: indentation },
80
+ }
81
+
82
+ [
83
+ create_text_edit(range, NEW_VARIABLE_NAME),
84
+ create_text_edit(target_range, "#{NEW_VARIABLE_NAME} = #{source}\n#{" " * indentation}"),
85
+ ]
86
+ end
87
+
88
+ sig { params(range: Document::RangeShape, new_text: String).returns(Interface::TextEdit) }
89
+ def create_text_edit(range, new_text)
90
+ Interface::TextEdit.new(
91
+ range: Interface::Range.new(
92
+ start: Interface::Position.new(line: range.dig(:start, :line), character: range.dig(:start, :character)),
93
+ end: Interface::Position.new(line: range.dig(:end, :line), character: range.dig(:end, :character)),
94
+ ),
95
+ new_text: new_text,
96
+ )
97
+ end
98
+ end
99
+ end
100
+ end
@@ -23,7 +23,7 @@ module RubyLsp
23
23
  params(
24
24
  uri: String,
25
25
  document: Document,
26
- range: T::Range[Integer],
26
+ range: Document::RangeShape,
27
27
  context: T::Hash[Symbol, T.untyped],
28
28
  ).void
29
29
  end
@@ -38,9 +38,8 @@ module RubyLsp
38
38
  sig { override.returns(T.nilable(T.all(T::Array[Interface::CodeAction], Object))) }
39
39
  def run
40
40
  diagnostics = @context[:diagnostics]
41
- return if diagnostics.nil? || diagnostics.empty?
42
41
 
43
- diagnostics.filter_map do |diagnostic|
42
+ code_actions = diagnostics.filter_map do |diagnostic|
44
43
  code_action = diagnostic.dig(:data, :code_action)
45
44
  next if code_action.nil?
46
45
 
@@ -49,13 +48,30 @@ module RubyLsp
49
48
  range = code_action.dig(:edit, :documentChanges, 0, :edits, 0, :range)
50
49
  code_action if diagnostic.dig(:data, :correctable) && cover?(range)
51
50
  end
51
+
52
+ code_actions << refactor_code_action(@range, @uri)
52
53
  end
53
54
 
54
55
  private
55
56
 
56
57
  sig { params(range: T.nilable(Document::RangeShape)).returns(T::Boolean) }
57
58
  def cover?(range)
58
- range.nil? || @range.cover?(range.dig(:start, :line)..range.dig(:end, :line))
59
+ range.nil? ||
60
+ ((@range.dig(:start, :line))..(@range.dig(:end, :line))).cover?(
61
+ (range.dig(:start, :line))..(range.dig(:end, :line)),
62
+ )
63
+ end
64
+
65
+ sig { params(range: Document::RangeShape, uri: String).returns(Interface::CodeAction) }
66
+ def refactor_code_action(range, uri)
67
+ Interface::CodeAction.new(
68
+ title: "Refactor: Extract Variable",
69
+ kind: Constant::CodeActionKind::REFACTOR_EXTRACT,
70
+ data: {
71
+ range: range,
72
+ uri: uri,
73
+ },
74
+ )
59
75
  end
60
76
  end
61
77
  end
@@ -37,6 +37,8 @@ module RubyLsp
37
37
  # Don't try to format files outside the current working directory
38
38
  return unless @uri.sub("file://", "").start_with?(Dir.pwd)
39
39
 
40
+ return if @document.syntax_error?
41
+
40
42
  formatted_text = formatted_file
41
43
  return unless formatted_text
42
44
 
@@ -21,17 +21,20 @@ module RubyLsp
21
21
  super(document)
22
22
 
23
23
  @tree = T.let(Support::PrefixTree.new(collect_load_path_files), Support::PrefixTree)
24
-
25
- char_position = document.create_scanner.find_char_position(position)
26
- @target = T.let(find(char_position), T.nilable(SyntaxTree::TStringContent))
24
+ @position = position
27
25
  end
28
26
 
29
- sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::CompletionItem], Object)) }
27
+ sig { override.returns(T.all(T::Array[Interface::CompletionItem], Object)) }
30
28
  def run
31
- # no @target means the we are not inside a `require` call
32
- return [] unless @target
29
+ # We can't verify if we're inside a require when there are syntax errors
30
+ return [] if @document.syntax_error?
31
+
32
+ char_position = @document.create_scanner.find_char_position(@position)
33
+ target = T.let(find(char_position), T.nilable(SyntaxTree::TStringContent))
34
+ # no target means the we are not inside a `require` call
35
+ return [] unless target
33
36
 
34
- text = @target.value
37
+ text = target.value
35
38
  @tree.search(text).sort.map! do |path|
36
39
  build_completion(path, path.delete_prefix(text))
37
40
  end
@@ -73,14 +76,18 @@ module RubyLsp
73
76
  end
74
77
  end
75
78
 
76
- sig do
77
- params(label: String, insert_text: String).returns(LanguageServer::Protocol::Interface::CompletionItem)
78
- end
79
+ sig { params(label: String, insert_text: String).returns(Interface::CompletionItem) }
79
80
  def build_completion(label, insert_text)
80
- LanguageServer::Protocol::Interface::CompletionItem.new(
81
+ Interface::CompletionItem.new(
81
82
  label: label,
82
- insert_text: insert_text,
83
- kind: LanguageServer::Protocol::Constant::CompletionItemKind::REFERENCE,
83
+ text_edit: Interface::TextEdit.new(
84
+ range: Interface::Range.new(
85
+ start: @position,
86
+ end: @position,
87
+ ),
88
+ new_text: insert_text,
89
+ ),
90
+ kind: Constant::CompletionItemKind::REFERENCE,
84
91
  )
85
92
  end
86
93
  end
@@ -14,6 +14,7 @@ module RubyLsp
14
14
  # - {RubyLsp::Requests::OnTypeFormatting}
15
15
  # - {RubyLsp::Requests::Diagnostics}
16
16
  # - {RubyLsp::Requests::CodeActions}
17
+ # - {RubyLsp::Requests::CodeActionResolve}
17
18
  # - {RubyLsp::Requests::DocumentHighlight}
18
19
  # - {RubyLsp::Requests::InlayHints}
19
20
  # - {RubyLsp::Requests::PathCompletion}
@@ -30,6 +31,7 @@ module RubyLsp
30
31
  autoload :OnTypeFormatting, "ruby_lsp/requests/on_type_formatting"
31
32
  autoload :Diagnostics, "ruby_lsp/requests/diagnostics"
32
33
  autoload :CodeActions, "ruby_lsp/requests/code_actions"
34
+ autoload :CodeActionResolve, "ruby_lsp/requests/code_action_resolve"
33
35
  autoload :DocumentHighlight, "ruby_lsp/requests/document_highlight"
34
36
  autoload :InlayHints, "ruby_lsp/requests/inlay_hints"
35
37
  autoload :PathCompletion, "ruby_lsp/requests/path_completion"
@@ -33,7 +33,7 @@ module RubyLsp
33
33
  # fall under the else branch which just pushes requests to the queue
34
34
  @reader.read do |request|
35
35
  case request[:method]
36
- when "initialize", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
36
+ when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
37
37
  result = Executor.new(@store).execute(request)
38
38
  finalize_request(result, request)
39
39
  when "$/cancelRequest"
@@ -56,6 +56,7 @@ module RubyLsp
56
56
  # We return zero if shutdown has already been received or one otherwise as per the recommendation in the spec
57
57
  # https://microsoft.github.io/language-server-protocol/specification/#exit
58
58
  status = @store.empty? ? 0 : 1
59
+ warn("Shutdown complete with status #{status}")
59
60
  exit(status)
60
61
  else
61
62
  # Default case: push the request to the queue to be executed by the worker
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.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-02-15 00:00:00.000000000 Z
11
+ date: 2023-02-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -76,6 +76,7 @@ files:
76
76
  - lib/ruby_lsp/internal.rb
77
77
  - lib/ruby_lsp/requests.rb
78
78
  - lib/ruby_lsp/requests/base_request.rb
79
+ - lib/ruby_lsp/requests/code_action_resolve.rb
79
80
  - lib/ruby_lsp/requests/code_actions.rb
80
81
  - lib/ruby_lsp/requests/diagnostics.rb
81
82
  - lib/ruby_lsp/requests/document_highlight.rb