irb 1.1.1 → 1.2.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.
@@ -16,11 +16,68 @@ Gem::Specification.new do |spec|
16
16
  spec.homepage = "https://github.com/ruby/irb"
17
17
  spec.license = "BSD-2-Clause"
18
18
 
19
- spec.files = [".gitignore", ".travis.yml", "Gemfile", "LICENSE.txt", "README.md", "Rakefile", "bin/console", "bin/setup", "exe/irb", "irb.gemspec", "lib/irb.rb", "lib/irb/cmd/chws.rb", "lib/irb/cmd/fork.rb", "lib/irb/cmd/help.rb", "lib/irb/cmd/load.rb", "lib/irb/cmd/nop.rb", "lib/irb/cmd/pushws.rb", "lib/irb/cmd/subirb.rb", "lib/irb/completion.rb", "lib/irb/context.rb", "lib/irb/ext/change-ws.rb", "lib/irb/ext/history.rb", "lib/irb/ext/loader.rb", "lib/irb/ext/multi-irb.rb", "lib/irb/ext/save-history.rb", "lib/irb/ext/tracer.rb", "lib/irb/ext/use-loader.rb", "lib/irb/ext/workspaces.rb", "lib/irb/extend-command.rb", "lib/irb/frame.rb", "lib/irb/help.rb", "lib/irb/init.rb", "lib/irb/input-method.rb", "lib/irb/inspector.rb", "lib/irb/lc/.document", "lib/irb/lc/error.rb", "lib/irb/lc/help-message", "lib/irb/lc/ja/encoding_aliases.rb", "lib/irb/lc/ja/error.rb", "lib/irb/lc/ja/help-message", "lib/irb/locale.rb", "lib/irb/magic-file.rb", "lib/irb/notifier.rb", "lib/irb/output-method.rb", "lib/irb/ruby-lex.rb", "lib/irb/ruby-token.rb", "lib/irb/slex.rb", "lib/irb/src_encoding.rb", "lib/irb/version.rb", "lib/irb/workspace.rb", "lib/irb/ws-for-case-2.rb", "lib/irb/xmp.rb"]
19
+ spec.files = [
20
+ "Gemfile",
21
+ "LICENSE.txt",
22
+ "README.md",
23
+ "Rakefile",
24
+ "bin/console",
25
+ "bin/setup",
26
+ "doc/irb/irb-tools.rd.ja",
27
+ "doc/irb/irb.rd.ja",
28
+ "exe/irb",
29
+ "irb.gemspec",
30
+ "lib/irb.rb",
31
+ "lib/irb/cmd/chws.rb",
32
+ "lib/irb/cmd/fork.rb",
33
+ "lib/irb/cmd/help.rb",
34
+ "lib/irb/cmd/load.rb",
35
+ "lib/irb/cmd/nop.rb",
36
+ "lib/irb/cmd/pushws.rb",
37
+ "lib/irb/cmd/subirb.rb",
38
+ "lib/irb/color.rb",
39
+ "lib/irb/completion.rb",
40
+ "lib/irb/context.rb",
41
+ "lib/irb/ext/change-ws.rb",
42
+ "lib/irb/ext/history.rb",
43
+ "lib/irb/ext/loader.rb",
44
+ "lib/irb/ext/multi-irb.rb",
45
+ "lib/irb/ext/save-history.rb",
46
+ "lib/irb/ext/tracer.rb",
47
+ "lib/irb/ext/use-loader.rb",
48
+ "lib/irb/ext/workspaces.rb",
49
+ "lib/irb/extend-command.rb",
50
+ "lib/irb/frame.rb",
51
+ "lib/irb/help.rb",
52
+ "lib/irb/init.rb",
53
+ "lib/irb/input-method.rb",
54
+ "lib/irb/inspector.rb",
55
+ "lib/irb/lc/.document",
56
+ "lib/irb/lc/error.rb",
57
+ "lib/irb/lc/help-message",
58
+ "lib/irb/lc/ja/encoding_aliases.rb",
59
+ "lib/irb/lc/ja/error.rb",
60
+ "lib/irb/lc/ja/help-message",
61
+ "lib/irb/locale.rb",
62
+ "lib/irb/magic-file.rb",
63
+ "lib/irb/notifier.rb",
64
+ "lib/irb/output-method.rb",
65
+ "lib/irb/ruby-lex.rb",
66
+ "lib/irb/ruby_logo.aa",
67
+ "lib/irb/src_encoding.rb",
68
+ "lib/irb/version.rb",
69
+ "lib/irb/workspace.rb",
70
+ "lib/irb/ws-for-case-2.rb",
71
+ "lib/irb/xmp.rb",
72
+ "man/irb.1",
73
+ ]
20
74
  spec.bindir = "exe"
