Skip to content

Commit ba800ed

Browse files
committed
Fix style offsets
1 parent a1ce1a4 commit ba800ed

20 files changed

Lines changed: 706 additions & 150 deletions

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
strategy:
1414
fail-fast: false
1515
matrix:
16-
ruby: [2.4, 2.5, 2.6, 2.7, ruby-3.0.0-preview1, ruby-head, jruby, jruby-head]
16+
ruby: [2.4, 2.5, 2.6, 2.7, '3.0', 3.1, 3.2, 3.3, 3.4, ruby-head, jruby, jruby-head]
1717
continue-on-error: ${{ endsWith(matrix.ruby, 'head') }}
1818
runs-on: ubuntu-latest
1919
steps:
@@ -47,7 +47,7 @@ jobs:
4747
- uses: actions/checkout@v2
4848
- uses: ruby/setup-ruby@v1
4949
with:
50-
ruby-version: 2.7
50+
ruby-version: 3.4
5151
bundler-cache: true
5252
- run: bundle exec rubocop
5353

@@ -67,7 +67,7 @@ jobs:
6767
- uses: actions/checkout@v2
6868
- uses: ruby/setup-ruby@v1
6969
with:
70-
ruby-version: 2.7
70+
ruby-version: 3.4
7171
bundler-cache: true
7272
- run: bundle exec leftovers
7373

@@ -77,6 +77,6 @@ jobs:
7777
- uses: actions/checkout@v2
7878
- uses: ruby/setup-ruby@v1
7979
with:
80-
ruby-version: 2.7
80+
ruby-version: 3.4
8181
bundler-cache: true
8282
- run: bundle exec rake build

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
/pkg/
44
/.rspec_status
55
/Gemfile.lock
6+
/.ruby-version

.spellr_wordlists/english.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ ansi
22
arity
33
changelog
44
cli
5+
csi
56
gemfile
67
i'll
78
jruby
@@ -23,3 +24,4 @@ sudo
2324
tmp
2425
truffleruby
2526
tty
27+
unstyled

.spellr_wordlists/ruby.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
cov
2+
cyclomatic
23
rspec
34
rubo
45
rubocop

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
# v2.0.0
2+
- TTYString is now a module not a class.
3+
- Address the issue where preserved styles and unknown codes would cause the cursor to be misaligned when moving, and potentially overwriting styles unexpectedly:
4+
- `clear_style: false` is now `style: TTYString::RENDER`, which modifies styles to display as they would rather than just passing them through them unprocessed
5+
- `clear_style: true` is now `style: TTYString::DROP` (and still the default)
6+
- Unknown codes are now dropped by default
7+
- it's now possible to set `unknown: TTYString::RAISE` to raise on unrecognized CSI codes (`unknown: TTYString::DROP` is the default)
8+
- drop more codes that are known to do nothing for the display of text `\e[?5l`,`\e[?5h`,`\e[?25l`,`\e[?25h`,`\e[?1004l`,`\e[?1004h`,`\e[?1049l`,`\e[?1049h`
9+
110
# v1.1.1
211
- i forgot how arity works
312

README.md

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,20 @@ Intended for use in tests of command line interfaces.
3232
| `\e[nK` | _n_=`0`: clear the line from the cursor forward <br>_n_=`1`: clear the line from the cursor backward <br>_n_=`2`: clear the line | _n_=`0` |
3333
| `\e[nS` | scroll up _n_ rows | _n_=`1` |
3434
| `\e[nT` | scroll down _n_ rows | _n_=`1` |
35-
| `\e[m` | styling codes: optionally suppressed with `clear_style: false` | |
36-
| `\e[?2004h` | enabled bracketed paste: suppressed | |
37-
| `\e[?2004l` | disable bracketed paste: suppressed | |
38-
| `\e[200~` | bracketed paste start: suppressed | |
39-
| `\e[201~` | bracketed paste end: suppressed | |
35+
| `\e[m` | styling codes: dropped with `style: :drop` (default), rendered with `style: :render`. | |
36+
| `\e[?5h` | reverse the screen: dropped | |
37+
| `\e[?5l` | normal the screen: dropped | |
38+
| `\e[?25h` | show the cursor: dropped | |
39+
| `\e[?25l` | hide the cursor: dropped | |
40+
| `\e[?1004h` | enable reporting focus: dropped | |
41+
| `\e[?1004l` | disable reporting focus: dropped | |
42+
| `\e[?1049h` | enable alternate screen buffer: dropped | |
43+
| `\e[?1049l` | disable alternate screen buffer: dropped | |
44+
| `\e[?2004h` | enable bracketed paste mode: dropped | |
45+
| `\e[?2004l` | disable bracketed paste mode: dropped | |
46+
| `\e[200~` | bracketed paste start: dropped | |
47+
| `\e[201~` | bracketed paste end: dropped | |
48+
| `\e[` | any other valid CSI code: dropped with `unknown: :drop` (default), raises TTYString::Error with `unknown: :raise`. | |
4049

