From 7129bbcce749439f6a3ea76289d5c73cae2da52f Mon Sep 17 00:00:00 2001 From: Artemiy Sartakov Date: Tue, 15 Oct 2019 18:12:27 +0700 Subject: [PATCH] JavaEnterInTextBlockHandler: new line on third open quote, preserve indent when inserting line break at block start (IDEA-CR-53311) GitOrigin-RevId: 6d919937a6624f6a05e0386c8f10c5ba26bce249 --- .../JavaEnterInTextBlockHandler.java | 118 ++++++++---------- .../editorActions/JavaQuoteHandler.java | 13 +- .../contentAtTheEndOfTextBlock_after.java | 4 +- .../onlyWhitespacesTextBlock_after.java | 4 +- .../editorActions/JavaQuoteTest.groovy | 4 +- 5 files changed, 73 insertions(+), 70 deletions(-) diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaEnterInTextBlockHandler.java b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaEnterInTextBlockHandler.java index 9cf0932d7dec..9c8169ef2982 100644 --- a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaEnterInTextBlockHandler.java +++ b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaEnterInTextBlockHandler.java @@ -5,87 +5,79 @@ import com.intellij.codeInsight.editorActions.enter.EnterInStringLiteralHandler; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.LogicalPosition; -import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.editor.actionSystem.EditorActionHandler; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl; import com.intellij.util.ObjectUtils; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; public class JavaEnterInTextBlockHandler extends EnterInStringLiteralHandler { @Override - public Result postProcessEnter(@NotNull PsiFile file, @NotNull Editor editor, @NotNull DataContext dataContext) { - if (!(file instanceof PsiJavaFile)) return super.postProcessEnter(file, editor, dataContext); - PsiDocumentManager.getInstance(file.getProject()).commitDocument(editor.getDocument()); - PsiLiteralExpressionImpl textBlock = getTextBlock(file, editor.getCaretModel().getOffset()); - if (textBlock == null) return super.postProcessEnter(file, editor, dataContext); + public Result preprocessEnter(@NotNull PsiFile file, + @NotNull Editor editor, + @NotNull Ref caretOffsetRef, + @NotNull Ref caretAdvanceRef, + @NotNull DataContext dataContext, + EditorActionHandler originalHandler) { int offset = editor.getCaretModel().getOffset(); + PsiLiteralExpressionImpl textBlock = getTextBlockAt(file, offset); + if (textBlock == null) return Result.Continue; + int textBlockOffset = textBlock.getTextOffset(); + String text = textBlock.getText(); + int offsetInTextBlock = offset - textBlockOffset; + boolean isAtFirstLine = !text.substring(0, offsetInTextBlock).contains("\n"); + if (!isAtFirstLine) return Result.Continue; Document document = editor.getDocument(); - if (isContentAtTheEnd(textBlock)) { - CodeStyleManager.getInstance(textBlock.getProject()).reformat(textBlock); - offset = editor.getCaretModel().getOffset(); + Project project = textBlock.getProject(); + int secondLineStart = text.indexOf('\n'); + if (secondLineStart == -1) { + document.insertString(offset, "\n"); + PsiDocumentManager.getInstance(project).commitDocument(document); + CodeStyleManager.getInstance(project).reformat(textBlock); + text = textBlock.getText(); + int indent = getIndent(text, offsetInTextBlock + 1); + if (indent == -1) return Result.Continue; + editor.getCaretModel().moveToOffset(offset + 1 + indent); } - else if (!isAtBlockStart(document, textBlock, offset)) { - return super.postProcessEnter(file, editor, dataContext); + else { + int indent = getIndent(text, secondLineStart + 1); + if (indent == -1) return Result.Continue; + String newLine = '\n' + StringUtil.repeatSymbol(' ', indent); + document.insertString(offset, newLine); + PsiDocumentManager.getInstance(project).commitDocument(document); + editor.getCaretModel().moveToOffset(offset + newLine.length()); } - editor.getCaretModel().moveToOffset(findOffset(editor, document, textBlock, offset)); - return Result.Continue; + return Result.Stop; } - private static boolean isContentAtTheEnd(@NotNull PsiLiteralExpressionImpl textBlock) { - String text = textBlock.getTextBlockText(); - if (text == null) return false; - boolean foundContent = false; - for (int i = 0; i < text.length(); i++) { - if (!isWhitespaceOrNewLine(text.charAt(i))) { - foundContent = true; - } - else if (foundContent) { - return false; - } - } - return true; - } - - private static boolean isAtBlockStart(@NotNull Document document, @NotNull PsiLiteralExpressionImpl textBlock, int offset) { - String text = document.getText(new TextRange(textBlock.getTextOffset(), offset)); - if (!text.startsWith("\"\"\"")) return false; - text = text.substring(3); - return text.chars().allMatch(JavaEnterInTextBlockHandler::isWhitespaceOrNewLine); - } - - private static boolean isWhitespaceOrNewLine(int c) { - return Character.isWhitespace(c) || c == '\n'; - } - - private static int findOffset(@NotNull Editor editor, - @NotNull Document document, - @NotNull PsiLiteralExpressionImpl textBlock, - int offset) { - TextRange afterOffset = new TextRange(offset, textBlock.getTextRange().getEndOffset()); - String text = document.getText(afterOffset); - int line = document.getLineNumber(offset); - int lineStart = document.getLineStartOffset(line); - int start = text.indexOf('\n') + 1; - for (int i = start; i < text.length(); i++) { - char c = text.charAt(i); - if (isWhitespaceOrNewLine(c)) continue; - int column = editor.offsetToLogicalPosition(offset + i).column; - String indent = StringUtil.repeatSymbol(' ', column); - document.replaceString(lineStart, offset, indent); - return editor.logicalPositionToOffset(new LogicalPosition(line, column)); - } - return offset; - } - - @Nullable - private static PsiLiteralExpressionImpl getTextBlock(@NotNull PsiFile file, int offset) { + @Contract("null, _ -> null") + private static PsiLiteralExpressionImpl getTextBlockAt(PsiFile file, int offset) { + if (!(file instanceof PsiJavaFile)) return null; PsiJavaToken token = ObjectUtils.tryCast(file.findElementAt(offset), PsiJavaToken.class); if (token == null || token.getTokenType() != JavaTokenType.TEXT_BLOCK_LITERAL) return null; return ObjectUtils.tryCast(token.getParent(), PsiLiteralExpressionImpl.class); } + + private static int getIndent(@NotNull String text, int start) { + int indent = 0; + for (int i = start; i < text.length(); i++) { + char c = text.charAt(i); + if (c == '\n') { + indent = 0; + continue; + } + if (Character.isWhitespace(c)) { + indent++; + continue; + } + return indent; + } + return -1; + } } diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaQuoteHandler.java b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaQuoteHandler.java index c6d127697fac..9f7d484a9c8f 100644 --- a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaQuoteHandler.java +++ b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaQuoteHandler.java @@ -4,11 +4,14 @@ package com.intellij.codeInsight.editorActions; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.highlighter.HighlighterIterator; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; +import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.impl.source.tree.ElementType; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; +import com.intellij.util.ObjectUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -116,6 +119,14 @@ public class JavaQuoteHandler extends SimpleTokenSetQuoteHandler implements Java @Override public void insertClosingQuote(@NotNull Editor editor, int offset, @NotNull PsiFile file, @NotNull CharSequence closingQuote) { - editor.getDocument().insertString(offset, "\"\"\""); + editor.getDocument().insertString(offset, "\n\"\"\""); + Project project = file.getProject(); + PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); + PsiJavaToken token = ObjectUtils.tryCast(file.findElementAt(offset), PsiJavaToken.class); + if (token == null) return; + PsiLiteralExpression textBlock = ObjectUtils.tryCast(token.getParent(), PsiLiteralExpression.class); + if (textBlock == null) return; + CodeStyleManager.getInstance(project).reformat(textBlock); + editor.getCaretModel().moveToOffset(textBlock.getTextRange().getEndOffset() - 3); } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/editorActions/stringLiteral/contentAtTheEndOfTextBlock_after.java b/java/java-tests/testData/codeInsight/editorActions/stringLiteral/contentAtTheEndOfTextBlock_after.java index b9557ce80290..02757a4bb5bc 100644 --- a/java/java-tests/testData/codeInsight/editorActions/stringLiteral/contentAtTheEndOfTextBlock_after.java +++ b/java/java-tests/testData/codeInsight/editorActions/stringLiteral/contentAtTheEndOfTextBlock_after.java @@ -2,8 +2,8 @@ class Test { void test() { String block = """ - - text"""; + + text"""; } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/editorActions/stringLiteral/onlyWhitespacesTextBlock_after.java b/java/java-tests/testData/codeInsight/editorActions/stringLiteral/onlyWhitespacesTextBlock_after.java index 6dea07989a05..f077ef1deb4f 100644 --- a/java/java-tests/testData/codeInsight/editorActions/stringLiteral/onlyWhitespacesTextBlock_after.java +++ b/java/java-tests/testData/codeInsight/editorActions/stringLiteral/onlyWhitespacesTextBlock_after.java @@ -1,10 +1,10 @@ class Test { void test() { String block = """ - + - """; + """; } } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/editorActions/JavaQuoteTest.groovy b/java/java-tests/testSrc/com/intellij/java/codeInsight/editorActions/JavaQuoteTest.groovy index 3e46d8ab1fc1..4d57f7884119 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/editorActions/JavaQuoteTest.groovy +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/editorActions/JavaQuoteTest.groovy @@ -23,7 +23,7 @@ class JavaQuoteTest extends LightJavaCodeInsightFixtureTestCase { void testSingleInComment() { doTest '/* */', '/* \' */', "'" as char } void testSingleInStringAfterEscape() { doTest ''' split(text, '\\); ''', ''' split(text, '\\'); ''', "'" as char } - void testTextBlock() { doTest ' "" ', ' """""" ' } + void testTextBlock() { doTest '"" ', ' """\n """ ' } void testDoubleQuoteInTextBlock() { doTest ' """ """ ', ' """ " """ ' } void testSingleQuoteInTextBlock() { doTest ' """ """ ', ' """ \' """ ', "'" as char } void testTextBlockClosing() { @@ -31,7 +31,7 @@ class JavaQuoteTest extends LightJavaCodeInsightFixtureTestCase { doTest ' """.""" ', ' """.""" ' doTest ' """.""" ', ' """.""" ' } - void testPrecedingTextBlock() { doTest 'f("" + """\n .""")', 'f("""""" + """\n .""")' } + void testPrecedingTextBlock() { doTest 'f("" + """\n .""")', 'f("""\n """ + """\n .""")' } private void doTest(String before, String after, char c = '"') { myFixture.configureByText("a.java", "class C {{\n ${before}\n}}")