21
75
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
76
  spec.require_paths = ["lib"]
23
77
 
78
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.5")
79
+
80
+ spec.add_dependency "reline", ">= 0.0.1"
24
81
  spec.add_development_dependency "bundler"
25
82
  spec.add_development_dependency "rake"
26
83
  end
data/lib/irb.rb CHANGED
@@ -9,7 +9,7 @@
9
9
  #
10
10
  #
11
11
  #
12
- require "e2mmap"
12
+ require "ripper"
13
13
 
14
14
  require "irb/init"
15
15
  require "irb/context"
@@ -18,6 +18,7 @@ require "irb/extend-command"
18
18
  require "irb/ruby-lex"
19
19
  require "irb/input-method"
20
20
  require "irb/locale"
21
+ require "irb/color"
21
22
 
22
23
  require "irb/version"
23
24
 
@@ -43,8 +44,8 @@ require "irb/version"
43
44
  # irb(main):006:1> end
44
45
  # #=> nil
45
46
  #
46
- # The Readline extension module can be used with irb. Use of Readline is
47
- # default if it's installed.
47
+ # The singleline editor module or multiline editor module can be used with irb.
48
+ # Use of multiline editor is default if it's installed.
48
49
  #
49
50
  # == Command line options
50
51
  #
@@ -59,21 +60,24 @@ require "irb/version"
59
60
  # -W[level=2] Same as `ruby -W`
60
61
  # --inspect Use `inspect' for output (default except for bc mode)
61
62
  # --noinspect Don't use inspect for output
62
- # --readline Use Readline extension module
63
- # --noreadline Don't use Readline extension module
63
+ # --multiline Use multiline editor module
64
+ # --nomultiline Don't use multiline editor module
65
+ # --singleline Use singleline editor module
66
+ # --nosingleline Don't use singleline editor module
67
+ # --colorize Use colorization
68
+ # --nocolorize Don't use colorization
64
69
  # --prompt prompt-mode
65
70
  # --prompt-mode prompt-mode
66
71
  # Switch prompt mode. Pre-defined prompt modes are
67
72
  # `default', `simple', `xmp' and `inf-ruby'
68
73
  # --inf-ruby-mode Use prompt appropriate for inf-ruby-mode on emacs.
69
- # Suppresses --readline.
74
+ # Suppresses --multiline and --singleline.
70
75
  # --simple-prompt Simple prompt mode
71
76
  # --noprompt No prompt mode
72
77
  # --tracer Display trace for each execution of commands.
73
78
  # --back-trace-limit n
74
79
  # Display backtrace top n and tail n. The default
75
80
  # value is 16.
76
- # --irb_debug n Set internal debug level to n (not for popular use)
77
81
  # -v, --version Print the version of irb
78
82
  #
79
83
  # == Configuration
@@ -95,19 +99,20 @@ require "irb/version"
95
99
  # IRB.conf[:IRB_RC] = nil
96
100
  # IRB.conf[:BACK_TRACE_LIMIT]=16
97
101
  # IRB.conf[:USE_LOADER] = false
98
- # IRB.conf[:USE_READLINE] = nil
102
+ # IRB.conf[:USE_MULTILINE] = nil
103
+ # IRB.conf[:USE_SINGLELINE] = nil
104
+ # IRB.conf[:USE_COLORIZE] = true
99
105
  # IRB.conf[:USE_TRACER] = false
100
106
  # IRB.conf[:IGNORE_SIGINT] = true
101
107
  # IRB.conf[:IGNORE_EOF] = false
102
108
  # IRB.conf[:PROMPT_MODE] = :DEFAULT
103
109
  # IRB.conf[:PROMPT] = {...}
104
- # IRB.conf[:DEBUG_LEVEL]=0
105
110
  #
106
111
  # === Auto indentation
107
112
  #
108
- # To enable auto-indent mode in irb, add the following to your +.irbrc+:
113
+ # To disable auto-indent mode in irb, add the following to your +.irbrc+:
109
114
  #
