aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--doc/sciteco.7.template2
-rw-r--r--src/cmdline.c119
-rw-r--r--src/core-commands.c43
-rw-r--r--tests/atlocal.in9
-rw-r--r--tests/testsuite.at32
5 files changed, 142 insertions, 63 deletions
diff --git a/doc/sciteco.7.template b/doc/sciteco.7.template
index d5fc4af..1fe1dba 100644
--- a/doc/sciteco.7.template
+++ b/doc/sciteco.7.template
@@ -518,6 +518,8 @@ Miscelleaneous
.br
(modifier \fIdisabled\fP)
T};T{
+\# Strictly speaking, it only does that from the start state.
+\# At the beginning of strings or Q-reg specs, it only erases until the start state.
Rub out until the beginning of the last command, which is not a no-op (whitespace).
\(lq@\(rq and \(lq:\(rq modifiers are considered part of the command and also rubbed out.
T}
diff --git a/src/cmdline.c b/src/cmdline.c
index 3fb7cb9..e367e9a 100644
--- a/src/cmdline.c
+++ b/src/cmdline.c
@@ -446,56 +446,22 @@ teco_state_process_edit_cmd(teco_machine_t *ctx, teco_machine_t *parent_ctx, gun
}
return TRUE;
- case TECO_CTL_KEY('W'): /* rubout/reinsert command */
+ case TECO_CTL_KEY('W'): /* rubout/reinsert construct */
teco_interface_popup_clear();
- /*
- * This mimics the behavior of the `Y` command,
- * so it also rubs out no-op commands.
- * See also teco_find_words().
- */
if (teco_cmdline.modifier_enabled) {
- /* reinsert command */
- /* @ and : are not separate states, but practically belong to the command */
- while (ctx->current->is_start &&
- teco_cmdline.effective_len < teco_cmdline.str.len &&
- (teco_cmdline.str.data[teco_cmdline.effective_len] == ':' ||
- teco_cmdline.str.data[teco_cmdline.effective_len] == '@'))
- if (!teco_cmdline_rubin(error))
- return FALSE;
-
+ /* reinsert construct */
do {
if (!teco_cmdline_rubin(error))
return FALSE;
} while (!ctx->current->is_start &&
teco_cmdline.effective_len < teco_cmdline.str.len);
-
- while (ctx->current->is_start &&
- teco_cmdline.effective_len < teco_cmdline.str.len &&
- strchr(TECO_NOOPS, teco_cmdline.str.data[teco_cmdline.effective_len]))
- if (!teco_cmdline_rubin(error))
- return FALSE;
-
- return TRUE;
+ } else {
+ /* rubout construct */
+ do
+ teco_cmdline_rubout();
+ while (!ctx->current->is_start);
}
-
- /* rubout command */
- while (ctx->current->is_start &&
- teco_cmdline.effective_len > 0 &&
- strchr(TECO_NOOPS, teco_cmdline.str.data[teco_cmdline.effective_len-1]))
- teco_cmdline_rubout();
-
- do
- teco_cmdline_rubout();
- while (!ctx->current->is_start);
-
- /* @ and : are not separate states, but practically belong to the command */
- while (ctx->current->is_start &&
- teco_cmdline.effective_len > 0 &&
- (teco_cmdline.str.data[teco_cmdline.effective_len-1] == ':' ||
- teco_cmdline.str.data[teco_cmdline.effective_len-1] == '@'))
- teco_cmdline_rubout();
-
return TRUE;
#if !defined(INTERFACE_GTK) && defined(SIGTSTP)
@@ -534,6 +500,69 @@ teco_state_caseinsensitive_process_edit_cmd(teco_machine_t *ctx, teco_machine_t
}
gboolean
+teco_state_command_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error)
+{
+ switch (key) {
+ case TECO_CTL_KEY('W'): /* rubout/reinsert command */
+ teco_interface_popup_clear();
+
+ /*
+ * This mimics the behavior of the `Y` command,
+ * so it also rubs out no-op commands.
+ * See also teco_find_words().
+ */
+ if (teco_cmdline.modifier_enabled) {
+ /* reinsert command */
+ /* @ and : are not separate states, but practically belong to the command */
+ while (ctx->parent.current->is_start &&
+ teco_cmdline.effective_len < teco_cmdline.str.len &&
+ (teco_cmdline.str.data[teco_cmdline.effective_len] == ':' ||
+ teco_cmdline.str.data[teco_cmdline.effective_len] == '@'))
+ if (!teco_cmdline_rubin(error))
+ return FALSE;
+
+ do {
+ if (!teco_cmdline_rubin(error))
+ return FALSE;
+ } while (!ctx->parent.current->is_start &&
+ teco_cmdline.effective_len < teco_cmdline.str.len);
+
+ while (ctx->parent.current->is_start &&
+ teco_cmdline.effective_len < teco_cmdline.str.len &&
+ strchr(TECO_NOOPS, teco_cmdline.str.data[teco_cmdline.effective_len]))
+ if (!teco_cmdline_rubin(error))
+ return FALSE;
+
+ return TRUE;
+ }
+
+ /* rubout command */
+ while (ctx->parent.current->is_start &&
+ teco_cmdline.effective_len > 0 &&
+ strchr(TECO_NOOPS, teco_cmdline.str.data[teco_cmdline.effective_len-1]))
+ teco_cmdline_rubout();
+
+ do
+ teco_cmdline_rubout();
+ while (!ctx->parent.current->is_start);
+
+ /*
+ * @ and : are not separate states, but practically belong to the command.
+ * We cannot rely on the last character though, since it might
+ * be part of another command.
+ */
+ while (ctx->parent.current->is_start &&
+ (ctx->modifier_at || ctx->modifier_colon) &&
+ teco_cmdline.effective_len > 0)
+ teco_cmdline_rubout();
+
+ return TRUE;
+ }
+
+ return teco_state_caseinsensitive_process_edit_cmd(&ctx->parent, parent_ctx, key, error);
+}
+
+gboolean
teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t *ctx, teco_machine_t *parent_ctx,
gunichar key, GError **error)
{
@@ -678,6 +707,10 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t *
* Chaining to the parent (embedding) state machine's handler
* makes sure that ^W at the beginning of the string argument
* rubs out the entire string command.
+ *
+ * FIXME: This does not rub out modifiers in front of
+ * string commands since this callback could be used in recursively
+ * embedded string building machines as well.
*/
return teco_state_process_edit_cmd(parent_ctx, NULL, key, error);
}
@@ -1050,6 +1083,10 @@ teco_state_qregspec_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_machine_
* the state machine. In particular ^W would crash.
* This also makes sure that commands like <EQ> are completely
* rub out via ^W.
+ *
+ * FIXME: This does not rub out modifiers in front of
+ * Q-Reg commands since this callback could be used in recursively
+ * embedded Q-Reg specification machines as well.
*/
return teco_state_process_edit_cmd(parent_ctx, NULL, key, error);
}
diff --git a/src/core-commands.c b/src/core-commands.c
index 5d6e3b9..7845dc2 100644
--- a/src/core-commands.c
+++ b/src/core-commands.c
@@ -47,6 +47,25 @@
#include "move-commands.h"
#include "core-commands.h"
+gboolean teco_state_command_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx,
+ gunichar key, GError **error);
+
+/**
+ * @class TECO_DEFINE_STATE_COMMAND
+ * @implements TECO_DEFINE_STATE_CASEINSENSITIVE
+ * @ingroup states
+ *
+ * Base state for everything that is the beginning of a one or two
+ * letter command.
+ */
+#define TECO_DEFINE_STATE_COMMAND(NAME, ...) \
+ TECO_DEFINE_STATE_CASEINSENSITIVE(NAME, \
+ .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t) \
+ teco_state_command_process_edit_cmd, \
+ .style = SCE_SCITECO_COMMAND, \
+ ##__VA_ARGS__ \
+ )
+
static teco_state_t *teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error);
/*
@@ -822,11 +841,10 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
teco_ascii_toupper(chr), error);
}
-TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_start,
+TECO_DEFINE_STATE_COMMAND(teco_state_start,
.end_of_macro_cb = NULL, /* Allowed at the end of a macro! */
.is_start = TRUE,
- .keymacro_mask = TECO_KEYMACRO_MASK_START | TECO_KEYMACRO_MASK_CASEINSENSITIVE,
- .style = SCE_SCITECO_COMMAND
+ .keymacro_mask = TECO_KEYMACRO_MASK_START | TECO_KEYMACRO_MASK_CASEINSENSITIVE
);
/*$ F<
@@ -983,9 +1001,7 @@ teco_state_fcommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error
teco_ascii_toupper(chr), error);
}
-TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_fcommand,
- .style = SCE_SCITECO_COMMAND
-);
+TECO_DEFINE_STATE_COMMAND(teco_state_fcommand);
static void
teco_undo_change_dir_action(gchar **dir, gboolean run)
@@ -1192,7 +1208,7 @@ teco_state_condcommand_input(teco_machine_main_t *ctx, gunichar chr, GError **er
return &teco_state_start;
}
-TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_condcommand,
+TECO_DEFINE_STATE_COMMAND(teco_state_condcommand,
.style = SCE_SCITECO_OPERATOR
);
@@ -1533,9 +1549,7 @@ teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
teco_ascii_toupper(chr), error);
}
-TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_control,
- .style = SCE_SCITECO_COMMAND
-);
+TECO_DEFINE_STATE_COMMAND(teco_state_control);
static teco_state_t *
teco_state_ascii_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
@@ -1661,15 +1675,14 @@ teco_state_escape_end_of_macro(teco_machine_t *ctx, GError **error)
return teco_expressions_discard_args(error);
}
-TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_escape,
+TECO_DEFINE_STATE_COMMAND(teco_state_escape,
.end_of_macro_cb = teco_state_escape_end_of_macro,
/*
* The state should behave like teco_state_start
* when it comes to function key macro masking.
*/
.is_start = TRUE,
- .keymacro_mask = TECO_KEYMACRO_MASK_START | TECO_KEYMACRO_MASK_CASEINSENSITIVE,
- .style = SCE_SCITECO_COMMAND
+ .keymacro_mask = TECO_KEYMACRO_MASK_START | TECO_KEYMACRO_MASK_CASEINSENSITIVE
);
/*$ ED flags
@@ -2464,9 +2477,7 @@ teco_state_ecommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error
teco_ascii_toupper(chr), error);
}
-TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_ecommand,
- .style = SCE_SCITECO_COMMAND
-);
+TECO_DEFINE_STATE_COMMAND(teco_state_ecommand);
gboolean
teco_state_insert_initial(teco_machine_main_t *ctx, GError **error)
diff --git a/tests/atlocal.in b/tests/atlocal.in
index adaf928..8465f96 100644
--- a/tests/atlocal.in
+++ b/tests/atlocal.in
@@ -13,6 +13,15 @@ fi
# For testing command-line editing:
SCITECO_CMDLINE="$SCITECO --no-profile --fake-cmdline"
+# Control characters for testing immediate editing commands with $SCITECO_CMDLINE.
+# Often, we can use {...} for testing rubout, but sometimes this is not enough.
+# Directly embedding escapes into strings is not portable.
+# Theoretically, we could directly embed control codes, but for the time being
+# I am trying to keep non-TECO sources clean of non-printable characters.
+RUBOUT=`printf '\8'`
+RUBOUT_WORD=`printf '\27'`
+ESCAPE=`printf '\33'`
+
# Make sure that the standard library from the source package
# is used.
SCITECOPATH="@abs_top_srcdir@/lib"
diff --git a/tests/testsuite.at b/tests/testsuite.at
index cb1fe61..20869f4 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -198,6 +198,23 @@ AT_CHECK([$SCITECO -e "@EC'dd if=/dev/zero bs=512 count=1' Z= Z-512\"N(0/0)'"],
AT_CHECK([$SCITECO -e "0,128ED @EC'dd if=/dev/zero bs=512 count=1' Z= Z-512\"N(0/0)'"], 0, ignore, ignore)
AT_CLEANUP
+#
+# Command-line editing.
+#
+# This either uses the portable $RUBOUT and $RUBOUT_WORD variables or
+# we use the push/pop command-line commands { and } from start states.
+#
+# NOTE: Most errors are not reported in exit codes - you must check stderr.
+#
+AT_SETUP([Rub out with immediate editing commands])
+# Must rub out @, but not the colon from the Q-Reg specification.
+AT_CHECK([$SCITECO_CMDLINE "Q:@I/XXX/ ${RUBOUT_WORD}{Z-2\"N(0/0)'}"], 0, ignore, stderr)
+AT_FAIL_IF([$GREP "^Error:" stderr])
+# Should not rub out @ and : characters.
+AT_CHECK([$SCITECO_CMDLINE "@I/ @:foo ${RUBOUT_WORD}/ Z-3\"N(0/0)'"], 0, ignore, stderr)
+AT_FAIL_IF([$GREP "^Error:" stderr])
+AT_CLEANUP
+
AT_BANNER([Regression Tests])
AT_SETUP([Glob patterns with character classes])
@@ -293,12 +310,8 @@ AT_CHECK([$SCITECO_CMDLINE "!foo!{-5D}"], 0, ignore, stderr)
AT_CLEANUP
#
-# Command-line editing bugs.
-#
-# NOTE: It would generally be possible to use control codes like ^H (8)
-# and ^W (23) for rubout as well, but this is tricky to write in a portable manner.
-# Therefore we usally use the push/pop command-line commands { and }.
-# NOTE: Most errors are not reported in exit codes - you must check stderr.
+# Command-line editing regressions:
+# See above for rules.
#
AT_SETUP([Rub out string append])
AT_CHECK([$SCITECO_CMDLINE "@I/XXX/ H:Xa{-4D} :Qa-0\"N(0/0)'"], 0, ignore, stderr)
@@ -364,3 +377,10 @@ AT_SETUP([Recursion overflow])
AT_CHECK([$SCITECO -e "@^Um{U.a Q.a-100000\"<%.aMm'} 0Mm"], 0, ignore, ignore)
AT_XFAIL_IF(true)
AT_CLEANUP
+
+AT_SETUP([Rub out from empty string argument])
+# Should rub out the modifiers as well.
+AT_CHECK([$SCITECO_CMDLINE ":@^Ua/${RUBOUT_WORD}{Z\"N(0/0)'}"], 0, ignore, stderr)
+AT_CHECK([$GREP "^Error:" stderr], 0, ignore ignore)
+AT_XFAIL_IF(true)
+AT_CLEANUP