]> git.neil.brown.name Git - edlib.git/blobdiff - lang-python.c
TODO: clean out done items.
[edlib.git] / lang-python.c
index ffb547d52716c15eb540d89df11abb28cbfab958..084f76434674fcd520ce9a06e6dbcba4e82e3926 100644 (file)
@@ -1,12 +1,12 @@
 /*
- * Copyright Neil Brown ©2015-2020 <neil@brown.name>
+ * Copyright Neil Brown ©2015-2023 <neil@brown.name>
  * May be distributed under terms of GPLv2 - see file:COPYING
  *
  * Python3 bindings for edlib.
- * An edlib command "python-load" will read and execute a python
- * script.
- * It can use "edlib.editor" to get the editor instance, and can
- * use "edlib.call()" to issue edlib commands.
+ * An edlib command "global-load-modules:python" will read and execute
+ * a python module.
+ * It must "import edlib" and it can use "edlib.editor" to get the editor
+ * instance.
  *
  * Types available are:
  *  edlib.pane  - a generic pane.  These form a tree of which edlib.editor
 #define __linux__
 #define __x86_64__
 #define __LP64__
+
+//#define PyType_HasFeature __PyType_HasFeature
 #include <Python.h>
+#undef PyType_HasFeature __PyType_HasFeature
+int PyType_HasFeature(PyTypeObject *type, unsigned long feature);
+
 #undef Py_INCREF
 #define Py_INCREF(op) (0)
 #undef Py_DECREF
 #define Py_DECREF(op) (0)
 #undef Py_XDECREF
 #define Py_XDECREF(op) (0)
-#else
+#undef Py_IS_TYPE
+#define Py_IS_TYPE(ob, type) (ob == (void*)type)
+#else /* CHECKER */
 #include <Python.h>
 #endif
-#define MARK_DATA_PTR PyObject
+struct Mark;
+#define MARK_DATA_PTR struct Mark
 #define PRIVATE_DOC_REF
 
 struct doc_ref {
        PyObject *c;
        int o;
 };
+
+#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,\
@@ -88,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) {
@@ -102,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;
@@ -109,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;
@@ -124,17 +162,16 @@ 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;
 
-typedef struct {
+typedef struct Mark {
        PyObject_HEAD;
        struct mark     *mark;
 } Mark;
@@ -147,24 +184,24 @@ typedef struct {
 } Comm;
 static PyTypeObject CommType;
 
-static inline int pane_valid(Pane *p safe)
+static inline bool pane_valid(Pane *p safe)
 {
        if (p->pane && p->pane->handle)
-               return 1;
+               return True;
        PyErr_SetString(PyExc_TypeError, "Pane has been freed");
-       return 0;
+       return False;
 }
 
-static inline int doc_valid(Doc *p safe)
+static inline bool doc_valid(Doc *p safe)
 {
        if (p->pane && p->pane->handle)
-               return 1;
+               return True;
        PyErr_SetString(PyExc_TypeError, "Doc pane has been freed");
-       return 0;
+       return False;
 }
 
-static int get_cmd_info(struct cmd_info *ci safe, PyObject *args safe, PyObject *kwds,
-                       PyObject **s1 safe, PyObject **s2 safe);
+static bool get_cmd_info(struct cmd_info *ci safe, PyObject *args safe, PyObject *kwds,
+                        PyObject **s1 safe, PyObject **s2 safe);
 
 static int in_pane_frompane = 0;
 static inline PyObject *safe Pane_Frompane(struct pane *p)
@@ -174,15 +211,19 @@ 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 {
                in_pane_frompane = 1;
-               pane = (Pane * safe)PyObject_CallObject((PyObject*)&PaneType, NULL);
+               pane = (Pane *)PyObject_CallObject((PyObject*)&PaneType, NULL);
                in_pane_frompane = 0;
-               if (pane)
+               if (!pane)
+                       ;
+               else if (p)
+                       pane->pane = pane_get(p);
+               else
                        pane->pane = p;
        }
        return (PyObject*)pane;
@@ -192,20 +233,22 @@ static inline PyObject *safe Mark_Frommark(struct mark *m safe)
 {
        Mark *mark;
 
-       if (m->mtype == &MarkType && m->mdata) {
+       if (mark_valid(m) && m->mtype == &MarkType && m->mdata) {
                /* This is a vmark, re-use the PyObject */
                Py_INCREF(m->mdata);
-               return m->mdata;
+               return (PyObject*)m->mdata;
        }
-       mark = (Mark * safe)PyObject_CallObject((PyObject*)&MarkType, NULL);
-       mark->mark = m;
+       mark = (Mark *)PyObject_CallObject((PyObject*)&MarkType, NULL);
+       if (mark && mark_valid(m))
+               mark->mark = m;
        return (PyObject*)mark;
 }
 
 static inline PyObject *safe Comm_Fromcomm(struct command *c safe)
 {
-       Comm *comm = (Comm*safe)PyObject_CallObject((PyObject*)&CommType, NULL);
-       comm->comm = command_get(c);
+       Comm *comm = (Comm *)PyObject_CallObject((PyObject*)&CommType, NULL);
+       if (comm)
+               comm->comm = command_get(c);
        return (PyObject*)comm;
 }
 
@@ -229,6 +272,8 @@ static void PyErr_LOG(void)
 
        PyErr_Fetch(&exc_typ, &exc_val, &exc_tb);
        PyErr_NormalizeException(&exc_typ, &exc_val, &exc_tb);
+       if (exc_tb)
+               PyException_SetTraceback(exc_val, exc_tb);
 
        /* Import the modules we need - StringIO and traceback */
        errorMsg = "Can't import io";
@@ -312,76 +357,73 @@ out:
        Py_XDECREF(exc_tb);
 }
 
-DEF_CMD(python_load)
-{
-       const char *fname = ci->str;
-       FILE *fp;
-       PyObject *globals, *main_mod;
-       PyObject *Ed;
-
-       if (!fname)
-               return Enoarg;
-       fp = fopen(fname, "r");
-       if (!fp)
-               return Efail;
-
-       main_mod = PyImport_AddModule("__main__");
-       if (main_mod == NULL)
-               return Einval;
-       globals = PyModule_GetDict(main_mod);
-
-       Ed = Pane_Frompane(ci->home);
-       PyDict_SetItemString(globals, "editor", Ed);
-       PyDict_SetItemString(globals, "edlib", EdlibModule);
-       PyRun_FileExFlags(fp, fname, Py_file_input, globals, globals, 0, NULL);
-       PyErr_LOG();
-       Py_DECREF(Ed);
-       fclose(fp);
-       return 1;
-}
-
 DEF_CMD(python_load_module)
 {
        const char *name = ci->str;
-       FILE *fp;
-       PyObject *globals, *main_mod;
-       PyObject *Ed;
-       char buf [PATH_MAX];
+       int fd;
+       long long size;
+       char *code;
+       char buf[PATH_MAX];
+       char buf2[PATH_MAX];
+       PyObject *builtins, *compile, *args, *bytecode;
 
        if (!name)
                return Enoarg;
        snprintf(buf, sizeof(buf), "%s/python/%s.py", module_dir, name);
-       fp = fopen(buf, "r");
-       if (!fp)
+       fd = open(buf, O_RDONLY);
+       if (fd < 0)
                return Efail;
+       size = lseek(fd, 0, SEEK_END);
+       lseek(fd, 0, SEEK_SET);
+       code = malloc(size+1);
+       if (!code) {
+               close(fd);
+               return Efail;
+       }
+       size = read(fd, code, size);
+       close(fd);
+       if (size <= 0) {
+               free(code);
+               return Efail;
+       }
+       code[size] = 0;
 
        LOG("Loading python module %s from %s", name, buf);
-       main_mod = PyImport_AddModule("__main__");
-       if (main_mod == NULL)
-               return Einval;
-       globals = PyModule_GetDict(main_mod);
-
-       Ed = Pane_Frompane(ci->home);
-       PyDict_SetItemString(globals, "editor", Ed);
-       PyDict_SetItemString(globals, "pane", Pane_Frompane(ci->focus));
-       PyDict_SetItemString(globals, "edlib", EdlibModule);
-       PyRun_FileExFlags(fp, buf, Py_file_input, globals, globals, 0, NULL);
-       PyErr_LOG();
-       Py_DECREF(Ed);
-       fclose(fp);
+
+       builtins = PyEval_GetBuiltins();
+       compile = PyDict_GetItemString(builtins, "compile");
+       args = safe_cast Py_BuildValue("(sss)", code, buf, "exec");
+       bytecode = PyObject_Call(compile, args, NULL);
+       Py_DECREF(args);
+       free(code);
+       if (bytecode == NULL) {
+               PyErr_LOG();
+               return Efail;
+       }
+
+       snprintf(buf2, sizeof(buf2), "edlib.%s", name);
+
+       if (PyImport_ExecCodeModule(buf2, bytecode) == NULL)
+               PyErr_LOG();
+       Py_DECREF(bytecode);
        return 1;
 }
 
-static PyObject *safe python_string(const char *s safe)
+static PyObject *safe python_string(const char *s safe, int len)
 {
        const char *c = s;
-       while (*c && !(*c & 0x80))
+       const char *e = NULL;
+       wint_t wch;
+
+       if (len >= 0)
+               e = s + len;
+       while ((!e || c < e) && *c && !(*c & 0x80))
                c++;
-       if (*c)
-               /* must be Unicode */
-               return safe_cast PyUnicode_DecodeUTF8(s, strlen(s), NULL);
-       else
-               return safe_cast Py_BuildValue("s", s);
+       while ((wch = get_utf8(&c, e)) != WEOF)
+               if (wch == WERR || wch > 0x10FFFF)
+                       break;
+
+       return safe_cast PyUnicode_DecodeUTF8(s, c - s, NULL);
 }
 
 static char *python_as_string(PyObject *s, PyObject **tofree safe)
@@ -411,11 +453,25 @@ static int dict_add(PyObject *kwds, char *name, PyObject *val)
        return 1;
 }
 
+static bool python_running;
+DEF_CB(handle_alarm)
+{
+       if (python_running)
+               PyErr_SetInterrupt();
+       return 1;
+}
+
 REDEF_CB(python_call)
 {
        struct python_command *pc = container_of(ci->comm, struct python_command, c);
-       PyObject *ret = NULL, *args, *kwds;
+       PyObject *ret = NULL, *args, *kwds, *str;
        int rv = 1;
+       bool unterminated = False;
+       int klen = strlen(ci->key);
+
+       if (klen > 13 &&
+           strcmp(ci->key + klen - 13, " unterminated") == 0)
+               unterminated = True;
 
        args = safe_cast Py_BuildValue("(s)", ci->key);
        kwds = PyDict_New();
@@ -428,11 +484,23 @@ REDEF_CB(python_call)
        rv = rv && dict_add(kwds, "mark2",
                            ci->mark2 ? Mark_Frommark(ci->mark2):
                            (Py_INCREF(Py_None), Py_None));
-       rv = rv && dict_add(kwds, "str",
-                           ci->str ? python_string(ci->str):
-                           (Py_INCREF(Py_None), safe_cast Py_None));
+
+       if (ci->str)
+               str = python_string(ci->str, unterminated ? ci->num2 : -1);
+       else {
+               str = Py_None;
+               Py_INCREF(Py_None);
+       }
+       if (str) {
+               dict_add(kwds, "str", str);
+               dict_add(kwds, "str1", str);
+               Py_INCREF(str);
+       } else {
+               rv = 0;
+       }
+
        rv = rv && dict_add(kwds, "str2",
-                           ci->str2 ? python_string(ci->str2):
+                           ci->str2 ? python_string(ci->str2, -1):
                            (Py_INCREF(Py_None), safe_cast Py_None));
        rv = rv && dict_add(kwds, "comm", Comm_Fromcomm(ci->comm));
        rv = rv && dict_add(kwds, "comm2",
@@ -447,8 +515,11 @@ REDEF_CB(python_call)
        rv = rv && dict_add(kwds, "xy",
                            Py_BuildValue("ii", ci->x, ci->y));
 
-       if (rv && pc->callable)
+       if (rv && pc->callable) {
+               python_running = True;
                ret = PyObject_Call(pc->callable, args, kwds);
+               python_running = False;
+       }
 
        Py_DECREF(args);
        Py_DECREF(kwds);
@@ -458,12 +529,12 @@ REDEF_CB(python_call)
                return Efail;
        }
        if (ret == Py_None)
-               rv = 0;
+               rv = Efallthrough;
        else if (PyLong_Check(ret))
                rv = PyLong_AsLong(ret);
        else if (PyBool_Check(ret))
-               rv = (ret == Py_True);
-       else if (PyUnicode_Check(ret) && PyUnicode_GET_SIZE(ret) >= 1)
+               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
                rv = 1;
@@ -488,6 +559,7 @@ static void do_map_init(Pane *self safe)
        if (!self->map || !self->pane || !l)
                return;
        n = PyList_Size(l);
+       /* First add the ranges, so individuals can over-ride them */
        for (i = 0; i < n ; i++) {
                PyObject *e = PyList_GetItem(l, i);
                PyObject *m = PyObject_GetAttr((PyObject*)self, e);
@@ -497,15 +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:", 7) == 0) {
-                                       struct python_command *comm =
-                                               export_callable(m);
-                                       key_add(self->map, docs+7, &comm->c);
-                                       command_put(&comm->c);
-                               }
-                               if (docs &&
-                                   strncmp(docs, "handle-range", 12) == 0 &&
+                                   strstarts(docs, "handle-range") &&
                                    docs[12]) {
                                        char sep = docs[12];
                                        char *s1 = strchr(docs+13, sep);
@@ -515,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);
@@ -523,24 +589,47 @@ 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);
                                }