110
- # IRB.conf[:AUTO_INDENT] = true
115
+ # IRB.conf[:AUTO_INDENT] = false
111
116
  #
112
117
  # === Autocompletion
113
118
  #
@@ -117,16 +122,23 @@ require "irb/version"
117
122
  #
118
123
  # === History
119
124
  #
120
- # By default, irb disables history and will not store any commands you used.
125
+ # By default, irb will store the last 1000 commands you used in
126
+ # <code>IRB.conf[:HISTORY_FILE]</code> (<code>~/.irb_history</code> by default).
121
127
  #
122
- # If you want to enable history, add the following to your +.irbrc+:
128
+ # If you want to disable history, add the following to your +.irbrc+:
123
129
  #
124
- # IRB.conf[:SAVE_HISTORY] = 1000
125
- #
126
- # This will now store the last 1000 commands in <code>~/.irb_history</code>.
130
+ # IRB.conf[:SAVE_HISTORY] = nil
127
131
  #
128
132
  # See IRB::Context#save_history= for more information.
129
133
  #
134
+ # The history of _resuls_ of commands evaluated is not stored by default,
135
+ # but can be turned on to be stored with this +.irbrc+ setting:
136
+ #
137
+ # IRB.conf[:EVAL_HISTORY] = <number>
138
+ #
139
+ # See IRB::Context#eval_history= and History class. The history of command
140
+ # results is not permanently saved in any file.
141
+ #
130
142
  # == Customizing the IRB Prompt
131
143
  #
132
144
  # In order to customize the prompt, you can change the following Hash:
@@ -136,7 +148,7 @@ require "irb/version"
136
148
  # This example can be used in your +.irbrc+
137
149
  #
138
150
  # IRB.conf[:PROMPT][:MY_PROMPT] = { # name of prompt mode
139
- # :AUTO_INDENT => true, # enables auto-indent mode
151
+ # :AUTO_INDENT => false, # disables auto-indent mode
140
152
  # :PROMPT_I => ">> ", # simple prompt
141
153
  # :PROMPT_S => nil, # prompt for continuated strings
142
154
  # :PROMPT_C => nil, # prompt for continuated statement
@@ -165,6 +177,7 @@ require "irb/version"
165
177
  #
166
178
  # IRB.conf[:PROMPT_MODE][:DEFAULT] = {
167
179
  # :PROMPT_I => "%N(%m):%03n:%i> ",
180
+ # :PROMPT_N => "%N(%m):%03n:%i> ",
168
181
  # :PROMPT_S => "%N(%m):%03n:%i%l ",
169
182
  # :PROMPT_C => "%N(%m):%03n:%i* ",
170
183
  # :RETURN => "%s\n" # used to printf
@@ -268,7 +281,9 @@ require "irb/version"
268
281
  # <code>_</code>::
269
282
  # The value command executed, as a local variable
270
283
  # <code>__</code>::
271
- # The history of evaluated commands
284
+ # The history of evaluated commands. Available only if
285
+ # <code>IRB.conf[:EVAL_HISTORY]</code> is not +nil+ (which is the default).
286
+ # See also IRB::Context#eval_history= and IRB::History.
272
287
  # <code>__[line_no]</code>::
273
288
  # Returns the evaluation value at the given line number, +line_no+.
274
289
  # If +line_no+ is a negative, the return value +line_no+ many lines before
@@ -405,14 +420,41 @@ module IRB
405
420
  end
406
421
 
407
422
  class Irb
423
+ ASSIGNMENT_NODE_TYPES = [
424
+ # Local, instance, global, class, constant, instance, and index assignment:
425
+ # "foo = bar",
426
+ # "@foo = bar",
427
+ # "$foo = bar",
428
+ # "@@foo = bar",
429
+ # "::Foo = bar",
430
+ # "a::Foo = bar",
431
+ # "Foo = bar"
432
+ # "foo.bar = 1"
433
+ # "foo[1] = bar"
434
+ :assign,
435
+
436
+ # Operation assignment:
437
+ # "foo += bar"
438
+ # "foo -= bar"
439
+ # "foo ||= bar"
440
+ # "foo &&= bar"
441
+ :opassign,
442
+
443
+ # Multiple assignment:
444
+ # "foo, bar = 1, 2
445
+ :massign,
446
+ ]
447
+ # Note: instance and index assignment expressions could also be written like:
448
+ # "foo.bar=(1)" and "foo.[]=(1, bar)", when expressed that way, the former
449
+ # be parsed as :assign and echo will be suppressed, but the latter is
450
+ # parsed as a :method_add_arg and the output won't be suppressed
451
+
408
452
  # Creates a new irb session
