/*
* Copyright 2011 Jon S Akhtar (Sylvanaar)
*
* 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.sylvanaar.idea.Lua.refactoring.introduce;
import com.intellij.codeInsight.highlighting.HighlightManager;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Pass;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.IntroduceTargetChooser;
import com.intellij.refactoring.RefactoringActionHandler;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.util.Function;
import com.sylvanaar.idea.Lua.lang.psi.LuaPsiFile;
import com.sylvanaar.idea.Lua.lang.psi.LuaPsiFileBase;
import com.sylvanaar.idea.Lua.lang.psi.LuaReferenceElement;
import com.sylvanaar.idea.Lua.lang.psi.expressions.LuaExpression;
import com.sylvanaar.idea.Lua.lang.psi.expressions.LuaExpressionList;
import com.sylvanaar.idea.Lua.lang.psi.expressions.LuaIdentifierList;
import com.sylvanaar.idea.Lua.lang.psi.impl.PsiUtil;
import com.sylvanaar.idea.Lua.lang.psi.statements.LuaDeclarationStatement;
import com.sylvanaar.idea.Lua.lang.psi.statements.LuaGenericForStatement;
import com.sylvanaar.idea.Lua.lang.psi.statements.LuaIfThenStatement;
import com.sylvanaar.idea.Lua.lang.psi.statements.LuaWhileStatement;
import com.sylvanaar.idea.Lua.lang.psi.symbols.LuaParameter;
import com.sylvanaar.idea.Lua.lang.psi.symbols.LuaSymbol;
import com.sylvanaar.idea.Lua.refactoring.LuaRefactoringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* @author Maxim.Medvedev
*/
public abstract class LuaIntroduceHandlerBase<Settings extends LuaIntroduceSettings> implements RefactoringActionHandler {
protected abstract String getRefactoringName();
protected abstract String getHelpID();
@NotNull
protected abstract PsiElement findScope(LuaExpression expression, LuaSymbol variable);
protected abstract void checkExpression(LuaExpression selectedExpr) throws LuaIntroduceRefactoringError;
protected abstract void checkVariable(LuaSymbol variable) throws LuaIntroduceRefactoringError;
protected abstract void checkOccurrences(PsiElement[] occurrences);
protected abstract LuaIntroduceDialog<Settings> getDialog(LuaIntroduceContext context);
@Nullable
public abstract LuaSymbol runRefactoring(LuaIntroduceContext context, Settings settings);
public static List<LuaExpression> collectExpressions(final PsiFile file, final Editor editor, final int offset) {
int correctedOffset = correctOffset(editor, offset);
final PsiElement elementAtCaret = file.findElementAt(correctedOffset);
final List<LuaExpression> expressions = new ArrayList<LuaExpression>();
for (LuaExpression expression = PsiTreeUtil.getParentOfType(elementAtCaret, LuaExpression.class);
expression != null;
expression = PsiTreeUtil.getParentOfType(expression, LuaExpression.class)) {
if (expressions.contains(expression)) continue;
if (expressionIsNotCorrect(expression)) continue;
expressions.add(expression);
}
return expressions;
}
private static boolean expressionIsNotCorrect(LuaExpression expression) {
if (expression instanceof LuaReferenceElement) return true;
if (expression instanceof LuaExpressionList) return true;
if (expression instanceof LuaIdentifierList) return true;
// if (expression instanceof GrSuperReferenceExpression) return true;
// if (expression.getType() == PsiType.VOID) return true;
// if (expression instanceof GrAssignmentExpression) return true;
// if (expression instanceof GrReferenceExpression && expression.getParent() instanceof GrCall) {
// final PsiElement resolved = ((GrReferenceExpression)expression).resolve();
// return resolved instanceof PsiMethod || resolved instanceof PsiClass;
// }
// if (expression instanceof GrApplicationStatement) {
// return !PsiUtil.isExpressionStatement(expression);
// }
// if (expression instanceof GrClosableBlock && expression.getParent() instanceof GrStringInjection) return true;
return false;
}
private static int correctOffset(Editor editor, int offset) {
Document document = editor.getDocument();
CharSequence text = document.getCharsSequence();
int correctedOffset = offset;
int textLength = document.getTextLength();
if (offset >= textLength) {
correctedOffset = textLength - 1;
}
else if (!Character.isJavaIdentifierPart(text.charAt(offset))) {
correctedOffset--;
}
if (correctedOffset < 0) {
correctedOffset = offset;
}
else if (!Character.isJavaIdentifierPart(text.charAt(correctedOffset))) {
if (text.charAt(correctedOffset) == ';') {//initially caret on the end of line
correctedOffset--;
}
if (text.charAt(correctedOffset) != ')') {
correctedOffset = offset;
}
}
return correctedOffset;
}
@Nullable
private static LuaSymbol findVariableAtCaret(final PsiFile file, final Editor editor, final int offset) {
final int correctOffset = correctOffset(editor, offset);
final PsiElement elementAtCaret = file.findElementAt(correctOffset);
final LuaSymbol variable = PsiTreeUtil.getParentOfType(elementAtCaret, LuaSymbol.class);
if (variable != null && variable.getTextRange().contains(correctOffset)) return variable;
return null;
}
public void invoke(final @NotNull Project project, final Editor editor, final PsiFile file, final @Nullable DataContext dataContext) {
final SelectionModel selectionModel = editor.getSelectionModel();
if (!selectionModel.hasSelection()) {
final int offset = editor.getCaretModel().getOffset();
final List<LuaExpression> expressions = collectExpressions(file, editor, offset);
if (expressions.isEmpty()) {
final LuaSymbol variable = findVariableAtCaret(file, editor, offset);
if (variable == null || variable instanceof LuaParameter) {
selectionModel.selectLineAtCaret();
}
else {
final TextRange textRange = variable.getTextRange();
selectionModel.setSelection(textRange.getStartOffset(), textRange.getEndOffset());
}
}
else if (expressions.size() == 1) {
final TextRange textRange = expressions.get(0).getTextRange();
selectionModel.setSelection(textRange.getStartOffset(), textRange.getEndOffset());
}
else {
IntroduceTargetChooser.showChooser(editor, expressions,
new Pass<LuaExpression>() {
public void pass(final LuaExpression selectedValue) {
invoke(project, editor, file, selectedValue.getTextRange().getStartOffset(),
selectedValue.getTextRange().getEndOffset());
}
},
new Function<LuaExpression, String>() {
@Override
public String fun(LuaExpression LuaExpression) {
return LuaExpression.getText();
}
});
return;
}
}
invoke(project, editor, file, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
}
@Override
public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {
// Does nothing
}
public LuaIntroduceContext getContext(Project project, Editor editor, LuaExpression expression, @Nullable LuaSymbol variable) {
final PsiElement scope = findScope(expression, variable);
if (variable == null) {
final PsiElement[] occurences = findOccurences(expression, scope);
return new LuaIntroduceContext(project, editor, expression, occurences, scope, variable);
}
else {
// final List<PsiElement> list = Collections.synchronizedList(new ArrayList<PsiElement>());
// ReferencesSearch.search(variable, new LocalSearchScope(scope)).forEach(new Processor<PsiReference>() {
// @Override
// public boolean process(PsiReference psiReference) {
// final PsiElement element = psiReference.getElement();
// if (element != null) {
// list.add(element);
// }
// return true;
// }
// });
final PsiElement[] occurences = findOccurences(variable, scope);
// return new LuaIntroduceContext(project, editor, variable, list.toArray(new PsiElement[list.size()]), scope,
// variable);
return new LuaIntroduceContext(project, editor, variable, occurences, scope,
variable);
}
}
protected PsiElement[] findOccurences(LuaExpression expression, PsiElement scope) {
final PsiElement expr = PsiUtil.skipParentheses(expression, false);
assert expr != null;
final PsiElement[] occurrences = LuaRefactoringUtil.getExpressionOccurrences(expr, scope);
if (occurrences == null || occurrences.length == 0) {
throw new LuaIntroduceRefactoringError("No occurances found");
}
return occurrences;
}
private boolean invoke(final Project project, final Editor editor, PsiFile file, int startOffset, int endOffset) {
try {
PsiDocumentManager.getInstance(project).commitAllDocuments();
if (!(file instanceof LuaPsiFileBase)) {
throw new LuaIntroduceRefactoringError("Only Lua files");
}
if (!CommonRefactoringUtil.checkReadOnlyStatus(project, file)) {
throw new LuaIntroduceRefactoringError("Read-only occurances found");
}
LuaExpression selectedExpr = findExpression((LuaPsiFileBase)file, startOffset, endOffset);
final LuaSymbol variable = findVariable((LuaPsiFile)file, startOffset, endOffset);
if (variable != null) {
checkVariable(variable);
}
else if (selectedExpr != null) {
checkExpression(selectedExpr);
}
else {
throw new LuaIntroduceRefactoringError(null);
}
final LuaIntroduceContext context = getContext(project, editor, selectedExpr, variable);
checkOccurrences(context.occurrences);
final Settings settings = showDialog(context);
if (settings == null) return false;
CommandProcessor.getInstance().executeCommand(context.project, new Runnable() {
public void run() {
ApplicationManager.getApplication().runWriteAction(new Computable<LuaSymbol>() {
public LuaSymbol compute() {
return runRefactoring(context, settings);
}
});
}
}, getRefactoringName(), null);
return true;
}
catch (LuaIntroduceRefactoringError e) {
CommonRefactoringUtil
.showErrorHint(project, editor, RefactoringBundle.getCannotRefactorMessage(e.getMessage()), getRefactoringName(), getHelpID());
return false;
}
}
@Nullable
private static LuaSymbol findVariable(LuaPsiFile file, int startOffset, int endOffset) {
LuaSymbol var = LuaRefactoringUtil.findElementInRange(file, startOffset, endOffset, LuaSymbol.class);
if (var == null) {
final LuaDeclarationStatement variableDeclaration =
LuaRefactoringUtil.findElementInRange(file, startOffset, endOffset, LuaDeclarationStatement.class);
if (variableDeclaration == null) return null;
final LuaSymbol[] variables = variableDeclaration.getDefinedSymbols();
if (variables.length == 1) {
var = variables[0];
}
}
if (var instanceof LuaParameter) {
return null;
}
return var;
}
@Nullable
public static LuaExpression findExpression(LuaPsiFileBase file, int startOffset, int endOffset) {
LuaExpression selectedExpr = LuaRefactoringUtil.findElementInRange(file, startOffset, endOffset, LuaExpression.class);
if (selectedExpr == null) return null;
// PsiType type = selectedExpr.getType();
// if (type != null) type = TypeConversionUtil.erasure(type);
//
// if (PsiType.VOID.equals(type)) {
// throw new GrIntroduceRefactoringError(LuaRefactoringBundle.message("selected.expression.has.void.type"));
// }
//
// if (expressionIsNotCorrect(selectedExpr)) {
// throw new GrIntroduceRefactoringError(LuaRefactoringBundle.message("selected.block.should.represent.an.expression"));
// }
return selectedExpr;
}
@Nullable
private Settings showDialog(LuaIntroduceContext context) {
// Add occurences highlighting
ArrayList<RangeHighlighter> highlighters = new ArrayList<RangeHighlighter>();
HighlightManager highlightManager = null;
if (context.editor != null) {
highlightManager = HighlightManager.getInstance(context.project);
EditorColorsManager colorsManager = EditorColorsManager.getInstance();
TextAttributes attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);
if (context.occurrences.length > 1) {
highlightManager.addOccurrenceHighlights(context.editor, context.occurrences, attributes, true, highlighters);
}
}
LuaIntroduceDialog<Settings> dialog = getDialog(context);
dialog.show();
if (dialog.isOK()) {
if (context.editor != null) {
assert highlightManager != null : "highlight manager is null";
for (RangeHighlighter highlighter : highlighters) {
highlightManager.removeSegmentHighlighter(context.editor, highlighter);
}
}
return dialog.getSettings();
}
else {
if (context.occurrences.length > 1) {
WindowManager.getInstance().getStatusBar(context.project)
.setInfo("Press escape to remove highlighting");
}
}
return null;
}
@Nullable
public static PsiElement findAnchor(LuaIntroduceContext context,
LuaIntroduceSettings settings,
PsiElement[] occurrences,
final PsiElement container) {
if (occurrences.length == 0) return null;
PsiElement candidate;
if (occurrences.length == 1 || !settings.replaceAllOccurrences()) {
candidate = context.expression;
}
else {
LuaRefactoringUtil.sortOccurrences(occurrences);
candidate = occurrences[0];
}
while (candidate != null && !container.equals(candidate.getParent())) {
candidate = candidate.getParent();
}
if (candidate == null) {
return null;
}
if ((container instanceof LuaWhileStatement) &&
candidate.equals(((LuaWhileStatement)container).getCondition())) {
return container;
}
if ((container instanceof LuaIfThenStatement) &&
candidate.equals(((LuaIfThenStatement)container).getIfCondition())) {
return container;
}
if ((container instanceof LuaGenericForStatement) &&
candidate.equals(((LuaGenericForStatement)container).getInClause())) {
return container;
}
return candidate;
}
// protected static void deleteLocalVar(LuaIntroduceContext context) {
// final LuaSymbol resolved = GrIntroduceFieldHandler.resolveLocalVar(context);
// final PsiElement parent = resolved.getParent();
// if (parent instanceof GrTupleDeclaration) {
// if (((GrTupleDeclaration)parent).getVariables().length == 1) {
// parent.getParent().delete();
// }
// else {
// final LuaExpression initializerLua = resolved.getInitializerLua();
// if (initializerLua != null) initializerLua.delete();
// resolved.delete();
// }
// }
// else {
// if (((LuaSymbolDeclaration)parent).getVariables().length == 1) {
// parent.delete();
// }
// else {
// resolved.delete();
// }
// }
// }
protected static LuaSymbol resolveLocalVar(LuaIntroduceContext context) {
if (context.var != null) return context.var;
return (LuaSymbol)((LuaReferenceElement)context.expression).resolve();
}
// public static boolean hasLhs(final PsiElement[] occurrences) {
// for (PsiElement element : occurrences) {
// if (element instanceof GrReferenceExpression) {
// if (PsiUtil.isLValue((LuaPsiElement)element)) return true;
// if (ControlFlowUtils.isIncOrDecOperand((GrReferenceExpression)element)) return true;
// }
// }
// return false;
// }
//
//
public interface Validator {
boolean isOK(LuaIntroduceDialog dialog);
}
}