/* * Copyright (C) 2012-2016 Robin Haberkorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_TIGETSTR #include /* * Some macros in term.h interfere with our code. */ #undef lines #endif #include #include #ifdef EMSCRIPTEN #include #endif #include "sciteco.h" #include "string-utils.h" #include "cmdline.h" #include "qregisters.h" #include "ring.h" #include "interface.h" #include "interface-curses.h" #include "curses-utils.h" #include "curses-info-popup.h" #ifdef HAVE_WINDOWS_H /* here it shouldn't cause conflicts with other headers */ #define WIN32_LEAN_AND_MEAN #include #endif /** * Whether we have PDCurses-only routines: * Could be 0, even on PDCurses */ #ifndef PDCURSES #define PDCURSES 0 #endif /** * Whether we're on PDCurses/win32 */ #if defined(__PDCURSES__) && defined(G_OS_WIN32) && \ !defined(PDCURSES_WIN32A) #define PDCURSES_WIN32 /* * A_UNDERLINE is not supported by PDCurses/win32 * and causes weird colors, so we simply disable it globally. */ #undef A_UNDERLINE #define A_UNDERLINE 0 #endif #ifdef NCURSES_VERSION #if defined(G_OS_UNIX) || defined(G_OS_HAIKU) /** * Whether we're on ncurses/UNIX. * Haiku has a UNIX-like terminal and is largely * POSIX compliant, so we can handle it like a * UNIX ncurses. */ #define NCURSES_UNIX #elif defined(G_OS_WIN32) /** * Whether we're on ncurses/win32 console */ #define NCURSES_WIN32 #endif #endif namespace SciTECO { extern "C" { /* * PDCurses/win32a by default assigns functions to certain * keys like CTRL+V, CTRL++, CTRL+- and CTRL+=. * This conflicts with SciTECO that must remain in control * of keyboard processing. * Unfortunately, the default mapping can only be disabled * or changed via the internal PDC_set_function_key() in * pdcwin.h. Therefore we declare it manually here. */ #ifdef PDCURSES_WIN32A int PDC_set_function_key(const unsigned function, const int new_key); #define N_FUNCTION_KEYS 5 #define FUNCTION_KEY_SHUT_DOWN 0 #define FUNCTION_KEY_PASTE 1 #define FUNCTION_KEY_ENLARGE_FONT 2 #define FUNCTION_KEY_SHRINK_FONT 3 #define FUNCTION_KEY_CHOOSE_FONT 4 #endif static void scintilla_notify(Scintilla *sci, int idFrom, void *notify, void *user_data); #if defined(PDCURSES_WIN32) || defined(NCURSES_WIN32) /** * This handler is the Windows-analogue of a signal * handler. MinGW provides signal(), but it's not * reliable. * This may also be used to handle CTRL_CLOSE_EVENTs. * NOTE: Unlike signal handlers, this is executed in a * separate thread. */ static BOOL WINAPI console_ctrl_handler(DWORD type) { switch (type) { case CTRL_C_EVENT: sigint_occurred = TRUE; return TRUE; } return FALSE; } #endif } /* extern "C" */ #define UNNAMED_FILE "(Unnamed)" /** * Get bright variant of one of the 8 standard * curses colors. * On 8 color terminals, this returns the non-bright * color - but you __may__ get a bright version using * the A_BOLD attribute. * NOTE: This references `COLORS` and is thus not a * constant expression. */ #define COLOR_LIGHT(C) \ (COLORS < 16 ? (C) : (C) + 8) /* * The 8 bright colors (if terminal supports at * least 16 colors), else they are identical to * the non-bright colors (default curses colors). */ #define COLOR_LBLACK COLOR_LIGHT(COLOR_BLACK) #define COLOR_LRED COLOR_LIGHT(COLOR_RED) #define COLOR_LGREEN COLOR_LIGHT(COLOR_GREEN) #define COLOR_LYELLOW COLOR_LIGHT(COLOR_YELLOW) #define COLOR_LBLUE COLOR_LIGHT(COLOR_BLUE) #define COLOR_LMAGENTA COLOR_LIGHT(COLOR_MAGENTA) #define COLOR_LCYAN COLOR_LIGHT(COLOR_CYAN) #define COLOR_LWHITE COLOR_LIGHT(COLOR_WHITE) /** * Curses attribute for the color combination * `f` (foreground) and `b` (background) * according to the color pairs initialized by * Scinterm. * NOTE: This depends on the global variable * `COLORS` and is thus not a constant expression. */ #define SCI_COLOR_ATTR(f, b) \ ((attr_t)COLOR_PAIR(SCI_COLOR_PAIR(f, b))) /** * Translate a Scintilla-compatible RGB color value * (0xBBGGRR) to a Curses color triple (0 to 1000 * for each component). */ static inline void rgb2curses(guint32 rgb, short &r, short &g, short &b) { /* NOTE: We could also use 200/51 */ r = ((rgb & 0x0000FF) >> 0)*1000/0xFF; g = ((rgb & 0x00FF00) >> 8)*1000/0xFF; b = ((rgb & 0xFF0000) >> 16)*1000/0xFF; } /** * Convert a Scintilla-compatible RGB color value * (0xBBGGRR) to a Curses color code (e.g. COLOR_BLACK). * This does not work with arbitrary RGB values but * only the 16 RGB color values defined by Scinterm * corresponding to the 16 terminal colors. * It is equivalent to Scinterm's internal `term_color` * function. */ static short rgb2curses(guint32 rgb) { switch (rgb) { case 0x000000: return COLOR_BLACK; case 0x000080: return COLOR_RED; case 0x008000: return COLOR_GREEN; case 0x008080: return COLOR_YELLOW; case 0x800000: return COLOR_BLUE; case 0x800080: return COLOR_MAGENTA; case 0x808000: return COLOR_CYAN; case 0xC0C0C0: return COLOR_WHITE; case 0x404040: return COLOR_LBLACK; case 0x0000FF: return COLOR_LRED; case 0x00FF00: return COLOR_LGREEN; case 0x00FFFF: return COLOR_LYELLOW; case 0xFF0000: return COLOR_LBLUE; case 0xFF00FF: return COLOR_LMAGENTA; case 0xFFFF00: return COLOR_LCYAN; case 0xFFFFFF: return COLOR_LWHITE; } return COLOR_WHITE; } void ViewCurses::initialize_impl(void) { sci = scintilla_new(scintilla_notify); setup(); } InterfaceCurses::InterfaceCurses() : stdout_orig(-1), stderr_orig(-1), screen(NULL), screen_tty(NULL), info_window(NULL), info_type(INFO_TYPE_BUFFER), info_current(NULL), msg_window(NULL), cmdline_window(NULL), cmdline_pad(NULL), cmdline_len(0), cmdline_rubout_len(0) { for (guint i = 0; i < G_N_ELEMENTS(color_table); i++) color_table[i] = -1; for (guint i = 0; i < G_N_ELEMENTS(orig_color_table); i++) orig_color_table[i].r = -1; } void InterfaceCurses::main_impl(int &argc, char **&argv) { /* * We must register this handler to handle * asynchronous interruptions via CTRL+C * reliably. The signal handler we already * have won't do. */ #if defined(PDCURSES_WIN32) || defined(NCURSES_WIN32) SetConsoleCtrlHandler(console_ctrl_handler, TRUE); #endif /* * Make sure we have a string for the info line * even if info_update() is never called. */ info_current = g_strdup(PACKAGE_NAME); } void InterfaceCurses::init_color_safe(guint color, guint32 rgb) { short r, g, b; #ifdef PDCURSES_WIN32 if (orig_color_table[color].r < 0) { color_content((short)color, &orig_color_table[color].r, &orig_color_table[color].g, &orig_color_table[color].b); } #endif rgb2curses(rgb, r, g, b); ::init_color((short)color, r, g, b); } #ifdef PDCURSES_WIN32 /* * On PDCurses/win32, color_content() will actually return * the real console color palette - or at least the default * palette when the console started. */ void InterfaceCurses::restore_colors(void) { if (!can_change_color()) return; for (guint i = 0; i < G_N_ELEMENTS(orig_color_table); i++) { if (orig_color_table[i].r < 0) continue; ::init_color((short)i, orig_color_table[i].r, orig_color_table[i].g, orig_color_table[i].b); } } #elif defined(NCURSES_UNIX) /* * FIXME: On UNIX/ncurses init_color_safe() __may__ change the * terminal's palette permanently and there does not appear to be * any portable way of restoring the original one. * Curses has color_content(), but there is actually no terminal * that allows querying the current palette and so color_content() * will return bogus "default" values and only for the first 8 colors. * It would do more damage to restore the palette returned by * color_content() than it helps. * xterm has the escape sequence "\e]104\x07" which restores * the palette from Xdefaults but not all terminal emulators * claiming to be "xterm" via $TERM support this escape sequence. * lxterminal for instance will print gibberish instead. * So we try to look whether $XTERM_VERSION is set. * There are hardly any other terminal emulators that support palette * resets. * The only emulator I'm aware of which can be identified reliably * by $TERM supporting a palette reset is the Linux console * (see console_codes(4)). The escape sequence "\e]R" is already * part of its terminfo description (orig_colors capability) * which is apparently sent by endwin(), so the palette is * already properly restored on endwin(). * Welcome in Curses hell. */ void InterfaceCurses::restore_colors(void) { if (g_str_has_prefix(g_getenv("TERM") ? : "", "xterm") && g_getenv("XTERM_VERSION")) { /* * Looks like a real xterm. $TERM alone is not * sufficient to tell. */ fputs("\e]104\x07", screen_tty); fflush(screen_tty); } } #else /* !PDCURSES_WIN32 && !NCURSES_UNIX */ void InterfaceCurses::restore_colors(void) { /* * No way to restore the palette, or it's * unnecessary (e.g. XCurses) */ } #endif void InterfaceCurses::init_color(guint color, guint32 rgb) { if (color >= G_N_ELEMENTS(color_table)) return; #if defined(__PDCURSES__) && !defined(PDC_RGB) /* * PDCurses will usually number color codes differently * (least significant bit is the blue component) while * SciTECO macros will assume a standard terminal color * code numbering with red as the LSB. * Therefore we have to swap the bit order of the least * significant 3 bits here. */ color = (color & ~0x5) | ((color & 0x1) << 2) | ((color & 0x4) >> 2); #endif if (cmdline_window) { /* interactive mode */ if (!can_change_color()) return; init_color_safe(color, rgb); } else { /* * batch mode: store colors, * they can only be initialized after start_color() * which is called by Scinterm when interactive * mode is initialized */ color_table[color] = (gint32)rgb; } } #ifdef NCURSES_UNIX void InterfaceCurses::init_screen(void) { screen_tty = g_fopen("/dev/tty", "r+"); /* should never fail */ g_assert(screen_tty != NULL); screen = newterm(NULL, screen_tty, screen_tty); if (!screen) { g_fprintf(stderr, "Error initializing interactive mode. " "$TERM may be incorrect.\n"); exit(EXIT_FAILURE); } /* * If stdout or stderr would go to the terminal, * redirect it. Otherwise, they are already redirected * (e.g. to a file) and writing to them does not * interrupt terminal interaction. */ if (isatty(1)) { FILE *stdout_new; stdout_orig = dup(1); g_assert(stdout_orig >= 0); stdout_new = g_freopen("/dev/null", "a+", stdout); g_assert(stdout_new != NULL); } if (isatty(2)) { FILE *stderr_new; stderr_orig = dup(2); g_assert(stderr_orig >= 0); stderr_new = g_freopen("/dev/null", "a+", stderr); g_assert(stderr_new != NULL); } } #elif defined(XCURSES) void InterfaceCurses::init_screen(void) { const char *argv[] = {PACKAGE_NAME, NULL}; /* * This sets the program name to "SciTECO" * which may then also be used as the X11 class name * for overwriting X11 resources in .Xdefaults * FIXME: We could support passing in resource * overrides via the SciTECO command line. * But unfortunately, Xinitscr() is called too * late to modify argc/argv for command-line parsing. * Therefore this could only be supported by * adding a special option like --resource. */ Xinitscr(1, (char **)argv); } #else void InterfaceCurses::init_screen(void) { initscr(); } #endif void InterfaceCurses::init_interactive(void) { /* * Curses accesses many environment variables * internally. In order to be able to modify them in * the SciTECO profile, we must update the process * environment before initscr()/newterm(). * This is safe to do here since there are no threads. */ QRegisters::globals.update_environ(); /* * On UNIX terminals, the escape key is usually * delivered as the escape character even though function * keys are delivered as escape sequences as well. * That's why there has to be a timeout for detecting * escape presses if function key handling is enabled. * This timeout can be controlled using $ESCDELAY on * ncurses but its default is much too long. * We set it to 25ms as Vim does. In the very rare cases * this won't suffice, $ESCDELAY can still be set explicitly. * * NOTE: The only terminal emulator I'm aware of that lets * us send an escape sequence for the escape key is Mintty * (see "\e[?7727h"). */ #ifdef NCURSES_UNIX if (!g_getenv("ESCDELAY")) set_escdelay(25); #endif /* * $TERM must be unset or "#win32con" for the win32 * driver to load. * So we always ignore any $TERM changes by the user. */ #ifdef NCURSES_WIN32 g_setenv("TERM", "#win32con", TRUE); #endif #ifdef PDCURSES_WIN32A /* * Necessary to enable window resizing in Win32a port */ PDC_set_resize_limits(25, 0xFFFF, 80, 0xFFFF); /* * Disable all magic function keys. * NOTE: This could also be used to assign * a "shutdown" key when program termination is requested. */ for (int i = 0; i < N_FUNCTION_KEYS; i++) PDC_set_function_key(i, 0); /* * Register the special shutdown function with the * CLOSE key, so closing the window behaves similar as on * GTK+. */ PDC_set_function_key(FUNCTION_KEY_SHUT_DOWN, KEY_CLOSE); #endif /* for displaying UTF-8 characters properly */ setlocale(LC_CTYPE, ""); init_screen(); cbreak(); noecho(); /* Scintilla draws its own cursor */ curs_set(0); info_window = newwin(1, 0, 0, 0); msg_window = newwin(1, 0, LINES - 2, 0); cmdline_window = newwin(0, 0, LINES - 1, 0); keypad(cmdline_window, TRUE); #ifdef EMSCRIPTEN nodelay(cmdline_window, TRUE); #endif /* * Will also initialize Scinterm, Curses color pairs * and resizes the current view. */ if (current_view) show_view(current_view); /* * Only now it's safe to redefine the 16 default colors. */ if (can_change_color()) { for (guint i = 0; i < G_N_ELEMENTS(color_table); i++) { /* * init_color() may still fail if COLORS < 16 */ if (color_table[i] >= 0) init_color_safe(i, (guint32)color_table[i]); } } } void InterfaceCurses::restore_batch(void) { /* * Set window title to a reasonable default, * in case it is not reset immediately by the * shell. * FIXME: See set_window_title() why this * is necessary. */ #if defined(NCURSES_UNIX) && defined(HAVE_TIGETSTR) set_window_title(g_getenv("TERM") ? : ""); #endif /* * Restore ordinary terminal behaviour * (i.e. return to batch mode) */ endwin(); restore_colors(); /* * Restore stdout and stderr, so output goes to * the terminal again in case we "muted" them. */ #ifdef NCURSES_UNIX if (stdout_orig >= 0) { int fd = dup2(stdout_orig, 1); g_assert(fd == 1); } if (stderr_orig >= 0) { int fd = dup2(stderr_orig, 2); g_assert(fd == 2); } #endif /* * See vmsg_impl(): It looks at msg_win to determine * whether we're in batch mode. */ if (msg_window) { delwin(msg_window); msg_window = NULL; } } void InterfaceCurses::resize_all_windows(void) { int lines, cols; /* screen dimensions */ getmaxyx(stdscr, lines, cols); wresize(info_window, 1, cols); wresize(current_view->get_window(), lines - 3, cols); wresize(msg_window, 1, cols); mvwin(msg_window, lines - 2, 0); wresize(cmdline_window, 1, cols); mvwin(cmdline_window, lines - 1, 0); draw_info(); msg_clear(); /* FIXME: use saved message */ popup_clear(); draw_cmdline(); } void InterfaceCurses::vmsg_impl(MessageType type, const gchar *fmt, va_list ap) { short fg, bg; if (!msg_window) { /* batch mode */ stdio_vmsg(type, fmt, ap); return; } /* * On most platforms we can write to stdout/stderr * even in interactive mode. */ #if defined(XCURSES) || defined(PDCURSES_WIN32A) || \ defined(NCURSES_UNIX) || defined(NCURSES_WIN32) va_list aq; va_copy(aq, ap); stdio_vmsg(type, fmt, aq); va_end(aq); #endif fg = rgb2curses(ssm(SCI_STYLEGETBACK, STYLE_DEFAULT)); switch (type) { default: case MSG_USER: bg = rgb2curses(ssm(SCI_STYLEGETFORE, STYLE_DEFAULT)); break; case MSG_INFO: bg = COLOR_GREEN; break; case MSG_WARNING: bg = COLOR_YELLOW; break; case MSG_ERROR: bg = COLOR_RED; beep(); break; } wmove(msg_window, 0, 0); wbkgdset(msg_window, ' ' | SCI_COLOR_ATTR(fg, bg)); vw_printw(msg_window, fmt, ap); wclrtoeol(msg_window); } void InterfaceCurses::msg_clear(void) { short fg, bg; if (!msg_window) /* batch mode */ return; fg = rgb2curses(ssm(SCI_STYLEGETBACK, STYLE_DEFAULT)); bg = rgb2curses(ssm(SCI_STYLEGETFORE, STYLE_DEFAULT)); wbkgdset(msg_window, ' ' | SCI_COLOR_ATTR(fg, bg)); werase(msg_window); } void InterfaceCurses::show_view_impl(ViewCurses *view) { int lines, cols; /* screen dimensions */ WINDOW *current_view_win; current_view = view; if (!cmdline_window) /* batch mode */ return; current_view_win = current_view->get_window(); /* * screen size might have changed since * this view's WINDOW was last active */ getmaxyx(stdscr, lines, cols); wresize(current_view_win, lines - 3, cols); /* Set up window position: never changes */ mvwin(current_view_win, 1, 0); } #if PDCURSES void InterfaceCurses::set_window_title(const gchar *title) { static gchar *last_title = NULL; /* * PDC_set_title() can result in flickering * even when executed only once per pressed key, * so we check whether it is really necessary to change * the title. * This is an issue at least with PDCurses/win32. */ if (!g_strcmp0(title, last_title)) return; PDC_set_title(title); g_free(last_title); last_title = g_strdup(title); } #elif defined(NCURSES_UNIX) && defined(HAVE_TIGETSTR) void InterfaceCurses::set_window_title(const gchar *title) { if (!has_status_line || !to_status_line || !from_status_line) return; /* * Modern terminal emulators map the window title to * the historic status line. * This feature is not standardized in ncurses, * so we query the terminfo database. * This feature may make problems with terminal emulators * that do support a status line but do not map them * to the window title. Some emulators (like xterm) * support setting the window title via custom escape * sequences and via the status line but their * terminfo entry does not say so. (xterm can also * save and restore window titles but there is not * even a terminfo capability defined for this.) * Taken the different emulator incompatibilites * it may be best to make this configurable. * Once we support configurable status lines, * there could be a special status line that's sent * to the terminal that may be set up in the profile * depending on $TERM. * * NOTE: The terminfo manpage advises us to use putp() * but on ncurses/UNIX (where terminfo is available), * we do not let curses write to stdout. * NOTE: This leaves the title set after we quit. */ fputs(to_status_line, screen_tty); fputs(title, screen_tty); fputs(from_status_line, screen_tty); fflush(screen_tty); } #else void InterfaceCurses::set_window_title(const gchar *title) { /* no way to set window title */ } #endif void InterfaceCurses::draw_info(void) { short fg, bg; const gchar *info_type_str; gchar *info_current_canon, *title; if (!info_window) /* batch mode */ return; /* * The info line is printed in reverse colors of * the current buffer's STYLE_DEFAULT. * The same style is used for MSG_USER messages. */ fg = rgb2curses(ssm(SCI_STYLEGETBACK, STYLE_DEFAULT)); bg = rgb2curses(ssm(SCI_STYLEGETFORE, STYLE_DEFAULT)); wmove(info_window, 0, 0); wbkgdset(info_window, ' ' | SCI_COLOR_ATTR(fg, bg)); switch (info_type) { case INFO_TYPE_QREGISTER: info_type_str = PACKAGE_NAME " - "; waddstr(info_window, info_type_str); /* same formatting as in command lines */ Curses::format_str(info_window, info_current); break; case INFO_TYPE_BUFFER: info_type_str = PACKAGE_NAME " - "; waddstr(info_window, info_type_str); Curses::format_filename(info_window, info_current); break; default: g_assert_not_reached(); } wclrtoeol(info_window); /* * Make sure the title will consist only of printable * characters */ info_current_canon = String::canonicalize_ctl(info_current); title = g_strconcat(info_type_str, info_current_canon, NIL); g_free(info_current_canon); set_window_title(title); g_free(title); } void InterfaceCurses::info_update_impl(const QRegister *reg) { g_free(info_current); /* NOTE: will contain control characters */ info_type = INFO_TYPE_QREGISTER; info_current = g_strdup(reg->name); /* NOTE: drawn in event_loop_iter() */ } void InterfaceCurses::info_update_impl(const Buffer *buffer) { g_free(info_current); info_type = INFO_TYPE_BUFFER; info_current = g_strconcat(buffer->filename ? : UNNAMED_FILE, buffer->dirty ? "*" : " ", NIL); /* NOTE: drawn in event_loop_iter() */ } void InterfaceCurses::cmdline_update_impl(const Cmdline *cmdline) { short fg, bg; int max_cols = 1; /* * Replace entire pre-formatted command-line. * We don't know if it is similar to the last one, * so resizing makes no sense. * We approximate the size of the new formatted command-line, * wasting a few bytes for control characters. */ if (cmdline_pad) delwin(cmdline_pad); for (guint i = 0; i < cmdline->len+cmdline->rubout_len; i++) max_cols += IS_CTL((*cmdline)[i]) ? 3 : 1; cmdline_pad = newpad(1, max_cols); fg = rgb2curses(ssm(SCI_STYLEGETFORE, STYLE_DEFAULT)); bg = rgb2curses(ssm(SCI_STYLEGETBACK, STYLE_DEFAULT)); wcolor_set(cmdline_pad, SCI_COLOR_PAIR(fg, bg), NULL); /* format effective command line */ cmdline_len = Curses::format_str(cmdline_pad, cmdline->str, cmdline->len); /* * A_BOLD should result in either a bold font or a brighter * color both on 8 and 16 color terminals. * This is not quite color-scheme-agnostic, but works * with both the `terminal` and `solarized` themes. * This problem will be gone once we use a Scintilla view * as command line, since we can then define a style * for rubbed out parts of the command line which will * be user-configurable. */ wattron(cmdline_pad, A_UNDERLINE | A_BOLD); /* * Format rubbed-out command line. * NOTE: This formatting will never be truncated since we're * writing into the pad which is large enough. */ cmdline_rubout_len = Curses::format_str(cmdline_pad, cmdline->str + cmdline->len, cmdline->rubout_len); /* highlight cursor after effective command line */ if (cmdline_rubout_len) { attr_t attr; short pair; wmove(cmdline_pad, 0, cmdline_len); wattr_get(cmdline_pad, &attr, &pair, NULL); wchgat(cmdline_pad, 1, (attr & A_UNDERLINE) | A_REVERSE, pair, NULL); } else { cmdline_len++; wattroff(cmdline_pad, A_UNDERLINE | A_BOLD); waddch(cmdline_pad, ' ' | A_REVERSE); } draw_cmdline(); } void InterfaceCurses::draw_cmdline(void) { short fg, bg; /* total width available for command line */ guint total_width = getmaxx(cmdline_window) - 1; /* beginning of command line to show */ guint disp_offset; /* length of command line to show */ guint disp_len; disp_offset = cmdline_len - MIN(cmdline_len, total_width/2 + cmdline_len % MAX(total_width/2, 1)); /* * NOTE: we do not use getmaxx(cmdline_pad) here since it may be * larger than the text the pad contains. */ disp_len = MIN(total_width, cmdline_len+cmdline_rubout_len - disp_offset); fg = rgb2curses(ssm(SCI_STYLEGETFORE, STYLE_DEFAULT)); bg = rgb2curses(ssm(SCI_STYLEGETBACK, STYLE_DEFAULT)); wbkgdset(cmdline_window, ' ' | SCI_COLOR_ATTR(fg, bg)); werase(cmdline_window); mvwaddch(cmdline_window, 0, 0, '*' | A_BOLD); copywin(cmdline_pad, cmdline_window, 0, disp_offset, 0, 1, 0, disp_len, FALSE); } void InterfaceCurses::popup_show_impl(void) { short fg, bg; if (!cmdline_window) /* batch mode */ return; fg = rgb2curses(ssm(SCI_STYLEGETFORE, STYLE_CALLTIP)); bg = rgb2curses(ssm(SCI_STYLEGETBACK, STYLE_CALLTIP)); popup.show(SCI_COLOR_ATTR(fg, bg)); } void InterfaceCurses::popup_clear_impl(void) { #ifdef __PDCURSES__ /* * PDCurses will not redraw all windows that may be * overlapped by the popup window correctly - at least * not the info window. * The Scintilla window is apparently always touched by * scintilla_noutrefresh(). * Actually we would expect this to be necessary on any curses, * but ncurses doesn't require this. */ if (popup.is_shown()) { touchwin(info_window); touchwin(msg_window); } #endif popup.clear(); } /** * One iteration of the event loop. * * This is a global function, so it may * be used as an Emscripten callback. * * @bug * Can probably be defined as a static method, * so we can avoid declaring it a fried function of * InterfaceCurses. */ void event_loop_iter() { int key; /* * On PDCurses/win32, raw() and cbreak() does * not disable and enable CTRL+C handling properly. * Since I don't want to patch PDCurses/win32, * we do this manually here. * NOTE: This exploits the fact that PDCurses uses * STD_INPUT_HANDLE internally! */ #ifdef PDCURSES_WIN32 HANDLE console_hnd = GetStdHandle(STD_INPUT_HANDLE); DWORD console_mode; GetConsoleMode(console_hnd, &console_mode); #endif /* * Setting function key processing is important * on Unix Curses, as ESCAPE is handled as the beginning * of a escape sequence when terminal emulators are * involved. * On some Curses variants (XCurses) however, keypad * must always be TRUE so we receive KEY_RESIZE. */ #ifdef NCURSES_UNIX keypad(interface.cmdline_window, Flags::ed & Flags::ED_FNKEYS); #endif /* no special handling */ raw(); #ifdef PDCURSES_WIN32 SetConsoleMode(console_hnd, console_mode & ~ENABLE_PROCESSED_INPUT); #endif key = wgetch(interface.cmdline_window); /* allow asynchronous interruptions on */ sigint_occurred = FALSE; noraw(); /* FIXME: necessary because of NCURSES_WIN32 bug */ cbreak(); #ifdef PDCURSES_WIN32 SetConsoleMode(console_hnd, console_mode | ENABLE_PROCESSED_INPUT); #endif if (key == ERR) return; switch (key) { #ifdef KEY_RESIZE case KEY_RESIZE: #if PDCURSES resize_term(0, 0); #endif interface.resize_all_windows(); break; #endif case CTL_KEY('H'): case 0x7F: /* ^? */ case KEY_BACKSPACE: /* * For historic reasons terminals can send * ASCII 8 (^H) or 127 (^?) for backspace. * Curses also defines KEY_BACKSPACE, probably * for terminals that send an escape sequence for * backspace. * In SciTECO backspace is normalized to ^H. */ cmdline.keypress(CTL_KEY('H')); break; case KEY_ENTER: case '\r': case '\n': cmdline.keypress('\n'); break; /* * Function key macros */ #define FN(KEY) case KEY_##KEY: cmdline.fnmacro(#KEY); break #define FNS(KEY) FN(KEY); FN(S##KEY) FN(DOWN); FN(UP); FNS(LEFT); FNS(RIGHT); FNS(HOME); case KEY_F(0)...KEY_F(63): { gchar macro_name[3+1]; g_snprintf(macro_name, sizeof(macro_name), "F%d", key - KEY_F0); cmdline.fnmacro(macro_name); break; } FNS(DC); FNS(IC); FN(NPAGE); FN(PPAGE); FNS(PRINT); FN(A1); FN(A3); FN(B2); FN(C1); FN(C3); FNS(END); FNS(HELP); FN(CLOSE); #undef FNS #undef FN /* * Control keys and keys with printable representation */ default: if (key <= 0xFF) cmdline.keypress((gchar)key); } /* * Info window is updated very often which is very * costly, especially when using PDC_set_title(), * so we redraw it here, where the overhead does * not matter much. */ interface.draw_info(); wnoutrefresh(interface.info_window); interface.current_view->noutrefresh(); wnoutrefresh(interface.msg_window); wnoutrefresh(interface.cmdline_window); interface.popup.noutrefresh(); doupdate(); } void InterfaceCurses::event_loop_impl(void) { static const Cmdline empty_cmdline; /* * Initialize Curses for interactive mode */ init_interactive(); /* initial refresh */ draw_info(); wnoutrefresh(info_window); current_view->noutrefresh(); msg_clear(); wnoutrefresh(msg_window); cmdline_update(&empty_cmdline); wnoutrefresh(cmdline_window); doupdate(); #ifdef EMSCRIPTEN PDC_emscripten_set_handler(event_loop_iter, TRUE); /* * We must not block emscripten's main loop, * instead event_loop_iter() is called asynchronously. * We also must not exit the event_loop() method, since * SciTECO would assume ordinary program termination. * We also must not call exit() since that would run * the global destructors. * The following exits the main() function immediately * while keeping the "runtime" alive. */ emscripten_exit_with_live_runtime(); #else try { for (;;) event_loop_iter(); } catch (Quit) { /* SciTECO termination (e.g. EX$$) */ } restore_batch(); #endif } InterfaceCurses::~InterfaceCurses() { if (info_window) delwin(info_window); g_free(info_current); if (cmdline_window) delwin(cmdline_window); if (cmdline_pad) delwin(cmdline_pad); if (msg_window) delwin(msg_window); /* * PDCurses (win32) crashes if initscr() wasn't called. * Others (XCurses) crash if we try to use isendwin() here. * Perhaps Curses cleanup should be in restore_batch() * instead. */ #ifndef XCURSES if (info_window && !isendwin()) endwin(); #endif if (screen) delscreen(screen); if (screen_tty) fclose(screen_tty); if (stderr_orig >= 0) close(stderr_orig); if (stdout_orig >= 0) close(stdout_orig); } /* * Callbacks */ static void scintilla_notify(Scintilla *sci, int idFrom, void *notify, void *user_data) { interface.process_notify((SCNotification *)notify); } } /* namespace SciTECO */