Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,6 +61,8 @@ GEM
nokogiri (1.15.2)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.15.2-arm64-darwin)
racc (~> 1.4)
parallel (1.23.0)
parser (3.2.2.3)
ast (~> 2.4.1)
Expand Down
57 changes: 27 additions & 30 deletions lib/ruby_lsp/document.rb
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,8 +9,8 @@ class Document
RangeShape = T.type_alias { { start: PositionShape, end: PositionShape } }
EditShape = T.type_alias { { range: RangeShape, text: String } }

sig { returns(T.nilable(SyntaxTree::Node)) }
attr_reader :tree
sig { returns(YARP::ParseResult) }
attr_reader :parse_result

sig { returns(String) }
attr_reader :source
Expand All@@ -29,10 +29,12 @@ def initialize(source:, version:, uri:, encoding: Constant::PositionEncodingKind
@version = T.let(version, Integer)
@uri = T.let(uri, URI::Generic)
@unparsed_edits = T.let([], T::Array[EditShape])
@syntax_error = T.let(false, T::Boolean)
@tree = T.let(SyntaxTree.parse(@source), T.nilable(SyntaxTree::Node))
rescue SyntaxTree::Parser::ParseError
@syntax_error = true
@parse_result = T.let(YARP.parse(@source), YARP::ParseResult)
end

sig { returns(YARP::Node) }
def tree
@parse_result.value
end

sig { params(other: Document).returns(T::Boolean) }
Expand DownExpand Up@@ -89,20 +91,17 @@ def parse
return if @unparsed_edits.empty?

@unparsed_edits.clear
@tree = SyntaxTree.parse(@source)
@syntax_error = false
rescue SyntaxTree::Parser::ParseError
@syntax_error = true
@parse_result = YARP.parse(@source)
end

sig { returns(T::Boolean) }
def syntax_error?
@syntax_error
@parse_result.failure?
end

sig { returns(T::Boolean) }
def parsed?
!@tree.nil?
!@parse_result.value.nil?
end

sig { returns(Scanner) }
Expand All@@ -113,27 +112,25 @@ def create_scanner
sig do
params(
position: PositionShape,
node_types: T::Array[T.class_of(SyntaxTree::Node)],
).returns([T.nilable(SyntaxTree::Node), T.nilable(SyntaxTree::Node), T::Array[String]])
node_types: T::Array[T.class_of(YARP::Node)],
).returns([T.nilable(YARP::Node), T.nilable(YARP::Node), T::Array[String]])
end
def locate_node(position, node_types: [])
return [nil, nil, []] unless parsed?

locate(T.must(@tree), create_scanner.find_char_position(position), node_types: node_types)
locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types)
end

sig do
params(
node: SyntaxTree::Node,
node: YARP::Node,
char_position: Integer,
node_types: T::Array[T.class_of(SyntaxTree::Node)],
).returns([T.nilable(SyntaxTree::Node), T.nilable(SyntaxTree::Node), T::Array[String]])
node_types: T::Array[T.class_of(YARP::Node)],
).returns([T.nilable(YARP::Node), T.nilable(YARP::Node), T::Array[String]])
end
def locate(node, char_position, node_types: [])
queue = T.let(node.child_nodes.compact, T::Array[T.nilable(SyntaxTree::Node)])
queue = T.let(node.child_nodes.compact, T::Array[T.nilable(YARP::Node)])
closest = node
parent = T.let(nil, T.nilable(SyntaxTree::Node))
nesting = T.let([], T::Array[T.any(SyntaxTree::ClassDeclaration, SyntaxTree::ModuleDeclaration)])
parent = T.let(nil, T.nilable(YARP::Node))
nesting = T.let([], T::Array[T.any(YARP::ClassNode, YARP::ModuleNode)])

until queue.empty?
candidate = queue.shift
Expand All@@ -144,24 +141,24 @@ def locate(node, char_position, node_types: [])
# Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
# same order as the visiting mechanism, which means searching the child nodes before moving on to the next
# sibling
queue.unshift(*candidate.child_nodes)
T.unsafe(queue).unshift(*candidate.child_nodes)

# Skip if the current node doesn't cover the desired position
loc = candidate.location
next unless (loc.start_char...loc.end_char).cover?(char_position)
next unless (loc.start_offset...loc.end_offset).cover?(char_position)

# If the node's start character is already past the position, then we should've found the closest node
# already
break if char_position < loc.start_char
break if char_position < loc.start_offset

# If the candidate starts after the end of the previous nesting level, then we've exited that nesting level and
# need to pop the stack
previous_level = nesting.last
nesting.pop if previous_level && candidate.start_char > previous_level.end_char
nesting.pop if previous_level && loc.start_offset > previous_level.location.end_offset

# Keep track of the nesting where we found the target. This is used to determine the fully qualified name of the
# target when it is a constant
if candidate.is_a?(SyntaxTree::ClassDeclaration) || candidate.is_a?(SyntaxTree::ModuleDeclaration)
if candidate.is_a?(YARP::ClassNode) || candidate.is_a?(YARP::ModuleNode)
nesting << candidate
end

