2023-09-17 @rrobin
This is how I format scheme code in Vim. Admittedly my setup is only useful if you follow the GUIX style, but maybe the notes on Vim are useful too.
Vim provides a few commands to format your code. I think the most common methods are to rely on (gq) and =. In some cases people use LSP provided formatters, but I don't cover it here.
'=' filters lines through an external program (defined in 'equalprg'). The purpose of the command is to indent lines.
If 'equalprg' is not defined, then Vim will fallback to an internal implementation. This fallback will depend on other options, in particular if 'lisp', 'indentexpr' or 'cindent'.
If 'lisp' is set, the default in scheme, then 'indentexpr' will be completely ignored. This can be changed by changing 'lispoptions'.
gq - formats lines that a motion moves over. This uses either an external program, custom expression, or an internal implementation.
'formatprg' is an option that holds the path of a program, used to format code with the (gq) operator. The program takes input on stdin and returns output on stdout.
'formatexpr' holds a vim expression used to format code (usually a function call) with the (gq) operator. This option takes precedence over 'formatprg'. Input is defined by the following variables
My main target is to format code according to the Guix rules. Guix provides a tool for this called guix style, that rewrites a file in place:
guix style --whole-file filename.scm
This clashes a bit with the way that Vim formats code, since it expects to process the entire file, while Vim can try to format a range of lines, for a buffer without a file.
Here is an example function for a formatexpr. Note that you should only set this in a buffer option (e.g. via autocommands for scheme files):
function! s:guixStyle() let tmpfile = tempname() " Unlike the usual formatexpr we ignore the actual motion range " because guix style needs a full file to be formatted "let startl = v:lnum "let endl = v:lnum + v:count - 1 let startl = 1 " deletebufline does not accept 0 here let endl = "$" let oldlines = getline(startl, endl) if writefile(oldlines, tmpfile, "s") <> 0 echohl ErrorMsg echom "Failed to setup tmp file for guix style" echohl None return 0 endif let out = systemlist(['guix', 'style', '--whole-file', tmpfile], '') if v:shell_error echohl ErrorMsg echom "Failed to run guix style:" for line in out echom " "..line endfor echom "Could not format scheme" echohl None else let newlines = readfile(tmpfile) call deletebufline("", startl, endl) call setline(startl, newlines) endif return 0 endfunction setlocal formatexpr=s:guixStyle()
The code is relatively straightforward, it copies buffer contents into a temporary file and calls guix style, replacing buffer lines on success. There are a few caveats though:
That still leaves us with '='. What happens when we try to indent scheme code in Vim? According to the documentation if options 'lisp' and 'autoindent' are set then:
When is typed in insert mode set the indent for the next line to Lisp standards (well, sort of).
so it is likely that 'equalprg' is not used (since 'lisp' is on). This means that most other options are ignored.
In particular the default settings for lisp indentation seem to always indent code to align with the operator (based on 'lispwords'), rather than a fixed number of spaces (2 in guix style).
This is not what I want, so I've adjusted this with a custom 'indentexpr'.
function! s:schemeIndent(line) if a:line <= 1 return 0 endif let prevline = a:line-1 let previndent = indent(prevline) let openp = count(getline(prevline), "(") let closep = count(getline(prevline), ")") if openp == closep return previndent elseif openp > closep return previndent + shiftwidth() else return previndent - shiftwidth() endif endfunction setlocal indentexpr=s:schemeIndent(v:lnum) setlocal expandtab setlocal shiftwidth=2 setlocal tabstop=2 setlocal softtabstop=2 setlocal lispoptions=expr:1
However this is a pretty naive implementation that works by counting parens and relying on the indentation of the previous line.
=> GUIX - Formatting Code | Riastradh's Lisp Style Rules This content has been proxied by September (ba2dc).Proxy Information
text/gemini;