Wednesday, 29 January 2025

Ada TS Mode

Ada TS Mode by Troy Brown is an Emacs-based IDE for Ada.

It’s a replacement for Emacs Ada Mode by Stephe Leake and others.

It’s prompted by Ada Mode’s difficult installation procedure, which requires building support packages on the user’s computer. This build process is somewhat sensitive to the compiler version used. Also, Ada Mode is no longer maintained, since the sole active maintainer retired.

Ada TS Mode uses Tree-sitter for parsing, and Ada Language Server for language support. Ada Language Server is an implementation of Microsoft’s Language Server Protocol for Ada.

Installing

Ada TS Mode

Ada TS Mode’s main documentation is in the README of the Github repository. It covers the prerequisites and installation. The important prerequisite is the use of Emacs 29 or later.

As well as the package ada-ts-mode, install the following packages:

gpr-ts-mode  
gpr-yasnippets  
lsp-mode  
company

If you’re planning to use eglot instead of lsp-mode, install the updated 1.18 version from the gnu-devel archive.

Ada Language Server

Download the latest ALS for your OS and architecture.

If you’re on a Mac, remove the ‘quarantine’ attribute on the downloaded archive by running

xattr -d com.apple.quarantine als-{rel}-{os}-{arch}.tar.gz

and unpack it. To support Visual Studio Code, this unpacks into integration/vscode/ada/{arch}/{os}/ - that last directory contains ada_language_server, and needs to be on your PATH.

Setting up

Lisp setup script

My setup script, to be invoked from ~/.emacs by (load "~/.emacs-ada.el"), is here. It’s based on Troy Brown’s init.el.

The main alteration is the method of finding the root of the project (LSP deals with all the files at and below the project root).

The default behaviour is to look for a repository, but this fails when your main project has subprojects (for instance, with Alire, a test crate contained in the main crate). Also, of course, you might not be in an Alire crate or a repo anyway, so probably a GPR file would be a reasonable choice.

The solution was to define two functions: to look for alire.toml,

(defun ada-mode--find-alire (dir)
  ;; Look up-tree, starting at dir, for alire.toml; return its full
  ;; path name if found, nil if not.
  (let ((alire (locate-dominating-file dir "alire.toml")))
    (if alire
      (cons 'transient alire)
      nil)))

and to look for a .gpr file,

(defun ada-mode--find-gpr (dir)
  ;; Look up-tree, starting at dir, for a .gpr file, returning its
  ;; full path name if found, nil if not.
  (let ((gpr (locate-dominating-file
              dir
              (lambda (dir) (directory-files dir nil "gpr"))
              )))
    (if gpr
      (cons 'transient gpr)
      nil)))

and to invoke them from the project package:

(use-package project
  :config
  (add-hook 'project-find-functions #'ada-mode--find-gpr)
  (add-hook 'project-find-functions #'ada-mode--find-alire)
  )

(the last hook installed is the first used).

It would have been possible to say this:

  :custom
  (project-vc-extra-root-markers '("alire.toml" "*.gpr")

but I prefer alire.toml to a .gpr file in a lower directory.

Customisations

Emacs customisations

Note, ada-ts-mode customisations are in the group ada-ts, gpr-ts-mode’s in the group gpr-ts.

I didn’t find any customisations necessary.

Formatting options

Currently, ada-ts-mode asks the Ada Language Server to apply formatting options using the GPR package Pretty_Printer.

This package can be used to provide options to the stand-alone GNATpp. ALS uses the same formatting engine, but be aware that

  • not all the options available in gnatpp are available in ALS, specifically --dictionary=casing-dictionary-file, and
  • some options may cause errors, specifically --call-threshold=nnn, --par-threshold=nnn (but ALS uses default 1 in both cases, which works well).

The default casing options, basically to use the same case as the declaration, work extremely well.

AdaCore intend to retire GNATpp in favour of the new GNATFormat, which is controlled by the GPR package Format. Ada TS Mode doesn’t yet have an option to select this.

Be aware that at present GNATformat doesn’t provide any casing support at all.

Usage notes

Since the general setup instructions assume you’re using package, you should put something like this in your ~/.emacs:

(require 'package)
(package-initialize)
(load "~/.emacs-ada.el")

(see here for my ~/.emacs-ada.el).

Key bindings

Meaning
M-. find definition
M-, go back
M-C-, go forward
M-? find references
M-C-. find apropos
C-x 4 . find definitions in other window
C-x 5 . find definitions in other frame
C-c C-b create a comment box for the current subprogram
C-c C-o find the other file (spec-> body, body->spec)
C-c C-p find the project file
C-<down-mouse-1> find definition

If you use mouse-buffer-menu, and have got used to using C-<down-mouse-1> to show a popup menu to select live buffers, you’ll need to consider the clash between that binding and the above binding in Ada TS Mode. I’ve resolved it by using Control-Shift-mouse button 1 for that purpose:

(global-set-key [C-S-down-mouse-1] #'mouse-buffer-menu)

though muscle memory makes me think I should maybe resolve that the other way.

Use with Alire

For Ada TS Mode to work well, it needs to have GPR_PROJECT_PATH set up. The best way to do this is to use alr edit (at the top of the crate), which you can set up with

alr settings --set --global editor.cmd 'open -n -a emacs ${GPR_FILE}'

which opens a new instance of Emacs, displaying the crate’s GPR file.

If you replace ${GPR_FILE} with just . (a period) the new instance will open a dired buffer on the current directory (which doesn’t have to be at the top of the crate, but must be in the crate).

No comments:

Post a Comment