JavaEnterInTextBlockHandler: new line on third open quote, preserve indent when inserting line break at block start (IDEA-CR-53311)

GitOrigin-RevId: 6d919937a6624f6a05e0386c8f10c5ba26bce249
This commit is contained in:
Artemiy Sartakov
2019-10-15 13:40:27 +00:00
committed by intellij-monorepo-bot
parent 443204183c
commit 7129bbcce7
5 changed files with 73 additions and 70 deletions
@@ -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<Integer> caretOffsetRef,
@NotNull Ref<Integer> 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;
}
}
@@ -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);
}
}
@@ -2,8 +2,8 @@ class Test {
void test() {
String block = """
<caret>
text""";
<caret>
text""";
}
}
@@ -1,10 +1,10 @@
class Test {
void test() {
String block = """
<caret>
<caret>
""";
""";
}
}
@@ -23,7 +23,7 @@ class JavaQuoteTest extends LightJavaCodeInsightFixtureTestCase {
void testSingleInComment() { doTest '/* <caret> */', '/* \'<caret> */', "'" as char }
void testSingleInStringAfterEscape() { doTest ''' split(text, '\\<caret>); ''', ''' split(text, '\\'<caret>); ''', "'" as char }
void testTextBlock() { doTest ' ""<caret> ', ' """<caret>""" ' }
void testTextBlock() { doTest '""<caret> ', ' """\n <caret>""" ' }
void testDoubleQuoteInTextBlock() { doTest ' """ <caret> """ ', ' """ "<caret> """ ' }
void testSingleQuoteInTextBlock() { doTest ' """ <caret> """ ', ' """ \'<caret> """ ', "'" as char }
void testTextBlockClosing() {
@@ -31,7 +31,7 @@ class JavaQuoteTest extends LightJavaCodeInsightFixtureTestCase {
doTest ' """."<caret>"" ', ' """.""<caret>" '
doTest ' """.""<caret>" ', ' """."""<caret> '
}
void testPrecedingTextBlock() { doTest 'f(""<caret> + """\n .""")', 'f("""<caret>""" + """\n .""")' }
void testPrecedingTextBlock() { doTest 'f(""<caret> + """\n .""")', 'f("""\n <caret>""" + """\n .""")' }
private void doTest(String before, String after, char c = '"') {
myFixture.configureByText("a.java", "class C {{\n ${before}\n}}")