77 lines
2.8 KiB
Swift
77 lines
2.8 KiB
Swift
import Foundation
|
|
|
|
/// A command in one live terminal session. File records may have no execution timestamp.
|
|
public nonisolated struct CommandHistoryEntry: Identifiable, Hashable, Sendable {
|
|
public var id: UUID = UUID()
|
|
public var sessionID: UUID
|
|
public var command: String
|
|
public var executedAt: Date?
|
|
/// `nil` means the shell never reported a completion event, including rows
|
|
/// written by versions that predate command lifecycle reporting.
|
|
public var exitStatus: Int32?
|
|
public var duration: TimeInterval?
|
|
public init(
|
|
id: UUID = UUID(), sessionID: UUID, command: String, executedAt: Date? = nil, exitStatus: Int32? = nil,
|
|
duration: TimeInterval? = nil
|
|
) {
|
|
self.id = id
|
|
self.sessionID = sessionID
|
|
self.command = command
|
|
self.executedAt = executedAt
|
|
self.exitStatus = exitStatus
|
|
self.duration = duration
|
|
}
|
|
|
|
/// Converts an explicit shell line continuation into the single separator
|
|
/// History needs for copying and replay. Real, unescaped line breaks stay
|
|
/// intact because they may separate commands or belong to quoted content.
|
|
public static func normalizingLineContinuations(in command: String) -> String {
|
|
let input = Array(command.unicodeScalars)
|
|
var output: [Unicode.Scalar] = []
|
|
output.reserveCapacity(input.count)
|
|
var index = 0
|
|
|
|
while index < input.count {
|
|
guard input[index].value == 0x5C else {
|
|
output.append(input[index])
|
|
index += 1
|
|
continue
|
|
}
|
|
|
|
let lineBreakLength: Int
|
|
if index + 1 < input.count, input[index + 1].value == 0x0A {
|
|
lineBreakLength = 1
|
|
} else if index + 2 < input.count, input[index + 1].value == 0x0D,
|
|
input[index + 2].value == 0x0A
|
|
{
|
|
lineBreakLength = 2
|
|
} else {
|
|
output.append(input[index])
|
|
index += 1
|
|
continue
|
|
}
|
|
|
|
let precedingBackslashes = output.reversed().prefix { $0.value == 0x5C }.count
|
|
guard precedingBackslashes.isMultiple(of: 2) else {
|
|
output.append(input[index])
|
|
index += 1
|
|
continue
|
|
}
|
|
|
|
if precedingBackslashes == 0 {
|
|
while output.last?.value == 0x20 || output.last?.value == 0x09 {
|
|
output.removeLast()
|
|
}
|
|
}
|
|
index += 1 + lineBreakLength
|
|
while index < input.count, input[index].value == 0x20 || input[index].value == 0x09 {
|
|
index += 1
|
|
}
|
|
if !output.isEmpty, index < input.count { output.append(" ") }
|
|
}
|
|
|
|
return String(String.UnicodeScalarView(output))
|
|
}
|
|
|
|
}
|