search/replace refactoring. added exclude function. fixed unexpected scrolling when document changes. multiline occurrences highlithting

This commit is contained in:
andrey zaytsev
2011-02-18 20:52:54 +03:00
parent dcad2f2248
commit a5f56173c8
12 changed files with 1034 additions and 1024 deletions
@@ -21,9 +21,10 @@ package com.intellij.find;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.find.impl.FindManagerImpl;
import com.intellij.find.impl.LiveOccurrence;
import com.intellij.find.impl.LivePreview;
import com.intellij.find.impl.LivePreviewControllerBase;
import com.intellij.find.impl.livePreview.LiveOccurrence;
import com.intellij.find.impl.livePreview.LivePreview;
import com.intellij.find.impl.livePreview.LivePreviewControllerBase;
import com.intellij.find.impl.livePreview.SearchResults;
import com.intellij.ide.ui.UISettings;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
@@ -38,7 +39,6 @@ import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.PopupChooserBuilder;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
@@ -69,8 +69,8 @@ import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
public class EditorSearchComponent extends JPanel implements DataProvider, LivePreview.CursorListener, SelectionListener {
private static final int MATCHES_LIMIT = 100;
public class EditorSearchComponent extends JPanel implements DataProvider, SelectionListener, SearchResults.SearchResultsListener {
private static final int MATCHES_LIMIT = 10000;
private final JLabel myMatchInfoLabel;
private final LinkLabel myClickToHighlightLabel;
private final Project myProject;
@@ -95,51 +95,14 @@ public class EditorSearchComponent extends JPanel implements DataProvider, LiveP
private final JCheckBox myCbInComments;
private final JCheckBox myCbInLiterals;
private final LivePreviewControllerBase myLivePreviewController = new LivePreviewControllerBase() {
@Override
public void getFocusBack() {
mySearchField.requestFocus();
}
@Override
public Editor getEditor(Ref<Boolean> needToUpdate) {
needToUpdate.set(true);
return myEditor;
}
@Override
public void searchEndsWith(int count) {
if (count <= getMatchesLimit()) {
myClickToHighlightLabel.setVisible(false);
if (count > 0) {
setRegularBackground();
if (count > 1) {
myMatchInfoLabel.setText(count + " matches");
}
else {
myMatchInfoLabel.setText("1 match");
}
}
else {
setNotFoundBackground();
myMatchInfoLabel.setText("No matches");
}
}
else {
setRegularBackground();
myMatchInfoLabel.setText("More than 100 matches");
myClickToHighlightLabel.setVisible(true);
boldMatchInfo();
}
}
};
private final LivePreviewControllerBase myLivePreviewController;
private final LivePreview myLivePreview;
private boolean myIsReplace;
private boolean myListeningSelection = false;
private boolean myToChangeSelection = true;
private SearchResults mySearchResults;
@Nullable
public Object getData(@NonNls final String dataId) {
@@ -153,12 +116,71 @@ public class EditorSearchComponent extends JPanel implements DataProvider, LiveP
this(e, p, false);
}
@Override
public void searchResultsUpdated(SearchResults sr) {
int count = sr.getActualFound();
if (mySearchField.getText().isEmpty()) {
nothingToSearchFor();
return;
}
if (count <= mySearchResults.getMatchesLimit()) {
myClickToHighlightLabel.setVisible(false);
if (count > 0) {
setRegularBackground();
if (count > 1) {
myMatchInfoLabel.setText(count + " matches");
}
else {
myMatchInfoLabel.setText("1 match");
}
}
else {
setNotFoundBackground();
myMatchInfoLabel.setText("No matches");
}
}
else {
setRegularBackground();
myMatchInfoLabel.setText("More than " + mySearchResults.getMatchesLimit() + " matches");
myClickToHighlightLabel.setVisible(true);
boldMatchInfo();
}
updateSelection();
}
@Override
public void cursorMoved() {
updateSelection();
}
private void updateSelection() {
if (myToChangeSelection && (mySelectionOnly == null || !mySelectionOnly.isSelected())) {
LiveOccurrence cursor = mySearchResults.getCursor();
if (cursor != null) {
TextRange range = cursor.getPrimaryRange();
myEditor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
myEditor.getCaretModel().moveToOffset(range.getEndOffset());
myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
myEditor.getCaretModel().moveToOffset(range.getStartOffset());
myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
}
myToChangeSelection = false;
}
}
@Override
public void editorChanged(SearchResults sr, Editor oldEditor) { }
public EditorSearchComponent(final Editor editor, final Project project, boolean isReplace) {
super(new BorderLayout(0, 0));
myIsReplace = isReplace;
setMatchesLimit(MATCHES_LIMIT);
GRADIENT_C1 = getBackground();
GRADIENT_C2 = new Color(Math.max(0, GRADIENT_C1.getRed() - 0x18), Math.max(0, GRADIENT_C1.getGreen() - 0x18), Math.max(0, GRADIENT_C1.getBlue() - 0x18));
@@ -166,9 +188,18 @@ public class EditorSearchComponent extends JPanel implements DataProvider, LiveP
myProject = project;
myEditor = editor;
myLivePreview = new LivePreview(myEditor.getProject(), 0);
myLivePreview.addCursorListener(this);
myLivePreview.setDelegate(myLivePreviewController);
mySearchResults = new SearchResults(myEditor);
myLivePreview = new LivePreview(mySearchResults);
myLivePreviewController = new LivePreviewControllerBase(mySearchResults, myLivePreview) {
@Override
public void getFocusBack() {
mySearchField.requestFocus();
}
};
mySearchResults.addListener(this);
setMatchesLimit(MATCHES_LIMIT);
JPanel leadPanel = createLeadPane();
add(leadPanel, BorderLayout.WEST);
@@ -338,7 +369,7 @@ public class EditorSearchComponent extends JPanel implements DataProvider, LiveP
}
private void setMatchesLimit(int value) {
myLivePreviewController.setMatchesLimit(value);
mySearchResults.setMatchesLimit(value);
}
private void configureReplacementPane() {
@@ -483,24 +514,6 @@ public class EditorSearchComponent extends JPanel implements DataProvider, LiveP
}
}
@Override
public void cursorMoved() {
if (myToChangeSelection && (mySelectionOnly == null || !mySelectionOnly.isSelected())) {
LiveOccurrence cursor = myLivePreview.getCursor();
if (cursor != null) {
TextRange range = cursor.getPrimaryRange();
myEditor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
myEditor.getCaretModel().moveToOffset(range.getEndOffset());
myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
myEditor.getCaretModel().moveToOffset(range.getStartOffset());
myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
}
myToChangeSelection = false;
}
}
@Override
public void selectionChanged(SelectionEvent e) {
updateResults(true);
@@ -509,9 +522,9 @@ public class EditorSearchComponent extends JPanel implements DataProvider, LiveP
public void moveCursor(boolean forwardOrBackward) {
myToChangeSelection = true;
if (forwardOrBackward) {
myLivePreview.nextOccurrence();
mySearchResults.nextOccurrence();
} else {
myLivePreview.prevOccurrence();
mySearchResults.prevOccurrence();
}
}
@@ -555,7 +568,7 @@ public class EditorSearchComponent extends JPanel implements DataProvider, LiveP
myEditor.getDocument().addDocumentListener(myDocumentListener);
if (myLivePreview != null) {
myLivePreview.update();
myLivePreviewController.updateInBackground(mySearchResults.getFindModel());
}
}
@@ -576,12 +589,7 @@ public class EditorSearchComponent extends JPanel implements DataProvider, LiveP
myMatchInfoLabel.setFont(myMatchInfoLabel.getFont().deriveFont(Font.PLAIN));
final String text = mySearchField.getText();
if (text.length() == 0) {
setRegularBackground();
myMatchInfoLabel.setText("");
myClickToHighlightLabel.setVisible(false);
if (myLivePreview != null) {
myLivePreview.cleanUp();
}
nothingToSearchFor();
}
else {
final FindModel model = new FindModel();
@@ -628,9 +636,17 @@ public class EditorSearchComponent extends JPanel implements DataProvider, LiveP
model.setPreserveCase(myPreserveCase.isEnabled() && myPreserveCase.isSelected());
}
myToChangeSelection = allowedToChangedEditorSelection;
myLivePreviewController.setFindModel(model);
myLivePreview.update();
myLivePreviewController.updateInBackground(model);
}
}
private void nothingToSearchFor() {
setRegularBackground();
myMatchInfoLabel.setText("");
myClickToHighlightLabel.setVisible(false);
if (myLivePreview != null) {
myLivePreview.cleanUp();
}
}
@@ -410,6 +410,15 @@ public class FindUtil {
}
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)) {
@@ -423,7 +432,7 @@ public class FindUtil {
((DocumentEx) document).setInBulkUpdate(true);
}
try {
toPrompt = doReplace(project, editor, model, document, offset, toPrompt);
toPrompt = doReplace(project, editor, model, document, offset, toPrompt, delegate);
}
catch (ReadOnlyFragmentModificationException e) {
EditorActionManager.getInstance().getReadonlyFragmentModificationHandler(document).handle(e);
@@ -438,7 +447,8 @@ public class FindUtil {
return true;
}
private static boolean doReplace(Project project, Editor editor, FindModel model, final Document document, int caretOffset, boolean toPrompt) {
private static boolean doReplace(Project project, Editor editor, FindModel model, final Document document, int caretOffset,
boolean toPrompt, ReplaceDelegate delegate) {
FindManager findManager = FindManager.getInstance(project);
model = (FindModel)model.clone();
int occurrences = 0;
@@ -517,7 +527,11 @@ public class FindUtil {
TextRange range = pair.getFirst();
String replace = pair.getSecond();
newText.append(text, offsetBefore, range.getStartOffset()); //before change
newText.append(replace);
if (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();
@@ -761,4 +775,8 @@ public class FindUtil {
editor.getCaretModel().moveToLogicalPosition(pos);
editor.getScrollingModel().scrollToCaret(scrollType);
}
public interface ReplaceDelegate {
boolean shouldReplace(TextRange range, String replace) ;
}
}
@@ -19,7 +19,6 @@ package com.intellij.find.impl;
import com.intellij.CommonBundle;
import com.intellij.find.FindBundle;
import com.intellij.find.FindManager;
import com.intellij.find.FindModel;
import com.intellij.find.FindSettings;
import com.intellij.ide.util.scopeChooser.ScopeChooserCombo;
@@ -108,9 +107,6 @@ class FindDialog extends DialogWrapper {
private JRadioButton myRbCustomScope;
private ScopeChooserCombo myScopeCombo;
private LivePreviewController myLivePreviewController;
public FindDialog(Project project, FindModel model, Runnable myOkHandler){
super(project, true);
myProject = project;
@@ -139,7 +135,7 @@ class FindDialog extends DialogWrapper {
setOKButtonIcon(IconLoader.getIcon("/actions/find.png"));
init();
initByModel();
myLivePreviewController = new LivePreviewController(this, new LivePreview(myProject), getContentPane());
//myLivePreviewController = new LivePreviewController(this, new LivePreview(myProject), getContentPane());
}
@Override
@@ -148,9 +144,9 @@ class FindDialog extends DialogWrapper {
e.getKey().removeDocumentListener(e.getValue());
}
myComboBoxListeners.clear();
if (myLivePreviewController != null) {
myLivePreviewController.cleanUp();
}
//if (myLivePreviewController != null) {
// myLivePreviewController.cleanUp();
//}
super.dispose();
}
@@ -1,570 +0,0 @@
/*
* Copyright 2000-2011 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.impl;
import com.intellij.codeInsight.highlighting.HighlightManager;
import com.intellij.ide.IdeTooltipManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.editor.VisualPosition;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.VisibleAreaEvent;
import com.intellij.openapi.editor.event.VisibleAreaListener;
import com.intellij.openapi.editor.markup.CustomHighlighterRenderer;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.ui.popup.BalloonBuilder;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.util.Alarm;
import com.intellij.util.containers.HashSet;
import com.intellij.util.ui.PositionTracker;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
public class LivePreview extends DocumentAdapter implements ReplacementView.Delegate {
private final Collection<RangeHighlighter> myHighlighters = new HashSet<RangeHighlighter>();
private RangeHighlighter myCursorHighlighter;
private final List<VisibleAreaListener> myVisibleAreaListenersToRemove = new ArrayList<VisibleAreaListener>();
private boolean myShouldStop;
@Override
public void performReplacement(LiveOccurrence occurrence, String replacement) {
if (myDelegate != null) {
final TextRange textRange = myDelegate.performReplace(occurrence, replacement, myEditor);
if (textRange != null) {
updateInBackground();
setContinuation(new Runnable() {
@Override
public void run() {
if (mySearchResults != null) {
LiveOccurrence nearest = null;
int minDist = Integer.MAX_VALUE;
for (LiveOccurrence o : mySearchResults) {
if (nearest == null) {
nearest = o;
}
int dist = Math.abs(o.getPrimaryRange().getStartOffset() - textRange.getStartOffset());
if (dist < minDist) {
minDist = dist;
nearest = o;
}
}
if (nearest != null) {
moveCursorTo(nearest);
}
}
}
});
myDelegate.getFocusBack();
}
}
}
@Override
public void performReplaceAll() {
myDelegate.performReplaceAll(myEditor);
}
public boolean hasMatches() {
return mySearchResults != null && !mySearchResults.isEmpty();
}
public LiveOccurrence getCursor() {
return myCursor;
}
public Project getProject() {
return myProject;
}
public Editor getEditor() {
return myEditor;
}
public interface CursorListener {
void cursorMoved();
}
private final List<CursorListener> myListeners = new ArrayList<CursorListener>();
public void addCursorListener(CursorListener listener) {
myListeners.add(listener);
}
public void removeCursorListener(CursorListener listener) {
myListeners.remove(listener);
}
public interface Delegate {
@NotNull
List<LiveOccurrence> performSearchInBackgroundInReadAction(Editor editor);
@Nullable
String getReplacementPreviewText(Editor editor, LiveOccurrence liveOccurrence);
TextRange performReplace(LiveOccurrence occurrence, String replacement, Editor editor);
void performReplaceAll(Editor e);
void getFocusBack();
Editor getEditor(Ref<Boolean> needToUpdate);
}
private static final int USER_ACTIVITY_TRIGGERING_DELAY = 300;
private static final TextAttributes OTHER_TARGETS_ATTRIBUTES = new TextAttributes(Color.BLACK, Color.GREEN, null, null, 0);
private static final TextAttributes MAIN_TARGET_ATTRIBUTES = new TextAttributes(Color.BLACK, Color.YELLOW, null, null, 0);
private final Alarm myLivePreviewAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
private final Project myProject;
private Editor myEditor;
private Delegate myDelegate;
private LiveOccurrence myCursor;
private Runnable myContinuation;
private int myUserActivityDelay = USER_ACTIVITY_TRIGGERING_DELAY;
public Runnable getContinuation() {
return myContinuation;
}
public void setContinuation(Runnable continuation) {
myContinuation = continuation;
}
public List<LiveOccurrence> getSearchResults() {
return mySearchResults;
}
private List<LiveOccurrence> mySearchResults;
private Balloon myReplacementBalloon;
public LivePreview(Project project) {
myProject = project;
}
public LivePreview(Project project, int delay) {
this(project);
myUserActivityDelay = delay;
}
public Delegate getDelegate() {
return myDelegate;
}
public void setDelegate(Delegate delegate) {
myDelegate = delegate;
}
@Nullable
private Editor updateEditorReference() {
if (myProject == null) return null;
Ref<Boolean> needToUpdate = new Ref<Boolean>();
Editor result = myDelegate.getEditor(needToUpdate);
if (!needToUpdate.isNull() && needToUpdate.get()) {
setEditor(result);
}
return myEditor;
}
private void setEditor(Editor editor) {
if (editor != myEditor) {
doInternalCleanUp();
}
if (myEditor != null) {
myEditor.getDocument().removeDocumentListener(this);
}
myEditor = editor;
if (myEditor != null) {
myEditor.getDocument().addDocumentListener(this);
}
}
@Override
public void documentChanged(DocumentEvent e) {
doInternalCleanUp();
}
public void update() {
myShouldStop = false;
myLivePreviewAlarm.cancelAllRequests();
if (updateEditorReference() != null) {
Runnable request = new Runnable() {
@Override
public void run() {
updateInBackground();
}
};
if (ApplicationManager.getApplication().isUnitTestMode()) {
request.run();
} else {
myLivePreviewAlarm.addRequest(request, myUserActivityDelay);
}
}
}
public void cleanUp() {
myShouldStop = true;
myLivePreviewAlarm.cancelAllRequests();
doInternalCleanUp();
}
private void doInternalCleanUp() {
if (myReplacementBalloon != null) {
myReplacementBalloon.hide();
}
if (myEditor != null) {
for (VisibleAreaListener visibleAreaListener : myVisibleAreaListenersToRemove) {
myEditor.getScrollingModel().removeVisibleAreaListener(visibleAreaListener);
}
myVisibleAreaListenersToRemove.clear();
for (RangeHighlighter h : myHighlighters) {
HighlightManager.getInstance(myProject).removeSegmentHighlighter(myEditor, h);
}
if (myCursorHighlighter != null) {
HighlightManager.getInstance(myProject).removeSegmentHighlighter(myEditor, myCursorHighlighter);
myCursorHighlighter = null;
}
}
}
private void updateInBackground() {
if (myDelegate == null) return;
final TextRange oldCursorRange = myCursor != null ? myCursor.getPrimaryRange() : null;
mySearchResults = performSearchInBackground();
Collections.sort(mySearchResults, new Comparator<LiveOccurrence>() {
@Override
public int compare(LiveOccurrence liveOccurrence, LiveOccurrence liveOccurence1) {
return liveOccurrence.getPrimaryRange().getStartOffset() - liveOccurence1.getPrimaryRange().getStartOffset();
}
});
if (mySearchResults != null) {
Runnable highlightUsagesBlock = new Runnable() {
@Override
public void run() {
doInternalCleanUp();
highlightUsages(oldCursorRange);
if (myContinuation != null) {
myContinuation.run();
myContinuation = null;
}
}
};
if (ApplicationManager.getApplication().isUnitTestMode()) {
highlightUsagesBlock.run();
} else {
ApplicationManager.getApplication().invokeLater(highlightUsagesBlock);
}
}
}
private List<LiveOccurrence> performSearchInBackground() {
final AtomicReference<List<LiveOccurrence>> occurrences = new AtomicReference<List<LiveOccurrence>>();
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
occurrences.set(myDelegate.performSearchInBackgroundInReadAction(myEditor));
}
});
return occurrences.get();
}
private void highlightUsages(TextRange oldCursorRange) {
if (myEditor == null || myShouldStop) return;
for (LiveOccurrence o : mySearchResults) {
for (TextRange textRange : o.getSecondaryRanges()) {
highlightRange(textRange, OTHER_TARGETS_ATTRIBUTES, myHighlighters);
}
highlightRange(o.getPrimaryRange(), MAIN_TARGET_ATTRIBUTES, myHighlighters);
}
if (!tryToRepairOldCursor(oldCursorRange)) {
LiveOccurrence afterCaret = firstOccurrenceAfterCaret();
if (afterCaret != null) {
setCursor(afterCaret);
} else {
LiveOccurrence occurrence = firstVisibleOccurrence();
if (occurrence != null) {
setCursor(occurrence);
}
}
}
}
@Nullable
private LiveOccurrence firstVisibleOccurrence() {
int offset = Integer.MAX_VALUE;
LiveOccurrence firstOccurrence = null;
LiveOccurrence firstVisibleOccurrence = null;
for (LiveOccurrence o : mySearchResults) {
if (insideVisibleArea(myEditor, o.getPrimaryRange())) {
if (firstVisibleOccurrence == null || o.getPrimaryRange().getStartOffset() < firstVisibleOccurrence.getPrimaryRange().getStartOffset()) {
firstVisibleOccurrence = o;
}
}
if (o.getPrimaryRange().getStartOffset() < offset) {
offset = o.getPrimaryRange().getStartOffset();
firstOccurrence = o;
}
}
return firstVisibleOccurrence != null ? firstVisibleOccurrence : firstOccurrence;
}
@Nullable
private LiveOccurrence firstOccurrenceAfterCaret() {
LiveOccurrence afterCaret = null;
int caret = myEditor.getCaretModel().getOffset();
for (LiveOccurrence occurrence : mySearchResults) {
if (occurrence.getPrimaryRange().getStartOffset() >= caret) {
if (afterCaret == null || occurrence.getPrimaryRange().getStartOffset() < afterCaret.getPrimaryRange().getStartOffset() ) {
afterCaret = occurrence;
}
}
}
return afterCaret;
}
private boolean tryToRepairOldCursor(TextRange oldCursorRange) {
if (oldCursorRange == null) return false;
LiveOccurrence mayBeOldCursor = null;
for (LiveOccurrence searchResult : mySearchResults) {
if (searchResult.getPrimaryRange().intersects(oldCursorRange)) {
mayBeOldCursor = searchResult;
break;
}
}
if (mayBeOldCursor != null && insideVisibleArea(myEditor, mayBeOldCursor.getPrimaryRange())) {
setCursor(mayBeOldCursor);
return true;
}
return false;
}
@Nullable
private LiveOccurrence prevOccurrence(LiveOccurrence o) {
if (mySearchResults == null) return null;
for (int i = 0; i < mySearchResults.size(); ++i) {
if (o == mySearchResults.get(i)) {
if (i > 0) {
return mySearchResults.get(i-1);
}
}
}
return null;
}
@Nullable
private LiveOccurrence nextOccurrence(LiveOccurrence o) {
if (mySearchResults == null) return null;
boolean found = false;
for (LiveOccurrence occurrence : mySearchResults) {
if (found) {
return occurrence;
}
if (o == occurrence) {
found = true;
}
}
return null;
}
public void prevOccurrence() {
LiveOccurrence prev = prevOccurrence(myCursor);
if (prev == null && !mySearchResults.isEmpty()) {
prev = mySearchResults.get(mySearchResults.size()-1);
}
moveCursorTo(prev);
}
public void nextOccurrence() {
LiveOccurrence next = nextOccurrence(myCursor);
if (next == null && !mySearchResults.isEmpty()) {
next = mySearchResults.get(0);
}
moveCursorTo(next);
}
public void moveCursorTo(LiveOccurrence next) {
if (next != null) {
setCursor(next);
} else {
showReplacementPreview();
}
}
private void setCursor(LiveOccurrence liveOccurrence) {
hideBalloon();
boolean toNotify = myCursor == null || !myCursor.equals(liveOccurrence);
myCursor = liveOccurrence;
if (myCursorHighlighter != null) {
HighlightManager.getInstance(myProject).removeSegmentHighlighter(myEditor, myCursorHighlighter);
myCursorHighlighter = null;
}
if (myCursor != null) {
ArrayList<RangeHighlighter> dummy = new ArrayList<RangeHighlighter>();
highlightRange(myCursor.getPrimaryRange(), new TextAttributes(null, null, null, null, 0), dummy);
if (!dummy.isEmpty()) {
myCursorHighlighter = dummy.get(0);
myCursorHighlighter.setCustomRenderer(new MyCustomCursorRenderer());
}
if (!insideVisibleArea(myEditor, myCursor.getPrimaryRange())) {
myEditor.getScrollingModel().scrollTo(myEditor.offsetToLogicalPosition(myCursor.getPrimaryRange().getStartOffset()),
ScrollType.CENTER);
myEditor.getScrollingModel().runActionOnScrollingFinished(new Runnable() {
@Override
public void run() {
showReplacementPreview();
}
});
} else {
showReplacementPreview();
}
}
if (toNotify) {
for (CursorListener l : myListeners) {
if (l!= null) {
l.cursorMoved();
}
}
}
}
private void showReplacementPreview() {
hideBalloon();
if (myDelegate != null && myCursor != null) {
String replacementPreviewText = myDelegate.getReplacementPreviewText(myEditor, myCursor);
if (replacementPreviewText != null) {
//JLabel balloonContent = new JLabel(replacementPreviewText);
//balloonContent.setForeground(Color.WHITE);
ReplacementView replacementView = new ReplacementView(replacementPreviewText, myCursor);
replacementView.setDelegate(this);
BalloonBuilder balloonBuilder = JBPopupFactory.getInstance().createBalloonBuilder(replacementView);
balloonBuilder.setFadeoutTime(0);
balloonBuilder.setFillColor(IdeTooltipManager.GRAPHITE_COLOR);
balloonBuilder.setAnimationCycle(0);
balloonBuilder.setHideOnClickOutside(false);
balloonBuilder.setHideOnKeyOutside(false);
balloonBuilder.setHideOnAction(false);
balloonBuilder.setCloseButtonEnabled(true);
myReplacementBalloon = balloonBuilder.createBalloon();
final int startOffset = myCursor.getPrimaryRange().getStartOffset();
final int endOffset = myCursor.getPrimaryRange().getEndOffset();
myReplacementBalloon.show(new PositionTracker<Balloon>(myEditor.getContentComponent()) {
@Override
public RelativePoint recalculateLocation(final Balloon object) {
Point startPoint = myEditor.visualPositionToXY(myEditor.offsetToVisualPosition(startOffset));
Point endPoint = myEditor.visualPositionToXY(myEditor.offsetToVisualPosition(endOffset));
Point point = new Point((startPoint.x + endPoint.x)/2, startPoint.y);
if (!insideVisibleArea(myEditor, myCursor.getPrimaryRange())) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
object.hide();
}
});
VisibleAreaListener visibleAreaListener = new VisibleAreaListener() {
@Override
public void visibleAreaChanged(VisibleAreaEvent e) {
if (insideVisibleArea(myEditor, myCursor.getPrimaryRange())) {
showReplacementPreview();
final VisibleAreaListener visibleAreaListener = this;
myEditor.getScrollingModel().removeVisibleAreaListener(visibleAreaListener);
myVisibleAreaListenersToRemove.remove(visibleAreaListener);
}
}
};
myEditor.getScrollingModel().addVisibleAreaListener(visibleAreaListener);
myVisibleAreaListenersToRemove.add(visibleAreaListener);
}
return new RelativePoint(myEditor.getContentComponent(), point);
}
}, Balloon.Position.above);
}
}
}
private void hideBalloon() {
if (myReplacementBalloon != null) {
myReplacementBalloon.hide();
myReplacementBalloon = null;
}
}
private void highlightRange(TextRange textRange, TextAttributes attributes, Collection<RangeHighlighter> highlighters) {
HighlightManager highlightManager = HighlightManager.getInstance(myProject);
if (highlightManager != null) {
highlightManager.addRangeHighlight(myEditor,
textRange.getStartOffset(), textRange.getEndOffset(),
attributes, false, highlighters);
}
}
private static boolean insideVisibleArea(Editor e, TextRange r) {
Rectangle visibleArea = e.getScrollingModel().getVisibleArea();
Point point = e.logicalPositionToXY(e.offsetToLogicalPosition(r.getStartOffset()));
return visibleArea.contains(point);
}
private static class MyCustomCursorRenderer implements CustomHighlighterRenderer {
@Override
public void paint(Editor editor, RangeHighlighter highlighter, Graphics g) {
Graphics2D g2d = (Graphics2D)g;
VisualPosition startVp = editor.offsetToVisualPosition(highlighter.getStartOffset());
VisualPosition endVp = editor.offsetToVisualPosition(highlighter.getEndOffset());
Point start = editor.visualPositionToXY(startVp);
Point end = editor.visualPositionToXY(endVp);
g2d.setColor(new Color(50, 50, 50));
g2d.translate(0, start.y - 4);
Color c1 = new Color(220, 200, 130);
Color c2 = new Color(220, 170, 30);
UIUtil.drawSearchMatch(g2d, start.x- 1, end.x+ 1, editor.getLineHeight() +2* 4, c1, c2);
g2d.translate(0, -start.y + 4);
}
}
}
@@ -1,147 +0,0 @@
package com.intellij.find.impl;
import com.intellij.find.FindModel;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.UserActivityListener;
import com.intellij.ui.UserActivityWatcher;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.util.List;
public class LivePreviewController implements LivePreview.Delegate, UserActivityListener {
private final FindDialog myFindDialog;
private final LivePreview myLivePreview;
private final UserActivityWatcher myWatcher = new MomentoUserActivityWatcher();
LivePreviewControllerBase myDelegate = new LivePreviewControllerBase();
private void updateDelegatesFindModel() {
FindModel currentModel = myFindDialog.getCurrentModel();
myDelegate.setFindModel(currentModel);
}
public LivePreviewController(FindDialog findDialog, LivePreview livePreview, Component toWatch) {
myFindDialog = findDialog;
myLivePreview = livePreview;
myLivePreview.setDelegate(this);
myWatcher.register(toWatch);
myWatcher.addUserActivityListener(this);
myFindDialog.getWindow().addWindowFocusListener(new WindowFocusListener() {
@Override
public void windowGainedFocus(WindowEvent windowEvent) {
myLivePreview.update();
}
@Override
public void windowLostFocus(WindowEvent windowEvent) {
}
});
JComponent contentPane = (JComponent)findDialog.getContentPane();
new AnAction() {
@Override
public void actionPerformed(AnActionEvent e) {
myLivePreview.nextOccurrence();
}
}.registerCustomShortcutSet(KeyEvent.VK_F3, 0, contentPane);
new AnAction() {
@Override
public void actionPerformed(AnActionEvent e) {
myLivePreview.prevOccurrence();
}
}.registerCustomShortcutSet(KeyEvent.VK_F3, KeyEvent.SHIFT_MASK, contentPane);
}
public void cleanUp() {
myWatcher.removeUserActivityListener(this);
myLivePreview.cleanUp();
}
@NotNull
@Override
public List<LiveOccurrence> performSearchInBackgroundInReadAction(Editor editor) {
updateDelegatesFindModel();
return myDelegate.performSearchInBackgroundInReadAction(editor);
}
@Override
public void performReplaceAll(Editor e) {
updateDelegatesFindModel();
myDelegate.performReplaceAll(e);
}
@Override
public void getFocusBack() {
myFindDialog.getContentPane().requestFocus();
}
@Override
public Editor getEditor(Ref<Boolean> needToUpdate) {
Editor result = null;
FileEditorManagerEx instanceEx = FileEditorManagerEx.getInstanceEx(myLivePreview.getProject());
if (instanceEx != null) {
VirtualFile currentFile = instanceEx.getCurrentFile();
if (currentFile != null) {
FileEditor[] editors = instanceEx.getEditors(currentFile);
if (editors.length > 0) {
FileEditor fileEditor = editors[0];
boolean focusedFound = false;
for (FileEditor e : editors) {
if (e instanceof TextEditor && ((TextEditor)e).getEditor().getContentComponent().hasFocus()) {
fileEditor = e;
focusedFound = true;
}
}
needToUpdate.set(true);
if (!focusedFound) {
for (FileEditor e : editors) {
if (e instanceof TextEditor && ((TextEditor)e).getEditor() == myLivePreview.getEditor()) {
needToUpdate.set(false);
}
}
}
if(needToUpdate.get() && fileEditor instanceof TextEditor) {
result = ((TextEditor)fileEditor).getEditor();
}
}
}
}
return result;
}
@Override
public TextRange performReplace(final LiveOccurrence occurrence, final String replacement, final Editor editor) {
updateDelegatesFindModel();
return myDelegate.performReplace(occurrence, replacement, editor);
}
@Override
public String getReplacementPreviewText(Editor editor, LiveOccurrence liveOccurrence) {
updateDelegatesFindModel();
return myDelegate.getReplacementPreviewText(editor, liveOccurrence);
}
@Override
public void stateChanged() {
myLivePreview.update();
}
}
@@ -1,150 +0,0 @@
package com.intellij.find.impl;
import com.intellij.find.FindManager;
import com.intellij.find.FindModel;
import com.intellij.find.FindResult;
import com.intellij.find.FindUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class LivePreviewControllerBase implements LivePreview.Delegate {
private static final String EMPTY_STRING_DISPLAY_TEXT = "<Empty string>";
private int myMatchesLimit = 100;
public int getMatchesLimit() {
return myMatchesLimit;
}
public void setMatchesLimit(int matchesLimit) {
myMatchesLimit = matchesLimit;
}
private FindModel myFindModel;
public FindModel getFindModel() {
return myFindModel;
}
public void setFindModel(FindModel findModel) {
myFindModel = findModel;
}
private static void findResultsToOccurrences(ArrayList<FindResult> results, Collection<LiveOccurrence> occurrences) {
for (FindResult r : results) {
LiveOccurrence occurrence = new LiveOccurrence();
occurrence.setPrimaryRange(r);
occurrences.add(occurrence);
}
}
@NotNull
@Override
public List<LiveOccurrence> performSearchInBackgroundInReadAction(Editor editor) {
ArrayList<LiveOccurrence> occurrences = new ArrayList<LiveOccurrence>();
if (myFindModel != null) {
TextRange r = myFindModel.isGlobal() ? new TextRange(0, Integer.MAX_VALUE) :
new TextRange(editor.getSelectionModel().getSelectionStart(),
editor.getSelectionModel().getSelectionEnd());
if (r.getLength() == 0) {
r = new TextRange(0, Integer.MAX_VALUE);
}
int offset = r.getStartOffset();
VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(editor.getDocument());
final ArrayList<FindResult> results = new ArrayList<FindResult>();
while (true) {
FindManager findManager = FindManager.getInstance(editor.getProject());
FindResult result = findManager.findString(editor.getDocument().getCharsSequence(), offset, myFindModel, virtualFile);
if (!result.isStringFound()) break;
int newOffset = result.getEndOffset();
if (offset == newOffset || result.getEndOffset() > r.getEndOffset()) break;
offset = newOffset;
results.add(result);
if (results.size() > myMatchesLimit) break;
}
if (results.size() < myMatchesLimit) {
findResultsToOccurrences(results, occurrences);
}
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
searchEndsWith(results.size());
}
});
}
return occurrences;
}
protected void searchEndsWith(int size) {
}
@Override
public String getReplacementPreviewText(Editor editor, LiveOccurrence liveOccurrence) {
String foundString = editor.getDocument().getText(liveOccurrence.getPrimaryRange());
String documentText = editor.getDocument().getText();
FindModel currentModel = myFindModel;
String stringToReplace = null;
if (currentModel != null) {
if (currentModel.isReplaceState()) {
FindManager findManager = FindManager.getInstance(editor.getProject());
try {
stringToReplace = findManager.getStringToReplace(foundString, currentModel,
liveOccurrence.getPrimaryRange().getStartOffset(), documentText);
}
catch (FindManager.MalformedReplacementStringException e) {
return null;
}
if (stringToReplace != null && stringToReplace.isEmpty()) {
stringToReplace = EMPTY_STRING_DISPLAY_TEXT;
}
}
}
return stringToReplace;
}
@Override
public TextRange performReplace(final LiveOccurrence occurrence, final String replacement, final Editor editor) {
TextRange range = occurrence.getPrimaryRange();
try {
return FindUtil.doReplace(editor.getProject(), editor.getDocument(), myFindModel, new FindResultImpl(range.getStartOffset(), range.getEndOffset()),
FindManager.getInstance(editor.getProject()).getStringToReplace(editor.getDocument().getText(range), myFindModel), true, new ArrayList<Pair<TextRange, String>>());
}
catch (FindManager.MalformedReplacementStringException e) {
/**/
}
return null;
}
@Override
public void performReplaceAll(Editor e) {
FindUtil.replace(e.getProject(), e, 0, myFindModel);
}
@Override
public void getFocusBack() {}
@Override
public Editor getEditor(Ref<Boolean> needToUpdate) {
return null;
}
}
@@ -1,66 +0,0 @@
package com.intellij.find.impl;
import com.intellij.ide.IdeTooltipManager;
import com.intellij.openapi.util.SystemInfo;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ReplacementView extends JPanel {
private String myReplacement;
private LiveOccurrence myOccurrence;
public interface Delegate {
void performReplacement(LiveOccurrence occurrence, String replacement);
void performReplaceAll();
}
private Delegate delegate;
public Delegate getDelegate() {
return delegate;
}
public void setDelegate(Delegate delegate) {
this.delegate = delegate;
}
public ReplacementView(final String replacement, final LiveOccurrence occurrence) {
myReplacement = replacement;
myOccurrence = occurrence;
JLabel jLabel = new JLabel(replacement);
jLabel.setForeground(Color.WHITE);
add(jLabel);
JButton replace = new JButton("Replace");
replace.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (delegate != null) {
delegate.performReplacement(occurrence, replacement);
}
}
});
replace.setMnemonic('R');
add(replace);
JButton replaceAllButton = new JButton("Replace all");
replaceAllButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (delegate != null) {
delegate.performReplaceAll();
}
}
});
replaceAllButton.setMnemonic('a');
add(replaceAllButton);
setBackground(IdeTooltipManager.GRAPHITE_COLOR);
if (SystemInfo.isMac) {
Font f = getFont();
setFont(f.deriveFont(f.getStyle(), f.getSize() - 4));
}
}
}
@@ -1,4 +1,4 @@
package com.intellij.find.impl;
package com.intellij.find.impl.livePreview;
import com.intellij.openapi.util.TextRange;
@@ -34,4 +34,9 @@ public class LiveOccurrence {
}
return false;
}
@Override
public int hashCode() {
return myPrimaryRange.hashCode();
}
}
@@ -0,0 +1,336 @@
/*
* Copyright 2000-2011 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.impl.livePreview;
import com.intellij.codeInsight.highlighting.HighlightManager;
import com.intellij.ide.IdeTooltipManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.editor.VisualPosition;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.VisibleAreaEvent;
import com.intellij.openapi.editor.event.VisibleAreaListener;
import com.intellij.openapi.editor.markup.CustomHighlighterRenderer;
import com.intellij.openapi.editor.markup.EffectType;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.ui.popup.BalloonBuilder;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.util.TextRange;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.util.containers.HashSet;
import com.intellij.util.ui.PositionTracker;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class LivePreview extends DocumentAdapter implements ReplacementView.Delegate, SearchResults.SearchResultsListener {
public interface Delegate {
@Nullable
String getReplacementPreviewText(Editor editor, LiveOccurrence liveOccurrence);
@Nullable
TextRange performReplace(LiveOccurrence occurrence, String replacement, Editor editor);
void performReplaceAll(Editor e);
void getFocusBack();
}
private final Collection<RangeHighlighter> myHighlighters = new HashSet<RangeHighlighter>();
private RangeHighlighter myCursorHighlighter;
private final List<VisibleAreaListener> myVisibleAreaListenersToRemove = new ArrayList<VisibleAreaListener>();
private static final TextAttributes EXCLUDED_TARGET_ATTRIBUTES = new TextAttributes(Color.BLACK, Color.YELLOW,
Color.BLACK, EffectType.STRIKEOUT, 0);
private static final TextAttributes OTHER_TARGETS_ATTRIBUTES = new TextAttributes(Color.BLACK, Color.GREEN, null, null, 0);
private static final TextAttributes MAIN_TARGET_ATTRIBUTES = new TextAttributes(Color.BLACK, Color.YELLOW, null, null, 0);
private Delegate myDelegate;
private SearchResults mySearchResults;
private Balloon myReplacementBalloon;
@Override
public void performReplacement(LiveOccurrence occurrence, String replacement) {
if (myDelegate != null) {
myDelegate.performReplace(occurrence, replacement, mySearchResults.getEditor());
myDelegate.getFocusBack();
}
}
@Override
public void performReplaceAll() {
myDelegate.performReplaceAll(mySearchResults.getEditor());
}
public boolean isExcluded(LiveOccurrence occurrence) {
return mySearchResults.isExcluded(occurrence);
}
public void exclude(LiveOccurrence occurrence) {
mySearchResults.exclude(occurrence);
myDelegate.getFocusBack();
}
public boolean hasMatches() {
return mySearchResults.hasMatches();
}
@Override
public void searchResultsUpdated(SearchResults sr) {
removeFromEditor(mySearchResults.getEditor());
highlightUsages();
updateCursorHighlighting(false);
}
@Override
public void cursorMoved() {
updateCursorHighlighting(true);
}
public void editorChanged(SearchResults sr, Editor oldEditor) {
removeFromEditor(mySearchResults.getEditor());
oldEditor.getDocument().removeDocumentListener(this);
mySearchResults.getEditor().getDocument().addDocumentListener(this);
}
private void updateCursorHighlighting(boolean scroll) {
hideBalloon();
if (myCursorHighlighter != null) {
HighlightManager.getInstance(mySearchResults.getProject()).removeSegmentHighlighter(mySearchResults.getEditor(), myCursorHighlighter);
myCursorHighlighter = null;
}
LiveOccurrence cursor = mySearchResults.getCursor();
Editor editor = mySearchResults.getEditor();
if (cursor != null) {
ArrayList<RangeHighlighter> dummy = new ArrayList<RangeHighlighter>();
highlightRange(cursor.getPrimaryRange(), new TextAttributes(null, null, null, null, 0), dummy);
if (!dummy.isEmpty()) {
myCursorHighlighter = dummy.get(0);
myCursorHighlighter.setCustomRenderer(new CursorRenderer());
}
if (!SearchResults.insideVisibleArea(editor, cursor.getPrimaryRange()) && scroll) {
editor.getScrollingModel().scrollTo(editor.offsetToLogicalPosition(cursor.getPrimaryRange().getStartOffset()),
ScrollType.CENTER);
editor.getScrollingModel().runActionOnScrollingFinished(new Runnable() {
@Override
public void run() {
showReplacementPreview();
}
});
} else {
showReplacementPreview();
}
}
}
public LivePreview(SearchResults searchResults) {
mySearchResults = searchResults;
searchResultsUpdated(searchResults);
searchResults.addListener(this);
}
public Delegate getDelegate() {
return myDelegate;
}
public void setDelegate(Delegate delegate) {
myDelegate = delegate;
}
public void cleanUp() {
removeFromEditor(mySearchResults.getEditor());
}
private void removeFromEditor(Editor editor) {
if (myReplacementBalloon != null) {
myReplacementBalloon.hide();
}
if (editor != null) {
for (VisibleAreaListener visibleAreaListener : myVisibleAreaListenersToRemove) {
editor.getScrollingModel().removeVisibleAreaListener(visibleAreaListener);
}
myVisibleAreaListenersToRemove.clear();
for (RangeHighlighter h : myHighlighters) {
HighlightManager.getInstance(mySearchResults.getProject()).removeSegmentHighlighter(editor, h);
}
if (myCursorHighlighter != null) {
HighlightManager.getInstance(mySearchResults.getProject()).removeSegmentHighlighter(editor, myCursorHighlighter);
myCursorHighlighter = null;
}
}
}
private void highlightUsages() {
if (mySearchResults.getEditor() == null) return;
for (LiveOccurrence o : mySearchResults.getOccurrences()) {
for (TextRange textRange : o.getSecondaryRanges()) {
highlightRange(textRange, OTHER_TARGETS_ATTRIBUTES, myHighlighters);
}
if (mySearchResults.isExcluded(o)) {
highlightRange(o.getPrimaryRange(), EXCLUDED_TARGET_ATTRIBUTES, myHighlighters);
} else {
highlightRange(o.getPrimaryRange(), MAIN_TARGET_ATTRIBUTES, myHighlighters);
}
}
}
private void showReplacementPreview() {
hideBalloon();
final LiveOccurrence cursor = mySearchResults.getCursor();
final Editor editor = mySearchResults.getEditor();
if (myDelegate != null && cursor != null) {
String replacementPreviewText = myDelegate.getReplacementPreviewText(editor, cursor);
if (replacementPreviewText != null) {
ReplacementView replacementView = new ReplacementView(replacementPreviewText, cursor);
replacementView.setDelegate(this);
BalloonBuilder balloonBuilder = JBPopupFactory.getInstance().createBalloonBuilder(replacementView);
balloonBuilder.setFadeoutTime(0);
balloonBuilder.setFillColor(IdeTooltipManager.GRAPHITE_COLOR);
balloonBuilder.setAnimationCycle(0);
balloonBuilder.setHideOnClickOutside(false);
balloonBuilder.setHideOnKeyOutside(false);
balloonBuilder.setHideOnAction(false);
balloonBuilder.setCloseButtonEnabled(true);
myReplacementBalloon = balloonBuilder.createBalloon();
final int startOffset = cursor.getPrimaryRange().getStartOffset();
final int endOffset = cursor.getPrimaryRange().getEndOffset();
myReplacementBalloon.show(new ReplacementBalloonPositionTracker(editor, startOffset, endOffset, cursor), Balloon.Position.above);
}
}
}
private void hideBalloon() {
if (myReplacementBalloon != null) {
myReplacementBalloon.hide();
myReplacementBalloon = null;
}
}
private void highlightRange(TextRange textRange, TextAttributes attributes, Collection<RangeHighlighter> highlighters) {
HighlightManager highlightManager = HighlightManager.getInstance(mySearchResults.getProject());
if (highlightManager != null) {
highlightManager.addRangeHighlight(mySearchResults.getEditor(),
textRange.getStartOffset(), textRange.getEndOffset(),
attributes, false, highlighters);
}
}
private static class CursorRenderer implements CustomHighlighterRenderer {
@Override
public void paint(Editor editor, RangeHighlighter highlighter, Graphics g) {
Document document = editor.getDocument();
int offset = highlighter.getStartOffset();
while (offset < highlighter.getEndOffset()) {
int line = document.getLineNumber(offset);
int newOffset = document.getLineEndOffset(line);
newOffset = Math.min(highlighter.getEndOffset(), newOffset);
drawSegment(editor, new TextRange(offset, newOffset), g);
offset = newOffset+1;
}
}
private static void drawSegment(Editor editor, Segment highlighter, Graphics g) {
Graphics2D g2d = (Graphics2D)g;
VisualPosition startVp = editor.offsetToVisualPosition(highlighter.getStartOffset());
VisualPosition endVp = editor.offsetToVisualPosition(highlighter.getEndOffset());
Point start = editor.visualPositionToXY(startVp);
Point end = editor.visualPositionToXY(endVp);
g2d.setColor(new Color(50, 50, 50));
g2d.translate(0, start.y - 4);
Color c1 = new Color(220, 200, 130);
Color c2 = new Color(220, 170, 30);
int endX = start.x != end.x ? end.x : end.x + 2;
UIUtil.drawSearchMatch(g2d, start.x - 1, endX + 1, editor.getLineHeight() + 2 * 4, c1, c2);
g2d.translate(0, -start.y + 4);
}
}
private class ReplacementBalloonPositionTracker extends PositionTracker<Balloon> {
private final Editor myEditor;
private final int myStartOffset;
private final int myEndOffset;
private final LiveOccurrence myCursor;
public ReplacementBalloonPositionTracker(Editor editor, int startOffset, int endOffset, LiveOccurrence cursor) {
super(editor.getContentComponent());
myEditor = editor;
myStartOffset = startOffset;
myEndOffset = endOffset;
myCursor = cursor;
}
@Override
public RelativePoint recalculateLocation(final Balloon object) {
Point startPoint = myEditor.visualPositionToXY(myEditor.offsetToVisualPosition(myStartOffset));
Point endPoint = myEditor.visualPositionToXY(myEditor.offsetToVisualPosition(myEndOffset));
Point point = new Point((startPoint.x + endPoint.x)/2, startPoint.y);
if (!SearchResults.insideVisibleArea(myEditor, myCursor.getPrimaryRange())) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
object.hide();
}
});
VisibleAreaListener visibleAreaListener = new VisibleAreaListener() {
@Override
public void visibleAreaChanged(VisibleAreaEvent e) {
if (SearchResults.insideVisibleArea(myEditor, myCursor.getPrimaryRange())) {
showReplacementPreview();
final VisibleAreaListener visibleAreaListener = this;
myEditor.getScrollingModel().removeVisibleAreaListener(visibleAreaListener);
myVisibleAreaListenersToRemove.remove(visibleAreaListener);
}
}
};
myEditor.getScrollingModel().addVisibleAreaListener(visibleAreaListener);
myVisibleAreaListenersToRemove.add(visibleAreaListener);
}
return new RelativePoint(myEditor.getContentComponent(), point);
}
}
}
@@ -0,0 +1,119 @@
package com.intellij.find.impl.livePreview;
import com.intellij.find.FindManager;
import com.intellij.find.FindModel;
import com.intellij.find.FindUtil;
import com.intellij.find.impl.FindResultImpl;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.util.Alarm;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
public class LivePreviewControllerBase implements LivePreview.Delegate, FindUtil.ReplaceDelegate {
private static final String EMPTY_STRING_DISPLAY_TEXT = "<Empty string>";
private static final int USER_ACTIVITY_TRIGGERING_DELAY = 300;
private int myUserActivityDelay = USER_ACTIVITY_TRIGGERING_DELAY;
private final Alarm myLivePreviewAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
private SearchResults mySearchResults;
private LivePreview myLivePreview;
public LivePreviewControllerBase(SearchResults searchResults, LivePreview livePreview) {
mySearchResults = searchResults;
myLivePreview = livePreview;
myLivePreview.setDelegate(this);
}
public int getUserActivityDelay() {
return myUserActivityDelay;
}
public void setUserActivityDelay(int userActivityDelay) {
myUserActivityDelay = userActivityDelay;
}
public void updateInBackground(final FindModel findModel) {
myLivePreviewAlarm.cancelAllRequests();
if (findModel == null) return;
Runnable request = new Runnable() {
@Override
public void run() {
mySearchResults.updateThreadSafe(findModel);
}
};
if (ApplicationManager.getApplication().isUnitTestMode()) {
request.run();
} else {
myLivePreviewAlarm.addRequest(request, myUserActivityDelay);
}
}
@Override
public String getReplacementPreviewText(Editor editor, LiveOccurrence liveOccurrence) {
String foundString = editor.getDocument().getText(liveOccurrence.getPrimaryRange());
String documentText = editor.getDocument().getText();
FindModel currentModel = mySearchResults.getFindModel();
String stringToReplace = null;
if (currentModel != null) {
if (currentModel.isReplaceState()) {
FindManager findManager = FindManager.getInstance(editor.getProject());
try {
stringToReplace = findManager.getStringToReplace(foundString, currentModel,
liveOccurrence.getPrimaryRange().getStartOffset(), documentText);
}
catch (FindManager.MalformedReplacementStringException e) {
return null;
}
if (stringToReplace != null && stringToReplace.isEmpty()) {
stringToReplace = EMPTY_STRING_DISPLAY_TEXT;
}
}
}
return stringToReplace;
}
@Nullable
@Override
public TextRange performReplace(final LiveOccurrence occurrence, final String replacement, final Editor editor) {
TextRange range = occurrence.getPrimaryRange();
FindModel findModel = mySearchResults.getFindModel();
try {
return FindUtil.doReplace(editor.getProject(), editor.getDocument(), findModel, new FindResultImpl(range.getStartOffset(), range.getEndOffset()),
FindManager.getInstance(editor.getProject()).getStringToReplace(editor.getDocument().getText(range), findModel),
true, new ArrayList<Pair<TextRange, String>>());
}
catch (FindManager.MalformedReplacementStringException e) {
/**/
}
mySearchResults.updateThreadSafe(findModel);
return null;
}
@Override
public void performReplaceAll(Editor e) {
FindUtil.replace(e.getProject(), e, 0, mySearchResults.getFindModel(), this);
}
@Override
public void getFocusBack() {}
@Override
public boolean shouldReplace(TextRange range, String replace) {
for (LiveOccurrence o : mySearchResults.getExcluded()) {
TextRange primaryRange = o.getPrimaryRange();
if (primaryRange.equals(range)) {
return false;
}
}
return true;
}
}
@@ -0,0 +1,125 @@
package com.intellij.find.impl.livePreview;
import com.intellij.ide.IdeTooltipManager;
import com.intellij.openapi.util.SystemInfo;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ReplacementView extends JPanel {
private String myReplacement;
private LiveOccurrence myOccurrence;
private JButton myStatusButton;
public interface Delegate {
void performReplacement(LiveOccurrence occurrence, String replacement);
void performReplaceAll();
boolean isExcluded(LiveOccurrence occurrence);
void exclude(LiveOccurrence occurrence);
}
private Delegate myDelegate;
public Delegate getDelegate() {
return myDelegate;
}
public void setDelegate(Delegate delegate) {
this.myDelegate = delegate;
updateStatusLabel();
}
@Override
protected void paintComponent(Graphics graphics) {
}
public ReplacementView(final String replacement, final LiveOccurrence occurrence) {
myReplacement = replacement;
myOccurrence = occurrence;
setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
JLabel jLabel = new JLabel(replacement);
jLabel.setForeground(Color.WHITE);
JPanel labelPane = new JPanel(new FlowLayout()) {
@Override
protected void paintComponent(Graphics graphics) {}
};
labelPane.setAlignmentX(Component.LEFT_ALIGNMENT);
labelPane.add(jLabel);
c.fill = GridBagConstraints.HORIZONTAL;
c.gridx = c.gridy = 0;
c.gridwidth = 2;
c.gridheight = 1;
add(labelPane, c);
JButton replace = new JButton("Replace");
replace.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (myDelegate != null) {
myDelegate.performReplacement(occurrence, replacement);
}
}
});
replace.setPreferredSize(new Dimension(80, 20));
replace.setMnemonic('R');
JPanel buttonsPane = new JPanel(){
@Override
protected void paintComponent(Graphics graphics) { }
};
JButton replaceAllButton = new JButton("Replace all");
replaceAllButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (myDelegate != null) {
myDelegate.performReplaceAll();
}
}
});
replaceAllButton.setPreferredSize(new Dimension(100, 20));
replaceAllButton.setMnemonic('a');
myStatusButton = new JButton("Exclude");
myStatusButton.setPreferredSize(new Dimension(80, 20));
myStatusButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
myDelegate.exclude(myOccurrence);
updateStatusLabel();
}
});
myStatusButton.setMnemonic('l');
buttonsPane.add(myStatusButton);
setBackground(IdeTooltipManager.GRAPHITE_COLOR);
if (SystemInfo.isMac) {
Font f = getFont();
setFont(f.deriveFont(f.getStyle(), f.getSize() - 4));
}
buttonsPane.add(replace);
buttonsPane.add(replaceAllButton);
c.gridx = 0;
c.gridy = 1;
c.gridwidth = 3;
c.gridheight = 1;
add(buttonsPane, c);
setAlignmentX(Component.LEFT_ALIGNMENT);
}
private void updateStatusLabel() {
myStatusButton.setText(myDelegate.isExcluded(myOccurrence) ? "Include" : "Exclude");
myStatusButton.repaint();
}
}
@@ -0,0 +1,328 @@
package com.intellij.find.impl.livePreview;
import com.intellij.find.FindManager;
import com.intellij.find.FindModel;
import com.intellij.find.FindResult;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.*;
import java.util.List;
public class SearchResults {
private int myActualFound = 0;
private List<SearchResultsListener> myListeners = new ArrayList<SearchResultsListener>();
private LiveOccurrence myCursor;
private List<LiveOccurrence> myOccurrences = new ArrayList<LiveOccurrence>();
private Set<LiveOccurrence> myExcluded = new HashSet<LiveOccurrence>();
private Editor myEditor;
private FindModel myFindModel;
private int myMatchesLimit = 100;
public SearchResults(Editor editor) {
myEditor = editor;
}
public int getActualFound() {
return myActualFound;
}
public boolean hasMatches() {
return !getOccurrences().isEmpty();
}
public FindModel getFindModel() {
return myFindModel;
}
public boolean isExcluded(LiveOccurrence occurrence) {
return myExcluded.contains(occurrence);
}
public void exclude(LiveOccurrence occurrence) {
if (myExcluded.contains(occurrence)) {
myExcluded.remove(occurrence);
} else {
myExcluded.add(occurrence);
}
notifyChanged();
}
public Set<LiveOccurrence> getExcluded() {
return myExcluded;
}
public interface SearchResultsListener {
void searchResultsUpdated(SearchResults sr);
void editorChanged(SearchResults sr, Editor oldEditor);
void cursorMoved();
}
public void addListener(SearchResultsListener srl) {
myListeners.add(srl);
}
public void removeListener(SearchResultsListener srl) {
myListeners.remove(srl);
}
public int getMatchesLimit() {
return myMatchesLimit;
}
public void setMatchesLimit(int matchesLimit) {
myMatchesLimit = matchesLimit;
}
public LiveOccurrence getCursor() {
return myCursor;
}
public List<LiveOccurrence> getOccurrences() {
return myOccurrences;
}
public Project getProject() {
return myEditor.getProject();
}
public synchronized void setEditor(Editor editor) {
Editor oldOne = myEditor;
myEditor = editor;
notifyEditorChanged(oldOne);
}
private void notifyEditorChanged(Editor oldOne) {
for (SearchResultsListener listener : myListeners) {
listener.editorChanged(this, oldOne);
}
}
public synchronized Editor getEditor() {
return myEditor;
}
private static void findResultsToOccurrences(ArrayList<FindResult> results, Collection<LiveOccurrence> occurrences) {
for (FindResult r : results) {
LiveOccurrence occurrence = new LiveOccurrence();
occurrence.setPrimaryRange(r);
occurrences.add(occurrence);
}
}
public void updateThreadSafe(final FindModel findModel) {
final ArrayList<LiveOccurrence> occurrences = new ArrayList<LiveOccurrence>();
final Editor editor = getEditor();
final ArrayList<FindResult> results = new ArrayList<FindResult>();
if (findModel != null) {
TextRange r = findModel.isGlobal() ? new TextRange(0, Integer.MAX_VALUE) :
new TextRange(editor.getSelectionModel().getSelectionStart(),
editor.getSelectionModel().getSelectionEnd());
if (r.getLength() == 0) {
r = new TextRange(0, Integer.MAX_VALUE);
}
int offset = r.getStartOffset();
VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(editor.getDocument());
while (true) {
FindManager findManager = FindManager.getInstance(editor.getProject());
FindResult result = findManager.findString(editor.getDocument().getCharsSequence(), offset, findModel, virtualFile);
if (!result.isStringFound()) break;
int newOffset = result.getEndOffset();
if (offset == newOffset || result.getEndOffset() > r.getEndOffset()) break;
offset = newOffset;
results.add(result);
if (results.size() > myMatchesLimit) break;
}
if (results.size() < myMatchesLimit) {
findResultsToOccurrences(results, occurrences);
}
}
final Runnable r = new Runnable() {
@Override
public void run() {
searchCompleted(occurrences, results.size(), editor, findModel);
}
};
if (!ApplicationManager.getApplication().isUnitTestMode()) {
ApplicationManager.getApplication().invokeLater(r);
} else {
r.run();
}
}
private void searchCompleted(List<LiveOccurrence> occurrences, int size, Editor editor, FindModel findModel) {
if (editor == getEditor()) {
myOccurrences = occurrences;
final TextRange oldCursorRange = myCursor != null ? myCursor.getPrimaryRange() : null;
Collections.sort(myOccurrences, new Comparator<LiveOccurrence>() {
@Override
public int compare(LiveOccurrence liveOccurrence, LiveOccurrence liveOccurrence1) {
return liveOccurrence.getPrimaryRange().getStartOffset() - liveOccurrence1.getPrimaryRange().getStartOffset();
}
});
updateCursor(oldCursorRange);
myFindModel = findModel;
myActualFound = size;
notifyChanged();
if (oldCursorRange == null) {
notifyCursorMoved();
}
}
}
private void updateCursor(TextRange oldCursorRange) {
if (!tryToRepairOldCursor(oldCursorRange)) {
LiveOccurrence afterCaret = firstOccurrenceAfterCaret();
if (afterCaret != null) {
myCursor = afterCaret;
} else {
LiveOccurrence occurrence = firstVisibleOccurrence();
myCursor = occurrence;
}
}
}
private void notifyChanged() {
for (SearchResultsListener listener : myListeners) {
listener.searchResultsUpdated(this);
}
}
static boolean insideVisibleArea(Editor e, TextRange r) {
Rectangle visibleArea = e.getScrollingModel().getVisibleArea();
Point point = e.logicalPositionToXY(e.offsetToLogicalPosition(r.getStartOffset()));
return visibleArea.contains(point);
}
@Nullable
private LiveOccurrence firstVisibleOccurrence() {
int offset = Integer.MAX_VALUE;
LiveOccurrence firstOccurrence = null;
LiveOccurrence firstVisibleOccurrence = null;
for (LiveOccurrence o : getOccurrences()) {
if (insideVisibleArea(myEditor, o.getPrimaryRange())) {
if (firstVisibleOccurrence == null || o.getPrimaryRange().getStartOffset() < firstVisibleOccurrence.getPrimaryRange().getStartOffset()) {
firstVisibleOccurrence = o;
}
}
if (o.getPrimaryRange().getStartOffset() < offset) {
offset = o.getPrimaryRange().getStartOffset();
firstOccurrence = o;
}
}
return firstVisibleOccurrence != null ? firstVisibleOccurrence : firstOccurrence;
}
@Nullable
private LiveOccurrence firstOccurrenceAfterCaret() {
LiveOccurrence afterCaret = null;
int caret = myEditor.getCaretModel().getOffset();
for (LiveOccurrence occurrence : getOccurrences()) {
if (occurrence.getPrimaryRange().getStartOffset() >= caret) {
if (afterCaret == null || occurrence.getPrimaryRange().getStartOffset() < afterCaret.getPrimaryRange().getStartOffset() ) {
afterCaret = occurrence;
}
}
}
return afterCaret;
}
private boolean tryToRepairOldCursor(TextRange oldCursorRange) {
if (oldCursorRange == null) return false;
LiveOccurrence mayBeOldCursor = null;
for (LiveOccurrence searchResult : getOccurrences()) {
if (searchResult.getPrimaryRange().intersects(oldCursorRange)) {
mayBeOldCursor = searchResult;
break;
}
}
if (mayBeOldCursor != null) {
myCursor = mayBeOldCursor;
return true;
}
return false;
}
@Nullable
private LiveOccurrence prevOccurrence(LiveOccurrence o) {
for (int i = 0; i < getOccurrences().size(); ++i) {
if (o == getOccurrences().get(i)) {
if (i > 0) {
return getOccurrences().get(i - 1);
}
}
}
return null;
}
@Nullable
private LiveOccurrence nextOccurrence(LiveOccurrence o) {
boolean found = false;
for (LiveOccurrence occurrence : getOccurrences()) {
if (found) {
return occurrence;
}
if (o == occurrence) {
found = true;
}
}
return null;
}
public void prevOccurrence() {
LiveOccurrence prev = prevOccurrence(myCursor);
if (prev == null && !getOccurrences().isEmpty()) {
prev = getOccurrences().get(getOccurrences().size() - 1);
}
moveCursorTo(prev);
}
public void nextOccurrence() {
LiveOccurrence next = nextOccurrence(myCursor);
if (next == null && !getOccurrences().isEmpty()) {
next = getOccurrences().get(0);
}
moveCursorTo(next);
}
public void moveCursorTo(LiveOccurrence next) {
if (next != null) {
setCursor(next);
}
}
private void setCursor(LiveOccurrence liveOccurrence) {
myCursor = liveOccurrence;
notifyCursorMoved();
}
private void notifyCursorMoved() {
for (SearchResultsListener listener : myListeners) {
listener.cursorMoved();
}
}
}