mirror of
https://github.com/apple/container.git
synced 2026-07-13 04:57:08 +00:00
b671690c17
There's a couple things I don't think are intuitive about this.
1. Because of the internal task, render() can still be called even after
finish() completes. Ideally async defers are supported and we could just
await the final render completing after cancelling the task and setting
.finished, but alas. To fix this we can just lock across the methods for
now.
2. We always clear the screen in the destructor, even if we don't use
the
progress bar. I don't think we should honestly do anything in the
destructor.
Feels a programmer error not to defer { bar.finish() } or call it
somewhere.
3. Our spaces based line clearing. Use the ansi escape sequence for
clearing line;
I think our calculations were slightly off and it would leave trailing
output ( "s]" )
in some cases.
4. Shrinking the window until the output is smaller than the terminal
window (and vice
versa) is wonky on various term emulators. Truthfully, this is just a
hard problem,
but we can truncate our output and still provide some useful info.
This fixes some single line output (cat /etc/hostname etc.) getting
cleared in our atexit handler, as well as the need for the usleep.
96 lines
2.8 KiB
Swift
96 lines
2.8 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// Copyright © 2025-2026 Apple Inc. and the container project authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import ContainerizationOS
|
|
import Foundation
|
|
|
|
enum EscapeSequence {
|
|
static let hideCursor = "\u{001B}[?25l"
|
|
static let showCursor = "\u{001B}[?25h"
|
|
static let moveUp = "\u{001B}[1A"
|
|
static let clearToEndOfLine = "\u{001B}[K"
|
|
}
|
|
|
|
extension ProgressBar {
|
|
var termWidth: Int {
|
|
guard
|
|
let terminalHandle = term,
|
|
let terminal = try? Terminal(descriptor: terminalHandle.fileDescriptor)
|
|
else {
|
|
return 0
|
|
}
|
|
|
|
return (try? Int(terminal.size.width)) ?? 0
|
|
}
|
|
|
|
/// Clears the progress bar and resets the cursor.
|
|
public func clearAndResetCursor() {
|
|
state.withLock { s in
|
|
clear(state: &s)
|
|
resetCursor()
|
|
}
|
|
}
|
|
|
|
/// Clears the progress bar.
|
|
public func clear() {
|
|
state.withLock { s in
|
|
clear(state: &s)
|
|
}
|
|
}
|
|
|
|
/// Clears the progress bar (caller must hold state lock).
|
|
func clear(state: inout State) {
|
|
displayText("", state: &state)
|
|
}
|
|
|
|
/// Resets the cursor.
|
|
public func resetCursor() {
|
|
display(EscapeSequence.showCursor)
|
|
}
|
|
|
|
func display(_ text: String) {
|
|
guard let term else {
|
|
return
|
|
}
|
|
termQueue.sync {
|
|
try? term.write(contentsOf: Data(text.utf8))
|
|
try? term.synchronize()
|
|
}
|
|
}
|
|
|
|
func displayText(_ text: String, terminating: String = "\r") {
|
|
state.withLock { s in
|
|
displayText(text, state: &s, terminating: terminating)
|
|
}
|
|
}
|
|
|
|
func displayText(_ text: String, state: inout State, terminating: String = "\r") {
|
|
state.output = text
|
|
|
|
// Clears previously printed lines.
|
|
var lines = ""
|
|
if terminating.hasSuffix("\r") && termWidth > 0 {
|
|
let lineCount = (text.count - 1) / termWidth
|
|
for _ in 0..<lineCount {
|
|
lines += EscapeSequence.moveUp
|
|
}
|
|
}
|
|
|
|
let output = "\(text)\(EscapeSequence.clearToEndOfLine)\(terminating)\(lines)"
|
|
display(output)
|
|
}
|
|
}
|