Consuming from Rust
Status: live. This is the real, shipped
gdscript-idesurface (plans/ROADMAP.mdtracks what's still ahead on the road to1.0, when this surface freezes).
Native Rust consumers depend on a single crate, gdscript-ide:
cargo add gdscript-ide
This is the crate we semver most carefully — it is the contract every other consumer (the napi addon, the wasm package, the LSP server, the CLI) is built on top of.
The AnalysisHost / Analysis model
/// The single mutable owner of analysis state. One per project/workspace.
pub struct AnalysisHost { /* ... */ }
impl AnalysisHost {
pub fn new() -> Self;
/// The ONLY mutation entry point: apply a batch of input changes
/// (file added/edited/removed, project config, Godot version, scenes).
pub fn apply_change(&mut self, change: Change);
/// A cheap, cloneable, immutable, `Send` snapshot for read queries.
pub fn analysis(&self) -> Analysis;
}
/// An immutable snapshot. Every query is cancellable.
pub struct Analysis { /* ... */ }
Analysis exposes one method per IDE feature, each taking a FileId +
byte offsets and returning POD wrapped in Cancellable<T>:
analysis.diagnostics(file)?; // Vec<Diagnostic>
analysis.completions(pos)?; // Vec<CompletionItem>
analysis.hover(pos)?; // Option<HoverResult>
analysis.signature_help(pos)?; // Option<SignatureHelp>
analysis.goto_definition(pos)?; // Vec<NavTarget>
analysis.find_references(pos)?; // Vec<Reference>
analysis.rename(pos, "new_name")?; // Result<SourceChange, RenameError>
analysis.document_symbols(file)?; // Vec<DocumentSymbol>
analysis.workspace_symbols("q")?; // Vec<NavTarget>
analysis.semantic_tokens(file)?; // Vec<SemanticToken>
analysis.inlay_hints(file)?; // Vec<InlayHint>
analysis.folding_ranges(file)?; // Vec<FoldRange>
analysis.code_actions(pos)?; // Vec<CodeAction>
analysis.format(file)?; // Option<String> — the reformatted file
analysis.format_range(file, s, e)?; // Option<(u32, u32, String)>
analysis.syntax_tree(file)?; // Option<String> — debug CST dump
The rules of the surface
- Inputs are injected. The host owns a virtual file system; you push text
via
apply_change. The library never touchesstd::fs— this is what keeps it portable to WASM (see the portability rules inplans/01-ARCHITECTURE.md§7). - Outputs are POD. A
Diagnosticcarries a byteTextRange, a code (e.g.GDSCRIPT_UNSAFE_CALL), a severity, a message, and optional fixes — never anlsp_types::Diagnostic. You convert at your boundary. - Cancellation. Reads return
Cancellable<T>; a concurrentapply_changecancels in-flight reads (at Tier 2 this is salsa's cancellation). Re-issue.
Why a separate crate stack
The crates are layered so each depends only downward
(base → syntax → api/db → hir → ide). Most consumers only ever name
gdscript-ide; the lower crates are an implementation detail you can reach for
if you are building specialized tooling. The full layering is in
Crate layout and
ADR-0001.