From e3691d9261233241d349dd4d39b72ddb144f6f1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20P=2E=20L=2E=20de=20Carvalho?= 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 new internal navigation functions. (sh-mode--treesit-beginning-of-defun) (sh-mode--treesit-end-of-defun) (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 | 171 +++++++++++++++++++++++++++++++++++- 1 file changed, 170 insertions(+), 1 deletion(-) diff --git a/lisp/progmodes/sh-script.el b/lisp/progmodes/sh-script.el index 408ebfc045..751e5d6993 100644 --- a/lisp/progmodes/sh-script.el +++ b/lisp/progmodes/sh-script.el @@ -1619,7 +1619,12 @@ 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) + ;; (keymap-set bash-ts-mode-map "C-M-a" #'sh-mode--treesit-beginning-of-defun) + ;; (keymap-set bash-ts-mode-map "C-M-e" #'sh-mode--treesit-end-of-defun) + (setq-local beginning-of-defun-function #'sh-mode--treesit-beginning-of-defun) + (setq-local end-of-defun-function #'sh-mode--treesit-end-of-defun))) (advice-add 'bash-ts-mode :around #'sh--redirect-bash-ts-mode ;; Give it lower precedence than normal advice, so other @@ -3364,5 +3369,169 @@ sh-mode--treesit-settings '((["$"]) @font-lock-misc-punctuation-face)) "Tree-sitter font-lock settings for `sh-mode'.") + +;;; 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 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 (&optional arg) + "Tree-sitter `end-of-defun' function. + +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, recursively." + (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