Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitc8286e2

Browse files
rrudakovbbatsov
authored andcommitted
Support nested indentation rules
1 parent9ae1f42 commitc8286e2

File tree

5 files changed

+192
-126
lines changed

5 files changed

+192
-126
lines changed

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
-[#61](https://github.com/clojure-emacs/clojure-ts-mode/issues/61): Fix issue with indentation of collection items with metadata.
1616
- Proper syntax highlighting for expressions with metadata.
1717
- Add basic support for dynamic indentation via`clojure-ts-get-indent-function`.
18+
- Add support for nested indentation rules.
1819

1920
##0.2.3 (2025-03-04)
2021

‎README.md‎

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,8 @@ your personal config. Let's assume you want to indent `->>` and `->` like this:
188188
You can do so by putting the following in your config:
189189

190190
```emacs-lisp
191-
(setopt clojure-ts-semantic-indent-rules '(("->" . (:block 1))
192-
("->>" . (:block 1))))
191+
(setopt clojure-ts-semantic-indent-rules '(("->" . ((:block 1)))
192+
("->>" . ((:block 1)))))
193193
```
194194

195195
This means that the body of the`->`/`->>` is after the first argument.
@@ -198,16 +198,17 @@ The default set of rules is defined as
198198
`clojure-ts--semantic-indent-rules-defaults`, any rule can be overridden using
199199
customization option.
200200

201-
There are 2types of rules supported:`:block` and`:inner`,similarly to
202-
cljfmt.Ifrule is defined as`:block n`,`n`means a number of arguments after
203-
which begins the body.Ifrule is defined as`:inner n`, each form in the body
204-
is indented with 2 spaces regardless of`n`value (currently all default rules
205-
has 0 value).
201+
Twotypes of rulesaresupported:`:block` and`:inner`,mirroring those in
202+
cljfmt.When arule is defined as`:block n`,`n`represents the number of
203+
arguments preceding the body.When arule is defined as`:inner n`, each form
204+
within the expression's body, nested`n`levels deep, is indented by two
205+
spaces. These rule definitions fully reflect the[cljfmt rules](https://github.com/weavejester/cljfmt/blob/0.13.0/docs/INDENTS.md).
206206

207207
For example:
208-
-`do` has a rule`:block 0`.
209-
-`when` has a rule`:block 1`.
210-
-`defn` and`fn` have a rule`:inner 0`.
208+
-`do` has a rule`((:block 0))`.
209+
-`when` has a rule`((:block 1))`.
210+
-`defn` and`fn` have a rule`((:inner 0))`.
211+
-`letfn` has a rule`((:block 1) (:inner 2 0))`.
211212

212213
###Font Locking
213214

‎clojure-ts-mode.el‎

Lines changed: 151 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,12 @@ Default set of rules is defined in
137137
`clojure-ts--semantic-indent-rules-defaults'."
138138
:safe#'listp
139139
:type '(alist:key-type string
140-
:value-type (list (choice (const:tag"Block indentation rule":block)
141-
(const:tag"Inner indentation rule":inner))
142-
integer))
140+
:value-type (repeat (choice (list (choice (const:tag"Block indentation rule":block)
141+
(const:tag"Inner indentation rule":inner))
142+
integer)
143+
(list (const:tag"Inner indentation rule":inner)
144+
integer
145+
integer))))
143146
:package-version '(clojure-ts-mode."0.2.4"))
144147

145148
(defvarclojure-ts-mode-remappings
@@ -769,74 +772,73 @@ The possible values for this variable are
769772
((parent-is"set_lit") parent2))))
770773

771774
(defvarclojure-ts--semantic-indent-rules-defaults
772-
'(("alt!". (:block0))
773-
("alt!!". (:block0))
774-
("comment". (:block0))
775-
("cond". (:block0))
776-
("delay". (:block0))
777-
("do". (:block0))
778-
("finally". (:block0))
779-
("future". (:block0))
780-
("go". (:block0))
781-
("thread". (:block0))
782-
("try". (:block0))
783-
("with-out-str". (:block0))
784-
("defprotocol". (:block1))
785-
("binding". (:block1))
786-
("defprotocol". (:block1))
787-
("binding". (:block1))
788-
("case". (:block1))
789-
("cond->". (:block1))
790-
("cond->>". (:block1))
791-
("doseq". (:block1))
792-
("dotimes". (:block1))
793-
("doto". (:block1))
794-
("extend". (:block1))
795-
("extend-protocol". (:block1))
796-
("extend-type". (:block1))
797-
("for". (:block1))
798-
("go-loop". (:block1))
799-
("if". (:block1))
800-
("if-let". (:block1))
801-
("if-not". (:block1))
802-
("if-some". (:block1))
803-
("let". (:block1))
804-
("letfn". (:block1))
805-
("locking". (:block1))
806-
("loop". (:block1))
807-
("match". (:block1))
808-
("ns". (:block1))
809-
("struct-map". (:block1))
810-
("testing". (:block1))
811-
("when". (:block1))
812-
("when-first". (:block1))
813-
("when-let". (:block1))
814-
("when-not". (:block1))
815-
("when-some". (:block1))
816-
("while". (:block1))
817-
("with-local-vars". (:block1))
818-
("with-open". (:block1))
819-
("with-precision". (:block1))
820-
("with-redefs". (:block1))
821-
("defrecord". (:block2))
822-
("deftype". (:block2))
823-
("are". (:block2))
824-
("as->". (:block2))
825-
("catch". (:block2))
826-
("condp". (:block2))
827-
("bound-fn". (:inner0))
828-
("def". (:inner0))
829-
("defmacro". (:inner0))
830-
("defmethod". (:inner0))
831-
("defmulti". (:inner0))
832-
("defn". (:inner0))
833-
("defn-". (:inner0))
834-
("defonce". (:inner0))
835-
("deftest". (:inner0))
836-
("fdef". (:inner0))
837-
("fn". (:inner0))
838-
("reify". (:inner0))
839-
("use-fixtures". (:inner0)))
775+
'(("alt!". ((:block0)))
776+
("alt!!". ((:block0)))
777+
("comment". ((:block0)))
778+
("cond". ((:block0)))
779+
("delay". ((:block0)))
780+
("do". ((:block0)))
781+
("finally". ((:block0)))
782+
("future". ((:block0)))
783+
("go". ((:block0)))
784+
("thread". ((:block0)))
785+
("try". ((:block0)))
786+
("with-out-str". ((:block0)))
787+
("defprotocol". ((:block1) (:inner1)))
788+
("binding". ((:block1)))
789+
("case". ((:block1)))
790+
("cond->". ((:block1)))
791+
("cond->>". ((:block1)))
792+
("doseq". ((:block1)))
793+
("dotimes". ((:block1)))
794+
("doto". ((:block1)))
795+
("extend". ((:block1)))
796+
("extend-protocol". ((:block1) (:inner1)))
797+
("extend-type". ((:block1) (:inner1)))
798+
("for". ((:block1)))
799+
("go-loop". ((:block1)))
800+
("if". ((:block1)))
801+
("if-let". ((:block1)))
802+
("if-not". ((:block1)))
803+
("if-some". ((:block1)))
804+
("let". ((:block1)))
805+
("letfn". ((:block1) (:inner20)))
806+
("locking". ((:block1)))
807+
("loop". ((:block1)))
808+
("match". ((:block1)))
809+
("ns". ((:block1)))
810+
("struct-map". ((:block1)))
811+
("testing". ((:block1)))
812+
("when". ((:block1)))
813+
("when-first". ((:block1)))
814+
("when-let". ((:block1)))
815+
("when-not". ((:block1)))
816+
("when-some". ((:block1)))
817+
("while". ((:block1)))
818+
("with-local-vars". ((:block1)))
819+
("with-open". ((:block1)))
820+
("with-precision". ((:block1)))
821+
("with-redefs". ((:block1)))
822+
("defrecord". ((:block2) (:inner1)))
823+
("deftype". ((:block2) (:inner1)))
824+
("are". ((:block2)))
825+
("as->". ((:block2)))
826+
("catch". ((:block2)))
827+
("condp". ((:block2)))
828+
("bound-fn". ((:inner0)))
829+
("def". ((:inner0)))
830+
("defmacro". ((:inner0)))
831+
("defmethod". ((:inner0)))
832+
("defmulti". ((:inner0)))
833+
("defn". ((:inner0)))
834+
("defn-". ((:inner0)))
835+
("defonce". ((:inner0)))
836+
("deftest". ((:inner0)))
837+
("fdef". ((:inner0)))
838+
("fn". ((:inner0)))
839+
("reify". ((:inner0) (:inner1)))
840+
("proxy". ((:block2) (:inner1)))
841+
("use-fixtures". ((:inner0))))
840842
"Default semantic indentation rules.
841843
842844
The format reflects cljfmt indentation rules. All the default rules are
@@ -882,22 +884,87 @@ The returned value is expected to be the same as
882884
`clojure-get-indent-function' from`clojure-mode' for compatibility
883885
reasons.")
884886

887+
(defunclojure-ts--unwrap-dynamic-spec (speccurrent-depth)
888+
"Recursively unwrap SPEC, incrementally increasing the CURRENT-DEPTH.
889+
890+
This function accepts a list SPEC, like ((:defn)) and produce a proper
891+
indent rule. For example, ((:defn)) is converted to (:inner 2),
892+
and (:defn) is converted to (:inner 1)."
893+
(if (consp spec)
894+
(clojure-ts--unwrap-dynamic-spec (car spec) (1+ current-depth))
895+
(cond
896+
((equal spec:defn) (list:inner current-depth))
897+
(tnil))))
898+
885899
(defunclojure-ts--dynamic-indent-for-symbol (symbol-name)
886-
"Return dynamic indentation spec for SYMBOL-NAME if found.
900+
"Returns the dynamic indentation specification for SYMBOL-NAME, if found.
901+
902+
If the function`clojure-ts-get-indent-function' is defined, call it and
903+
produce a valid indentation specification from its return value.
887904
888-
If function`clojure-ts-get-indent-function' is not nil, call it and
889-
produce a valid indentation spec from the returned value.
905+
The`clojure-ts-get-indent-function' should return an indentation
906+
specification compatible with`clojure-mode', which will then be
907+
converted to a suitable`clojure-ts-mode' specification.
890908
891-
The indentation rules for`clojure-ts-mode' are simpler than for
892-
`clojure-mode' so we only take the first integer N and produce `(:block
893-
N)' rule. If an integer cannot be found, this function returns nil and
894-
the default rule is used."
909+
For example, (1 ((:defn)) nil) is converted to ((:block 1) (:inner 2))."
895910
(when (functionp clojure-ts-get-indent-function)
896911
(let ((spec (funcall clojure-ts-get-indent-function symbol-name)))
897-
(if (consp spec)
898-
`(:block ,(car spec))
899-
(when (integerp spec)
900-
`(:block,spec))))))
912+
(if (integerp spec)
913+
(list (list:block spec))
914+
(when (sequencep spec)
915+
(thread-last spec
916+
(seq-map (lambda (el)
917+
(cond
918+
((integerp el) (list:block el))
919+
((equal el:defn) (list:inner0))
920+
((consp el) (clojure-ts--unwrap-dynamic-spec el0))
921+
(tnil))))
922+
(seq-remove#'null)
923+
;; Always put`:block' to the beginning.
924+
(seq-sort (lambda (spec1_spec2)
925+
(equal (car spec1):block)))))))))
926+
927+
(defunclojure-ts--find-semantic-rule (nodeparentcurrent-depth)
928+
"Returns a suitable indentation rule for NODE, considering the CURRENT-DEPTH.
929+
930+
Attempts to find an indentation rule by examining the symbol name of the
931+
PARENT's first child. If a rule is not found, it navigates up the
932+
syntax tree and recursively attempts to find a rule, incrementally
933+
increasing the CURRENT-DEPTH. If a rule is not found upon reaching the
934+
root of the syntax tree, it returns nil. A rule is considered a match
935+
only if the CURRENT-DEPTH matches the rule's required depth."
936+
(let* ((first-child (clojure-ts--node-child-skip-metadata parent0))
937+
(symbol-name (clojure-ts--named-node-text first-child))
938+
(idx (- (treesit-node-index node)2)))
939+
(if-let* ((rule-set (or (clojure-ts--dynamic-indent-for-symbol symbol-name)
940+
(alist-get symbol-name
941+
(seq-union clojure-ts-semantic-indent-rules
942+
clojure-ts--semantic-indent-rules-defaults
943+
(lambda (e1e2) (equal (car e1) (car e2))))
944+
nil
945+
nil
946+
#'equal))))
947+
(if (zerop current-depth)
948+
(let ((rule (car rule-set)))
949+
(if (equal (car rule):block)
950+
rule
951+
(pcase-let ((`(,_,rule-depth,rule-idx) rule))
952+
(when (and (equal rule-depth current-depth)
953+
(or (null rule-idx)
954+
(equal rule-idx idx)))
955+
rule))))
956+
(thread-last rule-set
957+
(seq-filter (lambda (rule)
958+
(pcase-let ((`(,rule-type,rule-depth,rule-idx) rule))
959+
(and (equal rule-type:inner)
960+
(equal rule-depth current-depth)
961+
(or (null rule-idx)
962+
(equal rule-idx idx))))))
963+
(seq-first)))
964+
(when-let* ((new-parent (treesit-node-parent parent)))
965+
(clojure-ts--find-semantic-rule parent
966+
new-parent
967+
(1+ current-depth))))))
901968

902969
(defunclojure-ts--match-form-body (nodeparentbol)
903970
"Match if NODE has to be indented as a for body.
@@ -907,16 +974,8 @@ indentation rule in `clojure-ts--semantic-indent-rules-defaults' or
907974
`clojure-ts-semantic-indent-rules' check if NODE should be indented
908975
according to the rule. If NODE is nil, use next node after BOL."
909976
(and (clojure-ts--list-node-p parent)
910-
(let* ((first-child (clojure-ts--node-child-skip-metadata parent0))
911-
(symbol-name (clojure-ts--named-node-text first-child)))
912-
(when-let* ((rule (or (clojure-ts--dynamic-indent-for-symbol symbol-name)
913-
(alist-get symbol-name
914-
(seq-union clojure-ts-semantic-indent-rules
915-
clojure-ts--semantic-indent-rules-defaults
916-
(lambda (e1e2) (equal (car e1) (car e2))))
917-
nil
918-
nil
919-
#'equal))))
977+
(let* ((first-child (clojure-ts--node-child-skip-metadata parent0)))
978+
(when-let* ((rule (clojure-ts--find-semantic-rule node parent0)))
920979
(and (not (clojure-ts--match-with-metadata node))
921980
(let ((rule-type (car rule))
922981
(rule-value (cadr rule)))
@@ -940,19 +999,6 @@ according to the rule. If NODE is nil, use next node after BOL."
940999
(clojure-ts--keyword-node-p first-child)
9411000
(clojure-ts--var-node-p first-child)))))
9421001

943-
(defunclojure-ts--match-method-body (_nodeparent_bol)
944-
"Matches a`NODE' in the body of a`PARENT' method implementation.
945-
A method implementation referes to concrete implementations being defined in
946-
forms like deftype, defrecord, reify, proxy, etc."
947-
(and
948-
(clojure-ts--list-node-p parent)
949-
(let* ((grandparent (treesit-node-parent parent))
950-
;; auncle: gender neutral sibling of parent, aka child of grandparent
951-
(first-auncle (treesit-node-child grandparent0t)))
952-
(and (clojure-ts--list-node-p grandparent)
953-
(clojure-ts--symbol-matches-p clojure-ts--type-symbol-regexp
954-
first-auncle)))))
955-
9561002
(defvarclojure-ts--threading-macro
9571003
(eval-and-compile
9581004
(rx (and"->" (?">") line-end)))
@@ -1043,7 +1089,6 @@ if NODE has metadata and its parent has type NODE-TYPE."
10431089
((parent-is"source") parent-bol0)
10441090
(clojure-ts--match-docstring parent0)
10451091
;; https://guide.clojure.style/#body-indentation
1046-
(clojure-ts--match-method-body parent2)
10471092
(clojure-ts--match-form-body clojure-ts--anchor-parent-skip-metadata2)
10481093
;; https://guide.clojure.style/#threading-macros-alignment
10491094
(clojure-ts--match-threading-macro-arg prev-sibling0)

‎test/clojure-ts-mode-indentation-test.el‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ DESCRIPTION is a string with the description of the spec."
9696
(when (stringp symbol-name)
9797
(cond
9898
((string-equal symbol-name"my-with-in-str")1)
99-
((string-equal symbol-name"my-letfn") '(1 ((:defn) (:form)))))))
99+
((string-equal symbol-name"my-letfn") '(1 ((:defn)):form)))))
100100

101101

102102
(describe"indentation"
@@ -242,7 +242,7 @@ DESCRIPTION is a string with the description of the spec."
242242
2 3
243243
4 5
244244
6 6)"
245-
(setopt clojure-ts-semantic-indent-rules '(("are". (:block1))))
245+
(setopt clojure-ts-semantic-indent-rules '(("are". ((:block1)))))
246246
(indent-region (point-min) (point-max))
247247
(expect (buffer-string):to-equal"
248248
(are [x y]
@@ -305,8 +305,10 @@ DESCRIPTION is a string with the description of the spec."
305305
[fnspecs & body]
306306
~@body)
307307
308-
(my-letfn [(twice [x] (* x 2))
309-
(six-times [y] (* (twice y) 3))]
308+
(my-letfn [(twice [x]
309+
(* x 2))
310+
(six-times [y]
311+
(* (twice y) 3))]
310312
(println\"Twice 15 =\" (twice 15))
311313
(println\"Six times 15 =\" (six-times 15)))"
312314
(setq-local clojure-ts-get-indent-function#'cider--get-symbol-indent-mock)
@@ -318,7 +320,9 @@ DESCRIPTION is a string with the description of the spec."
318320
[fnspecs & body]
319321
~@body)
320322
321-
(my-letfn [(twice [x] (* x 2))
322-
(six-times [y] (* (twice y) 3))]
323+
(my-letfn [(twice [x]
324+
(* x 2))
325+
(six-times [y]
326+
(* (twice y) 3))]
323327
(println\"Twice 15 =\" (twice 15))
324328
(println\"Six times 15 =\" (six-times 15)))"))))

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp