krz/orgstar

A native macOS editor for org-mode files. editor org-mode swift

Sources/OrgCore/SourceText.swift

16086b4cf2caff5328774b2cd5ae3ffe1ab65ca4
orgstar/Sources/OrgCore/SourceText.swift history · blame · raw

40 lines · 1456 bytes

 1import Foundation
 2
 3/// A file's bytes and their decoded text. Only UTF-8 (with or without a BOM) is editable;
 4/// anything else decodes with replacement characters for display and is never written back.
 5public struct SourceText: Sendable {
 6    public let originalBytes: [UInt8]
 7    public let hasBOM: Bool
 8    public let isValidUTF8: Bool
 9    /// Decoded text without the BOM.
10    public let text: String
11
12    private static let bom: [UInt8] = [0xEF, 0xBB, 0xBF]
13
14    public init(bytes: [UInt8]) {
15        originalBytes = bytes
16        hasBOM = bytes.starts(with: Self.bom)
17        let body = hasBOM ? Array(bytes.dropFirst(3)) : bytes
18        if let decoded = String(validating: body, as: UTF8.self) {
19            text = decoded
20            isValidUTF8 = true
21        } else {
22            text = String(decoding: body, as: UTF8.self)
23            isValidUTF8 = false
24        }
25    }
26
27    public init(_ text: String) {
28        self.init(bytes: Array(text.utf8))
29    }
30
31    public var isEditable: Bool { isValidUTF8 }
32
33    /// Bytes to write for `newText`. Unchanged text returns the original bytes. Valid UTF-8
34    /// round-trips through `String` unchanged, so untouched spans keep their exact bytes.
35    public func encode(_ newText: String) -> [UInt8] {
36        if newText == text { return originalBytes }
37        precondition(isEditable, "a read-only document cannot be re-encoded")
38        return (hasBOM ? Self.bom : []) + Array(newText.utf8)
39    }
40}