ruby-lsp 0.4.3 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7bf79fd6cdc704c8f874f58985f0e72ac034c76bb70a68166654185f6f00cf1
4
- data.tar.gz: '083edc726c05ffb6e5e3ae63d480b17fa0f7161ee62349665cc57e309acd5598'
3
+ metadata.gz: 61c8fe2a55057823533a8a17b4472309e3d463ec3d63302fa3393f046ec4b339
4
+ data.tar.gz: fb9636b8aafac9d5002a872d0f34f7f9852e9348009cdc2934369637a4cadb4c
5
5
  SHA512:
6
- metadata.gz: f6192daf27dfd82dbb505f5184eebd66308690ce10ead4d4cedc4d9c6667be882a8b9de4c87e66eb88ff43e896307be24bbcc6506b0544b496e1bcb3c9804498
7
- data.tar.gz: bb68928d9a72b3b2873509bf915f753a202a520187bf5f55da1870d552e0ba0917a7d21ea70d20853ac60aa614f7d85d117d7291ec9c0778cab80e4561a7aba0
6
+ metadata.gz: db51fd5526431b773125c0b27bfe8ca53b2861759457f9a36e7a681bcfbffb0f58026b4f8cc4dc991be2380309959aacde7c8cc2cd0a0f0373dfb313d0d28db9
7
+ data.tar.gz: 13cc3991bffd1a18dd1e2580c8d1051161b03a171a25e8ce681f46fb8d5baedb1de0f9f96304556e08f3db95c2262e29693d22bf3659ba45eff620157adc035c
data/README.md CHANGED
@@ -44,6 +44,7 @@ See the [documentation](https://shopify.github.io/ruby-lsp) for more in-depth de
44
44
  ## Learn More
45
45
 
46
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)
47
48
 
48
49
  ## Contributing
49
50
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.3
1
+ 0.4.4
@@ -22,7 +22,7 @@ module RubyLsp
22
22
  attr_reader :uri
23
23
 
24
24
  sig { params(source: String, version: Integer, uri: String, encoding: String).void }
25
- def initialize(source:, version:, uri:, encoding: "utf-8")
25
+ def initialize(source:, version:, uri:, encoding: Constant::PositionEncodingKind::UTF8)
26
26
  @cache = T.let({}, T::Hash[Symbol, T.untyped])
27
27
  @encoding = T.let(encoding, String)
28
28
  @source = T.let(source, String)
@@ -77,9 +77,9 @@ module RubyLsp
77
77
  def parse
78
78
  return if @unparsed_edits.empty?
79
79
 
80
+ @unparsed_edits.clear
80
81
  @tree = SyntaxTree.parse(@source)
81
82
  @syntax_error = false
82
- @unparsed_edits.clear
83
83
  rescue SyntaxTree::Parser::ParseError
84
84
  @syntax_error = true
85
85
  end
@@ -99,6 +99,61 @@ module RubyLsp
99
99
  Scanner.new(@source, @encoding)
100
100
  end
101
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
+
102
157
  class Scanner
103
158
  extend T::Sig
104
159
 
@@ -127,7 +182,11 @@ module RubyLsp
127
182
  # The final position is the beginning of the line plus the requested column. If the encoding is UTF-16, we also
128
183
  # need to adjust for surrogate pairs
129
184
  requested_position = @pos + position[:character]
130
- 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
+
131
190
  requested_position
132
191
  end
133
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
@@ -123,6 +123,8 @@ module RubyLsp
123
123
  end
124
124
  when "textDocument/completion"
125
125
  completion(uri, request.dig(:params, :position))
126
+ when "textDocument/codeLens"
127
+ code_lens(uri)
126
128
  end
127
129
  end
128
130
 
@@ -133,6 +135,13 @@ module RubyLsp
133
135
  end
134
136
  end
135
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
+
136
145
  sig do
137
146
  params(
138
147
  uri: String,
@@ -140,7 +149,20 @@ module RubyLsp
140
149
  ).returns(T.nilable(Interface::Hover))
141
150
  end
142
151
  def hover(uri, position)
143
- 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
144
166
  end
145
167
 
146
168
  sig { params(uri: String).returns(T::Array[Interface::DocumentLink]) }
@@ -326,11 +348,21 @@ module RubyLsp
326
348
  sig { params(options: T::Hash[Symbol, T.untyped]).returns(Interface::InitializeResult) }
327
349
  def initialize_request(options)
