brownts / ada-ts-mode

Ada major mode using tree-sitter for Emacs
GNU General Public License v3.0
7 stars 2 forks source link

+TITLE: Ada Major Mode using Tree-Sitter

+AUTHOR: Troy Brown

+LANGUAGE: en

+OPTIONS: toc:nil ':t

+EXPORT_FILE_NAME: doc/ada-ts-mode.texi

+TEXINFO_DIR_CATEGORY: Emacs

+TEXINFO_DIR_TITLE: Ada Tree-Sitter Mode: (ada-ts-mode)

+TEXINFO_DIR_DESC: Ada Major Mode using Tree-Sitter

+HTML: MELPA

+HTML: CI

A major mode for Ada files, which utilizes the Emacs built-in support for tree-sitter, first available starting with Emacs 29. The tree-sitter functionality is used to build an in-memory concrete syntax tree of the parsed language, allowing operations such as syntax highlighting to be performed more accurately than historical methods (e.g., regular expressions).

This major mode provides support for syntax highlighting, navigation, Imenu, "which function" (i.e., displaying the current function name in the mode line), indentation (via LSP) and outlining (via =outline-minor-mode=).

Note: This major mode is based on the Emacs 29 (or newer) built-in tree-sitter support, not to be confused with the separate Emacs tree-sitter package. The two are not compatible with each other.

Note: Outlining is made available through Emacs 30 (or newer) built-in support for outlining within tree-sitter major modes, therefore it is required for =outline-minor-mode= to work with =ada-ts-mode=.

There are a couple of requirements which must be met in order to use tree-sitter powered major modes. The Emacs documentation should be consulted which will provide complete details. The following are the main points to consider:

There are multiple ways in which a package can be installed in Emacs. The most convenient way is to use a package archive, however installation directly from the git repository is also possible. In addition, there are multiple third party package managers available, but installation instructions in this section will focus only on the built-in package manager (i.e., =package.el=). It is assumed that power-users will not need direction as to how to use other package managers.

In addition to package management, it is also common practice to perform package configuration. There are also multiple third party packages for managing your package configuration, however =use-package= is now built-in to Emacs. Refer to the example configuration section for ideas on how to utilize =use-package= to setup your own personal configuration.

** From the MELPA Package Archive

This package can be installed from the MELPA package archive using the Emacs built-in package manager (i.e., =package.el=). MELPA is not configured in the package manager by default, but the following can be used to configure the use of the MELPA archive. Refer to [[https://melpa.org/#/getting-started][Getting Started]] for additional details on configuring and using the MELPA package archive.

+BEGIN_SRC emacs-lisp

(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)

+END_SRC

Once configured as above, instruct the package manager to refresh the available packages and to perform the installation, as follows:

** From the Git Repository

Installation directly from the source repository is possible using ~package-vc-install~. The following command can be used to perform this installation:

=M-x package-vc-install RET https://github.com/brownts/ada-ts-mode RET=

In order for ada-ts-mode to be useful, it needs to have the specific tree-sitter Ada language grammar library installed. This library is different from the tree-sitter library mentioned in the prerequisites section (e.g., =libtree-sitter.so= vs =libtree-sitter-ada.so=). The library is not bundled with ada-ts-mode, but is maintained separately. With the default configuration, the first time ada-ts-mode is loaded (in the absence of an existing installed library) it will prompt to download, build and install the grammar library. The following settings provide control over this activity.

In order to build the library, you will need to have a C compiler installed. Refer to the Emacs documentation surrounding ~treesit-install-language-grammar~, as ada-ts-mode uses the built-in Emacs functionality to perform the download, building and installation of the library.

It's also possible to skip this step if you already have a pre-built library for the language. In which case, placing the pre-built library in the correct location will allow ada-ts-mode to find and use the library. You can customize ~treesit-extra-load-path~ to add extra locations to search for libraries.

You will only be prompted if the library can't be found in one of the expected locations. The prompting can also be controlled by changing the ~ada-ts-mode-grammar-install~ setting.

If manually installing, or troubleshooting the installation of the Ada language grammar, you can use the following to check whether Emacs can locate the library:

There are 4 different levels of syntax highlighting available, providing an increasing amount of highlighting. By default in Emacs, level 3 (controlled by ~treesit-font-lock-level~) is used to provide a compromise between providing too little and too much fontification. It should be noted that the levels are cumulative, meaning that each level also includes all of the fontification in the levels below it. The following provides the list of features and how they are mapped to the different font lock levels.

Built-in tree-sitter support for indentation is not currently available. However, if ~ada-ts-mode~ is used in conjunction with the Ada Language Server, indentation support can be provided by the language server itself.

The following user options can be customized to modify the indentation as needed.

** Ada Language Server Indentation

Since the Ada Language Server provides code formatting, not just an indentation engine, it may be necessary to configure the settings of the code formatter to meet behavioral desires. Under the hood, Ada LS uses the GNAT pretty printer engine to format the line or region of the buffer. For minimal impact to the buffer, it's likely desirable to use the =--source-line-breaks= switch to prevent the pretty printer from reformatting the buffer beyond indentation. Refer to the GNAT User's Guide for the full set of options available for the pretty printer.

The value of ~ada-ts-mode-indent-offset~ is provided to the Ada Language Server as the LSP "tab size" parameter. This corresponds to the main indentation amount (i.e., the =--indentation= switch for the pretty printer).

The switches for the pretty printer should be configured in the project's GPR file. GPR files support a =Pretty_Printer= package which is where the switches should reside. The Ada LS will read the pretty printer switches from the project's GPR file to control formatting.

The major mode implements the normal source navigation commands which can be used to move around the buffer (i.e., =C-M-a=, =C-M-e=, etc). It should also be noted that =which-function-mode= is also supported and will show the current package and/or subprogram in the mode line, when enabled.

With the provided Imenu support, additional options are available for ease of navigation within an Ada source file. Imenu supports indexing of declarations, bodies and stubs for packages, subprograms, task units and protected units as well as type declarations and with clauses.

This section identifies mode-specific commands which are provided to the user. The expectation is that if the user finds these commands useful, they will bind them into the local mode map. The commands are not bound by default as it would be presumptuous to assume where the user would want these bound, if at all. Binding examples are provided in the example configuration section.

In order to integrate functionality provided by the Ada Language Server (such as indentation), ~ada-ts-mode~ must interact with an active LSP client. Since multiple LSP clients exist, this interaction must be configurable such that additional LSP clients can be added when needed. Additionally, ~ada-ts-mode~ must be able to determine which LSP client is active in the buffer. If there is no active LSP client in the buffer, ~ada-ts-mode~ will not be able to make use of LSP provided capabilities and thus falls back on providing this capability without it's support, which likely will be less precise.

It's up to the user to configure the LSP client to be active in an ~ada-ts-mode~ buffer, typically through the use of the ~ada-ts-mode-hook~ to enable the LSP client minor mode, although those clients which utilize directory local variables for configuration (e.g., GPR project filename), may need to hook into the ~hack-local-variables-hook~ in order to initialize after the directory local variables have been initialized. Refer to the documentation for the specific LSP client used for details.

A generic interface is used to support different LSP clients. This interface is described in =ada-ts-mode-lspclient.el=. There are separate files for each supported LSP client which implement those interfaces. These separate files are configured to automatically load when both ~ada-ts-mode~ and the corresponding LSP client package have been loaded.

To add an unsupported LSP client, add client-specific methods for all of the interfaces described in =ada-ts-mode-lspclient.el=. In addition, a parameterless function for that client should be added to the ~ada-ts-mode-lspclient-find-functions~ hook variable (via ~add-hook~). That function, when invoked, should determine if the LSP client is active in the current buffer, and when active, return an LSP client instance (which can be used to dispatch on the client-specific methods), else return ~nil~.

** Org Mode Source Code Blocks

When Org Mode doesn't know the major mode for the language of a source block, it will guess by appending "-mode" to the end of the language name. If we use a language name of "ada", this means it will look for a major mode named "ada-mode". This default behavior doesn't work if we want to use Tree-Sitter enabled modes. Maybe in the future it will be aware of these modes, but in the meantime, we can explicitly configure Org Mode to map to the Tree-Sitter major mode using the customization variable =org-src-lang-modes=.

The following can be added to your configuration to persist the setting:

+BEGIN_SRC emacs-lisp

(with-eval-after-load 'org-src (add-to-list 'org-src-lang-modes '("ada" . ada-ts)))

+END_SRC

** Function Calls not Highlighted Correctly

If you observe places in the syntax highlighting where functions calls are not being properly highlighted, such as an array being highlighted as a function call, or a parameterless function call not being highlighted, this is due to ambiguities in the syntax. From a pure syntax perspective, array accesses look the same as function calls. Also parameterless function calls look the same as variable accesses. In places where it can be determined from the syntax (such as a generic package instantiation) care is taken to avoid highlighting these places as function calls. In other places, it cannot be known from the syntax tree alone and that is where the syntax highlighting will become inaccurate.

One way to address this slightly inaccurate syntax highlighting of function calls, is simply to disable it. An easy way to perform this is through the use of the =treesit-font-lock-recompute-features= function. Using this function when loading the major mode will allow you to customize which features are enabled/disabled from the default settings.

+BEGIN_SRC emacs-lisp

(defun ada-ts-mode-setup () (treesit-font-lock-recompute-features nil '(function)))

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

+END_SRC

If disabling function call highlighting is not sufficiently satisfying, another approach is to augment the syntax highlighting of =ada-ts-mode= with that of the Ada language server. The language server is capable of providing semantic highlighting, which is what is needed in this situation. Refer to the Ada language server and a corresponding Emacs Language Server Protocol (LSP) client. Not all LSP clients for Emacs support semantic highlighting, so investigate first before selecting one. When semantic highlighting is used with =ada-ts-mode=, inaccurate function call highlighting will be corrected. This includes both places where function calls are being highlighted, which aren't real function calls, as well as places which are function calls but are not being highlighted. In addition to function call highlighting, semantic highlighting provides highlighting of other semantic information, therefore it is highly recommended.

If you do observe places where function call syntax highlighting is inaccurate and it can be determined from the syntax tree, this is considered a bug and should be reported by filing an issue against the package.

The following is an example configuration using =use-package= to manage this configuration. It assumes that =package.el= is your package manager. This checks to make sure tree-sitter support is enabled in Emacs before attempting to install/configure the package, thus your configuration will remain compatible with versions of Emacs which don't yet support tree-sitter, and will not install and configure this package in its absence. This also demonstrates how to configure indentation support through the Ada Language Server (assuming the user has configured an LSP client to be active in the buffer).

+BEGIN_SRC emacs-lisp

(when (and (fboundp 'treesit-available-p) (treesit-available-p)) (use-package ada-ts-mode :ensure t :defer t ; autoload updates `auto-mode-alist' :custom (ada-ts-mode-indent-backend 'lsp) ; Use Ada LS indentation :bind (:map ada-ts-mode-map (("C-c C-b" . ada-ts-mode-defun-comment-box) ("C-c C-o" . ada-ts-mode-find-other-file) ("C-c C-p" . ada-ts-mode-find-project-file))) :init ;; Configure source blocks for Org Mode. (with-eval-after-load 'org-src (add-to-list 'org-src-lang-modes '("ada" . ada-ts)))))

;; Configure Electric Pair

(use-package elec-pair :ensure nil ; built-in :hook (ada-ts-mode . electric-pair-local-mode))

+END_SRC

Local Variables:

eval: (add-hook 'after-save-hook #'org-texinfo-export-to-info nil t)

End: