Skip to content

Commit 7d1677f

Browse files
committed
absorbRHS: Unindent concatenations
1 parent f1b271b commit 7d1677f

File tree

14 files changed

+686
-665
lines changed

14 files changed

+686
-665
lines changed

src/Nixfmt/Pretty.hs

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,34 @@ prettyApp indentFunction pre hasPost f a =
502502
(\fRendered -> group' RegularG $ fRendered <> line <> absorbLast a <> post)
503503
<> (if hasPost && not (null comment') then hardline else mempty)
504504

505+
prettyOp :: Bool -> Expression -> Leaf -> Doc
506+
prettyOp forceFirstTermWide operation op =
507+
let -- Walk the operation tree and put a list of things on the same level.
508+
-- We still need to keep the operators around because they might have comments attached to them.
509+
-- An operator is put together with its succeeding expression. Only the first operand has none.
510+
flatten :: Maybe Leaf -> Expression -> [(Maybe Leaf, Expression)]
511+
flatten opL (Operation a opR b) | opR == op = flatten opL a ++ flatten (Just opR) b
512+
flatten opL x = [(opL, x)]
513+
514+
-- Called on every operand except the first one (a.k.a. RHS)
515+
absorbOperation :: Expression -> Doc
516+
absorbOperation (Term t) | isAbsorbable t = hardspace <> pretty t
517+
-- Force nested operations to start on a new line
518+
absorbOperation x@(Operation{}) = group' RegularG $ line <> pretty x
519+
-- Force applications to start on a new line if more than the last argument is multiline
520+
absorbOperation (Application f a) = group $ prettyApp False line False f a
521+
absorbOperation x = hardspace <> pretty x
522+
523+
prettyOperation :: (Maybe Leaf, Expression) -> Doc
524+
-- First element
525+
prettyOperation (Nothing, Term t) | isAbsorbableTerm t && forceFirstTermWide = prettyTermWide t
526+
prettyOperation (Nothing, expr) = pretty expr
527+
-- The others
528+
prettyOperation (Just op', expr) =
529+
line <> pretty (moveTrailingCommentUp op') <> nest (absorbOperation expr)
530+
in group' RegularG $
531+
(concatMap prettyOperation . flatten Nothing) operation
532+
505533
prettyWith :: Bool -> Expression -> Doc
506534
-- absorb the body
507535
prettyWith True (With with expr0 semicolon (Term expr1)) =
@@ -608,15 +636,14 @@ absorbRHS expr = case expr of
608636
-- Absorb if all arguments except the last fit into the line, start on new line otherwise
609637
(Application f a) -> nest $ prettyApp False line False f a
610638
(With{}) -> nest $ group' RegularG $ line <> pretty expr
611-
-- Special case `//` and `++` operations to be more compact in some cases
612-
-- Case 1: two arguments, LHS is absorbable term, RHS fits onto the last line
613-
(Operation (Term t) (LoneAnn op) b)
614-
| isAbsorbable t
615-
&& isUpdateOrConcat op
616-
-- Exclude further operations on the RHS
617-
-- Hotfix for https://github.com/NixOS/nixfmt/issues/198
618-
&& case b of (Operation{}) -> False; _ -> True ->
619-
nest $ group' RegularG $ line <> group' Priority (prettyTermWide t) <> line <> pretty op <> hardspace <> pretty b
639+
-- Special case `//` and `++` and `+` operations to be more compact in some cases
640+
-- Case 1: LHS is absorbable term, unindent concatenations
641+
-- https://github.com/NixOS/nixfmt/issues/228
642+
(Operation (Term t) op@(Ann{value}) _)
643+
| isAbsorbableTerm t
644+
&& matchFirstToken (\Ann{preTrivia} -> preTrivia == []) t
645+
&& elem value [TUpdate, TConcat, TPlus] ->
646+
hardspace <> prettyOp True expr op
620647
-- Case 2a: LHS fits onto first line, RHS is an absorbable term
621648
(Operation l (LoneAnn op) (Term t))
622649
| isAbsorbable t && isUpdateOrConcat op ->
@@ -715,31 +742,7 @@ instance Pretty Expression where
715742
| op' == TLess || op' == TGreater || op' == TLessEqual || op' == TGreaterEqual || op' == TEqual || op' == TUnequal =
716743
pretty a <> softline <> pretty op <> hardspace <> pretty b
717744
-- all other operators
718-
pretty operation@(Operation _ op _) =
719-
let -- Walk the operation tree and put a list of things on the same level.
720-
-- We still need to keep the operators around because they might have comments attached to them.
721-
-- An operator is put together with its succeeding expression. Only the first operand has none.
722-
flatten :: Maybe Leaf -> Expression -> [(Maybe Leaf, Expression)]
723-
flatten opL (Operation a opR b) | opR == op = flatten opL a ++ flatten (Just opR) b
724-
flatten opL x = [(opL, x)]
725-
726-
-- Called on every operand except the first one (a.k.a. RHS)
727-
absorbOperation :: Expression -> Doc
728-
absorbOperation (Term t) | isAbsorbable t = hardspace <> pretty t
729-
-- Force nested operations to start on a new line
730-
absorbOperation x@(Operation{}) = group' RegularG $ line <> pretty x
731-
-- Force applications to start on a new line if more than the last argument is multiline
732-
absorbOperation (Application f a) = group $ prettyApp False line False f a
733-
absorbOperation x = hardspace <> pretty x
734-
735-
prettyOperation :: (Maybe Leaf, Expression) -> Doc
736-
-- First element
737-
prettyOperation (Nothing, expr) = pretty expr
738-
-- The others
739-
prettyOperation (Just op', expr) =
740-
line <> pretty (moveTrailingCommentUp op') <> nest (absorbOperation expr)
741-
in group' RegularG $
742-
(concatMap prettyOperation . flatten Nothing) operation
745+
pretty operation@(Operation _ op _) = prettyOp False operation op
743746
pretty (MemberCheck expr qmark sel) =
744747
pretty expr
745748
<> softline

standard.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,6 +1282,27 @@ Notable special cases are:
12821282
dependencies
12831283
];
12841284
```
1285+
- Operator chains (specifically, `++`, `//` and `+`) where the first element is absorbable are treated specially to avoid diff churn. Assuming the following input:
1286+
```nix
1287+
foo = [
1288+
bar
1289+
];
1290+
```
1291+
Normally (see "otherwise newline and indent" above), concatenating items onto that list would be formatted like this, causing the entire list to re-indent:
1292+
```nix
1293+
foo =
1294+
[
1295+
bar
1296+
]
1297+
++ baz;
1298+
```
1299+
Instead, we format it as such:
1300+
```nix
1301+
foo = [
1302+
bar
1303+
]
1304+
++ baz;
1305+
```
12851306

12861307
**Alternatives**
12871308

test/diff/attr_set/out-pure.nix

Lines changed: 57 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -131,22 +131,21 @@
131131
# several
132132
items
133133
];
134-
a =
135-
[
136-
some
137-
flags # multiline
138-
]
139-
++ [ short ]
140-
++ [
141-
more
142-
stuff # multiline
143-
]
144-
++ (if foo then [ bar ] else [ baz ])
145-
++ [ ]
146-
++ (optionals condition [
147-
more
148-
items
149-
]);
134+
a = [
135+
some
136+
flags # multiline
137+
]
138+
++ [ short ]
139+
++ [
140+
more
141+
stuff # multiline
142+
]
143+
++ (if foo then [ bar ] else [ baz ])
144+
++ [ ]
145+
++ (optionals condition [
146+
more
147+
items
148+
]);
150149
b = with pkgs; [
151150
a
152151
lot
@@ -276,34 +275,32 @@
276275
"${host_name}.lo.m-0.eu"
277276
];
278277
}) secret-config.ssh-hosts;
279-
programs.ssh.knownHosts9 =
280-
{
281-
multi = 1;
282-
line = 2;
283-
}
284-
// lib.mapAttrs (
285-
host_name: publicKey: {
286-
inherit publicKey;
287-
extraHostNames = [
288-
"${host_name}.m-0.eu"
289-
"${host_name}.vpn.m-0.eu"
290-
"${host_name}.lo.m-0.eu"
291-
];
292-
}
293-
);
294-
programs.ssh.knownHosts10 =
295-
{
296-
multi = 1;
297-
line = 2;
298-
}
299-
// lib.mapAttrs (host_name: publicKey: {
278+
programs.ssh.knownHosts9 = {
279+
multi = 1;
280+
line = 2;
281+
}
282+
// lib.mapAttrs (
283+
host_name: publicKey: {
300284
inherit publicKey;
301285
extraHostNames = [
302286
"${host_name}.m-0.eu"
303287
"${host_name}.vpn.m-0.eu"
304288
"${host_name}.lo.m-0.eu"
305289
];
306-
}) secret-config.ssh-hosts;
290+
}
291+
);
292+
programs.ssh.knownHosts10 = {
293+
multi = 1;
294+
line = 2;
295+
}
296+
// lib.mapAttrs (host_name: publicKey: {
297+
inherit publicKey;
298+
extraHostNames = [
299+
"${host_name}.m-0.eu"
300+
"${host_name}.vpn.m-0.eu"
301+
"${host_name}.lo.m-0.eu"
302+
];
303+
}) secret-config.ssh-hosts;
307304
}
308305

309306
# Parentheses
@@ -395,17 +392,19 @@
395392
+ ccccccccccccccccccccccccccccc
396393
+ ddddddddddddddddddddddddddddd;
397394

398-
boot.kernelParams =
399-
[ aaaaaaaaaaaaaa ]
400-
++ optionals config.boot.vesa [
401-
"vga=0x317"
402-
"nomodeset"
403-
];
395+
boot.kernelParams = [
396+
aaaaaaaaaaaaaa
397+
]
398+
++ optionals config.boot.vesa [
399+
"vga=0x317"
400+
"nomodeset"
401+
];
404402

405-
foo2 =
406-
[ bar ]
407-
++ baz # newline!
408-
++ meow;
403+
foo2 = [
404+
bar
405+
]
406+
++ baz # newline!
407+
++ meow;
409408

410409
foo3 =
411410
some function application that kinda is long # and
@@ -422,17 +421,21 @@
422421
a
423422
b
424423
c
425-
] ++ more stuff;
424+
]
425+
++ more stuff;
426426

427427
foo4 # nasty
428-
= # comments
429-
[ bar ]
430-
++ baz # newline!
431-
++ meow;
428+
= # comments
429+
[
430+
bar
431+
]
432+
++ baz # newline!
433+
++ meow;
432434

433435
environment.systemPackages =
434436
# Include the PAM modules in the system path mostly for the manpages.
435-
[ package ] ++ lib.optional config.users.ldap.enable pam_ldap;
437+
[ package ]
438+
++ lib.optional config.users.ldap.enable pam_ldap;
436439

437440
environment.systemPackages2 =
438441
# Include the PAM modules in the system path mostly for the manpages.

0 commit comments

Comments
 (0)