aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2014-11-21 19:36:01 +0100
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2014-11-21 19:36:01 +0100
commit16a47e287f44f4ea154f9bfc1d3a6fa083a0f43e (patch)
tree6ceb4f16b271f85a00eb2e969ad9999e34369928
parentefa646da494c659d24bc1f09936645eed1a10244 (diff)
downloadsciteco-16a47e287f44f4ea154f9bfc1d3a6fa083a0f43e.tar.gz
finally implemented the CLOSE and QUIT hooks
the QUIT hook is actually not that trivial and required some architectural changes. First, the QUIT hook execution and any error that might occurr cannot always be attached to an existing error stack frame. Thereforce, to give a stack frame for QUIT hooks and to improve the readability of error traces for ED hooks in general, a special EDHookFrame is added to every ED hook execution error. Secondly, since QUIT hooks can themselves throw errors, we cannot run it from an atexit() handler. Instead it's always called manually before __successful__ program termination. An error in a QUIT hook will result in a failure return code nevertheless. Thirdly, errors in QUIT hooks should not prevent program termination (in interactive mode), therefore they are only invoked from main() and always in batch mode. I.e. if the interactive mode is terminated (EX$$), SciTECO will switch back to batch mode and run the QUIT hook there. This is also symmetric to program startup, which is always in batch mode. This means that Interface::event_loop() no longer runs indefinitely. If it returns, this signals that the interface shut down and batch mode may be restored by SciTECO.
-rw-r--r--src/cmdline.cpp8
-rw-r--r--src/error.cpp17
-rw-r--r--src/error.h23
-rw-r--r--src/interface-curses.cpp24
-rw-r--r--src/interface-gtk.cpp44
-rw-r--r--src/main.cpp24
-rw-r--r--src/qregisters.cpp54
-rw-r--r--src/ring.cpp1
8 files changed, 151 insertions, 44 deletions
diff --git a/src/cmdline.cpp b/src/cmdline.cpp
index e28ce35..e78db60 100644
--- a/src/cmdline.cpp
+++ b/src/cmdline.cpp
@@ -20,7 +20,6 @@
#endif
#include <string.h>
-#include <stdlib.h>
#include <signal.h>
#include <glib.h>
@@ -253,10 +252,9 @@ process_edit_cmd(gchar key)
break;
}
- if (quit_requested) {
- /* FIXME */
- exit(EXIT_SUCCESS);
- }
+ if (quit_requested)
+ /* cought by user interface */
+ throw Quit();
undo.clear();
interface.ssm(SCI_EMPTYUNDOBUFFER);
diff --git a/src/error.cpp b/src/error.cpp
index 7bdd6f2..8dd4534 100644
--- a/src/error.cpp
+++ b/src/error.cpp
@@ -81,9 +81,24 @@ Error::FileFrame::display(gint nr)
}
Error::Frame *
+Error::EDHookFrame::copy() const
+{
+ /* coordinates do not matter */
+ return new EDHookFrame(type);
+}
+
+void
+Error::EDHookFrame::display(gint nr)
+{
+ interface.msg(InterfaceCurrent::MSG_INFO,
+ "#%d in \"%s\" hook execution",
+ nr, type);
+}
+
+Error::Frame *
Error::ToplevelFrame::copy() const
{
- Frame *frame = new ToplevelFrame;
+ Frame *frame = new ToplevelFrame();
frame->pos = pos;
frame->line = line;
diff --git a/src/error.h b/src/error.h
index d0849c7..9029b86 100644
--- a/src/error.h
+++ b/src/error.h
@@ -37,6 +37,12 @@ public:
ReplaceCmdline();
};
+/*
+ * Thrown as exception to signify that program
+ * should be terminated.
+ */
+class Quit {};
+
class Error {
gchar *description;
GSList *frames;
@@ -90,6 +96,23 @@ public:
void display(gint nr);
};
+ class EDHookFrame : public Frame {
+ gchar *type;
+
+ public:
+ EDHookFrame(const gchar *_type)
+ : type(g_strdup(_type)) {}
+
+ Frame *copy() const;
+
+ ~EDHookFrame()
+ {
+ g_free(type);
+ }
+
+ void display(gint nr);
+ };
+
class ToplevelFrame : public Frame {
public:
Frame *copy() const;
diff --git a/src/interface-curses.cpp b/src/interface-curses.cpp
index ad7296f..f4ea463 100644
--- a/src/interface-curses.cpp
+++ b/src/interface-curses.cpp
@@ -478,9 +478,29 @@ InterfaceCurses::event_loop_impl(void)
#ifdef EMSCRIPTEN
PDC_emscripten_set_handler(event_loop_iter, TRUE);
+ /*
+ * We must not block emscripten's main loop,
+ * instead event_loop_iter() is called asynchronously.
+ * We also must not exit the event_loop() method, since
+ * SciTECO would assume ordinary program termination.
+ * We also must not call exit() since that would run
+ * the global destructors.
+ * The following exits the main() function immediately
+ * while keeping the "runtime" alive.
+ */
+ emscripten_exit_with_live_runtime();
#else
- for (;;)
- event_loop_iter();
+ try {
+ for (;;)
+ event_loop_iter();
+ } catch (Quit) {
+ /* SciTECO termination (e.g. EX$$) */
+ }
+
+ /*
+ * Restore ordinary terminal behaviour
+ */
+ endwin();
#endif
}
diff --git a/src/interface-gtk.cpp b/src/interface-gtk.cpp
index 3421d6f..e94c42c 100644
--- a/src/interface-gtk.cpp
+++ b/src/interface-gtk.cpp
@@ -253,20 +253,10 @@ scintilla_notify(ScintillaObject *sci, uptr_t idFrom,
interface.process_notify(notify);
}
-static gboolean
-cmdline_key_pressed(GtkWidget *widget, GdkEventKey *event,
- gpointer user_data)
+static inline void
+handle_key_press(bool is_shift, bool is_ctl, guint keyval)
{
- bool is_shift = event->state & GDK_SHIFT_MASK;
- bool is_ctl = event->state & GDK_CONTROL_MASK;
-
-#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) {
+ switch (keyval) {
case GDK_Escape:
cmdline_keypress('\x1B');
break;
@@ -296,7 +286,7 @@ cmdline_key_pressed(GtkWidget *widget, GdkEventKey *event,
gchar macro_name[3+1];
g_snprintf(macro_name, sizeof(macro_name),
- "F%d", event->keyval - GDK_F1 + 1);
+ "F%d", keyval - GDK_F1 + 1);
cmdline_fnmacro(macro_name);
break;
}
@@ -316,7 +306,7 @@ cmdline_key_pressed(GtkWidget *widget, GdkEventKey *event,
* Control keys and keys with printable representation
*/
default:
- gunichar u = gdk_keyval_to_unicode(event->keyval);
+ gunichar u = gdk_keyval_to_unicode(keyval);
if (u && g_unichar_to_utf8(u, NULL) == 1) {
gchar key;
@@ -330,6 +320,30 @@ cmdline_key_pressed(GtkWidget *widget, GdkEventKey *event,
cmdline_keypress(key);
}
}
+}
+
+static gboolean
+cmdline_key_pressed(GtkWidget *widget, GdkEventKey *event,
+ gpointer user_data)
+{
+ bool is_shift = event->state & GDK_SHIFT_MASK;
+ bool is_ctl = event->state & GDK_CONTROL_MASK;
+
+#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
+
+ try {
+ handle_key_press(is_shift, is_ctl, event->keyval);
+ } catch (Quit) {
+ /*
+ * SciTECO should terminate, so we exit
+ * the main loop. event_loop() will return.
+ */
+ gtk_main_quit();
+ }
return TRUE;
}
diff --git a/src/main.cpp b/src/main.cpp
index 5697063..e7cf62b 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -308,6 +308,11 @@ main(int argc, char **argv)
/* add remaining arguments to unnamed buffer */
for (int i = 1; i < argc; i++) {
+ /*
+ * FIXME: arguments may contain line-feeds.
+ * Once SciTECO is 8-byte clear, we can add the
+ * command-line params null-terminated.
+ */
interface.ssm(SCI_APPENDTEXT, strlen(argv[i]), (sptr_t)argv[i]);
interface.ssm(SCI_APPENDTEXT, 1, (sptr_t)"\n");
}
@@ -323,6 +328,7 @@ main(int argc, char **argv)
error.add_frame(new Error::ToplevelFrame());
throw; /* forward */
}
+ QRegisters::hook(QRegisters::HOOK_QUIT);
exit(EXIT_SUCCESS);
}
@@ -331,7 +337,7 @@ main(int argc, char **argv)
/* FIXME: make quit immediate in batch/macro mode (non-UNDO)? */
if (quit_requested) {
- /* FIXME */
+ QRegisters::hook(QRegisters::HOOK_QUIT);
exit(EXIT_SUCCESS);
}
}
@@ -355,5 +361,21 @@ main(int argc, char **argv)
interface.event_loop();
+ /*
+ * Ordinary application termination:
+ * Interface is shut down, so we are
+ * in non-interactive mode again.
+ */
+ undo.enabled = false;
+ interface.ssm(SCI_EMPTYUNDOBUFFER);
+ undo.clear();
+
+ try {
+ QRegisters::hook(QRegisters::HOOK_QUIT);
+ } catch (Error &error) {
+ error.display_full();
+ exit(EXIT_FAILURE);
+ }
+
return 0;
}
diff --git a/src/qregisters.cpp b/src/qregisters.cpp
index 89b2afd..230dc5b 100644
--- a/src/qregisters.cpp
+++ b/src/qregisters.cpp
@@ -366,32 +366,46 @@ QRegisterStack::~QRegisterStack()
void
QRegisters::hook(Hook type)
{
+ static const gchar *type2name[] = {
+ /* [HOOK_ADD-1] = */ "ADD",
+ /* [HOOK_EDIT-1] = */ "EDIT",
+ /* [HOOK_CLOSE-1] = */ "CLOSE",
+ /* [HOOK_QUIT-1] = */ "QUIT",
+ };
+
QRegister *reg;
if (!(Flags::ed & Flags::ED_HOOKS))
return;
- reg = globals["ED"];
- if (!reg)
- throw Error("Undefined ED-hook register (\"ED\")");
+ try {
+ reg = globals["ED"];
+ if (!reg)
+ throw Error("Undefined ED-hook register (\"ED\")");
- /*
- * ED-hook execution should not see any
- * integer parameters but the hook type.
- * Such parameters could confuse the ED macro
- * and macro authors do not expect side effects
- * of ED macros on the expression stack.
- * Also make sure it does not leave behind
- * additional arguments on the stack.
- *
- * So this effectively executes:
- * (typeM[ED]^[)
- */
- expressions.push(Expressions::OP_BRACE);
- expressions.push(type);
- reg->execute();
- expressions.discard_args();
- expressions.eval(true);
+ /*
+ * ED-hook execution should not see any
+ * integer parameters but the hook type.
+ * Such parameters could confuse the ED macro
+ * and macro authors do not expect side effects
+ * of ED macros on the expression stack.
+ * Also make sure it does not leave behind
+ * additional arguments on the stack.
+ *
+ * So this effectively executes:
+ * (typeM[ED]^[)
+ */
+ expressions.push(Expressions::OP_BRACE);
+ expressions.push(type);
+ reg->execute();
+ expressions.discard_args();
+ expressions.eval(true);
+ } catch (Error &error) {
+ const gchar *type_str = type2name[type-1];
+
+ error.add_frame(new Error::EDHookFrame(type_str));
+ throw; /* forward */
+ }
}
void
diff --git a/src/ring.cpp b/src/ring.cpp
index 1c7087e..575d0e7 100644
--- a/src/ring.cpp
+++ b/src/ring.cpp
@@ -485,6 +485,7 @@ Ring::close(void)
{
Buffer *buffer = current;
+ QRegisters::hook(QRegisters::HOOK_CLOSE);
close(buffer);
current = buffer->next() ? : buffer->prev();
/* transfer responsibility to UndoToken object */