ruby-lsp 0.4.2 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +29 -35
  3. data/VERSION +1 -1
  4. data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +62 -0
  5. data/lib/ruby_lsp/document.rb +74 -6
  6. data/lib/ruby_lsp/event_emitter.rb +52 -0
  7. data/lib/ruby_lsp/executor.rb +60 -14
  8. data/lib/ruby_lsp/internal.rb +2 -0
  9. data/lib/ruby_lsp/listener.rb +39 -0
  10. data/lib/ruby_lsp/requests/base_request.rb +2 -85
  11. data/lib/ruby_lsp/requests/code_action_resolve.rb +46 -19
  12. data/lib/ruby_lsp/requests/code_actions.rb +5 -4
  13. data/lib/ruby_lsp/requests/code_lens.rb +135 -0
  14. data/lib/ruby_lsp/requests/diagnostics.rb +3 -3
  15. data/lib/ruby_lsp/requests/document_highlight.rb +7 -9
  16. data/lib/ruby_lsp/requests/document_link.rb +7 -7
  17. data/lib/ruby_lsp/requests/document_symbol.rb +13 -10
  18. data/lib/ruby_lsp/requests/folding_ranges.rb +13 -9
  19. data/lib/ruby_lsp/requests/formatting.rb +5 -4
  20. data/lib/ruby_lsp/requests/hover.rb +28 -32
  21. data/lib/ruby_lsp/requests/inlay_hints.rb +5 -4
  22. data/lib/ruby_lsp/requests/path_completion.rb +16 -10
  23. data/lib/ruby_lsp/requests/selection_ranges.rb +3 -3
  24. data/lib/ruby_lsp/requests/semantic_highlighting.rb +29 -15
  25. data/lib/ruby_lsp/requests/support/common.rb +55 -0
  26. data/lib/ruby_lsp/requests/support/highlight_target.rb +5 -4
  27. data/lib/ruby_lsp/requests/support/rails_document_client.rb +7 -6
  28. data/lib/ruby_lsp/requests/support/selection_range.rb +1 -1
  29. data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +2 -2
  30. data/lib/ruby_lsp/requests/support/sorbet.rb +5 -15
  31. data/lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb +39 -0
  32. data/lib/ruby_lsp/requests.rb +3 -0
  33. data/lib/ruby_lsp/server.rb +4 -1
  34. data/lib/ruby_lsp/store.rb +10 -10
  35. metadata +11 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c5d9c6b487aab55ce25eb2741fff92afa8ca0ee544f21374debacce7f887cc50
4
- data.tar.gz: 8da9da85900753306f5e8558bc1a42f25b7173ffe1087ea70612c10ef5558e27
3
+ metadata.gz: 61c8fe2a55057823533a8a17b4472309e3d463ec3d63302fa3393f046ec4b339
4
+ data.tar.gz: fb9636b8aafac9d5002a872d0f34f7f9852e9348009cdc2934369637a4cadb4c
5
5
  SHA512:
