From a8ff3ca6045e1fbef3ef76ea73382028421fe450 Mon Sep 17 00:00:00 2001 From: Shawn Hyam Date: Tue, 25 Jun 2024 10:55:23 -0400 Subject: [PATCH 1/2] Split the PrettyPrint class into two pieces. This change breaks up the PrettyPrint class into one piece that is responsible for tracking the state of the pretty printing algorithm - processing the tokens, tracking the break stack, comma delimited region stack, etc.; and another piece that is responsible for assembling the output and tracking the state of the output - current line, column position, and indentation. --- .../PrettyPrint/Indent+Length.swift | 10 +- .../SwiftFormat/PrettyPrint/PrettyPrint.swift | 175 +++++------------- .../PrettyPrint/PrettyPrintBuffer.swift | 140 ++++++++++++++ 3 files changed, 194 insertions(+), 131 deletions(-) create mode 100644 Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift diff --git a/Sources/SwiftFormat/PrettyPrint/Indent+Length.swift b/Sources/SwiftFormat/PrettyPrint/Indent+Length.swift index 062d50a07..3094735be 100644 --- a/Sources/SwiftFormat/PrettyPrint/Indent+Length.swift +++ b/Sources/SwiftFormat/PrettyPrint/Indent+Length.swift @@ -22,10 +22,10 @@ extension Indent { return String(repeating: character, count: count) } - func length(in configuration: Configuration) -> Int { + func length(tabWidth: Int) -> Int { switch self { case .spaces(let count): return count - case .tabs(let count): return count * configuration.tabWidth + case .tabs(let count): return count * tabWidth } } } @@ -36,6 +36,10 @@ extension Array where Element == Indent { } func length(in configuration: Configuration) -> Int { - return reduce(into: 0) { $0 += $1.length(in: configuration) } + return self.length(tabWidth: configuration.tabWidth) + } + + func length(tabWidth: Int) -> Int { + return reduce(into: 0) { $0 += $1.length(tabWidth: tabWidth) } } } diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift index 0b4ff792a..5ab608a8a 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift @@ -76,12 +76,13 @@ public class PrettyPrinter { /// original source. When enabling formatting, we copy the text between `disabledPosition` and the /// current position to `outputBuffer`. From then on, we continue to format until the next /// `disableFormatting` token. - private var disabledPosition: AbsolutePosition? = nil - - private var outputBuffer: String = "" + private var disabledPosition: AbsolutePosition? = nil { + didSet { + outputBuffer.isEnabled = disabledPosition == nil + } + } - /// The number of spaces remaining on the current line. - private var spaceRemaining: Int + private var outputBuffer: PrettyPrintBuffer /// Keep track of the token lengths. private var lengths = [Int]() @@ -103,7 +104,11 @@ public class PrettyPrinter { /// Keeps track of the line numbers and indentation states of the open (and unclosed) breaks seen /// so far. - private var activeOpenBreaks: [ActiveOpenBreak] = [] + private var activeOpenBreaks: [ActiveOpenBreak] = [] { + didSet { + outputBuffer.currentIndentation = currentIndentation + } + } /// Stack of the active breaking contexts. private var activeBreakingContexts: [ActiveBreakingContext] = [] @@ -111,11 +116,14 @@ public class PrettyPrinter { /// The most recently ended breaking context, used to force certain following `contextual` breaks. private var lastEndedBreakingContext: ActiveBreakingContext? = nil - /// Keeps track of the current line number being printed. - private var lineNumber: Int = 1 - /// Indicates whether or not the current line being printed is a continuation line. - private var currentLineIsContinuation = false + private var currentLineIsContinuation = false { + didSet { + if oldValue != currentLineIsContinuation { + outputBuffer.currentIndentation = currentIndentation + } + } + } /// Keeps track of the continuation line state as you go into and out of open-close break groups. private var continuationStack: [Bool] = [] @@ -124,18 +132,6 @@ public class PrettyPrinter { /// corresponding end token are encountered. private var commaDelimitedRegionStack: [Int] = [] - /// Keeps track of the most recent number of consecutive newlines that have been printed. - /// - /// This value is reset to zero whenever non-newline content is printed. - private var consecutiveNewlineCount = 0 - - /// Keeps track of the most recent number of spaces that should be printed before the next text - /// token. - private var pendingSpaces = 0 - - /// Indicates whether or not the printer is currently at the beginning of a line. - private var isAtStartOfLine = true - /// Tracks how many printer control tokens to suppress firing breaks are active. private var activeBreakSuppressionCount = 0 @@ -173,7 +169,7 @@ public class PrettyPrinter { /// line number to increase by one by the time we reach the break, when we really wish to consider /// the break as being located at the end of the previous line. private var openCloseBreakCompensatingLineNumber: Int { - return isAtStartOfLine ? lineNumber - 1 : lineNumber + return outputBuffer.lineNumber - (outputBuffer.isAtStartOfLine ? 1 : 0) } /// Creates a new PrettyPrinter with the provided formatting configuration. @@ -193,77 +189,9 @@ public class PrettyPrinter { selection: context.selection, operatorTable: context.operatorTable) self.maxLineLength = configuration.lineLength - self.spaceRemaining = self.maxLineLength self.printTokenStream = printTokenStream self.whitespaceOnly = whitespaceOnly - } - - /// Append the given string to the output buffer. - /// - /// No further processing is performed on the string. - private func writeRaw(_ str: S) { - if disabledPosition == nil { - outputBuffer.append(String(str)) - } - } - - /// Writes newlines into the output stream, taking into account any preexisting consecutive - /// newlines and the maximum allowed number of blank lines. - /// - /// This function does some implicit collapsing of consecutive newlines to ensure that the - /// results are consistent when breaks and explicit newlines coincide. For example, imagine a - /// break token that fires (thus creating a single non-discretionary newline) because it is - /// followed by a group that contains 2 discretionary newlines that were found in the user's - /// source code at that location. In that case, the break "overlaps" with the discretionary - /// newlines and it will write a newline before we get to the discretionaries. Thus, we have to - /// subtract the previously written newlines during the second call so that we end up with the - /// correct number overall. - /// - /// - Parameter newlines: The number and type of newlines to write. - private func writeNewlines(_ newlines: NewlineBehavior) { - let numberToPrint: Int - switch newlines { - case .elective: - numberToPrint = consecutiveNewlineCount == 0 ? 1 : 0 - case .soft(let count, _): - // We add 1 to the max blank lines because it takes 2 newlines to create the first blank line. - numberToPrint = min(count, configuration.maximumBlankLines + 1) - consecutiveNewlineCount - case .hard(let count): - numberToPrint = count - } - - guard numberToPrint > 0 else { return } - writeRaw(String(repeating: "\n", count: numberToPrint)) - lineNumber += numberToPrint - isAtStartOfLine = true - consecutiveNewlineCount += numberToPrint - pendingSpaces = 0 - } - - /// Request that the given number of spaces be printed out before the next text token. - /// - /// Spaces are printed only when the next text token is printed in order to prevent us from - /// printing lines that are only whitespace or have trailing whitespace. - private func enqueueSpaces(_ count: Int) { - pendingSpaces += count - spaceRemaining -= count - } - - /// Writes the given text to the output stream. - /// - /// Before printing the text, this function will print any line-leading indentation or interior - /// leading spaces that are required before the text itself. - private func write(_ text: String) { - if isAtStartOfLine { - writeRaw(currentIndentation.indentation()) - spaceRemaining = maxLineLength - currentIndentation.length(in: configuration) - isAtStartOfLine = false - } else if pendingSpaces > 0 { - writeRaw(String(repeating: " ", count: pendingSpaces)) - } - writeRaw(text) - consecutiveNewlineCount = 0 - pendingSpaces = 0 + self.outputBuffer = PrettyPrintBuffer(maximumBlankLines: configuration.maximumBlankLines, tabWidth: configuration.tabWidth) } /// Print out the provided token, and apply line-wrapping and indentation as needed. @@ -285,7 +213,7 @@ public class PrettyPrinter { switch token { case .contextualBreakingStart: - activeBreakingContexts.append(ActiveBreakingContext(lineNumber: lineNumber)) + activeBreakingContexts.append(ActiveBreakingContext(lineNumber: outputBuffer.lineNumber)) // Discard the last finished breaking context to keep it from effecting breaks inside of the // new context. The discarded context has already either had an impact on the contextual break @@ -306,7 +234,7 @@ public class PrettyPrinter { // the group. case .open(let breaktype): // Determine if the break tokens in this group need to be forced. - if (length > spaceRemaining || lastBreak), case .consistent = breaktype { + if (shouldBreak(length) || lastBreak), case .consistent = breaktype { forceBreakStack.append(true) } else { forceBreakStack.append(false) @@ -348,7 +276,7 @@ public class PrettyPrinter { // scope), so we need the continuation indentation to persist across all the lines in that // scope. Additionally, continuation open breaks must indent when the break fires. let continuationBreakWillFire = openKind == .continuation - && (isAtStartOfLine || length > spaceRemaining || mustBreak) + && (outputBuffer.isAtStartOfLine || shouldBreak(length) || mustBreak) let contributesContinuationIndent = currentLineIsContinuation || continuationBreakWillFire activeOpenBreaks.append( @@ -377,7 +305,7 @@ public class PrettyPrinter { if matchingOpenBreak.contributesBlockIndent { // The actual line number is used, instead of the compensating line number. When the close // break is at the start of a new line, the block indentation isn't carried to the new line. - let currentLine = lineNumber + let currentLine = outputBuffer.lineNumber // When two or more open breaks are encountered on the same line, only the final open // break is allowed to increase the block indent, avoiding multiple block indents. As the // open breaks on that line are closed, the new final open break must be enabled again to @@ -395,7 +323,7 @@ public class PrettyPrinter { // If it's a mandatory breaking close, then we must break (regardless of line length) if // the break is on a different line than its corresponding open break. mustBreak = openedOnDifferentLine - } else if spaceRemaining == 0 { + } else if shouldBreak(1) { // If there is no room left on the line, then we must force this break to fire so that the // next token that comes along (typically a closing bracket of some kind) ends up on the // next line. @@ -453,13 +381,13 @@ public class PrettyPrinter { // context includes a multiline trailing closure or multiline function argument list. if let lastBreakingContext = lastEndedBreakingContext { if configuration.lineBreakAroundMultilineExpressionChainComponents { - mustBreak = lastBreakingContext.lineNumber != lineNumber + mustBreak = lastBreakingContext.lineNumber != outputBuffer.lineNumber } } // Wait for a contextual break to fire and then update the breaking behavior for the rest of // the contextual breaks in this scope to match the behavior of the one that fired. - let willFire = (!isAtStartOfLine && length > spaceRemaining) || mustBreak + let willFire = shouldBreak(length) || mustBreak if willFire { // Update the active breaking context according to the most recently finished breaking // context so all following contextual breaks in this scope to have matching behavior. @@ -468,7 +396,7 @@ public class PrettyPrinter { case .unset = activeContext.contextualBreakingBehavior { activeBreakingContexts[activeBreakingContexts.count - 1].contextualBreakingBehavior = - (closedContext.lineNumber == lineNumber) ? .continuation : .maintain + (closedContext.lineNumber == outputBuffer.lineNumber) ? .continuation : .maintain } } @@ -499,12 +427,12 @@ public class PrettyPrinter { } let suppressBreaking = isBreakingSuppressed && !overrideBreakingSuppressed - if !suppressBreaking && ((!isAtStartOfLine && length > spaceRemaining) || mustBreak) { + if !suppressBreaking && (shouldBreak(length) || mustBreak) { currentLineIsContinuation = isContinuationIfBreakFires - writeNewlines(newline) + outputBuffer.writeNewlines(newline) lastBreak = true } else { - if isAtStartOfLine { + if outputBuffer.isAtStartOfLine { // Make sure that the continuation status is correct even at the beginning of a line // (for example, after a newline token). This is necessary because a discretionary newline // might be inserted into the token stream before a continuation break, and the length of @@ -512,39 +440,33 @@ public class PrettyPrinter { // treat the line as a continuation. currentLineIsContinuation = isContinuationIfBreakFires } - enqueueSpaces(size) + outputBuffer.enqueueSpaces(size) lastBreak = false } // Print out the number of spaces according to the size, and adjust spaceRemaining. case .space(let size, _): - enqueueSpaces(size) + outputBuffer.enqueueSpaces(size) // Print any indentation required, followed by the text content of the syntax token. case .syntax(let text): guard !text.isEmpty else { break } lastBreak = false - write(text) - spaceRemaining -= text.count + outputBuffer.write(text) case .comment(let comment, let wasEndOfLine): lastBreak = false - write(comment.print(indent: currentIndentation)) if wasEndOfLine { - if comment.length > spaceRemaining && !isBreakingSuppressed { + if shouldBreak(comment.length) && !isBreakingSuppressed { diagnose(.moveEndOfLineComment, category: .endOfLineComment) } - } else { - spaceRemaining -= comment.length } + outputBuffer.write(comment.print(indent: currentIndentation)) case .verbatim(let verbatim): - writeRaw(verbatim.print(indent: currentIndentation)) - consecutiveNewlineCount = 0 - pendingSpaces = 0 + outputBuffer.writeVerbatim(verbatim.print(indent: currentIndentation), length) lastBreak = false - spaceRemaining -= length case .printerControl(let kind): switch kind { @@ -583,8 +505,7 @@ public class PrettyPrinter { let shouldWriteComma = whitespaceOnly ? hasTrailingComma : shouldHaveTrailingComma if shouldWriteComma { - write(",") - spaceRemaining -= 1 + outputBuffer.write(",") } case .enableFormatting(let enabledPosition): @@ -607,14 +528,7 @@ public class PrettyPrinter { } self.disabledPosition = nil - writeRaw(text) - if text.hasSuffix("\n") { - isAtStartOfLine = true - consecutiveNewlineCount = 1 - } else { - isAtStartOfLine = false - consecutiveNewlineCount = 0 - } + outputBuffer.writeVerbatimAfterEnablingFormatting(text) case .disableFormatting(let newPosition): assert(disabledPosition == nil) @@ -622,6 +536,11 @@ public class PrettyPrinter { } } + private func shouldBreak(_ length: Int) -> Bool { + let spaceRemaining = configuration.lineLength - outputBuffer.column + return !outputBuffer.isAtStartOfLine && length > spaceRemaining + } + /// Scan over the array of Tokens and calculate their lengths. /// /// This method is based on the `scan` function described in Derek Oppen's "Pretty Printing" paper @@ -748,7 +667,7 @@ public class PrettyPrinter { fatalError("At least one .break(.open) was not matched by a .break(.close)") } - return outputBuffer + return outputBuffer.output } /// Used to track the indentation level for the debug token stream output. @@ -843,11 +762,11 @@ public class PrettyPrinter { /// Emits a finding with the given message and category at the current location in `outputBuffer`. private func diagnose(_ message: Finding.Message, category: PrettyPrintFindingCategory) { // Add 1 since columns uses 1-based indices. - let column = maxLineLength - spaceRemaining + 1 + let column = outputBuffer.column + 1 context.findingEmitter.emit( message, category: category, - location: Finding.Location(file: context.fileURL.path, line: lineNumber, column: column)) + location: Finding.Location(file: context.fileURL.path, line: outputBuffer.lineNumber, column: column)) } } diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift new file mode 100644 index 000000000..c975b1f7e --- /dev/null +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift @@ -0,0 +1,140 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Foundation + +struct PrettyPrintBuffer { + let maximumBlankLines: Int + let tabWidth: Int + + var isEnabled: Bool = true + + /// Indicates whether or not the printer is currently at the beginning of a line. + private(set) var isAtStartOfLine: Bool = true + + /// Keeps track of the most recent number of consecutive newlines that have been printed. + /// + /// This value is reset to zero whenever non-newline content is printed. + private(set) var consecutiveNewlineCount: Int = 0 + + /// Keeps track of the current line number being printed. + private(set) var lineNumber: Int = 1 + + /// Keeps track of the most recent number of spaces that should be printed before the next text + /// token. + private(set) var pendingSpaces: Int = 0 + + /// Current column position of the printer. If we just printed a newline and nothing else, it + /// will still point to the position of the previous line. + private(set) var column: Int + + var currentIndentation: [Indent] + + private(set) var output: String = "" + + init(maximumBlankLines: Int, tabWidth: Int, column: Int = 0) { + self.maximumBlankLines = maximumBlankLines + self.tabWidth = tabWidth + self.currentIndentation = [] + self.column = column + } + + /// Writes newlines into the output stream, taking into account any preexisting consecutive + /// newlines and the maximum allowed number of blank lines. + /// + /// This function does some implicit collapsing of consecutive newlines to ensure that the + /// results are consistent when breaks and explicit newlines coincide. For example, imagine a + /// break token that fires (thus creating a single non-discretionary newline) because it is + /// followed by a group that contains 2 discretionary newlines that were found in the user's + /// source code at that location. In that case, the break "overlaps" with the discretionary + /// newlines and it will write a newline before we get to the discretionaries. Thus, we have to + /// subtract the previously written newlines during the second call so that we end up with the + /// correct number overall. + /// + /// - Parameter newlines: The number and type of newlines to write. + mutating func writeNewlines(_ newlines: NewlineBehavior) { + let numberToPrint: Int + switch newlines { + case .elective: + numberToPrint = consecutiveNewlineCount == 0 ? 1 : 0 + case .soft(let count, _): + // We add 1 to the max blank lines because it takes 2 newlines to create the first blank line. + numberToPrint = min(count, maximumBlankLines + 1) - consecutiveNewlineCount + case .hard(let count): + numberToPrint = count + } + + guard numberToPrint > 0 else { return } + writeRaw(String(repeating: "\n", count: numberToPrint)) + lineNumber += numberToPrint + isAtStartOfLine = true + consecutiveNewlineCount += numberToPrint + pendingSpaces = 0 + column = 0 + } + + /// Writes the given text to the output stream. + /// + /// Before printing the text, this function will print any line-leading indentation or interior + /// leading spaces that are required before the text itself. + mutating func write(_ text: String) { + if isAtStartOfLine { + writeRaw(currentIndentation.indentation()) + column = currentIndentation.length(tabWidth: tabWidth) + isAtStartOfLine = false + } else if pendingSpaces > 0 { + writeRaw(String(repeating: " ", count: pendingSpaces)) + } + writeRaw(text) + consecutiveNewlineCount = 0 + pendingSpaces = 0 + column += text.count + } + + /// Request that the given number of spaces be printed out before the next text token. + /// + /// Spaces are printed only when the next text token is printed in order to prevent us from + /// printing lines that are only whitespace or have trailing whitespace. + mutating func enqueueSpaces(_ count: Int) { + pendingSpaces += count + column += count + } + + mutating func writeVerbatim(_ verbatim: String, _ length: Int) { + writeRaw(verbatim) + consecutiveNewlineCount = 0 + pendingSpaces = 0 + column += length + } + + /// Calls writeRaw, but also updates some state variables that are normally tracked by + /// higher level functions. This is used when we switch from disabled formatting to + /// enabled formatting, writing all the previous information as-is. + mutating func writeVerbatimAfterEnablingFormatting(_ str: S) { + writeRaw(str) + if str.hasSuffix("\n") { + isAtStartOfLine = true + consecutiveNewlineCount = 1 + } else { + isAtStartOfLine = false + consecutiveNewlineCount = 0 + } + } + + /// Append the given string to the output buffer. + /// + /// No further processing is performed on the string. + private mutating func writeRaw(_ str: S) { + guard isEnabled else { return } + output.append(String(str)) + } +} From 415b8ea939267d18aa198ba73705a450d8846a4a Mon Sep 17 00:00:00 2001 From: Shawn Hyam Date: Wed, 26 Jun 2024 12:02:47 -0400 Subject: [PATCH 2/2] Add missing comments for new struct and its properties. --- .../SwiftFormat/PrettyPrint/PrettyPrint.swift | 18 ++++++++++-------- .../PrettyPrint/PrettyPrintBuffer.swift | 10 ++++++++++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift index 5ab608a8a..ada574f1c 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift @@ -234,7 +234,7 @@ public class PrettyPrinter { // the group. case .open(let breaktype): // Determine if the break tokens in this group need to be forced. - if (shouldBreak(length) || lastBreak), case .consistent = breaktype { + if (!canFit(length) || lastBreak), case .consistent = breaktype { forceBreakStack.append(true) } else { forceBreakStack.append(false) @@ -276,7 +276,7 @@ public class PrettyPrinter { // scope), so we need the continuation indentation to persist across all the lines in that // scope. Additionally, continuation open breaks must indent when the break fires. let continuationBreakWillFire = openKind == .continuation - && (outputBuffer.isAtStartOfLine || shouldBreak(length) || mustBreak) + && (outputBuffer.isAtStartOfLine || !canFit(length) || mustBreak) let contributesContinuationIndent = currentLineIsContinuation || continuationBreakWillFire activeOpenBreaks.append( @@ -323,7 +323,7 @@ public class PrettyPrinter { // If it's a mandatory breaking close, then we must break (regardless of line length) if // the break is on a different line than its corresponding open break. mustBreak = openedOnDifferentLine - } else if shouldBreak(1) { + } else if !canFit() { // If there is no room left on the line, then we must force this break to fire so that the // next token that comes along (typically a closing bracket of some kind) ends up on the // next line. @@ -387,7 +387,7 @@ public class PrettyPrinter { // Wait for a contextual break to fire and then update the breaking behavior for the rest of // the contextual breaks in this scope to match the behavior of the one that fired. - let willFire = shouldBreak(length) || mustBreak + let willFire = !canFit(length) || mustBreak if willFire { // Update the active breaking context according to the most recently finished breaking // context so all following contextual breaks in this scope to have matching behavior. @@ -427,7 +427,7 @@ public class PrettyPrinter { } let suppressBreaking = isBreakingSuppressed && !overrideBreakingSuppressed - if !suppressBreaking && (shouldBreak(length) || mustBreak) { + if !suppressBreaking && (!canFit(length) || mustBreak) { currentLineIsContinuation = isContinuationIfBreakFires outputBuffer.writeNewlines(newline) lastBreak = true @@ -458,7 +458,7 @@ public class PrettyPrinter { lastBreak = false if wasEndOfLine { - if shouldBreak(comment.length) && !isBreakingSuppressed { + if !(canFit(comment.length) || isBreakingSuppressed) { diagnose(.moveEndOfLineComment, category: .endOfLineComment) } } @@ -536,9 +536,11 @@ public class PrettyPrinter { } } - private func shouldBreak(_ length: Int) -> Bool { + /// Indicates whether the current line can fit a string of the given length. If no length + /// is given, it indicates whether the current line can accomodate *any* text. + private func canFit(_ length: Int = 1) -> Bool { let spaceRemaining = configuration.lineLength - outputBuffer.column - return !outputBuffer.isAtStartOfLine && length > spaceRemaining + return outputBuffer.isAtStartOfLine || length <= spaceRemaining } /// Scan over the array of Tokens and calculate their lengths. diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift index c975b1f7e..02a2a7ac6 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift @@ -12,10 +12,18 @@ import Foundation +/// Used by the PrettyPrint class to actually assemble the output string. This struct +/// tracks state specific to the output (line number, column, etc.) rather than the pretty +/// printing algorithm itself. struct PrettyPrintBuffer { + /// The maximum number of consecutive blank lines that may appear in a file. let maximumBlankLines: Int + + /// The width of the horizontal tab in spaces. let tabWidth: Int + /// If true, output is generated as normal. If false, the various state variables are + /// updated as normal but nothing is appended to the output (used by selection formatting). var isEnabled: Bool = true /// Indicates whether or not the printer is currently at the beginning of a line. @@ -37,8 +45,10 @@ struct PrettyPrintBuffer { /// will still point to the position of the previous line. private(set) var column: Int + /// The current indentation level to be used when text is appended to a new line. var currentIndentation: [Indent] + /// The accumulated output of the pretty printer. private(set) var output: String = "" init(maximumBlankLines: Int, tabWidth: Int, column: Int = 0) {