/*
* Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan
*
* 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.goide.refactor;
import com.goide.inspections.GoInspectionUtil;
import com.goide.psi.*;
import com.goide.psi.impl.GoElementFactory;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Conditions;
import com.intellij.openapi.util.Pass;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.SyntaxTraverser;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.IntroduceTargetChooser;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.introduce.inplace.InplaceVariableIntroducer;
import com.intellij.refactoring.introduce.inplace.OccurrencesChooser;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
public class GoIntroduceVariableBase {
protected static void performAction(GoIntroduceOperation operation) {
SelectionModel selectionModel = operation.getEditor().getSelectionModel();
boolean hasSelection = selectionModel.hasSelection();
GoExpression expression =
hasSelection ? findExpressionInSelection(operation.getFile(), selectionModel.getSelectionStart(), selectionModel.getSelectionEnd())
: findExpressionAtOffset(operation);
if (expression instanceof GoParenthesesExpr) expression = ((GoParenthesesExpr)expression).getExpression();
if (expression == null) {
String message =
RefactoringBundle.message(hasSelection ? "selected.block.should.represent.an.expression" : "refactoring.introduce.selection.error");
showCannotPerform(operation, message);
return;
}
List<GoExpression> extractableExpressions = collectExtractableExpressions(expression);
if (extractableExpressions.isEmpty()) {
showCannotPerform(operation, RefactoringBundle.message("refactoring.introduce.context.error"));
return;
}
List<GoExpression> expressions = ContainerUtil.filter(extractableExpressions,
expression12 -> GoInspectionUtil.getExpressionResultCount(expression12) == 1);
if (expressions.isEmpty()) {
GoExpression closestExpression = ContainerUtil.getFirstItem(extractableExpressions);
int resultCount = closestExpression != null ? GoInspectionUtil.getExpressionResultCount(closestExpression) : 0;
showCannotPerform(operation, "Expression " + (closestExpression != null ? closestExpression.getText() + " " : "") +
(resultCount == 0 ? "doesn't return a value." : "returns multiple values."));
return;
}
if (expressions.size() == 1 || hasSelection || ApplicationManager.getApplication().isUnitTestMode()) {
//noinspection ConstantConditions
operation.setExpression(ContainerUtil.getFirstItem(expressions));
performOnElement(operation);
}
else {
IntroduceTargetChooser.showChooser(operation.getEditor(), expressions, new Pass<GoExpression>() {
@Override
public void pass(GoExpression expression) {
if (expression.isValid()) {
operation.setExpression(expression);
performOnElement(operation);
}
}
}, expression1 -> expression1.isValid() ? expression1.getText() : "<invalid expression>");
}
}
@Nullable
public static GoExpression findExpressionInSelection(@NotNull PsiFile file, int start, int end) {
return PsiTreeUtil.findElementOfClassAtRange(file, start, end, GoExpression.class);
}
@Nullable
private static GoExpression findExpressionAtOffset(GoIntroduceOperation operation) {
PsiFile file = operation.getFile();
int offset = operation.getEditor().getCaretModel().getOffset();
GoExpression expr = PsiTreeUtil.getNonStrictParentOfType(file.findElementAt(offset), GoExpression.class);
GoExpression preExpr = PsiTreeUtil.getNonStrictParentOfType(file.findElementAt(offset - 1), GoExpression.class);
if (expr == null || preExpr != null && PsiTreeUtil.isAncestor(expr, preExpr, false)) return preExpr;
return expr;
}
@NotNull
private static List<GoExpression> collectExtractableExpressions(@NotNull GoExpression expression) {
if (PsiTreeUtil.getParentOfType(expression, GoStatement.class) == null) {
return Collections.emptyList();
}
return SyntaxTraverser.psiApi().parents(expression).takeWhile(Conditions.notInstanceOf(GoTopLevelDeclaration.class))
.filter(GoExpression.class)
.filter(Conditions.notInstanceOf(GoParenthesesExpr.class))
.filter(expression1 -> !(expression1 instanceof GoReferenceExpression && expression1.getParent() instanceof GoCallExpr))
.toList();
}
private static void performOnElement(@NotNull GoIntroduceOperation operation) {
GoExpression expression = operation.getExpression();
LinkedHashSet<String> suggestedNames = GoRefactoringUtil.getSuggestedNames(expression);
operation.setSuggestedNames(suggestedNames);
operation.setOccurrences(GoRefactoringUtil.getLocalOccurrences(expression));
Editor editor = operation.getEditor();
if (editor.getSettings().isVariableInplaceRenameEnabled()) {
//noinspection ConstantConditions
operation.setName(ContainerUtil.getFirstItem(suggestedNames));
if (ApplicationManager.getApplication().isUnitTestMode()) {
performInplaceIntroduce(operation);
return;
}
OccurrencesChooser.simpleChooser(editor)
.showChooser(expression, operation.getOccurrences(), new Pass<OccurrencesChooser.ReplaceChoice>() {
@Override
public void pass(OccurrencesChooser.ReplaceChoice choice) {
operation.setReplaceAll(choice == OccurrencesChooser.ReplaceChoice.ALL);
performInplaceIntroduce(operation);
}
});
}
else {
GoIntroduceVariableDialog dialog = new GoIntroduceVariableDialog(operation);
if (dialog.showAndGet()) {
operation.setName(dialog.getName());
operation.setReplaceAll(dialog.getReplaceAll());
performReplace(operation);
}
}
}
private static void performInplaceIntroduce(GoIntroduceOperation operation) {
if (!operation.getExpression().isValid()) {
showCannotPerform(operation, RefactoringBundle.message("refactoring.introduce.context.error"));
return;
}
performReplace(operation);
new GoInplaceVariableIntroducer(operation).performInplaceRefactoring(operation.getSuggestedNames());
}
private static void performReplace(GoIntroduceOperation operation) {
Project project = operation.getProject();
PsiElement expression = operation.getExpression();
List<PsiElement> occurrences = operation.isReplaceAll() ? operation.getOccurrences() : Collections.singletonList(expression);
PsiElement anchor = GoRefactoringUtil.findLocalAnchor(occurrences);
if (anchor == null) {
showCannotPerform(operation, RefactoringBundle.message("refactoring.introduce.context.error"));
return;
}
GoBlock context = (GoBlock)anchor.getParent();
String name = operation.getName();
List<PsiElement> newOccurrences = ContainerUtil.newArrayList();
WriteCommandAction.runWriteCommandAction(project, () -> {
GoStatement declarationStatement = GoElementFactory.createShortVarDeclarationStatement(project, name, (GoExpression)expression);
PsiElement newLine = GoElementFactory.createNewLine(project);
PsiElement statement = context.addBefore(declarationStatement, context.addBefore(newLine, anchor));
GoVarDefinition varDefinition = PsiTreeUtil.findChildOfType(statement, GoVarDefinition.class);
assert varDefinition != null;
assert varDefinition.isValid() : "invalid var `" + varDefinition.getText() + "` definition in `" + statement.getText() + "`";
operation.setVar(varDefinition);
boolean firstOccurrence = true;
for (PsiElement occurrence : occurrences) {
PsiElement occurrenceParent = occurrence.getParent();
if (occurrenceParent instanceof GoParenthesesExpr) occurrence = occurrenceParent;
if (firstOccurrence && occurrence instanceof GoExpression) {
firstOccurrence = false;
PsiElement parent = occurrence.getParent();
// single-expression statement
if (parent instanceof GoLeftHandExprList && parent.getParent() instanceof GoSimpleStatement) {
parent.getParent().delete();
continue;
}
}
newOccurrences.add(occurrence.replace(GoElementFactory.createReferenceExpression(project, name)));
}
operation.getEditor().getCaretModel().moveToOffset(varDefinition.getIdentifier().getTextRange().getStartOffset());
});
operation.setOccurrences(newOccurrences);
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(operation.getEditor().getDocument());
}
private static void showCannotPerform(GoIntroduceOperation operation, String message) {
message = RefactoringBundle.getCannotRefactorMessage(message);
CommonRefactoringUtil.showErrorHint(operation.getProject(), operation.getEditor(), message,
RefactoringBundle.getCannotRefactorMessage(null), "refactoring.extractVariable");
}
private static class GoInplaceVariableIntroducer extends InplaceVariableIntroducer<PsiElement> {
public GoInplaceVariableIntroducer(GoIntroduceOperation operation) {
super(operation.getVar(), operation.getEditor(), operation.getProject(), "Introduce Variable",
ArrayUtil.toObjectArray(operation.getOccurrences(), PsiElement.class), null);
}
}
}