/*
* Copyright (C) 2012-2023 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 .
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include
#include
#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 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 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
* 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 string argument,
* EQ makes the currently edited Q-Register.
* Otherwise, when is specified, it is the
* name of a file to read into Q-Register .
* When loading a file, the currently edited
* buffer/register is not changed and the edit position
* of register is reset to 0.
*
* Undefined Q-Registers will be defined.
* The command fails if 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$ -- Save Q-Register string to file
*
* Saves the string contents of Q-Register to
* .
* The 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 .
* 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
* Qq -> character
* :Qq -> -1 | size
*
* Without any arguments, get and return the integer-part of
* Q-Register .
*
* With one argument, return the code at
* from the string-part of Q-Register .
* 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
* to be defined and fail otherwise.
*
* When colon-modified, Q does not pop any arguments from
* the expression stack and returns the of the string
* in Q-Register if register exists (i.e. is defined).
* Naturally, for empty strings, 0 is returned.
* When colon-modified and Q-Register 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 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 .
* If 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
* with all the values on the expression stack (interpreted as
* codepoints).
* It does so in the order of the arguments, i.e.
* will be the first character in , the second, etc.
* Eventually the argument is appended to the
* register.
* Any existing string value in is overwritten by this operation.
*
* In the colon-modified form ^U does not overwrite existing
* contents of but only appends to it.
*
* If 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_ENDUNDOACTION, 0, 0);
teco_ring_dirtify();
if (teco_current_doc_must_undo())
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 into the buffer
* at its current position.
* Specifying an undefined 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, teco_num_sign, 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 to .
* \(lq-U\(rq is equivalent to \(lq-1U\(rq, otherwise
* the command fails if is missing.
*
* If the command is colon-modified, it returns a success
* boolean if or \(lq-\(rq is given.
* Otherwise it returns a failure boolean and does not
* modify .
*
* 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 to the integer part of register , returning
* its new value.
* 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 .
* 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 will be copied upon macro execution,
* so subsequent changes to Q-Register 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 into memory and execute its contents
* as a macro.
* It is otherwise similar to the \(lqM\(rq command.
*
* If 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 from the buffer
* into the Q-Register string.
* If is omitted, the sign prefix is implied.
* If two arguments are specified, the characters beginning
* at position up to the character at position
* 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 instead.
*
* Register will be created if it is undefined.
*/
TECO_DEFINE_STATE_EXPECTQREG(teco_state_copytoqreg,
.expectqreg.type = TECO_QREG_OPTIONAL_INIT
);