From b120616b6da52e951097f69ad267de06081d218a Mon Sep 17 00:00:00 2001 From: Robin Haberkorn Date: Tue, 4 Dec 2012 16:58:20 +0100 Subject: refactoring: split qbuffers.cpp|h into a q-registers (qregisters.cpp) and Buffer ring part (ring.cpp) --- Makefile | 2 +- cmdline.cpp | 3 +- interface-gtk.cpp | 3 +- interface-ncurses.cpp | 3 +- main.cpp | 3 +- parser.cpp | 3 +- qbuffers.cpp | 1057 ------------------------------------------------- qbuffers.h | 542 ------------------------- qregisters.cpp | 522 ++++++++++++++++++++++++ qregisters.h | 343 ++++++++++++++++ ring.cpp | 539 +++++++++++++++++++++++++ ring.h | 246 ++++++++++++ search.cpp | 3 +- search.h | 2 +- 14 files changed, 1664 insertions(+), 1607 deletions(-) delete mode 100644 qbuffers.cpp delete mode 100644 qbuffers.h create mode 100644 qregisters.cpp create mode 100644 qregisters.h create mode 100644 ring.cpp create mode 100644 ring.h diff --git a/Makefile b/Makefile index 91dc300..27eef53 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,7 @@ ifneq ($(findstring MINGW32,$(OS)),) CPPFLAGS+=-Icompat endif -MINIMAL_OBJS:=main.o cmdline.o undo.o expressions.o qbuffers.o \ +MINIMAL_OBJS:=main.o cmdline.o undo.o expressions.o qregisters.o ring.o \ parser.o search.o goto.o rbtree.o symbols.o \ interface.a diff --git a/cmdline.cpp b/cmdline.cpp index 80cd2d7..212722a 100644 --- a/cmdline.cpp +++ b/cmdline.cpp @@ -9,7 +9,8 @@ #include "interface.h" #include "expressions.h" #include "parser.h" -#include "qbuffers.h" +#include "qregisters.h" +#include "ring.h" #include "goto.h" #include "undo.h" #include "symbols.h" diff --git a/interface-gtk.cpp b/interface-gtk.cpp index a55a2c8..7515b2f 100644 --- a/interface-gtk.cpp +++ b/interface-gtk.cpp @@ -14,7 +14,8 @@ #include #include "sciteco.h" -#include "qbuffers.h" +#include "qregisters.h" +#include "ring.h" #include "interface.h" #include "interface-gtk.h" diff --git a/interface-ncurses.cpp b/interface-ncurses.cpp index fe2c7e7..0e08914 100644 --- a/interface-ncurses.cpp +++ b/interface-ncurses.cpp @@ -17,7 +17,8 @@ #include #include "sciteco.h" -#include "qbuffers.h" +#include "qregisters.h" +#include "ring.h" #include "interface.h" #include "interface-ncurses.h" diff --git a/main.cpp b/main.cpp index e23eca8..4526da3 100644 --- a/main.cpp +++ b/main.cpp @@ -14,7 +14,8 @@ #include "interface.h" #include "parser.h" #include "goto.h" -#include "qbuffers.h" +#include "qregisters.h" +#include "ring.h" #include "undo.h" #ifdef G_OS_UNIX diff --git a/parser.cpp b/parser.cpp index bb05d66..5ba96fa 100644 --- a/parser.cpp +++ b/parser.cpp @@ -10,7 +10,8 @@ #include "undo.h" #include "expressions.h" #include "goto.h" -#include "qbuffers.h" +#include "qregisters.h" +#include "ring.h" #include "parser.h" #include "symbols.h" #include "search.h" diff --git a/qbuffers.cpp b/qbuffers.cpp deleted file mode 100644 index 8948f46..0000000 --- a/qbuffers.cpp +++ /dev/null @@ -1,1057 +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 "goto.h" -#include "qbuffers.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; - - 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; - static QRegister *current = NULL; - static QRegisterStack stack; - - static inline void - undo_edit(void) - { - current->dot = interface.ssm(SCI_GETCURRENTPOS); - undo.push_var(current)->undo_edit(); - } -} - -static QRegister *register_argument = NULL; - -Ring ring; - -/* 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; -} - -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(); -} - -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)); - } - - 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; -} - -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/qbuffers.h b/qbuffers.h deleted file mode 100644 index d9c1b94..0000000 --- a/qbuffers.h +++ /dev/null @@ -1,542 +0,0 @@ -#ifndef __QBUFFERS_H -#define __QBUFFERS_H - -#include -#include - -#include -#include -#include - -#include - -#include "sciteco.h" -#include "interface.h" -#include "undo.h" -#include "rbtree.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 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); -}; - -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); -}; - -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 StateEditFile editfile; - extern StateSaveFile savefile; - - 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; - - enum Hook { - HOOK_ADD = 1, - HOOK_EDIT, - HOOK_CLOSE, - HOOK_QUIT - }; - void hook(Hook type); -} - -#endif diff --git a/qregisters.cpp b/qregisters.cpp new file mode 100644 index 0000000..06947f7 --- /dev/null +++ b/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/qregisters.h b/qregisters.h new file mode 100644 index 0000000..e810b7f --- /dev/null +++ b/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/ring.cpp b/ring.cpp new file mode 100644 index 0000000..f1b051b --- /dev/null +++ b/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/ring.h b/ring.h new file mode 100644 index 0000000..da4f322 --- /dev/null +++ b/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/search.cpp b/search.cpp index f27e744..701f36c 100644 --- a/search.cpp +++ b/search.cpp @@ -6,7 +6,8 @@ #include "sciteco.h" #include "expressions.h" #include "undo.h" -#include "qbuffers.h" +#include "qregisters.h" +#include "ring.h" #include "parser.h" #include "search.h" diff --git a/search.h b/search.h index 12b95db..2bddedd 100644 --- a/search.h +++ b/search.h @@ -5,7 +5,7 @@ #include "sciteco.h" #include "parser.h" -#include "qbuffers.h" +#include "ring.h" /* * "S" command state and base class for all other search/replace commands -- cgit v1.2.3