# IDE and DAP integration This chapter covers editor-driven debugging: `Perl::LanguageServer` in VS Code and its counterparts in Neovim / Emacs / IntelliJ, remote-over-SSH debugging, and container-mode debugging via `kubectl` or `docker`. Readers who reach for an IDE rather than a terminal prompt will find here the minimum viable setup, the config keys that matter, and the known fragility points (Coro, path mapping, attach vs launch). ## The landscape Two wire protocols, both with Perl backends: | Protocol | Perl backend | Status | |----------|------------------------|--------------| | **DAP** | `Perl::LanguageServer` | Active. | | **DBGp** | `Devel::Debug::DBGp` | Stagnant since 2018. | For new setups, use DAP via `Perl::LanguageServer`. DBGp survives only where an existing client (vim `vdebug`, pugdebug) is already in use. `Perl::LanguageServer` bundles an LSP (code intelligence, diagnostics, go-to-definition) and a DAP (debugger) adapter in one process. The LSP half works on any Perl file in the editor; the DAP half runs only during a debug session. ## Minimal VS Code setup CPAN side — install into the Perl the debuggee will run under: ``` cpanm Perl::LanguageServer ``` Editor side — install the `richterger.perl` extension: ``` Ctrl-P → ext install richterger.perl ``` `.vscode/launch.json`: ```json { "version": "0.2.0", "configurations": [ { "type": "perl", "request": "launch", "name": "Debug current file", "program": "${workspaceFolder}/${relativeFile}", "stopOnEntry": true, "args": [], "cwd": "${workspaceFolder}", "env": {}, "reloadModules": true } ] } ``` Open a `.pl` or `.pm`, set breakpoints in the gutter, press `F5`. The Debug Console accepts any Perl expression, evaluated in the current stack frame. `.vscode/settings.json` for `@INC` and interpreter selection: ```json { "perl.perlInc": [ "${workspaceFolder}/lib", "${workspaceFolder}/local/lib/perl5" ], "perl.perlCmd": "/usr/bin/perl" } ``` ## Capabilities `Perl::LanguageServer` supports the DAP features that matter: - Launch, pause, step in / over / out, return. - Conditional and unconditional breakpoints, added at any time. - Variable inspection across stack frames. - **Set variable** — change a value mid-session from the editor. - Watch expressions. - Evaluate-in-context at the Debug Console. - Module hot-reload via `reloadModules: true`. Not supported: - **Attach to a running process.** `request: "attach"` is not implemented; only `"launch"` (the LS starts the debuggee itself). ## Remote debugging over SSH Edit locally; debug on a remote host. `settings.json`: ```json { "perl.sshCmd": "ssh", "perl.sshAddr": "deploy@host.example", "perl.sshUser": "deploy", "perl.sshWorkspaceRoot": "/srv/app", "perl.pathMap": [ ["/srv/app", "${workspaceFolder}"] ] } ``` `Perl::LanguageServer` runs on the remote host; the editor stays local. `pathMap` translates filenames the debuggee reports (`/srv/app/lib/X.pm`) to local editor paths (`${workspaceFolder}/lib/X.pm`). Breakpoints that silently fail to bind are almost always a `pathMap` mismatch. ## Container-mode debugging `launch.json` accepts container keys that invoke `docker`, `podman`, `docker-compose`, or `kubectl`: | Key | Values | |-----------------|---------------------------------------------------------| | `containerCmd` | `"docker"`, `"docker-compose"`, `"podman"`, `"kubectl"` | | `containerMode` | `"run"` (fresh container) or `"exec"` (existing) | | `containerName` | Image (for `run`) or container name (for `exec`) | | `containerArgs` | Extra args passed to the container runtime. | | `pathMap` | `[["/in/container", "/on/host"], ...]` | Attach to an already-running Docker container: ```json { "type": "perl", "request": "launch", "name": "Debug in container", "program": "/app/bin/worker.pl", "containerCmd": "docker", "containerMode": "exec", "containerName": "my-running-worker", "pathMap": [["/app", "${workspaceFolder}"]] } ``` Kubernetes pod: ```json { "type": "perl", "request": "launch", "name": "Debug in pod", "program": "/app/bin/worker.pl", "containerCmd": "kubectl", "containerMode": "exec", "containerName": "my-pod-abc123", "containerArgs": ["-n", "staging", "--container", "app"], "pathMap": [["/app", "${workspaceFolder}"]] } ``` Constraints: - **`Perl::LanguageServer` must be installed inside the image.** Bake it into the Dockerfile or build a dev-image variant. - `pathMap` is the single biggest failure mode. Breakpoints that never bind mean the debuggee reports a path that does not translate. ## Other editors - **Neovim** — use `nvim-dap` with `Perl::LanguageServer` as the adapter. The adapter spec is a short Lua snippet that points at `perl -MPerl::LanguageServer -e '...'`. - **Emacs** — `dap-mode` with the same adapter. - **IntelliJ** — `Devel::Camelcadedb` + the JetBrains Perl plugin. Not DAP; proprietary protocol. Viable but outside the `Perl::LanguageServer` ecosystem. - **Sublime Text** — DAP clients exist via plugins; use `Perl::LanguageServer` as the adapter. ## Special-mode launches `launch.json` keys for non-default invocation: | Key | Effect | |-------------------|--------| | `useTaintForDebug`| Injects `-T` into the debuggee's perl invocation. | | `sudoUser` | Re-execs the debuggee as that user (needs passwordless sudo). | | `reloadModules` | Enables module hot-reload during the session. | | `stopOnEntry` | Break at the first statement. | ## Gotchas - **BEGIN-time errors** surface as DAP "output" events, not as stopped-at-breakpoint events. Use `stopOnEntry: true` to get a stop at all if the script dies in `BEGIN`. - **Application `$SIG{__DIE__}`** can swallow exceptions before the debugger's handler runs. Wrap the app's handler in an `if ($^S)` guard (see [exceptions](exceptions)). - **Watch expressions re-evaluate on every step.** Avoid expensive or side-effect expressions. - **No source maps.** Perl has no concept equivalent to a JavaScript source map. Source filters (`Smart::Comments`) can shift reported line numbers by one; breakpoints in filtered files may land on the wrong line. - **`Future::AsyncAwait` stepping.** Works for simple cases; stepping through complex `await` chains is unreliable. Drop to `perl -d` for deep async investigation, or use logging. - **Windows native** — `Perl::LanguageServer`'s stdin handling is broken on Windows. WSL works. - **Coro.** `Perl::LanguageServer` relies on Coro internally; the most-cited fragility point is Coro interacting badly with unusual build configurations. If the server refuses to start, check Coro's install log first. ## Find out more - [interactive-debugger](interactive-debugger) — `perl -d` directly, when the IDE stack is overkill or unavailable.