-                               if (docs &&
-                                   strncmp(docs, "handle-list", 11) == 0 &&
+                               Py_XDECREF(tofree);
+                       }
+                       Py_XDECREF(doc);
+               }
+               Py_XDECREF(m);
+       }
+       /* Now add the non-ranges */
+       for (i = 0; i < n ; i++) {
+               PyObject *e = PyList_GetItem(l, i);
+               PyObject *m = PyObject_GetAttr((PyObject*)self, e);
+
+               if (m && PyMethod_Check(m)) {
+                       PyObject *doc = PyObject_GetAttrString(m, "__doc__");
+                       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, e);
+                                       key_add(self->map, docs+7, &comm->c);
+                                       command_put(&comm->c);
+                               }
+                               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) {
@@ -555,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);
@@ -602,55 +691,63 @@ static Doc *Doc_new(PyTypeObject *type safe, PyObject *args, PyObject *kwds)
 static void python_pane_free(struct command *c safe)
 {
        Pane *p = container_of(c, Pane, cmd);
+       struct pane *pn = p->pane;
        /* pane has been closed */
        p->pane = NULL;
        if (p->map)
                key_free(p->map);
        p->map = NULL;
-       if (PyObject_TypeCheck(p, &DocType)) {
-               Doc *d = (Doc*)p;
-               doc_free(&d->doc);
-       }
+       if (pn && PyObject_TypeCheck(p, &DocType))
+               doc_free(&pn->doc_data->doc, safe_cast pn);
+       if (pn)
+               pane_put(pn);
        Py_DECREF(p);
 }
 
-static int __Pane_init(Pane *self safe, PyObject *args, PyObject *kwds,
+DEF_CMD_CLOSED(python_close_mark)
+{
+       struct mark *m = ci->mark;
+
+       if (m && m->viewnum >= 0 && m->mtype == &MarkType && m->mdata) {
+               Mark *M = m->mdata;
+               m->mdata = NULL;
+               m->mtype = NULL;
+               M->mark = NULL;
+               Py_DECREF(M);
+       }
+       return 1;
+}
+
+static int do_Pane_init(Pane *self safe, PyObject *args, PyObject *kwds,
                       Pane **parentp safe,
                       int *zp safe)
 {
-       Pane *parent = NULL;
        int ret;
-       static char *keywords[] = {"parent", "z", NULL};
+       static const char *keywords[] = {"parent", "z", NULL};
 
        if (self->pane) {
                PyErr_SetString(PyExc_TypeError, "Pane already initialised");
                return -1;
        }
-       /* Pane(parent, handler, data, z=0 */
        if (in_pane_frompane)
                /* An internal Pane_Frompane call - it will set .pane,
                 * and we don't want a .handler.
                 */
                return 0;
 
-       ret = PyArg_ParseTupleAndKeywords(args, kwds, "|Oi", keywords,
-                                         &parent, zp);
+       /* Pane(parent=None, z=0) */
+       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;
 
        return 1;
 }
@@ -659,33 +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);
+       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);
-       self->doc.refcnt = mark_refcnt;
+       self->pane = doc_register(parent->pane, &self->cmd);
+       if (self->pane) {
+               pane_get(self->pane);
+               pd = self->pane->doc_data;
+               pd->pdoc = self;
+               pd->doc.refcnt = mark_refcnt;
+       }
        return 0;
 }
 
@@ -695,9 +804,43 @@ static inline void do_free(PyObject *ob safe)
                ob->ob_type->tp_free(ob);
 }
 
+DEF_CMD(python_null_call)
+{
+       return 1;
+}
+
+static void python_pane_free_final(struct command *c safe)
+{
+       Pane *p = container_of(c, Pane, cmd);
+
+       if (p->pane)
+               pane_put(p->pane);
+       python_pane_free(c);
+       do_free((PyObject*safe)p);
+}
+
 static void pane_dealloc(Pane *self safe)
 {
-       do_free((PyObject*safe)self);
+       struct pane *p = self->pane;
+
+       /* if initialization failed, then dealloc happens before the
+        * pane gets closed.  In that case we need to modify the
+        * free sequence so do_free() gets called after the close.
+        */
+
+       if (p && p->handle && p->handle->func == python_pane_call.func) {
+               p->handle = &python_null_call;
+               p->handle->free = python_pane_free_final;
+               pane_close(p);
+       } else if (p && p->handle && p->handle->func == python_doc_call.func) {
+               p->handle = &python_null_call;
+               p->handle->free = python_pane_free_final;
+               pane_close(p);
+       } else {
+               if (p)
+                       pane_put(p);
+               do_free((PyObject*safe)self);
+       }
 }
 
 static PyObject *pane_children(Pane *self safe, PyObject *args)
