Skip to content

Commit

Permalink
Progress
Browse files Browse the repository at this point in the history
  • Loading branch information
Nimaoth committed Nov 10, 2024
1 parent 9e6b260 commit 227eeb4
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 24 deletions.
9 changes: 9 additions & 0 deletions config/comp_test.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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()
96 changes: 93 additions & 3 deletions config/plugin_api_guest.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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()
2 changes: 1 addition & 1 deletion nev.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
15 changes: 15 additions & 0 deletions scripting/plugin_api.wit
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
1 change: 1 addition & 0 deletions src/app.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
63 changes: 61 additions & 2 deletions src/scripting/plugin_api_host.nim
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import
from std / unicode import Rune

import
results, wit_types
results, wit_types, wasmtime

{.pop.}
type
Expand All @@ -15,4 +15,63 @@ type
column*: int32
Selection* = object
first*: Cursor
last*: Cursor
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)
91 changes: 73 additions & 18 deletions src/scripting/scripting_wasm_components.nim
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
Expand All @@ -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}'"
Expand All @@ -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

Expand All @@ -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

Expand All @@ -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")

Expand Down

0 comments on commit 227eeb4

Please sign in to comment.