Skip to content

Add hover documentation for reserved keywords #3553

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
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
119 changes: 119 additions & 0 deletions lib/ruby_lsp/listeners/hover.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@ class Hover
include Requests::Support::Common

ALLOWED_TARGETS = [
Prism::BreakNode,
Prism::CallNode,
Prism::CaseNode,
Prism::ClassNode,
Prism::ConstantReadNode,
Prism::ConstantWriteNode,
Prism::ConstantPathNode,
Prism::DefNode,
Prism::DefinedNode,
Prism::ElseNode,
Prism::EnsureNode,
Prism::ForNode,
Prism::GlobalVariableAndWriteNode,
Prism::GlobalVariableOperatorWriteNode,
Prism::GlobalVariableOrWriteNode,
Expand All @@ -23,11 +31,20 @@ class Hover
Prism::InstanceVariableOrWriteNode,
Prism::InstanceVariableTargetNode,
Prism::InstanceVariableWriteNode,
Prism::ModuleNode,
Prism::NextNode,
Prism::RescueNode,
Prism::ReturnNode,
Prism::SymbolNode,
Prism::StringNode,
Prism::UndefNode,
Prism::UnlessNode,
Prism::UntilNode,
Prism::InterpolatedStringNode,
Prism::SuperNode,
Prism::ForwardingSuperNode,
Prism::WhenNode,
Prism::WhileNode,
Prism::YieldNode,
Prism::ClassVariableAndWriteNode,
Prism::ClassVariableOperatorWriteNode,
Expand All @@ -54,10 +71,18 @@ def initialize(response_builder, global_state, uri, node_context, dispatcher, so

dispatcher.register(
self,
:on_break_node_enter,
:on_case_node_enter,
:on_class_node_enter,
:on_constant_read_node_enter,
:on_constant_write_node_enter,
:on_constant_path_node_enter,
:on_call_node_enter,
:on_def_node_enter,
:on_defined_node_enter,
:on_else_node_enter,
:on_ensure_node_enter,
:on_for_node_enter,
:on_global_variable_and_write_node_enter,
:on_global_variable_operator_write_node_enter,
:on_global_variable_or_write_node_enter,
Expand All @@ -70,7 +95,16 @@ def initialize(response_builder, global_state, uri, node_context, dispatcher, so
:on_instance_variable_operator_write_node_enter,
:on_instance_variable_or_write_node_enter,
:on_instance_variable_target_node_enter,
:on_module_node_enter,
:on_next_node_enter,
:on_rescue_node_enter,
:on_return_node_enter,
:on_super_node_enter,
:on_undef_node_enter,
:on_unless_node_enter,
:on_until_node_enter,
:on_when_node_enter,
:on_while_node_enter,
:on_forwarding_super_node_enter,
:on_string_node_enter,
:on_interpolated_string_node_enter,
Expand All @@ -97,6 +131,91 @@ def on_string_node_enter(node)
generate_heredoc_hover(node)
end

#: (Prism::BreakNode node) -> void
def on_break_node_enter(node)
handle_keyword_documentation(node.keyword)
end

#: (Prism::CaseNode node) -> void
def on_case_node_enter(node)
handle_keyword_documentation(node.case_keyword)
end

#: (Prism::ClassNode node) -> void
def on_class_node_enter(node)
handle_keyword_documentation(node.class_keyword)
end

#: (Prism::DefNode node) -> void
def on_def_node_enter(node)
handle_keyword_documentation(node.def_keyword)
end

#: (Prism::DefinedNode node) -> void
def on_defined_node_enter(node)
handle_keyword_documentation(node.keyword)
end

#: (Prism::ElseNode node) -> void
def on_else_node_enter(node)
handle_keyword_documentation(node.else_keyword)
end

#: (Prism::EnsureNode node) -> void
def on_ensure_node_enter(node)
handle_keyword_documentation(node.ensure_keyword)
end

#: (Prism::ForNode node) -> void
def on_for_node_enter(node)
handle_keyword_documentation(node.for_keyword)
end

#: (Prism::ModuleNode node) -> void
def on_module_node_enter(node)
handle_keyword_documentation(node.module_keyword)
end

#: (Prism::NextNode node) -> void
def on_next_node_enter(node)
handle_keyword_documentation(node.keyword)
end

#: (Prism::RescueNode node) -> void
def on_rescue_node_enter(node)
handle_keyword_documentation(node.keyword)
end

#: (Prism::ReturnNode node) -> void
def on_return_node_enter(node)
handle_keyword_documentation(node.keyword)
end

#: (Prism::UndefNode node) -> void
def on_undef_node_enter(node)
handle_keyword_documentation(node.keyword)
end

#: (Prism::UnlessNode node) -> void
def on_unless_node_enter(node)
handle_keyword_documentation(node.keyword)
end

#: (Prism::UntilNode node) -> void
def on_until_node_enter(node)
handle_keyword_documentation(node.keyword)
end

#: (Prism::WhenNode node) -> void
def on_when_node_enter(node)
handle_keyword_documentation(node.keyword)
end

#: (Prism::WhileNode node) -> void
def on_while_node_enter(node)
handle_keyword_documentation(node.keyword)
end

#: (Prism::InterpolatedStringNode node) -> void
def on_interpolated_string_node_enter(node)
generate_heredoc_hover(node)
Expand Down
17 changes: 17 additions & 0 deletions lib/ruby_lsp/static_docs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,23 @@ module RubyLsp

# A map of keyword => short documentation to be displayed on hover or completion
KEYWORD_DOCS = {
"break" => "Terminates the execution of a block or loop",
"case" => "Starts a case expression for pattern matching or multiple condition checking",
"class" => "Defines a class",
"def" => "Defines a method",
"defined" => "Checks if a constant, variable or method is defined",
"else" => "Executes the code in the else block if the condition is false",
"ensure" => "Executes the code in the ensure block regardless of whether an exception is raised or not",
"for" => "Iterates over a collection of elements",
"module" => "Defines a module",
"next" => "Skips the rest of the current iteration and moves to the next iteration of a loop or block",
"rescue" => "Handles exceptions that occur in the code block",
"return" => "Exits a method and returns a value",
"undef" => "Removes a method definition from a class or module",
"unless" => "Executes the code in the unless block if the condition is false",
"until" => "Executes the code in the until block until the condition is true",
"when" => "Matches a value against a pattern",
"while" => "Executes the code in the while block while the condition is true",
"yield" => "Invokes the passed block with the given arguments",
}.freeze #: Hash[String, String]
end
5 changes: 5 additions & 0 deletions project-words
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ Lstart
metaprogramming
mkpath
multibyte
namespacing
Namespacing
nargs
nodoc
noreturn
Expand All @@ -69,6 +71,7 @@ onig # abbreviation for oniguruma
Pacman
pathlist
popen
procs
qorge
qtlzwssomeking
quickfixes
Expand All @@ -88,6 +91,7 @@ rruby
rubyfmt
rubylibdir
rubylibprefix
Rubyists
setqflist
setsockopt
shadowenv
Expand All @@ -114,6 +118,7 @@ unindexed
unparser
unresolve
vcall
Validatable
Vinicius
vscodemachineid
vsctm
Expand Down
103 changes: 103 additions & 0 deletions static_docs/break.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Break

In Ruby, the `break` keyword is used to exit a loop or block prematurely. Unlike `next` which skips to the next iteration, `break` terminates the loop entirely and continues with the code after the loop.

```ruby
# Basic break usage in a loop
5.times do |i|
break if i == 3

puts i
end
# Output:
# 0
# 1
# 2
```

The `break` statement can be used with any of Ruby's iteration methods or loops.

```ruby
array = [1, 2, 3, 4, 5]

# Break in each iteration
array.each do |num|
break if num > 3

puts "Number: #{num}"
end
# Output:
# Number: 1
# Number: 2
# Number: 3

# Break in an infinite loop
count = 0
loop do
count += 1
break if count >= 3

puts "Count: #{count}"
end
# Output:
# Count: 1
# Count: 2
```

## Break with a Value

When used inside a block, `break` can return a value that becomes the result of the method call.

```ruby
# Break with a return value in map
result = [1, 2, 3, 4, 5].map do |num|
break "Too large!" if num > 3

num * 2
end
puts result # Output: "Too large!"

# Break with a value in find
number = (1..10).find do |n|
break n if n > 5 && n.even?
end
puts number # Output: 6
```

## Break in Nested Loops

When using `break` in nested loops, it only exits the innermost loop. To break from nested loops, you typically need to use a flag or return.

```ruby
# Break in nested iteration
(1..3).each do |i|
puts "Outer: #{i}"

(1..3).each do |j|
break if j == 2

puts " Inner: #{j}"
end
end
# Output:
# Outer: 1
# Inner: 1
# Outer: 2
# Inner: 1
# Outer: 3
# Inner: 1

# Breaking from nested loops with a flag
found = false
(1..3).each do |i|
(1..3).each do |j|
if i * j == 4
found = true
break
end
end
break if found
end
```

The `break` keyword is essential for controlling loop execution and implementing early exit conditions. It's particularly useful when you've found what you're looking for and don't need to continue iterating.
Loading