/* * 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