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.c174
1 files changed, 121 insertions, 53 deletions
diff --git a/src/interface-curses/interface.c b/src/interface-curses/interface.c
index 073ff86..6c8b231 100644
--- a/src/interface-curses/interface.c
+++ b/src/interface-curses/interface.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -138,8 +138,6 @@ static gint teco_xterm_version(void) G_GNUC_UNUSED;
static gint teco_interface_blocking_getch(void);
-#define UNNAMED_FILE "(Unnamed)"
-
/**
* Get bright variant of one of the 8 standard
* curses colors.
@@ -202,6 +200,7 @@ static struct {
TECO_INFO_TYPE_BUFFER = 0,
TECO_INFO_TYPE_QREG
} info_type;
+ /* current document's name or empty string for "(Unnamed)" buffer */
teco_string_t info_current;
gboolean info_dirty;
@@ -233,9 +232,12 @@ static struct {
*
* Scinterm no longer initializes all color pairs for all combinations of
* the builtin foreground and background colors.
- * Since curses guarantees only 256 color pairs, we cannot do that either.
- * Instead we allocate color pairs beginnig at 128 on demand
- * (similar to what Scinterm does).
+ * Since curses does not guarantee any number of color pairs, we cannot do that either.
+ * Instead we allocate color pairs beginnig at the second half of
+ * color pair space on demand (similar to what Scinterm does).
+ *
+ * @note Scinterm now also has scintilla_set_color_offsets(),
+ * so we could use the color pairs beginning with 1 as well.
*
* @param fg curses foreground color
* @param bg curses background color
@@ -244,16 +246,24 @@ static struct {
static gshort
teco_color_pair(gshort fg, gshort bg)
{
- static gshort last_pair = 127;
+ static gshort allocated_pairs = 0;
G_STATIC_ASSERT(sizeof(gshort)*2 <= sizeof(guint));
- gpointer key = GUINT_TO_POINTER(((guint)fg << 16) | bg);
+ gpointer key = GUINT_TO_POINTER(((guint)fg << 8*sizeof(bg)) | bg);
gpointer value = g_hash_table_lookup(teco_interface.pair_table, key);
if (G_LIKELY(value != NULL))
return GPOINTER_TO_UINT(value);
- init_pair(++last_pair, fg, bg);
- g_hash_table_insert(teco_interface.pair_table, key, GUINT_TO_POINTER(last_pair));
- return last_pair;
+ /*
+ * We are still adding colors with COLOR_PAIR(), which allows
+ * at most 256 color pairs (1-255).
+ */
+ gshort new_pair = MIN(COLOR_PAIRS, 256)/2 + allocated_pairs;
+ if (G_UNLIKELY(new_pair >= 256))
+ return 0;
+ allocated_pairs++;
+ init_pair(new_pair, fg, bg);
+ g_hash_table_insert(teco_interface.pair_table, key, GUINT_TO_POINTER(new_pair));
+ return new_pair;
}
/**
@@ -276,8 +286,8 @@ teco_color_attr(gshort fg, gshort bg)
* Basic support for monochrome terminals:
* Every background, that is not black is assumed to be a
* dark-on-bright area, rendered in reverse.
- * This will at least work with the terminal.tes
- * color scheme.
+ * This will at least work with the terminal.tes and contrast.tes
+ * color schemes.
*/
return bg != COLOR_BLACK ? A_REVERSE : 0;
}
@@ -393,6 +403,13 @@ teco_view_noutrefresh(teco_view_t *ctx)
scintilla_noutrefresh(ctx);
}
+static inline void
+teco_view_update_cursor(teco_view_t *ctx)
+{
+ if (teco_view_ssm(ctx, SCI_GETCARETSTYLE, 0, 0) & CARETSTYLE_CURSES)
+ scintilla_update_cursor(ctx);
+}
+
static inline WINDOW *
teco_view_get_window(teco_view_t *ctx)
{
@@ -437,20 +454,16 @@ teco_interface_init(void)
teco_curses_info_popup_init(&teco_interface.popup);
- /*
- * Make sure we have a string for the info line
- * even if teco_interface_info_update() is never called.
- */
- teco_string_init(&teco_interface.info_current, PACKAGE_NAME, strlen(PACKAGE_NAME));
-
teco_cmdline_init();
/*
* The default INDIC_STRIKE wouldn't be visible.
- * Instead we use INDIC_STRAIGHTBOX, which will be rendered as underlined if
- * the alpha is 0.
+ * Instead we use INDIC_SQUIGGLE, which is rendered as A_UNDERLINE.
+ */
+ teco_cmdline_ssm(SCI_INDICSETSTYLE, INDICATOR_RUBBEDOUT, INDIC_SQUIGGLE);
+ /*
+ * Enable hardware cursor by default.
*/
- teco_cmdline_ssm(SCI_INDICSETSTYLE, INDICATOR_RUBBEDOUT, INDIC_STRAIGHTBOX);
- teco_cmdline_ssm(SCI_INDICSETALPHA, INDICATOR_RUBBEDOUT, 0);
+ teco_cmdline_ssm(SCI_SETCARETSTYLE, CARETSTYLE_CURSES, 0);
/*
* On all platforms except Curses/XTerm, it's
@@ -790,8 +803,6 @@ teco_interface_init_interactive(GError **error)
cbreak();
noecho();
- /* Scintilla draws its own cursor */
- curs_set(0);
/*
* This has also been observed to reduce flickering
* in teco_interface_refresh().
@@ -818,7 +829,7 @@ teco_interface_init_interactive(GError **error)
* must always be TRUE so we receive KEY_RESIZE.
*/
keypad(teco_interface.input_pad, TRUE);
- nodelay(teco_interface.input_pad, TRUE);
+ wtimeout(teco_interface.input_pad, 0);
teco_interface.input_queue = g_queue_new();
@@ -990,10 +1001,11 @@ teco_interface_getch(gboolean widechar)
/*
* Signal that we accept input by drawing a real cursor in the message bar.
+ * FIXME: curs_set(2) would be better, but isn't always visible.
*/
wmove(teco_interface.msg_window, 0, 0);
- curs_set(1);
wrefresh(teco_interface.msg_window);
+ curs_set(1);
gchar buf[4];
gint i = 0;
@@ -1020,7 +1032,6 @@ teco_interface_getch(gboolean widechar)
i = 0;
} while (cp < 0);
- curs_set(0);
return cp;
}
@@ -1160,26 +1171,30 @@ teco_interface_draw_info(void)
waddstr(teco_interface.info_window, PACKAGE_NAME " ");
+ teco_string_t info_current = teco_interface.info_current;
+ if (!info_current.len) {
+ info_current.data = TECO_UNNAMED_FILE;
+ info_current.len = strlen(info_current.data);
+ }
+
switch (teco_interface.info_type) {
case TECO_INFO_TYPE_QREG:
info_type_str = PACKAGE_NAME " - <QRegister> ";
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,
- teco_interface.info_current.len, -1);
+ info_current.data, info_current.len, -1);
break;
case TECO_INFO_TYPE_BUFFER:
info_type_str = PACKAGE_NAME " - <Buffer> ";
- g_assert(!teco_string_contains(&teco_interface.info_current, '\0'));
+ g_assert(!teco_string_contains(info_current, '\0'));
+ /* "(Unnamed)" buffer has to be looked up as "" */
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,
+ teco_curses_format_filename(teco_interface.info_window, info_current.data,
getmaxx(teco_interface.info_window) -
getcurx(teco_interface.info_window) - 1);
waddch(teco_interface.info_window, teco_interface.info_dirty ? '*' : ' ');
@@ -1195,8 +1210,7 @@ teco_interface_draw_info(void)
* 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);
+ info_current_printable = teco_string_echo(info_current.data, info_current.len);
g_autofree gchar *title = g_strconcat(info_type_str, info_current_printable,
teco_interface.info_dirty ? "*" : "", NULL);
teco_interface_set_window_title(title);
@@ -1216,10 +1230,9 @@ teco_interface_info_update_qreg(const teco_qreg_t *reg)
void
teco_interface_info_update_buffer(const teco_buffer_t *buffer)
{
- const gchar *filename = buffer->filename ? : UNNAMED_FILE;
-
teco_string_clear(&teco_interface.info_current);
- teco_string_init(&teco_interface.info_current, filename, strlen(filename));
+ teco_string_init(&teco_interface.info_current, buffer->filename,
+ buffer->filename ? strlen(buffer->filename) : 0);
teco_interface.info_dirty = buffer->state > TECO_BUFFER_CLEAN;
teco_interface.info_type = TECO_INFO_TYPE_BUFFER;
/* NOTE: drawn in teco_interface_event_loop_iter() */
@@ -1233,7 +1246,20 @@ teco_interface_info_update_buffer(const teco_buffer_t *buffer)
* default clipboard ("~") as we do not know whether
* it corresponds to the X11 PRIMARY, SECONDARY or
* CLIPBOARD selections.
+ *
+ * On XCurses we must not (and don't have to) probe
+ * the clipboard as it would be before Xinitscr().
*/
+#ifdef XCURSES
+
+static void
+teco_interface_init_clipboard(void)
+{
+ teco_qreg_table_replace(&teco_qreg_table_globals, teco_qreg_clipboard_new(""));
+}
+
+#else /* XCURSES */
+
static void
teco_interface_init_clipboard(void)
{
@@ -1258,6 +1284,8 @@ teco_interface_init_clipboard(void)
teco_qreg_table_replace(&teco_qreg_table_globals, teco_qreg_clipboard_new(""));
}
+#endif /* !XCURSES */
+
gboolean
teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len, GError **error)
{
@@ -1429,7 +1457,7 @@ teco_interface_osc52_get_clipboard(const gchar *name, gchar **str, gsize *len, G
* We restore all changed Curses settings before returning
* to be on the safe side.
*/
- halfdelay(1); /* 100ms timeout */
+ wtimeout(teco_interface.input_pad, 100);
/* don't interpret escape sequences */
keypad(teco_interface.input_pad, FALSE);
@@ -1496,7 +1524,7 @@ teco_interface_osc52_get_clipboard(const gchar *name, gchar **str, gsize *len, G
cleanup:
keypad(teco_interface.input_pad, TRUE);
- nodelay(teco_interface.input_pad, TRUE);
+ wtimeout(teco_interface.input_pad, 0);
return ret;
}
@@ -1547,7 +1575,7 @@ teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len,
g_auto(teco_string_t) command;
if (!reg->vtable->get_string(reg, &command.data, &command.len, NULL, error))
return FALSE;
- if (teco_string_contains(&command, '\0')) {
+ if (teco_string_contains(command, '\0')) {
teco_error_qregcontainsnull_set(error, reg_name, strlen(reg_name), FALSE);
return FALSE;
}
@@ -1608,7 +1636,7 @@ teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError
g_auto(teco_string_t) command;
if (!reg->vtable->get_string(reg, &command.data, &command.len, NULL, error))
return FALSE;
- if (teco_string_contains(&command, '\0')) {
+ if (teco_string_contains(command, '\0')) {
teco_error_qregcontainsnull_set(error, reg_name, strlen(reg_name), FALSE);
return FALSE;
}
@@ -1828,7 +1856,31 @@ teco_interface_refresh(gboolean force)
wnoutrefresh(teco_interface.msg_window);
teco_view_noutrefresh(teco_cmdline.view);
teco_curses_info_popup_noutrefresh(&teco_interface.popup);
+ /*
+ * If hardware cursors (CARETSTYLE_CURSES) are enabled on the
+ * command-line view, make sure that the cursor is left
+ * in the correct position.
+ *
+ * FIXME: This shouldn't be necessary if we refreshed the command-line
+ * view last. Also, if we wanted to support the hardware cursor
+ * in the main view as well, we'd have to handle a possibly
+ * overlappig info popup.
+ */
+ teco_view_update_cursor(teco_cmdline.view);
doupdate();
+
+ /*
+ * Scinterm enables/disables the hardware cursor,
+ * but only if CARETSTYLE_CURSES is used.
+ * Disabling the cursor repeatedly ensures you can change
+ * the caret style interactively.
+ * It also makes sure the cursor is reset after
+ * teco_interface_getch().
+ * Also, window resizes sometimes enable the hardware cursor on
+ * PDCurses/wincon (FIXME).
+ */
+ if (!(teco_view_ssm(teco_cmdline.view, SCI_GETCARETSTYLE, 0, 0) & CARETSTYLE_CURSES))
+ curs_set(0);
}
#if NCURSES_MOUSE_VERSION >= 2
@@ -1865,10 +1917,13 @@ teco_interface_process_mevent(MEVENT *event, GError **error)
event->y, event->x);
if (insert && machine->current->insert_completion_cb) {
- /* successfully clicked popup item */
+ /*
+ * Successfully clicked popup item.
+ * `insert` is the empty string for the "(Unnamed)" buffer.
+ */
const teco_string_t insert_suffix = {insert->data + teco_interface.popup_prefix_len,
insert->len - teco_interface.popup_prefix_len};
- if (!machine->current->insert_completion_cb(machine, &insert_suffix, error))
+ if (!machine->current->insert_completion_cb(machine, insert_suffix, error))
return FALSE;
teco_interface_popup_clear();
@@ -1968,6 +2023,20 @@ teco_interface_process_mevent(MEVENT *event, GError **error)
return teco_cmdline_keymacro("MOUSE", -1, error);
}
+#ifdef __PDCURSES__
+
+static gboolean
+teco_interface_getmouse(GError **error)
+{
+ MEVENT event;
+
+ /* in contrast to ncurses, there is no separate mouse event queue */
+ return getmouse(&event) != OK ||
+ teco_interface_process_mevent(&event, error);
+}
+
+#else /* __PDCURSES__ */
+
static gboolean
teco_interface_getmouse(GError **error)
{
@@ -1980,6 +2049,8 @@ teco_interface_getmouse(GError **error)
return TRUE;
}
+#endif /* !__PDCURSES__ */
+
#endif /* NCURSES_MOUSE_VERSION >= 2 */
static gint
@@ -2001,7 +2072,8 @@ teco_interface_blocking_getch(void)
gboolean new_mousekey = (teco_ed & TECO_ED_MOUSEKEY) != 0;
if (new_mousekey != old_mousekey) {
old_mousekey = new_mousekey;
- mmask_t mmask = BUTTON_EVENT(PRESSED) | BUTTON_EVENT(RELEASED);
+ mmask_t mmask = BUTTON_EVENT(PRESSED) | BUTTON_EVENT(RELEASED) |
+ BUTTON_SHIFT | BUTTON_CTRL | BUTTON_ALT;
#ifdef __PDCURSES__
/*
* On PDCurses it's crucial NOT to mask for BUTTONX_CLICKED.
@@ -2016,7 +2088,6 @@ teco_interface_blocking_getch(void)
/* no special <CTRL/C> handling */
raw();
- nodelay(teco_interface.input_pad, FALSE);
/*
* Make sure we return when it's time to create recovery dumps.
@@ -2027,6 +2098,8 @@ teco_interface_blocking_getch(void)
gdouble elapsed = g_timer_elapsed(teco_interface.recovery_timer, NULL);
wtimeout(teco_interface.input_pad,
MAX((gdouble)teco_ring_recovery_interval - elapsed, 0)*1000);
+ } else {
+ wtimeout(teco_interface.input_pad, -1);
}
/*
@@ -2038,7 +2111,7 @@ teco_interface_blocking_getch(void)
teco_memory_start_limiting();
/* allow asynchronous interruptions on <CTRL/C> */
teco_interrupted = FALSE;
- nodelay(teco_interface.input_pad, TRUE);
+ wtimeout(teco_interface.input_pad, 0);
#if defined(CURSES_TTY) || defined(PDCURSES_WINCON) || defined(NCURSES_WIN32)
noraw(); /* FIXME: necessary because of NCURSES_WIN32 bug */
cbreak();
@@ -2083,11 +2156,6 @@ teco_interface_event_loop_iter(void)
return;
#ifdef KEY_RESIZE
case KEY_RESIZE:
- /*
- * At least on PDCurses/Wincon, the hardware cursor is sometimes
- * reactivated.
- */
- curs_set(0);
teco_interface_resize_all_windows();
break;
#endif