409
- def initialize(workspace = nil, input_method = nil, output_method = nil)
410
- @context = Context.new(self, workspace, input_method, output_method)
453
+ def initialize(workspace = nil, input_method = nil)
454
+ @context = Context.new(self, workspace, input_method)
411
455
  @context.main.extend ExtendCommandBundle
412
456
  @signal_status = :IN_IRB
413
-
414
457
  @scanner = RubyLex.new
415
- @scanner.exception_on_syntax_error = false
416
458
  end
417
459
 
418
460
  def run(conf = IRB.conf)
@@ -458,14 +500,16 @@ module IRB
458
500
  else
459
501
  @context.io.prompt = p = ""
460
502
  end
461
- if @context.auto_indent_mode
503
+ if @context.auto_indent_mode and !@context.io.respond_to?(:auto_indent)
462
504
  unless ltype
463
- ind = prompt(@context.prompt_i, ltype, indent, line_no)[/.*\z/].size +
505
+ prompt_i = @context.prompt_i.nil? ? "" : @context.prompt_i
506
+ ind = prompt(prompt_i, ltype, indent, line_no)[/.*\z/].size +
464
507
  indent * 2 - p.size
465
508
  ind += 2 if continue
466
509
  @context.io.prompt = p + " " * ind if ind > 0
467
510
  end
468
511
  end
512
+ @context.io.prompt
469
513
  end
470
514
 
471
515
  @scanner.set_input(@context.io) do
@@ -486,12 +530,14 @@ module IRB
486
530
  end
487
531
  end
488
532
 
533
+ @scanner.set_auto_indent(@context) if @context.auto_indent_mode
534
+
489
535
  @scanner.each_top_level_statement do |line, line_no|
490
536
  signal_status(:IN_EVAL) do
491
537
  begin
492
- line.untaint
538
+ line.untaint if RUBY_VERSION < '2.7'
493
539
  @context.evaluate(line, line_no, exception: exc)
494
- output_value if @context.echo?
540
+ output_value if @context.echo? && (@context.echo_on_assignment? || !assignment_expression?(line))
495
541
  rescue Interrupt => exc
496
542
  rescue SystemExit, SignalException
497
543
  raise
@@ -506,7 +552,7 @@ module IRB
506
552
  end
507
553
 
508
554
  def handle_exception(exc)
509
- if exc.backtrace && exc.backtrace[0] =~ /irb(2)?(\/.*|-.*|\.rb)?:/ && exc.class.to_s !~ /^IRB/ &&
555
+ if exc.backtrace && exc.backtrace[0] =~ /\/irb(2)?(\/.*|-.*|\.rb)?:/ && exc.class.to_s !~ /^IRB/ &&
510
556
  !(SyntaxError === exc)
511
557
  irb_bug = true
512
558
  else
@@ -663,10 +709,18 @@ module IRB
663
709
  when "l"
664
710
  ltype
665
711
  when "i"
666
- if $1
667
- format("%" + $1 + "d", indent)
712
+ if indent < 0
713
+ if $1
714
+ "-".rjust($1.to_i)
715
+ else
716
+ "-"
717
+ end
668
718
  else
669
- indent.to_s
719
+ if $1
720
+ format("%" + $1 + "d", indent)
721
+ else
722
+ indent.to_s
723
+ end
670
724
  end
671
725
  when "n"
672
726
  if $1
@@ -702,6 +756,21 @@ module IRB
702
756
  format("#<%s: %s>", self.class, ary.join(", "))
703
757
  end
704
758
 
759
+ def assignment_expression?(line)
760
+ # Try to parse the line and check if the last of possibly multiple
761
+ # expressions is an assignment type.
762
+
763
+ # If the expression is invalid, Ripper.sexp should return nil which will
764
+ # result in false being returned. Any valid expression should return an
765
+ # s-expression where the second selement of the top level array is an
766
+ # array of parsed expressions. The first element of each expression is the
767
+ # expression's type.
768
+ verbose, $VERBOSE = $VERBOSE, nil
769
+ result = ASSIGNMENT_NODE_TYPES.include?(Ripper.sexp(line)&.dig(1,-1,0))
770
+ $VERBOSE = verbose
771
+ result
772
+ end
773
+
705
774
  ATTR_TTY = "\e[%sm"
