com.intellij.find.FindUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.find.FindUtil.java

Source

/*
 * Copyright 2000-2014 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.find;

import com.intellij.codeInsight.hint.HintManager;
import com.intellij.codeInsight.hint.HintManagerImpl;
import com.intellij.codeInsight.hint.HintUtil;
import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter;
import com.intellij.find.impl.FindInProjectUtil;
import com.intellij.find.replaceInProject.ReplaceInProjectManager;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
import com.intellij.openapi.editor.actions.EditorActionUtil;
import com.intellij.openapi.editor.actions.IncrementalFindAction;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.event.CaretAdapter;
import com.intellij.openapi.editor.event.CaretEvent;
import com.intellij.openapi.editor.event.CaretListener;
import com.intellij.openapi.editor.ex.RangeHighlighterEx;
import com.intellij.openapi.editor.markup.HighlighterLayer;
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory;
import com.intellij.openapi.keymap.KeymapUtil;
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.JDOMUtil;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.ui.LightweightHint;
import com.intellij.usageView.UsageInfo;
import com.intellij.usages.*;
import com.intellij.usages.impl.UsageViewImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.util.*;

public class FindUtil {
    private static final Key<Direction> KEY = Key.create("FindUtil.KEY");

    private FindUtil() {
    }

    @Nullable
    static VirtualFile getVirtualFile(@NotNull Editor myEditor) {
        Project project = myEditor.getProject();
        PsiFile file = project != null ? PsiDocumentManager.getInstance(project).getPsiFile(myEditor.getDocument())
                : null;
        return file != null ? file.getVirtualFile() : null;
    }

    public static void initStringToFindWithSelection(FindModel findModel, Editor editor) {
        if (editor != null) {
            String s = editor.getSelectionModel().getSelectedText();
            FindModel.initStringToFindNoMultiline(findModel, s);
        }
    }

    private static boolean isMultilineSelection(Editor editor) {
        SelectionModel selectionModel = editor != null ? editor.getSelectionModel() : null;
        if (selectionModel != null) {
            String selectedText = selectionModel.getSelectedText();
            if (selectedText != null) {
                if (selectedText.indexOf("\n") != -1) {
                    return true;
                }
            }
        }
        return false;
    }

    private static boolean isWholeLineSelection(Editor editor) {
        SelectionModel selectionModel = editor != null ? editor.getSelectionModel() : null;
        if (selectionModel != null) {
            String selectedText = selectionModel.getSelectedText();
            final Document document = editor.getDocument();
            final int line = document.getLineNumber(selectionModel.getSelectionStart());
            final String lineText = document
                    .getText(new TextRange(document.getLineStartOffset(line), document.getLineEndOffset(line)));
            if (lineText.trim().equals(selectedText)) {
                return true;
            }
        }
        return false;
    }

    public static void configureFindModel(boolean replace, @Nullable Editor editor, FindModel model,
            boolean firstSearch) {
        boolean isGlobal = true;
        String stringToFind = null;
        final SelectionModel selectionModel = editor != null ? editor.getSelectionModel() : null;
        String selectedText = selectionModel != null ? selectionModel.getSelectedText() : null;
        if (!StringUtil.isEmpty(selectedText)) {
            if (replace && (isMultilineSelection(editor) || isWholeLineSelection(editor))) {
                isGlobal = false;
                stringToFind = model.getStringToFind();
            } else if (isMultilineSelection(editor)) {
                model.setMultiline(true);
            }
            if (stringToFind == null) {
                stringToFind = selectedText;
            }
        } else {
            if (firstSearch) {
                stringToFind = "";
            } else {
                stringToFind = model.getStringToFind();
            }
        }
        model.setReplaceState(replace);
        model.setStringToFind(stringToFind);
        model.setGlobal(isGlobal);
        model.setPromptOnReplace(false);
    }

    private enum Direction {
        UP, DOWN
    }

    public static void findWordAtCaret(Project project, Editor editor) {
        int caretOffset = editor.getCaretModel().getOffset();
        Document document = editor.getDocument();
        CharSequence text = document.getCharsSequence();
        int start = 0;
        int end = document.getTextLength();
        if (!editor.getSelectionModel().hasSelection()) {
            for (int i = caretOffset - 1; i >= 0; i--) {
                char c = text.charAt(i);
                if (!Character.isJavaIdentifierPart(c)) {
                    start = i + 1;
                    break;
                }
            }
            for (int i = caretOffset; i < document.getTextLength(); i++) {
                char c = text.charAt(i);
                if (!Character.isJavaIdentifierPart(c)) {
                    end = i;
                    break;
                }
            }
        } else {
            start = editor.getSelectionModel().getSelectionStart();
            end = editor.getSelectionModel().getSelectionEnd();
        }
        if (start >= end) {
            return;
        }
        FindManager findManager = FindManager.getInstance(project);
        String s = text.subSequence(start, end).toString();
        FindSettings.getInstance().addStringToFind(s);
        findManager.getFindInFileModel().setStringToFind(s);
        findManager.setFindWasPerformed();
        findManager.clearFindingNextUsageInFile();
        FindModel model = new FindModel();
        model.setStringToFind(s);
        model.setCaseSensitive(true);
        model.setWholeWordsOnly(!editor.getSelectionModel().hasSelection());

        final JComponent header = editor.getHeaderComponent();
        if (header instanceof EditorSearchComponent) {
            final EditorSearchComponent searchComponent = (EditorSearchComponent) header;
            searchComponent.setTextInField(model.getStringToFind());
        }

        findManager.setFindNextModel(model);
        doSearch(project, editor, caretOffset, true, model, true);
    }

    public static void find(@NotNull final Project project, @NotNull final Editor editor) {
        ApplicationManager.getApplication().assertIsDispatchThread();
        final FindManager findManager = FindManager.getInstance(project);
        String s = editor.getSelectionModel().getSelectedText();

        final FindModel model = (FindModel) findManager.getFindInFileModel().clone();
        if (StringUtil.isEmpty(s)) {
            model.setGlobal(true);
        } else {
            if (s.indexOf('\n') >= 0) {
                model.setGlobal(false);
            } else {
                model.setStringToFind(s);
                model.setGlobal(true);
            }
        }

        model.setReplaceState(false);
        model.setFindAllEnabled(PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()) != null);

        findManager.showFindDialog(model, new Runnable() {
            @Override
            public void run() {
                if (model.isFindAll()) {
                    findManager.setFindNextModel(model);
                    findAllAndShow(project, editor, model);
                    return;
                }

                if (!model.isGlobal() && editor.getSelectionModel().hasSelection()) {
                    int offset = model.isForward() ? editor.getSelectionModel().getSelectionStart()
                            : editor.getSelectionModel().getSelectionEnd();
                    ScrollType scrollType = model.isForward() ? ScrollType.CENTER_DOWN : ScrollType.CENTER_UP;
                    moveCaretAndDontChangeSelection(editor, offset, scrollType);
                }

                int offset;
                if (model.isGlobal()) {
                    if (model.isFromCursor()) {
                        offset = editor.getCaretModel().getOffset();
                    } else {
                        offset = model.isForward() ? 0 : editor.getDocument().getTextLength();
                    }
                } else {
                    // in selection

                    if (!editor.getSelectionModel().hasSelection()) {
                        // TODO[anton] actually, this should never happen - Find dialog should not allow such combination
                        findManager.setFindNextModel(null);
                        return;
                    }

                    offset = model.isForward() ? editor.getSelectionModel().getSelectionStart()
                            : editor.getSelectionModel().getSelectionEnd();
                }

                findManager.setFindNextModel(null);
                findManager.getFindInFileModel().copyFrom(model);
                doSearch(project, editor, offset, true, model, true);
            }
        });
    }

    @Nullable
    public static List<Usage> findAll(@NotNull Project project, @NotNull Editor editor,
            @NotNull FindModel findModel) {
        final Document document = editor.getDocument();
        final PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document);
        if (psiFile == null)
            return null;

        CharSequence text = document.getCharsSequence();
        int textLength = document.getTextLength();
        final List<Usage> usages = new ArrayList<Usage>();
        FindManager findManager = FindManager.getInstance(project);
        findModel.setForward(true); // when find all there is no diff in direction

        int offset = 0;
        VirtualFile virtualFile = getVirtualFile(editor);

        while (offset < textLength) {
            FindResult result = findManager.findString(text, offset, findModel, virtualFile);
            if (!result.isStringFound())
                break;

            usages.add(new UsageInfo2UsageAdapter(
                    new UsageInfo(psiFile, result.getStartOffset(), result.getEndOffset())));

            final int prevOffset = offset;
            offset = result.getEndOffset();

            if (prevOffset == offset) {
                // for regular expr the size of the match could be zero -> could be infinite loop in finding usages!
                ++offset;
            }
        }
        return usages;
    }

    public static void findAllAndShow(@NotNull Project project, @NotNull Editor editor,
            @NotNull FindModel findModel) {
        List<Usage> usages = findAll(project, editor, findModel);
        if (usages == null)
            return;
        final UsageTarget[] usageTargets = { new FindInProjectUtil.StringUsageTarget(project, findModel) };
        final UsageViewPresentation usageViewPresentation = FindInProjectUtil.setupViewPresentation(false,
                findModel);
        UsageViewManager.getInstance(project).showUsages(usageTargets, usages.toArray(new Usage[usages.size()]),
                usageViewPresentation);
    }

    public static void searchBack(Project project, FileEditor fileEditor, @Nullable DataContext dataContext) {
        if (!(fileEditor instanceof TextEditor))
            return;
        TextEditor textEditor = (TextEditor) fileEditor;
        Editor editor = textEditor.getEditor();

        searchBack(project, editor, dataContext);
    }

    public static void searchBack(final Project project, final Editor editor, @Nullable DataContext context) {
        FindManager findManager = FindManager.getInstance(project);
        if (!findManager.findWasPerformed() && !findManager.selectNextOccurrenceWasPerformed()) {
            new IncrementalFindAction().getHandler().execute(editor, context);
            return;
        }

        FindModel model = findManager.getFindNextModel(editor);
        if (model == null) {
            model = findManager.getFindInFileModel();
        }
        model = (FindModel) model.clone();
        model.setForward(!model.isForward());
        if (!model.isGlobal() && !editor.getSelectionModel().hasSelection()) {
            model.setGlobal(true);
        }

        int offset;
        if (Direction.UP.equals(editor.getUserData(KEY)) && !model.isForward()) {
            offset = editor.getDocument().getTextLength();
        } else if (Direction.DOWN.equals(editor.getUserData(KEY)) && model.isForward()) {
            offset = 0;
        } else {
            editor.putUserData(KEY, null);
            offset = editor.getCaretModel().getOffset();
            if (!model.isForward() && offset > 0) {
                offset--;
            }
        }
        searchAgain(project, editor, offset, model);
    }

    public static boolean searchAgain(Project project, FileEditor fileEditor, @Nullable DataContext context) {
        if (!(fileEditor instanceof TextEditor))
            return false;
        TextEditor textEditor = (TextEditor) fileEditor;
        Editor editor = textEditor.getEditor();

        return searchAgain(project, editor, context);
    }

    public static boolean searchAgain(final Project project, final Editor editor, @Nullable DataContext context) {
        FindManager findManager = FindManager.getInstance(project);
        if (!findManager.findWasPerformed() && !findManager.selectNextOccurrenceWasPerformed()) {
            new IncrementalFindAction().getHandler().execute(editor, context);
            return false;
        }

        FindModel model = findManager.getFindNextModel(editor);
        if (model == null) {
            model = findManager.getFindInFileModel();
        }
        model = (FindModel) model.clone();

        int offset;
        if (Direction.DOWN.equals(editor.getUserData(KEY)) && model.isForward()) {
            offset = 0;
        } else if (Direction.UP.equals(editor.getUserData(KEY)) && !model.isForward()) {
            offset = editor.getDocument().getTextLength();
        } else {
            editor.putUserData(KEY, null);
            offset = model.isGlobal() && model.isForward() ? editor.getSelectionModel().getSelectionEnd()
                    : editor.getCaretModel().getOffset();
            if (!model.isForward() && offset > 0) {
                offset--;
            }
        }
        return searchAgain(project, editor, offset, model);
    }

    private static boolean searchAgain(Project project, Editor editor, int offset, FindModel model) {
        if (!model.isGlobal() && !editor.getSelectionModel().hasSelection()) {
            model.setGlobal(true);
        }
        model.setFromCursor(false);
        if (model.isReplaceState()) {
            model.setPromptOnReplace(true);
            model.setReplaceAll(false);
            replace(project, editor, offset, model);
            return true;
        } else {
            doSearch(project, editor, offset, true, model, true);
            return false;
        }
    }

    public static void replace(final Project project, final Editor editor) {
        final FindManager findManager = FindManager.getInstance(project);
        final FindModel model = (FindModel) findManager.getFindInFileModel().clone();
        final String s = editor.getSelectionModel().getSelectedText();
        if (!StringUtil.isEmpty(s)) {
            if (s.indexOf('\n') >= 0) {
                model.setGlobal(false);
            } else {
                model.setStringToFind(s);
                model.setGlobal(true);
            }
        } else {
            model.setGlobal(true);
        }
        model.setReplaceState(true);

        findManager.showFindDialog(model, new Runnable() {
            @Override
            public void run() {
                if (!model.isGlobal() && editor.getSelectionModel().hasSelection()) {
                    int offset = model.isForward() ? editor.getSelectionModel().getSelectionStart()
                            : editor.getSelectionModel().getSelectionEnd();
                    ScrollType scrollType = model.isForward() ? ScrollType.CENTER_DOWN : ScrollType.CENTER_UP;
                    moveCaretAndDontChangeSelection(editor, offset, scrollType);
                }
                int offset;
                if (model.isGlobal()) {
                    if (model.isFromCursor()) {
                        offset = editor.getCaretModel().getOffset();
                        if (!model.isForward()) {
                            offset++;
                        }
                    } else {
                        offset = model.isForward() ? 0 : editor.getDocument().getTextLength();
                    }
                } else {
                    // in selection

                    if (!editor.getSelectionModel().hasSelection()) {
                        // TODO[anton] actually, this should never happen - Find dialog should not allow such combination
                        findManager.setFindNextModel(null);
                        return;
                    }

                    offset = model.isForward() ? editor.getSelectionModel().getSelectionStart()
                            : editor.getSelectionModel().getSelectionEnd();
                }

                if (s != null && editor.getSelectionModel().hasSelection() && s.equals(model.getStringToFind())) {
                    if (model.isFromCursor() && model.isForward()) {
                        offset = Math.min(editor.getSelectionModel().getSelectionStart(), offset);
                    } else if (model.isFromCursor() && !model.isForward()) {
                        offset = Math.max(editor.getSelectionModel().getSelectionEnd(), offset);
                    }
                }
                findManager.setFindNextModel(null);
                findManager.getFindInFileModel().copyFrom(model);
                replace(project, editor, offset, model);
            }
        });
    }

    public static boolean replace(Project project, Editor editor, int offset, FindModel model) {
        return replace(project, editor, offset, model, new ReplaceDelegate() {
            @Override
            public boolean shouldReplace(TextRange range, String replace) {
                return true;
            }
        });
    }

    public static boolean replace(Project project, Editor editor, int offset, FindModel model,
            ReplaceDelegate delegate) {
        Document document = editor.getDocument();

        if (!FileDocumentManager.getInstance().requestWriting(document, project)) {
            return false;
        }

        document.startGuardedBlockChecking();
        boolean toPrompt = model.isPromptOnReplace();

        try {
            doReplace(project, editor, model, document, offset, toPrompt, delegate);
        } catch (ReadOnlyFragmentModificationException e) {
            EditorActionManager.getInstance().getReadonlyFragmentModificationHandler(document).handle(e);
        } finally {
            document.stopGuardedBlockChecking();
        }

        return true;
    }

    private static void doReplace(Project project, final Editor editor, final FindModel aModel,
            final Document document, int caretOffset, boolean toPrompt, ReplaceDelegate delegate) {
        FindManager findManager = FindManager.getInstance(project);
        final FindModel model = aModel.clone();
        int occurrences = 0;

        List<Pair<TextRange, String>> rangesToChange = new ArrayList<Pair<TextRange, String>>();

        boolean replaced = false;
        boolean reallyReplaced = false;

        int offset = caretOffset;
        while (offset >= 0 && offset < editor.getDocument().getTextLength()) {
            caretOffset = offset;
            FindResult result = doSearch(project, editor, offset, !replaced, model, toPrompt);
            if (result == null) {
                break;
            }
            int startResultOffset = result.getStartOffset();
            model.setFromCursor(true);

            int startOffset = result.getStartOffset();
            int endOffset = result.getEndOffset();
            String foundString = document.getCharsSequence().subSequence(startOffset, endOffset).toString();
            String toReplace;
            try {
                CharSequence textToMatch = document.getCharsSequence().subSequence(offset,
                        document.getTextLength());
                toReplace = findManager.getStringToReplace(foundString, model, startOffset - offset, textToMatch);
            } catch (FindManager.MalformedReplacementStringException e) {
                if (!ApplicationManager.getApplication().isUnitTestMode()) {
                    Messages.showErrorDialog(project, e.getMessage(),
                            FindBundle.message("find.replace.invalid.replacement.string.title"));
                }
                break;
            }

            if (toPrompt) {
                int promptResult = findManager.showPromptDialog(model,
                        FindBundle.message("find.replace.dialog.title"));
                if (promptResult == FindManager.PromptResult.SKIP) {
                    offset = model.isForward() ? result.getEndOffset() : startResultOffset;
                    continue;
                }
                if (promptResult == FindManager.PromptResult.CANCEL) {
                    break;
                }
                if (promptResult == FindManager.PromptResult.ALL) {
                    toPrompt = false;
                }
            }
            int newOffset;
            if (delegate == null || delegate.shouldReplace(result, toReplace)) {
                if (toPrompt) {
                    //[SCR 7258]
                    if (!reallyReplaced) {
                        editor.getCaretModel().moveToOffset(0);
                        reallyReplaced = true;
                    }
                }
                TextRange textRange = doReplace(project, document, model, result, toReplace, toPrompt,
                        rangesToChange);
                replaced = true;
                newOffset = model.isForward() ? textRange.getEndOffset() : textRange.getStartOffset();
                occurrences++;
            } else {
                newOffset = model.isForward() ? result.getEndOffset() : result.getStartOffset();
            }

            if (newOffset == offset) {
                newOffset += model.isForward() ? 1 : -1;
            }
            offset = newOffset;
        }

        if (replaced) {
            if (!toPrompt) {
                CharSequence text = document.getCharsSequence();
                final StringBuilder newText = new StringBuilder(document.getTextLength());
                Collections.sort(rangesToChange, new Comparator<Pair<TextRange, String>>() {
                    @Override
                    public int compare(Pair<TextRange, String> o1, Pair<TextRange, String> o2) {
                        return o1.getFirst().getStartOffset() - o2.getFirst().getStartOffset();
                    }
                });
                int offsetBefore = 0;
                for (Pair<TextRange, String> pair : rangesToChange) {
                    TextRange range = pair.getFirst();
                    String replace = pair.getSecond();
                    newText.append(text, offsetBefore, range.getStartOffset()); //before change
                    if (delegate == null || delegate.shouldReplace(range, replace)) {
                        newText.append(replace);
                    } else {
                        newText.append(text.subSequence(range.getStartOffset(), range.getEndOffset()));
                    }
                    offsetBefore = range.getEndOffset();
                    if (offsetBefore < caretOffset) {
                        caretOffset += replace.length() - range.getLength();
                    }
                }
                newText.append(text, offsetBefore, text.length()); //tail
                if (caretOffset > newText.length()) {
                    caretOffset = newText.length();
                }
                final int finalCaretOffset = caretOffset;
                CommandProcessor.getInstance().executeCommand(project, new Runnable() {
                    @Override
                    public void run() {
                        ApplicationManager.getApplication().runWriteAction(new Runnable() {
                            @Override
                            public void run() {
                                document.setText(newText);
                                editor.getCaretModel().moveToOffset(finalCaretOffset);
                                if (model.isGlobal()) {
                                    editor.getSelectionModel().removeSelection();
                                }
                            }
                        });
                    }
                }, null, document);
            } else {
                if (reallyReplaced) {
                    if (caretOffset > document.getTextLength()) {
                        caretOffset = document.getTextLength();
                    }
                    editor.getCaretModel().moveToOffset(caretOffset);
                }
            }
        }

        ReplaceInProjectManager.reportNumberReplacedOccurrences(project, occurrences);
    }

    private static boolean selectionMayContainRange(SelectionModel selection, TextRange range) {
        int[] starts = selection.getBlockSelectionStarts();
        int[] ends = selection.getBlockSelectionEnds();
        if (starts.length == 0) {
            return false;
        }
        return new TextRange(starts[0], ends[starts.length - 1]).contains(range);
    }

    private static boolean selectionStrictlyContainsRange(SelectionModel selection, TextRange range) {
        int[] starts = selection.getBlockSelectionStarts();
        int[] ends = selection.getBlockSelectionEnds();
        for (int i = 0; i < starts.length; ++i) {
            if (new TextRange(starts[i], ends[i]).contains(range)) { //todo
                return true;
            }
        }
        return false;
    }

    @Nullable
    private static FindResult doSearch(@NotNull Project project, @NotNull final Editor editor, int offset,
            boolean toWarn, @NotNull FindModel model, boolean adjustEditor) {
        FindManager findManager = FindManager.getInstance(project);
        Document document = editor.getDocument();

        final FindResult result = findManager.findString(document.getCharsSequence(), offset, model,
                getVirtualFile(editor));

        boolean isFound = result.isStringFound();
        final SelectionModel selection = editor.getSelectionModel();
        if (isFound && !model.isGlobal()) {
            if (!selectionMayContainRange(selection, result)) {
                isFound = false;
            } else if (!selectionStrictlyContainsRange(selection, result)) {
                final int[] starts = selection.getBlockSelectionStarts();
                for (int newOffset : starts) {
                    if (newOffset > result.getStartOffset()) {
                        return doSearch(project, editor, newOffset, toWarn, model, adjustEditor);
                    }
                }
            }
        }
        if (!isFound) {
            if (toWarn) {
                processNotFound(editor, model.getStringToFind(), model, project);
            }
            return null;
        }

        if (adjustEditor) {
            final CaretModel caretModel = editor.getCaretModel();
            final ScrollingModel scrollingModel = editor.getScrollingModel();
            int oldCaretOffset = caretModel.getOffset();
            boolean forward = oldCaretOffset < result.getStartOffset();
            final ScrollType scrollType = forward ? ScrollType.CENTER_DOWN : ScrollType.CENTER_UP;

            if (model.isGlobal()) {
                int targetCaretPosition = result.getEndOffset();
                if (selection.getSelectionEnd() - selection.getSelectionStart() == result.getLength()) {
                    // keeping caret's position relative to selection
                    // use case: FindNext is used after SelectNextOccurrence action
                    targetCaretPosition = caretModel.getOffset() - selection.getSelectionStart()
                            + result.getStartOffset();
                }
                if (caretModel.getCaretAt(editor.offsetToVisualPosition(targetCaretPosition)) != null) {
                    // if there's a different caret at target position, don't move current caret/selection
                    // use case: FindNext is used after SelectNextOccurrence action
                    return result;
                }
                caretModel.moveToOffset(targetCaretPosition);
                selection.removeSelection();
                scrollingModel.scrollToCaret(scrollType);
                scrollingModel.runActionOnScrollingFinished(new Runnable() {
                    @Override
                    public void run() {
                        scrollingModel.scrollTo(editor.offsetToLogicalPosition(result.getStartOffset()),
                                scrollType);
                        scrollingModel.scrollTo(editor.offsetToLogicalPosition(result.getEndOffset()), scrollType);
                    }
                });
            } else {
                moveCaretAndDontChangeSelection(editor, result.getStartOffset(), scrollType);
                moveCaretAndDontChangeSelection(editor, result.getEndOffset(), scrollType);
            }
            IdeDocumentHistory.getInstance(project).includeCurrentCommandAsNavigation();

            EditorColorsManager manager = EditorColorsManager.getInstance();
            TextAttributes selectionAttributes = manager.getGlobalScheme()
                    .getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);

            if (!model.isGlobal()) {
                final RangeHighlighterEx segmentHighlighter = (RangeHighlighterEx) editor.getMarkupModel()
                        .addRangeHighlighter(result.getStartOffset(), result.getEndOffset(),
                                HighlighterLayer.SELECTION + 1, selectionAttributes,
                                HighlighterTargetArea.EXACT_RANGE);
                MyListener listener = new MyListener(editor, segmentHighlighter);
                caretModel.addCaretListener(listener);
            } else {
                selection.setSelection(result.getStartOffset(), result.getEndOffset());
            }
        }

        return result;
    }

    private static class MyListener extends CaretAdapter {
        private final Editor myEditor;
        private final RangeHighlighter mySegmentHighlighter;

        private MyListener(@NotNull Editor editor, @NotNull RangeHighlighter segmentHighlighter) {
            myEditor = editor;
            mySegmentHighlighter = segmentHighlighter;
        }

        @Override
        public void caretPositionChanged(CaretEvent e) {
            removeAll();
        }

        private void removeAll() {
            myEditor.getCaretModel().removeCaretListener(this);
            mySegmentHighlighter.dispose();
        }
    }

    public static void processNotFound(final Editor editor, String stringToFind, FindModel model, Project project) {
        String message = FindBundle.message("find.search.string.not.found.message", stringToFind);

        short position = HintManager.UNDER;
        if (model.isGlobal()) {
            final FindModel newModel = (FindModel) model.clone();
            FindManager findManager = FindManager.getInstance(project);
            Document document = editor.getDocument();
            FindResult result = findManager.findString(document.getCharsSequence(),
                    newModel.isForward() ? 0 : document.getTextLength(), model, getVirtualFile(editor));
            if (!result.isStringFound()) {
                result = null;
            }

            FindModel modelForNextSearch = findManager.getFindNextModel(editor);
            if (modelForNextSearch == null) {
                modelForNextSearch = findManager.getFindInFileModel();
            }

            if (result != null) {
                if (newModel.isForward()) {
                    AnAction action = ActionManager.getInstance()
                            .getAction(modelForNextSearch.isForward() ? IdeActions.ACTION_FIND_NEXT
                                    : IdeActions.ACTION_FIND_PREVIOUS);
                    String shortcutsText = KeymapUtil.getFirstKeyboardShortcutText(action);
                    if (shortcutsText.length() > 0) {
                        message = FindBundle.message("find.search.again.from.top.hotkey.message", message,
                                shortcutsText);
                    } else {
                        message = FindBundle.message("find.search.again.from.top.action.message", message);
                    }
                    editor.putUserData(KEY, Direction.DOWN);
                } else {
                    AnAction action = ActionManager.getInstance()
                            .getAction(modelForNextSearch.isForward() ? IdeActions.ACTION_FIND_PREVIOUS
                                    : IdeActions.ACTION_FIND_NEXT);
                    String shortcutsText = KeymapUtil.getFirstKeyboardShortcutText(action);
                    if (shortcutsText.length() > 0) {
                        message = FindBundle.message("find.search.again.from.bottom.hotkey.message", message,
                                shortcutsText);
                    } else {
                        message = FindBundle.message("find.search.again.from.bottom.action.message", message);
                    }
                    editor.putUserData(KEY, Direction.UP);
                    position = HintManager.ABOVE;
                }
            }
            CaretListener listener = new CaretAdapter() {
                @Override
                public void caretPositionChanged(CaretEvent e) {
                    editor.putUserData(KEY, null);
                    editor.getCaretModel().removeCaretListener(this);
                }
            };
            editor.getCaretModel().addCaretListener(listener);
        }
        JComponent component = HintUtil.createInformationLabel(JDOMUtil.escapeText(message, false, false));
        final LightweightHint hint = new LightweightHint(component);
        HintManagerImpl.getInstanceImpl().showEditorHint(hint, editor, position,
                HintManager.HIDE_BY_ANY_KEY | HintManager.HIDE_BY_TEXT_CHANGE | HintManager.HIDE_BY_SCROLLING, 0,
                false);
    }

    public static TextRange doReplace(final Project project, final Document document, final FindModel model,
            FindResult result, @NotNull String stringToReplace, boolean reallyReplace,
            List<Pair<TextRange, String>> rangesToChange) {
        final int startOffset = result.getStartOffset();
        final int endOffset = result.getEndOffset();

        int newOffset;
        if (reallyReplace) {
            newOffset = doReplace(project, document, startOffset, endOffset, stringToReplace);
        } else {
            final String converted = StringUtil.convertLineSeparators(stringToReplace);
            TextRange textRange = new TextRange(startOffset, endOffset);
            rangesToChange.add(Pair.create(textRange, converted));

            newOffset = endOffset;
        }

        int start = startOffset;
        int end = newOffset;
        if (model.isRegularExpressions()) {
            String toFind = model.getStringToFind();
            if (model.isForward()) {
                if (StringUtil.endsWithChar(toFind, '$')) {
                    int i = 0;
                    int length = toFind.length();
                    while (i + 2 <= length && toFind.charAt(length - i - 2) == '\\')
                        i++;
                    if (i % 2 == 0)
                        end++; //This $ is a special symbol in regexp syntax
                } else if (StringUtil.startsWithChar(toFind, '^')) {
                    while (end < document.getTextLength() && document.getCharsSequence().charAt(end) != '\n')
                        end++;
                }
            } else {
                if (StringUtil.startsWithChar(toFind, '^')) {
                    start--;
                } else if (StringUtil.endsWithChar(toFind, '$')) {
                    while (start >= 0 && document.getCharsSequence().charAt(start) != '\n')
                        start--;
                }
            }
        }
        return new TextRange(start, end);
    }

    public static int doReplace(Project project, final Document document, final int startOffset,
            final int endOffset, final String stringToReplace) {
        final String converted = StringUtil.convertLineSeparators(stringToReplace);
        CommandProcessor.getInstance().executeCommand(project, new Runnable() {
            @Override
            public void run() {
                ApplicationManager.getApplication().runWriteAction(new Runnable() {
                    @Override
                    public void run() {
                        //[ven] I doubt converting is a good solution to SCR 21224
                        document.replaceString(startOffset, endOffset, converted);
                    }
                });
            }
        }, null, null);
        return startOffset + converted.length();
    }

    private static void moveCaretAndDontChangeSelection(final Editor editor, int offset, ScrollType scrollType) {
        LogicalPosition pos = editor.offsetToLogicalPosition(offset);
        editor.getCaretModel().moveToLogicalPosition(pos);
        editor.getScrollingModel().scrollToCaret(scrollType);
    }

    public interface ReplaceDelegate {
        boolean shouldReplace(TextRange range, String replace);
    }

    @Nullable
    public static UsageView showInUsageView(PsiElement sourceElement, @NotNull final PsiElement[] targets,
            @NotNull String title, @NotNull Project project) {
        if (targets.length == 0)
            return null;
        final UsageViewPresentation presentation = new UsageViewPresentation();
        presentation.setCodeUsagesString(title);
        presentation.setTabName(title);
        presentation.setTabText(title);
        final UsageTarget[] usageTargets = sourceElement == null ? UsageTarget.EMPTY_ARRAY
                : new UsageTarget[] { new PsiElement2UsageTargetAdapter(sourceElement) };

        final PsiElement[] primary = sourceElement == null ? PsiElement.EMPTY_ARRAY
                : new PsiElement[] { sourceElement };
        final Usage[] usages = { UsageInfoToUsageConverter.convert(primary, new UsageInfo(targets[0])) };
        final UsageView view = UsageViewManager.getInstance(project).showUsages(usageTargets, usages, presentation);
        ProgressManager.getInstance().run(new Task.Backgroundable(project, "Updating Usage View ...") {
            @Override
            public void run(@NotNull ProgressIndicator indicator) {
                for (int i = 1; i < targets.length; i++) {
                    if (((UsageViewImpl) view).isDisposed())
                        break;
                    final PsiElement target = targets[i];
                    ApplicationManager.getApplication().runReadAction(new Runnable() {
                        @Override
                        public void run() {
                            final Usage usage = UsageInfoToUsageConverter.convert(primary, new UsageInfo(target));
                            view.appendUsage(usage);
                        }
                    });
                }
            }
        });
        return view;
    }

    /**
     * Creates a selection in editor per each search result. Existing carets and selections in editor are discarded.
     *
     * @param caretShiftFromSelectionStart if non-negative, defines caret position relative to selection start, for each created selection.
     *                                     if negative, carets will be positioned at selection ends
     */
    public static void selectSearchResultsInEditor(@NotNull Editor editor,
            @NotNull Iterator<FindResult> resultIterator, int caretShiftFromSelectionStart) {
        if (!editor.getCaretModel().supportsMultipleCarets()) {
            return;
        }
        ArrayList<CaretState> caretStates = new ArrayList<CaretState>();
        while (resultIterator.hasNext()) {
            FindResult findResult = resultIterator.next();
            int caretOffset = getCaretPosition(findResult, caretShiftFromSelectionStart);
            int selectionStartOffset = findResult.getStartOffset();
            int selectionEndOffset = findResult.getEndOffset();
            EditorActionUtil.makePositionVisible(editor, caretOffset);
            EditorActionUtil.makePositionVisible(editor, selectionStartOffset);
            EditorActionUtil.makePositionVisible(editor, selectionEndOffset);
            caretStates.add(new CaretState(editor.offsetToLogicalPosition(caretOffset),
                    editor.offsetToLogicalPosition(selectionStartOffset),
                    editor.offsetToLogicalPosition(selectionEndOffset)));
        }
        if (caretStates.isEmpty()) {
            return;
        }
        editor.getCaretModel().setCaretsAndSelections(caretStates);
    }

    /**
     * Attempts to add a new caret to editor, with selection corresponding to given search result.
     *
     * @param caretShiftFromSelectionStart if non-negative, defines caret position relative to selection start, for each created selection.
     *                                     if negative, caret will be positioned at selection end
     * @return <code>true</code> if caret was added successfully, <code>false</code> if it cannot be done, e.g. because a caret already
     * exists at target position
     */
    public static boolean selectSearchResultInEditor(@NotNull Editor editor, @NotNull FindResult result,
            int caretShiftFromSelectionStart) {
        if (!editor.getCaretModel().supportsMultipleCarets()) {
            return false;
        }
        int caretOffset = getCaretPosition(result, caretShiftFromSelectionStart);
        EditorActionUtil.makePositionVisible(editor, caretOffset);
        Caret newCaret = editor.getCaretModel().addCaret(editor.offsetToVisualPosition(caretOffset));
        if (newCaret == null) {
            return false;
        } else {
            int selectionStartOffset = result.getStartOffset();
            int selectionEndOffset = result.getEndOffset();
            EditorActionUtil.makePositionVisible(editor, selectionStartOffset);
            EditorActionUtil.makePositionVisible(editor, selectionEndOffset);
            newCaret.setSelection(selectionStartOffset, selectionEndOffset);
            return true;
        }
    }

    private static int getCaretPosition(FindResult findResult, int caretShiftFromSelectionStart) {
        return caretShiftFromSelectionStart < 0 ? findResult.getEndOffset()
                : Math.min(findResult.getStartOffset() + caretShiftFromSelectionStart, findResult.getEndOffset());
    }
}