diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/IntroduceVariableIntentionAction.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/IntroduceVariableIntentionAction.java index 1f7306c00aa6..f200ac572a6e 100644 --- a/java/java-impl/src/com/intellij/codeInsight/intention/impl/IntroduceVariableIntentionAction.java +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/IntroduceVariableIntentionAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,16 +16,21 @@ package com.intellij.codeInsight.intention.impl; import com.intellij.codeInsight.CodeInsightBundle; +import com.intellij.codeInsight.completion.CompletionMemory; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.refactoring.BaseRefactoringIntentionAction; +import com.intellij.refactoring.introduceVariable.IntroduceEmptyVariableHandler; import com.intellij.refactoring.introduceVariable.IntroduceVariableHandler; import com.intellij.util.IncorrectOperationException; +import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.List; + /** * @author Danila Ponomarenko */ @@ -48,6 +53,8 @@ public class IntroduceVariableIntentionAction extends BaseRefactoringIntentionAc return false; } + if (getTypeOfUnfilledParameter(editor, element) != null) return true; + final PsiExpressionStatement statement = detectExpressionStatement(element); if (statement == null){ return false; @@ -61,6 +68,12 @@ public class IntroduceVariableIntentionAction extends BaseRefactoringIntentionAc @Override public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException { + PsiType type = getTypeOfUnfilledParameter(editor, element); + if (type != null) { + new IntroduceEmptyVariableHandler().invoke(editor, element.getContainingFile(), type); + return; + } + final PsiExpressionStatement statement = detectExpressionStatement(element); if (statement == null){ return; @@ -85,4 +98,29 @@ public class IntroduceVariableIntentionAction extends BaseRefactoringIntentionAc return prevSibling instanceof PsiExpressionStatement ? (PsiExpressionStatement)prevSibling : PsiTreeUtil.getParentOfType(element, PsiExpressionStatement.class); } + + @Nullable + private static PsiType getTypeOfUnfilledParameter(@NotNull Editor editor, @NotNull PsiElement element) { + if (element.getParent() instanceof PsiExpressionList && element.getParent().getParent() instanceof PsiMethodCallExpression) { + PsiJavaToken leftBoundary = PsiTreeUtil.getPrevSiblingOfType(element, PsiJavaToken.class); + PsiJavaToken rightBoundary = element instanceof PsiJavaToken ? (PsiJavaToken)element + : PsiTreeUtil.getNextSiblingOfType(element, PsiJavaToken.class); + if (leftBoundary != null && rightBoundary != null && + CharArrayUtil.isEmptyOrSpaces(editor.getDocument().getImmutableCharSequence(), + leftBoundary.getTextRange().getEndOffset(), + rightBoundary.getTextRange().getStartOffset())) { + PsiMethod method = CompletionMemory.getChosenMethod((PsiCall)element.getParent().getParent()); + if (method != null) { + List allTokens = PsiTreeUtil.getChildrenOfTypeAsList(element.getParent(), PsiJavaToken.class); + PsiParameterList parameterList = method.getParameterList(); + int parameterIndex = allTokens.indexOf(leftBoundary); + if (parameterIndex >= 0 && parameterIndex < parameterList.getParametersCount()) { + PsiParameter parameter = parameterList.getParameters()[parameterIndex]; + return parameter.getType(); + } + } + } + } + return null; + } } diff --git a/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceEmptyVariableHandler.java b/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceEmptyVariableHandler.java new file mode 100644 index 000000000000..153683a0ed7b --- /dev/null +++ b/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceEmptyVariableHandler.java @@ -0,0 +1,137 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.refactoring.introduceVariable; + +import com.intellij.codeInsight.completion.JavaCompletionUtil; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.codeInsight.template.*; +import com.intellij.codeInsight.template.impl.ConstantNode; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.codeStyle.SuggestedNameInfo; +import com.intellij.psi.codeStyle.VariableKind; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.search.searches.ReferencesSearch; +import com.intellij.psi.util.PsiTypesUtil; +import com.intellij.refactoring.HelpID; +import com.intellij.refactoring.RefactoringBundle; +import com.intellij.refactoring.util.CommonRefactoringUtil; +import com.intellij.refactoring.util.RefactoringUtil; +import com.intellij.util.ObjectUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class IntroduceEmptyVariableHandler { + private static final String VARIABLE_NAME = "IntroducedVariable"; + private static final String TYPE_NAME = "Type"; + + public void invoke(@NotNull Editor editor, @NotNull PsiFile file, @NotNull PsiType type) { + Project project = file.getProject(); + int offset = editor.getCaretModel().getOffset(); + PsiElement at = file.findElementAt(offset); + PsiElement anchorStatement = RefactoringUtil.getParentStatement(at, false); + if (anchorStatement == null) { + CommonRefactoringUtil.showErrorHint(project, editor, RefactoringBundle.getCannotRefactorMessage( + RefactoringBundle.message("invalid.expression.context")), + RefactoringBundle.message("introduce.variable.title"), HelpID.INTRODUCE_VARIABLE); + return; + } + + SuggestedNameInfo suggestedNameInfo = getSuggestedVariableNames(project, type, at); + assert suggestedNameInfo.names.length > 0; + + ApplicationManager.getApplication().runWriteAction(() -> { + Document document = editor.getDocument(); + String initialVariableName = suggestedNameInfo.names[0]; + document.insertString(offset, initialVariableName); + PsiDocumentManager.getInstance(project).commitDocument(document); + + PsiElement context = anchorStatement.getParent(); + PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory(); + String value = PsiTypesUtil.getDefaultValueOfType(type); + PsiExpression initializer = elementFactory.createExpressionFromText(value, anchorStatement); + PsiDeclarationStatement declaration = elementFactory + .createVariableDeclarationStatement(initialVariableName, type, initializer, context); + if (RefactoringUtil.isLoopOrIf(context)) { + declaration = (PsiDeclarationStatement)RefactoringUtil.putStatementInLoopBody(declaration, context, anchorStatement); + context = declaration.getParent(); + } else { + declaration = (PsiDeclarationStatement)context.addBefore(declaration, anchorStatement); + } + PsiLocalVariable localVariable = (PsiLocalVariable)declaration.getDeclaredElements()[0]; + PsiTypeElement typeElement = localVariable.getTypeElement(); + PsiReference reference = ReferencesSearch.search(localVariable, new LocalSearchScope(context)).findFirst(); + assert reference != null; + PsiElement variableReference = reference.getElement(); + PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(document); + + TemplateBuilderImpl templateBuilder = new TemplateBuilderImpl(context); + templateBuilder.replaceElement(typeElement, TYPE_NAME, new ConstantNode(typeElement.getText()), true, true); + templateBuilder.replaceElement(localVariable.getNameIdentifier(), VARIABLE_NAME, new MyExpression(suggestedNameInfo), true); + templateBuilder.replaceElement(variableReference, VARIABLE_NAME, (String)null, false); + templateBuilder.replaceElement(ObjectUtils.assertNotNull(localVariable.getInitializer()), ""); + templateBuilder.setEndVariableAfter(variableReference); + Template template = templateBuilder.buildInlineTemplate(); + + editor.getCaretModel().moveToOffset(context.getTextOffset()); + TemplateManager.getInstance(project).startTemplate(editor, template); + }); + } + + private static SuggestedNameInfo getSuggestedVariableNames(@NotNull Project project, PsiType type, PsiElement at) { + JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project); + SuggestedNameInfo nameInfo = codeStyleManager.suggestVariableName(VariableKind.LOCAL_VARIABLE, null, null, type); + String[] strings = JavaCompletionUtil.completeVariableNameForRefactoring(codeStyleManager, type, VariableKind.LOCAL_VARIABLE, nameInfo); + SuggestedNameInfo.Delegate delegate = new SuggestedNameInfo.Delegate(strings, nameInfo); + return codeStyleManager.suggestUniqueVariableName(delegate, at, true); + } + + private static class MyExpression extends Expression { + private final SuggestedNameInfo myNameInfo; + + private MyExpression(SuggestedNameInfo info) { + myNameInfo = info; + } + + @Nullable + @Override + public Result calculateResult(ExpressionContext context) { + return new TextResult(myNameInfo.names[0]); + } + + @Nullable + @Override + public Result calculateQuickResult(ExpressionContext context) { + return calculateResult(context); + } + + @Nullable + @Override + public LookupElement[] calculateLookupItems(ExpressionContext context) { + LookupElement[] elements = new LookupElement[myNameInfo.names.length]; + for (int i = 0; i < myNameInfo.names.length; i++) { + String name = myNameInfo.names[i]; + elements[i] = LookupElementBuilder.create(name); + } + return elements; + } + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/CompletionHintsTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/CompletionHintsTest.java index 60f026713042..52a96f95a5ea 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/CompletionHintsTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/CompletionHintsTest.java @@ -21,6 +21,7 @@ import com.intellij.codeInsight.daemon.impl.ParameterHintsPresentationManager; import com.intellij.codeInsight.hint.ParameterInfoController; import com.intellij.codeInsight.hints.JavaInlayParameterHintsProvider; import com.intellij.codeInsight.hints.Option; +import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementPresentation; import com.intellij.ide.highlighter.JavaFileType; @@ -249,6 +250,46 @@ public class CompletionHintsTest extends LightFixtureCompletionTestCase { myFixture.checkResultWithInlays("class C { void m() { Character.forDigit(1,2 ) } }"); } + public void testIntroduceVariableIntention() throws Exception { + myFixture.configureByText(JavaFileType.INSTANCE, "class C {\n" + + " void m() {\n" + + " Character.for\n" + + " }\n" + + "}"); + complete("forDigit"); + waitForAllAsyncStuff(); + IntentionAction intention = myFixture.findSingleIntention("Introduce local variable"); + assertNotNull(intention); + myFixture.launchAction(intention); + myFixture.checkResult("class C {\n" + + " void m() {\n" + + " int i = ;\n" + + " Character.forDigit(i, )\n" + + " }\n" + + "}"); + } + + public void testIntroduceVariableIntentionInIfWithoutBraces() throws Exception { + myFixture.configureByText(JavaFileType.INSTANCE, "class C {\n" + + " void m() {\n" + + " if (true) Character.for\n" + + " }\n" + + "}"); + complete("forDigit"); + waitForAllAsyncStuff(); + IntentionAction intention = myFixture.findSingleIntention("Introduce local variable"); + assertNotNull(intention); + myFixture.launchAction(intention); + myFixture.checkResult("class C {\n" + + " void m() {\n" + + " if (true) {\n" + + " int i = ;\n" + + " Character.forDigit(i, )\n" + + " }\n" + + " }\n" + + "}"); + } + private void waitForParameterInfoUpdate() throws TimeoutException { ParameterInfoController.waitForDelayedActions(getEditor(), 1, TimeUnit.MINUTES); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/TemplateBuilderImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/template/TemplateBuilderImpl.java index 82430b006989..c244c9324ab3 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/TemplateBuilderImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/TemplateBuilderImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; +import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; import java.util.HashMap; @@ -164,7 +165,7 @@ public class TemplateBuilderImpl implements TemplateBuilder { * Adds end variable after the specified element */ public void setEndVariableAfter(PsiElement element) { - element = element.getNextSibling(); + element = PsiTreeUtil.nextLeaf(element); setEndVariableBefore(element); }