diff options
Diffstat (limited to 'src/interface-curses')
-rw-r--r-- | src/interface-curses/curses-icons.c | 2 | ||||
-rw-r--r-- | src/interface-curses/curses-icons.h | 2 | ||||
-rw-r--r-- | src/interface-curses/curses-info-popup.c | 74 | ||||
-rw-r--r-- | src/interface-curses/curses-info-popup.h | 20 | ||||
-rw-r--r-- | src/interface-curses/curses-utils.c | 2 | ||||
-rw-r--r-- | src/interface-curses/curses-utils.h | 2 | ||||
-rw-r--r-- | src/interface-curses/interface.c | 437 |
7 files changed, 479 insertions, 60 deletions
diff --git a/src/interface-curses/curses-icons.c b/src/interface-curses/curses-icons.c index e2e4256..3e63d02 100644 --- a/src/interface-curses/curses-icons.c +++ b/src/interface-curses/curses-icons.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2024 Robin Haberkorn + * Copyright (C) 2012-2025 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-icons.h b/src/interface-curses/curses-icons.h index c1be06f..933241d 100644 --- a/src/interface-curses/curses-icons.h +++ b/src/interface-curses/curses-icons.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2024 Robin Haberkorn + * Copyright (C) 2012-2025 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 e6e1549..dffbcf8 100644 --- a/src/interface-curses/curses-info-popup.c +++ b/src/interface-curses/curses-info-popup.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2024 Robin Haberkorn + * Copyright (C) 2012-2025 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 @@ -200,6 +200,65 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr) wmove(ctx->window, bar_y, cols-1); wattron(ctx->window, A_REVERSE); wvline(ctx->window, ' ', bar_height); +} + +/** + * Find the entry at the given character coordinates. + * + * @param ctx The popup widget to look up + * @param y The pointer's Y position, relative to the popup's window + * @param x The pointer's X position, relative to the popup's window + * @return Pointer to the entry's string under the pointer or NULL. + * This string is owned by the popup and is only valid until the + * popup is cleared. + * + * @note This must match the calculations in teco_curses_info_popup_init_pad(). + * But we could perhaps also cache these values. + */ +const teco_string_t * +teco_curses_info_popup_getentry(teco_curses_info_popup_t *ctx, gint y, gint x) +{ + int cols = getmaxx(stdscr); /**! screen width */ + gint pad_cols; /**! entry columns */ + gint pad_colwidth; /**! width per entry column */ + + if (y == 0) + return NULL; + + /* + * With Unicode icons enabled, we reserve 2 characters at the beginning and one + * after the filename/directory. + * Otherwise 2 characters after the entry. + */ + gint reserve = teco_ed & TECO_ED_ICONS ? 2+1 : 2; + pad_colwidth = MIN(ctx->longest + reserve, cols - 2); + + /* pad_cols = floor((cols - 2) / pad_colwidth) */ + pad_cols = (cols - 2) / pad_colwidth; + + gint cur_col = 0; + for (teco_stailq_entry_t *cur = ctx->list.first; cur != NULL; cur = cur->next) { + teco_popup_entry_t *entry = (teco_popup_entry_t *)cur; + gint cur_line = cur_col/pad_cols + 1; + + if (cur_line > ctx->pad_first_line+y) + break; + if (cur_line == ctx->pad_first_line+y && + x > (cur_col % pad_cols)*pad_colwidth && x <= ((cur_col % pad_cols)+1)*pad_colwidth) + return &entry->name; + + cur_col++; + } + + return NULL; +} + +void +teco_curses_info_popup_scroll_page(teco_curses_info_popup_t *ctx) +{ + gint lines = getmaxy(stdscr); + gint pad_lines = getmaxy(ctx->pad); + gint popup_lines = MIN(pad_lines + 1, lines - 1); /* progress scroll position */ ctx->pad_first_line += popup_lines - 1; @@ -211,6 +270,19 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr) } void +teco_curses_info_popup_scroll(teco_curses_info_popup_t *ctx, gint delta) +{ + gint lines = getmaxy(stdscr); + gint pad_lines = getmaxy(ctx->pad); + gint popup_lines = MIN(pad_lines + 1, lines - 1); + + ctx->pad_first_line = MAX(ctx->pad_first_line+delta, 0); + if (pad_lines - ctx->pad_first_line < popup_lines - 1) + /* show last page */ + ctx->pad_first_line = pad_lines - (popup_lines - 1); +} + +void teco_curses_info_popup_clear(teco_curses_info_popup_t *ctx) { if (ctx->window) diff --git a/src/interface-curses/curses-info-popup.h b/src/interface-curses/curses-info-popup.h index a6c28a5..d845b29 100644 --- a/src/interface-curses/curses-info-popup.h +++ b/src/interface-curses/curses-info-popup.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2024 Robin Haberkorn + * Copyright (C) 2012-2025 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 @@ -23,6 +23,7 @@ #include <curses.h> #include "list.h" +#include "string-utils.h" #include "interface.h" typedef struct { @@ -49,6 +50,10 @@ void teco_curses_info_popup_add(teco_curses_info_popup_t *ctx, teco_popup_entry_ const gchar *name, gsize name_len, gboolean highlight); void teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr); +const teco_string_t *teco_curses_info_popup_getentry(teco_curses_info_popup_t *ctx, gint y, gint x); +void teco_curses_info_popup_scroll_page(teco_curses_info_popup_t *ctx); +void teco_curses_info_popup_scroll(teco_curses_info_popup_t *ctx, gint delta); + static inline bool teco_curses_info_popup_is_shown(teco_curses_info_popup_t *ctx) { @@ -58,8 +63,17 @@ teco_curses_info_popup_is_shown(teco_curses_info_popup_t *ctx) static inline void teco_curses_info_popup_noutrefresh(teco_curses_info_popup_t *ctx) { - if (ctx->window) - wnoutrefresh(ctx->window); + if (!ctx->window) + return; + /* + * NOTE: Scinterm always redraws its window, which is + * equivalent to touching it, even if it didn't change. + * Consequently, wnoutrefresh() will always copy it to newscr. + * We must therefore always redraw the popup as well, so it + * will still overlap the Scintilla view. + */ + touchwin(ctx->window); + wnoutrefresh(ctx->window); } void teco_curses_info_popup_clear(teco_curses_info_popup_t *ctx); diff --git a/src/interface-curses/curses-utils.c b/src/interface-curses/curses-utils.c index c751afd..f362424 100644 --- a/src/interface-curses/curses-utils.c +++ b/src/interface-curses/curses-utils.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2024 Robin Haberkorn + * Copyright (C) 2012-2025 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 2c819ee..9f2e8f3 100644 --- a/src/interface-curses/curses-utils.h +++ b/src/interface-curses/curses-utils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2024 Robin Haberkorn + * Copyright (C) 2012-2025 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/interface.c b/src/interface-curses/interface.c index f713bc1..42ffdc6 100644 --- a/src/interface-curses/interface.c +++ b/src/interface-curses/interface.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2024 Robin Haberkorn + * Copyright (C) 2012-2025 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 @@ -44,6 +44,10 @@ #include <glib/gprintf.h> #include <glib/gstdio.h> +#ifdef G_OS_UNIX +#include <sys/wait.h> +#endif + #include <curses.h> #ifdef HAVE_TIGETSTR @@ -53,6 +57,7 @@ * Some macros in term.h interfere with our code. */ #undef lines +#undef buttons #endif #include <Scintilla.h> @@ -355,6 +360,7 @@ static struct { GQueue *input_queue; teco_curses_info_popup_t popup; + gsize popup_prefix_len; /** * GError "thrown" by teco_interface_event_loop_iter(). @@ -688,6 +694,15 @@ teco_interface_init_interactive(GError **error) #endif /* + * Disables click-detection. + * If we'd want to discern PRESSED and CLICKED events, + * we'd have to emulate the same feature on GTK. + */ +#if NCURSES_MOUSE_VERSION >= 2 + mouseinterval(0); +#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. @@ -700,6 +715,11 @@ teco_interface_init_interactive(GError **error) noecho(); /* Scintilla draws its own cursor */ curs_set(0); + /* + * This has also been observed to reduce flickering + * in teco_interface_refresh(). + */ + leaveok(stdscr, TRUE); teco_interface.info_window = newwin(1, 0, 0, 0); teco_interface.msg_window = newwin(1, 0, LINES - 2, 0); @@ -781,11 +801,11 @@ teco_interface_restore_batch(void) */ #ifdef CURSES_TTY if (teco_interface.stdout_orig >= 0) { - int fd = dup2(teco_interface.stdout_orig, 1); + G_GNUC_UNUSED int fd = dup2(teco_interface.stdout_orig, 1); g_assert(fd == 1); } if (teco_interface.stderr_orig >= 0) { - int fd = dup2(teco_interface.stderr_orig, 2); + G_GNUC_UNUSED int fd = dup2(teco_interface.stderr_orig, 2); g_assert(fd == 2); } #endif @@ -1247,39 +1267,7 @@ teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError return TRUE; } -#elif defined(CURSES_TTY) - -static void -teco_interface_init_clipboard(void) -{ - /* - * At least on XTerm, there are escape sequences - * for modifying the clipboard (OSC-52). - * This is not standardized in terminfo, so we add special - * XTerm support here. Unfortunately, it is pretty hard to find out - * whether clipboard operations will actually work. - * XTerm must be at least at v203 and the corresponding window operations - * 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. - * 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_OSC52) || - (teco_xterm_version() >= 0 && teco_xterm_version() < 203)) - return; - - teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("")); - teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("P")); - teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("S")); - teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("C")); -} +#elif defined(G_OS_UNIX) && defined(CURSES_TTY) static inline gchar get_selection_by_name(const gchar *name) @@ -1294,9 +1282,48 @@ get_selection_by_name(const gchar *name) return g_ascii_tolower(*name) ? : 'c'; } -gboolean -teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len, - GError **error) +/* + * OSC-52 clipboard implementation. + * + * At least on XTerm, there are escape sequences + * for modifying the clipboard (OSC-52). + * This is not standardized in terminfo, so we add special + * XTerm support here. Unfortunately, it is pretty hard to find out + * whether clipboard operations will actually work. + * XTerm must be at least at v203 and the corresponding window operations + * 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. + * 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. + * + * This is a rarely used feature and could theoretically also be handled + * by the $SCITECO_CLIPBOARD_SET/GET feature. + * Unfortunately, there is no readily available command-line utility allowing both + * copying and pasting via OSC-52. + * That's really the only reason we keep built-in OSC-52 clipboard support. + * + * FIXME: This is the only thing here requiring CURSES_TTY. + * On the other hand, there is hardly any non-PDCurses on UNIX, which is not + * on a TTY, so we shouldn't be loosing much by requiring both. + */ + +static inline gboolean +teco_interface_osc52_is_enabled(void) +{ + return teco_ed & TECO_ED_OSC52 && + (teco_xterm_version() < 0 || teco_xterm_version() >= 203); +} + +static gboolean +teco_interface_osc52_set_clipboard(const gchar *name, const gchar *str, gsize str_len, + GError **error) { fputs("\e]52;", teco_interface.screen_tty); fputc(get_selection_by_name(name), teco_interface.screen_tty); @@ -1335,8 +1362,8 @@ teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len, return TRUE; } -gboolean -teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError **error) +static gboolean +teco_interface_osc52_get_clipboard(const gchar *name, gchar **str, gsize *len, GError **error) { gboolean ret = TRUE; @@ -1430,7 +1457,167 @@ cleanup: return ret; } -#else /* !PDCURSES && !CURSES_TTY */ +/* + * Implementation using external processes. + * + * NOTE: This could be done with the portable GSpawn API as well, + * but this implementation is much simpler. + * We don't really need it on Windows anyway as long as we are using + * only PDCurses. + * This might only be of interest on Windows if building for the Win32 version + * of ncurses. + * As a downside, compared to GSpawn, this cannot inherit the environment + * variables from the global Q-Register table. + */ + +static void +teco_interface_init_clipboard(void) +{ + if (!teco_interface_osc52_is_enabled() && + (!teco_qreg_table_find(&teco_qreg_table_globals, "$SCITECO_CLIPBOARD_SET", 22) || + !teco_qreg_table_find(&teco_qreg_table_globals, "$SCITECO_CLIPBOARD_GET", 22))) + return; + + teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("")); + teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("P")); + teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("S")); + teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("C")); +} + +gboolean +teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len, + GError **error) +{ + if (teco_interface_osc52_is_enabled()) + return teco_interface_osc52_set_clipboard(name, str, str_len, error); + + static const gchar *reg_name = "$SCITECO_CLIPBOARD_SET"; + + teco_qreg_t *reg = teco_qreg_table_find(&teco_qreg_table_globals, reg_name, strlen(reg_name)); + if (!reg) { + /* Q-Register could have been removed in the meantime */ + g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, + "Cannot set clipboard. %s is undefined.", reg_name); + return FALSE; + } + + 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')) { + teco_error_qregcontainsnull_set(error, reg_name, strlen(reg_name), FALSE); + return FALSE; + } + + gchar *sel = g_strstr_len(command.data, command.len, "{}"); + if (sel) { + *sel++ = ' '; + *sel = get_selection_by_name(name); + } + + FILE *pipe = popen(command.data, "w"); + if (!pipe) { + g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, + "Cannot spawn process from %s", reg_name); + return FALSE; + } + + size_t len = fwrite(str, 1, str_len, pipe); + + int status = pclose(pipe); + if (status < 0 || !WIFEXITED(status)) { + g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, + "Error reaping process from %s", reg_name); + return FALSE; + } + if (WEXITSTATUS(status) != 0) { + g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, + "Process from %s returned with exit code %d", + reg_name, WEXITSTATUS(status)); + return FALSE; + } + + if (len < str_len) { + g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, + "Error writing to process from %s", reg_name); + return FALSE; + } + + return TRUE; +} + +gboolean +teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError **error) +{ + if (teco_interface_osc52_is_enabled()) + return teco_interface_osc52_get_clipboard(name, str, len, error); + + static const gchar *reg_name = "$SCITECO_CLIPBOARD_GET"; + + teco_qreg_t *reg = teco_qreg_table_find(&teco_qreg_table_globals, reg_name, strlen(reg_name)); + if (!reg) { + /* Q-Register could have been removed in the meantime */ + g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, + "Cannot get clipboard. %s is undefined.", reg_name); + return FALSE; + } + + 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')) { + teco_error_qregcontainsnull_set(error, reg_name, strlen(reg_name), FALSE); + return FALSE; + } + + gchar *sel = g_strstr_len(command.data, command.len, "{}"); + if (sel) { + *sel++ = ' '; + *sel = get_selection_by_name(name); + } + + FILE *pipe = popen(command.data, "r"); + if (!pipe) { + g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, + "Cannot spawn process from %s", reg_name); + return FALSE; + } + + gchar buffer[1024]; + size_t read_len; + + g_auto(teco_string_t) ret = {NULL, 0}; + + do { + read_len = fread(buffer, 1, sizeof(buffer), pipe); + teco_string_append(&ret, buffer, read_len); + } while (read_len == sizeof(buffer)); + + int status = pclose(pipe); + if (status < 0 || !WIFEXITED(status)) { + g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, + "Error reaping process from %s", reg_name); + return FALSE; + } + /* + * You may have to add a `|| true` for instance to xclip if it + * could fail for empty selections. + */ + if (WEXITSTATUS(status) != 0) { + g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, + "Process from %s returned with exit code %d", + reg_name, WEXITSTATUS(status)); + return FALSE; + } + + *str = ret.data; + *len = ret.len; + memset(&ret, 0, sizeof(ret)); + + return TRUE; +} + +#else /* !PDCURSES && !G_OS_UNIX && !CURSES_TTY */ static void teco_interface_init_clipboard(void) @@ -1470,7 +1657,7 @@ teco_interface_popup_add(teco_popup_entry_type_t type, const gchar *name, gsize } void -teco_interface_popup_show(void) +teco_interface_popup_show(gsize prefix_len) { if (!teco_interface.cmdline_window) /* batch mode */ @@ -1479,9 +1666,21 @@ teco_interface_popup_show(void) short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0)); short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0)); + teco_interface.popup_prefix_len = prefix_len; teco_curses_info_popup_show(&teco_interface.popup, SCI_COLOR_ATTR(fg, bg)); } +void +teco_interface_popup_scroll(void) +{ + if (!teco_interface.cmdline_window) + /* batch mode */ + return; + + teco_curses_info_popup_scroll_page(&teco_interface.popup); + teco_interface_popup_show(teco_interface.popup_prefix_len); +} + gboolean teco_interface_popup_is_shown(void) { @@ -1496,8 +1695,7 @@ teco_interface_popup_clear(void) * 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(). + * The Scintilla window is always touched by scintilla_noutrefresh(). * Actually we would expect this to be necessary on any curses, * but ncurses doesn't require this. */ @@ -1563,13 +1761,6 @@ static void teco_interface_refresh(void) { /* - * Scintilla has been patched to avoid any automatic scrolling since that - * has been benchmarked to be a very costly operation. - * Instead we do it only once after every keypress. - */ - teco_interface_ssm(SCI_SCROLLCARET, 0, 0); - - /* * 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 @@ -1584,9 +1775,131 @@ teco_interface_refresh(void) doupdate(); } +#if NCURSES_MOUSE_VERSION >= 2 + +#define BUTTON_NUM(X) \ + (BUTTON##X##_PRESSED | BUTTON##X##_RELEASED | \ + BUTTON##X##_CLICKED | BUTTON##X##_DOUBLE_CLICKED | BUTTON##X##_TRIPLE_CLICKED) +#define BUTTON_EVENT(X) \ + (BUTTON1_##X | BUTTON2_##X | BUTTON3_##X | BUTTON4_##X | BUTTON5_##X) + +static gboolean +teco_interface_getmouse(GError **error) +{ + MEVENT event; + + if (getmouse(&event) != OK) + return TRUE; + + if (teco_curses_info_popup_is_shown(&teco_interface.popup) && + wmouse_trafo(teco_interface.popup.window, &event.y, &event.x, FALSE)) { + /* + * NOTE: Not all curses variants report the RELEASED event, + * but may also return REPORT_MOUSE_POSITION. + * So we might react to all button presses as well. + */ + if (event.bstate & (BUTTON1_RELEASED | REPORT_MOUSE_POSITION)) { + teco_machine_t *machine = &teco_cmdline.machine.parent; + const teco_string_t *insert = teco_curses_info_popup_getentry(&teco_interface.popup, event.y, event.x); + + if (insert && machine->current->insert_completion_cb) { + /* successfully clicked popup item */ + 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)) + return FALSE; + + teco_interface_popup_clear(); + teco_interface_msg_clear(); + teco_interface_cmdline_update(&teco_cmdline); + } + + return TRUE; + } + if (event.bstate & BUTTON_NUM(4)) + teco_curses_info_popup_scroll(&teco_interface.popup, -1); + else if (event.bstate & BUTTON_NUM(5)) + teco_curses_info_popup_scroll(&teco_interface.popup, +1); + + short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0)); + short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0)); + teco_curses_info_popup_show(&teco_interface.popup, SCI_COLOR_ATTR(fg, bg)); + + return TRUE; + } + + /* + * Return mouse coordinates relative to the view. + * They will be in characters, but that's what SCI_POSITIONFROMPOINT + * expects on Scinterm anyway. + */ + WINDOW *current = teco_view_get_window(teco_interface_current_view); + if (!wmouse_trafo(current, &event.y, &event.x, FALSE)) + /* no event inside of current view */ + return TRUE; + + /* + * NOTE: There will only be one of the button bits + * set in bstate, so we don't loose information translating + * them to enums. + * + * At least on ncurses, we don't always get a RELEASED event. + * It instead sends only REPORT_MOUSE_POSITION, + * so make sure not to overwrite teco_mouse.button in this case. + */ + if (event.bstate & BUTTON_NUM(4)) + /* scroll up - there will be no RELEASED event */ + teco_mouse.type = TECO_MOUSE_SCROLLUP; + else if (event.bstate & BUTTON_NUM(5)) + /* scroll down - there will be no RELEASED event */ + teco_mouse.type = TECO_MOUSE_SCROLLDOWN; + else if (event.bstate & BUTTON_EVENT(RELEASED)) + teco_mouse.type = TECO_MOUSE_RELEASED; + else if (event.bstate & BUTTON_EVENT(PRESSED)) + teco_mouse.type = TECO_MOUSE_PRESSED; + else + /* can also be REPORT_MOUSE_POSITION */ + teco_mouse.type = TECO_MOUSE_RELEASED; + + teco_mouse.x = event.x; + teco_mouse.y = event.y; + + if (event.bstate & BUTTON_NUM(1)) + teco_mouse.button = 1; + else if (event.bstate & BUTTON_NUM(2)) + teco_mouse.button = 2; + else if (event.bstate & BUTTON_NUM(3)) + teco_mouse.button = 3; + else if (!(event.bstate & REPORT_MOUSE_POSITION)) + teco_mouse.button = -1; + + teco_mouse.mods = 0; + if (event.bstate & BUTTON_SHIFT) + teco_mouse.mods |= TECO_MOUSE_SHIFT; + if (event.bstate & BUTTON_CTRL) + teco_mouse.mods |= TECO_MOUSE_CTRL; + if (event.bstate & BUTTON_ALT) + teco_mouse.mods |= TECO_MOUSE_ALT; + + return teco_cmdline_keymacro("MOUSE", -1, error); +} + +#endif /* NCURSES_MOUSE_VERSION >= 2 */ + static gint teco_interface_blocking_getch(void) { +#if NCURSES_MOUSE_VERSION >= 2 + /* + * FIXME: REPORT_MOUSE_POSITION is necessary at least on + * ncurses, so that BUTTONX_RELEASED events are reported. + * It does NOT report every cursor movement, though. + * What does PDCurses do? + */ + mousemask(teco_ed & TECO_ED_MOUSEKEY + ? ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION : 0, NULL); +#endif + /* no special <CTRL/C> handling */ raw(); nodelay(teco_interface.input_pad, FALSE); @@ -1630,6 +1943,9 @@ teco_interface_event_loop_iter(void) ? teco_interface_blocking_getch() : GPOINTER_TO_INT(g_queue_pop_head(teco_interface.input_queue)); + const teco_view_t *last_view = teco_interface_current_view; + sptr_t last_pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + switch (key) { case ERR: /* shouldn't really happen */ @@ -1697,6 +2013,14 @@ teco_interface_event_loop_iter(void) #undef FNS #undef FN +#if NCURSES_MOUSE_VERSION >= 2 + case KEY_MOUSE: + /* ANY of the mouse events */ + if (!teco_interface_getmouse(error)) + return; + break; +#endif + /* * Control keys and keys with printable representation */ @@ -1740,6 +2064,14 @@ teco_interface_event_loop_iter(void) } } + /* + * Scintilla has been patched to avoid any automatic scrolling since that + * has been benchmarked to be a very costly operation. + * Instead we do it only once after every keypress. + */ + if (teco_interface_current_view != last_view || + last_pos != teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0)) + teco_interface_ssm(SCI_SCROLLCARET, 0, 0); teco_interface_refresh(); } @@ -1755,6 +2087,7 @@ teco_interface_event_loop(GError **error) static const teco_cmdline_t empty_cmdline; // FIXME teco_interface_cmdline_update(&empty_cmdline); teco_interface_msg_clear(); + teco_interface_ssm(SCI_SCROLLCARET, 0, 0); teco_interface_refresh(); #ifdef EMCURSES |