lsp-bjolang/emacs/bjolang-lsp.el
Linus Björnstam c6f357c6f8 A language server for Bjolang
Runs the compiler in-process against the Bjolang checkout beside it, and
answers diagnostics, an outline, go to definition, hover and completion,
plus a request of its own for macro expansion.

Diagnostics come from the compiler's own check of the editor's text,
saved or not: on open, on save, and 400 ms after typing pauses. The
outline, definitions, hovers and completions read only as far as the
reader, with no type checking, so they answer on every request: the
top-level names of the file, its imports and the prelude, local names
from the binding forms the parser knows, the declarations and docs as
written, and the builtins with their types. emacs/bjolang-lsp.el shows a
macro call's expansion, one step or all of it, in a buffer.

test/smoke.py drives the server as an editor would.

🤖 Generated with [ECA](https://eca.dev)

Co-Authored-By: eca-agent <git@eca.dev>
2026-10-15 22:38:22 +02:00

83 lines
3.2 KiB
EmacsLisp

;;; bjolang-lsp.el --- Commands for the parts of BjolangLsp that LSP has no request for -*- lexical-binding: t; -*-
;;; Commentary:
;; Macro expansion: `bjo-expand-macro' shows the expansion of the macro call
;; at point all the way down, and `bjo-expand-macro-1' one step of it, in a
;; buffer of their own. With a prefix argument they keep the names hygiene
;; made, such as `tmp__37', which is what tells a template's name from the
;; caller's of the same spelling.
;;
;; The expansion is the compiler's, from its last check of the buffer; a
;; change not yet checked is checked first. `loop', `seql', `->' and the
;; other forms the parser turns into code itself are not macros, and have
;; no expansion to show.
;;; Code:
(require 'lsp-mode)
(defvar bjo-mode-map)
(defvar bjo-mode-hook)
(declare-function bjo-mode "bjomode")
(defun bjo--field (result key)
"The field KEY, a string, of RESULT as `lsp-mode' decoded it.
A hash table or a plist, as `lsp-use-plists' says, which can differ from
how this file was compiled."
(if (hash-table-p result)
(gethash key result)
(plist-get result (intern (concat ":" key)))))
(defun bjo--expand-macro (full hygienic)
"Show the expansion of the macro call at point.
FULL expands the macro calls in the expansion too; HYGIENIC keeps the
names hygiene made."
(let* ((params (append (lsp--text-document-position-params)
(list :full (if full t :json-false)
:hygienic (if hygienic t :json-false))))
(result (lsp-request "bjolang/expandMacro" params)))
(if (null result)
(message "No macro call at point.")
(let ((macro (bjo--field result "macro"))
(text (bjo--field result "text"))
(buffer (get-buffer-create "*bjo expansion*")))
(with-current-buffer buffer
(let ((inhibit-read-only t)
;; The expansion is not a file, and must not start a server.
(bjo-mode-hook nil))
(erase-buffer)
(bjo-mode)
(insert (format ";; (%s ...), %s%s\n\n"
macro
(if full "expanded all the way" "one step")
(if hygienic ", with hygiene's names" ""))
text
"\n")
(font-lock-ensure)
(goto-char (point-min)))
;; Read only, and `q' to leave, without binding `q' in the mode's
;; own map, which every Bjolang buffer shares.
(view-mode 1))
(display-buffer buffer)))))
(defun bjo-expand-macro (&optional hygienic)
"Show the macro call at point expanded all the way down.
With a prefix argument HYGIENIC, keep the names hygiene made."
(interactive "P")
(bjo--expand-macro t hygienic))
(defun bjo-expand-macro-1 (&optional hygienic)
"Show one step of the expansion of the macro call at point.
With a prefix argument HYGIENIC, keep the names hygiene made."
(interactive "P")
(bjo--expand-macro nil hygienic))
;; As SLIME binds its macroexpanders.
(with-eval-after-load 'bjo-mode
(define-key bjo-mode-map (kbd "C-c C-m") #'bjo-expand-macro-1)
(define-key bjo-mode-map (kbd "C-c M-m") #'bjo-expand-macro))
(provide 'bjolang-lsp)
;;; bjolang-lsp.el ends here