/* * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.refactoring.extractMethod; import com.intellij.codeInsight.highlighting.HighlightManager; import com.intellij.find.FindManager; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.ThrowableComputable; import com.intellij.psi.PsiElement; import com.intellij.refactoring.RefactoringBundle; import com.intellij.ui.ReplacePromptDialog; import com.intellij.util.Consumer; import org.jetbrains.annotations.NotNull; import java.util.*; /** * @author Dennis.Ushakov */ public class ExtractMethodHelper { public static void processDuplicates(@NotNull final PsiElement callElement, @NotNull final PsiElement generatedMethod, @NotNull final List<PsiElement> scope, @NotNull final SimpleDuplicatesFinder finder, @NotNull final Editor editor, @NotNull final Consumer<Pair<SimpleMatch, PsiElement>> replacer) { finder.setReplacement(callElement); if (ApplicationManager.getApplication().isUnitTestMode()) { replaceDuplicates(callElement, editor, replacer, finder.findDuplicates(scope, generatedMethod)); return; } final Project project = callElement.getProject(); ProgressManager.getInstance().run(new Task.Backgroundable(project, RefactoringBundle.message("searching.for.duplicates"), true) { public void run(@NotNull ProgressIndicator indicator) { if (myProject == null || myProject.isDisposed()) return; final List<SimpleMatch> duplicates = ReadAction.compute(() -> finder.findDuplicates(scope, generatedMethod)); ApplicationManager.getApplication().invokeLater(() -> replaceDuplicates(callElement, editor, replacer, duplicates)); } }); } /** * Finds duplicates of the code fragment specified in the finder in given scopes. * Note that in contrast to {@link #processDuplicates} the search is performed synchronously because normally you need the results in * order to complete the refactoring. If user cancels it, empty list will be returned. * * @param finder finder object to seek for duplicates * @param searchScopes scopes where to look them in * @param generatedMethod new method that should be excluded from the search * @return list of discovered duplicate code fragments or empty list if user interrupted the search * @see #replaceDuplicates(PsiElement, Editor, Consumer, List) */ @NotNull public static List<SimpleMatch> collectDuplicates(@NotNull SimpleDuplicatesFinder finder, @NotNull List<PsiElement> searchScopes, @NotNull PsiElement generatedMethod) { final Project project = generatedMethod.getProject(); try { //noinspection RedundantCast return ProgressManager.getInstance().runProcessWithProgressSynchronously( (ThrowableComputable<List<SimpleMatch>, RuntimeException>)() -> { ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true); return ReadAction.compute(() -> finder.findDuplicates(searchScopes, generatedMethod)); }, RefactoringBundle.message("searching.for.duplicates"), true, project); } catch (ProcessCanceledException e) { return Collections.emptyList(); } } /** * Notifies user about found duplicates and then highlights each of them in the editor and asks user how to proceed. * * @param callElement generated expression or statement that contains invocation of the new method * @param editor instance of editor where refactoring is performed * @param replacer strategy of substituting each duplicate occurence with the replacement fragment * @param duplicates discovered duplicates of extracted code fragment * @see #collectDuplicates(SimpleDuplicatesFinder, List, PsiElement) */ public static void replaceDuplicates(@NotNull PsiElement callElement, @NotNull Editor editor, @NotNull Consumer<Pair<SimpleMatch, PsiElement>> replacer, @NotNull List<SimpleMatch> duplicates) { if (!duplicates.isEmpty()) { final String message = RefactoringBundle .message("0.has.detected.1.code.fragments.in.this.file.that.can.be.replaced.with.a.call.to.extracted.method", ApplicationNamesInfo.getInstance().getProductName(), duplicates.size()); final boolean isUnittest = ApplicationManager.getApplication().isUnitTestMode(); final Project project = callElement.getProject(); final int exitCode = !isUnittest ? Messages.showYesNoDialog(project, message, RefactoringBundle.message("refactoring.extract.method.dialog.title"), Messages.getInformationIcon()) : Messages.YES; if (exitCode == Messages.YES) { boolean replaceAll = false; final Map<SimpleMatch, RangeHighlighter> highlighterMap = new HashMap<>(); for (SimpleMatch match : duplicates) { if (!match.getStartElement().isValid() || !match.getEndElement().isValid()) continue; final Pair<SimpleMatch, PsiElement> replacement = Pair.create(match, callElement); if (!replaceAll) { highlightInEditor(project, match, editor, highlighterMap); int promptResult = FindManager.PromptResult.ALL; //noinspection ConstantConditions if (!isUnittest) { ReplacePromptDialog promptDialog = new ReplacePromptDialog(false, RefactoringBundle.message("replace.fragment"), project); promptDialog.show(); promptResult = promptDialog.getExitCode(); } if (promptResult == FindManager.PromptResult.SKIP) { final HighlightManager highlightManager = HighlightManager.getInstance(project); final RangeHighlighter highlighter = highlighterMap.get(match); if (highlighter != null) highlightManager.removeSegmentHighlighter(editor, highlighter); continue; } if (promptResult == FindManager.PromptResult.CANCEL) break; if (promptResult == FindManager.PromptResult.OK) { replaceDuplicate(project, replacer, replacement); } else if (promptResult == FindManager.PromptResult.ALL) { replaceDuplicate(project, replacer, replacement); replaceAll = true; } } else { replaceDuplicate(project, replacer, replacement); } } } } } private static void replaceDuplicate(final Project project, final Consumer<Pair<SimpleMatch, PsiElement>> replacer, final Pair<SimpleMatch, PsiElement> replacement) { CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> replacer.consume(replacement)), "Replace duplicate", null); } private static void highlightInEditor(@NotNull final Project project, @NotNull final SimpleMatch match, @NotNull final Editor editor, Map<SimpleMatch, RangeHighlighter> highlighterMap) { final List<RangeHighlighter> highlighters = new ArrayList<>(); final HighlightManager highlightManager = HighlightManager.getInstance(project); final EditorColorsManager colorsManager = EditorColorsManager.getInstance(); final TextAttributes attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES); final int startOffset = match.getStartElement().getTextRange().getStartOffset(); final int endOffset = match.getEndElement().getTextRange().getEndOffset(); highlightManager.addRangeHighlight(editor, startOffset, endOffset, attributes, true, highlighters); highlighterMap.put(match, highlighters.get(0)); final LogicalPosition logicalPosition = editor.offsetToLogicalPosition(startOffset); editor.getScrollingModel().scrollTo(logicalPosition, ScrollType.MAKE_VISIBLE); } }