6
- metadata.gz: 3dca630b9cae262f1af8d0aef0300ab7399dafd348db5f578ed7fdffb22a7afd48fe9467aa65e467fb50e16663f8fad32f1d0cc46b30819b06a6b7472700dcc0
7
- data.tar.gz: f048c6c9fe14d20edefeca7ac504942af34c61699163f0d09ec54f4d80e7469e1bb1762757b96aa3d8d06a6e8168473dacfc932112b2d31ca4b380748eef8899
6
+ metadata.gz: db51fd5526431b773125c0b27bfe8ca53b2861759457f9a36e7a681bcfbffb0f58026b4f8cc4dc991be2380309959aacde7c8cc2cd0a0f0373dfb313d0d28db9
7
+ data.tar.gz: 13cc3991bffd1a18dd1e2580c8d1051161b03a171a25e8ce681f46fb8d5baedb1de0f9f96304556e08f3db95c2262e29693d22bf3659ba45eff620157adc035c
data/README.md CHANGED
@@ -1,57 +1,51 @@
1
- ![Build Status](https://github.com/Shopify/ruby-lsp/workflows/CI/badge.svg)
1
+ [![Build Status](https://github.com/Shopify/ruby-lsp/workflows/CI/badge.svg)](https://github.com/Shopify/ruby-lsp/actions/workflows/ci.yml)
2
+ [![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
+ [![Ruby DX Slack](https://img.shields.io/badge/Slack-Ruby%20DX-success?logo=slack)](https://join.slack.com/t/ruby-dx/shared_invite/zt-1s6f4y15t-v9jedZ9YUPQLM91TEJ4Gew)
2
4
 
3
- # Ruby LSP
4
-
5
- This gem is an implementation of the [language server protocol specification](https://microsoft.github.io/language-server-protocol/) for Ruby, used to improve editor features.
6
-
7
- # Overview
8
-
9
- The intention of Ruby LSP is to provide a fast, robust and feature-rich coding environment for Ruby developers.
10
-
11
- It's part of a [wider Shopify goal](https://github.com/Shopify/vscode-shopify-ruby) to provide a state-of-the-art experience to Ruby developers using modern standards for cross-editor features, documentation and debugging.
12
5
 
13
- It provides many features, including:
14
-
15
- * Syntax highlighting
16
- * Linting and formatting
17
- * Code folding
18
- * Selection ranges
19
-
20
- It does not perform typechecking, so its features are implemented on a best-effort basis, aiming to be as accurate as possible.
21
-
22
- Planned future features include:
6
+ # Ruby LSP
23
7
 
24
- * Auto-completion and navigation ("Go To Definition") ([prototype](https://github.com/Shopify/ruby-lsp/pull/429))
25
- * Support for plug-ins to extend behavior
8
+ The Ruby LSP is an implementation of the [language server protocol](https://microsoft.github.io/language-server-protocol/)
9
+ for Ruby, used to improve rich features in editors. It is a part of a wider goal to provide a state-of-the-art
10
+ experience to Ruby developers using modern standards for cross-editor features, documentation and debugging.
26
11
 
27
- The Ruby LSP does not perform any type-checking or provide any type-related assistance, but it can be used alongside [Sorbet](https://github.com/sorbet/sorbet)'s LSP server.
12
+ Want to discuss Ruby developer experience? Consider joining the public
13
+ [Ruby DX Slack workspace](https://join.slack.com/t/ruby-dx/shared_invite/zt-1s6f4y15t-v9jedZ9YUPQLM91TEJ4Gew).
28
14
 
29
- At the time of writing, these are the major differences between Ruby LSP and [Solargraph](https://solargraph.org/):
15
+ ## Usage
30
16
 
31
- * Solargraph [uses](https://solargraph.org/guides/yard) YARD documentation to gather information about your project and its gem dependencies. This provides functionality such as context-aware auto-completion and navigation ("Go To Definition")
32
- * Solargraph can be used as a globally installed gem, but Ruby LSP must be added to the Gemfile or gemspec if using RuboCop. (There are pros and cons to each approach)
17
+ ### With VS Code
33
18
 
34
- ## Learn More
19
+ If using VS Code, all you have to do is install the [Ruby LSP extension](https://github.com/Shopify/vscode-ruby-lsp) to
20
+ get the extra features in the editor. Do not install this gem manually.
35
21
 
36
- * [RubyConf 2022: Improving the development experience with language servers](https://www.youtube.com/watch?v=kEfXPTm1aCI) ([Vinicius Stock](https://github.com/vinistock))
22
+ ### With other editors
37
23
 
38
- ## Usage
24
+ See [editors](https://github.com/Shopify/ruby-lsp/blob/main/EDITORS.md) for community instructions on setting up the
25
+ Ruby LSP.
39
26
 
40
- Install the gem. There's no need to require it, since the server is used as a standalone executable.
27
+ The gem can be installed by doing
28
+ ```shell
29
+ gem install ruby-lsp
30
+ ```
41
31
 
32
+ If you decide to add the gem to the bundle, it is not necessary to require it.
42
33
  ```ruby
43
34
  group :development do
44
35
  gem "ruby-lsp", require: false
45
36
  end
46
37
  ```
47
38
 
48
- If using VS Code, install the [Ruby LSP extension](https://github.com/Shopify/vscode-ruby-lsp) to get the extra features
49
- in the editor. See [editors](https://github.com/Shopify/ruby-lsp/blob/main/EDITORS.md) for community instructions on
50
- setting up the Ruby LSP in different editors.
39
+ ### Documentation
51
40
 
52
41
  See the [documentation](https://shopify.github.io/ruby-lsp) for more in-depth details about the
53
42
  [supported features](https://shopify.github.io/ruby-lsp/RubyLsp/Requests.html).
54
43
 
44
+ ## Learn More
45
+
46
+ * [RubyConf 2022: Improving the development experience with language servers](https://www.youtube.com/watch?v=kEfXPTm1aCI) ([Vinicius Stock](https://github.com/vinistock))
47
+ * [Remote Ruby: Ruby Language Server with Vinicius Stock](https://remoteruby.com/221)
48
+
55
49
  ## Contributing
56
50
 
57
51
  Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/ruby-lsp.
@@ -113,7 +107,7 @@ To add a new expectations test runner for a new request handler:
113
107
  * Tests with expectations will be checked with `assert_expectations`
114
108
  * Tests without expectations will be ran against your new $HANDLER to check that nothing breaks
115
109
 
116
- ## Debugging
110
+ ### Debugging
117
111
 
118
112
  ### Debugging Tests
119
113
 
@@ -135,7 +129,7 @@ To add a new expectations test runner for a new request handler:
135
129
  7. [`ruby-lsp`] Use commands like `b <file>:<line>` or `b Class#method` to set breakpoints and type `c` to continue the process.
136
130
  8. In your `Extension Development Host` project (e.g. [`Tapioca`](https://github.com/Shopify/tapioca)), trigger the request that will hit the breakpoint.
137
131
 
138
- ## Spell Checking
132
+ ### Spell Checking
139
133
 
140
134
  VS Code users will be prompted to enable the [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) extension.
141
135
  By default this will be enabled for all workspaces, but you can choose to selectively enable or disable it per workspace.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.2
1
+ 0.4.4
@@ -0,0 +1,62 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "rubocop"
5
+ require "sorbet-runtime"
6
+
7
+ module RuboCop
8
+ module Cop
9
+ module RubyLsp
10
+ # Prefer using `Interface`, `Transport` and `Constant` aliases
11
+ # within the `RubyLsp` module, without having to prefix with
12
+ # `LanguageServer::Protocol`
13
+ #
14
+ # @example
15
+ # # bad
16
+ # module RubyLsp
17
+ # class FoldingRanges
18
+ # sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::FoldingRange], Object)) }
19
+ # def run; end
20
+ # end
21
+ #
22
+ # # good
23
+ # module RubyLsp
24
+ # class FoldingRanges
25
+ # sig { override.returns(T.all(T::Array[Interface::FoldingRange], Object)) }
26
+ # def run; end
27
+ # end
28
+ # end
29
+ class UseLanguageServerAliases < RuboCop::Cop::Base
30
+ extend RuboCop::Cop::AutoCorrector
31
+
32
+ ALIASED_CONSTANTS = T.let([:Interface, :Transport, :Constant].freeze, T::Array[Symbol])
33
+
34
+ MSG = "Use constant alias `%{constant}`."
35
+
36
+ def_node_search :ruby_lsp_modules, <<~PATTERN
37
+ (module (const nil? :RubyLsp) ...)
38
+ PATTERN
39
+
40
+ def_node_search :lsp_constant_usages, <<~PATTERN
41
+ (const (const (const nil? :LanguageServer) :Protocol) {:Interface | :Transport | :Constant})
42
+ PATTERN
43
+
44
+ def on_new_investigation
45
+ return if processed_source.blank?
46
+
47
+ ruby_lsp_modules(processed_source.ast).each do |ruby_lsp_mod|
48
+ lsp_constant_usages(ruby_lsp_mod).each do |node|
49
+ lsp_const = node.children.last
50
+
51
+ next unless ALIASED_CONSTANTS.include?(lsp_const)
52
+
53
+ add_offense(node, message: format(MSG, constant: lsp_const)) do |corrector|
54
+ corrector.replace(node, lsp_const)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -15,11 +15,19 @@ module RubyLsp
15
15
  sig { returns(String) }
16
16
  attr_reader :source
17
17
 
18
- sig { params(source: String, encoding: String).void }
19
- def initialize(source, encoding = "utf-8")
18
+ sig { returns(Integer) }
19
+ attr_reader :version
20
+
21
+ sig { returns(String) }
22
+ attr_reader :uri
23
+
24
+ sig { params(source: String, version: Integer, uri: String, encoding: String).void }
25
+ def initialize(source:, version:, uri:, encoding: Constant::PositionEncodingKind::UTF8)
20
26
  @cache = T.let({}, T::Hash[Symbol, T.untyped])
21
27
  @encoding = T.let(encoding, String)
22
28
  @source = T.let(source, String)
29
+ @version = T.let(version, Integer)
30
+ @uri = T.let(uri, String)
23
31
  @unparsed_edits = T.let([], T::Array[EditShape])
24
32
  @syntax_error = T.let(false, T::Boolean)
25
33
  @tree = T.let(SyntaxTree.parse(@source), T.nilable(SyntaxTree::Node))
@@ -48,8 +56,8 @@ module RubyLsp
48
56
  result
49
57
  end
50
58
 
51
- sig { params(edits: T::Array[EditShape]).void }
52
- def push_edits(edits)
59
+ sig { params(edits: T::Array[EditShape], version: Integer).void }
60
+ def push_edits(edits, version:)
53
61
  edits.each do |edit|
54
62
  range = edit[:range]
55
63
  scanner = create_scanner
@@ -60,6 +68,7 @@ module RubyLsp
60
68
  @source[start_position...end_position] = edit[:text]
61
69
  end
62
70
 
71
+ @version = version
63
72
  @unparsed_edits.concat(edits)
64
73
  @cache.clear
65
74
  end
@@ -68,9 +77,9 @@ module RubyLsp
68
77
  def parse
69
78
  return if @unparsed_edits.empty?
70
79
 
80
+ @unparsed_edits.clear
71
81
  @tree = SyntaxTree.parse(@source)
72
82
  @syntax_error = false
73
- @unparsed_edits.clear
74
83
  rescue SyntaxTree::Parser::ParseError
75
84
  @syntax_error = true
76
85
  end
@@ -90,6 +99,61 @@ module RubyLsp
90
99
  Scanner.new(@source, @encoding)
91
100
  end
92
101
 
102
+ sig do
103
+ params(
104
+ position: PositionShape,
105
+ node_types: T::Array[T.class_of(SyntaxTree::Node)],
106
+ ).returns([T.nilable(SyntaxTree::Node), T.nilable(SyntaxTree::Node)])
107
+ end
108
+ def locate_node(position, node_types: [])
109
+ return [nil, nil] unless parsed?
110
+
111
+ locate(T.must(@tree), create_scanner.find_char_position(position))
112
+ end
113
+
114
+ sig do
115
+ params(
116
+ node: SyntaxTree::Node,
117
+ char_position: Integer,
118
+ node_types: T::Array[T.class_of(SyntaxTree::Node)],
119
+ ).returns([T.nilable(SyntaxTree::Node), T.nilable(SyntaxTree::Node)])
120
+ end
121
+ def locate(node, char_position, node_types: [])
122
+ queue = T.let(node.child_nodes.compact, T::Array[T.nilable(SyntaxTree::Node)])
123
+ closest = node
124
+ parent = T.let(nil, T.nilable(SyntaxTree::Node))
125
+
126
+ until queue.empty?
127
+ candidate = queue.shift
128
+
129
+ # Skip nil child nodes
130
+ next if candidate.nil?
131
+
132
+ # Add the next child_nodes to the queue to be processed
133
+ queue.concat(candidate.child_nodes)
134
+
135
+ # Skip if the current node doesn't cover the desired position
136
+ loc = candidate.location
137
+ next unless (loc.start_char...loc.end_char).cover?(char_position)
138
+
139
+ # If the node's start character is already past the position, then we should've found the closest node
140
+ # already
141
+ break if char_position < loc.start_char
142
+
143
+ # If there are node types to filter by, and the current node is not one of those types, then skip it
144
+ next if node_types.any? && node_types.none? { |type| candidate.class == type }
145
+
146
+ # If the current node is narrower than or equal to the previous closest node, then it is more precise
147
+ closest_loc = closest.location
148
+ if loc.end_char - loc.start_char <= closest_loc.end_char - closest_loc.start_char
149
+ parent = closest
150
+ closest = candidate
151
+ end
152
+ end
153
+
154
+ [closest, parent]
155
+ end
156
+
93
157
  class Scanner
94
158
  extend T::Sig
95
159
 
@@ -118,7 +182,11 @@ module RubyLsp
118
182
  # The final position is the beginning of the line plus the requested column. If the encoding is UTF-16, we also
119
183
  # need to adjust for surrogate pairs
120
184
  requested_position = @pos + position[:character]
121
- requested_position -= utf_16_character_position_correction(@pos, requested_position) if @encoding == "utf-16"
185
+
186
+ if @encoding == Constant::PositionEncodingKind::UTF16
187
+ requested_position -= utf_16_character_position_correction(@pos, requested_position)
188
+ end
189
+
122
190
  requested_position
123
191
  end
124
192
 
@@ -0,0 +1,52 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ # EventEmitter is an intermediary between our requests and Syntax Tree visitors. It's used to visit the document's AST
6
+ # and emit events that the requests can listen to for providing functionality. Usages:
7
+ #
8
+ # - For positional requests, locate the target node and use `emit_for_target` to fire events for each listener
9
+ # - For nonpositional requests, use `visit` to go through the AST, which will fire events for each listener as nodes
10
+ # are found
11
+ #
12
+ # = Example
13
+ #
14
+ # ```ruby
15
+ # target_node = document.locate_node(position)
16
+ # listener = Requests::Hover.new
17
+ # EventEmitter.new(listener).emit_for_target(target_node)
18
+ # listener.response
19
+ # ```
20
+ class EventEmitter < SyntaxTree::Visitor
21
+ extend T::Sig
22
+
23
+ sig { params(listeners: Listener[T.untyped]).void }
24
+ def initialize(*listeners)
25
+ @listeners = listeners
26
+
27
+ # Create a map of event name to listeners that have registered it, so that we avoid unnecessary invocations
28
+ @event_to_listener_map = T.let(
29
+ listeners.each_with_object(Hash.new { |h, k| h[k] = [] }) do |listener, hash|
30
+ listener.class.events&.each { |event| hash[event] << listener }
31
+ end,
32
+ T::Hash[Symbol, T::Array[Listener[T.untyped]]],
33
+ )
34
+
35
+ super()
36
+ end
37
+
38
+ # Emit events for a specific node. This is similar to the regular `visit` method, but avoids going deeper into the
39
+ # tree for performance
40
+ sig { params(node: T.nilable(SyntaxTree::Node)).void }
41
+ def emit_for_target(node)
42
+ case node
43
+ when SyntaxTree::Command
44
+ @event_to_listener_map[:on_command]&.each { |listener| T.unsafe(listener).on_command(node) }
45
+ when SyntaxTree::CallNode
46
+ @event_to_listener_map[:on_call]&.each { |listener| T.unsafe(listener).on_call(node) }
47
+ when SyntaxTree::ConstPathRef
48
+ @event_to_listener_map[:on_const_path_ref]&.each { |listener| T.unsafe(listener).on_const_path_ref(node) }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -41,7 +41,11 @@ module RubyLsp
41
41
  warn("Ruby LSP is ready")
42
42
  VOID
43
43
  when "textDocument/didOpen"
44
- text_document_did_open(uri, request.dig(:params, :textDocument, :text))
44
+ text_document_did_open(
45
+ uri,
46
+ request.dig(:params, :textDocument, :text),
47
+ request.dig(:params, :textDocument, :version),
48
+ )
45
49
  when "textDocument/didClose"
46
50
  @notifications << Notification.new(
47
51
  message: "textDocument/publishDiagnostics",
@@ -50,7 +54,11 @@ module RubyLsp
50
54
 
51
55
  text_document_did_close(uri)
52
56
  when "textDocument/didChange"
53
- text_document_did_change(uri, request.dig(:params, :contentChanges))
57
+ text_document_did_change(
58
+ uri,
59
+ request.dig(:params, :contentChanges),
60
+ request.dig(:params, :textDocument, :version),
61
+ )
54
62
  when "textDocument/foldingRange"
55
63
  folding_range(uri)
56
64
  when "textDocument/documentLink"
@@ -115,6 +123,8 @@ module RubyLsp
115
123
  end
116
124
  when "textDocument/completion"
117
125
  completion(uri, request.dig(:params, :position))
126
+ when "textDocument/codeLens"
127
+ code_lens(uri)
118
128
  end
119
129
  end
120
130
 
@@ -125,6 +135,13 @@ module RubyLsp
125
135
  end
126
136
  end
127
137
 
138
+ sig { params(uri: String).returns(T::Array[Interface::CodeLens]) }
139
+ def code_lens(uri)
140
+ @store.cache_fetch(uri, :code_lens) do |document|
141
+ Requests::CodeLens.new(document).run
142
+ end
143
+ end
144
+
128
145
  sig do
129
146
  params(
130
147
  uri: String,
@@ -132,13 +149,26 @@ module RubyLsp
132
149
  ).returns(T.nilable(Interface::Hover))
133
150
  end
134
151
  def hover(uri, position)
135
- RubyLsp::Requests::Hover.new(@store.get(uri), position).run
152
+ document = @store.get(uri)
153
+ document.parse
154
+ return if document.syntax_error?
155
+
156
+ target, parent = document.locate_node(position)
157
+
158
+ if !Requests::Hover::ALLOWED_TARGETS.include?(target.class) &&
159
+ Requests::Hover::ALLOWED_TARGETS.include?(parent.class)
160
+ target = parent
161
+ end
162
+
163
+ listener = RubyLsp::Requests::Hover.new
164
+ EventEmitter.new(listener).emit_for_target(target)
165
+ listener.response
136
166
  end
137
167
 
138
168
  sig { params(uri: String).returns(T::Array[Interface::DocumentLink]) }
139
169
  def document_link(uri)
140
170
  @store.cache_fetch(uri, :document_link) do |document|
141
- RubyLsp::Requests::DocumentLink.new(uri, document).run
171
+ RubyLsp::Requests::DocumentLink.new(document).run
142
172
  end
143
173
  end
144
174
 
@@ -149,15 +179,15 @@ module RubyLsp
149
179
  end
150
180
  end
151
181
 
152
- sig { params(uri: String, content_changes: T::Array[Document::EditShape]).returns(Object) }
153
- def text_document_did_change(uri, content_changes)
154
- @store.push_edits(uri, content_changes)
182
+ sig { params(uri: String, content_changes: T::Array[Document::EditShape], version: Integer).returns(Object) }
183
+ def text_document_did_change(uri, content_changes, version)
184
+ @store.push_edits(uri: uri, edits: content_changes, version: version)
155
185
  VOID
156
186
  end
157
187
 
158
- sig { params(uri: String, text: String).returns(Object) }
159
- def text_document_did_open(uri, text)
160
- @store.set(uri, text)
188
+ sig { params(uri: String, text: String, version: Integer).returns(Object) }
189
+ def text_document_did_open(uri, text, version)
190
+ @store.set(uri: uri, source: text, version: version)
161
191
  VOID
162
192
  end
163
193
 
@@ -207,7 +237,7 @@ module RubyLsp
207
237
 
208
238
  sig { params(uri: String).returns(T.nilable(T::Array[Interface::TextEdit])) }
209
239
  def formatting(uri)
210
- Requests::Formatting.new(uri, @store.get(uri), formatter: @store.formatter).run
240
+ Requests::Formatting.new(@store.get(uri), formatter: @store.formatter).run
211
241
  end
212
242
 
213
243
  sig do
@@ -250,7 +280,7 @@ module RubyLsp
250
280
  def code_action(uri, range, context)
251
281
  document = @store.get(uri)
252
282
 
253
- Requests::CodeActions.new(uri, document, range, context).run
283
+ Requests::CodeActions.new(document, range, context).run
254
284
  end
255
285
 
256
286
  sig { params(params: T::Hash[Symbol, T.untyped]).returns(Interface::CodeAction) }
@@ -286,7 +316,7 @@ module RubyLsp
286
316
  sig { params(uri: String).returns(T.nilable(Interface::FullDocumentDiagnosticReport)) }
287
317
  def diagnostic(uri)
288
318
  response = @store.cache_fetch(uri, :diagnostics) do |document|
289
- Requests::Diagnostics.new(uri, document).run
319
+ Requests::Diagnostics.new(document).run
290
320
  end
291
321
 
292
322
  Interface::FullDocumentDiagnosticReport.new(kind: "full", items: response.map(&:to_lsp_diagnostic)) if response
@@ -318,11 +348,21 @@ module RubyLsp
318
348
  sig { params(options: T::Hash[Symbol, T.untyped]).returns(Interface::InitializeResult) }
319
349
  def initialize_request(options)
320
350
  @store.clear
321
- @store.encoding = options.dig(:capabilities, :general, :positionEncodings)
351
+
352
+ encodings = options.dig(:capabilities, :general, :positionEncodings)
353
+ @store.encoding = if encodings.nil? || encodings.empty?
354
+ Constant::PositionEncodingKind::UTF16
355
+ elsif encodings.include?(Constant::PositionEncodingKind::UTF8)
356
+ Constant::PositionEncodingKind::UTF8
357
+ else
358
+ encodings.first
359
+ end
360
+
322
361
  formatter = options.dig(:initializationOptions, :formatter)
323
362
  @store.formatter = formatter unless formatter.nil?
324
363
 
325
364
  configured_features = options.dig(:initializationOptions, :enabledFeatures)
365
+ experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled)
326
366
 
327
367
  enabled_features = case configured_features
328
368
  when Array
@@ -351,6 +391,10 @@ module RubyLsp
351
391
  Interface::DocumentLinkOptions.new(resolve_provider: false)
352
392
  end
353
393
 
394
+ code_lens_provider = if experimental_features
395
+ Interface::CodeLensOptions.new(resolve_provider: false)
396
+ end
397
+
354
398
  hover_provider = if enabled_features["hover"]
355
399
  Interface::HoverClientCapabilities.new(dynamic_registration: false)
356
400
  end
@@ -406,6 +450,7 @@ module RubyLsp
406
450
  change: Constant::TextDocumentSyncKind::INCREMENTAL,
407
451
  open_close: true,
408
452
  ),
453
+ position_encoding: @store.encoding,
409
454
  selection_range_provider: enabled_features["selectionRanges"],
410
455
  hover_provider: hover_provider,
411
456
  document_symbol_provider: document_symbol_provider,
@@ -419,6 +464,7 @@ module RubyLsp
419
464
  diagnostic_provider: diagnostics_provider,
420
465
  inlay_hint_provider: inlay_hint_provider,
421
466
  completion_provider: completion_provider,
467
+ code_lens_provider: code_lens_provider,
422
468
  ),
423
469
  )
424
470
  end
@@ -11,5 +11,7 @@ require "ruby-lsp"
11
11
  require "ruby_lsp/utils"
12
12
  require "ruby_lsp/server"
13
13
  require "ruby_lsp/executor"
14
+ require "ruby_lsp/event_emitter"
14
15
  require "ruby_lsp/requests"
16
+ require "ruby_lsp/listener"
15
17
  require "ruby_lsp/store"
@@ -0,0 +1,39 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ # Listener is an abstract class to be used by requests for listening to events emitted when visiting an AST using the
6
+ # EventEmitter.
7
+ class Listener
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ extend T::Generic
11
+ include Requests::Support::Common
12
+
13
+ ResponseType = type_member
14
+
15
+ abstract!
16
+
17
+ class << self
18
+ extend T::Sig
19
+
20
+ sig { returns(T.nilable(T::Array[Symbol])) }
21
+ attr_reader :events
22
+
23
+ # All listener events must be defined inside of a `listener_events` block. This is to ensure we know which events
24
+ # have been registered. Defining an event outside of this block will simply not register it and it'll never be
25
+ # invoked
26
+ sig { params(block: T.proc.void).void }
27
+ def listener_events(&block)
28
+ current_methods = instance_methods
29
+ block.call
30
+ @events = T.let(instance_methods - current_methods, T.nilable(T::Array[Symbol]))
31
+ end
32
+ end
33
+
34
+ # Override this method with an attr_reader that returns the response of your listener. The listener should
35
+ # accumulate results in a @response variable and then provide the reader so that it is accessible
36
+ sig { abstract.returns(ResponseType) }
37
+ def response; end
38
+ end
39
+ end