Skip to content
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

Revamp help command #877

Merged
merged 5 commits into from Feb 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion COMPARED_WITH_PRY.md
Expand Up @@ -9,7 +9,7 @@ Feel free to chip in and update this table - we appreciate your help!
| Supported Rubies | `>= 2.0` | `>= 2.7` | |
| Source code browsing | `show-source` | `show_source` | IRB's `show_source` can't display C source. See [#664](https://github.com/ruby/irb/issues/664) |
| Document browsing | `ri` | `show_doc` | |
| Live help system | `help` or `command_name --help` | `show_cmds` | IRB doesn't support detailed descriptions for individual commands yet |
| Live help system | `help` or `command_name --help` | `help` | IRB doesn't support detailed descriptions for individual commands yet |
| Open methods in editors | `edit` | `edit` | |
| Syntax highlighting | Yes | Yes | |
| Command shell integration | Yes | No | Currently, there's no plan to support such features in IRB |
Expand Down
13 changes: 7 additions & 6 deletions README.md
Expand Up @@ -107,17 +107,19 @@ Hello World

## Commands

The following commands are available on IRB. You can get the same output from the `show_cmds` command.
The following commands are available on IRB. You can get the same output from the `help` command.

```txt
Help
help List all available commands. Use `help <command>` to get information about a specific command.

IRB
exit Exit the current irb session.
exit! Exit the current process.
irb_load Load a Ruby file.
irb_require Require a Ruby file.
source Loads a given file in the current session.
irb_info Show information about IRB.
show_cmds List all available commands and their description.
history Shows the input history. `-g [query]` or `-G [query]` allows you to filter the output.

Workspace
Expand Down Expand Up @@ -146,13 +148,12 @@ Debugging
info Start the debugger of debug.gem and run its `info` command.

Misc
edit Open a file with the editor command defined with `ENV["VISUAL"]` or `ENV["EDITOR"]`.
edit Open a file or source location.
measure `measure` enables the mode to measure processing time. `measure :off` disables it.

Context
help [DEPRECATED] Enter the mode to look up RI documents.
show_doc Enter the mode to look up RI documents.
ls Show methods, constants, and variables. `-g [query]` or `-G [query]` allows you to filter out the output.
ls Show methods, constants, and variables.
show_source Show the source code of a given method or constant.
whereami Show the source code around binding.irb again.

Expand Down Expand Up @@ -228,7 +229,7 @@ end

To learn about these features, please refer to `debug.gem`'s [commands list](https://github.com/ruby/debug#debug-command-on-the-debug-console).

In the `irb:rdbg` session, the `show_cmds` command will also display all commands from `debug.gem`.
In the `irb:rdbg` session, the `help` command will also display all commands from `debug.gem`.

### Advantages Over `debug.gem`'s Console

Expand Down
2 changes: 1 addition & 1 deletion lib/irb.rb
Expand Up @@ -811,7 +811,7 @@
#
# === Commands
#
# Please use the `show_cmds` command to see the list of available commands.
# Please use the `help` command to see the list of available commands.
#
# === IRB Sessions
#
Expand Down
7 changes: 1 addition & 6 deletions lib/irb/command.rb
Expand Up @@ -162,6 +162,7 @@ def irb_context
[
:irb_help, :Help, "command/help",
[:help, NO_OVERRIDE],
[:show_cmds, NO_OVERRIDE],
],

[
Expand All @@ -187,16 +188,10 @@ def irb_context
:irb_show_source, :ShowSource, "command/show_source",
[:show_source, NO_OVERRIDE],
],

[
:irb_whereami, :Whereami, "command/whereami",
[:whereami, NO_OVERRIDE],
],
[
:irb_show_cmds, :ShowCmds, "command/show_cmds",
[:show_cmds, NO_OVERRIDE],
],

[
:irb_history, :History, "command/history",
[:history, NO_OVERRIDE],
Expand Down
9 changes: 9 additions & 0 deletions lib/irb/command/base.rb
Expand Up @@ -22,12 +22,21 @@ def description(description = nil)
@description
end

def help_message(help_message = nil)
@help_message = help_message if help_message
@help_message
end

private

def string_literal?(args)
sexp = Ripper.sexp(args)
sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal
end

def highlight(text)
Color.colorize(text, [:BOLD, :BLUE])
end
end

def self.execute(irb_context, *opts, **kwargs, &block)
Expand Down
18 changes: 17 additions & 1 deletion lib/irb/command/edit.rb
Expand Up @@ -8,7 +8,23 @@ module IRB
module Command
class Edit < Base
category "Misc"
description 'Open a file with the editor command defined with `ENV["VISUAL"]` or `ENV["EDITOR"]`.'
description 'Open a file or source location.'
help_message <<~HELP_MESSAGE
Usage: edit [FILE or constant or method signature]

Open a file in the editor specified in #{highlight('ENV["VISUAL"]')} or #{highlight('ENV["EDITOR"]')}

- If no arguments are provided, IRB will attempt to open the file the current context was defined in.
- If FILE is provided, IRB will open the file.
- If a constant or method signature is provided, IRB will attempt to locate the source file and open it.

Examples:

edit
edit foo.rb
edit Foo
edit Foo#bar
HELP_MESSAGE

class << self
def transform_args(args)
Expand Down
82 changes: 77 additions & 5 deletions lib/irb/command/help.rb
@@ -1,12 +1,84 @@
# frozen_string_literal: true

require_relative "show_cmds"

module IRB
module Command
class Help < ShowCmds
category "IRB"
description "List all available commands and their description."
class Help < Base
category "Help"
description "List all available commands. Use `help <command>` to get information about a specific command."

class << self
def transform_args(args)
# Return a string literal as is for backward compatibility
if args.empty? || string_literal?(args)
args
else # Otherwise, consider the input as a String for convenience
args.strip.dump
end
end
end

def execute(command_name = nil)
content =
if command_name
if command_class = ExtendCommandBundle.load_command(command_name)
command_class.help_message || command_class.description
else
"Can't find command `#{command_name}`. Please check the command name and try again.\n\n"
end
else
help_message
end
Pager.page_content(content)
end

private

def help_message
commands_info = IRB::ExtendCommandBundle.all_commands_info
commands_grouped_by_categories = commands_info.group_by { |cmd| cmd[:category] }

user_aliases = irb_context.instance_variable_get(:@user_aliases)

commands_grouped_by_categories["Aliases"] = user_aliases.map do |alias_name, target|
{ display_name: alias_name, description: "Alias for `#{target}`" }
end

if irb_context.with_debugger
# Remove the original "Debugging" category
commands_grouped_by_categories.delete("Debugging")
# Add an empty "Debugging (from debug.gem)" category at the end
commands_grouped_by_categories["Debugging (from debug.gem)"] = []
end

longest_cmd_name_length = commands_info.map { |c| c[:display_name].length }.max

output = StringIO.new

help_cmds = commands_grouped_by_categories.delete("Help")

add_category_to_output("Help", help_cmds, output, longest_cmd_name_length)

commands_grouped_by_categories.each do |category, cmds|
add_category_to_output(category, cmds, output, longest_cmd_name_length)
end

# Append the debugger help at the end
if irb_context.with_debugger
output.puts DEBUGGER__.help
end

output.string
end

def add_category_to_output(category, cmds, output, longest_cmd_name_length)
output.puts Color.colorize(category, [:BOLD])

cmds.each do |cmd|
output.puts " #{cmd[:display_name].to_s.ljust(longest_cmd_name_length)} #{cmd[:description]}"
end

output.puts
end
end
end
end
8 changes: 7 additions & 1 deletion lib/irb/command/ls.rb
Expand Up @@ -12,7 +12,13 @@ module IRB
module Command
class Ls < Base
category "Context"
description "Show methods, constants, and variables. `-g [query]` or `-G [query]` allows you to filter out the output."
description "Show methods, constants, and variables."

help_message <<~HELP_MESSAGE
Usage: ls [obj] [-g [query]]

-g [query] Filter the output with a query.
HELP_MESSAGE

def self.transform_args(args)
if match = args&.match(/\A(?<args>.+\s|)(-g|-G)\s+(?<grep>[^\s]+)\s*\n\z/)
Expand Down
59 changes: 0 additions & 59 deletions lib/irb/command/show_cmds.rb

This file was deleted.

3 changes: 1 addition & 2 deletions lib/irb/statement.rb
Expand Up @@ -83,9 +83,8 @@ def suppresses_echo?
end

def should_be_handled_by_debugger?
require_relative 'command/help'
require_relative 'command/debug'
IRB::Command::DebugCommand > @command_class || IRB::Command::Help == @command_class
IRB::Command::DebugCommand > @command_class
end

def evaluable_code
Expand Down
File renamed without changes.
66 changes: 66 additions & 0 deletions test/irb/command/test_help.rb
@@ -0,0 +1,66 @@
require "tempfile"
require_relative "../helper"

module TestIRB
class HelpTest < IntegrationTestCase
def setup
super

write_rc <<~'RUBY'
IRB.conf[:USE_PAGER] = false
RUBY

write_ruby <<~'RUBY'
binding.irb
RUBY
end

def test_help
out = run_ruby_file do
type "help"
type "exit"
end

assert_match(/List all available commands/, out)
assert_match(/Start the debugger of debug\.gem/, out)
end

def test_command_help
out = run_ruby_file do
type "help ls"
type "exit"
end

assert_match(/Usage: ls \[obj\]/, out)
end

def test_command_help_not_found
out = run_ruby_file do
type "help foo"
type "exit"
end

assert_match(/Can't find command `foo`\. Please check the command name and try again\./, out)
end

def test_show_cmds
out = run_ruby_file do
type "help"
type "exit"
end

assert_match(/List all available commands/, out)
assert_match(/Start the debugger of debug\.gem/, out)
end

def test_help_lists_user_aliases
out = run_ruby_file do
type "help"
type "exit"
end

assert_match(/\$\s+Alias for `show_source`/, out)
assert_match(/@\s+Alias for `whereami`/, out)
end
end
end
File renamed without changes.