chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user