From d8a316514c03d85b771a9dce4a8a51b875d955b3 Mon Sep 17 00:00:00 2001 From: Robin Haberkorn Date: Tue, 4 Dec 2012 17:29:01 +0100 Subject: autoconf preparation: move everything into src/ subdir --- cmdline.cpp | 371 ------------ expressions.cpp | 201 ------- expressions.h | 194 ------ goto.cpp | 147 ----- goto.h | 107 ---- gtk-info-popup.gob | 165 ------ interface-gtk.cpp | 248 -------- interface-gtk.h | 73 --- interface-ncurses.cpp | 426 -------------- interface-ncurses.h | 69 --- interface.h | 112 ---- main.cpp | 208 ------- parser.cpp | 1436 --------------------------------------------- parser.h | 266 --------- qregisters.cpp | 522 ---------------- qregisters.h | 343 ----------- rbtree.cpp | 5 - rbtree.h | 282 --------- ring.cpp | 539 ----------------- ring.h | 246 -------- sciteco.h | 76 --- search.cpp | 571 ------------------ search.h | 102 ---- src/cmdline.cpp | 371 ++++++++++++ src/expressions.cpp | 201 +++++++ src/expressions.h | 194 ++++++ src/goto.cpp | 147 +++++ src/goto.h | 107 ++++ src/gtk-info-popup.gob | 165 ++++++ src/interface-gtk.cpp | 248 ++++++++ src/interface-gtk.h | 73 +++ src/interface-ncurses.cpp | 426 ++++++++++++++ src/interface-ncurses.h | 69 +++ src/interface.h | 112 ++++ src/main.cpp | 208 +++++++ src/parser.cpp | 1436 +++++++++++++++++++++++++++++++++++++++++++++ src/parser.h | 266 +++++++++ src/qregisters.cpp | 522 ++++++++++++++++ src/qregisters.h | 343 +++++++++++ src/rbtree.cpp | 5 + src/rbtree.h | 282 +++++++++ src/ring.cpp | 539 +++++++++++++++++ src/ring.h | 246 ++++++++ src/sciteco.h | 76 +++ src/search.cpp | 571 ++++++++++++++++++ src/search.h | 102 ++++ src/symbols-extract.tes | 79 +++ src/symbols.cpp | 60 ++ src/symbols.h | 38 ++ src/undo.cpp | 78 +++ src/undo.h | 134 +++++ symbols-extract.tes | 79 --- symbols.cpp | 60 -- symbols.h | 38 -- undo.cpp | 78 --- undo.h | 134 ----- 56 files changed, 7098 insertions(+), 7098 deletions(-) delete mode 100644 cmdline.cpp delete mode 100644 expressions.cpp delete mode 100644 expressions.h delete mode 100644 goto.cpp delete mode 100644 goto.h delete mode 100644 gtk-info-popup.gob delete mode 100644 interface-gtk.cpp delete mode 100644 interface-gtk.h delete mode 100644 interface-ncurses.cpp delete mode 100644 interface-ncurses.h delete mode 100644 interface.h delete mode 100644 main.cpp delete mode 100644 parser.cpp delete mode 100644 parser.h delete mode 100644 qregisters.cpp delete mode 100644 qregisters.h delete mode 100644 rbtree.cpp delete mode 100644 rbtree.h delete mode 100644 ring.cpp delete mode 100644 ring.h delete mode 100644 sciteco.h delete mode 100644 search.cpp delete mode 100644 search.h create mode 100644 src/cmdline.cpp create mode 100644 src/expressions.cpp create mode 100644 src/expressions.h create mode 100644 src/goto.cpp create mode 100644 src/goto.h create mode 100644 src/gtk-info-popup.gob create mode 100644 src/interface-gtk.cpp create mode 100644 src/interface-gtk.h create mode 100644 src/interface-ncurses.cpp create mode 100644 src/interface-ncurses.h create mode 100644 src/interface.h create mode 100644 src/main.cpp create mode 100644 src/parser.cpp create mode 100644 src/parser.h create mode 100644 src/qregisters.cpp create mode 100644 src/qregisters.h create mode 100644 src/rbtree.cpp create mode 100644 src/rbtree.h create mode 100644 src/ring.cpp create mode 100644 src/ring.h create mode 100644 src/sciteco.h create mode 100644 src/search.cpp create mode 100644 src/search.h create mode 100755 src/symbols-extract.tes create mode 100644 src/symbols.cpp create mode 100644 src/symbols.h create mode 100644 src/undo.cpp create mode 100644 src/undo.h delete mode 100755 symbols-extract.tes delete mode 100644 symbols.cpp delete mode 100644 symbols.h delete mode 100644 undo.cpp delete mode 100644 undo.h diff --git a/cmdline.cpp b/cmdline.cpp deleted file mode 100644 index 212722a..0000000 --- a/cmdline.cpp +++ /dev/null @@ -1,371 +0,0 @@ -#include -#include - -#include -#include -#include - -#include "sciteco.h" -#include "interface.h" -#include "expressions.h" -#include "parser.h" -#include "qregisters.h" -#include "ring.h" -#include "goto.h" -#include "undo.h" -#include "symbols.h" - -static inline const gchar *process_edit_cmd(gchar key); -static gchar *macro_echo(const gchar *macro); - -static gchar *filename_complete(const gchar *filename, gchar completed = ' '); -static gchar *symbol_complete(SymbolList &list, const gchar *symbol, - gchar completed = ' '); - -static const gchar *last_occurrence(const gchar *str, - const gchar *chars = " \t\v\r\n\f<>,;@"); -static inline gboolean filename_is_dir(const gchar *filename); - -gchar *cmdline = NULL; - -bool quit_requested = false; - -void -cmdline_keypress(gchar key) -{ - gchar *cmdline_p; - const gchar *insert; - gchar *echo; - - /* - * Cleanup messages, popups, etc... - */ - interface.popup_clear(); - interface.msg_clear(); - - /* - * Process immediate editing commands - */ - insert = process_edit_cmd(key); - - /* - * Parse/execute characters - */ - if (cmdline) { - gint len = strlen(cmdline); - cmdline = (gchar *)g_realloc(cmdline, len + strlen(insert) + 1); - cmdline_p = cmdline + len; - } else { - cmdline = (gchar *)g_malloc(strlen(insert) + 1); - *cmdline = '\0'; - cmdline_p = cmdline; - } - - /* - * Execute one insertion character, extending cmdline, at a time so - * undo tokens get emitted for the corresponding characters. - */ - for (const gchar *p = insert; *p; p++) { - *cmdline_p++ = *p; - *cmdline_p = '\0'; - - try { - Execute::step(cmdline); - } catch (...) { - /* - * Undo tokens may have been emitted (or had to be) - * before the exception is thrown. They must be - * executed so as if the character had never been - * inserted. - */ - undo.pop(cmdline_p - cmdline); - cmdline_p[-1] = '\0'; - break; - } - } - - /* - * Echo command line - */ - echo = macro_echo(cmdline); - interface.cmdline_update(echo); - g_free(echo); -} - -static inline const gchar * -process_edit_cmd(gchar key) -{ - static gchar insert[255]; - gint cmdline_len = cmdline ? strlen(cmdline) : 0; - - insert[0] = '\0'; - - switch (key) { - case '\b': - if (cmdline_len) { - undo.pop(cmdline_len); - cmdline[cmdline_len - 1] = '\0'; - macro_pc--; - } - break; - - case CTL_KEY('T'): { - const gchar *filename = cmdline ? last_occurrence(cmdline) + 1 - : NULL; - gchar *new_chars = filename_complete(filename); - if (new_chars) - g_stpcpy(insert, new_chars); - g_free(new_chars); - break; - } - - case '\t': - if (States::current == &States::editfile || - States::current == &States::savefile || - States::current == &States::loadqreg) { - gchar complete = escape_char == '{' ? ' ' : escape_char; - gchar *new_chars = filename_complete(strings[0], - complete); - if (new_chars) - g_stpcpy(insert, new_chars); - g_free(new_chars); - } else if (States::current == &States::scintilla_symbols) { - const gchar *symbol = NULL; - SymbolList &list = Symbols::scintilla; - gchar *new_chars; - - if (strings[0]) { - symbol = last_occurrence(strings[0], ","); - if (*symbol == ',') { - symbol++; - list = Symbols::scilexer; - } - } - - new_chars = symbol_complete(list, symbol, ','); - if (new_chars) - g_stpcpy(insert, new_chars); - g_free(new_chars); - } else { - insert[0] = key; - insert[1] = '\0'; - } - break; - - case '\x1B': - if (States::current == &States::start && - cmdline && cmdline[cmdline_len - 1] == '\x1B') { - if (Goto::skip_label) { - interface.msg(Interface::MSG_ERROR, - "Label \"%s\" not found", - Goto::skip_label); - break; - } - - if (quit_requested) { - /* FIXME */ - exit(EXIT_SUCCESS); - } - - undo.clear(); - interface.ssm(SCI_EMPTYUNDOBUFFER); - Goto::table->clear(); - expressions.clear(); - - *cmdline = '\0'; - macro_pc = 0; - break; - } - /* fall through */ - default: - insert[0] = key; - insert[1] = '\0'; - } - - return insert; -} - -static gchar * -macro_echo(const gchar *macro) -{ - gchar *result, *rp; - - if (!macro) - return g_strdup(""); - - rp = result = (gchar *)g_malloc(strlen(macro)*5 + 1); - - for (const gchar *p = macro; *p; p++) { - switch (*p) { - case '\x1B': - *rp++ = '$'; - break; - case '\r': - rp = g_stpcpy(rp, ""); - break; - case '\n': - rp = g_stpcpy(rp, ""); - break; - case '\t': - rp = g_stpcpy(rp, ""); - break; - default: - if (IS_CTL(*p)) { - *rp++ = '^'; - *rp++ = CTL_ECHO(*p); - } else { - *rp++ = *p; - } - } - } - *rp = '\0'; - - return result; -} - -static gchar * -filename_complete(const gchar *filename, gchar completed) -{ - gchar *dirname, *basename; - GDir *dir; - GList *files = NULL, *matching; - GCompletion *completion; - gchar *new_prefix; - gchar *insert = NULL; - - if (!filename) - filename = ""; - - if (is_glob_pattern(filename)) - return NULL; - - dirname = g_path_get_dirname(filename); - dir = g_dir_open(dirname, 0, NULL); - if (!dir) { - g_free(dirname); - return NULL; - } - if (*dirname != *filename) - *dirname = '\0'; - - while ((basename = (gchar *)g_dir_read_name(dir))) { - gchar *filename = g_build_filename(dirname, basename, NULL); - - if (g_file_test(filename, G_FILE_TEST_IS_DIR)) { - gchar *new_filename; - new_filename = g_strconcat(filename, - G_DIR_SEPARATOR_S, NULL); - g_free(filename); - filename = new_filename; - } - - files = g_list_prepend(files, filename); - } - - g_free(dirname); - g_dir_close(dir); - - completion = g_completion_new(NULL); - g_completion_add_items(completion, files); - - matching = g_completion_complete(completion, filename, &new_prefix); - if (new_prefix && strlen(new_prefix) > strlen(filename)) - insert = g_strdup(new_prefix + strlen(filename)); - g_free(new_prefix); - - if (!insert && g_list_length(matching) > 1) { - matching = g_list_sort(matching, (GCompareFunc)g_strcmp0); - - for (GList *file = g_list_first(matching); - file != NULL; - file = g_list_next(file)) { - Interface::PopupEntryType type; - bool in_buffer = false; - - if (filename_is_dir((gchar *)file->data)) { - type = Interface::POPUP_DIRECTORY; - } else { - type = Interface::POPUP_FILE; - /* FIXME: inefficient */ - in_buffer = ring.find((gchar *)file->data); - } - - interface.popup_add(type, (gchar *)file->data, - in_buffer); - } - - interface.popup_show(); - } else if (g_list_length(matching) == 1 && - !filename_is_dir((gchar *)g_list_first(matching)->data)) { - String::append(insert, completed); - } - - g_completion_free(completion); - - for (GList *file = g_list_first(files); file; file = g_list_next(file)) - g_free(file->data); - g_list_free(files); - - return insert; -} - -static gchar * -symbol_complete(SymbolList &list, const gchar *symbol, gchar completed) -{ - GList *glist; - GList *matching; - GCompletion *completion; - gchar *new_prefix; - gchar *insert = NULL; - - if (!symbol) - symbol = ""; - - glist = list.get_glist(); - if (!glist) - return NULL; - - completion = g_completion_new(NULL); - g_completion_add_items(completion, glist); - - matching = g_completion_complete(completion, symbol, &new_prefix); - if (new_prefix && strlen(new_prefix) > strlen(symbol)) - insert = g_strdup(new_prefix + strlen(symbol)); - g_free(new_prefix); - - if (!insert && g_list_length(matching) > 1) { - for (GList *entry = g_list_first(matching); - entry != NULL; - entry = g_list_next(entry)) { - interface.popup_add(Interface::POPUP_PLAIN, - (gchar *)entry->data); - } - - interface.popup_show(); - } else if (g_list_length(matching) == 1) { - String::append(insert, completed); - } - - g_completion_free(completion); - - return insert; -} - -/* - * Auxiliary functions - */ - -static const gchar * -last_occurrence(const gchar *str, const gchar *chars) -{ - while (*chars) - str = strrchr(str, *chars++) ? : str; - - return str; -} - -static inline gboolean -filename_is_dir(const gchar *filename) -{ - return g_str_has_suffix(filename, G_DIR_SEPARATOR_S); -} diff --git a/expressions.cpp b/expressions.cpp deleted file mode 100644 index ac06b43..0000000 --- a/expressions.cpp +++ /dev/null @@ -1,201 +0,0 @@ -#include - -#include "sciteco.h" -#include "undo.h" -#include "expressions.h" - -Expressions expressions; - -void -Expressions::set_num_sign(gint sign) -{ - undo.push_var(num_sign); - num_sign = sign; -} - -void -Expressions::set_radix(gint r) -{ - undo.push_var(radix); - radix = r; -} - -gint64 -Expressions::push(gint64 number) -{ - while (operators.items() && operators.peek() == OP_NEW) - pop_op(); - - push(OP_NUMBER); - - if (num_sign < 0) { - set_num_sign(1); - number *= -1; - } - - numbers.undo_pop(); - return numbers.push(number); -} - -gint64 -Expressions::pop_num(int index) -{ - gint64 n = 0; - - pop_op(); - if (numbers.items() > 0) { - n = numbers.pop(index); - numbers.undo_push(n, index); - } - - return n; -} - -gint64 -Expressions::pop_num_calc(int index, gint64 imply) -{ - eval(); - if (num_sign < 0) - set_num_sign(1); - - return args() > 0 ? pop_num(index) : imply; -} - -gint64 -Expressions::add_digit(gchar digit) -{ - gint64 n = args() > 0 ? pop_num() : 0; - - return push(n*radix + (n < 0 ? -1 : 1)*(digit - '0')); -} - -Expressions::Operator -Expressions::push(Expressions::Operator op) -{ - operators.undo_pop(); - return operators.push(op); -} - -Expressions::Operator -Expressions::push_calc(Expressions::Operator op) -{ - int first = first_op(); - - /* calculate if op has lower precedence than op on stack */ - if (first && operators.peek(first) <= op) - calc(); - - return push(op); -} - -Expressions::Operator -Expressions::pop_op(int index) -{ - Operator op = OP_NIL; - - if (operators.items() > 0) { - op = operators.pop(index); - operators.undo_push(op, index); - } - - return op; -} - -void -Expressions::calc(void) -{ - gint64 result; - - gint64 vright = pop_num(); - Operator op = pop_op(); - gint64 vleft = pop_num(); - - switch (op) { - case OP_POW: for (result = 1; vright--; result *= vleft); break; - case OP_MUL: result = vleft * vright; break; - case OP_DIV: result = vleft / vright; break; - case OP_MOD: result = vleft % vright; break; - case OP_ADD: result = vleft + vright; break; - case OP_SUB: result = vleft - vright; break; - case OP_AND: result = vleft & vright; break; - case OP_OR: result = vleft | vright; break; - default: - /* shouldn't happen */ - g_assert(false); - } - - push(result); -} - -void -Expressions::eval(bool pop_brace) -{ - for (;;) { - gint n = first_op(); - Operator op; - - if (n < 2) - break; - - op = operators.peek(n); - if (op == OP_LOOP) - break; - if (op == OP_BRACE) { - if (pop_brace) - pop_op(n); - break; - } - - calc(); - } -} - -int -Expressions::args(void) -{ - int n = 0; - int items = operators.items(); - - while (n < items && operators.peek(n+1) == OP_NUMBER) - n++; - - return n; -} - -int -Expressions::find_op(Operator op) -{ - int items = operators.items(); - - for (int i = 1; i <= items; i++) - if (operators.peek(i) == op) - return i; - - return 0; -} - -int -Expressions::first_op(void) -{ - int items = operators.items(); - - for (int i = 1; i <= items; i++) { - switch (operators.peek(i)) { - case OP_NUMBER: - case OP_NEW: - break; - default: - return i; - } - } - - return 0; -} - -void -Expressions::discard_args(void) -{ - eval(); - for (int i = args(); i; i--) - pop_num_calc(); -} diff --git a/expressions.h b/expressions.h deleted file mode 100644 index a441e90..0000000 --- a/expressions.h +++ /dev/null @@ -1,194 +0,0 @@ -#ifndef __EXPRESSIONS_H -#define __EXPRESSIONS_H - -#include - -#include "undo.h" - -template -class ValueStack { - class UndoTokenPush : public UndoToken { - ValueStack *stack; - - Type value; - int index; - - public: - UndoTokenPush(ValueStack *_stack, - Type _value, int _index = 1) - : stack(_stack), value(_value), index(_index) {} - - void - run(void) - { - stack->push(value, index); - } - }; - - class UndoTokenPop : public UndoToken { - ValueStack *stack; - - int index; - - public: - UndoTokenPop(ValueStack *_stack, int _index = 1) - : stack(_stack), index(_index) {} - - void - run(void) - { - stack->pop(index); - } - }; - - int size; - - Type *stack; - Type *top; - -public: - ValueStack(int _size = 1024) : size(_size) - { - top = stack = new Type[size]; - } - - ~ValueStack() - { - delete stack; - } - - inline int - items(void) - { - return top - stack; - } - - inline Type & - push(Type value, int index = 1) - { - for (int i = -index + 1; i; i++) - top[i+1] = top[i]; - - top++; - return peek(index) = value; - } - inline void - undo_push(Type value, int index = 1) - { - undo.push(new UndoTokenPush(this, value, index)); - } - - inline Type - pop(int index = 1) - { - Type v = peek(index); - - top--; - while (--index) - top[-index] = top[-index + 1]; - - return v; - } - inline void - undo_pop(int index = 1) - { - undo.push(new UndoTokenPop(this, index)); - } - - inline Type & - peek(int index = 1) - { - return top[-index]; - } - - inline void - clear(void) - { - top = stack; - } -}; - -/* - * Arithmetic expression stacks - */ -extern class Expressions { -public: - /* reflects also operator precedence */ - enum Operator { - OP_NIL = 0, - OP_POW, // ^* - OP_MUL, // * - OP_DIV, // / - OP_MOD, // ^/ - OP_SUB, // - - OP_ADD, // + - OP_AND, // & - OP_OR, // # - // pseudo operators: - OP_NEW, - OP_BRACE, - OP_LOOP, - OP_NUMBER - }; - -private: - ValueStack numbers; - ValueStack operators; - -public: - Expressions() : num_sign(1), radix(10) {} - - gint num_sign; - void set_num_sign(gint sign); - - gint radix; - void set_radix(gint r); - - gint64 push(gint64 number); - - inline gint64 - peek_num(int index = 1) - { - return numbers.peek(index); - } - gint64 pop_num(int index = 1); - gint64 pop_num_calc(int index, gint64 imply); - inline gint64 - pop_num_calc(int index = 1) - { - return pop_num_calc(index, num_sign); - } - - gint64 add_digit(gchar digit); - - Operator push(Operator op); - Operator push_calc(Operator op); - inline Operator - peek_op(int index = 1) - { - return operators.peek(index); - } - Operator pop_op(int index = 1); - - void eval(bool pop_brace = false); - - int args(void); - - void discard_args(void); - - int find_op(Operator op); - - inline void - clear(void) - { - numbers.clear(); - operators.clear(); - } - -private: - void calc(void); - - int first_op(void); -} expressions; - -#endif diff --git a/goto.cpp b/goto.cpp deleted file mode 100644 index 7af5152..0000000 --- a/goto.cpp +++ /dev/null @@ -1,147 +0,0 @@ -#include -#include - -#include "sciteco.h" -#include "expressions.h" -#include "parser.h" -#include "undo.h" -#include "goto.h" - -namespace States { - StateLabel label; - StateGotoCmd gotocmd; -} - -namespace Goto { - GotoTable *table = NULL; - gchar *skip_label = NULL; -} - -gint -GotoTable::remove(gchar *name) -{ - gint existing_pc = -1; - - Label label(name); - Label *existing = (Label *)RBTree::find(&label); - - if (existing) { - existing_pc = existing->pc; - RBTree::remove(existing); - delete existing; - } - - return existing_pc; -} - -gint -GotoTable::find(gchar *name) -{ - Label label(name); - Label *existing = (Label *)RBTree::find(&label); - - return existing ? existing->pc : -1; -} - -gint -GotoTable::set(gchar *name, gint pc) -{ - if (pc < 0) - return remove(name); - - Label *label = new Label(name, pc); - Label *existing; - gint existing_pc = -1; - - existing = (Label *)RBTree::find(label); - if (existing) { - existing_pc = existing->pc; - g_free(existing->name); - existing->name = label->name; - existing->pc = label->pc; - - label->name = NULL; - delete label; - } else { - RBTree::insert(label); - } - -#ifdef DEBUG - dump(); -#endif - - return existing_pc; -} - -#ifdef DEBUG -void -GotoTable::dump(void) -{ - for (Label *cur = (Label *)RBTree::min(); - cur != NULL; - cur = (Label *)cur->next()) - g_printf("table[\"%s\"] = %d\n", cur->name, cur->pc); - g_printf("---END---\n"); -} -#endif - -/* - * Command states - */ - -StateLabel::StateLabel() : State() -{ - transitions['\0'] = this; -} - -State * -StateLabel::custom(gchar chr) throw (Error) -{ - if (chr == '!') { - Goto::table->undo_set(strings[0], - Goto::table->set(strings[0], macro_pc)); - - if (!g_strcmp0(strings[0], Goto::skip_label)) { - g_free(undo.push_str(Goto::skip_label)); - Goto::skip_label = NULL; - - undo.push_var(mode) = MODE_NORMAL; - } - - g_free(undo.push_str(strings[0])); - strings[0] = NULL; - - return &States::start; - } - - String::append(undo.push_str(strings[0]), chr); - return this; -} - -State * -StateGotoCmd::done(const gchar *str) throw (Error) -{ - gint64 value; - gchar **labels; - - BEGIN_EXEC(&States::start); - - value = expressions.pop_num_calc(); - labels = g_strsplit(str, ",", -1); - - if (value > 0 && value <= g_strv_length(labels) && *labels[value-1]) { - gint pc = Goto::table->find(labels[value-1]); - - if (pc >= 0) { - macro_pc = pc; - } else { - /* skip till label is defined */ - undo.push_str(Goto::skip_label); - Goto::skip_label = g_strdup(labels[value-1]); - undo.push_var(mode) = MODE_PARSE_ONLY_GOTO; - } - } - - g_strfreev(labels); - return &States::start; -} diff --git a/goto.h b/goto.h deleted file mode 100644 index 6275d34..0000000 --- a/goto.h +++ /dev/null @@ -1,107 +0,0 @@ -#ifndef __GOTO_H -#define __GOTO_H - -#include -#include - -#include "sciteco.h" -#include "parser.h" -#include "undo.h" -#include "rbtree.h" - -class GotoTable : public RBTree { - class UndoTokenSet : public UndoToken { - GotoTable *table; - - gchar *name; - gint pc; - - public: - UndoTokenSet(GotoTable *_table, gchar *_name, gint _pc = -1) - : table(_table), name(g_strdup(_name)), pc(_pc) {} - ~UndoTokenSet() - { - g_free(name); - } - - void - run(void) - { - table->set(name, pc); -#ifdef DEBUG - table->dump(); -#endif - } - }; - - class Label : public RBEntry { - public: - gchar *name; - gint pc; - - Label(gchar *_name, gint _pc = -1) - : name(g_strdup(_name)), pc(_pc) {} - ~Label() - { - g_free(name); - } - - int - operator <(RBEntry &l2) - { - return g_strcmp0(name, ((Label &)l2).name); - } - }; - - /* - * whether to generate UndoTokens (unnecessary in macro invocations) - */ - bool must_undo; - -public: - GotoTable(bool _undo = true) : RBTree(), must_undo(_undo) {} - - gint remove(gchar *name); - gint find(gchar *name); - - gint set(gchar *name, gint pc); - inline void - undo_set(gchar *name, gint pc = -1) - { - if (must_undo) - undo.push(new UndoTokenSet(this, name, pc)); - } - -#ifdef DEBUG - void dump(void); -#endif -}; - -namespace Goto { - extern GotoTable *table; - extern gchar *skip_label; -} - -/* - * Command states - */ - -class StateLabel : public State { -public: - StateLabel(); - -private: - State *custom(gchar chr) throw (Error); -}; - -class StateGotoCmd : public StateExpectString { -private: - State *done(const gchar *str) throw (Error); -}; - -namespace States { - extern StateLabel label; - extern StateGotoCmd gotocmd; -} - -#endif diff --git a/gtk-info-popup.gob b/gtk-info-popup.gob deleted file mode 100644 index 914647a..0000000 --- a/gtk-info-popup.gob +++ /dev/null @@ -1,165 +0,0 @@ -requires 2.0.16 - -%privateheader{ -#include -%} - -%h{ -#include -%} - -enum GTK_INFO_POPUP { - PLAIN, - FILE, - DIRECTORY -} Gtk:Info:Popup:Entry:Type; - -class Gtk:Info:Popup from Gtk:Window { - private GtkWidget *parent; - - init(self) - { - GtkWidget *vbox; - - //gtk_window_set_gravity(GTK_WINDOW(self), GDK_GRAVITY_SOUTH_WEST); - gtk_container_set_border_width(GTK_CONTAINER(self), 10); - - vbox = gtk_vbox_new(FALSE, 5); - gtk_container_set_resize_mode(GTK_CONTAINER(vbox), GTK_RESIZE_PARENT); - gtk_container_add(GTK_CONTAINER(self), vbox); - gtk_widget_show(vbox); - } - - public GtkWidget * - new(Gtk:Widget *parent) - { - Self *widget; - GtkWidget *toplevel; - - widget = GET_NEW_VARG("type", GTK_WINDOW_POPUP, NULL); - widget->_priv->parent = parent; - - g_signal_connect(parent, "size-allocate", - G_CALLBACK(self_size_allocate_cb), widget); - toplevel = gtk_widget_get_toplevel(parent); - g_signal_connect(toplevel, "configure-event", - G_CALLBACK(self_configure_cb), widget); - - return GTK_WIDGET(widget); - } - - private void - position(self) - { - GdkWindow *window = gtk_widget_get_window(self->_priv->parent); - gint x, y; - gint w, h; - - if (!window) - return; - - gdk_window_get_origin(window, &x, &y); - gtk_window_get_size(GTK_WINDOW(self), &w, &h); - - gtk_window_move(GTK_WINDOW(self), x, y - h); - } - - private void - size_allocate_cb(Gtk:Widget *parent, - Gtk:Allocation *alloc, gpointer user_data) - { - self_position(SELF(user_data)); - } - - private gboolean - configure_cb(Gtk:Widget *toplevel, - Gdk:Event:Configure *event, gpointer user_data) - { - self_position(SELF(user_data)); - return FALSE; - } - - override (Gtk:Widget) void - show(Gtk:Widget *self) - { - GtkRequisition req; - - gtk_widget_size_request(self, &req); - gtk_window_resize(GTK_WINDOW(self), req.width, req.height); - self_position(SELF(self)); - - PARENT_HANDLER(self); - } - - public void - add(self, Gtk:Info:Popup:Entry:Type type, - const gchar *name, gboolean highlight) - { - static const gchar *type2stock[] = { - [GTK_INFO_POPUP_PLAIN] = NULL, - [GTK_INFO_POPUP_FILE] = GTK_STOCK_FILE, - [GTK_INFO_POPUP_DIRECTORY] = GTK_STOCK_DIRECTORY - }; - - GtkWidget *vbox = gtk_bin_get_child(GTK_BIN(self)); - GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(self)); - GtkRequisition req; - - GtkWidget *hbox; - GtkWidget *label; - gchar *markup; - - gtk_widget_size_request(GTK_WIDGET(self), &req); - if (req.height > gdk_screen_get_height(screen)) - return; - - hbox = gtk_hbox_new(FALSE, 5); - - if (type2stock[type]) { - GtkWidget *image; - - image = gtk_image_new_from_stock(type2stock[type], - GTK_ICON_SIZE_MENU); - gtk_box_pack_start(GTK_BOX(hbox), image, - FALSE, FALSE, 0); - } - - /* - * FIXME: setting Pango attributes directly would be - * much more efficient - */ - label = gtk_label_new(NULL); - markup = g_markup_printf_escaped("%s", - highlight ? "bold" : "normal", - name); - gtk_label_set_markup(GTK_LABEL(label), markup); - g_free(markup); - gtk_misc_set_alignment(GTK_MISC(label), 0., 0.5); - gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); - - gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); - gtk_widget_show_all(hbox); - - gtk_widget_size_request(GTK_WIDGET(self), &req); - if (req.height > gdk_screen_get_height(screen)) { - label = gtk_label_new("..."); - gtk_box_pack_start(GTK_BOX(vbox), label, - FALSE, FALSE, 0); - gtk_widget_show(label); - } - } - - public void - clear(self) - { - GtkWidget *vbox = gtk_bin_get_child(GTK_BIN(self)); - GList *children; - - children = gtk_container_get_children(GTK_CONTAINER(vbox)); - for (GList *cur = g_list_first(children); - cur != NULL; - cur = g_list_next(cur)) - gtk_widget_destroy(GTK_WIDGET(cur->data)); - g_list_free(children); - } -} diff --git a/interface-gtk.cpp b/interface-gtk.cpp deleted file mode 100644 index 7515b2f..0000000 --- a/interface-gtk.cpp +++ /dev/null @@ -1,248 +0,0 @@ -#include - -#include -#include -#include - -#include -#include - -#include -#include "gtk-info-popup.h" - -#include -#include - -#include "sciteco.h" -#include "qregisters.h" -#include "ring.h" -#include "interface.h" -#include "interface-gtk.h" - -InterfaceGtk interface; - -extern "C" { -static void scintilla_notify(ScintillaObject *sci, uptr_t idFrom, - SCNotification *notify, gpointer user_data); -static gboolean cmdline_key_pressed(GtkWidget *widget, GdkEventKey *event, - gpointer user_data); -static gboolean exit_app(GtkWidget *w, GdkEventAny *e, gpointer p); -} - -#define UNNAMED_FILE "(Unnamed)" - -InterfaceGtk::InterfaceGtk() -{ - GtkWidget *vbox; - GtkWidget *info_content; - - gtk_init(NULL, NULL); - - window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_title(GTK_WINDOW(window), PACKAGE_NAME); - g_signal_connect(G_OBJECT(window), "delete-event", - G_CALLBACK(exit_app), NULL); - - vbox = gtk_vbox_new(FALSE, 0); - - editor_widget = scintilla_new(); - scintilla_set_id(SCINTILLA(editor_widget), 0); - gtk_widget_set_usize(editor_widget, 500, 300); - gtk_widget_set_can_focus(editor_widget, FALSE); - g_signal_connect(G_OBJECT(editor_widget), SCINTILLA_NOTIFY, - G_CALLBACK(scintilla_notify), NULL); - gtk_box_pack_start(GTK_BOX(vbox), editor_widget, TRUE, TRUE, 0); - - info_widget = gtk_info_bar_new(); - info_content = gtk_info_bar_get_content_area(GTK_INFO_BAR(info_widget)); - message_widget = gtk_label_new(""); - gtk_misc_set_alignment(GTK_MISC(message_widget), 0., 0.); - gtk_container_add(GTK_CONTAINER(info_content), message_widget); - gtk_box_pack_start(GTK_BOX(vbox), info_widget, FALSE, FALSE, 0); - - cmdline_widget = gtk_entry_new(); - gtk_entry_set_has_frame(GTK_ENTRY(cmdline_widget), FALSE); - gtk_editable_set_editable(GTK_EDITABLE(cmdline_widget), FALSE); - widget_set_font(cmdline_widget, "Courier"); - g_signal_connect(G_OBJECT(cmdline_widget), "key-press-event", - G_CALLBACK(cmdline_key_pressed), NULL); - gtk_box_pack_start(GTK_BOX(vbox), cmdline_widget, FALSE, FALSE, 0); - - gtk_container_add(GTK_CONTAINER(window), vbox); - - popup_widget = gtk_info_popup_new(cmdline_widget); - - gtk_widget_grab_focus(cmdline_widget); - - ssm(SCI_SETFOCUS, TRUE); - - cmdline_update(""); -} - -void -InterfaceGtk::vmsg(MessageType type, const gchar *fmt, va_list ap) -{ - static const GtkMessageType type2gtk[] = { - /* [MSG_USER] = */ GTK_MESSAGE_OTHER, - /* [MSG_INFO] = */ GTK_MESSAGE_INFO, - /* [MSG_WARNING] = */ GTK_MESSAGE_WARNING, - /* [MSG_ERROR] = */ GTK_MESSAGE_ERROR - }; - - va_list aq; - gchar buf[255]; - - va_copy(aq, ap); - stdio_vmsg(type, fmt, ap); - g_vsnprintf(buf, sizeof(buf), fmt, aq); - va_end(aq); - - gtk_info_bar_set_message_type(GTK_INFO_BAR(info_widget), - type2gtk[type]); - gtk_label_set_text(GTK_LABEL(message_widget), buf); -} - -void -InterfaceGtk::msg_clear(void) -{ - gtk_info_bar_set_message_type(GTK_INFO_BAR(info_widget), - GTK_MESSAGE_OTHER); - gtk_label_set_text(GTK_LABEL(message_widget), ""); -} - -void -InterfaceGtk::info_update(QRegister *reg) -{ - gchar buf[255]; - - g_snprintf(buf, sizeof(buf), "%s - %s", PACKAGE_NAME, - reg->name); - gtk_window_set_title(GTK_WINDOW(window), buf); -} - -void -InterfaceGtk::info_update(Buffer *buffer) -{ - gchar buf[255]; - - g_snprintf(buf, sizeof(buf), "%s - %s%s", PACKAGE_NAME, - buffer->filename ? : UNNAMED_FILE, - buffer->dirty ? "*" : ""); - gtk_window_set_title(GTK_WINDOW(window), buf); -} - -void -InterfaceGtk::cmdline_update(const gchar *cmdline) -{ - gint pos = 1; - - if (!cmdline) - /* widget automatically redrawn */ - return; - - gtk_entry_set_text(GTK_ENTRY(cmdline_widget), "*"); - gtk_editable_insert_text(GTK_EDITABLE(cmdline_widget), - cmdline, -1, &pos); - gtk_editable_set_position(GTK_EDITABLE(cmdline_widget), pos); -} - -void -InterfaceGtk::popup_add(PopupEntryType type, - const gchar *name, bool highlight) -{ - static const GtkInfoPopupEntryType type2gtk[] = { - /* [POPUP_PLAIN] = */ GTK_INFO_POPUP_PLAIN, - /* [POPUP_FILE] = */ GTK_INFO_POPUP_FILE, - /* [POPUP_DIRECTORY] = */ GTK_INFO_POPUP_DIRECTORY - }; - - gtk_info_popup_add(GTK_INFO_POPUP(popup_widget), - type2gtk[type], name, highlight); -} - -void -InterfaceGtk::popup_clear(void) -{ - if (gtk_widget_get_visible(popup_widget)) { - gtk_widget_hide(popup_widget); - gtk_info_popup_clear(GTK_INFO_POPUP(popup_widget)); - } -} - -void -InterfaceGtk::widget_set_font(GtkWidget *widget, const gchar *font_name) -{ - PangoFontDescription *font_desc; - - font_desc = pango_font_description_from_string(font_name); - gtk_widget_modify_font(widget, font_desc); - pango_font_description_free(font_desc); -} - -InterfaceGtk::~InterfaceGtk() -{ - gtk_widget_destroy(popup_widget); - gtk_widget_destroy(window); - - scintilla_release_resources(); -} - -/* - * GTK+ callbacks - */ - -static void -scintilla_notify(ScintillaObject *sci __attribute__((unused)), - uptr_t idFrom __attribute__((unused)), - SCNotification *notify, - gpointer user_data __attribute__((unused))) -{ - interface.process_notify(notify); -} - -static gboolean -cmdline_key_pressed(GtkWidget *widget, GdkEventKey *event, - gpointer user_data __attribute__((unused))) -{ -#ifdef DEBUG - g_printf("KEY \"%s\" (%d) SHIFT=%d CNTRL=%d\n", - event->string, *event->string, - event->state & GDK_SHIFT_MASK, event->state & GDK_CONTROL_MASK); -#endif - - switch (event->keyval) { - case GDK_BackSpace: - cmdline_keypress('\b'); - break; - case GDK_Tab: - cmdline_keypress('\t'); - break; - case GDK_Return: - switch (interface.ssm(SCI_GETEOLMODE)) { - case SC_EOL_CR: - cmdline_keypress('\r'); - break; - case SC_EOL_CRLF: - cmdline_keypress('\r'); - /* fall through */ - case SC_EOL_LF: - default: - cmdline_keypress('\n'); - } - break; - default: - if (*event->string) - cmdline_keypress(*event->string); - } - - return TRUE; -} - -static gboolean -exit_app(GtkWidget *w __attribute__((unused)), - GdkEventAny *e __attribute__((unused)), - gpointer p __attribute__((unused))) -{ - gtk_main_quit(); - return TRUE; -} diff --git a/interface-gtk.h b/interface-gtk.h deleted file mode 100644 index b46f821..0000000 --- a/interface-gtk.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef __INTERFACE_GTK_H -#define __INTERFACE_GTK_H - -#include - -#include -#include - -#include -#include - -#include "interface.h" - -extern class InterfaceGtk : public Interface { - GtkWidget *window; - GtkWidget *editor_widget; - GtkWidget *cmdline_widget; - GtkWidget *info_widget, *message_widget; - - GtkWidget *popup_widget; - -public: - InterfaceGtk(); - ~InterfaceGtk(); - - inline GOptionGroup * - get_options(void) - { - return gtk_get_option_group(TRUE); - } - inline void - parse_args(int &argc, char **&argv) - { - gtk_parse_args(&argc, &argv); - } - - void vmsg(MessageType type, const gchar *fmt, va_list ap); - void msg_clear(void); - - inline sptr_t - ssm(unsigned int iMessage, uptr_t wParam = 0, sptr_t lParam = 0) - { - return scintilla_send_message(SCINTILLA(editor_widget), - iMessage, wParam, lParam); - } - - void info_update(QRegister *reg); - void info_update(Buffer *buffer); - - void cmdline_update(const gchar *cmdline = NULL); - - void popup_add(PopupEntryType type, - const gchar *name, bool highlight = false); - inline void - popup_show(void) - { - gtk_widget_show(popup_widget); - } - void popup_clear(void); - - /* main entry point */ - inline void - event_loop(void) - { - gtk_widget_show_all(window); - gtk_main(); - } - -private: - static void widget_set_font(GtkWidget *widget, const gchar *font_name); -} interface; - -#endif diff --git a/interface-ncurses.cpp b/interface-ncurses.cpp deleted file mode 100644 index 0e08914..0000000 --- a/interface-ncurses.cpp +++ /dev/null @@ -1,426 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include - -#include -/* only a hack until we have Autoconf checks */ -#if defined(__PDCURSES__) && CHTYPE_LONG >= 2 -#define PDCURSES_WIN32A -#endif - -#include -#include - -#include "sciteco.h" -#include "qregisters.h" -#include "ring.h" -#include "interface.h" -#include "interface-ncurses.h" - -InterfaceNCurses interface; - -extern "C" { -static void scintilla_notify(Scintilla *sci, int idFrom, - void *notify, void *user_data); -} - -#define UNNAMED_FILE "(Unnamed)" - -/* FIXME: should be configurable in TECO (Function key substitutes) */ -#define ESCAPE_SURROGATE KEY_DC - -#ifndef SCI_COLOR_PAIR -/* from ScintillaTerm.cxx */ -#define SCI_COLOR_PAIR(f, b) \ - ((b) * 8 + (f) + 1) -#endif - -#define SCI_COLOR_ATTR(f, b) \ - COLOR_PAIR(SCI_COLOR_PAIR(f, b)) - -InterfaceNCurses::InterfaceNCurses() -{ - init_screen(); - raw(); - cbreak(); - noecho(); - curs_set(0); /* Scintilla draws its own cursor */ - - setlocale(LC_CTYPE, ""); /* for displaying UTF-8 characters properly */ - - info_window = newwin(1, 0, 0, 0); - info_current = g_strdup(PACKAGE_NAME); - - /* NOTE: Scintilla initializes color pairs */ - sci = scintilla_new(scintilla_notify); - sci_window = scintilla_get_window(sci); - wresize(sci_window, LINES - 3, COLS); - mvwin(sci_window, 1, 0); - - msg_window = newwin(1, 0, LINES - 2, 0); - - cmdline_window = newwin(0, 0, LINES - 1, 0); - keypad(cmdline_window, TRUE); - cmdline_current = NULL; - - ssm(SCI_SETFOCUS, TRUE); - - draw_info(); - /* scintilla will be refreshed in event loop */ - msg_clear(); - cmdline_update(""); - -#ifndef PDCURSES_WIN32A - /* workaround: endwin() is somewhat broken in the win32a port */ - endwin(); -#endif -} - -#ifdef __PDCURSES__ - -void -InterfaceNCurses::init_screen(void) -{ -#ifdef PDCURSES_WIN32A - /* enables window resizing on Win32a port */ - PDC_set_resize_limits(25, 0xFFFF, 80, 0xFFFF); -#endif - - initscr(); - - screen_tty = NULL; - screen = NULL; -} - -#else - -void -InterfaceNCurses::init_screen(void) -{ - /* - * Prevent the initial redraw and any escape sequences that may - * interfere with stdout, so we may use the terminal in - * cooked mode, for commandline help and batch processing. - * Scintilla must be initialized for batch processing to work. - * (Frankly I have no idea why this works!) - */ - screen_tty = g_fopen("/dev/tty", "r+b"); - screen = newterm(NULL, screen_tty, screen_tty); - set_term(screen); -} - -#endif /* !__PDCURSES__ */ - -void -InterfaceNCurses::resize_all_windows(void) -{ - int lines, cols; /* screen dimensions */ - - getmaxyx(stdscr, lines, cols); - - wresize(info_window, 1, cols); - wresize(sci_window, lines - 3, cols); - wresize(msg_window, 1, cols); - mvwin(msg_window, lines - 2, 0); - wresize(cmdline_window, 1, cols); - mvwin(cmdline_window, lines - 1, 0); - - draw_info(); - /* scintilla will be refreshed in event loop */ - msg_clear(); /* FIXME: use saved message */ - cmdline_update(); -} - -void -InterfaceNCurses::vmsg(MessageType type, const gchar *fmt, va_list ap) -{ - static const chtype type2attr[] = { - SCI_COLOR_ATTR(COLOR_BLACK, COLOR_WHITE), /* MSG_USER */ - SCI_COLOR_ATTR(COLOR_BLACK, COLOR_GREEN), /* MSG_INFO */ - SCI_COLOR_ATTR(COLOR_BLACK, COLOR_YELLOW), /* MSG_WARNING */ - SCI_COLOR_ATTR(COLOR_BLACK, COLOR_RED) /* MSG_ERROR */ - }; - -#ifdef PDCURSES_WIN32A - stdio_vmsg(type, fmt, ap); - if (isendwin()) /* batch mode */ - return; -#else - if (isendwin()) { /* batch mode */ - stdio_vmsg(type, fmt, ap); - return; - } -#endif - - wmove(msg_window, 0, 0); - wbkgdset(msg_window, ' ' | type2attr[type]); - vw_printw(msg_window, fmt, ap); - wclrtoeol(msg_window); - - wrefresh(msg_window); -} - -void -InterfaceNCurses::msg_clear(void) -{ - if (isendwin()) /* batch mode */ - return; - - wmove(msg_window, 0, 0); - wbkgdset(msg_window, ' ' | SCI_COLOR_ATTR(COLOR_BLACK, COLOR_WHITE)); - wclrtoeol(msg_window); - - wrefresh(msg_window); -} - -void -InterfaceNCurses::draw_info(void) -{ - if (isendwin()) /* batch mode */ - return; - - wmove(info_window, 0, 0); - wbkgdset(info_window, ' ' | SCI_COLOR_ATTR(COLOR_BLACK, COLOR_WHITE)); - waddstr(info_window, info_current); - wclrtoeol(info_window); - - wrefresh(info_window); -} - -void -InterfaceNCurses::info_update(QRegister *reg) -{ - g_free(info_current); - info_current = g_strdup_printf("%s - %s", PACKAGE_NAME, - reg->name); - - draw_info(); -} - -void -InterfaceNCurses::info_update(Buffer *buffer) -{ - g_free(info_current); - info_current = g_strdup_printf("%s - %s%s", PACKAGE_NAME, - buffer->filename ? : UNNAMED_FILE, - buffer->dirty ? "*" : ""); - - draw_info(); -} - -void -InterfaceNCurses::cmdline_update(const gchar *cmdline) -{ - size_t len; - int half_line = (getmaxx(stdscr) - 2) / 2; - const gchar *line; - - if (cmdline) { - g_free(cmdline_current); - cmdline_current = g_strdup(cmdline); - } else { - cmdline = cmdline_current; - } - len = strlen(cmdline); - - /* FIXME: optimize */ - line = cmdline + len - MIN(len, half_line + len % half_line); - - mvwaddch(cmdline_window, 0, 0, '*'); - waddstr(cmdline_window, line); - waddch(cmdline_window, ' ' | A_REVERSE); - wclrtoeol(cmdline_window); - - wrefresh(cmdline_window); -} - -void -InterfaceNCurses::popup_add(PopupEntryType type __attribute__((unused)), - const gchar *name, bool highlight) -{ - gchar *entry; - - if (isendwin()) /* batch mode */ - return; - - entry = g_strconcat(highlight ? "*" : " ", name, NULL); - - popup.longest = MAX(popup.longest, (gint)strlen(name)); - popup.length++; - - popup.list = g_slist_prepend(popup.list, entry); -} - -void -InterfaceNCurses::popup_show(void) -{ - int lines, cols; /* screen dimensions */ - int popup_lines; - gint popup_cols; - gint cur_file, cur_line; - - if (isendwin()) /* batch mode */ - goto cleanup; - - getmaxyx(stdscr, lines, cols); - - popup.longest += 3; - popup.list = g_slist_reverse(popup.list); - - /* popup_cols = floor(cols / popup.longest) */ - popup_cols = MAX(cols / popup.longest, 1); - /* popup_lines = ceil(popup.length / popup_cols) */ - popup_lines = popup.length / popup_cols; - if ((popup.length % popup_cols)) - popup_lines++; - popup_lines = MIN(popup_lines, lines - 1); - - /* window covers message, scintilla and info windows */ - popup.window = newwin(popup_lines, 0, lines - 1 - popup_lines, 0); - wbkgdset(popup.window, ' ' | SCI_COLOR_ATTR(COLOR_BLACK, COLOR_BLUE)); - - cur_file = 0; - cur_line = 1; - for (GSList *cur = popup.list; cur; cur = g_slist_next(cur)) { - gchar *entry = (gchar *)cur->data; - - if (cur_file && !(cur_file % popup_cols)) { - wclrtoeol(popup.window); - waddch(popup.window, '\n'); - cur_line++; - } - - cur_file++; - - if (cur_line == popup_lines && !(cur_file % popup_cols) && - cur_file < popup.length) { - (void)wattrset(popup.window, A_BOLD); - waddstr(popup.window, "..."); - break; - } - - (void)wattrset(popup.window, *entry == '*' ? A_BOLD : A_NORMAL); - waddstr(popup.window, entry + 1); - for (int i = popup.longest - strlen(entry) + 1; i; i--) - waddch(popup.window, ' '); - - g_free(cur->data); - } - wclrtoeol(popup.window); - -cleanup: - g_slist_free(popup.list); - popup.list = NULL; - popup.longest = popup.length = 0; -} - -void -InterfaceNCurses::popup_clear(void) -{ - if (!popup.window) - return; - - redrawwin(info_window); - wrefresh(info_window); - redrawwin(sci_window); - scintilla_refresh(sci); - redrawwin(msg_window); - wrefresh(msg_window); - - delwin(popup.window); - popup.window = NULL; -} - -void -InterfaceNCurses::event_loop(void) -{ - /* in commandline (visual) mode, enforce redraw */ - wrefresh(curscr); - draw_info(); - - for (;;) { - int key; - - /* also handles initial refresh (styles are configured...) */ - scintilla_refresh(sci); - if (popup.window) - wrefresh(popup.window); - - key = wgetch(cmdline_window); - switch (key) { -#ifdef KEY_RESIZE - case ERR: - case KEY_RESIZE: -#ifdef PDCURSES - resize_term(0, 0); -#endif - resize_all_windows(); - break; -#endif - case ESCAPE_SURROGATE: - cmdline_keypress('\x1B'); - break; - case KEY_BACKSPACE: - cmdline_keypress('\b'); - break; - case KEY_ENTER: - case '\r': - switch (ssm(SCI_GETEOLMODE)) { - case SC_EOL_CR: - cmdline_keypress('\r'); - break; - case SC_EOL_CRLF: - cmdline_keypress('\r'); - /* fall through */ - case SC_EOL_LF: - default: - cmdline_keypress('\n'); - } - break; - default: - if (key <= 0xFF) - cmdline_keypress((gchar)key); - } - } -} - -InterfaceNCurses::Popup::~Popup() -{ - if (window) - delwin(window); - if (list) - g_slist_free(list); -} - -InterfaceNCurses::~InterfaceNCurses() -{ - delwin(info_window); - g_free(info_current); - /* also deletes curses window */ - scintilla_delete(sci); - delwin(cmdline_window); - g_free(cmdline_current); - delwin(msg_window); - - if (!isendwin()) - endwin(); - - delscreen(screen); - if (screen_tty) - fclose(screen_tty); -} - -/* - * Callbacks - */ - -static void -scintilla_notify(Scintilla *sci, int idFrom, void *notify, void *user_data) -{ - interface.process_notify((SCNotification *)notify); -} diff --git a/interface-ncurses.h b/interface-ncurses.h deleted file mode 100644 index 97269a4..0000000 --- a/interface-ncurses.h +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef __INTERFACE_NCURSES_H -#define __INTERFACE_NCURSES_H - -#include - -#include - -#include - -#include -#include - -#include "interface.h" - -extern class InterfaceNCurses : public Interface { - SCREEN *screen; - FILE *screen_tty; - - Scintilla *sci; - - WINDOW *info_window; - gchar *info_current; - WINDOW *sci_window; - WINDOW *msg_window; - WINDOW *cmdline_window; - gchar *cmdline_current; - - struct Popup { - WINDOW *window; - GSList *list; - gint longest; - gint length; - - Popup() : window(NULL), list(NULL), longest(0), length(0) {} - ~Popup(); - } popup; - - void init_screen(void); - void resize_all_windows(void); - void draw_info(void); - -public: - InterfaceNCurses(); - ~InterfaceNCurses(); - - void vmsg(MessageType type, const gchar *fmt, va_list ap); - void msg_clear(void); - - inline sptr_t - ssm(unsigned int iMessage, uptr_t wParam = 0, sptr_t lParam = 0) - { - return scintilla_send_message(sci, iMessage, wParam, lParam); - } - - void info_update(QRegister *reg); - void info_update(Buffer *buffer); - - void cmdline_update(const gchar *cmdline = NULL); - - void popup_add(PopupEntryType type, - const gchar *name, bool highlight = false); - void popup_show(void); - void popup_clear(void); - - /* main entry point */ - void event_loop(void); -} interface; - -#endif diff --git a/interface.h b/interface.h deleted file mode 100644 index 35ee1f7..0000000 --- a/interface.h +++ /dev/null @@ -1,112 +0,0 @@ -#ifndef __INTERFACE_H -#define __INTERFACE_H - -#include - -#include - -#include - -#include "undo.h" - -/* avoid include dependency conflict */ -class QRegister; -class Buffer; - -/* - * Base class for all user interfaces - used mereley as a class interface. - * The actual instance of the interface has the platform-specific type - * (e.g. InterfaceGtk) since we would like to have the benefits of using - * classes but avoid the calling overhead when invoking virtual methods - * on Interface pointers. - * There's only one Interface* instance in the system. - */ -class Interface { - template - class UndoTokenInfoUpdate : public UndoToken { - Interface *iface; - Type *obj; - - public: - UndoTokenInfoUpdate(Interface *_iface, Type *_obj) - : iface(_iface), obj(_obj) {} - - void - run(void) - { - iface->info_update(obj); - } - }; - -public: - virtual GOptionGroup * - get_options(void) - { - return NULL; - } - virtual void parse_args(int &argc, char **&argv) {} - - enum MessageType { - MSG_USER, - MSG_INFO, - MSG_WARNING, - MSG_ERROR - }; - virtual void vmsg(MessageType type, const gchar *fmt, va_list ap) = 0; - inline void - msg(MessageType type, const gchar *fmt, ...) G_GNUC_PRINTF(3, 4) - { - va_list ap; - - va_start(ap, fmt); - vmsg(type, fmt, ap); - va_end(ap); - } - virtual void msg_clear(void) {} - - virtual sptr_t ssm(unsigned int iMessage, - uptr_t wParam = 0, sptr_t lParam = 0) = 0; - - virtual void info_update(QRegister *reg) = 0; - virtual void info_update(Buffer *buffer) = 0; - - template - inline void - undo_info_update(Type *obj) - { - undo.push(new UndoTokenInfoUpdate(this, obj)); - } - - /* NULL means to redraw the current cmdline if necessary */ - virtual void cmdline_update(const gchar *cmdline = NULL) = 0; - - enum PopupEntryType { - POPUP_PLAIN, - POPUP_FILE, - POPUP_DIRECTORY - }; - virtual void popup_add(PopupEntryType type, - const gchar *name, bool highlight = false) = 0; - virtual void popup_show(void) = 0; - virtual void popup_clear(void) = 0; - - /* main entry point */ - virtual void event_loop(void) = 0; - - /* - * Interfacing to the external SciTECO world - * See main.cpp - */ -protected: - void stdio_vmsg(MessageType type, const gchar *fmt, va_list ap); -public: - void process_notify(SCNotification *notify); -}; - -#ifdef INTERFACE_GTK -#include "interface-gtk.h" -#elif defined(INTERFACE_NCURSES) -#include "interface-ncurses.h" -#endif - -#endif diff --git a/main.cpp b/main.cpp deleted file mode 100644 index 4526da3..0000000 --- a/main.cpp +++ /dev/null @@ -1,208 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include "sciteco.h" -#include "interface.h" -#include "parser.h" -#include "goto.h" -#include "qregisters.h" -#include "ring.h" -#include "undo.h" - -#ifdef G_OS_UNIX -#define INI_FILE ".teco_ini" -#else -#define INI_FILE "teco.ini" -#endif - -namespace Flags { - gint64 ed = 0; -} - -static gchar *mung_file = NULL; - -void -Interface::stdio_vmsg(MessageType type, const gchar *fmt, va_list ap) -{ - gchar buf[255]; - - g_vsnprintf(buf, sizeof(buf), fmt, ap); - - switch (type) { - case MSG_USER: - g_printf("%s\n", buf); - break; - case MSG_INFO: - g_printf("Info: %s\n", buf); - break; - case MSG_WARNING: - g_fprintf(stderr, "Warning: %s\n", buf); - break; - case MSG_ERROR: - g_fprintf(stderr, "Error: %s\n", buf); - break; - } -} - -void -Interface::process_notify(SCNotification *notify) -{ -#ifdef DEBUG - g_printf("SCINTILLA NOTIFY: code=%d\n", notify->nmhdr.code); -#endif -} - -#ifdef G_OS_WIN32 - -/* - * keep program self-contained under Windows - * (look for profile in current directory) - */ -static inline gchar * -get_teco_ini(void) -{ - return g_strdup(INI_FILE); -} - -/* - * Windows sometimes sets the current working directory to very obscure - * paths when opening files in the Explorer, but we have to read the - * teco.ini from the directory where our binary resides. - */ -static inline void -fix_cwd(const gchar *program) -{ - gchar *bin_dir = g_path_get_dirname(program); - g_chdir(bin_dir); - g_free(bin_dir); -} - -#else - -static inline gchar * -get_teco_ini(void) -{ - const gchar *home; - -#ifdef G_OS_UNIX - home = g_get_home_dir(); -#else - home = g_get_user_config_dir(); -#endif - return g_build_filename(home, INI_FILE, NULL); -} - -static inline void fix_cwd(const gchar *program __attribute__((unused))) {} - -#endif /* !G_OS_WIN32 */ - -static inline void -process_options(int &argc, char **&argv) -{ - static const GOptionEntry option_entries[] = { - {"mung", 'm', 0, G_OPTION_ARG_FILENAME, &mung_file, - "Mung file instead of " INI_FILE, "filename"}, - {NULL} - }; - - GOptionContext *options; - GOptionGroup *interface_group = interface.get_options(); - - options = g_option_context_new("- " PACKAGE_STRING); - - g_option_context_add_main_entries(options, option_entries, NULL); - if (interface_group) - g_option_context_add_group(options, interface_group); - - if (!g_option_context_parse(options, &argc, &argv, NULL)) { - g_printf("Option parsing failed!\n"); - exit(EXIT_FAILURE); - } - - g_option_context_free(options); - - if (mung_file) { - if (!g_file_test(mung_file, G_FILE_TEST_IS_REGULAR)) { - g_printf("Cannot mung \"%s\". File does not exist!\n", - mung_file); - exit(EXIT_FAILURE); - } - } else { - mung_file = get_teco_ini(); - } - - interface.parse_args(argc, argv); - - /* remaining arguments, are arguments to the munged file */ -} - -int -main(int argc, char **argv) -{ - static GotoTable cmdline_goto_table; - static QRegisterTable local_qregs; - - fix_cwd(argv[0]); - - process_options(argc, argv); - - interface.ssm(SCI_SETCARETSTYLE, CARETSTYLE_BLOCK); - interface.ssm(SCI_SETCARETFORE, 0xFFFFFF); - - /* - * FIXME: Default styles should probably be set interface-based - * (system defaults) and be changeable by TECO macros - */ - interface.ssm(SCI_STYLESETFORE, STYLE_DEFAULT, 0xFFFFFF); - interface.ssm(SCI_STYLESETBACK, STYLE_DEFAULT, 0x000000); - interface.ssm(SCI_STYLESETFONT, STYLE_DEFAULT, (sptr_t)"Courier"); - interface.ssm(SCI_STYLECLEARALL); - - QRegisters::globals.initialize(); - /* search string and status register */ - QRegisters::globals.initialize("_"); - /* replacement string register */ - QRegisters::globals.initialize("-"); - /* current buffer name and number ("*") */ - QRegisters::globals.insert(new QRegisterBufferInfo()); - - local_qregs.initialize(); - QRegisters::locals = &local_qregs; - - ring.edit((const gchar *)NULL); - - /* add remaining arguments to unnamed buffer */ - for (int i = 1; i < argc; i++) { - interface.ssm(SCI_APPENDTEXT, strlen(argv[i]), (sptr_t)argv[i]); - interface.ssm(SCI_APPENDTEXT, 1, (sptr_t)"\n"); - } - - if (g_file_test(mung_file, G_FILE_TEST_IS_REGULAR)) { - if (!Execute::file(mung_file, false)) - exit(EXIT_FAILURE); - - /* FIXME: make quit immediate in batch/macro mode (non-UNDO)? */ - if (quit_requested) { - /* FIXME */ - exit(EXIT_SUCCESS); - } - } - g_free(mung_file); - - Goto::table = &cmdline_goto_table; - interface.ssm(SCI_EMPTYUNDOBUFFER); - undo.enabled = true; - - interface.event_loop(); - - return 0; -} diff --git a/parser.cpp b/parser.cpp deleted file mode 100644 index 5ba96fa..0000000 --- a/parser.cpp +++ /dev/null @@ -1,1436 +0,0 @@ -#include -#include - -#include -#include -#include - -#include "sciteco.h" -#include "interface.h" -#include "undo.h" -#include "expressions.h" -#include "goto.h" -#include "qregisters.h" -#include "ring.h" -#include "parser.h" -#include "symbols.h" -#include "search.h" - -//#define DEBUG - -gint macro_pc = 0; - -namespace States { - StateStart start; - StateControl control; - StateFCommand fcommand; - StateCondCommand condcommand; - StateECommand ecommand; - StateScintilla_symbols scintilla_symbols; - StateScintilla_lParam scintilla_lparam; - StateInsert insert; - - State *current = &start; -} - -namespace Modifiers { - static bool colon = false; - static bool at = false; -} - -enum Mode mode = MODE_NORMAL; - -/* FIXME: perhaps integrate into Mode */ -static bool skip_else = false; - -static gint nest_level = 0; - -gchar *strings[2] = {NULL, NULL}; -gchar escape_char = '\x1B'; - -void -Execute::step(const gchar *macro) throw (State::Error) -{ - while (macro[macro_pc]) { -#ifdef DEBUG - g_printf("EXEC(%d): input='%c'/%x, state=%p, mode=%d\n", - macro_pc, macro[macro_pc], macro[macro_pc], - States::current, mode); -#endif - - State::input(macro[macro_pc]); - macro_pc++; - } -} - -void -Execute::macro(const gchar *macro, bool locals) throw (State::Error) -{ - GotoTable *parent_goto_table = Goto::table; - GotoTable macro_goto_table(false); - - QRegisterTable *parent_locals = QRegisters::locals; - QRegisterTable macro_locals(false); - - State *parent_state = States::current; - gint parent_pc = macro_pc; - - /* - * need this to fixup state on rubout: state machine emits undo token - * resetting state to parent's one, but the macro executed also emitted - * undo tokens resetting the state to StateStart - */ - undo.push_var(States::current) = &States::start; - macro_pc = 0; - - Goto::table = ¯o_goto_table; - if (locals) { - macro_locals.initialize(); - QRegisters::locals = ¯o_locals; - } - - try { - step(macro); - if (Goto::skip_label) - throw State::Error("Label \"%s\" not found", - Goto::skip_label); - } catch (...) { - g_free(Goto::skip_label); - Goto::skip_label = NULL; - - QRegisters::locals = parent_locals; - Goto::table = parent_goto_table; - - macro_pc = parent_pc; - States::current = parent_state; - - throw; /* forward */ - } - - QRegisters::locals = parent_locals; - Goto::table = parent_goto_table; - - macro_pc = parent_pc; - States::current = parent_state; -} - -bool -Execute::file(const gchar *filename, bool locals) -{ - gchar *macro_str, *p = NULL; - - if (!g_file_get_contents(filename, ¯o_str, NULL, NULL)) - return false; - /* only when executing files, ignore Hash-Bang line */ - if (*macro_str == '#') - p = MAX(strchr(macro_str, '\r'), strchr(macro_str, '\n')); - - try { - macro(p ? p+1 : macro_str, locals); - } catch (...) { - g_free(macro_str); - return false; - } - - g_free(macro_str); - return true; -} - -State::Error::Error(const gchar *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - interface.vmsg(Interface::MSG_ERROR, fmt, ap); - va_end(ap); -} - -State::State() -{ - for (guint i = 0; i < G_N_ELEMENTS(transitions); i++) - transitions[i] = NULL; -} - -bool -State::eval_colon(void) -{ - if (!Modifiers::colon) - return false; - - undo.push_var(Modifiers::colon); - Modifiers::colon = false; - return true; -} - -void -State::input(gchar chr) throw (Error) -{ - State *state = States::current; - - for (;;) { - State *next = state->get_next_state(chr); - - if (next == state) - break; - - state = next; - chr = '\0'; - } - - if (state != States::current) { - undo.push_var(States::current); - States::current = state; - } -} - -State * -State::get_next_state(gchar chr) throw (Error) -{ - State *next = NULL; - guint upper = g_ascii_toupper(chr); - - if (upper < G_N_ELEMENTS(transitions)) - next = transitions[upper]; - if (!next) - next = custom(chr); - if (!next) - throw SyntaxError(chr); - - return next; -} - -gchar * -StateExpectString::machine_input(gchar chr) throw (Error) -{ - switch (machine.mode) { - case Machine::MODE_UPPER: - chr = g_ascii_toupper(chr); - break; - case Machine::MODE_LOWER: - chr = g_ascii_tolower(chr); - break; - default: - break; - } - - if (machine.toctl) { - chr = CTL_KEY(g_ascii_toupper(chr)); - machine.toctl = false; - } - - if (machine.state == Machine::STATE_ESCAPED) { - machine.state = Machine::STATE_START; - goto original; - } - - if (chr == '^') { - machine.toctl = true; - return NULL; - } - - switch (machine.state) { - case Machine::STATE_START: - switch (chr) { - case CTL_KEY('Q'): - case CTL_KEY('R'): machine.state = Machine::STATE_ESCAPED; break; - case CTL_KEY('V'): machine.state = Machine::STATE_LOWER; break; - case CTL_KEY('W'): machine.state = Machine::STATE_UPPER; break; - case CTL_KEY('E'): machine.state = Machine::STATE_CTL_E; break; - default: - goto original; - } - break; - - case Machine::STATE_LOWER: - machine.state = Machine::STATE_START; - if (chr != CTL_KEY('V')) - return g_strdup((gchar []){g_ascii_tolower(chr), '\0'}); - machine.mode = Machine::MODE_LOWER; - break; - - case Machine::STATE_UPPER: - machine.state = Machine::STATE_START; - if (chr != CTL_KEY('W')) - return g_strdup((gchar []){g_ascii_toupper(chr), '\0'}); - machine.mode = Machine::MODE_UPPER; - break; - - case Machine::STATE_CTL_E: - switch (g_ascii_toupper(chr)) { - case 'Q': machine.state = Machine::STATE_CTL_EQ; break; - case 'U': machine.state = Machine::STATE_CTL_EU; break; - default: - machine.state = Machine::STATE_START; - return g_strdup((gchar []){CTL_KEY('E'), chr, '\0'}); - } - break; - - case Machine::STATE_CTL_EU: - if (chr == '.') { - machine.state = Machine::STATE_CTL_EU_LOCAL; - } else { - QRegister *reg; - - machine.state = Machine::STATE_START; - reg = QRegisters::globals[g_ascii_toupper(chr)]; - if (!reg) - throw InvalidQRegError(chr); - return g_strdup(CHR2STR(reg->get_integer())); - } - break; - - case Machine::STATE_CTL_EU_LOCAL: { - QRegister *reg; - - machine.state = Machine::STATE_START; - reg = (*QRegisters::locals)[g_ascii_toupper(chr)]; - if (!reg) - throw InvalidQRegError(chr, true); - return g_strdup(CHR2STR(reg->get_integer())); - } - - case Machine::STATE_CTL_EQ: - if (chr == '.') { - machine.state = Machine::STATE_CTL_EQ_LOCAL; - } else { - QRegister *reg; - - machine.state = Machine::STATE_START; - reg = QRegisters::globals[g_ascii_toupper(chr)]; - if (!reg) - throw InvalidQRegError(chr); - return reg->get_string(); - } - break; - - case Machine::STATE_CTL_EQ_LOCAL: { - QRegister *reg; - - machine.state = Machine::STATE_START; - reg = (*QRegisters::locals)[g_ascii_toupper(chr)]; - if (!reg) - throw InvalidQRegError(chr, true); - return reg->get_string(); - } - - default: - g_assert(TRUE); - } - - return NULL; - -original: - return g_strdup((gchar []){chr, '\0'}); -} - -State * -StateExpectString::custom(gchar chr) throw (Error) -{ - gchar *insert; - - if (chr == '\0') { - BEGIN_EXEC(this); - initial(); - return this; - } - - /* - * String termination handling - */ - if (Modifiers::at) { - if (last) - undo.push_var(Modifiers::at) = false; - - switch (escape_char) { - case '\x1B': - case '{': - undo.push_var(escape_char) = g_ascii_toupper(chr); - return this; - } - } - - if (escape_char == '{') { - switch (chr) { - case '{': - undo.push_var(nesting); - nesting++; - break; - case '}': - undo.push_var(nesting); - nesting--; - break; - } - } else if (g_ascii_toupper(chr) == escape_char) { - undo.push_var(nesting); - nesting--; - } - - if (!nesting) { - State *next; - gchar *string = strings[0]; - - undo.push_str(strings[0]) = NULL; - if (last) - undo.push_var(escape_char) = '\x1B'; - nesting = 1; - - if (string_building) { - undo.push_var(machine); - machine.state = Machine::STATE_START; - machine.mode = Machine::MODE_NORMAL; - machine.toctl = false; - } - - next = done(string ? : ""); - g_free(string); - return next; - } - - BEGIN_EXEC(this); - - /* - * String building characters - */ - if (string_building) { - undo.push_var(machine); - insert = machine_input(chr); - if (!insert) - return this; - } else { - insert = g_strdup((gchar []){chr, '\0'}); - } - - /* - * String accumulation - */ - undo.push_str(strings[0]); - String::append(strings[0], insert); - - process(strings[0], strlen(insert)); - g_free(insert); - return this; -} - -StateExpectQReg::StateExpectQReg() : State(), got_local(false) -{ - transitions['\0'] = this; -} - -State * -StateExpectQReg::custom(gchar chr) throw (Error) -{ - QRegister *reg; - - if (chr == '.') { - undo.push_var(got_local) = true; - return this; - } - chr = g_ascii_toupper(chr); - - if (got_local) { - undo.push_var(got_local) = false; - reg = (*QRegisters::locals)[chr]; - } else { - reg = QRegisters::globals[chr]; - } - if (!reg) - throw InvalidQRegError(chr, got_local); - - return got_register(reg); -} - -StateStart::StateStart() : State() -{ - transitions['\0'] = this; - init(" \f\r\n\v"); - - transitions['!'] = &States::label; - transitions['O'] = &States::gotocmd; - transitions['^'] = &States::control; - transitions['F'] = &States::fcommand; - transitions['"'] = &States::condcommand; - transitions['E'] = &States::ecommand; - transitions['I'] = &States::insert; - transitions['S'] = &States::search; - transitions['N'] = &States::searchall; - - transitions['['] = &States::pushqreg; - transitions[']'] = &States::popqreg; - transitions['G'] = &States::getqregstring; - transitions['Q'] = &States::getqreginteger; - transitions['U'] = &States::setqreginteger; - transitions['%'] = &States::increaseqreg; - transitions['M'] = &States::macro; - transitions['X'] = &States::copytoqreg; -} - -void -StateStart::insert_integer(gint64 v) -{ - gchar buf[64+1]; /* maximum length if radix = 2 */ - gchar *p = buf + sizeof(buf); - - *--p = '\0'; - interface.ssm(SCI_BEGINUNDOACTION); - if (v < 0) { - interface.ssm(SCI_ADDTEXT, 1, (sptr_t)"-"); - v *= -1; - } - do { - *--p = '0' + (v % expressions.radix); - if (*p > '9') - *p += 'A' - '9'; - } while ((v /= expressions.radix)); - interface.ssm(SCI_ADDTEXT, buf + sizeof(buf) - p - 1, - (sptr_t)p); - interface.ssm(SCI_SCROLLCARET); - interface.ssm(SCI_ENDUNDOACTION); - ring.dirtify(); - - undo.push_msg(SCI_UNDO); -} - -gint64 -StateStart::read_integer(void) -{ - uptr_t pos = interface.ssm(SCI_GETCURRENTPOS); - gchar c = (gchar)interface.ssm(SCI_GETCHARAT, pos); - gint64 v = 0; - gint sign = 1; - - if (c == '-') { - pos++; - sign = -1; - } - - for (;;) { - c = g_ascii_toupper((gchar)interface.ssm(SCI_GETCHARAT, pos)); - if (c >= '0' && c <= '0' + MIN(expressions.radix, 10) - 1) - v = (v*expressions.radix) + (c - '0'); - else if (c >= 'A' && - c <= 'A' + MIN(expressions.radix - 10, 26) - 1) - v = (v*expressions.radix) + 10 + (c - 'A'); - else - break; - - pos++; - } - - return sign * v; -} - -tecoBool -StateStart::move_chars(gint64 n) -{ - sptr_t pos = interface.ssm(SCI_GETCURRENTPOS); - - if (!Validate::pos(pos + n)) - return FAILURE; - - interface.ssm(SCI_GOTOPOS, pos + n); - undo.push_msg(SCI_GOTOPOS, pos); - return SUCCESS; -} - -tecoBool -StateStart::move_lines(gint64 n) -{ - sptr_t pos = interface.ssm(SCI_GETCURRENTPOS); - sptr_t line = interface.ssm(SCI_LINEFROMPOSITION, pos) + n; - - if (!Validate::line(line)) - return FAILURE; - - interface.ssm(SCI_GOTOLINE, line); - undo.push_msg(SCI_GOTOPOS, pos); - return SUCCESS; -} - -tecoBool -StateStart::delete_words(gint64 n) -{ - sptr_t pos, size; - - if (!n) - return SUCCESS; - - pos = interface.ssm(SCI_GETCURRENTPOS); - size = interface.ssm(SCI_GETLENGTH); - interface.ssm(SCI_BEGINUNDOACTION); - /* - * FIXME: would be nice to do this with constant amount of - * editor messages. E.g. by using custom algorithm accessing - * the internal document buffer. - */ - if (n > 0) { - while (n--) { - sptr_t size = interface.ssm(SCI_GETLENGTH); - interface.ssm(SCI_DELWORDRIGHTEND); - if (size == interface.ssm(SCI_GETLENGTH)) - break; - } - } else { - n *= -1; - while (n--) { - sptr_t pos = interface.ssm(SCI_GETCURRENTPOS); - //interface.ssm(SCI_DELWORDLEFTEND); - interface.ssm(SCI_WORDLEFTEND); - if (pos == interface.ssm(SCI_GETCURRENTPOS)) - break; - interface.ssm(SCI_DELWORDRIGHTEND); - } - } - interface.ssm(SCI_ENDUNDOACTION); - - if (n >= 0) { - if (size != interface.ssm(SCI_GETLENGTH)) { - interface.ssm(SCI_UNDO); - interface.ssm(SCI_GOTOPOS, pos); - } - return FAILURE; - } - - undo.push_msg(SCI_GOTOPOS, pos); - undo.push_msg(SCI_UNDO); - ring.dirtify(); - - return SUCCESS; -} - -State * -StateStart::custom(gchar chr) throw (Error) -{ - gint64 v; - tecoBool rc; - - /* - * commands implemented in StateCtrlCmd - */ - if (IS_CTL(chr)) - return States::control.get_next_state(CTL_ECHO(chr)); - - /* - * arithmetics - */ - if (g_ascii_isdigit(chr)) { - BEGIN_EXEC(this); - expressions.add_digit(chr); - return this; - } - - chr = g_ascii_toupper(chr); - switch (chr) { - case '/': - BEGIN_EXEC(this); - expressions.push_calc(Expressions::OP_DIV); - break; - - case '*': - BEGIN_EXEC(this); - expressions.push_calc(Expressions::OP_MUL); - break; - - case '+': - BEGIN_EXEC(this); - expressions.push_calc(Expressions::OP_ADD); - break; - - case '-': - BEGIN_EXEC(this); - if (!expressions.args()) - expressions.set_num_sign(-expressions.num_sign); - else - expressions.push_calc(Expressions::OP_SUB); - break; - - case '&': - BEGIN_EXEC(this); - expressions.push_calc(Expressions::OP_AND); - break; - - case '#': - BEGIN_EXEC(this); - expressions.push_calc(Expressions::OP_OR); - break; - - case '(': - BEGIN_EXEC(this); - if (expressions.num_sign < 0) { - expressions.set_num_sign(1); - expressions.eval(); - expressions.push(-1); - expressions.push_calc(Expressions::OP_MUL); - } - expressions.push(Expressions::OP_BRACE); - break; - - case ')': - BEGIN_EXEC(this); - expressions.eval(true); - break; - - case ',': - BEGIN_EXEC(this); - expressions.eval(); - expressions.push(Expressions::OP_NEW); - break; - - case '.': - BEGIN_EXEC(this); - expressions.eval(); - expressions.push(interface.ssm(SCI_GETCURRENTPOS)); - break; - - case 'Z': - BEGIN_EXEC(this); - expressions.eval(); - expressions.push(interface.ssm(SCI_GETLENGTH)); - break; - - case 'H': - BEGIN_EXEC(this); - expressions.eval(); - expressions.push(0); - expressions.push(interface.ssm(SCI_GETLENGTH)); - break; - - case '\\': - BEGIN_EXEC(this); - expressions.eval(); - if (expressions.args()) - insert_integer(expressions.pop_num_calc()); - else - expressions.push(read_integer()); - break; - - /* - * control structures (loops) - */ - case '<': - if (mode == MODE_PARSE_ONLY_LOOP) { - undo.push_var(nest_level); - nest_level++; - return this; - } - BEGIN_EXEC(this); - - expressions.eval(); - if (!expressions.args()) - /* infinite loop */ - expressions.push(-1); - - if (!expressions.peek_num()) { - expressions.pop_num(); - - /* skip to end of loop */ - undo.push_var(mode); - mode = MODE_PARSE_ONLY_LOOP; - } else { - expressions.push(macro_pc); - expressions.push(Expressions::OP_LOOP); - } - break; - - case '>': - if (mode == MODE_PARSE_ONLY_LOOP) { - if (!nest_level) { - undo.push_var(mode); - mode = MODE_NORMAL; - } else { - undo.push_var(nest_level); - nest_level--; - } - } else { - BEGIN_EXEC(this); - gint64 loop_pc, loop_cnt; - - expressions.discard_args(); - g_assert(expressions.pop_op() == Expressions::OP_LOOP); - loop_pc = expressions.pop_num(); - loop_cnt = expressions.pop_num(); - - if (loop_cnt != 1) { - /* repeat loop */ - macro_pc = loop_pc; - expressions.push(MAX(loop_cnt - 1, -1)); - expressions.push(loop_pc); - expressions.push(Expressions::OP_LOOP); - } - } - break; - - case ';': - BEGIN_EXEC(this); - - v = QRegisters::globals["_"]->get_integer(); - rc = expressions.pop_num_calc(1, v); - if (eval_colon()) - rc = ~rc; - - if (IS_FAILURE(rc)) { - expressions.discard_args(); - g_assert(expressions.pop_op() == Expressions::OP_LOOP); - expressions.pop_num(); /* pc */ - expressions.pop_num(); /* counter */ - - /* skip to end of loop */ - undo.push_var(mode); - mode = MODE_PARSE_ONLY_LOOP; - } - break; - - /* - * control structures (conditionals) - */ - case '|': - if (mode == MODE_PARSE_ONLY_COND) { - if (!skip_else && !nest_level) { - undo.push_var(mode); - mode = MODE_NORMAL; - } - return this; - } - BEGIN_EXEC(this); - - /* skip to end of conditional; skip ELSE-part */ - undo.push_var(mode); - mode = MODE_PARSE_ONLY_COND; - break; - - case '\'': - if (mode != MODE_PARSE_ONLY_COND) - break; - - if (!nest_level) { - undo.push_var(mode); - mode = MODE_NORMAL; - undo.push_var(skip_else); - skip_else = false; - } else { - undo.push_var(nest_level); - nest_level--; - } - break; - - /* - * modifiers - */ - case '@': - /* - * @ modifier has syntactic significance, so set it even - * in PARSE_ONLY* modes - */ - undo.push_var(Modifiers::at); - Modifiers::at = true; - break; - - case ':': - BEGIN_EXEC(this); - undo.push_var(Modifiers::colon); - Modifiers::colon = true; - break; - - /* - * commands - */ - case 'J': - BEGIN_EXEC(this); - v = expressions.pop_num_calc(1, 0); - if (Validate::pos(v)) { - undo.push_msg(SCI_GOTOPOS, - interface.ssm(SCI_GETCURRENTPOS)); - interface.ssm(SCI_GOTOPOS, v); - - if (eval_colon()) - expressions.push(SUCCESS); - } else if (eval_colon()) { - expressions.push(FAILURE); - } else { - throw MoveError("J"); - } - break; - - case 'C': - BEGIN_EXEC(this); - rc = move_chars(expressions.pop_num_calc()); - if (eval_colon()) - expressions.push(rc); - else if (IS_FAILURE(rc)) - throw MoveError("C"); - break; - - case 'R': - BEGIN_EXEC(this); - rc = move_chars(-expressions.pop_num_calc()); - if (eval_colon()) - expressions.push(rc); - else if (IS_FAILURE(rc)) - throw MoveError("R"); - break; - - case 'L': - BEGIN_EXEC(this); - rc = move_lines(expressions.pop_num_calc()); - if (eval_colon()) - expressions.push(rc); - else if (IS_FAILURE(rc)) - throw MoveError("L"); - break; - - case 'B': - BEGIN_EXEC(this); - rc = move_lines(-expressions.pop_num_calc()); - if (eval_colon()) - expressions.push(rc); - else if (IS_FAILURE(rc)) - throw MoveError("B"); - break; - - case 'W': { - sptr_t pos; - unsigned int msg = SCI_WORDRIGHTEND; - - BEGIN_EXEC(this); - v = expressions.pop_num_calc(); - - pos = interface.ssm(SCI_GETCURRENTPOS); - /* - * FIXME: would be nice to do this with constant amount of - * editor messages. E.g. by using custom algorithm accessing - * the internal document buffer. - */ - if (v < 0) { - v *= -1; - msg = SCI_WORDLEFTEND; - } - while (v--) { - sptr_t pos = interface.ssm(SCI_GETCURRENTPOS); - interface.ssm(msg); - if (pos == interface.ssm(SCI_GETCURRENTPOS)) - break; - } - if (v < 0) { - undo.push_msg(SCI_GOTOPOS, pos); - if (eval_colon()) - expressions.push(SUCCESS); - } else { - interface.ssm(SCI_GOTOPOS, pos); - if (eval_colon()) - expressions.push(FAILURE); - else - throw MoveError("W"); - } - break; - } - - case 'V': - BEGIN_EXEC(this); - rc = delete_words(expressions.pop_num_calc()); - if (eval_colon()) - expressions.push(rc); - else if (IS_FAILURE(rc)) - throw Error("Not enough words to delete with "); - break; - - case 'Y': - BEGIN_EXEC(this); - rc = delete_words(-expressions.pop_num_calc()); - if (eval_colon()) - expressions.push(rc); - else if (IS_FAILURE(rc)) - throw Error("Not enough words to delete with "); - break; - - case '=': - BEGIN_EXEC(this); - interface.msg(Interface::MSG_USER, "%" G_GINT64_FORMAT, - expressions.pop_num_calc()); - break; - - case 'K': - case 'D': { - gint64 from, len; - - BEGIN_EXEC(this); - expressions.eval(); - - if (expressions.args() <= 1) { - from = interface.ssm(SCI_GETCURRENTPOS); - if (chr == 'D') { - len = expressions.pop_num_calc(); - rc = TECO_BOOL(Validate::pos(from + len)); - } else /* chr == 'K' */ { - sptr_t line; - line = interface.ssm(SCI_LINEFROMPOSITION, from) + - expressions.pop_num_calc(); - len = interface.ssm(SCI_POSITIONFROMLINE, line) - - from; - rc = TECO_BOOL(Validate::line(line)); - } - if (len < 0) { - len *= -1; - from -= len; - } - } else { - gint64 to = expressions.pop_num(); - from = expressions.pop_num(); - len = to - from; - rc = TECO_BOOL(len >= 0 && Validate::pos(from) && - Validate::pos(to)); - } - - if (eval_colon()) - expressions.push(rc); - else if (IS_FAILURE(rc)) - throw RangeError(chr); - - if (len == 0 || IS_FAILURE(rc)) - break; - - undo.push_msg(SCI_GOTOPOS, interface.ssm(SCI_GETCURRENTPOS)); - undo.push_msg(SCI_UNDO); - - interface.ssm(SCI_BEGINUNDOACTION); - interface.ssm(SCI_DELETERANGE, from, len); - interface.ssm(SCI_ENDUNDOACTION); - ring.dirtify(); - break; - } - - case 'A': - BEGIN_EXEC(this); - v = interface.ssm(SCI_GETCURRENTPOS) + - expressions.pop_num_calc(); - if (!Validate::pos(v)) - throw RangeError("A"); - expressions.push(interface.ssm(SCI_GETCHARAT, v)); - break; - - default: - throw SyntaxError(chr); - } - - return this; -} - -StateFCommand::StateFCommand() : State() -{ - transitions['\0'] = this; - transitions['K'] = &States::searchkill; - transitions['D'] = &States::searchdelete; - transitions['S'] = &States::replace; - transitions['R'] = &States::replacedefault; -} - -State * -StateFCommand::custom(gchar chr) throw (Error) -{ - switch (chr) { - /* - * loop flow control - */ - case '<': - BEGIN_EXEC(&States::start); - /* FIXME: what if in brackets? */ - expressions.discard_args(); - if (expressions.peek_op() == Expressions::OP_LOOP) - /* repeat loop */ - macro_pc = expressions.peek_num(); - else - macro_pc = -1; - break; - - case '>': { - gint64 loop_pc, loop_cnt; - - BEGIN_EXEC(&States::start); - /* FIXME: what if in brackets? */ - expressions.discard_args(); - g_assert(expressions.pop_op() == Expressions::OP_LOOP); - loop_pc = expressions.pop_num(); - loop_cnt = expressions.pop_num(); - - if (loop_cnt != 1) { - /* repeat loop */ - macro_pc = loop_pc; - expressions.push(MAX(loop_cnt - 1, -1)); - expressions.push(loop_pc); - expressions.push(Expressions::OP_LOOP); - } else { - /* skip to end of loop */ - undo.push_var(mode); - mode = MODE_PARSE_ONLY_LOOP; - } - break; - } - - /* - * conditional flow control - */ - case '\'': - BEGIN_EXEC(&States::start); - /* skip to end of conditional */ - undo.push_var(mode); - mode = MODE_PARSE_ONLY_COND; - undo.push_var(skip_else); - skip_else = true; - break; - - case '|': - BEGIN_EXEC(&States::start); - /* skip to ELSE-part or end of conditional */ - undo.push_var(mode); - mode = MODE_PARSE_ONLY_COND; - break; - - default: - throw SyntaxError(chr); - } - - return &States::start; -} - -StateCondCommand::StateCondCommand() : State() -{ - transitions['\0'] = this; -} - -State * -StateCondCommand::custom(gchar chr) throw (Error) -{ - gint64 value = 0; - bool result; - - switch (mode) { - case MODE_PARSE_ONLY_COND: - undo.push_var(nest_level); - nest_level++; - break; - case MODE_NORMAL: - value = expressions.pop_num_calc(); - break; - default: - break; - } - - switch (g_ascii_toupper(chr)) { - case 'A': - BEGIN_EXEC(&States::start); - result = g_ascii_isalpha((gchar)value); - break; - case 'C': - BEGIN_EXEC(&States::start); - result = g_ascii_isalnum((gchar)value) || - value == '.' || value == '$' || value == '_'; - break; - case 'D': - BEGIN_EXEC(&States::start); - result = g_ascii_isdigit((gchar)value); - break; - case 'E': - case 'F': - case 'U': - case '=': - BEGIN_EXEC(&States::start); - result = value == 0; - break; - case 'G': - case '>': - BEGIN_EXEC(&States::start); - result = value > 0; - break; - case 'L': - case 'S': - case 'T': - case '<': - BEGIN_EXEC(&States::start); - result = value < 0; - break; - case 'N': - BEGIN_EXEC(&States::start); - result = value != 0; - break; - case 'R': - BEGIN_EXEC(&States::start); - result = g_ascii_isalnum((gchar)value); - break; - case 'V': - BEGIN_EXEC(&States::start); - result = g_ascii_islower((gchar)value); - break; - case 'W': - BEGIN_EXEC(&States::start); - result = g_ascii_isupper((gchar)value); - break; - default: - throw Error("Invalid conditional type \"%c\"", chr); - } - - if (!result) { - /* skip to ELSE-part or end of conditional */ - undo.push_var(mode); - mode = MODE_PARSE_ONLY_COND; - } - - return &States::start; -} - -StateControl::StateControl() : State() -{ - transitions['\0'] = this; - transitions['U'] = &States::ctlucommand; -} - -State * -StateControl::custom(gchar chr) throw (Error) -{ - switch (g_ascii_toupper(chr)) { - case 'O': - BEGIN_EXEC(&States::start); - expressions.set_radix(8); - break; - - case 'D': - BEGIN_EXEC(&States::start); - expressions.set_radix(10); - break; - - case 'R': - BEGIN_EXEC(&States::start); - expressions.eval(); - if (!expressions.args()) - expressions.push(expressions.radix); - else - expressions.set_radix(expressions.pop_num_calc()); - break; - - /* - * Alternatives: ^i, ^I, , - */ - case 'I': - BEGIN_EXEC(&States::insert); - expressions.eval(); - expressions.push('\t'); - return &States::insert; - - /* - * Alternatives: ^[, , - */ - case '[': - BEGIN_EXEC(&States::start); - expressions.discard_args(); - break; - - /* - * Additional numeric operations - */ - case '_': - BEGIN_EXEC(&States::start); - expressions.push(~expressions.pop_num_calc()); - break; - - case '*': - BEGIN_EXEC(&States::start); - expressions.push_calc(Expressions::OP_POW); - break; - - case '/': - BEGIN_EXEC(&States::start); - expressions.push_calc(Expressions::OP_MOD); - break; - - default: - throw Error("Unsupported command <^%c>", chr); - } - - return &States::start; -} - -StateECommand::StateECommand() : State() -{ - transitions['\0'] = this; - transitions['B'] = &States::editfile; - transitions['S'] = &States::scintilla_symbols; - transitions['Q'] = &States::eqcommand; - transitions['W'] = &States::savefile; -} - -State * -StateECommand::custom(gchar chr) throw (Error) -{ - switch (g_ascii_toupper(chr)) { - case 'F': - BEGIN_EXEC(&States::start); - if (!ring.current) - throw Error("No buffer selected"); - - if (IS_FAILURE(expressions.pop_num_calc()) && - ring.current->dirty) - throw Error("Buffer \"%s\" is dirty", - ring.current->filename ? : "(Unnamed)"); - - ring.close(); - break; - - case 'D': - BEGIN_EXEC(&States::start); - expressions.eval(); - if (!expressions.args()) { - expressions.push(Flags::ed); - } else { - gint64 on = expressions.pop_num_calc(); - gint64 off = expressions.pop_num_calc(1, ~(gint64)0); - - undo.push_var(Flags::ed); - Flags::ed = (Flags::ed & ~off) | on; - } - break; - - case 'X': - BEGIN_EXEC(&States::start); - - if (IS_FAILURE(expressions.pop_num_calc()) && - ring.is_any_dirty()) - throw Error("Modified buffers exist"); - - undo.push_var(quit_requested); - quit_requested = true; - break; - - default: - throw SyntaxError(chr); - } - - return &States::start; -} - -static struct ScintillaMessage { - unsigned int iMessage; - uptr_t wParam; - sptr_t lParam; -} scintilla_message = {0, 0, 0}; - -State * -StateScintilla_symbols::done(const gchar *str) throw (Error) -{ - BEGIN_EXEC(&States::scintilla_lparam); - - undo.push_var(scintilla_message); - if (*str) { - gchar **symbols = g_strsplit(str, ",", -1); - gint64 v; - - if (!symbols[0]) - goto cleanup; - if (*symbols[0]) { - v = Symbols::scintilla.lookup(symbols[0], "SCI_"); - if (v < 0) - throw Error("Unknown Scintilla message symbol \"%s\"", - symbols[0]); - scintilla_message.iMessage = v; - } - - if (!symbols[1]) - goto cleanup; - if (*symbols[1]) { - v = Symbols::scilexer.lookup(symbols[1]); - if (v < 0) - throw Error("Unknown Scintilla Lexer symbol \"%s\"", - symbols[1]); - scintilla_message.wParam = v; - } - - if (!symbols[2]) - goto cleanup; - if (*symbols[2]) { - v = Symbols::scilexer.lookup(symbols[2]); - if (v < 0) - throw Error("Unknown Scintilla Lexer symbol \"%s\"", - symbols[2]); - scintilla_message.lParam = v; - } - -cleanup: - g_strfreev(symbols); - } - - expressions.eval(); - if (!scintilla_message.iMessage) { - if (!expressions.args()) - throw Error(" command requires at least a message code"); - - scintilla_message.iMessage = expressions.pop_num_calc(1, 0); - } - if (!scintilla_message.wParam) - scintilla_message.wParam = expressions.pop_num_calc(1, 0); - - return &States::scintilla_lparam; -} - -State * -StateScintilla_lParam::done(const gchar *str) throw (Error) -{ - BEGIN_EXEC(&States::start); - - if (!scintilla_message.lParam) - scintilla_message.lParam = *str ? (sptr_t)str - : expressions.pop_num_calc(1, 0); - - expressions.push(interface.ssm(scintilla_message.iMessage, - scintilla_message.wParam, - scintilla_message.lParam)); - - undo.push_var(scintilla_message); - memset(&scintilla_message, 0, sizeof(scintilla_message)); - - return &States::start; -} - -/* - * NOTE: cannot support VideoTECO's I because - * beginning and end of strings must be determined - * syntactically - */ -void -StateInsert::initial(void) throw (Error) -{ - int args; - - expressions.eval(); - args = expressions.args(); - if (!args) - return; - - interface.ssm(SCI_BEGINUNDOACTION); - for (int i = args; i > 0; i--) { - gchar chr = (gchar)expressions.peek_num(i); - interface.ssm(SCI_ADDTEXT, 1, (sptr_t)&chr); - } - for (int i = args; i > 0; i--) - expressions.pop_num_calc(); - interface.ssm(SCI_SCROLLCARET); - interface.ssm(SCI_ENDUNDOACTION); - ring.dirtify(); - - undo.push_msg(SCI_UNDO); -} - -void -StateInsert::process(const gchar *str, gint new_chars) throw (Error) -{ - interface.ssm(SCI_BEGINUNDOACTION); - interface.ssm(SCI_ADDTEXT, new_chars, - (sptr_t)(str + strlen(str) - new_chars)); - interface.ssm(SCI_SCROLLCARET); - interface.ssm(SCI_ENDUNDOACTION); - ring.dirtify(); - - undo.push_msg(SCI_UNDO); -} - -State * -StateInsert::done(const gchar *str __attribute__((unused))) throw (Error) -{ - /* nothing to be done when done */ - return &States::start; -} diff --git a/parser.h b/parser.h deleted file mode 100644 index 4ae9db6..0000000 --- a/parser.h +++ /dev/null @@ -1,266 +0,0 @@ -#ifndef __PARSER_H -#define __PARSER_H - -#include -#include - -#include "sciteco.h" - -/* TECO uses only lower 7 bits for commands */ -#define MAX_TRANSITIONS 127 - -class State { -public: - class Error { - public: - Error(const gchar *fmt, ...); - }; - - class SyntaxError : public Error { - public: - SyntaxError(gchar chr) - : Error("Syntax error \"%c\" (%d)", chr, chr) {} - }; - - class MoveError : public Error { - public: - MoveError(const gchar *cmd) - : Error("Attempt to move pointer off page with <%s>", - cmd) {} - MoveError(gchar cmd) - : Error("Attempt to move pointer off page with <%c>", - cmd) {} - }; - - class RangeError : public Error { - public: - RangeError(const gchar *cmd) - : Error("Invalid range specified for <%s>", cmd) {} - RangeError(gchar cmd) - : Error("Invalid range specified for <%c>", cmd) {} - }; - - class InvalidQRegError : public Error { - public: - InvalidQRegError(const gchar *name, bool local = false) - : Error("Invalid Q-Register \"%s%s\"", - local ? "." : "", name) {} - InvalidQRegError(gchar name, bool local = false) - : Error("Invalid Q-Register \"%s%c\"", - local ? "." : "", name) {} - }; - -protected: - /* static transitions */ - State *transitions[MAX_TRANSITIONS]; - - inline void - init(const gchar *chars, State &state) - { - while (*chars) - transitions[(int)*chars++] = &state; - } - inline void - init(const gchar *chars) - { - init(chars, *this); - } - -public: - State(); - - static void input(gchar chr) throw (Error); - State *get_next_state(gchar chr) throw (Error); - -protected: - static bool eval_colon(void); - - virtual State * - custom(gchar chr) throw (Error) - { - throw SyntaxError(chr); - return NULL; - } -}; - -/* - * Super-class for states accepting string arguments - * Opaquely cares about alternative-escape characters, - * string building commands and accumulation into a string - */ -class StateExpectString : public State { - struct Machine { - enum State { - STATE_START, - STATE_ESCAPED, - STATE_LOWER, - STATE_UPPER, - STATE_CTL_E, - STATE_CTL_EQ, - STATE_CTL_EQ_LOCAL, - STATE_CTL_EU, - STATE_CTL_EU_LOCAL - } state; - - enum Mode { - MODE_NORMAL, - MODE_UPPER, - MODE_LOWER - } mode; - - bool toctl; - - Machine() : state(STATE_START), - mode(MODE_NORMAL), toctl(false) {} - } machine; - - gint nesting; - - bool string_building; - bool last; - -public: - StateExpectString(bool _building = true, bool _last = true) - : State(), nesting(1), - string_building(_building), last(_last) {} - -private: - gchar *machine_input(gchar key) throw (Error); - State *custom(gchar chr) throw (Error); - -protected: - virtual void initial(void) throw (Error) {} - virtual void process(const gchar *str, gint new_chars) throw (Error) {} - virtual State *done(const gchar *str) throw (Error) = 0; -}; - -class QRegister; - -/* - * Super class for states accepting Q-Register specifications - */ -class StateExpectQReg : public State { - bool got_local; - -public: - StateExpectQReg(); - -private: - State *custom(gchar chr) throw (Error); - -protected: - /* - * FIXME: would be nice to pass reg as reference, but there are - * circular header dependencies... - */ - virtual State *got_register(QRegister *reg) throw (Error) = 0; -}; - -class StateStart : public State { -public: - StateStart(); - -private: - void insert_integer(gint64 v); - gint64 read_integer(void); - - tecoBool move_chars(gint64 n); - tecoBool move_lines(gint64 n); - - tecoBool delete_words(gint64 n); - - State *custom(gchar chr) throw (Error); -}; - -class StateControl : public State { -public: - StateControl(); - -private: - State *custom(gchar chr) throw (Error); -}; - -class StateFCommand : public State { -public: - StateFCommand(); - -private: - State *custom(gchar chr) throw (Error); -}; - -class StateCondCommand : public State { -public: - StateCondCommand(); - -private: - State *custom(gchar chr) throw (Error); -}; - -class StateECommand : public State { -public: - StateECommand(); - -private: - State *custom(gchar chr) throw (Error); -}; - -class StateScintilla_symbols : public StateExpectString { -public: - StateScintilla_symbols() : StateExpectString(true, false) {} - -private: - State *done(const gchar *str) throw (Error); -}; - -class StateScintilla_lParam : public StateExpectString { -private: - State *done(const gchar *str) throw (Error); -}; - -/* - * also serves as base class for replace-insertion states - */ -class StateInsert : public StateExpectString { -protected: - void initial(void) throw (Error); - void process(const gchar *str, gint new_chars) throw (Error); - State *done(const gchar *str) throw (Error); -}; - -namespace States { - extern StateStart start; - extern StateControl control; - extern StateFCommand fcommand; - extern StateCondCommand condcommand; - extern StateECommand ecommand; - extern StateScintilla_symbols scintilla_symbols; - extern StateScintilla_lParam scintilla_lparam; - extern StateInsert insert; - - extern State *current; -} - -extern enum Mode { - MODE_NORMAL = 0, - MODE_PARSE_ONLY_GOTO, - MODE_PARSE_ONLY_LOOP, - MODE_PARSE_ONLY_COND -} mode; - -#define BEGIN_EXEC(STATE) G_STMT_START { \ - if (mode > MODE_NORMAL) \ - return STATE; \ -} G_STMT_END - -extern gint macro_pc; - -extern gchar *strings[2]; -extern gchar escape_char; - -namespace Execute { - void step(const gchar *macro) throw (State::Error); - void macro(const gchar *macro, bool locals = true) throw (State::Error); - bool file(const gchar *filename, bool locals = true); -} - -#endif diff --git a/qregisters.cpp b/qregisters.cpp deleted file mode 100644 index 06947f7..0000000 --- a/qregisters.cpp +++ /dev/null @@ -1,522 +0,0 @@ -#include -#include - -#include -#include -#include - -#include - -#include "sciteco.h" -#include "interface.h" -#include "undo.h" -#include "parser.h" -#include "expressions.h" -#include "ring.h" -#include "qregisters.h" - -namespace States { - StatePushQReg pushqreg; - StatePopQReg popqreg; - StateEQCommand eqcommand; - StateLoadQReg loadqreg; - StateCtlUCommand ctlucommand; - StateSetQRegString setqregstring; - StateGetQRegString getqregstring; - StateGetQRegInteger getqreginteger; - StateSetQRegInteger setqreginteger; - StateIncreaseQReg increaseqreg; - StateMacro macro; - StateCopyToQReg copytoqreg; -} - -namespace QRegisters { - QRegisterTable globals; - QRegisterTable *locals = NULL; - QRegister *current = NULL; - - static QRegisterStack stack; -} - -static QRegister *register_argument = NULL; - -static inline void -current_edit(void) -{ - if (ring.current) - ring.current->edit(); - else if (QRegisters::current) - QRegisters::current->edit(); -} - -void -QRegisterData::set_string(const gchar *str) -{ - edit(); - dot = 0; - - interface.ssm(SCI_BEGINUNDOACTION); - interface.ssm(SCI_SETTEXT, 0, (sptr_t)(str ? : "")); - interface.ssm(SCI_ENDUNDOACTION); - - current_edit(); -} - -void -QRegisterData::undo_set_string(void) -{ - /* set_string() assumes that dot has been saved */ - current_save_dot(); - - if (!must_undo) - return; - - if (ring.current) - ring.current->undo_edit(); - else if (QRegisters::current) - QRegisters::current->undo_edit(); - - undo.push_var(dot); - undo.push_msg(SCI_UNDO); - - undo_edit(); -} - -void -QRegisterData::append_string(const gchar *str) -{ - if (!str) - return; - - edit(); - - interface.ssm(SCI_BEGINUNDOACTION); - interface.ssm(SCI_APPENDTEXT, strlen(str), (sptr_t)str); - interface.ssm(SCI_ENDUNDOACTION); - - current_edit(); -} - -gchar * -QRegisterData::get_string(void) -{ - gint size; - gchar *str; - - current_save_dot(); - edit(); - - size = interface.ssm(SCI_GETLENGTH) + 1; - str = (gchar *)g_malloc(size); - interface.ssm(SCI_GETTEXT, size, (sptr_t)str); - - current_edit(); - - return str; -} - -void -QRegisterData::edit(void) -{ - interface.ssm(SCI_SETDOCPOINTER, 0, (sptr_t)get_document()); - interface.ssm(SCI_GOTOPOS, dot); -} - -void -QRegisterData::undo_edit(void) -{ - if (!must_undo) - return; - - undo.push_msg(SCI_GOTOPOS, dot); - undo.push_msg(SCI_SETDOCPOINTER, 0, (sptr_t)get_document()); -} - -void -QRegister::edit(void) -{ - QRegisterData::edit(); - interface.info_update(this); -} - -void -QRegister::undo_edit(void) -{ - if (!must_undo) - return; - - interface.undo_info_update(this); - QRegisterData::undo_edit(); -} - -void -QRegister::execute(bool locals) throw (State::Error) -{ - gchar *str = get_string(); - - try { - Execute::macro(str, locals); - } catch (...) { - g_free(str); - throw; /* forward */ - } - - g_free(str); -} - -bool -QRegister::load(const gchar *filename) -{ - gchar *contents; - gsize size; - - /* FIXME: prevent excessive allocations by reading file into buffer */ - if (!g_file_get_contents(filename, &contents, &size, NULL)) - return false; - - edit(); - dot = 0; - - interface.ssm(SCI_BEGINUNDOACTION); - interface.ssm(SCI_CLEARALL); - interface.ssm(SCI_APPENDTEXT, size, (sptr_t)contents); - interface.ssm(SCI_ENDUNDOACTION); - - g_free(contents); - - current_edit(); - - return true; -} - -gint64 -QRegisterBufferInfo::get_integer(void) -{ - gint64 id = 1; - - if (!ring.current) - return 0; - - for (Buffer *buffer = ring.first(); - buffer != ring.current; - buffer = buffer->next()) - id++; - - return id; -} - -gchar * -QRegisterBufferInfo::get_string(void) -{ - gchar *filename = ring.current ? ring.current->filename : NULL; - - return g_strdup(filename ? : ""); -} - -void -QRegisterBufferInfo::edit(void) -{ - gchar *filename = ring.current ? ring.current->filename : NULL; - - QRegister::edit(); - - interface.ssm(SCI_BEGINUNDOACTION); - interface.ssm(SCI_SETTEXT, 0, (sptr_t)(filename ? : "")); - interface.ssm(SCI_ENDUNDOACTION); - - undo.push_msg(SCI_UNDO); -} - -void -QRegisterTable::initialize(void) -{ - /* general purpose registers */ - for (gchar q = 'A'; q <= 'Z'; q++) - initialize(q); - for (gchar q = '0'; q <= '9'; q++) - initialize(q); -} - -void -QRegisterTable::edit(QRegister *reg) -{ - current_save_dot(); - reg->edit(); - - ring.current = NULL; - QRegisters::current = reg; -} - -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 *reg) -{ - Entry *entry = new Entry(); - - entry->set_integer(reg->get_integer()); - if (reg->string) { - gchar *str = reg->get_string(); - entry->set_string(str); - g_free(str); - } - entry->dot = reg->dot; - - SLIST_INSERT_HEAD(&head, entry, entries); - undo.push(new UndoTokenPop(this)); -} - -bool -QRegisterStack::pop(QRegister *reg) -{ - Entry *entry = SLIST_FIRST(&head); - QRegisterData::document *string; - - if (!entry) - return false; - - reg->undo_set_integer(); - reg->set_integer(entry->get_integer()); - - /* exchange document ownership between Stack entry and Q-Register */ - string = reg->string; - if (reg->must_undo) - undo.push_var(reg->string); - reg->string = entry->string; - undo.push_var(entry->string); - entry->string = string; - - if (reg->must_undo) - undo.push_var(reg->dot); - reg->dot = entry->dot; - - SLIST_REMOVE_HEAD(&head, entries); - /* pass entry ownership to undo stack */ - undo.push(new UndoTokenPush(this, entry)); - - return true; -} - -QRegisterStack::~QRegisterStack() -{ - Entry *entry, *next; - - SLIST_FOREACH_SAFE(entry, &head, entries, next) - delete entry; -} - -void -QRegisters::hook(Hook type) -{ - if (!(Flags::ed & Flags::ED_HOOKS)) - return; - - expressions.push(type); - globals["0"]->execute(); -} - -/* - * Command states - */ - -State * -StatePushQReg::got_register(QRegister *reg) throw (Error) -{ - BEGIN_EXEC(&States::start); - - QRegisters::stack.push(reg); - - return &States::start; -} - -State * -StatePopQReg::got_register(QRegister *reg) throw (Error) -{ - BEGIN_EXEC(&States::start); - - if (!QRegisters::stack.pop(reg)) - throw Error("Q-Register stack is empty"); - - return &States::start; -} - -State * -StateEQCommand::got_register(QRegister *reg) throw (Error) -{ - BEGIN_EXEC(&States::loadqreg); - register_argument = reg; - return &States::loadqreg; -} - -State * -StateLoadQReg::done(const gchar *str) throw (Error) -{ - BEGIN_EXEC(&States::start); - - if (*str) { - register_argument->undo_load(); - if (!register_argument->load(str)) - throw Error("Cannot load \"%s\" into Q-Register \"%s\"", - str, register_argument->name); - } else { - if (ring.current) - ring.undo_edit(); - else /* QRegisters::current != NULL */ - QRegisters::undo_edit(); - QRegisters::globals.edit(register_argument); - } - - return &States::start; -} - -State * -StateCtlUCommand::got_register(QRegister *reg) throw (Error) -{ - BEGIN_EXEC(&States::setqregstring); - register_argument = reg; - return &States::setqregstring; -} - -State * -StateSetQRegString::done(const gchar *str) throw (Error) -{ - BEGIN_EXEC(&States::start); - - register_argument->undo_set_string(); - register_argument->set_string(str); - - return &States::start; -} - -State * -StateGetQRegString::got_register(QRegister *reg) throw (Error) -{ - 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(); - - undo.push_msg(SCI_UNDO); - } - g_free(str); - - return &States::start; -} - -State * -StateGetQRegInteger::got_register(QRegister *reg) throw (Error) -{ - BEGIN_EXEC(&States::start); - - expressions.eval(); - expressions.push(reg->get_integer()); - - return &States::start; -} - -State * -StateSetQRegInteger::got_register(QRegister *reg) throw (Error) -{ - BEGIN_EXEC(&States::start); - - reg->undo_set_integer(); - reg->set_integer(expressions.pop_num_calc()); - - return &States::start; -} - -State * -StateIncreaseQReg::got_register(QRegister *reg) throw (Error) -{ - gint64 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; -} - -State * -StateMacro::got_register(QRegister *reg) throw (Error) -{ - BEGIN_EXEC(&States::start); - - /* don't create new local Q-Registers if colon modifier is given */ - reg->execute(!eval_colon()); - - return &States::start; -} - -State * -StateCopyToQReg::got_register(QRegister *reg) throw (Error) -{ - gint64 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 { - gint64 to = expressions.pop_num(); - from = expressions.pop_num(); - - if (!Validate::pos(from) || !Validate::pos(to)) - throw RangeError("X"); - - len = to - from; - } - - 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; -} diff --git a/qregisters.h b/qregisters.h deleted file mode 100644 index e810b7f..0000000 --- a/qregisters.h +++ /dev/null @@ -1,343 +0,0 @@ -#ifndef __QREGISTERS_H -#define __QREGISTERS_H - -#include - -#include -#include - -#include - -#include "sciteco.h" -#include "interface.h" -#include "undo.h" -#include "rbtree.h" -#include "parser.h" - -/* - * Classes - */ - -class QRegisterData { - gint64 integer; - -public: - typedef void document; - document *string; - gint dot; - - /* - * whether to generate UndoTokens (unnecessary in macro invocations) - */ - bool must_undo; - - QRegisterData() : integer(0), string(NULL), dot(0), must_undo(true) {} - virtual - ~QRegisterData() - { - if (string) - interface.ssm(SCI_RELEASEDOCUMENT, 0, (sptr_t)string); - } - - inline document * - get_document(void) - { - if (!string) - string = (document *)interface.ssm(SCI_CREATEDOCUMENT); - return string; - } - - virtual gint64 - set_integer(gint64 i) - { - return integer = i; - } - virtual void - undo_set_integer(void) - { - if (must_undo) - undo.push_var(integer); - } - virtual gint64 - get_integer(void) - { - return integer; - } - - virtual void set_string(const gchar *str); - virtual void undo_set_string(void); - virtual void append_string(const gchar *str); - virtual inline void - undo_append_string(void) - { - undo_set_string(); - } - virtual gchar *get_string(void); - - virtual void edit(void); - virtual void undo_edit(void); -}; - -class QRegister : public RBTree::RBEntry, public QRegisterData { -public: - gchar *name; - - QRegister(const gchar *_name) - : QRegisterData(), name(g_strdup(_name)) {} - virtual - ~QRegister() - { - g_free(name); - } - - int - operator <(RBEntry &entry) - { - return g_strcmp0(name, ((QRegister &)entry).name); - } - - virtual void edit(void); - virtual void undo_edit(void); - - void execute(bool locals = true) throw (State::Error); - - bool load(const gchar *filename); - inline void - undo_load(void) - { - undo_set_string(); - } -}; - -class QRegisterBufferInfo : public QRegister { -public: - QRegisterBufferInfo() : QRegister("*") - { - get_document(); - } - - gint64 - set_integer(gint64 v) - { - return v; - } - void undo_set_integer(void) {} - - gint64 get_integer(void); - - void set_string(const gchar *str) {} - void undo_set_string(void) {} - void append_string(const gchar *str) {} - void undo_append_string(void) {} - - gchar *get_string(void); - - void edit(void); -}; - -class QRegisterTable : public RBTree { - bool must_undo; - -public: - QRegisterTable(bool _undo = true) : RBTree(), must_undo(_undo) {} - - inline QRegister * - insert(QRegister *reg) - { - reg->must_undo = must_undo; - RBTree::insert(reg); - return reg; - } - - inline void - initialize(const gchar *name) - { - QRegister *reg = new QRegister(name); - insert(reg); - /* make sure document is initialized */ - reg->get_document(); - } - inline void - initialize(gchar name) - { - initialize((gchar []){name, '\0'}); - } - void initialize(void); - - inline QRegister * - operator [](const gchar *name) - { - QRegister reg(name); - return (QRegister *)find(®); - } - inline QRegister * - operator [](gchar chr) - { - return operator []((gchar []){chr, '\0'}); - } - - void edit(QRegister *reg); - inline QRegister * - edit(const gchar *name) - { - QRegister *reg = operator [](name); - - if (!reg) - return NULL; - edit(reg); - return reg; - } -}; - -class QRegisterStack { - class Entry : public QRegisterData { - public: - SLIST_ENTRY(Entry) entries; - - Entry() : QRegisterData() {} - }; - - class UndoTokenPush : public UndoToken { - QRegisterStack *stack; - /* only remaining reference to stack entry */ - Entry *entry; - - public: - UndoTokenPush(QRegisterStack *_stack, Entry *_entry) - : UndoToken(), stack(_stack), entry(_entry) {} - - ~UndoTokenPush() - { - if (entry) - delete entry; - } - - void run(void); - }; - - class UndoTokenPop : public UndoToken { - QRegisterStack *stack; - - public: - UndoTokenPop(QRegisterStack *_stack) - : UndoToken(), stack(_stack) {} - - void run(void); - }; - - SLIST_HEAD(Head, Entry) head; - -public: - QRegisterStack() - { - SLIST_INIT(&head); - } - ~QRegisterStack(); - - void push(QRegister *reg); - bool pop(QRegister *reg); -}; - -/* - * Command states - */ - -class StatePushQReg : public StateExpectQReg { -private: - State *got_register(QRegister *reg) throw (Error); -}; - -class StatePopQReg : public StateExpectQReg { -private: - State *got_register(QRegister *reg) throw (Error); -}; - -class StateEQCommand : public StateExpectQReg { -private: - State *got_register(QRegister *reg) throw (Error); -}; - -class StateLoadQReg : public StateExpectString { -private: - State *done(const gchar *str) throw (Error); -}; - -class StateCtlUCommand : public StateExpectQReg { -private: - State *got_register(QRegister *reg) throw (Error); -}; - -class StateSetQRegString : public StateExpectString { -public: - StateSetQRegString() : StateExpectString(false) {} -private: - State *done(const gchar *str) throw (Error); -}; - -class StateGetQRegString : public StateExpectQReg { -private: - State *got_register(QRegister *reg) throw (Error); -}; - -class StateGetQRegInteger : public StateExpectQReg { -private: - State *got_register(QRegister *reg) throw (Error); -}; - -class StateSetQRegInteger : public StateExpectQReg { -private: - State *got_register(QRegister *reg) throw (Error); -}; - -class StateIncreaseQReg : public StateExpectQReg { -private: - State *got_register(QRegister *reg) throw (Error); -}; - -class StateMacro : public StateExpectQReg { -private: - State *got_register(QRegister *reg) throw (Error); -}; - -class StateCopyToQReg : public StateExpectQReg { -private: - State *got_register(QRegister *reg) throw (Error); -}; - -namespace States { - extern StatePushQReg pushqreg; - extern StatePopQReg popqreg; - extern StateEQCommand eqcommand; - extern StateLoadQReg loadqreg; - extern StateCtlUCommand ctlucommand; - extern StateSetQRegString setqregstring; - extern StateGetQRegString getqregstring; - extern StateGetQRegInteger getqreginteger; - extern StateSetQRegInteger setqreginteger; - extern StateIncreaseQReg increaseqreg; - extern StateMacro macro; - extern StateCopyToQReg copytoqreg; -} - -namespace QRegisters { - extern QRegisterTable globals; - extern QRegisterTable *locals; - extern QRegister *current; - - static inline void - undo_edit(void) - { - current->dot = interface.ssm(SCI_GETCURRENTPOS); - undo.push_var(current)->undo_edit(); - } - - enum Hook { - HOOK_ADD = 1, - HOOK_EDIT, - HOOK_CLOSE, - HOOK_QUIT - }; - void hook(Hook type); -} - -#endif diff --git a/rbtree.cpp b/rbtree.cpp deleted file mode 100644 index 28df67b..0000000 --- a/rbtree.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include - -#include "rbtree.h" - -RB_GENERATE(RBTree::Tree, RBTree::RBEntry, nodes, RBTree::compare_entries); diff --git a/rbtree.h b/rbtree.h deleted file mode 100644 index d5f2a6e..0000000 --- a/rbtree.h +++ /dev/null @@ -1,282 +0,0 @@ -#ifndef __RBTREE_H -#define __RBTREE_H - -#include - -#include - -#include "undo.h" - -class RBTree { -public: - class RBEntry; - -private: - RB_HEAD(Tree, RBEntry) head; - - RB_PROTOTYPE_INTERNAL(Tree, RBEntry, nodes, /* unused */, static); - -public: - class RBEntry { - public: - RB_ENTRY(RBEntry) nodes; - - virtual ~RBEntry() {} - - inline RBEntry * - next(void) - { - return RBTree::Tree_RB_NEXT(this); - } - inline RBEntry * - prev(void) - { - return RBTree::Tree_RB_PREV(this); - } - - virtual int operator <(RBEntry &entry) = 0; - }; - -private: - static inline int - compare_entries(RBEntry *e1, RBEntry *e2) - { - return *e1 < *e2; - } - -public: - RBTree() - { - RB_INIT(&head); - } - virtual - ~RBTree() - { - clear(); - } - - inline RBEntry * - insert(RBEntry *entry) - { - RB_INSERT(Tree, &head, entry); - return entry; - } - inline RBEntry * - remove(RBEntry *entry) - { - return RB_REMOVE(Tree, &head, entry); - } - inline RBEntry * - find(RBEntry *entry) - { - return RB_FIND(Tree, &head, entry); - } - inline RBEntry * - nfind(RBEntry *entry) - { - return RB_NFIND(Tree, &head, entry); - } - inline RBEntry * - min(void) - { - return RB_MIN(Tree, &head); - } - inline RBEntry * - max(void) - { - return RB_MAX(Tree, &head); - } - - inline void - clear(void) - { - RBEntry *cur; - - while ((cur = min())) { - remove(cur); - delete cur; - } - } -}; - -template -class Table : public RBTree { - class TableEntry : public RBEntry { - public: - KeyType key; - ValueType value; - - TableEntry(KeyType &_key, ValueType &_value) - : key(_key), value(_value) {} - - int - operator <(RBEntry &entry) - { - return key < ((TableEntry &)entry).key; - } - }; - -public: - ValueType nil; - - Table(ValueType &_nil) : RBTree(), nil(_nil) {} - - inline bool - hasEntry(KeyType &key) - { - return find(&TableEntry(key, nil)) != NULL; - } - - ValueType & - operator [](KeyType &key) - { - TableEntry *entry = new TableEntry(key, nil); - TableEntry *existing = (TableEntry *)find(entry); - - if (existing) - delete entry; - else - existing = (TableEntry *)insert(entry); - - return existing->value; - } - - inline void - remove(KeyType &key) - { - TableEntry entry(key, nil); - TableEntry *existing = (TableEntry *)find(&entry); - - if (existing) - RBTree::remove(existing); - } - -#if 0 - void - dump(void) - { - RBEntry *cur; - - RB_FOREACH(cur, Tree, &head) - g_printf("tree[\"%s\"] = %d\n", cur->name, cur->pc); - g_printf("---END---\n"); - } -#endif - - void - clear(void) - { - RBEntry *cur; - - while ((cur = min())) { - RBTree::remove(cur); - delete cur; - } - } -}; - -class CString { -public: - gchar *str; - - CString(const gchar *_str) : str(g_strdup(_str)) {} - ~CString() - { - g_free(str); - } - - inline int - operator <(CString &obj) - { - return (int)g_strcmp0(str, obj.str); - } -}; - -template -class StringTable : public Table { -public: - StringTable(ValueType &nil) : Table(nil) {} - - inline bool - hasEntry(const gchar *key) - { - CString str(key); - return Table::hasEntry(str); - } - - inline ValueType & - operator [](const gchar *key) - { - CString str(key); - return (Table::operator [])(str); - } - - inline void - remove(const gchar *key) - { - CString str(key); - Table::remove(str); - } -}; - -template -class StringTableUndo : public StringTable { - class UndoTokenSet : public UndoToken { - StringTableUndo *table; - - CString name; - ValueType value; - - public: - UndoTokenSet(StringTableUndo *_table, CString &_name, ValueType &_value) - : table(_table), name(_name), value(_value) {} - - void - run(void) - { - table->Table::operator [](name) = value; - name.str = NULL; -#if 0 - table->dump(); -#endif - } - }; - - class UndoTokenRemove : public UndoToken { - StringTableUndo *table; - - CString name; - - public: - UndoTokenRemove(StringTableUndo *_table, CString &_name) - : table(_table), name(_name) {} - - void - run(void) - { - table->Table::remove(name); -#if 0 - table->dump(); -#endif - } - }; - -public: - StringTableUndo(ValueType &nil) : StringTable(nil) {} - - void - set(const gchar *key, ValueType &value) - { - ValueType &old = (StringTable::operator [])(key); - CString str(key); - - if (old == StringTable::nil) - undo.push(new UndoTokenRemove(this, str)); - else - undo.push(new UndoTokenSet(this, str, old)); - - old = value; - } -}; - -#endif diff --git a/ring.cpp b/ring.cpp deleted file mode 100644 index f1b051b..0000000 --- a/ring.cpp +++ /dev/null @@ -1,539 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include "sciteco.h" -#include "interface.h" -#include "undo.h" -#include "parser.h" -#include "expressions.h" -#include "ring.h" - -#ifdef G_OS_WIN32 -/* here it shouldn't cause conflicts with other headers */ -#include - -/* still need to clean up */ -#ifdef interface -#undef interface -#endif -#endif - -namespace States { - StateEditFile editfile; - StateSaveFile savefile; -} - -Ring ring; - -void -Buffer::UndoTokenClose::run(void) -{ - ring.close(buffer); - /* NOTE: the buffer is NOT deleted on Token destruction */ - delete buffer; -} - -bool -Buffer::load(const gchar *filename) -{ - gchar *contents; - gsize size; - - /* FIXME: prevent excessive allocations by reading file into buffer */ - if (!g_file_get_contents(filename, &contents, &size, NULL)) - return false; - - edit(); - - interface.ssm(SCI_BEGINUNDOACTION); - interface.ssm(SCI_CLEARALL); - interface.ssm(SCI_APPENDTEXT, size, (sptr_t)contents); - interface.ssm(SCI_ENDUNDOACTION); - - g_free(contents); - - /* NOTE: currently buffer cannot be dirty */ -#if 0 - interface.undo_info_update(this); - undo.push_var(dirty); - dirty = false; -#endif - - set_filename(filename); - - return true; -} - -void -Ring::UndoTokenEdit::run(void) -{ - /* - * assumes that buffer still has correct prev/next - * pointers - */ - if (buffer->next()) - TAILQ_INSERT_BEFORE(buffer->next(), buffer, buffers); - else - TAILQ_INSERT_TAIL(&ring->head, buffer, buffers); - - ring->current = buffer; - buffer->edit(); - buffer = NULL; -} - -Buffer * -Ring::find(const gchar *filename) -{ - gchar *resolved = get_absolute_path(filename); - Buffer *cur; - - TAILQ_FOREACH(cur, &head, buffers) - if (!g_strcmp0(cur->filename, resolved)) - break; - - g_free(resolved); - return cur; -} - -Buffer * -Ring::find(gint64 id) -{ - Buffer *cur; - - TAILQ_FOREACH(cur, &head, buffers) - if (!--id) - break; - - return cur; -} - -void -Ring::dirtify(void) -{ - if (!current || current->dirty) - return; - - interface.undo_info_update(current); - undo.push_var(current->dirty); - current->dirty = true; - interface.info_update(current); -} - -bool -Ring::is_any_dirty(void) -{ - Buffer *cur; - - TAILQ_FOREACH(cur, &head, buffers) - if (cur->dirty) - return true; - - return false; -} - -bool -Ring::edit(gint64 id) -{ - Buffer *buffer = find(id); - - if (!buffer) - return false; - - current_save_dot(); - - QRegisters::current = NULL; - current = buffer; - buffer->edit(); - - QRegisters::hook(QRegisters::HOOK_EDIT); - - return true; -} - -void -Ring::edit(const gchar *filename) -{ - Buffer *buffer = find(filename); - - current_save_dot(); - - QRegisters::current = NULL; - if (buffer) { - current = buffer; - buffer->edit(); - - QRegisters::hook(QRegisters::HOOK_EDIT); - } else { - buffer = new Buffer(); - TAILQ_INSERT_TAIL(&head, buffer, buffers); - - current = buffer; - undo_close(); - - if (filename && g_file_test(filename, G_FILE_TEST_IS_REGULAR)) { - buffer->load(filename); - - interface.msg(Interface::MSG_INFO, - "Added file \"%s\" to ring", filename); - } else { - buffer->edit(); - buffer->set_filename(filename); - - if (filename) - interface.msg(Interface::MSG_INFO, - "Added new file \"%s\" to ring", - filename); - else - interface.msg(Interface::MSG_INFO, - "Added new unnamed file to ring."); - } - - QRegisters::hook(QRegisters::HOOK_ADD); - } -} - -#if 0 - -/* - * TODO: on UNIX it may be better to open() the current file, unlink() it - * and keep the file descriptor in the UndoToken. - * When the operation is undone, the file descriptor's contents are written to - * the file (which should be efficient enough because it is written to the same - * filesystem). This way we could avoid messing around with save point files. - */ - -#else - -class UndoTokenRestoreSavePoint : public UndoToken { - gchar *savepoint; - Buffer *buffer; - -public: -#ifdef G_OS_WIN32 - DWORD attributes; -#endif - - UndoTokenRestoreSavePoint(gchar *_savepoint, Buffer *_buffer) - : savepoint(_savepoint), buffer(_buffer) {} - ~UndoTokenRestoreSavePoint() - { - if (savepoint) - g_unlink(savepoint); - g_free(savepoint); - buffer->savepoint_id--; - } - - void - run(void) - { - if (!g_rename(savepoint, buffer->filename)) { - g_free(savepoint); - savepoint = NULL; -#ifdef G_OS_WIN32 - SetFileAttributes((LPCTSTR)buffer->filename, - attributes); -#endif - } else { - interface.msg(Interface::MSG_WARNING, - "Unable to restore save point file \"%s\"", - savepoint); - } - } -}; - -static inline void -make_savepoint(Buffer *buffer) -{ - gchar *dirname, *basename, *savepoint; - gchar savepoint_basename[FILENAME_MAX]; - - basename = g_path_get_basename(buffer->filename); - g_snprintf(savepoint_basename, sizeof(savepoint_basename), - ".teco-%s-%d", basename, buffer->savepoint_id); - g_free(basename); - dirname = g_path_get_dirname(buffer->filename); - savepoint = g_build_filename(dirname, savepoint_basename, NULL); - g_free(dirname); - - if (!g_rename(buffer->filename, savepoint)) { - UndoTokenRestoreSavePoint *token; - - buffer->savepoint_id++; - token = new UndoTokenRestoreSavePoint(savepoint, buffer); -#ifdef G_OS_WIN32 - token->attributes = GetFileAttributes((LPCTSTR)savepoint); - if (token->attributes != INVALID_FILE_ATTRIBUTES) - SetFileAttributes((LPCTSTR)savepoint, - token->attributes | - FILE_ATTRIBUTE_HIDDEN); -#endif - undo.push(token); - } else { - interface.msg(Interface::MSG_WARNING, - "Unable to create save point file \"%s\"", - savepoint); - g_free(savepoint); - } -} - -#endif /* !G_OS_UNIX */ - -bool -Ring::save(const gchar *filename) -{ - const gchar *buffer; - gssize size; - - if (!current) - return false; - - if (!filename) - filename = current->filename; - if (!filename) - return false; - - if (undo.enabled) { - if (current->filename && - g_file_test(current->filename, G_FILE_TEST_IS_REGULAR)) - make_savepoint(current); - else - undo.push(new UndoTokenRemoveFile(filename)); - } - - /* FIXME: improve by writing before and after document gap */ - buffer = (const gchar *)interface.ssm(SCI_GETCHARACTERPOINTER); - size = interface.ssm(SCI_GETLENGTH); - - if (!g_file_set_contents(filename, buffer, size, NULL)) - return false; - - interface.undo_info_update(current); - undo.push_var(current->dirty); - current->dirty = false; - - /* - * FIXME: necessary also if the filename was not specified but the file - * is (was) new, in order to canonicalize the filename. - * May be circumvented by cananonicalizing without requiring the file - * name to exist (like readlink -f) - */ - //if (filename) { - undo.push_str(current->filename); - current->set_filename(filename); - //} - - return true; -} - -void -Ring::close(Buffer *buffer) -{ - TAILQ_REMOVE(&head, buffer, buffers); - - if (buffer->filename) - interface.msg(Interface::MSG_INFO, - "Removed file \"%s\" from the ring", - buffer->filename); - else - interface.msg(Interface::MSG_INFO, - "Removed unnamed file from the ring."); -} - -void -Ring::close(void) -{ - Buffer *buffer = current; - - buffer->dot = interface.ssm(SCI_GETCURRENTPOS); - close(buffer); - current = buffer->next() ? : buffer->prev(); - /* transfer responsibility to UndoToken object */ - undo.push(new UndoTokenEdit(this, buffer)); - - if (current) { - current->edit(); - QRegisters::hook(QRegisters::HOOK_EDIT); - } else { - edit((const gchar *)NULL); - } -} - -Ring::~Ring() -{ - Buffer *buffer, *next; - - TAILQ_FOREACH_SAFE(buffer, &head, buffers, next) - delete buffer; -} - -/* - * Auxiliary functions - */ -#ifdef G_OS_UNIX - -gchar * -get_absolute_path(const gchar *path) -{ - gchar buf[PATH_MAX]; - gchar *resolved; - - if (!path) - return NULL; - - if (!realpath(path, buf)) { - if (g_path_is_absolute(path)) { - resolved = g_strdup(path); - } else { - gchar *cwd = g_get_current_dir(); - resolved = g_build_filename(cwd, path, NULL); - g_free(cwd); - } - } else { - resolved = g_strdup(buf); - } - - return resolved; -} - -#elif defined(G_OS_WIN32) - -gchar * -get_absolute_path(const gchar *path) -{ - TCHAR buf[MAX_PATH]; - gchar *resolved = NULL; - - if (path && GetFullPathName(path, sizeof(buf), buf, NULL)) - resolved = g_strdup(buf); - - return resolved; -} - -#else - -/* - * FIXME: I doubt that works on any platform... - */ -gchar * -get_absolute_path(const gchar *path) -{ - return path ? g_file_read_link(path, NULL) : NULL; -} - -#endif /* !G_OS_UNIX && !G_OS_WIN32 */ - -/* - * Command states - */ - -void -StateEditFile::do_edit(const gchar *filename) throw (Error) -{ - if (ring.current) - ring.undo_edit(); - else /* QRegisters::current != NULL */ - QRegisters::undo_edit(); - ring.edit(filename); -} - -void -StateEditFile::do_edit(gint64 id) throw (Error) -{ - if (ring.current) - ring.undo_edit(); - else /* QRegisters::current != NULL */ - QRegisters::undo_edit(); - if (!ring.edit(id)) - throw Error("Invalid buffer id %" G_GINT64_FORMAT, id); -} - -void -StateEditFile::initial(void) throw (Error) -{ - gint64 id = expressions.pop_num_calc(1, -1); - - allowFilename = true; - - if (id == 0) { - for (Buffer *cur = ring.first(); cur; cur = cur->next()) - interface.popup_add(Interface::POPUP_FILE, - cur->filename ? : "(Unnamed)", - cur == ring.current); - - interface.popup_show(); - } else if (id > 0) { - allowFilename = false; - do_edit(id); - } -} - -State * -StateEditFile::done(const gchar *str) throw (Error) -{ - BEGIN_EXEC(&States::start); - - if (!allowFilename) { - if (*str) - throw Error("If a buffer is selected by id, the " - "string argument must be empty"); - - return &States::start; - } - - if (is_glob_pattern(str)) { - gchar *dirname; - GDir *dir; - - dirname = g_path_get_dirname(str); - dir = g_dir_open(dirname, 0, NULL); - - if (dir) { - const gchar *basename; - GPatternSpec *pattern; - - basename = g_path_get_basename(str); - pattern = g_pattern_spec_new(basename); - g_free((gchar *)basename); - - while ((basename = g_dir_read_name(dir))) { - if (g_pattern_match_string(pattern, basename)) { - gchar *filename; - - filename = g_build_filename(dirname, - basename, - NULL); - do_edit(filename); - g_free(filename); - } - } - - g_pattern_spec_free(pattern); - g_dir_close(dir); - } - - g_free(dirname); - } else { - do_edit(*str ? str : NULL); - } - - return &States::start; -} - -State * -StateSaveFile::done(const gchar *str) throw (Error) -{ - BEGIN_EXEC(&States::start); - - if (!ring.save(*str ? str : NULL)) - throw Error("Unable to save file"); - - return &States::start; -} diff --git a/ring.h b/ring.h deleted file mode 100644 index da4f322..0000000 --- a/ring.h +++ /dev/null @@ -1,246 +0,0 @@ -#ifndef __RING_H -#define __RING_H - -#include -#include - -#include -#include -#include - -#include - -#include "sciteco.h" -#include "interface.h" -#include "undo.h" -#include "qregisters.h" -#include "parser.h" - -/* - * Auxiliary functions - */ -static inline bool -is_glob_pattern(const gchar *str) -{ - return strchr(str, '*') || strchr(str, '?'); -} - -/* - * Get absolute/full version of a possibly relative path. - * Works with existing and non-existing paths (in the latter case, - * heuristics may be applied.) - */ -gchar *get_absolute_path(const gchar *path); - -/* - * Classes - */ - -class Buffer { - class UndoTokenClose : public UndoToken { - Buffer *buffer; - - public: - UndoTokenClose(Buffer *_buffer) - : UndoToken(), buffer(_buffer) {} - - void run(void); - }; - -public: - TAILQ_ENTRY(Buffer) buffers; - - gchar *filename; - gint dot; - - gint savepoint_id; - - bool dirty; - -private: - typedef void document; - document *doc; - -public: - Buffer() : filename(NULL), dot(0), savepoint_id(0), dirty(false) - { - doc = (document *)interface.ssm(SCI_CREATEDOCUMENT); - } - ~Buffer() - { - interface.ssm(SCI_RELEASEDOCUMENT, 0, (sptr_t)doc); - g_free(filename); - } - - inline Buffer *& - next(void) - { - return TAILQ_NEXT(this, buffers); - } - inline Buffer *& - prev(void) - { - TAILQ_HEAD(Head, Buffer); - - return TAILQ_PREV(this, Head, buffers); - } - - inline void - set_filename(const gchar *filename) - { - gchar *resolved = get_absolute_path(filename); - g_free(Buffer::filename); - Buffer::filename = resolved; - interface.info_update(this); - } - - inline void - edit(void) - { - interface.ssm(SCI_SETDOCPOINTER, 0, (sptr_t)doc); - interface.ssm(SCI_GOTOPOS, dot); - interface.info_update(this); - } - inline void - undo_edit(void) - { - interface.undo_info_update(this); - undo.push_msg(SCI_GOTOPOS, dot); - undo.push_msg(SCI_SETDOCPOINTER, 0, (sptr_t)doc); - } - - bool load(const gchar *filename); - - inline void - undo_close(void) - { - undo.push(new UndoTokenClose(this)); - } -}; - -extern class Ring { - /* - * Emitted after a buffer close - * The pointer is the only remaining reference to the buffer! - */ - class UndoTokenEdit : public UndoToken { - Ring *ring; - Buffer *buffer; - - public: - UndoTokenEdit(Ring *_ring, Buffer *_buffer) - : UndoToken(), ring(_ring), buffer(_buffer) {} - ~UndoTokenEdit() - { - if (buffer) - delete buffer; - } - - void run(void); - }; - - class UndoTokenRemoveFile : public UndoToken { - gchar *filename; - - public: - UndoTokenRemoveFile(const gchar *_filename) - : filename(g_strdup(_filename)) {} - ~UndoTokenRemoveFile() - { - g_free(filename); - } - - void - run(void) - { - g_unlink(filename); - } - }; - - TAILQ_HEAD(Head, Buffer) head; - -public: - Buffer *current; - - Ring() : current(NULL) - { - TAILQ_INIT(&head); - } - ~Ring(); - - inline Buffer * - first(void) - { - return TAILQ_FIRST(&head); - } - inline Buffer * - last(void) - { - return TAILQ_LAST(&head, Head); - } - - Buffer *find(const gchar *filename); - Buffer *find(gint64 id); - - void dirtify(void); - bool is_any_dirty(void); - - bool edit(gint64 id); - void edit(const gchar *filename); - inline void - undo_edit(void) - { - current->dot = interface.ssm(SCI_GETCURRENTPOS); - undo.push_var(current); - current->undo_edit(); - } - - bool save(const gchar *filename); - - void close(Buffer *buffer); - void close(void); - inline void - undo_close(void) - { - current->undo_close(); - } -} ring; - -/* - * Command states - */ - -class StateEditFile : public StateExpectString { -private: - bool allowFilename; - - void do_edit(const gchar *filename) throw (Error); - void do_edit(gint64 id) throw (Error); - - void initial(void) throw (Error); - State *done(const gchar *str) throw (Error); -}; - -class StateSaveFile : public StateExpectString { -private: - State *done(const gchar *str) throw (Error); -}; - -namespace States { - extern StateEditFile editfile; - extern StateSaveFile savefile; -} - -/* FIXME: clean up current_save_dot() usage */ -static inline void -current_save_dot(void) -{ - gint dot = interface.ssm(SCI_GETCURRENTPOS); - - if (ring.current) - ring.current->dot = dot; - else if (QRegisters::current) - QRegisters::current->dot = dot; -} - -#endif diff --git a/sciteco.h b/sciteco.h deleted file mode 100644 index 88ab109..0000000 --- a/sciteco.h +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef __SCITECO_H -#define __SCITECO_H - -#include - -#include "interface.h" - -/* Autoconf-like */ -#define PACKAGE_VERSION "0.1" -#define PACKAGE_NAME "SciTECO" -#define PACKAGE_STRING PACKAGE_NAME " " PACKAGE_VERSION - -namespace Flags { - enum { - ED_HOOKS = (1 << 5) - }; - - extern gint64 ed; -} - -extern gchar *cmdline; -extern bool quit_requested; - -void cmdline_keypress(gchar key); - -#define IS_CTL(C) ((C) < ' ') -#define CTL_ECHO(C) ((C) | 0x40) -#define CTL_KEY(C) ((C) & ~0x40) - -typedef gint64 tecoBool; - -#define SUCCESS (-1) -#define FAILURE (0) -#define TECO_BOOL(X) ((X) ? SUCCESS : FAILURE) - -#define IS_SUCCESS(X) ((X) < 0) -#define IS_FAILURE(X) (!IS_SUCCESS(X)) - -#define CHR2STR(X) ((gchar []){X, '\0'}) - -namespace String { - -static inline void -append(gchar *&str1, const gchar *str2) -{ - /* FIXME: optimize */ - gchar *new_str = g_strconcat(str1 ? : "", str2, NULL); - g_free(str1); - str1 = new_str; -} - -static inline void -append(gchar *&str, gchar chr) -{ - append(str, CHR2STR(chr)); -} - -} /* namespace String */ - -namespace Validate { - -static inline bool -pos(gint n) -{ - return n >= 0 && n <= interface.ssm(SCI_GETLENGTH); -} - -static inline bool -line(gint n) -{ - return n >= 0 && n < interface.ssm(SCI_GETLINECOUNT); -} - -} /* namespace Validate */ - -#endif diff --git a/search.cpp b/search.cpp deleted file mode 100644 index 701f36c..0000000 --- a/search.cpp +++ /dev/null @@ -1,571 +0,0 @@ -#include - -#include -#include - -#include "sciteco.h" -#include "expressions.h" -#include "undo.h" -#include "qregisters.h" -#include "ring.h" -#include "parser.h" -#include "search.h" - -namespace States { - StateSearch search; - StateSearchAll searchall; - StateSearchKill searchkill; - StateSearchDelete searchdelete; - StateReplace replace; - StateReplace_insert replace_insert; - StateReplaceDefault replacedefault; - StateReplaceDefault_insert replacedefault_insert; -} - -/* - * Command states - */ - -void -StateSearch::initial(void) throw (Error) -{ - gint64 v1, v2; - - undo.push_var(parameters); - - parameters.dot = interface.ssm(SCI_GETCURRENTPOS); - - v2 = expressions.pop_num_calc(); - if (expressions.args()) { - /* TODO: optional count argument? */ - v1 = expressions.pop_num_calc(); - if (v1 <= v2) { - parameters.count = 1; - parameters.from = (gint)v1; - parameters.to = (gint)v2; - } else { - parameters.count = -1; - parameters.from = (gint)v2; - parameters.to = (gint)v1; - } - - if (!Validate::pos(parameters.from) || - !Validate::pos(parameters.to)) - throw RangeError("S"); - } else { - parameters.count = (gint)v2; - if (v2 >= 0) { - parameters.from = parameters.dot; - parameters.to = interface.ssm(SCI_GETLENGTH); - } else { - parameters.from = 0; - parameters.to = parameters.dot; - } - } - - parameters.from_buffer = ring.current; - parameters.to_buffer = NULL; -} - -static inline const gchar * -regexp_escape_chr(gchar chr) -{ - static gchar escaped[] = {'\\', '\0', '\0'}; - - escaped[1] = chr; - return g_ascii_isalnum(chr) ? escaped + 1 : escaped; -} - -gchar * -StateSearch::class2regexp(MatchState &state, const gchar *&pattern, - bool escape_default) -{ - while (*pattern) { - QRegister *reg; - gchar *temp, *temp2; - - switch (state) { - case STATE_START: - switch (*pattern) { - case CTL_KEY('S'): - return g_strdup("[:^alnum:]"); - case CTL_KEY('E'): - state = STATE_CTL_E; - break; - default: - temp = escape_default - ? g_strdup(regexp_escape_chr(*pattern)) - : NULL; - return temp; - } - break; - - case STATE_CTL_E: - switch (g_ascii_toupper(*pattern)) { - case 'A': - state = STATE_START; - return g_strdup("[:alpha:]"); - /* same as */ - case 'B': - state = STATE_START; - return g_strdup("[:^alnum:]"); - case 'C': - state = STATE_START; - return g_strdup("[:alnum:].$"); - case 'D': - state = STATE_START; - return g_strdup("[:digit:]"); - case 'G': - state = STATE_ANYQ; - break; - case 'L': - state = STATE_START; - return g_strdup("\r\n\v\f"); - case 'R': - state = STATE_START; - return g_strdup("[:alnum:]"); - case 'V': - state = STATE_START; - return g_strdup("[:lower:]"); - case 'W': - state = STATE_START; - return g_strdup("[:upper:]"); - default: - return NULL; - } - break; - - case STATE_ANYQ: - /* FIXME: Q-Register spec might get more complicated */ - reg = QRegisters::globals[g_ascii_toupper(*pattern)]; - if (!reg) - return NULL; - - temp = reg->get_string(); - temp2 = g_regex_escape_string(temp, -1); - g_free(temp); - state = STATE_START; - return temp2; - - default: - return NULL; - } - - pattern++; - } - - return NULL; -} - -gchar * -StateSearch::pattern2regexp(const gchar *&pattern, - bool single_expr) -{ - MatchState state = STATE_START; - gchar *re = NULL; - - while (*pattern) { - gchar *new_re, *temp; - - temp = class2regexp(state, pattern); - if (temp) { - new_re = g_strconcat(re ? : "", "[", temp, "]", NULL); - g_free(temp); - g_free(re); - re = new_re; - - goto next; - } - if (!*pattern) - break; - - switch (state) { - case STATE_START: - switch (*pattern) { - case CTL_KEY('X'): String::append(re, "."); break; - case CTL_KEY('N'): state = STATE_NOT; break; - default: - String::append(re, regexp_escape_chr(*pattern)); - } - break; - - case STATE_NOT: - state = STATE_START; - temp = class2regexp(state, pattern, true); - if (!temp) - goto error; - new_re = g_strconcat(re ? : "", "[^", temp, "]", NULL); - g_free(temp); - g_free(re); - re = new_re; - g_assert(state == STATE_START); - break; - - case STATE_CTL_E: - state = STATE_START; - switch (g_ascii_toupper(*pattern)) { - case 'M': state = STATE_MANY; break; - case 'S': String::append(re, "\\s+"); break; - /* same as */ - case 'X': String::append(re, "."); break; - /* TODO: ASCII octal code!? */ - case '[': - String::append(re, "("); - state = STATE_ALT; - break; - default: - goto error; - } - break; - - case STATE_MANY: - temp = pattern2regexp(pattern, true); - if (!temp) - goto error; - new_re = g_strconcat(re ? : "", "(", temp, ")+", NULL); - g_free(temp); - g_free(re); - re = new_re; - state = STATE_START; - break; - - case STATE_ALT: - switch (*pattern) { - case ',': - String::append(re, "|"); - break; - case ']': - String::append(re, ")"); - state = STATE_START; - break; - default: - temp = pattern2regexp(pattern, true); - if (!temp) - goto error; - String::append(re, temp); - g_free(temp); - } - break; - - default: - /* shouldn't happen */ - g_assert(true); - } - -next: - if (single_expr && state == STATE_START) - return re; - - pattern++; - } - - if (state == STATE_ALT) - String::append(re, ")"); - - return re; - -error: - g_free(re); - return NULL; -} - -void -StateSearch::do_search(GRegex *re, gint from, gint to, gint &count) -{ - GMatchInfo *info; - const gchar *buffer; - - gint matched_from = -1, matched_to = -1; - - buffer = (const gchar *)interface.ssm(SCI_GETCHARACTERPOINTER); - g_regex_match_full(re, buffer, (gssize)to, from, - (GRegexMatchFlags)0, &info, NULL); - - if (count >= 0) { - while (g_match_info_matches(info) && --count) - g_match_info_next(info, NULL); - - if (!count) - /* successful */ - g_match_info_fetch_pos(info, 0, - &matched_from, &matched_to); - } else { - /* only keep the last `count' matches, in a circular stack */ - struct Range { - gint from, to; - }; - Range *matched = new Range[-count]; - gint matched_total = 0, i = 0; - - while (g_match_info_matches(info)) { - g_match_info_fetch_pos(info, 0, - &matched[i].from, - &matched[i].to); - - g_match_info_next(info, NULL); - i = ++matched_total % -count; - } - - count = MIN(count + matched_total, 0); - if (!count) { - /* successful, i points to stack bottom */ - matched_from = matched[i].from; - matched_to = matched[i].to; - } - - delete matched; - } - - g_match_info_free(info); - - if (matched_from >= 0 && matched_to >= 0) - /* match success */ - interface.ssm(SCI_SETSEL, matched_from, matched_to); -} - -void -StateSearch::process(const gchar *str, - gint new_chars __attribute__((unused))) throw (Error) -{ - static const gint flags = G_REGEX_CASELESS | G_REGEX_MULTILINE | - G_REGEX_DOTALL | G_REGEX_RAW; - - QRegister *search_reg = QRegisters::globals["_"]; - - gchar *re_pattern; - GRegex *re; - - gint count = parameters.count; - - undo.push_msg(SCI_SETSEL, - interface.ssm(SCI_GETANCHOR), - interface.ssm(SCI_GETCURRENTPOS)); - - search_reg->undo_set_integer(); - search_reg->set_integer(FAILURE); - - /* NOTE: pattern2regexp() modifies str pointer */ - re_pattern = pattern2regexp(str); -#ifdef DEBUG - g_printf("REGEXP: %s\n", re_pattern); -#endif - if (!re_pattern) - goto failure; - re = g_regex_new(re_pattern, (GRegexCompileFlags)flags, - (GRegexMatchFlags)0, NULL); - g_free(re_pattern); - if (!re) - goto failure; - - if (ring.current != parameters.from_buffer) { - ring.undo_edit(); - parameters.from_buffer->edit(); - } - - do_search(re, parameters.from, parameters.to, count); - - if (parameters.to_buffer && count) { - Buffer *buffer = parameters.from_buffer; - - if (ring.current == buffer) - ring.undo_edit(); - - if (count > 0) { - do { - buffer = buffer->next() ? : ring.first(); - buffer->edit(); - - if (buffer == parameters.to_buffer) { - do_search(re, 0, parameters.dot, count); - break; - } - - do_search(re, 0, interface.ssm(SCI_GETLENGTH), - count); - } while (count); - } else /* count < 0 */ { - do { - buffer = buffer->prev() ? : ring.last(); - buffer->edit(); - - if (buffer == parameters.to_buffer) { - do_search(re, parameters.dot, - interface.ssm(SCI_GETLENGTH), - count); - break; - } - - do_search(re, 0, interface.ssm(SCI_GETLENGTH), - count); - } while (count); - } - - ring.current = buffer; - } - - search_reg->set_integer(TECO_BOOL(!count)); - - g_regex_unref(re); - - if (!count) - return; - -failure: - interface.ssm(SCI_GOTOPOS, parameters.dot); -} - -State * -StateSearch::done(const gchar *str) throw (Error) -{ - BEGIN_EXEC(&States::start); - - QRegister *search_reg = QRegisters::globals["_"]; - - if (*str) { - /* workaround: preserve selection (also on rubout) */ - gint anchor = interface.ssm(SCI_GETANCHOR); - undo.push_msg(SCI_SETANCHOR, anchor); - - search_reg->undo_set_string(); - search_reg->set_string(str); - - interface.ssm(SCI_SETANCHOR, anchor); - } else { - gchar *search_str = search_reg->get_string(); - process(search_str, 0 /* unused */); - g_free(search_str); - } - - if (eval_colon()) - expressions.push(search_reg->get_integer()); - else if (IS_FAILURE(search_reg->get_integer()) && - !expressions.find_op(Expressions::OP_LOOP) /* not in loop */) - interface.msg(Interface::MSG_ERROR, "Search string not found!"); - - return &States::start; -} - -void -StateSearchAll::initial(void) throw (Error) -{ - gint64 v1, v2; - - undo.push_var(parameters); - - parameters.dot = interface.ssm(SCI_GETCURRENTPOS); - - v2 = expressions.pop_num_calc(); - if (expressions.args()) { - /* TODO: optional count argument? */ - v1 = expressions.pop_num_calc(); - if (v1 <= v2) { - parameters.count = 1; - parameters.from_buffer = ring.find(v1); - parameters.to_buffer = ring.find(v2); - } else { - parameters.count = -1; - parameters.from_buffer = ring.find(v2); - parameters.to_buffer = ring.find(v1); - } - - if (!parameters.from_buffer || !parameters.to_buffer) - throw RangeError("N"); - } else { - parameters.count = (gint)v2; - /* NOTE: on Q-Registers, behave like "S" */ - parameters.from_buffer = parameters.to_buffer = ring.current; - } - - if (parameters.count >= 0) { - parameters.from = parameters.dot; - parameters.to = interface.ssm(SCI_GETLENGTH); - } else { - parameters.from = 0; - parameters.to = parameters.dot; - } -} - -State * -StateSearchAll::done(const gchar *str) throw (Error) -{ - BEGIN_EXEC(&States::start); - - StateSearch::done(str); - QRegisters::hook(QRegisters::HOOK_EDIT); - - return &States::start; -} - -State * -StateSearchKill::done(const gchar *str) throw (Error) -{ - gint anchor; - - BEGIN_EXEC(&States::start); - - StateSearch::done(str); - - undo.push_msg(SCI_GOTOPOS, interface.ssm(SCI_GETCURRENTPOS)); - anchor = interface.ssm(SCI_GETANCHOR); - interface.ssm(SCI_GOTOPOS, anchor); - - interface.ssm(SCI_BEGINUNDOACTION); - interface.ssm(SCI_DELETERANGE, - parameters.dot, anchor - parameters.dot); - interface.ssm(SCI_ENDUNDOACTION); - ring.dirtify(); - - undo.push_msg(SCI_UNDO); - - return &States::start; -} - -State * -StateSearchDelete::done(const gchar *str) throw (Error) -{ - BEGIN_EXEC(&States::start); - - StateSearch::done(str); - - interface.ssm(SCI_BEGINUNDOACTION); - interface.ssm(SCI_REPLACESEL, 0, (sptr_t)""); - interface.ssm(SCI_ENDUNDOACTION); - ring.dirtify(); - - undo.push_msg(SCI_UNDO); - - return &States::start; -} - -State * -StateReplace::done(const gchar *str) throw (Error) -{ - StateSearchDelete::done(str); - return &States::replace_insert; -} - -State * -StateReplaceDefault::done(const gchar *str) throw (Error) -{ - StateSearchDelete::done(str); - return &States::replacedefault_insert; -} - -State * -StateReplaceDefault_insert::done(const gchar *str) throw (Error) -{ - BEGIN_EXEC(&States::start); - - QRegister *replace_reg = QRegisters::globals["-"]; - - if (*str) { - replace_reg->undo_set_string(); - replace_reg->set_string(str); - } else { - gchar *replace_str = replace_reg->get_string(); - StateInsert::process(replace_str, strlen(replace_str)); - g_free(replace_str); - } - - return &States::start; -} diff --git a/search.h b/search.h deleted file mode 100644 index 2bddedd..0000000 --- a/search.h +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef __SEARCH_H -#define __SEARCH_H - -#include - -#include "sciteco.h" -#include "parser.h" -#include "ring.h" - -/* - * "S" command state and base class for all other search/replace commands - */ -class StateSearch : public StateExpectString { -public: - StateSearch(bool last = true) : StateExpectString(true, last) {} - -protected: - struct Parameters { - gint dot; - gint from, to; - gint count; - - Buffer *from_buffer, *to_buffer; - } parameters; - - enum MatchState { - STATE_START, - STATE_NOT, - STATE_CTL_E, - STATE_ANYQ, - STATE_MANY, - STATE_ALT - }; - - gchar *class2regexp(MatchState &state, const gchar *&pattern, - bool escape_default = false); - gchar *pattern2regexp(const gchar *&pattern, bool single_expr = false); - void do_search(GRegex *re, gint from, gint to, gint &count); - - virtual void initial(void) throw (Error); - virtual void process(const gchar *str, gint new_chars) throw (Error); - virtual State *done(const gchar *str) throw (Error); -}; - -class StateSearchAll : public StateSearch { -private: - void initial(void) throw (Error); - State *done(const gchar *str) throw (Error); -}; - -class StateSearchKill : public StateSearch { -private: - State *done(const gchar *str) throw (Error); -}; - -class StateSearchDelete : public StateSearch { -public: - StateSearchDelete(bool last = true) : StateSearch(last) {} - -protected: - State *done(const gchar *str) throw (Error); -}; - -class StateReplace : public StateSearchDelete { -public: - StateReplace() : StateSearchDelete(false) {} - -private: - State *done(const gchar *str) throw (Error); -}; - -class StateReplace_insert : public StateInsert { -private: - void initial(void) throw (Error) {} -}; - -class StateReplaceDefault : public StateSearchDelete { -public: - StateReplaceDefault() : StateSearchDelete(false) {} - -private: - State *done(const gchar *str) throw (Error); -}; - -class StateReplaceDefault_insert : public StateInsert { -private: - void initial(void) throw (Error) {} - State *done(const gchar *str) throw (Error); -}; - -namespace States { - extern StateSearch search; - extern StateSearchAll searchall; - extern StateSearchKill searchkill; - extern StateSearchDelete searchdelete; - extern StateReplace replace; - extern StateReplace_insert replace_insert; - extern StateReplaceDefault replacedefault; - extern StateReplaceDefault_insert replacedefault_insert; -} - -#endif diff --git a/src/cmdline.cpp b/src/cmdline.cpp new file mode 100644 index 0000000..212722a --- /dev/null +++ b/src/cmdline.cpp @@ -0,0 +1,371 @@ +#include +#include + +#include +#include +#include + +#include "sciteco.h" +#include "interface.h" +#include "expressions.h" +#include "parser.h" +#include "qregisters.h" +#include "ring.h" +#include "goto.h" +#include "undo.h" +#include "symbols.h" + +static inline const gchar *process_edit_cmd(gchar key); +static gchar *macro_echo(const gchar *macro); + +static gchar *filename_complete(const gchar *filename, gchar completed = ' '); +static gchar *symbol_complete(SymbolList &list, const gchar *symbol, + gchar completed = ' '); + +static const gchar *last_occurrence(const gchar *str, + const gchar *chars = " \t\v\r\n\f<>,;@"); +static inline gboolean filename_is_dir(const gchar *filename); + +gchar *cmdline = NULL; + +bool quit_requested = false; + +void +cmdline_keypress(gchar key) +{ + gchar *cmdline_p; + const gchar *insert; + gchar *echo; + + /* + * Cleanup messages, popups, etc... + */ + interface.popup_clear(); + interface.msg_clear(); + + /* + * Process immediate editing commands + */ + insert = process_edit_cmd(key); + + /* + * Parse/execute characters + */ + if (cmdline) { + gint len = strlen(cmdline); + cmdline = (gchar *)g_realloc(cmdline, len + strlen(insert) + 1); + cmdline_p = cmdline + len; + } else { + cmdline = (gchar *)g_malloc(strlen(insert) + 1); + *cmdline = '\0'; + cmdline_p = cmdline; + } + + /* + * Execute one insertion character, extending cmdline, at a time so + * undo tokens get emitted for the corresponding characters. + */ + for (const gchar *p = insert; *p; p++) { + *cmdline_p++ = *p; + *cmdline_p = '\0'; + + try { + Execute::step(cmdline); + } catch (...) { + /* + * Undo tokens may have been emitted (or had to be) + * before the exception is thrown. They must be + * executed so as if the character had never been + * inserted. + */ + undo.pop(cmdline_p - cmdline); + cmdline_p[-1] = '\0'; + break; + } + } + + /* + * Echo command line + */ + echo = macro_echo(cmdline); + interface.cmdline_update(echo); + g_free(echo); +} + +static inline const gchar * +process_edit_cmd(gchar key) +{ + static gchar insert[255]; + gint cmdline_len = cmdline ? strlen(cmdline) : 0; + + insert[0] = '\0'; + + switch (key) { + case '\b': + if (cmdline_len) { + undo.pop(cmdline_len); + cmdline[cmdline_len - 1] = '\0'; + macro_pc--; + } + break; + + case CTL_KEY('T'): { + const gchar *filename = cmdline ? last_occurrence(cmdline) + 1 + : NULL; + gchar *new_chars = filename_complete(filename); + if (new_chars) + g_stpcpy(insert, new_chars); + g_free(new_chars); + break; + } + + case '\t': + if (States::current == &States::editfile || + States::current == &States::savefile || + States::current == &States::loadqreg) { + gchar complete = escape_char == '{' ? ' ' : escape_char; + gchar *new_chars = filename_complete(strings[0], + complete); + if (new_chars) + g_stpcpy(insert, new_chars); + g_free(new_chars); + } else if (States::current == &States::scintilla_symbols) { + const gchar *symbol = NULL; + SymbolList &list = Symbols::scintilla; + gchar *new_chars; + + if (strings[0]) { + symbol = last_occurrence(strings[0], ","); + if (*symbol == ',') { + symbol++; + list = Symbols::scilexer; + } + } + + new_chars = symbol_complete(list, symbol, ','); + if (new_chars) + g_stpcpy(insert, new_chars); + g_free(new_chars); + } else { + insert[0] = key; + insert[1] = '\0'; + } + break; + + case '\x1B': + if (States::current == &States::start && + cmdline && cmdline[cmdline_len - 1] == '\x1B') { + if (Goto::skip_label) { + interface.msg(Interface::MSG_ERROR, + "Label \"%s\" not found", + Goto::skip_label); + break; + } + + if (quit_requested) { + /* FIXME */ + exit(EXIT_SUCCESS); + } + + undo.clear(); + interface.ssm(SCI_EMPTYUNDOBUFFER); + Goto::table->clear(); + expressions.clear(); + + *cmdline = '\0'; + macro_pc = 0; + break; + } + /* fall through */ + default: + insert[0] = key; + insert[1] = '\0'; + } + + return insert; +} + +static gchar * +macro_echo(const gchar *macro) +{ + gchar *result, *rp; + + if (!macro) + return g_strdup(""); + + rp = result = (gchar *)g_malloc(strlen(macro)*5 + 1); + + for (const gchar *p = macro; *p; p++) { + switch (*p) { + case '\x1B': + *rp++ = '$'; + break; + case '\r': + rp = g_stpcpy(rp, ""); + break; + case '\n': + rp = g_stpcpy(rp, ""); + break; + case '\t': + rp = g_stpcpy(rp, ""); + break; + default: + if (IS_CTL(*p)) { + *rp++ = '^'; + *rp++ = CTL_ECHO(*p); + } else { + *rp++ = *p; + } + } + } + *rp = '\0'; + + return result; +} + +static gchar * +filename_complete(const gchar *filename, gchar completed) +{ + gchar *dirname, *basename; + GDir *dir; + GList *files = NULL, *matching; + GCompletion *completion; + gchar *new_prefix; + gchar *insert = NULL; + + if (!filename) + filename = ""; + + if (is_glob_pattern(filename)) + return NULL; + + dirname = g_path_get_dirname(filename); + dir = g_dir_open(dirname, 0, NULL); + if (!dir) { + g_free(dirname); + return NULL; + } + if (*dirname != *filename) + *dirname = '\0'; + + while ((basename = (gchar *)g_dir_read_name(dir))) { + gchar *filename = g_build_filename(dirname, basename, NULL); + + if (g_file_test(filename, G_FILE_TEST_IS_DIR)) { + gchar *new_filename; + new_filename = g_strconcat(filename, + G_DIR_SEPARATOR_S, NULL); + g_free(filename); + filename = new_filename; + } + + files = g_list_prepend(files, filename); + } + + g_free(dirname); + g_dir_close(dir); + + completion = g_completion_new(NULL); + g_completion_add_items(completion, files); + + matching = g_completion_complete(completion, filename, &new_prefix); + if (new_prefix && strlen(new_prefix) > strlen(filename)) + insert = g_strdup(new_prefix + strlen(filename)); + g_free(new_prefix); + + if (!insert && g_list_length(matching) > 1) { + matching = g_list_sort(matching, (GCompareFunc)g_strcmp0); + + for (GList *file = g_list_first(matching); + file != NULL; + file = g_list_next(file)) { + Interface::PopupEntryType type; + bool in_buffer = false; + + if (filename_is_dir((gchar *)file->data)) { + type = Interface::POPUP_DIRECTORY; + } else { + type = Interface::POPUP_FILE; + /* FIXME: inefficient */ + in_buffer = ring.find((gchar *)file->data); + } + + interface.popup_add(type, (gchar *)file->data, + in_buffer); + } + + interface.popup_show(); + } else if (g_list_length(matching) == 1 && + !filename_is_dir((gchar *)g_list_first(matching)->data)) { + String::append(insert, completed); + } + + g_completion_free(completion); + + for (GList *file = g_list_first(files); file; file = g_list_next(file)) + g_free(file->data); + g_list_free(files); + + return insert; +} + +static gchar * +symbol_complete(SymbolList &list, const gchar *symbol, gchar completed) +{ + GList *glist; + GList *matching; + GCompletion *completion; + gchar *new_prefix; + gchar *insert = NULL; + + if (!symbol) + symbol = ""; + + glist = list.get_glist(); + if (!glist) + return NULL; + + completion = g_completion_new(NULL); + g_completion_add_items(completion, glist); + + matching = g_completion_complete(completion, symbol, &new_prefix); + if (new_prefix && strlen(new_prefix) > strlen(symbol)) + insert = g_strdup(new_prefix + strlen(symbol)); + g_free(new_prefix); + + if (!insert && g_list_length(matching) > 1) { + for (GList *entry = g_list_first(matching); + entry != NULL; + entry = g_list_next(entry)) { + interface.popup_add(Interface::POPUP_PLAIN, + (gchar *)entry->data); + } + + interface.popup_show(); + } else if (g_list_length(matching) == 1) { + String::append(insert, completed); + } + + g_completion_free(completion); + + return insert; +} + +/* + * Auxiliary functions + */ + +static const gchar * +last_occurrence(const gchar *str, const gchar *chars) +{ + while (*chars) + str = strrchr(str, *chars++) ? : str; + + return str; +} + +static inline gboolean +filename_is_dir(const gchar *filename) +{ + return g_str_has_suffix(filename, G_DIR_SEPARATOR_S); +} diff --git a/src/expressions.cpp b/src/expressions.cpp new file mode 100644 index 0000000..ac06b43 --- /dev/null +++ b/src/expressions.cpp @@ -0,0 +1,201 @@ +#include + +#include "sciteco.h" +#include "undo.h" +#include "expressions.h" + +Expressions expressions; + +void +Expressions::set_num_sign(gint sign) +{ + undo.push_var(num_sign); + num_sign = sign; +} + +void +Expressions::set_radix(gint r) +{ + undo.push_var(radix); + radix = r; +} + +gint64 +Expressions::push(gint64 number) +{ + while (operators.items() && operators.peek() == OP_NEW) + pop_op(); + + push(OP_NUMBER); + + if (num_sign < 0) { + set_num_sign(1); + number *= -1; + } + + numbers.undo_pop(); + return numbers.push(number); +} + +gint64 +Expressions::pop_num(int index) +{ + gint64 n = 0; + + pop_op(); + if (numbers.items() > 0) { + n = numbers.pop(index); + numbers.undo_push(n, index); + } + + return n; +} + +gint64 +Expressions::pop_num_calc(int index, gint64 imply) +{ + eval(); + if (num_sign < 0) + set_num_sign(1); + + return args() > 0 ? pop_num(index) : imply; +} + +gint64 +Expressions::add_digit(gchar digit) +{ + gint64 n = args() > 0 ? pop_num() : 0; + + return push(n*radix + (n < 0 ? -1 : 1)*(digit - '0')); +} + +Expressions::Operator +Expressions::push(Expressions::Operator op) +{ + operators.undo_pop(); + return operators.push(op); +} + +Expressions::Operator +Expressions::push_calc(Expressions::Operator op) +{ + int first = first_op(); + + /* calculate if op has lower precedence than op on stack */ + if (first && operators.peek(first) <= op) + calc(); + + return push(op); +} + +Expressions::Operator +Expressions::pop_op(int index) +{ + Operator op = OP_NIL; + + if (operators.items() > 0) { + op = operators.pop(index); + operators.undo_push(op, index); + } + + return op; +} + +void +Expressions::calc(void) +{ + gint64 result; + + gint64 vright = pop_num(); + Operator op = pop_op(); + gint64 vleft = pop_num(); + + switch (op) { + case OP_POW: for (result = 1; vright--; result *= vleft); break; + case OP_MUL: result = vleft * vright; break; + case OP_DIV: result = vleft / vright; break; + case OP_MOD: result = vleft % vright; break; + case OP_ADD: result = vleft + vright; break; + case OP_SUB: result = vleft - vright; break; + case OP_AND: result = vleft & vright; break; + case OP_OR: result = vleft | vright; break; + default: + /* shouldn't happen */ + g_assert(false); + } + + push(result); +} + +void +Expressions::eval(bool pop_brace) +{ + for (;;) { + gint n = first_op(); + Operator op; + + if (n < 2) + break; + + op = operators.peek(n); + if (op == OP_LOOP) + break; + if (op == OP_BRACE) { + if (pop_brace) + pop_op(n); + break; + } + + calc(); + } +} + +int +Expressions::args(void) +{ + int n = 0; + int items = operators.items(); + + while (n < items && operators.peek(n+1) == OP_NUMBER) + n++; + + return n; +} + +int +Expressions::find_op(Operator op) +{ + int items = operators.items(); + + for (int i = 1; i <= items; i++) + if (operators.peek(i) == op) + return i; + + return 0; +} + +int +Expressions::first_op(void) +{ + int items = operators.items(); + + for (int i = 1; i <= items; i++) { + switch (operators.peek(i)) { + case OP_NUMBER: + case OP_NEW: + break; + default: + return i; + } + } + + return 0; +} + +void +Expressions::discard_args(void) +{ + eval(); + for (int i = args(); i; i--) + pop_num_calc(); +} diff --git a/src/expressions.h b/src/expressions.h new file mode 100644 index 0000000..a441e90 --- /dev/null +++ b/src/expressions.h @@ -0,0 +1,194 @@ +#ifndef __EXPRESSIONS_H +#define __EXPRESSIONS_H + +#include + +#include "undo.h" + +template +class ValueStack { + class UndoTokenPush : public UndoToken { + ValueStack *stack; + + Type value; + int index; + + public: + UndoTokenPush(ValueStack *_stack, + Type _value, int _index = 1) + : stack(_stack), value(_value), index(_index) {} + + void + run(void) + { + stack->push(value, index); + } + }; + + class UndoTokenPop : public UndoToken { + ValueStack *stack; + + int index; + + public: + UndoTokenPop(ValueStack *_stack, int _index = 1) + : stack(_stack), index(_index) {} + + void + run(void) + { + stack->pop(index); + } + }; + + int size; + + Type *stack; + Type *top; + +public: + ValueStack(int _size = 1024) : size(_size) + { + top = stack = new Type[size]; + } + + ~ValueStack() + { + delete stack; + } + + inline int + items(void) + { + return top - stack; + } + + inline Type & + push(Type value, int index = 1) + { + for (int i = -index + 1; i; i++) + top[i+1] = top[i]; + + top++; + return peek(index) = value; + } + inline void + undo_push(Type value, int index = 1) + { + undo.push(new UndoTokenPush(this, value, index)); + } + + inline Type + pop(int index = 1) + { + Type v = peek(index); + + top--; + while (--index) + top[-index] = top[-index + 1]; + + return v; + } + inline void + undo_pop(int index = 1) + { + undo.push(new UndoTokenPop(this, index)); + } + + inline Type & + peek(int index = 1) + { + return top[-index]; + } + + inline void + clear(void) + { + top = stack; + } +}; + +/* + * Arithmetic expression stacks + */ +extern class Expressions { +public: + /* reflects also operator precedence */ + enum Operator { + OP_NIL = 0, + OP_POW, // ^* + OP_MUL, // * + OP_DIV, // / + OP_MOD, // ^/ + OP_SUB, // - + OP_ADD, // + + OP_AND, // & + OP_OR, // # + // pseudo operators: + OP_NEW, + OP_BRACE, + OP_LOOP, + OP_NUMBER + }; + +private: + ValueStack numbers; + ValueStack operators; + +public: + Expressions() : num_sign(1), radix(10) {} + + gint num_sign; + void set_num_sign(gint sign); + + gint radix; + void set_radix(gint r); + + gint64 push(gint64 number); + + inline gint64 + peek_num(int index = 1) + { + return numbers.peek(index); + } + gint64 pop_num(int index = 1); + gint64 pop_num_calc(int index, gint64 imply); + inline gint64 + pop_num_calc(int index = 1) + { + return pop_num_calc(index, num_sign); + } + + gint64 add_digit(gchar digit); + + Operator push(Operator op); + Operator push_calc(Operator op); + inline Operator + peek_op(int index = 1) + { + return operators.peek(index); + } + Operator pop_op(int index = 1); + + void eval(bool pop_brace = false); + + int args(void); + + void discard_args(void); + + int find_op(Operator op); + + inline void + clear(void) + { + numbers.clear(); + operators.clear(); + } + +private: + void calc(void); + + int first_op(void); +} expressions; + +#endif diff --git a/src/goto.cpp b/src/goto.cpp new file mode 100644 index 0000000..7af5152 --- /dev/null +++ b/src/goto.cpp @@ -0,0 +1,147 @@ +#include +#include + +#include "sciteco.h" +#include "expressions.h" +#include "parser.h" +#include "undo.h" +#include "goto.h" + +namespace States { + StateLabel label; + StateGotoCmd gotocmd; +} + +namespace Goto { + GotoTable *table = NULL; + gchar *skip_label = NULL; +} + +gint +GotoTable::remove(gchar *name) +{ + gint existing_pc = -1; + + Label label(name); + Label *existing = (Label *)RBTree::find(&label); + + if (existing) { + existing_pc = existing->pc; + RBTree::remove(existing); + delete existing; + } + + return existing_pc; +} + +gint +GotoTable::find(gchar *name) +{ + Label label(name); + Label *existing = (Label *)RBTree::find(&label); + + return existing ? existing->pc : -1; +} + +gint +GotoTable::set(gchar *name, gint pc) +{ + if (pc < 0) + return remove(name); + + Label *label = new Label(name, pc); + Label *existing; + gint existing_pc = -1; + + existing = (Label *)RBTree::find(label); + if (existing) { + existing_pc = existing->pc; + g_free(existing->name); + existing->name = label->name; + existing->pc = label->pc; + + label->name = NULL; + delete label; + } else { + RBTree::insert(label); + } + +#ifdef DEBUG + dump(); +#endif + + return existing_pc; +} + +#ifdef DEBUG +void +GotoTable::dump(void) +{ + for (Label *cur = (Label *)RBTree::min(); + cur != NULL; + cur = (Label *)cur->next()) + g_printf("table[\"%s\"] = %d\n", cur->name, cur->pc); + g_printf("---END---\n"); +} +#endif + +/* + * Command states + */ + +StateLabel::StateLabel() : State() +{ + transitions['\0'] = this; +} + +State * +StateLabel::custom(gchar chr) throw (Error) +{ + if (chr == '!') { + Goto::table->undo_set(strings[0], + Goto::table->set(strings[0], macro_pc)); + + if (!g_strcmp0(strings[0], Goto::skip_label)) { + g_free(undo.push_str(Goto::skip_label)); + Goto::skip_label = NULL; + + undo.push_var(mode) = MODE_NORMAL; + } + + g_free(undo.push_str(strings[0])); + strings[0] = NULL; + + return &States::start; + } + + String::append(undo.push_str(strings[0]), chr); + return this; +} + +State * +StateGotoCmd::done(const gchar *str) throw (Error) +{ + gint64 value; + gchar **labels; + + BEGIN_EXEC(&States::start); + + value = expressions.pop_num_calc(); + labels = g_strsplit(str, ",", -1); + + if (value > 0 && value <= g_strv_length(labels) && *labels[value-1]) { + gint pc = Goto::table->find(labels[value-1]); + + if (pc >= 0) { + macro_pc = pc; + } else { + /* skip till label is defined */ + undo.push_str(Goto::skip_label); + Goto::skip_label = g_strdup(labels[value-1]); + undo.push_var(mode) = MODE_PARSE_ONLY_GOTO; + } + } + + g_strfreev(labels); + return &States::start; +} diff --git a/src/goto.h b/src/goto.h new file mode 100644 index 0000000..6275d34 --- /dev/null +++ b/src/goto.h @@ -0,0 +1,107 @@ +#ifndef __GOTO_H +#define __GOTO_H + +#include +#include + +#include "sciteco.h" +#include "parser.h" +#include "undo.h" +#include "rbtree.h" + +class GotoTable : public RBTree { + class UndoTokenSet : public UndoToken { + GotoTable *table; + + gchar *name; + gint pc; + + public: + UndoTokenSet(GotoTable *_table, gchar *_name, gint _pc = -1) + : table(_table), name(g_strdup(_name)), pc(_pc) {} + ~UndoTokenSet() + { + g_free(name); + } + + void + run(void) + { + table->set(name, pc); +#ifdef DEBUG + table->dump(); +#endif + } + }; + + class Label : public RBEntry { + public: + gchar *name; + gint pc; + + Label(gchar *_name, gint _pc = -1) + : name(g_strdup(_name)), pc(_pc) {} + ~Label() + { + g_free(name); + } + + int + operator <(RBEntry &l2) + { + return g_strcmp0(name, ((Label &)l2).name); + } + }; + + /* + * whether to generate UndoTokens (unnecessary in macro invocations) + */ + bool must_undo; + +public: + GotoTable(bool _undo = true) : RBTree(), must_undo(_undo) {} + + gint remove(gchar *name); + gint find(gchar *name); + + gint set(gchar *name, gint pc); + inline void + undo_set(gchar *name, gint pc = -1) + { + if (must_undo) + undo.push(new UndoTokenSet(this, name, pc)); + } + +#ifdef DEBUG + void dump(void); +#endif +}; + +namespace Goto { + extern GotoTable *table; + extern gchar *skip_label; +} + +/* + * Command states + */ + +class StateLabel : public State { +public: + StateLabel(); + +private: + State *custom(gchar chr) throw (Error); +}; + +class StateGotoCmd : public StateExpectString { +private: + State *done(const gchar *str) throw (Error); +}; + +namespace States { + extern StateLabel label; + extern StateGotoCmd gotocmd; +} + +#endif diff --git a/src/gtk-info-popup.gob b/src/gtk-info-popup.gob new file mode 100644 index 0000000..914647a --- /dev/null +++ b/src/gtk-info-popup.gob @@ -0,0 +1,165 @@ +requires 2.0.16 + +%privateheader{ +#include +%} + +%h{ +#include +%} + +enum GTK_INFO_POPUP { + PLAIN, + FILE, + DIRECTORY +} Gtk:Info:Popup:Entry:Type; + +class Gtk:Info:Popup from Gtk:Window { + private GtkWidget *parent; + + init(self) + { + GtkWidget *vbox; + + //gtk_window_set_gravity(GTK_WINDOW(self), GDK_GRAVITY_SOUTH_WEST); + gtk_container_set_border_width(GTK_CONTAINER(self), 10); + + vbox = gtk_vbox_new(FALSE, 5); + gtk_container_set_resize_mode(GTK_CONTAINER(vbox), GTK_RESIZE_PARENT); + gtk_container_add(GTK_CONTAINER(self), vbox); + gtk_widget_show(vbox); + } + + public GtkWidget * + new(Gtk:Widget *parent) + { + Self *widget; + GtkWidget *toplevel; + + widget = GET_NEW_VARG("type", GTK_WINDOW_POPUP, NULL); + widget->_priv->parent = parent; + + g_signal_connect(parent, "size-allocate", + G_CALLBACK(self_size_allocate_cb), widget); + toplevel = gtk_widget_get_toplevel(parent); + g_signal_connect(toplevel, "configure-event", + G_CALLBACK(self_configure_cb), widget); + + return GTK_WIDGET(widget); + } + + private void + position(self) + { + GdkWindow *window = gtk_widget_get_window(self->_priv->parent); + gint x, y; + gint w, h; + + if (!window) + return; + + gdk_window_get_origin(window, &x, &y); + gtk_window_get_size(GTK_WINDOW(self), &w, &h); + + gtk_window_move(GTK_WINDOW(self), x, y - h); + } + + private void + size_allocate_cb(Gtk:Widget *parent, + Gtk:Allocation *alloc, gpointer user_data) + { + self_position(SELF(user_data)); + } + + private gboolean + configure_cb(Gtk:Widget *toplevel, + Gdk:Event:Configure *event, gpointer user_data) + { + self_position(SELF(user_data)); + return FALSE; + } + + override (Gtk:Widget) void + show(Gtk:Widget *self) + { + GtkRequisition req; + + gtk_widget_size_request(self, &req); + gtk_window_resize(GTK_WINDOW(self), req.width, req.height); + self_position(SELF(self)); + + PARENT_HANDLER(self); + } + + public void + add(self, Gtk:Info:Popup:Entry:Type type, + const gchar *name, gboolean highlight) + { + static const gchar *type2stock[] = { + [GTK_INFO_POPUP_PLAIN] = NULL, + [GTK_INFO_POPUP_FILE] = GTK_STOCK_FILE, + [GTK_INFO_POPUP_DIRECTORY] = GTK_STOCK_DIRECTORY + }; + + GtkWidget *vbox = gtk_bin_get_child(GTK_BIN(self)); + GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(self)); + GtkRequisition req; + + GtkWidget *hbox; + GtkWidget *label; + gchar *markup; + + gtk_widget_size_request(GTK_WIDGET(self), &req); + if (req.height > gdk_screen_get_height(screen)) + return; + + hbox = gtk_hbox_new(FALSE, 5); + + if (type2stock[type]) { + GtkWidget *image; + + image = gtk_image_new_from_stock(type2stock[type], + GTK_ICON_SIZE_MENU); + gtk_box_pack_start(GTK_BOX(hbox), image, + FALSE, FALSE, 0); + } + + /* + * FIXME: setting Pango attributes directly would be + * much more efficient + */ + label = gtk_label_new(NULL); + markup = g_markup_printf_escaped("%s", + highlight ? "bold" : "normal", + name); + gtk_label_set_markup(GTK_LABEL(label), markup); + g_free(markup); + gtk_misc_set_alignment(GTK_MISC(label), 0., 0.5); + gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); + + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show_all(hbox); + + gtk_widget_size_request(GTK_WIDGET(self), &req); + if (req.height > gdk_screen_get_height(screen)) { + label = gtk_label_new("..."); + gtk_box_pack_start(GTK_BOX(vbox), label, + FALSE, FALSE, 0); + gtk_widget_show(label); + } + } + + public void + clear(self) + { + GtkWidget *vbox = gtk_bin_get_child(GTK_BIN(self)); + GList *children; + + children = gtk_container_get_children(GTK_CONTAINER(vbox)); + for (GList *cur = g_list_first(children); + cur != NULL; + cur = g_list_next(cur)) + gtk_widget_destroy(GTK_WIDGET(cur->data)); + g_list_free(children); + } +} diff --git a/src/interface-gtk.cpp b/src/interface-gtk.cpp new file mode 100644 index 0000000..7515b2f --- /dev/null +++ b/src/interface-gtk.cpp @@ -0,0 +1,248 @@ +#include + +#include +#include +#include + +#include +#include + +#include +#include "gtk-info-popup.h" + +#include +#include + +#include "sciteco.h" +#include "qregisters.h" +#include "ring.h" +#include "interface.h" +#include "interface-gtk.h" + +InterfaceGtk interface; + +extern "C" { +static void scintilla_notify(ScintillaObject *sci, uptr_t idFrom, + SCNotification *notify, gpointer user_data); +static gboolean cmdline_key_pressed(GtkWidget *widget, GdkEventKey *event, + gpointer user_data); +static gboolean exit_app(GtkWidget *w, GdkEventAny *e, gpointer p); +} + +#define UNNAMED_FILE "(Unnamed)" + +InterfaceGtk::InterfaceGtk() +{ + GtkWidget *vbox; + GtkWidget *info_content; + + gtk_init(NULL, NULL); + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), PACKAGE_NAME); + g_signal_connect(G_OBJECT(window), "delete-event", + G_CALLBACK(exit_app), NULL); + + vbox = gtk_vbox_new(FALSE, 0); + + editor_widget = scintilla_new(); + scintilla_set_id(SCINTILLA(editor_widget), 0); + gtk_widget_set_usize(editor_widget, 500, 300); + gtk_widget_set_can_focus(editor_widget, FALSE); + g_signal_connect(G_OBJECT(editor_widget), SCINTILLA_NOTIFY, + G_CALLBACK(scintilla_notify), NULL); + gtk_box_pack_start(GTK_BOX(vbox), editor_widget, TRUE, TRUE, 0); + + info_widget = gtk_info_bar_new(); + info_content = gtk_info_bar_get_content_area(GTK_INFO_BAR(info_widget)); + message_widget = gtk_label_new(""); + gtk_misc_set_alignment(GTK_MISC(message_widget), 0., 0.); + gtk_container_add(GTK_CONTAINER(info_content), message_widget); + gtk_box_pack_start(GTK_BOX(vbox), info_widget, FALSE, FALSE, 0); + + cmdline_widget = gtk_entry_new(); + gtk_entry_set_has_frame(GTK_ENTRY(cmdline_widget), FALSE); + gtk_editable_set_editable(GTK_EDITABLE(cmdline_widget), FALSE); + widget_set_font(cmdline_widget, "Courier"); + g_signal_connect(G_OBJECT(cmdline_widget), "key-press-event", + G_CALLBACK(cmdline_key_pressed), NULL); + gtk_box_pack_start(GTK_BOX(vbox), cmdline_widget, FALSE, FALSE, 0); + + gtk_container_add(GTK_CONTAINER(window), vbox); + + popup_widget = gtk_info_popup_new(cmdline_widget); + + gtk_widget_grab_focus(cmdline_widget); + + ssm(SCI_SETFOCUS, TRUE); + + cmdline_update(""); +} + +void +InterfaceGtk::vmsg(MessageType type, const gchar *fmt, va_list ap) +{ + static const GtkMessageType type2gtk[] = { + /* [MSG_USER] = */ GTK_MESSAGE_OTHER, + /* [MSG_INFO] = */ GTK_MESSAGE_INFO, + /* [MSG_WARNING] = */ GTK_MESSAGE_WARNING, + /* [MSG_ERROR] = */ GTK_MESSAGE_ERROR + }; + + va_list aq; + gchar buf[255]; + + va_copy(aq, ap); + stdio_vmsg(type, fmt, ap); + g_vsnprintf(buf, sizeof(buf), fmt, aq); + va_end(aq); + + gtk_info_bar_set_message_type(GTK_INFO_BAR(info_widget), + type2gtk[type]); + gtk_label_set_text(GTK_LABEL(message_widget), buf); +} + +void +InterfaceGtk::msg_clear(void) +{ + gtk_info_bar_set_message_type(GTK_INFO_BAR(info_widget), + GTK_MESSAGE_OTHER); + gtk_label_set_text(GTK_LABEL(message_widget), ""); +} + +void +InterfaceGtk::info_update(QRegister *reg) +{ + gchar buf[255]; + + g_snprintf(buf, sizeof(buf), "%s - %s", PACKAGE_NAME, + reg->name); + gtk_window_set_title(GTK_WINDOW(window), buf); +} + +void +InterfaceGtk::info_update(Buffer *buffer) +{ + gchar buf[255]; + + g_snprintf(buf, sizeof(buf), "%s - %s%s", PACKAGE_NAME, + buffer->filename ? : UNNAMED_FILE, + buffer->dirty ? "*" : ""); + gtk_window_set_title(GTK_WINDOW(window), buf); +} + +void +InterfaceGtk::cmdline_update(const gchar *cmdline) +{ + gint pos = 1; + + if (!cmdline) + /* widget automatically redrawn */ + return; + + gtk_entry_set_text(GTK_ENTRY(cmdline_widget), "*"); + gtk_editable_insert_text(GTK_EDITABLE(cmdline_widget), + cmdline, -1, &pos); + gtk_editable_set_position(GTK_EDITABLE(cmdline_widget), pos); +} + +void +InterfaceGtk::popup_add(PopupEntryType type, + const gchar *name, bool highlight) +{ + static const GtkInfoPopupEntryType type2gtk[] = { + /* [POPUP_PLAIN] = */ GTK_INFO_POPUP_PLAIN, + /* [POPUP_FILE] = */ GTK_INFO_POPUP_FILE, + /* [POPUP_DIRECTORY] = */ GTK_INFO_POPUP_DIRECTORY + }; + + gtk_info_popup_add(GTK_INFO_POPUP(popup_widget), + type2gtk[type], name, highlight); +} + +void +InterfaceGtk::popup_clear(void) +{ + if (gtk_widget_get_visible(popup_widget)) { + gtk_widget_hide(popup_widget); + gtk_info_popup_clear(GTK_INFO_POPUP(popup_widget)); + } +} + +void +InterfaceGtk::widget_set_font(GtkWidget *widget, const gchar *font_name) +{ + PangoFontDescription *font_desc; + + font_desc = pango_font_description_from_string(font_name); + gtk_widget_modify_font(widget, font_desc); + pango_font_description_free(font_desc); +} + +InterfaceGtk::~InterfaceGtk() +{ + gtk_widget_destroy(popup_widget); + gtk_widget_destroy(window); + + scintilla_release_resources(); +} + +/* + * GTK+ callbacks + */ + +static void +scintilla_notify(ScintillaObject *sci __attribute__((unused)), + uptr_t idFrom __attribute__((unused)), + SCNotification *notify, + gpointer user_data __attribute__((unused))) +{ + interface.process_notify(notify); +} + +static gboolean +cmdline_key_pressed(GtkWidget *widget, GdkEventKey *event, + gpointer user_data __attribute__((unused))) +{ +#ifdef DEBUG + g_printf("KEY \"%s\" (%d) SHIFT=%d CNTRL=%d\n", + event->string, *event->string, + event->state & GDK_SHIFT_MASK, event->state & GDK_CONTROL_MASK); +#endif + + switch (event->keyval) { + case GDK_BackSpace: + cmdline_keypress('\b'); + break; + case GDK_Tab: + cmdline_keypress('\t'); + break; + case GDK_Return: + switch (interface.ssm(SCI_GETEOLMODE)) { + case SC_EOL_CR: + cmdline_keypress('\r'); + break; + case SC_EOL_CRLF: + cmdline_keypress('\r'); + /* fall through */ + case SC_EOL_LF: + default: + cmdline_keypress('\n'); + } + break; + default: + if (*event->string) + cmdline_keypress(*event->string); + } + + return TRUE; +} + +static gboolean +exit_app(GtkWidget *w __attribute__((unused)), + GdkEventAny *e __attribute__((unused)), + gpointer p __attribute__((unused))) +{ + gtk_main_quit(); + return TRUE; +} diff --git a/src/interface-gtk.h b/src/interface-gtk.h new file mode 100644 index 0000000..b46f821 --- /dev/null +++ b/src/interface-gtk.h @@ -0,0 +1,73 @@ +#ifndef __INTERFACE_GTK_H +#define __INTERFACE_GTK_H + +#include + +#include +#include + +#include +#include + +#include "interface.h" + +extern class InterfaceGtk : public Interface { + GtkWidget *window; + GtkWidget *editor_widget; + GtkWidget *cmdline_widget; + GtkWidget *info_widget, *message_widget; + + GtkWidget *popup_widget; + +public: + InterfaceGtk(); + ~InterfaceGtk(); + + inline GOptionGroup * + get_options(void) + { + return gtk_get_option_group(TRUE); + } + inline void + parse_args(int &argc, char **&argv) + { + gtk_parse_args(&argc, &argv); + } + + void vmsg(MessageType type, const gchar *fmt, va_list ap); + void msg_clear(void); + + inline sptr_t + ssm(unsigned int iMessage, uptr_t wParam = 0, sptr_t lParam = 0) + { + return scintilla_send_message(SCINTILLA(editor_widget), + iMessage, wParam, lParam); + } + + void info_update(QRegister *reg); + void info_update(Buffer *buffer); + + void cmdline_update(const gchar *cmdline = NULL); + + void popup_add(PopupEntryType type, + const gchar *name, bool highlight = false); + inline void + popup_show(void) + { + gtk_widget_show(popup_widget); + } + void popup_clear(void); + + /* main entry point */ + inline void + event_loop(void) + { + gtk_widget_show_all(window); + gtk_main(); + } + +private: + static void widget_set_font(GtkWidget *widget, const gchar *font_name); +} interface; + +#endif diff --git a/src/interface-ncurses.cpp b/src/interface-ncurses.cpp new file mode 100644 index 0000000..0e08914 --- /dev/null +++ b/src/interface-ncurses.cpp @@ -0,0 +1,426 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include +/* only a hack until we have Autoconf checks */ +#if defined(__PDCURSES__) && CHTYPE_LONG >= 2 +#define PDCURSES_WIN32A +#endif + +#include +#include + +#include "sciteco.h" +#include "qregisters.h" +#include "ring.h" +#include "interface.h" +#include "interface-ncurses.h" + +InterfaceNCurses interface; + +extern "C" { +static void scintilla_notify(Scintilla *sci, int idFrom, + void *notify, void *user_data); +} + +#define UNNAMED_FILE "(Unnamed)" + +/* FIXME: should be configurable in TECO (Function key substitutes) */ +#define ESCAPE_SURROGATE KEY_DC + +#ifndef SCI_COLOR_PAIR +/* from ScintillaTerm.cxx */ +#define SCI_COLOR_PAIR(f, b) \ + ((b) * 8 + (f) + 1) +#endif + +#define SCI_COLOR_ATTR(f, b) \ + COLOR_PAIR(SCI_COLOR_PAIR(f, b)) + +InterfaceNCurses::InterfaceNCurses() +{ + init_screen(); + raw(); + cbreak(); + noecho(); + curs_set(0); /* Scintilla draws its own cursor */ + + setlocale(LC_CTYPE, ""); /* for displaying UTF-8 characters properly */ + + info_window = newwin(1, 0, 0, 0); + info_current = g_strdup(PACKAGE_NAME); + + /* NOTE: Scintilla initializes color pairs */ + sci = scintilla_new(scintilla_notify); + sci_window = scintilla_get_window(sci); + wresize(sci_window, LINES - 3, COLS); + mvwin(sci_window, 1, 0); + + msg_window = newwin(1, 0, LINES - 2, 0); + + cmdline_window = newwin(0, 0, LINES - 1, 0); + keypad(cmdline_window, TRUE); + cmdline_current = NULL; + + ssm(SCI_SETFOCUS, TRUE); + + draw_info(); + /* scintilla will be refreshed in event loop */ + msg_clear(); + cmdline_update(""); + +#ifndef PDCURSES_WIN32A + /* workaround: endwin() is somewhat broken in the win32a port */ + endwin(); +#endif +} + +#ifdef __PDCURSES__ + +void +InterfaceNCurses::init_screen(void) +{ +#ifdef PDCURSES_WIN32A + /* enables window resizing on Win32a port */ + PDC_set_resize_limits(25, 0xFFFF, 80, 0xFFFF); +#endif + + initscr(); + + screen_tty = NULL; + screen = NULL; +} + +#else + +void +InterfaceNCurses::init_screen(void) +{ + /* + * Prevent the initial redraw and any escape sequences that may + * interfere with stdout, so we may use the terminal in + * cooked mode, for commandline help and batch processing. + * Scintilla must be initialized for batch processing to work. + * (Frankly I have no idea why this works!) + */ + screen_tty = g_fopen("/dev/tty", "r+b"); + screen = newterm(NULL, screen_tty, screen_tty); + set_term(screen); +} + +#endif /* !__PDCURSES__ */ + +void +InterfaceNCurses::resize_all_windows(void) +{ + int lines, cols; /* screen dimensions */ + + getmaxyx(stdscr, lines, cols); + + wresize(info_window, 1, cols); + wresize(sci_window, lines - 3, cols); + wresize(msg_window, 1, cols); + mvwin(msg_window, lines - 2, 0); + wresize(cmdline_window, 1, cols); + mvwin(cmdline_window, lines - 1, 0); + + draw_info(); + /* scintilla will be refreshed in event loop */ + msg_clear(); /* FIXME: use saved message */ + cmdline_update(); +} + +void +InterfaceNCurses::vmsg(MessageType type, const gchar *fmt, va_list ap) +{ + static const chtype type2attr[] = { + SCI_COLOR_ATTR(COLOR_BLACK, COLOR_WHITE), /* MSG_USER */ + SCI_COLOR_ATTR(COLOR_BLACK, COLOR_GREEN), /* MSG_INFO */ + SCI_COLOR_ATTR(COLOR_BLACK, COLOR_YELLOW), /* MSG_WARNING */ + SCI_COLOR_ATTR(COLOR_BLACK, COLOR_RED) /* MSG_ERROR */ + }; + +#ifdef PDCURSES_WIN32A + stdio_vmsg(type, fmt, ap); + if (isendwin()) /* batch mode */ + return; +#else + if (isendwin()) { /* batch mode */ + stdio_vmsg(type, fmt, ap); + return; + } +#endif + + wmove(msg_window, 0, 0); + wbkgdset(msg_window, ' ' | type2attr[type]); + vw_printw(msg_window, fmt, ap); + wclrtoeol(msg_window); + + wrefresh(msg_window); +} + +void +InterfaceNCurses::msg_clear(void) +{ + if (isendwin()) /* batch mode */ + return; + + wmove(msg_window, 0, 0); + wbkgdset(msg_window, ' ' | SCI_COLOR_ATTR(COLOR_BLACK, COLOR_WHITE)); + wclrtoeol(msg_window); + + wrefresh(msg_window); +} + +void +InterfaceNCurses::draw_info(void) +{ + if (isendwin()) /* batch mode */ + return; + + wmove(info_window, 0, 0); + wbkgdset(info_window, ' ' | SCI_COLOR_ATTR(COLOR_BLACK, COLOR_WHITE)); + waddstr(info_window, info_current); + wclrtoeol(info_window); + + wrefresh(info_window); +} + +void +InterfaceNCurses::info_update(QRegister *reg) +{ + g_free(info_current); + info_current = g_strdup_printf("%s - %s", PACKAGE_NAME, + reg->name); + + draw_info(); +} + +void +InterfaceNCurses::info_update(Buffer *buffer) +{ + g_free(info_current); + info_current = g_strdup_printf("%s - %s%s", PACKAGE_NAME, + buffer->filename ? : UNNAMED_FILE, + buffer->dirty ? "*" : ""); + + draw_info(); +} + +void +InterfaceNCurses::cmdline_update(const gchar *cmdline) +{ + size_t len; + int half_line = (getmaxx(stdscr) - 2) / 2; + const gchar *line; + + if (cmdline) { + g_free(cmdline_current); + cmdline_current = g_strdup(cmdline); + } else { + cmdline = cmdline_current; + } + len = strlen(cmdline); + + /* FIXME: optimize */ + line = cmdline + len - MIN(len, half_line + len % half_line); + + mvwaddch(cmdline_window, 0, 0, '*'); + waddstr(cmdline_window, line); + waddch(cmdline_window, ' ' | A_REVERSE); + wclrtoeol(cmdline_window); + + wrefresh(cmdline_window); +} + +void +InterfaceNCurses::popup_add(PopupEntryType type __attribute__((unused)), + const gchar *name, bool highlight) +{ + gchar *entry; + + if (isendwin()) /* batch mode */ + return; + + entry = g_strconcat(highlight ? "*" : " ", name, NULL); + + popup.longest = MAX(popup.longest, (gint)strlen(name)); + popup.length++; + + popup.list = g_slist_prepend(popup.list, entry); +} + +void +InterfaceNCurses::popup_show(void) +{ + int lines, cols; /* screen dimensions */ + int popup_lines; + gint popup_cols; + gint cur_file, cur_line; + + if (isendwin()) /* batch mode */ + goto cleanup; + + getmaxyx(stdscr, lines, cols); + + popup.longest += 3; + popup.list = g_slist_reverse(popup.list); + + /* popup_cols = floor(cols / popup.longest) */ + popup_cols = MAX(cols / popup.longest, 1); + /* popup_lines = ceil(popup.length / popup_cols) */ + popup_lines = popup.length / popup_cols; + if ((popup.length % popup_cols)) + popup_lines++; + popup_lines = MIN(popup_lines, lines - 1); + + /* window covers message, scintilla and info windows */ + popup.window = newwin(popup_lines, 0, lines - 1 - popup_lines, 0); + wbkgdset(popup.window, ' ' | SCI_COLOR_ATTR(COLOR_BLACK, COLOR_BLUE)); + + cur_file = 0; + cur_line = 1; + for (GSList *cur = popup.list; cur; cur = g_slist_next(cur)) { + gchar *entry = (gchar *)cur->data; + + if (cur_file && !(cur_file % popup_cols)) { + wclrtoeol(popup.window); + waddch(popup.window, '\n'); + cur_line++; + } + + cur_file++; + + if (cur_line == popup_lines && !(cur_file % popup_cols) && + cur_file < popup.length) { + (void)wattrset(popup.window, A_BOLD); + waddstr(popup.window, "..."); + break; + } + + (void)wattrset(popup.window, *entry == '*' ? A_BOLD : A_NORMAL); + waddstr(popup.window, entry + 1); + for (int i = popup.longest - strlen(entry) + 1; i; i--) + waddch(popup.window, ' '); + + g_free(cur->data); + } + wclrtoeol(popup.window); + +cleanup: + g_slist_free(popup.list); + popup.list = NULL; + popup.longest = popup.length = 0; +} + +void +InterfaceNCurses::popup_clear(void) +{ + if (!popup.window) + return; + + redrawwin(info_window); + wrefresh(info_window); + redrawwin(sci_window); + scintilla_refresh(sci); + redrawwin(msg_window); + wrefresh(msg_window); + + delwin(popup.window); + popup.window = NULL; +} + +void +InterfaceNCurses::event_loop(void) +{ + /* in commandline (visual) mode, enforce redraw */ + wrefresh(curscr); + draw_info(); + + for (;;) { + int key; + + /* also handles initial refresh (styles are configured...) */ + scintilla_refresh(sci); + if (popup.window) + wrefresh(popup.window); + + key = wgetch(cmdline_window); + switch (key) { +#ifdef KEY_RESIZE + case ERR: + case KEY_RESIZE: +#ifdef PDCURSES + resize_term(0, 0); +#endif + resize_all_windows(); + break; +#endif + case ESCAPE_SURROGATE: + cmdline_keypress('\x1B'); + break; + case KEY_BACKSPACE: + cmdline_keypress('\b'); + break; + case KEY_ENTER: + case '\r': + switch (ssm(SCI_GETEOLMODE)) { + case SC_EOL_CR: + cmdline_keypress('\r'); + break; + case SC_EOL_CRLF: + cmdline_keypress('\r'); + /* fall through */ + case SC_EOL_LF: + default: + cmdline_keypress('\n'); + } + break; + default: + if (key <= 0xFF) + cmdline_keypress((gchar)key); + } + } +} + +InterfaceNCurses::Popup::~Popup() +{ + if (window) + delwin(window); + if (list) + g_slist_free(list); +} + +InterfaceNCurses::~InterfaceNCurses() +{ + delwin(info_window); + g_free(info_current); + /* also deletes curses window */ + scintilla_delete(sci); + delwin(cmdline_window); + g_free(cmdline_current); + delwin(msg_window); + + if (!isendwin()) + endwin(); + + delscreen(screen); + if (screen_tty) + fclose(screen_tty); +} + +/* + * Callbacks + */ + +static void +scintilla_notify(Scintilla *sci, int idFrom, void *notify, void *user_data) +{ + interface.process_notify((SCNotification *)notify); +} diff --git a/src/interface-ncurses.h b/src/interface-ncurses.h new file mode 100644 index 0000000..97269a4 --- /dev/null +++ b/src/interface-ncurses.h @@ -0,0 +1,69 @@ +#ifndef __INTERFACE_NCURSES_H +#define __INTERFACE_NCURSES_H + +#include + +#include + +#include + +#include +#include + +#include "interface.h" + +extern class InterfaceNCurses : public Interface { + SCREEN *screen; + FILE *screen_tty; + + Scintilla *sci; + + WINDOW *info_window; + gchar *info_current; + WINDOW *sci_window; + WINDOW *msg_window; + WINDOW *cmdline_window; + gchar *cmdline_current; + + struct Popup { + WINDOW *window; + GSList *list; + gint longest; + gint length; + + Popup() : window(NULL), list(NULL), longest(0), length(0) {} + ~Popup(); + } popup; + + void init_screen(void); + void resize_all_windows(void); + void draw_info(void); + +public: + InterfaceNCurses(); + ~InterfaceNCurses(); + + void vmsg(MessageType type, const gchar *fmt, va_list ap); + void msg_clear(void); + + inline sptr_t + ssm(unsigned int iMessage, uptr_t wParam = 0, sptr_t lParam = 0) + { + return scintilla_send_message(sci, iMessage, wParam, lParam); + } + + void info_update(QRegister *reg); + void info_update(Buffer *buffer); + + void cmdline_update(const gchar *cmdline = NULL); + + void popup_add(PopupEntryType type, + const gchar *name, bool highlight = false); + void popup_show(void); + void popup_clear(void); + + /* main entry point */ + void event_loop(void); +} interface; + +#endif diff --git a/src/interface.h b/src/interface.h new file mode 100644 index 0000000..35ee1f7 --- /dev/null +++ b/src/interface.h @@ -0,0 +1,112 @@ +#ifndef __INTERFACE_H +#define __INTERFACE_H + +#include + +#include + +#include + +#include "undo.h" + +/* avoid include dependency conflict */ +class QRegister; +class Buffer; + +/* + * Base class for all user interfaces - used mereley as a class interface. + * The actual instance of the interface has the platform-specific type + * (e.g. InterfaceGtk) since we would like to have the benefits of using + * classes but avoid the calling overhead when invoking virtual methods + * on Interface pointers. + * There's only one Interface* instance in the system. + */ +class Interface { + template + class UndoTokenInfoUpdate : public UndoToken { + Interface *iface; + Type *obj; + + public: + UndoTokenInfoUpdate(Interface *_iface, Type *_obj) + : iface(_iface), obj(_obj) {} + + void + run(void) + { + iface->info_update(obj); + } + }; + +public: + virtual GOptionGroup * + get_options(void) + { + return NULL; + } + virtual void parse_args(int &argc, char **&argv) {} + + enum MessageType { + MSG_USER, + MSG_INFO, + MSG_WARNING, + MSG_ERROR + }; + virtual void vmsg(MessageType type, const gchar *fmt, va_list ap) = 0; + inline void + msg(MessageType type, const gchar *fmt, ...) G_GNUC_PRINTF(3, 4) + { + va_list ap; + + va_start(ap, fmt); + vmsg(type, fmt, ap); + va_end(ap); + } + virtual void msg_clear(void) {} + + virtual sptr_t ssm(unsigned int iMessage, + uptr_t wParam = 0, sptr_t lParam = 0) = 0; + + virtual void info_update(QRegister *reg) = 0; + virtual void info_update(Buffer *buffer) = 0; + + template + inline void + undo_info_update(Type *obj) + { + undo.push(new UndoTokenInfoUpdate(this, obj)); + } + + /* NULL means to redraw the current cmdline if necessary */ + virtual void cmdline_update(const gchar *cmdline = NULL) = 0; + + enum PopupEntryType { + POPUP_PLAIN, + POPUP_FILE, + POPUP_DIRECTORY + }; + virtual void popup_add(PopupEntryType type, + const gchar *name, bool highlight = false) = 0; + virtual void popup_show(void) = 0; + virtual void popup_clear(void) = 0; + + /* main entry point */ + virtual void event_loop(void) = 0; + + /* + * Interfacing to the external SciTECO world + * See main.cpp + */ +protected: + void stdio_vmsg(MessageType type, const gchar *fmt, va_list ap); +public: + void process_notify(SCNotification *notify); +}; + +#ifdef INTERFACE_GTK +#include "interface-gtk.h" +#elif defined(INTERFACE_NCURSES) +#include "interface-ncurses.h" +#endif + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..4526da3 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,208 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "sciteco.h" +#include "interface.h" +#include "parser.h" +#include "goto.h" +#include "qregisters.h" +#include "ring.h" +#include "undo.h" + +#ifdef G_OS_UNIX +#define INI_FILE ".teco_ini" +#else +#define INI_FILE "teco.ini" +#endif + +namespace Flags { + gint64 ed = 0; +} + +static gchar *mung_file = NULL; + +void +Interface::stdio_vmsg(MessageType type, const gchar *fmt, va_list ap) +{ + gchar buf[255]; + + g_vsnprintf(buf, sizeof(buf), fmt, ap); + + switch (type) { + case MSG_USER: + g_printf("%s\n", buf); + break; + case MSG_INFO: + g_printf("Info: %s\n", buf); + break; + case MSG_WARNING: + g_fprintf(stderr, "Warning: %s\n", buf); + break; + case MSG_ERROR: + g_fprintf(stderr, "Error: %s\n", buf); + break; + } +} + +void +Interface::process_notify(SCNotification *notify) +{ +#ifdef DEBUG + g_printf("SCINTILLA NOTIFY: code=%d\n", notify->nmhdr.code); +#endif +} + +#ifdef G_OS_WIN32 + +/* + * keep program self-contained under Windows + * (look for profile in current directory) + */ +static inline gchar * +get_teco_ini(void) +{ + return g_strdup(INI_FILE); +} + +/* + * Windows sometimes sets the current working directory to very obscure + * paths when opening files in the Explorer, but we have to read the + * teco.ini from the directory where our binary resides. + */ +static inline void +fix_cwd(const gchar *program) +{ + gchar *bin_dir = g_path_get_dirname(program); + g_chdir(bin_dir); + g_free(bin_dir); +} + +#else + +static inline gchar * +get_teco_ini(void) +{ + const gchar *home; + +#ifdef G_OS_UNIX + home = g_get_home_dir(); +#else + home = g_get_user_config_dir(); +#endif + return g_build_filename(home, INI_FILE, NULL); +} + +static inline void fix_cwd(const gchar *program __attribute__((unused))) {} + +#endif /* !G_OS_WIN32 */ + +static inline void +process_options(int &argc, char **&argv) +{ + static const GOptionEntry option_entries[] = { + {"mung", 'm', 0, G_OPTION_ARG_FILENAME, &mung_file, + "Mung file instead of " INI_FILE, "filename"}, + {NULL} + }; + + GOptionContext *options; + GOptionGroup *interface_group = interface.get_options(); + + options = g_option_context_new("- " PACKAGE_STRING); + + g_option_context_add_main_entries(options, option_entries, NULL); + if (interface_group) + g_option_context_add_group(options, interface_group); + + if (!g_option_context_parse(options, &argc, &argv, NULL)) { + g_printf("Option parsing failed!\n"); + exit(EXIT_FAILURE); + } + + g_option_context_free(options); + + if (mung_file) { + if (!g_file_test(mung_file, G_FILE_TEST_IS_REGULAR)) { + g_printf("Cannot mung \"%s\". File does not exist!\n", + mung_file); + exit(EXIT_FAILURE); + } + } else { + mung_file = get_teco_ini(); + } + + interface.parse_args(argc, argv); + + /* remaining arguments, are arguments to the munged file */ +} + +int +main(int argc, char **argv) +{ + static GotoTable cmdline_goto_table; + static QRegisterTable local_qregs; + + fix_cwd(argv[0]); + + process_options(argc, argv); + + interface.ssm(SCI_SETCARETSTYLE, CARETSTYLE_BLOCK); + interface.ssm(SCI_SETCARETFORE, 0xFFFFFF); + + /* + * FIXME: Default styles should probably be set interface-based + * (system defaults) and be changeable by TECO macros + */ + interface.ssm(SCI_STYLESETFORE, STYLE_DEFAULT, 0xFFFFFF); + interface.ssm(SCI_STYLESETBACK, STYLE_DEFAULT, 0x000000); + interface.ssm(SCI_STYLESETFONT, STYLE_DEFAULT, (sptr_t)"Courier"); + interface.ssm(SCI_STYLECLEARALL); + + QRegisters::globals.initialize(); + /* search string and status register */ + QRegisters::globals.initialize("_"); + /* replacement string register */ + QRegisters::globals.initialize("-"); + /* current buffer name and number ("*") */ + QRegisters::globals.insert(new QRegisterBufferInfo()); + + local_qregs.initialize(); + QRegisters::locals = &local_qregs; + + ring.edit((const gchar *)NULL); + + /* add remaining arguments to unnamed buffer */ + for (int i = 1; i < argc; i++) { + interface.ssm(SCI_APPENDTEXT, strlen(argv[i]), (sptr_t)argv[i]); + interface.ssm(SCI_APPENDTEXT, 1, (sptr_t)"\n"); + } + + if (g_file_test(mung_file, G_FILE_TEST_IS_REGULAR)) { + if (!Execute::file(mung_file, false)) + exit(EXIT_FAILURE); + + /* FIXME: make quit immediate in batch/macro mode (non-UNDO)? */ + if (quit_requested) { + /* FIXME */ + exit(EXIT_SUCCESS); + } + } + g_free(mung_file); + + Goto::table = &cmdline_goto_table; + interface.ssm(SCI_EMPTYUNDOBUFFER); + undo.enabled = true; + + interface.event_loop(); + + return 0; +} diff --git a/src/parser.cpp b/src/parser.cpp new file mode 100644 index 0000000..5ba96fa --- /dev/null +++ b/src/parser.cpp @@ -0,0 +1,1436 @@ +#include +#include + +#include +#include +#include + +#include "sciteco.h" +#include "interface.h" +#include "undo.h" +#include "expressions.h" +#include "goto.h" +#include "qregisters.h" +#include "ring.h" +#include "parser.h" +#include "symbols.h" +#include "search.h" + +//#define DEBUG + +gint macro_pc = 0; + +namespace States { + StateStart start; + StateControl control; + StateFCommand fcommand; + StateCondCommand condcommand; + StateECommand ecommand; + StateScintilla_symbols scintilla_symbols; + StateScintilla_lParam scintilla_lparam; + StateInsert insert; + + State *current = &start; +} + +namespace Modifiers { + static bool colon = false; + static bool at = false; +} + +enum Mode mode = MODE_NORMAL; + +/* FIXME: perhaps integrate into Mode */ +static bool skip_else = false; + +static gint nest_level = 0; + +gchar *strings[2] = {NULL, NULL}; +gchar escape_char = '\x1B'; + +void +Execute::step(const gchar *macro) throw (State::Error) +{ + while (macro[macro_pc]) { +#ifdef DEBUG + g_printf("EXEC(%d): input='%c'/%x, state=%p, mode=%d\n", + macro_pc, macro[macro_pc], macro[macro_pc], + States::current, mode); +#endif + + State::input(macro[macro_pc]); + macro_pc++; + } +} + +void +Execute::macro(const gchar *macro, bool locals) throw (State::Error) +{ + GotoTable *parent_goto_table = Goto::table; + GotoTable macro_goto_table(false); + + QRegisterTable *parent_locals = QRegisters::locals; + QRegisterTable macro_locals(false); + + State *parent_state = States::current; + gint parent_pc = macro_pc; + + /* + * need this to fixup state on rubout: state machine emits undo token + * resetting state to parent's one, but the macro executed also emitted + * undo tokens resetting the state to StateStart + */ + undo.push_var(States::current) = &States::start; + macro_pc = 0; + + Goto::table = ¯o_goto_table; + if (locals) { + macro_locals.initialize(); + QRegisters::locals = ¯o_locals; + } + + try { + step(macro); + if (Goto::skip_label) + throw State::Error("Label \"%s\" not found", + Goto::skip_label); + } catch (...) { + g_free(Goto::skip_label); + Goto::skip_label = NULL; + + QRegisters::locals = parent_locals; + Goto::table = parent_goto_table; + + macro_pc = parent_pc; + States::current = parent_state; + + throw; /* forward */ + } + + QRegisters::locals = parent_locals; + Goto::table = parent_goto_table; + + macro_pc = parent_pc; + States::current = parent_state; +} + +bool +Execute::file(const gchar *filename, bool locals) +{ + gchar *macro_str, *p = NULL; + + if (!g_file_get_contents(filename, ¯o_str, NULL, NULL)) + return false; + /* only when executing files, ignore Hash-Bang line */ + if (*macro_str == '#') + p = MAX(strchr(macro_str, '\r'), strchr(macro_str, '\n')); + + try { + macro(p ? p+1 : macro_str, locals); + } catch (...) { + g_free(macro_str); + return false; + } + + g_free(macro_str); + return true; +} + +State::Error::Error(const gchar *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + interface.vmsg(Interface::MSG_ERROR, fmt, ap); + va_end(ap); +} + +State::State() +{ + for (guint i = 0; i < G_N_ELEMENTS(transitions); i++) + transitions[i] = NULL; +} + +bool +State::eval_colon(void) +{ + if (!Modifiers::colon) + return false; + + undo.push_var(Modifiers::colon); + Modifiers::colon = false; + return true; +} + +void +State::input(gchar chr) throw (Error) +{ + State *state = States::current; + + for (;;) { + State *next = state->get_next_state(chr); + + if (next == state) + break; + + state = next; + chr = '\0'; + } + + if (state != States::current) { + undo.push_var(States::current); + States::current = state; + } +} + +State * +State::get_next_state(gchar chr) throw (Error) +{ + State *next = NULL; + guint upper = g_ascii_toupper(chr); + + if (upper < G_N_ELEMENTS(transitions)) + next = transitions[upper]; + if (!next) + next = custom(chr); + if (!next) + throw SyntaxError(chr); + + return next; +} + +gchar * +StateExpectString::machine_input(gchar chr) throw (Error) +{ + switch (machine.mode) { + case Machine::MODE_UPPER: + chr = g_ascii_toupper(chr); + break; + case Machine::MODE_LOWER: + chr = g_ascii_tolower(chr); + break; + default: + break; + } + + if (machine.toctl) { + chr = CTL_KEY(g_ascii_toupper(chr)); + machine.toctl = false; + } + + if (machine.state == Machine::STATE_ESCAPED) { + machine.state = Machine::STATE_START; + goto original; + } + + if (chr == '^') { + machine.toctl = true; + return NULL; + } + + switch (machine.state) { + case Machine::STATE_START: + switch (chr) { + case CTL_KEY('Q'): + case CTL_KEY('R'): machine.state = Machine::STATE_ESCAPED; break; + case CTL_KEY('V'): machine.state = Machine::STATE_LOWER; break; + case CTL_KEY('W'): machine.state = Machine::STATE_UPPER; break; + case CTL_KEY('E'): machine.state = Machine::STATE_CTL_E; break; + default: + goto original; + } + break; + + case Machine::STATE_LOWER: + machine.state = Machine::STATE_START; + if (chr != CTL_KEY('V')) + return g_strdup((gchar []){g_ascii_tolower(chr), '\0'}); + machine.mode = Machine::MODE_LOWER; + break; + + case Machine::STATE_UPPER: + machine.state = Machine::STATE_START; + if (chr != CTL_KEY('W')) + return g_strdup((gchar []){g_ascii_toupper(chr), '\0'}); + machine.mode = Machine::MODE_UPPER; + break; + + case Machine::STATE_CTL_E: + switch (g_ascii_toupper(chr)) { + case 'Q': machine.state = Machine::STATE_CTL_EQ; break; + case 'U': machine.state = Machine::STATE_CTL_EU; break; + default: + machine.state = Machine::STATE_START; + return g_strdup((gchar []){CTL_KEY('E'), chr, '\0'}); + } + break; + + case Machine::STATE_CTL_EU: + if (chr == '.') { + machine.state = Machine::STATE_CTL_EU_LOCAL; + } else { + QRegister *reg; + + machine.state = Machine::STATE_START; + reg = QRegisters::globals[g_ascii_toupper(chr)]; + if (!reg) + throw InvalidQRegError(chr); + return g_strdup(CHR2STR(reg->get_integer())); + } + break; + + case Machine::STATE_CTL_EU_LOCAL: { + QRegister *reg; + + machine.state = Machine::STATE_START; + reg = (*QRegisters::locals)[g_ascii_toupper(chr)]; + if (!reg) + throw InvalidQRegError(chr, true); + return g_strdup(CHR2STR(reg->get_integer())); + } + + case Machine::STATE_CTL_EQ: + if (chr == '.') { + machine.state = Machine::STATE_CTL_EQ_LOCAL; + } else { + QRegister *reg; + + machine.state = Machine::STATE_START; + reg = QRegisters::globals[g_ascii_toupper(chr)]; + if (!reg) + throw InvalidQRegError(chr); + return reg->get_string(); + } + break; + + case Machine::STATE_CTL_EQ_LOCAL: { + QRegister *reg; + + machine.state = Machine::STATE_START; + reg = (*QRegisters::locals)[g_ascii_toupper(chr)]; + if (!reg) + throw InvalidQRegError(chr, true); + return reg->get_string(); + } + + default: + g_assert(TRUE); + } + + return NULL; + +original: + return g_strdup((gchar []){chr, '\0'}); +} + +State * +StateExpectString::custom(gchar chr) throw (Error) +{ + gchar *insert; + + if (chr == '\0') { + BEGIN_EXEC(this); + initial(); + return this; + } + + /* + * String termination handling + */ + if (Modifiers::at) { + if (last) + undo.push_var(Modifiers::at) = false; + + switch (escape_char) { + case '\x1B': + case '{': + undo.push_var(escape_char) = g_ascii_toupper(chr); + return this; + } + } + + if (escape_char == '{') { + switch (chr) { + case '{': + undo.push_var(nesting); + nesting++; + break; + case '}': + undo.push_var(nesting); + nesting--; + break; + } + } else if (g_ascii_toupper(chr) == escape_char) { + undo.push_var(nesting); + nesting--; + } + + if (!nesting) { + State *next; + gchar *string = strings[0]; + + undo.push_str(strings[0]) = NULL; + if (last) + undo.push_var(escape_char) = '\x1B'; + nesting = 1; + + if (string_building) { + undo.push_var(machine); + machine.state = Machine::STATE_START; + machine.mode = Machine::MODE_NORMAL; + machine.toctl = false; + } + + next = done(string ? : ""); + g_free(string); + return next; + } + + BEGIN_EXEC(this); + + /* + * String building characters + */ + if (string_building) { + undo.push_var(machine); + insert = machine_input(chr); + if (!insert) + return this; + } else { + insert = g_strdup((gchar []){chr, '\0'}); + } + + /* + * String accumulation + */ + undo.push_str(strings[0]); + String::append(strings[0], insert); + + process(strings[0], strlen(insert)); + g_free(insert); + return this; +} + +StateExpectQReg::StateExpectQReg() : State(), got_local(false) +{ + transitions['\0'] = this; +} + +State * +StateExpectQReg::custom(gchar chr) throw (Error) +{ + QRegister *reg; + + if (chr == '.') { + undo.push_var(got_local) = true; + return this; + } + chr = g_ascii_toupper(chr); + + if (got_local) { + undo.push_var(got_local) = false; + reg = (*QRegisters::locals)[chr]; + } else { + reg = QRegisters::globals[chr]; + } + if (!reg) + throw InvalidQRegError(chr, got_local); + + return got_register(reg); +} + +StateStart::StateStart() : State() +{ + transitions['\0'] = this; + init(" \f\r\n\v"); + + transitions['!'] = &States::label; + transitions['O'] = &States::gotocmd; + transitions['^'] = &States::control; + transitions['F'] = &States::fcommand; + transitions['"'] = &States::condcommand; + transitions['E'] = &States::ecommand; + transitions['I'] = &States::insert; + transitions['S'] = &States::search; + transitions['N'] = &States::searchall; + + transitions['['] = &States::pushqreg; + transitions[']'] = &States::popqreg; + transitions['G'] = &States::getqregstring; + transitions['Q'] = &States::getqreginteger; + transitions['U'] = &States::setqreginteger; + transitions['%'] = &States::increaseqreg; + transitions['M'] = &States::macro; + transitions['X'] = &States::copytoqreg; +} + +void +StateStart::insert_integer(gint64 v) +{ + gchar buf[64+1]; /* maximum length if radix = 2 */ + gchar *p = buf + sizeof(buf); + + *--p = '\0'; + interface.ssm(SCI_BEGINUNDOACTION); + if (v < 0) { + interface.ssm(SCI_ADDTEXT, 1, (sptr_t)"-"); + v *= -1; + } + do { + *--p = '0' + (v % expressions.radix); + if (*p > '9') + *p += 'A' - '9'; + } while ((v /= expressions.radix)); + interface.ssm(SCI_ADDTEXT, buf + sizeof(buf) - p - 1, + (sptr_t)p); + interface.ssm(SCI_SCROLLCARET); + interface.ssm(SCI_ENDUNDOACTION); + ring.dirtify(); + + undo.push_msg(SCI_UNDO); +} + +gint64 +StateStart::read_integer(void) +{ + uptr_t pos = interface.ssm(SCI_GETCURRENTPOS); + gchar c = (gchar)interface.ssm(SCI_GETCHARAT, pos); + gint64 v = 0; + gint sign = 1; + + if (c == '-') { + pos++; + sign = -1; + } + + for (;;) { + c = g_ascii_toupper((gchar)interface.ssm(SCI_GETCHARAT, pos)); + if (c >= '0' && c <= '0' + MIN(expressions.radix, 10) - 1) + v = (v*expressions.radix) + (c - '0'); + else if (c >= 'A' && + c <= 'A' + MIN(expressions.radix - 10, 26) - 1) + v = (v*expressions.radix) + 10 + (c - 'A'); + else + break; + + pos++; + } + + return sign * v; +} + +tecoBool +StateStart::move_chars(gint64 n) +{ + sptr_t pos = interface.ssm(SCI_GETCURRENTPOS); + + if (!Validate::pos(pos + n)) + return FAILURE; + + interface.ssm(SCI_GOTOPOS, pos + n); + undo.push_msg(SCI_GOTOPOS, pos); + return SUCCESS; +} + +tecoBool +StateStart::move_lines(gint64 n) +{ + sptr_t pos = interface.ssm(SCI_GETCURRENTPOS); + sptr_t line = interface.ssm(SCI_LINEFROMPOSITION, pos) + n; + + if (!Validate::line(line)) + return FAILURE; + + interface.ssm(SCI_GOTOLINE, line); + undo.push_msg(SCI_GOTOPOS, pos); + return SUCCESS; +} + +tecoBool +StateStart::delete_words(gint64 n) +{ + sptr_t pos, size; + + if (!n) + return SUCCESS; + + pos = interface.ssm(SCI_GETCURRENTPOS); + size = interface.ssm(SCI_GETLENGTH); + interface.ssm(SCI_BEGINUNDOACTION); + /* + * FIXME: would be nice to do this with constant amount of + * editor messages. E.g. by using custom algorithm accessing + * the internal document buffer. + */ + if (n > 0) { + while (n--) { + sptr_t size = interface.ssm(SCI_GETLENGTH); + interface.ssm(SCI_DELWORDRIGHTEND); + if (size == interface.ssm(SCI_GETLENGTH)) + break; + } + } else { + n *= -1; + while (n--) { + sptr_t pos = interface.ssm(SCI_GETCURRENTPOS); + //interface.ssm(SCI_DELWORDLEFTEND); + interface.ssm(SCI_WORDLEFTEND); + if (pos == interface.ssm(SCI_GETCURRENTPOS)) + break; + interface.ssm(SCI_DELWORDRIGHTEND); + } + } + interface.ssm(SCI_ENDUNDOACTION); + + if (n >= 0) { + if (size != interface.ssm(SCI_GETLENGTH)) { + interface.ssm(SCI_UNDO); + interface.ssm(SCI_GOTOPOS, pos); + } + return FAILURE; + } + + undo.push_msg(SCI_GOTOPOS, pos); + undo.push_msg(SCI_UNDO); + ring.dirtify(); + + return SUCCESS; +} + +State * +StateStart::custom(gchar chr) throw (Error) +{ + gint64 v; + tecoBool rc; + + /* + * commands implemented in StateCtrlCmd + */ + if (IS_CTL(chr)) + return States::control.get_next_state(CTL_ECHO(chr)); + + /* + * arithmetics + */ + if (g_ascii_isdigit(chr)) { + BEGIN_EXEC(this); + expressions.add_digit(chr); + return this; + } + + chr = g_ascii_toupper(chr); + switch (chr) { + case '/': + BEGIN_EXEC(this); + expressions.push_calc(Expressions::OP_DIV); + break; + + case '*': + BEGIN_EXEC(this); + expressions.push_calc(Expressions::OP_MUL); + break; + + case '+': + BEGIN_EXEC(this); + expressions.push_calc(Expressions::OP_ADD); + break; + + case '-': + BEGIN_EXEC(this); + if (!expressions.args()) + expressions.set_num_sign(-expressions.num_sign); + else + expressions.push_calc(Expressions::OP_SUB); + break; + + case '&': + BEGIN_EXEC(this); + expressions.push_calc(Expressions::OP_AND); + break; + + case '#': + BEGIN_EXEC(this); + expressions.push_calc(Expressions::OP_OR); + break; + + case '(': + BEGIN_EXEC(this); + if (expressions.num_sign < 0) { + expressions.set_num_sign(1); + expressions.eval(); + expressions.push(-1); + expressions.push_calc(Expressions::OP_MUL); + } + expressions.push(Expressions::OP_BRACE); + break; + + case ')': + BEGIN_EXEC(this); + expressions.eval(true); + break; + + case ',': + BEGIN_EXEC(this); + expressions.eval(); + expressions.push(Expressions::OP_NEW); + break; + + case '.': + BEGIN_EXEC(this); + expressions.eval(); + expressions.push(interface.ssm(SCI_GETCURRENTPOS)); + break; + + case 'Z': + BEGIN_EXEC(this); + expressions.eval(); + expressions.push(interface.ssm(SCI_GETLENGTH)); + break; + + case 'H': + BEGIN_EXEC(this); + expressions.eval(); + expressions.push(0); + expressions.push(interface.ssm(SCI_GETLENGTH)); + break; + + case '\\': + BEGIN_EXEC(this); + expressions.eval(); + if (expressions.args()) + insert_integer(expressions.pop_num_calc()); + else + expressions.push(read_integer()); + break; + + /* + * control structures (loops) + */ + case '<': + if (mode == MODE_PARSE_ONLY_LOOP) { + undo.push_var(nest_level); + nest_level++; + return this; + } + BEGIN_EXEC(this); + + expressions.eval(); + if (!expressions.args()) + /* infinite loop */ + expressions.push(-1); + + if (!expressions.peek_num()) { + expressions.pop_num(); + + /* skip to end of loop */ + undo.push_var(mode); + mode = MODE_PARSE_ONLY_LOOP; + } else { + expressions.push(macro_pc); + expressions.push(Expressions::OP_LOOP); + } + break; + + case '>': + if (mode == MODE_PARSE_ONLY_LOOP) { + if (!nest_level) { + undo.push_var(mode); + mode = MODE_NORMAL; + } else { + undo.push_var(nest_level); + nest_level--; + } + } else { + BEGIN_EXEC(this); + gint64 loop_pc, loop_cnt; + + expressions.discard_args(); + g_assert(expressions.pop_op() == Expressions::OP_LOOP); + loop_pc = expressions.pop_num(); + loop_cnt = expressions.pop_num(); + + if (loop_cnt != 1) { + /* repeat loop */ + macro_pc = loop_pc; + expressions.push(MAX(loop_cnt - 1, -1)); + expressions.push(loop_pc); + expressions.push(Expressions::OP_LOOP); + } + } + break; + + case ';': + BEGIN_EXEC(this); + + v = QRegisters::globals["_"]->get_integer(); + rc = expressions.pop_num_calc(1, v); + if (eval_colon()) + rc = ~rc; + + if (IS_FAILURE(rc)) { + expressions.discard_args(); + g_assert(expressions.pop_op() == Expressions::OP_LOOP); + expressions.pop_num(); /* pc */ + expressions.pop_num(); /* counter */ + + /* skip to end of loop */ + undo.push_var(mode); + mode = MODE_PARSE_ONLY_LOOP; + } + break; + + /* + * control structures (conditionals) + */ + case '|': + if (mode == MODE_PARSE_ONLY_COND) { + if (!skip_else && !nest_level) { + undo.push_var(mode); + mode = MODE_NORMAL; + } + return this; + } + BEGIN_EXEC(this); + + /* skip to end of conditional; skip ELSE-part */ + undo.push_var(mode); + mode = MODE_PARSE_ONLY_COND; + break; + + case '\'': + if (mode != MODE_PARSE_ONLY_COND) + break; + + if (!nest_level) { + undo.push_var(mode); + mode = MODE_NORMAL; + undo.push_var(skip_else); + skip_else = false; + } else { + undo.push_var(nest_level); + nest_level--; + } + break; + + /* + * modifiers + */ + case '@': + /* + * @ modifier has syntactic significance, so set it even + * in PARSE_ONLY* modes + */ + undo.push_var(Modifiers::at); + Modifiers::at = true; + break; + + case ':': + BEGIN_EXEC(this); + undo.push_var(Modifiers::colon); + Modifiers::colon = true; + break; + + /* + * commands + */ + case 'J': + BEGIN_EXEC(this); + v = expressions.pop_num_calc(1, 0); + if (Validate::pos(v)) { + undo.push_msg(SCI_GOTOPOS, + interface.ssm(SCI_GETCURRENTPOS)); + interface.ssm(SCI_GOTOPOS, v); + + if (eval_colon()) + expressions.push(SUCCESS); + } else if (eval_colon()) { + expressions.push(FAILURE); + } else { + throw MoveError("J"); + } + break; + + case 'C': + BEGIN_EXEC(this); + rc = move_chars(expressions.pop_num_calc()); + if (eval_colon()) + expressions.push(rc); + else if (IS_FAILURE(rc)) + throw MoveError("C"); + break; + + case 'R': + BEGIN_EXEC(this); + rc = move_chars(-expressions.pop_num_calc()); + if (eval_colon()) + expressions.push(rc); + else if (IS_FAILURE(rc)) + throw MoveError("R"); + break; + + case 'L': + BEGIN_EXEC(this); + rc = move_lines(expressions.pop_num_calc()); + if (eval_colon()) + expressions.push(rc); + else if (IS_FAILURE(rc)) + throw MoveError("L"); + break; + + case 'B': + BEGIN_EXEC(this); + rc = move_lines(-expressions.pop_num_calc()); + if (eval_colon()) + expressions.push(rc); + else if (IS_FAILURE(rc)) + throw MoveError("B"); + break; + + case 'W': { + sptr_t pos; + unsigned int msg = SCI_WORDRIGHTEND; + + BEGIN_EXEC(this); + v = expressions.pop_num_calc(); + + pos = interface.ssm(SCI_GETCURRENTPOS); + /* + * FIXME: would be nice to do this with constant amount of + * editor messages. E.g. by using custom algorithm accessing + * the internal document buffer. + */ + if (v < 0) { + v *= -1; + msg = SCI_WORDLEFTEND; + } + while (v--) { + sptr_t pos = interface.ssm(SCI_GETCURRENTPOS); + interface.ssm(msg); + if (pos == interface.ssm(SCI_GETCURRENTPOS)) + break; + } + if (v < 0) { + undo.push_msg(SCI_GOTOPOS, pos); + if (eval_colon()) + expressions.push(SUCCESS); + } else { + interface.ssm(SCI_GOTOPOS, pos); + if (eval_colon()) + expressions.push(FAILURE); + else + throw MoveError("W"); + } + break; + } + + case 'V': + BEGIN_EXEC(this); + rc = delete_words(expressions.pop_num_calc()); + if (eval_colon()) + expressions.push(rc); + else if (IS_FAILURE(rc)) + throw Error("Not enough words to delete with "); + break; + + case 'Y': + BEGIN_EXEC(this); + rc = delete_words(-expressions.pop_num_calc()); + if (eval_colon()) + expressions.push(rc); + else if (IS_FAILURE(rc)) + throw Error("Not enough words to delete with "); + break; + + case '=': + BEGIN_EXEC(this); + interface.msg(Interface::MSG_USER, "%" G_GINT64_FORMAT, + expressions.pop_num_calc()); + break; + + case 'K': + case 'D': { + gint64 from, len; + + BEGIN_EXEC(this); + expressions.eval(); + + if (expressions.args() <= 1) { + from = interface.ssm(SCI_GETCURRENTPOS); + if (chr == 'D') { + len = expressions.pop_num_calc(); + rc = TECO_BOOL(Validate::pos(from + len)); + } else /* chr == 'K' */ { + sptr_t line; + line = interface.ssm(SCI_LINEFROMPOSITION, from) + + expressions.pop_num_calc(); + len = interface.ssm(SCI_POSITIONFROMLINE, line) + - from; + rc = TECO_BOOL(Validate::line(line)); + } + if (len < 0) { + len *= -1; + from -= len; + } + } else { + gint64 to = expressions.pop_num(); + from = expressions.pop_num(); + len = to - from; + rc = TECO_BOOL(len >= 0 && Validate::pos(from) && + Validate::pos(to)); + } + + if (eval_colon()) + expressions.push(rc); + else if (IS_FAILURE(rc)) + throw RangeError(chr); + + if (len == 0 || IS_FAILURE(rc)) + break; + + undo.push_msg(SCI_GOTOPOS, interface.ssm(SCI_GETCURRENTPOS)); + undo.push_msg(SCI_UNDO); + + interface.ssm(SCI_BEGINUNDOACTION); + interface.ssm(SCI_DELETERANGE, from, len); + interface.ssm(SCI_ENDUNDOACTION); + ring.dirtify(); + break; + } + + case 'A': + BEGIN_EXEC(this); + v = interface.ssm(SCI_GETCURRENTPOS) + + expressions.pop_num_calc(); + if (!Validate::pos(v)) + throw RangeError("A"); + expressions.push(interface.ssm(SCI_GETCHARAT, v)); + break; + + default: + throw SyntaxError(chr); + } + + return this; +} + +StateFCommand::StateFCommand() : State() +{ + transitions['\0'] = this; + transitions['K'] = &States::searchkill; + transitions['D'] = &States::searchdelete; + transitions['S'] = &States::replace; + transitions['R'] = &States::replacedefault; +} + +State * +StateFCommand::custom(gchar chr) throw (Error) +{ + switch (chr) { + /* + * loop flow control + */ + case '<': + BEGIN_EXEC(&States::start); + /* FIXME: what if in brackets? */ + expressions.discard_args(); + if (expressions.peek_op() == Expressions::OP_LOOP) + /* repeat loop */ + macro_pc = expressions.peek_num(); + else + macro_pc = -1; + break; + + case '>': { + gint64 loop_pc, loop_cnt; + + BEGIN_EXEC(&States::start); + /* FIXME: what if in brackets? */ + expressions.discard_args(); + g_assert(expressions.pop_op() == Expressions::OP_LOOP); + loop_pc = expressions.pop_num(); + loop_cnt = expressions.pop_num(); + + if (loop_cnt != 1) { + /* repeat loop */ + macro_pc = loop_pc; + expressions.push(MAX(loop_cnt - 1, -1)); + expressions.push(loop_pc); + expressions.push(Expressions::OP_LOOP); + } else { + /* skip to end of loop */ + undo.push_var(mode); + mode = MODE_PARSE_ONLY_LOOP; + } + break; + } + + /* + * conditional flow control + */ + case '\'': + BEGIN_EXEC(&States::start); + /* skip to end of conditional */ + undo.push_var(mode); + mode = MODE_PARSE_ONLY_COND; + undo.push_var(skip_else); + skip_else = true; + break; + + case '|': + BEGIN_EXEC(&States::start); + /* skip to ELSE-part or end of conditional */ + undo.push_var(mode); + mode = MODE_PARSE_ONLY_COND; + break; + + default: + throw SyntaxError(chr); + } + + return &States::start; +} + +StateCondCommand::StateCondCommand() : State() +{ + transitions['\0'] = this; +} + +State * +StateCondCommand::custom(gchar chr) throw (Error) +{ + gint64 value = 0; + bool result; + + switch (mode) { + case MODE_PARSE_ONLY_COND: + undo.push_var(nest_level); + nest_level++; + break; + case MODE_NORMAL: + value = expressions.pop_num_calc(); + break; + default: + break; + } + + switch (g_ascii_toupper(chr)) { + case 'A': + BEGIN_EXEC(&States::start); + result = g_ascii_isalpha((gchar)value); + break; + case 'C': + BEGIN_EXEC(&States::start); + result = g_ascii_isalnum((gchar)value) || + value == '.' || value == '$' || value == '_'; + break; + case 'D': + BEGIN_EXEC(&States::start); + result = g_ascii_isdigit((gchar)value); + break; + case 'E': + case 'F': + case 'U': + case '=': + BEGIN_EXEC(&States::start); + result = value == 0; + break; + case 'G': + case '>': + BEGIN_EXEC(&States::start); + result = value > 0; + break; + case 'L': + case 'S': + case 'T': + case '<': + BEGIN_EXEC(&States::start); + result = value < 0; + break; + case 'N': + BEGIN_EXEC(&States::start); + result = value != 0; + break; + case 'R': + BEGIN_EXEC(&States::start); + result = g_ascii_isalnum((gchar)value); + break; + case 'V': + BEGIN_EXEC(&States::start); + result = g_ascii_islower((gchar)value); + break; + case 'W': + BEGIN_EXEC(&States::start); + result = g_ascii_isupper((gchar)value); + break; + default: + throw Error("Invalid conditional type \"%c\"", chr); + } + + if (!result) { + /* skip to ELSE-part or end of conditional */ + undo.push_var(mode); + mode = MODE_PARSE_ONLY_COND; + } + + return &States::start; +} + +StateControl::StateControl() : State() +{ + transitions['\0'] = this; + transitions['U'] = &States::ctlucommand; +} + +State * +StateControl::custom(gchar chr) throw (Error) +{ + switch (g_ascii_toupper(chr)) { + case 'O': + BEGIN_EXEC(&States::start); + expressions.set_radix(8); + break; + + case 'D': + BEGIN_EXEC(&States::start); + expressions.set_radix(10); + break; + + case 'R': + BEGIN_EXEC(&States::start); + expressions.eval(); + if (!expressions.args()) + expressions.push(expressions.radix); + else + expressions.set_radix(expressions.pop_num_calc()); + break; + + /* + * Alternatives: ^i, ^I, , + */ + case 'I': + BEGIN_EXEC(&States::insert); + expressions.eval(); + expressions.push('\t'); + return &States::insert; + + /* + * Alternatives: ^[, , + */ + case '[': + BEGIN_EXEC(&States::start); + expressions.discard_args(); + break; + + /* + * Additional numeric operations + */ + case '_': + BEGIN_EXEC(&States::start); + expressions.push(~expressions.pop_num_calc()); + break; + + case '*': + BEGIN_EXEC(&States::start); + expressions.push_calc(Expressions::OP_POW); + break; + + case '/': + BEGIN_EXEC(&States::start); + expressions.push_calc(Expressions::OP_MOD); + break; + + default: + throw Error("Unsupported command <^%c>", chr); + } + + return &States::start; +} + +StateECommand::StateECommand() : State() +{ + transitions['\0'] = this; + transitions['B'] = &States::editfile; + transitions['S'] = &States::scintilla_symbols; + transitions['Q'] = &States::eqcommand; + transitions['W'] = &States::savefile; +} + +State * +StateECommand::custom(gchar chr) throw (Error) +{ + switch (g_ascii_toupper(chr)) { + case 'F': + BEGIN_EXEC(&States::start); + if (!ring.current) + throw Error("No buffer selected"); + + if (IS_FAILURE(expressions.pop_num_calc()) && + ring.current->dirty) + throw Error("Buffer \"%s\" is dirty", + ring.current->filename ? : "(Unnamed)"); + + ring.close(); + break; + + case 'D': + BEGIN_EXEC(&States::start); + expressions.eval(); + if (!expressions.args()) { + expressions.push(Flags::ed); + } else { + gint64 on = expressions.pop_num_calc(); + gint64 off = expressions.pop_num_calc(1, ~(gint64)0); + + undo.push_var(Flags::ed); + Flags::ed = (Flags::ed & ~off) | on; + } + break; + + case 'X': + BEGIN_EXEC(&States::start); + + if (IS_FAILURE(expressions.pop_num_calc()) && + ring.is_any_dirty()) + throw Error("Modified buffers exist"); + + undo.push_var(quit_requested); + quit_requested = true; + break; + + default: + throw SyntaxError(chr); + } + + return &States::start; +} + +static struct ScintillaMessage { + unsigned int iMessage; + uptr_t wParam; + sptr_t lParam; +} scintilla_message = {0, 0, 0}; + +State * +StateScintilla_symbols::done(const gchar *str) throw (Error) +{ + BEGIN_EXEC(&States::scintilla_lparam); + + undo.push_var(scintilla_message); + if (*str) { + gchar **symbols = g_strsplit(str, ",", -1); + gint64 v; + + if (!symbols[0]) + goto cleanup; + if (*symbols[0]) { + v = Symbols::scintilla.lookup(symbols[0], "SCI_"); + if (v < 0) + throw Error("Unknown Scintilla message symbol \"%s\"", + symbols[0]); + scintilla_message.iMessage = v; + } + + if (!symbols[1]) + goto cleanup; + if (*symbols[1]) { + v = Symbols::scilexer.lookup(symbols[1]); + if (v < 0) + throw Error("Unknown Scintilla Lexer symbol \"%s\"", + symbols[1]); + scintilla_message.wParam = v; + } + + if (!symbols[2]) + goto cleanup; + if (*symbols[2]) { + v = Symbols::scilexer.lookup(symbols[2]); + if (v < 0) + throw Error("Unknown Scintilla Lexer symbol \"%s\"", + symbols[2]); + scintilla_message.lParam = v; + } + +cleanup: + g_strfreev(symbols); + } + + expressions.eval(); + if (!scintilla_message.iMessage) { + if (!expressions.args()) + throw Error(" command requires at least a message code"); + + scintilla_message.iMessage = expressions.pop_num_calc(1, 0); + } + if (!scintilla_message.wParam) + scintilla_message.wParam = expressions.pop_num_calc(1, 0); + + return &States::scintilla_lparam; +} + +State * +StateScintilla_lParam::done(const gchar *str) throw (Error) +{ + BEGIN_EXEC(&States::start); + + if (!scintilla_message.lParam) + scintilla_message.lParam = *str ? (sptr_t)str + : expressions.pop_num_calc(1, 0); + + expressions.push(interface.ssm(scintilla_message.iMessage, + scintilla_message.wParam, + scintilla_message.lParam)); + + undo.push_var(scintilla_message); + memset(&scintilla_message, 0, sizeof(scintilla_message)); + + return &States::start; +} + +/* + * NOTE: cannot support VideoTECO's I because + * beginning and end of strings must be determined + * syntactically + */ +void +StateInsert::initial(void) throw (Error) +{ + int args; + + expressions.eval(); + args = expressions.args(); + if (!args) + return; + + interface.ssm(SCI_BEGINUNDOACTION); + for (int i = args; i > 0; i--) { + gchar chr = (gchar)expressions.peek_num(i); + interface.ssm(SCI_ADDTEXT, 1, (sptr_t)&chr); + } + for (int i = args; i > 0; i--) + expressions.pop_num_calc(); + interface.ssm(SCI_SCROLLCARET); + interface.ssm(SCI_ENDUNDOACTION); + ring.dirtify(); + + undo.push_msg(SCI_UNDO); +} + +void +StateInsert::process(const gchar *str, gint new_chars) throw (Error) +{ + interface.ssm(SCI_BEGINUNDOACTION); + interface.ssm(SCI_ADDTEXT, new_chars, + (sptr_t)(str + strlen(str) - new_chars)); + interface.ssm(SCI_SCROLLCARET); + interface.ssm(SCI_ENDUNDOACTION); + ring.dirtify(); + + undo.push_msg(SCI_UNDO); +} + +State * +StateInsert::done(const gchar *str __attribute__((unused))) throw (Error) +{ + /* nothing to be done when done */ + return &States::start; +} diff --git a/src/parser.h b/src/parser.h new file mode 100644 index 0000000..4ae9db6 --- /dev/null +++ b/src/parser.h @@ -0,0 +1,266 @@ +#ifndef __PARSER_H +#define __PARSER_H + +#include +#include + +#include "sciteco.h" + +/* TECO uses only lower 7 bits for commands */ +#define MAX_TRANSITIONS 127 + +class State { +public: + class Error { + public: + Error(const gchar *fmt, ...); + }; + + class SyntaxError : public Error { + public: + SyntaxError(gchar chr) + : Error("Syntax error \"%c\" (%d)", chr, chr) {} + }; + + class MoveError : public Error { + public: + MoveError(const gchar *cmd) + : Error("Attempt to move pointer off page with <%s>", + cmd) {} + MoveError(gchar cmd) + : Error("Attempt to move pointer off page with <%c>", + cmd) {} + }; + + class RangeError : public Error { + public: + RangeError(const gchar *cmd) + : Error("Invalid range specified for <%s>", cmd) {} + RangeError(gchar cmd) + : Error("Invalid range specified for <%c>", cmd) {} + }; + + class InvalidQRegError : public Error { + public: + InvalidQRegError(const gchar *name, bool local = false) + : Error("Invalid Q-Register \"%s%s\"", + local ? "." : "", name) {} + InvalidQRegError(gchar name, bool local = false) + : Error("Invalid Q-Register \"%s%c\"", + local ? "." : "", name) {} + }; + +protected: + /* static transitions */ + State *transitions[MAX_TRANSITIONS]; + + inline void + init(const gchar *chars, State &state) + { + while (*chars) + transitions[(int)*chars++] = &state; + } + inline void + init(const gchar *chars) + { + init(chars, *this); + } + +public: + State(); + + static void input(gchar chr) throw (Error); + State *get_next_state(gchar chr) throw (Error); + +protected: + static bool eval_colon(void); + + virtual State * + custom(gchar chr) throw (Error) + { + throw SyntaxError(chr); + return NULL; + } +}; + +/* + * Super-class for states accepting string arguments + * Opaquely cares about alternative-escape characters, + * string building commands and accumulation into a string + */ +class StateExpectString : public State { + struct Machine { + enum State { + STATE_START, + STATE_ESCAPED, + STATE_LOWER, + STATE_UPPER, + STATE_CTL_E, + STATE_CTL_EQ, + STATE_CTL_EQ_LOCAL, + STATE_CTL_EU, + STATE_CTL_EU_LOCAL + } state; + + enum Mode { + MODE_NORMAL, + MODE_UPPER, + MODE_LOWER + } mode; + + bool toctl; + + Machine() : state(STATE_START), + mode(MODE_NORMAL), toctl(false) {} + } machine; + + gint nesting; + + bool string_building; + bool last; + +public: + StateExpectString(bool _building = true, bool _last = true) + : State(), nesting(1), + string_building(_building), last(_last) {} + +private: + gchar *machine_input(gchar key) throw (Error); + State *custom(gchar chr) throw (Error); + +protected: + virtual void initial(void) throw (Error) {} + virtual void process(const gchar *str, gint new_chars) throw (Error) {} + virtual State *done(const gchar *str) throw (Error) = 0; +}; + +class QRegister; + +/* + * Super class for states accepting Q-Register specifications + */ +class StateExpectQReg : public State { + bool got_local; + +public: + StateExpectQReg(); + +private: + State *custom(gchar chr) throw (Error); + +protected: + /* + * FIXME: would be nice to pass reg as reference, but there are + * circular header dependencies... + */ + virtual State *got_register(QRegister *reg) throw (Error) = 0; +}; + +class StateStart : public State { +public: + StateStart(); + +private: + void insert_integer(gint64 v); + gint64 read_integer(void); + + tecoBool move_chars(gint64 n); + tecoBool move_lines(gint64 n); + + tecoBool delete_words(gint64 n); + + State *custom(gchar chr) throw (Error); +}; + +class StateControl : public State { +public: + StateControl(); + +private: + State *custom(gchar chr) throw (Error); +}; + +class StateFCommand : public State { +public: + StateFCommand(); + +private: + State *custom(gchar chr) throw (Error); +}; + +class StateCondCommand : public State { +public: + StateCondCommand(); + +private: + State *custom(gchar chr) throw (Error); +}; + +class StateECommand : public State { +public: + StateECommand(); + +private: + State *custom(gchar chr) throw (Error); +}; + +class StateScintilla_symbols : public StateExpectString { +public: + StateScintilla_symbols() : StateExpectString(true, false) {} + +private: + State *done(const gchar *str) throw (Error); +}; + +class StateScintilla_lParam : public StateExpectString { +private: + State *done(const gchar *str) throw (Error); +}; + +/* + * also serves as base class for replace-insertion states + */ +class StateInsert : public StateExpectString { +protected: + void initial(void) throw (Error); + void process(const gchar *str, gint new_chars) throw (Error); + State *done(const gchar *str) throw (Error); +}; + +namespace States { + extern StateStart start; + extern StateControl control; + extern StateFCommand fcommand; + extern StateCondCommand condcommand; + extern StateECommand ecommand; + extern StateScintilla_symbols scintilla_symbols; + extern StateScintilla_lParam scintilla_lparam; + extern StateInsert insert; + + extern State *current; +} + +extern enum Mode { + MODE_NORMAL = 0, + MODE_PARSE_ONLY_GOTO, + MODE_PARSE_ONLY_LOOP, + MODE_PARSE_ONLY_COND +} mode; + +#define BEGIN_EXEC(STATE) G_STMT_START { \ + if (mode > MODE_NORMAL) \ + return STATE; \ +} G_STMT_END + +extern gint macro_pc; + +extern gchar *strings[2]; +extern gchar escape_char; + +namespace Execute { + void step(const gchar *macro) throw (State::Error); + void macro(const gchar *macro, bool locals = true) throw (State::Error); + bool file(const gchar *filename, bool locals = true); +} + +#endif diff --git a/src/qregisters.cpp b/src/qregisters.cpp new file mode 100644 index 0000000..06947f7 --- /dev/null +++ b/src/qregisters.cpp @@ -0,0 +1,522 @@ +#include +#include + +#include +#include +#include + +#include + +#include "sciteco.h" +#include "interface.h" +#include "undo.h" +#include "parser.h" +#include "expressions.h" +#include "ring.h" +#include "qregisters.h" + +namespace States { + StatePushQReg pushqreg; + StatePopQReg popqreg; + StateEQCommand eqcommand; + StateLoadQReg loadqreg; + StateCtlUCommand ctlucommand; + StateSetQRegString setqregstring; + StateGetQRegString getqregstring; + StateGetQRegInteger getqreginteger; + StateSetQRegInteger setqreginteger; + StateIncreaseQReg increaseqreg; + StateMacro macro; + StateCopyToQReg copytoqreg; +} + +namespace QRegisters { + QRegisterTable globals; + QRegisterTable *locals = NULL; + QRegister *current = NULL; + + static QRegisterStack stack; +} + +static QRegister *register_argument = NULL; + +static inline void +current_edit(void) +{ + if (ring.current) + ring.current->edit(); + else if (QRegisters::current) + QRegisters::current->edit(); +} + +void +QRegisterData::set_string(const gchar *str) +{ + edit(); + dot = 0; + + interface.ssm(SCI_BEGINUNDOACTION); + interface.ssm(SCI_SETTEXT, 0, (sptr_t)(str ? : "")); + interface.ssm(SCI_ENDUNDOACTION); + + current_edit(); +} + +void +QRegisterData::undo_set_string(void) +{ + /* set_string() assumes that dot has been saved */ + current_save_dot(); + + if (!must_undo) + return; + + if (ring.current) + ring.current->undo_edit(); + else if (QRegisters::current) + QRegisters::current->undo_edit(); + + undo.push_var(dot); + undo.push_msg(SCI_UNDO); + + undo_edit(); +} + +void +QRegisterData::append_string(const gchar *str) +{ + if (!str) + return; + + edit(); + + interface.ssm(SCI_BEGINUNDOACTION); + interface.ssm(SCI_APPENDTEXT, strlen(str), (sptr_t)str); + interface.ssm(SCI_ENDUNDOACTION); + + current_edit(); +} + +gchar * +QRegisterData::get_string(void) +{ + gint size; + gchar *str; + + current_save_dot(); + edit(); + + size = interface.ssm(SCI_GETLENGTH) + 1; + str = (gchar *)g_malloc(size); + interface.ssm(SCI_GETTEXT, size, (sptr_t)str); + + current_edit(); + + return str; +} + +void +QRegisterData::edit(void) +{ + interface.ssm(SCI_SETDOCPOINTER, 0, (sptr_t)get_document()); + interface.ssm(SCI_GOTOPOS, dot); +} + +void +QRegisterData::undo_edit(void) +{ + if (!must_undo) + return; + + undo.push_msg(SCI_GOTOPOS, dot); + undo.push_msg(SCI_SETDOCPOINTER, 0, (sptr_t)get_document()); +} + +void +QRegister::edit(void) +{ + QRegisterData::edit(); + interface.info_update(this); +} + +void +QRegister::undo_edit(void) +{ + if (!must_undo) + return; + + interface.undo_info_update(this); + QRegisterData::undo_edit(); +} + +void +QRegister::execute(bool locals) throw (State::Error) +{ + gchar *str = get_string(); + + try { + Execute::macro(str, locals); + } catch (...) { + g_free(str); + throw; /* forward */ + } + + g_free(str); +} + +bool +QRegister::load(const gchar *filename) +{ + gchar *contents; + gsize size; + + /* FIXME: prevent excessive allocations by reading file into buffer */ + if (!g_file_get_contents(filename, &contents, &size, NULL)) + return false; + + edit(); + dot = 0; + + interface.ssm(SCI_BEGINUNDOACTION); + interface.ssm(SCI_CLEARALL); + interface.ssm(SCI_APPENDTEXT, size, (sptr_t)contents); + interface.ssm(SCI_ENDUNDOACTION); + + g_free(contents); + + current_edit(); + + return true; +} + +gint64 +QRegisterBufferInfo::get_integer(void) +{ + gint64 id = 1; + + if (!ring.current) + return 0; + + for (Buffer *buffer = ring.first(); + buffer != ring.current; + buffer = buffer->next()) + id++; + + return id; +} + +gchar * +QRegisterBufferInfo::get_string(void) +{ + gchar *filename = ring.current ? ring.current->filename : NULL; + + return g_strdup(filename ? : ""); +} + +void +QRegisterBufferInfo::edit(void) +{ + gchar *filename = ring.current ? ring.current->filename : NULL; + + QRegister::edit(); + + interface.ssm(SCI_BEGINUNDOACTION); + interface.ssm(SCI_SETTEXT, 0, (sptr_t)(filename ? : "")); + interface.ssm(SCI_ENDUNDOACTION); + + undo.push_msg(SCI_UNDO); +} + +void +QRegisterTable::initialize(void) +{ + /* general purpose registers */ + for (gchar q = 'A'; q <= 'Z'; q++) + initialize(q); + for (gchar q = '0'; q <= '9'; q++) + initialize(q); +} + +void +QRegisterTable::edit(QRegister *reg) +{ + current_save_dot(); + reg->edit(); + + ring.current = NULL; + QRegisters::current = reg; +} + +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 *reg) +{ + Entry *entry = new Entry(); + + entry->set_integer(reg->get_integer()); + if (reg->string) { + gchar *str = reg->get_string(); + entry->set_string(str); + g_free(str); + } + entry->dot = reg->dot; + + SLIST_INSERT_HEAD(&head, entry, entries); + undo.push(new UndoTokenPop(this)); +} + +bool +QRegisterStack::pop(QRegister *reg) +{ + Entry *entry = SLIST_FIRST(&head); + QRegisterData::document *string; + + if (!entry) + return false; + + reg->undo_set_integer(); + reg->set_integer(entry->get_integer()); + + /* exchange document ownership between Stack entry and Q-Register */ + string = reg->string; + if (reg->must_undo) + undo.push_var(reg->string); + reg->string = entry->string; + undo.push_var(entry->string); + entry->string = string; + + if (reg->must_undo) + undo.push_var(reg->dot); + reg->dot = entry->dot; + + SLIST_REMOVE_HEAD(&head, entries); + /* pass entry ownership to undo stack */ + undo.push(new UndoTokenPush(this, entry)); + + return true; +} + +QRegisterStack::~QRegisterStack() +{ + Entry *entry, *next; + + SLIST_FOREACH_SAFE(entry, &head, entries, next) + delete entry; +} + +void +QRegisters::hook(Hook type) +{ + if (!(Flags::ed & Flags::ED_HOOKS)) + return; + + expressions.push(type); + globals["0"]->execute(); +} + +/* + * Command states + */ + +State * +StatePushQReg::got_register(QRegister *reg) throw (Error) +{ + BEGIN_EXEC(&States::start); + + QRegisters::stack.push(reg); + + return &States::start; +} + +State * +StatePopQReg::got_register(QRegister *reg) throw (Error) +{ + BEGIN_EXEC(&States::start); + + if (!QRegisters::stack.pop(reg)) + throw Error("Q-Register stack is empty"); + + return &States::start; +} + +State * +StateEQCommand::got_register(QRegister *reg) throw (Error) +{ + BEGIN_EXEC(&States::loadqreg); + register_argument = reg; + return &States::loadqreg; +} + +State * +StateLoadQReg::done(const gchar *str) throw (Error) +{ + BEGIN_EXEC(&States::start); + + if (*str) { + register_argument->undo_load(); + if (!register_argument->load(str)) + throw Error("Cannot load \"%s\" into Q-Register \"%s\"", + str, register_argument->name); + } else { + if (ring.current) + ring.undo_edit(); + else /* QRegisters::current != NULL */ + QRegisters::undo_edit(); + QRegisters::globals.edit(register_argument); + } + + return &States::start; +} + +State * +StateCtlUCommand::got_register(QRegister *reg) throw (Error) +{ + BEGIN_EXEC(&States::setqregstring); + register_argument = reg; + return &States::setqregstring; +} + +State * +StateSetQRegString::done(const gchar *str) throw (Error) +{ + BEGIN_EXEC(&States::start); + + register_argument->undo_set_string(); + register_argument->set_string(str); + + return &States::start; +} + +State * +StateGetQRegString::got_register(QRegister *reg) throw (Error) +{ + 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(); + + undo.push_msg(SCI_UNDO); + } + g_free(str); + + return &States::start; +} + +State * +StateGetQRegInteger::got_register(QRegister *reg) throw (Error) +{ + BEGIN_EXEC(&States::start); + + expressions.eval(); + expressions.push(reg->get_integer()); + + return &States::start; +} + +State * +StateSetQRegInteger::got_register(QRegister *reg) throw (Error) +{ + BEGIN_EXEC(&States::start); + + reg->undo_set_integer(); + reg->set_integer(expressions.pop_num_calc()); + + return &States::start; +} + +State * +StateIncreaseQReg::got_register(QRegister *reg) throw (Error) +{ + gint64 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; +} + +State * +StateMacro::got_register(QRegister *reg) throw (Error) +{ + BEGIN_EXEC(&States::start); + + /* don't create new local Q-Registers if colon modifier is given */ + reg->execute(!eval_colon()); + + return &States::start; +} + +State * +StateCopyToQReg::got_register(QRegister *reg) throw (Error) +{ + gint64 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 { + gint64 to = expressions.pop_num(); + from = expressions.pop_num(); + + if (!Validate::pos(from) || !Validate::pos(to)) + throw RangeError("X"); + + len = to - from; + } + + 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; +} diff --git a/src/qregisters.h b/src/qregisters.h new file mode 100644 index 0000000..e810b7f --- /dev/null +++ b/src/qregisters.h @@ -0,0 +1,343 @@ +#ifndef __QREGISTERS_H +#define __QREGISTERS_H + +#include + +#include +#include + +#include + +#include "sciteco.h" +#include "interface.h" +#include "undo.h" +#include "rbtree.h" +#include "parser.h" + +/* + * Classes + */ + +class QRegisterData { + gint64 integer; + +public: + typedef void document; + document *string; + gint dot; + + /* + * whether to generate UndoTokens (unnecessary in macro invocations) + */ + bool must_undo; + + QRegisterData() : integer(0), string(NULL), dot(0), must_undo(true) {} + virtual + ~QRegisterData() + { + if (string) + interface.ssm(SCI_RELEASEDOCUMENT, 0, (sptr_t)string); + } + + inline document * + get_document(void) + { + if (!string) + string = (document *)interface.ssm(SCI_CREATEDOCUMENT); + return string; + } + + virtual gint64 + set_integer(gint64 i) + { + return integer = i; + } + virtual void + undo_set_integer(void) + { + if (must_undo) + undo.push_var(integer); + } + virtual gint64 + get_integer(void) + { + return integer; + } + + virtual void set_string(const gchar *str); + virtual void undo_set_string(void); + virtual void append_string(const gchar *str); + virtual inline void + undo_append_string(void) + { + undo_set_string(); + } + virtual gchar *get_string(void); + + virtual void edit(void); + virtual void undo_edit(void); +}; + +class QRegister : public RBTree::RBEntry, public QRegisterData { +public: + gchar *name; + + QRegister(const gchar *_name) + : QRegisterData(), name(g_strdup(_name)) {} + virtual + ~QRegister() + { + g_free(name); + } + + int + operator <(RBEntry &entry) + { + return g_strcmp0(name, ((QRegister &)entry).name); + } + + virtual void edit(void); + virtual void undo_edit(void); + + void execute(bool locals = true) throw (State::Error); + + bool load(const gchar *filename); + inline void + undo_load(void) + { + undo_set_string(); + } +}; + +class QRegisterBufferInfo : public QRegister { +public: + QRegisterBufferInfo() : QRegister("*") + { + get_document(); + } + + gint64 + set_integer(gint64 v) + { + return v; + } + void undo_set_integer(void) {} + + gint64 get_integer(void); + + void set_string(const gchar *str) {} + void undo_set_string(void) {} + void append_string(const gchar *str) {} + void undo_append_string(void) {} + + gchar *get_string(void); + + void edit(void); +}; + +class QRegisterTable : public RBTree { + bool must_undo; + +public: + QRegisterTable(bool _undo = true) : RBTree(), must_undo(_undo) {} + + inline QRegister * + insert(QRegister *reg) + { + reg->must_undo = must_undo; + RBTree::insert(reg); + return reg; + } + + inline void + initialize(const gchar *name) + { + QRegister *reg = new QRegister(name); + insert(reg); + /* make sure document is initialized */ + reg->get_document(); + } + inline void + initialize(gchar name) + { + initialize((gchar []){name, '\0'}); + } + void initialize(void); + + inline QRegister * + operator [](const gchar *name) + { + QRegister reg(name); + return (QRegister *)find(®); + } + inline QRegister * + operator [](gchar chr) + { + return operator []((gchar []){chr, '\0'}); + } + + void edit(QRegister *reg); + inline QRegister * + edit(const gchar *name) + { + QRegister *reg = operator [](name); + + if (!reg) + return NULL; + edit(reg); + return reg; + } +}; + +class QRegisterStack { + class Entry : public QRegisterData { + public: + SLIST_ENTRY(Entry) entries; + + Entry() : QRegisterData() {} + }; + + class UndoTokenPush : public UndoToken { + QRegisterStack *stack; + /* only remaining reference to stack entry */ + Entry *entry; + + public: + UndoTokenPush(QRegisterStack *_stack, Entry *_entry) + : UndoToken(), stack(_stack), entry(_entry) {} + + ~UndoTokenPush() + { + if (entry) + delete entry; + } + + void run(void); + }; + + class UndoTokenPop : public UndoToken { + QRegisterStack *stack; + + public: + UndoTokenPop(QRegisterStack *_stack) + : UndoToken(), stack(_stack) {} + + void run(void); + }; + + SLIST_HEAD(Head, Entry) head; + +public: + QRegisterStack() + { + SLIST_INIT(&head); + } + ~QRegisterStack(); + + void push(QRegister *reg); + bool pop(QRegister *reg); +}; + +/* + * Command states + */ + +class StatePushQReg : public StateExpectQReg { +private: + State *got_register(QRegister *reg) throw (Error); +}; + +class StatePopQReg : public StateExpectQReg { +private: + State *got_register(QRegister *reg) throw (Error); +}; + +class StateEQCommand : public StateExpectQReg { +private: + State *got_register(QRegister *reg) throw (Error); +}; + +class StateLoadQReg : public StateExpectString { +private: + State *done(const gchar *str) throw (Error); +}; + +class StateCtlUCommand : public StateExpectQReg { +private: + State *got_register(QRegister *reg) throw (Error); +}; + +class StateSetQRegString : public StateExpectString { +public: + StateSetQRegString() : StateExpectString(false) {} +private: + State *done(const gchar *str) throw (Error); +}; + +class StateGetQRegString : public StateExpectQReg { +private: + State *got_register(QRegister *reg) throw (Error); +}; + +class StateGetQRegInteger : public StateExpectQReg { +private: + State *got_register(QRegister *reg) throw (Error); +}; + +class StateSetQRegInteger : public StateExpectQReg { +private: + State *got_register(QRegister *reg) throw (Error); +}; + +class StateIncreaseQReg : public StateExpectQReg { +private: + State *got_register(QRegister *reg) throw (Error); +}; + +class StateMacro : public StateExpectQReg { +private: + State *got_register(QRegister *reg) throw (Error); +}; + +class StateCopyToQReg : public StateExpectQReg { +private: + State *got_register(QRegister *reg) throw (Error); +}; + +namespace States { + extern StatePushQReg pushqreg; + extern StatePopQReg popqreg; + extern StateEQCommand eqcommand; + extern StateLoadQReg loadqreg; + extern StateCtlUCommand ctlucommand; + extern StateSetQRegString setqregstring; + extern StateGetQRegString getqregstring; + extern StateGetQRegInteger getqreginteger; + extern StateSetQRegInteger setqreginteger; + extern StateIncreaseQReg increaseqreg; + extern StateMacro macro; + extern StateCopyToQReg copytoqreg; +} + +namespace QRegisters { + extern QRegisterTable globals; + extern QRegisterTable *locals; + extern QRegister *current; + + static inline void + undo_edit(void) + { + current->dot = interface.ssm(SCI_GETCURRENTPOS); + undo.push_var(current)->undo_edit(); + } + + enum Hook { + HOOK_ADD = 1, + HOOK_EDIT, + HOOK_CLOSE, + HOOK_QUIT + }; + void hook(Hook type); +} + +#endif diff --git a/src/rbtree.cpp b/src/rbtree.cpp new file mode 100644 index 0000000..28df67b --- /dev/null +++ b/src/rbtree.cpp @@ -0,0 +1,5 @@ +#include + +#include "rbtree.h" + +RB_GENERATE(RBTree::Tree, RBTree::RBEntry, nodes, RBTree::compare_entries); diff --git a/src/rbtree.h b/src/rbtree.h new file mode 100644 index 0000000..d5f2a6e --- /dev/null +++ b/src/rbtree.h @@ -0,0 +1,282 @@ +#ifndef __RBTREE_H +#define __RBTREE_H + +#include + +#include + +#include "undo.h" + +class RBTree { +public: + class RBEntry; + +private: + RB_HEAD(Tree, RBEntry) head; + + RB_PROTOTYPE_INTERNAL(Tree, RBEntry, nodes, /* unused */, static); + +public: + class RBEntry { + public: + RB_ENTRY(RBEntry) nodes; + + virtual ~RBEntry() {} + + inline RBEntry * + next(void) + { + return RBTree::Tree_RB_NEXT(this); + } + inline RBEntry * + prev(void) + { + return RBTree::Tree_RB_PREV(this); + } + + virtual int operator <(RBEntry &entry) = 0; + }; + +private: + static inline int + compare_entries(RBEntry *e1, RBEntry *e2) + { + return *e1 < *e2; + } + +public: + RBTree() + { + RB_INIT(&head); + } + virtual + ~RBTree() + { + clear(); + } + + inline RBEntry * + insert(RBEntry *entry) + { + RB_INSERT(Tree, &head, entry); + return entry; + } + inline RBEntry * + remove(RBEntry *entry) + { + return RB_REMOVE(Tree, &head, entry); + } + inline RBEntry * + find(RBEntry *entry) + { + return RB_FIND(Tree, &head, entry); + } + inline RBEntry * + nfind(RBEntry *entry) + { + return RB_NFIND(Tree, &head, entry); + } + inline RBEntry * + min(void) + { + return RB_MIN(Tree, &head); + } + inline RBEntry * + max(void) + { + return RB_MAX(Tree, &head); + } + + inline void + clear(void) + { + RBEntry *cur; + + while ((cur = min())) { + remove(cur); + delete cur; + } + } +}; + +template +class Table : public RBTree { + class TableEntry : public RBEntry { + public: + KeyType key; + ValueType value; + + TableEntry(KeyType &_key, ValueType &_value) + : key(_key), value(_value) {} + + int + operator <(RBEntry &entry) + { + return key < ((TableEntry &)entry).key; + } + }; + +public: + ValueType nil; + + Table(ValueType &_nil) : RBTree(), nil(_nil) {} + + inline bool + hasEntry(KeyType &key) + { + return find(&TableEntry(key, nil)) != NULL; + } + + ValueType & + operator [](KeyType &key) + { + TableEntry *entry = new TableEntry(key, nil); + TableEntry *existing = (TableEntry *)find(entry); + + if (existing) + delete entry; + else + existing = (TableEntry *)insert(entry); + + return existing->value; + } + + inline void + remove(KeyType &key) + { + TableEntry entry(key, nil); + TableEntry *existing = (TableEntry *)find(&entry); + + if (existing) + RBTree::remove(existing); + } + +#if 0 + void + dump(void) + { + RBEntry *cur; + + RB_FOREACH(cur, Tree, &head) + g_printf("tree[\"%s\"] = %d\n", cur->name, cur->pc); + g_printf("---END---\n"); + } +#endif + + void + clear(void) + { + RBEntry *cur; + + while ((cur = min())) { + RBTree::remove(cur); + delete cur; + } + } +}; + +class CString { +public: + gchar *str; + + CString(const gchar *_str) : str(g_strdup(_str)) {} + ~CString() + { + g_free(str); + } + + inline int + operator <(CString &obj) + { + return (int)g_strcmp0(str, obj.str); + } +}; + +template +class StringTable : public Table { +public: + StringTable(ValueType &nil) : Table(nil) {} + + inline bool + hasEntry(const gchar *key) + { + CString str(key); + return Table::hasEntry(str); + } + + inline ValueType & + operator [](const gchar *key) + { + CString str(key); + return (Table::operator [])(str); + } + + inline void + remove(const gchar *key) + { + CString str(key); + Table::remove(str); + } +}; + +template +class StringTableUndo : public StringTable { + class UndoTokenSet : public UndoToken { + StringTableUndo *table; + + CString name; + ValueType value; + + public: + UndoTokenSet(StringTableUndo *_table, CString &_name, ValueType &_value) + : table(_table), name(_name), value(_value) {} + + void + run(void) + { + table->Table::operator [](name) = value; + name.str = NULL; +#if 0 + table->dump(); +#endif + } + }; + + class UndoTokenRemove : public UndoToken { + StringTableUndo *table; + + CString name; + + public: + UndoTokenRemove(StringTableUndo *_table, CString &_name) + : table(_table), name(_name) {} + + void + run(void) + { + table->Table::remove(name); +#if 0 + table->dump(); +#endif + } + }; + +public: + StringTableUndo(ValueType &nil) : StringTable(nil) {} + + void + set(const gchar *key, ValueType &value) + { + ValueType &old = (StringTable::operator [])(key); + CString str(key); + + if (old == StringTable::nil) + undo.push(new UndoTokenRemove(this, str)); + else + undo.push(new UndoTokenSet(this, str, old)); + + old = value; + } +}; + +#endif diff --git a/src/ring.cpp b/src/ring.cpp new file mode 100644 index 0000000..f1b051b --- /dev/null +++ b/src/ring.cpp @@ -0,0 +1,539 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "sciteco.h" +#include "interface.h" +#include "undo.h" +#include "parser.h" +#include "expressions.h" +#include "ring.h" + +#ifdef G_OS_WIN32 +/* here it shouldn't cause conflicts with other headers */ +#include + +/* still need to clean up */ +#ifdef interface +#undef interface +#endif +#endif + +namespace States { + StateEditFile editfile; + StateSaveFile savefile; +} + +Ring ring; + +void +Buffer::UndoTokenClose::run(void) +{ + ring.close(buffer); + /* NOTE: the buffer is NOT deleted on Token destruction */ + delete buffer; +} + +bool +Buffer::load(const gchar *filename) +{ + gchar *contents; + gsize size; + + /* FIXME: prevent excessive allocations by reading file into buffer */ + if (!g_file_get_contents(filename, &contents, &size, NULL)) + return false; + + edit(); + + interface.ssm(SCI_BEGINUNDOACTION); + interface.ssm(SCI_CLEARALL); + interface.ssm(SCI_APPENDTEXT, size, (sptr_t)contents); + interface.ssm(SCI_ENDUNDOACTION); + + g_free(contents); + + /* NOTE: currently buffer cannot be dirty */ +#if 0 + interface.undo_info_update(this); + undo.push_var(dirty); + dirty = false; +#endif + + set_filename(filename); + + return true; +} + +void +Ring::UndoTokenEdit::run(void) +{ + /* + * assumes that buffer still has correct prev/next + * pointers + */ + if (buffer->next()) + TAILQ_INSERT_BEFORE(buffer->next(), buffer, buffers); + else + TAILQ_INSERT_TAIL(&ring->head, buffer, buffers); + + ring->current = buffer; + buffer->edit(); + buffer = NULL; +} + +Buffer * +Ring::find(const gchar *filename) +{ + gchar *resolved = get_absolute_path(filename); + Buffer *cur; + + TAILQ_FOREACH(cur, &head, buffers) + if (!g_strcmp0(cur->filename, resolved)) + break; + + g_free(resolved); + return cur; +} + +Buffer * +Ring::find(gint64 id) +{ + Buffer *cur; + + TAILQ_FOREACH(cur, &head, buffers) + if (!--id) + break; + + return cur; +} + +void +Ring::dirtify(void) +{ + if (!current || current->dirty) + return; + + interface.undo_info_update(current); + undo.push_var(current->dirty); + current->dirty = true; + interface.info_update(current); +} + +bool +Ring::is_any_dirty(void) +{ + Buffer *cur; + + TAILQ_FOREACH(cur, &head, buffers) + if (cur->dirty) + return true; + + return false; +} + +bool +Ring::edit(gint64 id) +{ + Buffer *buffer = find(id); + + if (!buffer) + return false; + + current_save_dot(); + + QRegisters::current = NULL; + current = buffer; + buffer->edit(); + + QRegisters::hook(QRegisters::HOOK_EDIT); + + return true; +} + +void +Ring::edit(const gchar *filename) +{ + Buffer *buffer = find(filename); + + current_save_dot(); + + QRegisters::current = NULL; + if (buffer) { + current = buffer; + buffer->edit(); + + QRegisters::hook(QRegisters::HOOK_EDIT); + } else { + buffer = new Buffer(); + TAILQ_INSERT_TAIL(&head, buffer, buffers); + + current = buffer; + undo_close(); + + if (filename && g_file_test(filename, G_FILE_TEST_IS_REGULAR)) { + buffer->load(filename); + + interface.msg(Interface::MSG_INFO, + "Added file \"%s\" to ring", filename); + } else { + buffer->edit(); + buffer->set_filename(filename); + + if (filename) + interface.msg(Interface::MSG_INFO, + "Added new file \"%s\" to ring", + filename); + else + interface.msg(Interface::MSG_INFO, + "Added new unnamed file to ring."); + } + + QRegisters::hook(QRegisters::HOOK_ADD); + } +} + +#if 0 + +/* + * TODO: on UNIX it may be better to open() the current file, unlink() it + * and keep the file descriptor in the UndoToken. + * When the operation is undone, the file descriptor's contents are written to + * the file (which should be efficient enough because it is written to the same + * filesystem). This way we could avoid messing around with save point files. + */ + +#else + +class UndoTokenRestoreSavePoint : public UndoToken { + gchar *savepoint; + Buffer *buffer; + +public: +#ifdef G_OS_WIN32 + DWORD attributes; +#endif + + UndoTokenRestoreSavePoint(gchar *_savepoint, Buffer *_buffer) + : savepoint(_savepoint), buffer(_buffer) {} + ~UndoTokenRestoreSavePoint() + { + if (savepoint) + g_unlink(savepoint); + g_free(savepoint); + buffer->savepoint_id--; + } + + void + run(void) + { + if (!g_rename(savepoint, buffer->filename)) { + g_free(savepoint); + savepoint = NULL; +#ifdef G_OS_WIN32 + SetFileAttributes((LPCTSTR)buffer->filename, + attributes); +#endif + } else { + interface.msg(Interface::MSG_WARNING, + "Unable to restore save point file \"%s\"", + savepoint); + } + } +}; + +static inline void +make_savepoint(Buffer *buffer) +{ + gchar *dirname, *basename, *savepoint; + gchar savepoint_basename[FILENAME_MAX]; + + basename = g_path_get_basename(buffer->filename); + g_snprintf(savepoint_basename, sizeof(savepoint_basename), + ".teco-%s-%d", basename, buffer->savepoint_id); + g_free(basename); + dirname = g_path_get_dirname(buffer->filename); + savepoint = g_build_filename(dirname, savepoint_basename, NULL); + g_free(dirname); + + if (!g_rename(buffer->filename, savepoint)) { + UndoTokenRestoreSavePoint *token; + + buffer->savepoint_id++; + token = new UndoTokenRestoreSavePoint(savepoint, buffer); +#ifdef G_OS_WIN32 + token->attributes = GetFileAttributes((LPCTSTR)savepoint); + if (token->attributes != INVALID_FILE_ATTRIBUTES) + SetFileAttributes((LPCTSTR)savepoint, + token->attributes | + FILE_ATTRIBUTE_HIDDEN); +#endif + undo.push(token); + } else { + interface.msg(Interface::MSG_WARNING, + "Unable to create save point file \"%s\"", + savepoint); + g_free(savepoint); + } +} + +#endif /* !G_OS_UNIX */ + +bool +Ring::save(const gchar *filename) +{ + const gchar *buffer; + gssize size; + + if (!current) + return false; + + if (!filename) + filename = current->filename; + if (!filename) + return false; + + if (undo.enabled) { + if (current->filename && + g_file_test(current->filename, G_FILE_TEST_IS_REGULAR)) + make_savepoint(current); + else + undo.push(new UndoTokenRemoveFile(filename)); + } + + /* FIXME: improve by writing before and after document gap */ + buffer = (const gchar *)interface.ssm(SCI_GETCHARACTERPOINTER); + size = interface.ssm(SCI_GETLENGTH); + + if (!g_file_set_contents(filename, buffer, size, NULL)) + return false; + + interface.undo_info_update(current); + undo.push_var(current->dirty); + current->dirty = false; + + /* + * FIXME: necessary also if the filename was not specified but the file + * is (was) new, in order to canonicalize the filename. + * May be circumvented by cananonicalizing without requiring the file + * name to exist (like readlink -f) + */ + //if (filename) { + undo.push_str(current->filename); + current->set_filename(filename); + //} + + return true; +} + +void +Ring::close(Buffer *buffer) +{ + TAILQ_REMOVE(&head, buffer, buffers); + + if (buffer->filename) + interface.msg(Interface::MSG_INFO, + "Removed file \"%s\" from the ring", + buffer->filename); + else + interface.msg(Interface::MSG_INFO, + "Removed unnamed file from the ring."); +} + +void +Ring::close(void) +{ + Buffer *buffer = current; + + buffer->dot = interface.ssm(SCI_GETCURRENTPOS); + close(buffer); + current = buffer->next() ? : buffer->prev(); + /* transfer responsibility to UndoToken object */ + undo.push(new UndoTokenEdit(this, buffer)); + + if (current) { + current->edit(); + QRegisters::hook(QRegisters::HOOK_EDIT); + } else { + edit((const gchar *)NULL); + } +} + +Ring::~Ring() +{ + Buffer *buffer, *next; + + TAILQ_FOREACH_SAFE(buffer, &head, buffers, next) + delete buffer; +} + +/* + * Auxiliary functions + */ +#ifdef G_OS_UNIX + +gchar * +get_absolute_path(const gchar *path) +{ + gchar buf[PATH_MAX]; + gchar *resolved; + + if (!path) + return NULL; + + if (!realpath(path, buf)) { + if (g_path_is_absolute(path)) { + resolved = g_strdup(path); + } else { + gchar *cwd = g_get_current_dir(); + resolved = g_build_filename(cwd, path, NULL); + g_free(cwd); + } + } else { + resolved = g_strdup(buf); + } + + return resolved; +} + +#elif defined(G_OS_WIN32) + +gchar * +get_absolute_path(const gchar *path) +{ + TCHAR buf[MAX_PATH]; + gchar *resolved = NULL; + + if (path && GetFullPathName(path, sizeof(buf), buf, NULL)) + resolved = g_strdup(buf); + + return resolved; +} + +#else + +/* + * FIXME: I doubt that works on any platform... + */ +gchar * +get_absolute_path(const gchar *path) +{ + return path ? g_file_read_link(path, NULL) : NULL; +} + +#endif /* !G_OS_UNIX && !G_OS_WIN32 */ + +/* + * Command states + */ + +void +StateEditFile::do_edit(const gchar *filename) throw (Error) +{ + if (ring.current) + ring.undo_edit(); + else /* QRegisters::current != NULL */ + QRegisters::undo_edit(); + ring.edit(filename); +} + +void +StateEditFile::do_edit(gint64 id) throw (Error) +{ + if (ring.current) + ring.undo_edit(); + else /* QRegisters::current != NULL */ + QRegisters::undo_edit(); + if (!ring.edit(id)) + throw Error("Invalid buffer id %" G_GINT64_FORMAT, id); +} + +void +StateEditFile::initial(void) throw (Error) +{ + gint64 id = expressions.pop_num_calc(1, -1); + + allowFilename = true; + + if (id == 0) { + for (Buffer *cur = ring.first(); cur; cur = cur->next()) + interface.popup_add(Interface::POPUP_FILE, + cur->filename ? : "(Unnamed)", + cur == ring.current); + + interface.popup_show(); + } else if (id > 0) { + allowFilename = false; + do_edit(id); + } +} + +State * +StateEditFile::done(const gchar *str) throw (Error) +{ + BEGIN_EXEC(&States::start); + + if (!allowFilename) { + if (*str) + throw Error("If a buffer is selected by id, the " + "string argument must be empty"); + + return &States::start; + } + + if (is_glob_pattern(str)) { + gchar *dirname; + GDir *dir; + + dirname = g_path_get_dirname(str); + dir = g_dir_open(dirname, 0, NULL); + + if (dir) { + const gchar *basename; + GPatternSpec *pattern; + + basename = g_path_get_basename(str); + pattern = g_pattern_spec_new(basename); + g_free((gchar *)basename); + + while ((basename = g_dir_read_name(dir))) { + if (g_pattern_match_string(pattern, basename)) { + gchar *filename; + + filename = g_build_filename(dirname, + basename, + NULL); + do_edit(filename); + g_free(filename); + } + } + + g_pattern_spec_free(pattern); + g_dir_close(dir); + } + + g_free(dirname); + } else { + do_edit(*str ? str : NULL); + } + + return &States::start; +} + +State * +StateSaveFile::done(const gchar *str) throw (Error) +{ + BEGIN_EXEC(&States::start); + + if (!ring.save(*str ? str : NULL)) + throw Error("Unable to save file"); + + return &States::start; +} diff --git a/src/ring.h b/src/ring.h new file mode 100644 index 0000000..da4f322 --- /dev/null +++ b/src/ring.h @@ -0,0 +1,246 @@ +#ifndef __RING_H +#define __RING_H + +#include +#include + +#include +#include +#include + +#include + +#include "sciteco.h" +#include "interface.h" +#include "undo.h" +#include "qregisters.h" +#include "parser.h" + +/* + * Auxiliary functions + */ +static inline bool +is_glob_pattern(const gchar *str) +{ + return strchr(str, '*') || strchr(str, '?'); +} + +/* + * Get absolute/full version of a possibly relative path. + * Works with existing and non-existing paths (in the latter case, + * heuristics may be applied.) + */ +gchar *get_absolute_path(const gchar *path); + +/* + * Classes + */ + +class Buffer { + class UndoTokenClose : public UndoToken { + Buffer *buffer; + + public: + UndoTokenClose(Buffer *_buffer) + : UndoToken(), buffer(_buffer) {} + + void run(void); + }; + +public: + TAILQ_ENTRY(Buffer) buffers; + + gchar *filename; + gint dot; + + gint savepoint_id; + + bool dirty; + +private: + typedef void document; + document *doc; + +public: + Buffer() : filename(NULL), dot(0), savepoint_id(0), dirty(false) + { + doc = (document *)interface.ssm(SCI_CREATEDOCUMENT); + } + ~Buffer() + { + interface.ssm(SCI_RELEASEDOCUMENT, 0, (sptr_t)doc); + g_free(filename); + } + + inline Buffer *& + next(void) + { + return TAILQ_NEXT(this, buffers); + } + inline Buffer *& + prev(void) + { + TAILQ_HEAD(Head, Buffer); + + return TAILQ_PREV(this, Head, buffers); + } + + inline void + set_filename(const gchar *filename) + { + gchar *resolved = get_absolute_path(filename); + g_free(Buffer::filename); + Buffer::filename = resolved; + interface.info_update(this); + } + + inline void + edit(void) + { + interface.ssm(SCI_SETDOCPOINTER, 0, (sptr_t)doc); + interface.ssm(SCI_GOTOPOS, dot); + interface.info_update(this); + } + inline void + undo_edit(void) + { + interface.undo_info_update(this); + undo.push_msg(SCI_GOTOPOS, dot); + undo.push_msg(SCI_SETDOCPOINTER, 0, (sptr_t)doc); + } + + bool load(const gchar *filename); + + inline void + undo_close(void) + { + undo.push(new UndoTokenClose(this)); + } +}; + +extern class Ring { + /* + * Emitted after a buffer close + * The pointer is the only remaining reference to the buffer! + */ + class UndoTokenEdit : public UndoToken { + Ring *ring; + Buffer *buffer; + + public: + UndoTokenEdit(Ring *_ring, Buffer *_buffer) + : UndoToken(), ring(_ring), buffer(_buffer) {} + ~UndoTokenEdit() + { + if (buffer) + delete buffer; + } + + void run(void); + }; + + class UndoTokenRemoveFile : public UndoToken { + gchar *filename; + + public: + UndoTokenRemoveFile(const gchar *_filename) + : filename(g_strdup(_filename)) {} + ~UndoTokenRemoveFile() + { + g_free(filename); + } + + void + run(void) + { + g_unlink(filename); + } + }; + + TAILQ_HEAD(Head, Buffer) head; + +public: + Buffer *current; + + Ring() : current(NULL) + { + TAILQ_INIT(&head); + } + ~Ring(); + + inline Buffer * + first(void) + { + return TAILQ_FIRST(&head); + } + inline Buffer * + last(void) + { + return TAILQ_LAST(&head, Head); + } + + Buffer *find(const gchar *filename); + Buffer *find(gint64 id); + + void dirtify(void); + bool is_any_dirty(void); + + bool edit(gint64 id); + void edit(const gchar *filename); + inline void + undo_edit(void) + { + current->dot = interface.ssm(SCI_GETCURRENTPOS); + undo.push_var(current); + current->undo_edit(); + } + + bool save(const gchar *filename); + + void close(Buffer *buffer); + void close(void); + inline void + undo_close(void) + { + current->undo_close(); + } +} ring; + +/* + * Command states + */ + +class StateEditFile : public StateExpectString { +private: + bool allowFilename; + + void do_edit(const gchar *filename) throw (Error); + void do_edit(gint64 id) throw (Error); + + void initial(void) throw (Error); + State *done(const gchar *str) throw (Error); +}; + +class StateSaveFile : public StateExpectString { +private: + State *done(const gchar *str) throw (Error); +}; + +namespace States { + extern StateEditFile editfile; + extern StateSaveFile savefile; +} + +/* FIXME: clean up current_save_dot() usage */ +static inline void +current_save_dot(void) +{ + gint dot = interface.ssm(SCI_GETCURRENTPOS); + + if (ring.current) + ring.current->dot = dot; + else if (QRegisters::current) + QRegisters::current->dot = dot; +} + +#endif diff --git a/src/sciteco.h b/src/sciteco.h new file mode 100644 index 0000000..88ab109 --- /dev/null +++ b/src/sciteco.h @@ -0,0 +1,76 @@ +#ifndef __SCITECO_H +#define __SCITECO_H + +#include + +#include "interface.h" + +/* Autoconf-like */ +#define PACKAGE_VERSION "0.1" +#define PACKAGE_NAME "SciTECO" +#define PACKAGE_STRING PACKAGE_NAME " " PACKAGE_VERSION + +namespace Flags { + enum { + ED_HOOKS = (1 << 5) + }; + + extern gint64 ed; +} + +extern gchar *cmdline; +extern bool quit_requested; + +void cmdline_keypress(gchar key); + +#define IS_CTL(C) ((C) < ' ') +#define CTL_ECHO(C) ((C) | 0x40) +#define CTL_KEY(C) ((C) & ~0x40) + +typedef gint64 tecoBool; + +#define SUCCESS (-1) +#define FAILURE (0) +#define TECO_BOOL(X) ((X) ? SUCCESS : FAILURE) + +#define IS_SUCCESS(X) ((X) < 0) +#define IS_FAILURE(X) (!IS_SUCCESS(X)) + +#define CHR2STR(X) ((gchar []){X, '\0'}) + +namespace String { + +static inline void +append(gchar *&str1, const gchar *str2) +{ + /* FIXME: optimize */ + gchar *new_str = g_strconcat(str1 ? : "", str2, NULL); + g_free(str1); + str1 = new_str; +} + +static inline void +append(gchar *&str, gchar chr) +{ + append(str, CHR2STR(chr)); +} + +} /* namespace String */ + +namespace Validate { + +static inline bool +pos(gint n) +{ + return n >= 0 && n <= interface.ssm(SCI_GETLENGTH); +} + +static inline bool +line(gint n) +{ + return n >= 0 && n < interface.ssm(SCI_GETLINECOUNT); +} + +} /* namespace Validate */ + +#endif diff --git a/src/search.cpp b/src/search.cpp new file mode 100644 index 0000000..701f36c --- /dev/null +++ b/src/search.cpp @@ -0,0 +1,571 @@ +#include + +#include +#include + +#include "sciteco.h" +#include "expressions.h" +#include "undo.h" +#include "qregisters.h" +#include "ring.h" +#include "parser.h" +#include "search.h" + +namespace States { + StateSearch search; + StateSearchAll searchall; + StateSearchKill searchkill; + StateSearchDelete searchdelete; + StateReplace replace; + StateReplace_insert replace_insert; + StateReplaceDefault replacedefault; + StateReplaceDefault_insert replacedefault_insert; +} + +/* + * Command states + */ + +void +StateSearch::initial(void) throw (Error) +{ + gint64 v1, v2; + + undo.push_var(parameters); + + parameters.dot = interface.ssm(SCI_GETCURRENTPOS); + + v2 = expressions.pop_num_calc(); + if (expressions.args()) { + /* TODO: optional count argument? */ + v1 = expressions.pop_num_calc(); + if (v1 <= v2) { + parameters.count = 1; + parameters.from = (gint)v1; + parameters.to = (gint)v2; + } else { + parameters.count = -1; + parameters.from = (gint)v2; + parameters.to = (gint)v1; + } + + if (!Validate::pos(parameters.from) || + !Validate::pos(parameters.to)) + throw RangeError("S"); + } else { + parameters.count = (gint)v2; + if (v2 >= 0) { + parameters.from = parameters.dot; + parameters.to = interface.ssm(SCI_GETLENGTH); + } else { + parameters.from = 0; + parameters.to = parameters.dot; + } + } + + parameters.from_buffer = ring.current; + parameters.to_buffer = NULL; +} + +static inline const gchar * +regexp_escape_chr(gchar chr) +{ + static gchar escaped[] = {'\\', '\0', '\0'}; + + escaped[1] = chr; + return g_ascii_isalnum(chr) ? escaped + 1 : escaped; +} + +gchar * +StateSearch::class2regexp(MatchState &state, const gchar *&pattern, + bool escape_default) +{ + while (*pattern) { + QRegister *reg; + gchar *temp, *temp2; + + switch (state) { + case STATE_START: + switch (*pattern) { + case CTL_KEY('S'): + return g_strdup("[:^alnum:]"); + case CTL_KEY('E'): + state = STATE_CTL_E; + break; + default: + temp = escape_default + ? g_strdup(regexp_escape_chr(*pattern)) + : NULL; + return temp; + } + break; + + case STATE_CTL_E: + switch (g_ascii_toupper(*pattern)) { + case 'A': + state = STATE_START; + return g_strdup("[:alpha:]"); + /* same as */ + case 'B': + state = STATE_START; + return g_strdup("[:^alnum:]"); + case 'C': + state = STATE_START; + return g_strdup("[:alnum:].$"); + case 'D': + state = STATE_START; + return g_strdup("[:digit:]"); + case 'G': + state = STATE_ANYQ; + break; + case 'L': + state = STATE_START; + return g_strdup("\r\n\v\f"); + case 'R': + state = STATE_START; + return g_strdup("[:alnum:]"); + case 'V': + state = STATE_START; + return g_strdup("[:lower:]"); + case 'W': + state = STATE_START; + return g_strdup("[:upper:]"); + default: + return NULL; + } + break; + + case STATE_ANYQ: + /* FIXME: Q-Register spec might get more complicated */ + reg = QRegisters::globals[g_ascii_toupper(*pattern)]; + if (!reg) + return NULL; + + temp = reg->get_string(); + temp2 = g_regex_escape_string(temp, -1); + g_free(temp); + state = STATE_START; + return temp2; + + default: + return NULL; + } + + pattern++; + } + + return NULL; +} + +gchar * +StateSearch::pattern2regexp(const gchar *&pattern, + bool single_expr) +{ + MatchState state = STATE_START; + gchar *re = NULL; + + while (*pattern) { + gchar *new_re, *temp; + + temp = class2regexp(state, pattern); + if (temp) { + new_re = g_strconcat(re ? : "", "[", temp, "]", NULL); + g_free(temp); + g_free(re); + re = new_re; + + goto next; + } + if (!*pattern) + break; + + switch (state) { + case STATE_START: + switch (*pattern) { + case CTL_KEY('X'): String::append(re, "."); break; + case CTL_KEY('N'): state = STATE_NOT; break; + default: + String::append(re, regexp_escape_chr(*pattern)); + } + break; + + case STATE_NOT: + state = STATE_START; + temp = class2regexp(state, pattern, true); + if (!temp) + goto error; + new_re = g_strconcat(re ? : "", "[^", temp, "]", NULL); + g_free(temp); + g_free(re); + re = new_re; + g_assert(state == STATE_START); + break; + + case STATE_CTL_E: + state = STATE_START; + switch (g_ascii_toupper(*pattern)) { + case 'M': state = STATE_MANY; break; + case 'S': String::append(re, "\\s+"); break; + /* same as */ + case 'X': String::append(re, "."); break; + /* TODO: ASCII octal code!? */ + case '[': + String::append(re, "("); + state = STATE_ALT; + break; + default: + goto error; + } + break; + + case STATE_MANY: + temp = pattern2regexp(pattern, true); + if (!temp) + goto error; + new_re = g_strconcat(re ? : "", "(", temp, ")+", NULL); + g_free(temp); + g_free(re); + re = new_re; + state = STATE_START; + break; + + case STATE_ALT: + switch (*pattern) { + case ',': + String::append(re, "|"); + break; + case ']': + String::append(re, ")"); + state = STATE_START; + break; + default: + temp = pattern2regexp(pattern, true); + if (!temp) + goto error; + String::append(re, temp); + g_free(temp); + } + break; + + default: + /* shouldn't happen */ + g_assert(true); + } + +next: + if (single_expr && state == STATE_START) + return re; + + pattern++; + } + + if (state == STATE_ALT) + String::append(re, ")"); + + return re; + +error: + g_free(re); + return NULL; +} + +void +StateSearch::do_search(GRegex *re, gint from, gint to, gint &count) +{ + GMatchInfo *info; + const gchar *buffer; + + gint matched_from = -1, matched_to = -1; + + buffer = (const gchar *)interface.ssm(SCI_GETCHARACTERPOINTER); + g_regex_match_full(re, buffer, (gssize)to, from, + (GRegexMatchFlags)0, &info, NULL); + + if (count >= 0) { + while (g_match_info_matches(info) && --count) + g_match_info_next(info, NULL); + + if (!count) + /* successful */ + g_match_info_fetch_pos(info, 0, + &matched_from, &matched_to); + } else { + /* only keep the last `count' matches, in a circular stack */ + struct Range { + gint from, to; + }; + Range *matched = new Range[-count]; + gint matched_total = 0, i = 0; + + while (g_match_info_matches(info)) { + g_match_info_fetch_pos(info, 0, + &matched[i].from, + &matched[i].to); + + g_match_info_next(info, NULL); + i = ++matched_total % -count; + } + + count = MIN(count + matched_total, 0); + if (!count) { + /* successful, i points to stack bottom */ + matched_from = matched[i].from; + matched_to = matched[i].to; + } + + delete matched; + } + + g_match_info_free(info); + + if (matched_from >= 0 && matched_to >= 0) + /* match success */ + interface.ssm(SCI_SETSEL, matched_from, matched_to); +} + +void +StateSearch::process(const gchar *str, + gint new_chars __attribute__((unused))) throw (Error) +{ + static const gint flags = G_REGEX_CASELESS | G_REGEX_MULTILINE | + G_REGEX_DOTALL | G_REGEX_RAW; + + QRegister *search_reg = QRegisters::globals["_"]; + + gchar *re_pattern; + GRegex *re; + + gint count = parameters.count; + + undo.push_msg(SCI_SETSEL, + interface.ssm(SCI_GETANCHOR), + interface.ssm(SCI_GETCURRENTPOS)); + + search_reg->undo_set_integer(); + search_reg->set_integer(FAILURE); + + /* NOTE: pattern2regexp() modifies str pointer */ + re_pattern = pattern2regexp(str); +#ifdef DEBUG + g_printf("REGEXP: %s\n", re_pattern); +#endif + if (!re_pattern) + goto failure; + re = g_regex_new(re_pattern, (GRegexCompileFlags)flags, + (GRegexMatchFlags)0, NULL); + g_free(re_pattern); + if (!re) + goto failure; + + if (ring.current != parameters.from_buffer) { + ring.undo_edit(); + parameters.from_buffer->edit(); + } + + do_search(re, parameters.from, parameters.to, count); + + if (parameters.to_buffer && count) { + Buffer *buffer = parameters.from_buffer; + + if (ring.current == buffer) + ring.undo_edit(); + + if (count > 0) { + do { + buffer = buffer->next() ? : ring.first(); + buffer->edit(); + + if (buffer == parameters.to_buffer) { + do_search(re, 0, parameters.dot, count); + break; + } + + do_search(re, 0, interface.ssm(SCI_GETLENGTH), + count); + } while (count); + } else /* count < 0 */ { + do { + buffer = buffer->prev() ? : ring.last(); + buffer->edit(); + + if (buffer == parameters.to_buffer) { + do_search(re, parameters.dot, + interface.ssm(SCI_GETLENGTH), + count); + break; + } + + do_search(re, 0, interface.ssm(SCI_GETLENGTH), + count); + } while (count); + } + + ring.current = buffer; + } + + search_reg->set_integer(TECO_BOOL(!count)); + + g_regex_unref(re); + + if (!count) + return; + +failure: + interface.ssm(SCI_GOTOPOS, parameters.dot); +} + +State * +StateSearch::done(const gchar *str) throw (Error) +{ + BEGIN_EXEC(&States::start); + + QRegister *search_reg = QRegisters::globals["_"]; + + if (*str) { + /* workaround: preserve selection (also on rubout) */ + gint anchor = interface.ssm(SCI_GETANCHOR); + undo.push_msg(SCI_SETANCHOR, anchor); + + search_reg->undo_set_string(); + search_reg->set_string(str); + + interface.ssm(SCI_SETANCHOR, anchor); + } else { + gchar *search_str = search_reg->get_string(); + process(search_str, 0 /* unused */); + g_free(search_str); + } + + if (eval_colon()) + expressions.push(search_reg->get_integer()); + else if (IS_FAILURE(search_reg->get_integer()) && + !expressions.find_op(Expressions::OP_LOOP) /* not in loop */) + interface.msg(Interface::MSG_ERROR, "Search string not found!"); + + return &States::start; +} + +void +StateSearchAll::initial(void) throw (Error) +{ + gint64 v1, v2; + + undo.push_var(parameters); + + parameters.dot = interface.ssm(SCI_GETCURRENTPOS); + + v2 = expressions.pop_num_calc(); + if (expressions.args()) { + /* TODO: optional count argument? */ + v1 = expressions.pop_num_calc(); + if (v1 <= v2) { + parameters.count = 1; + parameters.from_buffer = ring.find(v1); + parameters.to_buffer = ring.find(v2); + } else { + parameters.count = -1; + parameters.from_buffer = ring.find(v2); + parameters.to_buffer = ring.find(v1); + } + + if (!parameters.from_buffer || !parameters.to_buffer) + throw RangeError("N"); + } else { + parameters.count = (gint)v2; + /* NOTE: on Q-Registers, behave like "S" */ + parameters.from_buffer = parameters.to_buffer = ring.current; + } + + if (parameters.count >= 0) { + parameters.from = parameters.dot; + parameters.to = interface.ssm(SCI_GETLENGTH); + } else { + parameters.from = 0; + parameters.to = parameters.dot; + } +} + +State * +StateSearchAll::done(const gchar *str) throw (Error) +{ + BEGIN_EXEC(&States::start); + + StateSearch::done(str); + QRegisters::hook(QRegisters::HOOK_EDIT); + + return &States::start; +} + +State * +StateSearchKill::done(const gchar *str) throw (Error) +{ + gint anchor; + + BEGIN_EXEC(&States::start); + + StateSearch::done(str); + + undo.push_msg(SCI_GOTOPOS, interface.ssm(SCI_GETCURRENTPOS)); + anchor = interface.ssm(SCI_GETANCHOR); + interface.ssm(SCI_GOTOPOS, anchor); + + interface.ssm(SCI_BEGINUNDOACTION); + interface.ssm(SCI_DELETERANGE, + parameters.dot, anchor - parameters.dot); + interface.ssm(SCI_ENDUNDOACTION); + ring.dirtify(); + + undo.push_msg(SCI_UNDO); + + return &States::start; +} + +State * +StateSearchDelete::done(const gchar *str) throw (Error) +{ + BEGIN_EXEC(&States::start); + + StateSearch::done(str); + + interface.ssm(SCI_BEGINUNDOACTION); + interface.ssm(SCI_REPLACESEL, 0, (sptr_t)""); + interface.ssm(SCI_ENDUNDOACTION); + ring.dirtify(); + + undo.push_msg(SCI_UNDO); + + return &States::start; +} + +State * +StateReplace::done(const gchar *str) throw (Error) +{ + StateSearchDelete::done(str); + return &States::replace_insert; +} + +State * +StateReplaceDefault::done(const gchar *str) throw (Error) +{ + StateSearchDelete::done(str); + return &States::replacedefault_insert; +} + +State * +StateReplaceDefault_insert::done(const gchar *str) throw (Error) +{ + BEGIN_EXEC(&States::start); + + QRegister *replace_reg = QRegisters::globals["-"]; + + if (*str) { + replace_reg->undo_set_string(); + replace_reg->set_string(str); + } else { + gchar *replace_str = replace_reg->get_string(); + StateInsert::process(replace_str, strlen(replace_str)); + g_free(replace_str); + } + + return &States::start; +} diff --git a/src/search.h b/src/search.h new file mode 100644 index 0000000..2bddedd --- /dev/null +++ b/src/search.h @@ -0,0 +1,102 @@ +#ifndef __SEARCH_H +#define __SEARCH_H + +#include + +#include "sciteco.h" +#include "parser.h" +#include "ring.h" + +/* + * "S" command state and base class for all other search/replace commands + */ +class StateSearch : public StateExpectString { +public: + StateSearch(bool last = true) : StateExpectString(true, last) {} + +protected: + struct Parameters { + gint dot; + gint from, to; + gint count; + + Buffer *from_buffer, *to_buffer; + } parameters; + + enum MatchState { + STATE_START, + STATE_NOT, + STATE_CTL_E, + STATE_ANYQ, + STATE_MANY, + STATE_ALT + }; + + gchar *class2regexp(MatchState &state, const gchar *&pattern, + bool escape_default = false); + gchar *pattern2regexp(const gchar *&pattern, bool single_expr = false); + void do_search(GRegex *re, gint from, gint to, gint &count); + + virtual void initial(void) throw (Error); + virtual void process(const gchar *str, gint new_chars) throw (Error); + virtual State *done(const gchar *str) throw (Error); +}; + +class StateSearchAll : public StateSearch { +private: + void initial(void) throw (Error); + State *done(const gchar *str) throw (Error); +}; + +class StateSearchKill : public StateSearch { +private: + State *done(const gchar *str) throw (Error); +}; + +class StateSearchDelete : public StateSearch { +public: + StateSearchDelete(bool last = true) : StateSearch(last) {} + +protected: + State *done(const gchar *str) throw (Error); +}; + +class StateReplace : public StateSearchDelete { +public: + StateReplace() : StateSearchDelete(false) {} + +private: + State *done(const gchar *str) throw (Error); +}; + +class StateReplace_insert : public StateInsert { +private: + void initial(void) throw (Error) {} +}; + +class StateReplaceDefault : public StateSearchDelete { +public: + StateReplaceDefault() : StateSearchDelete(false) {} + +private: + State *done(const gchar *str) throw (Error); +}; + +class StateReplaceDefault_insert : public StateInsert { +private: + void initial(void) throw (Error) {} + State *done(const gchar *str) throw (Error); +}; + +namespace States { + extern StateSearch search; + extern StateSearchAll searchall; + extern StateSearchKill searchkill; + extern StateSearchDelete searchdelete; + extern StateReplace replace; + extern StateReplace_insert replace_insert; + extern StateReplaceDefault replacedefault; + extern StateReplaceDefault_insert replacedefault_insert; +} + +#endif diff --git a/src/symbols-extract.tes b/src/symbols-extract.tes new file mode 100755 index 0000000..6497324 --- /dev/null +++ b/src/symbols-extract.tes @@ -0,0 +1,79 @@ +#!./sciteco-minimal -m +! ./symbols-extract.tes ! + +! Mc - Compare string at pos1 with string at pos2 ! +@^Uc{ + U.2U.1 -.%.1^[ -.%.2^[ + + Q.c +} + +! Mx - Exchange line at I with line at J (I < J), returning new J ! +@^Ux{ + U.jU.i + Q.jJ X.xK + Q.iJ G.x .-Q.i%.j^[ .-(X.xL.)%.j^[ -K + Q.jJ G.x + Q.j +} + +! Mq - Sort lines beginning at I until J using Quicksort algorithm ! +@^Uq{ + U.rU.l + + Q.l-Q.r"< + Q.lU.i Q.rJB .U.j + + < + Q.iJ <.,Q.rMc-1; .-Q.r; L> .U.i + Q.jJ <.,Q.rMc:; .-Q.l-1:; B> .U.j + + Q.i-Q.j; + + Q.i,Q.jMxU.j + > + + Q.i,Q.rMc"> Q.i,Q.rMxU.r ' + + Q.l-Q.i"< Q.iJB Q.l,.Mq ' + Q.i-Q.r"< Q.iJL .,Q.rMq ' + ' +} + +! read commandline arguments ! +LR 0Xi 2LR 0Xo 2LR 0Xp 2LR 0Xn HK + +! copy all defines in input file beginning with prefix ! +EBQi EF + +! sort all defines ! +Ga ZJB 0,.Mq J + +! format as C/C++ array ! +I/* + * AUTOGENERATED - DO NOT EDIT + */ +#include + +#include "Qi" +#include "symbols.h" + +static const SymbolList::Entry entries[] = { + +< + .,W.Xa 0KK + I#ifdef Qa + {"Qa", ^EQa}, +#endif + +.-Z;> +I}; + +/* overwrites weak object in symbols.cpp */ +SymbolList Symbols::Qn(entries, G_N_ELEMENTS(entries)); + + +! write output file ! +EWQo + +EX diff --git a/src/symbols.cpp b/src/symbols.cpp new file mode 100644 index 0000000..c05d153 --- /dev/null +++ b/src/symbols.cpp @@ -0,0 +1,60 @@ +#include + +#include + +#include "symbols.h" + +/* + * defaults for sciteco-minimal + */ +namespace Symbols { + SymbolList __attribute__((weak)) scintilla; + SymbolList __attribute__((weak)) scilexer; +} + +/* + * Since symbol lists are presorted constant arrays we can do a simple + * binary search. + */ +gint +SymbolList::lookup(const gchar *name, const gchar *prefix, bool case_sensitive) +{ + int (*cmp_fnc)(const char *, const char *, size_t); + gint prefix_skip = strlen(prefix); + gint name_len = strlen(name); + + gint left = 0; + gint right = size - 1; + + cmp_fnc = case_sensitive ? strncmp : g_ascii_strncasecmp; + + if (!cmp_fnc(name, prefix, prefix_skip)) + prefix_skip = 0; + + while (left <= right) { + gint cur = left + (right-left)/2; + gint cmp = cmp_fnc(entries[cur].name + prefix_skip, + name, name_len + 1); + + if (!cmp) + return entries[cur].value; + + if (cmp > 0) + right = cur-1; + else /* cmp < 0 */ + left = cur+1; + } + + return -1; +} + +GList * +SymbolList::get_glist(void) +{ + if (!list) { + for (gint i = size; i; i--) + list = g_list_prepend(list, (gchar *)entries[i-1].name); + } + + return list; +} diff --git a/src/symbols.h b/src/symbols.h new file mode 100644 index 0000000..1c48ddf --- /dev/null +++ b/src/symbols.h @@ -0,0 +1,38 @@ +#ifndef __SYMBOLS_H +#define __SYMBOLS_H + +#include + +class SymbolList { +public: + struct Entry { + const gchar *name; + gint value; + }; + +private: + const Entry *entries; + gint size; + + /* for auto-completions */ + GList *list; + +public: + SymbolList(const Entry *_entries = NULL, gint _size = 0) + : entries(_entries), size(_size), list(NULL) {} + ~SymbolList() + { + g_list_free(list); + } + + gint lookup(const gchar *name, const gchar *prefix = "", + bool case_sensitive = false); + GList *get_glist(void); +}; + +namespace Symbols { + extern SymbolList __attribute__((weak)) scintilla; + extern SymbolList __attribute__((weak)) scilexer; +} + +#endif diff --git a/src/undo.cpp b/src/undo.cpp new file mode 100644 index 0000000..8072540 --- /dev/null +++ b/src/undo.cpp @@ -0,0 +1,78 @@ +#include +#include +#include + +#include +#include + +#include + +#include "sciteco.h" +#include "interface.h" +#include "undo.h" + +//#define DEBUG + +UndoStack undo; + +void +UndoTokenMessage::run(void) +{ + interface.ssm(iMessage, wParam, lParam); +} + +void +UndoStack::push(UndoToken *token) +{ + if (enabled) { +#ifdef DEBUG + g_printf("UNDO PUSH %p\n", token); +#endif + token->pos = strlen(cmdline); + SLIST_INSERT_HEAD(&head, token, tokens); + } else { + delete token; + } +} + +void +UndoStack::push_msg(unsigned int iMessage, uptr_t wParam, sptr_t lParam) +{ + push(new UndoTokenMessage(iMessage, wParam, lParam)); +} + +void +UndoStack::pop(gint pos) +{ + while (!SLIST_EMPTY(&head) && SLIST_FIRST(&head)->pos >= pos) { + UndoToken *top = SLIST_FIRST(&head); +#ifdef DEBUG + g_printf("UNDO POP %p\n", top); + fflush(stdout); +#endif + + top->run(); + + SLIST_REMOVE_HEAD(&head, tokens); + delete top; + } +} + +void +UndoStack::clear(void) +{ + UndoToken *cur; + + while ((cur = SLIST_FIRST(&head))) { + SLIST_REMOVE_HEAD(&head, tokens); + delete cur; + } +} + +UndoStack::~UndoStack() +{ + UndoToken *token, *next; + + SLIST_FOREACH_SAFE(token, &head, tokens, next) + delete token; +} diff --git a/src/undo.h b/src/undo.h new file mode 100644 index 0000000..43692e9 --- /dev/null +++ b/src/undo.h @@ -0,0 +1,134 @@ +#ifndef __UNDO_H +#define __UNDO_H + +#include + +#include +#include + +#include + +#ifdef DEBUG +#include "parser.h" +#endif + +class UndoToken { +public: + SLIST_ENTRY(UndoToken) tokens; + + gint pos; + + virtual ~UndoToken() {} + + virtual void run() = 0; +}; + +class UndoTokenMessage : public UndoToken { + unsigned int iMessage; + uptr_t wParam; + sptr_t lParam; + +public: + UndoTokenMessage(unsigned int _iMessage, + uptr_t _wParam = 0, sptr_t _lParam = 0) + : UndoToken(), iMessage(_iMessage), + wParam(_wParam), lParam(_lParam) {} + + void run(void); +}; + +template +class UndoTokenVariable : public UndoToken { + Type *ptr; + Type value; + +public: + UndoTokenVariable(Type &variable, Type _value) + : UndoToken(), ptr(&variable), value(_value) {} + + void + run(void) + { +#ifdef DEBUG + if ((State **)ptr == &States::current) + g_printf("undo state -> %p\n", (void *)value); +#endif + *ptr = value; + } +}; + +class UndoTokenString : public UndoToken { + gchar **ptr; + gchar *str; + +public: + UndoTokenString(gchar *&variable, gchar *_str) + : UndoToken(), ptr(&variable) + { + str = _str ? g_strdup(_str) : NULL; + } + + ~UndoTokenString() + { + g_free(str); + } + + void + run(void) + { + g_free(*ptr); + *ptr = str; + str = NULL; + } +}; + +extern class UndoStack { + SLIST_HEAD(Head, UndoToken) head; + +public: + bool enabled; + + UndoStack(bool _enabled = false) : enabled(_enabled) + { + SLIST_INIT(&head); + } + ~UndoStack(); + + void push(UndoToken *token); + + void push_msg(unsigned int iMessage, + uptr_t wParam = 0, sptr_t lParam = 0); + + template + inline Type & + push_var(Type &variable, Type value) + { + push(new UndoTokenVariable(variable, value)); + return variable; + } + + template + inline Type & + push_var(Type &variable) + { + return push_var(variable, variable); + } + + inline gchar *& + push_str(gchar *&variable, gchar *str) + { + push(new UndoTokenString(variable, str)); + return variable; + } + inline gchar *& + push_str(gchar *&variable) + { + return push_str(variable, variable); + } + + void pop(gint pos); + + void clear(void); +} undo; + +#endif \ No newline at end of file diff --git a/symbols-extract.tes b/symbols-extract.tes deleted file mode 100755 index 6497324..0000000 --- a/symbols-extract.tes +++ /dev/null @@ -1,79 +0,0 @@ -#!./sciteco-minimal -m -! ./symbols-extract.tes ! - -! Mc - Compare string at pos1 with string at pos2 ! -@^Uc{ - U.2U.1 -.%.1^[ -.%.2^[ - - Q.c -} - -! Mx - Exchange line at I with line at J (I < J), returning new J ! -@^Ux{ - U.jU.i - Q.jJ X.xK - Q.iJ G.x .-Q.i%.j^[ .-(X.xL.)%.j^[ -K - Q.jJ G.x - Q.j -} - -! Mq - Sort lines beginning at I until J using Quicksort algorithm ! -@^Uq{ - U.rU.l - - Q.l-Q.r"< - Q.lU.i Q.rJB .U.j - - < - Q.iJ <.,Q.rMc-1; .-Q.r; L> .U.i - Q.jJ <.,Q.rMc:; .-Q.l-1:; B> .U.j - - Q.i-Q.j; - - Q.i,Q.jMxU.j - > - - Q.i,Q.rMc"> Q.i,Q.rMxU.r ' - - Q.l-Q.i"< Q.iJB Q.l,.Mq ' - Q.i-Q.r"< Q.iJL .,Q.rMq ' - ' -} - -! read commandline arguments ! -LR 0Xi 2LR 0Xo 2LR 0Xp 2LR 0Xn HK - -! copy all defines in input file beginning with prefix ! -EBQi EF - -! sort all defines ! -Ga ZJB 0,.Mq J - -! format as C/C++ array ! -I/* - * AUTOGENERATED - DO NOT EDIT - */ -#include - -#include "Qi" -#include "symbols.h" - -static const SymbolList::Entry entries[] = { - -< - .,W.Xa 0KK - I#ifdef Qa - {"Qa", ^EQa}, -#endif - -.-Z;> -I}; - -/* overwrites weak object in symbols.cpp */ -SymbolList Symbols::Qn(entries, G_N_ELEMENTS(entries)); - - -! write output file ! -EWQo - -EX diff --git a/symbols.cpp b/symbols.cpp deleted file mode 100644 index c05d153..0000000 --- a/symbols.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include - -#include - -#include "symbols.h" - -/* - * defaults for sciteco-minimal - */ -namespace Symbols { - SymbolList __attribute__((weak)) scintilla; - SymbolList __attribute__((weak)) scilexer; -} - -/* - * Since symbol lists are presorted constant arrays we can do a simple - * binary search. - */ -gint -SymbolList::lookup(const gchar *name, const gchar *prefix, bool case_sensitive) -{ - int (*cmp_fnc)(const char *, const char *, size_t); - gint prefix_skip = strlen(prefix); - gint name_len = strlen(name); - - gint left = 0; - gint right = size - 1; - - cmp_fnc = case_sensitive ? strncmp : g_ascii_strncasecmp; - - if (!cmp_fnc(name, prefix, prefix_skip)) - prefix_skip = 0; - - while (left <= right) { - gint cur = left + (right-left)/2; - gint cmp = cmp_fnc(entries[cur].name + prefix_skip, - name, name_len + 1); - - if (!cmp) - return entries[cur].value; - - if (cmp > 0) - right = cur-1; - else /* cmp < 0 */ - left = cur+1; - } - - return -1; -} - -GList * -SymbolList::get_glist(void) -{ - if (!list) { - for (gint i = size; i; i--) - list = g_list_prepend(list, (gchar *)entries[i-1].name); - } - - return list; -} diff --git a/symbols.h b/symbols.h deleted file mode 100644 index 1c48ddf..0000000 --- a/symbols.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef __SYMBOLS_H -#define __SYMBOLS_H - -#include - -class SymbolList { -public: - struct Entry { - const gchar *name; - gint value; - }; - -private: - const Entry *entries; - gint size; - - /* for auto-completions */ - GList *list; - -public: - SymbolList(const Entry *_entries = NULL, gint _size = 0) - : entries(_entries), size(_size), list(NULL) {} - ~SymbolList() - { - g_list_free(list); - } - - gint lookup(const gchar *name, const gchar *prefix = "", - bool case_sensitive = false); - GList *get_glist(void); -}; - -namespace Symbols { - extern SymbolList __attribute__((weak)) scintilla; - extern SymbolList __attribute__((weak)) scilexer; -} - -#endif diff --git a/undo.cpp b/undo.cpp deleted file mode 100644 index 8072540..0000000 --- a/undo.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include -#include -#include - -#include -#include - -#include - -#include "sciteco.h" -#include "interface.h" -#include "undo.h" - -//#define DEBUG - -UndoStack undo; - -void -UndoTokenMessage::run(void) -{ - interface.ssm(iMessage, wParam, lParam); -} - -void -UndoStack::push(UndoToken *token) -{ - if (enabled) { -#ifdef DEBUG - g_printf("UNDO PUSH %p\n", token); -#endif - token->pos = strlen(cmdline); - SLIST_INSERT_HEAD(&head, token, tokens); - } else { - delete token; - } -} - -void -UndoStack::push_msg(unsigned int iMessage, uptr_t wParam, sptr_t lParam) -{ - push(new UndoTokenMessage(iMessage, wParam, lParam)); -} - -void -UndoStack::pop(gint pos) -{ - while (!SLIST_EMPTY(&head) && SLIST_FIRST(&head)->pos >= pos) { - UndoToken *top = SLIST_FIRST(&head); -#ifdef DEBUG - g_printf("UNDO POP %p\n", top); - fflush(stdout); -#endif - - top->run(); - - SLIST_REMOVE_HEAD(&head, tokens); - delete top; - } -} - -void -UndoStack::clear(void) -{ - UndoToken *cur; - - while ((cur = SLIST_FIRST(&head))) { - SLIST_REMOVE_HEAD(&head, tokens); - delete cur; - } -} - -UndoStack::~UndoStack() -{ - UndoToken *token, *next; - - SLIST_FOREACH_SAFE(token, &head, tokens, next) - delete token; -} diff --git a/undo.h b/undo.h deleted file mode 100644 index 43692e9..0000000 --- a/undo.h +++ /dev/null @@ -1,134 +0,0 @@ -#ifndef __UNDO_H -#define __UNDO_H - -#include - -#include -#include - -#include - -#ifdef DEBUG -#include "parser.h" -#endif - -class UndoToken { -public: - SLIST_ENTRY(UndoToken) tokens; - - gint pos; - - virtual ~UndoToken() {} - - virtual void run() = 0; -}; - -class UndoTokenMessage : public UndoToken { - unsigned int iMessage; - uptr_t wParam; - sptr_t lParam; - -public: - UndoTokenMessage(unsigned int _iMessage, - uptr_t _wParam = 0, sptr_t _lParam = 0) - : UndoToken(), iMessage(_iMessage), - wParam(_wParam), lParam(_lParam) {} - - void run(void); -}; - -template -class UndoTokenVariable : public UndoToken { - Type *ptr; - Type value; - -public: - UndoTokenVariable(Type &variable, Type _value) - : UndoToken(), ptr(&variable), value(_value) {} - - void - run(void) - { -#ifdef DEBUG - if ((State **)ptr == &States::current) - g_printf("undo state -> %p\n", (void *)value); -#endif - *ptr = value; - } -}; - -class UndoTokenString : public UndoToken { - gchar **ptr; - gchar *str; - -public: - UndoTokenString(gchar *&variable, gchar *_str) - : UndoToken(), ptr(&variable) - { - str = _str ? g_strdup(_str) : NULL; - } - - ~UndoTokenString() - { - g_free(str); - } - - void - run(void) - { - g_free(*ptr); - *ptr = str; - str = NULL; - } -}; - -extern class UndoStack { - SLIST_HEAD(Head, UndoToken) head; - -public: - bool enabled; - - UndoStack(bool _enabled = false) : enabled(_enabled) - { - SLIST_INIT(&head); - } - ~UndoStack(); - - void push(UndoToken *token); - - void push_msg(unsigned int iMessage, - uptr_t wParam = 0, sptr_t lParam = 0); - - template - inline Type & - push_var(Type &variable, Type value) - { - push(new UndoTokenVariable(variable, value)); - return variable; - } - - template - inline Type & - push_var(Type &variable) - { - return push_var(variable, variable); - } - - inline gchar *& - push_str(gchar *&variable, gchar *str) - { - push(new UndoTokenString(variable, str)); - return variable; - } - inline gchar *& - push_str(gchar *&variable) - { - return push_str(variable, variable); - } - - void pop(gint pos); - - void clear(void); -} undo; - -#endif \ No newline at end of file -- cgit v1.2.3