unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode
@ 2022-12-13  2:13 Randy Taylor
  2022-12-13 12:25 ` Eli Zaretskii
                   ` (2 more replies)
  0 siblings, 3 replies; 20+ messages in thread
From: Randy Taylor @ 2022-12-13  2:13 UTC (permalink / raw)
  To: 60025; +Cc: casouri, theo


[-- Attachment #1.1: Type: text/plain, Size: 732 bytes --]

X-Debbugs-CC: casouri@gmail.com, theo@thornhill.no

I am having a tiny bit of trouble with a go.mod indentation rule. Using the patch, create a go.mod file anywhere, activate go-mod-ts-mode and add the following:

require ()

Place point inside the parens, and then hit enter. The expectation is that point will end up indented inside that block. If you add the text "test v1.0.0" and hit TAB, it will indent properly (and if you hit enter after that text it will indent properly for the next entry). If you go to the end of the line for the top paren and hit enter, it will not indent (and we want it to). It seems to give us no-node in that circumstance. Is there a simple indent rule that can match exactly that that I'm missing?

[-- Attachment #1.2: Type: text/html, Size: 1732 bytes --]

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Add-go-ts-mode-and-go-mod-ts-mode.patch --]
[-- Type: text/x-patch; name=0001-Add-go-ts-mode-and-go-mod-ts-mode.patch, Size: 14678 bytes --]

From 06e483ec4425f9d30ecb139ea8c99846d0bc9b0b Mon Sep 17 00:00:00 2001
From: Randy Taylor <dev@rjt.dev>
Date: Sun, 11 Dec 2022 18:41:16 -0500
Subject: [PATCH] Add go-ts-mode and go-mod-ts-mode

* 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.
---
 admin/notes/tree-sitter/build-module/batch.sh |   1 +
 admin/notes/tree-sitter/build-module/build.sh |   5 +
 etc/NEWS                                      |   5 +
 lisp/progmodes/eglot.el                       |   3 +-
 lisp/progmodes/go-ts-mode.el                  | 331 ++++++++++++++++++
 5 files changed, 344 insertions(+), 1 deletion(-)
 create mode 100644 lisp/progmodes/go-ts-mode.el

diff --git a/admin/notes/tree-sitter/build-module/batch.sh b/admin/notes/tree-sitter/build-module/batch.sh
index c395449977..c322d405f2 100755
--- a/admin/notes/tree-sitter/build-module/batch.sh
+++ b/admin/notes/tree-sitter/build-module/batch.sh
@@ -8,6 +8,7 @@ languages=
     'c-sharp'
     'dockerfile'
     'go'
+    'go-mod'
     'html'
     'javascript'
     'json'
diff --git a/admin/notes/tree-sitter/build-module/build.sh b/admin/notes/tree-sitter/build-module/build.sh
index d020ee92c3..27de17c33e 100755
--- a/admin/notes/tree-sitter/build-module/build.sh
+++ b/admin/notes/tree-sitter/build-module/build.sh
@@ -26,6 +26,11 @@ grammardir=
     "cmake")
         namespace="uyha"
         ;;
+    "go-mod")
+        # The parser is called "gomod".
+        lang="gomod"
+        namespace="camdencheek"
+        ;;
     "typescript")
         sourcedir="tree-sitter-typescript/typescript/src"
         grammardir="tree-sitter-typescript/typescript"
diff --git a/etc/NEWS b/etc/NEWS
index 6cb531cd0f..e85e1fe244 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -3078,6 +3078,11 @@ A major mode based on the tree-sitter library for editing CMake files.
 It includes support for font-locking, indentation, Imenu, and
 which-func.
 
+** New major modes 'go-ts-mode' and 'go-mod-ts-mode'.
+Major modes based on the tree-sitter library for editing programs in
+the Go language.  It includes support for font-locking, indentation,
+Imenu, and which-func.
+
 \f
 * Incompatible Lisp Changes in Emacs 29.1
 
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 9c5a361df7..02bb6bb665 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -211,7 +211,8 @@ eglot-server-programs
                                 (elm-mode . ("elm-language-server"))
                                 (mint-mode . ("mint" "ls"))
                                 (kotlin-mode . ("kotlin-language-server"))
