blob: 674358a49379d21acc9f78646b01cf41ec3a9aa9 [file] [log] [blame]
/*
* 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.impl.livePreview.LivePreview;
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.DocumentEx;
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();
if (!toPrompt) {
((DocumentEx)document).setInBulkUpdate(true);
}
try {
toPrompt = doReplace(project, editor, model, document, offset, toPrompt, delegate);
}
catch (ReadOnlyFragmentModificationException e) {
EditorActionManager.getInstance().getReadonlyFragmentModificationHandler(document).handle(e);
}
finally {
if (!toPrompt) {
((DocumentEx)document).setInBulkUpdate(false);
}
document.stopGuardedBlockChecking();
}
return true;
}
private static boolean 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 = (FindModel)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;
((DocumentEx)document).setInBulkUpdate(true);
}
}
int newOffset;
if (delegate == null || delegate.shouldReplace(result, toReplace)) {
boolean reallyReplace = toPrompt;
if (reallyReplace) {
//[SCR 7258]
if (!reallyReplaced) {
editor.getCaretModel().moveToOffset(0);
reallyReplaced = true;
}
}
TextRange textRange = doReplace(project, document, model, result, toReplace, reallyReplace, 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);
return replaced;
}
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());
}
}