* README for `Pymacs' allout -*- outline -*- `http://www.iro.umontreal.ca/~pinard/pymacs/' contains a copy of this `README' file in HTML form. The canonical Pymacs distribution is available as `http://www.iro.umontreal.ca/~pinard/pymacs/Pymacs.tar.gz'. Report problems and suggestions to `mailto:pinard@iro.umontreal.ca'. .. Presentation. . : What is Pymacs? Pymacs is a powerful tool which, once started from Emacs, allows both-way communication between Emacs Lisp and Python. Yet, Pymacs aims Python as an extension language for Emacs rather than the other way around; this assymetry is reflected in some design choices. Within Emacs Lisp code, one may load and use Python modules. Python functions may themselves use Emacs services, and handle Emacs Lisp objects kept in Emacs Lisp space. The goals are to write "naturally" in both languages, debug with ease, fall back gracefully on errors, and allow full cross-recursivity. It is very easy to install Pymacs, as neither Emacs nor Python need to be compiled nor relinked. Emacs merely starts Python as a subprocess, and Pymacs implements a communication protocol between both processes. . : Warning to Pymacs users. I expect average Pymacs users to have a deeper knowledge of Python than Emacs Lisp. Some examples at the end of this file are meant for Python users having a limited experience with the Emacs API. Currently, there are only contains two examples, one is too small, the other is too big :-). As there is no dedicated mailing list nor discussion group for Pymacs, let's use `python-list@python.org' for asking questions or discussing Pymacs related matters. This is beta status software: specifications are slightly frozen, yet changes may still happen that would require small adaptations in your code. Report problems to François Pinard at `pinard@iro.umontreal.ca'. For discussing specifications or making suggestions, please also copy the `python-list@python.org' mailing list, to help brain-storming! :-) . : History and references. I once starved for a Python-extensible editor, and pondered the idea of dropping Emacs for other avenues, but found nothing much convincing. Moreover, looking at all LISP extensions I wrote for myself, and considering all those superb tools written by others and that became part of my computer life, it would have been a huge undertaking for me to reprogram these all in Python. So, when I began to see that something like Pymacs was possible, I felt strongly motivated! :-) Pymacs revisits previous Cedric Adjih's works about running Python as a process separate from Emacs. See `http://www.crepuscule.com/pyemacs/', or write Cedric at `adjih-pam@crepuscule.com'. Cedric presented `pyemacs' to me as a proof of concept. As I simplified that concept a bit, I dropped the `e' in `pyemacs' :-). Cedric also told me that there exist some older patches for linking Python right into XEmacs. Brian McErlean independently and simultaneously wrote a tool similar to this one, we decided to join our projects. Amusing coincidence, he even chose `pymacs' as a name. Brian paid good attention to complex details that escaped my courage, so his help and collaboration have been beneficial. You may reach Brian at `brianmce@crosswinds.net'. One other reference of interest is Doug Bagley shoot out project, which compares the relative speed of many popular languages. See `http://www.bagley.org/~doug/shootout/' for more information. .. Installation. . : Install the Pymacs proper. Currently, there are two installation scripts, and both should be run. If you prefer, you may use `make install lispdir=LISPDIR', where LISPDIR is some directory along the list kept in your Emacs `load-path'. The first installation script installs the Python package, including the Pymacs examples, using the Python standard Distutils tool. Merely `cd' into the Pymacs distribution, then execute `python setup.py install'. To get an option reminder, do `python setup.py install --help'. Check the Distutils documentation if you need more information about this. The second installation script installs the Emacs Lisp part only. (It used to do everything, but is now doomed to disappear completely.) Merely `cd' into the Pymacs distribution, then run `python setup -ie'. This will invite you to interactively confirm the Lisp installation directory. Without `-ie', the Lisp part of Pymacs will be installed in some automatically guessed place. Use `-n' to known about the guess without proceeding to the actual installation. `./setup -E xemacs ...' may be useful to XEmacs lovers. See `./setup -H' for all options. About Win32 systems, Syver Enstad says: "For Pymacs to operate correctly, one should create a batch file with `pymacs-services.bat' as a name, which runs the `pymacs-services' script. The `.bat' file could be placed along with `pymacs-services', wherever that maybe.". To check that `pymacs.el' is properly installed, start Emacs and give it the command `M-x load-library RET pymacs': you should not receive any error. To check that `pymacs.py' is properly installed, start an interactive Python session and type `from Pymacs import lisp': you should not receive any error. To check that `pymacs-services' is properly installed, type `pymacs-services ' expected.". Currently, there is only one installed Pymacs example, which comes in two parts: a batch script `rebox' and a `Pymacs.rebox' module. To check that both are properly installed, type `rebox '). To bind the F1 key to the `helper' function in some `module': lisp.global_set_key((lisp.f1,), lisp.module_helper) (item,) is a Python tuple yielding an Emacs Lisp vector. `lisp.f1' translates to the Emacs Lisp symbol `f1'. So, Python `(lisp.f1,)' is Emacs Lisp `[f1]'. Keys like `[M-f2]' might require some more ingenuity, one may write either (lisp['M-f2'],) or (lisp.M_f2,) on the Python side. .. Debugging. . : The `*Pymacs*' buffer. Emacs and Python are two separate processes (well, each may use more than one process). Pymacs implements a simple communication protocol between both, and does whatever needed so the programmers do not have to worry about details. The main debugging tool is the communication buffer between Emacs and Python, which is named `*Pymacs*'. To make good use of it, first set `pymacs-trace-transit' to `t', so all exchanges are accumulated in that buffer. As it is sometimes helpful to understand the communication protocol, it is briefly explained here, using an artificially complex example to do so. Consider: ----------------------------------------------------------------------> (pymacs-eval "lisp('(pymacs-eval \"`2L**111`\")')") "2596148429267413814265248164610048L" ----------------------------------------------------------------------< Here, Emacs asks Python to ask Emacs to ask Python for a simple bignum computation. Note that Emacs does not natively know how to handle big integers, nor has an internal representation for them. This is why I use backticks, so Python returns a string representation of the result, instead of the result itself. Here is a trace for this example. The `<' character flags a message going from Python to Emacs and is followed by an expression written in Emacs Lisp. The '>' character flags a message going from Emacs to Python and is followed by a expression written in Python. The number gives the length of the message. ----------------------------------------------------------------------> <22 (pymacs-version "0.3") >49 eval("lisp('(pymacs-eval \"`2L**111`\")')") <25 (pymacs-eval "`2L**111`") >18 eval("`2L**111`") <47 (pymacs-reply "2596148429267413814265248164610048L") >45 reply("2596148429267413814265248164610048L") <47 (pymacs-reply "2596148429267413814265248164610048L") ----------------------------------------------------------------------< Python evaluation is done in the context of the `Pymacs.pymacs' module, so for example a mere `reply' really means `Pymacs.pymacs.reply'. On the Emacs Lisp side, there is no concept of module namespaces, so we use the `pymacs-' prefix as an attempt to stay clean. Users should ideally refrain from naming their Emacs Lisp objects with a `pymacs-' prefix. `reply' and `pymacs-reply' are special functions meant to indicate that an expected result is finally transmitted. `error' and `pymacs-error' are special functions that introduce a string which explains an exception which recently occurred. `pymacs-expand' is a special function implementing the `copy()' methods of Emacs Lisp handles or symbols. In all other cases, the expression is a request for the other side, that request stacks until a corresponding reply is received. Part of the protocol manages memory, and this management generates some extra-noise in the `*Pymacs*' buffer. Whenever Emacs passes a structure to Python, an extra pointer is generated on the Emacs side to inhibit garbage collection by Emacs. Python garbage collector detects when the received structure is no longer needed on the Python side, at which time the next communication will tell Emacs to remove the extra pointer. It works symmetrically as well, that is, whenever Python passes a structure to Emacs, an extra Python reference is generated to inhibit garbage collection on the Python side. Emacs garbage collector detects when the received structure is no longer needed on the Emacs side, after which Python will be told to remove the extra reference. For efficiency, those allocation-related messages are delayed, merged and batched together within the next communication having another purpose. . : Emacs usual debugging. If cross-calls between Emacs Lisp and Python nest deeply, an error will raise successive exceptions alternatively on both sides as requests unstack, and the diagnostic gets transmitted back and forth, slightly growing as we go. So, errors will eventually be reported by Emacs. I made no kind of effort to transmit the Emacs Lisp backtrace on the Python side, as I do not see a purpose for it: all debugging is done within Emacs windows anyway. On recent Emacses, the Python backtrace gets displayed in the mini-buffer, and the Emacs Lisp backtrace is simultaneously shown in the `*Backtrace*' window. One useful thing is to allow to mini-buffer to grow big, so it has more chance to fully contain the Python backtrace, the last lines of which are often especially useful. Here, I use: (setq resize-mini-windows t max-mini-window-height .85) in my `.emacs' file, so the mini-buffer may use 85% of the screen, and quickly shrinks when fewer lines are needed. The mini-buffer contents disappear at the next keystroke, but you can recover the Python backtrace by looking at the end of the `*Messages*' buffer. In which case the `ffap' package in Emacs may be yet another friend! From the `*Messages*' buffer, once `ffap' activated, merely put the cursor on the file name of a Python module from the backtrace, and `C-x C-f RET' will quickly open that source for you. . : Auto-reloading on save. I found useful to automatically `pymacs-load' some Python files whenever they get saved from Emacs. Here is how I do it. The code below assumes that Python files meant for Pymacs are kept in `~/share/emacs/python'. (defun fp-maybe-pymacs-reload () (let ((pymacsdir (expand-file-name "~/share/emacs/python/"))) (when (and (string-equal (file-name-directory buffer-file-name) pymacsdir) (string-match "\\.py\\'" buffer-file-name)) (pymacs-load (substring buffer-file-name 0 -3))))) (add-hook 'after-save-hook 'fp-maybe-pymacs-reload) .. Exemples. . : Paul Winkler's. . , The problem. Let's say I have a a module, call it `manglers.py', containing this simple python function: def break_on_whitespace(some_string): words = some_string.split() return '\n'.join(words) The goal is telling Emacs about this function so that I can call it on a region of text and replace the region with the result of the call. And bind this action to a key, of course, let's say `[f7]'. The Emacs buffer ought to be handled in some way. If this is not on the Emacs Lisp side, it has to be on the Python side, but we cannot escape handling the buffer. So, there is an equilibrium in the work to do for the user, that could be displaced towards Emacs Lisp or towards Python. . , Python side. Here is a first draft for the Python side of the problem: from Pymacs import lisp def break_on_whitespace(): start = lisp.point() end = lisp.mark(lisp.t) if start > end: start, end = end, start text = lisp.buffer_substring(start, end) words = text.split() replacement = '\n'.join(words) lisp.delete_region(start, end) lisp.insert(replacement) interactions = {break_on_whitespace: ''} For various stylistic reasons, this could be rewritten into: from Pymacs import lisp interactions = {} def break_on_whitespace(): start, end = lisp.point(), lisp.mark(lisp.t) words = lisp.buffer_substring(start, end).split() lisp.delete_region(start, end) lisp.insert('\n'.join(words)) interactions[break_on_whitespace] = '' The above relies, in particular, on the fact that for those Emacs Lisp functions used here, `start' and `end' may be given in any order. . , Emacs side. On the Emacs side, one would do: (pymacs-load "manglers") (global-set-key [f7] 'manglers-break-on-whitespace) . : The `rebox' tool. . , The problem. For comments held within boxes, it is painful to fill paragraphs, while stretching or shrinking the surrounding box "by hand", as needed. This piece of Python code eases my life on this. It may be used interactively from within Emacs through the Pymacs interface, or in batch as a script which filters a single region to be reformatted. In batch, the reboxing is driven by command options and arguments and expects a complete, self-contained boxed comment from a file. Emacs function `rebox-region' also presumes that the region encloses a single boxed comment. Emacs `rebox-comment' is different, as it has to chase itself the extent of the surrounding boxed comment. . , Python side. The Python code is too big to be inserted in this documentation: see file `Pymacs/rebox.py' in the Pymacs distribution. You will observe in the code that Pymacs specific features are used exclusively from within the `pymacs_load_hook' function and the `Emacs_Rebox' class. In batch mode, `Pymacs' is not even imported. Here, we mean to discuss some of the design choices in the context of Pymacs. In batch mode, as well as with `rebox-region', the text to handle is turned over to Python, and fully processed in Python, with practically no Pymacs interaction while the work gets done. On the other hand, `rebox-comment' is rather Pymacs intensive: the comment boundaries are chased right from the Emacs buffer, as directed by the function `Emacs_Rebox.find_comment'. Once the boundaries are found, the remainder of the work is essentially done on the Python side. Once the boxed comment has been reformatted in Python, the old comment is removed in a single delete operation, the new comment is inserted in a second operation, this occurs in `Emacs_Rebox.process_emacs_region'. But by doing so, if point was within the boxed comment before the reformatting, its precise position is lost. To well preserve point, Python might have driven all reformatting details directly in the Emacs buffer. We really preferred doing it all on the Python side: as we gain legibility by expressing the algorithms in pure Python, the same Python code may be used in batch or interactively, and we avoid the slowdown that would result from heavy use of Emacs services. To avoid completely loosing point, I kludged a `Marker' class, which goal is to estimate the new value of point from the old. Reformatting may change the amount of white space, and either delete or insert an arbitrary number characters meant to draw the box. The idea is to initially count the number of characters between the beginning of the region and point, while ignoring any problematic character. Once the comment has been reboxed, point is advanced from the beginning of the region until we get the same count of characters, skipping all problematic characters. This `Marker' class works fully on the Python side, it does not involve Pymacs at all, but it does solve a problem that resulted from my choice of keeping the data on the Python side instead of handling it directly in the Emacs buffer. We want a comment reformatting to appear as a single operation, in the context of Emacs Undo. The method `Emacs_Rebox.clean_undo_after' handles the general case for this. Not that we do so much in practice: a reformatting implies one `delete-region' and one `insert', and maybe some other little adjustements at `Emacs_Rebox.find_comment' time. Even if this method scans and mofifies an Emacs Lisp list directly in the Emacs memory, the code doing this stays neat and legible. However, I found out that the undo list may grow quickly when the Emacs buffer use markers, with the consequence of making this routine so Pymacs intensive that most of the CPU is spent there. I rewrote that routine in Emacs Lisp so it executes in a single Pymacs interaction. Function `Emacs_Rebox.remainder_of_line' could have been written in Python, but it was probably not worth going away from this one-liner in Emacs Lisp. Also, given this routine is often called by `find_comment', a few Pymacs protocol interactions are spared this way. This function is useful when there is a need to apply a regexp already compiled on the Python side, it is probably better fetching the line from Emacs and do the pattern match on the Python side, than transmitting the source of the regexp to Emacs for it to compile and apply it. For refilling, I could have either used the refill algorithm built within in Emacs, programmed a new one in Python, or relied on Ross Paterson's `fmt', distributed by GNU and available on most Linuxes. In fact, `refill_lines' prefers the latter. My own Emacs setup is such that the built-in refill algorithm is _already_ overridden by GNU `fmt', and it really does a much better job. Experience taught me that calling an external program is fast enough to be very bearable, even interactively. If Python called Emacs to do the refilling, Emacs would itself call GNU `fmt' in my case, I preferred that Python calls GNU `fmt' directly. I could have reprogrammed GNU `fmt' in Python. Despite interesting, this is an uneasy project: `fmt' implements the Knuth refilling algorithm, which depends on dynamic programming techniques; Ross fine tuned them, and took care of many details. If GNU `fmt' fails, for not being available, say, `refill_lines' falls back on a dumb refilling algorithm, which is better than none. . , Emacs side. The Emacs recipe appears under the `Emacs usage' section, near the beginning of `Pymacs/rebox.py', so I do not repeat it here. .. François Pinard, pinard@iro.umontreal.ca