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
;
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
)) {
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
) {
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
);
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
) {
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() {
166 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
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
) {
180 if (snapshot
!= null) {
181 TextResult value
= templateState
.getVariableValue(PRIMARY_VARIABLE_NAME
);
183 final String newName
= value
.toString();
184 if (LanguageNamesValidation
.INSTANCE
.forLanguage(scope1
.getLanguage()).isIdentifier(newName
, myProject
)) {
185 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
187 snapshot
.apply(newName
);
195 public void templateCancelled(Template template
) {
201 final LookupImpl lookup
= (LookupImpl
)LookupManager
.getActiveLookup(myEditor
);
202 final boolean lookupShown
= lookup
!= null && lookup
.getLookupStart() < offset
;
204 lookup
.setAdditionalPrefix(myEditor
.getDocument().getCharsSequence().subSequence(lookup
.getLookupStart(), offset
).toString());
206 myEditor
.getCaretModel().moveToOffset(offset
);
208 lookup
.setAdditionalPrefix("");
212 addHighlights(rangesToHighlight
, topLevelEditor
, myHighlighters
, highlightManager
);
216 }, RefactoringBundle
.message("rename.title"), null);
221 private static VirtualFile
getTopLevelVirtualFile(final FileViewProvider fileViewProvider
) {
222 VirtualFile file
= fileViewProvider
.getVirtualFile();
223 if (file
instanceof VirtualFileWindow
) file
= ((VirtualFileWindow
)file
).getDelegate();
228 public static void checkCleared(){
230 assert ourRenamersStack
.isEmpty() : ourRenamersStack
;
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);
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);
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);
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
) {
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
);