Skip to content

[Gutter] Add support for multiple icons in a file#894

Merged
egorikftp merged 2 commits intomainfrom
feature/idea/gutter
Feb 25, 2026
Merged

[Gutter] Add support for multiple icons in a file#894
egorikftp merged 2 commits intomainfrom
feature/idea/gutter

Conversation

@egorikftp
Copy link
Member

@egorikftp egorikftp commented Feb 22, 2026

image

📝 Changelog

If this PR introduces user-facing changes, please update the relevant Unreleased section in changelogs:

@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4167455 and 31a199b.

📒 Files selected for processing (10)
  • sdk/intellij/psi/imagevector/api/imagevector.api
  • sdk/intellij/psi/imagevector/src/main/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/imagevector/ImageVectorPsiParser.kt
  • sdk/intellij/psi/imagevector/src/main/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/imagevector/parser/MaterialImageVectorPsiParser.kt
  • sdk/intellij/psi/imagevector/src/main/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/imagevector/parser/RegularImageVectorPsiParser.kt
  • tools/idea-plugin/CHANGELOG.md
  • tools/idea-plugin/build.gradle.kts
  • tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/completion/ImageVectorIcon.kt
  • tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/icon/ImageVectorIconProvider.kt
  • tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/util/ImageVectorPsiUtil.kt
  • tools/idea-plugin/src/test/kotlin/io/github/composegears/valkyrie/gutter/ImageVectorGutterTest.kt
🚧 Files skipped from review as they are similar to previous changes (3)
  • tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/completion/ImageVectorIcon.kt
  • tools/idea-plugin/CHANGELOG.md
  • tools/idea-plugin/src/test/kotlin/io/github/composegears/valkyrie/gutter/ImageVectorGutterTest.kt

Walkthrough

