aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/core-commands.c4
-rw-r--r--src/error.h2
-rw-r--r--src/interface-curses/interface.c54
-rw-r--r--src/interface-gtk/gtk-label.c24
-rw-r--r--src/interface-gtk/gtk-label.h2
-rw-r--r--src/interface-gtk/interface.c98
-rw-r--r--src/interface.c29
-rw-r--r--src/interface.h6
-rw-r--r--src/stdio-commands.c82
-rw-r--r--src/stdio-commands.h8
-rw-r--r--tests/testsuite.at7
11 files changed, 308 insertions, 8 deletions
diff --git a/src/core-commands.c b/src/core-commands.c
index 015865d..213d9ed 100644
--- a/src/core-commands.c
+++ b/src/core-commands.c
@@ -1628,7 +1628,9 @@ teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
.modifier_colon = 1},
['X'] = {&teco_state_start, teco_state_control_search_mode},
['Y'] = {&teco_state_start, teco_state_control_last_range},
- ['S'] = {&teco_state_start, teco_state_control_last_length}
+ ['S'] = {&teco_state_start, teco_state_control_last_length},
+ ['T'] = {&teco_state_start, teco_state_control_typeout,
+ .modifier_colon = 1}
};
/*
diff --git a/src/error.h b/src/error.h
index 04bb988..b4f92ad 100644
--- a/src/error.h
+++ b/src/error.h
@@ -95,7 +95,7 @@ static inline void
teco_error_codepoint_set(GError **error, const gchar *cmd)
{
g_set_error(error, TECO_ERROR, TECO_ERROR_CODEPOINT,
- "Invalid Unicode codepoint for <%s>", cmd);
+ "Invalid codepoint for <%s>", cmd);
}
static inline void
diff --git a/src/interface-curses/interface.c b/src/interface-curses/interface.c
index e461b3c..d92eade 100644
--- a/src/interface-curses/interface.c
+++ b/src/interface-curses/interface.c
@@ -134,6 +134,9 @@ teco_console_ctrl_handler(DWORD type)
static gint teco_xterm_version(void) G_GNUC_UNUSED;
+static void teco_interface_refresh(void);
+static gint teco_interface_blocking_getch(void);
+
#define UNNAMED_FILE "(Unnamed)"
/**
@@ -915,6 +918,50 @@ teco_interface_msg_clear(void)
teco_curses_clrtobot(teco_interface.msg_window);
}
+teco_int_t
+teco_interface_getch(gboolean widechar)
+{
+ if (!teco_interface.cmdline_window) /* batch mode */
+ return teco_interface_stdio_getch(widechar);
+
+ teco_interface_refresh();
+
+ /*
+ * Signal that we accept input by drawing a real cursor in the message bar.
+ */
+ wmove(teco_interface.msg_window, 0, 0);
+ curs_set(1);
+ wrefresh(teco_interface.msg_window);
+
+ 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;
+
+ if (!widechar || !cp)
+ break;
+
+ /* doesn't work as expected when passed a null byte */
+ buf[i] = cp;
+ cp = g_utf8_get_char_validated(buf, ++i);
+ if (i >= sizeof(buf) || cp != -2)
+ i = 0;
+ } while (cp < 0);
+
+ curs_set(0);
+ return cp;
+}
+
void
teco_interface_show_view(teco_view_t *view)
{
@@ -1933,6 +1980,9 @@ teco_interface_getmouse(GError **error)
static gint
teco_interface_blocking_getch(void)
{
+ if (!g_queue_is_empty(teco_interface.input_queue))
+ return GPOINTER_TO_INT(g_queue_pop_head(teco_interface.input_queue));
+
#if NCURSES_MOUSE_VERSION >= 2
#ifdef __PDCURSES__
/*
@@ -1991,9 +2041,7 @@ teco_interface_event_loop_iter(void)
GError **error = &teco_interface.event_loop_error;
- gint key = g_queue_is_empty(teco_interface.input_queue)
- ? teco_interface_blocking_getch()
- : GPOINTER_TO_INT(g_queue_pop_head(teco_interface.input_queue));
+ gint key = teco_interface_blocking_getch();
const teco_view_t *last_view = teco_interface_current_view;
sptr_t last_vpos = teco_interface_ssm(SCI_GETFIRSTVISIBLELINE, 0, 0);
diff --git a/src/interface-gtk/gtk-label.c b/src/interface-gtk/gtk-label.c
index ef370a2..9fc9e76 100644
--- a/src/interface-gtk/gtk-label.c
+++ b/src/interface-gtk/gtk-label.c
@@ -275,3 +275,27 @@ teco_gtk_label_get_text(TecoGtkLabel *self)
{
return &self->string;
}
+
+/**
+ * Signal that a keypress is expected (after executing ^T)
+ * by printing the first character in reverse.
+ *
+ * @fixme This mimics the current Curses implementation.
+ * Perhaps better show an icon?
+ */
+void
+teco_gtk_label_highlight_getch(TecoGtkLabel *self)
+{
+ const gchar *plaintext = gtk_label_get_text(GTK_LABEL(self));
+ g_assert(plaintext != NULL);
+ if (!*plaintext || !strcmp(plaintext, "\u258C")) {
+ gtk_label_set_text(GTK_LABEL(self), "\u258C");
+ } else {
+ PangoAttrList *attribs = gtk_label_get_attributes(GTK_LABEL(self));
+ teco_gtk_label_add_highlight_attribs(attribs,
+ &self->fg, self->fg_alpha,
+ &self->bg, self->bg_alpha,
+ 0, 1);
+ gtk_label_set_attributes(GTK_LABEL(self), attribs);
+ }
+}
diff --git a/src/interface-gtk/gtk-label.h b/src/interface-gtk/gtk-label.h
index c52d073..3cd4cb9 100644
--- a/src/interface-gtk/gtk-label.h
+++ b/src/interface-gtk/gtk-label.h
@@ -33,3 +33,5 @@ void teco_gtk_label_parse_string(const gchar *str, gssize len,
PangoColor *fg, guint16 fg_alpha,
PangoColor *bg, guint16 bg_alpha,
PangoAttrList **attribs, gchar **text);
+
+void teco_gtk_label_highlight_getch(TecoGtkLabel *self);
diff --git a/src/interface-gtk/interface.c b/src/interface-gtk/interface.c
index 544ff22..ae1dd74 100644
--- a/src/interface-gtk/interface.c
+++ b/src/interface-gtk/interface.c
@@ -75,6 +75,7 @@ static void teco_interface_popup_clicked_cb(GtkWidget *popup, gchar *str, gulong
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 gchar teco_interface_get_ansi_key(GdkEventKey *event);
/**
* Interval between polling for keypresses.
@@ -423,6 +424,103 @@ teco_interface_msg_clear(void)
teco_gtk_label_set_text(TECO_GTK_LABEL(teco_interface.message_widget), "", 0);
}
+static void
+teco_interface_getch_commit_cb(GtkIMContext *context, gchar *str, gpointer user_data)
+{
+ teco_int_t *cp = user_data;
+
+ /*
+ * FIXME: What if str contains several characters?
+ */
+ *cp = g_utf8_get_char_validated(str, -1);
+ g_assert(*cp >= 0);
+ gtk_main_quit();
+}
+
+/*
+ * FIXME: Redundancies with teco_interface_handle_keypress()
+ * FIXME: Report function keys
+ */
+static gboolean
+teco_interface_getch_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+ teco_int_t *cp = user_data;
+
+ g_assert(event->type == GDK_KEY_PRESS);
+
+ switch (event->key.keyval) {
+ case GDK_KEY_Escape: *cp = '\e'; break;
+ case GDK_KEY_BackSpace: *cp = TECO_CTL_KEY('H'); break;
+ case GDK_KEY_Tab: *cp = '\t'; break;
+ case GDK_KEY_Return: *cp = '\n'; break;
+ default:
+ /*
+ * NOTE: Alt-Gr key-combinations are sometimes reported as
+ * Ctrl+Alt, so we filter those out.
+ */
+ if ((event->key.state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) == GDK_CONTROL_MASK &&
+ (*cp = teco_interface_get_ansi_key(&event->key))) {
+ *cp = TECO_CTL_KEY(g_ascii_toupper(*cp));
+ switch (*cp) {
+ case TECO_CTL_KEY('C'):
+ teco_interrupted = TRUE;
+ /* fall through */
+ case TECO_CTL_KEY('D'):
+ *cp = -1;
+ }
+ break;
+ }
+
+ gtk_im_context_filter_keypress(teco_interface.input_method, &event->key);
+ return TRUE;
+ }
+
+ gtk_main_quit();
+ return TRUE;
+}
+
+teco_int_t
+teco_interface_getch(gboolean widechar)
+{
+ if (!gtk_main_level())
+ /* batch mode */
+ return teco_interface_stdio_getch(widechar);
+
+ teco_int_t cp = -1;
+ gulong key_handler, commit_handler;
+
+ /* temporarily replace the "key-press-event" and "commit" handlers */
+ g_signal_handlers_block_by_func(teco_interface.window,
+ teco_interface_input_cb, NULL);
+ key_handler = g_signal_connect(teco_interface.window, "key-press-event",
+ G_CALLBACK(teco_interface_getch_input_cb), &cp);
+ g_signal_handlers_block_by_func(teco_interface.input_method,
+ teco_interface_cmdline_commit_cb, NULL);
+ commit_handler = g_signal_connect(teco_interface.input_method, "commit",
+ G_CALLBACK(teco_interface_getch_commit_cb), &cp);
+
+ /*
+ * 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?
+ */
+ teco_gtk_label_highlight_getch(TECO_GTK_LABEL(teco_interface.message_widget));
+
+ GdkWindow *top_window = gdk_window_get_toplevel(gtk_widget_get_window(teco_interface.window));
+ gdk_window_thaw_updates(top_window);
+
+ gtk_main();
+
+ gdk_window_freeze_updates(top_window);
+
+ 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);
+ g_signal_handlers_unblock_by_func(teco_interface.window, teco_interface_input_cb, NULL);
+
+ return cp;
+}
+
void
teco_interface_show_view(teco_view_t *view)
{
diff --git a/src/interface.c b/src/interface.c
index cf8f1ca..c3103fd 100644
--- a/src/interface.c
+++ b/src/interface.c
@@ -130,3 +130,32 @@ teco_interface_stdio_msg(teco_msg_t type, const gchar *str, gsize len)
break;
}
}
+
+/**
+ * Get character from stdin.
+ *
+ * @param widechar If TRUE reads one glyph encoded in UTF-8.
+ * If FALSE, returns exactly one byte.
+ * @return Codepoint or -1 in case of EOF.
+ */
+teco_int_t
+teco_interface_stdio_getch(gboolean widechar)
+{
+ gchar buf[4];
+ gint i = 0;
+ gint32 cp;
+
+ do {
+ if (G_UNLIKELY(fread(buf+i, 1, 1, stdin) < 1))
+ return -1; /* EOF */
+ if (!widechar || !buf[i])
+ return (guchar)buf[i];
+
+ /* doesn't work as expected when passed a null byte */
+ cp = g_utf8_get_char_validated(buf, ++i);
+ if (i >= sizeof(buf) || cp != -2)
+ i = 0;
+ } while (cp < 0);
+
+ return cp;
+}
diff --git a/src/interface.h b/src/interface.h
index 0e03a98..f22c023 100644
--- a/src/interface.h
+++ b/src/interface.h
@@ -66,6 +66,9 @@ void teco_interface_msg_literal(teco_msg_t type, const gchar *str, gsize len);
void teco_interface_msg(teco_msg_t type, const gchar *fmt, ...) G_GNUC_PRINTF(2, 3);
/** @pure */
+teco_int_t teco_interface_getch(gboolean widechar);
+
+/** @pure */
void teco_interface_msg_clear(void);
/** @pure */
@@ -173,6 +176,9 @@ gboolean teco_interface_event_loop(GError **error);
/** @protected */
void teco_interface_stdio_msg(teco_msg_t type, const gchar *str, gsize len);
+/** @protected */
+teco_int_t teco_interface_stdio_getch(gboolean widechar);
+
/** @pure */
void teco_interface_cleanup(void);
diff --git a/src/stdio-commands.c b/src/stdio-commands.c
index 2d5da62..3a1b320 100644
--- a/src/stdio-commands.c
+++ b/src/stdio-commands.c
@@ -292,7 +292,7 @@ TECO_DEFINE_STATE_EXPECTSTRING(teco_state_print_string,
*
* Type out the next or previous number of <lines> from the buffer
* as a message, i.e. in the message line in interactive mode
- * and if possible on the terminal (stdout) as well..
+ * and if possible on the terminal (stdout) as well.
* If <lines> is omitted, the sign prefix is implied.
* If two arguments are specified, the characters beginning
* at position <from> up to the character at position <to>
@@ -320,3 +320,83 @@ teco_state_start_typeout(teco_machine_main_t *ctx, GError **error)
const gchar *str = (const gchar *)teco_interface_ssm(SCI_GETRANGEPOINTER, from, len);
teco_interface_msg_literal(TECO_MSG_USER, str, len);
}
+
+/*$ "^T" ":^T" "typeout glyph" "get char"
+ * <c1,c2,...>^T -- Type out the numeric arguments as a message or get character from user
+ * <c1,c2,...>:^T
+ * ^T -> codepoint
+ * :^T -> byte
+ *
+ * Types out characters for all the values
+ * on the argument stack (interpreted as codepoints) as messages,
+ * i.e. in the message line in interactive mode
+ * and if possible on the terminal (stdout) as well.
+ * It does so in the order of the arguments, i.e.
+ * <c1> is inserted before <c2>, ecetera.
+ * By default the codepoints are expected to be in the default
+ * codepage, but you can force raw ANSI encoding (for arbitrary
+ * bytes) by colon-modifying the command.
+ *
+ * When called without any argument, \fB^T\fP reads a key from the
+ * user (or from stdin) and returns the corresponding codepoint.
+ * If the default encoding is UTF-8, this will not work
+ * for function keys.
+ * If the default encoding is raw ANSI or if the command is
+ * colon-modified, \fB^T\fP returns raw bytes.
+ * When run in batch mode, this will return whatever byte is
+ * delivered by the attached terminal.
+ * In case stdin is closed, -1 is returned.
+ * In interactive mode, pressing CTRL+D or CTRL+C will also
+ * return -1.
+ */
+void
+teco_state_control_typeout(teco_machine_main_t *ctx, GError **error)
+{
+ if (!teco_expressions_eval(FALSE, error))
+ return;
+
+ gboolean utf8 = !teco_machine_main_eval_colon(ctx) &&
+ teco_default_codepage() == SC_CP_UTF8;
+
+ guint args = teco_expressions_args();
+ if (!args) {
+ teco_expressions_push(teco_interface_getch(utf8));
+ return;
+ }
+
+ if (!utf8) {
+ /* assume raw ANSI byte output */
+ g_autofree gchar *buf = g_malloc(args);
+ gchar *p = buf+args;
+
+ for (gint i = 0; i < args; i++) {
+ teco_int_t chr = teco_expressions_pop_num(0);
+ if (chr < 0 || chr > 0xFF) {
+ teco_error_codepoint_set(error, "^T");
+ return;
+ }
+ *--p = chr;
+ }
+
+ teco_interface_msg_literal(TECO_MSG_USER, p, args);
+ return;
+ }
+
+ /* 4 bytes should be enough for UTF-8, but we better follow the documentation */
+ g_autofree gchar *buf = g_malloc(args*6);
+ gchar *p = buf;
+
+ for (gint i = args; i > 0; i--) {
+ teco_int_t chr = teco_expressions_peek_num(i-1);
+ if (chr < 0 || !g_unichar_validate(chr)) {
+ teco_error_codepoint_set(error, "^T");
+ return;
+ }
+ p += g_unichar_to_utf8(chr, p);
+ }
+ /* we pop only now since we had to peek in reverse order */
+ for (gint i = 0; i < args; i++)
+ teco_expressions_pop_num(0);
+
+ teco_interface_msg_literal(TECO_MSG_USER, buf, p-buf);
+}
diff --git a/src/stdio-commands.h b/src/stdio-commands.h
index b6870e7..985d1ac 100644
--- a/src/stdio-commands.h
+++ b/src/stdio-commands.h
@@ -20,7 +20,11 @@
#include "parser.h"
+void teco_state_start_typeout(teco_machine_main_t *ctx, GError **error);
+void teco_state_control_typeout(teco_machine_main_t *ctx, GError **error);
+
+/*
+ * Command states
+ */
TECO_DECLARE_STATE(teco_state_print_decimal);
TECO_DECLARE_STATE(teco_state_print_string);
-
-void teco_state_start_typeout(teco_machine_main_t *ctx, GError **error);
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 9f84a23..a5390d6 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -204,6 +204,13 @@ TE_CHECK([[@EB'test.txt' 2T]], 0, stdout, ignore)
AT_FAIL_IF([test `$GREP -v "^Info:" stdout | wc -l` -ne 2], 0, ignore, ignore)
AT_CLEANUP
+AT_SETUP([Type out and get char])
+TE_CHECK([[1058,1045,1057,1058^T]], 0, stdout, ignore)
+AT_FAIL_IF([test "`$GREP -v "^Info:" stdout`" != "ТЕСТ"], 0, ignore, ignore)
+AT_CHECK([[echo -n "ТЕСТ" | $SCITECO -e '<^TUa Qa:; Qa=>']], 0, stdout, ignore)
+AT_FAIL_IF([test `$GREP -v "^Info:" stdout | wc -l` -ne 4], 0, ignore, ignore)
+AT_CLEANUP
+
AT_SETUP([Convert between line and glyph positions])
TE_CHECK([[@I/1^J2^J3/J 2^QC :^Q-3"N(0/0)']], 0, ignore, ignore)
AT_CLEANUP