Goose

Goose for Visual Studio Code

An extension for the Goose language. With a compiler that has goose lsp, the compiler itself is the language server: the extension starts it and it analyzes your programs as you type. With an older compiler, the extension checks saved files with goose check instead. Other editors can use the same server: Goose in other editors has the configuration for Neovim, Helix and Emacs.

Features

The language server also completes names in scope and members after a ., finds references and calls, renames, shows inlay hints, and folds. It does not provide formatting or debugging (docs/design/language_server.md has the plan). A body no instantiation reaches has no types: hover says so, and a member completion there offers every field and function.

Install the prebuilt extension

The repository includes goose-language.vsix. Download it and select Extensions: Install from VSIX... in VS Code, or install from a checkout with:

code --install-extension vscode/goose-language.vsix

No Python, Node.js, or npm is needed to install the prebuilt extension. Compiler checks and running programs still require the Goose compiler (see below).

Build or develop

To rebuild the VSIX, install Python 3 and Node.js with npm (Node.js 22+ recommended), then run from the repository root:

python vscode/build_vsix.py

The script works from any working directory. It installs the locked npm dependencies, runs the extension tests, bundles the extension and the language client library into dist/extension.js with esbuild, and writes vscode/goose-language.vsix. Use --skip-install to reuse existing node_modules. The VSIX file is tracked in Git. Commit the rebuilt file with extension updates so people can install without building locally. The extension version still comes from package.json.

Open this vscode/ directory in VS Code and press F5 to launch an Extension Development Host with the parent Goose repository open; the launch bundles first (npm run bundle, after an npm install). Packaging is local; the package is not published to the Marketplace. The extension is under the repository's Apache License 2.0; the build copies that file into this directory, where vsce looks for it, so it ships inside the package.

Compiler setup

Build Goose separately. By default the extension looks in the workspace folder for goose, build/Release/goose, build/Debug/goose, and build/goose (with .exe on Windows), then uses goose on PATH. For another location, set goose.compilerPath to the executable, without shell quotes or arguments.

Checking accepts a file with no fn main(), so editing an imported file does not report a missing main. For a project with imported files, set the entry file so that checks and runs use the whole program:

{
  "goose.compilerPath": "${workspaceFolder}/build/Release/goose.exe",
  "goose.entryFile": "samples/01_tour.goose",
  "goose.checkOnSave": true,
  "goose.checkTimeout": 15000,
  "goose.runArguments": []
}

Paths are relative to the containing workspace folder, or the source directory when no folder is open. Settings can be configured per folder in a multi-root workspace. goose.stdlibPath supplies --stdlib; leave it empty to use Goose's usual lookup, including GOOSE_STDLIB and directories near the executable.

The language server is started once per window, with the compiler and standard library of the first workspace folder (or of the open file, without one) and every folder's goose.entryFile as the programs it analyzes; a Goose file no entry file imports is analyzed on its own, as goose check would. The extension asks the compiler whether it serves one (goose lsp --probe) at activation and whenever the Goose settings, the folders or the workspace's trust change, and falls back to the checks below otherwise; the server's output is in the Goose output channel. The server fetches no packages: an unfetched package is an error at its import, which Goose: Check Program (a goose check in a terminal, with the language server) or goose fetch resolves.

Without the language server, checks use goose check --diagnostics json and never run the program or generate C. A compiler that rejects the flag is checked with plain goose check from then on, until it is rebuilt, and its text output is parsed instead; the Goose output channel shows the compiler's text either way. Checks run on saved files, wait while any open Goose file in the same folder has unsaved changes, and keep the last result, marked stale, until the next check completes. Changing Goose settings or workspace folders, or closing the files that use an entry file, clears its results instead. Explicit commands save open Goose files in the same folder first. The compiler currently stops at the first error and typechecks instantiated functions; the extension preserves those limitations. Configuring the entry file is particularly useful for libraries.

Run with JIT uses --jit and needs a Goose build with TinyCC enabled. Generate C writes a .c file alongside the entry file, replacing an existing file of that name. Compiler execution is disabled in untrusted workspaces; highlighting, snippets and the outline remain available.

Tasks

JIT Run is the extension's build task. Ctrl+Shift+B runs it directly while editing a Goose file; Check, Build Executable and Generate C remain explicit commands under Tasks: Run Task and in the Command Palette. The editor and status-bar play buttons use the same JIT command. Check-on-save still only checks the program.

The Goose shortcuts take precedence while a Goose editor has focus. To use your existing VS Code build/debug shortcuts instead, set "goose.useRunKeybindings": false. Existing tasks.json group/default choices are preserved. Buttons contributed by other extensions, such as CMake Tools, still control their own build systems; use the Run button for Goose.

Tasks: Run Task offers tasks for the current Goose file (or configured entry, even when no Goose editor is active). For a persistent task, add this to your project's .vscode/tasks.json:

{
  "version": "2.0.0",
  "tasks": [
    {
      "type": "goose",
      "label": "Run Goose with JIT",
      "action": "run",
      "file": "main.goose",
      "group": { "kind": "build", "isDefault": true },
      "problemMatcher": "$goose"
    },
    {
      "type": "goose",
      "label": "Generate C",
      "action": "generateC",
      "file": "main.goose",
      "output": "main.c"
    }
  ]
}

file defaults to goose.entryFile or the active Goose document; ${file} and ${workspaceFolder} are supported. output defaults to the entry file with a .c suffix. Tasks use process arguments directly, including paths with spaces. Use VS Code's task save behavior to save edited files before running custom tasks.

Run configurations

Select Goose: Run with JIT in the Run and Debug configuration picker to use VS Code's native Run controls. This launches the same terminal task, using goose.entryFile or the current Goose file. An optional .vscode/launch.json configuration can choose a specific program:

{
  "version": "0.2.0",
  "configurations": [{
    "type": "goose",
    "request": "launch",
    "name": "Goose: Run with JIT",
    "program": "${workspaceFolder}/main.goose",
    "noDebug": true
  }]
}

Run configurations execute programs without breakpoints or stepping. Stop an executing program with Tasks: Terminate Task or its terminal's trash button. Program arguments still come from goose.runArguments.

Verification

npm test exercises the actual TextMate/Oniguruma grammar, declaration/import scanning, parsing of the compiler's JSON and text diagnostics (including Windows paths and UTF-8 byte columns), the fallback from --diagnostics json to text, task arguments, cancellation/stale-result handling, the language server's probe and setup, and the real server over its own protocol (diagnostics as the text changes, and a hover). Compiler integration tests run when a Goose build exists in the parent repository; set GOOSE_TEST_COMPILER to test another executable. Missing compilers are reported as skipped tests, as are the JSON test with a compiler that lacks the flag and the server test with one that lacks the verb.

For the editor integration suite, create an empty directory build/goose-extension-fixture in the parent repository, then select Test Goose Extension in this directory's Run and Debug menu. This suite writes fixtures and settings only in that disposable directory. It exercises activation, real compiler diagnostics, related error locations, imports, outline, tasks, and the edit/save lifecycle, and real JIT execution through commands, build tasks, and Run configurations, with the language server off, and then a hover answered by the language server where the compiler has one, and its code lenses: a generic function's instantiations, and Run running the file it is in rather than the entry file. Build the compiler with TinyCC first.

The implementation uses VS Code's TextMate grammar support, task provider API, and workspace trust API.