diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/smartEnter/MethodCallFixer.java b/java/java-impl/src/com/intellij/codeInsight/editorActions/smartEnter/MethodCallFixer.java index 0f9bc4f3b1c6..a850d336910a 100644 --- a/java/java-impl/src/com/intellij/codeInsight/editorActions/smartEnter/MethodCallFixer.java +++ b/java/java-impl/src/com/intellij/codeInsight/editorActions/smartEnter/MethodCallFixer.java @@ -16,10 +16,14 @@ 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 int startLine(Editor editor, PsiElement psiElement) { + 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 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 index 000000000000..26b80731b3d6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/SmartEnterGuessArgumentCount.java @@ -0,0 +1,9 @@ +class Main { + public void test() { + Path path = Paths.get("/tmp"); + foo(getfpath, 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 index 000000000000..71444a134014 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/SmartEnterGuessArgumentCount_after.java @@ -0,0 +1,9 @@ +class Main { + public void test() { + Path path = Paths.get("/tmp"); + foo(getFileName(path), 5); + } + + private String getFileName(Path path) { return path.toString(); } + private void foo(String text, int count) {} +} diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy index 68f9124ed081..b3662e8dd134 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy @@ -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(); }