/*
* Copyright (C) 2012-2025 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 "sciteco.h"
#include "parser.h"
#include "error.h"
#include "undo.h"
#include "expressions.h"
#include "interface.h"
#include "cmdline.h"
#include "core-commands.h"
#include "stdio-commands.h"
/**
* Check whether we are executing directly from the end of the command line.
* This works \b only when invoked from the initial_cb.
*/
static inline gboolean
teco_cmdline_is_executing(teco_machine_main_t *ctx)
{
return ctx == &teco_cmdline.machine &&
ctx->macro_pc == teco_cmdline.effective_len;
}
static gboolean is_executing = FALSE;
/**
* Print number from stack in the given radix.
*
* It must be popped manually, so we can call it multiple times
* on the same number.
*/
static gboolean
teco_print(teco_machine_main_t *ctx, guint radix, GError **error)
{
if (!teco_expressions_eval(FALSE, error))
return FALSE;
if (!teco_expressions_args()) {
teco_error_argexpected_set(error, "=");
return FALSE;
}
/*
* teco_expressions_format() cannot easily be used
* to format __unsigned__ integers.
*/
const gchar *fmt = "%" TECO_INT_MODIFIER "d";
switch (radix) {
case 8: fmt = "%" TECO_INT_MODIFIER "o"; break;
case 16: fmt = "%" TECO_INT_MODIFIER "X"; break;
}
gchar buf[32];
gint len = g_snprintf(buf, sizeof(buf), fmt, teco_expressions_peek_num(0));
g_assert(len > 0);
if (!teco_machine_main_eval_colon(ctx))
buf[len++] = '\n';
teco_interface_msg_literal(TECO_MSG_USER, buf, len);
return TRUE;
}
/*$ "=" "==" "===" ":=" ":==" ":===" "print number"
* = -- Print integer as message
* ==
* ===
* :=
* :==
* :===
*
* Shows integer as a message in the message line and/or
* on the console.
* One \(lq=\(rq formats the integer as a signed decimal number,
* \(lq==\(rq formats as an unsigned octal number and
* \(lq===\(rq as an unsigned hexadecimal number.
* It is logged with the user-message severity.
* The command fails if is not given.
*
* A noteworthy quirk is that \(lq==\(rq and \(lq===\(rq
* will print 2 or 3 numbers in succession when executed
* from interactive mode at the end of the command line
* in order to guarantee immediate feedback.
*
* If you want to print multiple values from the stack,
* you have to put the \(lq=\(rq into a pass-through loop
* or separate the commands with
* whitespace (e.g. \(lq^Y= =\(rq).
*
* If colon-modified the number is printed without a trailing
* linefeed.
*/
/*
* In order to imitate TECO-11 closely, we apply the lookahead
* strategy -- `=` and `==` are not executed immediately but only
* when a non-`=` character is parsed (cf. `$$` and `^C^C`).
* However, this would be very annoying during interactive
* execution, therefore we still print the number immediately
* and perhaps multiple times:
* Typing `===` prints the number first in decimal,
* then octal and finally in hexadecimal.
* This won't happen e.g. in a loop that is closed on the command-line.
*/
TECO_DECLARE_STATE(teco_state_print_octal);
static gboolean
teco_state_print_decimal_initial(teco_machine_main_t *ctx, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return TRUE;
is_executing = teco_cmdline_is_executing(ctx);
if (G_LIKELY(!is_executing))
return TRUE;
/*
* Interactive invocation:
* don't yet pop number as we may have to print it repeatedly
*/
return teco_print(ctx, 10, error);
}
static teco_state_t *
teco_state_print_decimal_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
{
if (chr == '=')
return &teco_state_print_octal;
if (ctx->flags.mode == TECO_MODE_NORMAL) {
if (G_LIKELY(!is_executing) && !teco_print(ctx, 10, error))
return NULL;
teco_expressions_pop_num(0);
}
return teco_state_start_input(ctx, chr, error);
}
/*
* Due to the deferred nature of `=`,
* it is valid to end in this state as well.
*/
static gboolean
teco_state_print_decimal_end_of_macro(teco_machine_main_t *ctx, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return TRUE;
if (G_UNLIKELY(is_executing))
return TRUE;
if (!teco_print(ctx, 10, error))
return FALSE;
teco_expressions_pop_num(0);
return TRUE;
}
TECO_DEFINE_STATE_START(teco_state_print_decimal,
.initial_cb = (teco_state_initial_cb_t)teco_state_print_decimal_initial,
.end_of_macro_cb = (teco_state_end_of_macro_cb_t)
teco_state_print_decimal_end_of_macro
);
static gboolean
teco_state_print_octal_initial(teco_machine_main_t *ctx, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return TRUE;
is_executing = teco_cmdline_is_executing(ctx);
if (G_LIKELY(!is_executing))
return TRUE;
/*
* Interactive invocation:
* don't yet pop number as we may have to print it repeatedly
*/
return teco_print(ctx, 8, error);
}
static teco_state_t *
teco_state_print_octal_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
{
if (chr == '=') {
if (ctx->flags.mode == TECO_MODE_NORMAL) {
if (!teco_print(ctx, 16, error))
return NULL;
teco_expressions_pop_num(0);
}
return &teco_state_start;
}
if (ctx->flags.mode == TECO_MODE_NORMAL) {
if (G_LIKELY(!is_executing) && !teco_print(ctx, 8, error))
return NULL;
teco_expressions_pop_num(0);
}
return teco_state_start_input(ctx, chr, error);
}
/*
* Due to the deferred nature of `==`,
* it is valid to end in this state as well.
*/
static gboolean
teco_state_print_octal_end_of_macro(teco_machine_main_t *ctx, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return TRUE;
if (G_UNLIKELY(is_executing))
return TRUE;
if (!teco_print(ctx, 8, error))
return FALSE;
teco_expressions_pop_num(0);
return TRUE;
}
TECO_DEFINE_STATE_START(teco_state_print_octal,
.initial_cb = (teco_state_initial_cb_t)teco_state_print_octal_initial,
.end_of_macro_cb = (teco_state_end_of_macro_cb_t)
teco_state_print_octal_end_of_macro
);
static gboolean
teco_state_print_string_initial(teco_machine_main_t *ctx, GError **error)
{
/*
* ^A differs from all other string-taking commands in having
* a default ^A escape char.
*/
if (ctx->parent.must_undo)
teco_undo_gunichar(ctx->expectstring.machine.escape_char);
ctx->expectstring.machine.escape_char = TECO_CTL_KEY('A');
if (ctx->flags.mode > TECO_MODE_NORMAL)
return TRUE;
teco_machine_stringbuilding_set_codepage(&ctx->expectstring.machine,
teco_machine_main_eval_colon(ctx)
? SC_CHARSET_ANSI : teco_default_codepage());
return TRUE;
}
static teco_state_t *
teco_state_print_string_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
{
teco_interface_msg_literal(TECO_MSG_USER, str->data, str->len);
return &teco_state_start;
}
/*$ "^A" ":^A" print "print string"
* ^A^A -- Print string as message
* @^A/string/
* :^A^A
*
* Print as a message, i.e. in the message line
* in interactive mode and if possible on the terminal (stdout) as well.
*
* \fB^A\fP differs from all other commands in the way
* is terminated.
* It is terminated by ^A (CTRL+A, ASCII 1) by default.
* While the initial \fB^A\fP can be written with upcarets,
* the terminating ^A must always be ASCII 1.
* You can however overwrite the terminator as usual
* by \fB@\fP-modifying the command.
*
* String-building characters are enabled for this command.
* \fB^A\fP outputs strings in the default codepage,
* but when colon modified raw ANSI encoding is enforced.
*/
/*
* NOTE: Codepage is among other things important for
* ^EUq, ^E<...> and case folding.
*/
TECO_DEFINE_STATE_EXPECTSTRING(teco_state_print_string,
.initial_cb = (teco_state_initial_cb_t)teco_state_print_string_initial
);
/*$ T type typeout
* [lines]T -- Type out buffer contents as messages
* -T
* from,toT
*
* Type out the next or previous number of from the buffer
* as a message, i.e. in the message line in interactive mode
* and if possible on the terminal (stdout) as well.
* 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 \fBK\fP
* command's arguments.
*/
void
teco_state_start_typeout(teco_machine_main_t *ctx, GError **error)
{
gsize from, len;
if (!teco_get_range_args("T", &from, &len, error))
return;
/*
* NOTE: This may remove the buffer gap since we need a consecutive
* piece of memory to log as a single message.
* FIXME: In batch mode even this could theoretically be avoided.
* Need to add a function like teco_interface_is_batch().
* Still, this is probably more efficient than using a temporary
* allocation with SCI_GETTEXTRANGEFULL.
*/
const gchar *str = (const gchar *)teco_interface_ssm(SCI_GETRANGEPOINTER, from, len);
teco_interface_msg_literal(TECO_MSG_USER, str, len);
}
/*$ "^T" ":^T" "typeout glyph" "get char"
* ^T -- Type out the numeric arguments as a message or get character from user
* :^T
* ^T -> codepoint
* :^T -> byte
*
* Types out characters for all the values
* on the argument stack (interpreted as codepoints) as messages,
* i.e. in the message line in interactive mode
* and if possible on the terminal (stdout) as well.
* It does so in the order of the arguments, i.e.
* is inserted before , ecetera.
* By default the codepoints are expected to be in the default
* codepage, but you can force raw ANSI encoding (for arbitrary
* bytes) by colon-modifying the command.
*
* When called without any argument, \fB^T\fP reads a key from the
* user (or from stdin) and returns the corresponding codepoint.
* If the default encoding is UTF-8, this will not work
* for function keys.
* If the default encoding is raw ANSI or if the command is
* colon-modified, \fB^T\fP returns raw bytes.
* When run in batch mode, this will return whatever byte is
* delivered by the attached terminal.
* In case stdin is closed, -1 is returned.
* In interactive mode, pressing CTRL+D or CTRL+C will also
* return -1.
*/
void
teco_state_control_typeout(teco_machine_main_t *ctx, GError **error)
{
if (!teco_expressions_eval(FALSE, error))
return;
gboolean utf8 = !teco_machine_main_eval_colon(ctx) &&
teco_default_codepage() == SC_CP_UTF8;
guint args = teco_expressions_args();
if (!args) {
teco_expressions_push(teco_interface_getch(utf8));
return;
}
if (!utf8) {
/* assume raw ANSI byte output */
g_autofree gchar *buf = g_malloc(args);
gchar *p = buf+args;
for (gint i = 0; i < args; i++) {
teco_int_t chr = teco_expressions_pop_num(0);
if (chr < 0 || chr > 0xFF) {
teco_error_codepoint_set(error, "^T");
return;
}
*--p = chr;
}
teco_interface_msg_literal(TECO_MSG_USER, p, args);
return;
}
/* 4 bytes should be enough for UTF-8, but we better follow the documentation */
g_autofree gchar *buf = g_malloc(args*6);
gchar *p = buf;
for (gint i = args; i > 0; i--) {
teco_int_t chr = teco_expressions_peek_num(i-1);
if (chr < 0 || !g_unichar_validate(chr)) {
teco_error_codepoint_set(error, "^T");
return;
}
p += g_unichar_to_utf8(chr, p);
}
/* we pop only now since we had to peek in reverse order */
for (gint i = 0; i < args; i++)
teco_expressions_pop_num(0);
teco_interface_msg_literal(TECO_MSG_USER, buf, p-buf);
}