Sources/OrgCore/SourceText.swift
40 lines · 1456 bytes
2 symbols in this file
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}