ruby-lsp 0.4.2 → 0.4.4

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