Getting Emacs ada-mode working

I’ll try to speak to each of your concerns and am open to making changes that improves the user experience. I apologize in advance that this response ended up being rather lengthy.

Thank you about that. I appreciate your lengthy help.

For Imenu at least, you are just a M-x imenu-add-menubar-index away. You could also add this to the mode hook. I can add this to the example configuration if it would help. Personally, I use consult-imenu (and imenu in Emacs 30 with the imenu-flatten setting), so this is another case where people may use different approaches to solve their problem. I typically bind consult-imenu to C-c C-m.

I used M-x imenu-add-menubar-index but didn’t know about consult-imenu. It seems useful.

I don’t like choosing people’s key bindings, because everyone has different key chord styles they prefer, especially when it comes to the Control key and where it resides on their keyboard.

As a user, I appreciate when I’m not flooded by a sea of choices. Having a well thought set of defaults shouldn’t hart anybody, or does it in a way I don’t see?

What about project-find-file (i.e., C-x p f)?

I tried that, and it discovered the root of my project (as a git repository, I guess). But it completely ignored my ads and adb. It offered even * ~ files, but not my sources. I guess it requires a configuration. Could it be done by the Ada mode?

For building the project, does project-compile (i.e., C-x p c) not work for you?

(project-compile)

Run ‘compile’ in the project root.

Seems useful, but it would be nice if it populated the prompt with gprbuild if there is a gpr file in the project root, or alr build if there is alire.toml

Put please log a GitHub issue if you are struggling with this and I can help you there.

Using this other Pretty_Printer option and upgrading eglot to 1.7 did not solve the issue. And there is a strange behaviour now, that I don’t remember happening before; it inserts 4 characters after typing every “;”.

I’ll try to find out more, and probably I have to open that issue, thanks.

I personally prefer to use lsp-mode with ada-ts-mode because it supports semantic highlighting and Eglot doesn’t, but either LSP client should work with ada-ts-mode.

I tried lsp-mode and I received errors and strange behaviour including deleting what I typed.

Error running timer ‘lsp–on-idle’: (wrong-type-argument number-or-marker-p nil) [2 times]

lsp-mode is version 20240906.1743 from melpa.

If you have ideas on how to exploit it that haven’t been achieved, please share them with me.

The only thing that comes to my mind is providing a command to build the project, but I don’t know if that is provided by the Ada Language Server or the VS Code extension. Remaining things might be already there, but I don’t get the setup or the compatible set of packages to make it work. I’m not happy to admit it, but VS Code provided a very good out-of-the-box experience, but I miss a lot of functionality of Emacs for basic editing to make the switch. Indentation was also a negative point, compared to the traditional ada-mode.

By the way, I found an interesting discussion when searching for ada-ts-mode in the emacs-devel list: Re: Ada-mode to be abandoned? I don’t know if you were aware of it.

Not sure why, but it’s working now in the expected way (I guess) when running with lsp-mode and indentation set to LSP. I no longer receive the errors. Indentation is sometimes incorrect, but I asume is ALS fault, or a matter of adjusting the Pretty_Printer package. Example, it changes this:

         Text.setPosition (Score_Text,
                           (x => Width - Margin -
                              Text.getLocalBounds (Score_Text).width,
                            y => Y));

into this:

         Text.setPosition (Score_Text,
           (x     => Width - Margin -
               Text.getLocalBounds (Score_Text).width,
                y => Y));

But I’m activating lsp-mode by hand. How are you configuring it to start automatically?

What could be of help, might be to have an example project in the same repository or another with your proposed configuration for .emacs, the .dir-locals.el and whatever else needed.

Yes, as TKurtBond noted, one can certainly override keybindings (including unsetting them altogether). The issue is mainly about being considerate of others, and not blithely assuming that one’s package can do whatever it wants. It’s like spreading one trolley across the width of a supermarket aisle - sure, people can work around it, but why create extra work for people in the first place? Of course, having to manually specify key bindings as a result of not providing any by default also creates work that people might not want to have to deal with - hence my compromise of “Here’s a set of keybindings you can get simply by running this command (e.g. in your startup file)”.

Appendix D of the Emacs Lisp Reference Manual describes key binding conventions that people are encouraged to follow, which goes some way to mitigating the clashing keybindings issue, not least because of the space reserved for use by major modes. But still, with so many packages available nowadays - MELPA currently has over 5800+ - and with people using so many different subsystems / packages simultaneously, it’s becoming increasingly easy to get keybinding conflicts. i’d been using C-x x as a prefix for my “personal global keymap” for years before Emacs 28 annexed that space “for various buffer-oriented commands”. As it turns out, they’re commands i’m unlikely to use much and thus don’t need a binding for, so yeah, i just override them and continue to use the bindings i have in muscle memory. :slight_smile:

