Skip to content
Closed
158 changes: 51 additions & 107 deletions cpp-terminal/terminal.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,29 @@
#include <string>
#include <vector>
#include <thread>
#define CTRL_KEY(k) (char)(((unsigned char)(k) & 0x1f))
#define ALT_KEY(k) (char)(((unsigned char)(k) + 0x80))
#define CTRL_KEY(k) (char)((unsigned char)(k) & 0x1f)
#define ALT_KEY(k) (char)((unsigned char)(k) + 0x80)

#define cursor_off "\x1b[?25l"
#define cursor_on "\x1b[?25h"
// If an attempt is made to move the cursor out of the window, the result is
// undefined.
#define move_cursor(row,col) "\x1b[" + std::to_string(row) + ";" + std::to_string(col) + "H"
// If an attempt is made to move the cursor to the right of the right margin,
// the cursor stops at the right margin.
#define move_cursor_right(col) "\x1b[" + std::to_string(col) + "C"
// If an attempt is made to move the cursor below the bottom margin, the cursor
// stops at the bottom margin.
#define move_cursor_down(row) "\x1b[" + std::to_string(row) + "B"
#define cursor_position_report "\x1b[6n"
#define erase_to_eol "\x1b[K"
#define color(value) std::string("\033[") + std::to_string((int)value) + "m"
#define restore_screen_code "\033[?1049l"
#define save_screen_code "\033[?1049h"
#define restore_cursor_pos "\033" "8"
#define save_cursor_pos "\033" "7"
#define ansi_esc '\x1b'
#define ansi_carriagereturn '\x0d'

