diff --git a/lib/irb/command/cd.rb b/lib/irb/command/cd.rb new file mode 100644 index 000000000..b83c8689a --- /dev/null +++ b/lib/irb/command/cd.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module IRB + module Command + class CD < Base + category "Workspace" + description "Move into the given object or leave the current context." + + help_message(<<~HELP) + Usage: cd ([target]|..) + + IRB uses a stack of workspaces to keep track of context(s), with `pushws` and `popws` commands to manipulate the stack. + The `cd` command is an attempt to simplify the operation and will be subject to change. + + When given: + - an object, cd will use that object as the new context by pushing it onto the workspace stack. + - "..", cd will leave the current context by popping the top workspace off the stack. + - no arguments, cd will move to the top workspace on the stack by popping off all workspaces. + + Examples: + + cd Foo + cd Foo.new + cd @ivar + cd .. + cd + HELP + + def execute(arg) + case arg + when ".." + irb_context.pop_workspace + when "" + # TODO: decide what workspace commands should be kept, and underlying APIs should look like, + # and perhaps add a new API to clear the workspace stack. + prev_workspace = irb_context.pop_workspace + while prev_workspace + prev_workspace = irb_context.pop_workspace + end + else + begin + obj = eval(arg, irb_context.workspace.binding) + irb_context.push_workspace(obj) + rescue StandardError => e + warn "Error: #{e}" + end + end + end + end + end +end diff --git a/lib/irb/default_commands.rb b/lib/irb/default_commands.rb index 91c6b2d04..e27a3d4e0 100644 --- a/lib/irb/default_commands.rb +++ b/lib/irb/default_commands.rb @@ -5,6 +5,7 @@ require_relative "command/backtrace" require_relative "command/break" require_relative "command/catch" +require_relative "command/cd" require_relative "command/chws" require_relative "command/context" require_relative "command/continue" @@ -240,6 +241,8 @@ def load_command(command) _register_with_aliases(:irb_disable_irb, Command::DisableIrb, [:disable_irb, NO_OVERRIDE] ) + + register(:cd, Command::CD) end ExtendCommand = Command diff --git a/test/irb/command/test_cd.rb b/test/irb/command/test_cd.rb new file mode 100644 index 000000000..4537286f7 --- /dev/null +++ b/test/irb/command/test_cd.rb @@ -0,0 +1,65 @@ +require "tempfile" +require_relative "../helper" + +module TestIRB + class CDTest < IntegrationTestCase + def setup + super + + write_ruby <<~'RUBY' + class Foo + class Bar + def bar + "this is bar" + end + end + + def foo + "this is foo" + end + end + + binding.irb + RUBY + end + + def test_cd + out = run_ruby_file do + type "cd Foo" + type "ls" + type "cd Bar" + type "ls" + type "cd .." + type "exit" + end + + assert_match(/irb\(Foo\):002>/, out) + assert_match(/Foo#methods: foo/, out) + assert_match(/irb\(Foo::Bar\):004>/, out) + assert_match(/Bar#methods: bar/, out) + assert_match(/irb\(Foo\):006>/, out) + end + + def test_cd_moves_top_level_with_no_args + out = run_ruby_file do + type "cd Foo" + type "cd Bar" + type "cd" + type "exit" + end + + assert_match(/irb\(Foo::Bar\):003>/, out) + assert_match(/irb\(main\):004>/, out) + end + + def test_cd_with_error + out = run_ruby_file do + type "cd Baz" + type "exit" + end + + assert_match(/Error: uninitialized constant Baz/, out) + assert_match(/irb\(main\):002>/, out) # the context should not change + end + end +end