irb 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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