blob: a894781d5cb1868f688ea8ce048d9ad3d885e3bb [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.find.impl.livePreview;
import com.intellij.find.*;
import com.intellij.find.impl.FindResultImpl;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.SelectionEvent;
import com.intellij.openapi.editor.event.SelectionListener;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.ReadonlyStatusHandler;
import com.intellij.util.Alarm;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
public class LivePreviewController implements LivePreview.Delegate, FindUtil.ReplaceDelegate {
private static final Logger LOG = Logger.getInstance("#com.intellij.find.impl.livePreview.LivePreviewController");
public static final int USER_ACTIVITY_TRIGGERING_DELAY = 30;
public static final int MATCHES_LIMIT = 10000;
protected EditorSearchComponent myComponent;
private int myUserActivityDelay = USER_ACTIVITY_TRIGGERING_DELAY;
private final Alarm myLivePreviewAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
protected SearchResults mySearchResults;
private LivePreview myLivePreview;
private final boolean myReplaceDenied = false;
private boolean mySuppressUpdate = false;
private boolean myTrackingDocument;
private boolean myChanged;
private boolean myListeningSelection = false;
private final SelectionListener mySelectionListener = new SelectionListener() {
@Override
public void selectionChanged(SelectionEvent e) {
smartUpdate();
}
};
private boolean myDisposed;
public void setTrackingSelection(boolean b) {
if (b) {
if (!myListeningSelection) {
getEditor().getSelectionModel().addSelectionListener(mySelectionListener);
}
} else {
if (myListeningSelection) {
getEditor().getSelectionModel().removeSelectionListener(mySelectionListener);
}
}
myListeningSelection = b;
}
private final DocumentAdapter myDocumentListener = new DocumentAdapter() {
@Override
public void documentChanged(final DocumentEvent e) {
if (!myTrackingDocument) {
myChanged = true;
return;
}
if (!mySuppressUpdate) {
smartUpdate();
} else {
mySuppressUpdate = false;
}
}
};
private void smartUpdate() {
myLivePreview.inSmartUpdate();
updateInBackground(mySearchResults.getFindModel(), false);
}
public void moveCursor(SearchResults.Direction direction) {
if (direction == SearchResults.Direction.UP) {
mySearchResults.prevOccurrence(false);
} else {
mySearchResults.nextOccurrence(false);
}
}
public boolean isReplaceDenied() {
return myReplaceDenied;
}
public LivePreviewController(SearchResults searchResults, @Nullable EditorSearchComponent component) {
mySearchResults = searchResults;
myComponent = component;
getEditor().getDocument().addDocumentListener(myDocumentListener);
}
public int getUserActivityDelay() {
return myUserActivityDelay;
}
public void setUserActivityDelay(int userActivityDelay) {
myUserActivityDelay = userActivityDelay;
}
public void updateInBackground(FindModel findModel, final boolean allowedToChangedEditorSelection) {
final int stamp = mySearchResults.getStamp();
myLivePreviewAlarm.cancelAllRequests();
if (findModel == null) return;
final boolean unitTestMode = ApplicationManager.getApplication().isUnitTestMode();
final FindModel copy = new FindModel();
copy.copyFrom(findModel);
Runnable request = new Runnable() {
@Override
public void run() {
if (myDisposed) return;
Project project = mySearchResults.getProject();
if (project != null && project.isDisposed()) return;
mySearchResults.updateThreadSafe(copy, allowedToChangedEditorSelection, null, stamp);
}
};
if (unitTestMode) {
request.run();
} else {
myLivePreviewAlarm.addRequest(request, myUserActivityDelay);
}
}
@Override
public String getStringToReplace(@NotNull Editor editor, @Nullable FindResult findResult) {
if (findResult == null) {
return null;
}
String foundString = editor.getDocument().getText(findResult);
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,
findResult.getStartOffset(), documentText);
}
catch (FindManager.MalformedReplacementStringException e) {
return null;
}
}
}
return stringToReplace;
}
@Nullable
public TextRange performReplace(final FindResult occurrence, final String replacement, final Editor editor) {
if (myReplaceDenied || !ReadonlyStatusHandler.ensureDocumentWritable(editor.getProject(), editor.getDocument())) return null;
FindModel findModel = mySearchResults.getFindModel();
TextRange result = FindUtil.doReplace(editor.getProject(),
editor.getDocument(),
findModel,
new FindResultImpl(occurrence.getStartOffset(), occurrence.getEndOffset()),
replacement,
true,
new ArrayList<Pair<TextRange, String>>());
myLivePreview.inSmartUpdate();
mySearchResults.updateThreadSafe(findModel, true, result, mySearchResults.getStamp());
return result;
}
public void performReplaceAll(Editor e) {
if (!ReadonlyStatusHandler.ensureDocumentWritable(e.getProject(), e.getDocument())) return;
if (mySearchResults.getFindModel() != null) {
final FindModel copy = new FindModel();
copy.copyFrom(mySearchResults.getFindModel());
final SelectionModel selectionModel = mySearchResults.getEditor().getSelectionModel();
final int offset;
if ((!selectionModel.hasSelection() && !selectionModel.hasBlockSelection()) || copy.isGlobal()) {
copy.setGlobal(true);
offset = 0;
} else {
offset = selectionModel.getBlockSelectionStarts()[0];
}
FindUtil.replace(e.getProject(), e, offset, copy, this);
}
}
@Override
public boolean shouldReplace(TextRange range, String replace) {
for (RangeMarker r : mySearchResults.getExcluded()) {
if (TextRange.areSegmentsEqual(r, range)) {
return false;
}
}
return true;
}
public boolean canReplace() {
if (mySearchResults != null && mySearchResults.getCursor() != null &&
!isReplaceDenied() && (mySearchResults.getFindModel().isGlobal() ||
!mySearchResults.getEditor().getSelectionModel()
.hasBlockSelection()) ) {
final String replacement = getStringToReplace(getEditor(), mySearchResults.getCursor());
return replacement != null;
}
return false;
}
private Editor getEditor() {
return mySearchResults.getEditor();
}
public void performReplace() {
mySuppressUpdate = true;
String replacement = getStringToReplace(getEditor(), mySearchResults.getCursor());
if (replacement == null) {
return;
}
final TextRange textRange = performReplace(mySearchResults.getCursor(), replacement, getEditor());
if (textRange == null) {
mySuppressUpdate = false;
}
if (myComponent != null) {
myComponent.addTextToRecent(myComponent.getReplaceField());
myComponent.clearUndoInTextFields();
}
}
public void exclude() {
mySearchResults.exclude(mySearchResults.getCursor());
}
public void performReplaceAll() {
performReplaceAll(getEditor());
}
public void setTrackingDocument(boolean trackingDocument) {
myTrackingDocument = trackingDocument;
}
public void setLivePreview(LivePreview livePreview) {
if (myLivePreview != null) {
myLivePreview.dispose();
myLivePreview.setDelegate(null);
}
myLivePreview = livePreview;
if (myLivePreview != null) {
myLivePreview.setDelegate(this);
}
}
public void dispose() {
if (myDisposed) return;
myLivePreview.cleanUp();
off();
mySearchResults.dispose();
getEditor().getDocument().removeDocumentListener(myDocumentListener);
myDisposed = true;
}
public void on() {
if (myDisposed) return;
mySearchResults.setMatchesLimit(MATCHES_LIMIT);
setTrackingDocument(true);
if (myChanged) {
mySearchResults.clear();
myChanged = false;
}
setLivePreview(new LivePreview(mySearchResults));
}
public void off() {
if (myDisposed) return;
setTrackingDocument(false);
setLivePreview(null);
setTrackingSelection(false);
}
}