aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/core-commands.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core-commands.c')
-rw-r--r--src/core-commands.c834
1 files changed, 561 insertions, 273 deletions
diff --git a/src/core-commands.c b/src/core-commands.c
index dbf86bd..cd9a8fa 100644
--- a/src/core-commands.c
+++ b/src/core-commands.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -18,7 +18,9 @@
#include "config.h"
#endif
+#include <time.h>
#include <string.h>
+#include <stdio.h>
#include <glib.h>
#include <glib/gstdio.h>
@@ -42,31 +44,76 @@
#include "memory.h"
#include "eol.h"
#include "qreg.h"
+#include "stdio-commands.h"
#include "qreg-commands.h"
#include "goto-commands.h"
#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);
+static teco_state_t *teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error);
+static teco_state_t *teco_state_ctlc_control_input(teco_machine_main_t *ctx, gunichar chr, 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.
+ * Translate buffer range arguments from the expression stack to
+ * a from-position and length in bytes.
+ *
+ * If only one argument is given, it is interpreted as a number of lines
+ * beginning with dot.
+ * If two arguments are given, it is interpreted as two buffer positions
+ * in glyphs.
+ *
+ * @param cmd Name of the command
+ * @param from_ret Where to store the from-position in bytes
+ * @param len_ret Where to store the length of the range in bytes
+ * @param error A GError
+ * @return FALSE if an error occurred
+ *
+ * @fixme There are still redundancies with teco_state_start_kill().
+ * But it needs to discern between invalid ranges and other errors.
*/
-#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__ \
- )
+gboolean
+teco_get_range_args(const gchar *cmd, gsize *from_ret, gsize *len_ret, GError **error)
+{
+ gssize from, len; /* in bytes */
-static teco_state_t *teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error);
+ if (!teco_expressions_eval(FALSE, error))
+ return FALSE;
+
+ if (teco_expressions_args() <= 1) {
+ teco_int_t line;
+
+ if (!teco_expressions_pop_num_calc(&line, teco_num_sign, error))
+ return FALSE;
+
+ from = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
+ line += teco_interface_ssm(SCI_LINEFROMPOSITION, from, 0);
+
+ if (!teco_validate_line(line)) {
+ teco_error_range_set(error, cmd);
+ return FALSE;
+ }
+
+ len = teco_interface_ssm(SCI_POSITIONFROMLINE, line, 0) - from;
+
+ if (len < 0) {
+ from += len;
+ len *= -1;
+ }
+ } else {
+ gssize to = teco_interface_glyphs2bytes(teco_expressions_pop_num(0));
+ from = teco_interface_glyphs2bytes(teco_expressions_pop_num(0));
+ len = to - from;
+
+ if (len < 0 || from < 0 || to < 0) {
+ teco_error_range_set(error, cmd);
+ return FALSE;
+ }
+ }
+
+ *from_ret = from;
+ *len_ret = len;
+ return TRUE;
+}
/*
* NOTE: This needs some extra code in teco_state_start_input().
@@ -213,19 +260,20 @@ teco_state_start_backslash(teco_machine_main_t *ctx, GError **error)
return;
if (teco_expressions_args()) {
- teco_int_t value;
-
- if (!teco_expressions_pop_num_calc(&value, 0, error))
- return;
-
gchar buffer[TECO_EXPRESSIONS_FORMAT_LEN];
- gchar *str = teco_expressions_format(buffer, value,
+ gchar *str = teco_expressions_format(buffer,
+ teco_expressions_pop_num(0),
ctx->qreg_table_locals->radix);
g_assert(*str != '\0');
gsize len = strlen(str);
- teco_undo_gsize(teco_ranges[0].from) = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
- teco_undo_gsize(teco_ranges[0].to) = teco_ranges[0].from + len;
+ sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
+ teco_undo_int(teco_ranges[0].from) = teco_interface_bytes2glyphs(pos);
+ /*
+ * We can assume that `len` is already in glyphs,
+ * i.e. formatted numbers will never use multi-byte/Unicode characters.
+ */
+ teco_undo_int(teco_ranges[0].to) = teco_ranges[0].from + len;
teco_undo_guint(teco_ranges_count) = 1;
teco_interface_ssm(SCI_BEGINUNDOACTION, 0, 0);
@@ -276,8 +324,7 @@ static void
teco_state_start_loop_open(teco_machine_main_t *ctx, GError **error)
{
teco_loop_context_t lctx;
- if (!teco_expressions_eval(FALSE, error) ||
- !teco_expressions_pop_num_calc(&lctx.counter, -1, error))
+ if (!teco_expressions_pop_num_calc(&lctx.counter, -1, error))
return;
lctx.brace_level = teco_brace_level;
lctx.pass_through = teco_machine_main_eval_colon(ctx) > 0;
@@ -370,7 +417,7 @@ teco_state_start_loop_close(teco_machine_main_t *ctx, GError **error)
}
}
-/*$ ";" break
+/*$ ";" ":;" break
* [bool]; -- Conditionally break from loop
* [bool]:;
*
@@ -504,9 +551,11 @@ teco_state_start_cmdline_push(teco_machine_main_t *ctx, GError **error)
!teco_qreg_table_edit_name(&teco_qreg_table_globals, "\e", 1, error))
return;
+ const gchar *macro = (const gchar *)teco_cmdline_ssm(SCI_GETCHARACTERPOINTER, 0, 0);
+
teco_interface_ssm(SCI_BEGINUNDOACTION, 0, 0);
teco_interface_ssm(SCI_CLEARALL, 0, 0);
- teco_interface_ssm(SCI_ADDTEXT, teco_cmdline.pc, (sptr_t)teco_cmdline.str.data);
+ teco_interface_ssm(SCI_ADDTEXT, teco_cmdline.pc, (sptr_t)macro);
teco_interface_ssm(SCI_ENDUNDOACTION, 0, 0);
/*
@@ -536,34 +585,6 @@ teco_state_start_cmdline_pop(teco_machine_main_t *ctx, GError **error)
g_set_error_literal(error, TECO_ERROR, TECO_ERROR_CMDLINE, "");
}
-/*$ "=" print
- * <n>= -- Show value as message
- *
- * Shows integer <n> as a message in the message line and/or
- * on the console.
- * It is currently always formatted as a decimal integer and
- * shown with the user-message severity.
- * The command fails if <n> is not given.
- */
-/**
- * @todo perhaps care about current radix
- * @todo colon-modifier to suppress line-break on console?
- */
-static void
-teco_state_start_print(teco_machine_main_t *ctx, GError **error)
-{
- if (!teco_expressions_eval(FALSE, error))
- return;
- if (!teco_expressions_args()) {
- teco_error_argexpected_set(error, "=");
- return;
- }
- teco_int_t v;
- if (!teco_expressions_pop_num_calc(&v, teco_num_sign, error))
- return;
- teco_interface_msg(TECO_MSG_USER, "%" TECO_INT_FORMAT, v);
-}
-
/*$ A
* [n]A -> code -- Get character code from buffer
* -A -> code
@@ -573,8 +594,6 @@ teco_state_start_print(teco_machine_main_t *ctx, GError **error)
* This can be an ASCII <code> or Unicode codepoint
* depending on Scintilla's encoding of the current
* buffer.
- * Invalid Unicode byte sequences are reported as
- * -1 or -2.
*
* - If <n> is 0, return the <code> of the character
* pointed to by dot.
@@ -585,12 +604,11 @@ teco_state_start_print(teco_machine_main_t *ctx, GError **error)
* - If <n> is omitted, the sign prefix is implied.
*
* If the position of the queried character is off-page,
- * the command will yield an error.
- *
+ * the command will return -1.
* If the document is encoded as UTF-8 and there is
- * an incomplete sequence at the requested position,
- * -1 is returned.
- * All other invalid Unicode sequences are returned as -2.
+ * an invalid byte sequence at the requested position,
+ * -2 is returned.
+ * Incomplete byte sequences are returned as -3.
*/
static void
teco_state_start_get(teco_machine_main_t *ctx, GError **error)
@@ -603,15 +621,11 @@ teco_state_start_get(teco_machine_main_t *ctx, GError **error)
gssize get_pos = teco_interface_glyphs2bytes_relative(pos, v);
sptr_t len = teco_interface_ssm(SCI_GETLENGTH, 0, 0);
- if (get_pos < 0 || get_pos == len) {
- teco_error_range_set(error, "A");
- return;
- }
-
- teco_expressions_push(teco_interface_get_character(get_pos, len));
+ teco_expressions_push(get_pos < 0 || get_pos == len
+ ? -1 : teco_interface_get_character(get_pos, len));
}
-static teco_state_t *
+teco_state_t *
teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
{
static teco_machine_main_transition_t transitions[] = {
@@ -621,7 +635,7 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
['$'] = {&teco_state_escape},
['!'] = {&teco_state_label},
['O'] = {&teco_state_goto,
- .modifier_at = TRUE},
+ .modifier_at = TRUE, .modifier_colon = 1},
['^'] = {&teco_state_control,
.modifier_at = TRUE, .modifier_colon = 2},
['F'] = {&teco_state_fcommand,
@@ -629,7 +643,7 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
['"'] = {&teco_state_condcommand},
['E'] = {&teco_state_ecommand,
.modifier_at = TRUE, .modifier_colon = 2},
- ['I'] = {&teco_state_insert_building,
+ ['I'] = {&teco_state_insert,
.modifier_at = TRUE},
['?'] = {&teco_state_help,
.modifier_at = TRUE},
@@ -639,8 +653,10 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
.modifier_at = TRUE, .modifier_colon = 1},
['['] = {&teco_state_pushqreg},
- [']'] = {&teco_state_popqreg},
- ['G'] = {&teco_state_getqregstring},
+ [']'] = {&teco_state_popqreg,
+ .modifier_colon = 1},
+ ['G'] = {&teco_state_getqregstring,
+ .modifier_colon = 1},
['Q'] = {&teco_state_queryqreg,
.modifier_colon = 1},
['U'] = {&teco_state_setqreginteger,
@@ -650,6 +666,8 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
.modifier_colon = 1},
['X'] = {&teco_state_copytoqreg,
.modifier_at = TRUE, .modifier_colon = 1},
+ ['='] = {&teco_state_print_decimal,
+ .modifier_colon = 1},
/*
* Arithmetics
@@ -702,28 +720,25 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
.modifier_colon = 1},
['D'] = {&teco_state_start, teco_state_start_delete_chars,
.modifier_colon = 1},
- ['='] = {&teco_state_start, teco_state_start_print},
- ['A'] = {&teco_state_start, teco_state_start_get}
+ ['A'] = {&teco_state_start, teco_state_start_get},
+ ['T'] = {&teco_state_start, teco_state_start_typeout}
};
- switch (chr) {
/*
- * No-ops (same as TECO_NOOPS):
+ * Non-operational commands.
* These are explicitly not handled in teco_state_control,
* so that we can potentially reuse the upcaret notations like ^J.
*/
- case ' ':
- case '\f':
- case '\r':
- case '\n':
- case '\v':
+ if (teco_is_noop(chr)) {
if (ctx->flags.modifier_at ||
(ctx->flags.mode == TECO_MODE_NORMAL && ctx->flags.modifier_colon)) {
teco_error_modifier_set(error, chr);
return NULL;
}
return &teco_state_start;
+ }
+ switch (chr) {
/*$ 0 1 2 3 4 5 6 7 8 9 digit number
* [n]0|1|2|3|4|5|6|7|8|9 -> n*Radix+X -- Append digit
*
@@ -758,8 +773,10 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
* for beginnings of command-lines?
* It could also be used for a corresponding KEYMACRO mask.
*/
- if (teco_cmdline.effective_len == 1 && teco_cmdline.str.data[0] == '*')
+ if (teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) == 1 &&
+ teco_cmdline_ssm(SCI_GETCHARAT, 0, 0) == '*')
return &teco_state_save_cmdline;
+ /* treat as an operator */
break;
case '<':
@@ -898,20 +915,18 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
teco_ascii_toupper(chr), error);
}
-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
+TECO_DEFINE_STATE_START(teco_state_start,
+ .input_cb = (teco_state_input_cb_t)teco_state_start_input
);
-/*$ F<
+/*$ "F<" ":F<"
* F< -- Go to loop start or jump to beginning of macro
* :F<
*
* Immediately jumps to the current loop's start.
* Also works from inside conditionals.
*
- * This command behaves exactly like \fB>\fP with regard to
+ * This command behaves exactly like \fB<\fP with regard to
* colon-modifiers.
*
* Outside of loops \(em or in a macro without
@@ -945,7 +960,7 @@ teco_state_fcommand_loop_start(teco_machine_main_t *ctx, GError **error)
ctx->macro_pc = lctx->pc;
}
-/*$ F> continue
+/*$ "F>" ":F>" continue
* F> -- Go to loop end or return from macro
* :F>
*
@@ -1043,6 +1058,8 @@ teco_state_fcommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error
.modifier_at = TRUE, .modifier_colon = 2},
['R'] = {&teco_state_replace_default,
.modifier_at = TRUE, .modifier_colon = 2},
+ ['N'] = {&teco_state_replace_default_all,
+ .modifier_at = TRUE, .modifier_colon = 1},
['G'] = {&teco_state_changedir,
.modifier_at = TRUE},
@@ -1065,7 +1082,9 @@ teco_state_fcommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error
teco_ascii_toupper(chr), error);
}
-TECO_DEFINE_STATE_COMMAND(teco_state_fcommand);
+TECO_DEFINE_STATE_COMMAND(teco_state_fcommand,
+ .input_cb = (teco_state_input_cb_t)teco_state_fcommand_input
+);
static void
teco_undo_change_dir_action(gchar **dir, gboolean run)
@@ -1089,12 +1108,12 @@ teco_undo_change_dir_to_current(void)
}
static teco_state_t *
-teco_state_changedir_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_changedir_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_start;
- g_autofree gchar *dir = teco_file_expand_path(str->data);
+ g_autofree gchar *dir = teco_file_expand_path(str.data);
if (!*dir) {
teco_qreg_t *qreg = teco_qreg_table_find(&teco_qreg_table_globals, "$HOME", 5);
g_assert(qreg != NULL);
@@ -1105,7 +1124,7 @@ teco_state_changedir_done(teco_machine_main_t *ctx, const teco_string_t *str, GE
/*
* Null-characters must not occur in file names.
*/
- if (teco_string_contains(&home, '\0')) {
+ if (teco_string_contains(home, '\0')) {
teco_string_clear(&home);
g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
"Null-character not allowed in filenames");
@@ -1157,7 +1176,9 @@ teco_state_changedir_done(teco_machine_main_t *ctx, const teco_string_t *str, GE
* String-building characters are enabled on this
* command and directories can be tab-completed.
*/
-TECO_DEFINE_STATE_EXPECTDIR(teco_state_changedir);
+TECO_DEFINE_STATE_EXPECTDIR(teco_state_changedir,
+ .expectstring.done_cb = teco_state_changedir_done
+);
static teco_state_t *
teco_state_condcommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
@@ -1185,8 +1206,7 @@ teco_state_condcommand_input(teco_machine_main_t *ctx, gunichar chr, GError **er
teco_error_argexpected_set(error, "\"");
return NULL;
}
- if (!teco_expressions_pop_num_calc(&value, 0, error))
- return NULL;
+ value = teco_expressions_pop_num(0);
break;
default:
@@ -1273,7 +1293,8 @@ teco_state_condcommand_input(teco_machine_main_t *ctx, gunichar chr, GError **er
}
TECO_DEFINE_STATE_COMMAND(teco_state_condcommand,
- .style = SCE_SCITECO_OPERATOR
+ .style = SCE_SCITECO_OPERATOR,
+ .input_cb = (teco_state_input_cb_t)teco_state_condcommand_input
);
/*$ ^_ negate
@@ -1287,8 +1308,6 @@ TECO_DEFINE_STATE_COMMAND(teco_state_condcommand,
static void
teco_state_control_negate(teco_machine_main_t *ctx, GError **error)
{
- teco_int_t v;
-
if (!teco_expressions_eval(FALSE, error))
return;
@@ -1296,9 +1315,8 @@ teco_state_control_negate(teco_machine_main_t *ctx, GError **error)
teco_error_argexpected_set(error, "^_");
return;
}
- if (!teco_expressions_pop_num_calc(&v, 0, error))
- return;
- teco_expressions_push(~v);
+
+ teco_expressions_push(~teco_expressions_pop_num(0));
}
static void
@@ -1319,35 +1337,6 @@ teco_state_control_xor(teco_machine_main_t *ctx, GError **error)
teco_expressions_push_calc(TECO_OP_XOR, error);
}
-/*$ ^C exit
- * ^C -- Exit program immediately
- *
- * Lets the top-level macro return immediately
- * regardless of the current macro invocation frame.
- * This command is only allowed in batch mode,
- * so it is not invoked accidentally when using
- * the CTRL+C immediate editing command to
- * interrupt long running operations.
- * When using \fB^C\fP in a munged file,
- * interactive mode is never started, so it behaves
- * effectively just like \(lq-EX\fB$$\fP\(rq
- * (when executed in the top-level macro at least).
- *
- * The \fBquit\fP hook is still executed.
- */
-static void
-teco_state_control_exit(teco_machine_main_t *ctx, GError **error)
-{
- if (teco_undo_enabled) {
- g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
- "<^C> not allowed in interactive mode");
- return;
- }
-
- teco_quit_requested = TRUE;
- g_set_error_literal(error, TECO_ERROR, TECO_ERROR_QUIT, "");
-}
-
/*$ ^O octal
* ^O -- Set radix to 8 (octal)
*/
@@ -1401,14 +1390,13 @@ teco_state_control_radix(teco_machine_main_t *ctx, GError **error)
return;
teco_expressions_push(radix);
} else {
- if (!teco_expressions_pop_num_calc(&radix, 0, error) ||
- !qreg->vtable->undo_set_integer(qreg, error) ||
- !qreg->vtable->set_integer(qreg, radix, error))
+ if (!qreg->vtable->undo_set_integer(qreg, error) ||
+ !qreg->vtable->set_integer(qreg, teco_expressions_pop_num(0), error))
return;
}
}
-/*$ ^E glyphs2bytes bytes2glyphs
+/*$ "^E" ":^E" glyphs2bytes bytes2glyphs
* glyphs^E -> bytes -- Translate between glyph and byte indexes
* bytes:^E -> glyphs
* ^E -> bytes
@@ -1455,9 +1443,7 @@ teco_state_control_glyphs2bytes(teco_machine_main_t *ctx, GError **error)
*/
res = teco_interface_ssm(colon_modified ? SCI_GETLENGTH : SCI_GETCURRENTPOS, 0, 0);
} else {
- teco_int_t pos;
- if (!teco_expressions_pop_num_calc(&pos, 0, error))
- return;
+ teco_int_t pos = teco_expressions_pop_num(0);
if (colon_modified) {
/* teco_interface_bytes2glyphs() does not check addresses */
res = 0 <= pos && pos <= teco_interface_ssm(SCI_GETLENGTH, 0, 0)
@@ -1498,8 +1484,8 @@ teco_ranges_init(void)
* The default value 0 specifies the entire matched pattern,
* while higher numbers refer to \fB^E[\fI...\fB]\fR subpatterns.
* \fB^Y\fP can also be used to return the buffer range of the
- * last text insertion by any \*(ST command (\fBI\fP, \fBEI\fP, \fB^I\fP, \fBG\fIq\fR,
- * \fB\\\fP, \fBEC\fP, \fBEN\fP, etc).
+ * last text insertion by any \*(ST command (\fBI\fP, \fB^I\fP, \fBG\fIq\fR,
+ * \fB\\\fP, \fBEC\fP, \fBEN\fP, search replacements, etc).
* In this case <n> is only allowed to be 0 or missing.
*
* For instance, \(lq^YXq\(rq copies the entire matched pattern or text
@@ -1522,8 +1508,8 @@ teco_state_control_last_range(teco_machine_main_t *ctx, GError **error)
return;
}
- teco_expressions_push(teco_interface_bytes2glyphs(teco_ranges[n].from));
- teco_expressions_push(teco_interface_bytes2glyphs(teco_ranges[n].to));
+ teco_expressions_push(teco_ranges[n].from);
+ teco_expressions_push(teco_ranges[n].to);
}
/*$ ^S
@@ -1551,17 +1537,20 @@ teco_state_control_last_length(teco_machine_main_t *ctx, GError **error)
/*
* There is little use in supporting n^S for n != 0.
* This is just for consistency with ^Y.
+ *
+ * We do not use teco_expressions_pop_num_calc(),
+ * so as not to reset the sign prefix.
*/
- if (teco_expressions_args() > 0 &&
- !teco_expressions_pop_num_calc(&n, 0, error))
+ if (!teco_expressions_eval(FALSE, error))
return;
+ if (teco_expressions_args() > 0)
+ n = teco_expressions_pop_num(0);
if (n < 0 || n >= teco_ranges_count) {
- teco_error_subpattern_set(error, "^Y");
+ teco_error_subpattern_set(error, "^S");
return;
}
- teco_expressions_push(teco_interface_bytes2glyphs(teco_ranges[n].from) -
- teco_interface_bytes2glyphs(teco_ranges[n].to));
+ teco_expressions_push(teco_ranges[n].from - teco_ranges[n].to);
}
static void TECO_DEBUG_CLEANUP
@@ -1570,6 +1559,100 @@ teco_ranges_cleanup(void)
g_free(teco_ranges);
}
+/*$ ^B date
+ * ^B -> (((year-1900)*16 + month)*32 + day) -- Retrieve date
+ *
+ * Returns the current date via the given equation.
+ */
+/*
+ * FIXME: Perhaps :^B should directly return the
+ * decoded year, month and day.
+ */
+static void
+teco_state_control_date(teco_machine_main_t *ctx, GError **error)
+{
+ GDate date;
+
+ g_date_clear(&date, 1);
+ g_date_set_time_t(&date, time(NULL));
+ teco_expressions_push(((g_date_get_year(&date)-1900)*16 + g_date_get_month(&date))*32 +
+ g_date_get_day(&date));
+}
+
+/*$ "^H" ":^H" "::^H" time timestamp
+ * ^H -> seconds since midnight -- Retrieve time of day or timestamp
+ * :^H -> seconds
+ * ::^H -> timestamp
+ *
+ * By default returns the current time in seconds since midnight (UTC).
+ *
+ * If colon-modified it returns the number of <seconds> since the Epoch,
+ * 1970-01-01 00:00:00 +0000 (UTC).
+ *
+ * If modified by two colons it returns the system's monotonic time in microseconds,
+ * which can be used as a <timestamp>.
+ */
+static void
+teco_state_control_time(teco_machine_main_t *ctx, GError **error)
+{
+ switch (teco_machine_main_eval_colon(ctx)) {
+ case 0:
+ teco_expressions_push(time(NULL) % (60*60*24));
+ break;
+ case 1:
+ teco_expressions_push(time(NULL));
+ break;
+ case 2:
+ /*
+ * NOTE: Might not be reliable if TECO_INTEGER==32.
+ */
+ teco_expressions_push(g_get_monotonic_time());
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+/*$ ^W refresh sleep delay wait
+ * [n]^W -- Wait and refresh screen
+ *
+ * First sleep <n> milliseconds before refreshing the view,
+ * i.e. drawing it.
+ * By default it sleeps for 10ms.
+ * This can be added to loops to make progress visible
+ * in interactive mode.
+ * In batch mode this command is useful as a sleep command.
+ * Sleeps can of course be interrupted with CTRL+C.
+ *
+ * Since CTRL+W is an immediate editing command, you may
+ * have to type this command in upcaret mode.
+ * To enforce a complete screen redraw you can also
+ * press CTRL+L.
+ */
+static void
+teco_state_control_refresh(teco_machine_main_t *ctx, GError **error)
+{
+ teco_int_t ms;
+
+ if (!teco_expressions_pop_num_calc(&ms, 10, error))
+ return;
+
+ while (ms > 0 && !teco_interface_is_interrupted()) {
+ /*
+ * UNIX' usleep() would also be interrupted by
+ * SIGINT, but polling for interruptions is
+ * probably precise enough.
+ * We need this as a fallback anyway.
+ */
+ g_usleep(MIN(ms*1000, TECO_POLL_INTERVAL));
+ ms -= TECO_POLL_INTERVAL/1000;
+ }
+
+ teco_interface_unfold();
+ teco_interface_ssm(SCI_SCROLLCARET, 0, 0);
+ teco_interface_refresh(FALSE);
+}
+
static teco_state_t *
teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
{
@@ -1583,6 +1666,9 @@ teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
.modifier_at = TRUE, .modifier_colon = 1},
['^'] = {&teco_state_ascii},
['['] = {&teco_state_escape},
+ ['C'] = {&teco_state_ctlc},
+ ['A'] = {&teco_state_print_string,
+ .modifier_at = TRUE, .modifier_colon = 1},
/*
* Additional numeric operations
@@ -1595,7 +1681,9 @@ teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
/*
* Commands
*/
- ['C'] = {&teco_state_start, teco_state_control_exit},
+ ['B'] = {&teco_state_start, teco_state_control_date},
+ ['H'] = {&teco_state_start, teco_state_control_time,
+ .modifier_colon = 2},
['O'] = {&teco_state_start, teco_state_control_octal},
['D'] = {&teco_state_start, teco_state_control_decimal},
['R'] = {&teco_state_start, teco_state_control_radix},
@@ -1605,7 +1693,10 @@ 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},
+ ['W'] = {&teco_state_start, teco_state_control_refresh}
};
/*
@@ -1617,7 +1708,9 @@ teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
teco_ascii_toupper(chr), error);
}
-TECO_DEFINE_STATE_COMMAND(teco_state_control);
+TECO_DEFINE_STATE_COMMAND(teco_state_control,
+ .input_cb = (teco_state_input_cb_t)teco_state_control_input
+);
static teco_state_t *
teco_state_ascii_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
@@ -1640,7 +1733,82 @@ teco_state_ascii_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
* Note that this command can be typed CTRL+Caret or
* Caret-Caret.
*/
-TECO_DEFINE_STATE(teco_state_ascii);
+TECO_DEFINE_STATE(teco_state_ascii,
+ .input_cb = (teco_state_input_cb_t)teco_state_ascii_input
+);
+
+/*$ ^[^[ ^[$ $$ ^C terminate return
+ * [a1,a2,...]$$ -- Terminate command line or return from macro
+ * [a1,a2,...]^[$
+ * [a1,a2,...]^C
+ *
+ * Returns from the current macro invocation.
+ * This will pass control to the calling macro immediately
+ * and is thus faster than letting control reach the macro's end.
+ * Also, direct arguments to \fB$$\fP will be left on the expression
+ * stack when the macro returns.
+ * \fB$$\fP closes loops automatically and is thus safe to call
+ * from loop bodies.
+ * Furthermore, it has defined semantics when executed
+ * from within braced expressions:
+ * 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.
+ *
+ * Returning from the top-level macro in batch mode
+ * will exit the program or start up interactive mode depending
+ * on whether program exit has been requested.
+ * If \fB$$\fP exits the program, any remaining numeric parameter
+ * is returned by the process as its exit status.
+ * By default, the success code is returned.
+ * \(lqEX\fB$$\fP\(rq is thus a common idiom to exit
+ * prematurely.
+ *
+ * In interactive mode, returning from the top-level macro
+ * (i.e. typing \fB$$\fP at the command line) has the
+ * effect of command line termination.
+ * The arguments to \fB$$\fP are currently not used
+ * when terminating a command line \(em the new command line
+ * will always start with a clean expression stack.
+ *
+ * \fB^C\fP cannot be typed directly on the command-line
+ * as it could be inserted accidentally after interrupting
+ * operations with CTRL+C.
+ *
+ * The first \fIescape\fP of \fB$$\fP may be typed either
+ * as an escape character (ASCII 27), in up-arrow mode
+ * (e.g. \fB^[$\fP) or as a dollar character \(em the
+ * second character must be either a real escape character
+ * or a dollar character.
+ */
+/*
+ * FIXME: Analogous to ^C^C, we could support ^[^[ typed with carets only
+ * at the expense of yet another parser state.
+ */
+static teco_state_t *
+teco_return(teco_machine_main_t *ctx, GError **error)
+{
+ g_assert(ctx->flags.mode == TECO_MODE_NORMAL);
+
+ /*
+ * This check is not crucial, but a return command would
+ * terminate the command line and it would be impossible to apply the new
+ * command line with `}` after command-line termination.
+ */
+ if (G_UNLIKELY(ctx == &teco_cmdline.machine &&
+ teco_qreg_current && !teco_string_cmp(teco_qreg_current->head.name, "\e", 1))) {
+ g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Not allowed to terminate command-line while "
+ "editing command-line replacement register");
+ return NULL;
+ }
+
+ ctx->parent.current = &teco_state_start;
+ if (!teco_expressions_eval(FALSE, error))
+ return NULL;
+ teco_error_return_set(error, teco_expressions_args());
+ return NULL;
+}
/*
* The Escape state is special, as it implements
@@ -1658,52 +1826,9 @@ TECO_DEFINE_STATE(teco_state_ascii);
static teco_state_t *
teco_state_escape_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
{
- /*$ ^[^[ ^[$ $$ terminate return
- * [a1,a2,...]$$ -- Terminate command line or return from macro
- * [a1,a2,...]^[$
- *
- * Returns from the current macro invocation.
- * This will pass control to the calling macro immediately
- * and is thus faster than letting control reach the macro's end.
- * Also, direct arguments to \fB$$\fP will be left on the expression
- * stack when the macro returns.
- * \fB$$\fP closes loops automatically and is thus safe to call
- * from loop bodies.
- * Furthermore, it has defined semantics when executed
- * from within braced expressions:
- * 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.
- *
- * Returning from the top-level macro in batch mode
- * will exit the program or start up interactive mode depending
- * on whether program exit has been requested.
- * \(lqEX\fB$$\fP\(rq is thus a common idiom to exit
- * prematurely.
- *
- * In interactive mode, returning from the top-level macro
- * (i.e. typing \fB$$\fP at the command line) has the
- * effect of command line termination.
- * The arguments to \fB$$\fP are currently not used
- * when terminating a command line \(em the new command line
- * will always start with a clean expression stack.
- *
- * The first \fIescape\fP of \fB$$\fP may be typed either
- * as an escape character (ASCII 27), in up-arrow mode
- * (e.g. \fB^[$\fP) or as a dollar character \(em the
- * second character must be either a real escape character
- * or a dollar character.
- */
- if (chr == '\e' || chr == '$') {
- if (ctx->flags.mode > TECO_MODE_NORMAL)
- return &teco_state_start;
-
- ctx->parent.current = &teco_state_start;
- if (!teco_expressions_eval(FALSE, error))
- return NULL;
- teco_error_return_set(error, teco_expressions_args());
- return NULL;
- }
+ if (chr == '\e' || chr == '$')
+ return ctx->flags.mode > TECO_MODE_NORMAL
+ ? &teco_state_start : teco_return(ctx, error);
/*
* Alternatives: ^[, <CTRL/[>, <ESC>, $ (dollar)
@@ -1743,14 +1868,100 @@ teco_state_escape_end_of_macro(teco_machine_t *ctx, GError **error)
return teco_expressions_discard_args(error);
}
-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.
+TECO_DEFINE_STATE_START(teco_state_escape,
+ .input_cb = (teco_state_input_cb_t)teco_state_escape_input,
+ .end_of_macro_cb = teco_state_escape_end_of_macro
+);
+
+/*
+ * Just like ^[, ^C actually implements a lookahead,
+ * so a ^C itself does nothing.
+ * This does not break the user experience since ^C
+ * is disallowed to type at the command-line.
+ */
+static teco_state_t *
+teco_state_ctlc_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
+{
+ switch (chr) {
+ case TECO_CTL_KEY('C'): return teco_state_ctlc_control_input(ctx, 'C', error);
+ case '^': return &teco_state_ctlc_control;
+ }
+
+ return ctx->flags.mode > TECO_MODE_NORMAL
+ ? teco_state_start_input(ctx, chr, error) : teco_return(ctx, error);
+}
+
+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;
+}
+
+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
+);
+
+static teco_state_t *
+teco_state_ctlc_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
+{
+ /*$ ^C^C exit
+ * [n]^C^C -- Exit program immediately
+ *
+ * Lets the top-level macro return immediately
+ * regardless of the current macro invocation frame.
+ * This command is only allowed in batch mode,
+ * so it is not invoked accidentally when using
+ * the CTRL+C immediate editing command to
+ * interrupt long running operations.
+ * When using \fB^C^C\fP in a munged file,
+ * interactive mode is never started, so it behaves
+ * effectively just like \(lq-EX\fB$$\fP\(rq
+ * (when executed in the top-level macro at least).
+ *
+ * Any numeric parameter is returned by the process
+ * as its exit status.
+ * By default, the success code is returned.
+ * The \fBquit\fP hook is still executed.
+ *
+ * This command is currently disallowed in interactive mode.
+ *
+ * Note that both \(lq^C\(rq can be typed either
+ * as control codes (3) or with carets.
*/
- .is_start = TRUE,
- .keymacro_mask = TECO_KEYMACRO_MASK_START | TECO_KEYMACRO_MASK_CASEINSENSITIVE
+ if (chr == 'c' || chr == 'C') {
+ if (ctx->flags.mode > TECO_MODE_NORMAL)
+ return &teco_state_start;
+
+ if (teco_undo_enabled) {
+ g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "<^C^C> not allowed in interactive mode");
+ return NULL;
+ }
+
+ if (!teco_expressions_eval(FALSE, error))
+ return NULL;
+ teco_ed |= TECO_ED_EXIT;
+ g_set_error_literal(error, TECO_ERROR, TECO_ERROR_QUIT, "");
+ return NULL;
+ }
+
+ return ctx->flags.mode > TECO_MODE_NORMAL
+ ? teco_state_control_input(ctx, chr, error) : teco_return(ctx, error);
+}
+
+/*
+ * This state is necessary, so that you can type ^C^C exclusively with carets.
+ * Otherwise it would be very cumbersome to cause exits with ASCII characters only.
+ */
+TECO_DEFINE_STATE_COMMAND(teco_state_ctlc_control,
+ .input_cb = (teco_state_input_cb_t)teco_state_ctlc_control_input
);
/*$ ED flags
@@ -1772,7 +1983,12 @@ TECO_DEFINE_STATE_COMMAND(teco_state_escape,
* Without any argument ED returns the current flags.
*
* Currently, the following flags are used by \*(ST:
- * .IP 4: 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
+ * prior \fBEX\fP.
+ * .IP 4:
* If enabled, prefer raw single-byte ANSI encoding
* for all new buffers and registers.
* This does not change the encoding of any existing
@@ -1816,6 +2032,13 @@ TECO_DEFINE_STATE_COMMAND(teco_state_escape,
* by the \(lqNerd Fonts\(rq project.
* Changes to this flag in interactive mode may not become
* effective immediately.
+ * .IP 1024:
+ * If set the default clipboard register \(lq~\(rq will refer
+ * to the primary clipboard (\(lq~P\(rq) instead of the
+ * clipboard selection (\(lq~C\(rq).
+ * .IP 2048:
+ * Enable/Disable redirection of Scintilla messages (\fBES\fP)
+ * to the command line's Scintilla view.
*
* The features controlled thus are discribed in other sections
* of this manual.
@@ -1862,7 +2085,7 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error)
* The current user interface: 1 for Curses, 2 for GTK
* (\fBread-only\fP)
* .IP 1:
- * The current numbfer of buffers: Also the numeric id
+ * The current number of buffers: Also the numeric id
* of the last buffer in the ring. This is implied if
* no argument is given, so \(lqEJ\(rq returns the number
* of buffers in the ring.
@@ -1938,20 +2161,35 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error)
* The column after the last horizontal movement.
* This is only used by \fBfnkeys.tes\fP and is similar to the Scintilla-internal
* setting \fBSCI_CHOOSECARETX\fP.
- * Unless most other settings, this is on purpose not restored on rubout,
- * so it "survives" command line replacements.
+ * Unlike most other settings, this is on purpose not restored on rubout,
+ * so it \(lqsurvives\(rq command line replacements.
+ * .IP 5:
+ * Height of the command line view in lines (1 by default).
+ * Must not be smaller than 1.
+ * .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).
+ * 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.
+ * After changing the interval, the new value may become
+ * active only after the previous interval expires.
+ * Recovery files are not dumped in batch mode.
* .
* .IP -1:
* Type of the last mouse event (\fBread-only\fP).
* One of the following values will be returned:
* .RS
- * . IP 1: 4
+ * . IP 0: 4
* Some button has been pressed
- * . IP 2:
+ * . IP 1:
* Some button has been released
- * . IP 3:
+ * . IP 2:
* Scroll up
- * . IP 4:
+ * . IP 3:
* Scroll down
* .RE
* .IP -2:
@@ -1992,23 +2230,22 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error)
EJ_BUFFERS,
EJ_MEMORY_LIMIT,
EJ_INIT_COLOR,
- EJ_CARETX
+ EJ_CARETX,
+ EJ_CMDLINE_HEIGHT,
+ EJ_RECOVERY_INTERVAL
};
static teco_int_t caret_x = 0;
teco_int_t property;
- if (!teco_expressions_eval(FALSE, error) ||
- !teco_expressions_pop_num_calc(&property, teco_num_sign, error))
+ if (!teco_expressions_pop_num_calc(&property, teco_num_sign, error))
return;
if (teco_expressions_args() > 0) {
/*
* Set property
*/
- teco_int_t value, color;
- if (!teco_expressions_pop_num_calc(&value, 0, error))
- return;
+ teco_int_t value = teco_expressions_pop_num(0);
switch (property) {
case EJ_MEMORY_LIMIT:
@@ -2027,15 +2264,36 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error)
teco_error_argexpected_set(error, "EJ");
return;
}
- if (!teco_expressions_pop_num_calc(&color, 0, error))
- return;
- teco_interface_init_color((guint)value, (guint32)color);
+ teco_interface_init_color((guint)value,
+ (guint32)teco_expressions_pop_num(0));
break;
case EJ_CARETX:
+ /* DON'T undo on rubout */
caret_x = value;
break;
+ case EJ_CMDLINE_HEIGHT:
+ if (value < 1 || value > G_MAXUINT) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Invalid command line height %" TECO_INT_FORMAT " "
+ "for <EJ>", value);
+ return;
+ }
+ teco_undo_guint(teco_cmdline.height) = value;
+ break;
+
+ case EJ_RECOVERY_INTERVAL:
+ if (value < 0 || value > G_MAXUINT) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Invalid recovery interval %" TECO_INT_FORMAT "s "
+ "for <EJ>", value);
+ return;
+ }
+ teco_undo_guint(teco_ring_recovery_interval) = value;
+ /* FIXME: Perhaps signal the interface to reprogram timers */
+ break;
+
default:
g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
"Cannot set property %" TECO_INT_FORMAT " "
@@ -2091,6 +2349,14 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error)
teco_expressions_push(caret_x);
break;
+ case EJ_CMDLINE_HEIGHT:
+ teco_expressions_push(teco_cmdline.height);
+ break;
+
+ case EJ_RECOVERY_INTERVAL:
+ teco_expressions_push(teco_ring_recovery_interval);
+ break;
+
default:
g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
"Invalid property %" TECO_INT_FORMAT " "
@@ -2099,7 +2365,7 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error)
}
}
-/*$ EL eol
+/*$ "EL" ":EL" EOL
* 0EL -- Set or get End of Line mode
* 13,10:EL
* 1EL
@@ -2150,11 +2416,7 @@ teco_state_ecommand_eol(teco_machine_main_t *ctx, GError **error)
teco_int_t eol_mode;
if (teco_machine_main_eval_colon(ctx) > 0) {
- teco_int_t v1, v2;
- if (!teco_expressions_pop_num_calc(&v1, 0, error))
- return;
-
- switch (v1) {
+ switch (teco_expressions_pop_num(0)) {
case '\r':
eol_mode = SC_EOL_CR;
break;
@@ -2163,9 +2425,7 @@ teco_state_ecommand_eol(teco_machine_main_t *ctx, GError **error)
eol_mode = SC_EOL_LF;
break;
}
- if (!teco_expressions_pop_num_calc(&v2, 0, error))
- return;
- if (v2 == '\r') {
+ if (teco_expressions_pop_num(0) == '\r') {
eol_mode = SC_EOL_CRLF;
break;
}
@@ -2176,8 +2436,7 @@ teco_state_ecommand_eol(teco_machine_main_t *ctx, GError **error)
return;
}
} else {
- if (!teco_expressions_pop_num_calc(&eol_mode, 0, error))
- return;
+ eol_mode = teco_expressions_pop_num(0);
switch (eol_mode) {
case SC_EOL_CRLF:
case SC_EOL_CR:
@@ -2195,6 +2454,13 @@ teco_state_ecommand_eol(teco_machine_main_t *ctx, GError **error)
undo__teco_interface_ssm(SCI_SETEOLMODE,
teco_interface_ssm(SCI_GETEOLMODE, 0, 0), 0);
teco_interface_ssm(SCI_SETEOLMODE, eol_mode, 0);
+
+ /*
+ * While the buffer contents were not changed,
+ * the result of saving the file may differ,
+ * so we still dirtify the buffer.
+ */
+ teco_ring_dirtify();
} else if (teco_machine_main_eval_colon(ctx) > 0) {
const gchar *eol_seq = teco_eol_get_seq(teco_interface_ssm(SCI_GETEOLMODE, 0, 0));
teco_expressions_push(eol_seq);
@@ -2255,7 +2521,7 @@ teco_codepage2str(guint codepage)
return NULL;
}
-/*$ EE encoding codepage charset
+/*$ "EE" ":EE" encoding codepage charset
* codepageEE -- Edit current document's encoding (codepage/charset)
* EE -> codepage
* codepage:EE
@@ -2319,10 +2585,7 @@ teco_state_ecommand_encoding(teco_machine_main_t *ctx, GError **error)
/*
* Set code page
*/
- teco_int_t new_cp;
- if (!teco_expressions_pop_num_calc(&new_cp, 0, error))
- return;
-
+ teco_int_t new_cp = teco_expressions_pop_num(0);
if (old_cp == SC_CP_UTF8 && new_cp == SC_CP_UTF8)
return;
@@ -2449,7 +2712,26 @@ teco_state_ecommand_encoding(teco_machine_main_t *ctx, GError **error)
teco_interface_ssm(SCI_GOTOPOS, teco_interface_glyphs2bytes(dot_glyphs), 0);
}
-/*$ EX exit
+/*$ EO version
+ * EO -> major*10000 + minor*100 + micro -- Get program version
+ *
+ * Return the version of \*(ST encoded into an integer.
+ */
+static void
+teco_state_ecommand_version(teco_machine_main_t *ctx, GError **error)
+{
+ /*
+ * FIXME: This is inefficient and could be done at build-time.
+ * Or we could have PACKAGE_MAJOR_VERSION, PACKAGE_MINOR_VERSION etc. macros.
+ * But then, who cares?
+ */
+ guint major, minor, micro;
+ G_GNUC_UNUSED gint rc = sscanf(PACKAGE_VERSION, "%u.%u.%u", &major, &minor, &micro);
+ g_assert(rc == 3);
+ teco_expressions_push(major*10000 + minor*100 + micro);
+}
+
+/*$ "EX" ":EX" exit quit
* [bool]EX -- Exit program
* -EX
* :EX
@@ -2486,6 +2768,10 @@ teco_state_ecommand_encoding(teco_machine_main_t *ctx, GError **error)
* \(lq:EX\fB$$\fP\(rq is nevertheless the usual interactive
* command sequence to exit while saving all modified
* buffers.
+ *
+ * The program termination request is also available in bit 2
+ * of the \fBED\fP flags, so \(lqED&2\(rq can be used to
+ * check whether EX has been successfully called.
*/
/** @fixme what if changing file after EX? will currently still exit */
static void
@@ -2498,14 +2784,22 @@ teco_state_ecommand_exit(teco_machine_main_t *ctx, GError **error)
teco_int_t v;
if (!teco_expressions_pop_num_calc(&v, teco_num_sign, error))
return;
- if (teco_is_failure(v) && teco_ring_is_any_dirty()) {
- g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
- "Modified buffers exist");
+ guint id;
+ if (teco_is_failure(v) && (id = teco_ring_get_first_dirty())) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Buffer with id %u is dirty", id);
return;
}
}
- teco_undo_gboolean(teco_quit_requested) = TRUE;
+ teco_undo_int(teco_ed) |= TECO_ED_EXIT;
+}
+
+static void
+teco_state_macrofile_deprecated(teco_machine_main_t *ctx, GError **error)
+{
+ teco_interface_msg(TECO_MSG_WARNING,
+ "<EM> command is deprecated - use <EI> instead");
}
static teco_state_t *
@@ -2523,9 +2817,10 @@ teco_state_ecommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error
.modifier_at = TRUE, .modifier_colon = 1},
['G'] = {&teco_state_egcommand,
.modifier_at = TRUE, .modifier_colon = 1},
- ['I'] = {&teco_state_insert_nobuilding,
- .modifier_at = TRUE},
- ['M'] = {&teco_state_macrofile,
+ ['I'] = {&teco_state_indirect,
+ .modifier_at = TRUE, .modifier_colon = 1},
+ /* DEPRECATED: can be repurposed */
+ ['M'] = {&teco_state_indirect, teco_state_macrofile_deprecated,
.modifier_at = TRUE, .modifier_colon = 1},
['N'] = {&teco_state_glob_pattern,
.modifier_at = TRUE, .modifier_colon = 1},
@@ -2537,6 +2832,8 @@ teco_state_ecommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error
.modifier_at = TRUE, .modifier_colon = 1},
['W'] = {&teco_state_save_file,
.modifier_at = TRUE},
+ ['R'] = {&teco_state_read_file,
+ .modifier_at = TRUE},
/*
* Commands
@@ -2549,6 +2846,7 @@ teco_state_ecommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error
.modifier_colon = 1},
['E'] = {&teco_state_start, teco_state_ecommand_encoding,
.modifier_colon = 1},
+ ['O'] = {&teco_state_start, teco_state_ecommand_version},
['X'] = {&teco_state_start, teco_state_ecommand_exit,
.modifier_colon = 1},
};
@@ -2560,7 +2858,9 @@ teco_state_ecommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error
teco_ascii_toupper(chr), error);
}
-TECO_DEFINE_STATE_COMMAND(teco_state_ecommand);
+TECO_DEFINE_STATE_COMMAND(teco_state_ecommand,
+ .input_cb = (teco_state_input_cb_t)teco_state_ecommand_input
+);
gboolean
teco_state_insert_initial(teco_machine_main_t *ctx, GError **error)
@@ -2568,7 +2868,8 @@ teco_state_insert_initial(teco_machine_main_t *ctx, GError **error)
if (ctx->flags.mode > TECO_MODE_NORMAL)
return TRUE;
- teco_undo_gsize(teco_ranges[0].from) = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
+ sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
+ teco_undo_int(teco_ranges[0].from) = teco_interface_bytes2glyphs(pos);
teco_undo_guint(teco_ranges_count) = 1;
/*
@@ -2622,22 +2923,21 @@ teco_state_insert_initial(teco_machine_main_t *ctx, GError **error)
undo__teco_interface_ssm(SCI_UNDO, 0, 0);
/* This is done only now because it can _theoretically_ fail. */
- for (gint i = args; i > 0; i--)
- if (!teco_expressions_pop_num_calc(NULL, 0, error))
- return FALSE;
+ for (gint i = 0; i < args; i++)
+ teco_expressions_pop_num(0);
return TRUE;
}
gboolean
-teco_state_insert_process(teco_machine_main_t *ctx, const teco_string_t *str,
+teco_state_insert_process(teco_machine_main_t *ctx, teco_string_t str,
gsize new_chars, GError **error)
{
g_assert(new_chars > 0);
teco_interface_ssm(SCI_BEGINUNDOACTION, 0, 0);
teco_interface_ssm(SCI_ADDTEXT, new_chars,
- (sptr_t)(str->data + str->len - new_chars));
+ (sptr_t)(str.data + str.len - new_chars));
teco_interface_ssm(SCI_ENDUNDOACTION, 0, 0);
teco_ring_dirtify();
@@ -2648,11 +2948,13 @@ teco_state_insert_process(teco_machine_main_t *ctx, const teco_string_t *str,
}
teco_state_t *
-teco_state_insert_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_insert_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
- if (ctx->flags.mode == TECO_MODE_NORMAL)
- teco_undo_gsize(teco_ranges[0].to) = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
+ if (ctx->flags.mode > TECO_MODE_NORMAL)
+ return &teco_state_start;
+ sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
+ teco_undo_int(teco_ranges[0].to) = teco_interface_bytes2glyphs(pos);
return &teco_state_start;
}
@@ -2677,21 +2979,7 @@ teco_state_insert_done(teco_machine_main_t *ctx, const teco_string_t *str, GErro
* may be better, since it has string building characters
* disabled.
*/
-TECO_DEFINE_STATE_INSERT(teco_state_insert_building);
-
-/*$ EI
- * [c1,c2,...]EI[text]$ -- Insert text without string building characters
- *
- * Inserts text at the current position in the current
- * document.
- * This command is identical to the \fBI\fP command,
- * except that string building characters are \fBdisabled\fP.
- * Therefore it may be beneficial when editing \*(ST
- * macros.
- */
-TECO_DEFINE_STATE_INSERT(teco_state_insert_nobuilding,
- .expectstring.string_building = FALSE
-);
+TECO_DEFINE_STATE_INSERT(teco_state_insert);
static gboolean
teco_state_insert_indent_initial(teco_machine_main_t *ctx, GError **error)