From c83de1280e1a4df0e7670aed233bd5395cf32d41 Mon Sep 17 00:00:00 2001 From: anna Date: Thu, 12 Jan 2012 12:07:42 +0100 Subject: [PATCH] inplace: decouple introduces from rename (I) --- .../InplaceIntroduceConstantPopup.java | 48 +- .../InplaceIntroduceFieldPopup.java | 5 - .../InplaceIntroduceParameterPopup.java | 4 - .../IntroduceVariableBase.java | 2 +- .../JavaVariableInplaceIntroducer.java | 2 +- .../ReassignVariableUtil.java | 6 +- .../template/impl/TemplateState.java | 4 +- .../inplace/AbstractInplaceIntroducer.java | 77 +- .../inplace/InplaceVariableIntroducer.java | 57 +- ...CompletionContributorForInplaceRename.java | 2 +- .../rename/inplace/EscapeHandler.java | 2 +- .../rename/inplace/InplaceRefactoring.java | 722 ++++++++++++++ .../inplace/MemberInplaceRenameHandler.java | 2 +- .../rename/inplace/MemberInplaceRenamer.java | 15 +- .../rename/inplace/RenameChooser.java | 119 +++ .../inplace/VariableInplaceRenameHandler.java | 2 +- .../inplace/VariableInplaceRenamer.java | 887 +++--------------- .../testFramework/UsefulTestCase.java | 4 +- .../fixtures/CodeInsightTestUtil.java | 4 +- 19 files changed, 1050 insertions(+), 914 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/refactoring/rename/inplace/InplaceRefactoring.java create mode 100644 platform/lang-impl/src/com/intellij/refactoring/rename/inplace/RenameChooser.java diff --git a/java/java-impl/src/com/intellij/refactoring/introduceField/InplaceIntroduceConstantPopup.java b/java/java-impl/src/com/intellij/refactoring/introduceField/InplaceIntroduceConstantPopup.java index c5954c17f025..1c9bc6f99343 100644 --- a/java/java-impl/src/com/intellij/refactoring/introduceField/InplaceIntroduceConstantPopup.java +++ b/java/java-impl/src/com/intellij/refactoring/introduceField/InplaceIntroduceConstantPopup.java @@ -172,33 +172,31 @@ public class InplaceIntroduceConstantPopup extends AbstractInplaceIntroduceField } @Override - protected void moveOffsetAfter(boolean success) { - if (success) { - JavaRefactoringSettings.getInstance().INTRODUCE_CONSTANT_MOVE_TO_ANOTHER_CLASS = myMoveToAnotherClassCb.isSelected(); - if (myMoveToAnotherClassCb.isSelected()) { - ApplicationManager.getApplication().invokeLater(new Runnable() { - @Override - public void run() { - myEditor.putUserData(ACTIVE_INTRODUCE, InplaceIntroduceConstantPopup.this); - try { - final IntroduceConstantHandler constantHandler = new IntroduceConstantHandler(); - final PsiLocalVariable localVariable = (PsiLocalVariable)getLocalVariable(); - if (localVariable != null) { - constantHandler.invokeImpl(myProject, localVariable, myEditor); - } - else { - constantHandler.invokeImpl(myProject, myExpr, myEditor); - } + protected boolean performRefactoring() { + JavaRefactoringSettings.getInstance().INTRODUCE_CONSTANT_MOVE_TO_ANOTHER_CLASS = myMoveToAnotherClassCb.isSelected(); + if (myMoveToAnotherClassCb.isSelected()) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + myEditor.putUserData(ACTIVE_INTRODUCE, InplaceIntroduceConstantPopup.this); + try { + final IntroduceConstantHandler constantHandler = new IntroduceConstantHandler(); + final PsiLocalVariable localVariable = (PsiLocalVariable)getLocalVariable(); + if (localVariable != null) { + constantHandler.invokeImpl(myProject, localVariable, myEditor); } - finally { - myEditor.putUserData(ACTIVE_INTRODUCE, null); + else { + constantHandler.invokeImpl(myProject, myExpr, myEditor); } } - }); - return; - } + finally { + myEditor.putUserData(ACTIVE_INTRODUCE, null); + } + } + }); + return false; } - super.moveOffsetAfter(success); + return super.performRefactoring(); } @Override @@ -249,8 +247,4 @@ public class InplaceIntroduceConstantPopup extends AbstractInplaceIntroduceField protected String getActionName() { return "IntroduceConstant"; } - - public String getCommandName() { - return IntroduceConstantHandler.REFACTORING_NAME; - } } diff --git a/java/java-impl/src/com/intellij/refactoring/introduceField/InplaceIntroduceFieldPopup.java b/java/java-impl/src/com/intellij/refactoring/introduceField/InplaceIntroduceFieldPopup.java index 5b1eb5ccdfa7..ffd8645757fc 100644 --- a/java/java-impl/src/com/intellij/refactoring/introduceField/InplaceIntroduceFieldPopup.java +++ b/java/java-impl/src/com/intellij/refactoring/introduceField/InplaceIntroduceFieldPopup.java @@ -185,11 +185,6 @@ public class InplaceIntroduceFieldPopup extends AbstractInplaceIntroduceFieldPop return "IntroduceField"; } - @Override - protected String getCommandName() { - return IntroduceFieldHandler.REFACTORING_NAME; - } - public BaseExpressionToFieldHandler.InitializationPlace getInitializerPlace() { return myIntroduceFieldPanel.getInitializerPlace(); } diff --git a/java/java-impl/src/com/intellij/refactoring/introduceParameter/InplaceIntroduceParameterPopup.java b/java/java-impl/src/com/intellij/refactoring/introduceParameter/InplaceIntroduceParameterPopup.java index ea43d462aa05..0f90eace142a 100644 --- a/java/java-impl/src/com/intellij/refactoring/introduceParameter/InplaceIntroduceParameterPopup.java +++ b/java/java-impl/src/com/intellij/refactoring/introduceParameter/InplaceIntroduceParameterPopup.java @@ -287,10 +287,6 @@ public class InplaceIntroduceParameterPopup extends AbstractJavaInplaceIntroduce return "IntroduceParameter"; } - public String getCommandName() { - return IntroduceParameterHandler.REFACTORING_NAME; - } - private void normalizeParameterIdxAccordingToRemovedParams(TIntArrayList parametersToRemove) { parametersToRemove.forEach(new TIntProcedure() { @Override diff --git a/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableBase.java b/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableBase.java index 3ef062bce126..a7bf3c7ef33a 100644 --- a/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableBase.java +++ b/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableBase.java @@ -573,7 +573,7 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { IntroduceVariableBase.REFACTORING_NAME); renamer.initInitialText(expressionText); PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument()); - renamer.performInplaceRename(false, new LinkedHashSet(Arrays.asList(suggestedName.names))); + renamer.performInplaceRefactoring(new LinkedHashSet(Arrays.asList(suggestedName.names))); } } } diff --git a/java/java-impl/src/com/intellij/refactoring/introduceVariable/JavaVariableInplaceIntroducer.java b/java/java-impl/src/com/intellij/refactoring/introduceVariable/JavaVariableInplaceIntroducer.java index bb9065f2e2ab..25113798b210 100644 --- a/java/java-impl/src/com/intellij/refactoring/introduceVariable/JavaVariableInplaceIntroducer.java +++ b/java/java-impl/src/com/intellij/refactoring/introduceVariable/JavaVariableInplaceIntroducer.java @@ -194,7 +194,7 @@ public class JavaVariableInplaceIntroducer extends InplaceVariableIntroducer ACTIVE_INTRODUCE = Key.create("ACTIVE_INTRODUCE"); private EditorEx myPreview; @@ -168,13 +166,6 @@ public abstract class AbstractInplaceIntroducer> stringUsages) { - return true; - } - - @Override - protected void collectAdditionalElementsToRename(boolean processTextOccurrences, List> stringUsages) { + protected void collectAdditionalElementsToRename(List> stringUsages) { if (isReplaceAllOccurrences()) { for (E expression : getOccurrences()) { stringUsages.add(Pair.create(expression, new TextRange(0, expression.getTextLength()))); @@ -425,7 +411,6 @@ public abstract class AbstractInplaceIntroducer extends VariableInplaceRenamer { +public abstract class InplaceVariableIntroducer extends InplaceRefactoring { public static final Key INTRODUCE_RESTART = Key.create("INTRODUCE_RESTART"); protected E myExpr; @@ -66,13 +62,14 @@ public class InplaceVariableIntroducer extends VariableInp protected Balloon myBalloon; protected String myTitle; protected RelativePoint myTarget; + private RangeMarker myCaretRangeMarker; public InplaceVariableIntroducer(PsiNamedElement elementToRename, Editor editor, Project project, String title, E[] occurrences, @Nullable E expr) { - super(elementToRename, editor, project); + super(editor, elementToRename, project); myTitle = title; myOccurrences = occurrences; myExpr = expr; @@ -86,8 +83,8 @@ public class InplaceVariableIntroducer extends VariableInp } @Override - protected int getOffsetForCaret(RangeMarker rangeMarker, int offset) { - return rangeMarker.isValid() ? rangeMarker.getStartOffset() : offset; + protected int restoreCaretOffset(int offset) { + return myCaretRangeMarker.isValid() ? myCaretRangeMarker.getStartOffset() : offset; } @Override @@ -141,8 +138,28 @@ public class InplaceVariableIntroducer extends VariableInp } @Override - public boolean performInplaceRename(boolean processTextOccurrences, LinkedHashSet nameSuggestions) { - final boolean result = super.performInplaceRename(processTextOccurrences, nameSuggestions); + protected boolean performRefactoring() { + return false; + } + + @Override + protected void beforeTemplateStart() { + myCaretRangeMarker = myEditor.getDocument() + .createRangeMarker(new TextRange(myEditor.getCaretModel().getOffset(), myEditor.getCaretModel().getOffset())); + } + + @Override + protected void collectAdditionalElementsToRename(List> stringUsages) { + } + + @Override + protected String getCommandName() { + return myTitle; + } + + @Override + public boolean performInplaceRefactoring(LinkedHashSet nameSuggestions) { + final boolean result = super.performInplaceRefactoring(nameSuggestions); if (result) { if (myBalloon == null) { showBalloon(); @@ -225,21 +242,7 @@ public class InplaceVariableIntroducer extends VariableInp } } } - return super.createLookupItems(lookupItems, name); + return lookupItems; } - @Override - protected TextRange preserveSelectedRange(SelectionModel selectionModel) { - return null; - } - - @Override - protected void performOnInvalidIdentifier(String newName, LinkedHashSet nameSuggestions) { - //move logic to performRefactoring - } - - @Override - protected boolean performAutomaticRename() { - return false; - } } diff --git a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/CompletionContributorForInplaceRename.java b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/CompletionContributorForInplaceRename.java index 1a6069b582a9..ca10bd016c5a 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/CompletionContributorForInplaceRename.java +++ b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/CompletionContributorForInplaceRename.java @@ -33,7 +33,7 @@ public class CompletionContributorForInplaceRename extends CompletionContributor final Editor editor = parameters.getLookup().getEditor(); final TemplateState state = TemplateManagerImpl.getTemplateState(editor); if (state != null) { - if (editor.getUserData(VariableInplaceRenamer.INPLACE_RENAMER) != null) { + if (editor.getUserData(InplaceRefactoring.INPLACE_RENAMER) != null) { result.stopHere(); } } diff --git a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/EscapeHandler.java b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/EscapeHandler.java index ef7f66d7a8c8..bd7a043088b4 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/EscapeHandler.java +++ b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/EscapeHandler.java @@ -39,7 +39,7 @@ public class EscapeHandler extends EditorActionHandler { final SelectionModel selectionModel = editor.getSelectionModel(); if (selectionModel.hasSelection()) { final TemplateState state = TemplateManagerImpl.getTemplateState(editor); - if (state != null && editor.getUserData(VariableInplaceRenamer.INPLACE_RENAMER) != null) { + if (state != null && editor.getUserData(InplaceRefactoring.INPLACE_RENAMER) != null) { final LookupImpl lookup = (LookupImpl)LookupManager.getActiveLookup(editor); if (lookup != null) { selectionModel.removeSelection(); diff --git a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/InplaceRefactoring.java b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/InplaceRefactoring.java new file mode 100644 index 000000000000..5ce51eca1889 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/InplaceRefactoring.java @@ -0,0 +1,722 @@ +/* + * Copyright 2000-2012 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.inplace; + +import com.intellij.codeInsight.completion.InsertHandler; +import com.intellij.codeInsight.completion.InsertionContext; +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; +import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl; +import com.intellij.codeInsight.highlighting.HighlightManager; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.codeInsight.lookup.LookupManager; +import com.intellij.codeInsight.lookup.impl.LookupImpl; +import com.intellij.codeInsight.template.*; +import com.intellij.codeInsight.template.Result; +import com.intellij.codeInsight.template.impl.TemplateManagerImpl; +import com.intellij.codeInsight.template.impl.TemplateState; +import com.intellij.injected.editor.VirtualFileWindow; +import com.intellij.openapi.actionSystem.Shortcut; +import com.intellij.openapi.application.*; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.command.impl.FinishMarkAction; +import com.intellij.openapi.command.impl.StartMarkAction; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.RangeMarker; +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.extensions.Extensions; +import com.intellij.openapi.fileEditor.FileEditor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.fileEditor.TextEditor; +import com.intellij.openapi.keymap.Keymap; +import com.intellij.openapi.keymap.KeymapManager; +import com.intellij.openapi.keymap.KeymapUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.search.ProjectScope; +import com.intellij.psi.search.PsiSearchHelper; +import com.intellij.psi.search.SearchScope; +import com.intellij.psi.search.searches.ReferencesSearch; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.refactoring.rename.NameSuggestionProvider; +import com.intellij.refactoring.util.CommonRefactoringUtil; +import com.intellij.util.containers.Stack; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import java.util.*; + +/** + * User: anna + * Date: 1/11/12 + */ +public abstract class InplaceRefactoring { + protected static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.rename.inplace.VariableInplaceRenamer"); + @NonNls protected static final String PRIMARY_VARIABLE_NAME = "PrimaryVariable"; + @NonNls protected static final String OTHER_VARIABLE_NAME = "OtherVariable"; + protected static final Stack ourRenamersStack = new Stack(); + public static final Key INPLACE_RENAMER = Key.create("EditorInplaceRenamer"); + protected PsiNamedElement myElementToRename; + protected final Editor myEditor; + protected final Project myProject; + protected RangeMarker myRenameOffset; + private String myAdvertisementText; + private ArrayList myHighlighters; + protected String myInitialName; + protected final String myOldName; + protected RangeMarker myBeforeRevert = null; + protected String myInsertedName; + protected LinkedHashSet myNameSuggestions; + + protected StartMarkAction myMarkAction; + protected PsiElement myScope; + + public InplaceRefactoring(Editor editor, PsiNamedElement elementToRename, Project project) { + this(editor, elementToRename, project, elementToRename != null ? elementToRename.getName() : null, + elementToRename != null ? elementToRename.getName() : null); + } + + public InplaceRefactoring(Editor editor, PsiNamedElement elementToRename, Project project, final String oldName) { + this(editor, elementToRename, project, elementToRename != null ? elementToRename.getName() : null, oldName); + } + + public InplaceRefactoring( + Editor editor, PsiNamedElement elementToRename, Project project, String initialName, final String oldName) { + myEditor = /*(editor instanceof EditorWindow)? ((EditorWindow)editor).getDelegate() : */editor; + myElementToRename = elementToRename; + myProject = project; + myOldName = oldName; + if (myElementToRename != null) { + myInitialName = initialName; + final PsiFile containingFile = myElementToRename.getContainingFile(); + if (!notSameFile(getTopLevelVirtualFile(containingFile.getViewProvider()), containingFile)) { + myRenameOffset = myElementToRename != null && myElementToRename.getTextRange() != null ? myEditor.getDocument() + .createRangeMarker(myElementToRename.getTextRange()) : null; + } + } + } + + public void setAdvertisementText(String advertisementText) { + myAdvertisementText = advertisementText; + } + + + public boolean performInplaceRefactoring(final LinkedHashSet nameSuggestions) { + myNameSuggestions = nameSuggestions; + if (InjectedLanguageUtil.isInInjectedLanguagePrefixSuffix(myElementToRename)) { + return false; + } + + final FileViewProvider fileViewProvider = myElementToRename.getContainingFile().getViewProvider(); + VirtualFile file = getTopLevelVirtualFile(fileViewProvider); + + SearchScope referencesSearchScope = getReferencesSearchScope(file); + + final Collection refs = collectRefs(referencesSearchScope); + + addReferenceAtCaret(refs); + + for (PsiReference ref : refs) { + final PsiFile containingFile = ref.getElement().getContainingFile(); + + if (notSameFile(file, containingFile)) { + return false; + } + } + + final PsiElement scope = checkLocalScope(); + + if (scope == null) { + return false; // Should have valid local search scope for inplace rename + } + + final PsiFile containingFile = scope.getContainingFile(); + if (containingFile == null) { + return false; // Should have valid local search scope for inplace rename + } + //no need to process further when file is read-only + if (!CommonRefactoringUtil.checkReadOnlyStatus(myProject, containingFile)) return true; + + myEditor.putUserData(INPLACE_RENAMER, this); + ourRenamersStack.push(this); + + final List> stringUsages = new ArrayList>(); + collectAdditionalElementsToRename(stringUsages); + return buildTemplateAndStart(refs, stringUsages, scope, containingFile); + } + + protected boolean notSameFile(@Nullable VirtualFile file, PsiFile containingFile) { + return getTopLevelVirtualFile(containingFile.getViewProvider()) != file; + } + + protected SearchScope getReferencesSearchScope(VirtualFile file) { + return file == null || ProjectRootManager.getInstance(myProject).getFileIndex().isInContent(file) + ? ProjectScope.getProjectScope(myElementToRename.getProject()) + : new LocalSearchScope(myElementToRename.getContainingFile()); + } + + @Nullable + protected PsiElement checkLocalScope() { + final SearchScope searchScope = PsiSearchHelper.SERVICE.getInstance(myElementToRename.getProject()).getUseScope(myElementToRename); + if (searchScope instanceof LocalSearchScope) { + final PsiElement[] elements = ((LocalSearchScope)searchScope).getScope(); + return PsiTreeUtil.findCommonParent(elements); + } + + return null; + } + + protected abstract void collectAdditionalElementsToRename(final List> stringUsages); + + protected abstract boolean shouldSelectAll(); + + protected abstract LookupElement[] createLookupItems(LookupElement[] lookupItems, String name); + + protected Collection collectRefs(SearchScope referencesSearchScope) { + return ReferencesSearch.search(myElementToRename, referencesSearchScope, false).findAll(); + } + + protected boolean buildTemplateAndStart(final Collection refs, + final Collection> stringUsages, + final PsiElement scope, + final PsiFile containingFile) { + final PsiElement context = containingFile.getContext(); + myScope = context != null ? context.getContainingFile() : scope; + final TemplateBuilderImpl builder = new TemplateBuilderImpl(myScope); + + PsiElement nameIdentifier = getNameIdentifier(); + int offset = myEditor.getCaretModel().getOffset(); + PsiElement selectedElement = getSelectedInEditorElement(nameIdentifier, refs, stringUsages, offset); + + if (nameIdentifier != null) addVariable(nameIdentifier, selectedElement, builder); + for (PsiReference ref : refs) { + addVariable(ref, selectedElement, builder, offset); + } + for (Pair usage : stringUsages) { + addVariable(usage.first, usage.second, selectedElement, builder); + } + addAdditionalVariables(builder); + try { + myMarkAction = startRename(); + } + catch (final StartMarkAction.AlreadyStartedException e) { + final Document oldDocument = e.getDocument(); + if (oldDocument != myEditor.getDocument()) { + final int exitCode = Messages.showOkCancelDialog(myProject, e.getMessage(), getCommandName(), + "Navigate to continue", "Cancel started", Messages.getErrorIcon()); + if (exitCode == -1) return true; + navigateToAlreadyStarted(oldDocument, exitCode); + return true; + } + else { + revertState(); + } + return false; + } + + beforeTemplateStart(); + + new WriteCommandAction(myProject, getCommandName()) { + @Override + protected void run(com.intellij.openapi.application.Result result) throws Throwable { + startTemplate(builder); + } + }.execute(); + return true; + } + + protected abstract void beforeTemplateStart(); + + private void startTemplate(final TemplateBuilderImpl builder) { + + final DaemonCodeAnalyzer daemonCodeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject); + + final boolean previousUpdate; + if (daemonCodeAnalyzer != null) { + previousUpdate = ((DaemonCodeAnalyzerImpl)daemonCodeAnalyzer).isUpdateByTimerEnabled(); + daemonCodeAnalyzer.setUpdateByTimerEnabled(false); + } + else { + previousUpdate = false; + } + + final MyTemplateListener templateListener = new MyTemplateListener() { + @Override + protected void restoreDaemonUpdateState() { + if (daemonCodeAnalyzer != null) { + daemonCodeAnalyzer.setUpdateByTimerEnabled(previousUpdate); + } + } + }; + + final int offset = myEditor.getCaretModel().getOffset(); + + Template template = builder.buildInlineTemplate(); + template.setToShortenLongNames(false); + TextRange range = myScope.getTextRange(); + assert range != null; + myHighlighters = new ArrayList(); + Editor topLevelEditor = InjectedLanguageUtil.getTopLevelEditor(myEditor); + topLevelEditor.getCaretModel().moveToOffset(range.getStartOffset()); + + TemplateManager.getInstance(myProject).startTemplate(topLevelEditor, template, templateListener); + restoreOldCaretPositionAndSelection(offset); + highlightTemplateVariables(template, topLevelEditor); + } + + private void highlightTemplateVariables(Template template, Editor topLevelEditor) { + //add highlights + if (myHighlighters != null) { // can be null if finish is called during testing + Map rangesToHighlight = new HashMap(); + final TemplateState templateState = TemplateManagerImpl.getTemplateState(topLevelEditor); + if (templateState != null) { + EditorColorsManager colorsManager = EditorColorsManager.getInstance(); + for (int i = 0; i < templateState.getSegmentsCount(); i++) { + final TextRange segmentOffset = templateState.getSegmentRange(i); + final String name = template.getSegmentName(i); + TextAttributes attributes = null; + if (name.equals(PRIMARY_VARIABLE_NAME)) { + attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.WRITE_SEARCH_RESULT_ATTRIBUTES); + } + else if (name.equals(OTHER_VARIABLE_NAME)) { + attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES); + } + if (attributes == null) continue; + rangesToHighlight.put(segmentOffset, attributes); + } + } + addHighlights(rangesToHighlight, topLevelEditor, myHighlighters, HighlightManager.getInstance(myProject)); + } + } + + private void restoreOldCaretPositionAndSelection(final int offset) { + //move to old offset + Runnable runnable = new Runnable() { + public void run() { + myEditor.getCaretModel().moveToOffset(restoreCaretOffset(offset)); + restoreSelection(); + } + }; + + final LookupImpl lookup = (LookupImpl)LookupManager.getActiveLookup(myEditor); + if (lookup != null && lookup.getLookupStart() <= (restoreCaretOffset(offset))) { + lookup.setFocused(false); + lookup.performGuardedChange(runnable); + } + else { + runnable.run(); + } + } + + protected void restoreSelection() { + } + + protected int restoreCaretOffset(int offset) { + return offset; + } + + protected void navigateToAlreadyStarted(Document oldDocument, int exitCode) { + final PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(oldDocument); + if (file != null) { + final VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile != null) { + final FileEditor[] editors = FileEditorManager.getInstance(myProject).getEditors(virtualFile); + for (FileEditor editor : editors) { + if (editor instanceof TextEditor) { + final Editor textEditor = ((TextEditor)editor).getEditor(); + final TemplateState templateState = TemplateManagerImpl.getTemplateState(textEditor); + if (templateState != null) { + if (exitCode == DialogWrapper.OK_EXIT_CODE) { + final TextRange range = templateState.getVariableRange(PRIMARY_VARIABLE_NAME); + if (range != null) { + new OpenFileDescriptor(myProject, virtualFile, range.getStartOffset()).navigate(true); + return; + } + } + else { + templateState.gotoEnd(); + return; + } + } + } + } + } + } + } + + @Nullable + protected PsiElement getNameIdentifier() { + return myElementToRename instanceof PsiNameIdentifierOwner ? ((PsiNameIdentifierOwner)myElementToRename).getNameIdentifier() : null; + } + + @Nullable + protected StartMarkAction startRename() throws StartMarkAction.AlreadyStartedException { + final StartMarkAction[] markAction = new StartMarkAction[1]; + final StartMarkAction.AlreadyStartedException[] ex = new StartMarkAction.AlreadyStartedException[1]; + CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { + @Override + public void run() { + try { + markAction[0] = StartMarkAction.start(myEditor, myProject, getCommandName()); + } + catch (StartMarkAction.AlreadyStartedException e) { + ex[0] = e; + } + } + }, getCommandName(), null); + if (ex[0] != null) throw ex[0]; + return markAction[0]; + } + + @Nullable + protected PsiNamedElement getVariable() { + if (myElementToRename != null && myElementToRename.isValid()) return myElementToRename; + final PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument()); + if (psiFile != null) { + return PsiTreeUtil.getParentOfType(psiFile.findElementAt(myRenameOffset.getStartOffset()), PsiNameIdentifierOwner.class); + } + return myElementToRename; + } + + /** + * Called after the completion of the refactoring, either a successful or a failed one. + * + * @param success true if the refactoring was accepted, false if it was cancelled (by undo or Esc) + */ + protected void moveOffsetAfter(boolean success) { + } + + protected void addAdditionalVariables(TemplateBuilderImpl builder) { + } + + protected void addReferenceAtCaret(Collection refs) { + PsiFile myEditorFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument()); + // Note, that myEditorFile can be different from myElement.getContainingFile() e.g. in injections: myElement declaration in one + // file / usage in another ! + final PsiReference reference = (myEditorFile != null ? + myEditorFile : myElementToRename.getContainingFile()) + .findReferenceAt(myEditor.getCaretModel().getOffset()); + if (reference != null && !refs.contains(reference)) { + refs.add(reference); + } + } + + protected void showDialogAdvertisement(final String actionId) { + final Keymap keymap = KeymapManager.getInstance().getActiveKeymap(); + final Shortcut[] shortcuts = keymap.getShortcuts(actionId); + if (shortcuts.length > 0) { + setAdvertisementText("Press " + KeymapUtil.getShortcutText(shortcuts[0]) + " to show dialog"); + } + } + + public String getInitialName() { + if (myInitialName == null) { + final PsiNamedElement variable = getVariable(); + if (variable != null) { + return variable.getName(); + } + } + return myInitialName; + } + + protected void revertState() { + CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { + public void run() { + final Editor topLevelEditor = InjectedLanguageUtil.getTopLevelEditor(myEditor); + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + final TemplateState state = TemplateManagerImpl.getTemplateState(topLevelEditor); + assert state != null; + final int segmentsCount = state.getSegmentsCount(); + final Document document = topLevelEditor.getDocument(); + for (int i = 0; i < segmentsCount; i++) { + final TextRange segmentRange = state.getSegmentRange(i); + document.replaceString(segmentRange.getStartOffset(), segmentRange.getEndOffset(), myOldName); + } + } + }); + PsiDocumentManager.getInstance(myProject).commitDocument(topLevelEditor.getDocument()); + } + }, getCommandName(), null); + } + + /** + * Returns the name of the command performed by the refactoring. + * + * @return command name + */ + protected abstract String getCommandName(); + + public void finish() { + if (!ourRenamersStack.isEmpty() && ourRenamersStack.peek() == this) { + ourRenamersStack.pop(); + } + if (myHighlighters != null) { + if (!myProject.isDisposed()) { + final HighlightManager highlightManager = HighlightManager.getInstance(myProject); + for (RangeHighlighter highlighter : myHighlighters) { + highlightManager.removeSegmentHighlighter(myEditor, highlighter); + } + } + + myHighlighters = null; + myEditor.putUserData(INPLACE_RENAMER, null); + } + } + + protected void addHighlights(@NotNull Map ranges, + @NotNull Editor editor, + @NotNull Collection highlighters, + @NotNull HighlightManager highlightManager) { + for (Map.Entry entry : ranges.entrySet()) { + TextRange range = entry.getKey(); + TextAttributes attributes = entry.getValue(); + highlightManager.addOccurrenceHighlight(editor, range.getStartOffset(), range.getEndOffset(), attributes, 0, highlighters, null); + } + + for (RangeHighlighter highlighter : highlighters) { + highlighter.setGreedyToLeft(true); + highlighter.setGreedyToRight(true); + } + } + + protected abstract boolean performRefactoring(); + + private void addVariable(final PsiReference reference, + final PsiElement selectedElement, + final TemplateBuilderImpl builder, + int offset) { + if (reference.getElement() == selectedElement && + contains(reference.getRangeInElement().shiftRight(selectedElement.getTextRange().getStartOffset()), offset)) { + Expression expression = new MyExpression(getInitialName(), myNameSuggestions); + builder.replaceElement(reference, PRIMARY_VARIABLE_NAME, expression, true); + } + else { + builder.replaceElement(reference, OTHER_VARIABLE_NAME, PRIMARY_VARIABLE_NAME, false); + } + } + + private void addVariable(final PsiElement element, + final PsiElement selectedElement, + final TemplateBuilderImpl builder) { + addVariable(element, null, selectedElement, builder); + } + + private void addVariable(final PsiElement element, + @Nullable final TextRange textRange, + final PsiElement selectedElement, + final TemplateBuilderImpl builder) { + if (element == selectedElement) { + Expression expression = new MyExpression(getInitialName(), myNameSuggestions); + builder.replaceElement(element, PRIMARY_VARIABLE_NAME, expression, true); + } + else if (textRange != null) { + builder.replaceElement(element, textRange, OTHER_VARIABLE_NAME, PRIMARY_VARIABLE_NAME, false); + } + else { + builder.replaceElement(element, OTHER_VARIABLE_NAME, PRIMARY_VARIABLE_NAME, false); + } + } + + + public void setElementToRename(PsiNamedElement elementToRename) { + myElementToRename = elementToRename; + } + + protected static VirtualFile getTopLevelVirtualFile(final FileViewProvider fileViewProvider) { + VirtualFile file = fileViewProvider.getVirtualFile(); + if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate(); + return file; + } + + @TestOnly + public static void checkCleared() { + try { + assert ourRenamersStack.isEmpty() : ourRenamersStack; + } + finally { + ourRenamersStack.clear(); + } + } + + private static PsiElement getSelectedInEditorElement(@Nullable PsiElement nameIdentifier, + final Collection refs, + Collection> stringUsages, + final int offset) { + if (nameIdentifier != null) { + final TextRange range = nameIdentifier.getTextRange(); + if (contains(range, offset)) return nameIdentifier; + } + + for (PsiReference ref : refs) { + final PsiElement element = ref.getElement(); + if (contains(ref.getRangeInElement().shiftRight(element.getTextRange().getStartOffset()), offset)) return element; + } + + for (Pair stringUsage : stringUsages) { + final PsiElement element = stringUsage.first; + if (contains(stringUsage.second.shiftRight(element.getTextRange().getStartOffset()), offset)) return element; + } + + LOG.assertTrue(false); + return null; + } + + private static boolean contains(final TextRange range, final int offset) { + return range.getStartOffset() <= offset && offset <= range.getEndOffset(); + } + + protected class MyExpression extends Expression { + private final String myName; + private final LookupElement[] myLookupItems; + + protected MyExpression(String name, LinkedHashSet names) { + myName = name; + if (names == null) { + names = new LinkedHashSet(); + for (NameSuggestionProvider provider : Extensions.getExtensions(NameSuggestionProvider.EP_NAME)) { + provider.getSuggestedNames(myElementToRename, myElementToRename, names); + } + } + myLookupItems = new LookupElement[names.size()]; + final Iterator iterator = names.iterator(); + for (int i = 0; i < myLookupItems.length; i++) { + final String suggestion = iterator.next(); + myLookupItems[i] = LookupElementBuilder.create(suggestion).setInsertHandler(new InsertHandler() { + @Override + public void handleInsert(InsertionContext context, LookupElement item) { + if (shouldSelectAll()) return; + final Editor topLevelEditor = InjectedLanguageUtil.getTopLevelEditor(myEditor); + final TemplateState templateState = TemplateManagerImpl.getTemplateState(topLevelEditor); + if (templateState != null) { + final TextRange range = templateState.getCurrentVariableRange(); + if (range != null) { + topLevelEditor.getDocument().replaceString(range.getStartOffset(), range.getEndOffset(), suggestion); + } + } + } + }); + } + } + + public LookupElement[] calculateLookupItems(ExpressionContext context) { + return createLookupItems(myLookupItems, myName); + } + + public Result calculateQuickResult(ExpressionContext context) { + return calculateResult(context); + } + + public Result calculateResult(ExpressionContext context) { + TemplateState templateState = TemplateManagerImpl.getTemplateState(myEditor); + final TextResult insertedValue = templateState != null ? templateState.getVariableValue(PRIMARY_VARIABLE_NAME) : null; + if (insertedValue != null) { + if (!insertedValue.getText().isEmpty()) return insertedValue; + } + return new TextResult(myName); + } + + @Override + public String getAdvertisingText() { + return myAdvertisementText; + } + } + + private abstract class MyTemplateListener extends TemplateEditingAdapter { + + protected abstract void restoreDaemonUpdateState(); + + public void beforeTemplateFinished(final TemplateState templateState, Template template) { + try { + final TextResult value = templateState.getVariableValue(PRIMARY_VARIABLE_NAME); + myInsertedName = value != null ? value.toString() : null; + + final int currentOffset = myEditor.getCaretModel().getOffset(); + myBeforeRevert = + myRenameOffset != null && myRenameOffset.getEndOffset() >= currentOffset && myRenameOffset.getStartOffset() <= currentOffset + ? myEditor.getDocument().createRangeMarker(myRenameOffset.getStartOffset(), currentOffset) + : null; + if (myBeforeRevert != null) { + myBeforeRevert.setGreedyToRight(true); + } + finish(); + } + finally { + restoreDaemonUpdateState(); + } + } + + @Override + public void templateFinished(Template template, final boolean brokenOff) { + boolean bind = false; + try { + super.templateFinished(template, brokenOff); + if (!brokenOff) { + bind = performRefactoring(); + } + moveOffsetAfter(!brokenOff); + } + finally { + if (!bind) { + FinishMarkAction.finish(myProject, myEditor, myMarkAction); + if (myBeforeRevert != null) { + myBeforeRevert.dispose(); + } + } + } + } + + public void templateCancelled(Template template) { + try { + final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); + documentManager.commitAllDocuments(); + finish(); + moveOffsetAfter(false); + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + documentManager.doPostponedOperationsAndUnblockDocument(myEditor.getDocument()); + } + }); + } + finally { + try { + restoreDaemonUpdateState(); + } + finally { + FinishMarkAction.finish(myProject, myEditor, myMarkAction); + } + } + } + } +} diff --git a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/MemberInplaceRenameHandler.java b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/MemberInplaceRenameHandler.java index 0562651bc28c..77c672dde956 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/MemberInplaceRenameHandler.java +++ b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/MemberInplaceRenameHandler.java @@ -48,7 +48,7 @@ public class MemberInplaceRenameHandler extends VariableInplaceRenameHandler { } @Override - public VariableInplaceRenamer doRename(@NotNull final PsiElement elementToRename, final Editor editor, final DataContext dataContext) { + public InplaceRefactoring doRename(@NotNull final PsiElement elementToRename, final Editor editor, final DataContext dataContext) { if (elementToRename instanceof PsiNameIdentifierOwner) { final RenamePsiElementProcessor processor = RenamePsiElementProcessor.forElement(elementToRename); if (processor.isInplaceRenameSupported()) { diff --git a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/MemberInplaceRenamer.java b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/MemberInplaceRenamer.java index 97e5dea66d97..58bf29a516cb 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/MemberInplaceRenamer.java +++ b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/MemberInplaceRenamer.java @@ -138,7 +138,7 @@ public class MemberInplaceRenamer extends VariableInplaceRenamer { } @Override - protected boolean appendAdditionalElement(List> stringUsages) { + protected boolean appendAdditionalElement(Collection> stringUsages) { boolean showChooser = super.appendAdditionalElement(stringUsages); PsiNamedElement variable = getVariable(); if (variable != null) { @@ -157,7 +157,7 @@ public class MemberInplaceRenamer extends VariableInplaceRenamer { return showChooser; } - private void appendAdditionalElement(List> stringUsages, + private void appendAdditionalElement(Collection> stringUsages, PsiNamedElement variable, PsiElement element) { if (element != variable && element instanceof PsiNameIdentifierOwner && @@ -170,7 +170,6 @@ public class MemberInplaceRenamer extends VariableInplaceRenamer { } protected void performRefactoringRename(final String newName, - PsiElement context, final StartMarkAction markAction) { try { final PsiNamedElement variable = getVariable(); @@ -216,20 +215,16 @@ public class MemberInplaceRenamer extends VariableInplaceRenamer { } @Override - protected void collectAdditionalElementsToRename(boolean processTextOccurrences, List> stringUsages) { + protected void collectAdditionalElementsToRename(List> stringUsages) { //do not highlight non-code usages in file } @Override - protected void restoreStateBeforeTemplateIsFinished() { + public void finish() { + super.finish(); revertState(); } - @Override - protected String getNewName(String newName, ResolveSnapshotProvider.ResolveSnapshot snapshot) { - return newName; - } - @Nullable public PsiElement getSubstituted() { if (mySubstituted != null && mySubstituted.isValid()) return mySubstituted; diff --git a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/RenameChooser.java b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/RenameChooser.java new file mode 100644 index 000000000000..2f5243a75284 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/RenameChooser.java @@ -0,0 +1,119 @@ +/* + * Copyright 2000-2012 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.inplace; + +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.colors.EditorColors; +import com.intellij.openapi.editor.colors.EditorColorsManager; +import com.intellij.openapi.editor.markup.*; +import com.intellij.openapi.ui.popup.JBPopupAdapter; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.ui.popup.LightweightWindowEvent; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiReference; +import com.intellij.ui.components.JBList; +import org.jetbrains.annotations.NonNls; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import java.util.*; + +/** +* User: anna +* Date: 1/11/12 +*/ +abstract class RenameChooser { + @NonNls private static final String CODE_OCCURRENCES = "Rename code occurrences"; + @NonNls private static final String ALL_OCCURRENCES = "Rename all occurrences"; + private final Set myRangeHighlighters = new HashSet(); + private final Editor myEditor; + private final TextAttributes myAttributes; + + public RenameChooser(Editor editor) { + myEditor = editor; + myAttributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES); + } + + protected abstract void runRenameTemplate(Collection> stringUsages); + + public void showChooser(final Collection refs, + final Collection> stringUsages) { + + final DefaultListModel model = new DefaultListModel(); + model.addElement(CODE_OCCURRENCES); + model.addElement(ALL_OCCURRENCES); + final JList list = new JBList(model); + + list.addListSelectionListener(new ListSelectionListener() { + public void valueChanged(final ListSelectionEvent e) { + final String selectedValue = (String)list.getSelectedValue(); + if (selectedValue == null) return; + dropHighlighters(); + final MarkupModel markupModel = myEditor.getMarkupModel(); + + if (selectedValue == ALL_OCCURRENCES) { + for (Pair pair : stringUsages) { + final TextRange textRange = pair.second.shiftRight(pair.first.getTextOffset()); + final RangeHighlighter rangeHighlighter = markupModel.addRangeHighlighter( + textRange.getStartOffset(), textRange.getEndOffset(), HighlighterLayer.SELECTION - 1, myAttributes, + HighlighterTargetArea.EXACT_RANGE); + myRangeHighlighters.add(rangeHighlighter); + } + } + + for (PsiReference reference : refs) { + final PsiElement element = reference.getElement(); + if (element == null) continue; + final TextRange textRange = element.getTextRange(); + final RangeHighlighter rangeHighlighter = markupModel.addRangeHighlighter( + textRange.getStartOffset(), textRange.getEndOffset(), HighlighterLayer.SELECTION - 1, myAttributes, + HighlighterTargetArea.EXACT_RANGE); + myRangeHighlighters.add(rangeHighlighter); + } + } + }); + + JBPopupFactory.getInstance().createListPopupBuilder(list) + .setTitle("String occurrences found") + .setMovable(false) + .setResizable(false) + .setRequestFocus(true) + .setItemChoosenCallback(new Runnable() { + public void run() { + runRenameTemplate(list.getSelectedValue() == ALL_OCCURRENCES ? stringUsages : new ArrayList>()); + } + }) + .addListener(new JBPopupAdapter() { + @Override + public void onClosed(LightweightWindowEvent event) { + dropHighlighters(); + } + }) + .createPopup().showInBestPositionFor(myEditor); + } + + + + private void dropHighlighters() { + for (RangeHighlighter highlight : myRangeHighlighters) { + highlight.dispose(); + } + myRangeHighlighters.clear(); + } +} diff --git a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/VariableInplaceRenameHandler.java b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/VariableInplaceRenameHandler.java index cfc21482fab4..8c300ec631a9 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/VariableInplaceRenameHandler.java +++ b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/VariableInplaceRenameHandler.java @@ -107,7 +107,7 @@ public class VariableInplaceRenameHandler implements RenameHandler { } @Nullable - public VariableInplaceRenamer doRename(final @NotNull PsiElement elementToRename, final Editor editor, final DataContext dataContext) { + public InplaceRefactoring doRename(final @NotNull PsiElement elementToRename, final Editor editor, final DataContext dataContext) { VariableInplaceRenamer renamer = createRenamer(elementToRename, editor); boolean startedRename = renamer == null ? false : renamer.performInplaceRename(); diff --git a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/VariableInplaceRenamer.java b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/VariableInplaceRenamer.java index d5b924d69137..0c5fdc8cb062 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/VariableInplaceRenamer.java +++ b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/VariableInplaceRenamer.java @@ -15,115 +15,56 @@ */ package com.intellij.refactoring.rename.inplace; -import com.intellij.codeInsight.completion.InsertHandler; -import com.intellij.codeInsight.completion.InsertionContext; -import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; -import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl; -import com.intellij.codeInsight.highlighting.HighlightManager; import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.codeInsight.lookup.LookupElementBuilder; -import com.intellij.codeInsight.lookup.LookupManager; -import com.intellij.codeInsight.lookup.impl.LookupImpl; -import com.intellij.codeInsight.template.*; -import com.intellij.codeInsight.template.impl.TemplateManagerImpl; -import com.intellij.codeInsight.template.impl.TemplateState; -import com.intellij.injected.editor.VirtualFileWindow; +import com.intellij.lang.Language; import com.intellij.lang.LanguageExtension; import com.intellij.lang.LanguageNamesValidation; -import com.intellij.openapi.actionSystem.Shortcut; +import com.intellij.lang.refactoring.NamesValidator; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.application.Result; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.command.impl.FinishMarkAction; import com.intellij.openapi.command.impl.StartMarkAction; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.RangeMarker; 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.*; import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.fileEditor.FileEditor; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.fileEditor.OpenFileDescriptor; -import com.intellij.openapi.fileEditor.TextEditor; -import com.intellij.openapi.keymap.Keymap; -import com.intellij.openapi.keymap.KeymapManager; -import com.intellij.openapi.keymap.KeymapUtil; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.ui.popup.JBPopupAdapter; import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.ui.popup.LightweightWindowEvent; -import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; -import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; -import com.intellij.psi.search.LocalSearchScope; -import com.intellij.psi.search.ProjectScope; -import com.intellij.psi.search.PsiSearchHelper; -import com.intellij.psi.search.SearchScope; -import com.intellij.psi.search.searches.ReferencesSearch; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import com.intellij.refactoring.RefactoringBundle; -import com.intellij.refactoring.rename.*; +import com.intellij.refactoring.rename.AutomaticRenamingDialog; +import com.intellij.refactoring.rename.RenameHandlerRegistry; +import com.intellij.refactoring.rename.RenameProcessor; +import com.intellij.refactoring.rename.RenameUtil; import com.intellij.refactoring.rename.naming.AutomaticRenamer; import com.intellij.refactoring.rename.naming.AutomaticRenamerFactory; import com.intellij.refactoring.util.CommonRefactoringUtil; import com.intellij.refactoring.util.TextOccurrencesUtil; -import com.intellij.ui.components.JBList; import com.intellij.usageView.UsageInfo; import com.intellij.usageView.UsageViewUtil; import com.intellij.util.PairProcessor; import com.intellij.util.containers.MultiMap; -import com.intellij.util.containers.Stack; -import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; -import javax.swing.*; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; /** * @author ven */ -public class VariableInplaceRenamer { - public static final Key INPLACE_RENAMER = Key.create("EditorInplaceRenamer"); - protected static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.rename.inplace.VariableInplaceRenamer"); +public class VariableInplaceRenamer extends InplaceRefactoring { public static final LanguageExtension INSTANCE = new LanguageExtension( "com.intellij.rename.inplace.resolveSnapshotProvider" ); - static final String RENAME_TITLE = RefactoringBundle.message("rename.title"); - - protected PsiNamedElement myElementToRename; - @NonNls protected static final String PRIMARY_VARIABLE_NAME = "PrimaryVariable"; - @NonNls private static final String OTHER_VARIABLE_NAME = "OtherVariable"; - private ArrayList myHighlighters; - protected final Editor myEditor; - protected final Project myProject; - private RangeMarker myRenameOffset; - private String myInitialName; - protected final String myOldName; - protected RangeMarker myBeforeRevert = null; - - public void setAdvertisementText(String advertisementText) { - myAdvertisementText = advertisementText; - } - - private String myAdvertisementText; - - private static final Stack ourRenamersStack = new Stack(); + private ResolveSnapshotProvider.ResolveSnapshot mySnapshot; + private TextRange mySelectedRange; + private Language myLanguage; public VariableInplaceRenamer(@NotNull PsiNamedElement elementToRename, Editor editor) { this(elementToRename, editor, elementToRename.getProject()); @@ -132,7 +73,8 @@ public class VariableInplaceRenamer { public VariableInplaceRenamer(PsiNamedElement elementToRename, Editor editor, Project project) { - this(elementToRename, editor, project, elementToRename != null ? elementToRename.getName() : null, elementToRename != null ? elementToRename.getName() : null); + this(elementToRename, editor, project, elementToRename != null ? elementToRename.getName() : null, + elementToRename != null ? elementToRename.getName() : null); } public VariableInplaceRenamer(PsiNamedElement elementToRename, @@ -140,106 +82,19 @@ public class VariableInplaceRenamer { Project project, final String initialName, final String oldName) { - myElementToRename = elementToRename; - myEditor = /*(editor instanceof EditorWindow)? ((EditorWindow)editor).getDelegate() : */editor; - myProject = project; - myOldName = oldName; - if (myElementToRename != null) { - myInitialName = initialName; - final PsiFile containingFile = myElementToRename.getContainingFile(); - if (!notSameFile(containingFile.getVirtualFile(), containingFile)) { - myRenameOffset = myElementToRename != null && myElementToRename.getTextRange() != null ? myEditor.getDocument().createRangeMarker(myElementToRename.getTextRange()) : null; - } - } + super(editor, elementToRename, project, initialName, oldName); + } public boolean performInplaceRename() { - return performInplaceRename(true, null); + return performInplaceRefactoring(null); } - public boolean performInplaceRename(boolean processTextOccurrences, LinkedHashSet nameSuggestions) { - if (InjectedLanguageUtil.isInInjectedLanguagePrefixSuffix(myElementToRename)) { - return false; - } - - final FileViewProvider fileViewProvider = myElementToRename.getContainingFile().getViewProvider(); - VirtualFile file = getTopLevelVirtualFile(fileViewProvider); - - SearchScope referencesSearchScope = getReferencesSearchScope(file); - - final Collection refs = collectRefs(referencesSearchScope); - - addReferenceAtCaret(refs); - - for (PsiReference ref : refs) { - final PsiFile containingFile = ref.getElement().getContainingFile(); - - if (notSameFile(file, containingFile)) { - return false; - } - } - - PsiElement scope = checkLocalScope(); - - if (scope == null) { - return false; // Should have valid local search scope for inplace rename - } - - final PsiFile containingFile = scope.getContainingFile(); - if (containingFile == null){ - return false; // Should have valid local search scope for inplace rename - } - //no need to process further when file is read-only - if (!CommonRefactoringUtil.checkReadOnlyStatus(myProject, containingFile)) return true; - - myEditor.putUserData(INPLACE_RENAMER, this); - ourRenamersStack.push(this); - - final List> stringUsages = new ArrayList>(); - collectAdditionalElementsToRename(processTextOccurrences, stringUsages); - if (appendAdditionalElement(stringUsages)) { - return runRenameTemplate(nameSuggestions, refs, stringUsages, scope, containingFile); - } else { - new RenameChooser(myEditor).showChooser(refs, stringUsages, nameSuggestions, scope, containingFile); - } - - - return true; - } - - protected Collection collectRefs(SearchScope referencesSearchScope) { - return ReferencesSearch.search(myElementToRename, referencesSearchScope, false).findAll(); - } - - protected boolean notSameFile(@Nullable VirtualFile file, PsiFile containingFile) { - return getTopLevelVirtualFile(containingFile.getViewProvider()) != file; - } - - protected SearchScope getReferencesSearchScope(VirtualFile file) { - return file == null || ProjectRootManager.getInstance(myProject).getFileIndex().isInContent(file) - ? ProjectScope.getProjectScope(myElementToRename.getProject()) - : new LocalSearchScope(myElementToRename.getContainingFile()); - } - - @Nullable - protected PsiElement checkLocalScope() { - final SearchScope searchScope = PsiSearchHelper.SERVICE.getInstance(myElementToRename.getProject()).getUseScope(myElementToRename); - if (searchScope instanceof LocalSearchScope) { - final PsiElement[] elements = ((LocalSearchScope)searchScope).getScope(); - return PsiTreeUtil.findCommonParent(elements); - } - - return null; - } - - protected boolean appendAdditionalElement(List> stringUsages) { - return stringUsages.isEmpty(); - } - - protected void collectAdditionalElementsToRename(boolean processTextOccurrences, final List> stringUsages) { + @Override + protected void collectAdditionalElementsToRename(final List> stringUsages) { final String stringToSearch = myElementToRename.getName(); final PsiFile currentFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument()); - if (processTextOccurrences && stringToSearch != null) { + if (stringToSearch != null) { TextOccurrencesUtil .processUsagesInStringsAndComments(myElementToRename, stringToSearch, true, new PairProcessor() { public boolean process(PsiElement psiElement, TextRange textRange) { @@ -252,251 +107,57 @@ public class VariableInplaceRenamer { } } - private boolean runRenameTemplate(final LinkedHashSet nameSuggestions, - Collection refs, - final Collection> stringUsages, - PsiElement scope, - final PsiFile containingFile) { - final PsiElement context = containingFile.getContext(); - if (context != null) { - scope = context.getContainingFile(); + @Override + protected boolean buildTemplateAndStart(final Collection refs, + Collection> stringUsages, + final PsiElement scope, + final PsiFile containingFile) { + if (appendAdditionalElement(stringUsages)) { + return super.buildTemplateAndStart(refs, stringUsages, scope, containingFile); + } else { + final RenameChooser renameChooser = new RenameChooser(myEditor) { + @Override + protected void runRenameTemplate(Collection> stringUsages) { + VariableInplaceRenamer.super.buildTemplateAndStart(refs, stringUsages, scope, containingFile); + } + }; + renameChooser.showChooser(refs, stringUsages); } - - final HighlightManager highlightManager = HighlightManager.getInstance(myProject); - ResolveSnapshotProvider resolveSnapshotProvider = INSTANCE.forLanguage(scope.getLanguage()); - final ResolveSnapshotProvider.ResolveSnapshot snapshot = resolveSnapshotProvider != null ? - resolveSnapshotProvider.createSnapshot(scope):null; - final TemplateBuilderImpl builder = new TemplateBuilderImpl(scope); - - PsiElement nameIdentifier = getNameIdentifier(); - int offset = myEditor.getCaretModel().getOffset(); - PsiElement selectedElement = getSelectedInEditorElement(nameIdentifier, refs, stringUsages, offset); - - if (nameIdentifier != null) addVariable(nameIdentifier, selectedElement, builder, nameSuggestions); - for (PsiReference ref : refs) { - addVariable(ref, selectedElement, builder, offset, nameSuggestions); - } - for (Pair usage : stringUsages) { - addVariable(usage.first, usage.second, selectedElement, builder, nameSuggestions); - } - addAdditionalVariables(builder); - final StartMarkAction markAction; - try { - markAction = startRename(); - } - catch (final StartMarkAction.AlreadyStartedException e) { - final Document oldDocument = e.getDocument(); - if (oldDocument != myEditor.getDocument()) { - final int exitCode = Messages.showOkCancelDialog(myProject, e.getMessage(), RENAME_TITLE, - "Navigate to continue rename", "Cancel started rename", Messages.getErrorIcon()); - if (exitCode == -1) return true; - navigateToAlreadyStarted(oldDocument, exitCode); - return true; - } - else { - restoreStateBeforeDialogWouldBeShown(); - } - return false; - } - - final PsiElement scope1 = scope; - CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { - public void run() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - final RangeMarker rangeMarker = myEditor.getDocument().createRangeMarker(new TextRange(myEditor.getCaretModel().getOffset(), myEditor.getCaretModel().getOffset())); - final int offset = rangeMarker.getStartOffset(); - final SelectionModel selectionModel = myEditor.getSelectionModel(); - final TextRange selectedRange = preserveSelectedRange(selectionModel); - Template template = builder.buildInlineTemplate(); - template.setToShortenLongNames(false); - TextRange range = scope1.getTextRange(); - assert range != null; - myHighlighters = new ArrayList(); - Editor topLevelEditor = InjectedLanguageUtil.getTopLevelEditor(myEditor); - topLevelEditor.getCaretModel().moveToOffset(range.getStartOffset()); - final DaemonCodeAnalyzer daemonCodeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject); - final boolean previousUpdate; - if (daemonCodeAnalyzer != null) { - previousUpdate = ((DaemonCodeAnalyzerImpl)daemonCodeAnalyzer).isUpdateByTimerEnabled(); - daemonCodeAnalyzer.setUpdateByTimerEnabled(false); - } - else { - previousUpdate = false; - } - TemplateManager.getInstance(myProject).startTemplate(topLevelEditor, template, new TemplateEditingAdapter() { - private String myNewName = null; - public void beforeTemplateFinished(final TemplateState templateState, Template template) { - if (daemonCodeAnalyzer != null) { - daemonCodeAnalyzer.setUpdateByTimerEnabled(previousUpdate); - } - finish(); - - TextResult value = templateState.getVariableValue(PRIMARY_VARIABLE_NAME); - myNewName = getNewName(value != null ? value.toString() : null, snapshot); - if (myNewName != null && !LanguageNamesValidation.INSTANCE.forLanguage(scope1.getLanguage()).isIdentifier(myNewName, myProject)) { - performOnInvalidIdentifier(myNewName, nameSuggestions); - return; - } - if (myNewName != null && snapshot != null && performAutomaticRename()) { - if (LanguageNamesValidation.INSTANCE.forLanguage(scope1.getLanguage()).isIdentifier(myNewName, myProject)) { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - snapshot.apply(myNewName); - } - }); - } - } - final int currentOffset = myEditor.getCaretModel().getOffset(); - myBeforeRevert = myRenameOffset != null && myRenameOffset.getEndOffset() >= currentOffset && myRenameOffset.getStartOffset() <= currentOffset - ? myEditor.getDocument().createRangeMarker(myRenameOffset.getStartOffset(), currentOffset) - : null; - if (myBeforeRevert != null) { - myBeforeRevert.setGreedyToRight(true); - } - restoreStateBeforeTemplateIsFinished(); - } - - @Override - public void templateFinished(Template template, final boolean brokenOff) { - boolean bind = false; - try { - super.templateFinished(template, brokenOff); - moveOffsetAfter(!brokenOff); - if (myNewName != null && !brokenOff) { - bind = true; - final Runnable runnable = new Runnable() { - public void run() { - performRefactoringRename(myNewName, context, markAction); - if (myBeforeRevert != null) { - myBeforeRevert.dispose(); - } - } - }; - if (ApplicationManager.getApplication().isUnitTestMode()){ - runnable.run(); - } else { - ApplicationManager.getApplication().invokeLater(runnable); - } - } - } - finally { - if (!bind) { - FinishMarkAction.finish(myProject, myEditor, markAction); - if (myBeforeRevert != null) { - myBeforeRevert.dispose(); - } - } - } - } - - public void templateCancelled(Template template) { - if (daemonCodeAnalyzer != null) { - daemonCodeAnalyzer.setUpdateByTimerEnabled(previousUpdate); - } - try { - final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); - documentManager.commitAllDocuments(); - finish(); - moveOffsetAfter(false); - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - documentManager.doPostponedOperationsAndUnblockDocument(myEditor.getDocument()); - } - }); - } - finally { - FinishMarkAction.finish(myProject, myEditor, markAction); - } - } - }); - - //move to old offset - Runnable runnable = new Runnable() { - public void run() { - myEditor.getCaretModel().moveToOffset(getOffsetForCaret(rangeMarker, offset)); - if (selectedRange != null){ - myEditor.getSelectionModel().setSelection(selectedRange.getStartOffset(), selectedRange.getEndOffset()); - } else if (!shouldSelectAll()){ - myEditor.getSelectionModel().removeSelection(); - } - } - }; - - final LookupImpl lookup = (LookupImpl)LookupManager.getActiveLookup(myEditor); - if (lookup != null && lookup.getLookupStart() <= (getOffsetForCaret(rangeMarker, offset))) { - lookup.setFocused(false); - lookup.performGuardedChange(runnable); - } else { - runnable.run(); - } - - //add highlights - if (myHighlighters != null) { // can be null if finish is called during testing - Map rangesToHighlight = new HashMap(); - final TemplateState templateState = TemplateManagerImpl.getTemplateState(topLevelEditor); - if (templateState != null) { - EditorColorsManager colorsManager = EditorColorsManager.getInstance(); - for (int i = 0; i < templateState.getSegmentsCount(); i++) { - final TextRange segmentOffset = templateState.getSegmentRange(i); - final String name = template.getSegmentName(i); - TextAttributes attributes = null; - if (name.equals(PRIMARY_VARIABLE_NAME)) { - attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.WRITE_SEARCH_RESULT_ATTRIBUTES); - } else if (name.equals(OTHER_VARIABLE_NAME)) { - attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES); - } - if (attributes == null) continue; - rangesToHighlight.put(segmentOffset, attributes); - } - } - addHighlights(rangesToHighlight, topLevelEditor, myHighlighters, highlightManager); - } - } - }); - } - }, RENAME_TITLE, null); return true; } - protected int getOffsetForCaret(RangeMarker rangeMarker, int offset) { - return offset; + protected boolean appendAdditionalElement(Collection> stringUsages) { + return stringUsages.isEmpty(); } + @Override + protected void beforeTemplateStart() { + myLanguage = myScope.getLanguage(); + final ResolveSnapshotProvider resolveSnapshotProvider = INSTANCE.forLanguage(myLanguage); + mySnapshot = resolveSnapshotProvider != null ? resolveSnapshotProvider.createSnapshot(myScope) : null; + + final SelectionModel selectionModel = myEditor.getSelectionModel(); + mySelectedRange = + selectionModel.hasSelection() ? new TextRange(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd()) : null; + } + + @Override + protected void restoreSelection() { + if (mySelectedRange != null) { + myEditor.getSelectionModel().setSelection(mySelectedRange.getStartOffset(), mySelectedRange.getEndOffset()); + } + else if (!shouldSelectAll()) { + myEditor.getSelectionModel().removeSelection(); + } + } + + @Override protected boolean shouldSelectAll() { if (myEditor.getSettings().isPreselectRename()) return true; final Boolean selectAll = myEditor.getUserData(RenameHandlerRegistry.SELECT_ALL); return selectAll != null && selectAll.booleanValue(); } - protected void navigateToAlreadyStarted(Document oldDocument, int exitCode) { - final PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(oldDocument); - if (file != null) { - final VirtualFile virtualFile = file.getVirtualFile(); - if (virtualFile != null) { - final FileEditor[] editors = FileEditorManager.getInstance(myProject).getEditors(virtualFile); - for (FileEditor editor : editors) { - if (editor instanceof TextEditor) { - final Editor textEditor = ((TextEditor)editor).getEditor(); - final TemplateState templateState = TemplateManagerImpl.getTemplateState(textEditor); - if (templateState != null) { - if (exitCode == DialogWrapper.OK_EXIT_CODE) { - final TextRange range = templateState.getVariableRange(PRIMARY_VARIABLE_NAME); - if (range != null) { - new OpenFileDescriptor(myProject, virtualFile, range.getStartOffset()).navigate(true); - return; - } - } else { - templateState.gotoEnd(); - return; - } - } - } - } - } - } - } - protected VariableInplaceRenamer createInplaceRenamerToRestart(PsiNamedElement variable, Editor editor, String initialName) { return new VariableInplaceRenamer(variable, editor, myProject, initialName, myOldName); } @@ -507,105 +168,13 @@ public class VariableInplaceRenamer { .createConfirmation("Inserted identifier is not valid", "Continue editing", "Cancel", new Runnable() { @Override public void run() { - createInplaceRenamerToRestart(getVariable(), myEditor, newName).performInplaceRename(true, nameSuggestions); + createInplaceRenamerToRestart(getVariable(), myEditor, newName).performInplaceRefactoring(nameSuggestions); } }, 0).showInBestPositionFor(myEditor); } - protected void restoreStateBeforeTemplateIsFinished(){} - - - @Nullable - protected PsiElement getNameIdentifier() { - return myElementToRename instanceof PsiNameIdentifierOwner ? ((PsiNameIdentifierOwner)myElementToRename).getNameIdentifier() : null; - } - - protected void restoreStateBeforeDialogWouldBeShown() { - PsiNamedElement variable = getVariable(); - final TemplateState state = TemplateManagerImpl.getTemplateState(InjectedLanguageUtil.getTopLevelEditor(myEditor)); - assert state != null; - final String commandName = RefactoringBundle - .message("renaming.0.1.to.2", UsageViewUtil.getType(variable), UsageViewUtil.getDescriptiveName(variable), - variable.getName()); - Runnable runnable = new Runnable() { - public void run() { - state.gotoEnd(true); - } - }; - CommandProcessor.getInstance().executeCommand(myProject, runnable, commandName, null); - } - - @Nullable - protected String getNewName(String newName, ResolveSnapshotProvider.ResolveSnapshot snapshot) { - if (!performAutomaticRename()) return null; - return snapshot != null ? newName : null; - } - - @Nullable - protected StartMarkAction startRename() throws StartMarkAction.AlreadyStartedException { - final StartMarkAction[] markAction = new StartMarkAction[1]; - final StartMarkAction.AlreadyStartedException[] ex = new StartMarkAction.AlreadyStartedException[1]; - CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { - @Override - public void run() { - try { - markAction[0] = StartMarkAction.start(myEditor, myProject, RENAME_TITLE); - } - catch (StartMarkAction.AlreadyStartedException e) { - ex[0] = e; - } - } - }, RENAME_TITLE, null); - if (ex[0] != null) throw ex[0]; - return markAction[0]; - } - - @Nullable - protected TextRange preserveSelectedRange(SelectionModel selectionModel) { - if (selectionModel.hasSelection()) { - return new TextRange(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd()); - } - return null; - } - - @Nullable - protected PsiNamedElement getVariable() { - if (myElementToRename != null && myElementToRename.isValid()) return myElementToRename; - final PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument()); - if (psiFile != null) { - return PsiTreeUtil.getParentOfType(psiFile.findElementAt(myRenameOffset.getStartOffset()), PsiNameIdentifierOwner.class); - } - return myElementToRename; - } - - protected boolean performAutomaticRename() { - return true; - } - - /** - * Called after the completion of the refactoring, either a successful or a failed one. - * - * @param success true if the refactoring was accepted, false if it was cancelled (by undo or Esc) - */ - protected void moveOffsetAfter(boolean success) { - } - - protected void addAdditionalVariables(TemplateBuilderImpl builder) { - } - - protected void addReferenceAtCaret(Collection refs) { - PsiFile myEditorFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument()); - // Note, that myEditorFile can be different from myElement.getContainingFile() e.g. in injections: myElement declaration in one - // file / usage in another ! - final PsiReference reference = (myEditorFile != null ? - myEditorFile:myElementToRename.getContainingFile()).findReferenceAt(myEditor.getCaretModel().getOffset()); - if (reference != null && !refs.contains(reference)) { - refs.add(reference); - } - } protected void performRefactoringRename(final String newName, - PsiElement context, - final StartMarkAction markAction) { + final StartMarkAction markAction) { try { PsiNamedElement elementToRename = getVariable(); for (AutomaticRenamerFactory renamerFactory : Extensions.getExtensions(AutomaticRenamerFactory.EP_NAME)) { @@ -631,13 +200,13 @@ public class VariableInplaceRenamer { return; } - if (!CommonRefactoringUtil.checkReadOnlyStatus(myProject, PsiUtilBase.toPsiElementArray(renamer.getElements()))) return; + if (!CommonRefactoringUtil.checkReadOnlyStatus(myProject, PsiUtilCore.toPsiElementArray(renamer.getElements()))) return; final UsageInfo[] usageInfos = usages.toArray(new UsageInfo[usages.size()]); final MultiMap classified = RenameProcessor.classifyUsages(renamer.getElements(), usageInfos); for (final PsiNamedElement element : renamer.getElements()) { - new WriteCommandAction(myProject, RENAME_TITLE) { + new WriteCommandAction(myProject, getCommandName()) { @Override - protected void run(com.intellij.openapi.application.Result result) throws Throwable { + protected void run(Result result) throws Throwable { final String newElementName = renamer.getNewName(element); if (newElementName != null) { final Collection infos = classified.get(element); @@ -655,298 +224,58 @@ public class VariableInplaceRenamer { } } - public void setElementToRename(PsiNamedElement elementToRename) { - myElementToRename = elementToRename; + @Override + protected String getCommandName() { + PsiNamedElement variable = getVariable(); + return RefactoringBundle.message("renaming.0.1.to.2", UsageViewUtil.getType(variable), UsageViewUtil.getDescriptiveName(variable), + variable.getName()); } - protected void showDialogAdvertisement(final String actionId) { - final Keymap keymap = KeymapManager.getInstance().getActiveKeymap(); - final Shortcut[] shortcuts = keymap.getShortcuts(actionId); - if (shortcuts.length > 0) { - setAdvertisementText("Press " + KeymapUtil.getShortcutText(shortcuts[0]) + " to show dialog"); - } - } - - public String getInitialName() { - if (myInitialName == null) { - final PsiNamedElement variable = getVariable(); - if (variable != null) { - return variable.getName(); + @Override + protected boolean performRefactoring() { + boolean bind = false; + if (myInsertedName != null) { + bind = true; + if (!isIdentifier(myInsertedName)) { + performOnInvalidIdentifier(myInsertedName, myNameSuggestions); } - } - return myInitialName; - } - - protected void revertState() { - CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { - public void run() { - final Editor topLevelEditor = InjectedLanguageUtil.getTopLevelEditor(myEditor); - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - final TemplateState state = TemplateManagerImpl.getTemplateState(topLevelEditor); - assert state != null; - final int segmentsCount = state.getSegmentsCount(); - final Document document = topLevelEditor.getDocument(); - for (int i = 0; i < segmentsCount; i++) { - final TextRange segmentRange = state.getSegmentRange(i); - document.replaceString(segmentRange.getStartOffset(), segmentRange.getEndOffset(), myOldName); - } + else { + if (mySnapshot != null) { + if (isIdentifier(myInsertedName)) { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + mySnapshot.apply(myInsertedName); + } + }); } - }); - PsiDocumentManager.getInstance(myProject).commitDocument(topLevelEditor.getDocument()); - } - }, RENAME_TITLE, null); - } - - private static VirtualFile getTopLevelVirtualFile(final FileViewProvider fileViewProvider) { - VirtualFile file = fileViewProvider.getVirtualFile(); - if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate(); - return file; - } - - @TestOnly - public static void checkCleared(){ - try { - assert ourRenamersStack.isEmpty() : ourRenamersStack; - } - finally { - ourRenamersStack.clear(); - } - } - - public void finish() { - if (!ourRenamersStack.isEmpty() && ourRenamersStack.peek() == this) { - ourRenamersStack.pop(); - } - if (myHighlighters != null) { - if (!myProject.isDisposed()) { - final HighlightManager highlightManager = HighlightManager.getInstance(myProject); - for (RangeHighlighter highlighter : myHighlighters) { - highlightManager.removeSegmentHighlighter(myEditor, highlighter); } } - - myHighlighters = null; - myEditor.putUserData(INPLACE_RENAMER, null); + final Runnable runnable = new Runnable() { + public void run() { + performRefactoringRename(myInsertedName, myMarkAction); + if (myBeforeRevert != null) { + myBeforeRevert.dispose(); + } + } + }; + if (ApplicationManager.getApplication().isUnitTestMode()) { + runnable.run(); + } + else { + ApplicationManager.getApplication().invokeLater(runnable, myProject.getDisposed()); + } } + return bind; } - protected void addHighlights(@NotNull Map ranges, @NotNull Editor editor, @NotNull Collection highlighters, @NotNull HighlightManager highlightManager) { - for (Map.Entry entry : ranges.entrySet()) { - TextRange range = entry.getKey(); - TextAttributes attributes = entry.getValue(); - highlightManager.addOccurrenceHighlight(editor, range.getStartOffset(), range.getEndOffset(), attributes, 0, highlighters, null); - } - - for (RangeHighlighter highlighter : highlighters) { - highlighter.setGreedyToLeft(true); - highlighter.setGreedyToRight(true); - } - } - - private static PsiElement getSelectedInEditorElement(@Nullable PsiElement nameIdentifier, - final Collection refs, - Collection> stringUsages, - final int offset) { - if (nameIdentifier != null) { - final TextRange range = nameIdentifier.getTextRange()/*.shiftRight(PsiUtilBase.findInjectedElementOffsetInRealDocument(nameIdentifier))*/; - if (contains(range, offset)) return nameIdentifier; - } - - for (PsiReference ref : refs) { - final PsiElement element = ref.getElement(); - if (contains(ref.getRangeInElement().shiftRight(element.getTextRange().getStartOffset()), offset)) return element; - } - - for (Pair stringUsage : stringUsages) { - final PsiElement element = stringUsage.first; - if (contains(stringUsage.second.shiftRight(element.getTextRange().getStartOffset()), offset)) return element; - } - - LOG.assertTrue(false); - return null; - } - - private static boolean contains(final TextRange range, final int offset) { - return range.getStartOffset() <= offset && offset <= range.getEndOffset(); - } - - private void addVariable(final PsiReference reference, - final PsiElement selectedElement, - final TemplateBuilderImpl builder, - int offset, - final LinkedHashSet names) { - if (reference.getElement() == selectedElement && - contains(reference.getRangeInElement().shiftRight(selectedElement.getTextRange().getStartOffset()), offset)) { - Expression expression = new MyExpression(getInitialName(), names); - builder.replaceElement(reference, PRIMARY_VARIABLE_NAME, expression, true); - } - else { - builder.replaceElement(reference, OTHER_VARIABLE_NAME, PRIMARY_VARIABLE_NAME, false); - } - } - - private void addVariable(final PsiElement element, - final PsiElement selectedElement, - final TemplateBuilderImpl builder, - final LinkedHashSet names) { - addVariable(element, null, selectedElement, builder, names); - } - - private void addVariable(final PsiElement element, - @Nullable final TextRange textRange, - final PsiElement selectedElement, - final TemplateBuilderImpl builder, - final LinkedHashSet names) { - if (element == selectedElement) { - Expression expression = new MyExpression(getInitialName(), names); - builder.replaceElement(element, PRIMARY_VARIABLE_NAME, expression, true); - } else if (textRange != null) { - builder.replaceElement(element, textRange, OTHER_VARIABLE_NAME, PRIMARY_VARIABLE_NAME, false); - } - else { - builder.replaceElement(element, OTHER_VARIABLE_NAME, PRIMARY_VARIABLE_NAME, false); - } + private boolean isIdentifier(final String newName) { + + final NamesValidator namesValidator = LanguageNamesValidation.INSTANCE.forLanguage(myLanguage); + return namesValidator == null || namesValidator.isIdentifier(newName, myProject); } + @Override protected LookupElement[] createLookupItems(final LookupElement[] lookupItems, final String name) { return lookupItems; } - - private class MyExpression extends Expression { - private final String myName; - private final LookupElement[] myLookupItems; - - private MyExpression(String name, LinkedHashSet names) { - myName = name; - if (names == null) { - names = new LinkedHashSet(); - for(NameSuggestionProvider provider: Extensions.getExtensions(NameSuggestionProvider.EP_NAME)) { - provider.getSuggestedNames(myElementToRename, myElementToRename, names); - } - } - myLookupItems = new LookupElement[names.size()]; - final Iterator iterator = names.iterator(); - for (int i = 0; i < myLookupItems.length; i++) { - final String suggestion = iterator.next(); - myLookupItems[i] = LookupElementBuilder.create(suggestion).setInsertHandler(new InsertHandler() { - @Override - public void handleInsert(InsertionContext context, LookupElement item) { - if (shouldSelectAll()) return; - final Editor topLevelEditor = InjectedLanguageUtil.getTopLevelEditor(myEditor); - final TemplateState templateState = TemplateManagerImpl.getTemplateState(topLevelEditor); - if (templateState != null) { - final TextRange range = templateState.getCurrentVariableRange(); - if (range != null) { - topLevelEditor.getDocument().replaceString(range.getStartOffset(), range.getEndOffset(), suggestion); - } - } - } - }); - } - } - - public LookupElement[] calculateLookupItems(ExpressionContext context) { - return createLookupItems(myLookupItems, myName); - } - - public Result calculateQuickResult(ExpressionContext context) { - return calculateResult(context); - } - - public Result calculateResult(ExpressionContext context) { - TemplateState templateState = TemplateManagerImpl.getTemplateState(myEditor); - final TextResult insertedValue = templateState != null ? templateState.getVariableValue(PRIMARY_VARIABLE_NAME) : null; - if (insertedValue != null) { - if (!insertedValue.getText().isEmpty()) return insertedValue; - } - return new TextResult(myName); - } - - @Override - public String getAdvertisingText() { - return myAdvertisementText; - } - } - - private class RenameChooser { - @NonNls private static final String CODE_OCCURRENCES = "Rename code occurrences"; - @NonNls private static final String ALL_OCCURRENCES = "Rename all occurrences"; - private final Set myRangeHighlighters = new HashSet(); - private final Editor myEditor; - private final TextAttributes myAttributes; - - public RenameChooser(Editor editor) { - myEditor = editor; - myAttributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES); - } - - public void showChooser(final Collection refs, - final List> stringUsages, - final LinkedHashSet nameSuggestions, - final PsiElement scope, - final PsiFile containingFile) { - - final DefaultListModel model = new DefaultListModel(); - model.addElement(CODE_OCCURRENCES); - model.addElement(ALL_OCCURRENCES); - final JList list = new JBList(model); - - list.addListSelectionListener(new ListSelectionListener() { - public void valueChanged(final ListSelectionEvent e) { - final String selectedValue = (String)list.getSelectedValue(); - if (selectedValue == null) return; - dropHighlighters(); - final MarkupModel markupModel = myEditor.getMarkupModel(); - - if (selectedValue == ALL_OCCURRENCES) { - for (Pair pair : stringUsages) { - final TextRange textRange = pair.second.shiftRight(pair.first.getTextOffset()); - final RangeHighlighter rangeHighlighter = markupModel.addRangeHighlighter( - textRange.getStartOffset(), textRange.getEndOffset(), HighlighterLayer.SELECTION - 1, myAttributes, - HighlighterTargetArea.EXACT_RANGE); - myRangeHighlighters.add(rangeHighlighter); - } - } - - for (PsiReference reference : refs) { - final PsiElement element = reference.getElement(); - if (element == null) continue; - final TextRange textRange = element.getTextRange(); - final RangeHighlighter rangeHighlighter = markupModel.addRangeHighlighter( - textRange.getStartOffset(), textRange.getEndOffset(), HighlighterLayer.SELECTION - 1, myAttributes, - HighlighterTargetArea.EXACT_RANGE); - myRangeHighlighters.add(rangeHighlighter); - } - } - }); - - JBPopupFactory.getInstance().createListPopupBuilder(list) - .setTitle("String occurrences found") - .setMovable(false) - .setResizable(false) - .setRequestFocus(true) - .setItemChoosenCallback(new Runnable() { - public void run() { - runRenameTemplate(nameSuggestions, refs, list.getSelectedValue() == ALL_OCCURRENCES ? stringUsages : new ArrayList>(), scope, containingFile); - } - }) - .addListener(new JBPopupAdapter() { - @Override - public void onClosed(LightweightWindowEvent event) { - dropHighlighters(); - } - }) - .createPopup().showInBestPositionFor(myEditor); - } - - - - private void dropHighlighters() { - for (RangeHighlighter highlight : myRangeHighlighters) { - highlight.dispose(); - } - myRangeHighlighters.clear(); - } - } } diff --git a/platform/testFramework/src/com/intellij/testFramework/UsefulTestCase.java b/platform/testFramework/src/com/intellij/testFramework/UsefulTestCase.java index dbb29d75ea1e..a24b83e62008 100644 --- a/platform/testFramework/src/com/intellij/testFramework/UsefulTestCase.java +++ b/platform/testFramework/src/com/intellij/testFramework/UsefulTestCase.java @@ -36,7 +36,7 @@ import com.intellij.psi.codeStyle.CodeStyleSchemes; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.impl.source.PostprocessReformattingAspect; -import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer; +import com.intellij.refactoring.rename.inplace.InplaceRefactoring; import com.intellij.testFramework.exceptionCases.AbstractExceptionCase; import com.intellij.util.Consumer; import com.intellij.util.Function; @@ -195,7 +195,7 @@ public abstract class UsefulTestCase extends TestCase { myOldCodeStyleSettings = null; try { - VariableInplaceRenamer.checkCleared(); + InplaceRefactoring.checkCleared(); } catch (AssertionError e) { result.add(e); diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestUtil.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestUtil.java index 88874aca58a9..fe345424dd15 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestUtil.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestUtil.java @@ -40,8 +40,8 @@ import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.io.FileUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.refactoring.rename.inplace.InplaceRefactoring; import com.intellij.refactoring.rename.inplace.VariableInplaceRenameHandler; -import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -165,7 +165,7 @@ public class CodeInsightTestUtil { TemplateManagerImpl templateManager = (TemplateManagerImpl)TemplateManager.getInstance(project); try { templateManager.setTemplateTesting(true); - VariableInplaceRenamer renamer = handler.doRename(elementAtCaret, editor, null); + InplaceRefactoring renamer = handler.doRename(elementAtCaret, editor, null); if (editor instanceof EditorWindow) { editor = ((EditorWindow)editor).getDelegate(); }