diff options
Diffstat (limited to 'src/interface-curses')
-rw-r--r-- | src/interface-curses/Makefile.am | 11 | ||||
-rw-r--r-- | src/interface-curses/curses-info-popup.c | 211 | ||||
-rw-r--r-- | src/interface-curses/curses-info-popup.cpp | 219 | ||||
-rw-r--r-- | src/interface-curses/curses-info-popup.h | 96 | ||||
-rw-r--r-- | src/interface-curses/curses-utils.c (renamed from src/interface-curses/curses-utils.cpp) | 38 | ||||
-rw-r--r-- | src/interface-curses/curses-utils.h | 22 | ||||
-rw-r--r-- | src/interface-curses/interface-curses.h | 199 | ||||
-rw-r--r-- | src/interface-curses/interface.c (renamed from src/interface-curses/interface-curses.cpp) | 1044 |
8 files changed, 848 insertions, 992 deletions
diff --git a/src/interface-curses/Makefile.am b/src/interface-curses/Makefile.am index 01e68ae..14fc920 100644 --- a/src/interface-curses/Makefile.am +++ b/src/interface-curses/Makefile.am @@ -1,8 +1,9 @@ -AM_CPPFLAGS += -I$(top_srcdir)/src +AM_CPPFLAGS += -I$(top_srcdir)/contrib/rb3ptr \ + -I$(top_srcdir)/src -AM_CXXFLAGS = -Wall -Wno-char-subscripts +AM_CFLAGS = -std=gnu11 -Wall -Wno-initializer-overrides -Wno-unused-value noinst_LTLIBRARIES = libsciteco-interface.la -libsciteco_interface_la_SOURCES = interface-curses.cpp interface-curses.h \ - curses-utils.cpp curses-utils.h \ - curses-info-popup.cpp curses-info-popup.h +libsciteco_interface_la_SOURCES = interface.c \ + curses-utils.c curses-utils.h \ + curses-info-popup.c curses-info-popup.h diff --git a/src/interface-curses/curses-info-popup.c b/src/interface-curses/curses-info-popup.c new file mode 100644 index 0000000..7d661a2 --- /dev/null +++ b/src/interface-curses/curses-info-popup.c @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2012-2021 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 <glib.h> + +#include <curses.h> + +#include "list.h" +#include "string-utils.h" +#include "interface.h" +#include "curses-utils.h" +#include "curses-info-popup.h" + +/* + * FIXME: This is redundant with teco-gtk-info-popup.gob. + */ +typedef struct { + teco_stailq_entry_t entry; + + teco_popup_entry_type_t type; + teco_string_t name; + gboolean highlight; +} teco_popup_entry_t; + +void +teco_curses_info_popup_add(teco_curses_info_popup_t *ctx, teco_popup_entry_type_t type, + const gchar *name, gsize name_len, gboolean highlight) +{ + if (G_UNLIKELY(!ctx->chunk)) + ctx->chunk = g_string_chunk_new(32); + + /* + * FIXME: Test with g_slice_new()... + * It could however cause problems upon command-line termination + * and may not be measurably faster. + */ + teco_popup_entry_t *entry = g_new(teco_popup_entry_t, 1); + entry->type = type; + /* + * Popup entries aren't removed individually, so we can + * more efficiently store them via GStringChunk. + */ + teco_string_init_chunk(&entry->name, name, name_len, ctx->chunk); + entry->highlight = highlight; + + teco_stailq_insert_tail(&ctx->list, &entry->entry); + + ctx->longest = MAX(ctx->longest, (gint)name_len); + ctx->length++; +} + +static void +teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, 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 */ + + /* reserve 2 spaces between columns */ + pad_colwidth = MIN(ctx->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 = (ctx->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. + */ + ctx->pad = newpad(pad_lines, cols - 2); + + wbkgd(ctx->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) + */ + 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; + + wmove(ctx->pad, cur_line-1, + (cur_col % pad_cols)*pad_colwidth); + + wattrset(ctx->pad, entry->highlight ? A_BOLD : A_NORMAL); + + switch (entry->type) { + case TECO_POPUP_FILE: + case TECO_POPUP_DIRECTORY: + g_assert(!teco_string_contains(&entry->name, '\0')); + teco_curses_format_filename(ctx->pad, entry->name.data, -1); + break; + default: + teco_curses_format_str(ctx->pad, entry->name.data, entry->name.len, -1); + break; + } + + cur_col++; + } +} + +void +teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr) +{ + if (!ctx->length) + /* nothing to display */ + return; + + int lines, cols; /* screen dimensions */ + getmaxyx(stdscr, lines, cols); + + if (ctx->window) + delwin(ctx->window); + + if (!ctx->pad) + teco_curses_info_popup_init_pad(ctx, attr); + gint pad_lines = getmaxy(ctx->pad); + + /* + * Popup window can cover all but one screen row. + * Another row is reserved for the top border. + */ + gint popup_lines = MIN(pad_lines + 1, lines - 1); + + /* window covers message, scintilla and info windows */ + ctx->window = newwin(popup_lines, 0, lines - 1 - popup_lines, 0); + + wbkgdset(ctx->window, ' ' | attr); + + wborder(ctx->window, + ACS_VLINE, + ACS_VLINE, /* may be overwritten with scrollbar */ + ACS_HLINE, + ' ', /* no bottom line */ + ACS_ULCORNER, ACS_URCORNER, + ACS_VLINE, ACS_VLINE); + + copywin(ctx->pad, ctx->window, + ctx->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)) */ + gint 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 */ + gint bar_y = ctx->pad_first_line*(popup_lines-2) / pad_lines + 1; + + mvwvline(ctx->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(ctx->window, bar_y, cols-1); + wattron(ctx->window, A_REVERSE); + wvline(ctx->window, ' ', bar_height); + + /* progress scroll position */ + ctx->pad_first_line += popup_lines - 1; + /* wrap on last shown page */ + ctx->pad_first_line %= pad_lines; + 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) + delwin(ctx->window); + if (ctx->pad) + delwin(ctx->pad); + if (ctx->chunk) + g_string_chunk_free(ctx->chunk); + + teco_stailq_entry_t *entry; + while ((entry = teco_stailq_remove_head(&ctx->list))) + g_free(entry); + + teco_curses_info_popup_init(ctx); +} diff --git a/src/interface-curses/curses-info-popup.cpp b/src/interface-curses/curses-info-popup.cpp deleted file mode 100644 index 487f1b7..0000000 --- a/src/interface-curses/curses-info-popup.cpp +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (C) 2012-2017 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 <glib.h> - -#include <curses.h> - -#include "curses-utils.h" -#include "curses-info-popup.h" - -namespace SciTECO { - -void -CursesInfoPopup::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 -CursesInfoPopup::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: - Curses::format_filename(pad, entry->name); - break; - default: - Curses::format_str(pad, entry->name); - break; - } - - cur_col++; - } -} - -void -CursesInfoPopup::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 -CursesInfoPopup::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; - } -} - -CursesInfoPopup::~CursesInfoPopup() -{ - if (window) - delwin(window); - if (pad) - delwin(pad); - if (list) - g_slist_free_full(list, g_free); -} - -} /* namespace SciTECO */ diff --git a/src/interface-curses/curses-info-popup.h b/src/interface-curses/curses-info-popup.h index af09cb4..d911182 100644 --- a/src/interface-curses/curses-info-popup.h +++ b/src/interface-curses/curses-info-popup.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2017 Robin Haberkorn + * Copyright (C) 2012-2021 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 @@ -14,78 +14,52 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#pragma once -#ifndef __CURSES_INFO_POPUP_H -#define __CURSES_INFO_POPUP_H +#include <string.h> #include <glib.h> #include <curses.h> -#include "memory.h" +#include "list.h" +#include "interface.h" -namespace SciTECO { +typedef struct { + WINDOW *window; /**! window showing part of pad */ + WINDOW *pad; /**! full-height entry list */ -class CursesInfoPopup : public Object { -public: - /** - * @bug This is identical to the type defined in - * interface.h. But for the sake of abstraction - * we cannot access it here (or in gtk-info-popup - * for that matter). - */ - enum PopupEntryType { - POPUP_PLAIN, - POPUP_FILE, - POPUP_DIRECTORY - }; + teco_stailq_head_t list; /**! list of popup entries */ + gint longest; /**! size of longest entry */ + gint length; /**! total number of popup entries */ -private: - WINDOW *window; /**! window showing part of pad */ - WINDOW *pad; /**! full-height entry list */ + gint pad_first_line; /**! first line in pad to show */ - struct Entry { - PopupEntryType type; - bool highlight; - gchar name[]; - }; + GStringChunk *chunk; /**! string chunk for all popup entry names */ +} teco_curses_info_popup_t; - GSList *list; /**! list of popup entries */ - gint longest; /**! size of longest entry */ - gint length; /**! total number of popup entries */ +static inline void +teco_curses_info_popup_init(teco_curses_info_popup_t *ctx) +{ + memset(ctx, 0, sizeof(*ctx)); + ctx->list = TECO_STAILQ_HEAD_INITIALIZER(&ctx->list); +} - gint pad_first_line; /**! first line in pad to show */ +void teco_curses_info_popup_add(teco_curses_info_popup_t *ctx, teco_popup_entry_type_t type, + const gchar *name, gsize name_len, gboolean highlight); -public: - CursesInfoPopup() : window(NULL), pad(NULL), - list(NULL), longest(0), length(0), - pad_first_line(0) {} +void teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr); +static inline bool +teco_curses_info_popup_is_shown(teco_curses_info_popup_t *ctx) +{ + return ctx->window != NULL; +} - void add(PopupEntryType type, - const gchar *name, bool highlight = false); +static inline void +teco_curses_info_popup_noutrefresh(teco_curses_info_popup_t *ctx) +{ + if (ctx->window) + wnoutrefresh(ctx->window); +} - void show(attr_t attr); - inline bool - is_shown(void) - { - return window != NULL; - } - - void clear(void); - - inline void - noutrefresh(void) - { - if (window) - wnoutrefresh(window); - } - - ~CursesInfoPopup(); - -private: - void init_pad(attr_t attr); -}; - -} /* namespace SciTECO */ - -#endif +void teco_curses_info_popup_clear(teco_curses_info_popup_t *ctx); diff --git a/src/interface-curses/curses-utils.cpp b/src/interface-curses/curses-utils.c index f5d5c8c..ace5795 100644 --- a/src/interface-curses/curses-utils.cpp +++ b/src/interface-curses/curses-utils.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2017 Robin Haberkorn + * Copyright (C) 2012-2021 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 @@ -29,19 +29,14 @@ #include "string-utils.h" #include "curses-utils.h" -namespace SciTECO { - gsize -Curses::format_str(WINDOW *win, const gchar *str, - gssize len, gint max_width) +teco_curses_format_str(WINDOW *win, const gchar *str, gsize len, gint max_width) { 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; @@ -51,7 +46,7 @@ Curses::format_str(WINDOW *win, const gchar *str, * View::set_representations() */ switch (*str) { - case CTL_KEY_ESC: + case '\e': chars_added++; if (chars_added > max_width) goto truncate; @@ -80,12 +75,12 @@ Curses::format_str(WINDOW *win, const gchar *str, waddch(win, 'B' | A_REVERSE); break; default: - if (IS_CTL(*str)) { + if (TECO_IS_CTL(*str)) { chars_added += 2; if (chars_added > max_width) goto truncate; waddch(win, '^' | A_REVERSE); - waddch(win, CTL_ECHO(*str) | A_REVERSE); + waddch(win, TECO_CTL_ECHO(*str) | A_REVERSE); } else { chars_added++; if (chars_added > max_width) @@ -114,29 +109,29 @@ truncate: } gsize -Curses::format_filename(WINDOW *win, const gchar *filename, - gint max_width) +teco_curses_format_filename(WINDOW *win, const gchar *filename, + gint max_width) { int old_x = getcurx(win); - gchar *filename_canon = String::canonicalize_ctl(filename); - size_t filename_len = strlen(filename_canon); + g_autofree gchar *filename_printable = teco_string_echo(filename, strlen(filename)); + size_t filename_len = strlen(filename_printable); if (max_width < 0) max_width = getmaxx(win) - old_x; if (filename_len <= (size_t)max_width) { - waddstr(win, filename_canon); + waddstr(win, filename_printable); } else { - const gchar *keep_post = filename_canon + filename_len - + const gchar *keep_post = filename_printable + filename_len - max_width + 3; #ifdef G_OS_WIN32 - const gchar *keep_pre = g_path_skip_root(filename_canon); + const gchar *keep_pre = g_path_skip_root(filename_printable); if (keep_pre) { - waddnstr(win, filename_canon, - keep_pre - filename_canon); - keep_post += keep_pre - filename_canon; + waddnstr(win, filename_printable, + keep_pre - filename_printable); + keep_post += keep_pre - filename_printable; } #endif wattron(win, A_UNDERLINE | A_BOLD); @@ -145,8 +140,5 @@ Curses::format_filename(WINDOW *win, const gchar *filename, waddstr(win, keep_post); } - g_free(filename_canon); return getcurx(win) - old_x; } - -} /* namespace SciTECO */ diff --git a/src/interface-curses/curses-utils.h b/src/interface-curses/curses-utils.h index 778f39e..3a681a4 100644 --- a/src/interface-curses/curses-utils.h +++ b/src/interface-curses/curses-utils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2017 Robin Haberkorn + * Copyright (C) 2012-2021 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 @@ -14,26 +14,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef __CURSES_UTILS_H -#define __CURSES_UTILS_H +#pragma once #include <glib.h> #include <curses.h> -namespace SciTECO { - -namespace Curses { - -gsize format_str(WINDOW *win, const gchar *str, - gssize len = -1, gint max_width = -1); - -gsize format_filename(WINDOW *win, const gchar *filename, - gint max_width = -1); - -} /* namespace Curses */ - -} /* namespace SciTECO */ +gsize teco_curses_format_str(WINDOW *win, const gchar *str, gsize len, gint max_width); -#endif +gsize teco_curses_format_filename(WINDOW *win, const gchar *filename, gint max_width); diff --git a/src/interface-curses/interface-curses.h b/src/interface-curses/interface-curses.h deleted file mode 100644 index 32fff1d..0000000 --- a/src/interface-curses/interface-curses.h +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (C) 2012-2017 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/>. - */ - -#ifndef __INTERFACE_CURSES_H -#define __INTERFACE_CURSES_H - -#include <stdarg.h> - -#include <glib.h> - -#include <curses.h> - -#include <Scintilla.h> -#include <ScintillaTerm.h> - -#include "interface.h" -#include "curses-info-popup.h" - -namespace SciTECO { - -typedef class ViewCurses : public View<ViewCurses> { - Scintilla *sci; - -public: - ViewCurses() : sci(NULL) {} - - /* implementation of View::initialize() */ - void initialize_impl(void); - - inline ~ViewCurses() - { - /* - * NOTE: This deletes/frees the view's - * curses WINDOW, despite of what old versions - * of the Scinterm documentation claim. - */ - if (sci) - scintilla_delete(sci); - } - - inline void - noutrefresh(void) - { - scintilla_noutrefresh(sci); - } - - inline void - refresh(void) - { - scintilla_refresh(sci); - } - - inline WINDOW * - get_window(void) - { - return scintilla_get_window(sci); - } - - /* implementation of View::ssm() */ - inline sptr_t - ssm_impl(unsigned int iMessage, uptr_t wParam = 0, sptr_t lParam = 0) - { - return scintilla_send_message(sci, iMessage, wParam, lParam); - } -} ViewCurrent; - -typedef class InterfaceCurses : public Interface<InterfaceCurses, ViewCurses> { - /** - * Mapping of the first 16 curses color codes (that may or may not - * correspond with the standard terminal color codes) to - * Scintilla-compatible RGB values (red is LSB) to initialize after - * Curses startup. - * Negative values mean no color redefinition (keep the original - * palette entry). - */ - gint32 color_table[16]; - - /** - * Mapping of the first 16 curses color codes to their - * original values for restoring them on shutdown. - * Unfortunately, this may not be supported on all - * curses ports, so this array may be unused. - */ - struct { - short r, g, b; - } orig_color_table[16]; - - int stdout_orig, stderr_orig; - SCREEN *screen; - FILE *screen_tty; - - WINDOW *info_window; - enum { - INFO_TYPE_BUFFER = 0, - INFO_TYPE_QREGISTER - } info_type; - gchar *info_current; - - WINDOW *msg_window; - - WINDOW *cmdline_window, *cmdline_pad; - gsize cmdline_len, cmdline_rubout_len; - - CursesInfoPopup popup; - -public: - InterfaceCurses(); - ~InterfaceCurses(); - - /* override of Interface::init() */ - void init(void); - - /* override of Interface::init_color() */ - void init_color(guint color, guint32 rgb); - - /* implementation of Interface::vmsg() */ - void vmsg_impl(MessageType type, const gchar *fmt, va_list ap); - /* override of Interface::msg_clear() */ - void msg_clear(void); - - /* implementation of Interface::show_view() */ - void show_view_impl(ViewCurses *view); - - /* implementation of Interface::info_update() */ - void info_update_impl(const QRegister *reg); - void info_update_impl(const Buffer *buffer); - - /* implementation of Interface::cmdline_update() */ - void cmdline_update_impl(const Cmdline *cmdline); - - /* override of Interface::set_clipboard() */ - void set_clipboard(const gchar *name, - const gchar *str = NULL, gssize str_len = -1); - /* override of Interface::get_clipboard() */ - gchar *get_clipboard(const gchar *name, gsize *str_len = NULL); - - /* implementation of Interface::popup_add() */ - inline void - popup_add_impl(PopupEntryType type, - const gchar *name, bool highlight = false) - { - /* FIXME: The enum casting is dangerous */ - if (cmdline_window) - /* interactive mode */ - popup.add((CursesInfoPopup::PopupEntryType)type, - name, highlight); - } - - /* implementation of Interface::popup_show() */ - void popup_show_impl(void); - /* implementation of Interface::popup_is_shown() */ - inline bool - popup_is_shown_impl(void) - { - return popup.is_shown(); - } - - /* implementation of Interface::popup_clear() */ - void popup_clear_impl(void); - - /* main entry point (implementation) */ - void event_loop_impl(void); - -private: - void init_color_safe(guint color, guint32 rgb); - void restore_colors(void); - - void init_screen(void); - void init_interactive(void); - void restore_batch(void); - - void init_clipboard(void); - - void resize_all_windows(void); - - void set_window_title(const gchar *title); - void draw_info(void); - void draw_cmdline(void); - - friend void event_loop_iter(); -} InterfaceCurrent; - -} /* namespace SciTECO */ - -#endif diff --git a/src/interface-curses/interface-curses.cpp b/src/interface-curses/interface.c index a06fe30..821581b 100644 --- a/src/interface-curses/interface-curses.cpp +++ b/src/interface-curses/interface.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2017 Robin Haberkorn + * Copyright (C) 2012-2021 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 @@ -27,6 +27,15 @@ #include <locale.h> #include <errno.h> +#ifdef HAVE_WINDOWS_H +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#endif + +#ifdef EMSCRIPTEN +#include <emscripten.h> +#endif + #include <glib.h> #include <glib/gprintf.h> #include <glib/gstdio.h> @@ -45,26 +54,17 @@ #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 "qreg.h" #include "ring.h" #include "error.h" -#include "interface.h" -#include "interface-curses.h" #include "curses-utils.h" #include "curses-info-popup.h" - -#ifdef HAVE_WINDOWS_H -/* here it shouldn't cause conflicts with other headers */ -#define WIN32_LEAN_AND_MEAN -#include <windows.h> -#endif +#include "view.h" +#include "memory.h" +#include "interface.h" /** * Whether we have PDCurses-only routines: @@ -99,18 +99,11 @@ #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. - */ +#ifdef G_OS_UNIX +/** Whether we're on ncurses/UNIX. */ #define NCURSES_UNIX #elif defined(G_OS_WIN32) -/** - * Whether we're on ncurses/win32 console - */ +/** Whether we're on ncurses/win32 console */ #define NCURSES_WIN32 #endif #endif @@ -123,10 +116,6 @@ #define CURSES_TTY #endif -namespace SciTECO { - -extern "C" { - /* * PDCurses/win32a by default assigns functions to certain * keys like CTRL+V, CTRL++, CTRL+- and CTRL+=. @@ -147,9 +136,6 @@ int PDC_set_function_key(const unsigned function, const int new_key); #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) /** @@ -161,11 +147,11 @@ static void scintilla_notify(Scintilla *sci, int idFrom, * separate thread. */ static BOOL WINAPI -console_ctrl_handler(DWORD type) +teco_console_ctrl_handler(DWORD type) { switch (type) { case CTRL_C_EVENT: - sigint_occurred = TRUE; + teco_sigint_occurred = TRUE; return TRUE; } @@ -174,9 +160,7 @@ console_ctrl_handler(DWORD type) #endif -} /* extern "C" */ - -static gint xterm_version(void) G_GNUC_UNUSED; +static gint teco_xterm_version(void) G_GNUC_UNUSED; #define UNNAMED_FILE "(Unnamed)" @@ -223,12 +207,12 @@ static gint xterm_version(void) G_GNUC_UNUSED; * for each component). */ static inline void -rgb2curses(guint32 rgb, short &r, short &g, short &b) +teco_rgb2curses_triple(guint32 rgb, gshort *r, gshort *g, gshort *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; + *r = ((rgb & 0x0000FF) >> 0)*1000/0xFF; + *g = ((rgb & 0x00FF00) >> 8)*1000/0xFF; + *b = ((rgb & 0xFF0000) >> 16)*1000/0xFF; } /** @@ -240,8 +224,8 @@ rgb2curses(guint32 rgb, short &r, short &g, short &b) * It is equivalent to Scinterm's internal `term_color` * function. */ -static short -rgb2curses(guint32 rgb) +static gshort +teco_rgb2curses(guint32 rgb) { switch (rgb) { case 0x000000: return COLOR_BLACK; @@ -266,20 +250,19 @@ rgb2curses(guint32 rgb) } static gint -xterm_version(void) +teco_xterm_version(void) { static gint xterm_patch = -2; - const gchar *term = g_getenv("TERM"); - const gchar *xterm_version; - /* * The XTerm patch level (version) is cached. */ - if (xterm_patch != -2) + if (G_LIKELY(xterm_patch != -2)) return xterm_patch; xterm_patch = -1; + const gchar *term = g_getenv("TERM"); + if (!term || !g_str_has_prefix(term, "xterm")) /* no XTerm */ return -1; @@ -290,7 +273,7 @@ xterm_version(void) * XTERM_VERSION however should be sufficient to tell * whether we are running under a real XTerm. */ - xterm_version = g_getenv("XTERM_VERSION"); + const gchar *xterm_version = g_getenv("XTERM_VERSION"); if (!xterm_version) /* no XTerm */ return -1; @@ -305,32 +288,121 @@ xterm_version(void) return xterm_patch; } -void -ViewCurses::initialize_impl(void) +/* + * NOTE: The teco_view_t pointer is reused to directly + * point to the Scintilla object. + * This saves one heap object per view. + */ + +static void +teco_view_scintilla_notify(Scintilla *sci, int idFrom, void *notify, void *user_data) +{ + teco_interface_process_notify(notify); +} + +teco_view_t * +teco_view_new(void) +{ + return (teco_view_t *)scintilla_new(teco_view_scintilla_notify); +} + +static inline void +teco_view_noutrefresh(teco_view_t *ctx) +{ + scintilla_noutrefresh((Scintilla *)ctx); +} + +static inline WINDOW * +teco_view_get_window(teco_view_t *ctx) +{ + return scintilla_get_window((Scintilla *)ctx); +} + +sptr_t +teco_view_ssm(teco_view_t *ctx, unsigned int iMessage, uptr_t wParam, sptr_t lParam) { - sci = scintilla_new(scintilla_notify); - setup(); + return scintilla_send_message((Scintilla *)ctx, iMessage, wParam, lParam); } -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) +void +teco_view_free(teco_view_t *ctx) { - 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; + scintilla_delete((Scintilla *)ctx); } +static struct { + /** + * Mapping of the first 16 curses color codes (that may or may not + * correspond with the standard terminal color codes) to + * Scintilla-compatible RGB values (red is LSB) to initialize after + * Curses startup. + * Negative values mean no color redefinition (keep the original + * palette entry). + */ + gint32 color_table[16]; + + /** + * Mapping of the first 16 curses color codes to their + * original values for restoring them on shutdown. + * Unfortunately, this may not be supported on all + * curses ports, so this array may be unused. + */ + struct { + gshort r, g, b; + } orig_color_table[16]; + + int stdout_orig, stderr_orig; + SCREEN *screen; + FILE *screen_tty; + + WINDOW *info_window; + enum { + TECO_INFO_TYPE_BUFFER = 0, + TECO_INFO_TYPE_QREG + } info_type; + teco_string_t info_current; + + WINDOW *msg_window; + + WINDOW *cmdline_window, *cmdline_pad; + gsize cmdline_len, cmdline_rubout_len; + + teco_curses_info_popup_t popup; + + /** + * GError "thrown" by teco_interface_event_loop_iter(). + * Having this in a variable avoids problems with EMScripten. + */ + GError *event_loop_error; +} teco_interface; + +static void teco_interface_init_color_safe(guint color, guint32 rgb); +static void teco_interface_restore_colors(void); + +static void teco_interface_init_screen(void); +static gboolean teco_interface_init_interactive(GError **error); +static void teco_interface_restore_batch(void); + +static void teco_interface_init_clipboard(void); + +static void teco_interface_resize_all_windows(void); + +static void teco_interface_set_window_title(const gchar *title); +static void teco_interface_draw_info(void); +static void teco_interface_draw_cmdline(void); + void -InterfaceCurses::init(void) +teco_interface_init(void) { + for (guint i = 0; i < G_N_ELEMENTS(teco_interface.color_table); i++) + teco_interface.color_table[i] = -1; + for (guint i = 0; i < G_N_ELEMENTS(teco_interface.orig_color_table); i++) + teco_interface.orig_color_table[i].r = -1; + + teco_interface.stdout_orig = teco_interface.stderr_orig = -1; + + teco_curses_info_popup_init(&teco_interface.popup); + /* * We must register this handler to handle * asynchronous interruptions via CTRL+C @@ -338,40 +410,45 @@ InterfaceCurses::init(void) * have won't do. */ #if defined(PDCURSES_WIN32) || defined(NCURSES_WIN32) - SetConsoleCtrlHandler(console_ctrl_handler, TRUE); + SetConsoleCtrlHandler(teco_console_ctrl_handler, TRUE); #endif /* * Make sure we have a string for the info line - * even if info_update() is never called. + * even if teco_interface_info_update() is never called. */ - info_current = g_strdup(PACKAGE_NAME); + teco_string_init(&teco_interface.info_current, PACKAGE_NAME, strlen(PACKAGE_NAME)); /* * On all platforms except Curses/XTerm, it's * safe to initialize the clipboards now. */ #ifndef CURSES_TTY - init_clipboard(); + teco_interface_init_clipboard(); #endif } -void -InterfaceCurses::init_color_safe(guint color, guint32 rgb) +GOptionGroup * +teco_interface_get_options(void) { - short r, g, b; + return NULL; +} +static void +teco_interface_init_color_safe(guint color, guint32 rgb) +{ #ifdef PDCURSES_WIN32 - if (orig_color_table[color].r < 0) { + if (teco_interface.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); + &teco_interface.orig_color_table[color].r, + &teco_interface.orig_color_table[color].g, + &teco_interface.orig_color_table[color].b); } #endif - rgb2curses(rgb, r, g, b); - ::init_color((short)color, r, g, b); + gshort r, g, b; + teco_rgb2curses_triple(rgb, &r, &g, &b); + init_color((short)color, r, g, b); } #ifdef PDCURSES_WIN32 @@ -381,29 +458,29 @@ InterfaceCurses::init_color_safe(guint color, guint32 rgb) * the real console color palette - or at least the default * palette when the console started. */ -void -InterfaceCurses::restore_colors(void) +static void +teco_interface_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) + for (guint i = 0; i < G_N_ELEMENTS(teco_interface.orig_color_table); i++) { + if (teco_interface.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); + init_color((short)i, + teco_interface.orig_color_table[i].r, + teco_interface.orig_color_table[i].g, + teco_interface.orig_color_table[i].b); } } #elif defined(CURSES_TTY) /* - * 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. + * FIXME: On UNIX/ncurses teco_interface_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. @@ -424,23 +501,23 @@ InterfaceCurses::restore_colors(void) * already properly restored on endwin(). * Welcome in Curses hell. */ -void -InterfaceCurses::restore_colors(void) +static void +teco_interface_restore_colors(void) { - if (xterm_version() < 0) + if (teco_xterm_version() < 0) return; /* * Looks like a real XTerm */ - fputs(CTL_KEY_ESC_STR "]104\a", screen_tty); - fflush(screen_tty); + fputs("\e]104\a", teco_interface.screen_tty); + fflush(teco_interface.screen_tty); } #else /* !PDCURSES_WIN32 && !CURSES_TTY */ -void -InterfaceCurses::restore_colors(void) +static void +teco_interface_restore_colors(void) { /* * No way to restore the palette, or it's @@ -451,9 +528,9 @@ InterfaceCurses::restore_colors(void) #endif void -InterfaceCurses::init_color(guint color, guint32 rgb) +teco_interface_init_color(guint color, guint32 rgb) { - if (color >= G_N_ELEMENTS(color_table)) + if (color >= G_N_ELEMENTS(teco_interface.color_table)) return; #if defined(__PDCURSES__) && !defined(PDC_RGB) @@ -469,12 +546,12 @@ InterfaceCurses::init_color(guint color, guint32 rgb) ((color & 0x1) << 2) | ((color & 0x4) >> 2); #endif - if (cmdline_window) { + if (teco_interface.cmdline_window) { /* interactive mode */ if (!can_change_color()) return; - init_color_safe(color, rgb); + teco_interface_init_color_safe(color, rgb); } else { /* * batch mode: store colors, @@ -482,21 +559,21 @@ InterfaceCurses::init_color(guint color, guint32 rgb) * which is called by Scinterm when interactive * mode is initialized */ - color_table[color] = (gint32)rgb; + teco_interface.color_table[color] = (gint32)rgb; } } #ifdef CURSES_TTY -void -InterfaceCurses::init_screen(void) +static void +teco_interface_init_screen(void) { - screen_tty = g_fopen("/dev/tty", "r+"); + teco_interface.screen_tty = g_fopen("/dev/tty", "r+"); /* should never fail */ - g_assert(screen_tty != NULL); + g_assert(teco_interface.screen_tty != NULL); - screen = newterm(NULL, screen_tty, screen_tty); - if (!screen) { + teco_interface.screen = newterm(NULL, teco_interface.screen_tty, teco_interface.screen_tty); + if (!teco_interface.screen) { g_fprintf(stderr, "Error initializing interactive mode. " "$TERM may be incorrect.\n"); exit(EXIT_FAILURE); @@ -509,25 +586,23 @@ InterfaceCurses::init_screen(void) * 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); + teco_interface.stdout_orig = dup(1); + g_assert(teco_interface.stdout_orig >= 0); + FILE *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); + teco_interface.stderr_orig = dup(2); + g_assert(teco_interface.stderr_orig >= 0); + FILE *stderr_new = g_freopen("/dev/null", "a+", stderr); g_assert(stderr_new != NULL); } } #elif defined(XCURSES) -void -InterfaceCurses::init_screen(void) +static void +teco_interface_init_screen(void) { const char *argv[] = {PACKAGE_NAME, NULL}; @@ -551,16 +626,16 @@ InterfaceCurses::init_screen(void) #else -void -InterfaceCurses::init_screen(void) +static void +teco_interface_init_screen(void) { initscr(); } #endif -void -InterfaceCurses::init_interactive(void) +static gboolean +teco_interface_init_interactive(GError **error) { /* * Curses accesses many environment variables @@ -569,7 +644,8 @@ InterfaceCurses::init_interactive(void) * environment before initscr()/newterm(). * This is safe to do here since there are no threads. */ - QRegisters::globals.update_environ(); + if (!teco_qreg_table_set_environ(&teco_qreg_table_globals, error)) + return FALSE; /* * On UNIX terminals, the escape key is usually @@ -625,41 +701,41 @@ InterfaceCurses::init_interactive(void) /* for displaying UTF-8 characters properly */ setlocale(LC_CTYPE, ""); - init_screen(); + teco_interface_init_screen(); cbreak(); noecho(); /* Scintilla draws its own cursor */ curs_set(0); - info_window = newwin(1, 0, 0, 0); + teco_interface.info_window = newwin(1, 0, 0, 0); - msg_window = newwin(1, 0, LINES - 2, 0); + teco_interface.msg_window = newwin(1, 0, LINES - 2, 0); - cmdline_window = newwin(0, 0, LINES - 1, 0); - keypad(cmdline_window, TRUE); + teco_interface.cmdline_window = newwin(0, 0, LINES - 1, 0); + keypad(teco_interface.cmdline_window, TRUE); #ifdef EMCURSES - nodelay(cmdline_window, TRUE); + nodelay(teco_interface.cmdline_window, TRUE); #endif /* * Will also initialize Scinterm, Curses color pairs * and resizes the current view. */ - if (current_view) - show_view(current_view); + if (teco_interface_current_view) + teco_interface_show_view(teco_interface_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++) { + for (guint i = 0; i < G_N_ELEMENTS(teco_interface.color_table); i++) { /* * init_color() may still fail if COLORS < 16 */ - if (color_table[i] >= 0) - init_color_safe(i, (guint32)color_table[i]); + if (teco_interface.color_table[i] >= 0) + teco_interface_init_color_safe(i, (guint32)teco_interface.color_table[i]); } } @@ -670,12 +746,14 @@ InterfaceCurses::init_interactive(void) * with stdout. */ #ifdef CURSES_TTY - init_clipboard(); + teco_interface_init_clipboard(); #endif + + return TRUE; } -void -InterfaceCurses::restore_batch(void) +static void +teco_interface_restore_batch(void) { /* * Set window title to a reasonable default, @@ -685,7 +763,7 @@ InterfaceCurses::restore_batch(void) * is necessary. */ #if defined(CURSES_TTY) && defined(HAVE_TIGETSTR) - set_window_title(g_getenv("TERM") ? : ""); + teco_interface_set_window_title(g_getenv("TERM") ? : ""); #endif /* @@ -693,61 +771,59 @@ InterfaceCurses::restore_batch(void) * (i.e. return to batch mode) */ endwin(); - restore_colors(); + teco_interface_restore_colors(); /* * Restore stdout and stderr, so output goes to * the terminal again in case we "muted" them. */ #ifdef CURSES_TTY - if (stdout_orig >= 0) { - int fd = dup2(stdout_orig, 1); + if (teco_interface.stdout_orig >= 0) { + int fd = dup2(teco_interface.stdout_orig, 1); g_assert(fd == 1); } - if (stderr_orig >= 0) { - int fd = dup2(stderr_orig, 2); + if (teco_interface.stderr_orig >= 0) { + int fd = dup2(teco_interface.stderr_orig, 2); g_assert(fd == 2); } #endif /* - * See vmsg_impl(): It looks at msg_win to determine + * See teco_interface_vmsg(): It looks at msg_window to determine * whether we're in batch mode. */ - if (msg_window) { - delwin(msg_window); - msg_window = NULL; + if (teco_interface.msg_window) { + delwin(teco_interface.msg_window); + teco_interface.msg_window = NULL; } } -void -InterfaceCurses::resize_all_windows(void) +static void +teco_interface_resize_all_windows(void) { int lines, cols; /* screen dimensions */ getmaxyx(stdscr, lines, cols); - wresize(info_window, 1, cols); - wresize(current_view->get_window(), + wresize(teco_interface.info_window, 1, cols); + wresize(teco_view_get_window(teco_interface_current_view), 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(); + wresize(teco_interface.msg_window, 1, cols); + mvwin(teco_interface.msg_window, lines - 2, 0); + wresize(teco_interface.cmdline_window, 1, cols); + mvwin(teco_interface.cmdline_window, lines - 1, 0); + + teco_interface_draw_info(); + teco_interface_msg_clear(); /* FIXME: use saved message */ + teco_interface_popup_clear(); + teco_interface_draw_cmdline(); } void -InterfaceCurses::vmsg_impl(MessageType type, const gchar *fmt, va_list ap) +teco_interface_vmsg(teco_msg_t type, const gchar *fmt, va_list ap) { - short fg, bg; - - if (!msg_window) { /* batch mode */ - stdio_vmsg(type, fmt, ap); + if (!teco_interface.msg_window) { /* batch mode */ + teco_interface_stdio_vmsg(type, fmt, ap); return; } @@ -759,67 +835,65 @@ InterfaceCurses::vmsg_impl(MessageType type, const gchar *fmt, va_list ap) defined(CURSES_TTY) || defined(NCURSES_WIN32) va_list aq; va_copy(aq, ap); - stdio_vmsg(type, fmt, aq); + teco_interface_stdio_vmsg(type, fmt, aq); va_end(aq); #endif - fg = rgb2curses(ssm(SCI_STYLEGETBACK, STYLE_DEFAULT)); + short fg, bg; + + fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); switch (type) { default: - case MSG_USER: - bg = rgb2curses(ssm(SCI_STYLEGETFORE, STYLE_DEFAULT)); + case TECO_MSG_USER: + bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0)); break; - case MSG_INFO: + case TECO_MSG_INFO: bg = COLOR_GREEN; break; - case MSG_WARNING: + case TECO_MSG_WARNING: bg = COLOR_YELLOW; break; - case MSG_ERROR: + case TECO_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); + wmove(teco_interface.msg_window, 0, 0); + wbkgdset(teco_interface.msg_window, ' ' | SCI_COLOR_ATTR(fg, bg)); + vw_printw(teco_interface.msg_window, fmt, ap); + wclrtoeol(teco_interface.msg_window); } void -InterfaceCurses::msg_clear(void) +teco_interface_msg_clear(void) { - short fg, bg; - - if (!msg_window) /* batch mode */ + if (!teco_interface.msg_window) /* batch mode */ return; - fg = rgb2curses(ssm(SCI_STYLEGETBACK, STYLE_DEFAULT)); - bg = rgb2curses(ssm(SCI_STYLEGETFORE, STYLE_DEFAULT)); + short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); + short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0)); - wbkgdset(msg_window, ' ' | SCI_COLOR_ATTR(fg, bg)); - werase(msg_window); + wbkgdset(teco_interface.msg_window, ' ' | SCI_COLOR_ATTR(fg, bg)); + werase(teco_interface.msg_window); } void -InterfaceCurses::show_view_impl(ViewCurses *view) +teco_interface_show_view(teco_view_t *view) { - int lines, cols; /* screen dimensions */ - WINDOW *current_view_win; - - current_view = view; + teco_interface_current_view = view; - if (!cmdline_window) /* batch mode */ + if (!teco_interface.cmdline_window) /* batch mode */ return; - current_view_win = current_view->get_window(); + WINDOW *current_view_win = teco_view_get_window(teco_interface_current_view); /* * screen size might have changed since * this view's WINDOW was last active */ + int lines, cols; /* screen dimensions */ getmaxyx(stdscr, lines, cols); wresize(current_view_win, lines - 3, cols); /* Set up window position: never changes */ @@ -828,8 +902,8 @@ InterfaceCurses::show_view_impl(ViewCurses *view) #if PDCURSES -void -InterfaceCurses::set_window_title(const gchar *title) +static void +teco_interface_set_window_title(const gchar *title) { static gchar *last_title = NULL; @@ -851,8 +925,8 @@ InterfaceCurses::set_window_title(const gchar *title) #elif defined(CURSES_TTY) && defined(HAVE_TIGETSTR) -void -InterfaceCurses::set_window_title(const gchar *title) +static void +teco_interface_set_window_title(const gchar *title) { if (!has_status_line || !to_status_line || !from_status_line) return; @@ -882,30 +956,26 @@ InterfaceCurses::set_window_title(const gchar *title) * 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); + fputs(to_status_line, teco_interface.screen_tty); + fputs(title, teco_interface.screen_tty); + fputs(from_status_line, teco_interface.screen_tty); + fflush(teco_interface.screen_tty); } #else -void -InterfaceCurses::set_window_title(const gchar *title) +static void +teco_interface_set_window_title(const gchar *title) { /* no way to set window title */ } #endif -void -InterfaceCurses::draw_info(void) +static void +teco_interface_draw_info(void) { - short fg, bg; - const gchar *info_type_str; - gchar *info_current_canon, *title; - - if (!info_window) /* batch mode */ + if (!teco_interface.info_window) /* batch mode */ return; /* @@ -913,69 +983,75 @@ InterfaceCurses::draw_info(void) * 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)); + short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); + short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0)); + + wmove(teco_interface.info_window, 0, 0); + wbkgdset(teco_interface.info_window, ' ' | SCI_COLOR_ATTR(fg, bg)); - wmove(info_window, 0, 0); - wbkgdset(info_window, ' ' | SCI_COLOR_ATTR(fg, bg)); + const gchar *info_type_str; - switch (info_type) { - case INFO_TYPE_QREGISTER: + switch (teco_interface.info_type) { + case TECO_INFO_TYPE_QREG: info_type_str = PACKAGE_NAME " - <QRegister> "; - waddstr(info_window, info_type_str); + waddstr(teco_interface.info_window, info_type_str); /* same formatting as in command lines */ - Curses::format_str(info_window, info_current); + teco_curses_format_str(teco_interface.info_window, + teco_interface.info_current.data, + teco_interface.info_current.len, -1); break; - case INFO_TYPE_BUFFER: + case TECO_INFO_TYPE_BUFFER: info_type_str = PACKAGE_NAME " - <Buffer> "; - waddstr(info_window, info_type_str); - Curses::format_filename(info_window, info_current); + waddstr(teco_interface.info_window, info_type_str); + g_assert(!teco_string_contains(&teco_interface.info_current, '\0')); + teco_curses_format_filename(teco_interface.info_window, + teco_interface.info_current.data, -1); break; default: g_assert_not_reached(); } - wclrtoeol(info_window); + wclrtoeol(teco_interface.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); + 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); + teco_interface_set_window_title(title); } void -InterfaceCurses::info_update_impl(const QRegister *reg) +teco_interface_info_update_qreg(const teco_qreg_t *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() */ + 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_type = TECO_INFO_TYPE_QREG; + /* NOTE: drawn in teco_interface_event_loop_iter() */ } void -InterfaceCurses::info_update_impl(const Buffer *buffer) +teco_interface_info_update_buffer(const teco_buffer_t *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() */ + 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_append_c(&teco_interface.info_current, + buffer->dirty ? '*' : ' '); + teco_interface.info_type = TECO_INFO_TYPE_BUFFER; + /* NOTE: drawn in teco_interface_event_loop_iter() */ } void -InterfaceCurses::cmdline_update_impl(const Cmdline *cmdline) +teco_interface_cmdline_update(const teco_cmdline_t *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, @@ -983,18 +1059,22 @@ InterfaceCurses::cmdline_update_impl(const Cmdline *cmdline) * 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); + if (teco_interface.cmdline_pad) + delwin(teco_interface.cmdline_pad); - fg = rgb2curses(ssm(SCI_STYLEGETFORE, STYLE_DEFAULT)); - bg = rgb2curses(ssm(SCI_STYLEGETBACK, STYLE_DEFAULT)); - wcolor_set(cmdline_pad, SCI_COLOR_PAIR(fg, bg), NULL); + int max_cols = 1; + for (guint i = 0; i < cmdline->str.len; i++) + max_cols += TECO_IS_CTL(cmdline->str.data[i]) ? 3 : 1; + teco_interface.cmdline_pad = newpad(1, max_cols); + + short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0)); + short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); + wcolor_set(teco_interface.cmdline_pad, SCI_COLOR_PAIR(fg, bg), NULL); /* format effective command line */ - cmdline_len = Curses::format_str(cmdline_pad, cmdline->str, cmdline->len); + teco_interface.cmdline_len = + teco_curses_format_str(teco_interface.cmdline_pad, + cmdline->str.data, cmdline->effective_len, -1); /* * A_BOLD should result in either a bold font or a brighter @@ -1006,61 +1086,61 @@ InterfaceCurses::cmdline_update_impl(const Cmdline *cmdline) * for rubbed out parts of the command line which will * be user-configurable. */ - wattron(cmdline_pad, A_UNDERLINE | A_BOLD); + wattron(teco_interface.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 = Curses::format_str(cmdline_pad, cmdline->str + cmdline->len, - cmdline->rubout_len); + teco_interface.cmdline_rubout_len = + teco_curses_format_str(teco_interface.cmdline_pad, cmdline->str.data + cmdline->effective_len, + cmdline->str.len - cmdline->effective_len, -1); /* highlight cursor after effective command line */ - if (cmdline_rubout_len) { - attr_t attr; - short pair; + if (teco_interface.cmdline_rubout_len) { + attr_t attr = 0; + short pair = 0; - wmove(cmdline_pad, 0, cmdline_len); - wattr_get(cmdline_pad, &attr, &pair, NULL); - wchgat(cmdline_pad, 1, + wmove(teco_interface.cmdline_pad, 0, teco_interface.cmdline_len); + wattr_get(teco_interface.cmdline_pad, &attr, &pair, NULL); + wchgat(teco_interface.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); + teco_interface.cmdline_len++; + wattroff(teco_interface.cmdline_pad, A_UNDERLINE | A_BOLD); + waddch(teco_interface.cmdline_pad, ' ' | A_REVERSE); } - draw_cmdline(); + teco_interface_draw_cmdline(); } -void -InterfaceCurses::draw_cmdline(void) +static void +teco_interface_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; + guint total_width = getmaxx(teco_interface.cmdline_window) - 1; - disp_offset = cmdline_len - - MIN(cmdline_len, - total_width/2 + cmdline_len % MAX(total_width/2, 1)); + /* beginning of command line to show */ + guint disp_offset = teco_interface.cmdline_len - + MIN(teco_interface.cmdline_len, + total_width/2 + teco_interface.cmdline_len % MAX(total_width/2, 1)); /* + * length of command line to show + * * 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); + guint disp_len = MIN(total_width, teco_interface.cmdline_len + + teco_interface.cmdline_rubout_len - disp_offset); - fg = rgb2curses(ssm(SCI_STYLEGETFORE, STYLE_DEFAULT)); - bg = rgb2curses(ssm(SCI_STYLEGETBACK, STYLE_DEFAULT)); + short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0)); + short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); - wbkgdset(cmdline_window, ' ' | SCI_COLOR_ATTR(fg, bg)); - werase(cmdline_window); - mvwaddch(cmdline_window, 0, 0, '*' | A_BOLD); - copywin(cmdline_pad, cmdline_window, + wbkgdset(teco_interface.cmdline_window, ' ' | SCI_COLOR_ATTR(fg, bg)); + werase(teco_interface.cmdline_window); + mvwaddch(teco_interface.cmdline_window, 0, 0, '*' | A_BOLD); + copywin(teco_interface.cmdline_pad, teco_interface.cmdline_window, 0, disp_offset, 0, 1, 0, disp_len, FALSE); } @@ -1073,12 +1153,11 @@ InterfaceCurses::draw_cmdline(void) * it corresponds to the X11 PRIMARY, SECONDARY or * CLIPBOARD selections. */ -void -InterfaceCurses::init_clipboard(void) +static void +teco_interface_init_clipboard(void) { char *contents; long length; - int rc; /* * Even on PDCurses, while the clipboard functions are @@ -1089,71 +1168,68 @@ InterfaceCurses::init_clipboard(void) * This could be done at compile time, but this way is more * generic (albeit inefficient). */ - rc = PDC_getclipboard(&contents, &length); + int rc = PDC_getclipboard(&contents, &length); if (rc == PDC_CLIP_ACCESS_ERROR) return; if (rc == PDC_CLIP_SUCCESS) PDC_freeclipboard(contents); - QRegisters::globals.insert(new QRegisterClipboard()); + teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("")); } -void -InterfaceCurses::set_clipboard(const gchar *name, const gchar *str, gssize str_len) +gboolean +teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len, GError **error) { - int rc; - - if (str) { - if (str_len < 0) - str_len = strlen(str); - - rc = PDC_setclipboard(str, str_len); - } else { - rc = PDC_clearclipboard(); + int rc = str ? PDC_setclipboard(str, str_len) : PDC_clearclipboard(); + if (rc != PDC_CLIP_SUCCESS) { + g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, + "Error %d copying to clipboard", rc); + return FALSE; } - if (rc != PDC_CLIP_SUCCESS) - throw Error("Error %d copying to clipboard", rc); + return TRUE; } -gchar * -InterfaceCurses::get_clipboard(const gchar *name, gsize *str_len) +gboolean +teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError **error) { char *contents; long length = 0; - int rc; - gchar *str; /* * NOTE: It is undefined whether we can pass in NULL for length. */ - rc = PDC_getclipboard(&contents, &length); - if (str_len) - *str_len = length; + int rc = PDC_getclipboard(&contents, &length); + *len = length; if (rc == PDC_CLIP_EMPTY) - return NULL; - if (rc != PDC_CLIP_SUCCESS) - throw Error("Error %d retrieving clipboard", rc); + return TRUE; + if (rc != PDC_CLIP_SUCCESS) { + g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, + "Error %d retrieving clipboard", rc); + return FALSE; + } /* * PDCurses defines its own free function and there is no * way to find out which allocator was used. * We must therefore copy the memory to be on the safe side. - * At least we can null-terminate the return string in the - * process (PDCurses does not guarantee that either). + * At least, the result is guaranteed to be null-terminated + * and thus teco_string_t-compatible + * (PDCurses does not guarantee that either). */ - str = (gchar *)g_malloc(length + 1); - memcpy(str, contents, length); - str[length] = '\0'; + if (str) { + *str = memcpy(g_malloc(length + 1), contents, length); + (*str)[length] = '\0'; + } PDC_freeclipboard(contents); - return str; + return TRUE; } #elif defined(CURSES_TTY) -void -InterfaceCurses::init_clipboard(void) +static void +teco_interface_init_clipboard(void) { /* * At least on XTerm, there are escape sequences @@ -1167,13 +1243,13 @@ InterfaceCurses::init_clipboard(void) * not register the clipboard registers if they aren't. * Therefore, a special XTerm clipboard ED flag an be set by the user. */ - if (!(Flags::ed & Flags::ED_XTERM_CLIPBOARD) || xterm_version() < 203) + if (!(teco_ed & TECO_ED_XTERM_CLIPBOARD) || teco_xterm_version() < 203) return; - QRegisters::globals.insert(new QRegisterClipboard()); - QRegisters::globals.insert(new QRegisterClipboard("P")); - QRegisters::globals.insert(new QRegisterClipboard("S")); - QRegisters::globals.insert(new QRegisterClipboard("C")); + 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")); } static inline gchar @@ -1189,27 +1265,21 @@ get_selection_by_name(const gchar *name) return g_ascii_tolower(*name) ? : 'c'; } -void -InterfaceCurses::set_clipboard(const gchar *name, const gchar *str, gssize str_len) +gboolean +teco_interface_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); + fputc(';', teco_interface.screen_tty); + /* * Enough space for 1024 Base64-encoded bytes. */ gchar buffer[(1024 / 3) * 4 + 4]; gsize out_len; - /* g_base64_encode_step() state: */ - gint state = 0; - gint save = 0; - - fputs(CTL_KEY_ESC_STR "]52;", screen_tty); - fputc(get_selection_by_name(name), screen_tty); - fputc(';', screen_tty); - - if (!str) - str_len = 0; - else if (str_len < 0) - str_len = strlen(str); + gint state = 0, save = 0; while (str_len > 0) { gsize step_len = MIN(1024, str_len); @@ -1221,41 +1291,32 @@ InterfaceCurses::set_clipboard(const gchar *name, const gchar *str, gssize str_l out_len = g_base64_encode_step((const guchar *)str, step_len, FALSE, buffer, &state, &save); - fwrite(buffer, 1, out_len, screen_tty); + fwrite(buffer, 1, out_len, teco_interface.screen_tty); str_len -= step_len; str += step_len; } out_len = g_base64_encode_close(FALSE, buffer, &state, &save); - fwrite(buffer, 1, out_len, screen_tty); + fwrite(buffer, 1, out_len, teco_interface.screen_tty); - fputc('\a', screen_tty); - fflush(screen_tty); + fputc('\a', teco_interface.screen_tty); + fflush(teco_interface.screen_tty); + + return TRUE; } -gchar * -InterfaceCurses::get_clipboard(const gchar *name, gsize *str_len) +gboolean +teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError **error) { /* - * Space for storing one group of decoded Base64 characters - * and the OSC-52 response. - */ - gchar buffer[MAX(3, 7)]; - GString *str_base64; - - /* g_base64_decode_step() state: */ - gint state = 0; - guint save = 0; - - /* * Query the clipboard -- XTerm will reply with the * OSC-52 command that would set the current selection. */ - fputs(CTL_KEY_ESC_STR "]52;", screen_tty); - fputc(get_selection_by_name(name), screen_tty); - fputs(";?\a", screen_tty); - fflush(screen_tty); + fputs("\e]52;", teco_interface.screen_tty); + fputc(get_selection_by_name(name), teco_interface.screen_tty); + fputs(";?\a", teco_interface.screen_tty); + fflush(teco_interface.screen_tty); /* * It is very well possible that the XTerm clipboard @@ -1277,22 +1338,32 @@ InterfaceCurses::get_clipboard(const gchar *name, gsize *str_len) if (getch() == ERR) { /* timeout */ cbreak(); - throw Error("Timed out reading XTerm clipboard"); + g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, + "Timed out reading XTerm clipboard"); + return FALSE; } } - str_base64 = g_string_new(""); + GString *str_base64 = g_string_new(""); + /* g_base64_decode_step() state: */ + gint state = 0; + guint save = 0; for (;;) { - gchar c; - gsize out_len; + /* + * Space for storing one group of decoded Base64 characters + * and the OSC-52 response. + */ + gchar buffer[MAX(3, 7)]; - c = (gchar)getch(); + gchar c = (gchar)getch(); if (c == ERR) { /* timeout */ cbreak(); g_string_free(str_base64, TRUE); - throw Error("Timed out reading XTerm clipboard"); + g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, + "Timed out reading XTerm clipboard"); + return FALSE; } if (c == '\a') break; @@ -1301,30 +1372,29 @@ InterfaceCurses::get_clipboard(const gchar *name, gsize *str_len) * This could be simplified using sscanf() and * g_base64_decode(), but we avoid one allocation * to get the entire Base64 string. - * (Also to allow for timeouts, we must should + * (Also to allow for timeouts, we should * read character-wise using getch() anyway.) */ - out_len = g_base64_decode_step(&c, sizeof(c), - (guchar *)buffer, - &state, &save); + gsize out_len = g_base64_decode_step(&c, sizeof(c), + (guchar *)buffer, + &state, &save); g_string_append_len(str_base64, buffer, out_len); } cbreak(); - if (str_len) - *str_len = str_base64->len; + if (str) + *str = str_base64->str; + *len = str_base64->len; - /* - * If the clipboard answer is empty, return NULL. - */ - return g_string_free(str_base64, str_base64->len == 0); + g_string_free(str_base64, !str); + return TRUE; } #else -void -InterfaceCurses::init_clipboard(void) +static void +teco_interface_init_clipboard(void) { /* * No native clipboard support, so no clipboard Q-Regs are @@ -1332,37 +1402,55 @@ InterfaceCurses::init_clipboard(void) */ } -void -InterfaceCurses::set_clipboard(const gchar *name, const gchar *str, gssize str_len) +gboolean +teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len, + GError **error) { - throw Error("Setting clipboard unsupported"); + g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, + "Setting clipboard unsupported"); + return FALSE; } -gchar * -InterfaceCurses::get_clipboard(const gchar *name, gsize *str_len) +gboolean +teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError **error) { - throw Error("Getting clipboard unsupported"); + g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, + "Getting clipboard unsupported"); + return FALSE; } #endif /* !__PDCURSES__ && !CURSES_TTY */ void -InterfaceCurses::popup_show_impl(void) +teco_interface_popup_add(teco_popup_entry_type_t type, const gchar *name, gsize name_len, + gboolean highlight) { - short fg, bg; + if (teco_interface.cmdline_window) + /* interactive mode */ + teco_curses_info_popup_add(&teco_interface.popup, type, name, name_len, highlight); +} - if (!cmdline_window) +void +teco_interface_popup_show(void) +{ + if (!teco_interface.cmdline_window) /* batch mode */ return; - fg = rgb2curses(ssm(SCI_STYLEGETFORE, STYLE_CALLTIP)); - bg = rgb2curses(ssm(SCI_STYLEGETBACK, STYLE_CALLTIP)); + short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0)); + short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0)); - popup.show(SCI_COLOR_ATTR(fg, bg)); + teco_curses_info_popup_show(&teco_interface.popup, SCI_COLOR_ATTR(fg, bg)); +} + +gboolean +teco_interface_popup_is_shown(void) +{ + return teco_curses_info_popup_is_shown(&teco_interface.popup); } void -InterfaceCurses::popup_clear_impl(void) +teco_interface_popup_clear(void) { #ifdef __PDCURSES__ /* @@ -1374,31 +1462,35 @@ InterfaceCurses::popup_clear_impl(void) * 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); + if (teco_curses_info_popup_is_shown(&teco_interface.popup)) { + touchwin(teco_interface.info_window); + touchwin(teco_interface.msg_window); } #endif - popup.clear(); + teco_curses_info_popup_clear(&teco_interface.popup); + teco_curses_info_popup_init(&teco_interface.popup); +} + +gboolean +teco_interface_is_interrupted(void) +{ + return teco_sigint_occurred != FALSE; } /** * One iteration of the event loop. * - * This is a global function, so it may - * be used as an Emscripten callback. + * This is a global function, so it may be used as an asynchronous Emscripten callback. + * While this function cannot directly throw GErrors, + * it can set teco_interface.event_loop_error. * - * @bug - * Can probably be defined as a static method, - * so we can avoid declaring it a fried function of - * InterfaceCurses. + * @fixme Thrown errors should be somehow caught when building for EMScripten as well. + * Perhaps in a goto-block. */ void -event_loop_iter() +teco_interface_event_loop_iter(void) { - int key; - /* * On PDCurses/win32, raw() and cbreak() does * not disable and enable CTRL+C handling properly. @@ -1426,7 +1518,7 @@ event_loop_iter() * escape sequences. */ #ifdef NCURSES_UNIX - keypad(interface.cmdline_window, Flags::ed & Flags::ED_FNKEYS); + keypad(teco_interface.cmdline_window, teco_ed & TECO_ED_FNKEYS); #endif /* no special <CTRL/C> handling */ @@ -1434,9 +1526,15 @@ event_loop_iter() #ifdef PDCURSES_WIN32 SetConsoleMode(console_hnd, console_mode & ~ENABLE_PROCESSED_INPUT); #endif - key = wgetch(interface.cmdline_window); + /* + * Memory limiting is stopped temporarily, since it might otherwise + * constantly place 100% load on the CPU. + */ + teco_memory_stop_limiting(); + int key = wgetch(teco_interface.cmdline_window); + teco_memory_start_limiting(); /* allow asynchronous interruptions on <CTRL/C> */ - sigint_occurred = FALSE; + teco_sigint_occurred = FALSE; noraw(); /* FIXME: necessary because of NCURSES_WIN32 bug */ cbreak(); #ifdef PDCURSES_WIN32 @@ -1451,10 +1549,10 @@ event_loop_iter() #if PDCURSES resize_term(0, 0); #endif - interface.resize_all_windows(); + teco_interface_resize_all_windows(); break; #endif - case CTL_KEY('H'): + case TECO_CTL_KEY('H'): case 0x7F: /* ^? */ case KEY_BACKSPACE: /* @@ -1465,18 +1563,25 @@ event_loop_iter() * backspace. * In SciTECO backspace is normalized to ^H. */ - cmdline.keypress(CTL_KEY('H')); + if (!teco_cmdline_keypress_c(TECO_CTL_KEY('H'), + &teco_interface.event_loop_error)) + return; break; case KEY_ENTER: case '\r': case '\n': - cmdline.keypress('\n'); + if (!teco_cmdline_keypress_c('\n', &teco_interface.event_loop_error)) + return; break; /* * Function key macros */ -#define FN(KEY) case KEY_##KEY: cmdline.fnmacro(#KEY); break +#define FN(KEY) \ + case KEY_##KEY: \ + if (!teco_cmdline_fnmacro(#KEY, &teco_interface.event_loop_error)) \ + return; \ + break #define FNS(KEY) FN(KEY); FN(S##KEY) FN(DOWN); FN(UP); FNS(LEFT); FNS(RIGHT); FNS(HOME); @@ -1485,7 +1590,9 @@ event_loop_iter() g_snprintf(macro_name, sizeof(macro_name), "F%d", key - KEY_F0); - cmdline.fnmacro(macro_name); + if (!teco_cmdline_fnmacro(macro_name, + &teco_interface.event_loop_error)) + return; break; } FNS(DC); @@ -1503,8 +1610,9 @@ event_loop_iter() * Control keys and keys with printable representation */ default: - if (key <= 0xFF) - cmdline.keypress((gchar)key); + if (key <= 0xFF && + !teco_cmdline_keypress_c(key, &teco_interface.event_loop_error)) + return; } /* @@ -1513,37 +1621,38 @@ event_loop_iter() * 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(); + teco_interface_draw_info(); + wnoutrefresh(teco_interface.info_window); + teco_view_noutrefresh(teco_interface_current_view); + wnoutrefresh(teco_interface.msg_window); + wnoutrefresh(teco_interface.cmdline_window); + teco_curses_info_popup_noutrefresh(&teco_interface.popup); doupdate(); } -void -InterfaceCurses::event_loop_impl(void) +gboolean +teco_interface_event_loop(GError **error) { - static const Cmdline empty_cmdline; + static const teco_cmdline_t empty_cmdline; // FIXME /* * Initialize Curses for interactive mode */ - init_interactive(); + if (!teco_interface_init_interactive(error)) + return FALSE; /* initial refresh */ - draw_info(); - wnoutrefresh(info_window); - current_view->noutrefresh(); - msg_clear(); - wnoutrefresh(msg_window); - cmdline_update(&empty_cmdline); - wnoutrefresh(cmdline_window); + teco_interface_draw_info(); + wnoutrefresh(teco_interface.info_window); + teco_view_noutrefresh(teco_interface_current_view); + teco_interface_msg_clear(); + wnoutrefresh(teco_interface.msg_window); + teco_interface_cmdline_update(&empty_cmdline); + wnoutrefresh(teco_interface.cmdline_window); doupdate(); #ifdef EMCURSES - PDC_emscripten_set_handler(event_loop_iter, TRUE); + PDC_emscripten_set_handler(teco_interface_event_loop_iter, TRUE); /* * We must not block emscripten's main loop, * instead event_loop_iter() is called asynchronously. @@ -1556,28 +1665,41 @@ InterfaceCurses::event_loop_impl(void) */ emscripten_exit_with_live_runtime(); #else - try { - for (;;) - event_loop_iter(); - } catch (Quit) { - /* SciTECO termination (e.g. EX$$) */ + while (!teco_interface.event_loop_error) + teco_interface_event_loop_iter(); + + /* + * The error needs to be propagated only if this is + * NOT a SciTECO termination (e.g. EX$$) + */ + if (!g_error_matches(teco_interface.event_loop_error, + TECO_ERROR, TECO_ERROR_QUIT)) { + g_propagate_error(error, g_steal_pointer(&teco_interface.event_loop_error)); + return FALSE; } + g_clear_error(&teco_interface.event_loop_error); - restore_batch(); + teco_interface_restore_batch(); #endif + + return TRUE; } -InterfaceCurses::~InterfaceCurses() +void +teco_interface_cleanup(void) { - 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); + if (teco_interface.event_loop_error) + g_error_free(teco_interface.event_loop_error); + + if (teco_interface.info_window) + delwin(teco_interface.info_window); + teco_string_clear(&teco_interface.info_current); + if (teco_interface.cmdline_window) + delwin(teco_interface.cmdline_window); + if (teco_interface.cmdline_pad) + delwin(teco_interface.cmdline_pad); + if (teco_interface.msg_window) + delwin(teco_interface.msg_window); /* * PDCurses (win32) crashes if initscr() wasn't called. @@ -1586,28 +1708,16 @@ InterfaceCurses::~InterfaceCurses() * instead. */ #ifndef XCURSES - if (info_window && !isendwin()) + if (teco_interface.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); + if (teco_interface.screen) + delscreen(teco_interface.screen); + if (teco_interface.screen_tty) + fclose(teco_interface.screen_tty); + if (teco_interface.stderr_orig >= 0) + close(teco_interface.stderr_orig); + if (teco_interface.stdout_orig >= 0) + close(teco_interface.stdout_orig); } - -} /* namespace SciTECO */ |