diff --git a/CHANGELOG.md b/CHANGELOG.md index 591bb2b..0bf3041 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - [#161](https://github.com/clojure-emacs/clojure-ts-mode/pull/161): Fix converting collections with metadata: a quoted list with metadata after the quote (`'^:foo (1 2)`) failed to convert, and converting to a quoted list put the quote after the metadata. - [#153](https://github.com/clojure-emacs/clojure-ts-mode/pull/153): Align threading macro arguments with the first argument instead of the previous one, which went wrong after a line with several forms or a trailing comment. - [#154](https://github.com/clojure-emacs/clojure-ts-mode/pull/154): Fix semantic indentation of forms with metadata or a comment before an argument or the body, like `(doto ^Foo`, `(let ;; bindings` or `(comment ;; scratch`. +- [#155](https://github.com/clojure-emacs/clojure-ts-mode/pull/155): Fix indentation of a form on the line after its reader prefix (`'`, `` ` ``, `~`, `~@`, `@`, `#'`, `#=`), which no rule matched. ## 0.6.0 (2025-12-02) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 0a0c942..13a927b 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -1214,6 +1214,31 @@ The possible values for this variable are (const :tag "Simple fixed indentation rules." fixed)) :package-version '(clojure-ts-mode . "0.2.0")) +(defconst clojure-ts--reader-prefix-node-regexp + (rx bol (or "quoting_lit" "syn_quoting_lit" "unquoting_lit" "unquote_splicing_lit" + "derefing_lit" "var_quoting_lit" "evaling_lit") + eol) + "Node types of a form behind a reader prefix. +These are quote, syntax quote, unquote, unquote-splicing, deref, var +quote and read-eval.") + +(defun clojure-ts--prefixed-form-indent (prefixed) + "Return (ANCHOR . OFFSET) for PREFIXED as if it started a line. + +PREFIXED is a node like @form. When the form itself starts +on the line after the prefix, it is indented the way PREFIXED would be." + (treesit-simple-indent prefixed + (treesit-node-parent prefixed) + (treesit-node-start prefixed))) + +(defun clojure-ts--anchor-prefixed-form (_node parent _bol) + "Return the anchor of PARENT, a prefixed form, as if it started a line." + (car (clojure-ts--prefixed-form-indent parent))) + +(defun clojure-ts--offset-prefixed-form (_node parent _bol) + "Return the offset of PARENT, a prefixed form, as if it started a line." + (or (cdr (clojure-ts--prefixed-form-indent parent)) 0)) + (defvar clojure-ts--fixed-indent-rules ;; This is in contrast to semantic ;; fixed-indent-rules come from https://tonsky.me/blog/clojurefmt/ @@ -1234,7 +1259,11 @@ The possible values for this variable are ((parent-is "^map_lit$") parent 1) ((parent-is "^list_lit$") parent 1) ((parent-is "^set_lit$") parent 2) - ((parent-is "^ns_map_lit$") (nth-sibling 2) 1)))) + ((parent-is "^ns_map_lit$") (nth-sibling 2) 1) + ;; A form on the line after its reader prefix, e.g. @ at the end of a line. + ((parent-is ,clojure-ts--reader-prefix-node-regexp) + clojure-ts--anchor-prefixed-form + clojure-ts--offset-prefixed-form)))) (defvar clojure-ts--semantic-indent-rules-defaults '(("alt!" . ((:block 0))) @@ -1623,6 +1652,10 @@ used." ((parent-is "^read_cond_lit$") parent 3) ((parent-is "^tagged_or_ctor_lit$") parent 0) ((parent-is "^ns_map_lit$") (nth-sibling 2) 1) + ;; A form on the line after its reader prefix, e.g. @ at the end of a line. + ((parent-is ,clojure-ts--reader-prefix-node-regexp) + clojure-ts--anchor-prefixed-form + clojure-ts--offset-prefixed-form) ;; https://guide.clojure.style/#body-indentation (clojure-ts--match-form-body clojure-ts--anchor-parent-opening-paren 2) ;; https://guide.clojure.style/#vertically-align-fn-args diff --git a/test/clojure-ts-mode-indentation-test.el b/test/clojure-ts-mode-indentation-test.el index 2713882..2861e11 100644 --- a/test/clojure-ts-mode-indentation-test.el +++ b/test/clojure-ts-mode-indentation-test.el @@ -716,6 +716,31 @@ DESCRIPTION is a string with the description of the spec." x)] (f 1))") + (it "should indent a form on the line after its reader prefix" + ;; No rule used to match these lines, so they kept whatever indentation + ;; they had. Start from no indentation at all. + (dolist (expected '(" +(def table + [' + [identity int? pos-int?] + [0 true false]])" + " +`(let [y# ~ + x] + y#)" + " +(swap! state assoc :n @ + (:counter state))" + " +{:get #' + handle-get}" + " +(def q ' + (a b c))")) + (with-clojure-ts-buffer (replace-regexp-in-string "\n +" "\n" expected) + (indent-region (point-min) (point-max)) + (expect (buffer-string) :to-equal expected)))) + (it "should pick up dynamic indentation rules from clojure-ts-get-indent-function" (with-clojure-ts-buffer " (defmacro my-with-in-str @@ -775,6 +800,20 @@ DESCRIPTION is a string with the description of the spec." (java.util Date UUID)))") + (it "should indent a form on the line after its reader prefix in fixed style" + (dolist (expected '(" +(swap! state assoc :n @ + (:counter state))" + " +[' + [a b]]")) + (with-temp-buffer + (let ((clojure-ts-indent-style 'fixed)) + (clojure-ts-mode) + (insert (replace-regexp-in-string "\n +" "\n" expected)) + (indent-region (point-min) (point-max)) + (expect (buffer-string) :to-equal expected))))) + (when-indenting-fixed-it "should indent namespaced maps correctly" " #:foo{:bar 1