Expand All@@ -170,13 +167,13 @@ def locate(node, char_position, node_types: [])

# If the current node is narrower than or equal to the previous closest node, then it is more precise
closest_loc = closest.location
if loc.end_char - loc.start_char <= closest_loc.end_char - closest_loc.start_char
if loc.end_offset - loc.start_offset <= closest_loc.end_offset - closest_loc.start_offset
parent = closest
closest = candidate
end
end

[closest, parent, nesting.map { |n| n.constant.constant.value }]
[closest, parent, nesting.map { |n| n.constant_path.location.slice }]
end

class Scanner
Expand Down
2 changes: 1 addition & 1 deletion lib/ruby_lsp/event_emitter.rb
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,7 +52,7 @@ def emit_for_target(node)

# Visit dispatchers are below. Notice that for nodes that create a new scope (e.g.: classes, modules, method defs)
# we need both an `on_*` and `after_*` event. This is because some requests must know when we exit the scope
sig { override.params(node: T.nilable(SyntaxTree::Node)).void }
sig { override.params(node: T.nilable(YARP::Node)).void }
def visit(node)
@listeners[:on_node]&.each { |l| T.unsafe(l).on_node(node) }
super
Expand Down
27 changes: 15 additions & 12 deletions lib/ruby_lsp/requests/document_highlight.rb
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,8 +32,8 @@ class DocumentHighlight < Listener

sig do
params(
target: T.nilable(SyntaxTree::Node),
parent: T.nilable(SyntaxTree::Node),
target: T.nilable(YARP::Node),
parent: T.nilable(YARP::Node),
emitter: EventEmitter,
message_queue: Thread::Queue,
).void
Expand All@@ -49,17 +49,20 @@ def initialize(target, parent, emitter, message_queue)
case target
when *DIRECT_HIGHLIGHTS
Support::HighlightTarget.new(target)
when SyntaxTree::Ident
relevant_node = parent.is_a?(SyntaxTree::Params) ? target : parent
Support::HighlightTarget.new(relevant_node)
when YARP::GlobalVariableWriteNode # , InstanceVariableWriteNode, ConstantWriteNode, ClassVariableWriteNode
Support::HighlightTarget.new(target)

# when SyntaxTree::Ident
# relevant_node = parent.is_a?(YARP::ParametersNode) ? target : parent
# Support::HighlightTarget.new(relevant_node)
end

@target = T.let(highlight_target, T.nilable(Support::HighlightTarget))

emitter.register(self, :on_node) if @target
end

sig { params(node: T.nilable(SyntaxTree::Node)).void }
sig { params(node: T.nilable(YARP::Node)).void }
def on_node(node)
return if node.nil?

Expand All@@ -71,13 +74,13 @@ def on_node(node)

DIRECT_HIGHLIGHTS = T.let(
[
SyntaxTree::GVar,
SyntaxTree::IVar,
SyntaxTree::Const,
SyntaxTree::CVar,
SyntaxTree::VarField,
YARP::GlobalVariableReadNode,
YARP::InstanceVariableReadNode,
YARP::ConstantReadNode,
YARP::ClassVariableReadNode,
# YARP::VarField,
],
T::Array[T.class_of(SyntaxTree::Node)],
T::Array[T.class_of(YARP::Node)],
)

sig { params(match: Support::HighlightTarget::HighlightMatch).void }
Expand Down
71 changes: 35 additions & 36 deletions lib/ruby_lsp/requests/support/highlight_target.rb
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,81 +16,80 @@ class HighlightMatch
sig { returns(Integer) }
attr_reader :type

sig { returns(SyntaxTree::Node) }
sig { returns(YARP::Node) }
attr_reader :node

sig { params(type: Integer, node: SyntaxTree::Node).void }
sig { params(type: Integer, node: YARP::Node).void }
def initialize(type:, node:)
@type = type
@node = node
end
end

sig { params(node: SyntaxTree::Node).void }
sig { params(node: YARP::Node).void }
def initialize(node)
@node = node
@value = T.let(value(node), T.nilable(String))
end

sig { params(other: SyntaxTree::Node).returns(T.nilable(HighlightMatch)) }
sig { params(other: YARP::Node).returns(T.nilable(HighlightMatch)) }
def highlight_type(other)
matched_highlight(other) if other.is_a?(SyntaxTree::Params) || (@value && @value == value(other))
matched_highlight(other) if other.is_a?(YARP::ParametersNode) || (@value && @value == value(other))
end

private

# Match the target type (where the cursor is positioned) with the `other` type (the node we're currently
# visiting)
sig { params(other: SyntaxTree::Node).returns(T.nilable(HighlightMatch)) }
sig { params(other: YARP::Node).returns(T.nilable(HighlightMatch)) }
def matched_highlight(other)
case @node
# Method definitions and invocations
when SyntaxTree::VCall, SyntaxTree::CallNode, SyntaxTree::Command,
SyntaxTree::CommandCall, SyntaxTree::DefNode
when YARP::CallNode, YARP::DefNode
case other

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need this nested switch case?

