aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/qreg-commands.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/qreg-commands.c')
-rw-r--r--src/qreg-commands.c760
1 files changed, 760 insertions, 0 deletions
diff --git a/src/qreg-commands.c b/src/qreg-commands.c
new file mode 100644
index 0000000..35508d7
--- /dev/null
+++ b/src/qreg-commands.c
@@ -0,0 +1,760 @@
+/*
+ * Copyright (C) 2012-2021 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib.h>
+
+#include <Scintilla.h>
+
+#include "sciteco.h"
+#include "error.h"
+#include "file-utils.h"
+#include "expressions.h"
+#include "interface.h"
+#include "ring.h"
+#include "parser.h"
+#include "core-commands.h"
+#include "qreg.h"
+#include "qreg-commands.h"
+
+gboolean
+teco_state_expectqreg_initial(teco_machine_main_t *ctx, GError **error)
+{
+ teco_state_t *current = ctx->parent.current;
+
+ /*
+ * NOTE: We have to allocate a new instance always since `expectqreg`
+ * is part of an union.
+ */
+ ctx->expectqreg = teco_machine_qregspec_new(current->expectqreg.type, ctx->qreg_table_locals,
+ ctx->parent.must_undo);
+ if (ctx->parent.must_undo)
+ undo__teco_machine_qregspec_free(ctx->expectqreg);
+ return TRUE;
+}
+
+teco_state_t *
+teco_state_expectqreg_input(teco_machine_main_t *ctx, gchar chr, GError **error)
+{
+ teco_state_t *current = ctx->parent.current;
+
+ teco_qreg_t *qreg;
+ teco_qreg_table_t *table;
+
+ switch (teco_machine_qregspec_input(ctx->expectqreg, chr,
+ ctx->mode == TECO_MODE_NORMAL ? &qreg : NULL, &table, error)) {
+ case TECO_MACHINE_QREGSPEC_ERROR:
+ return NULL;
+ case TECO_MACHINE_QREGSPEC_MORE:
+ return current;
+ case TECO_MACHINE_QREGSPEC_DONE:
+ break;
+ }
+
+ /*
+ * NOTE: ctx->expectqreg is preserved since we may want to query it from follow-up
+ * states. This means, it must usually be stored manually in got_register_cb() via:
+ * teco_state_expectqreg_reset(ctx);
+ */
+ return current->expectqreg.got_register_cb(ctx, qreg, table, error);
+}
+
+static teco_state_t *
+teco_state_pushqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
+ teco_qreg_table_t *table, GError **error)
+{
+ teco_state_expectqreg_reset(ctx);
+
+ return ctx->mode == TECO_MODE_NORMAL &&
+ !teco_qreg_stack_push(qreg, error) ? NULL : &teco_state_start;
+}
+
+/*$ "[" "[q" push
+ * [q -- Save Q-Register
+ *
+ * Save Q-Register <q> contents on the global Q-Register push-down
+ * stack.
+ */
+TECO_DEFINE_STATE_EXPECTQREG(teco_state_pushqreg);
+
+static teco_state_t *
+teco_state_popqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
+ teco_qreg_table_t *table, GError **error)
+{
+ teco_state_expectqreg_reset(ctx);
+
+ return ctx->mode == TECO_MODE_NORMAL &&
+ !teco_qreg_stack_pop(qreg, error) ? NULL : &teco_state_start;
+}
+
+/*$ "]" "]q" pop
+ * ]q -- Restore Q-Register
+ *
+ * Restore Q-Register <q> by replacing its contents
+ * with the contents of the register saved on top of
+ * the Q-Register push-down stack.
+ * The stack entry is popped.
+ *
+ * In interactive mode, the original contents of <q>
+ * are not immediately reclaimed but are kept in memory
+ * to support rubbing out the command.
+ * Memory is reclaimed on command-line termination.
+ */
+TECO_DEFINE_STATE_EXPECTQREG(teco_state_popqreg,
+ .expectqreg.type = TECO_QREG_OPTIONAL_INIT
+);
+
+static teco_state_t *
+teco_state_eqcommand_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
+ teco_qreg_table_t *table, GError **error)
+{
+ /*
+ * NOTE: We will query ctx->expectqreg later in teco_state_loadqreg_done().
+ */
+ return &teco_state_loadqreg;
+}
+
+TECO_DEFINE_STATE_EXPECTQREG(teco_state_eqcommand,
+ .expectqreg.type = TECO_QREG_OPTIONAL_INIT
+);
+
+static teco_state_t *
+teco_state_loadqreg_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ teco_qreg_t *qreg;
+ teco_qreg_table_t *table;
+
+ teco_machine_qregspec_get_results(ctx->expectqreg, &qreg, &table);
+ teco_state_expectqreg_reset(ctx);
+
+ if (ctx->mode > TECO_MODE_NORMAL)
+ return &teco_state_start;
+
+ if (str->len > 0) {
+ /* Load file into Q-Register */
+ g_autofree gchar *filename = teco_file_expand_path(str->data);
+ if (!teco_qreg_load(qreg, filename, error))
+ return NULL;
+ } else {
+ /* Edit Q-Register */
+ if (!teco_current_doc_undo_edit(error) ||
+ !teco_qreg_table_edit(table, qreg, error))
+ return NULL;
+ }
+
+ return &teco_state_start;
+}
+
+/*$ EQ EQq
+ * EQq$ -- Edit or load Q-Register
+ * EQq[file]$
+ *
+ * When specified with an empty <file> string argument,
+ * EQ makes <q> the currently edited Q-Register.
+ * Otherwise, when <file> is specified, it is the
+ * name of a file to read into Q-Register <q>.
+ * When loading a file, the currently edited
+ * buffer/register is not changed and the edit position
+ * of register <q> is reset to 0.
+ *
+ * Undefined Q-Registers will be defined.
+ * The command fails if <file> could not be read.
+ */
+TECO_DEFINE_STATE_EXPECTFILE(teco_state_loadqreg);
+
+static teco_state_t *
+teco_state_epctcommand_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
+ teco_qreg_table_t *table, GError **error)
+{
+ /*
+ * NOTE: We will query ctx->expectqreg later in teco_state_saveqreg_done().
+ */
+ return &teco_state_saveqreg;
+}
+
+TECO_DEFINE_STATE_EXPECTQREG(teco_state_epctcommand);
+
+static teco_state_t *
+teco_state_saveqreg_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ teco_qreg_t *qreg;
+
+ teco_machine_qregspec_get_results(ctx->expectqreg, &qreg, NULL);
+ teco_state_expectqreg_reset(ctx);
+
+ if (ctx->mode > TECO_MODE_NORMAL)
+ return &teco_state_start;
+
+ g_autofree gchar *filename = teco_file_expand_path(str->data);
+ return teco_qreg_save(qreg, filename, error) ? &teco_state_start : NULL;
+}
+
+/*$ E% E%q
+ * E%q<file>$ -- Save Q-Register string to file
+ *
+ * Saves the string contents of Q-Register <q> to
+ * <file>.
+ * The <file> must always be specified, as Q-Registers
+ * have no notion of associated file names.
+ *
+ * In interactive mode, the E% command may be rubbed out,
+ * restoring the previous state of <file>.
+ * This follows the same rules as with the \fBEW\fP command.
+ *
+ * File names may also be tab-completed and string building
+ * characters are enabled by default.
+ */
+TECO_DEFINE_STATE_EXPECTFILE(teco_state_saveqreg);
+
+static gboolean
+teco_state_queryqreg_initial(teco_machine_main_t *ctx, GError **error)
+{
+ /*
+ * This prevents teco_state_queryqreg_got_register() from having to check
+ * for Q-Register existence, resulting in better error messages in case of
+ * required Q-Registers.
+ * In parse-only mode, the type does not matter.
+ */
+ teco_qreg_type_t type = ctx->modifier_colon ? TECO_QREG_OPTIONAL : TECO_QREG_REQUIRED;
+
+ /*
+ * NOTE: We have to allocate a new instance always since `expectqreg`
+ * is part of an union.
+ */
+ ctx->expectqreg = teco_machine_qregspec_new(type, ctx->qreg_table_locals,
+ ctx->parent.must_undo);
+ if (ctx->parent.must_undo)
+ undo__teco_machine_qregspec_free(ctx->expectqreg);
+ return TRUE;
+}
+
+static teco_state_t *
+teco_state_queryqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
+ teco_qreg_table_t *table, GError **error)
+{
+ teco_state_expectqreg_reset(ctx);
+
+ if (ctx->mode > TECO_MODE_NORMAL)
+ return &teco_state_start;
+
+ if (!teco_expressions_eval(FALSE, error))
+ return NULL;
+
+ if (teco_machine_main_eval_colon(ctx)) {
+ /* Query Q-Register's existence or string size */
+ if (qreg) {
+ gsize len;
+
+ if (!qreg->vtable->get_string(qreg, NULL, &len, error))
+ return NULL;
+ teco_expressions_push(len);
+ } else {
+ teco_expressions_push(-1);
+ }
+
+ return &teco_state_start;
+ }
+
+ if (teco_expressions_args() > 0) {
+ /* Query character from Q-Register string */
+ teco_int_t pos;
+ if (!teco_expressions_pop_num_calc(&pos, 0, error))
+ return NULL;
+ if (pos < 0) {
+ teco_error_range_set(error, "Q");
+ return NULL;
+ }
+
+ gint c = qreg->vtable->get_character(qreg, pos, error);
+ if (c < 0)
+ return NULL;
+
+ teco_expressions_push(c);
+ } else {
+ /* Query integer */
+ teco_int_t value;
+
+ if (!qreg->vtable->get_integer(qreg, &value, error))
+ return NULL;
+ teco_expressions_push(value);
+ }
+
+ return &teco_state_start;
+}
+
+/*$ Q Qq query
+ * Qq -> n -- Query Q-Register existence, its integer or string characters
+ * <position>Qq -> character
+ * :Qq -> -1 | size
+ *
+ * Without any arguments, get and return the integer-part of
+ * Q-Register <q>.
+ *
+ * With one argument, return the <character> code at <position>
+ * from the string-part of Q-Register <q>.
+ * Positions are handled like buffer positions \(em they
+ * begin at 0 up to the length of the string minus 1.
+ * An error is thrown for invalid positions.
+ * Both non-colon-modified forms of Q require register <q>
+ * to be defined and fail otherwise.
+ *
+ * When colon-modified, Q does not pop any arguments from
+ * the expression stack and returns the <size> of the string
+ * in Q-Register <q> if register <q> exists (i.e. is defined).
+ * Naturally, for empty strings, 0 is returned.
+ * When colon-modified and Q-Register <q> is undefined,
+ * -1 is returned instead.
+ * Therefore checking the return value \fB:Q\fP for values smaller
+ * 0 allows checking the existence of a register.
+ * Note that if <q> exists, its string part is not initialized,
+ * so \fB:Q\fP may be used to handle purely numeric data structures
+ * without creating Scintilla documents by accident.
+ * These semantics allow the useful idiom \(lq:Q\fIq\fP">\(rq for
+ * checking whether a Q-Register exists and has a non-empty string.
+ * Note also that the return value of \fB:Q\fP may be interpreted
+ * as a condition boolean that represents the non-existence of <q>.
+ * If <q> is undefined, it returns \fIsuccess\fP, else a \fIfailure\fP
+ * boolean.
+ */
+TECO_DEFINE_STATE_EXPECTQREG(teco_state_queryqreg,
+ .initial_cb = (teco_state_initial_cb_t)teco_state_queryqreg_initial
+);
+
+static teco_state_t *
+teco_state_ctlucommand_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
+ teco_qreg_table_t *table, GError **error)
+{
+ /*
+ * NOTE: We will query ctx->expectqreg later in teco_state_setqregstring_nobuilding_done().
+ */
+ return &teco_state_setqregstring_nobuilding;
+}
+
+TECO_DEFINE_STATE_EXPECTQREG(teco_state_ctlucommand,
+ .expectqreg.type = TECO_QREG_OPTIONAL_INIT
+);
+
+static teco_state_t *
+teco_state_setqregstring_nobuilding_done(teco_machine_main_t *ctx,
+ const teco_string_t *str, GError **error)
+{
+ teco_qreg_t *qreg;
+
+ teco_machine_qregspec_get_results(ctx->expectqreg, &qreg, NULL);
+ teco_state_expectqreg_reset(ctx);
+
+ if (ctx->mode > TECO_MODE_NORMAL)
+ return &teco_state_start;
+
+ gboolean colon_modified = teco_machine_main_eval_colon(ctx);
+
+ if (!teco_expressions_eval(FALSE, error))
+ return NULL;
+ gint args = teco_expressions_args();
+
+ if (args > 0) {
+ g_autofree gchar *buffer = g_malloc(args);
+
+ for (gint i = args; i > 0; i--) {
+ teco_int_t v;
+ if (!teco_expressions_pop_num_calc(&v, 0, error))
+ return NULL;
+ buffer[i-1] = (gchar)v;
+ }
+
+ if (colon_modified) {
+ /* append to register */
+ if (!qreg->vtable->undo_append_string(qreg, error) ||
+ !qreg->vtable->append_string(qreg, buffer, args, error))
+ return NULL;
+ } else {
+ /* set register */
+ if (!qreg->vtable->undo_set_string(qreg, error) ||
+ !qreg->vtable->set_string(qreg, buffer, args, error))
+ return NULL;
+ }
+ }
+
+ if (args > 0 || colon_modified) {
+ /* append to register */
+ if (!qreg->vtable->undo_append_string(qreg, error) ||
+ !qreg->vtable->append_string(qreg, str->data, str->len, error))
+ return NULL;
+ } else {
+ /* set register */
+ if (!qreg->vtable->undo_set_string(qreg, error) ||
+ !qreg->vtable->set_string(qreg, str->data, str->len, error))
+ return NULL;
+ }
+
+ return &teco_state_start;
+}
+
+/*$ ^Uq
+ * [c1,c2,...]^Uq[string]$ -- Set or append to Q-Register string without string building
+ * [c1,c2,...]:^Uq[string]$
+ *
+ * If not colon-modified, it first fills the Q-Register <q>
+ * with all the values on the expression stack (interpreted as
+ * codepoints).
+ * It does so in the order of the arguments, i.e.
+ * <c1> will be the first character in <q>, <c2> the second, etc.
+ * Eventually the <string> argument is appended to the
+ * register.
+ * Any existing string value in <q> is overwritten by this operation.
+ *
+ * In the colon-modified form ^U does not overwrite existing
+ * contents of <q> but only appends to it.
+ *
+ * If <q> is undefined, it will be defined.
+ *
+ * String-building characters are \fBdisabled\fP for ^U
+ * commands.
+ * Therefore they are especially well-suited for defining
+ * \*(ST macros, since string building characters in the
+ * desired Q-Register contents do not have to be escaped.
+ * The \fBEU\fP command may be used where string building
+ * is desired.
+ */
+TECO_DEFINE_STATE_EXPECTSTRING(teco_state_setqregstring_nobuilding,
+ .expectstring.string_building = FALSE
+);
+
+static teco_state_t *
+teco_state_eucommand_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
+ teco_qreg_table_t *table, GError **error)
+{
+ /*
+ * NOTE: We will query ctx->expectqreg later in teco_state_setqregstring_building_done().
+ */
+ return &teco_state_setqregstring_building;
+}
+
+TECO_DEFINE_STATE_EXPECTQREG(teco_state_eucommand,
+ .expectqreg.type = TECO_QREG_OPTIONAL_INIT
+);
+
+static teco_state_t *
+teco_state_setqregstring_building_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ return teco_state_setqregstring_nobuilding_done(ctx, str, error);
+}
+
+/*$ EU EUq
+ * [c1,c2,...]EUq[string]$ -- Set or append to Q-Register string with string building characters
+ * [c1,c2,...]:EUq[string]$
+ *
+ * This command sets or appends to the contents of
+ * Q-Register \fIq\fP.
+ * It is identical to the \fB^U\fP command, except
+ * that this form of the command has string building
+ * characters \fBenabled\fP.
+ */
+TECO_DEFINE_STATE_EXPECTSTRING(teco_state_setqregstring_building,
+ .expectstring.string_building = TRUE
+);
+
+static teco_state_t *
+teco_state_getqregstring_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
+ teco_qreg_table_t *table, GError **error)
+{
+ teco_state_expectqreg_reset(ctx);
+
+ if (ctx->mode > TECO_MODE_NORMAL)
+ return &teco_state_start;
+
+ g_auto(teco_string_t) str = {NULL, 0};
+
+ if (!qreg->vtable->get_string(qreg, &str.data, &str.len, error))
+ return NULL;
+
+ if (str.len > 0) {
+ teco_interface_ssm(SCI_BEGINUNDOACTION, 0, 0);
+ teco_interface_ssm(SCI_ADDTEXT, str.len, (sptr_t)str.data);
+ teco_interface_ssm(SCI_SCROLLCARET, 0, 0);
+ teco_interface_ssm(SCI_ENDUNDOACTION, 0, 0);
+ teco_ring_dirtify();
+
+ undo__teco_interface_ssm(SCI_UNDO, 0, 0);
+ }
+
+ return &teco_state_start;
+}
+
+/*$ G Gq get
+ * Gq -- Insert Q-Register string
+ *
+ * Inserts the string of Q-Register <q> into the buffer
+ * at its current position.
+ * Specifying an undefined <q> yields an error.
+ */
+TECO_DEFINE_STATE_EXPECTQREG(teco_state_getqregstring);
+
+static teco_state_t *
+teco_state_setqreginteger_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
+ teco_qreg_table_t *table, GError **error)
+{
+ teco_state_expectqreg_reset(ctx);
+
+ if (ctx->mode > TECO_MODE_NORMAL)
+ return &teco_state_start;
+
+ if (!teco_expressions_eval(FALSE, error))
+ return NULL;
+ if (teco_expressions_args() || teco_num_sign < 0) {
+ teco_int_t v;
+ if (!teco_expressions_pop_num_calc(&v, 0, error) ||
+ !qreg->vtable->undo_set_integer(qreg, error) ||
+ !qreg->vtable->set_integer(qreg, v, error))
+ return NULL;
+
+ if (teco_machine_main_eval_colon(ctx))
+ teco_expressions_push(TECO_SUCCESS);
+ } else if (teco_machine_main_eval_colon(ctx)) {
+ teco_expressions_push(TECO_FAILURE);
+ } else {
+ teco_error_argexpected_set(error, "U");
+ return NULL;
+ }
+
+ return &teco_state_start;
+}
+
+/*$ U Uq
+ * nUq -- Set Q-Register integer
+ * -Uq
+ * [n]:Uq -> Success|Failure
+ *
+ * Sets the integer-part of Q-Register <q> to <n>.
+ * \(lq-U\(rq is equivalent to \(lq-1U\(rq, otherwise
+ * the command fails if <n> is missing.
+ *
+ * If the command is colon-modified, it returns a success
+ * boolean if <n> or \(lq-\(rq is given.
+ * Otherwise it returns a failure boolean and does not
+ * modify <q>.
+ *
+ * The register is defined if it does not exist.
+ */
+TECO_DEFINE_STATE_EXPECTQREG(teco_state_setqreginteger,
+ .expectqreg.type = TECO_QREG_OPTIONAL_INIT
+);
+
+static teco_state_t *
+teco_state_increaseqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
+ teco_qreg_table_t *table, GError **error)
+{
+ teco_state_expectqreg_reset(ctx);
+
+ if (ctx->mode > TECO_MODE_NORMAL)
+ return &teco_state_start;
+
+ teco_int_t value, add;
+
+ if (!qreg->vtable->undo_set_integer(qreg, error) ||
+ !qreg->vtable->get_integer(qreg, &value, error) ||
+ !teco_expressions_pop_num_calc(&add, teco_num_sign, error) ||
+ !qreg->vtable->set_integer(qreg, value += add, error))
+ return NULL;
+ teco_expressions_push(value);
+
+ return &teco_state_start;
+}
+
+/*$ % %q increment
+ * [n]%q -> q+n -- Increase Q-Register integer
+ *
+ * Add <n> to the integer part of register <q>, returning
+ * its new value.
+ * <q> will be defined if it does not exist.
+ */
+TECO_DEFINE_STATE_EXPECTQREG(teco_state_increaseqreg,
+ .expectqreg.type = TECO_QREG_OPTIONAL_INIT
+);
+
+static teco_state_t *
+teco_state_macro_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
+ teco_qreg_table_t *table, GError **error)
+{
+ teco_state_expectqreg_reset(ctx);
+
+ if (ctx->mode > TECO_MODE_NORMAL)
+ return &teco_state_start;
+
+ if (teco_machine_main_eval_colon(ctx)) {
+ /* don't create new local Q-Registers if colon modifier is given */
+ if (!teco_qreg_execute(qreg, ctx->qreg_table_locals, error))
+ return NULL;
+ } else {
+ g_auto(teco_qreg_table_t) table;
+ teco_qreg_table_init(&table, FALSE);
+ if (!teco_qreg_execute(qreg, &table, error))
+ return NULL;
+ }
+
+ return &teco_state_start;
+}
+
+/*$ M Mq eval
+ * Mq -- Execute macro
+ * :Mq
+ *
+ * Execute macro stored in string of Q-Register <q>.
+ * The command itself does not push or pop and arguments from the stack
+ * but the macro executed might well do so.
+ * The new macro invocation level will contain its own go-to label table
+ * and local Q-Register table.
+ * Except when the command is colon-modified - in this case, local
+ * Q-Registers referenced in the macro refer to the parent macro-level's
+ * local Q-Register table (or whatever level defined one last).
+ *
+ * Errors during the macro execution will propagate to the M command.
+ * In other words if a command in the macro fails, the M command will fail
+ * and this failure propagates until the top-level macro (e.g.
+ * the command-line macro).
+ *
+ * Note that the string of <q> will be copied upon macro execution,
+ * so subsequent changes to Q-Register <q> from inside the macro do
+ * not modify the executed code.
+ */
+TECO_DEFINE_STATE_EXPECTQREG(teco_state_macro);
+
+static teco_state_t *
+teco_state_macrofile_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ if (ctx->mode > TECO_MODE_NORMAL)
+ return &teco_state_start;
+
+ g_autofree gchar *filename = teco_file_expand_path(str->data);
+
+ if (teco_machine_main_eval_colon(ctx)) {
+ /* don't create new local Q-Registers if colon modifier is given */
+ if (!teco_execute_file(filename, ctx->qreg_table_locals, error))
+ return NULL;
+ } else {
+ g_auto(teco_qreg_table_t) table;
+ teco_qreg_table_init(&table, FALSE);
+ if (!teco_execute_file(filename, &table, error))
+ return NULL;
+ }
+
+ return &teco_state_start;
+}
+
+/*$ EM
+ * EMfile$ -- Execute macro from file
+ * :EMfile$
+ *
+ * Read the file with name <file> into memory and execute its contents
+ * as a macro.
+ * It is otherwise similar to the \(lqM\(rq command.
+ *
+ * If <file> could not be read, the command yields an error.
+ */
+TECO_DEFINE_STATE_EXPECTFILE(teco_state_macrofile);
+
+static teco_state_t *
+teco_state_copytoqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
+ teco_qreg_table_t *table, GError **error)
+{
+ teco_state_expectqreg_reset(ctx);
+
+ if (ctx->mode > TECO_MODE_NORMAL)
+ return &teco_state_start;
+
+ teco_int_t from, len;
+
+ if (!teco_expressions_eval(FALSE, error))
+ return NULL;
+ if (teco_expressions_args() <= 1) {
+ teco_int_t line;
+
+ from = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
+ if (!teco_expressions_pop_num_calc(&line, teco_num_sign, error))
+ return NULL;
+ line += teco_interface_ssm(SCI_LINEFROMPOSITION, from, 0);
+
+ if (!teco_validate_line(line)) {
+ teco_error_range_set(error, "X");
+ return NULL;
+ }
+
+ len = teco_interface_ssm(SCI_POSITIONFROMLINE, line, 0) - from;
+
+ if (len < 0) {
+ from += len;
+ len *= -1;
+ }
+ } else {
+ teco_int_t to = teco_expressions_pop_num(0);
+ from = teco_expressions_pop_num(0);
+
+ len = to - from;
+
+ if (len < 0 || !teco_validate_pos(from) || !teco_validate_pos(to)) {
+ teco_error_range_set(error, "X");
+ return NULL;
+ }
+ }
+
+ g_autofree gchar *str = g_malloc(len + 1);
+
+ struct Sci_TextRange text_range = {
+ .chrg = {.cpMin = from, .cpMax = from + len},
+ .lpstrText = str
+ };
+ teco_interface_ssm(SCI_GETTEXTRANGE, 0, (sptr_t)&text_range);
+
+ if (teco_machine_main_eval_colon(ctx)) {
+ if (!qreg->vtable->undo_append_string(qreg, error) ||
+ !qreg->vtable->append_string(qreg, str, len, error))
+ return NULL;
+ } else {
+ if (!qreg->vtable->undo_set_string(qreg, error) ||
+ !qreg->vtable->set_string(qreg, str, len, error))
+ return NULL;
+ }
+
+ return &teco_state_start;
+}
+
+/*$ X Xq
+ * [lines]Xq -- Copy into or append to Q-Register
+ * -Xq
+ * from,toXq
+ * [lines]:Xq
+ * -:Xq
+ * from,to:Xq
+ *
+ * Copy the next or previous number of <lines> from the buffer
+ * into the Q-Register <q> string.
+ * 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>
+ * are copied.
+ * The semantics of the arguments is analogous to the K
+ * command's arguments.
+ * If the command is colon-modified, the characters will be
+ * appended to the end of register <q> instead.
+ *
+ * Register <q> will be created if it is undefined.
+ */
+TECO_DEFINE_STATE_EXPECTQREG(teco_state_copytoqreg,
+ .expectqreg.type = TECO_QREG_OPTIONAL_INIT
+);