3 * Copyright 2000-2009 JetBrains s.r.o.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
18 package com
.intellij
.find
.replaceInProject
;
20 import com
.intellij
.find
.FindBundle
;
21 import com
.intellij
.find
.FindManager
;
22 import com
.intellij
.find
.FindModel
;
23 import com
.intellij
.find
.FindResult
;
24 import com
.intellij
.find
.findInProject
.FindInProjectManager
;
25 import com
.intellij
.find
.impl
.FindInProjectUtil
;
26 import com
.intellij
.openapi
.actionSystem
.DataContext
;
27 import com
.intellij
.openapi
.actionSystem
.PlatformDataKeys
;
28 import com
.intellij
.openapi
.application
.ApplicationManager
;
29 import com
.intellij
.openapi
.command
.CommandProcessor
;
30 import com
.intellij
.openapi
.components
.ServiceManager
;
31 import com
.intellij
.openapi
.editor
.Document
;
32 import com
.intellij
.openapi
.editor
.Editor
;
33 import com
.intellij
.openapi
.editor
.RangeMarker
;
34 import com
.intellij
.openapi
.project
.Project
;
35 import com
.intellij
.openapi
.ui
.Messages
;
36 import com
.intellij
.openapi
.util
.Computable
;
37 import com
.intellij
.openapi
.util
.Factory
;
38 import com
.intellij
.openapi
.vfs
.ReadonlyStatusHandler
;
39 import com
.intellij
.openapi
.vfs
.VfsUtil
;
40 import com
.intellij
.openapi
.vfs
.VirtualFile
;
41 import com
.intellij
.openapi
.wm
.WindowManager
;
42 import com
.intellij
.psi
.PsiDirectory
;
43 import com
.intellij
.psi
.PsiElement
;
44 import com
.intellij
.psi
.PsiFile
;
45 import com
.intellij
.usageView
.UsageInfo
;
46 import com
.intellij
.usages
.*;
47 import com
.intellij
.usages
.rules
.UsageInFile
;
48 import com
.intellij
.util
.AdapterProcessor
;
49 import com
.intellij
.util
.Processor
;
50 import org
.jetbrains
.annotations
.NotNull
;
53 import java
.util
.Collection
;
54 import java
.util
.HashSet
;
55 import java
.util
.List
;
58 public class ReplaceInProjectManager
{
59 private final Project myProject
;
60 private boolean myIsFindInProgress
= false;
62 public static ReplaceInProjectManager
getInstance(Project project
) {
63 return ServiceManager
.getService(project
, ReplaceInProjectManager
.class);
66 public ReplaceInProjectManager(Project project
) {
70 static class ReplaceContext
{
71 private final UsageView usageView
;
72 private final FindModel findModel
;
73 private Set
<Usage
> excludedSet
;
75 ReplaceContext(@NotNull UsageView usageView
, @NotNull FindModel findModel
) {
76 this.usageView
= usageView
;
77 this.findModel
= findModel
;
81 public FindModel
getFindModel() {
86 public UsageView
getUsageView() {
91 public Set
<Usage
> getExcludedSet() {
92 if (excludedSet
== null) excludedSet
= usageView
.getExcludedUsages();
97 public void replaceInProject(DataContext dataContext
) {
98 final FindManager findManager
= FindManager
.getInstance(myProject
);
99 final FindModel findModel
= (FindModel
) findManager
.getFindInProjectModel().clone();
100 findModel
.setReplaceState(true);
101 FindInProjectUtil
.setDirectoryName(findModel
, dataContext
);
103 Editor editor
= PlatformDataKeys
.EDITOR
.getData(dataContext
);
105 String s
= editor
.getSelectionModel().getSelectedText();
106 if (s
!= null && !s
.contains("\r") && !s
.contains("\n")){
107 findModel
.setStringToFind(s
);
111 findManager
.showFindDialog(findModel
, new Runnable() {
113 final PsiDirectory psiDirectory
= FindInProjectUtil
.getPsiDirectory(findModel
, myProject
);
114 if (!findModel
.isProjectScope() && psiDirectory
== null && findModel
.getModuleName()==null && findModel
.getCustomScope() == null){
118 UsageViewManager manager
= UsageViewManager
.getInstance(myProject
);
120 if (manager
== null) return;
121 findManager
.getFindInProjectModel().copyFrom(findModel
);
122 final FindModel findModelCopy
= (FindModel
)findModel
.clone();
124 final UsageViewPresentation presentation
= FindInProjectUtil
.setupViewPresentation(true, findModelCopy
);
125 final FindUsagesProcessPresentation processPresentation
= FindInProjectUtil
.setupProcessPresentation(
126 myProject
, true, presentation
129 final ReplaceContext
[] context
= new ReplaceContext
[1];
131 manager
.searchAndShowUsages(
132 new UsageTarget
[] { new FindInProjectUtil
.StringUsageTarget(findModelCopy
.getStringToFind()) },
133 new Factory
<UsageSearcher
>() {
134 public UsageSearcher
create() {
135 return new UsageSearcher() {
137 public void generate(final Processor
<Usage
> processor
) {
139 myIsFindInProgress
= true;
141 FindInProjectUtil
.findUsages(
145 new AdapterProcessor
<UsageInfo
, Usage
>(processor
, UsageInfo2UsageAdapter
.CONVERTER
));
148 myIsFindInProgress
= false;
156 new UsageViewManager
.UsageViewStateListener() {
157 public void usageViewCreated(UsageView usageView
) {
158 context
[0] = new ReplaceContext(usageView
, findModelCopy
);
159 addReplaceActions(context
[0]);
162 public void findingUsagesFinished(final UsageView usageView
) {
163 if (context
[0]!=null && findManager
.getFindInProjectModel().isPromptOnReplace()){
164 SwingUtilities
.invokeLater(new Runnable() {
166 replaceWithPrompt(context
[0]);
177 private void replaceWithPrompt(final ReplaceContext replaceContext
) {
178 final List
<Usage
> _usages
= replaceContext
.getUsageView().getSortedUsages();
180 if (FindInProjectUtil
.hasReadOnlyUsages(_usages
)){
181 WindowManager
.getInstance().getStatusBar(myProject
).setInfo(
182 FindBundle
.message("find.replace.occurrences.found.in.read.only.files.status"));
186 final Usage
[] usages
= _usages
.toArray(new Usage
[_usages
.size()]);
188 //usageView.expandAll();
189 for(int i
= 0; i
< usages
.length
; ++i
){
190 final Usage usage
= usages
[i
];
191 final UsageInfo usageInfo
= ((UsageInfo2UsageAdapter
)usage
).getUsageInfo();
193 final PsiElement elt
= usageInfo
.getElement();
194 if (elt
== null) continue;
195 final PsiFile psiFile
= elt
.getContainingFile();
196 if (!psiFile
.isWritable()) continue;
198 Runnable selectOnEditorRunnable
= new Runnable() {
200 final VirtualFile virtualFile
= psiFile
.getVirtualFile();
202 if (virtualFile
!= null &&
203 ApplicationManager
.getApplication().runReadAction(
204 new Computable
<Boolean
>() {
205 public Boolean
compute() {
206 return virtualFile
.isValid() ? Boolean
.TRUE
: Boolean
.FALSE
;
211 if (usage
.isValid()) {
212 usage
.highlightInEditor();
213 replaceContext
.getUsageView().selectUsages(new Usage
[]{usage
});
219 CommandProcessor
.getInstance().executeCommand(myProject
, selectOnEditorRunnable
,
220 FindBundle
.message("find.replace.select.on.editor.command"), null);
221 String title
= FindBundle
.message("find.replace.found.usage.title", i
+ 1, usages
.length
);
222 int result
= FindManager
.getInstance(myProject
).showPromptDialog(replaceContext
.getFindModel(), title
);
224 if (result
== FindManager
.PromptResult
.CANCEL
){
227 if (result
== FindManager
.PromptResult
.SKIP
){
231 final int currentNumber
= i
;
232 if (result
== FindManager
.PromptResult
.OK
){
233 Runnable runnable
= new Runnable() {
235 doReplace(replaceContext
, usage
);
236 replaceContext
.getUsageView().removeUsage(usage
);
239 CommandProcessor
.getInstance().executeCommand(myProject
, runnable
, FindBundle
.message("find.replace.command"), null);
240 if (i
+ 1 == usages
.length
){
241 replaceContext
.getUsageView().close();
246 if (result
== FindManager
.PromptResult
.ALL_IN_THIS_FILE
){
247 final int[] nextNumber
= new int[1];
249 Runnable runnable
= new Runnable() {
251 int j
= currentNumber
;
253 for(; j
< usages
.length
; j
++){
254 final Usage usage
= usages
[j
];
255 final UsageInfo usageInfo
= ((UsageInfo2UsageAdapter
)usage
).getUsageInfo();
257 final PsiElement elt
= usageInfo
.getElement();
258 if (elt
== null) continue;
259 PsiFile otherPsiFile
= elt
.getContainingFile();
260 if (!otherPsiFile
.equals(psiFile
)){
263 doReplace(replaceContext
, usage
);
264 replaceContext
.getUsageView().removeUsage(usage
);
266 if (j
== usages
.length
){
267 replaceContext
.getUsageView().close();
273 CommandProcessor
.getInstance().executeCommand(myProject
, runnable
, FindBundle
.message("find.replace.command"), null);
275 //noinspection AssignmentToForLoopParameter
276 i
= nextNumber
[0] - 1;
279 if (result
== FindManager
.PromptResult
.ALL_FILES
) {
280 CommandProcessor
.getInstance().executeCommand(
281 myProject
, new Runnable() {
283 doReplace(replaceContext
, _usages
);
284 replaceContext
.getUsageView().close();
287 FindBundle
.message("find.replace.command"),
295 private void addReplaceActions(final ReplaceContext replaceContext
) {
296 final Runnable replaceRunnable
= new Runnable() {
298 doReplace(replaceContext
, replaceContext
.getUsageView().getUsages());
301 replaceContext
.getUsageView().addPerformOperationAction(replaceRunnable
, FindBundle
.message("find.replace.all.action"),
302 null, FindBundle
.message("find.replace.all.action.description"));
304 final Runnable replaceSelectedRunnable
= new Runnable() {
306 doReplaceSelected(replaceContext
);
310 replaceContext
.getUsageView().addButtonToLowerPane(
311 replaceSelectedRunnable
,
312 FindBundle
.message("find.replace.selected.action")
316 private void doReplace(final ReplaceContext replaceContext
, Collection
<Usage
> usages
) {
317 for (final Usage usage
: usages
) {
318 doReplace(replaceContext
, usage
);
320 reportNumberReplacedOccurences(myProject
, usages
.size());
323 public static void reportNumberReplacedOccurences(Project project
, int occurrences
) {
324 if (occurrences
!= 0) {
325 WindowManager
.getInstance().getStatusBar(project
).setInfo(FindBundle
.message("0.occurrences.replaced", occurrences
));
329 private void doReplace(final ReplaceContext replaceContext
, final Usage usage
) {
330 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
332 if (replaceContext
.getExcludedSet().contains(usage
)){
336 List
<RangeMarker
> markers
= ((UsageInfo2UsageAdapter
)usage
).getRangeMarkers();
337 for (RangeMarker marker
: markers
) {
338 Document document
= marker
.getDocument();
339 if (!document
.isWritable()) return;
341 final int textOffset
= marker
.getStartOffset();
342 if (textOffset
< 0 || textOffset
>= document
.getTextLength()){
345 final int textEndOffset
= marker
.getEndOffset();
346 if (textEndOffset
< 0 || textOffset
> document
.getTextLength()){
349 FindManager findManager
= FindManager
.getInstance(myProject
);
350 final CharSequence foundString
= document
.getCharsSequence().subSequence(textOffset
, textEndOffset
);
351 FindResult findResult
= findManager
.findString(document
.getCharsSequence(), textOffset
, replaceContext
.getFindModel());
352 if (!findResult
.isStringFound()){
355 String stringToReplace
= findManager
.getStringToReplace(foundString
.toString(), replaceContext
.getFindModel());
356 if (stringToReplace
!= null) {
357 document
.replaceString(textOffset
, textEndOffset
, stringToReplace
);
364 private void doReplaceSelected(final ReplaceContext replaceContext
) {
365 final Set
<Usage
> selectedUsages
= replaceContext
.getUsageView().getSelectedUsages();
366 if(selectedUsages
== null){
370 Set
<VirtualFile
> readOnlyFiles
= null;
371 for (final Usage usage
: selectedUsages
) {
372 final VirtualFile file
= ((UsageInFile
)usage
).getFile();
374 if (!file
.isWritable()) {
375 if (readOnlyFiles
== null) readOnlyFiles
= new HashSet
<VirtualFile
>();
376 readOnlyFiles
.add(file
);
380 if (readOnlyFiles
!= null) {
381 ReadonlyStatusHandler
.getInstance(myProject
).ensureFilesWritable(VfsUtil
.toVirtualFileArray(readOnlyFiles
));
384 if (FindInProjectUtil
.hasReadOnlyUsages(selectedUsages
)){
385 int result
= Messages
.showOkCancelDialog(
386 replaceContext
.getUsageView().getComponent(),
387 FindBundle
.message("find.replace.occurrences.in.read.only.files.prompt"),
388 FindBundle
.message("find.replace.occurrences.in.read.only.files.title"),
389 Messages
.getWarningIcon()
396 CommandProcessor
.getInstance().executeCommand(
397 myProject
, new Runnable() {
399 doReplace(replaceContext
, selectedUsages
);
400 for (final Usage selectedUsage
: selectedUsages
) {
401 replaceContext
.getUsageView().removeUsage(selectedUsage
);
404 if (replaceContext
.getUsageView().getUsages().isEmpty()){
405 replaceContext
.getUsageView().close();
408 replaceContext
.getUsageView().getComponent().requestFocus();
411 FindBundle
.message("find.replace.command"),
416 public boolean isWorkInProgress() {
417 return myIsFindInProgress
;
420 public boolean isEnabled () {
421 return !myIsFindInProgress
&& !FindInProjectManager
.getInstance(myProject
).isWorkInProgress();