Skip to content

Commit

Permalink
[screen modes] fix: explicit handling of screen modes
Browse files Browse the repository at this point in the history
Escape sequences that set screen modes were not handled
Now they are explicitly handled, and ignored.
  • Loading branch information
masukomi committed Oct 8, 2022
1 parent 4ccf064 commit 34445eb
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 15 deletions.
1 change: 1 addition & 0 deletions .changelog_entries/2e97ab2c3597636c8da399451aa57812.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"type":"Added","tickets":[],"description":"added a CHANGELOG.md file","tags":[]}
1 change: 1 addition & 0 deletions .changelog_entries/34e99410c2fe5f6f718294dda8a5c098.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"type":"Fixed","tickets":[],"description":"fixed indexing error when escape code included no integers","tags":[]}
1 change: 1 addition & 0 deletions .changelog_entries/9a8c11ee5c165b78734f8293c31ec0a3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"type":"Added","tickets":[],"description":"added specific non-handling of screen modes","tags":[]}
55 changes: 55 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
## [1.3.6] - 2022-10-08
### Added
* added a CHANGELOG.md file
* added specific non-handling of screen modes

### Fixed
* fixed indexing error when escape code included no integers

## [1.3.5] - 2021-02-05
### Changed
* Output no longer includes useless spans.

### Fixed
* better handling of non-display code amidst display ones

## [1.3.4] - 2021-02-05
### Changed
* now supports building under Crystal v0.35.1

### Fixed
* fixed missing end span edge case

## [1.3.3] - 2018-10-01
## [1.3.2] - 2018-10-01
### Fixed
* Corrected bug where the ends of escape sequences were not being correctly detected

## [1.3.1] - 2018-09-03
### Deprecated
* [\[4\]](https://github.com/masukomi/oho/issues/4) corrects detection and handling of rgb color escape sequences

## [1.3.0] - 2018-06-15
### Added
* Added Support for ITU's T.416 / 8613-6 color codes

## [1.2.0] - 2018-06-05
### Added
* Added support for customer styling via -s arg

### Fixed
* further corrections to default background and foreground handling

## [1.1.1] - 2018-06-05
### Fixed
* Corrected problem where requesting --help resulted in double output
* Corrected background and foreground color handling

## [1.1.0] - 2018-05-26
### Added
* Added print media CSS for better PDF conversions

### Fixed
* simplified build.sh to remove some git complications

## [1.0.0] - 2018-05-25
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,19 @@ crystal build src/oho.cr
An `oho` executable will be created in the current directory. Just move that
into your PATH and follow the Usage instructions.


## Caveats
There are many, _many_ escape sequences that are used in terminals. Oho supports ANSI 3/4 bit (basic and high intensity), 8 bit,
& 24 bit color codes as well as ITU's T.416 / 8613-6 color codes.

"Screen mode" escape sequences are _not_ supported. In general, this isn't going to be an issue.
For example: `^[=5;7h` would tell a terminal to render as 320 x 200 in Black & White mode.
To support that would require reformatting your text, and making a judgement call about which
colors should be converted to black, and which to white. Currently oho does not address
formatting issues or make judgement calls about colors. If, however, you feel like implementing this,
Pull Requests will be happily accepted.


## Development

If you're adding new functionality or fixing a bug in existing functionality
Expand Down
35 changes: 35 additions & 0 deletions spec/oho/color_escape_code_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,41 @@ describe Oho::ColorEscapeCode do
"</span><span style=\"font-weight: bold; opacity: 0.5; font-style: italic; text-decoration: underline; display: none; background-color: aqua; \">"))
end

it "should not blow up when it encounters private use codes" do
ec = Oho::ColorEscapeCode.new("[?1h", default_options) #background
ec.to_span(nil).should(eq(""))
end

it "should not blow up when it encounters ? screen mode codes" do
ec = Oho::ColorEscapeCode.new("[?7h", default_options) #background
ec.to_span(nil).should(eq(""))
end

