make 'Introduce local variable' intention available at parameter placeholder after completion

This commit is contained in:
Dmitry Batrak
2017-05-29 16:43:41 +03:00
parent 9cac0c8a9f
commit 180754291c
4 changed files with 220 additions and 3 deletions
@@ -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<PsiJavaToken> 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;
}
}
@@ -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;
}
}
}
@@ -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(<hint text=\"digit:\"/>1,<hint text=\"radix:\"/>2 ) } }");
}
public void testIntroduceVariableIntention() throws Exception {
myFixture.configureByText(JavaFileType.INSTANCE, "class C {\n" +
" void m() {\n" +
" Character.for<caret>\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<caret>, )\n" +
" }\n" +
"}");
}
public void testIntroduceVariableIntentionInIfWithoutBraces() throws Exception {
myFixture.configureByText(JavaFileType.INSTANCE, "class C {\n" +
" void m() {\n" +
" if (true) Character.for<caret>\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<caret>, )\n" +
" }\n" +
" }\n" +
"}");
}
private void waitForParameterInfoUpdate() throws TimeoutException {
ParameterInfoController.waitForDelayedActions(getEditor(), 1, TimeUnit.MINUTES);
}
@@ -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);
}