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.
- 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
|