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 was 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

There are currently two formatting engines available in ALS; GNATpp and GNATformat. For each, ada-ts-mode by default sets the indentation to 3 spaces and the character set to utf-8.

GNATformat is the default.

AdaCore intend to retire GNATpp in favour of GNATFormat. Be aware that at present GNATformat doesn’t provide any casing support at all.

You can make a global choice of which engine to use by a setting in ~/.config/als/config.json:

{
    "useGnatformat": false,
}

(false for GNATpp, true for GNATformat).

.als.json in the top directory of your project, with the same content, will override the global configuration.

GNATpp

You can pass options in your project file in package Pretty_Printer. Several are available, but we have had consistently good results using just

  package Pretty_Printer is
      for Switches ("ada") use
        (
         "--source-line-breaks"
        );
   end Pretty_Printer;

which respects your line breaks and uses default casing options (very helpfully, to adjust the case of a name to that with which is was declared).

GNATformat

The relevant control options can be passed in package Format. The defaults seem to work reasonably.

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 .'

which opens a new instance of Emacs, with a dired buffer open on the current directory (which doesn’t have to be at the top of the crate, but must be in the crate).


Edits:

  • 8 March 2025: Updated the Customisations section.

No comments:

Post a Comment