diff --git a/lib/irb/color.rb b/lib/irb/color.rb index b942299789..6b489646b5 100644 --- a/lib/irb/color.rb +++ b/lib/irb/color.rb @@ -67,7 +67,7 @@ module IRB # :nodoc: class << self def colorable? - $stdout.tty? && (/mswin|mingw/ =~ RUBY_PLATFORM || (ENV.key?('TERM') && ENV['TERM'] != 'dumb')) + $stdout.tty? && supported? && (/mswin|mingw/ =~ RUBY_PLATFORM || (ENV.key?('TERM') && ENV['TERM'] != 'dumb')) end def inspect_colorable?(obj) @@ -132,24 +132,37 @@ module IRB # :nodoc: private + # Ripper::Lexer::Elem#state is supported on Ruby 2.5+ + def supported? + return @supported if defined?(@supported) + @supported = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.5.0') + end + def scan(code, allow_last_error:) pos = [1, 0] - Ripper::Lexer.new(code).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 + lexer = Ripper::Lexer.new(code) + 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 + str.each_line do |line| + if line.end_with?("\n") + pos[0] += 1 + pos[1] = 0 + else + pos[1] += line.bytesize + end end - end - yield(elem.event, str, elem.state) + yield(elem.event, str, elem.state) + end + else + lexer.parse.each do |elem| + yield(elem.event, elem.tok, elem.state) + end end end diff --git a/lib/irb/irb.gemspec b/lib/irb/irb.gemspec index d16d6b0ecc..12633bf15c 100644 --- a/lib/irb/irb.gemspec +++ b/lib/irb/irb.gemspec @@ -21,6 +21,7 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] + spec.add_dependency "reline" spec.add_development_dependency "bundler" spec.add_development_dependency "rake" end diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb index d77354fb83..a57a5dfcd9 100644 --- a/lib/irb/ruby-lex.rb +++ b/lib/irb/ruby-lex.rb @@ -127,7 +127,6 @@ class RubyLex end def process_continue - continued_bits = Ripper::EXPR_BEG | Ripper::EXPR_FNAME # last token is always newline if @tokens.size >= 2 and @tokens[-2][1] == :on_regexp_end # end of regexp literal @@ -149,7 +148,7 @@ class RubyLex return true elsif @tokens.size >= 1 and @tokens[-1][1] == :on_heredoc_end # "EOH\n" return false - elsif @tokens.size >= 2 and @tokens[-2][3].anybits?(continued_bits) + elsif @tokens.size >= 2 and defined?(Ripper::EXPR_BEG) and @tokens[-2][3].anybits?(Ripper::EXPR_BEG | Ripper::EXPR_FNAME) # end of literal except for regexp return true end @@ -221,19 +220,21 @@ class RubyLex $VERBOSE = verbose end - last_lex_state = @tokens.last[3] - if last_lex_state.allbits?(Ripper::EXPR_BEG) - return false - elsif last_lex_state.allbits?(Ripper::EXPR_DOT) - return true - elsif last_lex_state.allbits?(Ripper::EXPR_CLASS) - return true - elsif last_lex_state.allbits?(Ripper::EXPR_FNAME) - return true - elsif last_lex_state.allbits?(Ripper::EXPR_VALUE) - return true - elsif last_lex_state.allbits?(Ripper::EXPR_ARG) - return false + if defined?(Ripper::EXPR_BEG) + last_lex_state = @tokens.last[3] + if last_lex_state.allbits?(Ripper::EXPR_BEG) + return false + elsif last_lex_state.allbits?(Ripper::EXPR_DOT) + return true + elsif last_lex_state.allbits?(Ripper::EXPR_CLASS) + return true + elsif last_lex_state.allbits?(Ripper::EXPR_FNAME) + return true + elsif last_lex_state.allbits?(Ripper::EXPR_VALUE) + return true + elsif last_lex_state.allbits?(Ripper::EXPR_ARG) + return false + end end false diff --git a/test/irb/test_color.rb b/test/irb/test_color.rb index 6af2e24a85..60b7a9f96e 100644 --- a/test/irb/test_color.rb +++ b/test/irb/test_color.rb @@ -18,13 +18,8 @@ module TestIRB CYAN = "\e[36m" def test_colorize_code - if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.5.0') - assert_equal({}, IRB::Color::TOKEN_SEQ_EXPRS) - skip "this Ripper version is not supported" - end - # Common behaviors. Warn parser error, but do not warn compile error. - { + tests = { "1" => "#{BLUE}#{BOLD}1#{CLEAR}", "2.3" => "#{MAGENTA}#{BOLD}2.3#{CLEAR}", "7r" => "#{BLUE}#{BOLD}7r#{CLEAR}", @@ -39,22 +34,17 @@ module TestIRB '"foo#{a} #{b}"' => "#{RED}\"#{CLEAR}#{RED}foo#{CLEAR}#{RED}\#{#{CLEAR}a#{RED}}#{CLEAR}#{RED} #{CLEAR}#{RED}\#{#{CLEAR}b#{RED}}#{CLEAR}#{RED}\"#{CLEAR}", '/r#{e}g/' => "#{RED}#{BOLD}/#{CLEAR}#{RED}r#{CLEAR}#{RED}\#{#{CLEAR}e#{RED}}#{CLEAR}#{RED}g#{CLEAR}#{RED}#{BOLD}/#{CLEAR}", "'a\nb'" => "#{RED}'#{CLEAR}#{RED}a#{CLEAR}\n#{RED}b#{CLEAR}#{RED}'#{CLEAR}", - "4.5.6" => "#{MAGENTA}#{BOLD}4.5#{CLEAR}#{RED}#{REVERSE}.6#{CLEAR}", "[1]]]\u0013" => "[1]]]^S", - "\e[0m\n" => "#{RED}#{REVERSE}^[#{CLEAR}[#{BLUE}#{BOLD}0#{CLEAR}#{RED}#{REVERSE}m#{CLEAR}\n", "%w[a b]" => "#{RED}%w[#{CLEAR}#{RED}a#{CLEAR} #{RED}b#{CLEAR}#{RED}]#{CLEAR}", "%i[c d]" => "#{RED}%i[#{CLEAR}#{RED}c#{CLEAR} #{RED}d#{CLEAR}#{RED}]#{CLEAR}", "{'a': 1}" => "{#{RED}'#{CLEAR}#{RED}a#{CLEAR}#{RED}':#{CLEAR} #{BLUE}#{BOLD}1#{CLEAR}}", ":Struct" => "#{YELLOW}:#{CLEAR}#{YELLOW}Struct#{CLEAR}", - "< "#{RED}< "#{RED}\"#{CLEAR}#{RED}\#{#{CLEAR}#{RED}}#{CLEAR}#{RED}\"#{CLEAR}", ':"a#{}b"' => "#{YELLOW}:\"#{CLEAR}#{YELLOW}a#{CLEAR}#{YELLOW}\#{#{CLEAR}#{YELLOW}}#{CLEAR}#{YELLOW}b#{CLEAR}#{YELLOW}\"#{CLEAR}", ':"a#{ def b; end; \'c\' + "#{ :d }" }e"' => "#{YELLOW}:\"#{CLEAR}#{YELLOW}a#{CLEAR}#{YELLOW}\#{#{CLEAR} #{GREEN}def#{CLEAR} #{BLUE}#{BOLD}b#{CLEAR}; #{GREEN}end#{CLEAR}; #{RED}'#{CLEAR}#{RED}c#{CLEAR}#{RED}'#{CLEAR} + #{RED}\"#{CLEAR}#{RED}\#{#{CLEAR} #{YELLOW}:#{CLEAR}#{YELLOW}d#{CLEAR} #{RED}}#{CLEAR}#{RED}\"#{CLEAR} #{YELLOW}}#{CLEAR}#{YELLOW}e#{CLEAR}#{YELLOW}\"#{CLEAR}", "[__FILE__, __LINE__]" => "[#{CYAN}#{BOLD}__FILE__#{CLEAR}, #{CYAN}#{BOLD}__LINE__#{CLEAR}]", ":self" => "#{YELLOW}:#{CLEAR}#{YELLOW}self#{CLEAR}", ":class" => "#{YELLOW}:#{CLEAR}#{YELLOW}class#{CLEAR}", - ":@1" => "#{YELLOW}:#{CLEAR}#{RED}#{REVERSE}@1#{CLEAR}", - "@@1" => "#{RED}#{REVERSE}@@1#{CLEAR}", "[:end, 2]" => "[#{YELLOW}:#{CLEAR}#{YELLOW}end#{CLEAR}, #{BLUE}#{BOLD}2#{CLEAR}]", "[:>, 3]" => "[#{YELLOW}:#{CLEAR}#{YELLOW}>#{CLEAR}, #{BLUE}#{BOLD}3#{CLEAR}]", ":Hello ? world : nil" => "#{YELLOW}:#{CLEAR}#{YELLOW}Hello#{CLEAR} ? world : #{CYAN}#{BOLD}nil#{CLEAR}", @@ -69,16 +59,37 @@ module TestIRB "\t" => "\t", # not ^I "foo(*%W(bar))" => "foo(*#{RED}%W(#{CLEAR}#{RED}bar#{CLEAR}#{RED})#{CLEAR})", "$stdout" => "#{GREEN}#{BOLD}$stdout#{CLEAR}", - }.each do |code, result| - actual = with_term { IRB::Color.colorize_code(code, complete: true) } - assert_equal(result, actual, "Case: colorize_code(#{code.dump}, complete: true)\nResult: #{humanized_literal(actual)}") + } - actual = with_term { IRB::Color.colorize_code(code, complete: false) } - assert_equal(result, actual, "Case: colorize_code(#{code.dump}, complete: false)\nResult: #{humanized_literal(actual)}") + if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.7.0') + tests.merge!({ + "4.5.6" => "#{MAGENTA}#{BOLD}4.5#{CLEAR}#{RED}#{REVERSE}.6#{CLEAR}", + "\e[0m\n" => "#{RED}#{REVERSE}^[#{CLEAR}[#{BLUE}#{BOLD}0#{CLEAR}#{RED}#{REVERSE}m#{CLEAR}\n", + "< "#{RED}< "#{YELLOW}:#{CLEAR}#{RED}#{REVERSE}@1#{CLEAR}", + "@@1" => "#{RED}#{REVERSE}@@1#{CLEAR}", + }) + end + + tests.each do |code, result| + if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.5.0') + # colorize_code is supported only for Ruby 2.5+. Just return the original code in 2.4-. + actual = with_term { IRB::Color.colorize_code(code) } + assert_equal(code, actual) + else + actual = with_term { IRB::Color.colorize_code(code, complete: true) } + assert_equal(result, actual, "Case: colorize_code(#{code.dump}, complete: true)\nResult: #{humanized_literal(actual)}") + + actual = with_term { IRB::Color.colorize_code(code, complete: false) } + assert_equal(result, actual, "Case: colorize_code(#{code.dump}, complete: false)\nResult: #{humanized_literal(actual)}") + end end end def test_colorize_code_complete_true + unless lexer_scan_supported? + skip '`complete: true` is the same as `complete: false` in Ruby 2.6-' + end # `complete: true` behaviors. Warn end-of-file. { "'foo' + 'bar" => "#{RED}'#{CLEAR}#{RED}foo#{CLEAR}#{RED}'#{CLEAR} + #{RED}'#{CLEAR}#{RED}#{REVERSE}bar#{CLEAR}", @@ -95,8 +106,19 @@ module TestIRB "'foo' + 'bar" => "#{RED}'#{CLEAR}#{RED}foo#{CLEAR}#{RED}'#{CLEAR} + #{RED}'#{CLEAR}#{RED}bar#{CLEAR}", "('foo" => "(#{RED}'#{CLEAR}#{RED}foo#{CLEAR}", }.each do |code, result| - actual = with_term { IRB::Color.colorize_code(code, complete: false) } - assert_equal(result, actual, "Case: colorize_code(#{code.dump}, complete: false)\nResult: #{humanized_literal(actual)}") + if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.5.0') + # colorize_code is supported only for Ruby 2.5+. Just return the original code in 2.4-. + actual = with_term { IRB::Color.colorize_code(code) } + assert_equal(code, actual) + else + actual = with_term { IRB::Color.colorize_code(code, complete: false) } + assert_equal(result, actual, "Case: colorize_code(#{code.dump}, complete: false)\nResult: #{humanized_literal(actual)}") + + unless lexer_scan_supported? + actual = with_term { IRB::Color.colorize_code(code, complete: true) } + assert_equal(result, actual, "Case: colorize_code(#{code.dump}, complete: false)\nResult: #{humanized_literal(actual)}") + end + end end end @@ -120,6 +142,10 @@ module TestIRB private + def lexer_scan_supported? + Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.7.0') + end + def with_term stdout = $stdout io = StringIO.new diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb index 83df9c1c47..204b321ce1 100644 --- a/test/irb/test_completion.rb +++ b/test/irb/test_completion.rb @@ -7,7 +7,8 @@ module TestIRB begin require "irb/completion" bug5938 = '[ruby-core:42244]' - cmds = %W[-W0 -rirb -rirb/completion -e IRB.setup(__FILE__) + bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] + cmds = bundle_exec + %W[-W0 -rirb -rirb/completion -e IRB.setup(__FILE__) -e IRB.conf[:MAIN_CONTEXT]=IRB::Irb.new.context -e module\sFoo;def\sself.name;//;end;end -e IRB::InputCompletor::CompletionProc.call("[1].first.") diff --git a/test/irb/test_option.rb b/test/irb/test_option.rb index 6f36d81bdd..aa634c02a2 100644 --- a/test/irb/test_option.rb +++ b/test/irb/test_option.rb @@ -5,7 +5,8 @@ module TestIRB class TestOption < Test::Unit::TestCase def test_end_of_option bug4117 = '[ruby-core:33574]' - status = assert_in_out_err(%w[-W0 -rirb -e IRB.start(__FILE__) -- -f --], "", //, [], bug4117) + bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] + status = assert_in_out_err(bundle_exec + %w[-W0 -rirb -e IRB.start(__FILE__) -- -f --], "", //, [], bug4117) assert(status.success?, bug4117) end end diff --git a/test/irb/test_raise_no_backtrace_exception.rb b/test/irb/test_raise_no_backtrace_exception.rb index 38c61f2a94..e92d8dc970 100644 --- a/test/irb/test_raise_no_backtrace_exception.rb +++ b/test/irb/test_raise_no_backtrace_exception.rb @@ -4,7 +4,8 @@ require 'test/unit' module TestIRB class TestRaiseNoBacktraceException < Test::Unit::TestCase def test_raise_exception - assert_in_out_err(%w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, /Exception: foo/, []) + bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] + assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, /Exception: foo/, []) e = Exception.new("foo") def e.backtrace; nil; end raise e