From: Evgeni Kolev <evgenysw@gmail.com>
To: "Eli Zaretskii" <eliz@gnu.org>, "João Távora" <joaotavora@gmail.com>
Cc: dev@rjt.dev, theo@thornhill.no, 61368@debbugs.gnu.org
Subject: bug#61368: [PATCH] Extend go-ts-mode with support for pre-filling return statements
Date: Sat, 18 Feb 2023 13:46:05 +0200 [thread overview]
Message-ID: <CAMCrgaVHtNZ6vAyTfxdfXQYmE8TKQHJK-wKoc78YcF=eCygyTQ@mail.gmail.com> (raw)
In-Reply-To: <83357flzys.fsf@gnu.org>
[-- Attachment #1: Type: text/plain, Size: 1931 bytes --]
+ João, author of yasnippet.
Hi, João, I'd appreciate any suggestions you might have. I'm aiming to
extend go-ts-mode with a snippet-like feature. go-ts-mode would be
able to insert a "smart / context-aware" return statement. My question
is - is there a way for major modes to provide yasnippet snippets? My
understanding is the snippets are either created by each user
individually, or distributed as a collection of snippets (e.g.
yasnippet-snippets). Are there any major modes which provide snippets
(I wasn't able to find any in emacs' repo)?
On Thu, Feb 9, 2023 at 3:40 PM Eli Zaretskii <eliz@gnu.org> wrote:
> > Makes sense. I am familiar with electric-pair-mode. I see that it adds
> > a hook in post-self-insert-hook. Is your suggestion to do something
> > like this:
> > - add hook in post-self-insert-hook
> > - in the hook check if RET is typed after an "return"
> > - if yes, replace the "return" with the "context-aware return
> > statement", possibly using (yas-expand-snippet)
>
> Yes, something like that. See electric.el for more examples.
>
> Electric modes are usually minor modes that let major modes customize
> them by setting variables. Again, I think you will find examples in
> electric.el.
The attached patch now adds a (go-ts-electric-return-mode) which, when
enabled, replaces "return RET" with a context-aware return statement.
I used sh-script.el's (sh-electric-here-document-mode) as an example.
I'm concerned this electric mode can turn out to be too
aggressive/surprising for the user - "return RET" is a common text to
be entered, I'm not sure the user would always want it to be replaced.
For example, when debugging I might insert return statements all over
the place.
Put differently, I would be more comfortable if there's a distinct
mechanism to trigger the context-aware return, for example with a
trigger key like when a snippet is inserted.
[-- Attachment #2: 0001-Extend-go-ts-mode-with-support-for-pre-filling-retur.patch --]
[-- Type: application/octet-stream, Size: 6340 bytes --]
From 3efaa713ac9fdcb4507e945c2db771f7bb3a1be6 Mon Sep 17 00:00:00 2001
From: Evgeni Kolev <evgenysw@gmail.com>
Date: Mon, 6 Feb 2023 09:49:52 +0200
Subject: [PATCH] Extend go-ts-mode with support for pre-filling return
statements
Extend go-ts-mode to insert context-aware return statement for the
current function. The return statement is pre-filled with the zero
values of the function's output parameters.
For example, this return statement is added by Emacs:
```
func f() (bool, int, myStruct, *int, chan bool, error) {
return false, 0, myStruct{}, nil, nil, err
}
```
* lisp/progmodes/go-ts-mode.el (go-ts-mode-error-zero-value): New
defcustom. (go-ts-mode-insert-return, go-ts-mode-return,
go-ts-mode--function-return-types, go-ts-mode--function-at-point,
go-ts-mode--types-from-param-declaration, go-ts-mode--zero-value) New
functions.
---
lisp/progmodes/go-ts-mode.el | 103 +++++++++++++++++++++++++++++++++++
1 file changed, 103 insertions(+)
diff --git a/lisp/progmodes/go-ts-mode.el b/lisp/progmodes/go-ts-mode.el
index 2a2783f45f6..0a163753f29 100644
--- a/lisp/progmodes/go-ts-mode.el
+++ b/lisp/progmodes/go-ts-mode.el
@@ -46,6 +46,15 @@ go-ts-mode-indent-offset
:safe 'integerp
:group 'go)
+(defcustom go-ts-mode-error-zero-value "err"
+ "The zero value for type \"error\".
+The default is \"err\", assuming there's a varialbe named \"err\" in
+the current scope."
+ :version "30"
+ :type 'string
+ :options '("err", "fmt.Errorf(\"format: %w\", err)", "errors.Wrap(err, \"format\")")
+ :group 'go)
+
(defvar go-ts-mode--syntax-table
(let ((table (make-syntax-table)))
(modify-syntax-entry ?+ "." table)
@@ -328,6 +337,100 @@ go-ts-mode--comment-on-previous-line-p
(<= (treesit-node-start node) point (treesit-node-end node))
(string-equal "comment" (treesit-node-type node)))))
+(define-minor-mode go-ts-electric-return-mode
+ "Make `return RET' insert a return statement with zero values."
+ :lighter nil
+ (if go-ts-electric-return-mode
+ (add-hook 'post-self-insert-hook #'go-ts-mode--maybe-insert-return nil t)
+ (remove-hook 'post-self-insert-hook #'go-ts-mode--maybe-insert-return t)))
+
+(defun go-ts-mode--maybe-insert-return ()
+ "If RET is entered after `return', add zero values."
+ (when (and (equal (this-command-keys) (kbd "RET"))
+ (looking-back (rx word-boundary "return" "\n" (* space)) (pos-bol 0)))
+ (delete-region (point) (pos-eol 0)) ;; delete the line added by RET
+ (delete-char (- (length "return")))
+ (insert (go-ts-mode-return))))
+
+(defun go-ts-mode-return()
+ "Return a return statement with zero values for function at point.
+It's an error to call this function outside of a function body."
+ (interactive)
+ (let* ((func-node (go-ts-mode--function-at-point))
+ (body-node (treesit-node-child-by-field-name func-node "body")))
+
+ (unless (and body-node (< (treesit-node-start body-node) (point) (treesit-node-end body-node)))
+ (user-error "Point must be inside a Go function body"))
+
+ (let* ((types (go-ts-mode--function-return-types func-node))
+ (zero-values (mapcar 'go-ts-mode--zero-value types))
+ (return-statement (format "return %s" (string-join zero-values ", "))))
+ (string-trim-right return-statement))))
+
+(defun go-ts-mode--function-at-point ()
+ "Return the treesit function node at point.
+Function, methods and closures (anonymous functions) are recognized.
+If point is not in a recognized function, nil is returned."
+ (interactive)
+ (treesit-parent-until
+ (treesit-node-at (point))
+ (lambda (x)
+ (member (treesit-node-type x)
+ '("func_literal" "function_declaration" "method_declaration"))) t))
+
+(defun go-ts-mode--function-return-types (func-node)
+ "Return the types of the output variables of FUNC-NODE.
+The result is a list which could contain a single element (the Go
+function has one return argument), multiple elements (multiple return
+arguments), or no elements (no return arguments)."
+ (let ((result-node (treesit-node-child-by-field-name func-node "result")))
+ (pcase (treesit-node-type result-node)
+ ("parameter_list" ;; multiple return values
+ (let ((types-nested (treesit-induce-sparse-tree result-node "parameter_declaration" #'go-ts-mode--types-from-param-declaration)))
+ (apply #'append (mapcar 'car (cdr types-nested)))))
+ ((pred null) ;; no return value
+ nil)
+ (_ ;; single return value
+ (list (treesit-node-text result-node t))))))
+
+(defun go-ts-mode--types-from-param-declaration (node)
+ "Extract the Go type(s) from NODE. It must have type parameter_declaration.
+
+A parameter_declaration node can contain single value `(int)', named
+single value `(sum int)', multiples values `(int, int)', or multiple
+named values `(x, y int)'.
+
+All the above cases are recognized. Only the types are returned, for
+example, with parameter_declaration `(x, y int)', the result is
+\(\"int\", \"int\")."
+ (let ((type (treesit-node-child-by-field-name node "type"))
+ (count (length (treesit-query-capture node "(parameter_declaration name:(_) @name)" nil nil t))))
+ (make-list (if (zerop count) 1 count) (treesit-node-text type t))))
+
+(defun go-ts-mode--zero-value (go-type)
+ "Return the Go zero value for GO-TYPE.
+For type \"error\", the zero value is determined by variable
+`go-ts-mode-error-zero-value'.
+
+This function assumes arbitrary types are Go structs, for example for
+GO-TYPE \"person\", the retun value is \"person{}\"."
+ (pcase go-type
+ ((or "int" "int8" "int16" "int32" "int64") "0")
+ ((or "uint" "uint8" "uint16" "uint32" "uint64") "0")
+ ("bool" "false")
+ ("string" "\"\"")
+ ((or "uintptr" "byte" "rune") "0")
+ ((or "float32" "float64") "0.0")
+ ((or "complex64" "complex128") "complex(0, 0)")
+ ((pred (string-prefix-p "map")) "nil")
+ ((pred (string-prefix-p "*")) "nil")
+ ((pred (string-prefix-p "[]")) "nil")
+ ((pred (string-prefix-p "chan")) "nil")
+ ((pred (string-prefix-p "func")) "nil")
+ ("error" go-ts-mode-error-zero-value)
+ ((rx bol (1+ (or alphanumeric "_" ".")) eol) (concat go-type "{}"))
+ (_ (user-error "Unknown Go type \"%s\"" go-type))))
+
;; go.mod support.
(defvar go-mod-ts-mode--syntax-table
--
2.39.1
next prev parent reply other threads:[~2023-02-18 11:46 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-02-08 15:27 bug#61368: [PATCH] Extend go-ts-mode with support for pre-filling return statements Evgeni Kolev
2023-02-08 16:30 ` Eli Zaretskii
2023-02-09 11:47 ` Evgeni Kolev
2023-02-09 13:39 ` Eli Zaretskii
2023-02-18 11:46 ` Evgeni Kolev [this message]
2023-02-18 12:14 ` João Távora
2023-02-20 8:54 ` Evgeni Kolev
2023-02-20 12:55 ` Eli Zaretskii
2023-02-22 14:36 ` Evgeni Kolev
2024-01-10 22:43 ` Stefan Kangas
2024-01-15 8:21 ` Evgeni Kolev
2024-01-15 8:39 ` Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-01-15 11:40 ` João Távora
2023-02-08 19:20 ` Theodor Thornhill via Bug reports for GNU Emacs, the Swiss army knife of text editors
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to='CAMCrgaVHtNZ6vAyTfxdfXQYmE8TKQHJK-wKoc78YcF=eCygyTQ@mail.gmail.com' \
--to=evgenysw@gmail.com \
--cc=61368@debbugs.gnu.org \
--cc=dev@rjt.dev \
--cc=eliz@gnu.org \
--cc=joaotavora@gmail.com \
--cc=theo@thornhill.no \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.