when SyntaxTree::VCall, SyntaxTree::CallNode, SyntaxTree::Command, SyntaxTree::CommandCall
when YARP::CallNode
HighlightMatch.new(type: READ, node: other)
when SyntaxTree::DefNode
when YARP::DefNode
HighlightMatch.new(type: WRITE, node: other.name)
end
# Variables, parameters and constants
when SyntaxTree::GVar, SyntaxTree::IVar, SyntaxTree::Const, SyntaxTree::CVar, SyntaxTree::VarField,
SyntaxTree::VarRef, SyntaxTree::Ident
when YARP::GlobalVariableReadNode, YARP::InstanceVariableReadNode, YARP::ConstantReadNode, YARP::ClassVariableReadNode, SyntaxTree::VarField,
# SyntaxTree::VarRef, SyntaxTree::Ident,
YARP::GlobalVariableReadNode
case other
when SyntaxTree::VarField
HighlightMatch.new(type: WRITE, node: other)
when SyntaxTree::VarRef
HighlightMatch.new(type: READ, node: other)
when SyntaxTree::ClassDeclaration, SyntaxTree::ModuleDeclaration
HighlightMatch.new(type: WRITE, node: other.constant)
when SyntaxTree::ConstPathRef
HighlightMatch.new(type: READ, node: other.constant)
when SyntaxTree::Params
# when SyntaxTree::VarField
# HighlightMatch.new(type: WRITE, node: other)
# when SyntaxTree::VarRef
# HighlightMatch.new(type: READ, node: other)
when YARP::ClassNode, YARP::ModuleNode
HighlightMatch.new(type: WRITE, node: other.location)
when YARP::ConstantPathNode
HighlightMatch.new(type: READ, node: other.location)
when YARP::ParametersNode
params = other.child_nodes.compact
match = params.find { |param| value(param) == @value }
HighlightMatch.new(type: WRITE, node: match) if match
end
end
end

sig { params(node: SyntaxTree::Node).returns(T.nilable(String)) }
sig { params(node: YARP::Node).returns(T.nilable(String)) }
def value(node)
case node
when SyntaxTree::ConstPathRef, SyntaxTree::ConstPathField, SyntaxTree::TopConstField
node.constant.value
when SyntaxTree::GVar, SyntaxTree::IVar, SyntaxTree::Const, SyntaxTree::CVar, SyntaxTree::Ident
node.value
when SyntaxTree::Field, SyntaxTree::DefNode, SyntaxTree::RestParam,
SyntaxTree::KwRestParam, SyntaxTree::BlockArg
when YARP::ConstantPathNode, YARP::ConstantPathNode # , SyntaxTree::TopConstField
node.location.slice
when YARP::GlobalVariableReadNode, YARP::InstanceVariableReadNode, YARP::ConstantReadNode, YARP::ClassVariableReadNode # , SyntaxTree::VarField,
node.location.slice
when YARP::DefNode, YARP::RestParameterNode,
YARP::KeywordRestParameterNode, YARP::BlockArgumentNode
node.name&.value
when SyntaxTree::VarField, SyntaxTree::VarRef, SyntaxTree::VCall
value = node.value
value.value unless value.nil? || value.is_a?(Symbol)
when SyntaxTree::CallNode, SyntaxTree::Command, SyntaxTree::CommandCall
message = node.message
message.value unless message.is_a?(Symbol)
when SyntaxTree::ClassDeclaration, SyntaxTree::ModuleDeclaration
node.constant.constant.value
# when SyntaxTree::VarField, SyntaxTree::VarRef, SyntaxTree::VCall
# value = node.value
# value.value unless value.nil? || value.is_a?(Symbol)
when YARP::CallNode
node.message
when YARP::ClassNode, YARP::ModuleNode,
node.location.slice
end
end
end
Expand Down
60 changes: 60 additions & 0 deletions sorbet/rbi/shims/yarp.rbi
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
# typed: true

module YARP
class << self
sig { params(source: String).returns(ParseResult) }
def parse(*source); end
end

class ParseResult
sig { returns(YARP::Node) }
def value; end

sig { returns(T::Boolean) }
def failure?; end

sig { returns(T::Boolean) }
def success?; end
end

class Node
sig { returns(T::Array[T.nilable(YARP::Node)]) }
def child_nodes; end

sig { returns(Location) }
def location; end
end

class Location
sig { returns(Integer) }
def start_offset; end

sig { returns(Integer) }
def end_offset; end

sig { returns(Integer) }
def start_line; end

sig { returns(Integer) }
def end_line; end

sig { returns(Integer) }
def start_column; end

sig { returns(Integer) }
def end_column; end

sig { returns(String) }
def slice; end
end

class ClassNode
sig { returns(ConstantPathNode) }
def constant_path; end
end

class ModuleNode
sig { returns(ConstantPathNode) }
def constant_path; end
end
end
Loading