irb 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +5 -0
- data/README.md +3 -3
- data/doc/irb/irb-tools.rd.ja +184 -0
- data/doc/irb/irb.rd.ja +411 -0
- data/irb.gemspec +58 -1
- data/lib/irb.rb +106 -35
- data/lib/irb/cmd/fork.rb +1 -1
- data/lib/irb/cmd/help.rb +9 -5
- data/lib/irb/color.rb +233 -0
- data/lib/irb/completion.rb +126 -33
- data/lib/irb/context.rb +105 -65
- data/lib/irb/ext/history.rb +47 -9
- data/lib/irb/ext/multi-irb.rb +7 -7
- data/lib/irb/ext/save-history.rb +17 -5
- data/lib/irb/ext/tracer.rb +14 -1
- data/lib/irb/ext/use-loader.rb +3 -0
- data/lib/irb/extend-command.rb +70 -48
- data/lib/irb/frame.rb +12 -7
- data/lib/irb/init.rb +30 -20
- data/lib/irb/input-method.rb +108 -3
- data/lib/irb/inspector.rb +12 -2
- data/lib/irb/lc/error.rb +55 -16
- data/lib/irb/lc/help-message +9 -6
- data/lib/irb/lc/ja/error.rb +55 -14
- data/lib/irb/lc/ja/help-message +9 -6
- data/lib/irb/locale.rb +5 -1
- data/lib/irb/notifier.rb +12 -8
- data/lib/irb/output-method.rb +6 -6
- data/lib/irb/ruby-lex.rb +345 -1039
- data/lib/irb/ruby_logo.aa +38 -0
- data/lib/irb/version.rb +2 -2
- data/lib/irb/workspace.rb +58 -20
- data/man/irb.1 +207 -0
- metadata +21 -6
- data/.gitignore +0 -9
- data/.travis.yml +0 -6
- data/lib/irb/ruby-token.rb +0 -267
- data/lib/irb/slex.rb +0 -282
data/irb.gemspec
CHANGED
@@ -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 = [
|
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 "
|
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
|
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
|
-
# --
|
63
|
-
# --
|
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 --
|
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[:
|
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
|
113
|
+
# To disable auto-indent mode in irb, add the following to your +.irbrc+:
|
109
114
|
#
|
110
|
-
# IRB.conf[:AUTO_INDENT] =
|
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
|
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
|
128
|
+
# If you want to disable history, add the following to your +.irbrc+:
|
123
129
|
#
|
124
|
-
# IRB.conf[:SAVE_HISTORY] =
|
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 =>
|
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
|
410
|
-
@context = Context.new(self, workspace, input_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
|
-
|
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] =~
|
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
|
667
|
-
|
712
|
+
if indent < 0
|
713
|
+
if $1
|
714
|
+
"-".rjust($1.to_i)
|
715
|
+
else
|
716
|
+
"-"
|
717
|
+
end
|
668
718
|
else
|
669
|
-
|
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
|
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
|
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(
|
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)
|
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
|
data/lib/irb/cmd/fork.rb
CHANGED
data/lib/irb/cmd/help.rb
CHANGED
@@ -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
|
-
|
21
|
-
|
22
|
-
|
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
|
data/lib/irb/color.rb
ADDED
@@ -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
|