/*
* Copyright (C) 2012-2024 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
#include
#include
#include "sciteco.h"
#include "string-utils.h"
#include "file-utils.h"
#include "interface.h"
#include "cmdline.h"
#include "view.h"
#include "undo.h"
#include "parser.h"
#include "core-commands.h"
#include "expressions.h"
#include "doc.h"
#include "ring.h"
#include "eol.h"
#include "error.h"
#include "qreg.h"
/**
* View used for editing Q-Registers.
* Initialized in main.c after the interface.
*/
teco_view_t *teco_qreg_view = NULL;
/** Table of currently edited Q-Register */
const teco_qreg_table_t *teco_qreg_table_current = NULL;
/** Currently edited Q-Register */
teco_qreg_t *teco_qreg_current = NULL;
/**
* Table for global Q-Registers.
* Initialized in main.c after the interface.
*/
teco_qreg_table_t teco_qreg_table_globals;
/** @private @static @memberof teco_qreg_t */
static teco_qreg_t *
teco_qreg_new(teco_qreg_vtable_t *vtable, const gchar *name, gsize len)
{
/*
* FIXME: Test with g_slice_new()...
* It could however cause problems upon command-line termination
* and may not be measurably faster.
*/
teco_qreg_t *qreg = g_new0(teco_qreg_t, 1);
qreg->vtable = vtable;
/*
* NOTE: This does not use GStringChunk/teco_string_init_chunk()
* since we want to implement Q-Register removing soon.
* Even without that, individual Q-Regs can be removed on rubout.
*/
teco_string_init(&qreg->head.name, name, len);
teco_doc_init(&qreg->string);
return qreg;
}
/** @memberof teco_qreg_t */
gboolean
teco_qreg_execute(teco_qreg_t *qreg, teco_qreg_table_t *qreg_table_locals, GError **error)
{
g_auto(teco_string_t) macro = {NULL, 0};
/*
* SciTECO macros must be in UTF-8, but we don't check the encoding,
* so as not to complicate TECO_ED_DEFAULT_ANSI mode.
* The UTF-8 byte sequences are checked anyway.
*/
if (!qreg->vtable->get_string(qreg, ¯o.data, ¯o.len, NULL, error) ||
!teco_execute_macro(macro.data, macro.len, qreg_table_locals, error)) {
teco_error_add_frame_qreg(qreg->head.name.data, qreg->head.name.len);
return FALSE;
}
return TRUE;
}
/** @memberof teco_qreg_t */
void
teco_qreg_undo_set_eol_mode(teco_qreg_t *qreg)
{
if (!qreg->must_undo)
return;
/*
* Necessary, so that upon rubout the
* string's parameters are restored.
*/
teco_doc_update(&qreg->string, teco_qreg_view);
if (teco_qreg_current && teco_qreg_current->must_undo) // FIXME
teco_doc_undo_edit(&teco_qreg_current->string);
undo__teco_view_ssm(teco_qreg_view, SCI_SETEOLMODE,
teco_view_ssm(teco_qreg_view, SCI_GETEOLMODE, 0, 0), 0);
teco_doc_undo_edit(&qreg->string);
}
/** @memberof teco_qreg_t */
void
teco_qreg_set_eol_mode(teco_qreg_t *qreg, gint mode)
{
if (teco_qreg_current)
teco_doc_update(&teco_qreg_current->string, teco_qreg_view);
teco_doc_edit(&qreg->string, teco_default_codepage());
teco_view_ssm(teco_qreg_view, SCI_SETEOLMODE, mode, 0);
if (teco_qreg_current)
teco_doc_edit(&teco_qreg_current->string, 0);
}
static gboolean
teco_qreg_plain_set_integer(teco_qreg_t *qreg, teco_int_t value, GError **error)
{
qreg->integer = value;
return TRUE;
}
static gboolean
teco_qreg_plain_undo_set_integer(teco_qreg_t *qreg, GError **error)
{
if (qreg->must_undo) // FIXME
teco_undo_int(qreg->integer);
return TRUE;
}
static gboolean
teco_qreg_plain_get_integer(teco_qreg_t *qreg, teco_int_t *ret, GError **error)
{
*ret = qreg->integer;
return TRUE;
}
static gboolean
teco_qreg_plain_set_string(teco_qreg_t *qreg, const gchar *str, gsize len,
guint codepage, GError **error)
{
teco_doc_set_string(&qreg->string, str, len, codepage);
return TRUE;
}
static gboolean
teco_qreg_plain_undo_set_string(teco_qreg_t *qreg, GError **error)
{
if (qreg->must_undo) // FIXME
teco_doc_undo_set_string(&qreg->string);
return TRUE;
}
static gboolean
teco_qreg_plain_append_string(teco_qreg_t *qreg, const gchar *str, gsize len, GError **error)
{
/*
* NOTE: Will not create undo action if string is empty.
* Also, appending preserves the string's parameters.
*/
if (!len)
return TRUE;
if (teco_qreg_current)
teco_doc_update(&teco_qreg_current->string, teco_qreg_view);
teco_doc_edit(&qreg->string, teco_default_codepage());
teco_view_ssm(teco_qreg_view, SCI_BEGINUNDOACTION, 0, 0);
teco_view_ssm(teco_qreg_view, SCI_APPENDTEXT, len, (sptr_t)str);
teco_view_ssm(teco_qreg_view, SCI_ENDUNDOACTION, 0, 0);
if (teco_qreg_current)
teco_doc_edit(&teco_qreg_current->string, 0);
return TRUE;
}
static gboolean
teco_qreg_plain_get_string(teco_qreg_t *qreg, gchar **str, gsize *len,
guint *codepage, GError **error)
{
teco_doc_get_string(&qreg->string, str, len, codepage);
return TRUE;
}
static gboolean
teco_qreg_plain_get_character(teco_qreg_t *qreg, teco_int_t position,
teco_int_t *chr, GError **error)
{
if (teco_qreg_current)
teco_doc_update(&teco_qreg_current->string, teco_qreg_view);
teco_doc_edit(&qreg->string, teco_default_codepage());
sptr_t len = teco_view_ssm(teco_qreg_view, SCI_GETLENGTH, 0, 0);
gssize off = teco_view_glyphs2bytes(teco_qreg_view, position);
gboolean ret = off >= 0 && off != len;
if (!ret)
g_set_error(error, TECO_ERROR, TECO_ERROR_RANGE,
"Position %" TECO_INT_FORMAT " out of range", position);
/* make sure we still restore the current Q-Register */
else
*chr = teco_view_get_character(teco_qreg_view, off, len);
if (teco_qreg_current)
teco_doc_edit(&teco_qreg_current->string, 0);
return ret;
}
static teco_int_t
teco_qreg_plain_get_length(teco_qreg_t *qreg, GError **error)
{
if (teco_qreg_current)
teco_doc_update(&teco_qreg_current->string, teco_qreg_view);
teco_doc_edit(&qreg->string, teco_default_codepage());
sptr_t len = teco_view_ssm(teco_qreg_view, SCI_GETLENGTH, 0, 0);
teco_int_t ret = teco_view_bytes2glyphs(teco_qreg_view, len);
if (teco_qreg_current)
teco_doc_edit(&teco_qreg_current->string, 0);
return ret;
}
static gboolean
teco_qreg_plain_exchange_string(teco_qreg_t *qreg, teco_doc_t *src, GError **error)
{
teco_doc_exchange(&qreg->string, src);
return TRUE;
}
static gboolean
teco_qreg_plain_undo_exchange_string(teco_qreg_t *qreg, teco_doc_t *src, GError **error)
{
if (qreg->must_undo) // FIXME
teco_doc_undo_exchange(&qreg->string);
teco_doc_undo_exchange(src);
return TRUE;
}
static gboolean
teco_qreg_plain_edit(teco_qreg_t *qreg, GError **error)
{
if (teco_qreg_current)
teco_doc_update(&teco_qreg_current->string, teco_qreg_view);
teco_doc_edit(&qreg->string, teco_default_codepage());
teco_interface_show_view(teco_qreg_view);
teco_interface_info_update(qreg);
return TRUE;
}
static gboolean
teco_qreg_plain_undo_edit(teco_qreg_t *qreg, GError **error)
{
/*
* We might be switching the current document
* to a buffer.
*/
teco_doc_update(&qreg->string, teco_qreg_view);
if (!qreg->must_undo) // FIXME
return TRUE;
undo__teco_interface_info_update_qreg(qreg);
teco_doc_undo_edit(&qreg->string);
undo__teco_interface_show_view(teco_qreg_view);
return TRUE;
}
static gboolean
teco_qreg_plain_load(teco_qreg_t *qreg, const gchar *filename, GError **error)
{
if (!qreg->vtable->undo_set_string(qreg, error))
return FALSE;
if (teco_qreg_current)
teco_doc_update(&teco_qreg_current->string, teco_qreg_view);
teco_doc_edit(&qreg->string, teco_default_codepage());
teco_doc_reset(&qreg->string);
/*
* teco_view_load() might change the EOL style.
*/
teco_qreg_undo_set_eol_mode(qreg);
/*
* undo_set_string() pushes undo tokens that restore
* the previous document in the view.
* So if loading fails, teco_qreg_current will be
* made the current document again.
*/
if (!teco_view_load(teco_qreg_view, filename, error))
return FALSE;
if (teco_qreg_current)
teco_doc_edit(&teco_qreg_current->string, 0);
return TRUE;
}
static gboolean
teco_qreg_plain_save(teco_qreg_t *qreg, const gchar *filename, GError **error)
{
if (teco_qreg_current)
teco_doc_update(&teco_qreg_current->string, teco_qreg_view);
teco_doc_edit(&qreg->string, teco_default_codepage());
gboolean ret = teco_view_save(teco_qreg_view, filename, error);
if (teco_qreg_current)
teco_doc_edit(&teco_qreg_current->string, 0);
return ret;
}
/**
* Initializer for vtables of Q-Registers with "plain" storage of strings.
* These store their string part as teco_docs.
*/
#define TECO_INIT_QREG(...) { \
.set_integer = teco_qreg_plain_set_integer, \
.undo_set_integer = teco_qreg_plain_undo_set_integer, \
.get_integer = teco_qreg_plain_get_integer, \
.set_string = teco_qreg_plain_set_string, \
.undo_set_string = teco_qreg_plain_undo_set_string, \
.append_string = teco_qreg_plain_append_string, \
.undo_append_string = teco_qreg_plain_undo_set_string, \
.get_string = teco_qreg_plain_get_string, \
.get_character = teco_qreg_plain_get_character, \
.get_length = teco_qreg_plain_get_length, \
.exchange_string = teco_qreg_plain_exchange_string, \
.undo_exchange_string = teco_qreg_plain_undo_exchange_string, \
.edit = teco_qreg_plain_edit, \
.undo_edit = teco_qreg_plain_undo_edit, \
.load = teco_qreg_plain_load, \
.save = teco_qreg_plain_save, \
##__VA_ARGS__ \
}
/** @static @memberof teco_qreg_t */
teco_qreg_t *
teco_qreg_plain_new(const gchar *name, gsize len)
{
static teco_qreg_vtable_t vtable = TECO_INIT_QREG();
return teco_qreg_new(&vtable, name, len);
}
static gboolean
teco_qreg_external_edit(teco_qreg_t *qreg, GError **error)
{
g_auto(teco_string_t) str = {NULL, 0};
if (!teco_qreg_plain_edit(qreg, error) ||
!qreg->vtable->get_string(qreg, &str.data, &str.len, NULL, error))
return FALSE;
teco_view_ssm(teco_qreg_view, SCI_BEGINUNDOACTION, 0, 0);
teco_view_ssm(teco_qreg_view, SCI_CLEARALL, 0, 0);
teco_view_ssm(teco_qreg_view, SCI_ADDTEXT, str.len, (sptr_t)str.data);
teco_view_ssm(teco_qreg_view, SCI_ENDUNDOACTION, 0, 0);
undo__teco_view_ssm(teco_qreg_view, SCI_UNDO, 0, 0);
return TRUE;
}
static gboolean
teco_qreg_external_exchange_string(teco_qreg_t *qreg, teco_doc_t *src, GError **error)
{
g_auto(teco_string_t) other_str, own_str = {NULL, 0};
guint other_cp, own_cp;
teco_doc_get_string(src, &other_str.data, &other_str.len, &other_cp);
if (!qreg->vtable->get_string(qreg, &own_str.data, &own_str.len, &own_cp, error) ||
!qreg->vtable->set_string(qreg, other_str.data, other_str.len, other_cp, error))
return FALSE;
teco_doc_set_string(src, own_str.data, own_str.len, own_cp);
return TRUE;
}
static gboolean
teco_qreg_external_undo_exchange_string(teco_qreg_t *qreg, teco_doc_t *src, GError **error)
{
if (!qreg->vtable->undo_set_string(qreg, error))
return FALSE;
if (qreg->must_undo) // FIXME
teco_doc_undo_set_string(src);
return TRUE;
}
static gboolean
teco_qreg_external_get_character(teco_qreg_t *qreg, teco_int_t position,
teco_int_t *chr, GError **error)
{
g_auto(teco_string_t) str = {NULL, 0};
if (!qreg->vtable->get_string(qreg, &str.data, &str.len, NULL, error))
return FALSE;
if (position < 0 || position >= g_utf8_strlen(str.data, str.len)) {
g_set_error(error, TECO_ERROR, TECO_ERROR_RANGE,
"Position %" TECO_INT_FORMAT " out of range", position);
return FALSE;
}
const gchar *p = g_utf8_offset_to_pointer(str.data, position);
/*
* Make sure that the -1/-2 error values are preserved.
* The sign bit in UCS-4/UTF-32 is unused, so this will even
* suffice if TECO_INTEGER == 32.
*/
*chr = (gint32)g_utf8_get_char_validated(p, -1);
return TRUE;
}
static teco_int_t
teco_qreg_external_get_length(teco_qreg_t *qreg, GError **error)
{
g_auto(teco_string_t) str = {NULL, 0};
if (!qreg->vtable->get_string(qreg, &str.data, &str.len, NULL, error))
return -1;
return g_utf8_strlen(str.data, str.len);
}
/*
* NOTE: This does not perform EOL normalization unlike teco_view_load().
* It shouldn't be critical since "external" registers are mainly used for filenames.
* Otherwise we could of course load into the view() and call set_string() afterwards.
*/
static gboolean
teco_qreg_external_load(teco_qreg_t *qreg, const gchar *filename, GError **error)
{
g_auto(teco_string_t) str = {NULL, 0};
return g_file_get_contents(filename, &str.data, &str.len, error) &&
qreg->vtable->undo_set_string(qreg, error) &&
qreg->vtable->set_string(qreg, str.data, str.len, teco_default_codepage(), error);
}
/*
* NOTE: This does not simply use g_file_set_contents(), as we have to create
* save point files as well.
* FIXME: On the other hand, this does not set the correct EOL style on the document,
* so teco_view_save() will save only with the default EOL style.
* It might therefore still be a good idea to avoid any conversion.
*/
static gboolean
teco_qreg_external_save(teco_qreg_t *qreg, const gchar *filename, GError **error)
{
if (teco_qreg_current)
teco_doc_update(&teco_qreg_current->string, teco_qreg_view);
teco_doc_edit(&qreg->string, teco_default_codepage());
g_auto(teco_string_t) str = {NULL, 0};
if (!qreg->vtable->get_string(qreg, &str.data, &str.len, NULL, error))
return FALSE;
teco_view_ssm(teco_qreg_view, SCI_BEGINUNDOACTION, 0, 0);
teco_view_ssm(teco_qreg_view, SCI_CLEARALL, 0, 0);
teco_view_ssm(teco_qreg_view, SCI_ADDTEXT, str.len, (sptr_t)str.data);
teco_view_ssm(teco_qreg_view, SCI_ENDUNDOACTION, 0, 0);
undo__teco_view_ssm(teco_qreg_view, SCI_UNDO, 0, 0);
gboolean ret = teco_view_save(teco_qreg_view, filename, error);
if (teco_qreg_current)
teco_doc_edit(&teco_qreg_current->string, 0);
return ret;
}
/**
* Initializer for vtables of Q-Registers with "external" storage of strings.
* These rely on custom implementations of get_string() and set_string().
*/
#define TECO_INIT_QREG_EXTERNAL(...) TECO_INIT_QREG( \
.exchange_string = teco_qreg_external_exchange_string, \
.undo_exchange_string = teco_qreg_external_undo_exchange_string, \
.edit = teco_qreg_external_edit, \
.get_character = teco_qreg_external_get_character, \
.get_length = teco_qreg_external_get_length, \
.load = teco_qreg_external_load, \
.save = teco_qreg_external_save, \
##__VA_ARGS__ \
)
/*
* NOTE: The integer-component is currently unused on the "*" special register.
*/
static gboolean
teco_qreg_bufferinfo_set_integer(teco_qreg_t *qreg, teco_int_t value, GError **error)
{
return teco_ring_edit(value, error);
}
static gboolean
teco_qreg_bufferinfo_undo_set_integer(teco_qreg_t *qreg, GError **error)
{
return teco_current_doc_undo_edit(error);
}
static gboolean
teco_qreg_bufferinfo_get_integer(teco_qreg_t *qreg, teco_int_t *ret, GError **error)
{
*ret = teco_ring_get_id(teco_ring_current);
return TRUE;
}
/*
* FIXME: Something could be implemented here. There are 2 possibilities:
* Either it renames the current buffer, or opens a file (alternative to EB).
*/
static gboolean
teco_qreg_bufferinfo_set_string(teco_qreg_t *qreg, const gchar *str, gsize len,
guint codepage, GError **error)
{
teco_error_qregopunsupported_set(error, qreg->head.name.data, qreg->head.name.len, FALSE);
return FALSE;
}
static gboolean
teco_qreg_bufferinfo_undo_set_string(teco_qreg_t *qreg, GError **error)
{
return TRUE;
}
static gboolean
teco_qreg_bufferinfo_append_string(teco_qreg_t *qreg, const gchar *str, gsize len, GError **error)
{
teco_error_qregopunsupported_set(error, qreg->head.name.data, qreg->head.name.len, FALSE);
return FALSE;
}
static gboolean
teco_qreg_bufferinfo_undo_append_string(teco_qreg_t *qreg, GError **error)
{
return HTTP/1.1 200 OK
Connection: keep-alive
Connection: keep-alive
Content-Disposition: inline; filename="qreg.c"
Content-Disposition: inline; filename="qreg.c"
Content-Length: 47070
Content-Length: 47070
Content-Security-Policy: default-src 'none'
Content-Security-Policy: default-src 'none'
Content-Type: text/plain; charset=UTF-8
Content-Type: text/plain; charset=UTF-8
Date: Mon, 20 Oct 2025 02:38:23 UTC
ETag: "a0e5415cd260b1bfe88a3c763c076d3a25c375cf"
ETag: "a0e5415cd260b1bfe88a3c763c076d3a25c375cf"
Expires: Thu, 18 Oct 2035 02:38:23 GMT
Expires: Thu, 18 Oct 2035 02:38:24 GMT
Last-Modified: Mon, 20 Oct 2025 02:38:23 GMT
Last-Modified: Mon, 20 Oct 2025 02:38:24 GMT
Server: OpenBSD httpd
Server: OpenBSD httpd
X-Content-Type-Options: nosniff
X-Content-Type-Options: nosniff
/*
* Copyright (C) 2012-2024 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
#include
#include
#include "sciteco.h"
#include "string-utils.h"
#include "file-utils.h"
#include "interface.h"
#include "cmdline.h"
#include "view.h"
#include "undo.h"
#include "parser.h"
#include "core-commands.h"
#include "expressions.h"
#include "doc.h"
#include "ring.h"
#include "eol.h"
#include "error.h"
#include "qreg.h"
/**
* View used for editing Q-Registers.
* Initialized in main.c after the interface.
*/
teco_view_t *teco_qreg_view = NULL;
/** Table of currently edited Q-Register */
const teco_qreg_table_t *teco_qreg_table_current = NULL;
/** Currently edited Q-Register */
teco_qreg_t *teco_qreg_current = NULL;
/**
* Table for global Q-Registers.
* Initialized in main.c after the interface.
*/
teco_qreg_table_t teco_qreg_table_globals;
/** @private @static @memberof teco_qreg_t */
static teco_qreg_t *
teco_qreg_new(teco_qreg_vtable_t *vtable, const gchar *name, gsize len)
{
/*
* FIXME: Test with g_slice_new()...
* It could however cause problems upon command-line termination
* and may not be measurably faster.
*/
teco_qreg_t *qreg = g_new0(teco_qreg_t, 1);
qreg->vtable = vtable;
/*
* NOTE: This does not use GStringChunk/teco_string_init_chunk()
* since we want to implement Q-Register removing soon.
* Even without that, individual Q-Regs can be removed on rubout.
*/
teco_string_init(&qreg->head.name, name, len);
teco_doc_init(&qreg->string);
return qreg;
}
/** @memberof teco_qreg_t */
gboolean
teco_qreg_execute(teco_qreg_t *qreg, teco_qreg_table_t *qreg_table_locals, GError **error)
{
g_auto(teco_string_t) macro = {NULL, 0};
/*
* SciTECO macros must be in UTF-8, but we don't check the encoding,
* so as not to complicate TECO_ED_DEFAULT_ANSI mode.
* The UTF-8 byte sequences are checked anyway.
*/
if (!qreg->vtable->get_string(qreg, ¯o.data, ¯o.len, NULL, error) ||
!teco_execute_macro(macro.data, macro.len, qreg_table_locals, error)) {
teco_error_add_frame_qreg(qreg->head.name.data, qreg->head.name.len);
return FALSE;
}
return TRUE;
}
/** @memberof teco_qreg_t */
void
teco_qreg_undo_set_eol_mode(teco_qreg_t *qreg)
{
if (!qreg->must_undo)
return;
/*
* Necessary, so that upon rubout the
* string's parameters are restored.
*/
teco_doc_update(&qreg->string, teco_qreg_view);
if (teco_qreg_current && teco_qreg_current->must_undo) // FIXME
teco_doc_undo_edit(&teco_qreg_current->string);
undo__teco_view_ssm(teco_qreg_view, SCI_SETEOLMODE,
teco_view_ssm(teco_qreg_view, SCI_GETEOLMODE, 0, 0), 0);
teco_doc_undo_edit(&qreg->string);
}
/** @memberof teco_qreg_t */
void
teco_qreg_set_eol_mode(teco_qreg_t *qreg, gint mode)
{
if (teco_qreg_current)
teco_doc_update(&teco_qreg_current->string, teco_qreg_view);
teco_doc_edit(&qreg->string, teco_default_codepage());
teco_view_ssm(teco_qreg_view, SCI_SETEOLMODE, mode, 0);
if (teco_qreg_current)
teco_doc_edit(&teco_qreg_current->string, 0);
}
static gboolean
teco_qreg_plain_set_integer(teco_qreg_t *qreg, teco_int_t value, GError **error)
{
qreg->integer = value;
return TRUE;
}
static gboolean
teco_qreg_plain_undo_set_integer(teco_qreg_t *qreg, GError **error)
{
if (qreg->must_undo) // FIXME
teco_undo_int(qreg->integer);
return TRUE;
}
static gboolean
teco_qreg_plain_get_integer(teco_qreg_t *qreg, teco_int_t *ret, GError **error)
{
*ret = qreg->integer;
return TRUE;
}
static gboolean
teco_qreg_plain_set_string(teco_qreg_t *qreg, const gchar *str, gsize len,
guint codepage, GError **error)
{
teco_doc_set_string(&qreg->string, str, len, codepage);
return TRUE;
}
static gboolean
teco_qreg_plain_undo_set_string(teco_qreg_t *qreg, GError **error)
{
if (qreg->must_undo) // FIXME
teco_doc_undo_set_string(&qreg->string);
return TRUE;
}
static gboolean
teco_qreg_plain_append_string(teco_qreg_t *qreg, const gchar *str, gsize len, GError **error)
{
/*
* NOTE: Will not create undo action if string is empty.
* Also, appending preserves the string's parameters.
*/
if (!len)
return TRUE;
if (teco_qreg_current)
teco_doc_update(&teco_qreg_current->string, teco_qreg_view);
teco_doc_edit(&qreg->string, teco_default_codepage());
teco_view_ssm(teco_qreg_view, SCI_BEGINUNDOACTION, 0, 0);
teco_view_ssm(teco_qreg_view, SCI_APPENDTEXT, len, (sptr_t)str);
teco_view_ssm(teco_qreg_view, SCI_ENDUNDOACTION, 0, 0);
if (teco_qreg_current)
teco_doc_edit(&teco_qreg_current->string, 0);
return TRUE;
}
static gboolean
teco_qreg_plain_get_string(teco_qreg_t *qreg, gchar **str, gsize *len,
guint *codepage, GError **error)
{
teco_doc_get_string(&qreg->string, str, len, codepage);
return TRUE;
}
static gboolean
teco_qreg_plain_get_character(teco_qreg_t *qreg, teco_int_t position,
teco_int_t *chr, GError **error)
{
if (teco_qreg_current)
teco_doc_update(&teco_qreg_current->string, teco_qreg_view);
teco_doc_edit(&qreg->string, teco_default_codepage());
sptr_t len = teco_view_ssm(teco_qreg_view, SCI_GETLENGTH, 0, 0);
gssize off = teco_view_glyphs2bytes(teco_qreg_view, position);
gboolean ret = off >= 0 && off != len;
if (!ret)
g_set_error(error, TECO_ERROR, TECO_ERROR_RANGE,
"Position %" TECO_INT_FORMAT " out of range", position);
/* make sure we still restore the current Q-Register */
else
*chr = teco_view_get_character(teco_qreg_view, off, len);
if (teco_qreg_current)
teco_doc_edit(&teco_qreg_current->string, 0);
return ret;
}
static teco_int_t
teco_qreg_plain_get_length(teco_qreg_t *qreg, GError **error)
{
if (teco_qreg_current)
teco_doc_update(&teco_qreg_current->string, teco_qreg_view);
teco_doc_edit(&qreg->string, teco_default_codepage());
sptr_t len = teco_view_ssm(teco_qreg_view, SCI_GETLENGTH, 0, 0);
teco_int_t ret = teco_view_bytes2glyphs(teco_qreg_view, len);
if (teco_qreg_current)
teco_doc_edit(&teco_qreg_current->string, 0);
return ret;
}
static gboolean
teco_qreg_plain_exchange_string(teco_qreg_t *qreg, teco_doc_t *src, GError **error)
{
teco_doc_exchange(&qreg->string, src);
return TRUE;
}
static gboolean
teco_qreg_plain_undo_exchange_string(teco_qreg_t *qreg, teco_doc_t *src, GError **error)
{
if (qreg->must_undo) // FIXME
teco_doc_undo_exchange(&qreg->string);
teco_doc_undo_exchange(src);
return TRUE;
}
static gboolean
teco_qreg_plain_edit(teco_qreg_t *qreg, GError **error)
{
if (teco_qreg_current)
teco_doc_update(&teco_qreg_current->string, teco_qreg_view);
teco_doc_edit(&qreg->string, teco_default_codepage());
teco_interface_show_view(teco_qreg_view);
teco_interface_info_update(qreg);
return TRUE;
}
static gboolean
teco_qreg_plain_undo_edit(teco_qreg_t *qreg, GError **error)
{
/*
* We might be switching the current document
* to a buffer.
*/
teco_doc_update(&qreg->string, teco_qreg_view);
if (!qreg->must_undo) // FIXME
return TRUE;
undo__teco_interface_info_update_qreg(qreg);
teco_doc_undo_edit(&qreg->string);
undo__teco_interface_show_view(teco_qreg_view);
return TRUE;
}
static gboolean
teco_qreg_plain_load(teco_qreg_t *qreg, const gchar *filename, GError **error)
{
if (!qreg->vtable->undo_set_string(qreg, error))
return FALSE;
if (teco_qreg_current)
teco_doc_update(&teco_qreg_current->string, teco_qreg_view);
teco_doc_edit(&qreg->string, teco_default_codepage());
teco_doc_reset(&qreg->string);
/*
* teco_view_load() might change the EOL style.
*/
teco_qreg_undo_set_eol_mode(qreg);
/*
* undo_set_string() pushes undo tokens that restore
* the previous document in the view.
* So if loading fails, teco_qreg_current will be
* made the current document again.
*/
if (!teco_view_load(teco_qreg_view, filename, error))
return FALSE;
if (teco_qreg_current)
teco_doc_edit(&teco_qreg_current->string, 0);
return TRUE;
}
static gboolean
teco_qreg_plain_save(teco_qreg_t *qreg, const gchar *filename, GError **error)
{
if (teco_qreg_current)
teco_doc_update(&teco_qreg_current->string, teco_qreg_view);
teco_doc_edit(&qreg->string, teco_default_codepage());
gboolean ret = teco_view_save(teco_qreg_view, filename, error);
if (teco_qreg_current)
teco_doc_edit(&teco_qreg_current->string, 0);
return ret;
}
/**
* Initializer for vtables of Q-Registers with "plain" storage of strings.
* These store their string part as teco_docs.
*/
#define TECO_INIT_QREG(...) { \
.set_integer = teco_qreg_plain_set_integer, \
.undo_set_integer = teco_qreg_plain_undo_set_integer, \
.get_integer = teco_qreg_plain_get_integer, \
.set_string = teco_qreg_plain_set_string, \
.undo_set_string = teco_qreg_plain_undo_set_string, \
.append_string = teco_qreg_plain_append_string, \
.undo_append_string = teco_qreg_plain_undo_set_string, \
.get_string = teco_qreg_plain_get_string, \
.get_character = teco_qreg_plain_get_character, \
.get_length = teco_qreg_plain_get_length, \
.exchange_string = teco_qreg_plain_exchange_string, \
.undo_exchange_string = teco_qreg_plain_undo_exchange_string, \
.edit = teco_qreg_plain_edit, \
.undo_edit = teco_qreg_plain_undo_edit, \
.load = teco_qreg_plain_load, \
.save = teco_qreg_plain_save, \
##__VA_ARGS__ \
}
/** @static @memberof teco_qreg_t */
teco_qreg_t *
teco_qreg_plain_new(const gchar *name, gsize len)
{
static teco_qreg_vtable_t vtable = TECO_INIT_QREG();
return teco_qreg_new(&vtable, name, len);
}
static gboolean
teco_qreg_external_edit(teco_qreg_t *qreg, GError **error)
{
g_auto(teco_string_t) str = {NULL, 0};
if (!teco_qreg_plain_edit(qreg, error) ||
!qreg->vtable->get_string(qreg, &str.data, &str.len, NULL, error))
return FALSE;
teco_view_ssm(teco_qreg_view, SCI_BEGINUNDOACTION, 0, 0);
teco_view_ssm(teco_qreg_view, SCI_CLEARALL, 0, 0);
teco_view_ssm(teco_qreg_view, SCI_ADDTEXT, str.len, (sptr_t)str.data);
teco_view_ssm(teco_qreg_view, SCI_ENDUNDOACTION, 0, 0);
undo__teco_view_ssm(teco_qreg_view, SCI_UNDO, 0, 0);
return TRUE;
}
static gboolean
teco_qreg_external_exchange_string(teco_qreg_t *qreg, teco_doc_t *src, GError **error)
{
g_auto(teco_string_t) other_str, own_str = {NULL, 0};
guint other_cp, own_cp;
teco_doc_get_string(src, &other_str.data, &other_str.len, &other_cp);
if (!qreg->vtable->get_string(qreg, &own_str.data, &own_str.len, &own_cp, error) ||
!qreg->vtable->set_string(qreg, other_str.data, other_str.len, other_cp, error))
return FALSE;
teco_doc_set_string(src, own_str.data, own_str.len, own_cp);
return TRUE;
}
static gboolean
teco_qreg_external_undo_exchange_string(teco_qreg_t *qreg, teco_doc_t *src, GError **error)
{
if (!qreg->vtable->undo_set_string(qreg, error))
return FALSE;
if (qreg->must_undo) // FIXME
teco_doc_undo_set_string(src);
return TRUE;
}
static gboolean
teco_qreg_external_get_character(teco_qreg_t *qreg, teco_int_t position,
teco_int_t *chr, GError **error)
{
g_auto(teco_string_t) str = {NULL, 0};
if (!qreg->vtable->get_string(qreg, &str.data, &str.len, NULL, error))
return FALSE;
if (position < 0 || position >= g_utf8_strlen(str.data, str.len)) {
g_set_error(error, TECO_ERROR, TECO_ERROR_RANGE,
"Position %" TECO_INT_FORMAT " out of range", position);
return FALSE;
}
const gchar *p = g_utf8_offset_to_pointer(str.data, position);
/*
* Make sure that the -1/-2 error values are preserved.
* The sign bit in UCS-4/UTF-32 is unused, so this will even
* suffice if TECO_INTEGER == 32.
*/
*chr = (gint32)g_utf8_get_char_validated(p, -1);
return TRUE;
}
static teco_int_t
teco_qreg_external_get_length(teco_qreg_t *qreg, GError **error)
{
g_auto(teco_string_t) str = {NULL, 0};
if (!qreg->vtable->get_string(qreg, &str.data, &str.len, NULL, error))
return -1;
return g_utf8_strlen(str.data, str.len);
}
/*
* NOTE: This does not perform EOL normalization unlike teco_view_load().
* It shouldn't be critical since "external" registers are mainly used for filenames.
* Otherwise we could of course load into the view() and call set_string() afterwards.
*/
static gboolean
teco_qreg_external_load(teco_qreg_t *qreg, const gchar *filename, GError **error)
{
g_auto(teco_string_t) str = {NULL, 0};
return g_file_get_contents(filename, &str.data, &str.len, error) &&
qreg->vtable->undo_set_string(qreg, error) &&
qreg->vtable->set_string(qreg, str.data, str.len, teco_default_codepage(), error);
}
/*
* NOTE: This does not simply use g_file_set_contents(), as we have to create
* save point files as well.
* FIXME: On the other hand, this does not set the correct EOL style on the document,
* so teco_view_save() will save only with the default EOL style.
* It might therefore still be a good idea to avoid any conversion.
*/
static gboolean
teco_qreg_external_save(teco_qreg_t *qreg, const gchar *filename, GError **error)
{
if (teco_qreg_current)
teco_doc_update(&teco_qreg_current->string, teco_qreg_view);
teco_doc_edit(&qreg->string, teco_default_codepage());
g_auto(teco_string_t) str = {NULL, 0};
if (!qreg->vtable->get_string(qreg, &str.data, &str.len, NULL, error))
return FALSE;
teco_view_ssm(teco_qreg_view, SCI_BEGINUNDOACTION, 0, 0);
teco_view_ssm(teco_qreg_view, SCI_CLEARALL, 0, 0);
teco_view_ssm(teco_qreg_view, SCI_ADDTEXT, str.len, (sptr_t)str.data);
teco_view_ssm(teco_qreg_view, SCI_ENDUNDOACTION, 0, 0);
undo__teco_view_ssm(teco_qreg_view, SCI_UNDO, 0, 0);
gboolean ret = teco_view_save(teco_qreg_view, filename, error);
if (teco_qreg_current)
teco_doc_edit(&teco_qreg_current->string, 0);
return ret;
}
/**
* Initializer for vtables of Q-Registers with "external" storage of strings.
* These rely on custom implementations of get_string() and set_string().
*/
#define TECO_INIT_QREG_EXTERNAL(...) TECO_INIT_QREG( \
.exchange_string = teco_qreg_external_exchange_string, \
.undo_exchange_string = teco_qreg_external_undo_exchange_string, \
.edit = teco_qreg_external_edit, \
.get_character = teco_qreg_external_get_character, \
.get_length = teco_qreg_external_get_length, \
.load = teco_qreg_external_load, \
.save = teco_qreg_external_save, \
##__VA_ARGS__ \
)
/*
* NOTE: The integer-component is currently unused on the "*" special register.
*/
static gboolean
teco_qreg_bufferinfo_set_integer(teco_qreg_t *qreg, teco_int_t value, GError **error)
{
return teco_ring_edit(value, error);
}
static gboolean
teco_qreg_bufferinfo_undo_set_integer(teco_qreg_t *qreg, GError **error)
{
return teco_current_doc_undo_edit(error);
}
static gboolean
teco_qreg_bufferinfo_get_integer(teco_qreg_t *qreg, teco_int_t *ret, GError **error)
{
*ret = teco_ring_get_id(teco_ring_current);
return TRUE;
}
/*
* FIXME: Something could be implemented here. There are 2 possibilities:
* Either it renames the current buffer, or opens a file (alternative to EB).
*/
static gboolean
teco_qreg_bufferinfo_set_string(teco_qreg_t *qreg, const gchar *str, gsize len,
guint codepage, GError **error)
{
teco_error_qregopunsupported_set(error, qreg->head.name.data, qreg->head.name.len, FALSE);
return FALSE;
}
static gboolean
teco_qreg_bufferinfo_undo_set_string(teco_qreg_t *qreg, GError **error)
{
return TRUE;
}
static gboolean
teco_qreg_bufferinfo_append_string(teco_qreg_t *qreg, const gchar *str, gsize len, GError **error)
{
teco_error_qregopunsupported_set(error, qreg->head.name.data, qreg->head.name.len, FALSE);
return FALSE;
}
static gboolean
teco_qreg_bufferinfo_undo_append_string(teco_qreg_t *qreg, GError **error)
{
return TRUE;
}
/*
* NOTE: The `string` component is currently unused on the "*" register.
*/
static gboolean
teco_qreg_bufferinfo_get_string(teco_qreg_t *qreg, gchar **str, gsize *len,
guint *codepage, GError **error)
{
/*
* On platforms with a default non-forward-slash directory
* separator (i.e. Windows), Buffer::filename will have
* the wrong separator.
* To make the life of macros that evaluate "*" easier,
* the directory separators are normalized to "/" here.
*/
if (str)
*str = teco_file_normalize_path(g_strdup(teco_ring_current->filename ? : ""));
/*
* NOTE: teco_file_normalize_path() does not change the size of the string.
*/
*len = teco_ring_current->filename ? strlen(teco_ring_current->filename) : 0;
if (codepage)
*codepage = teco_default_codepage();
return TRUE;
}
/** @static @memberof teco_qreg_t */
teco_qreg_t *
teco_qreg_bufferinfo_new(void)
{
static teco_qreg_vtable_t vtable = TECO_INIT_QREG_EXTERNAL(
.set_integer = teco_qreg_bufferinfo_set_integer,
.undo_set_integer = teco_qreg_bufferinfo_undo_set_integer,
.get_integer = teco_qreg_bufferinfo_get_integer,
.set_string = teco_qreg_bufferinfo_set_string,
.undo_set_string = teco_qreg_bufferinfo_undo_set_string,
.append_string = teco_qreg_bufferinfo_append_string,
.undo_append_string = teco_qreg_bufferinfo_undo_append_string,
.get_string = teco_qreg_bufferinfo_get_string,
/*
* As teco_qreg_bufferinfo_set_string() is not implemented,
* it's important to not inherit teco_qreg_external_exchange_string().
* `[*` and `]*` will still work though.
* The inherited teco_qreg_external_load() will simply fail.
*/
.exchange_string = teco_qreg_plain_exchange_string,
.undo_exchange_string = teco_qreg_plain_undo_exchange_string
);
return teco_qreg_new(&vtable, "*", 1);
}
static gboolean
teco_qreg_workingdir_set_string(teco_qreg_t *qreg, const gchar *str, gsize len,
guint codepage, GError **error)
{
/*
* NOTE: Makes sure that `dir` will be null-terminated as str[len] may not be '\0'.
*/
g_auto(teco_string_t) dir;
teco_string_init(&dir, str, len);
if (teco_string_contains(&dir, '\0')) {
g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
"Directory contains null-character");
return FALSE;
}
g_assert(dir.data != NULL);
int ret = g_chdir(dir.data);
if (ret) {
/* FIXME: Is errno usable on Windows here? */
g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
"Cannot change working directory to \"%s\"", dir.data);
return FALSE;
}
return TRUE;
}
static gboolean
teco_qreg_workingdir_undo_set_string(teco_qreg_t *qreg, GError **error)
{
teco_undo_change_dir_to_current();
return TRUE;
}
/*
* FIXME: Redundant with teco_qreg_bufferinfo_append_string()...
* Best solution would be to simply implement them.
*/
static gboolean
teco_qreg_workingdir_append_string(teco_qreg_t *qreg, const gchar *str, gsize len, GError **error)
{
teco_error_qregopunsupported_set(error, qreg->head.name.data, qreg->head.name.len, FALSE);
return FALSE;
}
static gboolean
teco_qreg_workingdir_undo_append_string(teco_qreg_t *qreg, GError **error)
{
return TRUE;
}
static gboolean
teco_qreg_workingdir_get_string(teco_qreg_t *qreg, gchar **str, gsize *len,
guint *codepage, GError **error)
{
/*
* On platforms with a default non-forward-slash directory
* separator (i.e. Windows), teco_buffer_t::filename will have
* the wrong separator.
* To make the life of macros that evaluate "$" easier,
* the directory separators are normalized to "/" here.
* This does not change the size of the string, so
* the return value for str == NULL is still correct.
*/
gchar *dir = g_get_current_dir();
*len = strlen(dir);
if (str)
*str = teco_file_normalize_path(dir);
else
g_free(dir);
if (codepage)
*codepage = teco_default_codepage();
return TRUE;
}
/** @static @memberof teco_qreg_t */
teco_qreg_t *
teco_qreg_workingdir_new(void)
{
static teco_qreg_vtable_t vtable = TECO_INIT_QREG_EXTERNAL(
.set_string = teco_qreg_workingdir_set_string,
.undo_set_string = teco_qreg_workingdir_undo_set_string,
.append_string = teco_qreg_workingdir_append_string,
.undo_append_string = teco_qreg_workingdir_undo_append_string,
.get_string = teco_qreg_workingdir_get_string
);
/*
* FIXME: Dollar is not the best name for it since it is already
* heavily overloaded in the language and easily confused with Escape and
* the "\e" register also exists.
* Not to mention that environment variable regs also start with dollar.
* Perhaps "~" would be a better choice, although it is also already used?
* Most logical would be ".", but this should probably map to to Dot and
* is also ugly to write in practice.
* Perhaps "@"...
*/
return teco_qreg_new(&vtable, "$", 1);
}
static gboolean
teco_qreg_clipboard_set_string(teco_qreg_t *qreg, const gchar *str, gsize len,
guint codepage, GError **error)
{
g_assert(!teco_string_contains(&qreg->head.name, '\0'));
const gchar *clipboard_name = qreg->head.name.data + 1;
if (teco_ed & TECO_ED_AUTOEOL) {
/*
* NOTE: Currently uses GString instead of teco_string_t to make use of
* preallocation.
* On the other hand GString has a higher overhead.
*/
g_autoptr(GString) str_converted = g_string_sized_new(len);
/*
* This will convert to the Q-Register view's EOL mode.
*/
g_auto(teco_eol_writer_t) writer;
teco_eol_writer_init_mem(&writer, teco_view_ssm(teco_qreg_view, SCI_GETEOLMODE, 0, 0),
str_converted);
gssize bytes_written = teco_eol_writer_convert(&writer, str, len, error);
if (bytes_written < 0)
return FALSE;
g_assert(bytes_written == len);
return teco_interface_set_clipboard(clipboard_name, str_converted->str,
str_converted->len, error);
} else {
/*
* No EOL conversion necessary. The teco_eol_writer_t can handle
* this as well, but will result in unnecessary allocations.
*/
return teco_interface_set_clipboard(clipboard_name, str, len, error);
}
/* should not be reached */
return TRUE;
}
/*
* FIXME: Redundant with teco_qreg_bufferinfo_append_string()...
* Best solution would be to simply implement them.
*/
static gboolean
teco_qreg_clipboard_append_string(teco_qreg_t *qreg, const gchar *str, gsize len, GError **error)
{
teco_error_qregopunsupported_set(error, qreg->head.name.data, qreg->head.name.len, FALSE);
return FALSE;
}
static gboolean
teco_qreg_clipboard_undo_append_string(teco_qreg_t *qreg, GError **error)
{
return TRUE;
}
static gboolean
teco_qreg_clipboard_undo_set_string(teco_qreg_t *qreg, GError **error)
{
/*
* Upon rubout, the current contents of the clipboard are
* restored.
* We are checking for teco_undo_enabled instead of relying on
* teco_undo_push(), since getting the clipboard
* is an expensive operation that we want to avoid.
*/
if (!teco_undo_enabled)
return TRUE;
g_assert(!teco_string_contains(&qreg->head.name, '\0'));
const gchar *clipboard_name = qreg->head.name.data + 1;
/*
* Ownership of str is passed to the undo token.
* This avoids any EOL translation as that would be cumbersome
* and could also modify the clipboard in unexpected ways.
*/
teco_string_t str;
if (!teco_interface_get_clipboard(clipboard_name, &str.data, &str.len, error))
return FALSE;
teco_interface_undo_set_clipboard(clipboard_name, str.data, str.len);
return TRUE;
}
static gboolean
teco_qreg_clipboard_get_string(teco_qreg_t *qreg, gchar **str, gsize *len,
guint *codepage, GError **error)
{
g_assert(!teco_string_contains(&qreg->head.name, '\0'));
const gchar *clipboard_name = qreg->head.name.data + 1;
if (!(teco_ed & TECO_ED_AUTOEOL))
/*
* No auto-eol conversion - avoid unnecessary copying and allocations.
*/
return teco_interface_get_clipboard(clipboard_name, str, len, error);
g_auto(teco_string_t) temp = {NULL, 0};
if (!teco_interface_get_clipboard(clipboard_name, &temp.data, &temp.len, error))
return FALSE;
g_auto(teco_eol_reader_t) reader;
teco_eol_reader_init_mem(&reader, temp.data, temp.len);
/*
* FIXME: Could be simplified if teco_eol_reader_convert_all() had the
* same conventions for passing NULL pointers.
*/
teco_string_t str_converted;
if (teco_eol_reader_convert_all(&reader, &str_converted.data,
&str_converted.len, error) == G_IO_STATUS_ERROR)
return FALSE;
if (str)
*str = str_converted.data;
else
teco_string_clear(&str_converted);
*len = str_converted.len;
if (codepage)
*codepage = teco_default_codepage();
return TRUE;
}
/*
* Regardless of whether EOL normalization is enabled,
* this will never perform it.
* Other than that, it's very similar to teco_qreg_external_load().
*/
static gboolean
teco_qreg_clipboard_load(teco_qreg_t *qreg, const gchar *filename, GError **error)
{
g_assert(!teco_string_contains(&qreg->head.name, '\0'));
const gchar *clipboard_name = qreg->head.name.data + 1;
g_auto(teco_string_t) str = {NULL, 0};
return g_file_get_contents(filename, &str.data, &str.len, error) &&
teco_qreg_clipboard_undo_set_string(qreg, error) &&
teco_interface_set_clipboard(clipboard_name, str.data, str.len, error);
}
/** @static @memberof teco_qreg_t */
teco_qreg_t *
teco_qreg_clipboard_new(const gchar *name)
{
static teco_qreg_vtable_t vtable = TECO_INIT_QREG_EXTERNAL(
.set_string = teco_qreg_clipboard_set_string,
.undo_set_string = teco_qreg_clipboard_undo_set_string,
.append_string = teco_qreg_clipboard_append_string,
.undo_append_string = teco_qreg_clipboard_undo_append_string,
.get_string = teco_qreg_clipboard_get_string,
.load = teco_qreg_clipboard_load
);
teco_qreg_t *qreg = teco_qreg_new(&vtable, "~", 1);
teco_string_append(&qreg->head.name, name, strlen(name));
return qreg;
}
/** @memberof teco_qreg_table_t */
void
teco_qreg_table_init(teco_qreg_table_t *table, gboolean must_undo)
{
rb3_reset_tree(&table->tree);
table->must_undo = must_undo;
/* general purpose registers */
for (gchar q = 'A'; q <= 'Z'; q++)
teco_qreg_table_insert(table, teco_qreg_plain_new(&q, sizeof(q)));
for (gchar q = '0'; q <= '9'; q++)
teco_qreg_table_insert(table, teco_qreg_plain_new(&q, sizeof(q)));
}
static inline void
teco_qreg_table_remove(teco_qreg_t *reg)
{
rb3_unlink_and_rebalance(®->head.head);
teco_qreg_free(reg);
}
TECO_DEFINE_UNDO_CALL(teco_qreg_table_remove, teco_qreg_t *);
static inline void
teco_qreg_table_undo_remove(teco_qreg_t *qreg)
{
if (qreg->must_undo)
undo__teco_qreg_table_remove(qreg);
}
/** @memberof teco_qreg_table_t */
teco_qreg_t *
teco_qreg_table_edit_name(teco_qreg_table_t *table, const gchar *name, gsize len, GError **error)
{
teco_qreg_t *qreg = teco_qreg_table_find(table, name, len);
if (!qreg) {
g_autofree gchar *name_printable = teco_string_echo(name, len);
g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
"Q-Register \"%s\" not found", name_printable);
return NULL;
}
return teco_qreg_table_edit(table, qreg, error) ? qreg : NULL;
}
/**
* Import process environment into table
* by setting environment registers for every
* environment variable.
*
* In general this method is only safe to call
* at startup.
*
* @memberof teco_qreg_table_t
*/
gboolean
teco_qreg_table_set_environ(teco_qreg_table_t *table, GError **error)
{
g_auto(GStrv) env = g_get_environ();
for (gchar **key = env; *key; key++) {
const gchar *p = *key;
/*
* FIXME: On Win32, the key sometimes starts with `=`
* which shouldn't be possible and in reality it is a `!`.
* For instance `=C:=C:\msys64`.
*/
#ifdef G_OS_WIN32
if (G_UNLIKELY(*p == '='))
p++;
#endif
gchar *value = strchr(p, '=');
assert(value != NULL);
*value++ = '\0';
g_autofree gchar *name = g_strconcat("$", *key, NULL);
/*
* FIXME: It might be a good idea to wrap this into
* a convenience function.
*/
teco_qreg_t *qreg = teco_qreg_plain_new(name, strlen(name));
teco_qreg_t *found = teco_qreg_table_insert(table, qreg);
if (found) {
teco_qreg_free(qreg);
qreg = found;
}
if (!qreg->vtable->set_string(qreg, value, strlen(value),
teco_default_codepage(), error))
return FALSE;
}
return TRUE;
}
/**
* Export environment registers as a list of environment
* variables compatible with `g_get_environ()`.
*
* @return Zero-terminated list of strings in the form
* `NAME=VALUE`. Should be freed with `g_strfreev()`.
* NULL in case of errors.
*
* @memberof teco_qreg_table_t
*/
gchar **
teco_qreg_table_get_environ(teco_qreg_table_t *table, GError **error)
{
teco_qreg_t *first = (teco_qreg_t *)teco_rb3str_nfind(&table->tree, TRUE, "$", 1);
gint envp_len = 1;
/*
* Iterate over all registers beginning with "$" to
* guess the size required for the environment array.
* This may waste a few bytes because not __every__
* register beginning with "$" is an environment
* register.
*/
for (teco_qreg_t *cur = first;
cur && cur->head.name.data[0] == '$';
cur = (teco_qreg_t *)teco_rb3str_get_next(&cur->head))
envp_len++;
gchar **envp, **p;
p = envp = g_new(gchar *, envp_len);
for (teco_qreg_t *cur = first;
cur && cur->head.name.data[0] == '$';
cur = (teco_qreg_t *)teco