diff --git a/lib/rouge.rb b/lib/rouge.rb index 14d6dc4198..6a72389b93 100644 --- a/lib/rouge.rb +++ b/lib/rouge.rb @@ -72,6 +72,7 @@ def lexer_dir(path = '') load_relative 'rouge/formatters/html_line_table' load_relative 'rouge/formatters/html_inline' load_relative 'rouge/formatters/terminal256' +load_relative 'rouge/formatters/terminal_truecolor' load_relative 'rouge/formatters/tex' load_relative 'rouge/formatters/null' diff --git a/lib/rouge/cli.rb b/lib/rouge/cli.rb index 62a54ef8b5..5f0992e2a4 100644 --- a/lib/rouge/cli.rb +++ b/lib/rouge/cli.rb @@ -4,6 +4,8 @@ # not required by the main lib. # to use this module, require 'rouge/cli'. +require 'rbconfig' + module Rouge class FileReader attr_reader :input @@ -200,9 +202,22 @@ def self.doc yield %[ delimiters. implies --escape] end + # There is no consistent way to do this, but this is used elsewhere, + # and we provide explicit opt-in and opt-out with $COLORTERM + def self.supports_truecolor? + return true if %w(24bit truecolor).include?(ENV['COLORTERM']) + return false if ENV['COLORTERM'] && ENV['COLORTERM'] =~ /256/ + + if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ + ENV['ConEmuANSI'] == 'ON' && !ENV['ANSICON'] + else + ENV['TERM'] !~ /(^rxvt)|(-color$)/ + end + end + def self.parse(argv) opts = { - :formatter => 'terminal256', + :formatter => supports_truecolor? ? 'terminal-truecolor' : 'terminal256', :theme => 'thankful_eyes', :css_class => 'codehilite', :input_file => '-', @@ -299,8 +314,10 @@ def initialize(opts={}) theme = Theme.find(opts[:theme]).new or error! "unknown theme #{opts[:theme]}" + # TODO: document this in --help @formatter = case opts[:formatter] when 'terminal256' then Formatters::Terminal256.new(theme) + when 'terminal-truecolor' then Formatters::TerminalTruecolor.new(theme) when 'html' then Formatters::HTML.new when 'html-pygments' then Formatters::HTMLPygments.new(Formatters::HTML.new, opts[:css_class]) when 'html-inline' then Formatters::HTMLInline.new(theme) diff --git a/lib/rouge/formatters/terminal256.rb b/lib/rouge/formatters/terminal256.rb index e6edf8641f..140d53cfdf 100644 --- a/lib/rouge/formatters/terminal256.rb +++ b/lib/rouge/formatters/terminal256.rb @@ -174,7 +174,11 @@ def escape_sequence(token) return Unescape.new if escape?(token) @escape_sequences ||= {} @escape_sequences[token.qualname] ||= - EscapeSequence.new(get_style(token)) + make_escape_sequence(get_style(token)) + end + + def make_escape_sequence(style) + EscapeSequence.new(style) end def get_style(token) diff --git a/lib/rouge/formatters/terminal_truecolor.rb b/lib/rouge/formatters/terminal_truecolor.rb new file mode 100644 index 0000000000..07feab07b0 --- /dev/null +++ b/lib/rouge/formatters/terminal_truecolor.rb @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- # +# frozen_string_literal: true +module Rouge + module Formatters + class TerminalTruecolor < Terminal256 + tag 'terminal_truecolor' + + class TruecolorEscapeSequence < Terminal256::EscapeSequence + def style_string + @style_string ||= begin + out = String.new('') + out << escape(['48', '2', *get_rgb(style.bg)]) if style.bg + out << escape(['38', '2', *get_rgb(style.fg)]) if style.fg + out << escape(['1']) if style[:bold] || style[:italic] + out + end + end + + def get_rgb(color) + color = $1 if color =~ /#(\h+)/ + + case color.size + when 3 then color.chars.map { |c| c.to_i(16) * 2 } + when 6 then color.scan(/../).map { |cc| cc.to_i(16) } + else + raise "invalid color: #{color.inspect}" + end + end + end + + # @override + def make_escape_sequence(style) + TruecolorEscapeSequence.new(style) + end + end + end +end diff --git a/spec/formatters/terminal_truecolor_spec.rb b/spec/formatters/terminal_truecolor_spec.rb new file mode 100644 index 0000000000..7d6dc55eaa --- /dev/null +++ b/spec/formatters/terminal_truecolor_spec.rb @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- # +# frozen_string_literal: true + +describe Rouge::Formatters::TerminalTruecolor do + let(:subject) { Rouge::Formatters::TerminalTruecolor.new } + + it 'renders a thing' do + result = subject.format([[Token['Text'], 'foo']]) + + assert { result == "\e[38;2;250;246;228mfoo\e[39m" } + end +end