The cl-format-server
is a simple server implementation that listens on a port
and accepts s-expressions to be formatted and returned. It is intended to be
used by text editors to request the formatting of a source file without
having to load a full lisp environment on each iteration.
The server does not implement its own formatting logic, but instead uses an abstraction to make use of existing algorithms/libraries. Currently, the following are supported:
trivial-formatter
sblint
cl-indentify
lisp-critic
The project is in an early state so expect bugs, missing features and breaking changes.
The server is started with the start-server
function with optional Port
argument (default 8080
). The server will then listen on the given port and
wait for requests. The s-expressions are expected to be lists of the form
(:FORMAT-FUN arg)
.
The server will dispatch on the handle function and
call the handler :FORMAT-FUN
and perform the defined task.
When you are e.g. using the format-all
mode, the following code should get you
started:
- Define general purpose function to send a string to a socket:
(defun send-string-to-socket (string port) "Send STRING to PORT on localhost." (interactive "sString: \nnPort: ") (let* ((bufname "*cl-format-server*") (buffer (progn (get-buffer-create bufname) (with-current-buffer bufname (erase-buffer) (current-buffer)))) (proc (open-network-stream "socket" buffer "localhost" port :type 'plain :nowait t)) (output (progn (unwind-protect (progn (process-send-string proc string) (process-send-eof proc) (accept-process-output proc)) (delete-process proc)) (with-current-buffer buffer (substring-no-properties (buffer-string))))))output))
- Implement functions for sending strings/filenames:
(defun remote-format-lisp-replace (fn port &optional formatter) (let ((formatter (or formatter ":trivial-formatter-replace"))) (send-string-to-socket(concat "(" formatter " " (prin1-to-string fn) ")") port))) (defun remote-format-lisp-string (str port &optional formatter) (let ((formatter (or formatter ":trivial-formatter"))) (send-string-to-socket(concat "(" formatter " " (format "%s" str) ")") port))) (defun remote-format-lisp-file (fn port &optional formatter) (let ((formatter (or formatter ":trivial-formatter-file"))) (send-string-to-socket(concat "(" formatter " " (prin1-to-string fn) ")") port))) ;; example usage: ;; (remote-format-lisp-replace "/path/to/my/file.lisp" 8080) ;; (remote-format-lisp-file "/path/to/my/file.lisp" 8080) ;; (remote-format-lisp-string (prin1-to-string "(defvar a 2) (defvar b 3)") 8080) ;; (remote-format-lisp-string "(defvar a 2) (defvar b 3)" 8080)
format-all
requires a function that receives a string and inserts the formatted string, so we define a function that does that:(defun format-lisp (x) (insert (remote-format-lisp-string x 8080)) '(nil "")) (set-formatter! 'cl-format-server #'format-lisp :modes '(lisp-mode lisp))
echo "(:trivial-formatter (def b 1) (def c 2))" | nc localhost 8081 # replace port, code...
curl -d "(:trivial-formatter (def b 1) (def c 2))" -X POST http://localhost:8081
;; Define a handler using the `defhandler` macro
;; (defhandler :example arg
;; (concatenate 'string "Arg: " (string arg)))
;; Test the handler
;; (handle :EXAMPLE "Some data")
;; (handle :EXAMPLE-FILE "oho.test")
;; (handle :EXAMPLE-REPLACE "oho.test")
;; (list-handlers)
For now we use the defhandler
macro. The defhandler
macro takes a keyword, a symbol name on which the string with the source is bound, and a function body returning the formatted string. For example:
(defhandler :trivial-formatter code
(with-output-to-string (stream)
(trivial-formatter:print-as-code
(read-sexps-from-string code
:read-fun #'trivial-formatter:read-as-code) stream)))
The result are three implementations of the handle
generic function:
- cl-format-server:handle :trivial-formatter code
- expects a string with the
source code (e.g.
(handle :trivial-formatter "(defun foo () (print 1))")
) and returns the formatted string. - cl-format-server:handle :trivial-formatter-file file-name
- expects a file-name
name (e.g.
(handle :trivial-formatter-file "my-src.lisp")
) and returns the formatted string. - cl-format-server:handle :trivial-formatter-replace file-name
- expects a file-name and replaces the file with the formatted string.
The last two functions obviously only make sense if the server runs on the same machine as the client.