diff options
author | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2021-05-30 02:38:43 +0200 |
---|---|---|
committer | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2021-05-30 03:12:56 +0200 |
commit | 432ad24e382681f1c13b07e8486e91063dd96e2e (patch) | |
tree | 51838adac822767bd5884b9383cd4c72f29d3840 /src/interface-curses | |
parent | 524bc3960e6a6e5645ce904e20f72479e24e0a23 (diff) | |
download | sciteco-432ad24e382681f1c13b07e8486e91063dd96e2e.tar.gz |
THE GREAT CEEIFICATION EVENT
This is a total conversion of SciTECO to plain C (GNU C11).
The chance was taken to improve a lot of internal datastructures,
fix fundamental bugs and lay the foundations of future features.
The GTK user interface is now in an useable state!
All changes have been squashed together.
The language itself has almost not changed at all, except for:
* Detection of string terminators (usually Escape) now takes
the string building characters into account.
A string is only terminated outside of string building characters.
In other words, you can now for instance write
I^EQ[Hello$world]$
This removes one of the last bits of shellisms which is out of
place in SciTECO where no tokenization/lexing is performed.
Consequently, the current termination character can also be
escaped using ^Q/^R.
This is used by auto completions to make sure that strings
are inserted verbatim and without unwanted sideeffects.
* All strings can now safely contain null-characters
(see also: 8-bit cleanliness).
The null-character itself (^@) is not (yet) a valid SciTECO
command, though.
An incomplete list of changes:
* We got rid of the BSD headers for RB trees and lists/queues.
The problem with them was that they used a form of metaprogramming
only to gain a bit of type safety. It also resulted in less
readble code. This was a C++ desease.
The new code avoids metaprogramming only to gain type safety.
The BSD tree.h has been replaced by rb3ptr by Jens Stimpfle
(https://github.com/jstimpfle/rb3ptr).
This implementation is also more memory efficient than BSD's.
The BSD list.h and queue.h has been replaced with a custom
src/list.h.
* Fixed crashes, performance issues and compatibility issues with
the Gtk 3 User Interface.
It is now more or less ready for general use.
The GDK lock is no longer used to avoid using deprecated functions.
On the downside, the new implementation (driving the Gtk event loop
stepwise) is even slower than the old one.
A few glitches remain (see TODO), but it is hoped that they will
be resolved by the Scintilla update which will be performed soon.
* A lot of program units have been split up, so they are shorter
and easier to maintain: core-commands.c, qreg-commands.c,
goto-commands.c, file-utils.h.
* Parser states are simply structs of callbacks now.
They still use a kind of polymorphy using a preprocessor trick.
TECO_DEFINE_STATE() takes an initializer list that will be
merged with the default list of field initializers.
To "subclass" states, you can simply define new macros that add
initializers to existing macros.
* Parsers no longer have a "transitions" table but the input_cb()
may use switch-case statements.
There are also teco_machine_main_transition_t now which can
be used to implement simple transitions. Additionally, you
can specify functions to execute during transitions.
This largely avoids long switch-case-statements.
* Parsers are embeddable/reusable now, at least in parse-only mode.
This does not currently bring any advantages but may later
be used to write a Scintilla lexer for TECO syntax highlighting.
Once parsers are fully embeddable, it will also be possible
to run TECO macros in a kind of coroutine which would allow
them to process string arguments in real time.
* undo.[ch] still uses metaprogramming extensively but via
the C preprocessor of course. On the downside, most undo
token generators must be initiated explicitly (theoretically
we could have used embedded functions / trampolines to
instantiate automatically but this has turned out to be
dangereous).
There is a TECO_DEFINE_UNDO_CALL() to generate closures for
arbitrary functions now (ie. to call an arbitrary function
at undo-time). This simplified a lot of code and is much
shorter than manually pushing undo tokens in many cases.
* Instead of the ridiculous C++ Curiously Recurring Template
Pattern to achieve static polymorphy for user interface
implementations, we now simply declare all functions to
implement in interface.h and link in the implementations.
This is possible since we no longer hace to define
interface subclasses (all state is static variables in
the interface's *.c files).
* Headers are now significantly shorter than in C++ since
we can often hide more of our "class" implementations.
* Memory counting is based on dlmalloc for most platforms now.
Unfortunately, there is no malloc implementation that
provides an efficient constant-time memory counter that
is guaranteed to decrease when freeing memory.
But since we use a defined malloc implementation now,
malloc_usable_size() can be used safely for tracking memory use.
malloc() replacement is very tricky on Windows, so we
use a poll thread on Windows. This can also be enabled
on other supported platforms using --disable-malloc-replacement.
All in all, I'm still not pleased with the state of memory
limiting. It is a mess.
* Error handling uses GError now. This has the advantage that
the GError codes can be reused once we support error catching
in the SciTECO language.
* Added a few more test suite cases.
* Haiku is no longer supported as builds are instable and
I did not manage to debug them - quite possibly Haiku bugs
were responsible.
* Glib v2.44 or later are now required.
The GTK UI requires Gtk+ v3.12 or later now.
The GtkFlowBox fallback and sciteco-wrapper workaround are
no longer required.
* We now extensively use the GCC/Clang-specific g_auto
feature (automatic deallocations when leaving the current
code block).
* Updated copyright to 2021.
SciTECO has been in continuous development, even though there
have been no commits since 2018.
* Since these changes are so significant, the target release has
been set to v2.0.
It is planned that beginning with v3.0, the language will be
kept stable.
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 */ |