8b5f4db0883f8a42942ce6fca8ada92ecc69e865
[fedora-idea.git] / platform / lang-impl / src / com / intellij / refactoring / rename / inplace / VariableInplaceRenamer.java
blob8b5f4db0883f8a42942ce6fca8ada92ecc69e865
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.refactoring.rename.inplace;
18 import com.intellij.codeInsight.highlighting.HighlightManager;
19 import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector;
20 import com.intellij.codeInsight.lookup.LookupElement;
21 import com.intellij.codeInsight.lookup.LookupElementBuilder;
22 import com.intellij.codeInsight.lookup.LookupManager;
23 import com.intellij.codeInsight.lookup.impl.LookupImpl;
24 import com.intellij.codeInsight.template.*;
25 import com.intellij.codeInsight.template.impl.TemplateState;
26 import com.intellij.injected.editor.VirtualFileWindow;
27 import com.intellij.lang.LanguageExtension;
28 import com.intellij.lang.LanguageNamesValidation;
29 import com.intellij.openapi.application.ApplicationManager;
30 import com.intellij.openapi.command.CommandProcessor;
31 import com.intellij.openapi.diagnostic.Logger;
32 import com.intellij.openapi.editor.Editor;
33 import com.intellij.openapi.editor.colors.EditorColors;
34 import com.intellij.openapi.editor.colors.EditorColorsManager;
35 import com.intellij.openapi.editor.markup.RangeHighlighter;
36 import com.intellij.openapi.editor.markup.TextAttributes;
37 import com.intellij.openapi.extensions.Extensions;
38 import com.intellij.openapi.progress.ProgressManager;
39 import com.intellij.openapi.project.Project;
40 import com.intellij.openapi.util.TextRange;
41 import com.intellij.openapi.vfs.VirtualFile;
42 import com.intellij.psi.*;
43 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
44 import com.intellij.psi.search.LocalSearchScope;
45 import com.intellij.psi.search.SearchScope;
46 import com.intellij.psi.search.searches.ReferencesSearch;
47 import com.intellij.psi.util.PsiTreeUtil;
48 import com.intellij.psi.util.PsiUtilBase;
49 import com.intellij.refactoring.RefactoringBundle;
50 import com.intellij.refactoring.rename.AutomaticRenamingDialog;
51 import com.intellij.refactoring.rename.NameSuggestionProvider;
52 import com.intellij.refactoring.rename.RenameProcessor;
53 import com.intellij.refactoring.rename.RenameUtil;
54 import com.intellij.refactoring.rename.naming.AutomaticRenamer;
55 import com.intellij.refactoring.rename.naming.AutomaticRenamerFactory;
56 import com.intellij.refactoring.util.CommonRefactoringUtil;
57 import com.intellij.refactoring.util.TextOccurrencesUtil;
58 import com.intellij.usageView.UsageInfo;
59 import com.intellij.util.PairProcessor;
60 import com.intellij.util.containers.Stack;
61 import gnu.trove.THashMap;
62 import org.jetbrains.annotations.NonNls;
63 import org.jetbrains.annotations.NotNull;
64 import org.jetbrains.annotations.TestOnly;
66 import java.util.ArrayList;
67 import java.util.Collection;
68 import java.util.List;
69 import java.util.Map;
71 /**
72 * @author ven
74 public class VariableInplaceRenamer {
75 private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.rename.inplace.VariableInplaceRenamer");
76 public static final LanguageExtension<ResolveSnapshotProvider> INSTANCE = new LanguageExtension<ResolveSnapshotProvider>(
77 "com.intellij.rename.inplace.resolveSnapshotProvider"
80 private final PsiNameIdentifierOwner myElementToRename;
81 @NonNls private static final String PRIMARY_VARIABLE_NAME = "PrimaryVariable";
82 @NonNls private static final String OTHER_VARIABLE_NAME = "OtherVariable";
83 private ArrayList<RangeHighlighter> myHighlighters;
84 private final Editor myEditor;
85 private final Project myProject;
87 private static final Stack<VariableInplaceRenamer> ourRenamersStack = new Stack<VariableInplaceRenamer>();
89 public VariableInplaceRenamer(PsiNameIdentifierOwner elementToRename, Editor editor) {
90 myElementToRename = elementToRename;
91 myEditor = /*(editor instanceof EditorWindow)? ((EditorWindow)editor).getDelegate() : */editor;
92 myProject = myElementToRename.getProject();
95 public boolean performInplaceRename() {
96 final Collection<PsiReference> refs = ReferencesSearch.search(myElementToRename).findAll();
98 final PsiReference reference = myElementToRename.getContainingFile().findReferenceAt(myEditor.getCaretModel().getOffset());
99 if (reference != null && !refs.contains(reference)) {
100 refs.add(reference);
103 final FileViewProvider fileViewProvider = myElementToRename.getContainingFile().getViewProvider();
104 VirtualFile file = getTopLevelVirtualFile(fileViewProvider);
106 for (PsiReference ref : refs) {
107 final FileViewProvider usageViewProvider = ref.getElement().getContainingFile().getViewProvider();
109 if (getTopLevelVirtualFile(usageViewProvider) != file) {
110 return false;
114 while (!ourRenamersStack.isEmpty()) {
115 ourRenamersStack.peek().finish();
118 ourRenamersStack.push(this);
120 final Map<TextRange, TextAttributes> rangesToHighlight = new THashMap<TextRange, TextAttributes>();
121 //it is crucial to highlight AFTER the template is started, so we collect ranges first
122 collectElementsToHighlight(rangesToHighlight, refs);
124 final HighlightManager highlightManager = HighlightManager.getInstance(myProject);
126 PsiElement scope = null;
127 final SearchScope searchScope = myElementToRename.getUseScope();
128 if (searchScope instanceof LocalSearchScope) {
129 final PsiElement[] elements = ((LocalSearchScope)searchScope).getScope();
130 scope = PsiTreeUtil.findCommonParent(elements);
133 if (scope == null) {
134 return false; // Should have valid local search scope for inplace rename
137 final PsiFile containingFile = scope.getContainingFile();
138 if (containingFile == null){
139 return false; // Should have valid local search scope for inplace rename
141 final PsiElement context = containingFile.getContext();
142 if (context != null) {
143 scope = context.getContainingFile();
146 String stringToSearch = myElementToRename.getName();
147 if (stringToSearch != null &&
148 !TextOccurrencesUtil.processUsagesInStringsAndComments(myElementToRename, stringToSearch, true, new PairProcessor<PsiElement, TextRange>() {
149 public boolean process(PsiElement psiElement, TextRange textRange) {
150 return false;
152 })) {
153 return false;
156 ResolveSnapshotProvider resolveSnapshotProvider = INSTANCE.forLanguage(scope.getLanguage());
157 final ResolveSnapshotProvider.ResolveSnapshot snapshot = resolveSnapshotProvider != null ?
158 resolveSnapshotProvider.createSnapshot(scope):null;
159 final TemplateBuilderImpl builder = new TemplateBuilderImpl(scope);
161 final PsiElement nameIdentifier = myElementToRename.getNameIdentifier();
162 PsiElement selectedElement = getSelectedInEditorElement(nameIdentifier, refs, myEditor.getCaretModel().getOffset());
163 if (!CommonRefactoringUtil.checkReadOnlyStatus(myProject, myElementToRename)) return true;
165 if (nameIdentifier != null) addVariable(nameIdentifier, selectedElement, builder);
166 for (PsiReference ref : refs) {
167 addVariable(ref, selectedElement, builder);
170 final PsiElement scope1 = scope;
171 final int renameOffset = myElementToRename.getTextOffset();
172 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
173 public void run() {
174 ApplicationManager.getApplication().runWriteAction(new Runnable() {
175 public void run() {
176 int offset = myEditor.getCaretModel().getOffset();
177 Template template = builder.buildInlineTemplate();
178 template.setToShortenLongNames(false);
179 TextRange range = scope1.getTextRange();
180 assert range != null;
181 myHighlighters = new ArrayList<RangeHighlighter>();
182 Editor topLevelEditor = InjectedLanguageUtil.getTopLevelEditor(myEditor);
183 topLevelEditor.getCaretModel().moveToOffset(range.getStartOffset());
184 TemplateManager.getInstance(myProject).startTemplate(topLevelEditor, template, new TemplateEditingAdapter() {
185 private String myNewName = null;
186 public void beforeTemplateFinished(final TemplateState templateState, Template template) {
187 finish();
189 if (snapshot != null) {
190 TextResult value = templateState.getVariableValue(PRIMARY_VARIABLE_NAME);
191 if (value != null) {
192 myNewName = value.toString();
193 if (LanguageNamesValidation.INSTANCE.forLanguage(scope1.getLanguage()).isIdentifier(myNewName, myProject)) {
194 ApplicationManager.getApplication().runWriteAction(new Runnable() {
195 public void run() {
196 snapshot.apply(myNewName);
204 @Override
205 public void templateFinished(Template template, boolean brokenOff) {
206 super.templateFinished(template, brokenOff);
207 if (myNewName != null) {
208 performAutomaticRename(myNewName, PsiTreeUtil.getParentOfType(containingFile.findElementAt(renameOffset), PsiNameIdentifierOwner.class));
212 public void templateCancelled(Template template) {
213 finish();
217 //move to old offset
218 final LookupImpl lookup = (LookupImpl)LookupManager.getActiveLookup(myEditor);
219 final boolean lookupShown = lookup != null && lookup.getLookupStart() < offset;
220 if (lookupShown) {
221 lookup.setAdditionalPrefix(myEditor.getDocument().getCharsSequence().subSequence(lookup.getLookupStart(), offset).toString());
223 myEditor.getCaretModel().moveToOffset(offset);
224 if (lookupShown) {
225 lookup.setAdditionalPrefix("");
228 //add highlights
229 addHighlights(rangesToHighlight, topLevelEditor, myHighlighters, highlightManager);
233 }, RefactoringBundle.message("rename.title"), null);
235 return true;
238 public void performAutomaticRename(final String newName, final PsiElement elementToRename) {
239 for (AutomaticRenamerFactory renamerFactory : Extensions.getExtensions(AutomaticRenamerFactory.EP_NAME)) {
240 if (renamerFactory.isApplicable(elementToRename)) {
241 final List<UsageInfo> usages = new ArrayList<UsageInfo>();
242 final AutomaticRenamer renamer =
243 renamerFactory.createRenamer(elementToRename, newName, new ArrayList<UsageInfo>());
244 if (renamer.hasAnythingToRename()) {
245 if (!ApplicationManager.getApplication().isUnitTestMode()) {
246 final AutomaticRenamingDialog renamingDialog = new AutomaticRenamingDialog(myProject, renamer);
247 renamingDialog.show();
248 if (!renamingDialog.isOK()) return;
251 final Runnable runnable = new Runnable() {
252 public void run() {
253 renamer.findUsages(usages, false, false);
257 if (!ProgressManager.getInstance()
258 .runProcessWithProgressSynchronously(runnable, RefactoringBundle.message("searching.for.variables"), true, myProject)) {
259 return;
262 final UsageInfo[] usageInfos = usages.toArray(new UsageInfo[usages.size()]);
263 for (final PsiNamedElement element : renamer.getElements()) {
264 ApplicationManager.getApplication().runWriteAction(new Runnable() {
265 public void run() {
266 RenameUtil.doRenameGenericNamedElement(element, renamer.getRenames().get(element), RenameProcessor.extractUsagesForElement(element, usageInfos), null);
275 private static VirtualFile getTopLevelVirtualFile(final FileViewProvider fileViewProvider) {
276 VirtualFile file = fileViewProvider.getVirtualFile();
277 if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate();
278 return file;
281 @TestOnly
282 public static void checkCleared(){
283 try {
284 assert ourRenamersStack.isEmpty() : ourRenamersStack;
286 finally {
287 ourRenamersStack.clear();
291 public void finish() {
292 if (!ourRenamersStack.isEmpty() && ourRenamersStack.peek() == this) {
293 ourRenamersStack.pop();
295 if (myHighlighters != null) {
296 final HighlightManager highlightManager = HighlightManager.getInstance(myProject);
297 for (RangeHighlighter highlighter : myHighlighters) {
298 highlightManager.removeSegmentHighlighter(myEditor, highlighter);
301 myHighlighters = null;
305 private void collectElementsToHighlight(Map<TextRange, TextAttributes> rangesToHighlight, Collection<PsiReference> refs) {
306 EditorColorsManager colorsManager = EditorColorsManager.getInstance();
307 PsiElement nameId = myElementToRename.getNameIdentifier();
308 LOG.assertTrue(nameId != null);
309 rangesToHighlight.put(nameId.getTextRange().shiftRight(PsiUtilBase.findInjectedElementOffsetInRealDocument(nameId)), colorsManager.getGlobalScheme().getAttributes(EditorColors.WRITE_SEARCH_RESULT_ATTRIBUTES));
311 for (PsiReference ref : refs) {
312 final PsiElement element = ref.getElement();
313 TextRange range = ref.getRangeInElement().shiftRight(
314 element.getTextRange().getStartOffset() +
315 PsiUtilBase.findInjectedElementOffsetInRealDocument(element)
318 ReadWriteAccessDetector writeAccessDetector = ReadWriteAccessDetector.findDetector(element);
319 // TODO: read / write usages
320 boolean isForWrite = writeAccessDetector != null &&
321 ReadWriteAccessDetector.Access.Write == writeAccessDetector.getExpressionAccess(element);
322 TextAttributes attributes = colorsManager.getGlobalScheme().getAttributes(isForWrite ?
323 EditorColors.WRITE_SEARCH_RESULT_ATTRIBUTES :
324 EditorColors.SEARCH_RESULT_ATTRIBUTES);
325 rangesToHighlight.put(range, attributes);
329 private static void addHighlights(@NotNull Map<TextRange, TextAttributes> ranges, @NotNull Editor editor, @NotNull Collection<RangeHighlighter> highlighters, @NotNull HighlightManager highlightManager) {
330 for (Map.Entry<TextRange,TextAttributes> entry : ranges.entrySet()) {
331 TextRange range = entry.getKey();
332 TextAttributes attributes = entry.getValue();
333 highlightManager.addOccurrenceHighlight(editor, range.getStartOffset(), range.getEndOffset(), attributes, 0, highlighters, null);
336 for (RangeHighlighter highlighter : highlighters) {
337 highlighter.setGreedyToLeft(true);
338 highlighter.setGreedyToRight(true);
342 private static PsiElement getSelectedInEditorElement(final PsiElement nameIdentifier, final Collection<PsiReference> refs, final int offset) {
343 if (nameIdentifier != null) {
344 final TextRange range = nameIdentifier.getTextRange()/*.shiftRight(PsiUtilBase.findInjectedElementOffsetInRealDocument(nameIdentifier))*/;
345 if (contains(range, offset)) return nameIdentifier;
348 for (PsiReference ref : refs) {
349 final PsiElement element = ref.getElement();
350 final TextRange range = element.getTextRange()/*.shiftRight(PsiUtilBase.findInjectedElementOffsetInRealDocument(ref.getElement()))*/;
351 if (contains(range, offset)) return element;
354 LOG.assertTrue(false);
355 return null;
358 private static boolean contains(final TextRange range, final int offset) {
359 return range.getStartOffset() <= offset && offset <= range.getEndOffset();
362 private void addVariable(final PsiReference reference, final PsiElement selectedElement, final TemplateBuilderImpl builder) {
363 if (reference.getElement() == selectedElement) {
364 Expression expression = new MyExpression(myElementToRename.getName());
365 builder.replaceElement(reference, PRIMARY_VARIABLE_NAME, expression, true);
367 else {
368 builder.replaceElement(reference, OTHER_VARIABLE_NAME, PRIMARY_VARIABLE_NAME, false);
372 private void addVariable(final PsiElement element, final PsiElement selectedElement, final TemplateBuilderImpl builder) {
373 if (element == selectedElement) {
374 Expression expression = new MyExpression(myElementToRename.getName());
375 builder.replaceElement(element, PRIMARY_VARIABLE_NAME, expression, true);
377 else {
378 builder.replaceElement(element, OTHER_VARIABLE_NAME, PRIMARY_VARIABLE_NAME, false);
382 private class MyExpression extends Expression {
383 private final String myName;
384 private final LookupElement[] myLookupItems;
386 private MyExpression(String name) {
387 myName = name;
388 List<String> names = new ArrayList<String>();
389 for(NameSuggestionProvider provider: Extensions.getExtensions(NameSuggestionProvider.EP_NAME)) {
390 provider.getSuggestedNames(myElementToRename, myElementToRename, names);
392 myLookupItems = new LookupElement[names.size()];
393 for (int i = 0; i < myLookupItems.length; i++) {
394 myLookupItems[i] = LookupElementBuilder.create(names.get(i));
398 public LookupElement[] calculateLookupItems(ExpressionContext context) {
399 return myLookupItems;
402 public Result calculateQuickResult(ExpressionContext context) {
403 return new TextResult(myName);
406 public Result calculateResult(ExpressionContext context) {
407 return new TextResult(myName);