Skip to content

chore: remove debug println from ExecutionInlayProvider#31

Open
xepozz wants to merge 52 commits into
mainfrom
claude/integration-all-plans
Open

chore: remove debug println from ExecutionInlayProvider#31
xepozz wants to merge 52 commits into
mainfrom
claude/integration-all-plans

Conversation

@xepozz
Copy link
Copy Markdown
Collaborator

@xepozz xepozz commented May 24, 2026

claude added 30 commits May 24, 2026 10:03
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
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
claude added 19 commits May 24, 2026 10:31
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
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 24, 2026

Qodana Community for JVM

8 new problems were found

Inspection name Severity Problems
Unresolved reference in KDoc 🔶 Warning 5
Local 'var' is never modified and can be declared as 'val' 🔶 Warning 1
Unnecessary non-capturing group 🔶 Warning 1
Unstable API Usage 🔶 Warning 1

💡 Qodana analysis was run in the pull request mode: only the changed files were checked

View the detailed Qodana report

To be able to view the detailed Qodana report, you can either:

To get *.log files or any other Qodana artifacts, run the action with upload-result option set to true,
so that the action will upload the files as the job artifacts:

      - name: 'Qodana Scan'
        uses: JetBrains/qodana-action@v2026.1.0
        with:
          upload-result: true
Contact Qodana team

Contact us at qodana-support@jetbrains.com

claude added 3 commits May 24, 2026 13:01
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants