aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/interface-curses/interface.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/interface-curses/interface.c')
-rw-r--r--src/interface-curses/interface.c246
1 files changed, 152 insertions, 94 deletions
diff --git a/src/interface-curses/interface.c b/src/interface-curses/interface.c
index ef3f0c7..95e86c9 100644
--- a/src/interface-curses/interface.c
+++ b/src/interface-curses/interface.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2023 Robin Haberkorn
+ * Copyright (C) 2012-2024 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
@@ -24,7 +24,6 @@
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
-#include <locale.h>
#include <errno.h>
#ifdef HAVE_WINDOWS_H
@@ -65,11 +64,12 @@
#include "qreg.h"
#include "ring.h"
#include "error.h"
-#include "curses-utils.h"
-#include "curses-info-popup.h"
#include "view.h"
#include "memory.h"
#include "interface.h"
+#include "curses-utils.h"
+#include "curses-info-popup.h"
+#include "curses-icons.h"
#if defined(__PDCURSES__) && defined(G_OS_WIN32) && \
!defined(PDCURSES_GUI)
@@ -340,12 +340,18 @@ static struct {
TECO_INFO_TYPE_QREG
} info_type;
teco_string_t info_current;
+ gboolean info_dirty;
WINDOW *msg_window;
WINDOW *cmdline_window, *cmdline_pad;
- gsize cmdline_len, cmdline_rubout_len;
+ guint cmdline_len, cmdline_rubout_len;
+ /**
+ * Pad used exclusively for wgetch() as it will not
+ * result in unwanted wrefresh().
+ */
+ WINDOW *input_pad;
GQueue *input_queue;
teco_curses_info_popup_t popup;
@@ -554,7 +560,7 @@ teco_interface_init_screen(void)
g_assert(teco_interface.screen_tty != NULL);
teco_interface.screen = newterm(NULL, teco_interface.screen_tty, teco_interface.screen_tty);
- if (!teco_interface.screen) {
+ if (G_UNLIKELY(!teco_interface.screen)) {
g_fprintf(stderr, "Error initializing interactive mode. "
"$TERM may be incorrect.\n");
exit(EXIT_FAILURE);
@@ -629,28 +635,6 @@ teco_interface_init_interactive(GError **error)
return FALSE;
/*
- * 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").
- *
- * FIXME: This appears to be ineffective for netbsd-curses.
- */
-#ifdef CURSES_TTY
- 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.
@@ -679,12 +663,31 @@ teco_interface_init_interactive(GError **error)
PDC_set_function_key(FUNCTION_KEY_SHUT_DOWN, KEY_CLOSE);
#endif
- /* for displaying UTF-8 characters properly */
- setlocale(LC_CTYPE, "");
-
teco_interface_init_screen();
/*
+ * 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").
+ *
+ * NOTE: The delay is overwritten by initscr() on netbsd-curses.
+ */
+#ifdef CURSES_TTY
+ if (!g_getenv("ESCDELAY"))
+ set_escdelay(25);
+#endif
+
+ /*
* We always have a CTRL handler on Windows, but doing it
* here again, ensures that we have a higher precedence
* than the one installed by PDCurses.
@@ -699,12 +702,22 @@ teco_interface_init_interactive(GError **error)
curs_set(0);
teco_interface.info_window = newwin(1, 0, 0, 0);
-
teco_interface.msg_window = newwin(1, 0, LINES - 2, 0);
-
teco_interface.cmdline_window = newwin(0, 0, LINES - 1, 0);
- keypad(teco_interface.cmdline_window, TRUE);
- nodelay(teco_interface.cmdline_window, TRUE);
+
+ teco_interface.input_pad = newpad(1, 1);
+ /*
+ * Controlling function key processing is important
+ * on Unix Curses, as ESCAPE is handled as the beginning
+ * of a escape sequence when terminal emulators are
+ * involved.
+ * Still, it's now enabled always since the ESCDELAY
+ * workaround works nicely.
+ * On some Curses variants (XCurses) keypad
+ * must always be TRUE so we receive KEY_RESIZE.
+ */
+ keypad(teco_interface.input_pad, TRUE);
+ nodelay(teco_interface.input_pad, TRUE);
teco_interface.input_queue = g_queue_new();
@@ -748,8 +761,8 @@ teco_interface_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.
+ * FIXME: See teco_interface_set_window_title()
+ * why this is necessary.
*/
#if defined(CURSES_TTY) && defined(HAVE_TIGETSTR)
teco_interface_set_window_title(g_getenv("TERM") ? : "");
@@ -978,10 +991,14 @@ teco_interface_draw_info(void)
const gchar *info_type_str;
+ waddstr(teco_interface.info_window, PACKAGE_NAME " ");
+
switch (teco_interface.info_type) {
case TECO_INFO_TYPE_QREG:
info_type_str = PACKAGE_NAME " - <QRegister> ";
- waddstr(teco_interface.info_window, info_type_str);
+ teco_curses_add_wc(teco_interface.info_window,
+ teco_ed & TECO_ED_ICONS ? TECO_CURSES_ICONS_QREG : '-');
+ waddstr(teco_interface.info_window, " <QRegister> ");
/* same formatting as in command lines */
teco_curses_format_str(teco_interface.info_window,
teco_interface.info_current.data,
@@ -990,10 +1007,15 @@ teco_interface_draw_info(void)
case TECO_INFO_TYPE_BUFFER:
info_type_str = PACKAGE_NAME " - <Buffer> ";
- waddstr(teco_interface.info_window, info_type_str);
g_assert(!teco_string_contains(&teco_interface.info_current, '\0'));
+ teco_curses_add_wc(teco_interface.info_window,
+ teco_ed & TECO_ED_ICONS ? teco_curses_icons_lookup_file(teco_interface.info_current.data) : '-');
+ waddstr(teco_interface.info_window, " <Buffer> ");
teco_curses_format_filename(teco_interface.info_window,
- teco_interface.info_current.data, -1);
+ teco_interface.info_current.data,
+ getmaxx(teco_interface.info_window) -
+ getcurx(teco_interface.info_window) - 1);
+ waddch(teco_interface.info_window, teco_interface.info_dirty ? '*' : ' ');
break;
default:
@@ -1003,13 +1025,13 @@ teco_interface_draw_info(void)
wclrtoeol(teco_interface.info_window);
/*
- * Make sure the title will consist only of printable
- * characters
+ * Make sure the title will consist only of printable characters.
*/
g_autofree gchar *info_current_printable;
info_current_printable = teco_string_echo(teco_interface.info_current.data,
teco_interface.info_current.len);
- g_autofree gchar *title = g_strconcat(info_type_str, info_current_printable, NULL);
+ g_autofree gchar *title = g_strconcat(info_type_str, info_current_printable,
+ teco_interface.info_dirty ? "*" : "", NULL);
teco_interface_set_window_title(title);
}
@@ -1019,6 +1041,7 @@ teco_interface_info_update_qreg(const teco_qreg_t *reg)
teco_string_clear(&teco_interface.info_current);
teco_string_init(&teco_interface.info_current,
reg->head.name.data, reg->head.name.len);
+ teco_interface.info_dirty = FALSE;
teco_interface.info_type = TECO_INFO_TYPE_QREG;
/* NOTE: drawn in teco_interface_event_loop_iter() */
}
@@ -1030,8 +1053,7 @@ teco_interface_info_update_buffer(const teco_buffer_t *buffer)
teco_string_clear(&teco_interface.info_current);
teco_string_init(&teco_interface.info_current, filename, strlen(filename));
- teco_string_append_c(&teco_interface.info_current,
- buffer->dirty ? '*' : ' ');
+ teco_interface.info_dirty = buffer->dirty;
teco_interface.info_type = TECO_INFO_TYPE_BUFFER;
/* NOTE: drawn in teco_interface_event_loop_iter() */
}
@@ -1044,7 +1066,8 @@ teco_interface_cmdline_update(const teco_cmdline_t *cmdline)
* 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.
+ * wasting a few bytes for control characters and
+ * multi-byte Unicode sequences.
*/
if (teco_interface.cmdline_pad)
delwin(teco_interface.cmdline_pad);
@@ -1172,7 +1195,7 @@ teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len,
{
int rc = str ? PDC_setclipboard(str, str_len) : PDC_clearclipboard();
if (rc != PDC_CLIP_SUCCESS) {
- g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ g_set_error(error, TECO_ERROR, TECO_ERROR_CLIPBOARD,
"Error %d copying to clipboard", rc);
return FALSE;
}
@@ -1194,7 +1217,7 @@ teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError
if (rc == PDC_CLIP_EMPTY)
return TRUE;
if (rc != PDC_CLIP_SUCCESS) {
- g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ g_set_error(error, TECO_ERROR, TECO_ERROR_CLIPBOARD,
"Error %d retrieving clipboard", rc);
return FALSE;
}
@@ -1232,9 +1255,17 @@ teco_interface_init_clipboard(void)
* must be enabled.
* There is no way to find out if they are but we must
* not register the clipboard registers if they aren't.
- * Therefore, a special XTerm clipboard ED flag an be set by the user.
+ * Still, XTerm clipboards are broken with Unicode characters.
+ * Also, there are other terminal emulators supporting OSC-52,
+ * so the XTerm version is only checked if the terminal identifies as XTerm.
+ * Also, a special clipboard ED flag must be set by the user.
+ *
+ * NOTE: Apparently there is also a terminfo entry Ms, but it's probably
+ * not worth using it since it won't always be set and even if set, does not
+ * tell you whether the terminal will actually answer to the escape sequence or not.
*/
- if (!(teco_ed & TECO_ED_XTERM_CLIPBOARD) || teco_xterm_version() < 203)
+ if (!(teco_ed & TECO_ED_OSC52) ||
+ (teco_xterm_version() >= 0 && teco_xterm_version() < 203))
return;
teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new(""));
@@ -1300,6 +1331,8 @@ teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len,
gboolean
teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError **error)
{
+ gboolean ret = TRUE;
+
/*
* Query the clipboard -- XTerm will reply with the
* OSC-52 command that would set the current selection.
@@ -1320,18 +1353,19 @@ teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError
* to be on the safe side.
*/
halfdelay(1); /* 100ms timeout */
- keypad(stdscr, FALSE);
+ /* don't interpret escape sequences */
+ keypad(teco_interface.input_pad, FALSE);
/*
* Skip "\e]52;x;" (7 characters).
*/
for (gint i = 0; i < 7; i++) {
- if (getch() == ERR) {
+ ret = wgetch(teco_interface.input_pad) != ERR;
+ if (!ret) {
/* timeout */
- cbreak();
- g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
+ g_set_error_literal(error, TECO_ERROR, TECO_ERROR_CLIPBOARD,
"Timed out reading XTerm clipboard");
- return FALSE;
+ goto cleanup;
}
}
@@ -1347,17 +1381,22 @@ teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError
*/
gchar buffer[MAX(3, 7)];
- gchar c = (gchar)getch();
- if (c == ERR) {
+ gchar c = (gchar)wgetch(teco_interface.input_pad);
+ ret = c != ERR;
+ if (!ret) {
/* timeout */
- cbreak();
g_string_free(str_base64, TRUE);
- g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
+ g_set_error_literal(error, TECO_ERROR, TECO_ERROR_CLIPBOARD,
"Timed out reading XTerm clipboard");
- return FALSE;
+ goto cleanup;
}
if (c == '\a')
break;
+ if (c == '\e') {
+ /* OSC escape sequence can also be terminated by "\e\\" */
+ c = (gchar)wgetch(teco_interface.input_pad);
+ break;
+ }
/*
* This could be simplified using sscanf() and
@@ -1372,14 +1411,16 @@ teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError
g_string_append_len(str_base64, buffer, out_len);
}
- cbreak();
-
if (str)
*str = str_base64->str;
*len = str_base64->len;
g_string_free(str_base64, !str);
- return TRUE;
+
+cleanup:
+ keypad(teco_interface.input_pad, TRUE);
+ nodelay(teco_interface.input_pad, TRUE);
+ return ret;
}
#else /* !PDCURSES && !CURSES_TTY */
@@ -1489,13 +1530,17 @@ teco_interface_is_interrupted(void)
gboolean
teco_interface_is_interrupted(void)
{
- if (!teco_interface.cmdline_window)
+ if (!teco_interface.input_pad)
/* batch mode */
return teco_interrupted != FALSE;
- /* NOTE: getch() is configured to be nonblocking. */
+ /*
+ * NOTE: wgetch() is configured to be nonblocking.
+ * We wgetch() on a dummy pad, so this does not call any
+ * wrefresh().
+ */
gint key;
- while ((key = wgetch(teco_interface.cmdline_window)) != ERR) {
+ while ((key = wgetch(teco_interface.input_pad)) != ERR) {
if (G_UNLIKELY(key == TECO_CTL_KEY('C')))
return TRUE;
g_queue_push_tail(teco_interface.input_queue,
@@ -1535,35 +1580,19 @@ teco_interface_refresh(void)
static gint
teco_interface_blocking_getch(void)
{
- /*
- * 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.
- *
- * FIXME: NetBSD's curses could be handled like ncurses,
- * but gets into an undefined state when SciTECO processes
- * escape sequences.
- */
-#ifdef NCURSES_UNIX
- keypad(teco_interface.cmdline_window, teco_ed & TECO_ED_FNKEYS);
-#endif
-
/* no special <CTRL/C> handling */
raw();
- nodelay(teco_interface.cmdline_window, FALSE);
+ nodelay(teco_interface.input_pad, FALSE);
/*
* Memory limiting is stopped temporarily, since it might otherwise
* constantly place 100% load on the CPU.
*/
teco_memory_stop_limiting();
- gint key = wgetch(teco_interface.cmdline_window);
+ gint key = wgetch(teco_interface.input_pad);
teco_memory_start_limiting();
/* allow asynchronous interruptions on <CTRL/C> */
teco_interrupted = FALSE;
- nodelay(teco_interface.cmdline_window, TRUE);
+ nodelay(teco_interface.input_pad, TRUE);
#if defined(CURSES_TTY) || defined(PDCURSES_WINCON) || defined(NCURSES_WIN32)
noraw(); /* FIXME: necessary because of NCURSES_WIN32 bug */
cbreak();
@@ -1585,6 +1614,11 @@ teco_interface_blocking_getch(void)
void
teco_interface_event_loop_iter(void)
{
+ static gchar keybuf[4];
+ static gint keybuf_i = 0;
+
+ GError **error = &teco_interface.event_loop_error;
+
gint key = g_queue_is_empty(teco_interface.input_queue)
? teco_interface_blocking_getch()
: GPOINTER_TO_INT(g_queue_pop_head(teco_interface.input_queue));
@@ -1613,23 +1647,24 @@ teco_interface_event_loop_iter(void)
* backspace.
* In SciTECO backspace is normalized to ^H.
*/
- if (!teco_cmdline_keypress_c(TECO_CTL_KEY('H'),
- &teco_interface.event_loop_error))
+ if (!teco_cmdline_keymacro_c(TECO_CTL_KEY('H'), error))
return;
break;
case KEY_ENTER:
case '\r':
case '\n':
- if (!teco_cmdline_keypress_c('\n', &teco_interface.event_loop_error))
+ if (!teco_cmdline_keymacro_c('\n', error))
return;
break;
/*
* Function key macros
+ *
+ * FIXME: Perhaps support everything returned by keyname()?
*/
#define FN(KEY) \
case KEY_##KEY: \
- if (!teco_cmdline_fnmacro(#KEY, &teco_interface.event_loop_error)) \
+ if (!teco_cmdline_keymacro(#KEY, -1, error)) \
return; \
break
#define FNS(KEY) FN(KEY); FN(S##KEY)
@@ -1639,9 +1674,8 @@ teco_interface_event_loop_iter(void)
gchar macro_name[3+1];
g_snprintf(macro_name, sizeof(macro_name),
- "F%d", key - KEY_F0);
- if (!teco_cmdline_fnmacro(macro_name,
- &teco_interface.event_loop_error))
+ "F%d", key - KEY_F0);
+ if (!teco_cmdline_keymacro(macro_name, -1, error))
return;
break;
}
@@ -1660,9 +1694,31 @@ teco_interface_event_loop_iter(void)
* Control keys and keys with printable representation
*/
default:
- if (key < 0x80 &&
- !teco_cmdline_keypress_c(key, &teco_interface.event_loop_error))
+ if (key > 0xFF)
+ /* unhandled function key */
return;
+
+ /*
+ * NOTE: There's also wget_wch(), but it requires
+ * a widechar version of Curses.
+ */
+ keybuf[keybuf_i++] = key;
+ gsize len = keybuf_i;
+ gunichar cp = g_utf8_get_char_validated(keybuf, len);
+ if (keybuf_i >= sizeof(keybuf) || cp != (gunichar)-2)
+ keybuf_i = 0;
+ if ((gint32)cp < 0)
+ /* incomplete or invalid */
+ return;
+ switch (teco_cmdline_keymacro(keybuf, len, error)) {
+ case TECO_KEYMACRO_ERROR:
+ return;
+ case TECO_KEYMACRO_SUCCESS:
+ break;
+ case TECO_KEYMACRO_UNDEFINED:
+ if (!teco_cmdline_keypress(keybuf, len, error))
+ return;
+ }
}
teco_interface_refresh();
@@ -1733,6 +1789,8 @@ teco_interface_cleanup(void)
delwin(teco_interface.cmdline_pad);
if (teco_interface.msg_window)
delwin(teco_interface.msg_window);
+ if (teco_interface.input_pad)
+ delwin(teco_interface.input_pad);
/*
* PDCurses/WinCon crashes if initscr() wasn't called.