52c35c5c8e920c2600da370f2bf763b161409dcc
[idea/community.git] / platform / lang-impl / src / com / intellij / refactoring / rename / inplace / InplaceRefactoring.java
1 /*
2  * Copyright 2000-2012 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.refactoring.rename.inplace;
17
18 import com.intellij.codeInsight.completion.InsertHandler;
19 import com.intellij.codeInsight.completion.InsertionContext;
20 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
21 import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl;
22 import com.intellij.codeInsight.highlighting.HighlightManager;
23 import com.intellij.codeInsight.lookup.LookupElement;
24 import com.intellij.codeInsight.lookup.LookupElementBuilder;
25 import com.intellij.codeInsight.lookup.LookupManager;
26 import com.intellij.codeInsight.lookup.impl.LookupImpl;
27 import com.intellij.codeInsight.template.*;
28 import com.intellij.codeInsight.template.Result;
29 import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
30 import com.intellij.codeInsight.template.impl.TemplateState;
31 import com.intellij.injected.editor.VirtualFileWindow;
32 import com.intellij.openapi.actionSystem.Shortcut;
33 import com.intellij.openapi.application.*;
34 import com.intellij.openapi.command.CommandProcessor;
35 import com.intellij.openapi.command.WriteCommandAction;
36 import com.intellij.openapi.command.impl.FinishMarkAction;
37 import com.intellij.openapi.command.impl.StartMarkAction;
38 import com.intellij.openapi.diagnostic.Logger;
39 import com.intellij.openapi.editor.Document;
40 import com.intellij.openapi.editor.Editor;
41 import com.intellij.openapi.editor.RangeMarker;
42 import com.intellij.openapi.editor.colors.EditorColors;
43 import com.intellij.openapi.editor.colors.EditorColorsManager;
44 import com.intellij.openapi.editor.markup.RangeHighlighter;
45 import com.intellij.openapi.editor.markup.TextAttributes;
46 import com.intellij.openapi.extensions.Extensions;
47 import com.intellij.openapi.fileEditor.FileEditor;
48 import com.intellij.openapi.fileEditor.FileEditorManager;
49 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
50 import com.intellij.openapi.fileEditor.TextEditor;
51 import com.intellij.openapi.keymap.Keymap;
52 import com.intellij.openapi.keymap.KeymapManager;
53 import com.intellij.openapi.keymap.KeymapUtil;
54 import com.intellij.openapi.project.Project;
55 import com.intellij.openapi.roots.ProjectRootManager;
56 import com.intellij.openapi.ui.DialogWrapper;
57 import com.intellij.openapi.ui.Messages;
58 import com.intellij.openapi.util.Key;
59 import com.intellij.openapi.util.Pair;
60 import com.intellij.openapi.util.TextRange;
61 import com.intellij.openapi.vfs.VirtualFile;
62 import com.intellij.psi.*;
63 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
64 import com.intellij.psi.search.LocalSearchScope;
65 import com.intellij.psi.search.ProjectScope;
66 import com.intellij.psi.search.PsiSearchHelper;
67 import com.intellij.psi.search.SearchScope;
68 import com.intellij.psi.search.searches.ReferencesSearch;
69 import com.intellij.psi.util.PsiTreeUtil;
70 import com.intellij.refactoring.rename.NameSuggestionProvider;
71 import com.intellij.refactoring.util.CommonRefactoringUtil;
72 import com.intellij.util.containers.Stack;
73 import org.jetbrains.annotations.NonNls;
74 import org.jetbrains.annotations.NotNull;
75 import org.jetbrains.annotations.Nullable;
76 import org.jetbrains.annotations.TestOnly;
77
78 import java.util.*;
79
80 /**
81  * User: anna
82  * Date: 1/11/12
83  */
84 public abstract class InplaceRefactoring {
85   protected static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.rename.inplace.VariableInplaceRenamer");
86   @NonNls protected static final String PRIMARY_VARIABLE_NAME = "PrimaryVariable";
87   @NonNls protected static final String OTHER_VARIABLE_NAME = "OtherVariable";
88   protected static final Stack<InplaceRefactoring> ourRenamersStack = new Stack<InplaceRefactoring>();
89   public static final Key<InplaceRefactoring> INPLACE_RENAMER = Key.create("EditorInplaceRenamer");
90   protected PsiNamedElement myElementToRename;
91   protected final Editor myEditor;
92   protected final Project myProject;
93   protected RangeMarker myRenameOffset;
94   private String myAdvertisementText;
95   private ArrayList<RangeHighlighter> myHighlighters;
96   protected String myInitialName;
97   protected final String myOldName;
98   protected RangeMarker myBeforeRevert = null;
99   protected String myInsertedName;
100   protected LinkedHashSet<String> myNameSuggestions;
101
102   protected StartMarkAction myMarkAction;
103   protected PsiElement myScope;
104
105   public InplaceRefactoring(Editor editor, PsiNamedElement elementToRename, Project project) {
106     this(editor, elementToRename, project, elementToRename != null ? elementToRename.getName() : null,
107          elementToRename != null ? elementToRename.getName() : null);
108   }
109
110   public InplaceRefactoring(Editor editor, PsiNamedElement elementToRename, Project project, final String oldName) {
111     this(editor, elementToRename, project, elementToRename != null ? elementToRename.getName() : null, oldName);
112   }
113
114   public InplaceRefactoring(
115     Editor editor, PsiNamedElement elementToRename, Project project, String initialName, final String oldName) {
116     myEditor = /*(editor instanceof EditorWindow)? ((EditorWindow)editor).getDelegate() : */editor;
117     myElementToRename = elementToRename;
118     myProject = project;
119     myOldName = oldName;
120     if (myElementToRename != null) {
121       myInitialName = initialName;
122       final PsiFile containingFile = myElementToRename.getContainingFile();
123       if (!notSameFile(getTopLevelVirtualFile(containingFile.getViewProvider()), containingFile)) {
124         myRenameOffset = myElementToRename != null && myElementToRename.getTextRange() != null ? myEditor.getDocument()
125           .createRangeMarker(myElementToRename.getTextRange()) : null;
126       }
127     }
128   }
129
130   public void setAdvertisementText(String advertisementText) {
131     myAdvertisementText = advertisementText;
132   }
133
134
135   public boolean performInplaceRefactoring(final LinkedHashSet<String> nameSuggestions) {
136     myNameSuggestions = nameSuggestions;
137     if (InjectedLanguageUtil.isInInjectedLanguagePrefixSuffix(myElementToRename)) {
138       return false;
139     }
140
141     final FileViewProvider fileViewProvider = myElementToRename.getContainingFile().getViewProvider();
142     VirtualFile file = getTopLevelVirtualFile(fileViewProvider);
143
144     SearchScope referencesSearchScope = getReferencesSearchScope(file);
145
146     final Collection<PsiReference> refs = collectRefs(referencesSearchScope);
147
148     addReferenceAtCaret(refs);
149
150     for (PsiReference ref : refs) {
151       final PsiFile containingFile = ref.getElement().getContainingFile();
152
153       if (notSameFile(file, containingFile)) {
154         return false;
155       }
156     }
157
158     final PsiElement scope = checkLocalScope();
159
160     if (scope == null) {
161       return false; // Should have valid local search scope for inplace rename
162     }
163
164     final PsiFile containingFile = scope.getContainingFile();
165     if (containingFile == null) {
166       return false; // Should have valid local search scope for inplace rename
167     }
168     //no need to process further when file is read-only
169     if (!CommonRefactoringUtil.checkReadOnlyStatus(myProject, containingFile)) return true;
170
171     myEditor.putUserData(INPLACE_RENAMER, this);
172     ourRenamersStack.push(this);
173
174     final List<Pair<PsiElement, TextRange>> stringUsages = new ArrayList<Pair<PsiElement, TextRange>>();
175     collectAdditionalElementsToRename(stringUsages);
176     return buildTemplateAndStart(refs, stringUsages, scope, containingFile);
177   }
178
179   protected boolean notSameFile(@Nullable VirtualFile file, PsiFile containingFile) {
180     return getTopLevelVirtualFile(containingFile.getViewProvider()) != file;
181   }
182
183   protected SearchScope getReferencesSearchScope(VirtualFile file) {
184     return file == null || ProjectRootManager.getInstance(myProject).getFileIndex().isInContent(file)
185            ? ProjectScope.getProjectScope(myElementToRename.getProject())
186            : new LocalSearchScope(myElementToRename.getContainingFile());
187   }
188
189   @Nullable
190   protected PsiElement checkLocalScope() {
191     final SearchScope searchScope = PsiSearchHelper.SERVICE.getInstance(myElementToRename.getProject()).getUseScope(myElementToRename);
192     if (searchScope instanceof LocalSearchScope) {
193       final PsiElement[] elements = ((LocalSearchScope)searchScope).getScope();
194       return PsiTreeUtil.findCommonParent(elements);
195     }
196
197     return null;
198   }
199
200   protected abstract void collectAdditionalElementsToRename(final List<Pair<PsiElement, TextRange>> stringUsages);
201
202   protected abstract boolean shouldSelectAll();
203
204   protected abstract LookupElement[] createLookupItems(LookupElement[] lookupItems, String name);
205
206   protected Collection<PsiReference> collectRefs(SearchScope referencesSearchScope) {
207     return ReferencesSearch.search(myElementToRename, referencesSearchScope, false).findAll();
208   }
209
210   protected boolean buildTemplateAndStart(final Collection<PsiReference> refs,
211                                           final Collection<Pair<PsiElement, TextRange>> stringUsages,
212                                           final PsiElement scope,
213                                           final PsiFile containingFile) {
214     final PsiElement context = containingFile.getContext();
215     myScope = context != null ? context.getContainingFile() : scope;
216     final TemplateBuilderImpl builder = new TemplateBuilderImpl(myScope);
217
218     PsiElement nameIdentifier = getNameIdentifier();
219     int offset = myEditor.getCaretModel().getOffset();
220     PsiElement selectedElement = getSelectedInEditorElement(nameIdentifier, refs, stringUsages, offset);
221
222     if (nameIdentifier != null) addVariable(nameIdentifier, selectedElement, builder);
223     for (PsiReference ref : refs) {
224       addVariable(ref, selectedElement, builder, offset);
225     }
226     for (Pair<PsiElement, TextRange> usage : stringUsages) {
227       addVariable(usage.first, usage.second, selectedElement, builder);
228     }
229     addAdditionalVariables(builder);
230     try {
231       myMarkAction = startRename();
232     }
233     catch (final StartMarkAction.AlreadyStartedException e) {
234       final Document oldDocument = e.getDocument();
235       if (oldDocument != myEditor.getDocument()) {
236         final int exitCode = Messages.showOkCancelDialog(myProject, e.getMessage(), getCommandName(),
237                                                          "Navigate to continue", "Cancel started", Messages.getErrorIcon());
238         if (exitCode == -1) return true;
239         navigateToAlreadyStarted(oldDocument, exitCode);
240         return true;
241       }
242       else {
243         revertState();
244       }
245       return false;
246     }
247
248     beforeTemplateStart();
249
250     new WriteCommandAction(myProject, getCommandName()) {
251       @Override
252       protected void run(com.intellij.openapi.application.Result result) throws Throwable {
253         startTemplate(builder);
254       }
255     }.execute();
256     return true;
257   }
258
259   protected abstract void beforeTemplateStart();
260
261   private void startTemplate(final TemplateBuilderImpl builder) {
262
263     final DaemonCodeAnalyzer daemonCodeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject);
264
265     final boolean previousUpdate;
266     if (daemonCodeAnalyzer != null) {
267       previousUpdate = ((DaemonCodeAnalyzerImpl)daemonCodeAnalyzer).isUpdateByTimerEnabled();
268       daemonCodeAnalyzer.setUpdateByTimerEnabled(false);
269     }
270     else {
271       previousUpdate = false;
272     }
273
274     final MyTemplateListener templateListener = new MyTemplateListener() {
275       @Override
276       protected void restoreDaemonUpdateState() {
277         if (daemonCodeAnalyzer != null) {
278           daemonCodeAnalyzer.setUpdateByTimerEnabled(previousUpdate);
279         }
280       }
281     };
282
283     final int offset = myEditor.getCaretModel().getOffset();
284
285     Template template = builder.buildInlineTemplate();
286     template.setToShortenLongNames(false);
287     TextRange range = myScope.getTextRange();
288     assert range != null;
289     myHighlighters = new ArrayList<RangeHighlighter>();
290     Editor topLevelEditor = InjectedLanguageUtil.getTopLevelEditor(myEditor);
291     topLevelEditor.getCaretModel().moveToOffset(range.getStartOffset());
292
293     TemplateManager.getInstance(myProject).startTemplate(topLevelEditor, template, templateListener);
294     restoreOldCaretPositionAndSelection(offset);
295     highlightTemplateVariables(template, topLevelEditor);
296   }
297
298   private void highlightTemplateVariables(Template template, Editor topLevelEditor) {
299     //add highlights
300     if (myHighlighters != null) { // can be null if finish is called during testing
301       Map<TextRange, TextAttributes> rangesToHighlight = new HashMap<TextRange, TextAttributes>();
302       final TemplateState templateState = TemplateManagerImpl.getTemplateState(topLevelEditor);
303       if (templateState != null) {
304         EditorColorsManager colorsManager = EditorColorsManager.getInstance();
305         for (int i = 0; i < templateState.getSegmentsCount(); i++) {
306           final TextRange segmentOffset = templateState.getSegmentRange(i);
307           final String name = template.getSegmentName(i);
308           TextAttributes attributes = null;
309           if (name.equals(PRIMARY_VARIABLE_NAME)) {
310             attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.WRITE_SEARCH_RESULT_ATTRIBUTES);
311           }
312           else if (name.equals(OTHER_VARIABLE_NAME)) {
313             attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);
314           }
315           if (attributes == null) continue;
316           rangesToHighlight.put(segmentOffset, attributes);
317         }
318       }
319       addHighlights(rangesToHighlight, topLevelEditor, myHighlighters, HighlightManager.getInstance(myProject));
320     }
321   }
322
323   private void restoreOldCaretPositionAndSelection(final int offset) {
324     //move to old offset
325     Runnable runnable = new Runnable() {
326       public void run() {
327         myEditor.getCaretModel().moveToOffset(restoreCaretOffset(offset));
328         restoreSelection();
329       }
330     };
331
332     final LookupImpl lookup = (LookupImpl)LookupManager.getActiveLookup(myEditor);
333     if (lookup != null && lookup.getLookupStart() <= (restoreCaretOffset(offset))) {
334       lookup.setFocused(false);
335       lookup.performGuardedChange(runnable);
336     }
337     else {
338       runnable.run();
339     }
340   }
341
342   protected void restoreSelection() {
343   }
344
345   protected int restoreCaretOffset(int offset) {
346     return offset;
347   }
348
349   protected void navigateToAlreadyStarted(Document oldDocument, int exitCode) {
350     final PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(oldDocument);
351     if (file != null) {
352       final VirtualFile virtualFile = file.getVirtualFile();
353       if (virtualFile != null) {
354         final FileEditor[] editors = FileEditorManager.getInstance(myProject).getEditors(virtualFile);
355         for (FileEditor editor : editors) {
356           if (editor instanceof TextEditor) {
357             final Editor textEditor = ((TextEditor)editor).getEditor();
358             final TemplateState templateState = TemplateManagerImpl.getTemplateState(textEditor);
359             if (templateState != null) {
360               if (exitCode == DialogWrapper.OK_EXIT_CODE) {
361                 final TextRange range = templateState.getVariableRange(PRIMARY_VARIABLE_NAME);
362                 if (range != null) {
363                   new OpenFileDescriptor(myProject, virtualFile, range.getStartOffset()).navigate(true);
364                   return;
365                 }
366               }
367               else {
368                 templateState.gotoEnd();
369                 return;
370               }
371             }
372           }
373         }
374       }
375     }
376   }
377
378   @Nullable
379   protected PsiElement getNameIdentifier() {
380     return myElementToRename instanceof PsiNameIdentifierOwner ? ((PsiNameIdentifierOwner)myElementToRename).getNameIdentifier() : null;
381   }
382
383   @Nullable
384   protected StartMarkAction startRename() throws StartMarkAction.AlreadyStartedException {
385     final StartMarkAction[] markAction = new StartMarkAction[1];
386     final StartMarkAction.AlreadyStartedException[] ex = new StartMarkAction.AlreadyStartedException[1];
387     CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
388       @Override
389       public void run() {
390         try {
391           markAction[0] = StartMarkAction.start(myEditor, myProject, getCommandName());
392         }
393         catch (StartMarkAction.AlreadyStartedException e) {
394           ex[0] = e;
395         }
396       }
397     }, getCommandName(), null);
398     if (ex[0] != null) throw ex[0];
399     return markAction[0];
400   }
401
402   @Nullable
403   protected PsiNamedElement getVariable() {
404     if (myElementToRename != null && myElementToRename.isValid()) return myElementToRename;
405     final PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument());
406     if (psiFile != null) {
407       return PsiTreeUtil.getParentOfType(psiFile.findElementAt(myRenameOffset.getStartOffset()), PsiNameIdentifierOwner.class);
408     }
409     return myElementToRename;
410   }
411
412   /**
413    * Called after the completion of the refactoring, either a successful or a failed one.
414    *
415    * @param success true if the refactoring was accepted, false if it was cancelled (by undo or Esc)
416    */
417   protected void moveOffsetAfter(boolean success) {
418   }
419
420   protected void addAdditionalVariables(TemplateBuilderImpl builder) {
421   }
422
423   protected void addReferenceAtCaret(Collection<PsiReference> refs) {
424     PsiFile myEditorFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument());
425     // Note, that myEditorFile can be different from myElement.getContainingFile() e.g. in injections: myElement declaration in one
426     // file / usage in another !
427     final PsiReference reference = (myEditorFile != null ?
428                                     myEditorFile : myElementToRename.getContainingFile())
429       .findReferenceAt(myEditor.getCaretModel().getOffset());
430     if (reference != null && !refs.contains(reference)) {
431       refs.add(reference);
432     }
433   }
434
435   protected void showDialogAdvertisement(final String actionId) {
436     final Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
437     final Shortcut[] shortcuts = keymap.getShortcuts(actionId);
438     if (shortcuts.length > 0) {
439       setAdvertisementText("Press " + KeymapUtil.getShortcutText(shortcuts[0]) + " to show dialog");
440     }
441   }
442
443   public String getInitialName() {
444     if (myInitialName == null) {
445       final PsiNamedElement variable = getVariable();
446       if (variable != null) {
447         return variable.getName();
448       }
449     }
450     return myInitialName;
451   }
452
453   protected void revertState() {
454     CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
455       public void run() {
456         final Editor topLevelEditor = InjectedLanguageUtil.getTopLevelEditor(myEditor);
457         ApplicationManager.getApplication().runWriteAction(new Runnable() {
458           public void run() {
459             final TemplateState state = TemplateManagerImpl.getTemplateState(topLevelEditor);
460             assert state != null;
461             final int segmentsCount = state.getSegmentsCount();
462             final Document document = topLevelEditor.getDocument();
463             for (int i = 0; i < segmentsCount; i++) {
464               final TextRange segmentRange = state.getSegmentRange(i);
465               document.replaceString(segmentRange.getStartOffset(), segmentRange.getEndOffset(), myOldName);
466             }
467           }
468         });
469         if (!myProject.isDisposed() && myProject.isOpen()) {
470           PsiDocumentManager.getInstance(myProject).commitDocument(topLevelEditor.getDocument());
471         }
472       }
473     }, getCommandName(), null);
474   }
475
476   /**
477    * Returns the name of the command performed by the refactoring.
478    *
479    * @return command name
480    */
481   protected abstract String getCommandName();
482
483   public void finish() {
484     if (!ourRenamersStack.isEmpty() && ourRenamersStack.peek() == this) {
485       ourRenamersStack.pop();
486     }
487     if (myHighlighters != null) {
488       if (!myProject.isDisposed()) {
489         final HighlightManager highlightManager = HighlightManager.getInstance(myProject);
490         for (RangeHighlighter highlighter : myHighlighters) {
491           highlightManager.removeSegmentHighlighter(myEditor, highlighter);
492         }
493       }
494
495       myHighlighters = null;
496       myEditor.putUserData(INPLACE_RENAMER, null);
497     }
498   }
499
500   protected void addHighlights(@NotNull Map<TextRange, TextAttributes> ranges,
501                                @NotNull Editor editor,
502                                @NotNull Collection<RangeHighlighter> highlighters,
503                                @NotNull HighlightManager highlightManager) {
504     for (Map.Entry<TextRange, TextAttributes> entry : ranges.entrySet()) {
505       TextRange range = entry.getKey();
506       TextAttributes attributes = entry.getValue();
507       highlightManager.addOccurrenceHighlight(editor, range.getStartOffset(), range.getEndOffset(), attributes, 0, highlighters, null);
508     }
509
510     for (RangeHighlighter highlighter : highlighters) {
511       highlighter.setGreedyToLeft(true);
512       highlighter.setGreedyToRight(true);
513     }
514   }
515
516   protected abstract boolean performRefactoring();
517
518   private void addVariable(final PsiReference reference,
519                            final PsiElement selectedElement,
520                            final TemplateBuilderImpl builder,
521                            int offset) {
522     if (reference.getElement() == selectedElement &&
523         contains(reference.getRangeInElement().shiftRight(selectedElement.getTextRange().getStartOffset()), offset)) {
524       Expression expression = new MyExpression(getInitialName(), myNameSuggestions);
525       builder.replaceElement(reference, PRIMARY_VARIABLE_NAME, expression, true);
526     }
527     else {
528       builder.replaceElement(reference, OTHER_VARIABLE_NAME, PRIMARY_VARIABLE_NAME, false);
529     }
530   }
531
532   private void addVariable(final PsiElement element,
533                            final PsiElement selectedElement,
534                            final TemplateBuilderImpl builder) {
535     addVariable(element, null, selectedElement, builder);
536   }
537
538   private void addVariable(final PsiElement element,
539                            @Nullable final TextRange textRange,
540                            final PsiElement selectedElement,
541                            final TemplateBuilderImpl builder) {
542     if (element == selectedElement) {
543       Expression expression = new MyExpression(getInitialName(), myNameSuggestions);
544       builder.replaceElement(element, PRIMARY_VARIABLE_NAME, expression, true);
545     }
546     else if (textRange != null) {
547       builder.replaceElement(element, textRange, OTHER_VARIABLE_NAME, PRIMARY_VARIABLE_NAME, false);
548     }
549     else {
550       builder.replaceElement(element, OTHER_VARIABLE_NAME, PRIMARY_VARIABLE_NAME, false);
551     }
552   }
553
554
555   public void setElementToRename(PsiNamedElement elementToRename) {
556     myElementToRename = elementToRename;
557   }
558
559   protected static VirtualFile getTopLevelVirtualFile(final FileViewProvider fileViewProvider) {
560     VirtualFile file = fileViewProvider.getVirtualFile();
561     if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate();
562     return file;
563   }
564
565   @TestOnly
566   public static void checkCleared() {
567     try {
568       assert ourRenamersStack.isEmpty() : ourRenamersStack;
569     }
570     finally {
571       ourRenamersStack.clear();
572     }
573   }
574
575   private static PsiElement getSelectedInEditorElement(@Nullable PsiElement nameIdentifier,
576                                                        final Collection<PsiReference> refs,
577                                                        Collection<Pair<PsiElement, TextRange>> stringUsages,
578                                                        final int offset) {
579     if (nameIdentifier != null) {
580       final TextRange range = nameIdentifier.getTextRange();
581       if (contains(range, offset)) return nameIdentifier;
582     }
583
584     for (PsiReference ref : refs) {
585       final PsiElement element = ref.getElement();
586       if (contains(ref.getRangeInElement().shiftRight(element.getTextRange().getStartOffset()), offset)) return element;
587     }
588
589     for (Pair<PsiElement, TextRange> stringUsage : stringUsages) {
590       final PsiElement element = stringUsage.first;
591       if (contains(stringUsage.second.shiftRight(element.getTextRange().getStartOffset()), offset)) return element;
592     }
593
594     LOG.assertTrue(false);
595     return null;
596   }
597
598   private static boolean contains(final TextRange range, final int offset) {
599     return range.getStartOffset() <= offset && offset <= range.getEndOffset();
600   }
601
602   protected class MyExpression extends Expression {
603     private final String myName;
604     private final LookupElement[] myLookupItems;
605
606     protected MyExpression(String name, LinkedHashSet<String> names) {
607       myName = name;
608       if (names == null) {
609         names = new LinkedHashSet<String>();
610         for (NameSuggestionProvider provider : Extensions.getExtensions(NameSuggestionProvider.EP_NAME)) {
611           provider.getSuggestedNames(myElementToRename, myElementToRename, names);
612         }
613       }
614       myLookupItems = new LookupElement[names.size()];
615       final Iterator<String> iterator = names.iterator();
616       for (int i = 0; i < myLookupItems.length; i++) {
617         final String suggestion = iterator.next();
618         myLookupItems[i] = LookupElementBuilder.create(suggestion).setInsertHandler(new InsertHandler<LookupElement>() {
619           @Override
620           public void handleInsert(InsertionContext context, LookupElement item) {
621             if (shouldSelectAll()) return;
622             final Editor topLevelEditor = InjectedLanguageUtil.getTopLevelEditor(myEditor);
623             final TemplateState templateState = TemplateManagerImpl.getTemplateState(topLevelEditor);
624             if (templateState != null) {
625               final TextRange range = templateState.getCurrentVariableRange();
626               if (range != null) {
627                 topLevelEditor.getDocument().replaceString(range.getStartOffset(), range.getEndOffset(), suggestion);
628               }
629             }
630           }
631         });
632       }
633     }
634
635     public LookupElement[] calculateLookupItems(ExpressionContext context) {
636       return createLookupItems(myLookupItems, myName);
637     }
638
639     public Result calculateQuickResult(ExpressionContext context) {
640       return calculateResult(context);
641     }
642
643     public Result calculateResult(ExpressionContext context) {
644       TemplateState templateState = TemplateManagerImpl.getTemplateState(myEditor);
645       final TextResult insertedValue = templateState != null ? templateState.getVariableValue(PRIMARY_VARIABLE_NAME) : null;
646       if (insertedValue != null) {
647         if (!insertedValue.getText().isEmpty()) return insertedValue;
648       }
649       return new TextResult(myName);
650     }
651
652     @Override
653     public String getAdvertisingText() {
654       return myAdvertisementText;
655     }
656   }
657
658   private abstract class MyTemplateListener extends TemplateEditingAdapter {
659
660     protected abstract void restoreDaemonUpdateState();
661
662     public void beforeTemplateFinished(final TemplateState templateState, Template template) {
663       try {
664         final TextResult value = templateState.getVariableValue(PRIMARY_VARIABLE_NAME);
665         myInsertedName = value != null ? value.toString() : null;
666
667         final int currentOffset = myEditor.getCaretModel().getOffset();
668         myBeforeRevert =
669           myRenameOffset != null && myRenameOffset.getEndOffset() >= currentOffset && myRenameOffset.getStartOffset() <= currentOffset
670           ? myEditor.getDocument().createRangeMarker(myRenameOffset.getStartOffset(), currentOffset)
671           : null;
672         if (myBeforeRevert != null) {
673           myBeforeRevert.setGreedyToRight(true);
674         }
675         finish();
676       }
677       finally {
678         restoreDaemonUpdateState();
679       }
680     }
681
682     @Override
683     public void templateFinished(Template template, final boolean brokenOff) {
684       boolean bind = false;
685       try {
686         super.templateFinished(template, brokenOff);
687         if (!brokenOff) {
688           bind = performRefactoring();
689         }
690         moveOffsetAfter(!brokenOff);
691       }
692       finally {
693         if (!bind) {
694           FinishMarkAction.finish(myProject, myEditor, myMarkAction);
695           if (myBeforeRevert != null) {
696             myBeforeRevert.dispose();
697           }
698         }
699       }
700     }
701
702     public void templateCancelled(Template template) {
703       try {
704         final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject);
705         documentManager.commitAllDocuments();
706         finish();
707         moveOffsetAfter(false);
708         ApplicationManager.getApplication().runWriteAction(new Runnable() {
709           public void run() {
710             documentManager.doPostponedOperationsAndUnblockDocument(myEditor.getDocument());
711           }
712         });
713       }
714       finally {
715         try {
716           restoreDaemonUpdateState();
717         }
718         finally {
719           FinishMarkAction.finish(myProject, myEditor, myMarkAction);
720         }
721       }
722     }
723   }
724 }