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.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/README.md +135 -14
- data/Rakefile +0 -7
- data/doc/irb/irb.rd.ja +1 -3
- data/irb.gemspec +2 -1
- data/lib/irb/cmd/chws.rb +2 -2
- data/lib/irb/cmd/debug.rb +36 -92
- data/lib/irb/cmd/edit.rb +6 -7
- data/lib/irb/cmd/help.rb +12 -46
- data/lib/irb/cmd/irb_info.rb +14 -18
- data/lib/irb/cmd/ls.rb +17 -7
- data/lib/irb/cmd/nop.rb +4 -8
- data/lib/irb/cmd/pushws.rb +3 -3
- data/lib/irb/cmd/show_cmds.rb +17 -3
- data/lib/irb/cmd/show_doc.rb +48 -0
- data/lib/irb/cmd/show_source.rb +4 -60
- data/lib/irb/cmd/subirb.rb +49 -6
- data/lib/irb/color.rb +2 -0
- data/lib/irb/completion.rb +2 -2
- data/lib/irb/context.rb +52 -21
- data/lib/irb/debug/ui.rb +104 -0
- data/lib/irb/debug.rb +115 -0
- data/lib/irb/ext/{history.rb → eval_history.rb} +4 -4
- data/lib/irb/ext/loader.rb +2 -0
- data/lib/irb/ext/tracer.rb +1 -1
- data/lib/irb/extend-command.rb +7 -5
- data/lib/irb/help.rb +1 -3
- data/lib/irb/{ext/save-history.rb → history.rb} +4 -55
- data/lib/irb/init.rb +4 -10
- data/lib/irb/input-method.rb +65 -131
- data/lib/irb/locale.rb +10 -43
- data/lib/irb/nesting_parser.rb +227 -0
- data/lib/irb/pager.rb +86 -0
- data/lib/irb/ruby-lex.rb +401 -747
- data/lib/irb/source_finder.rb +64 -0
- data/lib/irb/statement.rb +80 -0
- data/lib/irb/version.rb +2 -2
- data/lib/irb/workspace.rb +4 -0
- data/lib/irb.rb +222 -120
- metadata +28 -11
- data/lib/irb/cmd/fork.rb +0 -34
- data/lib/irb/lc/ja/encoding_aliases.rb +0 -13
- data/lib/irb/magic-file.rb +0 -38
- data/lib/irb/src_encoding.rb +0 -7
@@ -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
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
|
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
|
198
|
-
# :
|
199
|
-
# :
|
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
|
215
|
-
# # :
|
216
|
-
# # :
|
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
|
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.
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
564
|
-
|
565
|
-
|
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
|
-
|
584
|
-
|
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
|
-
|
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
|
-
|
601
|
-
|
602
|
-
|
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@
|
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
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
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
|