summaryrefslogtreecommitdiff
path: root/lib/irb/command
diff options
context:
space:
mode:
Diffstat (limited to 'lib/irb/command')
-rw-r--r--lib/irb/command/backtrace.rb21
-rw-r--r--lib/irb/command/base.rb55
-rw-r--r--lib/irb/command/break.rb21
-rw-r--r--lib/irb/command/catch.rb21
-rw-r--r--lib/irb/command/chws.rb34
-rw-r--r--lib/irb/command/continue.rb17
-rw-r--r--lib/irb/command/debug.rb79
-rw-r--r--lib/irb/command/delete.rb17
-rw-r--r--lib/irb/command/edit.rb54
-rw-r--r--lib/irb/command/exit.rb20
-rw-r--r--lib/irb/command/finish.rb17
-rw-r--r--lib/irb/command/force_exit.rb20
-rw-r--r--lib/irb/command/help.rb12
-rw-r--r--lib/irb/command/history.rb47
-rw-r--r--lib/irb/command/info.rb21
-rw-r--r--lib/irb/command/irb_info.rb32
-rw-r--r--lib/irb/command/load.rb74
-rw-r--r--lib/irb/command/ls.rb139
-rw-r--r--lib/irb/command/measure.rb43
-rw-r--r--lib/irb/command/next.rb17
-rw-r--r--lib/irb/command/pushws.rb44
-rw-r--r--lib/irb/command/show_cmds.rb59
-rw-r--r--lib/irb/command/show_doc.rb46
-rw-r--r--lib/irb/command/show_source.rb67
-rw-r--r--lib/irb/command/step.rb17
-rw-r--r--lib/irb/command/subirb.rb107
-rw-r--r--lib/irb/command/whereami.rb23
27 files changed, 1124 insertions, 0 deletions
diff --git a/lib/irb/command/backtrace.rb b/lib/irb/command/backtrace.rb
new file mode 100644
index 0000000000..47e5e60724
--- /dev/null
+++ b/lib/irb/command/backtrace.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require_relative "debug"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Backtrace < DebugCommand
+ def self.transform_args(args)
+ args&.dump
+ end
+
+ def execute(*args)
+ super(pre_cmds: ["backtrace", *args].join(" "))
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/base.rb b/lib/irb/command/base.rb
new file mode 100644
index 0000000000..87d2fea356
--- /dev/null
+++ b/lib/irb/command/base.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: false
+#
+# nop.rb -
+# by Keiju ISHITSUKA([email protected])
+#
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class CommandArgumentError < StandardError; end
+
+ class Base
+ class << self
+ def category(category = nil)
+ @category = category if category
+ @category
+ end
+
+ def description(description = nil)
+ @description = description if description
+ @description
+ end
+
+ private
+
+ def string_literal?(args)
+ sexp = Ripper.sexp(args)
+ sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal
+ end
+ end
+
+ def self.execute(irb_context, *opts, **kwargs, &block)
+ command = new(irb_context)
+ command.execute(*opts, **kwargs, &block)
+ rescue CommandArgumentError => e
+ puts e.message
+ end
+
+ def initialize(irb_context)
+ @irb_context = irb_context
+ end
+
+ attr_reader :irb_context
+
+ def execute(*opts)
+ #nop
+ end
+ end
+
+ Nop = Base
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/break.rb b/lib/irb/command/break.rb
new file mode 100644
index 0000000000..fa200f2d78
--- /dev/null
+++ b/lib/irb/command/break.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require_relative "debug"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Break < DebugCommand
+ def self.transform_args(args)
+ args&.dump
+ end
+
+ def execute(args = nil)
+ super(pre_cmds: "break #{args}")
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/catch.rb b/lib/irb/command/catch.rb
new file mode 100644
index 0000000000..6b2edff5e5
--- /dev/null
+++ b/lib/irb/command/catch.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require_relative "debug"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Catch < DebugCommand
+ def self.transform_args(args)
+ args&.dump
+ end
+
+ def execute(*args)
+ super(pre_cmds: ["catch", *args].join(" "))
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/chws.rb b/lib/irb/command/chws.rb
new file mode 100644
index 0000000000..341d516155
--- /dev/null
+++ b/lib/irb/command/chws.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: false
+#
+# change-ws.rb -
+# by Keiju ISHITSUKA([email protected])
+#
+require_relative "../ext/change-ws"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+
+ class CurrentWorkingWorkspace < Base
+ category "Workspace"
+ description "Show the current workspace."
+
+ def execute(*obj)
+ irb_context.main
+ end
+ end
+
+ class ChangeWorkspace < Base
+ category "Workspace"
+ description "Change the current workspace to an object."
+
+ def execute(*obj)
+ irb_context.change_workspace(*obj)
+ irb_context.main
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/continue.rb b/lib/irb/command/continue.rb
new file mode 100644
index 0000000000..8b6ffc860c
--- /dev/null
+++ b/lib/irb/command/continue.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require_relative "debug"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Continue < DebugCommand
+ def execute(*args)
+ super(do_cmds: ["continue", *args].join(" "))
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/debug.rb b/lib/irb/command/debug.rb
new file mode 100644
index 0000000000..9a2c641251
--- /dev/null
+++ b/lib/irb/command/debug.rb
@@ -0,0 +1,79 @@
+require_relative "../debug"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Debug < Base
+ category "Debugging"
+ description "Start the debugger of debug.gem."
+
+ BINDING_IRB_FRAME_REGEXPS = [
+ '<internal:prelude>',
+ binding.method(:irb).source_location.first,
+ ].map { |file| /\A#{Regexp.escape(file)}:\d+:in `irb'\z/ }
+
+ def execute(pre_cmds: nil, do_cmds: nil)
+ if irb_context.with_debugger
+ # If IRB is already running with a debug session, throw the command and IRB.debug_readline will pass it to the debugger.
+ if cmd = pre_cmds || do_cmds
+ throw :IRB_EXIT, cmd
+ else
+ puts "IRB is already running with a debug session."
+ return
+ end
+ else
+ # If IRB is not running with a debug session yet, then:
+ # 1. Check if the debugging command is run from a `binding.irb` call.
+ # 2. If so, try setting up the debug gem.
+ # 3. Insert a debug breakpoint at `Irb#debug_break` with the intended command.
+ # 4. Exit the current Irb#run call via `throw :IRB_EXIT`.
+ # 5. `Irb#debug_break` will be called and trigger the breakpoint, which will run the intended command.
+ unless binding_irb?
+ puts "Debugging commands are only available when IRB is started with binding.irb"
+ return
+ end
+
+ if IRB.respond_to?(:JobManager)
+ warn "Can't start the debugger when IRB is running in a multi-IRB session."
+ return
+ end
+
+ unless IRB::Debug.setup(irb_context.irb)
+ puts <<~MSG
+ You need to install the debug gem before using this command.
+ If you use `bundle exec`, please add `gem "debug"` into your Gemfile.
+ MSG
+ return
+ end
+
+ IRB::Debug.insert_debug_break(pre_cmds: pre_cmds, do_cmds: do_cmds)
+
+ # exit current Irb#run call
+ throw :IRB_EXIT
+ end
+ end
+
+ private
+
+ def binding_irb?
+ caller.any? do |frame|
+ BINDING_IRB_FRAME_REGEXPS.any? do |regexp|
+ frame.match?(regexp)
+ end
+ end
+ end
+ end
+
+ class DebugCommand < Debug
+ def self.category
+ "Debugging"
+ end
+
+ def self.description
+ command_name = self.name.split("::").last.downcase
+ "Start the debugger of debug.gem and run its `#{command_name}` command."
+ end
+ end
+ end
+end
diff --git a/lib/irb/command/delete.rb b/lib/irb/command/delete.rb
new file mode 100644
index 0000000000..a36b4577b0
--- /dev/null
+++ b/lib/irb/command/delete.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require_relative "debug"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Delete < DebugCommand
+ def execute(*args)
+ super(pre_cmds: ["delete", *args].join(" "))
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/edit.rb b/lib/irb/command/edit.rb
new file mode 100644
index 0000000000..1a8ded6bcf
--- /dev/null
+++ b/lib/irb/command/edit.rb
@@ -0,0 +1,54 @@
+require 'shellwords'
+
+require_relative "../source_finder"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Edit < Base
+ category "Misc"
+ description 'Open a file with the editor command defined with `ENV["VISUAL"]` or `ENV["EDITOR"]`.'
+
+ class << self
+ def transform_args(args)
+ # Return a string literal as is for backward compatibility
+ if args.nil? || args.empty? || string_literal?(args)
+ args
+ else # Otherwise, consider the input as a String for convenience
+ args.strip.dump
+ end
+ end
+ end
+
+ def execute(*args)
+ path = args.first
+
+ if path.nil?
+ path = @irb_context.irb_path
+ elsif !File.exist?(path)
+ source = SourceFinder.new(@irb_context).find_source(path)
+
+ if source&.file_exist? && !source.binary_file?
+ path = source.file
+ end
+ end
+
+ unless File.exist?(path)
+ puts "Can not find file: #{path}"
+ return
+ end
+
+ if editor = (ENV['VISUAL'] || ENV['EDITOR'])
+ puts "command: '#{editor}'"
+ puts " path: #{path}"
+ system(*Shellwords.split(editor), path)
+ else
+ puts "Can not find editor setting: ENV['VISUAL'] or ENV['EDITOR']"
+ end
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/exit.rb b/lib/irb/command/exit.rb
new file mode 100644
index 0000000000..3109ec16e3
--- /dev/null
+++ b/lib/irb/command/exit.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Exit < Base
+ category "IRB"
+ description "Exit the current irb session."
+
+ def execute(*)
+ IRB.irb_exit
+ rescue UncaughtThrowError
+ Kernel.exit
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/finish.rb b/lib/irb/command/finish.rb
new file mode 100644
index 0000000000..05501819e2
--- /dev/null
+++ b/lib/irb/command/finish.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require_relative "debug"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Finish < DebugCommand
+ def execute(*args)
+ super(do_cmds: ["finish", *args].join(" "))
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/force_exit.rb b/lib/irb/command/force_exit.rb
new file mode 100644
index 0000000000..c2c5542e24
--- /dev/null
+++ b/lib/irb/command/force_exit.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class ForceExit < Base
+ category "IRB"
+ description "Exit the current process."
+
+ def execute(*)
+ throw :IRB_EXIT, true
+ rescue UncaughtThrowError
+ Kernel.exit!
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/help.rb b/lib/irb/command/help.rb
new file mode 100644
index 0000000000..67cc31a0bf
--- /dev/null
+++ b/lib/irb/command/help.rb
@@ -0,0 +1,12 @@
+# 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."
+ end
+ end
+end
diff --git a/lib/irb/command/history.rb b/lib/irb/command/history.rb
new file mode 100644
index 0000000000..a47a8795dd
--- /dev/null
+++ b/lib/irb/command/history.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require "stringio"
+
+require_relative "../pager"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class History < Base
+ category "IRB"
+ description "Shows the input history. `-g [query]` or `-G [query]` allows you to filter the output."
+
+ def self.transform_args(args)
+ match = args&.match(/(-g|-G)\s+(?<grep>.+)\s*\n\z/)
+ return unless match
+
+ "grep: #{Regexp.new(match[:grep]).inspect}"
+ end
+
+ def execute(grep: nil)
+ formatted_inputs = irb_context.io.class::HISTORY.each_with_index.reverse_each.filter_map do |input, index|
+ next if grep && !input.match?(grep)
+
+ header = "#{index}: "
+
+ first_line, *other_lines = input.split("\n")
+ first_line = "#{header}#{first_line}"
+
+ truncated_lines = other_lines.slice!(1..) # Show 1 additional line (2 total)
+ other_lines << "..." if truncated_lines&.any?
+
+ other_lines.map! do |line|
+ " " * header.length + line
+ end
+
+ [first_line, *other_lines].join("\n") + "\n"
+ end
+
+ Pager.page_content(formatted_inputs.join)
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/info.rb b/lib/irb/command/info.rb
new file mode 100644
index 0000000000..a67be3eb85
--- /dev/null
+++ b/lib/irb/command/info.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require_relative "debug"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Info < DebugCommand
+ def self.transform_args(args)
+ args&.dump
+ end
+
+ def execute(*args)
+ super(pre_cmds: ["info", *args].join(" "))
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/irb_info.rb b/lib/irb/command/irb_info.rb
new file mode 100644
index 0000000000..7fd3e2104a
--- /dev/null
+++ b/lib/irb/command/irb_info.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: false
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class IrbInfo < Base
+ category "IRB"
+ description "Show information about IRB."
+
+ def execute
+ str = "Ruby version: #{RUBY_VERSION}\n"
+ str += "IRB version: #{IRB.version}\n"
+ str += "InputMethod: #{IRB.CurrentContext.io.inspect}\n"
+ str += "Completion: #{IRB.CurrentContext.io.respond_to?(:completion_info) ? IRB.CurrentContext.io.completion_info : 'off'}\n"
+ str += ".irbrc path: #{IRB.rc_file}\n" if File.exist?(IRB.rc_file)
+ str += "RUBY_PLATFORM: #{RUBY_PLATFORM}\n"
+ str += "LANG env: #{ENV["LANG"]}\n" if ENV["LANG"] && !ENV["LANG"].empty?
+ str += "LC_ALL env: #{ENV["LC_ALL"]}\n" if ENV["LC_ALL"] && !ENV["LC_ALL"].empty?
+ str += "East Asian Ambiguous Width: #{Reline.ambiguous_width.inspect}\n"
+ if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
+ codepage = `chcp`.b.sub(/.*: (\d+)\n/, '\1')
+ str += "Code page: #{codepage}\n"
+ end
+ puts str
+ nil
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/load.rb b/lib/irb/command/load.rb
new file mode 100644
index 0000000000..0558bc83b0
--- /dev/null
+++ b/lib/irb/command/load.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: false
+#
+# load.rb -
+# by Keiju ISHITSUKA([email protected])
+#
+require_relative "../ext/loader"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class LoaderCommand < Base
+ include IrbLoader
+
+ def raise_cmd_argument_error
+ raise CommandArgumentError.new("Please specify the file name.")
+ end
+ end
+
+ class Load < LoaderCommand
+ category "IRB"
+ description "Load a Ruby file."
+
+ def execute(file_name = nil, priv = nil)
+ raise_cmd_argument_error unless file_name
+ irb_load(file_name, priv)
+ end
+ end
+
+ class Require < LoaderCommand
+ category "IRB"
+ description "Require a Ruby file."
+ def execute(file_name = nil)
+ raise_cmd_argument_error unless file_name
+
+ rex = Regexp.new("#{Regexp.quote(file_name)}(\.o|\.rb)?")
+ return false if $".find{|f| f =~ rex}
+
+ case file_name
+ when /\.rb$/
+ begin
+ if irb_load(file_name)
+ $".push file_name
+ return true
+ end
+ rescue LoadError
+ end
+ when /\.(so|o|sl)$/
+ return ruby_require(file_name)
+ end
+
+ begin
+ irb_load(f = file_name + ".rb")
+ $".push f
+ return true
+ rescue LoadError
+ return ruby_require(file_name)
+ end
+ end
+ end
+
+ class Source < LoaderCommand
+ category "IRB"
+ description "Loads a given file in the current session."
+
+ def execute(file_name = nil)
+ raise_cmd_argument_error unless file_name
+
+ source_file(file_name)
+ end
+ end
+ end
+ # :startdoc:
+end
diff --git a/lib/irb/command/ls.rb b/lib/irb/command/ls.rb
new file mode 100644
index 0000000000..bbe4a1ee98
--- /dev/null
+++ b/lib/irb/command/ls.rb
@@ -0,0 +1,139 @@
+# frozen_string_literal: true
+
+require "reline"
+require "stringio"
+
+require_relative "../pager"
+require_relative "../color"
+
+module IRB
+ # :stopdoc:
+
+ 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."
+
+ def self.transform_args(args)
+ if match = args&.match(/\A(?<args>.+\s|)(-g|-G)\s+(?<grep>[^\s]+)\s*\n\z/)
+ args = match[:args]
+ "#{args}#{',' unless args.chomp.empty?} grep: /#{match[:grep]}/"
+ else
+ args
+ end
+ end
+
+ def execute(*arg, grep: nil)
+ o = Output.new(grep: grep)
+
+ obj = arg.empty? ? irb_context.workspace.main : arg.first
+ locals = arg.empty? ? irb_context.workspace.binding.local_variables : []
+ klass = (obj.class == Class || obj.class == Module ? obj : obj.class)
+
+ o.dump("constants", obj.constants) if obj.respond_to?(:constants)
+ dump_methods(o, klass, obj)
+ o.dump("instance variables", obj.instance_variables)
+ o.dump("class variables", klass.class_variables)
+ o.dump("locals", locals)
+ o.print_result
+ end
+
+ def dump_methods(o, klass, obj)
+ singleton_class = begin obj.singleton_class; rescue TypeError; nil end
+ dumped_mods = Array.new
+ ancestors = klass.ancestors
+ ancestors = ancestors.reject { |c| c >= Object } if klass < Object
+ singleton_ancestors = (singleton_class&.ancestors || []).reject { |c| c >= Class }
+
+ # singleton_class' ancestors should be at the front
+ maps = class_method_map(singleton_ancestors, dumped_mods) + class_method_map(ancestors, dumped_mods)
+ maps.each do |mod, methods|
+ name = mod == singleton_class ? "#{klass}.methods" : "#{mod}#methods"
+ o.dump(name, methods)
+ end
+ end
+
+ def class_method_map(classes, dumped_mods)
+ dumped_methods = Array.new
+ classes.map do |mod|
+ next if dumped_mods.include? mod
+
+ dumped_mods << mod
+
+ methods = mod.public_instance_methods(false).select do |method|
+ if dumped_methods.include? method
+ false
+ else
+ dumped_methods << method
+ true
+ end
+ end
+
+ [mod, methods]
+ end.compact
+ end
+
+ class Output
+ MARGIN = " "
+
+ def initialize(grep: nil)
+ @grep = grep
+ @line_width = screen_width - MARGIN.length # right padding
+ @io = StringIO.new
+ end
+
+ def print_result
+ Pager.page_content(@io.string)
+ end
+
+ def dump(name, strs)
+ strs = strs.grep(@grep) if @grep
+ strs = strs.sort
+ return if strs.empty?
+
+ # Attempt a single line
+ @io.print "#{Color.colorize(name, [:BOLD, :BLUE])}: "
+ if fits_on_line?(strs, cols: strs.size, offset: "#{name}: ".length)
+ @io.puts strs.join(MARGIN)
+ return
+ end
+ @io.puts
+
+ # Dump with the largest # of columns that fits on a line
+ cols = strs.size
+ until fits_on_line?(strs, cols: cols, offset: MARGIN.length) || cols == 1
+ cols -= 1
+ end
+ widths = col_widths(strs, cols: cols)
+ strs.each_slice(cols) do |ss|
+ @io.puts ss.map.with_index { |s, i| "#{MARGIN}%-#{widths[i]}s" % s }.join
+ end
+ end
+
+ private
+
+ def fits_on_line?(strs, cols:, offset: 0)
+ width = col_widths(strs, cols: cols).sum + MARGIN.length * (cols - 1)
+ width <= @line_width - offset
+ end
+
+ def col_widths(strs, cols:)
+ cols.times.map do |col|
+ (col...strs.size).step(cols).map do |i|
+ strs[i].length
+ end.max
+ end
+ end
+
+ def screen_width
+ Reline.get_screen_size.last
+ rescue Errno::EINVAL # in `winsize': Invalid argument - <STDIN>
+ 80
+ end
+ end
+ private_constant :Output
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/measure.rb b/lib/irb/command/measure.rb
new file mode 100644
index 0000000000..ee7927b010
--- /dev/null
+++ b/lib/irb/command/measure.rb
@@ -0,0 +1,43 @@
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Measure < Base
+ category "Misc"
+ description "`measure` enables the mode to measure processing time. `measure :off` disables it."
+
+ def initialize(*args)
+ super(*args)
+ end
+
+ def execute(type = nil, arg = nil)
+ # Please check IRB.init_config in lib/irb/init.rb that sets
+ # IRB.conf[:MEASURE_PROC] to register default "measure" methods,
+ # "measure :time" (abbreviated as "measure") and "measure :stackprof".
+
+ if block_given?
+ warn 'Configure IRB.conf[:MEASURE_PROC] to add custom measure methods.'
+ return
+ end
+
+ case type
+ when :off
+ IRB.unset_measure_callback(arg)
+ when :list
+ IRB.conf[:MEASURE_CALLBACKS].each do |type_name, _, arg_val|
+ puts "- #{type_name}" + (arg_val ? "(#{arg_val.inspect})" : '')
+ end
+ when :on
+ added = IRB.set_measure_callback(arg)
+ puts "#{added[0]} is added." if added
+ else
+ added = IRB.set_measure_callback(type, arg)
+ puts "#{added[0]} is added." if added
+ end
+ nil
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/next.rb b/lib/irb/command/next.rb
new file mode 100644
index 0000000000..6487c9d24c
--- /dev/null
+++ b/lib/irb/command/next.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require_relative "debug"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Next < DebugCommand
+ def execute(*args)
+ super(do_cmds: ["next", *args].join(" "))
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/pushws.rb b/lib/irb/command/pushws.rb
new file mode 100644
index 0000000000..a6fcd6a165
--- /dev/null
+++ b/lib/irb/command/pushws.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: false
+#
+# change-ws.rb -
+# by Keiju ISHITSUKA([email protected])
+#
+
+require_relative "../ext/workspaces"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Workspaces < Base
+ category "Workspace"
+ description "Show workspaces."
+
+ def execute(*obj)
+ irb_context.workspaces.collect{|ws| ws.main}
+ end
+ end
+
+ class PushWorkspace < Workspaces
+ category "Workspace"
+ description "Push an object to the workspace stack."
+
+ def execute(*obj)
+ irb_context.push_workspace(*obj)
+ super
+ end
+ end
+
+ class PopWorkspace < Workspaces
+ category "Workspace"
+ description "Pop a workspace from the workspace stack."
+
+ def execute(*obj)
+ irb_context.pop_workspace(*obj)
+ super
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/show_cmds.rb b/lib/irb/command/show_cmds.rb
new file mode 100644
index 0000000000..940ed490d3
--- /dev/null
+++ b/lib/irb/command/show_cmds.rb
@@ -0,0 +1,59 @@
+# 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/command/show_doc.rb b/lib/irb/command/show_doc.rb
new file mode 100644
index 0000000000..dca10ec4be
--- /dev/null
+++ b/lib/irb/command/show_doc.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module IRB
+ module Command
+ class ShowDoc < Base
+ 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
+
+ category "Context"
+ description "Enter the mode to look up RI documents."
+
+ def execute(*names)
+ require 'rdoc/ri/driver'
+
+ unless ShowDoc.const_defined?(:Ri)
+ opts = RDoc::RI::Driver.process_args([])
+ ShowDoc.const_set(:Ri, RDoc::RI::Driver.new(opts))
+ end
+
+ if names.empty?
+ Ri.interactive
+ else
+ names.each do |name|
+ begin
+ Ri.display_name(name.to_s)
+ rescue RDoc::RI::Error
+ puts $!.message
+ end
+ end
+ end
+
+ nil
+ rescue LoadError, SystemExit
+ warn "Can't display document because `rdoc` is not installed."
+ end
+ end
+ end
+end
diff --git a/lib/irb/command/show_source.rb b/lib/irb/command/show_source.rb
new file mode 100644
index 0000000000..cc783e7532
--- /dev/null
+++ b/lib/irb/command/show_source.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require_relative "../source_finder"
+require_relative "../pager"
+require_relative "../color"
+
+module IRB
+ module Command
+ class ShowSource < Base
+ category "Context"
+ description "Show the source code of a given method or constant."
+
+ 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(str = nil)
+ unless str.is_a?(String)
+ puts "Error: Expected a string but got #{str.inspect}"
+ return
+ end
+
+ str, esses = str.split(" -")
+ super_level = esses ? esses.count("s") : 0
+ source = SourceFinder.new(@irb_context).find_source(str, super_level)
+
+ if source
+ show_source(source)
+ elsif super_level > 0
+ puts "Error: Couldn't locate a super definition for #{str}"
+ else
+ puts "Error: Couldn't locate a definition for #{str}"
+ end
+ nil
+ end
+
+ private
+
+ def show_source(source)
+ if source.binary_file?
+ content = "\n#{bold('Defined in binary file')}: #{source.file}\n\n"
+ else
+ code = source.colorized_content || 'Source not available'
+ content = <<~CONTENT
+
+ #{bold("From")}: #{source.file}:#{source.line}
+
+ #{code.chomp}
+
+ CONTENT
+ end
+ Pager.page_content(content)
+ end
+
+ def bold(str)
+ Color.colorize(str, [:BOLD])
+ end
+ end
+ end
+end
diff --git a/lib/irb/command/step.rb b/lib/irb/command/step.rb
new file mode 100644
index 0000000000..cce7d2b0f8
--- /dev/null
+++ b/lib/irb/command/step.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require_relative "debug"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Step < DebugCommand
+ def execute(*args)
+ super(do_cmds: ["step", *args].join(" "))
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/subirb.rb b/lib/irb/command/subirb.rb
new file mode 100644
index 0000000000..0a75706f5f
--- /dev/null
+++ b/lib/irb/command/subirb.rb
@@ -0,0 +1,107 @@
+# frozen_string_literal: false
+#
+# multi.rb -
+# by Keiju ISHITSUKA([email protected])
+#
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class MultiIRBCommand < Base
+ def execute(*args)
+ extend_irb_context
+ end
+
+ private
+
+ def print_deprecated_warning
+ warn <<~MSG
+ Multi-irb commands are deprecated and will be removed in IRB 2.0.0. Please use workspace commands instead.
+ If you have any use case for multi-irb, please leave a comment at https://github.com/ruby/irb/issues/653
+ MSG
+ end
+
+ def extend_irb_context
+ # this extension patches IRB context like IRB.CurrentContext
+ require_relative "../ext/multi-irb"
+ end
+
+ def print_debugger_warning
+ warn "Multi-IRB commands are not available when the debugger is enabled."
+ end
+ end
+
+ class IrbCommand < MultiIRBCommand
+ category "Multi-irb (DEPRECATED)"
+ description "Start a child IRB."
+
+ def execute(*obj)
+ print_deprecated_warning
+
+ if irb_context.with_debugger
+ print_debugger_warning
+ return
+ end
+
+ super
+ IRB.irb(nil, *obj)
+ end
+ end
+
+ class Jobs < MultiIRBCommand
+ category "Multi-irb (DEPRECATED)"
+ description "List of current sessions."
+
+ def execute
+ print_deprecated_warning
+
+ if irb_context.with_debugger
+ print_debugger_warning
+ return
+ end
+
+ super
+ IRB.JobManager
+ end
+ end
+
+ class Foreground < MultiIRBCommand
+ category "Multi-irb (DEPRECATED)"
+ description "Switches to the session of the given number."
+
+ def execute(key = nil)
+ print_deprecated_warning
+
+ if irb_context.with_debugger
+ print_debugger_warning
+ return
+ end
+
+ super
+
+ raise CommandArgumentError.new("Please specify the id of target IRB job (listed in the `jobs` command).") unless key
+ IRB.JobManager.switch(key)
+ end
+ end
+
+ class Kill < MultiIRBCommand
+ category "Multi-irb (DEPRECATED)"
+ description "Kills the session with the given number."
+
+ def execute(*keys)
+ print_deprecated_warning
+
+ if irb_context.with_debugger
+ print_debugger_warning
+ return
+ end
+
+ super
+ IRB.JobManager.kill(*keys)
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/whereami.rb b/lib/irb/command/whereami.rb
new file mode 100644
index 0000000000..d6658d7043
--- /dev/null
+++ b/lib/irb/command/whereami.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Whereami < Base
+ category "Context"
+ description "Show the source code around binding.irb again."
+
+ def execute(*)
+ code = irb_context.workspace.code_around_binding
+ if code
+ puts code
+ else
+ puts "The current context doesn't have code."
+ end
+ end
+ end
+ end
+
+ # :startdoc:
+end