diff --git a/TESTS_ENVIRONMENT.in b/TESTS_ENVIRONMENT.in index 1e41d7f5..d8e5b13d 100644 --- a/TESTS_ENVIRONMENT.in +++ b/TESTS_ENVIRONMENT.in @@ -149,6 +149,8 @@ grep_output_for() { on_error_fail_with() { if test $? -ne 0; then echo $1 > /dev/stderr + cat ${test_file_base}_${test_num}.tmp + cat ${test_file_base}_${test_num}.err exit 1 fi } diff --git a/src/auto_mem.hh b/src/auto_mem.hh index a23d3b0e..552c0e41 100644 --- a/src/auto_mem.hh +++ b/src/auto_mem.hh @@ -56,7 +56,7 @@ public: auto_mem(const auto_mem &am) = delete; template - explicit auto_mem(F free_func) + explicit auto_mem(F free_func) noexcept : am_ptr(nullptr), am_free_func((free_func_t)free_func) { }; auto_mem(auto_mem &&other) noexcept diff --git a/src/base/intern_string.hh b/src/base/intern_string.hh index b1242907..05e6d05b 100644 --- a/src/base/intern_string.hh +++ b/src/base/intern_string.hh @@ -39,6 +39,7 @@ #include "optional.hpp" #include "strnatcmp.h" +#include "fmt/format.h" struct string_fragment { using iterator = const char *; @@ -212,6 +213,18 @@ struct string_fragment { int sf_end; }; +namespace fmt { +template<> +struct formatter : formatter { + template + auto format(const string_fragment& sf, FormatContext &ctx) + { + return formatter::format( + string_view{sf.data(), (size_t) sf.length()}, ctx); + } +}; +} + inline bool operator<(const char *left, const string_fragment &right) { int rc = strncmp(left, right.data(), right.length()); return rc < 0; diff --git a/src/base/result.h b/src/base/result.h index 1a3b663c..e1f17478 100644 --- a/src/base/result.h +++ b/src/base/result.h @@ -841,6 +841,14 @@ struct Result { return defaultValue; } + template + auto unwrapOrElse(Func func) const { + if (isOk()) { + return storage().template get(); + } + return func(this->storage().template get()); + } + template typename std::enable_if< !std::is_same::value, diff --git a/src/base/string_util.cc b/src/base/string_util.cc index 244068db..9452d391 100644 --- a/src/base/string_util.cc +++ b/src/base/string_util.cc @@ -168,3 +168,10 @@ void split_ws(const std::string &str, std::vector &toks_out) toks_out.push_back(buf); } } + +std::string repeat(const std::string& input, size_t num) +{ + std::ostringstream os; + std::fill_n(std::ostream_iterator(os), num, input); + return os.str(); +} diff --git a/src/base/string_util.hh b/src/base/string_util.hh index 75e53eb7..cf293168 100644 --- a/src/base/string_util.hh +++ b/src/base/string_util.hh @@ -143,4 +143,6 @@ size_t abbreviate_str(char *str, size_t len, size_t max_len); void split_ws(const std::string &str, std::vector &toks_out); +std::string repeat(const std::string& input, size_t num); + #endif diff --git a/src/bottom_status_source.cc b/src/bottom_status_source.cc index 5a9040bb..95f0fcef 100644 --- a/src/bottom_status_source.cc +++ b/src/bottom_status_source.cc @@ -163,6 +163,8 @@ void bottom_status_source::update_loading(off_t off, size_t total) { status_field &sf = this->bss_fields[BSF_LOADING]; + require(off >= 0); + if (total == 0 || (size_t)off == total) { sf.set_cylon(false); sf.set_role(view_colors::VCR_STATUS); diff --git a/src/command_executor.cc b/src/command_executor.cc index 07c73121..b79544ba 100644 --- a/src/command_executor.cc +++ b/src/command_executor.cc @@ -65,6 +65,10 @@ int sql_progress(const struct log_cursor &lc) size_t total = lnav_data.ld_log_source.text_line_count(); off_t off = lc.lc_curr_line; + if (off < 0) { + return 0; + } + if (lnav_data.ld_window == nullptr) { return 0; } diff --git a/src/highlighter.cc b/src/highlighter.cc index dfbcda5f..ea21d167 100644 --- a/src/highlighter.cc +++ b/src/highlighter.cc @@ -135,8 +135,23 @@ void highlighter::annotate(attr_line_t &al, int start) const attrs = this->h_attrs; } if (this->h_semantic) { - attrs |= vc.attrs_for_ident(&str[lr.lr_start], - lr.length()); + bool found_color = false; + + if (str[lr.lr_start] == '#' && + (lr.length() == 4 || lr.length() == 7)) { + auto fg_res = rgb_color::from_str( + string_fragment(str.data(), lr.lr_start, lr.lr_end)); + if (fg_res.isOk()) { + sa.emplace_back(lr, + &view_curses::VC_FOREGROUND, + vc.match_color(fg_res.unwrap())); + found_color = true; + } + } + if (!found_color) { + attrs |= vc.attrs_for_ident(&str[lr.lr_start], + lr.length()); + } } if (!this->h_fg.empty()) { sa.emplace_back(lr, diff --git a/src/lnav.cc b/src/lnav.cc index 56ff4375..80983161 100644 --- a/src/lnav.cc +++ b/src/lnav.cc @@ -1497,6 +1497,8 @@ static void looper() int session_stage = 0; while (lnav_data.ld_looping) { + static bool initial_build = false; + vector pollfds; auto poll_to = initial_rescan_completed ? timeval{ 0, 333000 } : @@ -1596,6 +1598,9 @@ static void looper() POLLIN, 0 }); + if (initial_build) { + view_curses::awaiting_user_input(); + } } rlc.update_poll_set(pollfds); lnav_data.ld_filter_source.fss_editor.update_poll_set(pollfds); @@ -1669,7 +1674,6 @@ static void looper() }; } - static bool initial_build = false; if (initial_rescan_completed && (!initial_build || timer.fade_diff(index_counter) == 0)) { if (lnav_data.ld_mode == LNM_PAGING) { diff --git a/src/log_format.cc b/src/log_format.cc index c601f058..d385f6b1 100644 --- a/src/log_format.cc +++ b/src/log_format.cc @@ -1923,31 +1923,34 @@ void external_log_format::build(std::vector &errors) { for (auto &hd_pair : this->elf_highlighter_patterns) { external_log_format::highlighter_def &hd = hd_pair.second; const std::string &pattern = hd.hd_pattern; - std::string errmsg; const char *errptr; rgb_color fg, bg; int eoff, attrs = 0; if (!hd.hd_color.empty()) { - if (!rgb_color::from_str(hd.hd_color, fg, errmsg)) { - errors.push_back("error:" - + this->elf_name.to_string() - + ":highlighters/" - + hd_pair.first.to_string() - + "/color:" - + errmsg); - } + fg = rgb_color::from_str(hd.hd_color) + .unwrapOrElse([&](const auto& msg) { + errors.push_back("error:" + + this->elf_name.to_string() + + ":highlighters/" + + hd_pair.first.to_string() + + "/color:" + + msg); + return rgb_color{}; + }); } if (!hd.hd_background_color.empty()) { - if (!rgb_color::from_str(hd.hd_background_color, bg, errmsg)) { - errors.push_back("error:" - + this->elf_name.to_string() - + ":highlighters/" - + hd_pair.first.to_string() - + "/color:" - + errmsg); - } + bg = rgb_color::from_str(hd.hd_background_color) + .unwrapOrElse([&](const auto& msg) { + errors.push_back("error:" + + this->elf_name.to_string() + + ":highlighters/" + + hd_pair.first.to_string() + + "/color:" + + msg); + return rgb_color{}; + }); } if (hd.hd_underline) { diff --git a/src/pcrepp/Makefile.am b/src/pcrepp/Makefile.am index 52deed2b..9c522e49 100644 --- a/src/pcrepp/Makefile.am +++ b/src/pcrepp/Makefile.am @@ -1,7 +1,8 @@ AM_CPPFLAGS = \ -Wall \ - -I$(top_srcdir)/src + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/fmtlib noinst_LIBRARIES = libpcrepp.a diff --git a/src/readline_curses.hh b/src/readline_curses.hh index c26c8471..850975a8 100644 --- a/src/readline_curses.hh +++ b/src/readline_curses.hh @@ -238,6 +238,7 @@ public: { this->rc_value = value; this->rc_value_expiration = time(nullptr) + VALUE_EXPIRATION; + this->set_needs_update(); }; std::string get_value() const { return this->rc_value; }; diff --git a/src/readline_highlighters.cc b/src/readline_highlighters.cc index 8cd72441..8ab4174e 100644 --- a/src/readline_highlighters.cc +++ b/src/readline_highlighters.cc @@ -363,7 +363,7 @@ void readline_command_highlighter(attr_line_t &al, int x) static const pcrepp SQL_PREFIXES("^:(filter-expr)"); static const pcrepp IDENT_PREFIXES("^:(tag|untag|delete-tags)"); static const pcrepp COLOR_PREFIXES("^:(config)"); - static const pcrepp COLOR_RE("(#(?:[a-fA-F0-9]{3}|[a-fA-F0-9]{6}))"); + static const pcrepp COLOR_RE("(#(?:[a-fA-F0-9]{6}|[a-fA-F0-9]{3}))"); view_colors &vc = view_colors::singleton(); int keyword_attrs = ( @@ -401,19 +401,18 @@ void readline_command_highlighter(attr_line_t &al, int x) pcre_context::capture_t *cap = pc[0]; string hash_color = pi.get_substr(cap); string errmsg; - rgb_color rgb_fg, rgb_bg; attr_t color_hint_attrs = vc.attrs_for_role(view_colors::VCR_COLOR_HINT); int pnum = PAIR_NUMBER(color_hint_attrs); - if (rgb_color::from_str(hash_color, rgb_bg, errmsg)) { + rgb_color::from_str(hash_color).then([&](const auto& rgb_fg) { pnum -= 1; - vc.ensure_color_pair(pnum, rgb_fg, rgb_bg); + vc.ensure_color_pair(pnum, rgb_fg, rgb_color{}); al.get_attrs().emplace_back( line_range{cap->c_begin, cap->c_begin + 1}, &view_curses::VC_ROLE, view_colors::VCR_COLOR_HINT); - } + }); } } pi.reset(line); diff --git a/src/styling.cc b/src/styling.cc index 8037bdfb..70f4c952 100644 --- a/src/styling.cc +++ b/src/styling.cc @@ -31,6 +31,7 @@ #include +#include "fmt/format.h" #include "yajlpp/yajlpp.hh" #include "yajlpp/yajlpp_def.hh" #include "styling.hh" @@ -53,6 +54,8 @@ static struct json_path_container term_color_handler = { .FOR_FIELD(term_color, xc_id), yajlpp::property_handler("name") .FOR_FIELD(term_color, xc_name), + yajlpp::property_handler("hexString") + .FOR_FIELD(term_color, xc_hex), yajlpp::property_handler("rgb") .with_obj_provider( [](const auto &pc, term_color *xc) { return &xc->xc_color; }) @@ -83,46 +86,46 @@ term_color_palette *ansi_colors() return &retval; } -bool rgb_color::from_str(const string_fragment &color, - rgb_color &rgb_out, - std::string &errmsg) +Result rgb_color::from_str(const string_fragment &sf) { - if (color.empty()) { - return true; + if (sf.empty()) { + return Ok(rgb_color()); } - if (color[0] == '#') { - switch (color.length()) { + rgb_color rgb_out; + + if (sf[0] == '#') { + switch (sf.length()) { case 4: - if (sscanf(color.data(), "#%1hx%1hx%1hx", + if (sscanf(sf.data(), "#%1hx%1hx%1hx", &rgb_out.rc_r, &rgb_out.rc_g, &rgb_out.rc_b) == 3) { rgb_out.rc_r |= rgb_out.rc_r << 4; rgb_out.rc_g |= rgb_out.rc_g << 4; rgb_out.rc_b |= rgb_out.rc_b << 4; - return true; + return Ok(rgb_out); } break; case 7: - if (sscanf(color.data(), "#%2hx%2hx%2hx", + if (sscanf(sf.data(), "#%2hx%2hx%2hx", &rgb_out.rc_r, &rgb_out.rc_g, &rgb_out.rc_b) == 3) { - return true; + return Ok(rgb_out); } break; } - errmsg = "Could not parse color: " + color.to_string(); - return false; + + return Err(fmt::format("Could not parse color: {}", sf)); } for (const auto &xc : xterm_colors()->tc_palette) { - if (color.iequal(xc.xc_name)) { - rgb_out = xc.xc_color; - return true; + if (sf.iequal(xc.xc_name)) { + return Ok(xc.xc_color); } } - errmsg = "Unknown color: '" + color.to_string() + - "'. See https://jonasjacek.github.io/colors/ for a list of supported color names"; - return false; + return Err(fmt::format( + "Unknown color: '{}'. " + "See https://jonasjacek.github.io/colors/ for a list of supported " + "color names", sf)); } bool rgb_color::operator<(const rgb_color &rhs) const diff --git a/src/styling.hh b/src/styling.hh index 3bcf8c4f..b367e3ae 100644 --- a/src/styling.hh +++ b/src/styling.hh @@ -35,12 +35,11 @@ #include #include "log_level.hh" +#include "base/result.h" #include "base/intern_string.hh" struct rgb_color { - static bool from_str(const string_fragment &color, - rgb_color &rgb_out, - std::string &errmsg); + static Result from_str(const string_fragment &sf); explicit rgb_color(short r = -1, short g = -1, short b = -1) : rc_r(r), rc_g(g), rc_b(b) { @@ -103,6 +102,7 @@ struct lab_color { struct term_color { short xc_id; std::string xc_name; + std::string xc_hex; rgb_color xc_color; lab_color xc_lab_color; }; diff --git a/src/textview_curses.cc b/src/textview_curses.cc index 9dd1adba..e7de0933 100644 --- a/src/textview_curses.cc +++ b/src/textview_curses.cc @@ -168,7 +168,7 @@ void textview_curses::reload_config(error_reporter &reporter) const auto &sc = hl_pair.second.hc_style; string fg1, bg1, fg_color, bg_color, errmsg; - rgb_color fg, bg; + bool invalid = false; int attrs = 0; fg1 = sc.sc_color; @@ -176,12 +176,19 @@ void textview_curses::reload_config(error_reporter &reporter) shlex(fg1).eval(fg_color, theme_iter->second.lt_vars); shlex(bg1).eval(bg_color, theme_iter->second.lt_vars); - if (!rgb_color::from_str(fg_color, fg, errmsg)) { - reporter(&sc.sc_color, errmsg); - continue; - } - if (!rgb_color::from_str(bg_color, bg, errmsg)) { - reporter(&sc.sc_background_color, errmsg); + auto fg = rgb_color::from_str(fg_color) + .unwrapOrElse([&](const auto& msg) { + reporter(&sc.sc_color, errmsg); + invalid = true; + return rgb_color{}; + }); + auto bg = rgb_color::from_str(bg_color) + .unwrapOrElse([&](const auto& msg) { + reporter(&sc.sc_background_color, errmsg); + invalid = true; + return rgb_color{}; + }); + if (invalid) { continue; } diff --git a/src/themes/default-theme.json b/src/themes/default-theme.json index 2dcf86dd..93f576c2 100644 --- a/src/themes/default-theme.json +++ b/src/themes/default-theme.json @@ -6,7 +6,7 @@ "styles": { "text": { "color": "Silver", - "background-color": "" + "background-color": "Black" }, "identifier": { "background-color": "" @@ -149,6 +149,12 @@ } }, "highlights": { + "colors": { + "pattern": "(?:#[a-fA-F0-9]{6}|#[a-fA-F0-9]{3})", + "style": { + "semantic": true + } + }, "ipv4": { "pattern": "\\b(?= 256) { @@ -466,6 +486,12 @@ void view_colors::init() inline attr_t attr_for_colors(int &pair_base, short fg, short bg) { + if (fg == -1) { + fg = COLOR_WHITE; + } + if (bg == -1) { + bg = COLOR_BLACK; + } if (COLOR_PAIRS <= 64) { return view_colors::ansi_color_pair(fg, bg); } else { @@ -476,13 +502,6 @@ inline attr_t attr_for_colors(int &pair_base, short fg, short bg) if (bg == COLOR_BLACK) { bg = -1; } - } else { - if (fg == -1) { - fg = COLOR_WHITE; - } - if (bg == -1) { - bg = COLOR_BLACK; - } } } @@ -502,8 +521,7 @@ pair view_colors::to_attrs( const lnav_theme <, const style_config &sc, const style_config &fallback_sc, lnav_config_listener::error_reporter &reporter) { - rgb_color fg, bg; - string fg1, bg1, fg_color, bg_color, errmsg; + string fg1, bg1, fg_color, bg_color; fg1 = sc.sc_color; if (fg1.empty()) { @@ -516,12 +534,14 @@ pair view_colors::to_attrs( shlex(fg1).eval(fg_color, lt.lt_vars); shlex(bg1).eval(bg_color, lt.lt_vars); - if (!rgb_color::from_str(fg_color, fg, errmsg)) { - reporter(&sc.sc_color, errmsg); - } - if (!rgb_color::from_str(bg_color, bg, errmsg)) { - reporter(&sc.sc_background_color, errmsg); - } + auto fg = rgb_color::from_str(fg_color).unwrapOrElse([&](const auto& msg) { + reporter(&sc.sc_color, msg); + return rgb_color{}; + }); + auto bg = rgb_color::from_str(bg_color).unwrapOrElse([&](const auto& msg) { + reporter(&sc.sc_background_color, msg); + return rgb_color{}; + }); attr_t retval1 = attr_for_colors(pair_base, this->match_color(fg), @@ -552,13 +572,14 @@ void view_colors::init_roles(const lnav_theme <, int ident_bg = (lnav_config.lc_ui_default_colors ? -1 : COLOR_BLACK); if (!ident_sc.sc_background_color.empty()) { - string bg_color, errmsg; - rgb_color rgb_bg; + string bg_color; shlex(ident_sc.sc_background_color).eval(bg_color, lt.lt_vars); - if (!rgb_color::from_str(bg_color, rgb_bg, errmsg)) { - reporter(&ident_sc.sc_background_color, errmsg); - } + auto rgb_bg = rgb_color::from_str(bg_color) + .unwrapOrElse([&](const auto& msg) { + reporter(&ident_sc.sc_background_color, msg); + return rgb_color{}; + }); ident_bg = vc_active_palette->match_color(lab_color(rgb_bg)); } for (int z = 0; z < 6; z++) { @@ -592,19 +613,17 @@ void view_colors::init_roles(const lnav_theme <, auto fg_str = lt.lt_vars.find(COLOR_NAMES[ansi_fg]); auto bg_str = lt.lt_vars.find(COLOR_NAMES[ansi_bg]); - rgb_color rgb_fg, rgb_bg; - string errmsg; - if (fg_str != lt.lt_vars.end() && - !rgb_color::from_str(fg_str->second, rgb_fg, errmsg)) { - reporter(&fg_str->second, errmsg); - return; - } - if (bg_str != lt.lt_vars.end() && - !rgb_color::from_str(bg_str->second, rgb_bg, errmsg)) { - reporter(&bg_str->second, errmsg); - return; - } + auto rgb_fg = rgb_color::from_str(fg_str->second) + .unwrapOrElse([&](const auto& msg) { + reporter(&fg_str->second, msg); + return rgb_color{}; + }); + auto rgb_bg = rgb_color::from_str(bg_str->second) + .unwrapOrElse([&](const auto& msg) { + reporter(&fg_str->second, msg); + return rgb_color{}; + }); short fg = vc_active_palette->match_color(lab_color(rgb_fg)); short bg = vc_active_palette->match_color(lab_color(rgb_bg)); @@ -617,6 +636,12 @@ void view_colors::init_roles(const lnav_theme <, } this->vc_ansi_to_theme[ansi_fg] = fg; + if (lnav_config.lc_ui_default_colors && bg == COLOR_BLACK) { + bg = -1; + if (fg == COLOR_WHITE) { + fg = -1; + } + } init_pair(ansi_color_pair_index(ansi_fg, ansi_bg), fg, bg); } } @@ -882,7 +907,7 @@ attr_t view_colors::attrs_for_ident(const char *str, size_t len) const pair_content(pnum, &fg, &bg); } else { - retval = BASIC_HL_PAIRS[index % BASIC_COLOR_COUNT]; + retval = A_BOLD; } return retval; diff --git a/src/view_curses.hh b/src/view_curses.hh index 59772c11..e0928741 100644 --- a/src/view_curses.hh +++ b/src/view_curses.hh @@ -442,6 +442,8 @@ public: static string_attr_type VC_FOREGROUND; static string_attr_type VC_BACKGROUND; + static void awaiting_user_input(); + static void mvwattrline(WINDOW *window, int y, int x, diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 45bbbfd4..a7abb936 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -62,6 +62,9 @@ add_executable(test_top_status test_top_status.cc) target_link_libraries(test_top_status diag PkgConfig::libpcre) add_test(NAME test_top_status COMMAND test_top_status) +add_executable(drive_vt52_curses drive_vt52_curses.cc) +target_link_libraries(drive_vt52_curses diag PkgConfig::ncursesw) + add_executable(drive_sql_anno drive_sql_anno.cc) target_link_libraries(drive_sql_anno diag PkgConfig::libpcre) diff --git a/test/Makefile.am b/test/Makefile.am index 40efee35..690d5605 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -322,6 +322,7 @@ TESTS = \ test_line_buffer.sh \ test_listview.sh \ test_meta.sh \ + test_mvwattrline.sh \ test_grep_proc.sh \ test_grep_proc2 \ test_json_format.sh \ @@ -342,11 +343,11 @@ TESTS = \ test_sql_xml_func.sh \ test_data_parser.sh \ test_pretty_print.sh \ + test_view_colors.sh \ test_vt52_curses.sh DISABLED_TESTS = \ - test_top_status \ - test_view_colors.sh + test_top_status if HAVE_LIBCURL TESTS += \ diff --git a/test/drive_data_scanner.cc b/test/drive_data_scanner.cc index 39542c97..ccc8e14b 100644 --- a/test/drive_data_scanner.cc +++ b/test/drive_data_scanner.cc @@ -35,7 +35,6 @@ #include #include -#include "pcrepp/pcrepp.hh" #include "textview_curses.hh" #include "data_scanner.hh" #include "data_parser.hh" diff --git a/test/drive_vt52_curses.cc b/test/drive_vt52_curses.cc index d936a1ad..980369fa 100644 --- a/test/drive_vt52_curses.cc +++ b/test/drive_vt52_curses.cc @@ -98,15 +98,10 @@ int main(int argc, char *argv[]) tgetstr((char *)"ce", nullptr), "de", "\n", - "1\n", - "2\n", - "3\n", - "4\n", - "5\n", - "6\n", - "7\n", - "8\n", - "9\n", + "1", + "2", + "3", + "\n", "abc", "\x02", "\a", @@ -124,6 +119,7 @@ int main(int argc, char *argv[]) vt.map_output(CANNED_INPUT[lpc], strlen(CANNED_INPUT[lpc])); vt.do_update(); refresh(); + view_curses::awaiting_user_input(); getch(); } diff --git a/test/listview_output.0 b/test/listview_output.0 index af483c73..3dbce26b 100644 --- a/test/listview_output.0 +++ b/test/listview_output.0 @@ -1 +1,69 @@ -read 1b29301b371b5b3f3437681b5b313b3234721b5b6d1b5b346c1b5b481b5b324a48656c6c6f1b5b3734430e780f1b5b323b31480e0f576f726c64211b5b3733430e780f1b5b333b3830480e780f1b5b343b3830480e780f1b5b353b3830480e780f1b5b363b3830480e780f1b5b373b3830480e780f1b5b383b3830480e780f1b5b393b3830480e780f1b5b31303b3830480e780f1b5b31313b3830480e780f1b5b31323b3830480e780f1b5b31333b3830480e780f1b5b31343b3830480e780f1b5b31353b3830480e780f1b5b31363b3830480e780f1b5b31373b3830480e780f1b5b31383b3830480e780f1b5b31393b3830480e780f1b5b32303b3830480e780f1b5b32313b3830480e780f1b5b32323b3830480e780f1b5b32333b3830480e780f1b5b32343b3830480e0f080e780f080e1b5b34680f201b5b346c0d1b5b32343b31481b5b324a1b5b3f34376c1b380d1b5b3f316c1b3e +CTRL Use alt charset +CTRL save cursor +CSI Use alternate screen buffer +CSI set scrolling region 1-24 +S -1 ┋ ┋ +A └ normal +CSI Replace mode +CSI Erase all +S 1 ┋Hello x┋ +A └┛ alt +S 2 ┋World! x┋ +A └┛ alt +S 3 ┋ x┋ +A └┛ alt +S 4 ┋ x┋ +A └┛ alt +S 5 ┋ x┋ +A └┛ alt +S 6 ┋ x┋ +A └┛ alt +S 7 ┋ x┋ +A └┛ alt +S 8 ┋ x┋ +A └┛ alt +S 9 ┋ x┋ +A └┛ alt +S 10 ┋ x┋ +A └┛ alt +S 11 ┋ x┋ +A └┛ alt +S 12 ┋ x┋ +A └┛ alt +S 13 ┋ x┋ +A └┛ alt +S 14 ┋ x┋ +A └┛ alt +S 15 ┋ x┋ +A └┛ alt +S 16 ┋ x┋ +A └┛ alt +S 17 ┋ x┋ +A └┛ alt +S 18 ┋ x┋ +A └┛ alt +S 19 ┋ x┋ +A └┛ alt +S 20 ┋ x┋ +A └┛ alt +S 21 ┋ x┋ +A └┛ alt +S 22 ┋ x┋ +A └┛ alt +S 23 ┋ x┋ +A └┛ alt +S 24 ┋ ┋ +A ···············································································└ backspace +A └┛ alt +A ···············································································└ backspace +A ··············································································└ [4h +CSI Replace mode +S 24 ┋ ┋ +A ···············································································└ carriage-return +CSI Erase all +CSI Use normal screen buffer +CTRL restore cursor +S 24 ┋ ┋ +A └ carriage-return +CSI Normal cursor keys +CTRL Normal keypad diff --git a/test/listview_output.1 b/test/listview_output.1 index 233f4aa8..32d10c60 100644 --- a/test/listview_output.1 +++ b/test/listview_output.1 @@ -1 +1,69 @@ -read 1b29301b371b5b3f3437681b5b313b3234721b5b6d1b5b346c1b5b481b5b324a576f726c64211b5b3733430e780f1b5b323b3830480e780f1b5b333b3830480e780f1b5b343b3830480e780f1b5b353b3830480e780f1b5b363b3830480e780f1b5b373b3830480e780f1b5b383b3830480e780f1b5b393b3830480e780f1b5b31303b3830480e780f1b5b31313b3830480e780f1b5b31323b3830480e780f1b5b31333b3830480e780f1b5b31343b3830480e780f1b5b31353b3830480e780f1b5b31363b3830480e780f1b5b31373b3830480e780f1b5b31383b3830480e780f1b5b31393b3830480e780f1b5b32303b3830480e780f1b5b32313b3830480e780f1b5b32323b3830480e780f1b5b32333b3830480e780f1b5b32343b3830480e0f080e780f080e1b5b34680f201b5b346c0d1b5b32343b31481b5b324a1b5b3f34376c1b380d1b5b3f316c1b3e +CTRL Use alt charset +CTRL save cursor +CSI Use alternate screen buffer +CSI set scrolling region 1-24 +S -1 ┋ ┋ +A └ normal +CSI Replace mode +CSI Erase all +S 1 ┋World! x┋ +A └┛ alt +S 2 ┋ x┋ +A └┛ alt +S 3 ┋ x┋ +A └┛ alt +S 4 ┋ x┋ +A └┛ alt +S 5 ┋ x┋ +A └┛ alt +S 6 ┋ x┋ +A └┛ alt +S 7 ┋ x┋ +A └┛ alt +S 8 ┋ x┋ +A └┛ alt +S 9 ┋ x┋ +A └┛ alt +S 10 ┋ x┋ +A └┛ alt +S 11 ┋ x┋ +A └┛ alt +S 12 ┋ x┋ +A └┛ alt +S 13 ┋ x┋ +A └┛ alt +S 14 ┋ x┋ +A └┛ alt +S 15 ┋ x┋ +A └┛ alt +S 16 ┋ x┋ +A └┛ alt +S 17 ┋ x┋ +A └┛ alt +S 18 ┋ x┋ +A └┛ alt +S 19 ┋ x┋ +A └┛ alt +S 20 ┋ x┋ +A └┛ alt +S 21 ┋ x┋ +A └┛ alt +S 22 ┋ x┋ +A └┛ alt +S 23 ┋ x┋ +A └┛ alt +S 24 ┋ ┋ +A ···············································································└ backspace +A └┛ alt +A ···············································································└ backspace +A ··············································································└ [4h +CSI Replace mode +S 24 ┋ ┋ +A ···············································································└ carriage-return +CSI Erase all +CSI Use normal screen buffer +CTRL restore cursor +S 24 ┋ ┋ +A └ carriage-return +CSI Normal cursor keys +CTRL Normal keypad diff --git a/test/listview_output.2 b/test/listview_output.2 index 6f8dece0..f3105f1d 100644 --- a/test/listview_output.2 +++ b/test/listview_output.2 @@ -1 +1,69 @@ -read 1b29301b371b5b3f3437681b5b313b3234721b5b6d1b5b346c1b5b481b5b324a656c6c6f1b5b3735430e780f1b5b323b31480e0f6f726c64211b5b3734430e780f1b5b333b3830480e780f1b5b343b3830480e780f1b5b353b3830480e780f1b5b363b3830480e780f1b5b373b3830480e780f1b5b383b3830480e780f1b5b393b3830480e780f1b5b31303b3830480e780f1b5b31313b3830480e780f1b5b31323b3830480e780f1b5b31333b3830480e780f1b5b31343b3830480e780f1b5b31353b3830480e780f1b5b31363b3830480e780f1b5b31373b3830480e780f1b5b31383b3830480e780f1b5b31393b3830480e780f1b5b32303b3830480e780f1b5b32313b3830480e780f1b5b32323b3830480e780f1b5b32333b3830480e780f1b5b32343b3830480e0f080e780f080e1b5b34680f201b5b346c0d1b5b32343b31481b5b324a1b5b3f34376c1b380d1b5b3f316c1b3e +CTRL Use alt charset +CTRL save cursor +CSI Use alternate screen buffer +CSI set scrolling region 1-24 +S -1 ┋ ┋ +A └ normal +CSI Replace mode +CSI Erase all +S 1 ┋ello x┋ +A └┛ alt +S 2 ┋orld! x┋ +A └┛ alt +S 3 ┋ x┋ +A └┛ alt +S 4 ┋ x┋ +A └┛ alt +S 5 ┋ x┋ +A └┛ alt +S 6 ┋ x┋ +A └┛ alt +S 7 ┋ x┋ +A └┛ alt +S 8 ┋ x┋ +A └┛ alt +S 9 ┋ x┋ +A └┛ alt +S 10 ┋ x┋ +A └┛ alt +S 11 ┋ x┋ +A └┛ alt +S 12 ┋ x┋ +A └┛ alt +S 13 ┋ x┋ +A └┛ alt +S 14 ┋ x┋ +A └┛ alt +S 15 ┋ x┋ +A └┛ alt +S 16 ┋ x┋ +A └┛ alt +S 17 ┋ x┋ +A └┛ alt +S 18 ┋ x┋ +A └┛ alt +S 19 ┋ x┋ +A └┛ alt +S 20 ┋ x┋ +A └┛ alt +S 21 ┋ x┋ +A └┛ alt +S 22 ┋ x┋ +A └┛ alt +S 23 ┋ x┋ +A └┛ alt +S 24 ┋ ┋ +A ···············································································└ backspace +A └┛ alt +A ···············································································└ backspace +A ··············································································└ [4h +CSI Replace mode +S 24 ┋ ┋ +A ···············································································└ carriage-return +CSI Erase all +CSI Use normal screen buffer +CTRL restore cursor +S 24 ┋ ┋ +A └ carriage-return +CSI Normal cursor keys +CTRL Normal keypad diff --git a/test/listview_output.3 b/test/listview_output.3 index dac9b251..dbeab53e 100644 --- a/test/listview_output.3 +++ b/test/listview_output.3 @@ -1 +1,69 @@ -read 1b29301b371b5b3f3437681b5b313b3234721b5b6d1b5b346c1b5b481b5b324a6f726c64211b5b3734430e780f1b5b323b3830480e780f1b5b333b3830480e780f1b5b343b3830480e780f1b5b353b3830480e780f1b5b363b3830480e780f1b5b373b3830480e780f1b5b383b3830480e780f1b5b393b3830480e780f1b5b31303b3830480e780f1b5b31313b3830480e780f1b5b31323b3830480e780f1b5b31333b3830480e780f1b5b31343b3830480e780f1b5b31353b3830480e780f1b5b31363b3830480e780f1b5b31373b3830480e780f1b5b31383b3830480e780f1b5b31393b3830480e780f1b5b32303b3830480e780f1b5b32313b3830480e780f1b5b32323b3830480e780f1b5b32333b3830480e780f1b5b32343b3830480e0f080e780f080e1b5b34680f201b5b346c0d1b5b32343b31481b5b324a1b5b3f34376c1b380d1b5b3f316c1b3e +CTRL Use alt charset +CTRL save cursor +CSI Use alternate screen buffer +CSI set scrolling region 1-24 +S -1 ┋ ┋ +A └ normal +CSI Replace mode +CSI Erase all +S 1 ┋orld! x┋ +A └┛ alt +S 2 ┋ x┋ +A └┛ alt +S 3 ┋ x┋ +A └┛ alt +S 4 ┋ x┋ +A └┛ alt +S 5 ┋ x┋ +A └┛ alt +S 6 ┋ x┋ +A └┛ alt +S 7 ┋ x┋ +A └┛ alt +S 8 ┋ x┋ +A └┛ alt +S 9 ┋ x┋ +A └┛ alt +S 10 ┋ x┋ +A └┛ alt +S 11 ┋ x┋ +A └┛ alt +S 12 ┋ x┋ +A └┛ alt +S 13 ┋ x┋ +A └┛ alt +S 14 ┋ x┋ +A └┛ alt +S 15 ┋ x┋ +A └┛ alt +S 16 ┋ x┋ +A └┛ alt +S 17 ┋ x┋ +A └┛ alt +S 18 ┋ x┋ +A └┛ alt +S 19 ┋ x┋ +A └┛ alt +S 20 ┋ x┋ +A └┛ alt +S 21 ┋ x┋ +A └┛ alt +S 22 ┋ x┋ +A └┛ alt +S 23 ┋ x┋ +A └┛ alt +S 24 ┋ ┋ +A ···············································································└ backspace +A └┛ alt +A ···············································································└ backspace +A ··············································································└ [4h +CSI Replace mode +S 24 ┋ ┋ +A ···············································································└ carriage-return +CSI Erase all +CSI Use normal screen buffer +CTRL restore cursor +S 24 ┋ ┋ +A └ carriage-return +CSI Normal cursor keys +CTRL Normal keypad diff --git a/test/listview_output.4 b/test/listview_output.4 index 0e6ee8b9..2e8cbe21 100644 --- a/test/listview_output.4 +++ b/test/listview_output.4 @@ -1 +1,67 @@ -read 1b29301b371b5b3f3437681b5b313b3234721b5b6d1b5b346c1b5b481b5b324a0a48656c6c6f1b5b3734430e780f1b5b333b31480e0f576f726c64211b5b3733430e780f1b5b343b31480e0f321b5b3738430e780f1b5b353b31480e0f331b5b3738430e780f1b5b363b31480e0f341b5b3738430e780f1b5b373b31480e0f351b5b3738430e780f1b5b383b31480e0f361b5b3738430e780f1b5b393b31480e0f371b5b3738430e780f1b5b31303b31480e0f381b5b3738430e780f1b5b31313b31480e0f391b5b3738430e780f1b5b31323b31480e0f31301b5b3737430e780f1b5b31333b31480e0f31311b5b3737430e780f1b5b31343b31480e0f31321b5b3737430e780f1b5b31353b31480e0f31331b5b3737430e780f1b5b31363b31480e0f31341b5b3737430e780f1b5b31373b31480e0f31351b5b3737430e780f1b5b31383b31480e0f31361b5b3737430e780f1b5b31393b31480e0f31371b5b3737430e780f1b5b32303b31480e0f31381b5b3737430e780f1b5b32313b31480e0f31391b5b3737430e780f1b5b32323b31480e0f32301b5b3737430e780f1b5b32333b31480e0f32311b5b3737430e780f1b5b32343b31480e0f32321b5b373743080e780f080e1b5b34680f201b5b346c0d1b5b32343b31481b5b324a1b5b3f34376c1b380d1b5b3f316c1b3e +CTRL Use alt charset +CTRL save cursor +CSI Use alternate screen buffer +CSI set scrolling region 1-24 +S -1 ┋ ┋ +A └ normal +CSI Replace mode +CSI Erase all +S 2 ┋Hello x┋ +A └┛ alt +S 3 ┋World! x┋ +A └┛ alt +S 4 ┋2 x┋ +A └┛ alt +S 5 ┋3 x┋ +A └┛ alt +S 6 ┋4 x┋ +A └┛ alt +S 7 ┋5 x┋ +A └┛ alt +S 8 ┋6 x┋ +A └┛ alt +S 9 ┋7 x┋ +A └┛ alt +S 10 ┋8 x┋ +A └┛ alt +S 11 ┋9 x┋ +A └┛ alt +S 12 ┋10 x┋ +A └┛ alt +S 13 ┋11 x┋ +A └┛ alt +S 14 ┋12 x┋ +A └┛ alt +S 15 ┋13 x┋ +A └┛ alt +S 16 ┋14 x┋ +A └┛ alt +S 17 ┋15 x┋ +A └┛ alt +S 18 ┋16 x┋ +A └┛ alt +S 19 ┋17 x┋ +A └┛ alt +S 20 ┋18 x┋ +A └┛ alt +S 21 ┋19 x┋ +A └┛ alt +S 22 ┋20 x┋ +A └┛ alt +S 23 ┋21 x┋ +A └┛ alt +S 24 ┋22 ┋ +A ···············································································└ backspace +A └┛ alt +A ···············································································└ backspace +A ··············································································└ [4h +CSI Replace mode +S 24 ┋ ┋ +A ···············································································└ carriage-return +CSI Erase all +CSI Use normal screen buffer +CTRL restore cursor +S 24 ┋ ┋ +A └ carriage-return +CSI Normal cursor keys +CTRL Normal keypad diff --git a/test/listview_output.5 b/test/listview_output.5 index 8ef310ae..dd0e4d43 100644 --- a/test/listview_output.5 +++ b/test/listview_output.5 @@ -1 +1,59 @@ -read 1b29301b371b5b3f3437681b5b313b3234721b5b6d1b5b346c1b5b481b5b324a0a48656c6c6f1b5b3734430e780f1b5b333b31480e0f576f726c64211b5b3733430e780f1b5b343b31480e0f321b5b3738430e780f1b5b353b31480e0f331b5b3738430e780f1b5b363b31480e0f341b5b3738430e780f1b5b373b31480e0f351b5b3738430e780f1b5b383b31480e0f361b5b3738430e780f1b5b393b31480e0f371b5b3738430e780f1b5b31303b31480e0f381b5b3738430e780f1b5b31313b31480e0f391b5b3738430e780f1b5b31323b31480e0f31301b5b3737430e780f1b5b31333b31480e0f31311b5b3737430e780f1b5b31343b31480e0f31321b5b3737430e780f1b5b31353b31480e0f31331b5b3737430e780f1b5b31363b31480e0f31341b5b3737430e780f1b5b31373b31480e0f31351b5b3737430e780f1b5b31383b31480e0f31361b5b3737430e780f1b5b31393b31480e0f31371b5b3737430e780f1b5b32303b31480e0f31381b5b3737430e780f1b5b32313b31480e0f31391b5b3737430e780f1b5b32323b31480e0f32301b5b3737430e780f1b5b32333b31480e0f32311b5b3737430e780f1b5b32333b31480e0f1b5b32343b31481b5b324a1b5b3f34376c1b380d1b5b3f316c1b3e +CTRL Use alt charset +CTRL save cursor +CSI Use alternate screen buffer +CSI set scrolling region 1-24 +S -1 ┋ ┋ +A └ normal +CSI Replace mode +CSI Erase all +S 2 ┋Hello x┋ +A └┛ alt +S 3 ┋World! x┋ +A └┛ alt +S 4 ┋2 x┋ +A └┛ alt +S 5 ┋3 x┋ +A └┛ alt +S 6 ┋4 x┋ +A └┛ alt +S 7 ┋5 x┋ +A └┛ alt +S 8 ┋6 x┋ +A └┛ alt +S 9 ┋7 x┋ +A └┛ alt +S 10 ┋8 x┋ +A └┛ alt +S 11 ┋9 x┋ +A └┛ alt +S 12 ┋10 x┋ +A └┛ alt +S 13 ┋11 x┋ +A └┛ alt +S 14 ┋12 x┋ +A └┛ alt +S 15 ┋13 x┋ +A └┛ alt +S 16 ┋14 x┋ +A └┛ alt +S 17 ┋15 x┋ +A └┛ alt +S 18 ┋16 x┋ +A └┛ alt +S 19 ┋17 x┋ +A └┛ alt +S 20 ┋18 x┋ +A └┛ alt +S 21 ┋19 x┋ +A └┛ alt +S 22 ┋20 x┋ +A └┛ alt +S 23 ┋21 x┋ +A └┛ alt +CSI Erase all +CSI Use normal screen buffer +CTRL restore cursor +S 24 ┋ ┋ +A └ carriage-return +CSI Normal cursor keys +CTRL Normal keypad diff --git a/test/listview_output.6 b/test/listview_output.6 index 2c0a2b48..a353b1de 100644 --- a/test/listview_output.6 +++ b/test/listview_output.6 @@ -1 +1,59 @@ -read 1b29301b371b5b3f3437681b5b313b3234721b5b6d1b5b346c1b5b481b5b324a0a576f726c64211b5b3733430e780f1b5b333b31480e0f321b5b3738430e780f1b5b343b31480e0f331b5b3738430e780f1b5b353b31480e0f341b5b3738430e780f1b5b363b31480e0f351b5b3738430e780f1b5b373b31480e0f361b5b3738430e780f1b5b383b31480e0f371b5b3738430e780f1b5b393b31480e0f381b5b3738430e780f1b5b31303b31480e0f391b5b3738430e780f1b5b31313b31480e0f31301b5b3737430e780f1b5b31323b31480e0f31311b5b3737430e780f1b5b31333b31480e0f31321b5b3737430e780f1b5b31343b31480e0f31331b5b3737430e780f1b5b31353b31480e0f31341b5b3737430e780f1b5b31363b31480e0f31351b5b3737430e780f1b5b31373b31480e0f31361b5b3737430e780f1b5b31383b31480e0f31371b5b3737430e780f1b5b31393b31480e0f31381b5b3737430e780f1b5b32303b31480e0f31391b5b3737430e780f1b5b32313b31480e0f32301b5b3737430e780f1b5b32323b31480e0f32311b5b3737430e780f1b5b32333b31480e0f32321b5b3737430e780f1b5b32333b31480e0f1b5b32343b31481b5b324a1b5b3f34376c1b380d1b5b3f316c1b3e +CTRL Use alt charset +CTRL save cursor +CSI Use alternate screen buffer +CSI set scrolling region 1-24 +S -1 ┋ ┋ +A └ normal +CSI Replace mode +CSI Erase all +S 2 ┋World! x┋ +A └┛ alt +S 3 ┋2 x┋ +A └┛ alt +S 4 ┋3 x┋ +A └┛ alt +S 5 ┋4 x┋ +A └┛ alt +S 6 ┋5 x┋ +A └┛ alt +S 7 ┋6 x┋ +A └┛ alt +S 8 ┋7 x┋ +A └┛ alt +S 9 ┋8 x┋ +A └┛ alt +S 10 ┋9 x┋ +A └┛ alt +S 11 ┋10 x┋ +A └┛ alt +S 12 ┋11 x┋ +A └┛ alt +S 13 ┋12 x┋ +A └┛ alt +S 14 ┋13 x┋ +A └┛ alt +S 15 ┋14 x┋ +A └┛ alt +S 16 ┋15 x┋ +A └┛ alt +S 17 ┋16 x┋ +A └┛ alt +S 18 ┋17 x┋ +A └┛ alt +S 19 ┋18 x┋ +A └┛ alt +S 20 ┋19 x┋ +A └┛ alt +S 21 ┋20 x┋ +A └┛ alt +S 22 ┋21 x┋ +A └┛ alt +S 23 ┋22 x┋ +A └┛ alt +CSI Erase all +CSI Use normal screen buffer +CTRL restore cursor +S 24 ┋ ┋ +A └ carriage-return +CSI Normal cursor keys +CTRL Normal keypad diff --git a/test/lnav_doctests.cc b/test/lnav_doctests.cc index 99c12ce1..11a0c1f0 100644 --- a/test/lnav_doctests.cc +++ b/test/lnav_doctests.cc @@ -115,12 +115,7 @@ TEST_CASE("ptime_fmt") { TEST_CASE("rgb_color from string") { string name = "SkyBlue1"; - rgb_color color; - string errmsg; - bool rc; - - rc = rgb_color::from_str(name, color, errmsg); - CHECK(rc); + auto color = rgb_color::from_str(name).unwrap(); CHECK(color.rc_r == 135); CHECK(color.rc_g == 215); CHECK(color.rc_b == 255); diff --git a/test/mvwattrline_output.0 b/test/mvwattrline_output.0 index 61182cee..e91cc643 100644 --- a/test/mvwattrline_output.0 +++ b/test/mvwattrline_output.0 @@ -1 +1,49 @@ -read 1b29301b371b5b3f3437681b5b313b3234721b5b6d1b5b346c1b5b6d1b5b6d1b5b33376d1b5b34306d1b5b313b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b323b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b333b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b343b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b353b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b363b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b373b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b383b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b393b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b31303b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b31313b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b31323b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b31333b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b31343b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b31353b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b31363b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b31373b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b31383b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b31393b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b32303b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b32313b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b32323b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b32333b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b32343b3148202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200820081b5b3468201b5b346c1b5b481b5b33376d1b5b34306d506c61696e20746578740d0a1b5b376d20202020202020201b5b6d1b5b33376d1b5b34306d4c656164696e67207461620d0a541b5b376d616220202020201b5b6d1b5b33376d1b5b34306d7769746820746578740d0a5461621b5b376d20202020201b5b6d1b5b33376d1b5b34306d7769746820746578742023320d0a54657874201b5b33316d1b5b34306d77691b5b376d74681b5b33376d1b5b34306d206d691b5b6d1b5b33376d1b5b34306d78656420617474726962757465732e0d0a54657874207769746820756e69636f646520ffffffe2ffffff96ffffffb6201b5b376d636861721b5b6d1b5b33376d1b5b34306d6163746572730d0a1b5b6d1b5b6d1b5b33376d1b5b34306d1b5b6d1b5b3137421b5b4b1b5b32343b31481b5b324a1b5b3f34376c1b380d1b5b3f316c1b3e +CTRL Use alt charset +CTRL save cursor +CSI Use alternate screen buffer +CSI set scrolling region 1-24 +S -1 ┋ ┋ +A └ normal +CSI Replace mode +S -1 ┋ ┋ +A └ normal, normal, normal +CSI Erase all +S 1 ┋Plain text ┋ +A ··········└ carriage-return +S 2 ┋ Leading tab ┋ +A └ inverse │ +A ········└ normal │ +A ···················└ carriage-return +S 3 ┋Tab with text ┋ +A ·└ inverse │ +A ········└ normal │ +A ·················└ carriage-return +S 4 ┋Tab with text #2 ┋ +A ···└ inverse │ +A ········└ normal │ +A ····················└ carriage-return +S 5 ┋Two tabs with text ┋ +A ········└ inverse │ │ +A ··········└ normal │ │ +A ················└ inverse│ +A ····················└ normal +A ·························└ carriage-return +S 6 ┋Text with mixed attributes. ┋ +A ·····└ fg(#800000) │ +A ·······└ inverse │ +A ·········└ normal │ +A ············└ normal │ +A ···························└ carriage-return +S 7 ┋Text with unicode ▶ characters ┋ +A ····················└ inverse │ +A ························└ normal +A ······························└ carriage-return +S 8 ┋ ┋ +A └ normal +CSI Erase all +CSI Use normal screen buffer +CTRL restore cursor +S 24 ┋ ┋ +A └ carriage-return +CSI Normal cursor keys +CTRL Normal keypad diff --git a/test/scripty.cc b/test/scripty.cc index cedb62f5..928d4ace 100644 --- a/test/scripty.cc +++ b/test/scripty.cc @@ -70,13 +70,31 @@ #include #endif +#include #include +#include +#include #include +#include + +#include "ww898/cp_utf8.hpp" +#include "fmt/format.h" +#include "ghc/filesystem.hpp" #include "auto_fd.hh" +#include "auto_mem.hh" +#include "termios_guard.hh" +#include "styling.hh" using namespace std; +std::string repeat(const std::string& input, size_t num) +{ + std::ostringstream os; + std::fill_n(std::ostream_iterator(os), num, input); + return os.str(); +} + /** * An RAII class for opening a PTY and forking a child process. */ @@ -113,8 +131,8 @@ public: if (openpty(this->ct_master.out(), slave.out(), - NULL, - NULL, + nullptr, + nullptr, &ws) < 0) { throw error(errno); } @@ -150,7 +168,7 @@ public: } }; - int wait_for_child(void) + int wait_for_child() { int retval = -1; @@ -164,13 +182,13 @@ public: return retval; }; - bool is_child() + bool is_child() const { return this->ct_child == 0; }; - pid_t get_child_pid() + pid_t get_child_pid() const { return this->ct_child; }; - int get_fd() + int get_fd() const { return this->ct_master; }; protected: @@ -214,126 +232,552 @@ static void dump_memory(FILE *dst, const char *src, int len) } } -static char *hex2bits(const char *src) +static std::vector hex2bits(const char *src) { - int len, pos = sizeof(int); - char *retval; + std::vector retval; - len = strlen(src) / 2; - retval = new char[sizeof(uint32_t) + len]; - *((uint32_t *) retval) = len; - while ((size_t) pos < (sizeof(uint32_t) + len)) { + for (size_t lpc = 0; src[lpc] && isnumber(src[lpc]); lpc += 2) { int val; - sscanf(src, "%2x", &val); - src += 2; - retval[pos] = (char) val; - pos += 1; + sscanf(&src[lpc], "%2x", &val); + retval.push_back((char) val); } return retval; } typedef enum { - ET_NONE, - ET_READ, -} expect_type_t; + CT_WRITE, +} command_type_t; -struct expect_read { - uint32_t er_length; - char er_data[]; +struct command { + command_type_t c_type; + vector c_arg; }; -struct expect { - expect_type_t e_type; - union { - struct expect_read *read; - } e_arg; -}; +static struct { + const char *sd_program_name{nullptr}; + sig_atomic_t sd_looping{true}; + + pid_t sd_child_pid{-1}; + + ghc::filesystem::path sd_actual_name; + auto_mem sd_from_child{fclose}; + ghc::filesystem::path sd_expected_name; -struct expect_read_state { - uint32_t ers_pos; + deque sd_replay; +} scripty_data; + +static const std::map CSI_TO_DESC = { + {")0", "Use alt charset"}, + + {"[?1000l", "Don't Send Mouse X & Y"}, + {"[?1002l", "Don’t Use Cell Motion Mouse Tracking"}, + {"[?1006l", "Don't ..."}, + {"[?1h", "Application cursor keys"}, + {"[?1l", "Normal cursor keys"}, + {"[?47h", "Use alternate screen buffer"}, + {"[?47l", "Use normal screen buffer"}, + {"[4l", "Replace mode"}, + {"[2J", "Erase all"}, }; -class expect_handler { -public: - expect_handler() - { - memset(&this->eh_state, 0, sizeof(this->eh_state)); +struct term_machine { + enum class state { + NORMAL, + ESCAPE_START, + ESCAPE_FIXED_LENGTH, + ESCAPE_VARIABLE_LENGTH, + ESCAPE_OSC, }; - int process_input(const char *buffer, size_t blen) - { - if (this->eh_queue.empty()) - return 0; - - uint32_t &exp_pos = this->eh_state.es_read.ers_pos; - struct expect &next = this->eh_queue.front(); - int cmp_len = min((next.e_arg.read->er_length - exp_pos), - (uint32_t) blen); - char *exp_start = &next.e_arg.read->er_data[this->eh_state.es_read.ers_pos]; - int retval = 0; - - assert(buffer != NULL || blen == 0); - - if (memcmp(exp_start, buffer, cmp_len) == 0) { - exp_pos += cmp_len; - if (exp_pos == next.e_arg.read->er_length) { - retval = 1; - if (!this->eh_queue.empty()) { - exp_pos = 0; - this->eh_queue.pop(); + struct term_attr { + term_attr(size_t pos, const std::string& desc) + : ta_pos(pos), ta_end(pos), ta_desc({desc}) { + } + + term_attr(size_t pos, size_t end, const std::string& desc) + : ta_pos(pos), ta_end(end), ta_desc({desc}) { + } + + size_t ta_pos; + size_t ta_end; + std::vector ta_desc; + }; + + term_machine(child_term& ct) : tm_child_term(ct) { + this->clear(); + } + + ~term_machine() { + this->flush_line(); + } + + void clear() { + std::fill(begin(this->tm_line), end(this->tm_line), ' '); + this->tm_line_attrs.clear(); + this->tm_new_data = false; + } + + void add_line_attr(const std::string& desc) { + if (!this->tm_line_attrs.empty() && + this->tm_line_attrs.back().ta_pos == this->tm_cursor_x) { + this->tm_line_attrs.back().ta_desc.emplace_back(desc); + } else { + this->tm_line_attrs.emplace_back(this->tm_cursor_x, desc); + } + } + + void write_char(char ch) { + if (isprint(ch)) { + require(ch); + + this->tm_new_data = true; + this->tm_line[this->tm_cursor_x++] = (unsigned char) ch; + } else { + switch (ch) { + case '\a': + this->flush_line(); + fprintf(scripty_data.sd_from_child, "CTRL bell\n"); + break; + case '\x08': + this->add_line_attr("backspace"); + if (this->tm_cursor_x > 0) { + this->tm_cursor_x -= 1; + } + break; + case '\r': + this->add_line_attr("carriage-return"); + this->tm_cursor_x = 0; + break; + case '\n': + this->flush_line(); + if (this->tm_cursor_y >= 0) { + this->tm_cursor_y += 1; + } + this->tm_cursor_x = 0; + break; + case '\x0e': + this->tm_shift_start = this->tm_cursor_x; + break; + case '\x0f': + if (this->tm_shift_start != this->tm_cursor_x) { + this->tm_line_attrs.emplace_back(this->tm_shift_start, + this->tm_cursor_x, + "alt"); + } + break; + default: + require(ch); + this->tm_new_data = true; + this->tm_line[this->tm_cursor_x++] = (unsigned char) ch; + break; + } + } + } + + void flush_line() { + if (std::exchange(this->tm_waiting_on_input, false) && + !this->tm_user_input.empty()) { + fprintf(stderr, "flush keys\n"); + fprintf(scripty_data.sd_from_child, "K "); + dump_memory(scripty_data.sd_from_child, + this->tm_user_input.data(), + this->tm_user_input.size()); + fprintf(scripty_data.sd_from_child, "\n"); + this->tm_user_input.clear(); + } + if (this->tm_new_data || !this->tm_line_attrs.empty()) { + // fprintf(scripty_data.sd_from_child, "flush %d\n", this->tm_flush_count); + fprintf(stderr, "flush %d\n", this->tm_flush_count++); + fprintf(scripty_data.sd_from_child, + "S % 3d \u250B", + this->tm_cursor_y); + for (auto uch : this->tm_line) { + ww898::utf::utf8::write(uch, [](auto ch) { + fputc(ch, scripty_data.sd_from_child); + }); + } + fprintf(scripty_data.sd_from_child, "\u250B\n"); + for (size_t lpc = 0; lpc < this->tm_line_attrs.size(); lpc++) { + const auto& ta = this->tm_line_attrs[lpc]; + auto full_desc = fmt::format("{}", fmt::join(ta.ta_desc.begin(), + ta.ta_desc.end(), + ", ")); + int line_len; + + if (ta.ta_pos == ta.ta_end) { + line_len = fprintf(scripty_data.sd_from_child, + "A %s%s %s", + repeat("\u00B7", ta.ta_pos).c_str(), + ((lpc + 1 < + this->tm_line_attrs.size()) && + (ta.ta_pos == + this->tm_line_attrs[lpc + 1].ta_pos)) ? + "\u251C" : + "\u2514", + full_desc.c_str()); + line_len -= 2 + ta.ta_pos; + } else { + line_len = fprintf(scripty_data.sd_from_child, + "A %s%s%s\u251b %s", + std::string(ta.ta_pos, ' ').c_str(), + ((lpc + 1 < + this->tm_line_attrs.size()) && + (ta.ta_pos == + this->tm_line_attrs[lpc + 1].ta_pos)) ? + "\u2518" : + "\u2514", + std::string(ta.ta_end - ta.ta_pos - 1, '-').c_str(), + full_desc.c_str()); + line_len -= 4; } + for (size_t lpc2 = lpc + 1; lpc2 < this->tm_line_attrs.size(); lpc2++) { + auto bar_pos = 7 + this->tm_line_attrs[lpc2].ta_pos; + + if (bar_pos < line_len) { + continue; + } + line_len += fprintf(scripty_data.sd_from_child, "%s\u2502", + std::string(bar_pos - line_len, ' ').c_str()); + line_len -= 2; + } + fprintf(scripty_data.sd_from_child, "\n"); } - } else { - printf("Detected output differences at offset %d, " - "expecting:\n ", exp_pos); - dump_memory(stdout, exp_start, cmp_len); - printf("\nGot:\n "); - dump_memory(stdout, buffer, cmp_len); - retval = -1; + this->clear(); } + fflush(scripty_data.sd_from_child); + } + + std::vector get_m_params() { + std::vector retval; + size_t index = 1; + + while (index < this->tm_escape_buffer.size()) { + int val, last; - fprintf(stderr, "pi ret %d\n", retval); + if (sscanf(&this->tm_escape_buffer[index], "%d%n", &val, &last) == 1) { + retval.push_back(val); + index += last; + if (this->tm_escape_buffer[index] != ';') { + break; + } + index += 1; + } else { + break; + } + } return retval; - }; + } - queue eh_queue; -private: - union { - struct expect_read_state es_read; - } eh_state; -}; + void new_user_input(char ch) { + this->tm_user_input.push_back(ch); + } -typedef enum { - CT_SLEEP, - CT_WRITE, -} command_type_t; + void new_input(char ch) { + if (this->tm_unicode_remaining > 0) { + this->tm_unicode_buffer.push_back(ch); + this->tm_unicode_remaining -= 1; + if (this->tm_unicode_remaining == 0) { + this->tm_new_data = true; + this->tm_line[this->tm_cursor_x++] = ww898::utf::utf8::read( + [this]() { + auto retval = this->tm_unicode_buffer.front(); + + this->tm_unicode_buffer.pop_front(); + return retval; + }); + } + return; + } else { + auto utfsize = ww898::utf::utf8::char_size([ch]() { + return std::make_pair(ch, 16); + }); -struct command { - command_type_t c_type; - union { - char *b; - } c_arg; -}; -static struct { - const char *sd_program_name; - sig_atomic_t sd_looping; + if (utfsize.unwrap() > 1) { + this->tm_unicode_remaining = utfsize.unwrap() - 1; + this->tm_unicode_buffer.push_back(ch); + return; + } + } - pid_t sd_child_pid; + switch (this->tm_state) { + case state::NORMAL: { + switch (ch) { + case '\x1b': { + this->tm_escape_buffer.clear(); + this->tm_state = state::ESCAPE_START; + break; + } + default: { + this->write_char(ch); + break; + } + } + break; + } + case state::ESCAPE_START: { + switch (ch) { + case '[': { + this->tm_escape_buffer.push_back(ch); + this->tm_state = state::ESCAPE_VARIABLE_LENGTH; + break; + } + case ']': { + this->tm_escape_buffer.push_back(ch); + this->tm_state = state::ESCAPE_OSC; + break; + } + case '(': + case ')': + case '*': + case '+': { + this->tm_state = state::ESCAPE_FIXED_LENGTH; + this->tm_escape_buffer.push_back(ch); + this->tm_escape_expected_size = 2; + break; + } + default: { + this->flush_line(); + switch (ch) { + case '7': + fprintf(scripty_data.sd_from_child, + "CTRL save cursor\n"); + break; + case '8': + fprintf(scripty_data.sd_from_child, + "CTRL restore cursor\n"); + break; + case '>': + fprintf(scripty_data.sd_from_child, + "CTRL Normal keypad\n"); + break; + default: { + fprintf(scripty_data.sd_from_child, + "CTRL %c\n", + ch); + break; + } + } + this->tm_state = state::NORMAL; + break; + } + } + break; + } + case state::ESCAPE_FIXED_LENGTH: { + this->tm_escape_buffer.push_back(ch); + if (this->tm_escape_buffer.size() == this->tm_escape_expected_size) { + auto iter = CSI_TO_DESC.find( + std::string(this->tm_escape_buffer.data(), + this->tm_escape_buffer.size())); + this->flush_line(); + if (iter == CSI_TO_DESC.end()) { + fprintf(scripty_data.sd_from_child, + "CTRL %.*s\n", + (int) this->tm_escape_buffer.size(), + this->tm_escape_buffer.data()); + } else { + fprintf(scripty_data.sd_from_child, + "CTRL %s\n", + iter->second.c_str()); + } + this->tm_state = state::NORMAL; + } + break; + } + case state::ESCAPE_VARIABLE_LENGTH: { + this->tm_escape_buffer.push_back(ch); + if (isalpha(ch)) { + auto iter = CSI_TO_DESC.find( + std::string(this->tm_escape_buffer.data(), + this->tm_escape_buffer.size())); + if (iter == CSI_TO_DESC.end()) { + this->tm_escape_buffer.push_back('\0'); + switch (ch) { + case 'C': { + auto amount = this->get_m_params(); + + this->tm_cursor_x += amount[0]; + break; + } + case 'J': { + auto param = this->get_m_params(); + + this->flush_line(); + + auto region = param.empty() ? 0 : param[0]; + switch (region) { + case 0: + fprintf(scripty_data.sd_from_child, + "CSI Erase Below\n"); + break; + case 1: + fprintf(scripty_data.sd_from_child, + "CSI Erase Above\n"); + break; + case 2: + fprintf(scripty_data.sd_from_child, + "CSI Erase All\n"); + break; + case 3: + fprintf(scripty_data.sd_from_child, + "CSI Erase Saved Lines\n"); + break; + } + break; + } + case 'H': { + auto coords = this->get_m_params(); + + if (coords.empty()) { + coords = {1, 1}; + } + this->flush_line(); + this->tm_cursor_y = coords[0]; + this->tm_cursor_x = coords[1] - 1; + break; + } + case 'r': { + auto region = this->get_m_params(); - const char *sd_to_child_name; - FILE *sd_to_child; + this->flush_line(); + fprintf(scripty_data.sd_from_child, + "CSI set scrolling region %d-%d\n", + region[0], + region[1]); + break; + } + case 'm': { + auto attrs = this->get_m_params(); + + if (attrs.empty()) { + this->add_line_attr("normal"); + } else if ((30 <= attrs[0]) && (attrs[0] <= 37)) { + auto xt = xterm_colors(); + + this->add_line_attr(fmt::format( + "fg({})", + xt->tc_palette[attrs[0] - 30].xc_hex)); + } else if (attrs[0] == 38) { + auto xt = xterm_colors(); + + require(attrs[1] == 5); + this->add_line_attr(fmt::format( + "fg({})", + xt->tc_palette[attrs[2]].xc_hex)); + } else if ((40 <= attrs[0]) && (attrs[0] <= 47)) { + auto xt = xterm_colors(); + + this->add_line_attr(fmt::format( + "bg({})", xt->tc_palette[attrs[0] - 40].xc_hex)); + } else if (attrs[0] == 48) { + auto xt = xterm_colors(); + + require(attrs[1] == 5); + this->add_line_attr(fmt::format( + "bg({})", + xt->tc_palette[attrs[2]].xc_hex)); + } else { + switch (attrs[0]) { + case 1: + this->add_line_attr("bold"); + break; + case 4: + this->add_line_attr("underline"); + break; + case 5: + this->add_line_attr("blink"); + break; + case 7: + this->add_line_attr("inverse"); + break; + default: + this->add_line_attr(this->tm_escape_buffer.data()); + break; + } + } + break; + } + default: + fprintf(stderr, "missed %c\n", ch); + this->add_line_attr(this->tm_escape_buffer.data()); + break; + } + } else { + this->flush_line(); + fprintf(scripty_data.sd_from_child, + "CSI %s\n", + iter->second.c_str()); + } + this->tm_state = state::NORMAL; + } else { - const char *sd_from_child_name; - FILE *sd_from_child; + } + break; + } + case state::ESCAPE_OSC: { + if (ch == '\a') { + this->tm_escape_buffer.push_back('\0'); + + auto num = this->get_m_params(); + auto semi_index = strchr( + this->tm_escape_buffer.data(), ';'); + + switch (num[0]) { + case 0: { + this->flush_line(); + fprintf(scripty_data.sd_from_child, + "OSC Set window title: %s\n", + semi_index + 1); + break; + } + case 999: { + this->flush_line(); + this->tm_waiting_on_input = true; + if (!scripty_data.sd_replay.empty()) { + const auto &cmd = scripty_data.sd_replay.front(); + + this->tm_user_input = cmd.c_arg; + write(this->tm_child_term.get_fd(), + this->tm_user_input.data(), + this->tm_user_input.size()); + + scripty_data.sd_replay.pop_front(); + } + break; + } + } - queue sd_replay; + this->tm_state = state::NORMAL; + } else { + this->tm_escape_buffer.push_back(ch); + } + break; + } + } + } - bool sd_user_step; -} scripty_data; + child_term& tm_child_term; + bool tm_waiting_on_input{false}; + state tm_state{state::NORMAL}; + std::vector tm_escape_buffer; + std::deque tm_unicode_buffer; + size_t tm_unicode_remaining{0}; + size_t tm_escape_expected_size{0}; + uint32_t tm_line[80]; + bool tm_new_data{false}; + size_t tm_cursor_x{0}; + int tm_cursor_y{-1}; + size_t tm_shift_start{0}; + std::vector tm_line_attrs; + + std::vector tm_user_input; + + size_t tm_flush_count{0}; +}; static void sigchld(int sig) { @@ -344,7 +788,7 @@ static void sigpass(int sig) kill(scripty_data.sd_child_pid, sig); } -static void usage(void) +static void usage() { const char *usage_msg = "usage: %s [-h] [-t to_child] [-f from_child] -- \n" @@ -356,21 +800,17 @@ static void usage(void) " -n Do not pass the output to the console.\n" " -i Pass stdin to the child process instead of connecting\n" " the child to the tty.\n" - " -t The file where any input sent to the child process\n" + " -a The file where the actual I/O from/to the child process\n" " should be stored.\n" - " -f The file where any output from the child process\n" - " should be stored.\n" - " -r The file containing the input to be sent to the child\n" - " process.\n" - " -e The file containing the expected output from the child\n" + " -e The file containing the expected I/O from/to the child\n" " process.\n" "\n" "Examples:\n" " To record a session for playback later:\n" - " $ scripty -t input.0 -f output.0 -- myCursesApp\n" + " $ scripty -f output.0 -- myCursesApp\n" "\n" " To replay the recorded session:\n" - " $ scripty -r input.0 -- myCursesApp\n"; + " $ scripty -e input.0 -- myCursesApp\n"; fprintf(stderr, usage_msg, scripty_data.sd_program_name); } @@ -378,95 +818,38 @@ static void usage(void) int main(int argc, char *argv[]) { int c, fd, retval = EXIT_SUCCESS; - expect_handler ex_handler; - bool passout = true, passin = false; - FILE *file; + bool passout = true, passin = false, prompt = false; + auto_mem file(fclose); scripty_data.sd_program_name = argv[0]; scripty_data.sd_looping = true; - while ((c = getopt(argc, argv, "ht:f:r:e:nsi")) != -1) { + while ((c = getopt(argc, argv, "ha:e:nip")) != -1) { switch (c) { case 'h': usage(); exit(retval); break; - case 's': - scripty_data.sd_user_step = true; - break; - case 't': - scripty_data.sd_to_child_name = optarg; - break; - case 'f': - scripty_data.sd_from_child_name = optarg; + case 'a': + scripty_data.sd_actual_name = optarg; break; case 'e': - if ((file = fopen(optarg, "r")) == NULL) { + scripty_data.sd_expected_name = optarg; + if ((file = fopen(optarg, "r")) == nullptr) { fprintf(stderr, "error: cannot open %s\n", optarg); retval = EXIT_FAILURE; } else { char line[32 * 1024]; while (fgets(line, sizeof(line), file)) { - char *sp; - - if (line[0] == '#' || - (sp = strchr(line, ' ')) == NULL) { - } else { - struct expect exp; - - *sp = '\0'; - sp += 1; - if (strcmp(line, "read") == 0) { - exp.e_type = ET_READ; - exp.e_arg.read = (struct expect_read *) hex2bits( - sp); - } else { - fprintf(stderr, - "error: unknown command -- %s\n", - line); - retval = EXIT_FAILURE; - } - ex_handler.eh_queue.push(exp); - } - } - fclose(file); - file = NULL; - } - break; - case 'r': - if ((file = fopen(optarg, "r")) == NULL) { - fprintf(stderr, "error: cannot open %s\n", optarg); - retval = EXIT_FAILURE; - } else { - char line[32 * 1024]; - - while (fgets(line, sizeof(line), file)) { - char *sp; - - if (line[0] == '#' || - (sp = strchr(line, ' ')) == NULL) { - } else { + if (line[0] == 'K') { struct command cmd; - *sp = '\0'; - sp += 1; - if (strcmp(line, "sleep") == 0) { - cmd.c_type = CT_SLEEP; - } else if (strcmp(line, "write") == 0) { - cmd.c_type = CT_WRITE; - cmd.c_arg.b = hex2bits(sp); - scripty_data.sd_replay.push(cmd); - } else { - fprintf(stderr, - "error: unknown command -- %s\n", - line); - retval = EXIT_FAILURE; - } + cmd.c_type = CT_WRITE; + cmd.c_arg = hex2bits(&line[2]); + scripty_data.sd_replay.push_back(cmd); } } - fclose(file); - file = NULL; } break; case 'n': @@ -475,7 +858,11 @@ int main(int argc, char *argv[]) case 'i': passin = true; break; + case 'p': + prompt = true; + break; default: + fprintf(stderr, "error: unknown flag -- %c\n", c); retval = EXIT_FAILURE; break; } @@ -484,40 +871,35 @@ int main(int argc, char *argv[]) argc -= optind; argv += optind; - if ((scripty_data.sd_to_child_name != NULL) && - (scripty_data.sd_to_child = - fopen(scripty_data.sd_to_child_name, "w")) == NULL) { - fprintf(stderr, - "error: unable to open %s -- %s\n", - scripty_data.sd_to_child_name, - strerror(errno)); - retval = EXIT_FAILURE; + if (!scripty_data.sd_expected_name.empty() && + scripty_data.sd_actual_name.empty()) { + scripty_data.sd_actual_name = + scripty_data.sd_expected_name.filename(); + scripty_data.sd_actual_name += ".tmp"; } - if (scripty_data.sd_from_child_name != NULL) { - if (strcmp(scripty_data.sd_from_child_name, "-") == 0) { - scripty_data.sd_from_child = stdout; - } else if ((scripty_data.sd_from_child = - fopen(scripty_data.sd_from_child_name, "w")) == NULL) { + if (!scripty_data.sd_actual_name.empty()) { + if ((scripty_data.sd_from_child = fopen( + scripty_data.sd_actual_name.c_str(), "w")) == nullptr) { fprintf(stderr, - "error: unable from open %s -- %s\n", - scripty_data.sd_from_child_name, + "error: unable to open %s -- %s\n", + scripty_data.sd_actual_name.c_str(), strerror(errno)); retval = EXIT_FAILURE; } } - fd = open("/tmp/scripty.err", O_WRONLY | O_CREAT | O_APPEND, 0666); - dup2(fd, STDERR_FILENO); - close(fd); - fprintf(stderr, "startup\n"); - - if (scripty_data.sd_to_child != NULL) - fcntl(fileno(scripty_data.sd_to_child), F_SETFD, 1); - if (scripty_data.sd_from_child != NULL) + if (scripty_data.sd_from_child != nullptr) { fcntl(fileno(scripty_data.sd_from_child), F_SETFD, 1); + } if (retval != EXIT_FAILURE) { + guard_termios gt(STDOUT_FILENO); + fd = open("/tmp/scripty.err", O_WRONLY | O_CREAT | O_APPEND, 0666); + dup2(fd, STDERR_FILENO); + close(fd); + fprintf(stderr, "startup\n"); + child_term ct(passin); if (ct.is_child()) { @@ -525,12 +907,10 @@ int main(int argc, char *argv[]) perror("execvp"); exit(-1); } else { - int maxfd, out_len = 0; - bool got_expected = true; - bool got_user_step = false; + int maxfd; struct timeval last, now; - char out_buffer[8192]; fd_set read_fds; + term_machine tm(ct); scripty_data.sd_child_pid = ct.get_child_pid(); signal(SIGINT, sigpass); @@ -538,7 +918,7 @@ int main(int argc, char *argv[]) signal(SIGCHLD, sigchld); - gettimeofday(&now, NULL); + gettimeofday(&now, nullptr); last = now; FD_ZERO(&read_fds); @@ -549,10 +929,6 @@ int main(int argc, char *argv[]) tty_raw(STDIN_FILENO); - if (!ex_handler.eh_queue.empty()) { - got_expected = false; - } - maxfd = max(STDIN_FILENO, ct.get_fd()); while (scripty_data.sd_looping) { fd_set ready_rfds = read_fds; @@ -561,45 +937,8 @@ int main(int argc, char *argv[]) to.tv_sec = 0; to.tv_usec = 10000; - rc = select(maxfd + 1, &ready_rfds, NULL, NULL, &to); + rc = select(maxfd + 1, &ready_rfds, nullptr, nullptr, &to); if (rc == 0) { - if (!got_expected) { - switch (ex_handler.process_input(NULL, 0)) { - case -1: - scripty_data.sd_looping = false; - retval = EXIT_FAILURE; - break; - case 0: - break; - case 1: - got_expected = true; - break; - } - } - if (!scripty_data.sd_replay.empty() && got_expected && - (!scripty_data.sd_user_step || got_user_step)) { - struct command cmd = scripty_data.sd_replay.front(); - int len; - - fprintf(stderr, " us %d got %d\n", - scripty_data.sd_user_step, got_user_step); - scripty_data.sd_replay.pop(); - fprintf(stderr, "replay %zd\n", - scripty_data.sd_replay.size()); - switch (cmd.c_type) { - case CT_SLEEP: - break; - case CT_WRITE: - len = *((int *) cmd.c_arg.b); - log_perror(write(ct.get_fd(), - cmd.c_arg.b + sizeof(int), - len)); - delete[] cmd.c_arg.b; - break; - } - got_user_step = false; - got_expected = false; - } } else if (rc < 0) { switch (errno) { case EINTR: @@ -613,7 +952,7 @@ int main(int argc, char *argv[]) char buffer[1024]; fprintf(stderr, "fds ready %d\n", rc); - gettimeofday(&now, NULL); + gettimeofday(&now, nullptr); timersub(&now, &last, &diff); if (FD_ISSET(STDIN_FILENO, &ready_rfds)) { rc = read(STDIN_FILENO, buffer, sizeof(buffer)); @@ -621,37 +960,11 @@ int main(int argc, char *argv[]) scripty_data.sd_looping = false; } else if (rc == 0) { FD_CLR(STDIN_FILENO, &read_fds); - } else if (!scripty_data.sd_replay.empty()) { - if (scripty_data.sd_user_step) { - got_user_step = true; - } } else { log_perror(write(ct.get_fd(), buffer, rc)); - if (scripty_data.sd_to_child != NULL) { - fprintf(scripty_data.sd_to_child, - "sleep %ld.%06ld\n" - "write ", - (long) diff.tv_sec, - (long) diff.tv_usec); - dump_memory(scripty_data.sd_to_child, - buffer, - rc); - fprintf(scripty_data.sd_to_child, "\n"); - } - if (scripty_data.sd_from_child != NULL) { - fprintf(stderr, "do write %d\n", out_len); - fprintf(scripty_data.sd_from_child, "read "); - dump_memory(scripty_data.sd_from_child, - out_buffer, - out_len); - fprintf(scripty_data.sd_from_child, "\n"); - fprintf(scripty_data.sd_from_child, - "# write "); - dump_memory(scripty_data.sd_from_child, - buffer, - rc); - fprintf(scripty_data.sd_from_child, "\n"); - out_len = 0; + + for (ssize_t lpc = 0; lpc < rc; lpc++) { + tm.new_user_input(buffer[lpc]); } } } @@ -660,34 +973,15 @@ int main(int argc, char *argv[]) fprintf(stderr, "read rc %d\n", rc); if (rc <= 0) { scripty_data.sd_looping = false; - if (scripty_data.sd_from_child) { - fprintf(scripty_data.sd_from_child, "read "); - dump_memory(scripty_data.sd_from_child, - out_buffer, - out_len); - fprintf(scripty_data.sd_from_child, "\n"); - out_len = 0; - } } else { if (passout) log_perror(write(STDOUT_FILENO, buffer, rc)); - if (scripty_data.sd_from_child != NULL) { - fprintf(stderr, "got out %d\n", rc); - memcpy(&out_buffer[out_len], - buffer, - rc); - out_len += rc; - } - switch (ex_handler.process_input(buffer, rc)) { - case -1: - scripty_data.sd_looping = false; - retval = EXIT_FAILURE; - break; - case 0: - break; - case 1: - got_expected = true; - break; + if (scripty_data.sd_from_child != nullptr) { + for (size_t lpc = 0; lpc < rc; lpc++) { + fprintf(stderr, "ch %02x\n", + buffer[lpc] & 0xff); + tm.new_input(buffer[lpc]); + } } } } @@ -696,22 +990,42 @@ int main(int argc, char *argv[]) } } - if (!ex_handler.eh_queue.empty()) { - fprintf(stderr, "More input expected from child\n"); - retval = EXIT_FAILURE; - } - retval = ct.wait_for_child() || retval; } - if (scripty_data.sd_to_child != NULL) { - fclose(scripty_data.sd_to_child); - scripty_data.sd_to_child = NULL; - } - - if (scripty_data.sd_from_child != NULL) { - fclose(scripty_data.sd_from_child); - scripty_data.sd_from_child = NULL; + if (retval == EXIT_SUCCESS && !scripty_data.sd_expected_name.empty()) { + auto cmd = fmt::format("diff -ua {} {}", + scripty_data.sd_expected_name.string(), + scripty_data.sd_actual_name.string()); + auto rc = system(cmd.c_str()); + if (rc != 0) { + if (prompt) { + char resp[4]; + + printf("Would you like to update the original file? (y/N) "); + fflush(stdout); + log_perror(scanf("%3s", resp)); + if (strcasecmp(resp, "y") == 0) { + printf("Updating: %s -> %s\n", + scripty_data.sd_actual_name.c_str(), + scripty_data.sd_expected_name.c_str()); + + auto options = + ghc::filesystem::copy_options::overwrite_existing; + ghc::filesystem::copy_file( + scripty_data.sd_actual_name, + scripty_data.sd_expected_name, + options); + } + else{ + retval = EXIT_FAILURE; + } + } + else { + fprintf(stderr, "error: mismatch\n"); + retval = EXIT_FAILURE; + } + } } return retval; diff --git a/test/test_vt52_curses.sh b/test/test_vt52_curses.sh index ca9a504c..946e6458 100644 --- a/test/test_vt52_curses.sh +++ b/test/test_vt52_curses.sh @@ -1,6 +1,6 @@ #! /bin/bash -run_test ./scripty -n -r ${srcdir}/vt52_curses_input.0 \ - -e ${srcdir}/vt52_curses_output.0 -- ./drive_vt52_curses < /dev/null +run_test ./scripty -n -e ${srcdir}/vt52_curses_input.0 \ + -- ./drive_vt52_curses < /dev/null on_error_fail_with "single line vt52 did not work?" diff --git a/test/view_colors_output.0 b/test/view_colors_output.0 index 2d0f645b..e10e28e4 100644 --- a/test/view_colors_output.0 +++ b/test/view_colors_output.0 @@ -1 +1,56 @@ -read 1b29301b371b5b3f3437681b5b313b3234721b5b6d1b5b346c1b5b6d1b5b6d1b5b33376d1b5b34306d1b5b313b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b323b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b333b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b343b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b353b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b363b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b373b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b383b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b393b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b31303b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b31313b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b31323b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b31333b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b31343b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b31353b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b31363b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b31373b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b31383b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b31393b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b32303b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b32313b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b32323b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b32333b314820202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020201b5b32343b3148202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200820081b5b3468201b5b346c1b5b481b5b33326d1b5b34376d54686973206973206c696e653a2030202020202020202020202020202020202020202020202020200d0a1b5b33346d1b5b34306d54686973206973206c696e653a2031202020202020202020202020202020202020202020202020200d0a1b5b33326d1b5b34306d54686973206973206c696e653a2032202020202020202020202020202020202020202020202020200d0a1b5b33346d1b5b34376d54686973206973206c696e653a2033202020202020202020202020202020202020202020202020200d0a1b5b33356d1b5b34376d54686973206973206c696e653a2034202020202020202020202020202020202020202020202020200d0a1b5b33366d1b5b34306d54686973206973206c696e653a2035202020202020202020202020202020202020202020202020200d0a1b5b33356d1b5b34306d54686973206973206c696e653a2036202020202020202020202020202020202020202020202020200d0a1b5b33366d1b5b34306d54686973206973206c696e653a2037202020202020202020202020202020202020202020202020200d0a1b5b33346d1b5b34376d54686973206973206c696e653a2038202020202020202020202020202020202020202020202020200d0a1b5b33326d1b5b34306d54686973206973206c696e653a2039202020202020202020202020202020202020202020202020200d0a1b5b33356d1b5b34306d54686973206973206c696e653a2031302020202020202020202020202020202020202020202020200d0a1b5b33366d1b5b34306d54686973206973206c696e653a2031312020202020202020202020202020202020202020202020200d0a1b5b33356d1b5b34376d54686973206973206c696e653a2031322020202020202020202020202020202020202020202020200d0a1b5b33366d1b5b34306d54686973206973206c696e653a2031332020202020202020202020202020202020202020202020200d0a1b5b33326d1b5b34306d54686973206973206c696e653a2031342020202020202020202020202020202020202020202020200d0a1b5b33346d1b5b34376d54686973206973206c696e653a2031352020202020202020202020202020202020202020202020200d0a6265666f7265203c1b5b6d1b5b33366d1b5b34306d1b5b376d3132331b5b6d3e206166746572202020202020202020202020202020202020202020201b5b31373b39481b5b6d1b5b6d1b5b33376d1b5b34306d1b5b6d0d1b5b37421b5b4b1b5b32343b31481b5b324a1b5b3f34376c1b380d1b5b3f316c1b3e +CTRL Use alt charset +CTRL save cursor +CSI Use alternate screen buffer +CSI set scrolling region 1-24 +S -1 ┋ ┋ +A └ normal +CSI Replace mode +S -1 ┋ ┋ +A └ normal, normal, normal +CSI Erase all +S 1 ┋This is line: 0 ┋ +A └ bold │ +A ········································└ carriage-return +S 2 ┋This is line: 1 ┋ +A ········································└ carriage-return +S 3 ┋This is line: 2 ┋ +A ········································└ carriage-return +S 4 ┋This is line: 3 ┋ +A ········································└ carriage-return +S 5 ┋This is line: 4 ┋ +A ········································└ carriage-return +S 6 ┋This is line: 5 ┋ +A ········································└ carriage-return +S 7 ┋This is line: 6 ┋ +A ········································└ carriage-return +S 8 ┋This is line: 7 ┋ +A ········································└ carriage-return +S 9 ┋This is line: 8 ┋ +A ········································└ carriage-return +S 10 ┋This is line: 9 ┋ +A ········································└ carriage-return +S 11 ┋This is line: 10 ┋ +A ········································└ carriage-return +S 12 ┋This is line: 11 ┋ +A ········································└ carriage-return +S 13 ┋This is line: 12 ┋ +A ········································└ carriage-return +S 14 ┋This is line: 13 ┋ +A ········································└ carriage-return +S 15 ┋This is line: 14 ┋ +A ········································└ carriage-return +S 16 ┋This is line: 15 ┋ +A ········································└ carriage-return +S 17 ┋before <123> after ┋ +A └ normal│ │ +A ········└ fg(#008080), inverse +A ···········└ normal +S 17 ┋ ┋ +A ········└ normal +CSI Erase all +CSI Use normal screen buffer +CTRL restore cursor +S 24 ┋ ┋ +A └ carriage-return +CSI Normal cursor keys +CTRL Normal keypad diff --git a/test/vt52_curses_input.0 b/test/vt52_curses_input.0 index 982d97fb..8f8126d8 100644 --- a/test/vt52_curses_input.0 +++ b/test/vt52_curses_input.0 @@ -1,38 +1,48 @@ -sleep 0.867286 -write 0d -sleep 0.141596 -write 0d -sleep 0.188837 -write 0d -sleep 0.177586 -write 0d -sleep 0.159950 -write 0d -sleep 0.158958 -write 0d -sleep 0.164105 -write 0d -sleep 0.176968 -write 0d -sleep 0.165942 -write 0d -sleep 0.187011 -write 0d -sleep 0.167987 -write 0d -sleep 0.173959 -write 0d -sleep 0.176091 -write 0d -sleep 0.180728 -write 0d -sleep 0.172983 -write 0d -sleep 0.167819 -write 0d -sleep 0.165876 -write 0d -sleep 0.175857 -write 0d -sleep 0.183068 -write 0d +CTRL Use alt charset +CTRL save cursor +CSI Use alternate screen buffer +CSI set scrolling region 1-24 +S -1 ┋ ┋ +A └ normal +CSI Replace mode +CSI Erase all +S 1 ┋Gruß ┋ +K 0d +S 1 ┋ ┋ +A ····└ carriage-return +K 0d +CSI Erase Below +K 0d +S 1 ┋de ┋ +K 0d +S 1 ┋ ┋ +A ··└ carriage-return +CSI Erase Below +K 0d +S 1 ┋1 ┋ +K 0d +S 1 ┋ 2 ┋ +K 0d +S 1 ┋ 3 ┋ +K 0d +S 1 ┋ ┋ +A ···└ carriage-return +CSI Erase Below +K 0d +S 1 ┋abc ┋ +K 0d +S 1 ┋ ┋ +A ···└ carriage-return +CSI Erase Below +K 0d +CTRL bell +K 0d +S 1 ┋acdef ┋ +K 0d0d +CSI Erase all +CSI Use normal screen buffer +CTRL restore cursor +S 24 ┋ ┋ +A └ carriage-return +CSI Normal cursor keys +CTRL Normal keypad