Saturday, 18 January 2014

Starting with Elisp and external processes

1 Getting started with Elisp

This post will describe my first steps with Emacs Lisp and how to use it to communicate with an external process.

The little 'script' I implemented will:

  • use the inotifywait tools on Linux to monitor filesystem changes1.
  • call a function to react to them.

As for all things Emacs, the Emacs documentation is incredibly detailed and very, very good and all informations in this post can be found (with a lot more information) in the documentation.

This post is simply a little write up of the most basic steps to get started.

It will cover the following steps:

  1. How to start a external program from Emacs.
  2. How to react to output generated from the started process.

2 Starting a process in Emacs

When deciding to start an external program there are two possibilities: either start a synchronous process or an asynchronous one.

As I want inotifywait to keep running in the background, while reacting to the output I have to start an external process using the start-process function provided by emacs:

(start-process "some-name" ;; A name for the process
        "*buffer-to-write-output-into*" ;; A buffer the process can write to
        "inotifywait" ;; The command to run
        ;; Following are any arguments that are required by the command:
        "-m"
        "-e"
        "create"
        "<file_to_watch>")
      

It is important that the command-line arguments are passed as variadic arguments, so if you want to apply this command to a list of arguments you already have stored in a list you can simply use the apply-function 2.

(process (apply 'start-process "some-name"
        "*buffer-to-write-output-into*"
        "inotifywait" argument-list))
      

That is all there is to starting an asynchronous process in Emacs.

3 Reacting to output from the process

Reacting to output generated by the process is done using classic callbacks.

To register the callback-function with a process, you have to store the return value of the start-process call and register a callback using the set-process-filter function:

(defun callback-func (process msg)
        "Callback function need to accept two arguments:
        - the process that created the output.
        - the message containing the currently produced output."
        (message msg))

        (let* ((arguments (list "-m" "-e" "create" "<filepath>"))
        (process (apply 'start-process "NOTIFYWAIT" "*notifywait*" "inotifywait" arguments)))
        (set-process-filter process 'callback-func))
      

Now all output generated by the inotifywait-command will be send to the callback and can be processed in whatever fashion required.

I wrote a little callback that will run all nosetests for a Python-project for example, as soon as a file under the source-directory changes. You can find the source-code on Github.

Footnotes:

1

If you are using Emacs ≥ 24.4 then cross-platform inotify-functionality is included in Emacs. As both Debian and OpenSUSE both still provide Emacs 24.3 however, I worked around this with this small script.

2

If you know Python then you can think of apply as being the same as calling a function with func(*a_list).

No comments:

Post a Comment