typo (IDEADEV-41471)
[fedora-idea.git] / platform / lang-impl / src / com / intellij / refactoring / rename / inplace / VariableInplaceRenamer.java
blob7e4a0e219d04b5c74d67ec37cb81db3162911e37
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.project.Project;
39 import com.intellij.openapi.util.TextRange;
40 import com.intellij.openapi.vfs.VirtualFile;
41 import com.intellij.psi.*;
42 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
43 import com.intellij.psi.search.LocalSearchScope;
44 import com.intellij.psi.search.SearchScope;
45 import com.intellij.psi.search.searches.ReferencesSearch;
46 import com.intellij.psi.util.PsiTreeUtil;
47 import com.intellij.psi.util.PsiUtilBase;
48 import com.intellij.refactoring.RefactoringBundle;
49 import com.intellij.refactoring.rename.NameSuggestionProvider;
50 import com.intellij.refactoring.util.CommonRefactoringUtil;
51 import com.intellij.refactoring.util.TextOccurrencesUtil;
52 import com.intellij.util.PairProcessor;
53 import com.intellij.util.containers.Stack;
54 import gnu.trove.THashMap;
55 import org.jetbrains.annotations.NonNls;
56 import org.jetbrains.annotations.NotNull;
57 import org.jetbrains.annotations.TestOnly;
59 import java.util.ArrayList;
60 import java.util.Collection;
61 import java.util.List;
62 import java.util.Map;
64 /**
65 * @author ven
67 public class VariableInplaceRenamer {
68 private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.rename.inplace.VariableInplaceRenamer");
69 public static final LanguageExtension<ResolveSnapshotProvider> INSTANCE = new LanguageExtension<ResolveSnapshotProvider>(
70 "com.intellij.rename.inplace.resolveSnapshotProvider"
73 private final PsiNameIdentifierOwner myElementToRename;
74 @NonNls private static final String PRIMARY_VARIABLE_NAME = "PrimaryVariable";
75 @NonNls private static final String OTHER_VARIABLE_NAME = "OtherVariable";
76 private ArrayList<RangeHighlighter> myHighlighters;
77 private final Editor myEditor;
78 private final Project myProject;
80 private static final Stack<VariableInplaceRenamer> ourRenamersStack = new Stack<VariableInplaceRenamer>();
82 public VariableInplaceRenamer(PsiNameIdentifierOwner elementToRename, Editor editor) {
83 myElementToRename = elementToRename;
84 myEditor = /*(editor instanceof EditorWindow)? ((EditorWindow)editor).getDelegate() : */editor;
85 myProject = myElementToRename.getProject();
88 public boolean performInplaceRename() {
89 final Collection<PsiReference> refs = ReferencesSearch.search(myElementToRename).findAll();
91 final PsiReference reference = myElementToRename.getContainingFile().findReferenceAt(myEditor.getCaretModel().getOffset());
92 if (reference != null && !refs.contains(reference)) {
93 refs.add(reference);
96 final FileViewProvider fileViewProvider = myElementToRename.getContainingFile().getViewProvider();
97 VirtualFile file = getTopLevelVirtualFile(fileViewProvider);
99 for (PsiReference ref : refs) {
100 final FileViewProvider usageViewProvider = ref.getElement().getContainingFile().getViewProvider();
102 if (getTopLevelVirtualFile(usageViewProvider) != file) {
103 return false;
107 while (!ourRenamersStack.isEmpty()) {
108 ourRenamersStack.peek().finish();
111 ourRenamersStack.push(this);
113 final Map<TextRange, TextAttributes> rangesToHighlight = new THashMap<TextRange, TextAttributes>();
114 //it is crucial to highlight AFTER the template is started, so we collect ranges first
115 collectElementsToHighlight(rangesToHighlight, refs);
117 final HighlightManager highlightManager = HighlightManager.getInstance(myProject);
119 PsiElement scope = null;
120 final SearchScope searchScope = myElementToRename.getUseScope();
121 if (searchScope instanceof LocalSearchScope) {
122 final PsiElement[] elements = ((LocalSearchScope)searchScope).getScope();
123 scope = PsiTreeUtil.findCommonParent(elements);
126 if (scope == null) {
127 return false; // Should have valid local search scope for inplace rename
130 final PsiFile containingFile = scope.getContainingFile();
131 if (containingFile == null){
132 return false; // Should have valid local search scope for inplace rename
134 final PsiElement context = containingFile.getContext();
135 if (context != null) {
136 scope = context.getContainingFile();
139 String stringToSearch = myElementToRename.getName();
140 if (stringToSearch != null &&
141 !TextOccurrencesUtil.processUsagesInStringsAndComments(myElementToRename, stringToSearch, true, new PairProcessor<PsiElement, TextRange>() {
142 public boolean process(PsiElement psiElement, TextRange textRange) {
143 return false;
145 })) {
146 return false;
149 ResolveSnapshotProvider resolveSnapshotProvider = INSTANCE.forLanguage(scope.getLanguage());
150 final ResolveSnapshotProvider.ResolveSnapshot snapshot = resolveSnapshotProvider != null ?
151 resolveSnapshotProvider.createSnapshot(scope):null;
152 final TemplateBuilderImpl builder = new TemplateBuilderImpl(scope);
154 final PsiElement nameIdentifier = myElementToRename.getNameIdentifier();
155 PsiElement selectedElement = getSelectedInEditorElement(nameIdentifier, refs, myEditor.getCaretModel().getOffset());
156 if (!CommonRefactoringUtil.checkReadOnlyStatus(myProject, myElementToRename)) return true;
158 if (nameIdentifier != null) addVariable(nameIdentifier, selectedElement, builder);
159 for (PsiReference ref : refs) {
160 addVariable(ref, selectedElement, builder);
163 final PsiElement scope1 = scope;
164 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
165 public void run() {
166 ApplicationManager.getApplication().runWriteAction(new Runnable() {
167 public void run() {
168 int offset = myEditor.getCaretModel().getOffset();
169 Template template = builder.buildInlineTemplate();
170 template.setToShortenLongNames(false);
171 TextRange range = scope1.getTextRange();
172 assert range != null;
173 myHighlighters = new ArrayList<RangeHighlighter>();
174 Editor topLevelEditor = InjectedLanguageUtil.getTopLevelEditor(myEditor);
175 topLevelEditor.getCaretModel().moveToOffset(range.getStartOffset());
176 TemplateManager.getInstance(myProject).startTemplate(topLevelEditor, template, new TemplateEditingAdapter() {
177 public void beforeTemplateFinished(final TemplateState templateState, Template template) {
178 finish();
180 if (snapshot != null) {
181 TextResult value = templateState.getVariableValue(PRIMARY_VARIABLE_NAME);
182 if (value != null) {
183 final String newName = value.toString();
184 if (LanguageNamesValidation.INSTANCE.forLanguage(scope1.getLanguage()).isIdentifier(newName, myProject)) {
185 ApplicationManager.getApplication().runWriteAction(new Runnable() {
186 public void run() {
187 snapshot.apply(newName);
195 public void templateCancelled(Template template) {
196 finish();
200 //move to old offset
201 final LookupImpl lookup = (LookupImpl)LookupManager.getActiveLookup(myEditor);
202 final boolean lookupShown = lookup != null && lookup.getLookupStart() < offset;
203 if (lookupShown) {
204 lookup.setAdditionalPrefix(myEditor.getDocument().getCharsSequence().subSequence(lookup.getLookupStart(), offset).toString());
206 myEditor.getCaretModel().moveToOffset(offset);
207 if (lookupShown) {
208 lookup.setAdditionalPrefix("");
211 //add highlights
212 addHighlights(rangesToHighlight, topLevelEditor, myHighlighters, highlightManager);
216 }, RefactoringBundle.message("rename.title"), null);
218 return true;
221 private static VirtualFile getTopLevelVirtualFile(final FileViewProvider fileViewProvider) {
222 VirtualFile file = fileViewProvider.getVirtualFile();
223 if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate();
224 return file;
227 @TestOnly
228 public static void checkCleared(){
229 try {
230 assert ourRenamersStack.isEmpty() : ourRenamersStack;
232 finally {
233 ourRenamersStack.clear();
237 public void finish() {
238 if (!ourRenamersStack.isEmpty() && ourRenamersStack.peek() == this) {
239 ourRenamersStack.pop();
241 if (myHighlighters != null) {
242 final HighlightManager highlightManager = HighlightManager.getInstance(myProject);
243 for (RangeHighlighter highlighter : myHighlighters) {
244 highlightManager.removeSegmentHighlighter(myEditor, highlighter);
247 myHighlighters = null;
251 private void collectElementsToHighlight(Map<TextRange, TextAttributes> rangesToHighlight, Collection<PsiReference> refs) {
252 EditorColorsManager colorsManager = EditorColorsManager.getInstance();
253 PsiElement nameId = myElementToRename.getNameIdentifier();
254 LOG.assertTrue(nameId != null);
255 rangesToHighlight.put(nameId.getTextRange().shiftRight(PsiUtilBase.findInjectedElementOffsetInRealDocument(nameId)), colorsManager.getGlobalScheme().getAttributes(EditorColors.WRITE_SEARCH_RESULT_ATTRIBUTES));
257 for (PsiReference ref : refs) {
258 final PsiElement element = ref.getElement();
259 TextRange range = ref.getRangeInElement().shiftRight(
260 element.getTextRange().getStartOffset() +
261 PsiUtilBase.findInjectedElementOffsetInRealDocument(element)
264 ReadWriteAccessDetector writeAccessDetector = ReadWriteAccessDetector.findDetector(element);
265 // TODO: read / write usages
266 boolean isForWrite = writeAccessDetector != null &&
267 ReadWriteAccessDetector.Access.Write == writeAccessDetector.getExpressionAccess(element);
268 TextAttributes attributes = colorsManager.getGlobalScheme().getAttributes(isForWrite ?
269 EditorColors.WRITE_SEARCH_RESULT_ATTRIBUTES :
270 EditorColors.SEARCH_RESULT_ATTRIBUTES);
271 rangesToHighlight.put(range, attributes);
275 private static void addHighlights(@NotNull Map<TextRange, TextAttributes> ranges, @NotNull Editor editor, @NotNull Collection<RangeHighlighter> highlighters, @NotNull HighlightManager highlightManager) {
276 for (Map.Entry<TextRange,TextAttributes> entry : ranges.entrySet()) {
277 TextRange range = entry.getKey();
278 TextAttributes attributes = entry.getValue();
279 highlightManager.addOccurrenceHighlight(editor, range.getStartOffset(), range.getEndOffset(), attributes, 0, highlighters, null);
282 for (RangeHighlighter highlighter : highlighters) {
283 highlighter.setGreedyToLeft(true);
284 highlighter.setGreedyToRight(true);
288 private static PsiElement getSelectedInEditorElement(final PsiElement nameIdentifier, final Collection<PsiReference> refs, final int offset) {
289 if (nameIdentifier != null) {
290 final TextRange range = nameIdentifier.getTextRange()/*.shiftRight(PsiUtilBase.findInjectedElementOffsetInRealDocument(nameIdentifier))*/;
291 if (contains(range, offset)) return nameIdentifier;
294 for (PsiReference ref : refs) {
295 final PsiElement element = ref.getElement();
296 final TextRange range = element.getTextRange()/*.shiftRight(PsiUtilBase.findInjectedElementOffsetInRealDocument(ref.getElement()))*/;
297 if (contains(range, offset)) return element;
300 LOG.assertTrue(false);
301 return null;
304 private static boolean contains(final TextRange range, final int offset) {
305 return range.getStartOffset() <= offset && offset <= range.getEndOffset();
308 private void addVariable(final PsiReference reference, final PsiElement selectedElement, final TemplateBuilderImpl builder) {
309 if (reference.getElement() == selectedElement) {
310 Expression expression = new MyExpression(myElementToRename.getName());
311 builder.replaceElement(reference, PRIMARY_VARIABLE_NAME, expression, true);
313 else {
314 builder.replaceElement(reference, OTHER_VARIABLE_NAME, PRIMARY_VARIABLE_NAME, false);
318 private void addVariable(final PsiElement element, final PsiElement selectedElement, final TemplateBuilderImpl builder) {
319 if (element == selectedElement) {
320 Expression expression = new MyExpression(myElementToRename.getName());
321 builder.replaceElement(element, PRIMARY_VARIABLE_NAME, expression, true);
323 else {
324 builder.replaceElement(element, OTHER_VARIABLE_NAME, PRIMARY_VARIABLE_NAME, false);
328 private class MyExpression extends Expression {
329 private final String myName;
330 private final LookupElement[] myLookupItems;
332 private MyExpression(String name) {
333 myName = name;
334 List<String> names = new ArrayList<String>();
335 for(NameSuggestionProvider provider: Extensions.getExtensions(NameSuggestionProvider.EP_NAME)) {
336 provider.getSuggestedNames(myElementToRename, myElementToRename, names);
338 myLookupItems = new LookupElement[names.size()];
339 for (int i = 0; i < myLookupItems.length; i++) {
340 myLookupItems[i] = LookupElementBuilder.create(names.get(i));
344 public LookupElement[] calculateLookupItems(ExpressionContext context) {
345 return myLookupItems;
348 public Result calculateQuickResult(ExpressionContext context) {
349 return new TextResult(myName);
352 public Result calculateResult(ExpressionContext context) {
353 return new TextResult(myName);