-                                ((go-mode go-dot-mod-mode go-dot-work-mode) . ("gopls"))
+                                ((go-mode go-dot-mod-mode go-dot-work-mode go-ts-mode go-mod-ts-mode)
+                                 . ("gopls"))
                                 ((R-mode ess-r-mode) . ("R" "--slave" "-e"
                                                         "languageserver::run()"))
                                 ((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 0000000000..3ff61d3ffc
--- /dev/null
+++ b/lisp/progmodes/go-ts-mode.el
@@ -0,0 +1,331 @@
+;;; go-ts-mode.el --- tree-sitter support for Go  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2022 Free Software Foundation, Inc.
+
+;; Author     : Randy Taylor <dev@rjt.dev>
+;; Maintainer : Randy Taylor <dev@rjt.dev>
+;; Created    : December 2022
+;; Keywords   : go languages tree-sitter
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+
+;;; Code:
+
+(require 'treesit)
+(eval-when-compile (require 'rx))
+
+(declare-function treesit-parser-create "treesit.c")
+(declare-function treesit-induce-sparse-tree "treesit.c")
+(declare-function treesit-node-child "treesit.c")
+(declare-function treesit-node-child-by-field-name "treesit.c")
+(declare-function treesit-node-start "treesit.c")
+(declare-function treesit-node-type "treesit.c")
+
+(defcustom go-ts-mode-indent-offset 4
+  "Number of spaces for each indentation step in `go-ts-mode'."
+  :version "29.1"
+  :type 'integer
+  :safe 'integerp
+  :group 'go)
+
+(defvar go-ts-mode--syntax-table
+  (let ((table (make-syntax-table)))
+    (modify-syntax-entry ?+   "."      table)
+    (modify-syntax-entry ?-   "."      table)
+    (modify-syntax-entry ?=   "."      table)
+    (modify-syntax-entry ?%   "."      table)
+    (modify-syntax-entry ?&   "."      table)
+    (modify-syntax-entry ?|   "."      table)
+    (modify-syntax-entry ?^   "."      table)
+    (modify-syntax-entry ?!   "."      table)
+    (modify-syntax-entry ?<   "."      table)
+    (modify-syntax-entry ?>   "."      table)
+    (modify-syntax-entry ?\\  "\\"     table)
+    (modify-syntax-entry ?/   ". 124b" table)
+    (modify-syntax-entry ?*   ". 23"   table)
+    (modify-syntax-entry ?\n  "> b"    table)
+    table)
+  "Syntax table for `go-ts-mode'.")
+
+(defvar go-ts-mode--indent-rules
+  `((go
+     ((node-is ")") parent-bol 0)
+     ((node-is "]") parent-bol 0)
+     ((node-is "}") parent-bol 0)
+     ((node-is "labeled_statement") no-indent)
+     ((parent-is "argument_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "block") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "const_declaration") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "default_case") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "expression_case") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "expression_switch_statement") parent-bol 0)
+     ((parent-is "field_declaration_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "import_spec_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "labeled_statement") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "literal_value") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "type_spec") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "var_declaration") parent-bol go-ts-mode-indent-offset)
+     (no-node parent-bol 0)))
+  "Tree-sitter indent rules for `go-ts-mode'.")
+
+(defvar go-ts-mode--keywords
+  '("break" "case" "chan" "const" "continue" "default" "defer" "else"
+    "fallthrough" "for" "func" "go" "goto" "if" "import" "interface" "map"
+    "package" "range" "return" "select" "struct" "switch" "type" "var")
+  "Go keywords for tree-sitter font-locking.")
+
+(defvar go-ts-mode--operators
+  '("+" "&" "+=" "&=" "&&" "==" "!=" "-" "|" "-=" "|=" "||" "<" "<="
+    "*" "^" "*=" "^=" "<-" ">" ">=" "/" "<<" "/=" "<<=" "++" "=" ":=" "%"
+    ">>" "%=" ">>=" "--" "!"  "..."  "&^" "&^=" "~")
+  "Go operators for tree-sitter font-locking.")
+
+(defvar go-ts-mode--font-lock-settings
+  (treesit-font-lock-rules
+   :language 'go
+   :feature 'bracket
+   '((["(" ")" "[" "]" "{" "}"]) @font-lock-bracket-face)
+
+   :language 'go
+   :feature 'comment
+   '((comment) @font-lock-comment-face)
+
+   :language 'go
+   :feature 'constant
+   '([(false) (iota) (nil) (true)] @font-lock-constant-face
+     (const_declaration
+      (const_spec name: (identifier) @font-lock-constant-face)))
+
+   :language 'go
+   :feature 'delimiter
+   '((["," "." ";" ":"]) @font-lock-delimiter-face)
+
+   :language 'go
+   :feature 'function
+   '((call_expression
+      function: (identifier) @font-lock-function-name-face)
+     (call_expression
+      function: (selector_expression
+                 field: (field_identifier) @font-lock-function-name-face))
+     (function_declaration
+      name: (identifier) @font-lock-function-name-face)
+     (method_declaration
+      name: (field_identifier) @font-lock-function-name-face))
+
+   :language 'go
+   :feature 'keyword
+   `([,@go-ts-mode--keywords] @font-lock-keyword-face)
+
+   :language 'go
+   :feature 'label
+   '((label_name) @font-lock-constant-face)
+
+   :language 'go
+   :feature 'number
+   '([(float_literal)
+      (imaginary_literal)
+      (int_literal)] @font-lock-number-face)
+
+   :language 'go
+   :feature 'string
+   '([(interpreted_string_literal)
+      (raw_string_literal)
+      (rune_literal)] @font-lock-string-face)
+
+   :language 'go
+   :feature 'type
+   '([(package_identifier) (type_identifier)] @font-lock-type-face)
+
+   :language 'go
+   :feature 'variable
+   '((identifier) @font-lock-variable-name-face)
+
+   :language 'go
+   :feature 'escape-sequence
+   :override t
+   '((escape_sequence) @font-lock-escape-face)
+
+   :language 'go
+   :feature 'property
+   :override t
+   '((field_identifier) @font-lock-property-face
+     (keyed_element (_ (identifier) @font-lock-property-face)))
+
+   :language 'go
+   :feature 'error
+   :override t
+   '((ERROR) @font-lock-warning-face))
+  "Tree-sitter font-lock settings for `go-ts-mode'.")
+
+(defun go-ts-mode--imenu ()
+  "Return Imenu alist for the current buffer."
+  (let* ((node (treesit-buffer-root-node))
+         (func-tree (treesit-induce-sparse-tree
+                     node "function_declaration" nil 1000))
+         (type-tree (treesit-induce-sparse-tree
+                     node "type_spec" nil 1000))
+         (func-index (go-ts-mode--imenu-1 func-tree))
+         (type-index (go-ts-mode--imenu-1 type-tree)))
+    (append
+     (when func-index `(("Function" . ,func-index)))
+     (when type-index `(("Type" . ,type-index))))))
+
+(defun go-ts-mode--imenu-1 (node)
+  "Helper for `go-ts-mode--imenu'.
+Find string representation for NODE and set marker, then recurse
+the subtrees."
+  (let* ((ts-node (car node))
+         (children (cdr node))
+         (subtrees (mapcan #'go-ts-mode--imenu-1
+                           children))
+         (name (when ts-node
+                 (treesit-node-text
+                  (pcase (treesit-node-type ts-node)
+                    ("function_declaration"
+                     (treesit-node-child-by-field-name ts-node "name"))
+                    ("type_spec"
+                     (treesit-node-child-by-field-name ts-node "name"))))))
+         (marker (when ts-node
+                   (set-marker (make-marker)
+                               (treesit-node-start ts-node)))))
+    (cond
+     ((or (null ts-node) (null name)) subtrees)
+     (subtrees
+      `((,name ,(cons name marker) ,@subtrees)))
+     (t
+      `((,name . ,marker))))))
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.go\\'" . go-ts-mode))
+
+;;;###autoload
+(define-derived-mode go-ts-mode prog-mode "Go"
+  "Major mode for editing Go, powered by tree-sitter."
+  :group 'go
+  :syntax-table go-ts-mode--syntax-table
+
+  (when (treesit-ready-p 'go)
+    (treesit-parser-create 'go)
+
+    ;; Comments.
+    (setq-local comment-start "// ")
+    (setq-local comment-end "")
+    (setq-local comment-start-skip (rx "//" (* (syntax whitespace))))
+
+    ;; Imenu.
+    (setq-local imenu-create-index-function #'go-ts-mode--imenu)
+    (setq-local which-func-functions nil)
+
+    ;; Indent.
+    (setq-local indent-tabs-mode t
+                treesit-simple-indent-rules go-ts-mode--indent-rules)
+
+    ;; Font-lock.
+    (setq-local treesit-font-lock-settings go-ts-mode--font-lock-settings)
+    (setq-local treesit-font-lock-feature-list
+                '(( comment)
+                  ( keyword string type)
+                  ( constant escape-sequence function label number
+                    property variable)
+                  ( bracket delimiter error operator)))
+
+    (treesit-major-mode-setup)))
+
+;; go.mod support.
+
+(defvar go-mod-ts-mode--syntax-table
+  (let ((table (make-syntax-table)))
+    (modify-syntax-entry ?/   ". 124b" table)
+    (modify-syntax-entry ?\n  "> b"    table)
+    table)
+  "Syntax table for `go-mod-ts-mode'.")
+
+(defvar go-mod-ts-mode--indent-rules
+  `((gomod
+     ((node-is ")") parent-bol 0)
+     ((parent-is "require_directive") parent-bol go-ts-mode-indent-offset)
+     (no-node parent-bol 0)))
+  "Tree-sitter indent rules for `go-mod-ts-mode'.")
+
+(defvar go-mod-ts-mode--keywords
+  '("exclude" "go" "module" "replace" "require" "retract")
+  "go.mod keywords for tree-sitter font-locking.")
+
+(defvar go-mod-ts-mode--font-lock-settings
+  (treesit-font-lock-rules
+   :language 'gomod
+   :feature 'bracket
+   '((["(" ")"]) @font-lock-bracket-face)
+
+   :language 'gomod
+   :feature 'comment
+   '((comment) @font-lock-comment-face)
+
+   :language 'gomod
+   :feature 'keyword
+   `([,@go-mod-ts-mode--keywords] @font-lock-keyword-face)
+
+   :language 'gomod
+   :feature 'number
+   '([(go_version) (version)] @font-lock-number-face)
+
+   :language 'gomod
+   :feature 'operator
+   '((["=>"]) @font-lock-operator-face)
+
+   :language 'gomod
+   :feature 'error
+   :override t
+   '((ERROR) @font-lock-warning-face))
+  "Tree-sitter font-lock settings for `go-mod-ts-mode'.")
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("go\\.mod\\'" . go-mod-ts-mode))
+
+;;;###autoload
+(define-derived-mode go-mod-ts-mode prog-mode "Go Mod"
+  "Major mode for editing go.mod files, powered by tree-sitter."
+  :group 'go
+  :syntax-table go-mod-ts-mode--syntax-table
+
+  (when (treesit-ready-p 'gomod)
+    (treesit-parser-create 'gomod)
+
+    ;; 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-mod-ts-mode--indent-rules)
+
+    ;; Font-lock.
+    (setq-local treesit-font-lock-settings go-mod-ts-mode--font-lock-settings)
+    (setq-local treesit-font-lock-feature-list
+                '((comment)
+                  (keyword)
+                  (number)
+                  (bracket error operator)))
+
+    (treesit-major-mode-setup)))
+
+(provide 'go-ts-mode)
+
+;;; go-ts-mode.el ends here
-- 
2.39.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode
  2022-12-13  2:13 bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode Randy Taylor
@ 2022-12-13 12:25 ` Eli Zaretskii
  2022-12-13 19:39   ` Randy Taylor
  2022-12-14  2:22 ` Yuan Fu
  2022-12-16  1:22 ` Yuan Fu
  2 siblings, 1 reply; 20+ messages in thread
From: Eli Zaretskii @ 2022-12-13 12:25 UTC (permalink / raw)
  To: Randy Taylor; +Cc: 60025, theo, casouri

> Cc: casouri@gmail.com, theo@thornhill.no
> Date: Tue, 13 Dec 2022 02:13:47 +0000
> From: Randy Taylor <dev@rjt.dev>
> 
> --- a/etc/NEWS
> +++ b/etc/NEWS
> @@ -3078,6 +3078,11 @@ A major mode based on the tree-sitter library for editing CMake files.
>  It includes support for font-locking, indentation, Imenu, and
>  which-func.
>  
> +** New major modes 'go-ts-mode' and 'go-mod-ts-mode'.
> +Major modes based on the tree-sitter library for editing programs in
> +the Go language.  It includes support for font-locking, indentation,
> +Imenu, and which-func.

This should say something about go-mod-ts-mode, at least which files
it's supposed to support and for what purpose.

> +(add-to-list 'auto-mode-alist '("go\\.mod\\'" . go-mod-ts-mode))

Aren't the files's base name always exactly "go.mod" and nothing else?
If so, the regexp should be augmented, because we don't want to use
this moder for, say, foogo.mod or fubar-go.mod, right?

Thanks.





^ permalink raw reply	[flat|nested] 20+ messages in thread

* bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode
  2022-12-13 12:25 ` Eli Zaretskii
@ 2022-12-13 19:39   ` Randy Taylor
  2022-12-14 12:02     ` Eli Zaretskii
  0 siblings, 1 reply; 20+ messages in thread
From: Randy Taylor @ 2022-12-13 19:39 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 60025, theo, casouri

[-- Attachment #1: Type: text/plain, Size: 1229 bytes --]

On Tuesday, December 13th, 2022 at 07:25, Eli Zaretskii <eliz@gnu.org> wrote: 
> 
> > Cc: casouri@gmail.com, theo@thornhill.no
> 
> > Date: Tue, 13 Dec 2022 02:13:47 +0000
> > From: Randy Taylor dev@rjt.dev
> > 
> > --- a/etc/NEWS
> > +++ b/etc/NEWS
> > @@ -3078,6 +3078,11 @@ A major mode based on the tree-sitter library for editing CMake files.
> > It includes support for font-locking, indentation, Imenu, and
> > which-func.
> > 
> > +** New major modes 'go-ts-mode' and 'go-mod-ts-mode'.
> > +Major modes based on the tree-sitter library for editing programs in
> > +the Go language. It includes support for font-locking, indentation,
> > +Imenu, and which-func.
> 
> 
> This should say something about go-mod-ts-mode, at least which files
> it's supposed to support and for what purpose.

Indeed, thanks. I decided to split them up, and I also added that they will be auto-enabled.

> > +(add-to-list 'auto-mode-alist '("go\\.mod\\'" . go-mod-ts-mode))
> 
> 
> Aren't the files's base name always exactly "go.mod" and nothing else?
> If so, the regexp should be augmented, because we don't want to use
> this moder for, say, foogo.mod or fubar-go.mod, right?

Oops, that's right. Good catch.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Add-go-ts-mode-and-go-mod-ts-mode-Bug-60025.patch --]
[-- Type: text/x-patch; name=0001-Add-go-ts-mode-and-go-mod-ts-mode-Bug-60025.patch, Size: 14958 bytes --]

From 9348436f0f851a9fdc3b2dca57c22cc88d55d8f9 Mon Sep 17 00:00:00 2001
From: Randy Taylor <dev@rjt.dev>
Date: Sun, 11 Dec 2022 18:41:16 -0500
Subject: [PATCH] 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.
---
 admin/notes/tree-sitter/build-module/batch.sh |   1 +
 admin/notes/tree-sitter/build-module/build.sh |   5 +
 etc/NEWS                                      |  11 +
 lisp/progmodes/eglot.el                       |   3 +-
 lisp/progmodes/go-ts-mode.el                  | 331 ++++++++++++++++++
 5 files changed, 350 insertions(+), 1 deletion(-)
 create mode 100644 lisp/progmodes/go-ts-mode.el

diff --git a/admin/notes/tree-sitter/build-module/batch.sh b/admin/notes/tree-sitter/build-module/batch.sh
index c395449977..c322d405f2 100755
--- a/admin/notes/tree-sitter/build-module/batch.sh
+++ b/admin/notes/tree-sitter/build-module/batch.sh
@@ -8,6 +8,7 @@ languages=
     'c-sharp'
     'dockerfile'
     'go'
+    'go-mod'
     'html'
     'javascript'
     'json'
diff --git a/admin/notes/tree-sitter/build-module/build.sh b/admin/notes/tree-sitter/build-module/build.sh
index d020ee92c3..27de17c33e 100755
--- a/admin/notes/tree-sitter/build-module/build.sh
+++ b/admin/notes/tree-sitter/build-module/build.sh
@@ -26,6 +26,11 @@ grammardir=
     "cmake")
         namespace="uyha"
         ;;
+    "go-mod")
+        # The parser is called "gomod".
+        lang="gomod"
+        namespace="camdencheek"
+        ;;
     "typescript")
         sourcedir="tree-sitter-typescript/typescript/src"
         grammardir="tree-sitter-typescript/typescript"
diff --git a/etc/NEWS b/etc/NEWS
index 6cb531cd0f..c14e337348 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -3078,6 +3078,17 @@ A major mode based on the tree-sitter library for editing CMake files.
 It includes support for font-locking, indentation, Imenu, and
 which-func.
 
+** New major mode 'go-ts-mode'.
+A major mode based on the tree-sitter library for editing programs in
+the Go language.  It includes support for font-locking, indentation,
+Imenu, and which-func.  This mode will be auto-enabled for files with
+the ".go" extension.
+
+** New major mode 'go-mod-ts-mode'.
+A major mode based on the tree-sitter library for editing go.mod
+files.  It includes support for font-locking and indentation.  This
+mode will be auto-enabled for go.mod files.
+
 \f
 * Incompatible Lisp Changes in Emacs 29.1
 
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 9c5a361df7..02bb6bb665 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -211,7 +211,8 @@ eglot-server-programs
                                 (elm-mode . ("elm-language-server"))
                                 (mint-mode . ("mint" "ls"))
                                 (kotlin-mode . ("kotlin-language-server"))
-                                ((go-mode go-dot-mod-mode go-dot-work-mode) . ("gopls"))
+                                ((go-mode go-dot-mod-mode go-dot-work-mode go-ts-mode go-mod-ts-mode)
+                                 . ("gopls"))
                                 ((R-mode ess-r-mode) . ("R" "--slave" "-e"
                                                         "languageserver::run()"))
                                 ((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 0000000000..b86ab7ab85
--- /dev/null
+++ b/lisp/progmodes/go-ts-mode.el
@@ -0,0 +1,331 @@
+;;; go-ts-mode.el --- tree-sitter support for Go  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2022 Free Software Foundation, Inc.
+
+;; Author     : Randy Taylor <dev@rjt.dev>
+;; Maintainer : Randy Taylor <dev@rjt.dev>
+;; Created    : December 2022
+;; Keywords   : go languages tree-sitter
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+
+;;; Code:
+
+(require 'treesit)
+(eval-when-compile (require 'rx))
+
+(declare-function treesit-parser-create "treesit.c")
+(declare-function treesit-induce-sparse-tree "treesit.c")
+(declare-function treesit-node-child "treesit.c")
+(declare-function treesit-node-child-by-field-name "treesit.c")
+(declare-function treesit-node-start "treesit.c")
+(declare-function treesit-node-type "treesit.c")
+
+(defcustom go-ts-mode-indent-offset 4
+  "Number of spaces for each indentation step in `go-ts-mode'."
+  :version "29.1"
+  :type 'integer
+  :safe 'integerp
+  :group 'go)
+
+(defvar go-ts-mode--syntax-table
+  (let ((table (make-syntax-table)))
+    (modify-syntax-entry ?+   "."      table)
+    (modify-syntax-entry ?-   "."      table)
+    (modify-syntax-entry ?=   "."      table)
+    (modify-syntax-entry ?%   "."      table)
+    (modify-syntax-entry ?&   "."      table)
+    (modify-syntax-entry ?|   "."      table)
+    (modify-syntax-entry ?^   "."      table)
+    (modify-syntax-entry ?!   "."      table)
+    (modify-syntax-entry ?<   "."      table)
+    (modify-syntax-entry ?>   "."      table)
+    (modify-syntax-entry ?\\  "\\"     table)
+    (modify-syntax-entry ?/   ". 124b" table)
+    (modify-syntax-entry ?*   ". 23"   table)
+    (modify-syntax-entry ?\n  "> b"    table)
+    table)
+  "Syntax table for `go-ts-mode'.")
+
+(defvar go-ts-mode--indent-rules
+  `((go
+     ((node-is ")") parent-bol 0)
+     ((node-is "]") parent-bol 0)
+     ((node-is "}") parent-bol 0)
+     ((node-is "labeled_statement") no-indent)
+     ((parent-is "argument_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "block") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "const_declaration") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "default_case") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "expression_case") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "expression_switch_statement") parent-bol 0)
+     ((parent-is "field_declaration_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "import_spec_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "labeled_statement") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "literal_value") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "type_spec") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "var_declaration") parent-bol go-ts-mode-indent-offset)
+     (no-node parent-bol 0)))
+  "Tree-sitter indent rules for `go-ts-mode'.")
+
+(defvar go-ts-mode--keywords
+  '("break" "case" "chan" "const" "continue" "default" "defer" "else"
+    "fallthrough" "for" "func" "go" "goto" "if" "import" "interface" "map"
+    "package" "range" "return" "select" "struct" "switch" "type" "var")
+  "Go keywords for tree-sitter font-locking.")
+
+(defvar go-ts-mode--operators
+  '("+" "&" "+=" "&=" "&&" "==" "!=" "-" "|" "-=" "|=" "||" "<" "<="
+    "*" "^" "*=" "^=" "<-" ">" ">=" "/" "<<" "/=" "<<=" "++" "=" ":=" "%"
+    ">>" "%=" ">>=" "--" "!"  "..."  "&^" "&^=" "~")
+  "Go operators for tree-sitter font-locking.")
+
+(defvar go-ts-mode--font-lock-settings
+  (treesit-font-lock-rules
+   :language 'go
+   :feature 'bracket
+   '((["(" ")" "[" "]" "{" "}"]) @font-lock-bracket-face)
+
+   :language 'go
+   :feature 'comment
+   '((comment) @font-lock-comment-face)
+
+   :language 'go
+   :feature 'constant
+   '([(false) (iota) (nil) (true)] @font-lock-constant-face
+     (const_declaration
+      (const_spec name: (identifier) @font-lock-constant-face)))
+
+   :language 'go
+   :feature 'delimiter
+   '((["," "." ";" ":"]) @font-lock-delimiter-face)
+
+   :language 'go
+   :feature 'function
+   '((call_expression
+      function: (identifier) @font-lock-function-name-face)
+     (call_expression
+      function: (selector_expression
+                 field: (field_identifier) @font-lock-function-name-face))
+     (function_declaration
+      name: (identifier) @font-lock-function-name-face)
+     (method_declaration
+      name: (field_identifier) @font-lock-function-name-face))
+
+   :language 'go
+   :feature 'keyword
+   `([,@go-ts-mode--keywords] @font-lock-keyword-face)
+
+   :language 'go
+   :feature 'label
+   '((label_name) @font-lock-constant-face)
+
+   :language 'go
+   :feature 'number
+   '([(float_literal)
+      (imaginary_literal)
+      (int_literal)] @font-lock-number-face)
+
+   :language 'go
+   :feature 'string
+   '([(interpreted_string_literal)
+      (raw_string_literal)
+      (rune_literal)] @font-lock-string-face)
+
+   :language 'go
+   :feature 'type
+   '([(package_identifier) (type_identifier)] @font-lock-type-face)
+
+   :language 'go
+   :feature 'variable
+   '((identifier) @font-lock-variable-name-face)
+
+   :language 'go
+   :feature 'escape-sequence
+   :override t
+   '((escape_sequence) @font-lock-escape-face)
+
+   :language 'go
+   :feature 'property
+   :override t
+   '((field_identifier) @font-lock-property-face
+     (keyed_element (_ (identifier) @font-lock-property-face)))
+
+   :language 'go
+   :feature 'error
+   :override t
+   '((ERROR) @font-lock-warning-face))
+  "Tree-sitter font-lock settings for `go-ts-mode'.")
+
+(defun go-ts-mode--imenu ()
+  "Return Imenu alist for the current buffer."
+  (let* ((node (treesit-buffer-root-node))
+         (func-tree (treesit-induce-sparse-tree
+                     node "function_declaration" nil 1000))
+         (type-tree (treesit-induce-sparse-tree
+                     node "type_spec" nil 1000))
+         (func-index (go-ts-mode--imenu-1 func-tree))
+         (type-index (go-ts-mode--imenu-1 type-tree)))
+    (append
+     (when func-index `(("Function" . ,func-index)))
+     (when type-index `(("Type" . ,type-index))))))
+
+(defun go-ts-mode--imenu-1 (node)
+  "Helper for `go-ts-mode--imenu'.
+Find string representation for NODE and set marker, then recurse
+the subtrees."
+  (let* ((ts-node (car node))
+         (children (cdr node))
+         (subtrees (mapcan #'go-ts-mode--imenu-1
+                           children))
+         (name (when ts-node
+                 (treesit-node-text
+                  (pcase (treesit-node-type ts-node)
+                    ("function_declaration"
+                     (treesit-node-child-by-field-name ts-node "name"))
+                    ("type_spec"
+                     (treesit-node-child-by-field-name ts-node "name"))))))
+         (marker (when ts-node
+                   (set-marker (make-marker)
+                               (treesit-node-start ts-node)))))
+    (cond
+     ((or (null ts-node) (null name)) subtrees)
+     (subtrees
+      `((,name ,(cons name marker) ,@subtrees)))
+     (t
+      `((,name . ,marker))))))
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.go\\'" . go-ts-mode))
+
+;;;###autoload
+(define-derived-mode go-ts-mode prog-mode "Go"
+  "Major mode for editing Go, powered by tree-sitter."
+  :group 'go
+  :syntax-table go-ts-mode--syntax-table
+
+  (when (treesit-ready-p 'go)
+    (treesit-parser-create 'go)
+
+    ;; Comments.
+    (setq-local comment-start "// ")
+    (setq-local comment-end "")
+    (setq-local comment-start-skip (rx "//" (* (syntax whitespace))))
+
+    ;; Imenu.
+    (setq-local imenu-create-index-function #'go-ts-mode--imenu)
+    (setq-local which-func-functions nil)
+
+    ;; Indent.
+    (setq-local indent-tabs-mode t
+                treesit-simple-indent-rules go-ts-mode--indent-rules)
+
+    ;; Font-lock.
+    (setq-local treesit-font-lock-settings go-ts-mode--font-lock-settings)
+    (setq-local treesit-font-lock-feature-list
+                '(( comment)
+                  ( keyword string type)
+                  ( constant escape-sequence function label number
+                    property variable)
+                  ( bracket delimiter error operator)))
+
+    (treesit-major-mode-setup)))
+
+;; go.mod support.
+
+(defvar go-mod-ts-mode--syntax-table
+  (let ((table (make-syntax-table)))
+    (modify-syntax-entry ?/   ". 124b" table)
+    (modify-syntax-entry ?\n  "> b"    table)
+    table)
+  "Syntax table for `go-mod-ts-mode'.")
+
+(defvar go-mod-ts-mode--indent-rules
+  `((gomod
+     ((node-is ")") parent-bol 0)
+     ((parent-is "require_directive") parent-bol go-ts-mode-indent-offset)
+     (no-node parent-bol 0)))
+  "Tree-sitter indent rules for `go-mod-ts-mode'.")
+
+(defvar go-mod-ts-mode--keywords
+  '("exclude" "go" "module" "replace" "require" "retract")
+  "go.mod keywords for tree-sitter font-locking.")
+
+(defvar go-mod-ts-mode--font-lock-settings
+  (treesit-font-lock-rules
+   :language 'gomod
+   :feature 'bracket
+   '((["(" ")"]) @font-lock-bracket-face)
+
+   :language 'gomod
+   :feature 'comment
+   '((comment) @font-lock-comment-face)
+
+   :language 'gomod
+   :feature 'keyword
+   `([,@go-mod-ts-mode--keywords] @font-lock-keyword-face)
+
+   :language 'gomod
+   :feature 'number
+   '([(go_version) (version)] @font-lock-number-face)
+
+   :language 'gomod
+   :feature 'operator
+   '((["=>"]) @font-lock-operator-face)
+
+   :language 'gomod
+   :feature 'error
+   :override t
+   '((ERROR) @font-lock-warning-face))
+  "Tree-sitter font-lock settings for `go-mod-ts-mode'.")
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("/go\\.mod\\'" . go-mod-ts-mode))
+
+;;;###autoload
+(define-derived-mode go-mod-ts-mode prog-mode "Go Mod"
+  "Major mode for editing go.mod files, powered by tree-sitter."
+  :group 'go
+  :syntax-table go-mod-ts-mode--syntax-table
+
+  (when (treesit-ready-p 'gomod)
+    (treesit-parser-create 'gomod)
+
+    ;; 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-mod-ts-mode--indent-rules)
+
+    ;; Font-lock.
+    (setq-local treesit-font-lock-settings go-mod-ts-mode--font-lock-settings)
+    (setq-local treesit-font-lock-feature-list
+                '((comment)
+                  (keyword)
+                  (number)
+                  (bracket error operator)))
+
+    (treesit-major-mode-setup)))
+
+(provide 'go-ts-mode)
+
+;;; go-ts-mode.el ends here
-- 
2.39.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode
  2022-12-13  2:13 bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode Randy Taylor
  2022-12-13 12:25 ` Eli Zaretskii
@ 2022-12-14  2:22 ` Yuan Fu
  2022-12-16  1:22 ` Yuan Fu
  2 siblings, 0 replies; 20+ messages in thread
From: Yuan Fu @ 2022-12-14  2:22 UTC (permalink / raw)
  To: Randy Taylor; +Cc: Eli Zaretskii, theo, 60025


Randy Taylor <dev@rjt.dev> writes:

> On Tuesday, December 13th, 2022 at 07:25, Eli Zaretskii <eliz@gnu.org> wrote: 
>> 
>> > Cc: casouri@gmail.com, theo@thornhill.no
>> 
>> > Date: Tue, 13 Dec 2022 02:13:47 +0000
>> > From: Randy Taylor dev@rjt.dev
>> > 
>> > --- a/etc/NEWS
>> > +++ b/etc/NEWS
>> > @@ -3078,6 +3078,11 @@ A major mode based on the tree-sitter library for editing CMake files.
>> > It includes support for font-locking, indentation, Imenu, and
>> > which-func.
>> > 
>> > +** New major modes 'go-ts-mode' and 'go-mod-ts-mode'.
>> > +Major modes based on the tree-sitter library for editing programs in
>> > +the Go language. It includes support for font-locking, indentation,
>> > +Imenu, and which-func.
>> 
>> 
>> This should say something about go-mod-ts-mode, at least which files
>> it's supposed to support and for what purpose.
>
> Indeed, thanks. I decided to split them up, and I also added that they will be auto-enabled.
>
>> > +(add-to-list 'auto-mode-alist '("go\\.mod\\'" . go-mod-ts-mode))
>> 
>> 
>> Aren't the files's base name always exactly "go.mod" and nothing else?
>> If so, the regexp should be augmented, because we don't want to use
>> this moder for, say, foogo.mod or fubar-go.mod, right?
>
> Oops, that's right. Good catch.

Looks pretty good to me :-)

Yuan





^ permalink raw reply	[flat|nested] 20+ messages in thread

* bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode
  2022-12-13 19:39   ` Randy Taylor
@ 2022-12-14 12:02     ` Eli Zaretskii
  2022-12-14 16:21       ` Randy Taylor
  0 siblings, 1 reply; 20+ messages in thread
From: Eli Zaretskii @ 2022-12-14 12:02 UTC (permalink / raw)
  To: Randy Taylor; +Cc: 60025, theo, casouri

> Date: Tue, 13 Dec 2022 19:39:18 +0000
> From: Randy Taylor <dev@rjt.dev>
> Cc: 60025@debbugs.gnu.org, theo@thornhill.no, casouri@gmail.com
> 
> +** New major mode 'go-mod-ts-mode'.
> +A major mode based on the tree-sitter library for editing go.mod
> +files.  It includes support for font-locking and indentation.  This
> +mode will be auto-enabled for go.mod files.

Please quote go.mod as 'go.mod' (we quote file names and symbols in
NEWS).

Otherwise, this LGTM, thanks.





^ permalink raw reply	[flat|nested] 20+ messages in thread

* bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode
  2022-12-14 12:02     ` Eli Zaretskii
@ 2022-12-14 16:21       ` Randy Taylor
  2022-12-14 19:55         ` Yuan Fu
  0 siblings, 1 reply; 20+ messages in thread
From: Randy Taylor @ 2022-12-14 16:21 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 60025, theo, casouri

[-- Attachment #1: Type: text/plain, Size: 970 bytes --]

On Wednesday, December 14th, 2022 at 07:02, Eli Zaretskii <eliz@gnu.org> wrote:
> 
> > Date: Tue, 13 Dec 2022 19:39:18 +0000
> 
> > From: Randy Taylor dev@rjt.dev
> > Cc: 60025@debbugs.gnu.org, theo@thornhill.no, casouri@gmail.com
> > 
> > +** New major mode 'go-mod-ts-mode'.
> > +A major mode based on the tree-sitter library for editing go.mod
> > +files. It includes support for font-locking and indentation. This
> > +mode will be auto-enabled for go.mod files.
> 
> 
> Please quote go.mod as 'go.mod' (we quote file names and symbols in
> NEWS).
> 
> Otherwise, this LGTM, thanks.
> 

Thanks, here's the new patch. I double-quoted it since that's what other file names and extensions have in NEWS - hopefully that's right. Assuming that's OK, should be good to install.

Yuan, did you see my original post in this thread? I'm wondering how to go about tackling that indentation problem (I'm probably missing something very simple and obvious).

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Add-go-ts-mode-and-go-mod-ts-mode-Bug-60025.patch --]
[-- Type: text/x-patch; name=0001-Add-go-ts-mode-and-go-mod-ts-mode-Bug-60025.patch, Size: 14956 bytes --]

From 364343de36e332a2bed4527c844782b8f1e508be Mon Sep 17 00:00:00 2001
From: Randy Taylor <dev@rjt.dev>
Date: Sun, 11 Dec 2022 18:41:16 -0500
Subject: [PATCH] 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.
---
 admin/notes/tree-sitter/build-module/batch.sh |   1 +
 admin/notes/tree-sitter/build-module/build.sh |   5 +
 etc/NEWS                                      |  11 +
 lisp/progmodes/eglot.el                       |   3 +-
 lisp/progmodes/go-ts-mode.el                  | 331 ++++++++++++++++++
 5 files changed, 350 insertions(+), 1 deletion(-)
 create mode 100644 lisp/progmodes/go-ts-mode.el

diff --git a/admin/notes/tree-sitter/build-module/batch.sh b/admin/notes/tree-sitter/build-module/batch.sh
index c395449977..c322d405f2 100755
--- a/admin/notes/tree-sitter/build-module/batch.sh
+++ b/admin/notes/tree-sitter/build-module/batch.sh
@@ -8,6 +8,7 @@ languages=
     'c-sharp'
     'dockerfile'
     'go'
+    'go-mod'
     'html'
     'javascript'
     'json'
diff --git a/admin/notes/tree-sitter/build-module/build.sh b/admin/notes/tree-sitter/build-module/build.sh
index 4195ea58c3..6d22127bd1 100755
--- a/admin/notes/tree-sitter/build-module/build.sh
+++ b/admin/notes/tree-sitter/build-module/build.sh
@@ -26,6 +26,11 @@ grammardir=
     "cmake")
         org="uyha"
         ;;
+    "go-mod")
+        # The parser is called "gomod".
+        lang="gomod"
+        namespace="camdencheek"
+        ;;
     "typescript")
         sourcedir="tree-sitter-typescript/typescript/src"
         grammardir="tree-sitter-typescript/typescript"
diff --git a/etc/NEWS b/etc/NEWS
index 6cb531cd0f..02c8d19778 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -3078,6 +3078,17 @@ A major mode based on the tree-sitter library for editing CMake files.
 It includes support for font-locking, indentation, Imenu, and
 which-func.
 
+** New major mode 'go-ts-mode'.
+A major mode based on the tree-sitter library for editing programs in
+the Go language.  It includes support for font-locking, indentation,
+Imenu, and which-func.  This mode will be auto-enabled for files with
+the ".go" extension.
+
+** New major mode 'go-mod-ts-mode'.
+A major mode based on the tree-sitter library for editing "go.mod"
+files.  It includes support for font-locking and indentation.  This
+mode will be auto-enabled for "go.mod" files.
+
 \f
 * Incompatible Lisp Changes in Emacs 29.1
 
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 9c5a361df7..02bb6bb665 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -211,7 +211,8 @@ eglot-server-programs
                                 (elm-mode . ("elm-language-server"))
                                 (mint-mode . ("mint" "ls"))
                                 (kotlin-mode . ("kotlin-language-server"))
-                                ((go-mode go-dot-mod-mode go-dot-work-mode) . ("gopls"))
+                                ((go-mode go-dot-mod-mode go-dot-work-mode go-ts-mode go-mod-ts-mode)
+                                 . ("gopls"))
                                 ((R-mode ess-r-mode) . ("R" "--slave" "-e"
                                                         "languageserver::run()"))
                                 ((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 0000000000..b86ab7ab85
--- /dev/null
+++ b/lisp/progmodes/go-ts-mode.el
@@ -0,0 +1,331 @@
+;;; go-ts-mode.el --- tree-sitter support for Go  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2022 Free Software Foundation, Inc.
+
+;; Author     : Randy Taylor <dev@rjt.dev>
+;; Maintainer : Randy Taylor <dev@rjt.dev>
+;; Created    : December 2022
+;; Keywords   : go languages tree-sitter
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+
+;;; Code:
+
+(require 'treesit)
+(eval-when-compile (require 'rx))
+
+(declare-function treesit-parser-create "treesit.c")
+(declare-function treesit-induce-sparse-tree "treesit.c")
+(declare-function treesit-node-child "treesit.c")
+(declare-function treesit-node-child-by-field-name "treesit.c")
+(declare-function treesit-node-start "treesit.c")
+(declare-function treesit-node-type "treesit.c")
+
+(defcustom go-ts-mode-indent-offset 4
+  "Number of spaces for each indentation step in `go-ts-mode'."
+  :version "29.1"
+  :type 'integer
+  :safe 'integerp
+  :group 'go)
+
+(defvar go-ts-mode--syntax-table
+  (let ((table (make-syntax-table)))
+    (modify-syntax-entry ?+   "."      table)
+    (modify-syntax-entry ?-   "."      table)
+    (modify-syntax-entry ?=   "."      table)
+    (modify-syntax-entry ?%   "."      table)
+    (modify-syntax-entry ?&   "."      table)
+    (modify-syntax-entry ?|   "."      table)
+    (modify-syntax-entry ?^   "."      table)
+    (modify-syntax-entry ?!   "."      table)
+    (modify-syntax-entry ?<   "."      table)
+    (modify-syntax-entry ?>   "."      table)
+    (modify-syntax-entry ?\\  "\\"     table)
+    (modify-syntax-entry ?/   ". 124b" table)
+    (modify-syntax-entry ?*   ". 23"   table)
+    (modify-syntax-entry ?\n  "> b"    table)
+    table)
+  "Syntax table for `go-ts-mode'.")
+
+(defvar go-ts-mode--indent-rules
+  `((go
+     ((node-is ")") parent-bol 0)
+     ((node-is "]") parent-bol 0)
+     ((node-is "}") parent-bol 0)
+     ((node-is "labeled_statement") no-indent)
+     ((parent-is "argument_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "block") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "const_declaration") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "default_case") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "expression_case") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "expression_switch_statement") parent-bol 0)
+     ((parent-is "field_declaration_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "import_spec_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "labeled_statement") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "literal_value") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "type_spec") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "var_declaration") parent-bol go-ts-mode-indent-offset)
+     (no-node parent-bol 0)))
+  "Tree-sitter indent rules for `go-ts-mode'.")
+
+(defvar go-ts-mode--keywords
+  '("break" "case" "chan" "const" "continue" "default" "defer" "else"
+    "fallthrough" "for" "func" "go" "goto" "if" "import" "interface" "map"
+    "package" "range" "return" "select" "struct" "switch" "type" "var")
+  "Go keywords for tree-sitter font-locking.")
+
+(defvar go-ts-mode--operators
+  '("+" "&" "+=" "&=" "&&" "==" "!=" "-" "|" "-=" "|=" "||" "<" "<="
+    "*" "^" "*=" "^=" "<-" ">" ">=" "/" "<<" "/=" "<<=" "++" "=" ":=" "%"
+    ">>" "%=" ">>=" "--" "!"  "..."  "&^" "&^=" "~")
+  "Go operators for tree-sitter font-locking.")
+
+(defvar go-ts-mode--font-lock-settings
+  (treesit-font-lock-rules
+   :language 'go
+   :feature 'bracket
+   '((["(" ")" "[" "]" "{" "}"]) @font-lock-bracket-face)
+
+   :language 'go
+   :feature 'comment
+   '((comment) @font-lock-comment-face)
+
+   :language 'go
+   :feature 'constant
+   '([(false) (iota) (nil) (true)] @font-lock-constant-face
+     (const_declaration
+      (const_spec name: (identifier) @font-lock-constant-face)))
+
+   :language 'go
+   :feature 'delimiter
+   '((["," "." ";" ":"]) @font-lock-delimiter-face)
+
+   :language 'go
+   :feature 'function
+   '((call_expression
+      function: (identifier) @font-lock-function-name-face)
+     (call_expression
+      function: (selector_expression
+                 field: (field_identifier) @font-lock-function-name-face))
+     (function_declaration
+      name: (identifier) @font-lock-function-name-face)
+     (method_declaration
+      name: (field_identifier) @font-lock-function-name-face))
+
+   :language 'go
+   :feature 'keyword
+   `([,@go-ts-mode--keywords] @font-lock-keyword-face)
+
+   :language 'go
+   :feature 'label
+   '((label_name) @font-lock-constant-face)
+
+   :language 'go
+   :feature 'number
+   '([(float_literal)
+      (imaginary_literal)
+      (int_literal)] @font-lock-number-face)
+
+   :language 'go
+   :feature 'string
+   '([(interpreted_string_literal)
+      (raw_string_literal)
+      (rune_literal)] @font-lock-string-face)
+
+   :language 'go
+   :feature 'type
+   '([(package_identifier) (type_identifier)] @font-lock-type-face)
+
+   :language 'go
+   :feature 'variable
+   '((identifier) @font-lock-variable-name-face)
+
+   :language 'go
+   :feature 'escape-sequence
+   :override t
+   '((escape_sequence) @font-lock-escape-face)
+
+   :language 'go
+   :feature 'property
+   :override t
+   '((field_identifier) @font-lock-property-face
+     (keyed_element (_ (identifier) @font-lock-property-face)))
+
+   :language 'go
+   :feature 'error
+   :override t
+   '((ERROR) @font-lock-warning-face))
+  "Tree-sitter font-lock settings for `go-ts-mode'.")
+
+(defun go-ts-mode--imenu ()
+  "Return Imenu alist for the current buffer."
+  (let* ((node (treesit-buffer-root-node))
+         (func-tree (treesit-induce-sparse-tree
+                     node "function_declaration" nil 1000))
+         (type-tree (treesit-induce-sparse-tree
+                     node "type_spec" nil 1000))
+         (func-index (go-ts-mode--imenu-1 func-tree))
+         (type-index (go-ts-mode--imenu-1 type-tree)))
+    (append
+     (when func-index `(("Function" . ,func-index)))
+     (when type-index `(("Type" . ,type-index))))))
+
+(defun go-ts-mode--imenu-1 (node)
+  "Helper for `go-ts-mode--imenu'.
+Find string representation for NODE and set marker, then recurse
+the subtrees."
+  (let* ((ts-node (car node))
+         (children (cdr node))
+         (subtrees (mapcan #'go-ts-mode--imenu-1
+                           children))
+         (name (when ts-node
+                 (treesit-node-text
+                  (pcase (treesit-node-type ts-node)
+                    ("function_declaration"
+                     (treesit-node-child-by-field-name ts-node "name"))
+                    ("type_spec"
+                     (treesit-node-child-by-field-name ts-node "name"))))))
+         (marker (when ts-node
+                   (set-marker (make-marker)
+                               (treesit-node-start ts-node)))))
+    (cond
+     ((or (null ts-node) (null name)) subtrees)
+     (subtrees
+      `((,name ,(cons name marker) ,@subtrees)))
+     (t
+      `((,name . ,marker))))))
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.go\\'" . go-ts-mode))
+
+;;;###autoload
+(define-derived-mode go-ts-mode prog-mode "Go"
+  "Major mode for editing Go, powered by tree-sitter."
+  :group 'go
+  :syntax-table go-ts-mode--syntax-table
+
+  (when (treesit-ready-p 'go)
+    (treesit-parser-create 'go)
+
+    ;; Comments.
+    (setq-local comment-start "// ")
+    (setq-local comment-end "")
+    (setq-local comment-start-skip (rx "//" (* (syntax whitespace))))
+
+    ;; Imenu.
+    (setq-local imenu-create-index-function #'go-ts-mode--imenu)
+    (setq-local which-func-functions nil)
+
+    ;; Indent.
+    (setq-local indent-tabs-mode t
+                treesit-simple-indent-rules go-ts-mode--indent-rules)
+
+    ;; Font-lock.
+    (setq-local treesit-font-lock-settings go-ts-mode--font-lock-settings)
+    (setq-local treesit-font-lock-feature-list
+                '(( comment)
+                  ( keyword string type)
+                  ( constant escape-sequence function label number
+                    property variable)
+                  ( bracket delimiter error operator)))
+
+    (treesit-major-mode-setup)))
+
+;; go.mod support.
+
+(defvar go-mod-ts-mode--syntax-table
+  (let ((table (make-syntax-table)))
+    (modify-syntax-entry ?/   ". 124b" table)
+    (modify-syntax-entry ?\n  "> b"    table)
+    table)
+  "Syntax table for `go-mod-ts-mode'.")
+
+(defvar go-mod-ts-mode--indent-rules
+  `((gomod
+     ((node-is ")") parent-bol 0)
+     ((parent-is "require_directive") parent-bol go-ts-mode-indent-offset)
+     (no-node parent-bol 0)))
+  "Tree-sitter indent rules for `go-mod-ts-mode'.")
+
+(defvar go-mod-ts-mode--keywords
+  '("exclude" "go" "module" "replace" "require" "retract")
+  "go.mod keywords for tree-sitter font-locking.")
+
+(defvar go-mod-ts-mode--font-lock-settings
+  (treesit-font-lock-rules
+   :language 'gomod
+   :feature 'bracket
+   '((["(" ")"]) @font-lock-bracket-face)
+
+   :language 'gomod
+   :feature 'comment
+   '((comment) @font-lock-comment-face)
+
+   :language 'gomod
+   :feature 'keyword
+   `([,@go-mod-ts-mode--keywords] @font-lock-keyword-face)
+
+   :language 'gomod
+   :feature 'number
+   '([(go_version) (version)] @font-lock-number-face)
+
+   :language 'gomod
+   :feature 'operator
+   '((["=>"]) @font-lock-operator-face)
+
+   :language 'gomod
+   :feature 'error
+   :override t
+   '((ERROR) @font-lock-warning-face))
+  "Tree-sitter font-lock settings for `go-mod-ts-mode'.")
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("/go\\.mod\\'" . go-mod-ts-mode))
+
+;;;###autoload
+(define-derived-mode go-mod-ts-mode prog-mode "Go Mod"
+  "Major mode for editing go.mod files, powered by tree-sitter."
+  :group 'go
+  :syntax-table go-mod-ts-mode--syntax-table
+
+  (when (treesit-ready-p 'gomod)
+    (treesit-parser-create 'gomod)
+
+    ;; 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-mod-ts-mode--indent-rules)
+
+    ;; Font-lock.
+    (setq-local treesit-font-lock-settings go-mod-ts-mode--font-lock-settings)
+    (setq-local treesit-font-lock-feature-list
+                '((comment)
+                  (keyword)
+                  (number)
+                  (bracket error operator)))
+
+    (treesit-major-mode-setup)))
+
+(provide 'go-ts-mode)
+
+;;; go-ts-mode.el ends here
-- 
2.39.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode
  2022-12-14 16:21       ` Randy Taylor
@ 2022-12-14 19:55         ` Yuan Fu
  2022-12-14 20:54           ` Randy Taylor
  0 siblings, 1 reply; 20+ messages in thread
From: Yuan Fu @ 2022-12-14 19:55 UTC (permalink / raw)
  To: Randy Taylor; +Cc: Eli Zaretskii, theo, 60025



> On Dec 14, 2022, at 8:21 AM, Randy Taylor <dev@rjt.dev> wrote:
> 
> On Wednesday, December 14th, 2022 at 07:02, Eli Zaretskii <eliz@gnu.org> wrote:
>> 
>>> Date: Tue, 13 Dec 2022 19:39:18 +0000
>> 
>>> From: Randy Taylor dev@rjt.dev
>>> Cc: 60025@debbugs.gnu.org, theo@thornhill.no, casouri@gmail.com
>>> 
>>> +** New major mode 'go-mod-ts-mode'.
>>> +A major mode based on the tree-sitter library for editing go.mod
>>> +files. It includes support for font-locking and indentation. This
>>> +mode will be auto-enabled for go.mod files.
>> 
>> 
>> Please quote go.mod as 'go.mod' (we quote file names and symbols in
>> NEWS).
>> 
>> Otherwise, this LGTM, thanks.
>> 
> 
> Thanks, here's the new patch. I double-quoted it since that's what other file names and extensions have in NEWS - hopefully that's right. Assuming that's OK, should be good to install.
> 
> Yuan, did you see my original post in this thread? I'm wondering how to go about tackling that indentation problem (I'm probably missing something very simple and obvious).

Ah, oops, replies below:

> I am having a tiny bit of trouble with a go.mod indentation rule. Using the patch, create a go.mod file anywhere, activate go-mod-ts-mode and add the following:
> 
> require ()
> 
> Place point inside the parens, and then hit enter. The expectation is that point will end up indented inside that block. If you add the text "test v1.0.0" and hit TAB, it will indent properly (and if you hit enter after that text it will indent properly for the next entry). If you go to the end of the line for the top paren and hit enter, it will not indent (and we want it to). It seems to give us no-node in that circumstance. Is there a simple indent rule that can match exactly that that I'm missing?

I think you can just test for the parent? In C, if point is at an empty line after a statement in a block, like this:

int main() {
  return 0;
  |
}

The matched rule is (parent-is “compond_statement”), where compound_statement is the block. In your case, I guess you can test if parent is the argument list.

Yuan




^ permalink raw reply	[flat|nested] 20+ messages in thread

* bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode
  2022-12-14 19:55         ` Yuan Fu
@ 2022-12-14 20:54           ` Randy Taylor
  2022-12-14 21:27             ` Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 20+ messages in thread
From: Randy Taylor @ 2022-12-14 20:54 UTC (permalink / raw)
  To: Yuan Fu; +Cc: Eli Zaretskii, theo, 60025

[-- Attachment #1: Type: text/plain, Size: 1903 bytes --]

On Wednesday, December 14th, 2022 at 14:55, Yuan Fu <casouri@gmail.com> wrote:
> 
> > I am having a tiny bit of trouble with a go.mod indentation rule. Using the patch, create a go.mod file anywhere, activate go-mod-ts-mode and add the following:
> > 
> > require ()
> > 
> > Place point inside the parens, and then hit enter. The expectation is that point will end up indented inside that block. If you add the text "test v1.0.0" and hit TAB, it will indent properly (and if you hit enter after that text it will indent properly for the next entry). If you go to the end of the line for the top paren and hit enter, it will not indent (and we want it to). It seems to give us no-node in that circumstance. Is there a simple indent rule that can match exactly that that I'm missing?
> 
> 
> I think you can just test for the parent? In C, if point is at an empty line after a statement in a block, like this:
> 
> int main() {
> return 0;
> |
> }
> 
> The matched rule is (parent-is “compond_statement”), where compound_statement is the block. In your case, I guess you can test if parent is the argument list.
> 
> Yuan

I do match for the parent, but it doesn't seem to help.
Here's what tree-sitter explorer shows for the following:
require (

)

(require_directive require ( \n ) \n)

If I put point on the line right below r and do C-S-a, tree-sitter explorer shows:
(require_directive require (*\n ) \n)
The * indicates that part is highlighted.

The second I make it "proper" like so:
require (
test v1
)

I can hit TAB and it will indent properly (but not anything above it, only that line and anything below).

I would think that checking parent-is for require_directive would be enough but it's not somehow???
It's instead matching my no-node rule...

(Also I attached a new patch which contains a few more indent rules for go.mod I forgot about).

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Add-go-ts-mode-and-go-mod-ts-mode-Bug-60025.patch --]
[-- Type: text/x-patch; name=0001-Add-go-ts-mode-and-go-mod-ts-mode-Bug-60025.patch, Size: 15259 bytes --]

From 7555a4b0f7830210b569bff2ddcd87c42cacda3e Mon Sep 17 00:00:00 2001
From: Randy Taylor <dev@rjt.dev>
Date: Sun, 11 Dec 2022 18:41:16 -0500
Subject: [PATCH] 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.
---
 admin/notes/tree-sitter/build-module/batch.sh |   1 +
 admin/notes/tree-sitter/build-module/build.sh |   5 +
 etc/NEWS                                      |  11 +
 lisp/progmodes/eglot.el                       |   3 +-
 lisp/progmodes/go-ts-mode.el                  | 335 ++++++++++++++++++
 5 files changed, 354 insertions(+), 1 deletion(-)
 create mode 100644 lisp/progmodes/go-ts-mode.el

diff --git a/admin/notes/tree-sitter/build-module/batch.sh b/admin/notes/tree-sitter/build-module/batch.sh
index c395449977..c322d405f2 100755
--- a/admin/notes/tree-sitter/build-module/batch.sh
+++ b/admin/notes/tree-sitter/build-module/batch.sh
@@ -8,6 +8,7 @@ languages=
     'c-sharp'
     'dockerfile'
     'go'
+    'go-mod'
     'html'
     'javascript'
     'json'
diff --git a/admin/notes/tree-sitter/build-module/build.sh b/admin/notes/tree-sitter/build-module/build.sh
index 4195ea58c3..6d22127bd1 100755
--- a/admin/notes/tree-sitter/build-module/build.sh
+++ b/admin/notes/tree-sitter/build-module/build.sh
@@ -26,6 +26,11 @@ grammardir=
     "cmake")
         org="uyha"
         ;;
+    "go-mod")
+        # The parser is called "gomod".
+        lang="gomod"
+        namespace="camdencheek"
+        ;;
     "typescript")
         sourcedir="tree-sitter-typescript/typescript/src"
         grammardir="tree-sitter-typescript/typescript"
diff --git a/etc/NEWS b/etc/NEWS
index 6cb531cd0f..02c8d19778 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -3078,6 +3078,17 @@ A major mode based on the tree-sitter library for editing CMake files.
 It includes support for font-locking, indentation, Imenu, and
 which-func.
 
+** New major mode 'go-ts-mode'.
+A major mode based on the tree-sitter library for editing programs in
+the Go language.  It includes support for font-locking, indentation,
+Imenu, and which-func.  This mode will be auto-enabled for files with
+the ".go" extension.
+
+** New major mode 'go-mod-ts-mode'.
+A major mode based on the tree-sitter library for editing "go.mod"
+files.  It includes support for font-locking and indentation.  This
+mode will be auto-enabled for "go.mod" files.
+
 \f
 * Incompatible Lisp Changes in Emacs 29.1
 
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 9c5a361df7..02bb6bb665 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -211,7 +211,8 @@ eglot-server-programs
                                 (elm-mode . ("elm-language-server"))
                                 (mint-mode . ("mint" "ls"))
                                 (kotlin-mode . ("kotlin-language-server"))
-                                ((go-mode go-dot-mod-mode go-dot-work-mode) . ("gopls"))
+                                ((go-mode go-dot-mod-mode go-dot-work-mode go-ts-mode go-mod-ts-mode)
+                                 . ("gopls"))
                                 ((R-mode ess-r-mode) . ("R" "--slave" "-e"
                                                         "languageserver::run()"))
                                 ((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 0000000000..02f5d53810
--- /dev/null
+++ b/lisp/progmodes/go-ts-mode.el
@@ -0,0 +1,335 @@
+;;; go-ts-mode.el --- tree-sitter support for Go  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2022 Free Software Foundation, Inc.
+
+;; Author     : Randy Taylor <dev@rjt.dev>
+;; Maintainer : Randy Taylor <dev@rjt.dev>
+;; Created    : December 2022
+;; Keywords   : go languages tree-sitter
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+
+;;; Code:
+
+(require 'treesit)
+(eval-when-compile (require 'rx))
+
+(declare-function treesit-parser-create "treesit.c")
+(declare-function treesit-induce-sparse-tree "treesit.c")
+(declare-function treesit-node-child "treesit.c")
+(declare-function treesit-node-child-by-field-name "treesit.c")
+(declare-function treesit-node-start "treesit.c")
+(declare-function treesit-node-type "treesit.c")
+
+(defcustom go-ts-mode-indent-offset 4
+  "Number of spaces for each indentation step in `go-ts-mode'."
+  :version "29.1"
+  :type 'integer
+  :safe 'integerp
+  :group 'go)
+
+(defvar go-ts-mode--syntax-table
+  (let ((table (make-syntax-table)))
+    (modify-syntax-entry ?+   "."      table)
+    (modify-syntax-entry ?-   "."      table)
+    (modify-syntax-entry ?=   "."      table)
+    (modify-syntax-entry ?%   "."      table)
+    (modify-syntax-entry ?&   "."      table)
+    (modify-syntax-entry ?|   "."      table)
+    (modify-syntax-entry ?^   "."      table)
+    (modify-syntax-entry ?!   "."      table)
+    (modify-syntax-entry ?<   "."      table)
+    (modify-syntax-entry ?>   "."      table)
+    (modify-syntax-entry ?\\  "\\"     table)
+    (modify-syntax-entry ?/   ". 124b" table)
+    (modify-syntax-entry ?*   ". 23"   table)
+    (modify-syntax-entry ?\n  "> b"    table)
+    table)
+  "Syntax table for `go-ts-mode'.")
+
+(defvar go-ts-mode--indent-rules
+  `((go
+     ((node-is ")") parent-bol 0)
+     ((node-is "]") parent-bol 0)
+     ((node-is "}") parent-bol 0)
+     ((node-is "labeled_statement") no-indent)
+     ((parent-is "argument_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "block") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "const_declaration") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "default_case") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "expression_case") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "expression_switch_statement") parent-bol 0)
+     ((parent-is "field_declaration_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "import_spec_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "labeled_statement") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "literal_value") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "type_spec") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "var_declaration") parent-bol go-ts-mode-indent-offset)
+     (no-node parent-bol 0)))
+  "Tree-sitter indent rules for `go-ts-mode'.")
+
+(defvar go-ts-mode--keywords
+  '("break" "case" "chan" "const" "continue" "default" "defer" "else"
+    "fallthrough" "for" "func" "go" "goto" "if" "import" "interface" "map"
+    "package" "range" "return" "select" "struct" "switch" "type" "var")
+  "Go keywords for tree-sitter font-locking.")
+
+(defvar go-ts-mode--operators
+  '("+" "&" "+=" "&=" "&&" "==" "!=" "-" "|" "-=" "|=" "||" "<" "<="
+    "*" "^" "*=" "^=" "<-" ">" ">=" "/" "<<" "/=" "<<=" "++" "=" ":=" "%"
+    ">>" "%=" ">>=" "--" "!"  "..."  "&^" "&^=" "~")
+  "Go operators for tree-sitter font-locking.")
+
+(defvar go-ts-mode--font-lock-settings
+  (treesit-font-lock-rules
+   :language 'go
+   :feature 'bracket
+   '((["(" ")" "[" "]" "{" "}"]) @font-lock-bracket-face)
+
+   :language 'go
+   :feature 'comment
+   '((comment) @font-lock-comment-face)
+
+   :language 'go
+   :feature 'constant
+   '([(false) (iota) (nil) (true)] @font-lock-constant-face
+     (const_declaration
+      (const_spec name: (identifier) @font-lock-constant-face)))
+
+   :language 'go
+   :feature 'delimiter
+   '((["," "." ";" ":"]) @font-lock-delimiter-face)
+
+   :language 'go
+   :feature 'function
+   '((call_expression
+      function: (identifier) @font-lock-function-name-face)
+     (call_expression
+      function: (selector_expression
+                 field: (field_identifier) @font-lock-function-name-face))
+     (function_declaration
+      name: (identifier) @font-lock-function-name-face)
+     (method_declaration
+      name: (field_identifier) @font-lock-function-name-face))
+
+   :language 'go
+   :feature 'keyword
+   `([,@go-ts-mode--keywords] @font-lock-keyword-face)
+
+   :language 'go
+   :feature 'label
+   '((label_name) @font-lock-constant-face)
+
+   :language 'go
+   :feature 'number
+   '([(float_literal)
+      (imaginary_literal)
+      (int_literal)] @font-lock-number-face)
+
+   :language 'go
+   :feature 'string
+   '([(interpreted_string_literal)
+      (raw_string_literal)
+      (rune_literal)] @font-lock-string-face)
+
+   :language 'go
+   :feature 'type
+   '([(package_identifier) (type_identifier)] @font-lock-type-face)
+
+   :language 'go
+   :feature 'variable
+   '((identifier) @font-lock-variable-name-face)
+
+   :language 'go
+   :feature 'escape-sequence
+   :override t
+   '((escape_sequence) @font-lock-escape-face)
+
+   :language 'go
+   :feature 'property
+   :override t
+   '((field_identifier) @font-lock-property-face
+     (keyed_element (_ (identifier) @font-lock-property-face)))
+
+   :language 'go
+   :feature 'error
+   :override t
+   '((ERROR) @font-lock-warning-face))
+  "Tree-sitter font-lock settings for `go-ts-mode'.")
+
+(defun go-ts-mode--imenu ()
+  "Return Imenu alist for the current buffer."
+  (let* ((node (treesit-buffer-root-node))
+         (func-tree (treesit-induce-sparse-tree
+                     node "function_declaration" nil 1000))
+         (type-tree (treesit-induce-sparse-tree
+                     node "type_spec" nil 1000))
+         (func-index (go-ts-mode--imenu-1 func-tree))
+         (type-index (go-ts-mode--imenu-1 type-tree)))
+    (append
+     (when func-index `(("Function" . ,func-index)))
+     (when type-index `(("Type" . ,type-index))))))
+
+(defun go-ts-mode--imenu-1 (node)
+  "Helper for `go-ts-mode--imenu'.
+Find string representation for NODE and set marker, then recurse
+the subtrees."
+  (let* ((ts-node (car node))
+         (children (cdr node))
+         (subtrees (mapcan #'go-ts-mode--imenu-1
+                           children))
+         (name (when ts-node
+                 (treesit-node-text
+                  (pcase (treesit-node-type ts-node)
+                    ("function_declaration"
+                     (treesit-node-child-by-field-name ts-node "name"))
+                    ("type_spec"
+                     (treesit-node-child-by-field-name ts-node "name"))))))
+         (marker (when ts-node
+                   (set-marker (make-marker)
+                               (treesit-node-start ts-node)))))
+    (cond
+     ((or (null ts-node) (null name)) subtrees)
+     (subtrees
+      `((,name ,(cons name marker) ,@subtrees)))
+     (t
+      `((,name . ,marker))))))
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.go\\'" . go-ts-mode))
+
+;;;###autoload
+(define-derived-mode go-ts-mode prog-mode "Go"
+  "Major mode for editing Go, powered by tree-sitter."
+  :group 'go
+  :syntax-table go-ts-mode--syntax-table
+
+  (when (treesit-ready-p 'go)
+    (treesit-parser-create 'go)
+
+    ;; Comments.
+    (setq-local comment-start "// ")
+    (setq-local comment-end "")
+    (setq-local comment-start-skip (rx "//" (* (syntax whitespace))))
+
+    ;; Imenu.
+    (setq-local imenu-create-index-function #'go-ts-mode--imenu)
+    (setq-local which-func-functions nil)
+
+    ;; Indent.
+    (setq-local indent-tabs-mode t
+                treesit-simple-indent-rules go-ts-mode--indent-rules)
+
+    ;; Font-lock.
+    (setq-local treesit-font-lock-settings go-ts-mode--font-lock-settings)
+    (setq-local treesit-font-lock-feature-list
+                '(( comment)
+                  ( keyword string type)
+                  ( constant escape-sequence function label number
+                    property variable)
+                  ( bracket delimiter error operator)))
+
+    (treesit-major-mode-setup)))
+
+;; go.mod support.
+
+(defvar go-mod-ts-mode--syntax-table
+  (let ((table (make-syntax-table)))
+    (modify-syntax-entry ?/   ". 124b" table)
+    (modify-syntax-entry ?\n  "> b"    table)
+    table)
+  "Syntax table for `go-mod-ts-mode'.")
+
+(defvar go-mod-ts-mode--indent-rules
+  `((gomod
+     ((node-is ")") parent-bol 0)
+     ((parent-is "exclude_directive") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "module_directive") parent-bol go-ts-mode-indent-offset)
+     ((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)
+     (no-node parent-bol 0)))
+  "Tree-sitter indent rules for `go-mod-ts-mode'.")
+
+(defvar go-mod-ts-mode--keywords
+  '("exclude" "go" "module" "replace" "require" "retract")
+  "go.mod keywords for tree-sitter font-locking.")
+
+(defvar go-mod-ts-mode--font-lock-settings
+  (treesit-font-lock-rules
+   :language 'gomod
+   :feature 'bracket
+   '((["(" ")"]) @font-lock-bracket-face)
+
+   :language 'gomod
+   :feature 'comment
+   '((comment) @font-lock-comment-face)
+
+   :language 'gomod
+   :feature 'keyword
+   `([,@go-mod-ts-mode--keywords] @font-lock-keyword-face)
+
+   :language 'gomod
+   :feature 'number
+   '([(go_version) (version)] @font-lock-number-face)
+
+   :language 'gomod
+   :feature 'operator
+   '((["=>"]) @font-lock-operator-face)
+
+   :language 'gomod
+   :feature 'error
+   :override t
+   '((ERROR) @font-lock-warning-face))
+  "Tree-sitter font-lock settings for `go-mod-ts-mode'.")
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("/go\\.mod\\'" . go-mod-ts-mode))
+
+;;;###autoload
+(define-derived-mode go-mod-ts-mode prog-mode "Go Mod"
+  "Major mode for editing go.mod files, powered by tree-sitter."
+  :group 'go
+  :syntax-table go-mod-ts-mode--syntax-table
+
+  (when (treesit-ready-p 'gomod)
+    (treesit-parser-create 'gomod)
+
+    ;; 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-mod-ts-mode--indent-rules)
+
+    ;; Font-lock.
+    (setq-local treesit-font-lock-settings go-mod-ts-mode--font-lock-settings)
+    (setq-local treesit-font-lock-feature-list
+                '((comment)
+                  (keyword)
+                  (number)
+                  (bracket error operator)))
+
+    (treesit-major-mode-setup)))
+
+(provide 'go-ts-mode)
+
+;;; go-ts-mode.el ends here
-- 
2.39.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode
  2022-12-14 20:54           ` Randy Taylor
@ 2022-12-14 21:27             ` Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2022-12-14 21:56               ` Randy Taylor
  2022-12-15  2:15               ` Randy Taylor
  0 siblings, 2 replies; 20+ messages in thread
From: Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2022-12-14 21:27 UTC (permalink / raw)
  To: Randy Taylor, Yuan Fu; +Cc: Eli Zaretskii, 60025

Randy Taylor <dev@rjt.dev> writes:

> On Wednesday, December 14th, 2022 at 14:55, Yuan Fu <casouri@gmail.com> wrote:
>> 
>> > I am having a tiny bit of trouble with a go.mod indentation rule. Using the patch, create a go.mod file anywhere, activate go-mod-ts-mode and add the following:
>> > 
>> > require ()
>> > 
>> > Place point inside the parens, and then hit enter. The expectation is that point will end up indented inside that block. If you add the text "test v1.0.0" and hit TAB, it will indent properly (and if you hit enter after that text it will indent properly for the next entry). If you go to the end of the line for the top paren and hit enter, it will not indent (and we want it to). It seems to give us no-node in that circumstance. Is there a simple indent rule that can match exactly that that I'm missing?
>> 
>> 
>> I think you can just test for the parent? In C, if point is at an empty line after a statement in a block, like this:
>> 
>> int main() {
>> return 0;
>> |
>> }
>> 
>> The matched rule is (parent-is “compond_statement”), where compound_statement is the block. In your case, I guess you can test if parent is the argument list.
>> 
>> Yuan
>
> I do match for the parent, but it doesn't seem to help.
> Here's what tree-sitter explorer shows for the following:
> require (
>
> )
>
> (require_directive require ( \n ) \n)
>
> If I put point on the line right below r and do C-S-a, tree-sitter explorer shows:
> (require_directive require (*\n ) \n)
> The * indicates that part is highlighted.
>
> The second I make it "proper" like so:
> require (
> test v1
> )
>
> I can hit TAB and it will indent properly (but not anything above it, only that line and anything below).
>
> I would think that checking parent-is for require_directive would be enough but it's not somehow???
> It's instead matching my no-node rule...

Yeah, I seem to remember seeing these \n nodes in the go-mode I made
some time ago.  There is no node there, so no-node is the rule that
matches, as there is no parent.  I believe you can solve it with
something like


(defun go-backward-up-list ()
  (lambda (node parent bol &rest _)
    (save-excursion
      (backward-up-list 1 nil t)
      (back-to-indentation)
      (point))))

and use some variant of that.  Now you can find a different node without
relying on there being a node where you start.

But I don't remember exactly.

BTW, I tried applying this mode, but it wouldn't apply.

Theo





^ permalink raw reply	[flat|nested] 20+ messages in thread

* bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode
  2022-12-14 21:27             ` Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2022-12-14 21:56               ` Randy Taylor
  2022-12-15  2:15               ` Randy Taylor
  1 sibling, 0 replies; 20+ messages in thread
From: Randy Taylor @ 2022-12-14 21:56 UTC (permalink / raw)
  To: Theodor Thornhill; +Cc: Yuan Fu, Eli Zaretskii, 60025

On Wednesday, December 14th, 2022 at 16:27, Theodor Thornhill <theo@thornhill.no> wrote:
> 
> Randy Taylor dev@rjt.dev writes:
> 
> > On Wednesday, December 14th, 2022 at 14:55, Yuan Fu casouri@gmail.com wrote:
> > 
> > > > I am having a tiny bit of trouble with a go.mod indentation rule. Using the patch, create a go.mod file anywhere, activate go-mod-ts-mode and add the following:
> > > > 
> > > > require ()
> > > > 
> > > > Place point inside the parens, and then hit enter. The expectation is that point will end up indented inside that block. If you add the text "test v1.0.0" and hit TAB, it will indent properly (and if you hit enter after that text it will indent properly for the next entry). If you go to the end of the line for the top paren and hit enter, it will not indent (and we want it to). It seems to give us no-node in that circumstance. Is there a simple indent rule that can match exactly that that I'm missing?
> > > 
> > > I think you can just test for the parent? In C, if point is at an empty line after a statement in a block, like this:
> > > 
> > > int main() {
> > > return 0;
> > > |
> > > }
> > > 
> > > The matched rule is (parent-is “compond_statement”), where compound_statement is the block. In your case, I guess you can test if parent is the argument list.
> > > 
> > > Yuan
> > 
> > I do match for the parent, but it doesn't seem to help.
> > Here's what tree-sitter explorer shows for the following:
> > require (
> > 
> > )
> > 
> > (require_directive require ( \n ) \n)
> > 
> > If I put point on the line right below r and do C-S-a, tree-sitter explorer shows:
> > (require_directive require (*\n ) \n)
> > The * indicates that part is highlighted.
> > 
> > The second I make it "proper" like so:
> > require (
> > test v1
> > )
> > 
> > I can hit TAB and it will indent properly (but not anything above it, only that line and anything below).
> > 
> > I would think that checking parent-is for require_directive would be enough but it's not somehow???
> > It's instead matching my no-node rule...
> 
> 
> Yeah, I seem to remember seeing these \n nodes in the go-mode I made
> some time ago. There is no node there, so no-node is the rule that
> matches, as there is no parent. I believe you can solve it with
> something like
> 
> 
> (defun go-backward-up-list ()
> (lambda (node parent bol &rest _)
> (save-excursion
> (backward-up-list 1 nil t)
> (back-to-indentation)
> (point))))
> 
> and use some variant of that. Now you can find a different node without
> relying on there being a node where you start.
> 
> But I don't remember exactly.

Thanks, I'll give that a try.

> 
> BTW, I tried applying this mode, but it wouldn't apply.

Applying the patch? It applies to emacs-29 fine for me, and both modes work as expected when I try them. What issue(s) are you seeing specifically?





^ permalink raw reply	[flat|nested] 20+ messages in thread

* bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode
  2022-12-14 21:27             ` Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2022-12-14 21:56               ` Randy Taylor
@ 2022-12-15  2:15               ` Randy Taylor
  2022-12-15  7:20                 ` Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors
  1 sibling, 1 reply; 20+ messages in thread
From: Randy Taylor @ 2022-12-15  2:15 UTC (permalink / raw)
  To: Theodor Thornhill; +Cc: Yuan Fu, Eli Zaretskii, 60025

[-- Attachment #1: Type: text/plain, Size: 1248 bytes --]

On Wednesday, December 14th, 2022 at 16:27, Theodor Thornhill <theo@thornhill.no> wrote:
> 
> Yeah, I seem to remember seeing these \n nodes in the go-mode I made
> some time ago. There is no node there, so no-node is the rule that
> matches, as there is no parent. I believe you can solve it with
> something like
> 
> 
> (defun go-backward-up-list ()
> (lambda (node parent bol &rest _)
> (save-excursion
> (backward-up-list 1 nil t)
> (back-to-indentation)
> (point))))
> 
> and use some variant of that. Now you can find a different node without
> relying on there being a node where you start.

Thanks Theo, that worked perfectly. We should consider adding something like this to the documentation somewhere, since I didn't see anything like this anywhere (although maybe I missed it).

New patch attached with the following changes:
- go-mod-ts-mode--indent-rules modified to utilize the new function to check if in a directive, and if so, indent appropriately.
- New function go-mod-ts-mode--in-directive-p that checks if we're in a directive.
  - I'm no elisp guru, so there may (probably) be a better way to do what I did. Happy to improve it (and the name and docstring, too).

Otherwise, feel free to install it.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Add-go-ts-mode-and-go-mod-ts-mode-Bug-60025.patch --]
[-- Type: text/x-patch; name=0001-Add-go-ts-mode-and-go-mod-ts-mode-Bug-60025.patch, Size: 16030 bytes --]

From 2f6ea5243f3919ec632505459cf3e9417c7cb7fe Mon Sep 17 00:00:00 2001
From: Randy Taylor <dev@rjt.dev>
Date: Sun, 11 Dec 2022 18:41:16 -0500
Subject: [PATCH] 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.
---
 admin/notes/tree-sitter/build-module/batch.sh |   1 +
 admin/notes/tree-sitter/build-module/build.sh |   5 +
 etc/NEWS                                      |  11 +
 lisp/progmodes/eglot.el                       |   3 +-
 lisp/progmodes/go-ts-mode.el                  | 354 ++++++++++++++++++
 5 files changed, 373 insertions(+), 1 deletion(-)
 create mode 100644 lisp/progmodes/go-ts-mode.el

diff --git a/admin/notes/tree-sitter/build-module/batch.sh b/admin/notes/tree-sitter/build-module/batch.sh
index e7ef45cf57..c50b9df37e 100755
--- a/admin/notes/tree-sitter/build-module/batch.sh
+++ b/admin/notes/tree-sitter/build-module/batch.sh
@@ -9,6 +9,7 @@ languages=
     'c-sharp'
     'dockerfile'
     'go'
+    'go-mod'
     'html'
     'javascript'
     'json'
diff --git a/admin/notes/tree-sitter/build-module/build.sh b/admin/notes/tree-sitter/build-module/build.sh
index 4195ea58c3..6d22127bd1 100755
--- a/admin/notes/tree-sitter/build-module/build.sh
+++ b/admin/notes/tree-sitter/build-module/build.sh
@@ -26,6 +26,11 @@ grammardir=
     "cmake")
         org="uyha"
         ;;
+    "go-mod")
+        # The parser is called "gomod".
+        lang="gomod"
+        namespace="camdencheek"
+        ;;
     "typescript")
         sourcedir="tree-sitter-typescript/typescript/src"
         grammardir="tree-sitter-typescript/typescript"
diff --git a/etc/NEWS b/etc/NEWS
index 662c3125e0..db92b2284b 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -3068,6 +3068,17 @@ A major mode based on the tree-sitter library for editing CMake files.
 It includes support for font-locking, indentation, Imenu, and
 which-func.
 
+** New major mode 'go-ts-mode'.
+A major mode based on the tree-sitter library for editing programs in
+the Go language.  It includes support for font-locking, indentation,
+Imenu, and which-func.  This mode will be auto-enabled for files with
+the ".go" extension.
+
+** New major mode 'go-mod-ts-mode'.
+A major mode based on the tree-sitter library for editing "go.mod"
+files.  It includes support for font-locking and indentation.  This
+mode will be auto-enabled for "go.mod" files.
+
 \f
 * Incompatible Lisp Changes in Emacs 29.1
 
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 9c5a361df7..02bb6bb665 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -211,7 +211,8 @@ eglot-server-programs
                                 (elm-mode . ("elm-language-server"))
                                 (mint-mode . ("mint" "ls"))
                                 (kotlin-mode . ("kotlin-language-server"))
-                                ((go-mode go-dot-mod-mode go-dot-work-mode) . ("gopls"))
+                                ((go-mode go-dot-mod-mode go-dot-work-mode go-ts-mode go-mod-ts-mode)
+                                 . ("gopls"))
                                 ((R-mode ess-r-mode) . ("R" "--slave" "-e"
                                                         "languageserver::run()"))
                                 ((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 0000000000..124d9b044a
--- /dev/null
+++ b/lisp/progmodes/go-ts-mode.el
@@ -0,0 +1,354 @@
+;;; go-ts-mode.el --- tree-sitter support for Go  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2022 Free Software Foundation, Inc.
+
+;; Author     : Randy Taylor <dev@rjt.dev>
+;; Maintainer : Randy Taylor <dev@rjt.dev>
+;; Created    : December 2022
+;; Keywords   : go languages tree-sitter
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+
+;;; Code:
+
+(require 'treesit)
+(eval-when-compile (require 'rx))
+
+(declare-function treesit-parser-create "treesit.c")
+(declare-function treesit-induce-sparse-tree "treesit.c")
+(declare-function treesit-node-child "treesit.c")
+(declare-function treesit-node-child-by-field-name "treesit.c")
+(declare-function treesit-node-start "treesit.c")
+(declare-function treesit-node-type "treesit.c")
+
+(defcustom go-ts-mode-indent-offset 4
+  "Number of spaces for each indentation step in `go-ts-mode'."
+  :version "29.1"
+  :type 'integer
+  :safe 'integerp
+  :group 'go)
+
+(defvar go-ts-mode--syntax-table
+  (let ((table (make-syntax-table)))
+    (modify-syntax-entry ?+   "."      table)
+    (modify-syntax-entry ?-   "."      table)
+    (modify-syntax-entry ?=   "."      table)
+    (modify-syntax-entry ?%   "."      table)
+    (modify-syntax-entry ?&   "."      table)
+    (modify-syntax-entry ?|   "."      table)
+    (modify-syntax-entry ?^   "."      table)
+    (modify-syntax-entry ?!   "."      table)
+    (modify-syntax-entry ?<   "."      table)
+    (modify-syntax-entry ?>   "."      table)
+    (modify-syntax-entry ?\\  "\\"     table)
+    (modify-syntax-entry ?/   ". 124b" table)
+    (modify-syntax-entry ?*   ". 23"   table)
+    (modify-syntax-entry ?\n  "> b"    table)
+    table)
+  "Syntax table for `go-ts-mode'.")
+
+(defvar go-ts-mode--indent-rules
+  `((go
+     ((node-is ")") parent-bol 0)
+     ((node-is "]") parent-bol 0)
+     ((node-is "}") parent-bol 0)
+     ((node-is "labeled_statement") no-indent)
+     ((parent-is "argument_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "block") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "const_declaration") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "default_case") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "expression_case") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "expression_switch_statement") parent-bol 0)
+     ((parent-is "field_declaration_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "import_spec_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "labeled_statement") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "literal_value") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "type_spec") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "var_declaration") parent-bol go-ts-mode-indent-offset)
+     (no-node parent-bol 0)))
+  "Tree-sitter indent rules for `go-ts-mode'.")
+
+(defvar go-ts-mode--keywords
+  '("break" "case" "chan" "const" "continue" "default" "defer" "else"
+    "fallthrough" "for" "func" "go" "goto" "if" "import" "interface" "map"
+    "package" "range" "return" "select" "struct" "switch" "type" "var")
+  "Go keywords for tree-sitter font-locking.")
+
+(defvar go-ts-mode--operators
+  '("+" "&" "+=" "&=" "&&" "==" "!=" "-" "|" "-=" "|=" "||" "<" "<="
+    "*" "^" "*=" "^=" "<-" ">" ">=" "/" "<<" "/=" "<<=" "++" "=" ":=" "%"
+    ">>" "%=" ">>=" "--" "!"  "..."  "&^" "&^=" "~")
+  "Go operators for tree-sitter font-locking.")
+
+(defvar go-ts-mode--font-lock-settings
+  (treesit-font-lock-rules
+   :language 'go
+   :feature 'bracket
+   '((["(" ")" "[" "]" "{" "}"]) @font-lock-bracket-face)
+
+   :language 'go
+   :feature 'comment
+   '((comment) @font-lock-comment-face)
+
+   :language 'go
+   :feature 'constant
+   '([(false) (iota) (nil) (true)] @font-lock-constant-face
+     (const_declaration
+      (const_spec name: (identifier) @font-lock-constant-face)))
+
+   :language 'go
+   :feature 'delimiter
+   '((["," "." ";" ":"]) @font-lock-delimiter-face)
+
+   :language 'go
+   :feature 'function
+   '((call_expression
+      function: (identifier) @font-lock-function-name-face)
+     (call_expression
+      function: (selector_expression
+                 field: (field_identifier) @font-lock-function-name-face))
+     (function_declaration
+      name: (identifier) @font-lock-function-name-face)
+     (method_declaration
+      name: (field_identifier) @font-lock-function-name-face))
+
+   :language 'go
+   :feature 'keyword
+   `([,@go-ts-mode--keywords] @font-lock-keyword-face)
+
+   :language 'go
+   :feature 'label
+   '((label_name) @font-lock-constant-face)
+
+   :language 'go
+   :feature 'number
+   '([(float_literal)
+      (imaginary_literal)
+      (int_literal)] @font-lock-number-face)
+
+   :language 'go
+   :feature 'string
+   '([(interpreted_string_literal)
+      (raw_string_literal)
+      (rune_literal)] @font-lock-string-face)
+
+   :language 'go
+   :feature 'type
+   '([(package_identifier) (type_identifier)] @font-lock-type-face)
+
+   :language 'go
+   :feature 'variable
+   '((identifier) @font-lock-variable-name-face)
+
+   :language 'go
+   :feature 'escape-sequence
+   :override t
+   '((escape_sequence) @font-lock-escape-face)
+
+   :language 'go
+   :feature 'property
+   :override t
+   '((field_identifier) @font-lock-property-face
+     (keyed_element (_ (identifier) @font-lock-property-face)))
+
+   :language 'go
+   :feature 'error
+   :override t
+   '((ERROR) @font-lock-warning-face))
+  "Tree-sitter font-lock settings for `go-ts-mode'.")
+
+(defun go-ts-mode--imenu ()
+  "Return Imenu alist for the current buffer."
+  (let* ((node (treesit-buffer-root-node))
+         (func-tree (treesit-induce-sparse-tree
+                     node "function_declaration" nil 1000))
+         (type-tree (treesit-induce-sparse-tree
+                     node "type_spec" nil 1000))
+         (func-index (go-ts-mode--imenu-1 func-tree))
+         (type-index (go-ts-mode--imenu-1 type-tree)))
+    (append
+     (when func-index `(("Function" . ,func-index)))
+     (when type-index `(("Type" . ,type-index))))))
+
+(defun go-ts-mode--imenu-1 (node)
+  "Helper for `go-ts-mode--imenu'.
+Find string representation for NODE and set marker, then recurse
+the subtrees."
+  (let* ((ts-node (car node))
+         (children (cdr node))
+         (subtrees (mapcan #'go-ts-mode--imenu-1
+                           children))
+         (name (when ts-node
+                 (treesit-node-text
+                  (pcase (treesit-node-type ts-node)
+                    ("function_declaration"
+                     (treesit-node-child-by-field-name ts-node "name"))
+                    ("type_spec"
+                     (treesit-node-child-by-field-name ts-node "name"))))))
+         (marker (when ts-node
+                   (set-marker (make-marker)
+                               (treesit-node-start ts-node)))))
+    (cond
+     ((or (null ts-node) (null name)) subtrees)
+     (subtrees
+      `((,name ,(cons name marker) ,@subtrees)))
+     (t
+      `((,name . ,marker))))))
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.go\\'" . go-ts-mode))
+
+;;;###autoload
+(define-derived-mode go-ts-mode prog-mode "Go"
+  "Major mode for editing Go, powered by tree-sitter."
+  :group 'go
+  :syntax-table go-ts-mode--syntax-table
+
+  (when (treesit-ready-p 'go)
+    (treesit-parser-create 'go)
+
+    ;; Comments.
+    (setq-local comment-start "// ")
+    (setq-local comment-end "")
+    (setq-local comment-start-skip (rx "//" (* (syntax whitespace))))
+
+    ;; Imenu.
+    (setq-local imenu-create-index-function #'go-ts-mode--imenu)
+    (setq-local which-func-functions nil)
+
+    ;; Indent.
+    (setq-local indent-tabs-mode t
+                treesit-simple-indent-rules go-ts-mode--indent-rules)
+
+    ;; Font-lock.
+    (setq-local treesit-font-lock-settings go-ts-mode--font-lock-settings)
+    (setq-local treesit-font-lock-feature-list
+                '(( comment)
+                  ( keyword string type)
+                  ( constant escape-sequence function label number
+                    property variable)
+                  ( bracket delimiter error operator)))
+
+    (treesit-major-mode-setup)))
+
+;; go.mod support.
+
+(defvar go-mod-ts-mode--syntax-table
+  (let ((table (make-syntax-table)))
+    (modify-syntax-entry ?/   ". 124b" table)
+    (modify-syntax-entry ?\n  "> b"    table)
+    table)
+  "Syntax table for `go-mod-ts-mode'.")
+
+(defvar go-mod-ts-mode--indent-rules
+  `((gomod
+     ((node-is ")") parent-bol 0)
+     ((parent-is "exclude_directive") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "module_directive") parent-bol go-ts-mode-indent-offset)
+     ((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)
+     (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 inside a 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)
+        (pcase (treesit-node-type (treesit-node-at (point)))
+          ("exclude" t)
+          ("module" t)
+          ("replace" t)
+          ("require" t)
+          ("retract" t))))))
+
+(defvar go-mod-ts-mode--keywords
+  '("exclude" "go" "module" "replace" "require" "retract")
+  "go.mod keywords for tree-sitter font-locking.")
+
+(defvar go-mod-ts-mode--font-lock-settings
+  (treesit-font-lock-rules
+   :language 'gomod
+   :feature 'bracket
+   '((["(" ")"]) @font-lock-bracket-face)
+
+   :language 'gomod
+   :feature 'comment
+   '((comment) @font-lock-comment-face)
+
+   :language 'gomod
+   :feature 'keyword
+   `([,@go-mod-ts-mode--keywords] @font-lock-keyword-face)
+
+   :language 'gomod
+   :feature 'number
+   '([(go_version) (version)] @font-lock-number-face)
+
+   :language 'gomod
+   :feature 'operator
+   '((["=>"]) @font-lock-operator-face)
+
+   :language 'gomod
+   :feature 'error
+   :override t
+   '((ERROR) @font-lock-warning-face))
+  "Tree-sitter font-lock settings for `go-mod-ts-mode'.")
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("/go\\.mod\\'" . go-mod-ts-mode))
+
+;;;###autoload
+(define-derived-mode go-mod-ts-mode prog-mode "Go Mod"
+  "Major mode for editing go.mod files, powered by tree-sitter."
+  :group 'go
+  :syntax-table go-mod-ts-mode--syntax-table
+
+  (when (treesit-ready-p 'gomod)
+    (treesit-parser-create 'gomod)
+
+    ;; 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-mod-ts-mode--indent-rules)
+
+    ;; Font-lock.
+    (setq-local treesit-font-lock-settings go-mod-ts-mode--font-lock-settings)
+    (setq-local treesit-font-lock-feature-list
+                '((comment)
+                  (keyword)
+                  (number)
+                  (bracket error operator)))
+
+    (treesit-major-mode-setup)))
+
+(provide 'go-ts-mode)
+
+;;; go-ts-mode.el ends here
-- 
2.39.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode
  2022-12-15  2:15               ` Randy Taylor
@ 2022-12-15  7:20                 ` Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2022-12-15 16:40                   ` Randy Taylor
  0 siblings, 1 reply; 20+ messages in thread
From: Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2022-12-15  7:20 UTC (permalink / raw)
  To: Randy Taylor; +Cc: Yuan Fu, Eli Zaretskii, 60025

Randy Taylor <dev@rjt.dev> writes:

> On Wednesday, December 14th, 2022 at 16:27, Theodor Thornhill <theo@thornhill.no> wrote:
>> 
>> Yeah, I seem to remember seeing these \n nodes in the go-mode I made
>> some time ago. There is no node there, so no-node is the rule that
>> matches, as there is no parent. I believe you can solve it with
>> something like
>> 
>> 
>> (defun go-backward-up-list ()
>> (lambda (node parent bol &rest _)
>> (save-excursion
>> (backward-up-list 1 nil t)
>> (back-to-indentation)
>> (point))))
>> 
>> and use some variant of that. Now you can find a different node without
>> relying on there being a node where you start.
>
> Thanks Theo, that worked perfectly. We should consider adding
> something like this to the documentation somewhere, since I didn't see
> anything like this anywhere (although maybe I missed it).

I'm glad!  Could be part of a tips and tricks or something?

[...]

> +(defvar go-ts-mode--indent-rules
> +  `((go
> +     ((node-is ")") parent-bol 0)
> +     ((node-is "]") parent-bol 0)
> +     ((node-is "}") parent-bol 0)
> +     ((node-is "labeled_statement") no-indent)
> +     ((parent-is "argument_list") parent-bol go-ts-mode-indent-offset)
> +     ((parent-is "block") parent-bol go-ts-mode-indent-offset)
> +     ((parent-is "const_declaration") parent-bol go-ts-mode-indent-offset)
> +     ((parent-is "default_case") parent-bol go-ts-mode-indent-offset)
> +     ((parent-is "expression_case") parent-bol go-ts-mode-indent-offset)
> +     ((parent-is "expression_switch_statement") parent-bol 0)
> +     ((parent-is "field_declaration_list") parent-bol go-ts-mode-indent-offset)
> +     ((parent-is "import_spec_list") parent-bol go-ts-mode-indent-offset)
> +     ((parent-is "labeled_statement") parent-bol go-ts-mode-indent-offset)
> +     ((parent-is "literal_value") parent-bol go-ts-mode-indent-offset)
> +     ((parent-is "type_spec") parent-bol go-ts-mode-indent-offset)
> +     ((parent-is "var_declaration") parent-bol go-ts-mode-indent-offset)
> +     (no-node parent-bol 0)))
> +  "Tree-sitter indent rules for `go-ts-mode'.")

No rules for comment?

[...]

> +;; go.mod support.
> +
> +(defvar go-mod-ts-mode--syntax-table
> +  (let ((table (make-syntax-table)))
> +    (modify-syntax-entry ?/   ". 124b" table)
> +    (modify-syntax-entry ?\n  "> b"    table)
> +    table)
> +  "Syntax table for `go-mod-ts-mode'.")
> +
> +(defvar go-mod-ts-mode--indent-rules
> +  `((gomod
> +     ((node-is ")") parent-bol 0)
> +     ((parent-is "exclude_directive") parent-bol go-ts-mode-indent-offset)
> +     ((parent-is "module_directive") parent-bol go-ts-mode-indent-offset)
> +     ((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)
> +     (no-node no-indent 0)))
> +  "Tree-sitter indent rules for `go-mod-ts-mode'.")

No rules for comment here either?

Theo





^ permalink raw reply	[flat|nested] 20+ messages in thread

* bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode
  2022-12-15  7:20                 ` Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2022-12-15 16:40                   ` Randy Taylor
  2022-12-15 18:06                     ` Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 20+ messages in thread
From: Randy Taylor @ 2022-12-15 16:40 UTC (permalink / raw)
  To: Theodor Thornhill; +Cc: Yuan Fu, Eli Zaretskii, 60025

[-- Attachment #1: Type: text/plain, Size: 913 bytes --]

On Thursday, December 15th, 2022 at 02:20, Theodor Thornhill <theo@thornhill.no> wrote:
> 
> Randy Taylor dev@rjt.dev writes:
>
> > 
> > Thanks Theo, that worked perfectly. We should consider adding
> > something like this to the documentation somewhere, since I didn't see
> > anything like this anywhere (although maybe I missed it).
> 
> 
> I'm glad! Could be part of a tips and tricks or something?

I think it should probably be part of whatever indentation documentation there is.

> 
> No rules for comment?
> 
> [...]
>
> No rules for comment here either?

Which rules should I add?

I see these rules in some of the ts modes:
((and (parent-is "comment") comment-end) comment-start -1)
((parent-is "comment") comment-start-skip 0)

What are they matching? When could a comment be a parent?

(BTW attached a new patch with an updated NEWS entry after the recent changes there).

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Add-go-ts-mode-and-go-mod-ts-mode-Bug-60025.patch --]
[-- Type: text/x-patch; name=0001-Add-go-ts-mode-and-go-mod-ts-mode-Bug-60025.patch, Size: 15927 bytes --]

From 214a1b64663c43d9ba7ffa748b5d77316fbc2b12 Mon Sep 17 00:00:00 2001
From: Randy Taylor <dev@rjt.dev>
Date: Sun, 11 Dec 2022 18:41:16 -0500
Subject: [PATCH] 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.
---
 admin/notes/tree-sitter/build-module/batch.sh |   1 +
 admin/notes/tree-sitter/build-module/build.sh |   5 +
 etc/NEWS                                      |   8 +
 lisp/progmodes/eglot.el                       |   3 +-
 lisp/progmodes/go-ts-mode.el                  | 354 ++++++++++++++++++
 5 files changed, 370 insertions(+), 1 deletion(-)
 create mode 100644 lisp/progmodes/go-ts-mode.el

diff --git a/admin/notes/tree-sitter/build-module/batch.sh b/admin/notes/tree-sitter/build-module/batch.sh
index e7ef45cf57..c50b9df37e 100755
--- a/admin/notes/tree-sitter/build-module/batch.sh
+++ b/admin/notes/tree-sitter/build-module/batch.sh
@@ -9,6 +9,7 @@ languages=
     'c-sharp'
     'dockerfile'
     'go'
+    'go-mod'
     'html'
     'javascript'
     'json'
diff --git a/admin/notes/tree-sitter/build-module/build.sh b/admin/notes/tree-sitter/build-module/build.sh
index 4195ea58c3..6d22127bd1 100755
--- a/admin/notes/tree-sitter/build-module/build.sh
+++ b/admin/notes/tree-sitter/build-module/build.sh
@@ -26,6 +26,11 @@ grammardir=
     "cmake")
         org="uyha"
         ;;
+    "go-mod")
+        # The parser is called "gomod".
+        lang="gomod"
+        namespace="camdencheek"
+        ;;
     "typescript")
         sourcedir="tree-sitter-typescript/typescript/src"
         grammardir="tree-sitter-typescript/typescript"
diff --git a/etc/NEWS b/etc/NEWS
index 6f0d1f65bf..121707e2d2 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -3081,6 +3081,14 @@ A major mode based on the tree-sitter library for editing files
 written in TOML, a format for writing configuration files.  It is
 auto-enabled for files with the ".toml" extension.
 
+*** New major mode 'go-ts-mode'.
+A major mode based on the tree-sitter library for editing programs in
+the Go language.  It is auto-enabled for files with the ".go" extension.
+
+*** New major mode 'go-mod-ts-mode'.
+A major mode based on the tree-sitter library for editing "go.mod"
+files.  It is auto-enabled for files which are named "go.mod".
+
 \f
 * Incompatible Lisp Changes in Emacs 29.1
 
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 9c5a361df7..02bb6bb665 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -211,7 +211,8 @@ eglot-server-programs
                                 (elm-mode . ("elm-language-server"))
                                 (mint-mode . ("mint" "ls"))
                                 (kotlin-mode . ("kotlin-language-server"))
-                                ((go-mode go-dot-mod-mode go-dot-work-mode) . ("gopls"))
+                                ((go-mode go-dot-mod-mode go-dot-work-mode go-ts-mode go-mod-ts-mode)
+                                 . ("gopls"))
                                 ((R-mode ess-r-mode) . ("R" "--slave" "-e"
                                                         "languageserver::run()"))
                                 ((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 0000000000..124d9b044a
--- /dev/null
+++ b/lisp/progmodes/go-ts-mode.el
@@ -0,0 +1,354 @@
+;;; go-ts-mode.el --- tree-sitter support for Go  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2022 Free Software Foundation, Inc.
+
+;; Author     : Randy Taylor <dev@rjt.dev>
+;; Maintainer : Randy Taylor <dev@rjt.dev>
+;; Created    : December 2022
+;; Keywords   : go languages tree-sitter
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+
+;;; Code:
+
+(require 'treesit)
+(eval-when-compile (require 'rx))
+
+(declare-function treesit-parser-create "treesit.c")
+(declare-function treesit-induce-sparse-tree "treesit.c")
+(declare-function treesit-node-child "treesit.c")
+(declare-function treesit-node-child-by-field-name "treesit.c")
+(declare-function treesit-node-start "treesit.c")
+(declare-function treesit-node-type "treesit.c")
+
+(defcustom go-ts-mode-indent-offset 4
+  "Number of spaces for each indentation step in `go-ts-mode'."
+  :version "29.1"
+  :type 'integer
+  :safe 'integerp
+  :group 'go)
+
+(defvar go-ts-mode--syntax-table
+  (let ((table (make-syntax-table)))
+    (modify-syntax-entry ?+   "."      table)
+    (modify-syntax-entry ?-   "."      table)
+    (modify-syntax-entry ?=   "."      table)
+    (modify-syntax-entry ?%   "."      table)
+    (modify-syntax-entry ?&   "."      table)
+    (modify-syntax-entry ?|   "."      table)
+    (modify-syntax-entry ?^   "."      table)
+    (modify-syntax-entry ?!   "."      table)
+    (modify-syntax-entry ?<   "."      table)
+    (modify-syntax-entry ?>   "."      table)
+    (modify-syntax-entry ?\\  "\\"     table)
+    (modify-syntax-entry ?/   ". 124b" table)
+    (modify-syntax-entry ?*   ". 23"   table)
+    (modify-syntax-entry ?\n  "> b"    table)
+    table)
+  "Syntax table for `go-ts-mode'.")
+
+(defvar go-ts-mode--indent-rules
+  `((go
+     ((node-is ")") parent-bol 0)
+     ((node-is "]") parent-bol 0)
+     ((node-is "}") parent-bol 0)
+     ((node-is "labeled_statement") no-indent)
+     ((parent-is "argument_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "block") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "const_declaration") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "default_case") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "expression_case") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "expression_switch_statement") parent-bol 0)
+     ((parent-is "field_declaration_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "import_spec_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "labeled_statement") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "literal_value") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "type_spec") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "var_declaration") parent-bol go-ts-mode-indent-offset)
+     (no-node parent-bol 0)))
+  "Tree-sitter indent rules for `go-ts-mode'.")
+
+(defvar go-ts-mode--keywords
+  '("break" "case" "chan" "const" "continue" "default" "defer" "else"
+    "fallthrough" "for" "func" "go" "goto" "if" "import" "interface" "map"
+    "package" "range" "return" "select" "struct" "switch" "type" "var")
+  "Go keywords for tree-sitter font-locking.")
+
+(defvar go-ts-mode--operators
+  '("+" "&" "+=" "&=" "&&" "==" "!=" "-" "|" "-=" "|=" "||" "<" "<="
+    "*" "^" "*=" "^=" "<-" ">" ">=" "/" "<<" "/=" "<<=" "++" "=" ":=" "%"
+    ">>" "%=" ">>=" "--" "!"  "..."  "&^" "&^=" "~")
+  "Go operators for tree-sitter font-locking.")
+
+(defvar go-ts-mode--font-lock-settings
+  (treesit-font-lock-rules
+   :language 'go
+   :feature 'bracket
+   '((["(" ")" "[" "]" "{" "}"]) @font-lock-bracket-face)
+
+   :language 'go
+   :feature 'comment
+   '((comment) @font-lock-comment-face)
+
+   :language 'go
+   :feature 'constant
+   '([(false) (iota) (nil) (true)] @font-lock-constant-face
+     (const_declaration
+      (const_spec name: (identifier) @font-lock-constant-face)))
+
+   :language 'go
+   :feature 'delimiter
+   '((["," "." ";" ":"]) @font-lock-delimiter-face)
+
+   :language 'go
+   :feature 'function
+   '((call_expression
+      function: (identifier) @font-lock-function-name-face)
+     (call_expression
+      function: (selector_expression
+                 field: (field_identifier) @font-lock-function-name-face))
+     (function_declaration
+      name: (identifier) @font-lock-function-name-face)
+     (method_declaration
+      name: (field_identifier) @font-lock-function-name-face))
+
+   :language 'go
+   :feature 'keyword
+   `([,@go-ts-mode--keywords] @font-lock-keyword-face)
+
+   :language 'go
+   :feature 'label
+   '((label_name) @font-lock-constant-face)
+
+   :language 'go
+   :feature 'number
+   '([(float_literal)
+      (imaginary_literal)
+      (int_literal)] @font-lock-number-face)
+
+   :language 'go
+   :feature 'string
+   '([(interpreted_string_literal)
+      (raw_string_literal)
+      (rune_literal)] @font-lock-string-face)
+
+   :language 'go
+   :feature 'type
+   '([(package_identifier) (type_identifier)] @font-lock-type-face)
+
+   :language 'go
+   :feature 'variable
+   '((identifier) @font-lock-variable-name-face)
+
+   :language 'go
+   :feature 'escape-sequence
+   :override t
+   '((escape_sequence) @font-lock-escape-face)
+
+   :language 'go
+   :feature 'property
+   :override t
+   '((field_identifier) @font-lock-property-face
+     (keyed_element (_ (identifier) @font-lock-property-face)))
+
+   :language 'go
+   :feature 'error
+   :override t
+   '((ERROR) @font-lock-warning-face))
+  "Tree-sitter font-lock settings for `go-ts-mode'.")
+
+(defun go-ts-mode--imenu ()
+  "Return Imenu alist for the current buffer."
+  (let* ((node (treesit-buffer-root-node))
+         (func-tree (treesit-induce-sparse-tree
+                     node "function_declaration" nil 1000))
+         (type-tree (treesit-induce-sparse-tree
+                     node "type_spec" nil 1000))
+         (func-index (go-ts-mode--imenu-1 func-tree))
+         (type-index (go-ts-mode--imenu-1 type-tree)))
+    (append
+     (when func-index `(("Function" . ,func-index)))
+     (when type-index `(("Type" . ,type-index))))))
+
+(defun go-ts-mode--imenu-1 (node)
+  "Helper for `go-ts-mode--imenu'.
+Find string representation for NODE and set marker, then recurse
+the subtrees."
+  (let* ((ts-node (car node))
+         (children (cdr node))
+         (subtrees (mapcan #'go-ts-mode--imenu-1
+                           children))
+         (name (when ts-node
+                 (treesit-node-text
+                  (pcase (treesit-node-type ts-node)
+                    ("function_declaration"
+                     (treesit-node-child-by-field-name ts-node "name"))
+                    ("type_spec"
+                     (treesit-node-child-by-field-name ts-node "name"))))))
+         (marker (when ts-node
+                   (set-marker (make-marker)
+                               (treesit-node-start ts-node)))))
+    (cond
+     ((or (null ts-node) (null name)) subtrees)
+     (subtrees
+      `((,name ,(cons name marker) ,@subtrees)))
+     (t
+      `((,name . ,marker))))))
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.go\\'" . go-ts-mode))
+
+;;;###autoload
+(define-derived-mode go-ts-mode prog-mode "Go"
+  "Major mode for editing Go, powered by tree-sitter."
+  :group 'go
+  :syntax-table go-ts-mode--syntax-table
+
+  (when (treesit-ready-p 'go)
+    (treesit-parser-create 'go)
+
+    ;; Comments.
+    (setq-local comment-start "// ")
+    (setq-local comment-end "")
+    (setq-local comment-start-skip (rx "//" (* (syntax whitespace))))
+
+    ;; Imenu.
+    (setq-local imenu-create-index-function #'go-ts-mode--imenu)
+    (setq-local which-func-functions nil)
+
+    ;; Indent.
+    (setq-local indent-tabs-mode t
+                treesit-simple-indent-rules go-ts-mode--indent-rules)
+
+    ;; Font-lock.
+    (setq-local treesit-font-lock-settings go-ts-mode--font-lock-settings)
+    (setq-local treesit-font-lock-feature-list
+                '(( comment)
+                  ( keyword string type)
+                  ( constant escape-sequence function label number
+                    property variable)
+                  ( bracket delimiter error operator)))
+
+    (treesit-major-mode-setup)))
+
+;; go.mod support.
+
+(defvar go-mod-ts-mode--syntax-table
+  (let ((table (make-syntax-table)))
+    (modify-syntax-entry ?/   ". 124b" table)
+    (modify-syntax-entry ?\n  "> b"    table)
+    table)
+  "Syntax table for `go-mod-ts-mode'.")
+
+(defvar go-mod-ts-mode--indent-rules
+  `((gomod
+     ((node-is ")") parent-bol 0)
+     ((parent-is "exclude_directive") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "module_directive") parent-bol go-ts-mode-indent-offset)
+     ((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)
+     (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 inside a 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)
+        (pcase (treesit-node-type (treesit-node-at (point)))
+          ("exclude" t)
+          ("module" t)
+          ("replace" t)
+          ("require" t)
+          ("retract" t))))))
+
+(defvar go-mod-ts-mode--keywords
+  '("exclude" "go" "module" "replace" "require" "retract")
+  "go.mod keywords for tree-sitter font-locking.")
+
+(defvar go-mod-ts-mode--font-lock-settings
+  (treesit-font-lock-rules
+   :language 'gomod
+   :feature 'bracket
+   '((["(" ")"]) @font-lock-bracket-face)
+
+   :language 'gomod
+   :feature 'comment
+   '((comment) @font-lock-comment-face)
+
+   :language 'gomod
+   :feature 'keyword
+   `([,@go-mod-ts-mode--keywords] @font-lock-keyword-face)
+
+   :language 'gomod
+   :feature 'number
+   '([(go_version) (version)] @font-lock-number-face)
+
+   :language 'gomod
+   :feature 'operator
+   '((["=>"]) @font-lock-operator-face)
+
+   :language 'gomod
+   :feature 'error
+   :override t
+   '((ERROR) @font-lock-warning-face))
+  "Tree-sitter font-lock settings for `go-mod-ts-mode'.")
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("/go\\.mod\\'" . go-mod-ts-mode))
+
+;;;###autoload
+(define-derived-mode go-mod-ts-mode prog-mode "Go Mod"
+  "Major mode for editing go.mod files, powered by tree-sitter."
+  :group 'go
+  :syntax-table go-mod-ts-mode--syntax-table
+
+  (when (treesit-ready-p 'gomod)
+    (treesit-parser-create 'gomod)
+
+    ;; 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-mod-ts-mode--indent-rules)
+
+    ;; Font-lock.
+    (setq-local treesit-font-lock-settings go-mod-ts-mode--font-lock-settings)
+    (setq-local treesit-font-lock-feature-list
+                '((comment)
+                  (keyword)
+                  (number)
+                  (bracket error operator)))
+
+    (treesit-major-mode-setup)))
+
+(provide 'go-ts-mode)
+
+;;; go-ts-mode.el ends here
-- 
2.39.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode
  2022-12-15 16:40                   ` Randy Taylor
@ 2022-12-15 18:06                     ` Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2022-12-15 19:45                       ` Randy Taylor
  0 siblings, 1 reply; 20+ messages in thread
From: Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2022-12-15 18:06 UTC (permalink / raw)
  To: Randy Taylor; +Cc: Yuan Fu, Eli Zaretskii, 60025



On 15 December 2022 17:40:54 CET, Randy Taylor <dev@rjt.dev> wrote:
>On Thursday, December 15th, 2022 at 02:20, Theodor Thornhill <theo@thornhill.no> wrote:
>> 
>> Randy Taylor dev@rjt.dev writes:
>>
>> > 
>> > Thanks Theo, that worked perfectly. We should consider adding
>> > something like this to the documentation somewhere, since I didn't see
>> > anything like this anywhere (although maybe I missed it).
>> 
>> 
>> I'm glad! Could be part of a tips and tricks or something?
>
>I think it should probably be part of whatever indentation documentation there is.
>
>> 
>> No rules for comment?
>> 
>> [...]
>>
>> No rules for comment here either?
>
>Which rules should I add?
>

I like to set no-indent in a comment so that indentation commands don't format. That should be fillings job, imo :)


>I see these rules in some of the ts modes:
>((and (parent-is "comment") comment-end) comment-start -1)
>((parent-is "comment") comment-start-skip 0)
>
>What are they matching? When could a comment be a parent?

In some languages comment has a parent which is a comment. Just use inspect-mode inside of a comment and see what makes sense :)

>
>(BTW attached a new patch with an updated NEWS entry after the recent changes there).





^ permalink raw reply	[flat|nested] 20+ messages in thread

* bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode
  2022-12-15 18:06                     ` Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2022-12-15 19:45                       ` Randy Taylor
  2022-12-15 19:59                         ` Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 20+ messages in thread
From: Randy Taylor @ 2022-12-15 19:45 UTC (permalink / raw)
  To: Theodor Thornhill; +Cc: Yuan Fu, Eli Zaretskii, 60025

On Thursday, December 15th, 2022 at 13:06, Theodor Thornhill <theo@thornhill.no> wrote:
> 
> On 15 December 2022 17:40:54 CET, Randy Taylor dev@rjt.dev wrote:
> 
> > Which rules should I add?
> 
> 
> I like to set no-indent in a comment so that indentation commands don't format. That should be fillings job, imo :)

I don't know if I agree with that since the indentation commands should format comments if they are formatted incorrectly. 

Fillings as in auto-fill-mode and the fill commands? I'm not familiar with those, beyond invoking fill-region and whatnot every now and then. I don't think any of that stuff is on by default though, right? It all requires manual invocation?

> 
> > I see these rules in some of the ts modes:
> > ((and (parent-is "comment") comment-end) comment-start -1)
> > ((parent-is "comment") comment-start-skip 0)
> > 
> > What are they matching? When could a comment be a parent?
> 
> 
> In some languages comment has a parent which is a comment. Just use inspect-mode inside of a comment and see what makes sense :)

c-ts-mode has those queries and I was never able to get a comment node as a parent so I guess they were either copy-pasted mistakenly or I am missing the magical invocation?

Aside from the aforementioned queries, I don't see any other comment-related indentation queries in any of the modes, and I can't think of any that should be introduced, at least for these modes.

Patch OK to install otherwise?





^ permalink raw reply	[flat|nested] 20+ messages in thread

* bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode
  2022-12-15 19:45                       ` Randy Taylor
@ 2022-12-15 19:59                         ` Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2022-12-15 20:09                           ` Randy Taylor
  0 siblings, 1 reply; 20+ messages in thread
From: Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2022-12-15 19:59 UTC (permalink / raw)
  To: Randy Taylor; +Cc: Yuan Fu, Eli Zaretskii, 60025

Randy Taylor <dev@rjt.dev> writes:

> On Thursday, December 15th, 2022 at 13:06, Theodor Thornhill <theo@thornhill.no> wrote:
>> 
>> On 15 December 2022 17:40:54 CET, Randy Taylor dev@rjt.dev wrote:
>> 
>> > Which rules should I add?
>> 
>> 
>> I like to set no-indent in a comment so that indentation commands don't format. That should be fillings job, imo :)
>
> I don't know if I agree with that since the indentation commands should format comments if they are formatted incorrectly. 
>
> Fillings as in auto-fill-mode and the fill commands? I'm not familiar with those, beyond invoking fill-region and whatnot every now and then. I don't think any of that stuff is on by default though, right? It all requires manual invocation?
>
>> 
>> > I see these rules in some of the ts modes:
>> > ((and (parent-is "comment") comment-end) comment-start -1)
>> > ((parent-is "comment") comment-start-skip 0)
>> > 
>> > What are they matching? When could a comment be a parent?
>> 
>> 
>> In some languages comment has a parent which is a comment. Just use inspect-mode inside of a comment and see what makes sense :)
>
> c-ts-mode has those queries and I was never able to get a comment node as a parent so I guess they were either copy-pasted mistakenly or I am missing the magical invocation?
>
> Aside from the aforementioned queries, I don't see any other comment-related indentation queries in any of the modes, and I can't think of any that should be introduced, at least for these modes.
>
> Patch OK to install otherwise?

Sure :-)

Theo





^ permalink raw reply	[flat|nested] 20+ messages in thread

* bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode
  2022-12-15 19:59                         ` Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2022-12-15 20:09                           ` Randy Taylor
  2022-12-15 22:22                             ` Randy Taylor
  0 siblings, 1 reply; 20+ messages in thread
From: Randy Taylor @ 2022-12-15 20:09 UTC (permalink / raw)
  To: Yuan Fu; +Cc: Eli Zaretskii, Theodor Thornhill, 60025

[-- Attachment #1: Type: text/plain, Size: 399 bytes --]

On Thursday, December 15th, 2022 at 14:59, Theodor Thornhill <theo@thornhill.no> wrote:
> Randy Taylor dev@rjt.dev writes:
> 
> > Patch OK to install otherwise?
> 
> 
> Sure :-)
> 
> Theo

Thanks.

Yuan, I've attached the latest and greatest patch (same as the one a few threads up but no need to make you hunt for it ;)). Ready to install whenever, unless anyone has any other comments.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Add-go-ts-mode-and-go-mod-ts-mode-Bug-60025.patch --]
[-- Type: text/x-patch; name=0001-Add-go-ts-mode-and-go-mod-ts-mode-Bug-60025.patch, Size: 15927 bytes --]

From 214a1b64663c43d9ba7ffa748b5d77316fbc2b12 Mon Sep 17 00:00:00 2001
From: Randy Taylor <dev@rjt.dev>
Date: Sun, 11 Dec 2022 18:41:16 -0500
Subject: [PATCH] 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.
---
 admin/notes/tree-sitter/build-module/batch.sh |   1 +
 admin/notes/tree-sitter/build-module/build.sh |   5 +
 etc/NEWS                                      |   8 +
 lisp/progmodes/eglot.el                       |   3 +-
 lisp/progmodes/go-ts-mode.el                  | 354 ++++++++++++++++++
 5 files changed, 370 insertions(+), 1 deletion(-)
 create mode 100644 lisp/progmodes/go-ts-mode.el

diff --git a/admin/notes/tree-sitter/build-module/batch.sh b/admin/notes/tree-sitter/build-module/batch.sh
index e7ef45cf57..c50b9df37e 100755
--- a/admin/notes/tree-sitter/build-module/batch.sh
+++ b/admin/notes/tree-sitter/build-module/batch.sh
@@ -9,6 +9,7 @@ languages=
     'c-sharp'
     'dockerfile'
     'go'
+    'go-mod'
     'html'
     'javascript'
     'json'
diff --git a/admin/notes/tree-sitter/build-module/build.sh b/admin/notes/tree-sitter/build-module/build.sh
index 4195ea58c3..6d22127bd1 100755
--- a/admin/notes/tree-sitter/build-module/build.sh
+++ b/admin/notes/tree-sitter/build-module/build.sh
@@ -26,6 +26,11 @@ grammardir=
     "cmake")
         org="uyha"
         ;;
+    "go-mod")
+        # The parser is called "gomod".
+        lang="gomod"
+        namespace="camdencheek"
+        ;;
     "typescript")
         sourcedir="tree-sitter-typescript/typescript/src"
         grammardir="tree-sitter-typescript/typescript"
diff --git a/etc/NEWS b/etc/NEWS
index 6f0d1f65bf..121707e2d2 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -3081,6 +3081,14 @@ A major mode based on the tree-sitter library for editing files
 written in TOML, a format for writing configuration files.  It is
 auto-enabled for files with the ".toml" extension.
 
+*** New major mode 'go-ts-mode'.
+A major mode based on the tree-sitter library for editing programs in
+the Go language.  It is auto-enabled for files with the ".go" extension.
+
+*** New major mode 'go-mod-ts-mode'.
+A major mode based on the tree-sitter library for editing "go.mod"
+files.  It is auto-enabled for files which are named "go.mod".
+
 \f
 * Incompatible Lisp Changes in Emacs 29.1
 
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 9c5a361df7..02bb6bb665 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -211,7 +211,8 @@ eglot-server-programs
                                 (elm-mode . ("elm-language-server"))
                                 (mint-mode . ("mint" "ls"))
                                 (kotlin-mode . ("kotlin-language-server"))
-                                ((go-mode go-dot-mod-mode go-dot-work-mode) . ("gopls"))
+                                ((go-mode go-dot-mod-mode go-dot-work-mode go-ts-mode go-mod-ts-mode)
+                                 . ("gopls"))
                                 ((R-mode ess-r-mode) . ("R" "--slave" "-e"
                                                         "languageserver::run()"))
                                 ((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 0000000000..124d9b044a
--- /dev/null
+++ b/lisp/progmodes/go-ts-mode.el
@@ -0,0 +1,354 @@
+;;; go-ts-mode.el --- tree-sitter support for Go  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2022 Free Software Foundation, Inc.
+
+;; Author     : Randy Taylor <dev@rjt.dev>
+;; Maintainer : Randy Taylor <dev@rjt.dev>
+;; Created    : December 2022
+;; Keywords   : go languages tree-sitter
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+
+;;; Code:
+
+(require 'treesit)
+(eval-when-compile (require 'rx))
+
+(declare-function treesit-parser-create "treesit.c")
+(declare-function treesit-induce-sparse-tree "treesit.c")
+(declare-function treesit-node-child "treesit.c")
+(declare-function treesit-node-child-by-field-name "treesit.c")
+(declare-function treesit-node-start "treesit.c")
+(declare-function treesit-node-type "treesit.c")
+
+(defcustom go-ts-mode-indent-offset 4
+  "Number of spaces for each indentation step in `go-ts-mode'."
+  :version "29.1"
+  :type 'integer
+  :safe 'integerp
+  :group 'go)
+
+(defvar go-ts-mode--syntax-table
+  (let ((table (make-syntax-table)))
+    (modify-syntax-entry ?+   "."      table)
+    (modify-syntax-entry ?-   "."      table)
+    (modify-syntax-entry ?=   "."      table)
+    (modify-syntax-entry ?%   "."      table)
+    (modify-syntax-entry ?&   "."      table)
+    (modify-syntax-entry ?|   "."      table)
+    (modify-syntax-entry ?^   "."      table)
+    (modify-syntax-entry ?!   "."      table)
+    (modify-syntax-entry ?<   "."      table)
+    (modify-syntax-entry ?>   "."      table)
+    (modify-syntax-entry ?\\  "\\"     table)
+    (modify-syntax-entry ?/   ". 124b" table)
+    (modify-syntax-entry ?*   ". 23"   table)
+    (modify-syntax-entry ?\n  "> b"    table)
+    table)
+  "Syntax table for `go-ts-mode'.")
+
+(defvar go-ts-mode--indent-rules
+  `((go
+     ((node-is ")") parent-bol 0)
+     ((node-is "]") parent-bol 0)
+     ((node-is "}") parent-bol 0)
+     ((node-is "labeled_statement") no-indent)
+     ((parent-is "argument_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "block") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "const_declaration") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "default_case") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "expression_case") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "expression_switch_statement") parent-bol 0)
+     ((parent-is "field_declaration_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "import_spec_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "labeled_statement") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "literal_value") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "type_spec") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "var_declaration") parent-bol go-ts-mode-indent-offset)
+     (no-node parent-bol 0)))
+  "Tree-sitter indent rules for `go-ts-mode'.")
+
+(defvar go-ts-mode--keywords
+  '("break" "case" "chan" "const" "continue" "default" "defer" "else"
+    "fallthrough" "for" "func" "go" "goto" "if" "import" "interface" "map"
+    "package" "range" "return" "select" "struct" "switch" "type" "var")
+  "Go keywords for tree-sitter font-locking.")
+
+(defvar go-ts-mode--operators
+  '("+" "&" "+=" "&=" "&&" "==" "!=" "-" "|" "-=" "|=" "||" "<" "<="
+    "*" "^" "*=" "^=" "<-" ">" ">=" "/" "<<" "/=" "<<=" "++" "=" ":=" "%"
+    ">>" "%=" ">>=" "--" "!"  "..."  "&^" "&^=" "~")
+  "Go operators for tree-sitter font-locking.")
+
+(defvar go-ts-mode--font-lock-settings
+  (treesit-font-lock-rules
+   :language 'go
+   :feature 'bracket
+   '((["(" ")" "[" "]" "{" "}"]) @font-lock-bracket-face)
+
+   :language 'go
+   :feature 'comment
+   '((comment) @font-lock-comment-face)
+
+   :language 'go
+   :feature 'constant
+   '([(false) (iota) (nil) (true)] @font-lock-constant-face
+     (const_declaration
+      (const_spec name: (identifier) @font-lock-constant-face)))
+
+   :language 'go
+   :feature 'delimiter
+   '((["," "." ";" ":"]) @font-lock-delimiter-face)
+
+   :language 'go
+   :feature 'function
+   '((call_expression
+      function: (identifier) @font-lock-function-name-face)
+     (call_expression
+      function: (selector_expression
+                 field: (field_identifier) @font-lock-function-name-face))
+     (function_declaration
+      name: (identifier) @font-lock-function-name-face)
+     (method_declaration
+      name: (field_identifier) @font-lock-function-name-face))
+
+   :language 'go
+   :feature 'keyword
+   `([,@go-ts-mode--keywords] @font-lock-keyword-face)
+
+   :language 'go
+   :feature 'label
+   '((label_name) @font-lock-constant-face)
+
+   :language 'go
+   :feature 'number
+   '([(float_literal)
+      (imaginary_literal)
+      (int_literal)] @font-lock-number-face)
+
+   :language 'go
+   :feature 'string
+   '([(interpreted_string_literal)
+      (raw_string_literal)
+      (rune_literal)] @font-lock-string-face)
+
+   :language 'go
+   :feature 'type
+   '([(package_identifier) (type_identifier)] @font-lock-type-face)
+
+   :language 'go
+   :feature 'variable
+   '((identifier) @font-lock-variable-name-face)
+
+   :language 'go
+   :feature 'escape-sequence
+   :override t
+   '((escape_sequence) @font-lock-escape-face)
+
+   :language 'go
+   :feature 'property
+   :override t
+   '((field_identifier) @font-lock-property-face
+     (keyed_element (_ (identifier) @font-lock-property-face)))
+
+   :language 'go
+   :feature 'error
+   :override t
+   '((ERROR) @font-lock-warning-face))
+  "Tree-sitter font-lock settings for `go-ts-mode'.")
+
+(defun go-ts-mode--imenu ()
+  "Return Imenu alist for the current buffer."
+  (let* ((node (treesit-buffer-root-node))
+         (func-tree (treesit-induce-sparse-tree
+                     node "function_declaration" nil 1000))
+         (type-tree (treesit-induce-sparse-tree
+                     node "type_spec" nil 1000))
+         (func-index (go-ts-mode--imenu-1 func-tree))
+         (type-index (go-ts-mode--imenu-1 type-tree)))
+    (append
+     (when func-index `(("Function" . ,func-index)))
+     (when type-index `(("Type" . ,type-index))))))
+
+(defun go-ts-mode--imenu-1 (node)
+  "Helper for `go-ts-mode--imenu'.
+Find string representation for NODE and set marker, then recurse
+the subtrees."
+  (let* ((ts-node (car node))
+         (children (cdr node))
+         (subtrees (mapcan #'go-ts-mode--imenu-1
+                           children))
+         (name (when ts-node
+                 (treesit-node-text
+                  (pcase (treesit-node-type ts-node)
+                    ("function_declaration"
+                     (treesit-node-child-by-field-name ts-node "name"))
+                    ("type_spec"
+                     (treesit-node-child-by-field-name ts-node "name"))))))
+         (marker (when ts-node
+                   (set-marker (make-marker)
+                               (treesit-node-start ts-node)))))
+    (cond
+     ((or (null ts-node) (null name)) subtrees)
+     (subtrees
+      `((,name ,(cons name marker) ,@subtrees)))
+     (t
+      `((,name . ,marker))))))
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.go\\'" . go-ts-mode))
+
+;;;###autoload
+(define-derived-mode go-ts-mode prog-mode "Go"
+  "Major mode for editing Go, powered by tree-sitter."
+  :group 'go
+  :syntax-table go-ts-mode--syntax-table
+
+  (when (treesit-ready-p 'go)
+    (treesit-parser-create 'go)
+
+    ;; Comments.
+    (setq-local comment-start "// ")
+    (setq-local comment-end "")
+    (setq-local comment-start-skip (rx "//" (* (syntax whitespace))))
+
+    ;; Imenu.
+    (setq-local imenu-create-index-function #'go-ts-mode--imenu)
+    (setq-local which-func-functions nil)
+
+    ;; Indent.
+    (setq-local indent-tabs-mode t
+                treesit-simple-indent-rules go-ts-mode--indent-rules)
+
+    ;; Font-lock.
+    (setq-local treesit-font-lock-settings go-ts-mode--font-lock-settings)
+    (setq-local treesit-font-lock-feature-list
+                '(( comment)
+                  ( keyword string type)
+                  ( constant escape-sequence function label number
+                    property variable)
+                  ( bracket delimiter error operator)))
+
+    (treesit-major-mode-setup)))
+
+;; go.mod support.
+
+(defvar go-mod-ts-mode--syntax-table
+  (let ((table (make-syntax-table)))
+    (modify-syntax-entry ?/   ". 124b" table)
+    (modify-syntax-entry ?\n  "> b"    table)
+    table)
+  "Syntax table for `go-mod-ts-mode'.")
+
+(defvar go-mod-ts-mode--indent-rules
+  `((gomod
+     ((node-is ")") parent-bol 0)
+     ((parent-is "exclude_directive") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "module_directive") parent-bol go-ts-mode-indent-offset)
+     ((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)
+     (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 inside a 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)
+        (pcase (treesit-node-type (treesit-node-at (point)))
+          ("exclude" t)
+          ("module" t)
+          ("replace" t)
+          ("require" t)
+          ("retract" t))))))
+
+(defvar go-mod-ts-mode--keywords
+  '("exclude" "go" "module" "replace" "require" "retract")
+  "go.mod keywords for tree-sitter font-locking.")
+
+(defvar go-mod-ts-mode--font-lock-settings
+  (treesit-font-lock-rules
+   :language 'gomod
+   :feature 'bracket
+   '((["(" ")"]) @font-lock-bracket-face)
+
+   :language 'gomod
+   :feature 'comment
+   '((comment) @font-lock-comment-face)
+
+   :language 'gomod
+   :feature 'keyword
+   `([,@go-mod-ts-mode--keywords] @font-lock-keyword-face)
+
+   :language 'gomod
+   :feature 'number
+   '([(go_version) (version)] @font-lock-number-face)
+
+   :language 'gomod
+   :feature 'operator
+   '((["=>"]) @font-lock-operator-face)
+
+   :language 'gomod
+   :feature 'error
+   :override t
+   '((ERROR) @font-lock-warning-face))
+  "Tree-sitter font-lock settings for `go-mod-ts-mode'.")
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("/go\\.mod\\'" . go-mod-ts-mode))
+
+;;;###autoload
+(define-derived-mode go-mod-ts-mode prog-mode "Go Mod"
+  "Major mode for editing go.mod files, powered by tree-sitter."
+  :group 'go
+  :syntax-table go-mod-ts-mode--syntax-table
+
+  (when (treesit-ready-p 'gomod)
+    (treesit-parser-create 'gomod)
+
+    ;; 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-mod-ts-mode--indent-rules)
+
+    ;; Font-lock.
+    (setq-local treesit-font-lock-settings go-mod-ts-mode--font-lock-settings)
+    (setq-local treesit-font-lock-feature-list
+                '((comment)
+                  (keyword)
+                  (number)
+                  (bracket error operator)))
+
+    (treesit-major-mode-setup)))
+
+(provide 'go-ts-mode)
+
+;;; go-ts-mode.el ends here
-- 
2.39.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode
  2022-12-15 20:09                           ` Randy Taylor
@ 2022-12-15 22:22                             ` Randy Taylor
  0 siblings, 0 replies; 20+ messages in thread
From: Randy Taylor @ 2022-12-15 22:22 UTC (permalink / raw)
  To: Yuan Fu; +Cc: Eli Zaretskii, Theodor Thornhill, 60025

[-- Attachment #1: Type: text/plain, Size: 655 bytes --]

On Thursday, December 15th, 2022 at 15:09, Randy Taylor <dev@rjt.dev> wrote:
> 
> On Thursday, December 15th, 2022 at 14:59, Theodor Thornhill theo@thornhill.no wrote:
> 
> > Randy Taylor dev@rjt.dev writes:
> > 
> > > Patch OK to install otherwise?
> > 
> > Sure :-)
> > 
> > Theo
> 
> 
> Thanks.
> 
> Yuan, I've attached the latest and greatest patch (same as the one a few threads up but no need to make you hunt for it ;)). Ready to install whenever, unless anyone has any other comments.

Sorry, THIS attached patch is the latest and greatest patch. build.sh was changed recently renaming namespace to org, so I made that adjustment.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Add-go-ts-mode-and-go-mod-ts-mode-Bug-60025.patch --]
[-- Type: text/x-patch; name=0001-Add-go-ts-mode-and-go-mod-ts-mode-Bug-60025.patch, Size: 15921 bytes --]

From 3a4327fc331c5d9876734a4be210668bb5957119 Mon Sep 17 00:00:00 2001
From: Randy Taylor <dev@rjt.dev>
Date: Sun, 11 Dec 2022 18:41:16 -0500
Subject: [PATCH] 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.
---
 admin/notes/tree-sitter/build-module/batch.sh |   1 +
 admin/notes/tree-sitter/build-module/build.sh |   5 +
 etc/NEWS                                      |   8 +
 lisp/progmodes/eglot.el                       |   3 +-
 lisp/progmodes/go-ts-mode.el                  | 354 ++++++++++++++++++
 5 files changed, 370 insertions(+), 1 deletion(-)
 create mode 100644 lisp/progmodes/go-ts-mode.el

diff --git a/admin/notes/tree-sitter/build-module/batch.sh b/admin/notes/tree-sitter/build-module/batch.sh
index e7ef45cf57..c50b9df37e 100755
--- a/admin/notes/tree-sitter/build-module/batch.sh
+++ b/admin/notes/tree-sitter/build-module/batch.sh
@@ -9,6 +9,7 @@ languages=
     'c-sharp'
     'dockerfile'
     'go'
+    'go-mod'
     'html'
     'javascript'
     'json'
diff --git a/admin/notes/tree-sitter/build-module/build.sh b/admin/notes/tree-sitter/build-module/build.sh
index 4195ea58c3..b6c83ea9b9 100755
--- a/admin/notes/tree-sitter/build-module/build.sh
+++ b/admin/notes/tree-sitter/build-module/build.sh
@@ -26,6 +26,11 @@ grammardir=
     "cmake")
         org="uyha"
         ;;
+    "go-mod")
+        # The parser is called "gomod".
+        lang="gomod"
+        org="camdencheek"
+        ;;
     "typescript")
         sourcedir="tree-sitter-typescript/typescript/src"
         grammardir="tree-sitter-typescript/typescript"
diff --git a/etc/NEWS b/etc/NEWS
index 701e414fdb..dd11b3c271 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -3081,6 +3081,14 @@ A major mode based on the tree-sitter library for editing files
 written in TOML, a format for writing configuration files.  It is
 auto-enabled for files with the ".toml" extension.
 
+*** New major mode 'go-ts-mode'.
+A major mode based on the tree-sitter library for editing programs in
+the Go language.  It is auto-enabled for files with the ".go" extension.
+
+*** New major mode 'go-mod-ts-mode'.
+A major mode based on the tree-sitter library for editing "go.mod"
+files.  It is auto-enabled for files which are named "go.mod".
+
 \f
 * Incompatible Lisp Changes in Emacs 29.1
 
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 9c5a361df7..02bb6bb665 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -211,7 +211,8 @@ eglot-server-programs
                                 (elm-mode . ("elm-language-server"))
                                 (mint-mode . ("mint" "ls"))
                                 (kotlin-mode . ("kotlin-language-server"))
-                                ((go-mode go-dot-mod-mode go-dot-work-mode) . ("gopls"))
+                                ((go-mode go-dot-mod-mode go-dot-work-mode go-ts-mode go-mod-ts-mode)
+                                 . ("gopls"))
                                 ((R-mode ess-r-mode) . ("R" "--slave" "-e"
                                                         "languageserver::run()"))
                                 ((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 0000000000..124d9b044a
--- /dev/null
+++ b/lisp/progmodes/go-ts-mode.el
@@ -0,0 +1,354 @@
+;;; go-ts-mode.el --- tree-sitter support for Go  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2022 Free Software Foundation, Inc.
+
+;; Author     : Randy Taylor <dev@rjt.dev>
+;; Maintainer : Randy Taylor <dev@rjt.dev>
+;; Created    : December 2022
+;; Keywords   : go languages tree-sitter
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+
+;;; Code:
+
+(require 'treesit)
+(eval-when-compile (require 'rx))
+
+(declare-function treesit-parser-create "treesit.c")
+(declare-function treesit-induce-sparse-tree "treesit.c")
+(declare-function treesit-node-child "treesit.c")
+(declare-function treesit-node-child-by-field-name "treesit.c")
+(declare-function treesit-node-start "treesit.c")
+(declare-function treesit-node-type "treesit.c")
+
+(defcustom go-ts-mode-indent-offset 4
+  "Number of spaces for each indentation step in `go-ts-mode'."
+  :version "29.1"
+  :type 'integer
+  :safe 'integerp
+  :group 'go)
+
+(defvar go-ts-mode--syntax-table
+  (let ((table (make-syntax-table)))
+    (modify-syntax-entry ?+   "."      table)
+    (modify-syntax-entry ?-   "."      table)
+    (modify-syntax-entry ?=   "."      table)
+    (modify-syntax-entry ?%   "."      table)
+    (modify-syntax-entry ?&   "."      table)
+    (modify-syntax-entry ?|   "."      table)
+    (modify-syntax-entry ?^   "."      table)
+    (modify-syntax-entry ?!   "."      table)
+    (modify-syntax-entry ?<   "."      table)
+    (modify-syntax-entry ?>   "."      table)
+    (modify-syntax-entry ?\\  "\\"     table)
+    (modify-syntax-entry ?/   ". 124b" table)
+    (modify-syntax-entry ?*   ". 23"   table)
+    (modify-syntax-entry ?\n  "> b"    table)
+    table)
+  "Syntax table for `go-ts-mode'.")
+
+(defvar go-ts-mode--indent-rules
+  `((go
+     ((node-is ")") parent-bol 0)
+     ((node-is "]") parent-bol 0)
+     ((node-is "}") parent-bol 0)
+     ((node-is "labeled_statement") no-indent)
+     ((parent-is "argument_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "block") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "const_declaration") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "default_case") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "expression_case") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "expression_switch_statement") parent-bol 0)
+     ((parent-is "field_declaration_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "import_spec_list") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "labeled_statement") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "literal_value") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "type_spec") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "var_declaration") parent-bol go-ts-mode-indent-offset)
+     (no-node parent-bol 0)))
+  "Tree-sitter indent rules for `go-ts-mode'.")
+
+(defvar go-ts-mode--keywords
+  '("break" "case" "chan" "const" "continue" "default" "defer" "else"
+    "fallthrough" "for" "func" "go" "goto" "if" "import" "interface" "map"
+    "package" "range" "return" "select" "struct" "switch" "type" "var")
+  "Go keywords for tree-sitter font-locking.")
+
+(defvar go-ts-mode--operators
+  '("+" "&" "+=" "&=" "&&" "==" "!=" "-" "|" "-=" "|=" "||" "<" "<="
+    "*" "^" "*=" "^=" "<-" ">" ">=" "/" "<<" "/=" "<<=" "++" "=" ":=" "%"
+    ">>" "%=" ">>=" "--" "!"  "..."  "&^" "&^=" "~")
+  "Go operators for tree-sitter font-locking.")
+
+(defvar go-ts-mode--font-lock-settings
+  (treesit-font-lock-rules
+   :language 'go
+   :feature 'bracket
+   '((["(" ")" "[" "]" "{" "}"]) @font-lock-bracket-face)
+
+   :language 'go
+   :feature 'comment
+   '((comment) @font-lock-comment-face)
+
+   :language 'go
+   :feature 'constant
+   '([(false) (iota) (nil) (true)] @font-lock-constant-face
+     (const_declaration
+      (const_spec name: (identifier) @font-lock-constant-face)))
+
+   :language 'go
+   :feature 'delimiter
+   '((["," "." ";" ":"]) @font-lock-delimiter-face)
+
+   :language 'go
+   :feature 'function
+   '((call_expression
+      function: (identifier) @font-lock-function-name-face)
+     (call_expression
+      function: (selector_expression
+                 field: (field_identifier) @font-lock-function-name-face))
+     (function_declaration
+      name: (identifier) @font-lock-function-name-face)
+     (method_declaration
+      name: (field_identifier) @font-lock-function-name-face))
+
+   :language 'go
+   :feature 'keyword
+   `([,@go-ts-mode--keywords] @font-lock-keyword-face)
+
+   :language 'go
+   :feature 'label
+   '((label_name) @font-lock-constant-face)
+
+   :language 'go
+   :feature 'number
+   '([(float_literal)
+      (imaginary_literal)
+      (int_literal)] @font-lock-number-face)
+
+   :language 'go
+   :feature 'string
+   '([(interpreted_string_literal)
+      (raw_string_literal)
+      (rune_literal)] @font-lock-string-face)
+
+   :language 'go
+   :feature 'type
+   '([(package_identifier) (type_identifier)] @font-lock-type-face)
+
+   :language 'go
+   :feature 'variable
+   '((identifier) @font-lock-variable-name-face)
+
+   :language 'go
+   :feature 'escape-sequence
+   :override t
+   '((escape_sequence) @font-lock-escape-face)
+
+   :language 'go
+   :feature 'property
+   :override t
+   '((field_identifier) @font-lock-property-face
+     (keyed_element (_ (identifier) @font-lock-property-face)))
+
+   :language 'go
+   :feature 'error
+   :override t
+   '((ERROR) @font-lock-warning-face))
+  "Tree-sitter font-lock settings for `go-ts-mode'.")
+
+(defun go-ts-mode--imenu ()
+  "Return Imenu alist for the current buffer."
+  (let* ((node (treesit-buffer-root-node))
+         (func-tree (treesit-induce-sparse-tree
+                     node "function_declaration" nil 1000))
+         (type-tree (treesit-induce-sparse-tree
+                     node "type_spec" nil 1000))
+         (func-index (go-ts-mode--imenu-1 func-tree))
+         (type-index (go-ts-mode--imenu-1 type-tree)))
+    (append
+     (when func-index `(("Function" . ,func-index)))
+     (when type-index `(("Type" . ,type-index))))))
+
+(defun go-ts-mode--imenu-1 (node)
+  "Helper for `go-ts-mode--imenu'.
+Find string representation for NODE and set marker, then recurse
+the subtrees."
+  (let* ((ts-node (car node))
+         (children (cdr node))
+         (subtrees (mapcan #'go-ts-mode--imenu-1
+                           children))
+         (name (when ts-node
+                 (treesit-node-text
+                  (pcase (treesit-node-type ts-node)
+                    ("function_declaration"
+                     (treesit-node-child-by-field-name ts-node "name"))
+                    ("type_spec"
+                     (treesit-node-child-by-field-name ts-node "name"))))))
+         (marker (when ts-node
+                   (set-marker (make-marker)
+                               (treesit-node-start ts-node)))))
+    (cond
+     ((or (null ts-node) (null name)) subtrees)
+     (subtrees
+      `((,name ,(cons name marker) ,@subtrees)))
+     (t
+      `((,name . ,marker))))))
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.go\\'" . go-ts-mode))
+
+;;;###autoload
+(define-derived-mode go-ts-mode prog-mode "Go"
+  "Major mode for editing Go, powered by tree-sitter."
+  :group 'go
+  :syntax-table go-ts-mode--syntax-table
+
+  (when (treesit-ready-p 'go)
+    (treesit-parser-create 'go)
+
+    ;; Comments.
+    (setq-local comment-start "// ")
+    (setq-local comment-end "")
+    (setq-local comment-start-skip (rx "//" (* (syntax whitespace))))
+
+    ;; Imenu.
+    (setq-local imenu-create-index-function #'go-ts-mode--imenu)
+    (setq-local which-func-functions nil)
+
+    ;; Indent.
+    (setq-local indent-tabs-mode t
+                treesit-simple-indent-rules go-ts-mode--indent-rules)
+
+    ;; Font-lock.
+    (setq-local treesit-font-lock-settings go-ts-mode--font-lock-settings)
+    (setq-local treesit-font-lock-feature-list
+                '(( comment)
+                  ( keyword string type)
+                  ( constant escape-sequence function label number
+                    property variable)
+                  ( bracket delimiter error operator)))
+
+    (treesit-major-mode-setup)))
+
+;; go.mod support.
+
+(defvar go-mod-ts-mode--syntax-table
+  (let ((table (make-syntax-table)))
+    (modify-syntax-entry ?/   ". 124b" table)
+    (modify-syntax-entry ?\n  "> b"    table)
+    table)
+  "Syntax table for `go-mod-ts-mode'.")
+
+(defvar go-mod-ts-mode--indent-rules
+  `((gomod
+     ((node-is ")") parent-bol 0)
+     ((parent-is "exclude_directive") parent-bol go-ts-mode-indent-offset)
+     ((parent-is "module_directive") parent-bol go-ts-mode-indent-offset)
+     ((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)
+     (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 inside a 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)
+        (pcase (treesit-node-type (treesit-node-at (point)))
+          ("exclude" t)
+          ("module" t)
+          ("replace" t)
+          ("require" t)
+          ("retract" t))))))
+
+(defvar go-mod-ts-mode--keywords
+  '("exclude" "go" "module" "replace" "require" "retract")
+  "go.mod keywords for tree-sitter font-locking.")
+
+(defvar go-mod-ts-mode--font-lock-settings
+  (treesit-font-lock-rules
+   :language 'gomod
+   :feature 'bracket
+   '((["(" ")"]) @font-lock-bracket-face)
+
+   :language 'gomod
+   :feature 'comment
+   '((comment) @font-lock-comment-face)
+
+   :language 'gomod
+   :feature 'keyword
+   `([,@go-mod-ts-mode--keywords] @font-lock-keyword-face)
+
+   :language 'gomod
+   :feature 'number
+   '([(go_version) (version)] @font-lock-number-face)
+
+   :language 'gomod
+   :feature 'operator
+   '((["=>"]) @font-lock-operator-face)
+
+   :language 'gomod
+   :feature 'error
+   :override t
+   '((ERROR) @font-lock-warning-face))
+  "Tree-sitter font-lock settings for `go-mod-ts-mode'.")
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("/go\\.mod\\'" . go-mod-ts-mode))
+
+;;;###autoload
+(define-derived-mode go-mod-ts-mode prog-mode "Go Mod"
+  "Major mode for editing go.mod files, powered by tree-sitter."
+  :group 'go
+  :syntax-table go-mod-ts-mode--syntax-table
+
+  (when (treesit-ready-p 'gomod)
+    (treesit-parser-create 'gomod)
+
+    ;; 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-mod-ts-mode--indent-rules)
+
+    ;; Font-lock.
+    (setq-local treesit-font-lock-settings go-mod-ts-mode--font-lock-settings)
+    (setq-local treesit-font-lock-feature-list
+                '((comment)
+                  (keyword)
+                  (number)
+                  (bracket error operator)))
+
+    (treesit-major-mode-setup)))
+
+(provide 'go-ts-mode)
+
+;;; go-ts-mode.el ends here
-- 
2.39.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode
  2022-12-13  2:13 bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode Randy Taylor
  2022-12-13 12:25 ` Eli Zaretskii
  2022-12-14  2:22 ` Yuan Fu
@ 2022-12-16  1:22 ` Yuan Fu
  2022-12-16  2:05   ` Randy Taylor
  2 siblings, 1 reply; 20+ messages in thread
From: Yuan Fu @ 2022-12-16  1:22 UTC (permalink / raw)
  To: Randy Taylor; +Cc: Eli Zaretskii, theo, 60025


Randy Taylor <dev@rjt.dev> writes:

> On Thursday, December 15th, 2022 at 15:09, Randy Taylor <dev@rjt.dev> wrote:
>> 
>> On Thursday, December 15th, 2022 at 14:59, Theodor Thornhill theo@thornhill.no wrote:
>> 
>> > Randy Taylor dev@rjt.dev writes:
>> > 
>> > > Patch OK to install otherwise?
>> > 
>> > Sure :-)
>> > 
>> > Theo
>> 
>> 
>> Thanks.
>> 
>> Yuan, I've attached the latest and greatest patch (same as the one a
>> few threads up but no need to make you hunt for it ;)). Ready to
>> install whenever, unless anyone has any other comments.
>
> Sorry, THIS attached patch is the latest and greatest patch. build.sh was changed recently renaming namespace to org, so I made that adjustment.

Thanks! Great work. I applied the patch. And sorry for the change in
build.sh, I meant to modify your patch to rename namespaces to org but
apparently forgot.

Yuan





^ permalink raw reply	[flat|nested] 20+ messages in thread

* bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode
  2022-12-16  1:22 ` Yuan Fu
@ 2022-12-16  2:05   ` Randy Taylor
  0 siblings, 0 replies; 20+ messages in thread
From: Randy Taylor @ 2022-12-16  2:05 UTC (permalink / raw)
  To: Yuan Fu; +Cc: Eli Zaretskii, theo, 60025-done@debbugs.gnu.org

On Thursday, December 15th, 2022 at 20:22, Yuan Fu <casouri@gmail.com> wrote:
> 
> Thanks! Great work. I applied the patch. And sorry for the change in
> build.sh, I meant to modify your patch to rename namespaces to org but
> apparently forgot.
> 
> Yuan

Thanks! Closing.





^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2022-12-16  2:05 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2022-12-13  2:13 bug#60025: [PATCH] Add go-ts-mode and go-mod-ts-mode Randy Taylor
2022-12-13 12:25 ` Eli Zaretskii
2022-12-13 19:39   ` Randy Taylor
2022-12-14 12:02     ` Eli Zaretskii
2022-12-14 16:21       ` Randy Taylor
2022-12-14 19:55         ` Yuan Fu
2022-12-14 20:54           ` Randy Taylor
2022-12-14 21:27             ` Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-12-14 21:56               ` Randy Taylor
2022-12-15  2:15               ` Randy Taylor
2022-12-15  7:20                 ` Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-12-15 16:40                   ` Randy Taylor
2022-12-15 18:06                     ` Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-12-15 19:45                       ` Randy Taylor
2022-12-15 19:59                         ` Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-12-15 20:09                           ` Randy Taylor
2022-12-15 22:22                             ` Randy Taylor
2022-12-14  2:22 ` Yuan Fu
2022-12-16  1:22 ` Yuan Fu
2022-12-16  2:05   ` Randy Taylor

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).