aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRandy Taylor2022-12-11 18:41:16 -0500
committerYuan Fu2022-12-15 17:20:50 -0800
commitfee2efe1b035d601ac53a32801227402e9be8bca (patch)
treeda0956063942da9764fea942547d18e100af5cfd
parente8f7ab67ad18063155f6a7ba14a29e3679dc1e76 (diff)
downloademacs-fee2efe1b035d601ac53a32801227402e9be8bca.tar.gz
emacs-fee2efe1b035d601ac53a32801227402e9be8bca.zip
Add go-ts-mode and go-mod-ts-mode (Bug#60025)
* admin/notes/tree-sitter/build-module/batch.sh: * admin/notes/tree-sitter/build-module/build.sh: Add go-mod support. * etc/NEWS: Mention them. * lisp/progmodes/eglot.el (eglot-server-programs): Add them. * lisp/progmodes/go-ts-mode.el: New major modes with tree-sitter support.
-rwxr-xr-xadmin/notes/tree-sitter/build-module/batch.sh1
-rwxr-xr-xadmin/notes/tree-sitter/build-module/build.sh5
-rw-r--r--etc/NEWS8
-rw-r--r--lisp/progmodes/eglot.el3
-rw-r--r--lisp/progmodes/go-ts-mode.el354
5 files changed, 370 insertions, 1 deletions
diff --git a/admin/notes/tree-sitter/build-module/batch.sh b/admin/notes/tree-sitter/build-module/batch.sh
index e7ef45cf57d..c50b9df37ed 100755
--- a/admin/notes/tree-sitter/build-module/batch.sh
+++ b/admin/notes/tree-sitter/build-module/batch.sh
@@ -9,6 +9,7 @@ languages=(
9 'c-sharp' 9 'c-sharp'
10 'dockerfile' 10 'dockerfile'
11 'go' 11 'go'
12 'go-mod'
12 'html' 13 'html'
13 'javascript' 14 'javascript'
14 'json' 15 'json'
diff --git a/admin/notes/tree-sitter/build-module/build.sh b/admin/notes/tree-sitter/build-module/build.sh
index 4195ea58c3c..b6c83ea9b99 100755
--- a/admin/notes/tree-sitter/build-module/build.sh
+++ b/admin/notes/tree-sitter/build-module/build.sh
@@ -26,6 +26,11 @@ case "${lang}" in
26 "cmake") 26 "cmake")
27 org="uyha" 27 org="uyha"
28 ;; 28 ;;
29 "go-mod")
30 # The parser is called "gomod".
31 lang="gomod"
32 org="camdencheek"
33 ;;
29 "typescript") 34 "typescript")
30 sourcedir="tree-sitter-typescript/typescript/src" 35 sourcedir="tree-sitter-typescript/typescript/src"
31 grammardir="tree-sitter-typescript/typescript" 36 grammardir="tree-sitter-typescript/typescript"
diff --git a/etc/NEWS b/etc/NEWS
index 701e414fdbb..dd11b3c2715 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -3081,6 +3081,14 @@ A major mode based on the tree-sitter library for editing files
3081written in TOML, a format for writing configuration files. It is 3081written in TOML, a format for writing configuration files. It is
3082auto-enabled for files with the ".toml" extension. 3082auto-enabled for files with the ".toml" extension.
3083 3083
3084*** New major mode 'go-ts-mode'.
3085A major mode based on the tree-sitter library for editing programs in
3086the Go language. It is auto-enabled for files with the ".go" extension.
3087
3088*** New major mode 'go-mod-ts-mode'.
3089A major mode based on the tree-sitter library for editing "go.mod"
3090files. It is auto-enabled for files which are named "go.mod".
3091
3084 3092
3085* Incompatible Lisp Changes in Emacs 29.1 3093* Incompatible Lisp Changes in Emacs 29.1
3086 3094
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 9c5a361df7b..02bb6bb665d 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -211,7 +211,8 @@ chosen (interactively or automatically)."
211 (elm-mode . ("elm-language-server")) 211 (elm-mode . ("elm-language-server"))
212 (mint-mode . ("mint" "ls")) 212 (mint-mode . ("mint" "ls"))
213 (kotlin-mode . ("kotlin-language-server")) 213 (kotlin-mode . ("kotlin-language-server"))
214 ((go-mode go-dot-mod-mode go-dot-work-mode) . ("gopls")) 214 ((go-mode go-dot-mod-mode go-dot-work-mode go-ts-mode go-mod-ts-mode)
215 . ("gopls"))
215 ((R-mode ess-r-mode) . ("R" "--slave" "-e" 216 ((R-mode ess-r-mode) . ("R" "--slave" "-e"
216 "languageserver::run()")) 217 "languageserver::run()"))
217 ((java-mode java-ts-mode) . ("jdtls")) 218 ((java-mode java-ts-mode) . ("jdtls"))
diff --git a/lisp/progmodes/go-ts-mode.el b/lisp/progmodes/go-ts-mode.el
new file mode 100644
index 00000000000..124d9b044a2
--- /dev/null
+++ b/lisp/progmodes/go-ts-mode.el
@@ -0,0 +1,354 @@
1;;; go-ts-mode.el --- tree-sitter support for Go -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2022 Free Software Foundation, Inc.
4
5;; Author : Randy Taylor <dev@rjt.dev>
6;; Maintainer : Randy Taylor <dev@rjt.dev>
7;; Created : December 2022
8;; Keywords : go languages tree-sitter
9
10;; This file is part of GNU Emacs.
11
12;; GNU Emacs is free software: you can redistribute it and/or modify
13;; it under the terms of the GNU General Public License as published by
14;; the Free Software Foundation, either version 3 of the License, or
15;; (at your option) any later version.
16
17;; GNU Emacs is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
23;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
24
25;;; Commentary:
26;;
27
28;;; Code:
29
30(require 'treesit)
31(eval-when-compile (require 'rx))
32
33(declare-function treesit-parser-create "treesit.c")
34(declare-function treesit-induce-sparse-tree "treesit.c")
35(declare-function treesit-node-child "treesit.c")
36(declare-function treesit-node-child-by-field-name "treesit.c")
37(declare-function treesit-node-start "treesit.c")
38(declare-function treesit-node-type "treesit.c")
39
40(defcustom go-ts-mode-indent-offset 4
41 "Number of spaces for each indentation step in `go-ts-mode'."
42 :version "29.1"
43 :type 'integer
44 :safe 'integerp
45 :group 'go)
46
47(defvar go-ts-mode--syntax-table
48 (let ((table (make-syntax-table)))
49 (modify-syntax-entry ?+ "." table)
50 (modify-syntax-entry ?- "." table)
51 (modify-syntax-entry ?= "." table)
52 (modify-syntax-entry ?% "." table)
53 (modify-syntax-entry ?& "." table)
54 (modify-syntax-entry ?| "." table)
55 (modify-syntax-entry ?^ "." table)
56 (modify-syntax-entry ?! "." table)
57 (modify-syntax-entry ?< "." table)
58 (modify-syntax-entry ?> "." table)
59 (modify-syntax-entry ?\\ "\\" table)
60 (modify-syntax-entry ?/ ". 124b" table)
61 (modify-syntax-entry ?* ". 23" table)
62 (modify-syntax-entry ?\n "> b" table)
63 table)
64 "Syntax table for `go-ts-mode'.")
65
66(defvar go-ts-mode--indent-rules
67 `((go
68 ((node-is ")") parent-bol 0)
69 ((node-is "]") parent-bol 0)
70 ((node-is "}") parent-bol 0)
71 ((node-is "labeled_statement") no-indent)
72 ((parent-is "argument_list") parent-bol go-ts-mode-indent-offset)
73 ((parent-is "block") parent-bol go-ts-mode-indent-offset)
74 ((parent-is "const_declaration") parent-bol go-ts-mode-indent-offset)
75 ((parent-is "default_case") parent-bol go-ts-mode-indent-offset)
76 ((parent-is "expression_case") parent-bol go-ts-mode-indent-offset)
77 ((parent-is "expression_switch_statement") parent-bol 0)
78 ((parent-is "field_declaration_list") parent-bol go-ts-mode-indent-offset)
79 ((parent-is "import_spec_list") parent-bol go-ts-mode-indent-offset)
80 ((parent-is "labeled_statement") parent-bol go-ts-mode-indent-offset)
81 ((parent-is "literal_value") parent-bol go-ts-mode-indent-offset)
82 ((parent-is "type_spec") parent-bol go-ts-mode-indent-offset)
83 ((parent-is "var_declaration") parent-bol go-ts-mode-indent-offset)
84 (no-node parent-bol 0)))
85 "Tree-sitter indent rules for `go-ts-mode'.")
86
87(defvar go-ts-mode--keywords
88 '("break" "case" "chan" "const" "continue" "default" "defer" "else"
89 "fallthrough" "for" "func" "go" "goto" "if" "import" "interface" "map"
90 "package" "range" "return" "select" "struct" "switch" "type" "var")
91 "Go keywords for tree-sitter font-locking.")
92
93(defvar go-ts-mode--operators
94 '("+" "&" "+=" "&=" "&&" "==" "!=" "-" "|" "-=" "|=" "||" "<" "<="
95 "*" "^" "*=" "^=" "<-" ">" ">=" "/" "<<" "/=" "<<=" "++" "=" ":=" "%"
96 ">>" "%=" ">>=" "--" "!" "..." "&^" "&^=" "~")
97 "Go operators for tree-sitter font-locking.")
98
99(defvar go-ts-mode--font-lock-settings
100 (treesit-font-lock-rules
101 :language 'go
102 :feature 'bracket
103 '((["(" ")" "[" "]" "{" "}"]) @font-lock-bracket-face)
104
105 :language 'go
106 :feature 'comment
107 '((comment) @font-lock-comment-face)
108
109 :language 'go
110 :feature 'constant
111 '([(false) (iota) (nil) (true)] @font-lock-constant-face
112 (const_declaration
113 (const_spec name: (identifier) @font-lock-constant-face)))
114
115 :language 'go
116 :feature 'delimiter
117 '((["," "." ";" ":"]) @font-lock-delimiter-face)
118
119 :language 'go
120 :feature 'function
121 '((call_expression
122 function: (identifier) @font-lock-function-name-face)
123 (call_expression
124 function: (selector_expression
125 field: (field_identifier) @font-lock-function-name-face))
126 (function_declaration
127 name: (identifier) @font-lock-function-name-face)
128 (method_declaration
129 name: (field_identifier) @font-lock-function-name-face))
130
131 :language 'go
132 :feature 'keyword
133 `([,@go-ts-mode--keywords] @font-lock-keyword-face)
134
135 :language 'go
136 :feature 'label
137 '((label_name) @font-lock-constant-face)
138
139 :language 'go
140 :feature 'number
141 '([(float_literal)
142 (imaginary_literal)
143 (int_literal)] @font-lock-number-face)
144
145 :language 'go
146 :feature 'string
147 '([(interpreted_string_literal)
148 (raw_string_literal)
149 (rune_literal)] @font-lock-string-face)
150
151 :language 'go
152 :feature 'type
153 '([(package_identifier) (type_identifier)] @font-lock-type-face)
154
155 :language 'go
156 :feature 'variable
157 '((identifier) @font-lock-variable-name-face)
158
159 :language 'go
160 :feature 'escape-sequence
161 :override t
162 '((escape_sequence) @font-lock-escape-face)
163
164 :language 'go
165 :feature 'property
166 :override t
167 '((field_identifier) @font-lock-property-face
168 (keyed_element (_ (identifier) @font-lock-property-face)))
169
170 :language 'go
171 :feature 'error
172 :override t
173 '((ERROR) @font-lock-warning-face))
174 "Tree-sitter font-lock settings for `go-ts-mode'.")
175
176(defun go-ts-mode--imenu ()
177 "Return Imenu alist for the current buffer."
178 (let* ((node (treesit-buffer-root-node))
179 (func-tree (treesit-induce-sparse-tree
180 node "function_declaration" nil 1000))
181 (type-tree (treesit-induce-sparse-tree
182 node "type_spec" nil 1000))
183 (func-index (go-ts-mode--imenu-1 func-tree))
184 (type-index (go-ts-mode--imenu-1 type-tree)))
185 (append
186 (when func-index `(("Function" . ,func-index)))
187 (when type-index `(("Type" . ,type-index))))))
188
189(defun go-ts-mode--imenu-1 (node)
190 "Helper for `go-ts-mode--imenu'.
191Find string representation for NODE and set marker, then recurse
192the subtrees."
193 (let* ((ts-node (car node))
194 (children (cdr node))
195 (subtrees (mapcan #'go-ts-mode--imenu-1
196 children))
197 (name (when ts-node
198 (treesit-node-text
199 (pcase (treesit-node-type ts-node)
200 ("function_declaration"
201 (treesit-node-child-by-field-name ts-node "name"))
202 ("type_spec"
203 (treesit-node-child-by-field-name ts-node "name"))))))
204 (marker (when ts-node
205 (set-marker (make-marker)
206 (treesit-node-start ts-node)))))
207 (cond
208 ((or (null ts-node) (null name)) subtrees)
209 (subtrees
210 `((,name ,(cons name marker) ,@subtrees)))
211 (t
212 `((,name . ,marker))))))
213
214;;;###autoload
215(add-to-list 'auto-mode-alist '("\\.go\\'" . go-ts-mode))
216
217;;;###autoload
218(define-derived-mode go-ts-mode prog-mode "Go"
219 "Major mode for editing Go, powered by tree-sitter."
220 :group 'go
221 :syntax-table go-ts-mode--syntax-table
222
223 (when (treesit-ready-p 'go)
224 (treesit-parser-create 'go)
225
226 ;; Comments.
227 (setq-local comment-start "// ")
228 (setq-local comment-end "")
229 (setq-local comment-start-skip (rx "//" (* (syntax whitespace))))
230
231 ;; Imenu.
232 (setq-local imenu-create-index-function #'go-ts-mode--imenu)
233 (setq-local which-func-functions nil)
234
235 ;; Indent.
236 (setq-local indent-tabs-mode t
237 treesit-simple-indent-rules go-ts-mode--indent-rules)
238
239 ;; Font-lock.
240 (setq-local treesit-font-lock-settings go-ts-mode--font-lock-settings)
241 (setq-local treesit-font-lock-feature-list
242 '(( comment)
243 ( keyword string type)
244 ( constant escape-sequence function label number
245 property variable)
246 ( bracket delimiter error operator)))
247
248 (treesit-major-mode-setup)))
249
250;; go.mod support.
251
252(defvar go-mod-ts-mode--syntax-table
253 (let ((table (make-syntax-table)))
254 (modify-syntax-entry ?/ ". 124b" table)
255 (modify-syntax-entry ?\n "> b" table)
256 table)
257 "Syntax table for `go-mod-ts-mode'.")
258
259(defvar go-mod-ts-mode--indent-rules
260 `((gomod
261 ((node-is ")") parent-bol 0)
262 ((parent-is "exclude_directive") parent-bol go-ts-mode-indent-offset)
263 ((parent-is "module_directive") parent-bol go-ts-mode-indent-offset)
264 ((parent-is "replace_directive") parent-bol go-ts-mode-indent-offset)
265 ((parent-is "require_directive") parent-bol go-ts-mode-indent-offset)
266 ((parent-is "retract_directive") parent-bol go-ts-mode-indent-offset)
267 ((go-mod-ts-mode--in-directive-p) no-indent go-ts-mode-indent-offset)
268 (no-node no-indent 0)))
269 "Tree-sitter indent rules for `go-mod-ts-mode'.")
270
271(defun go-mod-ts-mode--in-directive-p ()
272 "Return non-nil if inside a directive.
273When entering an empty directive or adding a new entry to one, no node
274will be present meaning none of the indentation rules will match,
275because there is no parent to match against. This function determines
276what the parent of the node would be if it were a node."
277 (lambda (node _ _ &rest _)
278 (unless (treesit-node-type node)
279 (save-excursion
280 (backward-up-list)
281 (back-to-indentation)
282 (pcase (treesit-node-type (treesit-node-at (point)))
283 ("exclude" t)
284 ("module" t)
285 ("replace" t)
286 ("require" t)
287 ("retract" t))))))
288
289(defvar go-mod-ts-mode--keywords
290 '("exclude" "go" "module" "replace" "require" "retract")
291 "go.mod keywords for tree-sitter font-locking.")
292
293(defvar go-mod-ts-mode--font-lock-settings
294 (treesit-font-lock-rules
295 :language 'gomod
296 :feature 'bracket
297 '((["(" ")"]) @font-lock-bracket-face)
298
299 :language 'gomod
300 :feature 'comment
301 '((comment) @font-lock-comment-face)
302
303 :language 'gomod
304 :feature 'keyword
305 `([,@go-mod-ts-mode--keywords] @font-lock-keyword-face)
306
307 :language 'gomod
308 :feature 'number
309 '([(go_version) (version)] @font-lock-number-face)
310
311 :language 'gomod
312 :feature 'operator
313 '((["=>"]) @font-lock-operator-face)
314
315 :language 'gomod
316 :feature 'error
317 :override t
318 '((ERROR) @font-lock-warning-face))
319 "Tree-sitter font-lock settings for `go-mod-ts-mode'.")
320
321;;;###autoload
322(add-to-list 'auto-mode-alist '("/go\\.mod\\'" . go-mod-ts-mode))
323
324;;;###autoload
325(define-derived-mode go-mod-ts-mode prog-mode "Go Mod"
326 "Major mode for editing go.mod files, powered by tree-sitter."
327 :group 'go
328 :syntax-table go-mod-ts-mode--syntax-table
329
330 (when (treesit-ready-p 'gomod)
331 (treesit-parser-create 'gomod)
332
333 ;; Comments.
334 (setq-local comment-start "// ")
335 (setq-local comment-end "")
336 (setq-local comment-start-skip (rx "//" (* (syntax whitespace))))
337
338 ;; Indent.
339 (setq-local indent-tabs-mode t
340 treesit-simple-indent-rules go-mod-ts-mode--indent-rules)
341
342 ;; Font-lock.
343 (setq-local treesit-font-lock-settings go-mod-ts-mode--font-lock-settings)
344 (setq-local treesit-font-lock-feature-list
345 '((comment)
346 (keyword)
347 (number)
348 (bracket error operator)))
349
350 (treesit-major-mode-setup)))
351
352(provide 'go-ts-mode)
353
354;;; go-ts-mode.el ends here