IDEA-140452 Complete-statement insert closing parenthesis at wrong place
authorpeter <peter@jetbrains.com>
Thu, 9 Jul 2015 18:40:52 +0000 (20:40 +0200)
committerpeter <peter@jetbrains.com>
Thu, 9 Jul 2015 18:49:01 +0000 (20:49 +0200)
java/java-impl/src/com/intellij/codeInsight/editorActions/smartEnter/MethodCallFixer.java
java/java-tests/testData/codeInsight/completion/normal/SmartEnterGuessArgumentCount.java [new file with mode: 0644]
java/java-tests/testData/codeInsight/completion/normal/SmartEnterGuessArgumentCount_after.java [new file with mode: 0644]
java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy

index 0f9bc4f3b1c6417eed96002080206fc3f7adac38..a850d336910ae6024f5b95f34b16ec3cf61aca8d 100644 (file)
 package com.intellij.codeInsight.editorActions.smartEnter;
 
 import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.DumbService;
 import com.intellij.psi.*;
 import com.intellij.psi.impl.source.jsp.jspJava.JspMethodCall;
+import com.intellij.psi.infos.CandidateInfo;
+import com.intellij.psi.util.PsiTreeUtil;
 import com.intellij.util.IncorrectOperationException;
 import com.intellij.util.text.CharArrayUtil;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Created by IntelliJ IDEA.
@@ -38,17 +42,20 @@ public class MethodCallFixer implements Fixer {
       args = ((PsiNewExpression) psiElement).getArgumentList();
     }
 
-    if (args == null) return;
+    if (args != null && !hasRParenth(args)) {
+      int caret = editor.getCaretModel().getOffset();
+      PsiCallExpression innermostCall = PsiTreeUtil.findElementOfClassAtOffset(psiElement.getContainingFile(), caret, PsiCallExpression.class, false);
+      if (innermostCall == null) return;
 
-    PsiElement parenth = args.getLastChild();
+      args = innermostCall.getArgumentList();
+      if (args == null) return;
 
-    if (parenth == null || !")".equals(parenth.getText())) {
       int endOffset = -1;
       PsiElement child = args.getFirstChild();
       while (child != null) {
         if (child instanceof PsiErrorElement) {
           final PsiErrorElement errorElement = (PsiErrorElement)child;
-          if (errorElement.getErrorDescription().indexOf("')'") >= 0) {
+          if (errorElement.getErrorDescription().contains("')'")) {
             endOffset = errorElement.getTextRange().getStartOffset();
             break;
           }
@@ -62,17 +69,48 @@ public class MethodCallFixer implements Fixer {
 
       final PsiExpression[] params = args.getExpressions();
       if (params.length > 0 && 
-          startLine(editor, args) != startLine(editor, params[0]) && 
-          editor.getCaretModel().getOffset() < params[0].getTextRange().getStartOffset()) {
+          startLine(editor, args) != startLine(editor, params[0]) &&
+          caret < params[0].getTextRange().getStartOffset()) {
         endOffset = args.getTextRange().getStartOffset() + 1;
       }
 
+      if (!DumbService.isDumb(args.getProject())) {
+        Integer argCount = getUnambiguousParameterCount(innermostCall);
+        if (argCount != null && argCount > 0 && argCount < params.length) {
+          endOffset = Math.min(endOffset, params[argCount - 1].getTextRange().getEndOffset());
+        }
+      }
+
       endOffset = CharArrayUtil.shiftBackward(editor.getDocument().getCharsSequence(), endOffset - 1, " \t\n") + 1;
       editor.getDocument().insertString(endOffset, ")");
+      editor.getCaretModel().moveToOffset(endOffset + 1);
+    }
+  }
+
+  private static boolean hasRParenth(PsiExpressionList args) {
+    PsiElement parenth = args.getLastChild();
+    return parenth != null && ")".equals(parenth.getText());
+  }
+
+  @Nullable
+  private static Integer getUnambiguousParameterCount(PsiCallExpression call) {
+    int argCount = -1;
+    for (CandidateInfo candidate : PsiResolveHelper.SERVICE.getInstance(call.getProject()).getReferencedMethodCandidates(call, false)) {
+      PsiElement element = candidate.getElement();
+      if (!(element instanceof PsiMethod)) return null;
+      if (((PsiMethod)element).isVarArgs()) return null;
+
+      int count = ((PsiMethod)element).getParameterList().getParametersCount();
+      if (argCount == -1) {
+        argCount = count;
+      } else if (argCount != count) {
+        return null;
+      }
     }
+    return argCount;
   }
 
-  private int startLine(Editor editor, PsiElement psiElement) {
+  private static int startLine(Editor editor, PsiElement psiElement) {
     return editor.getDocument().getLineNumber(psiElement.getTextRange().getStartOffset());
   }
 }
diff --git a/java/java-tests/testData/codeInsight/completion/normal/SmartEnterGuessArgumentCount.java b/java/java-tests/testData/codeInsight/completion/normal/SmartEnterGuessArgumentCount.java
new file mode 100644 (file)
index 0000000..26b8073
--- /dev/null
@@ -0,0 +1,9 @@
+class Main {
+  public void test() {
+      Path path = Paths.get("/tmp");
+      foo(getf<caret>path, 5);
+  }
+
+  private String getFileName(Path path) { return path.toString(); }
+  private void foo(String text, int count) {}
+}
diff --git a/java/java-tests/testData/codeInsight/completion/normal/SmartEnterGuessArgumentCount_after.java b/java/java-tests/testData/codeInsight/completion/normal/SmartEnterGuessArgumentCount_after.java
new file mode 100644 (file)
index 0000000..71444a1
--- /dev/null
@@ -0,0 +1,9 @@
+class Main {
+  public void test() {
+      Path path = Paths.get("/tmp");
+      foo(getFileName(path), 5);<caret>
+  }
+
+  private String getFileName(Path path) { return path.toString(); }
+  private void foo(String text, int count) {}
+}
index 68f9124ed08188fafc234d45014c80f5c18b813e..b3662e8dd134b0c6ead236e016f70d2901415e66 100644 (file)
@@ -934,6 +934,7 @@ public class ListUtils {
   public void testSmartEnterWrapsConstructorCall() throws Throwable { doTest(Lookup.COMPLETE_STATEMENT_SELECT_CHAR as String) }
   public void testSmartEnterNoNewLine() { doTest(Lookup.COMPLETE_STATEMENT_SELECT_CHAR as String) }
   public void testSmartEnterWithNewLine() { doTest(Lookup.COMPLETE_STATEMENT_SELECT_CHAR as String) }
+  public void testSmartEnterGuessArgumentCount() throws Throwable { doTest(Lookup.COMPLETE_STATEMENT_SELECT_CHAR as String) }
 
   public void testTabReplacesMethodNameWithLocalVariableName() throws Throwable { doTest('\t'); }
   public void testMethodParameterAnnotationClass() throws Throwable { doTest(); }