Unofficial Language Server Protocol support for Fantom in Visual Studio Code.
This extension brings a rich developer experience to Fantom projects inside VSCode — syntax highlighting, real-time diagnostics, autocompletion, hover docs, go-to-definition, debugging with breakpoints and variable inspection, and more. It is powered by a Fantom LSP server written entirely in Fantom itself (vscodeFantomLsp), automatically deployed into your Fantom installation when the extension activates.
| Feature | Description |
|---|---|
| 🔍 Go to Definition | Jump to the definition of types, methods, and fields across project files |
| 💡 Auto Completion | Context-aware suggestions triggered on . (dot) and keyword prefix |
| ⇥ Tab Completion | Complete identifiers and keywords without reaching for the mouse |
🔁 Duplicate static const detection |
Warns when two or more static const fields share the same string value across the project |
| 🗑️ Unused variable detection | Highlights variables that are declared but never read |
| 🧹 Unused import detection | Warns on using statements that are not referenced in the file |
| 🎯 Hover information | Shows type signatures and pod documentation when hovering over a symbol |
| 🚨 Real-time diagnostics | Syntax errors and type errors from the Fantom compiler, debounced while typing |
| 🔗 Cross-file validation | Detects unresolved type references across all project source files |
| 🏗️ Build integration | Runs fan build.fan on save and reports compiler errors with clickable file links |
| 🖊️ Syntax highlighting | Full TextMate grammar for .fan files |
| 🧩 Remove Unused Imports | Command to remove all unused using lines in a file or the whole project |
| 🧩 Remove Unused Variables | Command to remove all unused variable declarations in a file or the whole project |
| 📊 Status bar diagnostics | Live error and warning counts in the VSCode status bar |
| 🖋️ Source formatter | Format on demand or on save — indentation, blank-line limits, space collapsing, line wrapping, and .editorconfig support |
| 🐛 Debugger (DAP) | Set breakpoints, step through code, and inspect variables in Fantom programs on the JVM |
- Fantom ≥ 1.0.80 installed locally — Download
- Java JDK (not just JRE) available in your
PATHor viaJAVA_HOME— the debugger needs thejdk.jdimodule (JDK 9+) - VSCode ≥ 1.75.0
Don't have Fantom? Run the helper script included in this repo:
./install-fantom.shIt fetches and unpacks the latest Fantom release for you.
- Download the latest
.vsixfrom the Releases page. - In VSCode open the Command Palette (
Ctrl+Shift+P) → Extensions: Install from VSIX… - Select the downloaded file.
Or install from the terminal:
code --install-extension fantom-language-support-${TAG}.vsix# 1. Build the LSP pod (requires Fantom installed and in PATH)
fan build.fan
# 2. Install Node.js dependencies and build the extension
cd vscode-fantom
pnpm install
pnpm run compile
# 3. Package (optional)
pnpm dlx @vscode/vsce packageThe debug adapter JAR is built automatically the first time the extension activates (requires JDK 11+). To pre-build it manually:
bash vscode-fantom/debug-adapter/build.sh
The extension looks for configuration in two places, in priority order:
Create a fan.config.json file in the root of your workspace. The extension will offer to create a skeleton automatically on first activation if the file is missing.
{
"fanPath": "/opt/fantom-1.0.82",
"fanTargetBuild": "compile",
"debounceTime": 1000,
"enableUnusedImport": true
}| Key | Type | Default | Description |
|---|---|---|---|
fanPath |
string |
"" |
Absolute path to your Fantom installation directory (the folder that contains bin/fan). When empty or set to the placeholder value, the extension falls back to the FAN_HOME environment variable. |
finPath |
string |
"" |
Direct path to the fin executable (e.g. /path/to/fin-installation/bin/fin). When set, the debugger prefers this over fanPath for launch configurations. |
fanTargetBuild |
string |
"" |
The build target passed to fan build.fan <target> on every save. Leave empty to run the default target. |
debounceTime |
number |
2000 |
Milliseconds to wait after the last keystroke before running diagnostics. Lower values give faster feedback; higher values are gentler on large projects. Minimum: 100. |
enableUnusedImport |
boolean |
true |
When false, unused using import warnings are suppressed entirely. |
💾 The LSP server restarts automatically whenever
fan.config.jsonis saved.
If fanPath is not set (or left as the placeholder) in fan.config.json, the extension reads the FAN_HOME environment variable:
# ~/.bashrc / ~/.zshrc
export FAN_HOME=/opt/fantom-1.0.82
export PATH=$FAN_HOME/bin:$PATHResolution order (first wins):
fanPathinfan.config.jsonFAN_HOMEenvironment variable- Extension shows a warning and does not start
Additional settings available through the VSCode UI or settings.json:
| Setting | Type | Default | Description |
|---|---|---|---|
fantom.javaPath |
string |
"" |
Full path to the java executable. Defaults to $JAVA_HOME/bin/java, then just java from PATH. |
fantom.suppressJavaWarning |
boolean |
false |
Suppress the startup warning when Java is not found. Enable this if you do not use the Fantom debugger. |
fantom.suppressWarningPopup |
boolean |
false |
Suppress the "Warnings found" notification popup shown at startup when the build produces warnings. |
fantom.useBuiltInLspPod |
boolean |
true |
Use the vscodeFantomLsp bundled with the extension (recommended). Set to false only if you have built and installed your own LSP pod. |
fantom.pedanticMode |
boolean |
false |
Warn on local variable declarations that lack an explicit type annotation (neither a type on the left side nor an as cast on the right). |
fantom.trace.server |
string |
"off" |
Trace LSP message traffic: "off", "messages", or "verbose". Useful for debugging the extension itself. |
fantom.format.enable |
boolean |
true |
Enable the Fantom formatter (Format Document / Format Selection). |
fantom.format.formatOnSave |
boolean |
false |
Automatically format Fantom files on save. |
fantom.format.indentSize |
number |
2 |
Spaces per indentation level (ignored when useTabs is true). |
fantom.format.useTabs |
boolean |
false |
Use tab characters for indentation instead of spaces. |
fantom.format.insertFinalNewline |
boolean |
true |
Ensure the file ends with a newline character. |
fantom.format.trimTrailingWhitespace |
boolean |
true |
Remove trailing whitespace from each line. |
fantom.format.maxBlankLines |
number |
1 |
Maximum consecutive blank lines to preserve. 0 keeps all blank lines. |
fantom.format.respectEditorConfig |
boolean |
true |
Let .editorconfig files override the settings above. |
fantom.format.collapseSpaces |
boolean |
true |
Collapse runs of two or more spaces into one in code regions (outside strings and comments). |
fantom.format.maxLineLength |
number |
0 |
Wrap lines that exceed this length. 0 disables wrapping. |
Access these from the Command Palette (Ctrl+Shift+P):
| Command | Shortcut (Linux/Win · Mac) | Description |
|---|---|---|
| Fantom: Compile Project | Ctrl+F9 · Cmd+F9 |
Runs fan build.fan <target> and reports compiler errors with clickable file links |
| Fantom: Run All Tests | Ctrl+Shift+F9 · Cmd+Shift+F9 |
Runs all Fantom tests in the project |
| Fantom: Run All Tests in File… | Ctrl+F10 · Cmd+F10 |
Runs all test methods in the currently open .fan file |
| Fantom: Run Test Method… | Ctrl+Shift+F10 · Cmd+Shift+F10 |
Runs a single test method chosen from the current file |
| Fantom: Remove Unused Imports in File | — | Deletes all unused using lines in the current file and saves |
| Fantom: Remove Unused Imports in Project | — | Deletes unused using lines across every .fan file in the project |
| Fantom: Remove Unused Variables in File | — | Deletes unused variable declarations in the current file |
| Fantom: Remove Unused Variables in Project | — | Deletes unused variable declarations across the whole project |
| Fantom: Format Entire Project | — | Formats every .fan file in the project with a progress bar |
| Fantom: Create launch.json for Fantom Debugger | — | Creates .vscode/launch.json with default Launch and Attach configurations pre-filled from fan.config.json |
| Fantom: Rebuild debugger | — | (Re)compiles the debug adapter JAR from the bundled Java sources. Run this if the debugger is missing or broken |
VSCode Extension (TypeScript)
│
│ stdio (LSP protocol)
▼
vscodeFantomLsp ─── fan vscodeFantomLsp::Main
│
├── ProjectIndex (AST index of all .fan sources)
├── DiagnosticService (single-file + cross-file analysis)
├── CompletionService (dot-completion, keyword suggestions)
├── DefinitionService (go-to-definition)
├── HoverService (type signatures, pod docs)
└── fan build.fan (real compiler, error reporting)
VSCode Extension (TypeScript)
│
│ stdin/stdout (DAP protocol)
▼
fantom-debug-adapter.jar
│
│ JDWP / TCP
▼
JVM running your Fantom program
On activation the extension:
- Reads
fan.config.json(or falls back toFAN_HOME). - Deploys (or updates)
vscodeFantomLspinto$FAN_HOME/lib/fan/. - Spawns the LSP server via
fan vscodeFantomLsp::Main. - Registers the Fantom debug adapter so it is available immediately in the Run & Debug panel.
- Checks for Java (
JAVA_HOME→fantom.javaPathsetting →PATH). If Java is not found, a warning popup is shown with options to configure the path. If Java is found and the debug adapter JAR has not been built yet, it is compiled automatically from the bundled Java sources in the background with a progress notification. - The server indexes all
.fansources frombuild.fan'ssrcDirs, pre-loads available pods, runs diagnostics, and performs an initial build check — all in the background so the editor stays responsive.
The extension never modifies your Fantom installation while it is running. Instead, it creates a shadow directory — a lightweight mirror of FAN_HOME placed in the system temp folder — and points the LSP server and the debugger at that instead.
The LSP server holds vscodeFantomLsp.pod open as long as it runs. Without a shadow copy, a build command that tries to overwrite that file would fail with a "file in use" error on Windows. The shadow dir gives the server its own unlocked copy while the original stays free for builds.
| Path inside shadow dir | What it contains |
|---|---|
lib/fan/<main>.pod |
Real copy of the LSP pod — the original is never locked |
lib/fan/*.pod |
Hard links (Windows) or symlinks (Linux/Mac) to the real pods |
lib/java/ |
Junction (Windows) or symlink (Linux/Mac) to the real lib/java/; falls back to a full recursive copy if the junction cannot be created |
etc/sys/config.props |
Modified copy with java.options stripped to suppress JDWP output on the LSP stdout pipe |
etc/sys/<other> |
Copies (Windows) or symlinks (Linux/Mac) of the remaining etc/sys/ files |
etc/<other>/ |
Junction (Windows) or symlink (Linux/Mac) to each real etc/ subdirectory |
The shadow dir is created at LSP server startup and deleted when the server stops or restarts.
On Windows, fs.rmSync({ recursive: true }) follows directory junctions as real directories, which would delete the contents of the junction target — i.e. your Fantom installation. The extension guards against this through ShadowDir (vscode-fantom/src/shadowDir.ts), the single class responsible for all shadow dir lifecycle operations:
- Boundary check — refuses to delete any path that is not under the system temp directory.
- Junction unlink first — unlinks every junction and symlink before calling
rmSync, sormSynconly ever sees regular files and empty directories. - Abort on partial failure — if any junction cannot be unlinked on Windows, the
rmSynccall is skipped entirely. The shadow dir leaks in%TEMP%but your installation is never touched.
The same class is used by the debugger's launch-mode shadow dir.
- While typing — changes are debounced (
debounceTimems). No analysis runs until typing pauses. - After the debounce window — single-file analysis runs immediately for fast feedback.
- On save — a full project re-index, cross-file validation, and
fan build.fanall run together.
The extension includes a full source formatter for Fantom files, implemented inside the LSP server (FormatterService). It supports Format Document, Format Selection, and optional format on save.
- Format Document —
Shift+Alt+F(or Command Palette →Format Document) - Format Selection — select code, then
Ctrl+K Ctrl+F - Format on Save — enable
fantom.format.formatOnSavein settings (see below)
The formatter respects VS Code's standard
editor.formatOnSavesetting too: whenfantom.format.formatOnSaveis enabled, the extension hooks into the save pipeline independently so you do not need to enableeditor.formatOnSaveglobally.
Indentation is re-computed by tracking brace depth ({ / }). Braces inside string literals and // comments are ignored. The unit is controlled by fantom.format.indentSize (spaces) or fantom.format.useTabs (tabs).
Consecutive blank lines are reduced to at most fantom.format.maxBlankLines (default 1). Set to 0 to remove all consecutive blanks, or keep the default to allow one blank line between declarations.
When fantom.format.trimTrailingWhitespace is true (default), trailing spaces and tabs are stripped from every line. When fantom.format.insertFinalNewline is true (default), a newline is added at the end of the file if one is missing.
The formatter detects the dominant line ending in the file (LF, CRLF, or CR-only) and uses the same style in the output. A Windows CRLF file stays CRLF after formatting; a Unix LF file stays LF.
When fantom.format.collapseSpaces is true (default), runs of two or more consecutive spaces are collapsed to a single space in code regions — spaces inside string literals and // comments are never touched.
All Fantom string literal types are handled:
| Type | Example | Spaces inside |
|---|---|---|
| Double-quoted | "hello world" |
preserved |
| Triple-quoted | """hello world""" |
preserved |
| Single-quoted (char) | ' ' |
preserved |
| Backtick (DSL / URI) | `path/to thing` |
preserved |
Example:
// Before
return val ? 0 : 1
// After
return val ? 0 : 1When fantom.format.maxLineLength is set to a positive value, lines that exceed that length are split automatically. The formatter looks for the best split point before the limit, in this priority order:
| Priority | Split point | Example |
|---|---|---|
| 1 | Comma inside parentheses / brackets | foo(a, b, c) → split after , |
| 2 | && or || operator (any nesting depth) |
if (a && b) → split before && |
| 3 | Ternary ? surrounded by spaces (at depth 0) |
x ? y : z → split before ? |
| 4 | Word boundary inside a string literal | "long string" → split with + concatenation |
String literal splitting produces a concatenation expression:
// Before (exceeds maxLineLength)
msg := "hello world this is a very long string"
// After
msg := "hello world this" +
"is a very long string"The split is at the last word-boundary space before the column limit. Backtick (DSL) strings are never split — their content (URIs, raw values) must remain intact. Continuation lines are indented one level deeper than the base line.
When fantom.format.respectEditorConfig is true (default), the formatter walks up the directory tree from the file being formatted and applies the first matching .editorconfig section. Supported properties:
.editorconfig key |
Maps to |
|---|---|
indent_style = tab |
useTabs = true |
indent_style = space |
useTabs = false |
indent_size / tab_width |
indentSize |
trim_trailing_whitespace |
trimTrailingWhitespace |
insert_final_newline |
insertFinalNewline |
max_blank_lines |
maxBlankLines |
The .editorconfig walk stops at root = true or at the workspace root, whichever comes first. Per-file .editorconfig values override the VS Code settings for that file only.
The extension includes a full Debug Adapter Protocol (DAP) implementation that lets you set breakpoints, step through code, and inspect variables in Fantom programs running on the JVM.
Prerequisite: A JDK (not just a JRE) is required — the debugger uses JDI, which is part of the JDK tools (
jdk.jdimodule, available in JDK 9+).Debug adapter JAR: The JAR is not pre-built in the repository. On first activation the extension compiles it automatically from the bundled Java sources (requires JDK 11+). If the build is skipped or fails, use the Fantom: Rebuild debugger command from the Command Palette (
Ctrl+Shift+P) to trigger a fresh build.
VS Code ←DAP/JSON-RPC→ fantom-debug-adapter.jar ←JDWP/TCP→ JVM (your Fantom program)
- VS Code communicates with the debug adapter using the Debug Adapter Protocol over stdin/stdout.
fantom-debug-adapter.jar(bundled with this extension) translates those commands into JDI calls. JDI (Java Debug Interface) is available in every JDK.- The JVM exposes a JDWP server socket when started with
-agentlib:jdwp=.... The adapter connects to that socket to set breakpoints, read variables, and control execution.
Fantom compiles .fan files to standard JVM .class bytecode. Two standard attributes enable source-level debugging:
SourceFile— records the short filename (e.g.MyService.fan) in each.classfile.LineNumberTable— maps each bytecode instruction back to its original Fantom line number.
These attributes are always emitted by the Fantom compiler with no special build flags needed.
| Item | Available | Notes |
|---|---|---|
this fields |
✅ | All instance fields shown under the "this" scope |
| Local variables | ✅ (best-effort) | Shown when LocalVariableTable is present; otherwise method parameters are shown |
| Variable types | ✅ | Proper Fantom type names (myPod::MyClass, sys::List, etc.) |
| Variable values | ✅ | Calls Fantom's toStr() — shows the actual value, not fan.sys.List@123 |
| Step over / into / out | ✅ | Works at the Fantom source line level |
| Line breakpoints | ✅ | Click the gutter in any .fan file |
| Watch expressions | ✅ | Supports varName, this.field, and a.b.c chains |
| Conditional breakpoints | ❌ | Not yet implemented |
The JVM must be started with the JDWP agent to allow debugger connections.
Edit <fanHome>/etc/sys/config.props:
java.options=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005| JDWP option | Meaning |
|---|---|
transport=dt_socket |
Use a TCP socket |
server=y |
JVM opens the server socket |
suspend=n |
Program starts immediately; debugger can attach at any time |
suspend=y |
JVM waits for a debugger before starting — useful for debugging initialization |
address=5005 |
TCP port |
If you already have other JVM flags on java.options, separate them with a space (not a comma):
java.options=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -Dfile.encoding=UTF8Cleanup: Remove the
java.optionsline when done. Withsuspend=yleft in place the program will hang on startup waiting for a debugger.
Works for attach mode when you start the process manually before connecting:
export JAVA_TOOL_OPTIONS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"
finDo not use
JAVA_TOOL_OPTIONSwith launch mode. In launch mode the JDWP agent is injected viajava.optionsin a temporary shadowconfig.props, which is scoped to that one JVM and never inherited by child processes. UsingJAVA_TOOL_OPTIONSin parallel causes a port conflict and abind failed: Address already in useerror.
Step 1. Enable JDWP in <fanHome>/etc/sys/config.props:
java.options=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005Step 2. Start your program normally:
fin
# or: fan myPod::MainYou will see Listening for transport dt_socket at address: 5005 confirming the port is open.
Step 3. Create .vscode/launch.json:
Tip: Run Fantom: Create launch.json for Fantom Debugger from the Command Palette (
Ctrl+Shift+P) to generate this file automatically. The extension also offers to create it on first activation whenfan.config.jsonis present and nolaunch.jsonexists.
{
"version": "0.2.0",
"configurations": [
{
"type": "fantom",
"request": "attach",
"name": "Attach to Fantom / FIN",
"host": "localhost",
"port": 5005,
"sourceDir": "${workspaceFolder}"
}
]
}Step 4. Press F5 to connect.
Step 5. Click the gutter next to a line in any .fan file to set a breakpoint.
VS Code tip: If gutter clicks don't place breakpoints, add this to
settings.json:"debug.allowBreakpointsEverywhere": true
In launch mode VS Code starts the Fantom/FIN process for you when you press F5. You do not need to touch config.props or start anything manually.
- The adapter picks a free TCP port automatically (defaults to 5005, falls back to any free ephemeral port if 5005 is already in use — e.g. by a running FIN server).
- A temporary shadow
FAN_HOMEis created in/tmp/fantom-debug-home-*/:- Every top-level entry from your real
FAN_HOMEis symlinked exceptetc/andvar/. etc/sys/config.propsis rewritten:java.optionsfrom the original is stripped (prevents double-JDWP conflicts) and a fresh JDWP agent line for the chosen port is injected.debug=trueis also added so any pods rebuilt withpreLaunchRebuildemit full local variable info.var/is a real directory with only sub-directories symlinked — lock files likevm.lockare created fresh, completely isolated from any running FIN server.
- Every top-level entry from your real
- Your executable is launched with
FAN_HOMEpointing to the shadow directory. - The adapter attaches JDI to the JDWP port and is ready for breakpoints.
- When you press Stop (or close the session), the entire process group is killed with
SIGKILLand the shadow directory is deleted.
Because JDWP is injected via
java.optionsin the shadowconfig.props— not viaJAVA_TOOL_OPTIONS— it affects only the launched JVM and is never inherited by child processes or the running FIN server.
{
"fanPath": "/path/to/fantom"
}Plain fan launcher (explicit mainClass required):
{
"version": "0.2.0",
"configurations": [
{
"type": "fantom",
"request": "launch",
"name": "Launch Fantom",
"fanExe": "/path/to/fantom/bin/fan",
"mainClass": "myPod::Main",
"sourceDir": "${workspaceFolder}"
}
]
}fin launcher (already embeds its entry point — mainClass must be empty):
{
"version": "0.2.0",
"configurations": [
{
"type": "fantom",
"request": "launch",
"name": "Launch FIN",
"fanExe": "/path/to/fin-installation/bin/fin",
"mainClass": "",
"sourceDir": "${workspaceFolder}"
}
]
}Why
"mainClass": ""?
Thefinscript wrapsfanlaunch Fan finStackHost "$@"— the pod name is hardcoded inside the script. Passing an extrafinStackHost::Mainargument after-noAuthconfuses FIN's argument parser. LeavemainClassempty and rely on the script's built-in entry point.
fin with -noAuth:
{
"type": "fantom",
"request": "launch",
"name": "Launch FIN (no auth)",
"fanExe": "/path/to/fin-installation/bin/fin",
"mainClass": "",
"launcherArgs": ["-noAuth"],
"sourceDir": "${workspaceFolder}"
}With local variable support (preLaunchRebuild rebuilds your pod with debug=true before each session):
{
"type": "fantom",
"request": "launch",
"name": "Launch FIN (full debug)",
"fanExe": "/path/to/fin-installation/bin/fin",
"mainClass": "",
"launcherArgs": ["-noAuth"],
"sourceDir": "${workspaceFolder}",
"preLaunchRebuild": true
}
preLaunchRebuildrunsfan build.faninsourceDirusing the shadowFAN_HOME(which hasdebug=true) so the rebuilt pod containsLocalVariableTablebytecode attributes. Without this, only method parameters are visible in the Variables panel — local variables likeroom11,room12are invisible. Set it totruethe first time you debug and whenever you do a clean build; afterwards you can set it back tofalseto skip the overhead.
The Debug Console shows the exact command being run:
[Fantom Debug] Launching: /path/to/fin -noAuth
If you see unexpected extra tokens after your arguments, check the mainClass field — it must be "" for fin.
VS Code tip: If gutter clicks don't work, add to
settings.json:"debug.allowBreakpointsEverywhere": true
Press the Stop button (red square) or Shift+F5. The launched process and all its children are terminated immediately via SIGKILL on Linux/macOS.
| Key | Type | Default | Description |
|---|---|---|---|
fanExe |
string | auto-detected | Path to fan or fin. Auto-filled from fan.config.json (finPath first, then fanPath/bin/fan) |
mainClass |
string | "" |
Fantom class to run, e.g. myPod::Main. Leave empty for fin — fin already embeds its entry point; passing an extra class name after launcher args confuses its argument parser. |
launcherArgs |
string[] | [] |
Arguments inserted between the executable and mainClass, e.g. ["-noAuth"] → fin -noAuth. These are passed to the launcher script itself, before the Fantom pod name. |
args |
string[] | [] |
Arguments appended after mainClass — passed to the Fantom program's main() method. |
sourceDir |
string | ${workspaceFolder} |
Root directory searched for .fan source files (used for source ↔ JVM class mapping). |
debugPort |
number | 5005 |
Preferred JDWP port. If the port is already in use the adapter automatically picks a free ephemeral port. |
noDebug |
boolean | false |
Launch without attaching the debugger (plain run). |
preLaunchRebuild |
boolean | false |
Run fan build.fan in sourceDir before launching, using a shadow FAN_HOME with debug=true. This makes local variables (beyond method parameters) visible in the debugger. Only needed after a clean/release build. |
| Key | Type | Default | Description |
|---|---|---|---|
host |
string | localhost |
Hostname or IP of the running JVM |
port |
number | 5005 |
JDWP port to connect to |
sourceDir |
string | ${workspaceFolder} |
Root directory searched for .fan source files |
When the JVM reports a stopped location it provides only the short filename (e.g. MyService.fan) and the JVM class name (e.g. fan.myPod.MyService). The adapter searches sourceDir in this order:
<sourceDir>/<podName>/fan/<File.fan>— standard Fantom pod layout<sourceDir>/<podName>/<File.fan><sourceDir>/fan/<File.fan>— single-pod workspace- Full recursive walk under
sourceDir— fallback for non-standard layouts
Set sourceDir to the root of your source tree. For a multi-pod workspace this is typically ${workspaceFolder}.
The debug adapter JAR is built automatically the first time the extension activates, using the Java sources bundled at vscode-fantom/bundled-debug/java-src/ (requires JDK 11+, no Maven needed). You can also trigger a rebuild at any time:
- Command Palette (
Ctrl+Shift+P) → Fantom: Rebuild debugger - Or from a terminal:
cd vscode-fantom
bash debug-adapter/build.shThis downloads Gson if needed, compiles all Java source files, and packages a self-contained fat JAR.
| Symptom | What to check |
|---|---|
| "Extension idle" on startup | The workspace has no build.fan, no .fan files, and no fan.config.json. Add one. |
| "fanPath not configured and FAN_HOME is not set" | Set fanPath in fan.config.json or export FAN_HOME in your shell profile. |
| "fan executable not found" | Make sure bin/fan (or bin/fan.bat on Windows) exists in your Fantom directory. |
| No completions / wrong completions | Check that your file is inside the srcDirs listed in build.fan. Files outside srcDirs are not indexed. |
| Build errors not appearing | Set fanTargetBuild in fan.config.json to match your build target (e.g. "compile"). |
| Diagnostics too slow / too fast | Tune debounceTime in fan.config.json (default 2000 ms). |
| LSP server crashes | Open the Fantom Language Server output channel in VSCode and look for error messages. Enable fantom.trace.server: "verbose" for full protocol traces. |
| Debug adapter JAR not found | The JAR is built automatically at startup. If that failed, run Fantom: Rebuild debugger from the Command Palette. Requires JDK 11+. |
| Breakpoints never hit | Confirm JDWP is enabled on the JVM (see Java configuration above). Check the port matches between config.props and launch.json. |
Variables show <not found> |
The variable is out of scope or the frame is no longer active. Check the Call Stack panel to select the correct frame. |
| Local variables not shown (only method params) | Rebuild your pod with debug=true: either set "preLaunchRebuild": true in launch.json, or manually build with debug=true in etc/sys/config.props. |
| Program hangs on startup | You left suspend=y in java.options. Change to suspend=n or remove the line. |
bind failed: Address already in use |
Port 5005 is taken by the running FIN server's own JDWP listener. In launch mode the adapter picks a free port automatically. In attach mode use a different port. Do not set JAVA_TOOL_OPTIONS when using launch mode. |
-noAuth or other launcher flags ignored |
Set "mainClass": "" in launch.json when using fin. The fin script already embeds its entry point; a non-empty mainClass is appended after your flags and confuses FIN's argument parser. |
| FIN process keeps running after Stop | Ensure you are using the latest JAR (rebuild with bash debug-adapter/build.sh). Launched sessions are always force-killed on disconnect. |
This project is developed with the assistance of AI coding tools.
All AI-generated code is reviewed, tested against the full Fantom test suite (fan build.fan test), and compiled clean before being committed.
Contributions are welcome! The LSP server lives in src/fan/ and is written in Fantom. The VSCode extension glue code is in vscode-fantom/src/.
# Run Fantom tests
fan build.fan test
# Run extension grammar tests
cd vscode-fantom && pnpm run test:grammar👻 Happy coding with Fantom!