/* * 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(); } }