Add go-work-ts-mode for Go workspace files

* lisp/progmodes/eglot.el (eglot-server-programs): Add go-work-ts-mode.
* lisp/progmodes/go-ts-mode.el
(Commentary): Add the repositories for the grammars.
(go-work-ts-mode--indent-rules, go-work-ts-mode--keywords)
(go-work-ts-mode--font-lock-settings): New variables.
(go-work-ts-mode--directive-matcher, go-work-ts-mode): New functions.
(go-mod-ts-mode--directive-matcher): Rename from
go-mod-ts-mode--in-directive-p.  Be more specific on the directive
location (modules).  Replace mention of nil with function.  Use member
instead of pcase to check node types.

* admin/notes/tree-sitter/build-module/batch.sh
* admin/notes/tree-sitter/build-module/build.sh: Add go-work support.

* test/lisp/progmodes/go-ts-mode-resources/font-lock-package.go:
* test/lisp/progmodes/go-ts-mode-resources/indent-mod.erts:
* test/lisp/progmodes/go-ts-mode-resources/indent-work.erts:
New files for testing indentation and font-locking for Go
module and workspace files.
* test/lisp/progmodes/go-ts-mode-tests.el: Add tests for Go module and
workspace files.  (Bug#74461)

* etc/NEWS: Announce go-work-ts-mode.
This commit is contained in:
Gabriel Santos 2024-11-20 23:07:28 -03:00 committed by Stefan Kangas
parent 7aa4291728
commit aade1b707c
9 changed files with 176 additions and 11 deletions

View file

@ -11,6 +11,7 @@ languages=(
'elixir'
'go'
'go-mod'
'go-work'
'heex'
'html'
'java'

View file

@ -39,6 +39,11 @@ case "${lang}" in
lang="gomod"
org="camdencheek"
;;
"go-work")
# The parser is called "gowork".
lang="gowork"
org="omertuc"
;;
"heex")
org="phoenixframework"
;;

View file

@ -1474,6 +1474,12 @@ means of the GDI+ library.
In addition to ':file FILE' for playing a sound from a file, ':data
DATA' can now be used to play a sound from memory.
---
** New major mode 'go-work-ts-mode'.
A major mode based on the tree-sitter library for editing "go.work"
files. If tree-sitter is properly set-up by the user, it can be
enabled for files named "go.work".
----------------------------------------------------------------------
This file is part of GNU Emacs.

View file

