ruby/lib/irb/color.rb
aycabta 58509767d1
Backport lib/reline, ext/readline, and lib/irb for 3.0.1 (#4085)
* Get rid of inconsistent dll linkages against vcpkg readline

* [ruby/irb] Enhance colored inspect output

dffcdb5269

* [ruby/irb] Add color_printer.rb to gemspec

b4df0fd8b2

* [ruby/irb] Fix failing tests

7723ade899

* irb: add more syntax errors colorizing support (#3967)

* [ruby/irb] Do not colorize partially-correct inspect

This is to prevent a yellow-mixed output for ActiveSupport::TimeWithZone.

Follows up https://github.com/ruby/irb/pull/159 and https://github.com/ruby/ruby/pull/3967.

a5804c3560

* [ruby/irb] Remove unnecessary ignore_error in dispatch_seq

Just forgotten in a5804c3560

e42e548793

* Increase timeout for reline with --jit-wait

for failures like:
http://ci.rvm.jp/logfiles/brlog.trunk-mjit-wait.20201229-130509
http://ci.rvm.jp/logfiles/brlog.trunk-mjit-wait.20201229-165132
http://ci.rvm.jp/logfiles/brlog.trunk-mjit-wait.20201228-015519

* [ruby/irb] Stringify when a non-object is passed to PP#text

If a nested object is passed to #pp, it may be sometimes passed to the #text
method as an object without being stringified.

This is fixed on the Ruby main repository;
433a3be86a
but it was a bug of Ripper so still needs this workaround for using irb
as a gem on Ruby 3.0.0 or earlier.

Co-authored-by: k0kubun <takashikkbn@gmail.com>

8d13df22ee

* [ruby/irb] Newline in oneliner def doesn't reset indent

This closes ruby/irb#132.

43456dcf5e

* [ruby/irb] Escape invalid byte sequence in Exception

This fixes ruby/irb#141.

0815317d42

* [ruby/irb] Handle indentations related to keyword "do" correctly

This fixes ruby/irb#158.

964643400b

* [ruby/irb] Heredoc may contain multiple newlines in a single token

Use the start token as the indentation criteria so that it works properly in
heredoc.

ref. https://github.com/ruby/reline/pull/242

9704808dfd

* [ruby/irb] Use Ripper::Lexer#scan to take broken tokens

ref. https://github.com/ruby/reline/pull/242

54f90cb6c9

* [ruby/irb] Use error tokens if there are no correct tokens in the same place

For example, the broken code "%www" will result in only one error token.

9fa39a7cf3

* [ruby/irb] Ensure to restore $VERBOSE

cef474a76a

* 600x larger timeout for Reline

I didn't notice it's msec. 2.5s is too short.
http://ci.rvm.jp/results/trunk-mjit-wait@phosphorus-docker/3311385

* [ruby/irb] fix typo in `IRB::Irb#convert_invalid_byte_sequence`

d09d3c3d68

* [ruby/irb] do not escape a predicate method for doc namespace

* Fixes #88

d431a30af4

* [ruby/irb] refactoring an error handling in `IRB::Inspector`

* moved rescue clause to `#inspect_value` to catch all failures in inspectors
* test with all (currently five kind of) inspect modes
  - tweaked the input due to only `Marshal` can inspect(dump) a `BasicObject`

9d112fab8e

* [ruby/irb] Use Exception#full_message to show backtrace in the correct order

[Bug #17466]

1c76845cca

* [ruby/irb] Fix BACK_TRACE_LIMIT logic

30dc5d43fe

* irb: Drop lines from backtrace for tests in Ruby repository

* [ruby/reline] Update cursor correctly when just cursor moving

This fixes ruby/reline#236 and ruby/reline#239.

3e3c89d00b

* [ruby/reline] Correct var names in Reline were different from vi-*-mode-string

8255fc93b9

* [ruby/reline] Remove debug print

d7fbaedc6a

* [ruby/reline] Suppress crashing when auto_indent_proc returns broken indent info

Co-authored-by: Juanito Fatas <me@juanitofatas.com>

7c24276275

* [ruby/reline] Suppress crashing when dynamic_prompt_proc returns a broken prompt list

Co-authored-by: Juanito Fatas <me@juanitofatas.com>

558f7be168

* [ruby/reline] Suppress auto indent for adding newlines in pasting

Co-authored-by: Juanito Fatas <me@juanitofatas.com>

074bb017a7

* [ruby/reline] Add acknowledgments and license for rb-readline

19df59b916

* [ruby/irb] Fix comment, irb gem supports 2.5.0 or older

36118015ba

* should use `assert_include` here.

Random ordering test can introduce antoher candidate so it should be
`assert_include`.

* [ruby/irb] Add missing require

This is useful if you want to use IRB::ColorPrinter as a library like:

```
begin
  require 'irb/color_printer'
  IRB::ColorPrinter.pp(obj)
rescue LoadError
  pp(obj)
end
```

f8461691c7

* [ruby/irb] Make IRB::ColorPrinter.pp compatible with PP.pp

The incompatible interface is not helpful, again if you want to use it
as a standalone library, falling it back to PP.

Original PP.pp also ends with `out << "\n"`.

4c74c7d84c

* Suppress constant redefinition warnings

* Fix the failing test with XDG_CONFIG_HOME

* [ruby/irb] Version 1.3.1

c230d08911

* [ruby/reline] Handle ed_search_{prev,next}_history in multiline correctly

The current line was being handled incorrectly when displaying the hit
history, so it has been fixed to be correct.

a3df4343b3

* [ruby/reline] Move the cursor correctly when deleting at eol

This fixes ruby/reline#246.

07a73ba601

* [ruby/reline] Version 0.2.1

a3b3c6ee60

* [ruby/reline] Initialize a variable just in case

29b10f6e98

* [ruby/reline] Tests with yamatanooroti don't need chdir

Because of chdir, log files ware created in temporary directries on Windows.

200b469a68

* [ruby/reline] Windows needs more times to wait rendering

53ff2b09c7

* [ruby/reline] Support for change in Windows-specific behavior at eol

The behavior of automatically moving the cursor to the next line when
displaying a char at the eol on Windows suddenly disappeared.

cad4de6ee8

* [ruby/reline] Reline::Windows.erase_after_cursor erases attributes too

68b961dfc7

* [ruby/irb] [ruby/irb] [ruby/reline] Version 0.2.2

dfb710946f

1a1cdf9628

fe99faf8bd

* [ruby/irb] handle `__ENCODING__` as a keyword as well

a6a33d908f

* [ruby/irb] handle repeated exception separately

fcf6b34bc5

* [ruby/irb] skip a failling test on TruffleRuby

* due to the difference of backtrace pointed out by @aycabta

5e00a0ae61

* [ruby/irb] Version 1.3.2

a7699026cc

Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org>
Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com>
Co-authored-by: Nobuhiro IMAI <nov@yo.rim.or.jp>
Co-authored-by: Koichi Sasada <ko1@atdot.net>
Co-authored-by: Hiroshi SHIBATA <hsbt@ruby-lang.org>
2021-01-19 13:01:31 +09:00

248 lines
8.3 KiB
Ruby

# frozen_string_literal: true
require 'reline'
require 'ripper'
require 'irb/ruby-lex'
module IRB # :nodoc:
module Color
CLEAR = 0
BOLD = 1
UNDERLINE = 4
REVERSE = 7
RED = 31
GREEN = 32
YELLOW = 33
BLUE = 34
MAGENTA = 35
CYAN = 36
TOKEN_KEYWORDS = {
on_kw: ['nil', 'self', 'true', 'false', '__FILE__', '__LINE__', '__ENCODING__'],
on_const: ['ENV'],
}
private_constant :TOKEN_KEYWORDS
# A constant of all-bit 1 to match any Ripper's state in #dispatch_seq
ALL = -1
private_constant :ALL
begin
# Following pry's colors where possible, but sometimes having a compromise like making
# backtick and regexp as red (string's color, because they're sharing tokens).
TOKEN_SEQ_EXPRS = {
on_CHAR: [[BLUE, BOLD], ALL],
on_backtick: [[RED, BOLD], ALL],
on_comment: [[BLUE, BOLD], ALL],
on_const: [[BLUE, BOLD, UNDERLINE], ALL],
on_embexpr_beg: [[RED], ALL],
on_embexpr_end: [[RED], ALL],
on_embvar: [[RED], ALL],
on_float: [[MAGENTA, BOLD], ALL],
on_gvar: [[GREEN, BOLD], ALL],
on_heredoc_beg: [[RED], ALL],
on_heredoc_end: [[RED], ALL],
on_ident: [[BLUE, BOLD], Ripper::EXPR_ENDFN],
on_imaginary: [[BLUE, BOLD], ALL],
on_int: [[BLUE, BOLD], ALL],
on_kw: [[GREEN], ALL],
on_label: [[MAGENTA], ALL],
on_label_end: [[RED, BOLD], ALL],
on_qsymbols_beg: [[RED, BOLD], ALL],
on_qwords_beg: [[RED, BOLD], ALL],
on_rational: [[BLUE, BOLD], ALL],
on_regexp_beg: [[RED, BOLD], ALL],
on_regexp_end: [[RED, BOLD], ALL],
on_symbeg: [[YELLOW], ALL],
on_symbols_beg: [[RED, BOLD], ALL],
on_tstring_beg: [[RED, BOLD], ALL],
on_tstring_content: [[RED], ALL],
on_tstring_end: [[RED, BOLD], ALL],
on_words_beg: [[RED, BOLD], ALL],
on_parse_error: [[RED, REVERSE], ALL],
compile_error: [[RED, REVERSE], ALL],
on_assign_error: [[RED, REVERSE], ALL],
on_alias_error: [[RED, REVERSE], ALL],
on_class_name_error:[[RED, REVERSE], ALL],
on_param_error: [[RED, REVERSE], ALL],
}
rescue NameError
# Give up highlighting Ripper-incompatible older Ruby
TOKEN_SEQ_EXPRS = {}
end
private_constant :TOKEN_SEQ_EXPRS
ERROR_TOKENS = TOKEN_SEQ_EXPRS.keys.select { |k| k.to_s.end_with?('error') }
private_constant :ERROR_TOKENS
class << self
def colorable?
$stdout.tty? && supported? && (/mswin|mingw/ =~ RUBY_PLATFORM || (ENV.key?('TERM') && ENV['TERM'] != 'dumb'))
end
def inspect_colorable?(obj, seen: {}.compare_by_identity)
case obj
when String, Symbol, Regexp, Integer, Float, FalseClass, TrueClass, NilClass
true
when Hash
without_circular_ref(obj, seen: seen) do
obj.all? { |k, v| inspect_colorable?(k, seen: seen) && inspect_colorable?(v, seen: seen) }
end
when Array
without_circular_ref(obj, seen: seen) do
obj.all? { |o| inspect_colorable?(o, seen: seen) }
end
when Range
inspect_colorable?(obj.begin, seen: seen) && inspect_colorable?(obj.end, seen: seen)
when Module
!obj.name.nil?
else
false
end
end
def clear
return '' unless colorable?
"\e[#{CLEAR}m"
end
def colorize(text, seq)
return text unless colorable?
seq = seq.map { |s| "\e[#{const_get(s)}m" }.join('')
"#{seq}#{text}#{clear}"
end
# If `complete` is false (code is incomplete), this does not warn compile_error.
# This option is needed to avoid warning a user when the compile_error is happening
# because the input is not wrong but just incomplete.
def colorize_code(code, complete: true, ignore_error: false)
return code unless colorable?
symbol_state = SymbolState.new
colored = +''
length = 0
scan(code, allow_last_error: !complete) do |token, str, expr|
# IRB::ColorPrinter skips colorizing fragments with any invalid token
if ignore_error && ERROR_TOKENS.include?(token)
return Reline::Unicode.escape_for_print(code)
end
in_symbol = symbol_state.scan_token(token)
str.each_line do |line|
line = Reline::Unicode.escape_for_print(line)
if seq = dispatch_seq(token, expr, line, in_symbol: in_symbol)
colored << seq.map { |s| "\e[#{s}m" }.join('')
colored << line.sub(/\Z/, clear)
else
colored << line
end
end
length += str.bytesize
end
# give up colorizing incomplete Ripper tokens
if length != code.bytesize
return Reline::Unicode.escape_for_print(code)
end
colored
end
private
def without_circular_ref(obj, seen:, &block)
return false if seen.key?(obj)
seen[obj] = true
block.call
ensure
seen.delete(obj)
end
def supported?
return @supported if defined?(@supported)
@supported = Ripper::Lexer::Elem.method_defined?(:state)
end
def scan(code, allow_last_error:)
pos = [1, 0]
verbose, $VERBOSE = $VERBOSE, nil
RubyLex.compile_with_errors_suppressed(code) do |inner_code, line_no|
lexer = Ripper::Lexer.new(inner_code, '(ripper)', line_no)
if lexer.respond_to?(:scan) # Ruby 2.7+
lexer.scan.each do |elem|
str = elem.tok
next if allow_last_error and /meets end of file|unexpected end-of-input/ =~ elem.message
next if ([elem.pos[0], elem.pos[1] + str.bytesize] <=> pos) <= 0
str.each_line do |line|
if line.end_with?("\n")
pos[0] += 1
pos[1] = 0
else
pos[1] += line.bytesize
end
end
yield(elem.event, str, elem.state)
end
else
lexer.parse.each do |elem|
yield(elem.event, elem.tok, elem.state)
end
end
end
ensure
$VERBOSE = verbose
end
def dispatch_seq(token, expr, str, in_symbol:)
if ERROR_TOKENS.include?(token)
TOKEN_SEQ_EXPRS[token][0]
elsif in_symbol
[YELLOW]
elsif TOKEN_KEYWORDS.fetch(token, []).include?(str)
[CYAN, BOLD]
elsif (seq, exprs = TOKEN_SEQ_EXPRS[token]; (expr & (exprs || 0)) != 0)
seq
else
nil
end
end
end
# A class to manage a state to know whether the current token is for Symbol or not.
class SymbolState
def initialize
# Push `true` to detect Symbol. `false` to increase the nest level for non-Symbol.
@stack = []
end
# Return true if the token is a part of Symbol.
def scan_token(token)
prev_state = @stack.last
case token
when :on_symbeg, :on_symbols_beg, :on_qsymbols_beg
@stack << true
when :on_ident, :on_op, :on_const, :on_ivar, :on_cvar, :on_gvar, :on_kw
if @stack.last # Pop only when it's Symbol
@stack.pop
return prev_state
end
when :on_tstring_beg
@stack << false
when :on_embexpr_beg
@stack << false
return prev_state
when :on_tstring_end # :on_tstring_end may close Symbol
@stack.pop
return prev_state
when :on_embexpr_end
@stack.pop
end
@stack.last
end
end
private_constant :SymbolState
end
end