/* 88888888 888888888888 88888888888888 8888888888888888 888888888888888888 888888 8888 888888 88888 88 88888 888888 8888 888888 88888888888888888888 88888888888888888888 8888888888888888888888 8888888888888888888888888888 88888888888888888888888888888888 88888888888888888888 888888888888888888888888 888888 8888888888 888888 888 8888 8888 888 888 888 OCTOBANANA Term 0.1.0-develop Licensed under the MIT License Copyright (c) 2017-2019 Brett Robinson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef OB_TERM_HH #define OB_TERM_HH #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace OB::Term { namespace Key { enum { null = 0, bell = 7, tab = 9, newline = 10, enter = 13, escape = 27, space = 32, backspace = 127, up = 0xF0000, down, left, right, home, end, delete_, insert, page_up, page_down }; } namespace Mouse { enum { null = 0, btn_release = 0xF1000, btn1_press, btn1_release, btn2_press, btn2_release, btn3_press, btn3_release, scroll_up, scroll_down }; } inline int constexpr ctrl_key(int const c) { return (c & 0x1F); } inline char32_t utf8_to_char32(std::string_view str) { if (str.empty()) { return 0; } if ((str.at(0) & 0x80) == 0) { return static_cast(str.at(0)); } else if ((str.at(0) & 0xE0) == 0xC0 && str.size() == 2) { return (static_cast(str[0] & 0x1F) << 6) | static_cast(str[1] & 0x3F); } else if ((str.at(0) & 0xF0) == 0xE0 && str.size() == 3) { return (static_cast(str[0] & 0x0F) << 12) | (static_cast(str[1] & 0x3F) << 6) | static_cast(str[2] & 0x3F); } else if ((str.at(0) & 0xF8) == 0xF0 && str.size() == 4) { return (static_cast(str[0] & 0x07) << 18) | (static_cast(str[1] & 0x3F) << 12) | (static_cast(str[2] & 0x3F) << 6) | static_cast(str[3] & 0x3F); } return 0; } inline char32_t get_key(std::string* str = nullptr) { // NOTE term mode should be in raw state before call to this func char key[4] {0}; long int ec = read(STDIN_FILENO, &key[0], 1); if ((ec == -1) && (errno != EAGAIN)) { throw std::runtime_error("read failed"); } if (ec == 0) { return Key::null; } // utf-8 multibyte code point if (key[0] & 0x80) { std::size_t bytes {0}; for (; bytes < 3; ++bytes) { if (! (key[0] & (0x80 >> (bytes + 1)))) { break; } } if ((ec = read(STDIN_FILENO, &key[1], bytes)) != static_cast(bytes)) { if ((ec == -1) && (errno != EAGAIN)) { throw std::runtime_error("read failed"); } return static_cast(key[0]); } ++bytes; if (str != nullptr) { str->assign(&key[0], bytes); } return utf8_to_char32(std::string_view(&key[0], bytes)); } // utf-8 single-byte code point if (str != nullptr) { str->assign(&key[0], 1); } // esc / esc sequence if (key[0] == Key::escape) { char seq[3] {0}; if ((ec = read(STDIN_FILENO, &seq[0], 1)) != 1) { if ((ec == -1) && (errno != EAGAIN)) { throw std::runtime_error("read failed"); } return static_cast(key[0]); } if ((ec = read(STDIN_FILENO, &seq[1], 1)) != 1) { if ((ec == -1) && (errno != EAGAIN)) { throw std::runtime_error("read failed"); } return static_cast(key[0]); } if (seq[0] == '[') { if (seq[1] >= '0' && seq[1] <= '9') { if ((ec = read(STDIN_FILENO, &seq[2], 1)) != 1) { if ((ec == -1) && (errno != EAGAIN)) { throw std::runtime_error("read failed"); } return static_cast(key[0]); } if (seq[2] == '~') { switch (seq[1]) { case '1': { return Key::home; } case '2': { return Key::insert; } case '3': { return Key::delete_; } case '4': { return Key::end; } case '5': { return Key::page_up; } case '6': { return Key::page_down; } default: { return static_cast(key[0]); } } } } else { switch (seq[1]) { case 'A': { return Key::up; } case 'B': { return Key::down; } case 'C': { return Key::right; } case 'D': { return Key::left; } case '<': { // 1000;1006 mouse event std::size_t constexpr buf_size {64}; char mouse[buf_size] {0}; for (std::size_t i = 0; i < buf_size; ++i) { if ((ec = read(STDIN_FILENO, &mouse[i], 1)) != 1) { if ((ec == -1) && (errno != EAGAIN)) { throw std::runtime_error("read failed"); } return static_cast(key[0]); } if (mouse[i] >= 0x40 && mouse[i] <= 0x7E) { if (str != nullptr) { str->assign(mouse, i + 1); } switch (mouse[i]) { case 'm': { switch (mouse[0]) { case '0': { return Mouse::btn1_release; } case '1': { return Mouse::btn2_release; } case '2': { return Mouse::btn3_release; } default: { break; } } break; } case 'M': { switch (mouse[0]) { case '0': { return Mouse::btn1_press; } case '1': { return Mouse::btn2_press; } case '2': { return Mouse::btn3_press; } case '6': { switch (mouse[1]) { case '4': { return Mouse::scroll_up; } case '5': { return Mouse::scroll_down; } default: { break; } } break; } default: { break; } } break; } default: { break; } } break; } } return static_cast(key[0]); } case 'M': { // 1000 mouse event char mouse[3] {0}; if ((ec = read(STDIN_FILENO, &mouse[0], 3)) != 3) { if ((ec == -1) && (errno != EAGAIN)) { throw std::runtime_error("read failed"); } return static_cast(key[0]); } if (str != nullptr) { str->assign(mouse, 3); } switch (mouse[0] & 0x03) { case 0: { if (mouse[0] & 0x40) { return Mouse::scroll_up; } return Mouse::btn1_press; } case 1: { if (mouse[0] & 0x40) { return Mouse::scroll_down; } return Mouse::btn2_press; } case 2: { return Mouse::btn3_press; } case 3: { return Mouse::btn_release; } default: { break; } } return Mouse::null; } default: { return static_cast(key[0]); } } } } } return static_cast(key[0]); } class Stdin { public: Stdin() { _fp = std::fopen("/dev/tty", "r"); if (! _fp) { throw std::runtime_error("could not open stdin"); } _fd = fileno(_fp); } ~Stdin() { if (_fp) { std::fclose(_fp); } } int fd() { return _fd; } FILE* fp() { return _fp; } private: int _fd {0}; FILE* _fp {nullptr}; }; class Mode { public: Mode() { if (tcgetattr(_stdin.fd(), &_old) == -1) { throw std::runtime_error("tcgetattr failed"); } } ~Mode() { if (! _cooked) { set_cooked(); } } operator bool() { return _cooked; } void set_cooked() { if (_cooked) { return; } if (tcsetattr(_stdin.fd(), TCSAFLUSH, &_old) == -1) { throw std::runtime_error("tcsetattr failed"); } _cooked = true; } void set_raw() { if (! _cooked) { return; } _raw = _old; // _raw.c_iflag &= static_cast(~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON)); // _raw.c_oflag &= static_cast(~(OPOST)); _raw.c_lflag &= static_cast(~(ECHO | ECHONL | ICANON | ISIG | IEXTEN)); _raw.c_cflag &= static_cast(~(CSIZE | PARENB)); _raw.c_cflag |= static_cast(CS8); _raw.c_cc[VMIN] = static_cast(_min); _raw.c_cc[VTIME] = static_cast(_timeout); if (tcsetattr(_stdin.fd(), TCSAFLUSH, &_raw) == -1) { throw std::runtime_error("tcsetattr failed"); } _cooked = false; } void set_timeout(int val = 0) { if (_timeout == val) { return; } _timeout = val; if (_cooked) { return; } _raw.c_cc[VTIME] = static_cast(_timeout); if (tcsetattr(_stdin.fd(), TCSAFLUSH, &_raw) == -1) { throw std::runtime_error("tcsetattr failed"); } } int get_timeout() { return _timeout; } void set_min(int val = 0) { if (_min == val) { return; } _min = val; if (_cooked) { return; } _raw.c_cc[VMIN] = static_cast(_min); if (tcsetattr(_stdin.fd(), TCSAFLUSH, &_raw) == -1) { throw std::runtime_error("tcsetattr failed"); } } int get_min() { return _min; } private: Stdin _stdin; termios _old; termios _raw; bool _cooked {true}; int _timeout {0}; int _min {0}; }; // class Mode inline bool press_to_continue(std::string const& str = "ANY KEY", char32_t val = 0) { std::cerr << "Press " << str << " to continue"; Mode _term_mode; _term_mode.set_min(1); _term_mode.set_raw(); bool res {false}; char32_t key {0}; if ((key = get_key()) > 0) { res = (val == 0 ? true : val == key); } _term_mode.set_cooked(); std::cerr << "\n"; return res; } inline std::string env_var(std::string const& str) { std::string res; if (char const* envar = std::getenv(str.data())) { res = envar; } return res; } inline bool is_term(int fd_) { switch (fd_) { case STDIN_FILENO: return isatty(STDIN_FILENO); case STDOUT_FILENO: return isatty(STDOUT_FILENO); case STDERR_FILENO: return isatty(STDERR_FILENO); default: return false; } } inline bool is_colorterm() { auto const colorterm = env_var("COLORTERM"); if (colorterm == "truecolor" || colorterm == "24bit") { return true; } return false; } inline int width(std::size_t& width_, int fd_ = STDOUT_FILENO) { if (fd_ != STDIN_FILENO && fd_ != STDOUT_FILENO && fd_ != STDERR_FILENO) { return -1; } winsize w; ioctl(fd_, TIOCGWINSZ, &w); width_ = w.ws_col; return 0; } inline int height(std::size_t& height_, int fd_ = STDOUT_FILENO) { if (fd_ != STDIN_FILENO && fd_ != STDOUT_FILENO && fd_ != STDERR_FILENO) { return -1; } winsize w; ioctl(fd_, TIOCGWINSZ, &w); height_ = w.ws_row; return 0; } inline int size(std::size_t& width_, std::size_t& height_, int fd_ = STDOUT_FILENO) { if (fd_ != STDIN_FILENO && fd_ != STDOUT_FILENO && fd_ != STDERR_FILENO) { return -1; } winsize w; ioctl(fd_, TIOCGWINSZ, &w); width_ = w.ws_col; height_ = w.ws_row; return 0; } namespace ANSI_Escape_Codes { // constants std::string const space {" "}; // standard escaped characters std::string const cr {"\r"}; std::string const nl {"\n"}; std::string const crnl {cr + nl}; std::string const tab {"\t"}; std::string const alert {"\a"}; std::string const backspace {"\b"}; std::string const backslash {"\\"}; // escape code sequence std::string const esc {"\x1b"}; // reset terminal std::string const reset {esc + "c"}; // clear all attributes std::string const clear {esc + "[0m"}; // mouse std::string const mouse_enable {esc + "[?1000;1006h"}; std::string const mouse_disable {esc + "[?1000;1006l"}; // style std::string const bold {esc + "[1m"}; std::string const dim {esc + "[2m"}; std::string const italic {esc + "[3m"}; std::string const underline {esc + "[4m"}; std::string const blink {esc + "[5m"}; std::string const rblink {esc + "[6m"}; std::string const reverse {esc + "[7m"}; std::string const conceal {esc + "[8m"}; std::string const cross {esc + "[9m"}; std::string const nbold {esc + "[22m"}; // screen std::string const screen_push {esc + "[?1049h"}; std::string const screen_pop {esc + "[?1049l"}; std::string const screen_clear {esc + "[2J"}; // scroll std::string const scroll_up {esc + "M"}; std::string const scroll_down {esc + "D"}; // erase std::string const erase_end {esc + "[K"}; std::string const erase_start {esc + "[1K"}; std::string const erase_line {esc + "[2K"}; std::string const erase_down {esc + "[J"}; std::string const erase_up {esc + "[1J"}; // cursor visibility std::string const cursor_hide {esc + "[?25l"}; std::string const cursor_show {esc + "[?25h"}; // cursor position std::string const cursor_home {esc + "[H"}; std::string const cursor_save {esc + "7"}; std::string const cursor_load {esc + "8"}; // foreground color std::string const fg_black {esc + "[30m"}; std::string const fg_red {esc + "[31m"}; std::string const fg_green {esc + "[32m"}; std::string const fg_yellow {esc + "[33m"}; std::string const fg_blue {esc + "[34m"}; std::string const fg_magenta {esc + "[35m"}; std::string const fg_cyan {esc + "[36m"}; std::string const fg_white {esc + "[37m"}; // background color std::string const bg_black {esc + "[40m"}; std::string const bg_red {esc + "[41m"}; std::string const bg_green {esc + "[42m"}; std::string const bg_yellow {esc + "[43m"}; std::string const bg_blue {esc + "[44m"}; std::string const bg_magenta {esc + "[45m"}; std::string const bg_cyan {esc + "[46m"}; std::string const bg_white {esc + "[47m"}; // foreground color bright std::string const fg_black_bright {esc + "[90m"}; std::string const fg_red_bright {esc + "[91m"}; std::string const fg_green_bright {esc + "[92m"}; std::string const fg_yellow_bright {esc + "[99m"}; std::string const fg_blue_bright {esc + "[94m"}; std::string const fg_magenta_bright {esc + "[95m"}; std::string const fg_cyan_bright {esc + "[96m"}; std::string const fg_white_bright {esc + "[97m"}; // background color bright std::string const bg_black_bright {esc + "[100m"}; std::string const bg_red_bright {esc + "[101m"}; std::string const bg_green_bright {esc + "[102m"}; std::string const bg_yellow_bright {esc + "[103m"}; std::string const bg_blue_bright {esc + "[104m"}; std::string const bg_magenta_bright {esc + "[105m"}; std::string const bg_cyan_bright {esc + "[106m"}; std::string const bg_white_bright {esc + "[107m"}; inline std::string cursor_up(std::size_t val = 1) { return val ? esc + "[" + std::to_string(val) + "A" : ""; } inline std::string cursor_down(std::size_t val = 1) { return val ? esc + "[" + std::to_string(val) + "B" : ""; } inline std::string cursor_right(std::size_t val = 1) { return val ? esc + "[" + std::to_string(val) + "C" : ""; } inline std::string cursor_left(std::size_t val = 1) { return val ? esc + "[" + std::to_string(val) + "D" : ""; } inline std::string cursor_set(std::size_t x_, std::size_t y_) { std::stringstream ss; ss << esc << "[" << y_ << ";" << x_ << "H"; return ss.str(); } inline int cursor_get(std::size_t& x_, std::size_t& y_, bool mode_ = true) { Term::Mode mode; if (mode_) { mode.set_raw(); } std::cout << (esc + "[6n") << std::flush; char buf[32]; std::uint8_t i {0}; // attempt to read response for up to 1 second for (int retry = 20; i < sizeof(buf) - 1; ++i) { while (retry-- > 0 && read(STDIN_FILENO, &buf[i], 1) != 1) { std::this_thread::sleep_for(std::chrono::milliseconds(50)); } if (buf[i] == 'R') { break; } } buf[i] = '\0'; if (buf[0] != '\x1b' || buf[1] != '[') { return -1; } int x; int y; if (std::sscanf(&buf[2], "%d;%d", &y, &x) != 2) { return -1; } x_ = static_cast(x); y_ = static_cast(y); return 0; } inline std::string str_to_fg_color(std::string const& str_, bool bright_ = false) { if ("black" == str_) { if (bright_) { return fg_black_bright; } else { return fg_black; } } else if ("red" == str_) { if (bright_) { return fg_red_bright; } else { return fg_red; } } else if ("green" == str_) { if (bright_) { return fg_green_bright; } else { return fg_green; } } else if ("yellow" == str_) { if (bright_) { return fg_yellow_bright; } else { return fg_yellow; } } else if ("blue" == str_) { if (bright_) { return fg_blue_bright; } else { return fg_blue; } } else if ("magenta" == str_) { if (bright_) { return fg_magenta_bright; } else { return fg_magenta; } } else if ("cyan" == str_) { if (bright_) { return fg_cyan_bright; } else { return fg_cyan; } } else if ("white" == str_) { if (bright_) { return fg_white_bright; } else { return fg_white; } } else { return {}; } } inline std::string str_to_bg_color(std::string const& str_, bool bright_ = false) { if ("black" == str_) { if (bright_) { return bg_black_bright; } else { return bg_black; } } else if ("red" == str_) { if (bright_) { return bg_red_bright; } else { return bg_red; } } else if ("green" == str_) { if (bright_) { return bg_green_bright; } else { return bg_green; } } else if ("yellow" == str_) { if (bright_) { return bg_yellow_bright; } else { return bg_yellow; } } else if ("blue" == str_) { if (bright_) { return bg_blue_bright; } else { return bg_blue; } } else if ("magenta" == str_) { if (bright_) { return bg_magenta_bright; } else { return bg_magenta; } } else if ("cyan" == str_) { if (bright_) { return bg_cyan_bright; } else { return bg_cyan; } } else if ("white" == str_) { if (bright_) { return bg_white_bright; } else { return bg_white; } } else { return {}; } } inline std::string fg_256(std::string const& str_) { auto const n = std::stoi(str_); if (n < 0 || n > 256) return {}; std::stringstream ss; ss << esc << "[38;5;" << str_ << "m"; return ss.str(); } inline std::string bg_256(std::string const& str_) { auto const n = std::stoi(str_); if (n < 0 || n > 256) return {}; std::stringstream ss; ss << esc << "[48;5;" << str_ << "m"; return ss.str(); } inline std::string htoi(std::string const& str_) { std::stringstream ss; ss << str_; unsigned int n; ss >> std::hex >> n; return std::to_string(n); } inline bool valid_hstr(std::string& str_) { std::smatch m; std::regex rx {"^#?((?:[0-9a-fA-F]{3}){1,2})$"}; if (std::regex_match(str_, m, rx, std::regex_constants::match_not_null)) { std::string hstr {m[1]}; if (hstr.size() == 3) { std::stringstream ss; ss << hstr[0] << hstr[0] << hstr[1] << hstr[1] << hstr[2] << hstr[2]; hstr = ss.str(); } str_ = hstr; return true; } return false; } inline std::string fg_true(std::string str_) { if (! valid_hstr(str_)) { return {}; } std::string const h1 {str_.substr(0, 2)}; std::string const h2 {str_.substr(2, 2)}; std::string const h3 {str_.substr(4, 2)}; std::ostringstream os; os << esc << "[38;2;" << htoi(h1) << ";" << htoi(h2) << ";" << htoi(h3) << "m"; return os.str(); } inline std::string bg_true(std::string str_) { if (! valid_hstr(str_)) { return {}; } std::string const h1 {str_.substr(0, 2)}; std::string const h2 {str_.substr(2, 2)}; std::string const h3 {str_.substr(4, 2)}; std::stringstream ss; ss << esc << "[48;2;" << htoi(h1) << ";" << htoi(h2) << ";" << htoi(h3) << "m"; return ss.str(); } template std::string wrap(T const val_, std::string const attr_, bool color_ = true) { std::stringstream ss; if (color_) { ss << attr_ << val_ << clear; } else { ss << val_; } return ss.str(); } template std::string wrap(T const val_, std::vector const attr_, bool color_ = true) { std::stringstream ss; if (color_) { for (auto const& e : attr_) { ss << e; } ss << val_ << clear; } else { ss << val_ << clear; } return ss.str(); } } // namespace ANSI_Escape_Codes class ostream : public std::ostream { protected: class streambuf : public std::streambuf { using string = std::basic_string; public: explicit streambuf(std::streambuf* streambuf_, std::size_t width_ = std::numeric_limits::max()) : _streambuf {streambuf_}, _width {width_} { } ~streambuf() { flush(); } streambuf& flush() { if (! _buffer.empty()) { if (_size) { if (! _white_space && ! _buffer.empty() && _buffer.back() == ' ') { _buffer.erase(_buffer.size() - 1); } _streambuf->sputn(_prefix.data(), static_cast(_prefix.size())); _streambuf->sputn(_buffer.data(), static_cast(_buffer.size())); } else { _streambuf->sputn(_buffer.data(), static_cast(_buffer.size())); } _streambuf->sputc('\n'); } // if (_width != std::numeric_limits::max()) // { // while (_size + _prefix.size() < _width) // { // ++_size; // _streambuf->sputc(' '); // } // } _size = 0; _buffer.clear(); _esc_seq.clear(); return *this; } streambuf& endl() { if (! _buffer.empty()) { if (_size) { if (! _white_space && ! _buffer.empty() && _buffer.back() == ' ') { _buffer.erase(_buffer.size() - 1); } _streambuf->sputn(_prefix.data(), static_cast(_prefix.size())); _streambuf->sputn(_buffer.data(), static_cast(_buffer.size())); } else { _streambuf->sputn(_buffer.data(), static_cast(_buffer.size())); } } // if (_width != std::numeric_limits::max()) // { // while (_size + _prefix.size() < _width) // { // ++_size; // _streambuf->sputc(' '); // } // } _streambuf->sputc('\n'); _size = 0; _buffer.clear(); _esc_seq.clear(); return *this; } streambuf& push(std::size_t val_) { flush(); _level += val_; _prefix = string(_level * _indent, ' '); return *this; } streambuf& pop(std::size_t val_) { flush(); if (_level > val_) { _level -= val_; } else { _level = 0; } _prefix = string(_level * _indent, ' '); return *this; } streambuf& width(std::size_t val_) { _width = val_; return *this; } streambuf& escape_codes(bool val_) { _escape_codes = val_; _esc_seq.clear(); return *this; } streambuf& line_wrap(bool val_) { _line_wrap = val_; return *this; } streambuf& first_wrap(bool val_) { _first_wrap = val_; return *this; } streambuf& auto_wrap(bool val_) { _auto_wrap = val_; _prefix.clear(); return *this; } streambuf& word_break(bool val_) { _word_break = val_; return *this; } streambuf& white_space(bool val_) { _white_space = val_; return *this; } streambuf& indent(std::size_t val_) { _indent = val_; _prefix = string(_level * _indent, ' '); return *this; } streambuf& level(std::size_t val_) { _level = val_; _prefix = string(_level * _indent, ' '); return *this; } protected: int_type overflow(int_type ch_) { if (traits_type::eq_int_type(traits_type::eof(), ch_)) { return traits_type::not_eof(ch_); } // handle auto wrap prefix if (ch_ != ' ' && ch_ != '\t') { _is_prefix = false; } // handle ansi escape codes if (! _esc_seq.empty()) { if (_esc_seq.size() == 1) { if (ch_ != '[' && ch_ != '(' && ch_ != ')' && ch_ != '#') { _esc_seq += static_cast(ch_); if (_escape_codes) { _buffer += _esc_seq; } _esc_seq.clear(); } else if (ch_ == '[' || ch_ == '(' || ch_ == ')' || ch_ == '#') { _esc_seq += static_cast(ch_); } else if (ch_ == std::isalpha(static_cast(ch_))) { _esc_seq += static_cast(ch_); if (_escape_codes) { _buffer += _esc_seq; } _esc_seq.clear(); } else { _esc_seq.clear(); } } else if (_esc_seq.size() == 2 && _esc_seq.back() == '#') { _esc_seq += static_cast(ch_); if (_escape_codes) { _buffer += _esc_seq; } _esc_seq.clear(); } else if (std::isalpha(static_cast(ch_))) { _esc_seq += static_cast(ch_); if (_escape_codes) { _buffer += _esc_seq; } _esc_seq.clear(); } else { _esc_seq += static_cast(ch_); } return ch_; } switch (ch_) { case '\t': { if (! _first_wrap && _level == 0) { // don't wrap first line when level is 0 // block left intentionally empty } else if (_line_wrap && (_size + _indent >= _width - _prefix.size())) { if (auto pos = _buffer.find_last_of(" "); _word_break && pos != string::npos) { _streambuf->sputn(_prefix.data(), static_cast(_prefix.size())); _streambuf->sputn(_buffer.data(), static_cast(pos)); _size = _buffer.size() - pos - 1; _buffer = _buffer.substr(pos + 1); } else { _streambuf->sputn(_prefix.data(), static_cast(_prefix.size())); _streambuf->sputn(_buffer.data(), static_cast(_buffer.size())); // while (_size + _prefix.size() < _width) // { // ++_size; // _streambuf->sputc(' '); // } _size = 0; _buffer.clear(); } _streambuf->sputc('\n'); } if (_auto_wrap && _is_prefix) { _level = 1; _prefix += string(_indent, ' '); } else if (_white_space || _buffer.empty()) { _size += _indent; _buffer += string(_indent, ' '); } else if (! _buffer.empty() && _buffer.back() != ' ') { _size += _indent; _buffer += string(_indent, ' '); } return ch_; } case '\x1b': { _esc_seq.clear(); _esc_seq += static_cast(ch_); return ch_; } case '\a': { _buffer += static_cast(ch_); return ch_; } case '\b': { --_size; _buffer += static_cast(ch_); return ch_; } case '\n': case '\r': { if (! _white_space && ! _buffer.empty() && _buffer.back() == ' ') { _buffer.erase(_buffer.size() - 1); } _streambuf->sputn(_prefix.data(), static_cast(_prefix.size())); _streambuf->sputn(_buffer.data(), static_cast(_buffer.size())); // if (_width != std::numeric_limits::max()) // { // while (_size + _prefix.size() < _width) // { // ++_size; // _streambuf->sputc(' '); // } // } _streambuf->sputc(static_cast(ch_)); _size = 0; _buffer.clear(); if (_auto_wrap) { _level = 0; _is_prefix = true; _prefix.clear(); } return ch_; } case ' ': { if (! _first_wrap && _level == 0) { // don't wrap first line when level is 0 // block left intentionally empty } else if (_line_wrap && (_size + 1 + _prefix.size() >= _width)) { ++_size; _buffer += " "; if (auto pos = _buffer.find_last_of(" "); _word_break && pos != string::npos) { _streambuf->sputn(_prefix.data(), static_cast(_prefix.size())); _streambuf->sputn(_buffer.data(), static_cast(pos)); _size = _buffer.size() - pos - 1; _buffer = _buffer.substr(pos + 1); } else { _streambuf->sputn(_prefix.data(), static_cast(_prefix.size())); _streambuf->sputn(_buffer.data(), static_cast(_buffer.size())); // while (_size + _prefix.size() < _width) // { // ++_size; // _streambuf->sputc(' '); // } _size = 0; _buffer.clear(); } _streambuf->sputc('\n'); return ch_; } if (_auto_wrap && _is_prefix) { _level = 1; _prefix += " "; } else if (_white_space) { ++_size; _buffer += " "; } else if (! _buffer.empty() && _buffer.back() != ' ') { ++_size; _buffer += " "; } return ch_; } default: { if (! _first_wrap && _level == 0) { // don't wrap first line when level is 0 // block left intentionally empty } else if (_line_wrap && (_size + _prefix.size() >= _width)) { if (auto pos = _buffer.find_last_of(" "); _word_break && pos != string::npos) { _streambuf->sputn(_prefix.data(), static_cast(_prefix.size())); _streambuf->sputn(_buffer.data(), static_cast(pos)); _size = _buffer.size() - pos - 1; _buffer = _buffer.substr(pos + 1); } else { _streambuf->sputn(_prefix.data(), static_cast(_prefix.size())); _streambuf->sputn(_buffer.data(), static_cast(_buffer.size())); // while (_size + _prefix.size() < _width) // { // ++_size; // _streambuf->sputc(' '); // } _size = 0; _buffer.clear(); } _streambuf->sputc('\n'); } ++_size; _buffer += static_cast(ch_); return ch_; } } } // output streambuf std::streambuf* _streambuf; // maximum output width std::size_t _width; // number of spaces to indent std::size_t _indent {2}; // indentation level std::size_t _level {0}; // stream should wrap around at output width bool _line_wrap {true}; // stream should wrap around at output width when level=0 bool _first_wrap {true}; // auto calc indent when wrapping line bool _auto_wrap {false}; // stream should break words on wrap bool _word_break {true}; // stream should preserve whitespace bool _white_space {true}; // stream should output escape codes bool _escape_codes {true}; // used to calc the indent for auto wrap bool _is_prefix {true}; // size of buffer minus special chars std::size_t _size {0}; // prefix string prepended onto start of a new line string _prefix {""}; // buffer string for temporary storage of chars string _buffer {""}; // buffer string for escape sequences string _esc_seq {""}; }; // class streambuf public: explicit ostream(std::ostream& os_, std::size_t indent_width_ = 2, std::size_t output_width_ = std::numeric_limits::max()) : std::ostream {&_stream}, _stream {os_.rdbuf(), output_width_} { indent(indent_width_); } ostream& flush() { _stream.flush(); std::ostream::flush(); return *this; } ostream& endl() { _stream.endl(); std::ostream::flush(); return *this; } ostream& push(std::size_t val_ = 1) { _stream.push(val_); std::ostream::flush(); return *this; } ostream& pop(std::size_t val_ = 1) { _stream.pop(val_); std::ostream::flush(); return *this; } ostream& width(std::size_t val_ = std::numeric_limits::max()) { _stream.width(val_); return *this; } ostream& escape_codes(bool val_ = true) { _stream.escape_codes(val_); return *this; } ostream& line_wrap(bool val_ = true) { _stream.line_wrap(val_); return *this; } ostream& first_wrap(bool val_ = true) { _stream.first_wrap(val_); return *this; } ostream& auto_wrap(bool val_ = true) { _stream.auto_wrap(val_); return *this; } ostream& word_break(bool val_ = true) { _stream.word_break(val_); return *this; } ostream& white_space(bool val_ = true) { _stream.white_space(val_); return *this; } ostream& indent(std::size_t val_ = 2) { _stream.indent(val_); return *this; } ostream& level(std::size_t val_ = 0) { _stream.level(val_); return *this; } protected: streambuf _stream; }; // class ostream namespace iomanip { class flush { public: friend Term::ostream& operator<<(Term::ostream& os_, flush const&) { os_.flush(); return os_; } friend Term::ostream& operator<<(std::ostream& os_, flush const&) { auto& derived = dynamic_cast(os_); derived.flush(); return derived; } }; // class flush class endl { public: friend Term::ostream& operator<<(Term::ostream& os_, endl const&) { os_.endl(); return os_; } friend Term::ostream& operator<<(std::ostream& os_, endl const&) { auto& derived = dynamic_cast(os_); derived.endl(); return derived; } }; // class endl class push { public: explicit push(std::size_t val_ = 1) : _val {val_} { } friend Term::ostream& operator<<(Term::ostream& os_, push const& obj_) { os_.push(obj_._val); return os_; } friend Term::ostream& operator<<(std::ostream& os_, push const& obj_) { auto& derived = dynamic_cast(os_); derived.push(obj_._val); return derived; } private: std::size_t _val; }; // class push class pop { public: explicit pop(std::size_t val_ = 1) : _val {val_} { } friend Term::ostream& operator<<(Term::ostream& os_, pop const& obj_) { os_.pop(obj_._val); return os_; } friend Term::ostream& operator<<(std::ostream& os_, pop const& obj_) { auto& derived = dynamic_cast(os_); derived.pop(obj_._val); return derived; } private: std::size_t _val; }; // class pop class line_wrap { public: explicit line_wrap(bool val_ = true) : _val {val_} { } friend Term::ostream& operator<<(Term::ostream& os_, line_wrap const& obj_) { os_.line_wrap(obj_._val); return os_; } friend Term::ostream& operator<<(std::ostream& os_, line_wrap const& obj_) { auto& derived = dynamic_cast(os_); derived.line_wrap(obj_._val); return derived; } private: bool _val; }; // class line_wrap class first_wrap { public: explicit first_wrap(bool val_ = true) : _val {val_} { } friend Term::ostream& operator<<(Term::ostream& os_, first_wrap const& obj_) { os_.first_wrap(obj_._val); return os_; } friend Term::ostream& operator<<(std::ostream& os_, first_wrap const& obj_) { auto& derived = dynamic_cast(os_); derived.first_wrap(obj_._val); return derived; } private: bool _val; }; // class first_wrap class word_break { public: explicit word_break(bool val_ = true) : _val {val_} { } friend Term::ostream& operator<<(Term::ostream& os_, word_break const& obj_) { os_.word_break(obj_._val); return os_; } friend Term::ostream& operator<<(std::ostream& os_, word_break const& obj_) { auto& derived = dynamic_cast(os_); derived.word_break(obj_._val); return derived; } private: bool _val; }; // class word_break class white_space { public: explicit white_space(bool val_ = true) : _val {val_} { } friend Term::ostream& operator<<(Term::ostream& os_, white_space const& obj_) { os_.white_space(obj_._val); return os_; } friend Term::ostream& operator<<(std::ostream& os_, white_space const& obj_) { auto& derived = dynamic_cast(os_); derived.white_space(obj_._val); return derived; } private: bool _val; }; // class white_space class escape_codes { public: explicit escape_codes(bool val_ = true) : _val {val_} { } friend Term::ostream& operator<<(Term::ostream& os_, escape_codes const& obj_) { os_.escape_codes(obj_._val); return os_; } friend Term::ostream& operator<<(std::ostream& os_, escape_codes const& obj_) { auto& derived = dynamic_cast(os_); derived.escape_codes(obj_._val); return derived; } private: bool _val; }; // class escape_codes class width { public: explicit width(bool val_ = true) : _val {val_} { } friend Term::ostream& operator<<(Term::ostream& os_, width const& obj_) { os_.width(obj_._val); return os_; } friend Term::ostream& operator<<(std::ostream& os_, width const& obj_) { auto& derived = dynamic_cast(os_); derived.width(obj_._val); return derived; } private: bool _val; }; // class width class indent { public: explicit indent(bool val_ = true) : _val {val_} { } friend Term::ostream& operator<<(Term::ostream& os_, indent const& obj_) { os_.indent(obj_._val); return os_; } friend Term::ostream& operator<<(std::ostream& os_, indent const& obj_) { auto& derived = dynamic_cast(os_); derived.indent(obj_._val); return derived; } private: bool _val; }; // class indent class level { public: explicit level(bool val_ = true) : _val {val_} { } friend Term::ostream& operator<<(Term::ostream& os_, level const& obj_) { os_.level(obj_._val); return os_; } friend Term::ostream& operator<<(std::ostream& os_, level const& obj_) { auto& derived = dynamic_cast(os_); derived.level(obj_._val); return derived; } private: bool _val; }; // class level } // namespace iomanip } // namespace OB::Term #endif // OB_TERM_HH