@@ -749,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 {
@@ -773,16 +923,6 @@ static PyObject *Pane_has_focus(Pane *self safe, PyObject *args)
        }
 }
 
-static PyObject *Pane_refresh(Pane *self safe, PyObject *args)
-{
-       if (!pane_valid(self))
-               return NULL;
-
-       pane_refresh(self->pane);
-       Py_INCREF(Py_None);
-       return Py_None;
-}
-
 static PaneIter *pane_this_iter(PaneIter *self safe)
 {
        Py_INCREF(self);
@@ -829,9 +969,15 @@ DEF_CB(take_mark)
 
        if (pr->ret)
                return Einval;
-       if (!ci->mark)
+       if (!mark_valid(ci->mark))
                return Efallthrough;
-       pr->ret = Mark_Frommark(ci->mark);
+       if (ci->mark->viewnum == MARK_UNGROUPED) {
+               /* Cannot rely on this mark persisting, take a copy */
+               struct mark *m = mark_dup(ci->mark);
+               pr->ret = Mark_Frommark(m);
+               m->mtype = (void*)pr->ret;
+       } else
+               pr->ret = Mark_Frommark(ci->mark);
        return 1;
 }
 
@@ -841,12 +987,36 @@ DEF_CB(take_mark2)
 
        if (pr->ret)
                return Einval;
-       if (!ci->mark2)
+       if (!mark_valid(ci->mark2))
                return Efallthrough;
        pr->ret = Mark_Frommark(ci->mark2);
        return 1;
 }
 
+DEF_CB(take_2_marks)
+{
+       struct pyret *pr = container_of(ci->comm, struct pyret, comm);
+       PyObject *m1, *m2;
+
+       if (pr->ret)
+               return Einval;
+       if (mark_valid(ci->mark))
+               m1 = Mark_Frommark(ci->mark);
+       else
+               m1 = Py_None;
+       if (mark_valid(ci->mark2))
+               m2 = Mark_Frommark(ci->mark2);
+       else
+               m2 = Py_None;
+
+       pr->ret = Py_BuildValue("OO", m1, m2);
+       if (mark_valid(ci->mark))
+               Py_DECREF(m1);
+       if (mark_valid(ci->mark2))
+               Py_DECREF(m2);
+       return 1;
+}
+
 DEF_CB(take_str)
 {
        struct pyret *pr = container_of(ci->comm, struct pyret, comm);
@@ -855,7 +1025,19 @@ DEF_CB(take_str)
                return Einval;
        if (!ci->str)
                return Efallthrough;
-       pr->ret = python_string(ci->str);
+       pr->ret = python_string(ci->str, -1);
+       return 1;
+}
+
+DEF_CB(take_bytes)
+{
+       struct pyret *pr = container_of(ci->comm, struct pyret, comm);
+
+       if (pr->ret)
+               return Einval;
+       if (!ci->str)
+               return Efallthrough;
+       pr->ret = safe_cast PyBytes_FromStringAndSize(ci->str, ci->num);
        return 1;
 }
 
