From a775610324877bf80520c3c31cb13c8d7e954ea0 Mon Sep 17 00:00:00 2001 From: Bas Leijdekkers Date: Thu, 29 Jun 2017 17:30:19 +0200 Subject: [PATCH] SSR: fix replace tool window issues - ask for all files at once when replacing selected read-only files - don't cache excluded usages, so they can be changed after a replace - allow preview on multiple selected files - select a sensible next usage after replace - more efficient when replacing many usages by using removeUsagesBulk() --- .../replace/ui/ReplaceUsageViewContext.java | 171 +++++++----------- 1 file changed, 65 insertions(+), 106 deletions(-) diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceUsageViewContext.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceUsageViewContext.java index 9728828facf3..2c43042dc847 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceUsageViewContext.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceUsageViewContext.java @@ -19,6 +19,7 @@ import com.intellij.history.LocalHistory; import com.intellij.history.LocalHistoryAction; import com.intellij.openapi.vfs.ReadonlyStatusHandler; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; import com.intellij.structuralsearch.MatchResult; import com.intellij.structuralsearch.SSRBundle; import com.intellij.structuralsearch.plugin.replace.ReplacementInfo; @@ -26,21 +27,18 @@ import com.intellij.structuralsearch.plugin.replace.impl.Replacer; import com.intellij.structuralsearch.plugin.ui.Configuration; import com.intellij.structuralsearch.plugin.ui.SearchContext; import com.intellij.structuralsearch.plugin.ui.UsageViewContext; -import com.intellij.usageView.UsageInfo; import com.intellij.usages.Usage; import com.intellij.usages.UsageInfo2UsageAdapter; import com.intellij.usages.UsageView; +import com.intellij.util.ObjectUtils; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; class ReplaceUsageViewContext extends UsageViewContext { private final HashMap usage2ReplacementInfo = new HashMap<>(); private final Replacer replacer = new Replacer(mySearchContext.getProject(), ((ReplaceConfiguration)myConfiguration).getReplaceOptions()); private UsageView myUsageView; - private Set myExcludedSet; ReplaceUsageViewContext(SearchContext context, Configuration configuration, Runnable searchStarter) { super(configuration, context, searchStarter); @@ -51,128 +49,89 @@ class ReplaceUsageViewContext extends UsageViewContext { myUsageView = usageView; } - public Replacer getReplacer() { - return replacer; - } - public void addReplaceUsage(Usage usage, MatchResult result) { - usage2ReplacementInfo.put(usage, getReplacer().buildReplacement(result)); + usage2ReplacementInfo.put(usage, replacer.buildReplacement(result)); } - private boolean isValid(UsageInfo2UsageAdapter info) { - final UsageInfo usageInfo = info.getUsageInfo(); - return !isExcluded(info) && usageInfo.getElement() != null && usageInfo.getElement().isValid(); + private static boolean isValid(UsageInfo2UsageAdapter info) { + final PsiElement element = info.getUsageInfo().getElement(); + return element != null && element.isValid(); } @Override protected void configureActions() { - final Runnable replaceRunnable = () -> { - LocalHistoryAction labelAction = LocalHistory.getInstance().startAction(SSRBundle.message("structural.replace.title")); - - doReplace(); - myUsageView.close(); - - labelAction.finish(); - }; - - //noinspection HardCodedStringLiteral - myUsageView.addPerformOperationAction(replaceRunnable, "Replace All", null, SSRBundle.message("do.replace.all.button")); - - final Runnable replaceSelected = () -> { - final Set infos = myUsageView.getSelectedUsages(); - if (infos == null || infos.isEmpty()) return; - - LocalHistoryAction labelAction = LocalHistory.getInstance().startAction(SSRBundle.message("structural.replace.title")); - - for (final Usage info : infos) { - final UsageInfo2UsageAdapter usage = (UsageInfo2UsageAdapter)info; - - if (isValid(usage)) { - replaceOne(usage, false); - } - } - - labelAction.finish(); - - if (myUsageView.getUsagesCount() > 0) { - for (Usage usage : myUsageView.getSortedUsages()) { - if (!isExcluded(usage)) { - myUsageView.selectUsages(new Usage[]{usage}); - return; - } - } - } - }; - - myUsageView.addButtonToLowerPane(replaceSelected, SSRBundle.message("replace.selected.button")); + myUsageView.addButtonToLowerPane(() -> replace(myUsageView.getSortedUsages()), SSRBundle.message("do.replace.all.button")); + myUsageView.addButtonToLowerPane(() -> replace(myUsageView.getSelectedUsages()), SSRBundle.message("replace.selected.button")); final Runnable previewReplacement = () -> { - Set selection = myUsageView.getSelectedUsages(); - - if (selection != null && !selection.isEmpty()) { - UsageInfo2UsageAdapter usage = (UsageInfo2UsageAdapter)selection.iterator().next(); - - if (isValid(usage)) { - replaceOne(usage, true); + final Set selection = myUsageView.getSelectedUsages(); + if (selection == null || selection.isEmpty()) { + return; + } + for (Usage usage : selection) { + final UsageInfo2UsageAdapter info = (UsageInfo2UsageAdapter)usage; + if (!isValid(info) || myUsageView.getExcludedUsages().contains(usage)) { + continue; } + final ReplacementInfo replacementInfo = usage2ReplacementInfo.get(usage); + final ReplacementPreviewDialog previewDialog = + new ReplacementPreviewDialog(mySearchContext.getProject(), info.getUsageInfo(), replacementInfo.getReplacement()); + if (!previewDialog.showAndGet()) { + return; + } + replace(Collections.singleton(info)); } }; - myUsageView.addButtonToLowerPane(previewReplacement, SSRBundle.message("preview.replacement.button")); - - super.configureActions(); } - private static void ensureFileWritable(final UsageInfo2UsageAdapter usage) { - final VirtualFile file = usage.getFile(); + private void replace(Collection usages) { + if (usages == null) { + return; + } + final Set excluded = myUsageView.getExcludedUsages(); + usages = usages.stream().filter(u -> !excluded.contains(u)).filter(u -> isValid((UsageInfo2UsageAdapter)u)).collect(Collectors.toList()); - if (file != null && !file.isWritable()) { - ReadonlyStatusHandler.getInstance(usage.getElement().getProject()).ensureFilesWritable(file); + final List files = usages.stream().map(i -> ((UsageInfo2UsageAdapter)i).getFile()).collect(Collectors.toList()); + if (ReadonlyStatusHandler.getInstance(mySearchContext.getProject()).ensureFilesWritable(files).hasReadonlyFiles()) { + return; + } + removeUsagesAndSelectNext(usages, excluded); + final LocalHistoryAction action = LocalHistory.getInstance().startAction(SSRBundle.message("structural.replace.title")); + try { + replacer.replaceAll(usages.stream().map(usage2ReplacementInfo::get).collect(Collectors.toList())); + } finally { + action.finish(); } } - private void replaceOne(UsageInfo2UsageAdapter info, boolean doConfirm) { - ReplacementInfo replacementInfo = usage2ReplacementInfo.get(info); - boolean approved; - - if (doConfirm) { - ReplacementPreviewDialog wrapper = - new ReplacementPreviewDialog(mySearchContext.getProject(), info.getUsageInfo(), replacementInfo.getReplacement()); - - approved = wrapper.showAndGet(); + private void removeUsagesAndSelectNext(Collection usages, Collection excluded) { + final List sortedUsages = myUsageView.getSortedUsages(); + if (sortedUsages.size() == usages.size()) { + myUsageView.close(); } else { - approved = true; - } - - if (approved) { - ensureFileWritable(info); - myUsageView.removeUsage(info); - getReplacer().replace(replacementInfo); - - if (myUsageView.getUsagesCount() == 0) { - myUsageView.close(); + Usage firstValid = null; + Usage select = null; + for (Usage usage : sortedUsages) { + if (usages.contains(usage)) { + select = null; + continue; + } + if (excluded.contains(usage) || !isValid((UsageInfo2UsageAdapter)usage)) { + continue; + } + if (select == null) { + select = usage; + } + if (firstValid == null) { + firstValid = usage; + } } + myUsageView.removeUsagesBulk(usages); + myUsageView.selectUsages(new Usage[]{ + ObjectUtils.coalesce(select, firstValid, myUsageView.getSortedUsages().get(0)) + }); } } - - private void doReplace() { - List infos = myUsageView.getSortedUsages(); - List results = new ArrayList<>(infos.size()); - - for (final Usage info : infos) { - UsageInfo2UsageAdapter usage = (UsageInfo2UsageAdapter)info; - - if (isValid(usage)) { - results.add(usage2ReplacementInfo.get(usage)); - } - } - - getReplacer().replaceAll(results); - } - - private boolean isExcluded(Usage usage) { - if (myExcludedSet == null) myExcludedSet = myUsageView.getExcludedUsages(); - return myExcludedSet.contains(usage); - } }