4150
## Installation
4251

@@ -61,14 +70,19 @@ TTYString.parse("th\ta string\e[3Gis is")
6170
=> "this is a string"
6271
```
6372

64-
Styling information is suppressed by default:
73+
Styling information is dropped by default:
6574
```ruby
6675
TTYString.parse("th\ta \e[31mstring\e[0m\e[3Gis is")
6776
=> "this is a string"
6877
```
69-
But can be passed through:
78+
But can be rendered:
7079
```ruby
71-
TTYString.parse("th\ta \e[31mstring\e[0m\e[3Gis is", clear_style: false)
80+
TTYString.parse("th\ta \e[31mstring\e[0m\e[3Gis is", style: :render)
81+
=> "this is a \e[31mstring\e[0m"
82+
```
83+
84+
```ruby
85+
TTYString.parse("th\ta \e[31mstring\e[0m\e[3Gis is", style: :render)
7286
=> "this is a \e[31mstring\e[0m"
7387
```
7488

@@ -81,10 +95,7 @@ Just for fun TTYString.to_proc provides the `parse` method as a lambda, so:
8195
## Limitations
8296

8397
- Various terminals are wildly variously permissive with what they accept,
84-
so this doesn't even try to cover all possible cases,
85-
instead it covers the narrowest possible case, and leaves the codes in place when unrecognized
86-
87-
- `clear_style: false` treats the style codes as regular text which may work differently when rendering codes that move the cursor.
98+
so this doesn't even try to cover all possible cases
8899

89100
## Development
90101

lib/tty_string.rb

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,33 @@
44

55
# Renders a string taking into ANSI escape codes and \t\r\n etc
66
# Usage: TTYString.parse("This\r\e[KThat") => "That"
7-
class TTYString
7+
module TTYString
8+
class Error < StandardError; end
9+
class UnknownCodeError < Error; end
10+
11+
RENDER = :render
12+
RAISE = :raise
13+
DROP = :drop
14+
15+
UNKNOWN_OPTIONS = [RAISE, DROP].freeze
16+
private_constant :UNKNOWN_OPTIONS
17+
STYLE_OPTIONS = [RENDER, DROP].freeze
18+
private_constant :STYLE_OPTIONS
19+
820
class << self
9-
def parse(input_string, clear_style: true)
10-
new(input_string, clear_style: clear_style).to_s
21+
def parse(input_string, style: DROP, unknown: DROP) # rubocop:disable Metrics/MethodLength
22+
unless STYLE_OPTIONS.include?(style)
23+
raise ArgumentError, '`style:` must be either TTYString::RENDER or TTYString::DROP (default)'
24+
end
25+
unless UNKNOWN_OPTIONS.include?(unknown)
26+
raise ArgumentError, '`unknown:` must be either TTYString::RAISE or TTYString::DROP (default)'
27+
end
28+
29+
Parser.new(input_string).render(style: style, unknown: unknown)
1130
end
1231

1332
def to_proc
1433
method(:parse).to_proc
1534
end
1635
end
17-
18-
def initialize(input_string, clear_style: true)
19-
@parser = Parser.new(input_string)
20-
@parser.clear_style = clear_style
21-
end
22-
23-
def to_s
24-
parser.render
25-
end
26-
27-
private
28-
29-
attr_reader :clear_style, :parser
3036
end

lib/tty_string/cell.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# frozen_string_literal: true
2+
3+
module TTYString
4+
class Cell
5+
attr_reader :style, :value
6+
7+
def initialize(value, style: NullStyle)
8+
@style = style
9+
@value = value
10+
end
11+
12+
def to_s(style_context: NullStyle)
13+
"#{style.to_s(context: style_context)}#{value}"
14+
end
15+
end
16+
end

lib/tty_string/code.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# frozen_string_literal: true
22

3-
class TTYString
3+
module TTYString
44
class Code
55
class << self
66
def descendants

lib/tty_string/code_definitions.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
require_relative 'code'
44
require_relative 'csi_code'
55

6-
class TTYString
6+
module TTYString
77
class Code
88
class SlashA < TTYString::Code # leftovers:allow
99
char "\a"
@@ -32,7 +32,7 @@ class SlashN < TTYString::Code # leftovers:allow
3232
def action
3333
cursor.down
3434
cursor.col = 0
35-
screen.write('')
35+
screen.ensure_row
3636
end
3737
end
3838

0 commit comments

Comments
 (0)