Skip to content

fkr-0/cl-format-server

Repository files navigation

cl-format-server

cl-format-server

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.

Usage

Server

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.

Client

Emacs

When you are e.g. using the format-all mode, the following code should get you started:

  1. 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))
    
        
  2. 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)
        
  3. 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))
        

Netcat

echo "(:trivial-formatter (def b 1)                  (def c 2))" | nc localhost 8081 # replace port, code...

Curl

curl -d "(:trivial-formatter (def b 1)                  (def c 2))" -X POST http://localhost:8081

Implementing a handler

Tl;dr

;; 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)

Details

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.

About

Request formatted (Common) Lisp code

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published