Files
wehub-resource-sync a789495a98
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:34 +08:00

93 lines
3.2 KiB
Swift

import SwiftUI
/// Lightweight markdown renderer for assistant messages.
///
/// Handles fenced code blocks as monospaced cards and renders everything else
/// through SwiftUI's native AttributedString markdown (bold, italics, inline
/// code, links). Deliberately not a full CommonMark implementation.
struct MarkdownText: View {
let text: String
init(_ text: String) {
self.text = text
}
var body: some View {
VStack(alignment: .leading, spacing: 8) {
ForEach(Array(segments.enumerated()), id: \.offset) { _, segment in
switch segment {
case .prose(let prose):
Text(attributed(prose))
.font(.body)
.foregroundStyle(Theme.textPrimary)
.textSelection(.enabled)
case .code(let code, _):
ScrollView(.horizontal, showsIndicators: false) {
Text(code)
.font(Theme.mono(12))
.foregroundStyle(Theme.textSecondary)
.padding(12)
.textSelection(.enabled)
}
.frame(maxWidth: .infinity, alignment: .leading)
.background(Theme.surfaceElevated)
.clipShape(RoundedRectangle(cornerRadius: 10))
}
}
}
}
private enum Segment {
case prose(String)
case code(String, language: String?)
}
private var segments: [Segment] {
var result: [Segment] = []
var prose: [String] = []
var code: [String] = []
var language: String?
var inCode = false
for line in text.split(separator: "\n", omittingEmptySubsequences: false) {
if line.hasPrefix("```") {
if inCode {
result.append(.code(code.joined(separator: "\n"), language: language))
code = []
inCode = false
} else {
let joined = prose.joined(separator: "\n")
.trimmingCharacters(in: .whitespacesAndNewlines)
if !joined.isEmpty {
result.append(.prose(joined))
}
prose = []
language = line.dropFirst(3).isEmpty ? nil : String(line.dropFirst(3))
inCode = true
}
} else if inCode {
code.append(String(line))
} else {
prose.append(String(line))
}
}
if inCode {
result.append(.code(code.joined(separator: "\n"), language: language))
} else {
let joined = prose.joined(separator: "\n")
.trimmingCharacters(in: .whitespacesAndNewlines)
if !joined.isEmpty {
result.append(.prose(joined))
}
}
return result
}
private func attributed(_ string: String) -> AttributedString {
(try? AttributedString(
markdown: string,
options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace)
)) ?? AttributedString(string)
}
}