aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--TODO2
-rw-r--r--src/core-commands.c2
-rw-r--r--src/interface-curses/interface.c25
-rw-r--r--src/interface-gtk/interface.c24
-rw-r--r--src/interface.c49
-rw-r--r--src/interface.h15
-rw-r--r--src/parser.c4
-rw-r--r--src/qreg.h2
-rw-r--r--src/stdio-commands.c52
-rw-r--r--src/stdio-commands.h1
-rw-r--r--tests/testsuite.at6
11 files changed, 115 insertions, 67 deletions
diff --git a/TODO b/TODO
index 4ab28df..66f2476 100644
--- a/TODO
+++ b/TODO
@@ -339,7 +339,7 @@ Features:
just like in Video TECO.
The DEC behavior could be achieved by always searching till the end
of the buffer, but excluding all matches beyond the target range.
- * ^A, :Gq, T, ^T and stdio in general
+ * :Gq, T, ^T and stdio in general
* :=, :==, :=== to inhibit the linefeed.
* ^W was an immediate action command to repaint the screen.
This could be a regular command to allow refreshing in long loops.
diff --git a/src/core-commands.c b/src/core-commands.c
index 04cdf94..3aaf816 100644
--- a/src/core-commands.c
+++ b/src/core-commands.c
@@ -1546,6 +1546,8 @@ teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
['^'] = {&teco_state_ascii},
['['] = {&teco_state_escape},
['C'] = {&teco_state_ctlc},
+ ['A'] = {&teco_state_print_string,
+ .modifier_at = TRUE},
/*
* Additional numeric operations
diff --git a/src/interface-curses/interface.c b/src/interface-curses/interface.c
index 1d7f4ed..e461b3c 100644
--- a/src/interface-curses/interface.c
+++ b/src/interface-curses/interface.c
@@ -22,7 +22,6 @@
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
-#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
@@ -860,10 +859,10 @@ teco_interface_resize_all_windows(void)
}
void
-teco_interface_vmsg(teco_msg_t type, const gchar *fmt, va_list ap)
+teco_interface_msg_literal(teco_msg_t type, const gchar *str, gsize len)
{
if (!teco_interface.cmdline_window) { /* batch mode */
- teco_interface_stdio_vmsg(type, fmt, ap);
+ teco_interface_stdio_msg(type, str, len);
return;
}
@@ -872,10 +871,7 @@ teco_interface_vmsg(teco_msg_t type, const gchar *fmt, va_list ap)
* even in interactive mode.
*/
#if defined(PDCURSES_GUI) || defined(CURSES_TTY) || defined(NCURSES_WIN32)
- va_list aq;
- va_copy(aq, ap);
- teco_interface_stdio_vmsg(type, fmt, aq);
- va_end(aq);
+ teco_interface_stdio_msg(type, str, len);
#endif
short fg, bg;
@@ -899,14 +895,10 @@ teco_interface_vmsg(teco_msg_t type, const gchar *fmt, va_list ap)
break;
}
- /*
- * NOTE: This is safe since we don't have to cancel out any A_REVERSE,
- * that could be set in the background attributes.
- */
wmove(teco_interface.msg_window, 0, 0);
- wbkgdset(teco_interface.msg_window, teco_color_attr(fg, bg));
- vw_printw(teco_interface.msg_window, fmt, ap);
- wclrtoeol(teco_interface.msg_window);
+ wattrset(teco_interface.msg_window, teco_color_attr(fg, bg));
+ teco_curses_format_str(teco_interface.msg_window, str, len, -1);
+ teco_curses_clrtobot(teco_interface.msg_window);
}
void
@@ -918,8 +910,9 @@ teco_interface_msg_clear(void)
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(teco_interface.msg_window, teco_color_attr(fg, bg));
- werase(teco_interface.msg_window);
+ wmove(teco_interface.msg_window, 0, 0);
+ wattrset(teco_interface.msg_window, teco_color_attr(fg, bg));
+ teco_curses_clrtobot(teco_interface.msg_window);
}
void
diff --git a/src/interface-gtk/interface.c b/src/interface-gtk/interface.c
index 973225e..544ff22 100644
--- a/src/interface-gtk/interface.c
+++ b/src/interface-gtk/interface.c
@@ -19,7 +19,6 @@
#include "config.h"
#endif
-#include <stdarg.h>
#include <string.h>
#include <signal.h>
@@ -193,7 +192,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(NULL, 0);
+ teco_interface.info_name_widget = teco_gtk_label_new("", 0);
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),
@@ -279,8 +278,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));
- /* NOTE: Messages are always pre-canonicalized */
- teco_interface.message_widget = gtk_label_new(NULL);
+ teco_interface.message_widget = teco_gtk_label_new(NULL, 0);
/* 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");
@@ -391,7 +389,7 @@ teco_interface_get_options(void)
void teco_interface_init_color(guint color, guint32 rgb) {}
void
-teco_interface_vmsg(teco_msg_t type, const gchar *fmt, va_list ap)
+teco_interface_msg_literal(teco_msg_t type, const gchar *str, gsize len)
{
/*
* The message types are chosen such that there is a CSS class
@@ -407,21 +405,11 @@ teco_interface_vmsg(teco_msg_t type, const gchar *fmt, va_list ap)
g_assert(type < G_N_ELEMENTS(type2gtk));
- gchar buf[256];
-
- /*
- * stdio_vmsg() leaves `ap` undefined and we are expected
- * to do the same and behave like vprintf().
- */
- va_list aq;
- va_copy(aq, ap);
- teco_interface_stdio_vmsg(type, fmt, ap);
- g_vsnprintf(buf, sizeof(buf), fmt, aq);
- va_end(aq);
+ teco_interface_stdio_msg(type, str, len);
gtk_info_bar_set_message_type(GTK_INFO_BAR(teco_interface.message_bar_widget),
type2gtk[type]);
- gtk_label_set_text(GTK_LABEL(teco_interface.message_widget), buf);
+ teco_gtk_label_set_text(TECO_GTK_LABEL(teco_interface.message_widget), str, len);
if (type == TECO_MSG_ERROR)
gtk_widget_error_bell(teco_interface.window);
@@ -432,7 +420,7 @@ teco_interface_msg_clear(void)
{
gtk_info_bar_set_message_type(GTK_INFO_BAR(teco_interface.message_bar_widget),
GTK_MESSAGE_QUESTION);
- gtk_label_set_text(GTK_LABEL(teco_interface.message_widget), "");
+ teco_gtk_label_set_text(TECO_GTK_LABEL(teco_interface.message_widget), "", 0);
}
void
diff --git a/src/interface.c b/src/interface.c
index 9ec1bed..cf8f1ca 100644
--- a/src/interface.c
+++ b/src/interface.c
@@ -81,35 +81,52 @@ teco_interface_undo_set_clipboard(const gchar *name, gchar *str, gsize len)
}
}
+void
+teco_interface_msg(teco_msg_t type, const gchar *fmt, ...)
+{
+ gchar buf[512];
+ va_list ap;
+
+ va_start(ap, fmt);
+ /*
+ * If the buffer could ever be exceeded, perhaps
+ * use g_strdup_vprintf() instead.
+ */
+ gint len = g_vsnprintf(buf, sizeof(buf), fmt, ap);
+ g_assert(0 <= len && len < sizeof(buf));
+ va_end(ap);
+
+ teco_interface_msg_literal(type, buf, len);
+}
+
/**
- * Print a message to the appropriate stdio streams.
+ * Print a raw message to the appropriate stdio streams.
*
- * This method has similar semantics to `vprintf`, i.e.
- * it leaves `ap` undefined. Therefore to pass the format
- * string and arguments to another `vprintf`-like function,
- * you have to copy the arguments via `va_copy`.
+ * This deliberately does not echo (i.e. escape non-printable characters)
+ * the string. Either they are supposed to be written verbatim
+ * (TECO_MSG_USER) or are already echoed.
+ * Everything higher than TECO_MSG_USER is also terminated by LF.
+ *
+ * @fixme TECO_MSG_USER could always be flushed.
+ * This however makes the message disappear, though.
+ * We might also want to put flushing under control of the language instead.
*/
void
-teco_interface_stdio_vmsg(teco_msg_t type, const gchar *fmt, va_list ap)
+teco_interface_stdio_msg(teco_msg_t type, const gchar *str, gsize len)
{
- FILE *stream = stdout;
-
switch (type) {
case TECO_MSG_USER:
+ fwrite(str, 1, len, stdout);
+ //fflush(stdout);
break;
case TECO_MSG_INFO:
- fputs("Info: ", stream);
+ g_fprintf(stdout, "Info: %.*s\n", (gint)len, str);
break;
case TECO_MSG_WARNING:
- stream = stderr;
- fputs("Warning: ", stream);
+ g_fprintf(stderr, "Warning: %.*s\n", (gint)len, str);
break;
case TECO_MSG_ERROR:
- stream = stderr;
- fputs("Error: ", stream);
+ g_fprintf(stderr, "Error: %.*s\n", (gint)len, str);
break;
}
-
- g_vfprintf(stream, fmt, ap);
- fputc('\n', stream);
}
diff --git a/src/interface.h b/src/interface.h
index b1ad2b8..0e03a98 100644
--- a/src/interface.h
+++ b/src/interface.h
@@ -16,7 +16,6 @@
*/
#pragma once
-#include <stdarg.h>
#include <signal.h>
#include <glib.h>
@@ -62,17 +61,9 @@ typedef enum {
} teco_msg_t;
/** @pure */
-void teco_interface_vmsg(teco_msg_t type, const gchar *fmt, va_list ap);
+void teco_interface_msg_literal(teco_msg_t type, const gchar *str, gsize len);
-static inline void G_GNUC_PRINTF(2, 3)
-teco_interface_msg(teco_msg_t type, const gchar *fmt, ...)
-{
- va_list ap;
-
- va_start(ap, fmt);
- teco_interface_vmsg(type, fmt, ap);
- va_end(ap);
-}
+void teco_interface_msg(teco_msg_t type, const gchar *fmt, ...) G_GNUC_PRINTF(2, 3);
/** @pure */
void teco_interface_msg_clear(void);
@@ -180,7 +171,7 @@ gboolean teco_interface_event_loop(GError **error);
* Interfacing to the external SciTECO world
*/
/** @protected */
-void teco_interface_stdio_vmsg(teco_msg_t type, const gchar *fmt, va_list ap);
+void teco_interface_stdio_msg(teco_msg_t type, const gchar *str, gsize len);
/** @pure */
void teco_interface_cleanup(void);
diff --git a/src/parser.c b/src/parser.c
index 33d2e7f..347c1a6 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -1047,8 +1047,12 @@ teco_state_expectstring_input(teco_machine_main_t *ctx, gunichar chr, GError **e
* to the ANSI range (teco_ascii_toupper())?
* This would be faster than case folding each and every character
* of a string argument to check against the escape char.
+ *
+ * FIXME: This has undesired effects if you try to use one of
+ * of these characters with multiple string arguments.
*/
switch (ctx->expectstring.machine.escape_char) {
+ case TECO_CTL_KEY('A'):
case '\e':
case '{':
if (ctx->parent.must_undo)
diff --git a/src/qreg.h b/src/qreg.h
index 27a3653..2b970aa 100644
--- a/src/qreg.h
+++ b/src/qreg.h
@@ -45,6 +45,8 @@ extern teco_view_t *teco_qreg_view;
* teco_qreg_set_integer_t set_integer;
* ...
* teco_qreg_set_integer(qreg, 23, error);
+ *
+ * But this probably won't work. Perhaps use the X-macro pattern.
*/
typedef const struct {
gboolean (*set_integer)(teco_qreg_t *qreg, teco_int_t value, GError **error);
diff --git a/src/stdio-commands.c b/src/stdio-commands.c
index 263425c..09f5e2a 100644
--- a/src/stdio-commands.c
+++ b/src/stdio-commands.c
@@ -23,6 +23,7 @@
#include "sciteco.h"
#include "parser.h"
#include "error.h"
+#include "undo.h"
#include "expressions.h"
#include "interface.h"
#include "cmdline.h"
@@ -49,16 +50,16 @@ teco_print(guint radix, GError **error)
* also to allow output without trailing LF.
* Perhaps repurpose teco_expressions_format().
*/
- const gchar *fmt = "%" TECO_INT_MODIFIER "d";
+ const gchar *fmt = "%" TECO_INT_MODIFIER "d\n";
switch (radix) {
- case 8: fmt = "%" TECO_INT_MODIFIER "o"; break;
- case 16: fmt = "%" TECO_INT_MODIFIER "X"; break;
+ case 8: fmt = "%" TECO_INT_MODIFIER "o\n"; break;
+ case 16: fmt = "%" TECO_INT_MODIFIER "X\n"; break;
}
teco_interface_msg(TECO_MSG_USER, fmt, teco_expressions_peek_num(0));
return TRUE;
}
-/*$ "=" "==" "===" print
+/*$ "=" "==" "===" "print number"
* <n>= -- Print integer as message
* <n>==
* <n>===
@@ -195,3 +196,46 @@ TECO_DEFINE_STATE_START(teco_state_print_octal,
.end_of_macro_cb = (teco_state_end_of_macro_cb_t)
teco_state_print_octal_end_of_macro
);
+
+static gboolean
+teco_state_print_string_initial(teco_machine_main_t *ctx, GError **error)
+{
+ /*
+ * ^A differs from all other string-taking commands in having
+ * a default ^A escape char.
+ */
+ if (ctx->parent.must_undo)
+ teco_undo_gunichar(ctx->expectstring.machine.escape_char);
+ ctx->expectstring.machine.escape_char = TECO_CTL_KEY('A');
+
+ /* chain to the default initial_cb */
+ return teco_state_expectstring_initial(ctx, error);
+}
+
+static teco_state_t *
+teco_state_print_string_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ teco_interface_msg_literal(TECO_MSG_USER, str->data, str->len);
+ return &teco_state_start;
+}
+
+/*$ "^A" print
+ * ^A<string>^A -- Print string as message
+ * @^A/string/
+ *
+ * Print <string> as a message, i.e. in the message line
+ * in interactive mode and if possible on the terminal (stdout) as well.
+ *
+ * \fB^A\fP differs from all other commands in the way <string>
+ * is terminated.
+ * It is terminated by ^A (CTRL+A, ASCII 1) by default.
+ * While the initial \fB^A\fP can be written with upcarets,
+ * the terminating ^A must always be ASCII 1.
+ * You can however overwrite the <string> terminator as usual
+ * by \fB@\fP-modifying the command.
+ *
+ * String-building characters are enabled for this command.
+ */
+TECO_DEFINE_STATE_EXPECTSTRING(teco_state_print_string,
+ .initial_cb = (teco_state_initial_cb_t)teco_state_print_string_initial
+);
diff --git a/src/stdio-commands.h b/src/stdio-commands.h
index b795c08..33b2594 100644
--- a/src/stdio-commands.h
+++ b/src/stdio-commands.h
@@ -19,3 +19,4 @@
#include "parser.h"
TECO_DECLARE_STATE(teco_state_print_decimal);
+TECO_DECLARE_STATE(teco_state_print_string);
diff --git a/tests/testsuite.at b/tests/testsuite.at
index f7a8423..837fdef 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -183,6 +183,12 @@ TE_CHECK_CMDLINE([[2<255===>]], 0, stdout, ignore)
AT_FAIL_IF([test `$GREP -v "^Info:" stdout | wc -l` -ne 4], 0, ignore, ignore)
AT_CLEANUP
+AT_SETUP([Printing strings])
+# FIXME: Test that the LF is missing at line end.
+TE_CHECK([[@^A/TEST^JLine 2/]], 0, stdout, ignore)
+AT_FAIL_IF([test `$GREP -v "^Info:" stdout | wc -l` -ne 2], 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