ruby-lsp 0.4.3 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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