mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Implemented IDEABKL-4743 Automatic placement of
This commit is contained in:
@@ -19,6 +19,7 @@ import com.intellij.codeInsight.AutoPopupController;
|
||||
import com.intellij.codeInsight.CodeInsightSettings;
|
||||
import com.intellij.codeInsight.completion.JavaClassReferenceCompletionContributor;
|
||||
import com.intellij.codeInsight.editorActions.smartEnter.JavaSmartEnterProcessor;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.command.CommandProcessor;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
@@ -37,6 +38,7 @@ import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.tree.TokenSet;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.PsiErrorElementUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -108,7 +110,7 @@ public class JavaTypedHandler extends TypedHandlerDelegate {
|
||||
}
|
||||
|
||||
if (c == ';') {
|
||||
if (handleSemicolon(editor, fileType)) return Result.STOP;
|
||||
if (handleSemicolon(editor, file, fileType)) return Result.STOP;
|
||||
}
|
||||
if (fileType == StdFileTypes.JAVA && c == '{') {
|
||||
int offset = editor.getCaretModel().getOffset();
|
||||
@@ -245,11 +247,13 @@ public class JavaTypedHandler extends TypedHandlerDelegate {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean handleSemicolon(Editor editor, FileType fileType) {
|
||||
private static boolean handleSemicolon(@NotNull Editor editor, @NotNull PsiFile file, @NotNull FileType fileType) {
|
||||
if (fileType != StdFileTypes.JAVA) return false;
|
||||
int offset = editor.getCaretModel().getOffset();
|
||||
if (offset == editor.getDocument().getTextLength()) return false;
|
||||
|
||||
if (moveSemicolonAtRParen(editor, file, offset)) return true;
|
||||
|
||||
char charAt = editor.getDocument().getCharsSequence().charAt(offset);
|
||||
if (charAt != ';') return false;
|
||||
|
||||
@@ -260,6 +264,87 @@ public class JavaTypedHandler extends TypedHandlerDelegate {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean moveSemicolonAtRParen(@NotNull Editor editor, @NotNull PsiFile file, int caretOffset) {
|
||||
ApplicationManager.getApplication().assertWriteAccessAllowed();
|
||||
|
||||
// Disable feature if code model is not synchronized with the document.
|
||||
// Note: this feature can be implemented using only lexer because there
|
||||
// are not so many statements which allow semicolon inside them before
|
||||
// closing paren. Therefore, in other cases, if user types semicolon
|
||||
// before rparen, that usually means that he wants to end the statement.
|
||||
if (PsiDocumentManager.getInstance(file.getProject()).isUncommited(editor.getDocument())) {
|
||||
// But enable in unit tests to avoid commiting document after each typing
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
HighlighterIterator it = ((EditorEx)editor).getHighlighter().createIterator(caretOffset);
|
||||
int afterLastParenOffset = -1;
|
||||
|
||||
try {
|
||||
do {
|
||||
if (isAtLineEnd(it)) {
|
||||
break;
|
||||
}
|
||||
else if (it.getTokenType() == JavaTokenType.RBRACE) {
|
||||
break;
|
||||
}
|
||||
else if (it.getTokenType() == JavaTokenType.RPARENTH) {
|
||||
afterLastParenOffset = it.getEnd();
|
||||
}
|
||||
else if (it.getTokenType() != TokenType.WHITE_SPACE) {
|
||||
// Other tokens are not permitted
|
||||
return false;
|
||||
}
|
||||
it.advance();
|
||||
}
|
||||
while (!it.atEnd());
|
||||
}
|
||||
catch (IndexOutOfBoundsException ex) {
|
||||
// May be thrown when checking character at the current offset.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!it.atEnd() && afterLastParenOffset >= 0 && afterLastParenOffset >= caretOffset) {
|
||||
PsiElement curElement = file.findElementAt(caretOffset);
|
||||
PsiStatement curStmt = PsiTreeUtil.getParentOfType(curElement, PsiStatement.class);
|
||||
if (curStmt != null) {
|
||||
if (curStmt instanceof PsiTryStatement) {
|
||||
// try-with-resources can contain semicolons inside
|
||||
return false;
|
||||
}
|
||||
if (curStmt instanceof PsiForStatement) {
|
||||
// for loop can have semicolons inside
|
||||
return false;
|
||||
}
|
||||
// It may worth to check if the error element is about expecting semicolon
|
||||
if (PsiTreeUtil.getDeepestLast(curStmt) instanceof PsiErrorElement) {
|
||||
int stmtEndOffset = curStmt.getTextRange().getEndOffset();
|
||||
if (stmtEndOffset == afterLastParenOffset || stmtEndOffset == it.getStart()) {
|
||||
editor.getDocument().insertString(stmtEndOffset, ";");
|
||||
EditorModificationUtil.moveCaretRelatively(editor, stmtEndOffset - caretOffset + 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isAtLineEnd(HighlighterIterator it) {
|
||||
if (it.getTokenType() == TokenType.WHITE_SPACE) {
|
||||
for (int offset = it.getStart(); offset < it.getEnd(); ++offset) {
|
||||
char chr = it.getDocument().getCharsSequence().charAt(offset);
|
||||
if (chr == '\n') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void autoPopupJavadocLookup(final Project project, final Editor editor) {
|
||||
AutoPopupController.getInstance(project).autoPopupMemberLookup(editor, file -> {
|
||||
int offset = editor.getCaretModel().getOffset();
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
class Foo {
|
||||
|
||||
interface MyPredicate {
|
||||
boolean apply(int param);
|
||||
}
|
||||
|
||||
int foo(int param1, int param2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int boo(Foo that) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void zoo(MyPredicate predicate) {
|
||||
}
|
||||
|
||||
int test() {
|
||||
foo(5, 7/*typehere*/);
|
||||
foo(1, foo(2, 3/*typehere*/));
|
||||
int x = foo(3, 4/*typehere*/);
|
||||
int y = (foo(3, 4/*typehere*/));
|
||||
int z = (foo(3, 4) + boo(this/*typehere*/));
|
||||
zoo(val->true/*typehere*/);
|
||||
zoo(val->{ return (true/*typehere*/); })
|
||||
zoo(val->{ return (true) }/*typehere*/);
|
||||
test(/*typehere*/);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
class Foo {
|
||||
|
||||
interface MyPredicate {
|
||||
boolean apply(int param);
|
||||
}
|
||||
|
||||
int foo(int param1, int param2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int boo(Foo that) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void zoo(MyPredicate predicate) {
|
||||
}
|
||||
|
||||
int test() {
|
||||
foo(5, 7/*typehere*/)
|
||||
foo(1, foo(2, 3/*typehere*/))
|
||||
int x = foo(3, 4/*typehere*/)
|
||||
int y = (foo(3, 4/*typehere*/))
|
||||
int z = (foo(3, 4) + boo(this/*typehere*/))
|
||||
zoo(val->true/*typehere*/)
|
||||
zoo(val->{ return (true/*typehere*/) })
|
||||
zoo(val->{ return (true) }/*typehere*/)
|
||||
test(/*typehere*/)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
class Foo {
|
||||
|
||||
interface MyPredicate {
|
||||
boolean apply(int param);
|
||||
}
|
||||
|
||||
int foo(int param1, int param2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int boo(Foo that) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void zoo(MyPredicate predicate) {
|
||||
}
|
||||
|
||||
int testOnFinishedStatements() {
|
||||
foo(5, 7/*typehere*/;);
|
||||
foo(1, foo(2, 3/*typehere*/;));
|
||||
int x = foo(3, 4/*typehere*/;);
|
||||
int y = (foo(3, 4/*typehere*/;));
|
||||
int z = (foo(3, 4) + boo(this/*typehere*/;));
|
||||
zoo(val->true/*typehere*/;);
|
||||
zoo(val->{ return (true/*typehere*/;); });
|
||||
zoo(val->{ return (true); }/*typehere*/;);
|
||||
testOnFinishedStatements(/*typehere*/;);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int testOnUnfinishedStatements() {
|
||||
foo(5/*typehere*/;, 7)
|
||||
foo(1, /*typehere*/;foo(2, 3))
|
||||
int x = foo(/*typehere*/;3, 4)
|
||||
int y = /*typehere*/;(foo(3, 4))
|
||||
int z = (foo(3, 4) + boo(/*typehere*/;this));
|
||||
zoo(val->/*typehere*/;true)
|
||||
zoo(val->{
|
||||
int x = 1;
|
||||
foo(x, x/*typehere*/;);
|
||||
})
|
||||
return 0;
|
||||
}
|
||||
|
||||
void testWithFirstLoopSemicolon() {
|
||||
for (int x = 0/*typehere*/;)
|
||||
}
|
||||
|
||||
void testWithSecondLoopSemicolon() {
|
||||
for (int x = 0; x <= 10/*typehere*/;)
|
||||
}
|
||||
|
||||
void testWithTryWithResources() {
|
||||
try (int x = 1/*typehere*/;)
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
class Foo {
|
||||
|
||||
interface MyPredicate {
|
||||
boolean apply(int param);
|
||||
}
|
||||
|
||||
int foo(int param1, int param2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int boo(Foo that) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void zoo(MyPredicate predicate) {
|
||||
}
|
||||
|
||||
int testOnFinishedStatements() {
|
||||
foo(5, 7/*typehere*/);
|
||||
foo(1, foo(2, 3/*typehere*/));
|
||||
int x = foo(3, 4/*typehere*/);
|
||||
int y = (foo(3, 4/*typehere*/));
|
||||
int z = (foo(3, 4) + boo(this/*typehere*/));
|
||||
zoo(val->true/*typehere*/);
|
||||
zoo(val->{ return (true/*typehere*/); });
|
||||
zoo(val->{ return (true); }/*typehere*/);
|
||||
testOnFinishedStatements(/*typehere*/);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int testOnUnfinishedStatements() {
|
||||
foo(5/*typehere*/, 7)
|
||||
foo(1, /*typehere*/foo(2, 3))
|
||||
int x = foo(/*typehere*/3, 4)
|
||||
int y = /*typehere*/(foo(3, 4))
|
||||
int z = (foo(3, 4) + boo(/*typehere*/this));
|
||||
zoo(val->/*typehere*/true)
|
||||
zoo(val->{
|
||||
int x = 1;
|
||||
foo(x, x/*typehere*/);
|
||||
})
|
||||
return 0;
|
||||
}
|
||||
|
||||
void testWithFirstLoopSemicolon() {
|
||||
for (int x = 0/*typehere*/)
|
||||
}
|
||||
|
||||
void testWithSecondLoopSemicolon() {
|
||||
for (int x = 0; x <= 10/*typehere*/)
|
||||
}
|
||||
|
||||
void testWithTryWithResources() {
|
||||
try (int x = 1/*typehere*/)
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,25 @@
|
||||
package com.intellij.java.codeInsight;
|
||||
|
||||
import com.intellij.openapi.editor.ex.EditorEx;
|
||||
import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
|
||||
import com.intellij.openapi.util.text.LineTokenizer;
|
||||
import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.testFramework.PlatformTestUtil;
|
||||
import com.intellij.testFramework.UsefulTestCase;
|
||||
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase;
|
||||
import gnu.trove.THashMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class JavaTypingTest extends LightPlatformCodeInsightFixtureTestCase {
|
||||
public void testMulticaretIndentLBrace() {
|
||||
@@ -110,6 +124,14 @@ public class JavaTypingTest extends LightPlatformCodeInsightFixtureTestCase {
|
||||
doTest(';');
|
||||
}
|
||||
|
||||
public void testSemicolonBeforeRightParenMoved() {
|
||||
doMultiTypeTest(';');
|
||||
}
|
||||
|
||||
public void testSemicolonBeforeRightParenNotMoved() {
|
||||
doMultiTypeTest(';');
|
||||
}
|
||||
|
||||
public void testCommaAfterDefaultAnnotationArgumentWhenArrayIsExpected() {
|
||||
doTest(',');
|
||||
}
|
||||
@@ -122,8 +144,34 @@ public class JavaTypingTest extends LightPlatformCodeInsightFixtureTestCase {
|
||||
myFixture.checkResultByFile(getTestName(true) + "_after.java");
|
||||
}
|
||||
|
||||
private void doMultiTypeTest(char c) {
|
||||
myFixture.configureByFile(getTestName(true) + "_before.java");
|
||||
List<Integer> whereToType = findWhereToType(myFixture.getFile().getVirtualFile());
|
||||
assertNotNull("Test file must have at least one place where to type!", whereToType);
|
||||
assertNotEmpty(whereToType);
|
||||
for (Integer offset : whereToType) {
|
||||
myFixture.getEditor().getCaretModel().moveToOffset(offset);
|
||||
myFixture.type(c);
|
||||
}
|
||||
myFixture.checkResultByFile(getTestName(true) + "_after.java");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTestDataPath() {
|
||||
return PlatformTestUtil.getCommunityPath().replace(File.separatorChar, '/') + "/java/java-tests/testData/codeInsight/typing";
|
||||
}
|
||||
|
||||
private static List<Integer> findWhereToType(@NotNull VirtualFile file) {
|
||||
if (file.isDirectory() || file.getFileType().isBinary() || !file.getExtension().contentEquals("java")) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
CharSequence text = LoadTextUtil.loadText(file); //hide CRLF/LF conversion problem
|
||||
List<Integer> offsets = new ArrayList<>();
|
||||
Matcher m = Pattern.compile("/\\*typehere\\*/").matcher(text);
|
||||
while (m.find()) {
|
||||
offsets.add(m.end());
|
||||
}
|
||||
Collections.sort(offsets, (a, b) -> b - a); // sort in descending order
|
||||
return offsets;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user