IDEA-358678 Properly compute injection start

- First newline character
- First non-space character
- Any character after indentSize space characters

GitOrigin-RevId: 80f413bb01cf784ed0f9266af35cc503a0f9f05f
This commit is contained in:
Alexandr Evstigneev
2024-09-02 12:48:03 +04:00
committed by intellij-monorepo-bot
parent 6c46345ea6
commit fe9254efb5
3 changed files with 123 additions and 5 deletions

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.impl.source.tree.injected
import com.intellij.codeInsight.intention.impl.QuickEditAction
@@ -396,6 +396,67 @@ class JavaInjectedFileChangesHandlerTest : JavaCodeInsightFixtureTestCase() {
}
fun `test indented update`() {
myFixture.configureByText("Test.java", """
import org.intellij.lang.annotations.*;
class Hello {
void test() {
createClass(""${'"'}
class Foo {
static void foo(int a) {}<caret>
static void foo(int a, int b) {}
}""${'"'});
}
private static void createClass(@Language("JAVA") String text){};
}""".trimIndent())
val originalEditor = injectionTestFixture.topLevelEditor
val quickEditHandler = QuickEditAction().invokeImpl(project, injectionTestFixture.topLevelEditor, injectionTestFixture.topLevelFile)
val fragmentFile = quickEditHandler.newFile
TestCase.assertEquals("""
class Foo {
static void foo(int a) {}
static void foo(int a, int b) {}
}
""".trimIndent(), fragmentFile.text)
myFixture.openFileInEditor(fragmentFile.virtualFile)
myFixture.editor.caretModel.moveToOffset(fragmentFile.text.indexAfter("foo(int a) {}"))
myFixture.type("\n\n\n")
TestCase.assertEquals("""
class Foo {
static void foo(int a) {}
static void foo(int a, int b) {}
}
""".trimIndent(), myFixture.editor.document.text.replace(Regex("[ \t]+\n"), "\n"))
TestCase.assertEquals("""
import org.intellij.lang.annotations.*;
class Hello {
void test() {
createClass(""${'"'}
class Foo {
static void foo(int a) {}
\s
\s
\s
static void foo(int a, int b) {}
}""${'"'});
}
private static void createClass(@Language("JAVA") String text){};
}""".trimIndent(), originalEditor.document.text)
}
fun `test suffix-prefix-edit-reformat`() {
with(myFixture) {

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.intellij.plugins.intelliLang
import com.intellij.lang.Language
@@ -351,6 +351,37 @@ class JavaLanguageInjectionSupportTest : AbstractLanguageInjectionTestCase() {
}
}""".trimIndent())
}
fun testJavaInjectionWithLeadingNewLines() {
myFixture.configureByText("Test.java", """
import org.intellij.lang.annotations.*;
class Hello {
void test() {
createClass(""${'"'}
class Foo {
static void foo(int a) {}
static void foo(int a, int b) {}
}""${'"'});
}
private static void createClass(@Language("JAVA") String text){};
}""".trimIndent())
myFixture.checkHighlighting()
injectionTestFixture.assertInjectedContent("""
class Foo {
static void foo(int a) {}
static void foo(int a, int b) {}
}""".trimIndent())
}
fun testRegexJsonNotSingle() {
Configuration.getInstance().withInjections(listOf(jsonToPrintlnInjection().apply {

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.intellij.plugins.intelliLang.inject.java;
import com.intellij.lang.Language;
@@ -429,8 +429,7 @@ public final class ConcatenationInjector implements ConcatenationAwareInjector {
}
final String text = host.getText();
boolean noIndent = host instanceof PsiFragment fragment && fragment.getTokenType() != JavaTokenType.TEXT_BLOCK_TEMPLATE_BEGIN;
int startOffset = textRange.getStartOffset() + (noIndent ? 0 : indent);
var startOffset = computeInjectionStartOffset(host, textRange, indent, text);
int endOffset = text.indexOf('\n', startOffset);
final List<TextRange> result = new SmartList<>();
while (endOffset > 0) {
@@ -472,6 +471,33 @@ public final class ConcatenationInjector implements ConcatenationAwareInjector {
}
}
/**
* Injection starts from:<ul>
* <li>First newline character</li>
* <li>First non-space character</li>
* <li>Any character after {@code indentSize} space characters</li>
* </ul>
*/
private static int computeInjectionStartOffset(@NotNull PsiLanguageInjectionHost host,
@NotNull TextRange textRange,
int indentSize,
@NotNull String hostText) {
int startOffset = textRange.getStartOffset();
boolean noIndent = host instanceof PsiFragment fragment && fragment.getTokenType() != JavaTokenType.TEXT_BLOCK_TEMPLATE_BEGIN;
if (!noIndent) {
int firstLineIndent = 0;
while (startOffset < textRange.getEndOffset() && firstLineIndent < indentSize) {
char currentCharacter = hostText.charAt(startOffset);
if (currentCharacter == '\n' || !Character.isWhitespace(currentCharacter)) {
break;
}
startOffset++;
firstLineIndent++;
}
}
return startOffset;
}
private static boolean checkUnparsableReference(PsiExpression refExpression) {
PsiElement parent = refExpression.getParent();
if (parent instanceof PsiAssignmentExpression assignmentExpression) {