diff --git a/plugin/core/src/main/java/com/perl5/lang/perl/idea/manipulators/PerlHeredocElementManipulator.java b/plugin/core/src/main/java/com/perl5/lang/perl/idea/manipulators/PerlHeredocElementManipulator.java index 3e9080697e..a2dd58d0e7 100644 --- a/plugin/core/src/main/java/com/perl5/lang/perl/idea/manipulators/PerlHeredocElementManipulator.java +++ b/plugin/core/src/main/java/com/perl5/lang/perl/idea/manipulators/PerlHeredocElementManipulator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 Alexandr Evstigneev + * Copyright 2015-2024 Alexandr Evstigneev * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,10 @@ package com.perl5.lang.perl.idea.manipulators; -import com.intellij.application.options.CodeStyle; -import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.AbstractElementManipulator; -import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.util.IncorrectOperationException; -import com.perl5.lang.perl.PerlLanguage; import com.perl5.lang.perl.psi.impl.PerlHeredocElementImpl; import com.perl5.lang.perl.psi.utils.PerlElementFactory; import org.jetbrains.annotations.NotNull; @@ -34,58 +30,29 @@ public class PerlHeredocElementManipulator extends AbstractElementManipulator 0) { - sb.insert(0, getIndenter(project, range.getStartOffset())); - range = TextRange.from(0, range.getEndOffset()); - } + var lineStart = StringUtil.skipWhitespaceBackward(elementText, range.getStartOffset() - 1); + if (lineStart < range.getStartOffset()) { + indent = elementText.substring(lineStart, range.getStartOffset()); + range = TextRange.create(lineStart, range.getEndOffset()); - normalizeIndentation(project, sb, element.getRealIndentSize()); + newContent = prependLines(newContent, indent); + } + } - String newElementText = range.replace(element.getText(), sb.toString()); + String newElementText = range.replace(elementText, newContent); PerlHeredocElementImpl replacement = PerlElementFactory.createHeredocBodyReplacement(element, newElementText); return (PerlHeredocElementImpl)element.replace(replacement); } - private static @NotNull String getIndenter(@NotNull Project project, int indentSize) { - CommonCodeStyleSettings.IndentOptions indentOptions = - CodeStyle.getSettings(project).getCommonSettings(PerlLanguage.INSTANCE).getIndentOptions(); - - return StringUtil.repeat(indentOptions != null && indentOptions.USE_TAB_CHARACTER ? "\t" : " ", indentSize); - } - - private static void normalizeIndentation(@NotNull Project project, @NotNull StringBuilder sb, int indentSize) { - int offset = 0; - int currentLineStart = 0; - int currentLineIndentSize = 0; - boolean hasNonSpaces = false; - - while (offset < sb.length()) { - char currentChar = sb.charAt(offset++); - if (currentChar == '\n') { - if (hasNonSpaces && currentLineIndentSize < indentSize) { - int indentAdjustment = indentSize - currentLineIndentSize; - sb.insert(currentLineStart, getIndenter(project, indentAdjustment)); - offset += indentAdjustment; - } - currentLineStart = offset; - hasNonSpaces = false; - currentLineIndentSize = 0; - } - else if (!hasNonSpaces) { - if (Character.isWhitespace(currentChar)) { - currentLineIndentSize++; - } - else { - hasNonSpaces = true; - } - } - } + private static @NotNull String prependLines(@NotNull String newContent, @NotNull String prefix) { + return prefix + String.join(prefix, StringUtil.split(newContent, "\n", false, true)); } } diff --git a/plugin/src/test/java/intellilang/PerlQuickEditTest.kt b/plugin/src/test/java/intellilang/PerlQuickEditTest.kt index 0b62c8bec1..db1157bbf2 100644 --- a/plugin/src/test/java/intellilang/PerlQuickEditTest.kt +++ b/plugin/src/test/java/intellilang/PerlQuickEditTest.kt @@ -18,6 +18,8 @@ package intellilang import base.PerlLightTestCase import com.intellij.codeInsight.intention.impl.QuickEditAction +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiFile import com.intellij.testFramework.fixtures.InjectionTestFixture import org.jetbrains.annotations.NonNls import org.junit.Test @@ -27,42 +29,88 @@ class PerlQuickEditTest : PerlLightTestCase() { private val injectionTestFixture: InjectionTestFixture get() = InjectionTestFixture(myFixture) @Test - fun testEditHeredoc() { - initWithTextSmart( + fun testEditHeredocMiddle() { + val (originalEditor, fragmentFile) = initFileWithTestSample() + + myFixture.editor.caretModel.moveToOffset(fragmentFile.text.indexAfter("")) + myFixture.type("\nhello there") + assertFalse(myFixture.editor.isDisposed) + assertEquals( + """ +
+ + hello there + +
""".trimIndent(), myFixture.editor.document.text.trim().replace(Regex("[ \t]+\n"), "\n") + ) + + assertEquals( """ use v5.36; sub foo{ say <<~HTML;
- + + hello there
HTML - }""".trimIndent() + }""".trimIndent(), originalEditor.document.text ) - val originalEditor = injectionTestFixture.topLevelEditor - val quickEditHandler = QuickEditAction().invokeImpl(project, injectionTestFixture.topLevelEditor, injectionTestFixture.topLevelFile) - val fragmentFile = quickEditHandler.newFile + } + + @Test + fun testEditHeredocEnd() { + val (originalEditor, fragmentFile) = initFileWithTestSample() + + myFixture.editor.caretModel.moveToOffset(fragmentFile.text.indexAfter("")) + myFixture.type("\n\n\nhello there\n") + assertFalse(myFixture.editor.isDisposed) assertEquals( """
- """.trimIndent(), fragmentFile.text.trim() + + + hello there + """.trimIndent(), myFixture.editor.document.text.trim().replace(Regex("[ \t]+\n"), "\n") ) - myFixture.openFileInEditor(fragmentFile.virtualFile) + assertEquals( + """ + use v5.36; + + sub foo{ + say <<~HTML; +
+ + +
+ + + hello there + + HTML + }""".trimIndent(), originalEditor.document.text + ) + } - myFixture.editor.caretModel.moveToOffset(fragmentFile.text.indexAfter("")) - myFixture.type("\nhello there") + @Test + fun testEditHeredocStart() { + val (originalEditor, fragmentFile) = initFileWithTestSample() + + myFixture.editor.caretModel.moveToOffset(fragmentFile.text.indexOf("
")) + myFixture.type("\n\n\nhello there\n\n") assertFalse(myFixture.editor.isDisposed) assertEquals( """ + hello there +
- hello there
""".trimIndent(), myFixture.editor.document.text.trim().replace(Regex("[ \t]+\n"), "\n") ) @@ -73,9 +121,13 @@ class PerlQuickEditTest : PerlLightTestCase() { sub foo{ say <<~HTML; + + + + hello there +
- hello there
HTML @@ -83,6 +135,36 @@ class PerlQuickEditTest : PerlLightTestCase() { ) } + private fun initFileWithTestSample(): Pair { + initWithTextSmart( + """ + use v5.36; + + sub foo{ + say <<~HTML; +
+ + +
+ HTML + }""".trimIndent() + ) + val originalEditor = injectionTestFixture.topLevelEditor + val quickEditHandler = QuickEditAction().invokeImpl(project, injectionTestFixture.topLevelEditor, injectionTestFixture.topLevelFile) + val fragmentFile = quickEditHandler.newFile + assertEquals( + """ +
+ + +
+ """.trimIndent(), fragmentFile.text.trim() + ) + + myFixture.openFileInEditor(fragmentFile.virtualFile) + return Pair(originalEditor, fragmentFile) + } + private fun String.indexAfter(string: String): Int { val r = indexOf(string) return if (r == -1) -1 else r + string.length