From b9a27c85189a91ee7d75e9de4eca554414688018 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Sat, 25 Jan 2020 23:37:11 -0500 Subject: [PATCH 1/9] add a formatter for truecolor terminals --- lib/rouge.rb | 1 + lib/rouge/cli.rb | 1 + lib/rouge/formatters/terminal256.rb | 6 +++- lib/rouge/formatters/terminal_truecolor.rb | 32 ++++++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 lib/rouge/formatters/terminal_truecolor.rb 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..c167b9be3a 100644 --- a/lib/rouge/cli.rb +++ b/lib/rouge/cli.rb @@ -301,6 +301,7 @@ def initialize(opts={}) @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..a16beee287 --- /dev/null +++ b/lib/rouge/formatters/terminal_truecolor.rb @@ -0,0 +1,32 @@ +module Rouge + module Formatters + class TerminalTruecolor < Terminal256 + tag 'terminal_truecolor' + + class TruecolorEscapeSequence < Terminal256::EscapeSequence + def style_string + 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] + 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 'oh no' + end + end + end + + # @override + def make_escape_sequence(style) + TruecolorEscapeSequence.new(style) + end + end + end +end From 10ec33a0e84484c4770cb67fb92371a07864561d Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Sat, 25 Jan 2020 23:46:45 -0500 Subject: [PATCH 2/9] automatically use terminal-truecolor if $TERM == xterm --- lib/rouge/cli.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/rouge/cli.rb b/lib/rouge/cli.rb index c167b9be3a..97b5139caa 100644 --- a/lib/rouge/cli.rb +++ b/lib/rouge/cli.rb @@ -200,9 +200,14 @@ def self.doc yield %[ delimiters. implies --escape] end + # TODO: find a better way to do this? + def self.supports_truecolor? + ENV['TERM'] == 'xterm' + end + def self.parse(argv) opts = { - :formatter => 'terminal256', + :formatter => supports_truecolor? ? 'terminal-truecolor' : 'terminal256', :theme => 'thankful_eyes', :css_class => 'codehilite', :input_file => '-', @@ -299,9 +304,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 '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) From 5ce4617d72f90dc40eec75d93ebb77f16ed2b5c0 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Sat, 25 Jan 2020 23:47:08 -0500 Subject: [PATCH 3/9] cache the style string --- lib/rouge/formatters/terminal_truecolor.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/rouge/formatters/terminal_truecolor.rb b/lib/rouge/formatters/terminal_truecolor.rb index a16beee287..9eaeb70785 100644 --- a/lib/rouge/formatters/terminal_truecolor.rb +++ b/lib/rouge/formatters/terminal_truecolor.rb @@ -5,10 +5,13 @@ class TerminalTruecolor < Terminal256 class TruecolorEscapeSequence < Terminal256::EscapeSequence def style_string - 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] + @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) From c32aa88d7d2783c97d10dff955b0ced5d120d3e7 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Sat, 25 Jan 2020 23:48:21 -0500 Subject: [PATCH 4/9] better error message --- lib/rouge/formatters/terminal_truecolor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rouge/formatters/terminal_truecolor.rb b/lib/rouge/formatters/terminal_truecolor.rb index 9eaeb70785..5813733119 100644 --- a/lib/rouge/formatters/terminal_truecolor.rb +++ b/lib/rouge/formatters/terminal_truecolor.rb @@ -21,7 +21,7 @@ def get_rgb(color) 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 'oh no' + raise "invalid color: #{color.inspect}" end end end From c877390053458d996d1d72b5ca923f8ff9ade892 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Sun, 26 Jan 2020 00:17:22 -0500 Subject: [PATCH 5/9] add support for the $COLORTERM variable which appears to be in use someplaces? --- lib/rouge/cli.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rouge/cli.rb b/lib/rouge/cli.rb index 97b5139caa..9d80070723 100644 --- a/lib/rouge/cli.rb +++ b/lib/rouge/cli.rb @@ -202,7 +202,7 @@ def self.doc # TODO: find a better way to do this? def self.supports_truecolor? - ENV['TERM'] == 'xterm' + ENV['TERM'] == 'xterm' || %w(24bit truecolor).include?(ENV['COLORTERM']) end def self.parse(argv) From deb61488be003d7b6deb84654a233a164c6bc5a8 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Sun, 26 Jan 2020 08:30:41 -0500 Subject: [PATCH 6/9] add boilerplate to the top of the file --- lib/rouge/formatters/terminal_truecolor.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/rouge/formatters/terminal_truecolor.rb b/lib/rouge/formatters/terminal_truecolor.rb index 5813733119..07feab07b0 100644 --- a/lib/rouge/formatters/terminal_truecolor.rb +++ b/lib/rouge/formatters/terminal_truecolor.rb @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- # +# frozen_string_literal: true module Rouge module Formatters class TerminalTruecolor < Terminal256 From 9d151eb5705f5e4555097ff0b09dfb169092da8a Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Sun, 26 Jan 2020 08:36:00 -0500 Subject: [PATCH 7/9] add a spec --- spec/formatters/terminal_truecolor_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 spec/formatters/terminal_truecolor_spec.rb 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 From e53c649c1209301189b38f5ad49500cebcc9da1a Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Sun, 26 Jan 2020 21:56:42 -0500 Subject: [PATCH 8/9] more robust truecolor checking --- lib/rouge/cli.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/rouge/cli.rb b/lib/rouge/cli.rb index 9d80070723..2dad7eb4ab 100644 --- a/lib/rouge/cli.rb +++ b/lib/rouge/cli.rb @@ -200,9 +200,17 @@ def self.doc yield %[ delimiters. implies --escape] end - # TODO: find a better way to do this? + # 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? - ENV['TERM'] == 'xterm' || %w(24bit truecolor).include?(ENV['COLORTERM']) + 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) From 260dc3dbab633fe0af52181c15f1664ff9f9ccae Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 27 Jan 2020 00:19:16 -0500 Subject: [PATCH 9/9] add in missing `require 'rbconfig'` --- lib/rouge/cli.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/rouge/cli.rb b/lib/rouge/cli.rb index 2dad7eb4ab..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