328
350
  @store.clear
329
- @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
+
330
361
  formatter = options.dig(:initializationOptions, :formatter)
331
362
  @store.formatter = formatter unless formatter.nil?
332
363
 
333
364
  configured_features = options.dig(:initializationOptions, :enabledFeatures)
365
+ experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled)
334
366
 
335
367
  enabled_features = case configured_features
336
368
  when Array
@@ -359,6 +391,10 @@ module RubyLsp
359
391
  Interface::DocumentLinkOptions.new(resolve_provider: false)
360
392
  end
361
393
 
394
+ code_lens_provider = if experimental_features
395
+ Interface::CodeLensOptions.new(resolve_provider: false)
396
+ end
397
+
362
398
  hover_provider = if enabled_features["hover"]
363
399
  Interface::HoverClientCapabilities.new(dynamic_registration: false)
364
400
  end
@@ -414,6 +450,7 @@ module RubyLsp
414
450
  change: Constant::TextDocumentSyncKind::INCREMENTAL,
415
451
  open_close: true,
416
452
  ),
453
+ position_encoding: @store.encoding,
417
454
  selection_range_provider: enabled_features["selectionRanges"],
418
455
  hover_provider: hover_provider,
419
456
  document_symbol_provider: document_symbol_provider,
@@ -427,6 +464,7 @@ module RubyLsp
427
464
  diagnostic_provider: diagnostics_provider,
428
465
  inlay_hint_provider: inlay_hint_provider,
429
466
  completion_provider: completion_provider,
467
+ code_lens_provider: code_lens_provider,
430
468
  ),
431
469
  )
432
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
@@ -7,6 +7,7 @@ module RubyLsp
7
7
  class BaseRequest < SyntaxTree::Visitor
8
8
  extend T::Sig
9
9
  extend T::Helpers
10
+ include Support::Common
10
11
 
11
12
  abstract!
12
13
 
@@ -31,94 +32,10 @@ module RubyLsp
31
32
  # Syntax Tree implements `visit_all` using `map` instead of `each` for users who want to use the pattern
32
33
  # `result = visitor.visit(tree)`. However, we don't use that pattern and should avoid producing a new array for
33
34
  # every single node visited
34
- sig { params(nodes: T::Array[SyntaxTree::Node]).void }
35
+ sig { params(nodes: T::Array[T.nilable(SyntaxTree::Node)]).void }
35
36
  def visit_all(nodes)
36
37
  nodes.each { |node| visit(node) }
37
38
  end
38
-
39
- sig { params(node: SyntaxTree::Node).returns(Interface::Range) }
40
- def range_from_syntax_tree_node(node)
41
- loc = node.location
42
-
43
- Interface::Range.new(
44
- start: Interface::Position.new(
45
- line: loc.start_line - 1,
46
- character: loc.start_column,
47
- ),
48
- end: Interface::Position.new(line: loc.end_line - 1, character: loc.end_column),
49
- )
50
- end
51
-
52
- sig do
53
- params(node: T.any(SyntaxTree::ConstPathRef, SyntaxTree::ConstRef, SyntaxTree::TopConstRef)).returns(String)
54
- end
55
- def full_constant_name(node)
56
- name = +node.constant.value
57
- constant = T.let(node, SyntaxTree::Node)
58
-
59
- while constant.is_a?(SyntaxTree::ConstPathRef)
60
- constant = constant.parent
61
-
62
- case constant
63
- when SyntaxTree::ConstPathRef
64
- name.prepend("#{constant.constant.value}::")
65
- when SyntaxTree::VarRef
66
- name.prepend("#{constant.value.value}::")
67
- end
68
- end
69
-
70
- name
71
- end
72
-
73
- sig do
74
- params(
75
- node: SyntaxTree::Node,
76
- position: Integer,
77
- node_types: T::Array[T.class_of(SyntaxTree::Node)],
78
- ).returns([T.nilable(SyntaxTree::Node), T.nilable(SyntaxTree::Node)])
79
- end
80
- def locate(node, position, node_types: [])
81
- queue = T.let(node.child_nodes.compact, T::Array[T.nilable(SyntaxTree::Node)])
82
- closest = node
83
-
84
- until queue.empty?
85
- candidate = queue.shift
86
-
87
- # Skip nil child nodes
88
- next if candidate.nil?
89
-
90
- # Add the next child_nodes to the queue to be processed
91
- queue.concat(candidate.child_nodes)
92
-
93
- # Skip if the current node doesn't cover the desired position
94
- loc = candidate.location
95
- next unless (loc.start_char...loc.end_char).cover?(position)
96
-
97
- # If the node's start character is already past the position, then we should've found the closest node already
98
- break if position < loc.start_char
99
-
100
- # If there are node types to filter by, and the current node is not one of those types, then skip it
101
- next if node_types.any? && node_types.none? { |type| candidate.is_a?(type) }
102
-
103
- # If the current node is narrower than or equal to the previous closest node, then it is more precise
104
- closest_loc = closest.location
105
- if loc.end_char - loc.start_char <= closest_loc.end_char - closest_loc.start_char
106
- parent = T.let(closest, SyntaxTree::Node)
107
- closest = candidate
108
- end
109
- end
110
-
111
- [closest, parent]
112
- end
113
-
114
- sig { params(node: T.nilable(SyntaxTree::Node), range: T.nilable(T::Range[Integer])).returns(T::Boolean) }
115
- def visible?(node, range)
116
- return true if range.nil?
117
- return false if node.nil?
118
-
119
- loc = node.location
120
- range.cover?(loc.start_line - 1) && range.cover?(loc.end_line - 1)
121
- end
122
39
  end
