Skip to content

Define sexp-default thing to improve navigation in Emacs 31 #98

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
25 changes: 23 additions & 2 deletions clojure-ts-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -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)."
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)))
Expand Down Expand Up @@ -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
Expand Down
89 changes: 89 additions & 0 deletions test/clojure-ts-mode-navigation-test.el
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>

;; 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 <https://www.gnu.org/licenses/>.

;;; 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
7 changes: 6 additions & 1 deletion test/samples/refactoring.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down