From 227eeb40ba28700538a06a47f9b829f3bb20af49 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sun, 10 Nov 2024 18:36:31 +0100 Subject: [PATCH] Progress --- config/comp_test.nim | 9 ++ config/plugin_api_guest.nim | 96 ++++++++++++++++++++- nev.nimble | 2 +- scripting/plugin_api.wit | 15 ++++ src/app.nim | 1 + src/scripting/plugin_api_host.nim | 63 +++++++++++++- src/scripting/scripting_wasm_components.nim | 91 +++++++++++++++---- 7 files changed, 253 insertions(+), 24 deletions(-) diff --git a/config/comp_test.nim b/config/comp_test.nim index 8fff70b..b0aa900 100644 --- a/config/comp_test.nim +++ b/config/comp_test.nim @@ -22,3 +22,12 @@ proc initPlugin() = echo "[guest] initPlugin" echo getSelection() + + let r = newRope(ws"hello, what is going on today?") + echo r.slice(4, 14).debug() + echo r.slice(4, 14).text() + + let r2 = getCurrentEditorRope() + let s = getSelection() + echo r2.slice(s.first.column, s.last.column).debug() + echo r2.slice(s.first.column, s.last.column).text() diff --git a/config/plugin_api_guest.nim b/config/plugin_api_guest.nim index 50ebae9..5debe3c 100644 --- a/config/plugin_api_guest.nim +++ b/config/plugin_api_guest.nim @@ -16,16 +16,106 @@ type Selection* = object first*: Cursor last*: Cursor -proc getSelectionImported*(a0: int32): void {. + Rope* = object + handle*: int32 +proc textRopeDrop(a: int32): void {.wasmimport("[resource-drop]rope", + "nev:plugins/text").} +proc `=copy`*(a: var Rope; b: Rope) {.error.} +proc `=destroy`*(a: Rope) = + if a.handle != 0: + textRopeDrop(a.handle - 1) + +proc textEditorGetSelectionImported(a0: int32): void {. wasmimport("get-selection", "nev:plugins/text-editor").} -proc getSelection*(): Selection = +proc getSelection*(): Selection {.nodestroy.} = var retArea: array[16, uint8] - getSelectionImported(cast[int32](retArea[0].addr)) + textEditorGetSelectionImported(cast[int32](retArea[0].addr)) result.first.line = cast[ptr int32](retArea[0].addr)[] result.first.column = cast[ptr int32](retArea[4].addr)[] result.last.line = cast[ptr int32](retArea[8].addr)[] result.last.column = cast[ptr int32](retArea[12].addr)[] +proc textNewRopeImported(a0: int32; a1: int32): int32 {. + wasmimport("[constructor]rope", "nev:plugins/text").} +proc newRope*(content: WitString): Rope {.nodestroy.} = + var + arg0: int32 + arg1: int32 + if content.len > 0: + arg0 = cast[int32](content[0].addr) + else: + arg0 = 0 + arg1 = content.len + let res = textNewRopeImported(arg0, arg1) + result.handle = res + 1 + +proc textCloneImported(a0: int32): int32 {. + wasmimport("[method]rope.clone", "nev:plugins/text").} +proc clone*(self: Rope): Rope {.nodestroy.} = + var arg0: int32 + arg0 = cast[int32](self.handle - 1) + let res = textCloneImported(arg0) + result.handle = res + 1 + +proc textTextImported(a0: int32; a1: int32): void {. + wasmimport("[method]rope.text", "nev:plugins/text").} +proc text*(self: Rope): WitString {.nodestroy.} = + var + retArea: array[8, uint8] + arg0: int32 + arg0 = cast[int32](self.handle - 1) + textTextImported(arg0, cast[int32](retArea[0].addr)) + result = ws(cast[ptr char](cast[ptr int32](retArea[0].addr)[]), + cast[ptr int32](retArea[4].addr)[]) + +proc textDebugImported(a0: int32; a1: int32): void {. + wasmimport("[method]rope.debug", "nev:plugins/text").} +proc debug*(self: Rope): WitString {.nodestroy.} = + var + retArea: array[8, uint8] + arg0: int32 + arg0 = cast[int32](self.handle - 1) + textDebugImported(arg0, cast[int32](retArea[0].addr)) + result = ws(cast[ptr char](cast[ptr int32](retArea[0].addr)[]), + cast[ptr int32](retArea[4].addr)[]) + +proc textSliceImported(a0: int32; a1: int64; a2: int64): int32 {. + wasmimport("[method]rope.slice", "nev:plugins/text").} +proc slice*(self: Rope; a: int64; b: int64): Rope {.nodestroy.} = + var + arg0: int32 + arg1: int64 + arg2: int64 + arg0 = cast[int32](self.handle - 1) + arg1 = cast[int64](a) + arg2 = cast[int64](b) + let res = textSliceImported(arg0, arg1, arg2) + result.handle = res + 1 + +proc textSlicePointsImported(a0: int32; a1: int32; a2: int32; a3: int32; + a4: int32): int32 {. + wasmimport("[method]rope.slice-points", "nev:plugins/text").} +proc slicePoints*(self: Rope; a: Cursor; b: Cursor): Rope {.nodestroy.} = + var + arg0: int32 + arg1: int32 + arg2: int32 + arg3: int32 + arg4: int32 + arg0 = cast[int32](self.handle - 1) + arg1 = cast[int32](a.line) + arg2 = cast[int32](a.column) + arg3 = cast[int32](b.line) + arg4 = cast[int32](b.column) + let res = textSlicePointsImported(arg0, arg1, arg2, arg3, arg4) + result.handle = res + 1 + +proc textGetCurrentEditorRopeImported(): int32 {. + wasmimport("[static]rope.get-current-editor-rope", "nev:plugins/text").} +proc getCurrentEditorRope*(): Rope {.nodestroy.} = + let res = textGetCurrentEditorRopeImported() + result.handle = res + 1 + proc initPlugin(): void proc initPluginExported(): void {.wasmexport("init-plugin").} = initPlugin() diff --git a/nev.nimble b/nev.nimble index 74cb02d..7590532 100644 --- a/nev.nimble +++ b/nev.nimble @@ -37,7 +37,7 @@ requires "https://github.com/Nimaoth/wasm3 >= 0.1.15" requires "https://github.com/Nimaoth/lrucache.nim >= 1.1.4" requires "https://github.com/Nimaoth/boxy >= 0.4.4" requires "https://github.com/Nimaoth/nimtreesitter-api#498d284" -requires "https://github.com/Nimaoth/nimwasmtime#c12a100" +requires "https://github.com/Nimaoth/nimwasmtime#d70a21b" requires "https://github.com/Nimaoth/nimsumtree >= 0.3.7" # Use this to include all treesitter languages (takes longer to download) diff --git a/scripting/plugin_api.wit b/scripting/plugin_api.wit index b73d919..85e6528 100644 --- a/scripting/plugin_api.wit +++ b/scripting/plugin_api.wit @@ -18,10 +18,25 @@ interface text-editor { get-selection: func() -> selection; } +interface text { + use types.{cursor}; + + resource rope { + constructor(content: string); + clone: func() -> rope; + text: func() -> string; + debug: func() -> string; + slice: func(a: s64, b: s64) -> rope; + slice-points: func(a: cursor, b: cursor) -> rope; + get-current-editor-rope: static func() -> rope; + } +} + world plugin { use types.{cursor, selection}; import text-editor; + import text; export init-plugin: func(); } diff --git a/src/app.nim b/src/app.nim index 284f34a..4ab8de1 100644 --- a/src/app.nim +++ b/src/app.nim @@ -251,6 +251,7 @@ proc initScripting(self: App, options: AppOptions) {.async.} = log(lvlInfo, fmt"load wasm components") self.wasmCompScriptContext = new ScriptContextWasmComp # self.plugins.scriptContexts.add self.wasmCompScriptContext + self.wasmCompScriptContext.services = self.services self.wasmCompScriptContext.moduleVfs = VFS() self.wasmCompScriptContext.vfs = self.vfs self.vfs.mount("plugs://", self.wasmCompScriptContext.moduleVfs) diff --git a/src/scripting/plugin_api_host.nim b/src/scripting/plugin_api_host.nim index c9a9af8..aea3bd5 100644 --- a/src/scripting/plugin_api_host.nim +++ b/src/scripting/plugin_api_host.nim @@ -6,7 +6,7 @@ import from std / unicode import Rune import - results, wit_types + results, wit_types, wasmtime {.pop.} type @@ -15,4 +15,63 @@ type column*: int32 Selection* = object first*: Cursor - last*: Cursor \ No newline at end of file + last*: Cursor +when not declared(RopeResource): + {.error: "Missing resource type definition for " & "RopeResource" & + ". Define the type before the importWit statement.".} +proc textEditorGetSelection(host: WasmContext; store: ptr ComponentContextT): Selection +proc textNewRope(host: WasmContext; store: ptr ComponentContextT; + content: string): RopeResource +proc textClone(host: WasmContext; store: ptr ComponentContextT; + self: var RopeResource): RopeResource +proc textText(host: WasmContext; store: ptr ComponentContextT; + self: var RopeResource): string +proc textDebug(host: WasmContext; store: ptr ComponentContextT; + self: var RopeResource): string +proc textSlice(host: WasmContext; store: ptr ComponentContextT; + self: var RopeResource; a: int64; b: int64): RopeResource +proc textSlicePoints(host: WasmContext; store: ptr ComponentContextT; + self: var RopeResource; a: Cursor; b: Cursor): RopeResource +proc textGetCurrentEditorRope(host: WasmContext; store: ptr ComponentContextT): RopeResource +proc defineComponent*(linker: ptr ComponentLinkerT; host: WasmContext): WasmtimeResult[ + void] = + ?linker.defineResource("nev:plugins/text", "rope", RopeResource) + linker.defineFunc("nev:plugins/text-editor", "get-selection"): + let res = textEditorGetSelection(host, store) + results[0] = res.toVal + linker.defineFunc("nev:plugins/text", "[constructor]rope"): + let content = parameters[0].to(string) + let res = textNewRope(host, store, content) + results[0] = ?store.resourceNew(res) + linker.defineFunc("nev:plugins/text", "[method]rope.clone"): + let self = ?store.resourceHostData(parameters[0].addr, RopeResource) + let res = textClone(host, store, self[]) + results[0] = ?store.resourceNew(res) + ?store.resourceDrop(parameters[0].addr) + linker.defineFunc("nev:plugins/text", "[method]rope.text"): + let self = ?store.resourceHostData(parameters[0].addr, RopeResource) + let res = textText(host, store, self[]) + results[0] = res.toVal + ?store.resourceDrop(parameters[0].addr) + linker.defineFunc("nev:plugins/text", "[method]rope.debug"): + let self = ?store.resourceHostData(parameters[0].addr, RopeResource) + let res = textDebug(host, store, self[]) + results[0] = res.toVal + ?store.resourceDrop(parameters[0].addr) + linker.defineFunc("nev:plugins/text", "[method]rope.slice"): + let self = ?store.resourceHostData(parameters[0].addr, RopeResource) + let a = parameters[1].to(int64) + let b = parameters[2].to(int64) + let res = textSlice(host, store, self[], a, b) + results[0] = ?store.resourceNew(res) + ?store.resourceDrop(parameters[0].addr) + linker.defineFunc("nev:plugins/text", "[method]rope.slice-points"): + let self = ?store.resourceHostData(parameters[0].addr, RopeResource) + let a = parameters[1].to(Cursor) + let b = parameters[2].to(Cursor) + let res = textSlicePoints(host, store, self[], a, b) + results[0] = ?store.resourceNew(res) + ?store.resourceDrop(parameters[0].addr) + linker.defineFunc("nev:plugins/text", "[static]rope.get-current-editor-rope"): + let res = textGetCurrentEditorRope(host, store) + results[0] = ?store.resourceNew(res) diff --git a/src/scripting/scripting_wasm_components.nim b/src/scripting/scripting_wasm_components.nim index 347fc2b..c8202b9 100644 --- a/src/scripting/scripting_wasm_components.nim +++ b/src/scripting/scripting_wasm_components.nim @@ -1,6 +1,9 @@ import std/[macros, macrocache, genasts, json, strutils, os] import misc/[custom_logger, custom_async, util] -import scripting_base, document_editor, expose, vfs +import scripting_base, document_editor, expose, vfs, service +import nimsumtree/[rope, sumtree] +import layout +import text/[text_editor, text_document] import wasmtime, wit_host @@ -10,9 +13,29 @@ export scripting_base logCategory "scripting-wasm-comp" +type WasmContext = ref object + counter: int + layout: LayoutService + +func createRope(str: string): Rope = + Rope.new(str) + +type RopeResource = object + rope: RopeSlice[Point] + +proc `=destroy`*(self: RopeResource) = + if not self.rope.rope.tree.isNil: + let l = min(50, self.rope.len) + echo &"destroy rope {self.rope[0...l]}" + +template typeId*(_: typedesc[RopeResource]): int = 123 + when defined(witRebuild): static: hint("Rebuilding plugin_api.wit") - importWit "../../scripting/plugin_api.wit", "plugin_api_host.nim" + importWit "../../scripting/plugin_api.wit", WasmContext: + cacheFile = "plugin_api_host.nim" + mapName "rope", RopeResource + else: static: hint("Using cached plugin_api.wit (plugin_api_host.nim)") include plugin_api_host @@ -24,8 +47,9 @@ type linker: ptr ComponentLinkerT moduleVfs*: VFS vfs*: VFS + services*: Services -proc call(instance: ptr ComponentInstanceT, context: ptr ComponentContextT, name: string, params: openArray[ComponentValT], nresults: static[int]) = +proc call(instance: ptr ComponentInstanceT, context: ptr ComponentContextT, name: string, parameters: openArray[ComponentValT], nresults: static[int]) = var f: ptr ComponentFuncT = nil if not instance.getFunc(context, cast[ptr uint8](name[0].addr), name.len.csize_t, f.addr): log lvlError, &"[host] Failed to get func '{name}'" @@ -36,8 +60,8 @@ proc call(instance: ptr ComponentInstanceT, context: ptr ComponentContextT, name return var res: array[max(nresults, 1), ComponentValT] - echo &"[host] ------------------------------- call {name}, {params} -------------------------------------" - f.call(context, params, res.toOpenArray(0, nresults - 1)).okOr(e): + echo &"[host] ------------------------------- call {name}, {parameters} -------------------------------------" + f.call(context, parameters, res.toOpenArray(0, nresults - 1)).okOr(e): log lvlError, &"[host] Failed to call func '{name}': {e.msg}" return @@ -58,23 +82,57 @@ proc loadModules(self: ScriptContextWasmComp, path: string): Future[void] {.asyn let wasmBytes = self.vfs.read(file, {Binary}).await let component = self.engine.newComponent(wasmBytes).okOr(err): - log lvlError, "[host] Failed to create wasm component: {err.msg}" + log lvlError, &"[host] Failed to create wasm component: {err.msg}" continue var trap: ptr WasmTrapT = nil var instance: ptr ComponentInstanceT = nil self.linker.instantiate(self.store.context, component, instance.addr, trap.addr).okOr(err): - log lvlError, "[host] Failed to create component instance: {err.msg}" + log lvlError, &"[host] Failed to create component instance: {err.msg}" continue trap.okOr(err): - log lvlError, "[host][trap] Failed to create component instance: {err.msg}" + log lvlError, &"[host][trap] Failed to create component instance: {err.msg}" continue assert instance != nil instance.call(self.store.context, "init-plugin", [], 0) +proc textEditorGetSelection(host: WasmContext; store: ptr ComponentContextT): Selection = + if host.layout.tryGetCurrentEditorView().getSome(view) and view.editor of TextDocumentEditor: + let editor = view.editor.TextDocumentEditor + let s = editor.selection + Selection(first: Cursor(line: s.first.line.int32, column: s.first.column.int32), last: Cursor(line: s.last.line.int32, column: s.last.column.int32)) + else: + Selection(first: Cursor(line: 1, column: 2), last: Cursor(line: 6, column: 9)) + +proc textNewRope(host: WasmContext; store: ptr ComponentContextT, content: string): RopeResource = + RopeResource(rope: createRope(content).slice().suffix(Point())) + +proc textClone(host: WasmContext, store: ptr ComponentContextT, self: var RopeResource): RopeResource = + RopeResource(rope: self.rope.clone()) + +proc textText(host: WasmContext, store: ptr ComponentContextT, self: var RopeResource): string = + $self.rope + +proc textDebug(host: WasmContext, store: ptr ComponentContextT, self: var RopeResource): string = + &"Rope({self.rope.range}, {self.rope.summary}, {self.rope})" + +proc textSlice(host: WasmContext, store: ptr ComponentContextT, self: var RopeResource, a: int64, b: int64): RopeResource = + RopeResource(rope: self.rope[a.int...b.int].suffix(Point())) + +proc textSlicePoints(host: WasmContext, store: ptr ComponentContextT, self: var RopeResource, a: Cursor, b: Cursor): RopeResource = + let range = Point(row: a.line.uint32, column: a.column.uint32)...Point(row: a.line.uint32, column: a.column.uint32) + RopeResource(rope: self.rope[range]) + +proc textGetCurrentEditorRope(host: WasmContext, store: ptr ComponentContextT): RopeResource = + if host.layout.tryGetCurrentEditorView().getSome(view) and view.editor of TextDocumentEditor: + let editor = view.editor.TextDocumentEditor + RopeResource(rope: editor.document.rope.clone().slice().suffix(Point())) + else: + RopeResource(rope: createRope("no editor").slice().suffix(Point())) + method init*(self: ScriptContextWasmComp, path: string, vfs: VFS): Future[void] {.async.} = self.vfs = vfs @@ -85,22 +143,19 @@ method init*(self: ScriptContextWasmComp, path: string, vfs: VFS): Future[void] var trap: ptr WasmTrapT = nil self.linker.linkWasi(trap.addr).okOr(err): - log lvlError, "Failed to link wasi: {err.msg}" + log lvlError, &"Failed to link wasi: {err.msg}" return trap.okOr(err): - log lvlError, "[trap] Failed to link wasi: {err.msg}" + log lvlError, &"[trap] Failed to link wasi: {err.msg}" return - block: - proc cb(ctx: pointer, params: openArray[ComponentValT], results: var openArray[ComponentValT]) = - results[0] = Selection(first: Cursor(line: 1, column: 2), last: Cursor(line: 6, column: 9)).toVal - echo &"[host][get-selection]:\n {params}\n -> {results}" + var ctx = WasmContext(counter: 1) + ctx.layout = self.services.getService(LayoutService).get - echo "func new" - let funcName = "get-selection" - self.linker.funcNew("nev:plugins/text-editor", funcName, cb).okOr(err): - log lvlError, &"[host][trap] Failed to link func {funcName}: {err.msg}" + self.linker.defineComponent(ctx).okOr(err): + echo "[host] Failed to define component: ", err.msg + return await self.loadModules("app://config/wasm")