diff --git a/java/java-impl/src/com/intellij/refactoring/util/duplicates/DuplicatesImpl.java b/java/java-impl/src/com/intellij/refactoring/util/duplicates/DuplicatesImpl.java index f6fe8a74680a..be62ee4d9310 100644 --- a/java/java-impl/src/com/intellij/refactoring/util/duplicates/DuplicatesImpl.java +++ b/java/java-impl/src/com/intellij/refactoring/util/duplicates/DuplicatesImpl.java @@ -112,7 +112,7 @@ public class DuplicatesImpl { if (!ApplicationManager.getApplication().isUnitTestMode()) { if ((!skipPromptWhenOne || size > 1) && (showAll.get() == null || !showAll.get())) { final String prompt = provider.getConfirmDuplicatePrompt(match); - final ReplacePromptDialog promptDialog = new ReplacePromptDialog(false, provider.getReplaceDuplicatesTitle(idx, size), project){ + final ReplacePromptDialog promptDialog = new ReplacePromptDialog(false, provider.getReplaceDuplicatesTitle(idx, size), project) { @Override protected String getMessage() { final String message = super.getMessage(); diff --git a/java/java-tests/testSrc/com/intellij/find/FindManagerTest.java b/java/java-tests/testSrc/com/intellij/find/FindManagerTest.java index ae003acaf9d9..0fbce3182562 100644 --- a/java/java-tests/testSrc/com/intellij/find/FindManagerTest.java +++ b/java/java-tests/testSrc/com/intellij/find/FindManagerTest.java @@ -355,7 +355,7 @@ public class FindManagerTest extends DaemonAnalyzerTestCase { List usages = FindUtil.findAll(getProject(), myEditor, findModel); for (Usage usage : usages) { - ReplaceInProjectManager.getInstance(getProject()).doReplace(usage, findModel, Collections.emptySet()); + ReplaceInProjectManager.getInstance(getProject()).doReplace(usage, findModel, Collections.emptySet(), false); } String newText = StringUtil.repeat(toReplace + "\n",6); assertEquals(newText, getEditor().getDocument().getText()); diff --git a/platform/lang-api/src/com/intellij/find/FindManager.java b/platform/lang-api/src/com/intellij/find/FindManager.java index 48c04f692a5c..5b05a5bf6a5e 100644 --- a/platform/lang-api/src/com/intellij/find/FindManager.java +++ b/platform/lang-api/src/com/intellij/find/FindManager.java @@ -66,7 +66,7 @@ public abstract class FindManager { * * @param model the model containing the settings of the replace operation. * @param title the title of the dialog to show. - * @return the exit code of the dialog, as defined by the {@link PromptResult} + * @return the exit code of the dialog, as defined by the {@link com.intellij.find.FindManager.PromptResult} * interface. */ public abstract int showPromptDialog(FindModel model, String title); @@ -118,6 +118,18 @@ public abstract class FindManager { public abstract FindResult findString(@NotNull CharSequence text, int offset, @NotNull FindModel model, @Nullable VirtualFile findContextFile); + /** + * Shows a replace prompt dialog for the bad replace operation. + * + * @param model the model containing the settings of the replace operation. + * @param title the title of the dialog to show. + * @param exception exception from {@link FindManager#getStringToReplace} + * @return the exit code of the dialog, as defined by the {@link PromptResult} + * interface. May be only {@link PromptResult.CANCEL} or {@link PromptResult.SKIP} for bad replace operation + */ + + public abstract int showMalformedReplacementPrompt(FindModel model, String title, MalformedReplacementStringException exception); + public static class MalformedReplacementStringException extends Exception { public MalformedReplacementStringException(String s) { super(s); //To change body of overridden methods use File | Settings | File Templates. diff --git a/platform/lang-impl/src/com/intellij/find/FindUtil.java b/platform/lang-impl/src/com/intellij/find/FindUtil.java index 97aea5aa553b..9c4655703812 100644 --- a/platform/lang-impl/src/com/intellij/find/FindUtil.java +++ b/platform/lang-impl/src/com/intellij/find/FindUtil.java @@ -638,7 +638,7 @@ public class FindUtil { } } - ReplaceInProjectManager.reportNumberReplacedOccurences(project, occurrences); + ReplaceInProjectManager.reportNumberReplacedOccurrences(project, occurrences); return replaced; } diff --git a/platform/lang-impl/src/com/intellij/find/impl/FindManagerImpl.java b/platform/lang-impl/src/com/intellij/find/impl/FindManagerImpl.java index 88865cb426a8..ce99e3143b23 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/FindManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/find/impl/FindManagerImpl.java @@ -155,7 +155,11 @@ public class FindManagerImpl extends FindManager implements PersistentStateCompo @Override public int showPromptDialog(final FindModel model, String title) { - ReplacePromptDialog replacePromptDialog = new ReplacePromptDialog(model.isMultipleFiles(), title, myProject) { + return showPromptDialogImpl(model, title, null); + } + + public int showPromptDialogImpl(final FindModel model, String title, @Nullable final MalformedReplacementStringException exception) { + ReplacePromptDialog replacePromptDialog = new ReplacePromptDialog(model.isMultipleFiles(), title, myProject, exception) { @Override @Nullable public Point getInitialLocation() { @@ -313,6 +317,11 @@ public class FindManagerImpl extends FindManager implements PersistentStateCompo } } + @Override + public int showMalformedReplacementPrompt(FindModel model, String title, MalformedReplacementStringException exception) { + return showPromptDialogImpl(model, title, exception); + } + @Override public FindModel getPreviousFindModel() { return myPreviousFindModel; @@ -626,10 +635,14 @@ public class FindManagerImpl extends FindManager implements PersistentStateCompo return replaced.substring(matcher.start()); } catch (Exception e) { - throw new MalformedReplacementStringException(FindBundle.message("find.replace.invalid.replacement.string", model.getStringToReplace()), e); + throw createMalformedReplacementException(model, e); } } + private static MalformedReplacementStringException createMalformedReplacementException(FindModel model, Exception e) { + return new MalformedReplacementStringException(FindBundle.message("find.replace.invalid.replacement.string", model.getStringToReplace()), e); + } + private static String getStringToReplaceByRegexp0(String foundString, final FindModel model) throws MalformedReplacementStringException{ String toFind = model.getStringToFind(); String toReplace = model.getStringToReplace(); @@ -651,7 +664,7 @@ public class FindManagerImpl extends FindManager implements PersistentStateCompo return matcher.replaceAll(StringUtil.unescapeStringCharacters(toReplace)); } catch (Exception e) { - throw new MalformedReplacementStringException(FindBundle.message("find.replace.invalid.replacement.string", model.getStringToReplace()), e); + throw createMalformedReplacementException(model, e); } } else { diff --git a/platform/lang-impl/src/com/intellij/find/replaceInProject/ReplaceInProjectManager.java b/platform/lang-impl/src/com/intellij/find/replaceInProject/ReplaceInProjectManager.java index ab0e59bf4b6c..c5976f3e73bd 100644 --- a/platform/lang-impl/src/com/intellij/find/replaceInProject/ReplaceInProjectManager.java +++ b/platform/lang-impl/src/com/intellij/find/replaceInProject/ReplaceInProjectManager.java @@ -19,6 +19,7 @@ package com.intellij.find.replaceInProject; import com.intellij.find.*; import com.intellij.find.findInProject.FindInProjectManager; import com.intellij.find.impl.FindInProjectUtil; +import com.intellij.notification.NotificationGroup; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.application.ApplicationManager; @@ -27,14 +28,17 @@ import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Factory; +import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.Segment; import com.intellij.openapi.vfs.ReadonlyStatusHandler; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.StatusBar; +import com.intellij.openapi.wm.ToolWindowId; import com.intellij.openapi.wm.WindowManager; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiElement; @@ -47,12 +51,11 @@ import com.intellij.util.Processor; import org.jetbrains.annotations.NotNull; import javax.swing.*; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; public class ReplaceInProjectManager { + static final NotificationGroup NOTIFICATION_GROUP = NotificationGroup.toolWindowGroup("FindInPath", ToolWindowId.FIND, false); + private final Project myProject; private boolean myIsFindInProgress = false; @@ -214,7 +217,16 @@ public class ReplaceInProjectManager { CommandProcessor.getInstance() .executeCommand(myProject, selectOnEditorRunnable, FindBundle.message("find.replace.select.on.editor.command"), null); String title = FindBundle.message("find.replace.found.usage.title", i + 1, usages.length); - int result = FindManager.getInstance(myProject).showPromptDialog(replaceContext.getFindModel(), title); + + int result; + try { + doReplace(usage, replaceContext.getFindModel(), replaceContext.getExcludedSet(), true); + result = FindManager.getInstance(myProject).showPromptDialog(replaceContext.getFindModel(), title); + } + catch (FindManager.MalformedReplacementStringException e) { + markAsMalformedReplacement(replaceContext, usage); + result = FindManager.getInstance(myProject).showMalformedReplacementPrompt(replaceContext.getFindModel(), title, e); + } if (result == FindManager.PromptResult.CANCEL) { return; @@ -225,16 +237,15 @@ public class ReplaceInProjectManager { final int currentNumber = i; if (result == FindManager.PromptResult.OK) { + final Ref success = Ref.create(); Runnable runnable = new Runnable() { @Override public void run() { - doReplace(usage, replaceContext.getFindModel(), replaceContext.getExcludedSet()); - replaceContext.getUsageView().removeUsage(usage); + success.set(doReplace(usage, replaceContext)); } }; CommandProcessor.getInstance().executeCommand(myProject, runnable, FindBundle.message("find.replace.command"), null); - if (i + 1 == usages.length) { - replaceContext.getUsageView().close(); + if (closeUsageViewIfEmpty(replaceContext.getUsageView(), success.get())) { return; } } @@ -246,7 +257,7 @@ public class ReplaceInProjectManager { @Override public void run() { int j = currentNumber; - + boolean success = true; for (; j < usages.length; j++) { final Usage usage = usages[j]; final UsageInfo usageInfo = ((UsageInfo2UsageAdapter)usage).getUsageInfo(); @@ -257,12 +268,11 @@ public class ReplaceInProjectManager { if (!otherPsiFile.equals(psiFile)) { break; } - doReplace(usage, replaceContext.getFindModel(), replaceContext.getExcludedSet()); - replaceContext.getUsageView().removeUsage(usage); - } - if (j == usages.length) { - replaceContext.getUsageView().close(); + if (!doReplace(usage, replaceContext)) { + success = false; + } } + closeUsageViewIfEmpty(replaceContext.getUsageView(), success); nextNumber[0] = j; } }; @@ -277,8 +287,8 @@ public class ReplaceInProjectManager { CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { @Override public void run() { - doReplace(replaceContext, _usages); - replaceContext.getUsageView().close(); + final boolean success = doReplace(replaceContext, _usages); + closeUsageViewIfEmpty(replaceContext.getUsageView(), success); } }, FindBundle.message("find.replace.command"), null); break; @@ -286,15 +296,28 @@ public class ReplaceInProjectManager { } } + private boolean doReplace(Usage usage, ReplaceContext replaceContext) { + try { + doReplace(usage, replaceContext.getFindModel(), replaceContext.getExcludedSet(), false); + replaceContext.getUsageView().removeUsage(usage); + } + catch (FindManager.MalformedReplacementStringException e) { + markAsMalformedReplacement(replaceContext, usage); + return false; + } + return true; + } + private void addReplaceActions(final ReplaceContext replaceContext) { final Runnable replaceRunnable = new Runnable() { @Override public void run() { - doReplace(replaceContext, replaceContext.getUsageView().getUsages()); + final UsageView usageView = replaceContext.getUsageView(); + final boolean success = doReplace(replaceContext, usageView.getUsages()); + closeUsageViewIfEmpty(usageView, success); } }; - replaceContext.getUsageView().addPerformOperationAction(replaceRunnable, FindBundle.message("find.replace.all.action"), null, - FindBundle.message("find.replace.all.action.description")); + replaceContext.getUsageView().addButtonToLowerPane(replaceRunnable, FindBundle.message("find.replace.all.action")); final Runnable replaceSelectedRunnable = new Runnable() { @Override @@ -306,14 +329,29 @@ public class ReplaceInProjectManager { replaceContext.getUsageView().addButtonToLowerPane(replaceSelectedRunnable, FindBundle.message("find.replace.selected.action")); } - private void doReplace(final ReplaceContext replaceContext, Collection usages) { + private boolean doReplace(final ReplaceContext replaceContext, Collection usages) { + boolean success = true; + int replacedCount = 0; for (final Usage usage : usages) { - doReplace(usage, replaceContext.getFindModel(), replaceContext.getExcludedSet()); + try { + doReplace(usage, replaceContext.getFindModel(), replaceContext.getExcludedSet(), false); + replaceContext.getUsageView().removeUsage(usage); + replacedCount++; + } + catch (FindManager.MalformedReplacementStringException e) { + markAsMalformedReplacement(replaceContext, usage); + success = false; + } } - reportNumberReplacedOccurences(myProject, usages.size()); + reportNumberReplacedOccurrences(myProject, replacedCount); + return success; } - public static void reportNumberReplacedOccurences(Project project, int occurrences) { + private static void markAsMalformedReplacement(ReplaceContext replaceContext, Usage usage) { + replaceContext.getUsageView().excludeUsages(new Usage[]{usage}); + } + + public static void reportNumberReplacedOccurrences(Project project, int occurrences) { if (occurrences != 0) { final StatusBar statusBar = WindowManager.getInstance().getStatusBar(project); if (statusBar != null) { @@ -322,7 +360,12 @@ public class ReplaceInProjectManager { } } - public void doReplace(@NotNull final Usage usage, @NotNull final FindModel findModel, @NotNull final Set excludedSet) { + public void doReplace(@NotNull final Usage usage, + @NotNull final FindModel findModel, + @NotNull final Set excludedSet, + final boolean justCheck) + throws FindManager.MalformedReplacementStringException { + final Ref exceptionResult = Ref.create(); ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { @@ -331,41 +374,55 @@ public class ReplaceInProjectManager { } final Document document = ((UsageInfo2UsageAdapter)usage).getDocument(); + if (!document.isWritable()) return; ((UsageInfo2UsageAdapter)usage).processRangeMarkers(new Processor() { @Override public boolean process(Segment segment) { - if (!document.isWritable()) return false; - final int textOffset = segment.getStartOffset(); - if (textOffset < 0 || textOffset >= document.getTextLength()) { - return true; - } final int textEndOffset = segment.getEndOffset(); - if (textEndOffset < 0 || textOffset > document.getTextLength()) { - return true; - } - FindManager findManager = FindManager.getInstance(myProject); - final CharSequence foundString = document.getCharsSequence().subSequence(textOffset, textEndOffset); - FindResult findResult = findManager.findString(document.getCharsSequence(), textOffset, findModel); - if (!findResult.isStringFound()) { - return true; - } - String stringToReplace = null; + final Ref stringToReplace = Ref.create(); try { - stringToReplace = - findManager.getStringToReplace(foundString.toString(), findModel, textOffset, document.getText()); + if (!getStringToReplace(textOffset, textEndOffset, document, findModel, stringToReplace)) return true; + if (!stringToReplace.isNull() && !justCheck) { + document.replaceString(textOffset, textEndOffset, stringToReplace.get()); + } } catch (FindManager.MalformedReplacementStringException e) { - Messages.showErrorDialog(myProject, e.getMessage(), FindBundle.message("find.replace.invalid.replacement.string.title")); - } - if (stringToReplace != null) { - document.replaceString(textOffset, textEndOffset, stringToReplace); + exceptionResult.set(e); + return false; } return true; } }); } }); + if (!exceptionResult.isNull()) { + throw exceptionResult.get(); + } + } + + + private boolean getStringToReplace(int textOffset, + int textEndOffset, + Document document, FindModel findModel, Ref stringToReplace) + throws FindManager.MalformedReplacementStringException { + if (textOffset < 0 || textOffset >= document.getTextLength()) { + return false; + } + if (textEndOffset < 0 || textOffset > document.getTextLength()) { + return false; + } + FindManager findManager = FindManager.getInstance(myProject); + final CharSequence foundString = document.getCharsSequence().subSequence(textOffset, textEndOffset); + FindResult findResult = findManager.findString(document.getCharsSequence(), textOffset, findModel); + if (!findResult.isStringFound()) { + return false; + } + + stringToReplace.set( + FindManager.getInstance(myProject).getStringToReplace(foundString.toString(), findModel, textOffset, document.getText())); + + return true; } private void doReplaceSelected(final ReplaceContext replaceContext) { @@ -401,20 +458,25 @@ public class ReplaceInProjectManager { CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { @Override public void run() { - doReplace(replaceContext, selectedUsages); - for (final Usage selectedUsage : selectedUsages) { - replaceContext.getUsageView().removeUsage(selectedUsage); - } + final boolean success = doReplace(replaceContext, selectedUsages); + final UsageView usageView = replaceContext.getUsageView(); - if (replaceContext.getUsageView().getUsages().isEmpty()) { - replaceContext.getUsageView().close(); - return; - } - replaceContext.getUsageView().getComponent().requestFocus(); + if (closeUsageViewIfEmpty(usageView, success)) return; + usageView.getComponent().requestFocus(); } }, FindBundle.message("find.replace.command"), null); } + private boolean closeUsageViewIfEmpty(UsageView usageView, boolean success) { + if (usageView.getUsages().isEmpty()) { + usageView.close(); + return true; + } else if (!success) { + NOTIFICATION_GROUP.createNotification("One or more malformed replacement strings", MessageType.ERROR).notify(myProject); + } + return false; + } + public boolean isWorkInProgress() { return myIsFindInProgress; } diff --git a/platform/lang-impl/src/com/intellij/ui/ReplacePromptDialog.java b/platform/lang-impl/src/com/intellij/ui/ReplacePromptDialog.java index a1ebefe65387..b7d41dfa8369 100644 --- a/platform/lang-impl/src/com/intellij/ui/ReplacePromptDialog.java +++ b/platform/lang-impl/src/com/intellij/ui/ReplacePromptDialog.java @@ -16,10 +16,12 @@ package com.intellij.ui; +import com.intellij.find.FindManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.find.FindManager; import com.intellij.openapi.ui.Messages; +import org.jetbrains.annotations.Nullable; + import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; @@ -27,10 +29,16 @@ import java.awt.event.ActionEvent; public class ReplacePromptDialog extends DialogWrapper { private final boolean myIsMultiple; + @Nullable private FindManager.MalformedReplacementStringException myException; public ReplacePromptDialog(boolean isMultipleFiles, String title, Project project) { + this(isMultipleFiles, title, project, null); + } + + public ReplacePromptDialog(boolean isMultipleFiles, String title, Project project, @Nullable FindManager.MalformedReplacementStringException exception) { super(project, true); myIsMultiple = isMultipleFiles; + myException = exception; setButtonsAlignment(SwingUtilities.CENTER); setTitle(title); init(); @@ -39,24 +47,35 @@ public class ReplacePromptDialog extends DialogWrapper { protected Action[] createActions(){ DoAction replaceAction = new DoAction(UIBundle.message("replace.prompt.replace.button"), FindManager.PromptResult.OK); replaceAction.putValue(DEFAULT_ACTION,Boolean.TRUE); - if (myIsMultiple){ - return new Action[]{ - replaceAction, - new DoAction(UIBundle.message("replace.prompt.skip.button"), FindManager.PromptResult.SKIP), - new DoAction(UIBundle.message("replace.prompt.all.in.this.file.button"), FindManager.PromptResult.ALL_IN_THIS_FILE), - new DoAction(UIBundle.message("replace.prompt.all.files.action"), FindManager.PromptResult.ALL_FILES), - getCancelAction() - }; - }else{ - return new Action[]{ - replaceAction, - new DoAction(UIBundle.message("replace.prompt.skip.button"), FindManager.PromptResult.SKIP), - new DoAction(UIBundle.message("replace.prompt.all.button"), FindManager.PromptResult.ALL), + if (myException == null) { + if (myIsMultiple){ + return new Action[]{ + replaceAction, + createSkipAction(), + new DoAction(UIBundle.message("replace.prompt.all.in.this.file.button"), FindManager.PromptResult.ALL_IN_THIS_FILE), + new DoAction(UIBundle.message("replace.prompt.all.files.action"), FindManager.PromptResult.ALL_FILES), + getCancelAction() + }; + } else { + return new Action[]{ + replaceAction, + createSkipAction(), + new DoAction(UIBundle.message("replace.prompt.all.button"), FindManager.PromptResult.ALL), + getCancelAction() + }; + } + } else { + return new Action[] { + createSkipAction(), getCancelAction() }; } } + private DoAction createSkipAction() { + return new DoAction(UIBundle.message("replace.prompt.skip.button"), FindManager.PromptResult.SKIP); + } + public JComponent createNorthPanel() { JPanel panel = new JPanel(new BorderLayout()); panel.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10)); @@ -73,7 +92,7 @@ public class ReplacePromptDialog extends DialogWrapper { } protected String getMessage() { - return UIBundle.message("replace.propmt.replace.occurrence.label"); + return myException == null ? UIBundle.message("replace.propmt.replace.occurrence.label") : myException.getMessage(); } public JComponent createCenterPanel() {