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 the server 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, folds, and formats: Format Document (Shift+Alt+F) lays the file out as goose format does, and "editor.formatOnSave": true does it on every save. It does not provide debugging (docs/design/language_server.md has the plan). A function body that no instantiation reaches has no types: hover says so, and 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 runs the bundler 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. It uses the compiler and standard library of the first workspace folder (or of the open file, if there is no folder), and analyzes every folder's goose.entryFile as a program. A Goose file that no entry file imports is analyzed on its own, as goose check would analyze it. The extension asks the compiler whether it provides a language server (goose lsp --probe) at activation and whenever the Goose settings, the folders or the workspace's trust change, and otherwise falls back to the checks described below. The server's output is in the Goose output channel. The server does not fetch packages: an unfetched package is an error at its import, which Goose: Check Program (with the language server, this runs goose check in a terminal) 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 covers the TextMate/Oniguruma grammar, declaration and import scanning, and parsing of JSON and text diagnostics, including Windows paths and UTF-8 byte columns. It also checks fallback from --diagnostics json to text, task arguments, cancellation, and handling of stale results. Language-server tests cover probing and setup, then use the real protocol to check diagnostics after text changes and a hover response.

Compiler integration tests run when a Goose build exists in the parent repository. Set GOOSE_TEST_COMPILER to test another executable. A missing compiler is reported as a skip. So are JSON tests with a compiler that lacks the flag, and server tests 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. With the language server off, it exercises activation, real compiler diagnostics, related error locations, imports, outline, tasks, the edit/save lifecycle, and real JIT execution through commands, build tasks, and Run configurations. Then, where the compiler has a language server, it checks a hover answered by the server and the server's 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.