@@ -20,8 +20,9 @@ import SendableProperty
2020/// A progress bar that updates itself as tasks are completed.
2121public final class ProgressBar : Sendable {
2222 let config : ProgressConfig
23+ // `@SendableProperty` adds `_state: Synchronized<State>`, which can be updated inside a lock using `_state.withLock()`.
2324 @SendableProperty
24- var state : State
25+ var state = State ( )
2526 @SendableProperty
2627 var printedWidth = 0
2728 let term : FileHandle ?
@@ -97,7 +98,7 @@ public final class ProgressBar: Sendable {
9798 printFullDescription ( )
9899 }
99100
100- while !isFinished {
101+ while !state . finished {
101102 let intervalNanoseconds = UInt64 ( intervalSeconds * 1_000_000_000 )
102103 render ( )
103104 state. iteration += 1
@@ -117,11 +118,15 @@ public final class ProgressBar: Sendable {
117118
118119 /// Finishes the progress bar.
119120 public func finish( ) {
120- guard !isFinished else {
121+ guard !state . finished else {
121122 return
122123 }
123124
124125 state. finished = true
126+
127+ // The last render.
128+ render ( force: true )
129+
125130 if !config. disableProgressUpdates && !config. clearOnFinish {
126131 displayText ( state. output, terminating: " \n " )
127132 }
@@ -143,8 +148,8 @@ extension ProgressBar {
143148 return timeDifferenceSeconds
144149 }
145150
146- func render( ) {
147- guard term != nil && !config. disableProgressUpdates && !isFinished else {
151+ func render( force : Bool = false ) {
152+ guard term != nil && !config. disableProgressUpdates && ( force || !state . finished ) else {
148153 return
149154 }
150155 let output = draw ( )
@@ -154,8 +159,12 @@ extension ProgressBar {
154159 func draw( ) -> String {
155160 var components = [ String] ( )
156161 if config. showSpinner && !config. showProgressBar {
157- let spinnerIcon = config. theme. getSpinnerIcon ( state. iteration)
158- components. append ( " \( spinnerIcon) " )
162+ if !state. finished {
163+ let spinnerIcon = config. theme. getSpinnerIcon ( state. iteration)
164+ components. append ( " \( spinnerIcon) " )
165+ } else {
166+ components. append ( " \( config. theme. done) " )
167+ }
159168 }
160169
161170 if config. showTasks, let totalTasks = state. totalTasks {
@@ -176,13 +185,13 @@ extension ProgressBar {
176185 let total = state. totalSize ?? Int64 ( state. totalItems ?? 0 )
177186
178187 if config. showPercent && total > 0 && allowProgress {
179- components. append ( " \( state. percent) " )
188+ components. append ( " \( state. finished ? " 100% " : state . percent) " )
180189 }
181190
182191 if config. showProgressBar, total > 0 , allowProgress {
183192 let usedWidth = components. joined ( separator: " " ) . count + 45 /* the maximum number of characters we may need */
184193 let remainingWidth = max ( config. width - usedWidth, 1 /* the minumum width of a progress bar */)
185- let barLength = Int ( Int64 ( remainingWidth) * value / total)
194+ let barLength = state . finished ? remainingWidth : Int ( Int64 ( remainingWidth) * value / total)
186195 let barPaddingLength = remainingWidth - barLength
187196 let bar = " \( String ( repeating: config. theme. bar, count: barLength) ) \( String ( repeating: " " , count: barPaddingLength) ) "
188197 components. append ( " | \( bar) | " )
@@ -195,40 +204,56 @@ extension ProgressBar {
195204 if !state. itemsName. isEmpty {
196205 itemsName = " \( state. itemsName) "
197206 }
198- if let totalItems = state. totalItems {
199- additionalComponents. append ( " \( state. items. formattedNumber ( ) ) of \( totalItems. formattedNumber ( ) ) \( itemsName) " )
207+ if state. finished {
208+ if let totalItems = state. totalItems {
209+ additionalComponents. append ( " \( totalItems. formattedNumber ( ) ) \( itemsName) " )
210+ }
200211 } else {
201- additionalComponents. append ( " \( state. items. formattedNumber ( ) ) \( itemsName) " )
212+ if let totalItems = state. totalItems {
213+ additionalComponents. append ( " \( state. items. formattedNumber ( ) ) of \( totalItems. formattedNumber ( ) ) \( itemsName) " )
214+ } else {
215+ additionalComponents. append ( " \( state. items. formattedNumber ( ) ) \( itemsName) " )
216+ }
202217 }
203218 }
204219
205220 if state. size > 0 && allowProgress {
206- var formattedCombinedSize = " "
207- if config. showSize {
208- var formattedSize = state. size. formattedSize ( )
209- formattedSize = adjustFormattedSize ( formattedSize)
210- if let totalSize = state. totalSize {
211- var formattedTotalSize = totalSize. formattedSize ( )
212- formattedTotalSize = adjustFormattedSize ( formattedTotalSize)
213- formattedCombinedSize = combineSize ( size: formattedSize, totalSize: formattedTotalSize)
214- } else {
215- formattedCombinedSize = formattedSize
221+ if state. finished {
222+ if config. showSize {
223+ if let totalSize = state. totalSize {
224+ var formattedTotalSize = totalSize. formattedSize ( )
225+ formattedTotalSize = adjustFormattedSize ( formattedTotalSize)
226+ additionalComponents. append ( formattedTotalSize)
227+ }
228+ }
229+ } else {
230+ var formattedCombinedSize = " "
231+ if config. showSize {
232+ var formattedSize = state. size. formattedSize ( )
233+ formattedSize = adjustFormattedSize ( formattedSize)
234+ if let totalSize = state. totalSize {
235+ var formattedTotalSize = totalSize. formattedSize ( )
236+ formattedTotalSize = adjustFormattedSize ( formattedTotalSize)
237+ formattedCombinedSize = combineSize ( size: formattedSize, totalSize: formattedTotalSize)
238+ } else {
239+ formattedCombinedSize = formattedSize
240+ }
216241 }
217- }
218242
219- var formattedSpeed = " "
220- if config. showSpeed {
221- formattedSpeed = " \( state. sizeSpeed ?? state. averageSizeSpeed) "
222- formattedSpeed = adjustFormattedSize ( formattedSpeed)
223- }
243+ var formattedSpeed = " "
244+ if config. showSpeed {
245+ formattedSpeed = " \( state. sizeSpeed ?? state. averageSizeSpeed) "
246+ formattedSpeed = adjustFormattedSize ( formattedSpeed)
247+ }
224248
225- if config. showSize && config. showSpeed {
226- additionalComponents. append ( formattedCombinedSize)
227- additionalComponents. append ( formattedSpeed)
228- } else if config. showSize {
229- additionalComponents. append ( formattedCombinedSize)
230- } else if config. showSpeed {
231- additionalComponents. append ( formattedSpeed)
249+ if config. showSize && config. showSpeed {
250+ additionalComponents. append ( formattedCombinedSize)
251+ additionalComponents. append ( formattedSpeed)
252+ } else if config. showSize {
253+ additionalComponents. append ( formattedCombinedSize)
254+ } else if config. showSpeed {
255+ additionalComponents. append ( formattedSpeed)
256+ }
232257 }
233258 }
234259
0 commit comments