diff --git a/COMPARED_WITH_PRY.md b/COMPARED_WITH_PRY.md index 94bf3020f..ef7ea1dec 100644 --- a/COMPARED_WITH_PRY.md +++ b/COMPARED_WITH_PRY.md @@ -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 | diff --git a/README.md b/README.md index 86cfbe876..ac1b164bc 100644 --- a/README.md +++ b/README.md @@ -107,9 +107,12 @@ 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 ` to get information about a specific command. + IRB exit Exit the current irb session. exit! Exit the current process. @@ -117,7 +120,6 @@ IRB 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 @@ -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. @@ -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 diff --git a/lib/irb.rb b/lib/irb.rb index 73d96947e..09ea9256c 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -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 # diff --git a/lib/irb/command.rb b/lib/irb/command.rb index cab571cfe..c84474e63 100644 --- a/lib/irb/command.rb +++ b/lib/irb/command.rb @@ -162,6 +162,7 @@ def irb_context [ :irb_help, :Help, "command/help", [:help, NO_OVERRIDE], + [:show_cmds, NO_OVERRIDE], ], [ @@ -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], diff --git a/lib/irb/command/base.rb b/lib/irb/command/base.rb index 87d2fea35..880b781a4 100644 --- a/lib/irb/command/base.rb +++ b/lib/irb/command/base.rb @@ -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) diff --git a/lib/irb/command/edit.rb b/lib/irb/command/edit.rb index 1a8ded6bc..ab8c62663 100644 --- a/lib/irb/command/edit.rb +++ b/lib/irb/command/edit.rb @@ -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) diff --git a/lib/irb/command/help.rb b/lib/irb/command/help.rb index 67cc31a0b..19113dbbf 100644 --- a/lib/irb/command/help.rb +++ b/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 ` 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 diff --git a/lib/irb/command/ls.rb b/lib/irb/command/ls.rb index bbe4a1ee9..6b6136c2f 100644 --- a/lib/irb/command/ls.rb +++ b/lib/irb/command/ls.rb @@ -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(?.+\s|)(-g|-G)\s+(?[^\s]+)\s*\n\z/) diff --git a/lib/irb/command/show_cmds.rb b/lib/irb/command/show_cmds.rb deleted file mode 100644 index 940ed490d..000000000 --- a/lib/irb/command/show_cmds.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -require "stringio" - -require_relative "../pager" - -module IRB - # :stopdoc: - - module Command - class ShowCmds < Base - category "IRB" - description "List all available commands and their description." - - def execute(*args) - 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") - # Remove the `help` command as it's delegated to the debugger - commands_grouped_by_categories["Context"].delete_if { |cmd| cmd[:display_name] == :help } - # 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 - - commands_grouped_by_categories.each do |category, cmds| - 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 - - # Append the debugger help at the end - if irb_context.with_debugger - output.puts DEBUGGER__.help - end - - Pager.page_content(output.string) - end - end - end - - # :startdoc: -end diff --git a/lib/irb/statement.rb b/lib/irb/statement.rb index 009eb2d20..1e026d112 100644 --- a/lib/irb/statement.rb +++ b/lib/irb/statement.rb @@ -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 diff --git a/test/irb/cmd/test_force_exit.rb b/test/irb/command/test_force_exit.rb similarity index 100% rename from test/irb/cmd/test_force_exit.rb rename to test/irb/command/test_force_exit.rb diff --git a/test/irb/command/test_help.rb b/test/irb/command/test_help.rb new file mode 100644 index 000000000..c82c43a4c --- /dev/null +++ b/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 diff --git a/test/irb/cmd/test_show_source.rb b/test/irb/command/test_show_source.rb similarity index 100% rename from test/irb/cmd/test_show_source.rb rename to test/irb/command/test_show_source.rb diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index 15ae6a658..9ab7d5c81 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -600,39 +600,6 @@ def test_whereami_alias end end - - class ShowCmdsTest < CommandTestCase - def test_help - out, err = execute_lines( - "help\n", - ) - - assert_empty err - assert_match(/List all available commands and their description/, out) - assert_match(/Start the debugger of debug\.gem/, out) - end - - def test_show_cmds - out, err = execute_lines( - "show_cmds\n" - ) - - assert_empty err - assert_match(/List all available commands and their description/, out) - assert_match(/Start the debugger of debug\.gem/, out) - end - - def test_show_cmds_list_user_aliases - out, err = execute_lines( - "show_cmds\n" - ) - - assert_empty err - assert_match(/\$\s+Alias for `show_source`/, out) - assert_match(/@\s+Alias for `whereami`/, out) - end - end - class LsTest < CommandTestCase def test_ls out, err = execute_lines( diff --git a/test/irb/test_debugger_integration.rb b/test/irb/test_debugger_integration.rb index d95b01c3d..839a0d43f 100644 --- a/test/irb/test_debugger_integration.rb +++ b/test/irb/test_debugger_integration.rb @@ -345,19 +345,19 @@ def test_help_command_is_delegated_to_the_debugger assert_include(output, "### Frame control") end - def test_show_cmds_display_different_content_when_debugger_is_enabled + def test_help_display_different_content_when_debugger_is_enabled write_ruby <<~'ruby' binding.irb ruby output = run_ruby_file do type "debug" - type "show_cmds" + type "help" type "continue" end # IRB's commands should still be listed - assert_match(/show_cmds\s+List all available commands and their description\./, output) + assert_match(/help\s+List all available commands/, output) # debug gem's commands should be appended at the end assert_match(/Debugging \(from debug\.gem\)\s+### Control flow/, output) end diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index e121b302c..a0477ee48 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -356,7 +356,7 @@ def test_show_cmds_with_pager_can_quit_with_ctrl_c puts 'start IRB' LINES start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') - write("show_cmds\n") + write("help\n") write("G") # move to the end of the screen write("\C-c") # quit pager write("'foo' + 'bar'\n") # eval something to make sure IRB resumes