Skip to content

IntelliJ Tooling plugin - #838

Draft
devnatan wants to merge 23 commits into
mainfrom
feat/intellij-tooling-plugin
Draft

IntelliJ Tooling plugin#838
devnatan wants to merge 23 commits into
mainfrom
feat/intellij-tooling-plugin

Conversation

@devnatan

@devnatan devnatan commented Jul 30, 2026

Copy link
Copy Markdown
Owner

IntelliJ Tooling plugin will help developers preview inventories. In this first release only static analysis is done. I'll bring fully interactive inventory right in the IDE later.

Derived from #262
Closes #259

devnatan added 12 commits July 30, 2026 18:03
Adds a new intellij-plugin Gradle module (IntelliJ Platform Gradle Plugin
2.11.0, kept on Gradle 8.x since 2.12+ requires Gradle 9) with a minimal
plugin.xml and a no-op FileEditorProvider, as the foundation for a live
inventory preview panel that renders next to open View classes.
Adds UAST-based detection (ViewDetector) that recognizes View subclasses
and inline Views.rows/type/builder chains across Java and Kotlin, and
wires a TextEditorWithPreview split editor showing a placeholder preview
panel next to the source. FileEditor is implemented directly against
UserDataHolderBase since FileEditorBase no longer exists on this platform
version (confirmed empirically against the 2026.1 build 261 distribution).
Adds ViewExtractor (UAST-based) to pull ViewType, size, title, and
layout() chars from ViewConfigBuilder/ViewBuilder call chains, using a
hardcoded rows/columns table per view type since actual runtime
evaluation of the user's ViewType constants is out of scope for the
static analysis pass. The preview panel now paints an actual slot grid
sized and shaped from the extracted model instead of a placeholder label.
Adds ItemExtractor to resolve withItem(new ItemStack(Material.X)) calls
(directly or through a local variable initializer) to a slot/material,
covering both the slot(index, item)-style sugar methods and the
slot(index).withItem(item) chained builder style, plus layoutSlot char
bindings expanded against the extracted layout grid. renderWith/onRender
calls are marked as dynamic placeholders since their output depends on
runtime state. The preview panel now paints a deterministic color+label
per material and a distinct marker for dynamic slots.
ItemExtractor.kt was untracked and got skipped by the previous
pathspec-limited commit, which only picks up already-tracked paths for
new files. Adding it here so the module actually compiles as described.
Registers a PsiTreeChangeListener scoped to the open file, debounced
300ms via Alarm and disposed alongside the FileEditor, so the preview
re-extracts and repaints as the user edits the view's config/render
methods instead of only rendering once at open time.
Views.rows(n) is sugar for builder().type(CHEST).size(n) inside Views
itself, so that .type()/.size() call never appears in the user's own
source for the extractor to see - special-cased here since it's the
primary documented entry point for the inline API. Also adds a maxSize
per view type so irregular containers (furnace/smoker, whose 2x2 grid
only has 3 real slots) don't render a phantom extra slot, and clarifies
the empty-state message now that "no view detected" can no longer reach
this panel (the file editor only opens once a view is already detected).
… tab

ViewDetector.isViewFile used InheritanceUtil.isInheritor and method
resolution, both of which hit the stub index and throw
IndexNotReadyException while the project is still indexing; accept() is
invoked even for non-DumbAware providers in this platform version, so
the exception was surfacing as a PluginException. Now short-circuits via
DumbService.isDumb() with an IndexNotReadyException catch as a fallback
for the race window.

