irb 1.6.4 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ruby-lex"
4
+
5
+ module IRB
6
+ class SourceFinder
7
+ Source = Struct.new(
8
+ :file, # @param [String] - file name
9
+ :first_line, # @param [String] - first line
10
+ :last_line, # @param [String] - last line
11
+ keyword_init: true,
12
+ )
13
+ private_constant :Source
14
+
15
+ def initialize(irb_context)
16
+ @irb_context = irb_context
17
+ end
18
+
19
+ def find_source(signature)
20
+ context_binding = @irb_context.workspace.binding
21
+ case signature
22
+ when /\A[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name
23
+ eval(signature, context_binding) # trigger autoload
24
+ base = context_binding.receiver.yield_self { |r| r.is_a?(Module) ? r : Object }
25
+ file, line = base.const_source_location(signature)
26
+ when /\A(?<owner>[A-Z]\w*(::[A-Z]\w*)*)#(?<method>[^ :.]+)\z/ # Class#method
27
+ owner = eval(Regexp.last_match[:owner], context_binding)
28
+ method = Regexp.last_match[:method]
29
+ if owner.respond_to?(:instance_method)
30
+ methods = owner.instance_methods + owner.private_instance_methods
31
+ file, line = owner.instance_method(method).source_location if methods.include?(method.to_sym)
32
+ end
33
+ when /\A((?<receiver>.+)(\.|::))?(?<method>[^ :.]+)\z/ # method, receiver.method, receiver::method
34
+ receiver = eval(Regexp.last_match[:receiver] || 'self', context_binding)
35
+ method = Regexp.last_match[:method]
36
+ file, line = receiver.method(method).source_location if receiver.respond_to?(method, true)
37
+ end
38
+ if file && line && File.exist?(file)
39
+ Source.new(file: file, first_line: line, last_line: find_end(file, line))
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def find_end(file, first_line)
46
+ lex = RubyLex.new(@irb_context)
47
+ lines = File.read(file).lines[(first_line - 1)..-1]
48
+ tokens = RubyLex.ripper_lex_without_warning(lines.join)
49
+ prev_tokens = []
50
+
51
+ # chunk with line number
52
+ tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk|
53
+ code = lines[0..lnum].join
54
+ prev_tokens.concat chunk
55
+ continue = lex.should_continue?(prev_tokens)
56
+ syntax = lex.check_code_syntax(code)
57
+ if !continue && syntax == :valid
58
+ return first_line + lnum
59
+ end
60
+ end
61
+ first_line
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IRB
4
+ class Statement
5
+ attr_reader :code
6
+
7
+ def is_assignment?
8
+ raise NotImplementedError
9
+ end
10
+
11
+ def suppresses_echo?
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def should_be_handled_by_debugger?
16
+ raise NotImplementedError
17
+ end
18
+
19
+ def evaluable_code
20
+ raise NotImplementedError
21
+ end
22
+
23
+ class Expression < Statement
24
+ def initialize(code, is_assignment)
25
+ @code = code
26
+ @is_assignment = is_assignment
27
+ end
28
+
29
+ def suppresses_echo?
30
+ @code.match?(/;\s*\z/)
31
+ end
32
+
33
+ def should_be_handled_by_debugger?
34
+ true
35
+ end
36
+
37
+ def is_assignment?
38
+ @is_assignment
39
+ end
40
+
41
+ def evaluable_code
42
+ @code
43
+ end
44
+ end
45
+
46
+ class Command < Statement
47
+ def initialize(code, command, arg, command_class)
48
+ @code = code
49
+ @command = command
50
+ @arg = arg
51
+ @command_class = command_class
52
+ end
53
+
54
+ def is_assignment?
55
+ false
56
+ end
57
+
58
+ def suppresses_echo?
59
+ false
60
+ end
61
+
62
+ def should_be_handled_by_debugger?
63
+ require_relative 'cmd/help'
64
+ require_relative 'cmd/debug'
65
+ IRB::ExtendCommand::DebugCommand > @command_class || IRB::ExtendCommand::Help == @command_class
66
+ end
67
+
68
+ def evaluable_code
69
+ # Hook command-specific transformation to return valid Ruby code
70
+ if @command_class.respond_to?(:transform_args)
71
+ arg = @command_class.transform_args(@arg)
72
+ else
73
+ arg = @arg
74
+ end
75
+
76
+ [@command, arg].compact.join(' ')
77
+ end
78
+ end
79
+ end
80
+ end
data/lib/irb/version.rb CHANGED
@@ -5,7 +5,7 @@
5
5
  #
6
6
 
7
7
  module IRB # :nodoc:
8
- VERSION = "1.6.4"
8
+ VERSION = "1.8.0"
9
9
  @RELEASE_VERSION = VERSION
10
- @LAST_UPDATE_DATE = "2023-04-07"
10
+ @LAST_UPDATE_DATE = "2023-08-30"
11
11
  end
data/lib/irb/workspace.rb CHANGED
@@ -108,6 +108,10 @@ EOF
108
108
  # <code>IRB.conf[:__MAIN__]</code>
109
109
  attr_reader :main
110
110
 
111
+ def load_commands_to_main
112
+ main.extend ExtendCommandBundle
113
+ end
114
+
111
115
  # Evaluate the given +statements+ within the context of this workspace.
112
116
  def evaluate(statements, file = __FILE__, line = __LINE__)
113
117
  eval(statements, @binding, file, line)
data/lib/irb.rb CHANGED
@@ -12,12 +12,14 @@ require_relative "irb/context"
12
12
  require_relative "irb/extend-command"
13
13
 
14
14
  require_relative "irb/ruby-lex"
15
+ require_relative "irb/statement"
15
16
  require_relative "irb/input-method"
16
17
  require_relative "irb/locale"
17
18
  require_relative "irb/color"
18
19
 
19
20
  require_relative "irb/version"
20
21
  require_relative "irb/easter-egg"
22
+ require_relative "irb/debug"
21
23
 
22
24
  # IRB stands for "interactive Ruby" and is a tool to interactively execute Ruby
23
25
  # expressions read from the standard input.
@@ -154,7 +156,7 @@ require_relative "irb/easter-egg"
154
156
  #
155
157
  # IRB.conf[:EVAL_HISTORY] = <number>
156
158
  #
157
- # See IRB::Context#eval_history= and History class. The history of command
159
+ # See IRB::Context#eval_history= and EvalHistory class. The history of command
158
160
  # results is not permanently saved in any file.
159
161
  #
160
162
  # == Customizing the IRB Prompt
@@ -194,10 +196,9 @@ require_relative "irb/easter-egg"
194
196
  # For instance, the default prompt mode is defined as follows:
195
197
  #
196
198
  # IRB.conf[:PROMPT_MODE][:DEFAULT] = {
197
- # :PROMPT_I => "%N(%m):%03n:%i> ",
198
- # :PROMPT_N => "%N(%m):%03n:%i> ",
199
- # :PROMPT_S => "%N(%m):%03n:%i%l ",
200
- # :PROMPT_C => "%N(%m):%03n:%i* ",
199
+ # :PROMPT_I => "%N(%m):%03n> ",
200
+ # :PROMPT_S => "%N(%m):%03n%l ",
201
+ # :PROMPT_C => "%N(%m):%03n* ",
201
202
  # :RETURN => "%s\n" # used to printf
202
203
  # }
203
204
  #
@@ -205,35 +206,30 @@ require_relative "irb/easter-egg"
205
206
  #
206
207
  # # :NULL:
207
208
  # # :PROMPT_I:
208
- # # :PROMPT_N:
209
209
  # # :PROMPT_S:
210
210
  # # :PROMPT_C:
211
211
  # # :RETURN: |
212
212
  # # %s
213
213
  # # :DEFAULT:
214
- # # :PROMPT_I: ! '%N(%m):%03n:%i> '
215
- # # :PROMPT_N: ! '%N(%m):%03n:%i> '
216
- # # :PROMPT_S: ! '%N(%m):%03n:%i%l '
217
- # # :PROMPT_C: ! '%N(%m):%03n:%i* '
214
+ # # :PROMPT_I: ! '%N(%m):%03n> '
215
+ # # :PROMPT_S: ! '%N(%m):%03n%l '
216
+ # # :PROMPT_C: ! '%N(%m):%03n* '
218
217
  # # :RETURN: |
219
218
  # # => %s
220
219
  # # :CLASSIC:
221
220
  # # :PROMPT_I: ! '%N(%m):%03n:%i> '
222
- # # :PROMPT_N: ! '%N(%m):%03n:%i> '
223
221
  # # :PROMPT_S: ! '%N(%m):%03n:%i%l '
224
222
  # # :PROMPT_C: ! '%N(%m):%03n:%i* '
225
223
  # # :RETURN: |
226
224
  # # %s
227
225
  # # :SIMPLE:
228
226
  # # :PROMPT_I: ! '>> '
229
- # # :PROMPT_N: ! '>> '
230
227
  # # :PROMPT_S:
231
228
  # # :PROMPT_C: ! '?> '
232
229
  # # :RETURN: |
233
230
  # # => %s
234
231
  # # :INF_RUBY:
235
- # # :PROMPT_I: ! '%N(%m):%03n:%i> '
236
- # # :PROMPT_N:
232
+ # # :PROMPT_I: ! '%N(%m):%03n> '
237
233
  # # :PROMPT_S:
238
234
  # # :PROMPT_C:
239
235
  # # :RETURN: |
@@ -241,7 +237,6 @@ require_relative "irb/easter-egg"
241
237
  # # :AUTO_INDENT: true
242
238
  # # :XMP:
243
239
  # # :PROMPT_I:
244
- # # :PROMPT_N:
245
240
  # # :PROMPT_S:
246
241
  # # :PROMPT_C:
247
242
  # # :RETURN: |2
@@ -373,8 +368,6 @@ module IRB
373
368
  class Abort < Exception;end
374
369
 
375
370
  @CONF = {}
376
-
377
-
378
371
  # Displays current configuration.
379
372
  #
380
373
  # Modifying the configuration is achieved by sending a message to IRB.conf.
@@ -429,30 +422,6 @@ module IRB
429
422
  end
430
423
 
431
424
  class Irb
432
- ASSIGNMENT_NODE_TYPES = [
433
- # Local, instance, global, class, constant, instance, and index assignment:
434
- # "foo = bar",
435
- # "@foo = bar",
436
- # "$foo = bar",
437
- # "@@foo = bar",
438
- # "::Foo = bar",
439
- # "a::Foo = bar",
440
- # "Foo = bar"
441
- # "foo.bar = 1"
442
- # "foo[1] = bar"
443
- :assign,
444
-
445
- # Operation assignment:
446
- # "foo += bar"
447
- # "foo -= bar"
448
- # "foo ||= bar"
449
- # "foo &&= bar"
450
- :opassign,
451
-
452
- # Multiple assignment:
453
- # "foo, bar = 1, 2
454
- :massign,
455
- ]
456
425
  # Note: instance and index assignment expressions could also be written like:
457
426
  # "foo.bar=(1)" and "foo.[]=(1, bar)", when expressed that way, the former
458
427
  # be parsed as :assign and echo will be suppressed, but the latter is
@@ -465,14 +434,14 @@ module IRB
465
434
  # Creates a new irb session
466
435
  def initialize(workspace = nil, input_method = nil)
467
436
  @context = Context.new(self, workspace, input_method)
468
- @context.main.extend ExtendCommandBundle
437
+ @context.workspace.load_commands_to_main
469
438
  @signal_status = :IN_IRB
470
439
  @scanner = RubyLex.new(@context)
471
440
  end
472
441
 
473
- # A hook point for `debug` command's TracePoint after :IRB_EXIT as well as its clean-up
442
+ # A hook point for `debug` command's breakpoint after :IRB_EXIT as well as its clean-up
474
443
  def debug_break
475
- # it means the debug command is executed
444
+ # it means the debug integration has been activated
476
445
  if defined?(DEBUGGER__) && DEBUGGER__.respond_to?(:capture_frames_without_irb)
477
446
  # after leaving this initial breakpoint, revert the capture_frames patch
478
447
  DEBUGGER__.singleton_class.send(:alias_method, :capture_frames, :capture_frames_without_irb)
@@ -481,10 +450,49 @@ module IRB
481
450
  end
482
451
  end
483
452
 
453
+ def debug_readline(binding)
454
+ workspace = IRB::WorkSpace.new(binding)
455
+ context.workspace = workspace
456
+ context.workspace.load_commands_to_main
457
+ scanner.increase_line_no(1)
458
+
459
+ # When users run:
460
+ # 1. Debugging commands, like `step 2`
461
+ # 2. Any input that's not irb-command, like `foo = 123`
462
+ #
463
+ # Irb#eval_input will simply return the input, and we need to pass it to the debugger.
464
+ input = if IRB.conf[:SAVE_HISTORY] && context.io.support_history_saving?
465
+ # Previous IRB session's history has been saved when `Irb#run` is exited
466
+ # We need to make sure the saved history is not saved again by reseting the counter
467
+ context.io.reset_history_counter
468
+
469
+ begin
470
+ eval_input
471
+ ensure
472
+ context.io.save_history
473
+ end
474
+ else
475
+ eval_input
476
+ end
477
+
478
+ if input&.include?("\n")
479
+ scanner.increase_line_no(input.count("\n") - 1)
480
+ end
481
+
482
+ input
483
+ end
484
+
484
485
  def run(conf = IRB.conf)
486
+ in_nested_session = !!conf[:MAIN_CONTEXT]
485
487
  conf[:IRB_RC].call(context) if conf[:IRB_RC]
486
488
  conf[:MAIN_CONTEXT] = context
487
489
 
490
+ save_history = !in_nested_session && conf[:SAVE_HISTORY] && context.io.support_history_saving?
491
+
492
+ if save_history
493
+ context.io.load_history
494
+ end
495
+
488
496
  prev_trap = trap("SIGINT") do
489
497
  signal_handle
490
498
  end
@@ -496,6 +504,7 @@ module IRB
496
504
  ensure
497
505
  trap("SIGINT", prev_trap)
498
506
  conf[:AT_EXIT].each{|hook| hook.call}
507
+ context.io.save_history if save_history
499
508
  end
500
509
  end
501
510
 
@@ -506,16 +515,12 @@ module IRB
506
515
 
507
516
  # Evaluates input for this session.
508
517
  def eval_input
509
- exc = nil
510
-
511
518
  @scanner.set_prompt do
512
519
  |ltype, indent, continue, line_no|
513
520
  if ltype
514
521
  f = @context.prompt_s
515
522
  elsif continue
516
523
  f = @context.prompt_c
517
- elsif indent > 0
518
- f = @context.prompt_n
519
524
  else
520
525
  f = @context.prompt_i
521
526
  end
@@ -530,58 +535,26 @@ module IRB
530
535
  prompt_i = @context.prompt_i.nil? ? "" : @context.prompt_i
531
536
  ind = prompt(prompt_i, ltype, indent, line_no)[/.*\z/].size +
532
537
  indent * 2 - p.size
533
- ind += 2 if continue
534
538
  @context.io.prompt = p + " " * ind if ind > 0
535
539
  end
536
540
  end
537
541
  @context.io.prompt
538
542
  end
539
543
 
540
- @scanner.set_input(@context.io) do
541
- signal_status(:IN_INPUT) do
542
- if l = @context.io.gets
543
- print l if @context.verbose?
544
- else
545
- if @context.ignore_eof? and @context.io.readable_after_eof?
546
- l = "\n"
547
- if @context.verbose?
548
- printf "Use \"exit\" to leave %s\n", @context.ap_name
549
- end
550
- else
551
- print "\n" if @context.prompting?
552
- end
553
- end
554
- l
555
- end
556
- end
544
+ configure_io
557
545
 
558
- @scanner.set_auto_indent
559
-
560
- @scanner.each_top_level_statement do |line, line_no|
546
+ each_top_level_statement do |statement, line_no|
561
547
  signal_status(:IN_EVAL) do
562
548
  begin
563
- if IRB.conf[:MEASURE] && IRB.conf[:MEASURE_CALLBACKS].empty?
564
- IRB.set_measure_callback
565
- end
566
- # Assignment expression check should be done before @context.evaluate to handle code like `a /2#/ if false; a = 1`
567
- is_assignment = assignment_expression?(line)
568
- if IRB.conf[:MEASURE] && !IRB.conf[:MEASURE_CALLBACKS].empty?
569
- result = nil
570
- last_proc = proc{ result = @context.evaluate(line, line_no, exception: exc) }
571
- IRB.conf[:MEASURE_CALLBACKS].inject(last_proc) { |chain, item|
572
- _name, callback, arg = item
573
- proc {
574
- callback.(@context, line, line_no, arg, exception: exc) do
575
- chain.call
576
- end
577
- }
578
- }.call
579
- @context.set_last_value(result)
580
- else
581
- @context.evaluate(line, line_no, exception: exc)
549
+ # If the integration with debugger is activated, we return certain input if it should be dealt with by debugger
550
+ if @context.with_debugger && statement.should_be_handled_by_debugger?
551
+ return statement.code
582
552
  end
583
- if @context.echo?
584
- if is_assignment
553
+
554
+ @context.evaluate(statement.evaluable_code, line_no)
555
+
556
+ if @context.echo? && !statement.suppresses_echo?
557
+ if statement.is_assignment?
585
558
  if @context.echo_on_assignment?
586
559
  output_value(@context.echo_on_assignment? == :truncate)
587
560
  end
@@ -589,17 +562,144 @@ module IRB
589
562
  output_value
590
563
  end
591
564
  end
592
- rescue Interrupt => exc
593
565
  rescue SystemExit, SignalException
594
566
  raise
595
- rescue Exception => exc
567
+ rescue Interrupt, Exception => exc
568
+ handle_exception(exc)
569
+ @context.workspace.local_variable_set(:_, exc)
570
+ end
571
+ end
572
+ end
573
+ end
574
+
575
+ def read_input
576
+ signal_status(:IN_INPUT) do
577
+ if l = @context.io.gets
578
+ print l if @context.verbose?
579
+ else
580
+ if @context.ignore_eof? and @context.io.readable_after_eof?
581
+ l = "\n"
582
+ if @context.verbose?
583
+ printf "Use \"exit\" to leave %s\n", @context.ap_name
584
+ end
585
+ else
586
+ print "\n" if @context.prompting?
587
+ end
588
+ end
589
+ l
590
+ end
591
+ end
592
+
593
+ def readmultiline
594
+ @scanner.save_prompt_to_context_io([], false, 0)
595
+
596
+ # multiline
597
+ return read_input if @context.io.respond_to?(:check_termination)
598
+
599
+ # nomultiline
600
+ code = ''
601
+ line_offset = 0
602
+ loop do
603
+ line = read_input
604
+ unless line
605
+ return code.empty? ? nil : code
606
+ end
607
+
608
+ code << line
609
+
610
+ # Accept any single-line input for symbol aliases or commands that transform args
611
+ return code if single_line_command?(code)
612
+
613
+ tokens, opens, terminated = @scanner.check_code_state(code)
614
+ return code if terminated
615
+
616
+ line_offset += 1
617
+ continue = @scanner.should_continue?(tokens)
618
+ @scanner.save_prompt_to_context_io(opens, continue, line_offset)
619
+ end
620
+ end
621
+
622
+ def each_top_level_statement
623
+ loop do
624
+ code = readmultiline
625
+ break unless code
626
+
627
+ if code != "\n"
628
+ yield build_statement(code), @scanner.line_no
629
+ end
630
+ @scanner.increase_line_no(code.count("\n"))
631
+ rescue RubyLex::TerminateLineInput
632
+ end
633
+ end
634
+
635
+ def build_statement(code)
636
+ code.force_encoding(@context.io.encoding)
637
+ command_or_alias, arg = code.split(/\s/, 2)
638
+ # Transform a non-identifier alias (@, $) or keywords (next, break)
639
+ command_name = @context.command_aliases[command_or_alias.to_sym]
640
+ command = command_name || command_or_alias
641
+ command_class = ExtendCommandBundle.load_command(command)
642
+
643
+ if command_class
644
+ Statement::Command.new(code, command, arg, command_class)
645
+ else
646
+ Statement::Expression.new(code, @scanner.assignment_expression?(code))
647
+ end
648
+ end
649
+
650
+ def single_line_command?(code)
651
+ command = code.split(/\s/, 2).first
652
+ @context.symbol_alias?(command) || @context.transform_args?(command)
653
+ end
654
+
655
+ def configure_io
656
+ if @context.io.respond_to?(:check_termination)
657
+ @context.io.check_termination do |code|
658
+ if Reline::IOGate.in_pasting?
659
+ rest = @scanner.check_termination_in_prev_line(code)
660
+ if rest
661
+ Reline.delete_text
662
+ rest.bytes.reverse_each do |c|
663
+ Reline.ungetc(c)
664
+ end
665
+ true
666
+ else
667
+ false
668
+ end
596
669
  else
597
- exc = nil
598
- next
670
+ # Accept any single-line input for symbol aliases or commands that transform args
671
+ next true if single_line_command?(code)
672
+
673
+ _tokens, _opens, terminated = @scanner.check_code_state(code)
674
+ terminated
675
+ end
676
+ end
677
+ end
678
+ if @context.io.respond_to?(:dynamic_prompt)
679
+ @context.io.dynamic_prompt do |lines|
680
+ lines << '' if lines.empty?
681
+ tokens = RubyLex.ripper_lex_without_warning(lines.map{ |l| l + "\n" }.join, context: @context)
682
+ line_results = IRB::NestingParser.parse_by_line(tokens)
683
+ tokens_until_line = []
684
+ line_results.map.with_index do |(line_tokens, _prev_opens, next_opens, _min_depth), line_num_offset|
685
+ line_tokens.each do |token, _s|
686
+ # Avoid appending duplicated token. Tokens that include "\n" like multiline tstring_content can exist in multiple lines.
687
+ tokens_until_line << token if token != tokens_until_line.last
688
+ end
689
+ continue = @scanner.should_continue?(tokens_until_line)
690
+ @scanner.prompt(next_opens, continue, line_num_offset)
599
691
  end
600
- handle_exception(exc)
601
- @context.workspace.local_variable_set(:_, exc)
602
- exc = nil
692
+ end
693
+ end
694
+
695
+ if @context.io.respond_to?(:auto_indent) and @context.auto_indent_mode
696
+ @context.io.auto_indent do |lines, line_index, byte_pointer, is_newline|
697
+ next nil if lines == [nil] # Workaround for exit IRB with CTRL+d
698
+ next nil if !is_newline && lines[line_index]&.byteslice(0, byte_pointer)&.match?(/\A\s*\z/)
699
+
700
+ code = lines[0..line_index].map { |l| "#{l}\n" }.join
701
+ tokens = RubyLex.ripper_lex_without_warning(code, context: @context)
702
+ @scanner.process_indent_level(tokens, lines, line_index, is_newline)
603
703
  end
604
704
  end
605
705
  end
@@ -873,24 +973,6 @@ module IRB
873
973
  end
874
974
  format("#<%s: %s>", self.class, ary.join(", "))
875
975
  end
876
-
877
- def assignment_expression?(line)
878
- # Try to parse the line and check if the last of possibly multiple
879
- # expressions is an assignment type.
880
-
881
- # If the expression is invalid, Ripper.sexp should return nil which will
882
- # result in false being returned. Any valid expression should return an
883
- # s-expression where the second element of the top level array is an
884
- # array of parsed expressions. The first element of each expression is the
885
- # expression's type.
886
- verbose, $VERBOSE = $VERBOSE, nil
887
- code = "#{RubyLex.generate_local_variables_assign_code(@context.local_variables) || 'nil;'}\n#{line}"
888
- # Get the last node_type of the line. drop(1) is to ignore the local_variables_assign_code part.
889
- node_type = Ripper.sexp(code)&.dig(1)&.drop(1)&.dig(-1, 0)
890
- ASSIGNMENT_NODE_TYPES.include?(node_type)
891
- ensure
892
- $VERBOSE = verbose
893
- end
894
976
  end
895
977
 
896
978
  def @CONF.inspect
@@ -973,14 +1055,34 @@ class Binding
973
1055
  # Cooked potato: true
974
1056
  #
975
1057
  #
976
- # See IRB@IRB+Usage for more information.
1058
+ # See IRB@Usage for more information.
977
1059
  def irb(show_code: true)
1060
+ # Setup IRB with the current file's path and no command line arguments
978
1061
  IRB.setup(source_location[0], argv: [])
1062
+ # Create a new workspace using the current binding
979
1063
  workspace = IRB::WorkSpace.new(self)
1064
+ # Print the code around the binding if show_code is true
980
1065
  STDOUT.print(workspace.code_around_binding) if show_code
981
- binding_irb = IRB::Irb.new(workspace)
982
- binding_irb.context.irb_path = File.expand_path(source_location[0])
983
- binding_irb.run(IRB.conf)
984
- binding_irb.debug_break
1066
+ # Get the original IRB instance
1067
+ debugger_irb = IRB.instance_variable_get(:@debugger_irb)
1068
+
1069
+ irb_path = File.expand_path(source_location[0])
1070
+
1071
+ if debugger_irb
1072
+ # If we're already in a debugger session, set the workspace and irb_path for the original IRB instance
1073
+ debugger_irb.context.workspace = workspace
1074
+ debugger_irb.context.irb_path = irb_path
1075
+ # If we've started a debugger session and hit another binding.irb, we don't want to start an IRB session
1076
+ # instead, we want to resume the irb:rdbg session.
1077
+ IRB::Debug.setup(debugger_irb)
1078
+ IRB::Debug.insert_debug_break
1079
+ debugger_irb.debug_break
1080
+ else
1081
+ # If we're not in a debugger session, create a new IRB instance with the current workspace
1082
+ binding_irb = IRB::Irb.new(workspace)
1083
+ binding_irb.context.irb_path = irb_path
1084
+ binding_irb.run(IRB.conf)
1085
+ binding_irb.debug_break
1086
+ end
985
1087
  end
986
1088
  end