diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml
index de77e41..88ce941 100644
--- a/.github/workflows/test-build.yml
+++ b/.github/workflows/test-build.yml
@@ -46,7 +46,7 @@ jobs:
- name: run tests
timeout-minutes: 2
run: |
- make prove
+ make prove-j2
build-static:
runs-on: ubuntu-latest
container: alpine:latest
@@ -89,7 +89,7 @@ jobs:
- name: run tests [with iTerm2 palettes]
timeout-minutes: 1
run: |
- prove -v tests/*.sh
+ prove -v -j 2 tests/*.sh
- name: static compilation [no iTerm2 palettes] with mingw32-w64
timeout-minutes: 2
run: |
@@ -154,7 +154,7 @@ jobs:
- name: run tests
timeout-minutes: 2
run: |
- make prove
+ make prove-j2
- name: "Compress for ${{matrix.arch}} [with iTerm2 palettes]"
timeout-minutes: 1
run: |
diff --git a/Makefile b/Makefile
index cb83c68..32c7d97 100644
--- a/Makefile
+++ b/Makefile
@@ -93,3 +93,7 @@ run-tests:
.PHONY: prove
prove:
@prove tests/*.sh
+
+.PHONY: prove-j2
+prove-j2:
+ @prove -j 2 tests/*.sh
diff --git a/src/main.c b/src/main.c
index b6f8881..ff65e44 100644
--- a/src/main.c
+++ b/src/main.c
@@ -138,6 +138,8 @@ just_strip_it(bool ignore_sgr_errors)
STATE_TEXT,
STATE_GOT_ESC,
STATE_GOT_ESC_BRACKET,
+ STATE_GOT_ESC_BRACKET_EQUALS,
+ STATE_GOT_ESC_BRACKET_QUESTION,
} state = STATE_TEXT;
char buffer[INPUT_BUFFER_SIZE];
@@ -269,6 +271,19 @@ just_strip_it(bool ignore_sgr_errors)
// Ignore "Erase in Line" sequence.
state = STATE_TEXT;
}
+ else if (c == 'J')
+ {
+ // Ignore "Erase in Display" sequence.
+ state = STATE_TEXT;
+ }
+ else if (c == '=')
+ {
+ state = STATE_GOT_ESC_BRACKET_EQUALS;
+ }
+ else if (c == '?')
+ {
+ state = STATE_GOT_ESC_BRACKET_QUESTION;
+ }
else
{
if (ignore_sgr_errors)
@@ -282,6 +297,42 @@ just_strip_it(bool ignore_sgr_errors)
);
}
}
+ else if (state == STATE_GOT_ESC_BRACKET_EQUALS)
+ {
+ // Read numbers until "h" or "l", ignore everything in between.
+ if (c == 'h' || c == 'l')
+ state = STATE_TEXT;
+ else if (c < '0' || c > '9')
+ {
+ if (ignore_sgr_errors)
+ state = STATE_TEXT;
+ else
+ ERROR(
+ "SGR ]= sequence contains invalid character "
+ "'%c' at %zu characters read / %zu in SGR sequence "
+ "which begun at %zu characters read.\n",
+ c, read, sgr_chars_len, begun_at
+ );
+ }
+ }
+ else if (state == STATE_GOT_ESC_BRACKET_QUESTION)
+ {
+ // Read numbers until "h" or "l", ignore everything in between.
+ if (c == 'h' || c == 'l')
+ state = STATE_TEXT;
+ else if (c < '0' || c > '9')
+ {
+ if (ignore_sgr_errors)
+ state = STATE_TEXT;
+ else
+ ERROR(
+ "SGR ]? sequence contains invalid character "
+ "'%c' at %zu characters read / %zu in SGR sequence "
+ "which begun at %zu characters read.\n",
+ c, read, sgr_chars_len, begun_at
+ );
+ }
+ }
else
{
ERROR("Invalid state %d.\n", (int)state);
@@ -328,6 +379,8 @@ static inline __attribute__((always_inline)) void ansi2html(
STATE_TEXT,
STATE_GOT_ESC,
STATE_GOT_ESC_BRACKET,
+ STATE_GOT_ESC_BRACKET_EQUALS,
+ STATE_GOT_ESC_BRACKET_QUESTION,
} state = STATE_TEXT;
char buffer[INPUT_BUFFER_SIZE];
@@ -534,6 +587,24 @@ static inline __attribute__((always_inline)) void ansi2html(
span_outputted = true;
}
}
+ else if (c == 'J')
+ {
+ // Ignore "Erase in Display" sequence.
+ state = STATE_TEXT;
+ if (span && !span_outputted)
+ {
+ append_to_buffer(span);
+ span_outputted = true;
+ }
+ }
+ else if (c == '=')
+ {
+ state = STATE_GOT_ESC_BRACKET_EQUALS;
+ }
+ else if (c == '?')
+ {
+ state = STATE_GOT_ESC_BRACKET_QUESTION;
+ }
else
{
// The character should be a digit, or semicolon.
@@ -588,6 +659,30 @@ static inline __attribute__((always_inline)) void ansi2html(
);
}
break;
+ case STATE_GOT_ESC_BRACKET_EQUALS:
+ // Read numbers until "h" or "l", ignore everything in between.
+ if (c == 'h' || c == 'l')
+ state = STATE_TEXT;
+ else if (c < '0' || c > '9')
+ ERROR(
+ "SGR ]= sequence contains invalid character "
+ "'%c' at %zu characters read / %zu in SGR sequence "
+ "which begun at %zu characters read.\n",
+ c, read, sgr_chars_len, begun_at
+ );
+ break;
+ case STATE_GOT_ESC_BRACKET_QUESTION:
+ // Read numbers until "h" or "l", ignore everything in between.
+ if (c == 'h' || c == 'l')
+ state = STATE_TEXT;
+ else if (c < '0' || c > '9')
+ ERROR(
+ "SGR ]? sequence contains invalid character "
+ "'%c' at %zu characters read / %zu in SGR sequence "
+ "which begun at %zu characters read.\n",
+ c, read, sgr_chars_len, begun_at
+ );
+ break;
}
}
} while (1);
diff --git a/tests/csi-equals.sh b/tests/csi-equals.sh
new file mode 100755
index 0000000..4ffdb5d
--- /dev/null
+++ b/tests/csi-equals.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+set -e
+
+. "$(dirname "$0")/".functions.sh
+
+# CSI = 0 ... 19 h and CSI = ... l are no-op
+for l in h l; do
+ for k in $(seq 0 19); do
+ str=$(printf 'Hello, \e[0;1mBold\e[=%s%s\e[0m world!' "$k" "$l")
+ want=$'Hello, Bold world!'
+ got=$(printf '%s' "$str" | ./ansi2html -p vga)
+ str_eq_html "$str" "$want" "$got"
+
+ want=$'Hello, Bold world!'
+ got=$(printf '%s' "$str" | ./ansi2html -S)
+ str_eq_html "$str" "$want" "$got"
+ done
+done
+
+done_testing
diff --git a/tests/csi-j.sh b/tests/csi-j.sh
new file mode 100755
index 0000000..722b78c
--- /dev/null
+++ b/tests/csi-j.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+set -e
+
+. "$(dirname "$0")/".functions.sh
+
+# CSI J is a no-op when stripping OR converting to HTML
+str=$'Hello, \e[0;1mBold\e[J\e[0m world!'
+want=$'Hello, Bold world!'
+got=$(printf '%s' "$str" | ./ansi2html -p vga)
+str_eq_html "$str" "$want" "$got"
+
+want=$'Hello, Bold world!'
+got=$(printf '%s' "$str" | ./ansi2html -S)
+str_eq_html "$str" "$want" "$got"
+
+# CSI 0 J is also a no-op, same for CSI 1 J, CSI 2 J and CSI 3 J
+for k in 0 1 2 3; do
+ str=$(printf 'Hello, \e[0;1mBold\e[%sJ\e[0m world!' "$k")
+ want=$'Hello, Bold world!'
+ got=$(printf '%s' "$str" | ./ansi2html -p vga)
+ str_eq_html "$str" "$want" "$got"
+
+ want=$'Hello, Bold world!'
+ got=$(printf '%s' "$str" | ./ansi2html -S)
+ str_eq_html "$str" "$want" "$got"
+done
+
+done_testing
diff --git a/tests/csi-k.sh b/tests/csi-k.sh
index f7fb9c0..08a6215 100755
--- a/tests/csi-k.sh
+++ b/tests/csi-k.sh
@@ -14,4 +14,16 @@ want=$'Hello, Bold world!'
got=$(printf '%s' "$str" | ./ansi2html -S)
str_eq_html "$str" "$want" "$got"
+# CSI 0 K is also a no-op, same for CSI 1 K and CSI 2 K
+for k in 0 1 2; do
+ str=$(printf 'Hello, \e[0;1mBold\e[%sK\e[0m world!' "$k")
+ want=$'Hello, Bold world!'
+ got=$(printf '%s' "$str" | ./ansi2html -p vga)
+ str_eq_html "$str" "$want" "$got"
+
+ want=$'Hello, Bold world!'
+ got=$(printf '%s' "$str" | ./ansi2html -S)
+ str_eq_html "$str" "$want" "$got"
+done
+
done_testing
diff --git a/tests/csi-question.sh b/tests/csi-question.sh
new file mode 100755
index 0000000..fdd0eb5
--- /dev/null
+++ b/tests/csi-question.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+set -e
+
+. "$(dirname "$0")/".functions.sh
+
+# CSI ? ... h and CSI ? ... l are no-op
+for l in h l; do
+ for k in 25 47 1049; do
+ str=$(printf 'Hello, \e[0;1mBold\e[?%s%s\e[0m world!' "$k" "$l")
+ want=$'Hello, Bold world!'
+ got=$(printf '%s' "$str" | ./ansi2html -p vga)
+ str_eq_html "$str" "$want" "$got"
+
+ want=$'Hello, Bold world!'
+ got=$(printf '%s' "$str" | ./ansi2html -S)
+ str_eq_html "$str" "$want" "$got"
+ done
+done
+
+done_testing