Each edlib module is now a separate python module.
They must all "import edlib" and can find the editor pane in
"edlib.editor"
They must also import any modules they use.
Signed-off-by: NeilBrown <neil@brown.name>
- [X] ncurses - don't block in nc_external_viewer - at least abort after
30 seconds, but preferrably switch to a mode which leaves
everything else running.
-- [ ] lang-python should put each module in a separate module
+- [X] lang-python should put each module in a separate module
Maybe PyImport_ExecCodeModuleEx() after reading and compile()ing
the source file. Or set up path to find edlib modules.
### lang-python
- [ ] repeated alarm(10)/alarm(0) calls slow things down
-- [ ] lang-python should put each module in a separate module
+- [X] lang-python should put each module in a separate module
Maybe PyImport_ExecCodeModuleEx() after reading and compile()ing
the source file. Or set up path to find edlib modules.
- [ ] array index should allow two args, second being a mark for
int o;
};
+#include <fcntl.h>
#include <signal.h>
#include "core.h"
#include "misc.h"
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;
}
};
/* 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;
else
module_dir = ".";
- /* This cast is for sparse, which doesn't seem to cope with L".."
- * FIXME
- */
PyConfig_InitPythonConfig(&config);
config.isolated = 1;
PyConfig_SetBytesArgv(&config, 0, argv);
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, 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);
PyModule_AddIntMacro(m, MARK_POINT);
PyModule_AddIntConstant(m, "WEOF", 0x3FFFFF);
- call_comm("global-set-command", ed, &python_load_module,
- 0, NULL, "global-load-modules:python");
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, &python_load_module,
+ 0, NULL, "global-load-modules:python");
}
# config module with a more abstract language one day.
# But I want some simple configuration NOW
+from edlib import editor
import os
def config_appeared(key, focus, **a):
" *#+|" # section head
" *[0-9]*\\.)") # Numbered list
- return edlib.Efallthrough
-
editor.call("global-set-command", "doc:appeared-config", config_appeared)
# Some modules I want auto-loaded.
# receive mouse/keyboard events.
# provides eventloop function using gtk.main.
-import os
+import edlib
+import os, fcntl
import gi
import time
import cairo
+import subprocess
gi.require_version('Gtk', '3.0')
gi.require_version('PangoCairo', '1.0')
def handle_new(self, key, focus, **a):
"handle:Display:new"
- global editor
- newdisp = EdDisplay(editor, self['DISPLAY'])
+ newdisp = EdDisplay(edlib.editor, self['DISPLAY'])
p = newdisp.call("editor:activate-display", ret='pane')
if p:
focus.call("doc:attach-view", p, 1)
focus.call("doc:attach-view", p, 1);
return 1
-editor.call("global-set-command", "attach-display-gtk", new_display)
-editor.call("global-set-command", "interactive-cmd-gtkwindow", new_display2)
+edlib.editor.call("global-set-command", "attach-display-gtk", new_display)
+edlib.editor.call("global-set-command", "interactive-cmd-gtkwindow", new_display2)
# Anything else is passed to parent, and if that doesn't result
# in a callback, we self-destruct
+import edlib
+
class AbbrevPane(edlib.Pane):
def __init__(self, focus):
edlib.Pane.__init__(self, focus)
comm2("cb", p)
return 1
-editor.call("global-set-command", "attach-abbrev", abbrev_attach)
+edlib.editor.call("global-set-command", "attach-abbrev", abbrev_attach)
# choose_range(focus, viewnum, attr, start, end) - changes start and end to be
# a contiguous unchecked section in the range
+import edlib
+
def show_range(action, focus, viewnum, attr):
edlib.LOG("range:", attr, action)
f,l = focus.vmarks(viewnum)
return 1
-editor.call("global-set-command", "attach-autospell", autospell_attach)
-editor.call("global-set-command", "interactive-cmd-autospell",
+edlib.editor.call("global-set-command", "attach-autospell", autospell_attach)
+edlib.editor.call("global-set-command", "interactive-cmd-autospell",
autospell_activate)
# The content of filename is url encoded
#
+import edlib
+
+import os
+import subprocess
import email.utils
import email.message
import email.policy
import tempfile
import mimetypes
import urllib
+import re
from datetime import date
def read_status(p, key, focus, **a):
comm2("cb", p)
return 1
-editor.call("global-set-command", "attach-compose-email", compose_mode_attach)
+edlib.editor.call("global-set-command", "attach-compose-email", compose_mode_attach)
# - colourizes + and - lines
# - interprets ':Enter' to find the given line
+import edlib
+
import os.path
def djoin(dir, tail):
focus.call("doc:append:view-default", ",diff")
return 1
-editor.call("global-set-command", "attach-diff", diff_view_attach)
-editor.call("global-set-command", "interactive-cmd-diff-mode", add_diff)
-editor.call("global-load-module", "lib-wiggle")
+edlib.editor.call("global-set-command", "attach-diff", diff_view_attach)
+edlib.editor.call("global-set-command", "interactive-cmd-diff-mode", add_diff)
+edlib.editor.call("global-load-module", "lib-wiggle")
# converts it to text using lowriter.
# Unfortunately lowriter only reads and writes a file, not a pipe..
+import edlib
+
import subprocess
import tempfile
-import os
+import os, fcntl
class doc_pane(edlib.Pane):
def __init__(self, focus, path, newpath, delayed):
comm2("cb", doc)
return 1
-if "editor" in globals():
- editor.call("global-set-command", "doc-to-text", doc_to_text)
+edlib.editor.call("global-set-command", "doc-to-text", doc_to_text)
# edlib module for getting events from GLib
+import edlib
+
import signal
import gi
import os
events_activate(focus)
return 1
-editor.call("global-set-command", "attach-glibevents", register_events)
+edlib.editor.call("global-set-command", "attach-glibevents", register_events)
# converts it from html to markdown, and creates a text pane with the
# markdown text.
+import edlib
+
import html2text
def html_to_text(key, home, focus, comm2, **a):
comm2("cb", doc)
return 1
-if "editor" in globals():
- editor.call("global-set-command", "html-to-text", html_to_text)
+edlib.editor.call("global-set-command", "html-to-text", html_to_text)
# applied the changes to the text as render attributes.
#
+import edlib
+
+import os, fcntl
import subprocess
def get_attr(tagl, tag, attr):
return ret + str
-if "editor" in globals():
- editor.call("global-set-command", "html-to-text-w3m", html_to_w3m)
+edlib.editor.call("global-set-command", "html-to-text-w3m", html_to_w3m)
# converts it from ical to simple text, and creates a text pane with
# that text.
+import edlib
+
+import time
import icalendar
def ical_to_text(key, home, focus, comm2, **a):
comm2("cb", doc)
return 1
-if "editor" in globals():
- editor.call("global-set-command", "ical-to-text", ical_to_text)
+edlib.editor.call("global-set-command", "ical-to-text", ical_to_text)
# Other functionality is available simply through global commands.
#
+import edlib
+
docname = "*Macro History*"
# A macro is a list of keystroke separated by commas
num = 1
focus.call("history:get-last", num, docname, str)
-editor.call("global-set-command", "macro:capture", start_capture)
-editor.call("global-set-command", "macro:finished", end_capture)
-editor.call("global-set-command", "macro:replay", play_macro)
-editor.call("global-set-command", "macro:name", name_macro)
+edlib.editor.call("global-set-command", "macro:capture", start_capture)
+edlib.editor.call("global-set-command", "macro:finished", end_capture)
+edlib.editor.call("global-set-command", "macro:replay", play_macro)
+edlib.editor.call("global-set-command", "macro:name", name_macro)
# May be distributed under terms of GPLv2 - see file:COPYING
#
+import edlib
+
import os, fcntl, signal
class MakePane(edlib.Pane):
return 1
-editor.call("global-set-command", "attach-makecmd", make_attach)
-editor.call("global-set-command", "attach-make-viewer", make_view_attach)
-editor.call("global-set-command", "interactive-cmd-make", make_request)
-editor.call("global-set-command", "interactive-cmd-grep", make_request)
-editor.call("global-set-command", "interactive-cmd-git-grep", make_request)
-editor.call("global-set-command", "interactive-cmd-next-match", next_match)
+edlib.editor.call("global-set-command", "attach-makecmd", make_attach)
+edlib.editor.call("global-set-command", "attach-make-viewer", make_view_attach)
+edlib.editor.call("global-set-command", "interactive-cmd-make", make_request)
+edlib.editor.call("global-set-command", "interactive-cmd-grep", make_request)
+edlib.editor.call("global-set-command", "interactive-cmd-git-grep", make_request)
+edlib.editor.call("global-set-command", "interactive-cmd-next-match", next_match)
# If cursor is not on any, it is moved forward to the next one.
#
+import edlib
+
class MergePane(edlib.Pane):
def __init__(self, focus):
edlib.Pane.__init__(self, focus)
p.call("K:A-m", focus, mark)
return 1
-editor.call("global-set-command", "attach-merge", merge_view_attach)
-editor.call("global-set-command", "interactive-cmd-merge-mode", add_merge)
-editor.call("global-load-module", "lib-wiggle")
+edlib.editor.call("global-set-command", "attach-merge", merge_view_attach)
+edlib.editor.call("global-set-command", "interactive-cmd-merge-mode", add_merge)
+edlib.editor.call("global-load-module", "lib-wiggle")
# converts it from pdf to text, and creates a text doc with the
# text.
+import edlib
+
+import os, fcntl
import subprocess
class pdf_pane(edlib.Pane):
comm2("cb", doc)
return 1
-if "editor" in globals():
- editor.call("global-set-command", "pdf-to-text", pdf_to_text)
+edlib.editor.call("global-set-command", "pdf-to-text", pdf_to_text)
sockpath = "/tmp/edlib-neilb"
try:
+ import edlib
+
class ServerPane(edlib.Pane):
# This pane receives requests on a socket and
# forwards them to the editor. When a notification
# arrives, it is sent back to the client
def __init__(self, sock):
- edlib.Pane.__init__(self, editor)
+ edlib.Pane.__init__(self, edlib.editor)
self.sock = sock
self.term = None
self.disp = None
self.doc = None
self.want_close = False
self.lineno = None
- editor.call("event:read", sock.fileno(),
+ edlib.editor.call("event:read", sock.fileno(),
self.read)
def read(self, key, **a):
path = msg[5:].decode("utf-8",'ignore')
try:
# 8==reload
- d = editor.call("doc:open", -1, 8, path, ret='pane')
+ d = edlib.editor.call("doc:open", -1, 8, path, ret='pane')
except edlib.commandfailed:
d = None
if not d:
return 1
if msg[:21] == b"doc:request:doc:done:":
path = msg[21:].decode("utf-8", 'ignore')
- d = editor.call("doc:open", -1, path, ret='pane')
+ d = edlib.editor.call("doc:open", -1, path, ret='pane')
if not d:
self.sock.send(b"FAIL")
return 1
self.sock.send(b"OK")
return 1
if cmd == 'x11window' and not self.term:
- p = editor.call("interactive-cmd-x11window",
+ p = edlib.editor.call("interactive-cmd-x11window",
arg, env['XAUTHORITY'], ret='pane')
if p:
for v in env:
return 1
if cmd == 'term' and not self.term:
path = arg
- p = editor
+ p = edlib.editor
p = p.call("attach-display-ncurses", path, env['TERM'],
ret='pane')
for v in env:
if key != "key":
focus.call("Message", "Server restarted")
return 1
- server_rebind("key", editor)
- editor.call("global-set-command", "lib-server:done", server_done)
- editor.call("global-set-command", "interactive-cmd-server-start",
+ server_rebind("key", edlib.editor)
+ edlib.editor.call("global-set-command", "lib-server:done", server_done)
+ edlib.editor.call("global-set-command", "interactive-cmd-server-start",
server_rebind)
# May be distributed under terms of GPLv2 - see file:COPYING
#
+import edlib
+
import subprocess, os, fcntl, signal
class ShellPane(edlib.Pane):
comm2("callback", p)
return 1
-editor.call("global-set-command", "attach-shellcmd", shell_attach)
-editor.call("global-set-command", "attach-shell-viewer", shell_view_attach)
+edlib.editor.call("global-set-command", "attach-shellcmd", shell_attach)
+edlib.editor.call("global-set-command", "attach-shell-viewer", shell_view_attach)
# is then chosen as the maximal set of non-alphanumerics non-quote
# characters.
+import edlib
+
import re
def span(line, chars):
focus.call("doc:append:view-default", ",textfill")
return 1
-editor.call("global-set-command", "attach-textfill", fill_mode_attach)
-editor.call("global-set-command", "interactive-cmd-fill-mode",
+edlib.editor.call("global-set-command", "attach-textfill", fill_mode_attach)
+edlib.editor.call("global-set-command", "interactive-cmd-fill-mode",
fill_mode_activate)
# "query.misc-list" is a subset of current-list for which query:current should not
# be assumed.
+import edlib
+
from subprocess import Popen, PIPE, DEVNULL, TimeoutExpired
import re
-import os
-import os.path
+import tempfile
+import os, fcntl
import json
import time
import mimetypes
comm2("callback", focus, "%d" % self.searches.maxlen)
return 1
if str.startswith('config:'):
- p = subprocess.Popen(['/usr/bin/notmuch', 'config', 'get', str[7:]],
- close_fds = True,
- stderr = subprocess.PIPE,
- stdout = subprocess.PIPE)
+ p = Popen(['/usr/bin/notmuch', 'config', 'get', str[7:]],
+ close_fds = True,
+ stderr = PIPE, stdout = PIPE)
out,err = p.communicate()
p.wait()
if out:
p.call("doc:char-s")
return 1
-if "editor" in globals():
- editor.call("global-set-command", "attach-doc-notmuch", notmuch_doc)
- editor.call("global-set-command", "attach-render-notmuch:master-view",
- render_master_view_attach)
- editor.call("global-set-command", "attach-render-notmuch:threads",
- render_query_attach)
- editor.call("global-set-command", "attach-render-notmuch:message",
- render_message_attach)
- editor.call("global-set-command", "interactive-cmd-nm", notmuch_mode)
- editor.call("global-set-command", "interactive-cmd-nmc", notmuch_compose)
- editor.call("global-set-command", "interactive-cmd-nms", notmuch_search)
+edlib.editor.call("global-set-command", "attach-doc-notmuch", notmuch_doc)
+edlib.editor.call("global-set-command", "attach-render-notmuch:master-view",
+ render_master_view_attach)
+edlib.editor.call("global-set-command", "attach-render-notmuch:threads",
+ render_query_attach)
+edlib.editor.call("global-set-command", "attach-render-notmuch:message",
+ render_message_attach)
+edlib.editor.call("global-set-command", "interactive-cmd-nm", notmuch_mode)
+edlib.editor.call("global-set-command", "interactive-cmd-nmc", notmuch_compose)
+edlib.editor.call("global-set-command", "interactive-cmd-nms", notmuch_search)
# Copyright Neil Brown (c)2018-2023 <neil@brown.name>
# May be distributed under terms of GPLv2 - see file:COPYING
+import edlib
+
def textwidth(line, w=0):
for c in line:
if c == '\t':
CModePane(focus)
return 1
-editor.call("global-set-command", "doc:appeared-c-mode", c_mode_appeared)
-editor.call("global-set-command", "doc:appeared-py-mode", py_mode_appeared)
-editor.call("global-set-command", "attach-c-mode", c_mode_attach)
-editor.call("global-set-command", "attach-py-mode", py_mode_attach)
-editor.call("global-set-command", "interactive-cmd-indent", attach_indent)
+edlib.editor.call("global-set-command", "doc:appeared-c-mode", c_mode_appeared)
+edlib.editor.call("global-set-command", "doc:appeared-py-mode", py_mode_appeared)
+edlib.editor.call("global-set-command", "attach-c-mode", c_mode_attach)
+edlib.editor.call("global-set-command", "attach-py-mode", py_mode_attach)
+edlib.editor.call("global-set-command", "interactive-cmd-indent", attach_indent)
# starting '>'
#
+import edlib
+
class CalcView(edlib.Pane):
def __init__(self, focus):
edlib.Pane.__init__(self, focus)
focus["view-default"] = "view-calc"
return edlib.Efallthrough
-editor.call("global-set-command", "attach-view-calc", calc_view_attach)
-editor.call("global-load-module", "lib-calc")
-editor.call("global-set-command", "interactive-cmd-calc", add_calc)
-editor.call("global-set-command", "doc:appeared-calc", calc_appeared)
+edlib.editor.call("global-set-command", "attach-view-calc", calc_view_attach)
+edlib.editor.call("global-load-module", "lib-calc")
+edlib.editor.call("global-set-command", "interactive-cmd-calc", add_calc)
+edlib.editor.call("global-set-command", "doc:appeared-calc", calc_appeared)
# When change happens, type changed to 'unknown' which triggers self.mark_lines() to
# reparse some of the page.
+import edlib
import re
import os
focus["view-default"] = vd
return edlib.Efallthrough
-editor.call("global-set-command", "attach-markdown-present", markdown_attach)
-editor.call("global-set-command", "attach-present", present_attach)
-editor.call("global-set-command", "doc:appeared-present", markdown_appeared)
+edlib.editor.call("global-set-command", "attach-markdown-present", markdown_attach)
+edlib.editor.call("global-set-command", "attach-present", present_attach)
+edlib.editor.call("global-set-command", "doc:appeared-present", markdown_appeared)