Files
Ole André Vadla Ravnås b88a9be290 Replace SwiftyMonaco with native editors
Drop the Monaco webview. Edit in a GtkSourceView on GTK and in a
native text view on Apple, both speaking to Frida.Compiler's
in-process language server for completions, signature help,
hovers and diagnostics.

Color, complete and indent code natively, matching the GitHub
theme in light and dark.
2026-09-08 19:52:31 +02:00

105 lines
3.9 KiB
Swift

import Foundation
import LumaCore
let hookSource = """
defineHandler({
onEnter(log, args) {
const m = Process.mainModule;
m.
},
});
"""
@MainActor
func check() async -> Int32 {
let root = FileManager.default.temporaryDirectory
.appendingPathComponent("luma-editor-check-\(UUID().uuidString)", isDirectory: true)
try! FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)
defer { try? FileManager.default.removeItem(at: root) }
let project = TypeScriptProject(root: root)
do {
try await project.start(ambientDeclarations: [TracerTypings.handler])
} catch {
print("language server failed to start: \(error)")
return 1
}
defer { Task { await project.stop() } }
let document = project.openDocument(path: "Editor/hook.ts", text: hookSource, languageId: "typescript")
var complaints: [String] = []
let diagnostics = await firstDiagnostics(of: document, within: 10)
print("diagnostics: \(diagnostics.map(\.message))")
if !diagnostics.contains(where: { $0.code == .number(1003) }) {
complaints.append("the dangling member access went unreported")
}
if diagnostics.contains(where: { $0.message.contains("defineHandler") }) {
complaints.append("the tracer's ambient declarations were not seen")
}
if diagnostics.contains(where: { $0.message.contains("Process") }) {
complaints.append("the compiler's frida-gum typings were not seen")
}
do {
let completions = try await document.completions(at: LSP.Position(line: 3, character: 6), triggerCharacter: ".")
print("completions after m.: \(completions.items.count), e.g. \(completions.items.prefix(4).map(\.label))")
if !completions.items.contains(where: { $0.label == "enumerateExports" }) {
complaints.append("Module's members were not completed after m.")
}
let hover = try await document.hover(at: LSP.Position(line: 2, character: 16))
print("hover on Process: \(hover?.contents.text.prefix(80) ?? "nothing")")
if hover == nil {
complaints.append("no hover for Process")
}
let symbols = try await document.symbols()
print("symbols: \(symbols.map(\.name))")
} catch {
complaints.append("request failed: \(error)")
}
let indented = SourceIndentation.newline(in: "defineHandler({", atUTF16: 15)
print("newline after an opening brace: \(indented.text.debugDescription)")
if indented.text != "\n " {
complaints.append("a new line after an opening brace was not indented")
}
let split = SourceIndentation.newline(in: "foo({})", atUTF16: 5)
if split.text != "\n \n" || split.caretOffset != 5 {
complaints.append("a closer ahead of the caret was not moved to its own line")
}
if SourceIndentation.closerDedent(in: "{\n ", atUTF16: 6) != 2..<6 {
complaints.append("a closer on a blank line did not step back out")
}
let template = CallTemplate(name: "attach", parameters: ["target", "callbacks"])
print("call template: \(template.snippet)")
if template.snippet != "attach(${1:target}, ${2:callbacks})$0" || template.placeholders != [7..<13, 15..<24] {
complaints.append("the call template did not lay out its arguments")
}
for complaint in complaints {
print("FAIL: \(complaint)")
}
return complaints.isEmpty ? 0 : 1
}
@MainActor
func firstDiagnostics(of document: TypeScriptDocument, within seconds: Double) async -> [LSP.Diagnostic] {
let (stream, continuation) = AsyncStream<[LSP.Diagnostic]>.makeStream()
document.onDiagnostics = { continuation.yield($0) }
let deadline = Task {
try? await Task.sleep(for: .seconds(seconds))
continuation.finish()
}
defer { deadline.cancel() }
for await diagnostics in stream {
return diagnostics
}
return []
}
setlinebuf(stdout)
exit(await check())