/*
* Copyright 2000-2015 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.openapi.editor.impl;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.editor.impl.event.DocumentEventImpl;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.util.TextRange;
import com.intellij.util.ObjectUtils;
import com.intellij.util.diff.FilesTooBigForDiffException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* This class is an extension to range marker that tries to restore its range even in situations when target text referenced by it
* is replaced.
* <p/>
* Example: consider that the user selects all text at editor (Ctrl+A), copies it to the buffer (Ctrl+C) and performs paste (Ctrl+V).
* All document text is replaced then but in essence it's the same, hence, we may want particular range markers to be still valid.
*
* @author max
*/
class PersistentRangeMarker extends RangeMarkerImpl {
private LinesCols myLinesCols;
PersistentRangeMarker(DocumentEx document, int startOffset, int endOffset, boolean register) {
super(document, startOffset, endOffset, register);
myLinesCols = ObjectUtils.assertNotNull(storeLinesAndCols(document, getStartOffset(), getEndOffset()));
}
@Nullable
static LinesCols storeLinesAndCols(Document myDocument, int startOffset, int endOffset) {
int myStartLine;
int myStartColumn;
int myEndLine;
int myEndColumn;
// document might have been changed already
if (startOffset <= myDocument.getTextLength()) {
myStartLine = myDocument.getLineNumber(startOffset);
myStartColumn = startOffset - myDocument.getLineStartOffset(myStartLine);
if (myStartColumn < 0) {
return null;
}
}
else {
return null;
}
if (endOffset <= myDocument.getTextLength()) {
myEndLine = myDocument.getLineNumber(endOffset);
myEndColumn = endOffset - myDocument.getLineStartOffset(myEndLine);
if (myEndColumn < 0) {
return null;
}
}
else {
return null;
}
return new LinesCols(myStartLine, myStartColumn, myEndLine, myEndColumn);
}
@Nullable
static Pair<TextRange, LinesCols> translateViaDiff(@NotNull final DocumentEventImpl event, @NotNull LinesCols linesCols) {
try {
int myStartLine = event.translateLineViaDiffStrict(linesCols.myStartLine);
Document document = event.getDocument();
if (myStartLine < 0 || myStartLine >= document.getLineCount()) {
return null;
}
int start = document.getLineStartOffset(myStartLine) + linesCols.myStartColumn;
if (start >= document.getTextLength()) return null;
int myEndLine = event.translateLineViaDiffStrict(linesCols.myEndLine);
if (myEndLine < 0 || myEndLine >= document.getLineCount()) {
return null;
}
int end = document.getLineStartOffset(myEndLine) + linesCols.myEndColumn;
if (end > document.getTextLength() || end < start) return null;
if (end > event.getDocument().getTextLength() ||
myEndLine < myStartLine ||
myStartLine == myEndLine && linesCols.myEndColumn < linesCols.myStartColumn ||
event.getDocument().getLineCount() < myEndLine) {
return null;
}
return Pair.create(new TextRange(start, end), new LinesCols(myStartLine, linesCols.myStartColumn, myEndLine, linesCols.myEndColumn));
}
catch (FilesTooBigForDiffException e) {
return null;
}
}
@Override
protected void changedUpdateImpl(@NotNull DocumentEvent e) {
if (!isValid()) return;
Pair<TextRange, LinesCols> pair =
applyChange(e, this, intervalStart(), intervalEnd(), isGreedyToLeft(), isGreedyToRight(), myLinesCols);
if (pair == null) {
invalidate(e);
return;
}
setIntervalStart(pair.first.getStartOffset());
setIntervalEnd(pair.first.getEndOffset());
myLinesCols = pair.second;
}
@Nullable
private static Pair<TextRange, LinesCols> applyChange(DocumentEvent event, Segment range, int intervalStart, int intervalEnd, boolean greedyLeft, boolean greedyRight, LinesCols linesCols) {
final boolean shouldTranslateViaDiff = PersistentRangeMarkerUtil.shouldTranslateViaDiff(event, range.getStartOffset(), range.getEndOffset());
Pair<TextRange, LinesCols> translated = null;
if (shouldTranslateViaDiff) {
translated = translateViaDiff((DocumentEventImpl)event, linesCols);
}
if (translated == null) {
TextRange fallback = applyChange(event, intervalStart, intervalEnd, greedyLeft, greedyRight);
if (fallback == null) return null;
LinesCols lc = storeLinesAndCols(event.getDocument(), fallback.getStartOffset(), fallback.getEndOffset());
if (lc == null) return null;
translated = Pair.create(fallback, lc);
}
return translated;
}
@Override
public String toString() {
return "PersistentRangeMarker" +
(isGreedyToLeft() ? "[" : "(") +
(isValid() ? "valid" : "invalid") + "," + getStartOffset() + "," + getEndOffset() +
" " + myLinesCols +
(isGreedyToRight() ? "]" : ")");
}
static class LinesCols {
private final int myStartLine;
private final int myStartColumn;
private final int myEndLine;
private final int myEndColumn;
LinesCols(int startLine, int startColumn, int endLine, int endColumn) {
myStartLine = startLine;
myStartColumn = startColumn;
myEndLine = endLine;
myEndColumn = endColumn;
}
@Override
public String toString() {
return myStartLine + ":" + myStartColumn + "-" + myEndLine + ":" + myEndColumn;
}
}
}