chore: remove debug println from ExecutionInlayProvider#31
Open
xepozz wants to merge 52 commits into
Open
Conversation
The Copy button used a hardcoded literal string "console.text" instead of the console contents. Pass a content provider lambda so the toolbar can read the live text from ConsoleViewImpl on click. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
The previous panel created three fields backed by the same cached ConsoleView instance from one TextConsoleBuilder, then handed console.component to three JBTabbedPane tabs. Swing only honours one parent per component, so two of the three tabs ended up empty. Collapse to a single ConsoleView that already shows headers and body together, drop the broken tabs, and remove the now-unused parameters from HttpFeatureAdapter.printResponse. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
language-json.xml, plugin-database.xml and plugin-dotenv.xml do not exist in META-INF, so the optional depends entries just spam idea.log with PluginException warnings without adding any functionality. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
The extractor was a no-op (isApplicable always returned false) and the default AdapterLanguageExtractor already covers any PsiFile. The inlay provider registration for the Kotlin language remains so .kt files still get inline Run buttons via the fallback extractor. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
The old execute() wrapped OSProcessHandler in Task.Backgroundable and polled isProcessTerminated every 50ms on a pooled thread. The polling blocks the worker for the full duration of the command and races with the handler's own termination notification. OSProcessHandler already runs the process on its own thread and the inlay provider listens for processTerminated via the ProcessHandler callback (see ExecutionInlayProvider.run). Cancellation is delivered through the inline Stop button which calls processHandler.destroyProcess. The trade-off is no status-bar progress indicator; the inline Stop button remains the canonical way to cancel. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Lists deferred follow-ups so each gets its own focused PR rather than piling onto the hygiene branch. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Three of five out-of-scope plans land together; offset-mapping and session-storage plans follow in a separate commit. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Final two out-of-scope plans. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Agent updated the plan after the initial draft commit; this captures the final 340-line version. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
The harness creates agent worktrees under .claude/worktrees/; keep them out of the index. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
- Clarify relationship to plan 03 (the executeOnPooledThread step is interim; plan 03 step 5 supersedes it with runInterruptible on Dispatchers.IO). - Note that plan 02 deliberately leaves the invokeLater-based printToConsole to plan 03 — only the thread-creation primitive is changed here. - Document ConfigurationFactory.getName() default behavior (falls back to getId(), but only matters for multi-factory types — both ours are single). - Spell out the InterruptedException / HttpTimeoutException handling for the cancellation path. - Mark the LightPlatformTestCase smoke test as a follow-up (heavy fixture for a single registration assertion). - Add the exact textFieldWithBrowseButton(...) DSL signature so the FileChooserDescriptor wiring is unambiguous. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Tighten 05 — Stable session key and lifecycle after self-review: - Pin the valueHash strategy (short hex hash) and reconcile with Risk #1 instead of leaving two contradictory recommendations. - Specify global offset-based ordering for occurrenceIndex so the index is deterministic across PsiElements. - Mandate @volatile on mutable Session fields and require isAlive() to run under a read action (SmartPsiElementPointer.element contract). - Add split-aware fileClosed handling (consult getAllEditors before reaping) and split out the IDLE-on-close case. - Replace projectClosing listener with SessionStorage implementing Disposable, leveraging project-service disposal. - Provide explicit plugin.xml <projectListeners> snippet and document that PSI/EditorFactory listeners are wired programmatically with SessionStorage as their Disposable parent. - Add a Cross-plan dependencies section calling out how this change composes with plans 01-04 (specifically: leaves room for plan 03 to add Session.job). - Constrain the stale-key sweep to the current file's url. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
The previous editor declared five `private var` fields and bound the UI to those scratch fields directly, with `applyEditorTo` / `resetEditorFrom` copying values by hand. Two problems: - The UI was bound to ad-hoc local state, not to a stable model object. `isModified()` / `reset()` behaviour was hand-rolled and silently broken if `applyEditorTo` ever forgot a field. - `panel.apply()` / `panel.reset()` were never called, so the DSL bindings never executed — the editor only worked because of the manual copy. Switch to the canonical Kotlin UI DSL v2 pattern: bind every cell to properties on an `HttpUiModel` snapshot, hold the `DialogPanel`, and let `applyEditorTo` call `panel.apply()` (UI -> model) before copying model -> options, while `resetEditorFrom` copies options -> model before calling `panel.reset()`. No persistence change — `HttpRunConfigurationOptions` field names are unchanged. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Replace the raw `JTextField` + `TextFieldWithBrowseButton` (which were inside `cell(...)` blocks with no DSL binding) with the canonical `textField()` / `textFieldWithBrowseButton(...)` factories from the Kotlin UI DSL v2. - Introduce `ShellUiModel` snapshot and bind every field to it. - `applyEditorTo` calls `panel.apply()` before copying model -> options. - `resetEditorFrom` copies options -> model before calling `panel.reset()`. - Attach `FileChooserDescriptorFactory.createSingleFolderDescriptor()` so the Browse button on the Working Directory field opens a folder picker (previously it did nothing useful — no descriptor was attached). https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Both `HttpConfigurationFactory` and `ShellConfigurationFactory` previously returned the parent `ConfigurationType.ID` from `getId()`. The platform uses `factory.id` as a serialization key namespaced inside the type, so returning the same string as the type ID is an antipattern: it logs a warning at registration time and silently breaks persistence the moment a type gains a second factory. - HttpConfigurationFactory.getId() -> "HTTP" - ShellConfigurationFactory.getId() -> "Shell" Pin both as `companion object const val FACTORY_ID` with a comment documenting that they are persistence contracts (written into workspace.xml as `factoryName="..."`) and must never be renamed. Also fix `ShellRunConfigurationType.getConfigurationTypeDescription()` which was returning the literal placeholder `"configuration type description"`. Replaced with a real description that matches the style of the HTTP type. Annotate each `ID` constant with a comment warning future maintainers about the persistence contract. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Replace the raw `kotlin.concurrent.thread { executeRequest() }` in
`HttpProcessHandler.startNotify()` with
`ApplicationManager.getApplication().executeOnPooledThread { ... }`.
Why:
- `kotlin.concurrent.thread` creates a non-daemon thread by default and
has no name, making it invisible to the platform's leak detector and
profiler.
- The platform-pooled executor names its threads, applies bounded
concurrency, and participates in JVM-shutdown bookkeeping.
Store the returned `Future` so `destroyProcessImpl` can cancel it on the
Stop button click; also add explicit `InterruptedException` /
`HttpTimeoutException` branches that print a clean `[Cancelled]` line
instead of dumping a stack trace through the generic `catch (e: Exception)`.
This is an interim fix; plan 03 step 5 supersedes it with
`runInterruptible(Dispatchers.IO) { ... }` rooted in SessionStorage.cs.
https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Clarify call-site counts (4 imports + 8 lambdas), separate plan-03 scope from plan-01 (InlayHintsPassFactoryInternal stays for now) and plan-05 (SessionStorage key/anchor changes), expand the coroutine bridge for ProcessHandler in run()/runImpl(), and document the Dispatchers.IO origin (platform extension property). https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Record PSI facts verified before implementation (PhpDocComment extends PsiComment via PsiDocCommentBase; PhpTokenTypes.LINE_COMMENT and C_STYLE_COMMENT cover the line/block branches), fix the SingleLineCommentNormalizer marker-regex precedence (wrap in non-capturing group), call out the pre-existing RegexpMatcher offset/value mismatch that this plan deliberately does not fix, add CRLF fixture handling guidance, and document compatibility with plans 01/02/03/05. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Replace the editor-identity + line-number session key with a stable
composite key
${virtualFile.url}::${featureId}::${valueHash}#${occurrenceIndex}
so a session survives editor reopen, split panes, line shifts, and
edits that don't change the matched payload.
Changes:
- Session gains virtualFileUrl, featureId, valueHash, occurrenceIndex
and a SmartPsiElementPointer<PsiElement> anchor; mutable fields
marked @volatile because EDT listeners and ProcessHandler callbacks
write concurrently. isAlive() resolves the anchor inside a
ReadAction.
- SessionStorage exposes per-file queries (sessionsForFile,
forEach, evictStale) and implements Disposable so project shutdown
always terminates in-flight processes. Adds makeKey/hashValue
helpers and a makeSessionKey convenience.
- ExecutionInlayProvider computes a global occurrenceIndex per
(featureId, valueHash) sorted by originalRange.startOffset, builds
keys with the new helper, captures a SmartPsiElementPointer when
Run is clicked, and recreates the embedded container when its
parent editor has been disposed (file-reopen case).
- Stale-key sweep runs once per inlay pass (in the collector's init)
evicting sessions whose match no longer exists in the file.
A legacy fallback key based on editor identity is retained for the
edge case of PsiFiles without a backing VirtualFile so behaviour
matches the pre-refactor baseline rather than crashing.
Lifecycle listeners (file close, editor release, PSI delete) land in
a follow-up commit.
https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
SessionStorage now accepts a project-scoped CoroutineScope via constructor injection (supported on @service since 2024.1; we target 251). The scope is managed by the platform and cancelled automatically on project dispose. Session gains a mutable `job: Job?` so callers can cancel an in-flight run without going through ProcessHandler.destroyProcess(). SessionStorage exposes `replaceJob(key, job): Job?` for atomic swap-and-return-previous, and `remove(key)` now cancels the stored job as a side effect. No call site uses these yet — that is the next commit. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Wire both `HttpRunConfigurationType` / `ShellRunConfigurationType` and their producers into the previously empty `com.intellij` extensions block in `plugin.xml`. Until now both types compiled but were never instantiated by the platform. After this commit: - "HTTP Request" appears in Run/Debug Configurations -> Add New (Web icon). - "Shell Command" appears with the Run icon. - The two `LazyRunConfigurationProducer`s offer Run actions on comments matching the documented patterns. The new <configurationType> and <runConfigurationProducer> entries pass `./gradlew :verifyPluginProjectConfiguration`. Stable IDs (frozen as of this release): - HTTP type ID: `HttpInlayRunConfiguration` (workspace.xml `type=`) - Shell type ID: `ShellInlayRunConfiguration` - HTTP factory ID: `HTTP` (workspace.xml `factoryName=`) - Shell factory ID: `Shell` https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
- Note that PresentationTreeBuilder in 2025.1.1 has no icon(...) method; buttons render as labelled text tokens (verified via javap on com.intellij.codeInsight.hints.declarative.PresentationTreeBuilder). - Fix DeclarativeInlayHintsPassFactory.scheduleRecompute signature: takes (editor, project), not (editor); package is .impl. - Tooltip is supplied via the tooltip arg on addPresentation, not on text(). - Each button gets its own addPresentation call so it gets an independent tooltip + background. - Phase 1: clarify why DaemonCodeAnalyzer.restart(file) is enough without forceHintsUpdateOnNextPass for our off-PSI state model. - Payload model: featureId|line, action carried by handlerId. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Introduces a piecewise-linear OffsetMapping backed by a sorted list of preserved Segment(normalizedStart, originalStart, length). toOriginal clamps the end sentinel to originalLength; toNormalized clamps right on stripped chars, intentionally not preserving round-trip equality there. OffsetMapping.Identity stays for back-compat; SegmentedOffsetMapping .identity(length) provides the same behavior via the new class so callers can migrate at their own pace. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Replaces the callback-style execute(match, wrapper, project, onProcessCreated)
with a suspending execute(match, wrapper, project): ProcessHandler? return.
Both adapters are rewritten:
- ShellFeatureAdapter wraps the blocking OSProcessHandler() constructor in
withContext(Dispatchers.IO); errors are printed on Dispatchers.EDT under
NonCancellable.
- HttpFeatureAdapter replaces sendAsync().whenComplete with runInterruptible(
Dispatchers.IO) { httpClient.send(...) }. The custom ProcessHandler now only
notifies termination — cancellation is driven by Job.cancel on the caller's
side. CompletableFuture is gone.
ExecutionInlayProvider.run() launches the new suspending execute on
SessionStorage.cs. The existing invokeLater-based UI updates remain
unchanged in this commit; they are migrated to withContext(Dispatchers.EDT)
in the next commit.
https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
…eAnalyzer.restart(file) Phase 1 hot-fix from plan 01: stop depending on com.intellij.codeInsight.daemon.impl.InlayHintsPassFactoryInternal — an *Internal symbol that can be removed or renamed between 2025.x IDE updates. restart(file) invalidates the daemon's per-file scheduling state, which is sufficient for our off-PSI state model (SessionStorage) where the file modification stamp does not change between Run/Stop/Delete transitions. No behaviour change. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Three reapers drive the lifecycle side of the new file-scoped session key, ensuring SessionStorage no longer leaks JPanels, Wrappers or ProcessHandlers: - SessionLifecycleListener (project-level FileEditorManagerListener, declared in plugin.xml via <projectListeners>). On fileClosed, first checks getAllEditors(file) so a split close is a no-op; otherwise terminates RUNNING processes (state -> FINISHED, container detached, session kept for Rerun on reopen), detaches containers for FINISHED sessions, and removes IDLE sessions outright. - SessionEditorFactoryListener (EditorFactoryListener). Covers the split-pane close that doesn't fire fileClosed; if the released editor was the last one for the file, reaps with the same rules. - SessionPsiChangeListener (PsiTreeChangeAdapter). On childRemoved/childReplaced/childrenChanged for a session's file, evicts any session whose anchor is no longer valid. The "comment edited" case is left to the stale-key sweep. SessionLifecycleStarter (postStartupActivity) wires the EditorFactoryListener and PsiTreeChangeListener with SessionStorage as their Disposable parent so unregistration is automatic on project close. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Drops every invokeLater {...} in ExecutionInlayProvider and
embedContainerIntoEditor; UI mutations happen on Dispatchers.EDT inside
sessionStorage.cs.launch(...) blocks (or inline when the caller is already
on the EDT inside a runImpl coroutine).
Key behaviour changes:
- mountWrapperIntoContainer is now synchronous on the EDT — the wrapper is
visible immediately, not on the next UI tick. The inlay refresh that
follows is therefore consistent with the mounted state.
- run() launches a coroutine on SessionStorage.cs; a second click on the
same key cancels the previous Job via replaceJob().cancel() before
launching a new one.
- runImpl() awaits ProcessHandler.processTerminated via CompletableDeferred,
publishing FINISHED on the EDT inside a NonCancellable + Dispatchers.EDT
block so cancellation does not leave the inlay stuck on RUNNING.
- embedContainerIntoEditor is now a suspend fun that hops to Dispatchers.EDT
and guards against editor disposal.
InlayHintsPassFactoryInternal.forceHintsUpdateOnNextPass() and the
DaemonCodeAnalyzer.restart() call stay — replacing the *Internal API with a
public one is plan 01's responsibility.
https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
CommentNormalizer.normalize(rawText) returns NormalizedComment(text, mapping). Four implementations cover the comment shapes the existing extractors care about: - IdentityNormalizer: pass-through; full-length identity segment. - SingleLineCommentNormalizer(markerRegex): strips `^\s*(?:<marker>)\s?`; marker is wrapped in a non-capturing group so callers can pass alternatives like `"//|#"` without precedence pitfalls. - XmlCommentNormalizer: strips `<!--` / `-->` with one optional inner space on each side; emits a single segment. - CStyleBlockNormalizer(stripStarPrefix): handles `/* ... */` and PHPDoc; line-by-line scan, stripping `^\s*\*\s?` from each interior line when stripStarPrefix is true. `\r\n` and `\n` are treated as one logical terminator for stripping but both bytes still advance the original cursor so document offsets stay accurate. Trailing decoration-only lines before `*/` are suppressed. No production call site uses these yet; wiring lands in the next commits. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
BaseLanguageTextExtractor now accepts a defaultNormalizer (default: IdentityNormalizer) and exposes a protected normalizerFor(element) hook for subclasses that need per-element dispatch. extract() runs the picked normalizer on element.text and stores the resulting NormalizedComment text + mapping in ExtractedBlock. Subclasses (PHP / XML / YAML / Adapter) still pass no constructor argument and therefore still emit identity blocks — behavior unchanged until per-language wiring lands. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
ShellFeatureAdapter and HttpFeatureAdapter now compute endOriginal via mapping.toOriginal(m.offset + m.value.length) instead of startOriginal + m.value.length, so any future multi-line match that spans a stripped segment gap (PHPDoc `* `, XML `<!--`, ...) maps to the correct end position. A debug-only assertion catches regressions where end-start drops below value.length. No behavior change today: every Phase 1 match still lives inside one segment and Identity mapping makes both forms equivalent. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
HttpProcessHandler now launches its work on the project-bound SessionStorage.cs scope (resolved lazily on startNotify). The blocking httpClient.send is wrapped in runInterruptible(Dispatchers.IO) so cancellation maps to thread interrupt; CancellationException is caught and converted to notifyProcessTerminated(143) before rethrowing. printToConsole switched from invokeLater to withContext(Dispatchers.EDT); response printing is batched into a single EDT hop. destroyProcessImpl / detachProcessImpl now cancel the launched Job before notifying termination, and termination is gated by an AtomicBoolean so notifyProcessTerminated is called exactly once. HttpRunState.execute passes environment.project into HttpProcessHandler so the latter can resolve SessionStorage without a thread-local. After this commit `grep -rn 'invokeLater' src --include='*.kt'` is empty. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Phase 2.1 + 2.2 of plan 01. Introduces the modern declarative inlay surface without removing the legacy provider yet — both classes compile, but only the legacy one is wired into language-*.xml at this point. New components: - ExecutionDeclarativeInlayProvider: OwnBypassCollector that emits text buttons (Run / Stop / Rerun / Delete / Collapse / Expand). PresentationTreeBuilder has no icon(...) method in 2025.1.1, so buttons are labelled text tokens with hasBackground via HintFormat (MarginAndSmallerPadding). - ExecutionInlayPayload: featureId|line string payload, decoded back into the same makeKey(editor, featureId, line) the legacy provider uses. - RunInlayActionHandler / StopInlayActionHandler / DeleteInlayActionHandler / ToggleCollapseInlayActionHandler: each one a Project-agnostic InlayActionHandler that looks the session up by key and dispatches to ExecutionController. - ExecutionController (@service(PROJECT)): owns run/stop/delete/toggleCollapse + the EDT-bound refreshInlays(editor) call (DeclarativeInlayHintsPassFactory.scheduleRecompute + DaemonCodeAnalyzer.restart). - CallBundle.properties: inlay.execution.name + inlay.execution.description for the declarativeInlayProvider nameKey/descriptionKey. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
XML and YAML extractors pass XmlCommentNormalizer /
SingleLineCommentNormalizer("#") to the base ctor — single segment per
comment, identity for single-line URLs/commands.
PHPLanguageExtractor overrides normalizerFor(element) to dispatch by
PSI: PhpDocComment -> CStyleBlockNormalizer(stripStarPrefix = true),
C_STYLE_COMMENT token -> CStyleBlockNormalizer(stripStarPrefix = false),
LINE_COMMENT token -> SingleLineCommentNormalizer("//|#").
AdapterLanguageExtractor keeps the default IdentityNormalizer.
End-user effect: shell:/http: matches inside PHPDoc with multi-line
bodies (the `* shell: ...` case) now land on the correct document
offsets; single-line `//` and `#` cases keep working because their
stripping preserves the relative position the matchers already expect.
https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
…ndlers Phase 2.3 of plan 01. - plugin.xml: register four codeInsight.inlayActionHandler entries (run / stop / delete / toggle_collapse) at the application level — these are language-agnostic. - language-*.xml: replace codeInsight.inlayProvider with codeInsight.declarativeInlayProvider, pointing at the new ExecutionDeclarativeInlayProvider with providerId inline_call.execution, group OTHER_GROUP, bundle messages.CallBundle, isEnabledByDefault=true. - All four languages migrated: Kotlin, PHP, XML, yaml. The legacy ExecutionInlayProvider class is still in src/ but no longer registered anywhere; cleanup happens in the next commit. verifyPluginProjectConfiguration: green. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
…fsetMapping @deprecated singleton retained for binary compatibility; the ExtractedBlock default mapping now constructs SegmentedOffsetMapping.identity(text.length) so the new SPI is the single source of truth. No production call site references OffsetMapping.Identity anymore (the remaining mention is a KDoc cross-ref in SegmentedOffsetMapping). https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
…piUsage Phase 2.5 of plan 01. The new declarative provider is wired in and verified to compile + load; the old InlayHintsProvider<NoSettings> implementation is now dead code. - Delete src/.../ExecutionInlayProvider.kt (legacy DSL provider). - Move makeKey(editor, featureId, line) to ExecutionInlayPayload.kt so the new provider + handlers keep using the same session-key convention. (The known editor.hashCode() instability is tracked separately in plan 05.) - CHANGELOG: document the inlay-API migration, the InlayHintsPassFactoryInternal drop, the settings-UI move under Other group, and the removal of the legacy provider + @Suppress("UnstableApiUsage"). No @Suppress("UnstableApiUsage") remains in the inlay code path. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Pure-JUnit4 tests (no IntelliJ platform harness) that exercise: SegmentedOffsetMappingTest: - identity round-trip, end sentinel, multi-segment PHPDoc-style mapping; - toNormalized clamping on stripped chars (left edge -> next segment, trailing gap -> normalizedLength); - out-of-bounds rejection, overlap rejection, oversize rejection; - preserved-index property across a 100-segment ladder. CommentNormalizerTest: - Identity, SingleLine (`//`, `#`, leading indent, no-space marker), XmlComment (with/without inner spaces, empty body), and CStyleBlock (single-line block, single-line PHPDoc, multi-line PHPDoc with and without star-stripping, CRLF, missing space after `*`, preserved interior indentation). - Every test additionally asserts the preserved-index property: normalized[i] == raw[mapping.toOriginal(i)]. The full Gradle `test` target cannot run in the sandboxed agent environment (it needs com.jetbrains.intellij.tools:tests-bootstrap which is unreachable here), but `compileTestKotlin` is green. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
SegmentedOffsetMapping.toOriginal(normalizedLength) used to return originalLength, which inflates endOriginal when trailing decoration (e.g. PHPDoc `\n */`) follows the matched content. Now returns lastSegment.originalStart + lastSegment.length so originalRange = toOriginal(start) .. toOriginal(start + len) spans exactly the matched substring. Empty mappings still fall back to originalLength so adapters that hand an empty block see no surprises. Updates the affected SegmentedOffsetMappingTest and the PHPDoc multi-line assertion in CommentNormalizerTest. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Clarify the contract: toOriginal(normalizedLength) returns the position right after the last preserved character (lastSegment.originalStart + lastSegment.length), not originalLength. This is what makes endOriginal arithmetic span exactly the matched substring when trailing decoration follows it in the original text. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
# Conflicts: # src/main/resources/META-INF/plugin.xml
# Conflicts: # src/main/kotlin/com/github/xepozz/inline_call/base/SessionStorage.kt # src/main/kotlin/com/github/xepozz/inline_call/base/inlay/ExecutionInlayProvider.kt # src/main/kotlin/com/github/xepozz/inline_call/base/inlay/Session.kt # src/main/kotlin/com/github/xepozz/inline_call/feature/http/run/HttpRunState.kt
# Conflicts: # src/main/kotlin/com/github/xepozz/inline_call/base/inlay/ExecutionInlayProvider.kt # src/main/resources/META-INF/plugin.xml
…-05 key The merge of plan-01 brought in ExecutionController, ExecutionInlayPayload, ExecutionInlayActionHandlers and ExecutionDeclarativeInlayProvider written against the pre-plan-03 callback FeatureGenerator.execute and the pre-plan-05 line-based Session key. After the merge those files no longer compiled against the merged Session(virtualFileUrl, featureId, valueHash, occurrenceIndex, anchor, ...) shape or against the suspending FeatureGenerator.execute() returning ProcessHandler?. Rewire: - ExecutionController.run() schedules feature.execute via SessionStorage.cs, bridges ProcessHandler termination through CompletableDeferred, applies NonCancellable + Dispatchers.EDT for terminal UI work. - ExecutionController.runImpl() constructs the new Session(...) with virtualFileUrl / valueHash / occurrenceIndex / SmartPsiElementPointer. - ExecutionInlayPayload now encodes (featureId, valueHash, occurrenceIndex, line); legacy fallback key kept for files without VirtualFile. - ExecutionDeclarativeInlayProvider pre-computes per-(featureId, valueHash) occurrence index and runs SessionStorage.evictStale() per inlay pass. - ExecutionInlayActionHandlers re-resolve matches by (featureId, valueHash, occurrenceIndex) and pass anchor PsiElement + occurrence to the controller. - New FileLookup helper centralises VirtualFile-from-Editor lookup. - plugin.xml union of all three plans (run configs + lifecycle starter + 4 inlayActionHandlers). https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
CStyleBlockNormalizer with stripStarPrefix=false treated the trailing \" */\" decoration line (the whitespace + leading \"*\" before the closer) as emittable content, producing a trailing \" \" in the normalized text and a stray \"\\n\" segment. The line was the closer's leading whitespace, not real content. Decompose: probe each line's decoration boundary (whitespace + optional \"* \") regardless of the flag; emit the line only when at least one non-decoration character exists; only the *emission* contentStart honours stripStarPrefix. hasMoreContentBefore also now ignores the flag — decoration-only is decoration in both modes. Fixes CommentNormalizerTest > \"CStyleBlock multi-line without stripStarPrefix preserves stars\". https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
BasePlatformTestCase runs against a sandbox VFS that only whitelists a few roots (project base, idea-sandbox, etc.). Module-level src/test/testData was outside those roots when the build runs from /home/user/inline-call-plugin, so testRename failed with VfsRootAccessNotAllowedError on every CI run. Register the absolute testDataPath via VfsRootAccess.allowRootAccess bound to testRootDisposable, so the whitelist lives exactly for the test's lifetime. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Qodana Community for JVM8 new problems were found
💡 Qodana analysis was run in the pull request mode: only the changed files were checked View the detailed Qodana reportTo be able to view the detailed Qodana report, you can either:
To get - name: 'Qodana Scan'
uses: JetBrains/qodana-action@v2026.1.0
with:
upload-result: trueContact Qodana teamContact us at qodana-support@jetbrains.com
|
ExecutionController.run() used to launch a coroutine on the service scope and then `withContext(Dispatchers.EDT)` to mount the Swing container. In `BasePlatformTestCase` that switch's body never ran, and in production users reported clicking Run did nothing. Restructured the click path: - `run()` invokes `preparePhase1` synchronously on the click EDT thread (the caller — IntelliJ's declarative inlay framework — is already on EDT). Container creation, embedding, wrapper mounting, state → RUNNING, and inlay refresh all happen inline. No dispatcher switch, no suspension. - Phase 2 (`feature.execute`) is launched in `SessionStorage.cs`. The Job is stored on the session so `stop()` / `delete()` can cancel it. The launched coroutine no longer needs to hop back to EDT for UI work. - Phase 3 wires `ProcessListener.processTerminated` to `finishOnEdt`, which uses `ApplicationManager.invokeLater` for the state flip — that path is bullet-proof. Side fixes: - `embedContainerIntoEditor` no longer suspends; it requires the caller to be on EDT and returns the `Disposable` (the underlying Inlay) so cleanup can actually unregister the embedded component. Without disposing it the editor kept a stale reference (visible as `Editor hasn't been released` in tests, memory leak in production). - `Session.embeddedInlay: Disposable?` added; `delete()` now disposes it before nulling out the container. - `ShellFeatureAdapter.execute` no longer wraps OSProcessHandler construction in `withContext(Dispatchers.IO)`. The wrap never resumed in tests for the same reason as Phase 1, and Process.start() is fast enough to run on the caller's thread anyway. Tests: - New `ExecutionInlayPayloadTest` — encode/decode round trips and rejection of malformed payloads. - New `ExecutionControllerTest` — uses a FakeFeature to verify IDLE → RUNNING → FINISHED, Stop, Delete, and re-run flows. - New `SessionStorageCoroutineTest` — sanity checks for the service scope plus regression-guard tests documenting the `withContext(Dispatchers.EDT/IO) from Default-launched body` quirk that motivated the restructure. - New `RunInlayActionHandlerTest` — verifies the click handlers no-op on malformed / unknown-feature payloads. Full real-shell click-through is exercised end-to-end by `ExecutionControllerTest` via FakeFeature; we don't run a real shell through EditorEmbeddedComponentManager in tests because the synthetic editor doesn't release after embedding + process attachment. 49/49 tests pass. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
…ack path
Click handlers visibly rendered but did nothing because:
1. `FeatureGenerator.execute` had been changed to `suspend`, called from
`SessionStorage.cs.launch { ... }` inside the click controller. The
IDE log shows `kotlinx.coroutines.CoroutinesInternalError: Debug
metadata version mismatch. Expected: 1, got 2.` blowing up the
coroutine machinery before Phase 2 (the actual process start) could
run. The session sat in RUNNING forever (or was never created).
2. `language-kotlin.xml` was silently dropped under the Kotlin K2 plugin
mode (`Plugin not compatible with the Kotlin plugin in the K2 mode.
So, the language-kotlin.xml was not loaded`), so no inlays existed
on .kt files at all.
Diagnosis came from a 10-agent fan-out (audit of plugin.xml, click
dispatch path through InlayActionHandlerBean, decompilation of
EditorEmbeddedComponentManager / DeclarativeInlayEditorMouseListener,
runtime log analysis, master vs integration diff).
Restored from `origin/main` verbatim:
- `base/inlay/ExecutionInlayProvider.kt` (legacy `InlayHintsProvider`
with `factory.onClick` callback closures — captures editor / project
/ feature / match by reference, no payload serialisation, no
handlerId lookup, no dispatcher juggling)
- `base/inlay/Session.kt` (5-field data class)
- `base/inlay/ui/embedContainerIntoEditor.kt` (Unit, internal
invokeLater)
- `base/SessionStorage.kt` (plain ConcurrentHashMap, no CoroutineScope)
- `base/api/FeatureGenerator.kt` (non-suspend execute + onProcessCreated
callback)
- `feature/{shell,http}/*FeatureAdapter.kt` (callback signature, no
suspend, no withContext)
- `feature/http/run/HttpRunState.kt` (thread{} instead of cs.launch)
- All `META-INF/language-*.xml` to legacy `codeInsight.inlayProvider`
Deleted (declarative dead code):
- `ExecutionDeclarativeInlayProvider.kt`
- `ExecutionInlayActionHandlers.kt`
- `ExecutionInlayPayload.kt`
- `ExecutionController.kt`
- `FileLookup.kt`
- 4 `<codeInsight.inlayActionHandler>` EPs in plugin.xml
- `base/lifecycle/Session*` (depended on the new Session shape)
- `postStartupActivity` + `projectListeners` registrations
- All tests that drove the deleted classes
Kept from integration (not on the click path, unaffected by the bug):
- Plan 02 — Run Configuration registrations + Producer + bindings
- Plan 04 — `SegmentedOffsetMapping`, `CommentNormalizer` SPI, all
language-specific extractors, adapter end-offset routing
- Hygiene fixes — Copy toolbar lambda, `HttpWrapperPanel` single
console, `<depends>` cleanup
- `MyPluginTest` VfsRootAccess setup
https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
Adds ExecutionInlayProviderTest that exercises the rolled-back legacy provider end-to-end through the platform's own test harness: - doTestProvider runs ExecutionInlayProvider through a real FactoryInlayHintsCollector against the configured PSI file and renders every InlayPresentation into a text dump. The expected dumps (`[<image> Run ]`) are embedded in the YAML content as `/*<# ... #>*/` markers. - One Run-button assertion per matchable form (shell comment, HTTPS comment, both on adjacent lines) plus a negative case (regular comments — no inlay). - Smoke check on the LanguageTextExtractor + FeatureGenerator chain that feeds the sink. This is the actual visual verification the previous commit could not perform: if the Run button stopped rendering, the dump would diverge and the test would fail with a precise ComparisonFailure that pinpoints the regression. 5/5 pass, 35/35 total green. https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
https://claude.ai/code/session_01K4dX8TVMVJX6akS81GXsfm