From c2f671611a624fe7a7b1dbdf8130d48ea391296a Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 22 Nov 2023 21:06:19 +0900 Subject: [PATCH 001/263] Rescue Exception, ignore warning in completion doc_namespace (#777) --- lib/irb/type_completion/completor.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/irb/type_completion/completor.rb b/lib/irb/type_completion/completor.rb index e893fd8ad..df1e1c779 100644 --- a/lib/irb/type_completion/completor.rb +++ b/lib/irb/type_completion/completor.rb @@ -26,8 +26,8 @@ def inspect end def completion_candidates(preposing, target, _postposing, bind:) - @preposing = preposing verbose, $VERBOSE = $VERBOSE, nil + @preposing = preposing code = "#{preposing}#{target}" @result = analyze code, bind name, candidates = candidates_from_result(@result) @@ -36,8 +36,7 @@ def completion_candidates(preposing, target, _postposing, bind:) candidates.map(&:to_s).select { !_1.match?(all_symbols_pattern) && _1.start_with?(name) }.uniq.sort.map do target + _1[name.size..] end - rescue SyntaxError, StandardError => e - Completor.last_completion_error = e + rescue Exception => e handle_error(e) [] ensure @@ -45,6 +44,7 @@ def completion_candidates(preposing, target, _postposing, bind:) end def doc_namespace(preposing, matched, postposing, bind:) + verbose, $VERBOSE = $VERBOSE, nil name = matched[/[a-zA-Z_0-9]*[!?=]?\z/] method_doc = -> type do type = type.types.find { _1.all_methods.include? name.to_sym } @@ -102,6 +102,11 @@ def doc_namespace(preposing, matched, postposing, bind:) end else end + rescue Exception => e + handle_error(e) + nil + ensure + $VERBOSE = verbose end def candidates_from_result(result) @@ -229,6 +234,7 @@ def find_target(node, position) end def handle_error(e) + Completor.last_completion_error = e end end end From 943c14b12ed6c06f6785c2a7e8abc55e6532b322 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 23 Nov 2023 05:29:47 +0900 Subject: [PATCH 002/263] Require prism >= 0.18.0 (MatchWriteNode#targets and CaseMatchNode) (#778) --- Gemfile | 2 +- lib/irb/context.rb | 2 +- lib/irb/type_completion/type_analyzer.rb | 48 +++++++++++++++--------- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/Gemfile b/Gemfile index fb371cb6a..11be29977 100644 --- a/Gemfile +++ b/Gemfile @@ -20,5 +20,5 @@ gem "debug", github: "ruby/debug" if RUBY_VERSION >= "3.0.0" gem "rbs" - gem "prism", ">= 0.17.1" + gem "prism", ">= 0.18.0" end diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 5dfe9d0d7..a7b8ca2c2 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -164,7 +164,7 @@ def initialize(irb, workspace = nil, input_method = nil) RegexpCompletor.new end - TYPE_COMPLETION_REQUIRED_PRISM_VERSION = '0.17.1' + TYPE_COMPLETION_REQUIRED_PRISM_VERSION = '0.18.0' private def build_type_completor unless Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.0.0') && RUBY_ENGINE != 'truffleruby' diff --git a/lib/irb/type_completion/type_analyzer.rb b/lib/irb/type_completion/type_analyzer.rb index c4a41e499..344924c9f 100644 --- a/lib/irb/type_completion/type_analyzer.rb +++ b/lib/irb/type_completion/type_analyzer.rb @@ -703,19 +703,31 @@ def evaluate_for_node(node, scope) end def evaluate_case_node(node, scope) - target = evaluate(node.predicate, scope) if node.predicate + evaluate(node.predicate, scope) if node.predicate # TODO branches = node.conditions.map do |condition| - ->(s) { evaluate_case_match target, condition, s } + ->(s) { evaluate_case_when_condition condition, s } end if node.consequent branches << ->(s) { evaluate node.consequent, s } - elsif node.conditions.any? { _1.is_a? Prism::WhenNode } + else branches << ->(_s) { Types::NIL } end Types::UnionType[*scope.run_branches(*branches)] end + def evaluate_case_match_node(node, scope) + target = evaluate(node.predicate, scope) + # TODO + branches = node.conditions.map do |condition| + ->(s) { evaluate_case_in_condition target, condition, s } + end + if node.consequent + branches << ->(s) { evaluate node.consequent, s } + end + Types::UnionType[*scope.run_branches(*branches)] + end + def evaluate_match_required_node(node, scope) value_type = evaluate node.value, scope evaluate_match_pattern value_type, node.pattern, scope @@ -765,7 +777,8 @@ def evaluate_implicit_node(node, scope) def evaluate_match_write_node(node, scope) # /(?)(?)/ =~ string evaluate node.call, scope - node.locals.each { scope[_1.to_s] = Types::UnionType[Types::STRING, Types::NIL] } + locals = node.targets.map(&:name) + locals.each { scope[_1.to_s] = Types::UnionType[Types::STRING, Types::NIL] } Types::BOOLEAN end @@ -948,21 +961,20 @@ def assign_numbered_parameters(numbered_parameters, scope, args, _kwargs) end end - def evaluate_case_match(target, node, scope) - case node - when Prism::WhenNode - node.conditions.each { evaluate _1, scope } - node.statements ? evaluate(node.statements, scope) : Types::NIL - when Prism::InNode - pattern = node.pattern - if pattern.is_a?(Prism::IfNode) || pattern.is_a?(Prism::UnlessNode) - cond_node = pattern.predicate - pattern = pattern.statements.body.first - end - evaluate_match_pattern(target, pattern, scope) - evaluate cond_node, scope if cond_node # TODO: conditional branch - node.statements ? evaluate(node.statements, scope) : Types::NIL + def evaluate_case_when_condition(node, scope) + node.conditions.each { evaluate _1, scope } + node.statements ? evaluate(node.statements, scope) : Types::NIL + end + + def evaluate_case_in_condition(target, node, scope) + pattern = node.pattern + if pattern.is_a?(Prism::IfNode) || pattern.is_a?(Prism::UnlessNode) + cond_node = pattern.predicate + pattern = pattern.statements.body.first end + evaluate_match_pattern(target, pattern, scope) + evaluate cond_node, scope if cond_node # TODO: conditional branch + node.statements ? evaluate(node.statements, scope) : Types::NIL end def evaluate_match_pattern(value, pattern, scope) From 7d6849e44e753aec51ce0704b6ab109f6a78092a Mon Sep 17 00:00:00 2001 From: hogelog Date: Thu, 23 Nov 2023 16:24:31 +0900 Subject: [PATCH 003/263] Fix failure of more command with -R option (#781) --- lib/irb/pager.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/irb/pager.rb b/lib/irb/pager.rb index 1194e41f6..119515078 100644 --- a/lib/irb/pager.rb +++ b/lib/irb/pager.rb @@ -62,7 +62,7 @@ def setup_pager pager = Shellwords.split(pager) next if pager.empty? - if pager.first == 'less' || pager.first == 'more' + if pager.first == 'less' pager << '-R' unless pager.include?('-R') end From fdf24de85102536d28946207cf69b97679fe76a1 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 23 Nov 2023 07:29:08 +0000 Subject: [PATCH 004/263] Hint debugger command in irb:rdbg session (#768) When user enters irb:rdbg session, they don't get the same hint that the `debug` gem provides, like ``` (rdbg) n # next command ``` This means that users may accidentally execute commands when they want to retrieve the value of a variable. So this commit adds a Reline output modifier to add a simiar hint: ``` irb:rdbg(main):002> n # debug command ``` It is not exactly the same as `debug`'s because in this case the importance is to help users distinguish between value evaluation and debugger command execution. --- lib/irb/debug.rb | 18 ++++++++++ test/irb/yamatanooroti/test_rendering.rb | 42 ++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/lib/irb/debug.rb b/lib/irb/debug.rb index e522d3930..514395605 100644 --- a/lib/irb/debug.rb +++ b/lib/irb/debug.rb @@ -57,6 +57,24 @@ def DEBUGGER__.capture_frames(*args) DEBUGGER__::ThreadClient.prepend(SkipPathHelperForIRB) end + if !@output_modifier_defined && !DEBUGGER__::CONFIG[:no_hint] + irb_output_modifier_proc = Reline.output_modifier_proc + + Reline.output_modifier_proc = proc do |output, complete:| + unless output.strip.empty? + cmd = output.split(/\s/, 2).first + + if DEBUGGER__.commands.key?(cmd) + output = output.sub(/\n$/, " # debug command\n") + end + end + + irb_output_modifier_proc.call(output, complete: complete) + end + + @output_modifier_defined = true + end + true end diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index 96051ee43..5d8719ac3 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -379,6 +379,48 @@ def test_pager_page_content_doesnt_page_output_when_it_fits_in_the_screen assert_match(/foobar/, screen) end + def test_debug_integration_hints_debugger_commands + write_irbrc <<~'LINES' + IRB.conf[:USE_COLORIZE] = false + LINES + script = Tempfile.create(["debug", ".rb"]) + script.write <<~RUBY + puts 'start IRB' + binding.irb + RUBY + script.close + start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: 'start IRB') + write("debug\n") + write("n") + close + + screen = result.join("\n").sub(/\n*\z/, "\n") + assert_include(screen, "irb:rdbg(main):002> n # debug command") + ensure + File.unlink(script) if script + end + + def test_debug_integration_doesnt_hint_non_debugger_commands + write_irbrc <<~'LINES' + IRB.conf[:USE_COLORIZE] = false + LINES + script = Tempfile.create(["debug", ".rb"]) + script.write <<~RUBY + puts 'start IRB' + binding.irb + RUBY + script.close + start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: 'start IRB') + write("debug\n") + write("foo") + close + + screen = result.join("\n").sub(/\n*\z/, "\n") + assert_include(screen, "irb:rdbg(main):002> foo\n") + ensure + File.unlink(script) if script + end + private def write_irbrc(content) From d42138c47719f6f3809b8caa981401664441e4d9 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 24 Nov 2023 02:33:08 +0900 Subject: [PATCH 005/263] Handle handle_exception's exception (#780) --- lib/irb.rb | 9 ++++++- ...e_exception.rb => test_raise_exception.rb} | 25 +++++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) rename test/irb/{test_raise_no_backtrace_exception.rb => test_raise_exception.rb} (68%) diff --git a/lib/irb.rb b/lib/irb.rb index 655abaf06..754765425 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -743,7 +743,14 @@ def handle_exception(exc) message = message.gsub(/\(irb\):(?\d+):in `<(?top \(required\))>'/) { "(irb):#{$~[:num]}:in `
'" } puts message end - print "Maybe IRB bug!\n" if irb_bug + puts 'Maybe IRB bug!' if irb_bug + rescue Exception => handler_exc + begin + puts exc.inspect + puts "backtraces are hidden because #{handler_exc} was raised when processing them" + rescue Exception + puts 'Uninspectable exception occurred' + end end # Evaluates the given block using the given +path+ as the Context#irb_path diff --git a/test/irb/test_raise_no_backtrace_exception.rb b/test/irb/test_raise_exception.rb similarity index 68% rename from test/irb/test_raise_no_backtrace_exception.rb rename to test/irb/test_raise_exception.rb index 929577ad8..9ca534dba 100644 --- a/test/irb/test_raise_no_backtrace_exception.rb +++ b/test/irb/test_raise_exception.rb @@ -4,8 +4,8 @@ require_relative "helper" module TestIRB - class RaiseNoBacktraceExceptionTest < TestCase - def test_raise_exception + class RaiseExceptionTest < TestCase + def test_raise_exception_with_nil_backtrace bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, /Exception: foo/, []) e = Exception.new("foo") @@ -15,6 +15,27 @@ def e.backtrace; nil; end IRB end + def test_raise_exception_with_message_exception + bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] + expected = /#\nbacktraces are hidden because bar was raised when processing them/ + assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, expected, []) + e = Exception.new("foo") + def e.message; raise 'bar'; end + raise e +IRB + end + + def test_raise_exception_with_message_inspect_exception + bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] + expected = /Uninspectable exception occurred/ + assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, expected, []) + e = Exception.new("foo") + def e.message; raise; end + def e.inspect; raise; end + raise e +IRB + end + def test_raise_exception_with_invalid_byte_sequence pend if RUBY_ENGINE == 'truffleruby' || /mswin|mingw/ =~ RUBY_PLATFORM bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] From 85c6ddeb7dad9b1f38b0fd30c1b0a3a5cc817d13 Mon Sep 17 00:00:00 2001 From: hogelog Date: Sat, 25 Nov 2023 19:14:03 +0900 Subject: [PATCH 006/263] Fix flaky test case test_autocomplete_with_multiple_doc_namespaces (#786) --- test/irb/yamatanooroti/test_rendering.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index 5d8719ac3..f9b4befbf 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -207,15 +207,13 @@ def test_autocomplete_with_multiple_doc_namespaces write_irbrc <<~'LINES' puts 'start IRB' LINES - start_terminal(4, 40, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(3, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') write("{}.__id_") write("\C-i") close - assert_screen(<<~EOC) - start IRB - irb(main):001> {}.__id__ - }.__id__ - EOC + screen = result.join("\n").sub(/\n*\z/, "\n") + # This assertion passes whether showdoc dialog completed or not. + assert_match(/start\ IRB\nirb\(main\):001> {}\.__id__\n }\.__id__(?:Press )?/, screen) end def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_right From fa9ecf9a5b1cc212ba6c149ba6c6b68f5fd11360 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 25 Nov 2023 19:15:58 +0900 Subject: [PATCH 007/263] Fix exception(backtrace=nil) prints nothing (#782) --- lib/irb.rb | 67 +++++++++++++++----------------- test/irb/test_raise_exception.rb | 7 +--- 2 files changed, 34 insertions(+), 40 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 754765425..8c3039482 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -697,7 +697,7 @@ def encode_with_invalid_byte_sequence(str, enc) end def handle_exception(exc) - if exc.backtrace && exc.backtrace[0] =~ /\/irb(2)?(\/.*|-.*|\.rb)?:/ && exc.class.to_s !~ /^IRB/ && + if exc.backtrace[0] =~ /\/irb(2)?(\/.*|-.*|\.rb)?:/ && exc.class.to_s !~ /^IRB/ && !(SyntaxError === exc) && !(EncodingError === exc) # The backtrace of invalid encoding hash (ex. {"\xAE": 1}) raises EncodingError without lineno. irb_bug = true @@ -705,44 +705,41 @@ def handle_exception(exc) irb_bug = false end - if exc.backtrace - order = nil - if RUBY_VERSION < '3.0.0' - if STDOUT.tty? - message = exc.full_message(order: :bottom) - order = :bottom - else - message = exc.full_message(order: :top) - order = :top - end - else # '3.0.0' <= RUBY_VERSION + if RUBY_VERSION < '3.0.0' + if STDOUT.tty? + message = exc.full_message(order: :bottom) + order = :bottom + else message = exc.full_message(order: :top) order = :top end - message = convert_invalid_byte_sequence(message, exc.message.encoding) - message = encode_with_invalid_byte_sequence(message, IRB.conf[:LC_MESSAGES].encoding) unless message.encoding.to_s.casecmp?(IRB.conf[:LC_MESSAGES].encoding.to_s) - message = message.gsub(/((?:^\t.+$\n)+)/) { |m| - case order - when :top - lines = m.split("\n") - when :bottom - lines = m.split("\n").reverse - end - unless irb_bug - lines = lines.map { |l| @context.workspace.filter_backtrace(l) }.compact - if lines.size > @context.back_trace_limit - omit = lines.size - @context.back_trace_limit - lines = lines[0..(@context.back_trace_limit - 1)] - lines << "\t... %d levels..." % omit - end - end - lines = lines.reverse if order == :bottom - lines.map{ |l| l + "\n" }.join - } - # The "" in "(irb)" may be the top level of IRB so imitate the main object. - message = message.gsub(/\(irb\):(?\d+):in `<(?top \(required\))>'/) { "(irb):#{$~[:num]}:in `
'" } - puts message + else # '3.0.0' <= RUBY_VERSION + message = exc.full_message(order: :top) + order = :top end + message = convert_invalid_byte_sequence(message, exc.message.encoding) + message = encode_with_invalid_byte_sequence(message, IRB.conf[:LC_MESSAGES].encoding) unless message.encoding.to_s.casecmp?(IRB.conf[:LC_MESSAGES].encoding.to_s) + message = message.gsub(/((?:^\t.+$\n)+)/) { |m| + case order + when :top + lines = m.split("\n") + when :bottom + lines = m.split("\n").reverse + end + unless irb_bug + lines = lines.map { |l| @context.workspace.filter_backtrace(l) }.compact + if lines.size > @context.back_trace_limit + omit = lines.size - @context.back_trace_limit + lines = lines[0..(@context.back_trace_limit - 1)] + lines << "\t... %d levels..." % omit + end + end + lines = lines.reverse if order == :bottom + lines.map{ |l| l + "\n" }.join + } + # The "" in "(irb)" may be the top level of IRB so imitate the main object. + message = message.gsub(/\(irb\):(?\d+):in `<(?top \(required\))>'/) { "(irb):#{$~[:num]}:in `
'" } + puts message puts 'Maybe IRB bug!' if irb_bug rescue Exception => handler_exc begin diff --git a/test/irb/test_raise_exception.rb b/test/irb/test_raise_exception.rb index 9ca534dba..c373dd733 100644 --- a/test/irb/test_raise_exception.rb +++ b/test/irb/test_raise_exception.rb @@ -7,11 +7,8 @@ module TestIRB class RaiseExceptionTest < TestCase def test_raise_exception_with_nil_backtrace bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] - assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, /Exception: foo/, []) - e = Exception.new("foo") - puts e.inspect - def e.backtrace; nil; end - raise e + assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, /#/, []) + raise Exception.new("foo").tap {|e| def e.backtrace; nil; end } IRB end From df1c3b904284c4ec36924ca6deab2bcefc371eb1 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 26 Nov 2023 11:07:46 +0000 Subject: [PATCH 008/263] Support disabling pager (#783) With either `IRB.conf[:USE_PAGER] = false` or `--no-pager` commnad line flag. I decided use `--no-pager` instead of `--use-pager` because it matches with Pry and git's command line flags. --- lib/irb/init.rb | 3 +++ lib/irb/lc/help-message | 1 + lib/irb/pager.rb | 2 +- test/irb/test_cmd.rb | 5 +---- test/irb/test_debug_cmd.rb | 6 ++++-- test/irb/test_irb.rb | 6 ++---- 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/irb/init.rb b/lib/irb/init.rb index 4df285ce6..9704e36cb 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -82,6 +82,7 @@ def IRB.init_config(ap_path) @CONF[:USE_LOADER] = false @CONF[:IGNORE_SIGINT] = true @CONF[:IGNORE_EOF] = false + @CONF[:USE_PAGER] = true @CONF[:EXTRA_DOC_DIRS] = [] @CONF[:ECHO] = nil @CONF[:ECHO_ON_ASSIGNMENT] = nil @@ -285,6 +286,8 @@ def IRB.parse_opts(argv: ::ARGV) end when "--noinspect" @CONF[:INSPECT_MODE] = false + when "--no-pager" + @CONF[:USE_PAGER] = false when "--singleline", "--readline", "--legacy" @CONF[:USE_SINGLELINE] = true when "--nosingleline", "--noreadline" diff --git a/lib/irb/lc/help-message b/lib/irb/lc/help-message index c7846b755..37347306e 100644 --- a/lib/irb/lc/help-message +++ b/lib/irb/lc/help-message @@ -22,6 +22,7 @@ Usage: irb.rb [options] [programfile] [arguments] Show truncated result on assignment (default). --inspect Use 'inspect' for output. --noinspect Don't use 'inspect' for output. + --no-pager Don't use pager. --multiline Use multiline editor module (default). --nomultiline Don't use multiline editor module. --singleline Use single line editor module. diff --git a/lib/irb/pager.rb b/lib/irb/pager.rb index 119515078..e38d97e3c 100644 --- a/lib/irb/pager.rb +++ b/lib/irb/pager.rb @@ -18,7 +18,7 @@ def page_content(content) end def page - if STDIN.tty? && pager = setup_pager + if IRB.conf[:USE_PAGER] && STDIN.tty? && pager = setup_pager begin pid = pager.pid yield pager diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index 219710c92..62ef7a5b7 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -23,9 +23,6 @@ def setup save_encodings IRB.instance_variable_get(:@CONF).clear @is_win = (RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/) - STDIN.singleton_class.define_method :tty? do - false - end end def teardown @@ -34,13 +31,13 @@ def teardown Dir.chdir(@pwd) FileUtils.rm_rf(@tmpdir) restore_encodings - STDIN.singleton_class.remove_method :tty? end def execute_lines(*lines, conf: {}, main: self, irb_path: nil) IRB.init_config(nil) IRB.conf[:VERBOSE] = false IRB.conf[:PROMPT_MODE] = :SIMPLE + IRB.conf[:USE_PAGER] = false IRB.conf.merge!(conf) input = TestInputMethod.new(lines) irb = IRB::Irb.new(IRB::WorkSpace.new(main), input) diff --git a/test/irb/test_debug_cmd.rb b/test/irb/test_debug_cmd.rb index d669c174e..53d40f729 100644 --- a/test/irb/test_debug_cmd.rb +++ b/test/irb/test_debug_cmd.rb @@ -346,9 +346,11 @@ def test_help_command_is_delegated_to_the_debugger end def test_show_cmds_display_different_content_when_debugger_is_enabled + write_rc <<~RUBY + IRB.conf[:USE_PAGER] = false + RUBY + write_ruby <<~'ruby' - # disable pager - STDIN.singleton_class.define_method(:tty?) { false } binding.irb ruby diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index b32e857c1..e6eb3d5da 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -7,8 +7,7 @@ module TestIRB class InputTest < IntegrationTestCase def test_symbol_aliases_are_handled_correctly write_rc <<~RUBY - # disable pager - STDIN.singleton_class.define_method(:tty?) { false } + IRB.conf[:USE_PAGER] = false RUBY write_ruby <<~'RUBY' @@ -27,8 +26,7 @@ class Foo def test_symbol_aliases_are_handled_correctly_with_singleline_mode write_rc <<~RUBY - # disable pager - STDIN.singleton_class.define_method(:tty?) { false } + IRB.conf[:USE_PAGER] = false IRB.conf[:USE_SINGLELINE] = true RUBY From 2a0eacc89158cc1681b54c2ff7269ecdf4492f9e Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 26 Nov 2023 17:07:41 +0000 Subject: [PATCH 009/263] Display aliases in help message (#788) Similar to Pry, it displays user-defined aliases in the help message with a dedicated section. With the current default aliases, it looks like: ``` ...other sections... Aliases $ Alias for `show_source` @ Alias for `whereami` ``` --- lib/irb/cmd/show_cmds.rb | 6 ++++++ lib/irb/context.rb | 14 +++++++++++++- lib/irb/init.rb | 4 ---- test/irb/test_cmd.rb | 10 ++++++++++ 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/lib/irb/cmd/show_cmds.rb b/lib/irb/cmd/show_cmds.rb index 7d6b3ec26..a8d899e4a 100644 --- a/lib/irb/cmd/show_cmds.rb +++ b/lib/irb/cmd/show_cmds.rb @@ -16,6 +16,12 @@ 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") diff --git a/lib/irb/context.rb b/lib/irb/context.rb index a7b8ca2c2..3442fbf4d 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -146,9 +146,21 @@ def initialize(irb, workspace = nil, input_method = nil) @newline_before_multiline_output = true end - @command_aliases = IRB.conf[:COMMAND_ALIASES] + @user_aliases = IRB.conf[:COMMAND_ALIASES].dup + @command_aliases = @user_aliases.merge(KEYWORD_ALIASES) end + # because all input will eventually be evaluated as Ruby code, + # command names that conflict with Ruby keywords need special workaround + # we can remove them once we implemented a better command system for IRB + KEYWORD_ALIASES = { + :break => :irb_break, + :catch => :irb_catch, + :next => :irb_next, + }.freeze + + private_constant :KEYWORD_ALIASES + private def build_completor completor_type = IRB.conf[:COMPLETOR] case completor_type diff --git a/lib/irb/init.rb b/lib/irb/init.rb index 9704e36cb..b69f68d53 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -189,10 +189,6 @@ def IRB.init_config(ap_path) # Symbol aliases :'$' => :show_source, :'@' => :whereami, - # Keyword aliases - :break => :irb_break, - :catch => :irb_catch, - :next => :irb_next, } end diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index 62ef7a5b7..55373c2e8 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -680,6 +680,16 @@ def test_show_cmds 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 From f86d9dbe2fc05ed62332069a27f4aacc59ba9634 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Mon, 27 Nov 2023 10:34:36 +0000 Subject: [PATCH 010/263] Hide debugger hint after the input is submitted (#789) If `output_modifier_proc`'s `complete` arg is true, it means the input is submitted. In that case, debugger hint doesn't provide value to users and adds noise to the output. So we hide it in such case. --- lib/irb/debug.rb | 2 +- test/irb/yamatanooroti/test_rendering.rb | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/irb/debug.rb b/lib/irb/debug.rb index 514395605..1ec2335a8 100644 --- a/lib/irb/debug.rb +++ b/lib/irb/debug.rb @@ -64,7 +64,7 @@ def DEBUGGER__.capture_frames(*args) unless output.strip.empty? cmd = output.split(/\s/, 2).first - if DEBUGGER__.commands.key?(cmd) + if !complete && DEBUGGER__.commands.key?(cmd) output = output.sub(/\n$/, " # debug command\n") end end diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index f9b4befbf..ca8176dfe 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -389,11 +389,15 @@ def test_debug_integration_hints_debugger_commands script.close start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: 'start IRB') write("debug\n") - write("n") + write("pp 1\n") + write("pp 1") close screen = result.join("\n").sub(/\n*\z/, "\n") - assert_include(screen, "irb:rdbg(main):002> n # debug command") + # submitted input shouldn't contain hint + assert_include(screen, "irb:rdbg(main):002> pp 1\n") + # unsubmitted input should contain hint + assert_include(screen, "irb:rdbg(main):003> pp 1 # debug command\n") ensure File.unlink(script) if script end From 412ab26067e5e1cde1b3859a344ddd831073ea49 Mon Sep 17 00:00:00 2001 From: Kasumi Hanazuki Date: Tue, 28 Nov 2023 23:05:26 +0900 Subject: [PATCH 011/263] Rescue errors from main.to_s/inspect when formatting prompt (#791) Currently, IRB just terminates if `main.to_s` raises while IRB constructs the prompt string. This can easily happen if the user wants to start an IRB session in the instance scope of an uninitialized object, for example: ``` class C def initialize binding.irb @values = [] end def to_s = @values.join(',') # raises if uninitialized end C.new ``` This patch makes IRB rescue from such an exception and displays the class name of the exception instead of `main.to_s` to indicate some error has occurred. We may display more detailed information about the exception, but this patch chooses not to do so because 1) the prompt has limited space, 2) users can evaluate `to_s` in IRB to examine the error if they want, and 3) obtaining the details can also raise, which requires nested exception handling and can be complicated. --- lib/irb.rb | 6 ++++-- test/irb/test_context.rb | 9 +++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 8c3039482..66149eb45 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -930,9 +930,11 @@ def format_prompt(format, ltype, indent, line_no) # :nodoc: when "N" @context.irb_name when "m" - truncate_prompt_main(@context.main.to_s) + main_str = @context.main.to_s rescue "!#{$!.class}" + truncate_prompt_main(main_str) when "M" - truncate_prompt_main(@context.main.inspect) + main_str = @context.main.inspect rescue "!#{$!.class}" + truncate_prompt_main(main_str) when "l" ltype when "i" diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index ce57df6cd..5804607d1 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -631,6 +631,15 @@ def main.inspect; to_s.inspect; end assert_equal('irb("aaaaaaaaaaaaaaaaaaaaaaaaaaaa...)>', irb.send(:format_prompt, 'irb(%M)>', nil, 1, 1)) end + def test_prompt_main_raise + main = Object.new + def main.to_s; raise TypeError; end + def main.inspect; raise ArgumentError; end + irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new) + assert_equal("irb(!TypeError)>", irb.send(:format_prompt, 'irb(%m)>', nil, 1, 1)) + assert_equal("irb(!ArgumentError)>", irb.send(:format_prompt, 'irb(%M)>', nil, 1, 1)) + end + def test_lineno input = TestInputMethod.new([ "\n", From eec1329d5a1fa84fa97e226458934bd9efdfac5e Mon Sep 17 00:00:00 2001 From: paulreece <96156234+paulreece@users.noreply.github.com> Date: Tue, 28 Nov 2023 09:56:47 -0500 Subject: [PATCH 012/263] This enhancement allows a user to add the -s flag if they want to access a methods origin definition. It allows for chaining of multiple esses to further go up the classes as needed. (#770) --- lib/irb/cmd/show_source.rb | 11 ++- lib/irb/source_finder.rb | 30 ++++++-- test/irb/test_cmd.rb | 138 +++++++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 8 deletions(-) diff --git a/lib/irb/cmd/show_source.rb b/lib/irb/cmd/show_source.rb index 49cab43fa..9a0364e3e 100644 --- a/lib/irb/cmd/show_source.rb +++ b/lib/irb/cmd/show_source.rb @@ -27,11 +27,18 @@ def execute(str = nil) puts "Error: Expected a string but got #{str.inspect}" return end - - source = SourceFinder.new(@irb_context).find_source(str) + if str.include? " -s" + str, esses = str.split(" -") + s_count = esses.count("^s").zero? ? esses.size : 1 + source = SourceFinder.new(@irb_context).find_source(str, s_count) + else + source = SourceFinder.new(@irb_context).find_source(str) + end if source show_source(source) + elsif s_count + puts "Error: Couldn't locate a super definition for #{str}" else puts "Error: Couldn't locate a definition for #{str}" end diff --git a/lib/irb/source_finder.rb b/lib/irb/source_finder.rb index 959919e8a..a9450d457 100644 --- a/lib/irb/source_finder.rb +++ b/lib/irb/source_finder.rb @@ -16,7 +16,7 @@ def initialize(irb_context) @irb_context = irb_context end - def find_source(signature) + def find_source(signature, s_count = nil) context_binding = @irb_context.workspace.binding case signature when /\A[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name @@ -26,14 +26,13 @@ def find_source(signature) when /\A(?[A-Z]\w*(::[A-Z]\w*)*)#(?[^ :.]+)\z/ # Class#method owner = eval(Regexp.last_match[:owner], context_binding) method = Regexp.last_match[:method] - if owner.respond_to?(:instance_method) - methods = owner.instance_methods + owner.private_instance_methods - file, line = owner.instance_method(method).source_location if methods.include?(method.to_sym) - end + return unless owner.respond_to?(:instance_method) + file, line = method_target(owner, s_count, method, "owner") when /\A((?.+)(\.|::))?(?[^ :.]+)\z/ # method, receiver.method, receiver::method receiver = eval(Regexp.last_match[:receiver] || 'self', context_binding) method = Regexp.last_match[:method] - file, line = receiver.method(method).source_location if receiver.respond_to?(method, true) + return unless receiver.respond_to?(method, true) + file, line = method_target(receiver, s_count, method, "receiver") end if file && line && File.exist?(file) Source.new(file: file, first_line: line, last_line: find_end(file, line)) @@ -60,5 +59,24 @@ def find_end(file, first_line) end first_line end + + def method_target(owner_receiver, s_count, method, type) + case type + when "owner" + target_method = owner_receiver.instance_method(method) + return target_method.source_location unless s_count + when "receiver" + if s_count + target_method = owner_receiver.class.instance_method(method) + else + target_method = method + return owner_receiver.method(method).source_location + end + end + s_count.times do |s| + target_method = target_method.super_method if target_method + end + target_method.nil? ? nil : target_method.source_location + end end end diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index 55373c2e8..ee987920f 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -466,6 +466,144 @@ def test_show_source_method assert_match(%r[/irb\/init\.rb], out) end + def test_show_source_method_s + code = <<~RUBY + class Baz + def foo + end + end + + class Bar < Baz + def foo + super + end + end + RUBY + File.write("#{@tmpdir}/bazbar.rb", code) + out, err = execute_lines( + "irb_load '#{@tmpdir}/bazbar.rb'\n", + "show_source Bar#foo -s", + ) + assert_match(%r[bazbar.rb:2\n\n def foo\n end\n\n=> nil\n], out) + File.delete("#{@tmpdir}/bazbar.rb") + end + + def test_show_source_method_multiple_s + code = <<~RUBY + class Baz + def fob + end + end + + class Bar < Baz + def fob + super + end + end + + class Bob < Bar + def fob + super + end + end + RUBY + File.write("#{@tmpdir}/bazbarbob.rb", code) + out, err = execute_lines( + "irb_load '#{@tmpdir}/bazbarbob.rb'\n", + "show_source Bob#fob -ss", + ) + assert_match(%r[bazbarbob.rb:2\n\n def fob\n end\n\n=> nil\n], out) + File.delete("#{@tmpdir}/bazbarbob.rb") + end + + def test_show_source_method_no_instance_method + code = <<~RUBY + class Baz + end + + class Bar < Baz + def fee + super + end + end + RUBY + File.write("#{@tmpdir}/bazbar.rb", code) + out, err = execute_lines( + "irb_load '#{@tmpdir}/bazbar.rb'\n", + "show_source Bar#fee -s", + ) + assert_match(%r[Error: Couldn't locate a super definition for Bar#fee\n], out) + File.delete("#{@tmpdir}/bazbar.rb") + end + + def test_show_source_method_exceeds_super_chain + code = <<~RUBY + class Baz + def fow + end + end + + class Bar < Baz + def fow + super + end + end + RUBY + File.write("#{@tmpdir}/bazbar.rb", code) + out, err = execute_lines( + "irb_load '#{@tmpdir}/bazbar.rb'\n", + "show_source Bar#fow -ss", + ) + assert_match(%r[Error: Couldn't locate a super definition for Bar#fow\n], out) + File.delete("#{@tmpdir}/bazbar.rb") + end + + def test_show_source_method_accidental_characters + code = <<~RUBY + class Baz + def fol + end + end + + class Bar < Baz + def fol + super + end + end + RUBY + File.write("#{@tmpdir}/bazbar.rb", code) + out, err = execute_lines( + "irb_load '#{@tmpdir}/bazbar.rb'\n", + "show_source Bar#fol -sddddd", + ) + + assert_match(%r[bazbar.rb:2\n\n def fol\n end\n\n=> nil\n], out) + File.delete("#{@tmpdir}/bazbar.rb") + end + + def test_show_source_receiver_super + code = <<~RUBY + class Baz + def fot + end + end + + class Bar < Baz + def fot + super + end + end + RUBY + File.write("#{@tmpdir}/bazbar.rb", code) + out, err = execute_lines( + "irb_load '#{@tmpdir}/bazbar.rb'\n", + "bar = Bar.new", + "show_source bar.fot -s" + ) + assert_match(%r[bazbar.rb:2\n\n def fot\n end\n\n=> nil\n], out) + File.delete("#{@tmpdir}/bazbar.rb") + end + def test_show_source_string out, err = execute_lines( "show_source 'IRB.conf'\n", From 3c39f13397c72a8db24e50afdcb8942e6c4ea12f Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 28 Nov 2023 17:06:51 +0000 Subject: [PATCH 013/263] Change show_source tests into integration tests (#793) * Remove trailing spaces * Migrate show_source tests to integration tests Because show_source tests often need to define class and/or methods, they can easily leak state to other tests. Changing them to integration tests will ensure that they are run in a clean environment. * Fix NoMethodError caused by SourceFinder#method_target --- lib/irb/source_finder.rb | 2 + test/irb/cmd/test_show_source.rb | 263 +++++++++++++++++++++++++++++++ test/irb/test_cmd.rb | 228 --------------------------- 3 files changed, 265 insertions(+), 228 deletions(-) create mode 100644 test/irb/cmd/test_show_source.rb diff --git a/lib/irb/source_finder.rb b/lib/irb/source_finder.rb index a9450d457..a0aedcee6 100644 --- a/lib/irb/source_finder.rb +++ b/lib/irb/source_finder.rb @@ -77,6 +77,8 @@ def method_target(owner_receiver, s_count, method, type) target_method = target_method.super_method if target_method end target_method.nil? ? nil : target_method.source_location + rescue NameError + nil end end end diff --git a/test/irb/cmd/test_show_source.rb b/test/irb/cmd/test_show_source.rb new file mode 100644 index 000000000..89b114239 --- /dev/null +++ b/test/irb/cmd/test_show_source.rb @@ -0,0 +1,263 @@ +# frozen_string_literal: false +require 'irb' + +require_relative "../helper" + +module TestIRB + class ShowSourceTest < IntegrationTestCase + def setup + super + + write_rc <<~'RUBY' + IRB.conf[:USE_PAGER] = false + RUBY + end + + def test_show_source + write_ruby <<~'RUBY' + binding.irb + RUBY + + out = run_ruby_file do + type "show_source IRB.conf" + type "exit" + end + + assert_match(%r[/irb\/init\.rb], out) + end + + def test_show_source_alias + write_ruby <<~'RUBY' + binding.irb + RUBY + + out = run_ruby_file do + type "$ IRB.conf" + type "exit" + end + + assert_match(%r[/irb\/init\.rb], out) + end + + def test_show_source_string + write_ruby <<~'RUBY' + binding.irb + RUBY + + out = run_ruby_file do + type "show_source 'IRB.conf'" + type "exit" + end + + assert_match(%r[/irb\/init\.rb], out) + end + + def test_show_source_method_s + write_ruby <<~RUBY + class Baz + def foo + end + end + + class Bar < Baz + def foo + super + end + end + + binding.irb + RUBY + + out = run_ruby_file do + type "show_source Bar#foo -s" + type "exit" + end + + assert_match(%r[#{@ruby_file.to_path}:2\s+def foo\r\n end\r\n], out) + end + + def test_show_source_method_s_with_incorrect_signature + write_ruby <<~RUBY + class Baz + def foo + end + end + + class Bar < Baz + def foo + super + end + end + + binding.irb + RUBY + + out = run_ruby_file do + type "show_source Bar#fooo -s" + type "exit" + end + + assert_match(%r[Error: Couldn't locate a super definition for Bar#fooo], out) + end + + def test_show_source_private_method + write_ruby <<~RUBY + class Bar + private def foo + end + end + binding.irb + RUBY + + out = run_ruby_file do + type "show_source Bar#foo" + type "exit" + end + + assert_match(%r[#{@ruby_file.to_path}:2\s+private def foo\r\n end\r\n], out) + end + + def test_show_source_private_singleton_method + write_ruby <<~RUBY + class Bar + private def foo + end + end + binding.irb + RUBY + + out = run_ruby_file do + type "bar = Bar.new" + type "show_source bar.foo" + type "exit" + end + + assert_match(%r[#{@ruby_file.to_path}:2\s+private def foo\r\n end\r\n], out) + end + + def test_show_source_method_multiple_s + write_ruby <<~RUBY + class Baz + def foo + end + end + + class Bar < Baz + def foo + super + end + end + + class Bob < Bar + def foo + super + end + end + + binding.irb + RUBY + + out = run_ruby_file do + type "show_source Bob#foo -ss" + type "exit" + end + + assert_match(%r[#{@ruby_file.to_path}:2\s+def foo\r\n end\r\n], out) + end + + def test_show_source_method_no_instance_method + write_ruby <<~RUBY + class Baz + end + + class Bar < Baz + def foo + super + end + end + + binding.irb + RUBY + + out = run_ruby_file do + type "show_source Bar#foo -s" + type "exit" + end + + assert_match(%r[Error: Couldn't locate a super definition for Bar#foo], out) + end + + def test_show_source_method_exceeds_super_chain + write_ruby <<~RUBY + class Baz + def foo + end + end + + class Bar < Baz + def foo + super + end + end + + binding.irb + RUBY + + out = run_ruby_file do + type "show_source Bar#foo -ss" + type "exit" + end + + assert_match(%r[Error: Couldn't locate a super definition for Bar#foo], out) + end + + def test_show_source_method_accidental_characters + write_ruby <<~'RUBY' + class Baz + def foo + end + end + + class Bar < Baz + def foo + super + end + end + + binding.irb + RUBY + + out = run_ruby_file do + type "show_source Bar#foo -sddddd" + type "exit" + end + + assert_match(%r[#{@ruby_file.to_path}:2\s+def foo\r\n end], out) + end + + def test_show_source_receiver_super + write_ruby <<~RUBY + class Baz + def foo + end + end + + class Bar < Baz + def foo + super + end + end + + binding.irb + RUBY + + out = run_ruby_file do + type "bar = Bar.new" + type "show_source bar.foo -s" + type "exit" + end + + assert_match(%r[#{@ruby_file.to_path}:2\s+def foo\r\n end], out) + end + end +end diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index ee987920f..345f04bcf 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -449,234 +449,6 @@ def test_irb_load_without_argument end end - class ShowSourceTest < CommandTestCase - def test_show_source - out, err = execute_lines( - "show_source IRB.conf\n", - ) - assert_empty err - assert_match(%r[/irb\/init\.rb], out) - end - - def test_show_source_method - out, err = execute_lines( - "p show_source('IRB.conf')\n", - ) - assert_empty err - assert_match(%r[/irb\/init\.rb], out) - end - - def test_show_source_method_s - code = <<~RUBY - class Baz - def foo - end - end - - class Bar < Baz - def foo - super - end - end - RUBY - File.write("#{@tmpdir}/bazbar.rb", code) - out, err = execute_lines( - "irb_load '#{@tmpdir}/bazbar.rb'\n", - "show_source Bar#foo -s", - ) - assert_match(%r[bazbar.rb:2\n\n def foo\n end\n\n=> nil\n], out) - File.delete("#{@tmpdir}/bazbar.rb") - end - - def test_show_source_method_multiple_s - code = <<~RUBY - class Baz - def fob - end - end - - class Bar < Baz - def fob - super - end - end - - class Bob < Bar - def fob - super - end - end - RUBY - File.write("#{@tmpdir}/bazbarbob.rb", code) - out, err = execute_lines( - "irb_load '#{@tmpdir}/bazbarbob.rb'\n", - "show_source Bob#fob -ss", - ) - assert_match(%r[bazbarbob.rb:2\n\n def fob\n end\n\n=> nil\n], out) - File.delete("#{@tmpdir}/bazbarbob.rb") - end - - def test_show_source_method_no_instance_method - code = <<~RUBY - class Baz - end - - class Bar < Baz - def fee - super - end - end - RUBY - File.write("#{@tmpdir}/bazbar.rb", code) - out, err = execute_lines( - "irb_load '#{@tmpdir}/bazbar.rb'\n", - "show_source Bar#fee -s", - ) - assert_match(%r[Error: Couldn't locate a super definition for Bar#fee\n], out) - File.delete("#{@tmpdir}/bazbar.rb") - end - - def test_show_source_method_exceeds_super_chain - code = <<~RUBY - class Baz - def fow - end - end - - class Bar < Baz - def fow - super - end - end - RUBY - File.write("#{@tmpdir}/bazbar.rb", code) - out, err = execute_lines( - "irb_load '#{@tmpdir}/bazbar.rb'\n", - "show_source Bar#fow -ss", - ) - assert_match(%r[Error: Couldn't locate a super definition for Bar#fow\n], out) - File.delete("#{@tmpdir}/bazbar.rb") - end - - def test_show_source_method_accidental_characters - code = <<~RUBY - class Baz - def fol - end - end - - class Bar < Baz - def fol - super - end - end - RUBY - File.write("#{@tmpdir}/bazbar.rb", code) - out, err = execute_lines( - "irb_load '#{@tmpdir}/bazbar.rb'\n", - "show_source Bar#fol -sddddd", - ) - - assert_match(%r[bazbar.rb:2\n\n def fol\n end\n\n=> nil\n], out) - File.delete("#{@tmpdir}/bazbar.rb") - end - - def test_show_source_receiver_super - code = <<~RUBY - class Baz - def fot - end - end - - class Bar < Baz - def fot - super - end - end - RUBY - File.write("#{@tmpdir}/bazbar.rb", code) - out, err = execute_lines( - "irb_load '#{@tmpdir}/bazbar.rb'\n", - "bar = Bar.new", - "show_source bar.fot -s" - ) - assert_match(%r[bazbar.rb:2\n\n def fot\n end\n\n=> nil\n], out) - File.delete("#{@tmpdir}/bazbar.rb") - end - - def test_show_source_string - out, err = execute_lines( - "show_source 'IRB.conf'\n", - ) - assert_empty err - assert_match(%r[/irb\/init\.rb], out) - end - - def test_show_source_alias - out, err = execute_lines( - "$ 'IRB.conf'\n", - conf: { COMMAND_ALIASES: { :'$' => :show_source } } - ) - assert_empty err - assert_match(%r[/irb\/init\.rb], out) - end - - def test_show_source_end_finder - eval(code = <<-EOS, binding, __FILE__, __LINE__ + 1) - def show_source_test_method - unless true - end - end unless defined?(show_source_test_method) - EOS - - out, err = execute_lines( - "show_source '#{self.class.name}#show_source_test_method'\n", - ) - - assert_empty err - assert_include(out, code) - end - - def test_show_source_private_instance - eval(code = <<-EOS, binding, __FILE__, __LINE__ + 1) - class PrivateInstanceTest - private def show_source_test_method - unless true - end - end unless private_method_defined?(:show_source_test_method) - end - EOS - - out, err = execute_lines( - "show_source '#{self.class.name}::PrivateInstanceTest#show_source_test_method'\n", - ) - - assert_empty err - assert_include(out, code.lines[1..-2].join) - end - - - def test_show_source_private - eval(code = <<-EOS, binding, __FILE__, __LINE__ + 1) - class PrivateTest - private def show_source_test_method - unless true - end - end unless private_method_defined?(:show_source_test_method) - end - - Instance = PrivateTest.new unless defined?(Instance) - EOS - - out, err = execute_lines( - "show_source '#{self.class.name}::Instance.show_source_test_method'\n", - ) - - assert_empty err - assert_include(out, code.lines[1..-4].join) - end - end - class WorkspaceCommandTestCase < CommandTestCase def setup super From 1b60db3ec22b8c6c43580a36d82084cb6fb71e54 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 29 Nov 2023 15:32:22 +0000 Subject: [PATCH 014/263] Make rake test run all tests under `test/irb/` directory (#794) I think `rake test` should run all tests under `test/irb/` directory without the need to update it every time we add a subdirectory under it. While this means tests under `test/irb/yamatanooroti` will be run too, without the vterm gem installed they will be skipped anyway. --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 701d235ac..9231f6985 100644 --- a/Rakefile +++ b/Rakefile @@ -5,7 +5,7 @@ Rake::TestTask.new(:test) do |t| t.libs << "test" << "test/lib" t.libs << "lib" t.ruby_opts << "-rhelper" - t.test_files = FileList["test/irb/test_*.rb", "test/irb/type_completion/test_*.rb"] + t.test_files = FileList["test/irb/**/test_*.rb"] end # To make sure they have been correctly setup for Ruby CI. From a4868a5373f6d17bcfe3c00d223566a4a0e25716 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 30 Nov 2023 01:30:08 +0900 Subject: [PATCH 015/263] Use gem repl_type_completor, remove type_completion implementation (#772) --- Gemfile | 5 +- README.md | 18 +- Rakefile | 2 +- lib/irb/completion.rb | 21 + lib/irb/context.rb | 22 +- lib/irb/type_completion/completor.rb | 241 ---- lib/irb/type_completion/methods.rb | 13 - lib/irb/type_completion/scope.rb | 412 ------ lib/irb/type_completion/type_analyzer.rb | 1181 ----------------- lib/irb/type_completion/types.rb | 426 ------ test/irb/test_context.rb | 7 +- test/irb/test_type_completor.rb | 83 ++ test/irb/type_completion/test_scope.rb | 112 -- test/irb/type_completion/test_type_analyze.rb | 697 ---------- .../type_completion/test_type_completor.rb | 182 --- test/irb/type_completion/test_types.rb | 89 -- 16 files changed, 126 insertions(+), 3385 deletions(-) delete mode 100644 lib/irb/type_completion/completor.rb delete mode 100644 lib/irb/type_completion/methods.rb delete mode 100644 lib/irb/type_completion/scope.rb delete mode 100644 lib/irb/type_completion/type_analyzer.rb delete mode 100644 lib/irb/type_completion/types.rb create mode 100644 test/irb/test_type_completor.rb delete mode 100644 test/irb/type_completion/test_scope.rb delete mode 100644 test/irb/type_completion/test_type_analyze.rb delete mode 100644 test/irb/type_completion/test_type_completor.rb delete mode 100644 test/irb/type_completion/test_types.rb diff --git a/Gemfile b/Gemfile index 11be29977..940387ea7 100644 --- a/Gemfile +++ b/Gemfile @@ -18,7 +18,6 @@ gem "test-unit" gem "test-unit-ruby-core" gem "debug", github: "ruby/debug" -if RUBY_VERSION >= "3.0.0" - gem "rbs" - gem "prism", ">= 0.18.0" +if RUBY_VERSION >= "3.0.0" && !is_truffleruby + gem "repl_type_completor" end diff --git a/README.md b/README.md index 483fb9a63..9425f4341 100644 --- a/README.md +++ b/README.md @@ -237,11 +237,11 @@ However, there are also some limitations to be aware of: ## Type Based Completion -IRB's default completion `IRB::RegexpCompletor` uses Regexp. IRB has another experimental completion `IRB::TypeCompletion` that uses type analysis. +IRB's default completion `IRB::RegexpCompletor` uses Regexp. IRB has another experimental completion `IRB::TypeCompletor` that uses type analysis. -### How to Enable IRB::TypeCompletion +### How to Enable IRB::TypeCompletor -To enable IRB::TypeCompletion, run IRB with `--type-completor` option +To enable IRB::TypeCompletor, run IRB with `--type-completor` option ``` $ irb --type-completor ``` @@ -249,14 +249,14 @@ Or write the code below to IRB's rc-file. ```ruby IRB.conf[:COMPLETOR] = :type # default is :regexp ``` -You also need `gem prism` and `gem rbs` to use this feature. +You also need `gem repl_type_completor` to use this feature. To check if it's enabled, type `irb_info` into IRB and see the `Completion` section. ``` irb(main):001> irb_info ... # Enabled -Completion: Autocomplete, TypeCompletion::Completor(Prism: 0.17.1, RBS: 3.3.0) +Completion: Autocomplete, ReplTypeCompletor: 0.1.0, Prism: 0.18.0, RBS: 3.3.0 # Not enabled Completion: Autocomplete, RegexpCompletor ... @@ -265,7 +265,7 @@ If you have `sig/` directory or `rbs_collection.lock.yaml` in current directory, ### Advantage over Default IRB::RegexpCompletor -IRB::TypeCompletion can autocomplete chained methods, block parameters and more if type information is available. +IRB::TypeCompletor can autocomplete chained methods, block parameters and more if type information is available. These are some examples IRB::RegexpCompletor cannot complete. ```ruby @@ -287,11 +287,11 @@ As a trade-off, completion calculation takes more time than IRB::RegexpCompletor ### Difference between Steep's Completion -Compared with Steep, IRB::TypeCompletion has some difference and limitations. +Compared with Steep, IRB::TypeCompletor has some difference and limitations. ```ruby [0, 'a'].sample. # Steep completes intersection of Integer methods and String methods -# IRB::TypeCompletion completes both Integer and String methods +# IRB::TypeCompletor completes both Integer and String methods ``` Some features like type narrowing is not implemented. @@ -301,7 +301,7 @@ def f(arg = [0, 'a'].sample) arg. # Completes both Integer and String methods ``` -Unlike other static type checker, IRB::TypeCompletion uses runtime information to provide better completion. +Unlike other static type checker, IRB::TypeCompletor uses runtime information to provide better completion. ```ruby irb(main):001> a = [1] => [1] diff --git a/Rakefile b/Rakefile index 9231f6985..01bd83cb2 100644 --- a/Rakefile +++ b/Rakefile @@ -13,7 +13,7 @@ desc "Run each irb test file in isolation." task :test_in_isolation do failed = false - FileList["test/irb/test_*.rb", "test/irb/type_completion/test_*.rb"].each do |test_file| + FileList["test/irb/**/test_*.rb"].each do |test_file| ENV["TEST"] = test_file begin Rake::Task["test"].execute diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index 9b29a787b..af3b69eb2 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -93,6 +93,27 @@ def retrieve_files_to_require_relative_from_current_dir end end + class TypeCompletor < BaseCompletor # :nodoc: + def initialize(context) + @context = context + end + + def inspect + ReplTypeCompletor.info + end + + def completion_candidates(preposing, target, _postposing, bind:) + result = ReplTypeCompletor.analyze(preposing + target, binding: bind, filename: @context.irb_path) + return [] unless result + result.completion_candidates.map { target + _1 } + end + + def doc_namespace(preposing, matched, _postposing, bind:) + result = ReplTypeCompletor.analyze(preposing + matched, binding: bind, filename: @context.irb_path) + result&.doc_namespace('') + end + end + class RegexpCompletor < BaseCompletor # :nodoc: using Module.new { refine ::Binding do diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 3442fbf4d..ffbba4e8b 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -176,26 +176,22 @@ def initialize(irb, workspace = nil, input_method = nil) RegexpCompletor.new end - TYPE_COMPLETION_REQUIRED_PRISM_VERSION = '0.18.0' - private def build_type_completor - unless Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.0.0') && RUBY_ENGINE != 'truffleruby' - warn 'TypeCompletion requires RUBY_VERSION >= 3.0.0' + if RUBY_ENGINE == 'truffleruby' + # Avoid SynatxError. truffleruby does not support endless method definition yet. + warn 'TypeCompletor is not supported on TruffleRuby yet' return end + begin - require 'prism' + require 'repl_type_completor' rescue LoadError => e - warn "TypeCompletion requires Prism: #{e.message}" + warn "TypeCompletor requires `gem repl_type_completor`: #{e.message}" return end - unless Gem::Version.new(Prism::VERSION) >= Gem::Version.new(TYPE_COMPLETION_REQUIRED_PRISM_VERSION) - warn "TypeCompletion requires Prism::VERSION >= #{TYPE_COMPLETION_REQUIRED_PRISM_VERSION}" - return - end - require 'irb/type_completion/completor' - TypeCompletion::Types.preload_in_thread - TypeCompletion::Completor.new + + ReplTypeCompletor.preload_rbs + TypeCompletor.new(self) end def save_history=(val) diff --git a/lib/irb/type_completion/completor.rb b/lib/irb/type_completion/completor.rb deleted file mode 100644 index df1e1c779..000000000 --- a/lib/irb/type_completion/completor.rb +++ /dev/null @@ -1,241 +0,0 @@ -# frozen_string_literal: true - -require 'prism' -require 'irb/completion' -require_relative 'type_analyzer' - -module IRB - module TypeCompletion - class Completor < BaseCompletor # :nodoc: - HIDDEN_METHODS = %w[Namespace TypeName] # defined by rbs, should be hidden - - class << self - attr_accessor :last_completion_error - end - - def inspect - name = 'TypeCompletion::Completor' - prism_info = "Prism: #{Prism::VERSION}" - if Types.rbs_builder - "#{name}(#{prism_info}, RBS: #{RBS::VERSION})" - elsif Types.rbs_load_error - "#{name}(#{prism_info}, RBS: #{Types.rbs_load_error.inspect})" - else - "#{name}(#{prism_info}, RBS: loading)" - end - end - - def completion_candidates(preposing, target, _postposing, bind:) - verbose, $VERBOSE = $VERBOSE, nil - @preposing = preposing - code = "#{preposing}#{target}" - @result = analyze code, bind - name, candidates = candidates_from_result(@result) - - all_symbols_pattern = /\A[ -\/:-@\[-`\{-~]*\z/ - candidates.map(&:to_s).select { !_1.match?(all_symbols_pattern) && _1.start_with?(name) }.uniq.sort.map do - target + _1[name.size..] - end - rescue Exception => e - handle_error(e) - [] - ensure - $VERBOSE = verbose - end - - def doc_namespace(preposing, matched, postposing, bind:) - verbose, $VERBOSE = $VERBOSE, nil - name = matched[/[a-zA-Z_0-9]*[!?=]?\z/] - method_doc = -> type do - type = type.types.find { _1.all_methods.include? name.to_sym } - case type - when Types::SingletonType - "#{Types.class_name_of(type.module_or_class)}.#{name}" - when Types::InstanceType - "#{Types.class_name_of(type.klass)}##{name}" - end - end - call_or_const_doc = -> type do - if name =~ /\A[A-Z]/ - type = type.types.grep(Types::SingletonType).find { _1.module_or_class.const_defined?(name) } - type.module_or_class == Object ? name : "#{Types.class_name_of(type.module_or_class)}::#{name}" if type - else - method_doc.call(type) - end - end - - value_doc = -> type do - return unless type - type.types.each do |t| - case t - when Types::SingletonType - return Types.class_name_of(t.module_or_class) - when Types::InstanceType - return Types.class_name_of(t.klass) - end - end - nil - end - - case @result - in [:call_or_const, type, _name, _self_call] - call_or_const_doc.call type - in [:const, type, _name, scope] - if type - call_or_const_doc.call type - else - value_doc.call scope[name] - end - in [:gvar, _name, scope] - value_doc.call scope["$#{name}"] - in [:ivar, _name, scope] - value_doc.call scope["@#{name}"] - in [:cvar, _name, scope] - value_doc.call scope["@@#{name}"] - in [:call, type, _name, _self_call] - method_doc.call type - in [:lvar_or_method, _name, scope] - if scope.local_variables.include?(name) - value_doc.call scope[name] - else - method_doc.call scope.self_type - end - else - end - rescue Exception => e - handle_error(e) - nil - ensure - $VERBOSE = verbose - end - - def candidates_from_result(result) - candidates = case result - in [:require, name] - retrieve_files_to_require_from_load_path - in [:require_relative, name] - retrieve_files_to_require_relative_from_current_dir - in [:call_or_const, type, name, self_call] - ((self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS) | type.constants - in [:const, type, name, scope] - if type - scope_constants = type.types.flat_map do |t| - scope.table_module_constants(t.module_or_class) if t.is_a?(Types::SingletonType) - end - (scope_constants.compact | type.constants.map(&:to_s)).sort - else - scope.constants.sort | ReservedWords - end - in [:ivar, name, scope] - ivars = scope.instance_variables.sort - name == '@' ? ivars + scope.class_variables.sort : ivars - in [:cvar, name, scope] - scope.class_variables - in [:gvar, name, scope] - scope.global_variables - in [:symbol, name] - Symbol.all_symbols.map { _1.inspect[1..] } - in [:call, type, name, self_call] - (self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS - in [:lvar_or_method, name, scope] - scope.self_type.all_methods.map(&:to_s) | scope.local_variables | ReservedWords - else - [] - end - [name || '', candidates] - end - - def analyze(code, binding = Object::TOPLEVEL_BINDING) - # Workaround for https://github.com/ruby/prism/issues/1592 - return if code.match?(/%[qQ]\z/) - - ast = Prism.parse(code, scopes: [binding.local_variables]).value - name = code[/(@@|@|\$)?\w*[!?=]?\z/] - *parents, target_node = find_target ast, code.bytesize - name.bytesize - return unless target_node - - calculate_scope = -> { TypeAnalyzer.calculate_target_type_scope(binding, parents, target_node).last } - calculate_type_scope = ->(node) { TypeAnalyzer.calculate_target_type_scope binding, [*parents, target_node], node } - - case target_node - when Prism::StringNode, Prism::InterpolatedStringNode - call_node, args_node = parents.last(2) - return unless call_node.is_a?(Prism::CallNode) && call_node.receiver.nil? - return unless args_node.is_a?(Prism::ArgumentsNode) && args_node.arguments.size == 1 - - case call_node.name - when :require - [:require, name.rstrip] - when :require_relative - [:require_relative, name.rstrip] - end - when Prism::SymbolNode - if parents.last.is_a? Prism::BlockArgumentNode # method(&:target) - receiver_type, _scope = calculate_type_scope.call target_node - [:call, receiver_type, name, false] - else - [:symbol, name] unless name.empty? - end - when Prism::CallNode - return [:lvar_or_method, name, calculate_scope.call] if target_node.receiver.nil? - - self_call = target_node.receiver.is_a? Prism::SelfNode - op = target_node.call_operator - receiver_type, _scope = calculate_type_scope.call target_node.receiver - receiver_type = receiver_type.nonnillable if op == '&.' - [op == '::' ? :call_or_const : :call, receiver_type, name, self_call] - when Prism::LocalVariableReadNode, Prism::LocalVariableTargetNode - [:lvar_or_method, name, calculate_scope.call] - when Prism::ConstantReadNode, Prism::ConstantTargetNode - if parents.last.is_a? Prism::ConstantPathNode - path_node = parents.last - if path_node.parent # A::B - receiver, scope = calculate_type_scope.call(path_node.parent) - [:const, receiver, name, scope] - else # ::A - scope = calculate_scope.call - [:const, Types::SingletonType.new(Object), name, scope] - end - else - [:const, nil, name, calculate_scope.call] - end - when Prism::GlobalVariableReadNode, Prism::GlobalVariableTargetNode - [:gvar, name, calculate_scope.call] - when Prism::InstanceVariableReadNode, Prism::InstanceVariableTargetNode - [:ivar, name, calculate_scope.call] - when Prism::ClassVariableReadNode, Prism::ClassVariableTargetNode - [:cvar, name, calculate_scope.call] - end - end - - def find_target(node, position) - location = ( - case node - when Prism::CallNode - node.message_loc - when Prism::SymbolNode - node.value_loc - when Prism::StringNode - node.content_loc - when Prism::InterpolatedStringNode - node.closing_loc if node.parts.empty? - end - ) - return [node] if location&.start_offset == position - - node.compact_child_nodes.each do |n| - match = find_target(n, position) - next unless match - match.unshift node - return match - end - - [node] if node.location.start_offset == position - end - - def handle_error(e) - Completor.last_completion_error = e - end - end - end -end diff --git a/lib/irb/type_completion/methods.rb b/lib/irb/type_completion/methods.rb deleted file mode 100644 index 8a88b6d0f..000000000 --- a/lib/irb/type_completion/methods.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module IRB - module TypeCompletion - module Methods - OBJECT_SINGLETON_CLASS_METHOD = Object.instance_method(:singleton_class) - OBJECT_INSTANCE_VARIABLES_METHOD = Object.instance_method(:instance_variables) - OBJECT_INSTANCE_VARIABLE_GET_METHOD = Object.instance_method(:instance_variable_get) - OBJECT_CLASS_METHOD = Object.instance_method(:class) - MODULE_NAME_METHOD = Module.instance_method(:name) - end - end -end diff --git a/lib/irb/type_completion/scope.rb b/lib/irb/type_completion/scope.rb deleted file mode 100644 index 5a58a0ed6..000000000 --- a/lib/irb/type_completion/scope.rb +++ /dev/null @@ -1,412 +0,0 @@ -# frozen_string_literal: true - -require 'set' -require_relative 'types' - -module IRB - module TypeCompletion - - class RootScope - attr_reader :module_nesting, :self_object - - def initialize(binding, self_object, local_variables) - @binding = binding - @self_object = self_object - @cache = {} - modules = [*binding.eval('::Module.nesting'), Object] - @module_nesting = modules.map { [_1, []] } - binding_local_variables = binding.local_variables - uninitialized_locals = local_variables - binding_local_variables - uninitialized_locals.each { @cache[_1] = Types::NIL } - @local_variables = (local_variables | binding_local_variables).map(&:to_s).to_set - @global_variables = Kernel.global_variables.map(&:to_s).to_set - @owned_constants_cache = {} - end - - def level() = 0 - - def level_of(_name, _var_type) = 0 - - def mutable?() = false - - def module_own_constant?(mod, name) - set = (@owned_constants_cache[mod] ||= Set.new(mod.constants.map(&:to_s))) - set.include? name - end - - def get_const(nesting, path, _key = nil) - return unless nesting - - result = path.reduce nesting do |mod, name| - return nil unless mod.is_a?(Module) && module_own_constant?(mod, name) - mod.const_get name - end - Types.type_from_object result - end - - def get_cvar(nesting, path, name, _key = nil) - return Types::NIL unless nesting - - result = path.reduce nesting do |mod, n| - return Types::NIL unless mod.is_a?(Module) && module_own_constant?(mod, n) - mod.const_get n - end - value = result.class_variable_get name if result.is_a?(Module) && name.size >= 3 && result.class_variable_defined?(name) - Types.type_from_object value - end - - def [](name) - @cache[name] ||= ( - value = case RootScope.type_by_name name - when :ivar - begin - Methods::OBJECT_INSTANCE_VARIABLE_GET_METHOD.bind_call(@self_object, name) - rescue NameError - end - when :lvar - begin - @binding.local_variable_get(name) - rescue NameError - end - when :gvar - @binding.eval name if @global_variables.include? name - end - Types.type_from_object(value) - ) - end - - def self_type - Types.type_from_object @self_object - end - - def local_variables() = @local_variables.to_a - - def global_variables() = @global_variables.to_a - - def self.type_by_name(name) - if name.start_with? '@@' - # "@@cvar" or "@@cvar::[module_id]::[module_path]" - :cvar - elsif name.start_with? '@' - :ivar - elsif name.start_with? '$' - :gvar - elsif name.start_with? '%' - :internal - elsif name[0].downcase != name[0] || name[0].match?(/\d/) - # "ConstName" or "[module_id]::[const_path]" - :const - else - :lvar - end - end - end - - class Scope - BREAK_RESULT = '%break' - NEXT_RESULT = '%next' - RETURN_RESULT = '%return' - PATTERNMATCH_BREAK = '%match' - - attr_reader :parent, :mergeable_changes, :level, :module_nesting - - def self.from_binding(binding, locals) = new(RootScope.new(binding, binding.receiver, locals)) - - def initialize(parent, table = {}, trace_ivar: true, trace_lvar: true, self_type: nil, nesting: nil) - @parent = parent - @level = parent.level + 1 - @trace_ivar = trace_ivar - @trace_lvar = trace_lvar - @module_nesting = nesting ? [nesting, *parent.module_nesting] : parent.module_nesting - @self_type = self_type - @terminated = false - @jump_branches = [] - @mergeable_changes = @table = table.transform_values { [level, _1] } - end - - def mutable? = true - - def terminated? - @terminated - end - - def terminate_with(type, value) - return if terminated? - store_jump type, value, @mergeable_changes - terminate - end - - def store_jump(type, value, changes) - return if terminated? - if has_own?(type) - changes[type] = [level, value] - @jump_branches << changes - elsif @parent.mutable? - @parent.store_jump(type, value, changes) - end - end - - def terminate - return if terminated? - @terminated = true - @table = @mergeable_changes.dup - end - - def trace?(name) - return false unless @parent - type = RootScope.type_by_name(name) - type == :ivar ? @trace_ivar : type == :lvar ? @trace_lvar : true - end - - def level_of(name, var_type) - case var_type - when :ivar - return level unless @trace_ivar - when :gvar - return 0 - end - variable_level, = @table[name] - variable_level || parent.level_of(name, var_type) - end - - def get_const(nesting, path, key = nil) - key ||= [nesting.__id__, path].join('::') - _l, value = @table[key] - value || @parent.get_const(nesting, path, key) - end - - def get_cvar(nesting, path, name, key = nil) - key ||= [name, nesting.__id__, path].join('::') - _l, value = @table[key] - value || @parent.get_cvar(nesting, path, name, key) - end - - def [](name) - type = RootScope.type_by_name(name) - if type == :const - return get_const(nil, nil, name) || Types::NIL if name.include?('::') - - module_nesting.each do |(nesting, path)| - value = get_const nesting, [*path, name] - return value if value - end - return Types::NIL - elsif type == :cvar - return get_cvar(nil, nil, nil, name) if name.include?('::') - - nesting, path = module_nesting.first - return get_cvar(nesting, path, name) - end - level, value = @table[name] - if level - value - elsif trace? name - @parent[name] - elsif type == :ivar - self_instance_variable_get name - end - end - - def set_const(nesting, path, value) - key = [nesting.__id__, path].join('::') - @table[key] = [0, value] - end - - def set_cvar(nesting, path, name, value) - key = [name, nesting.__id__, path].join('::') - @table[key] = [0, value] - end - - def []=(name, value) - type = RootScope.type_by_name(name) - if type == :const - if name.include?('::') - @table[name] = [0, value] - else - parent_module, parent_path = module_nesting.first - set_const parent_module, [*parent_path, name], value - end - return - elsif type == :cvar - if name.include?('::') - @table[name] = [0, value] - else - parent_module, parent_path = module_nesting.first - set_cvar parent_module, parent_path, name, value - end - return - end - variable_level = level_of name, type - @table[name] = [variable_level, value] if variable_level - end - - def self_type - @self_type || @parent.self_type - end - - def global_variables - gvar_keys = @table.keys.select do |name| - RootScope.type_by_name(name) == :gvar - end - gvar_keys | @parent.global_variables - end - - def local_variables - lvar_keys = @table.keys.select do |name| - RootScope.type_by_name(name) == :lvar - end - lvar_keys |= @parent.local_variables if @trace_lvar - lvar_keys - end - - def table_constants - constants = module_nesting.flat_map do |mod, path| - prefix = [mod.__id__, *path].join('::') + '::' - @table.keys.select { _1.start_with? prefix }.map { _1.delete_prefix(prefix).split('::').first } - end.uniq - constants |= @parent.table_constants if @parent.mutable? - constants - end - - def table_module_constants(mod) - prefix = "#{mod.__id__}::" - constants = @table.keys.select { _1.start_with? prefix }.map { _1.delete_prefix(prefix).split('::').first } - constants |= @parent.table_constants if @parent.mutable? - constants - end - - def base_scope - @parent.mutable? ? @parent.base_scope : @parent - end - - def table_instance_variables - ivars = @table.keys.select { RootScope.type_by_name(_1) == :ivar } - ivars |= @parent.table_instance_variables if @parent.mutable? && @trace_ivar - ivars - end - - def instance_variables - self_singleton_types = self_type.types.grep(Types::SingletonType) - singleton_classes = self_type.types.grep(Types::InstanceType).map(&:klass).select(&:singleton_class?) - base_self = base_scope.self_object - self_instance_variables = singleton_classes.flat_map do |singleton_class| - if singleton_class.respond_to? :attached_object - Methods::OBJECT_INSTANCE_VARIABLES_METHOD.bind_call(singleton_class.attached_object).map(&:to_s) - elsif singleton_class == Methods::OBJECT_SINGLETON_CLASS_METHOD.bind_call(base_self) - Methods::OBJECT_INSTANCE_VARIABLES_METHOD.bind_call(base_self).map(&:to_s) - else - [] - end - end - [ - self_singleton_types.flat_map { _1.module_or_class.instance_variables.map(&:to_s) }, - self_instance_variables || [], - table_instance_variables - ].inject(:|) - end - - def self_instance_variable_get(name) - self_objects = self_type.types.grep(Types::SingletonType).map(&:module_or_class) - singleton_classes = self_type.types.grep(Types::InstanceType).map(&:klass).select(&:singleton_class?) - base_self = base_scope.self_object - singleton_classes.each do |singleton_class| - if singleton_class.respond_to? :attached_object - self_objects << singleton_class.attached_object - elsif singleton_class == base_self.singleton_class - self_objects << base_self - end - end - types = self_objects.map do |object| - value = begin - Methods::OBJECT_INSTANCE_VARIABLE_GET_METHOD.bind_call(object, name) - rescue NameError - end - Types.type_from_object value - end - Types::UnionType[*types] - end - - def table_class_variables - cvars = @table.keys.filter_map { _1.split('::', 2).first if RootScope.type_by_name(_1) == :cvar } - cvars |= @parent.table_class_variables if @parent.mutable? - cvars - end - - def class_variables - cvars = table_class_variables - m, = module_nesting.first - cvars |= m.class_variables.map(&:to_s) if m.is_a? Module - cvars - end - - def constants - module_nesting.flat_map do |nest,| - nest.constants - end.map(&:to_s) | table_constants - end - - def merge_jumps - if terminated? - @terminated = false - @table = @mergeable_changes - merge @jump_branches - @terminated = true - else - merge [*@jump_branches, {}] - end - end - - def conditional(&block) - run_branches(block, ->(_s) {}).first || Types::NIL - end - - def never(&block) - block.call Scope.new(self, { BREAK_RESULT => nil, NEXT_RESULT => nil, PATTERNMATCH_BREAK => nil, RETURN_RESULT => nil }) - end - - def run_branches(*blocks) - results = [] - branches = [] - blocks.each do |block| - scope = Scope.new self - result = block.call scope - next if scope.terminated? - results << result - branches << scope.mergeable_changes - end - terminate if branches.empty? - merge branches - results - end - - def has_own?(name) - @table.key? name - end - - def update(child_scope) - current_level = level - child_scope.mergeable_changes.each do |name, (level, value)| - self[name] = value if level <= current_level - end - end - - protected - - def merge(branches) - current_level = level - merge = {} - branches.each do |changes| - changes.each do |name, (level, value)| - next if current_level < level - (merge[name] ||= []) << value - end - end - merge.each do |name, values| - values << self[name] unless values.size == branches.size - values.compact! - self[name] = Types::UnionType[*values.compact] unless values.empty? - end - end - end - end -end diff --git a/lib/irb/type_completion/type_analyzer.rb b/lib/irb/type_completion/type_analyzer.rb deleted file mode 100644 index 344924c9f..000000000 --- a/lib/irb/type_completion/type_analyzer.rb +++ /dev/null @@ -1,1181 +0,0 @@ -# frozen_string_literal: true - -require 'set' -require_relative 'types' -require_relative 'scope' -require 'prism' - -module IRB - module TypeCompletion - class TypeAnalyzer - class DigTarget - def initialize(parents, receiver, &block) - @dig_ids = parents.to_h { [_1.__id__, true] } - @target_id = receiver.__id__ - @block = block - end - - def dig?(node) = @dig_ids[node.__id__] - def target?(node) = @target_id == node.__id__ - def resolve(type, scope) - @block.call type, scope - end - end - - OBJECT_METHODS = { - to_s: Types::STRING, - to_str: Types::STRING, - to_a: Types::ARRAY, - to_ary: Types::ARRAY, - to_h: Types::HASH, - to_hash: Types::HASH, - to_i: Types::INTEGER, - to_int: Types::INTEGER, - to_f: Types::FLOAT, - to_c: Types::COMPLEX, - to_r: Types::RATIONAL - } - - def initialize(dig_targets) - @dig_targets = dig_targets - end - - def evaluate(node, scope) - method = "evaluate_#{node.type}" - if respond_to? method - result = send method, node, scope - else - result = Types::NIL - end - @dig_targets.resolve result, scope if @dig_targets.target? node - result - end - - def evaluate_program_node(node, scope) - evaluate node.statements, scope - end - - def evaluate_statements_node(node, scope) - if node.body.empty? - Types::NIL - else - node.body.map { evaluate _1, scope }.last - end - end - - def evaluate_def_node(node, scope) - if node.receiver - self_type = evaluate node.receiver, scope - else - current_self_types = scope.self_type.types - self_types = current_self_types.map do |type| - if type.is_a?(Types::SingletonType) && type.module_or_class.is_a?(Class) - Types::InstanceType.new type.module_or_class - else - type - end - end - self_type = Types::UnionType[*self_types] - end - if @dig_targets.dig?(node.body) || @dig_targets.dig?(node.parameters) - params_table = node.locals.to_h { [_1.to_s, Types::NIL] } - method_scope = Scope.new( - scope, - { **params_table, Scope::BREAK_RESULT => nil, Scope::NEXT_RESULT => nil, Scope::RETURN_RESULT => nil }, - self_type: self_type, - trace_lvar: false, - trace_ivar: false - ) - if node.parameters - # node.parameters is Prism::ParametersNode - assign_parameters node.parameters, method_scope, [], {} - end - - if @dig_targets.dig?(node.body) - method_scope.conditional do |s| - evaluate node.body, s - end - end - method_scope.merge_jumps - scope.update method_scope - end - Types::SYMBOL - end - - def evaluate_integer_node(_node, _scope) = Types::INTEGER - - def evaluate_float_node(_node, _scope) = Types::FLOAT - - def evaluate_rational_node(_node, _scope) = Types::RATIONAL - - def evaluate_imaginary_node(_node, _scope) = Types::COMPLEX - - def evaluate_string_node(_node, _scope) = Types::STRING - - def evaluate_x_string_node(_node, _scope) - Types::UnionType[Types::STRING, Types::NIL] - end - - def evaluate_symbol_node(_node, _scope) = Types::SYMBOL - - def evaluate_regular_expression_node(_node, _scope) = Types::REGEXP - - def evaluate_string_concat_node(node, scope) - evaluate node.left, scope - evaluate node.right, scope - Types::STRING - end - - def evaluate_interpolated_string_node(node, scope) - node.parts.each { evaluate _1, scope } - Types::STRING - end - - def evaluate_interpolated_x_string_node(node, scope) - node.parts.each { evaluate _1, scope } - Types::STRING - end - - def evaluate_interpolated_symbol_node(node, scope) - node.parts.each { evaluate _1, scope } - Types::SYMBOL - end - - def evaluate_interpolated_regular_expression_node(node, scope) - node.parts.each { evaluate _1, scope } - Types::REGEXP - end - - def evaluate_embedded_statements_node(node, scope) - node.statements ? evaluate(node.statements, scope) : Types::NIL - Types::STRING - end - - def evaluate_embedded_variable_node(node, scope) - evaluate node.variable, scope - Types::STRING - end - - def evaluate_array_node(node, scope) - Types.array_of evaluate_list_splat_items(node.elements, scope) - end - - def evaluate_hash_node(node, scope) = evaluate_hash(node, scope) - def evaluate_keyword_hash_node(node, scope) = evaluate_hash(node, scope) - def evaluate_hash(node, scope) - keys = [] - values = [] - node.elements.each do |assoc| - case assoc - when Prism::AssocNode - keys << evaluate(assoc.key, scope) - values << evaluate(assoc.value, scope) - when Prism::AssocSplatNode - next unless assoc.value # def f(**); {**} - - hash = evaluate assoc.value, scope - unless hash.is_a?(Types::InstanceType) && hash.klass == Hash - hash = method_call hash, :to_hash, [], nil, nil, scope - end - if hash.is_a?(Types::InstanceType) && hash.klass == Hash - keys << hash.params[:K] if hash.params[:K] - values << hash.params[:V] if hash.params[:V] - end - end - end - if keys.empty? && values.empty? - Types::InstanceType.new Hash - else - Types::InstanceType.new Hash, K: Types::UnionType[*keys], V: Types::UnionType[*values] - end - end - - def evaluate_parentheses_node(node, scope) - node.body ? evaluate(node.body, scope) : Types::NIL - end - - def evaluate_constant_path_node(node, scope) - type, = evaluate_constant_node_info node, scope - type - end - - def evaluate_self_node(_node, scope) = scope.self_type - - def evaluate_true_node(_node, _scope) = Types::TRUE - - def evaluate_false_node(_node, _scope) = Types::FALSE - - def evaluate_nil_node(_node, _scope) = Types::NIL - - def evaluate_source_file_node(_node, _scope) = Types::STRING - - def evaluate_source_line_node(_node, _scope) = Types::INTEGER - - def evaluate_source_encoding_node(_node, _scope) = Types::InstanceType.new(Encoding) - - def evaluate_numbered_reference_read_node(_node, _scope) - Types::UnionType[Types::STRING, Types::NIL] - end - - def evaluate_back_reference_read_node(_node, _scope) - Types::UnionType[Types::STRING, Types::NIL] - end - - def evaluate_reference_read(node, scope) - scope[node.name.to_s] || Types::NIL - end - alias evaluate_constant_read_node evaluate_reference_read - alias evaluate_global_variable_read_node evaluate_reference_read - alias evaluate_local_variable_read_node evaluate_reference_read - alias evaluate_class_variable_read_node evaluate_reference_read - alias evaluate_instance_variable_read_node evaluate_reference_read - - - def evaluate_call_node(node, scope) - is_field_assign = node.name.match?(/[^<>=!\]]=\z/) || (node.name == :[]= && !node.call_operator) - receiver_type = node.receiver ? evaluate(node.receiver, scope) : scope.self_type - evaluate_method = lambda do |scope| - args_types, kwargs_types, block_sym_node, has_block = evaluate_call_node_arguments node, scope - - if block_sym_node - block_sym = block_sym_node.value - if @dig_targets.target? block_sym_node - # method(args, &:completion_target) - call_block_proc = ->(block_args, _self_type) do - block_receiver = block_args.first || Types::OBJECT - @dig_targets.resolve block_receiver, scope - Types::OBJECT - end - else - call_block_proc = ->(block_args, _self_type) do - block_receiver, *rest = block_args - block_receiver ? method_call(block_receiver || Types::OBJECT, block_sym, rest, nil, nil, scope) : Types::OBJECT - end - end - elsif node.block.is_a? Prism::BlockNode - call_block_proc = ->(block_args, block_self_type) do - scope.conditional do |s| - numbered_parameters = node.block.locals.grep(/\A_[1-9]/).map(&:to_s) - params_table = node.block.locals.to_h { [_1.to_s, Types::NIL] } - table = { **params_table, Scope::BREAK_RESULT => nil, Scope::NEXT_RESULT => nil } - block_scope = Scope.new s, table, self_type: block_self_type, trace_ivar: !block_self_type - # TODO kwargs - if node.block.parameters&.parameters - # node.block.parameters is Prism::BlockParametersNode - assign_parameters node.block.parameters.parameters, block_scope, block_args, {} - elsif !numbered_parameters.empty? - assign_numbered_parameters numbered_parameters, block_scope, block_args, {} - end - result = node.block.body ? evaluate(node.block.body, block_scope) : Types::NIL - block_scope.merge_jumps - s.update block_scope - nexts = block_scope[Scope::NEXT_RESULT] - breaks = block_scope[Scope::BREAK_RESULT] - if block_scope.terminated? - [Types::UnionType[*nexts], breaks] - else - [Types::UnionType[result, *nexts], breaks] - end - end - end - elsif has_block - call_block_proc = ->(_block_args, _self_type) { Types::OBJECT } - end - result = method_call receiver_type, node.name, args_types, kwargs_types, call_block_proc, scope - if is_field_assign - args_types.last || Types::NIL - else - result - end - end - if node.call_operator == '&.' - result = scope.conditional { evaluate_method.call _1 } - if receiver_type.nillable? - Types::UnionType[result, Types::NIL] - else - result - end - else - evaluate_method.call scope - end - end - - def evaluate_and_node(node, scope) = evaluate_and_or(node, scope, and_op: true) - def evaluate_or_node(node, scope) = evaluate_and_or(node, scope, and_op: false) - def evaluate_and_or(node, scope, and_op:) - left = evaluate node.left, scope - right = scope.conditional { evaluate node.right, _1 } - if and_op - Types::UnionType[right, Types::NIL, Types::FALSE] - else - Types::UnionType[left, right] - end - end - - def evaluate_call_operator_write_node(node, scope) = evaluate_call_write(node, scope, :operator, node.write_name) - def evaluate_call_and_write_node(node, scope) = evaluate_call_write(node, scope, :and, node.write_name) - def evaluate_call_or_write_node(node, scope) = evaluate_call_write(node, scope, :or, node.write_name) - def evaluate_index_operator_write_node(node, scope) = evaluate_call_write(node, scope, :operator, :[]=) - def evaluate_index_and_write_node(node, scope) = evaluate_call_write(node, scope, :and, :[]=) - def evaluate_index_or_write_node(node, scope) = evaluate_call_write(node, scope, :or, :[]=) - def evaluate_call_write(node, scope, operator, write_name) - receiver_type = evaluate node.receiver, scope - if write_name == :[]= - args_types, kwargs_types, block_sym_node, has_block = evaluate_call_node_arguments node, scope - else - args_types = [] - end - if block_sym_node - block_sym = block_sym_node.value - call_block_proc = ->(block_args, _self_type) do - block_receiver, *rest = block_args - block_receiver ? method_call(block_receiver || Types::OBJECT, block_sym, rest, nil, nil, scope) : Types::OBJECT - end - elsif has_block - call_block_proc = ->(_block_args, _self_type) { Types::OBJECT } - end - method = write_name.to_s.delete_suffix('=') - left = method_call receiver_type, method, args_types, kwargs_types, call_block_proc, scope - case operator - when :and - right = scope.conditional { evaluate node.value, _1 } - Types::UnionType[right, Types::NIL, Types::FALSE] - when :or - right = scope.conditional { evaluate node.value, _1 } - Types::UnionType[left, right] - else - right = evaluate node.value, scope - method_call left, node.operator, [right], nil, nil, scope, name_match: false - end - end - - def evaluate_variable_operator_write(node, scope) - left = scope[node.name.to_s] || Types::OBJECT - right = evaluate node.value, scope - scope[node.name.to_s] = method_call left, node.operator, [right], nil, nil, scope, name_match: false - end - alias evaluate_global_variable_operator_write_node evaluate_variable_operator_write - alias evaluate_local_variable_operator_write_node evaluate_variable_operator_write - alias evaluate_class_variable_operator_write_node evaluate_variable_operator_write - alias evaluate_instance_variable_operator_write_node evaluate_variable_operator_write - - def evaluate_variable_and_write(node, scope) - right = scope.conditional { evaluate node.value, scope } - scope[node.name.to_s] = Types::UnionType[right, Types::NIL, Types::FALSE] - end - alias evaluate_global_variable_and_write_node evaluate_variable_and_write - alias evaluate_local_variable_and_write_node evaluate_variable_and_write - alias evaluate_class_variable_and_write_node evaluate_variable_and_write - alias evaluate_instance_variable_and_write_node evaluate_variable_and_write - - def evaluate_variable_or_write(node, scope) - left = scope[node.name.to_s] || Types::OBJECT - right = scope.conditional { evaluate node.value, scope } - scope[node.name.to_s] = Types::UnionType[left, right] - end - alias evaluate_global_variable_or_write_node evaluate_variable_or_write - alias evaluate_local_variable_or_write_node evaluate_variable_or_write - alias evaluate_class_variable_or_write_node evaluate_variable_or_write - alias evaluate_instance_variable_or_write_node evaluate_variable_or_write - - def evaluate_constant_operator_write_node(node, scope) - left = scope[node.name.to_s] || Types::OBJECT - right = evaluate node.value, scope - scope[node.name.to_s] = method_call left, node.operator, [right], nil, nil, scope, name_match: false - end - - def evaluate_constant_and_write_node(node, scope) - right = scope.conditional { evaluate node.value, scope } - scope[node.name.to_s] = Types::UnionType[right, Types::NIL, Types::FALSE] - end - - def evaluate_constant_or_write_node(node, scope) - left = scope[node.name.to_s] || Types::OBJECT - right = scope.conditional { evaluate node.value, scope } - scope[node.name.to_s] = Types::UnionType[left, right] - end - - def evaluate_constant_path_operator_write_node(node, scope) - left, receiver, _parent_module, name = evaluate_constant_node_info node.target, scope - right = evaluate node.value, scope - value = method_call left, node.operator, [right], nil, nil, scope, name_match: false - const_path_write receiver, name, value, scope - value - end - - def evaluate_constant_path_and_write_node(node, scope) - _left, receiver, _parent_module, name = evaluate_constant_node_info node.target, scope - right = scope.conditional { evaluate node.value, scope } - value = Types::UnionType[right, Types::NIL, Types::FALSE] - const_path_write receiver, name, value, scope - value - end - - def evaluate_constant_path_or_write_node(node, scope) - left, receiver, _parent_module, name = evaluate_constant_node_info node.target, scope - right = scope.conditional { evaluate node.value, scope } - value = Types::UnionType[left, right] - const_path_write receiver, name, value, scope - value - end - - def evaluate_constant_path_write_node(node, scope) - receiver = evaluate node.target.parent, scope if node.target.parent - value = evaluate node.value, scope - const_path_write receiver, node.target.child.name.to_s, value, scope - value - end - - def evaluate_lambda_node(node, scope) - local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] } - block_scope = Scope.new scope, { **local_table, Scope::BREAK_RESULT => nil, Scope::NEXT_RESULT => nil, Scope::RETURN_RESULT => nil } - block_scope.conditional do |s| - assign_parameters node.parameters.parameters, s, [], {} if node.parameters&.parameters - evaluate node.body, s if node.body - end - block_scope.merge_jumps - scope.update block_scope - Types::PROC - end - - def evaluate_reference_write(node, scope) - scope[node.name.to_s] = evaluate node.value, scope - end - alias evaluate_constant_write_node evaluate_reference_write - alias evaluate_global_variable_write_node evaluate_reference_write - alias evaluate_local_variable_write_node evaluate_reference_write - alias evaluate_class_variable_write_node evaluate_reference_write - alias evaluate_instance_variable_write_node evaluate_reference_write - - def evaluate_multi_write_node(node, scope) - evaluated_receivers = {} - evaluate_multi_write_receiver node, scope, evaluated_receivers - value = ( - if node.value.is_a? Prism::ArrayNode - if node.value.elements.any?(Prism::SplatNode) - evaluate node.value, scope - else - node.value.elements.map do |n| - evaluate n, scope - end - end - elsif node.value - evaluate node.value, scope - else - Types::NIL - end - ) - evaluate_multi_write node, value, scope, evaluated_receivers - value.is_a?(Array) ? Types.array_of(*value) : value - end - - def evaluate_if_node(node, scope) = evaluate_if_unless(node, scope) - def evaluate_unless_node(node, scope) = evaluate_if_unless(node, scope) - def evaluate_if_unless(node, scope) - evaluate node.predicate, scope - Types::UnionType[*scope.run_branches( - -> { node.statements ? evaluate(node.statements, _1) : Types::NIL }, - -> { node.consequent ? evaluate(node.consequent, _1) : Types::NIL } - )] - end - - def evaluate_else_node(node, scope) - node.statements ? evaluate(node.statements, scope) : Types::NIL - end - - def evaluate_while_until(node, scope) - inner_scope = Scope.new scope, { Scope::BREAK_RESULT => nil } - evaluate node.predicate, inner_scope - if node.statements - inner_scope.conditional do |s| - evaluate node.statements, s - end - end - inner_scope.merge_jumps - scope.update inner_scope - breaks = inner_scope[Scope::BREAK_RESULT] - breaks ? Types::UnionType[breaks, Types::NIL] : Types::NIL - end - alias evaluate_while_node evaluate_while_until - alias evaluate_until_node evaluate_while_until - - def evaluate_break_node(node, scope) = evaluate_jump(node, scope, :break) - def evaluate_next_node(node, scope) = evaluate_jump(node, scope, :next) - def evaluate_return_node(node, scope) = evaluate_jump(node, scope, :return) - def evaluate_jump(node, scope, mode) - internal_key = ( - case mode - when :break - Scope::BREAK_RESULT - when :next - Scope::NEXT_RESULT - when :return - Scope::RETURN_RESULT - end - ) - jump_value = ( - arguments = node.arguments&.arguments - if arguments.nil? || arguments.empty? - Types::NIL - elsif arguments.size == 1 && !arguments.first.is_a?(Prism::SplatNode) - evaluate arguments.first, scope - else - Types.array_of evaluate_list_splat_items(arguments, scope) - end - ) - scope.terminate_with internal_key, jump_value - Types::NIL - end - - def evaluate_yield_node(node, scope) - evaluate_list_splat_items node.arguments.arguments, scope if node.arguments - Types::OBJECT - end - - def evaluate_redo_node(_node, scope) - scope.terminate - Types::NIL - end - - def evaluate_retry_node(_node, scope) - scope.terminate - Types::NIL - end - - def evaluate_forwarding_super_node(_node, _scope) = Types::OBJECT - - def evaluate_super_node(node, scope) - evaluate_list_splat_items node.arguments.arguments, scope if node.arguments - Types::OBJECT - end - - def evaluate_begin_node(node, scope) - return_type = node.statements ? evaluate(node.statements, scope) : Types::NIL - if node.rescue_clause - if node.else_clause - return_types = scope.run_branches( - ->{ evaluate node.rescue_clause, _1 }, - ->{ evaluate node.else_clause, _1 } - ) - else - return_types = [ - return_type, - scope.conditional { evaluate node.rescue_clause, _1 } - ] - end - return_type = Types::UnionType[*return_types] - end - if node.ensure_clause&.statements - # ensure_clause is Prism::EnsureNode - evaluate node.ensure_clause.statements, scope - end - return_type - end - - def evaluate_rescue_node(node, scope) - run_rescue = lambda do |s| - if node.reference - error_classes_type = evaluate_list_splat_items node.exceptions, s - error_types = error_classes_type.types.filter_map do - Types::InstanceType.new _1.module_or_class if _1.is_a?(Types::SingletonType) - end - error_types << Types::InstanceType.new(StandardError) if error_types.empty? - error_type = Types::UnionType[*error_types] - case node.reference - when Prism::LocalVariableTargetNode, Prism::InstanceVariableTargetNode, Prism::ClassVariableTargetNode, Prism::GlobalVariableTargetNode, Prism::ConstantTargetNode - s[node.reference.name.to_s] = error_type - when Prism::CallNode - evaluate node.reference, s - end - end - node.statements ? evaluate(node.statements, s) : Types::NIL - end - if node.consequent # begin; rescue A; rescue B; end - types = scope.run_branches( - run_rescue, - -> { evaluate node.consequent, _1 } - ) - Types::UnionType[*types] - else - run_rescue.call scope - end - end - - def evaluate_rescue_modifier_node(node, scope) - a = evaluate node.expression, scope - b = scope.conditional { evaluate node.rescue_expression, _1 } - Types::UnionType[a, b] - end - - def evaluate_singleton_class_node(node, scope) - klass_types = evaluate(node.expression, scope).types.filter_map do |type| - Types::SingletonType.new type.klass if type.is_a? Types::InstanceType - end - klass_types = [Types::CLASS] if klass_types.empty? - table = node.locals.to_h { [_1.to_s, Types::NIL] } - sclass_scope = Scope.new( - scope, - { **table, Scope::BREAK_RESULT => nil, Scope::NEXT_RESULT => nil, Scope::RETURN_RESULT => nil }, - trace_ivar: false, - trace_lvar: false, - self_type: Types::UnionType[*klass_types] - ) - result = node.body ? evaluate(node.body, sclass_scope) : Types::NIL - scope.update sclass_scope - result - end - - def evaluate_class_node(node, scope) = evaluate_class_module(node, scope, true) - def evaluate_module_node(node, scope) = evaluate_class_module(node, scope, false) - def evaluate_class_module(node, scope, is_class) - unless node.constant_path.is_a?(Prism::ConstantReadNode) || node.constant_path.is_a?(Prism::ConstantPathNode) - # Incomplete class/module `class (statement[cursor_here])::Name; end` - evaluate node.constant_path, scope - return Types::NIL - end - const_type, _receiver, parent_module, name = evaluate_constant_node_info node.constant_path, scope - if is_class - select_class_type = -> { _1.is_a?(Types::SingletonType) && _1.module_or_class.is_a?(Class) } - module_types = const_type.types.select(&select_class_type) - module_types += evaluate(node.superclass, scope).types.select(&select_class_type) if node.superclass - module_types << Types::CLASS if module_types.empty? - else - module_types = const_type.types.select { _1.is_a?(Types::SingletonType) && !_1.module_or_class.is_a?(Class) } - module_types << Types::MODULE if module_types.empty? - end - return Types::NIL unless node.body - - table = node.locals.to_h { [_1.to_s, Types::NIL] } - if !name.empty? && (parent_module.is_a?(Module) || parent_module.nil?) - value = parent_module.const_get name if parent_module&.const_defined? name - unless value - value_type = scope[name] - value = value_type.module_or_class if value_type.is_a? Types::SingletonType - end - - if value.is_a? Module - nesting = [value, []] - else - if parent_module - nesting = [parent_module, [name]] - else - parent_nesting, parent_path = scope.module_nesting.first - nesting = [parent_nesting, parent_path + [name]] - end - nesting_key = [nesting[0].__id__, nesting[1]].join('::') - nesting_value = is_class ? Types::CLASS : Types::MODULE - end - else - # parent_module == :unknown - # TODO: dummy module - end - module_scope = Scope.new( - scope, - { **table, Scope::BREAK_RESULT => nil, Scope::NEXT_RESULT => nil, Scope::RETURN_RESULT => nil }, - trace_ivar: false, - trace_lvar: false, - self_type: Types::UnionType[*module_types], - nesting: nesting - ) - module_scope[nesting_key] = nesting_value if nesting_value - result = evaluate(node.body, module_scope) - scope.update module_scope - result - end - - def evaluate_for_node(node, scope) - node.statements - collection = evaluate node.collection, scope - inner_scope = Scope.new scope, { Scope::BREAK_RESULT => nil } - ary_type = method_call collection, :to_ary, [], nil, nil, nil, name_match: false - element_types = ary_type.types.filter_map do |ary| - ary.params[:Elem] if ary.is_a?(Types::InstanceType) && ary.klass == Array - end - element_type = Types::UnionType[*element_types] - inner_scope.conditional do |s| - evaluate_write node.index, element_type, s, nil - evaluate node.statements, s if node.statements - end - inner_scope.merge_jumps - scope.update inner_scope - breaks = inner_scope[Scope::BREAK_RESULT] - breaks ? Types::UnionType[breaks, collection] : collection - end - - def evaluate_case_node(node, scope) - evaluate(node.predicate, scope) if node.predicate - # TODO - branches = node.conditions.map do |condition| - ->(s) { evaluate_case_when_condition condition, s } - end - if node.consequent - branches << ->(s) { evaluate node.consequent, s } - else - branches << ->(_s) { Types::NIL } - end - Types::UnionType[*scope.run_branches(*branches)] - end - - def evaluate_case_match_node(node, scope) - target = evaluate(node.predicate, scope) - # TODO - branches = node.conditions.map do |condition| - ->(s) { evaluate_case_in_condition target, condition, s } - end - if node.consequent - branches << ->(s) { evaluate node.consequent, s } - end - Types::UnionType[*scope.run_branches(*branches)] - end - - def evaluate_match_required_node(node, scope) - value_type = evaluate node.value, scope - evaluate_match_pattern value_type, node.pattern, scope - Types::NIL # void value - end - - def evaluate_match_predicate_node(node, scope) - value_type = evaluate node.value, scope - scope.conditional { evaluate_match_pattern value_type, node.pattern, _1 } - Types::BOOLEAN - end - - def evaluate_range_node(node, scope) - beg_type = evaluate node.left, scope if node.left - end_type = evaluate node.right, scope if node.right - elem = (Types::UnionType[*[beg_type, end_type].compact]).nonnillable - Types::InstanceType.new Range, Elem: elem - end - - def evaluate_defined_node(node, scope) - scope.conditional { evaluate node.value, _1 } - Types::UnionType[Types::STRING, Types::NIL] - end - - def evaluate_flip_flop_node(node, scope) - scope.conditional { evaluate node.left, _1 } if node.left - scope.conditional { evaluate node.right, _1 } if node.right - Types::BOOLEAN - end - - def evaluate_multi_target_node(node, scope) - # Raw MultiTargetNode, incomplete code like `a,b`, `*a`. - evaluate_multi_write_receiver node, scope, nil - Types::NIL - end - - def evaluate_splat_node(node, scope) - # Raw SplatNode, incomplete code like `*a.` - evaluate_multi_write_receiver node.expression, scope, nil if node.expression - Types::NIL - end - - def evaluate_implicit_node(node, scope) - evaluate node.value, scope - end - - def evaluate_match_write_node(node, scope) - # /(?)(?)/ =~ string - evaluate node.call, scope - locals = node.targets.map(&:name) - locals.each { scope[_1.to_s] = Types::UnionType[Types::STRING, Types::NIL] } - Types::BOOLEAN - end - - def evaluate_match_last_line_node(_node, _scope) - Types::BOOLEAN - end - - def evaluate_interpolated_match_last_line_node(node, scope) - node.parts.each { evaluate _1, scope } - Types::BOOLEAN - end - - def evaluate_pre_execution_node(node, scope) - node.statements ? evaluate(node.statements, scope) : Types::NIL - end - - def evaluate_post_execution_node(node, scope) - node.statements && @dig_targets.dig?(node.statements) ? evaluate(node.statements, scope) : Types::NIL - end - - def evaluate_alias_method_node(_node, _scope) = Types::NIL - def evaluate_alias_global_variable_node(_node, _scope) = Types::NIL - def evaluate_undef_node(_node, _scope) = Types::NIL - def evaluate_missing_node(_node, _scope) = Types::NIL - - def evaluate_call_node_arguments(call_node, scope) - # call_node.arguments is Prism::ArgumentsNode - arguments = call_node.arguments&.arguments&.dup || [] - block_arg = call_node.block.expression if call_node.block.is_a?(Prism::BlockArgumentNode) - kwargs = arguments.pop.elements if arguments.last.is_a?(Prism::KeywordHashNode) - args_types = arguments.map do |arg| - case arg - when Prism::ForwardingArgumentsNode - # `f(a, ...)` treat like splat - nil - when Prism::SplatNode - evaluate arg.expression, scope if arg.expression - nil # TODO: splat - else - evaluate arg, scope - end - end - if kwargs - kwargs_types = kwargs.map do |arg| - case arg - when Prism::AssocNode - if arg.key.is_a?(Prism::SymbolNode) - [arg.key.value, evaluate(arg.value, scope)] - else - evaluate arg.key, scope - evaluate arg.value, scope - nil - end - when Prism::AssocSplatNode - evaluate arg.value, scope if arg.value - nil - end - end.compact.to_h - end - if block_arg.is_a? Prism::SymbolNode - block_sym_node = block_arg - elsif block_arg - evaluate block_arg, scope - end - [args_types, kwargs_types, block_sym_node, !!block_arg] - end - - def const_path_write(receiver, name, value, scope) - if receiver # receiver::A = value - singleton_type = receiver.types.find { _1.is_a? Types::SingletonType } - scope.set_const singleton_type.module_or_class, name, value if singleton_type - else # ::A = value - scope.set_const Object, name, value - end - end - - def assign_required_parameter(node, value, scope) - case node - when Prism::RequiredParameterNode - scope[node.name.to_s] = value || Types::OBJECT - when Prism::MultiTargetNode - parameters = [*node.lefts, *node.rest, *node.rights] - values = value ? sized_splat(value, :to_ary, parameters.size) : [] - parameters.zip values do |n, v| - assign_required_parameter n, v, scope - end - when Prism::SplatNode - splat_value = value ? Types.array_of(value) : Types::ARRAY - assign_required_parameter node.expression, splat_value, scope if node.expression - end - end - - def evaluate_constant_node_info(node, scope) - case node - when Prism::ConstantPathNode - name = node.child.name.to_s - if node.parent - receiver = evaluate node.parent, scope - if receiver.is_a? Types::SingletonType - parent_module = receiver.module_or_class - end - else - parent_module = Object - end - if parent_module - type = scope.get_const(parent_module, [name]) || Types::NIL - else - parent_module = :unknown - type = Types::NIL - end - when Prism::ConstantReadNode - name = node.name.to_s - type = scope[name] - end - @dig_targets.resolve type, scope if @dig_targets.target? node - [type, receiver, parent_module, name] - end - - - def assign_parameters(node, scope, args, kwargs) - args = args.dup - kwargs = kwargs.dup - size = node.requireds.size + node.optionals.size + (node.rest ? 1 : 0) + node.posts.size - args = sized_splat(args.first, :to_ary, size) if size >= 2 && args.size == 1 - reqs = args.shift node.requireds.size - if node.rest - # node.rest is Prism::RestParameterNode - posts = [] - opts = args.shift node.optionals.size - rest = args - else - posts = args.pop node.posts.size - opts = args - rest = [] - end - node.requireds.zip reqs do |n, v| - assign_required_parameter n, v, scope - end - node.optionals.zip opts do |n, v| - # n is Prism::OptionalParameterNode - values = [v] - values << evaluate(n.value, scope) if n.value - scope[n.name.to_s] = Types::UnionType[*values.compact] - end - node.posts.zip posts do |n, v| - assign_required_parameter n, v, scope - end - if node.rest&.name - # node.rest is Prism::RestParameterNode - scope[node.rest.name.to_s] = Types.array_of(*rest) - end - node.keywords.each do |n| - name = n.name.to_s.delete(':') - values = [kwargs.delete(name)] - # n is Prism::OptionalKeywordParameterNode (has n.value) or Prism::RequiredKeywordParameterNode (does not have n.value) - values << evaluate(n.value, scope) if n.respond_to?(:value) - scope[name] = Types::UnionType[*values.compact] - end - # node.keyword_rest is Prism::KeywordRestParameterNode or Prism::ForwardingParameterNode or Prism::NoKeywordsParameterNode - if node.keyword_rest.is_a?(Prism::KeywordRestParameterNode) && node.keyword_rest.name - scope[node.keyword_rest.name.to_s] = Types::InstanceType.new(Hash, K: Types::SYMBOL, V: Types::UnionType[*kwargs.values]) - end - if node.block&.name - # node.block is Prism::BlockParameterNode - scope[node.block.name.to_s] = Types::PROC - end - end - - def assign_numbered_parameters(numbered_parameters, scope, args, _kwargs) - return if numbered_parameters.empty? - max_num = numbered_parameters.map { _1[1].to_i }.max - if max_num == 1 - scope['_1'] = args.first || Types::NIL - else - args = sized_splat(args.first, :to_ary, max_num) if args.size == 1 - numbered_parameters.each do |name| - index = name[1].to_i - 1 - scope[name] = args[index] || Types::NIL - end - end - end - - def evaluate_case_when_condition(node, scope) - node.conditions.each { evaluate _1, scope } - node.statements ? evaluate(node.statements, scope) : Types::NIL - end - - def evaluate_case_in_condition(target, node, scope) - pattern = node.pattern - if pattern.is_a?(Prism::IfNode) || pattern.is_a?(Prism::UnlessNode) - cond_node = pattern.predicate - pattern = pattern.statements.body.first - end - evaluate_match_pattern(target, pattern, scope) - evaluate cond_node, scope if cond_node # TODO: conditional branch - node.statements ? evaluate(node.statements, scope) : Types::NIL - end - - def evaluate_match_pattern(value, pattern, scope) - # TODO: scope.terminate_with Scope::PATTERNMATCH_BREAK, Types::NIL - case pattern - when Prism::FindPatternNode - # TODO - evaluate_match_pattern Types::OBJECT, pattern.left, scope - pattern.requireds.each { evaluate_match_pattern Types::OBJECT, _1, scope } - evaluate_match_pattern Types::OBJECT, pattern.right, scope - when Prism::ArrayPatternNode - # TODO - pattern.requireds.each { evaluate_match_pattern Types::OBJECT, _1, scope } - evaluate_match_pattern Types::OBJECT, pattern.rest, scope if pattern.rest - pattern.posts.each { evaluate_match_pattern Types::OBJECT, _1, scope } - Types::ARRAY - when Prism::HashPatternNode - # TODO - pattern.elements.each { evaluate_match_pattern Types::OBJECT, _1, scope } - if pattern.respond_to?(:rest) && pattern.rest - evaluate_match_pattern Types::OBJECT, pattern.rest, scope - end - Types::HASH - when Prism::AssocNode - evaluate_match_pattern value, pattern.value, scope if pattern.value - Types::OBJECT - when Prism::AssocSplatNode - # TODO - evaluate_match_pattern Types::HASH, pattern.value, scope - Types::OBJECT - when Prism::PinnedVariableNode - evaluate pattern.variable, scope - when Prism::PinnedExpressionNode - evaluate pattern.expression, scope - when Prism::LocalVariableTargetNode - scope[pattern.name.to_s] = value - when Prism::AlternationPatternNode - Types::UnionType[evaluate_match_pattern(value, pattern.left, scope), evaluate_match_pattern(value, pattern.right, scope)] - when Prism::CapturePatternNode - capture_type = class_or_value_to_instance evaluate_match_pattern(value, pattern.value, scope) - value = capture_type unless capture_type.types.empty? || capture_type.types == [Types::OBJECT] - evaluate_match_pattern value, pattern.target, scope - when Prism::SplatNode - value = Types.array_of value - evaluate_match_pattern value, pattern.expression, scope if pattern.expression - value - else - # literal node - type = evaluate(pattern, scope) - class_or_value_to_instance(type) - end - end - - def class_or_value_to_instance(type) - instance_types = type.types.map do |t| - t.is_a?(Types::SingletonType) ? Types::InstanceType.new(t.module_or_class) : t - end - Types::UnionType[*instance_types] - end - - def evaluate_write(node, value, scope, evaluated_receivers) - case node - when Prism::MultiTargetNode - evaluate_multi_write node, value, scope, evaluated_receivers - when Prism::CallNode - evaluated_receivers&.[](node.receiver) || evaluate(node.receiver, scope) if node.receiver - when Prism::SplatNode - evaluate_write node.expression, Types.array_of(value), scope, evaluated_receivers if node.expression - when Prism::LocalVariableTargetNode, Prism::GlobalVariableTargetNode, Prism::InstanceVariableTargetNode, Prism::ClassVariableTargetNode, Prism::ConstantTargetNode - scope[node.name.to_s] = value - when Prism::ConstantPathTargetNode - receiver = evaluated_receivers&.[](node.parent) || evaluate(node.parent, scope) if node.parent - const_path_write receiver, node.child.name.to_s, value, scope - value - end - end - - def evaluate_multi_write(node, values, scope, evaluated_receivers) - pre_targets = node.lefts - splat_target = node.rest - post_targets = node.rights - size = pre_targets.size + (splat_target ? 1 : 0) + post_targets.size - values = values.is_a?(Array) ? values.dup : sized_splat(values, :to_ary, size) - pre_pairs = pre_targets.zip(values.shift(pre_targets.size)) - post_pairs = post_targets.zip(values.pop(post_targets.size)) - splat_pairs = splat_target ? [[splat_target, Types::UnionType[*values]]] : [] - (pre_pairs + splat_pairs + post_pairs).each do |target, value| - evaluate_write target, value || Types::NIL, scope, evaluated_receivers - end - end - - def evaluate_multi_write_receiver(node, scope, evaluated_receivers) - case node - when Prism::MultiWriteNode, Prism::MultiTargetNode - targets = [*node.lefts, *node.rest, *node.rights] - targets.each { evaluate_multi_write_receiver _1, scope, evaluated_receivers } - when Prism::CallNode - if node.receiver - receiver = evaluate(node.receiver, scope) - evaluated_receivers[node.receiver] = receiver if evaluated_receivers - end - if node.arguments - node.arguments.arguments&.each do |arg| - if arg.is_a? Prism::SplatNode - evaluate arg.expression, scope - else - evaluate arg, scope - end - end - end - when Prism::SplatNode - evaluate_multi_write_receiver node.expression, scope, evaluated_receivers if node.expression - end - end - - def evaluate_list_splat_items(list, scope) - items = list.flat_map do |node| - if node.is_a? Prism::SplatNode - next unless node.expression # def f(*); [*] - - splat = evaluate node.expression, scope - array_elem, non_array = partition_to_array splat.nonnillable, :to_a - [*array_elem, *non_array] - else - evaluate node, scope - end - end.compact.uniq - Types::UnionType[*items] - end - - def sized_splat(value, method, size) - array_elem, non_array = partition_to_array value, method - values = [Types::UnionType[*array_elem, *non_array]] - values += [array_elem] * (size - 1) if array_elem && size >= 1 - values - end - - def partition_to_array(value, method) - arrays, non_arrays = value.types.partition { _1.is_a?(Types::InstanceType) && _1.klass == Array } - non_arrays.select! do |type| - to_array_result = method_call type, method, [], nil, nil, nil, name_match: false - if to_array_result.is_a?(Types::InstanceType) && to_array_result.klass == Array - arrays << to_array_result - false - else - true - end - end - array_elem = arrays.empty? ? nil : Types::UnionType[*arrays.map { _1.params[:Elem] || Types::OBJECT }] - non_array = non_arrays.empty? ? nil : Types::UnionType[*non_arrays] - [array_elem, non_array] - end - - def method_call(receiver, method_name, args, kwargs, block, scope, name_match: true) - methods = Types.rbs_methods receiver, method_name.to_sym, args, kwargs, !!block - block_called = false - type_breaks = methods.map do |method, given_params, method_params| - receiver_vars = receiver.is_a?(Types::InstanceType) ? receiver.params : {} - free_vars = method.type.free_variables - receiver_vars.keys.to_set - vars = receiver_vars.merge Types.match_free_variables(free_vars, method_params, given_params) - if block && method.block - params_type = method.block.type.required_positionals.map do |func_param| - Types.from_rbs_type func_param.type, receiver, vars - end - self_type = Types.from_rbs_type method.block.self_type, receiver, vars if method.block.self_type - block_response, breaks = block.call params_type, self_type - block_called = true - vars.merge! Types.match_free_variables(free_vars - vars.keys.to_set, [method.block.type.return_type], [block_response]) - end - if Types.method_return_bottom?(method) - [nil, breaks] - else - [Types.from_rbs_type(method.type.return_type, receiver, vars || {}), breaks] - end - end - block&.call [], nil unless block_called - terminates = !type_breaks.empty? && type_breaks.map(&:first).all?(&:nil?) - types = type_breaks.map(&:first).compact - breaks = type_breaks.map(&:last).compact - types << OBJECT_METHODS[method_name.to_sym] if name_match && OBJECT_METHODS.has_key?(method_name.to_sym) - - if method_name.to_sym == :new - receiver.types.each do |type| - if type.is_a?(Types::SingletonType) && type.module_or_class.is_a?(Class) - types << Types::InstanceType.new(type.module_or_class) - end - end - end - scope&.terminate if terminates && breaks.empty? - Types::UnionType[*types, *breaks] - end - - def self.calculate_target_type_scope(binding, parents, target) - dig_targets = DigTarget.new(parents, target) do |type, scope| - return type, scope - end - program = parents.first - scope = Scope.from_binding(binding, program.locals) - new(dig_targets).evaluate program, scope - [Types::NIL, scope] - end - end - end -end diff --git a/lib/irb/type_completion/types.rb b/lib/irb/type_completion/types.rb deleted file mode 100644 index f0f2342ff..000000000 --- a/lib/irb/type_completion/types.rb +++ /dev/null @@ -1,426 +0,0 @@ -# frozen_string_literal: true - -require_relative 'methods' - -module IRB - module TypeCompletion - module Types - OBJECT_TO_TYPE_SAMPLE_SIZE = 50 - - singleton_class.attr_reader :rbs_builder, :rbs_load_error - - def self.preload_in_thread - return if @preload_started - - @preload_started = true - Thread.new do - load_rbs_builder - end - end - - def self.load_rbs_builder - require 'rbs' - require 'rbs/cli' - loader = RBS::CLI::LibraryOptions.new.loader - loader.add path: Pathname('sig') - @rbs_builder = RBS::DefinitionBuilder.new env: RBS::Environment.from_loader(loader).resolve_type_names - rescue LoadError, StandardError => e - @rbs_load_error = e - nil - end - - def self.class_name_of(klass) - klass = klass.superclass if klass.singleton_class? - Methods::MODULE_NAME_METHOD.bind_call klass - end - - def self.rbs_search_method(klass, method_name, singleton) - klass.ancestors.each do |ancestor| - name = class_name_of ancestor - next unless name && rbs_builder - type_name = RBS::TypeName(name).absolute! - definition = (singleton ? rbs_builder.build_singleton(type_name) : rbs_builder.build_instance(type_name)) rescue nil - method = definition.methods[method_name] if definition - return method if method - end - nil - end - - def self.method_return_type(type, method_name) - receivers = type.types.map do |t| - case t - in SingletonType - [t, t.module_or_class, true] - in InstanceType - [t, t.klass, false] - end - end - types = receivers.flat_map do |receiver_type, klass, singleton| - method = rbs_search_method klass, method_name, singleton - next [] unless method - method.method_types.map do |method| - from_rbs_type(method.type.return_type, receiver_type, {}) - end - end - UnionType[*types] - end - - def self.rbs_methods(type, method_name, args_types, kwargs_type, has_block) - return [] unless rbs_builder - - receivers = type.types.map do |t| - case t - in SingletonType - [t, t.module_or_class, true] - in InstanceType - [t, t.klass, false] - end - end - has_splat = args_types.include?(nil) - methods_with_score = receivers.flat_map do |receiver_type, klass, singleton| - method = rbs_search_method klass, method_name, singleton - next [] unless method - method.method_types.map do |method_type| - score = 0 - score += 2 if !!method_type.block == has_block - reqs = method_type.type.required_positionals - opts = method_type.type.optional_positionals - rest = method_type.type.rest_positionals - trailings = method_type.type.trailing_positionals - keyreqs = method_type.type.required_keywords - keyopts = method_type.type.optional_keywords - keyrest = method_type.type.rest_keywords - args = args_types - if kwargs_type&.any? && keyreqs.empty? && keyopts.empty? && keyrest.nil? - kw_value_type = UnionType[*kwargs_type.values] - args += [InstanceType.new(Hash, K: SYMBOL, V: kw_value_type)] - end - if has_splat - score += 1 if args.count(&:itself) <= reqs.size + opts.size + trailings.size - elsif reqs.size + trailings.size <= args.size && (rest || args.size <= reqs.size + opts.size + trailings.size) - score += 2 - centers = args[reqs.size...-trailings.size] - given = args.first(reqs.size) + centers.take(opts.size) + args.last(trailings.size) - expected = (reqs + opts.take(centers.size) + trailings).map(&:type) - if rest - given << UnionType[*centers.drop(opts.size)] - expected << rest.type - end - if given.any? - score += given.zip(expected).count do |t, e| - e = from_rbs_type e, receiver_type - intersect?(t, e) || (intersect?(STRING, e) && t.methods.include?(:to_str)) || (intersect?(INTEGER, e) && t.methods.include?(:to_int)) || (intersect?(ARRAY, e) && t.methods.include?(:to_ary)) - end.fdiv(given.size) - end - end - [[method_type, given || [], expected || []], score] - end - end - max_score = methods_with_score.map(&:last).max - methods_with_score.select { _2 == max_score }.map(&:first) - end - - def self.intersect?(a, b) - atypes = a.types.group_by(&:class) - btypes = b.types.group_by(&:class) - if atypes[SingletonType] && btypes[SingletonType] - aa, bb = [atypes, btypes].map {|types| types[SingletonType].map(&:module_or_class) } - return true if (aa & bb).any? - end - - aa, bb = [atypes, btypes].map {|types| (types[InstanceType] || []).map(&:klass) } - (aa.flat_map(&:ancestors) & bb).any? - end - - def self.type_from_object(object) - case object - when Array - InstanceType.new Array, { Elem: union_type_from_objects(object) } - when Hash - InstanceType.new Hash, { K: union_type_from_objects(object.keys), V: union_type_from_objects(object.values) } - when Module - SingletonType.new object - else - klass = Methods::OBJECT_SINGLETON_CLASS_METHOD.bind_call(object) rescue Methods::OBJECT_CLASS_METHOD.bind_call(object) - InstanceType.new klass - end - end - - def self.union_type_from_objects(objects) - values = objects.size <= OBJECT_TO_TYPE_SAMPLE_SIZE ? objects : objects.sample(OBJECT_TO_TYPE_SAMPLE_SIZE) - klasses = values.map { Methods::OBJECT_CLASS_METHOD.bind_call(_1) } - UnionType[*klasses.uniq.map { InstanceType.new _1 }] - end - - class SingletonType - attr_reader :module_or_class - def initialize(module_or_class) - @module_or_class = module_or_class - end - def transform() = yield(self) - def methods() = @module_or_class.methods - def all_methods() = methods | Kernel.methods - def constants() = @module_or_class.constants - def types() = [self] - def nillable?() = false - def nonnillable() = self - def inspect - "#{module_or_class}.itself" - end - end - - class InstanceType - attr_reader :klass, :params - def initialize(klass, params = {}) - @klass = klass - @params = params - end - def transform() = yield(self) - def methods() = rbs_methods.select { _2.public? }.keys | @klass.instance_methods - def all_methods() = rbs_methods.keys | @klass.instance_methods | @klass.private_instance_methods - def constants() = [] - def types() = [self] - def nillable?() = (@klass == NilClass) - def nonnillable() = self - def rbs_methods - name = Types.class_name_of(@klass) - return {} unless name && Types.rbs_builder - - type_name = RBS::TypeName(name).absolute! - Types.rbs_builder.build_instance(type_name).methods rescue {} - end - def inspect - if params.empty? - inspect_without_params - else - params_string = "[#{params.map { "#{_1}: #{_2.inspect}" }.join(', ')}]" - "#{inspect_without_params}#{params_string}" - end - end - def inspect_without_params - if klass == NilClass - 'nil' - elsif klass == TrueClass - 'true' - elsif klass == FalseClass - 'false' - else - klass.singleton_class? ? klass.superclass.to_s : klass.to_s - end - end - end - - NIL = InstanceType.new NilClass - OBJECT = InstanceType.new Object - TRUE = InstanceType.new TrueClass - FALSE = InstanceType.new FalseClass - SYMBOL = InstanceType.new Symbol - STRING = InstanceType.new String - INTEGER = InstanceType.new Integer - RANGE = InstanceType.new Range - REGEXP = InstanceType.new Regexp - FLOAT = InstanceType.new Float - RATIONAL = InstanceType.new Rational - COMPLEX = InstanceType.new Complex - ARRAY = InstanceType.new Array - HASH = InstanceType.new Hash - CLASS = InstanceType.new Class - MODULE = InstanceType.new Module - PROC = InstanceType.new Proc - - class UnionType - attr_reader :types - - def initialize(*types) - @types = [] - singletons = [] - instances = {} - collect = -> type do - case type - in UnionType - type.types.each(&collect) - in InstanceType - params = (instances[type.klass] ||= {}) - type.params.each do |k, v| - (params[k] ||= []) << v - end - in SingletonType - singletons << type - end - end - types.each(&collect) - @types = singletons.uniq + instances.map do |klass, params| - InstanceType.new(klass, params.transform_values { |v| UnionType[*v] }) - end - end - - def transform(&block) - UnionType[*types.map(&block)] - end - - def nillable? - types.any?(&:nillable?) - end - - def nonnillable - UnionType[*types.reject { _1.is_a?(InstanceType) && _1.klass == NilClass }] - end - - def self.[](*types) - type = new(*types) - if type.types.empty? - OBJECT - elsif type.types.size == 1 - type.types.first - else - type - end - end - - def methods() = @types.flat_map(&:methods).uniq - def all_methods() = @types.flat_map(&:all_methods).uniq - def constants() = @types.flat_map(&:constants).uniq - def inspect() = @types.map(&:inspect).join(' | ') - end - - BOOLEAN = UnionType[TRUE, FALSE] - - def self.array_of(*types) - type = types.size >= 2 ? UnionType[*types] : types.first || OBJECT - InstanceType.new Array, Elem: type - end - - def self.from_rbs_type(return_type, self_type, extra_vars = {}) - case return_type - when RBS::Types::Bases::Self - self_type - when RBS::Types::Bases::Bottom, RBS::Types::Bases::Nil - NIL - when RBS::Types::Bases::Any, RBS::Types::Bases::Void - OBJECT - when RBS::Types::Bases::Class - self_type.transform do |type| - case type - in SingletonType - InstanceType.new(self_type.module_or_class.is_a?(Class) ? Class : Module) - in InstanceType - SingletonType.new type.klass - end - end - UnionType[*types] - when RBS::Types::Bases::Bool - BOOLEAN - when RBS::Types::Bases::Instance - self_type.transform do |type| - if type.is_a?(SingletonType) && type.module_or_class.is_a?(Class) - InstanceType.new type.module_or_class - else - OBJECT - end - end - when RBS::Types::Union - UnionType[*return_type.types.map { from_rbs_type _1, self_type, extra_vars }] - when RBS::Types::Proc - PROC - when RBS::Types::Tuple - elem = UnionType[*return_type.types.map { from_rbs_type _1, self_type, extra_vars }] - InstanceType.new Array, Elem: elem - when RBS::Types::Record - InstanceType.new Hash, K: SYMBOL, V: OBJECT - when RBS::Types::Literal - InstanceType.new return_type.literal.class - when RBS::Types::Variable - if extra_vars.key? return_type.name - extra_vars[return_type.name] - elsif self_type.is_a? InstanceType - self_type.params[return_type.name] || OBJECT - elsif self_type.is_a? UnionType - types = self_type.types.filter_map do |t| - t.params[return_type.name] if t.is_a? InstanceType - end - UnionType[*types] - else - OBJECT - end - when RBS::Types::Optional - UnionType[from_rbs_type(return_type.type, self_type, extra_vars), NIL] - when RBS::Types::Alias - case return_type.name.name - when :int - INTEGER - when :boolish - BOOLEAN - when :string - STRING - else - # TODO: ??? - OBJECT - end - when RBS::Types::Interface - # unimplemented - OBJECT - when RBS::Types::ClassInstance - klass = return_type.name.to_namespace.path.reduce(Object) { _1.const_get _2 } - if return_type.args - args = return_type.args.map { from_rbs_type _1, self_type, extra_vars } - names = rbs_builder.build_singleton(return_type.name).type_params - params = names.map.with_index { [_1, args[_2] || OBJECT] }.to_h - end - InstanceType.new klass, params || {} - end - end - - def self.method_return_bottom?(method) - method.type.return_type.is_a? RBS::Types::Bases::Bottom - end - - def self.match_free_variables(vars, types, values) - accumulator = {} - types.zip values do |t, v| - _match_free_variable(vars, t, v, accumulator) if v - end - accumulator.transform_values { UnionType[*_1] } - end - - def self._match_free_variable(vars, rbs_type, value, accumulator) - case [rbs_type, value] - in [RBS::Types::Variable,] - (accumulator[rbs_type.name] ||= []) << value if vars.include? rbs_type.name - in [RBS::Types::ClassInstance, InstanceType] - names = rbs_builder.build_singleton(rbs_type.name).type_params - names.zip(rbs_type.args).each do |name, arg| - v = value.params[name] - _match_free_variable vars, arg, v, accumulator if v - end - in [RBS::Types::Tuple, InstanceType] if value.klass == Array - v = value.params[:Elem] - rbs_type.types.each do |t| - _match_free_variable vars, t, v, accumulator - end - in [RBS::Types::Record, InstanceType] if value.klass == Hash - # TODO - in [RBS::Types::Interface,] - definition = rbs_builder.build_interface rbs_type.name - convert = {} - definition.type_params.zip(rbs_type.args).each do |from, arg| - convert[from] = arg.name if arg.is_a? RBS::Types::Variable - end - return if convert.empty? - ac = {} - definition.methods.each do |method_name, method| - return_type = method_return_type value, method_name - method.defs.each do |method_def| - interface_return_type = method_def.type.type.return_type - _match_free_variable convert, interface_return_type, return_type, ac - end - end - convert.each do |from, to| - values = ac[from] - (accumulator[to] ||= []).concat values if values - end - else - end - end - end - end -end diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index 5804607d1..f4a19ee3c 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -666,14 +666,9 @@ def test_build_completor original_completor = IRB.conf[:COMPLETOR] IRB.conf[:COMPLETOR] = :regexp assert_equal 'IRB::RegexpCompletor', @context.send(:build_completor).class.name - IRB.conf[:COMPLETOR] = :type - if RUBY_VERSION >= '3.0.0' && RUBY_ENGINE != 'truffleruby' - assert_equal 'IRB::TypeCompletion::Completor', @context.send(:build_completor).class.name - else - assert_equal 'IRB::RegexpCompletor', @context.send(:build_completor).class.name - end IRB.conf[:COMPLETOR] = :unknown assert_equal 'IRB::RegexpCompletor', @context.send(:build_completor).class.name + # :type is tested in test_type_completor.rb ensure $VERBOSE = verbose IRB.conf[:COMPLETOR] = original_completor diff --git a/test/irb/test_type_completor.rb b/test/irb/test_type_completor.rb new file mode 100644 index 000000000..cf4fc12c9 --- /dev/null +++ b/test/irb/test_type_completor.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +# Run test only when Ruby >= 3.0 and repl_type_completor is available +return unless RUBY_VERSION >= '3.0.0' +return if RUBY_ENGINE == 'truffleruby' # needs endless method definition +begin + require 'repl_type_completor' +rescue LoadError + return +end + +require 'irb/completion' +require 'tempfile' +require_relative './helper' + +module TestIRB + class TypeCompletorTest < TestCase + DummyContext = Struct.new(:irb_path) + + def setup + ReplTypeCompletor.load_rbs unless ReplTypeCompletor.rbs_loaded? + context = DummyContext.new('(irb)') + @completor = IRB::TypeCompletor.new(context) + end + + def empty_binding + binding + end + + def assert_completion(preposing, target, binding: empty_binding, include: nil, exclude: nil) + raise ArgumentError if include.nil? && exclude.nil? + candidates = @completor.completion_candidates(preposing, target, '', bind: binding) + assert ([*include] - candidates).empty?, "Expected #{candidates} to include #{include}" if include + assert (candidates & [*exclude]).empty?, "Expected #{candidates} not to include #{exclude}" if exclude + end + + def assert_doc_namespace(preposing, target, namespace, binding: empty_binding) + @completor.completion_candidates(preposing, target, '', bind: binding) + assert_equal namespace, @completor.doc_namespace(preposing, target, '', bind: binding) + end + + def test_type_completion + bind = eval('num = 1; binding') + assert_completion('num.times.map(&:', 'ab', binding: bind, include: 'abs') + assert_doc_namespace('num.chr.', 'upcase', 'String#upcase', binding: bind) + end + + def test_inspect + assert_match(/\AReplTypeCompletor.*\z/, @completor.inspect) + end + + def test_empty_completion + candidates = @completor.completion_candidates('(', ')', '', bind: binding) + assert_equal [], candidates + assert_doc_namespace('(', ')', nil) + end + end + + class TypeCompletorIntegrationTest < IntegrationTestCase + def test_type_completor + write_rc <<~RUBY + IRB.conf[:COMPLETOR] = :type + RUBY + + write_ruby <<~'RUBY' + binding.irb + RUBY + + output = run_ruby_file do + type "irb_info" + type "sleep 0.01 until ReplTypeCompletor.rbs_loaded?" + type "completor = IRB.CurrentContext.io.instance_variable_get(:@completor);" + type "n = 10" + type "puts completor.completion_candidates 'a = n.abs;', 'a.b', '', bind: binding" + type "puts completor.doc_namespace 'a = n.chr;', 'a.encoding', '', bind: binding" + type "exit!" + end + assert_match(/Completion: Autocomplete, ReplTypeCompletor/, output) + assert_match(/a\.bit_length/, output) + assert_match(/String#encoding/, output) + end + end +end diff --git a/test/irb/type_completion/test_scope.rb b/test/irb/type_completion/test_scope.rb deleted file mode 100644 index d7f9540b0..000000000 --- a/test/irb/type_completion/test_scope.rb +++ /dev/null @@ -1,112 +0,0 @@ -# frozen_string_literal: true - -return unless RUBY_VERSION >= '3.0.0' -return if RUBY_ENGINE == 'truffleruby' # needs endless method definition - -require 'irb/type_completion/scope' -require_relative '../helper' - -module TestIRB - class TypeCompletionScopeTest < TestCase - A, B, C, D, E, F, G, H, I, J, K = ('A'..'K').map do |name| - klass = Class.new - klass.define_singleton_method(:inspect) { name } - IRB::TypeCompletion::Types::InstanceType.new(klass) - end - - def assert_type(expected_types, type) - assert_equal [*expected_types].map(&:klass).to_set, type.types.map(&:klass).to_set - end - - def table(*local_variable_names) - local_variable_names.to_h { [_1, IRB::TypeCompletion::Types::NIL] } - end - - def base_scope - IRB::TypeCompletion::RootScope.new(binding, Object.new, []) - end - - def test_lvar - scope = IRB::TypeCompletion::Scope.new base_scope, table('a') - scope['a'] = A - assert_equal A, scope['a'] - end - - def test_conditional - scope = IRB::TypeCompletion::Scope.new base_scope, table('a') - scope.conditional do |sub_scope| - sub_scope['a'] = A - end - assert_type [A, IRB::TypeCompletion::Types::NIL], scope['a'] - end - - def test_branch - scope = IRB::TypeCompletion::Scope.new base_scope, table('a', 'b', 'c', 'd') - scope['c'] = A - scope['d'] = B - scope.run_branches( - -> { _1['a'] = _1['c'] = _1['d'] = C }, - -> { _1['a'] = _1['b'] = _1['d'] = D }, - -> { _1['a'] = _1['b'] = _1['d'] = E }, - -> { _1['a'] = _1['b'] = _1['c'] = F; _1.terminate } - ) - assert_type [C, D, E], scope['a'] - assert_type [IRB::TypeCompletion::Types::NIL, D, E], scope['b'] - assert_type [A, C], scope['c'] - assert_type [C, D, E], scope['d'] - end - - def test_scope_local_variables - scope1 = IRB::TypeCompletion::Scope.new base_scope, table('a', 'b') - scope2 = IRB::TypeCompletion::Scope.new scope1, table('b', 'c'), trace_lvar: false - scope3 = IRB::TypeCompletion::Scope.new scope2, table('c', 'd') - scope4 = IRB::TypeCompletion::Scope.new scope2, table('d', 'e') - assert_empty base_scope.local_variables - assert_equal %w[a b], scope1.local_variables.sort - assert_equal %w[b c], scope2.local_variables.sort - assert_equal %w[b c d], scope3.local_variables.sort - assert_equal %w[b c d e], scope4.local_variables.sort - end - - def test_nested_scope - scope = IRB::TypeCompletion::Scope.new base_scope, table('a', 'b', 'c') - scope['a'] = A - scope['b'] = A - scope['c'] = A - sub_scope = IRB::TypeCompletion::Scope.new scope, { 'c' => B } - assert_type A, sub_scope['a'] - - assert_type A, sub_scope['b'] - assert_type B, sub_scope['c'] - sub_scope['a'] = C - sub_scope.conditional { _1['b'] = C } - sub_scope['c'] = C - assert_type C, sub_scope['a'] - assert_type [A, C], sub_scope['b'] - assert_type C, sub_scope['c'] - scope.update sub_scope - assert_type C, scope['a'] - assert_type [A, C], scope['b'] - assert_type A, scope['c'] - end - - def test_break - scope = IRB::TypeCompletion::Scope.new base_scope, table('a') - scope['a'] = A - breakable_scope = IRB::TypeCompletion::Scope.new scope, { IRB::TypeCompletion::Scope::BREAK_RESULT => nil } - breakable_scope.conditional do |sub| - sub['a'] = B - assert_type [B], sub['a'] - sub.terminate_with IRB::TypeCompletion::Scope::BREAK_RESULT, C - sub['a'] = C - assert_type [C], sub['a'] - end - assert_type [A], breakable_scope['a'] - breakable_scope[IRB::TypeCompletion::Scope::BREAK_RESULT] = D - breakable_scope.merge_jumps - assert_type [C, D], breakable_scope[IRB::TypeCompletion::Scope::BREAK_RESULT] - scope.update breakable_scope - assert_type [A, B], scope['a'] - end - end -end diff --git a/test/irb/type_completion/test_type_analyze.rb b/test/irb/type_completion/test_type_analyze.rb deleted file mode 100644 index ac7a65c72..000000000 --- a/test/irb/type_completion/test_type_analyze.rb +++ /dev/null @@ -1,697 +0,0 @@ -# frozen_string_literal: true - -# Run test only when Ruby >= 3.0 and %w[prism rbs] are available -return unless RUBY_VERSION >= '3.0.0' -return if RUBY_ENGINE == 'truffleruby' # needs endless method definition -begin - require 'prism' - require 'rbs' -rescue LoadError - return -end - -require 'irb/version' -require 'irb/completion' -require 'irb/type_completion/completor' -require_relative '../helper' - -module TestIRB - class TypeCompletionAnalyzeTest < TestCase - def setup - IRB::TypeCompletion::Types.load_rbs_builder unless IRB::TypeCompletion::Types.rbs_builder - end - - def empty_binding - binding - end - - def analyze(code, binding: nil) - completor = IRB::TypeCompletion::Completor.new - def completor.handle_error(e) - raise e - end - completor.analyze(code, binding || empty_binding) - end - - def assert_analyze_type(code, type, token = nil, binding: empty_binding) - result_type, result_token = analyze(code, binding: binding) - assert_equal type, result_type - assert_equal token, result_token if token - end - - def assert_call(code, include: nil, exclude: nil, binding: nil) - raise ArgumentError if include.nil? && exclude.nil? - - result = analyze(code.strip, binding: binding) - type = result[1] if result[0] == :call - klasses = type.types.flat_map do - _1.klass.singleton_class? ? [_1.klass.superclass, _1.klass] : _1.klass - end - assert ([*include] - klasses).empty?, "Expected #{klasses} to include #{include}" if include - assert (klasses & [*exclude]).empty?, "Expected #{klasses} not to include #{exclude}" if exclude - end - - def test_lvar_ivar_gvar_cvar - assert_analyze_type('puts(x', :lvar_or_method, 'x') - assert_analyze_type('puts($', :gvar, '$') - assert_analyze_type('puts($x', :gvar, '$x') - assert_analyze_type('puts(@', :ivar, '@') - assert_analyze_type('puts(@x', :ivar, '@x') - assert_analyze_type('puts(@@', :cvar, '@@') - assert_analyze_type('puts(@@x', :cvar, '@@x') - end - - def test_rescue - assert_call '(1 rescue 1.0).', include: [Integer, Float] - assert_call 'a=""; (a=1) rescue (a=1.0); a.', include: [Integer, Float], exclude: String - assert_call 'begin; 1; rescue; 1.0; end.', include: [Integer, Float] - assert_call 'begin; 1; rescue A; 1.0; rescue B; 1i; end.', include: [Integer, Float, Complex] - assert_call 'begin; 1i; rescue; 1.0; else; 1; end.', include: [Integer, Float], exclude: Complex - assert_call 'begin; 1; rescue; 1.0; ensure; 1i; end.', include: [Integer, Float], exclude: Complex - assert_call 'begin; 1i; rescue; 1.0; else; 1; ensure; 1i; end.', include: [Integer, Float], exclude: Complex - assert_call 'a=""; begin; a=1; rescue; a=1.0; end; a.', include: [Integer, Float], exclude: [String] - assert_call 'a=""; begin; a=1; rescue; a=1.0; else; a=1r; end; a.', include: [Float, Rational], exclude: [String, Integer] - assert_call 'a=""; begin; a=1; rescue; a=1.0; else; a=1r; ensure; a = 1i; end; a.', include: Complex, exclude: [Float, Rational, String, Integer] - end - - def test_rescue_assign - assert_equal [:lvar_or_method, 'a'], analyze('begin; rescue => a')[0, 2] - assert_equal [:gvar, '$a'], analyze('begin; rescue => $a')[0, 2] - assert_equal [:ivar, '@a'], analyze('begin; rescue => @a')[0, 2] - assert_equal [:cvar, '@@a'], analyze('begin; rescue => @@a')[0, 2] - assert_equal [:const, 'A'], analyze('begin; rescue => A').values_at(0, 2) - assert_equal [:call, 'b'], analyze('begin; rescue => a.b').values_at(0, 2) - end - - def test_ref - bind = eval <<~RUBY - class (Module.new)::A - @ivar = :a - @@cvar = 'a' - binding - end - RUBY - assert_call('STDIN.', include: STDIN.singleton_class) - assert_call('$stdin.', include: $stdin.singleton_class) - assert_call('@ivar.', include: Symbol, binding: bind) - assert_call('@@cvar.', include: String, binding: bind) - lbind = eval('lvar = 1; binding') - assert_call('lvar.', include: Integer, binding: lbind) - end - - def test_self_ivar_ref - obj = Object.new - obj.instance_variable_set(:@hoge, 1) - assert_call('obj.instance_eval { @hoge.', include: Integer, binding: obj.instance_eval { binding }) - if Class.method_defined? :attached_object - bind = binding - assert_call('obj.instance_eval { @hoge.', include: Integer, binding: bind) - assert_call('@hoge = 1.0; obj.instance_eval { @hoge.', include: Integer, exclude: Float, binding: bind) - assert_call('@hoge = 1.0; obj.instance_eval { @hoge = "" }; @hoge.', include: Float, exclude: [Integer, String], binding: bind) - assert_call('@fuga = 1.0; obj.instance_eval { @fuga.', exclude: Float, binding: bind) - assert_call('@fuga = 1.0; obj.instance_eval { @fuga = "" }; @fuga.', include: Float, exclude: [Integer, String], binding: bind) - end - end - - class CVarModule - @@test_cvar = 1 - end - def test_module_cvar_ref - bind = binding - assert_call('@@foo=1; class A; @@foo.', exclude: Integer, binding: bind) - assert_call('@@foo=1; class A; @@foo=1.0; @@foo.', include: Float, exclude: Integer, binding: bind) - assert_call('@@foo=1; class A; @@foo=1.0; end; @@foo.', include: Integer, exclude: Float, binding: bind) - assert_call('module CVarModule; @@test_cvar.', include: Integer, binding: bind) - assert_call('class Array; @@foo = 1; end; class Array; @@foo.', include: Integer, binding: bind) - assert_call('class Array; class B; @@foo = 1; end; class B; @@foo.', include: Integer, binding: bind) - assert_call('class Array; class B; @@foo = 1; end; @@foo.', exclude: Integer, binding: bind) - end - - def test_lvar_singleton_method - a = 1 - b = +'' - c = Object.new - d = [a, b, c] - binding = Kernel.binding - assert_call('a.', include: Integer, exclude: String, binding: binding) - assert_call('b.', include: b.singleton_class, exclude: [Integer, Object], binding: binding) - assert_call('c.', include: c.singleton_class, exclude: [Integer, String], binding: binding) - assert_call('d.', include: d.class, exclude: [Integer, String, Object], binding: binding) - assert_call('d.sample.', include: [Integer, String, Object], exclude: [b.singleton_class, c.singleton_class], binding: binding) - end - - def test_local_variable_assign - assert_call('(a = 1).', include: Integer) - assert_call('a = 1; a = ""; a.', include: String, exclude: Integer) - assert_call('1 => a; a.', include: Integer) - end - - def test_block_symbol - assert_call('[1].map(&:', include: Integer) - assert_call('1.to_s.tap(&:', include: String) - end - - def test_union_splat - assert_call('a, = [[:a], 1, nil].sample; a.', include: [Symbol, Integer, NilClass], exclude: Object) - assert_call('[[:a], 1, nil].each do _2; _1.', include: [Symbol, Integer, NilClass], exclude: Object) - assert_call('a = [[:a], 1, nil, ("a".."b")].sample; [*a].sample.', include: [Symbol, Integer, NilClass, String], exclude: Object) - end - - def test_range - assert_call('(1..2).first.', include: Integer) - assert_call('("a".."b").first.', include: String) - assert_call('(..1.to_f).first.', include: Float) - assert_call('(1.to_s..).first.', include: String) - assert_call('(1..2.0).first.', include: [Float, Integer]) - end - - def test_conditional_assign - assert_call('a = 1; a = "" if cond; a.', include: [String, Integer], exclude: NilClass) - assert_call('a = 1 if cond; a.', include: [Integer, NilClass]) - assert_call(<<~RUBY, include: [String, Symbol], exclude: [Integer, NilClass]) - a = 1 - cond ? a = '' : a = :a - a. - RUBY - end - - def test_block - assert_call('nil.then{1}.', include: Integer, exclude: NilClass) - assert_call('nil.then(&:to_s).', include: String, exclude: NilClass) - end - - def test_block_break - assert_call('1.tap{}.', include: [Integer], exclude: NilClass) - assert_call('1.tap{break :a}.', include: [Symbol, Integer], exclude: NilClass) - assert_call('1.tap{break :a, :b}[0].', include: Symbol) - assert_call('1.tap{break :a; break "a"}.', include: [Symbol, Integer], exclude: [NilClass, String]) - assert_call('1.tap{break :a if b}.', include: [Symbol, Integer], exclude: NilClass) - assert_call('1.tap{break :a; break "a" if b}.', include: [Symbol, Integer], exclude: [NilClass, String]) - assert_call('1.tap{if cond; break :a; else; break "a"; end}.', include: [Symbol, Integer, String], exclude: NilClass) - end - - def test_instance_eval - assert_call('1.instance_eval{:a.then{self.', include: Integer, exclude: Symbol) - assert_call('1.then{:a.instance_eval{self.', include: Symbol, exclude: Integer) - end - - def test_block_next - assert_call('nil.then{1}.', include: Integer, exclude: [NilClass, Object]) - assert_call('nil.then{next 1}.', include: Integer, exclude: [NilClass, Object]) - assert_call('nil.then{next :a, :b}[0].', include: Symbol) - assert_call('nil.then{next 1; 1.0}.', include: Integer, exclude: [Float, NilClass, Object]) - assert_call('nil.then{next 1; next 1.0}.', include: Integer, exclude: [Float, NilClass, Object]) - assert_call('nil.then{1 if cond}.', include: [Integer, NilClass], exclude: Object) - assert_call('nil.then{if cond; 1; else; 1.0; end}.', include: [Integer, Float], exclude: [NilClass, Object]) - assert_call('nil.then{next 1 if cond; 1.0}.', include: [Integer, Float], exclude: [NilClass, Object]) - assert_call('nil.then{if cond; next 1; else; next 1.0; end; "a"}.', include: [Integer, Float], exclude: [String, NilClass, Object]) - assert_call('nil.then{if cond; next 1; else; next 1.0; end; next "a"}.', include: [Integer, Float], exclude: [String, NilClass, Object]) - end - - def test_vars_with_branch_termination - assert_call('a=1; tap{break; a=//}; a.', include: Integer, exclude: Regexp) - assert_call('a=1; tap{a=1.0; break; a=//}; a.', include: [Integer, Float], exclude: Regexp) - assert_call('a=1; tap{next; a=//}; a.', include: Integer, exclude: Regexp) - assert_call('a=1; tap{a=1.0; next; a=//}; a.', include: [Integer, Float], exclude: Regexp) - assert_call('a=1; while cond; break; a=//; end; a.', include: Integer, exclude: Regexp) - assert_call('a=1; while cond; a=1.0; break; a=//; end; a.', include: [Integer, Float], exclude: Regexp) - assert_call('a=1; ->{ break; a=// }; a.', include: Integer, exclude: Regexp) - assert_call('a=1; ->{ a=1.0; break; a=// }; a.', include: [Integer, Float], exclude: Regexp) - - assert_call('a=1; tap{ break; a=// if cond }; a.', include: Integer, exclude: Regexp) - assert_call('a=1; tap{ next; a=// if cond }; a.', include: Integer, exclude: Regexp) - assert_call('a=1; while cond; break; a=// if cond; end; a.', include: Integer, exclude: Regexp) - assert_call('a=1; ->{ break; a=// if cond }; a.', include: Integer, exclude: Regexp) - - assert_call('a=1; tap{if cond; a=:a; break; a=""; end; a.', include: Integer, exclude: [Symbol, String]) - assert_call('a=1; tap{if cond; a=:a; break; a=""; end; a=//}; a.', include: [Integer, Symbol, Regexp], exclude: String) - assert_call('a=1; tap{if cond; a=:a; break; a=""; else; break; end; a=//}; a.', include: [Integer, Symbol], exclude: [String, Regexp]) - assert_call('a=1; tap{if cond; a=:a; next; a=""; end; a.', include: Integer, exclude: [Symbol, String]) - assert_call('a=1; tap{if cond; a=:a; next; a=""; end; a=//}; a.', include: [Integer, Symbol, Regexp], exclude: String) - assert_call('a=1; tap{if cond; a=:a; next; a=""; else; next; end; a=//}; a.', include: [Integer, Symbol], exclude: [String, Regexp]) - assert_call('def f(a=1); if cond; a=:a; return; a=""; end; a.', include: Integer, exclude: [Symbol, String]) - assert_call('a=1; while cond; if cond; a=:a; break; a=""; end; a.', include: Integer, exclude: [Symbol, String]) - assert_call('a=1; while cond; if cond; a=:a; break; a=""; end; a=//; end; a.', include: [Integer, Symbol, Regexp], exclude: String) - assert_call('a=1; while cond; if cond; a=:a; break; a=""; else; break; end; a=//; end; a.', include: [Integer, Symbol], exclude: [String, Regexp]) - assert_call('a=1; ->{ if cond; a=:a; break; a=""; end; a.', include: Integer, exclude: [Symbol, String]) - assert_call('a=1; ->{ if cond; a=:a; break; a=""; end; a=// }; a.', include: [Integer, Symbol, Regexp], exclude: String) - assert_call('a=1; ->{ if cond; a=:a; break; a=""; else; break; end; a=// }; a.', include: [Integer, Symbol], exclude: [String, Regexp]) - - # continue evaluation on terminated branch - assert_call('a=1; tap{ a=1.0; break; a=// if cond; a.', include: [Regexp, Float], exclude: Integer) - assert_call('a=1; tap{ a=1.0; next; a=// if cond; a.', include: [Regexp, Float], exclude: Integer) - assert_call('a=1; ->{ a=1.0; break; a=// if cond; a.', include: [Regexp, Float], exclude: Integer) - assert_call('a=1; while cond; a=1.0; break; a=// if cond; a.', include: [Regexp, Float], exclude: Integer) - end - - def test_to_str_to_int - sobj = Struct.new(:to_str).new('a') - iobj = Struct.new(:to_int).new(1) - binding = Kernel.binding - assert_equal String, ([] * sobj).class - assert_equal Array, ([] * iobj).class - assert_call('([]*sobj).', include: String, exclude: Array, binding: binding) - assert_call('([]*iobj).', include: Array, exclude: String, binding: binding) - end - - def test_method_select - assert_call('([]*4).', include: Array, exclude: String) - assert_call('([]*"").', include: String, exclude: Array) - assert_call('([]*unknown).', include: [String, Array]) - assert_call('p(1).', include: Integer) - assert_call('p(1, 2).', include: Array, exclude: Integer) - assert_call('2.times.', include: Enumerator, exclude: Integer) - assert_call('2.times{}.', include: Integer, exclude: Enumerator) - end - - def test_interface_match_var - assert_call('([1]+[:a]+["a"]).sample.', include: [Integer, String, Symbol]) - end - - def test_lvar_scope - code = <<~RUBY - tap { a = :never } - a = 1 if x? - tap {|a| a = :never } - tap { a = 'maybe' } - a = {} if x? - a. - RUBY - assert_call(code, include: [Hash, Integer, String], exclude: [Symbol]) - end - - def test_lvar_scope_complex - assert_call('if cond; a = 1; else; tap { a = :a }; end; a.', include: [NilClass, Integer, Symbol], exclude: [Object]) - assert_call('def f; if cond; a = 1; return; end; tap { a = :a }; a.', include: [NilClass, Symbol], exclude: [Integer, Object]) - assert_call('def f; if cond; return; a = 1; end; tap { a = :a }; a.', include: [NilClass, Symbol], exclude: [Integer, Object]) - assert_call('def f; if cond; return; if cond; return; a = 1; end; end; tap { a = :a }; a.', include: [NilClass, Symbol], exclude: [Integer, Object]) - assert_call('def f; if cond; return; if cond; return; a = 1; end; end; tap { a = :a }; a.', include: [NilClass, Symbol], exclude: [Integer, Object]) - end - - def test_gvar_no_scope - code = <<~RUBY - tap { $a = :maybe } - $a = 'maybe' if x? - $a. - RUBY - assert_call(code, include: [Symbol, String]) - end - - def test_ivar_no_scope - code = <<~RUBY - tap { @a = :maybe } - @a = 'maybe' if x? - @a. - RUBY - assert_call(code, include: [Symbol, String]) - end - - def test_massign - assert_call('(a,=1).', include: Integer) - assert_call('(a,=[*1])[0].', include: Integer) - assert_call('(a,=[1,2])[0].', include: Integer) - assert_call('a,=[1,2]; a.', include: Integer, exclude: Array) - assert_call('a,b=[1,2]; a.', include: Integer, exclude: Array) - assert_call('a,b=[1,2]; b.', include: Integer, exclude: Array) - assert_call('a,*,b=[1,2]; a.', include: Integer, exclude: Array) - assert_call('a,*,b=[1,2]; b.', include: Integer, exclude: Array) - assert_call('a,*b=[1,2]; a.', include: Integer, exclude: Array) - assert_call('a,*b=[1,2]; b.', include: Array, exclude: Integer) - assert_call('a,*b=[1,2]; b.sample.', include: Integer) - assert_call('a,*,(*)=[1,2]; a.', include: Integer) - assert_call('*a=[1,2]; a.', include: Array, exclude: Integer) - assert_call('*a=[1,2]; a.sample.', include: Integer) - assert_call('a,*b,c=[1,2,3]; b.', include: Array, exclude: Integer) - assert_call('a,*b,c=[1,2,3]; b.sample.', include: Integer) - assert_call('a,b=(cond)?[1,2]:[:a,:b]; a.', include: [Integer, Symbol]) - assert_call('a,b=(cond)?[1,2]:[:a,:b]; b.', include: [Integer, Symbol]) - assert_call('a,b=(cond)?[1,2]:"s"; a.', include: [Integer, String]) - assert_call('a,b=(cond)?[1,2]:"s"; b.', include: Integer, exclude: String) - assert_call('a,*b=(cond)?[1,2]:"s"; a.', include: [Integer, String]) - assert_call('a,*b=(cond)?[1,2]:"s"; b.', include: Array, exclude: [Integer, String]) - assert_call('a,*b=(cond)?[1,2]:"s"; b.sample.', include: Integer, exclude: String) - assert_call('*a=(cond)?[1,2]:"s"; a.', include: Array, exclude: [Integer, String]) - assert_call('*a=(cond)?[1,2]:"s"; a.sample.', include: [Integer, String]) - assert_call('a,(b,),c=[1,[:a],4]; b.', include: Symbol) - assert_call('a,(b,(c,))=1; a.', include: Integer) - assert_call('a,(b,(*c))=1; c.', include: Array) - assert_call('(a=1).b, c = 1; a.', include: Integer) - assert_call('a, ((b=1).c, d) = 1; b.', include: Integer) - assert_call('a, b[c=1] = 1; c.', include: Integer) - assert_call('a, b[*(c=1)] = 1; c.', include: Integer) - # incomplete massign - assert_analyze_type('a,b', :lvar_or_method, 'b') - assert_call('(a=1).b, a.', include: Integer) - assert_call('a=1; *a.', include: Integer) - end - - def test_field_assign - assert_call('(a.!=1).', exclude: Integer) - assert_call('(a.b=1).', include: Integer, exclude: NilClass) - assert_call('(a&.b=1).', include: Integer) - assert_call('(nil&.b=1).', include: NilClass) - assert_call('(a[]=1).', include: Integer) - assert_call('(a[b]=1).', include: Integer) - assert_call('(a.[]=1).', exclude: Integer) - end - - def test_def - assert_call('def f; end.', include: Symbol) - assert_call('s=""; def s.f; self.', include: String) - assert_call('def (a="").f; end; a.', include: String) - assert_call('def f(a=1); a.', include: Integer) - assert_call('def f(**nil); 1.', include: Integer) - assert_call('def f((*),*); 1.', include: Integer) - assert_call('def f(a,*b); b.', include: Array) - assert_call('def f(a,x:1); x.', include: Integer) - assert_call('def f(a,x:,**); 1.', include: Integer) - assert_call('def f(a,x:,**y); y.', include: Hash) - assert_call('def f((*a)); a.', include: Array) - assert_call('def f(a,b=1,*c,d,x:0,y:,**z,&e); e.arity.', include: Integer) - assert_call('def f(...); 1.', include: Integer) - assert_call('def f(a,...); 1.', include: Integer) - assert_call('def f(...); g(...); 1.', include: Integer) - assert_call('def f(*,**,&); g(*,**,&); 1.', include: Integer) - assert_call('def f(*,**,&); {**}.', include: Hash) - assert_call('def f(*,**,&); [*,**].', include: Array) - assert_call('class Array; def f; self.', include: Array) - end - - def test_defined - assert_call('defined?(a.b+c).', include: [String, NilClass]) - assert_call('defined?(a = 1); tap { a = 1.0 }; a.', include: [Integer, Float, NilClass]) - end - - def test_ternary_operator - assert_call('condition ? 1.chr.', include: [String]) - assert_call('condition ? value : 1.chr.', include: [String]) - assert_call('condition ? cond ? cond ? value : cond ? value : 1.chr.', include: [String]) - end - - def test_block_parameter - assert_call('method { |arg = 1.chr.', include: [String]) - assert_call('method do |arg = 1.chr.', include: [String]) - assert_call('method { |arg1 = 1.|(2|3), arg2 = 1.chr.', include: [String]) - assert_call('method do |arg1 = 1.|(2|3), arg2 = 1.chr.', include: [String]) - end - - def test_self - integer_binding = 1.instance_eval { Kernel.binding } - assert_call('self.', include: [Integer], binding: integer_binding) - string = +'' - string_binding = string.instance_eval { Kernel.binding } - assert_call('self.', include: [string.singleton_class], binding: string_binding) - object = Object.new - object.instance_eval { @int = 1; @string = string } - object_binding = object.instance_eval { Kernel.binding } - assert_call('self.', include: [object.singleton_class], binding: object_binding) - assert_call('@int.', include: [Integer], binding: object_binding) - assert_call('@string.', include: [String], binding: object_binding) - end - - def test_optional_chain - assert_call('[1,nil].sample.', include: [Integer, NilClass]) - assert_call('[1,nil].sample&.', include: [Integer], exclude: [NilClass]) - assert_call('[1,nil].sample.chr.', include: [String], exclude: [NilClass]) - assert_call('[1,nil].sample&.chr.', include: [String, NilClass]) - assert_call('[1,nil].sample.chr&.ord.', include: [Integer], exclude: [NilClass]) - assert_call('a = 1; b.c(a = :a); a.', include: [Symbol], exclude: [Integer]) - assert_call('a = 1; b&.c(a = :a); a.', include: [Integer, Symbol]) - end - - def test_class_module - assert_call('class (1.', include: Integer) - assert_call('class (a=1)::B; end; a.', include: Integer) - assert_call('class Array; 1; end.', include: Integer) - assert_call('class ::Array; 1; end.', include: Integer) - assert_call('class Array::A; 1; end.', include: Integer) - assert_call('class Array; self.new.', include: Array) - assert_call('class ::Array; self.new.', include: Array) - assert_call('class Array::A; self.', include: Class) - assert_call('class (a=1)::A; end; a.', include: Integer) - assert_call('module M; 1; end.', include: Integer) - assert_call('module ::M; 1; end.', include: Integer) - assert_call('module Array::M; 1; end.', include: Integer) - assert_call('module M; self.', include: Module) - assert_call('module Array::M; self.', include: Module) - assert_call('module ::M; self.', include: Module) - assert_call('module (a=1)::M; end; a.', include: Integer) - assert_call('class << Array; 1; end.', include: Integer) - assert_call('class << a; 1; end.', include: Integer) - assert_call('a = ""; class << a; self.superclass.', include: Class) - end - - def test_constant_path - assert_call('class A; X=1; class B; X=""; X.', include: String, exclude: Integer) - assert_call('class A; X=1; class B; X=""; end; X.', include: Integer, exclude: String) - assert_call('class A; class B; X=1; end; end; class A; class B; X.', include: Integer) - assert_call('module IRB; VERSION.', include: String) - assert_call('module IRB; IRB::VERSION.', include: String) - assert_call('module IRB; VERSION=1; VERSION.', include: Integer) - assert_call('module IRB; VERSION=1; IRB::VERSION.', include: Integer) - assert_call('module IRB; module A; VERSION.', include: String) - assert_call('module IRB; module A; VERSION=1; VERSION.', include: Integer) - assert_call('module IRB; module A; VERSION=1; IRB::VERSION.', include: String) - assert_call('module IRB; module A; VERSION=1; end; VERSION.', include: String) - assert_call('module IRB; IRB=1; IRB.', include: Integer) - assert_call('module IRB; IRB=1; ::IRB::VERSION.', include: String) - module_binding = eval 'module ::IRB; binding; end' - assert_call('VERSION.', include: NilClass) - assert_call('VERSION.', include: String, binding: module_binding) - assert_call('IRB::VERSION.', include: String, binding: module_binding) - assert_call('A = 1; module M; A += 0.5; A.', include: Float) - assert_call('::A = 1; module M; A += 0.5; A.', include: Float) - assert_call('::A = 1; module M; A += 0.5; ::A.', include: Integer) - assert_call('IRB::A = 1; IRB::A += 0.5; IRB::A.', include: Float) - end - - def test_literal - assert_call('1.', include: Integer) - assert_call('1.0.', include: Float) - assert_call('1r.', include: Rational) - assert_call('1i.', include: Complex) - assert_call('true.', include: TrueClass) - assert_call('false.', include: FalseClass) - assert_call('nil.', include: NilClass) - assert_call('().', include: NilClass) - assert_call('//.', include: Regexp) - assert_call('/#{a=1}/.', include: Regexp) - assert_call('/#{a=1}/; a.', include: Integer) - assert_call(':a.', include: Symbol) - assert_call(':"#{a=1}".', include: Symbol) - assert_call(':"#{a=1}"; a.', include: Integer) - assert_call('"".', include: String) - assert_call('"#$a".', include: String) - assert_call('("a" "b").', include: String) - assert_call('"#{a=1}".', include: String) - assert_call('"#{a=1}"; a.', include: Integer) - assert_call('``.', include: String) - assert_call('`#{a=1}`.', include: String) - assert_call('`#{a=1}`; a.', include: Integer) - end - - def test_redo_retry_yield_super - assert_call('a=nil; tap do a=1; redo; a=1i; end; a.', include: Integer, exclude: Complex) - assert_call('a=nil; tap do a=1; retry; a=1i; end; a.', include: Integer, exclude: Complex) - assert_call('a = 0; a = yield; a.', include: Object, exclude: Integer) - assert_call('yield 1,(a=1); a.', include: Integer) - assert_call('a = 0; a = super; a.', include: Object, exclude: Integer) - assert_call('a = 0; a = super(1); a.', include: Object, exclude: Integer) - assert_call('super 1,(a=1); a.', include: Integer) - end - - def test_rarely_used_syntax - # FlipFlop - assert_call('if (a=1).even?..(a=1.0).even; a.', include: [Integer, Float]) - # MatchLastLine - assert_call('if /regexp/; 1.', include: Integer) - assert_call('if /reg#{a=1}exp/; a.', include: Integer) - # BlockLocalVariable - assert_call('tap do |i;a| a=1; a.', include: Integer) - # BEGIN{} END{} - assert_call('BEGIN{1.', include: Integer) - assert_call('END{1.', include: Integer) - # MatchWrite - assert_call('a=1; /(?)/=~b; a.', include: [String, NilClass], exclude: Integer) - # OperatorWrite with block `a[&b]+=c` - assert_call('a=[1]; (a[0,&:to_a]+=1.0).', include: Float) - assert_call('a=[1]; (a[0,&b]+=1.0).', include: Float) - end - - def test_hash - assert_call('{}.', include: Hash) - assert_call('{**a}.', include: Hash) - assert_call('{ rand: }.values.sample.', include: Float) - assert_call('rand=""; { rand: }.values.sample.', include: String, exclude: Float) - assert_call('{ 1 => 1.0 }.keys.sample.', include: Integer, exclude: Float) - assert_call('{ 1 => 1.0 }.values.sample.', include: Float, exclude: Integer) - assert_call('a={1=>1.0}; {"a"=>1i,**a}.keys.sample.', include: [Integer, String]) - assert_call('a={1=>1.0}; {"a"=>1i,**a}.values.sample.', include: [Float, Complex]) - end - - def test_array - assert_call('[1,2,3].sample.', include: Integer) - assert_call('a = 1.0; [1,2,a].sample.', include: [Integer, Float]) - assert_call('a = [1.0]; [1,2,*a].sample.', include: [Integer, Float]) - end - - def test_numbered_parameter - assert_call('loop{_1.', include: NilClass) - assert_call('1.tap{_1.', include: Integer) - assert_call('1.tap{_3.', include: NilClass, exclude: Integer) - assert_call('[:a,1].tap{_1.', include: Array, exclude: [Integer, Symbol]) - assert_call('[:a,1].tap{_2.', include: [Symbol, Integer], exclude: Array) - assert_call('[:a,1].tap{_2; _1.', include: [Symbol, Integer], exclude: Array) - assert_call('[:a].each_with_index{_1.', include: Symbol, exclude: [Integer, Array]) - assert_call('[:a].each_with_index{_2; _1.', include: Symbol, exclude: [Integer, Array]) - assert_call('[:a].each_with_index{_2.', include: Integer, exclude: Symbol) - end - - def test_if_unless - assert_call('if cond; 1; end.', include: Integer) - assert_call('unless true; 1; end.', include: Integer) - assert_call('a=1; (a=1.0) if cond; a.', include: [Integer, Float]) - assert_call('a=1; (a=1.0) unless cond; a.', include: [Integer, Float]) - assert_call('a=1; 123 if (a=1.0).foo; a.', include: Float, exclude: Integer) - assert_call('if cond; a=1; end; a.', include: [Integer, NilClass]) - assert_call('a=1; if cond; a=1.0; elsif cond; a=1r; else; a=1i; end; a.', include: [Float, Rational, Complex], exclude: Integer) - assert_call('a=1; if cond; a=1.0; else; a.', include: Integer, exclude: Float) - assert_call('a=1; if (a=1.0).foo; a.', include: Float, exclude: Integer) - assert_call('a=1; if (a=1.0).foo; end; a.', include: Float, exclude: Integer) - assert_call('a=1; if (a=1.0).foo; else; a.', include: Float, exclude: Integer) - assert_call('a=1; if (a=1.0).foo; elsif a.', include: Float, exclude: Integer) - assert_call('a=1; if (a=1.0).foo; elsif (a=1i); else; a.', include: Complex, exclude: [Integer, Float]) - end - - def test_while_until - assert_call('while cond; 123; end.', include: NilClass) - assert_call('until cond; 123; end.', include: NilClass) - assert_call('a=1; a=1.0 while cond; a.', include: [Integer, Float]) - assert_call('a=1; a=1.0 until cond; a.', include: [Integer, Float]) - assert_call('a=1; 1 while (a=1.0).foo; a.', include: Float, exclude: Integer) - assert_call('while cond; break 1; end.', include: Integer) - assert_call('while cond; a=1; end; a.', include: Integer) - assert_call('a=1; while cond; a=1.0; end; a.', include: [Integer, Float]) - assert_call('a=1; while (a=1.0).foo; end; a.', include: Float, exclude: Integer) - end - - def test_for - assert_call('for i in [1,2,3]; i.', include: Integer) - assert_call('for i,j in [1,2,3]; i.', include: Integer) - assert_call('for *,(*) in [1,2,3]; 1.', include: Integer) - assert_call('for *i in [1,2,3]; i.sample.', include: Integer) - assert_call('for (a=1).b in [1,2,3]; a.', include: Integer) - assert_call('for Array::B in [1,2,3]; Array::B.', include: Integer) - assert_call('for A in [1,2,3]; A.', include: Integer) - assert_call('for $a in [1,2,3]; $a.', include: Integer) - assert_call('for @a in [1,2,3]; @a.', include: Integer) - assert_call('for i in [1,2,3]; end.', include: Array) - assert_call('for i in [1,2,3]; break 1.0; end.', include: [Array, Float]) - assert_call('i = 1.0; for i in [1,2,3]; end; i.', include: [Integer, Float]) - assert_call('a = 1.0; for i in [1,2,3]; a = 1i; end; a.', include: [Float, Complex]) - end - - def test_special_var - assert_call('__FILE__.', include: String) - assert_call('__LINE__.', include: Integer) - assert_call('__ENCODING__.', include: Encoding) - assert_call('$1.', include: String) - assert_call('$&.', include: String) - end - - def test_and_or - assert_call('(1&&1.0).', include: Float, exclude: Integer) - assert_call('(nil&&1.0).', include: NilClass) - assert_call('(nil||1).', include: Integer) - assert_call('(1||1.0).', include: Float) - end - - def test_opwrite - assert_call('a=[]; a*=1; a.', include: Array) - assert_call('a=[]; a*=""; a.', include: String) - assert_call('a=[1,false].sample; a||=1.0; a.', include: [Integer, Float]) - assert_call('a=1; a&&=1.0; a.', include: Float, exclude: Integer) - assert_call('(a=1).b*=1; a.', include: Integer) - assert_call('(a=1).b||=1; a.', include: Integer) - assert_call('(a=1).b&&=1; a.', include: Integer) - assert_call('[][a=1]&&=1; a.', include: Integer) - assert_call('[][a=1]||=1; a.', include: Integer) - assert_call('[][a=1]+=1; a.', include: Integer) - assert_call('([1][0]+=1.0).', include: Float) - assert_call('([1.0][0]+=1).', include: Float) - assert_call('A=nil; A||=1; A.', include: Integer) - assert_call('A=1; A&&=1.0; A.', include: Float) - assert_call('A=1; A+=1.0; A.', include: Float) - assert_call('Array::A||=1; Array::A.', include: Integer) - assert_call('Array::A=1; Array::A&&=1.0; Array::A.', include: Float) - end - - def test_case_when - assert_call('case x; when A; 1; when B; 1.0; end.', include: [Integer, Float, NilClass]) - assert_call('case x; when A; 1; when B; 1.0; else; 1r; end.', include: [Integer, Float, Rational], exclude: NilClass) - assert_call('case; when (a=1); a.', include: Integer) - assert_call('case x; when (a=1); a.', include: Integer) - assert_call('a=1; case (a=1.0); when A; a.', include: Float, exclude: Integer) - assert_call('a=1; case (a=1.0); when A; end; a.', include: Float, exclude: Integer) - assert_call('a=1; case x; when A; a=1.0; else; a=1r; end; a.', include: [Float, Rational], exclude: Integer) - assert_call('a=1; case x; when A; a=1.0; when B; a=1r; end; a.', include: [Float, Rational, Integer]) - end - - def test_case_in - assert_call('case x; in A; 1; in B; 1.0; end.', include: [Integer, Float], exclude: NilClass) - assert_call('case x; in A; 1; in B; 1.0; else; 1r; end.', include: [Integer, Float, Rational], exclude: NilClass) - assert_call('a=""; case 1; in A; a=1; in B; a=1.0; end; a.', include: [Integer, Float], exclude: String) - assert_call('a=""; case 1; in A; a=1; in B; a=1.0; else; a=1r; end; a.', include: [Integer, Float, Rational], exclude: String) - assert_call('case 1; in x; x.', include: Integer) - assert_call('case x; in A if (a=1); a.', include: Integer) - assert_call('case x; in ^(a=1); a.', include: Integer) - assert_call('case x; in [1, String => a, 2]; a.', include: String) - assert_call('case x; in [*a, 1]; a.', include: Array) - assert_call('case x; in [1, *a]; a.', include: Array) - assert_call('case x; in [*a, 1, *b]; a.', include: Array) - assert_call('case x; in [*a, 1, *b]; b.', include: Array) - assert_call('case x; in {a: {b: **c}}; c.', include: Hash) - assert_call('case x; in (String | { x: Integer, y: ^$a }) => a; a.', include: [String, Hash]) - end - - def test_pattern_match - assert_call('1 in a; a.', include: Integer) - assert_call('a=1; x in String=>a; a.', include: [Integer, String]) - assert_call('a=1; x=>String=>a; a.', include: String, exclude: Integer) - end - - def test_bottom_type_termination - assert_call('a=1; tap { raise; a=1.0; a.', include: Float) - assert_call('a=1; tap { loop{}; a=1.0; a.', include: Float) - assert_call('a=1; tap { raise; a=1.0 } a.', include: Integer, exclude: Float) - assert_call('a=1; tap { loop{}; a=1.0 } a.', include: Integer, exclude: Float) - end - - def test_call_parameter - assert_call('f((x=1),*b,c:1,**d,&e); x.', include: Integer) - assert_call('f(a,*(x=1),c:1,**d,&e); x.', include: Integer) - assert_call('f(a,*b,(x=1):1,**d,&e); x.', include: Integer) - assert_call('f(a,*b,c:(x=1),**d,&e); x.', include: Integer) - assert_call('f(a,*b,c:1,**(x=1),&e); x.', include: Integer) - assert_call('f(a,*b,c:1,**d,&(x=1)); x.', include: Integer) - assert_call('f((x=1)=>1); x.', include: Integer) - end - - def test_block_args - assert_call('[1,2,3].tap{|a| a.', include: Array) - assert_call('[1,2,3].tap{|a,b| a.', include: Integer) - assert_call('[1,2,3].tap{|(a,b)| a.', include: Integer) - assert_call('[1,2,3].tap{|a,*b| b.', include: Array) - assert_call('[1,2,3].tap{|a=1.0| a.', include: [Array, Float]) - assert_call('[1,2,3].tap{|a,**b| b.', include: Hash) - assert_call('1.tap{|(*),*,**| 1.', include: Integer) - end - - def test_array_aref - assert_call('[1][0..].', include: [Array, NilClass], exclude: Integer) - assert_call('[1][0].', include: Integer, exclude: [Array, NilClass]) - assert_call('[1].[](0).', include: Integer, exclude: [Array, NilClass]) - assert_call('[1].[](0){}.', include: Integer, exclude: [Array, NilClass]) - end - end -end diff --git a/test/irb/type_completion/test_type_completor.rb b/test/irb/type_completion/test_type_completor.rb deleted file mode 100644 index f947cc4e6..000000000 --- a/test/irb/type_completion/test_type_completor.rb +++ /dev/null @@ -1,182 +0,0 @@ -# frozen_string_literal: true - -# Run test only when Ruby >= 3.0 and %w[prism rbs] are available -return unless RUBY_VERSION >= '3.0.0' -return if RUBY_ENGINE == 'truffleruby' # needs endless method definition -begin - require 'prism' - require 'rbs' -rescue LoadError - return -end - -require 'irb/version' -require 'irb/type_completion/completor' -require_relative '../helper' - -module TestIRB - class TypeCompletorTest < TestCase - def setup - IRB::TypeCompletion::Types.load_rbs_builder unless IRB::TypeCompletion::Types.rbs_builder - @completor = IRB::TypeCompletion::Completor.new - end - - def empty_binding - binding - end - - TARGET_REGEXP = /(@@|@|\$)?[a-zA-Z_]*[!?=]?$/ - - def assert_completion(code, binding: empty_binding, include: nil, exclude: nil) - raise ArgumentError if include.nil? && exclude.nil? - target = code[TARGET_REGEXP] - candidates = @completor.completion_candidates(code.delete_suffix(target), target, '', bind: binding) - assert ([*include] - candidates).empty?, "Expected #{candidates} to include #{include}" if include - assert (candidates & [*exclude]).empty?, "Expected #{candidates} not to include #{exclude}" if exclude - end - - def assert_doc_namespace(code, namespace, binding: empty_binding) - target = code[TARGET_REGEXP] - preposing = code.delete_suffix(target) - @completor.completion_candidates(preposing, target, '', bind: binding) - assert_equal namespace, @completor.doc_namespace(preposing, target, '', bind: binding) - end - - def test_require - assert_completion("require '", include: 'set') - assert_completion("require 's", include: 'set') - Dir.chdir(__dir__ + "/../../..") do - assert_completion("require_relative 'l", include: 'lib/irb') - end - # Incomplete double quote string is InterpolatedStringNode - assert_completion('require "', include: 'set') - assert_completion('require "s', include: 'set') - end - - def test_method_block_sym - assert_completion('[1].map(&:', include: 'abs') - assert_completion('[:a].map(&:', exclude: 'abs') - assert_completion('[1].map(&:a', include: 'abs') - assert_doc_namespace('[1].map(&:abs', 'Integer#abs') - end - - def test_symbol - sym = :test_completion_symbol - assert_completion(":test_com", include: sym.to_s) - end - - def test_call - assert_completion('1.', include: 'abs') - assert_completion('1.a', include: 'abs') - assert_completion('ran', include: 'rand') - assert_doc_namespace('1.abs', 'Integer#abs') - assert_doc_namespace('Integer.sqrt', 'Integer.sqrt') - assert_doc_namespace('rand', 'TestIRB::TypeCompletorTest#rand') - assert_doc_namespace('Object::rand', 'Object.rand') - end - - def test_lvar - bind = eval('lvar = 1; binding') - assert_completion('lva', binding: bind, include: 'lvar') - assert_completion('lvar.', binding: bind, include: 'abs') - assert_completion('lvar.a', binding: bind, include: 'abs') - assert_completion('lvar = ""; lvar.', binding: bind, include: 'ascii_only?') - assert_completion('lvar = ""; lvar.', include: 'ascii_only?') - assert_doc_namespace('lvar', 'Integer', binding: bind) - assert_doc_namespace('lvar.abs', 'Integer#abs', binding: bind) - assert_doc_namespace('lvar = ""; lvar.ascii_only?', 'String#ascii_only?', binding: bind) - end - - def test_const - assert_completion('Ar', include: 'Array') - assert_completion('::Ar', include: 'Array') - assert_completion('IRB::V', include: 'VERSION') - assert_completion('FooBar=1; F', include: 'FooBar') - assert_completion('::FooBar=1; ::F', include: 'FooBar') - assert_doc_namespace('Array', 'Array') - assert_doc_namespace('Array = 1; Array', 'Integer') - assert_doc_namespace('Object::Array', 'Array') - assert_completion('::', include: 'Array') - assert_completion('class ::', include: 'Array') - assert_completion('module IRB; class T', include: ['TypeCompletion', 'TracePoint']) - end - - def test_gvar - assert_completion('$', include: '$stdout') - assert_completion('$s', include: '$stdout') - assert_completion('$', exclude: '$foobar') - assert_completion('$foobar=1; $', include: '$foobar') - assert_doc_namespace('$foobar=1; $foobar', 'Integer') - assert_doc_namespace('$stdout', 'IO') - assert_doc_namespace('$stdout=1; $stdout', 'Integer') - end - - def test_ivar - bind = Object.new.instance_eval { @foo = 1; binding } - assert_completion('@', binding: bind, include: '@foo') - assert_completion('@f', binding: bind, include: '@foo') - assert_completion('@bar = 1; @', include: '@bar') - assert_completion('@bar = 1; @b', include: '@bar') - assert_doc_namespace('@bar = 1; @bar', 'Integer') - assert_doc_namespace('@foo', 'Integer', binding: bind) - assert_doc_namespace('@foo = 1.0; @foo', 'Float', binding: bind) - end - - def test_cvar - bind = eval('m=Module.new; module m::M; @@foo = 1; binding; end') - assert_equal(1, bind.eval('@@foo')) - assert_completion('@', binding: bind, include: '@@foo') - assert_completion('@@', binding: bind, include: '@@foo') - assert_completion('@@f', binding: bind, include: '@@foo') - assert_doc_namespace('@@foo', 'Integer', binding: bind) - assert_doc_namespace('@@foo = 1.0; @@foo', 'Float', binding: bind) - assert_completion('@@bar = 1; @', include: '@@bar') - assert_completion('@@bar = 1; @@', include: '@@bar') - assert_completion('@@bar = 1; @@b', include: '@@bar') - assert_doc_namespace('@@bar = 1; @@bar', 'Integer') - end - - def test_basic_object - bo = BasicObject.new - def bo.foo; end - bo.instance_eval { @bar = 1 } - bind = binding - bo_self_bind = bo.instance_eval { Kernel.binding } - assert_completion('bo.', binding: bind, include: 'foo') - assert_completion('def bo.baz; self.', binding: bind, include: 'foo') - assert_completion('[bo].first.', binding: bind, include: 'foo') - assert_doc_namespace('bo', 'BasicObject', binding: bind) - assert_doc_namespace('bo.__id__', 'BasicObject#__id__', binding: bind) - assert_doc_namespace('v = [bo]; v', 'Array', binding: bind) - assert_doc_namespace('v = [bo].first; v', 'BasicObject', binding: bind) - bo_self_bind = bo.instance_eval { Kernel.binding } - assert_completion('self.', binding: bo_self_bind, include: 'foo') - assert_completion('@', binding: bo_self_bind, include: '@bar') - assert_completion('@bar.', binding: bo_self_bind, include: 'abs') - assert_doc_namespace('self.__id__', 'BasicObject#__id__', binding: bo_self_bind) - assert_doc_namespace('@bar', 'Integer', binding: bo_self_bind) - if RUBY_VERSION >= '3.2.0' # Needs Class#attached_object to get instance variables from singleton class - assert_completion('def bo.baz; @bar.', binding: bind, include: 'abs') - assert_completion('def bo.baz; @', binding: bind, include: '@bar') - end - end - - def test_inspect - rbs_builder = IRB::TypeCompletion::Types.rbs_builder - assert_match(/TypeCompletion::Completor\(Prism: \d.+, RBS: \d.+\)/, @completor.inspect) - IRB::TypeCompletion::Types.instance_variable_set(:@rbs_builder, nil) - assert_match(/TypeCompletion::Completor\(Prism: \d.+, RBS: loading\)/, @completor.inspect) - IRB::TypeCompletion::Types.instance_variable_set(:@rbs_load_error, StandardError.new('[err]')) - assert_match(/TypeCompletion::Completor\(Prism: \d.+, RBS: .+\[err\].+\)/, @completor.inspect) - ensure - IRB::TypeCompletion::Types.instance_variable_set(:@rbs_builder, rbs_builder) - IRB::TypeCompletion::Types.instance_variable_set(:@rbs_load_error, nil) - end - - def test_none - candidates = @completor.completion_candidates('(', ')', '', bind: binding) - assert_equal [], candidates - assert_doc_namespace('()', nil) - end - end -end diff --git a/test/irb/type_completion/test_types.rb b/test/irb/type_completion/test_types.rb deleted file mode 100644 index 7698bd2fc..000000000 --- a/test/irb/type_completion/test_types.rb +++ /dev/null @@ -1,89 +0,0 @@ -# frozen_string_literal: true - -return unless RUBY_VERSION >= '3.0.0' -return if RUBY_ENGINE == 'truffleruby' # needs endless method definition - -require 'irb/type_completion/types' -require_relative '../helper' - -module TestIRB - class TypeCompletionTypesTest < TestCase - def test_type_inspect - true_type = IRB::TypeCompletion::Types::TRUE - false_type = IRB::TypeCompletion::Types::FALSE - nil_type = IRB::TypeCompletion::Types::NIL - string_type = IRB::TypeCompletion::Types::STRING - true_or_false = IRB::TypeCompletion::Types::UnionType[true_type, false_type] - array_type = IRB::TypeCompletion::Types::InstanceType.new Array, { Elem: true_or_false } - assert_equal 'nil', nil_type.inspect - assert_equal 'true', true_type.inspect - assert_equal 'false', false_type.inspect - assert_equal 'String', string_type.inspect - assert_equal 'Array', IRB::TypeCompletion::Types::InstanceType.new(Array).inspect - assert_equal 'true | false', true_or_false.inspect - assert_equal 'Array[Elem: true | false]', array_type.inspect - assert_equal 'Array', array_type.inspect_without_params - assert_equal 'Proc', IRB::TypeCompletion::Types::PROC.inspect - assert_equal 'Array.itself', IRB::TypeCompletion::Types::SingletonType.new(Array).inspect - end - - def test_type_from_object - obj = Object.new - bo = BasicObject.new - def bo.hash; 42; end # Needed to use this object as a hash key - arr = [1, 'a'] - hash = { 'key' => :value } - int_type = IRB::TypeCompletion::Types.type_from_object 1 - obj_type = IRB::TypeCompletion::Types.type_from_object obj - arr_type = IRB::TypeCompletion::Types.type_from_object arr - hash_type = IRB::TypeCompletion::Types.type_from_object hash - bo_type = IRB::TypeCompletion::Types.type_from_object bo - bo_arr_type = IRB::TypeCompletion::Types.type_from_object [bo] - bo_key_hash_type = IRB::TypeCompletion::Types.type_from_object({ bo => 1 }) - bo_value_hash_type = IRB::TypeCompletion::Types.type_from_object({ x: bo }) - - assert_equal Integer, int_type.klass - # Use singleton_class to autocomplete singleton methods - assert_equal obj.singleton_class, obj_type.klass - assert_equal Object.instance_method(:singleton_class).bind_call(bo), bo_type.klass - # Array and Hash are special - assert_equal Array, arr_type.klass - assert_equal Array, bo_arr_type.klass - assert_equal Hash, hash_type.klass - assert_equal Hash, bo_key_hash_type.klass - assert_equal Hash, bo_value_hash_type.klass - assert_equal BasicObject, bo_arr_type.params[:Elem].klass - assert_equal BasicObject, bo_key_hash_type.params[:K].klass - assert_equal BasicObject, bo_value_hash_type.params[:V].klass - assert_equal 'Object', obj_type.inspect - assert_equal 'Array[Elem: Integer | String]', arr_type.inspect - assert_equal 'Hash[K: String, V: Symbol]', hash_type.inspect - assert_equal 'Array.itself', IRB::TypeCompletion::Types.type_from_object(Array).inspect - assert_equal 'IRB::TypeCompletion.itself', IRB::TypeCompletion::Types.type_from_object(IRB::TypeCompletion).inspect - end - - def test_type_methods - s = +'' - class << s - def foobar; end - private def foobaz; end - end - String.define_method(:foobarbaz) {} - targets = [:foobar, :foobaz, :foobarbaz] - type = IRB::TypeCompletion::Types.type_from_object s - assert_equal [:foobar, :foobarbaz], targets & type.methods - assert_equal [:foobar, :foobaz, :foobarbaz], targets & type.all_methods - assert_equal [:foobarbaz], targets & IRB::TypeCompletion::Types::STRING.methods - assert_equal [:foobarbaz], targets & IRB::TypeCompletion::Types::STRING.all_methods - ensure - String.remove_method :foobarbaz - end - - def test_basic_object_methods - bo = BasicObject.new - def bo.foobar; end - type = IRB::TypeCompletion::Types.type_from_object bo - assert type.all_methods.include?(:foobar) - end - end -end From 4fedce93d3cd6c3283f938982936d4935d8d64bd Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 30 Nov 2023 15:22:17 +0000 Subject: [PATCH 016/263] Page evaluation result's output (#784) * Page evaluation result's output This will make it easier to work with long output that exceeds the terminal's height. * Use consistent TERM in rendering tests This makes sure we get consistent result on all platforms. --- lib/irb.rb | 8 +++-- lib/irb/pager.rb | 23 ++++++++------- test/irb/helper.rb | 14 +++++++-- test/irb/test_context.rb | 1 + test/irb/test_debug_cmd.rb | 4 --- test/irb/test_irb.rb | 5 ---- test/irb/yamatanooroti/test_rendering.rb | 37 ++++++++++++++++++++++++ 7 files changed, 67 insertions(+), 25 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 66149eb45..1ba335c08 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -20,6 +20,7 @@ require_relative "irb/version" require_relative "irb/easter-egg" require_relative "irb/debug" +require_relative "irb/pager" # IRB stands for "interactive Ruby" and is a tool to interactively execute Ruby # expressions read from the standard input. @@ -859,11 +860,12 @@ def output_value(omit = false) # :nodoc: end end end + if multiline_p && @context.newline_before_multiline_output? - printf @context.return_format, "\n#{str}" - else - printf @context.return_format, str + str = "\n" + str end + + Pager.page_content(format(@context.return_format, str), retain_content: true) end # Outputs the local variables to this current session, including diff --git a/lib/irb/pager.rb b/lib/irb/pager.rb index e38d97e3c..a0db5e93b 100644 --- a/lib/irb/pager.rb +++ b/lib/irb/pager.rb @@ -7,9 +7,9 @@ class Pager PAGE_COMMANDS = [ENV['RI_PAGER'], ENV['PAGER'], 'less', 'more'].compact.uniq class << self - def page_content(content) + def page_content(content, **options) if content_exceeds_screen_height?(content) - page do |io| + page(**options) do |io| io.puts content end else @@ -17,8 +17,8 @@ def page_content(content) end end - def page - if IRB.conf[:USE_PAGER] && STDIN.tty? && pager = setup_pager + def page(retain_content: false) + if IRB.conf[:USE_PAGER] && STDIN.tty? && pager = setup_pager(retain_content: retain_content) begin pid = pager.pid yield pager @@ -55,19 +55,20 @@ def content_exceeds_screen_height?(content) pageable_height * screen_width < Reline::Unicode.calculate_width(content, true) end - def setup_pager + def setup_pager(retain_content:) require 'shellwords' - PAGE_COMMANDS.each do |pager| - pager = Shellwords.split(pager) - next if pager.empty? + PAGE_COMMANDS.each do |pager_cmd| + cmd = Shellwords.split(pager_cmd) + next if cmd.empty? - if pager.first == 'less' - pager << '-R' unless pager.include?('-R') + if cmd.first == 'less' + cmd << '-R' unless cmd.include?('-R') + cmd << '-X' if retain_content && !cmd.include?('-X') end begin - io = IO.popen(pager, 'w') + io = IO.popen(cmd, 'w') rescue next end diff --git a/test/irb/helper.rb b/test/irb/helper.rb index ede48be64..38bdbb4c3 100644 --- a/test/irb/helper.rb +++ b/test/irb/helper.rb @@ -93,6 +93,10 @@ def setup if ruby_core? omit "This test works only under ruby/irb" end + + write_rc <<~RUBY + IRB.conf[:USE_PAGER] = false + RUBY end def teardown @@ -197,8 +201,14 @@ def write_ruby(program) end def write_rc(content) - @irbrc = Tempfile.new('irbrc') - @tmpfiles << @irbrc + # Append irbrc content if a tempfile for it already exists + if @irbrc + @irbrc = File.open(@irbrc, "a") + else + @irbrc = Tempfile.new('irbrc') + @tmpfiles << @irbrc + end + @irbrc.write(content) @irbrc.close @envs['IRBRC'] = @irbrc.path diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index f4a19ee3c..79186f13a 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -11,6 +11,7 @@ def setup IRB.init_config(nil) IRB.conf[:USE_SINGLELINE] = false IRB.conf[:VERBOSE] = false + IRB.conf[:USE_PAGER] = false workspace = IRB::WorkSpace.new(Object.new) @context = IRB::Context.new(nil, workspace, TestInputMethod.new) diff --git a/test/irb/test_debug_cmd.rb b/test/irb/test_debug_cmd.rb index 53d40f729..0fb45af47 100644 --- a/test/irb/test_debug_cmd.rb +++ b/test/irb/test_debug_cmd.rb @@ -346,10 +346,6 @@ def test_help_command_is_delegated_to_the_debugger end def test_show_cmds_display_different_content_when_debugger_is_enabled - write_rc <<~RUBY - IRB.conf[:USE_PAGER] = false - RUBY - write_ruby <<~'ruby' binding.irb ruby diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index e6eb3d5da..fb8b5c2bf 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -6,10 +6,6 @@ module TestIRB class InputTest < IntegrationTestCase def test_symbol_aliases_are_handled_correctly - write_rc <<~RUBY - IRB.conf[:USE_PAGER] = false - RUBY - write_ruby <<~'RUBY' class Foo end @@ -26,7 +22,6 @@ class Foo def test_symbol_aliases_are_handled_correctly_with_singleline_mode write_rc <<~RUBY - IRB.conf[:USE_PAGER] = false IRB.conf[:USE_SINGLELINE] = true RUBY diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index ca8176dfe..b18b95bbb 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -10,6 +10,8 @@ class IRB::RenderingTest < Yamatanooroti::TestCase def setup + @original_term = ENV['TERM'] + ENV['TERM'] = "xterm-256color" @pwd = Dir.pwd suffix = '%010d' % Random.rand(0..65535) @tmpdir = File.join(File.expand_path(Dir.tmpdir), "test_irb_#{$$}_#{suffix}") @@ -27,6 +29,7 @@ def setup def teardown FileUtils.rm_rf(@tmpdir) ENV['IRBRC'] = @irbrc_backup + ENV['TERM'] = @original_term ENV.delete('RELINE_TEST_PROMPT') if ENV['RELINE_TEST_PROMPT'] end @@ -377,6 +380,40 @@ def test_pager_page_content_doesnt_page_output_when_it_fits_in_the_screen assert_match(/foobar/, screen) end + def test_long_evaluation_output_is_paged + write_irbrc <<~'LINES' + puts 'start IRB' + require "irb/pager" + LINES + start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + write("'a' * 80 * 11\n") + write("'foo' + 'bar'\n") # eval something to make sure IRB resumes + close + + screen = result.join("\n").sub(/\n*\z/, "\n") + assert_match(/(a{80}\n){8}/, screen) + # because pager is invoked, foobar will not be evaluated + assert_not_match(/foobar/, screen) + end + + def test_long_evaluation_output_is_preserved_after_paging + write_irbrc <<~'LINES' + puts 'start IRB' + require "irb/pager" + LINES + start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + write("'a' * 80 * 11\n") + write("q") # quit pager + write("'foo' + 'bar'\n") # eval something to make sure IRB resumes + close + + screen = result.join("\n").sub(/\n*\z/, "\n") + # confirm pager has exited + assert_match(/foobar/, screen) + # confirm output is preserved + assert_match(/(a{80}\n){6}/, screen) + end + def test_debug_integration_hints_debugger_commands write_irbrc <<~'LINES' IRB.conf[:USE_COLORIZE] = false From 0f344f66d936c56a73e7ecf9b72b107441252d16 Mon Sep 17 00:00:00 2001 From: hogelog Date: Sat, 2 Dec 2023 03:03:58 +0900 Subject: [PATCH 017/263] Scrub past history input before split (#795) * Scrub past history input before split * Don't rewrite ENV["LANG"] --- lib/irb/history.rb | 2 +- test/irb/test_history.rb | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/irb/history.rb b/lib/irb/history.rb index 84d69e19c..06088adb0 100644 --- a/lib/irb/history.rb +++ b/lib/irb/history.rb @@ -60,7 +60,7 @@ def save_history end File.open(history_file, (append_history ? 'a' : 'w'), 0o600, encoding: IRB.conf[:LC_MESSAGES]&.encoding) do |f| - hist = history.map{ |l| l.split("\n").join("\\\n") } + hist = history.map{ |l| l.scrub.split("\n").join("\\\n") } unless append_history begin hist = hist.last(num) if hist.size > num and num > 0 diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index 868c05369..b211e87be 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -148,6 +148,23 @@ def test_history_concurrent_use_not_present ENV["IRBRC"] = backup_irbrc end + def test_history_different_encodings + backup_default_external = Encoding.default_external + IRB.conf[:SAVE_HISTORY] = 2 + Encoding.default_external = Encoding::US_ASCII + locale = IRB::Locale.new("C") + assert_history(<<~EXPECTED_HISTORY.encode(Encoding::US_ASCII), <<~INITIAL_HISTORY.encode(Encoding::UTF_8), <<~INPUT, locale: locale) + ???? + exit + EXPECTED_HISTORY + 😀 + INITIAL_HISTORY + exit + INPUT + ensure + Encoding.default_external = backup_default_external + end + private def history_concurrent_use_for_input_method(input_method) @@ -179,11 +196,11 @@ def history_concurrent_use_for_input_method(input_method) end end - def assert_history(expected_history, initial_irb_history, input, input_method = TestInputMethodWithRelineHistory) + def assert_history(expected_history, initial_irb_history, input, input_method = TestInputMethodWithRelineHistory, locale: IRB::Locale.new) backup_verbose, $VERBOSE = $VERBOSE, nil backup_home = ENV["HOME"] backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME") - IRB.conf[:LC_MESSAGES] = IRB::Locale.new + IRB.conf[:LC_MESSAGES] = locale actual_history = nil Dir.mktmpdir("test_irb_history_") do |tmpdir| ENV["HOME"] = tmpdir From cda6f5eefcbd8e9d51f582d7578b60f2ef1d5b44 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 1 Dec 2023 19:01:16 +0000 Subject: [PATCH 018/263] Only install debug with CRuby (#796) --- Gemfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 940387ea7..0683d3270 100644 --- a/Gemfile +++ b/Gemfile @@ -16,7 +16,8 @@ gem "reline", github: "ruby/reline" if ENV["WITH_LATEST_RELINE"] == "true" gem "rake" gem "test-unit" gem "test-unit-ruby-core" -gem "debug", github: "ruby/debug" + +gem "debug", github: "ruby/debug", platforms: [:mri, :mswin] if RUBY_VERSION >= "3.0.0" && !is_truffleruby gem "repl_type_completor" From 3f9eacbfa999bd040326c1013fca9f4b1c8c53d8 Mon Sep 17 00:00:00 2001 From: Gary Tou Date: Fri, 1 Dec 2023 20:32:00 -0800 Subject: [PATCH 019/263] Implement `history` command (#761) * Implement `history` command Lists IRB input history with indices. Also aliased as `hist`. * Add tests for `history` command * Address feedback: `puts` with multiple arguments instead of `join`ing * Address feedback: Handle nil from splitting an empty input string * Refactor line truncation * Add `-g` grep option to `history` command * Add `history` command to README * Remove unused `*args` parameter * Allow spaces to be included in grep * Allow `/` to be included in grep regex * Handle `input` being an empty string * Exclude "#{index}: " from matching the grep regex * Add new line after joining --- README.md | 1 + lib/irb/cmd/history.rb | 47 ++++++++++++++++++++++++++++ lib/irb/extend-command.rb | 6 ++++ test/irb/test_cmd.rb | 64 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+) create mode 100644 lib/irb/cmd/history.rb diff --git a/README.md b/README.md index 9425f4341..bc9164c14 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,7 @@ IRB 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. Multi-irb (DEPRECATED) irb Start a child IRB. diff --git a/lib/irb/cmd/history.rb b/lib/irb/cmd/history.rb new file mode 100644 index 000000000..5b712fa44 --- /dev/null +++ b/lib/irb/cmd/history.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "stringio" +require_relative "nop" +require_relative "../pager" + +module IRB + # :stopdoc: + + module ExtendCommand + class History < Nop + 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+(?.+)\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/extend-command.rb b/lib/irb/extend-command.rb index 514293a43..072069d4c 100644 --- a/lib/irb/extend-command.rb +++ b/lib/irb/extend-command.rb @@ -191,6 +191,12 @@ def irb_context [ :irb_show_cmds, :ShowCmds, "cmd/show_cmds", [:show_cmds, NO_OVERRIDE], + ], + + [ + :irb_history, :History, "cmd/history", + [:history, NO_OVERRIDE], + [:hist, NO_OVERRIDE], ] ] diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index 345f04bcf..a0a62e96d 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -888,4 +888,68 @@ def test_edit_with_editor_env_var assert_match("command: ': code2'", out) end end + + class HistoryCmdTest < CommandTestCase + def teardown + TestInputMethod.send(:remove_const, "HISTORY") if defined?(TestInputMethod::HISTORY) + super + end + + def test_history + TestInputMethod.const_set("HISTORY", %w[foo bar baz]) + + out, err = without_rdoc do + execute_lines("history") + end + + assert_include(out, <<~EOF) + 2: baz + 1: bar + 0: foo + EOF + assert_empty err + end + + def test_multiline_history_with_truncation + TestInputMethod.const_set("HISTORY", ["foo", "bar", <<~INPUT]) + [].each do |x| + puts x + end + INPUT + + out, err = without_rdoc do + execute_lines("hist") + end + + assert_include(out, <<~EOF) + 2: [].each do |x| + puts x + ... + 1: bar + 0: foo + EOF + assert_empty err + end + + def test_history_grep + TestInputMethod.const_set("HISTORY", ["foo", "bar", <<~INPUT]) + [].each do |x| + puts x + end + INPUT + + out, err = without_rdoc do + execute_lines("hist -g each\n") + end + + assert_include(out, <<~EOF) + 2: [].each do |x| + puts x + ... + EOF + assert_empty err + end + + end + end From 1939139713644de343a7848378965ec3e962ac95 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 3 Dec 2023 04:13:30 +0000 Subject: [PATCH 020/263] Update entries about history and pager support (#797) --- COMPARED_WITH_PRY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/COMPARED_WITH_PRY.md b/COMPARED_WITH_PRY.md index 0a852ba75..94bf3020f 100644 --- a/COMPARED_WITH_PRY.md +++ b/COMPARED_WITH_PRY.md @@ -16,7 +16,7 @@ Feel free to chip in and update this table - we appreciate your help! | Navigation | - `cd object` to enter `object`
- `cd ..` to leave the current object
- `nesting` to list nesting levels | - `pushws object`
- `popws`
- `workspaces` | We plan to refine IRB's commands in the future | | Runtime invocation | `binding.pry` | `binding.irb` | | | Command system | Yes | No | Planned in [#513](https://github.com/ruby/irb/issues/513) and [#588](https://github.com/ruby/irb/issues/588) | -| Input history | [Comprehensive support](https://github.com/pry/pry/wiki/History) | Retrieves previous input with Arrow-Up, but no associated commands | We plan to improve this in the future | -| Pager support | Command output and return value | Only command output | We plan to support paging return value too in the future | +| Input history | [Comprehensive support](https://github.com/pry/pry/wiki/History) | Supports retrieving previous input and the `history` command | The `history` command doesn't support as many flags yet, but contributions are welcome. | +| Pager support | Command output and return value | Command output and return value | | | Debugger integration | With `byebug` through [pry-byebug](https://github.com/deivid-rodriguez/pry-byebug) gem | Supports [`irb:rdbg`](https://github.com/ruby/irb#debugging-with-irb) sessions | | | Rails console | Through [`pry-rails`](https://github.com/pry/pry-rails) gem | Rails' default | Rails console with IRB doesn't have commands like `show-routes` or `show-models` | From ee85e849356c1f3626b4ce5307c9c86d53bc3aa3 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 3 Dec 2023 23:05:13 +0900 Subject: [PATCH 021/263] Disable pager in eval_history test (#799) --- test/irb/test_eval_history.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/irb/test_eval_history.rb b/test/irb/test_eval_history.rb index e81e65f7f..0f9ec4811 100644 --- a/test/irb/test_eval_history.rb +++ b/test/irb/test_eval_history.rb @@ -18,6 +18,7 @@ def execute_lines(*lines, conf: {}, main: self, irb_path: nil) IRB.init_config(nil) IRB.conf[:VERBOSE] = false IRB.conf[:PROMPT_MODE] = :SIMPLE + IRB.conf[:USE_PAGER] = false IRB.conf.merge!(conf) input = TestInputMethod.new(lines) irb = IRB::Irb.new(IRB::WorkSpace.new(main), input) From 4acc9b8d6cd939ddf3ba82dde8be9e9853aa5ff2 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 3 Dec 2023 17:06:38 +0000 Subject: [PATCH 022/263] Bump version to v1.10.0 (#798) --- lib/irb/version.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irb/version.rb b/lib/irb/version.rb index f1d9a4318..b3d7bebfb 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -5,7 +5,7 @@ # module IRB # :nodoc: - VERSION = "1.9.1" + VERSION = "1.10.0" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2023-11-21" + @LAST_UPDATE_DATE = "2023-12-03" end From 8a3002a39e9702d83b2379351ee57e99cb22eec5 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 5 Dec 2023 16:03:01 +0000 Subject: [PATCH 023/263] Pager should be disabled when TERM=dumb (#800) For apps/libs that test against IRB, it's recommended to set `TERM=dumb` so they get minimum disruption from Reline's interactive-focus features. Therefore, we should follow the convention to disable pager when `TERM=dumb`. --- lib/irb/pager.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/irb/pager.rb b/lib/irb/pager.rb index a0db5e93b..d50348786 100644 --- a/lib/irb/pager.rb +++ b/lib/irb/pager.rb @@ -18,7 +18,7 @@ def page_content(content, **options) end def page(retain_content: false) - if IRB.conf[:USE_PAGER] && STDIN.tty? && pager = setup_pager(retain_content: retain_content) + if should_page? && pager = setup_pager(retain_content: retain_content) begin pid = pager.pid yield pager @@ -40,6 +40,10 @@ def page(retain_content: false) private + def should_page? + IRB.conf[:USE_PAGER] && STDIN.tty? && ENV["TERM"] != "dumb" + end + def content_exceeds_screen_height?(content) screen_height, screen_width = begin Reline.get_screen_size From 173980974ba96b67b9e9baa291b13a0365b9e015 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 5 Dec 2023 16:32:09 +0000 Subject: [PATCH 024/263] Disable pager when TERM is not set too (#802) --- lib/irb/pager.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/irb/pager.rb b/lib/irb/pager.rb index d50348786..3391b32c6 100644 --- a/lib/irb/pager.rb +++ b/lib/irb/pager.rb @@ -41,7 +41,7 @@ def page(retain_content: false) private def should_page? - IRB.conf[:USE_PAGER] && STDIN.tty? && ENV["TERM"] != "dumb" + IRB.conf[:USE_PAGER] && STDIN.tty? && (ENV.key?("TERM") && ENV["TERM"] != "dumb") end def content_exceeds_screen_height?(content) From a1e431bd833e61c5de362a9dde31e8aac6b324c8 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 5 Dec 2023 16:34:15 +0000 Subject: [PATCH 025/263] Bump version to v1.10.1 (#801) --- lib/irb/version.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irb/version.rb b/lib/irb/version.rb index b3d7bebfb..d47903b6c 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -5,7 +5,7 @@ # module IRB # :nodoc: - VERSION = "1.10.0" + VERSION = "1.10.1" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2023-12-03" + @LAST_UPDATE_DATE = "2023-12-05" end From b7b57311cc65cbba46305c6ef2638172c7080d7b Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 7 Dec 2023 16:09:09 +0000 Subject: [PATCH 026/263] Debugging command warning should not be specific to the `debug` command (#806) --- lib/irb/cmd/debug.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/irb/cmd/debug.rb b/lib/irb/cmd/debug.rb index 9eca96421..e236084ca 100644 --- a/lib/irb/cmd/debug.rb +++ b/lib/irb/cmd/debug.rb @@ -31,7 +31,7 @@ def execute(pre_cmds: nil, do_cmds: nil) # 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 "`debug` command is only available when IRB is started with binding.irb" + puts "Debugging commands are only available when IRB is started with binding.irb" return end From 2cccc448de0592ee316a415113da00d49a04f604 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 10 Dec 2023 04:21:41 +0000 Subject: [PATCH 027/263] Simplify show_source's super calculation (#807) --- lib/irb/cmd/show_source.rb | 13 +++++-------- lib/irb/source_finder.rb | 18 ++++++------------ test/irb/cmd/test_show_source.rb | 13 +++++++++++++ 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/lib/irb/cmd/show_source.rb b/lib/irb/cmd/show_source.rb index 9a0364e3e..826cb11ed 100644 --- a/lib/irb/cmd/show_source.rb +++ b/lib/irb/cmd/show_source.rb @@ -27,17 +27,14 @@ def execute(str = nil) puts "Error: Expected a string but got #{str.inspect}" return end - if str.include? " -s" - str, esses = str.split(" -") - s_count = esses.count("^s").zero? ? esses.size : 1 - source = SourceFinder.new(@irb_context).find_source(str, s_count) - else - source = SourceFinder.new(@irb_context).find_source(str) - 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 s_count + elsif super_level > 0 puts "Error: Couldn't locate a super definition for #{str}" else puts "Error: Couldn't locate a definition for #{str}" diff --git a/lib/irb/source_finder.rb b/lib/irb/source_finder.rb index a0aedcee6..659d4200f 100644 --- a/lib/irb/source_finder.rb +++ b/lib/irb/source_finder.rb @@ -16,7 +16,7 @@ def initialize(irb_context) @irb_context = irb_context end - def find_source(signature, s_count = nil) + def find_source(signature, super_level = 0) context_binding = @irb_context.workspace.binding case signature when /\A[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name @@ -27,12 +27,12 @@ def find_source(signature, s_count = nil) owner = eval(Regexp.last_match[:owner], context_binding) method = Regexp.last_match[:method] return unless owner.respond_to?(:instance_method) - file, line = method_target(owner, s_count, method, "owner") + file, line = method_target(owner, super_level, method, "owner") when /\A((?.+)(\.|::))?(?[^ :.]+)\z/ # method, receiver.method, receiver::method receiver = eval(Regexp.last_match[:receiver] || 'self', context_binding) method = Regexp.last_match[:method] return unless receiver.respond_to?(method, true) - file, line = method_target(receiver, s_count, method, "receiver") + file, line = method_target(receiver, super_level, method, "receiver") end if file && line && File.exist?(file) Source.new(file: file, first_line: line, last_line: find_end(file, line)) @@ -60,20 +60,14 @@ def find_end(file, first_line) first_line end - def method_target(owner_receiver, s_count, method, type) + def method_target(owner_receiver, super_level, method, type) case type when "owner" target_method = owner_receiver.instance_method(method) - return target_method.source_location unless s_count when "receiver" - if s_count - target_method = owner_receiver.class.instance_method(method) - else - target_method = method - return owner_receiver.method(method).source_location - end + target_method = owner_receiver.method(method) end - s_count.times do |s| + super_level.times do |s| target_method = target_method.super_method if target_method end target_method.nil? ? nil : target_method.source_location diff --git a/test/irb/cmd/test_show_source.rb b/test/irb/cmd/test_show_source.rb index 89b114239..cedec8aa6 100644 --- a/test/irb/cmd/test_show_source.rb +++ b/test/irb/cmd/test_show_source.rb @@ -39,6 +39,19 @@ def test_show_source_alias assert_match(%r[/irb\/init\.rb], out) end + def test_show_source_with_missing_signature + write_ruby <<~'RUBY' + binding.irb + RUBY + + out = run_ruby_file do + type "show_source foo" + type "exit" + end + + assert_match(%r[Couldn't locate a definition for foo], out) + end + def test_show_source_string write_ruby <<~'RUBY' binding.irb From 3436bdc8ebee7f43de95ccec985690335f4bb253 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Tue, 12 Dec 2023 19:06:12 +0900 Subject: [PATCH 028/263] Prevent a warning: setting Encoding.default_external (#810) * Prevent a warning: setting Encoding.default_external * Save $VERBOSE properly --------- Co-authored-by: Nobuyoshi Nakada --- test/irb/test_history.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index b211e87be..f7ba2b9d3 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -151,6 +151,7 @@ def test_history_concurrent_use_not_present def test_history_different_encodings backup_default_external = Encoding.default_external IRB.conf[:SAVE_HISTORY] = 2 + verbose_bak, $VERBOSE = $VERBOSE, nil Encoding.default_external = Encoding::US_ASCII locale = IRB::Locale.new("C") assert_history(<<~EXPECTED_HISTORY.encode(Encoding::US_ASCII), <<~INITIAL_HISTORY.encode(Encoding::UTF_8), <<~INPUT, locale: locale) @@ -163,6 +164,7 @@ def test_history_different_encodings INPUT ensure Encoding.default_external = backup_default_external + $VERBOSE = verbose_bak end private From f3a0626298c780bfa5c8754382711349754e0e80 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 12 Dec 2023 04:34:34 -0600 Subject: [PATCH 029/263] [DOC] RDoc for module IRB (#738) [DOC] RDoc for module IRB --- .document | 2 +- .gitignore | 1 - doc/irb/indexes.rdoc | 190 ++++++++ lib/irb.rb | 1055 ++++++++++++++++++++++++++++++++++-------- lib/irb/context.rb | 12 +- lib/irb/help.rb | 2 +- lib/irb/xmp.rb | 4 +- 7 files changed, 1069 insertions(+), 197 deletions(-) create mode 100644 doc/irb/indexes.rdoc diff --git a/.document b/.document index d97eb7d73..9102e0963 100644 --- a/.document +++ b/.document @@ -1,4 +1,4 @@ LICENSE.txt README.md -doc +doc/irb/indexes.rdoc lib/**/*.rb diff --git a/.gitignore b/.gitignore index 4ea57987f..ff2a440ae 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ /.yardoc /_yardoc/ /coverage/ -/doc/ /pkg/ /spec/reports/ /tmp/ diff --git a/doc/irb/indexes.rdoc b/doc/irb/indexes.rdoc new file mode 100644 index 000000000..a0a95b4ba --- /dev/null +++ b/doc/irb/indexes.rdoc @@ -0,0 +1,190 @@ +== Indexes + +=== Index of Command-Line Options + +These are the \IRB command-line options, with links to explanatory text: + +- -d: Set $DEBUG and {$VERBOSE}[rdoc-ref:IRB@Verbosity] + to +true+. +- -E _ex_[:_in_]: Set initial external (ex) and internal (in) + {encodings}[rdoc-ref:IRB@Encodings] (same as ruby -E>). +- -f: Don't initialize from {configuration file}[rdoc-ref:IRB@Configuration+File]. +- -I _dirpath_: Specify {$LOAD_PATH directory}[rdoc-ref:IRB@Load+Modules] + (same as ruby -I). +- -r _load-module_: Require {load-module}[rdoc-ref:IRB@Load+Modules] + (same as ruby -r). +- -U: Set external and internal {encodings}[rdoc-ref:IRB@Encodings] to UTF-8. +- -w: Suppress {warnings}[rdoc-ref:IRB@Warnings] (same as ruby -w). +- -W[_level_]: Set {warning level}[rdoc-ref:IRB@Warnings]; + 0=silence, 1=medium, 2=verbose (same as ruby -W). +- --autocomplete: Use {auto-completion}[rdoc-ref:IRB@Automatic+Completion]. +- --back-trace-limit _n_: Set a {backtrace limit}[rdoc-ref:IRB@Tracer]; + display at most the top +n+ and bottom +n+ entries. +- --colorize: Use {color-highlighting}[rdoc-ref:IRB@Color+Highlighting] + for input and output. +- --context-mode _n_: Select method to create Binding object + for new {workspace}[rdoc-ref:IRB@Commands]; +n+ in range 0..4. +- --echo: Print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + return values. +- --extra-doc-dir _dirpath_: + Add a {documentation directory}[rdoc-ref:IRB@RI+Documentation+Directories] + for the documentation dialog. +- --inf-ruby-mode: Set prompt mode to {:INF_RUBY}[rdoc-ref:IRB@Pre-Defined+Prompts] + (appropriate for +inf-ruby-mode+ on Emacs); + suppresses --multiline and --singleline. +- --inspect: Use method +inspect+ for printing ({echoing}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + return values. +- --multiline: Use the multiline editor as the {input method}[rdoc-ref:IRB@Input+Method]. +- --noautocomplete: Don't use {auto-completion}[rdoc-ref:IRB@Automatic+Completion]. +- --nocolorize: Don't use {color-highlighting}[rdoc-ref:IRB@Color+Highlighting] + for input and output. +- --noecho: Don't print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + return values. +- --noecho-on-assignment: Don't print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + result on assignment. +- --noinspect: Don't se method +inspect+ for printing ({echoing}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + return values. +- --nomultiline: Don't use the multiline editor as the {input method}[rdoc-ref:IRB@Input+Method]. +- --noprompt: Don't print {prompts}[rdoc-ref:IRB@Prompt+and+Return+Formats]. +- --noscript: Treat the first command-line argument as a normal + {command-line argument}[rdoc-ref:IRB@Initialization+Script], + and include it in +ARGV+. +- --nosingleline: Don't use the singleline editor as the {input method}[rdoc-ref:IRB@Input+Method]. +- --noverboseDon't print {verbose}[rdoc-ref:IRB@Verbosity] details. +- --prompt _mode_, --prompt-mode _mode_: + Set {prompt and return formats}[rdoc-ref:IRB@Prompt+and+Return+Formats]; + +mode+ may be a {pre-defined prompt}[rdoc-ref:IRB@Pre-Defined+Prompts] + or the name of a {custom prompt}[rdoc-ref:IRB@Custom+Prompts]. +- --script: Treat the first command-line argument as the path to an + {initialization script}[rdoc-ref:IRB@Initialization+Script], + and omit it from +ARGV+. +- --simple-prompt, --sample-book-mode: + Set prompt mode to {:SIMPLE}[rdoc-ref:IRB@Pre-Defined+Prompts]. +- --singleline: Use the singleline editor as the {input method}[rdoc-ref:IRB@Input+Method]. +- --tracer: Use {Tracer}[rdoc-ref:IRB@Tracer] to print a stack trace for each input command. +- --truncate-echo-on-assignment: Print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + truncated result on assignment. +- --verbosePrint {verbose}[rdoc-ref:IRB@Verbosity] details. +- -v, --version: Print the {IRB version}[rdoc-ref:IRB@Version]. +- -h, --help: Print the {IRB help text}[rdoc-ref:IRB@Help]. +- --: Separate options from {arguments}[rdoc-ref:IRB@Command-Line+Arguments] + on the command-line. + +=== Index of \IRB.conf Entries + +These are the keys for hash \IRB.conf entries, with links to explanatory text; +for each entry that is pre-defined, the initial value is given: + +- :AP_NAME: \IRB {application name}[rdoc-ref:IRB@Application+Name]; + initial value: 'irb'. +- :AT_EXIT: Array of hooks to call + {at exit}[rdoc-ref:IRB@IRB]; + initial value: []. +- :AUTO_INDENT: Whether {automatic indentation}[rdoc-ref:IRB@Automatic+Indentation] + is enabled; initial value: +true+. +- :BACK_TRACE_LIMIT: Sets the {back trace limit}[rdoc-ref:IRB@Tracer]; + initial value: +16+. +- :COMMAND_ALIASES: Defines input {command aliases}[rdoc-ref:IRB@Command+Aliases]; + initial value: + + { + "$": :show_source, + "@": :whereami, + } + +- :CONTEXT_MODE: Sets the {context mode}[rdoc-ref:IRB@Context+Mode], + the type of binding to be used when evaluating statements; + initial value: +4+. +- :ECHO: Whether to print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + return values; + initial value: +nil+, which would set +conf.echo+ to +true+. +- :ECHO_ON_ASSIGNMENT: Whether to print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + return values on assignment; + initial value: +nil+, which would set +conf.echo_on_assignment+ to +:truncate+. +- :EVAL_HISTORY: How much {evaluation history}[rdoc-ref:IRB@Evaluation+History] + is to be stored; initial value: +nil+. +- :EXTRA_DOC_DIRS: \Array of + {RI documentation directories}[rdoc-ref:IRB@RI+Documentation+Directories] + to be parsed for the documentation dialog; + initial value: []. +- :IGNORE_EOF: Whether to ignore {end-of-file}[rdoc-ref:IRB@End-of-File]; + initial value: +false+. +- :IGNORE_SIGINT: Whether to ignore {SIGINT}[rdoc-ref:IRB@SIGINT]; + initial value: +true+. +- :INSPECT_MODE: Whether to use method +inspect+ for printing + ({echoing}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) return values; + initial value: +true+. +- :IRB_LIB_PATH: The path to the {IRB library directory}[rdoc-ref:IRB@IRB+Library+Directory]; initial value: + "RUBY_DIR/lib/ruby/gems/RUBY_VER_NUM/gems/irb-IRB_VER_NUM/lib/irb", + where: + + - RUBY_DIR is the Ruby installation dirpath. + - RUBY_VER_NUM is the Ruby version number. + - IRB_VER_NUM is the \IRB version number. + +- :IRB_NAME: {IRB name}[rdoc-ref:IRB@IRB+Name]; + initial value: 'irb'. +- :IRB_RC: {Configuration monitor}[rdoc-ref:IRB@Configuration+Monitor]; + initial value: +nil+. +- :LC_MESSAGES: {Locale}[rdoc-ref:IRB@Locale]; + initial value: IRB::Locale object. +- :LOAD_MODULES: deprecated. +- :MAIN_CONTEXT: The {context}[rdoc-ref:IRB@Session+Context] for the main \IRB session; + initial value: IRB::Context object. +- :MEASURE: Whether to + {measure performance}[rdoc-ref:IRB@Performance+Measurement]; + initial value: +false+. +- :MEASURE_CALLBACKS: Callback methods for + {performance measurement}[rdoc-ref:IRB@Performance+Measurement]; + initial value: []. +- :MEASURE_PROC: Procs for + {performance measurement}[rdoc-ref:IRB@Performance+Measurement]; + initial value: + + { + :TIME=>#, + :STACKPROF=># + } + +- :PROMPT: \Hash of {defined prompts}[rdoc-ref:IRB@Prompt+and+Return+Formats]; + initial value: + + { + :NULL=>{:PROMPT_I=>nil, :PROMPT_S=>nil, :PROMPT_C=>nil, :RETURN=>"%s\n"}, + :DEFAULT=>{:PROMPT_I=>"%N(%m):%03n> ", :PROMPT_S=>"%N(%m):%03n%l ", :PROMPT_C=>"%N(%m):%03n* ", :RETURN=>"=> %s\n"}, + :CLASSIC=>{:PROMPT_I=>"%N(%m):%03n:%i> ", :PROMPT_S=>"%N(%m):%03n:%i%l ", :PROMPT_C=>"%N(%m):%03n:%i* ", :RETURN=>"%s\n"}, + :SIMPLE=>{:PROMPT_I=>">> ", :PROMPT_S=>"%l> ", :PROMPT_C=>"?> ", :RETURN=>"=> %s\n"}, + :INF_RUBY=>{:PROMPT_I=>"%N(%m):%03n> ", :PROMPT_S=>nil, :PROMPT_C=>nil, :RETURN=>"%s\n", :AUTO_INDENT=>true}, + :XMP=>{:PROMPT_I=>nil, :PROMPT_S=>nil, :PROMPT_C=>nil, :RETURN=>" ==>%s\n"} + } + +- :PROMPT_MODE: Name of {current prompt}[rdoc-ref:IRB@Prompt+and+Return+Formats]; + initial value: +:DEFAULT+. +- :RC: Whether a {configuration file}[rdoc-ref:IRB@Configuration+File] + was found and interpreted; + initial value: +true+ if a configuration file was found, +false+ otherwise. +- :RC_NAME_GENERATOR: \Proc to generate paths of potential + {configuration files}[rdoc-ref:IRB@Configuration+File]; + initial value: => #. +- :SAVE_HISTORY: Number of commands to save in + {input command history}[rdoc-ref:IRB@Input+Command+History]; + initial value: +1000+. +- :SINGLE_IRB: Whether command-line option --single-irb was given; + initial value: +true+ if the option was given, +false+ otherwise. + See {Single-IRB Mode}[rdoc-ref:IRB@Single-IRB+Mode]. +- :USE_AUTOCOMPLETE: Whether to use + {automatic completion}[rdoc-ref:IRB@Automatic+Completion]; + initial value: +true+. +- :USE_COLORIZE: Whether to use + {color highlighting}[rdoc-ref:IRB@Color+Highlighting]; + initial value: +true+. +- :USE_LOADER: Whether to use the + {IRB loader}[rdoc-ref:IRB@IRB+Loader] for +require+ and +load+; + initial value: +false+. +- :USE_TRACER: Whether to use the + {IRB tracer}[rdoc-ref:IRB@Tracer]; + initial value: +false+. +- :VERBOSE: Whether to print {verbose output}[rdoc-ref:IRB@Verbosity]; + initial value: +nil+. +- :__MAIN__: The main \IRB object; + initial value: +main+. diff --git a/lib/irb.rb b/lib/irb.rb index 1ba335c08..aef1e69c6 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -22,36 +22,870 @@ require_relative "irb/debug" require_relative "irb/pager" -# IRB stands for "interactive Ruby" and is a tool to interactively execute Ruby -# expressions read from the standard input. +# == \IRB +# +# \Module \IRB ("Interactive Ruby") provides a shell-like interface +# that supports user interaction with the Ruby interpreter. # -# The +irb+ command from your shell will start the interpreter. +# It operates as a read-eval-print loop +# ({REPL}[https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop]) +# that: # -# == Usage +# - _Reads_ each character as you type. +# You can modify the \IRB context to change the way input works. +# See {Input}[rdoc-ref:IRB@Input]. +# - _Evaluates_ the code each time it has read a syntactically complete passage. +# - _Prints_ after evaluating. +# You can modify the \IRB context to change the way output works. +# See {Output}[rdoc-ref:IRB@Output]. # -# Use of irb is easy if you know Ruby. +# Example: # -# When executing irb, prompts are displayed as follows. Then, enter the Ruby -# expression. An input is executed when it is syntactically complete. +# $ irb +# irb(main):001> File.basename(Dir.pwd) +# => "irb" +# irb(main):002> Dir.entries('.').size +# => 25 +# irb(main):003* Dir.entries('.').select do |entry| +# irb(main):004* entry.start_with?('R') +# irb(main):005> end +# => ["README.md", "Rakefile"] # -# $ irb -# irb(main):001:0> 1+2 -# #=> 3 -# irb(main):002:0> class Foo -# irb(main):003:1> def foo -# irb(main):004:2> print 1 -# irb(main):005:2> end -# irb(main):006:1> end -# #=> nil +# The typed input may also include +# {\IRB-specific commands}[rdoc-ref:IRB@IRB-Specific+Commands]. # -# The singleline editor module or multiline editor module can be used with irb. -# Use of multiline editor is default if it's installed. +# As seen above, you can start \IRB by using the shell command +irb+. # -# == Command line options +# You can stop an \IRB session by typing command +exit+: # -# :include: ./irb/lc/help-message +# irb(main):006> exit +# $ # -# == Commands +# At that point, \IRB calls any hooks found in array IRB.conf[:AT_EXIT], +# then exits. +# +# == Startup +# +# At startup, \IRB: +# +# 1. Interprets (as Ruby code) the content of the +# {configuration file}[rdoc-ref:IRB@Configuration+File] (if given). +# 1. Constructs the initial session context +# from {hash IRB.conf}[rdoc-ref:IRB@Hash+IRB.conf] and from default values; +# the hash content may have been affected +# by {command-line options}[rdoc-ref:IB@Command-Line+Options], +# and by direct assignments in the configuration file. +# 1. Assigns the context to variable +conf+. +# 1. Assigns command-line arguments to variable ARGV. +# 1. Prints the {prompt}[rdoc-ref:IRB@Prompt+and+Return+Formats]. +# 1. Puts the content of the +# {initialization script}[rdoc-ref:IRB@Initialization+Script] +# onto the \IRB shell, just as if it were user-typed commands. +# +# === The Command Line +# +# On the command line, all options precede all arguments; +# the first item that is not recognized as an option is treated as an argument, +# as are all items that follow. +# +# ==== Command-Line Options +# +# Many command-line options affect entries in hash IRB.conf, +# which in turn affect the initial configuration of the \IRB session. +# +# Details of the options are described in the relevant subsections below. +# +# A cursory list of the \IRB command-line options +# may be seen in the {help message}[https://raw.githubusercontent.com/ruby/irb/master/lib/irb/lc/help-message], +# which is also displayed if you use command-line option --help. +# +# If you are interested in a specific option, consult the +# {index}[rdoc-ref:doc/irb/indexes.rdoc@Index+of+Command-Line+Options]. +# +# ==== Command-Line Arguments +# +# Command-line arguments are passed to \IRB in array +ARGV+: +# +# $ irb --noscript Foo Bar Baz +# irb(main):001> ARGV +# => ["Foo", "Bar", "Baz"] +# irb(main):002> exit +# $ +# +# Command-line option -- causes everything that follows +# to be treated as arguments, even those that look like options: +# +# $ irb --noscript -- --noscript -- Foo Bar Baz +# irb(main):001> ARGV +# => ["--noscript", "--", "Foo", "Bar", "Baz"] +# irb(main):002> exit +# $ +# +# === Configuration File +# +# You can initialize \IRB via a configuration file. +# +# If command-line option -f is given, +# no configuration file is looked for. +# +# Otherwise, \IRB reads and interprets a configuration file +# if one is available. +# +# The configuration file can contain any Ruby code, and can usefully include +# user code that: +# +# - Can then be debugged in \IRB. +# - Configures \IRB itself. +# - Requires or loads files. +# +# The path to the configuration file is the first found among: +# +# - The value of variable $IRBRC, if defined. +# - The value of variable $XDG_CONFIG_HOME/irb/irbrc, if defined. +# - File $HOME/.irbrc, if it exists. +# - File $HOME/.config/irb/irbrc, if it exists. +# - File +.config/irb/irbrc+ in the current directory, if it exists. +# - File +.irbrc+ in the current directory, if it exists. +# - File +irb.rc+ in the current directory, if it exists. +# - File +_irbrc+ in the current directory, if it exists. +# - File $irbrc in the current directory, if it exists. +# +# If the search fails, there is no configuration file. +# +# If the search succeeds, the configuration file is read as Ruby code, +# and so can contain any Ruby programming you like. +# +# \Method conf.rc? returns +true+ if a configuration file was read, +# +false+ otherwise. +# \Hash entry IRB.conf[:RC] also contains that value. +# +# === \Hash IRB.conf +# +# The initial entries in hash IRB.conf are determined by: +# +# - Default values. +# - Command-line options, which may override defaults. +# - Direct assignments in the configuration file. +# +# You can see the hash by typing IRB.conf. +# +# Details of the entries' meanings are described in the relevant subsections below. +# +# If you are interested in a specific entry, consult the +# {index}[rdoc-ref:doc/irb/indexes.rdoc@Index+of+IRB.conf+Entries]. +# +# === Notes on Initialization Precedence +# +# - Any conflict between an entry in hash IRB.conf and a command-line option +# is resolved in favor of the hash entry. +# - \Hash IRB.conf affects the context only once, +# when the configuration file is interpreted; +# any subsequent changes to it do not affect the context +# and are therefore essentially meaningless. +# +# === Initialization Script +# +# By default, the first command-line argument (after any options) +# is the path to a Ruby initialization script. +# +# \IRB reads the initialization script and puts its content onto the \IRB shell, +# just as if it were user-typed commands. +# +# Command-line option --noscript causes the first command-line argument +# to be treated as an ordinary argument (instead of an initialization script); +# --script is the default. +# +# == Input +# +# This section describes the features that allow you to change +# the way \IRB input works; +# see also {Input and Output}[rdoc-ref:IRB@Input+and+Output]. +# +# === Input Command History +# +# By default, \IRB stores a history of up to 1000 input commands +# in file ~/.irb_history +# (or, if a {configuration file}[rdoc-ref:IRB@Configuration+File] +# is found, in file +.irb_history+ +# inin the same directory as that file). +# +# A new \IRB session creates the history file if it does not exist, +# and appends to the file if it does exist. +# +# You can change the filepath by adding to your configuration file: +# IRB.conf[:HISTORY_FILE] = _filepath_, +# where _filepath_ is a string filepath. +# +# During the session, method conf.history_file returns the filepath, +# and method conf.history_file = new_filepath +# copies the history to the file at new_filepath, +# which becomes the history file for the session. +# +# You can change the number of commands saved by adding to your configuration file: +# IRB.conf[:SAVE_HISTORY] = _n_, +# where _n_ is one of: +# +# - Positive integer: the number of commands to be saved, +# - Zero: all commands are to be saved. +# - +nil+: no commands are to be saved,. +# +# During the session, you can use +# methods conf.save_history or conf.save_history= +# to retrieve or change the count. +# +# === Command Aliases +# +# By default, \IRB defines several command aliases: +# +# irb(main):001> conf.command_aliases +# => {:"$"=>:show_source, :"@"=>:whereami, :break=>:irb_break, :catch=>:irb_catch, :next=>:irb_next} +# +# You can change the initial aliases in the configuration file with: +# +# IRB.conf[:COMMAND_ALIASES] = {foo: :show_source, bar: :whereami} +# +# You can replace the current aliases at any time +# with configuration method conf.command_aliases=; +# Because conf.command_aliases is a hash, +# you can modify it. +# +# === End-of-File +# +# By default, IRB.conf[:IGNORE_EOF] is +false+, +# which means that typing the end-of-file character Ctrl-D +# causes the session to exit. +# +# You can reverse that behavior by adding IRB.conf[:IGNORE_EOF] = true +# to the configuration file. +# +# During the session, method conf.ignore_eof? returns the setting, +# and method conf.ignore_eof = _boolean_ sets it. +# +# === SIGINT +# +# By default, IRB.conf[:IGNORE_SIGINT] is +true+, +# which means that typing the interrupt character Ctrl-C +# causes the session to exit. +# +# You can reverse that behavior by adding IRB.conf[:IGNORE_SIGING] = false +# to the configuration file. +# +# During the session, method conf.ignore_siging? returns the setting, +# and method conf.ignore_sigint = _boolean_ sets it. +# +# === Automatic Completion +# +# By default, \IRB enables +# {automatic completion}[https://en.wikipedia.org/wiki/Autocomplete#In_command-line_interpreters]: +# +# You can disable it by either of these: +# +# - Adding IRB.conf[:USE_AUTOCOMPLETE] = false to the configuration file. +# - Giving command-line option --noautocomplete +# (--autocomplete is the default). +# +# \Method conf.use_autocomplete? returns +true+ +# if automatic completion is enabled, +false+ otherwise. +# +# The setting may not be changed during the session. +# +# === Automatic Indentation +# +# By default, \IRB automatically indents lines of code to show structure +# (e.g., it indent the contents of a block). +# +# The current setting is returned +# by the configuration method conf.auto_indent_mode. +# +# The default initial setting is +true+: +# +# irb(main):001> conf.auto_indent_mode +# => true +# irb(main):002* Dir.entries('.').select do |entry| +# irb(main):003* entry.start_with?('R') +# irb(main):004> end +# => ["README.md", "Rakefile"] +# +# You can change the initial setting in the +# configuration file with: +# +# IRB.conf[:AUTO_INDENT] = false +# +# Note that the _current_ setting may not be changed in the \IRB session. +# +# === Input \Method +# +# The \IRB input method determines how command input is to be read; +# by default, the input method for a session is IRB::RelineInputMethod. +# +# You can set the input method by: +# +# - Adding to the configuration file: +# +# - IRB.conf[:USE_SINGLELINE] = true +# or IRB.conf[:USE_MULTILINE]= false +# sets the input method to IRB::ReadlineInputMethod. +# - IRB.conf[:USE_SINGLELINE] = false +# or IRB.conf[:USE_MULTILINE] = true +# sets the input method to IRB::RelineInputMethod. +# +# - Giving command-line options: +# +# - --singleline +# or --nomultiline +# sets the input method to IRB::ReadlineInputMethod. +# - --nosingleline +# or --multiline/tt> +# sets the input method to IRB::RelineInputMethod. +# +# \Method conf.use_multiline? +# and its synonym conf.use_reline return: +# +# - +true+ if option --multiline was given. +# - +false+ if option --nomultiline was given. +# - +nil+ if neither was given. +# +# \Method conf.use_singleline? +# and its synonym conf.use_readline return: +# +# - +true+ if option --singleline was given. +# - +false+ if option --nosingleline was given. +# - +nil+ if neither was given. +# +# == Output +# +# This section describes the features that allow you to change +# the way \IRB output works; +# see also {Input and Output}[rdoc-ref:IRB@Input+and+Output]. +# +# === Return-Value Printing (Echoing) +# +# By default, \IRB prints (echoes) the values returned by all input commands. +# +# You can change the initial behavior and suppress all echoing by: +# +# - Adding to the configuration file: IRB.conf[:ECHO] = false. +# (The default value for this entry is +niL+, which means the same as +true+.) +# - Giving command-line option --noecho. +# (The default is --echo.) +# +# During the session, you can change the current setting +# with configuration method conf.echo= (set to +true+ or +false+). +# +# As stated above, by default \IRB prints the values returned by all input commands; +# but \IRB offers special treatment for values returned by assignment statements, +# which may be: +# +# - Printed with truncation (to fit on a single line of output), +# which is the default; +# an ellipsis (... is suffixed, to indicate the truncation): +# +# irb(main):001> x = 'abc' * 100 +# => "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc... +# +# - Printed in full (regardless of the length). +# - Suppressed (not printed at all) +# +# You can change the initial behavior by: +# +# - Adding to the configuration file: IRB.conf[:ECHO_ON_ASSIGNMENT] = false. +# (The default value for this entry is +niL+, which means the same as +:truncate+.) +# - Giving command-line option --noecho-on-assignment +# or --echo-on-assignment. +# (The default is --truncate-echo-on-assigment.) +# +# During the session, you can change the current setting +# with configuration method conf.echo_on_assignment= +# (set to +true+, +false+, or +:truncate+). +# +# By default, \IRB formats returned values by calling method +inspect+. +# +# You can change the initial behavior by: +# +# - Adding to the configuration file: IRB.conf[:INSPECT_MODE] = false. +# (The default value for this entry is +true+.) +# - Giving command-line option --noinspect. +# (The default is --inspect.) +# +# During the session, you can change the setting using method conf.inspect_mode=. +# +# === Multiline Output +# +# By default, \IRB prefixes a newline to a multiline response. +# +# You can change the initial default value by adding to the configuation file: +# +# IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT] = false +# +# During a session, you can retrieve or set the value using +# methods conf.newline_before_multiline_output? +# and conf.newline_before_multiline_output=. +# +# Examples: +# +# irb(main):001> conf.inspect_mode = false +# => false +# irb(main):002> "foo\nbar" +# => +# foo +# bar +# irb(main):003> conf.newline_before_multiline_output = false +# => false +# irb(main):004> "foo\nbar" +# => foo +# bar +# +# === Evaluation History +# +# By default, \IRB saves no history of evaluations (returned values), +# and the related methods conf.eval_history, _, +# and __ are undefined. +# +# You can turn on that history, and set the maximum number of evaluations to be stored: +# +# - In the configuration file: add IRB.conf[:EVAL_HISTORY] = _n_. +# (Examples below assume that we've added IRB.conf[:EVAL_HISTORY] = 5.) +# - In the session (at any time): conf.eval_history = _n_. +# +# If +n+ is zero, all evaluation history is stored. +# +# Doing either of the above: +# +# - Sets the maximum size of the evaluation history; +# defines method conf.eval_history, +# which returns the maximum size +n+ of the evaluation history: +# +# irb(main):001> conf.eval_history = 5 +# => 5 +# irb(main):002> conf.eval_history +# => 5 +# +# - Defines variable _, which contains the most recent evaluation, +# or +nil+ if none; same as method conf.last_value: +# +# irb(main):003> _ +# => 5 +# irb(main):004> :foo +# => :foo +# irb(main):005> :bar +# => :bar +# irb(main):006> _ +# => :bar +# irb(main):007> _ +# => :bar +# +# - Defines variable __: +# +# - __ unadorned: contains all evaluation history: +# +# irb(main):008> :foo +# => :foo +# irb(main):009> :bar +# => :bar +# irb(main):010> :baz +# => :baz +# irb(main):011> :bat +# => :bat +# irb(main):012> :bam +# => :bam +# irb(main):013> __ +# => +# 9 :bar +# 10 :baz +# 11 :bat +# 12 :bam +# irb(main):014> __ +# => +# 10 :baz +# 11 :bat +# 12 :bam +# 13 ...self-history... +# +# Note that when the evaluation is multiline, it is displayed differently. +# +# - __[_m_]: +# +# - Positive _m_: contains the evaluation for the given line number, +# or +nil+ if that line number is not in the evaluation history: +# +# irb(main):015> __[12] +# => :bam +# irb(main):016> __[1] +# => nil +# +# - Negative _m_: contains the +mth+-from-end evaluation, +# or +nil+ if that evaluation is not in the evaluation history: +# +# irb(main):017> __[-3] +# => :bam +# irb(main):018> __[-13] +# => nil +# +# - Zero _m_: contains +nil+: +# +# irb(main):019> __[0] +# => nil +# +# === Prompt and Return Formats +# +# By default, \IRB uses the prompt and return value formats +# defined in its +:DEFAULT+ prompt mode. +# +# ==== The Default Prompt and Return Format +# +# The default prompt and return values look like this: +# +# irb(main):001> 1 + 1 +# => 2 +# irb(main):002> 2 + 2 +# => 4 +# +# The prompt includes: +# +# - The name of the running program (irb); +# see {IRB Name}[rdoc-ref:IRB@IRB+Name]. +# - The name of the current session (main); +# See {IRB Sessions}[rdoc-ref:IRB@IRB+Sessions]. +# - A 3-digit line number (1-based). +# +# The default prompt actually defines three formats: +# +# - One for most situations (as above): +# +# irb(main):003> Dir +# => Dir +# +# - One for when the typed command is a statement continuation (adds trailing asterisk): +# +# irb(main):004* Dir. +# +# - One for when the typed command is a string continuation (adds trailing single-quote): +# +# irb(main):005' Dir.entries('. +# +# You can see the prompt change as you type the characters in the following: +# +# irb(main):001* Dir.entries('.').select do |entry| +# irb(main):002* entry.start_with?('R') +# irb(main):003> end +# => ["README.md", "Rakefile"] +# +# ==== Pre-Defined Prompts +# +# \IRB has several pre-defined prompts, stored in hash IRB.conf[:PROMPT]: +# +# irb(main):001> IRB.conf[:PROMPT].keys +# => [:NULL, :DEFAULT, :CLASSIC, :SIMPLE, :INF_RUBY, :XMP] +# +# To see the full data for these, type IRB.conf[:PROMPT]. +# +# Most of these prompt definitions include specifiers that represent +# values like the \IRB name, session name, and line number; +# see {Prompt Specifiers}[rdoc-ref:IRB@Prompt+Specifiers]. +# +# You can change the initial prompt and return format by: +# +# - Adding to the configuration file: IRB.conf[:PROMPT] = _mode_ +# where _mode_ is the symbol name of a prompt mode. +# - Giving a command-line option: +# +# - --prompt _mode_: sets the prompt mode to _mode_. +# where _mode_ is the symbol name of a prompt mode. +# - --simple-prompt or --sample-book-mode: +# sets the prompt mode to +:SIMPLE+. +# - --inf-ruby-mode: sets the prompt mode to +:INF_RUBY+ +# and suppresses both --multiline and --singleline. +# - --noprompt: suppresses prompting; does not affect echoing. +# +# You can retrieve or set the current prompt mode with methods +# +# conf.prompt_mode and conf.prompt_mode=. +# +# If you're interested in prompts and return formats other than the defaults, +# you might experiment by trying some of the others. +# +# ==== Custom Prompts +# +# You can also define custom prompts and return formats, +# which may be done either in an \IRB session or in the configuration file. +# +# A prompt in \IRB actually defines three prompts, as seen above. +# For simple custom data, we'll make all three the same: +# +# irb(main):001* IRB.conf[:PROMPT][:MY_PROMPT] = { +# irb(main):002* PROMPT_I: ': ', +# irb(main):003* PROMPT_C: ': ', +# irb(main):004* PROMPT_S: ': ', +# irb(main):005* RETURN: '=> ' +# irb(main):006> } +# => {:PROMPT_I=>": ", :PROMPT_C=>": ", :PROMPT_S=>": ", :RETURN=>"=> "} +# +# If you define the custom prompt in the configuration file, +# you can also make it the current prompt by adding: +# +# IRB.conf[:PROMPT_MODE] = :MY_PROMPT +# +# Regardless of where it's defined, you can make it the current prompt in a session: +# +# conf.prompt_mode = :MY_PROMPT +# +# You can view or modify the current prompt data with various configuration methods: +# +# - conf.prompt_mode, conf.prompt_mode=. +# - conf.prompt_c, conf.c=. +# - conf.prompt_i, conf.i=. +# - conf.prompt_s, conf.s=. +# - conf.return_format, return_format=. +# +# ==== Prompt Specifiers +# +# A prompt's definition can include specifiers for which certain values are substituted: +# +# - %N: the name of the running program. +# - %m: the value of self.to_s. +# - %M: the value of self.inspect. +# - %l: an indication of the type of string; +# one of ", ', /, ]. +# - NNi: Indentation level. +# - NNn: Line number. +# - %%: Literal %. +# +# === Verbosity +# +# By default, \IRB verbosity is disabled, which means that output is smaller +# rather than larger. +# +# You can enable verbosity by: +# +# - Adding to the configuration file: IRB.conf[:VERBOSE] = true +# (the default is +nil+). +# - Giving command-line options --verbose +# (the default is --noverbose). +# +# During a session, you can retrieve or set verbosity with methods +# conf.verbose and conf.verbose=. +# +# === Help +# +# Command-line option --version causes \IRB to print its help text +# and exit. +# +# === Version +# +# Command-line option --version causes \IRB to print its version text +# and exit. +# +# == Input and Output +# +# === \Color Highlighting +# +# By default, \IRB color highlighting is enabled, and is used for both: +# +# - Input: As you type, \IRB reads the typed characters and highlights +# elements that it recognizes; +# it also highlights errors such as mismatched parentheses. +# - Output: \IRB highlights syntactical elements. +# +# You can disable color highlighting by: +# +# - Adding to the configuration file: IRB.conf[:USE_COLORIZE] = false +# (the default value is +true+). +# - Giving command-line option --nocolorize +# +# == Debugging +# +# Command-line option -d sets variables $VERBOSE +# and $DEBUG to +true+; +# these have no effect on \IRB output. +# +# === Tracer +# +# \IRB's tracer feature controls whether a stack trace +# is to be displayed for each command. +# +# Command-line option -tracer sets +# variable IRB.conf[:USE_TRACER] to +true+ +# (the default is +false+). +# +# You can specify a back trace limit, +n+, +# which specifies that the back trace for an exception +# can contain no more than 2 * +n+ entries, +# consisting at most of the first +n+ and last +n+ entries. +# +# The current limit is returned +# by the configuration method conf.back_trace_limit. +# +# The initial limit is 16: +# +# irb(main):001> conf.back_trace_limit +# => 16 +# +# You can change the initial limit with command-line option +# --back-trace-limit _value_: +# +# irb --back-trace-limit 32 +# +# You can also change the initial limit in the configuration file +# (which overrides the command-line option above): +# +# IRB.conf[:BACK_TRACE_LIMIT] = 24 +# +# You can change the current limit at any time +# with configuration method conf.back_trace_limit=. +# +# Note that the _current_ limit may not +# be changed by IRB.conf[:BACK_TRACE_LIMIT] = '_value_' +# in the \IRB session. +# +# === Warnings +# +# Command-line option -w suppresses warnings. +# +# Command-line option -W[_level_] +# sets warning level; 0=silence, 1=medium, 2=verbose. +# +# :stopdoc: +# === Performance Measurement +# +# IRB.conf[:MEASURE] IRB.conf[:MEASURE_CALLBACKS] IRB.conf[:MEASURE_PROC] +# :startdoc: +# +# == Other Features +# +# === Load Modules +# +# You can specify the names of modules that are to be required at startup. +# +# \Array conf.load_modules determines the modules (if any) +# that are to be required during session startup. +# The array is used only during session startup, +# so the initial value is the only one that counts. +# +# The default initial value is [] (load no modules): +# +# irb(main):001> conf.load_modules +# => [] +# +# You can set the default initial value via: +# +# - Command-line option -r +# +# $ irb -r csv -r json +# irb(main):001> conf.load_modules +# => ["csv", "json"] +# +# - \Hash entry IRB.conf[:LOAD_MODULES] = _array_: +# +# IRB.conf[:LOAD_MODULES] = %w[csv, json] +# +# Note that the configuration file entry overrides the command-line options. +# +# :stopdoc: +# === \IRB Loader +# +# IRB.conf[:USE_LOADER] +# :startdoc: +# +# === RI Documentation Directories +# +# You can specify the paths to RI documentation directories +# that are to be loaded (in addition to the default directories) at startup; +# see details about RI by typing ri --help. +# +# \Array conf.extra_doc_dirs determines the directories (if any) +# that are to be loaded during session startup. +# The array is used only during session startup, +# so the initial value is the only one that counts. +# +# The default initial value is [] (load no extra documentation): +# +# irb(main):001> conf.extra_doc_dirs +# => [] +# +# You can set the default initial value via: +# +# - Command-line option --extra_doc_dir +# +# $ irb --extra-doc-dir your_doc_dir --extra-doc-dir my_doc_dir +# irb(main):001> conf.extra_doc_dirs +# => ["your_doc_dir", "my_doc_dir"] +# +# - \Hash entry IRB.conf[:EXTRA_DOC_DIRS] = _array_: +# +# IRB.conf[:EXTRA_DOC_DIRS] = %w[your_doc_dir my_doc_dir] +# +# Note that the configuration file entry overrides the command-line options. +# +# :stopdoc: +# === \Context Mode +# +# IRB.conf[:CONTEXT_MODE] +# :startdoc: +# +# === \IRB Name +# +# You can specify a name for \IRB. +# +# The default initial value is 'irb': +# +# irb(main):001> conf.irb_name +# => "irb" +# +# You can set the default initial value +# via hash entry IRB.conf[:IRB_NAME] = _string_: +# +# IRB.conf[:IRB_NAME] = 'foo' +# +# === Application Name +# +# You can specify an application name for the \IRB session. +# +# The default initial value is 'irb': +# +# irb(main):001> conf.ap_name +# => "irb" +# +# You can set the default initial value +# via hash entry IRB.conf[:AP_NAME] = _string_: +# +# IRB.conf[:AP_NAME] = 'my_ap_name' +# +# :stopdoc: +# === \IRB Library Directory +# +# IRB.conf[:IRB_LIB_PATH] +# :startdoc: +# +# === Configuration Monitor +# +# You can monitor changes to the configuration by assigning a proc +# to IRB.conf[:IRB_RC] in the configuration file: +# +# IRB.conf[:IRB_RC] = proc {|conf| puts conf.class } +# +# Each time the configuration is changed, +# that proc is called with argument +conf+: +# +# :stopdoc: +# === \Locale +# +# IRB.conf[:LC_MESSAGES] +# :startdoc: +# +# :stopdoc: +# === Single-IRB Mode +# +# IRB.conf[:SINGLE_IRB] +# :startdoc: +# +# === Encodings +# +# Command-line option -E _ex_[:_in_] +# sets initial external (ex) and internal (in) encodings. +# +# Command-line option -U sets both to UTF-8. +# +# === Commands # # The following commands are available on IRB. # @@ -97,169 +931,14 @@ # * break, delete, next, step, continue, finish, backtrace, info, catch # * Start the debugger of debug.gem and run the command on it. # -# == Configuration -# -# IRB reads a personal initialization file when it's invoked. -# IRB searches a file in the following order and loads the first one found. -# -# * $IRBRC (if $IRBRC is set) -# * $XDG_CONFIG_HOME/irb/irbrc (if $XDG_CONFIG_HOME is set) -# * ~/.irbrc -# * +.config/irb/irbrc+ -# * +.irbrc+ -# * +irb.rc+ -# * +_irbrc+ -# * $irbrc -# -# The following are alternatives to the command line options. To use them type -# as follows in an +irb+ session: -# -# IRB.conf[:IRB_NAME]="irb" -# IRB.conf[:INSPECT_MODE]=nil -# IRB.conf[:IRB_RC] = nil -# IRB.conf[:BACK_TRACE_LIMIT]=16 -# IRB.conf[:USE_LOADER] = false -# IRB.conf[:USE_MULTILINE] = nil -# IRB.conf[:USE_SINGLELINE] = nil -# IRB.conf[:USE_COLORIZE] = true -# IRB.conf[:USE_TRACER] = false -# IRB.conf[:USE_AUTOCOMPLETE] = true -# IRB.conf[:IGNORE_SIGINT] = true -# IRB.conf[:IGNORE_EOF] = false -# IRB.conf[:PROMPT_MODE] = :DEFAULT -# IRB.conf[:PROMPT] = {...} -# -# === Auto indentation -# -# To disable auto-indent mode in irb, add the following to your +.irbrc+: -# -# IRB.conf[:AUTO_INDENT] = false -# -# === Autocompletion -# -# To disable autocompletion for irb, add the following to your +.irbrc+: -# -# IRB.conf[:USE_AUTOCOMPLETE] = false -# -# To enable enhanced completion using type information, add the following to your +.irbrc+: -# -# IRB.conf[:COMPLETOR] = :type -# -# === History -# -# By default, irb will store the last 1000 commands you used in -# IRB.conf[:HISTORY_FILE] (~/.irb_history by default). -# -# If you want to disable history, add the following to your +.irbrc+: -# -# IRB.conf[:SAVE_HISTORY] = nil -# -# See IRB::Context#save_history= for more information. -# -# The history of _results_ of commands evaluated is not stored by default, -# but can be turned on to be stored with this +.irbrc+ setting: -# -# IRB.conf[:EVAL_HISTORY] = -# -# See IRB::Context#eval_history= and EvalHistory class. The history of command -# results is not permanently saved in any file. -# -# == Customizing the IRB Prompt -# -# In order to customize the prompt, you can change the following Hash: -# -# IRB.conf[:PROMPT] -# -# This example can be used in your +.irbrc+ -# -# IRB.conf[:PROMPT][:MY_PROMPT] = { # name of prompt mode -# :AUTO_INDENT => false, # disables auto-indent mode -# :PROMPT_I => ">> ", # simple prompt -# :PROMPT_S => nil, # prompt for continuated strings -# :PROMPT_C => nil, # prompt for continuated statement -# :RETURN => " ==>%s\n" # format to return value -# } -# -# IRB.conf[:PROMPT_MODE] = :MY_PROMPT -# -# Or, invoke irb with the above prompt mode by: -# -# irb --prompt my-prompt -# -# Constants +PROMPT_I+, +PROMPT_S+ and +PROMPT_C+ specify the format. In the -# prompt specification, some special strings are available: -# -# %N # command name which is running -# %m # to_s of main object (self) -# %M # inspect of main object (self) -# %l # type of string(", ', /, ]), `]' is inner %w[...] -# %NNi # indent level. NN is digits and means as same as printf("%NNd"). -# # It can be omitted -# %NNn # line number. -# %% # % -# -# For instance, the default prompt mode is defined as follows: -# -# IRB.conf[:PROMPT_MODE][:DEFAULT] = { -# :PROMPT_I => "%N(%m):%03n> ", -# :PROMPT_S => "%N(%m):%03n%l ", -# :PROMPT_C => "%N(%m):%03n* ", -# :RETURN => "%s\n" # used to printf -# } -# -# irb comes with a number of available modes: -# -# # :NULL: -# # :PROMPT_I: -# # :PROMPT_S: -# # :PROMPT_C: -# # :RETURN: | -# # %s -# # :DEFAULT: -# # :PROMPT_I: ! '%N(%m):%03n> ' -# # :PROMPT_S: ! '%N(%m):%03n%l ' -# # :PROMPT_C: ! '%N(%m):%03n* ' -# # :RETURN: | -# # => %s -# # :CLASSIC: -# # :PROMPT_I: ! '%N(%m):%03n:%i> ' -# # :PROMPT_S: ! '%N(%m):%03n:%i%l ' -# # :PROMPT_C: ! '%N(%m):%03n:%i* ' -# # :RETURN: | -# # %s -# # :SIMPLE: -# # :PROMPT_I: ! '>> ' -# # :PROMPT_S: -# # :PROMPT_C: ! '?> ' -# # :RETURN: | -# # => %s -# # :INF_RUBY: -# # :PROMPT_I: ! '%N(%m):%03n> ' -# # :PROMPT_S: -# # :PROMPT_C: -# # :RETURN: | -# # %s -# # :AUTO_INDENT: true -# # :XMP: -# # :PROMPT_I: -# # :PROMPT_S: -# # :PROMPT_C: -# # :RETURN: |2 -# # ==>%s -# -# == Restrictions -# -# Because irb evaluates input immediately after it is syntactically complete, -# the results may be slightly different than directly using Ruby. -# -# == IRB Sessions +# === IRB Sessions # # IRB has a special feature, that allows you to manage many sessions at once. # # You can create new sessions with Irb.irb, and get a list of current sessions # with the +jobs+ command in the prompt. # -# === Commands +# ==== \IRB-Specific Commands # # JobManager provides commands to handle the current sessions: # @@ -280,19 +959,19 @@ # +irb_require+:: # Loads the given file similarly to Kernel#require # -# === Configuration +# ==== Configuration # # The command line options, or IRB.conf, specify the default behavior of # Irb.irb. # -# On the other hand, each conf in IRB@Command+line+options is used to +# On the other hand, each conf in IRB@Command-Line+Options is used to # individually configure IRB.irb. # # If a proc is set for IRB.conf[:IRB_RC], its will be invoked after execution # of that proc with the context of the current session as its argument. Each # session can be configured using this mechanism. # -# === Session variables +# ==== Session variables # # There are a few variables in every Irb session that can come in handy: # @@ -307,7 +986,7 @@ # If +line_no+ is a negative, the return value +line_no+ many lines before # the most recent return value. # -# === Example using IRB Sessions +# ==== Example using IRB Sessions # # # invoke a new session # irb(main):001:0> irb @@ -367,6 +1046,15 @@ # #0->irb on main (# : running) # # quit irb # irb(main):010:0> exit +# +# == Restrictions +# +# Ruby code typed into \IRB behaves the same as Ruby code in a file, except that: +# +# - Because \IRB evaluates input immediately after it is syntactically complete, +# some results may be slightly different. +# - Forking may not be well behaved. +# module IRB # An exception raised by IRB.irb_abort @@ -1023,8 +1711,7 @@ class Binding # irb(#):005:0> exit # Cooked potato: true # - # - # See IRB@Usage for more information. + # See IRB for more information. def irb(show_code: true) # Setup IRB with the current file's path and no command line arguments IRB.setup(source_location[0], argv: []) diff --git a/lib/irb/context.rb b/lib/irb/context.rb index ffbba4e8b..c3690fcac 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -264,15 +264,15 @@ def main attr_reader :prompt_mode # Standard IRB prompt. # - # See IRB@Customizing+the+IRB+Prompt for more information. + # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. attr_accessor :prompt_i # IRB prompt for continuated strings. # - # See IRB@Customizing+the+IRB+Prompt for more information. + # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. attr_accessor :prompt_s # IRB prompt for continuated statement. (e.g. immediately after an +if+) # - # See IRB@Customizing+the+IRB+Prompt for more information. + # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. attr_accessor :prompt_c # TODO: Remove this when developing v2.0 @@ -394,8 +394,6 @@ def prompt_n=(_) # The default value is 16. # # Can also be set using the +--back-trace-limit+ command line option. - # - # See IRB@Command+line+options for more command line options. attr_accessor :back_trace_limit # User-defined IRB command aliases @@ -463,7 +461,7 @@ def set_last_value(value) # Sets the +mode+ of the prompt in this context. # - # See IRB@Customizing+the+IRB+Prompt for more information. + # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. def prompt_mode=(mode) @prompt_mode = mode pconf = IRB.conf[:PROMPT][mode] @@ -501,8 +499,6 @@ def file_input? # # Can also be set using the +--inspect+ and +--noinspect+ command line # options. - # - # See IRB@Command+line+options for more command line options. def inspect_mode=(opt) if i = Inspector::INSPECTORS[opt] diff --git a/lib/irb/help.rb b/lib/irb/help.rb index ca12810de..6861d7efc 100644 --- a/lib/irb/help.rb +++ b/lib/irb/help.rb @@ -5,7 +5,7 @@ # module IRB - # Outputs the irb help message, see IRB@Command+line+options. + # Outputs the irb help message, see IRB@Command-Line+Options. def IRB.print_usage lc = IRB.conf[:LC_MESSAGES] path = lc.find("irb/help-message") diff --git a/lib/irb/xmp.rb b/lib/irb/xmp.rb index 94c700b48..de2908942 100644 --- a/lib/irb/xmp.rb +++ b/lib/irb/xmp.rb @@ -44,8 +44,8 @@ class XMP # The top-level binding or, optional +bind+ parameter will be used when # creating the workspace. See WorkSpace.new for more information. # - # This uses the +:XMP+ prompt mode, see IRB@Customizing+the+IRB+Prompt for - # full detail. + # This uses the +:XMP+ prompt mode. + # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. def initialize(bind = nil) IRB.init_config(nil) From 3c77213209ff5ed095228ba530dfe41cf0339ba4 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 12 Dec 2023 11:27:20 +0000 Subject: [PATCH 030/263] Remove trailing space This is required to fix ruby/ruby's CI --- lib/irb.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/irb.rb b/lib/irb.rb index aef1e69c6..f9c3c1fae 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -23,7 +23,7 @@ require_relative "irb/pager" # == \IRB -# +# # \Module \IRB ("Interactive Ruby") provides a shell-like interface # that supports user interaction with the Ruby interpreter. # From 6a9193e88bd9fd5c0d32609a0fcd024f3014e058 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 12 Dec 2023 18:46:03 +0000 Subject: [PATCH 031/263] Remove documents about deprecated/WIP features and some slight adjustments (#811) --- lib/irb.rb | 185 +---------------------------------------------------- 1 file changed, 2 insertions(+), 183 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index f9c3c1fae..a2a3a7190 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -238,7 +238,7 @@ # By default, \IRB defines several command aliases: # # irb(main):001> conf.command_aliases -# => {:"$"=>:show_source, :"@"=>:whereami, :break=>:irb_break, :catch=>:irb_catch, :next=>:irb_next} +# => {:"$"=>:show_source, :"@"=>:whereami} # # You can change the initial aliases in the configuration file with: # @@ -698,45 +698,6 @@ # and $DEBUG to +true+; # these have no effect on \IRB output. # -# === Tracer -# -# \IRB's tracer feature controls whether a stack trace -# is to be displayed for each command. -# -# Command-line option -tracer sets -# variable IRB.conf[:USE_TRACER] to +true+ -# (the default is +false+). -# -# You can specify a back trace limit, +n+, -# which specifies that the back trace for an exception -# can contain no more than 2 * +n+ entries, -# consisting at most of the first +n+ and last +n+ entries. -# -# The current limit is returned -# by the configuration method conf.back_trace_limit. -# -# The initial limit is 16: -# -# irb(main):001> conf.back_trace_limit -# => 16 -# -# You can change the initial limit with command-line option -# --back-trace-limit _value_: -# -# irb --back-trace-limit 32 -# -# You can also change the initial limit in the configuration file -# (which overrides the command-line option above): -# -# IRB.conf[:BACK_TRACE_LIMIT] = 24 -# -# You can change the current limit at any time -# with configuration method conf.back_trace_limit=. -# -# Note that the _current_ limit may not -# be changed by IRB.conf[:BACK_TRACE_LIMIT] = '_value_' -# in the \IRB session. -# # === Warnings # # Command-line option -w suppresses warnings. @@ -780,12 +741,6 @@ # # Note that the configuration file entry overrides the command-line options. # -# :stopdoc: -# === \IRB Loader -# -# IRB.conf[:USE_LOADER] -# :startdoc: -# # === RI Documentation Directories # # You can specify the paths to RI documentation directories @@ -850,12 +805,6 @@ # # IRB.conf[:AP_NAME] = 'my_ap_name' # -# :stopdoc: -# === \IRB Library Directory -# -# IRB.conf[:IRB_LIB_PATH] -# :startdoc: -# # === Configuration Monitor # # You can monitor changes to the configuration by assigning a proc @@ -872,12 +821,6 @@ # IRB.conf[:LC_MESSAGES] # :startdoc: # -# :stopdoc: -# === Single-IRB Mode -# -# IRB.conf[:SINGLE_IRB] -# :startdoc: -# # === Encodings # # Command-line option -E _ex_[:_in_] @@ -887,49 +830,7 @@ # # === Commands # -# The following commands are available on IRB. -# -# * cwws -# * Show the current workspace. -# * cb, cws, chws -# * Change the current workspace to an object. -# * bindings, workspaces -# * Show workspaces. -# * pushb, pushws -# * Push an object to the workspace stack. -# * popb, popws -# * Pop a workspace from the workspace stack. -# * load -# * Load a Ruby file. -# * require -# * Require a Ruby file. -# * source -# * Loads a given file in the current session. -# * irb -# * Start a child IRB. -# * jobs -# * List of current sessions. -# * fg -# * Switches to the session of the given number. -# * kill -# * Kills the session with the given number. -# * help -# * Enter the mode to look up RI documents. -# * irb_info -# * Show information about IRB. -# * ls -# * Show methods, constants, and variables. -# -g [query] or -G [query] allows you to filter out the output. -# * measure -# * measure enables the mode to measure processing time. measure :off disables it. -# * $, show_source -# * Show the source code of a given method or constant. -# * @, whereami -# * Show the source code around binding.irb again. -# * debug -# * Start the debugger of debug.gem. -# * break, delete, next, step, continue, finish, backtrace, info, catch -# * Start the debugger of debug.gem and run the command on it. +# Please use the `show_cmds` command to see the list of available commands. # # === IRB Sessions # @@ -938,27 +839,6 @@ # You can create new sessions with Irb.irb, and get a list of current sessions # with the +jobs+ command in the prompt. # -# ==== \IRB-Specific Commands -# -# JobManager provides commands to handle the current sessions: -# -# jobs # List of current sessions -# fg # Switches to the session of the given number -# kill # Kills the session with the given number -# -# The +exit+ command, or ::irb_exit, will quit the current session and call any -# exit hooks with IRB.irb_at_exit. -# -# A few commands for loading files within the session are also available: -# -# +source+:: -# Loads a given file in the current session and displays the source lines, -# see IrbLoader#source_file -# +irb_load+:: -# Loads the given file similarly to Kernel#load, see IrbLoader#irb_load -# +irb_require+:: -# Loads the given file similarly to Kernel#require -# # ==== Configuration # # The command line options, or IRB.conf, specify the default behavior of @@ -986,67 +866,6 @@ # If +line_no+ is a negative, the return value +line_no+ many lines before # the most recent return value. # -# ==== Example using IRB Sessions -# -# # invoke a new session -# irb(main):001:0> irb -# # list open sessions -# irb.1(main):001:0> jobs -# #0->irb on main (# : stop) -# #1->irb#1 on main (# : running) -# -# # change the active session -# irb.1(main):002:0> fg 0 -# # define class Foo in top-level session -# irb(main):002:0> class Foo;end -# # invoke a new session with the context of Foo -# irb(main):003:0> irb Foo -# # define Foo#foo -# irb.2(Foo):001:0> def foo -# irb.2(Foo):002:1> print 1 -# irb.2(Foo):003:1> end -# -# # change the active session -# irb.2(Foo):004:0> fg 0 -# # list open sessions -# irb(main):004:0> jobs -# #0->irb on main (# : running) -# #1->irb#1 on main (# : stop) -# #2->irb#2 on Foo (# : stop) -# # check if Foo#foo is available -# irb(main):005:0> Foo.instance_methods #=> [:foo, ...] -# -# # change the active session -# irb(main):006:0> fg 2 -# # define Foo#bar in the context of Foo -# irb.2(Foo):005:0> def bar -# irb.2(Foo):006:1> print "bar" -# irb.2(Foo):007:1> end -# irb.2(Foo):010:0> Foo.instance_methods #=> [:bar, :foo, ...] -# -# # change the active session -# irb.2(Foo):011:0> fg 0 -# irb(main):007:0> f = Foo.new #=> # -# # invoke a new session with the context of f (instance of Foo) -# irb(main):008:0> irb f -# # list open sessions -# irb.3():001:0> jobs -# #0->irb on main (# : stop) -# #1->irb#1 on main (# : stop) -# #2->irb#2 on Foo (# : stop) -# #3->irb#3 on # (# : running) -# # evaluate f.foo -# irb.3():002:0> foo #=> 1 => nil -# # evaluate f.bar -# irb.3():003:0> bar #=> bar => nil -# # kill jobs 1, 2, and 3 -# irb.3():004:0> kill 1, 2, 3 -# # list open sessions, should only include main session -# irb(main):009:0> jobs -# #0->irb on main (# : running) -# # quit irb -# irb(main):010:0> exit -# # == Restrictions # # Ruby code typed into \IRB behaves the same as Ruby code in a file, except that: From e79a90a1e6ef04243b31959bfb2e9232d349f081 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 13 Dec 2023 20:06:21 +0900 Subject: [PATCH 032/263] Warn and do nothing if block is passed to measure command (#813) --- lib/irb/cmd/measure.rb | 23 +++++++--------- lib/irb/init.rb | 2 ++ test/irb/test_cmd.rb | 62 ++++++++++++++++++++++++++++++------------ 3 files changed, 57 insertions(+), 30 deletions(-) diff --git a/lib/irb/cmd/measure.rb b/lib/irb/cmd/measure.rb index 9122e2dac..4e1125a0a 100644 --- a/lib/irb/cmd/measure.rb +++ b/lib/irb/cmd/measure.rb @@ -12,32 +12,29 @@ def initialize(*args) super(*args) end - def execute(type = nil, arg = nil, &block) + 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.conf[:MEASURE] = nil 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 - IRB.conf[:MEASURE] = true - added = IRB.set_measure_callback(type, arg) + added = IRB.set_measure_callback(arg) puts "#{added[0]} is added." if added else - if block_given? - IRB.conf[:MEASURE] = true - added = IRB.set_measure_callback(&block) - puts "#{added[0]} is added." if added - else - IRB.conf[:MEASURE] = true - added = IRB.set_measure_callback(type, arg) - puts "#{added[0]} is added." if added - end + added = IRB.set_measure_callback(type, arg) + puts "#{added[0]} is added." if added end nil end diff --git a/lib/irb/init.rb b/lib/irb/init.rb index b69f68d53..d9676fd9a 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -215,6 +215,7 @@ def IRB.set_measure_callback(type = nil, arg = nil, &block) added = [:TIME, IRB.conf[:MEASURE_PROC][:TIME], arg] end if added + IRB.conf[:MEASURE] = true found = IRB.conf[:MEASURE_CALLBACKS].find{ |m| m[0] == added[0] && m[2] == added[2] } if found # already added @@ -235,6 +236,7 @@ def IRB.unset_measure_callback(type = nil) type_sym = type.upcase.to_sym IRB.conf[:MEASURE_CALLBACKS].reject!{ |t, | t == type_sym } end + IRB.conf[:MEASURE] = nil if IRB.conf[:MEASURE_CALLBACKS].empty? end def IRB.init_error diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index a0a62e96d..da7b223fc 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -226,17 +226,20 @@ def test_measure c = Class.new(Object) out, err = execute_lines( - "3\n", "measure\n", "3\n", "measure :off\n", "3\n", + "measure :on\n", + "3\n", + "measure :off\n", + "3\n", conf: conf, main: c ) assert_empty err - assert_match(/\A=> 3\nTIME is added\.\n=> nil\nprocessing time: .+\n=> 3\n=> nil\n=> 3\n/, out) + assert_match(/\A(TIME is added\.\n=> nil\nprocessing time: .+\n=> 3\n=> nil\n=> 3\n){2}/, out) assert_empty(c.class_variables) end @@ -353,7 +356,13 @@ def test_measure_with_custom assert_match(/\A=> 3\nCUSTOM is added\.\n=> nil\ncustom processing time: .+\n=> 3\n=> nil\n=> 3\n/, out) end - def test_measure_with_proc + def test_measure_toggle + measuring_proc = proc { |line, line_no, &block| + time = Time.now + result = block.() + puts 'custom processing time: %fs' % (Time.now - time) if IRB.conf[:MEASURE] + result + } conf = { PROMPT: { DEFAULT: { @@ -364,30 +373,49 @@ def test_measure_with_proc }, PROMPT_MODE: :DEFAULT, MEASURE: false, + MEASURE_PROC: { + FOO: proc { |&block| puts 'foo'; block.call }, + BAR: proc { |&block| puts 'bar'; block.call } + } } - c = Class.new(Object) out, err = execute_lines( + "measure :foo", + "measure :on, :bar", "3\n", - "measure { |context, code, line_no, &block|\n", - " result = block.()\n", - " puts 'aaa' if IRB.conf[:MEASURE]\n", - " result\n", - "}\n", + "measure :off, :foo\n", + "measure :off, :bar\n", "3\n", - "measure { |context, code, line_no, &block|\n", - " result = block.()\n", - " puts 'bbb' if IRB.conf[:MEASURE]\n", - " result\n", - "}\n", + conf: conf + ) + + assert_empty err + assert_match(/\AFOO is added\.\n=> nil\nfoo\nBAR is added\.\n=> nil\nbar\nfoo\n=> 3\nbar\nfoo\n=> nil\nbar\n=> nil\n=> 3\n/, out) + end + + def test_measure_with_proc_warning + conf = { + PROMPT: { + DEFAULT: { + PROMPT_I: '> ', + PROMPT_S: '> ', + PROMPT_C: '> ' + } + }, + PROMPT_MODE: :DEFAULT, + MEASURE: false, + } + c = Class.new(Object) + out, err = execute_lines( "3\n", - "measure :off\n", + "measure do\n", + "end\n", "3\n", conf: conf, main: c ) - assert_empty err - assert_match(/\A=> 3\nBLOCK is added\.\n=> nil\naaa\n=> 3\nBLOCK is added.\naaa\n=> nil\nbbb\n=> 3\n=> nil\n=> 3\n/, out) + assert_match(/to add custom measure/, err) + assert_match(/\A=> 3\n=> nil\n=> 3\n/, out) assert_empty(c.class_variables) end end From 320178b120303df3a212c9b891b8f15cd329083b Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 14 Dec 2023 02:05:30 +0900 Subject: [PATCH 033/263] Remove unused lvar in mesure command test (#814) --- test/irb/test_cmd.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index da7b223fc..d99ac05c5 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -357,12 +357,6 @@ def test_measure_with_custom end def test_measure_toggle - measuring_proc = proc { |line, line_no, &block| - time = Time.now - result = block.() - puts 'custom processing time: %fs' % (Time.now - time) if IRB.conf[:MEASURE] - result - } conf = { PROMPT: { DEFAULT: { From 1138e964503374ba493dc14d909b5084ea76c46b Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 17 Dec 2023 13:33:10 +0100 Subject: [PATCH 034/263] Avoid installing bundler manually for yamatanooroti builds (#816) --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bb493c442..313c6d672 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -60,6 +60,7 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} + bundler-cache: true - name: Install libvterm run: | sudo apt install -y libtool-bin @@ -71,7 +72,6 @@ jobs: sudo make install - name: Install dependencies run: | - gem install bundler --no-document WITH_VTERM=1 bundle install - name: rake test_yamatanooroti run: WITH_VTERM=1 bundle exec rake test_yamatanooroti From 37ffdc6b1997c8e13515534df28026a53a8a9fb3 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Mon, 18 Dec 2023 14:36:55 +0000 Subject: [PATCH 035/263] Warn users about errors in loading RC files (#817) 1. Because `IRB.rc_file` always generates an rc file name, even if the file doesn't exist, we should check the file exists before trying to load it. 2. If any type of errors occur while loading the rc file, we should warn the user about it. --- lib/irb/init.rb | 18 ++++++++---------- test/irb/test_init.rb | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/lib/irb/init.rb b/lib/irb/init.rb index d9676fd9a..66e7b6146 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -389,18 +389,16 @@ def IRB.parse_opts(argv: ::ARGV) $LOAD_PATH.unshift(*load_path) end - # running config + # Run the config file def IRB.run_config if @CONF[:RC] begin - load rc_file - rescue LoadError, Errno::ENOENT - rescue # StandardError, ScriptError - print "load error: #{rc_file}\n" - print $!.class, ": ", $!, "\n" - for err in $@[0, $@.size - 2] - print "\t", err, "\n" - end + file = rc_file + # Because rc_file always returns `HOME/.irbrc` even if no rc file is present, we can't warn users about missing rc files. + # Otherwise, it'd be very noisy. + load file if File.exist?(file) + rescue StandardError, ScriptError => e + warn "Error loading RC file '#{file}':\n#{e.full_message(highlight: false)}" end end end @@ -418,7 +416,7 @@ def IRB.rc_file(ext = IRBRC_EXT) end case rc_file = @CONF[:RC_NAME_GENERATOR].call(ext) when String - return rc_file + rc_file else fail IllegalRCNameGenerator end diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb index b6a8f5529..7d11b5b50 100644 --- a/test/irb/test_init.rb +++ b/test/irb/test_init.rb @@ -218,4 +218,44 @@ def with_argv(argv) ARGV.replace(orig) end end + + class InitIntegrationTest < IntegrationTestCase + def test_load_error_in_rc_file_is_warned + write_rc <<~'IRBRC' + require "file_that_does_not_exist" + IRBRC + + write_ruby <<~'RUBY' + binding.irb + RUBY + + output = run_ruby_file do + type "'foobar'" + type "exit" + end + + # IRB session should still be started + assert_includes output, "foobar" + assert_includes output, 'cannot load such file -- file_that_does_not_exist (LoadError)' + end + + def test_normal_errors_in_rc_file_is_warned + write_rc <<~'IRBRC' + raise "I'm an error" + IRBRC + + write_ruby <<~'RUBY' + binding.irb + RUBY + + output = run_ruby_file do + type "'foobar'" + type "exit" + end + + # IRB session should still be started + assert_includes output, "foobar" + assert_includes output, 'I\'m an error (RuntimeError)' + end + end end From b1cd53cbf7d5f220a076c9dd8807ea4135b9b086 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 18 Dec 2023 17:26:43 -0600 Subject: [PATCH 036/263] [DOC] Change indexes.rdoc to indexes.md (#812) * Change indexes.rdoc to indexes.md * Change indexes.rdoc to indexes.md * Change indexes.rdoc to indexes.md --- .document | 2 +- doc/irb/indexes.md | 192 +++++++++++++++++++++++++++++++++++++++++++ doc/irb/indexes.rdoc | 190 ------------------------------------------ lib/irb.rb | 4 +- 4 files changed, 195 insertions(+), 193 deletions(-) create mode 100644 doc/irb/indexes.md delete mode 100644 doc/irb/indexes.rdoc diff --git a/.document b/.document index 9102e0963..ab607a7f1 100644 --- a/.document +++ b/.document @@ -1,4 +1,4 @@ LICENSE.txt README.md -doc/irb/indexes.rdoc +doc/irb/indexes.md lib/**/*.rb diff --git a/doc/irb/indexes.md b/doc/irb/indexes.md new file mode 100644 index 000000000..9659db8c0 --- /dev/null +++ b/doc/irb/indexes.md @@ -0,0 +1,192 @@ +## Indexes + +### Index of Command-Line Options + +These are the \IRB command-line options, with links to explanatory text: + +- `-d`: Set `$DEBUG` and {$VERBOSE}[rdoc-ref:IRB@Verbosity] + to `true`. +- `-E _ex_[:_in_]`: Set initial external (ex) and internal (in) + {encodings}[rdoc-ref:IRB@Encodings] (same as `ruby -E>`). +- `-f`: Don't initialize from {configuration file}[rdoc-ref:IRB@Configuration+File]. +- `-I _dirpath_`: Specify {$LOAD_PATH directory}[rdoc-ref:IRB@Load+Modules] + (same as `ruby -I`). +- `-r _load-module_`: Require {load-module}[rdoc-ref:IRB@Load+Modules] + (same as `ruby -r`). +- `-U`: Set external and internal {encodings}[rdoc-ref:IRB@Encodings] to UTF-8. +- `-w`: Suppress {warnings}[rdoc-ref:IRB@Warnings] (same as `ruby -w`). +- `-W[_level_]`: Set {warning level}[rdoc-ref:IRB@Warnings]; + 0=silence, 1=medium, 2=verbose (same as `ruby -W`). +- `--autocomplete`: Use {auto-completion}[rdoc-ref:IRB@Automatic+Completion]. +- `--back-trace-limit _n_`: Set a {backtrace limit}[rdoc-ref:IRB@Tracer]; + display at most the top `n` and bottom `n` entries. +- `--colorize`: Use {color-highlighting}[rdoc-ref:IRB@Color+Highlighting] + for input and output. +- `--context-mode _n_`: Select method to create Binding object + for new {workspace}[rdoc-ref:IRB@Commands]; `n` in range `0..4`. +- `--echo`: Print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + return values. +- `--extra-doc-dir _dirpath_`: + Add a {documentation directory}[rdoc-ref:IRB@RI+Documentation+Directories] + for the documentation dialog. +- `--inf-ruby-mode`: Set prompt mode to {:INF_RUBY}[rdoc-ref:IRB@Pre-Defined+Prompts] + (appropriate for `inf-ruby-mode` on Emacs); + suppresses --multiline and --singleline. +- `--inspect`: Use method `inspect` for printing ({echoing}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + return values. +- `--multiline`: Use the multiline editor as the {input method}[rdoc-ref:IRB@Input+Method]. +- `--noautocomplete`: Don't use {auto-completion}[rdoc-ref:IRB@Automatic+Completion]. +- `--nocolorize`: Don't use {color-highlighting}[rdoc-ref:IRB@Color+Highlighting] + for input and output. +- `--noecho`: Don't print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + return values. +- `--noecho-on-assignment`: Don't print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + result on assignment. +- `--noinspect`: Don't se method `inspect` for printing ({echoing}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + return values. +- `--nomultiline`: Don't use the multiline editor as the {input method}[rdoc-ref:IRB@Input+Method]. +- `--noprompt`: Don't print {prompts}[rdoc-ref:IRB@Prompt+and+Return+Formats]. +- `--noscript`: Treat the first command-line argument as a normal + {command-line argument}[rdoc-ref:IRB@Initialization+Script], + and include it in `ARGV`. +- `--nosingleline`: Don't use the singleline editor as the {input method}[rdoc-ref:IRB@Input+Method]. +- `--noverbose`Don't print {verbose}[rdoc-ref:IRB@Verbosity] details. +- `--prompt _mode_`, `--prompt-mode _mode_`: + Set {prompt and return formats}[rdoc-ref:IRB@Prompt+and+Return+Formats]; + `mode` may be a {pre-defined prompt}[rdoc-ref:IRB@Pre-Defined+Prompts] + or the name of a {custom prompt}[rdoc-ref:IRB@Custom+Prompts]. +- `--script`: Treat the first command-line argument as the path to an + {initialization script}[rdoc-ref:IRB@Initialization+Script], + and omit it from `ARGV`. +- `--simple-prompt`, `--sample-book-mode`: + Set prompt mode to {:SIMPLE}[rdoc-ref:IRB@Pre-Defined+Prompts]. +- `--singleline`: Use the singleline editor as the {input method}[rdoc-ref:IRB@Input+Method]. +- `--tracer`: Use {Tracer}[rdoc-ref:IRB@Tracer] to print a stack trace for each input command. +- `--truncate-echo-on-assignment`: Print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + truncated result on assignment. +- `--verbose`Print {verbose}[rdoc-ref:IRB@Verbosity] details. +- `-v`, `--version`: Print the {IRB version}[rdoc-ref:IRB@Version]. +- `-h`, `--help`: Print the {IRB help text}[rdoc-ref:IRB@Help]. +- `--`: Separate options from {arguments}[rdoc-ref:IRB@Command-Line+Arguments] + on the command-line. + +### Index of \IRB.conf Entries + +These are the keys for hash \IRB.conf entries, with links to explanatory text; +for each entry that is pre-defined, the initial value is given: + +- `:AP_NAME`: \IRB {application name}[rdoc-ref:IRB@Application+Name]; + initial value: `'irb'`. +- `:AT_EXIT`: Array of hooks to call + {at exit}[rdoc-ref:IRB@IRB]; + initial value: `[]`. +- `:AUTO_INDENT`: Whether {automatic indentation}[rdoc-ref:IRB@Automatic+Indentation] + is enabled; initial value: `true`. +- `:BACK_TRACE_LIMIT`: Sets the {back trace limit}[rdoc-ref:IRB@Tracer]; + initial value: `16`. +- `:COMMAND_ALIASES`: Defines input {command aliases}[rdoc-ref:IRB@Command+Aliases]; + initial value: + + { + "$": :show_source, + "@": :whereami, + } + +- `:CONTEXT_MODE`: Sets the {context mode}[rdoc-ref:IRB@Context+Mode], + the type of binding to be used when evaluating statements; + initial value: `4`. +- `:ECHO`: Whether to print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + return values; + initial value: `nil`, which would set `conf.echo` to `true`. +- `:ECHO_ON_ASSIGNMENT`: Whether to print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + return values on assignment; + initial value: `nil`, which would set `conf.echo_on_assignment` to `:truncate`. +- `:EVAL_HISTORY`: How much {evaluation history}[rdoc-ref:IRB@Evaluation+History] + is to be stored; initial value: `nil`. +- `:EXTRA_DOC_DIRS`: \Array of + {RI documentation directories}[rdoc-ref:IRB@RI+Documentation+Directories] + to be parsed for the documentation dialog; + initial value: `[]`. +- `:IGNORE_EOF`: Whether to ignore {end-of-file}[rdoc-ref:IRB@End-of-File]; + initial value: `false`. +- `:IGNORE_SIGINT`: Whether to ignore {SIGINT}[rdoc-ref:IRB@SIGINT]; + initial value: `true`. +- `:INSPECT_MODE`: Whether to use method `inspect` for printing + ({echoing}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) return values; + initial value: `true`. +- `:IRB_LIB_PATH`: The path to the {IRB library directory}[rdoc-ref:IRB@IRB+Library+Directory]; initial value: + + - `"RUBY_DIR/lib/ruby/gems/RUBY_VER_NUM/gems/irb-IRB_VER_NUM/lib/irb"`, + + where: + + - RUBY_DIR is the Ruby installation dirpath. + - RUBY_VER_NUM is the Ruby version number. + - IRB_VER_NUM is the \IRB version number. + +- `:IRB_NAME`: {IRB name}[rdoc-ref:IRB@IRB+Name]; + initial value: `'irb'`. +- `:IRB_RC`: {Configuration monitor}[rdoc-ref:IRB@Configuration+Monitor]; + initial value: `nil`. +- `:LC_MESSAGES`: {Locale}[rdoc-ref:IRB@Locale]; + initial value: IRB::Locale object. +- `:LOAD_MODULES`: deprecated. +- `:MAIN_CONTEXT`: The {context}[rdoc-ref:IRB@Session+Context] for the main \IRB session; + initial value: IRB::Context object. +- `:MEASURE`: Whether to + {measure performance}[rdoc-ref:IRB@Performance+Measurement]; + initial value: `false`. +- `:MEASURE_CALLBACKS`: Callback methods for + {performance measurement}[rdoc-ref:IRB@Performance+Measurement]; + initial value: `[]`. +- `:MEASURE_PROC`: Procs for + {performance measurement}[rdoc-ref:IRB@Performance+Measurement]; + initial value: + + { + :TIME=>#, + :STACKPROF=># + } + +- `:PROMPT`: \Hash of {defined prompts}[rdoc-ref:IRB@Prompt+and+Return+Formats]; + initial value: + + { + :NULL=>{:PROMPT_I=>nil, :PROMPT_S=>nil, :PROMPT_C=>nil, :RETURN=>"%s\n"}, + :DEFAULT=>{:PROMPT_I=>"%N(%m):%03n> ", :PROMPT_S=>"%N(%m):%03n%l ", :PROMPT_C=>"%N(%m):%03n* ", :RETURN=>"=> %s\n"}, + :CLASSIC=>{:PROMPT_I=>"%N(%m):%03n:%i> ", :PROMPT_S=>"%N(%m):%03n:%i%l ", :PROMPT_C=>"%N(%m):%03n:%i* ", :RETURN=>"%s\n"}, + :SIMPLE=>{:PROMPT_I=>">> ", :PROMPT_S=>"%l> ", :PROMPT_C=>"?> ", :RETURN=>"=> %s\n"}, + :INF_RUBY=>{:PROMPT_I=>"%N(%m):%03n> ", :PROMPT_S=>nil, :PROMPT_C=>nil, :RETURN=>"%s\n", :AUTO_INDENT=>true}, + :XMP=>{:PROMPT_I=>nil, :PROMPT_S=>nil, :PROMPT_C=>nil, :RETURN=>" ==>%s\n"} + } + +- `:PROMPT_MODE`: Name of {current prompt}[rdoc-ref:IRB@Prompt+and+Return+Formats]; + initial value: `:DEFAULT`. +- `:RC`: Whether a {configuration file}[rdoc-ref:IRB@Configuration+File] + was found and interpreted; + initial value: `true` if a configuration file was found, `false` otherwise. +- `:RC_NAME_GENERATOR`: \Proc to generate paths of potential + {configuration files}[rdoc-ref:IRB@Configuration+File]; + initial value: `=> #`. +- `:SAVE_HISTORY`: Number of commands to save in + {input command history}[rdoc-ref:IRB@Input+Command+History]; + initial value: `1000`. +- `:SINGLE_IRB`: Whether command-line option `--single-irb` was given; + initial value: `true` if the option was given, `false` otherwise. + See {Single-IRB Mode}[rdoc-ref:IRB@Single-IRB+Mode]. +- `:USE_AUTOCOMPLETE`: Whether to use + {automatic completion}[rdoc-ref:IRB@Automatic+Completion]; + initial value: `true`. +- `:USE_COLORIZE`: Whether to use + {color highlighting}[rdoc-ref:IRB@Color+Highlighting]; + initial value: `true`. +- `:USE_LOADER`: Whether to use the + {IRB loader}[rdoc-ref:IRB@IRB+Loader] for `require` and `load`; + initial value: `false`. +- `:USE_TRACER`: Whether to use the + {IRB tracer}[rdoc-ref:IRB@Tracer]; + initial value: `false`. +- `:VERBOSE`: Whether to print {verbose output}[rdoc-ref:IRB@Verbosity]; + initial value: `nil`. +- `:__MAIN__`: The main \IRB object; + initial value: `main`. diff --git a/doc/irb/indexes.rdoc b/doc/irb/indexes.rdoc deleted file mode 100644 index a0a95b4ba..000000000 --- a/doc/irb/indexes.rdoc +++ /dev/null @@ -1,190 +0,0 @@ -== Indexes - -=== Index of Command-Line Options - -These are the \IRB command-line options, with links to explanatory text: - -- -d: Set $DEBUG and {$VERBOSE}[rdoc-ref:IRB@Verbosity] - to +true+. -- -E _ex_[:_in_]: Set initial external (ex) and internal (in) - {encodings}[rdoc-ref:IRB@Encodings] (same as ruby -E>). -- -f: Don't initialize from {configuration file}[rdoc-ref:IRB@Configuration+File]. -- -I _dirpath_: Specify {$LOAD_PATH directory}[rdoc-ref:IRB@Load+Modules] - (same as ruby -I). -- -r _load-module_: Require {load-module}[rdoc-ref:IRB@Load+Modules] - (same as ruby -r). -- -U: Set external and internal {encodings}[rdoc-ref:IRB@Encodings] to UTF-8. -- -w: Suppress {warnings}[rdoc-ref:IRB@Warnings] (same as ruby -w). -- -W[_level_]: Set {warning level}[rdoc-ref:IRB@Warnings]; - 0=silence, 1=medium, 2=verbose (same as ruby -W). -- --autocomplete: Use {auto-completion}[rdoc-ref:IRB@Automatic+Completion]. -- --back-trace-limit _n_: Set a {backtrace limit}[rdoc-ref:IRB@Tracer]; - display at most the top +n+ and bottom +n+ entries. -- --colorize: Use {color-highlighting}[rdoc-ref:IRB@Color+Highlighting] - for input and output. -- --context-mode _n_: Select method to create Binding object - for new {workspace}[rdoc-ref:IRB@Commands]; +n+ in range 0..4. -- --echo: Print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) - return values. -- --extra-doc-dir _dirpath_: - Add a {documentation directory}[rdoc-ref:IRB@RI+Documentation+Directories] - for the documentation dialog. -- --inf-ruby-mode: Set prompt mode to {:INF_RUBY}[rdoc-ref:IRB@Pre-Defined+Prompts] - (appropriate for +inf-ruby-mode+ on Emacs); - suppresses --multiline and --singleline. -- --inspect: Use method +inspect+ for printing ({echoing}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) - return values. -- --multiline: Use the multiline editor as the {input method}[rdoc-ref:IRB@Input+Method]. -- --noautocomplete: Don't use {auto-completion}[rdoc-ref:IRB@Automatic+Completion]. -- --nocolorize: Don't use {color-highlighting}[rdoc-ref:IRB@Color+Highlighting] - for input and output. -- --noecho: Don't print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) - return values. -- --noecho-on-assignment: Don't print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) - result on assignment. -- --noinspect: Don't se method +inspect+ for printing ({echoing}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) - return values. -- --nomultiline: Don't use the multiline editor as the {input method}[rdoc-ref:IRB@Input+Method]. -- --noprompt: Don't print {prompts}[rdoc-ref:IRB@Prompt+and+Return+Formats]. -- --noscript: Treat the first command-line argument as a normal - {command-line argument}[rdoc-ref:IRB@Initialization+Script], - and include it in +ARGV+. -- --nosingleline: Don't use the singleline editor as the {input method}[rdoc-ref:IRB@Input+Method]. -- --noverboseDon't print {verbose}[rdoc-ref:IRB@Verbosity] details. -- --prompt _mode_, --prompt-mode _mode_: - Set {prompt and return formats}[rdoc-ref:IRB@Prompt+and+Return+Formats]; - +mode+ may be a {pre-defined prompt}[rdoc-ref:IRB@Pre-Defined+Prompts] - or the name of a {custom prompt}[rdoc-ref:IRB@Custom+Prompts]. -- --script: Treat the first command-line argument as the path to an - {initialization script}[rdoc-ref:IRB@Initialization+Script], - and omit it from +ARGV+. -- --simple-prompt, --sample-book-mode: - Set prompt mode to {:SIMPLE}[rdoc-ref:IRB@Pre-Defined+Prompts]. -- --singleline: Use the singleline editor as the {input method}[rdoc-ref:IRB@Input+Method]. -- --tracer: Use {Tracer}[rdoc-ref:IRB@Tracer] to print a stack trace for each input command. -- --truncate-echo-on-assignment: Print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) - truncated result on assignment. -- --verbosePrint {verbose}[rdoc-ref:IRB@Verbosity] details. -- -v, --version: Print the {IRB version}[rdoc-ref:IRB@Version]. -- -h, --help: Print the {IRB help text}[rdoc-ref:IRB@Help]. -- --: Separate options from {arguments}[rdoc-ref:IRB@Command-Line+Arguments] - on the command-line. - -=== Index of \IRB.conf Entries - -These are the keys for hash \IRB.conf entries, with links to explanatory text; -for each entry that is pre-defined, the initial value is given: - -- :AP_NAME: \IRB {application name}[rdoc-ref:IRB@Application+Name]; - initial value: 'irb'. -- :AT_EXIT: Array of hooks to call - {at exit}[rdoc-ref:IRB@IRB]; - initial value: []. -- :AUTO_INDENT: Whether {automatic indentation}[rdoc-ref:IRB@Automatic+Indentation] - is enabled; initial value: +true+. -- :BACK_TRACE_LIMIT: Sets the {back trace limit}[rdoc-ref:IRB@Tracer]; - initial value: +16+. -- :COMMAND_ALIASES: Defines input {command aliases}[rdoc-ref:IRB@Command+Aliases]; - initial value: - - { - "$": :show_source, - "@": :whereami, - } - -- :CONTEXT_MODE: Sets the {context mode}[rdoc-ref:IRB@Context+Mode], - the type of binding to be used when evaluating statements; - initial value: +4+. -- :ECHO: Whether to print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) - return values; - initial value: +nil+, which would set +conf.echo+ to +true+. -- :ECHO_ON_ASSIGNMENT: Whether to print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) - return values on assignment; - initial value: +nil+, which would set +conf.echo_on_assignment+ to +:truncate+. -- :EVAL_HISTORY: How much {evaluation history}[rdoc-ref:IRB@Evaluation+History] - is to be stored; initial value: +nil+. -- :EXTRA_DOC_DIRS: \Array of - {RI documentation directories}[rdoc-ref:IRB@RI+Documentation+Directories] - to be parsed for the documentation dialog; - initial value: []. -- :IGNORE_EOF: Whether to ignore {end-of-file}[rdoc-ref:IRB@End-of-File]; - initial value: +false+. -- :IGNORE_SIGINT: Whether to ignore {SIGINT}[rdoc-ref:IRB@SIGINT]; - initial value: +true+. -- :INSPECT_MODE: Whether to use method +inspect+ for printing - ({echoing}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) return values; - initial value: +true+. -- :IRB_LIB_PATH: The path to the {IRB library directory}[rdoc-ref:IRB@IRB+Library+Directory]; initial value: - "RUBY_DIR/lib/ruby/gems/RUBY_VER_NUM/gems/irb-IRB_VER_NUM/lib/irb", - where: - - - RUBY_DIR is the Ruby installation dirpath. - - RUBY_VER_NUM is the Ruby version number. - - IRB_VER_NUM is the \IRB version number. - -- :IRB_NAME: {IRB name}[rdoc-ref:IRB@IRB+Name]; - initial value: 'irb'. -- :IRB_RC: {Configuration monitor}[rdoc-ref:IRB@Configuration+Monitor]; - initial value: +nil+. -- :LC_MESSAGES: {Locale}[rdoc-ref:IRB@Locale]; - initial value: IRB::Locale object. -- :LOAD_MODULES: deprecated. -- :MAIN_CONTEXT: The {context}[rdoc-ref:IRB@Session+Context] for the main \IRB session; - initial value: IRB::Context object. -- :MEASURE: Whether to - {measure performance}[rdoc-ref:IRB@Performance+Measurement]; - initial value: +false+. -- :MEASURE_CALLBACKS: Callback methods for - {performance measurement}[rdoc-ref:IRB@Performance+Measurement]; - initial value: []. -- :MEASURE_PROC: Procs for - {performance measurement}[rdoc-ref:IRB@Performance+Measurement]; - initial value: - - { - :TIME=>#, - :STACKPROF=># - } - -- :PROMPT: \Hash of {defined prompts}[rdoc-ref:IRB@Prompt+and+Return+Formats]; - initial value: - - { - :NULL=>{:PROMPT_I=>nil, :PROMPT_S=>nil, :PROMPT_C=>nil, :RETURN=>"%s\n"}, - :DEFAULT=>{:PROMPT_I=>"%N(%m):%03n> ", :PROMPT_S=>"%N(%m):%03n%l ", :PROMPT_C=>"%N(%m):%03n* ", :RETURN=>"=> %s\n"}, - :CLASSIC=>{:PROMPT_I=>"%N(%m):%03n:%i> ", :PROMPT_S=>"%N(%m):%03n:%i%l ", :PROMPT_C=>"%N(%m):%03n:%i* ", :RETURN=>"%s\n"}, - :SIMPLE=>{:PROMPT_I=>">> ", :PROMPT_S=>"%l> ", :PROMPT_C=>"?> ", :RETURN=>"=> %s\n"}, - :INF_RUBY=>{:PROMPT_I=>"%N(%m):%03n> ", :PROMPT_S=>nil, :PROMPT_C=>nil, :RETURN=>"%s\n", :AUTO_INDENT=>true}, - :XMP=>{:PROMPT_I=>nil, :PROMPT_S=>nil, :PROMPT_C=>nil, :RETURN=>" ==>%s\n"} - } - -- :PROMPT_MODE: Name of {current prompt}[rdoc-ref:IRB@Prompt+and+Return+Formats]; - initial value: +:DEFAULT+. -- :RC: Whether a {configuration file}[rdoc-ref:IRB@Configuration+File] - was found and interpreted; - initial value: +true+ if a configuration file was found, +false+ otherwise. -- :RC_NAME_GENERATOR: \Proc to generate paths of potential - {configuration files}[rdoc-ref:IRB@Configuration+File]; - initial value: => #. -- :SAVE_HISTORY: Number of commands to save in - {input command history}[rdoc-ref:IRB@Input+Command+History]; - initial value: +1000+. -- :SINGLE_IRB: Whether command-line option --single-irb was given; - initial value: +true+ if the option was given, +false+ otherwise. - See {Single-IRB Mode}[rdoc-ref:IRB@Single-IRB+Mode]. -- :USE_AUTOCOMPLETE: Whether to use - {automatic completion}[rdoc-ref:IRB@Automatic+Completion]; - initial value: +true+. -- :USE_COLORIZE: Whether to use - {color highlighting}[rdoc-ref:IRB@Color+Highlighting]; - initial value: +true+. -- :USE_LOADER: Whether to use the - {IRB loader}[rdoc-ref:IRB@IRB+Loader] for +require+ and +load+; - initial value: +false+. -- :USE_TRACER: Whether to use the - {IRB tracer}[rdoc-ref:IRB@Tracer]; - initial value: +false+. -- :VERBOSE: Whether to print {verbose output}[rdoc-ref:IRB@Verbosity]; - initial value: +nil+. -- :__MAIN__: The main \IRB object; - initial value: +main+. diff --git a/lib/irb.rb b/lib/irb.rb index a2a3a7190..006b52bec 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -100,7 +100,7 @@ # which is also displayed if you use command-line option --help. # # If you are interested in a specific option, consult the -# {index}[rdoc-ref:doc/irb/indexes.rdoc@Index+of+Command-Line+Options]. +# {index}[rdoc-ref:doc/irb/indexes.md@Index+of+Command-Line+Options]. # # ==== Command-Line Arguments # @@ -172,7 +172,7 @@ # Details of the entries' meanings are described in the relevant subsections below. # # If you are interested in a specific entry, consult the -# {index}[rdoc-ref:doc/irb/indexes.rdoc@Index+of+IRB.conf+Entries]. +# {index}[rdoc-ref:doc/irb/indexes.md@Index+of+IRB.conf+Entries]. # # === Notes on Initialization Precedence # From 7421359b92d807de37e8509bbee1717539dd2f4d Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 20 Dec 2023 10:04:53 +0000 Subject: [PATCH 037/263] Bump version to v1.11.0 (#818) --- lib/irb/version.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irb/version.rb b/lib/irb/version.rb index d47903b6c..c0be6d91e 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -5,7 +5,7 @@ # module IRB # :nodoc: - VERSION = "1.10.1" + VERSION = "1.11.0" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2023-12-05" + @LAST_UPDATE_DATE = "2023-12-19" end From 2d5a1afdf5acaf8d989fd986c8a4b7602f5e3f15 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Thu, 21 Dec 2023 14:26:23 -0600 Subject: [PATCH 038/263] Remove dead doc (#819) --- lib/irb.rb | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 006b52bec..daa0d64f2 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -705,12 +705,6 @@ # Command-line option -W[_level_] # sets warning level; 0=silence, 1=medium, 2=verbose. # -# :stopdoc: -# === Performance Measurement -# -# IRB.conf[:MEASURE] IRB.conf[:MEASURE_CALLBACKS] IRB.conf[:MEASURE_PROC] -# :startdoc: -# # == Other Features # # === Load Modules @@ -771,12 +765,6 @@ # # Note that the configuration file entry overrides the command-line options. # -# :stopdoc: -# === \Context Mode -# -# IRB.conf[:CONTEXT_MODE] -# :startdoc: -# # === \IRB Name # # You can specify a name for \IRB. @@ -815,12 +803,6 @@ # Each time the configuration is changed, # that proc is called with argument +conf+: # -# :stopdoc: -# === \Locale -# -# IRB.conf[:LC_MESSAGES] -# :startdoc: -# # === Encodings # # Command-line option -E _ex_[:_in_] From c2f90c49fb02346cb385b169d68ef7500b5ddf67 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 23 Dec 2023 22:53:04 +0900 Subject: [PATCH 039/263] Fix type completor section of README (#820) * Add missing table of content for type-based-completor * Update how to enable TypeCompletor section Add link to ruby/repl_type_completor Add how to install, (gem install, Gemfile) Add environment variable description * Fix how to enable type completor description Co-authored-by: Stan Lo * Add `group: :test` to sample Gemfile description in README --------- Co-authored-by: Stan Lo --- README.md | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bc9164c14..c6259fefb 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ The `irb` command from your shell will start the interpreter. - [Debugging with IRB](#debugging-with-irb) - [More about `debug.gem`](#more-about-debuggem) - [Advantages Over `debug.gem`'s Console](#advantages-over-debuggems-console) +- [Type Based Completion](#type-based-completion) + - [How to Enable IRB::TypeCompletor](#how-to-enable-irbtypecompletor) + - [Advantage over Default IRB::RegexpCompletor](#advantage-over-default-irbregexpcompletor) + - [Difference between Steep's Completion](#difference-between-steeps-completion) - [Configuration](#configuration) - [Environment Variables](#environment-variables) - [Documentation](#documentation) @@ -242,15 +246,33 @@ IRB's default completion `IRB::RegexpCompletor` uses Regexp. IRB has another exp ### How to Enable IRB::TypeCompletor -To enable IRB::TypeCompletor, run IRB with `--type-completor` option +Install [ruby/repl_type_completor](https://github.com/ruby/repl_type_completor/) with: +``` +$ gem install repl_type_completor +``` +Or add these lines to your project's Gemfile. +```ruby +gem 'irb' +gem 'repl_type_completor', group: [:development, :test] +``` + +Now you can use type based completion by: + +Running IRB with the `--type-completor` option ``` $ irb --type-completor ``` -Or write the code below to IRB's rc-file. + +Or writing this line to IRB's rc-file (e.g. `~/.irbrc`) ```ruby IRB.conf[:COMPLETOR] = :type # default is :regexp ``` -You also need `gem repl_type_completor` to use this feature. + +Or setting the environment variable `IRB_COMPLETOR` +```ruby +ENV['IRB_COMPLETOR'] = 'type' +IRB.start +``` To check if it's enabled, type `irb_info` into IRB and see the `Completion` section. ``` From 99b0017d75258b8074b328d3fd0688e9fba7a19b Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Mon, 1 Jan 2024 10:57:10 +0000 Subject: [PATCH 040/263] Remove redundant env cleanup in rendering test (#827) --- test/irb/yamatanooroti/test_rendering.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index b18b95bbb..a53dc83ac 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -30,7 +30,6 @@ def teardown FileUtils.rm_rf(@tmpdir) ENV['IRBRC'] = @irbrc_backup ENV['TERM'] = @original_term - ENV.delete('RELINE_TEST_PROMPT') if ENV['RELINE_TEST_PROMPT'] end def test_launch From 77ee59d026d68b450578c50ec6e6d00056927113 Mon Sep 17 00:00:00 2001 From: Sorah Fukumori Date: Mon, 1 Jan 2024 19:58:32 +0900 Subject: [PATCH 041/263] test_recovery_sigint: Ensure precondition is met (#829) * test_recovery_sigint: Ensure precondition is met test_recovery_sigint depends on its process has SIG_DEF sigaction for SIGINT. When its parent process set SIG_IGN which inherits to children, then this test fails. This patch ensures this precondition met regardless of inherited sigaction from its parent. * Add test for restoration of other SIGINT handlers Add another variant of test_recovery_sigint to ensure IRB to preserve existing SIGINT handler other than SIG_DEF. --- test/irb/test_init.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb index 7d11b5b50..036509813 100644 --- a/test/irb/test_init.rb +++ b/test/irb/test_init.rb @@ -66,10 +66,18 @@ def test_rc_file_in_subdir end end - def test_recovery_sigint + def test_sigint_restore_default pend "This test gets stuck on Solaris for unknown reason; contribution is welcome" if RUBY_PLATFORM =~ /solaris/ bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] - status = assert_in_out_err(bundle_exec + %w[-W0 -rirb -e binding.irb;loop{Process.kill("SIGINT",$$)} -- -f --], "exit\n", //, //) + # IRB should restore SIGINT handler + status = assert_in_out_err(bundle_exec + %w[-W0 -rirb -e Signal.trap("SIGINT","DEFAULT");binding.irb;loop{Process.kill("SIGINT",$$)} -- -f --], "exit\n", //, //) + Process.kill("SIGKILL", status.pid) if !status.exited? && !status.stopped? && !status.signaled? + end + + def test_sigint_restore_block + bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] + # IRB should restore SIGINT handler + status = assert_in_out_err(bundle_exec + %w[-W0 -rirb -e x=false;Signal.trap("SIGINT"){x=true};binding.irb;loop{Process.kill("SIGINT",$$);if(x);break;end} -- -f --], "exit\n", //, //) Process.kill("SIGKILL", status.pid) if !status.exited? && !status.stopped? && !status.signaled? end From 08208adb5ee150e96983590dad7c3319881a9e32 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Mon, 1 Jan 2024 22:57:11 +0900 Subject: [PATCH 042/263] Fix display_document params in noautocomplete mode (#826) * Fix display_document params in noautocomplete mode * Fix wrong preposing and target order in display_document The fixed wrong-ordered value is not used in RegexpCompletor, so this change does not affect the test. --- lib/irb/input-method.rb | 15 ++++++++------- test/irb/test_input_method.rb | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index b74974b92..7e8396376 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -291,11 +291,13 @@ def auto_indent(&block) @auto_indent_proc = block end + def retrieve_doc_namespace(matched) + preposing, _target, postposing, bind = @completion_params + @completor.doc_namespace(preposing, matched, postposing, bind: bind) + end + def show_doc_dialog_proc - doc_namespace = ->(matched) { - preposing, _target, postposing, bind = @completion_params - @completor.doc_namespace(preposing, matched, postposing, bind: bind) - } + input_method = self # self is changed in the lambda below. ->() { dialog.trap_key = nil alt_d = [ @@ -311,7 +313,7 @@ def show_doc_dialog_proc cursor_pos_to_render, result, pointer, autocomplete_dialog = context.pop(4) return nil if result.nil? or pointer.nil? or pointer < 0 - name = doc_namespace.call(result[pointer]) + name = input_method.retrieve_doc_namespace(result[pointer]) # Use first one because document dialog does not support multiple namespaces. name = name.first if name.is_a?(Array) @@ -419,8 +421,7 @@ def display_document(matched, driver: nil) return end - _target, preposing, postposing, bind = @completion_params - namespace = @completor.doc_namespace(preposing, matched, postposing, bind: bind) + namespace = retrieve_doc_namespace(matched) return unless namespace driver ||= RDoc::RI::Driver.new diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb index e6a1b06e8..6974fe2df 100644 --- a/test/irb/test_input_method.rb +++ b/test/irb/test_input_method.rb @@ -90,7 +90,7 @@ def setup def display_document(target, bind) input_method = IRB::RelineInputMethod.new(IRB::RegexpCompletor.new) - input_method.instance_variable_set(:@completion_params, [target, '', '', bind]) + input_method.instance_variable_set(:@completion_params, ['', target, '', bind]) input_method.display_document(target, driver: @driver) end From 5843616c78ba9d2798d58f05543a5fe8c749d093 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Mon, 1 Jan 2024 17:40:35 +0000 Subject: [PATCH 043/263] Make show_source resolve top-level constant names (#831) --- lib/irb/source_finder.rb | 2 +- test/irb/cmd/test_show_source.rb | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/irb/source_finder.rb b/lib/irb/source_finder.rb index 659d4200f..4e9cdc462 100644 --- a/lib/irb/source_finder.rb +++ b/lib/irb/source_finder.rb @@ -19,7 +19,7 @@ def initialize(irb_context) def find_source(signature, super_level = 0) context_binding = @irb_context.workspace.binding case signature - when /\A[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name + when /\A(::)?[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name eval(signature, context_binding) # trigger autoload base = context_binding.receiver.yield_self { |r| r.is_a?(Module) ? r : Object } file, line = base.const_source_location(signature) diff --git a/test/irb/cmd/test_show_source.rb b/test/irb/cmd/test_show_source.rb index cedec8aa6..12abae3c7 100644 --- a/test/irb/cmd/test_show_source.rb +++ b/test/irb/cmd/test_show_source.rb @@ -272,5 +272,33 @@ def foo assert_match(%r[#{@ruby_file.to_path}:2\s+def foo\r\n end], out) end + + def test_show_source_with_double_colons + write_ruby <<~RUBY + class Foo + end + + class Foo + class Bar + end + end + + binding.irb + RUBY + + out = run_ruby_file do + type "show_source ::Foo" + type "exit" + end + + assert_match(%r[#{@ruby_file.to_path}:1\s+class Foo\r\nend], out) + + out = run_ruby_file do + type "show_source ::Foo::Bar" + type "exit" + end + + assert_match(%r[#{@ruby_file.to_path}:5\s+class Bar\r\n end], out) + end end end From 812dc2df7b559e8eee101295945464292bf3161e Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 3 Jan 2024 13:47:47 +0000 Subject: [PATCH 044/263] Avoid completing empty input (#832) The candidate list for empty input is all methods + all variables + all constants + all keywords. It's a long list that is not useful. --- lib/irb/completion.rb | 8 +++++++- test/irb/test_completion.rb | 7 +++++++ test/irb/yamatanooroti/test_rendering.rb | 6 ++++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index af3b69eb2..7b9c1bbbd 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -406,7 +406,13 @@ def retrieve_completion_data(input, bind:, doc_namespace:) else select_message(receiver, message, candidates.sort) end - + when /^\s*$/ + # empty input + if doc_namespace + nil + else + [] + end else if doc_namespace vars = (bind.local_variables | bind.eval_instance_variables).collect{|m| m.to_s} diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb index 0625c9697..348cd4b6d 100644 --- a/test/irb/test_completion.rb +++ b/test/irb/test_completion.rb @@ -204,6 +204,13 @@ def test_complete_constants end end + def test_not_completing_empty_string + assert_equal([], completion_candidates("", binding)) + assert_equal([], completion_candidates(" ", binding)) + assert_equal([], completion_candidates("\t", binding)) + assert_equal(nil, doc_namespace("", binding)) + end + def test_complete_symbol symbols = %w"UTF-16LE UTF-7".map do |enc| "K".force_encoding(enc).to_sym diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index a53dc83ac..f698466e4 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -232,7 +232,8 @@ def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_right puts 'start IRB' LINES start_terminal(4, 19, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') - write("IR\C-i") + write("IR") + write("\C-i") close # This is because on macOS we display different shortcut for displaying the full doc @@ -268,7 +269,8 @@ def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_left puts 'start IRB' LINES start_terminal(4, 12, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') - write("IR\C-i") + write("IR") + write("\C-i") close assert_screen(<<~EOC) start IRB From eff8d0d46a81d86956b930cd638c6f5454c5f00a Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 6 Jan 2024 12:16:08 +0000 Subject: [PATCH 045/263] Require Reline v0.4.2+ (#834) --- irb.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irb.gemspec b/irb.gemspec index a008a39f9..6ed327a27 100644 --- a/irb.gemspec +++ b/irb.gemspec @@ -41,6 +41,6 @@ Gem::Specification.new do |spec| spec.required_ruby_version = Gem::Requirement.new(">= 2.7") - spec.add_dependency "reline", ">= 0.3.8" + spec.add_dependency "reline", ">= 0.4.2" spec.add_dependency "rdoc" end From 452b543a6565e9175938a9fca4a4b1dd84795c51 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 6 Jan 2024 17:15:12 +0000 Subject: [PATCH 046/263] Refactor exit command (#835) * Remove unnecessary code from the exit command's implementation 1. The parameters of `IRB.irb_exit` were never used. But there are some libraries seem to call it with arguments + it's declared on the top-level IRB constant. So I changed the params to anonymous splat instead of removing them. 2. `Context#exit` was completely unnecessary as `IRB.irb_exit` doesn't use the `@irb` instance it passes. And since it's (or should be treated as) a private method, I simply removed it. 3. The `exit` command doesn't use the status argument it receives at all. But to avoid raising errors on usages like `exit 1`, I changed the argument to anonymous splat instead removing it. * Make exit an actual command * Update readme --- README.md | 21 +++++++++++++-------- lib/irb.rb | 4 ++-- lib/irb/cmd/exit.rb | 22 ++++++++++++++++++++++ lib/irb/context.rb | 8 -------- lib/irb/extend-command.rb | 18 ++++++------------ 5 files changed, 43 insertions(+), 30 deletions(-) create mode 100644 lib/irb/cmd/exit.rb diff --git a/README.md b/README.md index c6259fefb..d6fbe9e39 100644 --- a/README.md +++ b/README.md @@ -109,15 +109,9 @@ Hello World The following commands are available on IRB. You can get the same output from the `show_cmds` command. -``` -Workspace - cwws Show the current workspace. - chws Change the current workspace to an object. - workspaces Show workspaces. - pushws Push an object to the workspace stack. - popws Pop a workspace from the workspace stack. - +```txt IRB + exit Exit the current irb session. irb_load Load a Ruby file. irb_require Require a Ruby file. source Loads a given file in the current session. @@ -125,6 +119,13 @@ 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 + cwws Show the current workspace. + chws Change the current workspace to an object. + workspaces Show workspaces. + pushws Push an object to the workspace stack. + popws Pop a workspace from the workspace stack. + Multi-irb (DEPRECATED) irb Start a child IRB. jobs List of current sessions. @@ -153,6 +154,10 @@ Context ls Show methods, constants, and variables. `-g [query]` or `-G [query]` allows you to filter out the output. show_source Show the source code of a given method or constant. whereami Show the source code around binding.irb again. + +Aliases + $ Alias for `show_source` + @ Alias for `whereami` ``` ## Debugging with IRB diff --git a/lib/irb.rb b/lib/irb.rb index daa0d64f2..4de8dda07 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -886,8 +886,8 @@ def IRB.start(ap_path = nil) end # Quits irb - def IRB.irb_exit(irb, ret) - throw :IRB_EXIT, ret + def IRB.irb_exit(*) + throw :IRB_EXIT end # Aborts then interrupts irb. diff --git a/lib/irb/cmd/exit.rb b/lib/irb/cmd/exit.rb new file mode 100644 index 000000000..415e55533 --- /dev/null +++ b/lib/irb/cmd/exit.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require_relative "nop" + +module IRB + # :stopdoc: + + module ExtendCommand + class Exit < Nop + 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/context.rb b/lib/irb/context.rb index c3690fcac..10dab8617 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -573,14 +573,6 @@ def inspect_last_value # :nodoc: @inspect_method.inspect_value(@last_value) end - alias __exit__ exit - # Exits the current session, see IRB.irb_exit - def exit(ret = 0) - IRB.irb_exit(@irb, ret) - rescue UncaughtThrowError - super - end - NOPRINTING_IVARS = ["@last_value"] # :nodoc: NO_INSPECTING_IVARS = ["@irb", "@io"] # :nodoc: IDNAME_IVARS = ["@prompt_mode"] # :nodoc: diff --git a/lib/irb/extend-command.rb b/lib/irb/extend-command.rb index 072069d4c..69d83080d 100644 --- a/lib/irb/extend-command.rb +++ b/lib/irb/extend-command.rb @@ -16,15 +16,6 @@ module ExtendCommandBundle # See #install_alias_method. OVERRIDE_ALL = 0x02 - # Quits the current irb context - # - # +ret+ is the optional signal or message to send to Context#exit - # - # Same as IRB.CurrentContext.exit. - def irb_exit(ret = 0) - irb_context.exit(ret) - end - # Displays current configuration. # # Modifying the configuration is achieved by sending a message to IRB.conf. @@ -35,13 +26,16 @@ def irb_context @ALIASES = [ [:context, :irb_context, NO_OVERRIDE], [:conf, :irb_context, NO_OVERRIDE], - [:irb_quit, :irb_exit, OVERRIDE_PRIVATE_ONLY], - [:exit, :irb_exit, OVERRIDE_PRIVATE_ONLY], - [:quit, :irb_exit, OVERRIDE_PRIVATE_ONLY], ] @EXTEND_COMMANDS = [ + [ + :irb_exit, :Exit, "cmd/exit", + [:exit, OVERRIDE_PRIVATE_ONLY], + [:quit, OVERRIDE_PRIVATE_ONLY], + [:irb_quit, OVERRIDE_PRIVATE_ONLY], + ], [ :irb_current_working_workspace, :CurrentWorkingWorkspace, "cmd/chws", [:cwws, NO_OVERRIDE], From 73b35bb7f488aafee9881ba0328e643b22c4ef07 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Mon, 8 Jan 2024 12:42:35 +0000 Subject: [PATCH 047/263] Make SourceFinder ignore binary sources (#836) --- lib/irb/source_finder.rb | 3 ++- test/irb/cmd/test_show_source.rb | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/irb/source_finder.rb b/lib/irb/source_finder.rb index 4e9cdc462..ad9ee2102 100644 --- a/lib/irb/source_finder.rb +++ b/lib/irb/source_finder.rb @@ -34,7 +34,8 @@ def find_source(signature, super_level = 0) return unless receiver.respond_to?(method, true) file, line = method_target(receiver, super_level, method, "receiver") end - if file && line && File.exist?(file) + # If the line is zero, it means that the target's source is probably in a binary file, which we should ignore. + if file && line && !line.zero? && File.exist?(file) Source.new(file: file, first_line: line, last_line: find_end(file, line)) end end diff --git a/test/irb/cmd/test_show_source.rb b/test/irb/cmd/test_show_source.rb index 12abae3c7..c2608926e 100644 --- a/test/irb/cmd/test_show_source.rb +++ b/test/irb/cmd/test_show_source.rb @@ -300,5 +300,24 @@ class Bar assert_match(%r[#{@ruby_file.to_path}:5\s+class Bar\r\n end], out) end + + def test_show_source_ignores_binary_source_file + write_ruby <<~RUBY + # io-console is an indirect dependency of irb + require "io/console" + + binding.irb + RUBY + + out = run_ruby_file do + # IO::ConsoleMode is defined in io-console gem's C extension + type "show_source IO::ConsoleMode" + type "exit" + end + + # A safeguard to make sure the test subject is actually defined + refute_match(/NameError/, out) + assert_match(%r[Error: Couldn't locate a definition for IO::ConsoleMode], out) + end end end From f052097c4b9df6b4014b1204fdac271d0473cf13 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 9 Jan 2024 13:20:08 +0000 Subject: [PATCH 048/263] Bump version to v1.11.1 (#837) --- lib/irb/version.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irb/version.rb b/lib/irb/version.rb index c0be6d91e..f2e0e7d7f 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -5,7 +5,7 @@ # module IRB # :nodoc: - VERSION = "1.11.0" + VERSION = "1.11.1" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2023-12-19" + @LAST_UPDATE_DATE = "2024-01-08" end From 79086a9dda4730376bade13aa4ff2a9500249007 Mon Sep 17 00:00:00 2001 From: Eddie Lebow Date: Sun, 21 Jan 2024 23:35:59 -0500 Subject: [PATCH 049/263] Fix documentation typo, `niL` -> `nil` --- lib/irb.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/irb.rb b/lib/irb.rb index 4de8dda07..5ced3d98a 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -365,7 +365,7 @@ # You can change the initial behavior and suppress all echoing by: # # - Adding to the configuration file: IRB.conf[:ECHO] = false. -# (The default value for this entry is +niL+, which means the same as +true+.) +# (The default value for this entry is +nil+, which means the same as +true+.) # - Giving command-line option --noecho. # (The default is --echo.) # From ca0a3cf11eba08bd4b0edb32dd26dc1961171b46 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 23 Jan 2024 19:39:55 +0900 Subject: [PATCH 050/263] Forward-port ruby-core changes (#841) * Use version dependant library for completion test * Fixup 2e69137dbe9fd7c03dac9b8adc30a7eba3ecb10b --- test/irb/test_completion.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb index 348cd4b6d..4500fedb0 100644 --- a/test/irb/test_completion.rb +++ b/test/irb/test_completion.rb @@ -124,8 +124,9 @@ def object.to_s; raise; end end def test_complete_require_library_name_first - candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'csv", "", bind: binding) - assert_equal "'csv", candidates.first + # Test that library name is completed first with subdirectories + candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'rubygems", "", bind: binding) + assert_equal "'rubygems", candidates.first end def test_complete_require_relative From b77b23aade3aad6f948287ed08c6cd99b21c4ae8 Mon Sep 17 00:00:00 2001 From: ydah <13041216+ydah@users.noreply.github.com> Date: Thu, 25 Jan 2024 11:25:56 +0900 Subject: [PATCH 051/263] overrided ==> overridden --- test/irb/test_input_method.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb index 6974fe2df..0b52d4cbc 100644 --- a/test/irb/test_input_method.rb +++ b/test/irb/test_input_method.rb @@ -15,7 +15,7 @@ def setup def teardown IRB.conf.replace(@conf_backup) restore_encodings - # Reset Reline configuration overrided by RelineInputMethod. + # Reset Reline configuration overridden by RelineInputMethod. Reline.instance_variable_set(:@core, nil) end end From 78dea580004b34b7988c9f1a05e5f62049b5de47 Mon Sep 17 00:00:00 2001 From: ydah <13041216+ydah@users.noreply.github.com> Date: Thu, 25 Jan 2024 11:26:13 +0900 Subject: [PATCH 052/263] inifinity ==> infinity --- lib/irb/history.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/irb/history.rb b/lib/irb/history.rb index 06088adb0..50fe1ce22 100644 --- a/lib/irb/history.rb +++ b/lib/irb/history.rb @@ -65,7 +65,7 @@ def save_history begin hist = hist.last(num) if hist.size > num and num > 0 rescue RangeError # bignum too big to convert into `long' - # Do nothing because the bignum should be treated as inifinity + # Do nothing because the bignum should be treated as infinity end end f.puts(hist) From dbd0e368c491fc1112cce0c1877a1c00a774448d Mon Sep 17 00:00:00 2001 From: ydah <13041216+ydah@users.noreply.github.com> Date: Thu, 25 Jan 2024 11:26:33 +0900 Subject: [PATCH 053/263] recever ==> receiver --- lib/irb/nesting_parser.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/irb/nesting_parser.rb b/lib/irb/nesting_parser.rb index 3d4db8244..23eeeaf6c 100644 --- a/lib/irb/nesting_parser.rb +++ b/lib/irb/nesting_parser.rb @@ -61,17 +61,17 @@ def self.scan_opens(tokens) if args.include?(:arg) case t.event when :on_nl, :on_semicolon - # def recever.f; + # def receiver.f; body = :normal when :on_lparen - # def recever.f() + # def receiver.f() next_args << :eq else if t.event == :on_op && t.tok == '=' # def receiver.f = body = :oneliner else - # def recever.f arg + # def receiver.f arg next_args << :arg_without_paren end end From a27a511777cca96d69e157b67eb65d3d47299bd6 Mon Sep 17 00:00:00 2001 From: ydah <13041216+ydah@users.noreply.github.com> Date: Thu, 25 Jan 2024 11:27:00 +0900 Subject: [PATCH 054/263] configuation ==> configuration --- lib/irb.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/irb.rb b/lib/irb.rb index 5ced3d98a..48879a57d 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -413,7 +413,7 @@ # # By default, \IRB prefixes a newline to a multiline response. # -# You can change the initial default value by adding to the configuation file: +# You can change the initial default value by adding to the configuration file: # # IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT] = false # From 6209f06c72fda62ccfcd32eab81e45356415aa9b Mon Sep 17 00:00:00 2001 From: ydah <13041216+ydah@users.noreply.github.com> Date: Thu, 25 Jan 2024 11:27:16 +0900 Subject: [PATCH 055/263] reseting ==> resetting --- lib/irb.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/irb.rb b/lib/irb.rb index 48879a57d..b3b9e0929 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -945,7 +945,7 @@ def debug_readline(binding) # Irb#eval_input will simply return the input, and we need to pass it to the debugger. input = if IRB.conf[:SAVE_HISTORY] && context.io.support_history_saving? # Previous IRB session's history has been saved when `Irb#run` is exited - # We need to make sure the saved history is not saved again by reseting the counter + # We need to make sure the saved history is not saved again by resetting the counter context.io.reset_history_counter begin From 38ed327e1ba6288d4eb861eae95cb4557a7f722f Mon Sep 17 00:00:00 2001 From: ydah <13041216+ydah@users.noreply.github.com> Date: Thu, 25 Jan 2024 11:30:18 +0900 Subject: [PATCH 056/263] yamatanarroroti ==> yamatanooroti --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d6fbe9e39..1027f2f12 100644 --- a/README.md +++ b/README.md @@ -384,7 +384,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/i ### Run integration tests -If your changes affect component rendering, such as the autocompletion's dialog/dropdown, you may need to run IRB's integration tests, known as `yamatanarroroti`. +If your changes affect component rendering, such as the autocompletion's dialog/dropdown, you may need to run IRB's integration tests, known as `yamatanooroti`. Before running these tests, ensure that you have `libvterm` installed. If you're using Homebrew, you can install it by running: From 24c7694467be1c6788bac22a7c05fce69f19382b Mon Sep 17 00:00:00 2001 From: ydah <13041216+ydah@users.noreply.github.com> Date: Thu, 25 Jan 2024 11:34:10 +0900 Subject: [PATCH 057/263] assigment ==> assignment --- lib/irb.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/irb.rb b/lib/irb.rb index b3b9e0929..0ff87d570 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -392,7 +392,7 @@ # (The default value for this entry is +niL+, which means the same as +:truncate+.) # - Giving command-line option --noecho-on-assignment # or --echo-on-assignment. -# (The default is --truncate-echo-on-assigment.) +# (The default is --truncate-echo-on-assignment.) # # During the session, you can change the current setting # with configuration method conf.echo_on_assignment= From 2ffacaa031147115fcfd09bc4a976d4c82a02f06 Mon Sep 17 00:00:00 2001 From: ydah <13041216+ydah@users.noreply.github.com> Date: Thu, 25 Jan 2024 11:36:41 +0900 Subject: [PATCH 058/263] Synatx ==> Syntax --- lib/irb/context.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 10dab8617..ac61b765c 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -178,7 +178,7 @@ def initialize(irb, workspace = nil, input_method = nil) private def build_type_completor if RUBY_ENGINE == 'truffleruby' - # Avoid SynatxError. truffleruby does not support endless method definition yet. + # Avoid SyntaxError. truffleruby does not support endless method definition yet. warn 'TypeCompletor is not supported on TruffleRuby yet' return end From 295797ff37f3fc341280d2f7b179eff105954033 Mon Sep 17 00:00:00 2001 From: ydah <13041216+ydah@users.noreply.github.com> Date: Thu, 25 Jan 2024 11:40:12 +0900 Subject: [PATCH 059/263] diabled ==> disabled --- test/irb/test_eval_history.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/irb/test_eval_history.rb b/test/irb/test_eval_history.rb index 0f9ec4811..a6ab36fb8 100644 --- a/test/irb/test_eval_history.rb +++ b/test/irb/test_eval_history.rb @@ -30,7 +30,7 @@ def execute_lines(*lines, conf: {}, main: self, irb_path: nil) end end - def test_eval_history_is_diabled_by_default + def test_eval_history_is_disabled_by_default out, err = execute_lines( "a = 1", "__" From bbabf818c7c424f44762f2a27458c9f29c289227 Mon Sep 17 00:00:00 2001 From: Eddie Lebow <7873686+elebow@users.noreply.github.com> Date: Thu, 25 Jan 2024 05:20:44 -0500 Subject: [PATCH 060/263] Reword history file documentation and fix typo (#842) --- lib/irb.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 0ff87d570..6e3448d69 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -203,11 +203,10 @@ # # === Input Command History # -# By default, \IRB stores a history of up to 1000 input commands -# in file ~/.irb_history -# (or, if a {configuration file}[rdoc-ref:IRB@Configuration+File] -# is found, in file +.irb_history+ -# inin the same directory as that file). +# By default, \IRB stores a history of up to 1000 input commands in a +# file named .irb_history. The history file will be in the same directory +# as the {configuration file}[rdoc-ref:IRB@Configuration+File] if one is found, or +# in ~/ otherwise. # # A new \IRB session creates the history file if it does not exist, # and appends to the file if it does exist. From 343b08fe8bcdf4f8b757c6ffc36ad350cd05e95e Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 26 Jan 2024 13:40:05 +0900 Subject: [PATCH 061/263] Try to use irb instead of rubygems for completion test I'm not sure why OpenBSD suggest `rubygems_plugin` file for this. https://rubyci.s3.amazonaws.com/openbsd-current/ruby-master/log/20240126T013005Z.fail.html.gz It seems environmental issue, not irb. --- test/irb/test_completion.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb index 4500fedb0..819446393 100644 --- a/test/irb/test_completion.rb +++ b/test/irb/test_completion.rb @@ -125,8 +125,8 @@ def object.to_s; raise; end def test_complete_require_library_name_first # Test that library name is completed first with subdirectories - candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'rubygems", "", bind: binding) - assert_equal "'rubygems", candidates.first + candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'irb", "", bind: binding) + assert_equal "'irb", candidates.first end def test_complete_require_relative From a641746b18faa35ef32eec55255894fffcd4b063 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 30 Jan 2024 21:55:43 +0900 Subject: [PATCH 062/263] Fix undef and alias indent (#838) --- lib/irb/nesting_parser.rb | 10 +++++++++ lib/irb/ruby-lex.rb | 2 +- test/irb/test_nesting_parser.rb | 38 +++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/lib/irb/nesting_parser.rb b/lib/irb/nesting_parser.rb index 23eeeaf6c..5aa940cc2 100644 --- a/lib/irb/nesting_parser.rb +++ b/lib/irb/nesting_parser.rb @@ -12,6 +12,8 @@ def self.scan_opens(tokens) skip = false last_tok, state, args = opens.last case state + when :in_alias_undef + skip = t.event == :on_kw when :in_unquoted_symbol unless IGNORE_TOKENS.include?(t.event) opens.pop @@ -130,6 +132,10 @@ def self.scan_opens(tokens) opens.pop opens << [t, nil] end + when 'alias' + opens << [t, :in_alias_undef, 2] + when 'undef' + opens << [t, :in_alias_undef, 1] when 'elsif', 'else', 'when' opens.pop opens << [t, nil] @@ -174,6 +180,10 @@ def self.scan_opens(tokens) pending_heredocs.reverse_each { |t| opens << [t, nil] } pending_heredocs = [] end + if opens.last && opens.last[1] == :in_alias_undef && !IGNORE_TOKENS.include?(t.event) && t.event != :on_heredoc_end + tok, state, arg = opens.pop + opens << [tok, state, arg - 1] if arg >= 1 + end yield t, opens if block_given? end opens.map(&:first) + pending_heredocs.reverse diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb index 4bce2aa6b..754acfad0 100644 --- a/lib/irb/ruby-lex.rb +++ b/lib/irb/ruby-lex.rb @@ -290,7 +290,7 @@ def calc_indent_level(opens) when :on_embdoc_beg indent_level = 0 else - indent_level += 1 + indent_level += 1 unless t.tok == 'alias' || t.tok == 'undef' end end indent_level diff --git a/test/irb/test_nesting_parser.rb b/test/irb/test_nesting_parser.rb index ea3a23aaf..2482d4008 100644 --- a/test/irb/test_nesting_parser.rb +++ b/test/irb/test_nesting_parser.rb @@ -280,6 +280,44 @@ def test_while_until end end + def test_undef_alias + codes = [ + 'undef foo', + 'alias foo bar', + 'undef !', + 'alias + -', + 'alias $a $b', + 'undef do', + 'alias do do', + 'undef :do', + 'alias :do :do', + 'undef :"#{alias do do}"', + 'alias :"#{undef do}" do', + 'alias do :"#{undef do}"' + ] + code_with_comment = <<~EOS + undef # + # + do # + alias # + # + do # + # + do # + EOS + code_with_heredoc = <<~EOS + <<~A; alias + A + :"#{<<~A}" + A + do + EOS + [*codes, code_with_comment, code_with_heredoc].each do |code| + opens = IRB::NestingParser.open_tokens(IRB::RubyLex.ripper_lex_without_warning('(' + code + "\nif")) + assert_equal(%w[( if], opens.map(&:tok)) + end + end + def test_case_in if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.7.0') pend 'This test requires ruby version that supports case-in syntax' From bfafaa5fbc901da1f921c5486b13545e07f782b6 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 31 Jan 2024 23:44:11 +0000 Subject: [PATCH 063/263] Omit 2 encoding error related tests for TruffleRuby (#854) They're failing due to an issue in Prism: https://github.com/ruby/prism/issues/2129 So we need to skip them until: - The issue is fixed in Prism - TruffleRuby is updated to a version of Prism that includes the fix --- test/irb/test_context.rb | 4 ++++ test/irb/test_ruby_lex.rb | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index 79186f13a..dad819b4c 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -39,6 +39,10 @@ def test_last_value end def test_evaluate_with_encoding_error_without_lineno + if RUBY_ENGINE == 'truffleruby' + omit "Remove me after https://github.com/ruby/prism/issues/2129 is addressed and adopted in TruffleRuby" + end + assert_raise_with_message(EncodingError, /invalid symbol/) { @context.evaluate(%q[:"\xAE"], 1) # The backtrace of this invalid encoding hash doesn't contain lineno. diff --git a/test/irb/test_ruby_lex.rb b/test/irb/test_ruby_lex.rb index 5cfd81dbe..4e406a8ce 100644 --- a/test/irb/test_ruby_lex.rb +++ b/test/irb/test_ruby_lex.rb @@ -221,6 +221,10 @@ def assert_should_continue(lines, expected, local_variables: []) end def assert_code_block_open(lines, expected, local_variables: []) + if RUBY_ENGINE == 'truffleruby' + omit "Remove me after https://github.com/ruby/prism/issues/2129 is addressed and adopted in TruffleRuby" + end + _indent_level, _continue, code_block_open = check_state(lines, local_variables: local_variables) error_message = "Wrong result of code_block_open for:\n #{lines.join("\n")}" assert_equal(expected, code_block_open, error_message) From 06b2d00dd3ceed2a38a0c2c8af890facf87ef649 Mon Sep 17 00:00:00 2001 From: Nuno Silva Date: Thu, 1 Feb 2024 12:12:01 +0000 Subject: [PATCH 064/263] Skip re-setup when creating a child session (#850) --- lib/irb.rb | 2 +- lib/irb/init.rb | 6 ++++ test/irb/yamatanooroti/test_rendering.rb | 36 ++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/lib/irb.rb b/lib/irb.rb index 6e3448d69..f3abed820 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1514,7 +1514,7 @@ class Binding # See IRB for more information. def irb(show_code: true) # Setup IRB with the current file's path and no command line arguments - IRB.setup(source_location[0], argv: []) + IRB.setup(source_location[0], argv: []) unless IRB.initialized? # Create a new workspace using the current binding workspace = IRB::WorkSpace.new(self) # Print the code around the binding if show_code is true diff --git a/lib/irb/init.rb b/lib/irb/init.rb index 66e7b6146..a434ab23e 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -6,6 +6,7 @@ module IRB # :nodoc: @CONF = {} + @INITIALIZED = false # Displays current configuration. # # Modifying the configuration is achieved by sending a message to IRB.conf. @@ -41,6 +42,10 @@ def IRB.version format("irb %s (%s)", @RELEASE_VERSION, @LAST_UPDATE_DATE) end + def IRB.initialized? + !!@INITIALIZED + end + # initialize config def IRB.setup(ap_path, argv: ::ARGV) IRB.init_config(ap_path) @@ -52,6 +57,7 @@ def IRB.setup(ap_path, argv: ::ARGV) unless @CONF[:PROMPT][@CONF[:PROMPT_MODE]] fail UndefinedPromptMode, @CONF[:PROMPT_MODE] end + @INITIALIZED = true end # @CONF default setting diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index f698466e4..e121b302c 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -49,6 +49,42 @@ def test_launch EOC end + def test_configuration_file_is_skipped_with_dash_f + write_irbrc <<~'LINES' + puts '.irbrc file should be ignored when -f is used' + LINES + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb -f}, startup_message: '') + write(<<~EOC) + 'Hello, World!' + EOC + close + assert_screen(<<~EOC) + irb(main):001> 'Hello, World!' + => "Hello, World!" + irb(main):002> + EOC + end + + def test_configuration_file_is_skipped_with_dash_f_for_nested_sessions + write_irbrc <<~'LINES' + puts '.irbrc file should be ignored when -f is used' + LINES + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb -f}, startup_message: '') + write(<<~EOC) + 'Hello, World!' + binding.irb + exit! + EOC + close + assert_screen(<<~EOC) + irb(main):001> 'Hello, World!' + => "Hello, World!" + irb(main):002> binding.irb + irb(main):003> exit! + irb(main):001> + EOC + end + def test_nomultiline write_irbrc <<~'LINES' puts 'start IRB' From 4afc98c25845d80ab9ae157f78f0b63dff0fe3f6 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 1 Feb 2024 16:19:03 +0000 Subject: [PATCH 065/263] Reset history counter even when @loaded_history_lines is not defined (#853) The issue (https://github.com/ruby/debug/issues/1064) is caused by a combination of factors: 1. When user starts an IRB session without a history file, the `@loaded_history_lines` ivar is not defined. 2. If the user then starts the `irb:rdbg` session, the history counter is not set, because the `@loaded_history_lines` is not defined. 3. Because `irb:rdbg` saves the history before passing Ruby expression to the debugger, it saves the history with duplicated lines. The number grows in exponential order. 4. When the user exits the `irb:rdbg` session, the history file could be bloated with duplicated lines. This commit fixes the issue by resetting the history counter even when `@loaded_history_lines` is not defined. --- lib/irb/history.rb | 2 +- test/irb/test_history.rb | 51 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/lib/irb/history.rb b/lib/irb/history.rb index 50fe1ce22..90aa9f0bc 100644 --- a/lib/irb/history.rb +++ b/lib/irb/history.rb @@ -5,7 +5,7 @@ def support_history_saving? end def reset_history_counter - @loaded_history_lines = self.class::HISTORY.size if defined? @loaded_history_lines + @loaded_history_lines = self.class::HISTORY.size end def load_history diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index f7ba2b9d3..fef42b498 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -251,10 +251,6 @@ def with_temp_stdio class IRBHistoryIntegrationTest < IntegrationTestCase def test_history_saving_with_debug - if ruby_core? - omit "This test works only under ruby/irb" - end - write_history "" write_ruby <<~'RUBY' @@ -293,6 +289,53 @@ def foo HISTORY end + def test_history_saving_with_debug_without_prior_history + tmpdir = Dir.mktmpdir("test_irb_history_") + # Intentionally not creating the file so we test the reset counter logic + history_file = File.join(tmpdir, "irb_history") + + write_rc <<~RUBY + IRB.conf[:HISTORY_FILE] = "#{history_file}" + RUBY + + write_ruby <<~'RUBY' + def foo + end + + binding.irb + + foo + RUBY + + output = run_ruby_file do + type "'irb session'" + type "next" + type "'irb:debug session'" + type "step" + type "irb_info" + type "puts Reline::HISTORY.to_a.to_s" + type "q!" + end + + assert_include(output, "InputMethod: RelineInputMethod") + # check that in-memory history is preserved across sessions + assert_include output, %q( + ["'irb session'", "next", "'irb:debug session'", "step", "irb_info", "puts Reline::HISTORY.to_a.to_s"] + ).strip + + assert_equal <<~HISTORY, File.read(history_file) + 'irb session' + next + 'irb:debug session' + step + irb_info + puts Reline::HISTORY.to_a.to_s + q! + HISTORY + ensure + FileUtils.rm_rf(tmpdir) + end + def test_history_saving_with_nested_sessions write_history "" From 4f60cd88bb25e18ec2bc28085252b84525c656ac Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 1 Feb 2024 17:45:54 +0000 Subject: [PATCH 066/263] Add rubocop with a few basic styling rules (#849) * Use rubocop to enforce a few styling rules * Add a CI job for linting --- .github/workflows/test.yml | 12 +++++++++++- .rubocop.yml | 32 ++++++++++++++++++++++++++++++++ Gemfile | 2 ++ lib/irb/inspector.rb | 2 +- lib/irb/locale.rb | 2 +- test/irb/test_input_method.rb | 1 - test/irb/test_workspace.rb | 2 +- 7 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 .rubocop.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 313c6d672..bab1fb84f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,17 @@ jobs: with: engine: cruby-truffleruby min_version: 2.7 - + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.3" + bundler-cache: true + - name: Run rubocop + run: bundle exec rubocop irb: needs: ruby-versions name: rake test ${{ matrix.ruby }} ${{ matrix.with_latest_reline && '(latest reline)' || '' }} diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 000000000..22d51af71 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,32 @@ +AllCops: + TargetRubyVersion: 3.0 + DisabledByDefault: true + SuggestExtensions: false + +Layout/TrailingWhitespace: + Enabled: true + +Layout/TrailingEmptyLines: + Enabled: true + +Layout/IndentationConsistency: + Enabled: true + +Layout/CaseIndentation: + Enabled: true + EnforcedStyle: end + +Layout/CommentIndentation: + Enabled: true + +Layout/IndentationStyle: + Enabled: true + +Layout/SpaceAroundKeyword: + Enabled: true + +Layout/SpaceBeforeComma: + Enabled: true + +Layout/SpaceAfterComma: + Enabled: true diff --git a/Gemfile b/Gemfile index 0683d3270..f4b670cb0 100644 --- a/Gemfile +++ b/Gemfile @@ -17,6 +17,8 @@ gem "rake" gem "test-unit" gem "test-unit-ruby-core" +gem "rubocop" + gem "debug", github: "ruby/debug", platforms: [:mri, :mswin] if RUBY_VERSION >= "3.0.0" && !is_truffleruby diff --git a/lib/irb/inspector.rb b/lib/irb/inspector.rb index ee3b19efd..7bdd855b9 100644 --- a/lib/irb/inspector.rb +++ b/lib/irb/inspector.rb @@ -46,7 +46,7 @@ class Inspector # Determines the inspector to use where +inspector+ is one of the keys passed # during inspector definition. def self.keys_with_inspector(inspector) - INSPECTORS.select{|k,v| v == inspector}.collect{|k, v| k} + INSPECTORS.select{|k, v| v == inspector}.collect{|k, v| k} end # Example diff --git a/lib/irb/locale.rb b/lib/irb/locale.rb index f94aa0f40..1510af2ab 100644 --- a/lib/irb/locale.rb +++ b/lib/irb/locale.rb @@ -94,7 +94,7 @@ def load(file) end end - def find(file , paths = $:) + def find(file, paths = $:) dir = File.dirname(file) dir = "" if dir == "." base = File.basename(file) diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb index 0b52d4cbc..7644d3176 100644 --- a/test/irb/test_input_method.rb +++ b/test/irb/test_input_method.rb @@ -170,4 +170,3 @@ def has_rdoc_content? end end end - diff --git a/test/irb/test_workspace.rb b/test/irb/test_workspace.rb index ff17b1a22..8c7310578 100644 --- a/test/irb/test_workspace.rb +++ b/test/irb/test_workspace.rb @@ -91,7 +91,7 @@ def test_toplevel_binding_local_variables irb_path = "#{top_srcdir}/#{dir}/irb" File.exist?(irb_path) end or omit 'irb command not found' - assert_in_out_err(bundle_exec + ['-W0', "-C#{top_srcdir}", '-e', <<~RUBY , '--', '-f', '--'], 'binding.local_variables', /\[:_\]/, [], bug17623) + assert_in_out_err(bundle_exec + ['-W0', "-C#{top_srcdir}", '-e', <<~RUBY, '--', '-f', '--'], 'binding.local_variables', /\[:_\]/, [], bug17623) version = 'xyz' # typical rubygems loading file load('#{irb_path}') RUBY From 9e6fa67212ac02850c680e72b3a01200f1f9688a Mon Sep 17 00:00:00 2001 From: Ignacio Chiazzo Cardarello Date: Fri, 2 Feb 2024 18:58:19 -0300 Subject: [PATCH 067/263] Add a warning for when the history path doesn't exist (#852) * Add a warning for when the history path doesn't exist * warn when the directory does not exist * added test for when the history_file does not exist * Update lib/irb/history.rb --------- Co-authored-by: Stan Lo --- lib/irb/history.rb | 6 ++++++ test/irb/test_history.rb | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/lib/irb/history.rb b/lib/irb/history.rb index 90aa9f0bc..ad17347fb 100644 --- a/lib/irb/history.rb +++ b/lib/irb/history.rb @@ -59,6 +59,12 @@ def save_history append_history = true end + pathname = Pathname.new(history_file) + unless Dir.exist?(pathname.dirname) + warn "Warning: The directory to save IRB's history file does not exist. Please double check `IRB.conf[:HISTORY_FILE]`'s value." + return + end + File.open(history_file, (append_history ? 'a' : 'w'), 0o600, encoding: IRB.conf[:LC_MESSAGES]&.encoding) do |f| hist = history.map{ |l| l.scrub.split("\n").join("\\\n") } unless append_history diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index fef42b498..2c762ae46 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -167,6 +167,19 @@ def test_history_different_encodings $VERBOSE = verbose_bak end + def test_history_does_not_raise_when_history_file_directory_does_not_exist + backup_history_file = IRB.conf[:HISTORY_FILE] + IRB.conf[:SAVE_HISTORY] = 1 + IRB.conf[:HISTORY_FILE] = "fake/fake/fake/history_file" + io = TestInputMethodWithRelineHistory.new + + assert_nothing_raised do + io.save_history + end + ensure + IRB.conf[:HISTORY_FILE] = backup_history_file + end + private def history_concurrent_use_for_input_method(input_method) From 0ab96ed426a54d80f21f8ef1607f7e88c177072b Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 2 Feb 2024 22:25:30 +0000 Subject: [PATCH 068/263] Require pathname (#860) --- lib/irb/history.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/irb/history.rb b/lib/irb/history.rb index ad17347fb..dffdee32d 100644 --- a/lib/irb/history.rb +++ b/lib/irb/history.rb @@ -1,3 +1,5 @@ +require "pathname" + module IRB module HistorySavingAbility # :nodoc: def support_history_saving? From 9a7e060e570e580af48451da84e155d40496b4ca Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 3 Feb 2024 15:46:21 +0900 Subject: [PATCH 069/263] Consume the warning for non-existent history path Fix https://github.com/ruby/irb/pull/852#issuecomment-1925170358 --- test/irb/test_history.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index 2c762ae46..e6448ada3 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -173,7 +173,7 @@ def test_history_does_not_raise_when_history_file_directory_does_not_exist IRB.conf[:HISTORY_FILE] = "fake/fake/fake/history_file" io = TestInputMethodWithRelineHistory.new - assert_nothing_raised do + assert_warn(/history file does not exist/) do io.save_history end ensure From 1b11fd9113493b0fca049ece423227921caecefa Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 3 Feb 2024 15:46:55 +0900 Subject: [PATCH 070/263] Remove unused variable --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 01bd83cb2..b29bf6387 100644 --- a/Rakefile +++ b/Rakefile @@ -17,7 +17,7 @@ task :test_in_isolation do ENV["TEST"] = test_file begin Rake::Task["test"].execute - rescue => e + rescue failed = true msg = "Test '#{test_file}' failed when being executed in isolation. Please make sure 'rake test TEST=#{test_file}' passes." separation_line = '=' * msg.length From af1d83be6a2a7d1e49a3a123015a591b4a58ea17 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 19:43:48 +0000 Subject: [PATCH 071/263] Bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bab1fb84f..37ea47a91 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: From 08834fbd5f7167b029c6d9f22e109f00eebd3119 Mon Sep 17 00:00:00 2001 From: Nuno Silva Date: Tue, 6 Feb 2024 16:46:41 +0000 Subject: [PATCH 072/263] Fix usage of tracer gem and add tests (#857) The new tests are skipped when ruby below 3.1, as it was a default gem on it, and in a version we do not support. This also move definition of `use_tracer` to module Context instead of monkey patch. --- .github/workflows/test.yml | 4 +- Gemfile | 1 + lib/irb/context.rb | 5 ++ lib/irb/ext/tracer.rb | 60 +++-------------------- lib/irb/extend-command.rb | 1 - test/irb/test_context.rb | 2 +- test/irb/test_init.rb | 6 +++ test/irb/test_tracer.rb | 97 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 120 insertions(+), 56 deletions(-) create mode 100644 test/irb/test_tracer.rb diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 37ea47a91..6010039a3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,17 +25,19 @@ jobs: run: bundle exec rubocop irb: needs: ruby-versions - name: rake test ${{ matrix.ruby }} ${{ matrix.with_latest_reline && '(latest reline)' || '' }} + name: rake test ${{ matrix.ruby }} ${{ matrix.with_latest_reline && '(latest reline)' || '' }} ${{ matrix.with_tracer && '(with tracer)' || '' }} strategy: matrix: ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} with_latest_reline: [true, false] + with_tracer: [true, false] exclude: - ruby: truffleruby fail-fast: false runs-on: ubuntu-latest env: WITH_LATEST_RELINE: ${{matrix.with_latest_reline}} + WITH_TRACER: ${{matrix.with_tracer}} timeout-minutes: 30 steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 diff --git a/Gemfile b/Gemfile index f4b670cb0..149268561 100644 --- a/Gemfile +++ b/Gemfile @@ -19,6 +19,7 @@ gem "test-unit-ruby-core" gem "rubocop" +gem "tracer" if ENV["WITH_TRACER"] == "true" gem "debug", github: "ruby/debug", platforms: [:mri, :mswin] if RUBY_VERSION >= "3.0.0" && !is_truffleruby diff --git a/lib/irb/context.rb b/lib/irb/context.rb index ac61b765c..5b8791c3b 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -161,6 +161,11 @@ def initialize(irb, workspace = nil, input_method = nil) private_constant :KEYWORD_ALIASES + def use_tracer=(val) + require_relative "ext/tracer" + @use_tracer = val + end + private def build_completor completor_type = IRB.conf[:COMPLETOR] case completor_type diff --git a/lib/irb/ext/tracer.rb b/lib/irb/ext/tracer.rb index 3eaeb70ef..53b2e6224 100644 --- a/lib/irb/ext/tracer.rb +++ b/lib/irb/ext/tracer.rb @@ -3,76 +3,30 @@ # irb/lib/tracer.rb - # by Keiju ISHITSUKA(keiju@ruby-lang.org) # - +# Loading the gem "tracer" will cause it to extend IRB commands with: +# https://github.com/ruby/tracer/blob/v0.2.2/lib/tracer/irb.rb begin require "tracer" rescue LoadError $stderr.puts "Tracer extension of IRB is enabled but tracer gem wasn't found." - module IRB - class Context - def use_tracer=(opt) - # do nothing - end - end - end return # This is about to disable loading below end module IRB - - # initialize tracing function - def IRB.initialize_tracer - Tracer.verbose = false - Tracer.add_filter { - |event, file, line, id, binding, *rests| - /^#{Regexp.quote(@CONF[:IRB_LIB_PATH])}/ !~ file and - File::basename(file) != "irb.rb" - } - end - - class Context - # Whether Tracer is used when evaluating statements in this context. - # - # See +lib/tracer.rb+ for more information. - attr_reader :use_tracer - alias use_tracer? use_tracer - - # Sets whether or not to use the Tracer library when evaluating statements - # in this context. - # - # See +lib/tracer.rb+ for more information. - def use_tracer=(opt) - if opt - Tracer.set_get_line_procs(@irb_path) { - |line_no, *rests| - @io.line(line_no) - } - elsif !opt && @use_tracer - Tracer.off - end - @use_tracer=opt - end - end - class WorkSpace alias __evaluate__ evaluate # Evaluate the context of this workspace and use the Tracer library to # output the exact lines of code are being executed in chronological order. # - # See +lib/tracer.rb+ for more information. - def evaluate(context, statements, file = nil, line = nil) - if context.use_tracer? && file != nil && line != nil - Tracer.on - begin + # See https://github.com/ruby/tracer for more information. + def evaluate(statements, file = __FILE__, line = __LINE__) + if IRB.conf[:USE_TRACER] == true + CallTracer.new(colorize: Color.colorable?).start do __evaluate__(statements, file, line) - ensure - Tracer.off end else - __evaluate__(statements, file || __FILE__, line || __LINE__) + __evaluate__(statements, file, line) end end end - - IRB.initialize_tracer end diff --git a/lib/irb/extend-command.rb b/lib/irb/extend-command.rb index 69d83080d..91ca96e91 100644 --- a/lib/irb/extend-command.rb +++ b/lib/irb/extend-command.rb @@ -317,7 +317,6 @@ module ContextExtender @EXTEND_COMMANDS = [ [:eval_history=, "ext/eval_history.rb"], - [:use_tracer=, "ext/tracer.rb"], [:use_loader=, "ext/use-loader.rb"], ] diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index dad819b4c..a76152169 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false require 'tempfile' require 'irb' -require 'rubygems' if defined?(Gem) +require 'rubygems' require_relative "helper" diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb index 036509813..64bd96d02 100644 --- a/test/irb/test_init.rb +++ b/test/irb/test_init.rb @@ -216,6 +216,12 @@ def test_dash assert_equal(['-f'], argv) end + def test_option_tracer + argv = %w[--tracer] + IRB.setup(eval("__FILE__"), argv: argv) + assert_equal(true, IRB.conf[:USE_TRACER]) + end + private def with_argv(argv) diff --git a/test/irb/test_tracer.rb b/test/irb/test_tracer.rb new file mode 100644 index 000000000..f8da066b6 --- /dev/null +++ b/test/irb/test_tracer.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: false +require 'tempfile' +require 'irb' +require 'rubygems' + +require_relative "helper" + +module TestIRB + class ContextWithTracerIntegrationTest < IntegrationTestCase + def setup + super + + @envs.merge!("NO_COLOR" => "true", "RUBY_DEBUG_HISTORY_FILE" => '') + end + + def example_ruby_file + <<~'RUBY' + class Foo + def self.foo + 100 + end + end + + def bar(obj) + obj.foo + end + + binding.irb + RUBY + end + + def test_use_tracer_is_disabled_by_default + write_rc <<~RUBY + IRB.conf[:USE_TRACER] = false + RUBY + + write_ruby example_ruby_file + + output = run_ruby_file do + type "bar(Foo)" + type "exit!" + end + + assert_nil IRB.conf[:USER_TRACER] + assert_not_include(output, "#depth:") + assert_not_include(output, "Foo.foo") + end + + def test_use_tracer_enabled_when_gem_is_unavailable + begin + gem 'tracer' + omit "Skipping because 'tracer' gem is available." + rescue Gem::LoadError + write_rc <<~RUBY + IRB.conf[:USE_TRACER] = true + RUBY + + write_ruby example_ruby_file + + output = run_ruby_file do + type "bar(Foo)" + type "exit!" + end + + assert_include(output, "Tracer extension of IRB is enabled but tracer gem wasn't found.") + end + end + + def test_use_tracer_enabled_when_gem_is_available + if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.1.0') + omit "Ruby version before 3.1.0 does not support Tracer integration. Skipping this test." + end + + begin + gem 'tracer' + rescue Gem::LoadError + omit "Skipping because 'tracer' gem is not available. Enable with WITH_TRACER=true." + end + + write_rc <<~RUBY + IRB.conf[:USE_TRACER] = true + RUBY + + write_ruby example_ruby_file + + output = run_ruby_file do + type "bar(Foo)" + type "exit!" + end + + assert_include(output, "Object#bar at") + assert_include(output, "Foo.foo at") + assert_include(output, "Foo.foo #=> 100") + assert_include(output, "Object#bar #=> 100") + end + end +end From a97a4129a710ac0d0cc5dc009c5d960a4725f905 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 7 Feb 2024 14:59:06 +0000 Subject: [PATCH 073/263] Polish tracer integration and tests (#864) * Remove useless ivar * Simplify tracer test setup * Treat tracer like a normal development dependency * Only require ext/tracer when value is truthy * Make tracer integration skip IRB traces --- .github/workflows/test.yml | 4 +-- Gemfile | 2 +- lib/irb/context.rb | 6 ++-- lib/irb/ext/tracer.rb | 7 ++++ lib/irb/init.rb | 3 -- test/irb/test_tracer.rb | 70 +++++++++++++++++--------------------- 6 files changed, 44 insertions(+), 48 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6010039a3..37ea47a91 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,19 +25,17 @@ jobs: run: bundle exec rubocop irb: needs: ruby-versions - name: rake test ${{ matrix.ruby }} ${{ matrix.with_latest_reline && '(latest reline)' || '' }} ${{ matrix.with_tracer && '(with tracer)' || '' }} + name: rake test ${{ matrix.ruby }} ${{ matrix.with_latest_reline && '(latest reline)' || '' }} strategy: matrix: ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} with_latest_reline: [true, false] - with_tracer: [true, false] exclude: - ruby: truffleruby fail-fast: false runs-on: ubuntu-latest env: WITH_LATEST_RELINE: ${{matrix.with_latest_reline}} - WITH_TRACER: ${{matrix.with_tracer}} timeout-minutes: 30 steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 diff --git a/Gemfile b/Gemfile index 149268561..3c2efa44d 100644 --- a/Gemfile +++ b/Gemfile @@ -19,7 +19,7 @@ gem "test-unit-ruby-core" gem "rubocop" -gem "tracer" if ENV["WITH_TRACER"] == "true" +gem "tracer" if !is_truffleruby gem "debug", github: "ruby/debug", platforms: [:mri, :mswin] if RUBY_VERSION >= "3.0.0" && !is_truffleruby diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 5b8791c3b..e30125f46 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -61,7 +61,7 @@ def initialize(irb, workspace = nil, input_method = nil) @io = nil self.inspect_mode = IRB.conf[:INSPECT_MODE] - self.use_tracer = IRB.conf[:USE_TRACER] if IRB.conf[:USE_TRACER] + self.use_tracer = IRB.conf[:USE_TRACER] self.use_loader = IRB.conf[:USE_LOADER] if IRB.conf[:USE_LOADER] self.eval_history = IRB.conf[:EVAL_HISTORY] if IRB.conf[:EVAL_HISTORY] @@ -162,8 +162,8 @@ def initialize(irb, workspace = nil, input_method = nil) private_constant :KEYWORD_ALIASES def use_tracer=(val) - require_relative "ext/tracer" - @use_tracer = val + require_relative "ext/tracer" if val + IRB.conf[:USE_TRACER] = val end private def build_completor diff --git a/lib/irb/ext/tracer.rb b/lib/irb/ext/tracer.rb index 53b2e6224..d498b5320 100644 --- a/lib/irb/ext/tracer.rb +++ b/lib/irb/ext/tracer.rb @@ -13,6 +13,13 @@ end module IRB + class CallTracer < ::CallTracer + IRB_DIR = File.expand_path('../..', __dir__) + + def skip?(tp) + super || tp.path.match?(IRB_DIR) || tp.path.match?('') + end + end class WorkSpace alias __evaluate__ evaluate # Evaluate the context of this workspace and use the Tracer library to diff --git a/lib/irb/init.rb b/lib/irb/init.rb index a434ab23e..8acfdea8e 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -62,9 +62,6 @@ def IRB.setup(ap_path, argv: ::ARGV) # @CONF default setting def IRB.init_config(ap_path) - # class instance variables - @TRACER_INITIALIZED = false - # default configurations unless ap_path and @CONF[:AP_NAME] ap_path = File.join(File.dirname(File.dirname(__FILE__)), "irb.rb") diff --git a/test/irb/test_tracer.rb b/test/irb/test_tracer.rb index f8da066b6..199c9586b 100644 --- a/test/irb/test_tracer.rb +++ b/test/irb/test_tracer.rb @@ -10,7 +10,9 @@ class ContextWithTracerIntegrationTest < IntegrationTestCase def setup super - @envs.merge!("NO_COLOR" => "true", "RUBY_DEBUG_HISTORY_FILE" => '') + omit "Tracer gem is not available when running on TruffleRuby" if RUBY_ENGINE == "truffleruby" + + @envs.merge!("NO_COLOR" => "true") end def example_ruby_file @@ -29,54 +31,30 @@ def bar(obj) RUBY end - def test_use_tracer_is_disabled_by_default + def test_use_tracer_enabled_when_gem_is_unavailable write_rc <<~RUBY - IRB.conf[:USE_TRACER] = false + # Simulate the absence of the tracer gem + ::Kernel.send(:alias_method, :irb_original_require, :require) + + ::Kernel.define_method(:require) do |name| + raise LoadError, "cannot load such file -- tracer (test)" if name.match?("tracer") + ::Kernel.send(:irb_original_require, name) + end + + IRB.conf[:USE_TRACER] = true RUBY write_ruby example_ruby_file output = run_ruby_file do type "bar(Foo)" - type "exit!" + type "exit" end - assert_nil IRB.conf[:USER_TRACER] - assert_not_include(output, "#depth:") - assert_not_include(output, "Foo.foo") - end - - def test_use_tracer_enabled_when_gem_is_unavailable - begin - gem 'tracer' - omit "Skipping because 'tracer' gem is available." - rescue Gem::LoadError - write_rc <<~RUBY - IRB.conf[:USE_TRACER] = true - RUBY - - write_ruby example_ruby_file - - output = run_ruby_file do - type "bar(Foo)" - type "exit!" - end - - assert_include(output, "Tracer extension of IRB is enabled but tracer gem wasn't found.") - end + assert_include(output, "Tracer extension of IRB is enabled but tracer gem wasn't found.") end def test_use_tracer_enabled_when_gem_is_available - if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.1.0') - omit "Ruby version before 3.1.0 does not support Tracer integration. Skipping this test." - end - - begin - gem 'tracer' - rescue Gem::LoadError - omit "Skipping because 'tracer' gem is not available. Enable with WITH_TRACER=true." - end - write_rc <<~RUBY IRB.conf[:USE_TRACER] = true RUBY @@ -85,13 +63,29 @@ def test_use_tracer_enabled_when_gem_is_available output = run_ruby_file do type "bar(Foo)" - type "exit!" + type "exit" end assert_include(output, "Object#bar at") assert_include(output, "Foo.foo at") assert_include(output, "Foo.foo #=> 100") assert_include(output, "Object#bar #=> 100") + + # Test that the tracer output does not include IRB's own files + assert_not_include(output, "irb/workspace.rb") + end + + def test_use_tracer_is_disabled_by_default + write_ruby example_ruby_file + + output = run_ruby_file do + type "bar(Foo)" + type "exit" + end + + assert_not_include(output, "#depth:") + assert_not_include(output, "Foo.foo") end + end end From afe1f459ccf11143055f1bdff5a70c03bdd681fe Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 7 Feb 2024 16:57:30 +0000 Subject: [PATCH 074/263] Bump version to v1.11.2 (#865) --- lib/irb/version.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irb/version.rb b/lib/irb/version.rb index f2e0e7d7f..585c83702 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -5,7 +5,7 @@ # module IRB # :nodoc: - VERSION = "1.11.1" + VERSION = "1.11.2" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2024-01-08" + @LAST_UPDATE_DATE = "2024-02-07" end From c0a5f31679ddada1a8e49239b2da30cb9f4d57b0 Mon Sep 17 00:00:00 2001 From: Ignacio Chiazzo Cardarello Date: Sat, 10 Feb 2024 19:07:48 -0300 Subject: [PATCH 075/263] Introduce exit! command (#851) * Added failing test for when writing history on exit * Save history on exit * Exit early when calling Kernel.exit * use status 0 for kernel.exit * Added test for nested sessions * Update lib/irb.rb --------- Co-authored-by: Stan Lo --- lib/irb.rb | 21 +++++++++++++--- lib/irb/cmd/exit_forced_action.rb | 22 +++++++++++++++++ lib/irb/extend-command.rb | 5 ++++ test/irb/test_debug_cmd.rb | 41 +++++++++++++++++++++++++++++++ test/irb/test_history.rb | 18 ++++++++++++++ 5 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 lib/irb/cmd/exit_forced_action.rb diff --git a/lib/irb.rb b/lib/irb.rb index f3abed820..ad6ec78aa 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -886,7 +886,11 @@ def IRB.start(ap_path = nil) # Quits irb def IRB.irb_exit(*) - throw :IRB_EXIT + throw :IRB_EXIT, false + end + + def IRB.irb_exit!(*) + throw :IRB_EXIT, true end # Aborts then interrupts irb. @@ -968,7 +972,8 @@ def run(conf = IRB.conf) conf[:IRB_RC].call(context) if conf[:IRB_RC] conf[:MAIN_CONTEXT] = context - save_history = !in_nested_session && conf[:SAVE_HISTORY] && context.io.support_history_saving? + supports_history_saving = conf[:SAVE_HISTORY] && context.io.support_history_saving? + save_history = !in_nested_session && supports_history_saving if save_history context.io.load_history @@ -979,13 +984,21 @@ def run(conf = IRB.conf) end begin - catch(:IRB_EXIT) do + forced_exit = false + + forced_exit = catch(:IRB_EXIT) do eval_input end ensure trap("SIGINT", prev_trap) conf[:AT_EXIT].each{|hook| hook.call} - context.io.save_history if save_history + + if forced_exit + context.io.save_history if supports_history_saving + Kernel.exit(0) + else + context.io.save_history if save_history + end end end diff --git a/lib/irb/cmd/exit_forced_action.rb b/lib/irb/cmd/exit_forced_action.rb new file mode 100644 index 000000000..e5df75b68 --- /dev/null +++ b/lib/irb/cmd/exit_forced_action.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require_relative "nop" + +module IRB + # :stopdoc: + + module ExtendCommand + class ExitForcedAction < Nop + category "IRB" + description "Exit the current process." + + def execute(*) + IRB.irb_exit! + rescue UncaughtThrowError + Kernel.exit(0) + end + end + end + + # :startdoc: +end diff --git a/lib/irb/extend-command.rb b/lib/irb/extend-command.rb index 91ca96e91..2db2b8057 100644 --- a/lib/irb/extend-command.rb +++ b/lib/irb/extend-command.rb @@ -36,6 +36,11 @@ def irb_context [:quit, OVERRIDE_PRIVATE_ONLY], [:irb_quit, OVERRIDE_PRIVATE_ONLY], ], + [ + :irb_exit!, :ExitForcedAction, "cmd/exit_forced_action", + [:exit!, OVERRIDE_PRIVATE_ONLY], + ], + [ :irb_current_working_workspace, :CurrentWorkingWorkspace, "cmd/chws", [:cwws, NO_OVERRIDE], diff --git a/test/irb/test_debug_cmd.rb b/test/irb/test_debug_cmd.rb index 0fb45af47..cbd012009 100644 --- a/test/irb/test_debug_cmd.rb +++ b/test/irb/test_debug_cmd.rb @@ -255,6 +255,47 @@ def test_exit assert_match(/irb\(main\):001> next/, output) end + def test_forced_exit_finishes_process_when_nested_sessions + write_ruby <<~'ruby' + puts "First line" + puts "Second line" + binding.irb + puts "Third line" + binding.irb + puts "Fourth line" + ruby + + output = run_ruby_file do + type "123" + type "456" + type "exit!" + end + + assert_match(/First line\r\n/, output) + assert_match(/Second line\r\n/, output) + assert_match(/irb\(main\):001> 123/, output) + assert_match(/irb\(main\):002> 456/, output) + refute_match(/Third line\r\n/, output) + refute_match(/Fourth line\r\n/, output) + end + + def test_forced_exit + write_ruby <<~'ruby' + puts "Hello" + binding.irb + ruby + + output = run_ruby_file do + type "123" + type "456" + type "exit!" + end + + assert_match(/Hello\r\n/, output) + assert_match(/irb\(main\):001> 123/, output) + assert_match(/irb\(main\):002> 456/, output) + end + def test_quit write_ruby <<~'RUBY' binding.irb diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index e6448ada3..49f3698f9 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -379,6 +379,24 @@ def foo HISTORY end + def test_history_saving_with_exit! + write_history "" + + write_ruby <<~'RUBY' + binding.irb + RUBY + + run_ruby_file do + type "'starting session'" + type "exit!" + end + + assert_equal <<~HISTORY, @history_file.open.read + 'starting session' + exit! + HISTORY + end + def test_history_saving_with_nested_sessions_and_prior_history write_history <<~HISTORY old_history_1 From 899d10ade1930dd87d70065f801d55b7906e450e Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 11 Feb 2024 05:17:37 +0000 Subject: [PATCH 076/263] Polish the exit! command and its tests (#867) * Remove IRB.irb_exit! method It's not necessary to introduce a new method just for the exit! command at this moment. * Rename ExitForcedAction to ForceExit * Move force exit tests to a dedicated file * Fix nested history saving with exit! command Because we switched to use `Kernel#exit` instead of `exit!`, the outer session's ensure block in `Irb#run` will be run, which will save the history. This means the separate check to save history when force exiting is no longer necessary. * execute_lines helper should also capture IRB setup's output This prevents setup warnings from being printed to test output while allowing those output to be tested. * Update readme --- README.md | 1 + lib/irb.rb | 15 ++---- .../{exit_forced_action.rb => force_exit.rb} | 4 +- lib/irb/extend-command.rb | 2 +- test/irb/cmd/test_force_exit.rb | 51 +++++++++++++++++++ test/irb/test_cmd.rb | 24 +++++---- test/irb/test_debug_cmd.rb | 41 --------------- test/irb/test_history.rb | 44 ++++++++++++++-- 8 files changed, 112 insertions(+), 70 deletions(-) rename lib/irb/cmd/{exit_forced_action.rb => force_exit.rb} (83%) create mode 100644 test/irb/cmd/test_force_exit.rb diff --git a/README.md b/README.md index 1027f2f12..86cfbe876 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,7 @@ The following commands are available on IRB. You can get the same output from th ```txt IRB exit Exit the current irb session. + exit! Exit the current process. irb_load Load a Ruby file. irb_require Require a Ruby file. source Loads a given file in the current session. diff --git a/lib/irb.rb b/lib/irb.rb index ad6ec78aa..3830867e6 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -889,10 +889,6 @@ def IRB.irb_exit(*) throw :IRB_EXIT, false end - def IRB.irb_exit!(*) - throw :IRB_EXIT, true - end - # Aborts then interrupts irb. # # Will raise an Abort exception, or the given +exception+. @@ -972,8 +968,7 @@ def run(conf = IRB.conf) conf[:IRB_RC].call(context) if conf[:IRB_RC] conf[:MAIN_CONTEXT] = context - supports_history_saving = conf[:SAVE_HISTORY] && context.io.support_history_saving? - save_history = !in_nested_session && supports_history_saving + save_history = !in_nested_session && conf[:SAVE_HISTORY] && context.io.support_history_saving? if save_history context.io.load_history @@ -993,12 +988,8 @@ def run(conf = IRB.conf) trap("SIGINT", prev_trap) conf[:AT_EXIT].each{|hook| hook.call} - if forced_exit - context.io.save_history if supports_history_saving - Kernel.exit(0) - else - context.io.save_history if save_history - end + context.io.save_history if save_history + Kernel.exit(0) if forced_exit end end diff --git a/lib/irb/cmd/exit_forced_action.rb b/lib/irb/cmd/force_exit.rb similarity index 83% rename from lib/irb/cmd/exit_forced_action.rb rename to lib/irb/cmd/force_exit.rb index e5df75b68..2b9f29686 100644 --- a/lib/irb/cmd/exit_forced_action.rb +++ b/lib/irb/cmd/force_exit.rb @@ -6,12 +6,12 @@ module IRB # :stopdoc: module ExtendCommand - class ExitForcedAction < Nop + class ForceExit < Nop category "IRB" description "Exit the current process." def execute(*) - IRB.irb_exit! + throw :IRB_EXIT, true rescue UncaughtThrowError Kernel.exit(0) end diff --git a/lib/irb/extend-command.rb b/lib/irb/extend-command.rb index 2db2b8057..d303bf76d 100644 --- a/lib/irb/extend-command.rb +++ b/lib/irb/extend-command.rb @@ -37,7 +37,7 @@ def irb_context [:irb_quit, OVERRIDE_PRIVATE_ONLY], ], [ - :irb_exit!, :ExitForcedAction, "cmd/exit_forced_action", + :irb_exit!, :ForceExit, "cmd/force_exit", [:exit!, OVERRIDE_PRIVATE_ONLY], ], diff --git a/test/irb/cmd/test_force_exit.rb b/test/irb/cmd/test_force_exit.rb new file mode 100644 index 000000000..191a78687 --- /dev/null +++ b/test/irb/cmd/test_force_exit.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: false +require 'irb' + +require_relative "../helper" + +module TestIRB + class ForceExitTest < IntegrationTestCase + def test_forced_exit_finishes_process_immediately + write_ruby <<~'ruby' + puts "First line" + puts "Second line" + binding.irb + puts "Third line" + binding.irb + puts "Fourth line" + ruby + + output = run_ruby_file do + type "123" + type "456" + type "exit!" + end + + assert_match(/First line\r\n/, output) + assert_match(/Second line\r\n/, output) + assert_match(/irb\(main\):001> 123/, output) + assert_match(/irb\(main\):002> 456/, output) + refute_match(/Third line\r\n/, output) + refute_match(/Fourth line\r\n/, output) + end + + def test_forced_exit_in_nested_sessions + write_ruby <<~'ruby' + def foo + binding.irb + end + + binding.irb + binding.irb + ruby + + output = run_ruby_file do + type "123" + type "foo" + type "exit!" + end + + assert_match(/irb\(main\):001> 123/, output) + end + end +end diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index d99ac05c5..7d7353281 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -34,17 +34,17 @@ def teardown end def execute_lines(*lines, conf: {}, main: self, irb_path: nil) - IRB.init_config(nil) - IRB.conf[:VERBOSE] = false - IRB.conf[:PROMPT_MODE] = :SIMPLE - IRB.conf[:USE_PAGER] = false - IRB.conf.merge!(conf) - input = TestInputMethod.new(lines) - irb = IRB::Irb.new(IRB::WorkSpace.new(main), input) - irb.context.return_format = "=> %s\n" - irb.context.irb_path = irb_path if irb_path - IRB.conf[:MAIN_CONTEXT] = irb.context capture_output do + IRB.init_config(nil) + IRB.conf[:VERBOSE] = false + IRB.conf[:PROMPT_MODE] = :SIMPLE + IRB.conf[:USE_PAGER] = false + IRB.conf.merge!(conf) + input = TestInputMethod.new(lines) + irb = IRB::Irb.new(IRB::WorkSpace.new(main), input) + irb.context.return_format = "=> %s\n" + irb.context.irb_path = irb_path if irb_path + IRB.conf[:MAIN_CONTEXT] = irb.context irb.eval_input end end @@ -58,7 +58,9 @@ def test_calling_command_on_a_frozen_main "irb_info", main: main ) - assert_empty err + # Because the main object is frozen, IRB would wrap a delegator around it + # Which's exit! method can't be overridden and would raise a warning + assert_match(/delegator does not forward private method #exit\!/, err) assert_match(/RUBY_PLATFORM/, out) end end diff --git a/test/irb/test_debug_cmd.rb b/test/irb/test_debug_cmd.rb index cbd012009..0fb45af47 100644 --- a/test/irb/test_debug_cmd.rb +++ b/test/irb/test_debug_cmd.rb @@ -255,47 +255,6 @@ def test_exit assert_match(/irb\(main\):001> next/, output) end - def test_forced_exit_finishes_process_when_nested_sessions - write_ruby <<~'ruby' - puts "First line" - puts "Second line" - binding.irb - puts "Third line" - binding.irb - puts "Fourth line" - ruby - - output = run_ruby_file do - type "123" - type "456" - type "exit!" - end - - assert_match(/First line\r\n/, output) - assert_match(/Second line\r\n/, output) - assert_match(/irb\(main\):001> 123/, output) - assert_match(/irb\(main\):002> 456/, output) - refute_match(/Third line\r\n/, output) - refute_match(/Fourth line\r\n/, output) - end - - def test_forced_exit - write_ruby <<~'ruby' - puts "Hello" - binding.irb - ruby - - output = run_ruby_file do - type "123" - type "456" - type "exit!" - end - - assert_match(/Hello\r\n/, output) - assert_match(/irb\(main\):001> 123/, output) - assert_match(/irb\(main\):002> 456/, output) - end - def test_quit write_ruby <<~'RUBY' binding.irb diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index 49f3698f9..2601bcad8 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -379,20 +379,58 @@ def foo HISTORY end - def test_history_saving_with_exit! + def test_nested_history_saving_from_inner_session_with_exit! write_history "" write_ruby <<~'RUBY' + def foo + binding.irb + end + binding.irb RUBY run_ruby_file do - type "'starting session'" + type "'outer session'" + type "foo" + type "'inner session'" type "exit!" end assert_equal <<~HISTORY, @history_file.open.read - 'starting session' + 'outer session' + foo + 'inner session' + exit! + HISTORY + end + + def test_nested_history_saving_from_outer_session_with_exit! + write_history "" + + write_ruby <<~'RUBY' + def foo + binding.irb + end + + binding.irb + RUBY + + run_ruby_file do + type "'outer session'" + type "foo" + type "'inner session'" + type "exit" + type "'outer session again'" + type "exit!" + end + + assert_equal <<~HISTORY, @history_file.open.read + 'outer session' + foo + 'inner session' + exit + 'outer session again' exit! HISTORY end From 372bc59bf5d3504d6ece066092f6a49581b008ba Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Mon, 12 Feb 2024 20:28:50 +0900 Subject: [PATCH 077/263] Fix exit! command warning and method behavior (#868) * Fix exit! command warning and method behavior * Remove arg(0) from Kernel.exit and Kernel.exit! --- lib/irb.rb | 2 +- lib/irb/cmd/force_exit.rb | 2 +- lib/irb/workspace.rb | 6 +++--- test/irb/cmd/test_force_exit.rb | 12 ++++++++++++ test/irb/test_cmd.rb | 4 +--- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 3830867e6..218920bc4 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -989,7 +989,7 @@ def run(conf = IRB.conf) conf[:AT_EXIT].each{|hook| hook.call} context.io.save_history if save_history - Kernel.exit(0) if forced_exit + Kernel.exit if forced_exit end end diff --git a/lib/irb/cmd/force_exit.rb b/lib/irb/cmd/force_exit.rb index 2b9f29686..7e9b308de 100644 --- a/lib/irb/cmd/force_exit.rb +++ b/lib/irb/cmd/force_exit.rb @@ -13,7 +13,7 @@ class ForceExit < Nop def execute(*) throw :IRB_EXIT, true rescue UncaughtThrowError - Kernel.exit(0) + Kernel.exit! end end end diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb index 2bf3d5e0f..aaf2f335e 100644 --- a/lib/irb/workspace.rb +++ b/lib/irb/workspace.rb @@ -90,11 +90,11 @@ def initialize(*main) IRB.conf[:__MAIN__] = @main @main.singleton_class.class_eval do private - define_method(:exit) do |*a, &b| - # Do nothing, will be overridden - end define_method(:binding, Kernel.instance_method(:binding)) define_method(:local_variables, Kernel.instance_method(:local_variables)) + # Define empty method to avoid delegator warning, will be overridden. + define_method(:exit) {|*a, &b| } + define_method(:exit!) {|*a, &b| } end @binding = eval("IRB.conf[:__MAIN__].instance_eval('binding', __FILE__, __LINE__)", @binding, *@binding.source_location) end diff --git a/test/irb/cmd/test_force_exit.rb b/test/irb/cmd/test_force_exit.rb index 191a78687..9e86c644d 100644 --- a/test/irb/cmd/test_force_exit.rb +++ b/test/irb/cmd/test_force_exit.rb @@ -47,5 +47,17 @@ def foo assert_match(/irb\(main\):001> 123/, output) end + + def test_forced_exit_out_of_irb_session + write_ruby <<~'ruby' + at_exit { puts 'un' + 'reachable' } + binding.irb + exit! # this will call exit! method overrided by command + ruby + output = run_ruby_file do + type "exit" + end + assert_not_include(output, 'unreachable') + end end end diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index 7d7353281..349d2c045 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -58,9 +58,7 @@ def test_calling_command_on_a_frozen_main "irb_info", main: main ) - # Because the main object is frozen, IRB would wrap a delegator around it - # Which's exit! method can't be overridden and would raise a warning - assert_match(/delegator does not forward private method #exit\!/, err) + assert_empty(err) assert_match(/RUBY_PLATFORM/, out) end end From 239683a937685cf3235f36c412f5a2c4fa00c8ae Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 13 Feb 2024 03:38:27 +0900 Subject: [PATCH 078/263] Powerup show_source by enabling RubyVM.keep_script_lines (#862) * Powerup show_source by enabling RubyVM.keep_script_lines * Add file_content field to avoid reading file twice while show_source * Change path passed to eval, don't change irb_path. * Encapsulate source coloring logic and binary file check insode class Source * Add edit command testcase when irb_path does not exist * Memoize irb_path existence to reduce file existence check calculating eval_path --- lib/irb.rb | 6 +- lib/irb/cmd/edit.rb | 18 +++--- lib/irb/cmd/show_source.rb | 15 +++-- lib/irb/context.rb | 12 +++- lib/irb/source_finder.rb | 96 +++++++++++++++++++++----------- test/irb/cmd/test_show_source.rb | 34 ++++++++++- test/irb/test_cmd.rb | 10 ++++ test/irb/test_context.rb | 9 +++ 8 files changed, 149 insertions(+), 51 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 218920bc4..4fa00aa16 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -979,12 +979,16 @@ def run(conf = IRB.conf) end begin - forced_exit = false + if defined?(RubyVM.keep_script_lines) + keep_script_lines_backup = RubyVM.keep_script_lines + RubyVM.keep_script_lines = true + end forced_exit = catch(:IRB_EXIT) do eval_input end ensure + RubyVM.keep_script_lines = keep_script_lines_backup if defined?(RubyVM.keep_script_lines) trap("SIGINT", prev_trap) conf[:AT_EXIT].each{|hook| hook.call} diff --git a/lib/irb/cmd/edit.rb b/lib/irb/cmd/edit.rb index 69606beea..2f89f83ec 100644 --- a/lib/irb/cmd/edit.rb +++ b/lib/irb/cmd/edit.rb @@ -24,11 +24,9 @@ def transform_args(args) def execute(*args) path = args.first - if path.nil? && (irb_path = @irb_context.irb_path) - path = irb_path - end - - if !File.exist?(path) + if path.nil? + path = @irb_context.irb_path + elsif !File.exist?(path) source = begin SourceFinder.new(@irb_context).find_source(path) @@ -37,14 +35,16 @@ def execute(*args) # in this case, we should just ignore the error end - if source + if source&.file_exist? && !source.binary_file? path = source.file - else - puts "Can not find file: #{path}" - return 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}" diff --git a/lib/irb/cmd/show_source.rb b/lib/irb/cmd/show_source.rb index 826cb11ed..cd07de3e9 100644 --- a/lib/irb/cmd/show_source.rb +++ b/lib/irb/cmd/show_source.rb @@ -45,15 +45,18 @@ def execute(str = nil) private def show_source(source) - file_content = IRB::Color.colorize_code(File.read(source.file)) - code = file_content.lines[(source.first_line - 1)...source.last_line].join - content = <<~CONTENT + 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.first_line} + #{bold("From")}: #{source.file}:#{source.line} - #{code} - CONTENT + #{code.chomp} + CONTENT + end Pager.page_content(content) end diff --git a/lib/irb/context.rb b/lib/irb/context.rb index e30125f46..814a8bd4a 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -557,7 +557,7 @@ def evaluate(line, line_no) # :nodoc: if IRB.conf[:MEASURE] && !IRB.conf[:MEASURE_CALLBACKS].empty? last_proc = proc do - result = @workspace.evaluate(line, irb_path, line_no) + result = @workspace.evaluate(line, eval_path, line_no) end IRB.conf[:MEASURE_CALLBACKS].inject(last_proc) do |chain, item| _name, callback, arg = item @@ -568,12 +568,20 @@ def evaluate(line, line_no) # :nodoc: end end.call else - result = @workspace.evaluate(line, irb_path, line_no) + result = @workspace.evaluate(line, eval_path, line_no) end set_last_value(result) end + private def eval_path + # We need to use differente path to distinguish source_location of method defined in the actual file and method defined in irb session. + if !defined?(@irb_path_existence) || @irb_path_existence[0] != irb_path + @irb_path_existence = [irb_path, File.exist?(irb_path)] + end + @irb_path_existence[1] ? "#{irb_path}(#{IRB.conf[:IRB_NAME]})" : irb_path + end + def inspect_last_value # :nodoc: @inspect_method.inspect_value(@last_value) end diff --git a/lib/irb/source_finder.rb b/lib/irb/source_finder.rb index ad9ee2102..26aae7643 100644 --- a/lib/irb/source_finder.rb +++ b/lib/irb/source_finder.rb @@ -4,12 +4,58 @@ module IRB class SourceFinder - Source = Struct.new( - :file, # @param [String] - file name - :first_line, # @param [String] - first line - :last_line, # @param [String] - last line - keyword_init: true, - ) + class Source + attr_reader :file, :line + def initialize(file, line, ast_source = nil) + @file = file + @line = line + @ast_source = ast_source + end + + def file_exist? + File.exist?(@file) + end + + def binary_file? + # If the line is zero, it means that the target's source is probably in a binary file. + @line.zero? + end + + def file_content + @file_content ||= File.read(@file) + end + + def colorized_content + if !binary_file? && file_exist? + end_line = Source.find_end(file_content, @line) + # To correctly colorize, we need to colorize full content and extract the relevant lines. + colored = IRB::Color.colorize_code(file_content) + colored.lines[@line - 1...end_line].join + elsif @ast_source + IRB::Color.colorize_code(@ast_source) + end + end + + def self.find_end(code, first_line) + lex = RubyLex.new + lines = code.lines[(first_line - 1)..-1] + tokens = RubyLex.ripper_lex_without_warning(lines.join) + prev_tokens = [] + + # chunk with line number + tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk| + code = lines[0..lnum].join + prev_tokens.concat chunk + continue = lex.should_continue?(prev_tokens) + syntax = lex.check_code_syntax(code, local_variables: []) + if !continue && syntax == :valid + return first_line + lnum + end + end + first_line + end + end + private_constant :Source def initialize(irb_context) @@ -27,40 +73,28 @@ def find_source(signature, super_level = 0) owner = eval(Regexp.last_match[:owner], context_binding) method = Regexp.last_match[:method] return unless owner.respond_to?(:instance_method) - file, line = method_target(owner, super_level, method, "owner") + method = method_target(owner, super_level, method, "owner") + file, line = method&.source_location when /\A((?.+)(\.|::))?(?[^ :.]+)\z/ # method, receiver.method, receiver::method receiver = eval(Regexp.last_match[:receiver] || 'self', context_binding) method = Regexp.last_match[:method] return unless receiver.respond_to?(method, true) - file, line = method_target(receiver, super_level, method, "receiver") + method = method_target(receiver, super_level, method, "receiver") + file, line = method&.source_location end - # If the line is zero, it means that the target's source is probably in a binary file, which we should ignore. - if file && line && !line.zero? && File.exist?(file) - Source.new(file: file, first_line: line, last_line: find_end(file, line)) + return unless file && line + + if File.exist?(file) + Source.new(file, line) + elsif method + # Method defined with eval, probably in IRB session + source = RubyVM::AbstractSyntaxTree.of(method)&.source rescue nil + Source.new(file, line, source) end end private - def find_end(file, first_line) - lex = RubyLex.new - lines = File.read(file).lines[(first_line - 1)..-1] - tokens = RubyLex.ripper_lex_without_warning(lines.join) - prev_tokens = [] - - # chunk with line number - tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk| - code = lines[0..lnum].join - prev_tokens.concat chunk - continue = lex.should_continue?(prev_tokens) - syntax = lex.check_code_syntax(code, local_variables: []) - if !continue && syntax == :valid - return first_line + lnum - end - end - first_line - end - def method_target(owner_receiver, super_level, method, type) case type when "owner" @@ -71,7 +105,7 @@ def method_target(owner_receiver, super_level, method, type) super_level.times do |s| target_method = target_method.super_method if target_method end - target_method.nil? ? nil : target_method.source_location + target_method rescue NameError nil end diff --git a/test/irb/cmd/test_show_source.rb b/test/irb/cmd/test_show_source.rb index c2608926e..062ab327d 100644 --- a/test/irb/cmd/test_show_source.rb +++ b/test/irb/cmd/test_show_source.rb @@ -301,7 +301,37 @@ class Bar assert_match(%r[#{@ruby_file.to_path}:5\s+class Bar\r\n end], out) end - def test_show_source_ignores_binary_source_file + def test_show_source_keep_script_lines + pend unless defined?(RubyVM.keep_script_lines) + + write_ruby <<~RUBY + binding.irb + RUBY + + out = run_ruby_file do + type "def foo; end" + type "show_source foo" + type "exit" + end + + assert_match(%r[#{@ruby_file.to_path}\(irb\):1\s+def foo; end], out) + end + + def test_show_source_unavailable_source + write_ruby <<~RUBY + binding.irb + RUBY + + out = run_ruby_file do + type "RubyVM.keep_script_lines = false if defined?(RubyVM.keep_script_lines)" + type "def foo; end" + type "show_source foo" + type "exit" + end + assert_match(%r[#{@ruby_file.to_path}\(irb\):2\s+Source not available], out) + end + + def test_show_source_shows_binary_source write_ruby <<~RUBY # io-console is an indirect dependency of irb require "io/console" @@ -317,7 +347,7 @@ def test_show_source_ignores_binary_source_file # A safeguard to make sure the test subject is actually defined refute_match(/NameError/, out) - assert_match(%r[Error: Couldn't locate a definition for IO::ConsoleMode], out) + assert_match(%r[Defined in binary file:.+io/console], out) end end end diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index 349d2c045..bc6358733 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -848,6 +848,16 @@ def test_edit_without_arg assert_match("command: ': code'", out) end + def test_edit_without_arg_and_non_existing_irb_path + out, err = execute_lines( + "edit", + irb_path: '/path/to/file.rb(irb)' + ) + + assert_empty err + assert_match(/Can not find file: \/path\/to\/file\.rb\(irb\)/, out) + end + def test_edit_with_path out, err = execute_lines( "edit #{__FILE__}" diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index a76152169..0fdd847a6 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -666,6 +666,15 @@ def test_lineno ], out) end + def test_eval_path + @context.irb_path = __FILE__ + assert_equal("#{__FILE__}(irb)", @context.send(:eval_path)) + @context.irb_path = 'file/does/not/exist' + assert_equal('file/does/not/exist', @context.send(:eval_path)) + @context.irb_path = "#{__FILE__}(irb)" + assert_equal("#{__FILE__}(irb)", @context.send(:eval_path)) + end + def test_build_completor verbose, $VERBOSE = $VERBOSE, nil original_completor = IRB.conf[:COMPLETOR] From c63e4c40359092bc707ade29d7ce851dcef44afa Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 13 Feb 2024 13:33:33 +0000 Subject: [PATCH 079/263] Refactor eval_path and `SourceFinder::Source` (#870) * Assign `@eval_path` through `irb_path=` method This simplifies the original caching logic for the `eval_path` method and makes it easier to understand. * Refactor SourceFinder::Source --- lib/irb/context.rb | 42 +++++++++++++++++++++++++--------------- lib/irb/ext/loader.rb | 6 +++--- lib/irb/source_finder.rb | 13 ++++++++----- test/irb/test_context.rb | 11 +++++++---- 4 files changed, 44 insertions(+), 28 deletions(-) diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 814a8bd4a..e50958978 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -77,7 +77,7 @@ def initialize(irb, workspace = nil, input_method = nil) else @irb_name = IRB.conf[:IRB_NAME]+"#"+IRB.JobManager.n_jobs.to_s end - @irb_path = "(" + @irb_name + ")" + self.irb_path = "(" + @irb_name + ")" case input_method when nil @@ -121,11 +121,11 @@ def initialize(irb, workspace = nil, input_method = nil) when '-' @io = FileInputMethod.new($stdin) @irb_name = '-' - @irb_path = '-' + self.irb_path = '-' when String @io = FileInputMethod.new(input_method) @irb_name = File.basename(input_method) - @irb_path = input_method + self.irb_path = input_method else @io = input_method end @@ -246,9 +246,27 @@ def main # Can be either name from IRB.conf[:IRB_NAME], or the number of # the current job set by JobManager, such as irb#2 attr_accessor :irb_name - # Can be either the #irb_name surrounded by parenthesis, or the - # +input_method+ passed to Context.new - attr_accessor :irb_path + + # Can be one of the following: + # - the #irb_name surrounded by parenthesis + # - the +input_method+ passed to Context.new + # - the file path of the current IRB context in a binding.irb session + attr_reader :irb_path + + # Sets @irb_path to the given +path+ as well as @eval_path + # @eval_path is used for evaluating code in the context of IRB session + # It's the same as irb_path, but with the IRB name postfix + # This makes sure users can distinguish the methods defined in the IRB session + # from the methods defined in the current file's context, especially with binding.irb + def irb_path=(path) + @irb_path = path + + if File.exist?(path) + @eval_path = "#{path}(#{IRB.conf[:IRB_NAME]})" + else + @eval_path = path + end + end # Whether multiline editor mode is enabled or not. # @@ -557,7 +575,7 @@ def evaluate(line, line_no) # :nodoc: if IRB.conf[:MEASURE] && !IRB.conf[:MEASURE_CALLBACKS].empty? last_proc = proc do - result = @workspace.evaluate(line, eval_path, line_no) + result = @workspace.evaluate(line, @eval_path, line_no) end IRB.conf[:MEASURE_CALLBACKS].inject(last_proc) do |chain, item| _name, callback, arg = item @@ -568,20 +586,12 @@ def evaluate(line, line_no) # :nodoc: end end.call else - result = @workspace.evaluate(line, eval_path, line_no) + result = @workspace.evaluate(line, @eval_path, line_no) end set_last_value(result) end - private def eval_path - # We need to use differente path to distinguish source_location of method defined in the actual file and method defined in irb session. - if !defined?(@irb_path_existence) || @irb_path_existence[0] != irb_path - @irb_path_existence = [irb_path, File.exist?(irb_path)] - end - @irb_path_existence[1] ? "#{irb_path}(#{IRB.conf[:IRB_NAME]})" : irb_path - end - def inspect_last_value # :nodoc: @inspect_method.inspect_value(@last_value) end diff --git a/lib/irb/ext/loader.rb b/lib/irb/ext/loader.rb index d65695df3..5bd25546f 100644 --- a/lib/irb/ext/loader.rb +++ b/lib/irb/ext/loader.rb @@ -98,13 +98,13 @@ def load_file(path, priv = nil) def old # :nodoc: back_io = @io - back_path = @irb_path + back_path = irb_path back_name = @irb_name back_scanner = @irb.scanner begin @io = FileInputMethod.new(path) @irb_name = File.basename(path) - @irb_path = path + self.irb_path = path @irb.signal_status(:IN_LOAD) do if back_io.kind_of?(FileInputMethod) @irb.eval_input @@ -119,7 +119,7 @@ def old # :nodoc: ensure @io = back_io @irb_name = back_name - @irb_path = back_path + self.irb_path = back_path @irb.scanner = back_scanner end end diff --git a/lib/irb/source_finder.rb b/lib/irb/source_finder.rb index 26aae7643..07e864dad 100644 --- a/lib/irb/source_finder.rb +++ b/lib/irb/source_finder.rb @@ -27,7 +27,7 @@ def file_content def colorized_content if !binary_file? && file_exist? - end_line = Source.find_end(file_content, @line) + end_line = find_end # To correctly colorize, we need to colorize full content and extract the relevant lines. colored = IRB::Color.colorize_code(file_content) colored.lines[@line - 1...end_line].join @@ -36,9 +36,12 @@ def colorized_content end end - def self.find_end(code, first_line) + private + + def find_end lex = RubyLex.new - lines = code.lines[(first_line - 1)..-1] + code = file_content + lines = code.lines[(@line - 1)..-1] tokens = RubyLex.ripper_lex_without_warning(lines.join) prev_tokens = [] @@ -49,10 +52,10 @@ def self.find_end(code, first_line) continue = lex.should_continue?(prev_tokens) syntax = lex.check_code_syntax(code, local_variables: []) if !continue && syntax == :valid - return first_line + lnum + return @line + lnum end end - first_line + @line end end diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index 0fdd847a6..adf472baa 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -666,13 +666,16 @@ def test_lineno ], out) end - def test_eval_path + def test_irb_path_setter @context.irb_path = __FILE__ - assert_equal("#{__FILE__}(irb)", @context.send(:eval_path)) + assert_equal(__FILE__, @context.irb_path) + assert_equal("#{__FILE__}(irb)", @context.instance_variable_get(:@eval_path)) @context.irb_path = 'file/does/not/exist' - assert_equal('file/does/not/exist', @context.send(:eval_path)) + assert_equal('file/does/not/exist', @context.irb_path) + assert_equal('file/does/not/exist', @context.instance_variable_get(:@eval_path)) @context.irb_path = "#{__FILE__}(irb)" - assert_equal("#{__FILE__}(irb)", @context.send(:eval_path)) + assert_equal("#{__FILE__}(irb)", @context.irb_path) + assert_equal("#{__FILE__}(irb)", @context.instance_variable_get(:@eval_path)) end def test_build_completor From 8c16e029d1ee978279b48f94128008870e548b1a Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 13 Feb 2024 13:36:29 +0000 Subject: [PATCH 080/263] Fix SourceFinder's constant evaluation issue (#869) Currently, if the signature's constant part is not defined, a NameError would be raised. ``` irb(main):001> show_source Foo (eval):1:in `': uninitialized constant Foo (NameError) Foo ^^^ from (irb):1:in `
' ``` This commit fixes the issue and simplifies the `edit` command's implementation. --- lib/irb/cmd/edit.rb | 8 +------- lib/irb/source_finder.rb | 20 +++++++++++++++----- test/irb/cmd/test_show_source.rb | 13 +++++++++++++ 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/lib/irb/cmd/edit.rb b/lib/irb/cmd/edit.rb index 2f89f83ec..dae65f3c3 100644 --- a/lib/irb/cmd/edit.rb +++ b/lib/irb/cmd/edit.rb @@ -27,13 +27,7 @@ def execute(*args) if path.nil? path = @irb_context.irb_path elsif !File.exist?(path) - source = - begin - SourceFinder.new(@irb_context).find_source(path) - rescue NameError - # if user enters a path that doesn't exist, it'll cause NameError when passed here because find_source would try to evaluate it as well - # in this case, we should just ignore the error - end + source = SourceFinder.new(@irb_context).find_source(path) if source&.file_exist? && !source.binary_file? path = source.file diff --git a/lib/irb/source_finder.rb b/lib/irb/source_finder.rb index 07e864dad..8b7fd538b 100644 --- a/lib/irb/source_finder.rb +++ b/lib/irb/source_finder.rb @@ -4,6 +4,8 @@ module IRB class SourceFinder + class EvaluationError < StandardError; end + class Source attr_reader :file, :line def initialize(file, line, ast_source = nil) @@ -66,20 +68,19 @@ def initialize(irb_context) end def find_source(signature, super_level = 0) - context_binding = @irb_context.workspace.binding case signature when /\A(::)?[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name - eval(signature, context_binding) # trigger autoload - base = context_binding.receiver.yield_self { |r| r.is_a?(Module) ? r : Object } + eval_receiver_or_owner(signature) # trigger autoload + base = @irb_context.workspace.binding.receiver.yield_self { |r| r.is_a?(Module) ? r : Object } file, line = base.const_source_location(signature) when /\A(?[A-Z]\w*(::[A-Z]\w*)*)#(?[^ :.]+)\z/ # Class#method - owner = eval(Regexp.last_match[:owner], context_binding) + owner = eval_receiver_or_owner(Regexp.last_match[:owner]) method = Regexp.last_match[:method] return unless owner.respond_to?(:instance_method) method = method_target(owner, super_level, method, "owner") file, line = method&.source_location when /\A((?.+)(\.|::))?(?[^ :.]+)\z/ # method, receiver.method, receiver::method - receiver = eval(Regexp.last_match[:receiver] || 'self', context_binding) + receiver = eval_receiver_or_owner(Regexp.last_match[:receiver] || 'self') method = Regexp.last_match[:method] return unless receiver.respond_to?(method, true) method = method_target(receiver, super_level, method, "receiver") @@ -94,6 +95,8 @@ def find_source(signature, super_level = 0) source = RubyVM::AbstractSyntaxTree.of(method)&.source rescue nil Source.new(file, line, source) end + rescue EvaluationError + nil end private @@ -112,5 +115,12 @@ def method_target(owner_receiver, super_level, method, type) rescue NameError nil end + + def eval_receiver_or_owner(code) + context_binding = @irb_context.workspace.binding + eval(code, context_binding) + rescue NameError + raise EvaluationError + end end end diff --git a/test/irb/cmd/test_show_source.rb b/test/irb/cmd/test_show_source.rb index 062ab327d..2b1c203da 100644 --- a/test/irb/cmd/test_show_source.rb +++ b/test/irb/cmd/test_show_source.rb @@ -52,6 +52,19 @@ def test_show_source_with_missing_signature assert_match(%r[Couldn't locate a definition for foo], out) end + def test_show_source_with_missing_constant + write_ruby <<~'RUBY' + binding.irb + RUBY + + out = run_ruby_file do + type "show_source Foo" + type "exit" + end + + assert_match(%r[Couldn't locate a definition for Foo], out) + end + def test_show_source_string write_ruby <<~'RUBY' binding.irb From 87c279ccf2cf20c9dda601d44717006303acdd4c Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 14 Feb 2024 22:46:45 +0900 Subject: [PATCH 081/263] Improve constant lookup in SourceFinder (#871) --- lib/irb/source_finder.rb | 19 ++++++++++++++++--- test/irb/cmd/test_show_source.rb | 31 +++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/lib/irb/source_finder.rb b/lib/irb/source_finder.rb index 8b7fd538b..5d7d729d1 100644 --- a/lib/irb/source_finder.rb +++ b/lib/irb/source_finder.rb @@ -69,10 +69,18 @@ def initialize(irb_context) def find_source(signature, super_level = 0) case signature - when /\A(::)?[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name + when /\A(::)?[A-Z]\w*(::[A-Z]\w*)*\z/ # ConstName, ::ConstName, ConstPath::ConstName eval_receiver_or_owner(signature) # trigger autoload - base = @irb_context.workspace.binding.receiver.yield_self { |r| r.is_a?(Module) ? r : Object } - file, line = base.const_source_location(signature) + *parts, name = signature.split('::', -1) + base = + if parts.empty? # ConstName + find_const_owner(name) + elsif parts == [''] # ::ConstName + Object + else # ConstPath::ConstName + eval_receiver_or_owner(parts.join('::')) + end + file, line = base.const_source_location(name) when /\A(?[A-Z]\w*(::[A-Z]\w*)*)#(?[^ :.]+)\z/ # Class#method owner = eval_receiver_or_owner(Regexp.last_match[:owner]) method = Regexp.last_match[:method] @@ -122,5 +130,10 @@ def eval_receiver_or_owner(code) rescue NameError raise EvaluationError end + + def find_const_owner(name) + module_nesting = @irb_context.workspace.binding.eval('::Module.nesting') + module_nesting.find { |mod| mod.const_defined?(name, false) } || module_nesting.find { |mod| mod.const_defined?(name) } || Object + end end end diff --git a/test/irb/cmd/test_show_source.rb b/test/irb/cmd/test_show_source.rb index 2b1c203da..d014c78fc 100644 --- a/test/irb/cmd/test_show_source.rb +++ b/test/irb/cmd/test_show_source.rb @@ -362,5 +362,36 @@ def test_show_source_shows_binary_source refute_match(/NameError/, out) assert_match(%r[Defined in binary file:.+io/console], out) end + + def test_show_source_with_constant_lookup + write_ruby <<~RUBY + X = 1 + module M + Y = 1 + Z = 2 + end + class A + Z = 1 + Array = 1 + class B + include M + Object.new.instance_eval { binding.irb } + end + end + RUBY + + out = run_ruby_file do + type "show_source X" + type "show_source Y" + type "show_source Z" + type "show_source Array" + type "exit" + end + + assert_match(%r[#{@ruby_file.to_path}:1\s+X = 1], out) + assert_match(%r[#{@ruby_file.to_path}:3\s+Y = 1], out) + assert_match(%r[#{@ruby_file.to_path}:7\s+Z = 1], out) + assert_match(%r[#{@ruby_file.to_path}:8\s+Array = 1], out) + end end end From d9192d92d03af8c2a9199b4ab1cd9021c1dbb901 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 14 Feb 2024 13:47:41 +0000 Subject: [PATCH 082/263] Repurpose the help command to display the help message (#872) See #787 for more details. --- lib/irb/cmd/help.rb | 19 ++++--------------- test/irb/test_cmd.rb | 26 ++++++++++---------------- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/lib/irb/cmd/help.rb b/lib/irb/cmd/help.rb index 64b885c38..7f894688c 100644 --- a/lib/irb/cmd/help.rb +++ b/lib/irb/cmd/help.rb @@ -1,23 +1,12 @@ # frozen_string_literal: true -require_relative "show_doc" +require_relative "show_cmds" module IRB module ExtendCommand - class Help < ShowDoc - category "Context" - description "[DEPRECATED] Enter the mode to look up RI documents." - - DEPRECATION_MESSAGE = <<~MSG - [Deprecation] The `help` command will be repurposed to display command help in the future. - For RI document lookup, please use the `show_doc` command instead. - For command help, please use `show_cmds` for now. - MSG - - def execute(*names) - warn DEPRECATION_MESSAGE - super - end + class Help < ShowCmds + category "IRB" + description "List all available commands and their description." end end end diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index bc6358733..b9a155ec0 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -603,6 +603,16 @@ def test_whereami_alias 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" @@ -774,22 +784,6 @@ def test_ls_with_no_singleton_class end class ShowDocTest < CommandTestCase - def test_help - out, err = execute_lines( - "help String#gsub\n", - "\n", - ) - - # the former is what we'd get without document content installed, like on CI - # the latter is what we may get locally - possible_rdoc_output = [/Nothing known about String#gsub/, /gsub\(pattern\)/] - assert_include err, "[Deprecation] The `help` command will be repurposed to display command help in the future.\n" - assert(possible_rdoc_output.any? { |output| output.match?(out) }, "Expect the `help` command to match one of the possible outputs. Got:\n#{out}") - ensure - # this is the only way to reset the redefined method without coupling the test with its implementation - EnvUtil.suppress_warning { load "irb/cmd/help.rb" } - end - def test_show_doc out, err = execute_lines( "show_doc String#gsub\n", From 9359d4b51d4e2ddfac53c17af0f88c93573ff48f Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 15 Feb 2024 17:09:11 +0000 Subject: [PATCH 083/263] Update error message assertions for Ruby 3.4 (#874) https://github.com/ruby/ruby/pull/9605 changes both quotes and format for exception messages. So we need to update the assertions in the tests. --- test/irb/helper.rb | 1 + test/irb/test_context.rb | 203 +++++++++++++++++++------------ test/irb/test_eval_history.rb | 2 +- test/irb/test_raise_exception.rb | 2 +- 4 files changed, 126 insertions(+), 82 deletions(-) diff --git a/test/irb/helper.rb b/test/irb/helper.rb index 38bdbb4c3..8d32e45ad 100644 --- a/test/irb/helper.rb +++ b/test/irb/helper.rb @@ -17,6 +17,7 @@ class InputMethod; end end module TestIRB + RUBY_3_4 = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0.dev") class TestCase < Test::Unit::TestCase class TestInputMethod < ::IRB::InputMethod attr_reader :list, :line_no diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index adf472baa..71d4aae90 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -68,11 +68,27 @@ def test_eval_input irb.eval_input end assert_empty err - assert_pattern_list([:*, /\(irb\):1:in `
': Foo \(RuntimeError\)\n/, - :*, /#\n/, - :*, /0$/, - :*, /0$/, - /\s*/], out) + + expected_output = + if RUBY_3_4 + [ + :*, /\(irb\):1:in '': Foo \(RuntimeError\)\n/, + :*, /#\n/, + :*, /0$/, + :*, /0$/, + /\s*/ + ] + else + [ + :*, /\(irb\):1:in `
': Foo \(RuntimeError\)\n/, + :*, /#\n/, + :*, /0$/, + :*, /0$/, + /\s*/ + ] + end + + assert_pattern_list(expected_output, out) ensure $VERBOSE = verbose end @@ -88,11 +104,21 @@ def test_eval_input_raise2x irb.eval_input end assert_empty err - assert_pattern_list([ - :*, /\(irb\):1:in `
': Foo \(RuntimeError\)\n/, - :*, /\(irb\):2:in `
': Bar \(RuntimeError\)\n/, - :*, /#\n/, - ], out) + expected_output = + if RUBY_3_4 + [ + :*, /\(irb\):1:in '': Foo \(RuntimeError\)\n/, + :*, /\(irb\):2:in '': Bar \(RuntimeError\)\n/, + :*, /#\n/, + ] + else + [ + :*, /\(irb\):1:in `
': Foo \(RuntimeError\)\n/, + :*, /\(irb\):2:in `
': Bar \(RuntimeError\)\n/, + :*, /#\n/, + ] + end + assert_pattern_list(expected_output, out) end def test_prompt_n_deprecation @@ -130,9 +156,9 @@ def test_output_to_pipe [:marshal, "123", Marshal.dump(123)], ], failed: [ - [false, "BasicObject.new", /#/, out) - assert_match(/An error occurred when running Kernel#inspect: #'\n/, - :*, /\t 1: from \(irb\):1:in `hoge'\n/, - :*, /\(irb\):1:in `fuga': unhandled exception\n/, - ] - else - expected = [ - :*, /\(irb\):1:in `fuga': unhandled exception\n/, - :*, /\tfrom \(irb\):1:in `hoge'\n/, - :*, /\tfrom \(irb\):1:in `
'\n/, - :* - ] - end - assert_pattern_list(expected, out) + expected_output = + if RUBY_3_4 + [ + :*, /\(irb\):1:in 'fuga': unhandled exception\n/, + :*, /\tfrom \(irb\):1:in 'hoge'\n/, + :*, /\tfrom \(irb\):1:in ''\n/, + :* + ] + elsif RUBY_VERSION < '3.0.0' && STDOUT.tty? + [ + :*, /Traceback \(most recent call last\):\n/, + :*, /\t 2: from \(irb\):1:in `
'\n/, + :*, /\t 1: from \(irb\):1:in `hoge'\n/, + :*, /\(irb\):1:in `fuga': unhandled exception\n/, + ] + else + [ + :*, /\(irb\):1:in `fuga': unhandled exception\n/, + :*, /\tfrom \(irb\):1:in `hoge'\n/, + :*, /\tfrom \(irb\):1:in `
'\n/, + :* + ] + end + assert_pattern_list(expected_output, out) ensure $VERBOSE = verbose end @@ -530,22 +564,31 @@ def test_eval_input_with_invalid_byte_sequence_exception irb.eval_input end assert_empty err - if RUBY_VERSION < '3.0.0' && STDOUT.tty? - expected = [ - :*, /Traceback \(most recent call last\):\n/, - :*, /\t 2: from \(irb\):1:in `
'\n/, - :*, /\t 1: from \(irb\):1:in `hoge'\n/, - :*, /\(irb\):1:in `fuga': A\\xF3B \(RuntimeError\)\n/, - ] - else - expected = [ - :*, /\(irb\):1:in `fuga': A\\xF3B \(RuntimeError\)\n/, - :*, /\tfrom \(irb\):1:in `hoge'\n/, - :*, /\tfrom \(irb\):1:in `
'\n/, - :* - ] - end - assert_pattern_list(expected, out) + expected_output = + if RUBY_3_4 + [ + :*, /\(irb\):1:in 'fuga': A\\xF3B \(RuntimeError\)\n/, + :*, /\tfrom \(irb\):1:in 'hoge'\n/, + :*, /\tfrom \(irb\):1:in ''\n/, + :* + ] + elsif RUBY_VERSION < '3.0.0' && STDOUT.tty? + [ + :*, /Traceback \(most recent call last\):\n/, + :*, /\t 2: from \(irb\):1:in `
'\n/, + :*, /\t 1: from \(irb\):1:in `hoge'\n/, + :*, /\(irb\):1:in `fuga': A\\xF3B \(RuntimeError\)\n/, + ] + else + [ + :*, /\(irb\):1:in `fuga': A\\xF3B \(RuntimeError\)\n/, + :*, /\tfrom \(irb\):1:in `hoge'\n/, + :*, /\tfrom \(irb\):1:in `
'\n/, + :* + ] + end + + assert_pattern_list(expected_output, out) ensure $VERBOSE = verbose end @@ -571,43 +614,43 @@ def test_eval_input_with_long_exception expected = [ :*, /Traceback \(most recent call last\):\n/, :*, /\t... \d+ levels...\n/, - :*, /\t16: from \(irb\):1:in `a4'\n/, - :*, /\t15: from \(irb\):1:in `a5'\n/, - :*, /\t14: from \(irb\):1:in `a6'\n/, - :*, /\t13: from \(irb\):1:in `a7'\n/, - :*, /\t12: from \(irb\):1:in `a8'\n/, - :*, /\t11: from \(irb\):1:in `a9'\n/, - :*, /\t10: from \(irb\):1:in `a10'\n/, - :*, /\t 9: from \(irb\):1:in `a11'\n/, - :*, /\t 8: from \(irb\):1:in `a12'\n/, - :*, /\t 7: from \(irb\):1:in `a13'\n/, - :*, /\t 6: from \(irb\):1:in `a14'\n/, - :*, /\t 5: from \(irb\):1:in `a15'\n/, - :*, /\t 4: from \(irb\):1:in `a16'\n/, - :*, /\t 3: from \(irb\):1:in `a17'\n/, - :*, /\t 2: from \(irb\):1:in `a18'\n/, - :*, /\t 1: from \(irb\):1:in `a19'\n/, - :*, /\(irb\):1:in `a20': unhandled exception\n/, + :*, /\t16: from \(irb\):1:in (`|')a4'\n/, + :*, /\t15: from \(irb\):1:in (`|')a5'\n/, + :*, /\t14: from \(irb\):1:in (`|')a6'\n/, + :*, /\t13: from \(irb\):1:in (`|')a7'\n/, + :*, /\t12: from \(irb\):1:in (`|')a8'\n/, + :*, /\t11: from \(irb\):1:in (`|')a9'\n/, + :*, /\t10: from \(irb\):1:in (`|')a10'\n/, + :*, /\t 9: from \(irb\):1:in (`|')a11'\n/, + :*, /\t 8: from \(irb\):1:in (`|')a12'\n/, + :*, /\t 7: from \(irb\):1:in (`|')a13'\n/, + :*, /\t 6: from \(irb\):1:in (`|')a14'\n/, + :*, /\t 5: from \(irb\):1:in (`|')a15'\n/, + :*, /\t 4: from \(irb\):1:in (`|')a16'\n/, + :*, /\t 3: from \(irb\):1:in (`|')a17'\n/, + :*, /\t 2: from \(irb\):1:in (`|')a18'\n/, + :*, /\t 1: from \(irb\):1:in (`|')a19'\n/, + :*, /\(irb\):1:in (`|')a20': unhandled exception\n/, ] else expected = [ - :*, /\(irb\):1:in `a20': unhandled exception\n/, - :*, /\tfrom \(irb\):1:in `a19'\n/, - :*, /\tfrom \(irb\):1:in `a18'\n/, - :*, /\tfrom \(irb\):1:in `a17'\n/, - :*, /\tfrom \(irb\):1:in `a16'\n/, - :*, /\tfrom \(irb\):1:in `a15'\n/, - :*, /\tfrom \(irb\):1:in `a14'\n/, - :*, /\tfrom \(irb\):1:in `a13'\n/, - :*, /\tfrom \(irb\):1:in `a12'\n/, - :*, /\tfrom \(irb\):1:in `a11'\n/, - :*, /\tfrom \(irb\):1:in `a10'\n/, - :*, /\tfrom \(irb\):1:in `a9'\n/, - :*, /\tfrom \(irb\):1:in `a8'\n/, - :*, /\tfrom \(irb\):1:in `a7'\n/, - :*, /\tfrom \(irb\):1:in `a6'\n/, - :*, /\tfrom \(irb\):1:in `a5'\n/, - :*, /\tfrom \(irb\):1:in `a4'\n/, + :*, /\(irb\):1:in (`|')a20': unhandled exception\n/, + :*, /\tfrom \(irb\):1:in (`|')a19'\n/, + :*, /\tfrom \(irb\):1:in (`|')a18'\n/, + :*, /\tfrom \(irb\):1:in (`|')a17'\n/, + :*, /\tfrom \(irb\):1:in (`|')a16'\n/, + :*, /\tfrom \(irb\):1:in (`|')a15'\n/, + :*, /\tfrom \(irb\):1:in (`|')a14'\n/, + :*, /\tfrom \(irb\):1:in (`|')a13'\n/, + :*, /\tfrom \(irb\):1:in (`|')a12'\n/, + :*, /\tfrom \(irb\):1:in (`|')a11'\n/, + :*, /\tfrom \(irb\):1:in (`|')a10'\n/, + :*, /\tfrom \(irb\):1:in (`|')a9'\n/, + :*, /\tfrom \(irb\):1:in (`|')a8'\n/, + :*, /\tfrom \(irb\):1:in (`|')a7'\n/, + :*, /\tfrom \(irb\):1:in (`|')a6'\n/, + :*, /\tfrom \(irb\):1:in (`|')a5'\n/, + :*, /\tfrom \(irb\):1:in (`|')a4'\n/, :*, /\t... \d+ levels...\n/, ] end diff --git a/test/irb/test_eval_history.rb b/test/irb/test_eval_history.rb index a6ab36fb8..54913ceff 100644 --- a/test/irb/test_eval_history.rb +++ b/test/irb/test_eval_history.rb @@ -37,7 +37,7 @@ def test_eval_history_is_disabled_by_default ) assert_empty(err) - assert_match(/undefined local variable or method `__'/, out) + assert_match(/undefined local variable or method (`|')__'/, out) end def test_eval_history_can_be_retrieved_with_double_underscore diff --git a/test/irb/test_raise_exception.rb b/test/irb/test_raise_exception.rb index c373dd733..44a5ae87e 100644 --- a/test/irb/test_raise_exception.rb +++ b/test/irb/test_raise_exception.rb @@ -61,7 +61,7 @@ def raise_euc_with_invalid_byte_sequence # TruffleRuby warns when the locale does not exist env['TRUFFLERUBYOPT'] = "#{ENV['TRUFFLERUBYOPT']} --log.level=SEVERE" if RUBY_ENGINE == 'truffleruby' args = [env] + bundle_exec + %W[-rirb -C #{tmpdir} -W0 -e IRB.start(__FILE__) -- -f --] - error = /`raise_euc_with_invalid_byte_sequence': あ\\xFF \(RuntimeError\)/ + error = /raise_euc_with_invalid_byte_sequence': あ\\xFF \(RuntimeError\)/ assert_in_out_err(args, <<~IRB, error, [], encoding: "UTF-8") require_relative 'euc' raise_euc_with_invalid_byte_sequence From ebffd3d976dc24596f550d211d3c92f66b564686 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 16 Feb 2024 14:30:22 +0000 Subject: [PATCH 084/263] Fix `irb:rdbg` for ruby head (#876) * Update binding.irb check for Ruby head With https://github.com/ruby/ruby/pull/9605, backtrace in Ruby head now has a new format. This commit updates the check for binding.irb to work with Ruby head. * Do not include a backtick in error messages and backtraces [Feature #16495] --------- Co-authored-by: Yusuke Endoh --- lib/irb.rb | 2 +- lib/irb/cmd/debug.rb | 2 +- test/irb/test_context.rb | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 4fa00aa16..67e03f8bc 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1235,7 +1235,7 @@ def handle_exception(exc) lines.map{ |l| l + "\n" }.join } # The "" in "(irb)" may be the top level of IRB so imitate the main object. - message = message.gsub(/\(irb\):(?\d+):in `<(?top \(required\))>'/) { "(irb):#{$~[:num]}:in `
'" } + message = message.gsub(/\(irb\):(?\d+):in (?[`'])<(?top \(required\))>'/) { "(irb):#{$~[:num]}:in #{$~[:open_quote]}
'" } puts message puts 'Maybe IRB bug!' if irb_bug rescue Exception => handler_exc diff --git a/lib/irb/cmd/debug.rb b/lib/irb/cmd/debug.rb index e236084ca..f5c985f07 100644 --- a/lib/irb/cmd/debug.rb +++ b/lib/irb/cmd/debug.rb @@ -12,7 +12,7 @@ class Debug < Nop BINDING_IRB_FRAME_REGEXPS = [ '', binding.method(:irb).source_location.first, - ].map { |file| /\A#{Regexp.escape(file)}:\d+:in `irb'\z/ } + ].map { |file| /\A#{Regexp.escape(file)}:\d+:in (`|'Binding#)irb'\z/ } def execute(pre_cmds: nil, do_cmds: nil) if irb_context.with_debugger diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index 71d4aae90..059c48676 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -72,7 +72,7 @@ def test_eval_input expected_output = if RUBY_3_4 [ - :*, /\(irb\):1:in '': Foo \(RuntimeError\)\n/, + :*, /\(irb\):1:in '
': Foo \(RuntimeError\)\n/, :*, /#\n/, :*, /0$/, :*, /0$/, @@ -107,8 +107,8 @@ def test_eval_input_raise2x expected_output = if RUBY_3_4 [ - :*, /\(irb\):1:in '': Foo \(RuntimeError\)\n/, - :*, /\(irb\):2:in '': Bar \(RuntimeError\)\n/, + :*, /\(irb\):1:in '
': Foo \(RuntimeError\)\n/, + :*, /\(irb\):2:in '
': Bar \(RuntimeError\)\n/, :*, /#\n/, ] else @@ -531,7 +531,7 @@ def test_eval_input_with_exception [ :*, /\(irb\):1:in 'fuga': unhandled exception\n/, :*, /\tfrom \(irb\):1:in 'hoge'\n/, - :*, /\tfrom \(irb\):1:in ''\n/, + :*, /\tfrom \(irb\):1:in '
'\n/, :* ] elsif RUBY_VERSION < '3.0.0' && STDOUT.tty? @@ -569,7 +569,7 @@ def test_eval_input_with_invalid_byte_sequence_exception [ :*, /\(irb\):1:in 'fuga': A\\xF3B \(RuntimeError\)\n/, :*, /\tfrom \(irb\):1:in 'hoge'\n/, - :*, /\tfrom \(irb\):1:in ''\n/, + :*, /\tfrom \(irb\):1:in '
'\n/, :* ] elsif RUBY_VERSION < '3.0.0' && STDOUT.tty? From 0e9db419beba215e97ec9f221421c89d3d2ce9ab Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 16 Feb 2024 16:12:50 +0000 Subject: [PATCH 085/263] Support repeating debugger input by passing empty input to it (#856) * Test IRB's behaviour with empty input * Handle empty input and pass it to debugger Since `rdbg` accepts empty input to repeat the previous command, IRB should take empty input in `irb:rdbg` sessions and pass them to the debugger. Currently, IRB simply ignores empty input and does nothing. This commit creates `EmptyInput` to represent empty input so it can fit into the current IRB's input processing flow in `Irb#eval_input`. --- lib/irb.rb | 9 ++++--- lib/irb/statement.rb | 23 ++++++++++++++++ ...ug_cmd.rb => test_debugger_integration.rb} | 26 ++++++++++++++++++- test/irb/test_irb.rb | 18 +++++++++++++ 4 files changed, 71 insertions(+), 5 deletions(-) rename test/irb/{test_debug_cmd.rb => test_debugger_integration.rb} (93%) diff --git a/lib/irb.rb b/lib/irb.rb index 67e03f8bc..fd06626e9 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1082,16 +1082,17 @@ def each_top_level_statement loop do code = readmultiline break unless code - - if code != "\n" - yield build_statement(code), @line_no - end + yield build_statement(code), @line_no @line_no += code.count("\n") rescue RubyLex::TerminateLineInput end end def build_statement(code) + if code.match?(/\A\n*\z/) + return Statement::EmptyInput.new + end + code.force_encoding(@context.io.encoding) command_or_alias, arg = code.split(/\s/, 2) # Transform a non-identifier alias (@, $) or keywords (next, break) diff --git a/lib/irb/statement.rb b/lib/irb/statement.rb index b12110600..4e17e5143 100644 --- a/lib/irb/statement.rb +++ b/lib/irb/statement.rb @@ -20,6 +20,29 @@ def evaluable_code raise NotImplementedError end + class EmptyInput < Statement + def is_assignment? + false + end + + def suppresses_echo? + true + end + + # Debugger takes empty input to repeat the last command + def should_be_handled_by_debugger? + true + end + + def code + "" + end + + def evaluable_code + code + end + end + class Expression < Statement def initialize(code, is_assignment) @code = code diff --git a/test/irb/test_debug_cmd.rb b/test/irb/test_debugger_integration.rb similarity index 93% rename from test/irb/test_debug_cmd.rb rename to test/irb/test_debugger_integration.rb index 0fb45af47..d95b01c3d 100644 --- a/test/irb/test_debug_cmd.rb +++ b/test/irb/test_debugger_integration.rb @@ -6,7 +6,7 @@ require_relative "helper" module TestIRB - class DebugCommandTest < IntegrationTestCase + class DebuggerIntegrationTest < IntegrationTestCase def setup super @@ -434,5 +434,29 @@ def test_multi_irb_commands_are_not_available_after_activating_the_debugger assert_match(/irb\(main\):001> next/, output) assert_include(output, "Multi-IRB commands are not available when the debugger is enabled.") end + + def test_irb_passes_empty_input_to_debugger_to_repeat_the_last_command + write_ruby <<~'ruby' + binding.irb + puts "foo" + puts "bar" + puts "baz" + ruby + + output = run_ruby_file do + type "next" + type "" + # Test that empty input doesn't repeat expressions + type "123" + type "" + type "next" + type "" + type "" + end + + assert_include(output, "=> 2\| puts \"foo\"") + assert_include(output, "=> 3\| puts \"bar\"") + assert_include(output, "=> 4\| puts \"baz\"") + end end end diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index fb8b5c2bf..8c4fb5dde 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -58,6 +58,24 @@ def test_symbol_aliases_dont_affect_ruby_syntax assert_include output, "=> \"It's a foo\"" assert_include output, "=> \"It's a bar\"" end + + def test_empty_input_echoing_behaviour + write_ruby <<~'RUBY' + binding.irb + RUBY + + output = run_ruby_file do + type "" + type " " + type "exit" + end + + # Input cramped together due to how Reline's Reline::GeneralIO works + assert_include( + output, + "irb(main):001> irb(main):002> irb(main):002> irb(main):002> => nil\r\n" + ) + end end class IrbIOConfigurationTest < TestCase From 462c1284af0e6d4c4177a6dd30ed3dfc84f303db Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 16 Feb 2024 16:47:32 +0000 Subject: [PATCH 086/263] Standardize command related names (#873) * Replace ExtendCommand with Command and standardize command related names 1. Rename lib/irb/extend-command.rb to lib/irb/command.rb 2. Rename lib/irb/cmd/*.rb to lib/irb/command/*.rb 3. Rename test/irb/test_cmd.rb to test/irb/test_command.rb 4. Rename ExtendCommand to Command * Alias ExtendCommand to Command and deprecate it * Rename Command::Nop to Command::Base * Not deprecate old constants just yet * Add lib/irb/cmd/nop.rb back --- lib/irb.rb | 2 +- lib/irb/cmd/nop.rb | 55 +-------------- lib/irb/{extend-command.rb => command.rb} | 85 ++++++++++++----------- lib/irb/{cmd => command}/backtrace.rb | 2 +- lib/irb/command/base.rb | 55 +++++++++++++++ lib/irb/{cmd => command}/break.rb | 2 +- lib/irb/{cmd => command}/catch.rb | 2 +- lib/irb/{cmd => command}/chws.rb | 8 +-- lib/irb/{cmd => command}/continue.rb | 2 +- lib/irb/{cmd => command}/debug.rb | 5 +- lib/irb/{cmd => command}/delete.rb | 2 +- lib/irb/{cmd => command}/edit.rb | 6 +- lib/irb/{cmd => command}/exit.rb | 6 +- lib/irb/{cmd => command}/finish.rb | 2 +- lib/irb/{cmd => command}/force_exit.rb | 6 +- lib/irb/{cmd => command}/help.rb | 2 +- lib/irb/{cmd => command}/history.rb | 6 +- lib/irb/{cmd => command}/info.rb | 2 +- lib/irb/{cmd => command}/irb_info.rb | 6 +- lib/irb/{cmd => command}/load.rb | 6 +- lib/irb/{cmd => command}/ls.rb | 6 +- lib/irb/{cmd => command}/measure.rb | 6 +- lib/irb/{cmd => command}/next.rb | 2 +- lib/irb/{cmd => command}/pushws.rb | 5 +- lib/irb/{cmd => command}/show_cmds.rb | 6 +- lib/irb/{cmd => command}/show_doc.rb | 6 +- lib/irb/{cmd => command}/show_source.rb | 5 +- lib/irb/{cmd => command}/step.rb | 2 +- lib/irb/{cmd => command}/subirb.rb | 6 +- lib/irb/{cmd => command}/whereami.rb | 6 +- lib/irb/ext/use-loader.rb | 4 +- lib/irb/statement.rb | 6 +- test/irb/{test_cmd.rb => test_command.rb} | 5 +- 33 files changed, 158 insertions(+), 169 deletions(-) rename lib/irb/{extend-command.rb => command.rb} (78%) rename lib/irb/{cmd => command}/backtrace.rb (93%) create mode 100644 lib/irb/command/base.rb rename lib/irb/{cmd => command}/break.rb (92%) rename lib/irb/{cmd => command}/catch.rb (92%) rename lib/irb/{cmd => command}/chws.rb (81%) rename lib/irb/{cmd => command}/continue.rb (91%) rename lib/irb/{cmd => command}/debug.rb (97%) rename lib/irb/{cmd => command}/delete.rb (91%) rename lib/irb/{cmd => command}/edit.rb (95%) rename lib/irb/{cmd => command}/exit.rb (79%) rename lib/irb/{cmd => command}/finish.rb (91%) rename lib/irb/{cmd => command}/force_exit.rb (78%) rename lib/irb/{cmd => command}/help.rb (90%) rename lib/irb/{cmd => command}/history.rb (94%) rename lib/irb/{cmd => command}/info.rb (92%) rename lib/irb/{cmd => command}/irb_info.rb (93%) rename lib/irb/{cmd => command}/load.rb (95%) rename lib/irb/{cmd => command}/ls.rb (98%) rename lib/irb/{cmd => command}/measure.rb (94%) rename lib/irb/{cmd => command}/next.rb (90%) rename lib/irb/{cmd => command}/pushws.rb (91%) rename lib/irb/{cmd => command}/show_cmds.rb (96%) rename lib/irb/{cmd => command}/show_doc.rb (93%) rename lib/irb/{cmd => command}/show_source.rb (95%) rename lib/irb/{cmd => command}/step.rb (90%) rename lib/irb/{cmd => command}/subirb.rb (96%) rename lib/irb/{cmd => command}/whereami.rb (84%) rename test/irb/{test_cmd.rb => test_command.rb} (99%) diff --git a/lib/irb.rb b/lib/irb.rb index fd06626e9..73d96947e 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -9,7 +9,7 @@ require_relative "irb/init" require_relative "irb/context" -require_relative "irb/extend-command" +require_relative "irb/command" require_relative "irb/ruby-lex" require_relative "irb/statement" diff --git a/lib/irb/cmd/nop.rb b/lib/irb/cmd/nop.rb index 7fb197c51..49f89bac9 100644 --- a/lib/irb/cmd/nop.rb +++ b/lib/irb/cmd/nop.rb @@ -1,53 +1,4 @@ -# frozen_string_literal: false -# -# nop.rb - -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# +# frozen_string_literal: true -module IRB - # :stopdoc: - - module ExtendCommand - class CommandArgumentError < StandardError; end - - class Nop - 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 - end - - # :startdoc: -end +# This file is just a placeholder for backward-compatibility. +# Please require 'irb' and inheirt your command from `IRB::Command::Base` instead. diff --git a/lib/irb/extend-command.rb b/lib/irb/command.rb similarity index 78% rename from lib/irb/extend-command.rb rename to lib/irb/command.rb index d303bf76d..cab571cfe 100644 --- a/lib/irb/extend-command.rb +++ b/lib/irb/command.rb @@ -1,10 +1,15 @@ # frozen_string_literal: false # -# irb/extend-command.rb - irb extend command +# irb/command.rb - irb command # by Keiju ISHITSUKA(keiju@ruby-lang.org) # +require_relative "command/base" + module IRB # :nodoc: + module Command; end + ExtendCommand = Command + # Installs the default irb extensions command bundle. module ExtendCommandBundle EXCB = ExtendCommandBundle # :nodoc: @@ -31,18 +36,18 @@ def irb_context @EXTEND_COMMANDS = [ [ - :irb_exit, :Exit, "cmd/exit", + :irb_exit, :Exit, "command/exit", [:exit, OVERRIDE_PRIVATE_ONLY], [:quit, OVERRIDE_PRIVATE_ONLY], [:irb_quit, OVERRIDE_PRIVATE_ONLY], ], [ - :irb_exit!, :ForceExit, "cmd/force_exit", + :irb_exit!, :ForceExit, "command/force_exit", [:exit!, OVERRIDE_PRIVATE_ONLY], ], [ - :irb_current_working_workspace, :CurrentWorkingWorkspace, "cmd/chws", + :irb_current_working_workspace, :CurrentWorkingWorkspace, "command/chws", [:cwws, NO_OVERRIDE], [:pwws, NO_OVERRIDE], [:irb_print_working_workspace, OVERRIDE_ALL], @@ -54,7 +59,7 @@ def irb_context [:irb_pwb, OVERRIDE_ALL], ], [ - :irb_change_workspace, :ChangeWorkspace, "cmd/chws", + :irb_change_workspace, :ChangeWorkspace, "command/chws", [:chws, NO_OVERRIDE], [:cws, NO_OVERRIDE], [:irb_chws, OVERRIDE_ALL], @@ -65,13 +70,13 @@ def irb_context ], [ - :irb_workspaces, :Workspaces, "cmd/pushws", + :irb_workspaces, :Workspaces, "command/pushws", [:workspaces, NO_OVERRIDE], [:irb_bindings, OVERRIDE_ALL], [:bindings, NO_OVERRIDE], ], [ - :irb_push_workspace, :PushWorkspace, "cmd/pushws", + :irb_push_workspace, :PushWorkspace, "command/pushws", [:pushws, NO_OVERRIDE], [:irb_pushws, OVERRIDE_ALL], [:irb_push_binding, OVERRIDE_ALL], @@ -79,7 +84,7 @@ def irb_context [:pushb, NO_OVERRIDE], ], [ - :irb_pop_workspace, :PopWorkspace, "cmd/pushws", + :irb_pop_workspace, :PopWorkspace, "command/pushws", [:popws, NO_OVERRIDE], [:irb_popws, OVERRIDE_ALL], [:irb_pop_binding, OVERRIDE_ALL], @@ -88,112 +93,112 @@ def irb_context ], [ - :irb_load, :Load, "cmd/load"], + :irb_load, :Load, "command/load"], [ - :irb_require, :Require, "cmd/load"], + :irb_require, :Require, "command/load"], [ - :irb_source, :Source, "cmd/load", + :irb_source, :Source, "command/load", [:source, NO_OVERRIDE], ], [ - :irb, :IrbCommand, "cmd/subirb"], + :irb, :IrbCommand, "command/subirb"], [ - :irb_jobs, :Jobs, "cmd/subirb", + :irb_jobs, :Jobs, "command/subirb", [:jobs, NO_OVERRIDE], ], [ - :irb_fg, :Foreground, "cmd/subirb", + :irb_fg, :Foreground, "command/subirb", [:fg, NO_OVERRIDE], ], [ - :irb_kill, :Kill, "cmd/subirb", + :irb_kill, :Kill, "command/subirb", [:kill, OVERRIDE_PRIVATE_ONLY], ], [ - :irb_debug, :Debug, "cmd/debug", + :irb_debug, :Debug, "command/debug", [:debug, NO_OVERRIDE], ], [ - :irb_edit, :Edit, "cmd/edit", + :irb_edit, :Edit, "command/edit", [:edit, NO_OVERRIDE], ], [ - :irb_break, :Break, "cmd/break", + :irb_break, :Break, "command/break", ], [ - :irb_catch, :Catch, "cmd/catch", + :irb_catch, :Catch, "command/catch", ], [ - :irb_next, :Next, "cmd/next" + :irb_next, :Next, "command/next" ], [ - :irb_delete, :Delete, "cmd/delete", + :irb_delete, :Delete, "command/delete", [:delete, NO_OVERRIDE], ], [ - :irb_step, :Step, "cmd/step", + :irb_step, :Step, "command/step", [:step, NO_OVERRIDE], ], [ - :irb_continue, :Continue, "cmd/continue", + :irb_continue, :Continue, "command/continue", [:continue, NO_OVERRIDE], ], [ - :irb_finish, :Finish, "cmd/finish", + :irb_finish, :Finish, "command/finish", [:finish, NO_OVERRIDE], ], [ - :irb_backtrace, :Backtrace, "cmd/backtrace", + :irb_backtrace, :Backtrace, "command/backtrace", [:backtrace, NO_OVERRIDE], [:bt, NO_OVERRIDE], ], [ - :irb_debug_info, :Info, "cmd/info", + :irb_debug_info, :Info, "command/info", [:info, NO_OVERRIDE], ], [ - :irb_help, :Help, "cmd/help", + :irb_help, :Help, "command/help", [:help, NO_OVERRIDE], ], [ - :irb_show_doc, :ShowDoc, "cmd/show_doc", + :irb_show_doc, :ShowDoc, "command/show_doc", [:show_doc, NO_OVERRIDE], ], [ - :irb_info, :IrbInfo, "cmd/irb_info" + :irb_info, :IrbInfo, "command/irb_info" ], [ - :irb_ls, :Ls, "cmd/ls", + :irb_ls, :Ls, "command/ls", [:ls, NO_OVERRIDE], ], [ - :irb_measure, :Measure, "cmd/measure", + :irb_measure, :Measure, "command/measure", [:measure, NO_OVERRIDE], ], [ - :irb_show_source, :ShowSource, "cmd/show_source", + :irb_show_source, :ShowSource, "command/show_source", [:show_source, NO_OVERRIDE], ], [ - :irb_whereami, :Whereami, "cmd/whereami", + :irb_whereami, :Whereami, "command/whereami", [:whereami, NO_OVERRIDE], ], [ - :irb_show_cmds, :ShowCmds, "cmd/show_cmds", + :irb_show_cmds, :ShowCmds, "command/show_cmds", [:show_cmds, NO_OVERRIDE], ], [ - :irb_history, :History, "cmd/history", + :irb_history, :History, "command/history", [:history, NO_OVERRIDE], [:hist, NO_OVERRIDE], ] @@ -210,11 +215,11 @@ def self.all_commands_info end @EXTEND_COMMANDS.each do |cmd_name, cmd_class, load_file, *aliases| - if !defined?(ExtendCommand) || !ExtendCommand.const_defined?(cmd_class, false) + if !defined?(Command) || !Command.const_defined?(cmd_class, false) require_relative load_file end - klass = ExtendCommand.const_get(cmd_class, false) + klass = Command.const_get(cmd_class, false) aliases = aliases.map { |a| a.first } if additional_aliases = user_aliases[cmd_name] @@ -234,10 +239,10 @@ def self.load_command(command) @EXTEND_COMMANDS.each do |cmd_name, cmd_class, load_file, *aliases| next if cmd_name != command && aliases.all? { |alias_name, _| alias_name != command } - if !defined?(ExtendCommand) || !ExtendCommand.const_defined?(cmd_class, false) + if !defined?(Command) || !Command.const_defined?(cmd_class, false) require_relative load_file end - return ExtendCommand.const_get(cmd_class, false) + return Command.const_get(cmd_class, false) end nil end @@ -267,7 +272,7 @@ def self.def_extend_command(cmd_name, cmd_class, load_file, *aliases) line = __LINE__; eval %[ def #{cmd_name}(*opts, **kwargs, &b) Kernel.require_relative "#{load_file}" - ::IRB::ExtendCommand::#{cmd_class}.execute(irb_context, *opts, **kwargs, &b) + ::IRB::Command::#{cmd_class}.execute(irb_context, *opts, **kwargs, &b) end ], nil, __FILE__, line diff --git a/lib/irb/cmd/backtrace.rb b/lib/irb/command/backtrace.rb similarity index 93% rename from lib/irb/cmd/backtrace.rb rename to lib/irb/command/backtrace.rb index f63289461..47e5e6072 100644 --- a/lib/irb/cmd/backtrace.rb +++ b/lib/irb/command/backtrace.rb @@ -5,7 +5,7 @@ module IRB # :stopdoc: - module ExtendCommand + module Command class Backtrace < DebugCommand def self.transform_args(args) args&.dump diff --git a/lib/irb/command/base.rb b/lib/irb/command/base.rb new file mode 100644 index 000000000..87d2fea35 --- /dev/null +++ b/lib/irb/command/base.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: false +# +# nop.rb - +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +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/cmd/break.rb b/lib/irb/command/break.rb similarity index 92% rename from lib/irb/cmd/break.rb rename to lib/irb/command/break.rb index df259a90c..fa200f2d7 100644 --- a/lib/irb/cmd/break.rb +++ b/lib/irb/command/break.rb @@ -5,7 +5,7 @@ module IRB # :stopdoc: - module ExtendCommand + module Command class Break < DebugCommand def self.transform_args(args) args&.dump diff --git a/lib/irb/cmd/catch.rb b/lib/irb/command/catch.rb similarity index 92% rename from lib/irb/cmd/catch.rb rename to lib/irb/command/catch.rb index 40b62c753..6b2edff5e 100644 --- a/lib/irb/cmd/catch.rb +++ b/lib/irb/command/catch.rb @@ -5,7 +5,7 @@ module IRB # :stopdoc: - module ExtendCommand + module Command class Catch < DebugCommand def self.transform_args(args) args&.dump diff --git a/lib/irb/cmd/chws.rb b/lib/irb/command/chws.rb similarity index 81% rename from lib/irb/cmd/chws.rb rename to lib/irb/command/chws.rb index 31045f9bb..341d51615 100644 --- a/lib/irb/cmd/chws.rb +++ b/lib/irb/command/chws.rb @@ -3,16 +3,14 @@ # change-ws.rb - # by Keiju ISHITSUKA(keiju@ruby-lang.org) # - -require_relative "nop" require_relative "../ext/change-ws" module IRB # :stopdoc: - module ExtendCommand + module Command - class CurrentWorkingWorkspace < Nop + class CurrentWorkingWorkspace < Base category "Workspace" description "Show the current workspace." @@ -21,7 +19,7 @@ def execute(*obj) end end - class ChangeWorkspace < Nop + class ChangeWorkspace < Base category "Workspace" description "Change the current workspace to an object." diff --git a/lib/irb/cmd/continue.rb b/lib/irb/command/continue.rb similarity index 91% rename from lib/irb/cmd/continue.rb rename to lib/irb/command/continue.rb index 9136177ee..8b6ffc860 100644 --- a/lib/irb/cmd/continue.rb +++ b/lib/irb/command/continue.rb @@ -5,7 +5,7 @@ module IRB # :stopdoc: - module ExtendCommand + module Command class Continue < DebugCommand def execute(*args) super(do_cmds: ["continue", *args].join(" ")) diff --git a/lib/irb/cmd/debug.rb b/lib/irb/command/debug.rb similarity index 97% rename from lib/irb/cmd/debug.rb rename to lib/irb/command/debug.rb index f5c985f07..bdf91766b 100644 --- a/lib/irb/cmd/debug.rb +++ b/lib/irb/command/debug.rb @@ -1,11 +1,10 @@ -require_relative "nop" require_relative "../debug" module IRB # :stopdoc: - module ExtendCommand - class Debug < Nop + module Command + class Debug < Base category "Debugging" description "Start the debugger of debug.gem." diff --git a/lib/irb/cmd/delete.rb b/lib/irb/command/delete.rb similarity index 91% rename from lib/irb/cmd/delete.rb rename to lib/irb/command/delete.rb index aeb26d257..a36b4577b 100644 --- a/lib/irb/cmd/delete.rb +++ b/lib/irb/command/delete.rb @@ -5,7 +5,7 @@ module IRB # :stopdoc: - module ExtendCommand + module Command class Delete < DebugCommand def execute(*args) super(pre_cmds: ["delete", *args].join(" ")) diff --git a/lib/irb/cmd/edit.rb b/lib/irb/command/edit.rb similarity index 95% rename from lib/irb/cmd/edit.rb rename to lib/irb/command/edit.rb index dae65f3c3..1a8ded6bc 100644 --- a/lib/irb/cmd/edit.rb +++ b/lib/irb/command/edit.rb @@ -1,12 +1,12 @@ require 'shellwords' -require_relative "nop" + require_relative "../source_finder" module IRB # :stopdoc: - module ExtendCommand - class Edit < Nop + module Command + class Edit < Base category "Misc" description 'Open a file with the editor command defined with `ENV["VISUAL"]` or `ENV["EDITOR"]`.' diff --git a/lib/irb/cmd/exit.rb b/lib/irb/command/exit.rb similarity index 79% rename from lib/irb/cmd/exit.rb rename to lib/irb/command/exit.rb index 415e55533..3109ec16e 100644 --- a/lib/irb/cmd/exit.rb +++ b/lib/irb/command/exit.rb @@ -1,12 +1,10 @@ # frozen_string_literal: true -require_relative "nop" - module IRB # :stopdoc: - module ExtendCommand - class Exit < Nop + module Command + class Exit < Base category "IRB" description "Exit the current irb session." diff --git a/lib/irb/cmd/finish.rb b/lib/irb/command/finish.rb similarity index 91% rename from lib/irb/cmd/finish.rb rename to lib/irb/command/finish.rb index 29f100feb..05501819e 100644 --- a/lib/irb/cmd/finish.rb +++ b/lib/irb/command/finish.rb @@ -5,7 +5,7 @@ module IRB # :stopdoc: - module ExtendCommand + module Command class Finish < DebugCommand def execute(*args) super(do_cmds: ["finish", *args].join(" ")) diff --git a/lib/irb/cmd/force_exit.rb b/lib/irb/command/force_exit.rb similarity index 78% rename from lib/irb/cmd/force_exit.rb rename to lib/irb/command/force_exit.rb index 7e9b308de..c2c5542e2 100644 --- a/lib/irb/cmd/force_exit.rb +++ b/lib/irb/command/force_exit.rb @@ -1,12 +1,10 @@ # frozen_string_literal: true -require_relative "nop" - module IRB # :stopdoc: - module ExtendCommand - class ForceExit < Nop + module Command + class ForceExit < Base category "IRB" description "Exit the current process." diff --git a/lib/irb/cmd/help.rb b/lib/irb/command/help.rb similarity index 90% rename from lib/irb/cmd/help.rb rename to lib/irb/command/help.rb index 7f894688c..67cc31a0b 100644 --- a/lib/irb/cmd/help.rb +++ b/lib/irb/command/help.rb @@ -3,7 +3,7 @@ require_relative "show_cmds" module IRB - module ExtendCommand + module Command class Help < ShowCmds category "IRB" description "List all available commands and their description." diff --git a/lib/irb/cmd/history.rb b/lib/irb/command/history.rb similarity index 94% rename from lib/irb/cmd/history.rb rename to lib/irb/command/history.rb index 5b712fa44..a47a8795d 100644 --- a/lib/irb/cmd/history.rb +++ b/lib/irb/command/history.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true require "stringio" -require_relative "nop" + require_relative "../pager" module IRB # :stopdoc: - module ExtendCommand - class History < Nop + module Command + class History < Base category "IRB" description "Shows the input history. `-g [query]` or `-G [query]` allows you to filter the output." diff --git a/lib/irb/cmd/info.rb b/lib/irb/command/info.rb similarity index 92% rename from lib/irb/cmd/info.rb rename to lib/irb/command/info.rb index 2c0a32b34..a67be3eb8 100644 --- a/lib/irb/cmd/info.rb +++ b/lib/irb/command/info.rb @@ -5,7 +5,7 @@ module IRB # :stopdoc: - module ExtendCommand + module Command class Info < DebugCommand def self.transform_args(args) args&.dump diff --git a/lib/irb/cmd/irb_info.rb b/lib/irb/command/irb_info.rb similarity index 93% rename from lib/irb/cmd/irb_info.rb rename to lib/irb/command/irb_info.rb index 5b905a09b..7fd3e2104 100644 --- a/lib/irb/cmd/irb_info.rb +++ b/lib/irb/command/irb_info.rb @@ -1,12 +1,10 @@ # frozen_string_literal: false -require_relative "nop" - module IRB # :stopdoc: - module ExtendCommand - class IrbInfo < Nop + module Command + class IrbInfo < Base category "IRB" description "Show information about IRB." diff --git a/lib/irb/cmd/load.rb b/lib/irb/command/load.rb similarity index 95% rename from lib/irb/cmd/load.rb rename to lib/irb/command/load.rb index a3e797a7e..0558bc83b 100644 --- a/lib/irb/cmd/load.rb +++ b/lib/irb/command/load.rb @@ -3,15 +3,13 @@ # load.rb - # by Keiju ISHITSUKA(keiju@ruby-lang.org) # - -require_relative "nop" require_relative "../ext/loader" module IRB # :stopdoc: - module ExtendCommand - class LoaderCommand < Nop + module Command + class LoaderCommand < Base include IrbLoader def raise_cmd_argument_error diff --git a/lib/irb/cmd/ls.rb b/lib/irb/command/ls.rb similarity index 98% rename from lib/irb/cmd/ls.rb rename to lib/irb/command/ls.rb index 791b1c1b2..bbe4a1ee9 100644 --- a/lib/irb/cmd/ls.rb +++ b/lib/irb/command/ls.rb @@ -2,15 +2,15 @@ require "reline" require "stringio" -require_relative "nop" + require_relative "../pager" require_relative "../color" module IRB # :stopdoc: - module ExtendCommand - class Ls < Nop + 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." diff --git a/lib/irb/cmd/measure.rb b/lib/irb/command/measure.rb similarity index 94% rename from lib/irb/cmd/measure.rb rename to lib/irb/command/measure.rb index 4e1125a0a..ee7927b01 100644 --- a/lib/irb/cmd/measure.rb +++ b/lib/irb/command/measure.rb @@ -1,10 +1,8 @@ -require_relative "nop" - module IRB # :stopdoc: - module ExtendCommand - class Measure < Nop + module Command + class Measure < Base category "Misc" description "`measure` enables the mode to measure processing time. `measure :off` disables it." diff --git a/lib/irb/cmd/next.rb b/lib/irb/command/next.rb similarity index 90% rename from lib/irb/cmd/next.rb rename to lib/irb/command/next.rb index d29c82e7f..6487c9d24 100644 --- a/lib/irb/cmd/next.rb +++ b/lib/irb/command/next.rb @@ -5,7 +5,7 @@ module IRB # :stopdoc: - module ExtendCommand + module Command class Next < DebugCommand def execute(*args) super(do_cmds: ["next", *args].join(" ")) diff --git a/lib/irb/cmd/pushws.rb b/lib/irb/command/pushws.rb similarity index 91% rename from lib/irb/cmd/pushws.rb rename to lib/irb/command/pushws.rb index 59996ceb0..a6fcd6a16 100644 --- a/lib/irb/cmd/pushws.rb +++ b/lib/irb/command/pushws.rb @@ -4,14 +4,13 @@ # by Keiju ISHITSUKA(keiju@ruby-lang.org) # -require_relative "nop" require_relative "../ext/workspaces" module IRB # :stopdoc: - module ExtendCommand - class Workspaces < Nop + module Command + class Workspaces < Base category "Workspace" description "Show workspaces." diff --git a/lib/irb/cmd/show_cmds.rb b/lib/irb/command/show_cmds.rb similarity index 96% rename from lib/irb/cmd/show_cmds.rb rename to lib/irb/command/show_cmds.rb index a8d899e4a..940ed490d 100644 --- a/lib/irb/cmd/show_cmds.rb +++ b/lib/irb/command/show_cmds.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true require "stringio" -require_relative "nop" + require_relative "../pager" module IRB # :stopdoc: - module ExtendCommand - class ShowCmds < Nop + module Command + class ShowCmds < Base category "IRB" description "List all available commands and their description." diff --git a/lib/irb/cmd/show_doc.rb b/lib/irb/command/show_doc.rb similarity index 93% rename from lib/irb/cmd/show_doc.rb rename to lib/irb/command/show_doc.rb index 99dd9ab95..dca10ec4b 100644 --- a/lib/irb/cmd/show_doc.rb +++ b/lib/irb/command/show_doc.rb @@ -1,10 +1,8 @@ # frozen_string_literal: true -require_relative "nop" - module IRB - module ExtendCommand - class ShowDoc < Nop + module Command + class ShowDoc < Base class << self def transform_args(args) # Return a string literal as is for backward compatibility diff --git a/lib/irb/cmd/show_source.rb b/lib/irb/command/show_source.rb similarity index 95% rename from lib/irb/cmd/show_source.rb rename to lib/irb/command/show_source.rb index cd07de3e9..cc783e753 100644 --- a/lib/irb/cmd/show_source.rb +++ b/lib/irb/command/show_source.rb @@ -1,13 +1,12 @@ # frozen_string_literal: true -require_relative "nop" require_relative "../source_finder" require_relative "../pager" require_relative "../color" module IRB - module ExtendCommand - class ShowSource < Nop + module Command + class ShowSource < Base category "Context" description "Show the source code of a given method or constant." diff --git a/lib/irb/cmd/step.rb b/lib/irb/command/step.rb similarity index 90% rename from lib/irb/cmd/step.rb rename to lib/irb/command/step.rb index 2bc74a9d7..cce7d2b0f 100644 --- a/lib/irb/cmd/step.rb +++ b/lib/irb/command/step.rb @@ -5,7 +5,7 @@ module IRB # :stopdoc: - module ExtendCommand + module Command class Step < DebugCommand def execute(*args) super(do_cmds: ["step", *args].join(" ")) diff --git a/lib/irb/cmd/subirb.rb b/lib/irb/command/subirb.rb similarity index 96% rename from lib/irb/cmd/subirb.rb rename to lib/irb/command/subirb.rb index 5ffd64641..0a75706f5 100644 --- a/lib/irb/cmd/subirb.rb +++ b/lib/irb/command/subirb.rb @@ -4,13 +4,11 @@ # by Keiju ISHITSUKA(keiju@ruby-lang.org) # -require_relative "nop" - module IRB # :stopdoc: - module ExtendCommand - class MultiIRBCommand < Nop + module Command + class MultiIRBCommand < Base def execute(*args) extend_irb_context end diff --git a/lib/irb/cmd/whereami.rb b/lib/irb/command/whereami.rb similarity index 84% rename from lib/irb/cmd/whereami.rb rename to lib/irb/command/whereami.rb index 8f56ba073..d6658d704 100644 --- a/lib/irb/cmd/whereami.rb +++ b/lib/irb/command/whereami.rb @@ -1,12 +1,10 @@ # frozen_string_literal: true -require_relative "nop" - module IRB # :stopdoc: - module ExtendCommand - class Whereami < Nop + module Command + class Whereami < Base category "Context" description "Show the source code around binding.irb again." diff --git a/lib/irb/ext/use-loader.rb b/lib/irb/ext/use-loader.rb index d0b8c2d4f..c5ecbe284 100644 --- a/lib/irb/ext/use-loader.rb +++ b/lib/irb/ext/use-loader.rb @@ -17,12 +17,12 @@ module ExtendCommandBundle remove_method :irb_load if method_defined?(:irb_load) # Loads the given file similarly to Kernel#load, see IrbLoader#irb_load def irb_load(*opts, &b) - ExtendCommand::Load.execute(irb_context, *opts, &b) + Command::Load.execute(irb_context, *opts, &b) end remove_method :irb_require if method_defined?(:irb_require) # Loads the given file similarly to Kernel#require def irb_require(*opts, &b) - ExtendCommand::Require.execute(irb_context, *opts, &b) + Command::Require.execute(irb_context, *opts, &b) end end diff --git a/lib/irb/statement.rb b/lib/irb/statement.rb index 4e17e5143..009eb2d20 100644 --- a/lib/irb/statement.rb +++ b/lib/irb/statement.rb @@ -83,9 +83,9 @@ def suppresses_echo? end def should_be_handled_by_debugger? - require_relative 'cmd/help' - require_relative 'cmd/debug' - IRB::ExtendCommand::DebugCommand > @command_class || IRB::ExtendCommand::Help == @command_class + require_relative 'command/help' + require_relative 'command/debug' + IRB::Command::DebugCommand > @command_class || IRB::Command::Help == @command_class end def evaluable_code diff --git a/test/irb/test_cmd.rb b/test/irb/test_command.rb similarity index 99% rename from test/irb/test_cmd.rb rename to test/irb/test_command.rb index b9a155ec0..15ae6a658 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_command.rb @@ -1,7 +1,6 @@ # frozen_string_literal: false require "rubygems" require "irb" -require "irb/extend-command" require_relative "helper" @@ -797,7 +796,7 @@ def test_show_doc assert(possible_rdoc_output.any? { |output| output.match?(out) }, "Expect the `show_doc` command to match one of the possible outputs. Got:\n#{out}") ensure # this is the only way to reset the redefined method without coupling the test with its implementation - EnvUtil.suppress_warning { load "irb/cmd/help.rb" } + EnvUtil.suppress_warning { load "irb/command/help.rb" } end def test_show_doc_without_rdoc @@ -813,7 +812,7 @@ def test_show_doc_without_rdoc assert_include(err, "Can't display document because `rdoc` is not installed.\n") ensure # this is the only way to reset the redefined method without coupling the test with its implementation - EnvUtil.suppress_warning { load "irb/cmd/help.rb" } + EnvUtil.suppress_warning { load "irb/command/help.rb" } end end From 43a2c99f3f3eca8ced4fad8114ee68ccda56a7c4 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 18 Feb 2024 18:21:00 +0000 Subject: [PATCH 087/263] Revamp `help` command (#877) * 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. * Rename test/irb/cmd to test/irb/command * Add banner to edit and ls commands * 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 * Rename banner to help_message --- COMPARED_WITH_PRY.md | 2 +- README.md | 13 +-- lib/irb.rb | 2 +- lib/irb/command.rb | 7 +- lib/irb/command/base.rb | 9 ++ lib/irb/command/edit.rb | 18 +++- lib/irb/command/help.rb | 82 +++++++++++++++++-- lib/irb/command/ls.rb | 8 +- lib/irb/command/show_cmds.rb | 59 ------------- lib/irb/statement.rb | 3 +- test/irb/{cmd => command}/test_force_exit.rb | 0 test/irb/command/test_help.rb | 66 +++++++++++++++ test/irb/{cmd => command}/test_show_source.rb | 0 test/irb/test_command.rb | 33 -------- test/irb/test_debugger_integration.rb | 6 +- test/irb/yamatanooroti/test_rendering.rb | 2 +- 16 files changed, 191 insertions(+), 119 deletions(-) delete mode 100644 lib/irb/command/show_cmds.rb rename test/irb/{cmd => command}/test_force_exit.rb (100%) create mode 100644 test/irb/command/test_help.rb rename test/irb/{cmd => command}/test_show_source.rb (100%) 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 From e14d6955dbbc669d8eb60a35f2e3ffb77d94e683 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 19 Feb 2024 22:07:53 +0900 Subject: [PATCH 088/263] Load rubygems explicitly for tests of test/irb (#879) --- test/irb/helper.rb | 1 + test/irb/test_color.rb | 1 - test/irb/test_color_printer.rb | 1 - test/irb/test_command.rb | 1 - test/irb/test_context.rb | 1 - test/irb/test_tracer.rb | 1 - test/irb/test_workspace.rb | 1 - 7 files changed, 1 insertion(+), 6 deletions(-) diff --git a/test/irb/helper.rb b/test/irb/helper.rb index 8d32e45ad..1614b42ad 100644 --- a/test/irb/helper.rb +++ b/test/irb/helper.rb @@ -1,5 +1,6 @@ require "test/unit" require "pathname" +require "rubygems" begin require_relative "../lib/helper" diff --git a/test/irb/test_color.rb b/test/irb/test_color.rb index c9d2512dc..72e036eab 100644 --- a/test/irb/test_color.rb +++ b/test/irb/test_color.rb @@ -1,6 +1,5 @@ # frozen_string_literal: false require 'irb/color' -require 'rubygems' require 'stringio' require_relative "helper" diff --git a/test/irb/test_color_printer.rb b/test/irb/test_color_printer.rb index f162b8854..c2c624d86 100644 --- a/test/irb/test_color_printer.rb +++ b/test/irb/test_color_printer.rb @@ -1,6 +1,5 @@ # frozen_string_literal: false require 'irb/color_printer' -require 'rubygems' require 'stringio' require_relative "helper" diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index 9ab7d5c81..e27fac67b 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -1,5 +1,4 @@ # frozen_string_literal: false -require "rubygems" require "irb" require_relative "helper" diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index 059c48676..1859339da 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -1,7 +1,6 @@ # frozen_string_literal: false require 'tempfile' require 'irb' -require 'rubygems' require_relative "helper" diff --git a/test/irb/test_tracer.rb b/test/irb/test_tracer.rb index 199c9586b..540f8be13 100644 --- a/test/irb/test_tracer.rb +++ b/test/irb/test_tracer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: false require 'tempfile' require 'irb' -require 'rubygems' require_relative "helper" diff --git a/test/irb/test_workspace.rb b/test/irb/test_workspace.rb index 8c7310578..199ce95a3 100644 --- a/test/irb/test_workspace.rb +++ b/test/irb/test_workspace.rb @@ -1,6 +1,5 @@ # frozen_string_literal: false require 'tempfile' -require 'rubygems' require 'irb' require 'irb/workspace' require 'irb/color' From ee83068b4710bb0ea14887ea32cf5be209879d14 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 19 Feb 2024 22:08:28 +0900 Subject: [PATCH 089/263] Invalid encoding symbols now raise SyntaxError in 3.4 (#880) ruby/ruby@d9b61e228ff86f00c195abb6c3e0e6f4525385e0 --- test/irb/test_context.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index 1859339da..5812ea041 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -42,6 +42,10 @@ def test_evaluate_with_encoding_error_without_lineno omit "Remove me after https://github.com/ruby/prism/issues/2129 is addressed and adopted in TruffleRuby" end + if RUBY_VERSION >= "3.4." + omit "Now raises SyntaxError" + end + assert_raise_with_message(EncodingError, /invalid symbol/) { @context.evaluate(%q[:"\xAE"], 1) # The backtrace of this invalid encoding hash doesn't contain lineno. From 675177894851eee96478a65bcad6f32d97d4726b Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 22 Feb 2024 22:16:41 +0900 Subject: [PATCH 090/263] Delete IRB::NotImplementedError (#878) --- lib/irb/input-method.rb | 2 +- lib/irb/lc/error.rb | 5 ----- lib/irb/lc/ja/error.rb | 5 ----- lib/irb/output-method.rb | 8 +------- 4 files changed, 2 insertions(+), 18 deletions(-) diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index 7e8396376..c0e6c7e38 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -20,7 +20,7 @@ class InputMethod # # See IO#gets for more information. def gets - fail NotImplementedError, "gets" + fail NotImplementedError end public :gets diff --git a/lib/irb/lc/error.rb b/lib/irb/lc/error.rb index a5ec15086..25f4ba2e1 100644 --- a/lib/irb/lc/error.rb +++ b/lib/irb/lc/error.rb @@ -12,11 +12,6 @@ def initialize(val) super("Unrecognized switch: #{val}") end end - class NotImplementedError < StandardError - def initialize(val) - super("Need to define `#{val}'") - end - end class CantReturnToNormalMode < StandardError def initialize super("Can't return to normal mode.") diff --git a/lib/irb/lc/ja/error.rb b/lib/irb/lc/ja/error.rb index 50d72c4a1..756b8b9af 100644 --- a/lib/irb/lc/ja/error.rb +++ b/lib/irb/lc/ja/error.rb @@ -12,11 +12,6 @@ def initialize(val) super("スイッチ(#{val})が分りません") end end - class NotImplementedError < StandardError - def initialize(val) - super("`#{val}'の定義が必要です") - end - end class CantReturnToNormalMode < StandardError def initialize super("Normalモードに戻れません.") diff --git a/lib/irb/output-method.rb b/lib/irb/output-method.rb index f5ea57111..06fd37157 100644 --- a/lib/irb/output-method.rb +++ b/lib/irb/output-method.rb @@ -9,16 +9,10 @@ module IRB # IRB::Notifier. You can define your own output method to use with Irb.new, # or Context.new class OutputMethod - class NotImplementedError < StandardError - def initialize(val) - super("Need to define `#{val}'") - end - end - # Open this method to implement your own output method, raises a # NotImplementedError if you don't define #print in your own class. def print(*opts) - raise NotImplementedError, "print" + raise NotImplementedError end # Prints the given +opts+, with a newline delimiter. From 67eba5401b1a3162b9fcb578d0d202cb537fdfd1 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 23 Feb 2024 18:02:14 +0800 Subject: [PATCH 091/263] Unroll extension method generation (#882) * Unroll extension method generation Given we only have 2 remaining extension setter methods, both of which only take 1 argument and don't have any alias, the current method generation logic is overly complicated. This commit simplifies the method generation logic by simply defining the methods directly in the `IRB::Context` class. * Fix use_loader extension --- lib/irb/command.rb | 40 --------------------------------------- lib/irb/context.rb | 12 ++++++++++++ lib/irb/ext/use-loader.rb | 4 ++-- 3 files changed, 14 insertions(+), 42 deletions(-) diff --git a/lib/irb/command.rb b/lib/irb/command.rb index c84474e63..83ec3b1b4 100644 --- a/lib/irb/command.rb +++ b/lib/irb/command.rb @@ -315,44 +315,4 @@ def self.extend_object(obj) install_extend_commands end - - # Extends methods for the Context module - module ContextExtender - CE = ContextExtender # :nodoc: - - @EXTEND_COMMANDS = [ - [:eval_history=, "ext/eval_history.rb"], - [:use_loader=, "ext/use-loader.rb"], - ] - - # Installs the default context extensions as irb commands: - # - # Context#eval_history=:: +irb/ext/history.rb+ - # Context#use_tracer=:: +irb/ext/tracer.rb+ - # Context#use_loader=:: +irb/ext/use-loader.rb+ - def self.install_extend_commands - for args in @EXTEND_COMMANDS - def_extend_command(*args) - end - end - - # Evaluate the given +command+ from the given +load_file+ on the Context - # module. - # - # Will also define any given +aliases+ for the method. - def self.def_extend_command(cmd_name, load_file, *aliases) - line = __LINE__; Context.module_eval %[ - def #{cmd_name}(*opts, &b) - Context.module_eval {remove_method(:#{cmd_name})} - require_relative "#{load_file}" - __send__ :#{cmd_name}, *opts, &b - end - for ali in aliases - alias_method ali, cmd_name - end - ], __FILE__, line - end - - CE.install_extend_commands - end end diff --git a/lib/irb/context.rb b/lib/irb/context.rb index e50958978..8eef3dff4 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -166,6 +166,18 @@ def use_tracer=(val) IRB.conf[:USE_TRACER] = val end + def eval_history=(val) + self.class.remove_method(__method__) + require_relative "ext/eval_history" + __send__(__method__, val) + end + + def use_loader=(val) + self.class.remove_method(__method__) + require_relative "ext/use-loader" + __send__(__method__, val) + end + private def build_completor completor_type = IRB.conf[:COMPLETOR] case completor_type diff --git a/lib/irb/ext/use-loader.rb b/lib/irb/ext/use-loader.rb index c5ecbe284..b22e6d2bf 100644 --- a/lib/irb/ext/use-loader.rb +++ b/lib/irb/ext/use-loader.rb @@ -4,7 +4,7 @@ # by Keiju ISHITSUKA(keiju@ruby-lang.org) # -require_relative "../cmd/load" +require_relative "../command/load" require_relative "loader" class Object @@ -49,7 +49,7 @@ def use_loader=(opt) if IRB.conf[:USE_LOADER] != opt IRB.conf[:USE_LOADER] = opt if opt - if !$".include?("irb/cmd/load") + if !$".include?("irb/command/load") end (class<<@workspace.main;self;end).instance_eval { alias_method :load, :irb_load From 83d90550c2db6fa5223f4bc0ae7dae47741a5ed6 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 23 Feb 2024 18:53:50 +0800 Subject: [PATCH 092/263] Turn on frozen literal in files (#881) --- lib/irb/command.rb | 2 +- lib/irb/command/base.rb | 2 +- lib/irb/command/chws.rb | 2 +- lib/irb/command/irb_info.rb | 2 +- lib/irb/command/load.rb | 2 +- lib/irb/command/pushws.rb | 2 +- lib/irb/command/subirb.rb | 2 +- lib/irb/completion.rb | 2 +- lib/irb/context.rb | 2 +- lib/irb/ext/change-ws.rb | 2 +- lib/irb/ext/eval_history.rb | 2 +- lib/irb/ext/loader.rb | 2 +- lib/irb/ext/multi-irb.rb | 2 +- lib/irb/ext/tracer.rb | 2 +- lib/irb/ext/use-loader.rb | 2 +- lib/irb/ext/workspaces.rb | 2 +- lib/irb/frame.rb | 2 +- lib/irb/help.rb | 2 +- lib/irb/init.rb | 2 +- lib/irb/input-method.rb | 2 +- lib/irb/lc/error.rb | 2 +- lib/irb/lc/ja/error.rb | 2 +- lib/irb/locale.rb | 2 +- lib/irb/notifier.rb | 2 +- lib/irb/output-method.rb | 2 +- lib/irb/ruby-lex.rb | 2 +- lib/irb/version.rb | 2 +- lib/irb/workspace.rb | 2 +- lib/irb/ws-for-case-2.rb | 2 +- lib/irb/xmp.rb | 2 +- 30 files changed, 30 insertions(+), 30 deletions(-) diff --git a/lib/irb/command.rb b/lib/irb/command.rb index 83ec3b1b4..4dc2994ba 100644 --- a/lib/irb/command.rb +++ b/lib/irb/command.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # irb/command.rb - irb command # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/command/base.rb b/lib/irb/command/base.rb index 880b781a4..3ce4f4d6c 100644 --- a/lib/irb/command/base.rb +++ b/lib/irb/command/base.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # nop.rb - # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/command/chws.rb b/lib/irb/command/chws.rb index 341d51615..e1047686c 100644 --- a/lib/irb/command/chws.rb +++ b/lib/irb/command/chws.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # change-ws.rb - # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/command/irb_info.rb b/lib/irb/command/irb_info.rb index 7fd3e2104..00ce7a5f0 100644 --- a/lib/irb/command/irb_info.rb +++ b/lib/irb/command/irb_info.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true module IRB # :stopdoc: diff --git a/lib/irb/command/load.rb b/lib/irb/command/load.rb index 0558bc83b..9e89a7b7f 100644 --- a/lib/irb/command/load.rb +++ b/lib/irb/command/load.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # load.rb - # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/command/pushws.rb b/lib/irb/command/pushws.rb index a6fcd6a16..fadd10d6a 100644 --- a/lib/irb/command/pushws.rb +++ b/lib/irb/command/pushws.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # change-ws.rb - # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/command/subirb.rb b/lib/irb/command/subirb.rb index 0a75706f5..5cc7b8c6f 100644 --- a/lib/irb/command/subirb.rb +++ b/lib/irb/command/subirb.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # multi.rb - # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index 7b9c1bbbd..ceb6eb087 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # irb/completion.rb - # by Keiju ISHITSUKA(keiju@ishitsuka.com) diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 8eef3dff4..d2d0bed52 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # irb/context.rb - irb context # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/ext/change-ws.rb b/lib/irb/ext/change-ws.rb index c0f810a4c..ec29f7a2b 100644 --- a/lib/irb/ext/change-ws.rb +++ b/lib/irb/ext/change-ws.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # irb/ext/cb.rb - # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/ext/eval_history.rb b/lib/irb/ext/eval_history.rb index 1a04178b4..316e0b312 100644 --- a/lib/irb/ext/eval_history.rb +++ b/lib/irb/ext/eval_history.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # history.rb - # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/ext/loader.rb b/lib/irb/ext/loader.rb index 5bd25546f..df5aaa8e5 100644 --- a/lib/irb/ext/loader.rb +++ b/lib/irb/ext/loader.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # loader.rb - # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/ext/multi-irb.rb b/lib/irb/ext/multi-irb.rb index 1c2048913..944c04c82 100644 --- a/lib/irb/ext/multi-irb.rb +++ b/lib/irb/ext/multi-irb.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # irb/multi-irb.rb - multiple irb module # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/ext/tracer.rb b/lib/irb/ext/tracer.rb index d498b5320..fd6daa88a 100644 --- a/lib/irb/ext/tracer.rb +++ b/lib/irb/ext/tracer.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # irb/lib/tracer.rb - # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/ext/use-loader.rb b/lib/irb/ext/use-loader.rb index b22e6d2bf..f47d1cab0 100644 --- a/lib/irb/ext/use-loader.rb +++ b/lib/irb/ext/use-loader.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # use-loader.rb - # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/ext/workspaces.rb b/lib/irb/ext/workspaces.rb index 9defc3e17..f447948fb 100644 --- a/lib/irb/ext/workspaces.rb +++ b/lib/irb/ext/workspaces.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # push-ws.rb - # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/frame.rb b/lib/irb/frame.rb index 14768bd8f..4b697c871 100644 --- a/lib/irb/frame.rb +++ b/lib/irb/frame.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # frame.rb - # by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) diff --git a/lib/irb/help.rb b/lib/irb/help.rb index 6861d7efc..c97656f0c 100644 --- a/lib/irb/help.rb +++ b/lib/irb/help.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # irb/help.rb - print usage module # by Keiju ISHITSUKA(keiju@ishitsuka.com) diff --git a/lib/irb/init.rb b/lib/irb/init.rb index 8acfdea8e..107cba592 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # irb/init.rb - irb initialize module # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index c0e6c7e38..a483ec671 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # irb/input-method.rb - input methods used irb # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/lc/error.rb b/lib/irb/lc/error.rb index 25f4ba2e1..9041ec4d6 100644 --- a/lib/irb/lc/error.rb +++ b/lib/irb/lc/error.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # irb/lc/error.rb - # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/lc/ja/error.rb b/lib/irb/lc/ja/error.rb index 756b8b9af..f7f2b13c4 100644 --- a/lib/irb/lc/ja/error.rb +++ b/lib/irb/lc/ja/error.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # irb/lc/ja/error.rb - # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/locale.rb b/lib/irb/locale.rb index 1510af2ab..2abcc7354 100644 --- a/lib/irb/locale.rb +++ b/lib/irb/locale.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # irb/locale.rb - internationalization module # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/notifier.rb b/lib/irb/notifier.rb index 612de3df1..dc1b9ef14 100644 --- a/lib/irb/notifier.rb +++ b/lib/irb/notifier.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # notifier.rb - output methods used by irb # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/output-method.rb b/lib/irb/output-method.rb index 06fd37157..69942f47a 100644 --- a/lib/irb/output-method.rb +++ b/lib/irb/output-method.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # output-method.rb - output methods used by irb # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb index 754acfad0..cfe36be83 100644 --- a/lib/irb/ruby-lex.rb +++ b/lib/irb/ruby-lex.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # irb/ruby-lex.rb - ruby lexcal analyzer # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/version.rb b/lib/irb/version.rb index 585c83702..d9753d3eb 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # irb/version.rb - irb version definition file # by Keiju ISHITSUKA(keiju@ishitsuka.com) diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb index aaf2f335e..4c3b5e425 100644 --- a/lib/irb/workspace.rb +++ b/lib/irb/workspace.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # irb/workspace-binding.rb - # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/ws-for-case-2.rb b/lib/irb/ws-for-case-2.rb index a0f617e4e..03f42d73d 100644 --- a/lib/irb/ws-for-case-2.rb +++ b/lib/irb/ws-for-case-2.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # irb/ws-for-case-2.rb - # by Keiju ISHITSUKA(keiju@ruby-lang.org) diff --git a/lib/irb/xmp.rb b/lib/irb/xmp.rb index de2908942..b1bc53283 100644 --- a/lib/irb/xmp.rb +++ b/lib/irb/xmp.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # xmp.rb - irb version of gotoken xmp # by Keiju ISHITSUKA(Nippon Rational Inc.) From 4bfdb23ae60eac7af52dabfb38c75ea6c2c13fa8 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 23 Feb 2024 21:32:47 +0900 Subject: [PATCH 093/263] Remove remaining `frozen_string_literal: false` in lib/ (#883) --- lib/irb.rb | 8 ++++---- lib/irb/inspector.rb | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 09ea9256c..f5a7d24da 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # irb.rb - irb main module # by Keiju ISHITSUKA(keiju@ruby-lang.org) @@ -903,8 +903,8 @@ class Irb # parsed as a :method_add_arg and the output won't be suppressed PROMPT_MAIN_TRUNCATE_LENGTH = 32 - PROMPT_MAIN_TRUNCATE_OMISSION = '...'.freeze - CONTROL_CHARACTERS_PATTERN = "\x00-\x1F".freeze + PROMPT_MAIN_TRUNCATE_OMISSION = '...' + CONTROL_CHARACTERS_PATTERN = "\x00-\x1F" # Returns the current context of this irb session attr_reader :context @@ -1056,7 +1056,7 @@ def readmultiline return read_input(prompt) if @context.io.respond_to?(:check_termination) # nomultiline - code = '' + code = +'' line_offset = 0 loop do line = read_input(prompt) diff --git a/lib/irb/inspector.rb b/lib/irb/inspector.rb index 7bdd855b9..667087ccb 100644 --- a/lib/irb/inspector.rb +++ b/lib/irb/inspector.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # irb/inspector.rb - inspect methods # by Keiju ISHITSUKA(keiju@ruby-lang.org) @@ -113,7 +113,7 @@ def inspect_value(v) Color.colorize_code(v.inspect, colorable: Color.colorable? && Color.inspect_colorable?(v)) } Inspector.def_inspector([true, :pp, :pretty_inspect], proc{require_relative "color_printer"}){|v| - IRB::ColorPrinter.pp(v, '').chomp + IRB::ColorPrinter.pp(v, +'').chomp } Inspector.def_inspector([:yaml, :YAML], proc{require "yaml"}){|v| begin From 820b9e8dd68f8df0070a40d72b7023f4d715b9d3 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 23 Feb 2024 21:32:55 +0900 Subject: [PATCH 094/263] Remove workaround for empty lines in dynamic_prompt (#884) --- lib/irb.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/irb.rb b/lib/irb.rb index f5a7d24da..559888586 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1138,7 +1138,6 @@ def configure_io end if @context.io.respond_to?(:dynamic_prompt) @context.io.dynamic_prompt do |lines| - lines << '' if lines.empty? tokens = RubyLex.ripper_lex_without_warning(lines.map{ |l| l + "\n" }.join, local_variables: @context.local_variables) line_results = IRB::NestingParser.parse_by_line(tokens) tokens_until_line = [] From f6d489658ec2e8feb6dbe89a2c9406bc4511a660 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 23 Feb 2024 22:11:16 +0900 Subject: [PATCH 095/263] Remove useless loaded file check (#885) --- lib/irb/ext/use-loader.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/irb/ext/use-loader.rb b/lib/irb/ext/use-loader.rb index f47d1cab0..77c53b7d5 100644 --- a/lib/irb/ext/use-loader.rb +++ b/lib/irb/ext/use-loader.rb @@ -49,8 +49,6 @@ def use_loader=(opt) if IRB.conf[:USE_LOADER] != opt IRB.conf[:USE_LOADER] = opt if opt - if !$".include?("irb/command/load") - end (class<<@workspace.main;self;end).instance_eval { alias_method :load, :irb_load alias_method :require, :irb_require From 7b323ee514921042fb426f00bd93e684a82e0d0e Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 25 Feb 2024 23:20:56 +0800 Subject: [PATCH 096/263] Refactor IRB::Context#prompting (#889) --- lib/irb/context.rb | 4 +--- lib/irb/input-method.rb | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/irb/context.rb b/lib/irb/context.rb index d2d0bed52..964732703 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -479,9 +479,7 @@ def verbose? # StdioInputMethod or RelineInputMethod or ReadlineInputMethod, see #io # for more information. def prompting? - verbose? || (STDIN.tty? && @io.kind_of?(StdioInputMethod) || - @io.kind_of?(RelineInputMethod) || - (defined?(ReadlineInputMethod) && @io.kind_of?(ReadlineInputMethod))) + verbose? || @io.prompting? end # The return value of the last statement evaluated. diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index a483ec671..c19983092 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -44,6 +44,10 @@ def support_history_saving? false end + def prompting? + false + end + # For debug message def inspect 'Abstract InputMethod' @@ -91,6 +95,10 @@ def readable_after_eof? true end + def prompting? + STDIN.tty? + end + # Returns the current line number for #io. # # #line counts the number of times #gets is called. @@ -220,6 +228,10 @@ def eof? @eof end + def prompting? + true + end + # For debug message def inspect readline_impl = (defined?(Reline) && Readline == Reline) ? 'Reline' : 'ext/readline' @@ -467,6 +479,10 @@ def eof? @eof end + def prompting? + true + end + # For debug message def inspect config = Reline::Config.new From 06f43aadb3a12626ee8024ff3df380edd9654559 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 25 Feb 2024 23:22:30 +0800 Subject: [PATCH 097/263] Add help messages to `show_source` and `show_doc` commands (#887) * Add help message to the show_source command * Add help message to the show_doc command --- lib/irb/command/show_doc.rb | 16 +++++++++++++++- lib/irb/command/show_source.rb | 16 +++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/lib/irb/command/show_doc.rb b/lib/irb/command/show_doc.rb index dca10ec4b..4dde28bee 100644 --- a/lib/irb/command/show_doc.rb +++ b/lib/irb/command/show_doc.rb @@ -15,7 +15,21 @@ def transform_args(args) end category "Context" - description "Enter the mode to look up RI documents." + 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(*names) require 'rdoc/ri/driver' diff --git a/lib/irb/command/show_source.rb b/lib/irb/command/show_source.rb index cc783e753..32bdf74d3 100644 --- a/lib/irb/command/show_source.rb +++ b/lib/irb/command/show_source.rb @@ -8,7 +8,21 @@ module IRB module Command class ShowSource < Base category "Context" - description "Show the source code of a given method or constant." + 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 class << self def transform_args(args) From 61560b99b3e3113ed2927346acda510d1fa4cea4 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 1 Mar 2024 23:51:14 +0800 Subject: [PATCH 098/263] Restructure workspace management (#888) * Remove dead irb_level method * Restructure workspace management Currently, workspace is an attribute of IRB::Context in most use cases. But when some workspace commands are used, like `pushws` or `popws`, a workspace will be created and used along side with the original workspace attribute. This complexity is not necessary and will prevent us from expanding multi-workspace support in the future. So this commit introduces a @workspace_stack ivar to IRB::Context so IRB can have a more natural way to manage workspaces. * Fix pushws without args * Always display workspace stack after related commands are used --- lib/irb.rb | 15 ++++++------ lib/irb/command/pushws.rb | 18 +++++++++++++- lib/irb/context.rb | 26 +++++++++++++------- lib/irb/ext/change-ws.rb | 6 ++--- lib/irb/ext/eval_history.rb | 4 ++-- lib/irb/ext/use-loader.rb | 4 ++-- lib/irb/ext/workspaces.rb | 43 ++++++++------------------------- test/irb/test_command.rb | 48 +++++++++++++++++++++---------------- 8 files changed, 86 insertions(+), 78 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 559888586..0c481ff1d 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -933,7 +933,7 @@ def debug_break def debug_readline(binding) workspace = IRB::WorkSpace.new(binding) - context.workspace = workspace + context.replace_workspace(workspace) context.workspace.load_commands_to_main @line_no += 1 @@ -1269,12 +1269,11 @@ def suspend_name(path = nil, name = nil) # Used by the irb command +irb_load+, see IRB@IRB+Sessions for more # information. def suspend_workspace(workspace) - @context.workspace, back_workspace = workspace, @context.workspace - begin - yield back_workspace - ensure - @context.workspace = back_workspace - end + current_workspace = @context.workspace + @context.replace_workspace(workspace) + yield + ensure + @context.replace_workspace current_workspace end # Evaluates the given block using the given +input_method+ as the @@ -1534,7 +1533,7 @@ def irb(show_code: true) if debugger_irb # If we're already in a debugger session, set the workspace and irb_path for the original IRB instance - debugger_irb.context.workspace = workspace + debugger_irb.context.replace_workspace(workspace) debugger_irb.context.irb_path = irb_path # If we've started a debugger session and hit another binding.irb, we don't want to start an IRB session # instead, we want to resume the irb:rdbg session. diff --git a/lib/irb/command/pushws.rb b/lib/irb/command/pushws.rb index fadd10d6a..888fe466f 100644 --- a/lib/irb/command/pushws.rb +++ b/lib/irb/command/pushws.rb @@ -15,7 +15,23 @@ class Workspaces < Base description "Show workspaces." def execute(*obj) - irb_context.workspaces.collect{|ws| ws.main} + 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 diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 964732703..60dfb9668 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -22,10 +22,11 @@ class Context # +other+:: uses this as InputMethod def initialize(irb, workspace = nil, input_method = nil) @irb = irb + @workspace_stack = [] if workspace - @workspace = workspace + @workspace_stack << workspace else - @workspace = WorkSpace.new + @workspace_stack << WorkSpace.new end @thread = Thread.current @@ -229,15 +230,24 @@ def history_file=(hist) IRB.conf[:HISTORY_FILE] = hist end + # Workspace in the current context. + def workspace + @workspace_stack.last + end + + # Replace the current workspace with the given +workspace+. + def replace_workspace(workspace) + @workspace_stack.pop + @workspace_stack.push(workspace) + end + # The top-level workspace, see WorkSpace#main def main - @workspace.main + workspace.main end # The toplevel workspace, see #home_workspace attr_reader :workspace_home - # WorkSpace in the current context. - attr_accessor :workspace # The current thread in this context. attr_reader :thread # The current input method. @@ -489,7 +499,7 @@ def prompting? # to #last_value. def set_last_value(value) @last_value = value - @workspace.local_variable_set :_, value + workspace.local_variable_set :_, value end # Sets the +mode+ of the prompt in this context. @@ -585,7 +595,7 @@ def evaluate(line, line_no) # :nodoc: if IRB.conf[:MEASURE] && !IRB.conf[:MEASURE_CALLBACKS].empty? last_proc = proc do - result = @workspace.evaluate(line, @eval_path, line_no) + result = workspace.evaluate(line, @eval_path, line_no) end IRB.conf[:MEASURE_CALLBACKS].inject(last_proc) do |chain, item| _name, callback, arg = item @@ -596,7 +606,7 @@ def evaluate(line, line_no) # :nodoc: end end.call else - result = @workspace.evaluate(line, @eval_path, line_no) + result = workspace.evaluate(line, @eval_path, line_no) end set_last_value(result) diff --git a/lib/irb/ext/change-ws.rb b/lib/irb/ext/change-ws.rb index ec29f7a2b..87fe03e23 100644 --- a/lib/irb/ext/change-ws.rb +++ b/lib/irb/ext/change-ws.rb @@ -12,7 +12,7 @@ def home_workspace if defined? @home_workspace @home_workspace else - @home_workspace = @workspace + @home_workspace = workspace end end @@ -25,11 +25,11 @@ def home_workspace # See IRB::WorkSpace.new for more information. def change_workspace(*_main) if _main.empty? - @workspace = home_workspace + replace_workspace(home_workspace) return main end - @workspace = WorkSpace.new(_main[0]) + replace_workspace(WorkSpace.new(_main[0])) if !(class< 1 + # swap the top two workspaces + previous_workspace, current_workspace = @workspace_stack.pop(2) + @workspace_stack.push current_workspace, previous_workspace + end + else + @workspace_stack.push WorkSpace.new(workspace.binding, _main[0]) + if !(class< 1 end end end diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index e27fac67b..d6c8c534f 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -482,7 +482,8 @@ class Foo; end class CwwsTest < WorkspaceCommandTestCase def test_cwws_returns_the_current_workspace_object out, err = execute_lines( - "cwws.class", + "cwws", + "self.class" ) assert_empty err @@ -493,51 +494,56 @@ def test_cwws_returns_the_current_workspace_object class PushwsTest < WorkspaceCommandTestCase def test_pushws_switches_to_new_workspace_and_pushes_the_current_one_to_the_stack out, err = execute_lines( - "pushws #{self.class}::Foo.new\n", - "cwws.class", + "pushws #{self.class}::Foo.new", + "self.class", + "popws", + "self.class" ) assert_empty err - assert_include(out, "#{self.class}::Foo") + + assert_match(/=> #{self.class}::Foo\n/, out) + assert_match(/=> #{self.class}\n$/, out) end def test_pushws_extends_the_new_workspace_with_command_bundle out, err = execute_lines( - "pushws Object.new\n", + "pushws Object.new", "self.singleton_class.ancestors" ) assert_empty err assert_include(out, "IRB::ExtendCommandBundle") end - def test_pushws_prints_help_message_when_no_arg_is_given + def test_pushws_prints_workspace_stack_when_no_arg_is_given out, err = execute_lines( - "pushws\n", + "pushws", ) assert_empty err - assert_match(/No other workspace/, out) + assert_include(out, "[#]") end - end - class WorkspacesTest < WorkspaceCommandTestCase - def test_workspaces_returns_the_array_of_non_main_workspaces + def test_pushws_without_argument_swaps_the_top_two_workspaces out, err = execute_lines( - "pushws #{self.class}::Foo.new\n", - "workspaces.map { |w| w.class.name }", + "pushws #{self.class}::Foo.new", + "self.class", + "pushws", + "self.class" ) - assert_empty err - # self.class::Foo would be the current workspace - # self.class would be the old workspace that's pushed to the stack - assert_include(out, "=> [\"#{self.class}\"]") + assert_match(/=> #{self.class}::Foo\n/, out) + assert_match(/=> #{self.class}\n$/, out) end + end - def test_workspaces_returns_empty_array_when_no_workspaces_were_added + class WorkspacesTest < WorkspaceCommandTestCase + def test_workspaces_returns_the_stack_of_workspaces out, err = execute_lines( - "workspaces.map(&:to_s)", + "pushws #{self.class}::Foo.new\n", + "workspaces", ) assert_empty err - assert_include(out, "=> []") + assert_match(/\[#, #]\n/, out) end end @@ -557,7 +563,7 @@ def test_popws_prints_help_message_if_the_workspace_is_empty "popws\n", ) assert_empty err - assert_match(/workspace stack empty/, out) + assert_match(/\[#\]\n/, out) end end From 7efadc243b2c9b88e67317a9182bd9e75559ec2c Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 1 Mar 2024 14:51:57 -0500 Subject: [PATCH 099/263] Escape closing square brackets in regexp Fixes the following warning: test/irb/test_command.rb:546: warning: regular expression has ']' without escape --- test/irb/test_command.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index d6c8c534f..5c65a72ee 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -543,7 +543,7 @@ def test_workspaces_returns_the_stack_of_workspaces ) assert_empty err - assert_match(/\[#, #]\n/, out) + assert_match(/\[#, #\]\n/, out) end end From b53ebc6655f1ee548f34e068bf09d3e0bd7b3e85 Mon Sep 17 00:00:00 2001 From: Haroon Ahmed Date: Tue, 5 Mar 2024 16:07:39 +0000 Subject: [PATCH 100/263] Add the ability to fetch and load multiple irb files. (#859) This allows hierarchy when loading rc files for example both files below are loaded; project/.irbrc ~/.irbrc Co-authored-by: Stan Lo --- lib/irb/command/irb_info.rb | 3 +- lib/irb/history.rb | 4 +- lib/irb/init.rb | 34 ++++++----- test/irb/test_command.rb | 7 ++- test/irb/test_history.rb | 9 +-- test/irb/test_init.rb | 112 +++++++++++++++++++++++++++++++++--- 6 files changed, 139 insertions(+), 30 deletions(-) diff --git a/lib/irb/command/irb_info.rb b/lib/irb/command/irb_info.rb index 00ce7a5f0..31e6d77d2 100644 --- a/lib/irb/command/irb_info.rb +++ b/lib/irb/command/irb_info.rb @@ -13,7 +13,8 @@ def execute 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) + rc_files = IRB.rc_files.select { |rc| File.exist?(rc) } + 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? diff --git a/lib/irb/history.rb b/lib/irb/history.rb index dffdee32d..2489f7403 100644 --- a/lib/irb/history.rb +++ b/lib/irb/history.rb @@ -16,7 +16,7 @@ def load_history if history_file = IRB.conf[:HISTORY_FILE] history_file = File.expand_path(history_file) end - history_file = IRB.rc_file("_history") unless history_file + history_file = IRB.rc_files("_history").first unless history_file if File.exist?(history_file) File.open(history_file, "r:#{IRB.conf[:LC_MESSAGES].encoding}") do |f| f.each { |l| @@ -41,7 +41,7 @@ def save_history if history_file = IRB.conf[:HISTORY_FILE] history_file = File.expand_path(history_file) end - history_file = IRB.rc_file("_history") unless history_file + history_file = IRB.rc_files("_history").first unless history_file # Change the permission of a file that already exists[BUG #7694] begin diff --git a/lib/irb/init.rb b/lib/irb/init.rb index 107cba592..5813ff7ae 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -395,33 +395,41 @@ def IRB.parse_opts(argv: ::ARGV) # Run the config file def IRB.run_config if @CONF[:RC] - begin - file = rc_file + rc_files.each do |rc| # Because rc_file always returns `HOME/.irbrc` even if no rc file is present, we can't warn users about missing rc files. # Otherwise, it'd be very noisy. - load file if File.exist?(file) + load rc if File.exist?(rc) rescue StandardError, ScriptError => e - warn "Error loading RC file '#{file}':\n#{e.full_message(highlight: false)}" + warn "Error loading RC file '#{rc}':\n#{e.full_message(highlight: false)}" end end end IRBRC_EXT = "rc" def IRB.rc_file(ext = IRBRC_EXT) + warn "rc_file is deprecated, please use rc_files instead." + rc_files(ext).first + end + + def IRB.rc_files(ext = IRBRC_EXT) if !@CONF[:RC_NAME_GENERATOR] + @CONF[:RC_NAME_GENERATOR] ||= [] + existing_rc_file_generators = [] + rc_file_generators do |rcgen| - @CONF[:RC_NAME_GENERATOR] ||= rcgen - if File.exist?(rcgen.call(IRBRC_EXT)) - @CONF[:RC_NAME_GENERATOR] = rcgen - break - end + @CONF[:RC_NAME_GENERATOR] << rcgen + existing_rc_file_generators << rcgen if File.exist?(rcgen.call(ext)) + end + + if existing_rc_file_generators.any? + @CONF[:RC_NAME_GENERATOR] = existing_rc_file_generators end end - case rc_file = @CONF[:RC_NAME_GENERATOR].call(ext) - when String + + @CONF[:RC_NAME_GENERATOR].map do |rc| + rc_file = rc.call(ext) + fail IllegalRCNameGenerator unless rc_file.is_a?(String) rc_file - else - fail IllegalRCNameGenerator end end diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index 5c65a72ee..8e074e97f 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -75,6 +75,7 @@ def teardown def test_irb_info_multiline FileUtils.touch("#{@tmpdir}/.inputrc") FileUtils.touch("#{@tmpdir}/.irbrc") + FileUtils.touch("#{@tmpdir}/_irbrc") out, err = execute_lines( "irb_info", @@ -86,7 +87,7 @@ def test_irb_info_multiline IRB\sversion:\sirb\s.+\n InputMethod:\sAbstract\sInputMethod\n Completion: .+\n - \.irbrc\spath:\s.+\n + \.irbrc\spaths:.*\.irbrc.*_irbrc\n RUBY_PLATFORM:\s.+\n East\sAsian\sAmbiguous\sWidth:\s\d\n #{@is_win ? 'Code\spage:\s\d+\n' : ''} @@ -110,7 +111,7 @@ def test_irb_info_singleline IRB\sversion:\sirb\s.+\n InputMethod:\sAbstract\sInputMethod\n Completion: .+\n - \.irbrc\spath:\s.+\n + \.irbrc\spaths:\s.+\n RUBY_PLATFORM:\s.+\n East\sAsian\sAmbiguous\sWidth:\s\d\n #{@is_win ? 'Code\spage:\s\d+\n' : ''} @@ -196,7 +197,7 @@ def test_irb_info_lang IRB\sversion:\sirb .+\n InputMethod:\sAbstract\sInputMethod\n Completion: .+\n - \.irbrc\spath: .+\n + \.irbrc\spaths: .+\n RUBY_PLATFORM: .+\n LANG\senv:\sja_JP\.UTF-8\n LC_ALL\senv:\sen_US\.UTF-8\n diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index 2601bcad8..618dcc3d9 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -136,7 +136,7 @@ def test_history_concurrent_use_not_present io.class::HISTORY << 'line1' io.class::HISTORY << 'line2' - history_file = IRB.rc_file("_history") + history_file = IRB.rc_files("_history").first assert_not_send [File, :file?, history_file] File.write(history_file, "line0\n") io.save_history @@ -217,9 +217,10 @@ def assert_history(expected_history, initial_irb_history, input, input_method = backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME") IRB.conf[:LC_MESSAGES] = locale actual_history = nil + history_file = IRB.rc_files("_history").first Dir.mktmpdir("test_irb_history_") do |tmpdir| ENV["HOME"] = tmpdir - File.open(IRB.rc_file("_history"), "w") do |f| + File.open(history_file, "w") do |f| f.write(initial_irb_history) end @@ -229,7 +230,7 @@ def assert_history(expected_history, initial_irb_history, input, input_method = if block_given? previous_history = [] io.class::HISTORY.each { |line| previous_history << line } - yield IRB.rc_file("_history") + yield history_file io.class::HISTORY.clear previous_history.each { |line| io.class::HISTORY << line } end @@ -237,7 +238,7 @@ def assert_history(expected_history, initial_irb_history, input, input_method = io.save_history io.load_history - File.open(IRB.rc_file("_history"), "r") do |f| + File.open(history_file, "r") do |f| actual_history = f.read end end diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb index 64bd96d02..fd1e06e39 100644 --- a/test/irb/test_init.rb +++ b/test/irb/test_init.rb @@ -11,7 +11,7 @@ def setup @backup_env = %w[HOME XDG_CONFIG_HOME IRBRC].each_with_object({}) do |env, hash| hash[env] = ENV.delete(env) end - ENV["HOME"] = @tmpdir = Dir.mktmpdir("test_irb_init_#{$$}") + ENV["HOME"] = @tmpdir = File.realpath(Dir.mktmpdir("test_irb_init_#{$$}")) end def teardown @@ -35,35 +35,133 @@ def test_setup_with_minimum_argv_does_not_change_dollar0 end def test_rc_file + verbose, $VERBOSE = $VERBOSE, nil tmpdir = @tmpdir Dir.chdir(tmpdir) do ENV["XDG_CONFIG_HOME"] = "#{tmpdir}/xdg" IRB.conf[:RC_NAME_GENERATOR] = nil - assert_equal(tmpdir+"/.irb#{IRB::IRBRC_EXT}", IRB.rc_file) + assert_equal(tmpdir+"/.irbrc", IRB.rc_file) assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history")) assert_file.not_exist?(tmpdir+"/xdg") IRB.conf[:RC_NAME_GENERATOR] = nil - FileUtils.touch(tmpdir+"/.irb#{IRB::IRBRC_EXT}") - assert_equal(tmpdir+"/.irb#{IRB::IRBRC_EXT}", IRB.rc_file) + FileUtils.touch(tmpdir+"/.irbrc") + assert_equal(tmpdir+"/.irbrc", IRB.rc_file) assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history")) assert_file.not_exist?(tmpdir+"/xdg") end + ensure + $VERBOSE = verbose end def test_rc_file_in_subdir + verbose, $VERBOSE = $VERBOSE, nil tmpdir = @tmpdir Dir.chdir(tmpdir) do FileUtils.mkdir_p("#{tmpdir}/mydir") Dir.chdir("#{tmpdir}/mydir") do IRB.conf[:RC_NAME_GENERATOR] = nil - assert_equal(tmpdir+"/.irb#{IRB::IRBRC_EXT}", IRB.rc_file) + assert_equal(tmpdir+"/.irbrc", IRB.rc_file) assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history")) IRB.conf[:RC_NAME_GENERATOR] = nil - FileUtils.touch(tmpdir+"/.irb#{IRB::IRBRC_EXT}") - assert_equal(tmpdir+"/.irb#{IRB::IRBRC_EXT}", IRB.rc_file) + FileUtils.touch(tmpdir+"/.irbrc") + assert_equal(tmpdir+"/.irbrc", IRB.rc_file) assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history")) end end + ensure + $VERBOSE = verbose + end + + def test_rc_files + tmpdir = @tmpdir + Dir.chdir(tmpdir) do + ENV["XDG_CONFIG_HOME"] = "#{tmpdir}/xdg" + IRB.conf[:RC_NAME_GENERATOR] = nil + assert_includes IRB.rc_files, tmpdir+"/.irbrc" + assert_includes IRB.rc_files("_history"), tmpdir+"/.irb_history" + assert_file.not_exist?(tmpdir+"/xdg") + IRB.conf[:RC_NAME_GENERATOR] = nil + FileUtils.touch(tmpdir+"/.irbrc") + assert_includes IRB.rc_files, tmpdir+"/.irbrc" + assert_includes IRB.rc_files("_history"), tmpdir+"/.irb_history" + assert_file.not_exist?(tmpdir+"/xdg") + end + end + + def test_rc_files_in_subdir + tmpdir = @tmpdir + Dir.chdir(tmpdir) do + FileUtils.mkdir_p("#{tmpdir}/mydir") + Dir.chdir("#{tmpdir}/mydir") do + IRB.conf[:RC_NAME_GENERATOR] = nil + assert_includes IRB.rc_files, tmpdir+"/.irbrc" + assert_includes IRB.rc_files("_history"), tmpdir+"/.irb_history" + IRB.conf[:RC_NAME_GENERATOR] = nil + FileUtils.touch(tmpdir+"/.irbrc") + assert_includes IRB.rc_files, tmpdir+"/.irbrc" + assert_includes IRB.rc_files("_history"), tmpdir+"/.irb_history" + end + end + end + + def test_rc_files_has_file_from_xdg_env + tmpdir = @tmpdir + ENV["XDG_CONFIG_HOME"] = "#{tmpdir}/xdg" + xdg_config = ENV["XDG_CONFIG_HOME"]+"/irb/irbrc" + + FileUtils.mkdir_p(xdg_config) + + Dir.chdir(tmpdir) do + IRB.conf[:RC_NAME_GENERATOR] = nil + assert_includes IRB.rc_files, xdg_config + end + ensure + ENV["XDG_CONFIG_HOME"] = nil + end + + def test_rc_files_has_file_from_irbrc_env + tmpdir = @tmpdir + ENV["IRBRC"] = "#{tmpdir}/irb" + + FileUtils.mkdir_p(ENV["IRBRC"]) + + Dir.chdir(tmpdir) do + IRB.conf[:RC_NAME_GENERATOR] = nil + assert_includes IRB.rc_files, ENV["IRBRC"] + end + ensure + ENV["IRBRC"] = nil + end + + def test_rc_files_has_file_from_home_env + tmpdir = @tmpdir + ENV["HOME"] = "#{tmpdir}/home" + + FileUtils.mkdir_p(ENV["HOME"]) + + Dir.chdir(tmpdir) do + IRB.conf[:RC_NAME_GENERATOR] = nil + assert_includes IRB.rc_files, ENV["HOME"]+"/.irbrc" + assert_includes IRB.rc_files, ENV["HOME"]+"/.config/irb/irbrc" + end + ensure + ENV["HOME"] = nil + end + + def test_rc_files_contains_non_env_files + tmpdir = @tmpdir + FileUtils.mkdir_p("#{tmpdir}/.irbrc") + FileUtils.mkdir_p("#{tmpdir}/_irbrc") + FileUtils.mkdir_p("#{tmpdir}/irb.rc") + FileUtils.mkdir_p("#{tmpdir}/$irbrc") + + Dir.chdir(tmpdir) do + IRB.conf[:RC_NAME_GENERATOR] = nil + assert_includes IRB.rc_files, tmpdir+"/.irbrc" + assert_includes IRB.rc_files, tmpdir+"/_irbrc" + assert_includes IRB.rc_files, tmpdir+"/irb.rc" + assert_includes IRB.rc_files, tmpdir+"/$irbrc" + end end def test_sigint_restore_default From a2a3cbb0efc55038190660a49f44cd1fef0862b5 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 6 Mar 2024 12:12:45 +0800 Subject: [PATCH 101/263] Prevent `irb_history`'s creation during HistoryTest (#893) Some cases of it currently create `~/.irb_history` files unintentionally while others don't. This is caused by the varying levels of setup/cleanup between them. This commit fixes the issue by wrapping every single test inside a consistent test setup and teardown callbacks. --- test/irb/test_history.rb | 97 +++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 52 deletions(-) diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index 618dcc3d9..b9f395524 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -10,11 +10,23 @@ module TestIRB class HistoryTest < TestCase def setup + @original_verbose, $VERBOSE = $VERBOSE, nil + @tmpdir = Dir.mktmpdir("test_irb_history_") + @backup_home = ENV["HOME"] + @backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME") + @backup_irbrc = ENV.delete("IRBRC") + @backup_default_external = Encoding.default_external + ENV["HOME"] = @tmpdir IRB.conf[:RC_NAME_GENERATOR] = nil end def teardown IRB.conf[:RC_NAME_GENERATOR] = nil + ENV["HOME"] = @backup_home + ENV["XDG_CONFIG_HOME"] = @backup_xdg_config_home + ENV["IRBRC"] = @backup_irbrc + Encoding.default_external = @backup_default_external + $VERBOSE = @original_verbose end class TestInputMethodWithRelineHistory < TestInputMethod @@ -123,35 +135,23 @@ def test_history_concurrent_use_readline end def test_history_concurrent_use_not_present - backup_home = ENV["HOME"] - backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME") - backup_irbrc = ENV.delete("IRBRC") IRB.conf[:LC_MESSAGES] = IRB::Locale.new IRB.conf[:SAVE_HISTORY] = 1 - Dir.mktmpdir("test_irb_history_") do |tmpdir| - ENV["HOME"] = tmpdir - io = TestInputMethodWithRelineHistory.new - io.class::HISTORY.clear - io.load_history - io.class::HISTORY << 'line1' - io.class::HISTORY << 'line2' + io = TestInputMethodWithRelineHistory.new + io.class::HISTORY.clear + io.load_history + io.class::HISTORY << 'line1' + io.class::HISTORY << 'line2' - history_file = IRB.rc_files("_history").first - assert_not_send [File, :file?, history_file] - File.write(history_file, "line0\n") - io.save_history - assert_equal(%w"line0 line1 line2", File.read(history_file).split) - end - ensure - ENV["HOME"] = backup_home - ENV["XDG_CONFIG_HOME"] = backup_xdg_config_home - ENV["IRBRC"] = backup_irbrc + history_file = IRB.rc_files("_history").first + assert_not_send [File, :file?, history_file] + File.write(history_file, "line0\n") + io.save_history + assert_equal(%w"line0 line1 line2", File.read(history_file).split) end def test_history_different_encodings - backup_default_external = Encoding.default_external IRB.conf[:SAVE_HISTORY] = 2 - verbose_bak, $VERBOSE = $VERBOSE, nil Encoding.default_external = Encoding::US_ASCII locale = IRB::Locale.new("C") assert_history(<<~EXPECTED_HISTORY.encode(Encoding::US_ASCII), <<~INITIAL_HISTORY.encode(Encoding::UTF_8), <<~INPUT, locale: locale) @@ -162,9 +162,6 @@ def test_history_different_encodings INITIAL_HISTORY exit INPUT - ensure - Encoding.default_external = backup_default_external - $VERBOSE = verbose_bak end def test_history_does_not_raise_when_history_file_directory_does_not_exist @@ -176,6 +173,11 @@ def test_history_does_not_raise_when_history_file_directory_does_not_exist assert_warn(/history file does not exist/) do io.save_history end + + # assert_warn reverts $VERBOSE to EnvUtil.original_verbose, which is true in some cases + # We want to keep $VERBOSE as nil until teardown is called + # TODO: check if this is an assert_warn issue + $VERBOSE = nil ensure IRB.conf[:HISTORY_FILE] = backup_history_file end @@ -212,35 +214,30 @@ def history_concurrent_use_for_input_method(input_method) end def assert_history(expected_history, initial_irb_history, input, input_method = TestInputMethodWithRelineHistory, locale: IRB::Locale.new) - backup_verbose, $VERBOSE = $VERBOSE, nil - backup_home = ENV["HOME"] - backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME") IRB.conf[:LC_MESSAGES] = locale actual_history = nil history_file = IRB.rc_files("_history").first - Dir.mktmpdir("test_irb_history_") do |tmpdir| - ENV["HOME"] = tmpdir - File.open(history_file, "w") do |f| - f.write(initial_irb_history) - end + ENV["HOME"] = @tmpdir + File.open(history_file, "w") do |f| + f.write(initial_irb_history) + end - io = input_method.new + io = input_method.new + io.class::HISTORY.clear + io.load_history + if block_given? + previous_history = [] + io.class::HISTORY.each { |line| previous_history << line } + yield history_file io.class::HISTORY.clear - io.load_history - if block_given? - previous_history = [] - io.class::HISTORY.each { |line| previous_history << line } - yield history_file - io.class::HISTORY.clear - previous_history.each { |line| io.class::HISTORY << line } - end - input.split.each { |line| io.class::HISTORY << line } - io.save_history + previous_history.each { |line| io.class::HISTORY << line } + end + input.split.each { |line| io.class::HISTORY << line } + io.save_history - io.load_history - File.open(history_file, "r") do |f| - actual_history = f.read - end + io.load_history + File.open(history_file, "r") do |f| + actual_history = f.read end assert_equal(expected_history, actual_history, <<~MESSAGE) expected: @@ -248,10 +245,6 @@ def assert_history(expected_history, initial_irb_history, input, input_method = but actual: #{actual_history} MESSAGE - ensure - $VERBOSE = backup_verbose - ENV["HOME"] = backup_home - ENV["XDG_CONFIG_HOME"] = backup_xdg_config_home end def with_temp_stdio From dc0dd25a3980f981bcd8ec5ce26e581bbb5e1631 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 6 Mar 2024 17:28:12 +0900 Subject: [PATCH 102/263] Clear temporary directories (#894) --- test/irb/test_history.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index b9f395524..ea220a2dd 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -27,6 +27,7 @@ def teardown ENV["IRBRC"] = @backup_irbrc Encoding.default_external = @backup_default_external $VERBOSE = @original_verbose + FileUtils.rm_rf(@tmpdir) end class TestInputMethodWithRelineHistory < TestInputMethod From bd728dc634ac1ec148c884da156af86b8738d8f7 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 6 Mar 2024 22:33:22 +0800 Subject: [PATCH 103/263] Update GH's note syntax The current syntax is outdated and doesn't display as expected anymore. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ac1b164bc..cb6e225b4 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ The `irb` command from your shell will start the interpreter. ## Installation -> **Note** +> [!Note] > > IRB is a default gem of Ruby so you shouldn't need to install it separately. > @@ -56,7 +56,7 @@ $ gem install irb ## Usage -> **Note** +> [!Note] > > We're working hard to match Pry's variety of powerful features in IRB, and you can track our progress or find contribution ideas in [this document](https://github.com/ruby/irb/blob/master/COMPARED_WITH_PRY.md). From a79e84a6929852a5a608a4de60a0e79c94ef9d46 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 7 Mar 2024 10:14:40 +0800 Subject: [PATCH 104/263] Bump version to v1.12.0 (#895) --- lib/irb/version.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irb/version.rb b/lib/irb/version.rb index d9753d3eb..9a7b12766 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -5,7 +5,7 @@ # module IRB # :nodoc: - VERSION = "1.11.2" + VERSION = "1.12.0" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2024-02-07" + @LAST_UPDATE_DATE = "2024-03-06" end From 1a1fbba020f1f504b2cb4d832ee4aa7af6aee0d3 Mon Sep 17 00:00:00 2001 From: alpaca-tc Date: Fri, 8 Mar 2024 18:23:36 +0900 Subject: [PATCH 105/263] rdoc version lock is required (#897) Some features of irb do not work properly when using the old rdoc. I have compared several major versions and found that it works as intended from 4.0.0. This problem occurs when there is a Gemfile.lock is installed with the old rdoc. I don't know why this Gemfile.lock installs an older rdoc than the ruby bundled rdoc, but specifying the version in the gemspec will at least prevent the problem. NOTE: ruby/irb#704 problem does not occur with this change. The following is test code. ``` ### Usage: ruby __FILE__.rb # # input RDoc and Tab # >> RDoc # ### Expect: Display document of RDoc ### Actual: :34:in `load': instance of RDoc::Constant needs to have method `marshal_load' (TypeError) require "bundler/inline" gemfile(true) do source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } gem 'irb' # gem 'rdoc', '~> 4.0.0' gem 'rdoc', '~> 3.12.0' end require 'rdoc' require 'irb' IRB.start ``` --- irb.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irb.gemspec b/irb.gemspec index 6ed327a27..b29002f59 100644 --- a/irb.gemspec +++ b/irb.gemspec @@ -42,5 +42,5 @@ Gem::Specification.new do |spec| spec.required_ruby_version = Gem::Requirement.new(">= 2.7") spec.add_dependency "reline", ">= 0.4.2" - spec.add_dependency "rdoc" + spec.add_dependency "rdoc", ">= 4.0.0" end From 29901e4f2191b74821da94c0f9c656da11d08fc2 Mon Sep 17 00:00:00 2001 From: hogelog Date: Fri, 15 Mar 2024 00:40:21 +0900 Subject: [PATCH 106/263] Add disable_irb command to disable debug of binding.irb (#898) * Add disable_irb command to disable debug of binding.irb * disable_irb doesn't override Kernel.exit Kernel.exit call is removed because disable_irb does not override Kernel.exit and workaround to https://bugs.ruby-lang.org/issues/18234 is not needed. --- lib/irb/command.rb | 7 ++++++- lib/irb/command/disable_irb.rb | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 lib/irb/command/disable_irb.rb diff --git a/lib/irb/command.rb b/lib/irb/command.rb index 4dc2994ba..ef9304923 100644 --- a/lib/irb/command.rb +++ b/lib/irb/command.rb @@ -196,7 +196,12 @@ def irb_context :irb_history, :History, "command/history", [:history, NO_OVERRIDE], [:hist, NO_OVERRIDE], - ] + ], + + [ + :irb_disable_irb, :DisableIrb, "command/disable_irb", + [:disable_irb, NO_OVERRIDE], + ], ] diff --git a/lib/irb/command/disable_irb.rb b/lib/irb/command/disable_irb.rb new file mode 100644 index 000000000..0b00d0302 --- /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 From 35b87cf8933f67cf6e9e0edd1ca7d6395595a758 Mon Sep 17 00:00:00 2001 From: Go Date: Fri, 15 Mar 2024 00:40:59 +0900 Subject: [PATCH 107/263] Rescue from exceptions raised by #name (#899) * Rescue from exceptions raised by #name Irb might terminate if the class overwrites `name` and raise errors. This commit rescue irb from termination. * fix for other unknown patterns --- lib/irb/completion.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index ceb6eb087..b3813e893 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -388,7 +388,7 @@ def retrieve_completion_data(input, bind:, doc_namespace:) if doc_namespace rec_class = rec.is_a?(Module) ? rec : rec.class - "#{rec_class.name}#{sep}#{candidates.find{ |i| i == message }}" + "#{rec_class.name}#{sep}#{candidates.find{ |i| i == message }}" rescue nil else select_message(receiver, message, candidates, sep) end @@ -418,7 +418,7 @@ def retrieve_completion_data(input, bind:, doc_namespace:) vars = (bind.local_variables | bind.eval_instance_variables).collect{|m| m.to_s} perfect_match_var = vars.find{|m| m.to_s == input} if perfect_match_var - eval("#{perfect_match_var}.class.name", bind) + eval("#{perfect_match_var}.class.name", bind) rescue nil else candidates = (bind.eval_methods | bind.eval_private_methods | bind.local_variables | bind.eval_instance_variables | bind.eval_class_constants).collect{|m| m.to_s} candidates |= ReservedWords From e9a175e06b27c182440b63a6b433dc9664fa715c Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 16 Mar 2024 21:51:01 +0800 Subject: [PATCH 108/263] Use markdown format for docs (#890) * Convert irb.rb's document into markdown format * Hide should-be-private top-level methods from docs * Skip xmp.rb's docs * Declare lib/irb.rb's markup do it works in ruby/ruby too * Ignore docs folder --- .gitignore | 1 + Rakefile | 9 + lib/irb.rb | 1188 +++++++++++++++++++------------------- lib/irb/ext/multi-irb.rb | 8 +- lib/irb/help.rb | 2 +- 5 files changed, 619 insertions(+), 589 deletions(-) diff --git a/.gitignore b/.gitignore index ff2a440ae..be35d6352 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ /pkg/ /spec/reports/ /tmp/ +/docs/ Gemfile.lock diff --git a/Rakefile b/Rakefile index b29bf6387..7a51839f7 100644 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,6 @@ require "bundler/gem_tasks" require "rake/testtask" +require "rdoc/task" Rake::TestTask.new(:test) do |t| t.libs << "test" << "test/lib" @@ -42,3 +43,11 @@ Rake::TestTask.new(:test_yamatanooroti) do |t| end task :default => :test + +RDoc::Task.new do |rdoc| + rdoc.title = "IRB" + rdoc.rdoc_files.include("*.md", "lib/**/*.rb") + rdoc.rdoc_files.exclude("lib/irb/xmp.rb") + rdoc.rdoc_dir = "docs" + rdoc.options.push("--copy-files", "LICENSE.txt") +end diff --git a/lib/irb.rb b/lib/irb.rb index 0c481ff1d..8e27aff0b 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true -# + +# :markup: markdown # irb.rb - irb main module # by Keiju ISHITSUKA(keiju@ruby-lang.org) # @@ -22,545 +23,551 @@ require_relative "irb/debug" require_relative "irb/pager" -# == \IRB +# ## IRB # -# \Module \IRB ("Interactive Ruby") provides a shell-like interface -# that supports user interaction with the Ruby interpreter. +# Module IRB ("Interactive Ruby") provides a shell-like interface that supports +# user interaction with the Ruby interpreter. # -# It operates as a read-eval-print loop -# ({REPL}[https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop]) +# It operates as a *read-eval-print loop* +# ([REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)) # that: # -# - _Reads_ each character as you type. -# You can modify the \IRB context to change the way input works. -# See {Input}[rdoc-ref:IRB@Input]. -# - _Evaluates_ the code each time it has read a syntactically complete passage. -# - _Prints_ after evaluating. -# You can modify the \IRB context to change the way output works. -# See {Output}[rdoc-ref:IRB@Output]. +# * ***Reads*** each character as you type. You can modify the IRB context to +# change the way input works. See [Input](rdoc-ref:IRB@Input). +# * ***Evaluates*** the code each time it has read a syntactically complete +# passage. +# * ***Prints*** after evaluating. You can modify the IRB context to change +# the way output works. See [Output](rdoc-ref:IRB@Output). +# # # Example: # -# $ irb -# irb(main):001> File.basename(Dir.pwd) -# => "irb" -# irb(main):002> Dir.entries('.').size -# => 25 -# irb(main):003* Dir.entries('.').select do |entry| -# irb(main):004* entry.start_with?('R') -# irb(main):005> end -# => ["README.md", "Rakefile"] +# $ irb +# irb(main):001> File.basename(Dir.pwd) +# => "irb" +# irb(main):002> Dir.entries('.').size +# => 25 +# irb(main):003* Dir.entries('.').select do |entry| +# irb(main):004* entry.start_with?('R') +# irb(main):005> end +# => ["README.md", "Rakefile"] +# +# The typed input may also include [\IRB-specific +# commands](rdoc-ref:IRB@IRB-Specific+Commands). # -# The typed input may also include -# {\IRB-specific commands}[rdoc-ref:IRB@IRB-Specific+Commands]. +# As seen above, you can start IRB by using the shell command `irb`. # -# As seen above, you can start \IRB by using the shell command +irb+. +# You can stop an IRB session by typing command `exit`: # -# You can stop an \IRB session by typing command +exit+: +# irb(main):006> exit +# $ # -# irb(main):006> exit -# $ +# At that point, IRB calls any hooks found in array `IRB.conf[:AT_EXIT]`, then +# exits. # -# At that point, \IRB calls any hooks found in array IRB.conf[:AT_EXIT], -# then exits. +# ## Startup # -# == Startup +# At startup, IRB: # -# At startup, \IRB: +# 1. Interprets (as Ruby code) the content of the [configuration +# file](rdoc-ref:IRB@Configuration+File) (if given). +# 2. Constructs the initial session context from [hash +# IRB.conf](rdoc-ref:IRB@Hash+IRB.conf) and from default values; the hash +# content may have been affected by [command-line +# options](rdoc-ref:IB@Command-Line+Options), and by direct assignments in +# the configuration file. +# 3. Assigns the context to variable `conf`. +# 4. Assigns command-line arguments to variable `ARGV`. +# 5. Prints the [prompt](rdoc-ref:IRB@Prompt+and+Return+Formats). +# 6. Puts the content of the [initialization +# script](rdoc-ref:IRB@Initialization+Script) onto the IRB shell, just as if +# it were user-typed commands. # -# 1. Interprets (as Ruby code) the content of the -# {configuration file}[rdoc-ref:IRB@Configuration+File] (if given). -# 1. Constructs the initial session context -# from {hash IRB.conf}[rdoc-ref:IRB@Hash+IRB.conf] and from default values; -# the hash content may have been affected -# by {command-line options}[rdoc-ref:IB@Command-Line+Options], -# and by direct assignments in the configuration file. -# 1. Assigns the context to variable +conf+. -# 1. Assigns command-line arguments to variable ARGV. -# 1. Prints the {prompt}[rdoc-ref:IRB@Prompt+and+Return+Formats]. -# 1. Puts the content of the -# {initialization script}[rdoc-ref:IRB@Initialization+Script] -# onto the \IRB shell, just as if it were user-typed commands. # -# === The Command Line +# ### The Command Line # -# On the command line, all options precede all arguments; -# the first item that is not recognized as an option is treated as an argument, -# as are all items that follow. +# On the command line, all options precede all arguments; the first item that is +# not recognized as an option is treated as an argument, as are all items that +# follow. # -# ==== Command-Line Options +# #### Command-Line Options # -# Many command-line options affect entries in hash IRB.conf, -# which in turn affect the initial configuration of the \IRB session. +# Many command-line options affect entries in hash `IRB.conf`, which in turn +# affect the initial configuration of the IRB session. # # Details of the options are described in the relevant subsections below. # -# A cursory list of the \IRB command-line options -# may be seen in the {help message}[https://raw.githubusercontent.com/ruby/irb/master/lib/irb/lc/help-message], -# which is also displayed if you use command-line option --help. +# A cursory list of the IRB command-line options may be seen in the [help +# message](https://raw.githubusercontent.com/ruby/irb/master/lib/irb/lc/help-message), +# which is also displayed if you use command-line option `--help`. # # If you are interested in a specific option, consult the -# {index}[rdoc-ref:doc/irb/indexes.md@Index+of+Command-Line+Options]. +# [index](rdoc-ref:doc/irb/indexes.md@Index+of+Command-Line+Options). # -# ==== Command-Line Arguments +# #### Command-Line Arguments # -# Command-line arguments are passed to \IRB in array +ARGV+: +# Command-line arguments are passed to IRB in array `ARGV`: # -# $ irb --noscript Foo Bar Baz -# irb(main):001> ARGV -# => ["Foo", "Bar", "Baz"] -# irb(main):002> exit -# $ +# $ irb --noscript Foo Bar Baz +# irb(main):001> ARGV +# => ["Foo", "Bar", "Baz"] +# irb(main):002> exit +# $ # -# Command-line option -- causes everything that follows -# to be treated as arguments, even those that look like options: +# Command-line option `--` causes everything that follows to be treated as +# arguments, even those that look like options: # -# $ irb --noscript -- --noscript -- Foo Bar Baz -# irb(main):001> ARGV -# => ["--noscript", "--", "Foo", "Bar", "Baz"] -# irb(main):002> exit -# $ +# $ irb --noscript -- --noscript -- Foo Bar Baz +# irb(main):001> ARGV +# => ["--noscript", "--", "Foo", "Bar", "Baz"] +# irb(main):002> exit +# $ # -# === Configuration File +# ### Configuration File # -# You can initialize \IRB via a configuration file. +# You can initialize IRB via a *configuration file*. # -# If command-line option -f is given, -# no configuration file is looked for. +# If command-line option `-f` is given, no configuration file is looked for. # -# Otherwise, \IRB reads and interprets a configuration file -# if one is available. +# Otherwise, IRB reads and interprets a configuration file if one is available. # # The configuration file can contain any Ruby code, and can usefully include # user code that: # -# - Can then be debugged in \IRB. -# - Configures \IRB itself. -# - Requires or loads files. +# * Can then be debugged in IRB. +# * Configures IRB itself. +# * Requires or loads files. +# # # The path to the configuration file is the first found among: # -# - The value of variable $IRBRC, if defined. -# - The value of variable $XDG_CONFIG_HOME/irb/irbrc, if defined. -# - File $HOME/.irbrc, if it exists. -# - File $HOME/.config/irb/irbrc, if it exists. -# - File +.config/irb/irbrc+ in the current directory, if it exists. -# - File +.irbrc+ in the current directory, if it exists. -# - File +irb.rc+ in the current directory, if it exists. -# - File +_irbrc+ in the current directory, if it exists. -# - File $irbrc in the current directory, if it exists. +# * The value of variable `$IRBRC`, if defined. +# * The value of variable `$XDG_CONFIG_HOME/irb/irbrc`, if defined. +# * File `$HOME/.irbrc`, if it exists. +# * File `$HOME/.config/irb/irbrc`, if it exists. +# * File `.config/irb/irbrc` in the current directory, if it exists. +# * File `.irbrc` in the current directory, if it exists. +# * File `irb.rc` in the current directory, if it exists. +# * File `_irbrc` in the current directory, if it exists. +# * File `$irbrc` in the current directory, if it exists. +# # # If the search fails, there is no configuration file. # -# If the search succeeds, the configuration file is read as Ruby code, -# and so can contain any Ruby programming you like. +# If the search succeeds, the configuration file is read as Ruby code, and so +# can contain any Ruby programming you like. +# +# Method `conf.rc?` returns `true` if a configuration file was read, `false` +# otherwise. Hash entry `IRB.conf[:RC]` also contains that value. # -# \Method conf.rc? returns +true+ if a configuration file was read, -# +false+ otherwise. -# \Hash entry IRB.conf[:RC] also contains that value. +# ### Hash `IRB.conf` # -# === \Hash IRB.conf +# The initial entries in hash `IRB.conf` are determined by: # -# The initial entries in hash IRB.conf are determined by: +# * Default values. +# * Command-line options, which may override defaults. +# * Direct assignments in the configuration file. # -# - Default values. -# - Command-line options, which may override defaults. -# - Direct assignments in the configuration file. # -# You can see the hash by typing IRB.conf. +# You can see the hash by typing `IRB.conf`. # -# Details of the entries' meanings are described in the relevant subsections below. +# Details of the entries' meanings are described in the relevant subsections +# below. # # If you are interested in a specific entry, consult the -# {index}[rdoc-ref:doc/irb/indexes.md@Index+of+IRB.conf+Entries]. +# [index](rdoc-ref:doc/irb/indexes.md@Index+of+IRB.conf+Entries). # -# === Notes on Initialization Precedence +# ### Notes on Initialization Precedence # -# - Any conflict between an entry in hash IRB.conf and a command-line option -# is resolved in favor of the hash entry. -# - \Hash IRB.conf affects the context only once, -# when the configuration file is interpreted; -# any subsequent changes to it do not affect the context -# and are therefore essentially meaningless. +# * Any conflict between an entry in hash `IRB.conf` and a command-line option +# is resolved in favor of the hash entry. +# * Hash `IRB.conf` affects the context only once, when the configuration file +# is interpreted; any subsequent changes to it do not affect the context and +# are therefore essentially meaningless. # -# === Initialization Script # -# By default, the first command-line argument (after any options) -# is the path to a Ruby initialization script. +# ### Initialization Script # -# \IRB reads the initialization script and puts its content onto the \IRB shell, +# By default, the first command-line argument (after any options) is the path to +# a Ruby initialization script. +# +# IRB reads the initialization script and puts its content onto the IRB shell, # just as if it were user-typed commands. # -# Command-line option --noscript causes the first command-line argument -# to be treated as an ordinary argument (instead of an initialization script); -# --script is the default. +# Command-line option `--noscript` causes the first command-line argument to be +# treated as an ordinary argument (instead of an initialization script); +# `--script` is the default. # -# == Input +# ## Input # -# This section describes the features that allow you to change -# the way \IRB input works; -# see also {Input and Output}[rdoc-ref:IRB@Input+and+Output]. +# This section describes the features that allow you to change the way IRB input +# works; see also [Input and Output](rdoc-ref:IRB@Input+and+Output). # -# === Input Command History +# ### Input Command History # -# By default, \IRB stores a history of up to 1000 input commands in a -# file named .irb_history. The history file will be in the same directory -# as the {configuration file}[rdoc-ref:IRB@Configuration+File] if one is found, or -# in ~/ otherwise. +# By default, IRB stores a history of up to 1000 input commands in a file named +# `.irb_history`. The history file will be in the same directory as the +# [configuration file](rdoc-ref:IRB@Configuration+File) if one is found, or in +# `~/` otherwise. # -# A new \IRB session creates the history file if it does not exist, -# and appends to the file if it does exist. +# A new IRB session creates the history file if it does not exist, and appends +# to the file if it does exist. # # You can change the filepath by adding to your configuration file: -# IRB.conf[:HISTORY_FILE] = _filepath_, -# where _filepath_ is a string filepath. +# `IRB.conf[:HISTORY_FILE] = *filepath*`, where *filepath* is a string filepath. +# +# During the session, method `conf.history_file` returns the filepath, and +# method `conf.history_file = *new_filepath*` copies the history to the file at +# *new_filepath*, which becomes the history file for the session. # -# During the session, method conf.history_file returns the filepath, -# and method conf.history_file = new_filepath -# copies the history to the file at new_filepath, -# which becomes the history file for the session. +# You can change the number of commands saved by adding to your configuration +# file: `IRB.conf[:SAVE_HISTORY] = *n*`, wheHISTORY_FILEre *n* is one of: # -# You can change the number of commands saved by adding to your configuration file: -# IRB.conf[:SAVE_HISTORY] = _n_, -# where _n_ is one of: +# * Positive integer: the number of commands to be saved, +# * Zero: all commands are to be saved. +# * `nil`: no commands are to be saved,. # -# - Positive integer: the number of commands to be saved, -# - Zero: all commands are to be saved. -# - +nil+: no commands are to be saved,. # -# During the session, you can use -# methods conf.save_history or conf.save_history= -# to retrieve or change the count. +# During the session, you can use methods `conf.save_history` or +# `conf.save_history=` to retrieve or change the count. # -# === Command Aliases +# ### Command Aliases # -# By default, \IRB defines several command aliases: +# By default, IRB defines several command aliases: # -# irb(main):001> conf.command_aliases -# => {:"$"=>:show_source, :"@"=>:whereami} +# irb(main):001> conf.command_aliases +# => {:"$"=>:show_source, :"@"=>:whereami} # # You can change the initial aliases in the configuration file with: # -# IRB.conf[:COMMAND_ALIASES] = {foo: :show_source, bar: :whereami} +# IRB.conf[:COMMAND_ALIASES] = {foo: :show_source, bar: :whereami} # -# You can replace the current aliases at any time -# with configuration method conf.command_aliases=; -# Because conf.command_aliases is a hash, -# you can modify it. +# You can replace the current aliases at any time with configuration method +# `conf.command_aliases=`; Because `conf.command_aliases` is a hash, you can +# modify it. # -# === End-of-File +# ### End-of-File # -# By default, IRB.conf[:IGNORE_EOF] is +false+, -# which means that typing the end-of-file character Ctrl-D -# causes the session to exit. +# By default, `IRB.conf[:IGNORE_EOF]` is `false`, which means that typing the +# end-of-file character `Ctrl-D` causes the session to exit. # -# You can reverse that behavior by adding IRB.conf[:IGNORE_EOF] = true -# to the configuration file. +# You can reverse that behavior by adding `IRB.conf[:IGNORE_EOF] = true` to the +# configuration file. # -# During the session, method conf.ignore_eof? returns the setting, -# and method conf.ignore_eof = _boolean_ sets it. +# During the session, method `conf.ignore_eof?` returns the setting, and method +# `conf.ignore_eof = *boolean*` sets it. # -# === SIGINT +# ### SIGINT # -# By default, IRB.conf[:IGNORE_SIGINT] is +true+, -# which means that typing the interrupt character Ctrl-C -# causes the session to exit. +# By default, `IRB.conf[:IGNORE_SIGINT]` is `true`, which means that typing the +# interrupt character `Ctrl-C` causes the session to exit. # -# You can reverse that behavior by adding IRB.conf[:IGNORE_SIGING] = false -# to the configuration file. +# You can reverse that behavior by adding `IRB.conf[:IGNORE_SIGING] = false` to +# the configuration file. # -# During the session, method conf.ignore_siging? returns the setting, -# and method conf.ignore_sigint = _boolean_ sets it. +# During the session, method `conf.ignore_siging?` returns the setting, and +# method `conf.ignore_sigint = *boolean*` sets it. # -# === Automatic Completion +# ### Automatic Completion # -# By default, \IRB enables -# {automatic completion}[https://en.wikipedia.org/wiki/Autocomplete#In_command-line_interpreters]: +# By default, IRB enables [automatic +# completion](https://en.wikipedia.org/wiki/Autocomplete#In_command-line_interpr +# eters): # # You can disable it by either of these: # -# - Adding IRB.conf[:USE_AUTOCOMPLETE] = false to the configuration file. -# - Giving command-line option --noautocomplete -# (--autocomplete is the default). +# * Adding `IRB.conf[:USE_AUTOCOMPLETE] = false` to the configuration file. +# * Giving command-line option `--noautocomplete` (`--autocomplete` is the +# default). # -# \Method conf.use_autocomplete? returns +true+ -# if automatic completion is enabled, +false+ otherwise. +# +# Method `conf.use_autocomplete?` returns `true` if automatic completion is +# enabled, `false` otherwise. # # The setting may not be changed during the session. # -# === Automatic Indentation +# ### Automatic Indentation # -# By default, \IRB automatically indents lines of code to show structure -# (e.g., it indent the contents of a block). +# By default, IRB automatically indents lines of code to show structure (e.g., +# it indent the contents of a block). # -# The current setting is returned -# by the configuration method conf.auto_indent_mode. +# The current setting is returned by the configuration method +# `conf.auto_indent_mode`. # -# The default initial setting is +true+: +# The default initial setting is `true`: # -# irb(main):001> conf.auto_indent_mode -# => true -# irb(main):002* Dir.entries('.').select do |entry| -# irb(main):003* entry.start_with?('R') -# irb(main):004> end -# => ["README.md", "Rakefile"] +# irb(main):001> conf.auto_indent_mode +# => true +# irb(main):002* Dir.entries('.').select do |entry| +# irb(main):003* entry.start_with?('R') +# irb(main):004> end +# => ["README.md", "Rakefile"] # -# You can change the initial setting in the -# configuration file with: +# You can change the initial setting in the configuration file with: # -# IRB.conf[:AUTO_INDENT] = false +# IRB.conf[:AUTO_INDENT] = false # -# Note that the _current_ setting may not be changed in the \IRB session. +# Note that the *current* setting *may not* be changed in the IRB session. # -# === Input \Method +# ### Input Method # -# The \IRB input method determines how command input is to be read; -# by default, the input method for a session is IRB::RelineInputMethod. +# The IRB input method determines how command input is to be read; by default, +# the input method for a session is IRB::RelineInputMethod. # # You can set the input method by: # -# - Adding to the configuration file: +# * Adding to the configuration file: +# +# * `IRB.conf[:USE_SINGLELINE] = true` or `IRB.conf[:USE_MULTILINE]= +# false` sets the input method to IRB::ReadlineInputMethod. +# * `IRB.conf[:USE_SINGLELINE] = false` or `IRB.conf[:USE_MULTILINE] = +# true` sets the input method to IRB::RelineInputMethod. +# +# +# * Giving command-line options: +# +# * `--singleline` or `--nomultiline` sets the input method to +# IRB::ReadlineInputMethod. +# * `--nosingleline` or `--multiline` sets the input method to +# IRB::RelineInputMethod. # -# - IRB.conf[:USE_SINGLELINE] = true -# or IRB.conf[:USE_MULTILINE]= false -# sets the input method to IRB::ReadlineInputMethod. -# - IRB.conf[:USE_SINGLELINE] = false -# or IRB.conf[:USE_MULTILINE] = true -# sets the input method to IRB::RelineInputMethod. # -# - Giving command-line options: # -# - --singleline -# or --nomultiline -# sets the input method to IRB::ReadlineInputMethod. -# - --nosingleline -# or --multiline/tt> -# sets the input method to IRB::RelineInputMethod. +# Method `conf.use_multiline?` and its synonym `conf.use_reline` return: # -# \Method conf.use_multiline? -# and its synonym conf.use_reline return: +# * `true` if option `--multiline` was given. +# * `false` if option `--nomultiline` was given. +# * `nil` if neither was given. # -# - +true+ if option --multiline was given. -# - +false+ if option --nomultiline was given. -# - +nil+ if neither was given. # -# \Method conf.use_singleline? -# and its synonym conf.use_readline return: +# Method `conf.use_singleline?` and its synonym `conf.use_readline` return: # -# - +true+ if option --singleline was given. -# - +false+ if option --nosingleline was given. -# - +nil+ if neither was given. +# * `true` if option `--singleline` was given. +# * `false` if option `--nosingleline` was given. +# * `nil` if neither was given. # -# == Output # -# This section describes the features that allow you to change -# the way \IRB output works; -# see also {Input and Output}[rdoc-ref:IRB@Input+and+Output]. +# ## Output # -# === Return-Value Printing (Echoing) +# This section describes the features that allow you to change the way IRB +# output works; see also [Input and Output](rdoc-ref:IRB@Input+and+Output). # -# By default, \IRB prints (echoes) the values returned by all input commands. +# ### Return-Value Printing (Echoing) +# +# By default, IRB prints (echoes) the values returned by all input commands. # # You can change the initial behavior and suppress all echoing by: # -# - Adding to the configuration file: IRB.conf[:ECHO] = false. -# (The default value for this entry is +nil+, which means the same as +true+.) -# - Giving command-line option --noecho. -# (The default is --echo.) +# * Adding to the configuration file: `IRB.conf[:ECHO] = false`. (The default +# value for this entry is `nil`, which means the same as `true`.) +# * Giving command-line option `--noecho`. (The default is `--echo`.) +# +# +# During the session, you can change the current setting with configuration +# method `conf.echo=` (set to `true` or `false`). +# +# As stated above, by default IRB prints the values returned by all input +# commands; but IRB offers special treatment for values returned by assignment +# statements, which may be: # -# During the session, you can change the current setting -# with configuration method conf.echo= (set to +true+ or +false+). +# * Printed with truncation (to fit on a single line of output), which is the +# default; an ellipsis (`...` is suffixed, to indicate the truncation): # -# As stated above, by default \IRB prints the values returned by all input commands; -# but \IRB offers special treatment for values returned by assignment statements, -# which may be: +# irb(main):001> x = 'abc' * 100 # -# - Printed with truncation (to fit on a single line of output), -# which is the default; -# an ellipsis (... is suffixed, to indicate the truncation): # -# irb(main):001> x = 'abc' * 100 -# => "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc... +# > "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc... +# +# * Printed in full (regardless of the length). +# * Suppressed (not printed at all) # -# - Printed in full (regardless of the length). -# - Suppressed (not printed at all) # # You can change the initial behavior by: # -# - Adding to the configuration file: IRB.conf[:ECHO_ON_ASSIGNMENT] = false. -# (The default value for this entry is +niL+, which means the same as +:truncate+.) -# - Giving command-line option --noecho-on-assignment -# or --echo-on-assignment. -# (The default is --truncate-echo-on-assignment.) +# * Adding to the configuration file: `IRB.conf[:ECHO_ON_ASSIGNMENT] = false`. +# (The default value for this entry is `niL`, which means the same as +# `:truncate`.) +# * Giving command-line option `--noecho-on-assignment` or +# `--echo-on-assignment`. (The default is `--truncate-echo-on-assignment`.) +# # -# During the session, you can change the current setting -# with configuration method conf.echo_on_assignment= -# (set to +true+, +false+, or +:truncate+). +# During the session, you can change the current setting with configuration +# method `conf.echo_on_assignment=` (set to `true`, `false`, or `:truncate`). # -# By default, \IRB formats returned values by calling method +inspect+. +# By default, IRB formats returned values by calling method `inspect`. # # You can change the initial behavior by: # -# - Adding to the configuration file: IRB.conf[:INSPECT_MODE] = false. -# (The default value for this entry is +true+.) -# - Giving command-line option --noinspect. -# (The default is --inspect.) +# * Adding to the configuration file: `IRB.conf[:INSPECT_MODE] = false`. (The +# default value for this entry is `true`.) +# * Giving command-line option `--noinspect`. (The default is `--inspect`.) # -# During the session, you can change the setting using method conf.inspect_mode=. # -# === Multiline Output +# During the session, you can change the setting using method +# `conf.inspect_mode=`. # -# By default, \IRB prefixes a newline to a multiline response. +# ### Multiline Output +# +# By default, IRB prefixes a newline to a multiline response. # # You can change the initial default value by adding to the configuration file: # -# IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT] = false +# IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT] = false # -# During a session, you can retrieve or set the value using -# methods conf.newline_before_multiline_output? -# and conf.newline_before_multiline_output=. +# During a session, you can retrieve or set the value using methods +# `conf.newline_before_multiline_output?` and +# `conf.newline_before_multiline_output=`. # # Examples: # -# irb(main):001> conf.inspect_mode = false -# => false -# irb(main):002> "foo\nbar" -# => -# foo -# bar -# irb(main):003> conf.newline_before_multiline_output = false -# => false -# irb(main):004> "foo\nbar" -# => foo -# bar +# irb(main):001> conf.inspect_mode = false +# => false +# irb(main):002> "foo\nbar" +# => +# foo +# bar +# irb(main):003> conf.newline_before_multiline_output = false +# => false +# irb(main):004> "foo\nbar" +# => foo +# bar +# +# ### Evaluation History # -# === Evaluation History +# By default, IRB saves no history of evaluations (returned values), and the +# related methods `conf.eval_history`, `_`, and `__` are undefined. # -# By default, \IRB saves no history of evaluations (returned values), -# and the related methods conf.eval_history, _, -# and __ are undefined. +# You can turn on that history, and set the maximum number of evaluations to be +# stored: # -# You can turn on that history, and set the maximum number of evaluations to be stored: +# * In the configuration file: add `IRB.conf[:EVAL_HISTORY] = *n*`. (Examples +# below assume that we've added `IRB.conf[:EVAL_HISTORY] = 5`.) +# * In the session (at any time): `conf.eval_history = *n*`. # -# - In the configuration file: add IRB.conf[:EVAL_HISTORY] = _n_. -# (Examples below assume that we've added IRB.conf[:EVAL_HISTORY] = 5.) -# - In the session (at any time): conf.eval_history = _n_. # -# If +n+ is zero, all evaluation history is stored. +# If `n` is zero, all evaluation history is stored. # # Doing either of the above: # -# - Sets the maximum size of the evaluation history; -# defines method conf.eval_history, -# which returns the maximum size +n+ of the evaluation history: -# -# irb(main):001> conf.eval_history = 5 -# => 5 -# irb(main):002> conf.eval_history -# => 5 -# -# - Defines variable _, which contains the most recent evaluation, -# or +nil+ if none; same as method conf.last_value: -# -# irb(main):003> _ -# => 5 -# irb(main):004> :foo -# => :foo -# irb(main):005> :bar -# => :bar -# irb(main):006> _ -# => :bar -# irb(main):007> _ -# => :bar -# -# - Defines variable __: -# -# - __ unadorned: contains all evaluation history: -# -# irb(main):008> :foo -# => :foo -# irb(main):009> :bar -# => :bar -# irb(main):010> :baz -# => :baz -# irb(main):011> :bat -# => :bat -# irb(main):012> :bam -# => :bam -# irb(main):013> __ -# => -# 9 :bar -# 10 :baz -# 11 :bat -# 12 :bam -# irb(main):014> __ -# => -# 10 :baz -# 11 :bat -# 12 :bam -# 13 ...self-history... -# -# Note that when the evaluation is multiline, it is displayed differently. -# -# - __[_m_]: -# -# - Positive _m_: contains the evaluation for the given line number, -# or +nil+ if that line number is not in the evaluation history: -# -# irb(main):015> __[12] -# => :bam -# irb(main):016> __[1] -# => nil -# -# - Negative _m_: contains the +mth+-from-end evaluation, -# or +nil+ if that evaluation is not in the evaluation history: -# -# irb(main):017> __[-3] -# => :bam -# irb(main):018> __[-13] -# => nil -# -# - Zero _m_: contains +nil+: -# -# irb(main):019> __[0] -# => nil -# -# === Prompt and Return Formats -# -# By default, \IRB uses the prompt and return value formats -# defined in its +:DEFAULT+ prompt mode. -# -# ==== The Default Prompt and Return Format +# * Sets the maximum size of the evaluation history; defines method +# `conf.eval_history`, which returns the maximum size `n` of the evaluation +# history: +# +# irb(main):001> conf.eval_history = 5 +# => 5 +# irb(main):002> conf.eval_history +# => 5 +# +# * Defines variable `_`, which contains the most recent evaluation, or `nil` +# if none; same as method `conf.last_value`: +# +# irb(main):003> _ +# => 5 +# irb(main):004> :foo +# => :foo +# irb(main):005> :bar +# => :bar +# irb(main):006> _ +# => :bar +# irb(main):007> _ +# => :bar +# +# * Defines variable `__`: +# +# * `__` unadorned: contains all evaluation history: +# +# irb(main):008> :foo +# => :foo +# irb(main):009> :bar +# => :bar +# irb(main):010> :baz +# => :baz +# irb(main):011> :bat +# => :bat +# irb(main):012> :bam +# => :bam +# irb(main):013> __ +# => +# 9 :bar +# 10 :baz +# 11 :bat +# 12 :bam +# irb(main):014> __ +# => +# 10 :baz +# 11 :bat +# 12 :bam +# 13 ...self-history... +# +# Note that when the evaluation is multiline, it is displayed +# differently. +# +# * `__[`*m*`]`: +# +# * Positive *m*: contains the evaluation for the given line number, +# or `nil` if that line number is not in the evaluation history: +# +# irb(main):015> __[12] +# => :bam +# irb(main):016> __[1] +# => nil +# +# * Negative *m*: contains the `mth`-from-end evaluation, or `nil` if +# that evaluation is not in the evaluation history: +# +# irb(main):017> __[-3] +# => :bam +# irb(main):018> __[-13] +# => nil +# +# * Zero *m*: contains `nil`: +# +# irb(main):019> __[0] +# => nil +# +# +# +# +# ### Prompt and Return Formats +# +# By default, IRB uses the prompt and return value formats defined in its +# `:DEFAULT` prompt mode. +# +# #### The Default Prompt and Return Format # # The default prompt and return values look like this: # -# irb(main):001> 1 + 1 -# => 2 -# irb(main):002> 2 + 2 -# => 4 +# irb(main):001> 1 + 1 +# => 2 +# irb(main):002> 2 + 2 +# => 4 # # The prompt includes: # -# - The name of the running program (irb); -# see {IRB Name}[rdoc-ref:IRB@IRB+Name]. -# - The name of the current session (main); -# See {IRB Sessions}[rdoc-ref:IRB@IRB+Sessions]. -# - A 3-digit line number (1-based). +# * The name of the running program (`irb`); see [IRB +# Name](rdoc-ref:IRB@IRB+Name). +# * The name of the current session (`main`); See [IRB +# Sessions](rdoc-ref:IRB@IRB+Sessions). +# * A 3-digit line number (1-based). +# # # The default prompt actually defines three formats: # -# - One for most situations (as above): +# * One for most situations (as above): # -# irb(main):003> Dir -# => Dir +# irb(main):003> Dir +# => Dir # -# - One for when the typed command is a statement continuation (adds trailing asterisk): +# * One for when the typed command is a statement continuation (adds trailing +# asterisk): # -# irb(main):004* Dir. +# irb(main):004* Dir. # -# - One for when the typed command is a string continuation (adds trailing single-quote): +# * One for when the typed command is a string continuation (adds trailing +# single-quote): +# +# irb(main):005' Dir.entries('. # -# irb(main):005' Dir.entries('. # # You can see the prompt change as you type the characters in the following: # @@ -569,258 +576,266 @@ # irb(main):003> end # => ["README.md", "Rakefile"] # -# ==== Pre-Defined Prompts +# #### Pre-Defined Prompts # -# \IRB has several pre-defined prompts, stored in hash IRB.conf[:PROMPT]: +# IRB has several pre-defined prompts, stored in hash `IRB.conf[:PROMPT]`: # -# irb(main):001> IRB.conf[:PROMPT].keys -# => [:NULL, :DEFAULT, :CLASSIC, :SIMPLE, :INF_RUBY, :XMP] +# irb(main):001> IRB.conf[:PROMPT].keys +# => [:NULL, :DEFAULT, :CLASSIC, :SIMPLE, :INF_RUBY, :XMP] # -# To see the full data for these, type IRB.conf[:PROMPT]. +# To see the full data for these, type `IRB.conf[:PROMPT]`. # -# Most of these prompt definitions include specifiers that represent -# values like the \IRB name, session name, and line number; -# see {Prompt Specifiers}[rdoc-ref:IRB@Prompt+Specifiers]. +# Most of these prompt definitions include specifiers that represent values like +# the IRB name, session name, and line number; see [Prompt +# Specifiers](rdoc-ref:IRB@Prompt+Specifiers). # # You can change the initial prompt and return format by: # -# - Adding to the configuration file: IRB.conf[:PROMPT] = _mode_ -# where _mode_ is the symbol name of a prompt mode. -# - Giving a command-line option: +# * Adding to the configuration file: `IRB.conf[:PROMPT] = *mode*` where +# *mode* is the symbol name of a prompt mode. +# * Giving a command-line option: +# +# * `--prompt *mode*`: sets the prompt mode to *mode*. where *mode* is the +# symbol name of a prompt mode. +# * `--simple-prompt` or `--sample-book-mode`: sets the prompt mode to +# `:SIMPLE`. +# * `--inf-ruby-mode`: sets the prompt mode to `:INF_RUBY` and suppresses +# both `--multiline` and `--singleline`. +# * `--noprompt`: suppresses prompting; does not affect echoing. +# # -# - --prompt _mode_: sets the prompt mode to _mode_. -# where _mode_ is the symbol name of a prompt mode. -# - --simple-prompt or --sample-book-mode: -# sets the prompt mode to +:SIMPLE+. -# - --inf-ruby-mode: sets the prompt mode to +:INF_RUBY+ -# and suppresses both --multiline and --singleline. -# - --noprompt: suppresses prompting; does not affect echoing. # # You can retrieve or set the current prompt mode with methods # -# conf.prompt_mode and conf.prompt_mode=. +# `conf.prompt_mode` and `conf.prompt_mode=`. # # If you're interested in prompts and return formats other than the defaults, # you might experiment by trying some of the others. # -# ==== Custom Prompts +# #### Custom Prompts +# +# You can also define custom prompts and return formats, which may be done +# either in an IRB session or in the configuration file. # -# You can also define custom prompts and return formats, -# which may be done either in an \IRB session or in the configuration file. +# A prompt in IRB actually defines three prompts, as seen above. For simple +# custom data, we'll make all three the same: # -# A prompt in \IRB actually defines three prompts, as seen above. -# For simple custom data, we'll make all three the same: +# irb(main):001* IRB.conf[:PROMPT][:MY_PROMPT] = { +# irb(main):002* PROMPT_I: ': ', +# irb(main):003* PROMPT_C: ': ', +# irb(main):004* PROMPT_S: ': ', +# irb(main):005* RETURN: '=> ' +# irb(main):006> } +# => {:PROMPT_I=>": ", :PROMPT_C=>": ", :PROMPT_S=>": ", :RETURN=>"=> "} # -# irb(main):001* IRB.conf[:PROMPT][:MY_PROMPT] = { -# irb(main):002* PROMPT_I: ': ', -# irb(main):003* PROMPT_C: ': ', -# irb(main):004* PROMPT_S: ': ', -# irb(main):005* RETURN: '=> ' -# irb(main):006> } -# => {:PROMPT_I=>": ", :PROMPT_C=>": ", :PROMPT_S=>": ", :RETURN=>"=> "} +# If you define the custom prompt in the configuration file, you can also make +# it the current prompt by adding: # -# If you define the custom prompt in the configuration file, -# you can also make it the current prompt by adding: +# IRB.conf[:PROMPT_MODE] = :MY_PROMPT # -# IRB.conf[:PROMPT_MODE] = :MY_PROMPT +# Regardless of where it's defined, you can make it the current prompt in a +# session: # -# Regardless of where it's defined, you can make it the current prompt in a session: +# conf.prompt_mode = :MY_PROMPT # -# conf.prompt_mode = :MY_PROMPT +# You can view or modify the current prompt data with various configuration +# methods: # -# You can view or modify the current prompt data with various configuration methods: +# * `conf.prompt_mode`, `conf.prompt_mode=`. +# * `conf.prompt_c`, `conf.c=`. +# * `conf.prompt_i`, `conf.i=`. +# * `conf.prompt_s`, `conf.s=`. +# * `conf.return_format`, `return_format=`. # -# - conf.prompt_mode, conf.prompt_mode=. -# - conf.prompt_c, conf.c=. -# - conf.prompt_i, conf.i=. -# - conf.prompt_s, conf.s=. -# - conf.return_format, return_format=. # -# ==== Prompt Specifiers +# #### Prompt Specifiers # -# A prompt's definition can include specifiers for which certain values are substituted: +# A prompt's definition can include specifiers for which certain values are +# substituted: # -# - %N: the name of the running program. -# - %m: the value of self.to_s. -# - %M: the value of self.inspect. -# - %l: an indication of the type of string; -# one of ", ', /, ]. -# - NNi: Indentation level. -# - NNn: Line number. -# - %%: Literal %. +# * `%N`: the name of the running program. +# * `%m`: the value of `self.to_s`. +# * `%M`: the value of `self.inspect`. +# * `%l`: an indication of the type of string; one of `"`, `'`, `/`, `]`. +# * `*NN*i`: Indentation level. +# * `*NN*n`: Line number. +# * `%%`: Literal `%`. # -# === Verbosity # -# By default, \IRB verbosity is disabled, which means that output is smaller +# ### Verbosity +# +# By default, IRB verbosity is disabled, which means that output is smaller # rather than larger. # # You can enable verbosity by: # -# - Adding to the configuration file: IRB.conf[:VERBOSE] = true -# (the default is +nil+). -# - Giving command-line options --verbose -# (the default is --noverbose). +# * Adding to the configuration file: `IRB.conf[:VERBOSE] = true` (the default +# is `nil`). +# * Giving command-line options `--verbose` (the default is `--noverbose`). +# # # During a session, you can retrieve or set verbosity with methods -# conf.verbose and conf.verbose=. +# `conf.verbose` and `conf.verbose=`. # -# === Help +# ### Help # -# Command-line option --version causes \IRB to print its help text -# and exit. +# Command-line option `--version` causes IRB to print its help text and exit. # -# === Version +# ### Version # -# Command-line option --version causes \IRB to print its version text -# and exit. +# Command-line option `--version` causes IRB to print its version text and exit. # -# == Input and Output +# ## Input and Output # -# === \Color Highlighting +# ### Color Highlighting # -# By default, \IRB color highlighting is enabled, and is used for both: +# By default, IRB color highlighting is enabled, and is used for both: +# +# * Input: As you type, IRB reads the typed characters and highlights elements +# that it recognizes; it also highlights errors such as mismatched +# parentheses. +# * Output: IRB highlights syntactical elements. # -# - Input: As you type, \IRB reads the typed characters and highlights -# elements that it recognizes; -# it also highlights errors such as mismatched parentheses. -# - Output: \IRB highlights syntactical elements. # # You can disable color highlighting by: # -# - Adding to the configuration file: IRB.conf[:USE_COLORIZE] = false -# (the default value is +true+). -# - Giving command-line option --nocolorize +# * Adding to the configuration file: `IRB.conf[:USE_COLORIZE] = false` (the +# default value is `true`). +# * Giving command-line option `--nocolorize` +# # -# == Debugging +# ## Debugging # -# Command-line option -d sets variables $VERBOSE -# and $DEBUG to +true+; -# these have no effect on \IRB output. +# Command-line option `-d` sets variables `$VERBOSE` and `$DEBUG` to `true`; +# these have no effect on IRB output. # -# === Warnings +# ### Warnings # -# Command-line option -w suppresses warnings. +# Command-line option `-w` suppresses warnings. # -# Command-line option -W[_level_] -# sets warning level; 0=silence, 1=medium, 2=verbose. +# Command-line option `-W[*level*]` sets warning level; # -# == Other Features +# * 0=silence +# * 1=medium +# * 2=verbose # -# === Load Modules +# ## Other Features +# +# ### Load Modules # # You can specify the names of modules that are to be required at startup. # -# \Array conf.load_modules determines the modules (if any) -# that are to be required during session startup. -# The array is used only during session startup, -# so the initial value is the only one that counts. +# Array `conf.load_modules` determines the modules (if any) that are to be +# required during session startup. The array is used only during session +# startup, so the initial value is the only one that counts. # -# The default initial value is [] (load no modules): +# The default initial value is `[]` (load no modules): # -# irb(main):001> conf.load_modules -# => [] +# irb(main):001> conf.load_modules +# => [] # # You can set the default initial value via: # -# - Command-line option -r +# * Command-line option `-r` # -# $ irb -r csv -r json -# irb(main):001> conf.load_modules -# => ["csv", "json"] +# $ irb -r csv -r json +# irb(main):001> conf.load_modules +# => ["csv", "json"] # -# - \Hash entry IRB.conf[:LOAD_MODULES] = _array_: +# * Hash entry `IRB.conf[:LOAD_MODULES] = *array*`: +# +# IRB.conf[:LOAD_MODULES] = %w[csv, json] # -# IRB.conf[:LOAD_MODULES] = %w[csv, json] # # Note that the configuration file entry overrides the command-line options. # -# === RI Documentation Directories +# ### RI Documentation Directories # -# You can specify the paths to RI documentation directories -# that are to be loaded (in addition to the default directories) at startup; -# see details about RI by typing ri --help. +# You can specify the paths to RI documentation directories that are to be +# loaded (in addition to the default directories) at startup; see details about +# RI by typing `ri --help`. # -# \Array conf.extra_doc_dirs determines the directories (if any) -# that are to be loaded during session startup. -# The array is used only during session startup, +# Array `conf.extra_doc_dirs` determines the directories (if any) that are to be +# loaded during session startup. The array is used only during session startup, # so the initial value is the only one that counts. # -# The default initial value is [] (load no extra documentation): +# The default initial value is `[]` (load no extra documentation): # -# irb(main):001> conf.extra_doc_dirs -# => [] +# irb(main):001> conf.extra_doc_dirs +# => [] # # You can set the default initial value via: # -# - Command-line option --extra_doc_dir +# * Command-line option `--extra_doc_dir` # -# $ irb --extra-doc-dir your_doc_dir --extra-doc-dir my_doc_dir -# irb(main):001> conf.extra_doc_dirs -# => ["your_doc_dir", "my_doc_dir"] +# $ irb --extra-doc-dir your_doc_dir --extra-doc-dir my_doc_dir +# irb(main):001> conf.extra_doc_dirs +# => ["your_doc_dir", "my_doc_dir"] +# +# * Hash entry `IRB.conf[:EXTRA_DOC_DIRS] = *array*`: # -# - \Hash entry IRB.conf[:EXTRA_DOC_DIRS] = _array_: +# IRB.conf[:EXTRA_DOC_DIRS] = %w[your_doc_dir my_doc_dir] # -# IRB.conf[:EXTRA_DOC_DIRS] = %w[your_doc_dir my_doc_dir] # # Note that the configuration file entry overrides the command-line options. # -# === \IRB Name +# ### IRB Name # -# You can specify a name for \IRB. +# You can specify a name for IRB. # -# The default initial value is 'irb': +# The default initial value is `'irb'`: # -# irb(main):001> conf.irb_name -# => "irb" +# irb(main):001> conf.irb_name +# => "irb" # -# You can set the default initial value -# via hash entry IRB.conf[:IRB_NAME] = _string_: +# You can set the default initial value via hash entry `IRB.conf[:IRB_NAME] = +# *string*`: # -# IRB.conf[:IRB_NAME] = 'foo' +# IRB.conf[:IRB_NAME] = 'foo' # -# === Application Name +# ### Application Name # -# You can specify an application name for the \IRB session. +# You can specify an application name for the IRB session. # -# The default initial value is 'irb': +# The default initial value is `'irb'`: # -# irb(main):001> conf.ap_name -# => "irb" +# irb(main):001> conf.ap_name +# => "irb" # -# You can set the default initial value -# via hash entry IRB.conf[:AP_NAME] = _string_: +# You can set the default initial value via hash entry `IRB.conf[:AP_NAME] = +# *string*`: # -# IRB.conf[:AP_NAME] = 'my_ap_name' +# IRB.conf[:AP_NAME] = 'my_ap_name' # -# === Configuration Monitor +# ### Configuration Monitor # -# You can monitor changes to the configuration by assigning a proc -# to IRB.conf[:IRB_RC] in the configuration file: +# You can monitor changes to the configuration by assigning a proc to +# `IRB.conf[:IRB_RC]` in the configuration file: # -# IRB.conf[:IRB_RC] = proc {|conf| puts conf.class } +# IRB.conf[:IRB_RC] = proc {|conf| puts conf.class } # -# Each time the configuration is changed, -# that proc is called with argument +conf+: +# Each time the configuration is changed, that proc is called with argument +# `conf`: # -# === Encodings +# ### Encodings # -# Command-line option -E _ex_[:_in_] -# sets initial external (ex) and internal (in) encodings. +# Command-line option `-E *ex*[:*in*]` sets initial external (ex) and internal +# (in) encodings. # -# Command-line option -U sets both to UTF-8. +# Command-line option `-U` sets both to UTF-8. # -# === Commands +# ### Commands # # Please use the `help` command to see the list of available commands. # -# === IRB Sessions +# ### IRB Sessions # # IRB has a special feature, that allows you to manage many sessions at once. # # You can create new sessions with Irb.irb, and get a list of current sessions -# with the +jobs+ command in the prompt. +# with the `jobs` command in the prompt. # -# ==== Configuration +# #### Configuration # # The command line options, or IRB.conf, specify the default behavior of # Irb.irb. @@ -828,32 +843,33 @@ # On the other hand, each conf in IRB@Command-Line+Options is used to # individually configure IRB.irb. # -# If a proc is set for IRB.conf[:IRB_RC], its will be invoked after execution +# If a proc is set for `IRB.conf[:IRB_RC]`, its will be invoked after execution # of that proc with the context of the current session as its argument. Each # session can be configured using this mechanism. # -# ==== Session variables +# #### Session variables # # There are a few variables in every Irb session that can come in handy: # -# _:: -# The value command executed, as a local variable -# __:: -# The history of evaluated commands. Available only if -# IRB.conf[:EVAL_HISTORY] is not +nil+ (which is the default). -# See also IRB::Context#eval_history= and IRB::History. -# __[line_no]:: -# Returns the evaluation value at the given line number, +line_no+. -# If +line_no+ is a negative, the return value +line_no+ many lines before -# the most recent return value. +# `_` +# : The value command executed, as a local variable +# `__` +# : The history of evaluated commands. Available only if +# `IRB.conf[:EVAL_HISTORY]` is not `nil` (which is the default). See also +# IRB::Context#eval_history= and IRB::History. +# `__[line_no]` +# : Returns the evaluation value at the given line number, `line_no`. If +# `line_no` is a negative, the return value `line_no` many lines before the +# most recent return value. # -# == Restrictions # -# Ruby code typed into \IRB behaves the same as Ruby code in a file, except that: +# ## Restrictions # -# - Because \IRB evaluates input immediately after it is syntactically complete, -# some results may be slightly different. -# - Forking may not be well behaved. +# Ruby code typed into IRB behaves the same as Ruby code in a file, except that: +# +# * Because IRB evaluates input immediately after it is syntactically +# complete, some results may be slightly different. +# * Forking may not be well behaved. # module IRB @@ -862,14 +878,14 @@ class Abort < Exception;end # The current IRB::Context of the session, see IRB.conf # - # irb - # irb(main):001:0> IRB.CurrentContext.irb_name = "foo" - # foo(main):002:0> IRB.conf[:MAIN_CONTEXT].irb_name #=> "foo" - def IRB.CurrentContext + # irb + # irb(main):001:0> IRB.CurrentContext.irb_name = "foo" + # foo(main):002:0> IRB.conf[:MAIN_CONTEXT].irb_name #=> "foo" + def IRB.CurrentContext # :nodoc: IRB.conf[:MAIN_CONTEXT] end - # Initializes IRB and creates a new Irb.irb object at the +TOPLEVEL_BINDING+ + # Initializes IRB and creates a new Irb.irb object at the `TOPLEVEL_BINDING` def IRB.start(ap_path = nil) STDOUT.sync = true $0 = File::basename(ap_path, ".rb") if ap_path @@ -885,22 +901,22 @@ def IRB.start(ap_path = nil) end # Quits irb - def IRB.irb_exit(*) + def IRB.irb_exit(*) # :nodoc: throw :IRB_EXIT, false end # Aborts then interrupts irb. # - # Will raise an Abort exception, or the given +exception+. - def IRB.irb_abort(irb, exception = Abort) + # Will raise an Abort exception, or the given `exception`. + def IRB.irb_abort(irb, exception = Abort) # :nodoc: irb.context.thread.raise exception, "abort then interrupt!" end class Irb # Note: instance and index assignment expressions could also be written like: - # "foo.bar=(1)" and "foo.[]=(1, bar)", when expressed that way, the former - # be parsed as :assign and echo will be suppressed, but the latter is - # parsed as a :method_add_arg and the output won't be suppressed + # "foo.bar=(1)" and "foo.[]=(1, bar)", when expressed that way, the former be + # parsed as :assign and echo will be suppressed, but the latter is parsed as a + # :method_add_arg and the output won't be suppressed PROMPT_MAIN_TRUNCATE_LENGTH = 32 PROMPT_MAIN_TRUNCATE_OMISSION = '...' @@ -920,7 +936,8 @@ def initialize(workspace = nil, input_method = nil) @line_no = 1 end - # A hook point for `debug` command's breakpoint after :IRB_EXIT as well as its clean-up + # A hook point for `debug` command's breakpoint after :IRB_EXIT as well as its + # clean-up def debug_break # it means the debug integration has been activated if defined?(DEBUGGER__) && DEBUGGER__.respond_to?(:capture_frames_without_irb) @@ -938,13 +955,15 @@ def debug_readline(binding) @line_no += 1 # When users run: - # 1. Debugging commands, like `step 2` - # 2. Any input that's not irb-command, like `foo = 123` + # 1. Debugging commands, like `step 2` + # 2. Any input that's not irb-command, like `foo = 123` + # # - # Irb#eval_input will simply return the input, and we need to pass it to the debugger. + # Irb#eval_input will simply return the input, and we need to pass it to the + # debugger. input = if IRB.conf[:SAVE_HISTORY] && context.io.support_history_saving? - # Previous IRB session's history has been saved when `Irb#run` is exited - # We need to make sure the saved history is not saved again by resetting the counter + # Previous IRB session's history has been saved when `Irb#run` is exited We need + # to make sure the saved history is not saved again by resetting the counter context.io.reset_history_counter begin @@ -1004,7 +1023,8 @@ def eval_input each_top_level_statement do |statement, line_no| signal_status(:IN_EVAL) do begin - # If the integration with debugger is activated, we return certain input if it should be dealt with by debugger + # If the integration with debugger is activated, we return certain input if it + # should be dealt with by debugger if @context.with_debugger && statement.should_be_handled_by_debugger? return statement.code end @@ -1066,7 +1086,8 @@ def readmultiline code << line - # Accept any single-line input for symbol aliases or commands that transform args + # Accept any single-line input for symbol aliases or commands that transform + # args return code if single_line_command?(code) tokens, opens, terminated = @scanner.check_code_state(code, local_variables: @context.local_variables) @@ -1128,7 +1149,8 @@ def configure_io false end else - # Accept any single-line input for symbol aliases or commands that transform args + # Accept any single-line input for symbol aliases or commands that transform + # args next true if single_line_command?(code) _tokens, _opens, terminated = @scanner.check_code_state(code, local_variables: @context.local_variables) @@ -1143,7 +1165,8 @@ def configure_io tokens_until_line = [] line_results.map.with_index do |(line_tokens, _prev_opens, next_opens, _min_depth), line_num_offset| line_tokens.each do |token, _s| - # Avoid appending duplicated token. Tokens that include "\n" like multiline tstring_content can exist in multiple lines. + # Avoid appending duplicated token. Tokens that include "n" like multiline + # tstring_content can exist in multiple lines. tokens_until_line << token if token != tokens_until_line.last end continue = @scanner.should_continue?(tokens_until_line) @@ -1247,11 +1270,10 @@ def handle_exception(exc) end end - # Evaluates the given block using the given +path+ as the Context#irb_path - # and +name+ as the Context#irb_name. + # Evaluates the given block using the given `path` as the Context#irb_path and + # `name` as the Context#irb_name. # - # Used by the irb command +source+, see IRB@IRB+Sessions for more - # information. + # Used by the irb command `source`, see IRB@IRB+Sessions for more information. def suspend_name(path = nil, name = nil) @context.irb_path, back_path = path, @context.irb_path if path @context.irb_name, back_name = name, @context.irb_name if name @@ -1263,11 +1285,10 @@ def suspend_name(path = nil, name = nil) end end - # Evaluates the given block using the given +workspace+ as the + # Evaluates the given block using the given `workspace` as the # Context#workspace. # - # Used by the irb command +irb_load+, see IRB@IRB+Sessions for more - # information. + # Used by the irb command `irb_load`, see IRB@IRB+Sessions for more information. def suspend_workspace(workspace) current_workspace = @context.workspace @context.replace_workspace(workspace) @@ -1276,11 +1297,10 @@ def suspend_workspace(workspace) @context.replace_workspace current_workspace end - # Evaluates the given block using the given +input_method+ as the - # Context#io. + # Evaluates the given block using the given `input_method` as the Context#io. # - # Used by the irb commands +source+ and +irb_load+, see IRB@IRB+Sessions - # for more information. + # Used by the irb commands `source` and `irb_load`, see IRB@IRB+Sessions for + # more information. def suspend_input_method(input_method) back_io = @context.io @context.instance_eval{@io = input_method} @@ -1313,7 +1333,7 @@ def signal_handle end end - # Evaluates the given block using the given +status+. + # Evaluates the given block using the given `status`. def signal_status(status) return yield if @signal_status == :IN_LOAD @@ -1363,8 +1383,8 @@ def output_value(omit = false) # :nodoc: Pager.page_content(format(@context.return_format, str), retain_content: true) end - # Outputs the local variables to this current session, including - # #signal_status and #context, using IRB::Locale. + # Outputs the local variables to this current session, including #signal_status + # and #context, using IRB::Locale. def inspect ary = [] for iv in instance_variables @@ -1463,12 +1483,11 @@ def format_prompt(format, ltype, indent, line_no) # :nodoc: end class Binding - # Opens an IRB session where +binding.irb+ is called which allows for - # interactive debugging. You can call any methods or variables available in - # the current scope, and mutate state if you need to. - # + # Opens an IRB session where `binding.irb` is called which allows for + # interactive debugging. You can call any methods or variables available in the + # current scope, and mutate state if you need to. # - # Given a Ruby file called +potato.rb+ containing the following code: + # Given a Ruby file called `potato.rb` containing the following code: # # class Potato # def initialize @@ -1480,8 +1499,8 @@ class Binding # # Potato.new # - # Running ruby potato.rb will open an IRB session where - # +binding.irb+ is called, and you will see the following: + # Running `ruby potato.rb` will open an IRB session where `binding.irb` is + # called, and you will see the following: # # $ ruby potato.rb # @@ -1511,8 +1530,8 @@ class Binding # irb(#):004:0> @cooked = true # => true # - # You can exit the IRB session with the +exit+ command. Note that exiting will - # resume execution where +binding.irb+ had paused it, as you can see from the + # You can exit the IRB session with the `exit` command. Note that exiting will + # resume execution where `binding.irb` had paused it, as you can see from the # output printed to standard output in this example: # # irb(#):005:0> exit @@ -1535,13 +1554,14 @@ def irb(show_code: true) # If we're already in a debugger session, set the workspace and irb_path for the original IRB instance debugger_irb.context.replace_workspace(workspace) debugger_irb.context.irb_path = irb_path - # If we've started a debugger session and hit another binding.irb, we don't want to start an IRB session - # instead, we want to resume the irb:rdbg session. + # If we've started a debugger session and hit another binding.irb, we don't want + # to start an IRB session instead, we want to resume the irb:rdbg session. IRB::Debug.setup(debugger_irb) IRB::Debug.insert_debug_break debugger_irb.debug_break else - # If we're not in a debugger session, create a new IRB instance with the current workspace + # If we're not in a debugger session, create a new IRB instance with the current + # workspace binding_irb = IRB::Irb.new(workspace) binding_irb.context.irb_path = irb_path binding_irb.run(IRB.conf) diff --git a/lib/irb/ext/multi-irb.rb b/lib/irb/ext/multi-irb.rb index 944c04c82..9f234f0cd 100644 --- a/lib/irb/ext/multi-irb.rb +++ b/lib/irb/ext/multi-irb.rb @@ -5,7 +5,7 @@ # module IRB - class JobManager + class JobManager # :nodoc: # Creates a new JobManager object def initialize @@ -166,12 +166,12 @@ def inspect @JobManager = JobManager.new # The current JobManager in the session - def IRB.JobManager + def IRB.JobManager # :nodoc: @JobManager end # The current Context in this session - def IRB.CurrentContext + def IRB.CurrentContext # :nodoc: IRB.JobManager.irb(Thread.current).context end @@ -179,7 +179,7 @@ def IRB.CurrentContext # # The optional +file+ argument is given to Context.new, along with the # workspace created with the remaining arguments, see WorkSpace.new - def IRB.irb(file = nil, *main) + def IRB.irb(file = nil, *main) # :nodoc: workspace = WorkSpace.new(*main) parent_thread = Thread.current Thread.start do diff --git a/lib/irb/help.rb b/lib/irb/help.rb index c97656f0c..a24bc10a1 100644 --- a/lib/irb/help.rb +++ b/lib/irb/help.rb @@ -6,7 +6,7 @@ module IRB # Outputs the irb help message, see IRB@Command-Line+Options. - def IRB.print_usage + def IRB.print_usage # :nodoc: lc = IRB.conf[:LC_MESSAGES] path = lc.find("irb/help-message") space_line = false From 11d03a6ff771e17c4b7808dd45e7329cca7f68d3 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 17 Mar 2024 00:19:59 +0900 Subject: [PATCH 109/263] Fix irb_history saved to current directory (#901) * Always save irb_history in HOME or XDG_CONFIG_HOME Also split irbrc search logic from irb_history search logic as a refactor * Remove IRB.conf[:RC_NAME_GENERATOR] because it's not configurable This conf is used to specify which irbrc to load. Need to configure before irbrc is loaded, so it's actually not configurable. This conf is also used for history file search, but it is configurable by conf[:HISTORY_FILE]. * remove rc_file_test because it is tested with rc_files, remove useless test setup * Make internal irbrc searching method private --- doc/irb/indexes.md | 3 - lib/irb/command/irb_info.rb | 2 +- lib/irb/history.rb | 9 +- lib/irb/init.rb | 104 ++++++++------ lib/irb/lc/error.rb | 5 - lib/irb/lc/ja/error.rb | 5 - test/irb/test_command.rb | 1 + test/irb/test_history.rb | 18 ++- test/irb/test_init.rb | 173 +++++++---------------- test/irb/yamatanooroti/test_rendering.rb | 6 + 10 files changed, 143 insertions(+), 183 deletions(-) diff --git a/doc/irb/indexes.md b/doc/irb/indexes.md index 9659db8c0..24a67b969 100644 --- a/doc/irb/indexes.md +++ b/doc/irb/indexes.md @@ -165,9 +165,6 @@ for each entry that is pre-defined, the initial value is given: - `:RC`: Whether a {configuration file}[rdoc-ref:IRB@Configuration+File] was found and interpreted; initial value: `true` if a configuration file was found, `false` otherwise. -- `:RC_NAME_GENERATOR`: \Proc to generate paths of potential - {configuration files}[rdoc-ref:IRB@Configuration+File]; - initial value: `=> #`. - `:SAVE_HISTORY`: Number of commands to save in {input command history}[rdoc-ref:IRB@Input+Command+History]; initial value: `1000`. diff --git a/lib/irb/command/irb_info.rb b/lib/irb/command/irb_info.rb index 31e6d77d2..cc93fdcbd 100644 --- a/lib/irb/command/irb_info.rb +++ b/lib/irb/command/irb_info.rb @@ -13,7 +13,7 @@ def execute 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.rc_files.select { |rc| File.exist?(rc) } + 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? diff --git a/lib/irb/history.rb b/lib/irb/history.rb index 2489f7403..685354b2d 100644 --- a/lib/irb/history.rb +++ b/lib/irb/history.rb @@ -16,8 +16,8 @@ def load_history if history_file = IRB.conf[:HISTORY_FILE] history_file = File.expand_path(history_file) end - history_file = IRB.rc_files("_history").first unless history_file - if File.exist?(history_file) + history_file = IRB.rc_file("_history") unless history_file + if history_file && File.exist?(history_file) File.open(history_file, "r:#{IRB.conf[:LC_MESSAGES].encoding}") do |f| f.each { |l| l = l.chomp @@ -41,7 +41,10 @@ def save_history if history_file = IRB.conf[:HISTORY_FILE] history_file = File.expand_path(history_file) end - history_file = IRB.rc_files("_history").first unless history_file + history_file = IRB.rc_file("_history") unless history_file + + # When HOME and XDG_CONFIG_HOME are not available, history_file might be nil + return unless history_file # Change the permission of a file that already exists[BUG #7694] begin diff --git a/lib/irb/init.rb b/lib/irb/init.rb index 5813ff7ae..355047519 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -395,10 +395,8 @@ def IRB.parse_opts(argv: ::ARGV) # Run the config file def IRB.run_config if @CONF[:RC] - rc_files.each do |rc| - # Because rc_file always returns `HOME/.irbrc` even if no rc file is present, we can't warn users about missing rc files. - # Otherwise, it'd be very noisy. - load rc if File.exist?(rc) + irbrc_files.each do |rc| + load rc rescue StandardError, ScriptError => e warn "Error loading RC file '#{rc}':\n#{e.full_message(highlight: false)}" end @@ -406,53 +404,27 @@ def IRB.run_config end IRBRC_EXT = "rc" - def IRB.rc_file(ext = IRBRC_EXT) - warn "rc_file is deprecated, please use rc_files instead." - rc_files(ext).first - end - - def IRB.rc_files(ext = IRBRC_EXT) - if !@CONF[:RC_NAME_GENERATOR] - @CONF[:RC_NAME_GENERATOR] ||= [] - existing_rc_file_generators = [] - rc_file_generators do |rcgen| - @CONF[:RC_NAME_GENERATOR] << rcgen - existing_rc_file_generators << rcgen if File.exist?(rcgen.call(ext)) - end + def IRB.rc_file(ext) + prepare_irbrc_name_generators - if existing_rc_file_generators.any? - @CONF[:RC_NAME_GENERATOR] = existing_rc_file_generators - end + # When irbrc exist in default location + if (rcgen = @existing_rc_name_generators.first) + return rcgen.call(ext) end - @CONF[:RC_NAME_GENERATOR].map do |rc| - rc_file = rc.call(ext) - fail IllegalRCNameGenerator unless rc_file.is_a?(String) - rc_file + # When irbrc does not exist in default location + rc_file_generators do |rcgen| + return rcgen.call(ext) end + + # When HOME and XDG_CONFIG_HOME are not available + nil end - # enumerate possible rc-file base name generators - def IRB.rc_file_generators - if irbrc = ENV["IRBRC"] - yield proc{|rc| rc == "rc" ? irbrc : irbrc+rc} - end - if xdg_config_home = ENV["XDG_CONFIG_HOME"] - irb_home = File.join(xdg_config_home, "irb") - if File.directory?(irb_home) - yield proc{|rc| irb_home + "/irb#{rc}"} - end - end - if home = ENV["HOME"] - yield proc{|rc| home+"/.irb#{rc}"} - yield proc{|rc| home+"/.config/irb/irb#{rc}"} - end - current_dir = Dir.pwd - yield proc{|rc| current_dir+"/.irb#{rc}"} - yield proc{|rc| current_dir+"/irb#{rc.sub(/\A_?/, '.')}"} - yield proc{|rc| current_dir+"/_irb#{rc}"} - yield proc{|rc| current_dir+"/$irb#{rc}"} + def IRB.irbrc_files + prepare_irbrc_name_generators + @irbrc_files end # loading modules @@ -468,6 +440,50 @@ def IRB.load_modules class << IRB private + + def prepare_irbrc_name_generators + return if @existing_rc_name_generators + + @existing_rc_name_generators = [] + @irbrc_files = [] + rc_file_generators do |rcgen| + irbrc = rcgen.call(IRBRC_EXT) + if File.exist?(irbrc) + @irbrc_files << irbrc + @existing_rc_name_generators << rcgen + end + end + generate_current_dir_irbrc_files.each do |irbrc| + @irbrc_files << irbrc if File.exist?(irbrc) + end + @irbrc_files.uniq! + end + + # enumerate possible rc-file base name generators + def rc_file_generators + if irbrc = ENV["IRBRC"] + yield proc{|rc| rc == "rc" ? irbrc : irbrc+rc} + end + if xdg_config_home = ENV["XDG_CONFIG_HOME"] + irb_home = File.join(xdg_config_home, "irb") + if File.directory?(irb_home) + yield proc{|rc| irb_home + "/irb#{rc}"} + end + end + if home = ENV["HOME"] + yield proc{|rc| home+"/.irb#{rc}"} + if xdg_config_home.nil? || xdg_config_home.empty? + yield proc{|rc| home+"/.config/irb/irb#{rc}"} + end + end + end + + # possible irbrc files in current directory + def generate_current_dir_irbrc_files + current_dir = Dir.pwd + %w[.irbrc irbrc _irbrc $irbrc].map { |file| "#{current_dir}/#{file}" } + end + def set_encoding(extern, intern = nil, override: true) verbose, $VERBOSE = $VERBOSE, nil Encoding.default_external = extern unless extern.nil? || extern.empty? diff --git a/lib/irb/lc/error.rb b/lib/irb/lc/error.rb index 9041ec4d6..ee0f04782 100644 --- a/lib/irb/lc/error.rb +++ b/lib/irb/lc/error.rb @@ -47,11 +47,6 @@ def initialize(val) super("Undefined prompt mode(#{val}).") end end - class IllegalRCGenerator < StandardError - def initialize - super("Define illegal RC_NAME_GENERATOR.") - end - end # :startdoc: end diff --git a/lib/irb/lc/ja/error.rb b/lib/irb/lc/ja/error.rb index f7f2b13c4..9e2e5b887 100644 --- a/lib/irb/lc/ja/error.rb +++ b/lib/irb/lc/ja/error.rb @@ -47,11 +47,6 @@ def initialize(val) super("プロンプトモード(#{val})は定義されていません.") end end - class IllegalRCGenerator < StandardError - def initialize - super("RC_NAME_GENERATORが正しく定義されていません.") - end - end # :startdoc: end diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index 8e074e97f..a8af0762a 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -20,6 +20,7 @@ def setup @xdg_config_home_backup = ENV.delete("XDG_CONFIG_HOME") save_encodings IRB.instance_variable_get(:@CONF).clear + IRB.instance_variable_set(:@existing_rc_name_generators, nil) @is_win = (RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/) end diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index ea220a2dd..63be35fda 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -17,11 +17,11 @@ def setup @backup_irbrc = ENV.delete("IRBRC") @backup_default_external = Encoding.default_external ENV["HOME"] = @tmpdir - IRB.conf[:RC_NAME_GENERATOR] = nil + IRB.instance_variable_set(:@existing_rc_name_generators, nil) end def teardown - IRB.conf[:RC_NAME_GENERATOR] = nil + IRB.instance_variable_set(:@existing_rc_name_generators, nil) ENV["HOME"] = @backup_home ENV["XDG_CONFIG_HOME"] = @backup_xdg_config_home ENV["IRBRC"] = @backup_irbrc @@ -144,7 +144,7 @@ def test_history_concurrent_use_not_present io.class::HISTORY << 'line1' io.class::HISTORY << 'line2' - history_file = IRB.rc_files("_history").first + history_file = IRB.rc_file("_history") assert_not_send [File, :file?, history_file] File.write(history_file, "line0\n") io.save_history @@ -183,6 +183,16 @@ def test_history_does_not_raise_when_history_file_directory_does_not_exist IRB.conf[:HISTORY_FILE] = backup_history_file end + def test_no_home_no_history_file_does_not_raise_history_save + ENV['HOME'] = nil + io = TestInputMethodWithRelineHistory.new + assert_nil(IRB.rc_file('_history')) + assert_nothing_raised do + io.load_history + io.save_history + end + end + private def history_concurrent_use_for_input_method(input_method) @@ -217,7 +227,7 @@ def history_concurrent_use_for_input_method(input_method) def assert_history(expected_history, initial_irb_history, input, input_method = TestInputMethodWithRelineHistory, locale: IRB::Locale.new) IRB.conf[:LC_MESSAGES] = locale actual_history = nil - history_file = IRB.rc_files("_history").first + history_file = IRB.rc_file("_history") ENV["HOME"] = @tmpdir File.open(history_file, "w") do |f| f.write(initial_irb_history) diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb index fd1e06e39..f11d7398c 100644 --- a/test/irb/test_init.rb +++ b/test/irb/test_init.rb @@ -14,10 +14,15 @@ def setup ENV["HOME"] = @tmpdir = File.realpath(Dir.mktmpdir("test_irb_init_#{$$}")) end + def reset_rc_name_generators + IRB.instance_variable_set(:@existing_rc_name_generators, nil) + end + def teardown ENV.update(@backup_env) FileUtils.rm_rf(@tmpdir) IRB.conf.delete(:SCRIPT) + reset_rc_name_generators end def test_setup_with_argv_preserves_global_argv @@ -34,133 +39,65 @@ def test_setup_with_minimum_argv_does_not_change_dollar0 assert_equal orig, $0 end - def test_rc_file - verbose, $VERBOSE = $VERBOSE, nil - tmpdir = @tmpdir - Dir.chdir(tmpdir) do - ENV["XDG_CONFIG_HOME"] = "#{tmpdir}/xdg" - IRB.conf[:RC_NAME_GENERATOR] = nil - assert_equal(tmpdir+"/.irbrc", IRB.rc_file) - assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history")) - assert_file.not_exist?(tmpdir+"/xdg") - IRB.conf[:RC_NAME_GENERATOR] = nil - FileUtils.touch(tmpdir+"/.irbrc") - assert_equal(tmpdir+"/.irbrc", IRB.rc_file) - assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history")) - assert_file.not_exist?(tmpdir+"/xdg") - end - ensure - $VERBOSE = verbose - end - - def test_rc_file_in_subdir - verbose, $VERBOSE = $VERBOSE, nil - tmpdir = @tmpdir - Dir.chdir(tmpdir) do - FileUtils.mkdir_p("#{tmpdir}/mydir") - Dir.chdir("#{tmpdir}/mydir") do - IRB.conf[:RC_NAME_GENERATOR] = nil - assert_equal(tmpdir+"/.irbrc", IRB.rc_file) - assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history")) - IRB.conf[:RC_NAME_GENERATOR] = nil - FileUtils.touch(tmpdir+"/.irbrc") - assert_equal(tmpdir+"/.irbrc", IRB.rc_file) - assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history")) - end - end - ensure - $VERBOSE = verbose - end - def test_rc_files tmpdir = @tmpdir Dir.chdir(tmpdir) do - ENV["XDG_CONFIG_HOME"] = "#{tmpdir}/xdg" - IRB.conf[:RC_NAME_GENERATOR] = nil - assert_includes IRB.rc_files, tmpdir+"/.irbrc" - assert_includes IRB.rc_files("_history"), tmpdir+"/.irb_history" - assert_file.not_exist?(tmpdir+"/xdg") - IRB.conf[:RC_NAME_GENERATOR] = nil - FileUtils.touch(tmpdir+"/.irbrc") - assert_includes IRB.rc_files, tmpdir+"/.irbrc" - assert_includes IRB.rc_files("_history"), tmpdir+"/.irb_history" - assert_file.not_exist?(tmpdir+"/xdg") - end - end - - def test_rc_files_in_subdir - tmpdir = @tmpdir - Dir.chdir(tmpdir) do - FileUtils.mkdir_p("#{tmpdir}/mydir") - Dir.chdir("#{tmpdir}/mydir") do - IRB.conf[:RC_NAME_GENERATOR] = nil - assert_includes IRB.rc_files, tmpdir+"/.irbrc" - assert_includes IRB.rc_files("_history"), tmpdir+"/.irb_history" - IRB.conf[:RC_NAME_GENERATOR] = nil - FileUtils.touch(tmpdir+"/.irbrc") - assert_includes IRB.rc_files, tmpdir+"/.irbrc" - assert_includes IRB.rc_files("_history"), tmpdir+"/.irb_history" + home = ENV['HOME'] = "#{tmpdir}/home" + xdg_config_home = ENV['XDG_CONFIG_HOME'] = "#{tmpdir}/xdg" + reset_rc_name_generators + assert_empty(IRB.irbrc_files) + assert_equal("#{home}/.irb_history", IRB.rc_file('_history')) + FileUtils.mkdir_p(home) + FileUtils.mkdir_p("#{xdg_config_home}/irb") + FileUtils.mkdir_p("#{home}/.config/irb") + reset_rc_name_generators + assert_empty(IRB.irbrc_files) + assert_equal("#{xdg_config_home}/irb/irb_history", IRB.rc_file('_history')) + home_irbrc = "#{home}/.irbrc" + config_irbrc = "#{home}/.config/irb/irbrc" + xdg_config_irbrc = "#{xdg_config_home}/irb/irbrc" + [home_irbrc, config_irbrc, xdg_config_irbrc].each do |file| + FileUtils.touch(file) end + current_dir_irbrcs = %w[.irbrc irbrc _irbrc $irbrc].map { |file| "#{tmpdir}/#{file}" } + current_dir_irbrcs.each { |file| FileUtils.touch(file) } + reset_rc_name_generators + assert_equal([xdg_config_irbrc, home_irbrc, *current_dir_irbrcs], IRB.irbrc_files) + assert_equal(xdg_config_irbrc.sub(/rc$/, '_history'), IRB.rc_file('_history')) + ENV['XDG_CONFIG_HOME'] = nil + reset_rc_name_generators + assert_equal([home_irbrc, config_irbrc, *current_dir_irbrcs], IRB.irbrc_files) + assert_equal(home_irbrc.sub(/rc$/, '_history'), IRB.rc_file('_history')) + ENV['XDG_CONFIG_HOME'] = '' + reset_rc_name_generators + assert_equal([home_irbrc, config_irbrc] + current_dir_irbrcs, IRB.irbrc_files) + assert_equal(home_irbrc.sub(/rc$/, '_history'), IRB.rc_file('_history')) + ENV['XDG_CONFIG_HOME'] = xdg_config_home + ENV['IRBRC'] = "#{tmpdir}/.irbrc" + reset_rc_name_generators + assert_equal([ENV['IRBRC'], xdg_config_irbrc, home_irbrc] + (current_dir_irbrcs - [ENV['IRBRC']]), IRB.irbrc_files) + assert_equal(ENV['IRBRC'] + '_history', IRB.rc_file('_history')) + ENV['IRBRC'] = ENV['HOME'] = ENV['XDG_CONFIG_HOME'] = nil + reset_rc_name_generators + assert_equal(current_dir_irbrcs, IRB.irbrc_files) + assert_nil(IRB.rc_file('_history')) end end - def test_rc_files_has_file_from_xdg_env - tmpdir = @tmpdir - ENV["XDG_CONFIG_HOME"] = "#{tmpdir}/xdg" - xdg_config = ENV["XDG_CONFIG_HOME"]+"/irb/irbrc" - - FileUtils.mkdir_p(xdg_config) - - Dir.chdir(tmpdir) do - IRB.conf[:RC_NAME_GENERATOR] = nil - assert_includes IRB.rc_files, xdg_config - end - ensure - ENV["XDG_CONFIG_HOME"] = nil - end - - def test_rc_files_has_file_from_irbrc_env + def test_duplicated_rc_files tmpdir = @tmpdir - ENV["IRBRC"] = "#{tmpdir}/irb" - - FileUtils.mkdir_p(ENV["IRBRC"]) - Dir.chdir(tmpdir) do - IRB.conf[:RC_NAME_GENERATOR] = nil - assert_includes IRB.rc_files, ENV["IRBRC"] - end - ensure - ENV["IRBRC"] = nil - end - - def test_rc_files_has_file_from_home_env - tmpdir = @tmpdir - ENV["HOME"] = "#{tmpdir}/home" - - FileUtils.mkdir_p(ENV["HOME"]) - - Dir.chdir(tmpdir) do - IRB.conf[:RC_NAME_GENERATOR] = nil - assert_includes IRB.rc_files, ENV["HOME"]+"/.irbrc" - assert_includes IRB.rc_files, ENV["HOME"]+"/.config/irb/irbrc" - end - ensure - ENV["HOME"] = nil - end - - def test_rc_files_contains_non_env_files - tmpdir = @tmpdir - FileUtils.mkdir_p("#{tmpdir}/.irbrc") - FileUtils.mkdir_p("#{tmpdir}/_irbrc") - FileUtils.mkdir_p("#{tmpdir}/irb.rc") - FileUtils.mkdir_p("#{tmpdir}/$irbrc") - - Dir.chdir(tmpdir) do - IRB.conf[:RC_NAME_GENERATOR] = nil - assert_includes IRB.rc_files, tmpdir+"/.irbrc" - assert_includes IRB.rc_files, tmpdir+"/_irbrc" - assert_includes IRB.rc_files, tmpdir+"/irb.rc" - assert_includes IRB.rc_files, tmpdir+"/$irbrc" + ENV['XDG_CONFIG_HOME'] = "#{ENV['HOME']}/.config" + FileUtils.mkdir_p("#{ENV['XDG_CONFIG_HOME']}/irb") + env_irbrc = ENV['IRBRC'] = "#{tmpdir}/_irbrc" + xdg_config_irbrc = "#{ENV['XDG_CONFIG_HOME']}/irb/irbrc" + home_irbrc = "#{ENV['HOME']}/.irbrc" + current_dir_irbrc = "#{tmpdir}/irbrc" + [env_irbrc, xdg_config_irbrc, home_irbrc, current_dir_irbrc].each do |file| + FileUtils.touch(file) + end + reset_rc_name_generators + assert_equal([env_irbrc, xdg_config_irbrc, home_irbrc, current_dir_irbrc], IRB.irbrc_files) end end diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index a0477ee48..511df58ea 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -11,6 +11,8 @@ class IRB::RenderingTest < Yamatanooroti::TestCase def setup @original_term = ENV['TERM'] + @home_backup = ENV['HOME'] + @xdg_config_home_backup = ENV['XDG_CONFIG_HOME'] ENV['TERM'] = "xterm-256color" @pwd = Dir.pwd suffix = '%010d' % Random.rand(0..65535) @@ -24,12 +26,16 @@ def setup @irbrc_backup = ENV['IRBRC'] @irbrc_file = ENV['IRBRC'] = File.join(@tmpdir, 'temporaty_irbrc') File.unlink(@irbrc_file) if File.exist?(@irbrc_file) + ENV['HOME'] = File.join(@tmpdir, 'home') + ENV['XDG_CONFIG_HOME'] = File.join(@tmpdir, 'xdg_config_home') end def teardown FileUtils.rm_rf(@tmpdir) ENV['IRBRC'] = @irbrc_backup ENV['TERM'] = @original_term + ENV['HOME'] = @home_backup + ENV['XDG_CONFIG_HOME'] = @xdg_config_home_backup end def test_launch From 3c6d452495255a7e9a358fd57e77caf16be06425 Mon Sep 17 00:00:00 2001 From: OKURA Masafumi Date: Mon, 18 Mar 2024 11:37:39 +0900 Subject: [PATCH 110/263] docs(help): Add latest options to ja help message (#903) --- lib/irb/lc/ja/help-message | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/irb/lc/ja/help-message b/lib/irb/lc/ja/help-message index cec339cf2..99f4449b3 100644 --- a/lib/irb/lc/ja/help-message +++ b/lib/irb/lc/ja/help-message @@ -9,10 +9,18 @@ Usage: irb.rb [options] [programfile] [arguments] -W[level=2] ruby -W と同じ. --context-mode n 新しいワークスペースを作成した時に関連する Binding オブジェクトの作成方法を 0 から 3 のいずれかに設定する. + --extra-doc-dir 指定したディレクトリのドキュメントを追加で読み込む. --echo 実行結果を表示する(デフォルト). --noecho 実行結果を表示しない. + --echo-on-assignment + 代入結果を表示する. + --noecho-on-assignment + 代入結果を表示しない. + --truncate-echo-on-assignment + truncateされた代入結果を表示する(デフォルト). --inspect 結果出力にinspectを用いる. --noinspect 結果出力にinspectを用いない. + --no-pager ページャを使用しない. --multiline マルチラインエディタを利用する. --nomultiline マルチラインエディタを利用しない. --singleline シングルラインエディタを利用する. @@ -34,6 +42,8 @@ Usage: irb.rb [options] [programfile] [arguments] --sample-book-mode/--simple-prompt 非常にシンプルなプロンプトを用いるモードです. --noprompt プロンプト表示を行なわない. + --script スクリプトモード(最初の引数をスクリプトファイルとして扱う、デフォルト) + --noscript 引数をargvとして扱う. --single-irb irb 中で self を実行して得られるオブジェクトをサ ブ irb と共有する. --tracer コマンド実行時にトレースを行なう. From 89bca01bbac51325a605e31d55e451f251bc5255 Mon Sep 17 00:00:00 2001 From: Artur <22315378+artur-intech@users.noreply.github.com> Date: Fri, 22 Mar 2024 14:34:44 +0300 Subject: [PATCH 111/263] Remove misleading documentation (#906) https://github.com/ruby/irb/issues/904 --- lib/irb.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/irb.rb b/lib/irb.rb index 8e27aff0b..c7d36e744 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -145,7 +145,6 @@ # * The value of variable `$XDG_CONFIG_HOME/irb/irbrc`, if defined. # * File `$HOME/.irbrc`, if it exists. # * File `$HOME/.config/irb/irbrc`, if it exists. -# * File `.config/irb/irbrc` in the current directory, if it exists. # * File `.irbrc` in the current directory, if it exists. # * File `irb.rc` in the current directory, if it exists. # * File `_irbrc` in the current directory, if it exists. From 7c16ce033e1cfc95221629092bc460c2f86b4d82 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 24 Mar 2024 21:54:33 +0900 Subject: [PATCH 112/263] Fix indent test for new reline (#908) --- test/irb/test_irb.rb | 7 ++----- test/irb/yamatanooroti/test_rendering.rb | 26 +++++++++++++----------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index 8c4fb5dde..966c84013 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -70,11 +70,8 @@ def test_empty_input_echoing_behaviour type "exit" end - # Input cramped together due to how Reline's Reline::GeneralIO works - assert_include( - output, - "irb(main):001> irb(main):002> irb(main):002> irb(main):002> => nil\r\n" - ) + assert_not_match(/irb\(main\):001> (\r*\n)?=> nil/, output) + assert_match(/irb\(main\):002> (\r*\n)?=> nil/, output) end end diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index 511df58ea..df4ec01a5 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -137,6 +137,7 @@ def b; true; end a .a .b + .itself EOC close assert_screen(<<~EOC) @@ -153,9 +154,10 @@ def b; true; end irb(main):008> irb(main):009> a irb(main):010> .a - irb(main):011> .b + irb(main):011> .b + irb(main):012> .itself => true - irb(main):012> + irb(main):013> EOC end @@ -181,7 +183,6 @@ def c; true; end (a) &.b() - class A def b; self; end; def c; true; end; end; a = A.new a @@ -190,6 +191,7 @@ class A def b; self; end; def c; true; end; end; .c (a) &.b() + .itself EOC close assert_screen(<<~EOC) @@ -214,17 +216,17 @@ class A def b; self; end; def c; true; end; end; irb(main):015> &.b() => # irb(main):016> - irb(main):017> - irb(main):018> class A def b; self; end; def c; true; end; end; - irb(main):019> a = A.new + irb(main):017> class A def b; self; end; def c; true; end; end; + irb(main):018> a = A.new => # - irb(main):020> a - irb(main):021> .b - irb(main):022> # aaa - irb(main):023> .c + irb(main):019> a + irb(main):020> .b + irb(main):021> # aaa + irb(main):022> .c => true - irb(main):024> (a) - irb(main):025> &.b() + irb(main):023> (a) + irb(main):024> &.b() + irb(main):025> .itself => # irb(main):026> EOC From da84e6cb56cb1b0fb16ab2c7e8b205fb6bb1b8e0 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Mon, 25 Mar 2024 20:48:08 +0900 Subject: [PATCH 113/263] Cache RDoc::RI::Driver.new (#911) * Cache RDoc::RI::Driver.new to improve performance and to avoid flaky test * Insert sleep to fix flaky rendering test that renders document dialog --- lib/irb/input-method.rb | 28 +++++++++++++++--------- test/irb/test_input_method.rb | 13 ++++++----- test/irb/yamatanooroti/test_rendering.rb | 4 +++- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index c19983092..e5adb350e 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -308,6 +308,20 @@ def retrieve_doc_namespace(matched) @completor.doc_namespace(preposing, matched, postposing, bind: bind) end + def rdoc_ri_driver + return @rdoc_ri_driver if defined?(@rdoc_ri_driver) + + begin + require 'rdoc' + rescue LoadError + @rdoc_ri_driver = nil + else + options = {} + options[:extra_doc_dirs] = IRB.conf[:EXTRA_DOC_DIRS] unless IRB.conf[:EXTRA_DOC_DIRS].empty? + @rdoc_ri_driver = RDoc::RI::Driver.new(options) + end + end + def show_doc_dialog_proc input_method = self # self is changed in the lambda below. ->() { @@ -331,9 +345,7 @@ def show_doc_dialog_proc show_easter_egg = name&.match?(/\ARubyVM/) && !ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER'] - options = {} - options[:extra_doc_dirs] = IRB.conf[:EXTRA_DOC_DIRS] unless IRB.conf[:EXTRA_DOC_DIRS].empty? - driver = RDoc::RI::Driver.new(options) + driver = input_method.rdoc_ri_driver if key.match?(dialog.name) if show_easter_egg @@ -421,12 +433,9 @@ def show_doc_dialog_proc } end - def display_document(matched, driver: nil) - begin - require 'rdoc' - rescue LoadError - return - end + def display_document(matched) + driver = rdoc_ri_driver + return unless driver if matched =~ /\A(?:::)?RubyVM/ and not ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER'] IRB.__send__(:easter_egg) @@ -436,7 +445,6 @@ def display_document(matched, driver: nil) namespace = retrieve_doc_namespace(matched) return unless namespace - driver ||= RDoc::RI::Driver.new if namespace.is_a?(Array) out = RDoc::Markup::Document.new namespace.each do |m| diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb index 7644d3176..ce317b4b3 100644 --- a/test/irb/test_input_method.rb +++ b/test/irb/test_input_method.rb @@ -88,17 +88,18 @@ def setup @driver = RDoc::RI::Driver.new(use_stdout: true) end - def display_document(target, bind) + def display_document(target, bind, driver = nil) input_method = IRB::RelineInputMethod.new(IRB::RegexpCompletor.new) + input_method.instance_variable_set(:@rdoc_ri_driver, driver) if driver input_method.instance_variable_set(:@completion_params, ['', target, '', bind]) - input_method.display_document(target, driver: @driver) + input_method.display_document(target) end def test_perfectly_matched_namespace_triggers_document_display omit unless has_rdoc_content? out, err = capture_output do - display_document("String", binding) + display_document("String", binding, @driver) end assert_empty(err) @@ -109,7 +110,7 @@ def test_perfectly_matched_namespace_triggers_document_display def test_perfectly_matched_multiple_namespaces_triggers_document_display result = nil out, err = capture_output do - result = display_document("{}.nil?", binding) + result = display_document("{}.nil?", binding, @driver) end assert_empty(err) @@ -131,7 +132,7 @@ def test_perfectly_matched_multiple_namespaces_triggers_document_display def test_not_matched_namespace_triggers_nothing result = nil out, err = capture_output do - result = display_document("Stri", binding) + result = display_document("Stri", binding, @driver) end assert_empty(err) @@ -156,7 +157,7 @@ def test_perfect_matching_stops_without_rdoc def test_perfect_matching_handles_nil_namespace out, err = capture_output do # symbol literal has `nil` doc namespace so it's a good test subject - assert_nil(display_document(":aiueo", binding)) + assert_nil(display_document(":aiueo", binding, @driver)) end assert_empty(err) diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index df4ec01a5..44e07a3a1 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -256,9 +256,9 @@ def test_autocomplete_with_multiple_doc_namespaces start_terminal(3, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') write("{}.__id_") write("\C-i") + sleep 0.2 close screen = result.join("\n").sub(/\n*\z/, "\n") - # This assertion passes whether showdoc dialog completed or not. assert_match(/start\ IRB\nirb\(main\):001> {}\.__id__\n }\.__id__(?:Press )?/, screen) end @@ -278,6 +278,7 @@ def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_right start_terminal(4, 19, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') write("IR") write("\C-i") + sleep 0.2 close # This is because on macOS we display different shortcut for displaying the full doc @@ -315,6 +316,7 @@ def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_left start_terminal(4, 12, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') write("IR") write("\C-i") + sleep 0.2 close assert_screen(<<~EOC) start IRB From 2057248e40f36bd81940a528b5585ba44fc4672f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 26 Mar 2024 20:30:29 +0900 Subject: [PATCH 114/263] Fix a typo (#912) --- lib/irb/cmd/nop.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/irb/cmd/nop.rb b/lib/irb/cmd/nop.rb index 49f89bac9..9d2e3c4d4 100644 --- a/lib/irb/cmd/nop.rb +++ b/lib/irb/cmd/nop.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true # This file is just a placeholder for backward-compatibility. -# Please require 'irb' and inheirt your command from `IRB::Command::Base` instead. +# Please require 'irb' and inherit your command from `IRB::Command::Base` instead. From 805ee008f91571744fb5b372edc1f2ce1f88b8aa Mon Sep 17 00:00:00 2001 From: Joshua Broughton Date: Fri, 5 Apr 2024 10:25:45 -0600 Subject: [PATCH 115/263] Filter backtrace before format in handle_exception (#916) handle_exception now applies the filter_backtrace to exception backtraces prior to formatting the lines with Exception#full_message This fixes a bug in upstream projects, notably Rails, where the backtrace filtering logic expects the lines to be formatted as Exception#backtrace. Co-authored-by: Hartley McGuire --- lib/irb.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/irb.rb b/lib/irb.rb index c7d36e744..0855c59de 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1222,6 +1222,13 @@ def handle_exception(exc) irb_bug = true else irb_bug = false + # This is mostly to make IRB work nicely with Rails console's backtrace filtering, which patches WorkSpace#filter_backtrace + # In such use case, we want to filter the exception's backtrace before its displayed through Exception#full_message + # And we clone the exception object in order to avoid mutating the original exception + # TODO: introduce better API to expose exception backtrace externally + backtrace = exc.backtrace.map { |l| @context.workspace.filter_backtrace(l) }.compact + exc = exc.clone + exc.set_backtrace(backtrace) end if RUBY_VERSION < '3.0.0' @@ -1246,7 +1253,6 @@ def handle_exception(exc) lines = m.split("\n").reverse end unless irb_bug - lines = lines.map { |l| @context.workspace.filter_backtrace(l) }.compact if lines.size > @context.back_trace_limit omit = lines.size - @context.back_trace_limit lines = lines[0..(@context.back_trace_limit - 1)] From 8fb776e37913d69b2208a3313f5824fd8f42bb0c Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 11 Apr 2024 01:52:47 +0900 Subject: [PATCH 116/263] Command implementation not by method (#824) * Command is not a method * Fix command test * Implement non-method command name completion * Add test for ExtendCommandBundle.def_extend_command * Add helper method install test * Remove spaces in command input parse * Remove command arg unquote in help command * Simplify Statement and handle execution in IRB::Irb * Tweak require, const name * Always install CommandBundle module to main object * Remove considering local variable in command or expression check * Remove unused method, tweak * Remove outdated comment for help command arg Co-authored-by: Stan Lo --------- Co-authored-by: Stan Lo --- lib/irb.rb | 58 ++++++++----- lib/irb/command.rb | 118 +++++++------------------- lib/irb/command/backtrace.rb | 8 +- lib/irb/command/base.rb | 35 ++++++-- lib/irb/command/break.rb | 8 +- lib/irb/command/catch.rb | 8 +- lib/irb/command/chws.rb | 11 ++- lib/irb/command/context.rb | 16 ++++ lib/irb/command/continue.rb | 4 +- lib/irb/command/debug.rb | 6 +- lib/irb/command/delete.rb | 4 +- lib/irb/command/edit.rb | 16 +--- lib/irb/command/finish.rb | 4 +- lib/irb/command/help.rb | 19 +---- lib/irb/command/history.rb | 10 +-- lib/irb/command/info.rb | 8 +- lib/irb/command/irb_info.rb | 2 +- lib/irb/command/load.rb | 22 ++++- lib/irb/command/ls.rb | 28 ++++--- lib/irb/command/measure.rb | 16 ++-- lib/irb/command/next.rb | 4 +- lib/irb/command/pushws.rb | 15 ++-- lib/irb/command/show_doc.rb | 27 ++---- lib/irb/command/show_source.rb | 15 +--- lib/irb/command/step.rb | 4 +- lib/irb/command/subirb.rb | 35 +++++--- lib/irb/command/whereami.rb | 2 +- lib/irb/completion.rb | 17 +++- lib/irb/context.rb | 12 --- lib/irb/ext/change-ws.rb | 8 +- lib/irb/ext/workspaces.rb | 7 +- lib/irb/statement.rb | 32 ++----- lib/irb/workspace.rb | 6 +- test/irb/test_command.rb | 143 ++++++++++++++++++++++++++++---- test/irb/test_completion.rb | 7 ++ test/irb/test_type_completor.rb | 7 +- 36 files changed, 414 insertions(+), 328 deletions(-) create mode 100644 lib/irb/command/context.rb diff --git a/lib/irb.rb b/lib/irb.rb index 0855c59de..edc4fc5d5 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -929,7 +929,7 @@ class Irb # Creates a new irb session def initialize(workspace = nil, input_method = nil) @context = Context.new(self, workspace, input_method) - @context.workspace.load_commands_to_main + @context.workspace.load_helper_methods_to_main @signal_status = :IN_IRB @scanner = RubyLex.new @line_no = 1 @@ -950,7 +950,7 @@ def debug_break def debug_readline(binding) workspace = IRB::WorkSpace.new(binding) context.replace_workspace(workspace) - context.workspace.load_commands_to_main + context.workspace.load_helper_methods_to_main @line_no += 1 # When users run: @@ -1028,7 +1028,15 @@ def eval_input return statement.code end - @context.evaluate(statement.evaluable_code, line_no) + case statement + when Statement::EmptyInput + # Do nothing + when Statement::Expression + @context.evaluate(statement.code, line_no) + when Statement::Command + ret = statement.command_class.execute(@context, statement.arg) + @context.set_last_value(ret) + end if @context.echo? && !statement.suppresses_echo? if statement.is_assignment? @@ -1084,10 +1092,7 @@ def readmultiline end code << line - - # Accept any single-line input for symbol aliases or commands that transform - # args - return code if single_line_command?(code) + return code if command?(code) tokens, opens, terminated = @scanner.check_code_state(code, local_variables: @context.local_variables) return code if terminated @@ -1114,23 +1119,36 @@ def build_statement(code) end code.force_encoding(@context.io.encoding) - command_or_alias, arg = code.split(/\s/, 2) - # Transform a non-identifier alias (@, $) or keywords (next, break) - command_name = @context.command_aliases[command_or_alias.to_sym] - command = command_name || command_or_alias - command_class = ExtendCommandBundle.load_command(command) - - if command_class - Statement::Command.new(code, command, arg, command_class) + if (command, arg = parse_command(code)) + command_class = ExtendCommandBundle.load_command(command) + Statement::Command.new(code, command_class, arg) else is_assignment_expression = @scanner.assignment_expression?(code, local_variables: @context.local_variables) Statement::Expression.new(code, is_assignment_expression) end end - def single_line_command?(code) - command = code.split(/\s/, 2).first - @context.symbol_alias?(command) || @context.transform_args?(command) + def parse_command(code) + command_name, arg = code.strip.split(/\s+/, 2) + return unless code.lines.size == 1 && command_name + + arg ||= '' + command = command_name.to_sym + # Command aliases are always command. example: $, @ + if (alias_name = @context.command_aliases[command]) + return [alias_name, arg] + end + + # Check visibility + public_method = !!Kernel.instance_method(:public_method).bind_call(@context.main, command) rescue false + private_method = !public_method && !!Kernel.instance_method(:method).bind_call(@context.main, command) rescue false + if ExtendCommandBundle.execute_as_command?(command, public_method: public_method, private_method: private_method) + [command, arg] + end + end + + def command?(code) + !!parse_command(code) end def configure_io @@ -1148,9 +1166,7 @@ def configure_io false end else - # Accept any single-line input for symbol aliases or commands that transform - # args - next true if single_line_command?(code) + next true if command?(code) _tokens, _opens, terminated = @scanner.check_code_state(code, local_variables: @context.local_variables) terminated diff --git a/lib/irb/command.rb b/lib/irb/command.rb index ef9304923..43cbda36b 100644 --- a/lib/irb/command.rb +++ b/lib/irb/command.rb @@ -12,29 +12,17 @@ module Command; end # Installs the default irb extensions command bundle. module ExtendCommandBundle - EXCB = ExtendCommandBundle # :nodoc: - - # See #install_alias_method. + # See ExtendCommandBundle.execute_as_command?. NO_OVERRIDE = 0 - # See #install_alias_method. OVERRIDE_PRIVATE_ONLY = 0x01 - # See #install_alias_method. OVERRIDE_ALL = 0x02 - # Displays current configuration. - # - # Modifying the configuration is achieved by sending a message to IRB.conf. - def irb_context - IRB.CurrentContext - end - - @ALIASES = [ - [:context, :irb_context, NO_OVERRIDE], - [:conf, :irb_context, NO_OVERRIDE], - ] - - @EXTEND_COMMANDS = [ + [ + :irb_context, :Context, "command/context", + [:context, NO_OVERRIDE], + [:conf, NO_OVERRIDE], + ], [ :irb_exit, :Exit, "command/exit", [:exit, OVERRIDE_PRIVATE_ONLY], @@ -204,6 +192,26 @@ def irb_context ], ] + def self.command_override_policies + @@command_override_policies ||= @EXTEND_COMMANDS.flat_map do |cmd_name, cmd_class, load_file, *aliases| + [[cmd_name, OVERRIDE_ALL]] + aliases + end.to_h + end + + def self.execute_as_command?(name, public_method:, private_method:) + case command_override_policies[name] + when OVERRIDE_ALL + true + when OVERRIDE_PRIVATE_ONLY + !public_method + when NO_OVERRIDE + !public_method && !private_method + end + end + + def self.command_names + command_override_policies.keys.map(&:to_s) + end @@commands = [] @@ -247,77 +255,13 @@ def self.load_command(command) nil end - # Installs the default irb commands. - def self.install_extend_commands - for args in @EXTEND_COMMANDS - def_extend_command(*args) - end - end - - # Evaluate the given +cmd_name+ on the given +cmd_class+ Class. - # - # Will also define any given +aliases+ for the method. - # - # The optional +load_file+ parameter will be required within the method - # definition. def self.def_extend_command(cmd_name, cmd_class, load_file, *aliases) - case cmd_class - when Symbol - cmd_class = cmd_class.id2name - when String - when Class - cmd_class = cmd_class.name - end - - line = __LINE__; eval %[ - def #{cmd_name}(*opts, **kwargs, &b) - Kernel.require_relative "#{load_file}" - ::IRB::Command::#{cmd_class}.execute(irb_context, *opts, **kwargs, &b) - end - ], nil, __FILE__, line - - for ali, flag in aliases - @ALIASES.push [ali, cmd_name, flag] - end - end - - # Installs alias methods for the default irb commands, see - # ::install_extend_commands. - def install_alias_method(to, from, override = NO_OVERRIDE) - to = to.id2name unless to.kind_of?(String) - from = from.id2name unless from.kind_of?(String) + @EXTEND_COMMANDS.delete_if { |name,| name == cmd_name } + @EXTEND_COMMANDS << [cmd_name, cmd_class, load_file, *aliases] - if override == OVERRIDE_ALL or - (override == OVERRIDE_PRIVATE_ONLY) && !respond_to?(to) or - (override == NO_OVERRIDE) && !respond_to?(to, true) - target = self - (class << self; self; end).instance_eval{ - if target.respond_to?(to, true) && - !target.respond_to?(EXCB.irb_original_method_name(to), true) - alias_method(EXCB.irb_original_method_name(to), to) - end - alias_method to, from - } - else - Kernel.warn "irb: warn: can't alias #{to} from #{from}.\n" - end - end - - def self.irb_original_method_name(method_name) # :nodoc: - "irb_" + method_name + "_org" - end - - # Installs alias methods for the default irb commands on the given object - # using #install_alias_method. - def self.extend_object(obj) - unless (class << obj; ancestors; end).include?(EXCB) - super - for ali, com, flg in @ALIASES - obj.install_alias_method(ali, com, flg) - end - end + # Just clear memoized values + @@commands = [] + @@command_override_policies = nil end - - install_extend_commands end end diff --git a/lib/irb/command/backtrace.rb b/lib/irb/command/backtrace.rb index 47e5e6072..610f9ee22 100644 --- a/lib/irb/command/backtrace.rb +++ b/lib/irb/command/backtrace.rb @@ -7,12 +7,8 @@ module IRB module Command class Backtrace < DebugCommand - def self.transform_args(args) - args&.dump - end - - def execute(*args) - super(pre_cmds: ["backtrace", *args].join(" ")) + def execute(arg) + execute_debug_command(pre_cmds: "backtrace #{arg}".rstrip) end end end diff --git a/lib/irb/command/base.rb b/lib/irb/command/base.rb index 3ce4f4d6c..ff74b5fb3 100644 --- a/lib/irb/command/base.rb +++ b/lib/irb/command/base.rb @@ -10,6 +10,10 @@ module IRB 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) @@ -29,19 +33,13 @@ def help_message(help_message = nil) 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) - command = new(irb_context) - command.execute(*opts, **kwargs, &block) + def self.execute(irb_context, arg) + new(irb_context).execute(arg) rescue CommandArgumentError => e puts e.message end @@ -52,7 +50,26 @@ def initialize(irb_context) attr_reader :irb_context - def execute(*opts) + 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 + + def execute(arg) #nop end end diff --git a/lib/irb/command/break.rb b/lib/irb/command/break.rb index fa200f2d7..42ee002ce 100644 --- a/lib/irb/command/break.rb +++ b/lib/irb/command/break.rb @@ -7,12 +7,8 @@ module IRB module Command class Break < DebugCommand - def self.transform_args(args) - args&.dump - end - - def execute(args = nil) - super(pre_cmds: "break #{args}") + def execute(arg) + execute_debug_command(pre_cmds: "break #{arg}".rstrip) end end end diff --git a/lib/irb/command/catch.rb b/lib/irb/command/catch.rb index 6b2edff5e..655c77d8a 100644 --- a/lib/irb/command/catch.rb +++ b/lib/irb/command/catch.rb @@ -7,12 +7,8 @@ module IRB module Command class Catch < DebugCommand - def self.transform_args(args) - args&.dump - end - - def execute(*args) - super(pre_cmds: ["catch", *args].join(" ")) + def execute(arg) + execute_debug_command(pre_cmds: "catch #{arg}".rstrip) end end end diff --git a/lib/irb/command/chws.rb b/lib/irb/command/chws.rb index e1047686c..e0a406885 100644 --- a/lib/irb/command/chws.rb +++ b/lib/irb/command/chws.rb @@ -14,7 +14,7 @@ class CurrentWorkingWorkspace < Base category "Workspace" description "Show the current workspace." - def execute(*obj) + def execute(_arg) irb_context.main end end @@ -23,8 +23,13 @@ class ChangeWorkspace < Base category "Workspace" description "Change the current workspace to an object." - def execute(*obj) - irb_context.change_workspace(*obj) + def execute(arg) + if arg.empty? + irb_context.change_workspace + else + obj = eval(arg, irb_context.workspace.binding) + irb_context.change_workspace(obj) + end irb_context.main end end diff --git a/lib/irb/command/context.rb b/lib/irb/command/context.rb new file mode 100644 index 000000000..b4fc80734 --- /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 index 8b6ffc860..49e4384eb 100644 --- a/lib/irb/command/continue.rb +++ b/lib/irb/command/continue.rb @@ -7,8 +7,8 @@ module IRB module Command class Continue < DebugCommand - def execute(*args) - super(do_cmds: ["continue", *args].join(" ")) + def execute(arg) + execute_debug_command(do_cmds: "continue #{arg}".rstrip) end end end diff --git a/lib/irb/command/debug.rb b/lib/irb/command/debug.rb index bdf91766b..aeafe19b5 100644 --- a/lib/irb/command/debug.rb +++ b/lib/irb/command/debug.rb @@ -13,7 +13,11 @@ class Debug < Base binding.method(:irb).source_location.first, ].map { |file| /\A#{Regexp.escape(file)}:\d+:in (`|'Binding#)irb'\z/ } - def execute(pre_cmds: nil, do_cmds: nil) + def execute(_arg) + execute_debug_command + end + + def execute_debug_command(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 diff --git a/lib/irb/command/delete.rb b/lib/irb/command/delete.rb index a36b4577b..4b45a51e7 100644 --- a/lib/irb/command/delete.rb +++ b/lib/irb/command/delete.rb @@ -7,8 +7,8 @@ module IRB module Command class Delete < DebugCommand - def execute(*args) - super(pre_cmds: ["delete", *args].join(" ")) + def execute(arg) + execute_debug_command(pre_cmds: "delete #{arg}".rstrip) end end end diff --git a/lib/irb/command/edit.rb b/lib/irb/command/edit.rb index ab8c62663..480100bfc 100644 --- a/lib/irb/command/edit.rb +++ b/lib/irb/command/edit.rb @@ -26,19 +26,9 @@ class Edit < Base edit Foo#bar HELP_MESSAGE - 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 + def execute(arg) + # Accept string literal for backward compatibility + path = unwrap_string_literal(arg) if path.nil? path = @irb_context.irb_path diff --git a/lib/irb/command/finish.rb b/lib/irb/command/finish.rb index 05501819e..c1d62357f 100644 --- a/lib/irb/command/finish.rb +++ b/lib/irb/command/finish.rb @@ -7,8 +7,8 @@ module IRB module Command class Finish < DebugCommand - def execute(*args) - super(do_cmds: ["finish", *args].join(" ")) + def execute(arg) + execute_debug_command(do_cmds: "finish #{arg}".rstrip) end end end diff --git a/lib/irb/command/help.rb b/lib/irb/command/help.rb index 19113dbbf..c9f16e05b 100644 --- a/lib/irb/command/help.rb +++ b/lib/irb/command/help.rb @@ -6,27 +6,16 @@ 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) + def execute(command_name) content = - if command_name + if command_name.empty? + help_message + else 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 diff --git a/lib/irb/command/history.rb b/lib/irb/command/history.rb index a47a8795d..90f87f910 100644 --- a/lib/irb/command/history.rb +++ b/lib/irb/command/history.rb @@ -12,14 +12,12 @@ 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+(?.+)\s*\n\z/) - return unless match + def execute(arg) - "grep: #{Regexp.new(match[:grep]).inspect}" - end + if (match = arg&.match(/(-g|-G)\s+(?.+)\s*\n\z/)) + grep = Regexp.new(match[:grep]) + 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) diff --git a/lib/irb/command/info.rb b/lib/irb/command/info.rb index a67be3eb8..897ee2c43 100644 --- a/lib/irb/command/info.rb +++ b/lib/irb/command/info.rb @@ -7,12 +7,8 @@ module IRB module Command class Info < DebugCommand - def self.transform_args(args) - args&.dump - end - - def execute(*args) - super(pre_cmds: ["info", *args].join(" ")) + def execute(arg) + execute_debug_command(pre_cmds: "info #{arg}".rstrip) end end end diff --git a/lib/irb/command/irb_info.rb b/lib/irb/command/irb_info.rb index cc93fdcbd..6d868de94 100644 --- a/lib/irb/command/irb_info.rb +++ b/lib/irb/command/irb_info.rb @@ -8,7 +8,7 @@ class IrbInfo < Base category "IRB" description "Show information about IRB." - def execute + def execute(_arg) str = "Ruby version: #{RUBY_VERSION}\n" str += "IRB version: #{IRB.version}\n" str += "InputMethod: #{IRB.CurrentContext.io.inspect}\n" diff --git a/lib/irb/command/load.rb b/lib/irb/command/load.rb index 9e89a7b7f..33e327f4a 100644 --- a/lib/irb/command/load.rb +++ b/lib/irb/command/load.rb @@ -21,7 +21,12 @@ class Load < LoaderCommand category "IRB" description "Load a Ruby file." - def execute(file_name = nil, priv = nil) + 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 @@ -30,7 +35,13 @@ def execute(file_name = nil, priv = nil) class Require < LoaderCommand category "IRB" description "Require a Ruby file." - def execute(file_name = nil) + + 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)?") @@ -63,7 +74,12 @@ class Source < LoaderCommand category "IRB" description "Loads a given file in the current session." - def execute(file_name = nil) + 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) diff --git a/lib/irb/command/ls.rb b/lib/irb/command/ls.rb index 6b6136c2f..f6b196486 100644 --- a/lib/irb/command/ls.rb +++ b/lib/irb/command/ls.rb @@ -20,27 +20,35 @@ class Ls < Base -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/) - args = match[:args] - "#{args}#{',' unless args.chomp.empty?} grep: /#{match[:grep]}/" + def execute(arg) + if match = arg.match(/\A(?.+\s|)(-g|-G)\s+(?.+)$/) + if match[:target].empty? + use_main = true + else + obj = @irb_context.workspace.binding.eval(match[:target]) + end + grep = Regexp.new(match[:grep]) else - args + 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 - 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.dump("locals", locals) if locals o.print_result end diff --git a/lib/irb/command/measure.rb b/lib/irb/command/measure.rb index ee7927b01..70dc69cde 100644 --- a/lib/irb/command/measure.rb +++ b/lib/irb/command/measure.rb @@ -10,15 +10,19 @@ 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? + 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 diff --git a/lib/irb/command/next.rb b/lib/irb/command/next.rb index 6487c9d24..92d28e33e 100644 --- a/lib/irb/command/next.rb +++ b/lib/irb/command/next.rb @@ -7,8 +7,8 @@ module IRB module Command class Next < DebugCommand - def execute(*args) - super(do_cmds: ["next", *args].join(" ")) + def execute(arg) + execute_debug_command(do_cmds: "next #{arg}".rstrip) end end end diff --git a/lib/irb/command/pushws.rb b/lib/irb/command/pushws.rb index 888fe466f..b51928c65 100644 --- a/lib/irb/command/pushws.rb +++ b/lib/irb/command/pushws.rb @@ -14,7 +14,7 @@ class Workspaces < Base category "Workspace" description "Show workspaces." - def execute(*obj) + def execute(_arg) inspection_resuls = irb_context.instance_variable_get(:@workspace_stack).map do |ws| truncated_inspect(ws.main) end @@ -39,8 +39,13 @@ class PushWorkspace < Workspaces category "Workspace" description "Push an object to the workspace stack." - def execute(*obj) - irb_context.push_workspace(*obj) + 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 @@ -49,8 +54,8 @@ class PopWorkspace < Workspaces category "Workspace" description "Pop a workspace from the workspace stack." - def execute(*obj) - irb_context.pop_workspace(*obj) + def execute(_arg) + irb_context.pop_workspace super end end diff --git a/lib/irb/command/show_doc.rb b/lib/irb/command/show_doc.rb index 4dde28bee..f9393cd3b 100644 --- a/lib/irb/command/show_doc.rb +++ b/lib/irb/command/show_doc.rb @@ -3,17 +3,6 @@ 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 "Look up documentation with RI." @@ -31,7 +20,9 @@ def transform_args(args) HELP_MESSAGE - def execute(*names) + def execute(arg) + # Accept string literal for backward compatibility + name = unwrap_string_literal(arg) require 'rdoc/ri/driver' unless ShowDoc.const_defined?(:Ri) @@ -39,15 +30,13 @@ def execute(*names) ShowDoc.const_set(:Ri, RDoc::RI::Driver.new(opts)) end - if names.empty? + if name.nil? Ri.interactive else - names.each do |name| - begin - Ri.display_name(name.to_s) - rescue RDoc::RI::Error - puts $!.message - end + begin + Ri.display_name(name) + rescue RDoc::RI::Error + puts $!.message end end diff --git a/lib/irb/command/show_source.rb b/lib/irb/command/show_source.rb index 32bdf74d3..c4c8fc004 100644 --- a/lib/irb/command/show_source.rb +++ b/lib/irb/command/show_source.rb @@ -24,18 +24,9 @@ class ShowSource < Base show_source Foo::BAR HELP_MESSAGE - 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) + 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 diff --git a/lib/irb/command/step.rb b/lib/irb/command/step.rb index cce7d2b0f..514981302 100644 --- a/lib/irb/command/step.rb +++ b/lib/irb/command/step.rb @@ -7,8 +7,8 @@ module IRB module Command class Step < DebugCommand - def execute(*args) - super(do_cmds: ["step", *args].join(" ")) + def execute(arg) + execute_debug_command(do_cmds: "step #{arg}".rstrip) end end end diff --git a/lib/irb/command/subirb.rb b/lib/irb/command/subirb.rb index 5cc7b8c6f..24428a5c1 100644 --- a/lib/irb/command/subirb.rb +++ b/lib/irb/command/subirb.rb @@ -9,10 +9,6 @@ module IRB module Command class MultiIRBCommand < Base - def execute(*args) - extend_irb_context - end - private def print_deprecated_warning @@ -36,7 +32,12 @@ class IrbCommand < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "Start a child IRB." - def execute(*obj) + 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 @@ -44,7 +45,7 @@ def execute(*obj) return end - super + extend_irb_context IRB.irb(nil, *obj) end end @@ -53,7 +54,7 @@ class Jobs < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "List of current sessions." - def execute + def execute(_arg) print_deprecated_warning if irb_context.with_debugger @@ -61,7 +62,7 @@ def execute return end - super + extend_irb_context IRB.JobManager end end @@ -70,7 +71,12 @@ class Foreground < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "Switches to the session of the given number." - def execute(key = nil) + 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 @@ -78,7 +84,7 @@ def execute(key = nil) return end - super + 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) @@ -89,7 +95,12 @@ class Kill < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "Kills the session with the given number." - def execute(*keys) + 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 @@ -97,7 +108,7 @@ def execute(*keys) return end - super + extend_irb_context IRB.JobManager.kill(*keys) end end diff --git a/lib/irb/command/whereami.rb b/lib/irb/command/whereami.rb index d6658d704..c8439f121 100644 --- a/lib/irb/command/whereami.rb +++ b/lib/irb/command/whereami.rb @@ -8,7 +8,7 @@ class Whereami < Base category "Context" description "Show the source code around binding.irb again." - def execute(*) + def execute(_arg) code = irb_context.workspace.code_around_binding if code puts code diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index b3813e893..8a1df1156 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -86,6 +86,14 @@ def retrieve_files_to_require_from_load_path ) end + def command_completions(preposing, target) + if preposing.empty? && !target.empty? + IRB::ExtendCommandBundle.command_names.select { _1.start_with?(target) } + else + [] + end + end + def retrieve_files_to_require_relative_from_current_dir @files_from_current_dir ||= Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: '.').map { |path| path.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '') @@ -103,9 +111,11 @@ def inspect end def completion_candidates(preposing, target, _postposing, bind:) + commands = command_completions(preposing, target) result = ReplTypeCompletor.analyze(preposing + target, binding: bind, filename: @context.irb_path) - return [] unless result - result.completion_candidates.map { target + _1 } + return commands unless result + + commands | result.completion_candidates.map { target + _1 } end def doc_namespace(preposing, matched, _postposing, bind:) @@ -181,7 +191,8 @@ def completion_candidates(preposing, target, postposing, bind:) result = complete_require_path(target, preposing, postposing) return result if result end - retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map{ |i| i.encode(Encoding.default_external) } + commands = command_completions(preposing || '', target) + commands | retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map{ |i| i.encode(Encoding.default_external) } end def doc_namespace(_preposing, matched, _postposing, bind:) diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 60dfb9668..e3c419245 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -646,17 +646,5 @@ def inspect # :nodoc: def local_variables # :nodoc: workspace.binding.local_variables end - - # Return true if it's aliased from the argument and it's not an identifier. - def symbol_alias?(command) - return nil if command.match?(/\A\w+\z/) - command_aliases.key?(command.to_sym) - end - - # Return true if the command supports transforming args - def transform_args?(command) - command = command_aliases.fetch(command.to_sym, command) - ExtendCommandBundle.load_command(command)&.respond_to?(:transform_args) - end end end diff --git a/lib/irb/ext/change-ws.rb b/lib/irb/ext/change-ws.rb index 87fe03e23..60e8afe31 100644 --- a/lib/irb/ext/change-ws.rb +++ b/lib/irb/ext/change-ws.rb @@ -29,11 +29,9 @@ def change_workspace(*_main) return main end - replace_workspace(WorkSpace.new(_main[0])) - - if !(class< @command_class end - - def evaluable_code - # Hook command-specific transformation to return valid Ruby code - if @command_class.respond_to?(:transform_args) - arg = @command_class.transform_args(@arg) - else - arg = @arg - end - - [@command, arg].compact.join(' ') - end end end end diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb index 4c3b5e425..1490f7b47 100644 --- a/lib/irb/workspace.rb +++ b/lib/irb/workspace.rb @@ -108,8 +108,10 @@ def initialize(*main) # IRB.conf[:__MAIN__] attr_reader :main - def load_commands_to_main - main.extend ExtendCommandBundle + def load_helper_methods_to_main + if !(class< nil\nfoo\nBAR is added\.\n=> nil\nbar\nfoo\n=> 3\nbar\nfoo\n=> nil\nbar\n=> nil\n=> 3\n/, out) + assert_match(/\AFOO is added\.\n=> nil\nfoo\n=> 1\nBAR is added\.\n=> nil\nbar\nfoo\n=> 2\n=> nil\nbar\n=> 3\n=> nil\n=> 4\n/, out) end def test_measure_with_proc_warning @@ -402,7 +496,6 @@ def test_measure_with_proc_warning out, err = execute_lines( "3\n", "measure do\n", - "end\n", "3\n", conf: conf, main: c @@ -554,7 +647,8 @@ def test_popws_replaces_the_current_workspace_with_the_previous_one out, err = execute_lines( "pushws Foo.new\n", "popws\n", - "cwws.class", + "cwws\n", + "_.class", ) assert_empty err assert_include(out, "=> #{self.class}") @@ -573,7 +667,8 @@ class ChwsTest < WorkspaceCommandTestCase def test_chws_replaces_the_current_workspace out, err = execute_lines( "chws #{self.class}::Foo.new\n", - "cwws.class", + "cwws\n", + "_.class", ) assert_empty err assert_include(out, "=> #{self.class}::Foo") @@ -582,7 +677,8 @@ def test_chws_replaces_the_current_workspace def test_chws_does_nothing_when_receiving_no_argument out, err = execute_lines( "chws\n", - "cwws.class", + "cwws\n", + "_.class", ) assert_empty err assert_include(out, "=> #{self.class}") @@ -730,18 +826,18 @@ def test_ls_grep def test_ls_grep_empty out, err = execute_lines("ls\n") assert_empty err - assert_match(/whereami/, out) - assert_match(/show_source/, out) + assert_match(/assert/, out) + assert_match(/refute/, out) [ - "ls grep: /whereami/\n", - "ls -g whereami\n", - "ls -G whereami\n", + "ls grep: /assert/\n", + "ls -g assert\n", + "ls -G assert\n", ].each do |line| out, err = execute_lines(line) assert_empty err - assert_match(/whereami/, out) - assert_not_match(/show_source/, out) + assert_match(/assert/, out) + assert_not_match(/refute/, out) end end @@ -951,4 +1047,19 @@ def test_history_grep end + class HelperMethodInsallTest < CommandTestCase + def test_helper_method_install + IRB::ExtendCommandBundle.module_eval do + def foobar + "test_helper_method_foobar" + end + end + + out, err = execute_lines("foobar.upcase") + assert_empty err + assert_include(out, '=> "TEST_HELPER_METHOD_FOOBAR"') + ensure + IRB::ExtendCommandBundle.remove_method :foobar + end + end end diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb index 819446393..5fe7952b3 100644 --- a/test/irb/test_completion.rb +++ b/test/irb/test_completion.rb @@ -14,6 +14,13 @@ def doc_namespace(target, bind) IRB::RegexpCompletor.new.doc_namespace('', target, '', bind: bind) end + class CommandCompletionTest < CompletionTest + def test_command_completion + assert_include(IRB::RegexpCompletor.new.completion_candidates('', 'show_s', '', bind: binding), 'show_source') + assert_not_include(IRB::RegexpCompletor.new.completion_candidates(';', 'show_s', '', bind: binding), 'show_source') + end + end + class MethodCompletionTest < CompletionTest def test_complete_string assert_include(completion_candidates("'foo'.up", binding), "'foo'.upcase") diff --git a/test/irb/test_type_completor.rb b/test/irb/test_type_completor.rb index cf4fc12c9..5ed8988b3 100644 --- a/test/irb/test_type_completor.rb +++ b/test/irb/test_type_completor.rb @@ -9,7 +9,7 @@ return end -require 'irb/completion' +require 'irb' require 'tempfile' require_relative './helper' @@ -54,6 +54,11 @@ def test_empty_completion assert_equal [], candidates assert_doc_namespace('(', ')', nil) end + + def test_command_completion + assert_include(@completor.completion_candidates('', 'show_s', '', bind: binding), 'show_source') + assert_not_include(@completor.completion_candidates(';', 'show_s', '', bind: binding), 'show_source') + end end class TypeCompletorIntegrationTest < IntegrationTestCase From 97898b6251beac84b70b437c70fea4524f72cd64 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 11 Apr 2024 01:33:40 +0800 Subject: [PATCH 117/263] Centralize rstrip calls (#918) --- lib/irb/command/backtrace.rb | 2 +- lib/irb/command/break.rb | 2 +- lib/irb/command/catch.rb | 2 +- lib/irb/command/continue.rb | 2 +- lib/irb/command/debug.rb | 3 +++ lib/irb/command/delete.rb | 2 +- lib/irb/command/finish.rb | 2 +- lib/irb/command/info.rb | 2 +- lib/irb/command/next.rb | 2 +- lib/irb/command/step.rb | 2 +- 10 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/irb/command/backtrace.rb b/lib/irb/command/backtrace.rb index 610f9ee22..687bb075a 100644 --- a/lib/irb/command/backtrace.rb +++ b/lib/irb/command/backtrace.rb @@ -8,7 +8,7 @@ module IRB module Command class Backtrace < DebugCommand def execute(arg) - execute_debug_command(pre_cmds: "backtrace #{arg}".rstrip) + execute_debug_command(pre_cmds: "backtrace #{arg}") end end end diff --git a/lib/irb/command/break.rb b/lib/irb/command/break.rb index 42ee002ce..a8f81fe66 100644 --- a/lib/irb/command/break.rb +++ b/lib/irb/command/break.rb @@ -8,7 +8,7 @@ module IRB module Command class Break < DebugCommand def execute(arg) - execute_debug_command(pre_cmds: "break #{arg}".rstrip) + execute_debug_command(pre_cmds: "break #{arg}") end end end diff --git a/lib/irb/command/catch.rb b/lib/irb/command/catch.rb index 655c77d8a..529dcbca5 100644 --- a/lib/irb/command/catch.rb +++ b/lib/irb/command/catch.rb @@ -8,7 +8,7 @@ module IRB module Command class Catch < DebugCommand def execute(arg) - execute_debug_command(pre_cmds: "catch #{arg}".rstrip) + execute_debug_command(pre_cmds: "catch #{arg}") end end end diff --git a/lib/irb/command/continue.rb b/lib/irb/command/continue.rb index 49e4384eb..0daa029b1 100644 --- a/lib/irb/command/continue.rb +++ b/lib/irb/command/continue.rb @@ -8,7 +8,7 @@ module IRB module Command class Continue < DebugCommand def execute(arg) - execute_debug_command(do_cmds: "continue #{arg}".rstrip) + execute_debug_command(do_cmds: "continue #{arg}") end end end diff --git a/lib/irb/command/debug.rb b/lib/irb/command/debug.rb index aeafe19b5..f9aca0a67 100644 --- a/lib/irb/command/debug.rb +++ b/lib/irb/command/debug.rb @@ -18,6 +18,9 @@ def execute(_arg) 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 diff --git a/lib/irb/command/delete.rb b/lib/irb/command/delete.rb index 4b45a51e7..2a57a4a3d 100644 --- a/lib/irb/command/delete.rb +++ b/lib/irb/command/delete.rb @@ -8,7 +8,7 @@ module IRB module Command class Delete < DebugCommand def execute(arg) - execute_debug_command(pre_cmds: "delete #{arg}".rstrip) + execute_debug_command(pre_cmds: "delete #{arg}") end end end diff --git a/lib/irb/command/finish.rb b/lib/irb/command/finish.rb index c1d62357f..3311a0e6e 100644 --- a/lib/irb/command/finish.rb +++ b/lib/irb/command/finish.rb @@ -8,7 +8,7 @@ module IRB module Command class Finish < DebugCommand def execute(arg) - execute_debug_command(do_cmds: "finish #{arg}".rstrip) + execute_debug_command(do_cmds: "finish #{arg}") end end end diff --git a/lib/irb/command/info.rb b/lib/irb/command/info.rb index 897ee2c43..d08ce00a3 100644 --- a/lib/irb/command/info.rb +++ b/lib/irb/command/info.rb @@ -8,7 +8,7 @@ module IRB module Command class Info < DebugCommand def execute(arg) - execute_debug_command(pre_cmds: "info #{arg}".rstrip) + execute_debug_command(pre_cmds: "info #{arg}") end end end diff --git a/lib/irb/command/next.rb b/lib/irb/command/next.rb index 92d28e33e..3fc6b68d2 100644 --- a/lib/irb/command/next.rb +++ b/lib/irb/command/next.rb @@ -8,7 +8,7 @@ module IRB module Command class Next < DebugCommand def execute(arg) - execute_debug_command(do_cmds: "next #{arg}".rstrip) + execute_debug_command(do_cmds: "next #{arg}") end end end diff --git a/lib/irb/command/step.rb b/lib/irb/command/step.rb index 514981302..29e5e35ac 100644 --- a/lib/irb/command/step.rb +++ b/lib/irb/command/step.rb @@ -8,7 +8,7 @@ module IRB module Command class Step < DebugCommand def execute(arg) - execute_debug_command(do_cmds: "step #{arg}".rstrip) + execute_debug_command(do_cmds: "step #{arg}") end end end From eb442c4ddab50a519f9f7707e53da3e5a442cc4c Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 11 Apr 2024 07:16:27 +0800 Subject: [PATCH 118/263] Add a workaround to make IRB work with debug's tests (#919) --- lib/irb.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/irb.rb b/lib/irb.rb index edc4fc5d5..99fd1c5df 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1035,6 +1035,12 @@ def eval_input @context.evaluate(statement.code, line_no) when Statement::Command ret = statement.command_class.execute(@context, statement.arg) + # TODO: Remove this output once we have a better way to handle it + # This is to notify `debug`'s test framework that the current input has been processed + # We also need to have a way to restart/stop threads around command execution + # when being used as `debug`'s console. + # https://github.com/ruby/debug/blob/master/lib/debug/irb_integration.rb#L8-L13 + puts "INTERNAL_INFO: {}" if @context.with_debugger && ENV['RUBY_DEBUG_TEST_UI'] == 'terminal' @context.set_last_value(ret) end From b32aee406873f3aa16b6c2a354c1d2e81786c60e Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 12 Apr 2024 20:00:58 +0800 Subject: [PATCH 119/263] Pass statements to Context#evaluate (#920) This has a few benefits: - We can keep hiding the evaluation logic inside the Context level, which has always been the convention until #824 was merged recently. - Although not an official API, gems like `debug` and `mission_control-jobs` patch `Context#evaluate` to wrap their own logic around it. This implicit contract was broken after #824, and this change restores it. In addition to the refactor, I also converted some context-level evaluation tests into integration tests, which are more robust and easier to maintain. --- lib/irb.rb | 16 +------------ lib/irb/context.rb | 25 +++++++++++++++----- test/irb/test_context.rb | 41 +++++--------------------------- test/irb/test_irb.rb | 50 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 56 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 99fd1c5df..723035f15 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1028,21 +1028,7 @@ def eval_input return statement.code end - case statement - when Statement::EmptyInput - # Do nothing - when Statement::Expression - @context.evaluate(statement.code, line_no) - when Statement::Command - ret = statement.command_class.execute(@context, statement.arg) - # TODO: Remove this output once we have a better way to handle it - # This is to notify `debug`'s test framework that the current input has been processed - # We also need to have a way to restart/stop threads around command execution - # when being used as `debug`'s console. - # https://github.com/ruby/debug/blob/master/lib/debug/irb_integration.rb#L8-L13 - puts "INTERNAL_INFO: {}" if @context.with_debugger && ENV['RUBY_DEBUG_TEST_UI'] == 'terminal' - @context.set_last_value(ret) - end + @context.evaluate(statement, line_no) if @context.echo? && !statement.suppresses_echo? if statement.is_assignment? diff --git a/lib/irb/context.rb b/lib/irb/context.rb index e3c419245..836b8d262 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -585,31 +585,44 @@ def inspect_mode=(opt) @inspect_mode end - def evaluate(line, line_no) # :nodoc: + def evaluate(statement, line_no) # :nodoc: @line_no = line_no result = nil + case statement + when Statement::EmptyInput + return + when Statement::Expression + result = evaluate_expression(statement.code, line_no) + when Statement::Command + result = statement.command_class.execute(self, statement.arg) + end + + set_last_value(result) + end + + def evaluate_expression(code, line_no) # :nodoc: + result = nil if IRB.conf[:MEASURE] && IRB.conf[:MEASURE_CALLBACKS].empty? IRB.set_measure_callback end if IRB.conf[:MEASURE] && !IRB.conf[:MEASURE_CALLBACKS].empty? last_proc = proc do - result = workspace.evaluate(line, @eval_path, line_no) + result = workspace.evaluate(code, @eval_path, line_no) end IRB.conf[:MEASURE_CALLBACKS].inject(last_proc) do |chain, item| _name, callback, arg = item proc do - callback.(self, line, line_no, arg) do + callback.(self, code, line_no, arg) do chain.call end end end.call else - result = workspace.evaluate(line, @eval_path, line_no) + result = workspace.evaluate(code, @eval_path, line_no) end - - set_last_value(result) + result end def inspect_last_value # :nodoc: diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index 5812ea041..aff4b5b67 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -28,35 +28,6 @@ def teardown restore_encodings end - def test_last_value - assert_nil(@context.last_value) - assert_nil(@context.evaluate('_', 1)) - obj = Object.new - @context.set_last_value(obj) - assert_same(obj, @context.last_value) - assert_same(obj, @context.evaluate('_', 1)) - end - - def test_evaluate_with_encoding_error_without_lineno - if RUBY_ENGINE == 'truffleruby' - omit "Remove me after https://github.com/ruby/prism/issues/2129 is addressed and adopted in TruffleRuby" - end - - if RUBY_VERSION >= "3.4." - omit "Now raises SyntaxError" - end - - assert_raise_with_message(EncodingError, /invalid symbol/) { - @context.evaluate(%q[:"\xAE"], 1) - # The backtrace of this invalid encoding hash doesn't contain lineno. - } - end - - def test_evaluate_still_emits_warning - assert_warning("(irb):1: warning: END in method; use at_exit\n") do - @context.evaluate(%q[def foo; END {}; end], 1) - end - end def test_eval_input verbose, $VERBOSE = $VERBOSE, nil @@ -382,7 +353,7 @@ def test_omit_multiline_on_assignment end assert_empty err assert_equal("=> \n#{value}\n", out) - irb.context.evaluate('A.remove_method(:inspect)', 0) + irb.context.evaluate_expression('A.remove_method(:inspect)', 0) input.reset irb.context.echo = true @@ -392,7 +363,7 @@ def test_omit_multiline_on_assignment end assert_empty err assert_equal("=> #{value_first_line[0..(input.winsize.last - 9)]}...\n=> \n#{value}\n", out) - irb.context.evaluate('A.remove_method(:inspect)', 0) + irb.context.evaluate_expression('A.remove_method(:inspect)', 0) input.reset irb.context.echo = true @@ -402,7 +373,7 @@ def test_omit_multiline_on_assignment end assert_empty err assert_equal("=> \n#{value}\n=> \n#{value}\n", out) - irb.context.evaluate('A.remove_method(:inspect)', 0) + irb.context.evaluate_expression('A.remove_method(:inspect)', 0) input.reset irb.context.echo = false @@ -412,7 +383,7 @@ def test_omit_multiline_on_assignment end assert_empty err assert_equal("", out) - irb.context.evaluate('A.remove_method(:inspect)', 0) + irb.context.evaluate_expression('A.remove_method(:inspect)', 0) input.reset irb.context.echo = false @@ -422,7 +393,7 @@ def test_omit_multiline_on_assignment end assert_empty err assert_equal("", out) - irb.context.evaluate('A.remove_method(:inspect)', 0) + irb.context.evaluate_expression('A.remove_method(:inspect)', 0) input.reset irb.context.echo = false @@ -432,7 +403,7 @@ def test_omit_multiline_on_assignment end assert_empty err assert_equal("", out) - irb.context.evaluate('A.remove_method(:inspect)', 0) + irb.context.evaluate_expression('A.remove_method(:inspect)', 0) end end diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index 966c84013..84b9ee364 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -42,6 +42,56 @@ class Foo assert_include output, "From: #{@ruby_file.path}:1" end + def test_underscore_stores_last_result + write_ruby <<~'RUBY' + binding.irb + RUBY + + output = run_ruby_file do + type "1 + 1" + type "_ + 10" + type "exit!" + end + + assert_include output, "=> 12" + end + + def test_evaluate_with_encoding_error_without_lineno + if RUBY_ENGINE == 'truffleruby' + omit "Remove me after https://github.com/ruby/prism/issues/2129 is addressed and adopted in TruffleRuby" + end + + if RUBY_VERSION >= "3.4." + omit "Now raises SyntaxError" + end + + write_ruby <<~'RUBY' + binding.irb + RUBY + + output = run_ruby_file do + type %q[:"\xAE"] + type "exit!" + end + + assert_include output, 'invalid symbol in encoding UTF-8 :"\xAE"' + # EncodingError would be wrapped with ANSI escape sequences, so we assert it separately + assert_include output, "EncodingError" + end + + def test_evaluate_still_emits_warning + write_ruby <<~'RUBY' + binding.irb + RUBY + + output = run_ruby_file do + type %q[def foo; END {}; end] + type "exit!" + end + + assert_include output, '(irb):1: warning: END in method; use at_exit' + end + def test_symbol_aliases_dont_affect_ruby_syntax write_ruby <<~'RUBY' $foo = "It's a foo" From 888643467ccf4f62a579579de447ae74ee2f6cf3 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 14 Apr 2024 19:01:38 +0800 Subject: [PATCH 120/263] Allow defining custom commands in IRB (#886) This is a feature that has been requested for a long time. It is now possible to define custom commands in IRB. Example usage: ```ruby require "irb/command" class HelloCommand < IRB::Command::Base description "Prints hello world" category "My commands" help_message "It doesn't do more than printing hello world." def execute puts "Hello world" end end IRB::Command.register(:hello, HelloCommand) ``` --- lib/irb.rb | 2 +- lib/irb/command.rb | 262 ++---------------------------------- lib/irb/command/edit.rb | 1 + lib/irb/default_commands.rb | 248 ++++++++++++++++++++++++++++++++++ test/irb/test_command.rb | 34 +---- 5 files changed, 268 insertions(+), 279 deletions(-) create mode 100644 lib/irb/default_commands.rb diff --git a/lib/irb.rb b/lib/irb.rb index 723035f15..ab50c797c 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -10,7 +10,7 @@ require_relative "irb/init" require_relative "irb/context" -require_relative "irb/command" +require_relative "irb/default_commands" require_relative "irb/ruby-lex" require_relative "irb/statement" diff --git a/lib/irb/command.rb b/lib/irb/command.rb index 43cbda36b..19fde5635 100644 --- a/lib/irb/command.rb +++ b/lib/irb/command.rb @@ -7,261 +7,23 @@ require_relative "command/base" module IRB # :nodoc: - module Command; end - ExtendCommand = Command + module Command + @commands = {} - # Installs the default irb extensions command bundle. - module ExtendCommandBundle - # See ExtendCommandBundle.execute_as_command?. - NO_OVERRIDE = 0 - OVERRIDE_PRIVATE_ONLY = 0x01 - OVERRIDE_ALL = 0x02 + class << self + attr_reader :commands - @EXTEND_COMMANDS = [ - [ - :irb_context, :Context, "command/context", - [:context, NO_OVERRIDE], - [:conf, NO_OVERRIDE], - ], - [ - :irb_exit, :Exit, "command/exit", - [:exit, OVERRIDE_PRIVATE_ONLY], - [:quit, OVERRIDE_PRIVATE_ONLY], - [:irb_quit, OVERRIDE_PRIVATE_ONLY], - ], - [ - :irb_exit!, :ForceExit, "command/force_exit", - [:exit!, OVERRIDE_PRIVATE_ONLY], - ], - - [ - :irb_current_working_workspace, :CurrentWorkingWorkspace, "command/chws", - [:cwws, NO_OVERRIDE], - [:pwws, NO_OVERRIDE], - [:irb_print_working_workspace, OVERRIDE_ALL], - [:irb_cwws, OVERRIDE_ALL], - [:irb_pwws, OVERRIDE_ALL], - [:irb_current_working_binding, OVERRIDE_ALL], - [:irb_print_working_binding, OVERRIDE_ALL], - [:irb_cwb, OVERRIDE_ALL], - [:irb_pwb, OVERRIDE_ALL], - ], - [ - :irb_change_workspace, :ChangeWorkspace, "command/chws", - [:chws, NO_OVERRIDE], - [:cws, NO_OVERRIDE], - [:irb_chws, OVERRIDE_ALL], - [:irb_cws, OVERRIDE_ALL], - [:irb_change_binding, OVERRIDE_ALL], - [:irb_cb, OVERRIDE_ALL], - [:cb, NO_OVERRIDE], - ], - - [ - :irb_workspaces, :Workspaces, "command/pushws", - [:workspaces, NO_OVERRIDE], - [:irb_bindings, OVERRIDE_ALL], - [:bindings, NO_OVERRIDE], - ], - [ - :irb_push_workspace, :PushWorkspace, "command/pushws", - [:pushws, NO_OVERRIDE], - [:irb_pushws, OVERRIDE_ALL], - [:irb_push_binding, OVERRIDE_ALL], - [:irb_pushb, OVERRIDE_ALL], - [:pushb, NO_OVERRIDE], - ], - [ - :irb_pop_workspace, :PopWorkspace, "command/pushws", - [:popws, NO_OVERRIDE], - [:irb_popws, OVERRIDE_ALL], - [:irb_pop_binding, OVERRIDE_ALL], - [:irb_popb, OVERRIDE_ALL], - [:popb, NO_OVERRIDE], - ], - - [ - :irb_load, :Load, "command/load"], - [ - :irb_require, :Require, "command/load"], - [ - :irb_source, :Source, "command/load", - [:source, NO_OVERRIDE], - ], - - [ - :irb, :IrbCommand, "command/subirb"], - [ - :irb_jobs, :Jobs, "command/subirb", - [:jobs, NO_OVERRIDE], - ], - [ - :irb_fg, :Foreground, "command/subirb", - [:fg, NO_OVERRIDE], - ], - [ - :irb_kill, :Kill, "command/subirb", - [:kill, OVERRIDE_PRIVATE_ONLY], - ], - - [ - :irb_debug, :Debug, "command/debug", - [:debug, NO_OVERRIDE], - ], - [ - :irb_edit, :Edit, "command/edit", - [:edit, NO_OVERRIDE], - ], - [ - :irb_break, :Break, "command/break", - ], - [ - :irb_catch, :Catch, "command/catch", - ], - [ - :irb_next, :Next, "command/next" - ], - [ - :irb_delete, :Delete, "command/delete", - [:delete, NO_OVERRIDE], - ], - [ - :irb_step, :Step, "command/step", - [:step, NO_OVERRIDE], - ], - [ - :irb_continue, :Continue, "command/continue", - [:continue, NO_OVERRIDE], - ], - [ - :irb_finish, :Finish, "command/finish", - [:finish, NO_OVERRIDE], - ], - [ - :irb_backtrace, :Backtrace, "command/backtrace", - [:backtrace, NO_OVERRIDE], - [:bt, NO_OVERRIDE], - ], - [ - :irb_debug_info, :Info, "command/info", - [:info, NO_OVERRIDE], - ], - - [ - :irb_help, :Help, "command/help", - [:help, NO_OVERRIDE], - [:show_cmds, NO_OVERRIDE], - ], - - [ - :irb_show_doc, :ShowDoc, "command/show_doc", - [:show_doc, NO_OVERRIDE], - ], - - [ - :irb_info, :IrbInfo, "command/irb_info" - ], - - [ - :irb_ls, :Ls, "command/ls", - [:ls, NO_OVERRIDE], - ], - - [ - :irb_measure, :Measure, "command/measure", - [:measure, NO_OVERRIDE], - ], - - [ - :irb_show_source, :ShowSource, "command/show_source", - [:show_source, NO_OVERRIDE], - ], - [ - :irb_whereami, :Whereami, "command/whereami", - [:whereami, NO_OVERRIDE], - ], - [ - :irb_history, :History, "command/history", - [:history, NO_OVERRIDE], - [:hist, NO_OVERRIDE], - ], - - [ - :irb_disable_irb, :DisableIrb, "command/disable_irb", - [:disable_irb, NO_OVERRIDE], - ], - ] - - def self.command_override_policies - @@command_override_policies ||= @EXTEND_COMMANDS.flat_map do |cmd_name, cmd_class, load_file, *aliases| - [[cmd_name, OVERRIDE_ALL]] + aliases - end.to_h - end - - def self.execute_as_command?(name, public_method:, private_method:) - case command_override_policies[name] - when OVERRIDE_ALL - true - when OVERRIDE_PRIVATE_ONLY - !public_method - when NO_OVERRIDE - !public_method && !private_method - end - end - - def self.command_names - command_override_policies.keys.map(&:to_s) - end - - @@commands = [] - - def self.all_commands_info - return @@commands unless @@commands.empty? - user_aliases = IRB.CurrentContext.command_aliases.each_with_object({}) do |(alias_name, target), result| - result[target] ||= [] - result[target] << alias_name - end - - @EXTEND_COMMANDS.each do |cmd_name, cmd_class, load_file, *aliases| - if !defined?(Command) || !Command.const_defined?(cmd_class, false) - require_relative load_file - end - - klass = Command.const_get(cmd_class, false) - aliases = aliases.map { |a| a.first } - - if additional_aliases = user_aliases[cmd_name] - aliases += additional_aliases - end - - display_name = aliases.shift || cmd_name - @@commands << { display_name: display_name, description: klass.description, category: klass.category } + # Registers a command with the given name. + # Aliasing is intentionally not supported at the moment. + def register(name, command_class) + @commands[name] = [command_class, []] end - @@commands - end - - # Convert a command name to its implementation class if such command exists - def self.load_command(command) - command = command.to_sym - @EXTEND_COMMANDS.each do |cmd_name, cmd_class, load_file, *aliases| - next if cmd_name != command && aliases.all? { |alias_name, _| alias_name != command } - - if !defined?(Command) || !Command.const_defined?(cmd_class, false) - require_relative load_file - end - return Command.const_get(cmd_class, false) + # This API is for IRB's internal use only and may change at any time. + # Please do NOT use it. + def _register_with_aliases(name, command_class, *aliases) + @commands[name] = [command_class, aliases] end - nil - end - - def self.def_extend_command(cmd_name, cmd_class, load_file, *aliases) - @EXTEND_COMMANDS.delete_if { |name,| name == cmd_name } - @EXTEND_COMMANDS << [cmd_name, cmd_class, load_file, *aliases] - - # Just clear memoized values - @@commands = [] - @@command_override_policies = nil end end end diff --git a/lib/irb/command/edit.rb b/lib/irb/command/edit.rb index 480100bfc..3c4a54e5e 100644 --- a/lib/irb/command/edit.rb +++ b/lib/irb/command/edit.rb @@ -1,5 +1,6 @@ require 'shellwords' +require_relative "../color" require_relative "../source_finder" module IRB diff --git a/lib/irb/default_commands.rb b/lib/irb/default_commands.rb new file mode 100644 index 000000000..6025b0547 --- /dev/null +++ b/lib/irb/default_commands.rb @@ -0,0 +1,248 @@ +# frozen_string_literal: true + +require_relative "command" +require_relative "command/context" +require_relative "command/exit" +require_relative "command/force_exit" +require_relative "command/chws" +require_relative "command/pushws" +require_relative "command/subirb" +require_relative "command/load" +require_relative "command/debug" +require_relative "command/edit" +require_relative "command/break" +require_relative "command/catch" +require_relative "command/next" +require_relative "command/delete" +require_relative "command/step" +require_relative "command/continue" +require_relative "command/finish" +require_relative "command/backtrace" +require_relative "command/info" +require_relative "command/help" +require_relative "command/show_doc" +require_relative "command/irb_info" +require_relative "command/ls" +require_relative "command/measure" +require_relative "command/show_source" +require_relative "command/whereami" +require_relative "command/history" + +module IRB + ExtendCommand = Command + + # Installs the default irb extensions command bundle. + module ExtendCommandBundle + # See #install_alias_method. + NO_OVERRIDE = 0 + # See #install_alias_method. + OVERRIDE_PRIVATE_ONLY = 0x01 + # See #install_alias_method. + OVERRIDE_ALL = 0x02 + + Command._register_with_aliases(:irb_context, Command::Context, + [ + [:context, NO_OVERRIDE], + [:conf, NO_OVERRIDE], + ], + ) + + Command._register_with_aliases(:irb_exit, Command::Exit, + [:exit, OVERRIDE_PRIVATE_ONLY], + [:quit, OVERRIDE_PRIVATE_ONLY], + [:irb_quit, OVERRIDE_PRIVATE_ONLY] + ) + + Command._register_with_aliases(:irb_exit!, Command::ForceExit, + [:exit!, OVERRIDE_PRIVATE_ONLY] + ) + + Command._register_with_aliases(:irb_current_working_workspace, Command::CurrentWorkingWorkspace, + [:cwws, NO_OVERRIDE], + [:pwws, NO_OVERRIDE], + [:irb_print_working_workspace, OVERRIDE_ALL], + [:irb_cwws, OVERRIDE_ALL], + [:irb_pwws, OVERRIDE_ALL], + [:irb_current_working_binding, OVERRIDE_ALL], + [:irb_print_working_binding, OVERRIDE_ALL], + [:irb_cwb, OVERRIDE_ALL], + [:irb_pwb, OVERRIDE_ALL], + ) + + Command._register_with_aliases(:irb_change_workspace, Command::ChangeWorkspace, + [:chws, NO_OVERRIDE], + [:cws, NO_OVERRIDE], + [:irb_chws, OVERRIDE_ALL], + [:irb_cws, OVERRIDE_ALL], + [:irb_change_binding, OVERRIDE_ALL], + [:irb_cb, OVERRIDE_ALL], + [:cb, NO_OVERRIDE], + ) + + Command._register_with_aliases(:irb_workspaces, Command::Workspaces, + [:workspaces, NO_OVERRIDE], + [:irb_bindings, OVERRIDE_ALL], + [:bindings, NO_OVERRIDE], + ) + + Command._register_with_aliases(:irb_push_workspace, Command::PushWorkspace, + [:pushws, NO_OVERRIDE], + [:irb_pushws, OVERRIDE_ALL], + [:irb_push_binding, OVERRIDE_ALL], + [:irb_pushb, OVERRIDE_ALL], + [:pushb, NO_OVERRIDE], + ) + + Command._register_with_aliases(:irb_pop_workspace, Command::PopWorkspace, + [:popws, NO_OVERRIDE], + [:irb_popws, OVERRIDE_ALL], + [:irb_pop_binding, OVERRIDE_ALL], + [:irb_popb, OVERRIDE_ALL], + [:popb, NO_OVERRIDE], + ) + + Command._register_with_aliases(:irb_load, Command::Load) + Command._register_with_aliases(:irb_require, Command::Require) + Command._register_with_aliases(:irb_source, Command::Source, + [:source, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb, Command::IrbCommand) + Command._register_with_aliases(:irb_jobs, Command::Jobs, + [:jobs, NO_OVERRIDE] + ) + Command._register_with_aliases(:irb_fg, Command::Foreground, + [:fg, NO_OVERRIDE] + ) + Command._register_with_aliases(:irb_kill, Command::Kill, + [:kill, OVERRIDE_PRIVATE_ONLY] + ) + + Command._register_with_aliases(:irb_debug, Command::Debug, + [:debug, NO_OVERRIDE] + ) + Command._register_with_aliases(:irb_edit, Command::Edit, + [:edit, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_break, Command::Break) + Command._register_with_aliases(:irb_catch, Command::Catch) + Command._register_with_aliases(:irb_next, Command::Next) + Command._register_with_aliases(:irb_delete, Command::Delete, + [:delete, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_step, Command::Step, + [:step, NO_OVERRIDE] + ) + Command._register_with_aliases(:irb_continue, Command::Continue, + [:continue, NO_OVERRIDE] + ) + Command._register_with_aliases(:irb_finish, Command::Finish, + [:finish, NO_OVERRIDE] + ) + Command._register_with_aliases(:irb_backtrace, Command::Backtrace, + [:backtrace, NO_OVERRIDE], + [:bt, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_debug_info, Command::Info, + [:info, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_help, Command::Help, + [:help, NO_OVERRIDE], + [:show_cmds, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_show_doc, Command::ShowDoc, + [:show_doc, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_info, Command::IrbInfo) + + Command._register_with_aliases(:irb_ls, Command::Ls, + [:ls, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_measure, Command::Measure, + [:measure, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_show_source, Command::ShowSource, + [:show_source, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_whereami, Command::Whereami, + [:whereami, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_history, Command::History, + [:history, NO_OVERRIDE], + [:hist, NO_OVERRIDE] + ) + + def self.all_commands_info + user_aliases = IRB.CurrentContext.command_aliases.each_with_object({}) do |(alias_name, target), result| + result[target] ||= [] + result[target] << alias_name + end + + Command.commands.map do |command_name, (command_class, aliases)| + aliases = aliases.map { |a| a.first } + + if additional_aliases = user_aliases[command_name] + aliases += additional_aliases + end + + display_name = aliases.shift || command_name + { + display_name: display_name, + description: command_class.description, + category: command_class.category + } + end + end + + def self.command_override_policies + @@command_override_policies ||= Command.commands.flat_map do |cmd_name, (cmd_class, aliases)| + [[cmd_name, OVERRIDE_ALL]] + aliases + end.to_h + end + + def self.execute_as_command?(name, public_method:, private_method:) + case command_override_policies[name] + when OVERRIDE_ALL + true + when OVERRIDE_PRIVATE_ONLY + !public_method + when NO_OVERRIDE + !public_method && !private_method + end + end + + def self.command_names + command_override_policies.keys.map(&:to_s) + end + + # Convert a command name to its implementation class if such command exists + def self.load_command(command) + command = command.to_sym + Command.commands.each do |command_name, (command_class, aliases)| + if command_name == command || aliases.any? { |alias_name, _| alias_name == command } + return command_class + end + end + nil + end + + # Deprecated. Doesn't have any effect. + @EXTEND_COMMANDS = [] + + # Drepcated. Use Command.regiser instead. + def self.def_extend_command(cmd_name, cmd_class, _, *aliases) + Command._register_with_aliases(cmd_name, cmd_class, *aliases) + @@command_override_policies = nil + end + end +end diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index ca90ec92f..03fdd3785 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -212,20 +212,13 @@ def test_irb_info_lang class CustomCommandTestCase < CommandTestCase def setup - super - execute_lines("help\n") # To ensure command initialization is done - @EXTEND_COMMANDS_backup = IRB::ExtendCommandBundle.instance_variable_get(:@EXTEND_COMMANDS).dup - @cvars_backup = IRB::ExtendCommandBundle.class_variables.to_h do |cvar| - [cvar, IRB::ExtendCommandBundle.class_variable_get(cvar)] - end + @commands_backup = IRB::Command.commands + IRB::ExtendCommandBundle.class_variable_set(:@@command_override_policies, nil) end def teardown - super - IRB::ExtendCommandBundle.instance_variable_set(:@EXTEND_COMMANDS, @EXTEND_COMMANDS_backup) - @cvars_backup.each do |cvar, value| - IRB::ExtendCommandBundle.class_variable_set(cvar, value) - end + IRB::ExtendCommandBundle.class_variable_set(:@@command_override_policies, nil) + IRB::Command.instance_variable_set(:@commands, @commands_backup) end end @@ -239,8 +232,7 @@ def execute(arg) end def test_arg - IRB::Command.const_set :PrintArgCommand, PrintArgCommand - IRB::ExtendCommandBundle.def_extend_command(:print_arg, :PrintArgCommand, nil, [:pa, IRB::ExtendCommandBundle::OVERRIDE_ALL]) + IRB::Command._register_with_aliases(:print_arg, PrintArgCommand, [:pa, IRB::ExtendCommandBundle::OVERRIDE_ALL]) out, err = execute_lines("print_arg\n") assert_empty err assert_include(out, 'arg=""') @@ -260,8 +252,6 @@ def test_arg out, err = execute_lines("pa a r g \n") assert_empty err assert_include(out, 'arg="a r g"') - ensure - IRB::Command.send(:remove_const, :PrintArgCommand) end end @@ -274,20 +264,8 @@ def execute(_arg) end end - def setup - super - IRB::Command.const_set :FooBarCommand, FooBarCommand - end - - def teardown - super - IRB::Command.send(:remove_const, :FooBarCommand) - end - def test_def_extend_command - command = [:foobar, :FooBarCommand, nil, [:fbalias, IRB::ExtendCommandBundle::OVERRIDE_ALL]] - IRB::ExtendCommandBundle.instance_variable_get(:@EXTEND_COMMANDS).push(command) - IRB::ExtendCommandBundle.def_extend_command(*command) + IRB::Command._register_with_aliases(:foobar, FooBarCommand, [:fbalias, IRB::ExtendCommandBundle::OVERRIDE_ALL]) out, err = execute_lines("foobar\n") assert_empty err assert_include(out, "FooBar executed") From 7405a841e8824aac3a7807228bb131f92eaf42bc Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 18 Apr 2024 03:36:25 +0900 Subject: [PATCH 121/263] Remove internal-only methods from Command::Base (#922) * Remove internal-only methods from Command::Base Command#ruby_args and Command#unwrap_string_literal are used for default command's argument backward compatibility. Moved these methods to another module to avoid being used from custom commands. * Update lib/irb/command/edit.rb --------- Co-authored-by: Stan Lo --- lib/irb/command/base.rb | 19 ------------------- lib/irb/command/edit.rb | 2 ++ lib/irb/command/internal_helpers.rb | 27 +++++++++++++++++++++++++++ lib/irb/command/load.rb | 1 + lib/irb/command/ls.rb | 2 ++ lib/irb/command/measure.rb | 2 ++ lib/irb/command/show_doc.rb | 2 ++ lib/irb/command/show_source.rb | 2 ++ lib/irb/command/subirb.rb | 2 ++ lib/irb/default_commands.rb | 1 + 10 files changed, 41 insertions(+), 19 deletions(-) create mode 100644 lib/irb/command/internal_helpers.rb diff --git a/lib/irb/command/base.rb b/lib/irb/command/base.rb index ff74b5fb3..b078b4823 100644 --- a/lib/irb/command/base.rb +++ b/lib/irb/command/base.rb @@ -50,25 +50,6 @@ def initialize(irb_context) attr_reader :irb_context - 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 - def execute(arg) #nop end diff --git a/lib/irb/command/edit.rb b/lib/irb/command/edit.rb index 3c4a54e5e..cb7e0c487 100644 --- a/lib/irb/command/edit.rb +++ b/lib/irb/command/edit.rb @@ -8,6 +8,8 @@ module IRB module Command class Edit < Base + include RubyArgsExtractor + category "Misc" description 'Open a file or source location.' help_message <<~HELP_MESSAGE diff --git a/lib/irb/command/internal_helpers.rb b/lib/irb/command/internal_helpers.rb new file mode 100644 index 000000000..249b5cded --- /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/load.rb b/lib/irb/command/load.rb index 33e327f4a..1cd3f279d 100644 --- a/lib/irb/command/load.rb +++ b/lib/irb/command/load.rb @@ -10,6 +10,7 @@ module IRB module Command class LoaderCommand < Base + include RubyArgsExtractor include IrbLoader def raise_cmd_argument_error diff --git a/lib/irb/command/ls.rb b/lib/irb/command/ls.rb index f6b196486..cbd9998bc 100644 --- a/lib/irb/command/ls.rb +++ b/lib/irb/command/ls.rb @@ -11,6 +11,8 @@ module IRB module Command class Ls < Base + include RubyArgsExtractor + category "Context" description "Show methods, constants, and variables." diff --git a/lib/irb/command/measure.rb b/lib/irb/command/measure.rb index 70dc69cde..f96be20de 100644 --- a/lib/irb/command/measure.rb +++ b/lib/irb/command/measure.rb @@ -3,6 +3,8 @@ module IRB module Command class Measure < Base + include RubyArgsExtractor + category "Misc" description "`measure` enables the mode to measure processing time. `measure :off` disables it." diff --git a/lib/irb/command/show_doc.rb b/lib/irb/command/show_doc.rb index f9393cd3b..8a2188e4e 100644 --- a/lib/irb/command/show_doc.rb +++ b/lib/irb/command/show_doc.rb @@ -3,6 +3,8 @@ module IRB module Command class ShowDoc < Base + include RubyArgsExtractor + category "Context" description "Look up documentation with RI." diff --git a/lib/irb/command/show_source.rb b/lib/irb/command/show_source.rb index c4c8fc004..f4c6f104a 100644 --- a/lib/irb/command/show_source.rb +++ b/lib/irb/command/show_source.rb @@ -7,6 +7,8 @@ module IRB module Command class ShowSource < Base + include RubyArgsExtractor + category "Context" description "Show the source code of a given method, class/module, or constant." diff --git a/lib/irb/command/subirb.rb b/lib/irb/command/subirb.rb index 24428a5c1..138d61c93 100644 --- a/lib/irb/command/subirb.rb +++ b/lib/irb/command/subirb.rb @@ -9,6 +9,8 @@ module IRB module Command class MultiIRBCommand < Base + include RubyArgsExtractor + private def print_deprecated_warning diff --git a/lib/irb/default_commands.rb b/lib/irb/default_commands.rb index 6025b0547..d680655fe 100644 --- a/lib/irb/default_commands.rb +++ b/lib/irb/default_commands.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative "command" +require_relative "command/internal_helpers" require_relative "command/context" require_relative "command/exit" require_relative "command/force_exit" From e8ea8f253d42447e38aad36f690d41a6d69e6d15 Mon Sep 17 00:00:00 2001 From: Lorenzo Zabot Date: Thu, 18 Apr 2024 12:33:19 +0200 Subject: [PATCH 122/263] Prompt specifiers documentation improvements (#926) --- lib/irb.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index ab50c797c..f7019ab0e 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -656,8 +656,10 @@ # * `%m`: the value of `self.to_s`. # * `%M`: the value of `self.inspect`. # * `%l`: an indication of the type of string; one of `"`, `'`, `/`, `]`. -# * `*NN*i`: Indentation level. -# * `*NN*n`: Line number. +# * `%NNi`: Indentation level. NN is a 2-digit number that specifies the number +# of digits of the indentation level (03 will result in 001). +# * `%NNn`: Line number. NN is a 2-digit number that specifies the number +# of digits of the line number (03 will result in 001). # * `%%`: Literal `%`. # # From c8182fa490d305d4643b18029470d13b87e8d211 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 18 Apr 2024 20:48:10 +0900 Subject: [PATCH 123/263] Accept " " for colorizing "\t" test (#924) --- test/irb/test_color.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/irb/test_color.rb b/test/irb/test_color.rb index 72e036eab..9d78f5233 100644 --- a/test/irb/test_color.rb +++ b/test/irb/test_color.rb @@ -99,7 +99,7 @@ def test_colorize_code "foo %i[bar]" => "foo #{YELLOW}%i[#{CLEAR}#{YELLOW}bar#{CLEAR}#{YELLOW}]#{CLEAR}", "foo :@bar, baz, :@@qux, :$quux" => "foo #{YELLOW}:#{CLEAR}#{YELLOW}@bar#{CLEAR}, baz, #{YELLOW}:#{CLEAR}#{YELLOW}@@qux#{CLEAR}, #{YELLOW}:#{CLEAR}#{YELLOW}$quux#{CLEAR}", "`echo`" => "#{RED}#{BOLD}`#{CLEAR}#{RED}echo#{CLEAR}#{RED}#{BOLD}`#{CLEAR}", - "\t" => "\t", # not ^I + "\t" => Reline::Unicode.escape_for_print("\t") == ' ' ? ' ' : "\t", # not ^I "foo(*%W(bar))" => "foo(*#{RED}#{BOLD}%W(#{CLEAR}#{RED}bar#{CLEAR}#{RED}#{BOLD})#{CLEAR})", "$stdout" => "#{GREEN}#{BOLD}$stdout#{CLEAR}", "__END__" => "#{GREEN}__END__#{CLEAR}", From e3f5875553e3175a4f6e228d1a6728901f0b1adf Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 18 Apr 2024 20:07:31 +0800 Subject: [PATCH 124/263] Add CI job to test IRB against the latest debug (#928) --- .github/workflows/test.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 37ea47a91..810c27590 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,6 +48,29 @@ jobs: run: bundle exec rake test - name: Run tests in isolation run: bundle exec rake test_in_isolation + debug-test: + name: Debug compatibility test + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.3 + - name: Install dependencies + run: bundle install + - name: Install IRB + run: | + rake install + - name: Download ruby/debug + run: | + git clone https://github.com/ruby/debug + - name: Run debug tests + working-directory: ./debug + run: | + bundle install + bundle exec rake vterm-yamatanooroti: needs: ruby-versions name: >- From 08eee25d28365e4fb2e11432c0535932f586cca3 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 18 Apr 2024 23:46:36 +0900 Subject: [PATCH 125/263] Fix % escape in prompt format (#927) --- lib/irb.rb | 4 ++-- test/irb/test_context.rb | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index f7019ab0e..d6afd6e51 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1457,7 +1457,7 @@ def truncate_prompt_main(str) # :nodoc: end def format_prompt(format, ltype, indent, line_no) # :nodoc: - format.gsub(/%([0-9]+)?([a-zA-Z])/) do + format.gsub(/%([0-9]+)?([a-zA-Z%])/) do case $2 when "N" @context.irb_name @@ -1490,7 +1490,7 @@ def format_prompt(format, ltype, indent, line_no) # :nodoc: line_no.to_s end when "%" - "%" + "%" unless $1 end end end diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index aff4b5b67..cd3f2c8f6 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -662,6 +662,14 @@ def main.inspect; raise ArgumentError; end assert_equal("irb(!ArgumentError)>", irb.send(:format_prompt, 'irb(%M)>', nil, 1, 1)) end + def test_prompt_format + main = 'main' + irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new) + assert_equal('%% main %m %main %%m >', irb.send(:format_prompt, '%%%% %m %%m %%%m %%%%m %l', '>', 1, 1)) + assert_equal('42,%i, 42,%3i,042,%03i', irb.send(:format_prompt, '%i,%%i,%3i,%%3i,%03i,%%03i', nil, 42, 1)) + assert_equal('42,%n, 42,%3n,042,%03n', irb.send(:format_prompt, '%n,%%n,%3n,%%3n,%03n,%%03n', nil, 1, 42)) + end + def test_lineno input = TestInputMethod.new([ "\n", From c6bbc424c368da80bbff779395c55a63eb366461 Mon Sep 17 00:00:00 2001 From: Kuniaki Igarashi Date: Sat, 20 Apr 2024 00:01:32 +0900 Subject: [PATCH 126/263] Add MultiIRB commands test (#929) --- test/irb/command/test_multi_irb_commands.rb | 50 +++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 test/irb/command/test_multi_irb_commands.rb diff --git a/test/irb/command/test_multi_irb_commands.rb b/test/irb/command/test_multi_irb_commands.rb new file mode 100644 index 000000000..e313c0c5d --- /dev/null +++ b/test/irb/command/test_multi_irb_commands.rb @@ -0,0 +1,50 @@ +require "tempfile" +require_relative "../helper" + +module TestIRB + class MultiIRBTest < IntegrationTestCase + def setup + super + + write_ruby <<~'RUBY' + binding.irb + RUBY + end + + def test_jobs_command_with_print_deprecated_warning + out = run_ruby_file do + type "jobs" + type "exit" + end + + assert_match(/Multi-irb commands are deprecated and will be removed in IRB 2\.0\.0\. Please use workspace commands instead\./, out) + assert_match(%r|If you have any use case for multi-irb, please leave a comment at https://github.com/ruby/irb/issues/653|, out) + assert_match(/#0->irb on main \(#: running\)/, out) + end + + def test_irb_jobs_and_kill_commands + out = run_ruby_file do + type "irb" + type "jobs" + type "kill 1" + type "exit" + end + + assert_match(/#0->irb on main \(#: stop\)/, out) + assert_match(/#1->irb#1 on main \(#: running\)/, out) + end + + def test_irb_fg_jobs_and_kill_commands + out = run_ruby_file do + type "irb" + type "fg 0" + type "jobs" + type "kill 1" + type "exit" + end + + assert_match(/#0->irb on main \(#: running\)/, out) + assert_match(/#1->irb#1 on main \(#: stop\)/, out) + end + end +end From 0b5dd6afd0380d2cbf15bc24b6e8c8adf3dcd05e Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 20 Apr 2024 16:45:38 +0900 Subject: [PATCH 127/263] Remove exit command workaround, handle IRB_EXIT in debug_readline (#923) * Remove exit and exti! command workaround when executed outside of IRB Command was a method. It could be executed outside of IRB. Workaround for it is no longer needed. * Handle IRB_EXIT in debug mode * Add exit and exit! command in rdbg mode --- lib/irb.rb | 26 +++++++++++++++---------- lib/irb/command/exit.rb | 4 +--- lib/irb/command/force_exit.rb | 4 +--- test/irb/command/test_force_exit.rb | 12 ------------ test/irb/test_debugger_integration.rb | 28 ++++++++++++++++++++++----- 5 files changed, 41 insertions(+), 33 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index d6afd6e51..faf82f32d 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -962,20 +962,26 @@ def debug_readline(binding) # # Irb#eval_input will simply return the input, and we need to pass it to the # debugger. - input = if IRB.conf[:SAVE_HISTORY] && context.io.support_history_saving? - # Previous IRB session's history has been saved when `Irb#run` is exited We need - # to make sure the saved history is not saved again by resetting the counter - context.io.reset_history_counter + input = nil + forced_exit = catch(:IRB_EXIT) do + if IRB.conf[:SAVE_HISTORY] && context.io.support_history_saving? + # Previous IRB session's history has been saved when `Irb#run` is exited We need + # to make sure the saved history is not saved again by resetting the counter + context.io.reset_history_counter - begin - eval_input - ensure - context.io.save_history + begin + input = eval_input + ensure + context.io.save_history + end + else + input = eval_input end - else - eval_input + false end + Kernel.exit if forced_exit + if input&.include?("\n") @line_no += input.count("\n") - 1 end diff --git a/lib/irb/command/exit.rb b/lib/irb/command/exit.rb index 3109ec16e..b4436f034 100644 --- a/lib/irb/command/exit.rb +++ b/lib/irb/command/exit.rb @@ -8,10 +8,8 @@ class Exit < Base category "IRB" description "Exit the current irb session." - def execute(*) + def execute(_arg) IRB.irb_exit - rescue UncaughtThrowError - Kernel.exit end end end diff --git a/lib/irb/command/force_exit.rb b/lib/irb/command/force_exit.rb index c2c5542e2..14086aa84 100644 --- a/lib/irb/command/force_exit.rb +++ b/lib/irb/command/force_exit.rb @@ -8,10 +8,8 @@ class ForceExit < Base category "IRB" description "Exit the current process." - def execute(*) + def execute(_arg) throw :IRB_EXIT, true - rescue UncaughtThrowError - Kernel.exit! end end end diff --git a/test/irb/command/test_force_exit.rb b/test/irb/command/test_force_exit.rb index 9e86c644d..191a78687 100644 --- a/test/irb/command/test_force_exit.rb +++ b/test/irb/command/test_force_exit.rb @@ -47,17 +47,5 @@ def foo assert_match(/irb\(main\):001> 123/, output) end - - def test_forced_exit_out_of_irb_session - write_ruby <<~'ruby' - at_exit { puts 'un' + 'reachable' } - binding.irb - exit! # this will call exit! method overrided by command - ruby - output = run_ruby_file do - type "exit" - end - assert_not_include(output, 'unreachable') - end end end diff --git a/test/irb/test_debugger_integration.rb b/test/irb/test_debugger_integration.rb index 839a0d43f..eca40c570 100644 --- a/test/irb/test_debugger_integration.rb +++ b/test/irb/test_debugger_integration.rb @@ -244,28 +244,46 @@ def test_catch def test_exit write_ruby <<~'RUBY' binding.irb - puts "hello" + puts "he" + "llo" RUBY output = run_ruby_file do - type "next" + type "debug" type "exit" end - assert_match(/irb\(main\):001> next/, output) + assert_match(/irb:rdbg\(main\):002>/, output) + assert_match(/hello/, output) + end + + def test_force_exit + write_ruby <<~'RUBY' + binding.irb + puts "he" + "llo" + RUBY + + output = run_ruby_file do + type "debug" + type "exit!" + end + + assert_match(/irb:rdbg\(main\):002>/, output) + assert_not_match(/hello/, output) end def test_quit write_ruby <<~'RUBY' binding.irb + puts "he" + "llo" RUBY output = run_ruby_file do - type "next" + type "debug" type "quit!" end - assert_match(/irb\(main\):001> next/, output) + assert_match(/irb:rdbg\(main\):002>/, output) + assert_not_match(/hello/, output) end def test_prompt_line_number_continues From f74ec97236369f8513c22e4fbbda2e9ba4f7b066 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 21 Apr 2024 02:55:51 +0800 Subject: [PATCH 128/263] Stop using ExtendCommandBundle internally (#925) This module was used to extend both commands and helpers when they're not separated. Now that they are, and we have a Command module, we should move command-related logic to the Command module and update related references. This will make the code easier to understand and refactor in the future. --- lib/irb.rb | 4 +- lib/irb/command.rb | 6 -- lib/irb/command/help.rb | 4 +- lib/irb/completion.rb | 2 +- lib/irb/default_commands.rb | 202 +++++++++++++++++++----------------- test/irb/test_command.rb | 8 +- 6 files changed, 117 insertions(+), 109 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index faf82f32d..168595d34 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1120,7 +1120,7 @@ def build_statement(code) code.force_encoding(@context.io.encoding) if (command, arg = parse_command(code)) - command_class = ExtendCommandBundle.load_command(command) + command_class = Command.load_command(command) Statement::Command.new(code, command_class, arg) else is_assignment_expression = @scanner.assignment_expression?(code, local_variables: @context.local_variables) @@ -1142,7 +1142,7 @@ def parse_command(code) # Check visibility public_method = !!Kernel.instance_method(:public_method).bind_call(@context.main, command) rescue false private_method = !public_method && !!Kernel.instance_method(:method).bind_call(@context.main, command) rescue false - if ExtendCommandBundle.execute_as_command?(command, public_method: public_method, private_method: private_method) + if Command.execute_as_command?(command, public_method: public_method, private_method: private_method) [command, arg] end end diff --git a/lib/irb/command.rb b/lib/irb/command.rb index 19fde5635..f4dd3b2b4 100644 --- a/lib/irb/command.rb +++ b/lib/irb/command.rb @@ -18,12 +18,6 @@ class << self def register(name, command_class) @commands[name] = [command_class, []] end - - # This API is for IRB's internal use only and may change at any time. - # Please do NOT use it. - def _register_with_aliases(name, command_class, *aliases) - @commands[name] = [command_class, aliases] - end end end end diff --git a/lib/irb/command/help.rb b/lib/irb/command/help.rb index c9f16e05b..fc44f6080 100644 --- a/lib/irb/command/help.rb +++ b/lib/irb/command/help.rb @@ -11,7 +11,7 @@ def execute(command_name) if command_name.empty? help_message else - if command_class = ExtendCommandBundle.load_command(command_name) + 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" @@ -23,7 +23,7 @@ def execute(command_name) private def help_message - commands_info = IRB::ExtendCommandBundle.all_commands_info + commands_info = IRB::Command.all_commands_info commands_grouped_by_categories = commands_info.group_by { |cmd| cmd[:category] } user_aliases = irb_context.instance_variable_get(:@user_aliases) diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index 8a1df1156..a3d89373c 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -88,7 +88,7 @@ def retrieve_files_to_require_from_load_path def command_completions(preposing, target) if preposing.empty? && !target.empty? - IRB::ExtendCommandBundle.command_names.select { _1.start_with?(target) } + IRB::Command.command_names.select { _1.start_with?(target) } else [] end diff --git a/lib/irb/default_commands.rb b/lib/irb/default_commands.rb index d680655fe..72884318a 100644 --- a/lib/irb/default_commands.rb +++ b/lib/irb/default_commands.rb @@ -30,35 +30,91 @@ require_relative "command/history" module IRB - ExtendCommand = Command - - # Installs the default irb extensions command bundle. - module ExtendCommandBundle - # See #install_alias_method. + module Command NO_OVERRIDE = 0 - # See #install_alias_method. OVERRIDE_PRIVATE_ONLY = 0x01 - # See #install_alias_method. OVERRIDE_ALL = 0x02 - Command._register_with_aliases(:irb_context, Command::Context, + class << self + # This API is for IRB's internal use only and may change at any time. + # Please do NOT use it. + def _register_with_aliases(name, command_class, *aliases) + @commands[name] = [command_class, aliases] + end + + def all_commands_info + user_aliases = IRB.CurrentContext.command_aliases.each_with_object({}) do |(alias_name, target), result| + result[target] ||= [] + result[target] << alias_name + end + + commands.map do |command_name, (command_class, aliases)| + aliases = aliases.map { |a| a.first } + + if additional_aliases = user_aliases[command_name] + aliases += additional_aliases + end + + display_name = aliases.shift || command_name + { + display_name: display_name, + description: command_class.description, + category: command_class.category + } + end + end + + def command_override_policies + @@command_override_policies ||= commands.flat_map do |cmd_name, (cmd_class, aliases)| + [[cmd_name, OVERRIDE_ALL]] + aliases + end.to_h + end + + def execute_as_command?(name, public_method:, private_method:) + case command_override_policies[name] + when OVERRIDE_ALL + true + when OVERRIDE_PRIVATE_ONLY + !public_method + when NO_OVERRIDE + !public_method && !private_method + end + end + + def command_names + command_override_policies.keys.map(&:to_s) + end + + # Convert a command name to its implementation class if such command exists + def load_command(command) + command = command.to_sym + commands.each do |command_name, (command_class, aliases)| + if command_name == command || aliases.any? { |alias_name, _| alias_name == command } + return command_class + end + end + nil + end + end + + _register_with_aliases(:irb_context, Command::Context, [ [:context, NO_OVERRIDE], [:conf, NO_OVERRIDE], ], ) - Command._register_with_aliases(:irb_exit, Command::Exit, + _register_with_aliases(:irb_exit, Command::Exit, [:exit, OVERRIDE_PRIVATE_ONLY], [:quit, OVERRIDE_PRIVATE_ONLY], [:irb_quit, OVERRIDE_PRIVATE_ONLY] ) - Command._register_with_aliases(:irb_exit!, Command::ForceExit, + _register_with_aliases(:irb_exit!, Command::ForceExit, [:exit!, OVERRIDE_PRIVATE_ONLY] ) - Command._register_with_aliases(:irb_current_working_workspace, Command::CurrentWorkingWorkspace, + _register_with_aliases(:irb_current_working_workspace, Command::CurrentWorkingWorkspace, [:cwws, NO_OVERRIDE], [:pwws, NO_OVERRIDE], [:irb_print_working_workspace, OVERRIDE_ALL], @@ -70,7 +126,7 @@ module ExtendCommandBundle [:irb_pwb, OVERRIDE_ALL], ) - Command._register_with_aliases(:irb_change_workspace, Command::ChangeWorkspace, + _register_with_aliases(:irb_change_workspace, Command::ChangeWorkspace, [:chws, NO_OVERRIDE], [:cws, NO_OVERRIDE], [:irb_chws, OVERRIDE_ALL], @@ -80,13 +136,13 @@ module ExtendCommandBundle [:cb, NO_OVERRIDE], ) - Command._register_with_aliases(:irb_workspaces, Command::Workspaces, + _register_with_aliases(:irb_workspaces, Command::Workspaces, [:workspaces, NO_OVERRIDE], [:irb_bindings, OVERRIDE_ALL], [:bindings, NO_OVERRIDE], ) - Command._register_with_aliases(:irb_push_workspace, Command::PushWorkspace, + _register_with_aliases(:irb_push_workspace, Command::PushWorkspace, [:pushws, NO_OVERRIDE], [:irb_pushws, OVERRIDE_ALL], [:irb_push_binding, OVERRIDE_ALL], @@ -94,7 +150,7 @@ module ExtendCommandBundle [:pushb, NO_OVERRIDE], ) - Command._register_with_aliases(:irb_pop_workspace, Command::PopWorkspace, + _register_with_aliases(:irb_pop_workspace, Command::PopWorkspace, [:popws, NO_OVERRIDE], [:irb_popws, OVERRIDE_ALL], [:irb_pop_binding, OVERRIDE_ALL], @@ -102,140 +158,98 @@ module ExtendCommandBundle [:popb, NO_OVERRIDE], ) - Command._register_with_aliases(:irb_load, Command::Load) - Command._register_with_aliases(:irb_require, Command::Require) - Command._register_with_aliases(:irb_source, Command::Source, + _register_with_aliases(:irb_load, Command::Load) + _register_with_aliases(:irb_require, Command::Require) + _register_with_aliases(:irb_source, Command::Source, [:source, NO_OVERRIDE] ) - Command._register_with_aliases(:irb, Command::IrbCommand) - Command._register_with_aliases(:irb_jobs, Command::Jobs, + _register_with_aliases(:irb, Command::IrbCommand) + _register_with_aliases(:irb_jobs, Command::Jobs, [:jobs, NO_OVERRIDE] ) - Command._register_with_aliases(:irb_fg, Command::Foreground, + _register_with_aliases(:irb_fg, Command::Foreground, [:fg, NO_OVERRIDE] ) - Command._register_with_aliases(:irb_kill, Command::Kill, + _register_with_aliases(:irb_kill, Command::Kill, [:kill, OVERRIDE_PRIVATE_ONLY] ) - Command._register_with_aliases(:irb_debug, Command::Debug, + _register_with_aliases(:irb_debug, Command::Debug, [:debug, NO_OVERRIDE] ) - Command._register_with_aliases(:irb_edit, Command::Edit, + _register_with_aliases(:irb_edit, Command::Edit, [:edit, NO_OVERRIDE] ) - Command._register_with_aliases(:irb_break, Command::Break) - Command._register_with_aliases(:irb_catch, Command::Catch) - Command._register_with_aliases(:irb_next, Command::Next) - Command._register_with_aliases(:irb_delete, Command::Delete, + _register_with_aliases(:irb_break, Command::Break) + _register_with_aliases(:irb_catch, Command::Catch) + _register_with_aliases(:irb_next, Command::Next) + _register_with_aliases(:irb_delete, Command::Delete, [:delete, NO_OVERRIDE] ) - Command._register_with_aliases(:irb_step, Command::Step, + _register_with_aliases(:irb_step, Command::Step, [:step, NO_OVERRIDE] ) - Command._register_with_aliases(:irb_continue, Command::Continue, + _register_with_aliases(:irb_continue, Command::Continue, [:continue, NO_OVERRIDE] ) - Command._register_with_aliases(:irb_finish, Command::Finish, + _register_with_aliases(:irb_finish, Command::Finish, [:finish, NO_OVERRIDE] ) - Command._register_with_aliases(:irb_backtrace, Command::Backtrace, + _register_with_aliases(:irb_backtrace, Command::Backtrace, [:backtrace, NO_OVERRIDE], [:bt, NO_OVERRIDE] ) - Command._register_with_aliases(:irb_debug_info, Command::Info, + _register_with_aliases(:irb_debug_info, Command::Info, [:info, NO_OVERRIDE] ) - Command._register_with_aliases(:irb_help, Command::Help, + _register_with_aliases(:irb_help, Command::Help, [:help, NO_OVERRIDE], [:show_cmds, NO_OVERRIDE] ) - Command._register_with_aliases(:irb_show_doc, Command::ShowDoc, + _register_with_aliases(:irb_show_doc, Command::ShowDoc, [:show_doc, NO_OVERRIDE] ) - Command._register_with_aliases(:irb_info, Command::IrbInfo) + _register_with_aliases(:irb_info, Command::IrbInfo) - Command._register_with_aliases(:irb_ls, Command::Ls, + _register_with_aliases(:irb_ls, Command::Ls, [:ls, NO_OVERRIDE] ) - Command._register_with_aliases(:irb_measure, Command::Measure, + _register_with_aliases(:irb_measure, Command::Measure, [:measure, NO_OVERRIDE] ) - Command._register_with_aliases(:irb_show_source, Command::ShowSource, + _register_with_aliases(:irb_show_source, Command::ShowSource, [:show_source, NO_OVERRIDE] ) - Command._register_with_aliases(:irb_whereami, Command::Whereami, + _register_with_aliases(:irb_whereami, Command::Whereami, [:whereami, NO_OVERRIDE] ) - Command._register_with_aliases(:irb_history, Command::History, + _register_with_aliases(:irb_history, Command::History, [:history, NO_OVERRIDE], [:hist, NO_OVERRIDE] ) + end - def self.all_commands_info - user_aliases = IRB.CurrentContext.command_aliases.each_with_object({}) do |(alias_name, target), result| - result[target] ||= [] - result[target] << alias_name - end - - Command.commands.map do |command_name, (command_class, aliases)| - aliases = aliases.map { |a| a.first } - - if additional_aliases = user_aliases[command_name] - aliases += additional_aliases - end - - display_name = aliases.shift || command_name - { - display_name: display_name, - description: command_class.description, - category: command_class.category - } - end - end - - def self.command_override_policies - @@command_override_policies ||= Command.commands.flat_map do |cmd_name, (cmd_class, aliases)| - [[cmd_name, OVERRIDE_ALL]] + aliases - end.to_h - end - - def self.execute_as_command?(name, public_method:, private_method:) - case command_override_policies[name] - when OVERRIDE_ALL - true - when OVERRIDE_PRIVATE_ONLY - !public_method - when NO_OVERRIDE - !public_method && !private_method - end - end - - def self.command_names - command_override_policies.keys.map(&:to_s) - end + ExtendCommand = Command - # Convert a command name to its implementation class if such command exists - def self.load_command(command) - command = command.to_sym - Command.commands.each do |command_name, (command_class, aliases)| - if command_name == command || aliases.any? { |alias_name, _| alias_name == command } - return command_class - end - end - nil - end + # For backward compatibility, we need to keep this module: + # - As a container of helper methods + # - As a place to register commands with the deprecated def_extend_command method + module ExtendCommandBundle + # For backward compatibility + NO_OVERRIDE = Command::NO_OVERRIDE + OVERRIDE_PRIVATE_ONLY = Command::OVERRIDE_PRIVATE_ONLY + OVERRIDE_ALL = Command::OVERRIDE_ALL # Deprecated. Doesn't have any effect. @EXTEND_COMMANDS = [] @@ -243,7 +257,7 @@ def self.load_command(command) # Drepcated. Use Command.regiser instead. def self.def_extend_command(cmd_name, cmd_class, _, *aliases) Command._register_with_aliases(cmd_name, cmd_class, *aliases) - @@command_override_policies = nil + Command.class_variable_set(:@@command_override_policies, nil) end end end diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index 03fdd3785..8bf95c107 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -213,11 +213,11 @@ def test_irb_info_lang class CustomCommandTestCase < CommandTestCase def setup @commands_backup = IRB::Command.commands - IRB::ExtendCommandBundle.class_variable_set(:@@command_override_policies, nil) + IRB::Command.class_variable_set(:@@command_override_policies, nil) end def teardown - IRB::ExtendCommandBundle.class_variable_set(:@@command_override_policies, nil) + IRB::Command.class_variable_set(:@@command_override_policies, nil) IRB::Command.instance_variable_set(:@commands, @commands_backup) end end @@ -232,7 +232,7 @@ def execute(arg) end def test_arg - IRB::Command._register_with_aliases(:print_arg, PrintArgCommand, [:pa, IRB::ExtendCommandBundle::OVERRIDE_ALL]) + IRB::Command._register_with_aliases(:print_arg, PrintArgCommand, [:pa, IRB::Command::OVERRIDE_ALL]) out, err = execute_lines("print_arg\n") assert_empty err assert_include(out, 'arg=""') @@ -265,7 +265,7 @@ def execute(_arg) end def test_def_extend_command - IRB::Command._register_with_aliases(:foobar, FooBarCommand, [:fbalias, IRB::ExtendCommandBundle::OVERRIDE_ALL]) + IRB::ExtendCommandBundle.def_extend_command(:foobar, FooBarCommand, nil, [:fbalias, IRB::Command::OVERRIDE_ALL]) out, err = execute_lines("foobar\n") assert_empty err assert_include(out, "FooBar executed") From f9347b1d3575e284017aa2310c3a2a7ecf6fb4fd Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 23 Apr 2024 23:48:13 +0800 Subject: [PATCH 129/263] Support helper method registration (#624) --- lib/irb/command/help.rb | 2 + lib/irb/default_commands.rb | 5 +- lib/irb/helper_method.rb | 29 +++++++++ lib/irb/helper_method/base.rb | 12 ++++ lib/irb/helper_method/conf.rb | 11 ++++ lib/irb/workspace.rb | 20 +++++- test/irb/command/test_help.rb | 9 +++ test/irb/test_helper_method.rb | 109 +++++++++++++++++++++++++++++++++ 8 files changed, 190 insertions(+), 7 deletions(-) create mode 100644 lib/irb/helper_method.rb create mode 100644 lib/irb/helper_method/base.rb create mode 100644 lib/irb/helper_method/conf.rb create mode 100644 test/irb/test_helper_method.rb diff --git a/lib/irb/command/help.rb b/lib/irb/command/help.rb index fc44f6080..1ed7a7707 100644 --- a/lib/irb/command/help.rb +++ b/lib/irb/command/help.rb @@ -24,7 +24,9 @@ def execute(command_name) 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 user_aliases = irb_context.instance_variable_get(:@user_aliases) diff --git a/lib/irb/default_commands.rb b/lib/irb/default_commands.rb index 72884318a..2c515674a 100644 --- a/lib/irb/default_commands.rb +++ b/lib/irb/default_commands.rb @@ -98,10 +98,7 @@ def load_command(command) end _register_with_aliases(:irb_context, Command::Context, - [ - [:context, NO_OVERRIDE], - [:conf, NO_OVERRIDE], - ], + [:context, NO_OVERRIDE] ) _register_with_aliases(:irb_exit, Command::Exit, diff --git a/lib/irb/helper_method.rb b/lib/irb/helper_method.rb new file mode 100644 index 000000000..f1f6fff91 --- /dev/null +++ b/lib/irb/helper_method.rb @@ -0,0 +1,29 @@ +require_relative "helper_method/base" + +module IRB + module HelperMethod + @helper_methods = {} + + class << self + attr_reader :helper_methods + + def register(name, helper_class) + @helper_methods[name] = helper_class + + if defined?(HelpersContainer) + HelpersContainer.install_helper_methods + end + end + + def all_helper_methods_info + @helper_methods.map do |name, helper_class| + { display_name: name, description: helper_class.description } + end + end + end + + # Default helper_methods + require_relative "helper_method/conf" + register(:conf, HelperMethod::Conf) + end +end diff --git a/lib/irb/helper_method/base.rb b/lib/irb/helper_method/base.rb new file mode 100644 index 000000000..dc74b046d --- /dev/null +++ b/lib/irb/helper_method/base.rb @@ -0,0 +1,12 @@ +module IRB + module HelperMethod + class Base + class << self + def description(description = nil) + @description = description if description + @description + end + end + end + end +end diff --git a/lib/irb/helper_method/conf.rb b/lib/irb/helper_method/conf.rb new file mode 100644 index 000000000..460f5ab78 --- /dev/null +++ b/lib/irb/helper_method/conf.rb @@ -0,0 +1,11 @@ +module IRB + module HelperMethod + class Conf < Base + description "Returns the current context." + + def execute + IRB.CurrentContext + end + end + end +end diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb index 1490f7b47..dd92d9901 100644 --- a/lib/irb/workspace.rb +++ b/lib/irb/workspace.rb @@ -6,6 +6,8 @@ require "delegate" +require_relative "helper_method" + IRB::TOPLEVEL_BINDING = binding module IRB # :nodoc: class WorkSpace @@ -109,9 +111,9 @@ def initialize(*main) attr_reader :main def load_helper_methods_to_main - if !(class< \"irb\"" + end + end + end + + class HelperMethodIntegrationTest < IntegrationTestCase + def test_arguments_propogation + write_ruby <<~RUBY + require "irb/helper_method" + + class MyHelper < IRB::HelperMethod::Base + description "This is a test helper" + + def execute( + required_arg, optional_arg = nil, *splat_arg, required_keyword_arg:, + optional_keyword_arg: nil, **double_splat_arg, &block_arg + ) + puts [required_arg, optional_arg, splat_arg, required_keyword_arg, optional_keyword_arg, double_splat_arg, block_arg.call].to_s + end + end + + IRB::HelperMethod.register(:my_helper, MyHelper) + + binding.irb + RUBY + + output = run_ruby_file do + type <<~INPUT + my_helper( + "required", "optional", "splat", required_keyword_arg: "required", + optional_keyword_arg: "optional", a: 1, b: 2 + ) { "block" } + INPUT + type "exit" + end + + assert_include(output, '["required", "optional", ["splat"], "required", "optional", {:a=>1, :b=>2}, "block"]') + end + + def test_helper_method_injection_can_happen_after_irb_require + write_ruby <<~RUBY + require "irb" + + class MyHelper < IRB::HelperMethod::Base + description "This is a test helper" + + def execute + puts "Hello from MyHelper" + end + end + + IRB::HelperMethod.register(:my_helper, MyHelper) + + binding.irb + RUBY + + output = run_ruby_file do + type <<~INPUT + my_helper + INPUT + type "exit" + end + + assert_include(output, 'Hello from MyHelper') + end + end +end From 169a9a2c30978811e16800cb31073ebc8c27b6ce Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 24 Apr 2024 16:59:39 +0100 Subject: [PATCH 130/263] Memoize helper method instances with Singleton module Some helpers, like Rails console's `app`, requires memoization of the helper's ivars. To support it IRB needs to memoize helper method instances as well. --- lib/irb/helper_method/base.rb | 4 ++++ lib/irb/workspace.rb | 2 +- test/irb/test_helper_method.rb | 31 ++++++++++++++++++++++++++++--- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/lib/irb/helper_method/base.rb b/lib/irb/helper_method/base.rb index dc74b046d..a68001ed2 100644 --- a/lib/irb/helper_method/base.rb +++ b/lib/irb/helper_method/base.rb @@ -1,6 +1,10 @@ +require "singleton" + module IRB module HelperMethod class Base + include Singleton + class << self def description(description = nil) @description = description if description diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb index dd92d9901..d24d1cc38 100644 --- a/lib/irb/workspace.rb +++ b/lib/irb/workspace.rb @@ -179,7 +179,7 @@ module HelpersContainer def self.install_helper_methods HelperMethod.helper_methods.each do |name, helper_method_class| define_method name do |*args, **opts, &block| - helper_method_class.new.execute(*args, **opts, &block) + helper_method_class.instance.execute(*args, **opts, &block) end unless method_defined?(name) end end diff --git a/test/irb/test_helper_method.rb b/test/irb/test_helper_method.rb index 5174e5ceb..291278c16 100644 --- a/test/irb/test_helper_method.rb +++ b/test/irb/test_helper_method.rb @@ -97,13 +97,38 @@ def execute RUBY output = run_ruby_file do - type <<~INPUT - my_helper - INPUT + type "my_helper" type "exit" end assert_include(output, 'Hello from MyHelper') end + + def test_helper_method_instances_are_memoized + write_ruby <<~RUBY + require "irb/helper_method" + + class MyHelper < IRB::HelperMethod::Base + description "This is a test helper" + + def execute(val) + @val ||= val + end + end + + IRB::HelperMethod.register(:my_helper, MyHelper) + + binding.irb + RUBY + + output = run_ruby_file do + type "my_helper(100)" + type "my_helper(200)" + type "exit" + end + + assert_include(output, '=> 100') + assert_not_include(output, '=> 200') + end end end From 221b0a492821b95bca2e733205c0c1dd1d2e3d43 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 24 Apr 2024 17:01:14 +0100 Subject: [PATCH 131/263] Revert "Memoize helper method instances with Singleton module" This reverts commit 169a9a2c30978811e16800cb31073ebc8c27b6ce. --- lib/irb/helper_method/base.rb | 4 ---- lib/irb/workspace.rb | 2 +- test/irb/test_helper_method.rb | 31 +++---------------------------- 3 files changed, 4 insertions(+), 33 deletions(-) diff --git a/lib/irb/helper_method/base.rb b/lib/irb/helper_method/base.rb index a68001ed2..dc74b046d 100644 --- a/lib/irb/helper_method/base.rb +++ b/lib/irb/helper_method/base.rb @@ -1,10 +1,6 @@ -require "singleton" - module IRB module HelperMethod class Base - include Singleton - class << self def description(description = nil) @description = description if description diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb index d24d1cc38..dd92d9901 100644 --- a/lib/irb/workspace.rb +++ b/lib/irb/workspace.rb @@ -179,7 +179,7 @@ module HelpersContainer def self.install_helper_methods HelperMethod.helper_methods.each do |name, helper_method_class| define_method name do |*args, **opts, &block| - helper_method_class.instance.execute(*args, **opts, &block) + helper_method_class.new.execute(*args, **opts, &block) end unless method_defined?(name) end end diff --git a/test/irb/test_helper_method.rb b/test/irb/test_helper_method.rb index 291278c16..5174e5ceb 100644 --- a/test/irb/test_helper_method.rb +++ b/test/irb/test_helper_method.rb @@ -97,38 +97,13 @@ def execute RUBY output = run_ruby_file do - type "my_helper" + type <<~INPUT + my_helper + INPUT type "exit" end assert_include(output, 'Hello from MyHelper') end - - def test_helper_method_instances_are_memoized - write_ruby <<~RUBY - require "irb/helper_method" - - class MyHelper < IRB::HelperMethod::Base - description "This is a test helper" - - def execute(val) - @val ||= val - end - end - - IRB::HelperMethod.register(:my_helper, MyHelper) - - binding.irb - RUBY - - output = run_ruby_file do - type "my_helper(100)" - type "my_helper(200)" - type "exit" - end - - assert_include(output, '=> 100') - assert_not_include(output, '=> 200') - end end end From a96c7a6668b32eea5282e56e57e781541f8814e0 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 25 Apr 2024 02:32:51 +0800 Subject: [PATCH 132/263] Memoize helper method instances with Singleton module (#931) Some helpers, like Rails console's `app`, requires memoization of the helper's ivars. To support it IRB needs to memoize helper method instances as well. --- lib/irb/helper_method/base.rb | 4 ++++ lib/irb/workspace.rb | 2 +- test/irb/test_helper_method.rb | 31 ++++++++++++++++++++++++++++--- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/lib/irb/helper_method/base.rb b/lib/irb/helper_method/base.rb index dc74b046d..a68001ed2 100644 --- a/lib/irb/helper_method/base.rb +++ b/lib/irb/helper_method/base.rb @@ -1,6 +1,10 @@ +require "singleton" + module IRB module HelperMethod class Base + include Singleton + class << self def description(description = nil) @description = description if description diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb index dd92d9901..d24d1cc38 100644 --- a/lib/irb/workspace.rb +++ b/lib/irb/workspace.rb @@ -179,7 +179,7 @@ module HelpersContainer def self.install_helper_methods HelperMethod.helper_methods.each do |name, helper_method_class| define_method name do |*args, **opts, &block| - helper_method_class.new.execute(*args, **opts, &block) + helper_method_class.instance.execute(*args, **opts, &block) end unless method_defined?(name) end end diff --git a/test/irb/test_helper_method.rb b/test/irb/test_helper_method.rb index 5174e5ceb..291278c16 100644 --- a/test/irb/test_helper_method.rb +++ b/test/irb/test_helper_method.rb @@ -97,13 +97,38 @@ def execute RUBY output = run_ruby_file do - type <<~INPUT - my_helper - INPUT + type "my_helper" type "exit" end assert_include(output, 'Hello from MyHelper') end + + def test_helper_method_instances_are_memoized + write_ruby <<~RUBY + require "irb/helper_method" + + class MyHelper < IRB::HelperMethod::Base + description "This is a test helper" + + def execute(val) + @val ||= val + end + end + + IRB::HelperMethod.register(:my_helper, MyHelper) + + binding.irb + RUBY + + output = run_ruby_file do + type "my_helper(100)" + type "my_helper(200)" + type "exit" + end + + assert_include(output, '=> 100') + assert_not_include(output, '=> 200') + end end end From a91a212dbef044ed8285521af475b4af22dbb765 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 26 Apr 2024 20:12:27 +0800 Subject: [PATCH 133/263] Command registration should take both strings and symbols as names (#932) This will save users some heads scratching when they try to register a command with a string name and found that it doesn't work. I also rewrote converted custom command tests into integration tests to make test setup/cleanup easier. --- lib/irb/command.rb | 2 +- lib/irb/default_commands.rb | 2 +- test/irb/command/test_custom_command.rb | 127 ++++++++++++++++++++++++ test/irb/test_command.rb | 70 ------------- 4 files changed, 129 insertions(+), 72 deletions(-) create mode 100644 test/irb/command/test_custom_command.rb diff --git a/lib/irb/command.rb b/lib/irb/command.rb index f4dd3b2b4..68a4b5272 100644 --- a/lib/irb/command.rb +++ b/lib/irb/command.rb @@ -16,7 +16,7 @@ class << self # Registers a command with the given name. # Aliasing is intentionally not supported at the moment. def register(name, command_class) - @commands[name] = [command_class, []] + @commands[name.to_sym] = [command_class, []] end end end diff --git a/lib/irb/default_commands.rb b/lib/irb/default_commands.rb index 2c515674a..1bbc68efa 100644 --- a/lib/irb/default_commands.rb +++ b/lib/irb/default_commands.rb @@ -39,7 +39,7 @@ class << self # This API is for IRB's internal use only and may change at any time. # Please do NOT use it. def _register_with_aliases(name, command_class, *aliases) - @commands[name] = [command_class, aliases] + @commands[name.to_sym] = [command_class, aliases] end def all_commands_info diff --git a/test/irb/command/test_custom_command.rb b/test/irb/command/test_custom_command.rb new file mode 100644 index 000000000..6642d2b16 --- /dev/null +++ b/test/irb/command/test_custom_command.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true +require "irb" + +require_relative "../helper" + +module TestIRB + class CustomCommandIntegrationTest < TestIRB::IntegrationTestCase + def test_command_regsitration_can_happen_after_irb_require + write_ruby <<~RUBY + require "irb" + require "irb/command" + + class PrintCommand < IRB::Command::Base + category 'CommandTest' + description 'print_command' + def execute(*) + puts "Hello from PrintCommand" + nil + end + end + + IRB::Command.register(:print!, PrintCommand) + + binding.irb + RUBY + + output = run_ruby_file do + type "print!\n" + type "exit" + end + + assert_include(output, "Hello from PrintCommand") + end + + def test_command_regsitration_accepts_string_too + write_ruby <<~RUBY + require "irb/command" + + class PrintCommand < IRB::Command::Base + category 'CommandTest' + description 'print_command' + def execute(*) + puts "Hello from PrintCommand" + nil + end + end + + IRB::Command.register("print!", PrintCommand) + + binding.irb + RUBY + + output = run_ruby_file do + type "print!\n" + type "exit" + end + + assert_include(output, "Hello from PrintCommand") + end + + def test_arguments_propogation + write_ruby <<~RUBY + require "irb/command" + + class PrintArgCommand < IRB::Command::Base + category 'CommandTest' + description 'print_command_arg' + def execute(arg) + $nth_execution ||= 0 + puts "\#{$nth_execution} arg=\#{arg.inspect}" + $nth_execution += 1 + nil + end + end + + IRB::Command.register(:print_arg, PrintArgCommand) + + binding.irb + RUBY + + output = run_ruby_file do + type "print_arg\n" + type "print_arg \n" + type "print_arg a r g\n" + type "print_arg a r g \n" + type "exit" + end + + assert_include(output, "0 arg=\"\"") + assert_include(output, "1 arg=\"\"") + assert_include(output, "2 arg=\"a r g\"") + assert_include(output, "3 arg=\"a r g\"") + end + + def test_def_extend_command_still_works + write_ruby <<~RUBY + require "irb" + + class FooBarCommand < IRB::Command::Base + category 'FooBarCategory' + description 'foobar_description' + def execute(*) + $nth_execution ||= 1 + puts "\#{$nth_execution} FooBar executed" + $nth_execution += 1 + nil + end + end + + IRB::ExtendCommandBundle.def_extend_command(:foobar, FooBarCommand, nil, [:fbalias, IRB::Command::OVERRIDE_ALL]) + + binding.irb + RUBY + + output = run_ruby_file do + type "foobar" + type "fbalias" + type "help foobar" + type "exit" + end + + assert_include(output, "1 FooBar executed") + assert_include(output, "2 FooBar executed") + assert_include(output, "foobar_description") + end + end +end diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index 8bf95c107..76789216c 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -210,76 +210,6 @@ def test_irb_info_lang end end - class CustomCommandTestCase < CommandTestCase - def setup - @commands_backup = IRB::Command.commands - IRB::Command.class_variable_set(:@@command_override_policies, nil) - end - - def teardown - IRB::Command.class_variable_set(:@@command_override_policies, nil) - IRB::Command.instance_variable_set(:@commands, @commands_backup) - end - end - - class CommandArgTest < CustomCommandTestCase - class PrintArgCommand < IRB::Command::Base - category 'CommandTest' - description 'print_command_arg' - def execute(arg) - puts "arg=#{arg.inspect}" - end - end - - def test_arg - IRB::Command._register_with_aliases(:print_arg, PrintArgCommand, [:pa, IRB::Command::OVERRIDE_ALL]) - out, err = execute_lines("print_arg\n") - assert_empty err - assert_include(out, 'arg=""') - - out, err = execute_lines("print_arg \n") - assert_empty err - assert_include(out, 'arg=""') - - out, err = execute_lines("print_arg a r g\n") - assert_empty err - assert_include(out, 'arg="a r g"') - - out, err = execute_lines("print_arg a r g \n") - assert_empty err - assert_include(out, 'arg="a r g"') - - out, err = execute_lines("pa a r g \n") - assert_empty err - assert_include(out, 'arg="a r g"') - end - end - - class ExtendCommandBundleCompatibilityTest < CustomCommandTestCase - class FooBarCommand < IRB::Command::Base - category 'FooBarCategory' - description 'foobar_description' - def execute(_arg) - puts "FooBar executed" - end - end - - def test_def_extend_command - IRB::ExtendCommandBundle.def_extend_command(:foobar, FooBarCommand, nil, [:fbalias, IRB::Command::OVERRIDE_ALL]) - out, err = execute_lines("foobar\n") - assert_empty err - assert_include(out, "FooBar executed") - - out, err = execute_lines("fbalias\n") - assert_empty err - assert_include(out, "FooBar executed") - - out, err = execute_lines("show_cmds\n") - assert_include(out, "FooBarCategory") - assert_include(out, "foobar_description") - end - end - class MeasureTest < CommandTestCase def test_measure conf = { From fa96bea76f83ea22e33803f73c7e6a965f292c47 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 27 Apr 2024 01:52:08 +0800 Subject: [PATCH 134/263] Suppress command return values (#934) Since commands can't be chained with methods, their return values are not intended to be used. But if IRB keeps storing command return values as the last value, and print them, users may rely on such implicit behaviour. So to avoid such confusion, this commit suppresses command's return values. It also updates some commands that currently rely on this implicit behaviour. --- lib/irb/command/chws.rb | 5 +++-- lib/irb/command/subirb.rb | 5 ++++- lib/irb/context.rb | 7 ++++--- test/irb/test_command.rb | 14 ++++++-------- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/irb/command/chws.rb b/lib/irb/command/chws.rb index e0a406885..ef456d096 100644 --- a/lib/irb/command/chws.rb +++ b/lib/irb/command/chws.rb @@ -15,7 +15,7 @@ class CurrentWorkingWorkspace < Base description "Show the current workspace." def execute(_arg) - irb_context.main + puts "Current workspace: #{irb_context.main}" end end @@ -30,7 +30,8 @@ def execute(arg) obj = eval(arg, irb_context.workspace.binding) irb_context.change_workspace(obj) end - irb_context.main + + puts "Current workspace: #{irb_context.main}" end end end diff --git a/lib/irb/command/subirb.rb b/lib/irb/command/subirb.rb index 138d61c93..85af28c1a 100644 --- a/lib/irb/command/subirb.rb +++ b/lib/irb/command/subirb.rb @@ -49,6 +49,7 @@ def execute_internal(*obj) extend_irb_context IRB.irb(nil, *obj) + puts IRB.JobManager.inspect end end @@ -65,7 +66,7 @@ def execute(_arg) end extend_irb_context - IRB.JobManager + puts IRB.JobManager.inspect end end @@ -90,6 +91,7 @@ def execute_internal(key = nil) 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 @@ -112,6 +114,7 @@ def execute_internal(*keys) extend_irb_context IRB.JobManager.kill(*keys) + puts IRB.JobManager.inspect end end end diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 836b8d262..22e855f1e 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -587,18 +587,19 @@ def inspect_mode=(opt) def evaluate(statement, line_no) # :nodoc: @line_no = line_no - result = nil case statement when Statement::EmptyInput return when Statement::Expression result = evaluate_expression(statement.code, line_no) + set_last_value(result) when Statement::Command - result = statement.command_class.execute(self, statement.arg) + statement.command_class.execute(self, statement.arg) + set_last_value(nil) end - set_last_value(result) + nil end def evaluate_expression(code, line_no) # :nodoc: diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index 76789216c..8cb8928ad 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -485,12 +485,11 @@ class Foo; end class CwwsTest < WorkspaceCommandTestCase def test_cwws_returns_the_current_workspace_object out, err = execute_lines( - "cwws", - "self.class" + "cwws" ) assert_empty err - assert_include(out, self.class.name) + assert_include(out, "Current workspace: #{self}") end end @@ -556,7 +555,7 @@ def test_popws_replaces_the_current_workspace_with_the_previous_one "pushws Foo.new\n", "popws\n", "cwws\n", - "_.class", + "self.class", ) assert_empty err assert_include(out, "=> #{self.class}") @@ -576,20 +575,19 @@ def test_chws_replaces_the_current_workspace out, err = execute_lines( "chws #{self.class}::Foo.new\n", "cwws\n", - "_.class", + "self.class\n" ) assert_empty err + assert_include(out, "Current workspace: #<#{self.class.name}::Foo") assert_include(out, "=> #{self.class}::Foo") end def test_chws_does_nothing_when_receiving_no_argument out, err = execute_lines( "chws\n", - "cwws\n", - "_.class", ) assert_empty err - assert_include(out, "=> #{self.class}") + assert_include(out, "Current workspace: #{self}") end end From d167ea014c1476c98867ac738d263656db6b88d2 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 27 Apr 2024 01:52:28 +0800 Subject: [PATCH 135/263] Add EXTEND_IRB.md to introduce IRB's command and helper registration mechanism (#933) --- EXTEND_IRB.md | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 12 +----- 2 files changed, 113 insertions(+), 10 deletions(-) create mode 100644 EXTEND_IRB.md diff --git a/EXTEND_IRB.md b/EXTEND_IRB.md new file mode 100644 index 000000000..7b63c83f2 --- /dev/null +++ b/EXTEND_IRB.md @@ -0,0 +1,111 @@ +# Extend IRB + +From v1.13.0, IRB provides official APIs to extend its functionality. This feature allows libraries to +customize and enhance their users' IRB sessions by adding new commands and helper methods tailored for +the libraries. + +## Commands + +Commands are designed to complete certain tasks or display information for the user, similar to shell commands. +Therefore, they are designed to accept a variety of inputs, including those that are not valid Ruby code, such +as `my_cmd Foo#bar` or `my_cmd --flag foo`. + +### Example + +```rb +require "irb/command" + +class Greet < IRB::Command::Base + category "Greeting" + description "Greets the user" + help_message <<~HELP + Greets the user with the given name. + + Usage: greet + HELP + + # Any input after the command name will be passed as a single string. + # If nothing is added after the command, an empty string will be passed. + def execute(arg) + puts "Hello! #{arg}" + end +end + +IRB::Command.register("greet", Greet) +``` + +As long as the above code is loaded before the IRB session is started, such as in a loaded library or a user's `.irbrc` file, `greet` will be accessible to the user. + +```txt +irb(main):001> greet +Hello! +=> nil +irb(main):002> greet Stan +Hello! Stan +=> nil +``` + +And because the `Greet` command introduces a new category, `Greeting`, a new help message category will be created: + +```txt +Help + help List all available commands. Use `help ` to get information about a specific command. + +Greeting + greet Greets the user + +IRB + context Displays current configuration. + ... +``` + +If the optional `help_message` attribute is specified, `help greet` will also display it: + +```txt +irb(main):001> help greet +Greets the user with the given name. + +Usage: greet +``` + +## Helper methods + +Helper methods are designed to be used as Ruby methods, such as `my_helper(arg, kwarg: val).foo`. + +The main use case of helper methods is to provide shortcuts for users, providing quick and easy access to +frequently used operations or components within the IRB session. For example, a helper method might simplify +the process of fetching and displaying specific configuration settings or data structures that would otherwise +require multiple steps to access. + +### Example + +```rb +# This only loads the minimum components required to define and register a helper method. +# It does not load the entire IRB, nor does it initialize it. +require "irb/helper_method" + +class MyHelper < IRB::HelperMethod::Base + description "This is a test helper" + + def execute(arg, kwarg:) + "arg: #{arg}, kwarg: #{kwarg}" + end +end + +IRB::HelperMethod.register(:my_helper, MyHelper) +``` + +As long as the above code is loaded before the IRB session is started, such as in a loaded library or a user's `.irbrc` file, `my_helper` will be accessible to the user. + +```txt +irb(main):001> my_helper("foo", kwarg: "bar").upcase +=> "ARG: FOO, KWARG: BAR" +``` + +The registered helper methods will also be listed in the help message's `Helper methods` section: + +```txt +Helper methods + conf Returns the current context. + my_helper This is a test helper +``` diff --git a/README.md b/README.md index cb6e225b4..591680b80 100644 --- a/README.md +++ b/README.md @@ -355,17 +355,9 @@ https://docs.ruby-lang.org/en/master/IRB.html ## Extending IRB -IRB is currently going through some refactoring to bring in some cool improvements and make things more flexible for developers. -We know that in the past, due to a lack of public APIs and documentation, many of you have had to use IRB's private APIs -and components to extend it. We also know that changes can be a bit annoying and might mess with your current setup. +IRB `v1.13.0` and later versions allows users/libraries to extend its functionality through official APIs. -We're sorry if this causes a bit of a scramble. We're working hard to make IRB better and your input is super important to us. -If you've been using private APIs or components in your projects, we'd love to hear about your use cases. Please feel free to file a new issue. Your feedback will be a massive help in guiding us on how to design and prioritize the development of official APIs in the future. - -Right now, we've got command extension APIs on the drawing board, as you can see in [#513](https://github.com/ruby/irb/issues/513). -We've also got a prototype for helper method extension APIs in the works, as shown in [#588](https://github.com/ruby/irb/issues/588). - -We really appreciate your understanding and patience during this transition. We're pretty excited about the improvements these changes will bring to the IRB ecosystem and we hope you are too! +For more information, please visit [EXTEND_IRB.md](./EXTEND_IRB.md). ## Development From a5a233763691b938bc709991ce9269a80e8df226 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 27 Apr 2024 05:10:31 +0800 Subject: [PATCH 136/263] Pass symbol to Command.register in doc for consistency (#935) --- EXTEND_IRB.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXTEND_IRB.md b/EXTEND_IRB.md index 7b63c83f2..e5bc4a155 100644 --- a/EXTEND_IRB.md +++ b/EXTEND_IRB.md @@ -31,7 +31,7 @@ class Greet < IRB::Command::Base end end -IRB::Command.register("greet", Greet) +IRB::Command.register(:greet, Greet) ``` As long as the above code is loaded before the IRB session is started, such as in a loaded library or a user's `.irbrc` file, `greet` will be accessible to the user. From c41f460a70ea86eee0b436ef1c3d46518ebd9c11 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 30 Apr 2024 19:29:34 +0900 Subject: [PATCH 137/263] Restore MAIN_CONTEXT correctly (#937) --- lib/irb.rb | 4 ++++ test/irb/test_irb.rb | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/lib/irb.rb b/lib/irb.rb index 168595d34..5cb91a293 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -992,6 +992,7 @@ def debug_readline(binding) def run(conf = IRB.conf) in_nested_session = !!conf[:MAIN_CONTEXT] conf[:IRB_RC].call(context) if conf[:IRB_RC] + prev_context = conf[:MAIN_CONTEXT] conf[:MAIN_CONTEXT] = context save_history = !in_nested_session && conf[:SAVE_HISTORY] && context.io.support_history_saving? @@ -1014,6 +1015,9 @@ def run(conf = IRB.conf) eval_input end ensure + # Do not restore to nil. It will cause IRB crash when used with threads. + IRB.conf[:MAIN_CONTEXT] = prev_context if prev_context + RubyVM.keep_script_lines = keep_script_lines_backup if defined?(RubyVM.keep_script_lines) trap("SIGINT", prev_trap) conf[:AT_EXIT].each{|hook| hook.call} diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index 84b9ee364..b05fc80f7 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -125,6 +125,26 @@ def test_empty_input_echoing_behaviour end end + class NestedBindingIrbTest < IntegrationTestCase + def test_current_context_restore + write_ruby <<~'RUBY' + binding.irb + RUBY + + output = run_ruby_file do + type '$ctx = IRB.CurrentContext' + type 'binding.irb' + type 'p context_changed: IRB.CurrentContext != $ctx' + type 'exit' + type 'p context_restored: IRB.CurrentContext == $ctx' + type 'exit' + end + + assert_include output, '{:context_changed=>true}' + assert_include output, '{:context_restored=>true}' + end + end + class IrbIOConfigurationTest < TestCase Row = Struct.new(:content, :current_line_spaces, :new_line_spaces, :indent_level) From 0cab3f7982ba879d69e4f0e03cdd3f6544da7fed Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 1 May 2024 01:11:00 +0800 Subject: [PATCH 138/263] Add a new GH workflow for deploying to GH pages (#938) --- .github/workflows/gh-pages.yml | 47 ++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/gh-pages.yml diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml new file mode 100644 index 000000000..62c3d87c1 --- /dev/null +++ b/.github/workflows/gh-pages.yml @@ -0,0 +1,47 @@ +name: Deploy IRB documentation to GitHub Pages + +on: + push: + branches: ["master"] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + if: ${{ github.repository == 'ruby/irb' && !startsWith(github.event_name, 'pull') }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Ruby + uses: ruby/setup-ruby@1198b074305f9356bd56dd4b311757cc0dab2f1c # v1.175.1 + with: + ruby-version: "3.3" + bundler-cache: true + - name: Setup Pages + id: pages + uses: actions/configure-pages@v5 + - name: Build with IRB + # Outputs to the './_site' directory by default + run: bundle exec rake rdoc + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 From 6ae87669f7a9b1bfc966e37d920822d2d335c73c Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 30 Apr 2024 18:19:29 +0100 Subject: [PATCH 139/263] Update gh-pages workflow to match output dir name --- .github/workflows/gh-pages.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 62c3d87c1..005c23d9b 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -30,10 +30,11 @@ jobs: id: pages uses: actions/configure-pages@v5 - name: Build with IRB - # Outputs to the './_site' directory by default run: bundle exec rake rdoc - name: Upload artifact uses: actions/upload-pages-artifact@v3 + with: + path: "docs/" deploy: environment: From 07d13a301521635969aa6d7e0466ee7689ef747a Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 30 Apr 2024 18:25:40 +0100 Subject: [PATCH 140/263] Use README.md as documentation home --- Rakefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Rakefile b/Rakefile index 7a51839f7..87c700aba 100644 --- a/Rakefile +++ b/Rakefile @@ -49,5 +49,6 @@ RDoc::Task.new do |rdoc| rdoc.rdoc_files.include("*.md", "lib/**/*.rb") rdoc.rdoc_files.exclude("lib/irb/xmp.rb") rdoc.rdoc_dir = "docs" + rdoc.main = "README.md" rdoc.options.push("--copy-files", "LICENSE.txt") end From 6f6e87d76992168dd9d8e70f5bef47f0302f665a Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 1 May 2024 22:23:05 +0800 Subject: [PATCH 141/263] Support `IRB.conf[:BACKTRACE_FILTER]` (#917) * Use 'irbtest-' instead if 'irb-' as prefix of test files. Otherwise IRB would mis-recognize exceptions raised in test files as exceptions raised in IRB itself. * Support `IRB.conf[:BACKTRACE_FILTER]`` This config allows users to customize the backtrace of exceptions raised and displayed in IRB sessions. This is useful for filtering out library frames from the backtrace. IRB expects the given value to response to `call` method and return the filtered backtrace. --- lib/irb.rb | 40 ++++++++++--------- test/irb/helper.rb | 2 +- test/irb/test_irb.rb | 91 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 18 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 5cb91a293..45a59087b 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1242,27 +1242,33 @@ def handle_exception(exc) irb_bug = true else irb_bug = false - # This is mostly to make IRB work nicely with Rails console's backtrace filtering, which patches WorkSpace#filter_backtrace - # In such use case, we want to filter the exception's backtrace before its displayed through Exception#full_message - # And we clone the exception object in order to avoid mutating the original exception - # TODO: introduce better API to expose exception backtrace externally - backtrace = exc.backtrace.map { |l| @context.workspace.filter_backtrace(l) }.compact + # To support backtrace filtering while utilizing Exception#full_message, we need to clone + # the exception to avoid modifying the original exception's backtrace. exc = exc.clone - exc.set_backtrace(backtrace) - end + filtered_backtrace = exc.backtrace.map { |l| @context.workspace.filter_backtrace(l) }.compact + backtrace_filter = IRB.conf[:BACKTRACE_FILTER] - if RUBY_VERSION < '3.0.0' - if STDOUT.tty? - message = exc.full_message(order: :bottom) - order = :bottom - else - message = exc.full_message(order: :top) - order = :top + if backtrace_filter + if backtrace_filter.respond_to?(:call) + filtered_backtrace = backtrace_filter.call(filtered_backtrace) + else + warn "IRB.conf[:BACKTRACE_FILTER] #{backtrace_filter} should respond to `call` method" + end end - else # '3.0.0' <= RUBY_VERSION - message = exc.full_message(order: :top) - order = :top + + exc.set_backtrace(filtered_backtrace) end + + highlight = Color.colorable? + + order = + if RUBY_VERSION < '3.0.0' + STDOUT.tty? ? :bottom : :top + else # '3.0.0' <= RUBY_VERSION + :top + end + + message = exc.full_message(order: order, highlight: highlight) message = convert_invalid_byte_sequence(message, exc.message.encoding) message = encode_with_invalid_byte_sequence(message, IRB.conf[:LC_MESSAGES].encoding) unless message.encoding.to_s.casecmp?(IRB.conf[:LC_MESSAGES].encoding.to_s) message = message.gsub(/((?:^\t.+$\n)+)/) { |m| diff --git a/test/irb/helper.rb b/test/irb/helper.rb index 1614b42ad..591bd05b7 100644 --- a/test/irb/helper.rb +++ b/test/irb/helper.rb @@ -196,7 +196,7 @@ def type(command) end def write_ruby(program) - @ruby_file = Tempfile.create(%w{irb- .rb}) + @ruby_file = Tempfile.create(%w{irbtest- .rb}) @tmpfiles << @ruby_file @ruby_file.write(program) @ruby_file.close diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index b05fc80f7..28be74408 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -823,4 +823,95 @@ def build_irb IRB::Irb.new(workspace, TestInputMethod.new) end end + + class BacktraceFilteringTest < TestIRB::IntegrationTestCase + def test_backtrace_filtering + write_ruby <<~'RUBY' + def foo + raise "error" + end + + def bar + foo + end + + binding.irb + RUBY + + output = run_ruby_file do + type "bar" + type "exit" + end + + assert_match(/irbtest-.*\.rb:2:in (`|'Object#)foo': error \(RuntimeError\)/, output) + frame_traces = output.split("\n").select { |line| line.strip.match?(/from /) }.map(&:strip) + + expected_traces = if RUBY_VERSION >= "3.3.0" + [ + /from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/, + /from .*\/irbtest-.*.rb\(irb\):1:in [`']
'/, + /from :\d+:in (`|'Kernel#)loop'/, + /from :\d+:in (`|'Binding#)irb'/, + /from .*\/irbtest-.*.rb:9:in [`']
'/ + ] + else + [ + /from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/, + /from .*\/irbtest-.*.rb\(irb\):1:in [`']
'/, + /from :\d+:in (`|'Binding#)irb'/, + /from .*\/irbtest-.*.rb:9:in [`']
'/ + ] + end + + expected_traces.reverse! if RUBY_VERSION < "3.0.0" + + expected_traces.each_with_index do |expected_trace, index| + assert_match(expected_trace, frame_traces[index]) + end + end + + def test_backtrace_filtering_with_backtrace_filter + write_rc <<~'RUBY' + class TestBacktraceFilter + def self.call(backtrace) + backtrace.reject { |line| line.include?("internal") } + end + end + + IRB.conf[:BACKTRACE_FILTER] = TestBacktraceFilter + RUBY + + write_ruby <<~'RUBY' + def foo + raise "error" + end + + def bar + foo + end + + binding.irb + RUBY + + output = run_ruby_file do + type "bar" + type "exit" + end + + assert_match(/irbtest-.*\.rb:2:in (`|'Object#)foo': error \(RuntimeError\)/, output) + frame_traces = output.split("\n").select { |line| line.strip.match?(/from /) }.map(&:strip) + + expected_traces = [ + /from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/, + /from .*\/irbtest-.*.rb\(irb\):1:in [`']
'/, + /from .*\/irbtest-.*.rb:9:in [`']
'/ + ] + + expected_traces.reverse! if RUBY_VERSION < "3.0.0" + + expected_traces.each_with_index do |expected_trace, index| + assert_match(expected_trace, frame_traces[index]) + end + end + end end From c55474fd65e03a5595a1fca664b722cc5906e33e Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 1 May 2024 22:24:00 +0800 Subject: [PATCH 142/263] Use the new GH pages site for documentation (#939) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 591680b80..3ff9b3c12 100644 --- a/README.md +++ b/README.md @@ -351,7 +351,7 @@ irb(main):002> a.first. # Completes Integer methods ## Documentation -https://docs.ruby-lang.org/en/master/IRB.html +https://ruby.github.io/irb/ ## Extending IRB From 0bbe435ffe6424f680564f96e5e240144394cfbb Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 1 May 2024 23:52:49 +0900 Subject: [PATCH 143/263] Let IRB::Color.colorable? always return true|false (#940) --- lib/irb/color.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irb/color.rb b/lib/irb/color.rb index ad8670160..fca942b28 100644 --- a/lib/irb/color.rb +++ b/lib/irb/color.rb @@ -79,12 +79,12 @@ module Color class << self def colorable? - supported = $stdout.tty? && (/mswin|mingw/ =~ RUBY_PLATFORM || (ENV.key?('TERM') && ENV['TERM'] != 'dumb')) + supported = $stdout.tty? && (/mswin|mingw/.match?(RUBY_PLATFORM) || (ENV.key?('TERM') && ENV['TERM'] != 'dumb')) # because ruby/debug also uses irb's color module selectively, # irb won't be activated in that case. if IRB.respond_to?(:conf) - supported && IRB.conf.fetch(:USE_COLORIZE, true) + supported && !!IRB.conf.fetch(:USE_COLORIZE, true) else supported end From b9b1f35c999b6b5387957e1c2626959869356688 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 1 May 2024 23:19:55 +0800 Subject: [PATCH 144/263] Bump version to v1.13.0 (#941) * Bump version to v1.13.0 * Add Documentation category to changelog --- .github/release.yml | 3 +++ lib/irb/version.rb | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/release.yml b/.github/release.yml index 354779dc5..c13e7050c 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -8,6 +8,9 @@ changelog: - title: 🐛 Bug Fixes labels: - bug + - title: 📚 Documentation + labels: + - documentation - title: 🛠 Other Changes labels: - "*" diff --git a/lib/irb/version.rb b/lib/irb/version.rb index 9a7b12766..146daf725 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -5,7 +5,7 @@ # module IRB # :nodoc: - VERSION = "1.12.0" + VERSION = "1.13.0" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2024-03-06" + @LAST_UPDATE_DATE = "2024-05-01" end From 241e06173fd68b8e827aafc72a5113ee566c40fa Mon Sep 17 00:00:00 2001 From: Dmitry Gutov Date: Wed, 1 May 2024 18:40:49 +0300 Subject: [PATCH 145/263] Switch to StdioInputMethod when TERM is 'dumb' (#907) * Switch to StdioInputMethod when TERM is 'dumb' This works around Reline's misbehavior on low-capability terminals, such as when IRB is launched inside Emacs (ruby/reline#616). It should also resolve #68 and resolve #113 which were filed out of similar need. * Add special env for testing See discussion in #907. --------- Co-authored-by: tomoya ishida --- lib/irb.rb | 7 +++++-- lib/irb/context.rb | 9 +++++++-- test/irb/helper.rb | 4 +++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 45a59087b..5a064cfce 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -311,7 +311,9 @@ # ### Input Method # # The IRB input method determines how command input is to be read; by default, -# the input method for a session is IRB::RelineInputMethod. +# the input method for a session is IRB::RelineInputMethod. Unless the +# value of the TERM environment variable is 'dumb', in which case the +# most simplistic input method is used. # # You can set the input method by: # @@ -329,7 +331,8 @@ # IRB::ReadlineInputMethod. # * `--nosingleline` or `--multiline` sets the input method to # IRB::RelineInputMethod. -# +# * `--nosingleline` together with `--nomultiline` sets the +# input to IRB::StdioInputMethod. # # # Method `conf.use_multiline?` and its synonym `conf.use_reline` return: diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 22e855f1e..173f3dae5 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -85,7 +85,7 @@ def initialize(irb, workspace = nil, input_method = nil) @io = nil case use_multiline? when nil - if STDIN.tty? && IRB.conf[:PROMPT_MODE] != :INF_RUBY && !use_singleline? + if term_interactive? && IRB.conf[:PROMPT_MODE] != :INF_RUBY && !use_singleline? # Both of multiline mode and singleline mode aren't specified. @io = RelineInputMethod.new(build_completor) else @@ -99,7 +99,7 @@ def initialize(irb, workspace = nil, input_method = nil) unless @io case use_singleline? when nil - if (defined?(ReadlineInputMethod) && STDIN.tty? && + if (defined?(ReadlineInputMethod) && term_interactive? && IRB.conf[:PROMPT_MODE] != :INF_RUBY) @io = ReadlineInputMethod.new else @@ -151,6 +151,11 @@ def initialize(irb, workspace = nil, input_method = nil) @command_aliases = @user_aliases.merge(KEYWORD_ALIASES) end + private def term_interactive? + return true if ENV['TEST_IRB_FORCE_INTERACTIVE'] + STDIN.tty? && ENV['TERM'] != 'dumb' + end + # because all input will eventually be evaluated as Ruby code, # command names that conflict with Ruby keywords need special workaround # we can remove them once we implemented a better command system for IRB diff --git a/test/irb/helper.rb b/test/irb/helper.rb index 591bd05b7..acaf6277f 100644 --- a/test/irb/helper.rb +++ b/test/irb/helper.rb @@ -121,7 +121,9 @@ def run_ruby_file(&block) @envs["XDG_CONFIG_HOME"] ||= tmp_dir @envs["IRBRC"] = nil unless @envs.key?("IRBRC") - PTY.spawn(@envs.merge("TERM" => "dumb"), *cmd) do |read, write, pid| + envs_for_spawn = @envs.merge('TERM' => 'dumb', 'TEST_IRB_FORCE_INTERACTIVE' => 'true') + + PTY.spawn(envs_for_spawn, *cmd) do |read, write, pid| Timeout.timeout(TIMEOUT_SEC) do while line = safe_gets(read) lines << line From acf3c1816ec7374cd375737711eab390132507fa Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 3 May 2024 01:21:41 +0900 Subject: [PATCH 146/263] Add workaround for ruby/debug/test/console/irb_test failing with StdioInputMethod (#943) --- lib/irb/input-method.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index e5adb350e..9a24c885b 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -67,6 +67,7 @@ def initialize # # See IO#gets for more information. def gets + puts print @prompt line = @stdin.gets @line[@line_no += 1] = line From f2b5fc192f4bf1e31e337535aa42422e093d23d9 Mon Sep 17 00:00:00 2001 From: Kenichi Kamiya Date: Sat, 4 May 2024 06:34:29 +0900 Subject: [PATCH 147/263] Fix typos in test/irb/command/test_custom_command.rb (#945) --- test/irb/command/test_custom_command.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/irb/command/test_custom_command.rb b/test/irb/command/test_custom_command.rb index 6642d2b16..16800db2c 100644 --- a/test/irb/command/test_custom_command.rb +++ b/test/irb/command/test_custom_command.rb @@ -5,7 +5,7 @@ module TestIRB class CustomCommandIntegrationTest < TestIRB::IntegrationTestCase - def test_command_regsitration_can_happen_after_irb_require + def test_command_registration_can_happen_after_irb_require write_ruby <<~RUBY require "irb" require "irb/command" @@ -32,7 +32,7 @@ def execute(*) assert_include(output, "Hello from PrintCommand") end - def test_command_regsitration_accepts_string_too + def test_command_registration_accepts_string_too write_ruby <<~RUBY require "irb/command" @@ -58,7 +58,7 @@ def execute(*) assert_include(output, "Hello from PrintCommand") end - def test_arguments_propogation + def test_arguments_propagation write_ruby <<~RUBY require "irb/command" From c8bba9f8dcc61d2bf32eacfed98723ecbbc491e2 Mon Sep 17 00:00:00 2001 From: Kenichi Kamiya Date: Sat, 4 May 2024 07:08:44 +0900 Subject: [PATCH 148/263] Avoid raising errors while running help for custom commands (#944) * Avoid raising errors while running help for custom commands Raising an error from the help command is not a pleasure for the end user, even if the command does not define any attributes * Update test/irb/command/test_custom_command.rb --------- Co-authored-by: Stan Lo --- lib/irb/command/help.rb | 2 +- test/irb/command/test_custom_command.rb | 26 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/irb/command/help.rb b/lib/irb/command/help.rb index 1ed7a7707..995b81bf1 100644 --- a/lib/irb/command/help.rb +++ b/lib/irb/command/help.rb @@ -12,7 +12,7 @@ def execute(command_name) help_message else if command_class = Command.load_command(command_name) - command_class.help_message || 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/test/irb/command/test_custom_command.rb b/test/irb/command/test_custom_command.rb index 16800db2c..eac0bd349 100644 --- a/test/irb/command/test_custom_command.rb +++ b/test/irb/command/test_custom_command.rb @@ -123,5 +123,31 @@ def execute(*) assert_include(output, "2 FooBar executed") assert_include(output, "foobar_description") end + + def test_no_meta_command_also_works + write_ruby <<~RUBY + require "irb/command" + + class NoMetaCommand < IRB::Command::Base + def execute(*) + puts "This command does not override meta attributes" + nil + end + end + + IRB::Command.register(:no_meta, NoMetaCommand) + + binding.irb + RUBY + + output = run_ruby_file do + type "no_meta\n" + type "help no_meta\n" + type "exit" + end + + assert_include(output, "This command does not override meta attributes") + assert_not_include(output, "Maybe IRB bug") + end end end From 4a4d7a42793601878a1de885f3499dde5750f8e9 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 4 May 2024 11:22:14 +0800 Subject: [PATCH 149/263] Use flag instead of caller for `debug`'s binding.irb check (#947) --- lib/irb.rb | 7 +++++-- lib/irb/command/debug.rb | 17 +---------------- lib/irb/context.rb | 4 ++++ test/irb/test_debugger_integration.rb | 16 ++++++++++++++++ 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 5a064cfce..b3435c257 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -931,8 +931,11 @@ class Irb # The lexer used by this irb session attr_accessor :scanner + attr_reader :from_binding + # Creates a new irb session - def initialize(workspace = nil, input_method = nil) + def initialize(workspace = nil, input_method = nil, from_binding: false) + @from_binding = from_binding @context = Context.new(self, workspace, input_method) @context.workspace.load_helper_methods_to_main @signal_status = :IN_IRB @@ -1596,7 +1599,7 @@ def irb(show_code: true) else # If we're not in a debugger session, create a new IRB instance with the current # workspace - binding_irb = IRB::Irb.new(workspace) + binding_irb = IRB::Irb.new(workspace, from_binding: true) binding_irb.context.irb_path = irb_path binding_irb.run(IRB.conf) binding_irb.debug_break diff --git a/lib/irb/command/debug.rb b/lib/irb/command/debug.rb index f9aca0a67..8a091a49e 100644 --- a/lib/irb/command/debug.rb +++ b/lib/irb/command/debug.rb @@ -8,11 +8,6 @@ class Debug < Base category "Debugging" description "Start the debugger of debug.gem." - BINDING_IRB_FRAME_REGEXPS = [ - '', - binding.method(:irb).source_location.first, - ].map { |file| /\A#{Regexp.escape(file)}:\d+:in (`|'Binding#)irb'\z/ } - def execute(_arg) execute_debug_command end @@ -36,7 +31,7 @@ def execute_debug_command(pre_cmds: nil, do_cmds: nil) # 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? + unless irb_context.from_binding? puts "Debugging commands are only available when IRB is started with binding.irb" return end @@ -60,16 +55,6 @@ def execute_debug_command(pre_cmds: nil, do_cmds: nil) 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 diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 173f3dae5..5d2ff9732 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -607,6 +607,10 @@ def evaluate(statement, line_no) # :nodoc: nil end + def from_binding? + @irb.from_binding + end + def evaluate_expression(code, line_no) # :nodoc: result = nil if IRB.conf[:MEASURE] && IRB.conf[:MEASURE_CALLBACKS].empty? diff --git a/test/irb/test_debugger_integration.rb b/test/irb/test_debugger_integration.rb index eca40c570..8b1bddea1 100644 --- a/test/irb/test_debugger_integration.rb +++ b/test/irb/test_debugger_integration.rb @@ -67,6 +67,22 @@ def test_debug_command_only_runs_once assert_match(/IRB is already running with a debug session/, output) end + def test_debug_command_can_only_be_called_from_binding_irb + write_ruby <<~'ruby' + require "irb" + # trick test framework + puts "binding.irb" + IRB.start + ruby + + output = run_ruby_file do + type "debug" + type "exit" + end + + assert_include(output, "Debugging commands are only available when IRB is started with binding.irb") + end + def test_next write_ruby <<~'ruby' binding.irb From b1ef58aeffcd80af741b20e45cb65e421b80a90e Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 4 May 2024 11:32:31 +0800 Subject: [PATCH 150/263] Improve help message for no meta commands (#948) * Remove unnecessary code from command tests * Improve help message for no meta commands 1. Add placeholder values for both command category and description 2. Update help command's output to give different types of categories more explicit ordering --- lib/irb/command/base.rb | 4 ++-- lib/irb/command/help.rb | 26 ++++++++++++++++--------- test/irb/command/test_custom_command.rb | 18 +++++++---------- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/lib/irb/command/base.rb b/lib/irb/command/base.rb index b078b4823..1d406630a 100644 --- a/lib/irb/command/base.rb +++ b/lib/irb/command/base.rb @@ -18,12 +18,12 @@ class Base class << self def category(category = nil) @category = category if category - @category + @category || "No category" end def description(description = nil) @description = description if description - @description + @description || "No description provided." end def help_message(help_message = nil) diff --git a/lib/irb/command/help.rb b/lib/irb/command/help.rb index 995b81bf1..c2018f9b3 100644 --- a/lib/irb/command/help.rb +++ b/lib/irb/command/help.rb @@ -12,7 +12,7 @@ def execute(command_name) help_message else if command_class = Command.load_command(command_name) - command_class.help_message || 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 @@ -28,17 +28,9 @@ def help_message commands_grouped_by_categories = commands_info.group_by { |cmd| cmd[:category] } commands_grouped_by_categories["Helper methods"] = helper_methods_info - 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 @@ -46,15 +38,31 @@ def help_message 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 diff --git a/test/irb/command/test_custom_command.rb b/test/irb/command/test_custom_command.rb index eac0bd349..3a3ad11d5 100644 --- a/test/irb/command/test_custom_command.rb +++ b/test/irb/command/test_custom_command.rb @@ -15,7 +15,6 @@ class PrintCommand < IRB::Command::Base description 'print_command' def execute(*) puts "Hello from PrintCommand" - nil end end @@ -25,7 +24,7 @@ def execute(*) RUBY output = run_ruby_file do - type "print!\n" + type "print!" type "exit" end @@ -41,7 +40,6 @@ class PrintCommand < IRB::Command::Base description 'print_command' def execute(*) puts "Hello from PrintCommand" - nil end end @@ -51,7 +49,7 @@ def execute(*) RUBY output = run_ruby_file do - type "print!\n" + type "print!" type "exit" end @@ -69,7 +67,6 @@ def execute(arg) $nth_execution ||= 0 puts "\#{$nth_execution} arg=\#{arg.inspect}" $nth_execution += 1 - nil end end @@ -79,9 +76,9 @@ def execute(arg) RUBY output = run_ruby_file do - type "print_arg\n" + type "print_arg" type "print_arg \n" - type "print_arg a r g\n" + type "print_arg a r g" type "print_arg a r g \n" type "exit" end @@ -103,7 +100,6 @@ def execute(*) $nth_execution ||= 1 puts "\#{$nth_execution} FooBar executed" $nth_execution += 1 - nil end end @@ -131,7 +127,6 @@ def test_no_meta_command_also_works class NoMetaCommand < IRB::Command::Base def execute(*) puts "This command does not override meta attributes" - nil end end @@ -141,12 +136,13 @@ def execute(*) RUBY output = run_ruby_file do - type "no_meta\n" - type "help no_meta\n" + type "no_meta" + type "help no_meta" type "exit" end assert_include(output, "This command does not override meta attributes") + assert_include(output, "No description provided.") assert_not_include(output, "Maybe IRB bug") end end From 3f231b8622c10b89f29fdb2efe17fd787698e5b6 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 4 May 2024 18:03:52 +0900 Subject: [PATCH 151/263] Change debug test workaround only enabled when output is tty (#949) --- lib/irb/input-method.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index 9a24c885b..684527edc 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -67,7 +67,7 @@ def initialize # # See IO#gets for more information. def gets - puts + puts if @stdout.tty? # workaround for debug compatibility test print @prompt line = @stdin.gets @line[@line_no += 1] = line From 8cde57f55a5a580f35bd4487515dbc9d88ea2090 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 5 May 2024 18:44:49 +0800 Subject: [PATCH 152/263] Clarify that the context is IRB context (#950) --- lib/irb/helper_method/conf.rb | 2 +- test/irb/command/test_help.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irb/helper_method/conf.rb b/lib/irb/helper_method/conf.rb index 460f5ab78..718ed279c 100644 --- a/lib/irb/helper_method/conf.rb +++ b/lib/irb/helper_method/conf.rb @@ -1,7 +1,7 @@ module IRB module HelperMethod class Conf < Base - description "Returns the current context." + description "Returns the current IRB context." def execute IRB.CurrentContext diff --git a/test/irb/command/test_help.rb b/test/irb/command/test_help.rb index df3753dae..b34832b02 100644 --- a/test/irb/command/test_help.rb +++ b/test/irb/command/test_help.rb @@ -69,7 +69,7 @@ def test_help_lists_helper_methods type "exit" end - assert_match(/Helper methods\s+conf\s+Returns the current context/, out) + assert_match(/Helper methods\s+conf\s+Returns the current IRB context/, out) end end end From 66318d0a343552715f214f07c57bc4a60552ccfd Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 5 May 2024 19:11:28 +0800 Subject: [PATCH 153/263] Bump version to v1.13.1 (#951) --- lib/irb/version.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irb/version.rb b/lib/irb/version.rb index 146daf725..c41917329 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -5,7 +5,7 @@ # module IRB # :nodoc: - VERSION = "1.13.0" + VERSION = "1.13.1" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2024-05-01" + @LAST_UPDATE_DATE = "2024-05-05" end From e0c29be074709c2853da1316439bbbd899652309 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 10 May 2024 05:39:07 -0400 Subject: [PATCH 154/263] Simplify regexp to account for prism error messages (#954) Co-authored-by: Stan Lo --- lib/irb/ruby-lex.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb index cfe36be83..86e340eb0 100644 --- a/lib/irb/ruby-lex.rb +++ b/lib/irb/ruby-lex.rb @@ -230,7 +230,7 @@ def check_code_syntax(code, local_variables:) # example: # ' return :recoverable_error - when /syntax error, unexpected end-of-input/ + when /unexpected end-of-input/ # "syntax error, unexpected end-of-input, expecting keyword_end" # # example: @@ -240,7 +240,7 @@ def check_code_syntax(code, local_variables:) # fuga # end return :recoverable_error - when /syntax error, unexpected keyword_end/ + when /unexpected keyword_end/ # "syntax error, unexpected keyword_end" # # example: @@ -250,7 +250,7 @@ def check_code_syntax(code, local_variables:) # example: # end return :unrecoverable_error - when /syntax error, unexpected '\.'/ + when /unexpected '\.'/ # "syntax error, unexpected '.'" # # example: From af8ef2948b49ef8169a1f079b84a2e998948df7c Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 10 May 2024 20:40:54 +0900 Subject: [PATCH 155/263] Add a new initialization step to validate IRB.conf's values (#953) Currently, users can only find out that they have set a wrong value for IRB configs when the value is used, with opaque error messages like "comparison of Integer with true failed (TypeError)". This commit adds a new initialization step to validate the values of some IRB configs, so that users can find out about the wrong values during the initialization of IRB. --- lib/irb/context.rb | 9 +++-- lib/irb/init.rb | 35 +++++++++++++++++ test/irb/test_init.rb | 91 +++++++++++++++++++++++++++++++++++++++---- 3 files changed, 123 insertions(+), 12 deletions(-) diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 5d2ff9732..aafce7aad 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -73,11 +73,12 @@ def initialize(irb, workspace = nil, input_method = nil) self.prompt_mode = IRB.conf[:PROMPT_MODE] - if IRB.conf[:SINGLE_IRB] or !defined?(IRB::JobManager) - @irb_name = IRB.conf[:IRB_NAME] - else - @irb_name = IRB.conf[:IRB_NAME]+"#"+IRB.JobManager.n_jobs.to_s + @irb_name = IRB.conf[:IRB_NAME] + + unless IRB.conf[:SINGLE_IRB] or !defined?(IRB::JobManager) + @irb_name = @irb_name + "#" + IRB.JobManager.n_jobs.to_s end + self.irb_path = "(" + @irb_name + ")" case input_method diff --git a/lib/irb/init.rb b/lib/irb/init.rb index 355047519..7dc08912e 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -52,6 +52,7 @@ def IRB.setup(ap_path, argv: ::ARGV) IRB.init_error IRB.parse_opts(argv: argv) IRB.run_config + IRB.validate_config IRB.load_modules unless @CONF[:PROMPT][@CONF[:PROMPT_MODE]] @@ -427,6 +428,40 @@ def IRB.irbrc_files @irbrc_files end + def IRB.validate_config + conf[:IRB_NAME] = conf[:IRB_NAME].to_s + + irb_rc = conf[:IRB_RC] + unless irb_rc.nil? || irb_rc.respond_to?(:call) + raise_validation_error "IRB.conf[:IRB_RC] should be a callable object. Got #{irb_rc.inspect}." + end + + back_trace_limit = conf[:BACK_TRACE_LIMIT] + unless back_trace_limit.is_a?(Integer) + raise_validation_error "IRB.conf[:BACK_TRACE_LIMIT] should be an integer. Got #{back_trace_limit.inspect}." + end + + prompt = conf[:PROMPT] + unless prompt.is_a?(Hash) + msg = "IRB.conf[:PROMPT] should be a Hash. Got #{prompt.inspect}." + + if prompt.is_a?(Symbol) + msg += " Did you mean to set `IRB.conf[:PROMPT_MODE]`?" + end + + raise_validation_error msg + end + + eval_history = conf[:EVAL_HISTORY] + unless eval_history.nil? || eval_history.is_a?(Integer) + raise_validation_error "IRB.conf[:EVAL_HISTORY] should be an integer. Got #{eval_history.inspect}." + end + end + + def IRB.raise_validation_error(msg) + raise TypeError, msg, @irbrc_files + end + # loading modules def IRB.load_modules for m in @CONF[:LOAD_MODULES] diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb index f11d7398c..645678701 100644 --- a/test/irb/test_init.rb +++ b/test/irb/test_init.rb @@ -268,15 +268,94 @@ def with_argv(argv) end end + class ConfigValidationTest < TestCase + def setup + @original_home = ENV["HOME"] + @original_irbrc = ENV["IRBRC"] + # To prevent the test from using the user's .irbrc file + ENV["HOME"] = Dir.mktmpdir + IRB.instance_variable_set(:@existing_rc_name_generators, nil) + super + end + + def teardown + super + ENV["IRBRC"] = @original_irbrc + ENV["HOME"] = @original_home + File.unlink(@irbrc) + end + + def test_irb_name_converts_non_string_values_to_string + assert_no_irb_validation_error(<<~'RUBY') + IRB.conf[:IRB_NAME] = :foo + RUBY + + assert_equal "foo", IRB.conf[:IRB_NAME] + end + + def test_irb_rc_name_only_takes_callable_objects + assert_irb_validation_error(<<~'RUBY', "IRB.conf[:IRB_RC] should be a callable object. Got :foo.") + IRB.conf[:IRB_RC] = :foo + RUBY + end + + def test_back_trace_limit_only_accepts_integers + assert_irb_validation_error(<<~'RUBY', "IRB.conf[:BACK_TRACE_LIMIT] should be an integer. Got \"foo\".") + IRB.conf[:BACK_TRACE_LIMIT] = "foo" + RUBY + end + + def test_prompt_only_accepts_hash + assert_irb_validation_error(<<~'RUBY', "IRB.conf[:PROMPT] should be a Hash. Got \"foo\".") + IRB.conf[:PROMPT] = "foo" + RUBY + end + + def test_eval_history_only_accepts_integers + assert_irb_validation_error(<<~'RUBY', "IRB.conf[:EVAL_HISTORY] should be an integer. Got \"foo\".") + IRB.conf[:EVAL_HISTORY] = "foo" + RUBY + end + + private + + def assert_irb_validation_error(rc_content, error_message) + write_rc rc_content + + assert_raise_with_message(TypeError, error_message) do + IRB.setup(__FILE__) + end + end + + def assert_no_irb_validation_error(rc_content) + write_rc rc_content + + assert_nothing_raised do + IRB.setup(__FILE__) + end + end + + def write_rc(content) + @irbrc = Tempfile.new('irbrc') + @irbrc.write(content) + @irbrc.close + ENV['IRBRC'] = @irbrc.path + end + end + class InitIntegrationTest < IntegrationTestCase - def test_load_error_in_rc_file_is_warned - write_rc <<~'IRBRC' - require "file_that_does_not_exist" - IRBRC + def setup + super write_ruby <<~'RUBY' binding.irb RUBY + end + + def test_load_error_in_rc_file_is_warned + write_rc <<~'IRBRC' + require "file_that_does_not_exist" + IRBRC output = run_ruby_file do type "'foobar'" @@ -293,10 +372,6 @@ def test_normal_errors_in_rc_file_is_warned raise "I'm an error" IRBRC - write_ruby <<~'RUBY' - binding.irb - RUBY - output = run_ruby_file do type "'foobar'" type "exit" From 7d603494990eab1e6273341a1d46c7d7ae15f041 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 12 May 2024 13:47:10 +0900 Subject: [PATCH 156/263] Clean up tmpdir --- test/irb/test_init.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb index 645678701..3207c2898 100644 --- a/test/irb/test_init.rb +++ b/test/irb/test_init.rb @@ -273,7 +273,7 @@ def setup @original_home = ENV["HOME"] @original_irbrc = ENV["IRBRC"] # To prevent the test from using the user's .irbrc file - ENV["HOME"] = Dir.mktmpdir + ENV["HOME"] = @home = Dir.mktmpdir IRB.instance_variable_set(:@existing_rc_name_generators, nil) super end @@ -283,6 +283,7 @@ def teardown ENV["IRBRC"] = @original_irbrc ENV["HOME"] = @original_home File.unlink(@irbrc) + Dir.rmdir(@home) end def test_irb_name_converts_non_string_values_to_string From 2f42b2360dd023319519d231863860bc2fd30a8a Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 16 May 2024 22:44:52 -0400 Subject: [PATCH 157/263] Reorder ruby lex clauses for unrecoverable first (#956) When a syntax error includes multiple error messages, we want to check for unrecoverable messages first so that we do not accidentally match a recoverable error later in the message. --- lib/irb/ruby-lex.rb | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb index 86e340eb0..f6ac7f0f5 100644 --- a/lib/irb/ruby-lex.rb +++ b/lib/irb/ruby-lex.rb @@ -219,27 +219,6 @@ def check_code_syntax(code, local_variables:) :unrecoverable_error rescue SyntaxError => e case e.message - when /unterminated (?:string|regexp) meets end of file/ - # "unterminated regexp meets end of file" - # - # example: - # / - # - # "unterminated string meets end of file" - # - # example: - # ' - return :recoverable_error - when /unexpected end-of-input/ - # "syntax error, unexpected end-of-input, expecting keyword_end" - # - # example: - # if true - # hoge - # if false - # fuga - # end - return :recoverable_error when /unexpected keyword_end/ # "syntax error, unexpected keyword_end" # @@ -262,6 +241,27 @@ def check_code_syntax(code, local_variables:) # example: # method / f / return :unrecoverable_error + when /unterminated (?:string|regexp) meets end of file/ + # "unterminated regexp meets end of file" + # + # example: + # / + # + # "unterminated string meets end of file" + # + # example: + # ' + return :recoverable_error + when /unexpected end-of-input/ + # "syntax error, unexpected end-of-input, expecting keyword_end" + # + # example: + # if true + # hoge + # if false + # fuga + # end + return :recoverable_error else return :other_error end From a572180b3a4c0da3dcbd71dea3cc4cf8a65d689c Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 4 Jun 2024 21:09:07 +0900 Subject: [PATCH 158/263] Remove useless Reline::Key.new and update wrong comment for alt+d (#963) --- lib/irb/input-method.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index 684527edc..ced35a2c5 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -328,10 +328,11 @@ def show_doc_dialog_proc ->() { dialog.trap_key = nil alt_d = [ - [Reline::Key.new(nil, 0xE4, true)], # Normal Alt+d. [27, 100], # Normal Alt+d when convert-meta isn't used. - [195, 164], # The "ä" that appears when Alt+d is pressed on xterm. - [226, 136, 130] # The "∂" that appears when Alt+d in pressed on iTerm2. + # When option/alt is not configured as a meta key in terminal emulator, + # option/alt + d will send a unicode character depend on OS keyboard setting. + [195, 164], # "ä" in somewhere (FIXME: environment information is unknown). + [226, 136, 130] # "∂" Alt+d on Mac keyboard. ] if just_cursor_moving and completion_journey_data.nil? From 1d627cebca50ad0ed729b4b15663e9e88469a441 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 5 Jun 2024 17:29:20 +0100 Subject: [PATCH 159/263] Add accidentally dropped disable_irb command back (#964) * Add accidentally dropped disable_irb command back * Sort command files require by name --- lib/irb/default_commands.rb | 37 ++++++++++++++++------------ test/irb/command/test_disable_irb.rb | 28 +++++++++++++++++++++ 2 files changed, 49 insertions(+), 16 deletions(-) create mode 100644 test/irb/command/test_disable_irb.rb diff --git a/lib/irb/default_commands.rb b/lib/irb/default_commands.rb index 1bbc68efa..91c6b2d04 100644 --- a/lib/irb/default_commands.rb +++ b/lib/irb/default_commands.rb @@ -2,32 +2,33 @@ require_relative "command" require_relative "command/internal_helpers" -require_relative "command/context" -require_relative "command/exit" -require_relative "command/force_exit" -require_relative "command/chws" -require_relative "command/pushws" -require_relative "command/subirb" -require_relative "command/load" -require_relative "command/debug" -require_relative "command/edit" +require_relative "command/backtrace" require_relative "command/break" require_relative "command/catch" -require_relative "command/next" -require_relative "command/delete" -require_relative "command/step" +require_relative "command/chws" +require_relative "command/context" require_relative "command/continue" +require_relative "command/debug" +require_relative "command/delete" +require_relative "command/disable_irb" +require_relative "command/edit" +require_relative "command/exit" require_relative "command/finish" -require_relative "command/backtrace" -require_relative "command/info" +require_relative "command/force_exit" require_relative "command/help" -require_relative "command/show_doc" +require_relative "command/history" +require_relative "command/info" require_relative "command/irb_info" +require_relative "command/load" require_relative "command/ls" require_relative "command/measure" +require_relative "command/next" +require_relative "command/pushws" +require_relative "command/show_doc" require_relative "command/show_source" +require_relative "command/step" +require_relative "command/subirb" require_relative "command/whereami" -require_relative "command/history" module IRB module Command @@ -235,6 +236,10 @@ def load_command(command) [:history, NO_OVERRIDE], [:hist, NO_OVERRIDE] ) + + _register_with_aliases(:irb_disable_irb, Command::DisableIrb, + [:disable_irb, NO_OVERRIDE] + ) end ExtendCommand = Command diff --git a/test/irb/command/test_disable_irb.rb b/test/irb/command/test_disable_irb.rb new file mode 100644 index 000000000..14a20043d --- /dev/null +++ b/test/irb/command/test_disable_irb.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: false +require 'irb' + +require_relative "../helper" + +module TestIRB + class DisableIRBTest < IntegrationTestCase + def test_disable_irb_disable_further_irb_breakpoints + write_ruby <<~'ruby' + puts "First line" + puts "Second line" + binding.irb + puts "Third line" + binding.irb + puts "Fourth line" + ruby + + output = run_ruby_file do + type "disable_irb" + end + + assert_match(/First line\r\n/, output) + assert_match(/Second line\r\n/, output) + assert_match(/Third line\r\n/, output) + assert_match(/Fourth line\r\n/, output) + end + end +end From dd339e6df540c5322e824b584b23d1bbc9aef841 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 8 Jun 2024 07:10:13 +0100 Subject: [PATCH 160/263] Add a section to guide users choose between helper methods and commands (#965) --- EXTEND_IRB.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/EXTEND_IRB.md b/EXTEND_IRB.md index e5bc4a155..684b0b7b1 100644 --- a/EXTEND_IRB.md +++ b/EXTEND_IRB.md @@ -4,6 +4,17 @@ From v1.13.0, IRB provides official APIs to extend its functionality. This featu customize and enhance their users' IRB sessions by adding new commands and helper methods tailored for the libraries. +## Helper Methods vs. Commands + +- Use a helper method if the operation is meant to return a Ruby object that interacts with the application. + - For example, an `admin_user` helper method that returns `User.where(admin: true).first`, which can then be used like `login_as(admin_user)`. +- Use a command if the operation fits one of the following: + - A utility operation that performs non-Ruby related tasks, such as IRB's `edit` command. + - Displays information, like the `show_source` command. + - If the operation requires non-Ruby syntax arguments, like `ls -g pattern`. + +If you don't know what to pick, go with commands first. Commands are generally safer as they can handle a wider variety of inputs and use cases. + ## Commands Commands are designed to complete certain tasks or display information for the user, similar to shell commands. From ad642795da180d2cb8513c9f9e13e438cda2b974 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 9 Jun 2024 21:22:27 +0100 Subject: [PATCH 161/263] Suppress Ruby warnings in certain backtrace filtering tests (#966) Since they're sensitive to the warnings, and the warnings are not relevant to the tests, we can suppress them to keep the tests simple. --- test/irb/test_irb.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index 28be74408..3d8044c5a 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -825,6 +825,13 @@ def build_irb end class BacktraceFilteringTest < TestIRB::IntegrationTestCase + def setup + super + # These tests are sensitive to warnings, so we disable them + original_rubyopt = [ENV["RUBYOPT"], @envs["RUBYOPT"]].compact.join(" ") + @envs["RUBYOPT"] = original_rubyopt + " -W0" + end + def test_backtrace_filtering write_ruby <<~'RUBY' def foo From 3512020f1ca07bc322b1fb3316f1c4a7a9bfdb31 Mon Sep 17 00:00:00 2001 From: Anatoly Busygin Date: Tue, 11 Jun 2024 08:44:24 +0300 Subject: [PATCH 162/263] fix typos in the `Index of Command-Line Options` --- doc/irb/indexes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/irb/indexes.md b/doc/irb/indexes.md index 24a67b969..d6dc5c160 100644 --- a/doc/irb/indexes.md +++ b/doc/irb/indexes.md @@ -7,7 +7,7 @@ These are the \IRB command-line options, with links to explanatory text: - `-d`: Set `$DEBUG` and {$VERBOSE}[rdoc-ref:IRB@Verbosity] to `true`. - `-E _ex_[:_in_]`: Set initial external (ex) and internal (in) - {encodings}[rdoc-ref:IRB@Encodings] (same as `ruby -E>`). + {encodings}[rdoc-ref:IRB@Encodings] (same as `ruby -E`). - `-f`: Don't initialize from {configuration file}[rdoc-ref:IRB@Configuration+File]. - `-I _dirpath_`: Specify {$LOAD_PATH directory}[rdoc-ref:IRB@Load+Modules] (same as `ruby -I`). @@ -50,7 +50,7 @@ These are the \IRB command-line options, with links to explanatory text: {command-line argument}[rdoc-ref:IRB@Initialization+Script], and include it in `ARGV`. - `--nosingleline`: Don't use the singleline editor as the {input method}[rdoc-ref:IRB@Input+Method]. -- `--noverbose`Don't print {verbose}[rdoc-ref:IRB@Verbosity] details. +- `--noverbose`: Don't print {verbose}[rdoc-ref:IRB@Verbosity] details. - `--prompt _mode_`, `--prompt-mode _mode_`: Set {prompt and return formats}[rdoc-ref:IRB@Prompt+and+Return+Formats]; `mode` may be a {pre-defined prompt}[rdoc-ref:IRB@Pre-Defined+Prompts] From 905184ff9ce2680a31164349be561438217508d5 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 12 Jun 2024 16:47:58 +0900 Subject: [PATCH 163/263] Cleanup irbrc generator cache always at teardown (#968) --- test/irb/test_init.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb index 3207c2898..c423fa112 100644 --- a/test/irb/test_init.rb +++ b/test/irb/test_init.rb @@ -274,7 +274,6 @@ def setup @original_irbrc = ENV["IRBRC"] # To prevent the test from using the user's .irbrc file ENV["HOME"] = @home = Dir.mktmpdir - IRB.instance_variable_set(:@existing_rc_name_generators, nil) super end @@ -284,6 +283,7 @@ def teardown ENV["HOME"] = @original_home File.unlink(@irbrc) Dir.rmdir(@home) + IRB.instance_variable_set(:@existing_rc_name_generators, nil) end def test_irb_name_converts_non_string_values_to_string From bad7492ab07f053b714b92d00e6caa0492870bb0 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 13 Jun 2024 01:57:52 +0900 Subject: [PATCH 164/263] Invalid encoding symbol now raises SyntaxError also in 3.3 (#969) --- test/irb/test_irb.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index 3d8044c5a..ece790290 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -61,7 +61,7 @@ def test_evaluate_with_encoding_error_without_lineno omit "Remove me after https://github.com/ruby/prism/issues/2129 is addressed and adopted in TruffleRuby" end - if RUBY_VERSION >= "3.4." + if RUBY_VERSION >= "3.3." omit "Now raises SyntaxError" end From 35de7dacd46c448566088c721411257a0687922b Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 15 Jun 2024 21:13:57 +0100 Subject: [PATCH 165/263] Bump version to v1.13.2 (#970) --- lib/irb/version.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irb/version.rb b/lib/irb/version.rb index c41917329..25b7903f0 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -5,7 +5,7 @@ # module IRB # :nodoc: - VERSION = "1.13.1" + VERSION = "1.13.2" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2024-05-05" + @LAST_UPDATE_DATE = "2024-06-15" end From c844176842b6fef80d046908cca761aca4644d93 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 18 Jun 2024 16:15:19 +0100 Subject: [PATCH 166/263] Improve how command calls' return value is handled (#972) In #934, we changed command calls to return nil only. This PR improves the behaviour even further by: - Not echoing the `nil` returned by command calls - Not overriding previous return value stored in `_` with the `nil` from commands --- lib/irb/context.rb | 1 - lib/irb/statement.rb | 2 +- test/irb/test_command.rb | 34 ++++++++++++---------------------- test/irb/test_irb.rb | 15 +++++++++++++++ 4 files changed, 28 insertions(+), 24 deletions(-) diff --git a/lib/irb/context.rb b/lib/irb/context.rb index aafce7aad..74a08ca69 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -602,7 +602,6 @@ def evaluate(statement, line_no) # :nodoc: set_last_value(result) when Statement::Command statement.command_class.execute(self, statement.arg) - set_last_value(nil) end nil diff --git a/lib/irb/statement.rb b/lib/irb/statement.rb index a3391c12a..9591a4035 100644 --- a/lib/irb/statement.rb +++ b/lib/irb/statement.rb @@ -68,7 +68,7 @@ def is_assignment? end def suppresses_echo? - false + true end def should_be_handled_by_debugger? diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index 8cb8928ad..30c3f5ca2 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -239,7 +239,7 @@ def test_measure ) assert_empty err - assert_match(/\A(TIME is added\.\n=> nil\nprocessing time: .+\n=> 3\n=> nil\n=> 3\n){2}/, out) + assert_match(/\A(TIME is added\.\nprocessing time: .+\n=> 3\n=> 3\n){2}/, out) assert_empty(c.class_variables) end @@ -266,7 +266,7 @@ def test_measure_keeps_previous_value ) assert_empty err - assert_match(/\ATIME is added\.\n=> nil\nprocessing time: .+\n=> 3\nprocessing time: .+\n=> 3/, out) + assert_match(/\ATIME is added\.\nprocessing time: .+\n=> 3\nprocessing time: .+\n=> 3/, out) assert_empty(c.class_variables) end @@ -291,7 +291,7 @@ def test_measure_enabled_by_rc ) assert_empty err - assert_match(/\Aprocessing time: .+\n=> 3\n=> nil\n=> 3\n/, out) + assert_match(/\Aprocessing time: .+\n=> 3\n=> 3\n/, out) end def test_measure_enabled_by_rc_with_custom @@ -321,7 +321,7 @@ def test_measure_enabled_by_rc_with_custom conf: conf, ) assert_empty err - assert_match(/\Acustom processing time: .+\n=> 3\n=> nil\n=> 3\n/, out) + assert_match(/\Acustom processing time: .+\n=> 3\n=> 3\n/, out) end def test_measure_with_custom @@ -353,7 +353,7 @@ def test_measure_with_custom ) assert_empty err - assert_match(/\A=> 3\nCUSTOM is added\.\n=> nil\ncustom processing time: .+\n=> 3\n=> nil\n=> 3\n/, out) + assert_match(/\A=> 3\nCUSTOM is added\.\ncustom processing time: .+\n=> 3\n=> 3\n/, out) end def test_measure_toggle @@ -385,7 +385,7 @@ def test_measure_toggle ) assert_empty err - assert_match(/\AFOO is added\.\n=> nil\nfoo\n=> 1\nBAR is added\.\n=> nil\nbar\nfoo\n=> 2\n=> nil\nbar\n=> 3\n=> nil\n=> 4\n/, out) + assert_match(/\AFOO is added\.\nfoo\n=> 1\nBAR is added\.\nbar\nfoo\n=> 2\nbar\n=> 3\n=> 4\n/, out) end def test_measure_with_proc_warning @@ -410,7 +410,7 @@ def test_measure_with_proc_warning ) assert_match(/to add custom measure/, err) - assert_match(/\A=> 3\n=> nil\n=> 3\n/, out) + assert_match(/\A=> 3\n=> 3\n/, out) assert_empty(c.class_variables) end end @@ -429,8 +429,7 @@ def test_irb_source /=> "bug17564"\n/, /=> "bug17564"\n/, / => "hi"\n/, - / => nil\n/, - /=> "hi"\n/, + / => "hi"\n/, ], out) end @@ -457,8 +456,7 @@ def test_irb_load /=> "bug17564"\n/, /=> "bug17564"\n/, / => "hi"\n/, - / => nil\n/, - /=> "bug17564"\n/, + / => "bug17564"\n/, ], out) end @@ -760,10 +758,7 @@ def test_ls_with_no_singleton_class class ShowDocTest < CommandTestCase def test_show_doc - out, err = execute_lines( - "show_doc String#gsub\n", - "\n", - ) + out, err = execute_lines("show_doc String#gsub") # the former is what we'd get without document content installed, like on CI # the latter is what we may get locally @@ -776,15 +771,10 @@ def test_show_doc end def test_show_doc_without_rdoc - out, err = without_rdoc do - execute_lines( - "show_doc String#gsub\n", - "\n", - ) + _, err = without_rdoc do + execute_lines("show_doc String#gsub") end - # if it fails to require rdoc, it only returns the command object - assert_match(/=> nil\n/, out) assert_include(err, "Can't display document because `rdoc` is not installed.\n") ensure # this is the only way to reset the redefined method without coupling the test with its implementation diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index ece790290..e8d5be285 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -56,6 +56,21 @@ def test_underscore_stores_last_result assert_include output, "=> 12" end + def test_commands_dont_override_stored_last_result + write_ruby <<~'RUBY' + binding.irb + RUBY + + output = run_ruby_file do + type "1 + 1" + type "ls" + type "_ + 10" + type "exit!" + end + + assert_include output, "=> 12" + end + def test_evaluate_with_encoding_error_without_lineno if RUBY_ENGINE == 'truffleruby' omit "Remove me after https://github.com/ruby/prism/issues/2129 is addressed and adopted in TruffleRuby" From 00603d470f259d4cb7d2860e4ca796cb2be5b3a2 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Mon, 1 Jul 2024 02:13:22 +0900 Subject: [PATCH 167/263] Allow assigning and using local variable name conflicting with command (#961) --- lib/irb.rb | 8 +++++ test/irb/command/test_custom_command.rb | 45 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/lib/irb.rb b/lib/irb.rb index b3435c257..b417d9c2e 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1138,6 +1138,8 @@ def build_statement(code) end end + ASSIGN_OPERATORS_REGEXP = Regexp.union(%w[= += -= *= /= %= **= &= |= &&= ||= ^= <<= >>=]) + def parse_command(code) command_name, arg = code.strip.split(/\s+/, 2) return unless code.lines.size == 1 && command_name @@ -1149,6 +1151,12 @@ def parse_command(code) return [alias_name, arg] end + # Assignment-like expression is not a command + return if arg.start_with?(ASSIGN_OPERATORS_REGEXP) && !arg.start_with?(/==|=~/) + + # Local variable have precedence over command + return if @context.local_variables.include?(command) + # Check visibility public_method = !!Kernel.instance_method(:public_method).bind_call(@context.main, command) rescue false private_method = !public_method && !!Kernel.instance_method(:method).bind_call(@context.main, command) rescue false diff --git a/test/irb/command/test_custom_command.rb b/test/irb/command/test_custom_command.rb index 3a3ad11d5..13f412c21 100644 --- a/test/irb/command/test_custom_command.rb +++ b/test/irb/command/test_custom_command.rb @@ -145,5 +145,50 @@ def execute(*) assert_include(output, "No description provided.") assert_not_include(output, "Maybe IRB bug") end + + def test_command_name_local_variable + write_ruby <<~RUBY + require "irb/command" + + class FooBarCommand < IRB::Command::Base + category 'CommandTest' + description 'test' + def execute(arg) + puts "arg=\#{arg.inspect}" + end + end + + IRB::Command.register(:foo_bar, FooBarCommand) + + binding.irb + RUBY + + output = run_ruby_file do + type "binding.irb" + type "foo_bar == 1 || 1" + type "foo_bar =~ /2/ || 2" + type "exit" + type "binding.irb" + type "foo_bar = '3'; foo_bar" + type "foo_bar == 4 || '4'" + type "foo_bar =~ /5/ || '5'" + type "exit" + type "binding.irb" + type "foo_bar ||= '6'; foo_bar" + type "foo_bar == 7 || '7'" + type "foo_bar =~ /8/ || '8'" + type "exit" + type "exit" + end + + assert_include(output, 'arg="== 1 || 1"') + assert_include(output, 'arg="=~ /2/ || 2"') + assert_include(output, '=> "3"') + assert_include(output, '=> "4"') + assert_include(output, '=> "5"') + assert_include(output, '=> "6"') + assert_include(output, '=> "7"') + assert_include(output, '=> "8"') + end end end From 4a0e0e89b7f9afaf0b9c9bb4d04b771bf3c1d170 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 3 Jul 2024 18:17:38 +0100 Subject: [PATCH 168/263] Introduce cd command (#971) It's essentially a combination of pushws and popws commands that are easier to use. Help message: ``` 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 ``` --- lib/irb/command/cd.rb | 51 +++++++++++++++++++++++++++++ lib/irb/default_commands.rb | 3 ++ test/irb/command/test_cd.rb | 65 +++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 lib/irb/command/cd.rb create mode 100644 test/irb/command/test_cd.rb 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 From 7b6557cc240568309dbb009125b440fbfeb666f5 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 5 Jul 2024 18:25:06 +0100 Subject: [PATCH 169/263] Return only commands when completing help command's argument (#973) The command only takes command names as arguments, so we should only return command names as candidates. This will help users find a command faster as completion will be another useful hint too. --- lib/irb/completion.rb | 38 +++++++++++++++++++++++++-------- test/irb/test_completion.rb | 10 +++++++-- test/irb/test_type_completor.rb | 5 +++++ 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index a3d89373c..7f102dcdf 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -33,6 +33,8 @@ def defined? do yield ] + HELP_COMMAND_PREPOSING = /\Ahelp\s+/ + def completion_candidates(preposing, target, postposing, bind:) raise NotImplementedError end @@ -86,8 +88,8 @@ def retrieve_files_to_require_from_load_path ) end - def command_completions(preposing, target) - if preposing.empty? && !target.empty? + def command_candidates(target) + if !target.empty? IRB::Command.command_names.select { _1.start_with?(target) } else [] @@ -111,8 +113,18 @@ def inspect end def completion_candidates(preposing, target, _postposing, bind:) - commands = command_completions(preposing, target) + # When completing the argument of `help` command, only commands should be candidates + return command_candidates(target) if preposing.match?(HELP_COMMAND_PREPOSING) + + commands = if preposing.empty? + command_candidates(target) + # It doesn't make sense to propose commands with other preposing + else + [] + end + result = ReplTypeCompletor.analyze(preposing + target, binding: bind, filename: @context.irb_path) + return commands unless result commands | result.completion_candidates.map { target + _1 } @@ -187,12 +199,20 @@ def complete_require_path(target, preposing, postposing) end def completion_candidates(preposing, target, postposing, bind:) - if preposing && postposing - result = complete_require_path(target, preposing, postposing) - return result if result + if result = complete_require_path(target, preposing, postposing) + return result end - commands = command_completions(preposing || '', target) - commands | retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map{ |i| i.encode(Encoding.default_external) } + + commands = command_candidates(target) + + # When completing the argument of `help` command, only commands should be candidates + return commands if preposing.match?(HELP_COMMAND_PREPOSING) + + # It doesn't make sense to propose commands with other preposing + commands = [] unless preposing.empty? + + completion_data = retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map{ |i| i.encode(Encoding.default_external) } + commands | completion_data end def doc_namespace(_preposing, matched, _postposing, bind:) @@ -470,7 +490,7 @@ def retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace.bind end end CompletionProc = ->(target, preposing = nil, postposing = nil) { - regexp_completor.completion_candidates(preposing, target, postposing, bind: IRB.conf[:MAIN_CONTEXT].workspace.binding) + regexp_completor.completion_candidates(preposing || '', target, postposing || '', bind: IRB.conf[:MAIN_CONTEXT].workspace.binding) } end deprecate_constant :InputCompletor diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb index 5fe7952b3..c9a0eafa3 100644 --- a/test/irb/test_completion.rb +++ b/test/irb/test_completion.rb @@ -16,8 +16,14 @@ def doc_namespace(target, bind) class CommandCompletionTest < CompletionTest def test_command_completion - assert_include(IRB::RegexpCompletor.new.completion_candidates('', 'show_s', '', bind: binding), 'show_source') - assert_not_include(IRB::RegexpCompletor.new.completion_candidates(';', 'show_s', '', bind: binding), 'show_source') + completor = IRB::RegexpCompletor.new + binding.eval("some_var = 1") + # completion for help command's argument should only include command names + assert_include(completor.completion_candidates('help ', 's', '', bind: binding), 'show_source') + assert_not_include(completor.completion_candidates('help ', 's', '', bind: binding), 'some_var') + + assert_include(completor.completion_candidates('', 'show_s', '', bind: binding), 'show_source') + assert_not_include(completor.completion_candidates(';', 'show_s', '', bind: binding), 'show_source') end end diff --git a/test/irb/test_type_completor.rb b/test/irb/test_type_completor.rb index 5ed8988b3..412d7c696 100644 --- a/test/irb/test_type_completor.rb +++ b/test/irb/test_type_completor.rb @@ -56,6 +56,11 @@ def test_empty_completion end def test_command_completion + binding.eval("some_var = 1") + # completion for help command's argument should only include command names + assert_include(@completor.completion_candidates('help ', 's', '', bind: binding), 'show_source') + assert_not_include(@completor.completion_candidates('help ', 's', '', bind: binding), 'some_var') + assert_include(@completor.completion_candidates('', 'show_s', '', bind: binding), 'show_source') assert_not_include(@completor.completion_candidates(';', 'show_s', '', bind: binding), 'show_source') end From 6a9e1297149553a67bca37683e91430d4cb2da8c Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 6 Jul 2024 18:54:57 +0100 Subject: [PATCH 170/263] Bump version to v1.14.0 (#980) --- lib/irb/version.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irb/version.rb b/lib/irb/version.rb index 25b7903f0..e935a1d7f 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -5,7 +5,7 @@ # module IRB # :nodoc: - VERSION = "1.13.2" + VERSION = "1.14.0" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2024-06-15" + @LAST_UPDATE_DATE = "2024-07-06" end From c1be413c7457099bd3f676a0ea97171c0e8fbc30 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 6 Jul 2024 19:03:55 +0100 Subject: [PATCH 171/263] Update commands list in the readme --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3ff9b3c12..fec8350ef 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ Help help List all available commands. Use `help ` to get information about a specific command. IRB + context Displays current configuration. exit Exit the current irb session. exit! Exit the current process. irb_load Load a Ruby file. @@ -121,6 +122,7 @@ IRB source Loads a given file in the current session. irb_info Show information about IRB. history Shows the input history. `-g [query]` or `-G [query]` allows you to filter the output. + disable_irb Disable binding.irb. Workspace cwws Show the current workspace. @@ -128,6 +130,7 @@ Workspace workspaces Show workspaces. pushws Push an object to the workspace stack. popws Pop a workspace from the workspace stack. + cd Move into the given object or leave the current context. Multi-irb (DEPRECATED) irb Start a child IRB. @@ -152,11 +155,14 @@ Misc measure `measure` enables the mode to measure processing time. `measure :off` disables it. Context - show_doc Enter the mode to look up RI documents. + show_doc Look up documentation with RI. ls Show methods, constants, and variables. - show_source Show the source code of a given method or constant. + show_source Show the source code of a given method, class/module, or constant. whereami Show the source code around binding.irb again. +Helper methods + conf Returns the current IRB context. + Aliases $ Alias for `show_source` @ Alias for `whereami` From cdaa356df2c4e2636ee2eeb906f2b73ab55b8549 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 16 Jul 2024 16:58:08 +0100 Subject: [PATCH 172/263] Group class methods under `class << self` (#981) --- .rubocop.yml | 7 + lib/irb.rb | 58 +++--- lib/irb/command/base.rb | 18 +- lib/irb/command/debug.rb | 14 +- lib/irb/default_commands.rb | 10 +- lib/irb/input-method.rb | 12 +- lib/irb/inspector.rb | 61 +++--- lib/irb/nesting_parser.rb | 390 ++++++++++++++++++------------------ lib/irb/ruby-lex.rb | 166 +++++++-------- lib/irb/workspace.rb | 12 +- test/irb/test_context.rb | 1 - test/irb/test_workspace.rb | 1 - 12 files changed, 386 insertions(+), 364 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 22d51af71..6c693cc59 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -30,3 +30,10 @@ Layout/SpaceBeforeComma: Layout/SpaceAfterComma: Enabled: true + +Style/ClassMethodsDefinitions: + Enabled: true + EnforcedStyle: self_class + +Layout/EmptyLines: + Enabled: true diff --git a/lib/irb.rb b/lib/irb.rb index b417d9c2e..3d45fa89d 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -880,40 +880,42 @@ module IRB # An exception raised by IRB.irb_abort class Abort < Exception;end - # The current IRB::Context of the session, see IRB.conf - # - # irb - # irb(main):001:0> IRB.CurrentContext.irb_name = "foo" - # foo(main):002:0> IRB.conf[:MAIN_CONTEXT].irb_name #=> "foo" - def IRB.CurrentContext # :nodoc: - IRB.conf[:MAIN_CONTEXT] - end + class << self + # The current IRB::Context of the session, see IRB.conf + # + # irb + # irb(main):001:0> IRB.CurrentContext.irb_name = "foo" + # foo(main):002:0> IRB.conf[:MAIN_CONTEXT].irb_name #=> "foo" + def CurrentContext # :nodoc: + conf[:MAIN_CONTEXT] + end - # Initializes IRB and creates a new Irb.irb object at the `TOPLEVEL_BINDING` - def IRB.start(ap_path = nil) - STDOUT.sync = true - $0 = File::basename(ap_path, ".rb") if ap_path + # Initializes IRB and creates a new Irb.irb object at the `TOPLEVEL_BINDING` + def start(ap_path = nil) + STDOUT.sync = true + $0 = File::basename(ap_path, ".rb") if ap_path - IRB.setup(ap_path) + setup(ap_path) - if @CONF[:SCRIPT] - irb = Irb.new(nil, @CONF[:SCRIPT]) - else - irb = Irb.new + if @CONF[:SCRIPT] + irb = Irb.new(nil, @CONF[:SCRIPT]) + else + irb = Irb.new + end + irb.run(@CONF) end - irb.run(@CONF) - end - # Quits irb - def IRB.irb_exit(*) # :nodoc: - throw :IRB_EXIT, false - end + # Quits irb + def irb_exit(*) # :nodoc: + throw :IRB_EXIT, false + end - # Aborts then interrupts irb. - # - # Will raise an Abort exception, or the given `exception`. - def IRB.irb_abort(irb, exception = Abort) # :nodoc: - irb.context.thread.raise exception, "abort then interrupt!" + # Aborts then interrupts irb. + # + # Will raise an Abort exception, or the given `exception`. + def irb_abort(irb, exception = Abort) # :nodoc: + irb.context.thread.raise exception, "abort then interrupt!" + end end class Irb diff --git a/lib/irb/command/base.rb b/lib/irb/command/base.rb index 1d406630a..af810ed34 100644 --- a/lib/irb/command/base.rb +++ b/lib/irb/command/base.rb @@ -10,8 +10,10 @@ module IRB module Command class CommandArgumentError < StandardError; end - def self.extract_ruby_args(*args, **kwargs) - throw :EXTRACT_RUBY_ARGS, [args, kwargs] + class << self + def extract_ruby_args(*args, **kwargs) + throw :EXTRACT_RUBY_ARGS, [args, kwargs] + end end class Base @@ -31,6 +33,12 @@ def help_message(help_message = nil) @help_message end + def execute(irb_context, arg) + new(irb_context).execute(arg) + rescue CommandArgumentError => e + puts e.message + end + private def highlight(text) @@ -38,12 +46,6 @@ def highlight(text) 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 diff --git a/lib/irb/command/debug.rb b/lib/irb/command/debug.rb index 8a091a49e..3ebb57fe5 100644 --- a/lib/irb/command/debug.rb +++ b/lib/irb/command/debug.rb @@ -58,13 +58,15 @@ def execute_debug_command(pre_cmds: nil, do_cmds: nil) end class DebugCommand < Debug - def self.category - "Debugging" - end + class << self + def 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." + def 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/default_commands.rb b/lib/irb/default_commands.rb index e27a3d4e0..fec41df4e 100644 --- a/lib/irb/default_commands.rb +++ b/lib/irb/default_commands.rb @@ -259,10 +259,12 @@ module ExtendCommandBundle # Deprecated. Doesn't have any effect. @EXTEND_COMMANDS = [] - # Drepcated. Use Command.regiser instead. - def self.def_extend_command(cmd_name, cmd_class, _, *aliases) - Command._register_with_aliases(cmd_name, cmd_class, *aliases) - Command.class_variable_set(:@@command_override_policies, nil) + class << self + # Drepcated. Use Command.regiser instead. + def def_extend_command(cmd_name, cmd_class, _, *aliases) + Command._register_with_aliases(cmd_name, cmd_class, *aliases) + Command.class_variable_set(:@@command_override_policies, nil) + end end end end diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index ced35a2c5..f6b8d00e5 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -171,11 +171,13 @@ def close end class ReadlineInputMethod < StdioInputMethod - def self.initialize_readline - require "readline" - rescue LoadError - else - include ::Readline + class << self + def initialize_readline + require "readline" + rescue LoadError + else + include ::Readline + end end include HistorySavingAbility diff --git a/lib/irb/inspector.rb b/lib/irb/inspector.rb index 667087ccb..8046744f8 100644 --- a/lib/irb/inspector.rb +++ b/lib/irb/inspector.rb @@ -6,7 +6,6 @@ module IRB # :nodoc: - # Convenience method to create a new Inspector, using the given +inspect+ # proc, and optional +init+ proc and passes them to Inspector.new # @@ -43,38 +42,40 @@ class Inspector # +:marshal+:: Using Marshal.dump INSPECTORS = {} - # Determines the inspector to use where +inspector+ is one of the keys passed - # during inspector definition. - def self.keys_with_inspector(inspector) - INSPECTORS.select{|k, v| v == inspector}.collect{|k, v| k} - end - - # Example - # - # Inspector.def_inspector(key, init_p=nil){|v| v.inspect} - # Inspector.def_inspector([key1,..], init_p=nil){|v| v.inspect} - # Inspector.def_inspector(key, inspector) - # Inspector.def_inspector([key1,...], inspector) - def self.def_inspector(key, arg=nil, &block) - if block_given? - inspector = IRB::Inspector(block, arg) - else - inspector = arg + class << self + # Determines the inspector to use where +inspector+ is one of the keys passed + # during inspector definition. + def keys_with_inspector(inspector) + INSPECTORS.select{|k, v| v == inspector}.collect{|k, v| k} end - case key - when Array - for k in key - def_inspector(k, inspector) + # Example + # + # Inspector.def_inspector(key, init_p=nil){|v| v.inspect} + # Inspector.def_inspector([key1,..], init_p=nil){|v| v.inspect} + # Inspector.def_inspector(key, inspector) + # Inspector.def_inspector([key1,...], inspector) + def def_inspector(key, arg=nil, &block) + if block_given? + inspector = IRB::Inspector(block, arg) + else + inspector = arg + end + + case key + when Array + for k in key + def_inspector(k, inspector) + end + when Symbol + INSPECTORS[key] = inspector + INSPECTORS[key.to_s] = inspector + when String + INSPECTORS[key] = inspector + INSPECTORS[key.intern] = inspector + else + INSPECTORS[key] = inspector end - when Symbol - INSPECTORS[key] = inspector - INSPECTORS[key.to_s] = inspector - when String - INSPECTORS[key] = inspector - INSPECTORS[key.intern] = inspector - else - INSPECTORS[key] = inspector end end diff --git a/lib/irb/nesting_parser.rb b/lib/irb/nesting_parser.rb index 5aa940cc2..fc71d64ae 100644 --- a/lib/irb/nesting_parser.rb +++ b/lib/irb/nesting_parser.rb @@ -3,235 +3,237 @@ module IRB module NestingParser IGNORE_TOKENS = %i[on_sp on_ignored_nl on_comment on_embdoc_beg on_embdoc on_embdoc_end] - # Scan each token and call the given block with array of token and other information for parsing - def self.scan_opens(tokens) - opens = [] - pending_heredocs = [] - first_token_on_line = true - tokens.each do |t| - skip = false - last_tok, state, args = opens.last - case state - when :in_alias_undef - skip = t.event == :on_kw - when :in_unquoted_symbol - unless IGNORE_TOKENS.include?(t.event) - opens.pop - skip = true - end - when :in_lambda_head - opens.pop if t.event == :on_tlambeg || (t.event == :on_kw && t.tok == 'do') - when :in_method_head - unless IGNORE_TOKENS.include?(t.event) - next_args = [] - body = nil - if args.include?(:receiver) - case t.event - when :on_lparen, :on_ivar, :on_gvar, :on_cvar - # def (receiver). | def @ivar. | def $gvar. | def @@cvar. - next_args << :dot - when :on_kw - case t.tok - when 'self', 'true', 'false', 'nil' - # def self(arg) | def self. - next_args.push(:arg, :dot) - else - # def if(arg) + class << self + # Scan each token and call the given block with array of token and other information for parsing + def scan_opens(tokens) + opens = [] + pending_heredocs = [] + first_token_on_line = true + tokens.each do |t| + skip = false + last_tok, state, args = opens.last + case state + when :in_alias_undef + skip = t.event == :on_kw + when :in_unquoted_symbol + unless IGNORE_TOKENS.include?(t.event) + opens.pop + skip = true + end + when :in_lambda_head + opens.pop if t.event == :on_tlambeg || (t.event == :on_kw && t.tok == 'do') + when :in_method_head + unless IGNORE_TOKENS.include?(t.event) + next_args = [] + body = nil + if args.include?(:receiver) + case t.event + when :on_lparen, :on_ivar, :on_gvar, :on_cvar + # def (receiver). | def @ivar. | def $gvar. | def @@cvar. + next_args << :dot + when :on_kw + case t.tok + when 'self', 'true', 'false', 'nil' + # def self(arg) | def self. + next_args.push(:arg, :dot) + else + # def if(arg) + skip = true + next_args << :arg + end + when :on_op, :on_backtick + # def +(arg) skip = true next_args << :arg + when :on_ident, :on_const + # def a(arg) | def a. + next_args.push(:arg, :dot) end - when :on_op, :on_backtick - # def +(arg) - skip = true - next_args << :arg - when :on_ident, :on_const - # def a(arg) | def a. - next_args.push(:arg, :dot) end - end - if args.include?(:dot) - # def receiver.name - next_args << :name if t.event == :on_period || (t.event == :on_op && t.tok == '::') - end - if args.include?(:name) - if %i[on_ident on_const on_op on_kw on_backtick].include?(t.event) - # def name(arg) | def receiver.name(arg) - next_args << :arg - skip = true + if args.include?(:dot) + # def receiver.name + next_args << :name if t.event == :on_period || (t.event == :on_op && t.tok == '::') end - end - if args.include?(:arg) - case t.event - when :on_nl, :on_semicolon - # def receiver.f; - body = :normal - when :on_lparen - # def receiver.f() - next_args << :eq - else + if args.include?(:name) + if %i[on_ident on_const on_op on_kw on_backtick].include?(t.event) + # def name(arg) | def receiver.name(arg) + next_args << :arg + skip = true + end + end + if args.include?(:arg) + case t.event + when :on_nl, :on_semicolon + # def receiver.f; + body = :normal + when :on_lparen + # def receiver.f() + next_args << :eq + else + if t.event == :on_op && t.tok == '=' + # def receiver.f = + body = :oneliner + else + # def receiver.f arg + next_args << :arg_without_paren + end + end + end + if args.include?(:eq) if t.event == :on_op && t.tok == '=' - # def receiver.f = body = :oneliner else - # def receiver.f arg - next_args << :arg_without_paren + body = :normal end end - end - if args.include?(:eq) - if t.event == :on_op && t.tok == '=' - body = :oneliner - else - body = :normal + if args.include?(:arg_without_paren) + if %i[on_semicolon on_nl].include?(t.event) + # def f a; + body = :normal + else + # def f a, b + next_args << :arg_without_paren + end end - end - if args.include?(:arg_without_paren) - if %i[on_semicolon on_nl].include?(t.event) - # def f a; - body = :normal + if body == :oneliner + opens.pop + elsif body + opens[-1] = [last_tok, nil] else - # def f a, b - next_args << :arg_without_paren + opens[-1] = [last_tok, :in_method_head, next_args] end end - if body == :oneliner - opens.pop - elsif body + when :in_for_while_until_condition + if t.event == :on_semicolon || t.event == :on_nl || (t.event == :on_kw && t.tok == 'do') + skip = true if t.event == :on_kw && t.tok == 'do' opens[-1] = [last_tok, nil] - else - opens[-1] = [last_tok, :in_method_head, next_args] end end - when :in_for_while_until_condition - if t.event == :on_semicolon || t.event == :on_nl || (t.event == :on_kw && t.tok == 'do') - skip = true if t.event == :on_kw && t.tok == 'do' - opens[-1] = [last_tok, nil] - end - end - unless skip - case t.event - when :on_kw - case t.tok - when 'begin', 'class', 'module', 'do', 'case' - opens << [t, nil] - when 'end' - opens.pop - when 'def' - opens << [t, :in_method_head, [:receiver, :name]] - when 'if', 'unless' - unless t.state.allbits?(Ripper::EXPR_LABEL) + unless skip + case t.event + when :on_kw + case t.tok + when 'begin', 'class', 'module', 'do', 'case' opens << [t, nil] - end - when 'while', 'until' - unless t.state.allbits?(Ripper::EXPR_LABEL) - opens << [t, :in_for_while_until_condition] - end - when 'ensure', 'rescue' - unless t.state.allbits?(Ripper::EXPR_LABEL) + when 'end' + opens.pop + when 'def' + opens << [t, :in_method_head, [:receiver, :name]] + when 'if', 'unless' + unless t.state.allbits?(Ripper::EXPR_LABEL) + opens << [t, nil] + end + when 'while', 'until' + unless t.state.allbits?(Ripper::EXPR_LABEL) + opens << [t, :in_for_while_until_condition] + end + when 'ensure', 'rescue' + unless t.state.allbits?(Ripper::EXPR_LABEL) + opens.pop + opens << [t, nil] + end + when 'alias' + opens << [t, :in_alias_undef, 2] + when 'undef' + opens << [t, :in_alias_undef, 1] + when 'elsif', 'else', 'when' opens.pop opens << [t, nil] + when 'for' + opens << [t, :in_for_while_until_condition] + when 'in' + if last_tok&.event == :on_kw && %w[case in].include?(last_tok.tok) && first_token_on_line + opens.pop + opens << [t, nil] + end end - when 'alias' - opens << [t, :in_alias_undef, 2] - when 'undef' - opens << [t, :in_alias_undef, 1] - when 'elsif', 'else', 'when' + when :on_tlambda + opens << [t, :in_lambda_head] + when :on_lparen, :on_lbracket, :on_lbrace, :on_tlambeg, :on_embexpr_beg, :on_embdoc_beg + opens << [t, nil] + when :on_rparen, :on_rbracket, :on_rbrace, :on_embexpr_end, :on_embdoc_end + opens.pop + when :on_heredoc_beg + pending_heredocs << t + when :on_heredoc_end opens.pop + when :on_backtick + opens << [t, nil] if t.state.allbits?(Ripper::EXPR_BEG) + when :on_tstring_beg, :on_words_beg, :on_qwords_beg, :on_symbols_beg, :on_qsymbols_beg, :on_regexp_beg opens << [t, nil] - when 'for' - opens << [t, :in_for_while_until_condition] - when 'in' - if last_tok&.event == :on_kw && %w[case in].include?(last_tok.tok) && first_token_on_line - opens.pop + when :on_tstring_end, :on_regexp_end, :on_label_end + opens.pop + when :on_symbeg + if t.tok == ':' + opens << [t, :in_unquoted_symbol] + else opens << [t, nil] end end - when :on_tlambda - opens << [t, :in_lambda_head] - when :on_lparen, :on_lbracket, :on_lbrace, :on_tlambeg, :on_embexpr_beg, :on_embdoc_beg - opens << [t, nil] - when :on_rparen, :on_rbracket, :on_rbrace, :on_embexpr_end, :on_embdoc_end - opens.pop - when :on_heredoc_beg - pending_heredocs << t - when :on_heredoc_end - opens.pop - when :on_backtick - opens << [t, nil] if t.state.allbits?(Ripper::EXPR_BEG) - when :on_tstring_beg, :on_words_beg, :on_qwords_beg, :on_symbols_beg, :on_qsymbols_beg, :on_regexp_beg - opens << [t, nil] - when :on_tstring_end, :on_regexp_end, :on_label_end - opens.pop - when :on_symbeg - if t.tok == ':' - opens << [t, :in_unquoted_symbol] - else - opens << [t, nil] - end end + if t.event == :on_nl || t.event == :on_semicolon + first_token_on_line = true + elsif t.event != :on_sp + first_token_on_line = false + end + if pending_heredocs.any? && t.tok.include?("\n") + pending_heredocs.reverse_each { |t| opens << [t, nil] } + pending_heredocs = [] + end + if opens.last && opens.last[1] == :in_alias_undef && !IGNORE_TOKENS.include?(t.event) && t.event != :on_heredoc_end + tok, state, arg = opens.pop + opens << [tok, state, arg - 1] if arg >= 1 + end + yield t, opens if block_given? end - if t.event == :on_nl || t.event == :on_semicolon - first_token_on_line = true - elsif t.event != :on_sp - first_token_on_line = false - end - if pending_heredocs.any? && t.tok.include?("\n") - pending_heredocs.reverse_each { |t| opens << [t, nil] } - pending_heredocs = [] - end - if opens.last && opens.last[1] == :in_alias_undef && !IGNORE_TOKENS.include?(t.event) && t.event != :on_heredoc_end - tok, state, arg = opens.pop - opens << [tok, state, arg - 1] if arg >= 1 - end - yield t, opens if block_given? + opens.map(&:first) + pending_heredocs.reverse end - opens.map(&:first) + pending_heredocs.reverse - end - def self.open_tokens(tokens) - # scan_opens without block will return a list of open tokens at last token position - scan_opens(tokens) - end + def open_tokens(tokens) + # scan_opens without block will return a list of open tokens at last token position + scan_opens(tokens) + end - # Calculates token information [line_tokens, prev_opens, next_opens, min_depth] for each line. - # Example code - # ["hello - # world"+( - # First line - # line_tokens: [[lbracket, '['], [tstring_beg, '"'], [tstring_content("hello\nworld"), "hello\n"]] - # prev_opens: [] - # next_tokens: [lbracket, tstring_beg] - # min_depth: 0 (minimum at beginning of line) - # Second line - # line_tokens: [[tstring_content("hello\nworld"), "world"], [tstring_end, '"'], [op, '+'], [lparen, '(']] - # prev_opens: [lbracket, tstring_beg] - # next_tokens: [lbracket, lparen] - # min_depth: 1 (minimum just after tstring_end) - def self.parse_by_line(tokens) - line_tokens = [] - prev_opens = [] - min_depth = 0 - output = [] - last_opens = scan_opens(tokens) do |t, opens| - depth = t == opens.last&.first ? opens.size - 1 : opens.size - min_depth = depth if depth < min_depth - if t.tok.include?("\n") - t.tok.each_line do |line| - line_tokens << [t, line] - next if line[-1] != "\n" - next_opens = opens.map(&:first) - output << [line_tokens, prev_opens, next_opens, min_depth] - prev_opens = next_opens - min_depth = prev_opens.size - line_tokens = [] + # Calculates token information [line_tokens, prev_opens, next_opens, min_depth] for each line. + # Example code + # ["hello + # world"+( + # First line + # line_tokens: [[lbracket, '['], [tstring_beg, '"'], [tstring_content("hello\nworld"), "hello\n"]] + # prev_opens: [] + # next_tokens: [lbracket, tstring_beg] + # min_depth: 0 (minimum at beginning of line) + # Second line + # line_tokens: [[tstring_content("hello\nworld"), "world"], [tstring_end, '"'], [op, '+'], [lparen, '(']] + # prev_opens: [lbracket, tstring_beg] + # next_tokens: [lbracket, lparen] + # min_depth: 1 (minimum just after tstring_end) + def parse_by_line(tokens) + line_tokens = [] + prev_opens = [] + min_depth = 0 + output = [] + last_opens = scan_opens(tokens) do |t, opens| + depth = t == opens.last&.first ? opens.size - 1 : opens.size + min_depth = depth if depth < min_depth + if t.tok.include?("\n") + t.tok.each_line do |line| + line_tokens << [t, line] + next if line[-1] != "\n" + next_opens = opens.map(&:first) + output << [line_tokens, prev_opens, next_opens, min_depth] + prev_opens = next_opens + min_depth = prev_opens.size + line_tokens = [] + end + else + line_tokens << [t, t.tok] end - else - line_tokens << [t, t.tok] end + output << [line_tokens, prev_opens, last_opens, min_depth] if line_tokens.any? + output end - output << [line_tokens, prev_opens, last_opens, min_depth] if line_tokens.any? - output end end end diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb index f6ac7f0f5..3abb53b4e 100644 --- a/lib/irb/ruby-lex.rb +++ b/lib/irb/ruby-lex.rb @@ -36,29 +36,6 @@ class RubyLex :massign, ] - class TerminateLineInput < StandardError - def initialize - super("Terminate Line Input") - end - end - - def self.compile_with_errors_suppressed(code, line_no: 1) - begin - result = yield code, line_no - rescue ArgumentError - # Ruby can issue an error for the code if there is an - # incomplete magic comment for encoding in it. Force an - # expression with a new line before the code in this - # case to prevent magic comment handling. To make sure - # line numbers in the lexed code remain the same, - # decrease the line number by one. - code = ";\n#{code}" - line_no -= 1 - result = yield code, line_no - end - result - end - ERROR_TOKENS = [ :on_parse_error, :compile_error, @@ -68,70 +45,102 @@ def self.compile_with_errors_suppressed(code, line_no: 1) :on_param_error ] - def self.generate_local_variables_assign_code(local_variables) - "#{local_variables.join('=')}=nil;" unless local_variables.empty? + LTYPE_TOKENS = %i[ + on_heredoc_beg on_tstring_beg + on_regexp_beg on_symbeg on_backtick + on_symbols_beg on_qsymbols_beg + on_words_beg on_qwords_beg + ] + + class TerminateLineInput < StandardError + def initialize + super("Terminate Line Input") + end end - # Some part of the code is not included in Ripper's token. - # Example: DATA part, token after heredoc_beg when heredoc has unclosed embexpr. - # With interpolated tokens, tokens.map(&:tok).join will be equal to code. - def self.interpolate_ripper_ignored_tokens(code, tokens) - line_positions = [0] - code.lines.each do |line| - line_positions << line_positions.last + line.bytesize + class << self + def compile_with_errors_suppressed(code, line_no: 1) + begin + result = yield code, line_no + rescue ArgumentError + # Ruby can issue an error for the code if there is an + # incomplete magic comment for encoding in it. Force an + # expression with a new line before the code in this + # case to prevent magic comment handling. To make sure + # line numbers in the lexed code remain the same, + # decrease the line number by one. + code = ";\n#{code}" + line_no -= 1 + result = yield code, line_no + end + result + end + + def generate_local_variables_assign_code(local_variables) + "#{local_variables.join('=')}=nil;" unless local_variables.empty? end - prev_byte_pos = 0 - interpolated = [] - prev_line = 1 - tokens.each do |t| - line, col = t.pos - byte_pos = line_positions[line - 1] + col - if prev_byte_pos < byte_pos - tok = code.byteslice(prev_byte_pos...byte_pos) + + # Some part of the code is not included in Ripper's token. + # Example: DATA part, token after heredoc_beg when heredoc has unclosed embexpr. + # With interpolated tokens, tokens.map(&:tok).join will be equal to code. + def interpolate_ripper_ignored_tokens(code, tokens) + line_positions = [0] + code.lines.each do |line| + line_positions << line_positions.last + line.bytesize + end + prev_byte_pos = 0 + interpolated = [] + prev_line = 1 + tokens.each do |t| + line, col = t.pos + byte_pos = line_positions[line - 1] + col + if prev_byte_pos < byte_pos + tok = code.byteslice(prev_byte_pos...byte_pos) + pos = [prev_line, prev_byte_pos - line_positions[prev_line - 1]] + interpolated << Ripper::Lexer::Elem.new(pos, :on_ignored_by_ripper, tok, 0) + prev_line += tok.count("\n") + end + interpolated << t + prev_byte_pos = byte_pos + t.tok.bytesize + prev_line += t.tok.count("\n") + end + if prev_byte_pos < code.bytesize + tok = code.byteslice(prev_byte_pos..) pos = [prev_line, prev_byte_pos - line_positions[prev_line - 1]] interpolated << Ripper::Lexer::Elem.new(pos, :on_ignored_by_ripper, tok, 0) - prev_line += tok.count("\n") end - interpolated << t - prev_byte_pos = byte_pos + t.tok.bytesize - prev_line += t.tok.count("\n") - end - if prev_byte_pos < code.bytesize - tok = code.byteslice(prev_byte_pos..) - pos = [prev_line, prev_byte_pos - line_positions[prev_line - 1]] - interpolated << Ripper::Lexer::Elem.new(pos, :on_ignored_by_ripper, tok, 0) + interpolated end - interpolated - end - def self.ripper_lex_without_warning(code, local_variables: []) - verbose, $VERBOSE = $VERBOSE, nil - lvars_code = generate_local_variables_assign_code(local_variables) - original_code = code - if lvars_code - code = "#{lvars_code}\n#{code}" - line_no = 0 - else - line_no = 1 - end + def ripper_lex_without_warning(code, local_variables: []) + verbose, $VERBOSE = $VERBOSE, nil + lvars_code = generate_local_variables_assign_code(local_variables) + original_code = code + if lvars_code + code = "#{lvars_code}\n#{code}" + line_no = 0 + else + line_no = 1 + end - compile_with_errors_suppressed(code, line_no: line_no) do |inner_code, line_no| - lexer = Ripper::Lexer.new(inner_code, '-', line_no) - tokens = [] - lexer.scan.each do |t| - next if t.pos.first == 0 - prev_tk = tokens.last - position_overlapped = prev_tk && t.pos[0] == prev_tk.pos[0] && t.pos[1] < prev_tk.pos[1] + prev_tk.tok.bytesize - if position_overlapped - tokens[-1] = t if ERROR_TOKENS.include?(prev_tk.event) && !ERROR_TOKENS.include?(t.event) - else - tokens << t + compile_with_errors_suppressed(code, line_no: line_no) do |inner_code, line_no| + lexer = Ripper::Lexer.new(inner_code, '-', line_no) + tokens = [] + lexer.scan.each do |t| + next if t.pos.first == 0 + prev_tk = tokens.last + position_overlapped = prev_tk && t.pos[0] == prev_tk.pos[0] && t.pos[1] < prev_tk.pos[1] + prev_tk.tok.bytesize + if position_overlapped + tokens[-1] = t if ERROR_TOKENS.include?(prev_tk.event) && !ERROR_TOKENS.include?(t.event) + else + tokens << t + end end + interpolate_ripper_ignored_tokens(original_code, tokens) end - interpolate_ripper_ignored_tokens(original_code, tokens) + ensure + $VERBOSE = verbose end - ensure - $VERBOSE = verbose end def check_code_state(code, local_variables:) @@ -391,13 +400,6 @@ def process_indent_level(tokens, lines, line_index, is_newline) end end - LTYPE_TOKENS = %i[ - on_heredoc_beg on_tstring_beg - on_regexp_beg on_symbeg on_backtick - on_symbols_beg on_qsymbols_beg - on_words_beg on_qwords_beg - ] - def ltype_from_open_tokens(opens) start_token = opens.reverse_each.find do |tok| LTYPE_TOKENS.include?(tok.event) diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb index d24d1cc38..632b43243 100644 --- a/lib/irb/workspace.rb +++ b/lib/irb/workspace.rb @@ -176,11 +176,13 @@ def code_around_binding end module HelpersContainer - def self.install_helper_methods - HelperMethod.helper_methods.each do |name, helper_method_class| - define_method name do |*args, **opts, &block| - helper_method_class.instance.execute(*args, **opts, &block) - end unless method_defined?(name) + class << self + def install_helper_methods + HelperMethod.helper_methods.each do |name, helper_method_class| + define_method name do |*args, **opts, &block| + helper_method_class.instance.execute(*args, **opts, &block) + end unless method_defined?(name) + end end end diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index cd3f2c8f6..7ad8fd2fc 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -28,7 +28,6 @@ def teardown restore_encodings end - def test_eval_input verbose, $VERBOSE = $VERBOSE, nil input = TestInputMethod.new([ diff --git a/test/irb/test_workspace.rb b/test/irb/test_workspace.rb index 199ce95a3..ad515f91d 100644 --- a/test/irb/test_workspace.rb +++ b/test/irb/test_workspace.rb @@ -80,7 +80,6 @@ def test_code_around_binding_on_irb assert_equal(nil, workspace.code_around_binding) end - def test_toplevel_binding_local_variables bug17623 = '[ruby-core:102468]' bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] From 632da0ff295fe78b5e4d9647786972889f0fb4d5 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 18 Jul 2024 19:56:14 +0900 Subject: [PATCH 173/263] Clear ENV["XDG_CONFIG_HOME"] to avoid loading user-defined irbrc in TestIRB::ConfigValidationTest (#982) --- test/irb/helper.rb | 13 +++++++++++++ test/irb/test_command.rb | 7 ++----- test/irb/test_history.rb | 9 ++------- test/irb/test_init.rb | 8 +++----- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/test/irb/helper.rb b/test/irb/helper.rb index acaf6277f..ea2c6ef16 100644 --- a/test/irb/helper.rb +++ b/test/irb/helper.rb @@ -49,6 +49,19 @@ def ruby_core? !Pathname(__dir__).join("../../", "irb.gemspec").exist? end + def setup_envs(home:) + @backup_home = ENV["HOME"] + ENV["HOME"] = home + @backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME") + @backup_irbrc = ENV.delete("IRBRC") + end + + def teardown_envs + ENV["HOME"] = @backup_home + ENV["XDG_CONFIG_HOME"] = @backup_xdg_config_home + ENV["IRBRC"] = @backup_irbrc + end + def save_encodings @default_encoding = [Encoding.default_external, Encoding.default_internal] @stdio_encodings = [STDIN, STDOUT, STDERR].map {|io| [io.external_encoding, io.internal_encoding] } diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index 30c3f5ca2..567c3216c 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -15,9 +15,7 @@ def setup Dir.mkdir(@tmpdir) end Dir.chdir(@tmpdir) - @home_backup = ENV["HOME"] - ENV["HOME"] = @tmpdir - @xdg_config_home_backup = ENV.delete("XDG_CONFIG_HOME") + setup_envs(home: @tmpdir) save_encodings IRB.instance_variable_get(:@CONF).clear IRB.instance_variable_set(:@existing_rc_name_generators, nil) @@ -25,8 +23,7 @@ def setup end def teardown - ENV["XDG_CONFIG_HOME"] = @xdg_config_home_backup - ENV["HOME"] = @home_backup + teardown_envs Dir.chdir(@pwd) FileUtils.rm_rf(@tmpdir) restore_encodings diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index 63be35fda..84f043892 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -12,19 +12,14 @@ class HistoryTest < TestCase def setup @original_verbose, $VERBOSE = $VERBOSE, nil @tmpdir = Dir.mktmpdir("test_irb_history_") - @backup_home = ENV["HOME"] - @backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME") - @backup_irbrc = ENV.delete("IRBRC") + setup_envs(home: @tmpdir) @backup_default_external = Encoding.default_external - ENV["HOME"] = @tmpdir IRB.instance_variable_set(:@existing_rc_name_generators, nil) end def teardown IRB.instance_variable_set(:@existing_rc_name_generators, nil) - ENV["HOME"] = @backup_home - ENV["XDG_CONFIG_HOME"] = @backup_xdg_config_home - ENV["IRBRC"] = @backup_irbrc + teardown_envs Encoding.default_external = @backup_default_external $VERBOSE = @original_verbose FileUtils.rm_rf(@tmpdir) diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb index c423fa112..3e8d01c5c 100644 --- a/test/irb/test_init.rb +++ b/test/irb/test_init.rb @@ -270,17 +270,15 @@ def with_argv(argv) class ConfigValidationTest < TestCase def setup - @original_home = ENV["HOME"] - @original_irbrc = ENV["IRBRC"] # To prevent the test from using the user's .irbrc file - ENV["HOME"] = @home = Dir.mktmpdir + @home = Dir.mktmpdir + setup_envs(home: @home) super end def teardown super - ENV["IRBRC"] = @original_irbrc - ENV["HOME"] = @original_home + teardown_envs File.unlink(@irbrc) Dir.rmdir(@home) IRB.instance_variable_set(:@existing_rc_name_generators, nil) From 9ce6972e715e567236f03c8629d5095b7c1f00fe Mon Sep 17 00:00:00 2001 From: Ricardo Trindade Date: Sun, 4 Aug 2024 16:53:44 +0200 Subject: [PATCH 174/263] Remove Ruby version checks (#985) --- test/irb/test_irb.rb | 3 --- test/irb/test_nesting_parser.rb | 3 --- 2 files changed, 6 deletions(-) diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index e8d5be285..2913f3d48 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -526,9 +526,6 @@ def test_embdoc_indent end def test_heredoc_with_indent - if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.7.0') - pend 'This test needs Ripper::Lexer#scan to take broken tokens' - end input_with_correct_indents = [ [%q(<<~Q+<<~R), 0, 2, 1], [%q(a), 2, 2, 1], diff --git a/test/irb/test_nesting_parser.rb b/test/irb/test_nesting_parser.rb index 2482d4008..2db3cdab5 100644 --- a/test/irb/test_nesting_parser.rb +++ b/test/irb/test_nesting_parser.rb @@ -319,9 +319,6 @@ def test_undef_alias end def test_case_in - if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.7.0') - pend 'This test requires ruby version that supports case-in syntax' - end code = <<~EOS case 1 in 1 From b20590747a941c108d59ce29dbf8bee71293cb66 Mon Sep 17 00:00:00 2001 From: Alessandro Fazzi Date: Mon, 5 Aug 2024 01:20:26 +0200 Subject: [PATCH 175/263] Update COMPARED_WITH_PRY.md (#984) Document `cd object` and `cd ..` commands introduced in 1.14.0 by #971 --- COMPARED_WITH_PRY.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/COMPARED_WITH_PRY.md b/COMPARED_WITH_PRY.md index ef7ea1dec..3b24a41a2 100644 --- a/COMPARED_WITH_PRY.md +++ b/COMPARED_WITH_PRY.md @@ -4,19 +4,19 @@ Feel free to chip in and update this table - we appreciate your help! -| Feature | Pry | IRB | Note | -| ------------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | -| 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` | `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 | -| Navigation | - `cd object` to enter `object`
- `cd ..` to leave the current object
- `nesting` to list nesting levels | - `pushws object`
- `popws`
- `workspaces` | We plan to refine IRB's commands in the future | -| Runtime invocation | `binding.pry` | `binding.irb` | | -| Command system | Yes | No | Planned in [#513](https://github.com/ruby/irb/issues/513) and [#588](https://github.com/ruby/irb/issues/588) | -| Input history | [Comprehensive support](https://github.com/pry/pry/wiki/History) | Supports retrieving previous input and the `history` command | The `history` command doesn't support as many flags yet, but contributions are welcome. | -| Pager support | Command output and return value | Command output and return value | | -| Debugger integration | With `byebug` through [pry-byebug](https://github.com/deivid-rodriguez/pry-byebug) gem | Supports [`irb:rdbg`](https://github.com/ruby/irb#debugging-with-irb) sessions | | -| Rails console | Through [`pry-rails`](https://github.com/pry/pry-rails) gem | Rails' default | Rails console with IRB doesn't have commands like `show-routes` or `show-models` | +| Feature | Pry | IRB | Note | +| ------------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------- | +| 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` | `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 | +| Navigation | - `cd object` to enter `object`
- `cd ..` to leave the current object
- `nesting` to list nesting levels | - `pushws object`
- `popws`
- `workspaces`
- `cd object`/`cd ..` (since [1.14.0](https://github.com/ruby/irb/releases/tag/v1.14.0)) | We plan to refine IRB's commands in the future | +| Runtime invocation | `binding.pry` | `binding.irb` | | +| Command system | Yes | No | Planned in #513 and #588 | +| Input history | [Comprehensive support](https://github.com/pry/pry/wiki/History) | Supports retrieving previous input and the `history` command | The history command doesn't support as many flags yet, but contributions are welcome. | +| Pager support | Command output and return value | Command output and return value | | +| Debugger integration | With `byebug` through [pry-byebug](https://github.com/deivid-rodriguez/pry-byebug) gem | Supports [`irb:rdbg`](https://github.com/ruby/irb#debugging-with-irb) sessions | | +| Rails console | Through [`pry-rails`](https://github.com/pry/pry-rails) gem | Rails' default | Rails console with IRB doesn't have commands like `show-routes` or `show-models` | From ab394db93fd764a2d0a216a484224b1be1d29726 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 14 Aug 2024 13:06:02 +0900 Subject: [PATCH 176/263] Improve easter_egg logo resolution (#987) --- lib/irb/easter-egg.rb | 5 +- lib/irb/ruby_logo.aa | 112 ++++++++++++++++++++++++++++-------------- 2 files changed, 78 insertions(+), 39 deletions(-) diff --git a/lib/irb/easter-egg.rb b/lib/irb/easter-egg.rb index adc0834d5..439e5755b 100644 --- a/lib/irb/easter-egg.rb +++ b/lib/irb/easter-egg.rb @@ -100,7 +100,7 @@ def render_frame(i) private def easter_egg_logo(type) @easter_egg_logos ||= File.read(File.join(__dir__, 'ruby_logo.aa'), encoding: 'UTF-8:UTF-8') - .split(/TYPE: ([A-Z]+)\n/)[1..] + .split(/TYPE: ([A-Z_]+)\n/)[1..] .each_slice(2) .to_h @easter_egg_logos[type.to_s.upcase] @@ -112,7 +112,8 @@ def render_frame(i) when :logo require "rdoc" RDoc::RI::Driver.new.page do |io| - io.write easter_egg_logo(:large) + type = STDOUT.external_encoding == Encoding::UTF_8 ? :unicode_large : :ascii_large + io.write easter_egg_logo(type) end when :dancing STDOUT.cooked do diff --git a/lib/irb/ruby_logo.aa b/lib/irb/ruby_logo.aa index 61fe22c94..d0143a448 100644 --- a/lib/irb/ruby_logo.aa +++ b/lib/irb/ruby_logo.aa @@ -1,41 +1,41 @@ -TYPE: LARGE +TYPE: ASCII_LARGE - -+smJYYN?mm- - HB"BBYT TQg NggT - 9Q+g Nm,T 8g NJW - YS+ N2NJ"Sg N? - BQg #( gT Nggggk J - 5j NJ NJ NNge - #Q #JJ NgT N( - @j bj mT J - Bj @/d NJ ( - #q #(( NgT #J - 5d #(t mT $d - #q @(@J NJB; - @( 5d ? HHH H HQmgggggggmN qD - 5d #uN 2QdH E O - 5 5JSd Nd NJH @d j - Fd @J4d s NQH #d ( - #( #o6d Nd NgH #d #d - 4 B&Od v NgT #d F - #( 9JGd NH NgUd F - #d #GJQ d NP $ - #J #U+#Q N Q # j - j /W BQ+ BQ d NJ NJ - - NjJH HBIjTQggPJQgW N W k #J - #J b HYWgggN j s Nag d NN b #d - #J 5- D s Ngg N d Nd F - Fd BKH2 #+ s NNgg J Q J ] - F H @ J N y K(d P I - F4 E N? #d y #Q NJ E j - F W Nd q m Bg NxW N(H- - F d b @ m Hd gW vKJ - NJ d K d s Bg aT FDd - b # d N m BQ mV N> - e5 Nd #d NggggggQWH HHHH NJ - - m7 NW H N HSVO1z=?11- - NgTH bB kH WBHWWHBHWmQgg&gggggNNN - NNggggggNN + ,,,;;;;;;;;;;;;;;;;;;;;;;,, + ,,,;;;;;;;;;,, ,;;;' ''';;, + ,,;;;''' ';;;, ,,;;'' '';, + ,;;'' ;;;;;;;;,,,,,, ';; + ,;;'' ;;;;';;;'''';;;;;;;;;,,,;; + ,,;'' ;;;; ';;, ''''';;, + ,;;' ;;;' ';;, ;; + ,;;' ,;;; '';,, ;; + ,;;' ;;; ';;, ,;; + ;;' ;;;' '';,, ;;; + ,;' ;;;; ';;, ;;' + ,;;' ,;;;;' ,,,,,,,,,,,,;;;;; + ,;' ,;;;;;;;;;;;;;;;;;;;;'''''''';;; + ;;' ,;;;;;;;;;,, ;;;; + ;;' ,;;;'' ;;, ';;,, ,;;;; + ;;' ,;;;' ;; '';;, ,;';;; + ;;' ,;;;' ;;, '';;,, ,;',;;; + ,;;; ,;;;' ;; '';;,, ,;' ;;;' + ;;;; ,,;;;' ;;, ';;;' ;;; +,;;; ,;;;;' ;; ,;;; ;;; +;;;;; ,,;;;;;' ;;, ,;';; ;;; +;;;;;, ,,;;;;;;;' ;; ,;;' ;;; ;;; +;;;;;;;,,,,,,,;;;;;;;;;;;;;;,,, ;;, ,;' ;; ;;; +;;' ;;;;;;;;;;'''' ,;';; ''';;;;,,, ;; ,;; ;; ;;; +;; ;;;'' ;; ';; ''';;;;,,,, ;;, ,;;' ;;, ;; +;; ;;;;, ;;' ';; ''';;;;,,;;;;' ';; ;; +;;;;;;';, ,;; ;; '';;;;, ;;,;; +;;; ;; ;;, ;; ;; ,;;' ';;, ;;;;; +;; ;;; ;;, ;;' ;; ,,;'' ';;, ;;;;; +;; ;; ;; ;; ;; ,;;' '';, ;;;; +;;,;; ;; ;;' ;; ,;;'' ';,, ;;;' + ;;;; ';; ,;; ;;,,;;'' ';;, ;;; + ';;; ';; ;; ,;;;;;;;;;;;;;,,,,,,,,,,,, ';;;;; + ';, ';,;;' ,,,;;'' '''''''';;;;;;;;;;;;;;;;;;; + ';;,,, ;;;; ,,,,;;;;;;,,,,,;;;;;;;;;;;;;;;;;;;'''''''''''''' + ''';;;;;;;;;;;;;;''''''''''''''' TYPE: ASCII ,,,;;;;''''';;;'';, ,,;'' ';;,;;; ', @@ -57,6 +57,44 @@ TYPE: ASCII ;;; '; ;' ';,,'' ';,;; '; ';,; ,,;''''''''';;;;;;,,;;; ';,,;;,,;;;;;;;;;;'''''''''''''' +TYPE: UNICODE_LARGE + + ⣀⣤⣴⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣤⣄⡀ + ⢀⣀⣤⣴⣾⣿⣿⣿⠿⣿⣿⣿⣿⣦⣀ ⢀⣤⣶⣿⠿⠛⠁⠈⠉⠙⠻⢿⣷⣦⡀ + ⢀⣠⣴⣾⡿⠿⠛⠉⠉ ⠈⠙⢿⣿⣷⣤⡀ ⣠⣴⣾⡿⠟⠉ ⠉⠻⣿⣦ + ⢀⣤⣶⣿⠟⠋⠁ ⢿⣿⣿⣿⣿⣿⣿⣧⣤⣤⣤⣀⣀⣀⡀ ⠘⢿⣷⡀ + ⢀⣠⣾⡿⠟⠉ ⢸⣿⣿⣿⠟⢿⣿⣯⡙⠛⠛⠛⠿⠿⠿⢿⣿⣿⣶⣶⣶⣦⣤⣬⣿⣧ + ⣠⣴⣿⠟⠋ ⢸⣿⣿⡿ ⠈⠻⣿⣶⣄ ⠉⠉⠉⠙⠛⢻⣿⡆ + ⣠⣾⡿⠛⠁ ⣼⣿⣿⠃ ⠈⠙⢿⣷⣤⡀ ⠈⣿⡇ + ⣠⣾⡿⠋ ⢠⣿⣿⡏ ⠙⠻⣿⣦⣀ ⣿⡇ + ⣠⣾⡿⠋ ⢀⣿⣿⣿ ⠈⠛⢿⣷⣄⡀ ⢠⣿⡇ + ⢀⣾⡿⠋ ⢀⣾⣿⣿⠇ ⠙⠻⣿⣦⣀ ⢸⣿⡇ + ⢀⣴⣿⠟⠁ ⢀⣾⣿⣿⡟ ⠈⠻⢿⣷⣄ ⣾⣿⠇ + ⢠⣾⡿⠃ ⣠⣿⣿⣿⣿⠃ ⣀⣀⣀⣀⣀⣀⣀⣀⣤⣤⣤⣤⣽⣿⣿⣿⣿ + ⣰⣿⠟ ⣴⣿⣿⣿⣿⣿⣶⣶⣿⣿⣿⣿⣿⠿⠿⠿⠿⠿⠿⠿⠿⠛⠛⠛⠛⠛⠛⠛⠛⣿⣿⣿ + ⣼⣿⠏ ⢠⣾⣿⣿⣿⡿⣿⣿⢿⣷⣦⣄ ⣼⣿⣿⣿ + ⣼⣿⠃ ⢀⣴⣿⣿⣿⠟⠋ ⢸⣿⡆⠈⠛⠿⣿⣦⣄⡀ ⣰⣿⣿⣿⡇ + ⢀⣾⣿⠃ ⢀⣴⣿⣿⣿⠟⠁ ⣿⣷ ⠈⠙⠻⣿⣶⣄⡀ ⣰⣿⠟⣿⣿⡇ + ⢀⣾⣿⠇ ⢀⣴⣿⣿⣿⠟⠁ ⢸⣿⡆ ⠙⠻⢿⣷⣤⣀ ⣰⣿⠏⢠⣿⣿⡇ + ⢠⣿⣿⡟ ⢀⣴⣿⣿⡿⠛⠁ ⣿⣷ ⠉⠻⢿⣷⣦⣀ ⣴⣿⠏ ⢸⣿⣿⠃ + ⣿⣿⣿⡇ ⣠⣴⣿⣿⡿⠋ ⢸⣿⡆ ⠈⠛⢿⣿⣿⠃ ⢸⣿⣿ +⢠⣿⣿⣿ ⢀⣴⣾⣿⣿⡿⠋ ⠈⣿⣧ ⢠⣾⣿⣿ ⢸⣿⣿ +⢸⣿⣿⣿⡇ ⣀⣴⣾⣿⣿⣿⡿⠋ ⢹⣿⡆ ⣴⣿⠟⢹⣿⡀ ⢸⣿⡿ +⢸⣿⡟⣿⣿⣄ ⣀⣤⣶⣿⣿⣿⣿⣿⡟⠉ ⠈⣿⣷ ⢠⣾⡿⠋ ⢸⣿⡇ ⣼⣿⡇ +⢸⣿⡇⢹⣿⣿⣷⣦⣤⣤⣤⣤⣤⣴⣶⣾⣿⣿⣿⣿⡿⠿⣿⣿⣿⣿⣷⣶⣤⣤⣀⡀ ⢹⣿⡆ ⢀⣴⣿⠟ ⣿⣧ ⣿⣿⡇ +⢸⣿⠃ ⢿⣿⣿⣿⣿⣿⣿⡿⠿⠿⠛⠛⠉⠉⠁ ⢰⣿⠟⣿⣷⡀⠉⠙⠛⠿⢿⣿⣶⣦⣤⣀⡀ ⠈⣿⣷ ⣠⣿⡿⠁ ⢿⣿ ⣿⣿⡇ +⢸⣿ ⢀⣾⣿⣿⠋⠉⠁ ⢀⣿⡿ ⠘⣿⣷⡀ ⠉⠙⠛⠿⠿⣿⣶⣦⣤⣄⣀ ⢹⣿⡄ ⣠⣾⡿⠋ ⢸⣿⡆ ⣿⣿ +⣸⣿⢀⣾⣿⣿⣿⣆ ⣸⣿⠃ ⠘⢿⣷⡀ ⠈⠉⠛⠻⠿⣿⣷⣶⣤⣌⣿⣷⣾⡿⠋ ⠘⣿⡇ ⣿⣿ +⣿⣿⣾⡿⣿⡿⠹⣿⡆ ⢠⣿⡏ ⠈⢿⣷⡀ ⠈⠉⠙⣻⣿⣿⣿⣀ ⣿⣷⢰⣿⣿ +⣿⣿⡿⢁⣿⡇ ⢻⣿⡄ ⣾⣿ ⠈⢿⣷⡀ ⢀⣤⣾⡿⠋⠈⠻⢿⣷⣄ ⢻⣿⢸⣿⡟ +⣿⣿⠁⢸⣿⡇ ⢻⣿⡄ ⢸⣿⠇ ⠈⢿⣷⡀ ⣀⣴⣿⠟⠋ ⠙⢿⣷⣤⡀ ⢸⣿⣿⣿⡇ +⣿⣿ ⢸⣿⠁ ⠈⢿⣷⡀ ⢀⣿⡟ ⠈⢿⣷⡀ ⢀⣤⣾⡿⠛⠁ ⠙⠻⣿⣦⡀ ⠈⣿⣿⣿⡇ +⢸⣿⡄⣿⣿ ⠈⣿⣷⡀ ⣼⣿⠃ ⠈⢿⣷⡀ ⢀⣠⣶⣿⠟⠋ ⠈⠻⣿⣦⣄ ⣿⣿⣿⠇ +⠈⣿⣷⣿⡿ ⠘⣿⣧ ⢠⣿⡏ ⠈⢿⣷⣄⣤⣶⣿⠟⠋ ⠈⠛⢿⣷⣄ ⢸⣿⣿ + ⠘⣿⣿⡇ ⠘⣿⣧ ⣾⣿ ⢀⣠⣼⣿⣿⣿⣿⣿⣷⣶⣶⣶⣶⣶⣶⣤⣤⣤⣤⣤⣤⣀⣀⣀⣀⣀⣀⡀ ⠙⢿⣷⣼⣿⣿ + ⠈⠻⣿⣦⡀ ⠹⣿⣆⢸⣿⠇ ⣀⣠⣴⣾⡿⠟⠋⠁ ⠉⠉⠉⠉⠉⠉⠛⠛⣛⣛⣛⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣿⣿⡿ + ⠈⠻⢿⣷⣦⣄⣀⡀ ⢹⣿⣿⡟ ⢀⣀⣀⣤⣤⣶⣾⣿⣿⣿⣯⣥⣤⣤⣤⣤⣶⣶⣶⣶⣶⣶⣶⣾⣿⣿⣿⣿⠿⠿⠿⠿⠿⠿⠿⠟⠛⠛⠛⠛⠛⠛⠛⠉⠉⠉⠉⠉⠉ + ⠉⠙⠛⠿⠿⠿⣿⣿⣿⣿⠿⠿⠿⠿⠿⠿⠿⠛⠛⠛⠛⠛⠛⠛⠋⠉⠉⠉⠉⠉⠉⠉ TYPE: UNICODE ⣀⣤⣴⣾⣿⣿⣿⡛⠛⠛⠛⠛⣻⣿⠿⠛⠛⠶⣤⡀ ⣀⣴⠾⠛⠉⠁ ⠙⣿⣶⣤⣶⣟⣉ ⠈⠻⣦ From 949f032e9bae29b38afc66cb7d2142d34d3784ac Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 15 Aug 2024 03:06:50 +0900 Subject: [PATCH 177/263] Fix kill pager pid throwing Errno::ESRCH when pager process already terminated (#989) --- lib/irb/pager.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/irb/pager.rb b/lib/irb/pager.rb index 3391b32c6..558318cdb 100644 --- a/lib/irb/pager.rb +++ b/lib/irb/pager.rb @@ -33,7 +33,11 @@ def page(retain_content: false) # the `IRB::Abort` exception only interrupts IRB's execution but doesn't affect the pager # So to properly terminate the pager with Ctrl-C, we need to catch `IRB::Abort` and kill the pager process rescue IRB::Abort - Process.kill("TERM", pid) if pid + begin + Process.kill("TERM", pid) if pid + rescue Errno::ESRCH + # Pager process already terminated + end nil rescue Errno::EPIPE end From 9a4487af612f5b155d6e9c045ba40d7be98d0bc3 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 27 Aug 2024 13:49:17 +0100 Subject: [PATCH 178/263] Move parse_command method to Context (#993) Since Context dictates whether a line is a command or an expression, moving the parse_command method to Context makes the relationship more explicit. --- lib/irb.rb | 31 ++----------------------------- lib/irb/context.rb | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 3d45fa89d..213e23117 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1131,7 +1131,7 @@ def build_statement(code) end code.force_encoding(@context.io.encoding) - if (command, arg = parse_command(code)) + if (command, arg = @context.parse_command(code)) command_class = Command.load_command(command) Statement::Command.new(code, command_class, arg) else @@ -1140,35 +1140,8 @@ def build_statement(code) end end - ASSIGN_OPERATORS_REGEXP = Regexp.union(%w[= += -= *= /= %= **= &= |= &&= ||= ^= <<= >>=]) - - def parse_command(code) - command_name, arg = code.strip.split(/\s+/, 2) - return unless code.lines.size == 1 && command_name - - arg ||= '' - command = command_name.to_sym - # Command aliases are always command. example: $, @ - if (alias_name = @context.command_aliases[command]) - return [alias_name, arg] - end - - # Assignment-like expression is not a command - return if arg.start_with?(ASSIGN_OPERATORS_REGEXP) && !arg.start_with?(/==|=~/) - - # Local variable have precedence over command - return if @context.local_variables.include?(command) - - # Check visibility - public_method = !!Kernel.instance_method(:public_method).bind_call(@context.main, command) rescue false - private_method = !public_method && !!Kernel.instance_method(:method).bind_call(@context.main, command) rescue false - if Command.execute_as_command?(command, public_method: public_method, private_method: private_method) - [command, arg] - end - end - def command?(code) - !!parse_command(code) + !!@context.parse_command(code) end def configure_io diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 74a08ca69..aa0c60b0e 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -13,6 +13,7 @@ module IRB # A class that wraps the current state of the irb session, including the # configuration of IRB.conf. class Context + ASSIGN_OPERATORS_REGEXP = Regexp.union(%w[= += -= *= /= %= **= &= |= &&= ||= ^= <<= >>=]) # Creates a new IRB context. # # The optional +input_method+ argument: @@ -635,6 +636,31 @@ def evaluate_expression(code, line_no) # :nodoc: result end + def parse_command(code) + command_name, arg = code.strip.split(/\s+/, 2) + return unless code.lines.size == 1 && command_name + + arg ||= '' + command = command_name.to_sym + # Command aliases are always command. example: $, @ + if (alias_name = command_aliases[command]) + return [alias_name, arg] + end + + # Assignment-like expression is not a command + return if arg.start_with?(ASSIGN_OPERATORS_REGEXP) && !arg.start_with?(/==|=~/) + + # Local variable have precedence over command + return if local_variables.include?(command) + + # Check visibility + public_method = !!Kernel.instance_method(:public_method).bind_call(main, command) rescue false + private_method = !public_method && !!Kernel.instance_method(:method).bind_call(main, command) rescue false + if Command.execute_as_command?(command, public_method: public_method, private_method: private_method) + [command, arg] + end + end + def inspect_last_value # :nodoc: @inspect_method.inspect_value(@last_value) end From 0e64136e766f99549faeedb46e04afebdc6655ac Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 29 Aug 2024 01:16:27 +0900 Subject: [PATCH 179/263] Colorize command input (#983) --- lib/irb/input-method.rb | 8 +++++++- test/irb/test_input_method.rb | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index f6b8d00e5..82c1e7329 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -270,7 +270,13 @@ def initialize(completor) proc do |output, complete: | next unless IRB::Color.colorable? lvars = IRB.CurrentContext&.local_variables || [] - IRB::Color.colorize_code(output, complete: complete, local_variables: lvars) + if IRB.CurrentContext&.parse_command(output) + name, sep, arg = output.split(/(\s+)/, 2) + arg = IRB::Color.colorize_code(arg, complete: complete, local_variables: lvars) + "#{IRB::Color.colorize(name, [:BOLD])}\e[m#{sep}#{arg}" + else + IRB::Color.colorize_code(output, complete: complete, local_variables: lvars) + end end else proc do |output| diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb index ce317b4b3..078631db8 100644 --- a/test/irb/test_input_method.rb +++ b/test/irb/test_input_method.rb @@ -8,6 +8,7 @@ module TestIRB class InputMethodTest < TestCase def setup @conf_backup = IRB.conf.dup + IRB.init_config(nil) IRB.conf[:LC_MESSAGES] = IRB::Locale.new save_encodings end @@ -33,6 +34,21 @@ def test_initialization assert_not_nil Reline.dig_perfect_match_proc end + def test_colorize + original_colorable = IRB::Color.method(:colorable?) + IRB::Color.instance_eval { undef :colorable? } + IRB::Color.define_singleton_method(:colorable?) { true } + workspace = IRB::WorkSpace.new(binding) + input_method = IRB::RelineInputMethod.new(IRB::RegexpCompletor.new) + IRB.conf[:MAIN_CONTEXT] = IRB::Irb.new(workspace, input_method).context + assert_equal "\e[1m$\e[0m\e[m", Reline.output_modifier_proc.call('$', complete: false) + assert_equal "\e[1m$\e[0m\e[m \e[34m\e[1m1\e[0m + \e[34m\e[1m2\e[0m", Reline.output_modifier_proc.call('$ 1 + 2', complete: false) + assert_equal "\e[32m\e[1m$a\e[0m", Reline.output_modifier_proc.call('$a', complete: false) + ensure + IRB::Color.instance_eval { undef :colorable? } + IRB::Color.define_singleton_method(:colorable?, original_colorable) + end + def test_initialization_without_use_autocomplete original_show_doc_proc = Reline.dialog_proc(:show_doc)&.dialog_proc empty_proc = Proc.new {} From 985ac509c1d026f675f0aa8d5ffcb1139a2bbd5c Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 29 Aug 2024 03:07:37 +0900 Subject: [PATCH 180/263] Make colorize test pass with NO_COLOR env set (#994) --- test/irb/test_input_method.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb index 078631db8..fdf348341 100644 --- a/test/irb/test_input_method.rb +++ b/test/irb/test_input_method.rb @@ -35,6 +35,7 @@ def test_initialization end def test_colorize + IRB.conf[:USE_COLORIZE] = true original_colorable = IRB::Color.method(:colorable?) IRB::Color.instance_eval { undef :colorable? } IRB::Color.define_singleton_method(:colorable?) { true } From 06eb0bff1750c5a082b0a5d8e97dc27058b74a16 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 29 Aug 2024 19:22:28 +0900 Subject: [PATCH 181/263] =?UTF-8?q?Surpressing=20'unknown=20command:=20?= =?UTF-8?q?=E2=80=9CSwitch=20to=20inspect=20mode.=E2=80=9D'=20message=20(#?= =?UTF-8?q?995)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/irb/test_input_method.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb index fdf348341..9d0f393ce 100644 --- a/test/irb/test_input_method.rb +++ b/test/irb/test_input_method.rb @@ -36,6 +36,7 @@ def test_initialization def test_colorize IRB.conf[:USE_COLORIZE] = true + IRB.conf[:VERBOSE] = false original_colorable = IRB::Color.method(:colorable?) IRB::Color.instance_eval { undef :colorable? } IRB::Color.define_singleton_method(:colorable?) { true } From 87d0754609dff88232a1074bfd9f993c33c95488 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 29 Aug 2024 17:41:16 +0900 Subject: [PATCH 182/263] Skip show_doc tests if RDoc is not available --- test/irb/test_command.rb | 2 +- test/irb/test_input_method.rb | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index 567c3216c..69931e3e4 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -765,7 +765,7 @@ def test_show_doc ensure # this is the only way to reset the redefined method without coupling the test with its implementation EnvUtil.suppress_warning { load "irb/command/help.rb" } - end + end if defined?(RDoc) def test_show_doc_without_rdoc _, err = without_rdoc do diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb index 9d0f393ce..915297f56 100644 --- a/test/irb/test_input_method.rb +++ b/test/irb/test_input_method.rb @@ -1,7 +1,10 @@ # frozen_string_literal: false require "irb" -require "rdoc" +begin + require "rdoc" +rescue LoadError +end require_relative "helper" module TestIRB @@ -188,4 +191,4 @@ def has_rdoc_content? File.exist?(RDoc::RI::Paths::BASE) end end -end +end if defined?(RDoc) From 30fa1595d9c592778336a0b41ac5d1813548e11e Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 4 Sep 2024 00:45:37 +0900 Subject: [PATCH 183/263] Fix easter_egg run without RDoc, fix input-method test run without RDoc (#998) * EasterEgg no longer depend on RDoc * Run most of the input-method tests even if RDoc is not avialable --- lib/irb/easter-egg.rb | 12 +++++++----- test/irb/test_input_method.rb | 5 +++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/irb/easter-egg.rb b/lib/irb/easter-egg.rb index 439e5755b..14dc93fc9 100644 --- a/lib/irb/easter-egg.rb +++ b/lib/irb/easter-egg.rb @@ -107,13 +107,14 @@ def render_frame(i) end private def easter_egg(type = nil) + print "\e[?1049h" type ||= [:logo, :dancing].sample case type when :logo - require "rdoc" - RDoc::RI::Driver.new.page do |io| - type = STDOUT.external_encoding == Encoding::UTF_8 ? :unicode_large : :ascii_large - io.write easter_egg_logo(type) + Pager.page do |io| + logo_type = STDOUT.external_encoding == Encoding::UTF_8 ? :unicode_large : :ascii_large + io.write easter_egg_logo(logo_type) + STDIN.raw { STDIN.getc } if io == STDOUT end when :dancing STDOUT.cooked do @@ -138,10 +139,11 @@ def render_frame(i) end rescue Interrupt ensure - print "\e[0m\e[?1049l" trap("SIGINT", prev_trap) end end + ensure + print "\e[0m\e[?1049l" end end end diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb index 915297f56..bd107551d 100644 --- a/test/irb/test_input_method.rb +++ b/test/irb/test_input_method.rb @@ -70,6 +70,7 @@ def test_initialization_without_use_autocomplete end def test_initialization_with_use_autocomplete + omit 'This test requires RDoc' unless defined?(RDoc) original_show_doc_proc = Reline.dialog_proc(:show_doc)&.dialog_proc empty_proc = Proc.new {} Reline.add_dialog_proc(:show_doc, empty_proc) @@ -190,5 +191,5 @@ def test_perfect_matching_handles_nil_namespace def has_rdoc_content? File.exist?(RDoc::RI::Paths::BASE) end - end -end if defined?(RDoc) + end if defined?(RDoc) +end From bd06069075749c286392a9a0b6404a2d99b87a66 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 7 Sep 2024 01:06:08 +0900 Subject: [PATCH 184/263] Specify commit hash of yamatanooroti (#1000) --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 3c2efa44d..772c8fbd2 100644 --- a/Gemfile +++ b/Gemfile @@ -7,7 +7,7 @@ is_truffleruby = RUBY_DESCRIPTION =~ /truffleruby/ if is_unix && ENV['WITH_VTERM'] gem "vterm", github: "ruby/vterm-gem" - gem "yamatanooroti", github: "ruby/yamatanooroti" + gem "yamatanooroti", github: "ruby/yamatanooroti", ref: "f6e47192100d6089f70cf64c1de540dcaadf005a" end gem "stackprof" if is_unix && !is_truffleruby From f256d7899fe448a5e0de0771fc415475f006b3fa Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 13 Sep 2024 00:04:33 +0900 Subject: [PATCH 185/263] Remove KEYWORD_ALIASES which handled special alias name of irb_break irb_catch and irb_next command (#1004) * Remove KEYWORD_ALIASES which handled special alias name of irb_break irb_catch and irb_next command * Remove unused instance variable user_aliases Co-authored-by: Stan Lo --------- Co-authored-by: Stan Lo --- lib/irb/command/help.rb | 2 +- lib/irb/context.rb | 14 +------------- lib/irb/default_commands.rb | 12 +++++++++--- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/lib/irb/command/help.rb b/lib/irb/command/help.rb index c2018f9b3..12b468fef 100644 --- a/lib/irb/command/help.rb +++ b/lib/irb/command/help.rb @@ -39,7 +39,7 @@ def help_message 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| + aliases = irb_context.instance_variable_get(:@command_aliases).map do |alias_name, target| { display_name: alias_name, description: "Alias for `#{target}`" } end diff --git a/lib/irb/context.rb b/lib/irb/context.rb index aa0c60b0e..fafe99d57 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -149,8 +149,7 @@ def initialize(irb, workspace = nil, input_method = nil) @newline_before_multiline_output = true end - @user_aliases = IRB.conf[:COMMAND_ALIASES].dup - @command_aliases = @user_aliases.merge(KEYWORD_ALIASES) + @command_aliases = IRB.conf[:COMMAND_ALIASES].dup end private def term_interactive? @@ -158,17 +157,6 @@ def initialize(irb, workspace = nil, input_method = nil) STDIN.tty? && ENV['TERM'] != 'dumb' end - # because all input will eventually be evaluated as Ruby code, - # command names that conflict with Ruby keywords need special workaround - # we can remove them once we implemented a better command system for IRB - KEYWORD_ALIASES = { - :break => :irb_break, - :catch => :irb_catch, - :next => :irb_next, - }.freeze - - private_constant :KEYWORD_ALIASES - def use_tracer=(val) require_relative "ext/tracer" if val IRB.conf[:USE_TRACER] = val diff --git a/lib/irb/default_commands.rb b/lib/irb/default_commands.rb index fec41df4e..768fbee9d 100644 --- a/lib/irb/default_commands.rb +++ b/lib/irb/default_commands.rb @@ -181,9 +181,15 @@ def load_command(command) [:edit, NO_OVERRIDE] ) - _register_with_aliases(:irb_break, Command::Break) - _register_with_aliases(:irb_catch, Command::Catch) - _register_with_aliases(:irb_next, Command::Next) + _register_with_aliases(:irb_break, Command::Break, + [:break, OVERRIDE_ALL] + ) + _register_with_aliases(:irb_catch, Command::Catch, + [:catch, OVERRIDE_PRIVATE_ONLY] + ) + _register_with_aliases(:irb_next, Command::Next, + [:next, OVERRIDE_ALL] + ) _register_with_aliases(:irb_delete, Command::Delete, [:delete, NO_OVERRIDE] ) From bcfaa72d5a6e1d59d59f2feef77e9dc553350b25 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 17 Sep 2024 02:36:21 +0900 Subject: [PATCH 186/263] Use InstructionSequence#script_lines to get method source (#1005) It works with both prism and parse.y --- lib/irb/source_finder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/irb/source_finder.rb b/lib/irb/source_finder.rb index 5d7d729d1..c515da570 100644 --- a/lib/irb/source_finder.rb +++ b/lib/irb/source_finder.rb @@ -100,7 +100,7 @@ def find_source(signature, super_level = 0) Source.new(file, line) elsif method # Method defined with eval, probably in IRB session - source = RubyVM::AbstractSyntaxTree.of(method)&.source rescue nil + source = RubyVM::InstructionSequence.of(method)&.script_lines&.join rescue nil Source.new(file, line, source) end rescue EvaluationError From 71f4d6bfb5a372e9c03f8b403da3d5cb2f106ad4 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 20 Sep 2024 19:13:34 +0900 Subject: [PATCH 187/263] Fix debug command in nomultiline mode (#1006) * Fix debug command in nomultiline mode * context.colorize_code -> context.colorize_input --- lib/irb/context.rb | 15 +++++++++++++++ lib/irb/debug.rb | 16 ++++++---------- lib/irb/input-method.rb | 21 +++------------------ test/irb/yamatanooroti/test_rendering.rb | 22 ++++++++++++++++++++++ 4 files changed, 46 insertions(+), 28 deletions(-) diff --git a/lib/irb/context.rb b/lib/irb/context.rb index fafe99d57..668a823f5 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -649,6 +649,21 @@ def parse_command(code) end end + def colorize_input(input, complete:) + if IRB.conf[:USE_COLORIZE] && IRB::Color.colorable? + lvars = local_variables || [] + if parse_command(input) + name, sep, arg = input.split(/(\s+)/, 2) + arg = IRB::Color.colorize_code(arg, complete: complete, local_variables: lvars) + "#{IRB::Color.colorize(name, [:BOLD])}\e[m#{sep}#{arg}" + else + IRB::Color.colorize_code(input, complete: complete, local_variables: lvars) + end + else + Reline::Unicode.escape_for_print(input) + end + end + def inspect_last_value # :nodoc: @inspect_method.inspect_value(@last_value) end diff --git a/lib/irb/debug.rb b/lib/irb/debug.rb index 1ec2335a8..cd64b77ad 100644 --- a/lib/irb/debug.rb +++ b/lib/irb/debug.rb @@ -57,22 +57,18 @@ def DEBUGGER__.capture_frames(*args) DEBUGGER__::ThreadClient.prepend(SkipPathHelperForIRB) end - if !@output_modifier_defined && !DEBUGGER__::CONFIG[:no_hint] - irb_output_modifier_proc = Reline.output_modifier_proc - - Reline.output_modifier_proc = proc do |output, complete:| - unless output.strip.empty? - cmd = output.split(/\s/, 2).first + if !DEBUGGER__::CONFIG[:no_hint] && irb.context.io.is_a?(RelineInputMethod) + Reline.output_modifier_proc = proc do |input, complete:| + unless input.strip.empty? + cmd = input.split(/\s/, 2).first if !complete && DEBUGGER__.commands.key?(cmd) - output = output.sub(/\n$/, " # debug command\n") + input = input.sub(/\n$/, " # debug command\n") end end - irb_output_modifier_proc.call(output, complete: complete) + irb.context.colorize_input(input, complete: complete) end - - @output_modifier_defined = true end true diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index 82c1e7329..210d3da78 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -265,24 +265,9 @@ def initialize(completor) @completion_params = [preposing, target, postposing, bind] @completor.completion_candidates(preposing, target, postposing, bind: bind) } - Reline.output_modifier_proc = - if IRB.conf[:USE_COLORIZE] - proc do |output, complete: | - next unless IRB::Color.colorable? - lvars = IRB.CurrentContext&.local_variables || [] - if IRB.CurrentContext&.parse_command(output) - name, sep, arg = output.split(/(\s+)/, 2) - arg = IRB::Color.colorize_code(arg, complete: complete, local_variables: lvars) - "#{IRB::Color.colorize(name, [:BOLD])}\e[m#{sep}#{arg}" - else - IRB::Color.colorize_code(output, complete: complete, local_variables: lvars) - end - end - else - proc do |output| - Reline::Unicode.escape_for_print(output) - end - end + Reline.output_modifier_proc = proc do |input, complete:| + IRB.CurrentContext.colorize_input(input, complete: complete) + end Reline.dig_perfect_match_proc = ->(matched) { display_document(matched) } Reline.autocompletion = IRB.conf[:USE_AUTOCOMPLETE] diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index 44e07a3a1..834c501d5 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -507,6 +507,28 @@ def test_debug_integration_doesnt_hint_non_debugger_commands File.unlink(script) if script end + def test_debug_integration_doesnt_hint_debugger_commands_in_nomultiline_mode + write_irbrc <<~'LINES' + IRB.conf[:USE_SINGLELINE] = true + LINES + script = Tempfile.create(["debug", ".rb"]) + script.write <<~RUBY + puts 'start IRB' + binding.irb + RUBY + script.close + start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: 'start IRB') + write("debug\n") + write("pp 1") + close + + screen = result.join("\n").sub(/\n*\z/, "\n") + # submitted input shouldn't contain hint + assert_include(screen, "irb:rdbg(main):002> pp 1\n") + ensure + File.unlink(script) if script + end + private def write_irbrc(content) From f6b06a9a403b2de78df2702891c09a37820fa9bd Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 24 Sep 2024 23:07:43 +0900 Subject: [PATCH 188/263] Use proper locale in history encoding test (#1008) --- test/irb/test_history.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index 84f043892..7a17491d8 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -149,7 +149,7 @@ def test_history_concurrent_use_not_present def test_history_different_encodings IRB.conf[:SAVE_HISTORY] = 2 Encoding.default_external = Encoding::US_ASCII - locale = IRB::Locale.new("C") + locale = IRB::Locale.new("en_US.ASCII") assert_history(<<~EXPECTED_HISTORY.encode(Encoding::US_ASCII), <<~INITIAL_HISTORY.encode(Encoding::UTF_8), <<~INPUT, locale: locale) ???? exit From 04cd2317efaeab3575c50cdeb1b83de17ea78f01 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 26 Sep 2024 15:38:00 +0200 Subject: [PATCH 189/263] Bump version to v1.14.1 (#1009) --- lib/irb/version.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irb/version.rb b/lib/irb/version.rb index e935a1d7f..955a3a81d 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -5,7 +5,7 @@ # module IRB # :nodoc: - VERSION = "1.14.0" + VERSION = "1.14.1" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2024-07-06" + @LAST_UPDATE_DATE = "2024-09-25" end From 68f718de219986aa500feaee17e558259200d60c Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 2 Oct 2024 12:18:29 +0900 Subject: [PATCH 190/263] Use correct binding in debug mode (#1007) In debug command, IRB's context was using wrong binding. Some code colorization, command detection failed because binding.local_variable returned wrong value. --- lib/irb/debug/ui.rb | 2 +- test/irb/test_debugger_integration.rb | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/irb/debug/ui.rb b/lib/irb/debug/ui.rb index 307097b8c..7a1cd6dd1 100644 --- a/lib/irb/debug/ui.rb +++ b/lib/irb/debug/ui.rb @@ -56,7 +56,7 @@ def puts str = nil def readline _ setup_interrupt do tc = DEBUGGER__::SESSION.instance_variable_get(:@tc) - cmd = @irb.debug_readline(tc.current_frame.binding || TOPLEVEL_BINDING) + cmd = @irb.debug_readline(tc.current_frame.eval_binding || TOPLEVEL_BINDING) case cmd when nil # when user types C-d diff --git a/test/irb/test_debugger_integration.rb b/test/irb/test_debugger_integration.rb index 8b1bddea1..45ffb2a52 100644 --- a/test/irb/test_debugger_integration.rb +++ b/test/irb/test_debugger_integration.rb @@ -365,6 +365,23 @@ def bar assert_include(output, "InputMethod: RelineInputMethod") end + def test_irb_command_can_check_local_variables + write_ruby <<~'ruby' + binding.irb + ruby + + output = run_ruby_file do + type "debug" + type 'foobar = IRB' + type "show_source foobar.start" + type "show_source = 'Foo'" + type "show_source + 'Bar'" + type "continue" + end + assert_include(output, "def start(ap_path = nil)") + assert_include(output, '"FooBar"') + end + def test_help_command_is_delegated_to_the_debugger write_ruby <<~'ruby' binding.irb From ecd08a527e810d49f1cee040e935bb5b74604dc6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 6 Oct 2024 00:45:46 +0900 Subject: [PATCH 191/263] Hash#inspect style has changed in ruby 3.4 [[Bug #20433]](https://bugs.ruby-lang.org/issues/20433) --- test/irb/test_helper_method.rb | 3 ++- test/irb/test_irb.rb | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/irb/test_helper_method.rb b/test/irb/test_helper_method.rb index 291278c16..a3e2c43b2 100644 --- a/test/irb/test_helper_method.rb +++ b/test/irb/test_helper_method.rb @@ -76,7 +76,8 @@ def execute( type "exit" end - assert_include(output, '["required", "optional", ["splat"], "required", "optional", {:a=>1, :b=>2}, "block"]') + optional = {a: 1, b: 2} + assert_include(output, %[["required", "optional", ["splat"], "required", "optional", #{optional.inspect}, "block"]]) end def test_helper_method_injection_can_happen_after_irb_require diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index 2913f3d48..617e9c961 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -155,8 +155,8 @@ def test_current_context_restore type 'exit' end - assert_include output, '{:context_changed=>true}' - assert_include output, '{:context_restored=>true}' + assert_include output, {context_changed: true}.inspect + assert_include output, {context_restored: true}.inspect end end From bb6a99d81574204fa79b54a9ef33f56b9dbeff47 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 6 Oct 2024 20:10:09 +0900 Subject: [PATCH 192/263] Change default completor from regexp to auto, try TypeCompletor and fallback to RegexpCompletor. (#1010) --- lib/irb/context.rb | 14 ++++++++++---- lib/irb/init.rb | 2 +- test/irb/test_context.rb | 2 ++ test/irb/test_init.rb | 10 +++++++--- test/irb/test_type_completor.rb | 16 ++++++++++++++++ 5 files changed, 36 insertions(+), 8 deletions(-) diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 668a823f5..505bed80a 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -176,11 +176,17 @@ def use_loader=(val) private def build_completor completor_type = IRB.conf[:COMPLETOR] + + # Gem repl_type_completor is added to bundled gems in Ruby 3.4. + # Use :type as default completor only in Ruby 3.4 or later. + verbose = !!completor_type + completor_type ||= RUBY_VERSION >= '3.4' ? :type : :regexp + case completor_type when :regexp return RegexpCompletor.new when :type - completor = build_type_completor + completor = build_type_completor(verbose: verbose) return completor if completor else warn "Invalid value for IRB.conf[:COMPLETOR]: #{completor_type}" @@ -189,17 +195,17 @@ def use_loader=(val) RegexpCompletor.new end - private def build_type_completor + private def build_type_completor(verbose:) if RUBY_ENGINE == 'truffleruby' # Avoid SyntaxError. truffleruby does not support endless method definition yet. - warn 'TypeCompletor is not supported on TruffleRuby yet' + warn 'TypeCompletor is not supported on TruffleRuby yet' if verbose return end begin require 'repl_type_completor' rescue LoadError => e - warn "TypeCompletor requires `gem repl_type_completor`: #{e.message}" + warn "TypeCompletor requires `gem repl_type_completor`: #{e.message}" if verbose return end diff --git a/lib/irb/init.rb b/lib/irb/init.rb index 7dc08912e..d474bd41d 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -80,7 +80,7 @@ def IRB.init_config(ap_path) @CONF[:USE_SINGLELINE] = false unless defined?(ReadlineInputMethod) @CONF[:USE_COLORIZE] = (nc = ENV['NO_COLOR']).nil? || nc.empty? @CONF[:USE_AUTOCOMPLETE] = ENV.fetch("IRB_USE_AUTOCOMPLETE", "true") != "false" - @CONF[:COMPLETOR] = ENV.fetch("IRB_COMPLETOR", "regexp").to_sym + @CONF[:COMPLETOR] = ENV["IRB_COMPLETOR"]&.to_sym @CONF[:INSPECT_MODE] = true @CONF[:USE_TRACER] = false @CONF[:USE_LOADER] = false diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index 7ad8fd2fc..9fa23ccce 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -705,6 +705,8 @@ def test_irb_path_setter def test_build_completor verbose, $VERBOSE = $VERBOSE, nil original_completor = IRB.conf[:COMPLETOR] + IRB.conf[:COMPLETOR] = nil + assert_match /IRB::(Regexp|Type)Completor/, @context.send(:build_completor).class.name IRB.conf[:COMPLETOR] = :regexp assert_equal 'IRB::RegexpCompletor', @context.send(:build_completor).class.name IRB.conf[:COMPLETOR] = :unknown diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb index 3e8d01c5c..f7168e02f 100644 --- a/test/irb/test_init.rb +++ b/test/irb/test_init.rb @@ -167,9 +167,10 @@ def test_completor_environment_variable orig_use_autocomplete_env = ENV['IRB_COMPLETOR'] orig_use_autocomplete_conf = IRB.conf[:COMPLETOR] + # Default value is nil: auto-detect ENV['IRB_COMPLETOR'] = nil IRB.setup(__FILE__) - assert_equal(:regexp, IRB.conf[:COMPLETOR]) + assert_equal(nil, IRB.conf[:COMPLETOR]) ENV['IRB_COMPLETOR'] = 'regexp' IRB.setup(__FILE__) @@ -193,10 +194,12 @@ def test_completor_environment_variable def test_completor_setup_with_argv orig_completor_conf = IRB.conf[:COMPLETOR] + orig_completor_env = ENV['IRB_COMPLETOR'] + ENV['IRB_COMPLETOR'] = nil - # Default is :regexp + # Default value is nil: auto-detect IRB.setup(__FILE__, argv: []) - assert_equal :regexp, IRB.conf[:COMPLETOR] + assert_equal nil, IRB.conf[:COMPLETOR] IRB.setup(__FILE__, argv: ['--type-completor']) assert_equal :type, IRB.conf[:COMPLETOR] @@ -205,6 +208,7 @@ def test_completor_setup_with_argv assert_equal :regexp, IRB.conf[:COMPLETOR] ensure IRB.conf[:COMPLETOR] = orig_completor_conf + ENV['IRB_COMPLETOR'] = orig_completor_env end def test_noscript diff --git a/test/irb/test_type_completor.rb b/test/irb/test_type_completor.rb index 412d7c696..3d0e25d19 100644 --- a/test/irb/test_type_completor.rb +++ b/test/irb/test_type_completor.rb @@ -27,6 +27,22 @@ def empty_binding binding end + def test_build_completor + IRB.init_config(nil) + verbose, $VERBOSE = $VERBOSE, nil + original_completor = IRB.conf[:COMPLETOR] + workspace = IRB::WorkSpace.new(Object.new) + @context = IRB::Context.new(nil, workspace, TestInputMethod.new) + IRB.conf[:COMPLETOR] = nil + expected_default_completor = RUBY_VERSION >= '3.4' ? 'IRB::TypeCompletor' : 'IRB::RegexpCompletor' + assert_equal expected_default_completor, @context.send(:build_completor).class.name + IRB.conf[:COMPLETOR] = :type + assert_equal 'IRB::TypeCompletor', @context.send(:build_completor).class.name + ensure + $VERBOSE = verbose + IRB.conf[:COMPLETOR] = original_completor + end + def assert_completion(preposing, target, binding: empty_binding, include: nil, exclude: nil) raise ArgumentError if include.nil? && exclude.nil? candidates = @completor.completion_candidates(preposing, target, '', bind: binding) From 9933584754c246e25dd2753dfe763c786bb08537 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 9 Oct 2024 00:15:39 +0900 Subject: [PATCH 193/263] Change debug test workaround to use ENV RUBY_DEBUG_TEST_UI (#1014) --- lib/irb/input-method.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index 210d3da78..38f05d771 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -67,7 +67,9 @@ def initialize # # See IO#gets for more information. def gets - puts if @stdout.tty? # workaround for debug compatibility test + # Workaround for debug compatibility test https://github.com/ruby/debug/pull/1100 + puts if ENV['RUBY_DEBUG_TEST_UI'] + print @prompt line = @stdin.gets @line[@line_no += 1] = line From 52307f9026396a7533959a301e889d820ab9dc4b Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Fri, 11 Oct 2024 18:34:15 +0200 Subject: [PATCH 194/263] History refactors (#1013) * Extract logic save_history in separate helper * Extract logic history_file in helper * Allow for readonly history --- lib/irb.rb | 8 ++- lib/irb/history.rb | 144 ++++++++++++++++++++++++--------------- test/irb/test_history.rb | 17 ++++- 3 files changed, 110 insertions(+), 59 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 213e23117..7e91b0f64 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -14,6 +14,7 @@ require_relative "irb/ruby-lex" require_relative "irb/statement" +require_relative "irb/history" require_relative "irb/input-method" require_relative "irb/locale" require_relative "irb/color" @@ -972,7 +973,7 @@ def debug_readline(binding) # debugger. input = nil forced_exit = catch(:IRB_EXIT) do - if IRB.conf[:SAVE_HISTORY] && context.io.support_history_saving? + if History.save_history? && context.io.support_history_saving? # Previous IRB session's history has been saved when `Irb#run` is exited We need # to make sure the saved history is not saved again by resetting the counter context.io.reset_history_counter @@ -1003,9 +1004,10 @@ def run(conf = IRB.conf) prev_context = conf[:MAIN_CONTEXT] conf[:MAIN_CONTEXT] = context - save_history = !in_nested_session && conf[:SAVE_HISTORY] && context.io.support_history_saving? + load_history = !in_nested_session && context.io.support_history_saving? + save_history = load_history && History.save_history? - if save_history + if load_history context.io.load_history end diff --git a/lib/irb/history.rb b/lib/irb/history.rb index 685354b2d..a428003d2 100644 --- a/lib/irb/history.rb +++ b/lib/irb/history.rb @@ -1,6 +1,42 @@ require "pathname" module IRB + module History + class << self + # Integer representation of IRB.conf[:HISTORY_FILE]. + def save_history + num = IRB.conf[:SAVE_HISTORY].to_i + # Bignums cause RangeErrors when slicing arrays. + # Treat such values as 'infinite'. + (num > save_history_max) ? -1 : num + end + + def save_history? + !save_history.zero? + end + + def infinite? + save_history.negative? + end + + # Might be nil when HOME and XDG_CONFIG_HOME are not available. + def history_file + if (history_file = IRB.conf[:HISTORY_FILE]) + File.expand_path(history_file) + else + IRB.rc_file("_history") + end + end + + private + + def save_history_max + # Max fixnum (32-bit) that can be used without getting RangeError. + 2**30 - 1 + end + end + end + module HistorySavingAbility # :nodoc: def support_history_saving? true @@ -11,76 +47,74 @@ def reset_history_counter end def load_history + history_file = History.history_file + return unless File.exist?(history_file.to_s) + history = self.class::HISTORY - if history_file = IRB.conf[:HISTORY_FILE] - history_file = File.expand_path(history_file) - end - history_file = IRB.rc_file("_history") unless history_file - if history_file && File.exist?(history_file) - File.open(history_file, "r:#{IRB.conf[:LC_MESSAGES].encoding}") do |f| - f.each { |l| - l = l.chomp - if self.class == RelineInputMethod and history.last&.end_with?("\\") - history.last.delete_suffix!("\\") - history.last << "\n" << l - else - history << l - end - } - end - @loaded_history_lines = history.size - @loaded_history_mtime = File.mtime(history_file) + File.open(history_file, "r:#{IRB.conf[:LC_MESSAGES].encoding}") do |f| + f.each { |l| + l = l.chomp + if self.class == RelineInputMethod and history.last&.end_with?("\\") + history.last.delete_suffix!("\\") + history.last << "\n" << l + else + history << l + end + } end + @loaded_history_lines = history.size + @loaded_history_mtime = File.mtime(history_file) end def save_history + return unless History.save_history? + return unless (history_file = History.history_file) + unless ensure_history_file_writable(history_file) + warn <<~WARN + Can't write history to #{History.history_file.inspect} due to insufficient permissions. + Please verify the value of `IRB.conf[:HISTORY_FILE]`. Ensure the folder exists and that both the folder and file (if it exists) are writable. + WARN + return + end + history = self.class::HISTORY.to_a - if num = IRB.conf[:SAVE_HISTORY] and (num = num.to_i) != 0 - if history_file = IRB.conf[:HISTORY_FILE] - history_file = File.expand_path(history_file) - end - history_file = IRB.rc_file("_history") unless history_file + if File.exist?(history_file) && + File.mtime(history_file) != @loaded_history_mtime + history = history[@loaded_history_lines..-1] if @loaded_history_lines + append_history = true + end - # When HOME and XDG_CONFIG_HOME are not available, history_file might be nil - return unless history_file + File.open(history_file, (append_history ? "a" : "w"), 0o600, encoding: IRB.conf[:LC_MESSAGES]&.encoding) do |f| + hist = history.map { |l| l.scrub.split("\n").join("\\\n") } - # Change the permission of a file that already exists[BUG #7694] - begin - if File.stat(history_file).mode & 066 != 0 - File.chmod(0600, history_file) - end - rescue Errno::ENOENT - rescue Errno::EPERM - return - rescue - raise + unless append_history || History.infinite? + hist = hist.last(History.save_history) end - if File.exist?(history_file) && - File.mtime(history_file) != @loaded_history_mtime - history = history[@loaded_history_lines..-1] if @loaded_history_lines - append_history = true - end + f.puts(hist) + end + end - pathname = Pathname.new(history_file) - unless Dir.exist?(pathname.dirname) - warn "Warning: The directory to save IRB's history file does not exist. Please double check `IRB.conf[:HISTORY_FILE]`'s value." - return - end + private - File.open(history_file, (append_history ? 'a' : 'w'), 0o600, encoding: IRB.conf[:LC_MESSAGES]&.encoding) do |f| - hist = history.map{ |l| l.scrub.split("\n").join("\\\n") } - unless append_history - begin - hist = hist.last(num) if hist.size > num and num > 0 - rescue RangeError # bignum too big to convert into `long' - # Do nothing because the bignum should be treated as infinity - end - end - f.puts(hist) + # Returns boolean whether writing to +history_file+ will be possible. + # Permissions of already existing +history_file+ are changed to + # owner-only-readable if necessary [BUG #7694]. + def ensure_history_file_writable(history_file) + history_file = Pathname.new(history_file) + + return false unless history_file.dirname.writable? + return true unless history_file.exist? + + begin + if history_file.stat.mode & 0o66 != 0 + history_file.chmod 0o600 end + true + rescue Errno::EPERM # no permissions + false end end end diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index 7a17491d8..791eef1ac 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -39,6 +39,21 @@ class TestInputMethodWithReadlineHistory < TestInputMethod include IRB::HistorySavingAbility end + def test_history_dont_save + omit "Skip Editline" if /EditLine/n.match(Readline::VERSION) + IRB.conf[:SAVE_HISTORY] = nil + assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT) + 1 + 2 + EXPECTED_HISTORY + 1 + 2 + INITIAL_HISTORY + 3 + exit + INPUT + end + def test_history_save_1 omit "Skip Editline" if /EditLine/n.match(Readline::VERSION) IRB.conf[:SAVE_HISTORY] = 1 @@ -166,7 +181,7 @@ def test_history_does_not_raise_when_history_file_directory_does_not_exist IRB.conf[:HISTORY_FILE] = "fake/fake/fake/history_file" io = TestInputMethodWithRelineHistory.new - assert_warn(/history file does not exist/) do + assert_warn(/ensure the folder exists/i) do io.save_history end From 15e3f50c3ff70532738f30b0d8cbc6c910ac7b81 Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Fri, 11 Oct 2024 18:34:56 +0200 Subject: [PATCH 195/263] Document infinite history (#1012) As introduced in 824473e8 --- lib/irb.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 7e91b0f64..12eb69c5b 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -221,11 +221,11 @@ # *new_filepath*, which becomes the history file for the session. # # You can change the number of commands saved by adding to your configuration -# file: `IRB.conf[:SAVE_HISTORY] = *n*`, wheHISTORY_FILEre *n* is one of: +# file: `IRB.conf[:SAVE_HISTORY] = *n*`, where *n* is one of: # -# * Positive integer: the number of commands to be saved, -# * Zero: all commands are to be saved. -# * `nil`: no commands are to be saved,. +# * Positive integer: the number of commands to be saved. +# * Negative integer: all commands are to be saved. +# * Zero or `nil`: no commands are to be saved. # # # During the session, you can use methods `conf.save_history` or From 2c2956bc1f8d91abfdc6af53bcc153c60154064e Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 12 Oct 2024 13:24:32 +0900 Subject: [PATCH 196/263] Make rendering test faster using updated yamatanooroti (#1001) --- Gemfile | 2 +- test/irb/yamatanooroti/test_rendering.rb | 169 ++++++++--------------- 2 files changed, 56 insertions(+), 115 deletions(-) diff --git a/Gemfile b/Gemfile index 772c8fbd2..3c2efa44d 100644 --- a/Gemfile +++ b/Gemfile @@ -7,7 +7,7 @@ is_truffleruby = RUBY_DESCRIPTION =~ /truffleruby/ if is_unix && ENV['WITH_VTERM'] gem "vterm", github: "ruby/vterm-gem" - gem "yamatanooroti", github: "ruby/yamatanooroti", ref: "f6e47192100d6089f70cf64c1de540dcaadf005a" + gem "yamatanooroti", github: "ruby/yamatanooroti" end gem "stackprof" if is_unix && !is_truffleruby diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index 834c501d5..57aa0ecb3 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -39,49 +39,44 @@ def teardown end def test_launch - write_irbrc <<~'LINES' - puts 'start IRB' - LINES - start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write(<<~EOC) 'Hello, World!' EOC - close assert_screen(<<~EOC) - start IRB irb(main):001> 'Hello, World!' => "Hello, World!" irb(main):002> EOC + close end def test_configuration_file_is_skipped_with_dash_f write_irbrc <<~'LINES' puts '.irbrc file should be ignored when -f is used' LINES - start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb -f}, startup_message: '') + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb -f}, startup_message: /irb\(main\)/) write(<<~EOC) 'Hello, World!' EOC - close assert_screen(<<~EOC) irb(main):001> 'Hello, World!' => "Hello, World!" irb(main):002> EOC + close end def test_configuration_file_is_skipped_with_dash_f_for_nested_sessions write_irbrc <<~'LINES' puts '.irbrc file should be ignored when -f is used' LINES - start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb -f}, startup_message: '') + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb -f}, startup_message: /irb\(main\)/) write(<<~EOC) 'Hello, World!' binding.irb exit! EOC - close assert_screen(<<~EOC) irb(main):001> 'Hello, World!' => "Hello, World!" @@ -89,13 +84,11 @@ def test_configuration_file_is_skipped_with_dash_f_for_nested_sessions irb(main):003> exit! irb(main):001> EOC + close end def test_nomultiline - write_irbrc <<~'LINES' - puts 'start IRB' - LINES - start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb --nomultiline}, startup_message: 'start IRB') + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb --nomultiline}, startup_message: /irb\(main\)/) write(<<~EOC) if true if false @@ -105,9 +98,7 @@ def test_nomultiline end end EOC - close assert_screen(<<~EOC) - start IRB irb(main):001> if true irb(main):002* if false irb(main):003* a = "hello @@ -118,13 +109,11 @@ def test_nomultiline => nil irb(main):008> EOC + close end def test_multiline_paste - write_irbrc <<~'LINES' - puts 'start IRB' - LINES - start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write(<<~EOC) class A def inspect; '#
'; end @@ -139,9 +128,7 @@ def b; true; end .b .itself EOC - close assert_screen(<<~EOC) - start IRB irb(main):001* class A irb(main):002* def inspect; '#'; end irb(main):003* def a; self; end @@ -159,13 +146,11 @@ def b; true; end => true irb(main):013> EOC + close end def test_evaluate_each_toplevel_statement_by_multiline_paste - write_irbrc <<~'LINES' - puts 'start IRB' - LINES - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write(<<~EOC) class A def inspect; '#'; end @@ -193,9 +178,7 @@ class A def b; self; end; def c; true; end; end; &.b() .itself EOC - close assert_screen(<<~EOC) - start IRB irb(main):001* class A irb(main):002* def inspect; '#'; end irb(main):003* def b; self; end @@ -230,36 +213,28 @@ class A def b; self; end; def c; true; end; end; => # irb(main):026> EOC + close end def test_symbol_with_backtick - write_irbrc <<~'LINES' - puts 'start IRB' - LINES - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write(<<~EOC) :` EOC - close assert_screen(<<~EOC) - start IRB irb(main):001> :` => :` irb(main):002> EOC + close end def test_autocomplete_with_multiple_doc_namespaces - write_irbrc <<~'LINES' - puts 'start IRB' - LINES - start_terminal(3, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(3, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write("{}.__id_") write("\C-i") - sleep 0.2 + assert_screen(/irb\(main\):001> {}\.__id__\n }\.__id__(?:Press )?/) close - screen = result.join("\n").sub(/\n*\z/, "\n") - assert_match(/start\ IRB\nirb\(main\):001> {}\.__id__\n }\.__id__(?:Press )?/, screen) end def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_right @@ -273,31 +248,27 @@ def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_right :PROMPT_C => "%03n> " } IRB.conf[:PROMPT_MODE] = :MY_PROMPT - puts 'start IRB' LINES - start_terminal(4, 19, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(4, 19, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /001>/) write("IR") write("\C-i") - sleep 0.2 - close # This is because on macOS we display different shortcut for displaying the full doc # 'O' is for 'Option' and 'A' is for 'Alt' if RUBY_PLATFORM =~ /darwin/ assert_screen(<<~EOC) - start IRB 001> IRB IRBPress Opti IRB EOC else assert_screen(<<~EOC) - start IRB 001> IRB IRBPress Alt+ IRB EOC end + close end def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_left @@ -311,154 +282,129 @@ def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_left :PROMPT_C => "%03n> " } IRB.conf[:PROMPT_MODE] = :MY_PROMPT - puts 'start IRB' LINES - start_terminal(4, 12, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(4, 12, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /001>/) write("IR") write("\C-i") - sleep 0.2 - close assert_screen(<<~EOC) - start IRB 001> IRB PressIRB IRB EOC + close end def test_assignment_expression_truncate - write_irbrc <<~'LINES' - puts 'start IRB' - LINES - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) # Assignment expression code that turns into non-assignment expression after evaluation code = "a /'/i if false; a=1; x=1000.times.to_a#'.size" write(code + "\n") - close assert_screen(<<~EOC) - start IRB irb(main):001> #{code} => [0, ... irb(main):002> EOC + close end def test_ctrl_c_is_handled - write_irbrc <<~'LINES' - puts 'start IRB' - LINES - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) # Assignment expression code that turns into non-assignment expression after evaluation write("\C-c") - close assert_screen(<<~EOC) - start IRB irb(main):001> ^C irb(main):001> EOC + close end def test_show_cmds_with_pager_can_quit_with_ctrl_c - write_irbrc <<~'LINES' - puts 'start IRB' - LINES - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) 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 - close - screen = result.join("\n").sub(/\n*\z/, "\n") - # IRB::Abort should be rescued - assert_not_match(/IRB::Abort/, screen) # IRB should resume - assert_match(/foobar/, screen) + assert_screen(/foobar/) + # IRB::Abort should be rescued + assert_screen(/\A(?!IRB::Abort)/) + close end def test_pager_page_content_pages_output_when_it_does_not_fit_in_the_screen_because_of_total_length write_irbrc <<~'LINES' - puts 'start IRB' require "irb/pager" LINES - start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write("IRB::Pager.page_content('a' * (80 * 8))\n") write("'foo' + 'bar'\n") # eval something to make sure IRB resumes - close - screen = result.join("\n").sub(/\n*\z/, "\n") - assert_match(/a{80}/, screen) + assert_screen(/a{80}/) # because pager is invoked, foobar will not be evaluated - assert_not_match(/foobar/, screen) + assert_screen(/\A(?!foobar)/) + close end def test_pager_page_content_pages_output_when_it_does_not_fit_in_the_screen_because_of_screen_height write_irbrc <<~'LINES' - puts 'start IRB' require "irb/pager" LINES - start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write("IRB::Pager.page_content('a\n' * 8)\n") write("'foo' + 'bar'\n") # eval something to make sure IRB resumes - close - screen = result.join("\n").sub(/\n*\z/, "\n") - assert_match(/(a\n){8}/, screen) + assert_screen(/(a\n){8}/) # because pager is invoked, foobar will not be evaluated - assert_not_match(/foobar/, screen) + assert_screen(/\A(?!foobar)/) + close end def test_pager_page_content_doesnt_page_output_when_it_fits_in_the_screen write_irbrc <<~'LINES' - puts 'start IRB' require "irb/pager" LINES - start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write("IRB::Pager.page_content('a' * (80 * 7))\n") write("'foo' + 'bar'\n") # eval something to make sure IRB resumes - close - screen = result.join("\n").sub(/\n*\z/, "\n") - assert_match(/a{80}/, screen) + assert_screen(/a{80}/) # because pager is not invoked, foobar will be evaluated - assert_match(/foobar/, screen) + assert_screen(/foobar/) + close end def test_long_evaluation_output_is_paged write_irbrc <<~'LINES' - puts 'start IRB' require "irb/pager" LINES - start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write("'a' * 80 * 11\n") write("'foo' + 'bar'\n") # eval something to make sure IRB resumes - close - screen = result.join("\n").sub(/\n*\z/, "\n") - assert_match(/(a{80}\n){8}/, screen) + assert_screen(/(a{80}\n){8}/) # because pager is invoked, foobar will not be evaluated - assert_not_match(/foobar/, screen) + assert_screen(/\A(?!foobar)/) + close end def test_long_evaluation_output_is_preserved_after_paging write_irbrc <<~'LINES' - puts 'start IRB' require "irb/pager" LINES - start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write("'a' * 80 * 11\n") write("q") # quit pager write("'foo' + 'bar'\n") # eval something to make sure IRB resumes - close - screen = result.join("\n").sub(/\n*\z/, "\n") # confirm pager has exited - assert_match(/foobar/, screen) + assert_screen(/foobar/) # confirm output is preserved - assert_match(/(a{80}\n){6}/, screen) + assert_screen(/(a{80}\n){6}/) + close end def test_debug_integration_hints_debugger_commands @@ -467,21 +413,19 @@ def test_debug_integration_hints_debugger_commands LINES script = Tempfile.create(["debug", ".rb"]) script.write <<~RUBY - puts 'start IRB' binding.irb RUBY script.close - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: 'start IRB') + start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: /irb\(main\)/) write("debug\n") write("pp 1\n") write("pp 1") - close - screen = result.join("\n").sub(/\n*\z/, "\n") # submitted input shouldn't contain hint - assert_include(screen, "irb:rdbg(main):002> pp 1\n") + assert_screen(/irb:rdbg\(main\):002> pp 1\n/) # unsubmitted input should contain hint - assert_include(screen, "irb:rdbg(main):003> pp 1 # debug command\n") + assert_screen(/irb:rdbg\(main\):003> pp 1 # debug command\n/) + close ensure File.unlink(script) if script end @@ -492,17 +436,14 @@ def test_debug_integration_doesnt_hint_non_debugger_commands LINES script = Tempfile.create(["debug", ".rb"]) script.write <<~RUBY - puts 'start IRB' binding.irb RUBY script.close - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: 'start IRB') + start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: /irb\(main\)/) write("debug\n") write("foo") + assert_screen(/irb:rdbg\(main\):002> foo\n/) close - - screen = result.join("\n").sub(/\n*\z/, "\n") - assert_include(screen, "irb:rdbg(main):002> foo\n") ensure File.unlink(script) if script end From a21b953a996e4bc3b4b97180a80efa9fa343a50f Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 13 Oct 2024 13:30:30 +0900 Subject: [PATCH 197/263] Fix rendering test broken by conflict (#1016) --- test/irb/yamatanooroti/test_rendering.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index 57aa0ecb3..ac6ce5346 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -461,11 +461,9 @@ def test_debug_integration_doesnt_hint_debugger_commands_in_nomultiline_mode start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: 'start IRB') write("debug\n") write("pp 1") - close - - screen = result.join("\n").sub(/\n*\z/, "\n") # submitted input shouldn't contain hint - assert_include(screen, "irb:rdbg(main):002> pp 1\n") + assert_screen(/irb:rdbg\(main\):002> pp 1\n/) + close ensure File.unlink(script) if script end From f4404bd471823ed1e17847384fed05dfcabf2d58 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 13 Oct 2024 12:43:06 +0800 Subject: [PATCH 198/263] Improve Debugging with IRB section to make it easier to get started (#1015) --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index fec8350ef..84c9db465 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,13 @@ Aliases ## Debugging with IRB +### Getting Started + +- In `binding.irb`, use the `debug` command to start a `irb:rdbg` session with access to all `debug.gem` commands. +- Use `RUBY_DEBUG_IRB_CONSOLE=1` environment variable to make `debug.gem` use IRB as the debugging console. + +### Details + Starting from version 1.8.0, IRB boasts a powerful integration with `debug.gem`, providing a debugging experience akin to `pry-byebug`. After hitting a `binding.irb` breakpoint, you can activate the debugger with the `debug` command. Alternatively, if the `debug` method happens to already be defined in the current scope, you can call `irb_debug`. From 83361c6768b1a534ce406bc947f23f9478d0389f Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 13 Oct 2024 21:54:42 +0900 Subject: [PATCH 199/263] Update setup/ruby used in gh-pages workflow because it is failing on ci (#1017) --- .github/workflows/gh-pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 005c23d9b..1efe11e33 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -22,7 +22,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Setup Ruby - uses: ruby/setup-ruby@1198b074305f9356bd56dd4b311757cc0dab2f1c # v1.175.1 + uses: ruby/setup-ruby@f26937343756480a8cb3ae1f623b9c8d89ed6984 # v1.196.0 with: ruby-version: "3.3" bundler-cache: true From 5151467e6ad0d7c4f3273ae96196e78349a5f7fb Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 13 Oct 2024 22:00:17 +0900 Subject: [PATCH 200/263] Remove bignum check from save_history (#1018) IRB need to accept bignum history size, but we don't want explicit bignum checks because threshold of bignum and fixnum depends on platform. --- lib/irb/history.rb | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/lib/irb/history.rb b/lib/irb/history.rb index a428003d2..25fa71b9c 100644 --- a/lib/irb/history.rb +++ b/lib/irb/history.rb @@ -5,10 +5,7 @@ module History class << self # Integer representation of IRB.conf[:HISTORY_FILE]. def save_history - num = IRB.conf[:SAVE_HISTORY].to_i - # Bignums cause RangeErrors when slicing arrays. - # Treat such values as 'infinite'. - (num > save_history_max) ? -1 : num + IRB.conf[:SAVE_HISTORY].to_i end def save_history? @@ -27,13 +24,6 @@ def history_file IRB.rc_file("_history") end end - - private - - def save_history_max - # Max fixnum (32-bit) that can be used without getting RangeError. - 2**30 - 1 - end end end @@ -90,7 +80,8 @@ def save_history hist = history.map { |l| l.scrub.split("\n").join("\\\n") } unless append_history || History.infinite? - hist = hist.last(History.save_history) + # Check size before slicing because array.last(huge_number) raises RangeError. + hist = hist.last(History.save_history) if hist.size > History.save_history end f.puts(hist) From db0a923d620d2c6b039b6c27927f65003b387820 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 19 Oct 2024 02:15:05 +0900 Subject: [PATCH 201/263] Always use alternate sceen on alt-d (#988) --- lib/irb/easter-egg.rb | 1 - lib/irb/input-method.rb | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/irb/easter-egg.rb b/lib/irb/easter-egg.rb index 14dc93fc9..bf8884838 100644 --- a/lib/irb/easter-egg.rb +++ b/lib/irb/easter-egg.rb @@ -125,7 +125,6 @@ def render_frame(i) canvas = Canvas.new(Reline.get_screen_size) end ruby_model = RubyModel.new - print "\e[?1049h" 0.step do |i| # TODO (0..).each needs Ruby 2.6 or later buff = canvas.draw do ruby_model.render_frame(i) do |p1, p2| diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index 38f05d771..260d9a1cb 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -348,9 +348,15 @@ def show_doc_dialog_proc if show_easter_egg IRB.__send__(:easter_egg) else + # RDoc::RI::Driver#display_names uses pager command internally. + # Some pager command like `more` doesn't use alternate screen + # so we need to turn on and off alternate screen manually. begin + print "\e[?1049h" driver.display_names([name]) rescue RDoc::RI::Driver::NotFoundError + ensure + print "\e[?1049l" end end end From 3da04b97866631ca134026388b226eda92a9549e Mon Sep 17 00:00:00 2001 From: Tsutomu Katsube Date: Sat, 19 Oct 2024 02:15:22 +0900 Subject: [PATCH 202/263] Suppress "literal string will be frozen in the future" warning (#1019) * Suppress "literal string will be frozen in the future" warning Before change: ```console $ ruby -W -I lib -e 'require "irb"; IRB.setup(nil); IRB::Irb.new.build_statement("1 + 2")' /Users/zzz/src/github.com/ruby/irb/lib/irb.rb:1135: warning: literal string will be frozen in the future ``` After change: ```console $ ruby -W -I lib -e 'require "irb"; IRB.setup(nil); IRB::Irb.new.build_statement("1 + 2")' ``` * Making build_statement not modify the given argument Because improves readability and code quality. Co-authored-by: tomoya ishida --------- Co-authored-by: tomoya ishida --- lib/irb.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/irb.rb b/lib/irb.rb index 12eb69c5b..528892797 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1132,7 +1132,7 @@ def build_statement(code) return Statement::EmptyInput.new end - code.force_encoding(@context.io.encoding) + code = code.dup.force_encoding(@context.io.encoding) if (command, arg = @context.parse_command(code)) command_class = Command.load_command(command) Statement::Command.new(code, command_class, arg) From 7f385bc19badd2100b76e8b2404be5be46306bf6 Mon Sep 17 00:00:00 2001 From: Go Date: Sun, 20 Oct 2024 15:18:04 +0900 Subject: [PATCH 203/263] Improve history test's encoding setting (#1022) * improve history test's encoding setting * fix missing locale error for ci and refactoring --- test/irb/test_history.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index 791eef1ac..15c16ed89 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -10,17 +10,20 @@ module TestIRB class HistoryTest < TestCase def setup + @conf_backup = IRB.conf.dup @original_verbose, $VERBOSE = $VERBOSE, nil @tmpdir = Dir.mktmpdir("test_irb_history_") setup_envs(home: @tmpdir) - @backup_default_external = Encoding.default_external + IRB.conf[:LC_MESSAGES] = IRB::Locale.new + save_encodings IRB.instance_variable_set(:@existing_rc_name_generators, nil) end def teardown + IRB.conf.replace(@conf_backup) IRB.instance_variable_set(:@existing_rc_name_generators, nil) teardown_envs - Encoding.default_external = @backup_default_external + restore_encodings $VERBOSE = @original_verbose FileUtils.rm_rf(@tmpdir) end @@ -146,7 +149,6 @@ def test_history_concurrent_use_readline end def test_history_concurrent_use_not_present - IRB.conf[:LC_MESSAGES] = IRB::Locale.new IRB.conf[:SAVE_HISTORY] = 1 io = TestInputMethodWithRelineHistory.new io.class::HISTORY.clear @@ -163,9 +165,9 @@ def test_history_concurrent_use_not_present def test_history_different_encodings IRB.conf[:SAVE_HISTORY] = 2 - Encoding.default_external = Encoding::US_ASCII - locale = IRB::Locale.new("en_US.ASCII") - assert_history(<<~EXPECTED_HISTORY.encode(Encoding::US_ASCII), <<~INITIAL_HISTORY.encode(Encoding::UTF_8), <<~INPUT, locale: locale) + IRB.conf[:LC_MESSAGES] = IRB::Locale.new("en_US.ASCII") + IRB.__send__(:set_encoding, Encoding::US_ASCII.name, override: false) + assert_history(<<~EXPECTED_HISTORY.encode(Encoding::US_ASCII), <<~INITIAL_HISTORY.encode(Encoding::UTF_8), <<~INPUT) ???? exit EXPECTED_HISTORY @@ -234,8 +236,7 @@ def history_concurrent_use_for_input_method(input_method) end end - def assert_history(expected_history, initial_irb_history, input, input_method = TestInputMethodWithRelineHistory, locale: IRB::Locale.new) - IRB.conf[:LC_MESSAGES] = locale + def assert_history(expected_history, initial_irb_history, input, input_method = TestInputMethodWithRelineHistory) actual_history = nil history_file = IRB.rc_file("_history") ENV["HOME"] = @tmpdir From 7bbb885163d6c03f27c4b89509de0e7d74ce7d2e Mon Sep 17 00:00:00 2001 From: YO4 Date: Tue, 5 Nov 2024 22:53:36 +0900 Subject: [PATCH 204/263] windows does not support Process.kill("TERM", pid) (#1026) --- lib/irb/pager.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/irb/pager.rb b/lib/irb/pager.rb index 558318cdb..7c1249dd5 100644 --- a/lib/irb/pager.rb +++ b/lib/irb/pager.rb @@ -34,7 +34,12 @@ def page(retain_content: false) # So to properly terminate the pager with Ctrl-C, we need to catch `IRB::Abort` and kill the pager process rescue IRB::Abort begin - Process.kill("TERM", pid) if pid + begin + Process.kill("TERM", pid) if pid + rescue Errno::EINVAL + # SIGTERM not supported (windows) + Process.kill("KILL", pid) + end rescue Errno::ESRCH # Pager process already terminated end From b21432daf70f6faf41cba65da00969b7f1353259 Mon Sep 17 00:00:00 2001 From: Kouhei Yanagita Date: Thu, 7 Nov 2024 21:51:57 +0900 Subject: [PATCH 205/263] Correct ja/help-message for --context-mode and --prompt (#1029) --- lib/irb/lc/ja/help-message | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/irb/lc/ja/help-message b/lib/irb/lc/ja/help-message index 99f4449b3..844c67bbb 100644 --- a/lib/irb/lc/ja/help-message +++ b/lib/irb/lc/ja/help-message @@ -8,7 +8,7 @@ Usage: irb.rb [options] [programfile] [arguments] -w ruby -w と同じ. -W[level=2] ruby -W と同じ. --context-mode n 新しいワークスペースを作成した時に関連する Binding - オブジェクトの作成方法を 0 から 3 のいずれかに設定する. + オブジェクトの作成方法を 0 から 4 のいずれかに設定する. --extra-doc-dir 指定したディレクトリのドキュメントを追加で読み込む. --echo 実行結果を表示する(デフォルト). --noecho 実行結果を表示しない. @@ -33,9 +33,9 @@ Usage: irb.rb [options] [programfile] [arguments] 補完に正規表現を利用する. --type-completor 補完に型情報を利用する. --prompt prompt-mode/--prompt-mode prompt-mode - プロンプトモードを切替えます. 現在定義されているプ - ロンプトモードは, default, simple, xmp, inf-rubyが - 用意されています. + プロンプトモードを切り替える. + 現在定義されているプロンプトモードは, + default, classic, simple, inf-ruby, xmp, null. --inf-ruby-mode emacsのinf-ruby-mode用のプロンプト表示を行なう. 特 に指定がない限り, シングルラインエディタとマルチラ インエディタは使わなくなる. From a6fae8b3ca25763777131ead4837a89d9cf6f05e Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Thu, 7 Nov 2024 13:17:54 +0900 Subject: [PATCH 206/263] Prevent a warning: ambiguous `/` --- test/irb/test_context.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index 9fa23ccce..d2c007af3 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -706,7 +706,7 @@ def test_build_completor verbose, $VERBOSE = $VERBOSE, nil original_completor = IRB.conf[:COMPLETOR] IRB.conf[:COMPLETOR] = nil - assert_match /IRB::(Regexp|Type)Completor/, @context.send(:build_completor).class.name + assert_match(/IRB::(Regexp|Type)Completor/, @context.send(:build_completor).class.name) IRB.conf[:COMPLETOR] = :regexp assert_equal 'IRB::RegexpCompletor', @context.send(:build_completor).class.name IRB.conf[:COMPLETOR] = :unknown From 68f5cf0535c7827b9784b74460257c72af42f653 Mon Sep 17 00:00:00 2001 From: Kouhei Yanagita Date: Tue, 19 Nov 2024 22:13:12 +0900 Subject: [PATCH 207/263] Complete the missing documentation abount the environment variables (#1028) --- README.md | 4 ++++ man/irb.1 | 37 +++++++++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 84c9db465..7186bc41a 100644 --- a/README.md +++ b/README.md @@ -361,6 +361,10 @@ irb(main):002> a.first. # Completes Integer methods - `VISUAL`: Its value would be used to open files by the `edit` command. - `EDITOR`: Its value would be used to open files by the `edit` command if `VISUAL` is unset. - `IRBRC`: The file specified would be evaluated as IRB's rc-file. +- `XDG_CONFIG_HOME`: If it is set and `IRBRC` is unset, the file `$XDG_CONFIG_HOME/irb/irbrc` would be evaluated as IRB's rc-file. +- `RI_PAGER`: The command specified would be used as a pager. +- `PAGER`: The command specified would be used as a pager if `RI_PAGER` is unset. +- `IRB_LANG`, `LC_MESSAGES`, `LC_ALL`, `LANG`: The first of these that is set would be used as the locale value. ## Documentation diff --git a/man/irb.1 b/man/irb.1 index 93ef9b8f6..90cf5d4ae 100644 --- a/man/irb.1 +++ b/man/irb.1 @@ -180,7 +180,7 @@ The default value is 16. .El .Pp .Sh ENVIRONMENT -.Bl -tag -compact -width "XDG_CONFIG_HOME" +.Bl -tag -compact -width "IRB_USE_AUTOCOMPLETE" .It Ev IRB_LANG The locale used for .Nm . @@ -190,10 +190,43 @@ The path to the personal initialization file. .Pp .It Ev XDG_CONFIG_HOME .Nm -respects XDG_CONFIG_HOME. If this is set, load +respects XDG_CONFIG_HOME. If it is set and +.Ev IRBRC +is unset, load .Pa $XDG_CONFIG_HOME/irb/irbrc as a personal initialization file. .Pp +.It Ev RI_PAGER +The command specified would be used as a pager. +.Pp +.It Ev PAGER +The command specified would be used as a pager if +.Ev RI_PAGER +is unset. +.Pp +.It Ev VISUAL +Its value would be used to open files by the edit command. +.Pp +.It Ev EDITOR +Its value would be used to open files by the edit command if +.Ev VISUAL +is unset. +.Pp +.It Ev NO_COLOR +Assigning a value to it disables colorization. +.Pp +.It Ev IRB_USE_AUTOCOMPLETE +Assigning +.Sy false +to it disables autocompletion. +.Pp +.It Ev IRB_COMPLETOR +Autocompletion behavior. Allowed values are +.Sy regexp +or +.Sy type +. +.Pp .El .Pp Also From 2f1c5938018ccc7cec07e37359bb4bf28d933d40 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 19 Nov 2024 22:17:07 +0900 Subject: [PATCH 208/263] Don't use delegator to install helper methods to main object (#1031) IRB used delegator to install command as a method of frozen main object. Command is not a method now. We can drop it. --- lib/irb.rb | 9 ++++++-- lib/irb/command/internal_helpers.rb | 2 +- lib/irb/completion.rb | 3 ++- lib/irb/workspace.rb | 32 ++++++----------------------- test/irb/command/test_cd.rb | 19 +++++++++++++++++ test/irb/test_context.rb | 7 +++++++ 6 files changed, 42 insertions(+), 30 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 528892797..e60e8e1e2 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1463,16 +1463,21 @@ def truncate_prompt_main(str) # :nodoc: end end + def basic_object_safe_main_call(method) + main = @context.main + Object === main ? main.__send__(method) : Object.instance_method(method).bind_call(main) + end + def format_prompt(format, ltype, indent, line_no) # :nodoc: format.gsub(/%([0-9]+)?([a-zA-Z%])/) do case $2 when "N" @context.irb_name when "m" - main_str = @context.main.to_s rescue "!#{$!.class}" + main_str = basic_object_safe_main_call(:to_s) rescue "!#{$!.class}" truncate_prompt_main(main_str) when "M" - main_str = @context.main.inspect rescue "!#{$!.class}" + main_str = basic_object_safe_main_call(:inspect) rescue "!#{$!.class}" truncate_prompt_main(main_str) when "l" ltype diff --git a/lib/irb/command/internal_helpers.rb b/lib/irb/command/internal_helpers.rb index 249b5cded..a01ddb1d4 100644 --- a/lib/irb/command/internal_helpers.rb +++ b/lib/irb/command/internal_helpers.rb @@ -19,7 +19,7 @@ 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}" + @irb_context.workspace.binding.eval "::IRB::Command.extract_ruby_args #{arg}" end || [[], {}] end end diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index 7f102dcdf..36a8b084f 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -156,7 +156,8 @@ def eval_global_variables end def eval_class_constants - ::Module.instance_method(:constants).bind(eval("self.class")).call + klass = ::Object.instance_method(:class).bind_call(receiver) + ::Module.instance_method(:constants).bind_call(klass) end end } diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb index 632b43243..ced9d7866 100644 --- a/lib/irb/workspace.rb +++ b/lib/irb/workspace.rb @@ -4,8 +4,6 @@ # by Keiju ISHITSUKA(keiju@ruby-lang.org) # -require "delegate" - require_relative "helper_method" IRB::TOPLEVEL_BINDING = binding @@ -16,7 +14,7 @@ class WorkSpace # set self to main if specified, otherwise # inherit main from TOPLEVEL_BINDING. def initialize(*main) - if main[0].kind_of?(Binding) + if Binding === main[0] @binding = main.shift elsif IRB.conf[:SINGLE_IRB] @binding = TOPLEVEL_BINDING @@ -70,37 +68,16 @@ def initialize(*main) unless main.empty? case @main when Module - @binding = eval("IRB.conf[:__MAIN__].module_eval('binding', __FILE__, __LINE__)", @binding, __FILE__, __LINE__) + @binding = eval("::IRB.conf[:__MAIN__].module_eval('::Kernel.binding', __FILE__, __LINE__)", @binding, __FILE__, __LINE__) else begin - @binding = eval("IRB.conf[:__MAIN__].instance_eval('binding', __FILE__, __LINE__)", @binding, __FILE__, __LINE__) + @binding = eval("::IRB.conf[:__MAIN__].instance_eval('::Kernel.binding', __FILE__, __LINE__)", @binding, __FILE__, __LINE__) rescue TypeError fail CantChangeBinding, @main.inspect end end end - case @main - when Object - use_delegator = @main.frozen? - else - use_delegator = true - end - - if use_delegator - @main = SimpleDelegator.new(@main) - IRB.conf[:__MAIN__] = @main - @main.singleton_class.class_eval do - private - define_method(:binding, Kernel.instance_method(:binding)) - define_method(:local_variables, Kernel.instance_method(:local_variables)) - # Define empty method to avoid delegator warning, will be overridden. - define_method(:exit) {|*a, &b| } - define_method(:exit!) {|*a, &b| } - end - @binding = eval("IRB.conf[:__MAIN__].instance_eval('binding', __FILE__, __LINE__)", @binding, *@binding.source_location) - end - @binding.local_variable_set(:_, nil) end @@ -111,6 +88,9 @@ def initialize(*main) attr_reader :main def load_helper_methods_to_main + # Do not load helper methods to frozen objects and BasicObject + return unless Object === @main && !@main.frozen? + ancestors = class</, out) end + def test_cd_basic_object_or_frozen + out = run_ruby_file do + type "cd BO.new" + type "cd 1" + type "cd Object.new.freeze" + type "exit" + end + + assert_match(/irb\(#/, out) + assert_match(/irb\(1\):003>/, out) + assert_match(/irb\(#/, out) + end + def test_cd_moves_top_level_with_no_args out = run_ruby_file do type "cd Foo" diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index d2c007af3..b02d8dbe0 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -652,6 +652,13 @@ def main.inspect; to_s.inspect; end assert_equal('irb("aaaaaaaaaaaaaaaaaaaaaaaaaaaa...)>', irb.send(:format_prompt, 'irb(%M)>', nil, 1, 1)) end + def test_prompt_main_basic_object + main = BasicObject.new + irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new) + assert_match(/irb\(#', nil, 1, 1)) + assert_match(/irb\(#', nil, 1, 1)) + end + def test_prompt_main_raise main = Object.new def main.to_s; raise TypeError; end From 9750fa23cc0aa4e0ef2cd570e22c49c0ca4f1862 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 20 Nov 2024 15:02:14 +0000 Subject: [PATCH 209/263] Move main object's safe call logic to Context (#1034) --- lib/irb.rb | 9 ++------- lib/irb/context.rb | 5 +++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index e60e8e1e2..f5a875d08 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1463,21 +1463,16 @@ def truncate_prompt_main(str) # :nodoc: end end - def basic_object_safe_main_call(method) - main = @context.main - Object === main ? main.__send__(method) : Object.instance_method(method).bind_call(main) - end - def format_prompt(format, ltype, indent, line_no) # :nodoc: format.gsub(/%([0-9]+)?([a-zA-Z%])/) do case $2 when "N" @context.irb_name when "m" - main_str = basic_object_safe_main_call(:to_s) rescue "!#{$!.class}" + main_str = @context.safe_method_call_on_main(:to_s) rescue "!#{$!.class}" truncate_prompt_main(main_str) when "M" - main_str = basic_object_safe_main_call(:inspect) rescue "!#{$!.class}" + main_str = @context.safe_method_call_on_main(:inspect) rescue "!#{$!.class}" truncate_prompt_main(main_str) when "l" ltype diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 505bed80a..bcbbb8b82 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -704,5 +704,10 @@ def inspect # :nodoc: def local_variables # :nodoc: workspace.binding.local_variables end + + def safe_method_call_on_main(method_name) + main_object = main + Object === main_object ? main_object.__send__(method_name) : Object.instance_method(method_name).bind_call(main_object) + end end end From f1e25ec7aeeda8e2f5d1de98b461fb95766f8cf2 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 20 Nov 2024 18:59:23 +0000 Subject: [PATCH 210/263] Store method objects in constants (#1033) It probably won't speed up things significantly, but these are hot paths and we can save a few method calls per completion/input call. --- lib/irb/completion.rb | 18 ++++++++++++------ lib/irb/context.rb | 7 +++++-- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index 36a8b084f..3e9704706 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -137,27 +137,33 @@ def doc_namespace(preposing, matched, _postposing, bind:) end class RegexpCompletor < BaseCompletor # :nodoc: + KERNEL_METHODS = ::Kernel.instance_method(:methods) + KERNEL_PRIVATE_METHODS = ::Kernel.instance_method(:private_methods) + KERNEL_INSTANCE_VARIABLES = ::Kernel.instance_method(:instance_variables) + OBJECT_CLASS_INSTANCE_METHOD = ::Object.instance_method(:class) + MODULE_CONSTANTS_INSTANCE_METHOD = ::Module.instance_method(:constants) + using Module.new { refine ::Binding do def eval_methods - ::Kernel.instance_method(:methods).bind(eval("self")).call + KERNEL_METHODS.bind_call(receiver) end def eval_private_methods - ::Kernel.instance_method(:private_methods).bind(eval("self")).call + KERNEL_PRIVATE_METHODS.bind_call(receiver) end def eval_instance_variables - ::Kernel.instance_method(:instance_variables).bind(eval("self")).call + KERNEL_INSTANCE_VARIABLES.bind_call(receiver) end def eval_global_variables - ::Kernel.instance_method(:global_variables).bind(eval("self")).call + ::Kernel.global_variables end def eval_class_constants - klass = ::Object.instance_method(:class).bind_call(receiver) - ::Module.instance_method(:constants).bind_call(klass) + klass = OBJECT_CLASS_INSTANCE_METHOD.bind_call(receiver) + MODULE_CONSTANTS_INSTANCE_METHOD.bind_call(klass) end end } diff --git a/lib/irb/context.rb b/lib/irb/context.rb index bcbbb8b82..c65628192 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -13,6 +13,9 @@ module IRB # A class that wraps the current state of the irb session, including the # configuration of IRB.conf. class Context + KERNEL_PUBLIC_METHOD = ::Kernel.instance_method(:public_method) + KERNEL_METHOD = ::Kernel.instance_method(:method) + ASSIGN_OPERATORS_REGEXP = Regexp.union(%w[= += -= *= /= %= **= &= |= &&= ||= ^= <<= >>=]) # Creates a new IRB context. # @@ -648,8 +651,8 @@ def parse_command(code) return if local_variables.include?(command) # Check visibility - public_method = !!Kernel.instance_method(:public_method).bind_call(main, command) rescue false - private_method = !public_method && !!Kernel.instance_method(:method).bind_call(main, command) rescue false + public_method = !!KERNEL_PUBLIC_METHOD.bind_call(main, command) rescue false + private_method = !public_method && !!KERNEL_METHOD.bind_call(main, command) rescue false if Command.execute_as_command?(command, public_method: public_method, private_method: private_method) [command, arg] end From 84366b8cdd7ac4c200a03405a07ed074aff5ae23 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 22 Nov 2024 10:01:51 +0000 Subject: [PATCH 211/263] Extract truffleruby workflow (#1035) * Add a new GA workflow for truffleruby-head * Remove truffleruby-head from the default CI workflow --- .github/workflows/test.yml | 7 +----- .github/workflows/truffle-ruby-test.yml | 30 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/truffle-ruby-test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 810c27590..95b4f46d3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,7 @@ jobs: ruby-versions: uses: ruby/actions/.github/workflows/ruby_versions.yml@master with: - engine: cruby-truffleruby + engine: cruby min_version: 2.7 lint: runs-on: ubuntu-latest @@ -30,8 +30,6 @@ jobs: matrix: ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} with_latest_reline: [true, false] - exclude: - - ruby: truffleruby fail-fast: false runs-on: ubuntu-latest env: @@ -80,9 +78,6 @@ jobs: matrix: ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} with_latest_reline: [true, false] - exclude: - - ruby: truffleruby - - ruby: truffleruby-head fail-fast: false env: WITH_LATEST_RELINE: ${{matrix.with_latest_reline}} diff --git a/.github/workflows/truffle-ruby-test.yml b/.github/workflows/truffle-ruby-test.yml new file mode 100644 index 000000000..43ffb0ba5 --- /dev/null +++ b/.github/workflows/truffle-ruby-test.yml @@ -0,0 +1,30 @@ +name: build-with-truffleruby-head + +on: + push: + pull_request: + schedule: + - cron: "30 14 * * *" + +jobs: + irb: + name: rake test truffleruby-head ${{ matrix.with_latest_reline && '(latest reline)' || '' }} + strategy: + matrix: + with_latest_reline: [true, false] + fail-fast: false + runs-on: ubuntu-latest + env: + WITH_LATEST_RELINE: ${{matrix.with_latest_reline}} + timeout-minutes: 30 + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: truffleruby-head + bundler-cache: true + - name: Run tests + run: bundle exec rake test + - name: Run tests in isolation + run: bundle exec rake test_in_isolation From 4217a46f5d7adbe717ef678d72227be9a052ae92 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 27 Nov 2024 02:50:24 +0900 Subject: [PATCH 212/263] Fix indentation of xstring literal (#1038) Fixes indent calculation of this input ``` if false p `ls` end ``` --- lib/irb/nesting_parser.rb | 2 +- test/irb/test_nesting_parser.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/irb/nesting_parser.rb b/lib/irb/nesting_parser.rb index fc71d64ae..c1c9a5cc7 100644 --- a/lib/irb/nesting_parser.rb +++ b/lib/irb/nesting_parser.rb @@ -159,7 +159,7 @@ def scan_opens(tokens) when :on_heredoc_end opens.pop when :on_backtick - opens << [t, nil] if t.state.allbits?(Ripper::EXPR_BEG) + opens << [t, nil] unless t.state == Ripper::EXPR_ARG when :on_tstring_beg, :on_words_beg, :on_qwords_beg, :on_symbols_beg, :on_qsymbols_beg, :on_regexp_beg opens << [t, nil] when :on_tstring_end, :on_regexp_end, :on_label_end diff --git a/test/irb/test_nesting_parser.rb b/test/irb/test_nesting_parser.rb index 2db3cdab5..6b4f54ee2 100644 --- a/test/irb/test_nesting_parser.rb +++ b/test/irb/test_nesting_parser.rb @@ -59,6 +59,7 @@ def `; end def f() = 1 %(); %w[]; %q(); %r{}; %i[] "#{1}"; ''; /#{1}/; `#{1}` + p(``); p ``; p x: ``; p 1, ``; :sym; :"sym"; :+; :`; :if [1, 2, 3] { x: 1, y: 2 } From 0506ed0e1163a288023a6162b73e324e9608b795 Mon Sep 17 00:00:00 2001 From: Mari Imaizumi Date: Sun, 1 Dec 2024 01:21:59 +0900 Subject: [PATCH 213/263] Prevent cursor flickering (#1041) --- lib/irb/easter-egg.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/irb/easter-egg.rb b/lib/irb/easter-egg.rb index bf8884838..07b6137be 100644 --- a/lib/irb/easter-egg.rb +++ b/lib/irb/easter-egg.rb @@ -125,6 +125,7 @@ def render_frame(i) canvas = Canvas.new(Reline.get_screen_size) end ruby_model = RubyModel.new + print "\e[?25l" # hide cursor 0.step do |i| # TODO (0..).each needs Ruby 2.6 or later buff = canvas.draw do ruby_model.render_frame(i) do |p1, p2| @@ -138,6 +139,7 @@ def render_frame(i) end rescue Interrupt ensure + print "\e[?25h" # show cursor trap("SIGINT", prev_trap) end end From 9eb14a3a0b4b0132457907e05c472cbfe4b2342d Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 4 Dec 2024 06:55:27 +0900 Subject: [PATCH 214/263] Don't show 'Maybe IRB bug!' in show_source and ls command (#1039) --- lib/irb/command/ls.rb | 34 +++++++++++++++++++--------- lib/irb/source_finder.rb | 5 ++-- test/irb/command/test_show_source.rb | 13 +++++++++++ test/irb/test_command.rb | 13 +++++++++++ 4 files changed, 51 insertions(+), 14 deletions(-) diff --git a/lib/irb/command/ls.rb b/lib/irb/command/ls.rb index cbd9998bc..944efd757 100644 --- a/lib/irb/command/ls.rb +++ b/lib/irb/command/ls.rb @@ -11,7 +11,7 @@ module IRB module Command class Ls < Base - include RubyArgsExtractor + class EvaluationError < StandardError; end category "Context" description "Show methods, constants, and variables." @@ -22,24 +22,35 @@ class Ls < Base -g [query] Filter the output with a query. HELP_MESSAGE + def evaluate(code) + @irb_context.workspace.binding.eval(code) + rescue Exception => e + puts "#{e.class}: #{e.message}" + raise EvaluationError + end + def execute(arg) if match = arg.match(/\A(?.+\s|)(-g|-G)\s+(?.+)$/) - if match[:target].empty? - use_main = true - else - obj = @irb_context.workspace.binding.eval(match[:target]) - end + target = match[:target] grep = Regexp.new(match[:grep]) + elsif match = arg.match(/\A((?.+),|)\s*grep:(?.+)/) + # Legacy style `ls obj, grep: /regexp/` + # Evaluation order should be eval(target) then eval(grep) + target = match[:target] || '' + grep_regexp_code = match[:grep] else - args, kwargs = ruby_args(arg) - use_main = args.empty? - obj = args.first - grep = kwargs[:grep] + target = arg.strip end - if use_main + if target.empty? obj = irb_context.workspace.main locals = irb_context.workspace.binding.local_variables + else + obj = evaluate(target) + end + + if grep_regexp_code + grep = evaluate(grep_regexp_code) end o = Output.new(grep: grep) @@ -52,6 +63,7 @@ def execute(arg) o.dump("class variables", klass.class_variables) o.dump("locals", locals) if locals o.print_result + rescue EvaluationError end def dump_methods(o, klass, obj) diff --git a/lib/irb/source_finder.rb b/lib/irb/source_finder.rb index c515da570..6e1e58069 100644 --- a/lib/irb/source_finder.rb +++ b/lib/irb/source_finder.rb @@ -125,9 +125,8 @@ def method_target(owner_receiver, super_level, method, type) end def eval_receiver_or_owner(code) - context_binding = @irb_context.workspace.binding - eval(code, context_binding) - rescue NameError + @irb_context.workspace.binding.eval(code) + rescue Exception raise EvaluationError end diff --git a/test/irb/command/test_show_source.rb b/test/irb/command/test_show_source.rb index d014c78fc..a4227231e 100644 --- a/test/irb/command/test_show_source.rb +++ b/test/irb/command/test_show_source.rb @@ -65,6 +65,19 @@ def test_show_source_with_missing_constant assert_match(%r[Couldn't locate a definition for Foo], out) end + def test_show_source_with_eval_error + write_ruby <<~'RUBY' + binding.irb + RUBY + + out = run_ruby_file do + type "show_source raise(Exception).itself" + type "exit" + end + + assert_match(%r[Couldn't locate a definition for raise\(Exception\)\.itself], out) + end + def test_show_source_string write_ruby <<~'RUBY' binding.irb diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index 69931e3e4..21e05752b 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -742,6 +742,19 @@ def test_ls_grep_empty end end + def test_ls_with_eval_error + [ + "ls raise(Exception,'foo')\n", + "ls raise(Exception,'foo'), grep: /./\n", + "ls Integer, grep: raise(Exception,'foo')\n", + ].each do |line| + out, err = execute_lines(line) + assert_empty err + assert_match(/Exception: foo/, out) + assert_not_match(/Maybe IRB bug!/, out) + end + end + def test_ls_with_no_singleton_class out, err = execute_lines( "ls 42", From 8241ec9a0c981af6cb483112a204b3827bf018c6 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 11 Dec 2024 19:30:30 +0800 Subject: [PATCH 215/263] Page the output in irb:rdbg sessions too (#1043) IRB started to page its evaluation output and it became a useful feature for users. However, in `irb:rdbg` sessions, the output is not paged so the sudden change in behavior is surprising and inconvenient. This commit makes `irb:rdbg` sessions page the output of the debugger too. --- lib/irb/debug/ui.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/irb/debug/ui.rb b/lib/irb/debug/ui.rb index 7a1cd6dd1..a21ec6b11 100644 --- a/lib/irb/debug/ui.rb +++ b/lib/irb/debug/ui.rb @@ -45,9 +45,7 @@ def puts str = nil $stdout.puts line.chomp } when String - str.each_line{|line| - $stdout.puts line.chomp - } + Pager.page_content(str, retain_content: true) when nil $stdout.puts end From dd318846578230de89f71cb0b7af8b3ff53601f7 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 12 Dec 2024 21:14:52 +0800 Subject: [PATCH 216/263] Bump version to v1.14.2 (#1045) --- lib/irb/version.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irb/version.rb b/lib/irb/version.rb index 955a3a81d..825233e21 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -5,7 +5,7 @@ # module IRB # :nodoc: - VERSION = "1.14.1" + VERSION = "1.14.2" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2024-09-25" + @LAST_UPDATE_DATE = "2024-12-12" end From 7f851b5353714b050933da46e5af29159c7aa11f Mon Sep 17 00:00:00 2001 From: James Reid-Smith Date: Thu, 12 Dec 2024 12:26:03 -0500 Subject: [PATCH 217/263] Load history when starting a direct debug session (#1046) * Load history when starting a direct debug session When starting a debug session directly with RUBY_DEBUG_IRB_CONSOLE=1 and `require 'debug'; debugger`, IRB's history wasn't loaded. This commit ensures history is loaded in this case by calling `load_history` when configuring IRB for the debugger. Fixes ruby/irb#975 * Update test/irb/test_history.rb * Update lib/irb/debug.rb --------- Co-authored-by: Stan Lo --- lib/irb/debug.rb | 1 + test/irb/test_history.rb | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/lib/irb/debug.rb b/lib/irb/debug.rb index cd64b77ad..59be1365b 100644 --- a/lib/irb/debug.rb +++ b/lib/irb/debug.rb @@ -81,6 +81,7 @@ def configure_irb_for_debugger(irb) IRB.instance_variable_set(:@debugger_irb, irb) irb.context.with_debugger = true irb.context.irb_name += ":rdbg" + irb.context.io.load_history if irb.context.io.class < HistorySavingAbility end module SkipPathHelperForIRB diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index 15c16ed89..021bb682c 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -488,6 +488,36 @@ def foo HISTORY end + def test_direct_debug_session_loads_history + @envs['RUBY_DEBUG_IRB_CONSOLE'] = "1" + write_history <<~HISTORY + old_history_1 + old_history_2 + old_history_3 + HISTORY + + write_ruby <<~'RUBY' + require 'debug' + debugger + binding.irb # needed to satisfy run_ruby_file + RUBY + + output = run_ruby_file do + type "history" + type "puts 'foo'" + type "history" + type "exit!" + end + + assert_include(output, "irb:rdbg(main):002") # assert that we're in an irb:rdbg session + assert_include(output, "5: history") + assert_include(output, "4: puts 'foo'") + assert_include(output, "3: history") + assert_include(output, "2: old_history_3") + assert_include(output, "1: old_history_2") + assert_include(output, "0: old_history_1") + end + private def write_history(history) From f57025a35eac0854cf565bb1907e52414f727203 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 14 Dec 2024 01:08:28 +0800 Subject: [PATCH 218/263] Avoid generating documentation pages for internal components (#1047) --- .document | 6 +++++- Rakefile | 2 -- lib/irb.rb | 2 +- lib/irb/command/base.rb | 10 +++------- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/.document b/.document index ab607a7f1..39cee3186 100644 --- a/.document +++ b/.document @@ -1,4 +1,8 @@ LICENSE.txt README.md +EXTEND_IRB.md +COMPARED_WITH_PRY.md doc/irb/indexes.md -lib/**/*.rb +lib/irb.rb +lib/irb/context.rb +lib/irb/command/base.rb diff --git a/Rakefile b/Rakefile index 87c700aba..aaf5a936a 100644 --- a/Rakefile +++ b/Rakefile @@ -46,8 +46,6 @@ task :default => :test RDoc::Task.new do |rdoc| rdoc.title = "IRB" - rdoc.rdoc_files.include("*.md", "lib/**/*.rb") - rdoc.rdoc_files.exclude("lib/irb/xmp.rb") rdoc.rdoc_dir = "docs" rdoc.main = "README.md" rdoc.options.push("--copy-files", "LICENSE.txt") diff --git a/lib/irb.rb b/lib/irb.rb index f5a875d08..de1cdf026 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -879,7 +879,7 @@ module IRB # An exception raised by IRB.irb_abort - class Abort < Exception;end + class Abort < Exception;end # :nodoc: class << self # The current IRB::Context of the session, see IRB.conf diff --git a/lib/irb/command/base.rb b/lib/irb/command/base.rb index af810ed34..2f39b75cc 100644 --- a/lib/irb/command/base.rb +++ b/lib/irb/command/base.rb @@ -5,13 +5,11 @@ # module IRB - # :stopdoc: - module Command - class CommandArgumentError < StandardError; end + class CommandArgumentError < StandardError; end # :nodoc: class << self - def extract_ruby_args(*args, **kwargs) + def extract_ruby_args(*args, **kwargs) # :nodoc: throw :EXTRACT_RUBY_ARGS, [args, kwargs] end end @@ -57,8 +55,6 @@ def execute(arg) end end - Nop = Base + Nop = Base # :nodoc: end - - # :startdoc: end From cdc88fe87f042440cf526d097fc8690c8567d83f Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 15 Dec 2024 03:37:53 +0800 Subject: [PATCH 219/263] Fix broken rdoc-ref caused by a typo (#1049) --- lib/irb.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/irb.rb b/lib/irb.rb index de1cdf026..3aff5deff 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -75,7 +75,7 @@ # 2. Constructs the initial session context from [hash # IRB.conf](rdoc-ref:IRB@Hash+IRB.conf) and from default values; the hash # content may have been affected by [command-line -# options](rdoc-ref:IB@Command-Line+Options), and by direct assignments in +# options](rdoc-ref:IRB@Command-Line+Options), and by direct assignments in # the configuration file. # 3. Assigns the context to variable `conf`. # 4. Assigns command-line arguments to variable `ARGV`. From 49050f9bf3f5a7be42719e07cf6af1a430900c86 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 18 Dec 2024 18:20:02 +0800 Subject: [PATCH 220/263] Bump version to v1.14.3 (#1050) --- lib/irb/version.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irb/version.rb b/lib/irb/version.rb index 825233e21..cae57e127 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -5,7 +5,7 @@ # module IRB # :nodoc: - VERSION = "1.14.2" + VERSION = "1.14.3" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2024-12-12" + @LAST_UPDATE_DATE = "2024-12-18" end From 52e77dd113a7450c1324057fc061a58ff8b15e4b Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 28 Dec 2024 23:55:20 +0800 Subject: [PATCH 221/263] Add `ri` an alias to the `show_doc` command (#1054) --- lib/irb/default_commands.rb | 3 ++- test/irb/test_command.rb | 48 +++++++++++++++++++++++++++---------- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/lib/irb/default_commands.rb b/lib/irb/default_commands.rb index 768fbee9d..533bdfc87 100644 --- a/lib/irb/default_commands.rb +++ b/lib/irb/default_commands.rb @@ -218,7 +218,8 @@ def load_command(command) ) _register_with_aliases(:irb_show_doc, Command::ShowDoc, - [:show_doc, NO_OVERRIDE] + [:show_doc, NO_OVERRIDE], + [:ri, NO_OVERRIDE] ) _register_with_aliases(:irb_info, Command::IrbInfo) diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index 21e05752b..286fe0476 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -4,6 +4,15 @@ require_relative "helper" module TestIRB + # In case when RDoc becomes a bundled gem, we may not be able to load it when running tests + # in ruby/ruby + HAS_RDOC = begin + require "rdoc" + true + rescue LoadError + false + end + class CommandTestCase < TestCase def setup @pwd = Dir.pwd @@ -767,18 +776,33 @@ def test_ls_with_no_singleton_class end class ShowDocTest < CommandTestCase - def test_show_doc - out, err = execute_lines("show_doc String#gsub") - - # the former is what we'd get without document content installed, like on CI - # the latter is what we may get locally - possible_rdoc_output = [/Nothing known about String#gsub/, /gsub\(pattern\)/] - assert_not_include err, "[Deprecation]" - assert(possible_rdoc_output.any? { |output| output.match?(out) }, "Expect the `show_doc` command to match one of the possible outputs. Got:\n#{out}") - ensure - # this is the only way to reset the redefined method without coupling the test with its implementation - EnvUtil.suppress_warning { load "irb/command/help.rb" } - end if defined?(RDoc) + if HAS_RDOC + def test_show_doc + out, err = execute_lines("show_doc String#gsub") + + # the former is what we'd get without document content installed, like on CI + # the latter is what we may get locally + possible_rdoc_output = [/Nothing known about String#gsub/, /gsub\(pattern\)/] + assert_not_include err, "[Deprecation]" + assert(possible_rdoc_output.any? { |output| output.match?(out) }, "Expect the `show_doc` command to match one of the possible outputs. Got:\n#{out}") + ensure + # this is the only way to reset the redefined method without coupling the test with its implementation + EnvUtil.suppress_warning { load "irb/command/help.rb" } + end + + def test_ri + out, err = execute_lines("ri String#gsub") + + # the former is what we'd get without document content installed, like on CI + # the latter is what we may get locally + possible_rdoc_output = [/Nothing known about String#gsub/, /gsub\(pattern\)/] + assert_not_include err, "[Deprecation]" + assert(possible_rdoc_output.any? { |output| output.match?(out) }, "Expect the `ri` command to match one of the possible outputs. Got:\n#{out}") + ensure + # this is the only way to reset the redefined method without coupling the test with its implementation + EnvUtil.suppress_warning { load "irb/command/help.rb" } + end + end def test_show_doc_without_rdoc _, err = without_rdoc do From 30de714552a25df7c6854f97ab3c975181b2aa28 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 29 Dec 2024 02:21:25 +0800 Subject: [PATCH 222/263] Restructure IRB's documentation (#1053) Currently, part of the documentation lives in `lib/irb.rb` as RDoc comments, and the rest lives in `README.md`. The RDoc comments are not for viewing on GitHub, while README.md is. So this means users need to read part of the documentation on https://ruby.github.io/irb/, and the rest on GitHub. This is not a great user experience. This commit restructures the documentation so all major documentation is in `doc/index.md` that will become the main documentation for IRB on https://ruby.github.io/irb/. This solves a 2 main problems: 1. Users can read IRB documentation all in one place, on https://ruby.github.io/irb/. 2. It makes updating the documentation easier, especially the content that currently lives in `lib/irb.rb`. --- .document | 8 - .github/workflows/gh-pages.yml | 2 - .gitignore | 2 +- .rdoc_options | 2 + Rakefile | 8 +- doc/.document | 1 + doc/COMMAND_LINE_OPTIONS.md | 69 ++ .../COMPARED_WITH_PRY.md | 0 doc/{irb/indexes.md => Configurations.md} | 235 +++-- doc/EXTEND_IRB.md | 122 +++ doc/Index.md | 698 ++++++++++++++ irb.gemspec | 1 - lib/.document | 3 + lib/irb.rb | 852 ------------------ test/irb/yamatanooroti/test_rendering.rb | 4 +- 15 files changed, 1061 insertions(+), 946 deletions(-) delete mode 100644 .document create mode 100644 .rdoc_options create mode 100644 doc/.document create mode 100644 doc/COMMAND_LINE_OPTIONS.md rename COMPARED_WITH_PRY.md => doc/COMPARED_WITH_PRY.md (100%) rename doc/{irb/indexes.md => Configurations.md} (51%) create mode 100644 doc/EXTEND_IRB.md create mode 100644 doc/Index.md create mode 100644 lib/.document diff --git a/.document b/.document deleted file mode 100644 index 39cee3186..000000000 --- a/.document +++ /dev/null @@ -1,8 +0,0 @@ -LICENSE.txt -README.md -EXTEND_IRB.md -COMPARED_WITH_PRY.md -doc/irb/indexes.md -lib/irb.rb -lib/irb/context.rb -lib/irb/command/base.rb diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 1efe11e33..5eb32bc48 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -33,8 +33,6 @@ jobs: run: bundle exec rake rdoc - name: Upload artifact uses: actions/upload-pages-artifact@v3 - with: - path: "docs/" deploy: environment: diff --git a/.gitignore b/.gitignore index be35d6352..b4f2f43d1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,5 @@ /pkg/ /spec/reports/ /tmp/ -/docs/ Gemfile.lock +_site/ diff --git a/.rdoc_options b/.rdoc_options new file mode 100644 index 000000000..81831200d --- /dev/null +++ b/.rdoc_options @@ -0,0 +1,2 @@ +page_dir: doc +warn_missing_rdoc_ref: true diff --git a/Rakefile b/Rakefile index aaf5a936a..e956107ad 100644 --- a/Rakefile +++ b/Rakefile @@ -45,8 +45,8 @@ end task :default => :test RDoc::Task.new do |rdoc| - rdoc.title = "IRB" - rdoc.rdoc_dir = "docs" - rdoc.main = "README.md" - rdoc.options.push("--copy-files", "LICENSE.txt") + rdoc.title = "IRB Documentation" + rdoc.main = "Index.md" + rdoc.rdoc_dir = "_site" + rdoc.options.push("lib") end diff --git a/doc/.document b/doc/.document new file mode 100644 index 000000000..dd449725e --- /dev/null +++ b/doc/.document @@ -0,0 +1 @@ +*.md diff --git a/doc/COMMAND_LINE_OPTIONS.md b/doc/COMMAND_LINE_OPTIONS.md new file mode 100644 index 000000000..babbdece7 --- /dev/null +++ b/doc/COMMAND_LINE_OPTIONS.md @@ -0,0 +1,69 @@ +# Index of Command-Line Options + +These are the \IRB command-line options, with links to explanatory text: + +- `-d`: Set `$DEBUG` and {$VERBOSE}[rdoc-ref:IRB@Verbosity] + to `true`. +- `-E _ex_[:_in_]`: Set initial external (ex) and internal (in) + {encodings}[rdoc-ref:IRB@Encodings] (same as `ruby -E`). +- `-f`: Don't initialize from {configuration file}[rdoc-ref:IRB@Configuration+File]. +- `-I _dirpath_`: Specify {$LOAD_PATH directory}[rdoc-ref:IRB@Load+Modules] + (same as `ruby -I`). +- `-r _load-module_`: Require {load-module}[rdoc-ref:IRB@Load+Modules] + (same as `ruby -r`). +- `-U`: Set external and internal {encodings}[rdoc-ref:IRB@Encodings] to UTF-8. +- `-w`: Suppress {warnings}[rdoc-ref:IRB@Warnings] (same as `ruby -w`). +- `-W[_level_]`: Set {warning level}[rdoc-ref:IRB@Warnings]; + 0=silence, 1=medium, 2=verbose (same as `ruby -W`). +- `--autocomplete`: Use {auto-completion}[rdoc-ref:IRB@Automatic+Completion]. +- `--back-trace-limit _n_`: Set a {backtrace limit}[rdoc-ref:IRB@Tracer]; + display at most the top `n` and bottom `n` entries. +- `--colorize`: Use {color-highlighting}[rdoc-ref:IRB@Color+Highlighting] + for input and output. +- `--context-mode _n_`: Select method to create Binding object + for new {workspace}[rdoc-ref:IRB@Commands]; `n` in range `0..4`. +- `--echo`: Print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + return values. +- `--extra-doc-dir _dirpath_`: + Add a {documentation directory}[rdoc-ref:IRB@RI+Documentation+Directories] + for the documentation dialog. +- `--inf-ruby-mode`: Set prompt mode to {:INF_RUBY}[rdoc-ref:IRB@Pre-Defined+Prompts] + (appropriate for `inf-ruby-mode` on Emacs); + suppresses --multiline and --singleline. +- `--inspect`: Use method `inspect` for printing ({echoing}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + return values. +- `--multiline`: Use the multiline editor as the {input method}[rdoc-ref:IRB@Input+Method]. +- `--noautocomplete`: Don't use {auto-completion}[rdoc-ref:IRB@Automatic+Completion]. +- `--nocolorize`: Don't use {color-highlighting}[rdoc-ref:IRB@Color+Highlighting] + for input and output. +- `--noecho`: Don't print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + return values. +- `--noecho-on-assignment`: Don't print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + result on assignment. +- `--noinspect`: Don't se method `inspect` for printing ({echoing}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + return values. +- `--nomultiline`: Don't use the multiline editor as the {input method}[rdoc-ref:IRB@Input+Method]. +- `--noprompt`: Don't print {prompts}[rdoc-ref:IRB@Prompt+and+Return+Formats]. +- `--noscript`: Treat the first command-line argument as a normal + {command-line argument}[rdoc-ref:IRB@Initialization+Script], + and include it in `ARGV`. +- `--nosingleline`: Don't use the singleline editor as the {input method}[rdoc-ref:IRB@Input+Method]. +- `--noverbose`: Don't print {verbose}[rdoc-ref:IRB@Verbosity] details. +- `--prompt _mode_`, `--prompt-mode _mode_`: + Set {prompt and return formats}[rdoc-ref:IRB@Prompt+and+Return+Formats]; + `mode` may be a {pre-defined prompt}[rdoc-ref:IRB@Pre-Defined+Prompts] + or the name of a {custom prompt}[rdoc-ref:IRB@Custom+Prompts]. +- `--script`: Treat the first command-line argument as the path to an + {initialization script}[rdoc-ref:IRB@Initialization+Script], + and omit it from `ARGV`. +- `--simple-prompt`, `--sample-book-mode`: + Set prompt mode to {:SIMPLE}[rdoc-ref:IRB@Pre-Defined+Prompts]. +- `--singleline`: Use the singleline editor as the {input method}[rdoc-ref:IRB@Input+Method]. +- `--tracer`: Use {Tracer}[rdoc-ref:IRB@Tracer] to print a stack trace for each input command. +- `--truncate-echo-on-assignment`: Print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) + truncated result on assignment. +- `--verbose`Print {verbose}[rdoc-ref:IRB@Verbosity] details. +- `-v`, `--version`: Print the {IRB version}[rdoc-ref:IRB@Version]. +- `-h`, `--help`: Print the {IRB help text}[rdoc-ref:IRB@Help]. +- `--`: Separate options from {arguments}[rdoc-ref:IRB@Command-Line+Arguments] + on the command-line. diff --git a/COMPARED_WITH_PRY.md b/doc/COMPARED_WITH_PRY.md similarity index 100% rename from COMPARED_WITH_PRY.md rename to doc/COMPARED_WITH_PRY.md diff --git a/doc/irb/indexes.md b/doc/Configurations.md similarity index 51% rename from doc/irb/indexes.md rename to doc/Configurations.md index d6dc5c160..755c88095 100644 --- a/doc/irb/indexes.md +++ b/doc/Configurations.md @@ -1,79 +1,51 @@ -## Indexes - -### Index of Command-Line Options - -These are the \IRB command-line options, with links to explanatory text: - -- `-d`: Set `$DEBUG` and {$VERBOSE}[rdoc-ref:IRB@Verbosity] - to `true`. -- `-E _ex_[:_in_]`: Set initial external (ex) and internal (in) - {encodings}[rdoc-ref:IRB@Encodings] (same as `ruby -E`). -- `-f`: Don't initialize from {configuration file}[rdoc-ref:IRB@Configuration+File]. -- `-I _dirpath_`: Specify {$LOAD_PATH directory}[rdoc-ref:IRB@Load+Modules] - (same as `ruby -I`). -- `-r _load-module_`: Require {load-module}[rdoc-ref:IRB@Load+Modules] - (same as `ruby -r`). -- `-U`: Set external and internal {encodings}[rdoc-ref:IRB@Encodings] to UTF-8. -- `-w`: Suppress {warnings}[rdoc-ref:IRB@Warnings] (same as `ruby -w`). -- `-W[_level_]`: Set {warning level}[rdoc-ref:IRB@Warnings]; - 0=silence, 1=medium, 2=verbose (same as `ruby -W`). -- `--autocomplete`: Use {auto-completion}[rdoc-ref:IRB@Automatic+Completion]. -- `--back-trace-limit _n_`: Set a {backtrace limit}[rdoc-ref:IRB@Tracer]; - display at most the top `n` and bottom `n` entries. -- `--colorize`: Use {color-highlighting}[rdoc-ref:IRB@Color+Highlighting] - for input and output. -- `--context-mode _n_`: Select method to create Binding object - for new {workspace}[rdoc-ref:IRB@Commands]; `n` in range `0..4`. -- `--echo`: Print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) - return values. -- `--extra-doc-dir _dirpath_`: - Add a {documentation directory}[rdoc-ref:IRB@RI+Documentation+Directories] - for the documentation dialog. -- `--inf-ruby-mode`: Set prompt mode to {:INF_RUBY}[rdoc-ref:IRB@Pre-Defined+Prompts] - (appropriate for `inf-ruby-mode` on Emacs); - suppresses --multiline and --singleline. -- `--inspect`: Use method `inspect` for printing ({echoing}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) - return values. -- `--multiline`: Use the multiline editor as the {input method}[rdoc-ref:IRB@Input+Method]. -- `--noautocomplete`: Don't use {auto-completion}[rdoc-ref:IRB@Automatic+Completion]. -- `--nocolorize`: Don't use {color-highlighting}[rdoc-ref:IRB@Color+Highlighting] - for input and output. -- `--noecho`: Don't print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) - return values. -- `--noecho-on-assignment`: Don't print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) - result on assignment. -- `--noinspect`: Don't se method `inspect` for printing ({echoing}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) - return values. -- `--nomultiline`: Don't use the multiline editor as the {input method}[rdoc-ref:IRB@Input+Method]. -- `--noprompt`: Don't print {prompts}[rdoc-ref:IRB@Prompt+and+Return+Formats]. -- `--noscript`: Treat the first command-line argument as a normal - {command-line argument}[rdoc-ref:IRB@Initialization+Script], - and include it in `ARGV`. -- `--nosingleline`: Don't use the singleline editor as the {input method}[rdoc-ref:IRB@Input+Method]. -- `--noverbose`: Don't print {verbose}[rdoc-ref:IRB@Verbosity] details. -- `--prompt _mode_`, `--prompt-mode _mode_`: - Set {prompt and return formats}[rdoc-ref:IRB@Prompt+and+Return+Formats]; - `mode` may be a {pre-defined prompt}[rdoc-ref:IRB@Pre-Defined+Prompts] - or the name of a {custom prompt}[rdoc-ref:IRB@Custom+Prompts]. -- `--script`: Treat the first command-line argument as the path to an - {initialization script}[rdoc-ref:IRB@Initialization+Script], - and omit it from `ARGV`. -- `--simple-prompt`, `--sample-book-mode`: - Set prompt mode to {:SIMPLE}[rdoc-ref:IRB@Pre-Defined+Prompts]. -- `--singleline`: Use the singleline editor as the {input method}[rdoc-ref:IRB@Input+Method]. -- `--tracer`: Use {Tracer}[rdoc-ref:IRB@Tracer] to print a stack trace for each input command. -- `--truncate-echo-on-assignment`: Print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) - truncated result on assignment. -- `--verbose`Print {verbose}[rdoc-ref:IRB@Verbosity] details. -- `-v`, `--version`: Print the {IRB version}[rdoc-ref:IRB@Version]. -- `-h`, `--help`: Print the {IRB help text}[rdoc-ref:IRB@Help]. -- `--`: Separate options from {arguments}[rdoc-ref:IRB@Command-Line+Arguments] - on the command-line. - -### Index of \IRB.conf Entries - -These are the keys for hash \IRB.conf entries, with links to explanatory text; -for each entry that is pre-defined, the initial value is given: +# Configure \IRB + +## Configuration Sources + +\IRB configurations can be set through multiple sources, each with its own precedence: + +1. **Command-Line Options**: When some options are specified when starting \IRB, they can override default settings. +2. **Configuration File**: If present, \IRB reads a configuration file containing Ruby code to set configurations. +3. **Environment Variables**: Certain environment variables influence \IRB's behavior. +4. **Hash `IRB.conf`**: This hash holds the current configuration settings, which can be modified during a session. + +### Configuration File Path Resolution + +\IRB searches for a configuration file in the following order: + +1. `$IRBRC` +2. `$XDG_CONFIG_HOME/irb/irbrc` +3. `$HOME/.irbrc` +4. `$HOME/.config/irb/irbrc` +5. `.irbrc` in the current directory +6. `irb.rc` in the current directory +7. `_irbrc` in the current directory +8. `$irbrc` in the current directory + +If the `-f` command-line option is used, no configuration file is loaded. + +Method `conf.rc?` returns `true` if a configuration file was read, `false` otherwise. Hash entry `IRB.conf[:RC]` also contains that value. + +## Environment Variables + +- `NO_COLOR`: Disables \IRB's colorization. +- `IRB_USE_AUTOCOMPLETE`: Setting to `false` disables autocompletion. +- `IRB_COMPLETOR`: Configures auto-completion behavior (`regexp` or `type`). +- `VISUAL` / `EDITOR`: Specifies the editor for the `edit` command. +- `IRBRC`: Specifies the rc-file for configuration. +- `XDG_CONFIG_HOME`: Used to locate the rc-file if `IRBRC` is unset. +- `RI_PAGER` / `PAGER`: Specifies the pager for documentation. +- `IRB_LANG`, `LC_MESSAGES`, `LC_ALL`, `LANG`: Determines the locale. + +## Hash `IRB.conf` + +The initial entries in hash `IRB.conf` are determined by: + +- Default values. +- Command-line options, which may override defaults. +- Direct assignments in the configuration file. + +You can see the hash by typing `IRB.conf`. Below are the primary entries: - `:AP_NAME`: \IRB {application name}[rdoc-ref:IRB@Application+Name]; initial value: `'irb'`. @@ -187,3 +159,114 @@ for each entry that is pre-defined, the initial value is given: initial value: `nil`. - `:__MAIN__`: The main \IRB object; initial value: `main`. + +## Notes on Initialization Precedence + +- Any conflict between an entry in hash `IRB.conf` and a command-line option is resolved in favor of the hash entry. +- Hash `IRB.conf` affects the context only once, when the configuration file is interpreted; any subsequent changes to it do not affect the context and are therefore essentially meaningless. + +## Load Modules + +You can specify the names of modules that are to be required at startup. + +Array `conf.load_modules` determines the modules (if any) that are to be required during session startup. The array is used only during session startup, so the initial value is the only one that counts. + +The default initial value is `[]` (load no modules): + +```console +irb(main):001> conf.load_modules +=> [] +``` + +You can set the default initial value via: + +- Command-line option `-r` + + ```console + $ irb -r csv -r json + irb(main):001> conf.load_modules + => ["csv", "json"] + ``` + +- Hash entry `IRB.conf[:LOAD_MODULES] = *array*`: + + ```ruby + IRB.conf[:LOAD_MODULES] = %w[csv json] + ``` + +Note that the configuration file entry overrides the command-line options. + +## RI Documentation Directories + +You can specify the paths to RI documentation directories that are to be loaded (in addition to the default directories) at startup; see details about RI by typing `ri --help`. + +Array `conf.extra_doc_dirs` determines the directories (if any) that are to be loaded during session startup. The array is used only during session startup, so the initial value is the only one that counts. + +The default initial value is `[]` (load no extra documentation): + +```console +irb(main):001> conf.extra_doc_dirs +=> [] +``` + +You can set the default initial value via: + +- Command-line option `--extra_doc_dir` + + ```console + $ irb --extra-doc-dir your_doc_dir --extra-doc-dir my_doc_dir + irb(main):001> conf.extra_doc_dirs + => ["your_doc_dir", "my_doc_dir"] + ``` + +- Hash entry `IRB.conf[:EXTRA_DOC_DIRS] = *array*`: + + ```ruby + IRB.conf[:EXTRA_DOC_DIRS] = %w[your_doc_dir my_doc_dir] + ``` + +Note that the configuration file entry overrides the command-line options. + +## \IRB Name + +You can specify a name for \IRB. + +The default initial value is `'irb'`: + +```console +irb(main):001> conf.irb_name +=> "irb" +``` + +You can set the default initial value via hash entry `IRB.conf[:IRB_NAME] = *string*`: + +```ruby +IRB.conf[:IRB_NAME] = 'foo' +``` + +## Application Name + +You can specify an application name for the \IRB session. + +The default initial value is `'irb'`: + +```console +irb(main):001> conf.ap_name +=> "irb" +``` + +You can set the default initial value via hash entry `IRB.conf[:AP_NAME] = *string*`: + +```ruby +IRB.conf[:AP_NAME] = 'my_ap_name' +``` + +## Configuration Monitor + +You can monitor changes to the configuration by assigning a proc to `IRB.conf[:IRB_RC]` in the configuration file: + +```console +IRB.conf[:IRB_RC] = proc {|conf| puts conf.class } +``` + +Each time the configuration is changed, that proc is called with argument `conf`: diff --git a/doc/EXTEND_IRB.md b/doc/EXTEND_IRB.md new file mode 100644 index 000000000..684b0b7b1 --- /dev/null +++ b/doc/EXTEND_IRB.md @@ -0,0 +1,122 @@ +# Extend IRB + +From v1.13.0, IRB provides official APIs to extend its functionality. This feature allows libraries to +customize and enhance their users' IRB sessions by adding new commands and helper methods tailored for +the libraries. + +## Helper Methods vs. Commands + +- Use a helper method if the operation is meant to return a Ruby object that interacts with the application. + - For example, an `admin_user` helper method that returns `User.where(admin: true).first`, which can then be used like `login_as(admin_user)`. +- Use a command if the operation fits one of the following: + - A utility operation that performs non-Ruby related tasks, such as IRB's `edit` command. + - Displays information, like the `show_source` command. + - If the operation requires non-Ruby syntax arguments, like `ls -g pattern`. + +If you don't know what to pick, go with commands first. Commands are generally safer as they can handle a wider variety of inputs and use cases. + +## Commands + +Commands are designed to complete certain tasks or display information for the user, similar to shell commands. +Therefore, they are designed to accept a variety of inputs, including those that are not valid Ruby code, such +as `my_cmd Foo#bar` or `my_cmd --flag foo`. + +### Example + +```rb +require "irb/command" + +class Greet < IRB::Command::Base + category "Greeting" + description "Greets the user" + help_message <<~HELP + Greets the user with the given name. + + Usage: greet + HELP + + # Any input after the command name will be passed as a single string. + # If nothing is added after the command, an empty string will be passed. + def execute(arg) + puts "Hello! #{arg}" + end +end + +IRB::Command.register(:greet, Greet) +``` + +As long as the above code is loaded before the IRB session is started, such as in a loaded library or a user's `.irbrc` file, `greet` will be accessible to the user. + +```txt +irb(main):001> greet +Hello! +=> nil +irb(main):002> greet Stan +Hello! Stan +=> nil +``` + +And because the `Greet` command introduces a new category, `Greeting`, a new help message category will be created: + +```txt +Help + help List all available commands. Use `help ` to get information about a specific command. + +Greeting + greet Greets the user + +IRB + context Displays current configuration. + ... +``` + +If the optional `help_message` attribute is specified, `help greet` will also display it: + +```txt +irb(main):001> help greet +Greets the user with the given name. + +Usage: greet +``` + +## Helper methods + +Helper methods are designed to be used as Ruby methods, such as `my_helper(arg, kwarg: val).foo`. + +The main use case of helper methods is to provide shortcuts for users, providing quick and easy access to +frequently used operations or components within the IRB session. For example, a helper method might simplify +the process of fetching and displaying specific configuration settings or data structures that would otherwise +require multiple steps to access. + +### Example + +```rb +# This only loads the minimum components required to define and register a helper method. +# It does not load the entire IRB, nor does it initialize it. +require "irb/helper_method" + +class MyHelper < IRB::HelperMethod::Base + description "This is a test helper" + + def execute(arg, kwarg:) + "arg: #{arg}, kwarg: #{kwarg}" + end +end + +IRB::HelperMethod.register(:my_helper, MyHelper) +``` + +As long as the above code is loaded before the IRB session is started, such as in a loaded library or a user's `.irbrc` file, `my_helper` will be accessible to the user. + +```txt +irb(main):001> my_helper("foo", kwarg: "bar").upcase +=> "ARG: FOO, KWARG: BAR" +``` + +The registered helper methods will also be listed in the help message's `Helper methods` section: + +```txt +Helper methods + conf Returns the current context. + my_helper This is a test helper +``` diff --git a/doc/Index.md b/doc/Index.md new file mode 100644 index 000000000..24d216885 --- /dev/null +++ b/doc/Index.md @@ -0,0 +1,698 @@ +# \IRB + +[![Gem Version](https://badge.fury.io/rb/irb.svg)](https://badge.fury.io/rb/irb) +[![build](https://github.com/ruby/irb/actions/workflows/test.yml/badge.svg)](https://github.com/ruby/irb/actions/workflows/test.yml) + +## Overview + +\IRB stands for "Interactive Ruby" and is a tool to interactively execute Ruby expressions read from the standard input. The `irb` command from your shell will start the interpreter. + +\IRB provides a shell-like interface that supports user interaction with the Ruby interpreter. It operates as a *read-eval-print loop* ([REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)) that: + +- **Reads** each character as you type. You can modify the \IRB context to change the way input works. See [Input](#label-Input). +- **Evaluates** the code each time it has read a syntactically complete passage. +- **Prints** after evaluating. You can modify the \IRB context to change the way output works. See [Output](#label-Output). + +## Installation + +> **Note** +> +> \IRB is a default gem of Ruby, so you shouldn't need to install it separately. However, if you're using Ruby 2.6 or later and want to upgrade/install a specific version of \IRB, follow these steps. + +To install it with `bundler`, add this line to your application's Gemfile: + +```ruby +gem 'irb' +``` + +Then execute: + +```console +$ bundle +``` + +Or install it directly with: + +```console +$ gem install irb +``` + +## Usage + +> **Note** +> +> We're working hard to match Pry's variety of powerful features in \IRB. Track our progress or find contribution ideas in [COMPARED_WITH_PRY.md](./COMPARED_WITH_PRY.md). + +### Starting \IRB + +You can start a fresh \IRB session by typing `irb` in your terminal. In the session, you can evaluate Ruby expressions or prototype small Ruby scripts. Input is executed when it is syntactically complete. + +```console +$ irb +irb(main):001> 1 + 2 +=> 3 +irb(main):002* class Foo +irb(main):003* def foo +irb(main):004* puts 1 +irb(main):005* end +irb(main):006> end +=> :foo +irb(main):007> Foo.new.foo +1 +=> nil +``` + +### The `binding.irb` Breakpoint + +If you use Ruby 2.5 or later versions, you can use `binding.irb` in your program as breakpoints. Once `binding.irb` is evaluated, a new \IRB session starts with the surrounding context: + +```console +$ ruby test.rb + +From: test.rb @ line 2 : + + 1: def greet(word) + => 2: binding.irb + 3: puts "Hello #{word}" + 4: end + 5: + 6: greet("World") + +irb(main):001> word +=> "World" +irb(main):002> exit +Hello World +``` + +### Debugging + +You can use \IRB as a debugging console with `debug.gem` with these options: + +- In `binding.irb`, use the `debug` command to start an `irb:rdbg` session with access to all `debug.gem` commands. +- Use the `RUBY_DEBUG_IRB_CONSOLE=1` environment variable to make `debug.gem` use \IRB as the debugging console. + +To learn more about debugging with \IRB, see [Debugging with \IRB](#label-Debugging+with+IRB). + +## Startup + +At startup, \IRB: + +1. Interprets (as Ruby code) the content of the [configuration file](#label-Configuration) (if given). +2. Constructs the initial session context from [hash IRB.conf](#label-Hash+IRB.conf) and from default values; the hash content may have been affected by [command-line options](#command-line-options), and by direct assignments in the configuration file. +3. Assigns the context to variable `conf`. +4. Assigns command-line arguments to variable `ARGV`. +5. Prints the prompt. +6. Puts the content of the [initialization script](#label-Initialization+script) onto the \IRB shell, just as if it were user-typed commands. + +## Command Line + +On the command line, all options precede all arguments; the first item that is not recognized as an option is treated as an argument, as are all items that follow. + +### Command-Line Options + +Many command-line options affect entries in hash `IRB.conf`, which in turn affect the initial configuration of the \IRB session. + +Details of the options are described in relevant subsections below. A cursory list of \IRB command-line options may be seen in the [help message](https://raw.githubusercontent.com/ruby/irb/master/lib/irb/lc/help-message), which is also displayed if you use command-line option `--help`. + +If you are interested in a specific option, consult the [index](rdoc-ref:COMMAND_LINE_OPTIONS.md). + +### Command-Line Arguments + +Command-line arguments are passed to \IRB in array `ARGV`: + +```console +$ irb --noscript Foo Bar Baz +irb(main):001> ARGV +=> ["Foo", "Bar", "Baz"] +irb(main):002> exit +$ +``` + +Command-line option `--` causes everything that follows to be treated as arguments, even those that look like options: + +```console +$ irb --noscript -- --noscript -- Foo Bar Baz +irb(main):001> ARGV +=> ["--noscript", "--", "Foo", "Bar", "Baz"] +irb(main):002> exit +$ +``` + +## Commands + +The following commands are available in \IRB. Use the `help` command to see the list of available commands. + +```txt +Help + help List all available commands. Use `help ` to get information about a specific command. + +IRB + context Displays current configuration. + exit Exit the current irb session. + exit! Exit the current process. + irb_load Load a Ruby file. + irb_require Require a Ruby file. + source Loads a given file in the current session. + irb_info Show information about IRB. + history Shows the input history. `-g [query]` or `-G [query]` allows you to filter the output. + disable_irb Disable binding.irb. + +Workspace + cwws Show the current workspace. + chws Change the current workspace to an object. + workspaces Show workspaces. + pushws Push an object to the workspace stack. + popws Pop a workspace from the workspace stack. + cd Move into the given object or leave the current context. + +Multi-irb (DEPRECATED) + irb Start a child IRB. + jobs List of current sessions. + fg Switches to the session of the given number. + kill Kills the session with the given number. + +Debugging + debug Start the debugger of debug.gem. + break Start the debugger of debug.gem and run its `break` command. + catch Start the debugger of debug.gem and run its `catch` command. + next Start the debugger of debug.gem and run its `next` command. + delete Start the debugger of debug.gem and run its `delete` command. + step Start the debugger of debug.gem and run its `step` command. + continue Start the debugger of debug.gem and run its `continue` command. + finish Start the debugger of debug.gem and run its `finish` command. + backtrace Start the debugger of debug.gem and run its `backtrace` command. + info Start the debugger of debug.gem and run its `info` command. + +Misc + edit Open a file or source location. + measure `measure` enables the mode to measure processing time. `measure :off` disables it. + +Context + show_doc Look up documentation with RI. + ls Show methods, constants, and variables. + show_source Show the source code of a given method, class/module, or constant. + whereami Show the source code around binding.irb again. + +Helper methods + conf Returns the current IRB context. + +Aliases + $ Alias for `show_source` + @ Alias for `whereami` +``` + +## Configure \IRB + +See [Configurations](rdoc-ref:Configurations.md) for more details. + +## Input + +This section describes the features that allow you to change the way \IRB input works; see also [Output](#output). + +### Input Command History + +By default, \IRB stores a history of up to 1000 input commands in a file named `.irb_history`. The history file will be in the same directory as the [configuration file](#label-Configuration+File) if one is found, or in `~/` otherwise. + +A new \IRB session creates the history file if it does not exist and appends to the file if it does exist. + +You can change the filepath by adding to your configuration file: +`IRB.conf[:HISTORY_FILE] = *filepath*`, where *filepath* is a string filepath. + +During the session, method `conf.history_file` returns the filepath, and method `conf.history_file = *new_filepath*` copies the history to the file at *new_filepath*, which becomes the history file for the session. + +You can change the number of commands saved by adding to your configuration file: `IRB.conf[:SAVE_HISTORY] = *n*`, where *n* is one of: + +- Positive integer: the number of commands to be saved. +- Negative integer: all commands are to be saved. +- Zero or `nil`: no commands are to be saved. + +During the session, you can use methods `conf.save_history` or `conf.save_history=` to retrieve or change the count. + +### Command Aliases + +By default, \IRB defines several command aliases: + +```console +irb(main):001> conf.command_aliases +=> {:"$"=>:show_source, :"@"=>:whereami} +``` + +You can change the initial aliases in the configuration file with: + +```ruby +IRB.conf[:COMMAND_ALIASES] = {foo: :show_source, bar: :whereami} +``` + +You can replace the current aliases at any time with configuration method `conf.command_aliases=`; because `conf.command_aliases` is a hash, you can modify it. + +### End-of-File + +By default, `IRB.conf[:IGNORE_EOF]` is `false`, which means that typing the end-of-file character `Ctrl-D` causes the session to exit. + +You can reverse that behavior by adding `IRB.conf[:IGNORE_EOF] = true` to the configuration file. + +During the session, method `conf.ignore_eof?` returns the setting, and method `conf.ignore_eof = *boolean*` sets it. + +### SIGINT + +By default, `IRB.conf[:IGNORE_SIGINT]` is `true`, which means that typing the interrupt character `Ctrl-C` causes the session to exit. + +You can reverse that behavior by adding `IRB.conf[:IGNORE_SIGING] = false` to the configuration file. + +During the session, method `conf.ignore_siging?` returns the setting, and method `conf.ignore_sigint = *boolean*` sets it. + +### Automatic Completion + +By default, \IRB enables [automatic completion](https://en.wikipedia.org/wiki/Autocomplete#In_command-line_interpreter): + +You can disable it by either of these: + +- Adding `IRB.conf[:USE_AUTOCOMPLETE] = false` to the configuration file. +- Giving command-line option `--noautocomplete` (`--autocomplete` is the default). + +Method `conf.use_autocomplete?` returns `true` if automatic completion is enabled, `false` otherwise. + +The setting may not be changed during the session. + +### Type Based Completion + +\IRB's default completion `\IRB::RegexpCompletor` uses Regexp. \IRB offers an experimental completion `\IRB::TypeCompletor` that uses type analysis. + +#### How to Enable \IRB::TypeCompletor + +Install [ruby/repl_type_completor](https://github.com/ruby/repl_type_completor/) with: + +```console +$ gem install repl_type_completor +``` + +Or add these lines to your project's Gemfile. + +```ruby +gem 'irb' +gem 'repl_type_completor', group: [:development, :test] +``` + +Now you can use type-based completion by: + +- Running \IRB with the `--type-completor` option + + ```console + $ irb --type-completor + ``` + +- Or writing this line to \IRB's rc-file (e.g., `~/.irbrc`) + + ```ruby + IRB.conf[:COMPLETOR] = :type # default is :regexp + ``` + +- Or setting the environment variable `IRB_COMPLETOR` + + ```ruby + ENV['IRB_COMPLETOR'] = 'type' + IRB.start + ``` + +To check if it's enabled, type `irb_info` into \IRB and see the `Completion` section. + +```console +irb(main):001> irb_info +... +# Enabled +Completion: Autocomplete, ReplTypeCompletor: 0.1.0, Prism: 0.18.0, RBS: 3.3.0 +# Not enabled +Completion: Autocomplete, RegexpCompletor +... +``` + +If you have a `sig/` directory or `rbs_collection.lock.yaml` in the current directory, \IRB will load it. + +#### Advantage over Default \IRB::RegexpCompletor + +\IRB::TypeCompletor can autocomplete chained methods, block parameters, and more if type information is available. These are some examples \IRB::RegexpCompletor cannot complete. + +```console +irb(main):001> 'Ruby'.upcase.chars.s # Array methods (sample, select, shift, size) +``` + +```console +irb(main):001> 10.times.map(&:to_s).each do |s| +irb(main):002> s.up # String methods (upcase, upcase!, upto) +``` + +```console +irb(main):001> class User < ApplicationRecord +irb(main):002> def foo +irb(main):003> sa # save, save! +``` + +As a trade-off, completion calculation takes more time than \IRB::RegexpCompletor. + +#### Difference between Steep's Completion + +Compared with Steep, \IRB::TypeCompletor has some differences and limitations. +```ruby +[0, 'a'].sample. +# Steep completes the intersection of Integer methods and String methods +# IRB::TypeCompletor completes both Integer and String methods +``` + +Some features like type narrowing are not implemented. +```ruby +def f(arg = [0, 'a'].sample) + if arg.is_a?(String) + arg. # Completes both Integer and String methods +``` + +Unlike other static type checkers, \IRB::TypeCompletor uses runtime information to provide better completion. + +```console +irb(main):001> a = [1] +=> [1] +irb(main):002> a.first. # Completes Integer methods +``` + +### Automatic Indentation + +By default, \IRB automatically indents lines of code to show structure (e.g., it indents the contents of a block). + +The current setting is returned by the configuration method `conf.auto_indent_mode`. + +The default initial setting is `true`: + +```console +irb(main):001> conf.auto_indent_mode +=> true +irb(main):002* Dir.entries('.').select do |entry| +irb(main):003* entry.start_with?('R') +irb(main):004> end +=> ["README.md", "Rakefile"] +``` + +You can change the initial setting in the configuration file with: + +```ruby +IRB.conf[:AUTO_INDENT] = false +``` + +Note that the *current* setting *may not* be changed in the \IRB session. + +### Input Method + +The \IRB input method determines how command input is read; by default, the input method for a session is \IRB::RelineInputMethod unless the TERM environment variable is 'dumb', in which case the most simplistic input method is used. + +You can set the input method by: + +- Adding to the configuration file: + + - `IRB.conf[:USE_SINGLELINE] = true` or `IRB.conf[:USE_MULTILINE] = false` sets the input method to \IRB::ReadlineInputMethod. + - `IRB.conf[:USE_SINGLELINE] = false` or `IRB.conf[:USE_MULTILINE] = true` sets the input method to \IRB::RelineInputMethod. + +- Giving command-line options: + + - `--singleline` or `--nomultiline` sets the input method to \IRB::ReadlineInputMethod. + - `--nosingleline` or `--multiline` sets the input method to \IRB::RelineInputMethod. + - `--nosingleline` together with `--nomultiline` sets the input to \IRB::StdioInputMethod. + +Method `conf.use_multiline?` and its synonym `conf.use_reline` return: + +- `true` if option `--multiline` was given. +- `false` if option `--nomultiline` was given. +- `nil` if neither was given. + +Method `conf.use_singleline?` and its synonym `conf.use_readline` return: + +- `true` if option `--singleline` was given. +- `false` if option `--nosingleline` was given. +- `nil` if neither was given. + +## Output + +This section describes the features that allow you to change the way \IRB output works; see also [Input](#label-Input). + +### Return-Value Printing (Echoing) + +By default, \IRB prints (echoes) the values returned by all input commands. + +You can change the initial behavior and suppress all echoing by: + +- Adding to the configuration file: `IRB.conf[:ECHO] = false`. (The default value for this entry is `nil`, which means the same as `true`.) +- Giving command-line option `--noecho`. (The default is `--echo`.) + +During the session, you can change the current setting with configuration method `conf.echo=` (set to `true` or `false`). + +As stated above, by default \IRB prints the values returned by all input commands; but \IRB offers special treatment for values returned by assignment statements, which may be: + +- Printed with truncation (to fit on a single line of output), which is the default; an ellipsis (`...` is suffixed, to indicate the truncation): + + ```console + irb(main):001> x = 'abc' * 100 + > "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc..." + ``` + +- Printed in full (regardless of the length). +- Suppressed (not printed at all). + +You can change the initial behavior by: + +- Adding to the configuration file: `IRB.conf[:ECHO_ON_ASSIGNMENT] = false`. (The default value for this entry is `nil`, which means the same as `:truncate`.) +- Giving command-line option `--noecho-on-assignment` or `--echo-on-assignment`. (The default is `--truncate-echo-on-assignment`.) + +During the session, you can change the current setting with configuration method `conf.echo_on_assignment=` (set to `true`, `false`, or `:truncate`). + +By default, \IRB formats returned values by calling method `inspect`. + +You can change the initial behavior by: + +- Adding to the configuration file: `IRB.conf[:INSPECT_MODE] = false`. (The default value for this entry is `true`.) +- Giving command-line option `--noinspect`. (The default is `--inspect`.) + +During the session, you can change the setting using method `conf.inspect_mode=`. + +### Multiline Output + +By default, \IRB prefixes a newline to a multiline response. + +You can change the initial default value by adding to the configuration file: + +```ruby +IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT] = false +``` + +During a session, you can retrieve or set the value using methods `conf.newline_before_multiline_output?` and `conf.newline_before_multiline_output=`. + +Examples: + +```console +irb(main):001> conf.inspect_mode = false +=> false +irb(main):002> "foo\nbar" +=> +foo +bar +irb(main):003> conf.newline_before_multiline_output = false +=> false +irb(main):004> "foo\nbar" +=> foo +bar +``` + +### Evaluation History + +By default, \IRB saves no history of evaluations (returned values), and the related methods `conf.eval_history`, `_`, and `__` are undefined. + +You can turn on that history and set the maximum number of evaluations to be stored: + +- In the configuration file: add `IRB.conf[:EVAL_HISTORY] = *n*`. (Examples below assume that we've added `IRB.conf[:EVAL_HISTORY] = 5`.) +- In the session (at any time): `conf.eval_history = *n*`. + +If `n` is zero, all evaluation history is stored. + +Doing either of the above: + +- Sets the maximum size of the evaluation history; defines method `conf.eval_history`, which returns the maximum size `n` of the evaluation history: + + ```console + irb(main):001> conf.eval_history = 5 + => 5 + irb(main):002> conf.eval_history + => 5 + ``` + +- Defines variable `_`, which contains the most recent evaluation, or `nil` if none; same as method `conf.last_value`: + + ```console + irb(main):003> _ + => 5 + irb(main):004> :foo + => :foo + irb(main):005> :bar + => :bar + irb(main):006> _ + => :bar + irb(main):007> _ + => :bar + ``` + +- Defines variable `__`: + + - `__` unadorned: contains all evaluation history: + + ```console + irb(main):008> :foo + => :foo + irb(main):009> :bar + => :bar + irb(main):010> :baz + => :baz + irb(main):011> :bat + => :bat + irb(main):012> :bam + => :bam + irb(main):013> __ + => + 9 :bar + 10 :baz + 11 :bat + 12 :bam + 13 ...self-history... + ``` + + Note that when the evaluation is multiline, it is displayed differently. + + - `__[m]`: + + - Positive `m`: contains the evaluation for the given line number, or `nil` if that line number is not in the evaluation history: + + ```console + irb(main):015> __[12] + => :bam + irb(main):016> __[1] + => nil + ``` + + - Negative `m`: contains the `mth`-from-end evaluation, or `nil` if that evaluation is not in the evaluation history: + + ```console + irb(main):017> __[-3] + => :bam + irb(main):018> __[-13] + => nil + ``` + + - Zero `m`: contains `nil`: + + ```console + irb(main):019> __[0] + => nil + ``` + +### Initialization Script + +By default, the first command-line argument (after any options) is the path to a Ruby initialization script. + +\IRB reads the initialization script and puts its content onto the \IRB shell, just as if it were user-typed commands. + +Command-line option `--noscript` causes the first command-line argument to be treated as an ordinary argument (instead of an initialization script); `--script` is the default. + +## Debugging with \IRB + +Starting from version 1.8.0, \IRB offers a powerful integration with `debug.gem`, providing a debugging experience similar to `pry-byebug`. + +After hitting a `binding.irb` breakpoint, you can activate the debugger with the `debug` command. Alternatively, if the `debug` method is already defined in the current scope, you can call `irb_debug`. + +```console +From: test.rb @ line 3 : + + 1: + 2: def greet(word) + => 3: binding.irb + 4: puts "Hello #{word}" + 5: end + 6: + 7: greet("World") + +irb(main):001> debug +irb:rdbg(main):002> +``` + +Once activated, the prompt's header changes from `irb` to `irb:rdbg`, enabling you to use any of `debug.gem`'s [commands](https://github.com/ruby/debug#debug-command-on-the-debug-console): + +```console +irb:rdbg(main):002> info # use info command to see available variables +%self = main +_ = nil +word = "World" +irb:rdbg(main):003> next # use next command to move to the next line +[1, 7] in test.rb + 1| + 2| def greet(word) + 3| binding.irb +=> 4| puts "Hello #{word}" + 5| end + 6| + 7| greet("World") +=>#0 Object#greet(word="World") at test.rb:4 + #1
at test.rb:7 +irb:rdbg(main):004> +``` + +Simultaneously, you maintain access to \IRB's commands, such as `show_source`: + +```console +irb:rdbg(main):004> show_source greet + +From: test.rb:2 + +def greet(word) + binding.irb + puts "Hello #{word}" +end +``` + +### More about `debug.gem` + +`debug.gem` offers many advanced debugging features that simple REPLs can't provide, including: + +- Step-debugging +- Frame navigation +- Setting breakpoints with commands +- Thread control +- ...and many more + +To learn about these features, refer to `debug.gem`'s [commands list](https://github.com/ruby/debug#debug-command-on-the-debug-console). + +In the `irb:rdbg` session, the `help` command also displays all commands from `debug.gem`. + +### Advantages Over `debug.gem`'s Console + +This integration offers several benefits over `debug.gem`'s native console: + +1. Access to handy \IRB commands like `show_source` or `show_doc`. +2. Support for multi-line input. +3. Symbol shortcuts such as `@` (`whereami`) and `$` (`show_source`). +4. Autocompletion. +5. Customizable prompt. + +However, there are some limitations to be aware of: + +1. `binding.irb` doesn't support `pre` and `do` arguments like [binding.break](https://github.com/ruby/debug#bindingbreak-method). +2. As \IRB [doesn't currently support remote-connection](https://github.com/ruby/irb/issues/672), it can't be used with `debug.gem`'s remote debugging feature. +3. Access to the previous return value via the underscore `_` is not supported. + +## Encodings + +Command-line option `-E *ex*[:*in*]` sets initial external (ex) and internal (in) encodings. + +Command-line option `-U` sets both to UTF-8. + +## Extending \IRB + +\IRB `v1.13.0` and later versions allow users/libraries to extend its functionality through official APIs. + +For more information, visit [EXTEND_IRB.md](rdoc-ref:EXTEND_IRB.md). + +## License + +The gem is available as open source under the terms of the [2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause). diff --git a/irb.gemspec b/irb.gemspec index b29002f59..d3d481d08 100644 --- a/irb.gemspec +++ b/irb.gemspec @@ -22,7 +22,6 @@ Gem::Specification.new do |spec| spec.metadata["changelog_uri"] = "#{spec.homepage}/releases" spec.files = [ - ".document", "Gemfile", "LICENSE.txt", "README.md", diff --git a/lib/.document b/lib/.document new file mode 100644 index 000000000..85f1b2217 --- /dev/null +++ b/lib/.document @@ -0,0 +1,3 @@ +irb.rb +irb/context.rb +irb/command/base.rb diff --git a/lib/irb.rb b/lib/irb.rb index 3aff5deff..169985773 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -24,858 +24,6 @@ require_relative "irb/debug" require_relative "irb/pager" -# ## IRB -# -# Module IRB ("Interactive Ruby") provides a shell-like interface that supports -# user interaction with the Ruby interpreter. -# -# It operates as a *read-eval-print loop* -# ([REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)) -# that: -# -# * ***Reads*** each character as you type. You can modify the IRB context to -# change the way input works. See [Input](rdoc-ref:IRB@Input). -# * ***Evaluates*** the code each time it has read a syntactically complete -# passage. -# * ***Prints*** after evaluating. You can modify the IRB context to change -# the way output works. See [Output](rdoc-ref:IRB@Output). -# -# -# Example: -# -# $ irb -# irb(main):001> File.basename(Dir.pwd) -# => "irb" -# irb(main):002> Dir.entries('.').size -# => 25 -# irb(main):003* Dir.entries('.').select do |entry| -# irb(main):004* entry.start_with?('R') -# irb(main):005> end -# => ["README.md", "Rakefile"] -# -# The typed input may also include [\IRB-specific -# commands](rdoc-ref:IRB@IRB-Specific+Commands). -# -# As seen above, you can start IRB by using the shell command `irb`. -# -# You can stop an IRB session by typing command `exit`: -# -# irb(main):006> exit -# $ -# -# At that point, IRB calls any hooks found in array `IRB.conf[:AT_EXIT]`, then -# exits. -# -# ## Startup -# -# At startup, IRB: -# -# 1. Interprets (as Ruby code) the content of the [configuration -# file](rdoc-ref:IRB@Configuration+File) (if given). -# 2. Constructs the initial session context from [hash -# IRB.conf](rdoc-ref:IRB@Hash+IRB.conf) and from default values; the hash -# content may have been affected by [command-line -# options](rdoc-ref:IRB@Command-Line+Options), and by direct assignments in -# the configuration file. -# 3. Assigns the context to variable `conf`. -# 4. Assigns command-line arguments to variable `ARGV`. -# 5. Prints the [prompt](rdoc-ref:IRB@Prompt+and+Return+Formats). -# 6. Puts the content of the [initialization -# script](rdoc-ref:IRB@Initialization+Script) onto the IRB shell, just as if -# it were user-typed commands. -# -# -# ### The Command Line -# -# On the command line, all options precede all arguments; the first item that is -# not recognized as an option is treated as an argument, as are all items that -# follow. -# -# #### Command-Line Options -# -# Many command-line options affect entries in hash `IRB.conf`, which in turn -# affect the initial configuration of the IRB session. -# -# Details of the options are described in the relevant subsections below. -# -# A cursory list of the IRB command-line options may be seen in the [help -# message](https://raw.githubusercontent.com/ruby/irb/master/lib/irb/lc/help-message), -# which is also displayed if you use command-line option `--help`. -# -# If you are interested in a specific option, consult the -# [index](rdoc-ref:doc/irb/indexes.md@Index+of+Command-Line+Options). -# -# #### Command-Line Arguments -# -# Command-line arguments are passed to IRB in array `ARGV`: -# -# $ irb --noscript Foo Bar Baz -# irb(main):001> ARGV -# => ["Foo", "Bar", "Baz"] -# irb(main):002> exit -# $ -# -# Command-line option `--` causes everything that follows to be treated as -# arguments, even those that look like options: -# -# $ irb --noscript -- --noscript -- Foo Bar Baz -# irb(main):001> ARGV -# => ["--noscript", "--", "Foo", "Bar", "Baz"] -# irb(main):002> exit -# $ -# -# ### Configuration File -# -# You can initialize IRB via a *configuration file*. -# -# If command-line option `-f` is given, no configuration file is looked for. -# -# Otherwise, IRB reads and interprets a configuration file if one is available. -# -# The configuration file can contain any Ruby code, and can usefully include -# user code that: -# -# * Can then be debugged in IRB. -# * Configures IRB itself. -# * Requires or loads files. -# -# -# The path to the configuration file is the first found among: -# -# * The value of variable `$IRBRC`, if defined. -# * The value of variable `$XDG_CONFIG_HOME/irb/irbrc`, if defined. -# * File `$HOME/.irbrc`, if it exists. -# * File `$HOME/.config/irb/irbrc`, if it exists. -# * File `.irbrc` in the current directory, if it exists. -# * File `irb.rc` in the current directory, if it exists. -# * File `_irbrc` in the current directory, if it exists. -# * File `$irbrc` in the current directory, if it exists. -# -# -# If the search fails, there is no configuration file. -# -# If the search succeeds, the configuration file is read as Ruby code, and so -# can contain any Ruby programming you like. -# -# Method `conf.rc?` returns `true` if a configuration file was read, `false` -# otherwise. Hash entry `IRB.conf[:RC]` also contains that value. -# -# ### Hash `IRB.conf` -# -# The initial entries in hash `IRB.conf` are determined by: -# -# * Default values. -# * Command-line options, which may override defaults. -# * Direct assignments in the configuration file. -# -# -# You can see the hash by typing `IRB.conf`. -# -# Details of the entries' meanings are described in the relevant subsections -# below. -# -# If you are interested in a specific entry, consult the -# [index](rdoc-ref:doc/irb/indexes.md@Index+of+IRB.conf+Entries). -# -# ### Notes on Initialization Precedence -# -# * Any conflict between an entry in hash `IRB.conf` and a command-line option -# is resolved in favor of the hash entry. -# * Hash `IRB.conf` affects the context only once, when the configuration file -# is interpreted; any subsequent changes to it do not affect the context and -# are therefore essentially meaningless. -# -# -# ### Initialization Script -# -# By default, the first command-line argument (after any options) is the path to -# a Ruby initialization script. -# -# IRB reads the initialization script and puts its content onto the IRB shell, -# just as if it were user-typed commands. -# -# Command-line option `--noscript` causes the first command-line argument to be -# treated as an ordinary argument (instead of an initialization script); -# `--script` is the default. -# -# ## Input -# -# This section describes the features that allow you to change the way IRB input -# works; see also [Input and Output](rdoc-ref:IRB@Input+and+Output). -# -# ### Input Command History -# -# By default, IRB stores a history of up to 1000 input commands in a file named -# `.irb_history`. The history file will be in the same directory as the -# [configuration file](rdoc-ref:IRB@Configuration+File) if one is found, or in -# `~/` otherwise. -# -# A new IRB session creates the history file if it does not exist, and appends -# to the file if it does exist. -# -# You can change the filepath by adding to your configuration file: -# `IRB.conf[:HISTORY_FILE] = *filepath*`, where *filepath* is a string filepath. -# -# During the session, method `conf.history_file` returns the filepath, and -# method `conf.history_file = *new_filepath*` copies the history to the file at -# *new_filepath*, which becomes the history file for the session. -# -# You can change the number of commands saved by adding to your configuration -# file: `IRB.conf[:SAVE_HISTORY] = *n*`, where *n* is one of: -# -# * Positive integer: the number of commands to be saved. -# * Negative integer: all commands are to be saved. -# * Zero or `nil`: no commands are to be saved. -# -# -# During the session, you can use methods `conf.save_history` or -# `conf.save_history=` to retrieve or change the count. -# -# ### Command Aliases -# -# By default, IRB defines several command aliases: -# -# irb(main):001> conf.command_aliases -# => {:"$"=>:show_source, :"@"=>:whereami} -# -# You can change the initial aliases in the configuration file with: -# -# IRB.conf[:COMMAND_ALIASES] = {foo: :show_source, bar: :whereami} -# -# You can replace the current aliases at any time with configuration method -# `conf.command_aliases=`; Because `conf.command_aliases` is a hash, you can -# modify it. -# -# ### End-of-File -# -# By default, `IRB.conf[:IGNORE_EOF]` is `false`, which means that typing the -# end-of-file character `Ctrl-D` causes the session to exit. -# -# You can reverse that behavior by adding `IRB.conf[:IGNORE_EOF] = true` to the -# configuration file. -# -# During the session, method `conf.ignore_eof?` returns the setting, and method -# `conf.ignore_eof = *boolean*` sets it. -# -# ### SIGINT -# -# By default, `IRB.conf[:IGNORE_SIGINT]` is `true`, which means that typing the -# interrupt character `Ctrl-C` causes the session to exit. -# -# You can reverse that behavior by adding `IRB.conf[:IGNORE_SIGING] = false` to -# the configuration file. -# -# During the session, method `conf.ignore_siging?` returns the setting, and -# method `conf.ignore_sigint = *boolean*` sets it. -# -# ### Automatic Completion -# -# By default, IRB enables [automatic -# completion](https://en.wikipedia.org/wiki/Autocomplete#In_command-line_interpr -# eters): -# -# You can disable it by either of these: -# -# * Adding `IRB.conf[:USE_AUTOCOMPLETE] = false` to the configuration file. -# * Giving command-line option `--noautocomplete` (`--autocomplete` is the -# default). -# -# -# Method `conf.use_autocomplete?` returns `true` if automatic completion is -# enabled, `false` otherwise. -# -# The setting may not be changed during the session. -# -# ### Automatic Indentation -# -# By default, IRB automatically indents lines of code to show structure (e.g., -# it indent the contents of a block). -# -# The current setting is returned by the configuration method -# `conf.auto_indent_mode`. -# -# The default initial setting is `true`: -# -# irb(main):001> conf.auto_indent_mode -# => true -# irb(main):002* Dir.entries('.').select do |entry| -# irb(main):003* entry.start_with?('R') -# irb(main):004> end -# => ["README.md", "Rakefile"] -# -# You can change the initial setting in the configuration file with: -# -# IRB.conf[:AUTO_INDENT] = false -# -# Note that the *current* setting *may not* be changed in the IRB session. -# -# ### Input Method -# -# The IRB input method determines how command input is to be read; by default, -# the input method for a session is IRB::RelineInputMethod. Unless the -# value of the TERM environment variable is 'dumb', in which case the -# most simplistic input method is used. -# -# You can set the input method by: -# -# * Adding to the configuration file: -# -# * `IRB.conf[:USE_SINGLELINE] = true` or `IRB.conf[:USE_MULTILINE]= -# false` sets the input method to IRB::ReadlineInputMethod. -# * `IRB.conf[:USE_SINGLELINE] = false` or `IRB.conf[:USE_MULTILINE] = -# true` sets the input method to IRB::RelineInputMethod. -# -# -# * Giving command-line options: -# -# * `--singleline` or `--nomultiline` sets the input method to -# IRB::ReadlineInputMethod. -# * `--nosingleline` or `--multiline` sets the input method to -# IRB::RelineInputMethod. -# * `--nosingleline` together with `--nomultiline` sets the -# input to IRB::StdioInputMethod. -# -# -# Method `conf.use_multiline?` and its synonym `conf.use_reline` return: -# -# * `true` if option `--multiline` was given. -# * `false` if option `--nomultiline` was given. -# * `nil` if neither was given. -# -# -# Method `conf.use_singleline?` and its synonym `conf.use_readline` return: -# -# * `true` if option `--singleline` was given. -# * `false` if option `--nosingleline` was given. -# * `nil` if neither was given. -# -# -# ## Output -# -# This section describes the features that allow you to change the way IRB -# output works; see also [Input and Output](rdoc-ref:IRB@Input+and+Output). -# -# ### Return-Value Printing (Echoing) -# -# By default, IRB prints (echoes) the values returned by all input commands. -# -# You can change the initial behavior and suppress all echoing by: -# -# * Adding to the configuration file: `IRB.conf[:ECHO] = false`. (The default -# value for this entry is `nil`, which means the same as `true`.) -# * Giving command-line option `--noecho`. (The default is `--echo`.) -# -# -# During the session, you can change the current setting with configuration -# method `conf.echo=` (set to `true` or `false`). -# -# As stated above, by default IRB prints the values returned by all input -# commands; but IRB offers special treatment for values returned by assignment -# statements, which may be: -# -# * Printed with truncation (to fit on a single line of output), which is the -# default; an ellipsis (`...` is suffixed, to indicate the truncation): -# -# irb(main):001> x = 'abc' * 100 -# -# -# > "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc... -# -# * Printed in full (regardless of the length). -# * Suppressed (not printed at all) -# -# -# You can change the initial behavior by: -# -# * Adding to the configuration file: `IRB.conf[:ECHO_ON_ASSIGNMENT] = false`. -# (The default value for this entry is `niL`, which means the same as -# `:truncate`.) -# * Giving command-line option `--noecho-on-assignment` or -# `--echo-on-assignment`. (The default is `--truncate-echo-on-assignment`.) -# -# -# During the session, you can change the current setting with configuration -# method `conf.echo_on_assignment=` (set to `true`, `false`, or `:truncate`). -# -# By default, IRB formats returned values by calling method `inspect`. -# -# You can change the initial behavior by: -# -# * Adding to the configuration file: `IRB.conf[:INSPECT_MODE] = false`. (The -# default value for this entry is `true`.) -# * Giving command-line option `--noinspect`. (The default is `--inspect`.) -# -# -# During the session, you can change the setting using method -# `conf.inspect_mode=`. -# -# ### Multiline Output -# -# By default, IRB prefixes a newline to a multiline response. -# -# You can change the initial default value by adding to the configuration file: -# -# IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT] = false -# -# During a session, you can retrieve or set the value using methods -# `conf.newline_before_multiline_output?` and -# `conf.newline_before_multiline_output=`. -# -# Examples: -# -# irb(main):001> conf.inspect_mode = false -# => false -# irb(main):002> "foo\nbar" -# => -# foo -# bar -# irb(main):003> conf.newline_before_multiline_output = false -# => false -# irb(main):004> "foo\nbar" -# => foo -# bar -# -# ### Evaluation History -# -# By default, IRB saves no history of evaluations (returned values), and the -# related methods `conf.eval_history`, `_`, and `__` are undefined. -# -# You can turn on that history, and set the maximum number of evaluations to be -# stored: -# -# * In the configuration file: add `IRB.conf[:EVAL_HISTORY] = *n*`. (Examples -# below assume that we've added `IRB.conf[:EVAL_HISTORY] = 5`.) -# * In the session (at any time): `conf.eval_history = *n*`. -# -# -# If `n` is zero, all evaluation history is stored. -# -# Doing either of the above: -# -# * Sets the maximum size of the evaluation history; defines method -# `conf.eval_history`, which returns the maximum size `n` of the evaluation -# history: -# -# irb(main):001> conf.eval_history = 5 -# => 5 -# irb(main):002> conf.eval_history -# => 5 -# -# * Defines variable `_`, which contains the most recent evaluation, or `nil` -# if none; same as method `conf.last_value`: -# -# irb(main):003> _ -# => 5 -# irb(main):004> :foo -# => :foo -# irb(main):005> :bar -# => :bar -# irb(main):006> _ -# => :bar -# irb(main):007> _ -# => :bar -# -# * Defines variable `__`: -# -# * `__` unadorned: contains all evaluation history: -# -# irb(main):008> :foo -# => :foo -# irb(main):009> :bar -# => :bar -# irb(main):010> :baz -# => :baz -# irb(main):011> :bat -# => :bat -# irb(main):012> :bam -# => :bam -# irb(main):013> __ -# => -# 9 :bar -# 10 :baz -# 11 :bat -# 12 :bam -# irb(main):014> __ -# => -# 10 :baz -# 11 :bat -# 12 :bam -# 13 ...self-history... -# -# Note that when the evaluation is multiline, it is displayed -# differently. -# -# * `__[`*m*`]`: -# -# * Positive *m*: contains the evaluation for the given line number, -# or `nil` if that line number is not in the evaluation history: -# -# irb(main):015> __[12] -# => :bam -# irb(main):016> __[1] -# => nil -# -# * Negative *m*: contains the `mth`-from-end evaluation, or `nil` if -# that evaluation is not in the evaluation history: -# -# irb(main):017> __[-3] -# => :bam -# irb(main):018> __[-13] -# => nil -# -# * Zero *m*: contains `nil`: -# -# irb(main):019> __[0] -# => nil -# -# -# -# -# ### Prompt and Return Formats -# -# By default, IRB uses the prompt and return value formats defined in its -# `:DEFAULT` prompt mode. -# -# #### The Default Prompt and Return Format -# -# The default prompt and return values look like this: -# -# irb(main):001> 1 + 1 -# => 2 -# irb(main):002> 2 + 2 -# => 4 -# -# The prompt includes: -# -# * The name of the running program (`irb`); see [IRB -# Name](rdoc-ref:IRB@IRB+Name). -# * The name of the current session (`main`); See [IRB -# Sessions](rdoc-ref:IRB@IRB+Sessions). -# * A 3-digit line number (1-based). -# -# -# The default prompt actually defines three formats: -# -# * One for most situations (as above): -# -# irb(main):003> Dir -# => Dir -# -# * One for when the typed command is a statement continuation (adds trailing -# asterisk): -# -# irb(main):004* Dir. -# -# * One for when the typed command is a string continuation (adds trailing -# single-quote): -# -# irb(main):005' Dir.entries('. -# -# -# You can see the prompt change as you type the characters in the following: -# -# irb(main):001* Dir.entries('.').select do |entry| -# irb(main):002* entry.start_with?('R') -# irb(main):003> end -# => ["README.md", "Rakefile"] -# -# #### Pre-Defined Prompts -# -# IRB has several pre-defined prompts, stored in hash `IRB.conf[:PROMPT]`: -# -# irb(main):001> IRB.conf[:PROMPT].keys -# => [:NULL, :DEFAULT, :CLASSIC, :SIMPLE, :INF_RUBY, :XMP] -# -# To see the full data for these, type `IRB.conf[:PROMPT]`. -# -# Most of these prompt definitions include specifiers that represent values like -# the IRB name, session name, and line number; see [Prompt -# Specifiers](rdoc-ref:IRB@Prompt+Specifiers). -# -# You can change the initial prompt and return format by: -# -# * Adding to the configuration file: `IRB.conf[:PROMPT] = *mode*` where -# *mode* is the symbol name of a prompt mode. -# * Giving a command-line option: -# -# * `--prompt *mode*`: sets the prompt mode to *mode*. where *mode* is the -# symbol name of a prompt mode. -# * `--simple-prompt` or `--sample-book-mode`: sets the prompt mode to -# `:SIMPLE`. -# * `--inf-ruby-mode`: sets the prompt mode to `:INF_RUBY` and suppresses -# both `--multiline` and `--singleline`. -# * `--noprompt`: suppresses prompting; does not affect echoing. -# -# -# -# You can retrieve or set the current prompt mode with methods -# -# `conf.prompt_mode` and `conf.prompt_mode=`. -# -# If you're interested in prompts and return formats other than the defaults, -# you might experiment by trying some of the others. -# -# #### Custom Prompts -# -# You can also define custom prompts and return formats, which may be done -# either in an IRB session or in the configuration file. -# -# A prompt in IRB actually defines three prompts, as seen above. For simple -# custom data, we'll make all three the same: -# -# irb(main):001* IRB.conf[:PROMPT][:MY_PROMPT] = { -# irb(main):002* PROMPT_I: ': ', -# irb(main):003* PROMPT_C: ': ', -# irb(main):004* PROMPT_S: ': ', -# irb(main):005* RETURN: '=> ' -# irb(main):006> } -# => {:PROMPT_I=>": ", :PROMPT_C=>": ", :PROMPT_S=>": ", :RETURN=>"=> "} -# -# If you define the custom prompt in the configuration file, you can also make -# it the current prompt by adding: -# -# IRB.conf[:PROMPT_MODE] = :MY_PROMPT -# -# Regardless of where it's defined, you can make it the current prompt in a -# session: -# -# conf.prompt_mode = :MY_PROMPT -# -# You can view or modify the current prompt data with various configuration -# methods: -# -# * `conf.prompt_mode`, `conf.prompt_mode=`. -# * `conf.prompt_c`, `conf.c=`. -# * `conf.prompt_i`, `conf.i=`. -# * `conf.prompt_s`, `conf.s=`. -# * `conf.return_format`, `return_format=`. -# -# -# #### Prompt Specifiers -# -# A prompt's definition can include specifiers for which certain values are -# substituted: -# -# * `%N`: the name of the running program. -# * `%m`: the value of `self.to_s`. -# * `%M`: the value of `self.inspect`. -# * `%l`: an indication of the type of string; one of `"`, `'`, `/`, `]`. -# * `%NNi`: Indentation level. NN is a 2-digit number that specifies the number -# of digits of the indentation level (03 will result in 001). -# * `%NNn`: Line number. NN is a 2-digit number that specifies the number -# of digits of the line number (03 will result in 001). -# * `%%`: Literal `%`. -# -# -# ### Verbosity -# -# By default, IRB verbosity is disabled, which means that output is smaller -# rather than larger. -# -# You can enable verbosity by: -# -# * Adding to the configuration file: `IRB.conf[:VERBOSE] = true` (the default -# is `nil`). -# * Giving command-line options `--verbose` (the default is `--noverbose`). -# -# -# During a session, you can retrieve or set verbosity with methods -# `conf.verbose` and `conf.verbose=`. -# -# ### Help -# -# Command-line option `--version` causes IRB to print its help text and exit. -# -# ### Version -# -# Command-line option `--version` causes IRB to print its version text and exit. -# -# ## Input and Output -# -# ### Color Highlighting -# -# By default, IRB color highlighting is enabled, and is used for both: -# -# * Input: As you type, IRB reads the typed characters and highlights elements -# that it recognizes; it also highlights errors such as mismatched -# parentheses. -# * Output: IRB highlights syntactical elements. -# -# -# You can disable color highlighting by: -# -# * Adding to the configuration file: `IRB.conf[:USE_COLORIZE] = false` (the -# default value is `true`). -# * Giving command-line option `--nocolorize` -# -# -# ## Debugging -# -# Command-line option `-d` sets variables `$VERBOSE` and `$DEBUG` to `true`; -# these have no effect on IRB output. -# -# ### Warnings -# -# Command-line option `-w` suppresses warnings. -# -# Command-line option `-W[*level*]` sets warning level; -# -# * 0=silence -# * 1=medium -# * 2=verbose -# -# ## Other Features -# -# ### Load Modules -# -# You can specify the names of modules that are to be required at startup. -# -# Array `conf.load_modules` determines the modules (if any) that are to be -# required during session startup. The array is used only during session -# startup, so the initial value is the only one that counts. -# -# The default initial value is `[]` (load no modules): -# -# irb(main):001> conf.load_modules -# => [] -# -# You can set the default initial value via: -# -# * Command-line option `-r` -# -# $ irb -r csv -r json -# irb(main):001> conf.load_modules -# => ["csv", "json"] -# -# * Hash entry `IRB.conf[:LOAD_MODULES] = *array*`: -# -# IRB.conf[:LOAD_MODULES] = %w[csv, json] -# -# -# Note that the configuration file entry overrides the command-line options. -# -# ### RI Documentation Directories -# -# You can specify the paths to RI documentation directories that are to be -# loaded (in addition to the default directories) at startup; see details about -# RI by typing `ri --help`. -# -# Array `conf.extra_doc_dirs` determines the directories (if any) that are to be -# loaded during session startup. The array is used only during session startup, -# so the initial value is the only one that counts. -# -# The default initial value is `[]` (load no extra documentation): -# -# irb(main):001> conf.extra_doc_dirs -# => [] -# -# You can set the default initial value via: -# -# * Command-line option `--extra_doc_dir` -# -# $ irb --extra-doc-dir your_doc_dir --extra-doc-dir my_doc_dir -# irb(main):001> conf.extra_doc_dirs -# => ["your_doc_dir", "my_doc_dir"] -# -# * Hash entry `IRB.conf[:EXTRA_DOC_DIRS] = *array*`: -# -# IRB.conf[:EXTRA_DOC_DIRS] = %w[your_doc_dir my_doc_dir] -# -# -# Note that the configuration file entry overrides the command-line options. -# -# ### IRB Name -# -# You can specify a name for IRB. -# -# The default initial value is `'irb'`: -# -# irb(main):001> conf.irb_name -# => "irb" -# -# You can set the default initial value via hash entry `IRB.conf[:IRB_NAME] = -# *string*`: -# -# IRB.conf[:IRB_NAME] = 'foo' -# -# ### Application Name -# -# You can specify an application name for the IRB session. -# -# The default initial value is `'irb'`: -# -# irb(main):001> conf.ap_name -# => "irb" -# -# You can set the default initial value via hash entry `IRB.conf[:AP_NAME] = -# *string*`: -# -# IRB.conf[:AP_NAME] = 'my_ap_name' -# -# ### Configuration Monitor -# -# You can monitor changes to the configuration by assigning a proc to -# `IRB.conf[:IRB_RC]` in the configuration file: -# -# IRB.conf[:IRB_RC] = proc {|conf| puts conf.class } -# -# Each time the configuration is changed, that proc is called with argument -# `conf`: -# -# ### Encodings -# -# Command-line option `-E *ex*[:*in*]` sets initial external (ex) and internal -# (in) encodings. -# -# Command-line option `-U` sets both to UTF-8. -# -# ### Commands -# -# Please use the `help` command to see the list of available commands. -# -# ### IRB Sessions -# -# IRB has a special feature, that allows you to manage many sessions at once. -# -# You can create new sessions with Irb.irb, and get a list of current sessions -# with the `jobs` command in the prompt. -# -# #### Configuration -# -# The command line options, or IRB.conf, specify the default behavior of -# Irb.irb. -# -# On the other hand, each conf in IRB@Command-Line+Options is used to -# individually configure IRB.irb. -# -# If a proc is set for `IRB.conf[:IRB_RC]`, its will be invoked after execution -# of that proc with the context of the current session as its argument. Each -# session can be configured using this mechanism. -# -# #### Session variables -# -# There are a few variables in every Irb session that can come in handy: -# -# `_` -# : The value command executed, as a local variable -# `__` -# : The history of evaluated commands. Available only if -# `IRB.conf[:EVAL_HISTORY]` is not `nil` (which is the default). See also -# IRB::Context#eval_history= and IRB::History. -# `__[line_no]` -# : Returns the evaluation value at the given line number, `line_no`. If -# `line_no` is a negative, the return value `line_no` many lines before the -# most recent return value. -# -# -# ## Restrictions -# -# Ruby code typed into IRB behaves the same as Ruby code in a file, except that: -# -# * Because IRB evaluates input immediately after it is syntactically -# complete, some results may be slightly different. -# * Forking may not be well behaved. -# module IRB # An exception raised by IRB.irb_abort diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index ac6ce5346..212ab0cf8 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -239,7 +239,7 @@ def test_autocomplete_with_multiple_doc_namespaces def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_right rdoc_dir = File.join(@tmpdir, 'rdoc') - system("bundle exec rdoc -r -o #{rdoc_dir}") + system("bundle exec rdoc lib -r -o #{rdoc_dir}") write_irbrc <<~LINES IRB.conf[:EXTRA_DOC_DIRS] = ['#{rdoc_dir}'] IRB.conf[:PROMPT][:MY_PROMPT] = { @@ -273,7 +273,7 @@ def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_right def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_left rdoc_dir = File.join(@tmpdir, 'rdoc') - system("bundle exec rdoc -r -o #{rdoc_dir}") + system("bundle exec rdoc lib -r -o #{rdoc_dir}") write_irbrc <<~LINES IRB.conf[:EXTRA_DOC_DIRS] = ['#{rdoc_dir}'] IRB.conf[:PROMPT][:MY_PROMPT] = { From d2b73cb91e4c48d3cef613a01d9d7da49cc53c13 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 29 Dec 2024 03:17:03 +0800 Subject: [PATCH 223/263] Use the documentation site as the source of truth (#1055) * Use the documentation site as the source of truth 1. Remove detailed content from README.md and point links to the documentation site. 2. Remove the content of EXTEND_IRB.md and point links to the documentation site. * Use GitHub pages as Rubygems' documentation target --- EXTEND_IRB.md | 121 +-------------------- README.md | 291 +++----------------------------------------------- irb.gemspec | 2 +- 3 files changed, 14 insertions(+), 400 deletions(-) diff --git a/EXTEND_IRB.md b/EXTEND_IRB.md index 684b0b7b1..9cd27aa29 100644 --- a/EXTEND_IRB.md +++ b/EXTEND_IRB.md @@ -1,122 +1,3 @@ # Extend IRB -From v1.13.0, IRB provides official APIs to extend its functionality. This feature allows libraries to -customize and enhance their users' IRB sessions by adding new commands and helper methods tailored for -the libraries. - -## Helper Methods vs. Commands - -- Use a helper method if the operation is meant to return a Ruby object that interacts with the application. - - For example, an `admin_user` helper method that returns `User.where(admin: true).first`, which can then be used like `login_as(admin_user)`. -- Use a command if the operation fits one of the following: - - A utility operation that performs non-Ruby related tasks, such as IRB's `edit` command. - - Displays information, like the `show_source` command. - - If the operation requires non-Ruby syntax arguments, like `ls -g pattern`. - -If you don't know what to pick, go with commands first. Commands are generally safer as they can handle a wider variety of inputs and use cases. - -## Commands - -Commands are designed to complete certain tasks or display information for the user, similar to shell commands. -Therefore, they are designed to accept a variety of inputs, including those that are not valid Ruby code, such -as `my_cmd Foo#bar` or `my_cmd --flag foo`. - -### Example - -```rb -require "irb/command" - -class Greet < IRB::Command::Base - category "Greeting" - description "Greets the user" - help_message <<~HELP - Greets the user with the given name. - - Usage: greet - HELP - - # Any input after the command name will be passed as a single string. - # If nothing is added after the command, an empty string will be passed. - def execute(arg) - puts "Hello! #{arg}" - end -end - -IRB::Command.register(:greet, Greet) -``` - -As long as the above code is loaded before the IRB session is started, such as in a loaded library or a user's `.irbrc` file, `greet` will be accessible to the user. - -```txt -irb(main):001> greet -Hello! -=> nil -irb(main):002> greet Stan -Hello! Stan -=> nil -``` - -And because the `Greet` command introduces a new category, `Greeting`, a new help message category will be created: - -```txt -Help - help List all available commands. Use `help ` to get information about a specific command. - -Greeting - greet Greets the user - -IRB - context Displays current configuration. - ... -``` - -If the optional `help_message` attribute is specified, `help greet` will also display it: - -```txt -irb(main):001> help greet -Greets the user with the given name. - -Usage: greet -``` - -## Helper methods - -Helper methods are designed to be used as Ruby methods, such as `my_helper(arg, kwarg: val).foo`. - -The main use case of helper methods is to provide shortcuts for users, providing quick and easy access to -frequently used operations or components within the IRB session. For example, a helper method might simplify -the process of fetching and displaying specific configuration settings or data structures that would otherwise -require multiple steps to access. - -### Example - -```rb -# This only loads the minimum components required to define and register a helper method. -# It does not load the entire IRB, nor does it initialize it. -require "irb/helper_method" - -class MyHelper < IRB::HelperMethod::Base - description "This is a test helper" - - def execute(arg, kwarg:) - "arg: #{arg}, kwarg: #{kwarg}" - end -end - -IRB::HelperMethod.register(:my_helper, MyHelper) -``` - -As long as the above code is loaded before the IRB session is started, such as in a loaded library or a user's `.irbrc` file, `my_helper` will be accessible to the user. - -```txt -irb(main):001> my_helper("foo", kwarg: "bar").upcase -=> "ARG: FOO, KWARG: BAR" -``` - -The registered helper methods will also be listed in the help message's `Helper methods` section: - -```txt -Helper methods - conf Returns the current context. - my_helper This is a test helper -``` +This page has been moved to the [IRB Extension Guide](https://ruby.github.io/irb/EXTEND_IRB_md.html). diff --git a/README.md b/README.md index 7186bc41a..b469eb715 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,14 @@ # IRB [![Gem Version](https://badge.fury.io/rb/irb.svg)](https://badge.fury.io/rb/irb) +[![Static Badge](https://img.shields.io/badge/RDoc-flat?style=flat&label=documentation&link=https%3A%2F%2Fsiteproxy.ruqli.workers.dev%3A443%2Fhttps%2Fruby.github.io%2Firb%2F)](https://ruby.github.io/irb/) [![build](https://github.com/ruby/irb/actions/workflows/test.yml/badge.svg)](https://github.com/ruby/irb/actions/workflows/test.yml) + IRB stands for "interactive Ruby" and is a tool to interactively execute Ruby expressions read from the standard input. The `irb` command from your shell will start the interpreter. -- [Installation](#installation) -- [Usage](#usage) - - [The `irb` Executable](#the-irb-executable) - - [The `binding.irb` Breakpoint](#the-bindingirb-breakpoint) -- [Commands](#commands) -- [Debugging with IRB](#debugging-with-irb) - - [More about `debug.gem`](#more-about-debuggem) - - [Advantages Over `debug.gem`'s Console](#advantages-over-debuggems-console) -- [Type Based Completion](#type-based-completion) - - [How to Enable IRB::TypeCompletor](#how-to-enable-irbtypecompletor) - - [Advantage over Default IRB::RegexpCompletor](#advantage-over-default-irbregexpcompletor) - - [Difference between Steep's Completion](#difference-between-steeps-completion) -- [Configuration](#configuration) - - [Environment Variables](#environment-variables) -- [Documentation](#documentation) -- [Extending IRB](#extending-irb) -- [Development](#development) -- [Contributing](#contributing) -- [Releasing](#releasing) -- [License](#license) - ## Installation > [!Note] @@ -58,7 +39,7 @@ $ gem install irb > [!Note] > -> We're working hard to match Pry's variety of powerful features in IRB, and you can track our progress or find contribution ideas in [this document](https://github.com/ruby/irb/blob/master/COMPARED_WITH_PRY.md). +> We're working hard to match Pry's variety of powerful features in IRB, and you can track our progress or find contribution ideas in [this document](https://ruby.github.io/irb/COMPARED_WITH_PRY_md.html). ### The `irb` Executable @@ -105,276 +86,28 @@ irb(main):002:0> exit Hello World ``` -## Commands - -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 - context Displays current configuration. - exit Exit the current irb session. - exit! Exit the current process. - irb_load Load a Ruby file. - irb_require Require a Ruby file. - source Loads a given file in the current session. - irb_info Show information about IRB. - history Shows the input history. `-g [query]` or `-G [query]` allows you to filter the output. - disable_irb Disable binding.irb. - -Workspace - cwws Show the current workspace. - chws Change the current workspace to an object. - workspaces Show workspaces. - pushws Push an object to the workspace stack. - popws Pop a workspace from the workspace stack. - cd Move into the given object or leave the current context. - -Multi-irb (DEPRECATED) - irb Start a child IRB. - jobs List of current sessions. - fg Switches to the session of the given number. - kill Kills the session with the given number. - -Debugging - debug Start the debugger of debug.gem. - break Start the debugger of debug.gem and run its `break` command. - catch Start the debugger of debug.gem and run its `catch` command. - next Start the debugger of debug.gem and run its `next` command. - delete Start the debugger of debug.gem and run its `delete` command. - step Start the debugger of debug.gem and run its `step` command. - continue Start the debugger of debug.gem and run its `continue` command. - finish Start the debugger of debug.gem and run its `finish` command. - backtrace Start the debugger of debug.gem and run its `backtrace` command. - info Start the debugger of debug.gem and run its `info` command. - -Misc - edit Open a file or source location. - measure `measure` enables the mode to measure processing time. `measure :off` disables it. - -Context - show_doc Look up documentation with RI. - ls Show methods, constants, and variables. - show_source Show the source code of a given method, class/module, or constant. - whereami Show the source code around binding.irb again. - -Helper methods - conf Returns the current IRB context. - -Aliases - $ Alias for `show_source` - @ Alias for `whereami` -``` - -## Debugging with IRB - -### Getting Started - -- In `binding.irb`, use the `debug` command to start a `irb:rdbg` session with access to all `debug.gem` commands. -- Use `RUBY_DEBUG_IRB_CONSOLE=1` environment variable to make `debug.gem` use IRB as the debugging console. - -### Details - -Starting from version 1.8.0, IRB boasts a powerful integration with `debug.gem`, providing a debugging experience akin to `pry-byebug`. - -After hitting a `binding.irb` breakpoint, you can activate the debugger with the `debug` command. Alternatively, if the `debug` method happens to already be defined in the current scope, you can call `irb_debug`. - -```shell -From: test.rb @ line 3 : - - 1: - 2: def greet(word) - => 3: binding.irb - 4: puts "Hello #{word}" - 5: end - 6: - 7: greet("World") - -irb(main):001> debug -irb:rdbg(main):002> -``` - -Once activated, the prompt's header changes from `irb` to `irb:rdbg`, enabling you to use any of `debug.gem`'s [commands](https://github.com/ruby/debug#debug-command-on-the-debug-console): - -```shell -irb:rdbg(main):002> info # use info command to see available variables -%self = main -_ = nil -word = "World" -irb:rdbg(main):003> next # use next command to move to the next line -[1, 7] in test.rb - 1| - 2| def greet(word) - 3| binding.irb -=> 4| puts "Hello #{word}" - 5| end - 6| - 7| greet("World") -=>#0 Object#greet(word="World") at test.rb:4 - #1
at test.rb:7 -irb:rdbg(main):004> -``` - -Simultaneously, you maintain access to IRB's commands, such as `show_source`: - -```shell -irb:rdbg(main):004> show_source greet - -From: test.rb:2 - -def greet(word) - binding.irb - puts "Hello #{word}" -end -``` - -### More about `debug.gem` - -`debug.gem` offers many advanced debugging features that simple REPLs can't provide, including: - -- Step-debugging -- Frame navigation -- Setting breakpoints with commands -- Thread control -- ...and many more - -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 `help` command will also display all commands from `debug.gem`. - -### Advantages Over `debug.gem`'s Console - -This integration offers several benefits over `debug.gem`'s native console: - -1. Access to handy IRB commands like `show_source` or `show_doc`. -2. Support for multi-line input. -3. Symbol shortcuts such as `@` (`whereami`) and `$` (`show_source`). -4. Autocompletion. -5. Customizable prompt. - -However, there are also some limitations to be aware of: - -1. `binding.irb` doesn't support `pre` and `do` arguments like [`binding.break`](https://github.com/ruby/debug#bindingbreak-method). -2. As IRB [doesn't currently support remote-connection](https://github.com/ruby/irb/issues/672), it can't be used with `debug.gem`'s remote debugging feature. -3. Access to the previous return value via the underscore `_` is not supported. - -## Type Based Completion - -IRB's default completion `IRB::RegexpCompletor` uses Regexp. IRB has another experimental completion `IRB::TypeCompletor` that uses type analysis. - -### How to Enable IRB::TypeCompletor - -Install [ruby/repl_type_completor](https://github.com/ruby/repl_type_completor/) with: -``` -$ gem install repl_type_completor -``` -Or add these lines to your project's Gemfile. -```ruby -gem 'irb' -gem 'repl_type_completor', group: [:development, :test] -``` - -Now you can use type based completion by: - -Running IRB with the `--type-completor` option -``` -$ irb --type-completor -``` - -Or writing this line to IRB's rc-file (e.g. `~/.irbrc`) -```ruby -IRB.conf[:COMPLETOR] = :type # default is :regexp -``` - -Or setting the environment variable `IRB_COMPLETOR` -```ruby -ENV['IRB_COMPLETOR'] = 'type' -IRB.start -``` - -To check if it's enabled, type `irb_info` into IRB and see the `Completion` section. -``` -irb(main):001> irb_info -... -# Enabled -Completion: Autocomplete, ReplTypeCompletor: 0.1.0, Prism: 0.18.0, RBS: 3.3.0 -# Not enabled -Completion: Autocomplete, RegexpCompletor -... -``` -If you have `sig/` directory or `rbs_collection.lock.yaml` in current directory, IRB will load it. - -### Advantage over Default IRB::RegexpCompletor - -IRB::TypeCompletor can autocomplete chained methods, block parameters and more if type information is available. -These are some examples IRB::RegexpCompletor cannot complete. - -```ruby -irb(main):001> 'Ruby'.upcase.chars.s # Array methods (sample, select, shift, size) -``` - -```ruby -irb(main):001> 10.times.map(&:to_s).each do |s| -irb(main):002> s.up # String methods (upcase, upcase!, upto) -``` +### Debugging -```ruby -irb(main):001> class User < ApplicationRecord -irb(main):002> def foo -irb(main):003> sa # save, save! -``` - -As a trade-off, completion calculation takes more time than IRB::RegexpCompletor. +You can use IRB as a debugging console with `debug.gem` with these options: -### Difference between Steep's Completion +- In `binding.irb`, use the `debug` command to start an `irb:rdbg` session with access to all `debug.gem` commands. +- Use the `RUBY_DEBUG_IRB_CONSOLE=1` environment variable to make `debug.gem` use IRB as the debugging console. -Compared with Steep, IRB::TypeCompletor has some difference and limitations. -```ruby -[0, 'a'].sample. -# Steep completes intersection of Integer methods and String methods -# IRB::TypeCompletor completes both Integer and String methods -``` +To learn more about debugging with IRB, see [Debugging with IRB](https://ruby.github.io/irb/#label-Debugging+with+IRB). -Some features like type narrowing is not implemented. -```ruby -def f(arg = [0, 'a'].sample) - if arg.is_a?(String) - arg. # Completes both Integer and String methods -``` +## Documentation -Unlike other static type checker, IRB::TypeCompletor uses runtime information to provide better completion. -```ruby -irb(main):001> a = [1] -=> [1] -irb(main):002> a.first. # Completes Integer methods -``` +https://ruby.github.io/irb/ provides a comprehensive guide to IRB's features and usage. ## Configuration -### Environment Variables - -- `NO_COLOR`: Assigning a value to it disables IRB's colorization. -- `IRB_USE_AUTOCOMPLETE`: Setting it to `false` disables IRB's autocompletion. -- `IRB_COMPLETOR`: Configures IRB's auto-completion behavior, allowing settings for either `regexp` or `type`. -- `VISUAL`: Its value would be used to open files by the `edit` command. -- `EDITOR`: Its value would be used to open files by the `edit` command if `VISUAL` is unset. -- `IRBRC`: The file specified would be evaluated as IRB's rc-file. -- `XDG_CONFIG_HOME`: If it is set and `IRBRC` is unset, the file `$XDG_CONFIG_HOME/irb/irbrc` would be evaluated as IRB's rc-file. -- `RI_PAGER`: The command specified would be used as a pager. -- `PAGER`: The command specified would be used as a pager if `RI_PAGER` is unset. -- `IRB_LANG`, `LC_MESSAGES`, `LC_ALL`, `LANG`: The first of these that is set would be used as the locale value. - -## Documentation - -https://ruby.github.io/irb/ +See the [Configuration page](https://ruby.github.io/irb/Configurations_md.html) in the documentation. ## Extending IRB IRB `v1.13.0` and later versions allows users/libraries to extend its functionality through official APIs. -For more information, please visit [EXTEND_IRB.md](./EXTEND_IRB.md). +For more information, please visit the [IRB Extension Guide](https://ruby.github.io/irb/EXTEND_IRB_md.html). ## Development diff --git a/irb.gemspec b/irb.gemspec index d3d481d08..6bb257c8c 100644 --- a/irb.gemspec +++ b/irb.gemspec @@ -18,7 +18,7 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.metadata["documentation_uri"] = spec.homepage + spec.metadata["documentation_uri"] = "https://ruby.github.io/irb/" spec.metadata["changelog_uri"] = "#{spec.homepage}/releases" spec.files = [ From a282bbc0cf851dc51bbb930f05897b2bcfbeeb63 Mon Sep 17 00:00:00 2001 From: sanfrecce-osaka Date: Sun, 29 Dec 2024 20:00:08 +0900 Subject: [PATCH 224/263] Fix broken `history` command with -g (#1057) Local variable `grep` was always nil because the regular expression parsing options contained an unnecessary `\n`. `test_history_grep` did not detect this because it only asserted what was included in the output. --- lib/irb/command/history.rb | 2 +- test/irb/test_command.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/irb/command/history.rb b/lib/irb/command/history.rb index 90f87f910..e385c6610 100644 --- a/lib/irb/command/history.rb +++ b/lib/irb/command/history.rb @@ -14,7 +14,7 @@ class History < Base def execute(arg) - if (match = arg&.match(/(-g|-G)\s+(?.+)\s*\n\z/)) + if (match = arg&.match(/(-g|-G)\s+(?.+)\s*\z/)) grep = Regexp.new(match[:grep]) end diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index 286fe0476..ec2d1f92d 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -972,6 +972,12 @@ def test_history_grep puts x ... EOF + assert_not_include(out, <<~EOF) + foo + EOF + assert_not_include(out, <<~EOF) + bar + EOF assert_empty err end From 3e6c12b174c0a961d8065eae22f6c4afc7b2c3e8 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 3 Jan 2025 19:24:25 +0800 Subject: [PATCH 225/263] Extract contributing guideline into a CONTRIBUTING.md (#1056) --- CONTRIBUTING.md | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 32 +----------------------------- doc/Index.md | 4 ++++ 3 files changed, 57 insertions(+), 31 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..518544ea7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,52 @@ +# Contributing to IRB + +Bug reports and pull requests are welcome on GitHub at [https://github.com/ruby/irb](https://github.com/ruby/irb). + +## Set up the environment + +1. Fork the project to your GitHub account. +2. Clone the fork with `git clone git@github.com:[your_username]/irb.git`. +3. Run `bundle install`. +4. Run `bundle exec rake` to make sure tests pass locally. + +## Run integration tests + +If your changes affect component rendering, such as the autocompletion's dialog/dropdown, you may need to run IRB's integration tests, known as `yamatanooroti`. + +Before running these tests, ensure that you have `libvterm` installed. If you're using Homebrew, you can install it by running: + +```bash +brew install libvterm +``` + +After installing `libvterm`, you can run the integration tests using the following commands: + +```bash +WITH_VTERM=1 bundle install +WITH_VTERM=1 bundle exec rake test test_yamatanooroti +``` + +## Update documentation + +IRB's documentation is generated by [RDoc](https://ruby.github.io/rdoc/) and published to [ruby.github.io/irb](https://ruby.github.io/irb/). Most of the documentation source lives under the `doc/` directory. + +Run the following command to generate the documentation site locally. + +```bash +bundle exec rake rdoc +bundle exec rake rerdoc # to force regeneration +``` + +Follow the output message to open the documentation site in your browser. + +> [!Note] +> +> Please ensure that the changes are rendered correctly on the documentation site. +> RDoc's Markdown support is limited, so the rendered result on GitHub might differ from what’s rendered on [https://ruby.github.io/irb](https://ruby.github.io/irb). + +We welcome any improvements to the documentation, including: + +- Fixing typos and grammatical errors. +- Adding missing documentation for features. +- Adding missing documentation for configuration options. +- Adding demo images/gifs for features. diff --git a/README.md b/README.md index b469eb715..b08ba26ca 100644 --- a/README.md +++ b/README.md @@ -109,39 +109,9 @@ IRB `v1.13.0` and later versions allows users/libraries to extend its functional For more information, please visit the [IRB Extension Guide](https://ruby.github.io/irb/EXTEND_IRB_md.html). -## Development - -After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. - -To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). - ## Contributing -Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/irb. - -### Set up the environment - -1. Fork the project to your GithHub account -2. Clone the fork with `git clone git@github.com:[your_username]/irb.git` -3. Run `bundle install` -4. Run `bundle exec rake` to make sure tests pass locally - -### Run integration tests - -If your changes affect component rendering, such as the autocompletion's dialog/dropdown, you may need to run IRB's integration tests, known as `yamatanooroti`. - -Before running these tests, ensure that you have `libvterm` installed. If you're using Homebrew, you can install it by running: - -```bash -brew install libvterm -``` - -After installing `libvterm`, you can run the integration tests using the following commands: - -```bash -WITH_VTERM=1 bundle install -WITH_VTERM=1 bundle exec rake test test_yamatanooroti -``` +See [CONTRIBUTING.md](./CONTRIBUTING.md) for more information. ## Releasing diff --git a/doc/Index.md b/doc/Index.md index 24d216885..04d619361 100644 --- a/doc/Index.md +++ b/doc/Index.md @@ -687,6 +687,10 @@ Command-line option `-E *ex*[:*in*]` sets initial external (ex) and internal (in Command-line option `-U` sets both to UTF-8. +## Contributing + +See [CONTRIBUTING.md](https://github.com/ruby/irb/blob/main/CONTRIBUTING.md) for more information. + ## Extending \IRB \IRB `v1.13.0` and later versions allow users/libraries to extend its functionality through official APIs. From 96941c32299446565fc7e9a524e69592af1c5efd Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 7 Jan 2025 01:03:00 +0800 Subject: [PATCH 226/263] Simplify vterm-yamatanooroti's steps (#1060) --- .github/workflows/test.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 95b4f46d3..adcc536c7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -81,14 +81,10 @@ jobs: fail-fast: false env: WITH_LATEST_RELINE: ${{matrix.with_latest_reline}} + WITH_VTERM: 1 timeout-minutes: 30 steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - bundler-cache: true - name: Install libvterm run: | sudo apt install -y libtool-bin @@ -98,8 +94,10 @@ jobs: sed -i -e 's/^PREFIX=.*$/PREFIX=\/usr/g' Makefile make sudo make install - - name: Install dependencies - run: | - WITH_VTERM=1 bundle install + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true - name: rake test_yamatanooroti - run: WITH_VTERM=1 bundle exec rake test_yamatanooroti + run: bundle exec rake test_yamatanooroti From 9fc14eb74bc447108683bc830eb169369d3994d2 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 11 Jan 2025 05:29:27 +0800 Subject: [PATCH 227/263] Gracefully handle incorrect command aliases (#1059) * Gracefully handle incorrect command aliases Even if the aliased target is a helper method or does not exist, IRB should not crash. This commit warns users in such cases and treat the input as normal expression. * Streamline command parsing and introduce warnings for incorrect command aliases --- lib/irb.rb | 16 +++---- lib/irb/context.rb | 55 +++++++++++++++++------ lib/irb/statement.rb | 21 +++++++++ test/irb/command/test_command_aliasing.rb | 50 +++++++++++++++++++++ 4 files changed, 118 insertions(+), 24 deletions(-) create mode 100644 test/irb/command/test_command_aliasing.rb diff --git a/lib/irb.rb b/lib/irb.rb index 169985773..29be6386c 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -269,29 +269,25 @@ def each_top_level_statement loop do code = readmultiline break unless code - yield build_statement(code), @line_no + yield parse_input(code), @line_no @line_no += code.count("\n") rescue RubyLex::TerminateLineInput end end - def build_statement(code) + def parse_input(code) if code.match?(/\A\n*\z/) return Statement::EmptyInput.new end code = code.dup.force_encoding(@context.io.encoding) - if (command, arg = @context.parse_command(code)) - command_class = Command.load_command(command) - Statement::Command.new(code, command_class, arg) - else - is_assignment_expression = @scanner.assignment_expression?(code, local_variables: @context.local_variables) - Statement::Expression.new(code, is_assignment_expression) - end + is_assignment_expression = @scanner.assignment_expression?(code, local_variables: @context.local_variables) + + @context.parse_input(code, is_assignment_expression) end def command?(code) - !!@context.parse_command(code) + parse_input(code).is_a?(Statement::Command) end def configure_io diff --git a/lib/irb/context.rb b/lib/irb/context.rb index c65628192..8d6545224 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -600,6 +600,8 @@ def evaluate(statement, line_no) # :nodoc: set_last_value(result) when Statement::Command statement.command_class.execute(self, statement.arg) + when Statement::IncorrectAlias + warn statement.message end nil @@ -633,35 +635,60 @@ def evaluate_expression(code, line_no) # :nodoc: result end - def parse_command(code) + def parse_input(code, is_assignment_expression) command_name, arg = code.strip.split(/\s+/, 2) - return unless code.lines.size == 1 && command_name - arg ||= '' - command = command_name.to_sym - # Command aliases are always command. example: $, @ - if (alias_name = command_aliases[command]) - return [alias_name, arg] + + # command can only be 1 line + if code.lines.size != 1 || + # command name is required + command_name.nil? || + # local variable have precedence over command + local_variables.include?(command_name.to_sym) || + # assignment expression is not a command + (is_assignment_expression || + (arg.start_with?(ASSIGN_OPERATORS_REGEXP) && !arg.start_with?(/==|=~/))) + return Statement::Expression.new(code, is_assignment_expression) end - # Assignment-like expression is not a command - return if arg.start_with?(ASSIGN_OPERATORS_REGEXP) && !arg.start_with?(/==|=~/) + command = command_name.to_sym - # Local variable have precedence over command - return if local_variables.include?(command) + # Check command aliases + if aliased_name = command_aliases[command] + if command_class = Command.load_command(aliased_name) + command = aliased_name + elsif HelperMethod.helper_methods[aliased_name] + message = <<~MESSAGE + Using command alias `#{command}` for helper method `#{aliased_name}` is not supported. + Please check the value of `IRB.conf[:COMMAND_ALIASES]`. + MESSAGE + return Statement::IncorrectAlias.new(message) + else + message = <<~MESSAGE + You're trying to use command alias `#{command}` for command `#{aliased_name}`, but `#{aliased_name}` does not exist. + Please check the value of `IRB.conf[:COMMAND_ALIASES]`. + MESSAGE + return Statement::IncorrectAlias.new(message) + end + else + command_class = Command.load_command(command) + end # Check visibility public_method = !!KERNEL_PUBLIC_METHOD.bind_call(main, command) rescue false private_method = !public_method && !!KERNEL_METHOD.bind_call(main, command) rescue false - if Command.execute_as_command?(command, public_method: public_method, private_method: private_method) - [command, arg] + if command_class && Command.execute_as_command?(command, public_method: public_method, private_method: private_method) + Statement::Command.new(code, command_class, arg) + else + Statement::Expression.new(code, is_assignment_expression) end end def colorize_input(input, complete:) if IRB.conf[:USE_COLORIZE] && IRB::Color.colorable? lvars = local_variables || [] - if parse_command(input) + parsed_input = parse_input(input, false) + if parsed_input.is_a?(Statement::Command) name, sep, arg = input.split(/(\s+)/, 2) arg = IRB::Color.colorize_code(arg, complete: complete, local_variables: lvars) "#{IRB::Color.colorize(name, [:BOLD])}\e[m#{sep}#{arg}" diff --git a/lib/irb/statement.rb b/lib/irb/statement.rb index 9591a4035..6a959995d 100644 --- a/lib/irb/statement.rb +++ b/lib/irb/statement.rb @@ -54,6 +54,27 @@ def is_assignment? end end + class IncorrectAlias < Statement + attr_reader :message + + def initialize(message) + @code = "" + @message = message + end + + def should_be_handled_by_debugger? + false + end + + def is_assignment? + false + end + + def suppresses_echo? + true + end + end + class Command < Statement attr_reader :command_class, :arg diff --git a/test/irb/command/test_command_aliasing.rb b/test/irb/command/test_command_aliasing.rb new file mode 100644 index 000000000..4ecc88c0a --- /dev/null +++ b/test/irb/command/test_command_aliasing.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "tempfile" +require_relative "../helper" + +module TestIRB + class CommandAliasingTest < IntegrationTestCase + def setup + super + write_rc <<~RUBY + IRB.conf[:COMMAND_ALIASES] = { + :c => :conf, # alias to helper method + :f => :foo + } + RUBY + + write_ruby <<~'RUBY' + binding.irb + RUBY + end + + def test_aliasing_to_helper_method_triggers_warning + out = run_ruby_file do + type "c" + type "exit" + end + assert_include(out, "Using command alias `c` for helper method `conf` is not supported.") + assert_not_include(out, "Maybe IRB bug!") + end + + def test_alias_to_non_existent_command_triggers_warning + message = "You're trying to use command alias `f` for command `foo`, but `foo` does not exist." + out = run_ruby_file do + type "f" + type "exit" + end + assert_include(out, message) + assert_not_include(out, "Maybe IRB bug!") + + # Local variables take precedence over command aliases + out = run_ruby_file do + type "f = 123" + type "f" + type "exit" + end + assert_not_include(out, message) + assert_not_include(out, "Maybe IRB bug!") + end + end +end From 4d74d39261fe7dff59094d737b66bd816b9042e6 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 11 Jan 2025 20:59:16 +0800 Subject: [PATCH 228/263] Print more actionable message when the exception may be an IRB issue (#1061) --- lib/irb.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/irb.rb b/lib/irb.rb index 29be6386c..80cfa6cd8 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -426,7 +426,10 @@ def handle_exception(exc) # The "" in "(irb)" may be the top level of IRB so imitate the main object. message = message.gsub(/\(irb\):(?\d+):in (?[`'])<(?top \(required\))>'/) { "(irb):#{$~[:num]}:in #{$~[:open_quote]}
'" } puts message - puts 'Maybe IRB bug!' if irb_bug + + if irb_bug + puts "This may be an issue with IRB. If you believe this is an unexpected behavior, please report it to https://github.com/ruby/irb/issues" + end rescue Exception => handler_exc begin puts exc.inspect From 8b1a07b2a8a84b345f26eb8148a075d63ce38abd Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 11 Jan 2025 22:03:54 +0800 Subject: [PATCH 229/263] `IRB.conf[:SAVE_HISTORY]` should handle boolean values (#1062) Although not documented, `IRB.conf[:SAVE_HISTORY]` used to accept boolean, which now causes `NoMethodError` when used. This commit changes the behavior to accept boolean values and adds tests for the behavior. --- lib/irb/history.rb | 4 ++++ lib/irb/init.rb | 2 +- test/irb/test_history.rb | 41 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/lib/irb/history.rb b/lib/irb/history.rb index 25fa71b9c..0beff1553 100644 --- a/lib/irb/history.rb +++ b/lib/irb/history.rb @@ -2,9 +2,13 @@ module IRB module History + DEFAULT_ENTRY_LIMIT = 1000 + class << self # Integer representation of IRB.conf[:HISTORY_FILE]. def save_history + return 0 if IRB.conf[:SAVE_HISTORY] == false + return DEFAULT_ENTRY_LIMIT if IRB.conf[:SAVE_HISTORY] == true IRB.conf[:SAVE_HISTORY].to_i end diff --git a/lib/irb/init.rb b/lib/irb/init.rb index d474bd41d..b41536e61 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -93,7 +93,7 @@ def IRB.init_config(ap_path) @CONF[:VERBOSE] = nil @CONF[:EVAL_HISTORY] = nil - @CONF[:SAVE_HISTORY] = 1000 + @CONF[:SAVE_HISTORY] = History::DEFAULT_ENTRY_LIMIT @CONF[:BACK_TRACE_LIMIT] = 16 diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index 021bb682c..0171bb0ec 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -279,6 +279,47 @@ def with_temp_stdio end class IRBHistoryIntegrationTest < IntegrationTestCase + def test_history_saving_can_be_disabled_with_false + write_history "" + write_rc <<~RUBY + IRB.conf[:SAVE_HISTORY] = false + RUBY + + write_ruby <<~'RUBY' + binding.irb + RUBY + + output = run_ruby_file do + type "puts 'foo' + 'bar'" + type "exit" + end + + assert_include(output, "foobar") + assert_equal "", @history_file.open.read + end + + def test_history_saving_accepts_true + write_history "" + write_rc <<~RUBY + IRB.conf[:SAVE_HISTORY] = true + RUBY + + write_ruby <<~'RUBY' + binding.irb + RUBY + + output = run_ruby_file do + type "puts 'foo' + 'bar'" + type "exit" + end + + assert_include(output, "foobar") + assert_equal <<~HISTORY, @history_file.open.read + puts 'foo' + 'bar' + exit + HISTORY + end + def test_history_saving_with_debug write_history "" From a6b976cfb770c99ec04ccf4af0223b8b8cbe5f88 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 11 Jan 2025 22:09:13 +0800 Subject: [PATCH 230/263] Remove unnecessary escape from completor class names (#1063) --- doc/Index.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/Index.md b/doc/Index.md index 04d619361..bc1461eff 100644 --- a/doc/Index.md +++ b/doc/Index.md @@ -276,9 +276,9 @@ The setting may not be changed during the session. ### Type Based Completion -\IRB's default completion `\IRB::RegexpCompletor` uses Regexp. \IRB offers an experimental completion `\IRB::TypeCompletor` that uses type analysis. +\IRB's default completion `IRB::RegexpCompletor` uses Regexp. \IRB offers an experimental completion `IRB::TypeCompletor` that uses type analysis. -#### How to Enable \IRB::TypeCompletor +#### How to Enable IRB::TypeCompletor Install [ruby/repl_type_completor](https://github.com/ruby/repl_type_completor/) with: @@ -328,9 +328,9 @@ Completion: Autocomplete, RegexpCompletor If you have a `sig/` directory or `rbs_collection.lock.yaml` in the current directory, \IRB will load it. -#### Advantage over Default \IRB::RegexpCompletor +#### Advantage over Default IRB::RegexpCompletor -\IRB::TypeCompletor can autocomplete chained methods, block parameters, and more if type information is available. These are some examples \IRB::RegexpCompletor cannot complete. +`IRB::TypeCompletor` can autocomplete chained methods, block parameters, and more if type information is available. These are some examples `IRB::RegexpCompletor` cannot complete. ```console irb(main):001> 'Ruby'.upcase.chars.s # Array methods (sample, select, shift, size) @@ -347,11 +347,11 @@ irb(main):002> def foo irb(main):003> sa # save, save! ``` -As a trade-off, completion calculation takes more time than \IRB::RegexpCompletor. +As a trade-off, completion calculation takes more time than `IRB::RegexpCompletor`. #### Difference between Steep's Completion -Compared with Steep, \IRB::TypeCompletor has some differences and limitations. +Compared with Steep, `IRB::TypeCompletor` has some differences and limitations. ```ruby [0, 'a'].sample. # Steep completes the intersection of Integer methods and String methods @@ -365,7 +365,7 @@ def f(arg = [0, 'a'].sample) arg. # Completes both Integer and String methods ``` -Unlike other static type checkers, \IRB::TypeCompletor uses runtime information to provide better completion. +Unlike other static type checkers, `IRB::TypeCompletor` uses runtime information to provide better completion. ```console irb(main):001> a = [1] From 08908d43c7260d2c3a692174c3957661554a64f3 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 12 Jan 2025 20:47:25 +0800 Subject: [PATCH 231/263] Drop ColorPrinter's workaround for BasicObject (#1051) `pp` 0.6.0+ includes https://github.com/ruby/pp/pull/26 to handle BasicObject, so we can drop the workaround. --- .github/workflows/test.yml | 2 ++ irb.gemspec | 1 + lib/irb/color_printer.rb | 5 ----- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index adcc536c7..415cffa38 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,6 +42,8 @@ jobs: with: ruby-version: ${{ matrix.ruby }} bundler-cache: true + # Added to make Ruby 2.7 correctly require installed default gems, like `pp`. + rubygems: latest - name: Run tests run: bundle exec rake test - name: Run tests in isolation diff --git a/irb.gemspec b/irb.gemspec index 6bb257c8c..9a93382b7 100644 --- a/irb.gemspec +++ b/irb.gemspec @@ -42,4 +42,5 @@ Gem::Specification.new do |spec| spec.add_dependency "reline", ">= 0.4.2" spec.add_dependency "rdoc", ">= 4.0.0" + spec.add_dependency "pp", ">= 0.6.0" end diff --git a/lib/irb/color_printer.rb b/lib/irb/color_printer.rb index 31644aa7f..6cfaefd05 100644 --- a/lib/irb/color_printer.rb +++ b/lib/irb/color_printer.rb @@ -4,9 +4,6 @@ module IRB class ColorPrinter < ::PP - METHOD_RESPOND_TO = Object.instance_method(:respond_to?) - METHOD_INSPECT = Object.instance_method(:inspect) - class << self def pp(obj, out = $>, width = screen_width) q = ColorPrinter.new(out, width) @@ -28,8 +25,6 @@ def pp(obj) if String === obj # Avoid calling Ruby 2.4+ String#pretty_print that splits a string by "\n" text(obj.inspect) - elsif !METHOD_RESPOND_TO.bind(obj).call(:inspect) - text(METHOD_INSPECT.bind(obj).call) else super end From ce8fa6857c6f43898d5d51f34bfbebf472bc26d0 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 12 Jan 2025 20:59:32 +0800 Subject: [PATCH 232/263] Group private methods together in `IRB::Context` (#1064) This makes them easier to find and matches the convention of the codebase. --- lib/irb/context.rb | 90 +++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 8d6545224..d87e8451e 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -155,11 +155,6 @@ def initialize(irb, workspace = nil, input_method = nil) @command_aliases = IRB.conf[:COMMAND_ALIASES].dup end - private def term_interactive? - return true if ENV['TEST_IRB_FORCE_INTERACTIVE'] - STDIN.tty? && ENV['TERM'] != 'dumb' - end - def use_tracer=(val) require_relative "ext/tracer" if val IRB.conf[:USE_TRACER] = val @@ -177,45 +172,6 @@ def use_loader=(val) __send__(__method__, val) end - private def build_completor - completor_type = IRB.conf[:COMPLETOR] - - # Gem repl_type_completor is added to bundled gems in Ruby 3.4. - # Use :type as default completor only in Ruby 3.4 or later. - verbose = !!completor_type - completor_type ||= RUBY_VERSION >= '3.4' ? :type : :regexp - - case completor_type - when :regexp - return RegexpCompletor.new - when :type - completor = build_type_completor(verbose: verbose) - return completor if completor - else - warn "Invalid value for IRB.conf[:COMPLETOR]: #{completor_type}" - end - # Fallback to RegexpCompletor - RegexpCompletor.new - end - - private def build_type_completor(verbose:) - if RUBY_ENGINE == 'truffleruby' - # Avoid SyntaxError. truffleruby does not support endless method definition yet. - warn 'TypeCompletor is not supported on TruffleRuby yet' if verbose - return - end - - begin - require 'repl_type_completor' - rescue LoadError => e - warn "TypeCompletor requires `gem repl_type_completor`: #{e.message}" if verbose - return - end - - ReplTypeCompletor.preload_rbs - TypeCompletor.new(self) - end - def save_history=(val) IRB.conf[:SAVE_HISTORY] = val end @@ -739,5 +695,51 @@ def safe_method_call_on_main(method_name) main_object = main Object === main_object ? main_object.__send__(method_name) : Object.instance_method(method_name).bind_call(main_object) end + + private + + def term_interactive? + return true if ENV['TEST_IRB_FORCE_INTERACTIVE'] + STDIN.tty? && ENV['TERM'] != 'dumb' + end + + def build_completor + completor_type = IRB.conf[:COMPLETOR] + + # Gem repl_type_completor is added to bundled gems in Ruby 3.4. + # Use :type as default completor only in Ruby 3.4 or later. + verbose = !!completor_type + completor_type ||= RUBY_VERSION >= '3.4' ? :type : :regexp + + case completor_type + when :regexp + return RegexpCompletor.new + when :type + completor = build_type_completor(verbose: verbose) + return completor if completor + else + warn "Invalid value for IRB.conf[:COMPLETOR]: #{completor_type}" + end + # Fallback to RegexpCompletor + RegexpCompletor.new + end + + def build_type_completor(verbose:) + if RUBY_ENGINE == 'truffleruby' + # Avoid SyntaxError. truffleruby does not support endless method definition yet. + warn 'TypeCompletor is not supported on TruffleRuby yet' if verbose + return + end + + begin + require 'repl_type_completor' + rescue LoadError => e + warn "TypeCompletor requires `gem repl_type_completor`: #{e.message}" if verbose + return + end + + ReplTypeCompletor.preload_rbs + TypeCompletor.new(self) + end end end From 0b60a5be1d0a32819a730c3da17158fab44c31b9 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 16 Jan 2025 02:57:42 +0900 Subject: [PATCH 233/263] Colorize backref token bold green like global variables (#1065) --- lib/irb/color.rb | 1 + test/irb/test_color.rb | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/irb/color.rb b/lib/irb/color.rb index fca942b28..a7e311087 100644 --- a/lib/irb/color.rb +++ b/lib/irb/color.rb @@ -41,6 +41,7 @@ module Color on_embvar: [[RED], ALL], on_float: [[MAGENTA, BOLD], ALL], on_gvar: [[GREEN, BOLD], ALL], + on_backref: [[GREEN, BOLD], ALL], on_heredoc_beg: [[RED], ALL], on_heredoc_end: [[RED], ALL], on_ident: [[BLUE, BOLD], Ripper::EXPR_ENDFN], diff --git a/test/irb/test_color.rb b/test/irb/test_color.rb index 9d78f5233..5529e2904 100644 --- a/test/irb/test_color.rb +++ b/test/irb/test_color.rb @@ -102,6 +102,7 @@ def test_colorize_code "\t" => Reline::Unicode.escape_for_print("\t") == ' ' ? ' ' : "\t", # not ^I "foo(*%W(bar))" => "foo(*#{RED}#{BOLD}%W(#{CLEAR}#{RED}bar#{CLEAR}#{RED}#{BOLD})#{CLEAR})", "$stdout" => "#{GREEN}#{BOLD}$stdout#{CLEAR}", + "$&" => "#{GREEN}#{BOLD}$&#{CLEAR}", "__END__" => "#{GREEN}__END__#{CLEAR}", "foo\n__END__\nbar" => "foo\n#{GREEN}__END__#{CLEAR}\nbar", "foo\n< "foo\n#{RED}< "[#{BLUE}#{BOLD}1#{CLEAR}]#{RED}#{REVERSE}]#{CLEAR}]^S", "def req(true) end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}req#{CLEAR}(#{RED}#{REVERSE}true#{CLEAR}) end", "nil = 1" => "#{CYAN}#{BOLD}nil#{CLEAR} = #{BLUE}#{BOLD}1#{CLEAR}", - "alias $x $1" => "#{GREEN}alias#{CLEAR} #{GREEN}#{BOLD}$x#{CLEAR} $1", + "alias $x $1" => "#{GREEN}alias#{CLEAR} #{GREEN}#{BOLD}$x#{CLEAR} #{GREEN}#{BOLD}$1#{CLEAR}", "class bad; end" => "#{GREEN}class#{CLEAR} bad; #{GREEN}end#{CLEAR}", "def req(@a) end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}req#{CLEAR}(@a) #{GREEN}end#{CLEAR}", }) From a24ac53d486a4e51d1e44ee2b9551955a854f6bd Mon Sep 17 00:00:00 2001 From: Prajjwal Singh Date: Mon, 20 Jan 2025 16:39:34 -0500 Subject: [PATCH 234/263] Add copy command (#1044) Closes #753 --- doc/Configurations.md | 1 + lib/irb/color_printer.rb | 14 +++++-- lib/irb/command/copy.rb | 63 ++++++++++++++++++++++++++++++ lib/irb/context.rb | 2 + lib/irb/default_commands.rb | 2 + lib/irb/init.rb | 2 + lib/irb/inspector.rb | 12 +++--- man/irb.1 | 2 + test/irb/command/test_copy.rb | 70 ++++++++++++++++++++++++++++++++++ test/irb/test_color_printer.rb | 13 +++++++ test/irb/test_init.rb | 20 ++++++++++ 11 files changed, 191 insertions(+), 10 deletions(-) create mode 100644 lib/irb/command/copy.rb create mode 100644 test/irb/command/test_copy.rb diff --git a/doc/Configurations.md b/doc/Configurations.md index 755c88095..a7efbc8a8 100644 --- a/doc/Configurations.md +++ b/doc/Configurations.md @@ -31,6 +31,7 @@ Method `conf.rc?` returns `true` if a configuration file was read, `false` other - `NO_COLOR`: Disables \IRB's colorization. - `IRB_USE_AUTOCOMPLETE`: Setting to `false` disables autocompletion. - `IRB_COMPLETOR`: Configures auto-completion behavior (`regexp` or `type`). +- `IRB_COPY_COMMAND`: Overrides the default program used to interface with the system clipboard. - `VISUAL` / `EDITOR`: Specifies the editor for the `edit` command. - `IRBRC`: Specifies the rc-file for configuration. - `XDG_CONFIG_HOME`: Used to locate the rc-file if `IRBRC` is unset. diff --git a/lib/irb/color_printer.rb b/lib/irb/color_printer.rb index 6cfaefd05..7a7e81785 100644 --- a/lib/irb/color_printer.rb +++ b/lib/irb/color_printer.rb @@ -5,8 +5,8 @@ module IRB class ColorPrinter < ::PP class << self - def pp(obj, out = $>, width = screen_width) - q = ColorPrinter.new(out, width) + def pp(obj, out = $>, width = screen_width, colorize: true) + q = ColorPrinter.new(out, width, colorize: colorize) q.guard_inspect_key {q.pp obj} q.flush out << "\n" @@ -21,6 +21,12 @@ def screen_width end end + def initialize(out, width, colorize: true) + @colorize = colorize + + super(out, width) + end + def pp(obj) if String === obj # Avoid calling Ruby 2.4+ String#pretty_print that splits a string by "\n" @@ -41,9 +47,9 @@ def text(str, width = nil) when ',', '=>', '[', ']', '{', '}', '..', '...', /\A@\w+\z/ super(str, width) when /\A#' - super(Color.colorize(str, [:GREEN]), width) + super(@colorize ? Color.colorize(str, [:GREEN]) : str, width) else - super(Color.colorize_code(str, ignore_error: true), width) + super(@colorize ? Color.colorize_code(str, ignore_error: true) : str, width) end end end diff --git a/lib/irb/command/copy.rb b/lib/irb/command/copy.rb new file mode 100644 index 000000000..3fd3f5493 --- /dev/null +++ b/lib/irb/command/copy.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module IRB + module Command + class Copy < Base + category "Workspace" + description "Copy command output to clipboard" + + help_message(<<~HELP) + Usage: copy [command] + HELP + + def execute(arg) + # Copy last value if no expression was supplied + arg = '_' if arg.to_s.strip.empty? + + value = irb_context.workspace.binding.eval(arg) + output = irb_context.inspect_method.inspect_value(value, colorize: false) + + if clipboard_available? + copy_to_clipboard(output) + else + warn "System clipboard not found" + end + rescue StandardError => e + warn "Error: #{e}" + end + + private + + def copy_to_clipboard(text) + IO.popen(clipboard_program, 'w') do |io| + io.write(text) + end + + raise IOError.new("Copying to clipboard failed") unless $? == 0 + + puts "Copied to system clipboard" + rescue Errno::ENOENT => e + warn e.message + warn "Is IRB.conf[:COPY_COMMAND] set to a bad value?" + end + + def clipboard_program + @clipboard_program ||= if IRB.conf[:COPY_COMMAND] + IRB.conf[:COPY_COMMAND] + elsif executable?("pbcopy") + "pbcopy" + elsif executable?("xclip") + "xclip -selection clipboard" + end + end + + def executable?(command) + system("which #{command} > /dev/null 2>&1") + end + + def clipboard_available? + !!clipboard_program + end + end + end +end diff --git a/lib/irb/context.rb b/lib/irb/context.rb index d87e8451e..2642256c4 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -264,6 +264,8 @@ def irb_path=(path) attr_reader :use_autocomplete # A copy of the default IRB.conf[:INSPECT_MODE] attr_reader :inspect_mode + # Inspector for the current context + attr_reader :inspect_method # A copy of the default IRB.conf[:PROMPT_MODE] attr_reader :prompt_mode diff --git a/lib/irb/default_commands.rb b/lib/irb/default_commands.rb index 533bdfc87..9820a1f30 100644 --- a/lib/irb/default_commands.rb +++ b/lib/irb/default_commands.rb @@ -9,6 +9,7 @@ require_relative "command/chws" require_relative "command/context" require_relative "command/continue" +require_relative "command/copy" require_relative "command/debug" require_relative "command/delete" require_relative "command/disable_irb" @@ -250,6 +251,7 @@ def load_command(command) ) register(:cd, Command::CD) + register(:copy, Command::Copy) end ExtendCommand = Command diff --git a/lib/irb/init.rb b/lib/irb/init.rb index b41536e61..720c4fec4 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -194,6 +194,8 @@ def IRB.init_config(ap_path) :'$' => :show_source, :'@' => :whereami, } + + @CONF[:COPY_COMMAND] = ENV.fetch("IRB_COPY_COMMAND", nil) end def IRB.set_measure_callback(type = nil, arg = nil, &block) diff --git a/lib/irb/inspector.rb b/lib/irb/inspector.rb index 8046744f8..a1679b20f 100644 --- a/lib/irb/inspector.rb +++ b/lib/irb/inspector.rb @@ -93,8 +93,8 @@ def init end # Proc to call when the input is evaluated and output in irb. - def inspect_value(v) - @inspect.call(v) + def inspect_value(v, colorize: true) + @inspect.call(v, colorize: colorize) rescue => e puts "An error occurred when inspecting the object: #{e.inspect}" @@ -110,11 +110,11 @@ def inspect_value(v) end Inspector.def_inspector([false, :to_s, :raw]){|v| v.to_s} - Inspector.def_inspector([:p, :inspect]){|v| - Color.colorize_code(v.inspect, colorable: Color.colorable? && Color.inspect_colorable?(v)) + Inspector.def_inspector([:p, :inspect]){|v, colorize: true| + Color.colorize_code(v.inspect, colorable: colorize && Color.colorable? && Color.inspect_colorable?(v)) } - Inspector.def_inspector([true, :pp, :pretty_inspect], proc{require_relative "color_printer"}){|v| - IRB::ColorPrinter.pp(v, +'').chomp + Inspector.def_inspector([true, :pp, :pretty_inspect], proc{require_relative "color_printer"}){|v, colorize: true| + IRB::ColorPrinter.pp(v, +'', colorize: colorize).chomp } Inspector.def_inspector([:yaml, :YAML], proc{require "yaml"}){|v| begin diff --git a/man/irb.1 b/man/irb.1 index 90cf5d4ae..48586a3b7 100644 --- a/man/irb.1 +++ b/man/irb.1 @@ -227,6 +227,8 @@ or .Sy type . .Pp +.It Ev IRB_COPY_COMMAND +Overrides the default program used to interface with the system clipboard. .El .Pp Also diff --git a/test/irb/command/test_copy.rb b/test/irb/command/test_copy.rb new file mode 100644 index 000000000..8ba62bd96 --- /dev/null +++ b/test/irb/command/test_copy.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'irb' + +require_relative "../helper" + +module TestIRB + class CopyTest < IntegrationTestCase + def setup + super + @envs['IRB_COPY_COMMAND'] = "ruby -e \"puts 'foo' + STDIN.read\"" + end + + def test_copy_with_pbcopy + write_ruby <<~'ruby' + class Answer + def initialize(answer) + @answer = answer + end + end + + binding.irb + ruby + + output = run_ruby_file do + type "copy Answer.new(42)" + type "exit" + end + + assert_match(/foo# "1\n", + "a\nb" => %["a\\nb"\n], + IRBTestColorPrinter.new('test') => "#\n", + Ripper::Lexer.new('1').scan => "[#]\n", + Class.new{define_method(:pretty_print){|q| q.text("[__FILE__, __LINE__, __ENCODING__]")}}.new => "[__FILE__, __LINE__, __ENCODING__]\n", + }.each do |object, result| + actual = with_term { IRB::ColorPrinter.pp(object, '', colorize: false) } + assert_equal(result, actual, "Case: IRB::ColorPrinter.pp(#{object.inspect}, '')") + end + end + private def with_term diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb index f7168e02f..f34f692f0 100644 --- a/test/irb/test_init.rb +++ b/test/irb/test_init.rb @@ -163,6 +163,26 @@ def test_use_autocomplete_environment_variable IRB.conf[:USE_AUTOCOMPLETE] = orig_use_autocomplete_conf end + def test_copy_command_environment_variable + orig_copy_command_env = ENV['IRB_COPY_COMMAND'] + orig_copy_command_conf = IRB.conf[:COPY_COMMAND] + + ENV['IRB_COPY_COMMAND'] = nil + IRB.setup(__FILE__) + refute IRB.conf[:COPY_COMMAND] + + ENV['IRB_COPY_COMMAND'] = '' + IRB.setup(__FILE__) + assert_equal('', IRB.conf[:COPY_COMMAND]) + + ENV['IRB_COPY_COMMAND'] = 'blah' + IRB.setup(__FILE__) + assert_equal('blah', IRB.conf[:COPY_COMMAND]) + ensure + ENV['IRB_COPY_COMMAND'] = orig_copy_command_env + IRB.conf[:COPY_COMMAND] = orig_copy_command_conf + end + def test_completor_environment_variable orig_use_autocomplete_env = ENV['IRB_COMPLETOR'] orig_use_autocomplete_conf = IRB.conf[:COMPLETOR] From 03c36586e6159f6b76e8b0c9106f2ee69545e737 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 21 Jan 2025 22:58:05 +0900 Subject: [PATCH 235/263] Show a quick preview of inspect result before pager launch (#1040) * Quickly show inspect preview even if pretty_print takes too much time * Show a message "Inspecting..." while generating pretty_print content * Update inspecting message Co-authored-by: Stan Lo * Update rendering test for preparing inspect message * Don't show preview if pretty_print does not take time --------- Co-authored-by: Stan Lo --- lib/irb.rb | 58 +++++------ lib/irb/command/copy.rb | 2 +- lib/irb/context.rb | 8 +- lib/irb/inspector.rb | 13 ++- lib/irb/pager.rb | 122 +++++++++++++++++++++-- test/irb/test_context.rb | 2 +- test/irb/test_pager.rb | 66 ++++++++++++ test/irb/yamatanooroti/test_rendering.rb | 18 +++- 8 files changed, 243 insertions(+), 46 deletions(-) create mode 100644 test/irb/test_pager.rb diff --git a/lib/irb.rb b/lib/irb.rb index 80cfa6cd8..fd0bfe35c 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -516,40 +516,36 @@ def signal_status(status) end def output_value(omit = false) # :nodoc: - str = @context.inspect_last_value - multiline_p = str.include?("\n") - if omit - winwidth = @context.io.winsize.last - if multiline_p - first_line = str.split("\n").first - result = @context.newline_before_multiline_output? ? (@context.return_format % first_line) : first_line - output_width = Reline::Unicode.calculate_width(result, true) - diff_size = output_width - Reline::Unicode.calculate_width(first_line, true) - if diff_size.positive? and output_width > winwidth - lines, _ = Reline::Unicode.split_by_width(first_line, winwidth - diff_size - 3) - str = "%s..." % lines.first - str += "\e[0m" if Color.colorable? - multiline_p = false - else - str = str.gsub(/(\A.*?\n).*/m, "\\1...") - str += "\e[0m" if Color.colorable? - end - else - output_width = Reline::Unicode.calculate_width(@context.return_format % str, true) - diff_size = output_width - Reline::Unicode.calculate_width(str, true) - if diff_size.positive? and output_width > winwidth - lines, _ = Reline::Unicode.split_by_width(str, winwidth - diff_size - 3) - str = "%s..." % lines.first - str += "\e[0m" if Color.colorable? - end - end + unless @context.return_format.include?('%') + puts @context.return_format + return end - if multiline_p && @context.newline_before_multiline_output? - str = "\n" + str + winheight, winwidth = @context.io.winsize + if omit + content, overflow = Pager.take_first_page(winwidth, 1) do |out| + @context.inspect_last_value(out) + end + if overflow + content = "\n#{content}" if @context.newline_before_multiline_output? + content = "#{content}..." + content = "#{content}\e[0m" if Color.colorable? + end + puts format(@context.return_format, content.chomp) + elsif Pager.should_page? && @context.inspector_support_stream_output? + formatter_proc = ->(content, multipage) do + content = content.chomp + content = "\n#{content}" if @context.newline_before_multiline_output? && (multipage || content.include?("\n")) + format(@context.return_format, content) + end + Pager.page_with_preview(winwidth, winheight, formatter_proc) do |out| + @context.inspect_last_value(out) + end + else + content = @context.inspect_last_value.chomp + content = "\n#{content}" if @context.newline_before_multiline_output? && content.include?("\n") + Pager.page_content(format(@context.return_format, content), retain_content: true) end - - Pager.page_content(format(@context.return_format, str), retain_content: true) end # Outputs the local variables to this current session, including #signal_status diff --git a/lib/irb/command/copy.rb b/lib/irb/command/copy.rb index 3fd3f5493..b6aee0c33 100644 --- a/lib/irb/command/copy.rb +++ b/lib/irb/command/copy.rb @@ -15,7 +15,7 @@ def execute(arg) arg = '_' if arg.to_s.strip.empty? value = irb_context.workspace.binding.eval(arg) - output = irb_context.inspect_method.inspect_value(value, colorize: false) + output = irb_context.inspect_method.inspect_value(value, +'', colorize: false).chomp if clipboard_available? copy_to_clipboard(output) diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 2642256c4..395d9081f 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -658,8 +658,12 @@ def colorize_input(input, complete:) end end - def inspect_last_value # :nodoc: - @inspect_method.inspect_value(@last_value) + def inspect_last_value(output = +'') # :nodoc: + @inspect_method.inspect_value(@last_value, output) + end + + def inspector_support_stream_output? + @inspect_method.support_stream_output? end NOPRINTING_IVARS = ["@last_value"] # :nodoc: diff --git a/lib/irb/inspector.rb b/lib/irb/inspector.rb index a1679b20f..75a257b4b 100644 --- a/lib/irb/inspector.rb +++ b/lib/irb/inspector.rb @@ -92,9 +92,14 @@ def init @init.call if @init end + def support_stream_output? + second_parameter_type = @inspect.parameters[1]&.first + second_parameter_type == :req || second_parameter_type == :opt + end + # Proc to call when the input is evaluated and output in irb. - def inspect_value(v, colorize: true) - @inspect.call(v, colorize: colorize) + def inspect_value(v, output, colorize: true) + support_stream_output? ? @inspect.call(v, output, colorize: colorize) : output << @inspect.call(v, colorize: colorize) rescue => e puts "An error occurred when inspecting the object: #{e.inspect}" @@ -113,8 +118,8 @@ def inspect_value(v, colorize: true) Inspector.def_inspector([:p, :inspect]){|v, colorize: true| Color.colorize_code(v.inspect, colorable: colorize && Color.colorable? && Color.inspect_colorable?(v)) } - Inspector.def_inspector([true, :pp, :pretty_inspect], proc{require_relative "color_printer"}){|v, colorize: true| - IRB::ColorPrinter.pp(v, +'', colorize: colorize).chomp + Inspector.def_inspector([true, :pp, :pretty_inspect], proc{require_relative "color_printer"}){|v, output, colorize: true| + IRB::ColorPrinter.pp(v, output, colorize: colorize) } Inspector.def_inspector([:yaml, :YAML], proc{require "yaml"}){|v| begin diff --git a/lib/irb/pager.rb b/lib/irb/pager.rb index 7c1249dd5..65303e5ac 100644 --- a/lib/irb/pager.rb +++ b/lib/irb/pager.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'reline' + module IRB # The implementation of this class is borrowed from RDoc's lib/rdoc/ri/driver.rb. # Please do NOT use this class directly outside of IRB. @@ -47,12 +49,42 @@ def page(retain_content: false) rescue Errno::EPIPE end - private - def should_page? IRB.conf[:USE_PAGER] && STDIN.tty? && (ENV.key?("TERM") && ENV["TERM"] != "dumb") end + def page_with_preview(width, height, formatter_proc) + overflow_callback = ->(lines) do + modified_output = formatter_proc.call(lines.join, true) + content, = take_first_page(width, [height - 2, 0].max) {|o| o.write modified_output } + content = content.chomp + content = "#{content}\e[0m" if Color.colorable? + $stdout.puts content + $stdout.puts 'Preparing full inspection value...' + end + out = PageOverflowIO.new(width, height, overflow_callback, delay: 0.1) + yield out + content = formatter_proc.call(out.string, out.multipage?) + if out.multipage? + page(retain_content: true) do |io| + io.puts content + end + else + $stdout.puts content + end + end + + def take_first_page(width, height) + overflow_callback = proc do |lines| + return lines.join, true + end + out = Pager::PageOverflowIO.new(width, height, overflow_callback) + yield out + [out.string, false] + end + + private + def content_exceeds_screen_height?(content) screen_height, screen_width = begin Reline.get_screen_size @@ -62,10 +94,10 @@ def content_exceeds_screen_height?(content) pageable_height = screen_height - 3 # leave some space for previous and the current prompt - # If the content has more lines than the pageable height - content.lines.count > pageable_height || - # Or if the content is a few long lines - pageable_height * screen_width < Reline::Unicode.calculate_width(content, true) + return true if content.lines.size > pageable_height + + _, overflow = take_first_page(screen_width, pageable_height) {|out| out.write content } + overflow end def setup_pager(retain_content:) @@ -96,5 +128,83 @@ def setup_pager(retain_content:) nil end end + + # Writable IO that has page overflow callback + class PageOverflowIO + attr_reader :string, :first_page_lines + + # Maximum size of a single cell in terminal + # Assumed worst case: "\e[1;3;4;9;38;2;255;128;128;48;2;128;128;255mA\e[0m" + # bold, italic, underline, crossed_out, RGB forgound, RGB background + MAX_CHAR_PER_CELL = 50 + + def initialize(width, height, overflow_callback, delay: nil) + @lines = [] + @first_page_lines = nil + @width = width + @height = height + @buffer = +'' + @overflow_callback = overflow_callback + @col = 0 + @string = +'' + @multipage = false + @delay_until = (Time.now + delay if delay) + end + + def puts(text = '') + write(text) + write("\n") unless text.end_with?("\n") + end + + def write(text) + @string << text + if @multipage + if @delay_until && Time.now > @delay_until + @overflow_callback.call(@first_page_lines) + @delay_until = nil + end + return + end + + overflow_size = (@width * (@height - @lines.size) + @width - @col) * MAX_CHAR_PER_CELL + if text.size >= overflow_size + text = text[0, overflow_size] + overflow = true + end + + @buffer << text + @col += Reline::Unicode.calculate_width(text) + if text.include?("\n") || @col >= @width + @buffer.lines.each do |line| + wrapped_lines = Reline::Unicode.split_by_width(line.chomp, @width).first.compact + wrapped_lines.pop if wrapped_lines.last == '' + @lines.concat(wrapped_lines) + if @lines.empty? + @lines << "\n" + elsif line.end_with?("\n") + @lines[-1] += "\n" + end + end + @buffer.clear + @buffer << @lines.pop unless @lines.last.end_with?("\n") + @col = Reline::Unicode.calculate_width(@buffer) + end + if overflow || @lines.size > @height || (@lines.size == @height && @col > 0) + @first_page_lines = @lines.take(@height) + if !@delay_until || Time.now > @delay_until + @overflow_callback.call(@first_page_lines) + @delay_until = nil + end + @multipage = true + end + end + + def multipage? + @multipage + end + + alias print write + alias << write + end end end diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index b02d8dbe0..c44c8e057 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -361,7 +361,7 @@ def test_omit_multiline_on_assignment irb.eval_input end assert_empty err - assert_equal("=> #{value_first_line[0..(input.winsize.last - 9)]}...\n=> \n#{value}\n", out) + assert_equal("=> \n#{value_first_line[0, input.winsize.last]}...\n=> \n#{value}\n", out) irb.context.evaluate_expression('A.remove_method(:inspect)', 0) input.reset diff --git a/test/irb/test_pager.rb b/test/irb/test_pager.rb new file mode 100644 index 000000000..5842519e6 --- /dev/null +++ b/test/irb/test_pager.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: false +require 'irb/pager' + +require_relative 'helper' + +module TestIRB + class PagerTest < TestCase + def test_take_first_page + assert_equal ['a' * 40, true], IRB::Pager.take_first_page(10, 4) {|io| io.puts 'a' * 41; raise 'should not reach here' } + assert_equal ['a' * 39, false], IRB::Pager.take_first_page(10, 4) {|io| io.write 'a' * 39 } + assert_equal ['a' * 39 + 'b', false], IRB::Pager.take_first_page(10, 4) {|io| io.write 'a' * 39 + 'b' } + assert_equal ['a' * 39 + 'b', true], IRB::Pager.take_first_page(10, 4) {|io| io.write 'a' * 39 + 'bc' } + assert_equal ["a\nb\nc\nd\n", false], IRB::Pager.take_first_page(10, 4) {|io| io.write "a\nb\nc\nd\n" } + assert_equal ["a\nb\nc\nd\n", true], IRB::Pager.take_first_page(10, 4) {|io| io.write "a\nb\nc\nd\ne" } + assert_equal ['a' * 15 + "\n" + 'b' * 20, true], IRB::Pager.take_first_page(10, 4) {|io| io.puts 'a' * 15; io.puts 'b' * 30 } + assert_equal ["\e[31mA\e[0m" * 10 + 'x' * 30, true], IRB::Pager.take_first_page(10, 4) {|io| io.puts "\e[31mA\e[0m" * 10 + 'x' * 31; } + end + end + + class PageOverflowIOTest < TestCase + def test_overflow + actual_events = [] + overflow_callback = ->(lines) do + actual_events << [:callback_called, lines] + end + out = IRB::Pager::PageOverflowIO.new(10, 4, overflow_callback) + out.puts 'a' * 15 + out.write 'b' * 15 + + actual_events << :before_write + out.write 'c' * 1000 + actual_events << :after_write + + out.puts 'd' * 1000 + out.write 'e' * 1000 + + expected_events = [ + :before_write, + [:callback_called, ['a' * 10, 'a' * 5 + "\n", 'b' * 10, 'b' * 5 + 'c' * 5]], + :after_write, + ] + assert_equal expected_events, actual_events + + expected_whole_content = 'a' * 15 + "\n" + 'b' * 15 + 'c' * 1000 + 'd' * 1000 + "\n" + 'e' * 1000 + assert_equal expected_whole_content, out.string + end + + def test_callback_delay + actual_events = [] + overflow_callback = ->(lines) do + actual_events << [:callback_called, lines] + end + out = IRB::Pager::PageOverflowIO.new(10, 4, overflow_callback, delay: 0.2) + out.write 'a' * 1000 + assert_equal ['a' * 10] * 4, out.first_page_lines + out.write 'b' + actual_events << :before_delay + sleep 0.2 + out.write 'c' + actual_events << :after_delay + out.write 'd' + assert_equal 'a' * 1000 + 'bcd', out.string + assert_equal [:before_delay, [:callback_called, ['a' * 10] * 4], :after_delay], actual_events + end + end +end diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index 212ab0cf8..c1bc81241 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -385,12 +385,28 @@ def test_long_evaluation_output_is_paged write("'a' * 80 * 11\n") write("'foo' + 'bar'\n") # eval something to make sure IRB resumes - assert_screen(/(a{80}\n){8}/) + assert_screen(/"a{79}\n(a{80}\n){7}/) # because pager is invoked, foobar will not be evaluated assert_screen(/\A(?!foobar)/) close end + def test_pretty_print_preview_with_slow_inspect + write_irbrc <<~'LINES' + require "irb/pager" + LINES + start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) + write("o1 = Object.new; def o1.inspect; 'INSPECT'; end\n") + write("o2 = Object.new; def o2.inspect; sleep 0.1; 'SLOW'; end\n") + # preview should be shown even if pretty_print is not completed. + write("[o1] * 20 + [o2] * 100\n") + assert_screen(/=>\n\[INSPECT,\n( INSPECT,\n){6}Preparing full inspection value\.\.\./) + write("\C-c") # abort pretty_print + write("'foo' + 'bar'\n") # eval something to make sure IRB resumes + assert_screen(/foobar/) + close + end + def test_long_evaluation_output_is_preserved_after_paging write_irbrc <<~'LINES' require "irb/pager" From 6194111611ae6f52c0e81fda528cbda12e3f8c0d Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 22 Jan 2025 00:12:28 +0800 Subject: [PATCH 236/263] Update documentation about the new copy command (#1067) --- doc/Index.md | 1 + lib/irb/command/copy.rb | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/doc/Index.md b/doc/Index.md index bc1461eff..20a86e2d9 100644 --- a/doc/Index.md +++ b/doc/Index.md @@ -186,6 +186,7 @@ Debugging Misc edit Open a file or source location. measure `measure` enables the mode to measure processing time. `measure :off` disables it. + copy Copy expression output to clipboard Context show_doc Look up documentation with RI. diff --git a/lib/irb/command/copy.rb b/lib/irb/command/copy.rb index b6aee0c33..93410b878 100644 --- a/lib/irb/command/copy.rb +++ b/lib/irb/command/copy.rb @@ -3,11 +3,21 @@ module IRB module Command class Copy < Base - category "Workspace" - description "Copy command output to clipboard" + category "Misc" + description "Copy expression output to clipboard" help_message(<<~HELP) - Usage: copy [command] + Usage: copy ([expression]) + + When given: + - an expression, copy the inspect result of the expression to the clipboard. + - no arguments, copy the last evaluated result (`_`) to the clipboard. + + Examples: + + copy Foo.new + copy User.all.to_a + copy HELP def execute(arg) From cb15c29550a910ecac637b9be45955cdf4dbc6de Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 22 Jan 2025 00:23:56 +0800 Subject: [PATCH 237/263] [DOC] Exclude the word `IRB` from RDoc's autolinking (#1068) * Use RDoc 6.11.0's word exclusion feature * Remove unnecessary escapes from documentation --- .rdoc_options | 3 ++ Gemfile | 2 + doc/COMMAND_LINE_OPTIONS.md | 2 +- doc/Configurations.md | 32 +++++------ doc/Index.md | 104 ++++++++++++++++++------------------ 5 files changed, 74 insertions(+), 69 deletions(-) diff --git a/.rdoc_options b/.rdoc_options index 81831200d..cf6fa833d 100644 --- a/.rdoc_options +++ b/.rdoc_options @@ -1,2 +1,5 @@ page_dir: doc warn_missing_rdoc_ref: true + +autolink_excluded_words: +- IRB diff --git a/Gemfile b/Gemfile index 3c2efa44d..b4e8955a3 100644 --- a/Gemfile +++ b/Gemfile @@ -22,6 +22,8 @@ gem "rubocop" gem "tracer" if !is_truffleruby gem "debug", github: "ruby/debug", platforms: [:mri, :mswin] +gem "rdoc", ">= 6.11.0" + if RUBY_VERSION >= "3.0.0" && !is_truffleruby gem "repl_type_completor" end diff --git a/doc/COMMAND_LINE_OPTIONS.md b/doc/COMMAND_LINE_OPTIONS.md index babbdece7..08077ff1b 100644 --- a/doc/COMMAND_LINE_OPTIONS.md +++ b/doc/COMMAND_LINE_OPTIONS.md @@ -1,6 +1,6 @@ # Index of Command-Line Options -These are the \IRB command-line options, with links to explanatory text: +These are the IRB command-line options, with links to explanatory text: - `-d`: Set `$DEBUG` and {$VERBOSE}[rdoc-ref:IRB@Verbosity] to `true`. diff --git a/doc/Configurations.md b/doc/Configurations.md index a7efbc8a8..1a7479956 100644 --- a/doc/Configurations.md +++ b/doc/Configurations.md @@ -1,17 +1,17 @@ -# Configure \IRB +# Configure IRB ## Configuration Sources -\IRB configurations can be set through multiple sources, each with its own precedence: +IRB configurations can be set through multiple sources, each with its own precedence: -1. **Command-Line Options**: When some options are specified when starting \IRB, they can override default settings. -2. **Configuration File**: If present, \IRB reads a configuration file containing Ruby code to set configurations. -3. **Environment Variables**: Certain environment variables influence \IRB's behavior. +1. **Command-Line Options**: When some options are specified when starting IRB, they can override default settings. +2. **Configuration File**: If present, IRB reads a configuration file containing Ruby code to set configurations. +3. **Environment Variables**: Certain environment variables influence IRB's behavior. 4. **Hash `IRB.conf`**: This hash holds the current configuration settings, which can be modified during a session. ### Configuration File Path Resolution -\IRB searches for a configuration file in the following order: +IRB searches for a configuration file in the following order: 1. `$IRBRC` 2. `$XDG_CONFIG_HOME/irb/irbrc` @@ -28,7 +28,7 @@ Method `conf.rc?` returns `true` if a configuration file was read, `false` other ## Environment Variables -- `NO_COLOR`: Disables \IRB's colorization. +- `NO_COLOR`: Disables IRB's colorization. - `IRB_USE_AUTOCOMPLETE`: Setting to `false` disables autocompletion. - `IRB_COMPLETOR`: Configures auto-completion behavior (`regexp` or `type`). - `IRB_COPY_COMMAND`: Overrides the default program used to interface with the system clipboard. @@ -48,7 +48,7 @@ The initial entries in hash `IRB.conf` are determined by: You can see the hash by typing `IRB.conf`. Below are the primary entries: -- `:AP_NAME`: \IRB {application name}[rdoc-ref:IRB@Application+Name]; +- `:AP_NAME`: IRB {application name}[rdoc-ref:IRB@Application+Name]; initial value: `'irb'`. - `:AT_EXIT`: Array of hooks to call {at exit}[rdoc-ref:IRB@IRB]; @@ -76,7 +76,7 @@ You can see the hash by typing `IRB.conf`. Below are the primary entries: initial value: `nil`, which would set `conf.echo_on_assignment` to `:truncate`. - `:EVAL_HISTORY`: How much {evaluation history}[rdoc-ref:IRB@Evaluation+History] is to be stored; initial value: `nil`. -- `:EXTRA_DOC_DIRS`: \Array of +- `:EXTRA_DOC_DIRS`: Array of {RI documentation directories}[rdoc-ref:IRB@RI+Documentation+Directories] to be parsed for the documentation dialog; initial value: `[]`. @@ -95,7 +95,7 @@ You can see the hash by typing `IRB.conf`. Below are the primary entries: - RUBY_DIR is the Ruby installation dirpath. - RUBY_VER_NUM is the Ruby version number. - - IRB_VER_NUM is the \IRB version number. + - IRB_VER_NUM is the IRB version number. - `:IRB_NAME`: {IRB name}[rdoc-ref:IRB@IRB+Name]; initial value: `'irb'`. @@ -104,7 +104,7 @@ You can see the hash by typing `IRB.conf`. Below are the primary entries: - `:LC_MESSAGES`: {Locale}[rdoc-ref:IRB@Locale]; initial value: IRB::Locale object. - `:LOAD_MODULES`: deprecated. -- `:MAIN_CONTEXT`: The {context}[rdoc-ref:IRB@Session+Context] for the main \IRB session; +- `:MAIN_CONTEXT`: The {context}[rdoc-ref:IRB@Session+Context] for the main IRB session; initial value: IRB::Context object. - `:MEASURE`: Whether to {measure performance}[rdoc-ref:IRB@Performance+Measurement]; @@ -121,7 +121,7 @@ You can see the hash by typing `IRB.conf`. Below are the primary entries: :STACKPROF=># } -- `:PROMPT`: \Hash of {defined prompts}[rdoc-ref:IRB@Prompt+and+Return+Formats]; +- `:PROMPT`: Hash of {defined prompts}[rdoc-ref:IRB@Prompt+and+Return+Formats]; initial value: { @@ -158,7 +158,7 @@ You can see the hash by typing `IRB.conf`. Below are the primary entries: initial value: `false`. - `:VERBOSE`: Whether to print {verbose output}[rdoc-ref:IRB@Verbosity]; initial value: `nil`. -- `:__MAIN__`: The main \IRB object; +- `:__MAIN__`: The main IRB object; initial value: `main`. ## Notes on Initialization Precedence @@ -228,9 +228,9 @@ You can set the default initial value via: Note that the configuration file entry overrides the command-line options. -## \IRB Name +## IRB Name -You can specify a name for \IRB. +You can specify a name for IRB. The default initial value is `'irb'`: @@ -247,7 +247,7 @@ IRB.conf[:IRB_NAME] = 'foo' ## Application Name -You can specify an application name for the \IRB session. +You can specify an application name for the IRB session. The default initial value is `'irb'`: diff --git a/doc/Index.md b/doc/Index.md index 20a86e2d9..ad0dcf111 100644 --- a/doc/Index.md +++ b/doc/Index.md @@ -1,23 +1,23 @@ -# \IRB +# IRB [![Gem Version](https://badge.fury.io/rb/irb.svg)](https://badge.fury.io/rb/irb) [![build](https://github.com/ruby/irb/actions/workflows/test.yml/badge.svg)](https://github.com/ruby/irb/actions/workflows/test.yml) ## Overview -\IRB stands for "Interactive Ruby" and is a tool to interactively execute Ruby expressions read from the standard input. The `irb` command from your shell will start the interpreter. +IRB stands for "Interactive Ruby" and is a tool to interactively execute Ruby expressions read from the standard input. The `irb` command from your shell will start the interpreter. -\IRB provides a shell-like interface that supports user interaction with the Ruby interpreter. It operates as a *read-eval-print loop* ([REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)) that: +IRB provides a shell-like interface that supports user interaction with the Ruby interpreter. It operates as a *read-eval-print loop* ([REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)) that: -- **Reads** each character as you type. You can modify the \IRB context to change the way input works. See [Input](#label-Input). +- **Reads** each character as you type. You can modify the IRB context to change the way input works. See [Input](#label-Input). - **Evaluates** the code each time it has read a syntactically complete passage. -- **Prints** after evaluating. You can modify the \IRB context to change the way output works. See [Output](#label-Output). +- **Prints** after evaluating. You can modify the IRB context to change the way output works. See [Output](#label-Output). ## Installation > **Note** > -> \IRB is a default gem of Ruby, so you shouldn't need to install it separately. However, if you're using Ruby 2.6 or later and want to upgrade/install a specific version of \IRB, follow these steps. +> IRB is a default gem of Ruby, so you shouldn't need to install it separately. However, if you're using Ruby 2.6 or later and want to upgrade/install a specific version of IRB, follow these steps. To install it with `bundler`, add this line to your application's Gemfile: @@ -41,11 +41,11 @@ $ gem install irb > **Note** > -> We're working hard to match Pry's variety of powerful features in \IRB. Track our progress or find contribution ideas in [COMPARED_WITH_PRY.md](./COMPARED_WITH_PRY.md). +> We're working hard to match Pry's variety of powerful features in IRB. Track our progress or find contribution ideas in [COMPARED_WITH_PRY.md](./COMPARED_WITH_PRY.md). -### Starting \IRB +### Starting IRB -You can start a fresh \IRB session by typing `irb` in your terminal. In the session, you can evaluate Ruby expressions or prototype small Ruby scripts. Input is executed when it is syntactically complete. +You can start a fresh IRB session by typing `irb` in your terminal. In the session, you can evaluate Ruby expressions or prototype small Ruby scripts. Input is executed when it is syntactically complete. ```console $ irb @@ -64,7 +64,7 @@ irb(main):007> Foo.new.foo ### The `binding.irb` Breakpoint -If you use Ruby 2.5 or later versions, you can use `binding.irb` in your program as breakpoints. Once `binding.irb` is evaluated, a new \IRB session starts with the surrounding context: +If you use Ruby 2.5 or later versions, you can use `binding.irb` in your program as breakpoints. Once `binding.irb` is evaluated, a new IRB session starts with the surrounding context: ```console $ ruby test.rb @@ -86,23 +86,23 @@ Hello World ### Debugging -You can use \IRB as a debugging console with `debug.gem` with these options: +You can use IRB as a debugging console with `debug.gem` with these options: - In `binding.irb`, use the `debug` command to start an `irb:rdbg` session with access to all `debug.gem` commands. -- Use the `RUBY_DEBUG_IRB_CONSOLE=1` environment variable to make `debug.gem` use \IRB as the debugging console. +- Use the `RUBY_DEBUG_IRB_CONSOLE=1` environment variable to make `debug.gem` use IRB as the debugging console. -To learn more about debugging with \IRB, see [Debugging with \IRB](#label-Debugging+with+IRB). +To learn more about debugging with IRB, see [Debugging with IRB](#label-Debugging+with+IRB). ## Startup -At startup, \IRB: +At startup, IRB: 1. Interprets (as Ruby code) the content of the [configuration file](#label-Configuration) (if given). 2. Constructs the initial session context from [hash IRB.conf](#label-Hash+IRB.conf) and from default values; the hash content may have been affected by [command-line options](#command-line-options), and by direct assignments in the configuration file. 3. Assigns the context to variable `conf`. 4. Assigns command-line arguments to variable `ARGV`. 5. Prints the prompt. -6. Puts the content of the [initialization script](#label-Initialization+script) onto the \IRB shell, just as if it were user-typed commands. +6. Puts the content of the [initialization script](#label-Initialization+script) onto the IRB shell, just as if it were user-typed commands. ## Command Line @@ -110,15 +110,15 @@ On the command line, all options precede all arguments; the first item that is n ### Command-Line Options -Many command-line options affect entries in hash `IRB.conf`, which in turn affect the initial configuration of the \IRB session. +Many command-line options affect entries in hash `IRB.conf`, which in turn affect the initial configuration of the IRB session. -Details of the options are described in relevant subsections below. A cursory list of \IRB command-line options may be seen in the [help message](https://raw.githubusercontent.com/ruby/irb/master/lib/irb/lc/help-message), which is also displayed if you use command-line option `--help`. +Details of the options are described in relevant subsections below. A cursory list of IRB command-line options may be seen in the [help message](https://raw.githubusercontent.com/ruby/irb/master/lib/irb/lc/help-message), which is also displayed if you use command-line option `--help`. If you are interested in a specific option, consult the [index](rdoc-ref:COMMAND_LINE_OPTIONS.md). ### Command-Line Arguments -Command-line arguments are passed to \IRB in array `ARGV`: +Command-line arguments are passed to IRB in array `ARGV`: ```console $ irb --noscript Foo Bar Baz @@ -140,7 +140,7 @@ $ ## Commands -The following commands are available in \IRB. Use the `help` command to see the list of available commands. +The following commands are available in IRB. Use the `help` command to see the list of available commands. ```txt Help @@ -202,19 +202,19 @@ Aliases @ Alias for `whereami` ``` -## Configure \IRB +## Configure IRB See [Configurations](rdoc-ref:Configurations.md) for more details. ## Input -This section describes the features that allow you to change the way \IRB input works; see also [Output](#output). +This section describes the features that allow you to change the way IRB input works; see also [Output](#output). ### Input Command History -By default, \IRB stores a history of up to 1000 input commands in a file named `.irb_history`. The history file will be in the same directory as the [configuration file](#label-Configuration+File) if one is found, or in `~/` otherwise. +By default, IRB stores a history of up to 1000 input commands in a file named `.irb_history`. The history file will be in the same directory as the [configuration file](#label-Configuration+File) if one is found, or in `~/` otherwise. -A new \IRB session creates the history file if it does not exist and appends to the file if it does exist. +A new IRB session creates the history file if it does not exist and appends to the file if it does exist. You can change the filepath by adding to your configuration file: `IRB.conf[:HISTORY_FILE] = *filepath*`, where *filepath* is a string filepath. @@ -231,7 +231,7 @@ During the session, you can use methods `conf.save_history` or `conf.save_histor ### Command Aliases -By default, \IRB defines several command aliases: +By default, IRB defines several command aliases: ```console irb(main):001> conf.command_aliases @@ -264,7 +264,7 @@ During the session, method `conf.ignore_siging?` returns the setting, and method ### Automatic Completion -By default, \IRB enables [automatic completion](https://en.wikipedia.org/wiki/Autocomplete#In_command-line_interpreter): +By default, IRB enables [automatic completion](https://en.wikipedia.org/wiki/Autocomplete#In_command-line_interpreter): You can disable it by either of these: @@ -277,7 +277,7 @@ The setting may not be changed during the session. ### Type Based Completion -\IRB's default completion `IRB::RegexpCompletor` uses Regexp. \IRB offers an experimental completion `IRB::TypeCompletor` that uses type analysis. +IRB's default completion `IRB::RegexpCompletor` uses Regexp. IRB offers an experimental completion `IRB::TypeCompletor` that uses type analysis. #### How to Enable IRB::TypeCompletor @@ -296,13 +296,13 @@ gem 'repl_type_completor', group: [:development, :test] Now you can use type-based completion by: -- Running \IRB with the `--type-completor` option +- Running IRB with the `--type-completor` option ```console $ irb --type-completor ``` -- Or writing this line to \IRB's rc-file (e.g., `~/.irbrc`) +- Or writing this line to IRB's rc-file (e.g., `~/.irbrc`) ```ruby IRB.conf[:COMPLETOR] = :type # default is :regexp @@ -315,7 +315,7 @@ Now you can use type-based completion by: IRB.start ``` -To check if it's enabled, type `irb_info` into \IRB and see the `Completion` section. +To check if it's enabled, type `irb_info` into IRB and see the `Completion` section. ```console irb(main):001> irb_info @@ -327,7 +327,7 @@ Completion: Autocomplete, RegexpCompletor ... ``` -If you have a `sig/` directory or `rbs_collection.lock.yaml` in the current directory, \IRB will load it. +If you have a `sig/` directory or `rbs_collection.lock.yaml` in the current directory, IRB will load it. #### Advantage over Default IRB::RegexpCompletor @@ -376,7 +376,7 @@ irb(main):002> a.first. # Completes Integer methods ### Automatic Indentation -By default, \IRB automatically indents lines of code to show structure (e.g., it indents the contents of a block). +By default, IRB automatically indents lines of code to show structure (e.g., it indents the contents of a block). The current setting is returned by the configuration method `conf.auto_indent_mode`. @@ -397,24 +397,24 @@ You can change the initial setting in the configuration file with: IRB.conf[:AUTO_INDENT] = false ``` -Note that the *current* setting *may not* be changed in the \IRB session. +Note that the *current* setting *may not* be changed in the IRB session. ### Input Method -The \IRB input method determines how command input is read; by default, the input method for a session is \IRB::RelineInputMethod unless the TERM environment variable is 'dumb', in which case the most simplistic input method is used. +The IRB input method determines how command input is read; by default, the input method for a session is IRB::RelineInputMethod unless the TERM environment variable is 'dumb', in which case the most simplistic input method is used. You can set the input method by: - Adding to the configuration file: - - `IRB.conf[:USE_SINGLELINE] = true` or `IRB.conf[:USE_MULTILINE] = false` sets the input method to \IRB::ReadlineInputMethod. - - `IRB.conf[:USE_SINGLELINE] = false` or `IRB.conf[:USE_MULTILINE] = true` sets the input method to \IRB::RelineInputMethod. + - `IRB.conf[:USE_SINGLELINE] = true` or `IRB.conf[:USE_MULTILINE] = false` sets the input method to IRB::ReadlineInputMethod. + - `IRB.conf[:USE_SINGLELINE] = false` or `IRB.conf[:USE_MULTILINE] = true` sets the input method to IRB::RelineInputMethod. - Giving command-line options: - - `--singleline` or `--nomultiline` sets the input method to \IRB::ReadlineInputMethod. - - `--nosingleline` or `--multiline` sets the input method to \IRB::RelineInputMethod. - - `--nosingleline` together with `--nomultiline` sets the input to \IRB::StdioInputMethod. + - `--singleline` or `--nomultiline` sets the input method to IRB::ReadlineInputMethod. + - `--nosingleline` or `--multiline` sets the input method to IRB::RelineInputMethod. + - `--nosingleline` together with `--nomultiline` sets the input to IRB::StdioInputMethod. Method `conf.use_multiline?` and its synonym `conf.use_reline` return: @@ -430,11 +430,11 @@ Method `conf.use_singleline?` and its synonym `conf.use_readline` return: ## Output -This section describes the features that allow you to change the way \IRB output works; see also [Input](#label-Input). +This section describes the features that allow you to change the way IRB output works; see also [Input](#label-Input). ### Return-Value Printing (Echoing) -By default, \IRB prints (echoes) the values returned by all input commands. +By default, IRB prints (echoes) the values returned by all input commands. You can change the initial behavior and suppress all echoing by: @@ -443,7 +443,7 @@ You can change the initial behavior and suppress all echoing by: During the session, you can change the current setting with configuration method `conf.echo=` (set to `true` or `false`). -As stated above, by default \IRB prints the values returned by all input commands; but \IRB offers special treatment for values returned by assignment statements, which may be: +As stated above, by default IRB prints the values returned by all input commands; but IRB offers special treatment for values returned by assignment statements, which may be: - Printed with truncation (to fit on a single line of output), which is the default; an ellipsis (`...` is suffixed, to indicate the truncation): @@ -462,7 +462,7 @@ You can change the initial behavior by: During the session, you can change the current setting with configuration method `conf.echo_on_assignment=` (set to `true`, `false`, or `:truncate`). -By default, \IRB formats returned values by calling method `inspect`. +By default, IRB formats returned values by calling method `inspect`. You can change the initial behavior by: @@ -473,7 +473,7 @@ During the session, you can change the setting using method `conf.inspect_mode=` ### Multiline Output -By default, \IRB prefixes a newline to a multiline response. +By default, IRB prefixes a newline to a multiline response. You can change the initial default value by adding to the configuration file: @@ -501,7 +501,7 @@ bar ### Evaluation History -By default, \IRB saves no history of evaluations (returned values), and the related methods `conf.eval_history`, `_`, and `__` are undefined. +By default, IRB saves no history of evaluations (returned values), and the related methods `conf.eval_history`, `_`, and `__` are undefined. You can turn on that history and set the maximum number of evaluations to be stored: @@ -593,13 +593,13 @@ Doing either of the above: By default, the first command-line argument (after any options) is the path to a Ruby initialization script. -\IRB reads the initialization script and puts its content onto the \IRB shell, just as if it were user-typed commands. +IRB reads the initialization script and puts its content onto the IRB shell, just as if it were user-typed commands. Command-line option `--noscript` causes the first command-line argument to be treated as an ordinary argument (instead of an initialization script); `--script` is the default. -## Debugging with \IRB +## Debugging with IRB -Starting from version 1.8.0, \IRB offers a powerful integration with `debug.gem`, providing a debugging experience similar to `pry-byebug`. +Starting from version 1.8.0, IRB offers a powerful integration with `debug.gem`, providing a debugging experience similar to `pry-byebug`. After hitting a `binding.irb` breakpoint, you can activate the debugger with the `debug` command. Alternatively, if the `debug` method is already defined in the current scope, you can call `irb_debug`. @@ -639,7 +639,7 @@ irb:rdbg(main):003> next # use next command to move to the next line irb:rdbg(main):004> ``` -Simultaneously, you maintain access to \IRB's commands, such as `show_source`: +Simultaneously, you maintain access to IRB's commands, such as `show_source`: ```console irb:rdbg(main):004> show_source greet @@ -670,7 +670,7 @@ In the `irb:rdbg` session, the `help` command also displays all commands from `d This integration offers several benefits over `debug.gem`'s native console: -1. Access to handy \IRB commands like `show_source` or `show_doc`. +1. Access to handy IRB commands like `show_source` or `show_doc`. 2. Support for multi-line input. 3. Symbol shortcuts such as `@` (`whereami`) and `$` (`show_source`). 4. Autocompletion. @@ -679,7 +679,7 @@ This integration offers several benefits over `debug.gem`'s native console: However, there are some limitations to be aware of: 1. `binding.irb` doesn't support `pre` and `do` arguments like [binding.break](https://github.com/ruby/debug#bindingbreak-method). -2. As \IRB [doesn't currently support remote-connection](https://github.com/ruby/irb/issues/672), it can't be used with `debug.gem`'s remote debugging feature. +2. As IRB [doesn't currently support remote-connection](https://github.com/ruby/irb/issues/672), it can't be used with `debug.gem`'s remote debugging feature. 3. Access to the previous return value via the underscore `_` is not supported. ## Encodings @@ -692,9 +692,9 @@ Command-line option `-U` sets both to UTF-8. See [CONTRIBUTING.md](https://github.com/ruby/irb/blob/main/CONTRIBUTING.md) for more information. -## Extending \IRB +## Extending IRB -\IRB `v1.13.0` and later versions allow users/libraries to extend its functionality through official APIs. +IRB `v1.13.0` and later versions allow users/libraries to extend its functionality through official APIs. For more information, visit [EXTEND_IRB.md](rdoc-ref:EXTEND_IRB.md). From d3531d8fc06445df726f66f2eac56693d0b9e009 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 22 Jan 2025 00:24:09 +0800 Subject: [PATCH 238/263] Bump version to v1.15.0 (#1066) --- lib/irb/version.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irb/version.rb b/lib/irb/version.rb index cae57e127..37261082f 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -5,7 +5,7 @@ # module IRB # :nodoc: - VERSION = "1.14.3" + VERSION = "1.15.0" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2024-12-18" + @LAST_UPDATE_DATE = "2025-01-21" end From a139562a0749a2dff0c929d4fed5abf5a72ea80c Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 22 Jan 2025 10:39:55 +0900 Subject: [PATCH 239/263] Fix pager preview with escape sequence and newlines (#1069) --- lib/irb/pager.rb | 17 ++++++++++------- test/irb/test_pager.rb | 10 +++++++++- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/irb/pager.rb b/lib/irb/pager.rb index 65303e5ac..16ff30cf8 100644 --- a/lib/irb/pager.rb +++ b/lib/irb/pager.rb @@ -152,11 +152,13 @@ def initialize(width, height, overflow_callback, delay: nil) end def puts(text = '') + text = text.to_s unless text.is_a?(String) write(text) write("\n") unless text.end_with?("\n") end def write(text) + text = text.to_s unless text.is_a?(String) @string << text if @multipage if @delay_until && Time.now > @delay_until @@ -171,23 +173,24 @@ def write(text) text = text[0, overflow_size] overflow = true end - @buffer << text - @col += Reline::Unicode.calculate_width(text) + @col += Reline::Unicode.calculate_width(text, true) if text.include?("\n") || @col >= @width @buffer.lines.each do |line| wrapped_lines = Reline::Unicode.split_by_width(line.chomp, @width).first.compact wrapped_lines.pop if wrapped_lines.last == '' @lines.concat(wrapped_lines) - if @lines.empty? - @lines << "\n" - elsif line.end_with?("\n") - @lines[-1] += "\n" + if line.end_with?("\n") + if @lines.empty? || @lines.last.end_with?("\n") + @lines << "\n" + else + @lines[-1] += "\n" + end end end @buffer.clear @buffer << @lines.pop unless @lines.last.end_with?("\n") - @col = Reline::Unicode.calculate_width(@buffer) + @col = Reline::Unicode.calculate_width(@buffer, true) end if overflow || @lines.size > @height || (@lines.size == @height && @col > 0) @first_page_lines = @lines.take(@height) diff --git a/test/irb/test_pager.rb b/test/irb/test_pager.rb index 5842519e6..0fad94da3 100644 --- a/test/irb/test_pager.rb +++ b/test/irb/test_pager.rb @@ -7,13 +7,21 @@ module TestIRB class PagerTest < TestCase def test_take_first_page assert_equal ['a' * 40, true], IRB::Pager.take_first_page(10, 4) {|io| io.puts 'a' * 41; raise 'should not reach here' } + assert_equal ["a\nb\na\nb\n", true], IRB::Pager.take_first_page(10, 4) {|io| 10.times { io.puts "a\nb\n" } } + assert_equal ["a\n\n\na\n", true], IRB::Pager.take_first_page(10, 4) {|io| 10.times { io.puts "a\n\n\n" } } + assert_equal ["11\n" * 4, true], IRB::Pager.take_first_page(10, 4) {|io| 10.times { io.write 1; io.puts 1 } } + assert_equal ["\n" * 4, true], IRB::Pager.take_first_page(10, 4) {|io| 10.times { io.write nil; io.puts nil } } assert_equal ['a' * 39, false], IRB::Pager.take_first_page(10, 4) {|io| io.write 'a' * 39 } assert_equal ['a' * 39 + 'b', false], IRB::Pager.take_first_page(10, 4) {|io| io.write 'a' * 39 + 'b' } assert_equal ['a' * 39 + 'b', true], IRB::Pager.take_first_page(10, 4) {|io| io.write 'a' * 39 + 'bc' } assert_equal ["a\nb\nc\nd\n", false], IRB::Pager.take_first_page(10, 4) {|io| io.write "a\nb\nc\nd\n" } assert_equal ["a\nb\nc\nd\n", true], IRB::Pager.take_first_page(10, 4) {|io| io.write "a\nb\nc\nd\ne" } assert_equal ['a' * 15 + "\n" + 'b' * 20, true], IRB::Pager.take_first_page(10, 4) {|io| io.puts 'a' * 15; io.puts 'b' * 30 } - assert_equal ["\e[31mA\e[0m" * 10 + 'x' * 30, true], IRB::Pager.take_first_page(10, 4) {|io| io.puts "\e[31mA\e[0m" * 10 + 'x' * 31; } + assert_equal ["\e[31mA\e[0m" * 10 + 'x' * 30, true], IRB::Pager.take_first_page(10, 4) {|io| io.puts "\e[31mA\e[0m" * 10 + 'x' * 31 } + text, overflow = IRB::Pager.take_first_page(10, 4) {|io| 41.times { io.write "\e[31mA\e[0m" } } + assert_equal ['A' * 40, true], [text.gsub(/\e\[\d+m/, ''), overflow] + text, overflow = IRB::Pager.take_first_page(10, 4) {|io| 41.times { io.write "\e[31mAAA\e[0m" } } + assert_equal ['A' * 40, true], [text.gsub(/\e\[\d+m/, ''), overflow] end end From df37b074e333bb164a67f8126c56e3c2068eedef Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 22 Jan 2025 14:58:59 +0900 Subject: [PATCH 240/263] Bump version to 1.15.1 (#1070) --- lib/irb/version.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irb/version.rb b/lib/irb/version.rb index 37261082f..b195956fe 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -5,7 +5,7 @@ # module IRB # :nodoc: - VERSION = "1.15.0" + VERSION = "1.15.1" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2025-01-21" + @LAST_UPDATE_DATE = "2025-01-22" end From f6c5106364d9413af4ae925c37408db814e5baeb Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 23 Jan 2025 00:58:50 +0900 Subject: [PATCH 241/263] Use EnvUtil.rubybin instead of "ruby" in copy command test (#1071) `ruby` is not always available. --- test/irb/command/test_copy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/irb/command/test_copy.rb b/test/irb/command/test_copy.rb index 8ba62bd96..505812a1d 100644 --- a/test/irb/command/test_copy.rb +++ b/test/irb/command/test_copy.rb @@ -8,7 +8,7 @@ module TestIRB class CopyTest < IntegrationTestCase def setup super - @envs['IRB_COPY_COMMAND'] = "ruby -e \"puts 'foo' + STDIN.read\"" + @envs['IRB_COPY_COMMAND'] = "#{EnvUtil.rubybin} -e \"puts 'foo' + STDIN.read\"" end def test_copy_with_pbcopy From 876bfcc188671970e680c7fda1f77acb48e8940d Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 26 Jan 2025 18:48:10 +0800 Subject: [PATCH 242/263] Remove ruby-core workflow (#1075) Since IRB is not a default gem anymore, we don't need to run CI against Ruby core anymore, at least not in the current form. --- .github/workflows/ruby-core.yml | 59 --------------------------------- 1 file changed, 59 deletions(-) delete mode 100644 .github/workflows/ruby-core.yml diff --git a/.github/workflows/ruby-core.yml b/.github/workflows/ruby-core.yml deleted file mode 100644 index d1d5f69c8..000000000 --- a/.github/workflows/ruby-core.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: ruby-core - -on: - pull_request: - - push: - branches: - - master - -concurrency: - group: ci-${{ github.ref }}-${{ github.workflow }} - -permissions: # added using https://github.com/step-security/secure-workflows - contents: read - -jobs: - ruby_core: - name: IRB under a ruby-core setup - runs-on: ubuntu-20.04 - strategy: - fail-fast: false - timeout-minutes: 30 - steps: - - name: Set up latest ruby head - uses: ruby/setup-ruby@250fcd6a742febb1123a77a841497ccaa8b9e939 # v1.152.0 - with: - ruby-version: head - bundler: none - - name: Save latest buildable revision to environment - run: echo "REF=$(ruby -v | cut -d')' -f1 | cut -d' ' -f5)" >> $GITHUB_ENV - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3.1.0 - with: - repository: ruby/ruby - path: ruby/ruby - fetch-depth: 10 - - name: Checkout the latest buildable revision - run: git switch -c ${{ env.REF }} - working-directory: ruby/ruby - - name: Install libraries - run: | - set -x - sudo apt-get update -q || : - sudo apt-get install --no-install-recommends -q -y build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm-dev bison autoconf ruby - - name: Build Ruby - run: | - autoconf - ./configure -C --disable-install-doc - make -j2 - working-directory: ruby/ruby - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3.1.0 - with: - path: ruby/irb - - name: Sync tools - run: | - ruby tool/sync_default_gems.rb irb - working-directory: ruby/ruby - - name: Test IRB - run: make -j2 -s test-all TESTS="irb --no-retry" - working-directory: ruby/ruby From 5623f0a829f324a474b534d65699f0ff6b3434de Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Mon, 27 Jan 2025 20:41:54 +0900 Subject: [PATCH 243/263] Suppress irb_info measures ambiguous_width in command test (#1074) --- test/irb/helper.rb | 19 +++++++++++++++++++ test/irb/test_command.rb | 16 ---------------- test/irb/test_eval_history.rb | 16 ---------------- test/irb/test_helper_method.rb | 16 ---------------- 4 files changed, 19 insertions(+), 48 deletions(-) diff --git a/test/irb/helper.rb b/test/irb/helper.rb index ea2c6ef16..17e463e2a 100644 --- a/test/irb/helper.rb +++ b/test/irb/helper.rb @@ -91,6 +91,25 @@ def without_rdoc(&block) ::Kernel.undef_method :irb_original_require } end + + def execute_lines(*lines, conf: {}, main: self, irb_path: nil) + # To suppress irb_info measure ambiguous_width with escape sequences + Reline.core.instance_variable_set(:@ambiguous_width, 1) + + IRB.init_config(nil) + IRB.conf[:VERBOSE] = false + IRB.conf[:PROMPT_MODE] = :SIMPLE + IRB.conf.merge!(conf) + input = TestInputMethod.new(lines) + irb = IRB::Irb.new(IRB::WorkSpace.new(main), input) + irb.context.return_format = "=> %s\n" + irb.context.irb_path = irb_path if irb_path + IRB.conf[:MAIN_CONTEXT] = irb.context + IRB.conf[:USE_PAGER] = false + capture_output do + irb.eval_input + end + end end class IntegrationTestCase < TestCase diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index ec2d1f92d..fd98a5a1b 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -37,22 +37,6 @@ def teardown FileUtils.rm_rf(@tmpdir) restore_encodings end - - def execute_lines(*lines, conf: {}, main: self, irb_path: nil) - capture_output do - IRB.init_config(nil) - IRB.conf[:VERBOSE] = false - IRB.conf[:PROMPT_MODE] = :SIMPLE - IRB.conf[:USE_PAGER] = false - IRB.conf.merge!(conf) - input = TestInputMethod.new(lines) - irb = IRB::Irb.new(IRB::WorkSpace.new(main), input) - irb.context.return_format = "=> %s\n" - irb.context.irb_path = irb_path if irb_path - IRB.conf[:MAIN_CONTEXT] = irb.context - irb.eval_input - end - end end class FrozenObjectTest < CommandTestCase diff --git a/test/irb/test_eval_history.rb b/test/irb/test_eval_history.rb index 54913ceff..685ad679a 100644 --- a/test/irb/test_eval_history.rb +++ b/test/irb/test_eval_history.rb @@ -14,22 +14,6 @@ def teardown restore_encodings end - def execute_lines(*lines, conf: {}, main: self, irb_path: nil) - IRB.init_config(nil) - IRB.conf[:VERBOSE] = false - IRB.conf[:PROMPT_MODE] = :SIMPLE - IRB.conf[:USE_PAGER] = false - IRB.conf.merge!(conf) - input = TestInputMethod.new(lines) - irb = IRB::Irb.new(IRB::WorkSpace.new(main), input) - irb.context.return_format = "=> %s\n" - irb.context.irb_path = irb_path if irb_path - IRB.conf[:MAIN_CONTEXT] = irb.context - capture_output do - irb.eval_input - end - end - def test_eval_history_is_disabled_by_default out, err = execute_lines( "a = 1", diff --git a/test/irb/test_helper_method.rb b/test/irb/test_helper_method.rb index a3e2c43b2..4b61397b7 100644 --- a/test/irb/test_helper_method.rb +++ b/test/irb/test_helper_method.rb @@ -16,22 +16,6 @@ def teardown $VERBOSE = @verbosity restore_encodings end - - def execute_lines(*lines, conf: {}, main: self, irb_path: nil) - IRB.init_config(nil) - IRB.conf[:VERBOSE] = false - IRB.conf[:PROMPT_MODE] = :SIMPLE - IRB.conf.merge!(conf) - input = TestInputMethod.new(lines) - irb = IRB::Irb.new(IRB::WorkSpace.new(main), input) - irb.context.return_format = "=> %s\n" - irb.context.irb_path = irb_path if irb_path - IRB.conf[:MAIN_CONTEXT] = irb.context - IRB.conf[:USE_PAGER] = false - capture_output do - irb.eval_input - end - end end module TestHelperMethod From 1ca7472f76cb05a67c86c21fdf90ad3712c9c043 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Mon, 27 Jan 2025 23:01:02 +0900 Subject: [PATCH 244/263] Fallback to Reline when `require 'readline'` fails (#1076) Require readline may fail because it is a bundled gem in 3.5.0.dev. --- lib/irb/input-method.rb | 19 ++++++++++++------- test/irb/test_history.rb | 8 +++++++- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index 260d9a1cb..f7f0c80ae 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -175,10 +175,15 @@ def close class ReadlineInputMethod < StdioInputMethod class << self def initialize_readline - require "readline" - rescue LoadError - else - include ::Readline + return if defined?(self::Readline) + + begin + require 'readline' + const_set(:Readline, ::Readline) + rescue LoadError + const_set(:Readline, ::Reline) + end + const_set(:HISTORY, self::Readline::HISTORY) end end @@ -216,8 +221,8 @@ def completion_info def gets Readline.input = @stdin Readline.output = @stdout - if l = readline(@prompt, false) - HISTORY.push(l) if !l.empty? + if l = Readline.readline(@prompt, false) + Readline::HISTORY.push(l) if !l.empty? @line[@line_no += 1] = l + "\n" else @eof = true @@ -239,7 +244,7 @@ def prompting? # For debug message def inspect - readline_impl = (defined?(Reline) && Readline == Reline) ? 'Reline' : 'ext/readline' + readline_impl = Readline == ::Reline ? 'Reline' : 'ext/readline' str = "ReadlineInputMethod with #{readline_impl} #{Readline::VERSION}" inputrc_path = File.expand_path(ENV['INPUTRC'] || '~/.inputrc') str += " and #{inputrc_path}" if File.exist?(inputrc_path) diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index 0171bb0ec..77b680d91 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -1,6 +1,5 @@ # frozen_string_literal: false require 'irb' -require 'readline' require "tempfile" require_relative "helper" @@ -8,6 +7,13 @@ return if RUBY_PLATFORM.match?(/solaris|mswin|mingw/i) module TestIRB + begin + require 'readline' + Readline = ::Readline + rescue LoadError + Readline = ::Reline + end + class HistoryTest < TestCase def setup @conf_backup = IRB.conf.dup From 095a1f73d272a0b67cdf5cfbd49474d6315c1b19 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 27 Jan 2025 23:02:40 +0900 Subject: [PATCH 245/263] bundled gems migration (#1078) * Pass lib directory of src repository for assert_in_out_err explicitly * Use git ls-files for bundled gems preparation * Inject reline gem path into assert_in_out_err * Keep current file list --- irb.gemspec | 2 +- test/irb/test_option.rb | 4 +++- test/irb/test_raise_exception.rb | 20 +++++++++++++++----- test/irb/test_ruby_lex.rb | 4 +++- test/irb/test_workspace.rb | 3 ++- 5 files changed, 24 insertions(+), 9 deletions(-) diff --git a/irb.gemspec b/irb.gemspec index 9a93382b7..cce77e68f 100644 --- a/irb.gemspec +++ b/irb.gemspec @@ -33,7 +33,7 @@ Gem::Specification.new do |spec| "exe/irb", "irb.gemspec", "man/irb.1", - ] + Dir.glob("lib/**/*") + ] + Dir.chdir(File.expand_path('..', __FILE__)) { Dir.glob("lib/**/*") } spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] diff --git a/test/irb/test_option.rb b/test/irb/test_option.rb index fec31f384..a3a40c0b9 100644 --- a/test/irb/test_option.rb +++ b/test/irb/test_option.rb @@ -6,7 +6,9 @@ class OptionTest < TestCase def test_end_of_option bug4117 = '[ruby-core:33574]' bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] - status = assert_in_out_err(bundle_exec + %w[-W0 -rirb -e IRB.start(__FILE__) -- -f --], "", //, [], bug4117) + libdir = File.expand_path("../../lib", __dir__) + reline_libdir = Gem.loaded_specs["reline"].full_gem_path + "/lib" + status = assert_in_out_err(bundle_exec + %W[-W0 -I#{libdir} -I#{reline_libdir} -rirb -e IRB.start(__FILE__) -- -f --], "", //, [], bug4117) assert(status.success?, bug4117) end end diff --git a/test/irb/test_raise_exception.rb b/test/irb/test_raise_exception.rb index 44a5ae87e..133f073f5 100644 --- a/test/irb/test_raise_exception.rb +++ b/test/irb/test_raise_exception.rb @@ -7,15 +7,19 @@ module TestIRB class RaiseExceptionTest < TestCase def test_raise_exception_with_nil_backtrace bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] - assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, /#/, []) + libdir = File.expand_path("../../lib", __dir__) + reline_libdir = Gem.loaded_specs["reline"].full_gem_path + "/lib" + assert_in_out_err(bundle_exec + %W[-I#{libdir} -I#{reline_libdir} -rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, /#/, []) raise Exception.new("foo").tap {|e| def e.backtrace; nil; end } IRB end def test_raise_exception_with_message_exception bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] + libdir = File.expand_path("../../lib", __dir__) + reline_libdir = Gem.loaded_specs["reline"].full_gem_path + "/lib" expected = /#\nbacktraces are hidden because bar was raised when processing them/ - assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, expected, []) + assert_in_out_err(bundle_exec + %W[-I#{libdir} -I#{reline_libdir} -rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, expected, []) e = Exception.new("foo") def e.message; raise 'bar'; end raise e @@ -24,8 +28,10 @@ def e.message; raise 'bar'; end def test_raise_exception_with_message_inspect_exception bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] + libdir = File.expand_path("../../lib", __dir__) + reline_libdir = Gem.loaded_specs["reline"].full_gem_path + "/lib" expected = /Uninspectable exception occurred/ - assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, expected, []) + assert_in_out_err(bundle_exec + %W[-I#{libdir} -I#{reline_libdir} -rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, expected, []) e = Exception.new("foo") def e.message; raise; end def e.inspect; raise; end @@ -36,7 +42,9 @@ def e.inspect; raise; end def test_raise_exception_with_invalid_byte_sequence pend if RUBY_ENGINE == 'truffleruby' || /mswin|mingw/ =~ RUBY_PLATFORM bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] - assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<~IRB, /A\\xF3B \(StandardError\)/, []) + libdir = File.expand_path("../../lib", __dir__) + reline_libdir = Gem.loaded_specs["reline"].full_gem_path + "/lib" + assert_in_out_err(bundle_exec + %W[-I#{libdir} -I#{reline_libdir} -rirb -W0 -e IRB.start(__FILE__) -- -f --], <<~IRB, /A\\xF3B \(StandardError\)/, []) raise StandardError, "A\\xf3B" IRB end @@ -47,6 +55,8 @@ def test_raise_exception_with_different_encoding_containing_invalid_byte_sequenc ENV["HOME"] = tmpdir bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] + libdir = File.expand_path("../../lib", __dir__) + reline_libdir = Gem.loaded_specs["reline"].full_gem_path + "/lib" File.open("#{tmpdir}/euc.rb", 'w') do |f| f.write(<<~EOF) # encoding: euc-jp @@ -60,7 +70,7 @@ def raise_euc_with_invalid_byte_sequence %w(LC_MESSAGES LC_ALL LC_CTYPE LANG).each {|n| env[n] = "ja_JP.UTF-8" } # TruffleRuby warns when the locale does not exist env['TRUFFLERUBYOPT'] = "#{ENV['TRUFFLERUBYOPT']} --log.level=SEVERE" if RUBY_ENGINE == 'truffleruby' - args = [env] + bundle_exec + %W[-rirb -C #{tmpdir} -W0 -e IRB.start(__FILE__) -- -f --] + args = [env] + bundle_exec + %W[-I#{libdir} -I#{reline_libdir} -rirb -C #{tmpdir} -W0 -e IRB.start(__FILE__) -- -f --] error = /raise_euc_with_invalid_byte_sequence': あ\\xFF \(RuntimeError\)/ assert_in_out_err(args, <<~IRB, error, [], encoding: "UTF-8") require_relative 'euc' diff --git a/test/irb/test_ruby_lex.rb b/test/irb/test_ruby_lex.rb index 4e406a8ce..9392b49fa 100644 --- a/test/irb/test_ruby_lex.rb +++ b/test/irb/test_ruby_lex.rb @@ -200,7 +200,9 @@ def test_assignment_expression_with_local_variable end def test_initialising_the_old_top_level_ruby_lex - assert_in_out_err(["--disable-gems", "-W:deprecated"], <<~RUBY, [], /warning: constant ::RubyLex is deprecated/) + libdir = File.expand_path("../../lib", __dir__) + reline_libdir = Gem.loaded_specs["reline"].full_gem_path + "/lib" + assert_in_out_err(["-I#{libdir}", "-I#{reline_libdir}", "--disable-gems", "-W:deprecated"], <<~RUBY, [], /warning: constant ::RubyLex is deprecated/) require "irb" ::RubyLex.new(nil) RUBY diff --git a/test/irb/test_workspace.rb b/test/irb/test_workspace.rb index ad515f91d..99ade80f6 100644 --- a/test/irb/test_workspace.rb +++ b/test/irb/test_workspace.rb @@ -84,12 +84,13 @@ def test_toplevel_binding_local_variables bug17623 = '[ruby-core:102468]' bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] top_srcdir = "#{__dir__}/../.." + reline_libdir = Gem.loaded_specs["reline"].full_gem_path + "/lib" irb_path = nil %w[exe libexec].find do |dir| irb_path = "#{top_srcdir}/#{dir}/irb" File.exist?(irb_path) end or omit 'irb command not found' - assert_in_out_err(bundle_exec + ['-W0', "-C#{top_srcdir}", '-e', <<~RUBY, '--', '-f', '--'], 'binding.local_variables', /\[:_\]/, [], bug17623) + assert_in_out_err(bundle_exec + ['-W0', "-I#{top_srcdir}/lib", "-I#{reline_libdir}", "-C#{top_srcdir}", '-e', <<~RUBY, '--', '-f', '--'], 'binding.local_variables', /\[:_\]/, [], bug17623) version = 'xyz' # typical rubygems loading file load('#{irb_path}') RUBY From b37c1c2db92c0c9277de8ece09ece5a76cee505e Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 28 Jan 2025 03:03:27 +0900 Subject: [PATCH 246/263] Zero winsize bugfix (#1073) * Page overflow calculation should work even with winsize=[0,0] * InputMethod#winsize fallback to [24, 80] if width or height is zero --- lib/irb/input-method.rb | 7 ++++--- lib/irb/pager.rb | 2 +- test/irb/test_pager.rb | 12 ++++++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index f7f0c80ae..b9bbdeb1e 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -26,10 +26,11 @@ def gets def winsize if instance_variable_defined?(:@stdout) && @stdout.tty? - @stdout.winsize - else - [24, 80] + winsize = @stdout.winsize + # If width or height is 0, something is wrong. + return winsize unless winsize.include? 0 end + [24, 80] end # Whether this input method is still readable when there is no more data to diff --git a/lib/irb/pager.rb b/lib/irb/pager.rb index 16ff30cf8..89e1e7100 100644 --- a/lib/irb/pager.rb +++ b/lib/irb/pager.rb @@ -189,7 +189,7 @@ def write(text) end end @buffer.clear - @buffer << @lines.pop unless @lines.last.end_with?("\n") + @buffer << @lines.pop if !@lines.empty? && !@lines.last.end_with?("\n") @col = Reline::Unicode.calculate_width(@buffer, true) end if overflow || @lines.size > @height || (@lines.size == @height && @col > 0) diff --git a/test/irb/test_pager.rb b/test/irb/test_pager.rb index 0fad94da3..5a61b0e91 100644 --- a/test/irb/test_pager.rb +++ b/test/irb/test_pager.rb @@ -70,5 +70,17 @@ def test_callback_delay assert_equal 'a' * 1000 + 'bcd', out.string assert_equal [:before_delay, [:callback_called, ['a' * 10] * 4], :after_delay], actual_events end + + def test_zero_width + out = IRB::Pager::PageOverflowIO.new(0, 0, ->*{}) + 100.times { out.write 'a' } + assert_equal [true, [], 'a' * 100], [out.multipage?, out.first_page_lines, out.string] + out = IRB::Pager::PageOverflowIO.new(10, 0, ->*{}) + 100.times { out.write 'a' } + assert_equal [true, [], 'a' * 100], [out.multipage?, out.first_page_lines, out.string] + out = IRB::Pager::PageOverflowIO.new(0, 10, ->*{}) + 100.times { out.write 'a' } + assert_equal [true, [], 'a' * 100], [out.multipage?, out.first_page_lines, out.string] + end end end From f4439b470786145d744c93c75ef3465089415a78 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 28 Jan 2025 09:36:44 +0900 Subject: [PATCH 247/263] Ignore to contain directory to Gem::Specification#files --- irb.gemspec | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/irb.gemspec b/irb.gemspec index cce77e68f..af14713f6 100644 --- a/irb.gemspec +++ b/irb.gemspec @@ -33,7 +33,9 @@ Gem::Specification.new do |spec| "exe/irb", "irb.gemspec", "man/irb.1", - ] + Dir.chdir(File.expand_path('..', __FILE__)) { Dir.glob("lib/**/*") } + ] + Dir.chdir(File.expand_path('..', __FILE__)) do + Dir.glob("lib/**/*").map {|f| f unless File.directory?(f) }.compact + end spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] From a05231fc022edd958d678b19c30889d7fe40a9fa Mon Sep 17 00:00:00 2001 From: Andy Waite Date: Wed, 5 Feb 2025 13:04:38 -0500 Subject: [PATCH 248/263] Document the keys for completion (#1082) --- doc/Index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/Index.md b/doc/Index.md index ad0dcf111..172fdebc9 100644 --- a/doc/Index.md +++ b/doc/Index.md @@ -266,6 +266,8 @@ During the session, method `conf.ignore_siging?` returns the setting, and method By default, IRB enables [automatic completion](https://en.wikipedia.org/wiki/Autocomplete#In_command-line_interpreter): +To cycle through the completion suggestions, use the tab key (and shift-tab to reverse). + You can disable it by either of these: - Adding `IRB.conf[:USE_AUTOCOMPLETE] = false` to the configuration file. From 421c28f7ca857541df6b66114d815cdd57c62a00 Mon Sep 17 00:00:00 2001 From: Andy Waite Date: Thu, 13 Feb 2025 12:12:12 -0500 Subject: [PATCH 249/263] Disable schedule jobs for forks (#1084) --- .github/workflows/test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 415cffa38..ef8778cc4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,11 +8,13 @@ on: jobs: ruby-versions: + if: github.repository == 'ruby/irb' || github.event_name != 'schedule' uses: ruby/actions/.github/workflows/ruby_versions.yml@master with: engine: cruby min_version: 2.7 lint: + if: github.repository == 'ruby/irb' || github.event_name != 'schedule' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -49,6 +51,7 @@ jobs: - name: Run tests in isolation run: bundle exec rake test_in_isolation debug-test: + if: github.repository == 'ruby/irb' || github.event_name != 'schedule' name: Debug compatibility test runs-on: ubuntu-latest timeout-minutes: 30 From 9f1bfde29eba377c5454b7e85c718191b64a2456 Mon Sep 17 00:00:00 2001 From: Hiroaki Osawa Date: Fri, 14 Feb 2025 02:13:21 +0900 Subject: [PATCH 250/263] add context.ap_name test (#1052) --- test/irb/test_context.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index c44c8e057..432f56e8f 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -724,6 +724,13 @@ def test_build_completor IRB.conf[:COMPLETOR] = original_completor end + def test_ap_name + assert_equal 'irb', @context.ap_name + IRB.conf[:AP_NAME] = 'foo' + ap_name_modified_context = IRB::Context.new(nil, IRB::WorkSpace.new(Object.new), TestInputMethod.new) + assert_equal 'foo', ap_name_modified_context.ap_name + end + private def without_colorize From 3c90aa613a7689b406ab04e5f4ce5a7b0e6539d7 Mon Sep 17 00:00:00 2001 From: Artur <22315378+artur-intech@users.noreply.github.com> Date: Wed, 19 Feb 2025 15:39:12 +0300 Subject: [PATCH 251/263] Document `USE_PAGER` config (#1086) * Document `USE_PAGER` config Enhances https://github.com/ruby/irb/pull/783. * Fix --- doc/Configurations.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/Configurations.md b/doc/Configurations.md index 1a7479956..d66ecf242 100644 --- a/doc/Configurations.md +++ b/doc/Configurations.md @@ -156,6 +156,8 @@ You can see the hash by typing `IRB.conf`. Below are the primary entries: - `:USE_TRACER`: Whether to use the {IRB tracer}[rdoc-ref:IRB@Tracer]; initial value: `false`. +- `:USE_PAGER`: Controls whether pager is enabled. + initial value: `true`. - `:VERBOSE`: Whether to print {verbose output}[rdoc-ref:IRB@Verbosity]; initial value: `nil`. - `:__MAIN__`: The main IRB object; From 8b63f05160aa90e0205cf392b10cb85d6ecfa9a3 Mon Sep 17 00:00:00 2001 From: Andy Waite Date: Sat, 8 Mar 2025 03:54:27 -0500 Subject: [PATCH 252/263] Disable truffle-ruby scheduled job on forks (#1087) --- .github/workflows/truffle-ruby-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/truffle-ruby-test.yml b/.github/workflows/truffle-ruby-test.yml index 43ffb0ba5..37d6f06b4 100644 --- a/.github/workflows/truffle-ruby-test.yml +++ b/.github/workflows/truffle-ruby-test.yml @@ -8,6 +8,7 @@ on: jobs: irb: + if: github.repository == 'ruby/irb' || github.event_name != 'schedule' name: rake test truffleruby-head ${{ matrix.with_latest_reline && '(latest reline)' || '' }} strategy: matrix: From ef6037140e54fa4cc5f9b8d328fed680658e2964 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 3 Apr 2025 20:47:49 +0900 Subject: [PATCH 253/263] Bump version to 1.15.2 (#1088) --- lib/irb/version.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irb/version.rb b/lib/irb/version.rb index b195956fe..950370112 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -5,7 +5,7 @@ # module IRB # :nodoc: - VERSION = "1.15.1" + VERSION = "1.15.2" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2025-01-22" + @LAST_UPDATE_DATE = "2025-04-03" end From 6349b03f64f173a9d31bc9042430e1134f3e1689 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 9 Apr 2025 19:13:44 +0900 Subject: [PATCH 254/263] Handle keyword local variables correctly (#1085) Local variable can be a keyword. Example: `def f(if:0, and:0); binding.irb; end` IRB prepends local variable assignment code `a=_=nil;` to the input code but keyword local variables should be excluded. --- lib/irb/completion.rb | 24 ++---------------------- lib/irb/ruby-lex.rb | 25 +++++++++++++++++++++++++ test/irb/test_color.rb | 4 ++-- test/irb/test_irb.rb | 14 ++++++++++++++ test/irb/test_ruby_lex.rb | 12 ++++++++++-- 5 files changed, 53 insertions(+), 26 deletions(-) diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index 3e9704706..a78adef8d 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -12,26 +12,6 @@ class BaseCompletor # :nodoc: # Set of reserved words used by Ruby, you should not use these for # constants or variables - ReservedWords = %w[ - __ENCODING__ __LINE__ __FILE__ - BEGIN END - alias and - begin break - case class - def defined? do - else elsif end ensure - false for - if in - module - next nil not - or - redo rescue retry return - self super - then true - undef unless until - when while - yield - ] HELP_COMMAND_PREPOSING = /\Ahelp\s+/ @@ -459,12 +439,12 @@ def retrieve_completion_data(input, bind:, doc_namespace:) eval("#{perfect_match_var}.class.name", bind) rescue nil else candidates = (bind.eval_methods | bind.eval_private_methods | bind.local_variables | bind.eval_instance_variables | bind.eval_class_constants).collect{|m| m.to_s} - candidates |= ReservedWords + candidates |= RubyLex::RESERVED_WORDS.map(&:to_s) candidates.find{ |i| i == input } end else candidates = (bind.eval_methods | bind.eval_private_methods | bind.local_variables | bind.eval_instance_variables | bind.eval_class_constants).collect{|m| m.to_s} - candidates |= ReservedWords + candidates |= RubyLex::RESERVED_WORDS.map(&:to_s) candidates.grep(/^#{Regexp.quote(input)}/).sort end end diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb index 3abb53b4e..dd4a8d060 100644 --- a/lib/irb/ruby-lex.rb +++ b/lib/irb/ruby-lex.rb @@ -52,6 +52,27 @@ class RubyLex on_words_beg on_qwords_beg ] + RESERVED_WORDS = %i[ + __ENCODING__ __LINE__ __FILE__ + BEGIN END + alias and + begin break + case class + def defined? do + else elsif end ensure + false for + if in + module + next nil not + or + redo rescue retry return + self super + then true + undef unless until + when while + yield + ] + class TerminateLineInput < StandardError def initialize super("Terminate Line Input") @@ -77,6 +98,10 @@ def compile_with_errors_suppressed(code, line_no: 1) end def generate_local_variables_assign_code(local_variables) + # Some reserved words could be a local variable + # Example: def f(if: 1); binding.irb; end + # These reserved words should be removed from assignment code + local_variables -= RESERVED_WORDS "#{local_variables.join('=')}=nil;" unless local_variables.empty? end diff --git a/test/irb/test_color.rb b/test/irb/test_color.rb index 5529e2904..363f56977 100644 --- a/test/irb/test_color.rb +++ b/test/irb/test_color.rb @@ -167,8 +167,8 @@ def test_colorize_code_with_local_variables result_with_lvars = "a /(b +#{BLUE}#{BOLD}1#{CLEAR})/i" assert_equal_with_term(result_without_lvars, code) - assert_equal_with_term(result_with_lvar, code, local_variables: ['a']) - assert_equal_with_term(result_with_lvars, code, local_variables: ['a', 'b']) + assert_equal_with_term(result_with_lvar, code, local_variables: [:a]) + assert_equal_with_term(result_with_lvars, code, local_variables: [:a, :b]) end def test_colorize_code_complete_true diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index 617e9c961..d687ca9af 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -56,6 +56,20 @@ def test_underscore_stores_last_result assert_include output, "=> 12" end + def test_local_variable_handeld_correctly + write_ruby <<~'RUBY' + tap do |if:0, and:0, foo:100| + binding.irb + end + RUBY + + output = run_ruby_file do + type 'foo /4#/ do' + type 'exit!' + end + assert_include output, '=> 25' + end + def test_commands_dont_override_stored_last_result write_ruby <<~'RUBY' binding.irb diff --git a/test/irb/test_ruby_lex.rb b/test/irb/test_ruby_lex.rb index 9392b49fa..4e44ab44f 100644 --- a/test/irb/test_ruby_lex.rb +++ b/test/irb/test_ruby_lex.rb @@ -38,8 +38,16 @@ def test_local_variables_dependent_code lines = ["a /1#/ do", "2"] assert_indent_level(lines, 1) assert_code_block_open(lines, true) - assert_indent_level(lines, 0, local_variables: ['a']) - assert_code_block_open(lines, false, local_variables: ['a']) + assert_indent_level(lines, 0, local_variables: [:a]) + assert_code_block_open(lines, false, local_variables: [:a]) + end + + def test_keyword_local_variables + # Assuming `def f(if: 1, and: 2, ); binding.irb; end` + local_variables = [:if, :and] + lines = ['1 + 2'] + assert_indent_level(lines, 0, local_variables: local_variables) + assert_code_block_open(lines, false, local_variables: local_variables) end def test_literal_ends_with_space From e91b21f885e66cc0a58a4a3424eafa94cd7bd4b4 Mon Sep 17 00:00:00 2001 From: Mau Magnaguagno Date: Sun, 27 Apr 2025 04:32:54 -0300 Subject: [PATCH 255/263] Avoid intermediate array from split (#1093) Match first sequence of non-whitespace characters. --- lib/irb/debug.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/irb/debug.rb b/lib/irb/debug.rb index 59be1365b..e429eaaff 100644 --- a/lib/irb/debug.rb +++ b/lib/irb/debug.rb @@ -60,7 +60,7 @@ def DEBUGGER__.capture_frames(*args) if !DEBUGGER__::CONFIG[:no_hint] && irb.context.io.is_a?(RelineInputMethod) Reline.output_modifier_proc = proc do |input, complete:| unless input.strip.empty? - cmd = input.split(/\s/, 2).first + cmd = input[/\S+/] if !complete && DEBUGGER__.commands.key?(cmd) input = input.sub(/\n$/, " # debug command\n") From 6a519e9d5cb1ad85531e1c79d1fce468ba45415f Mon Sep 17 00:00:00 2001 From: Mau Magnaguagno Date: Tue, 6 May 2025 13:53:50 -0300 Subject: [PATCH 256/263] Replace gsub with rstrip (#1095) The regular expression ``/\s*\z/`` matches any whitespace characters at end of string, which means only one match is possible with ``gsub``, replacing it with an empty string. The same can be achieved by ``String#rstrip``. --- lib/irb/completion.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index a78adef8d..1254d92a5 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -159,7 +159,7 @@ def complete_require_path(target, preposing, postposing) else return nil # It's not String literal end - tokens = RubyLex.ripper_lex_without_warning(preposing.gsub(/\s*\z/, '')) + tokens = RubyLex.ripper_lex_without_warning(preposing.rstrip) tok = nil tokens.reverse_each do |t| unless [:on_lparen, :on_sp, :on_ignored_sp, :on_nl, :on_ignored_nl, :on_comment].include?(t.event) From 3265f098caa6d26899932ec5dbfc8e059cf102ad Mon Sep 17 00:00:00 2001 From: Mau Magnaguagno Date: Tue, 6 May 2025 15:43:07 -0300 Subject: [PATCH 257/263] Prefer filter_map and map+grep instead of map+compact and select+map (#1094) * Prefer filter_map in completion * Prefer filter_map in test_irb * Prefer filter_map in inspector * map with grep is more readable * map with grep is more readable --- lib/irb/completion.rb | 16 ++++++---------- lib/irb/inspector.rb | 2 +- test/irb/test_irb.rb | 4 ++-- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index 1254d92a5..3795cea43 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -40,13 +40,13 @@ def doc_namespace(preposing, matched, postposing, bind:) def retrieve_gem_and_system_load_path candidates = (GEM_PATHS | $LOAD_PATH) - candidates.map do |p| + candidates.filter_map do |p| if p.respond_to?(:to_path) p.to_path else String(p) rescue nil end - end.compact.sort + end.sort end def retrieve_files_to_require_from_load_path @@ -171,16 +171,12 @@ def complete_require_path(target, preposing, postposing) case tok.tok when 'require' - retrieve_files_to_require_from_load_path.select { |path| - path.start_with?(actual_target) - }.map { |path| - quote + path + retrieve_files_to_require_from_load_path.filter_map { |path| + quote + path if path.start_with?(actual_target) } when 'require_relative' - retrieve_files_to_require_relative_from_current_dir.select { |path| - path.start_with?(actual_target) - }.map { |path| - quote + path + retrieve_files_to_require_relative_from_current_dir.filter_map { |path| + quote + path if path.start_with?(actual_target) } end end diff --git a/lib/irb/inspector.rb b/lib/irb/inspector.rb index 75a257b4b..07d2cf75d 100644 --- a/lib/irb/inspector.rb +++ b/lib/irb/inspector.rb @@ -46,7 +46,7 @@ class << self # Determines the inspector to use where +inspector+ is one of the keys passed # during inspector definition. def keys_with_inspector(inspector) - INSPECTORS.select{|k, v| v == inspector}.collect{|k, v| k} + INSPECTORS.filter_map {|k, v| k if v == inspector} end # Example diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index d687ca9af..f20045082 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -877,7 +877,7 @@ def bar end assert_match(/irbtest-.*\.rb:2:in (`|'Object#)foo': error \(RuntimeError\)/, output) - frame_traces = output.split("\n").select { |line| line.strip.match?(/from /) }.map(&:strip) + frame_traces = output.split("\n").map(&:strip).grep(/from /) expected_traces = if RUBY_VERSION >= "3.3.0" [ @@ -932,7 +932,7 @@ def bar end assert_match(/irbtest-.*\.rb:2:in (`|'Object#)foo': error \(RuntimeError\)/, output) - frame_traces = output.split("\n").select { |line| line.strip.match?(/from /) }.map(&:strip) + frame_traces = output.split("\n").map(&:strip).grep(/from /) expected_traces = [ /from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/, From f5254a34ce2ae64c3c21c52fa55c78cec1c1bcb7 Mon Sep 17 00:00:00 2001 From: muno92 <34187067+muno92@users.noreply.github.com> Date: Fri, 9 May 2025 00:09:57 +0900 Subject: [PATCH 258/263] Fix nil error on debugger prompt (#1097) * Fix nil error on debugger prompt * Delete unnecessary test case * Use empty string on prompt when to_s returns nil. --- lib/irb.rb | 4 ++-- test/irb/test_debugger_integration.rb | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index fd0bfe35c..6d9c96c8f 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -612,10 +612,10 @@ def format_prompt(format, ltype, indent, line_no) # :nodoc: when "N" @context.irb_name when "m" - main_str = @context.safe_method_call_on_main(:to_s) rescue "!#{$!.class}" + main_str = "#{@context.safe_method_call_on_main(:to_s)}" rescue "!#{$!.class}" truncate_prompt_main(main_str) when "M" - main_str = @context.safe_method_call_on_main(:inspect) rescue "!#{$!.class}" + main_str = "#{@context.safe_method_call_on_main(:inspect)}" rescue "!#{$!.class}" truncate_prompt_main(main_str) when "l" ltype diff --git a/test/irb/test_debugger_integration.rb b/test/irb/test_debugger_integration.rb index 45ffb2a52..e125dbf85 100644 --- a/test/irb/test_debugger_integration.rb +++ b/test/irb/test_debugger_integration.rb @@ -51,6 +51,31 @@ def test_debug assert_match(/=> 2\| puts "hello"/, output) end + def test_debug_class_to_s_return_nil + write_ruby <<~'ruby' + class ToSReturnsNil + def to_s + nil + end + + def do_something + binding.irb + end + end + + ToSReturnsNil.new.do_something + ruby + + output = run_ruby_file do + type "debug" + type "next" + type "continue" + end + + assert_match(/irb\(\):001> debug/, output) + assert_match(/irb:rdbg\(\):002> next/, output) + end + def test_debug_command_only_runs_once write_ruby <<~'ruby' binding.irb From 29cdd266c4290075c9907b55a8a22ecea32ba1eb Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 30 May 2025 13:34:51 +0900 Subject: [PATCH 259/263] Enabled trusted publisher for rubygems.org --- .github/workflows/push_gem.yml | 46 ++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/push_gem.yml diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml new file mode 100644 index 000000000..388629480 --- /dev/null +++ b/.github/workflows/push_gem.yml @@ -0,0 +1,46 @@ +name: Publish gem to rubygems.org + +on: + push: + tags: + - 'v*' + +permissions: + contents: read + +jobs: + push: + if: github.repository == 'ruby/irb' + runs-on: ubuntu-latest + + environment: + name: rubygems.org + url: https://rubygems.org/gems/irb + + permissions: + contents: write + id-token: write + + steps: + - name: Harden Runner + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Set up Ruby + uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 + with: + bundler-cache: true + ruby-version: "ruby" + + - name: Publish to RubyGems + uses: rubygems/release-gem@a25424ba2ba8b387abc8ef40807c2c85b96cbe32 # v1.1.1 + + - name: Create GitHub release + run: | + tag_name="$(git describe --tags --abbrev=0)" + gh release create "${tag_name}" --verify-tag --generate-notes + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From f8fa4d448a144c1c1a90bb31e861c8e08ac7b39b Mon Sep 17 00:00:00 2001 From: Stevo-S <4288648+Stevo-S@users.noreply.github.com> Date: Sat, 14 Jun 2025 18:22:44 +0300 Subject: [PATCH 260/263] fix typos and wording on sigint section of docs (#1104) - fix typos where SIGINT is misspelt as SIGING - fix wording on the docs to reflect that the session will, by default, not be exited when Ctrl-C is input --- doc/Index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/Index.md b/doc/Index.md index 172fdebc9..f64e6047e 100644 --- a/doc/Index.md +++ b/doc/Index.md @@ -256,11 +256,11 @@ During the session, method `conf.ignore_eof?` returns the setting, and method `c ### SIGINT -By default, `IRB.conf[:IGNORE_SIGINT]` is `true`, which means that typing the interrupt character `Ctrl-C` causes the session to exit. +By default, `IRB.conf[:IGNORE_SIGINT]` is `true`, which means that typing the interrupt character `Ctrl-C` does not cause the session to exit. -You can reverse that behavior by adding `IRB.conf[:IGNORE_SIGING] = false` to the configuration file. +You can reverse that behavior by adding `IRB.conf[:IGNORE_SIGINT] = false` to the configuration file. -During the session, method `conf.ignore_siging?` returns the setting, and method `conf.ignore_sigint = *boolean*` sets it. +During the session, method `conf.ignore_sigint?` returns the setting, and method `conf.ignore_sigint = *boolean*` sets it. ### Automatic Completion From 85abf44ddb8f97d920256555739ae6c1a8a198a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 20:38:36 +0000 Subject: [PATCH 261/263] Bump step-security/harden-runner from 2.12.0 to 2.12.1 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.12.0 to 2.12.1. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/0634a2670c59f64b4a01f0f96f84700a4088b9f0...002fdce3c6a235733a90a27c80493a3241e56863) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.12.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 388629480..f9efbb949 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 with: egress-policy: audit From 27805078ce21a4a556be016000c791186b10dcf3 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Thu, 5 Jun 2025 02:24:46 +0900 Subject: [PATCH 262/263] Remove all internal frames from a backtrace In Ruby 3.5, the debug inspector API returns a raw backtrace, so irb/debug needs to remove not only `` but also all internal frames. --- lib/irb/debug.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/irb/debug.rb b/lib/irb/debug.rb index e429eaaff..0225a8a50 100644 --- a/lib/irb/debug.rb +++ b/lib/irb/debug.rb @@ -49,7 +49,7 @@ def setup(irb) def DEBUGGER__.capture_frames(*args) frames = capture_frames_without_irb(*args) frames.reject! do |frame| - frame.realpath&.start_with?(IRB_DIR) || frame.path == "" + frame.realpath&.start_with?(IRB_DIR) || frame.path.start_with?(" Date: Wed, 18 Jun 2025 12:48:35 +0900 Subject: [PATCH 263/263] Update the expected values of backtraces In ruby 3.5, `Kernel#caller_location` will not include `` frames. Instead, it will use its caller-side location for an internal frame. https://github.com/ruby/ruby/pull/13238 This change updates the expected values of backtraces in irb test to support the behavior change. --- test/irb/test_irb.rb | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index f20045082..30f3ad5a0 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -879,7 +879,14 @@ def bar assert_match(/irbtest-.*\.rb:2:in (`|'Object#)foo': error \(RuntimeError\)/, output) frame_traces = output.split("\n").map(&:strip).grep(/from /) - expected_traces = if RUBY_VERSION >= "3.3.0" + expected_traces = if RUBY_VERSION >= "3.5.0" + [ + /from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/, + /from .*\/irbtest-.*.rb\(irb\):1:in [`']
'/, + /from .*\/irbtest-.*.rb:9:in (`|'Binding#)irb'/, + /from .*\/irbtest-.*.rb:9:in [`']
'/ + ] + elsif RUBY_VERSION >= "3.3.0" [ /from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/, /from .*\/irbtest-.*.rb\(irb\):1:in [`']
'/, @@ -934,11 +941,20 @@ def bar assert_match(/irbtest-.*\.rb:2:in (`|'Object#)foo': error \(RuntimeError\)/, output) frame_traces = output.split("\n").map(&:strip).grep(/from /) - expected_traces = [ - /from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/, - /from .*\/irbtest-.*.rb\(irb\):1:in [`']
'/, - /from .*\/irbtest-.*.rb:9:in [`']
'/ - ] + expected_traces = if RUBY_VERSION >= "3.5.0" + [ + /from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/, + /from .*\/irbtest-.*.rb\(irb\):1:in [`']
'/, + /from .*\/irbtest-.*.rb:9:in (`|'Binding#)irb'/, + /from .*\/irbtest-.*.rb:9:in [`']
'/ + ] + else + [ + /from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/, + /from .*\/irbtest-.*.rb\(irb\):1:in [`']
'/, + /from .*\/irbtest-.*.rb:9:in [`']
'/ + ] + end expected_traces.reverse! if RUBY_VERSION < "3.0.0"