Finally, another factor is that there are many people nowadays using Evil / Vim keybindings (including in the context of Emacs distros like Doom), such that mode authors can’t assume that creating a set of Emacs-style keybindings will be sufficient for everyone - it might be necessary to provide a set of Evil keybindings, e.g. via evil-collection.

1 Like

Thanks!

Okay, I’ll put something together for this. It’ll probably have a configurable prefix so the mapping can be moved around.

I should be able to add some useful defaults here. Most of what happens in VSCode with regards to creating a build command is happening on the extension side. You can query the Language Server for executables in the project file, but the rest is handled by the extension itself.

I know what you mean. I use both Emacs and VSCode, but I’m trying to get my Emacs setup to the point where it’s on-par with VSCode settings. VSCode has set the bar high.

Glad to hear you have it working now. Yes, the indentation provided by the Ada Language Server can be a bit odd sometimes. I’m assuming this is because it’s trying to use a code formatter to perform line indentation and so it’s not always perfect. I know of a few issues where the indentation changes depending on which part of the file you’re at. The formatting engine is supposed to get an overhaul so I’m hoping that will address these oddities.

Adding the lsp command (or eglot-ensure for Eglot) to the major mode hook should be sufficient to enable it.

(add-hook 'ada-ts-mode-hook #'lsp)

That was a great idea. I’ve now created a new repository with a minimal Emacs configuration that will install a minimal set of packages and allow you to choose between lsp-mode or Eglot for your LSP client. Check out dotemacs-ada and let me know if it helps. The README provides instructions for using it, including creation of .dir-locals.el and updating the Pretty_Printer package in the GPR file.

1 Like

It helps indeed! Thanks for your aid in An error occurred while loading `.emacs` · Issue #1 · brownts/dotemacs-ada · GitHub

1 Like

Having been infuriated once too often by crashes in the Ada syntax/highlight/cross-reference support in ada-mode, and seen Stephe’s mention of eglot, I thought it might be - interesting - to check it out.

macOS 15.0.1/arm64
emacs 29.3
ada-mode 8.1.0
eglot 1.17 (from elpa)
ada language server 25.0.20240915

Formatting OK, so long as

  • I set tab-width to ada-indent in the buffer, ada-mode’s attempt to do via (let tab-width ada-indent) doesn’t work
  • I use the very latest ALS, because it has to think your code is syntactically correct.

Earlier versions of ALS (for example, the one you get via Alire) did not work well.

Further work, unless someone has better notions:

  • get gpr-mode working
  • get the ada-mode menu options (e.g. ‘find references’) working where possible

I’ve being trying with this configuration:

(use-package company
  :ensure t
  :defer t
  :hook (eglot-mode . company-ensure))

(use-package eglot
  :ensure t
  :defer t)

