unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: "João Paulo Labegalini de Carvalho" <jaopaulolc@gmail.com>
To: Stefan Monnier <monnier@iro.umontreal.ca>
Cc: Alan Mackenzie <acm@muc.de>, Yuan Fu <casouri@gmail.com>,
	emacs-devel@gnu.org
Subject: Re: Code navigation for sh-mode with Tree-sitter
Date: Tue, 6 Dec 2022 18:12:25 -0700	[thread overview]
Message-ID: <CAGjvy28QDKTcEWU38vuo0wDzw8Wr2RLJEECMM+F6LK_2VVb7SQ@mail.gmail.com> (raw)
In-Reply-To: <jwva640xfj8.fsf-monnier+emacs@gnu.org>


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

On Tue, Dec 6, 2022 at 4:50 PM Stefan Monnier <monnier@iro.umontreal.ca>
wrote:

> > However, with negative arguments that does not happen, as
> > `sh-mode--treesit-beginning-of-defun' moves point to (beginning of) the
> > closest sibling function (after point) and
> > `sh-mode--treesit-end-of-defun' moves
> > point to (end of) the closest sibling function (before point).  In this
> > case, the selected functions to which point move to are not the same.
>
> Please read the docstring of `end-of-defun-function`, because I suspect
> that you are confused about what it's supposed to do.  E.g. it's not
> supposed to "move point to (end of) the closest sibling function", so
> I think you'll need to set it to a different function than
> `sh-mode--treesit-end-of-defun`.
>

Indeed. I was trying to impose the behavior I desired to achieve instead of
the intended use. I corrected that in my patch.

Looking forward to comments and suggestions for the patch.

-- 
João Paulo L. de Carvalho
Ph.D Computer Science |  IC-UNICAMP | Campinas , SP - Brazil
Postdoctoral Research Fellow | University of Alberta | Edmonton, AB - Canada
joao.carvalho@ic.unicamp.br
joao.carvalho@ualberta.ca

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

[-- Attachment #2: 0001-Basic-navigation-for-bash-ts-mode.patch --]
[-- Type: text/x-patch, Size: 9461 bytes --]

From e5430b13fe7f3945864e194c9207ed1a5e02835f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20P=2E=20L=2E=20de=20Carvalho?=
 <jaopaulolc@gmail.com>
Date: Sat, 3 Dec 2022 12:55:27 -0700
Subject: [PATCH] Basic navigation for bash-ts-mode

Enables navigation to beginning/end of function in bash-ts-mode.
  * lisp/progmodes/sh-script.el (bash-ts-mode): enable navegation via
  `beginning-of-defun-function' & `end-of-defun-function'.
  (sh-mode-treesit-beginning-of-defun)
  (sh-mode-treesit-end-of-defun)
  (sh-mode--treesit-end-of-defun-function)
  (sh-mode--treesit-defun-p)
  (sh-mode--treesit-not-sc-p)
  (sh-mode--treesit-next-sibling-defun)
  (sh-mode--treesit-prev-sibling-defun): New functions.
  (sh-mode--treesit-parent-defun): New macro.
---
 lisp/progmodes/sh-script.el | 184 +++++++++++++++++++++++++++++++++++-
 1 file changed, 183 insertions(+), 1 deletion(-)

diff --git a/lisp/progmodes/sh-script.el b/lisp/progmodes/sh-script.el
index 408ebfc045..04193ca0f8 100644
--- a/lisp/progmodes/sh-script.el
+++ b/lisp/progmodes/sh-script.el
@@ -1619,7 +1619,10 @@ bash-ts-mode
                   ( bracket delimiter misc-punctuation operator)))
     (setq-local treesit-font-lock-settings
                 sh-mode--treesit-settings)
