Skip to content

Conversation

@claude
Copy link

@claude claude bot commented Dec 29, 2025

Summary

Fixes VIM-4030: The % motion doesn't work when {} are separated by quotes.

  • Implements PSI-based string literal detection in IjVimPsiService that correctly identifies when the cursor is inside a string literal
  • Works across different languages (Java, Kotlin, Rust, etc.) by checking element type names for common string literal patterns
  • Falls back to text-based detection when PSI is not available (e.g., plain text files)
  • Fixes % motion, va{, and vi{ text objects to correctly ignore braces inside string literals

Test plan

  • Added tests for % motion with braces inside Java string literals
  • Added tests for % motion with braces inside Java text blocks
  • Added tests for va{ with braces inside string literals
  • Added tests for vi{ with braces inside string literals
  • Updated existing disabled test with clarification comment
  • Verified existing tests pass (no regressions)

Workflow run

https://github.com/JetBrains/ideavim/actions/runs/20569002980

🤖 Generated with Claude Code

The % motion and block text objects (va{, vi{) now correctly identify
matching braces when braces appear inside string literals. The fix
implements PSI-based string detection that works across different
languages (Java, Kotlin, Rust, etc.) by checking element type names.

Falls back to text-based detection when PSI is not available.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
!elementType.contains("INTEGER", ignoreCase = true) &&
!elementType.contains("FLOAT", ignoreCase = true) &&
!elementType.contains("DOUBLE", ignoreCase = true) &&
!elementType.contains("CHAR_LITERAL", ignoreCase = true))
Copy link
Author

Choose a reason for hiding this comment

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

Missing CHAR exclusion. CHAR_LITERAL exclusion exists but some languages use just CHAR (e.g., C++). This could incorrectly match character literals.

Comment on lines +112 to +113
return when (quoteChar) {
'"' -> text.startsWith("\"") || text.startsWith("@\"") || text.startsWith("r\"") ||
Copy link
Author

Choose a reason for hiding this comment

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

Rust raw strings like r###"..."### aren't fully handled. The check only looks for r# prefix but Rust allows multiple # characters. findContentEnd also doesn't handle the matching ### suffix - it will only strip a single quote character.

Comment on lines +150 to +151
// Skip language-specific prefixes (r, R, f, b, @, $, etc.)
while (startIndex < text.length && text[startIndex] != '"' && text[startIndex] != '\'') {
Copy link
Author

Choose a reason for hiding this comment

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

Fragile prefix detection. Skips all non-quote characters but this could incorrectly skip past digits in edge cases like 123"text". Should validate that skipped characters are valid prefix characters (letters/symbols only).

Comment on lines +159 to +160
// Check for triple quotes
if (startIndex + 2 < text.length &&
Copy link
Author

Choose a reason for hiding this comment

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

Off-by-one check. Should be startIndex + 2 <= text.length (not <). Current code requires 3 chars total but accesses text[startIndex + 2] which needs startIndex + 3 <= text.length.

Comment on lines +195 to +262
fun `test percent skips brace inside string literal`() {
configureByJavaText(
"""
void test() $c{
String json = "{\"key\":\"value\"}";
}
""".trimIndent()
)
typeText("%")
assertState(
"""
void test() {
String json = "{\"key\":\"value\"}";
$c}
""".trimIndent()
)
}

@Test
@TestFor(issues = ["VIM-4030"])
@TestWithoutNeovim(SkipNeovimReason.PSI)
fun `test percent skips brace inside string with unbalanced quotes`() {
// This test specifically targets the bug: the string contains a closing brace
// preceded by an unescaped quote which confuses text-based detection
configureByJavaText(
"""
void test() $c{
String s = "{\"}";
}
""".trimIndent()
)
typeText("%")
assertState(
"""
void test() {
String s = "{\"}";
$c}
""".trimIndent()
)
}

@Test
@TestFor(issues = ["VIM-4030"])
@TestWithoutNeovim(SkipNeovimReason.PSI)
fun `test percent skips brace inside text block`() {
// Java text blocks (triple-quoted strings) should be recognized via PSI
// The text-based detection would misinterpret the triple quotes
configureByJavaText(
"""
void test() $c{
String json = ${"\"\"\""}
{"key":"value"}
${"\"\"\""};
}
""".trimIndent()
)
typeText("%")
assertState(
"""
void test() {
String json = ${"\"\"\""}
{"key":"value"}
${"\"\"\""};
$c}
""".trimIndent()
)
}

Copy link
Author

Choose a reason for hiding this comment

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

Missing test coverage:

  • Escaped quotes: "{\"}"
  • Nested braces: "{{}}"
  • Empty strings: "{}"
  • Multiple strings on same line
  • Cursor positions (start/middle/end of string)
  • Edge case: string at EOF without newline

@claude
Copy link
Author

claude bot commented Dec 29, 2025

Code Review

Overall: Good solution using PSI for string detection. Fixes the core issue but has several bugs and gaps.

Critical Issues

  1. IjVimPsiService.kt:160 - Off-by-one bounds check will throw IndexOutOfBoundsException
  2. IjVimPsiService.kt:113 - Rust raw strings (r###"..."###) incorrectly handled - only checks prefix, findContentEnd strips wrong suffix length

Quality Issues

  1. IjVimPsiService.kt:106 - Missing CHAR exclusion (only has CHAR_LITERAL)
  2. IjVimPsiService.kt:151 - Prefix detection skips ALL non-quote chars, could skip digits in malformed input

Test Coverage

Tests only cover basic cases. Missing:

  • Escaped quotes in strings
  • Nested braces
  • Empty strings with braces
  • Multiple strings per line
  • Various cursor positions
  • String at EOF

Performance

PSI tree walk on every % motion could be slow in large files. Consider caching or early termination.

Recommendation

Fix critical bugs before merge. Test coverage acceptable for initial fix but should expand.

@AlexPl292 AlexPl292 closed this Jan 16, 2026
@AlexPl292 AlexPl292 deleted the fix/vim-4030-percent-string-literals branch January 16, 2026 11:48
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.

1 participant