#include #include #include #include #ifdef __APPLE__ #include #endif #include #include #include "TerminalFrontend.h" #include "Command.h" #include "Editor.h" bool TerminalFrontend::Init(Editor &ed) { // Enable UTF-8 locale so ncurses and the terminal handle multibyte correctly // This relies on the user's environment (e.g., LANG/LC_ALL) being set to a UTF-8 locale. // If not set, try a couple of common UTF-8 fallbacks. const char *loc = std::setlocale(LC_ALL, ""); auto is_utf8_codeset = []() -> bool { const char *cs = nl_langinfo(CODESET); if (!cs) return false; std::string s(cs); for (auto &ch: s) ch = static_cast(std::toupper(static_cast(ch))); return (s.find("UTF-8") != std::string::npos) || (s.find("UTF8") != std::string::npos); }; bool utf8_ok = (MB_CUR_MAX > 1) && is_utf8_codeset(); if (!utf8_ok) { // Try common UTF-8 locales loc = std::setlocale(LC_CTYPE, "C.UTF-8"); utf8_ok = (loc != nullptr) && (MB_CUR_MAX > 1) && is_utf8_codeset(); if (!utf8_ok) { loc = std::setlocale(LC_CTYPE, "en_US.UTF-8"); utf8_ok = (loc != nullptr) && (MB_CUR_MAX > 1) && is_utf8_codeset(); } if (!utf8_ok) { // macOS often uses plain "UTF-8" locale identifier loc = std::setlocale(LC_CTYPE, "UTF-8"); utf8_ok = (loc != nullptr) && (MB_CUR_MAX > 1) && is_utf8_codeset(); } } // Ensure Control keys reach the app: disable XON/XOFF and dsusp/susp bindings (e.g., ^S/^Q, ^Y on macOS) { struct termios tio{}; if (tcgetattr(STDIN_FILENO, &tio) == 0) { // Save original to restore on shutdown orig_tio_ = tio; have_orig_tio_ = true; // Disable software flow control so C-s/C-q work tio.c_iflag &= static_cast(~IXON); #ifdef IXOFF tio.c_iflag &= static_cast(~IXOFF); #endif // Disable dsusp/susp characters so C-y (VDSUSP on macOS) and C-z don't signal-stop the app #ifdef _POSIX_VDISABLE #ifdef VSUSP tio.c_cc[VSUSP] = _POSIX_VDISABLE; #endif #ifdef VDSUSP tio.c_cc[VDSUSP] = _POSIX_VDISABLE; #endif #endif (void) tcsetattr(STDIN_FILENO, TCSANOW, &tio); } } initscr(); cbreak(); noecho(); keypad(stdscr, TRUE); // Enable 8-bit meta key sequences (Alt/ESC-prefix handling in terminals) meta(stdscr, TRUE); // Make ESC key sequences resolve quickly so ESC+ works as meta #ifdef set_escdelay set_escdelay(50); #endif nodelay(stdscr, TRUE); curs_set(1); // Enable mouse support if available mouseinterval(0); mousemask(ALL_MOUSE_EVENTS, nullptr); int r = 0, c = 0; getmaxyx(stdscr, r, c); prev_r_ = r; prev_c_ = c; ed.SetDimensions(static_cast(r), static_cast(c)); // Inform renderer of UTF-8 capability so it can choose proper output path renderer_.SetUtf8Enabled(utf8_ok); input_.SetUtf8Enabled(utf8_ok); return true; } void TerminalFrontend::Step(Editor &ed, bool &running) { // Handle resize and keep editor dimensions synced int r, c; getmaxyx(stdscr, r, c); if (r != prev_r_ || c != prev_c_) { resizeterm(r, c); clear(); prev_r_ = r; prev_c_ = c; } ed.SetDimensions(static_cast(r), static_cast(c)); MappedInput mi; if (input_.Poll(mi)) { if (mi.hasCommand) { Execute(ed, mi.id, mi.arg, mi.count); } } else { // Avoid busy loop usleep(1000); } if (ed.QuitRequested()) { running = false; } renderer_.Draw(ed); } void TerminalFrontend::Shutdown() { // Restore original terminal settings if we changed them if (have_orig_tio_) { (void) tcsetattr(STDIN_FILENO, TCSANOW, &orig_tio_); have_orig_tio_ = false; } endwin(); }