]> git.neil.brown.name Git - edlib.git/blobdiff - lang-python.c
TODO: clean out done items.
[edlib.git] / lang-python.c
index 743bd1c5f83d91c6e8dcf393a601c97c1839dff4..084f76434674fcd520ce9a06e6dbcba4e82e3926 100644 (file)
@@ -66,10 +66,21 @@ struct doc_ref {
 
 #include <fcntl.h>
 #include <signal.h>
+
+#define DOC_DATA_TYPE struct python_doc
+#define PANE_DATA_PTR_TYPE struct Pane *
+struct Pane;
 #include "core.h"
 #include "misc.h"
 #include "rexel.h"
 
+struct python_doc {
+       struct doc      doc;
+       struct pydoc    *pdoc;
+};
+
+#include "core-pane.h"
+
 #define SAFE_CI {.key=safe_cast NULL,\
                 .home=safe_cast NULL,\
                 .focus=safe_cast NULL,\
@@ -100,9 +111,18 @@ DEF_CMD(python_pane_call);
 DEF_CMD(python_doc_call);
 static void python_free_command(struct command *c safe);
 
-static struct python_command *export_callable(PyObject *callable safe)
+static struct python_command *export_callable(PyObject *callable safe,
+                                             PyObject *nm)
 {
        struct python_command *c;
+       const char *name = NULL;
+       int len;
+
+       if (nm && PyUnicode_Check(nm))
+               name = PyUnicode_AsUTF8(nm);
+       if (!name)
+               name = "";
+       len = strlen(name);
 
        list_for_each_entry(c, &exported_commands, lst)
                if (c->callable == callable) {
@@ -114,6 +134,12 @@ static struct python_command *export_callable(PyObject *callable safe)
        c->c = python_call;
        c->c.free = python_free_command;
        c->c.refcnt = 0;
+       if (strcmp(name, "handle_close") == 0 ||
+           (len > 10 &&
+            strcmp(name+len-10, "_closed_ok") == 0))
+               c->c.closed_ok = 1;
+       else
+               c->c.closed_ok = 0;
        command_get(&c->c);
        Py_INCREF(callable);
        c->callable = callable;
@@ -121,7 +147,7 @@ static struct python_command *export_callable(PyObject *callable safe)
        return c;
 }
 
-typedef struct {
+typedef struct Pane {
        PyObject_HEAD;
        struct pane     *pane;
        struct command  cmd;
@@ -136,13 +162,12 @@ typedef struct {
 } PaneIter;
 static PyTypeObject PaneIterType;
 
-typedef struct {
+typedef struct pydoc {
        PyObject_HEAD;
        struct pane     *pane;
        struct command  cmd;
        struct map      *map;
        int             map_init;
-       struct doc      doc;
 } Doc;
 static PyTypeObject DocType;
 
@@ -186,8 +211,8 @@ static inline PyObject *safe Pane_Frompane(struct pane *p)
                pane = p->data;
                Py_INCREF(pane);
        } else if (p && p->handle && p->handle->func == python_doc_call.func) {
-               struct doc *doc = p->data;
-               Doc *pdoc = container_of(doc, Doc, doc);
+               struct python_doc *pd = p->doc_data;
+               Doc *pdoc = pd->pdoc;
                pane = (Pane*)pdoc;
                Py_INCREF(pane);
        } else {
@@ -428,12 +453,12 @@ static int dict_add(PyObject *kwds, char *name, PyObject *val)
        return 1;
 }
 
-static void python_interrupt(int sig)
+static bool python_running;
+DEF_CB(handle_alarm)
 {
-       /* Python code has been running for too long,
-        * interrupt it.
-        */
-       kill(getpid(), 2);
+       if (python_running)
+               PyErr_SetInterrupt();
+       return 1;
 }
 
 REDEF_CB(python_call)
@@ -491,11 +516,9 @@ REDEF_CB(python_call)
                            Py_BuildValue("ii", ci->x, ci->y));
 
        if (rv && pc->callable) {
-               signal(SIGALRM, python_interrupt);
-               alarm(10);
+               python_running = True;
                ret = PyObject_Call(pc->callable, args, kwds);
-               alarm(0);
-               signal(SIGALRM, SIG_DFL);
+               python_running = False;
        }
 
        Py_DECREF(args);
@@ -510,7 +533,7 @@ REDEF_CB(python_call)
        else if (PyLong_Check(ret))
                rv = PyLong_AsLong(ret);
        else if (PyBool_Check(ret))
-               rv = (ret == Py_True);
+               rv = (ret == Py_True) ? 1 : Efalse;
        else if (PyUnicode_Check(ret) && PyUnicode_GET_LENGTH(ret) >= 1)
                rv = CHAR_RET(PyUnicode_READ_CHAR(ret, 0));
        else
@@ -546,8 +569,9 @@ static void do_map_init(Pane *self safe)
                        if (doc && doc != Py_None) {
                                PyObject *tofree = NULL;
                                char *docs = python_as_string(doc, &tofree);
+
                                if (docs &&
-                                   strncmp(docs, "handle-range", 12) == 0 &&
+                                   strstarts(docs, "handle-range") &&
                                    docs[12]) {
                                        char sep = docs[12];
                                        char *s1 = strchr(docs+13, sep);
@@ -557,7 +581,7 @@ static void do_map_init(Pane *self safe)
                                                char *b = strndup(s1+1, s2-(s1+1));
 
                                                struct python_command *comm =
-                                                       export_callable(m);
+                                                       export_callable(m, e);
                                                key_add_range(self->map, a, b,
                                                              &comm->c);
                                                free(a); free(b);
@@ -565,12 +589,12 @@ static void do_map_init(Pane *self safe)
                                        }
                                }
                                if (docs &&
-                                   strncmp(docs, "handle-prefix:", 14) == 0) {
+                                   strstarts(docs, "handle-prefix:")) {
                                        char *a = strconcat(self->pane, docs+14);
                                        char *b = strconcat(self->pane,
                                                            a, "\xFF\xFF\xFF\xFF");
                                        struct python_command *comm =
-                                               export_callable(m);
+                                               export_callable(m, e);
                                        key_add_range(self->map, a, b,
                                                      &comm->c);
                                        command_put(&comm->c);
@@ -588,24 +612,24 @@ static void do_map_init(Pane *self safe)
 
                if (m && PyMethod_Check(m)) {
                        PyObject *doc = PyObject_GetAttrString(m, "__doc__");
-                       if (doc && doc != Py_None) {
-                               PyObject *tofree = NULL;
-                               char *docs = python_as_string(doc, &tofree);
-                               if (docs &&
-                                   strncmp(docs, "handle:", 7) == 0) {
+                       char *docs;
+                       PyObject *tofree = NULL;
+                       if (doc && doc != Py_None &&
+                           (docs = python_as_string(doc, &tofree)) != NULL) {
+
+                               if (strstarts(docs, "handle:")) {
                                        struct python_command *comm =
-                                               export_callable(m);
+                                               export_callable(m, e);
                                        key_add(self->map, docs+7, &comm->c);
                                        command_put(&comm->c);
                                }
-                               if (docs &&
-                                   strncmp(docs, "handle-list", 11) == 0 &&
+                               if (strstarts(docs, "handle-list") &&
                                    docs[11]) {
                                        char sep = docs[11];
                                        char *s1 = docs + 12;
                                        while (s1 && *s1 && *s1 != sep) {
                                                struct python_command *comm =
-                                                       export_callable(m);
+                                                       export_callable(m, e);
                                                char *a;
                                                char *s2 = strchr(s1, sep);
                                                if (s2) {
@@ -620,8 +644,8 @@ static void do_map_init(Pane *self safe)
                                                command_put(&comm->c);
                                        }
                                }
-                               Py_XDECREF(tofree);
                        }
+                       Py_XDECREF(tofree);
                        Py_XDECREF(doc);
                }
                Py_XDECREF(m);
@@ -673,16 +697,14 @@ static void python_pane_free(struct command *c safe)
        if (p->map)
                key_free(p->map);
        p->map = NULL;
-       if (PyObject_TypeCheck(p, &DocType)) {
-               Doc *d = (Doc*)p;
-               doc_free(&d->doc, safe_cast pn);
-       }
+       if (pn && PyObject_TypeCheck(p, &DocType))
+               doc_free(&pn->doc_data->doc, safe_cast pn);
        if (pn)
                pane_put(pn);
        Py_DECREF(p);
 }
 
-DEF_CMD(python_close_mark)
+DEF_CMD_CLOSED(python_close_mark)
 {
        struct mark *m = ci->mark;
 
@@ -696,11 +718,10 @@ DEF_CMD(python_close_mark)
        return 1;
 }
 
-static int __Pane_init(Pane *self safe, PyObject *args, PyObject *kwds,
+static int do_Pane_init(Pane *self safe, PyObject *args, PyObject *kwds,
                       Pane **parentp safe,
                       int *zp safe)
 {
-       Pane *parent = NULL;
        int ret;
        static const char *keywords[] = {"parent", "z", NULL};
 
@@ -715,24 +736,15 @@ static int __Pane_init(Pane *self safe, PyObject *args, PyObject *kwds,
                return 0;
 
        /* Pane(parent=None, z=0) */
-       ret = PyArg_ParseTupleAndKeywords(args, kwds, "|Oi", (char**)keywords,
-                                         &parent, zp);
+       ret = PyArg_ParseTupleAndKeywords(args, kwds, "O!|i", (char**)keywords,
+                                         &PaneType, parentp, zp);
        if (ret <= 0)
                return -1;
 
-       if ((PyObject*)parent == Py_None)
-               parent = NULL;
-
-       if (parent && !PyObject_TypeCheck(parent, &PaneType)) {
-               PyErr_SetString(PyExc_TypeError, "First arg must be edlib.Pane or None");
-               return -1;
-       }
-
-       *parentp = parent;
-
        self->map = key_alloc();
        key_add(self->map, "Close:mark", &python_close_mark);
        self->cmd = python_pane_call;
+       self->cmd.closed_ok = 1;
        self->cmd.free = python_pane_free;
        if (self->ob_base.ob_type)
                self->cmd.name = self->ob_base.ob_type->tp_name;
@@ -744,37 +756,45 @@ static int Pane_init(Pane *self safe, PyObject *args, PyObject *kwds)
 {
        Pane *parent = NULL;
        int z = 0;
-       int ret = __Pane_init(self, args, kwds, &parent, &z);
+       int ret = do_Pane_init(self, args, kwds, &parent, &z);
 
        if (ret <= 0)
                return ret;
+       if (!parent || !parent->pane)
+               return -1;
 
        /* The pane holds a reference to the Pane through the ->handle
         * function
         */
        Py_INCREF(self);
-       self->pane = pane_register(parent ? parent->pane : NULL,
-                                  z, &self->cmd, self);
-       if (self->pane)
-               pane_get(self->pane);
+       self->pane = pane_register(parent->pane, z, &self->cmd, self);
+       if (!self->pane)
+               return -1;
+
+       pane_get(self->pane);
        return 0;
 }
 
 static int Doc_init(Doc *self, PyObject *args, PyObject *kwds)
 {
        Pane *parent = NULL;
+       struct python_doc *pd;
        int z = 0;
-       int ret = __Pane_init((Pane*safe)self, args, kwds, &parent, &z);
+       int ret = do_Pane_init((Pane*safe)self, args, kwds, &parent, &z);
 
-       if (ret <= 0 || !self)
+       if (ret <= 0)
                return ret;
+       if (!self || !parent || !parent->pane)
+               return -1;
 
        self->cmd.func = python_doc_call_func;
-       self->pane = __doc_register(parent ? parent->pane : NULL,
-                                   &self->cmd, &self->doc, self, 0);
-       if (self->pane)
+       self->pane = doc_register(parent->pane, &self->cmd);
+       if (self->pane) {
                pane_get(self->pane);
-       self->doc.refcnt = mark_refcnt;
+               pd = self->pane->doc_data;
+               pd->pdoc = self;
+               pd->doc.refcnt = mark_refcnt;
+       }
        return 0;
 }
 
@@ -872,22 +892,29 @@ static PyObject *Pane_clone_children(Pane *self safe, PyObject *args)
        return Py_None;
 }
 
-static PyObject *Pane_focus(Pane *self safe, PyObject *args)
+static PyObject *Pane_take_focus(Pane *self safe, PyObject *args)
 {
        if (!pane_valid(self))
                return NULL;
 
-       pane_focus(self->pane);
+       pane_take_focus(self->pane);
        Py_INCREF(Py_None);
        return Py_None;
 }
 
 static PyObject *Pane_has_focus(Pane *self safe, PyObject *args)
 {
+       Pane *other = NULL;
+       int ret;
+
        if (!pane_valid(self))
                return NULL;
 
-       if (pane_has_focus(self->pane)) {
+       ret = PyArg_ParseTuple(args, "|O!", &PaneType, &other);
+       if (ret <= 0)
+               return NULL;
+
+       if (pane_has_focus(self->pane, other ? other->pane : NULL)) {
                Py_INCREF(Py_True);
                return Py_True;
        } else {
@@ -1143,8 +1170,6 @@ static PyObject *Pane_call(Pane *self safe, PyObject *args safe, PyObject *kwds)
        int rv;
        PyObject *s1, *s2;
        struct pyret pr;
-       int remain;
-       sighandler_t oldhan;
 
        if (!pane_valid(self))
                return NULL;
@@ -1158,13 +1183,9 @@ static PyObject *Pane_call(Pane *self safe, PyObject *args safe, PyObject *kwds)
                return NULL;
        }
 
-       remain = alarm(0);
-       oldhan = signal(SIGALRM, SIG_DFL);
+       python_running = False;
        rv = key_handle(&ci);
-       if (oldhan != SIG_DFL) {
-               signal(SIGALRM, oldhan);
-               alarm(remain);
-       }
+       python_running = True;
 
        /* Just in case ... */
        PyErr_Clear();
@@ -1379,8 +1400,13 @@ static PyObject *Pane_reparent(Pane *self safe, PyObject *args)
        Pane *newparent = NULL;
        int ret = PyArg_ParseTuple(args, "O!", &PaneType, &newparent);
 
-       if (ret > 0 && newparent && self->pane && newparent->pane)
+       if (ret > 0 && newparent && self->pane && newparent->pane) {
                pane_reparent(self->pane, newparent->pane);
+               if (self->pane->parent != newparent->pane) {
+                       PyErr_SetString(PyExc_TypeError, "reparent failed");
+                       return NULL;
+               }
+       }
        Py_INCREF(Py_None);
        return Py_None;
 }
@@ -1504,9 +1530,9 @@ static const PyMethodDef pane_methods[] = {
         "provides an iterator which will iterate over all children"},
        {"clone_children", (PyCFunction)Pane_clone_children, METH_VARARGS,
         "Clone all children onto the target"},
-       {"take_focus", (PyCFunction)Pane_focus, METH_NOARGS,
+       {"take_focus", (PyCFunction)Pane_take_focus, METH_NOARGS,
         "Claim the focus for this pane"},
-       {"has_focus", (PyCFunction)Pane_has_focus, METH_NOARGS,
+       {"has_focus", (PyCFunction)Pane_has_focus, METH_VARARGS,
         "Check if pane is focus of display"},
        {"call", (void*)(PyCFunctionWithKeywords)Pane_call, METH_VARARGS|METH_KEYWORDS,
         "Call a command from a pane"},
@@ -1619,6 +1645,8 @@ static Pane *pane_getpane(Pane *p safe, char *which safe)
                new = p->pane->focus;
        if (*which == 'r')
                new = pane_root(p->pane);
+       if (*which == 'F')
+               new = pane_focus(p->pane);
        if (*which == 'L')
                new = pane_leaf(p->pane);
        if (new == NULL) {
@@ -1706,6 +1734,9 @@ static const PyGetSetDef pane_getseters[] = {
        {"root",
         (getter)pane_getpane, (setter)pane_nosetpane,
         "Root pane", "r"},
+       {"final_focus",
+        (getter)pane_getpane, (setter)pane_nosetpane,
+        "Final focus pane", "F"},
        {"leaf",
         (getter)pane_getpane, (setter)pane_nosetpane,
         "Leaf pane", "L"},
@@ -1801,7 +1832,7 @@ static PyObject *first_mark(Doc *self safe, PyObject *args)
        if (!doc_valid(self))
                return NULL;
 
-       m = mark_first(&self->doc);
+       m = mark_first(&self->pane->doc_data->doc);
        if (!m) {
                Py_INCREF(Py_None);
                return Py_None;
@@ -1858,7 +1889,7 @@ static PyObject *mark_getoffset(Mark *m safe, void *x)
                PyErr_SetString(PyExc_TypeError, "Mark is NULL");
                return NULL;
        }
-       d = m->mark->owner->data;
+       d = &m->mark->owner->doc;
        if (d->refcnt == mark_refcnt)
                return PyLong_FromLong(m->mark->ref.o);
        return PyLong_FromLong(0);
@@ -1876,7 +1907,7 @@ static int mark_setoffset(Mark *m safe, PyObject *v safe, void *x)
        val = PyLong_AsLong(v);
        if (val == -1 && PyErr_Occurred())
                return -1;
-       d = m->mark->owner->data;
+       d = &m->mark->owner->doc;
        if (d->refcnt == mark_refcnt)
                m->mark->ref.o = val;
        else {
@@ -1922,7 +1953,7 @@ static PyObject *mark_getpos(Mark *m safe, void *x)
                PyErr_SetString(PyExc_TypeError, "Mark is NULL");
                return NULL;
        }
-       d = m->mark->owner->data;
+       d = &m->mark->owner->doc;
        if (d->refcnt == mark_refcnt && m->mark->ref.c) {
                Py_INCREF(m->mark->ref.c);
                return m->mark->ref.c;
@@ -1941,7 +1972,7 @@ static int mark_setpos(Mark *m safe, PyObject *v, void *x)
                PyErr_SetString(PyExc_TypeError, "Mark is NULL");
                return -1;
        }
-       d = m->mark->owner->data;
+       d = &m->mark->owner->doc;
        if (d->refcnt != mark_refcnt) {
                PyErr_SetString(PyExc_TypeError, "Cannot set ref for non-local mark");
                return -1;
@@ -1953,12 +1984,12 @@ static int mark_setpos(Mark *m safe, PyObject *v, void *x)
         * use that instead, so that mark_same() works.
         */
        if ((m2 = mark_next(m->mark)) != NULL &&
-           ((struct doc *safe)m2->owner->data)->refcnt == mark_refcnt &&
+           m2->owner->doc.refcnt == mark_refcnt &&
            m2->ref.c != NULL && v != NULL &&
            PyObject_RichCompareBool(v, m2->ref.c, Py_EQ) == 1)
                m->mark->ref.c = m2->ref.c;
        else if ((m2 = mark_prev(m->mark)) != NULL &&
-                ((struct doc *safe)m2->owner->data)->refcnt == mark_refcnt &&
+                m2->owner->doc.refcnt == mark_refcnt &&
                 m2->ref.c != NULL && v != NULL &&
                 PyObject_RichCompareBool(v, m2->ref.c, Py_EQ) == 1)
                m->mark->ref.c = m2->ref.c;
@@ -2019,7 +2050,7 @@ static const PyGetSetDef mark_getseters[] = {
        {NULL}  /* Sentinel */
 };
 
-static Mark *mark_new(PyTypeObject *type safe, PyObject *args, PyObject *kwds)
+static Mark *Mark_new(PyTypeObject *type safe, PyObject *args, PyObject *kwds)
 {
        Mark *self;
 
@@ -2423,7 +2454,7 @@ static PyTypeObject MarkType = {
        .tp_methods     = (PyMethodDef*)mark_methods,
        .tp_getset      = (PyGetSetDef*)mark_getseters,
        .tp_init        = (initproc)Mark_init,
-       .tp_new         = (newfunc)mark_new,
+       .tp_new         = (newfunc)Mark_new,
        .tp_repr        = (reprfunc)mark_repr,
        .tp_as_number   = (PyNumberMethods*)&mark_as_num,
 };
@@ -2628,7 +2659,7 @@ static bool get_cmd_info(struct cmd_info *ci safe, PyObject *args safe, PyObject
                                return False;
                        }
                } else if (PyCallable_Check(a)) {
-                       struct python_command *pc = export_callable(a);
+                       struct python_command *pc = export_callable(a, NULL);
 
                        if (ci->comm2 == NULL)
                                ci->comm2 = &pc->c;
@@ -2791,7 +2822,7 @@ static bool get_cmd_info(struct cmd_info *ci safe, PyObject *args safe, PyObject
                                        return False;
                                }
                        } else if (PyCallable_Check(a)) {
-                               struct python_command *pc = export_callable(a);
+                               struct python_command *pc = export_callable(a, NULL);
 
                                ci->comm2 = &pc->c;
                        } else {
@@ -2911,17 +2942,25 @@ void edlib_init(struct pane *ed safe)
        PyObject *m;
        PyConfig config;
        char *argv[2]= { "edlib", NULL };
+       sighandler_t sigint;
 
        if (edlib_module_path)
                module_dir = strdup(edlib_module_path);
        else
                module_dir = ".";
 
+       /* de-register SIGINT while we initialise python,
+        * so that Python thinks that it's registration is
+        * valid - so PyErr_SetInterrupt() don't think there
+        * is a race.
+        */
+       sigint = signal(SIGINT, SIG_DFL);
        PyConfig_InitPythonConfig(&config);
        config.isolated = 1;
        PyConfig_SetBytesArgv(&config, 0, argv);
        Py_InitializeFromConfig(&config);
        PyConfig_Clear(&config);
+       signal(SIGINT, sigint);
 
        PaneType.tp_new = PyType_GenericNew;
        PaneIterType.tp_new = PyType_GenericNew;
@@ -2982,6 +3021,8 @@ void edlib_init(struct pane *ed safe)
 
        PyModule_AddIntConstant(m, "WEOF", 0x3FFFFF);
 
+       PyModule_AddIntConstant(m, "testing", edlib_testing(ed));
+
        Edlib_CommandFailed = PyErr_NewException("edlib.commandfailed", NULL, NULL);
        Py_INCREF(Edlib_CommandFailed);
        PyModule_AddObject(m, "commandfailed", Edlib_CommandFailed);
@@ -2989,6 +3030,9 @@ void edlib_init(struct pane *ed safe)
        EdlibModule = m;
        ed_pane = ed;
 
+       call_comm("global-set-command", ed, &handle_alarm,
+                 0, NULL, "fast-alarm-python");
+
        call_comm("global-set-command", ed, &python_load_module,
                  0, NULL, "global-load-modules:python");
 }