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