Files
Termind/Frameworks/TermindDomain/Shell/CommandHistoryEntry.swift
T

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))
}
}