(use-package ada-mode
    :ensure t
    :defer t
    :custom
    (ada-auto-case t)
    (ada-build-prompt-prj 'search-prompt)
    (ada-case-strict nil)
    (ada-diagnostics-backend 'eglot)
    (ada-xref-backend 'eglot))

(add-hook 'after-init-hook 'global-company-mode)

EDIT: I removed :hook (ada-mode . eglot-ensure) from (use-package eglot ...) because it could lead to errors when activating eglot in other language modes, and it wasn’t needed.

Then in the root of your project, a .dir-locals.el file for configuring the project, for example:

((ada-mode . ((ada-eglot-gpr-file . "play_2048.gpr"))))

For cross-reference, eglot will activate the standard Xref keys.

For completion, this is using company-mode.

This is still using wisi for indentation support, which I find much better than ALS.

I haven’t tested this configuration for too long, but it seems to work fine.

Using Emacs 29.4, ALS 25.0w (20240915) and these versions for the packages:

  ada-mode                       8.1.0          installed             major-mode for editing Ada sources
  company                        0.10.2         installed             Modular text completion framework
  eglot                          1.17           installed             The Emacs Client for LSP servers
  gpr-mode                       1.0.4          installed             Major mode for editing GNAT project files

Great stuff, thanks!

I added (ada-face-backend 'none).

I’ve also tried adding these (or changes to these effects)

    (add-hook 'before-save-hook #'delete-trailing-whitespace nil t)
    (add-hook 'before-save-hook
             (lambda () (untabify (point-min) (point-max))) nil t)

but using hook:, init:, config:, custom: … ignored.

That doesn’t seem to have any effect. I don’t think ada-eglot-gpr-file ever gets passed into eglot?

It took me a while to see that you didn’t mean the no-longer-bound ada-mode keys!

ALS is about as opinionated as the Rational Environment (i.e. you had no control!), but I find it better to put up with that than to have to restart Emacs when the wisi parser gets confused! (which it seems to have been doing more lately? new OS? new SDK?)

Is that because you had problems with wisi, or because you don’t like so much fontification?

I had problems using hook: with ada-mode inside the use-package sentence. I think something is broken on that regard.

Regarding, deleting trailing whitespace, I’m using MELPA because it doesn’t introduce unexpected changes in shared sources.

You are right, because eglot-show-workspace-configuration says null, so I don’t know what’s going on. I took that variable from Ada Mode. It seems ALS is finding the project file because it’s the only one in the project root.

Is it not enough with running “Ada > Misc > Restart parser” or M-x wisi-kill-parser?
Can you point to some source snippet that makes the wisi parser confused? I could then tell you whether I reproduce the problem.

Faces, wisi: I had so much on my mind with the riscv ports of FreeRTOS-Ada (which I’ve now put on the back burner) that dealing with wisi was too much. Sometimes I could save the current buffer, kill it, and re-open; sometimes that didn’t work, and I had to restart emacs.

Trailing whitespace: this shouldn’t happen with Ada that’s been style-checked. I do agree about introducing whitespace changes when making PRs.

Project root; getting close to solving this one! Eglot uses project.el to find the root, and the default method is to look for a repo. I’ve managed to get it to look first for alire.toml; it would be good if that fails to look for a .gpr, then a repo.

(defun ada-mode--find-alire (dir)
  (let ((alire (locate-dominating-file dir "alire.toml")))
    (if alire
      (cons 'transient alire)
      nil)))

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

This works for projects using Alire, and for projects with a single .gpr file in the root. Multiple .gpr files confuse ALS.

(defun ada-mode--find-alire (dir)
  (let ((alire (locate-dominating-file dir "alire.toml")))
    (if alire
      (cons 'transient alire)
      nil)))

(defun ada-mode--find-gpr (dir)
  (let ((gpr (locate-dominating-file
              dir
              (lambda (dir) (directory-files dir nil "gpr"))
              )))
    (if gpr
      (cons 'transient gpr)
      nil)))

(use-package project
  :config
  ;; last-in, first-used
  (add-hook 'project-find-functions #'ada-mode--find-gpr)
  (add-hook 'project-find-functions #'ada-mode--find-alire)
  )

I was going to suggest you this configuration by @brownts, but I see that you have found a solution with your requirements.

An advantage of using project-vc-extra-root-markers over rolling your own project implementation is that you leverage the filtering performed by the VC implementation of project-files, which differs from the default implementation. Using project-vc-extra-root-markers, if your “Alire project” is also a “Git project”, the VC implementation of project-files will omit VC-specific files (such as all files under the .git directory), and thus they won’t show up as candidates in project-specific commands, such as project-find-file, project-find-regexp, etc.

1 Like

It seems to me there are several different ways in which a file tree could be a project (from the ALS point of view):

  • it’s a repo
  • alire.toml present
  • (one) GPR file present
  • none of the above, just a file tree

and I wanted to cater for any of them, in combinations (bar the last, ofc).

I was put off project-vc-extra-root-markers mainly because of the ‘vc’ in the name, and the trouble I was having with project’s default being to look for a repo.

I’ll try the suggestion (without @mgrojo’s adainclude, I don’t see the reason for that!), though a quick check with project-find-file didn’t work for me.

I don’t consider a GPR file to be a foolproof way to indicate the top of a project as I’ve see many projects that have GPR files in sibling directories, nested in source directories, etc. However, maybe in cases where it really does indicate the top of a project, project-vc-extra-root-markers could be set in a .dir-locals.el. When all else fails, you can also specify a project manually. However, if you find it useful, it certainly could be in project-vc-extra-root-markers, as it also supports glob patterns.

I agree that the name is a bit of a misnomer, but take a look at the documentation for it, as the additional markers don’t have to mark a VC directory.

It is most useful when a project has subdirectories inside 
it that need to be considered as separate projects.  It can 
also be used for projects outside of VC repositories.

I initially went down the route of creating my own project detection similar to you before I discovered that project-vc-extra-root-markers was really what I was looking for, due to the extra filtering it provides as well as the order in which projects are detected.

Using project-vc-extra-root-markers checks for all of these (i.e., VC repositories and all of the extra markers at each directory level). This is beneficial because if you didn’t use this, the ordering of your project detection functions in project-find-functions will determine the order in which your dedicated projects are found. Therefore, if you place an “Alire finding function” first, you’ll find it first even if it’s higher up in the directory tree than a .git directory.

The inclusion of adainclude is to catch a corner case. If you visit a run-time file from your application (via the xref commands) and use Eglot as the LSP client, Eglot has special logic to treat that visited file as part of your application project. For lsp-mode, it has a similar concept of a library, for which I have a pending PR for that to treat adainclude directories as libraries.

However, if using Eglot, you visit that file directly (not through a xref command), Eglot won’t use it’s special logic. Thus, since the run-time is not guaranteed to have an alire.toml or even a GPR file necessarily, this is to catch that particular corner case.

This breadth-first strategy is one reason I went for a depth-first approach, since I think the presence of an alire.toml is a very good indication of there being a project. Will have to think about this, thanks for the pointer.