@@ -873,14 +1055,18 @@ DEF_CB(take_comm)
 
 static struct command *map_ret(char *ret safe)
 {
-       if (strcmp(ret, "focus") == 0)
+       if (strcmp(ret, "pane") == 0)
                return &take_focus;
        if (strcmp(ret, "mark") == 0)
                return &take_mark;
        if (strcmp(ret, "mark2") == 0)
                return &take_mark2;
+       if (strcmp(ret, "marks") == 0)
+               return &take_2_marks;
        if (strcmp(ret, "str") == 0)
                return &take_str;
+       if (strcmp(ret, "bytes") == 0)
+               return &take_bytes;
        if (strcmp(ret, "comm") == 0)
                return &take_comm;
        return NULL;
@@ -926,9 +1112,34 @@ static bool handle_ret(PyObject *kwds, struct cmd_info *ci safe,
        return True;
 }
 
+static void set_err(int rv)
+{
+       switch(rv) {
+       case Enoarg:
+               PyErr_SetObject(Edlib_CommandFailed,
+                               PyUnicode_FromFormat("Enoarg"));
+               break;
+       case Einval:
+               PyErr_SetObject(Edlib_CommandFailed,
+                               PyUnicode_FromFormat("Einval"));
+               break;
+       case Enosup:
+               PyErr_SetObject(Edlib_CommandFailed,
+                               PyUnicode_FromFormat("Enosup"));
+               break;
+       case Efail:
+               PyErr_SetObject(Edlib_CommandFailed,
+                               PyUnicode_FromFormat("Efail"));
+               break;
+       default:
+               PyErr_SetObject(Edlib_CommandFailed,
+                               PyUnicode_FromFormat("%d", rv));
+       }
+}
+
 static PyObject *choose_ret(int rv, struct pyret *pr safe)
 {
-       if (pr->comm.func && rv >= 0) {
+       if (pr->comm.func && rv >= Efalse) {
                if (pr->ret)
                        return pr->ret;
                Py_INCREF(Py_None);
@@ -936,7 +1147,7 @@ static PyObject *choose_ret(int rv, struct pyret *pr safe)
        }
        Py_XDECREF(pr->ret);
        if (rv < Efalse) {
-               PyErr_SetObject(Edlib_CommandFailed, PyLong_FromLong(rv));
+               set_err(rv);
                return NULL;
        }
        if (pr->return_char) {
@@ -948,7 +1159,7 @@ static PyObject *choose_ret(int rv, struct pyret *pr safe)
                        Py_INCREF(Py_None);
                        return Py_None;
                }
-               return PyUnicode_FromFormat("%c", rv & 0xFFFFF);
+               return PyUnicode_FromFormat("%c", rv & 0x1FFFFF);
        }
        return PyLong_FromLong(rv);
 }
@@ -965,15 +1176,16 @@ static PyObject *Pane_call(Pane *self safe, PyObject *args safe, PyObject *kwds)
 
        ci.home = self->pane;
 
-       rv = get_cmd_info(&ci, args, kwds, &s1, &s2);
-
-       if (rv <= 0 || !handle_ret(kwds, &ci, &pr)) {
+       if (!get_cmd_info(&ci, args, kwds, &s1, &s2) ||
+           !handle_ret(kwds, &ci, &pr)) {
                Py_XDECREF(s1); Py_XDECREF(s2);
                command_put(ci.comm2);
                return NULL;
        }
 
+       python_running = False;
        rv = key_handle(&ci);
+       python_running = True;
 
        /* Just in case ... */
        PyErr_Clear();
@@ -996,9 +1208,8 @@ static PyObject *pane_direct_call(Pane *self safe, PyObject *args safe, PyObject
 
        ci.home = self->pane;
 
-       rv = get_cmd_info(&ci, args, kwds, &s1, &s2);
-
-       if (rv <= 0 || !handle_ret(kwds, &ci, &pr)) {
+       if (!get_cmd_info(&ci, args, kwds, &s1, &s2) ||
+           !handle_ret(kwds, &ci, &pr)) {
                Py_XDECREF(s1); Py_XDECREF(s2);
                command_put(ci.comm2);
                return NULL;
@@ -1023,9 +1234,7 @@ static PyObject *Pane_notify(Pane *self safe, PyObject *args safe, PyObject *kwd
 
        ci.home = self->pane;
 
-       rv = get_cmd_info(&ci, args, kwds, &s1, &s2);
-
-       if (rv <= 0) {
+       if (!get_cmd_info(&ci, args, kwds, &s1, &s2)) {
                Py_XDECREF(s1); Py_XDECREF(s2);
                command_put(ci.comm2);
                return NULL;
@@ -1038,7 +1247,7 @@ static PyObject *Pane_notify(Pane *self safe, PyObject *args safe, PyObject *kwd
        Py_XDECREF(s1); Py_XDECREF(s2);
        command_put(ci.comm2);
        if (rv < Efalse) {
-               PyErr_SetObject(Edlib_CommandFailed, PyLong_FromLong(rv));
+               set_err(rv);
                return NULL;
        }
        return PyLong_FromLong(rv);
@@ -1133,6 +1342,28 @@ static PyObject *Pane_get_scale(Pane *self safe, PyObject *args)
        return Py_BuildValue("ii", xy.x, xy.y);
 }
 
+static PyObject *Pane_set_time(Pane *self safe, PyObject *args)
+{
+       struct pane *p = self->pane;
+       if (p)
+               pane_set_time(p);
+       Py_INCREF(Py_None);
+       return Py_None;
+}
+
+static PyObject *Pane_too_long(Pane *self safe, PyObject *args)
+{
+       struct pane *p = self->pane;
+
+       if (!p || pane_too_long(p, 0)) {
+               Py_INCREF(Py_True);
+               return Py_True;
+       } else {
+               Py_INCREF(Py_False);
+               return Py_False;
+       }
+}
+
 static PyObject *Pane_mychild(Pane *self safe, PyObject *args)
 {
        Pane *child = NULL;
@@ -1152,12 +1383,14 @@ static PyObject *Pane_clip(Pane *self safe, PyObject *args)
 {
        Mark *start = NULL, *end = NULL;
        int view = -1;
-       int ret = PyArg_ParseTuple(args, "i|O!O!", &view, &MarkType, &start,
-                                  &MarkType, &end);
+       int tostart = 0;
+       int ret = PyArg_ParseTuple(args, "iO!O!|i", &view, &MarkType, &start,
+                                  &MarkType, &end, &tostart);
 
        if (ret > 0 && start && end && self->pane &&
            start->mark && end->mark && view >= 0)
-               marks_clip(self->pane, start->mark, end->mark, view, self->pane);
+               marks_clip(self->pane, start->mark, end->mark, view, self->pane,
+                          !!tostart);
        Py_INCREF(Py_None);
        return Py_None;
 }
@@ -1167,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;
 }
@@ -1196,12 +1434,15 @@ static PyObject *Pane_step(Pane *self safe, PyObject *args, int dir, int move)
 
        if (!pane_valid(self))
                return NULL;
-       if (ret <= 0 || !m) {
+       if (ret <= 0 || !m || !mark_valid(m->mark)) {
                PyErr_SetString(PyExc_TypeError, "Arg must be a mark");
                return NULL;
        }
 
-       wch = doc_step(self->pane, m->mark, dir, move);
+       if (move)
+               wch = doc_move(self->pane, m->mark, dir);
+       else
+               wch = doc_pending(self->pane, m->mark, dir);
        if (wch == WEOF) {
                Py_INCREF(Py_None);
                return Py_None;
@@ -1216,7 +1457,7 @@ static PyObject *Pane_step_next(Pane *self safe, PyObject *args)
 
 static PyObject *Pane_step_prev(Pane *self safe, PyObject *args)
 {
-       return Pane_step(self, args, 0, 1);
+       return Pane_step(self, args, -1, 1);
 }
 
 static PyObject *Pane_step_following(Pane *self safe, PyObject *args)
@@ -1226,22 +1467,73 @@ static PyObject *Pane_step_following(Pane *self safe, PyObject *args)
 
 static PyObject *Pane_step_prior(Pane *self safe, PyObject *args)
 {
-       return Pane_step(self, args, 0, 0);
+       return Pane_step(self, args, -1, 0);
+}
+
+static PyObject *Pane_get_vmarks(Pane *self safe, PyObject *args)
+{
+       struct pyret pr;
+       Pane *owner = NULL;
+       int view = -1;
+       int ret;
+
+       if (!pane_valid(self))
+               return NULL;
+       ret = PyArg_ParseTuple(args, "i|O!", &view, &PaneType, &owner);
+       if (ret <= 0 || view < 0 || (owner && !pane_valid(owner))) {
+               Py_INCREF(Py_None);
+               return Py_None;
+       }
+       memset(&pr, 0, sizeof(pr));
+       pr.comm = take_2_marks;
+       home_call_comm(self->pane, "doc:vmark-get",
+                      owner ? owner->pane : self->pane,
+                      &pr.comm, view);
+       if (pr.ret)
+               return pr.ret;
+       Py_INCREF(Py_None);
+       return Py_None;
+}
+
+static PyObject *Pane_vmark_at_or_before(Pane *self safe, PyObject *args)
+{
+       Mark *m = NULL;
+       Pane *owner = NULL;
+       struct pyret pr;
+       int view = -1;
+       int ret;
+
+       if (!pane_valid(self))
+               return NULL;
+       ret = PyArg_ParseTuple(args, "iO!|O!", &view, &MarkType, &m,
+                              &PaneType, &owner);
+       if (ret <= 0 || view < 0 || !m || !mark_valid(m->mark) ||
+           (owner && !pane_valid(owner))) {
+               Py_INCREF(Py_None);
+               return Py_None;
+       }
+       memset(&pr, 0, sizeof(pr));
+       pr.comm = take_mark;
+       home_call_comm(self->pane, "doc:vmark-prev",
+                      owner ? owner->pane : self->pane,
+                      &pr.comm, view, m->mark);
+       if (pr.ret)
+               return pr.ret;
+       Py_INCREF(Py_None);
+       return Py_None;
 }
 
-static PyMethodDef pane_methods[] = {
+static const PyMethodDef pane_methods[] = {
        {"close", (PyCFunction)Pane_close, METH_NOARGS,
         "close the pane"},
        {"children", (PyCFunction)pane_children, METH_NOARGS,
         "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"},
-       {"refresh", (PyCFunction)Pane_refresh, METH_NOARGS,
-        "Trigger refresh on this pane"},
        {"call", (void*)(PyCFunctionWithKeywords)Pane_call, METH_VARARGS|METH_KEYWORDS,
         "Call a command from a pane"},
        {"notify", (void*)(PyCFunctionWithKeywords)Pane_notify, METH_VARARGS|METH_KEYWORDS,
@@ -1274,6 +1566,14 @@ static PyMethodDef pane_methods[] = {
         "returning the character after mark"},
        {"prior", (PyCFunction)Pane_step_prior, METH_VARARGS,
         "returning the character before mark"},
+       {"vmarks", (PyCFunction)Pane_get_vmarks, METH_VARARGS,
+        "return first and last vmark given view number"},
+       {"vmark_at_or_before", (PyCFunction)Pane_vmark_at_or_before, METH_VARARGS,
+        "return vmark at-or-before given mark"},
+       {"set_time", (PyCFunction)Pane_set_time, METH_NOARGS,
+        "Set start time for long running operation"},
+       {"too_long", (PyCFunction)Pane_too_long, METH_NOARGS,
+        "Check if command in pane has been running for too long"},
        {NULL}
 };
 
@@ -1345,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) {
@@ -1369,7 +1671,7 @@ static PyObject *pane_repr(Pane *self safe)
        if (!pane_valid(self))
                asprintf(&s, "<edlib.Pane FREED!!! %p>", self);
        else
-               asprintf(&s, "<edlib.Pane %p>", self->pane);
+               asprintf(&s, "<edlib.Pane %p-%s>", self->pane, self->pane->name);
        ret = Py_BuildValue("s", s);
        free(s);
        return ret;
@@ -1382,7 +1684,7 @@ static PyObject *doc_repr(Doc *self safe)
        if (!doc_valid(self))
                asprintf(&s, "<edlib.Doc FREED!!! %p>", self);
        else
-               asprintf(&s, "<edlib.Doc %p>", self->pane);
+               asprintf(&s, "<edlib.Doc %p-%s>", self->pane, self->pane->name);
        ret = Py_BuildValue("s", s);
        free(s);
        return ret;
@@ -1398,7 +1700,7 @@ static PyObject *pane_cmp(Pane *p1 safe, Pane *p2 safe, int op)
        Py_RETURN_RICHCOMPARE(p1->pane, p2->pane, op);
 }
 
-static PyGetSetDef pane_getseters[] = {
+static const PyGetSetDef pane_getseters[] = {
        {"x",
         (getter)pane_getnum, (setter)pane_setnum,
         "X offset in parent", "x" },
@@ -1432,6 +1734,9 @@ static 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"},
@@ -1484,7 +1789,7 @@ static int Pane_set_item(Pane *self safe, PyObject *key, PyObject *val)
        return 0;
 }
 
-static PyMappingMethods pane_mapping = {
+static const PyMappingMethods pane_mapping = {
        .mp_length = NULL,
        .mp_subscript = (binaryfunc)Pane_get_item,
        .mp_ass_subscript = (objobjargproc)Pane_set_item,
@@ -1497,13 +1802,13 @@ static PyTypeObject PaneType = {
        .tp_dealloc     = (destructor)pane_dealloc,
        .tp_richcompare = (richcmpfunc)pane_cmp,
        .tp_repr        = (reprfunc)pane_repr,
-       .tp_as_mapping  = &pane_mapping,
+       .tp_as_mapping  = (PyMappingMethods*)&pane_mapping,
        .tp_hash        = (hashfunc)pane_hash,
        .tp_call        = (ternaryfunc)pane_direct_call,
        .tp_flags       = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
        .tp_doc         = "edlib panes",
-       .tp_methods     = pane_methods,
-       .tp_getset      = pane_getseters,
+       .tp_methods     = (PyMethodDef*)pane_methods,
+       .tp_getset      = (PyGetSetDef*)pane_getseters,
        .tp_init        = (initproc)Pane_init,
        .tp_new         = (newfunc)pane_new,
 };
@@ -1527,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;
@@ -1544,18 +1849,18 @@ static PyObject *to_end(Doc *self safe, PyObject *args)
        if (!doc_valid(self))
                return NULL;
 
-       ret = PyArg_ParseTuple(args, "O!i", &MarkType, &mark, &end);
-       if (ret <= 0 || !mark || !mark->mark) {
+       ret = PyArg_ParseTuple(args, "O!p", &MarkType, &mark, &end);
+       if (ret <= 0 || !mark || !mark_valid(mark->mark)) {
                PyErr_SetString(PyExc_TypeError, "Mark undefined or uninitialized");
                return NULL;
        }
 
-       mark_to_end(&self->doc, mark->mark, end);
+       mark_to_end(self->pane, mark->mark, end);
        Py_INCREF(Py_None);
        return Py_None;
 }
 
-static PyMethodDef doc_methods[] = {
+static const PyMethodDef doc_methods[] = {
        {"first_mark", (PyCFunction)first_mark, METH_NOARGS,
         "first mark of document"},
        {"to_end", (PyCFunction)to_end, METH_VARARGS,
@@ -1571,7 +1876,7 @@ static PyTypeObject DocType = {
        .tp_repr        = (reprfunc)doc_repr,
        .tp_flags       = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
        .tp_doc         = "edlib document",
-       .tp_methods     = doc_methods,
+       .tp_methods     = (PyMethodDef*)doc_methods,
        .tp_base        = &PaneType,
        .tp_init        = (initproc)Doc_init,
        .tp_new         = (newfunc)Doc_new,
@@ -1579,27 +1884,31 @@ static PyTypeObject DocType = {
 
 static PyObject *mark_getoffset(Mark *m safe, void *x)
 {
-       if (m->mark == NULL) {
+       struct doc *d;
+       if (!mark_valid(m->mark)) {
                PyErr_SetString(PyExc_TypeError, "Mark is NULL");
                return NULL;
        }
-       if (m->mark->owner->refcnt == mark_refcnt)
+       d = &m->mark->owner->doc;
+       if (d->refcnt == mark_refcnt)
                return PyLong_FromLong(m->mark->ref.o);
        return PyLong_FromLong(0);
 }
 
 static int mark_setoffset(Mark *m safe, PyObject *v safe, void *x)
 {
+       struct doc *d;
        long val;
 
-       if (m->mark == NULL) {
+       if (!mark_valid(m->mark)) {
                PyErr_SetString(PyExc_TypeError, "Mark is NULL");
                return -1;
        }
        val = PyLong_AsLong(v);
        if (val == -1 && PyErr_Occurred())
                return -1;
-       if (m->mark->owner->refcnt == mark_refcnt)
+       d = &m->mark->owner->doc;
+       if (d->refcnt == mark_refcnt)
                m->mark->ref.o = val;
        else {
                PyErr_SetString(PyExc_TypeError, "Setting offset on non-local mark");
@@ -1610,7 +1919,7 @@ static int mark_setoffset(Mark *m safe, PyObject *v safe, void *x)
 
 static PyObject *mark_getseq(Mark *m safe, void *x)
 {
-       if (m->mark == NULL) {
+       if (!mark_valid(m->mark)) {
                PyErr_SetString(PyExc_TypeError, "Mark is NULL");
                return NULL;
        }
@@ -1639,11 +1948,13 @@ static void mark_refcnt(struct mark *m safe, int inc)
 
 static PyObject *mark_getpos(Mark *m safe, void *x)
 {
-       if (m->mark == NULL) {
+       struct doc *d;
+       if (!mark_valid(m->mark)) {
                PyErr_SetString(PyExc_TypeError, "Mark is NULL");
                return NULL;
        }
-       if (m->mark->owner->refcnt == mark_refcnt && m->mark->ref.c) {
+       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;
        } else {
@@ -1655,36 +1966,42 @@ static PyObject *mark_getpos(Mark *m safe, void *x)
 static int mark_setpos(Mark *m safe, PyObject *v, void *x)
 {
        struct mark *m2;
-       if (m->mark == NULL) {
+       struct doc *d;
+
+       if (!mark_valid(m->mark)) {
                PyErr_SetString(PyExc_TypeError, "Mark is NULL");
                return -1;
        }
-       if (m->mark->owner->refcnt != mark_refcnt) {
-               PyErr_SetString(PyExc_TypeError, "Not set ref for non-local mark");
+       d = &m->mark->owner->doc;
+       if (d->refcnt != mark_refcnt) {
+               PyErr_SetString(PyExc_TypeError, "Cannot set ref for non-local mark");
                return -1;
        }
-       m->mark->owner->refcnt(m->mark, -1);
+       d->refcnt(m->mark, -1);
+       if (v == Py_None)
+               v = NULL;
        /* If an adjacent mark has a ref.c with a matching value
         * use that instead, so that mark_same() works.
         */
-       m2 = mark_next(m->mark);
-       if (m2 && m2->owner->refcnt == mark_refcnt && m2->ref.c != NULL &&
+       if ((m2 = mark_next(m->mark)) != NULL &&
+           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 &&
-                m2->owner->refcnt == mark_refcnt &&
-                m2->ref.c != NULL &&
+                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
                m->mark->ref.c = v;
-       m->mark->owner->refcnt(m->mark, 1);
+       d->refcnt(m->mark, 1);
        return 0;
 }
 
 static PyObject *mark_getview(Mark *m safe, void *x)
 {
-       if (m->mark == NULL) {
+       if (!mark_valid(m->mark)) {
                PyErr_SetString(PyExc_TypeError, "Mark is NULL");
                return NULL;
        }
@@ -1707,7 +2024,7 @@ static PyObject *mark_compare(Mark *a safe, Mark *b safe, int op)
                 PyObject_TypeCheck(b, &MarkType) == 0) {
                PyErr_SetString(PyExc_TypeError, "Mark compared with non-Mark");
                return NULL;
-       } else if (!a->mark || !b->mark)
+       } else if (!mark_valid(a->mark) || !mark_valid(b->mark))
                return NULL;
        else {
                int cmp = a->mark->seq - b->mark->seq;
@@ -1717,7 +2034,7 @@ static PyObject *mark_compare(Mark *a safe, Mark *b safe, int op)
        }
 }
 
-static PyGetSetDef mark_getseters[] = {
+static const PyGetSetDef mark_getseters[] = {
        {"pos",
         (getter)mark_getpos, (setter)mark_setpos,
         "Position ref", NULL},
@@ -1733,7 +2050,7 @@ static 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;
 
@@ -1750,7 +2067,7 @@ static int Mark_init(Mark *self safe, PyObject *args safe, PyObject *kwds)
        Pane *owner = NULL;
        int view = MARK_UNGROUPED;
        Mark *orig = NULL;
-       static char *keywords[] = {"pane","view","orig", "owner", NULL};
+       static const char *keywords[] = {"pane","view","orig", "owner", NULL};
        int ret;
 
        if (!PyTuple_Check(args) ||
@@ -1758,7 +2075,7 @@ static int Mark_init(Mark *self safe, PyObject *args safe, PyObject *kwds)
                /* Internal Mark_Frommark call */
                return 1;
 
-       ret = PyArg_ParseTupleAndKeywords(args, kwds, "|O!iO!O!", keywords,
+       ret = PyArg_ParseTupleAndKeywords(args, kwds, "|O!iO!O!", (char**)keywords,
                                          &PaneType, &doc,
                                          &view,
                                          &MarkType, &orig,
@@ -1781,7 +2098,7 @@ static int Mark_init(Mark *self safe, PyObject *args safe, PyObject *kwds)
                if (!op)
                        op = p;
                self->mark = vmark_new(p, view, op);
-       } else if (orig && orig->mark) {
+       } else if (orig && mark_valid(orig->mark)) {
                self->mark = mark_dup_view(orig->mark);
        }
        if (!self->mark) {
@@ -1793,7 +2110,7 @@ static int Mark_init(Mark *self safe, PyObject *args safe, PyObject *kwds)
                 * explicitly released.
                 */
                self->mark->mtype = &MarkType;
-               self->mark->mdata = (PyObject*)self;
+               self->mark->mdata = self;
                Py_INCREF(self);
        } else {
                /* Other marks cannot use mdata and get freed when
@@ -1806,7 +2123,7 @@ static int Mark_init(Mark *self safe, PyObject *args safe, PyObject *kwds)
 
 static void mark_dealloc(Mark *self safe)
 {
-       if (self->mark && self->mark->mtype == (void*)self) {
+       if (mark_valid(self->mark) && self->mark->mtype == (void*)self) {
                /* Python allocated this mark, so can free it. */
                struct mark *m = self->mark;
                self->mark = NULL;
@@ -1821,7 +2138,8 @@ static PyObject *Mark_to_mark(Mark *self safe, PyObject *args)
 {
        Mark *other = NULL;
        int ret = PyArg_ParseTuple(args, "O!", &MarkType, &other);
-       if (ret <= 0 || !other || !self->mark || !other->mark)
+       if (ret <= 0 || !other ||
+           !mark_valid(self->mark) || !mark_valid(other->mark))
                return NULL;
        mark_to_mark(self->mark, other->mark);
 
@@ -1833,7 +2151,8 @@ static PyObject *Mark_to_mark_noref(Mark *self safe, PyObject *args)
 {
        Mark *other = NULL;
        int ret = PyArg_ParseTuple(args, "O!", &MarkType, &other);
-       if (ret <= 0 || !other || !self->mark || !other->mark)
+       if (ret <= 0 || !other ||
+           !mark_valid(self->mark) || !mark_valid(other->mark))
                return NULL;
        mark_to_mark_noref(self->mark, other->mark);
 
@@ -1844,11 +2163,13 @@ static PyObject *Mark_to_mark_noref(Mark *self safe, PyObject *args)
 static PyObject *Mark_clip(Mark *self safe, PyObject *args)
 {
        Mark *start = NULL, *end = NULL;
-       int ret = PyArg_ParseTuple(args, "O!O!", &MarkType, &start, &MarkType, &end);
+       int tostart = 0;
+       int ret = PyArg_ParseTuple(args, "O!O!|p", &MarkType, &start,
+                                  &MarkType, &end, &tostart);
 
-       if (ret > 0 && start && end && self->mark &&
-           start->mark && end->mark)
-               mark_clip(self->mark, start->mark, end->mark);
+       if (ret > 0 && start && end && mark_valid(self->mark) &&
+           mark_valid(start->mark) && mark_valid(end->mark))
+               mark_clip(self->mark, start->mark, end->mark, tostart);
 
        Py_INCREF(Py_None);
        return Py_None;
@@ -1856,7 +2177,7 @@ static PyObject *Mark_clip(Mark *self safe, PyObject *args)
 
 static PyObject *Mark_step(Mark *self safe, PyObject *args)
 {
-       /* Convenience function to help implement doc:step */
+       /* Convenience function to help implement doc:char */
        int forward = 1;
        int ret = PyArg_ParseTuple(args, "i", &forward);
 
@@ -1867,10 +2188,23 @@ static PyObject *Mark_step(Mark *self safe, PyObject *args)
        return Py_None;
 }
 
+static PyObject *Mark_step_sharesref(Mark *self safe, PyObject *args)
+{
+       /* Convenience function to help implement doc:char */
+       int forward = 1;
+       int ret = PyArg_ParseTuple(args, "i", &forward);
+
+       if (ret > 0 && self->mark)
+               mark_step_sharesref(self->mark, forward);
+
+       Py_INCREF(Py_None);
+       return Py_None;
+}
+
 static PyObject *Mark_next(Mark *self safe, PyObject *args)
 {
        struct mark *next;
-       if (!self->mark) {
+       if (!mark_valid(self->mark)) {
                PyErr_SetString(PyExc_TypeError, "Mark is NULL");
                return NULL;
        }
@@ -1887,7 +2221,7 @@ static PyObject *Mark_next(Mark *self safe, PyObject *args)
 static PyObject *Mark_prev(Mark *self safe, PyObject *args)
 {
        struct mark *prev;
-       if (!self->mark) {
+       if (!mark_valid(self->mark)) {
                PyErr_SetString(PyExc_TypeError, "Mark is NULL");
                return NULL;
        }
@@ -1904,7 +2238,7 @@ static PyObject *Mark_prev(Mark *self safe, PyObject *args)
 static PyObject *Mark_next_any(Mark *self safe, PyObject *args)
 {
        struct mark *next;
-       if (!self->mark) {
+       if (!mark_valid(self->mark)) {
                PyErr_SetString(PyExc_TypeError, "Mark is NULL");
                return NULL;
        }
@@ -1918,7 +2252,7 @@ static PyObject *Mark_next_any(Mark *self safe, PyObject *args)
 static PyObject *Mark_prev_any(Mark *self safe, PyObject *args)
 {
        struct mark *prev;
-       if (!self->mark) {
+       if (!mark_valid(self->mark)) {
                PyErr_SetString(PyExc_TypeError, "Mark is NULL");
                return NULL;
        }
@@ -1932,7 +2266,7 @@ static PyObject *Mark_prev_any(Mark *self safe, PyObject *args)
 static PyObject *Mark_dup(Mark *self safe, PyObject *args)
 {
        struct mark *new;
-       if (!self->mark) {
+       if (!mark_valid(self->mark)) {
                PyErr_SetString(PyExc_TypeError, "Mark is NULL");
                return NULL;
        }
@@ -1954,7 +2288,7 @@ static PyObject *Mark_release(Mark *self safe, PyObject *args)
 {
        struct mark *m = self->mark;
 
-       if (!m) {
+       if (!mark_valid(m)) {
                PyErr_SetString(PyExc_TypeError, "Mark has been freed");
                return NULL;
        }
@@ -1963,34 +2297,38 @@ static PyObject *Mark_release(Mark *self safe, PyObject *args)
                                "Cannot release ungrouped marks or points");
                return NULL;
        }
-       if (m->mtype == &MarkType) {
-               /* We are dropping this mark - there cannot be any other ref */
-               ASSERT(m->mdata == (PyObject*)self);
-               Py_DECREF(self);
-               m->mdata = NULL;
-               m->mtype = NULL;
-               self->mark = NULL;
-               mark_free(m);
+       if (m->mtype != &MarkType) {
+               PyErr_SetString(PyExc_TypeError,
+                               "Mark is not managed by python, and cannot be released");
+               return NULL;
        }
 
+       /* We are dropping this mark - there cannot be any other ref */
+       ASSERT(m->mdata == self);
+       self->mark = NULL;
+       Py_DECREF(self);
+       m->mdata = NULL;
+       m->mtype = NULL;
+       mark_free(m);
+
        Py_INCREF(Py_None);
        return Py_None;
 }
 
-static PyObject *Mark_ack(Mark *self safe, PyObject *args)
+static PyObject *Mark_watch(Mark *self safe, PyObject *args)
 {
        struct mark *m = self->mark;
 
-       if (!m) {
+       if (!mark_valid(m)) {
                PyErr_SetString(PyExc_TypeError, "Mark has been freed");
                return NULL;
        }
-       mark_ack(m);
+       mark_watch(m);
        Py_INCREF(Py_None);
        return Py_None;
 }
 
-static PyMethodDef mark_methods[] = {
+static const PyMethodDef mark_methods[] = {
        {"to_mark", (PyCFunction)Mark_to_mark, METH_VARARGS,
         "Move one mark to another"},
        {"to_mark_noref", (PyCFunction)Mark_to_mark_noref, METH_VARARGS,
@@ -2006,13 +2344,15 @@ static PyMethodDef mark_methods[] = {
        {"dup", (PyCFunction)Mark_dup, METH_NOARGS,
         "duplicate a mark, as ungrouped"},
        {"clip", (PyCFunction)Mark_clip, METH_VARARGS,
-        "If this mark is in range, move to start"},
+        "If this mark is in range, move to end"},
        {"release", (PyCFunction)Mark_release, METH_NOARGS,
         "release a vmark so it can disappear"},
-       {"ack", (PyCFunction)Mark_ack, METH_NOARGS,
+       {"watch", (PyCFunction)Mark_watch, METH_NOARGS,
         "acknowledge movement of a point - allow further notifications"},
        {"step", (PyCFunction)Mark_step, METH_VARARGS,
         "Move mark over any adjacent marks with same reference"},
+       {"step_sharesref", (PyCFunction)Mark_step_sharesref, METH_VARARGS,
+        "Move mark over any adjacent marks with same reference"},
        {NULL}
 };
 
@@ -2021,7 +2361,7 @@ static PyObject *mark_get_item(Mark *self safe, PyObject *key safe)
        char *k, *v;
        PyObject *t1 = NULL;
 
-       if (!self->mark) {
+       if (!mark_valid(self->mark)) {
                PyErr_SetString(PyExc_TypeError, "Mark is NULL");
                return NULL;
        }
@@ -2042,7 +2382,7 @@ static int mark_set_item(Mark *self safe, PyObject *key safe, PyObject *val safe
 {
        char *k, *v;
        PyObject *t1 = NULL, *t2 = NULL;
-       if (!self->mark) {
+       if (!mark_valid(self->mark)) {
                PyErr_SetString(PyExc_TypeError, "Mark is NULL");
                return -1;
        }
@@ -2065,39 +2405,58 @@ static int mark_set_item(Mark *self safe, PyObject *key safe, PyObject *val safe
 
 static PyObject *mark_repr(Mark *self safe)
 {
-       char *s = NULL;
+       char *s = NULL, *dm;
        PyObject *ret;
 
-       if (self->mark)
-               asprintf(&s, "<edlib.Mark seq=%d i=%d %p>",
-                        self->mark->seq, self->mark->ref.o, self->mark);
-       else
+       if (!self->mark)
                asprintf(&s, "<edlib.Mark NULL %p>", self);
+       else if (!mark_valid(self->mark))
+               asprintf(&s, "<edlib.Mark FREED %p>", self);
+       else if ((dm = call_ret(str, "doc:debug:mark", self->mark->owner,
+                               0, self->mark))
+                != NULL)
+               asprintf(&s, "<edlib.Mark seq=%d v=%d %s>",
+                        self->mark->seq, self->mark->viewnum, dm);
+       else
+               asprintf(&s, "<edlib.Mark seq=%d v=%d i=%d %p>",
+                        self->mark->seq, self->mark->viewnum,
+                        self->mark->ref.o, self->mark);
+
        ret = Py_BuildValue("s", s);
        free(s);
        return ret;
 }
 
-static PyMappingMethods mark_mapping = {
+static const PyMappingMethods mark_mapping = {
        .mp_length = NULL,
        .mp_subscript = (binaryfunc)mark_get_item,
        .mp_ass_subscript = (objobjargproc)mark_set_item,
 };
 
+static int mark_bool(Mark *self safe)
+{
+       return mark_valid(self->mark);
+}
+
+static const PyNumberMethods mark_as_num = {
+       .nb_bool        = (inquiry) mark_bool,
+};
+
 static PyTypeObject MarkType = {
        PyVarObject_HEAD_INIT(NULL, 0)
        .tp_name        = "edlib.Mark",
        .tp_basicsize   = sizeof(Mark),
        .tp_dealloc     = (destructor)mark_dealloc,
-       .tp_as_mapping  = &mark_mapping,
+       .tp_as_mapping  = (PyMappingMethods*)&mark_mapping,
        .tp_flags       = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
        .tp_doc         = "edlib marks",
        .tp_richcompare = (richcmpfunc)mark_compare,
-       .tp_methods     = mark_methods,
-       .tp_getset      = mark_getseters,
+       .tp_methods     = (PyMethodDef*)mark_methods,
+       .tp_getset      = (PyGetSetDef*)mark_getseters,
        .tp_init        = (initproc)Mark_init,
-       .tp_new         = (newfunc)mark_new,
-       .tp_repr        = (reprfunc)mark_repr
+       .tp_new         = (newfunc)Mark_new,
+       .tp_repr        = (reprfunc)mark_repr,
+       .tp_as_number   = (PyNumberMethods*)&mark_as_num,
 };
 
 static void comm_dealloc(Comm *self safe)
@@ -2130,8 +2489,8 @@ static PyObject *Comm_call(Comm *c safe, PyObject *args safe, PyObject *kwds)
 
        if (!c->comm)
                return NULL;
-       rv = get_cmd_info(&ci, args, kwds, &s1, &s2);
-       if (rv <= 0 || !handle_ret(kwds, &ci, &pr)) {
+       if (!get_cmd_info(&ci, args, kwds, &s1, &s2) ||
+           !handle_ret(kwds, &ci, &pr)) {
                Py_XDECREF(s1); Py_XDECREF(s2);
                command_put(ci.comm2);
                return NULL;
@@ -2196,8 +2555,8 @@ static void python_free_command(struct command *c safe)
  * key, home, focus, xy, hxy, str, str2, mark, mark2, comm2.
  * A 'None' arg is ignored - probably a mark or string or something. Just use NULL
  */
-static int get_cmd_info(struct cmd_info *ci safe, PyObject *args safe, PyObject *kwds,
-                       PyObject **s1 safe, PyObject **s2 safe)
+static bool get_cmd_info(struct cmd_info *ci safe, PyObject *args safe, PyObject *kwds,
+                        PyObject **s1 safe, PyObject **s2 safe)
 {
        int argc;
        PyObject *a;
@@ -2208,20 +2567,20 @@ static int get_cmd_info(struct cmd_info *ci safe, PyObject *args safe, PyObject
        *s1 = *s2 = NULL;
 
        if (!PyTuple_Check(args))
-               return 0;
+               return False;
        argc = PyTuple_GET_SIZE(args);
        if (argc >= 1) {
                /* First positional arg must be the key */
                a = PyTuple_GetItem(args, 0);
                if (!PyUnicode_Check(a)) {
                        PyErr_SetString(PyExc_TypeError, "First arg must be key");
-                       return 0;
+                       return False;
                }
                ci->key = safe_cast PyUnicode_AsUTF8(a);
        }
        for (i = 1; i < argc; i++) {
                a = PyTuple_GetItem(args, i);
-               if (a == Py_None)
+               if (!a || a == Py_None)
                        /* quietly ignore */;
                else if (PyObject_TypeCheck(a, &PaneType)) {
                        if ((void*)ci->home == NULL)
@@ -2230,7 +2589,7 @@ static int get_cmd_info(struct cmd_info *ci safe, PyObject *args safe, PyObject
                                ci->focus = safe_cast ((Pane*)a)->pane;
                        else {
                                PyErr_SetString(PyExc_TypeError, "Only 2 Pane args permitted");
-                               return 0;
+                               return False;
                        }
                } else if (PyObject_TypeCheck(a, &MarkType)) {
                        if (ci->mark == NULL)
@@ -2239,18 +2598,18 @@ static int get_cmd_info(struct cmd_info *ci safe, PyObject *args safe, PyObject
                                ci->mark2 = ((Mark*)a)->mark;
                        else {
                                PyErr_SetString(PyExc_TypeError, "Only 2 Mark args permitted");
-                               return 0;
+                               return False;
                        }
                } else if (PyUnicode_Check(a)) {
                        char *str;
                        PyObject *s = NULL;
                        if (ci->str && ci->str2) {
                                PyErr_SetString(PyExc_TypeError, "Only 3 String args permitted");
-                               return 0;
+                               return False;
                        }
                        str = python_as_string(a, &s);
                        if (!str)
-                               return 0;
+                               return False;
                        if (s) {
                                *s2 = *s1;
                                *s1 = s;
@@ -2268,20 +2627,20 @@ static int get_cmd_info(struct cmd_info *ci safe, PyObject *args safe, PyObject
                                num2_set = 1;
                        } else {
                                PyErr_SetString(PyExc_TypeError, "Only 2 Number args permitted");
-                               return 0;
+                               return False;
                        }
                } else if (PyTuple_Check(a)) {
                        int n = PyTuple_GET_SIZE(a);
                        PyObject *n1, *n2;
                        if (n != 2) {
                                PyErr_SetString(PyExc_TypeError, "Only 2-element tuples permitted");
-                               return 0;
+                               return False;
                        }
                        n1 = PyTuple_GetItem(a, 0);
                        n2 = PyTuple_GetItem(a, 1);
                        if (!PyLong_Check(n1) || !PyLong_Check(n2)) {
                                PyErr_SetString(PyExc_TypeError, "Only tuples of integers permitted");
-                               return 0;
+                               return False;
                        }
                        if (!xy_set) {
                                ci->x = PyLong_AsLong(n1);
@@ -2289,7 +2648,7 @@ static int get_cmd_info(struct cmd_info *ci safe, PyObject *args safe, PyObject
                                xy_set = 1;
                        } else {
                                PyErr_SetString(PyExc_TypeError, "Only one tuple permitted");
-                               return 0;
+                               return False;
                        }
                } else if (PyObject_TypeCheck(a, &CommType)) {
                        Comm *c = (Comm*)a;
@@ -2297,35 +2656,37 @@ static int get_cmd_info(struct cmd_info *ci safe, PyObject *args safe, PyObject
                                ci->comm2 = command_get(c->comm);
                        } else {
                                PyErr_SetString(PyExc_TypeError, "Only one callable permitted");
-                               return 0;
+                               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;
                        else {
                                command_put(&pc->c);
                                PyErr_SetString(PyExc_TypeError, "Only one callable permitted");
-                               return 0;
+                               return False;
                        }
                } else {
                        PyErr_Format(PyExc_TypeError, "Unsupported arg type %d", i);
-                       return 0;
+                       return False;
                }
        }
        if (kwds && PyDict_Check(kwds)) {
-               a = PyDict_GetItemString(kwds, "str");
+               a = PyDict_GetItemString(kwds, "str1");
+               if (!a || a == Py_None)
+                       a = PyDict_GetItemString(kwds, "str");
                if (a && a != Py_None) {
                        if (*s1 || ci->str) {
                                PyErr_SetString(PyExc_TypeError,
                                                "'str' given with other strings");
-                               return 0;
+                               return False;
                        }
                        if (!PyUnicode_Check(a)) {
                                PyErr_SetString(PyExc_TypeError,
                                                "'str' must be string or unicode");
-                               return 0;
+                               return False;
                        }
                        ci->str = python_as_string(a, s1);
                }
@@ -2334,12 +2695,12 @@ static int get_cmd_info(struct cmd_info *ci safe, PyObject *args safe, PyObject
                        if (*s2 || ci->str2) {
                                PyErr_SetString(PyExc_TypeError,
                                                "'str2' given with 2 strings");
-                               return 0;
+                               return False;
                        }
                        if (!PyUnicode_Check(a)) {
                                PyErr_SetString(PyExc_TypeError,
                                                "'str2' must be string or unicode");
-                               return 0;
+                               return False;
                        }
                        ci->str2 = python_as_string(a, s2);
                }
@@ -2348,12 +2709,12 @@ static int get_cmd_info(struct cmd_info *ci safe, PyObject *args safe, PyObject
                        if (ci->mark) {
                                PyErr_SetString(PyExc_TypeError,
                                                "'mark' given with other marks");
-                               return 0;
+                               return False;
                        }
                        if (!PyObject_TypeCheck(a, &MarkType)) {
                                PyErr_SetString(PyExc_TypeError,
                                                "'mark' must be an edlib.Mark");
-                               return 0;
+                               return False;
                        }
                        ci->mark = ((Mark*)a)->mark;
                }
@@ -2362,12 +2723,12 @@ static int get_cmd_info(struct cmd_info *ci safe, PyObject *args safe, PyObject
                        if (ci->mark2) {
                                PyErr_SetString(PyExc_TypeError,
                                                "'mark2' given with 2 other marks");
-                               return 0;
+                               return False;
                        }
                        if (!PyObject_TypeCheck(a, &MarkType)) {
                                PyErr_SetString(PyExc_TypeError,
                                                "'mark2' must be an edlib.Mark");
-                               return 0;
+                               return False;
                        }
                        ci->mark2 = ((Mark*)a)->mark;
                }
@@ -2376,12 +2737,12 @@ static int get_cmd_info(struct cmd_info *ci safe, PyObject *args safe, PyObject
                        if (num_set) {
                                PyErr_SetString(PyExc_TypeError,
                                                "'num' given with other numbers");
-                               return 0;
+                               return False;
                        }
                        if (!PyLong_Check(a)) {
                                PyErr_SetString(PyExc_TypeError,
                                                "'num' must be an integer");
-                               return 0;
+                               return False;
                        }
                        ci->num = PyLong_AsLong(a);
                        num_set = 1;
@@ -2391,29 +2752,98 @@ static int get_cmd_info(struct cmd_info *ci safe, PyObject *args safe, PyObject
                        if (num2_set) {
                                PyErr_SetString(PyExc_TypeError,
                                                "'num2' given with 2 other numbers");
-                               return 0;
+                               return False;
                        }
                        if (!PyLong_Check(a)) {
                                PyErr_SetString(PyExc_TypeError,
                                                "'num2' must be an integer");
-                               return 0;
+                               return False;
                        }
                        ci->num2 = PyLong_AsLong(a);
                        num2_set = 1;
                }
+               a = PyDict_GetItemString(kwds, "focus");
+               if (a && a != Py_None) {
+                       Pane *p;
+                       if ((void*)ci->focus) {
+                               PyErr_SetString(PyExc_TypeError,
+                                               "'focus' given with other pane");
+                               return False;
+                       }
+                       if (!PyObject_TypeCheck(a, &PaneType)) {
+                               PyErr_SetString(PyExc_TypeError,
+                                               "'focus' must be a pane");
+                               return False;
+                       }
+                       p = (Pane*)a;
+                       if (p->pane)
+                               ci->focus = p->pane;
+                       else {
+                               PyErr_SetString(PyExc_TypeError, "focus value invalid");
+                               return False;
+                       }
+               }
+               a = PyDict_GetItemString(kwds, "xy");
+               if (a && a != Py_None) {
+                       PyObject *n1, *n2;
+                       if (xy_set) {
+                               PyErr_SetString(PyExc_TypeError,
+                                               "'xy' given with other tuple");
+                               return False;
+                       }
+                       if (!PyTuple_Check(a) || PyTuple_GET_SIZE(a) != 2) {
+                               PyErr_SetString(PyExc_TypeError,
+                                               "'xy' must be a tuple of 2 integers");
+                               return False;
+                       }
+                       n1 = PyTuple_GetItem(a, 0);
+                       n2 = PyTuple_GetItem(a, 1);
+                       if (!PyLong_Check(n1) || !PyLong_Check(n2)) {
+                               PyErr_SetString(PyExc_TypeError, "Only tuples of integers permitted");
+                               return False;
+                       }
+                       ci->x = PyLong_AsLong(n1);
+                       ci->y = PyLong_AsLong(n2);
+                       xy_set = 1;
+               }
+               a = PyDict_GetItemString(kwds, "comm2");
+               if (a && a != Py_None) {
+                       if (ci->comm2) {
+                               PyErr_SetString(PyExc_TypeError,
+                                               "'comm2' given with other command");
+                               return False;
+                       }
+                       if (PyObject_TypeCheck(a, &CommType)) {
+                               Comm *c = (Comm*)a;
+                               if (c->comm)
+                                       ci->comm2 = command_get(c->comm);
+                               else {
+                                       PyErr_SetString(PyExc_TypeError, "comm2 value invalid");
+                                       return False;
+                               }
+                       } else if (PyCallable_Check(a)) {
+                               struct python_command *pc = export_callable(a, NULL);
+
+                               ci->comm2 = &pc->c;
+                       } else {
+                               PyErr_SetString(PyExc_TypeError,
+                                               "'comm2' must be a callable");
+                               return False;
+                       }
+               }
        }
        if (!(void*)ci->key) {
                PyErr_SetString(PyExc_TypeError, "No key specified");
-               return 0;
+               return False;
        }
        if (!(void*)ci->home) {
                PyErr_SetString(PyExc_TypeError, "No pane specified");
-               return 0;
+               return False;
        }
        if (!(void*)ci->focus)
                ci->focus = ci->home;
 
-       return xy_set ? 2 : 1;
+       return True;
 }
 
 static PyObject *py_time_start(PyObject *self, PyObject *args)
@@ -2449,7 +2879,7 @@ static PyObject *py_LOG(PyObject *self, PyObject *args)
        char buf[1024];
        int l = 0;
 
-       for (i = 0; i < argc; i++) {
+       for (i = 0; i < argc && l < (int)sizeof(buf) - 2; i++) {
                PyObject *o = PySequence_GetItem(args, i);
                PyObject *s, *tofree = NULL;
                char *str;
@@ -2463,62 +2893,74 @@ static PyObject *py_LOG(PyObject *self, PyObject *args)
                        continue;
                str = python_as_string(s, &tofree);
                slen = str ? strlen(str) : 0;
-               if (str && slen < sizeof(buf) - l - 2) {
+               if (slen + l + 2 > sizeof(buf))
+                       slen = sizeof(buf) - l - 2;
+               if (str) {
                        if (l)
                                buf[l++] = ' ';
-                       strcpy(buf+l, str);
+                       strncpy(buf+l, str, slen);
                        l += slen;
                }
                Py_XDECREF(tofree);
        }
        buf[l] = 0;
        if (l)
-               LOG(buf);
+               LOG("%s", buf);
+
+       Py_INCREF(Py_None);
+       return Py_None;
+}
 
+static PyObject *py_LOG_BT(PyObject *self, PyObject *args)
+{
+       LOG_BT();
        Py_INCREF(Py_None);
        return Py_None;
 }
 
-static PyMethodDef edlib_methods[] = {
+static const PyMethodDef edlib_methods[] = {
        {"time_start", py_time_start, METH_VARARGS,
         "Record start time"},
        {"time_stop", py_time_stop, METH_VARARGS,
         "Record stop time"},
        {"LOG", py_LOG, METH_VARARGS,
         "Generate log message"},
+       {"LOG_BT", py_LOG_BT, METH_NOARGS,
+        "Generate backtrace message"},
        {NULL, NULL, 0, NULL}
 };
 
 /* This must be visible when the module is loaded so it
- * cannot be static.  spares doesn't like variables that are
+ * cannot be static.  sparse doesn't like variables that are
  * neither extern nor static.  So mark it extern
  */
 extern char *edlib_module_path;
 char *edlib_module_path;
 
-static struct PyModuleDef edlib_mod = {
-       PyModuleDef_HEAD_INIT,
-       .m_name         = "edlib",
-       .m_doc          = "edlib - one more editor is never enough.",
-       .m_methods      = edlib_methods,
-};
-
 void edlib_init(struct pane *ed safe)
 {
        PyObject *m;
-       wchar_t *argv[1]= { NULL };
+       PyConfig config;
+       char *argv[2]= { "edlib", NULL };
+       sighandler_t sigint;
 
        if (edlib_module_path)
                module_dir = strdup(edlib_module_path);
        else
                module_dir = ".";
 
-       /* This cast is for sparse, which doesn't seem to cope with L".."
-        * FIXME
+       /* 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.
         */
-       Py_SetProgramName((wchar_t*)L"edlib");
-       Py_Initialize();
-       PySys_SetArgv(0, argv);
+       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;
@@ -2532,11 +2974,16 @@ void edlib_init(struct pane *ed safe)
            PyType_Ready(&CommType) < 0)
                return;
 
-       m = PyModule_Create(&edlib_mod);
-
-       if (!m)
+       m = PyImport_AddModule("edlib");
+       if (!m) {
+               PyErr_LOG();
                return;
+       }
+
+       PyModule_SetDocString(m , "edlib - one more editor is never enough");
 
+       PyModule_AddFunctions(m, (PyMethodDef*)edlib_methods);
+       PyModule_AddObject(m, "editor", Pane_Frompane(ed));
        PyModule_AddObject(m, "Pane", (PyObject *)&PaneType);
        PyModule_AddObject(m, "PaneIter", (PyObject *)&PaneIterType);
        PyModule_AddObject(m, "Mark", (PyObject *)&MarkType);
@@ -2564,15 +3011,28 @@ void edlib_init(struct pane *ed safe)
        PyModule_AddIntMacro(m, TIME_TIMER);
        PyModule_AddIntMacro(m, TIME_IDLE);
        PyModule_AddIntMacro(m, TIME_REFRESH);
+       PyModule_AddIntMacro(m, TIME_MISC);
 
-       PyModule_AddIntConstant(m, "WEOF", 0x1FFFFF);
-       call_comm("global-set-command", ed, &python_load, 0, NULL, "python-load");
-       call_comm("global-set-command", ed, &python_load_module,
-                 0, NULL, "global-load-modules:python");
+       PyModule_AddIntMacro(m, RXLF_ANCHORED);
+       PyModule_AddIntMacro(m, RXLF_BACKTRACK);
+
+       PyModule_AddIntMacro(m, MARK_UNGROUPED);
+       PyModule_AddIntMacro(m, MARK_POINT);
+
+       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);
+
        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");
 }