Setting up Emacs for Stan in 2026

It is easier than ever to set up Emacs for Stan development. Here’s a screen shot of the kind of thing you can expect:

The entire init.el needed to get this behavior is below (excluding the theme, which is zenburn).

Simply ensure Emacs is at least version 29 (released July 2023) and it was compiled with tree-sitter support and the elisp will do the rest

(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
;; elgot is built in to emacs 29+, but some features work better if you use the
;; latest version from GNU ELPA
(add-to-list 'package-archives '("gnu-devel" . "https://elpa.gnu.org/devel/") t)
(package-initialize)


(use-package treesit
  :if (treesit-available-p)
  :ensure nil
  :config
  (setq treesit-language-source-alist
      '((stan . ("https://github.com/WardBrian/tree-sitter-stan" "v0.3.0" "grammars/stan/src"))
        (stanfunctions . ("https://github.com/WardBrian/tree-sitter-stan" "v0.3.0" "grammars/stanfunctions/src"))))
  (unless (treesit-language-available-p 'stan)
    (treesit-install-language-grammar 'stan))
  (unless (treesit-language-available-p 'stanfunctions)
    (treesit-install-language-grammar 'stanfunctions)))

(use-package stan-ts-mode
  :requires treesit
  :mode (("\\.stan\\'" . stan-ts-mode) ("\\.stanfunctions\\'" . stan-functions-ts-mode))
  :defer t
  :ensure t)

(defcustom bmw/stan-language-server-location
  (expand-file-name (concat "bin/stan-language-server" (car exec-suffixes)) user-emacs-directory)
  "Location to download the stan-language-server binary to."
  :type 'file
  :group 'stan)

(use-package url)

(defun bmw/download-stan-language-server (&optional force)
  "Download the latest copy of the stan-language-server.
The location is determined by stan-ts-mode-language-server-location.
Argument FORCE will make the download proceed even if the file exists."
  (interactive "P")
  (when (or force (not (file-exists-p bmw/stan-language-server-location)))
    (let*
        ((version
          (with-temp-buffer
            (url-insert-file-contents
             "https://api.github.com/repos/tomatitito/stan-language-server/releases/latest")
            (let ((json  (json-parse-buffer)))
              (gethash "tag_name" json))))
         (os-tag
          (pcase system-type
            ((or 'windows-nt 'cygwin 'ms-dos) "windows-x86_64")
            ('darwin (concat "macos-"
                             (if (string-match-p "aarch64\\|arm" system-configuration ) "aarch64" "x86_64") ))
            (_  (concat "linux-"
                        (if (string-match-p "aarch64\\|arm" system-configuration ) "arm64" "x86_64")))))
         (url
          (concat
           "https://github.com/tomatitito/stan-language-server/releases/download/"
           version
           "/stan-ls-"
           version
           "-"
           os-tag
           (car exec-suffixes)))
         (file bmw/stan-language-server-location))
      (make-empty-file file t)
      (delete-file file)
      (url-copy-file url file)
      (chmod file 500))))

;; work around https://debbugs.gnu.org/cgi/bugreport.cgi?bug=69423
(assq-delete-all 'eglot package--builtins)
(assq-delete-all 'eglot package--builtin-versions)

(use-package eglot
  :ensure t
  :pin gnu-devel
  :hook ((stan-ts-base-mode . bmw/download-stan-language-server)
         (stan-ts-base-mode . eglot-ensure))
  :config
  (add-to-list
   'eglot-server-programs
   `(stan-ts-base-mode . (,bmw/stan-language-server-location "--stdio"))))

This also serves as an announcement that stan-ts-mode is now on MELPA. Thanks to @avehtari for encouraging me to finish polishing the mode and for implementing some nice features like better auto-indentation.

3 Likes

Awesome!

Should there be

:mode ("\\.stan\\'" "\\.stanfunctions\\'")

So that the stan-ts-mode is loaded if you open a .stanfunctions file before opening any .stan -files? (Probably a rare case, but…)

1 Like

If you’re a big user of .stanfunctions files that is probably a better default config. Note that the stan-language-server support for .stanfunctions is more complete than the tree-sitter-stan support (which does its best, but is not actually aware of the functions-only syntax, see Grammar for `.stanfunctions` · Issue #10 · WardBrian/tree-sitter-stan · GitHub)

Some updates: stan-ts-mode now also includes a mode for proper stanfunctions support. I’ve updated the elisp snippet in the OP to reflect this, and to include a function for automatically fetching the stan-language-server for use with eglot.

2 Likes