Embedded Java in IntelliJ
Scope
The IntelliJ plugin supplies a lightweight Vernac PSI host and a Java
MultiHostInjector. Each inline behavior block becomes one virtual Java class.
Its method names and bodies remain mapped to ranges in the original .vernac
document. Imports, the containing class, method signatures and the implicit
self receiver are synthetic Java context. Nothing is written to disk.
This enables IntelliJ’s Java highlighting, completion and reference navigation inside inline method bodies, including calls between private helper methods in the same block. The existing language server continues to handle Vernac syntax, model diagnostics, type completion and namespace navigation.
The LSP descriptor explicitly enables semantic tokens for Vernac PSI files. IntelliJ’s default semantic-token policy only enables plain text and TextMate; adding a custom parser without this override would remove Vernac highlighting. Injected Java files are excluded from this opt-in and retain Java highlighting.
Projection covers reviewed behavior contexts and validation delegates, including
entity/aggregate read/modify access and application/adapter bodies. Unsupported
legacy Java blocks are not a documented extension mechanism. implemented by has no inline body to inject; Java navigation on
that Vernac target declaration is a separate feature.
Prerequisites and editing
- Use an imported Maven project with a configured JDK and Java dependencies.
- Keep models under
src/main/vernac, matching their namespace directories. - Ordinary local VOs have live Java APIs before generation, including validation Read views. Other domain APIs and application access interfaces still require generated Java to be indexed. Regenerate those APIs after changing their declarations; runtime execution always needs a build.
- Changes inside inline bodies and private helper declarations use the live document. Saving or rebuilding is not required for those edits.
- Declare additional Java imports explicitly in
java imports. Automatic import insertion into that DSL block is not provided by this integration.
Java can navigate from a helper call to its original inline method name because that name is an injected source fragment, not merely synthetic scaffolding. Receiver and parameter declarations are currently synthetic; they do not yet provide source navigation into Vernac signatures. Model type references now use the separate Java-to-Vernac type navigation integration when the referenced Java source is owned by the compiler. Domain getters and public behavior references also navigate to their Vernac declarations; private helper calls retain their local injected-Java targets.
IntelliJ Java inspections are not NullAway. The configured Maven checker remains the authoritative nullness build gate. No live NullAway analysis is promised.
Shared semantics
BehaviorJavaProjection lives in the compiler and reuses FileTypeScope,
BehaviorImports and JavaTypeNames. The IDE does not invent a parallel set of
namespace/import priorities or built-in mappings. Body-only model references
receive the same visible imports as generated behavior. Imported Java types
cannot replace reserved or visible Vernac types.
The plugin loads an editor snapshot of the Vernac source root, including unsaved files. Independently parseable files remain available when another file has a syntax error. Invalid imports or unresolved signature types suppress the affected Java projection rather than binding to a guessed type. An incomplete Vernac structure can temporarily suspend injection until it parses again; ordinary incomplete Java expressions inside a structurally intact body remain editable.
Projection offsets are UTF-16, converted from ANTLR code-point offsets. No string escape transformation is performed. This also applies to supplementary Unicode characters before a method or within its body.
Packaging and lifecycle
The compiler and its runtime dependencies are bundled in plugin/lib for local
projection. The executable LSP JAR stays separately in plugin/server; it is not
loaded into the IDE’s plugin classloader. IntelliJ libraries retain provided scope.
Shared naming classes come from the compiler JAR, with no second plugin copy.
Projection results are cached per PSI host and invalidated by PSI modifications and VFS structure changes. This initial implementation rebuilds a source-root snapshot after invalidation. Incremental project indexing and finer-grained invalidation are future performance work for large projects.
Verification
Automated projection tests cover shared helper context, real Java compilation, scoped imports, cross-namespace types, optional parameters/results, Unicode source ranges, empty bodies, enums and collections. Distribution checks verify the registered injector and the packaged compiler/parser dependencies.
The final integration check requires a running IntelliJ instance:
- Build/import the example and open its inline behavior.
- Invoke completion after
self.string().and navigate totoUpperCase. - Navigate from
suffix()to the private inline helper. - Edit a body and verify Java assistance still works without a Maven rebuild.
- Check ordinary Vernac completion, diagnostics and cross-namespace navigation remain available outside injected Java fragments.
Reference: IntelliJ language injection.
Generated source roots
VernacGeneratedSourcesActivity repairs the conventional generated Java source
root after startup, VFS changes and Maven/project-root updates. It adds only an
existing target/generated-sources/vernac belonging to a content root with a POM
and src/main/vernac, checks module ownership, and preserves existing source-root
classifications. Registration is coalesced and performed on the EDT in a write
action. It neither invokes the compiler nor changes source files, dependencies,
inspection severity or project-wide Java resolve rules. Ordinary Java indexing
and the owning module’s scope then provide class and factory resolution.
Regression tests cover first generation, idempotence, aggregator isolation,
clean/recreation event selection and plugin registration. Installed-IDE acceptance
must additionally verify native completion, navigation and var inference in a
new wizard project after its first generation build; these are not proven by the
filesystem tests alone.
Declaration-level recovery
The editor loader and Java tooling share declaration recovery. A broken declaration no longer disables complete Java contexts or navigable types elsewhere in the same file. Only declarations parsed without local recovery errors are admitted. Build entrypoints continue using the strict source parser.