Skip to content

Async-extern ABI: bind withProgress + LanguageClient.sendRequest #103

@hyperpolymath

Description

@hyperpolymath

Problem

The AffineScript extern-call ABI is synchronous: every extern fn is a Wasm import that returns a single primitive value to the caller in the same frame. This means Thenable-returning JS APIs (which are most of vscode's "interactive" surface) cannot be bound at all.

Two concrete APIs needed by rhodium-standard-repositories/satellites/rsr-certifier/extensions/vscode/src/extension.affine (ported in standards#46) are blocked by this:

// 1. withProgress: second arg is async () => Thenable<T>
vscode.window.withProgress(
    { location: vscode.ProgressLocation.Notification, title: '...' },
    async () => { /* await client.sendRequest(...) */ }
);

// 2. LanguageClient.sendRequest: returns Thenable<T>
const result = await client.sendRequest('rsr/getCompliance');

The current rsr-certifier port falls back to shelling out to the rsr CLI in a fresh terminal for all four commands (the same fallback path the original TS extension already had for the no-LSP case). The downstream effects of the unbound APIs are gone:

  • Status-bar text mutation on every check result (now: static initial value)
  • DiagnosticCollection population from check results (now: collection created and disposed, never populated)
  • Webview report with per-check rendered HTML (now: constant placeholder shape)
  • onDidSaveTextDocument filtering by saved doc's basename (now: zero-arg handler, no-op to avoid spawning a terminal per save)

Why this matters

Every vscode extension that wants real-time in-process behaviour (not "open a terminal and run a CLI") needs one or both of these APIs. As the AffineScript-port footprint grows, this becomes load-bearing — withProgress alone is ubiquitous in extensions that show notifications, perform long-running work, or call out to language servers.

Design sketch

The simplest viable shape would surface JS Promise/Thenable handles to AffineScript via a small set of primitives, e.g.:

extern type Thenable;

// Resolve a wasm-table thunk against a Thenable. The thunk re-enters wasm
// when the Thenable settles. The result is registered in the handle table
// and accessible via the result-getter primitives below.
extern fn thenableThen(t: Thenable, handler: Int) -> Disposable;

// After the thunk fires, read the last-settled result for `t`.
extern fn thenableResultString(t: Thenable) -> String;
extern fn thenableResultInt(t: Thenable) -> Int;
extern fn thenableResultJson(t: Thenable) -> String;

withProgress then becomes:

extern fn withProgressNotification(title: String, work_thunk: Int) -> Thenable;

sendRequest becomes:

extern fn languageClientSendRequest(c: LanguageClient,
                                     method: String,
                                     params_json: String) -> Thenable;

This keeps the synchronous extern-call shape (each binding returns immediately) while modelling async by chaining wasm-table thunks. Compositionally it's awkward — you can't write let x = await foo() in AffineScript yet — but it's enough to express the rsr-certifier patterns.

A more invasive option is to grow the AffineScript language with proper async fn / await and have codegen emit JS shims that await on the host side. That's a much bigger lift.

Out of scope

  • Browser-host webworker extension host. The current Node-CJS target binds to require() + Node Buffer. A webworker target would have to drop those.
  • Multi-promise composition (Promise.all etc.). The minimum viable design is single-thenable resolution.

Acceptance criteria

  • Concrete binding surface for vscode.window.withProgress(opts, task) and LanguageClient.sendRequest(method, params) in stdlib/Vscode.affine + stdlib/VscodeLanguageClient.affine.
  • JS-side implementation in packages/affine-vscode/mod.js.
  • A pilot consumer (most likely hyperpolymath/standards rsr-certifier) restores the lost in-process behaviour using these bindings.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions