-
Notifications
You must be signed in to change notification settings - Fork 2
Syntax validation using workers
In order to perform syntax checking in Ace, you typically need a web worker which will do the heavy lifting asynchronously in the background. Ace provides web workers for several programming languages, but building a custom worker is not exactly simple, because you have to integrate it into Ace and then build it from source.
To make this process a little more accessible, the worker module provides the necessary Ace classes in a separate javascript file, which you simply load before your main worker script. This will give you access to the standard Ace classes like Mirror and Document which you can use to communicate with the editor.
Remember that the worker always runs in a separate context and therefore you should not mix worker and web code in one module (you can include it, but only as a runtime dependency). What you need to do is to create one module with your worker code and then another for your web related code. If you have some code you want to share between those two (such as the parser or the tokenizer), put it into a separate module, ending up with a structure similar to this:
common:
depends on whatever you need...
my-worker:
implementation common
implementation ace-worker
my-web:
implementation common
implementation ace-web
runtime my-worker
The full example of how to set-up your worker related classes is in the demo and demo-worker modules.
WARNING: Unfortunately, due to the way Ace handles workers, this approach does not work in pure static html files, because the worker is started as a blob and cannot access the dependencies using relative paths or file paths. Therefore, in order to use workers created using this method, you will have to serve them using a web server. Of course, for testing, you can use a local web server, the only restriction is that each dependency needs an absolute url.
Once the modules are created, you can define your first web worker. You will want to extend the Mirror class, as it gives you access to a copy of the Document managed by the live editor:
class DemoWorker(sender: Sender) : Mirror(sender) {
init { setTimeout(2000) } // set the refresh interval
override fun onUpdate() {
// Perform syntax validation - using document to access the current text in the editor
// and sender.emit to send the results back to the main context.
}
}
fun main(args: Array<String>) {
// You also have to register the worker class in your main method, otherwise
// Ace won't know how to find it.
DemoWorker::class.js.register()
}
The full example is again included in the demo module. Note that here, we perform a very simple task (checking the parenthesis match), so we don't have any common module.
WARNING: The data you send using
sender.emitwill be serialised and deserialised in order to move it between the contexts. Kotlin's meta-information about classes will be lost during this process. Therefore, you can only send primitive types (including Arrays) if you want to preserve type safety. You can also send structured data, but you have to treat the data asdynamicobjects in your web module because the type info is missing.
To start the worker, you have to override the createWorker method in your custom TextMode and create a WorkerClient which will run and communicate with our worker. In order to safely load all the dependencies, the worker module provides a worker-init.js file, which will load all dependencies sent using the importScripts parameter of WorkerClient. To make this process more intuitive, there is a convenience method named startWorkerClient which you can use:
override fun createWorker(session: EditSession): WorkerClient? {
val client = startWorkerClient(
workerClassName = "DemoWorker", // the name of your worker class
workerInitUrl = "http://.../worker-init.js" // path to the worker-init.js script
dependencies = arrayOf(
"http://.../kotlin.js", // first, load Kotlin
"http://.../ace-classes.js", // then load Ace worker bundle
"http://.../ace-common.js", // wrapper for common classes
"http://.../ace-worker.js", // wrapper for worker-specific classes
"http://.../my-worker.js", // your custom worker
)
)
client.attachToDocument(session.getDocument())
return client
}
This will load the given JS files and then start a worker with class name "DemoWorker" (assuming it is properly registered).
In order to listen to events sent from the worker context, you simply need to listen to events emitted on the corresponding WorkerClient. Ace will also wrap your data into a custom event object, which is described by the WorkerClient.Event interface:
// In worker context:
sender.emit("isValid", true)
// In main context:
client.on<WorkerClient.Event<Boolean>>("isValid") { event ->
if (event.data) ... // do something
}