aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cmdline.c32
-rw-r--r--src/core-commands.c128
-rw-r--r--src/interface-curses/curses-info-popup.c32
-rw-r--r--src/interface-curses/curses-info-popup.h2
-rw-r--r--src/interface-curses/curses-utils.c16
-rw-r--r--src/interface-curses/curses-utils.h9
-rw-r--r--src/interface-curses/interface.c541
-rw-r--r--src/interface-gtk/gtk-info-popup.c4
-rw-r--r--src/interface-gtk/gtk-label.c22
-rw-r--r--src/interface-gtk/gtk-label.h2
-rw-r--r--src/interface-gtk/interface.c128
-rw-r--r--src/interface.h9
-rw-r--r--src/main.c31
-rw-r--r--src/qreg.c6
-rw-r--r--src/ring.c19
-rw-r--r--src/sciteco.h6
-rw-r--r--src/spawn.c2
-rw-r--r--src/stdio-commands.c37
-rw-r--r--src/string-utils.c10
-rw-r--r--src/view.c12
20 files changed, 597 insertions, 451 deletions
diff --git a/src/cmdline.c b/src/cmdline.c
index fa69d91..b944b5e 100644
--- a/src/cmdline.c
+++ b/src/cmdline.c
@@ -99,6 +99,14 @@ teco_cmdline_init(void)
teco_cmdline_ssm(SCI_SETTABDRAWMODE, SCTD_CONTROLCHAR, 0);
/*
+ * FIXME: This works around a problem where the caret is not
+ * scrolled in multi-line mode after it wraps at the end of the
+ * line. You cannot scroll the command-line with the mouse, so the
+ * user won't see any difference.
+ */
+ teco_cmdline_ssm(SCI_SETENDATLASTLINE, FALSE, 0);
+
+ /*
* FIXME: Something resets the margin text, so we have to set it last.
*/
teco_cmdline_ssm(SCI_MARGINSETTEXT, 0, (sptr_t)"*");
@@ -162,12 +170,32 @@ teco_cmdline_insert(const gchar *data, gsize len, GError **error)
* Result of command line replacement (}):
* Exchange command lines
*/
+ g_clear_error(&tmp_error);
+
teco_qreg_t *cmdline_reg = teco_qreg_table_find(&teco_qreg_table_globals, "\e", 1);
g_auto(teco_string_t) new_cmdline = {NULL, 0};
if (!cmdline_reg->vtable->get_string(cmdline_reg, &new_cmdline.data, &new_cmdline.len,
- NULL, error))
+ NULL, &tmp_error)) {
+ teco_error_add_frame_toplevel();
+ teco_error_display_short(tmp_error);
+ g_propagate_error(error, g_steal_pointer(&tmp_error));
+ return FALSE;
+ }
+
+ /*
+ * SciTECO code must always be UTF-8, but you can smuggle arbitrary bytes
+ * into the "\e" register.
+ * This would be cumbersome to test for in teco_state_start_cmdline_pop().
+ */
+ if (!teco_string_validate_utf8(new_cmdline)) {
+ g_set_error_literal(&tmp_error, TECO_ERROR, TECO_ERROR_CODEPOINT,
+ "Invalid UTF-8 byte sequence in command-line replacement");
+ teco_error_add_frame_toplevel();
+ teco_error_display_short(tmp_error);
+ g_propagate_error(error, g_steal_pointer(&tmp_error));
return FALSE;
+ }
/*
* Search for first differing character in old and
@@ -229,7 +257,7 @@ teco_cmdline_insert(const gchar *data, gsize len, GError **error)
}
}
- /* error is handled in teco_cmdline_keypress_c() */
+ /* error is handled in teco_cmdline_keypress() */
g_propagate_error(error, g_steal_pointer(&tmp_error));
return FALSE;
}
diff --git a/src/core-commands.c b/src/core-commands.c
index f81bdf3..d2c8b9d 100644
--- a/src/core-commands.c
+++ b/src/core-commands.c
@@ -1739,8 +1739,10 @@ TECO_DEFINE_STATE(teco_state_ascii,
/*$ ^[^[ ^[$ $$ ^C terminate return
* [a1,a2,...]$$ -- Terminate command line or return from macro
+ * -$$
* [a1,a2,...]^[$
* [a1,a2,...]^C
+ * -^C
*
* Returns from the current macro invocation.
* This will pass control to the calling macro immediately
@@ -1754,6 +1756,8 @@ TECO_DEFINE_STATE(teco_state_ascii,
* All braces opened in the current macro invocation will
* be closed and their values discarded.
* Only the direct arguments to \fB$$\fP will be kept.
+ * If the prefix sign is set (\(lq-\fB$$\fP\(rq) the command
+ * will always leave -1 on the stack.
*
* Returning from the top-level macro in batch mode
* will exit the program or start up interactive mode depending
@@ -1806,6 +1810,9 @@ teco_return(teco_machine_main_t *ctx, GError **error)
ctx->parent.current = &teco_state_start;
if (!teco_expressions_eval(FALSE, error))
return NULL;
+ if (teco_num_sign < 0)
+ /* only if you wrote -$$ */
+ teco_expressions_push(1);
teco_error_return_set(error, teco_expressions_args());
return NULL;
}
@@ -1873,6 +1880,18 @@ TECO_DEFINE_STATE_START(teco_state_escape,
.end_of_macro_cb = teco_state_escape_end_of_macro
);
+static gboolean
+teco_state_ctlc_initial(teco_machine_main_t *ctx, GError **error)
+{
+ if (G_UNLIKELY(ctx == &teco_cmdline.machine)) {
+ g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "<^C> is not allowed to terminate command-lines");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
/*
* Just like ^[, ^C actually implements a lookahead,
* so a ^C itself does nothing.
@@ -1892,20 +1911,20 @@ teco_state_ctlc_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
}
static gboolean
-teco_state_ctlc_initial(teco_machine_main_t *ctx, GError **error)
+teco_state_ctlc_end_of_macro(teco_machine_main_t *ctx, GError **error)
{
- if (G_UNLIKELY(ctx == &teco_cmdline.machine)) {
- g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
- "<^C> is not allowed to terminate command-lines");
- return FALSE;
- }
-
+ if (ctx->flags.mode > TECO_MODE_NORMAL)
+ return TRUE;
+ if (teco_num_sign < 0)
+ /* only if you wrote -^C */
+ teco_expressions_push(1);
return TRUE;
}
TECO_DEFINE_STATE_START(teco_state_ctlc,
.initial_cb = (teco_state_initial_cb_t)teco_state_ctlc_initial,
- .input_cb = (teco_state_input_cb_t)teco_state_ctlc_input
+ .input_cb = (teco_state_input_cb_t)teco_state_ctlc_input,
+ .end_of_macro_cb = (teco_state_end_of_macro_cb_t)teco_state_ctlc_end_of_macro
);
static teco_state_t *
@@ -1983,7 +2002,7 @@ TECO_DEFINE_STATE_COMMAND(teco_state_ctlc_control,
* Without any argument ED returns the current flags.
*
* Currently, the following flags are used by \*(ST:
- * .IP 2: 5
+ * .IP 2: 6
* Reflects whether program termination has been requested
* by successfully performing the \fBEX\fP command.
* This flag can also be used to cancel the effect of any
@@ -2117,46 +2136,35 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error)
* first.
* Memory limiting is enabled by default.
* .IP 3:
- * This \fBwrite-only\fP property allows redefining the
- * first 16 entries of the terminal color palette \(em a
- * feature required by some
- * color schemes when using the Curses user interface.
- * When setting this property, you are making a request
- * to define the terminal \fIcolor\fP as the Scintilla-compatible
- * RGB color value given in the \fIrgb\fP parameter.
- * \fIcolor\fP must be a value between 0 and 15
- * corresponding to black, red, green, yellow, blue, magenta,
- * cyan, white, bright black, bright red, etc. in that order.
- * The \fIrgb\fP value has the format 0xBBGGRR, i.e. the red
- * component is the least-significant byte and all other bytes
- * are ignored.
- * Note that on curses, RGB color values sent to Scintilla
- * are actually mapped to these 16 colors by the Scinterm port
- * and may represent colors with no resemblance to the \(lqRGB\(rq
- * value used (depending on the current palette) \(em they should
- * instead be viewed as placeholders for 16 standard terminal
- * color codes.
- * Please refer to the Scinterm manual for details on the allowed
- * \(lqRGB\(rq values and how they map to terminal colors.
- * This command provides a crude way to request exact RGB colors
- * for the first 16 terminal colors.
- * The color definition may be queued or be completely ignored
- * on other user interfaces and no feedback is given
- * if it fails. In fact feedback cannot be given reliably anyway.
- * Note that on 8 color terminals, only the first 8 colors
- * can be redefined (if you are lucky).
- * Note that due to restrictions of most terminal emulators
- * and some curses implementations, this command simply will not
- * restore the original palette entry or request
- * when rubbed out and should generally only be used in
- * \fIbatch-mode\fP \(em typically when loading a color scheme.
- * For the same reasons \(em even though \*(ST tries hard to
- * restore the original palette on exit \(em palette changes may
- * persist after \*(ST terminates on most terminal emulators on Unix.
- * The only emulator which will restore their default palette
- * on exit the author is aware of is \fBxterm\fP(1) and
- * the Linux console driver.
- * You have been warned. Good luck.
+ * This \fBwrite-only\fP property allows disabling use of
+ * the 16 color palette of default colors when using
+ * the Curses user interface.
+ * By default, when working with RGB values in Scintilla
+ * messages (and \*(ST color schemes for that matter)
+ * the 16 \(lqdefault\(rq colors are represented by
+ * special RGB placeholders (e.g. 0x000080 for \fICOLOR_RED\fP)
+ * as documented in the Scinterm manual.
+ * Any other RGB values will be automatically allocated
+ * in the terminal emulator if supported, giving the illusion
+ * of true color support, even though the actual number of
+ * concurrent colors on screen may be limited by the terminal
+ * emulator (see \fImax_colors\fP and \fImax_pairs\fP
+ * .B terminfo (1)
+ * capabilities) and other factors.
+ * \*(ST tries to avoid the first 16 color ids so as not to
+ * interfer with other terminal-based applications.
+ * The default palette however may be overwritten when requesting
+ * new colors in terminals restricted to 16 colors.
+ * In this case the original palette might not be restored properly on
+ * program termination and affect other terminal applications.
+ * The 16 predefined colors do not map to exact RGB values,
+ * though, but point into a palette at the terminal emulator.
+ * \*(ST therefore allows you to disable the 16 RGB placeholders
+ * by calling \(lq0,3EJ\(rq so that all colors are freshly initialized
+ * and will represent their exact RGB values.
+ * It has no effect when using the GTK interface.
+ * You are recommended to add \(lq0,3EJ\(rq to all color
+ * schemes that rely on exact RGB values.
* .IP 4:
* The column after the last horizontal movement.
* This is only used by \fBfnkeys.tes\fP and is similar to the Scintilla-internal
@@ -2169,12 +2177,14 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error)
* .IP 6:
* .SCITECO_TOPIC recovery
* Interval in seconds for the creation of recovery files
- * or 0 if those dumps are disabled (the default is 300 seconds).
+ * or 0 if those dumps are disabled (the default is 120 seconds).
* When enabled all dirty buffers are dumped to files with hash
* signs around the original basename (\fB#\fIfilename\fB#\fR).
* They are removed automatically when no longer required,
* but may be left around when the \*(ST crashes or terminates
* unexpectedly.
+ * During graceful shutdowns (\fBSIGTERM\fP etc.) recovery
+ * files may be dumped immediately even before the interval expires.
* After changing the interval, the new value may become
* active only after the previous interval expires.
* Recovery files are not dumped in batch mode.
@@ -2229,7 +2239,7 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error)
EJ_USER_INTERFACE = 0,
EJ_BUFFERS,
EJ_MEMORY_LIMIT,
- EJ_INIT_COLOR,
+ EJ_PALETTE,
EJ_CARETX,
EJ_CMDLINE_HEIGHT,
EJ_RECOVERY_INTERVAL
@@ -2253,19 +2263,13 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error)
return;
break;
- case EJ_INIT_COLOR:
- if (value < 0 || value >= 16) {
- g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
- "Invalid color code %" TECO_INT_FORMAT " "
- "specified for <EJ>", value);
- return;
- }
- if (!teco_expressions_args()) {
- teco_error_argexpected_set(error, "EJ");
+ case EJ_PALETTE:
+ if (teco_is_success(value)) {
+ g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Default color palette can only be disabled");
return;
}
- teco_interface_init_color((guint)value,
- (guint32)teco_expressions_pop_num(0));
+ teco_interface_disable_palette();
break;
case EJ_CARETX:
diff --git a/src/interface-curses/curses-info-popup.c b/src/interface-curses/curses-info-popup.c
index edb6e15..83d4665 100644
--- a/src/interface-curses/curses-info-popup.c
+++ b/src/interface-curses/curses-info-popup.c
@@ -73,7 +73,7 @@ teco_curses_info_popup_add(teco_curses_info_popup_t *ctx, teco_popup_entry_type_
}
static void
-teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr)
+teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr, gshort pair)
{
int pad_lines; /**! pad height */
gint pad_cols; /**! entry columns */
@@ -102,11 +102,11 @@ teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr)
ctx->pad = newpad(pad_lines, COLS - 2);
/*
- * NOTE: attr could contain A_REVERSE on monochrome terminals,
+ * NOTE: attr could contain WA_REVERSE on monochrome terminals,
* so we use foreground attributes instead of background attributes.
- * This way, we can cancel out the A_REVERSE if necessary.
+ * This way, we can cancel out the WA_REVERSE if necessary.
*/
- wattrset(ctx->pad, attr);
+ wattr_set(ctx->pad, attr, pair, NULL);
teco_curses_clrtobot(ctx->pad);
/*
@@ -161,7 +161,7 @@ teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr)
}
void
-teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr)
+teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr, gshort pair)
{
if (!ctx->length)
/* nothing to display */
@@ -171,7 +171,7 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr)
delwin(ctx->window);
if (!ctx->pad)
- teco_curses_info_popup_init_pad(ctx, attr);
+ teco_curses_info_popup_init_pad(ctx, attr, pair);
gint pad_lines = getmaxy(ctx->pad);
/*
@@ -183,15 +183,17 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr)
/* window covers message, scintilla and info windows */
ctx->window = newwin(popup_lines, 0, LINES - teco_cmdline.height - popup_lines, 0);
- wattrset(ctx->window, attr);
+ wattr_set(ctx->window, attr, pair, NULL);
- 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);
+ /*
+ * NOTE: wborder() is broken for large pair numbers, at least on ncurses.
+ */
+ waddch(ctx->window, ACS_ULCORNER);
+ whline(ctx->window, ACS_HLINE, COLS - 2);
+ mvwaddch(ctx->window, 0, COLS - 1, ACS_URCORNER);
+ mvwvline(ctx->window, 1, 0, ACS_VLINE, getmaxy(ctx->window)-1);
+ /* may be overwritten with scrollbar */
+ mvwvline(ctx->window, 1, COLS - 1, ACS_VLINE, getmaxy(ctx->window)-1);
copywin(ctx->pad, ctx->window,
ctx->pad_first_line, 0,
@@ -214,7 +216,7 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr)
* Instead, simply draw reverse blanks.
*/
wmove(ctx->window, bar_y, COLS-1);
- wattrset(ctx->window, attr ^ A_REVERSE);
+ wattr_set(ctx->window, attr ^ WA_REVERSE, pair, NULL);
wvline(ctx->window, ' ', bar_height);
}
diff --git a/src/interface-curses/curses-info-popup.h b/src/interface-curses/curses-info-popup.h
index fd923e9..898ba70 100644
--- a/src/interface-curses/curses-info-popup.h
+++ b/src/interface-curses/curses-info-popup.h
@@ -49,7 +49,7 @@ teco_curses_info_popup_init(teco_curses_info_popup_t *ctx)
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);
-void teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr);
+void teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr, gshort pair);
const teco_string_t *teco_curses_info_popup_getentry(teco_curses_info_popup_t *ctx, gint y, gint x);
void teco_curses_info_popup_scroll_page(teco_curses_info_popup_t *ctx);
void teco_curses_info_popup_scroll(teco_curses_info_popup_t *ctx, gint delta);
diff --git a/src/interface-curses/curses-utils.c b/src/interface-curses/curses-utils.c
index 875c332..3b25d56 100644
--- a/src/interface-curses/curses-utils.c
+++ b/src/interface-curses/curses-utils.c
@@ -53,9 +53,9 @@ teco_curses_format_str(WINDOW *win, const gchar *str, gsize len, gint max_width)
/*
* The entire background might be in reverse, especially
* on monochrome terminals.
- * In those cases, we have to __remove__ the A_REVERSE flag.
+ * In those cases, we have to __remove__ the WA_REVERSE flag.
*/
- attr_t attrs = A_NORMAL;
+ attr_t attrs = WA_NORMAL;
short pair = 0;
wattr_get(win, &attrs, &pair, NULL);
@@ -81,28 +81,28 @@ teco_curses_format_str(WINDOW *win, const gchar *str, gsize len, gint max_width)
chars_added++;
if (chars_added > max_width)
goto truncate;
- wattr_set(win, attrs ^ A_REVERSE, pair, NULL);
+ wattr_set(win, attrs ^ WA_REVERSE, pair, NULL);
waddch(win, '$');
break;
case '\r':
chars_added += 2;
if (chars_added > max_width)
goto truncate;
- wattr_set(win, attrs ^ A_REVERSE, pair, NULL);
+ wattr_set(win, attrs ^ WA_REVERSE, pair, NULL);
waddstr(win, "CR");
break;
case '\n':
chars_added += 2;
if (chars_added > max_width)
goto truncate;
- wattr_set(win, attrs ^ A_REVERSE, pair, NULL);
+ wattr_set(win, attrs ^ WA_REVERSE, pair, NULL);
waddstr(win, "LF");
break;
case '\t':
chars_added += 3;
if (chars_added > max_width)
goto truncate;
- wattr_set(win, attrs ^ A_REVERSE, pair, NULL);
+ wattr_set(win, attrs ^ WA_REVERSE, pair, NULL);
waddstr(win, "TAB");
break;
default:
@@ -110,7 +110,7 @@ teco_curses_format_str(WINDOW *win, const gchar *str, gsize len, gint max_width)
chars_added += 2;
if (chars_added > max_width)
goto truncate;
- wattr_set(win, attrs ^ A_REVERSE, pair, NULL);
+ wattr_set(win, attrs ^ WA_REVERSE, pair, NULL);
waddch(win, '^');
waddch(win, TECO_CTL_ECHO(*str));
} else {
@@ -126,7 +126,7 @@ teco_curses_format_str(WINDOW *win, const gchar *str, gsize len, gint max_width)
waddnstr(win, str, clen);
}
}
- /* restore original state of A_REVERSE */
+ /* restore original state of WA_REVERSE */
wattr_set(win, attrs, pair, NULL);
str += clen;
diff --git a/src/interface-curses/curses-utils.h b/src/interface-curses/curses-utils.h
index 97fc1cc..c6d9d8d 100644
--- a/src/interface-curses/curses-utils.h
+++ b/src/interface-curses/curses-utils.h
@@ -27,15 +27,12 @@ guint teco_curses_format_str(WINDOW *win, const gchar *str, gsize len, gint max_
guint teco_curses_format_filename(WINDOW *win, const gchar *filename, gint max_width);
-/**
- * Add Unicode character to window.
- * This is just like wadd_wch(), but does not require wide-char APIs.
- */
+/** Add Unicode character to window. */
static inline void
teco_curses_add_wc(WINDOW *win, gunichar chr)
{
- gchar buf[6];
- waddnstr(win, buf, g_unichar_to_utf8(chr, buf));
+ wchar_t wc = chr;
+ waddnwstr(win, &wc, 1);
}
/**
diff --git a/src/interface-curses/interface.c b/src/interface-curses/interface.c
index 0764db3..21e728f 100644
--- a/src/interface-curses/interface.c
+++ b/src/interface-curses/interface.c
@@ -125,7 +125,8 @@ teco_console_ctrl_handler(DWORD type)
{
switch (type) {
case CTRL_C_EVENT:
- teco_interrupted = TRUE;
+ case CTRL_BREAK_EVENT:
+ teco_interrupted = TECO_INTERRUPTED;
return TRUE;
}
@@ -137,6 +138,17 @@ teco_console_ctrl_handler(DWORD type)
static gint teco_xterm_version(void) G_GNUC_UNUSED;
static gint teco_interface_blocking_getch(void);
+static inline gboolean teco_interface_check_key(gint key);
+
+#define MAX_COLORS (MIN(COLORS, G_MAXSHORT)/2)
+/**
+ * Maximum effective number of color pairs.
+ * Scinterm uses shorts for color pairs, so G_MAXSHORT is the maximum.
+ * Some Curses implementations (NetBSD, PDCurses) may support less,
+ * but set COLOR_PAIRS accordingly.
+ * Half of the range is reserved to Scinterm.
+ */
+#define MAX_PAIRS (MIN(COLOR_PAIRS, G_MAXSHORT)/2)
/**
* Get bright variant of one of the 8 standard
@@ -154,6 +166,12 @@ static gint teco_interface_blocking_getch(void);
* The 8 bright colors (if terminal supports at
* least 16 colors), else they are identical to
* the non-bright colors (default curses colors).
+ *
+ * As a consequence a light black (grey) foreground
+ * on black background might be invisible on 8 color
+ * terminals.
+ * If you add in A_BOLD, there is a good chance that
+ * the text will still become grey.
*/
#define COLOR_LBLACK COLOR_LIGHT(COLOR_BLACK)
#define COLOR_LRED COLOR_LIGHT(COLOR_RED)
@@ -166,30 +184,22 @@ static gint teco_interface_blocking_getch(void);
static struct {
/**
+ * Mapping of RGB codes (encoded into a pointer)
+ * to a curses color number.
+ */
+ GHashTable *color_table;
+ /**
* Mapping of foreground and background curses color tuples
* (encoded into a pointer) to a color pair number.
*/
GHashTable *pair_table;
-
/**
- * 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).
+ * Whether to map the 16 standard palettized colors
+ * in color_table.
+ * Should be disabled when using arbitrary RGB values,
+ * so every color is initialized to exact RGB values.
*/
- 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];
+ gboolean use_palette;
int stdin_orig, stdout_orig, stderr_orig;
SCREEN *screen;
@@ -232,56 +242,50 @@ static struct {
*
* Scinterm no longer initializes all color pairs for all combinations of
* the builtin foreground and background colors.
- * Since curses guarantees only 256 color pairs, we cannot do that either.
- * Instead we allocate color pairs beginnig at 128 on demand
- * (similar to what Scinterm does).
- *
- * @note Scinterm now also has scintilla_set_color_offsets(),
- * so we could use the lower 127 color pairs as well.
+ * Since curses does not guarantee any number of color pairs, we cannot do that either.
+ * Instead we allocate color pairs in the first half of
+ * color pair space on demand, while the second half is reserved to Scinterm.
*
+ * @param attr attributes to modify (for supporting monochrome terminals)
* @param fg curses foreground color
* @param bg curses background color
* @return curses color pair number
*/
static gshort
-teco_color_pair(gshort fg, gshort bg)
+teco_color_pair(attr_t *attr, gshort fg, gshort bg)
{
- static gshort last_pair = 127;
+ static guint next_pair = 1;
+
+ /*
+ * Basic support for monochrome terminals:
+ * Every background, that is not black is assumed to be a
+ * dark-on-bright area, rendered in reverse.
+ * This will at least work with the terminal.tes and contrast.tes
+ * color schemes.
+ */
+ if (!has_colors()) {
+ if (bg != COLOR_BLACK)
+ *attr |= WA_REVERSE;
+ return 0;
+ }
G_STATIC_ASSERT(sizeof(gshort)*2 <= sizeof(guint));
- gpointer key = GUINT_TO_POINTER(((guint)fg << 16) | bg);
+ gpointer key = GUINT_TO_POINTER(((guint)fg << 8*sizeof(bg)) | bg);
gpointer value = g_hash_table_lookup(teco_interface.pair_table, key);
if (G_LIKELY(value != NULL))
return GPOINTER_TO_UINT(value);
- init_pair(++last_pair, fg, bg);
- g_hash_table_insert(teco_interface.pair_table, key, GUINT_TO_POINTER(last_pair));
- return last_pair;
+ if (G_UNLIKELY(next_pair >= MAX_PAIRS))
+ return 0;
+ init_pair(next_pair, fg, bg);
+ g_hash_table_insert(teco_interface.pair_table, key, GUINT_TO_POINTER(next_pair));
+ return next_pair++;
}
-/**
- * Curses attribute for the color combination
- * according to the color pairs initialized by
- * Scinterm.
- * This is equivalent to Scinterm's internal term_color_attr().
- *
- * @param fg foreground color
- * @param bg background color
- * @return curses attribute
- */
-static inline attr_t
-teco_color_attr(gshort fg, gshort bg)
+static inline gint
+teco_wattr_set(WINDOW *win, attr_t attr, gshort fg, gshort bg)
{
- if (has_colors())
- return COLOR_PAIR(teco_color_pair(fg, bg));
-
- /*
- * Basic support for monochrome terminals:
- * Every background, that is not black is assumed to be a
- * dark-on-bright area, rendered in reverse.
- * This will at least work with the terminal.tes
- * color scheme.
- */
- return bg != COLOR_BLACK ? A_REVERSE : 0;
+ gshort pair = teco_color_pair(&attr, fg, bg);
+ return wattr_set(win, attr, pair, NULL);
}
/**
@@ -298,38 +302,60 @@ teco_rgb2curses_triple(guint32 rgb, gshort *r, gshort *g, gshort *b)
*b = ((rgb & 0xFF0000) >> 16)*1000/0xFF;
}
+static inline void
+teco_interface_insert_color(guint32 rgb, gshort color)
+{
+ /*
+ * Color ids are stored one-offset, so that COLOR_BLACK
+ * can be discerned from a missing entry.
+ */
+ g_hash_table_insert(teco_interface.color_table,
+ GUINT_TO_POINTER(rgb), GUINT_TO_POINTER(color)+1);
+}
+
/**
* Convert a Scintilla-compatible RGB color value
* (0xBBGGRR) to a Curses color code (e.g. COLOR_BLACK).
- * This does not work with arbitrary RGB values but
- * only the 16 RGB color values defined by Scinterm
- * corresponding to the 16 terminal colors.
- * It is equivalent to Scinterm's internal `term_color`
- * function.
+ *
+ * This will automatically initialize new curses colors
+ * when necessary.
+ * Unless the palette is disabled with
+ * teco_interface_disable_palette(), this will return
+ * the 16 "standard" colors for a predefined list of
+ * RGB values (just like defined by Scinterm).
*/
static gshort
teco_rgb2curses(guint32 rgb)
{
- switch (rgb) {
- case 0x000000: return COLOR_BLACK;
- case 0x000080: return COLOR_RED;
- case 0x008000: return COLOR_GREEN;
- case 0x008080: return COLOR_YELLOW;
- case 0x800000: return COLOR_BLUE;
- case 0x800080: return COLOR_MAGENTA;
- case 0x808000: return COLOR_CYAN;
- case 0xC0C0C0: return COLOR_WHITE;
- case 0x404040: return COLOR_LBLACK;
- case 0x0000FF: return COLOR_LRED;
- case 0x00FF00: return COLOR_LGREEN;
- case 0x00FFFF: return COLOR_LYELLOW;
- case 0xFF0000: return COLOR_LBLUE;
- case 0xFF00FF: return COLOR_LMAGENTA;
- case 0xFFFF00: return COLOR_LCYAN;
- case 0xFFFFFF: return COLOR_LWHITE;
- }
+ static guint colors = 0;
+
+ if (!has_colors())
+ return COLOR_WHITE;
- return COLOR_WHITE;
+ gpointer value = g_hash_table_lookup(teco_interface.color_table,
+ GUINT_TO_POINTER(rgb));
+ if (G_LIKELY(value != NULL))
+ return GPOINTER_TO_UINT(value)-1;
+
+ /*
+ * Even when initializing new colors, we will __try__ not to
+ * touch the 16 palettized colors.
+ * First of all, we might still need them and secondly,
+ * this ensures we don't have to reset them on exit
+ * (which is not always possible anyway).
+ * There can still be 16 color terminals with
+ * can_change_color() == TRUE, though.
+ */
+ guint next_color = colors + (COLORS > 16 ? 16 : 0);
+
+ if (G_UNLIKELY(next_color >= MAX_COLORS) || !can_change_color())
+ return COLOR_WHITE;
+ gshort r, g, b;
+ teco_rgb2curses_triple(rgb, &r, &g, &b);
+ init_color(next_color, r, g, b);
+ teco_interface_insert_color(rgb, next_color);
+ colors++;
+ return next_color;
}
static gint
@@ -395,6 +421,13 @@ teco_view_noutrefresh(teco_view_t *ctx)
scintilla_noutrefresh(ctx);
}
+static inline void
+teco_view_update_cursor(teco_view_t *ctx)
+{
+ if (teco_view_ssm(ctx, SCI_GETCARETSTYLE, 0, 0) & CARETSTYLE_CURSES)
+ scintilla_update_cursor(ctx);
+}
+
static inline WINDOW *
teco_view_get_window(teco_view_t *ctx)
{
@@ -413,9 +446,6 @@ teco_view_free(teco_view_t *ctx)
scintilla_delete(ctx);
}
-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);
@@ -428,12 +458,9 @@ static void teco_interface_set_window_title(const gchar *title);
static void teco_interface_draw_info(void);
void
-teco_interface_init(void)
+teco_interface_init(gint argc, gchar **argv)
{
- 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.use_palette = TRUE;
teco_interface.stdin_orig = teco_interface.stdout_orig = teco_interface.stderr_orig = -1;
@@ -442,9 +469,13 @@ teco_interface_init(void)
teco_cmdline_init();
/*
* The default INDIC_STRIKE wouldn't be visible.
- * Instead we use INDIC_SQUIGGLE, which is rendered as A_UNDERLINE.
+ * Instead we use INDIC_SQUIGGLE, which is rendered as WA_UNDERLINE.
*/
teco_cmdline_ssm(SCI_INDICSETSTYLE, INDICATOR_RUBBEDOUT, INDIC_SQUIGGLE);
+ /*
+ * Enable hardware cursor by default.
+ */
+ teco_cmdline_ssm(SCI_SETCARETSTYLE, CARETSTYLE_CURSES, 0);
/*
* On all platforms except Curses/XTerm, it's
@@ -471,133 +502,11 @@ teco_interface_get_options(void)
return NULL;
}
-static void
-teco_interface_init_color_safe(guint color, guint32 rgb)
-{
-#if defined(__PDCURSES__) && !defined(PDCURSES_GUI)
- if (teco_interface.orig_color_table[color].r < 0) {
- color_content((short)color,
- &teco_interface.orig_color_table[color].r,
- &teco_interface.orig_color_table[color].g,
- &teco_interface.orig_color_table[color].b);
- }
-#endif
-
- gshort r, g, b;
- teco_rgb2curses_triple(rgb, &r, &g, &b);
- init_color((short)color, r, g, b);
-}
-
-#if defined(__PDCURSES__) && !defined(PDCURSES_GUI)
-
-/*
- * On PDCurses/WinCon, color_content() will actually return
- * the real console color palette - or at least the default
- * palette when the console started.
- */
-static void
-teco_interface_restore_colors(void)
-{
- if (!can_change_color())
- return;
-
- 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,
- 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 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.
- * It would do more damage to restore the palette returned by
- * color_content() than it helps.
- * xterm has the escape sequence "\e]104\a" which restores
- * the palette from Xdefaults but not all terminal emulators
- * claiming to be "xterm" via $TERM support this escape sequence.
- * lxterminal for instance will print gibberish instead.
- * So we try to look whether $XTERM_VERSION is set.
- * There are hardly any other terminal emulators that support palette
- * resets.
- * The only emulator I'm aware of which can be identified reliably
- * by $TERM supporting a palette reset is the Linux console
- * (see console_codes(4)). The escape sequence "\e]R" is already
- * part of its terminfo description (orig_colors capability)
- * which is apparently sent by endwin(), so the palette is
- * already properly restored on endwin().
- * Welcome in Curses hell.
- */
-static void
-teco_interface_restore_colors(void)
-{
- if (teco_xterm_version() < 0)
- return;
-
- /*
- * Looks like a real XTerm
- */
- fputs("\e]104\a", teco_interface.screen_tty);
- fflush(teco_interface.screen_tty);
-}
-
-#else /* (!__PDCURSES__ || PDCURSES_GUI) && !CURSES_TTY */
-
-static void
-teco_interface_restore_colors(void)
-{
- /*
- * No way to restore the palette, or it's
- * unnecessary (e.g. XCurses)
- */
-}
-
-#endif
-
void
-teco_interface_init_color(guint color, guint32 rgb)
+teco_interface_disable_palette(void)
{
- if (color >= G_N_ELEMENTS(teco_interface.color_table))
- return;
-
-#if defined(__PDCURSES__) && !defined(PDC_RGB)
- /*
- * PDCurses will usually number color codes differently
- * (least significant bit is the blue component) while
- * SciTECO macros will assume a standard terminal color
- * code numbering with red as the LSB.
- * Therefore we have to swap the bit order of the least
- * significant 3 bits here.
- */
- color = (color & ~0x5) |
- ((color & 0x1) << 2) | ((color & 0x4) >> 2);
-#endif
-
- if (teco_interface.input_pad) {
- /* interactive mode */
- if (!can_change_color())
- return;
-
- teco_interface_init_color_safe(color, rgb);
- } else {
- /*
- * batch mode: store colors,
- * they can only be initialized after start_color()
- * which is called by Scinterm when interactive
- * mode is initialized
- */
- teco_interface.color_table[color] = (gint32)rgb;
- }
+ teco_interface.use_palette = FALSE;
+ scintilla_disable_color_palette();
}
#ifdef CURSES_TTY
@@ -739,8 +648,33 @@ teco_interface_init_interactive(GError **error)
teco_interface_init_screen();
+ teco_interface.color_table = g_hash_table_new(g_direct_hash, g_direct_equal);
teco_interface.pair_table = g_hash_table_new(g_direct_hash, g_direct_equal);
start_color();
+ scintilla_set_color_offsets(MAX_COLORS, MAX_PAIRS);
+
+ if (teco_interface.use_palette && has_colors()) {
+ teco_interface_insert_color(0x000000, COLOR_BLACK);
+ teco_interface_insert_color(0x000080, COLOR_RED);
+ teco_interface_insert_color(0x008000, COLOR_GREEN);
+ teco_interface_insert_color(0x008080, COLOR_YELLOW);
+ teco_interface_insert_color(0x800000, COLOR_BLUE);
+ teco_interface_insert_color(0x800080, COLOR_MAGENTA);
+ teco_interface_insert_color(0x808000, COLOR_CYAN);
+ teco_interface_insert_color(0xC0C0C0, COLOR_WHITE);
+ /*
+ * On 8-color terminals, we still support the
+ * higher (light) colors, but map them to the lower codes as well.
+ */
+ teco_interface_insert_color(0x404040, COLOR_LBLACK);
+ teco_interface_insert_color(0x0000FF, COLOR_LRED);
+ teco_interface_insert_color(0x00FF00, COLOR_LGREEN);
+ teco_interface_insert_color(0x00FFFF, COLOR_LYELLOW);
+ teco_interface_insert_color(0xFF0000, COLOR_LBLUE);
+ teco_interface_insert_color(0xFF00FF, COLOR_LMAGENTA);
+ teco_interface_insert_color(0xFFFF00, COLOR_LCYAN);
+ teco_interface_insert_color(0xFFFFFF, COLOR_LWHITE);
+ }
/*
* On UNIX terminals, the escape key is usually
@@ -784,8 +718,6 @@ teco_interface_init_interactive(GError **error)
cbreak();
noecho();
- /* Scintilla draws its own cursor */
- curs_set(0);
/*
* This has also been observed to reduce flickering
* in teco_interface_refresh().
@@ -824,19 +756,6 @@ teco_interface_init_interactive(GError **error)
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(teco_interface.color_table); i++) {
- /*
- * init_color() may still fail if COLORS < 16
- */
- if (teco_interface.color_table[i] >= 0)
- teco_interface_init_color_safe(i, (guint32)teco_interface.color_table[i]);
- }
- }
-
- /*
* Only now (in interactive mode), it's safe to initialize
* the clipboard Q-Registers on ncurses with a compatible terminal
* emulator since clipboard operations will no longer interfer
@@ -868,7 +787,6 @@ teco_interface_restore_batch(void)
* (i.e. return to batch mode)
*/
endwin();
- teco_interface_restore_colors();
/*
* Restore stdin, stdout and stderr, so output goes to
@@ -933,7 +851,7 @@ teco_interface_msg_literal(teco_msg_t type, const gchar *str, gsize len)
teco_interface_stdio_msg(type, str, len);
#endif
- short fg, bg;
+ gshort fg, bg;
fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0));
@@ -955,7 +873,7 @@ teco_interface_msg_literal(teco_msg_t type, const gchar *str, gsize len)
}
wmove(teco_interface.msg_window, 0, 0);
- wattrset(teco_interface.msg_window, teco_color_attr(fg, bg));
+ teco_wattr_set(teco_interface.msg_window, 0, fg, bg);
teco_curses_format_str(teco_interface.msg_window, str, len, -1);
teco_curses_clrtobot(teco_interface.msg_window);
}
@@ -966,11 +884,11 @@ teco_interface_msg_clear(void)
if (!teco_interface.input_pad) /* batch mode */
return;
- short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0));
- short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0));
+ gshort fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0));
+ gshort bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0));
wmove(teco_interface.msg_window, 0, 0);
- wattrset(teco_interface.msg_window, teco_color_attr(fg, bg));
+ teco_wattr_set(teco_interface.msg_window, 0, fg, bg);
teco_curses_clrtobot(teco_interface.msg_window);
}
@@ -984,25 +902,41 @@ teco_interface_getch(gboolean widechar)
/*
* Signal that we accept input by drawing a real cursor in the message bar.
+ * FIXME: curs_set(2) would be better, but isn't always visible.
*/
wmove(teco_interface.msg_window, 0, 0);
- curs_set(1);
wrefresh(teco_interface.msg_window);
+ curs_set(1);
gchar buf[4];
gint i = 0;
gint32 cp;
do {
- cp = teco_interface_blocking_getch();
- if (cp == TECO_CTL_KEY('C'))
- teco_interrupted = TRUE;
- if (cp == TECO_CTL_KEY('C') || cp == TECO_CTL_KEY('D')) {
- cp = -1;
- break;
- }
- if (cp < 0 || cp > 0xFF)
- continue;
+ do {
+ cp = teco_interface_blocking_getch();
+ switch (cp) {
+#ifdef KEY_RESIZE
+ case KEY_RESIZE:
+ teco_interface_resize_all_windows();
+ teco_interface_refresh(FALSE);
+ /* cursor was moved by the resize */
+ wmove(teco_interface.msg_window, 0, 0);
+ wrefresh(teco_interface.msg_window);
+ break;
+#endif
+ case KEY_BACKSPACE:
+ return TECO_CTL_KEY('H');
+ case KEY_ENTER:
+ return '\n';
+ case TECO_CTL_KEY('C'):
+ teco_interrupted = TECO_INTERRUPTED;
+ /* fall through */
+ case TECO_CTL_KEY('D'):
+ /* emulates EOF on stdin */
+ return -1;
+ }
+ } while (cp < 0 || cp > 0xFF || !teco_interface_check_key(cp));
if (!widechar || !cp)
break;
@@ -1014,7 +948,6 @@ teco_interface_getch(gboolean widechar)
i = 0;
} while (cp < 0);
- curs_set(0);
return cp;
}
@@ -1144,11 +1077,11 @@ teco_interface_draw_info(void)
* the current buffer's STYLE_DEFAULT.
* The same style is used for MSG_USER messages.
*/
- short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0));
- short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0));
+ gshort fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0));
+ gshort bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0));
wmove(teco_interface.info_window, 0, 0);
- wattrset(teco_interface.info_window, teco_color_attr(fg, bg));
+ teco_wattr_set(teco_interface.info_window, 0, fg, bg);
const gchar *info_type_str;
@@ -1229,7 +1162,20 @@ teco_interface_info_update_buffer(const teco_buffer_t *buffer)
* default clipboard ("~") as we do not know whether
* it corresponds to the X11 PRIMARY, SECONDARY or
* CLIPBOARD selections.
+ *
+ * On XCurses we must not (and don't have to) probe
+ * the clipboard as it would be before Xinitscr().
*/
+#ifdef XCURSES
+
+static void
+teco_interface_init_clipboard(void)
+{
+ teco_qreg_table_replace(&teco_qreg_table_globals, teco_qreg_clipboard_new(""));
+}
+
+#else /* XCURSES */
+
static void
teco_interface_init_clipboard(void)
{
@@ -1254,6 +1200,8 @@ teco_interface_init_clipboard(void)
teco_qreg_table_replace(&teco_qreg_table_globals, teco_qreg_clipboard_new(""));
}
+#endif /* !XCURSES */
+
gboolean
teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len, GError **error)
{
@@ -1702,11 +1650,13 @@ teco_interface_popup_show(gsize prefix_len)
/* batch mode */
return;
- short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0));
- short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0));
+ gshort fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0));
+ gshort bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0));
teco_interface.popup_prefix_len = prefix_len;
- teco_curses_info_popup_show(&teco_interface.popup, teco_color_attr(fg, bg));
+ attr_t attr = 0;
+ gshort pair = teco_color_pair(&attr, fg, bg);
+ teco_curses_info_popup_show(&teco_interface.popup, attr, pair);
}
void
@@ -1757,7 +1707,7 @@ teco_interface_popup_clear(void)
gboolean
teco_interface_is_interrupted(void)
{
- return teco_interrupted != FALSE;
+ return teco_interrupted == TECO_INTERRUPTED;
}
#else /* !CURSES_TTY && !PDCURSES_WINCON && !NCURSES_WIN32 */
@@ -1776,7 +1726,7 @@ teco_interface_is_interrupted(void)
{
if (!teco_interface.input_pad)
/* batch mode */
- return teco_interrupted != FALSE;
+ return teco_interrupted == TECO_INTERRUPTED;
/*
* NOTE: wgetch() is configured to be nonblocking.
@@ -1791,7 +1741,7 @@ teco_interface_is_interrupted(void)
GINT_TO_POINTER(key));
}
- return teco_interrupted != FALSE;
+ return teco_interrupted == TECO_INTERRUPTED;
}
#endif
@@ -1824,7 +1774,31 @@ teco_interface_refresh(gboolean force)
wnoutrefresh(teco_interface.msg_window);
teco_view_noutrefresh(teco_cmdline.view);
teco_curses_info_popup_noutrefresh(&teco_interface.popup);
+ /*
+ * If hardware cursors (CARETSTYLE_CURSES) are enabled on the
+ * command-line view, make sure that the cursor is left
+ * in the correct position.
+ *
+ * FIXME: This shouldn't be necessary if we refreshed the command-line
+ * view last. Also, if we wanted to support the hardware cursor
+ * in the main view as well, we'd have to handle a possibly
+ * overlappig info popup.
+ */
+ teco_view_update_cursor(teco_cmdline.view);
doupdate();
+
+ /*
+ * Scinterm enables/disables the hardware cursor,
+ * but only if CARETSTYLE_CURSES is used.
+ * Disabling the cursor repeatedly ensures you can change
+ * the caret style interactively.
+ * It also makes sure the cursor is reset after
+ * teco_interface_getch().
+ * Also, window resizes sometimes enable the hardware cursor on
+ * PDCurses/wincon (FIXME).
+ */
+ if (!(teco_view_ssm(teco_cmdline.view, SCI_GETCARETSTYLE, 0, 0) & CARETSTYLE_CURSES))
+ curs_set(0);
}
#if NCURSES_MOUSE_VERSION >= 2
@@ -1882,9 +1856,11 @@ teco_interface_process_mevent(MEVENT *event, GError **error)
else if (event->bstate & BUTTON_NUM(5))
teco_curses_info_popup_scroll(&teco_interface.popup, +2);
- short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0));
- short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0));
- teco_curses_info_popup_show(&teco_interface.popup, teco_color_attr(fg, bg));
+ gshort fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0));
+ gshort bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0));
+ attr_t attr = 0;
+ gshort pair = teco_color_pair(&attr, fg, bg);
+ teco_curses_info_popup_show(&teco_interface.popup, attr, pair);
return TRUE;
}
@@ -2016,7 +1992,8 @@ teco_interface_blocking_getch(void)
gboolean new_mousekey = (teco_ed & TECO_ED_MOUSEKEY) != 0;
if (new_mousekey != old_mousekey) {
old_mousekey = new_mousekey;
- mmask_t mmask = BUTTON_EVENT(PRESSED) | BUTTON_EVENT(RELEASED);
+ mmask_t mmask = BUTTON_EVENT(PRESSED) | BUTTON_EVENT(RELEASED) |
+ BUTTON_SHIFT | BUTTON_CTRL | BUTTON_ALT;
#ifdef __PDCURSES__
/*
* On PDCurses it's crucial NOT to mask for BUTTONX_CLICKED.
@@ -2053,7 +2030,7 @@ teco_interface_blocking_getch(void)
gint key = wgetch(teco_interface.input_pad);
teco_memory_start_limiting();
/* allow asynchronous interruptions on <CTRL/C> */
- teco_interrupted = FALSE;
+ teco_interrupted = TECO_NORMAL;
wtimeout(teco_interface.input_pad, 0);
#if defined(CURSES_TTY) || defined(PDCURSES_WINCON) || defined(NCURSES_WIN32)
noraw(); /* FIXME: necessary because of NCURSES_WIN32 bug */
@@ -2069,6 +2046,31 @@ teco_interface_blocking_getch(void)
return key;
}
+#ifdef __PDCURSES__
+
+/*
+ * Especially PDCurses/WinGUI likes to report two keypresses,
+ * e.g. for CTRL+Shift+6 (CTRL+^).
+ * Make sure we don't filter out AltGr, which may be reported as CTRL+ALT.
+ */
+static inline gboolean
+teco_interface_check_key(gint key)
+{
+ return (PDC_get_key_modifiers() &
+ (PDC_KEY_MODIFIER_CONTROL | PDC_KEY_MODIFIER_ALT)) != PDC_KEY_MODIFIER_CONTROL ||
+ TECO_IS_CTL(key);
+}
+
+#else /* __PDCURSES__ */
+
+static inline gboolean
+teco_interface_check_key(gint key)
+{
+ return TRUE;
+}
+
+#endif
+
/**
* One iteration of the event loop.
*
@@ -2099,11 +2101,6 @@ teco_interface_event_loop_iter(void)
return;
#ifdef KEY_RESIZE
case KEY_RESIZE:
- /*
- * At least on PDCurses/Wincon, the hardware cursor is sometimes
- * reactivated.
- */
- curs_set(0);
teco_interface_resize_all_windows();
break;
#endif
@@ -2179,21 +2176,9 @@ teco_interface_event_loop_iter(void)
* Control keys and keys with printable representation
*/
default:
- if (key > 0xFF)
- /* unhandled function key */
- return;
-
-#ifdef __PDCURSES__
- /*
- * Especially PDCurses/WinGUI likes to report two keypresses,
- * e.g. for CTRL+Shift+6 (CTRL+^).
- * Make sure we don't filter out AltGr, which may be reported as CTRL+ALT.
- */
- if ((PDC_get_key_modifiers() &
- (PDC_KEY_MODIFIER_CONTROL | PDC_KEY_MODIFIER_ALT)) == PDC_KEY_MODIFIER_CONTROL &&
- !TECO_IS_CTL(key))
+ if (key > 0xFF || !teco_interface_check_key(key))
+ /* unhandled function key or bogus key press */
return;
-#endif
/*
* NOTE: There's also wget_wch(), but it requires
@@ -2325,6 +2310,8 @@ teco_interface_cleanup(void)
if (teco_interface.stdout_orig >= 0)
close(teco_interface.stdout_orig);
+ if (teco_interface.color_table)
+ g_hash_table_destroy(teco_interface.color_table);
if (teco_interface.pair_table)
g_hash_table_destroy(teco_interface.pair_table);
diff --git a/src/interface-gtk/gtk-info-popup.c b/src/interface-gtk/gtk-info-popup.c
index 769f772..f2c8dc8 100644
--- a/src/interface-gtk/gtk-info-popup.c
+++ b/src/interface-gtk/gtk-info-popup.c
@@ -30,6 +30,8 @@
#include "gtk-label.h"
#include "gtk-info-popup.h"
+#define TECO_UNNAMED_FILE "(Unnamed)"
+
/*
* FIXME: This is redundant with curses-info-popup.c.
*/
@@ -336,7 +338,7 @@ teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t typ
}
}
- GtkWidget *label = teco_gtk_label_new(name, len);
+ GtkWidget *label = teco_gtk_label_new(name, len, TECO_UNNAMED_FILE);
/*
* Gtk v3.20 changed the CSS element names.
* Adding a style class eases writing a portable fallback.css.
diff --git a/src/interface-gtk/gtk-label.c b/src/interface-gtk/gtk-label.c
index 5052cdc..6e05045 100644
--- a/src/interface-gtk/gtk-label.c
+++ b/src/interface-gtk/gtk-label.c
@@ -32,8 +32,6 @@
#include "gtk-label.h"
-#define TECO_UNNAMED_FILE "(Unnamed)"
-
#define GDK_TO_PANGO_COLOR(X) ((guint16)((X) * G_MAXUINT16))
struct _TecoGtkLabel {
@@ -42,8 +40,10 @@ struct _TecoGtkLabel {
PangoColor fg, bg;
guint16 fg_alpha, bg_alpha;
- /** text backing the label or empty string for "(Unnamed)" buffer */
+ /** text backing the label or empty string for fallback */
teco_string_t string;
+ /** fallback string to render if `string` is empty or NULL */
+ const gchar *fallback;
};
G_DEFINE_TYPE(TecoGtkLabel, teco_gtk_label, GTK_TYPE_LABEL)
@@ -125,11 +125,21 @@ teco_gtk_label_class_init(TecoGtkLabelClass *klass)
static void teco_gtk_label_init(TecoGtkLabel *self) {}
+/**
+ * Create new TECO label widget.
+ *
+ * @param str String to render (can be NULL)
+ * @param len Length of str or negative value for null-terminated strings.
+ * @param fallback Null-terminated fallback string to render
+ * instead of empty strings.
+ * Must be a string constant with global lifetime or NULL.
+ */
GtkWidget *
-teco_gtk_label_new(const gchar *str, gssize len)
+teco_gtk_label_new(const gchar *str, gssize len, const gchar *fallback)
{
TecoGtkLabel *widget = TECO_GTK_LABEL(g_object_new(TECO_TYPE_GTK_LABEL, NULL));
+ widget->fallback = fallback;
teco_gtk_label_set_text(widget, str, len);
return GTK_WIDGET(widget);
@@ -255,8 +265,8 @@ teco_gtk_label_set_text(TecoGtkLabel *self, const gchar *str, gssize len)
teco_string_init(&self->string, str, len < 0 ? strlen(str) : len);
teco_string_t string = self->string;
- if (!string.len) {
- string.data = TECO_UNNAMED_FILE;
+ if (!string.len && self->fallback) {
+ string.data = (gchar *)self->fallback;
string.len = strlen(string.data);
}
diff --git a/src/interface-gtk/gtk-label.h b/src/interface-gtk/gtk-label.h
index ad39c6e..a84608a 100644
--- a/src/interface-gtk/gtk-label.h
+++ b/src/interface-gtk/gtk-label.h
@@ -24,7 +24,7 @@
#define TECO_TYPE_GTK_LABEL teco_gtk_label_get_type()
G_DECLARE_FINAL_TYPE(TecoGtkLabel, teco_gtk_label, TECO, GTK_LABEL, GtkLabel)
-GtkWidget *teco_gtk_label_new(const gchar *str, gssize len);
+GtkWidget *teco_gtk_label_new(const gchar *str, gssize len, const gchar *fallback);
void teco_gtk_label_set_text(TecoGtkLabel *self, const gchar *str, gssize len);
teco_string_t teco_gtk_label_get_text(TecoGtkLabel *self);
diff --git a/src/interface-gtk/interface.c b/src/interface-gtk/interface.c
index 0e1507f..9ef86a1 100644
--- a/src/interface-gtk/interface.c
+++ b/src/interface-gtk/interface.c
@@ -22,6 +22,7 @@
#include <stdlib.h>
#include <string.h>
#include <signal.h>
+#include <unistd.h>
#include <glib.h>
#include <glib/gprintf.h>
@@ -77,7 +78,7 @@ static void teco_interface_popup_clicked_cb(GtkWidget *popup, gchar *str, gulong
gpointer user_data);
static gboolean teco_interface_window_delete_cb(GtkWidget *widget, GdkEventAny *event,
gpointer user_data);
-static gboolean teco_interface_sigterm_handler(gpointer user_data) G_GNUC_UNUSED;
+static gboolean teco_interface_termination_handler(gpointer user_data) G_GNUC_UNUSED;
static gchar teco_interface_get_ansi_key(GdkEventKey *event);
#define TECO_UNNAMED_FILE "(Unnamed)"
@@ -132,10 +133,10 @@ static struct {
} teco_interface;
void
-teco_interface_init(void)
+teco_interface_init(gint argc, gchar **argv)
{
#ifdef G_OS_UNIX
- if (teco_interface.detach) {
+ if (teco_interface.detach && !g_getenv("__SCITECO_DETACHED")) {
/*
* NOTE: There is also daemon() on BSD/Linux,
* but the following should be more portable.
@@ -148,9 +149,29 @@ teco_interface_init(void)
setsid();
- g_freopen("/dev/null", "r", stdin);
- g_freopen("/dev/null", "a+", stdout);
- g_freopen("/dev/null", "a+", stderr);
+ if (isatty(0)) {
+ G_GNUC_UNUSED FILE *stdin_new = g_freopen("/dev/null", "r", stdin);
+ g_assert(stdin_new != NULL);
+ }
+ if (isatty(1)) {
+ G_GNUC_UNUSED FILE *stdout_new = g_freopen("/dev/null", "a+", stdout);
+ g_assert(stdout_new != NULL);
+ }
+ if (isatty(2)) {
+ G_GNUC_UNUSED FILE *stderr_new = g_freopen("/dev/null", "a+", stderr);
+ g_assert(stderr_new != NULL);
+ }
+
+ /*
+ * gtk_get_option_group() already initialized GTK and even though the
+ * display is not yet opened, it's unsafe to continue.
+ * Instead, we re-exec already in the child process.
+ * We cannot easily remove --detach from argv, but still guard against
+ * recursive forks by using an environment variable.
+ */
+ g_setenv("__SCITECO_DETACHED", "1", TRUE);
+ execv(argv[0], argv);
+ g_assert_not_reached();
}
#endif
@@ -202,7 +223,7 @@ teco_interface_init(void)
*/
teco_interface.info_bar_widget = gtk_header_bar_new();
gtk_widget_set_name(teco_interface.info_bar_widget, "sciteco-info-bar");
- teco_interface.info_name_widget = teco_gtk_label_new("", 0);
+ teco_interface.info_name_widget = teco_gtk_label_new(NULL, 0, NULL);
gtk_widget_set_valign(teco_interface.info_name_widget, GTK_ALIGN_CENTER);
/* eases writing portable fallback.css that avoids CSS element names */
gtk_style_context_add_class(gtk_widget_get_style_context(teco_interface.info_name_widget),
@@ -288,7 +309,7 @@ teco_interface_init(void)
gtk_widget_set_name(teco_interface.message_bar_widget, "sciteco-message-bar");
GtkWidget *message_bar_content =
gtk_info_bar_get_content_area(GTK_INFO_BAR(teco_interface.message_bar_widget));
- teco_interface.message_widget = teco_gtk_label_new(NULL, 0);
+ teco_interface.message_widget = teco_gtk_label_new(NULL, 0, NULL);
/* eases writing portable fallback.css that avoids CSS element names */
gtk_style_context_add_class(gtk_widget_get_style_context(teco_interface.message_widget),
"label");
@@ -383,7 +404,7 @@ teco_interface_get_options(void)
return group;
}
-void teco_interface_init_color(guint color, guint32 rgb) {}
+void teco_interface_disable_palette(void) {}
void
teco_interface_msg_literal(teco_msg_t type, const gchar *str, gsize len)
@@ -430,14 +451,15 @@ teco_interface_getch_commit_cb(GtkIMContext *context, gchar *str, gpointer user_
*/
*cp = g_utf8_get_char_validated(str, -1);
g_assert(*cp >= 0);
- gtk_main_quit();
+
+ /* we might be invoked outside of a nested event loop */
+ if (gtk_main_level() > 1)
+ gtk_main_quit();
}
static gboolean
-teco_interface_getch_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data)
+teco_interface_getch_process_event(GdkEvent *event, teco_int_t *cp)
{
- teco_int_t *cp = user_data;
-
g_assert(event->type == GDK_KEY_PRESS);
switch (event->key.keyval) {
@@ -455,19 +477,34 @@ teco_interface_getch_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_
*cp = TECO_CTL_KEY(g_ascii_toupper(*cp));
switch (*cp) {
case TECO_CTL_KEY('C'):
- teco_interrupted = TRUE;
+ teco_interrupted = TECO_INTERRUPTED;
/* fall through */
case TECO_CTL_KEY('D'):
+ /* emulates EOF on stdin */
*cp = -1;
}
break;
}
+ /*
+ * NOTE: The teco_interface_getch_commit_cb() could be called immediately
+ * without returning to the event loop.
+ */
gtk_im_context_filter_keypress(teco_interface.input_method, &event->key);
- return TRUE;
+ return *cp >= 0;
}
- gtk_main_quit();
+ return TRUE;
+}
+
+static gboolean
+teco_interface_getch_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+ teco_int_t *cp = user_data;
+
+ if (teco_interface_getch_process_event(event, cp))
+ gtk_main_quit();
+
return TRUE;
}
@@ -492,6 +529,22 @@ teco_interface_getch(gboolean widechar)
G_CALLBACK(teco_interface_getch_commit_cb), &cp);
/*
+ * The original teco_interface_input_cb() could have already enqueued events
+ * (also between ^T calls).
+ * This must be done after registering teco_interface_getch_commit_cb() already.
+ *
+ * NOTE: Already enqueued mouse events will currently be discarded silently.
+ */
+ for (;;) {
+ g_autoptr(GdkEvent) event = g_queue_pop_head(teco_interface.event_queue);
+ if (!event)
+ break;
+ if (event->type == GDK_KEY_PRESS &&
+ teco_interface_getch_process_event(event, &cp))
+ goto cleanup;
+ }
+
+ /*
* Highlights the first character in the label.
* This mimics what the Curses UI does.
* Is there a better way to signal that we expect input?
@@ -505,6 +558,7 @@ teco_interface_getch(gboolean widechar)
gdk_window_freeze_updates(top_window);
+cleanup:
g_signal_handler_disconnect(teco_interface.input_method, commit_handler);
g_signal_handlers_unblock_by_func(teco_interface.input_method, teco_interface_cmdline_commit_cb, NULL);
g_signal_handler_disconnect(teco_interface.window, key_handler);
@@ -769,7 +823,7 @@ teco_interface_is_interrupted(void)
{
if (!gtk_main_level())
/* batch mode */
- return teco_interrupted != FALSE;
+ return teco_interrupted == TECO_INTERRUPTED;
/*
* By polling only every TECO_POLL_INTERVAL microseconds
@@ -779,11 +833,11 @@ teco_interface_is_interrupted(void)
guint64 now_ts = g_get_monotonic_time();
if (G_LIKELY(last_poll_ts+TECO_POLL_INTERVAL > now_ts))
- return teco_interrupted != FALSE;
+ return teco_interrupted == TECO_INTERRUPTED;
last_poll_ts = now_ts;
gtk_main_iteration_do(FALSE);
- return teco_interrupted != FALSE;
+ return teco_interrupted == TECO_INTERRUPTED;
}
void
@@ -1244,13 +1298,12 @@ teco_interface_event_loop(GError **error)
#endif
/*
- * SIGTERM emulates the "Close" key just like when
- * closing the window if supported by this version of glib.
- * Note that this replaces SciTECO's default SIGTERM handler
- * so it will additionally raise(SIGINT).
+ * SIGTERM and SIGHUP terminate the program, but will
+ * dump recovery files first (if enabled).
*/
#ifdef G_OS_UNIX
- g_unix_signal_add(SIGTERM, teco_interface_sigterm_handler, NULL);
+ g_unix_signal_add(SIGTERM, teco_interface_termination_handler, NULL);
+ g_unix_signal_add(SIGHUP, teco_interface_termination_handler, NULL);
#endif
/* the interval might have been changed in the profile */
@@ -1374,7 +1427,7 @@ teco_interface_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data)
* If the execution thread is currently blocking,
* the key is delivered like an ordinary key press.
*/
- teco_interrupted = TRUE;
+ teco_interrupted = TECO_INTERRUPTED;
else
g_queue_push_tail(teco_interface.event_queue,
gdk_event_copy(event));
@@ -1415,7 +1468,7 @@ teco_interface_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data)
const teco_view_t *last_view = teco_interface_current_view;
sptr_t last_vpos = teco_interface_ssm(SCI_GETFIRSTVISIBLELINE, 0, 0);
- teco_interrupted = FALSE;
+ teco_interrupted = TECO_NORMAL;
switch (event->type) {
case GDK_KEY_PRESS:
teco_interface_handle_key_press(&event->key, &error);
@@ -1432,7 +1485,7 @@ teco_interface_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data)
default:
g_assert_not_reached();
}
- teco_interrupted = FALSE;
+ teco_interrupted = TECO_NORMAL;
teco_interface_update(teco_interface_current_view != last_view);
/* always expand folds, even after mouse clicks */
@@ -1542,16 +1595,23 @@ teco_interface_window_delete_cb(GtkWidget *widget, GdkEventAny *event, gpointer
}
static gboolean
-teco_interface_sigterm_handler(gpointer user_data)
+teco_interface_termination_handler(gpointer user_data)
{
+ teco_interface_msg(TECO_MSG_WARNING, "Received termination signal - dumping recovery files");
+ teco_interrupted = TECO_TERMINATED;
+
/*
- * Similar to window deletion - emulate "close" key press.
+ * This may be an emergency shutdown or another kind of
+ * termination that we cannot ignore, so better dump
+ * recovery files before terminating.
*/
- GtkWidget *widget = teco_interface.window;
+ if (teco_ring_recovery_interval != 0)
+ teco_ring_dump_recovery();
- g_autoptr(GdkEvent) close_event = gdk_event_new(GDK_KEY_PRESS);
- close_event->key.window = gtk_widget_get_parent_window(widget);
- close_event->key.keyval = GDK_KEY_Close;
+ /*
+ * Otherwise we terminate and clean up as if by `-EX`.
+ */
+ gtk_main_quit();
- return teco_interface_input_cb(widget, close_event, NULL);
+ return G_SOURCE_REMOVE;
}
diff --git a/src/interface.h b/src/interface.h
index f196a83..ace0e3d 100644
--- a/src/interface.h
+++ b/src/interface.h
@@ -51,19 +51,20 @@
extern teco_view_t *teco_interface_current_view;
/** @pure */
-void teco_interface_init(void);
+void teco_interface_init(gint argc, gchar **argv);
/** @pure */
GOptionGroup *teco_interface_get_options(void);
/** @pure makes sense only on Curses */
-void teco_interface_init_color(guint color, guint32 rgb);
+void teco_interface_disable_palette(void);
typedef enum {
- TECO_MSG_USER,
+ TECO_MSG_USER = 0,
TECO_MSG_INFO,
TECO_MSG_WARNING,
- TECO_MSG_ERROR
+ TECO_MSG_ERROR,
+ TECO_MSG_MAX = TECO_MSG_ERROR
} teco_msg_t;
extern teco_msg_t teco_interface_msg_level;
diff --git a/src/main.c b/src/main.c
index 4d46817..29ef024 100644
--- a/src/main.c
+++ b/src/main.c
@@ -54,7 +54,7 @@
/*
* Define this to pause the program at the beginning
- * of main() (Windows only).
+ * of main().
* This is a useful hack on Windows, where gdbserver
* sometimes refuses to start SciTECO but attaches
* to a running process just fine.
@@ -66,12 +66,15 @@
teco_int_t teco_ed = TECO_ED_AUTOEOL;
/**
- * Whether there was an asyncronous interruption (usually after pressing CTRL+C).
+ * Whether there was an asynchronous interruption (usually after
+ * pressing CTRL+C) or termination (SIGTERM).
* However you should always use teco_interface_is_interrupted(),
* to check for interruptions because of its side effects.
* This variable is safe to set to TRUE from signal handlers and threads.
+ *
+ * This is a teco_interrupted_t.
*/
-volatile sig_atomic_t teco_interrupted = FALSE;
+volatile sig_atomic_t teco_interrupted = TECO_NORMAL;
/*
* FIXME: Move this into file-utils.c?
@@ -346,7 +349,7 @@ teco_initialize_environment(void)
static void
teco_sigint_handler(int signal)
{
- teco_interrupted = TRUE;
+ teco_interrupted = TECO_INTERRUPTED;
}
#ifdef G_OS_WIN32
@@ -382,12 +385,24 @@ main(int argc, char **argv)
#endif
#ifdef DEBUG_PAUSE
- /* Windows debugging hack (see above) */
- system("pause");
+ getchar();
#endif
+ /*
+ * FIXME: Gracefully handle SIGTERM.
+ * This however would require polling for the flag in Curses
+ * getch() loops.
+ * GTK already catches SIGTERM.
+ */
+#ifdef HAVE_SIGACTION
+ static const struct sigaction sigint = {
+ .sa_handler = teco_sigint_handler,
+ .sa_flags = 0 /* don't auto-restart syscalls */
+ };
+ sigaction(SIGINT, &sigint, NULL);
+#else
signal(SIGINT, teco_sigint_handler);
- signal(SIGTERM, teco_sigint_handler);
+#endif
/*
* Important for Unicode handling in curses and glib.
@@ -445,7 +460,7 @@ main(int argc, char **argv)
*/
teco_qreg_table_init(&teco_qreg_table_globals, TRUE);
- teco_interface_init();
+ teco_interface_init(argc, argv);
/*
* QRegister view must be initialized only now
diff --git a/src/qreg.c b/src/qreg.c
index 4cf92f0..8ef82c0 100644
--- a/src/qreg.c
+++ b/src/qreg.c
@@ -1694,11 +1694,7 @@ teco_machine_qregspec_auto_complete(teco_machine_qregspec_t *ctx, teco_string_t
/* two-letter Q-Reg */
restrict_len = 2;
- /*
- * FIXME: This is not quite right as it will propose even
- * lower case single or two-letter Q-Register names.
- */
- return teco_rb3str_auto_complete(&ctx->result_table->tree, !restrict_len,
+ return teco_rb3str_auto_complete(&ctx->result_table->tree, TRUE,
ctx->name.data, ctx->name.len, restrict_len, insert) &&
ctx->nesting == 1;
}
diff --git a/src/ring.c b/src/ring.c
index 2445718..9feb444 100644
--- a/src/ring.c
+++ b/src/ring.c
@@ -143,7 +143,12 @@ teco_buffer_save(teco_buffer_t *ctx, const gchar *filename, GError **error)
static inline void
teco_buffer_free(teco_buffer_t *ctx)
{
- if (ctx->state > TECO_BUFFER_DIRTY_NO_DUMP) {
+ /*
+ * During graceful, but unexpected shutdowns (SIGTERM etc.),
+ * we must preserve the recovery files.
+ */
+ if (ctx->state > TECO_BUFFER_DIRTY_NO_DUMP &&
+ teco_interrupted != TECO_TERMINATED) {
g_autofree gchar *filename_recovery = teco_buffer_get_recovery(ctx);
g_unlink(filename_recovery);
}
@@ -312,12 +317,13 @@ teco_ring_save_all_dirty_buffers(GError **error)
* Recovery creation interval in seconds or 0 if disabled.
* It's not currently enforced in batch mode.
*/
-guint teco_ring_recovery_interval = 5*60;
+guint teco_ring_recovery_interval = 2*60;
/**
* Create recovery files for all dirty buffers.
*
- * Should be called by the interface every teco_ring_recovery_interval seconds.
+ * Should be called by the interface every teco_ring_recovery_interval seconds
+ * or before graceful terminations (SIGTERM etc.).
* This does not generate or expect undo tokens, so it can be called
* even when idlying.
*/
@@ -731,13 +737,18 @@ teco_state_read_file_done(teco_machine_main_t *ctx, teco_string_t str, GError **
return &teco_state_start;
sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
+ teco_undo_int(teco_ranges[0].from) = teco_interface_bytes2glyphs(pos);
g_autofree gchar *filename = teco_file_expand_path(str.data);
/* FIXME: Add wrapper to interface.h? */
if (!teco_view_load(teco_interface_current_view, filename, FALSE, error))
return NULL;
- if (teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0) != pos) {
+ pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
+ teco_undo_int(teco_ranges[0].to) = teco_interface_bytes2glyphs(pos);
+ teco_undo_guint(teco_ranges_count) = 1;
+
+ if (teco_ranges[0].from != teco_ranges[0].to) {
teco_ring_dirtify();
if (teco_current_doc_must_undo())
diff --git a/src/sciteco.h b/src/sciteco.h
index 16dba69..88078c2 100644
--- a/src/sciteco.h
+++ b/src/sciteco.h
@@ -109,6 +109,12 @@ teco_default_codepage(void)
return teco_ed & TECO_ED_DEFAULT_ANSI ? SC_CHARSET_ANSI : SC_CP_UTF8;
}
+typedef enum {
+ TECO_NORMAL = 0,
+ TECO_INTERRUPTED,
+ TECO_TERMINATED
+} teco_interrupted_t;
+
/* in main.c */
extern volatile sig_atomic_t teco_interrupted;
diff --git a/src/spawn.c b/src/spawn.c
index 61718fd..716bafa 100644
--- a/src/spawn.c
+++ b/src/spawn.c
@@ -869,7 +869,7 @@ teco_spawn_idle_cb(gpointer user_data)
{
if (G_LIKELY(!teco_interface_is_interrupted()))
return G_SOURCE_CONTINUE;
- teco_interrupted = FALSE;
+ teco_interrupted = TECO_NORMAL;
/*
* The first CTRL+C will try to gracefully terminate the process.
diff --git a/src/stdio-commands.c b/src/stdio-commands.c
index abb6566..c2a1c16 100644
--- a/src/stdio-commands.c
+++ b/src/stdio-commands.c
@@ -255,17 +255,42 @@ teco_state_print_string_initial(teco_machine_main_t *ctx, GError **error)
static teco_state_t *
teco_state_print_string_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
- teco_interface_msg_literal(TECO_MSG_USER, str.data, str.len);
+ teco_int_t type;
+
+ if (ctx->flags.mode > TECO_MODE_NORMAL)
+ return &teco_state_start;
+
+ if (!teco_expressions_pop_num_calc(&type, TECO_MSG_USER, error))
+ return NULL;
+ if (type < TECO_MSG_USER || type > TECO_MSG_MAX) {
+ g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Invalid message level specified for <^A>");
+ return NULL;
+ }
+
+ teco_interface_msg_literal(type, str.data, str.len);
return &teco_state_start;
}
/*$ "^A" ":^A" print "print string"
- * ^A<string>^A -- Print string as message
- * @^A/string/
- * :^A<string>^A
+ * [type]^A<string>^A -- Print string as message
+ * [type]@^A/string/
+ * [type]:^A<string>^A
*
- * Print <string> as a message, i.e. in the message line
- * in interactive mode and if possible on the terminal (stdout) as well.
+ * Print <string> as a message with severity <type>, i.e. in the
+ * message line in interactive mode and if possible on the terminal
+ * as well.
+ * The following message levels are supported:
+ * .IP 0: 3
+ * User-level message:
+ * They are written without any prefixes directly to stdout.
+ * This is the default if <type> is omitted.
+ * .IP 1:
+ * Info-level message
+ * .IP 2:
+ * Warnings
+ * .IP 3:
+ * Errors
*
* \fB^A\fP differs from all other commands in the way <string>
* is terminated.
diff --git a/src/string-utils.c b/src/string-utils.c
index e9dd148..d98b6b0 100644
--- a/src/string-utils.c
+++ b/src/string-utils.c
@@ -87,8 +87,10 @@ teco_string_get_coord(const gchar *str, gsize off, guint *pos, guint *line, guin
}
/**
- * Get the length of the prefix common to two strings.
- * Works with UTF-8 and single-byte encodings.
+ * Get the length of the prefix common to two UTF-8 strings.
+ *
+ * The UTF-8 strings must be validated, which should be the case
+ * for help labels and short Q-Register names.
*
* @param a Left string.
* @param b Right string.
@@ -103,8 +105,8 @@ teco_string_diff(teco_string_t a, const gchar *b, gsize b_len)
gsize len = 0;
while (len < a.len && len < b_len &&
- a.data[len] == b[len])
- len++;
+ g_utf8_get_char(a.data+len) == g_utf8_get_char(b+len))
+ len = g_utf8_next_char(b+len) - b;
return len;
}
diff --git a/src/view.c b/src/view.c
index a522d1c..620c80a 100644
--- a/src/view.c
+++ b/src/view.c
@@ -112,9 +112,9 @@ teco_view_setup(teco_view_t *ctx)
teco_view_ssm(ctx, SCI_SETCARETFORE, 0xFFFFFF, 0);
teco_view_ssm(ctx, SCI_SETSELFORE, TRUE, 0x000000);
- teco_view_ssm(ctx, SCI_SETSELBACK, TRUE, 0xFFFFFF);
+ teco_view_ssm(ctx, SCI_SETSELBACK, TRUE, 0xC0C0C0);
- teco_view_ssm(ctx, SCI_STYLESETFORE, STYLE_DEFAULT, 0xFFFFFF);
+ teco_view_ssm(ctx, SCI_STYLESETFORE, STYLE_DEFAULT, 0xC0C0C0);
teco_view_ssm(ctx, SCI_STYLESETBACK, STYLE_DEFAULT, 0x000000);
teco_view_ssm(ctx, SCI_STYLESETFONT, STYLE_DEFAULT, (sptr_t)"Monospace");
teco_view_ssm(ctx, SCI_STYLECLEARALL, 0, 0);
@@ -234,8 +234,8 @@ teco_view_load_from_channel(teco_view_t *ctx, GIOChannel *channel,
*/
guint cp = teco_view_get_codepage(ctx);
if (cp == SC_CP_UTF8)
- teco_interface_ssm(SCI_RELEASELINECHARACTERINDEX,
- SC_LINECHARACTERINDEX_UTF32, 0);
+ teco_view_ssm(ctx, SCI_RELEASELINECHARACTERINDEX,
+ SC_LINECHARACTERINDEX_UTF32, 0);
teco_view_ssm(ctx, SCI_BEGINUNDOACTION, 0, 0);
if (clear) {
@@ -314,8 +314,8 @@ cleanup:
teco_view_ssm(ctx, SCI_ENDUNDOACTION, 0, 0);
if (cp == SC_CP_UTF8)
- teco_interface_ssm(SCI_ALLOCATELINECHARACTERINDEX,
- SC_LINECHARACTERINDEX_UTF32, 0);
+ teco_view_ssm(ctx, SCI_ALLOCATELINECHARACTERINDEX,
+ SC_LINECHARACTERINDEX_UTF32, 0);
return ret;
}