it "should not blow up when it encounters = screen mode codes" do
ec = Oho::ColorEscapeCode.new("[=1;7h", default_options) #background
ec.to_span(nil).should(eq(""))
end

it "should not blow up when it encounters = screen mode codes 2" do
ec = Oho::ColorEscapeCode.new("[=0h", default_options) #background
ec.to_span(nil).should(eq(""))
end

it "should not blow up when it encounters ? screen mode reset codes" do
ec = Oho::ColorEscapeCode.new("[?7l", default_options) #background
ec.to_span(nil).should(eq(""))
end

it "should not blow up when it encounters = screen mode reset codes" do
ec = Oho::ColorEscapeCode.new("[=1;7l", default_options) #background
ec.to_span(nil).should(eq(""))
end

it "should not blow up when it encounters = screen mode reset codes 2" do
ec = Oho::ColorEscapeCode.new("[=0l", default_options) #background
ec.to_span(nil).should(eq(""))
end

it "0 resets all" do
ec = Oho::ColorEscapeCode.new("[0m", default_options) #reset all the things
prior_ec = Oho::ColorEscapeCode.new("[1;2;3;4;8;36;46m", default_options)
Expand Down
21 changes: 21 additions & 0 deletions spec/oho/converter_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,27 @@ describe Oho::Converter do
test_string = "\033[36mfoo\033[Kbar\033[0m"
response, escape_code = c.process(test_string, nil)
response.should(eq("<span style=\"color: aqua; \">foobar</span>"))
end

it "ignores screen mode sequences" do
c = Oho::Converter.new(default_options)
test_string = "\033[=1;7hfoo\033[=0l"
response, escape_code = c.process(test_string, nil)
response.should(eq("foo"))
end

it "really ignores screen mode sequences" do
c = Oho::Converter.new(default_options)
test_string="\033[?1h\033=\r\033[33mcommit abcd\r\033[K\033[?1l\033>"
response, escape_code = c.process(test_string, nil)
response.should(eq("\r<span style=\"color: yellow; \">commit abcd\r"))
end

it "ignores question mark screen mode sequences" do
c = Oho::Converter.new(default_options)
test_string = "\033[?7hfoo\033[?7l"
response, escape_code = c.process(test_string, nil)
response.should(eq("foo"))
end

it "removes empty spans that do nothing" do
Expand All @@ -44,6 +64,7 @@ describe Oho::Converter do
response, escape_code = c.process(test_string, nil)
response.should(eq("foo"))
end

it "removes spans that encapsulate nothing" do
c = Oho::Converter.new(default_options)
test_string = "\033[0mfoo\033[0m"
Expand Down
77 changes: 62 additions & 15 deletions src/oho/color_escape_code.cr
Original file line number Diff line number Diff line change
Expand Up @@ -373,18 +373,26 @@ module Oho
getter background_color
getter styles : Array(Int32)
getter string
getter ignorable

@foreground_color : String?
@background_color : String?
@styles : Array(Int32)
@ignorable : Bool