Adds KtProperty-based parsing across the ImageVector parsing stack: a new parseToIrImageVector(KtProperty) in ImageVectorPsiParser and corresponding parse(KtProperty) entry points plus internal KtProperty.parseImageVector() extensions in MaterialImageVectorPsiParser and RegularImageVectorPsiParser. ImageVectorPsiUtil now prefers parsing from a KtProperty/navigation element. ImageVectorIconProvider gains KtProperty handling and early guards. ImageVectorIcon.toString() was added. Tests for multiple gutter icons and Gradle/JUnit5 test config changes were included.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title '[Gutter] Add support for multiple icons in a file' is a concise, clear summary of the main change in the PR, focusing on the gutter icon feature enhancement.
Description check ✅ Passed The PR description includes the issue closure reference (#877) and completes the changelog template by marking the IntelliJ Plugin checkbox as completed.
Linked Issues check ✅ Passed The PR meets the objective of #877: it adds support for multiple icons in a single file by extending parseToIrImageVector to handle KtProperty inputs and updating ImageVectorIconProvider to show gutter icons for individual properties.
Out of Scope Changes check ✅ Passed All changes are directly related to supporting multiple gutter icons in a file; no extraneous modifications were introduced outside the scope of issue #877.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/idea/gutter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (5)
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/completion/ImageVectorIcon.kt (1)

88-90: toString() truncation always appends ... even for short XML.

Minor nit: if vectorXml is ≤100 chars, the trailing ... is misleading since nothing was truncated.

Also, this toString() is used in tests to assert icon inequality. Consider implementing equals/hashCode (or a dedicated content-comparison method) for more robust comparisons, since two icons differing only after the 100th character of vectorXml would appear equal via toString().

Suggested fix for the trailing ellipsis
     override fun toString(): String {
-        return "ImageVectorIcon(size=$size, aspectRatio=$aspectRatio, dominantShade=$dominantShade, vectorXml=${vectorXml.take(100)}...)"
+        val xmlPreview = if (vectorXml.length > 100) "${vectorXml.take(100)}..." else vectorXml
+        return "ImageVectorIcon(size=$size, aspectRatio=$aspectRatio, dominantShade=$dominantShade, vectorXml=$xmlPreview)"
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/completion/ImageVectorIcon.kt`
around lines 88 - 90, The toString() implementation of ImageVectorIcon always
appends "..." after vectorXml even when vectorXml.length ≤ 100, and tests rely
on toString() for comparisons which can hide differences beyond 100 chars;
update the toString() method to conditionally append "..." only if
vectorXml.length > 100 and include the full string when shorter, and
additionally implement proper equality semantics by overriding equals() and
hashCode() (or add a dedicated contentEquals(other: ImageVectorIcon) method) to
compare size, aspectRatio, dominantShade and the full vectorXml so tests and
consumers compare actual content rather than a truncated representation.
tools/idea-plugin/src/test/kotlin/io/github/composegears/valkyrie/gutter/ImageVectorGutterTest.kt (2)

116-119: Icon inequality relies on toString() — fragile but acceptable for now.

The comparison uses toString() which truncates vectorXml at 100 chars. If two icons ever differ only beyond that point, this assertion would incorrectly pass. For these specific test cases, the builder names differ ("WithoutPath" vs "MyPath"), so it works.

If you add equals/hashCode to ImageVectorIcon (or a content-based comparison method), the test could use that directly for a more robust assertion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/test/kotlin/io/github/composegears/valkyrie/gutter/ImageVectorGutterTest.kt`
around lines 116 - 119, The test in ImageVectorGutterTest uses toString() on
ImageVectorIcon for inequality (the assertNotEquals comparing
gutterMark1.lineMarkerInfo.icon.cast<ImageVectorIcon>().toString() and
gutterMark2...toString()), which is fragile because toString() truncates
vectorXml; update the test to compare icons by content instead: either
add/enable an equals/hashCode implementation on ImageVectorIcon (or a dedicated
contentEquals method) and use assertNotEquals on the cast icons themselves
(gutterMark1.lineMarkerInfo.icon.cast<ImageVectorIcon>() vs gutterMark2...), or,
if changing the model is unwanted, extract and compare the full vectorXml or
builder name fields from the ImageVectorIcon instances for a deterministic
content-based comparison.

1-12: Mixed assertion libraries — consider consistency.

The tests use three different assertion sources:

  • assertk (assertThat(...).hasSize(...))
  • kotlin.test (assertNotEquals)
  • Inherited JUnit assertions (assertEquals, assertNotNull — presumably from the KotlinCodeInsightTest base class)

This works, but mixing assertion libraries in a single file can be confusing for contributors. Consider unifying on one style (e.g., all assertk or all kotlin.test).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/test/kotlin/io/github/composegears/valkyrie/gutter/ImageVectorGutterTest.kt`
around lines 1 - 12, The test mixes assertion libraries (assertk, kotlin.test,
and JUnit via KotlinCodeInsightTest); pick one (recommend assertk) and make the
file consistent: replace kotlin.test.assertNotEquals and any JUnit-style
assertEquals/assertNotNull usages with assertk equivalents (e.g., isNotEqualTo,
isEqualTo, isNotNull) in ImageVectorGutterTest and update imports to only import
assertk.assertThat and assertk.assertions.* while removing kotlin.test and JUnit
assertion imports; ensure assertions that check collection size use hasSize from
assertk and run tests to confirm no remaining mixed assertions.
sdk/intellij/psi/imagevector/src/main/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/imagevector/parser/RegularImageVectorPsiParser.kt (2)

38-40: parse(ktProperty) silently accepts any KtProperty without a type guard.

Unlike parse(ktFile), which pre-filters properties with typeReference?.text == "ImageVector", this overload performs no such check. It will silently attempt to parse and return null for non-ImageVector properties (likely fine), but a malformed property with a coincidentally matching structure could produce a corrupt IrImageVector. If the call site always pre-filters, at minimum a KDoc contract note would prevent future misuse.

💡 Suggested guard (or document the precondition)
 fun parse(ktProperty: KtProperty): IrImageVector? {
+    require(ktProperty.typeReference?.text == "ImageVector") {
+        "ktProperty must be of type ImageVector"
+    }
     return ktProperty.parseImageVector()
 }

Or, at minimum, document the precondition:

+/** Caller must ensure [ktProperty] has a type reference of `ImageVector`. */
 fun parse(ktProperty: KtProperty): IrImageVector? {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/intellij/psi/imagevector/src/main/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/imagevector/parser/RegularImageVectorPsiParser.kt`
around lines 38 - 40, The parse(ktProperty: KtProperty) overload lacks a type
guard and may attempt to parse non-ImageVector properties; update
parse(ktProperty) to first check the property's typeReference (e.g.
ktProperty.typeReference?.text == "ImageVector") and return null (or throw) if
it doesn't match, or alternatively add a clear KDoc on parse(ktProperty) stating
the precondition that callers must only pass ImageVector-typed KtProperty
instances; reference parseFile/parse(ktFile) which already filters by type and
ensure parseImageVector is only invoked when the type guard passes.

42-42: Consider making KtProperty.parseImageVector() private for tighter encapsulation.

The extension is only used within RegularImageVectorPsiParser and is not shared with other parsers (e.g., MaterialImageVectorPsiParser defines its own separate implementation). Using private instead of internal restricts visibility to the immediate scope where it's needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/intellij/psi/imagevector/src/main/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/imagevector/parser/RegularImageVectorPsiParser.kt`
at line 42, The extension function KtProperty.parseImageVector() is declared
internal but only used inside RegularImageVectorPsiParser; change its visibility
to private to tighten encapsulation by replacing the internal modifier with
private on the KtProperty.parseImageVector() declaration and verify there are no
external references (e.g., other parser classes like
MaterialImageVectorPsiParser) before committing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@sdk/intellij/psi/imagevector/src/main/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/imagevector/ImageVectorPsiParser.kt`:
- Around line 21-29: The KtProperty overload parseToIrImageVector(ktProperty)
treats a null containingKtFile.importList as isMaterial = false and proceeds to
parse, which is inconsistent with the KtFile overload that returns null when
importList is missing; change the importList handling in
parseToIrImageVector(ktProperty) to short-circuit and return null when
containingKtFile.importList is null (e.g., use
containingKtFile.importList?.isMaterial() ?: return null) so both overloads
behave consistently before calling MaterialImageVectorPsiParser.parse or
RegularImageVectorPsiParser.parse.

In `@tools/idea-plugin/build.gradle.kts`:
- Around line 86-87: The test task in build.gradle.kts is not configured to run
JUnit5, so add configuration to the Gradle test task to call useJUnitPlatform()
(i.e. configure the Test task named "test" to invoke useJUnitPlatform()) so
JUnit5 tests are discovered and executed; locate the test task declaration or
add a test { useJUnitPlatform() } block in build.gradle.kts and verify any prior
IJPL workarounds (opentest4j/IJPL-157292, JUnit4 runtime/IJPL-159134) are still
needed with the current IntelliJ Platform Gradle Plugin version.

---

Nitpick comments:
In
`@sdk/intellij/psi/imagevector/src/main/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/imagevector/parser/RegularImageVectorPsiParser.kt`:
- Around line 38-40: The parse(ktProperty: KtProperty) overload lacks a type
guard and may attempt to parse non-ImageVector properties; update
parse(ktProperty) to first check the property's typeReference (e.g.
ktProperty.typeReference?.text == "ImageVector") and return null (or throw) if
it doesn't match, or alternatively add a clear KDoc on parse(ktProperty) stating
the precondition that callers must only pass ImageVector-typed KtProperty
instances; reference parseFile/parse(ktFile) which already filters by type and
ensure parseImageVector is only invoked when the type guard passes.
- Line 42: The extension function KtProperty.parseImageVector() is declared
internal but only used inside RegularImageVectorPsiParser; change its visibility
to private to tighten encapsulation by replacing the internal modifier with
private on the KtProperty.parseImageVector() declaration and verify there are no
external references (e.g., other parser classes like
MaterialImageVectorPsiParser) before committing.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/completion/ImageVectorIcon.kt`:
- Around line 88-90: The toString() implementation of ImageVectorIcon always
appends "..." after vectorXml even when vectorXml.length ≤ 100, and tests rely
on toString() for comparisons which can hide differences beyond 100 chars;
update the toString() method to conditionally append "..." only if
vectorXml.length > 100 and include the full string when shorter, and
additionally implement proper equality semantics by overriding equals() and
hashCode() (or add a dedicated contentEquals(other: ImageVectorIcon) method) to
compare size, aspectRatio, dominantShade and the full vectorXml so tests and
consumers compare actual content rather than a truncated representation.

In
`@tools/idea-plugin/src/test/kotlin/io/github/composegears/valkyrie/gutter/ImageVectorGutterTest.kt`:
- Around line 116-119: The test in ImageVectorGutterTest uses toString() on
ImageVectorIcon for inequality (the assertNotEquals comparing
gutterMark1.lineMarkerInfo.icon.cast<ImageVectorIcon>().toString() and
gutterMark2...toString()), which is fragile because toString() truncates
vectorXml; update the test to compare icons by content instead: either
add/enable an equals/hashCode implementation on ImageVectorIcon (or a dedicated
contentEquals method) and use assertNotEquals on the cast icons themselves
(gutterMark1.lineMarkerInfo.icon.cast<ImageVectorIcon>() vs gutterMark2...), or,
if changing the model is unwanted, extract and compare the full vectorXml or
builder name fields from the ImageVectorIcon instances for a deterministic
content-based comparison.
- Around line 1-12: The test mixes assertion libraries (assertk, kotlin.test,
and JUnit via KotlinCodeInsightTest); pick one (recommend assertk) and make the
file consistent: replace kotlin.test.assertNotEquals and any JUnit-style
assertEquals/assertNotNull usages with assertk equivalents (e.g., isNotEqualTo,
isEqualTo, isNotNull) in ImageVectorGutterTest and update imports to only import
assertk.assertThat and assertk.assertions.* while removing kotlin.test and JUnit
assertion imports; ensure assertions that check collection size use hasSize from
assertk and run tests to confirm no remaining mixed assertions.

@egorikftp egorikftp force-pushed the feature/idea/gutter branch 2 times, most recently from 76d7ef0 to 1c2e4b1 Compare February 22, 2026 18:17
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/util/ImageVectorPsiUtil.kt (1)

84-93: ⚠️ Potential issue | 🟠 Major

Fallback still uses file-level parsing — wrong icon returned for non-first library properties.

navigationElement is already confirmed to be a KtProperty at line 86. Calling parseToIrImageVector(sourceFile) at line 91 always resolves to the first ImageVector property in the library source file, so any non-first library icon resolves to the wrong gutter icon. Since parseToIrImageVector(KtProperty) now exists, use it directly.

🐛 Proposed fix
         if (navigationElement is KtProperty && navigationElement != property) {
             val sourceFile = navigationElement.containingKtFile

             // Only parse if we have actual source code (not a stub)
             if (COMPILED_CODE_MARKER !in sourceFile.text) {
-                return ImageVectorPsiParser.parseToIrImageVector(sourceFile)
+                return ImageVectorPsiParser.parseToIrImageVector(navigationElement)
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/util/ImageVectorPsiUtil.kt`
around lines 84 - 93, The current fallback parses the whole source file via
ImageVectorPsiParser.parseToIrImageVector(sourceFile) which always returns the
first ImageVector in the file; instead, when navigationElement is a KtProperty
(we've already checked this) call
ImageVectorPsiParser.parseToIrImageVector(navigationElement as KtProperty) so
the parser targets that specific property; update the block around
navigationElement / KtProperty / property to return the result of
parseToIrImageVector(navigationElement) rather than
parseToIrImageVector(sourceFile).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/completion/ImageVectorIcon.kt`:
- Around line 88-90: The ImageVectorIcon.toString() always appends "..." after
vectorXml.take(100) even when vectorXml is shorter than 100 chars; update
toString() in class ImageVectorIcon to conditionally append "..." only when
vectorXml.length > 100 (or use takeLast/substring logic) so the representation
shows the full vectorXml when short; reference the toString() method, the
vectorXml property and existing fields size, aspectRatio, dominantShade when
implementing the conditional truncation.

---

Outside diff comments:
In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/util/ImageVectorPsiUtil.kt`:
- Around line 84-93: The current fallback parses the whole source file via
ImageVectorPsiParser.parseToIrImageVector(sourceFile) which always returns the
first ImageVector in the file; instead, when navigationElement is a KtProperty
(we've already checked this) call
ImageVectorPsiParser.parseToIrImageVector(navigationElement as KtProperty) so
the parser targets that specific property; update the block around
navigationElement / KtProperty / property to return the result of
parseToIrImageVector(navigationElement) rather than
parseToIrImageVector(sourceFile).

@egorikftp egorikftp force-pushed the feature/idea/gutter branch 2 times, most recently from be55b85 to 4167455 Compare February 24, 2026 14:52
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tools/idea-plugin/build.gradle.kts`:
- Around line 74-75: Remove the redundant explicit JUnit5 dependencies by
deleting the testImplementation(libs.junit5.jupiter) and
testRuntimeOnly(libs.junit.launcher) declarations; the project already
configures JUnit5 via testFramework(TestFrameworkType.JUnit5) so leaving those
two entries causes duplicate JUnit classes on the test classpath—locate and
remove the lines that call testImplementation and testRuntimeOnly for junit5 to
rely on the IDE plugin-managed JUnit setup.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1c2e4b1 and 4167455.

📒 Files selected for processing (10)
  • sdk/intellij/psi/imagevector/api/imagevector.api
  • sdk/intellij/psi/imagevector/src/main/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/imagevector/ImageVectorPsiParser.kt
  • sdk/intellij/psi/imagevector/src/main/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/imagevector/parser/MaterialImageVectorPsiParser.kt
  • sdk/intellij/psi/imagevector/src/main/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/imagevector/parser/RegularImageVectorPsiParser.kt
  • tools/idea-plugin/CHANGELOG.md
  • tools/idea-plugin/build.gradle.kts
  • tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/completion/ImageVectorIcon.kt
  • tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/icon/ImageVectorIconProvider.kt
  • tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/util/ImageVectorPsiUtil.kt
  • tools/idea-plugin/src/test/kotlin/io/github/composegears/valkyrie/gutter/ImageVectorGutterTest.kt
🚧 Files skipped from review as they are similar to previous changes (4)
  • tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/completion/ImageVectorIcon.kt
  • sdk/intellij/psi/imagevector/src/main/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/imagevector/parser/RegularImageVectorPsiParser.kt
  • tools/idea-plugin/CHANGELOG.md
  • tools/idea-plugin/src/test/kotlin/io/github/composegears/valkyrie/gutter/ImageVectorGutterTest.kt

@egorikftp egorikftp requested a review from t-regbs February 24, 2026 17:56
@egorikftp egorikftp merged commit f7cde34 into main Feb 25, 2026
3 checks passed
@egorikftp egorikftp deleted the feature/idea/gutter branch February 25, 2026 09:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[IntelliJ] [Gutter] Add support for multiple icons in a file

2 participants