diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d48391..ecabdef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change log +## Unreleased + +### Fixed + +* Fix IndexError in `Strings::Wrap.wrap` and correct ANSI color insertion on wrap (@onk) +* Track unclosed ANSI sequences to preserve correct color state across wraps (@onk) + ## [v0.2.1] - 2021-03-09 ### Changed diff --git a/lib/strings/wrap.rb b/lib/strings/wrap.rb index 7dbda07..c4003e4 100644 --- a/lib/strings/wrap.rb +++ b/lib/strings/wrap.rb @@ -135,24 +135,25 @@ def insert_ansi(string, ansi_stack = []) new_stack = [] output = string.dup length = string.size - matched_reset = false + pending_resets = 0 ansi_reset = Strings::ANSI::RESET # Reversed so that string index don't count ansi ansi_stack.reverse_each do |ansi| if ansi[0] =~ /#{Regexp.quote(ansi_reset)}/ - matched_reset = true + pending_resets += 1 output.insert(ansi[1], ansi_reset) next - elsif !matched_reset # ansi without reset - matched_reset = false - new_stack << ansi # keep the ansi + elsif pending_resets.zero? # ansi without reset + new_stack.unshift([ansi[0], 0]) # carry over ANSI to the start of next line preserving order next if ansi[1] == length if output.end_with?(NEWLINE) output.insert(-2, ansi_reset) else output.insert(-1, ansi_reset) # add reset at the end end + else + pending_resets -= 1 end output.insert(ansi[1], ansi[0]) diff --git a/spec/unit/wrap/insert_ansi_spec.rb b/spec/unit/wrap/insert_ansi_spec.rb index 4497da3..15b84d2 100644 --- a/spec/unit/wrap/insert_ansi_spec.rb +++ b/spec/unit/wrap/insert_ansi_spec.rb @@ -60,6 +60,6 @@ val = Strings::Wrap.insert_ansi(text, stack) expect(val).to eq("\e[32mone\e[0m") - expect(stack).to eq([["\e[33m", 3]]) + expect(stack).to eq([["\e[33m", 0]]) end end diff --git a/spec/unit/wrap/wrap_spec.rb b/spec/unit/wrap/wrap_spec.rb index 1fdcc96..8b9e4dd 100644 --- a/spec/unit/wrap/wrap_spec.rb +++ b/spec/unit/wrap/wrap_spec.rb @@ -178,6 +178,23 @@ ].join("\n")) end + it "wraps when ANSI start carries over to the next line" do + text = "aaaaaaa \e[31mbb" + expect(Strings::Wrap.wrap(text, 8)).to eq([ + "aaaaaaa ", + "\e[31mbb\e[0m" + ].join("\n")) + end + + it "applies stacked ANSI colors after wrapping" do + text = "aaaa \e[31mbbbb \e[32mcc" + expect(Strings::Wrap.wrap(text, 5)).to eq([ + "aaaa ", + "\e[31mbbbb \e[0m", + "\e[31m\e[32mcc\e[0m\e[0m" + ].join("\n")) + end + it "applies ANSI codes when below wrap width" do str = "\e[32mone\e[0m\e[33mtwo\e[0m" @@ -194,7 +211,7 @@ expect(val).to eq("\e[32mone\e[0m\n\e[33mtwo\e[0m") end - xit "splits ANSI codes matching wrap width" do + it "splits ANSI codes matching wrap width" do str = "\e[32mone\e[0m\e[33mtwo\e[0m" val = Strings::Wrap.wrap(str, 3) @@ -202,14 +219,14 @@ expect(val).to eq("\e[32mone\e[0m\n\e[33mtwo\e[0m") end - xit "wraps deeply nested ANSI codes correctly" do + it "wraps deeply nested ANSI codes correctly" do str = "\e[32mone\e[33mtwo\e[0m\e[0m" val = Strings::Wrap.wrap(str, 3) expect(val).to eq([ "\e[32mone\e[0m", - "\e[33mtwo\e[0m", + "\e[32m\e[33mtwo\e[0m\e[0m", ].join("\n")) end end