Also switches the editor policy from PLACE_AFTER_DEFAULT_EDITOR to
HIDE_DEFAULT_EDITOR: since our editor already embeds the full text
editor inside TextEditorWithPreview, the previous policy was adding a
redundant second "Inventory Preview" tab alongside the plain text tab
instead of replacing it with one always-visible split view.
HIDE_DEFAULT_EDITOR requires the platform to trust the provider's
accept() during dumb mode, which it now can since ViewDetector already
short-circuits to false while indexing instead of throwing.
The initial refreshPreview() in init{} could run while the project was
still indexing (e.g. restoring a previously-open view file on IDE
startup), and ViewExtractor's PSI resolution has no dumb-mode guard, so
it threw, got caught, and cached a permanent null model with nothing
ever retrying it. DumbService.runWhenSmart() covers both cases: it runs
immediately if already smart, or queues a single retry once indexing
finishes.
render.firstRow()/lastRow()/row(n)/firstColumn()/lastColumn()/column(n)
return the "next available slot" in that row/column, not the whole
row/column - but the idiomatic usage (see RowColumnSample, which loops
columnsCount times calling firstRow()) is to call them repeatedly to
fill the entire row/column. Static analysis can't detect loop iteration
counts from a single call site, so each occurrence is now treated as
"fill the whole row/column" - wrong for genuine single-slot usage, but
renders the common case instead of showing nothing, which is what was
happening for RowColumnSample.java before this change.
render.firstRow((pos, slot) -> slot.withItem(item)) calls withItem on
the lambda's builder parameter rather than chaining it directly onto
the row/column call, so the existing receiver-chain check in
resolveChainTarget never matched it. Adds resolveFactoryCall to
recognize the factory-argument overloads directly and search the
lambda body for its withItem call, reusing the same row/column "fill
the whole line" indices as the chain-style handling.
@devnatan devnatan self-assigned this Jul 30, 2026
@devnatan devnatan added the feature New feature or enchancement label Jul 30, 2026
devnatan added 11 commits July 30, 2026 18:33
Adds chest-1.png..chest-6.png (one per row count) under
resources/assets/sprites and uses them as the chest inventory
background instead of a hand-drawn grid of rectangles. Slot content
(material color+label, dynamic marker, layout fill) is overlaid at the
exact pixel offsets derived from the sprites' geometry (7px side
border, 17px top title area, 18px/slot, scaled 2x with nearest-neighbor
interpolation to keep the pixel art crisp). Non-chest view types still
fall back to the drawn grid since there's no sprite for them.
Recreating this file - a prior commit adding it was dropped by a branch
rebase. Updated to reflect the new sprite-based chest rendering (frame
is a real bundled sprite now, item icons are still placeholders).
render.firstRow(((pos, slot) -> slot.withItem(item)))) - note the extra
parens around the lambda - resolved to a UParenthesizedExpression
wrapping the ULambdaExpression rather than the lambda directly, so
`lambdaArg as? ULambdaExpression` silently failed and the slot was
never placed. render.lastColumn(...) worked because it happened to have
no redundant parens. Applies skipParenthesizedExprDown() at every point
an argument expression gets downcast to a specific UAST subtype
(lambda, call receiver, reference, item expression), since redundant
parens are valid anywhere in Java/Kotlin, not just around this one
lambda.
Records the source TextRange that produced each slot (the outer call
expression - slot()/withItem()/the row-column factory call - depending
on which extraction path resolved it) directly on PreviewSlot. The
preview editor listens for caret movement and highlights the slot whose
range contains the caret offset; clicking a filled slot in the preview
moves the caret to (and scrolls/focuses) its source location. Ranges
are plain TextRange (two ints), not held PSI/UAST references, so they
stay safe to keep on the model between read actions - though they can
go stale for the ~300ms between an edit and the next debounced
re-extraction, same tradeoff the live-refresh feature already accepts.
firstRow()/lastRow()/column(n)/etc. produce multiple slots that all
share the same PreviewSlot (same sourceRange, same item), but
updateHighlightForCaret() used firstOrNull(), so only one of them ever
lit up when the caret was inside that call. Switched the highlight
state from a single nullable index to a Set<Int> and filter() instead
of firstOrNull() to collect every matching slot.
Adds a LocalInspectionTool that walks call expressions and simple name
references (via UastHintedVisitorAdapter, so it works across Java and
Kotlin like the rest of the plugin) and flags anything whose resolved
declaration - or its containing class, since inventory-framework marks
whole classes like View/ViewBuilder rather than every member - carries
@ApiStatus.Experimental or @ApiStatus.Internal. Uses
ProblemHighlightType.LIKE_DEPRECATED (same strikethrough treatment as
@deprecated usage) since both convey "this may change or disappear."
The grid/sprite was always drawn at a fixed top-left offset regardless
of the panel's actual size, so it sat pinned to the corner in a wider
split-editor pane instead of centered. gridOrigin() now computes the
origin from the panel's live width/height (falling back to a fixed
margin when the panel is smaller than the content), and both painting
and click hit-testing derive their coordinates from it so they can't
drift out of sync as the panel resizes.
… highlight

Both used ProblemHighlightType.LIKE_DEPRECATED before, so an @internal
usage - which really shouldn't happen at all - looked the same as an
@experimental one, which is just "may change." Internal now uses
GENERIC_ERROR_OR_WARNING (the ordinary yellow warning underline);
Experimental uses WEAK_WARNING (the lighter gray dotted underline
IntelliJ reserves for softer suggestions).
WEAK_WARNING was still in the yellow family, just thinner. Trying
POSSIBLE_PROBLEM for a genuinely different color per user request;
LIKE_UNKNOWN_SYMBOL is the other candidate to compare against if this
one isn't right either - registerProblem only accepts the built-in
ProblemHighlightType set, so picking among these is the extent of
control available without switching to an Annotator.
…spection

Experimental stays WEAK_WARNING (lighter dotted underline); Internal
moved to GENERIC_ERROR (a stricter red squiggly, since using it at all
usually indicates a mistake) - settled on after comparing a few
ProblemHighlightType options. Comment updated to match; it previously
still described the POSSIBLE_PROBLEM experiment.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or enchancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IntelliJ tooling plugin

1 participant