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