706
775
  def ATTR_TTY.[](*a) self % a.join(";"); end
707
776
  ATTR_PLAIN = ""
@@ -749,8 +818,8 @@ class Binding
749
818
  #
750
819
  # Potato.new
751
820
  #
752
- # Running +ruby potato.rb+ will open an IRB session where +binding.irb+ is
753
- # called, and you will see the following:
821
+ # Running <code>ruby potato.rb</code> will open an IRB session where
822
+ # +binding.irb+ is called, and you will see the following:
754
823
  #
755
824
  # $ ruby potato.rb
756
825
  #
@@ -780,7 +849,7 @@ class Binding
780
849
  # irb(#<Potato:0x00007feea1916670>):004:0> @cooked = true
781
850
  # => true
782
851
  #
783
- # You can exit the IRB session with the `exit` command. Note that exiting will
852
+ # You can exit the IRB session with the +exit+ command. Note that exiting will
784
853
  # resume execution where +binding.irb+ had paused it, as you can see from the
785
854
  # output printed to standard output in this example:
786
855
  #
@@ -790,9 +859,11 @@ class Binding
790
859
  #
791
860
  # See IRB@IRB+Usage for more information.
792
861
  def irb
793
- IRB.setup(eval("__FILE__"), argv: [])
862
+ IRB.setup(source_location[0], argv: [])
794
863
  workspace = IRB::WorkSpace.new(self)
795
864
  STDOUT.print(workspace.code_around_binding)
796
- IRB::Irb.new(workspace).run(IRB.conf)
865
+ binding_irb = IRB::Irb.new(workspace)
866
+ binding_irb.context.irb_path = File.expand_path(source_location[0])
867
+ binding_irb.run(IRB.conf)
797
868
  end
798
869
  end
@@ -21,7 +21,7 @@ module IRB
21
21
  class << self
22
22
  alias_method :exit, ExtendCommand.irb_original_method_name('exit')
23
23
  end
24
- if iterator?
24
+ if block_given?
25
25
  begin
26
26
  yield
27
27
  ensure
@@ -9,17 +9,18 @@
9
9
  #
10
10
  #
11
11
 
12
- require 'rdoc/ri/driver'
13
-
14
12
  require_relative "nop"
15
13
 
16
14
  # :stopdoc:
17
15
  module IRB
18
16
  module ExtendCommand
19
17
  class Help < Nop
20
- begin
21
- Ri = RDoc::RI::Driver.new
22
- rescue SystemExit
18
+ def execute(*names)
19
+ require 'rdoc/ri/driver'
20
+ IRB::ExtendCommand::Help.const_set(:Ri, RDoc::RI::Driver.new)
21
+ rescue LoadError, SystemExit
22
+ IRB::ExtendCommand::Help.remove_method(:execute)
23
+ # raise NoMethodError in ensure
23
24
  else
24
25
  def execute(*names)
25
26
  if names.empty?
@@ -35,6 +36,9 @@ module IRB
35
36
  end
36
37
  nil
37
38
  end
39
+ nil
40
+ ensure
41
+ execute(*names)
38
42
  end
39
43
  end
40
44
  end
