Parley - Line editing for CHICKEN Scheme

There are several eggs available already that will give your application line editing capabilities. Each on of these offers its own share of advantages: readline being mature (and some might say bloated) and linenoise being bsd licensed and small.

Both of these are made available through chicken's FFI but this approach has a drawback when it comes to threading:

 Blocking I/O will block all threads, except for some socket
 operations (see the section about the tcp unit). An exception is the
 read-eval-print loop on UNIX platforms: waiting for input will not
 block other threads, provided the current input port reads input
 from a console.

(taken from notes to srfi-18)

This means that code like this will not work as expected:

(use linenoise miscmacros srfi-18 tcp)


(define-values (i o) (tcp-connect "ifmud.port4000.com" 4000))

(define reader
  (make-thread
   (lambda ()
       (let loop ()
         (thread-wait-for-i/o! (port->fileno i))
	 (while (and (char-ready? i) (not (eof-object? (peek-char i))))
		(printf "~a" (read-char i))
		(flush-output))
	 (loop)))))

(thread-start! reader)

(let loop ()
  (let ((c (linenoise "")))
    (fprintf o "~a~%" c)
    (flush-output o)
    (if (equal? c "quit")
        (begin
          (close-input-port i)
          (close-output-port o)
          (exit 0))
	(loop))))

Here you can see that I wanted a small telnet client for playing on IFmud. But instead of getting the expected behaviour, the loop blocked the reader procedure. By repeatedly hitting the return key one could see IFmud's banner scrolling by.

So another approach was needed. I liked the simplicity of the linenoise library and I kept hearing the need for a line editing lib for csi, especially from newcomers (or people that do not want to run CSI inside EMACS).

After thinking about it for a while feature creep set in. I settled for these features:

Enter parley. Think of it as "Parley - When you need to negotiate input". It is a small chicken egg with a dependency for the stty egg, which may also vanish soon.

Parley can be used as a drop-in replacement for linenoise. The above example will work like a charm if you replace linenoise with parley.

Currently the following key bindings are implemented:

A handler for a character is a procedure that follows the simple rule #1. Accept and return this (rather long) list of the following arguments:

prompt
The shown prompt for the line or ""
in
the input port
out
the output port
line
The current line as string
pos
The current (0-based) cursor position with respect to line
return
A continuation that should be called if the input is done. This usually breaks out of the prompt loop.

New key bindings are added with the add-key-binding! procedure, which will override the current handler for the given key if present. If you like to add an escape sequence set the esc-sequence: keyword parameter to #t. This will match on ESC Sequences like '\x1b[<char>'. So if you are not scared of adding yet another dependency you can add a handler for deleting the last typed word before the cursor with CTRL-W like this:

(use parley srfi-14)

(define (delete-last-word line pos)
  (let* ((del-pos (or (string-index-right
                      line
                      (char-set-union char-set:whitespace
                                      char-set:punctuation)
                      pos)
                     0))
         (left-part (if (> del-pos 0) (string-take line del-pos)
                    ""))
         (npos (- pos (- pos
                         del-pos))))
    (values (string-append left-part (string-drop line pos))
            npos)))

(define (cw prompt in out line pos exit)
  (receive (l p) (delete-last-word line pos)
           (list prompt in out l p exit)))

(add-key-binding! #\x17 cw)

Parley of course can also be used within CSI. Just add this to .csirc:

(use parley)
(let ((old (current-input-port)))
     (current-input-port (make-parley-port old)))

Things that are still on my list are:

So I hope this small introduction did wet your appetite for this shiny new egg. Please mail me war stories of your usage and the key handlers you have made. If there are enough it might be worth bundling them together.

Code on this site is licensed under a 2 clause BSD license, everything else unless noted otherwise is licensed under a CreativeCommonsAttribution-ShareAlike3.0UnportedLicense