diff options
| author | Christian Cleberg <[email protected]> | 2026-07-15 21:42:38 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-07-15 21:42:38 -0500 |
| commit | ccec322f8eac4d14638f5abdae7dae7abc95eb7e (patch) | |
| tree | 7eb92e38d5dc88ba839b94c294ae9be0ea21a6d0 /Hutch/Views/Inbox/ThreadViewModel.swift | |
| parent | b9ec80716ea015de5b6b31395fdc5ff03191398c (diff) | |
| download | hutch-ccec322f8eac4d14638f5abdae7dae7abc95eb7e.tar.gz hutch-ccec322f8eac4d14638f5abdae7dae7abc95eb7e.tar.bz2 hutch-ccec322f8eac4d14638f5abdae7dae7abc95eb7e.zip | |
refactor: share the email body diff splitter
segmentMessageBody and its helpers were private to ThreadViewModel, reachable
from tests only through a segmentMessageBodyForTesting shim. Patchset review
needs the same splitting, because sr.ht's Patch type carries no diff — the diff
only exists inside the email body — so this has to be shared rather than
duplicated.
Moved to InboxThreadUtilities. The shim is gone; the existing test calls the
real function directly now.
Also adds the Patchset model layer that the coming views build on.
Diffstat (limited to 'Hutch/Views/Inbox/ThreadViewModel.swift')
| -rw-r--r-- | Hutch/Views/Inbox/ThreadViewModel.swift | 91 |
1 files changed, 2 insertions, 89 deletions
diff --git a/Hutch/Views/Inbox/ThreadViewModel.swift b/Hutch/Views/Inbox/ThreadViewModel.swift index 850b83c..f422fe3 100644 --- a/Hutch/Views/Inbox/ThreadViewModel.swift +++ b/Hutch/Views/Inbox/ThreadViewModel.swift @@ -446,7 +446,7 @@ final class ThreadViewModel { let normalizedIdentity = normalizedSenderIdentity(from: body, fallbackAuthor: author) let displayBody = sanitizedDisplayBody(from: body) - let contentBlocks = segmentMessageBody(displayBody, isPatch: payload.patch != nil) + let contentBlocks = InboxThreadUtilities.segmentMessageBody(displayBody, isPatch: payload.patch != nil) return InboxMessage( id: id, @@ -540,7 +540,7 @@ final class ThreadViewModel { } private static func sanitizedDisplayBody(from body: String) -> String { - let normalizedBody = normalizeLineEndings(in: body) + let normalizedBody = InboxThreadUtilities.normalizeLineEndings(in: body) let lines = normalizedBody.components(separatedBy: "\n") let headerPrefixes = ["From:", "Date:", "To:", "Cc:", "Subject:"] var headerCount = 0 @@ -565,93 +565,6 @@ final class ThreadViewModel { return lines.dropFirst(blankLineIndex + 1).joined(separator: "\n") } - nonisolated static func segmentMessageBodyForTesting(_ body: String, isPatch: Bool) -> [InboxMessageContentBlock] { - segmentMessageBody(body, isPatch: isPatch) - } - - private nonisolated static func segmentMessageBody(_ body: String, isPatch: Bool) -> [InboxMessageContentBlock] { - guard isPatch else { - let trimmedBody = body.trimmingCharacters(in: .whitespacesAndNewlines) - return trimmedBody.isEmpty ? [] : [.plainText(trimmedBody)] - } - - let normalizedBody = normalizeLineEndings(in: body) - let lines = normalizedBody.components(separatedBy: "\n") - guard let diffStartIndex = actualDiffStartIndex(in: lines) else { - let trimmedBody = normalizedBody.trimmingCharacters(in: .whitespacesAndNewlines) - return trimmedBody.isEmpty ? [] : [.plainText(trimmedBody)] - } - - var blocks: [InboxMessageContentBlock] = [] - let leadingPlainText = lines[..<diffStartIndex] - .joined(separator: "\n") - .trimmingCharacters(in: .whitespacesAndNewlines) - if !leadingPlainText.isEmpty { - blocks.append(.plainText(leadingPlainText)) - } - - let remainingLines = Array(lines[diffStartIndex...]) - let signatureIndex = remainingLines.firstIndex(where: isEmailSignatureSeparator) - - let diffLines: ArraySlice<String> - let trailingPlainText: String - if let signatureIndex { - diffLines = remainingLines[..<signatureIndex] - trailingPlainText = remainingLines[signatureIndex...] - .joined(separator: "\n") - .trimmingCharacters(in: .whitespacesAndNewlines) - } else { - diffLines = remainingLines[...] - trailingPlainText = "" - } - - let diff = diffLines.joined(separator: "\n").trimmingCharacters(in: .whitespacesAndNewlines) - if !diff.isEmpty { - blocks.append(.diff(diff)) - } - - if !trailingPlainText.isEmpty { - blocks.append(.plainText(trailingPlainText)) - } - return blocks - } - - private nonisolated static func actualDiffStartIndex(in lines: [String]) -> Int? { - if let explicitDiffIndex = lines.firstIndex(where: { $0.hasPrefix("diff --git ") }) { - return explicitDiffIndex - } - - for index in lines.indices { - let line = lines[index] - guard line.hasPrefix("--- ") else { continue } - let nextIndex = lines.index(after: index) - guard nextIndex < lines.endIndex else { continue } - let nextLine = lines[nextIndex] - guard nextLine.hasPrefix("+++ ") else { continue } - - let oldPath = String(line.dropFirst(4)) - let newPath = String(nextLine.dropFirst(4)) - let looksLikeUnifiedDiff = (oldPath.hasPrefix("a/") || oldPath == "/dev/null") && - (newPath.hasPrefix("b/") || newPath == "/dev/null") - - if looksLikeUnifiedDiff { - return index - } - } - - return nil - } - - private nonisolated static func isEmailSignatureSeparator(_ line: String) -> Bool { - line == "-- " || line == "--" - } - - private nonisolated static func normalizeLineEndings(in text: String) -> String { - text - .replacingOccurrences(of: "\r\n", with: "\n") - .replacingOccurrences(of: "\r", with: "\n") - } - private static func stripLeadingFromLineIfPresent(in body: String) -> String { let lines = body.components(separatedBy: "\n") guard let firstLine = lines.first, firstLine.hasPrefix("From:") else { |
