Skip to content

feat: enable Shift+F6 rename inside Qiq injected fragments#14

Merged
jingu merged 1 commit into
developfrom
feat/template-side-rename-trigger
May 26, 2026
Merged

feat: enable Shift+F6 rename inside Qiq injected fragments#14
jingu merged 1 commit into
developfrom
feat/template-side-rename-trigger

Conversation

@jingu
Copy link
Copy Markdown
Owner

@jingu jingu commented May 26, 2026

Summary

Pressing Shift+F6 on a PHP identifier inside a Qiq template — for example the property name in {{h \$article->title }} or any identifier inside an inline <?php ... ?> host — did not open the rename dialog. Local variable rename happened to work, but property and method accesses silently failed.

Closes the follow-up tracked in PR #13.

Root cause

The platform's default PsiElementRenameHandler reads CommonDataKeys.PSI_ELEMENT from the data context, which resolves to the outer Qiq host (an unnamed wrapper). The host satisfies neither PsiNamedElement nor canRename, so the action is disabled. The previous PR's ElementManipulator work was correct for the rewrite step, but the dispatch step (recognizing the caret position as renameable) was missing — <lang.elementManipulator> only matters once a rename pipeline has already chosen a target.

Confirmed empirically by adding a temporary Logger.warn to a draft handler and reading idea.log: the leaf returned by file.findElementAt(offset) inside an injected fragment was always a PHP leaf with no QiqCodeHost / QiqPhpHost ancestor, because PSI_FILE in the data context was the injected PHP file itself.

Implementation

Single new class QiqInjectedRenameHandler, registered via <renameHandler>:

  • Scope check uses InjectedLanguageManager.getTopLevelFile(file).language is QiqTemplateLanguage. This normalizes both cases the platform may give us (PSI_FILE = injected PHP file vs. outer Qiq file) and prevents competing with PHP's own handlers in plain .php files.
  • Target lookup descends into the injected fragment via InjectedLanguageManager.findInjectedElementAt, then walks parents. At each step it asks PHP's own references to resolve themselves (PsiReference.resolve() directly when the element implements it, and via element.references otherwise), falling back to the element itself when it is a PsiNameIdentifierOwner. This handles variables, properties, methods and class references uniformly through PHP's resolution, which already has the injected context wired by our MultiHostInjector.
  • Dispatch hands the resolved target off to PsiElementRenameHandler's standard pipeline, which then uses the host's ElementManipulator (added in PR feat: propagate refactorings into Qiq host text #13) to rewrite the Qiq host text.

Bidirectional rename, end-to-end

With this PR merged together with #13:

Trigger Before #13 After #13 After this PR
Shift+F6 on PHP class field (in .php file) declaration renamed, templates not updated declaration and template references both renamed (unchanged — already worked)
Shift+F6 on local $variable inside {{h \$foo->bar }} dialog opens but no update in template dialog opens, all template refs updated dialog opens, all template refs updated
Shift+F6 on property name inside {{h \$foo->bar }} no dialog no dialog dialog opens, all refs updated
Shift+F6 on identifier inside <?php ... ?> host no dialog (for properties) no dialog (for properties) dialog opens, all refs updated

Tests

Action-layer tests for RenameHandler would require a LightJavaCodeInsightFixtureTestCase with PHP plugin fixtures and a full injected setup, which is significantly heavier than the rest of this project's test surface. Verified manually in PhpStorm 2026.1 instead:

  • ✅ Property rename triggered from inside {{h \$article->title }} opens the dialog, renames the class field, and propagates back to all template references.
  • ✅ Local variable rename inside templates continues to work (no regression).
  • ✅ Rename inside <?php ... ?> blocks inside templates works for properties.
  • ✅ Rename in plain .php files (outside any Qiq template) is unaffected — the scope check excludes them so this handler does not compete with PHP's own.

Risks

  • Handler precedence: the handler returns true from isAvailableOnDataContext only when the caret resolves to a PsiNamedElement reachable from a Qiq host. In Qiq files where the default handler would otherwise also fire (e.g. on $article itself, where the default works), the registry will list both. The current observation is that variable rename works as before with no chooser, but if a chooser surfaces in some corner case, a follow-up can return false when the resolved target is something PHP's handler would already handle directly.

Test plan

  • ./gradlew test — existing tests green
  • ./gradlew buildPlugin — produces a valid distribution
  • Manual: Shift+F6 on a property reference inside {{h ... }} opens the rename dialog and propagates correctly to both the PHP class and all template references

🤖 Generated with Claude Code

Pressing Shift+F6 on a PHP identifier inside a Qiq template (e.g. the
property name in `{{h $article->title }}` or any identifier in an inline
`<?php ... ?>` host) did not open the rename dialog. Local variable
rename happened to work via the default handler, but property and
method accesses silently failed because CommonDataKeys.PSI_ELEMENT
resolves to the outer Qiq host (an unnamed wrapper) and the default
PsiElementRenameHandler.canRename rejects it.

Add QiqInjectedRenameHandler, registered via <renameHandler>:

- It scopes itself by checking that the top-level (host) file is Qiq,
  using InjectedLanguageManager.getTopLevelFile. The platform pre-
  narrows PSI_FILE in the data context to the most specific file at
  the caret — sometimes the injected PHP file, sometimes the outer
  Qiq file — so the scope check normalizes both shapes.
- It resolves the rename target by descending into the injected
  fragment via InjectedLanguageManager.findInjectedElementAt, walking
  parents, and asking each PSI element (or its references) to resolve
  to a PsiNameIdentifierOwner. This handles variables, properties,
  methods and class references uniformly through PHP's own resolution.
- It then hands the resolved target off to PsiElementRenameHandler's
  standard pipeline, which in turn uses the host's ElementManipulator
  (added in the previous PR) to rewrite the Qiq host text.

Combined with the existing PHP→Qiq propagation, rename refactoring now
flows bidirectionally between Qiq templates and PHP classes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 26, 2026 06:45
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a custom RenameHandler so that Shift+F6 on PHP identifiers inside Qiq injection hosts (e.g. {{h $article->title }} or inline <?php ... ?>) opens the rename dialog. Complements PR #13, which already wired the rewrite step via ElementManipulators.

Changes:

  • New QiqInjectedRenameHandler that descends into the injected PHP fragment, resolves the target via PHP references / PsiNameIdentifierOwner, and delegates to PsiElementRenameHandler.
  • Scope is restricted to files whose top-level language is QiqTemplateLanguage, to avoid competing with PHP's default handler in plain .php files.
  • Registers the handler in plugin.xml.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/main/resources/META-INF/plugin.xml Registers the new renameHandler extension.
src/main/kotlin/io/github/jingu/idea_qiq_plugin/navigation/QiqInjectedRenameHandler.kt Implements the injected-fragment-aware rename handler.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@jingu jingu merged commit 8d5d254 into develop May 26, 2026
2 checks passed
@jingu jingu deleted the feat/template-side-rename-trigger branch May 26, 2026 06:49
@jingu jingu mentioned this pull request May 27, 2026
3 tasks
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