From cbeab56bf5dd6c39166f6c1e893673a8937d3aa8 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 7 Nov 2026 10:58:07 +0200 Subject: [PATCH] Fetch the node to align again on every line clojure-ts--search-whitespace-after-next-sexp fixes the space after metadata, which leaves the node it searches outdated, and it went on to the next line with that node when the form after the metadata ended its line. Aligning {:a ^:m b :ccc d} signalled treesit-node-outdated. --- CHANGELOG.md | 1 + clojure-ts-mode.el | 27 ++++++++++++++---------- test/clojure-ts-mode-indentation-test.el | 10 ++++++++- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70cc02a..e7f7e73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ - [#164](https://github.com/clojure-emacs/clojure-ts-mode/pull/164): Support `setopt-local` for `clojure-ts-indent-style`, `clojure-ts-extra-def-forms` and `clojure-ts-comment-macro-font-lock-body`, which Emacs 31 refused to set locally and Emacs 32 warned about in `.dir-locals.el`. - [#168](https://github.com/clojure-emacs/clojure-ts-mode/pull/168): Indent docstrings like `clojure-mode`, moving the lines indented less than `clojure-ts-docstring-fill-prefix-width` to that column and keeping deeper ones, instead of leaving them alone with `clojure-ts-use-markdown-inline` and flattening them without it. - [#174](https://github.com/clojure-emacs/clojure-ts-mode/pull/174): Speed up indentation of forms with many children, like a big `comment` form, which slowed down with the cube of their length. +- [#175](https://github.com/clojure-emacs/clojure-ts-mode/pull/175): Fix `clojure-ts-align` signalling `treesit-node-outdated` on a line that ends in a form after metadata and more than one space. ## 0.6.0 (2025-12-02) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 17049c7..d919ddb 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -1896,19 +1896,21 @@ children of the rich comment form are also considered as top-level." "Return the point that represents the end of the current defun." (treesit-node-end (treesit-defun-at-point))) -(defun clojure-ts--search-whitespace-after-next-sexp (root-node bound) +(defun clojure-ts--search-whitespace-after-next-sexp (root-node-fn bound) "Move the point after all whitespace following the next s-expression. Set match data group 1 to this region of whitespace and return the point. To move over the next s-expression, fetch the next node after the -current cursor position that is a direct child of ROOT-NODE and navigate -to its end. The most complex aspect here is handling nodes with -metadata. Some forms are represented in the syntax tree as a single -s-expression (for example, ^long my-var or ^String (str \"Hello\" -\"world\")), while other forms are two separate s-expressions (for -example, ^long 123 or ^String \"Hello\"). Expressions with two nodes +current cursor position that is a direct child of the node ROOT-NODE-FN +returns and navigate to its end. It's called again for every line, as +fixing the space after metadata on the way leaves the old node outdated. +The most complex aspect here is handling nodes with metadata. Some +forms are represented in the syntax tree as a single s-expression (for +example, ^long my-var or ^String (str \"Hello\" \"world\")), while +other forms are two separate s-expressions (for example, ^long 123 or +^String \"Hello\"). Expressions with two nodes share some common features: - The top-level node type is usually sym_lit @@ -1927,7 +1929,8 @@ BOUND bounds the whitespace search." (progn (search-forward-regexp regex bound) (point)) - (when-let* ((cur-sexp (treesit-node-first-child-for-pos root-node (point) t))) + (when-let* ((cur-sexp (treesit-node-first-child-for-pos + (funcall root-node-fn) (point) t))) (goto-char (treesit-node-start cur-sexp)) (if (clojure-ts--metadata-node-p cur-sexp) (progn @@ -1943,7 +1946,7 @@ BOUND bounds the whitespace search." ;; End-of-line, try again on next line. (`(12) (progn (forward-char 1) - (clojure-ts--search-whitespace-after-next-sexp root-node bound))) + (clojure-ts--search-whitespace-after-next-sexp root-node-fn bound))) ;; Closing paren, stop here. (`(5 . ,_) nil) ;; Anything else is something to align. @@ -2112,8 +2115,10 @@ between BEG and END." (clojure-ts--point-to-align-position sexp-type node) (align-region (point) node-end nil `((clojure-align (regexp . ,(lambda (&optional bound _noerror) - (let ((updated-node (clojure-ts--node-from-sexp-data beg (marker-position end) sexp))) - (clojure-ts--search-whitespace-after-next-sexp updated-node bound)))) + (clojure-ts--search-whitespace-after-next-sexp + (lambda () + (clojure-ts--node-from-sexp-data beg (marker-position end) sexp)) + bound))) (group . 1) (separate . ,clojure-ts-align-separator) (repeat . t))) diff --git a/test/clojure-ts-mode-indentation-test.el b/test/clojure-ts-mode-indentation-test.el index 796fa75..c12e854 100644 --- a/test/clojure-ts-mode-indentation-test.el +++ b/test/clojure-ts-mode-indentation-test.el @@ -1482,4 +1482,12 @@ c) :foo \"bar\"}" "#::alias{:a 1 - :bbb 2}")) + :bbb 2}") + + (it "should align a line ending in a form after metadata and extra spaces" + ;; Fixing the space after the metadata used to leave the node it + ;; searched outdated for the next line. + (let ((clojure-ts-align-forms-automatically t)) + (with-clojure-ts-buffer "{:a ^:m b\n:ccc d}" + (indent-region (point-min) (point-max)) + (expect (buffer-string) :to-equal "{:a ^:m b\n :ccc d}")))))