@ -279,7 +279,7 @@ automatically)."
(elm-mode . ("elm-language-server"))
(mint-mode . ("mint" "ls"))
((kotlin-mode kotlin-ts-mode) . ("kotlin-language-server"))
((go-mode go-dot-mod-mode go-dot-work-mode go-ts-mode go-mod-ts-mode)
((go-mode go-dot-mod-mode go-dot-work-mode go-ts-mode go-mod-ts-mode go-work-ts-mode)
. ("gopls"))
((R-mode ess-r-mode) . ("R" "--slave" "-e"
"languageserver::run()"))

View file

@ -26,6 +26,8 @@
;;
;; go-ts-mode is known to work with the following languages and version:
;; - tree-sitter-go: v0.23.4-1-g12fe553
;; - tree-sitter-go-mod: v1.1.0-3b01edce
;; - tree-sitter-go-work: 949a8a47
;;
;; We try our best to make builtin modes work with latest grammar
;; versions, so a more recent grammar version has a good chance to work.
@ -33,6 +35,9 @@
;;; Commentary:
;;
;; Go uses tabs as a convention for indentation:
;; https://go.dev/doc/effective_go#formatting
;; so `indent-tabs-mode' is enabled for the modes.
;;; Code:
@ -478,7 +483,7 @@ be run."
default-directory
(go-ts-mode--get-test-flags))))
;; go.mod support.
;;;; go.mod support.
(defvar go-mod-ts-mode--syntax-table
(let ((table (make-syntax-table)))
@ -495,12 +500,12 @@ be run."
((parent-is "replace_directive") parent-bol go-ts-mode-indent-offset)
((parent-is "require_directive") parent-bol go-ts-mode-indent-offset)
((parent-is "retract_directive") parent-bol go-ts-mode-indent-offset)
((go-mod-ts-mode--in-directive-p) no-indent go-ts-mode-indent-offset)
((go-mod-ts-mode--directive-matcher) no-indent go-ts-mode-indent-offset)
(no-node no-indent 0)))
"Tree-sitter indent rules for `go-mod-ts-mode'.")
(defun go-mod-ts-mode--in-directive-p ()
"Return non-nil if point is inside a directive.
(defun go-mod-ts-mode--directive-matcher ()
"Return a function for determining if point is inside a Go module directive.
When entering an empty directive or adding a new entry to one, no node
will be present meaning none of the indentation rules will match,
because there is no parent to match against. This function determines
@ -510,12 +515,12 @@ what the parent of the node would be if it were a node."
(save-excursion
(backward-up-list)
(back-to-indentation)
(pcase (treesit-node-type (treesit-node-at (point)))
("exclude" t)
("module" t)
("replace" t)
("require" t)
("retract" t))))))
(member (treesit-node-type (treesit-node-at (point)))
'("exclude"
"module"
"replace"
"require"
"retract"))))))
(defvar go-mod-ts-mode--keywords
'("exclude" "go" "module" "replace" "require" "retract")
@ -582,6 +587,94 @@ what the parent of the node would be if it were a node."
(if (treesit-ready-p 'gomod)
(add-to-list 'auto-mode-alist '("/go\\.mod\\'" . go-mod-ts-mode)))
;;;; go.work support.
(defvar go-work-ts-mode--indent-rules
`((gowork
((node-is ")") parent-bol 0)
((parent-is "replace_directive") parent-bol go-ts-mode-indent-offset)
((parent-is "use_directive") parent-bol go-ts-mode-indent-offset)
((go-work-ts-mode--directive-matcher) no-indent go-ts-mode-indent-offset)
(no-node no-indent 0)))
"Tree-sitter indent rules for `go-work-ts-mode'.")
(defun go-work-ts-mode--directive-matcher ()
"Return a function for determining if point is inside a Go workspace directive.
When entering an empty directive or adding a new entry to one, no node
will be present meaning none of the indentation rules will match,
because there is no parent to match against. This function determines
what the parent of the node would be if it were a node."
(lambda (node _ _ &rest _)
(unless (treesit-node-type node)
(save-excursion
(backward-up-list)
(back-to-indentation)
(member (treesit-node-type (treesit-node-at (point)))
'("replace"
"use"))))))
(defvar go-work-ts-mode--keywords
'("go" "replace" "use")
"go.work keywords for tree-sitter font-locking.")
(defvar go-work-ts-mode--font-lock-settings
(treesit-font-lock-rules
:language 'gowork
:feature 'bracket
'((["(" ")"]) @font-lock-bracket-face)
:language 'gowork
:feature 'comment
'((comment) @font-lock-comment-face)
:language 'gowork
:feature 'keyword
`([,@go-work-ts-mode--keywords] @font-lock-keyword-face)
:language 'gowork
:feature 'number
'([(go_version) (version)] @font-lock-number-face)
:language 'gowork
:feature 'operator
'((["=>"]) @font-lock-operator-face)
:language 'gowork
:feature 'error
:override t
'((ERROR) @font-lock-warning-face))
"Tree-sitter font-lock settings for `go-work-ts-mode'.")
;;;###autoload
(define-derived-mode go-work-ts-mode prog-mode "Go Work"
"Major mode for editing go.work files, powered by tree-sitter."
:group 'go
(when (treesit-ready-p 'gowork)
(setq treesit-primary-parser (treesit-parser-create 'gowork))
;; Comments.
(setq-local comment-start "// ")
(setq-local comment-end "")
(setq-local comment-start-skip (rx "//" (* (syntax whitespace))))
;; Indent.
(setq-local indent-tabs-mode t
treesit-simple-indent-rules go-work-ts-mode--indent-rules)
;; Font-lock.
(setq-local treesit-font-lock-settings go-work-ts-mode--font-lock-settings)
(setq-local treesit-font-lock-feature-list
'((comment)
(keyword)
(number)
(bracket error operator)))
(treesit-major-mode-setup)))
;;;###autoload
(add-to-list 'auto-mode-alist '("/go\\.work\\'" . go-work-ts-mode))
(provide 'go-ts-mode)
;;; go-ts-mode.el ends here

View file

@ -0,0 +1,4 @@
replace gnu.org/go/package1 v1.0.0 => gnu.org/go/package2 v1.0.0
// ^ font-lock-keyword-face
// ^ font-lock-number-face
// ^ font-lock-operator-face

View file

@ -0,0 +1,16 @@
Code:
(lambda ()
(go-mod-ts-mode)
(indent-region (point-min) (point-max)))
Point-Char: |
Name: Basic
=-=
require (
gnu.org/go/package1 v1.0.0
gnu.org/go/package2 v1.0.0
)
=-=-=

View file

@ -0,0 +1,16 @@
Code:
(lambda ()
(go-work-ts-mode)
(indent-region (point-min) (point-max)))
Point-Char: |
Name: Basic
=-=
use (
./package1
./package2
)
=-=-=

View file

@ -23,6 +23,8 @@
(require 'ert-x)
(require 'treesit)
;; go-ts-mode
(ert-deftest go-ts-mode-test-indentation ()
(skip-unless (treesit-ready-p 'go))
(ert-test-erts-file (ert-resource-file "indent.erts")))
@ -32,5 +34,27 @@
(let ((treesit-font-lock-level 4))
(ert-font-lock-test-file (ert-resource-file "font-lock.go") 'go-ts-mode)))
;; go-mod-ts-mode
(ert-deftest go-work-ts-mode-test-indentation ()
(skip-unless (treesit-ready-p 'gomod))
(ert-test-erts-file (ert-resource-file "indent-mod.erts")))
(ert-deftest go-mod-ts-test-font-lock ()
(skip-unless (treesit-ready-p 'gomod))
(let ((treesit-font-lock-level 4))
(ert-font-lock-test-file (ert-resource-file "font-lock-package.go") 'go-mod-ts-mode)))
;; go-work-ts-mode
(ert-deftest go-work-ts-mode-test-indentation ()
(skip-unless (treesit-ready-p 'gowork))
(ert-test-erts-file (ert-resource-file "indent-work.erts")))
(ert-deftest go-work-ts-test-font-lock ()
(skip-unless (treesit-ready-p 'gowork))
(let ((treesit-font-lock-level 4))
(ert-font-lock-test-file (ert-resource-file "font-lock-package.go") 'go-work-ts-mode)))
(provide 'go-ts-mode-tests)
;;; go-ts-mode-tests.el ends here