123
40
  end
124
41
  end
@@ -54,7 +54,8 @@ module RubyLsp
54
54
  extracted_source = T.must(@document.source[start_index...end_index])
55
55
 
56
56
  # Find the closest statements node, so that we place the refactor in a valid position
57
- closest_statements = locate(T.must(@document.tree), start_index, node_types: [SyntaxTree::Statements]).first
57
+ closest_statements, parent_statements = @document
58
+ .locate(T.must(@document.tree), start_index, node_types: [SyntaxTree::Statements])
58
59
  return Error::InvalidTargetRange if closest_statements.nil?
59
60
 
60
61
  # Find the node with the end line closest to the requested position, so that we can place the refactor
@@ -64,25 +65,51 @@ module RubyLsp
64
65
  distance <= 0 ? Float::INFINITY : distance
65
66
  end
66
67
 
67
- # When trying to extract the first node inside of a statements block, then we can just select one line above it
68
- target_line = if closest_node == closest_statements.child_nodes.first
69
- closest_node.location.start_line - 1
68
+ # Find the parent expression of the closest node
69
+ parent_expression = parent_statements.child_nodes.compact.find do |node|
70
+ loc = node.location
71
+ loc.start_line - 1 <= source_range.dig(:start, :line) && loc.end_line - 1 >= source_range.dig(:end, :line)
72
+ end if parent_statements
73
+
74
+ closest_node_loc = closest_node.location
75
+ # If the parent expression is a single line block, then we have to extract it inside of the oneline block
76
+ if parent_expression.is_a?(SyntaxTree::MethodAddBlock) &&
77
+ parent_expression.location.start_line == parent_expression.location.end_line
78
+
79
+ variable_source = " #{NEW_VARIABLE_NAME} = #{extracted_source};"
80
+ character = source_range.dig(:start, :character) - 1
81
+ target_range = {
82
+ start: { line: closest_node_loc.end_line - 1, character: character },
83
+ end: { line: closest_node_loc.end_line - 1, character: character },
84
+ }
70
85
  else
71
- closest_node.location.end_line
72
- end
73
-
74
- lines = @document.source.lines
75
- indentation = T.must(T.must(lines[target_line - 1])[/\A */]).size
76
-
77
- target_range = {
78
- start: { line: target_line, character: indentation },
79
- end: { line: target_line, character: indentation },
80
- }
81
-
82
- variable_source = if T.must(lines[target_line]).strip.empty?
83
- "\n#{" " * indentation}#{NEW_VARIABLE_NAME} = #{extracted_source}"
84
- else
85
- "#{NEW_VARIABLE_NAME} = #{extracted_source}\n#{" " * indentation}"
86
+ # If the closest node covers the requested location, then we're extracting a statement nested inside of it. In
87
+ # that case, we want to place the extraction at the start of the closest node (one line above). Otherwise, we
88
+ # want to place the extract right below the closest node
89
+ if closest_node_loc.start_line - 1 <= source_range.dig(
90
+ :start,
91
+ :line,
92
+ ) && closest_node_loc.end_line - 1 >= source_range.dig(:end, :line)
93
+ indentation_line = closest_node_loc.start_line - 1
94
+ target_line = indentation_line
95
+ else
96
+ target_line = closest_node_loc.end_line
97
+ indentation_line = closest_node_loc.end_line - 1
98
+ end
99
+
100
+ lines = @document.source.lines
101
+ indentation = T.must(T.must(lines[indentation_line])[/\A */]).size
102
+
103
+ target_range = {
104
+ start: { line: target_line, character: indentation },
105
+ end: { line: target_line, character: indentation },
106
+ }
107
+
108
+ variable_source = if T.must(lines[target_line]).strip.empty?
109
+ "\n#{" " * indentation}#{NEW_VARIABLE_NAME} = #{extracted_source}"
110
+ else
111
+ "#{NEW_VARIABLE_NAME} = #{extracted_source}\n#{" " * indentation}"
112
+ end
86
113
  end
