aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/interface-curses
diff options
context:
space:
mode:
Diffstat (limited to 'src/interface-curses')
-rw-r--r--src/interface-curses/curses-icons.c6
-rw-r--r--src/interface-curses/curses-icons.h2
-rw-r--r--src/interface-curses/curses-info-popup.c22
-rw-r--r--src/interface-curses/curses-info-popup.h2
-rw-r--r--src/interface-curses/curses-utils.c2
-rw-r--r--src/interface-curses/curses-utils.h5
-rw-r--r--src/interface-curses/interface.c150
7 files changed, 133 insertions, 56 deletions
diff --git a/src/interface-curses/curses-icons.c b/src/interface-curses/curses-icons.c
index 7c021d6..0e14655 100644
--- a/src/interface-curses/curses-icons.c
+++ b/src/interface-curses/curses-icons.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
@@ -362,6 +362,10 @@ teco_curses_icon_cmp(const void *a, const void *b)
gunichar
teco_curses_icons_lookup_file(const gchar *filename)
{
+ if (!filename || !*filename)
+ /* "(Unnamed)" file */
+ return 0xf1036; /* 󱀶 */
+
g_autofree gchar *basename = g_path_get_basename(filename);
const teco_curses_icon_t *icon;
diff --git a/src/interface-curses/curses-icons.h b/src/interface-curses/curses-icons.h
index fce9d75..a12fe88 100644
--- a/src/interface-curses/curses-icons.h
+++ b/src/interface-curses/curses-icons.h
@@ -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
diff --git a/src/interface-curses/curses-info-popup.c b/src/interface-curses/curses-info-popup.c
index c51a99b..edb6e15 100644
--- a/src/interface-curses/curses-info-popup.c
+++ b/src/interface-curses/curses-info-popup.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
@@ -19,6 +19,8 @@
#include "config.h"
#endif
+#include <string.h>
+
#include <glib.h>
#include <curses.h>
@@ -38,6 +40,7 @@ typedef struct {
teco_stailq_entry_t entry;
teco_popup_entry_type_t type;
+ /** entry name or empty string for the "(Unnamed)" buffer */
teco_string_t name;
gboolean highlight;
} teco_popup_entry_t;
@@ -122,25 +125,32 @@ teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr)
if (entry->highlight)
wattron(ctx->pad, A_BOLD);
+ teco_string_t name = entry->name;
+ if (!name.len) {
+ name.data = TECO_UNNAMED_FILE;
+ name.len = strlen(name.data);
+ }
+
switch (entry->type) {
case TECO_POPUP_FILE:
- g_assert(!teco_string_contains(&entry->name, '\0'));
+ g_assert(!teco_string_contains(name, '\0'));
if (teco_ed & TECO_ED_ICONS) {
+ /* "(Unnamed)" buffer is looked up as "" */
teco_curses_add_wc(ctx->pad, teco_curses_icons_lookup_file(entry->name.data));
waddch(ctx->pad, ' ');
}
- teco_curses_format_filename(ctx->pad, entry->name.data, -1);
+ teco_curses_format_filename(ctx->pad, name.data, -1);
break;
case TECO_POPUP_DIRECTORY:
- g_assert(!teco_string_contains(&entry->name, '\0'));
+ g_assert(!teco_string_contains(name, '\0'));
if (teco_ed & TECO_ED_ICONS) {
teco_curses_add_wc(ctx->pad, teco_curses_icons_lookup_dir(entry->name.data));
waddch(ctx->pad, ' ');
}
- teco_curses_format_filename(ctx->pad, entry->name.data, -1);
+ teco_curses_format_filename(ctx->pad, name.data, -1);
break;
default:
- teco_curses_format_str(ctx->pad, entry->name.data, entry->name.len, -1);
+ teco_curses_format_str(ctx->pad, name.data, name.len, -1);
break;
}
diff --git a/src/interface-curses/curses-info-popup.h b/src/interface-curses/curses-info-popup.h
index d845b29..fd923e9 100644
--- a/src/interface-curses/curses-info-popup.h
+++ b/src/interface-curses/curses-info-popup.h
@@ -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
diff --git a/src/interface-curses/curses-utils.c b/src/interface-curses/curses-utils.c
index f94b6dc..875c332 100644
--- a/src/interface-curses/curses-utils.c
+++ b/src/interface-curses/curses-utils.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
diff --git a/src/interface-curses/curses-utils.h b/src/interface-curses/curses-utils.h
index 18cdd3d..97fc1cc 100644
--- a/src/interface-curses/curses-utils.h
+++ b/src/interface-curses/curses-utils.h
@@ -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
@@ -20,6 +20,9 @@
#include <curses.h>
+/** what is displayed for unnamed buffers in the info line and popups */
+#define TECO_UNNAMED_FILE "(Unnamed)"
+
guint teco_curses_format_str(WINDOW *win, const gchar *str, gsize len, gint max_width);
guint teco_curses_format_filename(WINDOW *win, const gchar *filename, gint max_width);
diff --git a/src/interface-curses/interface.c b/src/interface-curses/interface.c
index 073ff86..d07ff21 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;
@@ -237,6 +236,9 @@ static struct {
* Instead we allocate color pairs beginnig at 128 on demand
* (similar to what Scinterm does).
*
+ * @note Scinterm now also has scintilla_set_color_offsets(),
+ * so we could use the lower 127 color pairs as well.
+ *
* @param fg curses foreground color
* @param bg curses background color
* @return curses color pair number
@@ -276,8 +278,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 +395,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 +446,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_STRAIGHTBOX);
- teco_cmdline_ssm(SCI_INDICSETALPHA, INDICATOR_RUBBEDOUT, 0);
+ teco_cmdline_ssm(SCI_INDICSETSTYLE, INDICATOR_RUBBEDOUT, INDIC_SQUIGGLE);
+ /*
+ * Enable hardware cursor by default.
+ */
+ teco_cmdline_ssm(SCI_SETCARETSTYLE, CARETSTYLE_CURSES, 0);
/*
* On all platforms except Curses/XTerm, it's
@@ -790,8 +795,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 +821,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 +993,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 +1024,6 @@ teco_interface_getch(gboolean widechar)
i = 0;
} while (cp < 0);
- curs_set(0);
return cp;
}
@@ -1160,26 +1163,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 +1202,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 +1222,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 +1238,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 +1276,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 +1449,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 +1516,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 +1567,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 +1628,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 +1848,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 +1909,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 +2015,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 +2041,8 @@ teco_interface_getmouse(GError **error)
return TRUE;
}
+#endif /* !__PDCURSES__ */
+
#endif /* NCURSES_MOUSE_VERSION >= 2 */
static gint
@@ -2001,7 +2064,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 +2080,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 +2090,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 +2103,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 +2148,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