LSP / IDE
craftgo ships its own language server, craftgo-lsp. It powers completion, hover, diagnostics, go-to-definition, find references, rename, document and workspace symbols, document highlight, signature help and formatting for .craftgo files (the shorter .cg extension is also recognised). The server watches and resolves both extensions; to have your editor launch the server for .cg files, associate that extension with the craftgo language on the client side.
The only first-party editor integration is the VS Code extension. Other editors that speak LSP can spawn the binary directly, but they are not officially supported and may need extra setup for syntax highlighting (no shipped grammar package outside the VS Code extension).
Install the LSP
go install github.com/craftgodotdev/craftgo/cmd/craftgo-lsp@latestThe binary lands in $GOBIN (or $GOPATH/bin). Make sure the directory is on your PATH.
VS Code
Install the craftgo extension from the marketplace. The extension auto-spawns craftgo-lsp for any file with the .craftgo extension. Syntax highlighting, snippets, and the language server all activate together.
Verify it works:
- Open a
.craftgofile - Type
@inside a field - The completion popup should list valid decorators
If completion stops working after you upgrade craftgo, restart the language server:
Cmd+Shift+P -> craftgo: Restart Language ServerThe LSP runs as a separate binary; rebuilding craftgo's source does not refresh the running server until you reinstall the binary and restart it.
Features
Completion
Type @ inside a field to see decorators valid for the field's type:
type User {
age int @|
// ^ shows: gt, gte, lt, lte, range, positive, negative, multipleOf, default, ...
}The list is filtered by the field's type: @length appears on string and bytes fields, @minItems on arrays and maps. On a line of its own, @ offers the decorators of the field below it, the one it will decorate.
Smart argument completion
Inside @default(...) on an enum field, only the enum's values are suggested:
enum Status { Active Inactive Pending }
type User {
status Status @default(|)
// ^ shows: Active, Inactive, Pending
}Inside @timeout(...) or @maxBodySize(...), common values appear as presets. Once you type a number, the matching unit suffixes get offered:
@timeout(10|)
// ^ shows: 10ns, 10us, 10µs, 10ms, 10s, 10m, 10hHover
Hover over an identifier to see its declaration and doc comment. Works on:
- Decorators (
@length,@format,@default, ...) - shows purpose, allowed sites, argument shape - Type references inside fields - shows the declaration's header line (
type Contact,enum Status) and its//doc comment - HTTP verbs, built-in primitives, error categories, field names, and middleware and error names inside
@middlewares(...)/@errors(...)
Diagnostics
Errors appear inline as you type:
syntax/token- a character or token no design holds: an unterminated string, a bad escape, a NUL bytesyntax/parse- tokens the grammar does not take where they stand: a misspelt keyword, a bracket left open (reported at its opener)decorator/unknown-@nopeis not registereddecorator/placement- decorator appears at the wrong site (@prefixon a field)decorator/conflict- incompatible decorator pair (@sensitivewith@length)decorator/default-needs-optional-@defaulton a non-optional field (warning; format auto-fixes)decorator/typemismatch- validator on the wrong primitive (@lengthonint)field/duplicate- same field name twiceservice/duplicate-route- two methods of one service on the same verb + routepath/collision- two methods, in any services, whose routes net/http's ServeMux cannot both register: the same route up to its variable names, or two that match a common path with neither more specific ({name...}is less specific than any other segment, a literal more specific than{name}, and{name}shares no path with{$})enum/value-collision- two values produce the same Go constant (warning)
A key of craftgo.design.yaml that craftgo does not read shows as a warning at the top of the manifest, naming the key, and an edit that stops the manifest from loading shows the error there.
Go-to-definition
Cmd+click (or gd in vim-mode) on a type reference jumps to its declaration. Works across files in the same package and across packages (a shared.Type reference jumps to the shared package's declaration). On a value inside @default(...) or @example(...) of an enum-typed field it jumps to that enum value; on a name inside @middlewares(...) or @errors(...), to that middleware or error.
Rename
Rename a type, enum, scalar, error, middleware, event or service from its name or any reference to it; every reference in the project follows. A new name the declaration cannot hold is refused: a reserved word, a built-in type such as string, a name another declaration already has, or one starting in lower case for anything but a service.
Format
The craftgo fmt CLI rewrites a file in canonical form: aligned field columns, consistent decorator spacing, tab indentation. The VS Code extension wires this to the editor's "Format Document" command. A buffer with an error, parse or semantic, is left untouched: fix the diagnostic first, then format. So is a buffer whose formatted text would not parse, or would lose or move a comment.
Other editors
Any editor that speaks LSP can spawn craftgo-lsp. There is no first-party plugin for Neovim, Helix, Zed, or others. If you set one up:
- The binary lives at
$(which craftgo-lsp)aftergo install - It speaks plain LSP over stdin/stdout
- File extensions are
.craftgoand.cg; the grammar scope issource.craftgo
Without the VS Code extension you will not have syntax highlighting; the language server still provides completion, hover, and diagnostics. Contributions for other editor integrations are welcome.
Troubleshooting
Completion stopped working
Reinstall and restart:
go install github.com/craftgodotdev/craftgo/cmd/craftgo-lsp@latest
# in VS Code: Cmd+Shift+P -> craftgo: Restart Language ServerCross-file references show as unresolved
The LSP analyses every design file under the design root - the folder holding craftgo.design.yaml - with open buffers in place of their saved copies. A file with no loadable manifest above it is analysed on its own, so everything declared in other files shows as unresolved: check that craftgo.design.yaml sits above the file and loads (craftgo gen reports why it does not). Inside the project a bare name resolves within the file's package - every file declaring the same package name, in any folder - and a declaration of another package needs its qualifier (shared.Contact).
Errors on a syntactically valid file
The LSP and the craftgo CLI use the same semantic analyzer. Run craftgo fmt -l <design-dir> to reproduce: it lists every file with errors under <file>: not formatted, fix these first: and writes nothing (craftgo gen stops on the same errors). Warnings such as decorator/default-needs-optional show only in the editor. If the message is unclear, file a bug.