diff --git a/CHANGELOG.md b/CHANGELOG.md index 4022bcb..1561f31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - [#96](https://github.com/clojure-emacs/clojure-ts-mode/pull/96): Highlight function name properly in `extend-protocol` form. - [#96](https://github.com/clojure-emacs/clojure-ts-mode/pull/96): Add support for extend-protocol forms to `clojure-ts-add-arity` refactoring command. +- [#98](https://github.com/clojure-emacs/clojure-ts-mode/pull/98): Slightly improved navigation by s-expressions for Emacs 31. ## 0.4.0 (2025-05-15) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 8b6bca7..b489c96 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -788,7 +788,8 @@ literals with regex grammar." (defun clojure-ts--metadata-node-p (node) "Return non-nil if NODE is a Clojure metadata node." - (string-equal "meta_lit" (treesit-node-type node))) + (or (string-equal "meta_lit" (treesit-node-type node)) + (string-equal "old_meta_lit" (treesit-node-type node)))) (defun clojure-ts--var-node-p (node) "Return non-nil if NODE is a var (eg. #\\'foo)." @@ -1545,10 +1546,23 @@ function literal." "code_span") "Nodes representing s-expressions in the `markdown-inline' parser.") +(defun clojure-ts--default-sexp-node-p (node) + "Return TRUE if point is NODE is either function literal or set or has metadata." + (or + ;; Opening paren of set or function literal. + (and (eq (char-before (point)) ?\#) + (string-match-p (rx bol (or "anon_fn_lit" "set_lit") eol) + (treesit-node-type (treesit-node-parent node)))) + ;; Node with metadata. + (clojure-ts--metadata-node-p (treesit-node-prev-sibling node)))) + (defconst clojure-ts--thing-settings `((clojure (sexp ,(regexp-opt clojure-ts--sexp-nodes)) (list ,(regexp-opt clojure-ts--list-nodes)) + (sexp-default + ;; For `C-M-f' in "#|(a)" or "#|{1 2 3}" + (,(rx (or "(" "{" "[")) . ,#'clojure-ts--default-sexp-node-p)) (text ,(regexp-opt '("comment"))) (defun ,#'clojure-ts--defun-node-p)) (when clojure-ts-use-markdown-inline @@ -2130,7 +2144,7 @@ type, etc. See `treesit-thing-settings' for more details." (newline-and-indent)) (when single-arity-p (insert-pair 2 ?\( ?\)) - (backward-up-list)) + (forward-char -1)) (insert "([])\n") ;; Put the point between square brackets. (down-list -2))) @@ -2601,6 +2615,13 @@ REGEX-AVAILABLE." 0 t) + ;; Improve navigation by matching parenthesis for Emacs 31+ + (when (>= emacs-major-version 31) + (setq-local up-list-function + (lambda (&optional arg escape-strings no-syntax-crossing) + (let ((treesit-sexp-type-regexp 'sexp)) + (treesit-up-list arg escape-strings no-syntax-crossing))))) + ;; Workaround for treesit-transpose-sexps not correctly working with ;; treesit-thing-settings on Emacs 30. ;; Once treesit-transpose-sexps it working again this can be removed diff --git a/test/clojure-ts-mode-navigation-test.el b/test/clojure-ts-mode-navigation-test.el new file mode 100644 index 0000000..59f83c9 --- /dev/null +++ b/test/clojure-ts-mode-navigation-test.el @@ -0,0 +1,89 @@ +;;; clojure-ts-mode-navigation-test.el --- Clojure[TS] Mode: code navigation test suite -*- lexical-binding: t; -*- + +;; Copyright (C) 2025 Roman Rudakov + +;; Author: Roman Rudakov + +;; This program 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. + +;; This program 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 this program. If not, see . + +;;; Commentary: + +;; Verify that Emacs' built-in navigation commands work as expected. + +;;; Code: + +(require 'clojure-ts-mode) +(require 'buttercup) +(require 'test-helper "test/test-helper") + +(describe "function literals" + (describe "forward-sexp" + (when-refactoring-with-point-it "should navigate to a closing paren if point is before an opening paren" + "#|(-> (.-value (.-target %)))" + "#(-> (.-value (.-target %)))|" + (forward-sexp)) + (when-refactoring-with-point-it "should navigate to a closing paren if point is before a # character" + "|#(-> (.-value (.-target %)))" + "#(-> (.-value (.-target %)))|" + (forward-sexp))) + + (describe "backward-up-list" + (when-refactoring-with-point-it "should navigate to the beginning of a parent expression" + "#(-> |(.-value (.-target %)))" + "|#(-> (.-value (.-target %)))" + (backward-up-list))) + + (describe "raise-sexp" + (when-refactoring-with-point-it "should keep balanced parenthesis" + "#(-> |(.-value (.-target %)))" + "|(.-value (.-target %))" + (raise-sexp)))) + +(describe "sets" + (describe "forward-sexp" + (when-refactoring-with-point-it "should navigate to a closing paren if point is before an opening paren" + "#|{1 2 3}" + "#{1 2 3}|" + (forward-sexp)) + (when-refactoring-with-point-it "should navigate to a closing paren if point is before a # character" + "|#{1 2 3}" + "#{1 2 3}|" + (forward-sexp))) + + (describe "backward-up-list" + (when-refactoring-with-point-it "should navigate to the beginning of a parent expression" + "#{1 |2 3}" + "|#{1 2 3}" + (backward-up-list))) + + (describe "raise-sexp" + (when-refactoring-with-point-it "should not keep unwanted characters" + "#{1 |2 3}" + "|2" + (raise-sexp)))) + +(describe "nodes with metadata" + (describe "forward-sexp" + (when-refactoring-with-point-it "should navigate to a closing paren if point is before an opening paren" + "^String |[arg]" + "^String [arg]|" + (forward-sexp)) + ;; This is not perfect, but with the current grammar we cannot handle it better. + (when-refactoring-with-point-it "should naigate to a closing paren if point is before metadata" + "|^String [arg]" + "^String [arg]|" + (forward-sexp)))) + +(provide 'clojure-ts-mode-navigation-test) +;;; clojure-ts-mode-navigation-test.el ends here diff --git a/test/samples/refactoring.clj b/test/samples/refactoring.clj index 5a87bf7..d9aab60 100644 --- a/test/samples/refactoring.clj +++ b/test/samples/refactoring.clj @@ -76,9 +76,14 @@ (def my-name "Roma") (defn say-hello - [] + ^String [] (println "Hello" my-name)) +(defn foo + ^{:bla "meta"} + [arg] + body) + (definline bad-sqr [x] `(* ~x ~x)) (defmulti service-charge (juxt account-level :tag))