aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/interface-curses.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/interface-curses.cpp')
-rw-r--r--src/interface-curses.cpp1548
1 files changed, 0 insertions, 1548 deletions
diff --git a/src/interface-curses.cpp b/src/interface-curses.cpp
deleted file mode 100644
index 49339d4..0000000
--- a/src/interface-curses.cpp
+++ /dev/null
@@ -1,1548 +0,0 @@
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <unistd.h>
-#include <locale.h>
-#include <errno.h>
-
-#include <glib.h>
-#include <glib/gprintf.h>
-#include <glib/gstdio.h>
-
-#include <curses.h>
-
-#ifdef HAVE_TIGETSTR
-#include <term.h>
-
-/*
- * Some macros in term.h interfere with our code.
- */
-#undef lines
-#endif
-
-#include <Scintilla.h>
-#include <ScintillaTerm.h>
-
-#ifdef EMSCRIPTEN
-#include <emscripten.h>
-#endif
-
-#include "sciteco.h"
-#include "string-utils.h"
-#include "cmdline.h"
-#include "qregisters.h"
-#include "ring.h"
-#include "interface.h"
-#include "interface-curses.h"
-
-#ifdef HAVE_WINDOWS_H
-/* here it shouldn't cause conflicts with other headers */
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h>
-#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;
-}
-
-static gsize
-format_str(WINDOW *win, const gchar *str,
- gssize len = -1, gint max_width = -1)
-{
- int old_x, old_y;
- gint chars_added = 0;
-
- getyx(win, old_y, old_x);
-
- if (len < 0)
- len = strlen(str);
- if (max_width < 0)
- max_width = getmaxx(win) - old_x;
-
- while (len > 0) {
- /*
- * NOTE: This mapping is similar to
- * View::set_representations()
- */
- switch (*str) {
- case CTL_KEY_ESC:
- chars_added++;
- if (chars_added > max_width)
- goto truncate;
- waddch(win, '$' | A_REVERSE);
- break;
- case '\r':
- chars_added += 2;
- if (chars_added > max_width)
- goto truncate;
- waddch(win, 'C' | A_REVERSE);
- waddch(win, 'R' | A_REVERSE);
- break;
- case '\n':
- chars_added += 2;
- if (chars_added > max_width)
- goto truncate;
- waddch(win, 'L' | A_REVERSE);
- waddch(win, 'F' | A_REVERSE);
- break;
- case '\t':
- chars_added += 3;
- if (chars_added > max_width)
- goto truncate;
- waddch(win, 'T' | A_REVERSE);
- waddch(win, 'A' | A_REVERSE);
- waddch(win, 'B' | A_REVERSE);
- break;
- default:
- if (IS_CTL(*str)) {
- chars_added += 2;
- if (chars_added > max_width)
- goto truncate;
- waddch(win, '^' | A_REVERSE);
- waddch(win, CTL_ECHO(*str) | A_REVERSE);
- } else {
- chars_added++;
- if (chars_added > max_width)
- goto truncate;
- waddch(win, *str);
- }
- }
-
- str++;
- len--;
- }
-
- return getcurx(win) - old_x;
-
-truncate:
- if (max_width >= 3) {
- /*
- * Truncate string
- */
- wattron(win, A_UNDERLINE | A_BOLD);
- mvwaddstr(win, old_y, old_x + max_width - 3, "...");
- wattroff(win, A_UNDERLINE | A_BOLD);
- }
-
- return getcurx(win) - old_x;
-}
-
-static gsize
-format_filename(WINDOW *win, const gchar *filename,
- gint max_width = -1)
-{
- int old_x = getcurx(win);
-
- gchar *filename_canon = String::canonicalize_ctl(filename);
- size_t filename_len = strlen(filename_canon);
-
- if (max_width < 0)
- max_width = getmaxx(win) - old_x;
-
- if (filename_len <= (size_t)max_width) {
- waddstr(win, filename_canon);
- } else {
- const gchar *keep_post = filename_canon + filename_len -
- max_width + 3;
-
-#ifdef G_OS_WIN32
- const gchar *keep_pre = g_path_skip_root(filename_canon);
- if (keep_pre) {
- waddnstr(win, filename_canon,
- keep_pre - filename_canon);
- keep_post += keep_pre - filename_canon;
- }
-#endif
- wattron(win, A_UNDERLINE | A_BOLD);
- waddstr(win, "...");
- wattroff(win, A_UNDERLINE | A_BOLD);
- waddstr(win, keep_post);
- }
-
- g_free(filename_canon);
- return getcurx(win) - old_x;
-}
-
-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::Popup::add(PopupEntryType type,
- const gchar *name, bool highlight)
-{
- size_t name_len = strlen(name);
- Entry *entry = (Entry *)g_malloc(sizeof(Entry) + name_len + 1);
-
- entry->type = type;
- entry->highlight = highlight;
- strcpy(entry->name, name);
-
- longest = MAX(longest, (gint)name_len);
- length++;
-
- /*
- * Entries are added in reverse (constant time for GSList),
- * so they will later have to be reversed.
- */
- list = g_slist_prepend(list, entry);
-}
-
-void
-InterfaceCurses::Popup::init_pad(attr_t attr)
-{
- int cols = getmaxx(stdscr); /* screen width */
- int pad_lines; /* pad height */
- gint pad_cols; /* entry columns */
- gint pad_colwidth; /* width per entry column */
-
- gint cur_col;
-
- /* reserve 2 spaces between columns */
- pad_colwidth = MIN(longest + 2, cols - 2);
-
- /* pad_cols = floor((cols - 2) / pad_colwidth) */
- pad_cols = (cols - 2) / pad_colwidth;
- /* pad_lines = ceil(length / pad_cols) */
- pad_lines = (length+pad_cols-1) / pad_cols;
-
- /*
- * Render the entire autocompletion list into a pad
- * which can be higher than the physical screen.
- * The pad uses two columns less than the screen since
- * it will be drawn into the popup window which has left
- * and right borders.
- */
- pad = newpad(pad_lines, cols - 2);
-
- wbkgd(pad, ' ' | attr);
-
- /*
- * cur_col is the row currently written.
- * It does not wrap but grows indefinitely.
- * Therefore the real current row is (cur_col % popup_cols)
- */
- cur_col = 0;
- for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) {
- Entry *entry = (Entry *)cur->data;
- gint cur_line = cur_col/pad_cols + 1;
-
- wmove(pad, cur_line-1,
- (cur_col % pad_cols)*pad_colwidth);
-
- wattrset(pad, entry->highlight ? A_BOLD : A_NORMAL);
-
- switch (entry->type) {
- case POPUP_FILE:
- case POPUP_DIRECTORY:
- format_filename(pad, entry->name);
- break;
- default:
- format_str(pad, entry->name);
- break;
- }
-
- cur_col++;
- }
-}
-
-void
-InterfaceCurses::Popup::show(attr_t attr)
-{
- int lines, cols; /* screen dimensions */
- gint pad_lines;
- gint popup_lines;
- gint bar_height, bar_y;
-
- if (!length)
- /* nothing to display */
- return;
-
- getmaxyx(stdscr, lines, cols);
-
- if (window)
- delwin(window);
- else
- /* reverse list only once */
- list = g_slist_reverse(list);
-
- if (!pad)
- init_pad(attr);
- pad_lines = getmaxy(pad);
-
- /*
- * Popup window can cover all but one screen row.
- * Another row is reserved for the top border.
- */
- popup_lines = MIN(pad_lines + 1, lines - 1);
-
- /* window covers message, scintilla and info windows */
- window = newwin(popup_lines, 0, lines - 1 - popup_lines, 0);
-
- wbkgdset(window, ' ' | attr);
-
- wborder(window,
- ACS_VLINE,
- ACS_VLINE, /* may be overwritten with scrollbar */
- ACS_HLINE,
- ' ', /* no bottom line */
- ACS_ULCORNER, ACS_URCORNER,
- ACS_VLINE, ACS_VLINE);
-
- copywin(pad, window,
- pad_first_line, 0,
- 1, 1, popup_lines - 1, cols - 2, FALSE);
-
- if (pad_lines <= popup_lines - 1)
- /* no need for scrollbar */
- return;
-
- /* bar_height = ceil((popup_lines-1)/pad_lines * (popup_lines-2)) */
- bar_height = ((popup_lines-1)*(popup_lines-2) + pad_lines-1) /
- pad_lines;
- /* bar_y = floor(pad_first_line/pad_lines * (popup_lines-2)) + 1 */
- bar_y = pad_first_line*(popup_lines-2) / pad_lines + 1;
-
- mvwvline(window, 1, cols-1, ACS_CKBOARD, popup_lines-2);
- /*
- * We do not use ACS_BLOCK here since it will not
- * always be drawn as a solid block (e.g. xterm).
- * Instead, simply draw reverse blanks.
- */
- wmove(window, bar_y, cols-1);
- wattron(window, A_REVERSE);
- wvline(window, ' ', bar_height);
-
- /* progress scroll position */
- pad_first_line += popup_lines - 1;
- /* wrap on last shown page */
- pad_first_line %= pad_lines;
- if (pad_lines - pad_first_line < popup_lines - 1)
- /* show last page */
- pad_first_line = pad_lines - (popup_lines - 1);
-}
-
-void
-InterfaceCurses::Popup::clear(void)
-{
- g_slist_free_full(list, g_free);
- list = NULL;
- length = 0;
- longest = 0;
-
- pad_first_line = 0;
-
- if (window) {
- delwin(window);
- window = NULL;
- }
-
- if (pad) {
- delwin(pad);
- pad = NULL;
- }
-}
-
-InterfaceCurses::Popup::~Popup()
-{
- if (window)
- delwin(window);
- if (pad)
- delwin(pad);
- if (list)
- g_slist_free_full(list, g_free);
-}
-
-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 " - <QRegister> ";
- waddstr(info_window, info_type_str);
- /* same formatting as in command lines */
- format_str(info_window, info_current);
- break;
-
- case INFO_TYPE_BUFFER:
- info_type_str = PACKAGE_NAME " - <Buffer> ";
- waddstr(info_window, info_type_str);
- 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 = 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 = 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 <CTRL/C> handling */
- raw();
-#ifdef PDCURSES_WIN32
- SetConsoleMode(console_hnd, console_mode & ~ENABLE_PROCESSED_INPUT);
-#endif
- key = wgetch(interface.cmdline_window);
- /* allow asynchronous interruptions on <CTRL/C> */
- 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 */