@@ -0,0 +1,233 @@
1
+ # frozen_string_literal: true
2
+ require 'reline'
3
+ require 'ripper'
4
+
5
+ module IRB # :nodoc:
6
+ module Color
7
+ CLEAR = 0
8
+ BOLD = 1
9
+ UNDERLINE = 4
10
+ REVERSE = 7
11
+ RED = 31
12
+ GREEN = 32
13
+ YELLOW = 33
14
+ BLUE = 34
15
+ MAGENTA = 35
16
+ CYAN = 36
17
+
18
+ TOKEN_KEYWORDS = {
19
+ on_kw: ['nil', 'self', 'true', 'false', '__FILE__', '__LINE__'],
20
+ on_const: ['ENV'],
21
+ }
22
+ private_constant :TOKEN_KEYWORDS
23
+
24
+ # A constant of all-bit 1 to match any Ripper's state in #dispatch_seq
25
+ ALL = -1
26
+ private_constant :ALL
27
+
28
+ begin
29
+ # Following pry's colors where possible, but sometimes having a compromise like making
30
+ # backtick and regexp as red (string's color, because they're sharing tokens).
31
+ TOKEN_SEQ_EXPRS = {
32
+ on_CHAR: [[BLUE, BOLD], ALL],
33
+ on_backtick: [[RED, BOLD], ALL],
34
+ on_comment: [[BLUE, BOLD], ALL],
35
+ on_const: [[BLUE, BOLD, UNDERLINE], ALL],
36
+ on_embexpr_beg: [[RED], ALL],
37
+ on_embexpr_end: [[RED], ALL],
38
+ on_embvar: [[RED], ALL],
39
+ on_float: [[MAGENTA, BOLD], ALL],
40
+ on_gvar: [[GREEN, BOLD], ALL],
41
+ on_heredoc_beg: [[RED], ALL],
42
+ on_heredoc_end: [[RED], ALL],
43
+ on_ident: [[BLUE, BOLD], Ripper::EXPR_ENDFN],
44
+ on_imaginary: [[BLUE, BOLD], ALL],
45
+ on_int: [[BLUE, BOLD], ALL],
46
+ on_kw: [[GREEN], ALL],
47
+ on_label: [[MAGENTA], ALL],
48
+ on_label_end: [[RED, BOLD], ALL],
49
+ on_qsymbols_beg: [[RED, BOLD], ALL],
50
+ on_qwords_beg: [[RED, BOLD], ALL],
51
+ on_rational: [[BLUE, BOLD], ALL],
52
+ on_regexp_beg: [[RED, BOLD], ALL],
53
+ on_regexp_end: [[RED, BOLD], ALL],
54
+ on_symbeg: [[YELLOW], ALL],
55
+ on_symbols_beg: [[RED, BOLD], ALL],
56
+ on_tstring_beg: [[RED, BOLD], ALL],
57
+ on_tstring_content: [[RED], ALL],
58
+ on_tstring_end: [[RED, BOLD], ALL],
59
+ on_words_beg: [[RED, BOLD], ALL],
60
+ on_parse_error: [[RED, REVERSE], ALL],
61
+ compile_error: [[RED, REVERSE], ALL],
62
+ }
63
+ rescue NameError
64
+ # Give up highlighting Ripper-incompatible older Ruby
65
+ TOKEN_SEQ_EXPRS = {}
66
+ end
67
+ private_constant :TOKEN_SEQ_EXPRS
68
+
69
+ class << self
70
+ def colorable?
71
+ $stdout.tty? && supported? && (/mswin|mingw/ =~ RUBY_PLATFORM || (ENV.key?('TERM') && ENV['TERM'] != 'dumb'))
72
+ end
73
+
74
+ def inspect_colorable?(obj, seen: {}.compare_by_identity)
75
+ case obj
76
+ when String, Symbol, Regexp, Integer, Float, FalseClass, TrueClass, NilClass
77
+ true
78
+ when Hash
79
+ without_circular_ref(obj, seen: seen) do
80
+ obj.all? { |k, v| inspect_colorable?(k, seen: seen) && inspect_colorable?(v, seen: seen) }
81
+ end
82
+ when Array
83
+ without_circular_ref(obj, seen: seen) do
84
+ obj.all? { |o| inspect_colorable?(o, seen: seen) }
85
+ end
86
+ when Range
87
+ inspect_colorable?(obj.begin, seen: seen) && inspect_colorable?(obj.end, seen: seen)
88
+ when Module
89
+ !obj.name.nil?
90
+ else
91
+ false
92
+ end
93
+ end
94
+
95
+ def clear
96
+ return '' unless colorable?
97
+ "\e[#{CLEAR}m"
98
+ end
99
+
100
+ def colorize(text, seq)
101
+ return text unless colorable?
102
+ seq = seq.map { |s| "\e[#{const_get(s)}m" }.join('')
103
+ "#{seq}#{text}#{clear}"
104
+ end
105
+
106
+ # If `complete` is false (code is incomplete), this does not warn compile_error.
107
+ # This option is needed to avoid warning a user when the compile_error is happening
108
+ # because the input is not wrong but just incomplete.
109
+ def colorize_code(code, complete: true)
110
+ return code unless colorable?
111
+
112
+ symbol_state = SymbolState.new
113
+ colored = +''
114
+ length = 0
115
+
116
+ scan(code, allow_last_error: !complete) do |token, str, expr|
117
+ in_symbol = symbol_state.scan_token(token)
118
+ str.each_line do |line|
119
+ line = Reline::Unicode.escape_for_print(line)
120
+ if seq = dispatch_seq(token, expr, line, in_symbol: in_symbol)
121
+ colored << seq.map { |s| "\e[#{s}m" }.join('')
122
+ colored << line.sub(/\Z/, clear)
123
+ else
124
+ colored << line
125
+ end
126
+ end
127
+ length += str.bytesize
128
+ end
129
+
130
+ # give up colorizing incomplete Ripper tokens
131
+ if length != code.bytesize
132
+ return Reline::Unicode.escape_for_print(code)
133
+ end
134
+
135
+ colored
136
+ end
137
+
138
+ private
139
+
140
+ def without_circular_ref(obj, seen:, &block)
141
+ return false if seen.key?(obj)
142
+ seen[obj] = true
143
+ block.call
144
+ ensure
145
+ seen.delete(obj)
146
+ end
147
+
148
+ # Ripper::Lexer::Elem#state is supported on Ruby 2.5+
149
+ def supported?
150
+ return @supported if defined?(@supported)
151
+ @supported = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.5.0')
152
+ end
153
+
154
+ def scan(code, allow_last_error:)
155
+ pos = [1, 0]
156
+
157
+ verbose, $VERBOSE = $VERBOSE, nil
158
+ lexer = Ripper::Lexer.new(code)
159
+ if lexer.respond_to?(:scan) # Ruby 2.7+
160
+ lexer.scan.each do |elem|
161
+ str = elem.tok
162
+ next if allow_last_error and /meets end of file|unexpected end-of-input/ =~ elem.message
163
+ next if ([elem.pos[0], elem.pos[1] + str.bytesize] <=> pos) <= 0
164
+
165
+ str.each_line do |line|
166
+ if line.end_with?("\n")
167
+ pos[0] += 1
168
+ pos[1] = 0
169
+ else
170
+ pos[1] += line.bytesize
171
+ end
172
+ end
173
+
174
+ yield(elem.event, str, elem.state)
175
+ end
176
+ else
177
+ lexer.parse.each do |elem|
178
+ yield(elem.event, elem.tok, elem.state)
179
+ end
180
+ end
181
+ $VERBOSE = verbose
182
+ end
183
+
184
+ def dispatch_seq(token, expr, str, in_symbol:)
185
+ if token == :on_parse_error or token == :compile_error
186
+ TOKEN_SEQ_EXPRS[token][0]
187
+ elsif in_symbol
188
+ [YELLOW]
189
+ elsif TOKEN_KEYWORDS.fetch(token, []).include?(str)
190
+ [CYAN, BOLD]
191
+ elsif (seq, exprs = TOKEN_SEQ_EXPRS[token]; (expr & (exprs || 0)) != 0)
192
+ seq
193
+ else
194
+ nil
195
+ end
196
+ end
197
+ end
198
+
199
+ # A class to manage a state to know whether the current token is for Symbol or not.
200
+ class SymbolState
201
+ def initialize
202
+ # Push `true` to detect Symbol. `false` to increase the nest level for non-Symbol.
203
+ @stack = []
204
+ end
205
+
206
+ # Return true if the token is a part of Symbol.
207
+ def scan_token(token)
208
+ prev_state = @stack.last
209
+ case token
210
+ when :on_symbeg, :on_symbols_beg, :on_qsymbols_beg
211
+ @stack << true
212
+ when :on_ident, :on_op, :on_const, :on_ivar, :on_cvar, :on_gvar, :on_kw
213
+ if @stack.last # Pop only when it's Symbol
214
+ @stack.pop
215
+ return prev_state
216
+ end
217
+ when :on_tstring_beg
218
+ @stack << false
219
+ when :on_embexpr_beg
220
+ @stack << false
221
+ return prev_state
222
+ when :on_tstring_end # :on_tstring_end may close Symbol
223
+ @stack.pop
224
+ return prev_state
225
+ when :on_embexpr_end
226
+ @stack.pop
227
+ end
228
+ @stack.last
229
+ end
230
+ end
231
+ private_constant :SymbolState
232
+ end
233
+ end