blob: 8e3cdcdd85678016b16b28164f4baab6b84fb396 [file] [log] [blame]
/*
* Copyright 2000-2009 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.diagnostic.Logger;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.editor.ex.RangeMarkerEx;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.util.Processor;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
public class RangeMarkerImpl extends UserDataHolderBase implements RangeMarkerEx, MutableInterval {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.RangeMarkerImpl");
protected final DocumentEx myDocument;
protected RangeMarkerTree.RMNode<RangeMarkerEx> myNode;
private final long myId;
private static final StripedIDGenerator counter = new StripedIDGenerator();
protected RangeMarkerImpl(@NotNull DocumentEx document, int start, int end, boolean register) {
this(document, start, end, register, false, false);
}
private RangeMarkerImpl(@NotNull DocumentEx document, int start, int end, boolean register, boolean greedyToLeft, boolean greedyToRight) {
if (start < 0) {
throw new IllegalArgumentException("Wrong start: " + start+"; end="+end);
}
if (end > document.getTextLength()) {
throw new IllegalArgumentException("Wrong end: " + end+ "; document length="+document.getTextLength()+"; start="+start);
}
if (start > end){
throw new IllegalArgumentException("start > end: start=" + start+"; end="+end);
}
myDocument = document;
myId = counter.next();
if (register) {
registerInTree(start, end, greedyToLeft, greedyToRight, 0);
}
}
protected void registerInTree(int start, int end, boolean greedyToLeft, boolean greedyToRight, int layer) {
myDocument.registerRangeMarker(this, start, end, greedyToLeft, greedyToRight, layer);
}
protected boolean unregisterInTree() {
if (!isValid()) return false;
IntervalTreeImpl tree = myNode.getTree();
tree.checkMax(true);
boolean b = myDocument.removeRangeMarker(this);
tree.checkMax(true);
return b;
}
@Override
public long getId() {
return myId;
}
@Override
public void dispose() {
unregisterInTree();
}
@Override
public int getStartOffset() {
RangeMarkerTree.RMNode node = myNode;
return intervalStart() + (node == null ? 0 : node.computeDeltaUpToRoot());
}
@Override
public int getEndOffset() {
RangeMarkerTree.RMNode node = myNode;
return intervalEnd() + (node == null ? 0 : node.computeDeltaUpToRoot());
}
public void invalidate(@NotNull final Object reason) {
setValid(false);
RangeMarkerTree.RMNode<RangeMarkerEx> node = myNode;
if (node != null) {
node.processAliveKeys(new Processor<RangeMarkerEx>() {
@Override
public boolean process(RangeMarkerEx markerEx) {
myNode.getTree().reportInvalidation(markerEx, reason);
return true;
}
});
}
}
@Override
@NotNull
public DocumentEx getDocument() {
return myDocument;
}
// fake method to simplify setGreedyToLeft/right methods. overridden in RangeHighlighter
public int getLayer() {
return 0;
}
@Override
public void setGreedyToLeft(final boolean greedy) {
if (!isValid() || greedy == isGreedyToLeft()) return;
myNode.getTree().changeData(this, getStartOffset(), getEndOffset(), greedy, isGreedyToRight(), getLayer());
}
@Override
public void setGreedyToRight(final boolean greedy) {
if (!isValid() || greedy == isGreedyToRight()) return;
myNode.getTree().changeData(this, getStartOffset(), getEndOffset(), isGreedyToLeft(), greedy, getLayer());
}
@Override
public boolean isGreedyToLeft() {
RangeMarkerTree.RMNode node = myNode;
return node != null && node.isGreedyToLeft();
}
@Override
public boolean isGreedyToRight() {
RangeMarkerTree.RMNode node = myNode;
return node != null && node.isGreedyToRight();
}
@Override
public final void documentChanged(DocumentEvent e) {
int oldStart = intervalStart();
int oldEnd = intervalEnd();
int docLength = myDocument.getTextLength();
if (!isValid()) {
LOG.error("Invalid range marker "+ (isGreedyToLeft() ? "[" : "(") + oldStart + ", " + oldEnd + (isGreedyToRight() ? "]" : ")") +
". Event = " + e + ". Doc length=" + docLength + "; "+getClass());
return;
}
if (intervalStart() > intervalEnd() || intervalStart() < 0 || intervalEnd() > docLength - e.getNewLength() + e.getOldLength()) {
LOG.error("RangeMarker" + (isGreedyToLeft() ? "[" : "(") + oldStart + ", " + oldEnd + (isGreedyToRight() ? "]" : ")") +
" is invalid before update. Event = " + e + ". Doc length=" + docLength + "; "+getClass());
invalidate(e);
return;
}
changedUpdateImpl(e);
if (isValid() && (intervalStart() > intervalEnd() || intervalStart() < 0 || intervalEnd() > docLength)) {
LOG.error("Update failed. Event = " + e + ". " +
"old doc length=" + docLength + "; real doc length = "+myDocument.getTextLength()+
"; "+getClass()+"." +
" After update: '"+this+"'");
invalidate(e);
}
}
protected void changedUpdateImpl(DocumentEvent e) {
if (!isValid()) return;
// Process if one point.
if (intervalStart() == intervalEnd()) {
processIfOnePoint(e);
return;
}
final int offset = e.getOffset();
final int oldLength = e.getOldLength();
final int newLength = e.getNewLength();
// changes after the end.
if (intervalEnd() < offset || !isGreedyToRight() && intervalEnd() == offset) {
return;
}
// changes before start
if (intervalStart() > offset + oldLength || !isGreedyToLeft() && intervalStart() == offset + oldLength) {
setIntervalStart(intervalStart() + newLength - oldLength);
setIntervalEnd(intervalEnd() + newLength - oldLength);
return;
}
// Changes inside marker's area. Expand/collapse.
if (intervalStart() <= offset && intervalEnd() >= offset + oldLength) {
setIntervalEnd(intervalEnd() + newLength - oldLength);
return;
}
// At this point we either have (myStart xor myEnd inside changed area) or whole area changed.
// Replacing prefix or suffix...
if (intervalStart() >= offset && intervalStart() <= offset + oldLength && intervalEnd() > offset + oldLength) {
setIntervalEnd(intervalEnd() + newLength - oldLength);
setIntervalStart(offset + newLength);
return;
}
if (intervalEnd() >= offset && intervalEnd() <= offset + oldLength && intervalStart() < offset) {
setIntervalEnd(offset);
return;
}
invalidate(e);
}
private void processIfOnePoint(DocumentEvent e) {
int offset = e.getOffset();
int oldLength = e.getOldLength();
int oldEnd = offset + oldLength;
if (offset < intervalStart() && intervalStart() < oldEnd) {
invalidate(e);
return;
}
if (offset == intervalStart() && oldLength == 0 && isGreedyToRight()) {
setIntervalEnd(intervalEnd() + e.getNewLength());
return;
}
if (intervalStart() > oldEnd || intervalStart() == oldEnd && oldLength > 0) {
setIntervalStart(intervalStart() + e.getNewLength() - oldLength);
setIntervalEnd(intervalEnd() + e.getNewLength() - oldLength);
}
}
@NonNls
public String toString() {
return "RangeMarker" + (isGreedyToLeft() ? "[" : "(") + (isValid() ? "valid" : "invalid") + "," + getStartOffset() + "," + getEndOffset() + (
isGreedyToRight() ? "]" : ")") + " " + getId();
}
@Override
public int setIntervalStart(int start) {
return myNode.setIntervalStart(start);
}
@Override
public int setIntervalEnd(int end) {
return myNode.setIntervalEnd(end);
}
@Override
public boolean isValid() {
RangeMarkerTree.RMNode node = myNode;
return node != null && node.isValid();
}
@Override
public boolean setValid(boolean value) {
RangeMarkerTree.RMNode node = myNode;
return node == null || node.setValid(value);
}
@Override
public int intervalStart() {
RangeMarkerTree.RMNode node = myNode;
if (node == null) {
return -1;
}
return node.intervalStart();
}
@Override
public int intervalEnd() {
RangeMarkerTree.RMNode node = myNode;
if (node == null) {
return -1;
}
return node.intervalEnd();
}
}