// SPDX-License-Identifier: CC-BY-SA-4.0
// Copyright (c) 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
= affine-vscode

JS-side adapter for the `stdlib/Vscode.affine` and
`stdlib/VscodeLanguageClient.affine` binding modules. Issue #35 Phase 2
deliverable.

== Install

[source,sh]
----
npm install @hyperpolymath/affine-vscode
----

Add it to your extension's `package.json` `dependencies`. `vscode` and
`vscode-languageclient` are peer dependencies (the latter is optional —
omit it with `--vscode-extension-no-lc`).

== Usage

=== Recommended: `--vscode-extension` (issue #105)

Compile with the `--vscode-extension` flag and the generated `.cjs` is
directly loadable as the extension's `main` — the adapter wiring is
emitted inline, so there is no hand-written entry point to maintain:

[source,sh]
----
affinescript compile src/extension.affine -o out/extension.cjs --vscode-extension
----

Point your extension's `package.json` `main` at the unmodified
`out/extension.cjs` and list `@hyperpolymath/affine-vscode` in
`dependencies`. Sub-flags: `--vscode-extension-adapter <specifier>`
overrides the adapter `require()` target; `--vscode-extension-no-lc`
omits the `vscode-languageclient/node` dependency.

=== Manual wiring (fallback)

If you cannot use the flag, set the shim's `extraImports` hook before the
first `activate`/`deactivate` call so the extern fns resolve to live
vscode APIs. Pass the shim module itself as the third argument — the
adapter reads its handle table and `_instance` lazily:

[source,javascript]
----
const vscode = require("vscode");
const lc = require("vscode-languageclient/node");
const makeBindings = require("@hyperpolymath/affine-vscode");

const shim = require("./extension.cjs");
shim.extraImports = () => makeBindings(vscode, lc, shim);

exports.activate = shim.activate;
exports.deactivate = shim.deactivate;
----

== Surface

The adapter implements every `extern fn` declared in:

* `stdlib/Vscode.affine` — initial set (issue #35 Phase 2): commands.registerCommand,
  workspace.getConfiguration / createFileSystemWatcher,
  window.{showError,showWarning,showInformation}Message,
  window.createTerminal + terminal.show / sendText,
  ExtensionContext.subscriptions.push, window.activeTextEditor,
  editorActive{FilePath,LanguageId}, workspaceConfigGet{Bool,String},
  consoleLog, execSync, and three string helpers.
+
Expanded 2026-05-11 for the rsr-certifier port (issue #64):
workspaceFolderFirstPath / workspaceRootUri, uriFromPath / uriJoinPath / uriPath,
fsWriteFile, openTextDocument / showTextDocument,
createStatusBarItem + statusBarItem.{setText,setTooltip,setCommand,setBackgroundColorTheme,show,hide,asDisposable},
createDiagnosticCollection + diagnosticCollection.{clear,setForUri,asDisposable},
createWebviewPanel + webviewPanel.{setHtml,asDisposable},
clipboardWriteText, onDidSaveTextDocument,
pathBasename / pathJoin / processPlatform, and extensionAbsolutePath.

* `stdlib/VscodeLanguageClient.affine` — 3 bindings covering
  new LanguageClient(...) / start() / stop().

== Deliberate omissions

Two API shapes are not bound because they cannot be expressed in the
current synchronous extern-call ABI without an async-extern hookup:

* `vscode.window.withProgress(opts, async task)` — the second arg is an
  async Thenable-returning task.
* `LanguageClient.sendRequest(method, params)` — returns a Thenable.

Extensions that need either should fall back to shelling out to a CLI
via `Vscode::createTerminal` / `Vscode::execSync` until an async-extern
ABI lands.

== Design notes

* All host objects (Disposable, Terminal, ExtensionContext, StatusBarItem,
  DiagnosticCollection, WebviewPanel, Uri, TextDocument, ...) are
  represented as opaque integer handles on both sides of the FFI. The
  adapter maintains a JS-side handle table.
* String args are passed across as i32 pointers into the wasm linear
  memory; the adapter reads the `[u32 length][utf-8 bytes]` layout out
  of `instance.exports.memory.buffer`.
* Wasm function-pointer args (e.g. command handlers) come in as
  `__indirect_function_table` indices; the adapter wraps each in a JS
  thunk that re-enters the wasm module on invocation.
* Diagnostics avoid a Range+Diagnostic struct FFI by accepting a JSON
  array: `[{startLine,startCol,endLine,endCol,message,severity}, ...]`.
  The adapter parses it and constructs the vscode objects.

== Status

Phase 2 — bindings landed; package published on npm as
`@hyperpolymath/affine-vscode@0.1.0` (2026-05-26, issue #104). The
pilot affinescript port (issue #63), `hyperpolymath/my-lang/vscode-extension/`
and `hyperpolymath/standards/rhodium-standard-repositories/satellites/rsr-certifier/extensions/vscode/`
(issue #64) all consume this adapter from the registry. The publish
workflow `affine-vscode-publish.yml` ships subsequent versions on
`affine-vscode-v*` tag pushes. Phase 4 (rattlescript-face sweep) still
to do.
