@@ -44,6 +44,25 @@ public class PrettyPrinter {
44
44
var contributesBlockIndent : Bool
45
45
}
46
46
47
+ /// Records state of `contextualBreakingStart` tokens.
48
+ private struct ActiveBreakingContext {
49
+ /// The line number in the `outputBuffer` where a start token appeared.
50
+ let lineNumber : Int
51
+
52
+ enum BreakingBehavior {
53
+ /// The behavior hasn't been determined. This is treated as `continuation`.
54
+ case unset
55
+ /// The break is created as a `continuation` break, setting `currentLineIsContinuation` when
56
+ /// it fires.
57
+ case continuation
58
+ /// The break maintains the existing value of `currentLineIsContinuation` when it fires.
59
+ case maintain
60
+ }
61
+
62
+ /// The behavior to use when a `contextual` break fires inside of this break context.
63
+ var contextualBreakingBehavior = BreakingBehavior . unset
64
+ }
65
+
47
66
private let context : Context
48
67
private var configuration : Configuration { return context. configuration }
49
68
private let maxLineLength : Int
@@ -75,6 +94,12 @@ public class PrettyPrinter {
75
94
/// so far.
76
95
private var activeOpenBreaks : [ ActiveOpenBreak ] = [ ]
77
96
97
+ /// Stack of the active breaking contexts.
98
+ private var activeBreakingContexts : [ ActiveBreakingContext ] = [ ]
99
+
100
+ /// The most recently ended breaking context, used to force certain following `contextual` breaks.
101
+ private var lastEndedBreakingContext : ActiveBreakingContext ? = nil
102
+
78
103
/// Keeps track of the current line number being printed.
79
104
private var lineNumber : Int = 1
80
105
@@ -243,6 +268,24 @@ public class PrettyPrinter {
243
268
assert ( length >= 0 , " Token lengths must be positive " )
244
269
245
270
switch token {
271
+ case . contextualBreakingStart:
272
+ activeBreakingContexts. append ( ActiveBreakingContext ( lineNumber: lineNumber) )
273
+
274
+ // Discard the last finished breaking context to keep it from effecting breaks inside of the
275
+ // new context. The discarded context has already either had an impact on the contextual break
276
+ // after it or there was no relevant contextual break, so it's safe to discard.
277
+ lastEndedBreakingContext = nil
278
+
279
+ case . contextualBreakingEnd:
280
+ guard let closedContext = activeBreakingContexts. popLast ( ) else {
281
+ fatalError ( " Encountered unmatched contextualBreakingEnd token. " )
282
+ }
283
+
284
+ // Break contexts create scopes, and a breaking context should never be carried between
285
+ // scopes. When there's no active break context, discard the popped one to prevent carrying it
286
+ // into a new scope.
287
+ lastEndedBreakingContext = activeBreakingContexts. isEmpty ? nil : closedContext
288
+
246
289
// Check if we need to force breaks in this group, and calculate the indentation to be used in
247
290
// the group.
248
291
case . open( let breaktype) :
@@ -384,6 +427,42 @@ public class PrettyPrinter {
384
427
385
428
case . reset:
386
429
mustBreak = currentLineIsContinuation
430
+
431
+ case . contextual:
432
+ // When the last context spanned multiple lines, move the next context (in the same parent
433
+ // break context scope) onto its own line. For example, this is used when the previous
434
+ // context includes a multiline trailing closure or multiline function argument list.
435
+ if let lastBreakingContext = lastEndedBreakingContext {
436
+ if configuration. lineBreakAroundMultilineExpressionChainComponents {
437
+ mustBreak = lastBreakingContext. lineNumber != lineNumber
438
+ }
439
+ }
440
+
441
+ // Wait for a contextual break to fire and then update the breaking behavior for the rest of
442
+ // the contextual breaks in this scope to match the behavior of the one that fired.
443
+ let willFire = ( !isAtStartOfLine && length > spaceRemaining) || mustBreak
444
+ if willFire {
445
+ // Update the active breaking context according to the most recently finished breaking
446
+ // context so all following contextual breaks in this scope to have matching behavior.
447
+ if let closedContext = lastEndedBreakingContext,
448
+ let activeContext = activeBreakingContexts. last,
449
+ case . unset = activeContext. contextualBreakingBehavior
450
+ {
451
+ activeBreakingContexts [ activeBreakingContexts. count - 1 ] . contextualBreakingBehavior =
452
+ ( closedContext. lineNumber == lineNumber) ? . continuation : . maintain
453
+ }
454
+ }
455
+
456
+ if let activeBreakingContext = activeBreakingContexts. last {
457
+ switch activeBreakingContext. contextualBreakingBehavior {
458
+ case . unset, . continuation:
459
+ isContinuationIfBreakFires = true
460
+ case . maintain:
461
+ isContinuationIfBreakFires = currentLineIsContinuation
462
+ }
463
+ }
464
+
465
+ lastEndedBreakingContext = nil
387
466
}
388
467
389
468
var overrideBreakingSuppressed = false
@@ -494,6 +573,12 @@ public class PrettyPrinter {
494
573
// Calculate token lengths
495
574
for (i, token) in tokens. enumerated ( ) {
496
575
switch token {
576
+ case . contextualBreakingStart:
577
+ lengths. append ( 0 )
578
+
579
+ case . contextualBreakingEnd:
580
+ lengths. append ( 0 )
581
+
497
582
// Open tokens have lengths equal to the total of the contents of its group. The value is
498
583
// calcualted when close tokens are encountered.
499
584
case . open:
@@ -667,9 +752,17 @@ public class PrettyPrinter {
667
752
printDebugIndent ( )
668
753
print ( " [COMMA DELIMITED START Idx: \( idx) ] " )
669
754
670
- case . commaDelimitedRegionEnd:
671
- printDebugIndent ( )
672
- print ( " [COMMA DELIMITED END Idx: \( idx) ] " )
755
+ case . commaDelimitedRegionEnd:
756
+ printDebugIndent ( )
757
+ print ( " [COMMA DELIMITED END Idx: \( idx) ] " )
758
+
759
+ case . contextualBreakingStart:
760
+ printDebugIndent ( )
761
+ print ( " [START BREAKING CONTEXT Idx: \( idx) ] " )
762
+
763
+ case . contextualBreakingEnd:
764
+ printDebugIndent ( )
765
+ print ( " [END BREAKING CONTEXT Idx: \( idx) ] " )
673
766
}
674
767
}
675
768
0 commit comments