ruby-lsp 0.4.0 → 0.4.1

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: 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