(macros)
Code

Language servers (LSP)

Macros speaks the Language Server Protocol. The protocol layer — sending requests, routing responses, jumping to locations, applying edits — is in Rust; the user-facing commands, the result-buffer keymaps, and the default keys are wired in Scheme, so you can rebind and extend everything.

Setting up a language server

Language servers are opt-in: none are configured out of the box (the one exception is the built-in, in-process Scheme server, which needs no binary). You enable a language by declaring it with the lsp-language form in your init.scm; the server then starts lazily the first time you open a matching file — provided the server binary is on your PATH. (Syntax highlighting is separate — it comes from the bundled tree-sitter grammars and is always on, with or without a server.)

The smallest useful example — Rust, via rust-analyzer, formatting on save:

(lsp-language "rust"
  'extensions     '("rs")
  'command        "rust-analyzer"
  'format-on-save 'lsp)          ; format through the server on save

Enabling the common servers

Paste any of these into your init.scm and keep the ones whose server binaries are installed and on your PATH. This is the set Macros used to configure automatically; format-on-save 'lsp uses each server's own formatter (rustfmt, gofmt, clang-format, …) — no extra binary, and a harmless no-op if the server can't format.

(lsp-language "rust"       'extensions '("rs")              'command "rust-analyzer"                 'format-on-save 'lsp)
(lsp-language "go"         'extensions '("go")              'command "gopls"                         'format-on-save 'lsp)
(lsp-language "c"          'extensions '("c" "h")           'command "clangd"                        'format-on-save 'lsp)
(lsp-language "cpp"        'extensions '("cpp" "cc" "hpp")  'command "clangd"                        'format-on-save 'lsp)
(lsp-language "python"     'extensions '("py")              'command "pylsp"                         'format-on-save 'lsp)

;; .ts and .tsx share one server but must announce different languageIds, or a
;; .tsx file opens as plain "typescript" and every JSX element is a syntax error.
(lsp-language "typescript"
  'extensions   '("ts" "tsx")
  'language-ids '(("ts" . "typescript") ("tsx" . "typescriptreact"))
  'command      "typescript-language-server --stdio"
  'format-on-save 'lsp)
(lsp-language "javascript"
  'extensions   '("js" "jsx")
  'language-ids '(("js" . "javascript") ("jsx" . "javascriptreact"))
  'command      "typescript-language-server --stdio"
  'format-on-save 'lsp)

;; JVM languages (highlighting is bundled; install the server to enable LSP):
(lsp-language "scala"      'extensions '("scala" "sc" "sbt")        'command "metals"      'format-on-save 'lsp)
(lsp-language "clojure"    'extensions '("clj" "cljs" "cljc" "edn") 'command "clojure-lsp" 'format-on-save 'lsp)

The same block lives (commented) in the DEFAULT LANGUAGE CONFIGURATION section at the end of scheme/lsp.scm.

'command is the server invocation; put any flags right in the string:

(lsp-language "typescript"
  'extensions   '("ts" "tsx")
  'language-ids '(("tsx" . "typescriptreact"))   ; per-extension wire id
  'command      "typescript-language-server --stdio")

Options

Option What it does
'extensions File extensions that activate this language (a list)
'command The server command line — flags allowed inline
'format-on-save 'lsp (format via the server), a shell formatter like (list "black" "-"), or the name of your own Scheme function
'inlay-hints On by default; pass #f to turn the type/parameter hints off
'semantic-tokens #t to color from the server's semantic tokens — turn rainbow delimiters off for that language so they don't fight over the overlay layer
'code-lens #t to show code lenses
'language-ids Per-extension languageId overrides, an alist of (ext . id)

More examples

;; Python, formatted by black instead of the server ("-" makes black read stdin):
(lsp-language "python"
  'extensions     '("py")
  'command        "pylsp"
  'format-on-save (list "black" "-"))

;; Go with inlay hints and semantic-token coloring on:
(lsp-language "go"
  'extensions      '("go")
  'command         "gopls"
  'format-on-save  'lsp
  'inlay-hints     #t
  'semantic-tokens #t)

;; Zig via zls, with inlay hints off:
(lsp-language "zig"
  'extensions     '("zig")
  'command        "zls"
  'format-on-save 'lsp
  'inlay-hints    #f)

Declaring a language is idempotent — re-declaring it (e.g. to flip 'inlay-hints) just replaces the previous definition, so you can refine a config by stating it again later in your init.scm.

The built-in Scheme server

The in-process Scheme server needs no binary and runs automatically for .scm files. Editor config and extension code (anything under ~/.config/macros, or a *.macros.scm file) get the macros-scheme variant, which additionally seeds the whole editor API. It provides completion, "unknown function" diagnostics, go-to-definition (M-.), find-references (M-?), hover (C-c C-l h), and rename (C-c C-l n).

Because command/keybinding references are 'symbols rather than strings, the server sees them as real identifiers: M-. on 'magit-status in a keybinding jumps to its (define …) (in your config or the bundled source), M-? lists its uses — quoted references included — and hover shows its docs. The leading quote is a word boundary, so the bare name is what resolves.

Rename is scoped to the current document and to symbols it defines — renaming a command you wrote in init.scm updates its definition, keybindings, and calls there (quoted references included). A symbol defined elsewhere (a bundled command, another file) is declined ("Cannot rename this symbol"), since the built-in server can't safely update across files. (In config buffers, C-c C-d / C-c C-f also give doc-at-point via the Scheme-side macros-eldoc / macros-describe-point — see Scripting.)

All are M-x-discoverable. The most common two have short bindings; the rest live under the C-c C-l prefix.

Key Command Action
M-. lsp-find-definition Go to definition
M-? lsp-find-references List all references
M-* xref-pop-marker Jump back
C-c C-d lsp-hover Documentation at point
C-c C-l d lsp-find-definition Definition
C-c C-l D lsp-find-declaration Declaration
C-c C-l t lsp-find-type-definition Type definition
C-c C-l i lsp-find-implementation Implementation(s)
C-c C-l r lsp-find-references References
C-c C-l n lsp-rename Rename everywhere
C-c C-l h lsp-hover Hover docs
C-c C-l k lsp-signature-help Signature of the call at point
C-c C-l s lsp-document-symbols Symbols in this buffer
C-c C-l S lsp-workspace-symbol Query symbols across the project
C-c C-l f lsp-format-buffer Format the buffer
C-c C-l a lsp-code-action Code actions / quick fixes

More under C-c C-l

Key Command
C-c C-l H lsp-document-highlight
C-c C-l I lsp-inlay-hints
C-c C-l L lsp-code-lens
C-c C-l e / C-c C-l E lsp-expand-selection / lsp-contract-selection
C-c C-l z lsp-fold-all
C-c C-l c / C-c C-l C call hierarchy incoming / outgoing
C-c C-l T lsp-semantic-tokens-apply
C-c C-l o lsp-open-link

Result buffers

References and symbol queries open read-only list buffers (*lsp-xref*, *lsp-symbols*). They behave like every list buffer in Macros:

Key Action
Enter visit the target for the current row
n / p move down / up
q close

Hover docs open in an *lsp-doc* buffer (q to dismiss). Code actions open an actions list where Enter applies the selected action.

Rename

lsp-rename validates the position with prepareRename, prompts you (pre-filled with the current name), and applies the resulting workspace edit across every file.

Logging

The language server's stderr is captured. View it with C-h l (list-lsp-log). To log server startup, set:

(set-option "lsp-log-on-start" #t)

Diagnostics & semantic tokens

LSP diagnostics drive the diagnostics list and gutter dots. Semantic-token highlighting shares the overlay-face layer with rainbow delimiters — enable one per buffer at a time so they don't fight over that layer.