87
114
 
88
115
  Interface::CodeAction.new(
@@ -0,0 +1,135 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ # ![Code lens demo](../../misc/code_lens.gif)
7
+ #
8
+ # This feature is currently experimental. Clients will need to pass `experimentalFeaturesEnabled`
9
+ # in the initialization options to enable it.
10
+ #
11
+ # The
12
+ # [code lens](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeLens)
13
+ # request informs the editor of runnable commands such as tests
14
+ #
15
+ # # Example
16
+ #
17
+ # ```ruby
18
+ # # Run
19
+ # class Test < Minitest::Test
20
+ # end
21
+ # ```
22
+
23
+ class CodeLens < BaseRequest
24
+ BASE_COMMAND = T.let((File.exist?("Gemfile.lock") ? "bundle exec ruby" : "ruby") + " -Itest ", String)
25
+ ACCESS_MODIFIERS = T.let(["public", "private", "protected"], T::Array[String])
26
+
27
+ sig do
28
+ params(
29
+ document: Document,
30
+ ).void
31
+ end
32
+ def initialize(document)
33
+ super(document)
34
+ @results = T.let([], T::Array[Interface::CodeLens])
35
+ @path = T.let(document.uri.delete_prefix("file://"), String)
36
+ @modifier = T.let("public", String)
37
+ end
38
+
39
+ sig { override.returns(T.all(T::Array[Interface::CodeLens], Object)) }
40
+ def run
41
+ visit(@document.tree) if @document.parsed?
42
+ @results
43
+ end
44
+
45
+ sig { override.params(node: SyntaxTree::ClassDeclaration).void }
46
+ def visit_class(node)
47
+ class_name = node.constant.constant.value
48
+ if class_name.end_with?("Test")
49
+ add_code_lens(node, name: class_name, command: BASE_COMMAND + @path)
50
+ end
51
+ visit(node.bodystmt)
52
+ end
53
+
54
+ sig { override.params(node: SyntaxTree::DefNode).void }
55
+ def visit_def(node)
56
+ if @modifier == "public"
57
+ method_name = node.name.value
58
+ if method_name.start_with?("test_")
59
+ add_code_lens(
60
+ node,
61
+ name: method_name,
62
+ command: BASE_COMMAND + @path + " --name " + method_name,
63
+ )
64
+ end
65
+ end
66
+ end
67
+
68
+ sig { override.params(node: SyntaxTree::Command).void }
69
+ def visit_command(node)
70
+ if node.message.value == "public"
71
+ with_visiblity("public", node)
72
+ end
73
+ end
74
+
75
+ sig { override.params(node: SyntaxTree::CallNode).void }
76
+ def visit_call(node)
77
+ ident = node.message if node.message.is_a?(SyntaxTree::Ident)
78
+
79
+ if ident
80
+ if T.cast(ident, SyntaxTree::Ident).value == "public"
81
+ with_visiblity("public", node)
82
+ end
83
+ end
84
+ end
85
+
86
+ sig { override.params(node: SyntaxTree::VCall).void }
87
+ def visit_vcall(node)
88
+ vcall_value = node.value.value
89
+
90
+ if ACCESS_MODIFIERS.include?(vcall_value)
91
+ @modifier = vcall_value
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ sig do
98
+ params(
99
+ visibility: String,
100
+ node: T.any(SyntaxTree::CallNode, SyntaxTree::Command),
101
+ ).void
102
+ end
103
+ def with_visiblity(visibility, node)
104
+ current_visibility = @modifier
105
+ @modifier = visibility
106
+ visit(node.arguments)
107
+ ensure
108
+ @modifier = T.must(current_visibility)
109
+ end
110
+
111
+ sig { params(node: SyntaxTree::Node, name: String, command: String).void }
112
+ def add_code_lens(node, name:, command:)
113
+ @results << Interface::CodeLens.new(
114
+ range: range_from_syntax_tree_node(node),
115
+ command: Interface::Command.new(
116
+ title: "Run",
117
+ command: "rubyLsp.runTest",
118
+ arguments: [
119
+ @path,
120
+ name,
121
+ command,
122
+ {
123
+ start_line: node.location.start_line - 1,
124
+ start_column: node.location.start_column,
125
+ end_line: node.location.end_line - 1,
126
+ end_column: node.location.end_column,
127
+ },
128
+ ],
129
+ ),
130
+ data: { type: "test" },
131
+ )
132
+ end
133
+ end
134
+ end
135
+ end
@@ -32,8 +32,7 @@ module RubyLsp
32
32
  @highlights = T.let([], T::Array[Interface::DocumentHighlight])
33
33
  return unless document.parsed?
34
34
 
35
- position = document.create_scanner.find_char_position(position)
36
- @target = T.let(find(T.must(document.tree), position), T.nilable(Support::HighlightTarget))
35
+ @target = T.let(find(position), T.nilable(Support::HighlightTarget))
37
36
  end
38
37
 
39
38
  sig { override.returns(T.all(T::Array[Interface::DocumentHighlight], Object)) }
@@ -68,12 +67,11 @@ module RubyLsp
68
67
 
69
68
  sig do
70
69
  params(
71
- node: SyntaxTree::Node,
72
- position: Integer,
70
+ position: Document::PositionShape,
73
71
  ).returns(T.nilable(Support::HighlightTarget))
74
72
  end
75
- def find(node, position)
76
- matched, parent = locate(node, position)
73
+ def find(position)
74
+ matched, parent = @document.locate_node(position)
77
75
 
78
76
  return unless matched && parent
79
77
  return unless matched.is_a?(SyntaxTree::Ident) || DIRECT_HIGHLIGHTS.include?(matched.class)
@@ -17,8 +17,11 @@ module RubyLsp
17
17
  # before_save :do_something # when hovering on before_save, the link will be rendered
18
18
  # end
19
19
  # ```
20
- class Hover < BaseRequest
20
+ class Hover < Listener
21
21
  extend T::Sig
22
+ extend T::Generic
23
+
24
+ ResponseType = type_member { { fixed: T.nilable(Interface::Hover) } }
22
25
 
23
26
  ALLOWED_TARGETS = T.let(
24
27
  [
@@ -29,53 +32,45 @@ module RubyLsp
29
32
  T::Array[T.class_of(SyntaxTree::Node)],
30
33
  )
31
34
 
32
- sig { params(document: Document, position: Document::PositionShape).void }
33
- def initialize(document, position)
34
- super(document)
35
+ sig { override.returns(ResponseType) }
36
+ attr_reader :response
35
37
 
36
- @position = T.let(document.create_scanner.find_char_position(position), Integer)
38
+ sig { void }
39
+ def initialize
40
+ @response = T.let(nil, ResponseType)
41
+ super()
37
42
  end
38
43
 
39
- sig { override.returns(T.nilable(Interface::Hover)) }
40
- def run
41
- return unless @document.parsed?
44
+ listener_events do
45
+ sig { params(node: SyntaxTree::Command).void }
46
+ def on_command(node)
47
+ message = node.message
48
+ @response = generate_rails_document_link_hover(message.value, message)
49
+ end
42
50
 
43
- target, parent = locate(T.must(@document.tree), @position)
44
- target = parent if !ALLOWED_TARGETS.include?(target.class) && ALLOWED_TARGETS.include?(parent.class)
51
+ sig { params(node: SyntaxTree::ConstPathRef).void }
52
+ def on_const_path_ref(node)
53
+ @response = generate_rails_document_link_hover(full_constant_name(node), node)
54
+ end
45
55
 
46
- case target
47
- when SyntaxTree::Command
48
- message = target.message
49
- generate_rails_document_link_hover(message.value, message)
50
- when SyntaxTree::CallNode
51
- message = target.message
56
+ sig { params(node: SyntaxTree::CallNode).void }
57
+ def on_call(node)
58
+ message = node.message
52
59
  return if message.is_a?(Symbol)
53
60
 
54
- generate_rails_document_link_hover(message.value, message)
55
- when SyntaxTree::ConstPathRef
56
- constant_name = full_constant_name(target)
57
- generate_rails_document_link_hover(constant_name, target)
61
+ @response = generate_rails_document_link_hover(message.value, message)
58
62
  end
59
63
  end
60
64
 
61
65
  private
62
66
 
63
- sig do
64
- params(name: String, node: SyntaxTree::Node).returns(T.nilable(Interface::Hover))
65
- end
67
+ sig { params(name: String, node: SyntaxTree::Node).returns(T.nilable(Interface::Hover)) }
66
68
  def generate_rails_document_link_hover(name, node)
67
69
  urls = Support::RailsDocumentClient.generate_rails_document_urls(name)
68
-
69
70
  return if urls.empty?
70
71
 
71
- contents = Interface::MarkupContent.new(
72
- kind: "markdown",
73
- value: urls.join("\n\n"),
74
- )
75
- Interface::Hover.new(
76
- range: range_from_syntax_tree_node(node),
77
- contents: contents,
78
- )
72
+ contents = Interface::MarkupContent.new(kind: "markdown", value: urls.join("\n\n"))
73
+ Interface::Hover.new(range: range_from_syntax_tree_node(node), contents: contents)
79
74
  end
80
75
  end
81
76
  end
@@ -29,8 +29,7 @@ module RubyLsp
29
29
  # We can't verify if we're inside a require when there are syntax errors
30
30
  return [] if @document.syntax_error?
31
31
 
32
- char_position = @document.create_scanner.find_char_position(@position)
33
- target = T.let(find(char_position), T.nilable(SyntaxTree::TStringContent))
32
+ target = T.let(find, T.nilable(SyntaxTree::TStringContent))
34
33
  # no target means the we are not inside a `require` call
35
34
  return [] unless target
36
35
 
@@ -51,11 +50,12 @@ module RubyLsp
51
50
  end
52
51
  end
53
52
 
54
- sig { params(position: Integer).returns(T.nilable(SyntaxTree::TStringContent)) }
55
- def find(position)
56
- matched, parent = locate(
53
+ sig { returns(T.nilable(SyntaxTree::TStringContent)) }
54
+ def find
55
+ char_position = @document.create_scanner.find_char_position(@position)
56
+ matched, parent = @document.locate(
57
57
  T.must(@document.tree),
58
- position,
58
+ char_position,
59
59
  node_types: [SyntaxTree::Command, SyntaxTree::CommandCall, SyntaxTree::CallNode],
60
60
  )
61
61
 
@@ -76,7 +76,7 @@ module RubyLsp
76
76
 
77
77
  path_node = argument.parts.first
78
78
  return unless path_node.is_a?(SyntaxTree::TStringContent)
79
- return unless (path_node.location.start_char..path_node.location.end_char).cover?(position)
79
+ return unless (path_node.location.start_char..path_node.location.end_char).cover?(char_position)
80
80
 
81
81
  path_node
82
82
  end
@@ -0,0 +1,55 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ module Support
7
+ module Common
8
+ extend T::Sig
9
+
10
+ sig { params(node: SyntaxTree::Node).returns(Interface::Range) }
11
+ def range_from_syntax_tree_node(node)
12
+ loc = node.location
13
+
14
+ Interface::Range.new(
15
+ start: Interface::Position.new(
16
+ line: loc.start_line - 1,
17
+ character: loc.start_column,
18
+ ),
19
+ end: Interface::Position.new(line: loc.end_line - 1, character: loc.end_column),
20
+ )
21
+ end
22
+
23
+ sig do
24
+ params(node: T.any(SyntaxTree::ConstPathRef, SyntaxTree::ConstRef, SyntaxTree::TopConstRef)).returns(String)
25
+ end
26
+ def full_constant_name(node)
27
+ name = +node.constant.value
28
+ constant = T.let(node, SyntaxTree::Node)
29
+
30
+ while constant.is_a?(SyntaxTree::ConstPathRef)
31
+ constant = constant.parent
32
+
33
+ case constant
34
+ when SyntaxTree::ConstPathRef
35
+ name.prepend("#{constant.constant.value}::")
36
+ when SyntaxTree::VarRef
37
+ name.prepend("#{constant.value.value}::")
38
+ end
39
+ end
40
+
41
+ name
42
+ end
43
+
44
+ sig { params(node: T.nilable(SyntaxTree::Node), range: T.nilable(T::Range[Integer])).returns(T::Boolean) }
45
+ def visible?(node, range)
46
+ return true if range.nil?
47
+ return false if node.nil?
48
+
49
+ loc = node.location
50
+ range.cover?(loc.start_line - 1) && range.cover?(loc.end_line - 1)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -18,6 +18,7 @@ module RubyLsp
18
18
  # - {RubyLsp::Requests::DocumentHighlight}
19
19
  # - {RubyLsp::Requests::InlayHints}
20
20
  # - {RubyLsp::Requests::PathCompletion}
21
+ # - {RubyLsp::Requests::CodeLens}
21
22
 
22
23
  module Requests
23
24
  autoload :BaseRequest, "ruby_lsp/requests/base_request"
@@ -35,6 +36,7 @@ module RubyLsp
35
36
  autoload :DocumentHighlight, "ruby_lsp/requests/document_highlight"
36
37
  autoload :InlayHints, "ruby_lsp/requests/inlay_hints"
37
38
  autoload :PathCompletion, "ruby_lsp/requests/path_completion"
39
+ autoload :CodeLens, "ruby_lsp/requests/code_lens"
38
40
 
39
41
  # :nodoc:
40
42
  module Support
@@ -46,6 +48,7 @@ module RubyLsp
46
48
  autoload :HighlightTarget, "ruby_lsp/requests/support/highlight_target"
47
49
  autoload :RailsDocumentClient, "ruby_lsp/requests/support/rails_document_client"
48
50
  autoload :PrefixTree, "ruby_lsp/requests/support/prefix_tree"
51
+ autoload :Common, "ruby_lsp/requests/support/common"
49
52
  end
50
53
  end
51
54
  end
@@ -9,8 +9,8 @@ module RubyLsp
9
9
  class Store
10
10
  extend T::Sig
11
11
 
12
- sig { params(encoding: String).void }
13
- attr_writer :encoding
12
+ sig { returns(String) }
13
+ attr_accessor :encoding
14
14
 
15
15
  sig { returns(String) }
16
16
  attr_accessor :formatter
@@ -18,7 +18,7 @@ module RubyLsp
18
18
  sig { void }
19
19
  def initialize
20
20
  @state = T.let({}, T::Hash[String, Document])
21
- @encoding = T.let("utf-8", String)
21
+ @encoding = T.let(Constant::PositionEncodingKind::UTF8, String)
22
22
  @formatter = T.let("auto", String)
23
23
  end
24
24
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-03-30 00:00:00.000000000 Z
11
+ date: 2023-04-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -73,12 +73,15 @@ files:
73
73
  - lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
74
74
  - lib/ruby-lsp.rb
75
75
  - lib/ruby_lsp/document.rb
76
+ - lib/ruby_lsp/event_emitter.rb
76
77
  - lib/ruby_lsp/executor.rb
77
78
  - lib/ruby_lsp/internal.rb
79
+ - lib/ruby_lsp/listener.rb
78
80
  - lib/ruby_lsp/requests.rb
79
81
  - lib/ruby_lsp/requests/base_request.rb
80
82
  - lib/ruby_lsp/requests/code_action_resolve.rb
81
83
  - lib/ruby_lsp/requests/code_actions.rb
84
+ - lib/ruby_lsp/requests/code_lens.rb
82
85
  - lib/ruby_lsp/requests/diagnostics.rb
83
86
  - lib/ruby_lsp/requests/document_highlight.rb
84
87
  - lib/ruby_lsp/requests/document_link.rb
@@ -92,6 +95,7 @@ files:
92
95
  - lib/ruby_lsp/requests/selection_ranges.rb
93
96
  - lib/ruby_lsp/requests/semantic_highlighting.rb
94
97
  - lib/ruby_lsp/requests/support/annotation.rb
98
+ - lib/ruby_lsp/requests/support/common.rb
95
99
  - lib/ruby_lsp/requests/support/highlight_target.rb
96
100
  - lib/ruby_lsp/requests/support/prefix_tree.rb
97
101
  - lib/ruby_lsp/requests/support/rails_document_client.rb
@@ -127,7 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
131
  - !ruby/object:Gem::Version
128
132
  version: '0'
129
133
  requirements: []
130
- rubygems_version: 3.4.9
134
+ rubygems_version: 3.4.10
131
135
  signing_key:
132
136
  specification_version: 4
133
137
  summary: An opinionated language server for Ruby