Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

RFD 0032 — oxup manages editor-extension installation

  • State: committed
  • Opened: 2026-06-13
  • Decides: how the Argon editor integration (the VS Code extension today; Neovim / Vim / Emacs later) is installed and kept in sync with the active toolchain — by oxup, abstracted over editors, rather than hand-installed. Closes the “users must hand-install the VS Code extension” gap. Builds on RFD 0013 (the oxup manager + the argon.sharpe-dev.com CDN) and RFD 0013 (the oxup manager + dist layout).
  • Implements: a new oxup extension (alias ext) subcommand; an EditorIntegration abstraction; a CDN asset path for the .vsix; auto-wiring from oxup init / oxup update.

This RFD records, as built, the editor-extension story. The first cut shipped the VS Code family (VS Code, Cursor, VSCodium, VS Code Insiders) via an --install-extension CLI; Neovim, Vim, and Emacs (#393) now install by file placement — the embedded plugin tree plus a managed config block in the user’s init file — since those editors have no install-CLI contract. Nothing is silently skipped (the no-hollow-features house rule).


Question

Today the Argon VS Code extension (editors/vscode/, id argon-lang.argon) is built by release.yml’s build-vsix job and attached to the GitHub Release. A user who wants it must find the .vsix, download it, and run code --install-extension by hand — there is no version coupling to the toolchain they installed, and nothing refreshes it when they oxup update. We already own the install story for the toolchain (oxup install fetches a version-matched, sha256-verified artifact from the CDN). The editor extension should ride the same rails.

  1. What is the command surface? One subcommand, abstracted over editors, so vim/neovim/emacs can slot in without a new top-level verb.
  2. How does the extension version stay coupled to the toolchain? A user on stable 0.2.1 must get the 0.2.1 extension, not “latest”.
  3. Where does the asset live and how is it verified? Same discipline as the toolchain fetch: immutable versioned CDN path, sha256 sidecar, fail-closed.
  4. What happens for editors we don’t yet support? Loud refusal or silent skip?

Decision

1. Command surface — oxup extension (alias ext)

oxup extension install   [--editor <vscode|cursor|codium|code-insiders|code-server|neovim|vim|emacs>] [--archive <path.vsix>] [--extensions-dir <dir>]
oxup extension uninstall [--editor <name>] [--extensions-dir <dir>]
oxup extension list
  • install (no --editor): auto-detect every installed VS Code-family editor (by its CLI on PATH) and install the extension matching the active toolchain version into each. With --editor, target exactly one. With --archive <path.vsix>, install a local .vsix (offline / a freshly built extension) instead of fetching from the CDN.
  • uninstall: remove argon-lang.argon from the detected (or --editor-named) editors.
  • list: show detected editors and, for each, the installed argon-lang.argon version (or “not installed”).

2. Editor abstraction — EditorIntegration

An enum Editor with a small behavioral surface (oxup/src/extension.rs):

methodmeaning
name() -> &strthe --editor key (vscode, cursor, codium, code-insiders, code-server, neovim, vim, emacs)
detect() -> Option<PathBuf>the editor’s CLI on PATH, or None
install_argv(vsix) -> Result<Vec<OsString>>the exact argv to install a .vsix
uninstall_argv() -> Result<Vec<OsString>>the exact argv to uninstall argon-lang.argon

The VS Code family maps vscode→code, cursor→cursor, codium→codium, code-insiders→code-insiders, code-server→code-server; detect by that CLI on PATH; install via <cli> --install-extension <vsix> --force; uninstall via <cli> --uninstall-extension argon-lang.argon.

code-server (browser-hosted, server-side VS Code — Daytona/ODE sandboxes) is a full member of the family: it honors the same --install-extension/--uninstall-extension contract. A server sandbox usually has only code-server on PATH (no desktop code), so it is in the auto-detect order — but last, after the desktop editors, so a desktop editor wins when both are present. Because the running code-server instance is launched with an explicit --extensions-dir, a fresh code-server --install-extension would otherwise land in the default dir; the optional --extensions-dir <DIR> argument on install/uninstall is appended to the editor argv to target the dir the live server actually reads. The flag is accept-and-passthrough for the desktop CLIs too (VS Code’s code supports it), optional, and omitted by default (the CLI’s default dir). code-server has no macOS .app bundle, so its detection is PATH-only (no bundle fallback).

Neovim, Vim, Emacs have no --install-extension CLI, so they install by file placement (oxup/src/editor_plugin.rs, #393): the plugin sources (editors/nvim/, editors/emacs/) are embedded into the oxup binary, placed into the editor’s native package dir, and the user’s init file gets a managed config block between begin/end sentinels that loads the plugin and points ox lsp at the active toolchain.

  • Neovim$XDG_CONFIG_HOME/nvim/pack/argon/start/argon (auto-loaded by Neovim’s built-in packages), managed block in init.lua.
  • Vim~/.vim/pack/argon/start/argon (the syntax + filetype-detection floor; the Lua LSP client needs Neovim 0.8+), managed block in ~/.vimrc.
  • Emacs~/.emacs.d/argon on load-path, managed block in ~/.emacs.d/init.el.

The block is idempotent and non-destructive: re-running rewrites only the region between the sentinels (one block, never duplicated), leaving hand-written config untouched; uninstall strips the block and removes the placed tree. The sentinels are commented in the init file’s own language (-- for Lua, " for Vimscript, ;; for Lisp) so the line is never a syntax error. The install_argv / uninstall_argv CLI helpers still refuse these editors loudly — they have no CLI contract — and redirect to the file-placement path; nothing is a silent no-op.

Version coupling for file-placement editors. There is no CDN .vsix to address. Instead the coupling is structural: oxup ships from the same release pipeline as the toolchain and carries the same version, so the embedded plugin (including its generated version.lua / argon-version.el stamp) matches the toolchain oxup installs, and oxup update (which self-updates oxup and re-runs the install) refreshes it. The managed block additionally points ox lsp at the active toolchain’s ox, so the editor always talks to the matching language server. Auto-wire stays VS-Code-only: oxup init/update never write into a user’s init.lua/init.el unbidden; file-placement editors install only on an explicit --editor.

The placement plan is constructed and asserted in tests over a temp tree without invoking a real editor; only the write/create_dir_all/remove side effects run at the CLI boundary.

3. Asset source + version coupling

The extension version tracks the toolchain version. In release.yml’s build-vsix job, the resolved $version is stamped into editors/vscode/package.json version before vsce package, and the artifact is named argon-<version>.vsix. publish-dist uploads it (plus a bare-hex .sha256 sidecar) to:

s3://argon-dist-sharpe/editors/vscode/<version>/argon-<version>.vsix
                       /editors/vscode/<version>/argon-<version>.vsix.sha256
CDN: https://argon.sharpe-dev.com/editors/vscode/<version>/argon-<version>.vsix

oxup extension install (no --archive) resolves the active toolchain’s concrete version (read from the installed toolchain’s manifest.toml version, so a stable channel maps to the real 0.2.1), fetches editors/vscode/<version>/argon-<version>.vsix, sha256-verifies it against the sidecar (same fail-closed discipline as the toolchain fetch), writes it to a temp file, and hands that path to the editor CLI’s --install-extension. The .vsix is also still attached to the GitHub Release (the secondary download path).

The CDN base URL is the existing dist_base_url() ($OXUP_DIST_URL, default https://argon.sharpe-dev.com), so a mirror / smoke host is honored end to end.

4. Auto-wire from init / update

After the toolchain is placed:

  • oxup init (non-minimal) detects installed VS Code-family editors and installs/refreshes the matching extension. --no-extension skips it; --minimal already skips it (it doesn’t fetch a toolchain at all). If no editor is detected, print a quiet one-line note — not an error.
  • oxup update refreshes the extension for the channel it updated, but only when the version changed: the installed extension version is recorded in settings.toml ([extension] installed_version), and update re-installs only if the new toolchain version differs.

A failed extension install during init/update is a soft failure (warn, don’t abort): the toolchain is what matters; the extension can be installed later with oxup extension install.

5. Editor-support matrix

EditorStatus
VS Code (code)supported
Cursor (cursor)supported
VSCodium (codium)supported
VS Code Insiders (code-insiders)supported
code-server (code-server)supported — server-side VS Code (Daytona/ODE); --extensions-dir for the live server dir
Neovim (neovim)supported — file placement into nvim/pack/argon/start/argon + managed init.lua block (#393)
Vim (vim)supported — syntax + ftdetect floor into ~/.vim/pack/... + managed .vimrc block (#393)
Emacs (emacs)supported — .el package into ~/.emacs.d/argon + managed init.el block (#393)

Why this shape

  • One verb, editor-abstracted. A single extension subcommand with an Editor enum keeps the vim/neovim/emacs work a matter of filling in install_argv, not adding CLI surface. The no---editor auto-detect mirrors how a user expects “install the extension” to just work across whatever VS Code-family editors they have.
  • Version coupling over “latest”. Stamping the toolchain version into the .vsix and addressing it at an immutable /editors/vscode/<version>/ path means a pinned toolchain gets a matching extension, and the CDN path is 1-year-cacheable like the toolchains. This avoids a “latest extension against an old toolchain” skew once the extension grows toolchain-coupled behavior (LSP protocol versions, server flags).
  • Reuse the fetch discipline. The .vsix fetch reuses dist_base_url() and the same sha256-verify-before-use path as the toolchain, so there is no second, weaker download path.
  • Loud, not silent, for unsupported editors. Recognizing vim/neovim/emacs and refusing with a specific pointer (and an issue number) is the house rule: a no-op that pretends to work is worse than an honest “not yet.”

Out of scope / deferred

  • Windows. v0.2 is macOS + Linux only; the VS Code CLIs exist on Windows but the rest of the oxup layout is unix-only (RFD 0013).
  • Marketplace / Open VSX publish. Argon is Sharpe-internal; the .vsix is distributed via the private CDN + the GitHub Release, not a public marketplace.