-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathada-light-mode.el
240 lines (207 loc) · 9.74 KB
/
ada-light-mode.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
;;; ada-light-mode.el --- Light major mode for Ada -*- lexical-binding: t; -*-
;; Copyright (C) 2023 Sebastian Poeplau
;; Author: Sebastian Poeplau <[email protected]>
;; Keywords: languages
;; URL: https://github.com/sebastianpoeplau/ada-light-mode
;; Version: 0.2
;; Package-Requires: ((emacs "24.3") (compat "29.1"))
;; This file is not part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; This is a lightweight major mode for the Ada programming language. In
;; contrast to ada-mode, it doesn't require a precompiled parser, and it doesn't
;; do any intensive processing. As a consequence, it is faster but less
;; accurate.
;;; Code:
(require 'compat) ; for while-let
(defvar ada-light-mode-keywords
;; https://www.adaic.org/resources/add_content/standards/05rm/html/RM-2-9.html
'("abort" "else" "new" "return" "abs" "elsif" "not" "reverse" "abstract" "end"
"null" "accept" "entry" "select" "access" "exception" "of" "separate"
"aliased" "exit" "or" "subtype" "all" "others" "synchronized" "and" "for"
"out" "array" "function" "overriding" "tagged" "at" "task" "generic"
"package" "terminate" "begin" "goto" "pragma" "then" "body" "private" "type"
"if" "procedure" "case" "in" "protected" "until" "constant" "interface"
"use" "is" "raise" "declare" "range" "when" "delay" "limited" "record"
"while" "delta" "loop" "rem" "with" "digits" "renames" "do" "mod" "requeue"
"xor")
"Keywords of the Ada 2012 language.")
(defvar ada-light-mode--font-lock-rules
(list (regexp-opt ada-light-mode-keywords 'symbols))
"Rules for search-based fontification in `ada-light-mode'.
The format is appropriate for `font-lock-keywords'.")
(defvar ada-light-mode-syntax-table ; used automatically by define-derived-mode
(let ((table (make-syntax-table)))
;; Comments start with "--".
(modify-syntax-entry ?- ". 12" table)
;; Newlines end comments.
(modify-syntax-entry ?\n ">" table)
(modify-syntax-entry ?\r ">" table)
;; Backslash is a regular symbol, not an escape character.
(modify-syntax-entry ?\\ "_" table)
table)
"Syntax table used in `ada-light-mode'.")
(defvar ada-light-mode-other-file-alist
'(("\\.ads\\'" (".adb"))
("\\.adb\\'" (".ads")))
"Value for `ff-other-file-alist' in `ada-light-mode'.")
(defun ada-light-mode--syntax-propertize (start end)
"Apply syntax properties to the region from START to END."
;; Ada delimits character literals with single quotes, but also uses the
;; single quote for other purposes. Since character literals are always
;; exactly one character long (i.e., there are no escape sequences), we can
;; easily find them with a regular expression and change the syntax class of
;; the enclosing single quotes to "generic string". This also nicely handles
;; the case of '"': generic string delimiters only match other generic string
;; delimiters, but not ordinary quote characters (i.e., the double quote).
(goto-char start)
(while-let ((pos (re-search-forward "'.'" end t)))
(put-text-property (- pos 3) (- pos 2) 'syntax-table '(15))
(put-text-property (- pos 1) pos 'syntax-table '(15))))
(defvar ada-light-mode--imenu-rules
`(("Functions"
,(rx bol
(* space)
(? (? "not" (* space)) "overriding" (* space))
"function"
(+ space)
(group (+ (or word (syntax symbol)))))
1)
("Procedures"
,(rx bol
(* space)
(? (? "not" (* space)) "overriding" (* space))
"procedure"
(+ space)
(group (+ (or word (syntax symbol)))))
1)
("Types"
,(rx bol
(* space)
(? "sub")
"type"
(+ space)
(group (+ (or word (syntax symbol)))))
1)
("Packages"
,(rx bol
(* space)
"package"
(+ space)
(group (+ (or word (syntax symbol))))
(+ space)
"is")
1))
"Imenu configuration for `ada-light-mode'.
The format is appropriate for `imenu-generic-expression'.")
(defun ada-light-mode--indent-line ()
"Indent a single line of Ada code."
;; This is a really dumb implementation which just indents to the most recent
;; non-empty line's indentation. It's better than the default though because
;; it stops there, so that users who want completion on TAB can get it after
;; indenting. (The default behavior is to insert TAB characters indefinitely.)
(let ((indent (save-excursion
(beginning-of-line)
(if (re-search-backward "^[^\n]" nil t) ; non-empty line
(current-indentation)
0))))
(if (<= (current-column) (current-indentation))
(indent-line-to indent)
(when (< (current-indentation) indent)
(save-excursion (indent-line-to indent))))))
;;;###autoload
(define-derived-mode ada-light-base-mode prog-mode "AdaLBase"
"Base mode for `ada-light-mode' and `gpr-light-mode'."
;; Set up commenting; Ada uses "--" followed by two spaces.
(setq-local comment-use-syntax t
comment-start "--"
comment-padding 2)
;; Set up fontification.
(setq-local font-lock-defaults '(ada-light-mode--font-lock-rules nil t)
syntax-propertize-function #'ada-light-mode--syntax-propertize)
;; And finally, configure indentation. Since our indentation function isn't
;; particularly good, don't force it upon the user.
(setq-local standard-indent 3
tab-width 3 ; used by eglot for range formatting
indent-line-function 'ada-light-mode--indent-line
electric-indent-inhibit t))
;;;###autoload
(define-derived-mode ada-light-mode ada-light-base-mode "AdaL"
"Major mode for the Ada programming language.
It doesn't define any keybindings. In comparison with `ada-mode',
`ada-light-mode' is faster but less accurate."
(setq-local ff-other-file-alist ada-light-mode-other-file-alist
imenu-generic-expression ada-light-mode--imenu-rules))
;;;###autoload
(define-derived-mode gpr-light-mode ada-light-base-mode "GPRL"
"Major mode for GPR project files."
:syntax-table ada-light-mode-syntax-table)
;; Register the mode for Ada code following GNAT naming conventions.
;;;###autoload
(progn (add-to-list 'auto-mode-alist '("\\.ad[abcs]\\'" . ada-light-mode))
(add-to-list 'auto-mode-alist '("\\.gpr\\'" . gpr-light-mode)))
;; Configure eglot if available.
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs '((ada-light-mode :language-id "ada")
"ada_language_server"))
;; The Ada Language Server doesn't support formatting .gpr files, but it
;; provides completion and detects syntax errors.
(add-to-list 'eglot-server-programs '((gpr-light-mode :language-id "ada")
"ada_language_server" "--language-gpr"))
(defun ada-light-other-file ()
"Jump from spec to body or vice versa using the Ada Language Server."
(interactive)
(if-let ((server (eglot-current-server)))
(eglot-execute-command server
"als-other-file"
(vector (eglot--TextDocumentIdentifier)))
(message "%s" "Not connected to the Ada Language Server")))
;; The "als-other-file" command used by `ada-light-other-file' requires
;; support for the "window/showDocument" server request in eglot; add it if
;; necessary.
(unless (cl-find-method 'eglot-handle-request nil '(t (eql window/showDocument)))
(cl-defmethod eglot-handle-request
(_server (_method (eql window/showDocument)) &key uri &allow-other-keys)
(find-file (eglot--uri-to-path uri))
(list :success t)))
(defun ada-light-mode--current-line-empty-p ()
(save-excursion
(beginning-of-line)
(looking-at-p (rx (* space) eol))))
(defun ada-light-mode--indent-line-eglot ()
"Indent the current line using the Ada Language Server."
(interactive)
(if (ada-light-mode--current-line-empty-p)
;; Let's not "indent" empty lines with the language server, it would
;; just delete them. Instead, take a guess at the required indentation
;; based on the most recent non-empty line.
(indent-relative t t)
(condition-case err
(eglot-format (line-beginning-position) (line-end-position))
;; When `eglot-format' fails due to a server issue it signals the
;; underlying `jsonrpc-error'. In this case, let's return normally to
;; give completion a chance.
(jsonrpc-error
(when-let ((msg (alist-get 'jsonrpc-error-message (cdr err))))
(message "Language server error: %s" msg))
nil))))
(defun ada-light-mode--eglot-setup ()
"Set up `eglot' integration for `ada-light-mode'."
(when (eq major-mode 'ada-light-mode)
(if (eglot-managed-p)
(setq-local indent-line-function 'ada-light-mode--indent-line-eglot
electric-indent-inhibit nil)
(setq-local indent-line-function 'ada-light-mode--indent-line
electric-indent-inhibit t))))
(add-hook 'eglot-managed-mode-hook #'ada-light-mode--eglot-setup))
(provide 'ada-light-mode)
;;; ada-light-mode.el ends here