Skip to content

Align the whole region in clojure-ts-indent-region - #159

Merged
bbatsov merged 1 commit into
clojure-emacs:mainfrom
jasonjckn:fix-indent-region-align-to-region-end
Oct 29, 2026
Merged

bbatsov merged 1 commit into
clojure-emacs:mainfrom
jasonjckn:fix-indent-region-align-to-region-end

Conversation

@jasonjckn

@jasonjckn jasonjckn commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

clojure-ts-indent-region first indents the region, then aligns forms in it up to the region's original END. But indenting usually adds whitespace, so by the time the align step runs, the region really ends further on. Forms in that last stretch don't get aligned. Running indent-region (or a formatter built on it) a second time aligns them, which is why formatting a file twice could change it again.

This PR keeps END as a marker, so the align step uses the region's end after indenting. One pass now gives what two passes gave before.

Examples

Each example shows the result of indent-region with default settings and alignment turned on (clojure-ts-align-forms-automatically / clojure-align-forms-automatically). Identical outputs are grouped under one heading.

Several aligned forms; only the last one is left unaligned

One pass with clojure-mode or clojure-ts-mode main:

(let [a           1
      bbbbbb      2
      {:keys [c]} x]
  c)
(cond
  (= a 1)          :one
  (= bbbbbb 22222) :two
  :else            :other)
(condp = x
  1   :one
  222 :two)
(case x
  :a 1
  :bbbbb 2)

A second pass with clojure-mode or clojure-ts-mode main, or one pass with this PR:

(let [a           1
      bbbbbb      2
      {:keys [c]} x]
  c)
(cond
  (= a 1)          :one
  (= bbbbbb 22222) :two
  :else            :other)
(condp = x
  1   :one
  222 :two)
(case x
  :a     1
  :bbbbb 2)

A deeply nested function followed by a short case

One pass with clojure-mode or clojure-ts-mode main:

(defn handler [request]
  (let [user (:user request)]
    (when user
      (let [roles (:roles user)]
        (doseq [role roles]
          (when (allowed? role)
            (log/info "allowed" role)
            (respond user role)))))))

(case op
  :get 1
  :delete 2)

A second pass with clojure-mode or clojure-ts-mode main, or one pass with this PR:

(defn handler [request]
  (let [user (:user request)]
    (when user
      (let [roles (:roles user)]
        (doseq [role roles]
          (when (allowed? role)
            (log/info "allowed" role)
            (respond user role)))))))

(case op
  :get    1
  :delete 2)

A test namespace ending with a short cond->

One pass with clojure-mode or clojure-ts-mode main:

(deftest parse-test
  (testing "numbers"
    (testing "integers"
      (is (= 1 (parse "1")))
      (is (= -1 (parse "-1"))))
    (testing "decimals"
      (is (= 1.5 (parse "1.5")))
      (is (= -0.5 (parse "-0.5")))))
  (testing "strings"
    (is (= "a" (parse "\"a\"")))))

(cond-> m
  a f
  bbb g)

A second pass with clojure-mode or clojure-ts-mode main, or one pass with this PR:

(deftest parse-test
  (testing "numbers"
    (testing "integers"
      (is (= 1 (parse "1")))
      (is (= -1 (parse "-1"))))
    (testing "decimals"
      (is (= 1.5 (parse "1.5")))
      (is (= -0.5 (parse "-0.5")))))
  (testing "strings"
    (is (= "a" (parse "\"a\"")))))

(cond-> m
  a   f
  bbb g)

clojure-mode's indent-region has the same one-pass behaviour, as the first and second outputs show. This PR fixes it for clojure-ts-mode only.

Tests

This PR adds a new spec, should align the forms at the end of the region after reindenting. It fails on main and passes with this PR.

How it was checked locally:

  • make all (Eldev: compile with warnings as errors, lint, indent check, tests) passes on Emacs 30.1 (the nix-emacs-ci build CI uses) and on 31.1.50, the closest local build to CI's snapshot.

Notes

  • Idempotence check: formatting with main and then formatting again changes 23 of 146 inputs (73 real-world files as-is, plus the same files with indentation stripped). With this PR, none change.
  • The examples only reproduce when the final aligned form starts within the whitespace that indenting adds before it, which is why they end with a short form.

Part of a series

This is one of 7 small, independent PRs, each fixing one problem found while making clojure-ts-mode format like clojure-mode. Each one is based on main and none depends on another. Some touch neighbouring lines in CHANGELOG.md and the indentation tests, so once one is merged the others may need a quick rebase, which we're happy to do:

  1. Align threading macro args with the first arg #153: threading macro arguments
  2. Don't count metadata and comments as block form arguments #154: metadata/comments in :block forms
  3. Indent a form on the line after its reader prefix #155: form on the line after a reader prefix
  4. Align clojure.test/are, not the nonexistent clojure.core/are #156: clojure.test/are alignment
  5. Compile the align query from the current align form options #157: align options changed after loading
  6. Run the two metadata indentation specs #158: two specs that checked nothing (test-only)
  7. Align the whole region in clojure-ts-indent-region #159: indent-region leaving the last forms unaligned (this PR)

About this PR

The code, tests and this description were generated by an AI (Claude, by Anthropic), at @jasonjckn's request. Sorry for the PR spam: the fixes are split up so that each one can be accepted or rejected on its own instead of in one big change. If AI-generated contributions are a problem for this project, please feel free to close these PRs right away.


  • The commits are consistent with our contribution guidelines.
  • You've run M-x checkdoc and fixed any warnings in the code you've written.
  • You've updated the changelog (if adding/changing user-visible functionality).
  • You've updated the readme (if adding/changing user-visible functionality).

clojure-ts-indent-region indents the region, then aligns it up to the
END it was given.  Indenting adds whitespace, so the region's real end
moves, but the align step still stopped at the old position, leaving
forms at the end of the region unaligned.  A second indent-region then
aligned them, which is why formatting a file twice changed it.

Keep END as a marker so the align step uses where the region ends after
indenting.
@bbatsov
bbatsov force-pushed the fix-indent-region-align-to-region-end branch from 62fa2a4 to 5d18ce8 Compare October 29, 2026 06:13
@bbatsov
bbatsov merged commit 914223c into clojure-emacs:main Oct 29, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants