Skip to content

Commit 02a8c46

Browse files
committed
add option to remove (common) ansi escape codes from on_write
1 parent 9dcc218 commit 02a8c46

File tree

5 files changed

+127
-7
lines changed

5 files changed

+127
-7
lines changed

CMakeLists.txt

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@ if (DEFINED COMMANDLINE_SHARED_LIBS)
2727
set(BUILD_SHARED_LIBS "${COMMANDLINE_SHARED_LIBS}")
2828
endif ()
2929

30-
add_library(commandline
31-
${COMMANDLINE_LIBTYPE}
32-
src/impls.h
30+
set(COMMANDLINE_SRCS src/impls.h
3331
src/windows_impl.cpp
3432
src/linux_impl.cpp
3533
src/backends/InteractiveBackend.cpp
@@ -39,17 +37,24 @@ add_library(commandline
3937
src/commandline.h
4038
src/commandline.cpp
4139
src/backends/BufferedBackend.cpp
42-
src/backends/BufferedBackend.h)
40+
src/backends/BufferedBackend.h
41+
src/helper/ansi.h
42+
src/helper/ansi.cpp)
43+
44+
add_library(commandline
45+
${COMMANDLINE_LIBTYPE}
46+
${COMMANDLINE_SRCS})
4347

4448
add_library(commandline::commandline ALIAS commandline)
4549

50+
target_compile_definitions(commandline PRIVATE -DDOCTEST_CONFIG_DISABLE=1)
4651
target_link_libraries(commandline Threads::Threads)
4752
if (${COMMANDLINE_PLATFORM_WINDOWS})
4853
target_compile_definitions(commandline PRIVATE -DPLATFORM_WINDOWS=1)
4954
elseif (${COMMANDLINE_PLATFORM_LINUX})
5055
target_compile_definitions(commandline PRIVATE -DPLATFORM_LINUX=1)
5156
endif ()
52-
target_include_directories(commandline PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
57+
target_include_directories(commandline PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src" "${CMAKE_CURRENT_SOURCE_DIR}/tests")
5358

5459
option(BUILD_EXAMPLES "Build example program" ON)
5560

@@ -64,3 +69,14 @@ if (BUILD_EXAMPLES)
6469
set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT commandline_test)
6570
endif ()
6671

72+
option(COMMANDLINE_TESTS "Build tests" ON)
73+
74+
if (DEFINED COMMANDLINE_TESTS)
75+
add_executable(commandline_tests tests/tests.cpp ${COMMANDLINE_SRCS})
76+
target_include_directories(commandline_tests PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src" "${CMAKE_CURRENT_SOURCE_DIR}/tests")
77+
if (${COMMANDLINE_PLATFORM_WINDOWS})
78+
target_compile_definitions(commandline_tests PRIVATE -DPLATFORM_WINDOWS=1)
79+
elseif (${COMMANDLINE_PLATFORM_LINUX})
80+
target_compile_definitions(commandline_tests PRIVATE -DPLATFORM_LINUX=1)
81+
endif ()
82+
endif ()

src/commandline.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "backends/BufferedBackend.h"
44
#include "backends/InteractiveBackend.h"
5+
#include "helper/ansi.h"
56
#include "impls.h"
67

78
#include <memory>
@@ -20,7 +21,11 @@ Commandline::Commandline(const std::string& prompt) {
2021
};
2122
m_backend->on_write = [this](const std::string& str) {
2223
if (on_write) {
23-
on_write(str);
24+
if (m_ansi_escape_removal) {
25+
on_write(ansi::remove_ansi_escape_codes(str));
26+
} else {
27+
on_write(str);
28+
}
2429
}
2530
};
2631
m_backend->on_autocomplete = [this](lk::Backend&, std::string str, int n) {

src/commandline.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ class Commandline final {
2525
void enable_key_debug() { m_backend->enable_key_debug(); }
2626
void disable_key_debug() { m_backend->disable_key_debug(); }
2727

28+
/// Enable the removal of ANSI escape codes from all writes before submitting them to the
29+
/// on_write callback. For example, this can be used to remove color codes before getting the
30+
/// string back through the on_write callback to be logged.
31+
/// This is disabled by default.
32+
void enable_ansi_escape_removal_on_write() { m_ansi_escape_removal = true; }
33+
/// Opposite of enable_ansi_escape_for_write.
34+
void disable_ansi_escape_removal_on_write() { m_ansi_escape_removal = false; }
35+
2836
// gets called when a command is ready
2937
std::function<void(Commandline&)> on_command { nullptr };
3038

@@ -36,4 +44,5 @@ class Commandline final {
3644

3745
private:
3846
std::unique_ptr<lk::Backend> m_backend;
39-
};
47+
bool m_ansi_escape_removal = false;
48+
};

src/helper/ansi.cpp

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#include "ansi.h"
2+
#include <string>
3+
4+
enum class State {
5+
Normal,
6+
Escaped,
7+
};
8+
9+
std::string ansi::remove_ansi_escape_codes(const std::string& original) {
10+
std::string new_str {};
11+
new_str.reserve(original.size());
12+
13+
State state = State::Normal;
14+
15+
for (size_t i = 0; i < original.size(); ++i) {
16+
if (state == State::Normal && original[i] == '\x1b') {
17+
state = State::Escaped;
18+
continue;
19+
}
20+
21+
if (state == State::Escaped) {
22+
switch (original[i]) {
23+
case 'A': // Up
24+
case 'B': // Down
25+
case 'C': // Right
26+
case 'D': // Left
27+
case 'E': // Next line
28+
case 'F': // Previous line
29+
case 'G': // Set column
30+
case 'H': // Set cursor position
31+
case 'J': // Erase display
32+
case 'K': // Erase line
33+
case 'T': // Scroll down
34+
case 'f': // Cursor position
35+
case 'm': // Graphics mode
36+
state = State::Normal;
37+
continue;
38+
}
39+
}
40+
41+
if (state == State::Normal) {
42+
new_str.push_back(original[i]);
43+
}
44+
}
45+
46+
return new_str;
47+
}
48+
49+
#include "doctest.h"
50+
51+
TEST_CASE("ansi removal") {
52+
CHECK(ansi::remove_ansi_escape_codes("hello") == "hello");
53+
// Colors
54+
CHECK(ansi::remove_ansi_escape_codes("\x1b[1;2mhello world") == "hello world");
55+
// Cursor up
56+
CHECK(ansi::remove_ansi_escape_codes("\x1b[Ahello\nworld") == "hello\nworld");
57+
// Cursor down
58+
CHECK(ansi::remove_ansi_escape_codes("\x1b[Bfoo bar") == "foo bar");
59+
// Cursor right
60+
CHECK(ansi::remove_ansi_escape_codes("\x1b[Cfoo\nbar") == "foo\nbar");
61+
// Cursor left
62+
CHECK(ansi::remove_ansi_escape_codes("\x1b[Dfoo\tbar") == "foo\tbar");
63+
// Next line
64+
CHECK(ansi::remove_ansi_escape_codes("\x1b[Efoo\rbar") == "foo\rbar");
65+
// Previous line
66+
CHECK(ansi::remove_ansi_escape_codes("\x1b[Fhello world!") == "hello world!");
67+
// Set column
68+
CHECK(ansi::remove_ansi_escape_codes("\x1b[Gfoo bar baz") == "foo bar baz");
69+
// Set cursor position
70+
CHECK(ansi::remove_ansi_escape_codes("\x1b[Hfoo\nbar\nbaz") == "foo\nbar\nbaz");
71+
// Erase display
72+
CHECK(ansi::remove_ansi_escape_codes("\x1b[Jfoo\tbar\tbaz") == "foo\tbar\tbaz");
73+
// Erase line
74+
CHECK(ansi::remove_ansi_escape_codes("\x1b[Kfoo\rbar\rbaz") == "foo\rbar\rbaz");
75+
// Scroll down
76+
CHECK(ansi::remove_ansi_escape_codes("\x1b[Thello\nworld!") == "hello\nworld!");
77+
// Cursor position (alternative)
78+
CHECK(ansi::remove_ansi_escape_codes("\x1b[fhello\tworld!") == "hello\tworld!");
79+
// Graphics mode
80+
CHECK(ansi::remove_ansi_escape_codes("\x1b[mfoo bar baz") == "foo bar baz");
81+
}

src/helper/ansi.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#pragma once
2+
3+
#include <string>
4+
5+
namespace ansi {
6+
7+
std::string remove_ansi_escape_codes(const std::string& input);
8+
9+
}

0 commit comments

Comments
 (0)