-    (treesit-major-mode-setup)))
+    (setq-local treesit-defun-type-regexp "function_definition")
+    (treesit-major-mode-setup)
+    (setq-local beginning-of-defun-function #'sh-mode-treesit-beginning-of-defun)
+    (setq-local end-of-defun-function #'sh-mode--treesit-end-of-defun-function)))
 
 (advice-add 'bash-ts-mode :around #'sh--redirect-bash-ts-mode
             ;; Give it lower precedence than normal advice, so other
@@ -3364,5 +3367,184 @@ sh-mode--treesit-settings
    '((["$"]) @font-lock-misc-punctuation-face))
   "Tree-sitter font-lock settings for `sh-mode'.")
 
+\f
+;;; Tree-sitter navigation
+
+(defun sh-mode--treesit-defun-p (node)
+  "Return t if NODE is a function and nil otherwise."
+  (string-match treesit-defun-type-regexp
+                (treesit-node-type node)))
+
+(defun sh-mode-treesit-not-cs-p (node)
+  "Returns t if NODE is *not* a compound-statement
+and nil otherwise."
+  (lambda (p)
+    (not (string-match "compound_statement"
+                       (treesit-node-type p)))))
+
+(defun sh-mode--treesit-next-sibling-defun (node)
+  "Returns the next sibling function of NODE, if any, or nil."
+  (let ((sibling node))
+    (while (and sibling
+                (not (sh-mode--treesit-defun-p sibling)))
+      (setq sibling (treesit-node-next-sibling sibling)))
+    sibling))
+
+(defun sh-mode--treesit-prev-sibling-defun (node)
+  "Returns the previous sibling function of NODE, if any, or nil."
+  (let ((sibling (treesit-node-prev-sibling node)))
+    (while (and sibling
+                (not (sh-mode--treesit-defun-p sibling)))
+      (setq sibling (treesit-node-prev-sibling sibling)))
+    sibling))
+
+(defmacro sh-mode--treesit-parent-defun (node)
+  "Returns nearest function-node that surrounds NODE, if any, or nil.
+
+This macro can be used to determine if NODE is within a function.  If
+so, the macro evaluates to the nearest function-node and parent of NODE.
+Otherwise it evaluates to NIL."
+  `(treesit-parent-until ,node 'sh-mode--treesit-defun-p))
+
+(defmacro sh-mode--treesit-oldest-parent-in-defun (node)
+  "Returns oldest parent of NODE in common function, if any, or NIL.
+
+This function returns the oldest parent of NODE such that the common
+parent is the nearest function-node."
+  `(treesit-parent-while ,node 'sh-mode--treesit-not-cp-p))
+
+(defun sh-mode-treesit-beginning-of-defun (&optional arg)
+  "Tree-sitter `beginning-of-defun' function.
+ARG is the same as in `beginning-of-defun'.
+
+This function can be used either to set `beginning-of-defun-function'
+or as a direct replacement to `beginning-of-defun'.
+
+This function works the same way the non-tree-sitter
+`beginning-of-defun' when point is not within a function.  It diverges
+from `beginning-of-defun' when inside a function by moving point to
+the beginning of the closest enclosing function when ARG is positive.
+When ARG is negative and inside a function, point is moved to the
+beggining of closest sibling function, recursively."
+  (interactive "P")
+  (let ((arg (or arg 1))
+        (target nil)
+        (curr (treesit-node-at (point)))
+        (function treesit-defun-type-regexp))
+    (if (> arg 0)
+        ;; Go backward.
+        (while (and (> arg 0) curr)
+          (if (string= (treesit-node-type curr) "function")
+              (setq curr (treesit-node-parent curr)))
+          (setq target (sh-mode--treesit-parent-defun curr))
+          (unless target
+            (let ((maybe-target nil))
+              (setq maybe-target (treesit-search-forward curr
+                                                         function
+                                                         t))
+              (setq target (or (treesit-node-top-level maybe-target)
+                               maybe-target))))
+          (when target
+            (setq curr target))
+          (setq arg (1- arg)))
+      ;; Go forward.
+      (while (and (< arg 0) curr)
+        (setq target nil)
+        (if (sh-mode--treesit-defun-p curr)
+            (setq curr (treesit-node-at
+                        (treesit-node-end
+                         (treesit-node-parent curr)))))
+        (let ((parent-defun (sh-mode--treesit-parent-defun curr)))
+          (while (and (not target)
+                      parent-defun)
+            (setq target (sh-mode--treesit-next-sibling-defun curr))
+            (unless target
+              (setq curr (treesit-node-next-sibling parent-defun))
+              (setq parent-defun
+                    (sh-mode--treesit-parent-defun curr))))
+          (unless target
+            (let ((maybe-target nil))
+              (setq maybe-target (treesit-search-forward curr
+                                                         function))
+              (setq target (or (treesit-node-top-level maybe-target)
+                               maybe-target))))
+          (when target
+            (setq curr target)))
+        (setq arg (1+ arg))))
+    (when target
+      (goto-char (treesit-node-start target)))))
+
+(defun sh-mode--treesit-end-of-defun-function ()
+  "Tree-sitter `end-of-defun-function' function."
+  (let ((curr (treesit-node-at (point))))
+    (if curr
+        (let ((curr-defun (sh-mode--treesit-parent-defun curr)))
+          (if curr-defun
+              (goto-char (treesit-node-end curr-defun)))))))
+
+(defun sh-mode-treesit-end-of-defun (&optional arg)
+  "Tree-sitter `end-of-defun' function.
+
+This function is a more opinionated version of `end-of-defun' and can
+be used as its direct replacement.
+
+This function works the same way the non-tree-sitter `end-of-defun'
+when point is not within a function.  It diverges from `end-of-defun'
+when inside a function by moving point to the end of the closest
+enclosing function when ARG is positive.  When ARG is negative and
+inside a function, point is moved to the end of closest sibling
+function, if any.  Otherwise the search recurses to function enclosing
+the current function."
+  (interactive "P")
+  (let ((arg (or arg 1))
+        (curr (treesit-node-at (point)))
+        (target nil)
+        (function treesit-defun-type-regexp))
+    (if (> arg 0)
+        ;; Go forward.
+        (while (and (> arg 0) curr)
+          (setq target (sh-mode--treesit-parent-defun curr))
+          (unless target
+            (setq target (treesit-search-forward curr
+                                                 function))
+            (when (and target
+                       (sh-mode--treesit-parent-defun target))
+              (setq target (treesit-node-top-level target))))
+          (when target
+            (setq curr target))
+          (setq arg (1- arg)))
+      ;; Go backward.
+      (while (and (< arg 0) curr)
+        (setq target nil)
+        (if (sh-mode--treesit-parent-defun curr)
+            (setq curr
+                  (or (sh-mode--treesit-oldest-parent-in-defun curr)
+                      curr)))
+        (let* ((prev-defun (sh-mode--treesit-prev-sibling-defun curr))
+               (prev-defun-end (treesit-node-at
+                                (treesit-node-end prev-defun))))
+          (if (and prev-defun (treesit-node-eq curr prev-defun-end))
+              (setq curr prev-defun)))
+        (let ((parent-defun (sh-mode--treesit-parent-defun curr)))
+          (while (and (not target)
+                      parent-defun)
+            (setq target (sh-mode--treesit-prev-sibling-defun curr))
+            (unless target
+              (setq curr (treesit-node-prev-sibling parent-defun))
+              (setq parent-defun
+                    (sh-mode--treesit-parent-defun curr))))
+          (unless target
+            (let ((maybe-target nil))
+              (setq maybe-target (treesit-search-forward curr
+                                                         function
+                                                         t))
+              (setq target (or (treesit-node-top-level maybe-target)
+                               maybe-target))))
+          (when target
+            (setq curr target)))
+        (setq arg (1+ arg))))
+    (when target
+      (goto-char (treesit-node-end target)))))
+
 (provide 'sh-script)
 ;;; sh-script.el ends here
-- 
2.31.1


  reply	other threads:[~2022-12-07  1:12 UTC|newest]

Thread overview: 35+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-12-03 20:23 Code navigation for sh-mode with Tree-sitter João Paulo Labegalini de Carvalho
2022-12-03 21:46 ` Alan Mackenzie
2022-12-05 15:24   ` João Paulo Labegalini de Carvalho
2022-12-05 20:12     ` Stefan Monnier
2022-12-05 21:29       ` Alan Mackenzie
2022-12-05 21:56         ` Stefan Monnier
2022-12-06 15:51       ` João Paulo Labegalini de Carvalho
2022-12-06 16:48         ` Stefan Monnier
2022-12-06 21:04           ` Yuan Fu
2022-12-06 21:08             ` Yuan Fu
2022-12-06 21:40               ` Alan Mackenzie
2022-12-06 21:46                 ` João Paulo Labegalini de Carvalho
2022-12-06 21:55                   ` João Paulo Labegalini de Carvalho
2022-12-06 22:35                     ` Stefan Monnier
2022-12-06 22:41                       ` João Paulo Labegalini de Carvalho
2022-12-06 22:57                       ` Stefan Monnier
2022-12-06 23:43                         ` João Paulo Labegalini de Carvalho
2022-12-06 23:50                           ` Stefan Monnier
2022-12-07  1:12                             ` João Paulo Labegalini de Carvalho [this message]
2022-12-07 17:20                               ` João Paulo Labegalini de Carvalho
2022-12-10  4:58                                 ` Yuan Fu
2022-12-13  4:55                                 ` Yuan Fu
2022-12-13 16:00                                   ` João Paulo Labegalini de Carvalho
2022-12-13  5:20                                 ` New defun navigation for tree-sitter (Was: Code navigation for sh-mode with Tree-sitter) Yuan Fu
2022-12-13 16:11                                   ` João Paulo Labegalini de Carvalho
2022-12-13 16:38                                     ` Eli Zaretskii
2022-12-13 18:03                                       ` João Paulo Labegalini de Carvalho
2022-12-13 18:07                                     ` Yuan Fu
2022-12-13 18:48                                       ` João Paulo Labegalini de Carvalho
2022-12-13 18:56                                         ` Yuan Fu
2022-12-13 19:46                                           ` João Paulo Labegalini de Carvalho
2022-12-16  1:49                                             ` Yuan Fu
2022-12-16 16:24                                               ` João Paulo Labegalini de Carvalho
2022-12-17 23:32                                                 ` Yuan Fu
2022-12-07  0:41                 ` Code navigation for sh-mode with Tree-sitter Yuan Fu

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

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=CAGjvy28QDKTcEWU38vuo0wDzw8Wr2RLJEECMM+F6LK_2VVb7SQ@mail.gmail.com \
    --to=jaopaulolc@gmail.com \
    --cc=acm@muc.de \
    --cc=casouri@gmail.com \
    --cc=emacs-devel@gnu.org \
    --cc=monnier@iro.umontreal.ca \
    /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 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).