mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
search/replace refactoring. added exclude function. fixed unexpected scrolling when document changes. multiline occurrences highlithting
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+6
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+119
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user