import Foundation struct NoteInfo: Identifiable { let id: UUID var title: String var lastModified: Date } enum EvalFormat: String { case inline case table case tree } struct EvalEntry { let result: String let format: EvalFormat } class RustBridge { static let shared = RustBridge() private var docs: [UUID: OpaquePointer] = [:] private init() {} func newDocument() -> UUID { let ptr = swiftly_doc_new()! let uuidStr = cacheSaveRaw(ptr) let id = UUID(uuidString: uuidStr) ?? UUID() docs[id] = ptr return id } func freeDocument(_ id: UUID) { guard let ptr = docs.removeValue(forKey: id) else { return } swiftly_doc_free(ptr) } func setText(_ id: UUID, text: String) { guard let ptr = docs[id] else { return } text.withCString { cstr in swiftly_doc_set_text(ptr, cstr) } } func getText(_ id: UUID) -> String { guard let ptr = docs[id] else { return "" } guard let cstr = swiftly_doc_get_text(ptr) else { return "" } let str = String(cString: cstr) swiftly_free_string(cstr) return str } func evaluate(_ id: UUID) -> [Int: EvalEntry] { guard let ptr = docs[id] else { return [:] } guard let cstr = swiftly_doc_evaluate(ptr) else { return [:] } let json = String(cString: cstr) swiftly_free_string(cstr) return parseEvalJSON(json) } func evaluateLine(_ line: String) -> String { guard let cstr = line.withCString({ swiftly_eval_line($0) }) else { return "" } let str = String(cString: cstr) swiftly_free_string(cstr) return str } func saveNote(_ id: UUID, path: String) -> Bool { guard let ptr = docs[id] else { return false } return path.withCString { cstr in swiftly_doc_save(ptr, cstr) } } func loadNote(path: String) -> (UUID, String)? { guard let ptr = path.withCString({ swiftly_doc_load($0) }) else { return nil } let uuidStr = cacheSaveRaw(ptr) guard let id = UUID(uuidString: uuidStr) else { swiftly_doc_free(ptr) return nil } if let old = docs[id] { swiftly_doc_free(old) } docs[id] = ptr guard let cstr = swiftly_doc_get_text(ptr) else { return (id, "") } let text = String(cString: cstr) swiftly_free_string(cstr) return (id, text) } func cacheSave(_ id: UUID) -> Bool { guard let ptr = docs[id] else { return false } guard let cstr = swiftly_cache_save(ptr) else { return false } swiftly_free_string(cstr) return true } func cacheLoad(_ id: UUID) -> Bool { let uuidStr = id.uuidString.lowercased() guard let ptr = uuidStr.withCString({ swiftly_cache_load($0) }) else { return false } if let old = docs[id] { swiftly_doc_free(old) } docs[id] = ptr return true } func listNotes() -> [NoteInfo] { guard let cstr = swiftly_list_notes() else { return [] } let json = String(cString: cstr) swiftly_free_string(cstr) return parseNoteListJSON(json) } struct HighlightSpan { let start: Int let end: Int let kind: Int } func highlight(source: String, lang: String) -> [HighlightSpan] { guard let cstr = source.withCString({ src in lang.withCString({ lng in swiftly_highlight(src, lng) }) }) else { return [] } let json = String(cString: cstr) swiftly_free_string(cstr) return parseHighlightJSON(json) } private func parseHighlightJSON(_ json: String) -> [HighlightSpan] { guard let data = json.data(using: .utf8) else { return [] } guard let arr = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else { return [] } var spans: [HighlightSpan] = [] for item in arr { guard let start = item["start"] as? Int, let end = item["end"] as? Int, let kind = item["kind"] as? Int else { continue } spans.append(HighlightSpan(start: start, end: end, kind: kind)) } return spans } func deleteNote(_ id: UUID) { freeDocument(id) let cacheDir = FileManager.default.homeDirectoryForCurrentUser .appendingPathComponent(".swiftly/cache") let cacheFile = cacheDir.appendingPathComponent("\(id.uuidString.lowercased()).sw") try? FileManager.default.removeItem(at: cacheFile) } // MARK: - Internal private func cacheSaveRaw(_ ptr: OpaquePointer) -> String { guard let cstr = swiftly_cache_save(ptr) else { return UUID().uuidString } let str = String(cString: cstr) swiftly_free_string(cstr) return str } private func parseEvalJSON(_ json: String) -> [Int: EvalEntry] { guard let data = json.data(using: .utf8) else { return [:] } guard let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return [:] } guard let results = obj["results"] as? [[String: Any]] else { return [:] } var dict: [Int: EvalEntry] = [:] for item in results { if let line = item["line"] as? Int, let result = item["result"] as? String { let fmt = EvalFormat(rawValue: item["format"] as? String ?? "inline") ?? .inline dict[line] = EvalEntry(result: result, format: fmt) } } return dict } private func parseNoteListJSON(_ json: String) -> [NoteInfo] { guard let data = json.data(using: .utf8) else { return [] } guard let arr = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else { return [] } var notes: [NoteInfo] = [] for item in arr { guard let uuidStr = item["uuid"] as? String, let uuid = UUID(uuidString: uuidStr), let title = item["title"] as? String else { continue } let modified = item["modified"] as? Double ?? 0 let date = Date(timeIntervalSince1970: modified) notes.append(NoteInfo(id: uuid, title: title, lastModified: date)) } return notes.sorted { $0.lastModified > $1.lastModified } } }