std/extensions

Extensions of an application. Each is a folder with a manifest, extension.toml, and a Cuarzo program, made with std/extension, that runs in a process of its own and talks to the application over JSON messages; an extension that fails, or never answers, does not take the application down. An editor built on it runs language servers for other languages as extensions, as their manifests say.

import "std/extensions"

ExtensionHost host = extension_host("1.2")       // the application's API
host.load_folder(config_dir() + "/app/extensions")?
host.activate("onStartup")
for c in host.commands() do
    print(c.id, c.title)
end
print(host.run_command("todo.count", args)?)
[extension]
id = "acme.todo"
name = "TODO"
version = "1.0.0"
api = "1.0"                     # the API it was written for
main = "todo.cz"                # built by the host, or: program = "build/todo"
activation = ["onCommand:todo.count", "onLanguage:todo"]

[[commands]]
id = "todo.count"
title = "Count TODOs"

[[menus]]
location = "editor/context"
command = "todo.count"
when = "textFocus"

[[keybindings]]
key = "ctrl+alt+t"
command = "todo.count"
when = "textFocus"

[[settings]]
key = "todo.pattern"
type = "string"
default = "TODO"

[[languages]]
id = "todo"
extensions = [".todo"]

[[grammars]]
language = "todo"
path = "todo.tmLanguage.json"

[[themes]]
label = "Todo Dark"
path = "dark.json"
kind = "dark"

[[views]]
id = "todo.list"
title = "TODOs"
location = "sidebar"

[[language_servers]]
language = "todo"
command = ["./server", "--stdio"]

An extension runs on the application when it was written for the same major version of the API and a minor version up to the application's (api_compatible(wanted, has)); one that is not, or whose manifest is wrong, is loaded as failed, with its problem.

Method of ExtensionHost
load_folder(folder), load(folder), find(id) the extensions; each an Extension with its manifest, state ("inactive", "active", or "failed"), problem, and crashes
commands(), menus(), keybindings(), settings_schema(), languages(), grammars(), themes(), views(), language_server(language) what the usable extensions add, without running them: key bindings for std/keybindings, settings as a JSON Schema for std/settings
activate(event) starts the extensions that wait for event, as "onStartup", "onLanguage:todo", or "onView:todo.list"; "*" starts all
run_command(command, args) runs a command, starting its extension first ("onCommand:<id>"); its result, or an ERROR of kind "extension" when the extension answers with one, stops, or takes longer than timeout_ms (10 seconds)
send_event(name, data) an event to every running extension, as "settingsChanged"
pump(wait_ms) handles what the extensions sent, while the application waits
stop() asks each extension to shut down, and ends those that do not

host.answer answers what extensions ask (an ExtensionRequest with the extension's id, the method, and the params): SOME with a result or an error, or NONE, which tells the extension the application does not know the request. What extensions log is in host.log. An extension that stops, or does not answer in time, is ended and started again for the next command, up to max_crashes (3) times. A host builds an extension's main file with host.compiler (cuarzo).

std/extension

An extension's side. run_extension(activate) serves the application until it shuts the extension down: it calls activate once, with an ExtensionContext, then runs the commands registered and calls the listeners of events. A command that panics answers with an error, and the extension goes on.

import "std/extension"

exit(run_extension(func(ExtensionContext ctx) do
    ctx.register_command("todo.count", func(Json args) Result[Json] do
        return Result.OK(Json.NUMBER(f64(count_todos(json_text(json_get(args, "text"))))))
    end)
end))
Method of ExtensionContext
register_command(id, handler) a command: handler gets its arguments and gives its result
on_event(name, handler) listens to an event
request(method, params) asks the application and waits for its answer
notify(method, params), log(text) tells it something

It also has the application's api, the event that activated it, and the settings the application last sent. The messages are JSON with a Content-Length header, as the Language Server Protocol has them: activate, command, event, and shutdown from the application; any request and log from the extension.