diff options
Diffstat (limited to 'lib/irb/color.rb')
-rw-r--r-- | lib/irb/color.rb | 82 |
1 files changed, 49 insertions, 33 deletions
diff --git a/lib/irb/color.rb b/lib/irb/color.rb index 40e9e04c97..ad8670160c 100644 --- a/lib/irb/color.rb +++ b/lib/irb/color.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'reline' require 'ripper' -require 'irb/ruby-lex' +require_relative 'ruby-lex' module IRB # :nodoc: module Color @@ -9,12 +9,14 @@ module IRB # :nodoc: BOLD = 1 UNDERLINE = 4 REVERSE = 7 + BLACK = 30 RED = 31 GREEN = 32 YELLOW = 33 BLUE = 34 MAGENTA = 35 CYAN = 36 + WHITE = 37 TOKEN_KEYWORDS = { on_kw: ['nil', 'self', 'true', 'false', '__FILE__', '__LINE__', '__ENCODING__'], @@ -77,7 +79,15 @@ module IRB # :nodoc: class << self def colorable? - $stdout.tty? && (/mswin|mingw/ =~ RUBY_PLATFORM || (ENV.key?('TERM') && ENV['TERM'] != 'dumb')) + supported = $stdout.tty? && (/mswin|mingw/ =~ RUBY_PLATFORM || (ENV.key?('TERM') && ENV['TERM'] != 'dumb')) + + # because ruby/debug also uses irb's color module selectively, + # irb won't be activated in that case. + if IRB.respond_to?(:conf) + supported && IRB.conf.fetch(:USE_COLORIZE, true) + else + supported + end end def inspect_colorable?(obj, seen: {}.compare_by_identity) @@ -115,15 +125,21 @@ module IRB # :nodoc: # 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, colorable: colorable?) + def colorize_code(code, complete: true, ignore_error: false, colorable: colorable?, local_variables: []) return code unless colorable symbol_state = SymbolState.new colored = +'' - length = 0 - end_seen = false + lvars_code = RubyLex.generate_local_variables_assign_code(local_variables) + code_with_lvars = lvars_code ? "#{lvars_code}\n#{code}" : code + + scan(code_with_lvars, allow_last_error: !complete) do |token, str, expr| + # handle uncolorable code + if token.nil? + colored << Reline::Unicode.escape_for_print(str) + next + end - 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) @@ -139,15 +155,12 @@ module IRB # :nodoc: colored << line end end - length += str.bytesize - end_seen = true if token == :on___end__ end - # give up colorizing incomplete Ripper tokens - unless end_seen or length == code.bytesize - return Reline::Unicode.escape_for_print(code) + if lvars_code + raise "#{lvars_code.dump} should have no \\n" if lvars_code.include?("\n") + colored.sub!(/\A.+\n/, '') # delete_prefix lvars_code with colors end - colored end @@ -162,33 +175,36 @@ module IRB # :nodoc: 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 + byte_pos = 0 + line_positions = [0] + inner_code.lines.each do |line| + line_positions << line_positions.last + line.bytesize + end - yield(elem.event, str, elem.state) + on_scan = proc do |elem| + start_pos = line_positions[elem.pos[0] - 1] + elem.pos[1] + + # yield uncolorable code + if byte_pos < start_pos + yield(nil, inner_code.byteslice(byte_pos...start_pos), nil) end - else - lexer.parse.each do |elem| - yield(elem.event, elem.tok, elem.state) + + if byte_pos <= start_pos + str = elem.tok + yield(elem.event, str, elem.state) + byte_pos = start_pos + str.bytesize end end + + lexer.scan.each do |elem| + next if allow_last_error and /meets end of file|unexpected end-of-input/ =~ elem.message + on_scan.call(elem) + end + # yield uncolorable DATA section + yield(nil, inner_code.byteslice(byte_pos...inner_code.bytesize), nil) if byte_pos < inner_code.bytesize end ensure $VERBOSE = verbose @@ -222,7 +238,7 @@ module IRB # :nodoc: 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 + when :on_ident, :on_op, :on_const, :on_ivar, :on_cvar, :on_gvar, :on_kw, :on_backtick if @stack.last # Pop only when it's Symbol @stack.pop return prev_state |