ruby-lsp 0.2.4 → 0.3.0

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