namespace Term {

Expand Down Expand Up @@ -83,53 +104,6 @@ enum class bgB {
gray = 107
};

template <typename T>
std::string color(T const value)
{
return "\033[" + std::to_string(static_cast<int>(value)) + "m";
}

inline std::string cursor_off()
{
return "\x1b[?25l";
}

inline std::string cursor_on()
{
return "\x1b[?25h";
}

// If an attempt is made to move the cursor out of the window, the result is
// undefined.
inline std::string move_cursor(size_t row, size_t col)
{
return "\x1b[" + std::to_string(row) + ";" + std::to_string(col) + "H";
}

// If an attempt is made to move the cursor to the right of the right margin,
// the cursor stops at the right margin.
inline std::string move_cursor_right(int col)
{
return "\x1b[" + std::to_string(col) + "C";
}

// If an attempt is made to move the cursor below the bottom margin, the cursor
// stops at the bottom margin.
inline std::string move_cursor_down(int row)
{
return "\x1b[" + std::to_string(row) + "B";
}

inline std::string cursor_position_report()
{
return "\x1b[6n";
}

inline std::string erase_to_eol()
{
return "\x1b[K";
}

enum Key {
BACKSPACE = 1000,
ENTER,
Expand Down Expand Up @@ -179,22 +153,19 @@ class Terminal: public BaseTerminal {
void restore_screen()
{
if (restore_screen_) {
write("\033[?1049l"); // restore screen
write("\033" "8"); // restore current cursor position
std::cout << restore_screen_code
<< restore_cursor_pos
<< std::flush;
restore_screen_ = false;
}
}

void save_screen()
{
restore_screen_ = true;
write("\033" "7"); // save current cursor position
write("\033[?1049h"); // save screen
}

static inline void write(const std::string& s)
{
std::cout << s << std::flush;
std::cout << save_cursor_pos
<< save_screen_code
<< std::flush;
}

// Waits for a key press, translates escape codes
Expand All @@ -217,7 +188,7 @@ class Terminal: public BaseTerminal {
if (!read_raw(&c))
return 0;

if (c == '\x1b') {
if (c == ansi_esc) {
char seq[4];

if (!read_raw(&seq[0]))
Expand All @@ -227,7 +198,7 @@ class Terminal: public BaseTerminal {
// gnome-term, Windows Console
return ALT_KEY(seq[0]);
}
if (seq[0] == '\x0d') {
if (seq[0] == ansi_carriagereturn) {
// gnome-term
return Key::ALT_ENTER;
}
Expand Down Expand Up @@ -384,61 +355,34 @@ class Terminal: public BaseTerminal {
void get_cursor_position(int& rows, int& cols) const
{
char buf[32];
unsigned int i = 0;
write(cursor_position_report());
while (i < sizeof(buf) - 1) {
while (!read_raw(&buf[i])) {
};
std::cout << cursor_position_report
<< std::endl;
for (unsigned int i = 0; i < sizeof(buf) -1; i++) {
while (!read_raw(&buf[i]));
if (buf[i] == 'R')
{
if (i < 5)
throw std::runtime_error("get_cursor_position(): too short response");
else
buf[i] = '\0';
break;
i++;
}
buf[i] = '\0';
if (i < 5) {
throw std::runtime_error("get_cursor_position(): too short response");
}
}
// Find the result in the response, drop the rest:
i = 0;
while (i < sizeof(buf) - 1 - 5) {
for (unsigned int i = 0; i < sizeof(buf) - 6; i++) {
if (buf[i] == '\x1b' && buf[i+1] == '[') {
if (convert_string_to_int(&buf[i+2], "%d;%d", &rows, &cols) == 2) {
return;
} else {
if (convert_string_to_int(&buf[i+2], "%d;%d", &rows, &cols) != 2) {
throw std::runtime_error("get_cursor_position(): result could not be parsed");
}
return;
}
if (buf[i] == '\0') break;
i++;
if (buf[i] == '\0')
break;
}
throw std::runtime_error("get_cursor_position(): result not found in the response");
}

// This function takes about 23ms, so it should only be used as a fallback
void get_term_size_slow(int& rows, int& cols) const
{
struct CursorOff {
const Terminal& term;
explicit CursorOff(const Terminal& term)
: term{ term }
{
Term::Terminal::write(cursor_off());
}
~CursorOff()
{
Term::Terminal::write(cursor_on());
}
};
CursorOff cursor_off(*this);
int old_row, old_col;
get_cursor_position(old_row, old_col);
write(move_cursor_right(999) + move_cursor_down(999));
get_cursor_position(rows, cols);
write(move_cursor(old_row, old_col));
}
};



/*----------------------------------------------------------------------------*/

#define UTF8_ACCEPT 0
Expand Down Expand Up @@ -512,7 +456,7 @@ inline std::u32string utf8_to_utf32(const std::string &s)
if (state == UTF8_ACCEPT) {
r.push_back(codepoint);
}
if (state == UTF8_REJECT) {
else if (state == UTF8_REJECT) {
throw std::runtime_error("Invalid byte in UTF8 encoded string");
}
}
Expand Down Expand Up @@ -678,7 +622,7 @@ class Window

std::string render() {
std::string out;
out.append(cursor_off());
out.append(cursor_off);
fg current_fg = fg::reset;
bg current_bg = bg::reset;
style current_style = style::reset;
Expand Down Expand Up @@ -718,7 +662,7 @@ class Window
if (current_fg != fg::reset) out.append(color(fg::reset));
if (current_bg != bg::reset) out.append(color(bg::reset));
if (current_style != style::reset) out.append(color(style::reset));
out.append(cursor_on());
out.append(cursor_on);
return out;
}
};
Expand All @@ -736,15 +680,15 @@ struct Model
inline std::string render(const Model &m, int prompt_row, int term_cols)
{
std::string out;
out = cursor_off();
out = cursor_off;
out += move_cursor(prompt_row, 1) + m.prompt_string + m.input;
size_t last_col = m.prompt_string.size() + m.input.size();
for (size_t i=0; i < term_cols-last_col; i++) {
out.append(" ");
}
out.append(move_cursor(prompt_row+m.cursor_row-1,
m.prompt_string.size() + m.cursor_col));
out.append(cursor_on());
out.append(cursor_on);
return out;
}

Expand Down
59 changes: 25 additions & 34 deletions cpp-terminal/terminal_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,28 @@
# include <cerrno>
#endif

// Returns true if the standard input is attached to a terminal
#ifdef _WIN32
#define is_stdin_a_tty _isatty(_fileno(stdin))
#else
#define is_stdin_a_tty isatty(STDIN_FILENO)
#endif
// Returns true if the standard output is attached to a terminal
#ifdef _WIN32
#define is_stdout_a_tty _isatty(_fileno(stdout)
#else
#define is_stdout_a_tty isatty(STDOUT_FILENO)
#endif

// coverts a string into an integer
#ifdef _WIN32
// windows provides it's own alternative to sscanf()
#define convert_string_to_int(string, format, rows, cols) sscanf_s(string, format, rows, cols)
#else
// TODO move to a better way
#define convert_string_to_int(string, format, rows, cols) sscanf(string, format, rows, cols)
#endif

namespace Term {

/* Note: the code that uses Terminal must be inside try/catch block, otherwise
Expand Down Expand Up @@ -108,6 +130,7 @@ class BaseTerminal {
throw std::runtime_error("SetConsoleMode() failed");
}
}
}
#else
explicit BaseTerminal(bool enable_keyboard=false, bool disable_ctrl_c=true)
: keyboard_enabled{enable_keyboard}
Expand Down Expand Up @@ -232,42 +255,10 @@ class BaseTerminal {
rows = ws.ws_row;
return true;
}
#endif
}

// Returns true if the standard input is attached to a terminal
static bool is_stdin_a_tty()
{
#ifdef _WIN32
return _isatty(_fileno(stdin));
#else
return isatty(STDIN_FILENO);
#endif
}

// Returns true if the standard output is attached to a terminal
static bool is_stdout_a_tty()
{
#ifdef _WIN32
return _isatty(_fileno(stdout));
#else
return isatty(STDOUT_FILENO);
#endif
}

// coverts a string into an integer
static int convert_string_to_int(const char *string, const char *format, int* rows, int* cols)
{
#ifdef _WIN32
// windows provides it's own alternative to sscanf()
return sscanf_s(string, format, rows, cols);
#else
// TODO move to a better way
return sscanf(string, format, rows, cols);
#endif
}
};

} // namespace Term

#endif // TERMINAL_BASE_H
#endif
// TERMINAL_BASE_H
8 changes: 3 additions & 5 deletions examples/colors.cpp
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
#include <cpp-terminal/terminal.h>

using Term::Terminal;
using Term::color;
using Term::fg;
using Term::bg;
using Term::style;

int main() {
try {
Terminal term;
if (Term::Terminal::is_stdout_a_tty()) {
if (is_stdout_a_tty) {
std::cout << "Standard output is attached to a terminal."
<< std::endl;
<< std::endl;
} else {
std::cout << "Standard output is not attached to a terminal."
<< std::endl;
}
std::string text = "Some text with "
+ color(fg::red) + color(bg::green) + "red on green"
+ color(fg::red) + color(bg::green) + std::string("red on green")
+ color(bg::reset) + color(fg::reset) + " and some "
+ color(style::bold) + "bold text" + color(style::reset) + ".\n";
text += "Unicode works too: originally written by Ondřej Čertík.";
Expand Down
Loading