@@ -76,12 +76,13 @@ public class PrettyPrinter {
76
76
/// original source. When enabling formatting, we copy the text between `disabledPosition` and the
77
77
/// current position to `outputBuffer`. From then on, we continue to format until the next
78
78
/// `disableFormatting` token.
79
- private var disabledPosition : AbsolutePosition ? = nil
80
-
81
- private var outputBuffer : String = " "
79
+ private var disabledPosition : AbsolutePosition ? = nil {
80
+ didSet {
81
+ outputBuffer. isEnabled = disabledPosition == nil
82
+ }
83
+ }
82
84
83
- /// The number of spaces remaining on the current line.
84
- private var spaceRemaining : Int
85
+ private var outputBuffer : PrettyPrintBuffer
85
86
86
87
/// Keep track of the token lengths.
87
88
private var lengths = [ Int] ( )
@@ -103,19 +104,26 @@ public class PrettyPrinter {
103
104
104
105
/// Keeps track of the line numbers and indentation states of the open (and unclosed) breaks seen
105
106
/// so far.
106
- private var activeOpenBreaks : [ ActiveOpenBreak ] = [ ]
107
+ private var activeOpenBreaks : [ ActiveOpenBreak ] = [ ] {
108
+ didSet {
109
+ outputBuffer. currentIndentation = currentIndentation
110
+ }
111
+ }
107
112
108
113
/// Stack of the active breaking contexts.
109
114
private var activeBreakingContexts : [ ActiveBreakingContext ] = [ ]
110
115
111
116
/// The most recently ended breaking context, used to force certain following `contextual` breaks.
112
117
private var lastEndedBreakingContext : ActiveBreakingContext ? = nil
113
118
114
- /// Keeps track of the current line number being printed.
115
- private var lineNumber : Int = 1
116
-
117
119
/// Indicates whether or not the current line being printed is a continuation line.
118
- private var currentLineIsContinuation = false
120
+ private var currentLineIsContinuation = false {
121
+ didSet {
122
+ if oldValue != currentLineIsContinuation {
123
+ outputBuffer. currentIndentation = currentIndentation
124
+ }
125
+ }
126
+ }
119
127
120
128
/// Keeps track of the continuation line state as you go into and out of open-close break groups.
121
129
private var continuationStack : [ Bool ] = [ ]
@@ -124,18 +132,6 @@ public class PrettyPrinter {
124
132
/// corresponding end token are encountered.
125
133
private var commaDelimitedRegionStack : [ Int ] = [ ]
126
134
127
- /// Keeps track of the most recent number of consecutive newlines that have been printed.
128
- ///
129
- /// This value is reset to zero whenever non-newline content is printed.
130
- private var consecutiveNewlineCount = 0
131
-
132
- /// Keeps track of the most recent number of spaces that should be printed before the next text
133
- /// token.
134
- private var pendingSpaces = 0
135
-
136
- /// Indicates whether or not the printer is currently at the beginning of a line.
137
- private var isAtStartOfLine = true
138
-
139
135
/// Tracks how many printer control tokens to suppress firing breaks are active.
140
136
private var activeBreakSuppressionCount = 0
141
137
@@ -173,7 +169,7 @@ public class PrettyPrinter {
173
169
/// line number to increase by one by the time we reach the break, when we really wish to consider
174
170
/// the break as being located at the end of the previous line.
175
171
private var openCloseBreakCompensatingLineNumber : Int {
176
- return isAtStartOfLine ? lineNumber - 1 : lineNumber
172
+ return outputBuffer . lineNumber - ( outputBuffer . isAtStartOfLine ? 1 : 0 )
177
173
}
178
174
179
175
/// Creates a new PrettyPrinter with the provided formatting configuration.
@@ -193,77 +189,9 @@ public class PrettyPrinter {
193
189
selection: context. selection,
194
190
operatorTable: context. operatorTable)
195
191
self . maxLineLength = configuration. lineLength
196
- self . spaceRemaining = self . maxLineLength
197
192
self . printTokenStream = printTokenStream
198
193
self . whitespaceOnly = whitespaceOnly
199
- }
200
-
201
- /// Append the given string to the output buffer.
202
- ///
203
- /// No further processing is performed on the string.
204
- private func writeRaw< S: StringProtocol > ( _ str: S ) {
205
- if disabledPosition == nil {
206
- outputBuffer. append ( String ( str) )
207
- }
208
- }
209
-
210
- /// Writes newlines into the output stream, taking into account any preexisting consecutive
211
- /// newlines and the maximum allowed number of blank lines.
212
- ///
213
- /// This function does some implicit collapsing of consecutive newlines to ensure that the
214
- /// results are consistent when breaks and explicit newlines coincide. For example, imagine a
215
- /// break token that fires (thus creating a single non-discretionary newline) because it is
216
- /// followed by a group that contains 2 discretionary newlines that were found in the user's
217
- /// source code at that location. In that case, the break "overlaps" with the discretionary
218
- /// newlines and it will write a newline before we get to the discretionaries. Thus, we have to
219
- /// subtract the previously written newlines during the second call so that we end up with the
220
- /// correct number overall.
221
- ///
222
- /// - Parameter newlines: The number and type of newlines to write.
223
- private func writeNewlines( _ newlines: NewlineBehavior ) {
224
- let numberToPrint : Int
225
- switch newlines {
226
- case . elective:
227
- numberToPrint = consecutiveNewlineCount == 0 ? 1 : 0
228
- case . soft( let count, _) :
229
- // We add 1 to the max blank lines because it takes 2 newlines to create the first blank line.
230
- numberToPrint = min ( count, configuration. maximumBlankLines + 1 ) - consecutiveNewlineCount
231
- case . hard( let count) :
232
- numberToPrint = count
233
- }
234
-
235
- guard numberToPrint > 0 else { return }
236
- writeRaw ( String ( repeating: " \n " , count: numberToPrint) )
237
- lineNumber += numberToPrint
238
- isAtStartOfLine = true
239
- consecutiveNewlineCount += numberToPrint
240
- pendingSpaces = 0
241
- }
242
-
243
- /// Request that the given number of spaces be printed out before the next text token.
244
- ///
245
- /// Spaces are printed only when the next text token is printed in order to prevent us from
246
- /// printing lines that are only whitespace or have trailing whitespace.
247
- private func enqueueSpaces( _ count: Int ) {
248
- pendingSpaces += count
249
- spaceRemaining -= count
250
- }
251
-
252
- /// Writes the given text to the output stream.
253
- ///
254
- /// Before printing the text, this function will print any line-leading indentation or interior
255
- /// leading spaces that are required before the text itself.
256
- private func write( _ text: String ) {
257
- if isAtStartOfLine {
258
- writeRaw ( currentIndentation. indentation ( ) )
259
- spaceRemaining = maxLineLength - currentIndentation. length ( in: configuration)
260
- isAtStartOfLine = false
261
- } else if pendingSpaces > 0 {
262
- writeRaw ( String ( repeating: " " , count: pendingSpaces) )
263
- }
264
- writeRaw ( text)
265
- consecutiveNewlineCount = 0
266
- pendingSpaces = 0
194
+ self . outputBuffer = PrettyPrintBuffer ( maximumBlankLines: configuration. maximumBlankLines, tabWidth: configuration. tabWidth)
267
195
}
268
196
269
197
/// Print out the provided token, and apply line-wrapping and indentation as needed.
@@ -285,7 +213,7 @@ public class PrettyPrinter {
285
213
286
214
switch token {
287
215
case . contextualBreakingStart:
288
- activeBreakingContexts. append ( ActiveBreakingContext ( lineNumber: lineNumber) )
216
+ activeBreakingContexts. append ( ActiveBreakingContext ( lineNumber: outputBuffer . lineNumber) )
289
217
290
218
// Discard the last finished breaking context to keep it from effecting breaks inside of the
291
219
// new context. The discarded context has already either had an impact on the contextual break
@@ -306,7 +234,7 @@ public class PrettyPrinter {
306
234
// the group.
307
235
case . open( let breaktype) :
308
236
// Determine if the break tokens in this group need to be forced.
309
- if ( length > spaceRemaining || lastBreak) , case . consistent = breaktype {
237
+ if ( !canFit ( length) || lastBreak) , case . consistent = breaktype {
310
238
forceBreakStack. append ( true )
311
239
} else {
312
240
forceBreakStack. append ( false )
@@ -348,7 +276,7 @@ public class PrettyPrinter {
348
276
// scope), so we need the continuation indentation to persist across all the lines in that
349
277
// scope. Additionally, continuation open breaks must indent when the break fires.
350
278
let continuationBreakWillFire = openKind == . continuation
351
- && ( isAtStartOfLine || length > spaceRemaining || mustBreak)
279
+ && ( outputBuffer . isAtStartOfLine || !canFit ( length) || mustBreak)
352
280
let contributesContinuationIndent = currentLineIsContinuation || continuationBreakWillFire
353
281
354
282
activeOpenBreaks. append (
@@ -377,7 +305,7 @@ public class PrettyPrinter {
377
305
if matchingOpenBreak. contributesBlockIndent {
378
306
// The actual line number is used, instead of the compensating line number. When the close
379
307
// break is at the start of a new line, the block indentation isn't carried to the new line.
380
- let currentLine = lineNumber
308
+ let currentLine = outputBuffer . lineNumber
381
309
// When two or more open breaks are encountered on the same line, only the final open
382
310
// break is allowed to increase the block indent, avoiding multiple block indents. As the
383
311
// open breaks on that line are closed, the new final open break must be enabled again to
@@ -395,7 +323,7 @@ public class PrettyPrinter {
395
323
// If it's a mandatory breaking close, then we must break (regardless of line length) if
396
324
// the break is on a different line than its corresponding open break.
397
325
mustBreak = openedOnDifferentLine
398
- } else if spaceRemaining == 0 {
326
+ } else if !canFit ( ) {
399
327
// If there is no room left on the line, then we must force this break to fire so that the
400
328
// next token that comes along (typically a closing bracket of some kind) ends up on the
401
329
// next line.
@@ -453,13 +381,13 @@ public class PrettyPrinter {
453
381
// context includes a multiline trailing closure or multiline function argument list.
454
382
if let lastBreakingContext = lastEndedBreakingContext {
455
383
if configuration. lineBreakAroundMultilineExpressionChainComponents {
456
- mustBreak = lastBreakingContext. lineNumber != lineNumber
384
+ mustBreak = lastBreakingContext. lineNumber != outputBuffer . lineNumber
457
385
}
458
386
}
459
387
460
388
// Wait for a contextual break to fire and then update the breaking behavior for the rest of
461
389
// the contextual breaks in this scope to match the behavior of the one that fired.
462
- let willFire = ( !isAtStartOfLine && length > spaceRemaining ) || mustBreak
390
+ let willFire = !canFit ( length) || mustBreak
463
391
if willFire {
464
392
// Update the active breaking context according to the most recently finished breaking
465
393
// context so all following contextual breaks in this scope to have matching behavior.
@@ -468,7 +396,7 @@ public class PrettyPrinter {
468
396
case . unset = activeContext. contextualBreakingBehavior
469
397
{
470
398
activeBreakingContexts [ activeBreakingContexts. count - 1 ] . contextualBreakingBehavior =
471
- ( closedContext. lineNumber == lineNumber) ? . continuation : . maintain
399
+ ( closedContext. lineNumber == outputBuffer . lineNumber) ? . continuation : . maintain
472
400
}
473
401
}
474
402
@@ -499,52 +427,46 @@ public class PrettyPrinter {
499
427
}
500
428
501
429
let suppressBreaking = isBreakingSuppressed && !overrideBreakingSuppressed
502
- if !suppressBreaking && ( ( !isAtStartOfLine && length > spaceRemaining ) || mustBreak) {
430
+ if !suppressBreaking && ( !canFit ( length) || mustBreak) {
503
431
currentLineIsContinuation = isContinuationIfBreakFires
504
- writeNewlines ( newline)
432
+ outputBuffer . writeNewlines ( newline)
505
433
lastBreak = true
506
434
} else {
507
- if isAtStartOfLine {
435
+ if outputBuffer . isAtStartOfLine {
508
436
// Make sure that the continuation status is correct even at the beginning of a line
509
437
// (for example, after a newline token). This is necessary because a discretionary newline
510
438
// might be inserted into the token stream before a continuation break, and the length of
511
439
// that break might not be enough to satisfy the conditions above but we still need to
512
440
// treat the line as a continuation.
513
441
currentLineIsContinuation = isContinuationIfBreakFires
514
442
}
515
- enqueueSpaces ( size)
443
+ outputBuffer . enqueueSpaces ( size)
516
444
lastBreak = false
517
445
}
518
446
519
447
// Print out the number of spaces according to the size, and adjust spaceRemaining.
520
448
case . space( let size, _) :
521
- enqueueSpaces ( size)
449
+ outputBuffer . enqueueSpaces ( size)
522
450
523
451
// Print any indentation required, followed by the text content of the syntax token.
524
452
case . syntax( let text) :
525
453
guard !text. isEmpty else { break }
526
454
lastBreak = false
527
- write ( text)
528
- spaceRemaining -= text. count
455
+ outputBuffer. write ( text)
529
456
530
457
case . comment( let comment, let wasEndOfLine) :
531
458
lastBreak = false
532
459
533
- write ( comment. print ( indent: currentIndentation) )
534
460
if wasEndOfLine {
535
- if comment. length > spaceRemaining && ! isBreakingSuppressed {
461
+ if ! ( canFit ( comment. length) || isBreakingSuppressed) {
536
462
diagnose ( . moveEndOfLineComment, category: . endOfLineComment)
537
463
}
538
- } else {
539
- spaceRemaining -= comment. length
540
464
}
465
+ outputBuffer. write ( comment. print ( indent: currentIndentation) )
541
466
542
467
case . verbatim( let verbatim) :
543
- writeRaw ( verbatim. print ( indent: currentIndentation) )
544
- consecutiveNewlineCount = 0
545
- pendingSpaces = 0
468
+ outputBuffer. writeVerbatim ( verbatim. print ( indent: currentIndentation) , length)
546
469
lastBreak = false
547
- spaceRemaining -= length
548
470
549
471
case . printerControl( let kind) :
550
472
switch kind {
@@ -583,8 +505,7 @@ public class PrettyPrinter {
583
505
584
506
let shouldWriteComma = whitespaceOnly ? hasTrailingComma : shouldHaveTrailingComma
585
507
if shouldWriteComma {
586
- write ( " , " )
587
- spaceRemaining -= 1
508
+ outputBuffer. write ( " , " )
588
509
}
589
510
590
511
case . enableFormatting( let enabledPosition) :
@@ -607,21 +528,21 @@ public class PrettyPrinter {
607
528
}
608
529
609
530
self . disabledPosition = nil
610
- writeRaw ( text)
611
- if text. hasSuffix ( " \n " ) {
612
- isAtStartOfLine = true
613
- consecutiveNewlineCount = 1
614
- } else {
615
- isAtStartOfLine = false
616
- consecutiveNewlineCount = 0
617
- }
531
+ outputBuffer. writeVerbatimAfterEnablingFormatting ( text)
618
532
619
533
case . disableFormatting( let newPosition) :
620
534
assert ( disabledPosition == nil )
621
535
disabledPosition = newPosition
622
536
}
623
537
}
624
538
539
+ /// Indicates whether the current line can fit a string of the given length. If no length
540
+ /// is given, it indicates whether the current line can accomodate *any* text.
541
+ private func canFit( _ length: Int = 1 ) -> Bool {
542
+ let spaceRemaining = configuration. lineLength - outputBuffer. column
543
+ return outputBuffer. isAtStartOfLine || length <= spaceRemaining
544
+ }
545
+
625
546
/// Scan over the array of Tokens and calculate their lengths.
626
547
///
627
548
/// This method is based on the `scan` function described in Derek Oppen's "Pretty Printing" paper
@@ -748,7 +669,7 @@ public class PrettyPrinter {
748
669
fatalError ( " At least one .break(.open) was not matched by a .break(.close) " )
749
670
}
750
671
751
- return outputBuffer
672
+ return outputBuffer. output
752
673
}
753
674
754
675
/// Used to track the indentation level for the debug token stream output.
@@ -843,11 +764,11 @@ public class PrettyPrinter {
843
764
/// Emits a finding with the given message and category at the current location in `outputBuffer`.
844
765
private func diagnose( _ message: Finding . Message , category: PrettyPrintFindingCategory ) {
845
766
// Add 1 since columns uses 1-based indices.
846
- let column = maxLineLength - spaceRemaining + 1
767
+ let column = outputBuffer . column + 1
847
768
context. findingEmitter. emit (
848
769
message,
849
770
category: category,
850
- location: Finding . Location ( file: context. fileURL. path, line: lineNumber, column: column) )
771
+ location: Finding . Location ( file: context. fileURL. path, line: outputBuffer . lineNumber, column: column) )
851
772
}
852
773
}
853
774
0 commit comments