blob: 8735d9eaa76976f3ce632f0c4d9cfe093cd659c7 [file] [log] [blame]
/*
* Copyright 2000-2013 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.rename;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.DataKey;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import com.intellij.psi.meta.PsiMetaOwner;
import com.intellij.psi.meta.PsiWritableMetaData;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.actions.BaseRefactoringAction;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.usageView.UsageViewUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
/**
* created at Nov 13, 2001
*
* @author Jeka, dsl
*/
public class PsiElementRenameHandler implements RenameHandler {
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.rename.PsiElementRenameHandler");
public static final ExtensionPointName<Condition<PsiElement>> VETO_RENAME_CONDITION_EP = ExtensionPointName.create("com.intellij.vetoRenameCondition");
public static DataKey<String> DEFAULT_NAME = DataKey.create("DEFAULT_NAME");
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file, DataContext dataContext) {
PsiElement element = getElement(dataContext);
if (element == null) {
element = BaseRefactoringAction.getElementAtCaret(editor, file);
}
editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
final PsiElement nameSuggestionContext = InjectedLanguageUtil.findElementAtNoCommit(file, editor.getCaretModel().getOffset());
invoke(element, project, nameSuggestionContext, editor);
}
@Override
public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {
PsiElement element = elements.length == 1 ? elements[0] : null;
if (element == null) element = getElement(dataContext);
LOG.assertTrue(element != null);
Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
if (ApplicationManager.getApplication().isUnitTestMode()) {
final String newName = DEFAULT_NAME.getData(dataContext);
LOG.assertTrue(newName != null);
rename(element, project, element, editor, newName);
}
else {
invoke(element, project, element, editor);
}
}
public static void invoke(PsiElement element, Project project, PsiElement nameSuggestionContext, @Nullable Editor editor) {
if (element != null && !canRename(project, editor, element)) {
return;
}
if (nameSuggestionContext != null &&
nameSuggestionContext.isPhysical() &&
!PsiManager.getInstance(project).isInProject(nameSuggestionContext)) {
final String message = "Selected element is used from non-project files. These usages won't be renamed. Proceed anyway?";
if (ApplicationManager.getApplication().isUnitTestMode()) throw new CommonRefactoringUtil.RefactoringErrorHintException(message);
if (Messages.showYesNoDialog(project, message,
RefactoringBundle.getCannotRefactorMessage(null), Messages.getWarningIcon()) != Messages.YES) {
return;
}
}
FeatureUsageTracker.getInstance().triggerFeatureUsed("refactoring.rename");
rename(element, project, nameSuggestionContext, editor);
}
static boolean canRename(Project project, Editor editor, PsiElement element) throws CommonRefactoringUtil.RefactoringErrorHintException {
String message = renameabilityStatus(project, element);
if (StringUtil.isNotEmpty(message)) {
showErrorMessage(project, editor, message);
return false;
}
return true;
}
@Nullable
static String renameabilityStatus(Project project, PsiElement element) {
if (element == null) return "";
boolean hasRenameProcessor = RenamePsiElementProcessor.forElement(element) != RenamePsiElementProcessor.DEFAULT;
boolean hasWritableMetaData = element instanceof PsiMetaOwner && ((PsiMetaOwner)element).getMetaData() instanceof PsiWritableMetaData;
if (!hasRenameProcessor && !hasWritableMetaData && !(element instanceof PsiNamedElement)) {
return RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("error.wrong.caret.position.symbol.to.rename"));
}
if (!PsiManager.getInstance(project).isInProject(element)) {
if (element.isPhysical()) {
final String message = RefactoringBundle.message("error.out.of.project.element", UsageViewUtil.getType(element));
return RefactoringBundle.getCannotRefactorMessage(message);
}
if (!element.isWritable()) {
return RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("error.cannot.be.renamed"));
}
}
if (InjectedLanguageUtil.isInInjectedLanguagePrefixSuffix(element)) {
final String message = RefactoringBundle.message("error.in.injected.lang.prefix.suffix", UsageViewUtil.getType(element));
return RefactoringBundle.getCannotRefactorMessage(message);
}
return null;
}
static void showErrorMessage(Project project, @Nullable Editor editor, String message) {
CommonRefactoringUtil.showErrorHint(project, editor, message, RefactoringBundle.message("rename.title"), null);
}
public static void rename(PsiElement element, final Project project, PsiElement nameSuggestionContext, Editor editor) {
rename(element, project, nameSuggestionContext, editor, null);
}
private static void rename(PsiElement element, final Project project, PsiElement nameSuggestionContext, Editor editor, String defaultName) {
RenamePsiElementProcessor processor = RenamePsiElementProcessor.forElement(element);
PsiElement substituted = processor.substituteElementToRename(element, editor);
if (substituted == null || !canRename(project, editor, substituted)) return;
RenameDialog dialog = processor.createRenameDialog(project, substituted, nameSuggestionContext, editor);
if (defaultName == null && ApplicationManager.getApplication().isUnitTestMode()) {
String[] strings = dialog.getSuggestedNames();
if (strings != null && strings.length > 0) {
Arrays.sort(strings);
defaultName = strings[0];
} else {
defaultName = "undefined"; // need to avoid show dialog in test
}
}
if (defaultName != null) {
try {
dialog.performRename(defaultName);
}
finally {
dialog.close(DialogWrapper.CANCEL_EXIT_CODE); // to avoid dialog leak
}
}
else {
dialog.show();
}
}
@Override
public boolean isAvailableOnDataContext(DataContext dataContext) {
return !isVetoed(getElement(dataContext));
}
public static boolean isVetoed(PsiElement element) {
if (element == null || element instanceof SyntheticElement) return true;
for(Condition<PsiElement> condition: Extensions.getExtensions(VETO_RENAME_CONDITION_EP)) {
if (condition.value(element)) return true;
}
return false;
}
@Nullable
public static PsiElement getElement(final DataContext dataContext) {
PsiElement[] elementArray = BaseRefactoringAction.getPsiElementArray(dataContext);
if (elementArray.length != 1) {
return null;
}
return elementArray[0];
}
@Override
public boolean isRenaming(DataContext dataContext) {
return isAvailableOnDataContext(dataContext);
}
}