/* * Copyright (C) 2012-2016 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 #include "sciteco.h" #include "string-utils.h" #include "interface.h" #include "undo.h" #include "parser.h" #include "expressions.h" #include "document.h" #include "ring.h" #include "ioview.h" #include "error.h" #include "qregisters.h" namespace SciTECO { namespace States { StatePushQReg pushqreg; StatePopQReg popqreg; StateEQCommand eqcommand; StateLoadQReg loadqreg; StateEPctCommand epctcommand; StateSaveQReg saveqreg; StateQueryQReg queryqreg; StateCtlUCommand ctlucommand; StateEUCommand eucommand; StateSetQRegString setqregstring_nobuilding(false); StateSetQRegString setqregstring_building(true); StateGetQRegString getqregstring; StateSetQRegInteger setqreginteger; StateIncreaseQReg increaseqreg; StateMacro macro; StateMacroFile macro_file; StateCopyToQReg copytoqreg; } namespace QRegisters { QRegisterTable *locals = NULL; QRegister *current = NULL; static QRegisterStack stack; } static QRegister *register_argument = NULL; void QRegisterData::set_string(const gchar *str, gsize len) { if (QRegisters::current) QRegisters::current->string.update(QRegisters::view); string.reset(); string.edit(QRegisters::view); QRegisters::view.ssm(SCI_BEGINUNDOACTION); QRegisters::view.ssm(SCI_CLEARALL); QRegisters::view.ssm(SCI_APPENDTEXT, len, (sptr_t)(str ? : "")); QRegisters::view.ssm(SCI_ENDUNDOACTION); if (QRegisters::current) QRegisters::current->string.edit(QRegisters::view); } void QRegisterData::undo_set_string(void) { if (!must_undo) return; /* * Necessary, so that upon rubout the * string's parameters are restored. */ string.update(QRegisters::view); if (QRegisters::current && QRegisters::current->must_undo) QRegisters::current->string.undo_edit(QRegisters::view); string.undo_reset(); QRegisters::view.undo_ssm(SCI_UNDO); string.undo_edit(QRegisters::view); } void QRegisterData::append_string(const gchar *str, gsize len) { /* * NOTE: Will not create undo action * if string is empty. * Also, appending preserves the string's * parameters. */ if (!len) return; if (QRegisters::current) QRegisters::current->string.update(QRegisters::view); string.edit(QRegisters::view); QRegisters::view.ssm(SCI_BEGINUNDOACTION); QRegisters::view.ssm(SCI_APPENDTEXT, len, (sptr_t)str); QRegisters::view.ssm(SCI_ENDUNDOACTION); if (QRegisters::current) QRegisters::current->string.edit(QRegisters::view); } gchar * QRegisterData::get_string(void) { gint size; gchar *str; if (!string.is_initialized()) return g_strdup(""); if (QRegisters::current) QRegisters::current->string.update(QRegisters::view); string.edit(QRegisters::view); size = QRegisters::view.ssm(SCI_GETLENGTH) + 1; str = (gchar *)g_malloc(size); QRegisters::view.ssm(SCI_GETTEXT, size, (sptr_t)str); if (QRegisters::current) QRegisters::current->string.edit(QRegisters::view); return str; } gsize QRegisterData::get_string_size(void) { gsize size; if (!string.is_initialized()) return 0; if (QRegisters::current) QRegisters::current->string.update(QRegisters::view); string.edit(QRegisters::view); size = QRegisters::view.ssm(SCI_GETLENGTH); if (QRegisters::current) QRegisters::current->string.edit(QRegisters::view); return size; } gint QRegisterData::get_character(gint position) { gint ret = -1; if (position < 0) return -1; if (QRegisters::current) QRegisters::current->string.update(QRegisters::view); string.edit(QRegisters::view); if (position < QRegisters::view.ssm(SCI_GETLENGTH)) ret = QRegisters::view.ssm(SCI_GETCHARAT, position); if (QRegisters::current) QRegisters::current->string.edit(QRegisters::view); return ret; } void QRegisterData::undo_exchange_string(QRegisterData ®) { if (must_undo) string.undo_exchange(); if (reg.must_undo) reg.string.undo_exchange(); } void QRegister::edit(void) { if (QRegisters::current) QRegisters::current->string.update(QRegisters::view); string.edit(QRegisters::view); interface.show_view(&QRegisters::view); interface.info_update(this); } void QRegister::undo_edit(void) { /* * We might be switching the current document * to a buffer. */ string.update(QRegisters::view); if (!must_undo) return; interface.undo_info_update(this); string.undo_edit(QRegisters::view); interface.undo_show_view(&QRegisters::view); } void QRegister::execute(bool locals) { gchar *str = get_string(); try { Execute::macro(str, locals); } catch (Error &error) { error.add_frame(new Error::QRegFrame(name)); g_free(str); throw; /* forward */ } catch (...) { g_free(str); throw; /* forward */ } g_free(str); } void QRegister::undo_set_eol_mode(void) { if (!must_undo) return; /* * Necessary, so that upon rubout the * string's parameters are restored. */ string.update(QRegisters::view); if (QRegisters::current && QRegisters::current->must_undo) QRegisters::current->string.undo_edit(QRegisters::view); QRegisters::view.undo_ssm(SCI_SETEOLMODE, QRegisters::view.ssm(SCI_GETEOLMODE)); string.undo_edit(QRegisters::view); } void QRegister::set_eol_mode(gint mode) { if (QRegisters::current) QRegisters::current->string.update(QRegisters::view); string.edit(QRegisters::view); QRegisters::view.ssm(SCI_SETEOLMODE, mode); if (QRegisters::current) QRegisters::current->string.edit(QRegisters::view); } void QRegister::load(const gchar *filename) { undo_set_string(); if (QRegisters::current) QRegisters::current->string.update(QRegisters::view); string.edit(QRegisters::view); string.reset(); /* * IOView::load() might change the EOL style. */ undo_set_eol_mode(); /* * undo_set_string() pushes undo tokens that restore * the previous document in the view. * So if loading fails, QRegisters::current will be * made the current document again. */ QRegisters::view.load(filename); if (QRegisters::current) QRegisters::current->string.edit(QRegisters::view); } void QRegister::save(const gchar *filename) { if (QRegisters::current) QRegisters::current->string.update(QRegisters::view); string.edit(QRegisters::view); try { QRegisters::view.save(filename); } catch (...) { if (QRegisters::current) QRegisters::current->string.edit(QRegisters::view); throw; /* forward */ } if (QRegisters::current) QRegisters::current->string.edit(QRegisters::view); } tecoInt QRegisterBufferInfo::set_integer(tecoInt v) { if (!ring.edit(v)) throw Error("Invalid buffer id %" TECO_INTEGER_FORMAT, v); return v; } void QRegisterBufferInfo::undo_set_integer(void) { current_doc_undo_edit(); } tecoInt QRegisterBufferInfo::get_integer(void) { return ring.get_id(); } gchar * QRegisterBufferInfo::get_string(void) { gchar *str = g_strdup(ring.current->filename ? : ""); /* * 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. * This does not change the size of the string, so * get_string_size() still works. */ return normalize_path(str); } gsize QRegisterBufferInfo::get_string_size(void) { return ring.current->filename ? strlen(ring.current->filename) : 0; } gint QRegisterBufferInfo::get_character(gint position) { if (position < 0 || position >= (gint)QRegisterBufferInfo::get_string_size()) return -1; return ring.current->filename[position]; } void QRegisterBufferInfo::edit(void) { gchar *str; QRegister::edit(); QRegisters::view.ssm(SCI_BEGINUNDOACTION); str = QRegisterBufferInfo::get_string(); QRegisters::view.ssm(SCI_SETTEXT, 0, (sptr_t)str); g_free(str); QRegisters::view.ssm(SCI_ENDUNDOACTION); QRegisters::view.undo_ssm(SCI_UNDO); } void QRegisterWorkingDir::set_string(const gchar *str, gsize len) { /* str is not null-terminated */ gchar *dir = g_strndup(str, len); int ret = g_chdir(dir); g_free(dir); if (ret) /* FIXME: Is errno usable on Windows here? */ throw Error("Cannot change working directory " "to \"%.*s\"", (int)len, str); } void QRegisterWorkingDir::undo_set_string(void) { /* pass ownership of current dir string */ undo.push_own(g_get_current_dir()); } gchar * QRegisterWorkingDir::get_string(void) { /* * 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. * This does not change the size of the string, so * get_string_size() still works. */ return normalize_path(g_get_current_dir()); } gsize QRegisterWorkingDir::get_string_size(void) { gchar *str = g_get_current_dir(); gsize len = strlen(str); g_free(str); return len; } gint QRegisterWorkingDir::get_character(gint position) { gchar *str = QRegisterWorkingDir::get_string(); gint ret = -1; if (position >= 0 && position < (gint)strlen(str)) ret = str[position]; g_free(str); return ret; } void QRegisterWorkingDir::edit(void) { gchar *str; QRegister::edit(); QRegisters::view.ssm(SCI_BEGINUNDOACTION); str = QRegisterWorkingDir::get_string(); QRegisters::view.ssm(SCI_SETTEXT, 0, (sptr_t)str); g_free(str); QRegisters::view.ssm(SCI_ENDUNDOACTION); QRegisters::view.undo_ssm(SCI_UNDO); } void QRegisterWorkingDir::exchange_string(QRegisterData ®) { gchar *own_str = QRegisterWorkingDir::get_string(); gchar *other_str = reg.get_string(); QRegisterData::set_string(other_str); g_free(other_str); reg.set_string(own_str); g_free(own_str); } void QRegisterWorkingDir::undo_exchange_string(QRegisterData ®) { QRegisterWorkingDir::undo_set_string(); reg.undo_set_string(); } QRegisterTable::QRegisterTable(bool _undo) : RBTree(), must_undo(_undo) { /* general purpose registers */ for (gchar q = 'A'; q <= 'Z'; q++) insert(q); for (gchar q = '0'; q <= '9'; q++) insert(q); } /* * NOTE: by not making this inline, * we can access QRegisters::current */ void QRegisterTable::edit(QRegister *reg) { reg->edit(); QRegisters::current = reg; } /** * Import process environment into table * by setting environment registers for every * environment variable. * It is assumed that the table does not yet * contain any environment register. * * In general this method is only safe to call * at startup. */ void QRegisterTable::set_environ(void) { /* * NOTE: Using g_get_environ() would be more efficient, * but it appears to be broken, at least on Wine * and Windows 2000. */ gchar **env = g_listenv(); for (gchar **key = env; *key; key++) { gchar name[1 + strlen(*key) + 1]; QRegister *reg; name[0] = '$'; strcpy(name + 1, *key); reg = insert(name); reg->set_string(g_getenv(*key)); } g_strfreev(env); } /** * 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()`. */ gchar ** QRegisterTable::get_environ(void) { QRegister *first = nfind("$"); gint envp_len = 1; gchar **envp, **p; /* * 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 (QRegister *cur = first; cur && cur->name[0] == '$'; cur = (QRegister *)cur->next()) envp_len++; p = envp = (gchar **)g_malloc(sizeof(gchar *)*envp_len); for (QRegister *cur = first; cur && cur->name[0] == '$'; cur = (QRegister *)cur->next()) { gchar *value; /* * Ignore the "$" register (not an environment * variable register) and registers whose * name contains "=" (not allowed in environment * variable names). */ if (!cur->name[1] || strchr(cur->name+1, '=')) continue; value = cur->get_string(); /* more efficient than g_environ_setenv() */ *p++ = g_strconcat(cur->name+1, "=", value, NIL); g_free(value); } *p = NULL; return envp; } /** * Update process environment with environment registers * using `g_setenv()`. * It does not try to unset environment variables that * are no longer in the Q-Register table. * * This method may be dangerous in a multi-threaded environment * but may be necessary for libraries that access important * environment variables internally without providing alternative * APIs. */ void QRegisterTable::update_environ(void) { for (QRegister *cur = nfind("$"); cur && cur->name[0] == '$'; cur = (QRegister *)cur->next()) { gchar *value; /* * Ignore the "$" register (not an environment * variable register) and registers whose * name contains "=" (not allowed in environment * variable names). */ if (!cur->name[1] || strchr(cur->name+1, '=')) continue; value = cur->get_string(); g_setenv(cur->name+1, value, TRUE); g_free(value); } } /** * Free resources associated with table. * * This is similar to RBTree::clear() but * has the advantage that we can check whether some * register is currently edited. * Since this is not a destructor, we can throw * errors. * Therefore this method should be called before * a (local) QRegisterTable is deleted. */ void QRegisterTable::clear(void) { QRegister *cur; while ((cur = (QRegister *)min())) { if (cur == QRegisters::current) throw Error("Currently edited Q-Register \"%s\" " "cannot be discarded", cur->name); remove(cur); delete cur; } } void QRegisterStack::UndoTokenPush::run(void) { SLIST_INSERT_HEAD(&stack->head, entry, entries); entry = NULL; } void QRegisterStack::UndoTokenPop::run(void) { Entry *entry = SLIST_FIRST(&stack->head); SLIST_REMOVE_HEAD(&stack->head, entries); delete entry; } void QRegisterStack::push(QRegister ®) { Entry *entry = new Entry(); gchar *str = reg.get_string(); if (*str) entry->set_string(str); g_free(str); entry->string.update(reg.string); entry->set_integer(reg.get_integer()); SLIST_INSERT_HEAD(&head, entry, entries); undo.push(this); } bool QRegisterStack::pop(QRegister ®) { Entry *entry = SLIST_FIRST(&head); if (!entry) return false; reg.undo_set_integer(); reg.set_integer(entry->get_integer()); /* exchange document ownership between Stack entry and Q-Register */ reg.undo_exchange_string(*entry); reg.exchange_string(*entry); SLIST_REMOVE_HEAD(&head, entries); /* Pass entry ownership to undo stack. */ undo.push_own(this, entry); return true; } QRegisterStack::~QRegisterStack() { Entry *entry, *next; SLIST_FOREACH_SAFE(entry, &head, entries, next) delete entry; } void QRegisters::hook(Hook type) { static const gchar *type2name[] = { /* [HOOK_ADD-1] = */ "ADD", /* [HOOK_EDIT-1] = */ "EDIT", /* [HOOK_CLOSE-1] = */ "CLOSE", /* [HOOK_QUIT-1] = */ "QUIT", }; QRegister *reg; if (!(Flags::ed & Flags::ED_HOOKS)) return; try { reg = globals["ED"]; if (!reg) throw Error("Undefined ED-hook register (\"ED\")"); /* * ED-hook execution should not see any * integer parameters but the hook type. * Such parameters could confuse the ED macro * and macro authors do not expect side effects * of ED macros on the expression stack. * Also make sure it does not leave behind * additional arguments on the stack. * * So this effectively executes: * (typeM[ED]^[) */ expressions.push(Expressions::OP_BRACE); expressions.push(type); reg->execute(); expressions.discard_args(); expressions.eval(true); } catch (Error &error) { const gchar *type_str = type2name[type-1]; error.add_frame(new Error::EDHookFrame(type_str)); throw; /* forward */ } } void QRegSpecMachine::reset(void) { MicroStateMachine::reset(); string_machine.reset(); undo.push_var(is_local) = false; undo.push_var(nesting) = 0; undo.push_str(name); g_free(name); name = NULL; } bool QRegSpecMachine::input(gchar chr, QRegister *&result) { gchar *insert; MICROSTATE_START; switch (chr) { case '.': undo.push_var(is_local) = true; break; case '#': set(&&StateFirstChar); break; case '[': set(&&StateString); break; default: undo.push_str(name) = String::chrdup(String::toupper(chr)); goto done; } return false; StateFirstChar: undo.push_str(name) = (gchar *)g_malloc(3); name[0] = String::toupper(chr); set(&&StateSecondChar); return false; StateSecondChar: name[1] = String::toupper(chr); name[2] = '\0'; goto done; StateString: switch (chr) { case '[': undo.push_var(nesting)++; break; case ']': if (!nesting) goto done; undo.push_var(nesting)--; break; } if (mode > MODE_NORMAL) return false; if (!string_machine.input(chr, insert)) return false; undo.push_str(name); String::append(name, insert); g_free(insert); return false; done: if (mode > MODE_NORMAL) { /* * StateExpectQRegs with type != OPTIONAL * will never see this NULL pointer beyond * BEGIN_EXEC() */ result = NULL; return true; } QRegisterTable &table = is_local ? *QRegisters::locals : QRegisters::globals; switch (type) { case QREG_REQUIRED: result = table[name]; if (!result) fail(); break; case QREG_OPTIONAL: result = table[name]; break; case QREG_OPTIONAL_INIT: result = table[name]; if (!result) { result = table.insert(name); table.undo_remove(result); } break; } return true; } /* * Command states */ StateExpectQReg::StateExpectQReg(QRegSpecType type) : machine(type) { transitions['\0'] = this; } State * StateExpectQReg::custom(gchar chr) { QRegister *reg; if (!machine.input(chr, reg)) return this; /* * NOTE: We must reset the Q-Reg machine * now, since we have commands like * that indirectly call their state recursively. */ machine.reset(); return got_register(reg); } /*$ * [q -- Save Q-Register * * Save Q-Register contents on the global Q-Register push-down * stack. */ State * StatePushQReg::got_register(QRegister *reg) { BEGIN_EXEC(&States::start); QRegisters::stack.push(*reg); return &States::start; } /*$ * ]q -- Restore Q-Register * * Restore Q-Register by replacing its contents * with the contents of the register saved on top of * the Q-Register push-down stack. * The stack entry is popped. * * In interactive mode, the original contents of * are not immediately reclaimed but are kept in memory * to support rubbing out the command. * Memory is reclaimed on command-line termination. */ State * StatePopQReg::got_register(QRegister *reg) { BEGIN_EXEC(&States::start); if (!QRegisters::stack.pop(*reg)) throw Error("Q-Register stack is empty"); return &States::start; } /*$ * EQq$ -- Edit or load Q-Register * EQq[file]$ * * When specified with an empty string argument, * EQ makes the currently edited Q-Register. * Otherwise, when is specified, it is the * name of a file to read into Q-Register . * When loading a file, the currently edited * buffer/register is not changed and the edit position * of register is reset to 0. * * Undefined Q-Registers will be defined. * The command fails if could not be read. */ State * StateEQCommand::got_register(QRegister *reg) { BEGIN_EXEC(&States::loadqreg); register_argument = reg; return &States::loadqreg; } State * StateLoadQReg::got_file(const gchar *filename) { BEGIN_EXEC(&States::start); if (*filename) { /* Load file into Q-Register */ register_argument->load(filename); } else { /* Edit Q-Register */ current_doc_undo_edit(); QRegisters::globals.edit(register_argument); } return &States::start; } /*$ * E%q$ -- Save Q-Register string to file * * Saves the string contents of Q-Register to * . * The must always be specified, as Q-Registers * have no notion of associated file names. * * In interactive mode, the E% command may be rubbed out, * restoring the previous state of . * This follows the same rules as with the \fBEW\fP command. * * File names may also be tab-completed and string building * characters are enabled by default. */ State * StateEPctCommand::got_register(QRegister *reg) { BEGIN_EXEC(&States::saveqreg); register_argument = reg; return &States::saveqreg; } State * StateSaveQReg::got_file(const gchar *filename) { BEGIN_EXEC(&States::start); register_argument->save(filename); return &States::start; } /*$ * Qq -> n -- Query Q-Register existence, its integer or string characters * Qq -> character * :Qq -> -1 | size * * Without any arguments, get and return the integer-part of * Q-Register . * * With one argument, return the code at * from the string-part of Q-Register . * Positions are handled like buffer positions \(em they * begin at 0 up to the length of the string minus 1. * An error is thrown for invalid positions. * Both non-colon-modified forms of Q require register * to be defined and fail otherwise. * * When colon-modified, Q does not pop any arguments from * the expression stack and returns the of the string * in Q-Register if register exists (i.e. is defined). * Naturally, for empty strings, 0 is returned. * When colon-modified and Q-Register is undefined, * -1 is returned instead. * Therefore checking the return value \fB:Q\fP for values smaller * 0 allows checking the existence of a register. * Note that if exists, its string part is not initialized, * so \fB:Q\fP may be used to handle purely numeric data structures * without creating Scintilla documents by accident. * These semantics allow the useful idiom \(lq:Q\fIq\fP">\(rq for * checking whether a Q-Register exists and has a non-empty string. * Note also that the return value of \fB:Q\fP may be interpreted * as a condition boolean that represents the non-existence of . * If is undefined, it returns \fIsuccess\fP, else a \fIfailure\fP * boolean. */ StateQueryQReg::StateQueryQReg() : machine(QREG_OPTIONAL) { transitions['\0'] = this; } State * StateQueryQReg::custom(gchar chr) { QRegister *reg; if (!machine.input(chr, reg)) return this; /* like BEGIN_EXEC(&States::start), but resets machine */ if (mode > MODE_NORMAL) goto reset; expressions.eval(); if (eval_colon()) { /* Query Q-Register's existence or string size */ expressions.push(reg ? reg->get_string_size() : (tecoInt)-1); goto reset; } /* * NOTE: This command is special since the QRegister is required * without colon and otherwise optional. * While it may be clearer to model this as two States, * we cannot currently let parsing depend on the colon-modifier. * That's why we have to declare the Q-Reg machine as QREG_OPTIONAL * and care about exception throwing on our own. * Since we need the machine's state to throw a reasonable error * we cannot derive from StateExpectQReg since it has to reset the * machine before calling got_register(). */ if (!reg) machine.fail(); if (expressions.args() > 0) { /* Query character from Q-Register string */ gint c = reg->get_character(expressions.pop_num_calc()); if (c < 0) throw RangeError('Q'); expressions.push(c); } else { /* Query integer */ expressions.push(reg->get_integer()); } reset: machine.reset(); return &States::start; } /*$ * [c1,c2,...]^Uq[string]$ -- Set or append to Q-Register string without string building * [c1,c2,...]:^Uq[string]$ * * If not colon-modified, it first fills the Q-Register * with all the values on the expression stack (interpreted as * codepoints). * It does so in the order of the arguments, i.e. * will be the first character in , the second, etc. * Eventually the argument is appended to the * register. * Any existing string value in is overwritten by this operation. * * In the colon-modified form ^U does not overwrite existing * contents of but only appends to it. * * If is undefined, it will be defined. * * String-building characters are \fBdisabled\fP for ^U * commands. * Therefore they are especially well-suited for defining * \*(ST macros, since string building characters in the * desired Q-Register contents do not have to be escaped. * The \fBEU\fP command may be used where string building * is desired. */ State * StateCtlUCommand::got_register(QRegister *reg) { BEGIN_EXEC(&States::setqregstring_nobuilding); register_argument = reg; return &States::setqregstring_nobuilding; } /*$ * [c1,c2,...]EUq[string]$ -- Set or append to Q-Register string with string building characters * [c1,c2,...]:EUq[string]$ * * This command sets or appends to the contents of * Q-Register \fIq\fP. * It is identical to the \fB^U\fP command, except * that this form of the command has string building * characters \fBenabled\fP. */ State * StateEUCommand::got_register(QRegister *reg) { BEGIN_EXEC(&States::setqregstring_building); register_argument = reg; return &States::setqregstring_building; } void StateSetQRegString::initial(void) { int args; expressions.eval(); args = expressions.args(); text_added = args > 0; if (!args) return; gchar buffer[args+1]; buffer[args] = '\0'; while (args--) buffer[args] = (gchar)expressions.pop_num_calc(); if (eval_colon()) { /* append to register */ register_argument->undo_append_string(); register_argument->append_string(buffer); } else { /* set register */ register_argument->undo_set_string(); register_argument->set_string(buffer); } } State * StateSetQRegString::done(const gchar *str) { BEGIN_EXEC(&States::start); if (text_added || eval_colon()) { /* * Append to register: * Note that append_string() does not create an UNDOACTION * if str == NULL */ if (str) { register_argument->undo_append_string(); register_argument->append_string(str); } } else { /* set register */ register_argument->undo_set_string(); register_argument->set_string(str); } return &States::start; } /*$ * Gq -- Insert Q-Register string * * Inserts the string of Q-Register into the buffer * at its current position. * Specifying an undefined yields an error. */ State * StateGetQRegString::got_register(QRegister *reg) { gchar *str; BEGIN_EXEC(&States::start); str = reg->get_string(); if (*str) { interface.ssm(SCI_BEGINUNDOACTION); interface.ssm(SCI_ADDTEXT, strlen(str), (sptr_t)str); interface.ssm(SCI_SCROLLCARET); interface.ssm(SCI_ENDUNDOACTION); ring.dirtify(); interface.undo_ssm(SCI_UNDO); } g_free(str); return &States::start; } /*$ * nUq -- Set Q-Register integer * -Uq * [n]:Uq -> Success|Failure * * Sets the integer-part of Q-Register to . * \(lq-U\(rq is equivalent to \(lq-1U\(rq, otherwise * the command fails if is missing. * * If the command is colon-modified, it returns a success * boolean if or \(lq-\(rq is given. * Otherwise it returns a failure boolean and does not * modify . * * The register is defined if it does not exist. */ State * StateSetQRegInteger::got_register(QRegister *reg) { BEGIN_EXEC(&States::start); expressions.eval(); if (expressions.args() || expressions.num_sign < 0) { reg->undo_set_integer(); reg->set_integer(expressions.pop_num_calc()); if (eval_colon()) expressions.push(SUCCESS); } else if (eval_colon()) { expressions.push(FAILURE); } else { throw ArgExpectedError('U'); } return &States::start; } /*$ * [n]%q -> q+n -- Increase Q-Register integer * * Add to the integer part of register , returning * its new value. * will be defined if it does not exist. */ State * StateIncreaseQReg::got_register(QRegister *reg) { tecoInt res; BEGIN_EXEC(&States::start); reg->undo_set_integer(); res = reg->get_integer() + expressions.pop_num_calc(); expressions.push(reg->set_integer(res)); return &States::start; } /*$ * Mq -- Execute macro * :Mq * * Execute macro stored in string of Q-Register . * The command itself does not push or pop and arguments from the stack * but the macro executed might well do so. * The new macro invocation level will contain its own go-to label table * and local Q-Register table. * Except when the command is colon-modified - in this case, local * Q-Registers referenced in the macro refer to the parent macro-level's * local Q-Register table (or whatever level defined one last). * * Errors during the macro execution will propagate to the M command. * In other words if a command in the macro fails, the M command will fail * and this failure propagates until the top-level macro (e.g. * the command-line macro). * * Note that the string of will be copied upon macro execution, * so subsequent changes to Q-Register from inside the macro do * not modify the executed code. */ State * StateMacro::got_register(QRegister *reg) { BEGIN_EXEC(&States::start); /* don't create new local Q-Registers if colon modifier is given */ reg->execute(!eval_colon()); return &States::start; } /*$ * EMfile$ -- Execute macro from file * :EMfile$ * * Read the file with name into memory and execute its contents * as a macro. * It is otherwise similar to the \(lqM\(rq command. * * If could not be read, the command yields an error. */ State * StateMacroFile::got_file(const gchar *filename) { BEGIN_EXEC(&States::start); /* don't create new local Q-Registers if colon modifier is given */ Execute::file(filename, !eval_colon()); return &States::start; } /*$ * [lines]Xq -- Copy into or append to Q-Register * -Xq * from,toXq * [lines]:Xq * -:Xq * from,to:Xq * * Copy the next or previous number of from the buffer * into the Q-Register string. * If is omitted, the sign prefix is implied. * If two arguments are specified, the characters beginning * at position up to the character at position * are copied. * The semantics of the arguments is analogous to the K * command's arguments. * If the command is colon-modified, the characters will be * appended to the end of register instead. * * Register will be created if it is undefined. */ State * StateCopyToQReg::got_register(QRegister *reg) { tecoInt from, len; Sci_TextRange tr; BEGIN_EXEC(&States::start); expressions.eval(); if (expressions.args() <= 1) { from = interface.ssm(SCI_GETCURRENTPOS); sptr_t line = interface.ssm(SCI_LINEFROMPOSITION, from) + expressions.pop_num_calc(); if (!Validate::line(line)) throw RangeError("X"); len = interface.ssm(SCI_POSITIONFROMLINE, line) - from; if (len < 0) { from += len; len *= -1; } } else { tecoInt to = expressions.pop_num(); from = expressions.pop_num(); len = to - from; if (len < 0 || !Validate::pos(from) || !Validate::pos(to)) throw RangeError("X"); } tr.chrg.cpMin = from; tr.chrg.cpMax = from + len; tr.lpstrText = (char *)g_malloc(len + 1); interface.ssm(SCI_GETTEXTRANGE, 0, (sptr_t)&tr); if (eval_colon()) { reg->undo_append_string(); reg->append_string(tr.lpstrText); } else { reg->undo_set_string(); reg->set_string(tr.lpstrText); } g_free(tr.lpstrText); return &States::start; } } /* namespace SciTECO */