Shoddy Documentation

VS Code

Highlighting, snippets, debugging and mill commands — now with a Run button

This page covers everything about writing Shoddy in VS Code: installing the extension, the tasks, native debugging (the perch — Shoddy's debugger), and the one environment wrinkle worth understanding. An extension is an add-on package that teaches VS Code new tricks — here, a whole language. This one carries its own mill (the program that compiles and runs Shoddy) and machines, so the only prerequisite is the .NET 10 runtime — Setup, Track A, two steps.

Only two sections here ask you to do anything: installing the extension (required, once) and the smoke test (recommended, a minute). Everything else is optional with its trigger stated on the section, plain usage (§3§4, §6), or background (§5).

Key note. Shortcuts are written Cmd+… / Ctrl+… — macOS uses Cmd, Windows and Linux use Ctrl. Everything else on this page is identical on every platform.

1. Install the extension Required · once

Shoddy has its own extension, and it provides:

It also carries a complete toolchain — every tool needed to build and run Shoddy. A mill and every machine ride inside the package, so installing it is the whole install. Download the latest .vsix from the latest release (one package, every platform), then in VS Code: Extensions view → ··· menu → Install from VSIX… → pick the file you downloaded. Reload when prompted.

The bundled mill is portable .NET: mill.exe on Windows, and dotnet mill.dll everywhere else, through the runtime Setup had you install. The machines travel with it. That is why a bare Include "seq.shoddy" resolves in any folder you open, with nothing set.

Optional — only if you've edited the extension: repackage from the repo root, which stages a fresh mill and freshly built machines into the package first:

./build.sh vsix          # ./build.ps1 vsix on Windows

Optional — only if you're developing the extension: to iterate without packaging at all, open the vscode-shoddy/ folder in VS Code and press F5 — that launches an Extension Development Host, a second VS Code window with your edited extension loaded.

Tip If GitHub Copilot is in this window too, spend five more minutes on Grounding an Assistant. Copilot has never read a line of Shoddy and will write you BASIC unless a .github/copilot-instructions.md tells it otherwise; that page has the file to copy.

2. Point it at the mill Optional

The extension looks for a mill in four places, in order: the shoddy.millPath setting; bin/mill inside your workspace — the folder you have open (bin/mill.exe on Windows); its own bundled copy; then mill on your PATH, the list of folders your terminal searches for commands. The bundled copy means this section is skippable for almost everyone.

The one ordering to know: a workspace's own bin/mill beats the bundled one. That is deliberate. When the repo is your open folder, the mill you just built is the one you want tested, not the one that shipped. Set millPath explicitly in two cases: you keep several checkouts — separate copies of the repository — and want the editor pinned to one regardless of which is open, or something else named mill shadows the real one.

If you do set it, set it in User settings, not per-workspace. Then every folder you ever open — inside the repo or out — finds it with zero per-folder setup: Ctrl+Shift+P / Cmd+Shift+PPreferences: Open User Settings (JSON), then add:

"shoddy.millPath": "C:\\github\\shoddy\\bin\\mill.exe"

(macOS/Linux: the full absolute path to bin/mill — don't rely on ~ expanding inside settings.)

The repo's .vscode/settings.json pins what matters either way: Shoddy's blocks are indentation-based (a tab counts as 4) and the whole corpus uses 4 spaces — let the editor keep it that way.

3. Debugging — the perch Usage · nothing to configure

Start here: it's the fastest way to see the whole chain working, and it needs zero configuration. Shoddy has native debugging — running your program one line at a time while you watch what it does. In mill-speak the debugger is the perch — where woven cloth was inspected for flaws. Open any .shoddy file and press F5: no launch.json needed. The program compiles with debug instrumentation — the extra bookkeeping that lets the debugger watch each line. It is the same program mill run builds, machines and all, and it stops on its first line. If it would not compile, the launch fails with the error the runner gives, rather than starting and dying part-way through. Then:

Limitation: under the perch, Input reads end-of-file, because typed input (interactive stdin) isn't wired through the debug session. Debug the logic here, and run the interactive session with the run task from section 4.

4. Run, weave, build — inside VS Code Usage · nothing to configure

Open the repo (code /path/to/shoddy) and the shipped .vscode/tasks.json wires six tasks to the mill — they're committed, so there's nothing to set up:

Task What it does
Shoddy: run current file mill run on the file you're editing — bound to Cmd+Shift+B / Ctrl+Shift+B
Shoddy: weave current file compiles to a .dll beside the source (dotnet FILE.dll runs it)
Shoddy: build machine from current file compiles a machines/*.shoddy to Shoddy.Machines.<Name>.dll
Shoddy: show generated C# for current file prints what the weave emits — the readable compiler output
Shoddy: golden conformance tests dotnet test — the five goldens, the machines path, and the TCO check
Shoddy: rebuild mill into bin/ re-publishes the mill into bin/ (folder)

Workflow: open tst/examples.shoddy, press Cmd+Shift+B / Ctrl+Shift+B, and the program runs in the integrated terminal — the terminal panel inside the editor. Everything else is under Terminal → Run Task… (Cmd+Shift+P → “Run Task”).

Note the tasks are repo-local — in a folder outside the repo, use the extension's own Γû╢ Run button (or ctrl+r), which works everywhere.

Optional — only if you want a dedicated run key beyond Ctrl+Shift+B: add to your keybindings.json (Cmd+Shift+P → "Open Keyboard Shortcuts (JSON)"; on Windows use ctrl+r ctrl+r):

{
    "key": "cmd+r cmd+r",
    "command": "workbench.action.tasks.runTask",
    "args": "Shoddy: run current file",
    "when": "editorLangId == shoddy"
}

Building, without leaving the editor

Everything the mill builds can be built from in here — no terminal commands to remember:

5. How the extension sees your environment Background · no action

Nothing to configure here. This section explains what the extension does with SHODDYLIB on your behalf, and matters only if you decide to set one yourself.

The extension invokes mill five different ways — Run, Weave, Machine, Show Generated C#, and F5 Debug — and they don't all receive environment variables the same way. (An environment variable is a named setting the operating system hands to a program when it starts.) Run, Weave and Machine go through VS Code's integrated terminal, which picks up terminal.integrated.env.* from workspace settings. Show Generated C# and F5 Debug spawn the process directly from the extension host — the background process that runs extensions. Those two inherit only the environment the VS Code process itself was launched with. Historically that split was a trap: a SHODDYLIB set in workspace settings made Γû╢ Run work while silently breaking F5 on the first Include-using program.

The extension now closes that gap itself. It points SHODDYLIB at its own bundled machines by both routes at once — a terminal environment collection for the terminal commands, and an explicit environment for the two processes it spawns — so all five behave identically out of the box.

If you set SHODDYLIB yourself, the extension steps aside entirely and yours wins everywhere. Set it where the VS Code process will see it — a User environment variable on Windows, a shell-profile export on macOS (VS Code resolves your login-shell environment at launch, Dock launches included). Then restart VS Code fully (macOS: Cmd+Q, relaunch), because a program copies its environment variables once, at launch. Setting it in a workspace's terminal.integrated.env reopens exactly the split described above, so don't.

One other environment variable the mill reads: SHODDY_LINT, set to anything, turns on the weave-time accessor-shadowing warning. The same launch rules apply — set it where the VS Code process will see it if you want F5 and Γû╢ Run to lint alike. What it warns about, and why it's off by default, is on the Toolchain page.

6. Interactive programs Usage · nothing to configure

Tasks run in the integrated terminal, so programs that use Input just work — run tst/gradebook.shoddy with Cmd+Shift+B / Ctrl+Shift+B and type at the prompts. To replay its scripted session instead (a command-line affair — the only one on this page):

bin/mill run tst/gradebook.shoddy < tst/golden/gradebook.in

7. Smoke-test the whole chain Recommended · once

Do this once after installing the extension — and again whenever SHODDYLIB or millPath changes, or a mill you build yourself moves. Use a scratch folder outside any checkout. That is the real test: it proves a program folder anywhere on disk will work, not just files inside the source tree.

  1. Create check.shoddy in a scratch folder:
    Include "stats.shoddy"
    
    Def Main()
        Print(Sum({ 1, 2, 3 }))
  2. Open the folder in VS Code and run it with the Γû╢ Run button — expect 6, not unknown word: Sum or a “cannot open” error. (Ctrl+Shift+B won't exist out here — the tasks are repo-local.)
  3. Press F5 on the same file and confirm it also runs clean under the debugger — this is the step that catches the terminal-vs-extension-host environment gap from section 5, before it surprises you mid-program.
  4. Delete the scratch folder once both pass.

8. Troubleshooting As needed

Editor-side symptoms. If the command line is broken too, start with Setup's troubleshooting instead — fix the machine before the editor.

SymptomLikely cause
Works on Γû╢ (Run) but fails only on F5 or “Show Generated C#” A SHODDYLIB of your own was set only in a workspace's terminal.integrated.env, where the two spawned processes never see it — section 5
unknown word: Sum (or any library word) on Run and F5 alike A SHODDYLIB left over from an older install points somewhere that no longer holds the machines — it overrides the bundled ones. Clear it and fully restart VS Code (macOS: Cmd+Q); with it unset the extension supplies its own
macOS: works when VS Code is launched from a terminal (code .) but not from the Dock Only possible with a SHODDYLIB or PATH of your own: the login-shell environment wasn't resolved. Keep the export in ~/.zshrc or ~/.zprofile and fully relaunch — or drop it and let the bundled toolchain answer, which no Dock launch can miss
“The framework Microsoft.NETCore.App version 10 was not found” The .NET 10 runtime isn't installed, or VS Code predates the install — Setup step A1, then restart VS Code
Mill not found at all All four lookups missed, which normally means the package was built without its mill staged. Check shoddy.millPath isn't set to a stale path (it's checked first, and a wrong value beats the bundled copy), then reinstall the .vsix from the Releases page
Ctrl+Shift+B does nothing outside the repo Expected — the six tasks live in the repo's .vscode/tasks.json. Use the Γû╢ Run button or ctrl+r, which the extension provides everywhere