ruby-lsp 0.2.4 → 0.3.0

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: f263a066e68d798f4808df8b595c06345061623ab706b9f3ed8ffa0573d8ea47
4
- data.tar.gz: 46eeeb5c2938d46e24a2d0a01e5772a49c27db9f3ab4a03ad840a1059e5161c7
3
+ metadata.gz: 6e66f508823e7d8e663c90e9990ae13bf26c3480d4ce9d7d43db14185518064d
4
+ data.tar.gz: 70cfc0be15d1ad988d66a641191fa64679109ae1f6be56afdc01ded665b0a439
5
5
  SHA512:
6
- metadata.gz: e9c78db0a6b5262fc618fd1e7d4832f7a74d76130a29767a339897a06b5ff6fbc2799b328127f596d327d0adbff074e17ebacc5b88a7af97dff640220466ce1b
7
- data.tar.gz: 3ddc525a0ad7220537db5ee0a94a208182821d0f5c321f5ab3babafba97d84ee196b392cedc7c925be20df801244c519d425822941e791a73563224f0b00b6b6
6
+ metadata.gz: a2f71a448863bf25e229032cb06d3ae7e7ebf1ef50b414e6486bd902ec7f100dad1ae4cb7160ef9c5fa8069f3ff0bcbb3da29d9c16a49f2e347f8d3d8797049a
7
+ data.tar.gz: 4cd747fb8505531a1ab0f1c28c8b3a09a134d544c1834b2f821c220bce2e3b6efce325b73326e23d92891a27ae5ae3cd9a79e9bbfdfa1b45c8f6e490b74d5c9a
data/CHANGELOG.md CHANGED
@@ -6,32 +6,11 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
- ## [0.2.3]
10
-
11
- - Resolve generic source URIs for jump to gem source (https://github.com/Shopify/ruby-lsp/pull/237)
12
-
13
- ## [0.2.2]
14
-
15
- - Support document links (https://github.com/Shopify/ruby-lsp/pull/195)
16
- - Avoid returning on request blocks (https://github.com/Shopify/ruby-lsp/pull/232)
17
- - Better specify gemspec files (https://github.com/Shopify/ruby-lsp/pull/233)
18
- - Include Kernel instance methods as special methods for semantic highlighting (https://github.com/Shopify/ruby-lsp/pull/231)
19
- - Fix call processing when message is a :call symbol literal (https://github.com/Shopify/ruby-lsp/pull/236)
20
- - Alert users about non auto-correctable diagnostics (https://github.com/Shopify/ruby-lsp/pull/230)
21
- - Let clients pull diagnostics instead of pushing on edits (https://github.com/Shopify/ruby-lsp/pull/242)
22
-
23
- ## [0.2.1]
24
-
25
- - Implement the exit lifecycle request (https://github.com/Shopify/ruby-lsp/pull/198)
26
- - Remove the Sorbet runtime from the gem's default load path (https://github.com/Shopify/ruby-lsp/pull/214)
27
- - Return nil if the document is already formatted (https://github.com/Shopify/ruby-lsp/pull/216)
28
- - Handle nameless keyword rest parameters in semantic highlighting (https://github.com/Shopify/ruby-lsp/pull/222)
29
- - Display a warning on invalid RuboCop configuration (https://github.com/Shopify/ruby-lsp/pull/226)
30
- - Centralize request handling logic in server.rb (https://github.com/Shopify/ruby-lsp/pull/221)
31
- - Fix folding ranges for chained invocations involving an FCall (https://github.com/Shopify/ruby-lsp/pull/223)
32
- - Fix handling of argument fowarding in semantic highlighting (https://github.com/Shopify/ruby-lsp/pull/228)
33
- - Recover from initial syntax errors when opening documents (https://github.com/Shopify/ruby-lsp/pull/224)
34
- - Highlight occurrences and definitions in document highlight (https://github.com/Shopify/ruby-lsp/pull/187)
9
+ ## [0.3.0]
10
+ - Add on type formatting completions (https://github.com/Shopify/ruby-lsp/pull/253)
11
+ - Upgrade syntax_tree requirement to >= 3.4 (https://github.com/Shopify/ruby-lsp/pull/254)
12
+ - Show error message when there's a InfiniteCorrectionLoop exception (https://github.com/Shopify/ruby-lsp/pull/252)
13
+ - Add request cancellation (https://github.com/Shopify/ruby-lsp/pull/243)
35
14
 
36
15
  ## [0.2.0]
37
16
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.4
1
+ 0.3.0
@@ -3,7 +3,7 @@
3
3
 
4
4
  require "ruby_lsp/requests"
5
5
  require "ruby_lsp/store"
6
- require "benchmark"
6
+ require "ruby_lsp/queue"
7
7
 
8
8
  module RubyLsp
9
9
  Interface = LanguageServer::Protocol::Interface
@@ -14,11 +14,35 @@ module RubyLsp
14
14
  extend T::Sig
15
15
  VOID = T.let(Object.new.freeze, Object)
16
16
 
17
- sig { params(blk: T.proc.bind(Handler).params(arg0: T.untyped).void).void }
18
- def self.start(&blk)
19
- handler = new
20
- handler.instance_exec(&blk)
21
- handler.start
17
+ class RequestHandler < T::Struct
18
+ extend T::Sig
19
+
20
+ const :action, T.proc.params(request: T::Hash[Symbol, T.untyped]).returns(T.untyped)
21
+ const :parallel, T::Boolean
22
+ prop :error_handler,
23
+ T.nilable(T.proc.params(error: StandardError, request: T::Hash[Symbol, T.untyped]).void)
24
+
25
+ # A proc that runs in case a request has errored. Receives the error and the original request as arguments. Useful
26
+ # for displaying window messages on errors
27
+ sig do
28
+ params(
29
+ block: T.proc.bind(Handler).params(error: StandardError, request: T::Hash[Symbol, T.untyped]).void
30
+ ).void
31
+ end
32
+ def on_error(&block)
33
+ self.error_handler = block
34
+ end
35
+ end
36
+
37
+ class << self
38
+ extend T::Sig
39
+
40
+ sig { params(blk: T.proc.bind(Handler).params(arg0: T.untyped).void).void }
41
+ def start(&blk)
42
+ handler = new
43
+ handler.instance_exec(&blk)
44
+ handler.start
45
+ end
22
46
  end
23
47
 
24
48
  sig { returns(Store) }
@@ -28,61 +52,50 @@ module RubyLsp
28
52
  def initialize
29
53
  @writer = T.let(Transport::Stdio::Writer.new, Transport::Stdio::Writer)
30
54
  @reader = T.let(Transport::Stdio::Reader.new, Transport::Stdio::Reader)
31
- @handlers = T.let({}, T::Hash[String, T.proc.params(request: T::Hash[Symbol, T.untyped]).returns(T.untyped)])
55
+ @handlers = T.let({}, T::Hash[String, RequestHandler])
32
56
  @store = T.let(Store.new, Store)
57
+ @queue = T.let(Queue.new(@writer, @handlers), Queue)
33
58
  end
34
59
 
35
60
  sig { void }
36
61
  def start
37
62
  $stderr.puts "Starting Ruby LSP..."
38
- @reader.read { |request| handle(request) }
63
+
64
+ @reader.read do |request|
65
+ handler = @handlers[request[:method]]
66
+ next if handler.nil?
67
+
68
+ if handler.parallel
69
+ @queue.push(request)
70
+ else
71
+ result = @queue.execute(request)
72
+ @queue.finalize_request(result, request)
73
+ end
74
+ end
39
75
  end
40
76
 
41
77
  private
42
78
 
79
+ sig { params(id: T.any(String, Integer)).void }
80
+ def cancel_request(id)
81
+ @queue.cancel(id)
82
+ end
83
+
43
84
  sig do
44
85
  params(
45
86
  msg: String,
87
+ parallel: T::Boolean,
46
88
  blk: T.proc.bind(Handler).params(request: T::Hash[Symbol, T.untyped]).returns(T.untyped)
47
- ).void
89
+ ).returns(RequestHandler)
48
90
  end
49
- def on(msg, &blk)
50
- @handlers[msg] = blk
51
- end
52
-
53
- sig { params(request: T::Hash[Symbol, T.untyped]).void }
54
- def handle(request)
55
- result = T.let(nil, T.untyped)
56
- error = T.let(nil, T.nilable(StandardError))
57
- handler = @handlers[request[:method]]
58
-
59
- request_time = Benchmark.realtime do
60
- if handler
61
- begin
62
- result = handler.call(request)
63
- rescue StandardError => e
64
- error = e
65
- end
66
-
67
- if error
68
- @writer.write(
69
- {
70
- id: request[:id],
71
- error: { code: Constant::ErrorCodes::INTERNAL_ERROR, message: error.inspect, data: request.to_json },
72
- }
73
- )
74
- elsif result != VOID
75
- @writer.write(id: request[:id], result: result)
76
- end
77
- end
78
- end
79
-
80
- @writer.write(method: "telemetry/event", params: telemetry_params(request, request_time, error))
91
+ def on(msg, parallel: false, &blk)
92
+ @handlers[msg] = RequestHandler.new(action: blk, parallel: parallel)
81
93
  end
82
94
 
83
95
  sig { void }
84
96
  def shutdown
85
97
  $stderr.puts "Shutting down Ruby LSP..."
98
+ @queue.shutdown
86
99
  store.clear
87
100
  end
88
101
 
@@ -101,30 +114,5 @@ module RubyLsp
101
114
  params: Interface::ShowMessageParams.new(type: type, message: message)
102
115
  )
103
116
  end
104
-
105
- sig do
106
- params(
107
- request: T::Hash[Symbol, T.untyped],
108
- request_time: Float,
109
- error: T.nilable(StandardError)
110
- ).returns(T::Hash[Symbol, T.any(String, Float)])
111
- end
112
- def telemetry_params(request, request_time, error)
113
- uri = request.dig(:params, :textDocument, :uri)
114
-
115
- params = {
116
- request: request[:method],
117
- lspVersion: RubyLsp::VERSION,
118
- requestTime: request_time,
119
- }
120
-
121
- if error
122
- params[:errorClass] = error.class.name
123
- params[:errorMessage] = error.message
124
- end
125
-
126
- params[:uri] = uri.sub(%r{.*://#{Dir.home}}, "~") if uri
127
- params
128
- end
129
117
  end
130
118
  end
@@ -0,0 +1,187 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "benchmark"
5
+
6
+ module RubyLsp
7
+ class Queue
8
+ extend T::Sig
9
+
10
+ class Cancelled < Interrupt; end
11
+
12
+ class Result < T::Struct
13
+ const :response, T.untyped # rubocop:disable Sorbet/ForbidUntypedStructProps
14
+ const :error, T.nilable(StandardError)
15
+ const :request_time, T.nilable(Float)
16
+ end
17
+
18
+ class Job < T::Struct
19
+ extend T::Sig
20
+
21
+ const :request, T::Hash[Symbol, T.untyped]
22
+ prop :cancelled, T::Boolean
23
+
24
+ sig { void }
25
+ def cancel
26
+ self.cancelled = true
27
+ end
28
+ end
29
+
30
+ sig do
31
+ params(
32
+ writer: LanguageServer::Protocol::Transport::Stdio::Writer,
33
+ handlers: T::Hash[String, Handler::RequestHandler]
34
+ ).void
35
+ end
36
+ def initialize(writer, handlers)
37
+ @writer = writer
38
+ @handlers = handlers
39
+ # The job queue is the actual list of requests we have to process
40
+ @job_queue = T.let(Thread::Queue.new, Thread::Queue)
41
+ # The jobs hash is just a way of keeping a handle to jobs based on the request ID, so we can cancel them
42
+ @jobs = T.let({}, T::Hash[T.any(String, Integer), Job])
43
+ # The current job is a handle to cancel jobs that are currently being processed
44
+ @current_job = T.let(nil, T.nilable(Job))
45
+ @mutex = T.let(Mutex.new, Mutex)
46
+ @worker = T.let(new_worker, Thread)
47
+
48
+ Thread.main.priority = 1
49
+ end
50
+
51
+ sig { params(request: T::Hash[Symbol, T.untyped]).void }
52
+ def push(request)
53
+ job = Job.new(request: request, cancelled: false)
54
+
55
+ # Remember a handle to the job, so that we can cancel it
56
+ @mutex.synchronize do
57
+ @jobs[request[:id]] = job
58
+ end
59
+
60
+ @job_queue << job
61
+ end
62
+
63
+ sig { params(id: T.any(String, Integer)).void }
64
+ def cancel(id)
65
+ @mutex.synchronize do
66
+ # Cancel the job if it's still in the queue
67
+ @jobs[id]&.cancel
68
+
69
+ # Cancel the job if we're in the middle of processing it
70
+ if @current_job&.request&.dig(:id) == id
71
+ @worker.raise(Cancelled)
72
+ end
73
+ end
74
+ end
75
+
76
+ sig { void }
77
+ def shutdown
78
+ # Close the queue so that we can no longer receive items
79
+ @job_queue.close
80
+ # Clear any remaining jobs so that the thread can terminate
81
+ @job_queue.clear
82
+ # Wait until the thread is finished
83
+ @worker.join
84
+ end
85
+
86
+ # Executes a request and returns a Queue::Result. No IO should happen in this method, because it can be cancelled in
87
+ # the middle with a raise
88
+ sig { params(request: T::Hash[Symbol, T.untyped]).returns(Queue::Result) }
89
+ def execute(request)
90
+ response = T.let(nil, T.untyped)
91
+ error = T.let(nil, T.nilable(StandardError))
92
+
93
+ request_time = Benchmark.realtime do
94
+ response = T.must(@handlers[request[:method]]).action.call(request)
95
+ rescue StandardError => e
96
+ error = e
97
+ end
98
+
99
+ Queue::Result.new(response: response, error: error, request_time: request_time)
100
+ end
101
+
102
+ # Finalize a Queue::Result. All IO operations should happen here to avoid any issues with cancelling requests
103
+ sig do
104
+ params(
105
+ result: Result,
106
+ request: T::Hash[Symbol, T.untyped]
107
+ ).void
108
+ end
109
+ def finalize_request(result, request)
110
+ error = result.error
111
+ if error
112
+ T.must(@handlers[request[:method]]).error_handler&.call(error, request)
113
+
114
+ @writer.write(
115
+ id: request[:id],
116
+ error: {
117
+ code: LanguageServer::Protocol::Constant::ErrorCodes::INTERNAL_ERROR,
118
+ message: result.error.inspect,
119
+ data: request.to_json,
120
+ },
121
+ )
122
+ elsif result.response != Handler::VOID
123
+ @writer.write(id: request[:id], result: result.response)
124
+ end
125
+
126
+ request_time = result.request_time
127
+ if request_time
128
+ @writer.write(method: "telemetry/event", params: telemetry_params(request, request_time, result.error))
129
+ end
130
+ end
131
+
132
+ private
133
+
134
+ sig { returns(Thread) }
135
+ def new_worker
136
+ Thread.new do
137
+ # Thread::Queue#pop is thread safe and will wait until an item is available
138
+ while (job = T.let(@job_queue.pop, T.nilable(Job)))
139
+ # The only time when the job is nil is when the queue is closed and we can then terminate the thread
140
+
141
+ request = job.request
142
+ @mutex.synchronize do
143
+ @jobs.delete(request[:id])
144
+ @current_job = job
145
+ end
146
+
147
+ begin
148
+ next if job.cancelled
149
+
150
+ result = execute(request)
151
+ rescue Cancelled
152
+ # We need to return nil to the client even if the request was cancelled
153
+ result = Queue::Result.new(response: nil, error: nil, request_time: nil)
154
+ ensure
155
+ @mutex.synchronize { @current_job = nil }
156
+ finalize_request(result, request) unless result.nil?
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ sig do
163
+ params(
164
+ request: T::Hash[Symbol, T.untyped],
165
+ request_time: Float,
166
+ error: T.nilable(StandardError)
167
+ ).returns(T::Hash[Symbol, T.any(String, Float)])
168
+ end
169
+ def telemetry_params(request, request_time, error)
170
+ uri = request.dig(:params, :textDocument, :uri)
171
+
172
+ params = {
173
+ request: request[:method],
174
+ lspVersion: RubyLsp::VERSION,
175
+ requestTime: request_time,
176
+ }
177
+
178
+ if error
179
+ params[:errorClass] = error.class.name
180
+ params[:errorMessage] = error.message
181
+ end
182
+
183
+ params[:uri] = uri.sub(%r{.*://#{Dir.home}}, "~") if uri
184
+ params
185
+ end
186
+ end
187
+ end
@@ -105,9 +105,13 @@ module RubyLsp
105
105
  sig { returns(Integer) }
106
106
  attr_reader :end_line
107
107
 
108
- sig { params(node: SyntaxTree::Node, kind: String).returns(PartialRange) }
109
- def self.from(node, kind)
110
- new(node.location.start_line - 1, node.location.end_line - 1, kind)
108
+ class << self
109
+ extend T::Sig
110
+
111
+ sig { params(node: SyntaxTree::Node, kind: String).returns(PartialRange) }
112
+ def from(node, kind)
113
+ new(node.location.start_line - 1, node.location.end_line - 1, kind)
114
+ end
111
115
  end
112
116
 
113
117
  sig { params(start_line: Integer, end_line: Integer, kind: String).void }
@@ -0,0 +1,135 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ # ![On type formatting demo](../../misc/on_type_formatting.gif)
7
+ #
8
+ # The [on type formatting](https://microsoft.github.io/language-server-protocol/specification#textDocument_onTypeFormatting)
9
+ # request formats code as the user is typing. For example, automatically adding `end` to class definitions.
10
+ #
11
+ # # Example
12
+ #
13
+ # ```ruby
14
+ # class Foo # <-- upon adding a line break, on type formatting is triggered
15
+ # # <-- cursor ends up here
16
+ # end # <-- end is automatically added
17
+ # ```
18
+ class OnTypeFormatting < BaseRequest
19
+ extend T::Sig
20
+
21
+ END_REGEXES = T.let([
22
+ /(if|unless|for|while|class|module|until|def|case).*/,
23
+ /.*\sdo/,
24
+ ], T::Array[Regexp])
25
+
26
+ sig { params(document: Document, position: Document::PositionShape, trigger_character: String).void }
27
+ def initialize(document, position, trigger_character)
28
+ super(document)
29
+
30
+ scanner = Document::Scanner.new(document.source)
31
+ line_begin = position[:line] == 0 ? 0 : scanner.find_position({ line: position[:line] - 1, character: 0 })
32
+ line_end = scanner.find_position(position)
33
+ line = T.must(@document.source[line_begin..line_end])
34
+
35
+ @indentation = T.let(find_indentation(line), Integer)
36
+ @previous_line = T.let(line.strip.chomp, String)
37
+ @position = position
38
+ @edits = T.let([], T::Array[Interface::TextEdit])
39
+ @trigger_character = trigger_character
40
+ end
41
+
42
+ sig { override.returns(T.nilable(T.all(T::Array[Interface::TextEdit], Object))) }
43
+ def run
44
+ return unless @document.syntax_errors?
45
+
46
+ case @trigger_character
47
+ when "{"
48
+ handle_curly_brace
49
+ when "|"
50
+ handle_pipe
51
+ when "\n"
52
+ handle_statement_end
53
+ end
54
+
55
+ @edits
56
+ end
57
+
58
+ private
59
+
60
+ sig { void }
61
+ def handle_pipe
62
+ return unless /".*|/.match?(@previous_line)
63
+
64
+ add_edit_with_text("|")
65
+ move_cursor_to(@position[:line], @position[:character])
66
+ end
67
+
68
+ sig { void }
69
+ def handle_curly_brace
70
+ return unless /".*#\{/.match?(@previous_line)
71
+
72
+ add_edit_with_text("}")
73
+ move_cursor_to(@position[:line], @position[:character])
74
+ end
75
+
76
+ sig { void }
77
+ def handle_statement_end
78
+ return unless END_REGEXES.any? { |regex| regex.match?(@previous_line) }
79
+
80
+ indents = " " * @indentation
81
+
82
+ add_edit_with_text(" \n#{indents}end")
83
+ move_cursor_to(@position[:line], @indentation + 2)
84
+ end
85
+
86
+ sig { params(text: String).void }
87
+ def add_edit_with_text(text)
88
+ position = Interface::Position.new(
89
+ line: @position[:line],
90
+ character: @position[:character]
91
+ )
92
+
93
+ @edits << Interface::TextEdit.new(
94
+ range: Interface::Range.new(
95
+ start: position,
96
+ end: position
97
+ ),
98
+ new_text: text
99
+ )
100
+ end
101
+
102
+ sig { params(line: Integer, character: Integer).void }
103
+ def move_cursor_to(line, character)
104
+ position = Interface::Position.new(
105
+ line: line,
106
+ character: character
107
+ )
108
+
109
+ # The $0 is a special snippet anchor that moves the cursor to that given position. See the snippets
110
+ # documentation for more information:
111
+ # https://code.visualstudio.com/docs/editor/userdefinedsnippets#_create-your-own-snippets
112
+ @edits << Interface::TextEdit.new(
113
+ range: Interface::Range.new(
114
+ start: position,
115
+ end: position
116
+ ),
117
+ new_text: "$0"
118
+ )
119
+ end
120
+
121
+ sig { params(line: String).returns(Integer) }
122
+ def find_indentation(line)
123
+ count = 0
124
+
125
+ line.chars.each do |c|
126
+ break unless c == " "
127
+
128
+ count += 1
129
+ end
130
+
131
+ count
132
+ end
133
+ end
134
+ end
135
+ end
@@ -10,6 +10,7 @@ module RubyLsp
10
10
  # - {RubyLsp::Requests::SelectionRanges}
11
11
  # - {RubyLsp::Requests::SemanticHighlighting}
12
12
  # - {RubyLsp::Requests::Formatting}
13
+ # - {RubyLsp::Requests::OnTypeFormatting}
13
14
  # - {RubyLsp::Requests::Diagnostics}
14
15
  # - {RubyLsp::Requests::CodeActions}
15
16
  # - {RubyLsp::Requests::DocumentHighlight}
@@ -21,6 +22,7 @@ module RubyLsp
21
22
  autoload :SelectionRanges, "ruby_lsp/requests/selection_ranges"
22
23
  autoload :SemanticHighlighting, "ruby_lsp/requests/semantic_highlighting"
23
24
  autoload :Formatting, "ruby_lsp/requests/formatting"
25
+ autoload :OnTypeFormatting, "ruby_lsp/requests/on_type_formatting"
24
26
  autoload :Diagnostics, "ruby_lsp/requests/diagnostics"
25
27
  autoload :CodeActions, "ruby_lsp/requests/code_actions"
26
28
  autoload :DocumentHighlight, "ruby_lsp/requests/document_highlight"
@@ -48,6 +48,13 @@ module RubyLsp
48
48
  }
49
49
  end
50
50
 
51
+ on_type_formatting_provider = if enabled_features.include?("onTypeFormatting")
52
+ Interface::DocumentOnTypeFormattingOptions.new(
53
+ first_trigger_character: "{",
54
+ more_trigger_character: ["\n", "|"]
55
+ )
56
+ end
57
+
51
58
  # TODO: switch back to using Interface::ServerCapabilities once the gem is updated for spec 3.17
52
59
  Interface::InitializeResult.new(
53
60
  capabilities: {
@@ -63,6 +70,7 @@ module RubyLsp
63
70
  documentFormattingProvider: enabled_features.include?("formatting"),
64
71
  documentHighlightProvider: enabled_features.include?("documentHighlights"),
65
72
  codeActionProvider: enabled_features.include?("codeActions"),
73
+ documentOnTypeFormattingProvider: on_type_formatting_provider,
66
74
  diagnosticProvider: diagnostics_provider,
67
75
  }.reject { |_, v| !v }
68
76
  )
@@ -91,26 +99,26 @@ module RubyLsp
91
99
  Handler::VOID
92
100
  end
93
101
 
94
- on("textDocument/documentSymbol") do |request|
102
+ on("textDocument/documentSymbol", parallel: true) do |request|
95
103
  store.cache_fetch(request.dig(:params, :textDocument, :uri), :document_symbol) do |document|
96
104
  Requests::DocumentSymbol.new(document).run
97
105
  end
98
106
  end
99
107
 
100
- on("textDocument/documentLink") do |request|
108
+ on("textDocument/documentLink", parallel: true) do |request|
101
109
  uri = request.dig(:params, :textDocument, :uri)
102
110
  store.cache_fetch(uri, :document_link) do |document|
103
111
  RubyLsp::Requests::DocumentLink.new(uri, document).run
104
112
  end
105
113
  end
106
114
 
107
- on("textDocument/foldingRange") do |request|
115
+ on("textDocument/foldingRange", parallel: true) do |request|
108
116
  store.cache_fetch(request.dig(:params, :textDocument, :uri), :folding_ranges) do |document|
109
117
  Requests::FoldingRanges.new(document).run
110
118
  end
111
119
  end
112
120
 
113
- on("textDocument/selectionRange") do |request|
121
+ on("textDocument/selectionRange", parallel: true) do |request|
114
122
  uri = request.dig(:params, :textDocument, :uri)
115
123
  positions = request.dig(:params, :positions)
116
124
 
@@ -132,7 +140,7 @@ module RubyLsp
132
140
  end
133
141
  end
134
142
 
135
- on("textDocument/semanticTokens/full") do |request|
143
+ on("textDocument/semanticTokens/full", parallel: true) do |request|
136
144
  store.cache_fetch(request.dig(:params, :textDocument, :uri), :semantic_highlighting) do |document|
137
145
  T.cast(
138
146
  Requests::SemanticHighlighting.new(
@@ -144,13 +152,24 @@ module RubyLsp
144
152
  end
145
153
  end
146
154
 
147
- on("textDocument/formatting") do |request|
155
+ on("textDocument/formatting", parallel: true) do |request|
148
156
  uri = request.dig(:params, :textDocument, :uri)
149
157
 
150
158
  Requests::Formatting.new(uri, store.get(uri)).run
159
+ rescue RuboCop::Runner::InfiniteCorrectionLoop => e
160
+ show_message(Constant::MessageType::ERROR, "Error from RuboCop: #{e.message}")
161
+ nil
151
162
  end
152
163
 
153
- on("textDocument/documentHighlight") do |request|
164
+ on("textDocument/onTypeFormatting", parallel: true) do |request|
165
+ uri = request.dig(:params, :textDocument, :uri)
166
+ position = request.dig(:params, :position)
167
+ character = request.dig(:params, :ch)
168
+
169
+ Requests::OnTypeFormatting.new(store.get(uri), position, character).run
170
+ end
171
+
172
+ on("textDocument/documentHighlight", parallel: true) do |request|
154
173
  document = store.get(request.dig(:params, :textDocument, :uri))
155
174
 
156
175
  if document.parsed?
@@ -158,7 +177,7 @@ module RubyLsp
158
177
  end
159
178
  end
160
179
 
161
- on("textDocument/codeAction") do |request|
180
+ on("textDocument/codeAction", parallel: true) do |request|
162
181
  uri = request.dig(:params, :textDocument, :uri)
163
182
  range = request.dig(:params, :range)
164
183
  start_line = range.dig(:start, :line)
@@ -169,6 +188,11 @@ module RubyLsp
169
188
  end
170
189
  end
171
190
 
191
+ on("$/cancelRequest") do |request|
192
+ cancel_request(request[:params][:id])
193
+ Handler::VOID
194
+ end
195
+
172
196
  on("textDocument/diagnostic") do |request|
173
197
  uri = request.dig(:params, :textDocument, :uri)
174
198
  response = store.cache_fetch(uri, :diagnostics) do |document|
@@ -176,9 +200,10 @@ module RubyLsp
176
200
  end
177
201
 
178
202
  { kind: "full", items: response.map(&:to_lsp_diagnostic) } if response
179
- rescue RuboCop::ValidationError => e
180
- show_message(Constant::MessageType::ERROR, "Error in RuboCop configuration file: #{e.message}")
181
- nil
203
+ end.on_error do |error|
204
+ if error.is_a?(RuboCop::ValidationError)
205
+ show_message(Constant::MessageType::ERROR, "Error in RuboCop configuration file: #{error.message}")
206
+ end
182
207
  end
183
208
 
184
209
  on("shutdown") { shutdown }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '2.4'
47
+ version: '3.4'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '2.4'
54
+ version: '3.4'
55
55
  description: An opinionated language server for Ruby
56
56
  email:
57
57
  - ruby@shopify.com
@@ -69,6 +69,7 @@ files:
69
69
  - lib/ruby_lsp/document.rb
70
70
  - lib/ruby_lsp/handler.rb
71
71
  - lib/ruby_lsp/internal.rb
72
+ - lib/ruby_lsp/queue.rb
72
73
  - lib/ruby_lsp/requests.rb
73
74
  - lib/ruby_lsp/requests/base_request.rb
74
75
  - lib/ruby_lsp/requests/code_actions.rb
@@ -78,6 +79,7 @@ files:
78
79
  - lib/ruby_lsp/requests/document_symbol.rb
79
80
  - lib/ruby_lsp/requests/folding_ranges.rb
80
81
  - lib/ruby_lsp/requests/formatting.rb
82
+ - lib/ruby_lsp/requests/on_type_formatting.rb
81
83
  - lib/ruby_lsp/requests/selection_ranges.rb
82
84
  - lib/ruby_lsp/requests/semantic_highlighting.rb
83
85
  - lib/ruby_lsp/requests/support/highlight_target.rb