def initialize(@string : String, @options : Hash(Symbol, String))
if @string.size < 3 || @string[0] != '[' || @string[-1] != 'm'
if @string != "[m"
raise InvalidEscapeCode.new("Invalid escape code: #{@string.split("").inspect}")
else
@ignorable = false
if @string.size < 3 || @string[0] != '[' || @string[-1] != 'm' || @string[1] == '?'
if @string == "[m"
# really? was it so hard to write a damn zero?
@string = "[0m"
elsif @string[1] == '?' || is_screen_mode?(@string)
#argh. ? marks it as "private use"
# (a category set aside for implementation-specific features in the standard)
# "screen mode" sequences are not supported.
@ignorable = true
else
raise InvalidEscapeCode.new("Invalid escape code: #{@string.split("").inspect}")
end
end
# if the string has a zero code in it that isn't part of
Expand All @@ -405,21 +413,27 @@ module Oho
# end
# end
# hell_regexp=".*(?<![34]8;5#{rgb})[;\[](0[;m])"
hell_regexp=".*(?<![34]8;5|[34]8;2;\\d|[34]8;2;\\d;\\d|[34]8;2;\\d;\\d;\\d|[34]8;2;\\d;\\d;\\d\\d|[34]8;2;\\d;\\d;\\d\\d\\d|[34]8;2;\\d;\\d\\d|[34]8;2;\\d;\\d\\d;\\d|[34]8;2;\\d;\\d\\d;\\d\\d|[34]8;2;\\d;\\d\\d;\\d\\d\\d|[34]8;2;\\d;\\d\\d\\d|[34]8;2;\\d;\\d\\d\\d;\\d|[34]8;2;\\d;\\d\\d\\d;\\d\\d|[34]8;2;\\d;\\d\\d\\d;\\d\\d\\d|[34]8;2;\\d\\d|[34]8;2;\\d\\d;\\d|[34]8;2;\\d\\d;\\d;\\d|[34]8;2;\\d\\d;\\d;\\d\\d|[34]8;2;\\d\\d;\\d;\\d\\d\\d|[34]8;2;\\d\\d;\\d\\d|[34]8;2;\\d\\d;\\d\\d;\\d|[34]8;2;\\d\\d;\\d\\d;\\d\\d|[34]8;2;\\d\\d;\\d\\d;\\d\\d\\d|[34]8;2;\\d\\d;\\d\\d\\d|[34]8;2;\\d\\d;\\d\\d\\d;\\d|[34]8;2;\\d\\d;\\d\\d\\d;\\d\\d|[34]8;2;\\d\\d;\\d\\d\\d;\\d\\d\\d|[34]8;2;\\d\\d\\d|[34]8;2;\\d\\d\\d;\\d|[34]8;2;\\d\\d\\d;\\d;\\d|[34]8;2;\\d\\d\\d;\\d;\\d\\d|[34]8;2;\\d\\d\\d;\\d;\\d\\d\\d|[34]8;2;\\d\\d\\d;\\d\\d|[34]8;2;\\d\\d\\d;\\d\\d;\\d|[34]8;2;\\d\\d\\d;\\d\\d;\\d\\d|[34]8;2;\\d\\d\\d;\\d\\d;\\d\\d\\d|[34]8;2;\\d\\d\\d;\\d\\d\\d|[34]8;2;\\d\\d\\d;\\d\\d\\d;\\d|[34]8;2;\\d\\d\\d;\\d\\d\\d;\\d\\d|[34]8;2;\\d\\d\\d;\\d\\d\\d;\\d\\d\\d)[;[](0[;m])"
cleansed_string = @string.sub( /#{hell_regexp}/, "\\1")
@styles = extract_styling(cleansed_string)
@background_color = extract_background_color(cleansed_string)
if @background_color == "USE_DEFAULT"
@background_color = @options.fetch(:background_color, "initial")
end
@foreground_color = extract_foreground_color(cleansed_string)
if @foreground_color == "USE_DEFAULT"
@foreground_color = @options.fetch(:foreground_color, "initial")
if ! @ignorable
hell_regexp=".*(?<![34]8;5|[34]8;2;\\d|[34]8;2;\\d;\\d|[34]8;2;\\d;\\d;\\d|[34]8;2;\\d;\\d;\\d\\d|[34]8;2;\\d;\\d;\\d\\d\\d|[34]8;2;\\d;\\d\\d|[34]8;2;\\d;\\d\\d;\\d|[34]8;2;\\d;\\d\\d;\\d\\d|[34]8;2;\\d;\\d\\d;\\d\\d\\d|[34]8;2;\\d;\\d\\d\\d|[34]8;2;\\d;\\d\\d\\d;\\d|[34]8;2;\\d;\\d\\d\\d;\\d\\d|[34]8;2;\\d;\\d\\d\\d;\\d\\d\\d|[34]8;2;\\d\\d|[34]8;2;\\d\\d;\\d|[34]8;2;\\d\\d;\\d;\\d|[34]8;2;\\d\\d;\\d;\\d\\d|[34]8;2;\\d\\d;\\d;\\d\\d\\d|[34]8;2;\\d\\d;\\d\\d|[34]8;2;\\d\\d;\\d\\d;\\d|[34]8;2;\\d\\d;\\d\\d;\\d\\d|[34]8;2;\\d\\d;\\d\\d;\\d\\d\\d|[34]8;2;\\d\\d;\\d\\d\\d|[34]8;2;\\d\\d;\\d\\d\\d;\\d|[34]8;2;\\d\\d;\\d\\d\\d;\\d\\d|[34]8;2;\\d\\d;\\d\\d\\d;\\d\\d\\d|[34]8;2;\\d\\d\\d|[34]8;2;\\d\\d\\d;\\d|[34]8;2;\\d\\d\\d;\\d;\\d|[34]8;2;\\d\\d\\d;\\d;\\d\\d|[34]8;2;\\d\\d\\d;\\d;\\d\\d\\d|[34]8;2;\\d\\d\\d;\\d\\d|[34]8;2;\\d\\d\\d;\\d\\d;\\d|[34]8;2;\\d\\d\\d;\\d\\d;\\d\\d|[34]8;2;\\d\\d\\d;\\d\\d;\\d\\d\\d|[34]8;2;\\d\\d\\d;\\d\\d\\d|[34]8;2;\\d\\d\\d;\\d\\d\\d;\\d|[34]8;2;\\d\\d\\d;\\d\\d\\d;\\d\\d|[34]8;2;\\d\\d\\d;\\d\\d\\d;\\d\\d\\d)[;[](0[;m])"
cleansed_string = @string.sub( /#{hell_regexp}/, "\\1")
@styles = extract_styling(cleansed_string)
@background_color = extract_background_color(cleansed_string)
if @background_color == "USE_DEFAULT"
@background_color = @options.fetch(:background_color, "initial")
end
@foreground_color = extract_foreground_color(cleansed_string)
if @foreground_color == "USE_DEFAULT"
@foreground_color = @options.fetch(:foreground_color, "initial")
end
else # @ignorable == true
@foreground_color="USE_DEFAULT"
@background_color="USE_DEFAULT"
@styles=extract_styling("")
end
end

def affects_display?() : Bool
true
@ignorable ? false : true
end
# we take in the prior escape_code
# in part to know we have to end the prior code
Expand All @@ -430,6 +444,7 @@ module Oho
# give the headaches to the browser and anyone
# reading the source. Headaches are bad.
def to_span(escape_code : EscapeCode?) : String
return "" if @ignorable
span = String.build do |str|
if ! escape_code.nil? && escape_code.as(EscapeCode).affects_display?
str << "</span>"
Expand Down Expand Up @@ -470,6 +485,38 @@ module Oho
def raw : String
@string
end

# Screen Modes
# Documented for future work, but not directly supported.
# ESC[=#;7h or
# ESC[=h or
# ESC[=0h or
# ESC[?7h
# put screen in indicated mode where # is:
# 0 40 x 25 black & white
# 1 40 x 25 color
# 2 80 x 25 b&w
# 3 80 x 25 color
# 4 320 x 200 color graphics
# 5 320 x 200 b & w graphics
# 6 640 x 200 b & w graphics
# 7 to wrap at end of line
# RESETTING....
# ESC[=#;7l or
# ESC[=l or
# ESC[=0l or
# ESC[?7l
# resets mode # set with above command
private def is_screen_mode?(sequence : String ) : Bool

# they start with [=Xh or [?7h
# they end with [=xl or [?7l
# start screen mode
return true if /^\[=\d?(?:h|l)$/ =~ @string
return true if /^\[?7(?:h|l)/ =~ @string
return true if /^\[=\d;7(?:h|l)/ =~ @string
false
end
private def generate_foreground_string(escape_code : EscapeCode?) : String
if ! foreground_color.nil? && foreground_color != ""
return "color: #{foreground_color}; "
Expand Down

0 comments on commit 34445eb

Please sign in to comment.