Acord/src/RustBridge.swift

191 lines
6.3 KiB
Swift

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 }
}
}