diff options
Diffstat (limited to 'lib/irb/command')
29 files changed, 1279 insertions, 0 deletions
diff --git a/lib/irb/command/backtrace.rb b/lib/irb/command/backtrace.rb new file mode 100644 index 0000000000..687bb075ac --- /dev/null +++ b/lib/irb/command/backtrace.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module Command + class Backtrace < DebugCommand + def execute(arg) + execute_debug_command(pre_cmds: "backtrace #{arg}") + 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..1d406630a2 --- /dev/null +++ b/lib/irb/command/base.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true +# +# nop.rb - +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +module IRB + # :stopdoc: + + module Command + class CommandArgumentError < StandardError; end + + def self.extract_ruby_args(*args, **kwargs) + throw :EXTRACT_RUBY_ARGS, [args, kwargs] + end + + class Base + class << self + def category(category = nil) + @category = category if category + @category || "No category" + end + + def description(description = nil) + @description = description if description + @description || "No description provided." + end + + def help_message(help_message = nil) + @help_message = help_message if help_message + @help_message + end + + private + + def highlight(text) + Color.colorize(text, [:BOLD, :BLUE]) + end + end + + def self.execute(irb_context, arg) + new(irb_context).execute(arg) + rescue CommandArgumentError => e + puts e.message + end + + def initialize(irb_context) + @irb_context = irb_context + end + + attr_reader :irb_context + + def execute(arg) + #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..a8f81fe665 --- /dev/null +++ b/lib/irb/command/break.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module Command + class Break < DebugCommand + def execute(arg) + execute_debug_command(pre_cmds: "break #{arg}") + 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..529dcbca5a --- /dev/null +++ b/lib/irb/command/catch.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module Command + class Catch < DebugCommand + def execute(arg) + execute_debug_command(pre_cmds: "catch #{arg}") + 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..ef456d0961 --- /dev/null +++ b/lib/irb/command/chws.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true +# +# change-ws.rb - +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# +require_relative "../ext/change-ws" + +module IRB + # :stopdoc: + + module Command + + class CurrentWorkingWorkspace < Base + category "Workspace" + description "Show the current workspace." + + def execute(_arg) + puts "Current workspace: #{irb_context.main}" + end + end + + class ChangeWorkspace < Base + category "Workspace" + description "Change the current workspace to an object." + + def execute(arg) + if arg.empty? + irb_context.change_workspace + else + obj = eval(arg, irb_context.workspace.binding) + irb_context.change_workspace(obj) + end + + puts "Current workspace: #{irb_context.main}" + end + end + end + + # :startdoc: +end diff --git a/lib/irb/command/context.rb b/lib/irb/command/context.rb new file mode 100644 index 0000000000..b4fc807343 --- /dev/null +++ b/lib/irb/command/context.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module IRB + module Command + class Context < Base + category "IRB" + description "Displays current configuration." + + def execute(_arg) + # This command just displays the configuration. + # Modifying the configuration is achieved by sending a message to IRB.conf. + Pager.page_content(IRB.CurrentContext.inspect) + end + end + end +end diff --git a/lib/irb/command/continue.rb b/lib/irb/command/continue.rb new file mode 100644 index 0000000000..0daa029b15 --- /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(arg) + execute_debug_command(do_cmds: "continue #{arg}") + 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..8a091a49ed --- /dev/null +++ b/lib/irb/command/debug.rb @@ -0,0 +1,71 @@ +require_relative "../debug" + +module IRB + # :stopdoc: + + module Command + class Debug < Base + category "Debugging" + description "Start the debugger of debug.gem." + + def execute(_arg) + execute_debug_command + end + + def execute_debug_command(pre_cmds: nil, do_cmds: nil) + pre_cmds = pre_cmds&.rstrip + do_cmds = do_cmds&.rstrip + + 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 irb_context.from_binding? + 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 + 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..2a57a4a3de --- /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(arg) + execute_debug_command(pre_cmds: "delete #{arg}") + end + end + end + + # :startdoc: +end diff --git a/lib/irb/command/disable_irb.rb b/lib/irb/command/disable_irb.rb new file mode 100644 index 0000000000..0b00d0302b --- /dev/null +++ b/lib/irb/command/disable_irb.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module IRB + # :stopdoc: + + module Command + class DisableIrb < Base + category "IRB" + description "Disable binding.irb." + + def execute(*) + ::Binding.define_method(:irb) {} + IRB.irb_exit + 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..cb7e0c4873 --- /dev/null +++ b/lib/irb/command/edit.rb @@ -0,0 +1,63 @@ +require 'shellwords' + +require_relative "../color" +require_relative "../source_finder" + +module IRB + # :stopdoc: + + module Command + class Edit < Base + include RubyArgsExtractor + + category "Misc" + 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 + + def execute(arg) + # Accept string literal for backward compatibility + path = unwrap_string_literal(arg) + + 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..b4436f0343 --- /dev/null +++ b/lib/irb/command/exit.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module IRB + # :stopdoc: + + module Command + class Exit < Base + category "IRB" + description "Exit the current irb session." + + def execute(_arg) + IRB.irb_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..3311a0e6e9 --- /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(arg) + execute_debug_command(do_cmds: "finish #{arg}") + 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..14086aa849 --- /dev/null +++ b/lib/irb/command/force_exit.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module IRB + # :stopdoc: + + module Command + class ForceExit < Base + category "IRB" + description "Exit the current process." + + def execute(_arg) + throw :IRB_EXIT, true + 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..c2018f9b30 --- /dev/null +++ b/lib/irb/command/help.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +module IRB + module Command + class Help < Base + category "Help" + description "List all available commands. Use `help <command>` to get information about a specific command." + + def execute(command_name) + content = + if command_name.empty? + help_message + else + if command_class = Command.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 + end + Pager.page_content(content) + end + + private + + def help_message + commands_info = IRB::Command.all_commands_info + helper_methods_info = IRB::HelperMethod.all_helper_methods_info + commands_grouped_by_categories = commands_info.group_by { |cmd| cmd[:category] } + commands_grouped_by_categories["Helper methods"] = helper_methods_info + + if irb_context.with_debugger + # Remove the original "Debugging" category + commands_grouped_by_categories.delete("Debugging") + 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") + no_category_cmds = commands_grouped_by_categories.delete("No category") + aliases = irb_context.instance_variable_get(:@user_aliases).map do |alias_name, target| + { display_name: alias_name, description: "Alias for `#{target}`" } + end + + # Display help commands first + add_category_to_output("Help", help_cmds, output, longest_cmd_name_length) + + # Display the rest of the commands grouped by categories + commands_grouped_by_categories.each do |category, cmds| + add_category_to_output(category, cmds, output, longest_cmd_name_length) + end + + # Display commands without a category + if no_category_cmds + add_category_to_output("No category", no_category_cmds, output, longest_cmd_name_length) + end + + # Display aliases + add_category_to_output("Aliases", aliases, output, longest_cmd_name_length) + + # Append the debugger help at the end + if irb_context.with_debugger + # Add "Debugging (from debug.gem)" category as title + add_category_to_output("Debugging (from debug.gem)", [], output, longest_cmd_name_length) + 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/history.rb b/lib/irb/command/history.rb new file mode 100644 index 0000000000..90f87f9102 --- /dev/null +++ b/lib/irb/command/history.rb @@ -0,0 +1,45 @@ +# 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 execute(arg) + + if (match = arg&.match(/(-g|-G)\s+(?<grep>.+)\s*\n\z/)) + grep = Regexp.new(match[:grep]) + end + + 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..d08ce00a32 --- /dev/null +++ b/lib/irb/command/info.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module Command + class Info < DebugCommand + def execute(arg) + execute_debug_command(pre_cmds: "info #{arg}") + end + end + end + + # :startdoc: +end diff --git a/lib/irb/command/internal_helpers.rb b/lib/irb/command/internal_helpers.rb new file mode 100644 index 0000000000..249b5cdede --- /dev/null +++ b/lib/irb/command/internal_helpers.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module IRB + module Command + # Internal use only, for default command's backward compatibility. + module RubyArgsExtractor # :nodoc: + def unwrap_string_literal(str) + return if str.empty? + + sexp = Ripper.sexp(str) + if sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal + @irb_context.workspace.binding.eval(str).to_s + else + str + end + end + + def ruby_args(arg) + # Use throw and catch to handle arg that includes `;` + # For example: "1, kw: (2; 3); 4" will be parsed to [[1], { kw: 3 }] + catch(:EXTRACT_RUBY_ARGS) do + @irb_context.workspace.binding.eval "IRB::Command.extract_ruby_args #{arg}" + end || [[], {}] + end + end + end +end diff --git a/lib/irb/command/irb_info.rb b/lib/irb/command/irb_info.rb new file mode 100644 index 0000000000..6d868de94c --- /dev/null +++ b/lib/irb/command/irb_info.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module IRB + # :stopdoc: + + module Command + class IrbInfo < Base + category "IRB" + description "Show information about IRB." + + def execute(_arg) + 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" + rc_files = IRB.irbrc_files + str += ".irbrc paths: #{rc_files.join(", ")}\n" if rc_files.any? + 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..1cd3f279d1 --- /dev/null +++ b/lib/irb/command/load.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true +# +# load.rb - +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# +require_relative "../ext/loader" + +module IRB + # :stopdoc: + + module Command + class LoaderCommand < Base + include RubyArgsExtractor + 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(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(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(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(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(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(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..cbd9998bc4 --- /dev/null +++ b/lib/irb/command/ls.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +require "reline" +require "stringio" + +require_relative "../pager" +require_relative "../color" + +module IRB + # :stopdoc: + + module Command + class Ls < Base + include RubyArgsExtractor + + category "Context" + 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 execute(arg) + if match = arg.match(/\A(?<target>.+\s|)(-g|-G)\s+(?<grep>.+)$/) + if match[:target].empty? + use_main = true + else + obj = @irb_context.workspace.binding.eval(match[:target]) + end + grep = Regexp.new(match[:grep]) + else + args, kwargs = ruby_args(arg) + use_main = args.empty? + obj = args.first + grep = kwargs[:grep] + end + + if use_main + obj = irb_context.workspace.main + locals = irb_context.workspace.binding.local_variables + end + + o = Output.new(grep: grep) + + 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) if 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..f96be20de8 --- /dev/null +++ b/lib/irb/command/measure.rb @@ -0,0 +1,49 @@ +module IRB + # :stopdoc: + + module Command + class Measure < Base + include RubyArgsExtractor + + category "Misc" + description "`measure` enables the mode to measure processing time. `measure :off` disables it." + + def initialize(*args) + super(*args) + end + + def execute(arg) + if arg&.match?(/^do$|^do[^\w]|^\{/) + warn 'Configure IRB.conf[:MEASURE_PROC] to add custom measure methods.' + return + end + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(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". + + 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..3fc6b68d21 --- /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(arg) + execute_debug_command(do_cmds: "next #{arg}") + 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..b51928c650 --- /dev/null +++ b/lib/irb/command/pushws.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true +# +# change-ws.rb - +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +require_relative "../ext/workspaces" + +module IRB + # :stopdoc: + + module Command + class Workspaces < Base + category "Workspace" + description "Show workspaces." + + def execute(_arg) + inspection_resuls = irb_context.instance_variable_get(:@workspace_stack).map do |ws| + truncated_inspect(ws.main) + end + + puts "[" + inspection_resuls.join(", ") + "]" + end + + private + + def truncated_inspect(obj) + obj_inspection = obj.inspect + + if obj_inspection.size > 20 + obj_inspection = obj_inspection[0, 19] + "...>" + end + + obj_inspection + end + end + + class PushWorkspace < Workspaces + category "Workspace" + description "Push an object to the workspace stack." + + def execute(arg) + if arg.empty? + irb_context.push_workspace + else + obj = eval(arg, irb_context.workspace.binding) + irb_context.push_workspace(obj) + end + super + end + end + + class PopWorkspace < Workspaces + category "Workspace" + description "Pop a workspace from the workspace stack." + + def execute(_arg) + irb_context.pop_workspace + super + 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..8a2188e4eb --- /dev/null +++ b/lib/irb/command/show_doc.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module IRB + module Command + class ShowDoc < Base + include RubyArgsExtractor + + category "Context" + description "Look up documentation with RI." + + help_message <<~HELP_MESSAGE + Usage: show_doc [name] + + When name is provided, IRB will look up the documentation for the given name. + When no name is provided, a RI session will be started. + + Examples: + + show_doc + show_doc Array + show_doc Array#each + + HELP_MESSAGE + + def execute(arg) + # Accept string literal for backward compatibility + name = unwrap_string_literal(arg) + 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 name.nil? + Ri.interactive + else + begin + Ri.display_name(name) + rescue RDoc::RI::Error + puts $!.message + 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..f4c6f104a2 --- /dev/null +++ b/lib/irb/command/show_source.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require_relative "../source_finder" +require_relative "../pager" +require_relative "../color" + +module IRB + module Command + class ShowSource < Base + include RubyArgsExtractor + + category "Context" + description "Show the source code of a given method, class/module, or constant." + + help_message <<~HELP_MESSAGE + Usage: show_source [target] [-s] + + -s Show the super method. You can stack it like `-ss` to show the super of the super, etc. + + Examples: + + show_source Foo + show_source Foo#bar + show_source Foo#bar -s + show_source Foo.baz + show_source Foo::BAR + HELP_MESSAGE + + def execute(arg) + # Accept string literal for backward compatibility + str = unwrap_string_literal(arg) + 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..29e5e35ac0 --- /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(arg) + execute_debug_command(do_cmds: "step #{arg}") + 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..85af28c1a5 --- /dev/null +++ b/lib/irb/command/subirb.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true +# +# multi.rb - +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +module IRB + # :stopdoc: + + module Command + class MultiIRBCommand < Base + include RubyArgsExtractor + + 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(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(*obj) + print_deprecated_warning + + if irb_context.with_debugger + print_debugger_warning + return + end + + extend_irb_context + IRB.irb(nil, *obj) + puts IRB.JobManager.inspect + end + end + + class Jobs < MultiIRBCommand + category "Multi-irb (DEPRECATED)" + description "List of current sessions." + + def execute(_arg) + print_deprecated_warning + + if irb_context.with_debugger + print_debugger_warning + return + end + + extend_irb_context + puts IRB.JobManager.inspect + end + end + + class Foreground < MultiIRBCommand + category "Multi-irb (DEPRECATED)" + description "Switches to the session of the given number." + + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(key = nil) + print_deprecated_warning + + if irb_context.with_debugger + print_debugger_warning + return + end + + extend_irb_context + + raise CommandArgumentError.new("Please specify the id of target IRB job (listed in the `jobs` command).") unless key + IRB.JobManager.switch(key) + puts IRB.JobManager.inspect + end + end + + class Kill < MultiIRBCommand + category "Multi-irb (DEPRECATED)" + description "Kills the session with the given number." + + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(*keys) + print_deprecated_warning + + if irb_context.with_debugger + print_debugger_warning + return + end + + extend_irb_context + IRB.JobManager.kill(*keys) + puts IRB.JobManager.inspect + 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..c8439f1212 --- /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(_arg) + 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 |