From 7d33f0b1e2ce0f6a2a6e33b9bd6985d02f45b053 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 16 Feb 2024 18:51:38 +0000 Subject: [PATCH 1/5] Make help command display help for individual commands Usage: `help [command]` If the command is not specified, it will display a list of all available commands. If the command is specified, it will display the banner OR description of the command. If the command is not found, it will display a message saying that the command is not found. --- lib/irb/command/base.rb | 5 +++ lib/irb/command/show_cmds.rb | 31 +++++++++++++++-- test/irb/cmd/test_help.rb | 66 ++++++++++++++++++++++++++++++++++++ test/irb/test_command.rb | 33 ------------------ 4 files changed, 100 insertions(+), 35 deletions(-) create mode 100644 test/irb/cmd/test_help.rb diff --git a/lib/irb/command/base.rb b/lib/irb/command/base.rb index 87d2fea35..eec14101c 100644 --- a/lib/irb/command/base.rb +++ b/lib/irb/command/base.rb @@ -22,6 +22,11 @@ def description(description = nil) @description end + def banner(banner = nil) + @banner = banner if banner + @banner + end + private def string_literal?(args) diff --git a/lib/irb/command/show_cmds.rb b/lib/irb/command/show_cmds.rb index 940ed490d..7f266d207 100644 --- a/lib/irb/command/show_cmds.rb +++ b/lib/irb/command/show_cmds.rb @@ -12,7 +12,34 @@ class ShowCmds < Base category "IRB" description "List all available commands and their description." - def execute(*args) + 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.banner || 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] } @@ -50,7 +77,7 @@ def execute(*args) output.puts DEBUGGER__.help end - Pager.page_content(output.string) + output.string end end end diff --git a/test/irb/cmd/test_help.rb b/test/irb/cmd/test_help.rb new file mode 100644 index 000000000..f2b49e7f5 --- /dev/null +++ b/test/irb/cmd/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 and their description/, 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(/Show methods, constants, and variables\./, 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 "show_cmds" + type "exit" + end + + assert_match(/List all available commands and their description/, 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/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( From df462a2f3b763961a99cd35812cedd4d768b87ec Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 16 Feb 2024 18:56:48 +0000 Subject: [PATCH 2/5] Rename test/irb/cmd to test/irb/command --- test/irb/{cmd => command}/test_force_exit.rb | 0 test/irb/{cmd => command}/test_help.rb | 0 test/irb/{cmd => command}/test_show_source.rb | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename test/irb/{cmd => command}/test_force_exit.rb (100%) rename test/irb/{cmd => command}/test_help.rb (100%) rename test/irb/{cmd => command}/test_show_source.rb (100%) 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/cmd/test_help.rb b/test/irb/command/test_help.rb similarity index 100% rename from test/irb/cmd/test_help.rb rename to test/irb/command/test_help.rb 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 From 4186e03936f32b862f1a077f4325644ac105dd0c Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 16 Feb 2024 19:46:50 +0000 Subject: [PATCH 3/5] Add banner to edit and ls commands --- lib/irb/command/base.rb | 4 ++++ lib/irb/command/edit.rb | 18 +++++++++++++++++- lib/irb/command/ls.rb | 8 +++++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/lib/irb/command/base.rb b/lib/irb/command/base.rb index eec14101c..58f6b83c4 100644 --- a/lib/irb/command/base.rb +++ b/lib/irb/command/base.rb @@ -33,6 +33,10 @@ 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..454f66e87 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.' + banner <<~BANNER + 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 + BANNER class << self def transform_args(args) diff --git a/lib/irb/command/ls.rb b/lib/irb/command/ls.rb index bbe4a1ee9..f8508aa85 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." + + banner <<~BANNER + Usage: ls [obj] [-g [query]] + + -g [query] Filter the output with a query. + BANNER def self.transform_args(args) if match = args&.match(/\A(?.+\s|)(-g|-G)\s+(?[^\s]+)\s*\n\z/) From 90eb545f8761a4d1b8758bd5c2970d9478e5d805 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 16 Feb 2024 21:30:08 +0000 Subject: [PATCH 4/5] Promote help command in the help message 1. Make `show_cmds` an alias of `help` so it's not displayed in the help message 2. Update description of the help command to reflect `help ` syntax --- COMPARED_WITH_PRY.md | 2 +- README.md | 13 ++-- lib/irb.rb | 2 +- lib/irb/command.rb | 7 +- lib/irb/command/help.rb | 82 ++++++++++++++++++++-- lib/irb/command/show_cmds.rb | 86 ------------------------ lib/irb/statement.rb | 3 +- test/irb/command/test_help.rb | 8 +-- test/irb/test_debugger_integration.rb | 6 +- test/irb/yamatanooroti/test_rendering.rb | 2 +- 10 files changed, 96 insertions(+), 115 deletions(-) delete mode 100644 lib/irb/command/show_cmds.rb 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/help.rb b/lib/irb/command/help.rb index 67cc31a0b..a111e4d11 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.banner || 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/show_cmds.rb b/lib/irb/command/show_cmds.rb deleted file mode 100644 index 7f266d207..000000000 --- a/lib/irb/command/show_cmds.rb +++ /dev/null @@ -1,86 +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." - - 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.banner || 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") - # 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 - - 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/command/test_help.rb b/test/irb/command/test_help.rb index f2b49e7f5..c82c43a4c 100644 --- a/test/irb/command/test_help.rb +++ b/test/irb/command/test_help.rb @@ -21,7 +21,7 @@ def test_help type "exit" end - assert_match(/List all available commands and their description/, out) + assert_match(/List all available commands/, out) assert_match(/Start the debugger of debug\.gem/, out) end @@ -31,7 +31,7 @@ def test_command_help type "exit" end - assert_match(/Show methods, constants, and variables\./, out) + assert_match(/Usage: ls \[obj\]/, out) end def test_command_help_not_found @@ -45,11 +45,11 @@ def test_command_help_not_found def test_show_cmds out = run_ruby_file do - type "show_cmds" + type "help" type "exit" end - assert_match(/List all available commands and their description/, out) + assert_match(/List all available commands/, out) assert_match(/Start the debugger of debug\.gem/, out) end 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 From 3170d26bdce7d85338b9c67e4abaa33622c8ef03 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 17 Feb 2024 21:55:16 +0000 Subject: [PATCH 5/5] Rename banner to help_message --- lib/irb/command/base.rb | 6 +++--- lib/irb/command/edit.rb | 4 ++-- lib/irb/command/help.rb | 2 +- lib/irb/command/ls.rb | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/irb/command/base.rb b/lib/irb/command/base.rb index 58f6b83c4..880b781a4 100644 --- a/lib/irb/command/base.rb +++ b/lib/irb/command/base.rb @@ -22,9 +22,9 @@ def description(description = nil) @description end - def banner(banner = nil) - @banner = banner if banner - @banner + def help_message(help_message = nil) + @help_message = help_message if help_message + @help_message end private diff --git a/lib/irb/command/edit.rb b/lib/irb/command/edit.rb index 454f66e87..ab8c62663 100644 --- a/lib/irb/command/edit.rb +++ b/lib/irb/command/edit.rb @@ -9,7 +9,7 @@ module Command class Edit < Base category "Misc" description 'Open a file or source location.' - banner <<~BANNER + 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"]')} @@ -24,7 +24,7 @@ class Edit < Base edit foo.rb edit Foo edit Foo#bar - BANNER + HELP_MESSAGE class << self def transform_args(args) diff --git a/lib/irb/command/help.rb b/lib/irb/command/help.rb index a111e4d11..19113dbbf 100644 --- a/lib/irb/command/help.rb +++ b/lib/irb/command/help.rb @@ -21,7 +21,7 @@ def execute(command_name = nil) content = if command_name if command_class = ExtendCommandBundle.load_command(command_name) - command_class.banner || command_class.description + 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 diff --git a/lib/irb/command/ls.rb b/lib/irb/command/ls.rb index f8508aa85..6b6136c2f 100644 --- a/lib/irb/command/ls.rb +++ b/lib/irb/command/ls.rb @@ -14,11 +14,11 @@ class Ls < Base category "Context" description "Show methods, constants, and variables." - banner <<~BANNER + help_message <<~HELP_MESSAGE Usage: ls [obj] [-g [query]] -g [query] Filter the output with a query. - BANNER + HELP_MESSAGE def self.transform_args(args) if match = args&.match(/\A(?.+\s|)(-g|-G)\s+(?[^\s]+)\s*\n\z/)