# Debugging

> Debug Langoost programs with the terminal (TUI) debugger or the built-in Debug Adapter Protocol (DAP) server — breakpoints, stepping, locals and globals, in VS Code, Neovim, and any DAP editor.

# Debugging

Langoost ships two debuggers: a **terminal (TUI) debugger** for quick
command-line sessions, and a **Debug Adapter Protocol (DAP)** server for editors.

## Terminal debugger

`langoost debug <file.goost>` opens an interactive TUI — a source viewer with
breakpoints and stepping, no editor required:

```
$ langoost debug app.goost
```

It pauses on the first line by default so you can set breakpoints; pass
`--stop-on-entry=false` to start running immediately.

## Editor debugging (DAP)

Langoost also ships a **Debug Adapter Protocol (DAP)** server, so any DAP-aware
editor — VS Code, Neovim (nvim-dap), JetBrains, Helix, Zed — can debug `.goost`
programs with breakpoints, stepping, and variable inspection.

## Running the adapter

```
$ langoost dap                 # speak DAP over stdin/stdout (editor default)
$ langoost dap --port 4711     # listen on a TCP port (long-running adapter)
```

Editors normally use stdio: the editor spawns `langoost dap` as a child process
and the framing handshake happens over the pipes automatically.

## VS Code

Add a launch configuration in `.vscode/launch.json`:

```jsonc
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "langoost",
      "name": "Run current Langoost file",
      "request": "launch",
      "program": "${file}",
      "stopOnEntry": false
    }
  ]
}
```

VS Code also needs a small extension shell that tells it how to spawn the
adapter — a `package.json` contributing a `debuggers` entry with
`"program": "langoost", "args": ["dap"]`. Package it with `vsce package` and
install the `.vsix`; then **F5** debugs the active file.

## Neovim (nvim-dap)

```lua
local dap = require('dap')
dap.adapters.langoost = {
  type = 'executable',
  command = 'langoost',
  args = { 'dap' },
}
dap.configurations.langoost = {
  {
    type = 'langoost',
    request = 'launch',
    name = 'Run current file',
    program = function() return vim.fn.expand('%:p') end,
    stopOnEntry = false,
  },
}
```

## What works

| Capability | |
| --- | --- |
| Set / unset breakpoints by `file:line` | ✅ |
| Continue, Step Over, Step Into, Step Out | ✅ |
| Stop on entry (`stopOnEntry: true`) | ✅ |
| Stack trace with function names and source paths | ✅ |
| Locals panel — function arguments and `let` bindings | ✅ |
| Globals panel — top-level `let` declarations | ✅ |
| Stdout / stderr forwarded to the debug console | ✅ |
| Evaluate a single identifier in scope (watch / hover) | ✅ |

## Not yet

Conditional breakpoints (the `condition` is accepted but ignored), log points,
evaluating arbitrary expressions (only bare identifiers today), and per-task
debugging of `thread.core.spawn` child VMs are planned but not yet implemented.

## How it works

On `launch`, the adapter compiles the script, attaches `DebugHooks` to a fresh
VM, and runs it in a goroutine. Each iteration of the run loop checks the hooks:
if the next instruction's line has a breakpoint (or you're stepping), the VM
emits a `stopped` event and blocks until a resume command arrives. When no
debugger is attached the per-instruction cost is a single nil check. See
**[architecture](/docs/architecture)** for the VM internals.